Get a Maps Demo Key: Try out select Maps JavaScript API and Places UI Kit features at no cost with a Maps Demo Key—no billing information required.
Google Maps and deck.gl HeatmapLayer
Stay organized with collections
Save and categorize content based on your preferences.
Visualize data with a deck.gl heatmap layer
This example uses uses the deck.gl HeatmapLayer to show bicycle parking
capacity. Use the HeatmapLayer
to visualize spatial distribution of data.
TypeScript
// Initialize and add the map letmap:google.maps.Map; // Use global types for Deck.gl components letheatmapLayer:deck.HeatmapLayer; letgoogleMapsOverlay:deck.GoogleMapsOverlay; letmarker:google.maps.marker.AdvancedMarkerElement|undefined; letinfoWindow:google.maps.InfoWindow; // Declare global namespace for MDC to satisfy TypeScript compiler declarenamespacemdc{ namespacelinearProgress{ classMDCLinearProgress{ constructor(el:Element); open():void; close():void; determinate:boolean; done?:()=>void; } } } // Declare global namespace for Deck.gl to satisfy TypeScript compiler declarenamespacedeck{ classHeatmapLayer{ constructor(props:any); props:any; clone(props:any):HeatmapLayer; pickable:boolean;// Added pickable property } classGoogleMapsOverlay{ constructor(props:any); setMap(map:google.maps.Map|null):void; setProps(props:any):void; } // Add other Deck.gl types used globally if needed } asyncfunctioninit():Promise<void>{ // Progress bar logic moved from index.html letprogress:mdc.linearProgress.MDCLinearProgress|undefined; constprogressDiv=document.querySelector('.mdc-linear-progress'); if(progressDiv){ // Assuming 'mdc' is globally available, potentially loaded via a script tag // If not, you might need to import it or add type definitions. progress=newmdc.linearProgress.MDCLinearProgress(progressDiv); progress.open(); progress.determinate=false; progress.done=function(){ progress!.close(); progressDiv.remove(); }; } // The location for the map center. constposition={lat:37.77325660358167,lng:-122.41712341793448};// Using the center from original deckgl-polygon.js // Request needed libraries. const[{Map,InfoWindow},{AdvancedMarkerElement}]=awaitPromise.all([ google.maps.importLibrary('maps'), google.maps.importLibrary('marker'), ]); constmapDiv=document.getElementById('map'); if(!mapDiv){ console.error('Map element not found!'); return; } // The map, centered at the specified position map=newMap(mapDiv,{ zoom:13,// Using the zoom from original deckgl-polygon.js center:position, tilt:90,// Add tilt heading:-25,// Add heading mapId:'6b73a9fe7e831a00', fullscreenControl:false,// Disable fullscreen control clickableIcons:false,// Disable clicks on base map POIs }); // Deck.gl Layer and Overlay // Use global deck object heatmapLayer=newdeck.HeatmapLayer({ // Assign to the outer heatmapLayer id:'HeatmapLayer',// Change layer ID data:'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json',// Use the loaded data getPosition:(d:any)=>d.COORDINATES,// Use 'any' for simplicity, or define a proper type getWeight:(d:any)=>d.SPACES,// Use 'any' for simplicity, or define a proper type radiusPixels:25,// Adjust radius as in user's example visible:true, pickable:true, onHover:(info:any)=>{ // Use 'any' for info for simplicity, or define a proper type consttooltip=document.getElementById('tooltip'); if(tooltip){ console.log('Hovered object:',info.object); if(info.object){ // Format data for tooltip (display ADDRESS, RACKS, SPACES) lettooltipContent='<h4>Bike Parking Info:</h4>';// Updated title if(info.object.ADDRESS!==undefined){ tooltipContent+=`<strong>Address:</strong> ${info.object.ADDRESS}<br>`; } if(info.object.RACKS!==undefined){ tooltipContent+=`<strong>Racks:</strong> ${info.object.RACKS}<br>`; } if(info.object.SPACES!==undefined){ tooltipContent+=`<strong>Spaces:</strong> ${info.object.SPACES}<br>`; } tooltip.innerHTML=tooltipContent; tooltip.style.left=info.x+'px'; tooltip.style.top=info.y+'px'; tooltip.style.display='block'; }else{ tooltip.style.display='none'; } console.log('Tooltip content set to:',tooltip.innerHTML); } }, }); heatmapLayer.pickable=true;// Ensure pickable is true after creation // Use global deck object googleMapsOverlay=newdeck.GoogleMapsOverlay({ // Assign to the outer googleMapsOverlay layers:[heatmapLayer], controller:true,// Enable Deck.gl to control map view }); googleMapsOverlay.setMap(map); // Hide progress bar after data is loaded and layer is added if(progress){ // Check if progress is defined // Add a small delay to ensure the progress bar is removed setTimeout(()=>{ progress.done?.();// hides progress bar },100);// 100ms delay } // Create a single InfoWindow instance infoWindow=newInfoWindow(); // Add click listener to the map map.addListener('click',(event:google.maps.MapMouseEvent)=>{ constlatLng=event.latLng; if(!latLng)return;// Ensure latLng is not null if(!marker){ // Create the marker on the first click marker=newAdvancedMarkerElement({ map, position:latLng, gmpClickable:true, }); // Add click listener to the marker marker.addEventListener('gmp-click',()=>{ infoWindow.close(); constcontent=` <div>Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}</div> <div><a href="https://www.google.com/maps/search/?api=1&query=${latLng.lat()},${latLng.lng()}" target="_blank">Open in Google Maps</a></div> `; infoWindow.setContent(content); infoWindow.open(map,marker); }); // Open InfoWindow immediately on first click constcontent=` <div>Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}</div> <div><a href="https://www.google.com/maps/search/?api=1&query=${latLng.lat()},${latLng.lng()}" target="_blank">Open in Google Maps</a></div> `; infoWindow.setContent(content); infoWindow.open(map,marker); }else{ // Move the existing marker on subsequent clicks marker.position=latLng; // InfoWindow remains open constcontent=` <div>Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}</div> <div><a href="https://www.google.com/maps/search/?api=1&query=${latLng.lat()},${latLng.lng()}" target="_blank">Open in Google Maps</a></div> `; infoWindow.setContent(content); infoWindow.open(map,marker); } }); // Button functionality consttoggleButton=document.getElementById('toggleButton'); if(toggleButton){ // Check if toggleButton is found toggleButton.addEventListener('click',()=>{ constcurrentVisible=heatmapLayer.props.visible; // Create a new layer instance with toggled visibility and update the overlay constnewLayer=heatmapLayer.clone({visible:!currentVisible}); googleMapsOverlay.setProps({ layers:[newLayer], }); heatmapLayer=newLayer;// Update the heatmapLayer variable toggleButton.textContent=!currentVisible ?'Hide Heatmap Layer' :'Show Heatmap Layer'; }); } } voidinit();
JavaScript
// Initialize and add the map letmap; // Use global types for Deck.gl components letheatmapLayer; letgoogleMapsOverlay; letmarker; letinfoWindow; asyncfunctioninit(){ // Progress bar logic moved from index.html letprogress; constprogressDiv=document.querySelector('.mdc-linear-progress'); if(progressDiv){ // Assuming 'mdc' is globally available, potentially loaded via a script tag // If not, you might need to import it or add type definitions. progress=newmdc.linearProgress.MDCLinearProgress(progressDiv); progress.open(); progress.determinate=false; progress.done=function(){ progress.close(); progressDiv.remove(); }; } // The location for the map center. constposition={lat:37.77325660358167,lng:-122.41712341793448};// Using the center from original deckgl-polygon.js // Request needed libraries. const[{Map,InfoWindow},{AdvancedMarkerElement}]=awaitPromise.all([ google.maps.importLibrary('maps'), google.maps.importLibrary('marker'), ]); constmapDiv=document.getElementById('map'); if(!mapDiv){ console.error('Map element not found!'); return; } // The map, centered at the specified position map=newMap(mapDiv,{ zoom:13,// Using the zoom from original deckgl-polygon.js center:position, tilt:90,// Add tilt heading:-25,// Add heading mapId:'6b73a9fe7e831a00', fullscreenControl:false,// Disable fullscreen control clickableIcons:false,// Disable clicks on base map POIs }); // Deck.gl Layer and Overlay // Use global deck object heatmapLayer=newdeck.HeatmapLayer({ // Assign to the outer heatmapLayer id:'HeatmapLayer',// Change layer ID data:'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf-bike-parking.json',// Use the loaded data getPosition:(d)=>d.COORDINATES,// Use 'any' for simplicity, or define a proper type getWeight:(d)=>d.SPACES,// Use 'any' for simplicity, or define a proper type radiusPixels:25,// Adjust radius as in user's example visible:true, pickable:true, onHover:(info)=>{ // Use 'any' for info for simplicity, or define a proper type consttooltip=document.getElementById('tooltip'); if(tooltip){ console.log('Hovered object:',info.object); if(info.object){ // Format data for tooltip (display ADDRESS, RACKS, SPACES) lettooltipContent='<h4>Bike Parking Info:</h4>';// Updated title if(info.object.ADDRESS!==undefined){ tooltipContent+=`<strong>Address:</strong> ${info.object.ADDRESS}<br>`; } if(info.object.RACKS!==undefined){ tooltipContent+=`<strong>Racks:</strong> ${info.object.RACKS}<br>`; } if(info.object.SPACES!==undefined){ tooltipContent+=`<strong>Spaces:</strong> ${info.object.SPACES}<br>`; } tooltip.innerHTML=tooltipContent; tooltip.style.left=info.x+'px'; tooltip.style.top=info.y+'px'; tooltip.style.display='block'; }else{ tooltip.style.display='none'; } console.log('Tooltip content set to:',tooltip.innerHTML); } }, }); heatmapLayer.pickable=true;// Ensure pickable is true after creation // Use global deck object googleMapsOverlay=newdeck.GoogleMapsOverlay({ // Assign to the outer googleMapsOverlay layers:[heatmapLayer], controller:true,// Enable Deck.gl to control map view }); googleMapsOverlay.setMap(map); // Hide progress bar after data is loaded and layer is added if(progress){ // Check if progress is defined // Add a small delay to ensure the progress bar is removed setTimeout(()=>{ progress.done?.();// hides progress bar },100);// 100ms delay } // Create a single InfoWindow instance infoWindow=newInfoWindow(); // Add click listener to the map map.addListener('click',(event)=>{ constlatLng=event.latLng; if(!latLng)return;// Ensure latLng is not null if(!marker){ // Create the marker on the first click marker=newAdvancedMarkerElement({ map, position:latLng, gmpClickable:true, }); // Add click listener to the marker marker.addEventListener('gmp-click',()=>{ infoWindow.close(); constcontent=` <div>Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}</div> <div><a href="https://www.google.com/maps/search/?api=1&query=${latLng.lat()},${latLng.lng()}" target="_blank">Open in Google Maps</a></div> `; infoWindow.setContent(content); infoWindow.open(map,marker); }); // Open InfoWindow immediately on first click constcontent=` <div>Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}</div> <div><a href="https://www.google.com/maps/search/?api=1&query=${latLng.lat()},${latLng.lng()}" target="_blank">Open in Google Maps</a></div> `; infoWindow.setContent(content); infoWindow.open(map,marker); }else{ // Move the existing marker on subsequent clicks marker.position=latLng; // InfoWindow remains open constcontent=` <div>Location: ${latLng.lat().toFixed(3)}, ${latLng.lng().toFixed(3)}</div> <div><a href="https://www.google.com/maps/search/?api=1&query=${latLng.lat()},${latLng.lng()}" target="_blank">Open in Google Maps</a></div> `; infoWindow.setContent(content); infoWindow.open(map,marker); } }); // Button functionality consttoggleButton=document.getElementById('toggleButton'); if(toggleButton){ // Check if toggleButton is found toggleButton.addEventListener('click',()=>{ constcurrentVisible=heatmapLayer.props.visible; // Create a new layer instance with toggled visibility and update the overlay constnewLayer=heatmapLayer.clone({visible:!currentVisible}); googleMapsOverlay.setProps({ layers:[newLayer], }); heatmapLayer=newLayer;// Update the heatmapLayer variable toggleButton.textContent=!currentVisible ?'Hide Heatmap Layer' :'Show Heatmap Layer'; }); } } voidinit();
CSS
/* * Always set the map height explicitly to define the size of the div element * that contains the map. */ #map{ height:100%; flex-grow:1;/* Make map take up remaining space */ } /* * Optional: Makes the sample page fill the window. */ html, body{ height:100%; margin:0; padding:0; display:flex;/* Use flexbox for layout */ flex-direction:column;/* Stack children vertically */ position:relative;/* Set body as positioning context */ font-family:'Roboto',Arial,sans-serif;/* Set font family */ } #toggleButton{ position:absolute; top:70px;/* Position towards the top of the map area */ left:50%; transform:translateX(-50%); z-index:1000;/* Ensure it's above the map */ } #tooltip{ position:absolute; z-index:1001;/* Ensure it's above the button and map */ padding:10px; background:rgba(0,0,0,0.8); color:#fff; border-radius:4px; pointer-events:none;/* Allows interaction with elements behind the tooltip */ display:none;/* Hidden by default */ font-size:12px; } h1{ text-align:center; margin:10px; } #legend{ position:absolute; top:90px;/* Adjust position as needed */ right:10px;/* Position on the left */ z-index:1000;/* Ensure it's above the map */ background-color:rgba(255,255,255,0.9); padding:10px; border-radius:4px; font-size:14px; } #legendh4{ margin-top:0; margin-bottom:5px; } #legenddiv{ display:flex; align-items:center; margin-bottom:3px; } #legendspan{ display:inline-block; width:20px; height:14px; margin-right:5px; border:1pxsolid#000;/* Add a border for visibility */ } /* Media query for mobile devices */ @media(max-width:600px){ #legend{ position:absolute; bottom:50px;/* Position above the button */ left:50%;/* Center horizontally */ transform:translateX(-50%);/* Adjust for centering */ top:auto;/* Remove top positioning */ right:auto;/* Remove right positioning */ } #toggleButton{ position:absolute; bottom:10px;/* Position at the bottom */ left:50%;/* Center horizontally */ transform:translateX(-50%);/* Adjust for centering */ top:auto;/* Remove top positioning */ } }
HTML
<html>
<head>
<title>deck.gl HeatmapLayer and Google Maps Platform</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Use Material Design Progress indicator -->
<link
href="https://unpkg.com/material-components-web@6.0.0/dist/material-components-web.css"
rel="stylesheet" />
<script src="https://unpkg.com/material-components-web@6.0.0/dist/material-components-web.min.js"></script>
<script src="https://unpkg.com/deck.gl@8.9.22/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/google-maps@8.9.22/dist.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script type="module" src="index.js"></script>
<script>
// prettier-ignore
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
key: "GOOGLE_MAPS_API_KEY"
});
</script>
</head>
<body>
<div
role="progressbar"
class="mdc-linear-progress"
aria-label="Data Progress Bar">
<div class="mdc-linear-progress__buffer">
<div class="mdc-linear-progress__buffer-bar"></div>
<div class="mdc-linear-progress__buffer-dots"></div>
</div>
<div
class="mdc-linear-progress__bar mdc-linear-progress__primary-bar">
<span class="mdc-linear-progress__bar-inner"></span>
</div>
<div
class="mdc-linear-progress__bar mdc-linear-progress__secondary-bar">
<span class="mdc-linear-progress__bar-inner"></span>
</div>
</div>
<h1>Bike Parking Heatmap</h1>
<div id="map"></div>
<div id="legend">
<h4>Bike Parking Spaces</h4>
<div><span style="background-color: #ffffe5"></span>0 - 33</div>
<div><span style="background-color: #ffffb2"></span>34 - 66</div>
<div><span style="background-color: #fecc5c"></span>67 - 100</div>
<div><span style="background-color: #fd8d3c"></span>101 - 133</div>
<div><span style="background-color: #f03b20"></span>134 - 166</div>
<div><span style="background-color: #bd0026"></span>167+</div>
</div>
<button id="toggleButton">Hide Heatmap Layer</button>
</body>
</html>Clone Sample
Git and Node.js are required to run this sample locally. Follow these instructions to install Node.js and NPM. The following commands clone, install dependencies and start the sample application.
gitclonehttps://github.com/googlemaps-samples/js-api-samples.gitcdsamples/deckgl-heatmapnpminpmstart