I'm trying to calculate the surface area of a building using the Overpass API, OpenStreetMap API, ReactJS. Here's the code:
import { useState, useEffect } from 'react';
import axios from 'axios';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css'; // Import Leaflet CSS
import 'leaflet/dist/leaflet-src.esm'; // Import Leaflet as ES module
import * as turf from '@turf/turf'; // Import turf library
const MapComponent = () => {
const [buildingData, setBuildingData] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
// Make a request to the Overpass API
const response = await axios.get('https://api.openstreetmap.org/api/interpreter');
setBuildingData(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []); // Run once on component mount
const calculateArea = () => {
if (!buildingData || buildingData.features.length === 0) {
return { areaSquareMeters: 0, areaSquareKilometers: 0 };
}
// Assuming buildingData is in a suitable format, adjust accordingly
const osmCoordinates = buildingData.features[0].geometry.coordinates[0];
// Convert OSM coordinates to Leaflet-compatible coordinates
const leafletCoordinates = osmCoordinates.map(([lon, lat]) => [lat, lon]);
// Create a Leaflet map with a simple base layer
const map = L.map('map').setView([59.132659900251944, 9.727169813491393], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
// Create a Leaflet polygon
const polygon = new L.Polygon(leafletCoordinates);
// Add the polygon to the map
polygon.addTo(map);
// Convert Leaflet polygon coordinates to GeoJSON format
const geoJsonPolygon = {
type: 'Polygon',
coordinates: [leafletCoordinates],
};
// Calculate the area using the turf library
const areaSquareMeters = turf.area(geoJsonPolygon);
return {
areaSquareMeters
};
};
const area = calculateArea();
return (
<div style={{ width: "100%", height: "100vh", display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column" }}>
<p>Area: {area.areaSquareMeters} square meters</p>
<div id="map" style={{ height: '500px', width: '500px', border: "3.2px solid red" }}></div>
</div>
);
};
export default MapComponent;
I'm expecting a map to show (id="map" container) but it doesn't, and the area outputs 0. I've figured out that the problem lies with the Overpass API Endpoint (https://api.openstreetmap.org/api/interpreter).
Is there a way to fix this?
You Component needs there change two items
API Endpoint: Later, you switched to using https://overpass-api.de/api/interpreter, which is specifically designed for complex and extensive queries on the OpenStreetMap database.
And not GET
, should be POST
call.
Query Format: The query format in Overpass QL was likely enhanced to include more specific parameters, filters, or bounding box coordinates to fetch more targeted data like buildings within a certain radius.
More documentation in here
https://api.openstreetmap.org/api/interpreter
https://overpass-api.de/api/interpreter
Body with text
format
[out:json];
(
// Fetch buildings in a specific area
way["building"](around:3000,59.132659900251944, 9.727169813491393);
);
out body;
>;
out skel qt;
Save as 'MapComponent.js'
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 MapComponent = () => {
const [buildingData, setBuildingData] = useState(null);
const [area, setArea] = useState(0);
const mapRef = useRef(null);
// Initialize the map
useEffect(() => {
if (!mapRef.current) {
mapRef.current = L.map('map').setView([59.132659900251944, 9.727169813491393], 13); // Centered on Paris
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(mapRef.current);
}
}, []);
// Fetch building data
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.post(
'https://overpass-api.de/api/interpreter',
`[out:json];(way["building"](around:3000,59.132659900251944, 9.727169813491393););out body;>;out skel qt;`,
{
headers: { 'Content-Type': 'text/plain' }
}
);
setBuildingData(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
const calculateAndDisplayArea = useCallback(() => {
if (buildingData && mapRef.current) {
let totalArea = 0;
const nodeMapping = {}; // Assuming you have this mapping from node ID to { lat, lon }
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];
});
if (coordinates.length > 0) {
L.polygon(coordinates).addTo(mapRef.current); // Add polygon to map
const geoJsonPolygon = {
type: 'Polygon',
coordinates: [coordinates.map(coord => [coord[1], coord[0]])], // Convert to [lon, lat]
};
totalArea += turf.area(geoJsonPolygon);
}
}
});
setArea(totalArea);
}
}, [buildingData]);
useEffect(() => {
calculateAndDisplayArea();
}, [calculateAndDisplayArea, buildingData]);
return (
<div>
<p>Area: {area.toFixed(2)} square meters</p>
<div id="map" style={{ height: '800px', width: '1000px', margin: '0 auto'}}></div>
</div>
);
};
export default MapComponent;
The text "Area: 303102.35 square meters" represents This means that the total area covered by the buildings or features you queried is approximately 303,102.35 square meters. It's a quantitative measure of how much surface area these buildings occupy.
Zoom in for seeing detail polygon
the calculateAndDisplayArea function checks the building tag of each feature and applies a blue color for "office" buildings and a red color for "industrial" buildings.
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 MapComponent = () => {
const [buildingData, setBuildingData] = useState(null);
const [area, setArea] = useState(0);
const mapRef = useRef(null);
// Initialize the map
useEffect(() => {
if (!mapRef.current) {
mapRef.current = L.map('map').setView([59.132659900251944, 9.727169813491393], 13); // Centered on Paris
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(mapRef.current);
}
}, []);
// Fetch building data
useEffect(() => {
const fetchData = async () => {
try {
const query = `[out:json];
(
way["building"="office"](around:3000,59.132659900251944, 9.727169813491393);
way["building"="industrial"](around:3000,59.132659900251944, 9.727169813491393);
);
out body; >; out skel qt;`;
const response = await axios.post(
'https://overpass-api.de/api/interpreter', query, {
headers: { 'Content-Type': 'text/plain' }
}
);
setBuildingData(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
const calculateAndDisplayArea = useCallback(() => {
if (buildingData && mapRef.current) {
let totalArea = 0;
const nodeMapping = {}; // Assuming you have this mapping from node ID to { lat, lon }
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];
});
if (coordinates.length > 0) {
// Check building type and apply color
const buildingType = feature.tags?.building;
const color = buildingType === 'office' ? 'blue' : buildingType === 'industrial' ? 'red' : 'grey';
L.polygon(coordinates, { color }).addTo(mapRef.current); // Apply color to polygon
const geoJsonPolygon = {
type: 'Polygon',
coordinates: [coordinates.map(coord => [coord[1], coord[0]])], // Convert to [lon, lat]
};
totalArea += turf.area(geoJsonPolygon);
}
}
});
setArea(totalArea);
}
}, [buildingData]);
useEffect(() => {
calculateAndDisplayArea();
}, [calculateAndDisplayArea, buildingData]);
return (
<div>
<p>Area: {area.toFixed(2)} square meters</p>
<div id="map" style={{ height: '800px', width: '1000px', margin: '0 auto'}}></div>
</div>
);
};
export default MapComponent;