{"id":1371,"date":"2026-10-25T08:08:36","date_gmt":"2026-10-25T08:08:36","guid":{"rendered":"https:\/\/helpwithmygisassignment.com\/blog\/?p=1371"},"modified":"2026-10-25T08:08:55","modified_gmt":"2026-10-25T08:08:55","slug":"create-3d-web-scenes-deck-gl-mapbox-gl-js","status":"publish","type":"post","link":"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/","title":{"rendered":"Creating 3D Web Scenes with Deck.gl and Mapbox GL JS"},"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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#Creating_3D_Web_Scenes_with_Deckgl_and_Mapbox_GL_JS\" >Creating 3D Web Scenes with Deck.gl and Mapbox GL JS<\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#Introduction\" >Introduction<\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#1_Set_up_the_development_environment\" >1. Set up the development environment<\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#11_Create_the_project\" >1.1 Create the project<\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#12_Organize_the_project\" >1.2 Organize the project<\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#13_Configure_the_Mapbox_access_token\" >1.3 Configure the Mapbox access token<\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#2_Create_the_HTML_page\" >2. Create the HTML page<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#3_Style_the_map_interface\" >3. Style the map interface<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#4_Initialize_Mapbox_GL_JS_with_3D_terrain\" >4. Initialize Mapbox GL JS with 3D terrain<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#41_Read_the_configuration_and_initialize_the_map\" >4.1 Read the configuration and initialize the map<\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#42_Add_the_terrain_elevation_source\" >4.2 Add the terrain elevation source<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#5_Load_spatial_point_and_polygon_datasets_asynchronously\" >5. Load spatial point and polygon datasets asynchronously<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#51_Create_a_reusable_asynchronous_loader\" >5.1 Create a reusable asynchronous loader<\/a><\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#52_Store_the_loaded_data\" >5.2 Store the loaded data<\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#6_Configure_dynamic_lighting_and_polygon_extrusions\" >6. Configure dynamic lighting and polygon extrusions<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#61_Add_a_lighting_effect\" >6.1 Add a lighting effect<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#62_Create_attribute-driven_polygon_styles\" >6.2 Create attribute-driven polygon styles<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#63_Build_the_polygon_layer\" >6.3 Build the polygon layer<\/a><\/li><\/ul><\/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\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#7_Build_a_Deckgl_HexagonLayer_for_3D_density\" >7. Build a Deck.gl HexagonLayer for 3D density<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#71_Define_a_shared_color_scale\" >7.1 Define a shared color scale<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#72_Configure_the_HexagonLayer\" >7.2 Configure the HexagonLayer<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#8_Create_an_alternative_GridLayer\" >8. Create an alternative GridLayer<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#9_Add_interactive_hover_feedback\" >9. Add interactive hover feedback<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#10_Connect_Deckgl_to_the_Mapbox_camera\" >10. Connect Deck.gl to the Mapbox camera<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#101_Build_the_layer_collection\" >10.1 Build the layer collection<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-26\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#102_Create_the_MapboxOverlay_and_load_the_data\" >10.2 Create the MapboxOverlay and load the data<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-27\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#103_Run_the_application\" >10.3 Run the application<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-28\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#11_Understanding_the_3D_scenes_rendering_modes\" >11. Understanding the 3D scene&#8217;s rendering modes<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-29\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#12_Performance_optimization_for_high-performance_web_GIS_3D\" >12. Performance optimization for high-performance web GIS 3D<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-30\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#121_Keep_aggregation_on_the_GPU_where_possible\" >12.1 Keep aggregation on the GPU where possible<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-31\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#122_Avoid_rebuilding_layers_on_every_camera_movement\" >12.2 Avoid rebuilding layers on every camera movement<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-32\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#123_Keep_the_rendered_geometry_manageable\" >12.3 Keep the rendered geometry manageable<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-33\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#124_Stream_and_partition_data_carefully\" >12.4 Stream and partition data carefully<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-34\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#125_Monitor_browser_and_GPU_limitations\" >12.5 Monitor browser and GPU limitations<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-35\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#13_Troubleshooting\" >13. Troubleshooting<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-36\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/#Conclusion\" >Conclusion<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h1><span class=\"ez-toc-section\" id=\"Creating_3D_Web_Scenes_with_Deckgl_and_Mapbox_GL_JS\"><\/span>Creating 3D Web Scenes with Deck.gl and Mapbox GL JS<span class=\"ez-toc-section-end\"><\/span><\/h1>\n<h2><span class=\"ez-toc-section\" id=\"Introduction\"><\/span>Introduction<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1587\" src=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_sjfqbsjfqbsjfqbs-1024x559.jpg\" alt=\"Create 3D Web Scenes Deck.gl Mapbox GL JS\" width=\"1024\" height=\"559\" srcset=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_sjfqbsjfqbsjfqbs-1024x559.jpg 1024w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_sjfqbsjfqbsjfqbs-300x164.jpg 300w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_sjfqbsjfqbsjfqbs-768x419.jpg 768w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_sjfqbsjfqbsjfqbs.jpg 1408w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>When you create 3D web scenes Deck.gl Mapbox GL JS, traditional two-dimensional maps often fall short when displaying massive spatial observations. Traditional web maps work well when displaying points, lines, and polygons in two dimensions. However, when a dataset contains hundreds of thousands of spatial observations, rendering every feature as an individual marker can overwhelm the visual display and consume substantial browser resources.<\/p>\n<p>Imagine building a web GIS application to visualize road incidents, population density, flood reports, or infrastructure demand. A static point map may reveal where observations occur, but it often fails to communicate the intensity and spatial distribution of those observations clearly.<\/p>\n<p>A better approach is to aggregate observations into three-dimensional hexagonal or rectangular cells, extrude geographic polygons, and combine these visualizations with a terrain basemap.<\/p>\n<p>Two JavaScript libraries are particularly useful for this workflow:<\/p>\n<ul>\n<li>\n<div>\n<div>\n<p>Mapbox GL JS provides the interactive basemap, camera navigation, terrain, and map controls.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Deck.gl provides GPU-accelerated visualization layers, including <code>HexagonLayer<\/code>, <code>GridLayer<\/code>, and <code>GeoJsonLayer<\/code>.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<p>Deck.gl&#8217;s aggregation layers support GPU-based aggregation when the browser and selected layer configuration permit it. This can significantly improve performance compared with CPU-based aggregation, although actual performance still depends on the dataset, hardware, and rendering workload.<\/p>\n<p>In this tutorial, we will build an interactive 3D web GIS scene with:<\/p>\n<ul>\n<li>\n<div>\n<div>\n<p>A Mapbox terrain basemap.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>An angled camera with configurable pitch and bearing.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Asynchronous GeoJSON loading for point and polygon data.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Three-dimensional hexagon and grid aggregation.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Attribute-driven elevation and color.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Dynamic lighting and interactive hover feedback.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>A synchronized Deck.gl overlay controlled by Mapbox navigation.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"1_Set_up_the_development_environment\"><\/span>1. Set up the development environment<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>We will use a lightweight JavaScript project powered by Vite. This provides a development server and bundles the application for deployment.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"11_Create_the_project\"><\/span>1.1 Create the project<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Install Node.js and npm, then run:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code># Create a vanilla JavaScript project\r\nnpm create vite@latest deck-3d-scene -- --template vanilla\r\n\r\n# Enter the project directory\r\ncd deck-3d-scene\r\n\r\n# Install the starter dependencies\r\nnpm install<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Install Mapbox GL JS and the required Deck.gl modules:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>npm install mapbox-gl \\\r\n  @deck.gl\/core \\\r\n  @deck.gl\/layers \\\r\n  @deck.gl\/aggregation-layers \\\r\n  @deck.gl\/mapbox<\/code><\/pre>\n<\/div>\n<\/div>\n<p>These packages provide the map renderer, core Deck.gl classes, GeoJSON rendering, aggregation layers, and Mapbox integration. The official Deck.gl documentation supports installing its individual modules through npm.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"12_Organize_the_project\"><\/span>1.2 Organize the project<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Use the following structure:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>deck-3d-scene\/\r\n\u251c\u2500\u2500 .env.local\r\n\u251c\u2500\u2500 index.html\r\n\u251c\u2500\u2500 public\/\r\n\u2502   \u2514\u2500\u2500 data\/\r\n\u2502       \u251c\u2500\u2500 events.geojson\r\n\u2502       \u2514\u2500\u2500 districts.geojson\r\n\u2514\u2500\u2500 src\/\r\n    \u251c\u2500\u2500 main.js\r\n    \u2514\u2500\u2500 style.css<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The point dataset will be called <code>events.geojson<\/code>. The polygon dataset will be called <code>districts.geojson<\/code>.<\/p>\n<p>For this example, we will assume the point features contain a numeric <code>weight<\/code> property, while polygons may contain <code>height_m<\/code> and <code>risk_score<\/code> properties. Adapt these field names to your own data.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"13_Configure_the_Mapbox_access_token\"><\/span>1.3 Configure the Mapbox access token<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Mapbox GL JS requires an access token to load Mapbox-hosted map styles and services. You can create a public token through the <a href=\"https:\/\/account.mapbox.com\/?utm_source=chatgpt.com\" target=\"_blank\" rel=\"noopener\">Mapbox account dashboard<\/a>.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<p>Create <code>.env.local<\/code>:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>VITE_MAPBOX_ACCESS_TOKEN=pk.your_public_mapbox_token_here<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Replace the placeholder with your actual public token.<\/p>\n<p>Security note: A Vite variable prefixed with <code>VITE_<\/code> is included in the client-side application. Use a public token with appropriate URL restrictions; never put a secret server-side token in browser code. Add <code>.env.local<\/code> to <code>.gitignore<\/code> so you do not commit your local configuration.<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"2_Create_the_HTML_page\"><\/span>2. Create the HTML page<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Replace the contents of <code>index.html<\/code> with the following code.<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>&lt;!doctype html&gt;\r\n&lt;html lang=\"en\"&gt;\r\n  &lt;head&gt;\r\n    &lt;meta charset=\"UTF-8\" \/&gt;\r\n\r\n    &lt;!-- Ensure the interface scales on mobile screens --&gt;\r\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/&gt;\r\n\r\n    &lt;title&gt;3D Spatial Explorer&lt;\/title&gt;\r\n  &lt;\/head&gt;\r\n\r\n  &lt;body&gt;\r\n    &lt;header class=\"toolbar\"&gt;\r\n      &lt;div&gt;\r\n        &lt;h1&gt;3D Spatial Explorer&lt;\/h1&gt;\r\n        &lt;p&gt;Explore spatial intensity and polygon attributes&lt;\/p&gt;\r\n      &lt;\/div&gt;\r\n\r\n      &lt;!-- Switch between hexagonal and rectangular aggregation --&gt;\r\n      &lt;label for=\"aggregation-mode\"&gt;\r\n        Aggregation\r\n        &lt;select id=\"aggregation-mode\"&gt;\r\n          &lt;option value=\"hexagon\"&gt;3D Hexagons&lt;\/option&gt;\r\n          &lt;option value=\"grid\"&gt;3D Grid&lt;\/option&gt;\r\n        &lt;\/select&gt;\r\n      &lt;\/label&gt;\r\n\r\n      &lt;!-- Updated by JavaScript during loading and error handling --&gt;\r\n      &lt;span id=\"status\" role=\"status\" aria-live=\"polite\"&gt;\r\n        Initializing map\u2026\r\n      &lt;\/span&gt;\r\n    &lt;\/header&gt;\r\n\r\n    &lt;!-- Mapbox renders the basemap; Deck.gl renders the data layers --&gt;\r\n    &lt;main id=\"map\" aria-label=\"Interactive three-dimensional spatial map\"&gt;\r\n      &lt;div id=\"tooltip\" class=\"tooltip\" hidden&gt;&lt;\/div&gt;\r\n    &lt;\/main&gt;\r\n\r\n    &lt;!-- Vite loads and bundles the application module --&gt;\r\n    &lt;script type=\"module\" src=\"\/src\/main.js\"&gt;&lt;\/script&gt;\r\n  &lt;\/body&gt;\r\n&lt;\/html&gt;<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The HTML defines the map container, aggregation selector, loading status, and a tooltip. The actual map and visualization layers will be initialized in JavaScript.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"3_Style_the_map_interface\"><\/span>3. Style the map interface<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Create <code>src\/style.css<\/code>:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/* Use predictable sizing for the full application *\/\r\n* {\r\n  box-sizing: border-box;\r\n}\r\n\r\nhtml,\r\nbody {\r\n  width: 100%;\r\n  height: 100%;\r\n  margin: 0;\r\n  font-family: Inter, system-ui, -apple-system, sans-serif;\r\n  color: #f1f5f9;\r\n  background: #0b1220;\r\n}\r\n\r\n\/* Header sits above the full-screen map *\/\r\n.toolbar {\r\n  position: relative;\r\n  z-index: 10;\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  gap: 1.25rem;\r\n  min-height: 84px;\r\n  padding: 12px 20px;\r\n  background: #101a2b;\r\n  border-bottom: 1px solid #25344a;\r\n}\r\n\r\n.toolbar h1 {\r\n  margin: 0;\r\n  font-size: 1.1rem;\r\n}\r\n\r\n.toolbar p {\r\n  margin: 5px 0 0;\r\n  color: #a9b8cb;\r\n  font-size: 0.85rem;\r\n}\r\n\r\n.toolbar label {\r\n  display: flex;\r\n  align-items: center;\r\n  gap: 10px;\r\n  font-size: 0.9rem;\r\n}\r\n\r\n.toolbar select {\r\n  padding: 9px 12px;\r\n  border: 1px solid #42536b;\r\n  border-radius: 6px;\r\n  color: #f1f5f9;\r\n  background: #1b2a40;\r\n}\r\n\r\n#status {\r\n  max-width: 280px;\r\n  color: #b7c7da;\r\n  font-size: 0.8rem;\r\n}\r\n\r\n#status.error {\r\n  color: #ffaaaa;\r\n}\r\n\r\n\/* Map fills all available space below the toolbar *\/\r\n#map {\r\n  position: absolute;\r\n  top: 84px;\r\n  right: 0;\r\n  bottom: 0;\r\n  left: 0;\r\n  overflow: hidden;\r\n}\r\n\r\n\/* Tooltip uses map-local pixel coordinates *\/\r\n.tooltip {\r\n  position: absolute;\r\n  z-index: 20;\r\n  max-width: 240px;\r\n  padding: 9px 12px;\r\n  color: #ffffff;\r\n  background: rgba(10, 18, 32, 0.95);\r\n  border: 1px solid #526781;\r\n  border-radius: 6px;\r\n  font-size: 0.8rem;\r\n  pointer-events: none;\r\n  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.25);\r\n}\r\n\r\n@media (max-width: 720px) {\r\n  .toolbar {\r\n    align-items: flex-start;\r\n    flex-direction: column;\r\n    gap: 10px;\r\n  }\r\n\r\n  #map {\r\n    top: 155px;\r\n  }\r\n\r\n  #status {\r\n    max-width: 100%;\r\n  }\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The <code>#map<\/code> element is the shared container for Mapbox GL JS. Its dimensions determine the size of the map viewport, while the tooltip is positioned relative to the same container.<\/p>\n<p>The responsive styling is useful because 3D scenes can be particularly demanding on mobile devices, where memory and rendering performance may be more limited.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"4_Initialize_Mapbox_GL_JS_with_3D_terrain\"><\/span>4. Initialize Mapbox GL JS with 3D terrain<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Create <code>src\/main.js<\/code>.<\/p>\n<p>Begin by importing the libraries:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ Mapbox GL JS: interactive basemap and camera controls\r\nimport mapboxgl from \"mapbox-gl\";\r\nimport \"mapbox-gl\/dist\/mapbox-gl.css\";\r\n\r\n\/\/ Deck.gl: synchronized rendering on the Mapbox map\r\nimport { MapboxOverlay } from \"@deck.gl\/mapbox\";\r\n\r\n\/\/ Standard vector and GeoJSON visualization\r\nimport { GeoJsonLayer } from \"@deck.gl\/layers\";\r\n\r\n\/\/ Aggregation layers for 3D density visualization\r\nimport {\r\n  HexagonLayer,\r\n  GridLayer\r\n} from \"@deck.gl\/aggregation-layers\";\r\n\r\n\/\/ Lighting classes for shaded 3D geometry\r\nimport {\r\n  AmbientLight,\r\n  DirectionalLight,\r\n  LightingEffect\r\n} from \"@deck.gl\/core\";\r\n\r\n\/\/ Application styles\r\nimport \".\/style.css\";<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We import the modules individually so that the application has clear dependencies and does not rely on global library variables.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"41_Read_the_configuration_and_initialize_the_map\"><\/span>4.1 Read the configuration and initialize the map<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Add the following code below the imports:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ Read configuration and interface elements.\r\nconst accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;\r\nconst statusElement = document.getElementById(\"status\");\r\nconst tooltip = document.getElementById(\"tooltip\");\r\nconst aggregationSelect = document.getElementById(\"aggregation-mode\");\r\n\r\n\/\/ Centralized helper for loading and error messages.\r\nfunction setStatus(message, isError = false) {\r\n  statusElement.textContent = message;\r\n  statusElement.classList.toggle(\"error\", isError);\r\n}\r\n\r\n\/\/ Stop early if the Mapbox token has not been configured.\r\nif (!accessToken) {\r\n  setStatus(\r\n    \"Missing Mapbox token. Configure VITE_MAPBOX_ACCESS_TOKEN.\",\r\n    true\r\n  );\r\n\r\n  throw new Error(\"Mapbox access token is missing.\");\r\n}\r\n\r\n\/\/ Make the token available to Mapbox GL JS.\r\nmapboxgl.accessToken = accessToken;\r\n\r\n\/\/ Create the map with an oblique camera view.\r\nconst map = new mapboxgl.Map({\r\n  container: \"map\",\r\n  style: \"mapbox:\/\/styles\/mapbox\/standard-satellite\",\r\n  center: [-122.4194, 37.7749],\r\n  zoom: 11,\r\n  pitch: 58,\r\n  bearing: -24,\r\n  maxPitch: 80,\r\n  antialias: false\r\n});\r\n\r\n\/\/ Add native Mapbox camera controls.\r\nmap.addControl(new mapboxgl.NavigationControl(), \"top-right\");\r\n\r\n\/\/ Add a scale indicator.\r\nmap.addControl(\r\n  new mapboxgl.ScaleControl({\r\n    maxWidth: 120,\r\n    unit: \"metric\"\r\n  }),\r\n  \"bottom-left\"\r\n);<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Several settings are particularly important:<\/p>\n<div>\n<table>\n<tbody>\n<tr>\n<th>Property<\/th>\n<th>Purpose<\/th>\n<\/tr>\n<tr>\n<td><code>center<\/code><\/td>\n<td>Sets the initial longitude and latitude.<\/td>\n<\/tr>\n<tr>\n<td><code>zoom<\/code><\/td>\n<td>Determines the initial zoom level.<\/td>\n<\/tr>\n<tr>\n<td><code>pitch<\/code><\/td>\n<td>Tilts the camera to reveal 3D geometry.<\/td>\n<\/tr>\n<tr>\n<td><code>bearing<\/code><\/td>\n<td>Rotates the camera around the map.<\/td>\n<\/tr>\n<tr>\n<td><code>maxPitch<\/code><\/td>\n<td>Limits how far the camera can tilt.<\/td>\n<\/tr>\n<tr>\n<td><code>antialias<\/code><\/td>\n<td>Enables multisample anti-aliasing when supported; disabled here as a performance-conscious default.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Mapbox GL JS supports setting the initial pitch and bearing when constructing a map. A nonzero pitch makes elevated columns and extruded geometry much easier to interpret.<\/p>\n<p>The coordinates above are only an example. Replace them with the center of your study area.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"42_Add_the_terrain_elevation_source\"><\/span>4.2 Add the terrain elevation source<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Mapbox terrain can be enabled by adding a raster digital elevation model (DEM) and passing its source ID to <code>setTerrain()<\/code>.<\/p>\n<p>Add this code:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ Add terrain after the map style has loaded.\r\nmap.on(\"style.load\", () =&gt; {\r\n  \/\/ Avoid adding the same source more than once.\r\n  if (!map.getSource(\"mapbox-dem\")) {\r\n    map.addSource(\"mapbox-dem\", {\r\n      type: \"raster-dem\",\r\n      url: \"mapbox:\/\/mapbox.mapbox-terrain-dem-v1\",\r\n      tileSize: 512,\r\n      maxzoom: 14\r\n    });\r\n  }\r\n\r\n  \/\/ Apply the DEM as the map's elevation surface.\r\n  map.setTerrain({\r\n    source: \"mapbox-dem\",\r\n    exaggeration: 1.25\r\n  });\r\n});\r\n\r\n\/\/ Log rendering or network errors for debugging.\r\nmap.on(\"error\", (event) =&gt; {\r\n  if (event.error) {\r\n    console.error(\"Mapbox GL JS error:\", event.error);\r\n  }\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The <code>exaggeration<\/code> property increases the apparent relief of the terrain. A value of <code>1.25<\/code> is a starting point; adjust it to suit your geographic region and the scale of the map.<\/p>\n<p>At this point, Mapbox handles the basemap, camera movements, terrain tiles, and map controls. The next step is to load your spatial data.<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"5_Load_spatial_point_and_polygon_datasets_asynchronously\"><\/span>5. Load spatial point and polygon datasets asynchronously<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>For this tutorial, our data schema is:<\/p>\n<p>Point features<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>{\r\n  \"type\": \"Feature\",\r\n  \"geometry\": {\r\n    \"type\": \"Point\",\r\n    \"coordinates\": [-122.4194, 37.7749]\r\n  },\r\n  \"properties\": {\r\n    \"weight\": 4.2,\r\n    \"category\": \"high\"\r\n  }\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Polygon features should contain numeric attributes such as:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>{\r\n  \"properties\": {\r\n    \"height_m\": 80,\r\n    \"risk_score\": 75\r\n  }\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The examples above illustrate the properties expected by the visualization. Your files must contain valid GeoJSON geometries and be saved as FeatureCollections in the <code>public\/data\/<\/code> directory.<\/p>\n<p>GeoJSON point coordinates use the order longitude, latitude, not latitude, longitude.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"51_Create_a_reusable_asynchronous_loader\"><\/span>5.1 Create a reusable asynchronous loader<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Add the following functions to <code>main.js<\/code>:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ Fetch a GeoJSON document and check the HTTP response.\r\nasync function fetchGeoJSON(url) {\r\n  const response = await fetch(url);\r\n\r\n  \/\/ Handle 404, 500, and other unsuccessful HTTP responses.\r\n  if (!response.ok) {\r\n    throw new Error(\r\n      `Failed to fetch ${url}: HTTP ${response.status}`\r\n    );\r\n  }\r\n\r\n  const geojson = await response.json();\r\n\r\n  \/\/ Confirm that the file has the expected top-level structure.\r\n  if (\r\n    geojson.type !== \"FeatureCollection\" ||\r\n    !Array.isArray(geojson.features)\r\n  ) {\r\n    throw new Error(`${url} is not a valid GeoJSON FeatureCollection.`);\r\n  }\r\n\r\n  return geojson;\r\n}\r\n\r\n\/\/ Convert point features into a flat array suitable for Deck.gl.\r\nfunction normalizePointFeatures(geojson) {\r\n  return geojson.features\r\n    .filter((feature) =&gt; {\r\n      return (\r\n        feature?.geometry?.type === \"Point\" &amp;&amp;\r\n        Array.isArray(feature.geometry.coordinates) &amp;&amp;\r\n        feature.geometry.coordinates.length &gt;= 2\r\n      );\r\n    })\r\n    .map((feature, index) =&gt; {\r\n      const [longitude, latitude] = feature.geometry.coordinates;\r\n\r\n      \/\/ If weight is absent, treat each point as weight 1.\r\n      const weight = Number(feature.properties?.weight ?? 1);\r\n\r\n      \/\/ Discard invalid coordinates and invalid or negative weights.\r\n      if (\r\n        !Number.isFinite(longitude) ||\r\n        !Number.isFinite(latitude) ||\r\n        !Number.isFinite(weight) ||\r\n        weight &lt; 0\r\n      ) {\r\n        return null;\r\n      }\r\n\r\n      return {\r\n        id: feature.id ?? index,\r\n        position: [longitude, latitude],\r\n        weight,\r\n        properties: feature.properties ?? {}\r\n      };\r\n    })\r\n    .filter(Boolean);\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p><code>fetchGeoJSON()<\/code> checks both the HTTP response and the data structure. <code>normalizePointFeatures()<\/code> converts GeoJSON point features into flat JavaScript objects containing a position and a numeric weight.<\/p>\n<p>This preprocessing makes the later layer accessors simpler and guards against malformed records.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"52_Store_the_loaded_data\"><\/span>5.2 Store the loaded data<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Add two variables:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ These are populated when asynchronous loading completes.\r\nlet pointData = [];\r\nlet polygonData = null;<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We will load both files using <code>Promise.all()<\/code>. This allows the browser to request them concurrently rather than waiting for one download to finish before starting the other.<\/p>\n<p>Note on large datasets: <code>fetch()<\/code> with <code>response.json()<\/code> is appropriate for many moderate datasets, including some datasets with hundreds of thousands of points. But downloading asynchronously does not make JSON parsing itself non-blocking. For very large datasets, streaming, spatial tiling, or worker-based processing may be necessary.<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"6_Configure_dynamic_lighting_and_polygon_extrusions\"><\/span>6. Configure dynamic lighting and polygon extrusions<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Before configuring the aggregation layer, we will define a lighting effect and a polygon visualization.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"61_Add_a_lighting_effect\"><\/span>6.1 Add a lighting effect<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ Ambient light illuminates the geometry generally.\r\nconst ambientLight = new AmbientLight({\r\n  color: [255, 255, 255],\r\n  intensity: 0.7\r\n});\r\n\r\n\/\/ Directional light adds stronger shading from one direction.\r\nconst directionalLight = new DirectionalLight({\r\n  color: [255, 255, 255],\r\n  intensity: 0.9,\r\n  direction: [-1, -3, -1]\r\n});\r\n\r\n\/\/ Bundle the light sources into a Deck.gl effect.\r\nconst lightingEffect = new LightingEffect({\r\n  ambientLight,\r\n  directionalLights: [directionalLight]\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Ambient light reduces overly dark surfaces, while directional lighting makes the vertical sides and tops of extruded cells easier to distinguish.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"62_Create_attribute-driven_polygon_styles\"><\/span>6.2 Create attribute-driven polygon styles<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We will assign polygon color based on <code>risk_score<\/code> and height based on <code>height_m<\/code>. If <code>height_m<\/code> is missing or invalid, we will derive a simple illustrative height from <code>risk_score<\/code>.<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ Convert a risk score from 0-100 into an RGB color.\r\nfunction getRiskColor(feature) {\r\n  const raw = Number(feature.properties?.risk_score);\r\n  const risk = Number.isFinite(raw)\r\n    ? Math.max(0, Math.min(100, raw))\r\n    : 35;\r\n\r\n  \/\/ Normalize the score into the interval 0-1.\r\n  const t = risk \/ 100;\r\n\r\n  \/\/ Low scores are blue; high scores are red.\r\n  return [\r\n    Math.round(40 + 205 * t),\r\n    Math.round(150 - 95 * t),\r\n    Math.round(230 - 185 * t),\r\n    180\r\n  ];\r\n}\r\n\r\n\/\/ Get a polygon's height for 3D extrusion.\r\nfunction getPolygonElevation(feature) {\r\n  const measuredHeight = Number(feature.properties?.height_m);\r\n\r\n  \/\/ Use a valid supplied height when possible.\r\n  if (Number.isFinite(measuredHeight)) {\r\n    return Math.max(0, measuredHeight);\r\n  }\r\n\r\n  \/\/ Otherwise use risk score as an illustrative extrusion height.\r\n  const risk = Number(feature.properties?.risk_score);\r\n\r\n  if (Number.isFinite(risk)) {\r\n    return Math.max(0, Math.min(100, risk)) * 8;\r\n  }\r\n\r\n  return 0;\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The color function normalizes the score and interpolates between blue and red. The elevation function prefers a supplied height, making the visualization easier to interpret when actual height measurements exist.<\/p>\n<p>The fallback height is a visual encoding\u2014not a claim that the polygon is physically that tall.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"63_Build_the_polygon_layer\"><\/span>6.3 Build the polygon layer<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>We will use <code>GeoJsonLayer<\/code>, which supports polygon extrusion through <code>extruded<\/code> and <code>getElevation<\/code>.<\/p>\n<p>&nbsp;<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>function createPolygonLayer() {\r\n  return new GeoJsonLayer({\r\n    id: \"districts-3d\",\r\n    data: polygonData,\r\n\r\n    \/\/ Draw filled polygon surfaces and their outlines.\r\n    filled: true,\r\n    stroked: true,\r\n    getLineColor: [225, 235, 245, 200],\r\n    getLineWidth: 1,\r\n    lineWidthUnits: \"pixels\",\r\n\r\n    \/\/ Turn polygons into three-dimensional extrusions.\r\n    extruded: true,\r\n    getElevation: getPolygonElevation,\r\n\r\n    \/\/ Encode risk in polygon color.\r\n    getFillColor: getRiskColor,\r\n\r\n    \/\/ Apply shading to the extruded geometry.\r\n    material: true,\r\n\r\n    \/\/ Partial transparency retains visual context from the basemap.\r\n    opacity: 0.75,\r\n\r\n    \/\/ The polygon layer is contextual, so it does not capture the tooltip.\r\n    pickable: false\r\n  });\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>A polygon with a higher <code>risk_score<\/code> will be redder. A polygon with a higher <code>height_m<\/code> will extend farther above its coordinate plane.<\/p>\n<p>For a real-world application, choose an elevation scale appropriate to the data units. For example, a real building height in meters should not be treated as a population count or a risk score without a deliberate visual transformation.<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"7_Build_a_Deckgl_HexagonLayer_for_3D_density\"><\/span>7. Build a Deck.gl HexagonLayer for 3D density<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1588\" src=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_pgm0gfpgm0gfpgm0-1024x572.jpg\" alt=\"Comparison of HexagonLayer and GridLayer for 3D web scenes using Deck.gl and Mapbox GL J\" width=\"1024\" height=\"572\" srcset=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_pgm0gfpgm0gfpgm0-1024x572.jpg 1024w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_pgm0gfpgm0gfpgm0-300x167.jpg 300w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_pgm0gfpgm0gfpgm0-768x429.jpg 768w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_pgm0gfpgm0gfpgm0.jpg 1376w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>The <code>HexagonLayer<\/code> aggregates points into hexagonal cells. Each cell can receive a color and height based on the attributes of the observations falling within it.<\/p>\n<p>The layer supports built-in aggregation methods such as <code>SUM<\/code>, <code>MEAN<\/code>, <code>MIN<\/code>, <code>MAX<\/code>, and <code>COUNT<\/code>. It can perform aggregation on the GPU when the configuration permits it.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"71_Define_a_shared_color_scale\"><\/span>7.1 Define a shared color scale<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ RGB colors, ordered from low to high aggregate weight.\r\nconst densityColorRange = [\r\n  [35, 80, 210],\r\n  [40, 160, 220],\r\n  [70, 200, 120],\r\n  [250, 190, 60],\r\n  [220, 55, 55]\r\n];<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We will use blue and green for lower values and yellow and red for higher aggregate values.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"72_Configure_the_HexagonLayer\"><\/span>7.2 Configure the HexagonLayer<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Add this function to <code>main.js<\/code>:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>function createHexagonLayer() {\r\n  return new HexagonLayer({\r\n    id: \"event-hexagons\",\r\n    data: pointData,\r\n\r\n    \/\/ Aggregate on the GPU when supported by this configuration.\r\n    gpuAggregation: true,\r\n\r\n    \/\/ Read geographic coordinates from each point.\r\n    getPosition: (d) =&gt; d.position,\r\n\r\n    \/\/ Use the weight attribute to determine cell color.\r\n    getColorWeight: (d) =&gt; d.weight,\r\n    colorAggregation: \"SUM\",\r\n    colorRange: densityColorRange,\r\n\r\n    \/\/ Use the number of points to determine cell height.\r\n    getElevationWeight: 1,\r\n    elevationAggregation: \"COUNT\",\r\n\r\n    \/\/ Extrude cells into three dimensions.\r\n    extruded: true,\r\n\r\n    \/\/ Hexagon radius is specified in meters.\r\n    radius: 400,\r\n    coverage: 0.88,\r\n\r\n    \/\/ Map elevation counts into a visible height range.\r\n    elevationRange: [0, 1200],\r\n    elevationScale: 1,\r\n\r\n    \/\/ Enable material shading and hover interactions.\r\n    material: {\r\n      ambient: 0.35,\r\n      diffuse: 0.6,\r\n      shininess: 32,\r\n      specularColor: [0.3, 0.3, 0.3]\r\n    },\r\n\r\n    pickable: true,\r\n    autoHighlight: true,\r\n    opacity: 0.88,\r\n\r\n    \/\/ Handle hover feedback.\r\n    onHover: handleLayerHover\r\n  });\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Here is how the main properties work:<\/p>\n<div>\n<table>\n<tbody>\n<tr>\n<th>Property<\/th>\n<th>Meaning<\/th>\n<\/tr>\n<tr>\n<td><code>getPosition<\/code><\/td>\n<td>Returns the coordinates of each observation.<\/td>\n<\/tr>\n<tr>\n<td><code>getColorWeight<\/code><\/td>\n<td>Supplies the numeric attribute used for color aggregation.<\/td>\n<\/tr>\n<tr>\n<td><code>colorAggregation: \"SUM\"<\/code><\/td>\n<td>Sums <code>weight<\/code> values inside each cell.<\/td>\n<\/tr>\n<tr>\n<td><code>getElevationWeight: 1<\/code><\/td>\n<td>Gives each observation an equal elevation weight.<\/td>\n<\/tr>\n<tr>\n<td><code>elevationAggregation: \"COUNT\"<\/code><\/td>\n<td>Makes cell height represent the number of points.<\/td>\n<\/tr>\n<tr>\n<td><code>radius<\/code><\/td>\n<td>Sets the hexagon radius in meters.<\/td>\n<\/tr>\n<tr>\n<td><code>elevationRange<\/code><\/td>\n<td>Maps aggregated counts to the visual elevation range.<\/td>\n<\/tr>\n<tr>\n<td><code>coverage<\/code><\/td>\n<td>Controls the visible size of the hexagons relative to their bin radius.<\/td>\n<\/tr>\n<tr>\n<td><code>pickable<\/code><\/td>\n<td>Allows user interaction with the generated cells.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>This configuration intentionally separates color from height. A cell can be tall because it contains many observations, while its color represents the sum of their weights. That is useful when visualizing both event frequency and event magnitude.<\/p>\n<p>The <code>radius<\/code> affects how points are grouped spatially. A smaller radius provides finer spatial detail, while a larger radius creates broader patterns with fewer cells.<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"8_Create_an_alternative_GridLayer\"><\/span>8. Create an alternative GridLayer<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A rectangular grid offers an alternative view of the same points. <code>GridLayer<\/code> exposes a similar aggregation interface, but uses square cells with a configurable cell size in me<\/p>\n<p>Add this function:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>function createGridLayer() {\r\n  return new GridLayer({\r\n    id: \"event-grid\",\r\n    data: pointData,\r\n\r\n    gpuAggregation: true,\r\n\r\n    \/\/ Return the point coordinates.\r\n    getPosition: (d) =&gt; d.position,\r\n\r\n    \/\/ Color encodes the sum of each cell's weights.\r\n    getColorWeight: (d) =&gt; d.weight,\r\n    colorAggregation: \"SUM\",\r\n    colorRange: densityColorRange,\r\n\r\n    \/\/ Height encodes the number of points.\r\n    getElevationWeight: 1,\r\n    elevationAggregation: \"COUNT\",\r\n\r\n    \/\/ Set rectangular cell dimensions.\r\n    cellSize: 500,\r\n\r\n    \/\/ Configure 3D extrusion and its visual range.\r\n    extruded: true,\r\n    elevationRange: [0, 1200],\r\n    elevationScale: 1,\r\n\r\n    material: {\r\n      ambient: 0.35,\r\n      diffuse: 0.6,\r\n      shininess: 32,\r\n      specularColor: [0.3, 0.3, 0.3]\r\n    },\r\n\r\n    pickable: true,\r\n    autoHighlight: true,\r\n    opacity: 0.88,\r\n\r\n    onHover: handleLayerHover\r\n  });\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Choose between the layers according to the analytical goal:<\/p>\n<ul>\n<li>\n<div>\n<div>\n<p><code>HexagonLayer<\/code> is useful for density patterns and spatial clustering.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p><code>GridLayer<\/code> is useful when rectangular bins correspond more naturally to an analytical grid or reporting framework.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<p>Both aggregate points into cells, reducing the need to draw every observation separately as an individually visible marker.<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"9_Add_interactive_hover_feedback\"><\/span>9. Add interactive hover feedback<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-1589\" src=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_btk3u7btk3u7btk3-1024x572.jpg\" alt=\"Interactive hover tooltip in a 3D web scene built with Deck.gl and Mapbox GL JS\" width=\"1024\" height=\"572\" srcset=\"https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_btk3u7btk3u7btk3-1024x572.jpg 1024w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_btk3u7btk3u7btk3-300x167.jpg 300w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_btk3u7btk3u7btk3-768x429.jpg 768w, https:\/\/helpwithmygisassignment.com\/blog\/wp-content\/uploads\/2026\/10\/Gemini_Generated_Image_btk3u7btk3u7btk3.jpg 1376w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>A useful 3D web scene should let users inspect the values behind the visualization.<\/p>\n<p>Add this function:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>function handleLayerHover({ object, x, y }) {\r\n  \/\/ Hide the tooltip when the pointer leaves an interactive cell.\r\n  if (!object) {\r\n    tooltip.hidden = true;\r\n    return;\r\n  }\r\n\r\n  \/\/ Read aggregated properties returned by the layer.\r\n  const count = Number(object.count ?? 0);\r\n  const totalWeight = Number(object.colorValue ?? 0);\r\n\r\n  tooltip.textContent =\r\n    `${count.toLocaleString()} points \u00b7 ` +\r\n    `total weight ${totalWeight.toFixed(1)}`;\r\n\r\n  \/\/ Keep the tooltip inside the map viewport.\r\n  const mapWidth = map.getContainer().clientWidth;\r\n  const mapHeight = map.getContainer().clientHeight;\r\n\r\n  const left = Math.max(\r\n    8,\r\n    Math.min(x + 12, mapWidth - 250)\r\n  );\r\n\r\n  const top = Math.max(\r\n    8,\r\n    Math.min(y + 12, mapHeight - 50)\r\n  );\r\n\r\n  tooltip.style.left = `${left}px`;\r\n  tooltip.style.top = `${top}px`;\r\n  tooltip.hidden = false;\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>When the user hovers over an aggregated cell, the tooltip displays the number of observations and the summed weight. Both values help explain the visual encodings.<\/p>\n<p>The exact picking information available depends on the layer and aggregation configuration; the HexagonLayer documentation describes the aggregated object and its count and color\/elevation values.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"10_Connect_Deckgl_to_the_Mapbox_camera\"><\/span>10. Connect Deck.gl to the Mapbox camera<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The important integration component is <code>MapboxOverlay<\/code>.<\/p>\n<p>Rather than constructing an independent Deck.gl map with a separate camera, we attach the overlay to the existing Mapbox map. This allows Deck.gl to synchronize its rendering with Mapbox&#8217;s camera navigation. The integration supports both overlaid and interleaved modes.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"101_Build_the_layer_collection\"><\/span>10.1 Build the layer collection<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Add this function:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>function buildLayers(mode) {\r\n  const layers = [];\r\n\r\n  \/\/ Add polygons first so they provide contextual geography.\r\n  if (polygonData) {\r\n    layers.push(createPolygonLayer());\r\n  }\r\n\r\n  \/\/ Add exactly one aggregation layer.\r\n  if (pointData.length &gt; 0) {\r\n    if (mode === \"grid\") {\r\n      layers.push(createGridLayer());\r\n    } else {\r\n      layers.push(createHexagonLayer());\r\n    }\r\n  }\r\n\r\n  return layers;\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<p>This function returns a layer array that can be submitted to Deck.gl. The selected aggregation mode determines which density layer is rendered.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"102_Create_the_MapboxOverlay_and_load_the_data\"><\/span>10.2 Create the MapboxOverlay and load the data<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Add the final initialization block:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ Wait until Mapbox has finished its initial load.\r\nmap.once(\"load\", async () =&gt; {\r\n  \/\/ Use interleaved rendering to share Mapbox's WebGL2 context.\r\n  const deckOverlay = new MapboxOverlay({\r\n    interleaved: true,\r\n    layers: [],\r\n    effects: [lightingEffect]\r\n  });\r\n\r\n  \/\/ Register the overlay as a Mapbox control.\r\n  map.addControl(deckOverlay);\r\n\r\n  try {\r\n    setStatus(\"Loading spatial datasets\u2026\");\r\n\r\n    \/\/ Fetch both datasets concurrently.\r\n    const [eventsGeoJSON, districtsGeoJSON] = await Promise.all([\r\n      fetchGeoJSON(\"\/data\/events.geojson\"),\r\n      fetchGeoJSON(\"\/data\/districts.geojson\")\r\n    ]);\r\n\r\n    \/\/ Normalize point records for the aggregation layers.\r\n    pointData = normalizePointFeatures(eventsGeoJSON);\r\n\r\n    \/\/ Preserve the polygon FeatureCollection for GeoJsonLayer.\r\n    polygonData = districtsGeoJSON;\r\n\r\n    if (pointData.length === 0) {\r\n      throw new Error(\"No valid point features were found.\");\r\n    }\r\n\r\n    \/\/ Render the initial layer configuration.\r\n    deckOverlay.setProps({\r\n      layers: buildLayers(aggregationSelect.value)\r\n    });\r\n\r\n    setStatus(\r\n      `Loaded ${pointData.length.toLocaleString()} points and ` +\r\n      `${polygonData.features.length.toLocaleString()} polygons.`\r\n    );\r\n  } catch (error) {\r\n    console.error(\"Spatial data loading failed:\", error);\r\n\r\n    setStatus(\r\n      `Could not load spatial data: ${error.message}`,\r\n      true\r\n    );\r\n  }\r\n\r\n  \/\/ Update the visualization when the dropdown changes.\r\n  aggregationSelect.addEventListener(\"change\", () =&gt; {\r\n    deckOverlay.setProps({\r\n      layers: buildLayers(aggregationSelect.value)\r\n    });\r\n  });\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>This is where the application comes together.<\/p>\n<ol>\n<li>\n<div>\n<div>\n<p>The map finishes initializing.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>A Deck.gl overlay is attached to Mapbox.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>The two datasets are fetched concurrently.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Point records are normalized.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>The polygon and selected aggregation layers are created.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>The overlay renders the layers in synchronization with the map.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Changing the dropdown swaps the current aggregation layer without creating a second map.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<\/ol>\n<p>The official integration documentation identifies <code>MapboxOverlay<\/code> as the recommended integration for applications that want Deck.gl layers within the Mapbox ecosystem. In interleaved mode, Deck.gl layers share Mapbox&#8217;s WebGL2 context and can participate in layer ordering and 3D occlusion.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"103_Run_the_application\"><\/span>10.3 Run the application<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Start the Vite development server:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>npm run dev<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Open the local address printed by Vite in your browser.<\/p>\n<p>For the application to display data, ensure that both files exist at:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>public\/data\/events.geojson\r\npublic\/data\/districts.geojson<\/code><\/pre>\n<\/div>\n<\/div>\n<p>If the map loads but your data does not appear, inspect the browser console for fetch errors, geometry issues, or unexpected attribute values.<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"11_Understanding_the_3D_scenes_rendering_modes\"><\/span>11. Understanding the 3D scene&#8217;s rendering modes<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>You will see this configuration in the overlay:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>const deckOverlay = new MapboxOverlay({\r\n  interleaved: true,\r\n  layers: [],\r\n  effects: [lightingEffect]\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The <code>interleaved<\/code> option changes how Deck.gl is rendered alongside the basemap.<\/p>\n<div>\n<table>\n<tbody>\n<tr>\n<th>Mode<\/th>\n<th>Rendering behavior<\/th>\n<th>Appropriate use<\/th>\n<\/tr>\n<tr>\n<td><code>interleaved: true<\/code><\/td>\n<td>Renders Deck.gl layers in Mapbox&#8217;s WebGL2 context and layer stack.<\/td>\n<td>When the visual scene requires integrated layer ordering or 3D occlusion.<\/td>\n<\/tr>\n<tr>\n<td><code>interleaved: false<\/code><\/td>\n<td>Uses a separate Deck.gl canvas above the Mapbox map.<\/td>\n<td>When interleaving is unnecessary or when overlay compatibility is more important.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>With interleaving enabled, Deck.gl layers can be placed in relation to Mapbox style layers where supported, so certain objects can appear behind labels or interact correctly with 3D scene depth. The mode requires a compatible WebGL2 environment.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<p>Important terrain limitation: Deck.gl&#8217;s Mapbox integration has partial terrain support. The cameras can be synchronized, but Deck.gl geometry positioned at <code>z = 0<\/code> is rendered at sea level rather than automatically following the Mapbox terrain surface.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<p>Consequently, the 3D hexagons in this tutorial are data extrusions, not terrain-draped hexagons. In steep terrain, they may appear below or out of alignment with the ground surface. For a terrain-conforming analysis, you must account for the underlying elevation\u2014for example, by sampling elevation data and preparing appropriate 3D<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"12_Performance_optimization_for_high-performance_web_GIS_3D\"><\/span>12. Performance optimization for high-performance web GIS 3D<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>GPU acceleration helps, but it does not eliminate all browser constraints. The most effective strategy is to reduce unnecessary work before and during rendering.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"121_Keep_aggregation_on_the_GPU_where_possible\"><\/span>12.1 Keep aggregation on the GPU where possible<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>In this tutorial, the aggregation layers use:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>gpuAggregation: true,\r\ngetColorWeight: (d) =&gt; d.weight,\r\ncolorAggregation: \"SUM\",\r\ngetElevationWeight: 1,\r\nelevationAggregation: \"COUNT\"<\/code><\/pre>\n<\/div>\n<\/div>\n<p>These built-in accessors use supported aggregation operations, allowing GPU aggregation in compatible configurations.<\/p>\n<p>Be aware that custom <code>getColorValue<\/code> or <code>getElevationValue<\/code> callbacks disable GPU aggregation for these layers. That can be reasonable for specialized statistics, but it should be a deliberate trade-off.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"122_Avoid_rebuilding_layers_on_every_camera_movement\"><\/span>12.2 Avoid rebuilding layers on every camera movement<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A common mistake is to reconstruct the entire dataset or the Deck.gl layer array every time the Mapbox camera changes.<\/p>\n<p>For example, avoid attaching code like this to a <code>move<\/code> event:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>\/\/ Avoid this pattern for large, unchanged datasets.\r\nmap.on(\"move\", () =&gt; {\r\n  deckOverlay.setProps({\r\n    layers: buildLayers(aggregationSelect.value)\r\n  });\r\n});<\/code><\/pre>\n<\/div>\n<\/div>\n<p>The Mapbox overlay already synchronizes rendering with camera movement. Recreating layers unnecessarily can trigger additional processing and resource uploads.<\/p>\n<p>Instead, update the layers when the data changes, when the user switches modes, or when a relevant visualization setting changes. Deck.gl&#8217;s performance guidance emphasizes minimizing data changes and layer updates because buffer generation and uploading may be expensive for large inputs.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"123_Keep_the_rendered_geometry_manageable\"><\/span>12.3 Keep the rendered geometry manageable<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A few practical adjustments make a difference:<\/p>\n<ul>\n<li>\n<div>\n<div>\n<p>Increase <code>radius<\/code> or <code>cellSize<\/code> when a more generalized density surface is sufficient.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Use the polygon level of detail appropriate to the current zoom level.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Simplify large polygon geometries before transmitting them to the browser.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Avoid rendering every point as a separate 3D mesh when aggregation communicates the required information.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<li>\n<div>\n<div>\n<p>Reduce unnecessary transparency, effects, and excessive vertical exaggeration if rendering becomes slow.<\/p>\n<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<p>Remember that a large point dataset and a large polygon dataset have different costs. GPU aggregation may reduce the visual complexity of the points, but it does not automatically simplify the polygon geometries in <code>GeoJsonLayer<\/code>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"124_Stream_and_partition_data_carefully\"><\/span>12.4 Stream and partition data carefully<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Loading a single GeoJSON FeatureCollection is convenient for a demonstration, but large files can consume substantial memory during download, parsing, normalization, and GPU buffer generation.<\/p>\n<p>For much larger or continuously changing datasets, consider:<\/p>\n<p>Spatial filtering on the server. Return only records intersecting the current study area or viewport rather than downloading the entire dataset.<\/p>\n<p><a href=\"https:\/\/helpwithmygisassignment.com\/blog\/vector-tiles-vs-traditional-raster-tiles\/\">Vector tiles<\/a>. Serve pre-tiled features and request only the tiles visible at the current zoom. Deck.gl includes a <code>MVTLayer<\/code> for rendering Mapbox Vector Tiles; this is available through its Geo-layer package.<\/p>\n<div>\n<div><\/div>\n<\/div>\n<p>Binary formats and workers. Formats such as Apache Arrow or custom binary layouts can reduce JSON processing overhead. Deck.gl&#8217;s performance documentation also describes transferring typed arrays and preparing data in workers for demanding applications.<\/p>\n<p>Batched updates. If data arrives continuously, accumulate changes and update the visualization at controlled intervals. Appending one record and rebuilding the aggregate layer for every incoming event can be more expensive than batching the updates.<\/p>\n<p>The best choice depends on whether the application is primarily a static density map, a live monitoring dashboard, or a high-volume analytical system.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"125_Monitor_browser_and_GPU_limitations\"><\/span>12.5 Monitor browser and GPU limitations<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Mapbox GL JS requires WebGL2 support, and the browser must have hardware acceleration available for the best experience.<\/p>\n<p>Test the application on realistic devices, especially if users will access it on mobile. Start with a representative portion of your dataset and gradually increase its size while monitoring loading time, responsiveness, memory pressure, and rendering quality.<\/p>\n<div><\/div>\n<h2><span class=\"ez-toc-section\" id=\"13_Troubleshooting\"><\/span>13. Troubleshooting<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<div>\n<table>\n<tbody>\n<tr>\n<th>Problem<\/th>\n<th>Likely cause<\/th>\n<th>What to check<\/th>\n<\/tr>\n<tr>\n<td>Map tiles fail to load<\/td>\n<td>Missing or restricted access token<\/td>\n<td>Check <code>.env.local<\/code> and token URL restrictions.<\/td>\n<\/tr>\n<tr>\n<td>Map appears but data is absent<\/td>\n<td>Incorrect file path or invalid GeoJSON<\/td>\n<td>Open the two data URLs directly and inspect the console.<\/td>\n<\/tr>\n<tr>\n<td>No visible 3D columns<\/td>\n<td><code>extruded<\/code> is false or elevation values are zero<\/td>\n<td>Check <code>extruded<\/code>, <code>elevationRange<\/code>, and the aggregated values.<\/td>\n<\/tr>\n<tr>\n<td>Cells appear too tall<\/td>\n<td>Height range is excessive for the density distribution<\/td>\n<td>Reduce <code>elevationRange<\/code> or adjust the weight semantics.<\/td>\n<\/tr>\n<tr>\n<td>Scene is sluggish<\/td>\n<td>Excessive geometry, frequent data updates, or GPU\/memory constraints<\/td>\n<td>Simplify polygons, increase cell size, filter data, and minimize updates.<\/td>\n<\/tr>\n<tr>\n<td>Deck.gl geometry is misaligned with terrain<\/td>\n<td>The overlay&#8217;s geometry is not automatically draped over Mapbox terrain<\/td>\n<td>Sample terrain elevation or use a terrain-aware rendering workflow.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<h2><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Creating 3D Web Scenes with Deck.gl and Mapbox GL JS involves more than displaying an angled basemap. The strength of the combination is that Mapbox handles navigation and geographic context while Deck.gl provides specialized layers for turning large spatial datasets into interactive visual summaries.<\/p>\n<p>The core workflow is:<\/p>\n<div>\n<div>\n<pre dir=\"ltr\"><code>GeoJSON datasets\r\n      \u2193\r\nAsync loading and validation\r\n      \u2193\r\nMapbox GL JS\r\n      \u251c\u2500\u2500 Basemap\r\n      \u251c\u2500\u2500 Camera navigation\r\n      \u2514\u2500\u2500 3D terrain\r\n              \u2193\r\n       MapboxOverlay\r\n              \u2193\r\n          Deck.gl\r\n          \u251c\u2500\u2500 GeoJsonLayer\r\n          \u251c\u2500\u2500 HexagonLayer\r\n          \u251c\u2500\u2500 GridLayer\r\n          \u2514\u2500\u2500 Lighting and interactions\r\n              \u2193\r\n      Interactive 3D web GIS<\/code><\/pre>\n<\/div>\n<\/div>\n<p>For large point datasets, <code>HexagonLayer<\/code> and <code>GridLayer<\/code> are useful because they aggregate observations into cells whose color and height can represent different attributes. <code>GeoJsonLayer<\/code> provides a complementary way to extrude polygon data.<\/p>\n<p>For demanding applications, the most important performance decisions often concern data delivery, geometry complexity, GPU aggregation, and how frequently the application updates its layers.<\/p>\n<p>Finally, remember that synchronized cameras and a 3D terrain basemap do not automatically make every Deck.gl feature conform to the terrain surface. A production application that relies on ground-level placement must address that distinction explicitly.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Creating 3D Web Scenes with Deck.gl and Mapbox GL JS Introduction When you create 3D web scenes Deck.gl Mapbox GL JS, traditional two-dimensional maps often fall short when displaying massive spatial observations. Traditional web maps work well when displaying points, lines, and polygons in two dimensions. However, when a dataset contains hundreds of thousands of &#8230; <a title=\"Creating 3D Web Scenes with Deck.gl and Mapbox GL JS\" class=\"read-more\" href=\"https:\/\/helpwithmygisassignment.com\/blog\/create-3d-web-scenes-deck-gl-mapbox-gl-js\/\" aria-label=\"Read more about Creating 3D Web Scenes with Deck.gl and Mapbox GL JS\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":1587,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,4],"tags":[],"class_list":["post-1371","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","category-gis-articles"],"_links":{"self":[{"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/posts\/1371","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=1371"}],"version-history":[{"count":5,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/posts\/1371\/revisions"}],"predecessor-version":[{"id":1591,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/posts\/1371\/revisions\/1591"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/media\/1587"}],"wp:attachment":[{"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/media?parent=1371"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/categories?post=1371"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/helpwithmygisassignment.com\/blog\/wp-json\/wp\/v2\/tags?post=1371"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}