changed
CHANGELOG.md
|
@@ -1,5 +1,72 @@
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+ ## v4.0.0 — 2024-09-17
|
4
|
+
|
5
|
+ ### Potentially breaking change: [Default decoded GeoJSON to SRID 4326 (WGS 84)](https://github.com/felt/geo/pull/219)
|
6
|
+
|
7
|
+ This aligns our GeoJSON decoding with [the GeoJSON spec](https://tools.ietf.org/html/rfc7946#section-4) by making all decoded GeoJSON infer the WGS 84 datum (SRID 4326) by default. Whereas previously when you called `Geo.JSON.decode/1` or `decode!/1`, we would return geometries with an `:srid` of `nil`, we now return `srid: 4326`. Likewise when encoding GeoJSON, we explicitly output a `crs` field indicating the datum.
|
8
|
+
|
9
|
+ This is unlikely to break real-world usage unless your implementation was assuming a different datum by default.
|
10
|
+
|
11
|
+ A couple examples of the changes:
|
12
|
+
|
13
|
+ **Before**:
|
14
|
+
|
15
|
+ ```elixir
|
16
|
+ iex> Geo.JSON.decode!(%{"type" => "Point", "coordinates" => [1.0, 2.0]})
|
17
|
+ %Geo.Point{
|
18
|
+ coordinates: {1.0, 2.0},
|
19
|
+ # Note the old default nil SRID!
|
20
|
+ srid: nil
|
21
|
+ }
|
22
|
+ ```
|
23
|
+
|
24
|
+ **After**
|
25
|
+
|
26
|
+ ```elixir
|
27
|
+ iex> Geo.JSON.decode!(%{"type" => "Point", "coordinates" => [1.0, 2.0]})
|
28
|
+ %Geo.Point{
|
29
|
+ coordinates: {1.0, 2.0},
|
30
|
+ # New explicit default of WGS 84
|
31
|
+ srid: 4326
|
32
|
+ }
|
33
|
+ ```
|
34
|
+
|
35
|
+ If you were to then encode this value again, you'd end up with a new `crs` field in the output GeoJSON:
|
36
|
+
|
37
|
+ ```elixir
|
38
|
+ iex> %{"type" => "Point", "coordinates" => [1.0, 2.0]}
|
39
|
+ ...> |> Geo.JSON.decode!()
|
40
|
+ ...> |> GeoJSON.encode!()
|
41
|
+ %{
|
42
|
+ "type" => "Point",
|
43
|
+ "coordinates" => [1.0, 2.0],
|
44
|
+ # Note the new `crs` field which was not present in the input to Geo.JSON.decode!/1
|
45
|
+ "crs" => %{"properties" => %{"name" => "EPSG:4326"}, "type" => "name"}
|
46
|
+ }
|
47
|
+ ```
|
48
|
+
|
49
|
+ This last behavior is the most potentially troublesome. However, we don't have a good way of distinguishing a case where you explicitly had the `crs` set in the input to the decoding function (in which case you would probably also like to have it present in the re-encoded version) compared to one in which it's been inferred.
|
50
|
+
|
51
|
+ Thanks to @gworkman for reporting this issue ([#129](https://github.com/felt/geo/issues/129)).
|
52
|
+
|
53
|
+ ### Potentially breaking change: [Convert string coordinates to floats, or raise an error](https://github.com/felt/geo/pull/218)
|
54
|
+
|
55
|
+ This fixes an issue where we were silently accepting non-numeric coordinates in the GeoJSON decoder, such that you could wind up doing things like decoding a point like `%Geo.Point{coordinates: {"100.0", "-10.0"}}`. This would obviously not have gone well for you later in your processing pipeline, and it violates our typespecs.
|
56
|
+
|
57
|
+ The fix here, suggested by @LostKobrakai, is to convert those strings to numbers where we can do so unambiguously. While such inputs are clearly invalid, it's easy enough to handle them in the way that the user was hoping that we should probably just do it. In cases where there's any ambiguity at all, we raise an `ArgumentError`.
|
58
|
+
|
59
|
+ ### Other bug fixes in v4.0.0
|
60
|
+
|
61
|
+ - [Support GeoJSON Feature object with nested GeometryCollection](https://github.com/felt/geo/pull/194) by new contributor @carstenpiepel (🎉)
|
62
|
+
|
63
|
+ ### Other changes in v4.0.0
|
64
|
+
|
65
|
+ - [Fix typo in the README](https://github.com/felt/geo/pull/197) by @caspg
|
66
|
+ - [Fix typo](https://github.com/felt/geo/pull/216) by new contributor @preciz (🎉)
|
67
|
+ - [Optional dependency bump for `jason` to v1.4.4](https://github.com/felt/geo/pull/215)
|
68
|
+ - Dev dependency bumps for ex_doc, benchee, stream_data
|
69
|
+
|
3
70
|
## v3.6.0 — 2023-10-19
|
4
71
|
|
5
72
|
As of v3.6.0, `geo` (like [`geo_postgis`](https://github.com/felt/geo_postgis)) is being maintained by the Felt team. As a company building a geospatial product on Elixir, with a track record of [supporting open source software](https://felt.com/open-source), we're excited for the future of the project.
|
changed
README.md
|
@@ -20,8 +20,8 @@ A collection of GIS functions. Handles conversions to and from well-known text (
|
20
20
|
* PolygonZ
|
21
21
|
* MultiPoint
|
22
22
|
* MultiPointZ
|
23
|
- * MuliLineString
|
24
|
- * MuliLineStringZ
|
23
|
+ * MultiLineString
|
24
|
+ * MultiLineStringZ
|
25
25
|
* MultiPolygon
|
26
26
|
* MultiPolygonZ
|
27
27
|
* GeometryCollection
|
|
@@ -33,7 +33,7 @@ _Note_: If you are looking to do geospatial calculations in memory with Geo's st
|
33
33
|
```elixir
|
34
34
|
defp deps do
|
35
35
|
[
|
36
|
- {:geo, "~> 3.6"}
|
36
|
+ {:geo, "~> 4.0"}
|
37
37
|
]
|
38
38
|
end
|
39
39
|
```
|
changed
hex_metadata.config
|
@@ -1,6 +1,6 @@
|
1
1
|
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/felt/geo">>}]}.
|
2
2
|
{<<"name">>,<<"geo">>}.
|
3
|
- {<<"version">>,<<"3.6.0">>}.
|
3
|
+ {<<"version">>,<<"4.0.0">>}.
|
4
4
|
{<<"description">>,<<"Encodes and decodes WKB, WKT, and GeoJSON formats.">>}.
|
5
5
|
{<<"elixir">>,<<"~> 1.10">>}.
|
6
6
|
{<<"app">>,<<"geo">>}.
|
changed
lib/geo/json.ex
|
@@ -7,13 +7,16 @@ defmodule Geo.JSON do
|
7
7
|
so that you can use the resulting GeoJSON structure as a property
|
8
8
|
in larger JSON structures.
|
9
9
|
|
10
|
+ Note that, per [the GeoJSON spec](https://tools.ietf.org/html/rfc7946#section-4),
|
11
|
+ all geometries are assumed to use the WGS 84 datum (SRID 4326) by default.
|
12
|
+
|
10
13
|
## Examples
|
11
14
|
|
12
15
|
# Using Jason as the JSON parser for these examples
|
13
16
|
|
14
17
|
iex> json = "{ \\"type\\": \\"Point\\", \\"coordinates\\": [100.0, 0.0] }"
|
15
18
|
...> json |> Jason.decode!() |> Geo.JSON.decode!()
|
16
|
- %Geo.Point{coordinates: {100.0, 0.0}, srid: nil}
|
19
|
+ %Geo.Point{coordinates: {100.0, 0.0}, srid: 4326}
|
17
20
|
|
18
21
|
iex> geom = %Geo.Point{coordinates: {100.0, 0.0}, srid: nil}
|
19
22
|
...> Jason.encode!(geom)
|
changed
lib/geo/json/decoder.ex
|
@@ -46,7 +46,7 @@ defmodule Geo.JSON.Decoder do
|
46
46
|
Enum.map(Map.get(geo_json, "geometries"), fn x ->
|
47
47
|
do_decode(
|
48
48
|
Map.get(x, "type"),
|
49
|
- Map.get(x, "coordinates"),
|
49
|
+ ensure_numeric(x["coordinates"]),
|
50
50
|
Map.get(x, "properties", %{}),
|
51
51
|
crs
|
52
52
|
)
|
|
@@ -62,7 +62,7 @@ defmodule Geo.JSON.Decoder do
|
62
62
|
|
63
63
|
do_decode(
|
64
64
|
Map.get(geo_json, "type"),
|
65
|
- Map.get(geo_json, "coordinates"),
|
65
|
+ ensure_numeric(geo_json["coordinates"]),
|
66
66
|
Map.get(geo_json, "properties", %{}),
|
67
67
|
crs
|
68
68
|
)
|
|
@@ -96,6 +96,9 @@ defmodule Geo.JSON.Decoder do
|
96
96
|
true ->
|
97
97
|
raise DecodeError, value: geo_json
|
98
98
|
end
|
99
|
+ # Per #129, the GeoJSON spec says all GeoJSON coordinates default to SRID 4326 (WGS 84)
|
100
|
+ # https://tools.ietf.org/html/rfc7946#section-4
|
101
|
+ |> default_srid_4326()
|
99
102
|
end
|
100
103
|
|
101
104
|
@doc """
|
|
@@ -197,7 +200,21 @@ defmodule Geo.JSON.Decoder do
|
197
200
|
defp do_decode("Feature", nil, _properties, _id), do: nil
|
198
201
|
|
199
202
|
defp do_decode("Feature", geometry, properties, _id) do
|
200
|
- do_decode(Map.get(geometry, "type"), Map.get(geometry, "coordinates"), properties, nil)
|
203
|
+ if geometry["type"] == "GeometryCollection" do
|
204
|
+ geometry_collection = decode!(geometry)
|
205
|
+
|
206
|
+ %GeometryCollection{
|
207
|
+ geometries: geometry_collection.geometries,
|
208
|
+ properties: properties
|
209
|
+ }
|
210
|
+ else
|
211
|
+ do_decode(
|
212
|
+ Map.get(geometry, "type"),
|
213
|
+ ensure_numeric(geometry["coordinates"]),
|
214
|
+ properties,
|
215
|
+ nil
|
216
|
+ )
|
217
|
+ end
|
201
218
|
end
|
202
219
|
|
203
220
|
defp do_decode(type, [x, y, _z], properties, crs) do
|
|
@@ -222,4 +239,55 @@ defmodule Geo.JSON.Decoder do
|
222
239
|
defp get_srid(nil) do
|
223
240
|
nil
|
224
241
|
end
|
242
|
+
|
243
|
+ # Fast paths for the common (correct) cases
|
244
|
+ defp ensure_numeric(num) when is_number(num), do: num
|
245
|
+ defp ensure_numeric([x, y] = l) when is_number(x) and is_number(y), do: l
|
246
|
+
|
247
|
+ defp ensure_numeric([x, y, z] = l)
|
248
|
+ when is_number(x) and is_number(y) and (is_number(z) or is_nil(z)) do
|
249
|
+ l
|
250
|
+ end
|
251
|
+
|
252
|
+ defp ensure_numeric([x, y, z, m] = l)
|
253
|
+ when is_number(x) and is_number(y) and (is_number(z) or is_nil(z)) and
|
254
|
+ (is_number(m) or is_nil(z)) do
|
255
|
+ l
|
256
|
+ end
|
257
|
+
|
258
|
+ defp ensure_numeric(l) when is_list(l) do
|
259
|
+ Enum.map(l, fn
|
260
|
+ num when is_number(num) ->
|
261
|
+ num
|
262
|
+
|
263
|
+ str when is_binary(str) ->
|
264
|
+ try do
|
265
|
+ String.to_float(str)
|
266
|
+ catch
|
267
|
+ ArgumentError ->
|
268
|
+ raise ArgumentError, "expected a numeric coordinate, got the string #{inspect(str)}"
|
269
|
+ end
|
270
|
+
|
271
|
+ nil ->
|
272
|
+ nil
|
273
|
+
|
274
|
+ l when is_list(l) ->
|
275
|
+ Enum.map(l, &ensure_numeric/1)
|
276
|
+
|
277
|
+ other ->
|
278
|
+ raise ArgumentError, "expected a numeric coordinate, got: #{inspect(other)}"
|
279
|
+ end)
|
280
|
+ end
|
281
|
+
|
282
|
+ defp ensure_numeric(other) do
|
283
|
+ raise ArgumentError, "expected a numeric coordinate, got: #{inspect(other)}"
|
284
|
+ end
|
285
|
+
|
286
|
+ defp default_srid_4326(%{srid: nil} = geom), do: %{geom | srid: 4326}
|
287
|
+
|
288
|
+ defp default_srid_4326(%{geometries: geometries} = geom) when is_list(geometries) do
|
289
|
+ %{geom | geometries: Enum.map(geometries, &default_srid_4326/1)}
|
290
|
+ end
|
291
|
+
|
292
|
+ defp default_srid_4326(geom), do: geom
|
225
293
|
end
|
changed
lib/geo/polygonz.ex
|
@@ -1,6 +1,6 @@
|
1
1
|
defmodule Geo.PolygonZ do
|
2
2
|
@moduledoc """
|
3
|
- Defines the Polygon struct.
|
3
|
+ Defines the PolygonZ struct.
|
4
4
|
"""
|
5
5
|
|
6
6
|
@type t :: %__MODULE__{
|
changed
mix.exs
|
@@ -2,7 +2,7 @@ defmodule Geo.Mixfile do
|
2
2
|
use Mix.Project
|
3
3
|
|
4
4
|
@source_url "https://github.com/felt/geo"
|
5
|
- @version "3.6.0"
|
5
|
+ @version "4.0.0"
|
6
6
|
|
7
7
|
def project do
|
8
8
|
[
|
|
@@ -33,7 +33,7 @@ defmodule Geo.Mixfile do
|
33
33
|
[
|
34
34
|
{:jason, "~> 1.4", optional: true},
|
35
35
|
{:ex_doc, "~> 0.29", only: :dev, runtime: false},
|
36
|
- {:stream_data, "~> 0.5", only: :test, runtime: false},
|
36
|
+ {:stream_data, "~> 0.5 or ~> 1.0", only: :test, runtime: false},
|
37
37
|
{:benchee, "~> 1.1", only: :dev, runtime: false}
|
38
38
|
]
|
39
39
|
end
|