Topo

repository·master·Indexed 41 days ago

https://github.com/pkinney/topo

A geometry library for Elixir designed to calculate spatial relationships between geometry types including Point, LineString, Polygon, MultiPoint, MultiLineString, and MultiPolygon. It provides functions such as intersects?, disjoint?, contains?, within?, and equals?, and supports input formats including maps, Geo library structs, and raw coordinate tuples.

Tokens
2.1K
Snippets
11
Records
12
Agent score
1%

What's inside Topo

  1. Understand contains? edge cases

    master

    The contains? function has specific behaviors regarding boundaries:

    • Polygons and Boundaries: A Polygon does not contain its own boundary. A LineString representing the exact same exterior ring as a Polygon will return false for contains?, but true for intersects?.
    • LineStrings and Endpoints: A LineString does not contain its own first and last points unless those points are identical (forming a closed loop).
    # Polygon does not contain its own boundary
    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
    
    # LineString does not contain its own endpoints
    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
  2. Automatic geometry cleaning

    master

    The Topo library automatically performs several cleaning operations on geometries passed to its functions:

    • Linear Rings (including Polygons): Reordered to a counter-clockwise direction.
    • Polygon Linear Rings: Automatically closed if the first point is not repeated as the last point.
    • LineStrings/Polygons: Points that are equal or collinear with surrounding points are removed.
  3. Geometry input formats

    master

    The Topo functions are flexible regarding input formats. You can pass geometries as:

    1. Maps: A map containing :type and :coordinates keys.
    2. Geo structs: Structs generated via the Geo library.
    3. Raw coordinates: For Point geometry, you can pass a single coordinate tuple {x, y} instead of a full geometry object.

    Coordinates are represented as atoms {x, y} and multiple coordinates as Lists.

    a = %{type: "Polygon", coordinates: [[{2, 2}, {20, 2}, {11, 11}, {2, 2}]]}
    
    # Using a raw coordinate tuple instead of a Point geometry
    Topo.intersects? a, {4, 6} # => true
  4. Handle float precision issues with epsilon

    master

    Due to floating point math imprecision, strict intersection math can sometimes produce incorrect results. By default, Topo is strict. To allow for a less strict requirement for line segment intersection, you can set an :epsilon value at compile time.

    To enable this, add the following to your application configuration:

    config :topo, epsilon: true

    Note: Topo uses Application.config_env/3 to avoid querying the value during every computation. If you change this setting, you must clean and recompile your application's dependencies.

  5. Calculate spatial relationships with Topo

    master

    The Topo module provides boolean functions to determine how two geometries relate to one another.

    Supported geometry types include:

    • Point
    • LineString
    • Polygon
    • MultiPoint
    • MultiLineString
    • MultiPolygon

    Functions:

    • intersects?: Returns true if geometries share at least one point in common.
    • disjoint?: Returns true if geometries share no points in common (the inverse of intersects?).
    • contains?: Returns true if all points of geometry B lie within A.
    • within?: Returns true if all points of geometry A lie within B (the inverse of contains?).
    • equals?: Returns true if geometries cover the exact same set of points. Note that equality does not require the same geometry type (e.g., a Point can equal a MultiPoint containing only that point).
    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
  6. Use the Topo API for spatial relationship queries

    master

    The Topo module provides functions to calculate spatial relationships between two geometries. Geometries can be passed as either a Map with :type and :coordinates keys, a struct from the Geo library, or a single coordinate tuple {x, y} (representing a Point).

    Supported geometry types include:

    • Point
    • LineString
    • Polygon
    • MultiPoint
    • MultiLineString
    • MultiPolygon

    All Topo functions automatically apply a cleaning process to the input geometries to ensure consistency (e.g., reordering Linear Rings to counter-clockwise, closing Polygons, and removing collinear points).

    # Using Maps
    a = %{type: "Polygon", coordinates: [[{2, 2}, {20, 2}, {11, 11}, {2, 2}]]}
    b = %{type: "LineString", coordinates: [{11, 10}, {4, 2.5}, {16, 2.5}, {11, 10}]}
    Topo.intersects?(a, b)
    
    # Using Geo structs
    geo_poly = %Geo.Polygon{coordinates: [[{2, 2}, {20, 2}, {11, 11}, {2, 2}]]}
    Topo.equals?(a, geo_poly)
    
    # Using a single coordinate as a Point
    Topo.intersects?(a, {4, 6})
  7. Check if a geometry contains another with contains?

    master

    Returns true if all points of geometry B lie within A.

    Important Edge Cases:

    • Polygons and Boundaries: A Polygon does not contain its own boundary. A LineString that matches a Polygon's exterior ring will return false for contains?, though it will return true for intersects?.
    • LineStrings and Endpoints: A LineString does not contain its own first and last points unless they are the same (forming a LinearRing).
    # Example: Polygon boundary case
    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
    
    # Example: LineString endpoint case
    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
  8. Check if geometries are equivalent with equals?

    master

    Returns true if geometries A and B are equivalent and cover the exact same set of points.

    Equality is defined as A containing B AND B containing A. Equality does not require the geometries to be of the same type; for example, a Point is equal to a MultiPoint that contains only that same point.

    Topo.equals?(a, b)