Current section

Files

Jump to
exkml lib exkml.ex
Raw

lib/exkml.ex

defmodule Exkml do
import SweetXml
@moduledoc """
Documentation for Exkml.
"""
defmodule Point do
defstruct [:x, :y, :z]
end
defmodule Line do
defstruct [:points]
end
defmodule Polygon do
defstruct [:outer_boundary, inner_boundaries: []]
end
defmodule Multipoint do
defstruct points: []
end
defmodule Multiline do
defstruct lines: []
end
defmodule Multipolygon do
defstruct polygons: []
end
defp do_str_to_point([x, y]) do
with {x, _} <- Float.parse(x),
{y, _} <- Float.parse(y) do
{:ok, %Point{x: x, y: y}}
else
_ -> :error
end
end
defp do_str_to_point([x, y, z]) do
with {x, _} <- Float.parse(x),
{y, _} <- Float.parse(y),
{z, _} <- Float.parse(z) do
{:ok, %Point{x: x, y: y, z: z}}
else
_ -> :error
end
end
defp str_to_point(point_str) do
point_str
|> String.trim
|> String.split(",")
|> do_str_to_point
|> case do
:error -> {:error, "Invalid point #{point_str}"}
{:ok, _} = ok -> ok
end
end
defp str_to_line(line_str) do
line_str
|> :binary.split([" ", "\n"], [:global])
|> Enum.map(&String.trim/1)
|> extract_many(&str_to_point/1, fn "" -> false; _ -> true end)
|> case do
{:ok, points} -> {:ok, %Line{points: points}}
err -> err
end
end
def extract_many(things, fun, test \\ fn _ -> true end) do
things
|> Enum.reduce_while([], fn thing, acc ->
if test.(thing) do
case fun.(thing) do
{:ok, shape_like} -> {:cont, [shape_like | acc]}
{:error, _} = e -> {:halt, e}
end
else
{:cont, acc}
end
end)
|> case do
{:error, _} = e -> e
shapes -> {:ok, Enum.reverse(shapes)}
end
end
defp add_geom(geoms, pm, fun) do
case fun.(pm) do
:none -> geoms
{:ok, geom} -> [geom | geoms]
{:error, _} = e -> e
end
end
defp shape(nil, _, _), do: :none
defp shape(pm, xp, to_shape) do
case xpath(pm, xp) do
"" -> :none
[] -> :none
shape_str -> to_shape.(shape_str)
end
end
defp extract_point(pm), do: shape(pm, ~x"//Point/coordinates/text()"s, &str_to_point/1)
defp extract_line(pm), do: shape(pm, ~x"//LineString/coordinates/text()"s, &str_to_line/1)
defp extract_linear_ring(pm), do: shape(pm, ~x"//LinearRing/coordinates/text()"s, &str_to_line/1)
defp extract_polygon(pm) do
with {:ok, outer_boundary} <- shape(pm, ~x"//Polygon/outerBoundaryIs/LinearRing/coordinates/text()"s, &str_to_line/1),
{:ok, inner_boundaries} <- extract_many(xpath(pm, ~x"//Polygon/innerBoundaryIs"el), &extract_linear_ring/1) do
{:ok, %Polygon{
outer_boundary: outer_boundary,
inner_boundaries: inner_boundaries
}}
end
end
defp point_from_placemark(pm), do: pm |> xpath(~x"//Placemark/Point"e) |> extract_point
defp line_from_placemark(pm), do: pm |> xpath(~x"//Placemark/LineString"e) |> extract_line
defp polygon_from_placemark(pm), do: pm |> xpath(~x"//Placemark/Polygon"e) |> extract_polygon
defp multipoint_from_placemark(pm) do
case xpath(pm, ~x"//Placemark/MultiGeometry/Point"l) do
[] -> :none
coords ->
with {:ok, points} <- extract_many(coords, &extract_point/1) do
{:ok, %Multipoint{points: points}}
end
end
end
defp multiline_from_placemark(pm) do
case xpath(pm, ~x"//Placemark/MultiGeometry/LineString"l) do
[] -> :none
coords ->
with {:ok, lines} <- extract_many(coords, &extract_line/1) do
{:ok, %Multiline{lines: lines}}
end
end
end
defp multipolygon_from_placemark(pm) do
case xpath(pm, ~x"//Placemark/MultiGeometry/Polygon"l) do
[] -> :none
coords ->
with {:ok, polygons} <- extract_many(coords, &extract_polygon/1) do
{:ok, %Multipolygon{polygons: polygons}}
end
end
end
defp extract_geoms(pm) do
[]
|> add_geom(pm, &point_from_placemark/1)
|> add_geom(pm, &line_from_placemark/1)
|> add_geom(pm, &polygon_from_placemark/1)
|> add_geom(pm, &multipoint_from_placemark/1)
|> add_geom(pm, &multiline_from_placemark/1)
|> add_geom(pm, &multipolygon_from_placemark/1)
end
defp simple_data_to_attrs(pm) do
pm
|> xpath(~x"//SimpleData"el)
|> Enum.map(fn attr ->
value = attr
|> xpath(~x"text()"s)
|> String.trim
{xpath(attr, ~x"@name"s), value}
end)
end
defp data_to_attrs(pm) do
pm
|> xpath(~x"//Data"el)
|> Enum.map(fn attr ->
value = attr
|> xpath(~x"//value/text()"s)
|> String.trim
{xpath(attr, ~x"@name"s), value}
end)
end
def maybe_put(map, _, nil), do: map
def maybe_put(map, name, value), do: Map.put(map, name, value)
defp to_placemark({_, pm}) do
with geoms when is_list(geoms) <- extract_geoms(pm) do
attrs = (simple_data_to_attrs(pm) ++ data_to_attrs(pm))
|> Enum.into(%{})
|> maybe_put("name", xpath(pm, ~x"//Placemark/name/text()"))
|> maybe_put("description", xpath(pm, ~x"//Placemark/description/text()"))
|> maybe_put("snippet", xpath(pm, ~x"//Placemark/snippet/text()"))
{geoms, attrs}
end
end
@doc """
Parse a stream of binaries, return a stream of placemarks
"""
def placemarks!(doc) do
doc
|> SweetXml.stream_tags([:Placemark, :placemark])
|> Stream.map(&to_placemark/1)
end
end