{"id":1776,"date":"2026-11-04T05:27:48","date_gmt":"2026-11-04T05:27:48","guid":{"rendered":"https:\/\/helpwithmygisassignment.com\/blog\/?p=1776"},"modified":"2026-11-04T05:27:48","modified_gmt":"2026-11-04T05:27:48","slug":"ogc-3d-tiles-web-map-tutorial","status":"publish","type":"post","link":"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/","title":{"rendered":"Building Immersive 3D Digital Twins in the Browser with Deck.gl, MapLibre GL JS, and 3D Tiles"},"content":{"rendered":"<div><\/div>\n<div>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_89 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-1'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Building_Immersive_3D_Digital_Twins_in_the_Browser_with_Deckgl_MapLibre_GL_JS_and_3D_Tiles\" >Building Immersive 3D Digital Twins in the Browser with Deck.gl, MapLibre GL JS, and 3D Tiles<\/a><ul class='ez-toc-list-level-2' ><li class='ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Introduction_From_2D_web_maps_to_immersive_digital_twins\" >Introduction: From 2D web maps to immersive digital twins<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#1_Core_frontend_architecture\" >1. Core frontend architecture<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#MapLibre_GL_JS_the_geographic_foundation\" >MapLibre GL JS: the geographic foundation<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Deckgl_the_geospatial_rendering_engine\" >Deck.gl: the geospatial rendering engine<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#OGC_3D_Tiles_the_streaming_data_format\" >OGC 3D Tiles: the streaming data format<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#2_Step-by-step_implementation\" >2. Step-by-step implementation<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Step_1_Set_up_the_frontend_project\" >Step 1: Set up the frontend project<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Step_2_Initialize_MapLibre_and_Deckgl\" >Step 2: Initialize MapLibre and Deck.gl<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Step_3_Load_photogrammetry_and_3D_building_models\" >Step 3: Load photogrammetry and 3D building models<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Step_4_Configure_dynamic_lighting_shadows_and_camera_controls\" >Step 4: Configure dynamic lighting, shadows, and camera controls<\/a><ul class='ez-toc-list-level-4' ><li class='ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Enabling_shadow_projections\" >Enabling shadow projections<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-4'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Controlling_camera_pitch\" >Controlling camera pitch<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Step_5_Implement_building-level_feature_picking_and_attribute_popups\" >Step 5: Implement building-level feature picking and attribute popups<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#3_Overlay_real-time_IoT_sensor_streams\" >3. Overlay real-time IoT sensor streams<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#4_Visualizing_the_completed_digital_twin\" >4. Visualizing the completed digital twin<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#5_Performance_deployment_and_troubleshooting\" >5. Performance, deployment, and troubleshooting<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#6_SEO_best_practices_and_internal_interlinking\" >6. SEO best practices and internal interlinking<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h1><span class=\"ez-toc-section\" id=\"Building_Immersive_3D_Digital_Twins_in_the_Browser_with_Deckgl_MapLibre_GL_JS_and_3D_Tiles\"><\/span>Building Immersive 3D Digital Twins in the Browser with Deck.gl, MapLibre GL JS, and 3D Tiles<code><\/code><span class=\"ez-toc-section-end\"><\/span><\/h1>\n<h2><span class=\"ez-toc-section\" id=\"Introduction_From_2D_web_maps_to_immersive_digital_twins\"><\/span>Introduction: From 2D web maps to immersive digital twins<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Welcome to this practical <b data-path-to-node=\"1,0\" data-index-in-node=\"27\">OGC 3D Tiles web map tutorial<\/b> on building immersive 3D digital twins in the browser using MapLibre GL JS and <a href=\"https:\/\/deck.gl\/\" target=\"_blank\" rel=\"noopener\">Deck.gl<\/a>. Traditional web maps are excellent at displaying roads, land parcels, administrative boundaries, and points of interest. However, they become less expressive when developers need to represent complex building facades, construction progress, urban infrastructure, or the real-time state of physical assets. A three-dimensional digital twin extends this familiar mapping environment into an interactive spatial model that combines geographic context, realistic geometry, asset information, and live operational data.<\/p>\n<p>For urban planners, infrastructure engineers, and smart-city developers, a browser-based digital twin can support building inspections, solar exposure analysis, utility monitoring, construction coordination, and environmental assessment without requiring users to install specialist desktop GIS software.<\/p>\n<p>The challenge is rendering large, detailed three-dimensional scenes without overwhelming browser memory or sacrificing interaction quality. Loading an entire photogrammetric city as one mesh is rarely practical. Instead, modern 3D geospatial systems stream only the model content needed for the current camera position and level of detail.<\/p>\n<p>This tutorial demonstrates how to build a 3D digital twin in the browser using MapLibre GL JS, Deck.gl, and the Open Geospatial Consortium (OGC) 3D Tiles standard. We will configure a shared rendering environment, load photogrammetry and building meshes, introduce dynamic lighting and camera controls, implement building-level feature selection, and overlay live Internet of Things (IoT) sensor observations.<\/p>\n<p>The approach uses modern JavaScript modules and a Vite development environment. It assumes that you already have a published 3D Tiles dataset and, for building-level interaction, a matching building-footprint dataset with stable asset identifiers.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1836\" src=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_vrcmkyvrcmkyvrcm-1024x559.jpg\" alt=\"A sleek, high-tech web browser interface displaying an immersive 3D digital twin of a modern city at dusk, featuring detailed photorealistic architectural building models, realistic directional lighting, shadows, and smooth vector basemap integration rendered in WebGL. Professional software UI\/UX aesthetic, clean dashboard style.\" width=\"1024\" height=\"559\" srcset=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_vrcmkyvrcmkyvrcm-1024x559.jpg 1024w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_vrcmkyvrcmkyvrcm-300x164.jpg 300w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_vrcmkyvrcmkyvrcm-768x419.jpg 768w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_vrcmkyvrcmkyvrcm.jpg 1408w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<h2><span class=\"ez-toc-section\" id=\"1_Core_frontend_architecture\"><\/span>1. Core frontend architecture<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A reliable implementation separates geographic navigation, detailed geometry, and application data rather than attempting to manage everything inside a single rendering layer.<\/p>\n<div>\n<div>\n<p>Application and data services<\/p>\n<p>Building registry \u00b7 IoT API \u00b7 asset metadata \u00b7 selection state<\/p>\n<\/div>\n<div><\/div>\n<div>\n<div>\n<div>\n<p>MapLibre GL JS<\/p>\n<p>Basemap, geographic camera, zoom, bearing, pitch, and map controls<\/p>\n<\/div>\n<\/div>\n<div>\n<div>\n<p>Deck.gl<\/p>\n<p>3D Tiles, GPU-accelerated layers, interactive features, and sensor overlays<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div><\/div>\n<div>\n<p>OGC 3D Tiles and geospatial services<\/p>\n<p>Photogrammetry \u00b7 building meshes \u00b7 footprints \u00b7 live observations<\/p>\n<\/div>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"MapLibre_GL_JS_the_geographic_foundation\"><\/span>MapLibre GL JS: the geographic foundation<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>MapLibre GL JS renders interactive vector-tile maps using WebGL. It manages the map&#8217;s geographic camera and navigation, making it a suitable base for applications that require familiar pan, zoom, rotation, and pitch controls.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Deckgl_the_geospatial_rendering_engine\"><\/span>Deck.gl: the geospatial rendering engine<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Deck.gl provides a layer-based visualization framework designed for high-volume geospatial rendering. Its <code>Tile3DLayer<\/code> loads 3D Tiles or supported Esri I3S datasets, while other layers can display building footprints, sensor locations, labels, and analytical overlays.<\/p>\n<div>\n<div><\/div>\n<p>deck.gl+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<p>For MapLibre GL JS versions 4.5.1, 5, and 6, use <code>MapLibreOverlay<\/code> from <code>@deck.gl\/maplibre<\/code>. The interleaved rendering mode shares MapLibre&#8217;s WebGL2 rendering context, allowing Deck.gl layers to participate in the map&#8217;s layer stack. This is particularly important when 3D models must interact visually with other geographic layers.<\/p>\n<div>\n<div><\/div>\n<p>deck.gl+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"OGC_3D_Tiles_the_streaming_data_format\"><\/span>OGC 3D Tiles: the streaming data format<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>3D Tiles organizes massive geospatial scenes into a hierarchy of tiles. The renderer selects appropriate detail as the camera moves, avoiding the need to download every model at maximum resolution. The standard supports photogrammetry, 3D buildings, point clouds, and building information modelling (BIM) or computer-aided design (CAD) content.<\/p>\n<div>\n<div><\/div>\n<p>Streaming Massive 3D Geospatial Data+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<p>One important compatibility detail: B3DM (Batched 3D Model) is a legacy 3D Tiles 1.0 format that was deprecated in 3D Tiles 1.1. Deck.gl supports B3DM through its 3D Tiles loader, but new publishing workflows should generally favour glTF-based tile content and current 3D Tiles 1.1 capabilities where possible.<\/p>\n<div>\n<div><\/div>\n<p>GitHub+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<h2><span class=\"ez-toc-section\" id=\"2_Step-by-step_implementation\"><\/span>2. Step-by-step implementation<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"Step_1_Set_up_the_frontend_project\"><\/span>Step 1: Set up the frontend project<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Create a new Vite application and install the required packages:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>npm create vite@latest city-digital-twin -- --template vanilla\r\ncd city-digital-twin\r\nnpm install\r\nnpm install maplibre-gl @deck.gl\/core @deck.gl\/layers @deck.gl\/geo-layers @deck.gl\/maplibre\r\nnpm run dev<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Use a recent compatible version of each package. For the examples below, the integration uses <code>MapLibreOverlay<\/code> rather than the older Mapbox-specific overlay class.<\/p>\n<p>In <code>index.html<\/code>, create a map container:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>&lt;div id=\"map\"&gt;&lt;\/div&gt;\r\n&lt;div id=\"asset-popup\" aria-live=\"polite\"&gt;&lt;\/div&gt;\r\n&lt;script type=\"module\" src=\"\/src\/main.js\"&gt;&lt;\/script&gt;<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Add the following styles to <code>src\/style.css<\/code>:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>html,\r\nbody,\r\n#map {\r\n  width: 100%;\r\n  height: 100%;\r\n  margin: 0;\r\n  overflow: hidden;\r\n}\r\n\r\n#asset-popup {\r\n  position: absolute;\r\n  z-index: 10;\r\n  left: 16px;\r\n  bottom: 16px;\r\n  max-width: 320px;\r\n  padding: 12px;\r\n  background: white;\r\n  color: #202a35;\r\n  border-radius: 8px;\r\n  font: 14px\/1.5 system-ui, sans-serif;\r\n  box-shadow: 0 4px 20px #0002;\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The popup is an HTML interface rather than a canvas-rendered label, making it suitable for detailed building attributes, accessible text, and links to maintenance records.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Step_2_Initialize_MapLibre_and_Deckgl\"><\/span>Step 2: Initialize MapLibre and Deck.gl<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Import the relevant modules in <code>src\/main.js<\/code>:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>import {\r\n  Map,\r\n  NavigationControl,\r\n  setWorkerUrl\r\n} from 'maplibre-gl';\r\n\r\nimport maplibreWorkerUrl\r\n  from 'maplibre-gl\/dist\/maplibre-gl-worker.mjs?worker&amp;url';\r\n\r\nimport { MapLibreOverlay } from '@deck.gl\/maplibre';\r\nimport { Tile3DLayer } from '@deck.gl\/geo-layers';\r\nimport { GeoJsonLayer, ScatterplotLayer } from '@deck.gl\/layers';\r\n\r\nimport {\r\n  AmbientLight,\r\n  DirectionalLight,\r\n  LightingEffect\r\n} from '@deck.gl\/core';\r\n\r\nimport 'maplibre-gl\/dist\/maplibre-gl.css';\r\nimport '.\/style.css';\r\n\r\nsetWorkerUrl(maplibreWorkerUrl);\r\n\r\nconst map = new Map({\r\n  container: 'map',\r\n  style: 'https:\/\/tiles.openfreemap.org\/styles\/positron',\r\n  center: [36.8219, -1.2921],\r\n  zoom: 15,\r\n  pitch: 58,\r\n  bearing: -25,\r\n  maxPitch: 80,\r\n  antialias: true\r\n});\r\n\r\nmap.addControl(\r\n  new NavigationControl({ visualizePitch: true }),\r\n  'top-right'\r\n);<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The camera starts over Nairobi as an example; replace the center, zoom, and bearing with values appropriate for your own city. The basemap URL is a development starting point, so verify the service&#8217;s usage terms and availability before production deployment.<\/p>\n<p>The <code>antialias<\/code> option requests a smoother shared rendering context. Interleaved mode requires WebGL2, so the target device and browser must support it.<\/p>\n<div>\n<div><\/div>\n<p>deck.gl+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Step_3_Load_photogrammetry_and_3D_building_models\"><\/span>Step 3: Load photogrammetry and 3D building models<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Publish a valid 3D Tiles dataset, then point <code>Tile3DLayer<\/code> at its root <code>tileset.json<\/code> document.<\/p>\n<p>A tileset may contain textured photogrammetry, detailed building meshes, or multiple levels of geometric detail. The loader resolves the tileset hierarchy and fetches content as required by the camera.<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>const cityLayer = new Tile3DLayer({\r\n  id: 'city-model',\r\n  data: '\/assets\/city\/tileset.json',\r\n  pickable: true,\r\n\r\n  onTilesetLoad: (tileset) =&gt; {\r\n    console.log('3D Tileset loaded', tileset);\r\n  },\r\n\r\n  onTileError: (tile, url, message) =&gt; {\r\n    console.error('Tile loading failed:', url, message);\r\n  }\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The URL is an example. Serve the tileset and its referenced content from your own static asset host or a compatible 3D Tiles service. Ensure relative content URIs resolve correctly and the server supplies appropriate cross-origin resource sharing (CORS) headers.<\/p>\n<p>Initialize the layer after MapLibre has loaded:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>await map.once('load');\r\n\r\nconst overlay = new MapLibreOverlay({\r\n  interleaved: true,\r\n  layers: [cityLayer]\r\n});\r\n\r\nmap.addControl(overlay);<\/code><\/pre>\n<\/div>\n<\/div>\n<p>This creates the initial map-and-model integration. The camera remains under MapLibre&#8217;s control, while Deck.gl handles the 3D Tiles layer. For interactive buildings that must appear correctly alongside map labels and other 3D elements, interleaving is generally preferable to placing an independent canvas over the map.<\/p>\n<div>\n<div><\/div>\n<p>deck.gl+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<p>Production consideration: A valid tileset is more than a directory of model files. It includes spatial bounding volumes, geometric error thresholds, refinement rules, and references to tile content. Incorrect coordinate transforms or bounding volumes can cause models to appear displaced, disappear at certain zoom levels, or load inefficiently.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Step_4_Configure_dynamic_lighting_shadows_and_camera_controls\"><\/span>Step 4: Configure dynamic lighting, shadows, and camera controls<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Realistic illumination helps distinguish facades, rooftops, roads, and terrain. Deck.gl&#8217;s <code>LightingEffect<\/code> supplies ambient and directional lighting for supported material-aware layers, including the scenegraph rendering used for supported 3D Tiles content.<\/p>\n<div>\n<div><\/div>\n<p>deck.gl+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<p>Create a lighting effect:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>const ambientLight = new AmbientLight({\r\n  color: [255, 255, 255],\r\n  intensity: 0.65\r\n});\r\n\r\nconst sunLight = new DirectionalLight({\r\n  color: [255, 244, 224],\r\n  intensity: 1.4,\r\n  direction: [-3, -5, -2]\r\n});\r\n\r\nconst lightingEffect = new LightingEffect({\r\n  ambientLight,\r\n  sunLight\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Attach the effect to the overlay when creating it:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>const overlay = new MapLibreOverlay({\r\n  interleaved: true,\r\n  effects: [lightingEffect],\r\n  layers: [cityLayer]\r\n});\r\n\r\nmap.addControl(overlay);<\/code><\/pre>\n<\/div>\n<\/div>\n<p>If you have already constructed the overlay, add effects through its supported <code>setProps<\/code> method rather than creating a second overlay. Only one interleaved overlay should be attached to a map at a time.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Enabling_shadow_projections\"><\/span>Enabling shadow projections<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>Deck.gl provides an experimental directional-light shadow option:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>const sunLight = new DirectionalLight({\r\n  color: [255, 244, 224],\r\n  intensity: 1.4,\r\n  direction: [-3, -5, -2],\r\n  _shadow: true\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The <code>_shadow<\/code> property is experimental, not a guarantee of production-quality shadows across every layer and device. Shadow rendering also has a performance cost. Test the result using your actual model formats, browser, and target hardware.<\/p>\n<div>\n<div><\/div>\n<p>deck.gl+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<p>It is equally important to distinguish lighting from shadows: directional lighting can give a facade realistic shading without projecting a shadow onto the street. For predictable production results, test shadow support explicitly and provide a fallback with directional lighting only.<\/p>\n<h4><span class=\"ez-toc-section\" id=\"Controlling_camera_pitch\"><\/span>Controlling camera pitch<span class=\"ez-toc-section-end\"><\/span><\/h4>\n<p>MapLibre controls the camera through geographic coordinates, zoom, bearing, and pitch. For a dramatic urban view, set pitch between approximately 45 and 70 degrees and let users switch between aerial and street-level perspectives.<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>map.easeTo({\r\n  center: [36.8219, -1.2921],\r\n  zoom: 16,\r\n  pitch: 65,\r\n  bearing: -35,\r\n  duration: 1200\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Use <code>flyTo<\/code> for longer transitions between districts and <code>easeTo<\/code> for shorter camera adjustments. Avoid automatically animating the camera while users are selecting assets or examining sensor readings.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Step_5_Implement_building-level_feature_picking_and_attribute_popups\"><\/span>Step 5: Implement building-level feature picking and attribute popups<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Selecting a building is more complicated than detecting a mouse click. A 3D Tiles dataset may contain an entire cluster of buildings inside one tile. Selecting that tile does not necessarily identify the individual structure under the pointer.<\/p>\n<p>Deck.gl uses GPU-based picking rather than requiring developers to implement a separate CPU raycasting engine. However, <code>Tile3DLayer<\/code>&#8216;s documented picking result is a tile header, not a guaranteed individual building feature. Building-level picking therefore depends on the available per-feature identifiers and metadata.<\/p>\n<div>\n<div><\/div>\n<p>deck.gl+1<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<p>A practical frontend architecture combines the detailed mesh with an indexed GeoJSON building-footprint dataset. Each footprint must contain a stable identifier matching the building registry. The footprint layer provides dependable GIS feature selection, while the Tiles layer supplies detailed visual geometry.<\/p>\n<p>For example, assume <code>buildingFootprints<\/code> contains valid GeoJSON features with properties such as <code>building_id<\/code>, <code>name<\/code>, <code>use<\/code>, and <code>height_m<\/code>.<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>let selectedBuildingId = null;\r\n\r\nconst popup = document.querySelector('#asset-popup');\r\n\r\nfunction showBuilding(feature) {\r\n  const p = feature.properties;\r\n\r\n  popup.replaceChildren();\r\n\r\n  const title = document.createElement('strong');\r\n  title.textContent = p.name || `Building ${p.building_id}`;\r\n\r\n  const details = document.createElement('p');\r\n  details.textContent =\r\n    `Use: ${p.use || 'Unknown'} \u00b7 ` +\r\n    `Height: ${p.height_m ?? 'Unknown'} m`;\r\n\r\n  const id = document.createElement('small');\r\n  id.textContent = `Asset ID: ${p.building_id}`;\r\n\r\n  popup.append(title, details, id);\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Use a pickable <code>GeoJsonLayer<\/code> for selecting indexed buildings:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>function createBuildingLayer(buildingFootprints) {\r\n  return new GeoJsonLayer({\r\n    id: 'building-selection',\r\n    data: buildingFootprints,\r\n    pickable: true,\r\n    filled: true,\r\n    stroked: true,\r\n\r\n    \/\/ Keep the hit-test geometry subtle but visible.\r\n    getFillColor: (feature) =&gt;\r\n      feature.properties.building_id === selectedBuildingId\r\n        ? [255, 165, 0, 100]\r\n        : [40, 130, 220, 18],\r\n\r\n    getLineColor: [40, 145, 230, 180],\r\n    getLineWidth: 1,\r\n    lineWidthUnits: 'pixels',\r\n\r\n    onClick: (info) =&gt; {\r\n      if (!info.object) return;\r\n\r\n      selectedBuildingId =\r\n        info.object.properties.building_id;\r\n\r\n      showBuilding(info.object);\r\n    }\r\n  });\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Load the companion dataset and include the layer in the same Deck.gl overlay:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>const buildingFootprints = await fetch(\r\n  '\/data\/building-footprints.geojson'\r\n).then((response) =&gt; {\r\n  if (!response.ok) {\r\n    throw new Error('Unable to load building footprints');\r\n  }\r\n  return response.json();\r\n});\r\n\r\noverlay.setProps({\r\n  layers: [\r\n    cityLayer,\r\n    createBuildingLayer(buildingFootprints)\r\n  ]\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>This sample establishes building-level selection through the footprint geometry. The highlight and popup depend on that indexed feature layer, so verify alignment and picking behaviour in your scene; footprints alone do not perform exact triangle-level raycasting against a roof or facade.<\/p>\n<p>For direct model picking, inspect the tile format and its feature IDs or structural metadata. Where a tile contains multiple structures, map those identifiers to the authoritative building registry and implement a feature-aware picking path. If exact surface coordinates are also required, use depth-aware picking where supported by the rendering layer, or a custom 3D picking implementation. This distinction matters for engineering inspections, BIM selection, and asset-specific maintenance workflows.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"3_Overlay_real-time_IoT_sensor_streams\"><\/span>3. Overlay real-time IoT sensor streams<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1837\" src=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_3lylwk3lylwk3lyl-1024x559.jpg\" alt=\"Visualizing live IoT sensor data overlays on building meshes in an OGC 3D Tiles web map tutorial.\" width=\"1024\" height=\"559\" srcset=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_3lylwk3lylwk3lyl-1024x559.jpg 1024w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_3lylwk3lylwk3lyl-300x164.jpg 300w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_3lylwk3lylwk3lyl-768x419.jpg 768w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/11\/Gemini_Generated_Image_3lylwk3lylwk3lyl.jpg 1408w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>A digital twin becomes operationally useful when it connects model geometry to changing data. Consider a city application that tracks rooftop temperature, electricity consumption, indoor air quality, or structural vibration.<\/p>\n<p>Each sensor observation should have a stable asset or sensor ID, a timestamp, a measurement, and preferably a quality or status flag. The frontend should transform these observations into visualization-ready objects rather than rebuilding the underlying 3D Tiles model whenever a new reading arrives.<\/p>\n<p>Example sensor records:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>let sensors = [\r\n  {\r\n    id: 'sensor-104',\r\n    buildingId: 'BLD-001',\r\n    position: [36.8219, -1.2921, 28],\r\n    temperature: 34.8,\r\n    status: 'warning'\r\n  },\r\n  {\r\n    id: 'sensor-105',\r\n    buildingId: 'BLD-002',\r\n    position: [36.8230, -1.2915, 16],\r\n    temperature: 25.6,\r\n    status: 'normal'\r\n  }\r\n];<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The third coordinate represents an elevation in metres above the geographic reference level used by your visualization. Confirm that it matches the model&#8217;s vertical datum and transformation rules.<\/p>\n<p>Create a sensor layer:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>function createSensorLayer() {\r\n  return new ScatterplotLayer({\r\n    id: 'iot-sensors',\r\n    data: sensors,\r\n    pickable: true,\r\n\r\n    getPosition: (d) =&gt; d.position,\r\n    getRadius: 7,\r\n    radiusUnits: 'pixels',\r\n\r\n    getFillColor: (d) =&gt;\r\n      d.status === 'warning'\r\n        ? [240, 70, 50, 230]\r\n        : [40, 190, 120, 220],\r\n\r\n    getLineColor: [255, 255, 255, 220],\r\n    lineWidthMinPixels: 1\r\n  });\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Add the sensor layer to the existing overlay:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>overlay.setProps({\r\n  layers: [\r\n    cityLayer,\r\n    createBuildingLayer(buildingFootprints),\r\n    createSensorLayer()\r\n  ]\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>To receive measurements over time, connect the frontend to an authorized WebSocket service:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>const socket = new WebSocket(\r\n  'wss:\/\/example.com\/api\/city-sensors'\r\n);\r\n\r\nsocket.addEventListener('message', (event) =&gt; {\r\n  const update = JSON.parse(event.data);\r\n\r\n  sensors = sensors.map((sensor) =&gt;\r\n    sensor.id === update.id\r\n      ? { ...sensor, ...update }\r\n      : sensor\r\n  );\r\n\r\n  overlay.setProps({\r\n    layers: [\r\n      cityLayer,\r\n      createBuildingLayer(buildingFootprints),\r\n      createSensorLayer()\r\n    ]\r\n  });\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>This is a simplified update handler: validate incoming messages, verify sensor IDs, handle connection failures, and throttle redraws when observations arrive rapidly. For thousands of sensors, batch updates, maintain a normalized sensor store, and update only the affected layer data rather than reloading static datasets.<\/p>\n<p>Also, a sensor&#8217;s geographic position should be registered to the actual building or roof elevation. Arbitrary elevation values may display an otherwise valid sensor above the wrong floor or outside its architectural model.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"4_Visualizing_the_completed_digital_twin\"><\/span>4. Visualizing the completed digital twin<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>[Image 1: Web browser rendering a 3D city digital twin with lit architectural models and sensor data overlays]<\/p>\n<p>Alt text: &#8220;Browser screenshot showing an interactive 3D city digital twin rendered using Deck.gl and 3D Tiles with dynamic lighting and attribute overlays.&#8221;<\/p>\n<p>The finished interface should show detailed building geometry, recognizable geographic context, camera navigation, a selected-building highlight, an attribute popup, and sensor markers whose colors reflect their current status.<\/p>\n<p>Design for operational clarity rather than visual complexity alone. A user should be able to distinguish physical geometry from analytical overlays immediately. Keep the default camera position useful, make selections easy to clear, and ensure that the asset panel remains readable when the map is rotated or pitched.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"5_Performance_deployment_and_troubleshooting\"><\/span>5. Performance, deployment, and troubleshooting<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Large 3D scenes can stress GPU memory, CPU parsing, network bandwidth, and browser responsiveness. Address these concerns before deploying a city-scale model.<\/p>\n<p>Optimize the tileset. Use sensible geometric error thresholds, level-of-detail hierarchies, texture resolutions, and tile sizes. Avoid using maximum-resolution photogrammetry throughout the entire camera view.<\/p>\n<p>Keep interaction data lightweight. The building registry should contain identifiers and display attributes, not duplicate entire 3D model files. Fetch maintenance histories or detailed telemetry only when a user opens an asset.<\/p>\n<p>Validate spatial alignment. Check the coordinate reference system, vertical units, tile transforms, and feature IDs. Use the same stable building identifiers across the tileset, footprints, and IoT data.<\/p>\n<p>Monitor network and rendering failures. Confirm that <code>tileset.json<\/code> and its child resources return successfully, CORS allows the requests, and the browser supports the required WebGL features. Use the <code>onTileError<\/code> callback for diagnostics.<\/p>\n<div>\n<div>\n<table>\n<tbody>\n<tr>\n<th>Symptom<\/th>\n<th>Likely cause<\/th>\n<th>Recommended check<\/th>\n<\/tr>\n<tr>\n<td>Model does not appear<\/td>\n<td>Invalid URL, CORS, or failed tile request<\/td>\n<td>Inspect network requests and tile errors<\/td>\n<\/tr>\n<tr>\n<td>Model is displaced<\/td>\n<td>Incorrect transform or coordinate reference<\/td>\n<td>Validate geographic origin and elevation<\/td>\n<\/tr>\n<tr>\n<td>Building selection is inconsistent<\/td>\n<td>Missing or misaligned footprint features<\/td>\n<td>Check asset IDs and geometry alignment<\/td>\n<\/tr>\n<tr>\n<td>Frame rate drops<\/td>\n<td>Excessive geometry, textures, or redraws<\/td>\n<td>Reduce detail and batch sensor updates<\/td>\n<\/tr>\n<tr>\n<td>Shadows look incorrect<\/td>\n<td>Unsupported feature or costly shadow pass<\/td>\n<td>Disable shadows and compare lighting-only output<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>For production, also verify the licensing and attribution requirements of the basemap, imagery, photogrammetry, and building data. Implement authentication and authorization on data services that expose sensitive infrastructure information; a browser interface alone does not protect private endpoints.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"6_SEO_best_practices_and_internal_interlinking\"><\/span>6. SEO best practices and internal interlinking<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The main keyword, 3D Digital Twin Browser Deck.gl MapLibre 3D Tiles, should appear naturally in the introduction and be reinforced through relevant headings, image alt text, and the conclusion. Avoid repeating the entire keyword unnaturally in every paragraph.<\/p>\n<p>Use descriptive links to support readers who need deeper technical coverage. The following are suggested internal-link targets; replace the paths if your site&#8217;s published URLs differ.<\/p>\n<ul>\n<li>\n<div>\n<div>\n<p>Rendering 1 Million Points with WebGPU \u2014 expand on GPU rendering strategies for dense geospatial datasets.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Building <a href=\"https:\/\/helpwithmygisassignment.com\/blog\/cloud-optimized-geotiff-web-map-architecture\/\">Web GIS Feature Popups<\/a> \u2014 explore reusable selection, popup, and attribute-display patterns.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Python Streamlit PyDeck \u2014 compare a Python-oriented workflow for building geospatial visualization applications.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<p>Link from relevant existing articles back to this tutorial using varied, descriptive anchor text. Add structured data only when it accurately reflects the published article, and keep the page&#8217;s title, description, and heading structure consistent with its actual content.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Building an immersive 3D digital twin in the browser requires more than rendering a detailed city model. It requires coordinating a geographic camera, streamed geometry, lighting, asset selection, and live data while keeping the user interface responsive.<\/p>\n<p>MapLibre GL JS provides the mapping foundation, Deck.gl&#8217;s <code>Tile3DLayer<\/code> supplies the streamed 3D content, and indexed geospatial features connect the rendered environment to meaningful building attributes. Adding IoT overlays then turns a static city model into a dynamic view of physical assets.<\/p>\n<p>The key engineering principle is to separate 3D geometry, geographic identity, and live operational state. With stable identifiers, carefully prepared tilesets, tested picking behaviour, and efficient data updates, the result is a scalable foundation for browser-based urban planning, infrastructure management, and smart-city applications.<\/p>\n<p>Want to take your spatial development further? Bookmark this <b data-path-to-node=\"4,0\" data-index-in-node=\"62\"><a href=\"https:\/\/helpwithmygisassignment.com\/blog\/?p=1776&amp;preview=true\">OGC 3D Tiles web map tutoria<\/a>l<\/b>, or <a href=\"https:\/\/helpwithmygisassignment.com\/\">reach out to our team<\/a> if you need expert guidance, code optimization, or professional assistance with your advanced GIS assignments and digital twin projects.<\/p>\n<p><a href=\"https:\/\/helpwithmygisassignment.com\/\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone\" src=\"https:\/\/img.magnific.com\/premium-vector\/order-now-ecommerce-icon-call-action-button-offer-design-marketing-symbol_1280603-2464.jpg\" alt=\"An OGC 3D Tiles web map tutorial rendering a 3D city digital twin in a web browser.\" width=\"626\" height=\"522\" \/><\/a><\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Building Immersive 3D Digital Twins in the Browser with Deck.gl, MapLibre GL JS, and 3D Tiles Introduction: From 2D web maps to immersive digital twins Welcome to this practical OGC 3D Tiles web map tutorial on building immersive 3D digital twins in the browser using MapLibre GL JS and Deck.gl. Traditional web maps are excellent &#8230; <a title=\"Building Immersive 3D Digital Twins in the Browser with Deck.gl, MapLibre GL JS, and 3D Tiles\" class=\"read-more\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/ogc-3d-tiles-web-map-tutorial\/\" aria-label=\"Read more about Building Immersive 3D Digital Twins in the Browser with Deck.gl, MapLibre GL JS, and 3D Tiles\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":1836,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1776","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/posts\/1776","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/comments?post=1776"}],"version-history":[{"count":3,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/posts\/1776\/revisions"}],"predecessor-version":[{"id":1839,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/posts\/1776\/revisions\/1839"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/media\/1836"}],"wp:attachment":[{"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/media?parent=1776"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/categories?post=1776"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/tags?post=1776"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}