Spaces:
Runtime error
Runtime error
Commit
·
2f322ce
1
Parent(s):
1d6d0dd
Fix area using geographiclib-geodesic package
Browse files- geodesiclib package is deprecated (2025). so i used geographiclib-geodesic package
- did plenty of trials on how to accurately calculated geodesic polygon's area.
- got error whenever I selected points in china or anywhere near that longitude.
- found out that i was actually inverting the (lat, lon) input to (lon, lat) and lat cannon > 90, while lon can be.
- frontend/package-lock.json +7 -0
- frontend/package.json +1 -0
- frontend/src/components/Map.js +1 -1
- frontend/src/utils/mapUtils.js +19 -55
frontend/package-lock.json
CHANGED
@@ -13,6 +13,7 @@
|
|
13 |
"@testing-library/react": "^16.3.0",
|
14 |
"@testing-library/user-event": "^13.5.0",
|
15 |
"geodesy": "^2.4.0",
|
|
|
16 |
"leaflet": "^1.9.4",
|
17 |
"leaflet.geodesic": "^2.7.1",
|
18 |
"react": "^19.0.0-rc.1",
|
@@ -8509,6 +8510,12 @@
|
|
8509 |
"node": ">=8.0.0"
|
8510 |
}
|
8511 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
8512 |
"node_modules/get-caller-file": {
|
8513 |
"version": "2.0.5",
|
8514 |
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
|
|
13 |
"@testing-library/react": "^16.3.0",
|
14 |
"@testing-library/user-event": "^13.5.0",
|
15 |
"geodesy": "^2.4.0",
|
16 |
+
"geographiclib-geodesic": "^2.1.1",
|
17 |
"leaflet": "^1.9.4",
|
18 |
"leaflet.geodesic": "^2.7.1",
|
19 |
"react": "^19.0.0-rc.1",
|
|
|
8510 |
"node": ">=8.0.0"
|
8511 |
}
|
8512 |
},
|
8513 |
+
"node_modules/geographiclib-geodesic": {
|
8514 |
+
"version": "2.1.1",
|
8515 |
+
"resolved": "https://registry.npmjs.org/geographiclib-geodesic/-/geographiclib-geodesic-2.1.1.tgz",
|
8516 |
+
"integrity": "sha512-lkd8EUkPSByobWu9BPMHTdYA5AUZxOa8McmUNtBE9KrvUJEvSADnN6gTDmhXbi6NzdA16LtWLpSxLE/lIIRhyA==",
|
8517 |
+
"license": "MIT"
|
8518 |
+
},
|
8519 |
"node_modules/get-caller-file": {
|
8520 |
"version": "2.0.5",
|
8521 |
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
frontend/package.json
CHANGED
@@ -8,6 +8,7 @@
|
|
8 |
"@testing-library/react": "^16.3.0",
|
9 |
"@testing-library/user-event": "^13.5.0",
|
10 |
"geodesy": "^2.4.0",
|
|
|
11 |
"leaflet": "^1.9.4",
|
12 |
"leaflet.geodesic": "^2.7.1",
|
13 |
"react": "^19.0.0-rc.1",
|
|
|
8 |
"@testing-library/react": "^16.3.0",
|
9 |
"@testing-library/user-event": "^13.5.0",
|
10 |
"geodesy": "^2.4.0",
|
11 |
+
"geographiclib-geodesic": "^2.1.1",
|
12 |
"leaflet": "^1.9.4",
|
13 |
"leaflet.geodesic": "^2.7.1",
|
14 |
"react": "^19.0.0-rc.1",
|
frontend/src/components/Map.js
CHANGED
@@ -276,7 +276,7 @@ const Map = ( { onMapClick, searchQuery, contentType } ) => {
|
|
276 |
if (geoToolMode === "area" && areaPoints.length >= 3) {
|
277 |
// Just ensuring that the polygon is closed (first == last)
|
278 |
const closed = [...areaPoints, areaPoints[0]];
|
279 |
-
const area = calculatePolygonArea(closed.map(([lat, lon]) => [
|
280 |
setPolygonArea(area);
|
281 |
} else {
|
282 |
setPolygonArea(null);
|
|
|
276 |
if (geoToolMode === "area" && areaPoints.length >= 3) {
|
277 |
// Just ensuring that the polygon is closed (first == last)
|
278 |
const closed = [...areaPoints, areaPoints[0]];
|
279 |
+
const area = calculatePolygonArea(closed.map(([lat, lon]) => [lat, lon])); // This took me a while to figure out, it should be just (lat, lon), not (lon, lat)
|
280 |
setPolygonArea(area);
|
281 |
} else {
|
282 |
setPolygonArea(null);
|
frontend/src/utils/mapUtils.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
|
2 |
// Haversine-based geodesic interpolator
|
3 |
function generateGeodesicPoints(lat1, lon1, lat2, lon2, numPoints = 512) {
|
4 |
/**
|
@@ -47,61 +47,24 @@ function generateGeodesicPoints(lat1, lon1, lat2, lon2, numPoints = 512) {
|
|
47 |
|
48 |
|
49 |
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
const f = 1 / 298.257223563; // Flattening
|
63 |
-
const e2 = f * (2 - f); // First eccentricity squared
|
64 |
-
|
65 |
-
// Ensure polygon is closed
|
66 |
-
const coords = [...coordinates];
|
67 |
-
if (coords[0][0] !== coords[coords.length - 1][0] ||
|
68 |
-
coords[0][1] !== coords[coords.length - 1][1]) {
|
69 |
-
coords.push(coords[0]);
|
70 |
-
}
|
71 |
-
|
72 |
-
let area = 0;
|
73 |
-
const n = coords.length - 1;
|
74 |
-
|
75 |
-
// Calculate area using simplified geodesic excess method
|
76 |
-
for (let i = 0; i < n; i++) {
|
77 |
-
const [lat1, lon1] = coords[i];
|
78 |
-
const [lat2, lon2] = coords[i + 1];
|
79 |
-
|
80 |
-
// Convert to radians
|
81 |
-
const phi1 = lat1 * Math.PI / 180;
|
82 |
-
const phi2 = lat2 * Math.PI / 180;
|
83 |
-
let dL = (lon2 - lon1) * Math.PI / 180;
|
84 |
-
|
85 |
-
// Normalize longitude difference
|
86 |
-
while (dL > Math.PI) dL -= 2 * Math.PI;
|
87 |
-
while (dL < -Math.PI) dL += 2 * Math.PI;
|
88 |
-
|
89 |
-
// Geodesic excess contribution
|
90 |
-
const E = 2 * Math.atan2(
|
91 |
-
Math.tan(dL / 2) * (Math.sin(phi1) + Math.sin(phi2)),
|
92 |
-
2 + Math.sin(phi1) * Math.sin(phi2) + Math.cos(phi1) * Math.cos(phi2) * Math.cos(dL)
|
93 |
-
);
|
94 |
-
|
95 |
-
area += E;
|
96 |
-
}
|
97 |
-
|
98 |
-
// Convert to actual area using ellipsoid parameters
|
99 |
-
const ellipsoidArea = Math.abs(area) * (a * a / 2) * (1 - e2);
|
100 |
-
|
101 |
-
return ellipsoidArea; // Return area in square meters
|
102 |
-
|
103 |
-
}
|
104 |
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
function getPolygonCentroid(points) {
|
107 |
// Simple centroid calculation for small polygons
|
@@ -113,6 +76,7 @@ function getPolygonCentroid(points) {
|
|
113 |
function formatArea(area, unit = 'sqm', format = "normal") {
|
114 |
|
115 |
if (typeof area !== 'number' || isNaN(area)) {
|
|
|
116 |
return 'Invalid area';
|
117 |
}
|
118 |
let value;
|
|
|
1 |
+
import geodesic from 'geographiclib-geodesic';
|
2 |
// Haversine-based geodesic interpolator
|
3 |
function generateGeodesicPoints(lat1, lon1, lat2, lon2, numPoints = 512) {
|
4 |
/**
|
|
|
47 |
|
48 |
|
49 |
|
50 |
+
function calculatePolygonArea(coords) {
|
51 |
+
/*** Calculate the geodesic area of a polygon on the WGS84 ellipsoid.
|
52 |
+
* @param {Array<Array<number>>} coords - Array of [lat, lon] pairs.
|
53 |
+
* @returns {number} Area in square meters.
|
54 |
+
*/
|
55 |
+
// console.log(coords); // Lifesaver
|
56 |
+
const geod = geodesic.Geodesic.WGS84;
|
57 |
+
const poly = geod.Polygon(false); // false = polygon, not polyline
|
58 |
+
|
59 |
+
for (const [lat, lon] of coords) {
|
60 |
+
poly.AddPoint(lat, lon);
|
61 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
+
// Closing the polygon is not required as I am already doing it in the frontend.
|
64 |
+
|
65 |
+
const result = poly.Compute();
|
66 |
+
return result.area; // area in square meters, important.
|
67 |
+
}
|
68 |
|
69 |
function getPolygonCentroid(points) {
|
70 |
// Simple centroid calculation for small polygons
|
|
|
76 |
function formatArea(area, unit = 'sqm', format = "normal") {
|
77 |
|
78 |
if (typeof area !== 'number' || isNaN(area)) {
|
79 |
+
console.log('Invalid area input:', area);
|
80 |
return 'Invalid area';
|
81 |
}
|
82 |
let value;
|