ReactJS中如何通过Overpass API计算指定建筑的屋顶面积?
如何获取指定坐标处单个建筑的屋顶面积
我正尝试使用Overpass API、OpenStreetMap API和ReactJS计算指定坐标处单个建筑的屋顶面积,但当前代码返回的是周边所有建筑的总面积(约30000平方米)。以下是我的代码:
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 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], 20); // 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;
核心问题分析
当前代码存在两个关键问题:
- Overpass查询范围过大:
around:3000会拉取坐标周围3000米内的所有建筑,返回大量无关数据。 - 面积计算逻辑错误:代码累加了所有返回建筑的面积,而非仅目标单个建筑的面积。
修改步骤
1. 缩小Overpass查询范围
将查询语句中的around:3000改为极小范围(比如around:5),确保只获取坐标点所在的单个建筑:
const response = await axios.post( 'https://overpass-api.de/api/interpreter', `[out:json];(way["building"](around:5,59.132659900251944, 9.727169813491393););out body;>;out skel qt;`, { headers: { 'Content-Type': 'text/plain' } } );
如果已知目标建筑的OSM ID,直接用ID查询更精准:
`[out:json];way(你的建筑ID);out body;>;out skel qt;`
2. 修改面积计算逻辑,仅计算单个建筑的面积
去掉累加逻辑,只提取第一个匹配的建筑(或指定建筑)的面积:
const calculateAndDisplayArea = useCallback(() => { if (buildingData && mapRef.current) { const nodeMapping = {}; buildingData.elements.forEach(element => { if (element.type === 'node') { nodeMapping[element.id] = { lat: element.lat, lon: element.lon }; } }); // 只取第一个建筑way const targetBuilding = buildingData.elements.find(element => element.type === 'way'); if (!targetBuilding || !targetBuilding.nodes) return; const coordinates = targetBuilding.nodes.map(nodeId => { const node = nodeMapping[nodeId]; return [node.lat, node.lon]; }); if (coordinates.length > 0) { L.polygon(coordinates).addTo(mapRef.current); const geoJsonPolygon = { type: 'Polygon', coordinates: [coordinates.map(coord => [coord[1], coord[0]])], }; // 直接设置单个建筑的面积,而非累加 setArea(turf.area(geoJsonPolygon)); } } }, [buildingData]);
3. 可选:添加交互选择建筑
如果需要让用户点击地图选择建筑,给地图添加点击事件,获取点击坐标后重新查询该点附近的建筑:
// 修改初始化地图的useEffect,添加点击事件 useEffect(() => { if (!mapRef.current) { mapRef.current = L.map('map').setView([59.132659900251944, 9.727169813491393], 20); 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); // 添加地图点击事件 mapRef.current.on('click', (e) => { const { lat, lng } = e.latlng; fetchData(lat, lng); }); } }, []); // 修改fetchData为接收坐标参数 const fetchData = async (lat = 59.132659900251944, lon = 9.727169813491393) => { try { const response = await axios.post( 'https://overpass-api.de/api/interpreter', `[out:json];(way["building"](around:5,${lat}, ${lon}););out body;>;out skel qt;`, { headers: { 'Content-Type': 'text/plain' } } ); setBuildingData(response.data); } catch (error) { console.error('Error fetching data:', error); } }; // 调整fetchData的调用时机 useEffect(() => { fetchData(); }, []);
内容的提问来源于stack exchange,提问作者David Guri
相关产品推荐
相关产品推荐

