Skip to content

3D Building

Buildings: 0
Status: Loading...
html
<!DOCTYPE html>
<html>
<head>
    <title>3D Buildings</title>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
    <link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
    <script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
    <style>
        body { margin: 0; padding: 0; }
        #map { position: absolute; top: 0; bottom: 0; width: 100%; }
    </style>
</head>
<body>
    <div id="map"></div>
    
    <h3>3D Buildings</h3>
    
    <div class="info">
        <div>Buildings: <span id="buildingCount">0</span></div>
        <div>Status: <span id="status">Loading...</span></div>
    </div>

    <script>
      const token = '<YOUR_ACCESS_TOKEN>';
        // Initialize map with the MapMetrics Atlas style
        const map = new mapmetricsgl.Map({
            container: 'map',
            style: `${token}`,
            center: [5.47, 51.49], // Center of your building data
            zoom: 16,
            pitch: 60,
            bearing: -30
        });
        map.on('load', () => {
            console.log('Map loaded');
            document.getElementById('status').textContent = 'Map loaded';
            
            // Add the 3D buildings source
            map.addSource('buildings', {
                type: 'vector',
                tiles: ['https://building.mapmetrics-atlas.net/data/buildings/{z}/{x}/{y}.pbf'],
                minzoom: 13,
                maxzoom: 16
            });

            // Add 3D buildings layer
            map.addLayer({
                id: 'buildings-3d',
                type: 'fill-extrusion',
                source: 'buildings',
                'source-layer': 'building',
                paint: {
                    'fill-extrusion-color': '#aaa',
                    'fill-extrusion-height': [
                        'case',
                        ['has', 'height'],
                        ['get', 'height'],
                        ['has', 'estimated_height'],
                        ['get', 'estimated_height'],
                        ['has', 'building:levels'],
                        ['*', ['to-number', ['get', 'building:levels']], 3],
                        ['has', 'floors'],
                        ['*', ['to-number', ['get', 'floors']], 3],
                        10
                    ],
                    'fill-extrusion-base': 0,
                    'fill-extrusion-opacity': 0.9
                }
            });            
   
            updateBuildingCount();
        });

        function updateBuildingCount() {
            const features = map.queryRenderedFeatures({ layers: ['buildings-3d'] });
            document.getElementById('buildingCount').textContent = features.length;
        }

    

        // Update on map movement
        map.on('moveend', () => {
            updateBuildingCount();
        });

        
        // Add popup on click for building info
        map.on('click', 'buildings-3d', (e) => {
            const coordinates = e.lngLat;
            const properties = e.features[0].properties;
            
            const actualHeight = properties.height || properties.estimated_height || 
                                (properties['building:levels'] ? parseInt(properties['building:levels']) * 3 : null) ||
                                (properties.floors ? parseInt(properties.floors) * 3 : null) || 10;
            const popupContent = `
                <strong style="color:black;">Building Info:</strong><br>
                Height: ${actualHeight}m<br>
                Levels: ${properties['building:levels'] || 'N/A'}<br>
                Type: ${properties['building'] || properties['building:type'] || 'N/A'}<br>
                Name: ${properties.name || 'N/A'}
            `;
               new mapmetricsgl.Popup()
        .setLngLat(coordinates) // place popup at clicked point
        .setHTML(popupContent)  // set the content
        .addTo(map);            // add popup to the map

        });

        // Change cursor on hover
        map.on('mouseenter', 'buildings-3d', () => {
            map.getCanvas().style.cursor = 'pointer';
        });

        map.on('mouseleave', 'buildings-3d', () => {
            map.getCanvas().style.cursor = '';
        });
    </script>
</body>
</html>
jsx
import React, { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

const Building3D = () => {
  const mapContainerRef = useRef(null);
  const mapRef = useRef(null);
  const [buildingCount, setBuildingCount] = useState(0);
  const [status, setStatus] = useState('Loading...');

  useEffect(() => {
    if (!mapContainerRef.current || mapRef.current) return;

    const token = '<YOUR_ACCESS_TOKEN>';

    const map = new mapmetricsgl.Map({
      container: mapContainerRef.current,
      style: `${token}`,
      center: [5.47, 51.49],
      zoom: 16,
      pitch: 60,
      bearing: -30
    });

    map.on('load', () => {
      console.log('Map loaded');
      setStatus('Map loaded');

      // Add the 3D buildings source
      map.addSource('buildings', {
        type: 'vector',
        tiles: ['https://building.mapmetrics-atlas.net/data/buildings/{z}/{x}/{y}.pbf'],
        minzoom: 13,
        maxzoom: 16
      });

      // Add 3D buildings layer
      map.addLayer({
        id: 'buildings-3d',
        type: 'fill-extrusion',
        source: 'buildings',
        'source-layer': 'building',
        paint: {
          'fill-extrusion-color': '#aaa',
          'fill-extrusion-height': [
            'case',
            ['has', 'height'],
            ['get', 'height'],
            ['has', 'estimated_height'],
            ['get', 'estimated_height'],
            ['has', 'building:levels'],
            ['*', ['to-number', ['get', 'building:levels']], 3],
            ['has', 'floors'],
            ['*', ['to-number', ['get', 'floors']], 3],
            10
          ],
          'fill-extrusion-base': 0,
          'fill-extrusion-opacity': 0.9
        }
      });

      updateBuildingCount();
    });

    const updateBuildingCount = () => {
      const features = map.queryRenderedFeatures({ layers: ['buildings-3d'] });
      setBuildingCount(features.length);
    };

    // Update on map movement
    map.on('moveend', () => {
      updateBuildingCount();
    });

    // Add popup on click for building info
    map.on('click', 'buildings-3d', (e) => {
      const coordinates = e.lngLat;
      const properties = e.features[0].properties;

      const actualHeight = properties.height || properties.estimated_height ||
        (properties['building:levels'] ? parseInt(properties['building:levels']) * 3 : null) ||
        (properties.floors ? parseInt(properties.floors) * 3 : null) || 10;

      const popupContent = `
        <strong style="color:black;">Building Info:</strong><br>
        Height: ${actualHeight}m<br>
        Levels: ${properties['building:levels'] || 'N/A'}<br>
        Type: ${properties['building'] || properties['building:type'] || 'N/A'}<br>
        Name: ${properties.name || 'N/A'}
      `;

      new mapmetricsgl.Popup()
        .setLngLat(coordinates)
        .setHTML(popupContent)
        .addTo(map);
    });

    // Change cursor on hover
    map.on('mouseenter', 'buildings-3d', () => {
      map.getCanvas().style.cursor = 'pointer';
    });

    map.on('mouseleave', 'buildings-3d', () => {
      map.getCanvas().style.cursor = '';
    });

    mapRef.current = map;

    return () => {
      map.remove();
      mapRef.current = null;
    };
  }, []);

  return (
    <div style={{ position: 'relative', width: '100%', height: '100vh' }}>
      <div
        ref={mapContainerRef}
        style={{
          position: 'absolute',
          top: 0,
          bottom: 0,
          width: '100%'
        }}
      />
      <div
        style={{
          position: 'absolute',
          bottom: '10px',
          left: '10px',
          background: 'white',
          padding: '2px',
          width: '200px',
          borderRadius: '5px',
          boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
          fontFamily: 'monospace',
          fontSize: '11px',
          color: 'black',
          zIndex: 1000
        }}
      >
        <div>Buildings: <span>{buildingCount}</span></div>
        <div>Status: <span>{status}</span></div>
      </div>
    </div>
  );
};

export default Building3D;