juliageo / geojson.jl Goto Github PK
View Code? Open in Web Editor NEWUtilities for working with GeoJSON data in Julia
Home Page: https://juliageo.org/GeoJSON.jl/stable/
License: MIT License
Utilities for working with GeoJSON data in Julia
Home Page: https://juliageo.org/GeoJSON.jl/stable/
License: MIT License
Maybe add Shapefile as test dependency.
It would be nice if arrays/iterables of geometries would just write as a GeometryCollection
without having to explicitly wrap them as that, as its a common way to hold and manipulate geometries you are working on.
Hi there. I'm new to GeoJSON. I'm having issues trying to load the following json file:
https://github.com/finiterank/mapa-colombia-js/blob/master/colombia-municipios.json
I downloaded it to my computer. This is how I'm trying to read the file:
using GeoJSON, GeoMakie, GLMakie
# Use local geojson file
# Source: https://github.com/finiterank/mapa-colombia-js/blob/master/colombia-municipios.json
filepath = joinpath(@__DIR__, "colombia-municipios.json")
geo = GeoJSON.read(read(filepath)) # TODO: does not work
The issue is that nothing get's loaded (Nothing
literally gets stored in geo
).
I've investigated a little to see if I could discover what is going wrong.
I actually managed to read and plot the following geojson file:
https://observablehq.com/@john-guerra/spike-map-of-colombian-cities-by-population
I compared the content of both files and I found differences. For example, the one that fails to be read does not have a FeatureCollection
or Features
in it. However, I find it strange that GitHub is able to visualize it correctly.
Could it be that the first geojson file has a different format that is not supported by GeoJSON.jl?
Hello,
The older version of the package had a geo2dict function to convert the continents json into a dictionary.
What is the alternative way to doing that with the new updates to the package ?
GeoJSON.read(read(joinpath(@__DIR__,"..","data","continents","continents.json")))
conts = geo2dict(continents)
coords = conts["features"][id]["geometry"]["coordinates"]
Thanks
#sdsl23
We should at least warn if you provide a spatial thing with a (defined?) crs other than wgs84.
Right now we can convert FeatureCollection to DataFrames, but not back:
julia> using GeoJSON, DataFrames
julia> p = GeoJSON.Point(coordinates = [1.1, 2.2])
Point([1.1, 2.2])
julia> f = GeoJSON.Feature(p; properties = (a = 1, geometry = "g", b = 2))
Feature with a Point and 3 properties: (:geometry, :a, :b)
julia> features = [f]
1-element Vector{GeoJSON.Feature{NamedTuple{(:type, :geometry, :properties), Tuple{String, GeoJSON.Point{NamedTuple{(:type, :coordinates), Tuple{String, Vector{Float64}}}}, NamedTuple{(:a, :geometry, :b), Tuple{Int64, String, Int64}}}}}}:
Feature with a Point and 3 properties: (:geometry, :a, :b)
julia> fc = GeoJSON.FeatureCollection(features)
FeatureCollection with 1 Features
julia> df = DataFrame(fc)
1×3 DataFrame
Row │ geometry a b
│ Geometry Int64 Int64
─────┼─────────────────────────────────
1 │ Point([1.1, 2.2]) 1 2
julia> GeoJSON.FeatureCollection(df)
ERROR: ArgumentError: column name :features not found in the data frame
It would be nice if this worked. We could also test with GeoDataFrames, and also look at that package for how to identify geometry columns (GeoInterface.geometrycolumns
).
I had been porting the functionality for Turf in a separate repository, but that codebase had been designed for javascript apps, and there's [significant] overlap in functionality with DataFrames-type operations, and other packages (in interpolations, etc).
Does it make sense to
If so, I'll migrate the relevant portions of the code over from Turf.jl to this repo (to src/turf.jl
) through a PR. Your thoughts?
cc @garborg
Migration steps:
Similar to Shapefiles.jl it would be more user-friendly. Currently we have to read the file with Base.read and then read it again with GeoJSON.read.
Should hopefully be an allround improvement in both speed and inferred types.
In the sense that an GeoInterface.coordinates
will yield a JSON3 Array that no other package will parse as the coordinates they are.
julia> GeoInterface.coordinates(poly1)
1-element Vector{JSON3.Array}:
JSON3.Array[[0.1, 0.0], [1.1, 0.2], [0, 1], [0.3, 0.0]]
A MultiLineString
is currently being saved as a Polygon
. This creates problems when interfacing with other geometry packages because MultiLineStrings
are saved as invalid Polygons
. So we obtain errors when trying to load this invalid Polygons
. This is happening in our GeoTables.jl
when intarfecing with GeoJSON.jl
(JuliaEarth/GeoTables.jl#24).
Let's consider the following geometry:
d = """{
"type": "Feature",
"id": "1",
"bbox": [-180.0, -90.0, 180.0, 90.0],
"geometry": {"type": "MultiLineString", "coordinates": [[[3.75, 9.25], [-130.95, 1.52]], [[23.15, -34.25], [-1.35, -4.65], [3.45, 77.95]]]},
"properties": {"title": "Dict 1"}
}"""
featu = GJS.read(d)
Given that featu
is a GeoJSONT
, writing to a file works well because it does not to use the _lower
function.
GJS.write("nolower.json", featu)
# {"type":"Feature","id":"1","bbox":[-180.0,-90.0,180.0,90.0],"geometry":{"type":"MultiLineString","coordinates":[[[3.75,9.25],[-130.95,1.52]],[[23.15,-34.25],[-1.35,-4.65],[3.45,77.95]]]},"properties":{"title":"Dict 1"}}
However, if we use the _lower
function, the MultiLineString
is saved as a Polygon
.
featul = GJS._lower(featu)
JSON3.write("lower.json", featul)
# {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[3.75,9.25],[-130.95,1.52]],[[23.15,-34.25],[-1.35,-4.65],[3.45,77.95]]]},"properties":{"title":"Dict 1"},"bbox":[-180.0,-90.0,180.0,90.0]}
The outputs are different when the geometry type should actually be MultiLineString
.
When I looked at https://app.codecov.io/gh/JuliaGeo/GeoJSON.jl/tree/master/src?displayType=list, it seems to think the latest commit is 674f18a
It would be convenient if you could do something like filter(f -> f.NAME == "France", collection)
, but currently filtering is not supported.
Right now, I'm naivley pirating this like so:
function Base.filter(f::Function, fc::GeoJSON.FeatureCollection)
features = [feat for feat in fc if f(feat)]
GeoJSON.FeatureCollection(; bbox=nothing, features, crs=getfield(fc, :crs))
end
But I'm sure there is potential for improvement.
help?> GI.properties
GeoInterface.properties(feat) => properties
Retrieve the properties of feat. This can be any Iterable that behaves like an AbstractRow. Ensures
backwards compatibility with GeoInterface version 0.
But here we return a Dict
which doesn't have getproperty
like an AbstractRow
However you can call getproperty
on the feature itself as a workaround, its' just confusing - and we cant standardise code accross feature types.
I do not know if all GeoJSON features should share the same properties. But if not, the following is a bit of a nuisance.
With GeoJSON 0.6.0
a = """{"type": "FeatureCollection", "features": [
{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.0, 0.0]]]}, "properties": {"foo": "pippo", "bar": "pluto"}},
{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[0.0, 0.0], [-1.0, 0.0], [0.0, -1.0], [0.0, 0.0]]]}, "properties": {"foo": "fulano"}}
]}"""
b = GeoJSON.read(a)
now propertynames(b)
gives (:geometry, :bar, :foo)
.
This is same what I get when tab expanding
julia> b.<TAB>
bar foo geometry
There is b.foo
julia> b.foo
2-element Vector{String}:
"pippo"
"fulano"
but there is no b.bar
julia> b.bar
ERROR: KeyError: key :bar not found
Stacktrace:
[1] getindex(h::Dict{Symbol, Int64}, key::Symbol)
@ Base .\dict.jl:498
[2] get(obj::JSON3.Object{Base.CodeUnits{UInt8, String}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}, key::Symbol)
@ JSON3 C:\Users\jaakkor2\.julia\packages\JSON3\GoF7x\src\JSON3.jl:86
[3] getproperty(obj::JSON3.Object{Base.CodeUnits{UInt8, String}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}, prop::Symbol)
@ JSON3 C:\Users\jaakkor2\.julia\packages\JSON3\GoF7x\src\JSON3.jl:126
[4] getproperty(f::GeoJSON.Feature{JSON3.Object{Base.CodeUnits{UInt8, String}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}}, nm::Symbol)
@ GeoJSON C:\Users\jaakkor2\.julia\packages\GeoJSON\XejMl\src\features.jl:46
[5] _broadcast_getindex_evalf
@ .\broadcast.jl:670 [inlined]
[6] _broadcast_getindex
@ .\broadcast.jl:643 [inlined]
[7] getindex
@ .\broadcast.jl:597 [inlined]
[8] copyto_nonleaf!(dest::Vector{String}, bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Tuple{Base.OneTo{Int64}}, typeof(getproperty), Tuple{Base.Broadcast.Extruded{GeoJSON.FeatureCollection{GeoJSON.Feature{JSON3.Object{Base.CodeUnits{UInt8, String}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}}, JSON3.Object{Base.CodeUnits{UInt8, String}, Vector{UInt64}}, JSON3.Array{JSON3.Object, Base.CodeUnits{UInt8, String}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}}, Tuple{Bool}, Tuple{Int64}}, Base.RefValue{Symbol}}}, iter::Base.OneTo{Int64}, state::Int64, count::Int64)
@ Base.Broadcast .\broadcast.jl:1055
[9] copy
@ .\broadcast.jl:907 [inlined]
[10] materialize
@ .\broadcast.jl:860 [inlined]
[11] getproperty(fc::GeoJSON.FeatureCollection{GeoJSON.Feature{JSON3.Object{Base.CodeUnits{UInt8, String}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}}, JSON3.Object{Base.CodeUnits{UInt8, String}, Vector{UInt64}}, JSON3.Array{JSON3.Object, Base.CodeUnits{UInt8, String}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}}, nm::Symbol)
@ GeoJSON C:\Users\jaakkor2\.julia\packages\GeoJSON\XejMl\src\features.jl:103
[12] top-level scope
@ REPL[96]:1
This issue is used to trigger TagBot; feel free to unsubscribe.
If you haven't already, you should update your TagBot.yml
to include issue comment triggers.
Please see this post on Discourse for instructions and more details.
If you'd like for me to do this for you, comment TagBot fix
on this issue.
I'll open a PR within a few hours, please be patient!
Follow-up from #30: in that PR, we're made it clearer that
Note that GeoJSON.jl loads features into the GeoInterface.jl format and that this differs from GeoJSON in the following ways:
- Julia Geometries do not provide a
bbox
andcrs
method. If you wish to provide abbox
orcrs
attribute, wrap the geometry into aFeature
orFeatureCollection
.- Features do not have special fields for
id
,bbox
, andcrs
. These are to be provided (or found) in theproperties
field, under the keysfeatureid
,bbox
, andcrs
respectively (if they exist).When saving GeoJSON, these transformations will be reversed: if
properties
has a keyfeatureid
, that will be removed fromproperties
and a matching memberid
will be added to the Feature; similarly forcrs
andbbox
.
There was the following question (which is left unaddressed):
I wonder if GeoJSON.jl should also warn or error if it would overwrite existing properties when doing these transformations. I think it should.
Opening this issue for discussions before we have a resolution for it.
Its a bit confusing that you have to do write("filename.json", GeoJSON.write(obj))
.
We could add a GeoJSON.write(filename, obj)
version that wrapped that.
When converting a geoJSON object to either a dict or printing a json string, the "properties" element of each Feature seems not to be included.
a = """{
"properties": {
"Ã": "Ã"
},
"type": "Feature",
"geometry": null,
"crs": {
"type": "link",
"properties": {
"href": "data.crs",
"type": "ogcwkt"
}
}
}"""
a = GeoJSON.parse(a)
geojson(a)
output:
"{\"geometry\":null,\"type\":\"Feature\",\"crs\":{\"properties\":{\"href\":\"data.crs\",\"type\":\"ogcwkt\"},\"type\":\"link\"}}"
If I have, say, a GeoJSON.LineString
ls_json = GeoJSON.LineString([[1.0,2.0],[3.0,4.0]])
and would like to convert it to a LibGEOS.LineString
LibGEOS.LineString(ls_json)
I get a MethodError
MethodError: Cannot `convert` an object of type GeoJSON.LineString to an object of type LibGEOS.LineString
This may have arisen from a call to the constructor LibGEOS.LineString(...),
since type constructors fall back to convert methods.
in LibGEOS.LineString(::GeoJSON.LineString) at ./sysimg.jl:53
LibGEOS does have a method for converting any GeoInterface.AbstractLineString to a LibGEOS.LineString:
methods(LibGEOS.LineString)
# 4 methods for generic function "(::Type)":
…
LibGEOS.LineString{T<:GeoInterface.AbstractLineString}(obj::T) at ~/.julia/v0.5/LibGEOS/src/geos_types.jl:36
(::Type{T}){T}(arg) at sysimg.jl:53
but GeoJSON.LineString
is not a GeoInterface.AbstractLineString
LibGEOS.LineString <: GeoInterface.AbstractLineString
true
GeoJSON.LineString <: GeoInterface.AbstractLineString
false
and so I end up having to do the conversion myself
LibGEOS.LineString(ls_json.coordinates)
which admittedly isn't the end of the world, but it would be convenient and elegant if GeoJSON.LineString
were a subtype of GeoInterface.AbstractLineString
.
julia> a = """{"type": "FeatureCollection", "features": [
{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[0.1, 0.0], [1, 0.2], [0.0, 1.0], [0.3, 0.0]]]}}
]}"""
julia> poly1 = GeoJSON.read(a)[1].geometry
julia> poly1[1]
4-element JSON3.Array{JSON3.Array, Base.CodeUnits{UInt8, String}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}:
[0.1, 0.0]
Union{Float64, Int64}[1, 0.2]
[0, 1] # <-- Int64
[0.3, 0.0]
The concrete types in GeoInterface.jl were written initially for this package and thus are a pretty good match. But if we want GeoInterface to support a wide selection of concrete types we probably need to move to an approach similar to Tables.jl, meaning we can take the concrete types out of GeoInterface.jl
Step one would be to move the types more or less as is. But possibly we directly switch the concrete types over to GeometryTypes, as suggested in JuliaGeo/GeoInterface.jl#24?
I've posted a question at Stackoverflow, but I guess I should post it here as well. In a nutshell, I'm reading a layer with census tracts, neighbourhoods, and districts in Barcelona and the census tracts aren't being read properly. It's not a problem with the data, since it reads fine in R. It looks like it's reading the polygon coordinates in the wrong order.
Here's the SO question with all the code and plots.
https://stackoverflow.com/questions/74126814/geojson-jl-reading-some-geometries-incorrectly
When GeometryBasics works with GeoInterface, we will need to switch to using GeometryBasics.jl types here, as the GeoInterface types used currently will go away.
Does this make sense? Just adding this before I write it in case there are other opinions.
GeoInterface 1.0 has breaking change that does not work with current version of GeoJSON (v0.5.1).
This errors for all GeoJSON types that otherwise support getindex:
using GeoJSON
p = GeoJSON.read("""{"type":"Point","coordinates":[30,10]}""")
p[begin] # MethodError: no method matching firstindex(::GeoJSON.Point{2, Float32})
x[end]
does work so I guess that has a fine fallback.
I assume this is a matter of Base.firstindex(...) = 1
for a few types.
https://docs.julialang.org/en/v1/manual/interfaces/#Indexing
It would be nice if GeoJSON.read()
could accept a URL, e.g. "https://its-live-data.s3.amazonaws.com/datacubes/catalog_v02.json"
For a given FeatureCollection, it would be useful to be able to retrieve a set of features, such as:
geodata = GeoJSON.read("some geojson file")
# This works
geodata[1]
# This is not supported
geodata[1:10]
I could submit a PR to do this if there is interest. I believe an additional function hooking into getindex
in the general location below would do fine:
Alternatively, if the above isn't desirable, what is the GeoJSON "way" of extracting a set of features?
It would be good if an object we already know is GeoFormatTypes.GeoJSON
would pass through GeoJSON.write
. It will already be a Dict
or String
. The string is assumed to be correctly formatted JSON so would not need anything done. A Dict
would go to JSON3.write
.
We could even wrap the output as a GeoJSON string, but maybe that's going to far for now... It would be like JSString
in WebIO.jl that wraps javascipt strings.
The use case for this is to allow users to pass in various objects that need different degrees of processing to be formatted as GeoJSON, and they all work.
It seems that write
does not work with all AbstractGeometry
. I'm not sure if that is intentional. But currently e.g. ArchGDAL.IGeometry
does not work.
julia> using GADM, GeoJSON
julia> GeoJSON.write(GADM.get("MUS").geom[1])
ERROR: MethodError: no method matching write(::ArchGDAL.IGeometry{ArchGDAL.wkbMultiPolygon})
Closest candidates are:
write(::AbstractFeatureCollection) at ~/.julia/packages/GeoJSON/JVqvX/src/GeoJSON.jl:40
write(::AbstractGeometryCollection) at ~/.julia/packages/GeoJSON/JVqvX/src/GeoJSON.jl:40
write(::AbstractFeature) at ~/.julia/packages/GeoJSON/JVqvX/src/GeoJSON.jl:40
...
Stacktrace:
[1] top-level scope
@ REPL[33]:1
In the docs we say that any AbstractGeometry
will work:
help?> GeoJSON.write
write(obj)
Create a GeoJSON string from an object that implements the GeoInterface, either AbstractGeometry,
AbstractFeature or AbstractFeatureCollection.
The current workaround is to wrap in a Feature
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.