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

@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

Link to this function

contains?(a, b)

@spec contains?(geometry(), geometry()) :: boolean()

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
Link to this function

disjoint?(a, b)

@spec disjoint?(geometry(), geometry()) :: boolean()

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
@spec equals?(geometry(), geometry()) :: boolean()

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
Link to this function

intersects?(a, b)

@spec intersects?(geometry(), geometry()) :: boolean()

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
@spec within?(geometry(), geometry()) :: boolean()

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