pointOnFeature function

Feature<Point> pointOnFeature(
  1. dynamic featureInput
)

Returns a Feature<Point> that represents a point guaranteed to be on the feature.

  • For Point geometries: returns the original point
  • For Polygon geometries: computes a point inside the polygon (preference to centroid)
  • For MultiPolygon geometries: uses the first polygon to compute a point
  • For LineString geometries: computes the midpoint along the line
  • For FeatureCollection: returns a point on the largest feature

The resulting point is guaranteed to be on the feature.

Throws an ArgumentError if the input type is unsupported or if a valid point cannot be computed.

Implementation

Feature<Point> pointOnFeature(dynamic featureInput) {
  // Handle FeatureCollection
  if (featureInput is FeatureCollection) {
    if (featureInput.features.isEmpty) {
      throw ArgumentError('Cannot compute point on empty FeatureCollection');
    }

    // Find the largest feature in the collection
    Feature largestFeature = featureInput.features.first;
    double maxSize = _calculateFeatureSize(largestFeature);

    for (final feature in featureInput.features.skip(1)) {
      final size = _calculateFeatureSize(feature);
      if (size > maxSize) {
        maxSize = size;
        largestFeature = feature;
      }
    }

    // Get a point on the largest feature
    return pointOnFeature(largestFeature);
  }

  // Handle individual feature
  if (featureInput is Feature) {
    final geometry = featureInput.geometry;

    if (geometry is Point) {
      // Already a point: return it.
      return Feature<Point>(
          geometry: geometry, properties: featureInput.properties);
    } else if (geometry is LineString) {
      // For LineString: compute the midpoint
      return _midpointOnLine(geometry, featureInput.properties);
    } else if (geometry is Polygon) {
      // Use the existing centroid function
      final Feature<Point> centroidFeature = turf_centroid.centroid(
        featureInput,
        properties: featureInput.properties,
      );
      // Use non-null assertion operator since we know the geometry exists
      final Point centroid = centroidFeature.geometry!;
      // Convert Point to Position for boolean check
      final pointPos = Position(
          centroid.coordinates[0] ?? 0.0, centroid.coordinates[1] ?? 0.0);

      // Use point-in-polygon from turf_pip package directly
      final pipResult = pointInPolygon(Point(coordinates: pointPos), geometry);
      if (pipResult == PointInPolygonResult.isInside ||
          pipResult == PointInPolygonResult.isOnEdge) {
        return centroidFeature;
      } else {
        // Try each vertex of the outer ring.
        final outerRing = geometry.coordinates.first;
        for (final pos in outerRing) {
          final candidate = Point(coordinates: pos);
          final candidatePos = Position(
              candidate.coordinates[0] ?? 0.0, candidate.coordinates[1] ?? 0.0);
          final candidatePipResult =
              pointInPolygon(Point(coordinates: candidatePos), geometry);
          if (candidatePipResult == PointInPolygonResult.isInside ||
              candidatePipResult == PointInPolygonResult.isOnEdge) {
            return Feature<Point>(
                geometry: candidate, properties: featureInput.properties);
          }
        }
        // Fallback: return the centroid.
        return centroidFeature;
      }
    } else if (geometry is MultiPolygon) {
      // Use the first polygon from the MultiPolygon.
      if (geometry.coordinates.isNotEmpty &&
          geometry.coordinates.first.isNotEmpty) {
        final firstPoly = Polygon(coordinates: geometry.coordinates.first);
        return pointOnFeature(
            Feature(geometry: firstPoly, properties: featureInput.properties));
      }
      throw ArgumentError('Cannot compute point on empty MultiPolygon');
    } else {
      throw ArgumentError('Unsupported geometry type: ${geometry.runtimeType}');
    }
  }

  // If we reach here, the input type is unsupported
  throw ArgumentError('Unsupported input type: ${featureInput.runtimeType}');
}