Skip to content
Extrude Polygons for 3D Indoor Mapping

Extrude Polygons for 3D Indoor Mapping

Indoor maps in 3D are built by storing room polygons as GeoJSON features with height and base-height properties, then rendering them with a fill-extrusion layer that reads those values. The result is a scaled architectural view of the interior that can include walls, rooms, and corridors at different heights. Use this approach for shopping mall directories, office building navigation, airport gate maps, or any interior space that benefits from a 3D overhead perspective.

const API_KEY = 'YOUR_API_KEY';

    const floorplan = {
        type: 'FeatureCollection',
        features: [
            {
                type: 'Feature',
                geometry: { type: 'Polygon', coordinates: [[[11.390, 47.275], [11.392, 47.275], [11.392, 47.277], [11.390, 47.277], [11.390, 47.275]]] },
                properties: { level: 1, name: 'Lobby', color: '#aee', height: 4 }
            },
            {
                type: 'Feature',
                geometry: { type: 'Polygon', coordinates: [[[11.392, 47.275], [11.394, 47.275], [11.394, 47.277], [11.392, 47.277], [11.392, 47.275]]] },
                properties: { level: 1, name: 'Hall A', color: '#fea', height: 6 }
            },
            {
                type: 'Feature',
                geometry: { type: 'Polygon', coordinates: [[[11.390, 47.277], [11.394, 47.277], [11.394, 47.279], [11.390, 47.279], [11.390, 47.277]]] },
                properties: { level: 2, name: 'Hall B', color: '#eaf', height: 8 }
            }
        ]
    };

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [11.392, 47.277],
        zoom: 16,
        pitch: 50,
        bearing: 20,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', () => {
        map.addSource('terrain-rgb', {
            type: 'raster-dem',
            tiles: [`https://vtc-cdn.maptoolkit.net/terrain/{z}/{x}/{y}.webp?api_key=${API_KEY}`],
            tileSize: 256,
            minzoom: 5,
            maxzoom: 12,
            encoding: 'terrarium'
        });

        map.setTerrain({ source: 'terrain-rgb', exaggeration: 1.5 });

        // Force one render after the DEM source loads so terrain shows on initial load
        map.once('idle', () => map.triggerRepaint());

        map.addSource('floorplan', { type: 'geojson', data: floorplan });

        map.addLayer({
            id: 'rooms-extrusion',
            type: 'fill-extrusion',
            source: 'floorplan',
            paint: {
                'fill-extrusion-color': ['get', 'color'],
                'fill-extrusion-height': ['get', 'height'],
                'fill-extrusion-base': 0,
                'fill-extrusion-opacity': 0.8
            }
        });
    });
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Extrude Polygons for 3D Indoor Mapping - Maptoolkit Maps JS</title>
    <meta property="og:description" content="Use fill-extrusion layers to create a 3D indoor map from a GeoJSON floor plan." />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/[email protected]/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/[email protected]/dist/maptoolkit.css" />
    <style>
        html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
        #map { width: 100%; height: 100%; }
    </style>
</head>
<body>
<div id="map"></div>
<script>
    const API_KEY = 'YOUR_API_KEY';

    const floorplan = {
        type: 'FeatureCollection',
        features: [
            {
                type: 'Feature',
                geometry: { type: 'Polygon', coordinates: [[[11.390, 47.275], [11.392, 47.275], [11.392, 47.277], [11.390, 47.277], [11.390, 47.275]]] },
                properties: { level: 1, name: 'Lobby', color: '#aee', height: 4 }
            },
            {
                type: 'Feature',
                geometry: { type: 'Polygon', coordinates: [[[11.392, 47.275], [11.394, 47.275], [11.394, 47.277], [11.392, 47.277], [11.392, 47.275]]] },
                properties: { level: 1, name: 'Hall A', color: '#fea', height: 6 }
            },
            {
                type: 'Feature',
                geometry: { type: 'Polygon', coordinates: [[[11.390, 47.277], [11.394, 47.277], [11.394, 47.279], [11.390, 47.279], [11.390, 47.277]]] },
                properties: { level: 2, name: 'Hall B', color: '#eaf', height: 8 }
            }
        ]
    };

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [11.392, 47.277],
        zoom: 16,
        pitch: 50,
        bearing: 20,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', () => {
        map.addSource('terrain-rgb', {
            type: 'raster-dem',
            tiles: [`https://vtc-cdn.maptoolkit.net/terrain/{z}/{x}/{y}.webp?api_key=${API_KEY}`],
            tileSize: 256,
            minzoom: 5,
            maxzoom: 12,
            encoding: 'terrarium'
        });

        map.setTerrain({ source: 'terrain-rgb', exaggeration: 1.5 });

        // Force one render after the DEM source loads so terrain shows on initial load
        map.once('idle', () => map.triggerRepaint());

        map.addSource('floorplan', { type: 'geojson', data: floorplan });

        map.addLayer({
            id: 'rooms-extrusion',
            type: 'fill-extrusion',
            source: 'floorplan',
            paint: {
                'fill-extrusion-color': ['get', 'color'],
                'fill-extrusion-height': ['get', 'height'],
                'fill-extrusion-base': 0,
                'fill-extrusion-opacity': 0.8
            }
        });
    });
</script>
</body>
</html>

Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.

Use the Maptoolkit Connector. Create an interactive map with zoom level 16, centered around [11.392, 47.277], with pitch 50 and bearing 20. Display a 3D indoor floor plan with extruded rooms (Lobby, Hall A, Hall B) of varying heights and colors.