使用 Overpass API 端点选择多个建筑物

问题描述 投票:0回答:1

我有下面的代码,显示悬停时 OpenStreetMap 中建筑物的表面积。这是代码:

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;

我希望它可以在单击时选择,然后保存该值。需要保存多座建筑物。另外,有没有办法也获取屋顶角度数据?

我需要角度数据,因为我需要计算建筑物屋顶的真实面积,而不仅仅是它在 2D 地图上占据的空间。然后需要保存这个特定的真实面积值。

我该如何解决这个问题?

reactjs openstreetmap overpass-api
1个回答
0
投票

有没有办法获取屋顶角度数据?不,我找不到解决方案。我检查了立交桥文档,没有建筑物或屋顶的角度数据。

此演示可以处理多个建筑物,通过在构建区域上单击鼠标来保存数据。

保存格式示例

Area: 197.70, Latitude: 59.13330, Longitude: 9.72721, Tags: {"building":"house","ref:bygningsnr":"300001805"}

添加功能标签并通过单击显示所有已保存的构建信息。

  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]);
  };

从地图呼叫

<div> tag

<div id="map" style={{ height: '800px', width: '100%', maxWidth: '1000px' }} onClick={handleMouseClick}></div>

从 Overpass API 获取标签信息

if (element.type === 'way' && element.tags) {
   setBuildingTag(JSON.stringify(element.tags));
}

数据来源

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

结果

© www.soinside.com 2019 - 2024. All rights reserved.