Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Las 1.3 and 1.4 read support. #16

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ os:
- linux
- osx
julia:
- 0.7
- 1.0
- nightly
matrix:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Julia package for reading and writing the LAS lidar format.

This is a pure Julia alternative to [LibLAS.jl](https://github.com/visr/LibLAS.jl) or [Laszip.jl](https://github.com/joa-quim/Laszip.jl). Currently only LAS versions 1.1 - 1.3 and point formats 0 - 3 are supported. For LAZ support see below.
This is a pure Julia alternative to [LibLAS.jl](https://github.com/visr/LibLAS.jl) or [Laszip.jl](https://github.com/joa-quim/Laszip.jl). Currently LAS versions 1.1 - 1.4 and point formats 0 - 10 are supported. For LAZ support see below.

If the file fits into memory, it can be loaded using

Expand Down
1 change: 1 addition & 0 deletions REQUIRE
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ ColorTypes 0.7
FixedPointNumbers 0.5
FileIO 1.0
GeometryTypes 0.6
StaticArrays
1 change: 0 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
environment:
matrix:
- julia_version: 0.7
- julia_version: 1.0
- julia_version: nightly

Expand Down
13 changes: 12 additions & 1 deletion src/LasIO.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,26 @@ using FileIO
using FixedPointNumbers
using ColorTypes
using GeometryTypes # for conversion
using StaticArrays
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUIRE needs to be updated for this


export

# Types
LasHeader,
LasVariableLengthRecord,
ExtendedLasVariableLengthRecord,
LasPoint,
LasPoint0,
LasPoint1,
LasPoint2,
LasPoint3,
LasPoint4,
LasPoint5,
LasPoint6,
LasPoint7,
LasPoint8,
LasPoint9,
LasPoint10,
PointVector,

# Functions on LasHeader
Expand Down Expand Up @@ -49,12 +58,14 @@ export
blue,
RGB

include("fixedstrings.jl")
include("meta.jl")
include("vlrs.jl")
include("header.jl")
include("meta.jl")
include("point.jl")
include("util.jl")
include("fileio.jl")
include("waveform.jl")
include("srs.jl")

function __init__()
Expand Down
68 changes: 55 additions & 13 deletions src/fileio.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
using Mmap

pointformats = Dict(
0x00 => LasPoint0,
0x01 => LasPoint1,
0x02 => LasPoint2,
0x03 => LasPoint3,
0x04 => LasPoint4,
0x05 => LasPoint5,
0x06 => LasPoint6,
0x07 => LasPoint7,
0x08 => LasPoint8,
0x09 => LasPoint9,
0x10 => LasPoint10
)

function pointformat(header::LasHeader)
id = header.data_format_id
if id == 0x00
return LasPoint0
elseif id == 0x01
return LasPoint1
elseif id == 0x02
return LasPoint2
elseif id == 0x03
return LasPoint3
if id in keys(pointformats)
return pointformats[id]
else
error("unsupported point format $(Int(id))")
end
Expand All @@ -28,8 +36,8 @@ end
function load(s::Base.AbstractPipe)
skiplasf(s)
header = read(s, LasHeader)

n = header.records_count
lv = VersionNumber(header.version_major, header.version_minor)
n = header.records_count_new
pointtype = pointformat(header)
pointdata = Vector{pointtype}(undef, n)
for i=1:n
Expand All @@ -41,9 +49,30 @@ end
function load(s::Stream{format"LAS"}; mmap=false)
skiplasf(s)
header = read(s, LasHeader)
lv = VersionNumber(header.version_major, header.version_minor)

n = header.records_count
n = header.records_count_new
pointtype = pointformat(header)
extra_bytes = header.data_record_length - sizeof(pointtype)

# Determine extra bytes
if extra_bytes != 0 && 4 in keys(header.variable_length_records)
ebs = header.variable_length_records[4].data # extra_byte structures
total_size = sum([sizeof(eb.data_type) for eb in ebs])

if total_size > extra_bytes
# this renders this VLR invalid according to spec
warn("Extra bytes mismatch, skipping all extra bytes.")
else
# this is allowed according to spec
total_size < extra_bytes && warn("There are undocumented extra bytes!")

# generate new point type structure to read
newfields = [(Symbol(eb.name), eb.data_type) for eb in ebs]
pointtype = gen_append_struct(pointtype, newfields)
extra_bytes -= total_size
end
end

if mmap
pointsize = Int(header.data_record_length)
Expand All @@ -53,7 +82,21 @@ function load(s::Stream{format"LAS"}; mmap=false)
pointdata = Vector{pointtype}(undef, n)
for i=1:n
pointdata[i] = read(s, pointtype)
extra_bytes > 0 && read(s, extra_bytes) # skip extra bytes
end
end

# Extended Variable Length Records for 1.3 and 1.4
if lv == v"1.3" && header.waveform_offset > 0
evlr = read(s, ExtendedLasVariableLengthRecord)
header.variable_length_records[evlr.record_id] = evlr
elseif lv == v"1.4" && header.n_evlr > 0
for i=1:header.n_evlr
evlr = read(s, ExtendedLasVariableLengthRecord)
header.variable_length_records[evlr.record_id] = evlr
end
else
nothing
end

header, pointdata
Expand Down Expand Up @@ -86,15 +129,14 @@ end

function save(s::Stream{format"LAS"}, header::LasHeader, pointdata::AbstractVector{<:LasPoint})
# checks
header_n = header.records_count
header_n = header.records_count_new
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here

n = length(pointdata)
msg = "number of records in header ($header_n) does not match data length ($n)"
@assert header_n == n msg

# write header
write(s, magic(format"LAS"))
write(s, header)

# write points
for p in pointdata
write(s, p)
Expand Down
57 changes: 57 additions & 0 deletions src/fixedstrings.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
FixedString{N<:Int}(str, truncate=false, nullterm=true)

A string type with a fixed maximum size `N` in bytes. This is useful for
serializing to and from binary data formats containing fixed length strings.
When constructing a FixedString, if `truncate` is true, the input string will
be truncated to fit into the number of bytes `N`. If `nullterm` is true, ensure
that the string has length strictly less than `N`, to fit in a single
terminating byte.

When a FixedString{N} is serialized using `read()` and `write()`, exactly `N`
bytes are read and written, with any padding being set to '0'.
"""
struct FixedString{N} <: AbstractString
str::String

function (::Type{FixedString{N}})(str::String) where N
n = sizeof(str)
n <= N || throw(ArgumentError("sizeof(str) = $n does not fit into a FixedString{$N}"))
return new{N}(str)
end
end

function (::Type{FixedString{N}})(str::AbstractString; nullterm=true, truncate=false) where N
maxbytes = nullterm ? N-1 : N
if sizeof(str) > maxbytes
truncate || throw(ArgumentError("sizeof(str) = $(sizeof(str)) too long for FixedString{$N}"))
strunc = String(str[1:maxbytes])
while sizeof(strunc) > maxbytes
strunc = strunc[1:end-1] # Needed for non-ascii chars
end
return FixedString{N}(String(strunc))
else
return FixedString{N}(String(str))
end
end

# Minimal AbstractString required interface
Base.sizeof(f::FixedString{N}) where {N} = N
Base.iterate(f::FixedString{N}) where {N} = iterate(f.str)
Base.iterate(f::FixedString{N}, state::Integer) where {N} = iterate(f.str, state)

# Be permissive by setting nullterm to false for reading by default.
function Base.read(io::IO, ::Type{FixedString{N}}; nullterm=false) where {N}
bytes = zeros(UInt8, N)
bytes = read!(io, bytes)
i = findfirst(isequal(0), bytes)
idx = i === nothing ? N : i - 1
FixedString{N}(String(bytes[1:idx]), nullterm=nullterm)
end

function Base.write(io::IO, f::FixedString{N}) where {N}
write(io, f.str)
for i=1:N-sizeof(f.str)
write(io, UInt8(0))
end
end
Loading