DebasishDhal99 commited on
Commit
5e62073
·
1 Parent(s): efcf6f0

Display perimeter in addition to area of a closed polygon

Browse files
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
- return result.area; // area in square meters, important.
 
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