Packages
geo
1.2.0
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/wkb.ex
defmodule Geo.WKB do
alias Geo.Point
alias Geo.LineString
alias Geo.Polygon
alias Geo.MultiPoint
alias Geo.MultiLineString
alias Geo.MultiPolygon
alias Geo.GeometryCollection
alias Geo.WKB.Reader
alias Geo.WKB.Writer
alias Geo.Utils
use Bitwise
@moduledoc """
Converts to and from WKB and EWKB
point = Geo.WKB.decode("0101000000000000000000F03F000000000000F03F")
Geo.Point[coordinates: {1, 1}, srid: nil]
Geo.WKT.encode(point)
"POINT(1 1)"
point = Geo.WKB.decode("0101000020E61000009EFB613A637B4240CF2C0950D3735EC0")
Geo.Point[coordinates: {36.9639657, -121.8097725}, srid: 4326]
"""
@doc """
Takes a Geometry and returns a WKB string. The endian decides
what the byte order will be
"""
@spec encode(binary, Geo.endian) :: binary
def encode(geom, endian \\ :xdr) do
writer = Writer.start(endian)
do_encode(geom, writer)
end
defp do_encode(%GeometryCollection{} = geom, writer) do
type = Utils.type_to_hex(geom, geom.srid != nil)
|> Integer.to_string(16)
|> Utils.pad_left(8)
srid = if geom.srid, do: Integer.to_string(geom.srid, 16) |>Utils.pad_left(8), else: ""
count = Integer.to_string(Enum.count(geom.geometries), 16) |>Utils.pad_left(8)
writer = Writer.write(writer, type)
writer = Writer.write(writer, srid)
writer = Writer.write(writer, count)
coordinates = Enum.map(geom.geometries,
fn(x) ->
x = %{x | srid: nil}
encode(x, Writer.get_endian(writer))
end)
coordinates = Enum.join(coordinates)
Writer.get_wkb(writer) <> coordinates
end
defp do_encode(geom, writer) do
type = Utils.type_to_hex(geom, geom.srid != nil)
|> Integer.to_string(16)
|> Utils.pad_left(8)
srid = if geom.srid, do: Integer.to_string(geom.srid, 16) |>Utils.pad_left(8), else: ""
writer = Writer.write(writer, type)
writer = Writer.write(writer, srid)
writer = encode_coordinates(writer, geom)
Writer.get_wkb(writer)
end
defp encode_coordinates(writer, %Point{coordinates: {0, 0}}) do
Writer.write(writer,Utils.repeat("0", 32))
end
defp encode_coordinates(writer, %Point{coordinates: {x, y}}) do
x = x |> Utils.float_to_hex(64) |> Integer.to_string(16)
y = y |> Utils.float_to_hex(64) |> Integer.to_string(16)
writer = Writer.write(writer, x)
Writer.write(writer, y)
end
defp encode_coordinates(writer, %LineString{coordinates: coordinates}) do
number_of_points = Integer.to_string(length(coordinates), 16) |> Utils.pad_left(8)
writer = Writer.write(writer, number_of_points)
{_nils, writer} = Enum.map_reduce(coordinates, writer, fn(pair, acc) ->
acc = encode_coordinates(acc, %Point{ coordinates: pair })
{nil, acc}
end)
writer
end
defp encode_coordinates(writer, %Polygon{coordinates: coordinates}) do
number_of_lines = Integer.to_string(length(coordinates), 16) |> Utils.pad_left(8)
writer = Writer.write(writer, number_of_lines)
{_nils, writer} = Enum.map_reduce(coordinates, writer, fn(line, acc) ->
acc = encode_coordinates(acc, %LineString{ coordinates: line })
{nil, acc}
end)
writer
end
defp encode_coordinates(writer, %MultiPoint{coordinates: coordinates}) do
writer = Writer.write(writer, Integer.to_string(length(coordinates), 16) |> Utils.pad_left(8))
geoms = Enum.map(coordinates, fn(geom) ->
encode(%Point{ coordinates: geom }, Writer.get_endian(writer))
end) |> Enum.join
Writer.write_no_endian(writer, geoms)
end
defp encode_coordinates(writer, %MultiLineString{coordinates: coordinates}) do
writer = Writer.write(writer, Integer.to_string(length(coordinates), 16) |> Utils.pad_left(8))
geoms = Enum.map(coordinates, fn(geom) ->
encode(%LineString{ coordinates: geom }, Writer.get_endian(writer))
end) |> Enum.join
Writer.write_no_endian(writer, geoms)
end
defp encode_coordinates(writer, %MultiPolygon{coordinates: coordinates}) do
writer = Writer.write(writer, Integer.to_string(length(coordinates), 16) |> Utils.pad_left(8))
geoms = Enum.map(coordinates, fn(geom) ->
encode(%Polygon{ coordinates: geom }, Writer.get_endian(writer))
end) |> Enum.join
Writer.write_no_endian(writer, geoms)
end
@doc """
Takes a WKB string and returns a Geometry
"""
@spec decode(binary, [Geo.geometry]) :: Geo.geometry
def decode(wkb, geometries \\ []) do
wkb_reader = Reader.start(wkb)
{ type, wkb_reader } = Reader.read(wkb_reader, 8)
type = String.to_integer(type, 16)
{srid, wkb_reader} =
if (type &&& 0x20000000) != 0 do
{ srid, wkb_reader } = Reader.read(wkb_reader, 8)
{String.to_integer(srid, 16), wkb_reader}
else
{nil, wkb_reader}
end
type = Utils.hex_to_type(type &&& 0xff)
{coordinates, wkb_reader} = decode_coordinates(type, wkb_reader)
geometries =
case type do
%Geo.GeometryCollection{} ->
coordinates = coordinates
|> Enum.map(fn(x) -> %{ x | srid: srid } end)
%{ type | geometries: coordinates, srid: srid }
_ ->
geometries ++ [ %{ type | coordinates: coordinates, srid: srid }]
end
if(Reader.eof?(wkb_reader)) do
return_geom(geometries)
else
Reader.get_wkb(wkb_reader) |> decode(geometries)
end
end
defp return_geom(%GeometryCollection{} = geom) do
geom
end
defp return_geom(geom) when is_list(geom) do
if length(geom) == 1 do
hd(geom)
else
geom
end
end
defp decode_coordinates(%Point{}, wkb_reader) do
{x, wkb_reader} = Reader.read(wkb_reader, 16)
x = Utils.hex_to_float(x)
{y, wkb_reader} = Reader.read(wkb_reader, 16)
y = Utils.hex_to_float(y)
{ {x,y}, wkb_reader }
end
defp decode_coordinates(%LineString{}, wkb_reader) do
{number_of_points, wkb_reader} = Reader.read(wkb_reader, 8)
number_of_points = number_of_points |> String.to_integer(16)
Enum.map_reduce(Enum.to_list(0..(number_of_points-1)), wkb_reader, fn(_x, acc) ->
decode_coordinates(%Point{}, acc)
end)
end
defp decode_coordinates(%Polygon{}, wkb_reader) do
{number_of_lines, wkb_reader} = Reader.read(wkb_reader, 8)
number_of_lines = number_of_lines |> String.to_integer(16)
Enum.map_reduce(Enum.to_list(0..(number_of_lines-1)), wkb_reader, fn(_x, acc) ->
decode_coordinates(%LineString{}, acc)
end)
end
defp decode_coordinates(%GeometryCollection{}, wkb_reader) do
{_number_of_items, wkb_reader} = Reader.read(wkb_reader, 8)
geometries = decode(Reader.get_wkb(wkb_reader))
{ List.wrap(geometries), Reader.start("00") }
end
defp decode_coordinates(_geom, wkb_reader) do
{_number_of_items, wkb_reader} = Reader.read(wkb_reader, 8)
decoded_geom = Reader.get_wkb(wkb_reader) |> decode
coordinates = if is_list(decoded_geom) do
Enum.map(decoded_geom, fn(x) ->
x.coordinates
end)
else
[decoded_geom.coordinates]
end
{ coordinates, Reader.start("00") }
end
end