Skip to content

Commit

Permalink
Support for Empty Point (#172)
Browse files Browse the repository at this point in the history
* Support for Empty Point

Adds support for (E)WKB and GeoJSON conversion for Empty Point.

An Empty Point in
  - in WKB is represented as `Point(NaN NaN)`.
  - in GeoJson is represented as `{"type" => "Point", "coordinates" => []}`

This PR implements an Empty Point in Geo as `%Geo.Point{coordinates: nil}`

* Make empty point happy with WKB
  • Loading branch information
bolek authored Sep 14, 2023
1 parent 6b870d8 commit 78ac946
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 0 deletions.
4 changes: 4 additions & 0 deletions lib/geo/json/decoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ defmodule Geo.JSON.Decoder do
%Point{coordinates: {x, y}, srid: get_srid(crs), properties: properties}
end

defp do_decode("Point", [], properties, crs) do
%Point{coordinates: nil, srid: get_srid(crs), properties: properties}
end

defp do_decode("LineString", coordinates, properties, crs) do
coordinates = Enum.map(coordinates, &list_to_tuple(&1))

Expand Down
4 changes: 4 additions & 0 deletions lib/geo/json/encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ defmodule Geo.JSON.Encoder do
%{"type" => "Point", "coordinates" => [x, y]}
end

defp do_encode(%Point{coordinates: nil}) do
%{"type" => "Point", "coordinates" => []}
end

defp do_encode(%PointZ{coordinates: {x, y, z}}) do
%{"type" => "Point", "coordinates" => [x, y, z]}
end
Expand Down
23 changes: 23 additions & 0 deletions lib/geo/wkb/decoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -317,4 +317,27 @@ defmodule Geo.WKB.Decoder do
{%GeometryCollection{geometries: geometries, srid: srid}, rest}
end
end

defp do_decode(
@point,
<<0, 0, 0, 0, 0, 0, 248, 127, 0, 0, 0, 0, 0, 0, 248, 127, rest::bits>>,
srid,
1
) do
{%Point{coordinates: nil, srid: srid}, rest}
end

defp do_decode(
@point,
binary,
srid,
0
) do
little_binary =
binary
|> :binary.decode_unsigned(:big)
|> :binary.encode_unsigned(:little)

do_decode(@point, little_binary, srid, 1)
end
end
8 changes: 8 additions & 0 deletions lib/geo/wkb/encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ defmodule Geo.WKB.Encoder do
[unquote(endian), binary, rest]
end

def do_encode(%Point{coordinates: nil}, :ndr) do
{@point, [<<00, 00, 00, 00, 00, 00, 248, 127>>, <<00, 00, 00, 00, 00, 00, 248, 127>>]}
end

def do_encode(%Point{coordinates: nil}, :xdr) do
{@point, [<<127, 248, 00, 00, 00, 00, 00, 00>>, <<127, 248, 00, 00, 00, 00, 00, 00>>]}
end

def do_encode(%Point{coordinates: {x, y}}, unquote(endian_atom)) do
{@point, [<<x::unquote(modifier)-float-64>>, <<y::unquote(modifier)-float-64>>]}
end
Expand Down
18 changes: 18 additions & 0 deletions test/geo/json_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ defmodule Geo.JSON.Test do
assert(exjson == new_exjson)
end

test "GeoJson Point without coordinates" do
json = "{ \"type\": \"Point\", \"coordinates\": [] }"
exjson = Jason.decode!(json)
geom = Jason.decode!(json) |> Geo.JSON.decode!()
assert(is_nil(geom.coordinates))

new_exjson = Geo.JSON.encode!(geom)
assert(exjson == new_exjson)
end

test "GeoJson with SRID to Point and back" do
json =
"{\"type\":\"Point\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[100.0, 101.0]}"
Expand Down Expand Up @@ -350,6 +360,14 @@ defmodule Geo.JSON.Test do
end
end

test "encodes and decodes back to the correct Empty Point struct" do
geom = %Geo.Point{coordinates: nil}
json = Geo.JSON.encode!(geom) |> Jason.encode!()

assert(json == "{\"coordinates\":[],\"type\":\"Point\"}")
assert geom == Geo.JSON.encode!(geom) |> Geo.JSON.decode!()
end

property "encodes and decodes back to the correct LineString struct" do
check all(list <- list_of({float(), float()}, min_length: 1)) do
geom = %Geo.LineString{coordinates: list}
Expand Down
22 changes: 22 additions & 0 deletions test/geo/wkb_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,12 @@ defmodule Geo.WKB.Test do
assert(point.srid == 4326)
end

test "Decode empty Point EWKB to Point" do
point = Geo.WKB.decode!("0101000020E6100000000000000000F87F000000000000F87F")
assert(point.coordinates == nil)
assert(point.srid == 4326)
end

test "Decode empty MultiPolygon EWKB to MultiPolygon" do
multipolygon = Geo.WKB.decode!("0106000020E610000000000000")
assert(multipolygon.coordinates == [])
Expand Down Expand Up @@ -703,6 +709,22 @@ defmodule Geo.WKB.Test do
assert {:ok, "0101000000000000000000F03F000000000000F03F"} = Geo.WKB.encode(geom, :ndr)
end

test "Encode/Decode Empty Point in big endian" do
geom = %Geo.Point{coordinates: nil}
geom_wkb = "00000000017FF80000000000007FF8000000000000"

assert Geo.WKB.encode!(geom, :xdr) == geom_wkb
assert Geo.WKB.decode!(geom_wkb) == geom
end

test "Encode/Decode Empty Point in little endian" do
geom = %Geo.Point{coordinates: nil}
geom_wkb = "00000000017FF80000000000007FF8000000000000"

assert Geo.WKB.encode!(geom, :xdr) == geom_wkb
assert Geo.WKB.decode!(geom_wkb) == geom
end

property "encodes and decodes back to the correct Point struct" do
check all(
x <- float(),
Expand Down

0 comments on commit 78ac946

Please sign in to comment.