navibyte / geospatial

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

Centroid for all geometry objects #223

Closed jaumard closed 2 months ago

jaumard commented 5 months ago

Hello,

I was hoping to find a .center available on the geometry collection or polygon but look like it doesn't exist.

Do you know any way to have that center ? Or calculate it?

navispatial commented 3 months ago

Sorry for the late answer, but if you have not found an answer yet here some hints for current capabilities of the library.

See blog post: https://medium.com/@navibyte/geospatial-tools-for-dart-version-1-0-published-0f9673e510b3

There is a section about Operators and functions on cartesian positions.

A PositionSeries containing a closed linear ring (or simple polygon) could be created for example:

// a closed linear ring with positions in the counterclockwise (CCW) order
final polygon = [
  [1.0, 6.0].xy,
  [3.0, 1.0].xy,
  [7.0, 2.0].xy,
  [4.0, 4.0].xy,
  [8.0, 5.0].xy,
  [1.0, 6.0].xy,
].series();

Now you can calculate a centroid point of such a polygon, this sample prints coordinates:

// the centroid position of a polygon - prints "3.9,3.7"
print(polygon.centroid2D()!.toText(decimals: 1));

See source code for a position series, it has centroid2D method:

  /// 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;
  }

A centroid for a closed linear ring of positions (or a simple polygon) might be center point asked.

If you have a Polygon geometry object, then you should access an outer linear ring of a polygon using exterior member, and use centroid2D method to get a centroid position for that.

However currently not supported calculating a centroid for:

Implementing these might be a good possible future feature to add.

navispatial commented 2 months ago

Implemented in geobase version 1.1.0, see milestone.