diff --git a/.travis.yml b/.travis.yml index 9ca9710..3dcd193 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: julia julia: - - 1.0 - - 1.1 - - 1.2 - 1.3 + - 1.4 - nightly diff --git a/Project.toml b/Project.toml index 15ed4ee..ec848fc 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "0.1.0" [deps] GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" +GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" diff --git a/src/GeoJSONTables.jl b/src/GeoJSONTables.jl index 061dcb8..fc0039f 100644 --- a/src/GeoJSONTables.jl +++ b/src/GeoJSONTables.jl @@ -1,19 +1,20 @@ module GeoJSONTables import JSON3, Tables, GeoInterface - +using GeometryBasics +using GeometryBasics.StructArrays struct FeatureCollection{T} <: AbstractVector{eltype(T)} json::T end -function read(source) +function read(source, master_way = false) fc = JSON3.read(source) features = get(fc, :features, nothing) - if get(fc, :type, nothing) == "FeatureCollection" && features isa JSON3.Array - FeatureCollection{typeof(features)}(features) - else - throw(ArgumentError("input source is not a GeoJSON FeatureCollection")) - end + if get(fc, :type, nothing) == "FeatureCollection" && features isa JSON3.Array + FeatureCollection{typeof(features)}(features) + else + throw(ArgumentError("input source is not a GeoJSON FeatureCollection")) + end end Tables.istable(::Type{<:FeatureCollection}) = true @@ -82,6 +83,6 @@ end Base.show(io::IO, ::MIME"text/plain", fc::FeatureCollection) = show(io, fc) Base.show(io::IO, ::MIME"text/plain", f::Feature) = show(io, f) -include("geointerface.jl") - +# include("geointerface.jl") +include("basics.jl") end # module diff --git a/src/basics.jl b/src/basics.jl new file mode 100644 index 0000000..258e13d --- /dev/null +++ b/src/basics.jl @@ -0,0 +1,144 @@ +function basicgeometry(fc::GeoJSONTables.FeatureCollection) + geom = [basicgeometry(feat) for feat in fc] + structarray(geom) +end + +function basicgeometry(f::GeoJSONTables.Feature) + geom = geometry(f) + prop = properties(f) + + t = geom.type + k = Tuple(keys(prop)) + v = Tuple(values(prop)) + tup = NamedTuple{k}(v) + + if t == "Point" + # geom.coordinates = pt(a) + return basicgeometry(Point, geom.coordinates, tup) + elseif t == "LineString" + return basicgeometry(LineString, geom.coordinates, tup) + elseif t == "Polygon" + return basicgeometry(Polygon, geom.coordinates, tup) + elseif t == "MultiPoint" + return basicgeometry(MultiPoint, geom.coordinates, tup) + elseif t == "MultiLineString" + return basicgeometry(MultiLineString, geom.coordinates, tup) + elseif t == "MultiPolygon" + return basicgeometry(MultiPolygon, geom.coordinates, tup) + elseif t == "FeatureCollection" + return basicgeometry(FeatureCollection, geom.geometries, tup) + else + throw(ArgumentError("Unknown geometry type")) + end +end + +function basicgeometry(::Type{Point}, g, tup::NamedTuple) + pt = Point{2, Float64}(g) + return GeometryBasics.Meta(pt, tup) +end + +function basicgeometry(::Type{Point}, g) + return Point{2, Float64}(g) +end + +function basicgeometry(::Type{LineString} , g, tup::NamedTuple) + coord = Point{2, Float64}[] + for i in 1:length(g) + push!(coord,collect(pts for pts in g[1])) + end + + return LineStringMeta(LineString([Point{2, Float64}(p) for p in coord], 1), tup) +end + +function basicgeometry(::Type{LineString} , g) + return LineString([Point{2, Float64}(p) for p in g], 1) +end + +function basicgeometry(::Type{Polygon}, g, tup::NamedTuple) + coord = Array{Point{2, Float64}}[] + for i in 1:length(g) + push!(coord,collect(pts for pts in g[1])) + end + # TODO introduce LinearRing type in GeometryBasics? + nring = length(coord) + exterior = LineString([Point{2, Float64}(p) for p in coord[1]], 1) + if nring == 1 # only exterior + poly = Polygon(exterior) + return PolygonMeta(poly, tup) + else # exterior and interior(s) + interiors = Vector{typeof(exterior)}(undef, nring) + for i in 2:nring + interiors[i-1] = LineString([Point{2, Float64}(p) for p in coord[i]], 1) + end + poly = Polygon(exterior, interiors) + return PolygonMeta(poly, tup) + end +end +""" +will receive stuff from MultiPolygon +""" +function basicgeometry(::Type{Polygon}, g) + # TODO introduce LinearRing type in GeometryBasics? + nring = length(g) + exterior = LineString([Point{2, Float64}(p) for p in g[1]], 1) + if nring == 1 # only exterior + return Polygon(exterior) + else # exterior and interior(s) + interiors = Vector{typeof(exterior)}(undef, nring) + for i in 2:nring + interiors[i-1] = LineString([Point{2, Float64}(p) for p in g[i]], 1) + end + return Polygon(exterior, interiors) + end +end + + +function basicgeometry(::Type{MultiPoint}, g, tup::NamedTuple) + coord = Point{2, Float64}[] + for i in 1:length(g) + push!(coord,collect(pts for pts in g[1])) + end + + return MultiPointMeta([basicgeometry(Point, x) for x in coord], tup) +end + +function basicgeometry(::Type{MultiLineString}, g, tup::NamedTuple) + coord = Array{Point{2, Float64}}[] + for i in 1:length(g) + push!(coord,collect(pts for pts in g[1])) + end + + return MultiLineStringMeta([basicgeometry(LineString, x) for x in coord], tup) +end + +function basicgeometry(::Type{MultiPolygon}, g, tup::NamedTuple) + coord = Array{Array{Point{2, Float64}}}[] + for i in 1:length(g) + push!(coord,collect(pts for pts in g[1])) + end + poly = [basicgeometry(Polygon, x) for x in coord] + return MultiPolygonMeta(poly; tup...) +end + +function basicgeometry(::Type{FeatureCollection}, g, tup::NamedTuple) + #todo workout a way to represent metadata in this case + return [basicgeometry(geom) for geom in g] +end + +function structarray(geom) + meta = collect(GeometryBasics.meta(s) for s in geom) + meta_cols = Tables.columntable(meta) + return StructArray(Geometry = collect(GeometryBasics.metafree(i) for i in geom); meta_cols...) +end + + +# @btime GeoJSONTables.read($bytes_ne_10m_land) +# # 53.805 ms (17 allocations: 800 bytes) +# @btime [basicgeometry(f) for f in $t] +# # 446.689 ms (2990547 allocations: 150.06 MiB) + +# # the file contains 9 MultiPolygons followed by 1 Polygon +# # so StructArrays only works if we take the first 9 only + +# sa = StructArray([basicgeometry(f) for f in take(t, 9)]) +# sa = StructArray([basicgeometry(f) for f in t]) diff --git a/test/basics.jl b/test/basics.jl new file mode 100644 index 0000000..dd58e03 --- /dev/null +++ b/test/basics.jl @@ -0,0 +1,101 @@ +using Base.Iterators +using GeoJSONTables +using JSON3 +import GeoInterface +using Tables +using Test +using BenchmarkTools +using GeometryBasics +using GeometryBasics.StructArrays + +struct GeometryCollection end + +function basicgeometry(f::GeoJSONTables.Feature) + object = GeoJSONTables.geometry(f) + return basicgeometry(object) +end + +function basicgeometry(g::JSON3.Object) + t = g.type + if t == "Point" + return basicgeometry(Point, g.coordinates) + elseif t == "LineString" + return basicgeometry(LineString, g.coordinates) + elseif t == "Polygon" + return basicgeometry(Polygon, g.coordinates) + elseif t == "MultiPoint" + return basicgeometry(MultiPoint, g.coordinates) + elseif t == "MultiLineString" + return basicgeometry(MultiLineString, g.coordinates) + elseif t == "MultiPolygon" + return basicgeometry(MultiPolygon, g.coordinates) + elseif t == "GeometryCollection" + return basicgeometry(GeometryCollection, g.geometries) + else + throw(ArgumentError("Unknown geometry type")) + end +end + +function basicgeometry(::Type{Point}, g::JSON3.Array) + return Point{2, Float64}(g) +end + +function basicgeometry(::Type{LineString}, g::JSON3.Array) + return LineString([Point{2, Float64}(p) for p in g], 1) +end + +function basicgeometry(::Type{Polygon}, g::JSON3.Array) + # TODO introduce LinearRing type in GeometryBasics? + nring = length(g) + exterior = LineString([Point{2, Float64}(p) for p in g[1]], 1) + if nring == 1 # only exterior + return Polygon(exterior) + else # exterior and interior(s) + interiors = Vector{typeof(exterior)}(undef, nring) + for i in 2:nring + interiors[i-1] = LineString([Point{2, Float64}(p) for p in g[i]], 1) + end + return Polygon(exterior, interiors) + end +end + +function basicgeometry(::Type{MultiPoint}, g::JSON3.Array) + return MultiPoint([basicgeometry(Point, x) for x in g]) +end + +function basicgeometry(::Type{MultiLineString}, g::JSON3.Array) + return MultiLineString([basicgeometry(LineString, x) for x in g]) +end + +function basicgeometry(::Type{MultiPolygon}, g::JSON3.Array) + return MultiPolygon([basicgeometry(Polygon, x) for x in g]) +end + +function basicgeometry(::Type{GeometryCollection}, g::JSON3.Array) + return [basicgeometry(geom) for geom in g] +end + +# https://github.com/nvkelso/natural-earth-vector/blob/master/geojson/ne_10m_land.geojson +path_ne_10m_land = joinpath(@__DIR__, "..", "dev", "ne_10m_land.geojson") +bytes_ne_10m_land = read(path_ne_10m_land) + +t = GeoJSONTables.read(bytes_ne_10m_land) +f = first(t) + +GeoJSONTables.geometry(f) +basicgeometry(f) +[basicgeometry(f) for f in t] # Vector{Any} + +prop = GeoJSONTables.properties(f) +g = GeoJSONTables.geometry(f) + +@btime GeoJSONTables.read($bytes_ne_10m_land) +# 53.805 ms (17 allocations: 800 bytes) +@btime [basicgeometry(f) for f in $t] +# 446.689 ms (2990547 allocations: 150.06 MiB) + +# the file contains 9 MultiPolygons followed by 1 Polygon +# so StructArrays only works if we take the first 9 only + +sa = StructArray([basicgeometry(f) for f in take(t, 9)]) +# sa = StructArray([basicgeometry(f) for f in t])