Topo (topo v1.0.2)
A Geometry library for Elixir that calculates relationships between two geometries. Geometries can be of any of the following types:
- Point
- LineString
- Polygon
- MultiPoint
- MultiLineString
- MultiPolygon
Each of these functions can be passed any two Geometries in either a Map with a
:type
and :coordinates
keys or as a struct generated via the Geo library
(https://github.com/bryanjos/geo). Coordinates are represented as atoms {x, y}
and multiple coordinates as Lists.
a = %{type: "Polygon", coordinates: [[{2, 2}, {20, 2}, {11, 11}, {2, 2}]]}
b = %Geo.Polygon{coordinates: [[{2, 2}, {20, 2}, {11, 11}, {2, 2}]]}
Topo.equals? a, b # => true
Instead of a Point geometry, just a single coordinate can be used.
a = %{type: "Polygon", coordinates: [[{2, 2}, {20, 2}, {11, 11}, {2, 2}]]}
Topo.intersects? a, {4, 6} # => true
The Topo
library's functions will automatically attempt to "clean" geometries
passed to them:
- Linear Rings (including Polygons) will be reordered to a counter-clockwise direction.
- Polygon's Linear Rings will automatically be closed if the first point is not repeated as the last point.
- Points that are equal or collinear with surrounding points are removed from LineStrings or Polygons.
Summary
Functions
Returns true
if all points of geometry B lie within A.
Returns true
if geometries A and B do not have any points in
common.
Geometries A and B are equivalent and cover the exact same set of points. By definition, A and B are equal if A contains B and B contains A. Equality does not necessarily mean that the geometries are of the same type. A Point A is equal to a MultiPoint that contains only the same Point A.
Returns true
if geometries A and B share at least one point in
common.
This is the direct converse of contains?
. All points of
geometry A lie within geometry B.
Types
geometry()
@type geometry() :: {number(), number()} | %{type: String.t(), coordinates: list()} | %Geo.Point{coordinates: term(), properties: term(), srid: term()} | %Geo.MultiPoint{coordinates: term(), properties: term(), srid: term()} | %Geo.LineString{coordinates: term(), properties: term(), srid: term()} | %Geo.MultiLineString{coordinates: term(), properties: term(), srid: term()} | %Geo.Polygon{coordinates: term(), properties: term(), srid: term()} | %Geo.MultiPolygon{coordinates: term(), properties: term(), srid: term()}
Functions
contains?(a, b)
Returns true
if all points of geometry B lie within A.
There are a few non-obvious special cases that are worth mentioning:
- A Polygon does not contain its own boundary. Specifically a LineString that is the exact same as a Polygon's exterior Linear ring is not contained within a that Polygon.
a = %Geo.Polygon{coordinates: [[{2, 2}, {20, 2}, {11, 11}, {2, 2}]]}
b = %Geo.LineString{coordinates: [{2, 2}, {20, 2}, {11, 11}, {2, 2}]}
Topo.contains? a, b # => false
Topo.intersects? a, b # => true
- A LineString does not contain it's own first and last point (unless those points are the same, as in a LinearRing)
a = %Geo.LineString{coordinates: [{1, 3}, {2, -1}, {0, -1}]}
b = %Geo.LineString{coordinates: [{1, 3}, {2, -1}, {0, -1}, {1, 3}]}
Topo.contains? a, {1, 3} # => false
Topo.intersects? a, {1, 3} # => true
Topo.contains? b, {1, 3} # => true
Examples
iex> Topo.contains?(
...> %{type: "Polygon", coordinates: [[{20, 20}, {20, 180}, {220, 180}, {220, 20}, {20, 20}]]},
...> %{type: "Polygon", coordinates: [[{160, 60}, {20, 20}, {100, 140}, {160, 60}]]}
...> )
true
iex> Topo.contains?(
...> %{type: "Polygon", coordinates: [[{160, 60}, {20, 20}, {100, 140}, {160, 60}]]},
...> %{type: "Polygon", coordinates: [[{20, 20}, {20, 180}, {220, 180}, {220, 20}, {20, 20}]]}
...> )
false
disjoint?(a, b)
Returns true
if geometries A and B do not have any points in
common.
Examples
iex> Topo.disjoint?({1, -3}, %{type: "MultiPoint", coordinates: [{70, 35}, {100, 80}]})
true
iex> Topo.disjoint?(
...> %{type: "Polygon", coordinates: [
...> [{60, 120}, {60, 40}, {160, 40}, {160, 120}, {60, 120}],
...> [{140, 100}, {80, 100}, {80, 60}, {140, 60}, {140, 100}]]},
...> %Geo.MultiPoint{coordinates: [{70, 35}, {100, 80}]}
...> )
true
equals?(a, b)
Geometries A and B are equivalent and cover the exact same set of points. By definition, A and B are equal if A contains B and B contains A. Equality does not necessarily mean that the geometries are of the same type. A Point A is equal to a MultiPoint that contains only the same Point A.
Examples
iex> Topo.equals?(
...> %{type: "Point", coordinates: {2, -3}},
...> %{type: "MultiPoint", coordinates: [{2, -3}]}
...> )
true
iex> Topo.equals?(
...> %{type: "Polygon", coordinates: [[{160, 60}, {20, 20}, {100, 140}, {160, 60}]]},
...> %{type: "Polygon", coordinates: [[{20, 20}, {20, 180}, {220, 180}, {220, 20}, {20, 20}]]}
...> )
false
intersects?(a, b)
Returns true
if geometries A and B share at least one point in
common.
Examples
iex> Topo.intersects?(
...> %{type: "Polygon", coordinates: [[{2, 2}, {20, 2}, {11, 11}, {2, 2}]]},
...> %{type: "LineString", coordinates: [{11, 10}, {4, 2.5}, {16, 2.5}, {11, 10}]}
...> )
true
iex> Topo.intersects?(
...> %{type: "Polygon", coordinates: [
...> [{60, 120}, {60, 40}, {160, 40}, {160, 120}, {60, 120}],
...> [{140, 100}, {80, 100}, {80, 60}, {140, 60}, {140, 100}]]},
...> %{type: "MultiPoint", coordinates: [{70, 35}, {100, 80}]}
...> )
false
within?(a, b)
This is the direct converse of contains?
. All points of
geometry A lie within geometry B.
Examples
iex> Topo.within?(
...> %{type: "Polygon", coordinates: [[{20, 20}, {20, 180}, {220, 180}, {220, 20}, {20, 20}]]},
...> %{type: "Polygon", coordinates: [[{160, 60}, {20, 20}, {100, 140}, {160, 60}]]}
...> )
false
iex> Topo.within?(
...> %{type: "Polygon", coordinates: [[{160, 60}, {20, 20}, {100, 140}, {160, 60}]]},
...> %{type: "Polygon", coordinates: [[{20, 20}, {20, 180}, {220, 180}, {220, 20}, {20, 20}]]}
...> )
true