I have the code below that shows the surface area of a building in OpenStreetMap when hovered. Here's the code:
import { useState, useEffect, useRef, useCallback } from 'react';
import axios from 'axios';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import * as turf from '@turf/turf';
const debounce = (func: any, wait: any) => {
let timeout: any;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
const MapComponent = () => {
const [area, setArea] = useState(0);
const [mousePosition, setMousePosition] = useState({ lat: 0, lon: 0 });
const mapRef = useRef(null);
// Initialize the map
useEffect(() => {
if (!mapRef.current) {
const map = L.map('map').setView([59.132659900251944, 9.727169813491393], 18);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
map.on('mousemove', (e) => {
setMousePosition({ lat: e.latlng.lat, lon: e.latlng.lng });
});
mapRef.current = map;
}
}, []);
const displayBuildings = useCallback((buildingData) => {
if (buildingData && mapRef.current) {
mapRef.current.eachLayer((layer) => {
if (layer instanceof L.Polygon) {
mapRef.current.removeLayer(layer);
}
});
let totalArea = 0;
const nodeMapping = {};
buildingData.elements.forEach(element => {
if (element.type === 'node') {
nodeMapping[element.id] = { lat: element.lat, lon: element.lon };
}
});
const features = buildingData.elements.filter(element => element.type === 'way');
features.forEach(feature => {
if (feature.nodes && feature.nodes.length > 0) {
const coordinates = feature.nodes.map(nodeId => {
const node = nodeMapping[nodeId];
return [node.lat, node.lon]; // Lon, Lat format for Leaflet
});
if (coordinates.length > 0) {
L.polygon(coordinates, { color: 'blue' }).addTo(mapRef.current);
const geoJsonPolygon = {
type: 'Polygon',
coordinates: [coordinates],
};
totalArea += turf.area(geoJsonPolygon);
}
}
});
setArea(totalArea);
}
}, []);
// Function to fetch and display building data
const fetchAndDisplayBuildingData = useCallback(async (lat, lon) => {
try {
const response = await axios.post(
'https://overpass-api.de/api/interpreter',
`[out:json];
(
is_in(${lat},${lon});
area._[building];
);
out body; >; out skel qt;`,
{
headers: { 'Content-Type': 'text/plain' }
}
);
displayBuildings(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
}, [displayBuildings]);
// Debounced version of fetchAndDisplayBuildingData
const debouncedFetchAndDisplay = useCallback(debounce(fetchAndDisplayBuildingData, 100), [
fetchAndDisplayBuildingData
]);
// Handle mouse movement
useEffect(() => {
if (mapRef.current) {
mapRef.current.on('mousemove', (e) => {
setMousePosition({ lat: e.latlng.lat, lon: e.latlng.lng });
debouncedFetchAndDisplay(e.latlng.lat, e.latlng.lng);
});
}
}, [debouncedFetchAndDisplay]);
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<p style={{ fontSize: '24px', textAlign: 'center' }}>Area: {area.toFixed(2)} square meters</p>
<p style={{ fontSize: '24px', textAlign: 'center' }}>Mouse Position: Latitude: {mousePosition.lat.toFixed(5)}, Longitude: {mousePosition.lon.toFixed(5)}</p>
<div id="map" style={{ height: '420px', width: '420px' }}></div>
</div>
);
};
export default MapComponent;
I want it to be selectable on click and have that value then saved. Multiple buildings need to be saved. In addition, is there a way to get the roof angle data as well?
I need the angle data as I need to calculate the true area of the roof of a building, not just the space it occupies on a 2D map. This specific true area value needs to then be saved.
How can I fix this?
Is there a way to get the roof angle data as well? No, I can't find the solution. I check Overpass documentation, There are no angle of building or roof data.
This demo can handle Multiple buildings save data by mouse click over the build area.
Save format example
Area: 197.70, Latitude: 59.13330, Longitude: 9.72721, Tags: {"building":"house","ref:bygningsnr":"300001805"}
Adding feature Tags and shows all of saved build information by Clicking.
const handleMouseClick = () => {
const newDataItem = {
area: area.toFixed(2),
geo_location: {
lat: mousePosition.lat.toFixed(5),
longitude: mousePosition.lon.toFixed(5)
},
tags: buildingTag
};
setDataToSave([...dataToSave, newDataItem]);
};
Call this call back function from map's <div>
tag by onClick()
event.
<div id="map" style={{ height: '800px', width: '100%', maxWidth: '1000px' }} onClick={handleMouseClick}></div>
Getting Tags information from Overpass API
if (element.type === 'way' && element.tags) {
setBuildingTag(JSON.stringify(element.tags));
}
Data source from Overpass
{
"type": "way",
"id": 944874632,
"nodes": [
8747572457,
8747572456,
8747572455,
8747572454,
8747572453,
8747557628,
8747557629,
8747572452,
8747572451,
8747572457
],
"tags": {
"building": "house",
"building:levels": "2",
"ref:bygningsnr": "22480235"
}
}
import React, { useState, useEffect, useRef, useCallback } from 'react';
import axios from 'axios';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import * as turf from '@turf/turf';
const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
const MapComponent = () => {
const [area, setArea] = useState(0);
const [mousePosition, setMousePosition] = useState({ lat: 0, lon: 0 });
const [dataToSave, setDataToSave] = useState([]);
const [buildingTag, setBuildingTag] = useState("");
const mapRef = useRef(null);
// Initialize the map
useEffect(() => {
if (!mapRef.current) {
const map = L.map('map').setView([59.132659900251944, 9.727169813491393], 18);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
map.on('mousemove', (e) => {
setMousePosition({ lat: e.latlng.lat, lon: e.latlng.lng });
});
mapRef.current = map;
}
}, []);
const displayBuildings = useCallback((buildingData) => {
if (buildingData && mapRef.current) {
mapRef.current.eachLayer((layer) => {
if (layer instanceof L.Polygon) {
mapRef.current.removeLayer(layer);
}
});
let totalArea = 0;
const nodeMapping = {};
buildingData.elements.forEach(element => {
if (element.type === 'node') {
nodeMapping[element.id] = { lat: element.lat, lon: element.lon };
}
if (element.type === 'way' && element.tags) {
setBuildingTag(JSON.stringify(element.tags));
}
});
const features = buildingData.elements.filter(element => element.type === 'way');
features.forEach(feature => {
if (feature.nodes && feature.nodes.length > 0) {
const coordinates = feature.nodes.map(nodeId => {
const node = nodeMapping[nodeId];
return [node.lat, node.lon]; // Lon, Lat format for Leaflet
});
if (coordinates.length > 0) {
L.polygon(coordinates, { color: 'blue' }).addTo(mapRef.current);
const geoJsonPolygon = {
type: 'Polygon',
coordinates: [coordinates],
};
totalArea += turf.area(geoJsonPolygon);
}
}
});
setArea(totalArea);
}
}, []);
// Function to fetch and display building data
const fetchAndDisplayBuildingData = useCallback(async (lat, lon) => {
try {
const response = await axios.post(
'https://overpass-api.de/api/interpreter',
`[out:json];
(
is_in(${lat},${lon});
area._[building];
);
out body; >; out skel qt;`,
{
headers: { 'Content-Type': 'text/plain' }
}
);
displayBuildings(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
}, [displayBuildings]);
const handleMouseClick = () => {
const newDataItem = {
area: area.toFixed(2),
geo_location: {
lat: mousePosition.lat.toFixed(5),
longitude: mousePosition.lon.toFixed(5)
},
tags: buildingTag
};
setDataToSave([...dataToSave, newDataItem]);
};
const handleClearData = () => {
setDataToSave([]);
};
// Debounced version of fetchAndDisplayBuildingData
const debouncedFetchAndDisplay = useCallback(debounce(fetchAndDisplayBuildingData, 100), [
fetchAndDisplayBuildingData
]);
// Handle mouse movement
useEffect(() => {
if (mapRef.current) {
mapRef.current.on('mousemove', (e) => {
setMousePosition({ lat: e.latlng.lat, lon: e.latlng.lng });
debouncedFetchAndDisplay(e.latlng.lat, e.latlng.lng);
});
}
}, [debouncedFetchAndDisplay]);
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<p style={{ fontSize: '24px', textAlign: 'center' }}>Area: {area.toFixed(2)} square meters</p>
<p style={{ fontSize: '24px', textAlign: 'center' }}>Mouse Position: Latitude: {mousePosition.lat.toFixed(5)}, Longitude: {mousePosition.lon.toFixed(5)}</p>
<p style={{ fontSize: '24px', textAlign: 'center' }}>Tag: {buildingTag}</p>
<div id="map" style={{ height: '800px', width: '100%', maxWidth: '1000px' }} onClick={handleMouseClick}></div>
<button onClick={handleClearData}>Clear data</button>
<div style={{ maxHeight: '400px', overflowY: 'auto' }}>
<h2>Saved Data</h2>
<ul>
{dataToSave.map((data, index) => (
<li key={index}>
Area: {data.area}, Latitude: {data.geo_location.lat}, Longitude: {data.geo_location.longitude}, Tags: {data.tags}
</li>
))}
</ul>
</div>
</div>
);
};
export default MapComponent;