Skip to content

Commit

Permalink
CBIDXT-253: Add wkb library
Browse files Browse the repository at this point in the history
Add the wkb library in order to encode geometries as Well-Known Binary (WKB).

The library was originally a WKB writer only and created by Cloudant. It's
licensed under the Apache License 2.0.

I extended it with a WKB reader.

This commit includes the full library, but without the build system, as it
originally uses rebar, but Couchbase uses CMake.

Change-Id: I04096132ed7d559bf7dd0ec6d5b137c804e249ca
Reviewed-on: http://review.couchbase.org/45340
Tested-by: buildbot <build@couchbase.com>
Reviewed-by: Harsha H S <hhs.couchbase@gmail.com>
Reviewed-by: Volker Mische <volker.mische@gmail.com>
  • Loading branch information
Volker Mische authored and vmx committed Jan 16, 2015
1 parent 7d52751 commit c1351e4
Show file tree
Hide file tree
Showing 7 changed files with 645 additions and 0 deletions.
4 changes: 4 additions & 0 deletions wkb/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
wkb
===

OGC Well Known Binary (WKB) writer and reader in Erlang
130 changes: 130 additions & 0 deletions wkb/include/wkb.hrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-define(POINT, <<"Point">>).
-define(LINESTRING, <<"LineString">>).
-define(POLYGON, <<"Polygon">>).
-define(MULTIPOINT, <<"MultiPoint">>).
-define(MULTILINESTRING, <<"MultiLineString">>).
-define(MULTIPOLYGON, <<"MultiPolygon">>).
-define(GEOMETRYCOLLECTION, <<"GeometryCollection">>).

-define(wkbPoint, 1).
-define(wkbLineString, 2).
-define(wkbPolygon, 3).
-define(wkbMultiPoint, 4).
-define(wkbMultiLineString, 5).
-define(wkbMultiPolygon, 6).
-define(wkbGeometryCollection, 7).

-define(wkbZ, 16#80000000).
-define(wkbM, 16#40000000).

-define(BIG_ENDIAN, 0).
-define(LITTLE_ENDIAN, 1).


% spec - http://edndoc.esri.com/arcsde/9.0/general_topics/wkb_representation.htm

% // Basic Type definitions
% // byte : 1 byte
% // uint32 : 32 bit unsigned integer (4 bytes)
% // double : double precision number (8 bytes)

% // Building Blocks : Point, LinearRing

% Point {
% double x;
% double y;
% };

% LinearRing {
% uint32 numPoints;
% Point points[numPoints];
% }

% enum wkbGeometryType {
% wkbPoint = 1,
% wkbLineString = 2,
% wkbPolygon = 3,
% wkbMultiPoint = 4,
% wkbMultiLineString = 5,
% wkbMultiPolygon = 6,
% wkbGeometryCollection = 7
% };

% enum wkbByteOrder {

% wkbXDR = 0, // Big Endian

% wkbNDR = 1 // Little Endian

% };

% WKBPoint {
% byte byteOrder;
% uint32 wkbType; // 1
% Point point;
% }

% WKBLineString {
% byte byteOrder;
% uint32 wkbType; // 2
% uint32 numPoints;
% Point points[numPoints];
% }

% WKBPolygon {
% byte byteOrder;
% uint32 wkbType; // 3
% uint32 numRings;
% LinearRing rings[numRings];
% }

% WKBMultiPoint {
% byte byteOrder;
% uint32 wkbType; // 4
% uint32 num_wkbPoints;
% WKBPoint WKBPoints[num_wkbPoints];
% }

% WKBMultiLineString {
% byte byteOrder;
% uint32 wkbType; // 5
% uint32 num_wkbLineStrings;
% WKBLineString WKBLineStrings[num_wkbLineStrings];
% }

% wkbMultiPolygon {
% byte byteOrder;
% uint32 wkbType; // 6
% uint32 num_wkbPolygons;
% WKBPolygon wkbPolygons[num_wkbPolygons];
% }

% WKBGeometry {
% union {
% WKBPoint point;
% WKBLineString linestring;
% WKBPolygon polygon;
% WKBGeometryCollection collection;
% WKBMultiPoint mpoint;
% WKBMultiLineString mlinestring;
% WKBMultiPolygon mpolygon;
% }
% };

% WKBGeometryCollection {
% byte byte_order;
% uint32 wkbType; // 7
% uint32 num_wkbGeometries;
% WKBGeometry wkbGeometries[num_wkbGeometries]
% }
8 changes: 8 additions & 0 deletions wkb/src/wkb.app.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{application, 'wkb', [
{description, "wkb - OGC Well Known Binary (WKB) writer and reader in Erlang"},
{vsn, "1.2.0"},
{modules, []}]}.
{registered, []},
{applications, [kernel, stdlib]},
{env, {}}
]}.
136 changes: 136 additions & 0 deletions wkb/src/wkb_reader.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
% Copyright 2015 Couchbase Inc.
%
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(wkb_reader).

-include("wkb.hrl").

-export([wkb_to_geojson/1]).

-type coords() :: [float()] | [[float()]] | [[[float()]]] | [[[[float()]]]].
-type geojson() :: {[{binary(), binary() | coords()}]}.


-spec wkb_to_geojson(binary()) -> {ok, geojson()}.
wkb_to_geojson(Data) ->
{GeoJson, <<>>} = parse_wkb(Data),
{ok, GeoJson}.



-spec parse_wkb(binary()) -> {geojson(), binary()}.
parse_wkb(<<?BIG_ENDIAN:8, ?wkbPoint:32, _/binary>> = Wkb) ->
{Coords, Rest} = parse_coords(Wkb),
{create_json(?POINT, Coords), Rest};

parse_wkb(<<?BIG_ENDIAN:8, ?wkbLineString:32, _/binary>> = Wkb) ->
{Coords, Rest} = parse_coords(Wkb),
{create_json(?LINESTRING, Coords), Rest};

parse_wkb(<<?BIG_ENDIAN:8, ?wkbPolygon:32, _/binary>> = Wkb) ->
{Coords, Rest} = parse_coords(Wkb),
{create_json(?POLYGON, Coords), Rest};

parse_wkb(<<?BIG_ENDIAN:8, ?wkbMultiPoint:32, _/binary>> = Wkb) ->
{Coords, Rest} = parse_coords(Wkb),
{create_json(?MULTIPOINT, Coords), Rest};

parse_wkb(<<?BIG_ENDIAN:8, ?wkbMultiLineString:32, _/binary>> = Wkb) ->
{Coords, Rest} = parse_coords(Wkb),
{create_json(?MULTILINESTRING, Coords), Rest};

parse_wkb(<<?BIG_ENDIAN:8, ?wkbMultiPolygon:32, _/binary>> = Wkb) ->
{Coords, Rest} = parse_coords(Wkb),
{create_json(?MULTIPOLYGON, Coords), Rest};

parse_wkb(<<?BIG_ENDIAN:8, ?wkbGeometryCollection:32, NumWkbGeometries:32,
Rest/binary>>) ->
Geoms = parse_wkb_multi(Rest, NumWkbGeometries),
{{[{<<"type">>, ?GEOMETRYCOLLECTION}, {<<"geometries">>, Geoms}]}, <<>>}.


-spec parse_coords(binary()) -> {coords(), binary()}.
parse_coords(<<?BIG_ENDIAN:8, ?wkbPoint:32, X:64/float, Y:64/float,
Rest/binary>>) ->
{[X, Y], Rest};

parse_coords(<<?BIG_ENDIAN:8, ?wkbLineString:32, NumPoints:32,
Points:NumPoints/binary-unit:128, Rest/binary>>) ->
{parse_points(Points), Rest};

parse_coords(<<?BIG_ENDIAN:8, ?wkbPolygon:32, NumRings:32, Rest/binary>>) ->
parse_rings(Rest, NumRings);

parse_coords(<<?BIG_ENDIAN:8, ?wkbMultiPoint:32, NumWkbPoints:32,
Rest/binary>>) ->
parse_coords_multi(Rest, NumWkbPoints);

parse_coords(<<?BIG_ENDIAN:8, ?wkbMultiLineString:32, NumWkbLineStrings:32,
Rest/binary>>) ->
parse_coords_multi(Rest, NumWkbLineStrings);

parse_coords(<<?BIG_ENDIAN:8, ?wkbMultiPolygon:32, NumWkbPolygons:32,
Rest/binary>>) ->
parse_coords_multi(Rest, NumWkbPolygons).


-spec parse_points(binary()) -> [[float()]].
parse_points(Points) ->
[[X, Y] || <<X:64/float, Y:64/float>> <= Points].


-spec parse_rings(binary(), non_neg_integer()) -> {[[[float()]]], binary()}.
parse_rings(Rings, NumRings) ->
parse_rings(Rings, NumRings, []).

-spec parse_rings(binary(), non_neg_integer(), [[[float()]]]) ->
{[[[float()]]], binary()}.
parse_rings(Rest, NumRings, Acc) when length(Acc) =:= NumRings ->
{lists:reverse(Acc), Rest};

parse_rings(<<NumPoints:32, Points:NumPoints/binary-unit:128, Rest/binary>>,
NumRings, Acc) ->
Parsed = parse_points(Points),
parse_rings(Rest, NumRings, [Parsed | Acc]).


-spec parse_coords_multi(binary(), non_neg_integer()) -> {coords(), binary()}.
parse_coords_multi(WkbMulti, NumWkbMulti) ->
parse_coords_multi(WkbMulti, NumWkbMulti, []).

-spec parse_coords_multi(binary(), non_neg_integer(), coords()) ->
{coords(), binary()}.
parse_coords_multi(Rest, NumWkbMulti, Acc) when length(Acc) =:= NumWkbMulti ->
{lists:reverse(Acc), Rest};

parse_coords_multi(<<WkbMulti/binary>>, NumWkbMulti, Acc) ->
{Parsed, Rest} = parse_coords(WkbMulti),
parse_coords_multi(Rest, NumWkbMulti, [Parsed | Acc]).


-spec parse_wkb_multi(binary(), non_neg_integer()) -> [geojson()].
parse_wkb_multi(WkbMulti, NumWkbMulti) ->
parse_wkb_multi(WkbMulti, NumWkbMulti, []).

-spec parse_wkb_multi(binary(), non_neg_integer(), [geojson()]) -> [geojson()].
parse_wkb_multi(<<>>, NumWkbMulti, Acc) when length(Acc) =:= NumWkbMulti ->
lists:reverse(Acc);

parse_wkb_multi(<<WkbMulti/binary>>, NumWkbMulti, Acc) ->
{Parsed, Rest} = parse_wkb(WkbMulti),
parse_wkb_multi(Rest, NumWkbMulti, [Parsed | Acc]).


-spec create_json(binary(), coords()) -> geojson().
create_json(Type, Coordinates) ->
{[{<<"type">>, Type}, {<<"coordinates">>, Coordinates}]}.
Loading

0 comments on commit c1351e4

Please sign in to comment.