reactjsopenstreetmapoverpass-api

Calculate Surface Area of One Building using Overpass API Endpoint


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, 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;

I need to get the area of just the roof of the specific building located at the coordinates given, but instead I'm getting the area of all the surrounding ones (approx 30'000 square meters).

How can I fix this?


Solution

  • This Query can get the specific potion information. is_in()

    The standalone query is_in returns the areas and closed ways that cover
    
    the given coordinates (when specified) or
    one or more nodes from the input set (when no coordinates are specified).
    

    This query will address it

    `[out:json];
      (
        is_in(${lat},${lon});
        area._[building];
      );
      out body; >; out skel qt;`,
    

    I added Debounce code in the map component.

    Debounce is a technique used in programming to delay(100 msec) or limit the execution of a function or action until a certain amount of time has passed since the last time it was triggered. It helps prevent repetitive or frequent calls to the function, making it more efficient, especially in scenarios like user input handling or API requests.

    
    const debounce = (func, wait) => {
      let timeout;
    
      return function executedFunction(...args) {
        const later = () => {
          clearTimeout(timeout);
          func(...args);
        };
    
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
      };
    };
    ---
      const debouncedFetchAndDisplay = useCallback(debounce(fetchAndDisplayBuildingData, 100), [
        fetchAndDisplayBuildingData
      ]);
    

    Displaying the mouse position by mouse move event using debounce means that the function to update the mouse position is triggered only after a brief delay following the last mouse movement. This delay helps prevent excessive updates and ensures that the function is called only when the user has stopped moving the mouse for a short period. It's a way to optimize and control the frequency of updates in response to mouse movements, similar to how debounce works for other functions.

    map.on('mousemove', (e) => {
      setMousePosition({ lat: e.latlng.lat, lon: e.latlng.lng });
    });
    

    This demo code will display one building area

    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 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 };
            }
          });
    
          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: '800px', width: '100%', maxWidth: '1000px' }}></div>
        </div>
      );
    };
    
    export default MapComponent;
    

    Result

    enter image description here

    enter image description here