Skip to content
This repository was archived by the owner on Jul 4, 2022. It is now read-only.

move to GeometryBasics #2

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
language: julia
julia:
- 1.0
- 1.1
- 1.2
- 1.3
- 1.4
- nightly
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
19 changes: 10 additions & 9 deletions src/GeoJSONTables.jl
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
144 changes: 144 additions & 0 deletions src/basics.jl
Original file line number Diff line number Diff line change
@@ -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])
101 changes: 101 additions & 0 deletions test/basics.jl
Original file line number Diff line number Diff line change
@@ -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])