Packages
geo
3.4.3
4.1.0
4.0.1
4.0.0
3.6.0
3.5.1
3.5.0
3.4.3
3.4.2
3.4.1
3.4.0
3.3.8
3.3.7
3.3.5
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.0
3.1.0
3.0.0
2.1.0
2.0.0
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.18.0
0.17.0
0.16.1
0.16.0
0.15.2
0.15.1
0.15.0
0.14.0
0.13.0
0.12.0
0.11.2
0.11.1
0.11.0
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.2
0.7.1
0.6.1
0.5.1
Encodes and decodes WKB, WKT, and GeoJSON formats.
Current section
Files
Jump to
Current section
Files
lib/geo/json/encoder.ex
defmodule Geo.JSON.Encoder do
@moduledoc false
alias Geo.{
Point,
PointZ,
LineString,
LineStringZ,
Polygon,
PolygonZ,
MultiPoint,
MultiPointZ,
MultiLineString,
MultiLineStringZ,
MultiPolygon,
MultiPolygonZ,
GeometryCollection
}
defmodule EncodeError do
@type t :: %__MODULE__{message: String.t(), value: any}
defexception [:message, :value]
def message(%{message: nil, value: value}) do
"unable to encode value: #{inspect(value)}"
end
def message(%{message: message}) do
message
end
end
@doc """
Takes a Geometry and returns a map representing the GeoJSON.
"""
@spec encode!(Geo.geometry()) :: map()
def encode!(geom, opts \\ [])
def encode!(geom, []) do
case geom do
%GeometryCollection{geometries: geometries, srid: srid, properties: properties} ->
%{"type" => "GeometryCollection", "geometries" => Enum.map(geometries, &encode!(&1))}
|> add_crs(srid)
|> add_properties(properties)
_ ->
geom
|> do_encode()
|> add_crs(geom.srid)
|> add_properties(geom.properties)
end
end
# translate a %GeometryCollection{} to a GeoJSON FeatureCollection (3.3).
#
# GeometryCollections and their encapsulated Geo.geometry() structs MAY have
# properties. GeoJSON Features MUST have properties even if empty (3.2), and
# properties objects on other types are considered "foreign members" (6.1).
#
# This function attempts to merge accordingly into individual Feature
# properties. Geo.geometry() properties override Collection properties.
# This function disregards SRID information as GeoJSON is expected to be in
# WGS 84, deviating only in pre-agreed cases (4).
#
# see:
# https://tools.ietf.org/html/rfc7946#section-3.2
# https://tools.ietf.org/html/rfc7946#section-3.3
# https://tools.ietf.org/html/rfc7946#section-4
# https://tools.ietf.org/html/rfc7946#section-6.1
#
def encode!(%GeometryCollection{geometries: gs, properties: ps}, feature: true) do
ps = Enum.reduce(ps, %{}, &properties_reduce/2)
%{
"type" => "FeatureCollection",
"features" => Enum.map(gs, &encode!(&1, feature: true, properties: ps))
}
end
# translate a Geo.geometry() to a GeoJSON Feature (3.2), with optional default
# properties.
#
# This function disregards SRID information as GeoJSON is expected to be in
# WGS 84, deviating only in pre-agreed cases (4).
#
# see:
# https://tools.ietf.org/html/rfc7946#section-3.2
# https://tools.ietf.org/html/rfc7946#section-4
#
def encode!(geom, opts) do
if Keyword.get(opts, :feature, false) do
ps =
Enum.reduce(
geom.properties,
Keyword.get(opts, :properties, %{}),
&properties_reduce/2
)
%{
"type" => "Feature",
"properties" => ps,
"geometry" => do_encode(geom)
}
else
encode!(geom)
end
end
defp properties_reduce({k, v}, m) when is_atom(k), do: Map.put(m, Atom.to_string(k), v)
defp properties_reduce({k, v}, m), do: Map.put(m, k, v)
@doc """
Takes a Geometry and returns a map representing the GeoJSON.
"""
@spec encode(Geo.geometry()) :: {:ok, map()} | {:error, EncodeError.t()}
def encode(geom, opts \\ []) do
{:ok, encode!(geom, opts)}
rescue
exception in [EncodeError] ->
{:error, exception}
end
defp do_encode(%Point{coordinates: {x, y}}) do
%{"type" => "Point", "coordinates" => [x, y]}
end
defp do_encode(%PointZ{coordinates: {x, y, z}}) do
%{"type" => "Point", "coordinates" => [x, y, z]}
end
defp do_encode(%LineString{coordinates: coordinates}) do
coordinates = Enum.map(coordinates, &Tuple.to_list(&1))
%{"type" => "LineString", "coordinates" => coordinates}
end
defp do_encode(%LineStringZ{coordinates: coordinates}) do
coordinates = Enum.map(coordinates, &Tuple.to_list(&1))
%{"type" => "LineStringZ", "coordinates" => coordinates}
end
defp do_encode(%Polygon{coordinates: coordinates}) do
coordinates =
Enum.map(coordinates, fn sub_coordinates ->
Enum.map(sub_coordinates, &Tuple.to_list(&1))
end)
%{"type" => "Polygon", "coordinates" => coordinates}
end
defp do_encode(%PolygonZ{coordinates: coordinates}) do
coordinates =
Enum.map(coordinates, fn sub_coordinates ->
Enum.map(sub_coordinates, &Tuple.to_list(&1))
end)
%{"type" => "PolygonZ", "coordinates" => coordinates}
end
defp do_encode(%MultiPoint{coordinates: coordinates}) do
coordinates = Enum.map(coordinates, &Tuple.to_list(&1))
%{"type" => "MultiPoint", "coordinates" => coordinates}
end
defp do_encode(%MultiPointZ{coordinates: coordinates}) do
coordinates = Enum.map(coordinates, &Tuple.to_list(&1))
%{"type" => "MultiPointZ", "coordinates" => coordinates}
end
defp do_encode(%MultiLineString{coordinates: coordinates}) do
coordinates =
Enum.map(coordinates, fn sub_coordinates ->
Enum.map(sub_coordinates, &Tuple.to_list(&1))
end)
%{"type" => "MultiLineString", "coordinates" => coordinates}
end
defp do_encode(%MultiLineStringZ{coordinates: coordinates}) do
coordinates =
Enum.map(coordinates, fn sub_coordinates ->
Enum.map(sub_coordinates, &Tuple.to_list(&1))
end)
%{"type" => "MultiLineStringZ", "coordinates" => coordinates}
end
defp do_encode(%MultiPolygon{coordinates: coordinates}) do
coordinates =
Enum.map(coordinates, fn sub_coordinates ->
Enum.map(sub_coordinates, fn third_sub_coordinates ->
Enum.map(third_sub_coordinates, &Tuple.to_list(&1))
end)
end)
%{"type" => "MultiPolygon", "coordinates" => coordinates}
end
defp do_encode(%MultiPolygonZ{coordinates: coordinates}) do
coordinates =
Enum.map(coordinates, fn sub_coordinates ->
Enum.map(sub_coordinates, fn third_sub_coordinates ->
Enum.map(third_sub_coordinates, &Tuple.to_list(&1))
end)
end)
%{"type" => "MultiPolygon", "coordinates" => coordinates}
end
defp do_encode(data) do
raise EncodeError, message: "Unable to encode given value: #{inspect(data)}"
end
defp add_crs(map, nil) do
map
end
defp add_crs(map, srid) do
Map.put(map, "crs", %{"type" => "name", "properties" => %{"name" => "EPSG:#{srid}"}})
end
def add_properties(map, props) do
if Enum.empty?(props) do
map
else
Map.put(map, "properties", props)
end
end
end