Spaces:
Runtime error
Runtime error
Commit
·
5e62073
1
Parent(s):
efcf6f0
Display perimeter in addition to area of a closed polygon
Browse files- README.md +4 -0
- frontend/src/components/Map.js +16 -2
- frontend/src/utils/mapUtils.js +34 -3
README.md
CHANGED
@@ -19,3 +19,7 @@ pinned: false
|
|
19 |
- Use `localtunnel` for backend, `ngrok http 8004` (I don't want remote users to have to enter password or click on a suspicious looking link).
|
20 |
- It works, I tested it on my phone, but it doesn't work on sandbox, most likely due to sandbox's requirement of `Authentication` from backend. Throws an `Error 511`.
|
21 |
- Unfortunately, user still has to visit the `localtunnel` link to avoid `Network Authentication` issue. For that they would reuire the `localtunnel` link, and a password, which can be obtianed from `https://loca.lt/mytunnelpassword`, on the device that is hosting the codebase.
|
|
|
|
|
|
|
|
|
|
19 |
- Use `localtunnel` for backend, `ngrok http 8004` (I don't want remote users to have to enter password or click on a suspicious looking link).
|
20 |
- It works, I tested it on my phone, but it doesn't work on sandbox, most likely due to sandbox's requirement of `Authentication` from backend. Throws an `Error 511`.
|
21 |
- Unfortunately, user still has to visit the `localtunnel` link to avoid `Network Authentication` issue. For that they would reuire the `localtunnel` link, and a password, which can be obtianed from `https://loca.lt/mytunnelpassword`, on the device that is hosting the codebase.
|
22 |
+
|
23 |
+
## Resources
|
24 |
+
- [Geodesic Area calculator](https://geographiclib.sourceforge.io/cgi-bin/Planimeter?type=polygon&rhumb=geodesic&radius=6378137&flattening=1%2F298.257223563&input=40.7128%2C+-74.0060%0D%0A34.0522%2C+-118.2437%0D%0A51.5074%2C+0.1278&option=Submit)
|
25 |
+
- [Area calculator function in geographiclib-geodesic module's codebase](https://github.com/geographiclib/geographiclib-js/blob/main/geodesic/PolygonArea.js)
|
frontend/src/components/Map.js
CHANGED
@@ -12,7 +12,7 @@ import { MapContainer, TileLayer,
|
|
12 |
} from 'react-leaflet';
|
13 |
import L from 'leaflet';
|
14 |
import 'leaflet/dist/leaflet.css';
|
15 |
-
import { generateGeodesicPoints, calculatePolygonArea, getPolygonCentroid, formatArea } from '../utils/mapUtils';
|
16 |
|
17 |
|
18 |
delete L.Icon.Default.prototype._getIconUrl;
|
@@ -70,6 +70,8 @@ const Map = ( { onMapClick, searchQuery, contentType } ) => {
|
|
70 |
|
71 |
const [numberFormat, setNumberFormat] = useState('normal'); // 'normal' | 'scientific'
|
72 |
|
|
|
|
|
73 |
const handleMouseDown = (e) => {
|
74 |
isDragging.current = true;
|
75 |
startX.current = e.clientX;
|
@@ -276,10 +278,13 @@ 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); // 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);
|
|
|
283 |
}
|
284 |
}, [geoToolMode, areaPoints]);
|
285 |
|
@@ -676,6 +681,7 @@ const Map = ( { onMapClick, searchQuery, contentType } ) => {
|
|
676 |
setGeoToolMode("menu");
|
677 |
setAreaPoints([]);
|
678 |
setPolygonArea(null);
|
|
|
679 |
}}
|
680 |
style={{
|
681 |
background: 'none',
|
@@ -692,11 +698,17 @@ const Map = ( { onMapClick, searchQuery, contentType } ) => {
|
|
692 |
{formatArea(polygonArea, areaUnit, numberFormat)}
|
693 |
</div>
|
694 |
)}
|
|
|
|
|
|
|
|
|
|
|
695 |
<button
|
696 |
onClick={() => {
|
697 |
setGeoToolMode("menu");
|
698 |
setAreaPoints([]);
|
699 |
setPolygonArea(null);
|
|
|
700 |
}}
|
701 |
style={{
|
702 |
marginTop: 8,
|
@@ -722,6 +734,8 @@ const Map = ( { onMapClick, searchQuery, contentType } ) => {
|
|
722 |
<option value="km2">km²</option>
|
723 |
<option value="ha">ha</option>
|
724 |
<option value="mi2">mi²</option>
|
|
|
|
|
725 |
</select>
|
726 |
</div>
|
727 |
<div>
|
|
|
12 |
} from 'react-leaflet';
|
13 |
import L from 'leaflet';
|
14 |
import 'leaflet/dist/leaflet.css';
|
15 |
+
import { generateGeodesicPoints, calculatePolygonArea, getPolygonCentroid, formatArea, formatPerimeter } from '../utils/mapUtils';
|
16 |
|
17 |
|
18 |
delete L.Icon.Default.prototype._getIconUrl;
|
|
|
70 |
|
71 |
const [numberFormat, setNumberFormat] = useState('normal'); // 'normal' | 'scientific'
|
72 |
|
73 |
+
const [polygonPerimeter, setPolygonPerimeter] = useState(null);
|
74 |
+
|
75 |
const handleMouseDown = (e) => {
|
76 |
isDragging.current = true;
|
77 |
startX.current = e.clientX;
|
|
|
278 |
if (geoToolMode === "area" && areaPoints.length >= 3) {
|
279 |
// Just ensuring that the polygon is closed (first == last)
|
280 |
const closed = [...areaPoints, areaPoints[0]];
|
281 |
+
const {area, perimeter} = calculatePolygonArea(closed); // This took me a while to figure out, it should be just (lat, lon), not (lon, lat)
|
282 |
setPolygonArea(area);
|
283 |
+
setPolygonPerimeter(perimeter);
|
284 |
+
// console.log("Polygon area:", area, "Perimeter:", perimeter);
|
285 |
} else {
|
286 |
setPolygonArea(null);
|
287 |
+
setPolygonPerimeter(null);
|
288 |
}
|
289 |
}, [geoToolMode, areaPoints]);
|
290 |
|
|
|
681 |
setGeoToolMode("menu");
|
682 |
setAreaPoints([]);
|
683 |
setPolygonArea(null);
|
684 |
+
setPolygonPerimeter(null);
|
685 |
}}
|
686 |
style={{
|
687 |
background: 'none',
|
|
|
698 |
{formatArea(polygonArea, areaUnit, numberFormat)}
|
699 |
</div>
|
700 |
)}
|
701 |
+
{polygonPerimeter !== null && (
|
702 |
+
<div style={{ fontSize: 16, color: '#555' }}>
|
703 |
+
Perimeter: {formatPerimeter(polygonPerimeter, areaUnit, numberFormat)}
|
704 |
+
</div>
|
705 |
+
)}
|
706 |
<button
|
707 |
onClick={() => {
|
708 |
setGeoToolMode("menu");
|
709 |
setAreaPoints([]);
|
710 |
setPolygonArea(null);
|
711 |
+
setPolygonPerimeter(null);
|
712 |
}}
|
713 |
style={{
|
714 |
marginTop: 8,
|
|
|
734 |
<option value="km2">km²</option>
|
735 |
<option value="ha">ha</option>
|
736 |
<option value="mi2">mi²</option>
|
737 |
+
<option value="acres">acres</option>
|
738 |
+
<option value="sqft">ft²</option>
|
739 |
</select>
|
740 |
</div>
|
741 |
<div>
|
frontend/src/utils/mapUtils.js
CHANGED
@@ -62,8 +62,9 @@ function calculatePolygonArea(coords) {
|
|
62 |
|
63 |
// Closing the polygon is not required as I am already doing it in the frontend.
|
64 |
|
65 |
-
const result = poly.Compute();
|
66 |
-
|
|
|
67 |
}
|
68 |
|
69 |
function getPolygonCentroid(points) {
|
@@ -96,11 +97,41 @@ function formatArea(area, unit = 'sqm', format = "normal") {
|
|
96 |
case "mi2":
|
97 |
value = area / 2589988.110336;
|
98 |
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' mi²';
|
|
|
|
|
|
|
99 |
default:
|
100 |
value = area;
|
101 |
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' m²';
|
102 |
}
|
103 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
|
105 |
-
export {generateGeodesicPoints, calculatePolygonArea, getPolygonCentroid, formatArea};
|
106 |
// calculatePolygonArea
|
|
|
62 |
|
63 |
// Closing the polygon is not required as I am already doing it in the frontend.
|
64 |
|
65 |
+
const result = poly.Compute(); // Returns object (number (of vertices), perimeter, area)
|
66 |
+
//console.log(result)
|
67 |
+
return {area: result.area, perimeter: result.perimeter}; // area in square meters, important.
|
68 |
}
|
69 |
|
70 |
function getPolygonCentroid(points) {
|
|
|
97 |
case "mi2":
|
98 |
value = area / 2589988.110336;
|
99 |
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' mi²';
|
100 |
+
case "sqft":
|
101 |
+
value = area * 10.76391041671;
|
102 |
+
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' ft²';
|
103 |
default:
|
104 |
value = area;
|
105 |
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' m²';
|
106 |
}
|
107 |
}
|
108 |
+
function formatPerimeter(perimeter, unit = 'sqm', format = "normal") {
|
109 |
+
if (typeof perimeter !== 'number' || isNaN(perimeter)) {
|
110 |
+
console.log('Invalid perimeter input:', perimeter);
|
111 |
+
return 'Invalid perimeter';
|
112 |
+
}
|
113 |
+
let value;
|
114 |
+
switch (unit) {
|
115 |
+
case "km2":
|
116 |
+
value = perimeter / 1000;
|
117 |
+
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' km';
|
118 |
+
case "ha":
|
119 |
+
value = perimeter / 1000;
|
120 |
+
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' km';
|
121 |
+
case "m2":
|
122 |
+
value = perimeter;
|
123 |
+
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' m';
|
124 |
+
case "mi2":
|
125 |
+
value = perimeter / 1609.344;
|
126 |
+
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' mi';
|
127 |
+
case "sqft":
|
128 |
+
value = perimeter * 3.280839895013123; // meters to feet
|
129 |
+
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' ft';
|
130 |
+
default:
|
131 |
+
value = perimeter;
|
132 |
+
return (format === "scientific" ? value.toExponential(2) : value.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })) + ' m';
|
133 |
+
}
|
134 |
+
}
|
135 |
|
136 |
+
export {generateGeodesicPoints, calculatePolygonArea, getPolygonCentroid, formatArea, formatPerimeter};
|
137 |
// calculatePolygonArea
|