diff --git a/README.md b/README.md index 438c823..d57fb3b 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ A collection of GIS functions. Handles conversions to and from well-known text ( * MultiPointZ * MultiLineString * MultiLineStringZ +* MultiLineStringZM * MultiPolygon * MultiPolygonZ * GeometryCollection diff --git a/lib/geo.ex b/lib/geo.ex index c130fd5..dc7d6ab 100644 --- a/lib/geo.ex +++ b/lib/geo.ex @@ -17,6 +17,7 @@ defmodule Geo do | Geo.MultiPointZ.t() | Geo.MultiLineString.t() | Geo.MultiLineStringZ.t() + | Geo.MultiLineStringZM.t() | Geo.MultiPolygon.t() | Geo.MultiPolygonZ.t() | Geo.GeometryCollection.t() diff --git a/lib/geo/json/decoder.ex b/lib/geo/json/decoder.ex index ef9135d..6de628e 100644 --- a/lib/geo/json/decoder.ex +++ b/lib/geo/json/decoder.ex @@ -11,6 +11,7 @@ defmodule Geo.JSON.Decoder do MultiPoint, MultiLineString, MultiLineStringZ, + MultiLineStringZM, MultiPolygon, MultiPolygonZ, GeometryCollection @@ -175,6 +176,15 @@ defmodule Geo.JSON.Decoder do %MultiLineStringZ{coordinates: coordinates, srid: get_srid(crs), properties: properties} end + defp do_decode("MultiLineStringZM", coordinates, properties, crs) do + coordinates = + Enum.map(coordinates, fn sub_coordinates -> + Enum.map(sub_coordinates, &List.to_tuple(&1)) + end) + + %MultiLineStringZM{coordinates: coordinates, srid: get_srid(crs), properties: properties} + end + defp do_decode("MultiPolygon", coordinates, properties, crs) do coordinates = Enum.map(coordinates, fn sub_coordinates -> diff --git a/lib/geo/json/encoder.ex b/lib/geo/json/encoder.ex index 0314bd0..5a54a14 100644 --- a/lib/geo/json/encoder.ex +++ b/lib/geo/json/encoder.ex @@ -13,6 +13,7 @@ defmodule Geo.JSON.Encoder do MultiPointZ, MultiLineString, MultiLineStringZ, + MultiLineStringZM, MultiPolygon, MultiPolygonZ, GeometryCollection @@ -202,6 +203,15 @@ defmodule Geo.JSON.Encoder do %{"type" => "MultiLineStringZ", "coordinates" => coordinates} end + defp do_encode(%MultiLineStringZM{coordinates: coordinates}) do + coordinates = + Enum.map(coordinates, fn sub_coordinates -> + Enum.map(sub_coordinates, &Tuple.to_list(&1)) + end) + + %{"type" => "MultiLineStringZM", "coordinates" => coordinates} + end + defp do_encode(%MultiPolygon{coordinates: coordinates}) do coordinates = Enum.map(coordinates, fn sub_coordinates -> diff --git a/lib/geo/multi_line_stringzm.ex b/lib/geo/multi_line_stringzm.ex new file mode 100644 index 0000000..95f19e1 --- /dev/null +++ b/lib/geo/multi_line_stringzm.ex @@ -0,0 +1,12 @@ +defmodule Geo.MultiLineStringZM do + @moduledoc """ + Defines the MultiLineStringZM struct. + """ + + @type t :: %__MODULE__{ + coordinates: [[{number, number, number, number}]], + srid: integer | nil, + properties: map + } + defstruct coordinates: [], srid: nil, properties: %{} +end diff --git a/lib/geo/wkb/decoder.ex b/lib/geo/wkb/decoder.ex index 7481bb2..5b2eb04 100644 --- a/lib/geo/wkb/decoder.ex +++ b/lib/geo/wkb/decoder.ex @@ -15,6 +15,7 @@ defmodule Geo.WKB.Decoder do @multi_point_z 0x80_00_00_04 @multi_line_string 0x00_00_00_05 @multi_line_string_z 0x80_00_00_05 + @multi_line_string_zm 0xC0_00_00_05 @multi_polygon 0x00_00_00_06 @multi_polygon_z 0x80_00_00_06 @geometry_collection 0x00_00_00_07 @@ -38,6 +39,7 @@ defmodule Geo.WKB.Decoder do MultiPointZ, MultiLineString, MultiLineStringZ, + MultiLineStringZM, MultiPolygon, MultiPolygonZ } @@ -299,6 +301,22 @@ defmodule Geo.WKB.Decoder do {%MultiLineStringZ{coordinates: coordinates, srid: srid}, rest} end + defp do_decode( + @multi_line_string_zm, + <>, + srid, + unquote(endian) + ) do + {coordinates, rest} = + Enum.map_reduce(List.duplicate(1, count), rest, fn _, <> -> + {%LineStringZM{coordinates: coordinates}, rest} = decode(rest) + + {coordinates, rest} + end) + + {%MultiLineStringZM{coordinates: coordinates, srid: srid}, rest} + end + defp do_decode( @multi_polygon, <>, diff --git a/lib/geo/wkb/encoder.ex b/lib/geo/wkb/encoder.ex index 5d5abd4..3ae0273 100644 --- a/lib/geo/wkb/encoder.ex +++ b/lib/geo/wkb/encoder.ex @@ -15,6 +15,7 @@ defmodule Geo.WKB.Encoder do @multi_point_z 0x80_00_00_04 @multi_line_string 0x00_00_00_05 @multi_line_string_z 0x80_00_00_05 + @multi_line_string_zm 0xC0_00_00_05 @multi_polygon 0x00_00_00_06 @multi_polygon_z 0x80_00_00_06 @geometry_collection 0x00_00_00_07 @@ -35,6 +36,7 @@ defmodule Geo.WKB.Encoder do MultiPointZ, MultiLineString, MultiLineStringZ, + MultiLineStringZM, MultiPolygon, MultiPolygonZ, GeometryCollection @@ -194,6 +196,16 @@ defmodule Geo.WKB.Encoder do {@multi_line_string_z, [<> | coordinates]} end + def do_encode(%MultiLineStringZM{coordinates: coordinates}, unquote(endian_atom)) do + {coordinates, count} = + Enum.map_reduce(coordinates, 0, fn coordinate, acc -> + geom = encode!(%LineStringZM{coordinates: coordinate}, unquote(endian_atom)) + {geom, acc + 1} + end) + + {@multi_line_string_zm, [<> | coordinates]} + end + def do_encode(%MultiPolygon{coordinates: coordinates}, unquote(endian_atom)) do {coordinates, count} = Enum.map_reduce(coordinates, 0, fn coordinate, acc -> diff --git a/lib/geo/wkt/decoder.ex b/lib/geo/wkt/decoder.ex index c81343e..8e12ba0 100644 --- a/lib/geo/wkt/decoder.ex +++ b/lib/geo/wkt/decoder.ex @@ -14,6 +14,8 @@ defmodule Geo.WKT.Decoder do MultiPoint, MultiPointZ, MultiLineString, + MultiLineStringZ, + MultiLineStringZM, MultiPolygon, MultiPolygonZ, GeometryCollection @@ -88,14 +90,22 @@ defmodule Geo.WKT.Decoder do %MultiPointZ{coordinates: create_line_string(coordinates), srid: srid} end - defp do_decode("MULTILINESTRING" <> coordinates, srid) do - %MultiLineString{coordinates: create_polygon(coordinates), srid: srid} - end - defp do_decode("MULTIPOINT" <> coordinates, srid) do %MultiPoint{coordinates: create_line_string(coordinates), srid: srid} end + defp do_decode("MULTILINESTRINGZM" <> coordinates, srid) do + %MultiLineStringZM{coordinates: create_polygon(coordinates), srid: srid} + end + + defp do_decode("MULTILINESTRINGZ" <> coordinates, srid) do + %MultiLineStringZ{coordinates: create_polygon(coordinates), srid: srid} + end + + defp do_decode("MULTILINESTRING" <> coordinates, srid) do + %MultiLineString{coordinates: create_polygon(coordinates), srid: srid} + end + defp do_decode("MULTIPOLYGONZ" <> coordinates, srid) do %MultiPolygonZ{coordinates: create_multi_polygon(coordinates), srid: srid} end diff --git a/lib/geo/wkt/encoder.ex b/lib/geo/wkt/encoder.ex index 589b222..9e9124c 100644 --- a/lib/geo/wkt/encoder.ex +++ b/lib/geo/wkt/encoder.ex @@ -15,6 +15,7 @@ defmodule Geo.WKT.Encoder do MultiPointZ, MultiLineString, MultiLineStringZ, + MultiLineStringZM, MultiPolygon, MultiPolygonZ, GeometryCollection @@ -109,6 +110,12 @@ defmodule Geo.WKT.Encoder do "MULTILINESTRINGZ#{coordinate_string}" end + defp do_encode(%MultiLineStringZM{coordinates: coordinates}) do + coordinate_string = create_polygon_str(coordinates) + + "MULTILINESTRINGZM#{coordinate_string}" + end + defp do_encode(%MultiPolygon{coordinates: coordinates}) do coordinate_string = create_multi_polygon_str(coordinates) diff --git a/test/geo/json_test.exs b/test/geo/json_test.exs index 3f73064..fe49aa3 100644 --- a/test/geo/json_test.exs +++ b/test/geo/json_test.exs @@ -227,6 +227,24 @@ defmodule Geo.JSON.Test do assert_geojson_equal(exjson, new_exjson) end + test "GeoJson to MultiLineStringZM and back" do + json = + "{ \"type\": \"MultiLineStringZM\", \"coordinates\": [[ [100.0, 0.0, 50.0, 1], [101.0, 1.0, 51.0, 2] ],[ [102.0, 2.0, 52.0, 3], [103.0, 3.0, 53.0, 4] ]]}" + + exjson = Jason.decode!(json) + geom = Jason.decode!(json) |> Geo.JSON.decode!() + + assert( + geom.coordinates == [ + [{100.0, 0.0, 50.0, 1}, {101.0, 1.0, 51.0, 2}], + [{102.0, 2.0, 52.0, 3}, {103.0, 3.0, 53.0, 4}] + ] + ) + + new_exjson = Geo.JSON.encode!(geom) + assert_geojson_equal(exjson, new_exjson) + end + test "GeoJson to MultiPolygon and back" do json = "{ \"type\": \"MultiPolygon\", \"coordinates\": [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]],[[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],[[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]]}" diff --git a/test/geo/wkb_test.exs b/test/geo/wkb_test.exs index 0ffd150..61c9210 100644 --- a/test/geo/wkb_test.exs +++ b/test/geo/wkb_test.exs @@ -423,6 +423,52 @@ defmodule Geo.WKB.Test do ) end + test "Decode WKB to MultiLineStringZM" do + geom = + Geo.WKB.decode!( + "00C00000050000000200C0000002000000024024000000000000402400000000000040240000000000003FF0000000000000403400000000000040340000000000004034000000000000400000000000000000C000000200000002402E000000000000402E000000000000402E0000000000004008000000000000403E000000000000402E00000000000040240000000000004010000000000000" + ) + + expected_coords = [[{10, 10, 10, 1}, {20, 20, 20, 2}], [{15, 15, 15, 3}, {30, 15, 10, 4}]] + + assert(geom.coordinates == expected_coords) + end + + test "Encode MultiLineStringZM to WKB" do + geom = %Geo.MultiLineStringZM{ + coordinates: [[{10, 10, 10, 1}, {20, 20, 20, 2}], [{15, 15, 15, 3}, {30, 15, 10, 4}]] + } + + assert( + Geo.WKB.encode!(geom) == + "00C00000050000000200C0000002000000024024000000000000402400000000000040240000000000003FF0000000000000403400000000000040340000000000004034000000000000400000000000000000C000000200000002402E000000000000402E000000000000402E0000000000004008000000000000403E000000000000402E00000000000040240000000000004010000000000000" + ) + end + + test "Decode EWKB to MultiLineStringZM" do + geom = + Geo.WKB.decode!( + "01050000E0E61000000200000001020000C002000000000000000000244000000000000024400000000000002440000000000000F03F000000000000344000000000000034400000000000003440000000000000004001020000C0020000000000000000002E400000000000002E400000000000002E4000000000000008400000000000003E400000000000002E4000000000000024400000000000001040" + ) + + expected_coords = [[{10, 10, 10, 1}, {20, 20, 20, 2}], [{15, 15, 15, 3}, {30, 15, 10, 4}]] + + assert(geom.coordinates == expected_coords) + assert(geom.srid == 4326) + end + + test "Encode MultiLineStringZM to EWKB" do + geom = %Geo.MultiLineStringZM{ + coordinates: [[{10, 10, 10, 1}, {20, 20, 20, 2}], [{15, 15, 15, 3}, {30, 15, 10, 4}]], + srid: 4326 + } + + assert( + Geo.WKB.encode!(geom, :ndr) == + "01050000E0E61000000200000001020000C002000000000000000000244000000000000024400000000000002440000000000000F03F000000000000344000000000000034400000000000003440000000000000004001020000C0020000000000000000002E400000000000002E400000000000002E4000000000000008400000000000003E400000000000002E4000000000000024400000000000001040" + ) + end + test "Decode WKB to MultiLineStringZ" do geom = Geo.WKB.decode!( diff --git a/test/geo/wkt_test.exs b/test/geo/wkt_test.exs index 8ba5a44..ecc0a40 100644 --- a/test/geo/wkt_test.exs +++ b/test/geo/wkt_test.exs @@ -176,6 +176,122 @@ defmodule Geo.WKT.Test do assert(geom.srid == 4326) end + test "Encode MultiLineStringZ to WKT" do + geom = %Geo.MultiLineStringZ{ + coordinates: [ + [{10, 10, 10}, {20, 20, 20}, {10, 40, 60}], + [{40, 40, 40}, {30, 30, 30}, {40, 20, 40}, {30, 10, 20}] + ] + } + + assert( + Geo.WKT.encode!(geom) == + "MULTILINESTRINGZ((10 10 10,20 20 20,10 40 60),(40 40 40,30 30 30,40 20 40,30 10 20))" + ) + end + + test "Decode WKT to MultiLineStringZ" do + geom = + Geo.WKT.decode!( + "MULTILINESTRINGZ((10 10 10,20 20 20,10 40 60),(40 40 40,30 30 30,40 20 40,30 10 20))" + ) + + assert( + geom.coordinates == [ + [{10, 10, 10}, {20, 20, 20}, {10, 40, 60}], + [{40, 40, 40}, {30, 30, 30}, {40, 20, 40}, {30, 10, 20}] + ] + ) + end + + test "Decode WKT with spaces between LineStrings to MultiLineStringZ" do + geom = + Geo.WKT.decode!( + "MULTILINESTRINGZ((10 10 10,20 20 20,10 40 60), (40 40 40,30 30 30,40 20 40,30 10 20))" + ) + + assert( + geom.coordinates == [ + [{10, 10, 10}, {20, 20, 20}, {10, 40, 60}], + [{40, 40, 40}, {30, 30, 30}, {40, 20, 40}, {30, 10, 20}] + ] + ) + end + + test "Decode EWKT to MultiLineStringZ" do + geom = + Geo.WKT.decode!( + "SRID=4326;MULTILINESTRINGZ((10 10 10,20 20 20,10 40 60),(40 40 40,30 30 30,40 20 40,30 10 20))" + ) + + assert( + geom.coordinates == [ + [{10, 10, 10}, {20, 20, 20}, {10, 40, 60}], + [{40, 40, 40}, {30, 30, 30}, {40, 20, 40}, {30, 10, 20}] + ] + ) + + assert(geom.srid == 4326) + end + + test "Encode MultiLineStringZM to WKT" do + geom = %Geo.MultiLineStringZM{ + coordinates: [ + [{10, 10, 10, 1}, {20, 20, 20, 2}, {10, 40, 60, 3}], + [{40, 40, 40, 4}, {30, 30, 30, 5}, {40, 20, 40, 6}, {30, 10, 20, 7}] + ] + } + + assert( + Geo.WKT.encode!(geom) == + "MULTILINESTRINGZM((10 10 10 1,20 20 20 2,10 40 60 3),(40 40 40 4,30 30 30 5,40 20 40 6,30 10 20 7))" + ) + end + + test "Decode WKT to MultiLineStringZM" do + geom = + Geo.WKT.decode!( + "MULTILINESTRINGZM((10 10 10 1,20 20 20 2,10 40 60 3),(40 40 40 4,30 30 30 5,40 20 40 6,30 10 20 7))" + ) + + assert( + geom.coordinates == [ + [{10, 10, 10, 1}, {20, 20, 20, 2}, {10, 40, 60, 3}], + [{40, 40, 40, 4}, {30, 30, 30, 5}, {40, 20, 40, 6}, {30, 10, 20, 7}] + ] + ) + end + + test "Decode WKT with spaces between LineStrings to MultiLineStringZM" do + geom = + Geo.WKT.decode!( + "MULTILINESTRINGZM((10 10 10 1,20 20 20 2,10 40 60 3), (40 40 40 4,30 30 30 5,40 20 40 6,30 10 20 7))" + ) + + assert( + geom.coordinates == [ + [{10, 10, 10, 1}, {20, 20, 20, 2}, {10, 40, 60, 3}], + [{40, 40, 40, 4}, {30, 30, 30, 5}, {40, 20, 40, 6}, {30, 10, 20, 7}] + ] + ) + end + + test "Decode EWKT to MultiLineStringZM" do + geom = + Geo.WKT.decode!( + "SRID=4326;MULTILINESTRINGZM((10 10 10 1,20 20 20 2,10 40 60 3),(40 40 40 4,30 30 30 5,40 20 40 6,30 10 20 7))" + ) + + assert( + geom.coordinates == [ + [{10, 10, 10, 1}, {20, 20, 20, 2}, {10, 40, 60, 3}], + [{40, 40, 40, 4}, {30, 30, 30, 5}, {40, 20, 40, 6}, {30, 10, 20, 7}] + ] + ) + + assert(geom.srid == 4326) + end + test "Encode MultiPolygon to WKT" do geom = %Geo.MultiPolygon{ coordinates: [