You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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: '&copy; <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: '&copy; <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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:24:58