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

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.git
cdsamples/deckgl-heatmap
npmi
npmstart

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026年09月01日 UTC.