import { area as turfArea } from "@turf/area" import { Polygon, MultiPolygon, Feature, Geometry, GeoJsonObject } from "geojson" // @ts-ignore import { Geodesic } from "geographiclib-geodesic" /** * Calculates the area of a GeoJSON polygon or multipolygon with high accuracy. * * This function uses several strategies for accurate area calculation: * 1. First attempts to use geodesic calculations via geographiclib-geodesic * 2. Falls back to @turf/area if geodesic calculation fails * * The geodesic approach provides better accuracy by calculating areas on the WGS84 ellipsoid, * compared to @turf/area which uses spherical approximation. * * @param geojson - A GeoJSON Polygon, MultiPolygon, Feature, or FeatureCollection * @returns Area in square meters */ export function calculateArea(geojson: GeoJsonObject): number { // Extract geometry from feature if needed let geometry: Geometry | null = null if (geojson.type === "Feature") { geometry = (geojson as Feature).geometry } else if (geojson.type === "FeatureCollection") { // Sum areas of all features let totalArea = 0 for (const feature of (geojson as any).features) { if (feature.geometry) { totalArea += calculateArea(feature.geometry) } } return totalArea } else if (geojson.type === "Polygon" || geojson.type === "MultiPolygon") { geometry = geojson as Geometry } if (!geometry || (geometry.type !== "Polygon" && geometry.type !== "MultiPolygon")) { return 0 } try { // Try to use proj4 for reprojection to equal-area projection return calculateGeodesicArea(geometry as Polygon | MultiPolygon) } catch (error) { console.warn("Falling back to @turf/area due to geodesic error:", error) // Fall back to @turf/area return turfArea(geometry) } } function calculateGeodesicArea(geometry: Polygon | MultiPolygon): number { const geod = Geodesic.WGS84 let area = 0 if (geometry.type === "Polygon") { area = calculatePolygonGeodesicArea(geometry.coordinates, geod) } else { for (const polygon of geometry.coordinates) { area += calculatePolygonGeodesicArea(polygon, geod) } } return area } function calculatePolygonGeodesicArea(rings: number[][][], geod: any): number { let totalArea = 0 // The first ring is the exterior ring, the rest are holes. // GeoJSON winding order (CCW for exterior, CW for holes) will result in // positive and negative areas, so we can just sum them up. for (const ring of rings) { const p = geod.Polygon(false) for (const point of ring) { p.AddPoint(point[1], point[0]) } const result = p.Compute(false, true) totalArea += result.area } return Math.abs(totalArea) }