navibyte / geospatial

Geospatial data structures, tools and utilities for Dart and Flutter.
Other
52 stars 5 forks source link

Add basic geometry calculation functions #191

Closed navispatial closed 11 months ago

navispatial commented 1 year ago

Add basic geometry calculation functions to geometry classes (Point, LineSegment, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection), like filter(), reverse(), normalize(), isClosed(), length(), area(), midPoint(), pointAlong(), etc. (not all valid for all geometry types).

The aim is not to be a complete geometric or topological model supporting all use cases in geospatial applications, but to cover some most used functions on geometry classes supported by the package. These functions are not following ellipsoidal or spherical geodesy (there is a separate classes for spherical geodesy on great circle and rhumb line calculations published on geobase 0.5.0).

References (and potentially porting some code):

navispatial commented 11 months ago

For example calculating centroid (see also #220 about dimensionalities):

 /// Returns the centroid of a geometry represented by this position series
  /// calculated in a cartesian 2D plane.
  ///
  /// The *centroid* is - as by definition - *a geometric center of mass of a
  /// geometry*.
  ///
  /// The centroid is computed according to [dimensionality]:
  /// * `Dimensionality.volymetric`: not supported, works as `areal`
  /// * `Dimensionality.areal`: weighted by the area with this position series
  ///    representing a polygon with positions in the counterclockwise (CCW)
  ///    order.
  /// * `Dimensionality.linear`: computed from midpoints of line segments that
  ///    are weighted by the length of each line segment.
  /// * `Dimensionality.punctual`: the arithmetic mean of all separate
  ///    positions in this series.
  ///
  /// Returns null if a centroid position could not be calculated.
  ///
  /// See also [Centroid](https://en.wikipedia.org/wiki/Centroid) in Wikipedia.
  Position? centroid2D({
    Dimensionality dimensionality = Dimensionality.areal,
  }) {
    final topoDim = dimensionality.topologicalDimension;
    final posCount = positionCount;

    // Areal geometry (weighted by area triangles).
    if (topoDim >= 2 && posCount >= 3) {
      // See "Of a polygon" in https://en.wikipedia.org/wiki/Centroid
      var area = 0.0;
      var cx = 0.0;
      var cy = 0.0;
      var x1 = x(0);
      var y1 = y(0);
      for (var i = 1; i <= posCount; i++) {
        final isLast = i == posCount;
        final x2 = x(isLast ? 0 : i);
        final y2 = y(isLast ? 0 : i);
        final shoelace = x1 * y2 - x2 * y1;
        area += shoelace;
        cx += (x1 + x2) * shoelace;
        cy += (y1 + y2) * shoelace;
        x1 = x2;
        y1 = y2;
      }
      if (area.abs() > 0.0) {
        final area6 = 6.0 * (area / 2.0);
        return Position.create(
          x: cx / area6,
          y: cy / area6,
        );
      }
    }

    // Linear geometry (weighted by line segments).
    if (topoDim >= 1 && posCount >= 2) {
      var length = 0.0;
      var cx = 0.0;
      var cy = 0.0;
      var x1 = x(0);
      var y1 = y(0);
      for (var i = 1; i < posCount; i++) {
        final x2 = x(i);
        final y2 = y(i);
        final dx = x2 - x1;
        final dy = y2 - y1;
        final segmentLength = math.sqrt(dx * dx + dy * dy);
        if (segmentLength > 0.0) {
          length += segmentLength;
          cx += segmentLength * (x1 + x2) / 2.0;
          cy += segmentLength * (y1 + y2) / 2.0;
        }
        x1 = x2;
        y1 = y2;
      }
      if (length > 0.0) {
        return Position.create(
          x: cx / length,
          y: cy / length,
        );
      }
    }

    // Punctual geometry (arithmethic mean of all points).
    if (posCount >= 1) {
      var cx = 0.0;
      var cy = 0.0;
      for (var i = 0; i < posCount; i++) {
        cx += x(i);
        cy += y(i);
      }
      return Position.create(
        x: cx / posCount,
        y: cy / posCount,
      );
    }

    // could not calculate
    return null;
  }
navispatial commented 11 months ago

Basic functions implemented, however there still much work to do in future versions related to spatial predicates and geometry computation and manipulation functions.

Implemented in geobase 1.0.0.