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.

AI-powered summaries

This sample lets you search for AI-powered summaries. Some suggested searches:

  • "Hotel" for neighborhood summaries.
  • "EV charging station" for EVCS amenity summaries.
  • Any restaurant or business for place and review summaries.

Read the documentation.

[フレーム]

TypeScript

// Define DOM elements.
constmapElement=document.querySelector('gmp-map')!;
constplaceAutocomplete=document.querySelector('gmp-place-autocomplete')!;
constsummaryPanel=document.getElementById('summary-panel')!;
constplaceName=document.getElementById('place-name')!;
constplaceAddress=document.getElementById('place-address')!;
consttabContainer=document.getElementById('tab-container')!;
constsummaryContent=document.getElementById('summary-content')!;
constaiDisclosure=document.getElementById('ai-disclosure')!;
constflagContentLink=document.getElementById(
'flag-content-link'
)asHTMLAnchorElement;
letinnerMap:google.maps.Map;
letmarker:google.maps.marker.AdvancedMarkerElement;
asyncfunctioninit():Promise<void>{
// Request needed libraries.
const[{AdvancedMarkerElement}]=awaitPromise.all([
google.maps.importLibrary('marker'),
google.maps.importLibrary('places'),
]);
innerMap=mapElement.innerMap;
innerMap.setOptions({
mapTypeControl:false,
streetViewControl:false,
fullscreenControl:false,
});
// Bind autocomplete bounds to map bounds.
innerMap.addListener('bounds_changed',()=>{
placeAutocomplete.locationRestriction=innerMap.getBounds()!;
});
// Create the marker.
marker=newAdvancedMarkerElement({
map:innerMap,
});
// Handle selection of an autocomplete result.
placeAutocomplete.addEventListener(
'gmp-select',
async({placePrediction})=>{
constplace=placePrediction.toPlace();
// Fetch all summary fields.
awaitplace.fetchFields({
fields:[
'displayName',
'formattedAddress',
'location',
'generativeSummary',
'neighborhoodSummary',
'reviewSummary',
'evChargeAmenitySummary',
],
});
// Update the map viewport and position the marker.
if(place.viewport){
innerMap.fitBounds(place.viewport);
}else{
innerMap.setCenter(place.location!);
innerMap.setZoom(17);
}
marker.position=place.location;
// Update the panel UI.
updateSummaryPanel(place);
}
);
}
functionupdateSummaryPanel(place:google.maps.places.Place){
// Reset UI
summaryPanel.classList.remove('hidden');
tabContainer.innerHTML='';// innerHTML is OK here since we're clearing known child elements.
summaryContent.textContent='';
aiDisclosure.textContent='';
placeName.textContent=place.displayName||'';
placeAddress.textContent=place.formattedAddress||'';
letfirstTabActivated=false;
/**
 * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment).
 */
constcreateTab=(
label:string,
content:string|Node,
disclosure:string|null,
flagUrl:string|null
)=>{
constbtn=document.createElement('button');
btn.className='tab-button';
btn.textContent=label;
btn.onclick=()=>{
// Do nothing if the tab is already active.
if(btn.classList.contains('active')){
return;
}
// Manage the active class state.
document.querySelectorAll('.tab-button').forEach((b)=>{
b.classList.remove('active');
});
btn.classList.add('active');
if(typeofcontent==='string'){
summaryContent.textContent=content;
}else{
summaryContent.replaceChildren(content.cloneNode(true));
}
// Set the disclosure text.
aiDisclosure.textContent=disclosure||'AI-generated content.';
// Add the content flag URI.
if(flagUrl){
flagContentLink.href=flagUrl;
flagContentLink.textContent='Report an issue';
}
};
tabContainer.appendChild(btn);
// Auto-select the first available summary.
if(!firstTabActivated){
btn.click();
firstTabActivated=true;
}
};
// --- 1. Generative Summary (Place) ---
if(place.generativeSummary?.overview){
createTab(
'Overview',
place.generativeSummary.overview,
place.generativeSummary.disclosureText,
place.generativeSummary.flagContentURI
);
}
// --- 2. Review Summary ---
if(place.reviewSummary?.text){
createTab(
'Reviews',
place.reviewSummary.text,
place.reviewSummary.disclosureText,
place.reviewSummary.flagContentURI
);
}
// --- 3. Neighborhood Summary ---
if(place.neighborhoodSummary?.overview?.content){
createTab(
'Neighborhood',
place.neighborhoodSummary.overview.content,
place.neighborhoodSummary.disclosureText,
place.neighborhoodSummary.flagContentURI
);
}
// --- 4. EV Amenity Summary (uses content blocks)) ---
if(place.evChargeAmenitySummary){
constevSummary=place.evChargeAmenitySummary;
constevContainer=document.createDocumentFragment();
// Helper to build a safe DOM section for EV categories.
constcreateSection=(title:string,text:string)=>{
constwrapper=document.createElement('div');
wrapper.style.marginBottom='15px';// Or use a CSS class
consttitleEl=document.createElement('strong');
titleEl.textContent=title;
consttextEl=document.createElement('div');
textEl.textContent=text;
wrapper.appendChild(titleEl);
wrapper.appendChild(textEl);
returnwrapper;
};
// Check and append each potential section
if(evSummary.overview?.content){
evContainer.appendChild(
createSection('Overview',evSummary.overview.content)
);
}
if(evSummary.coffee?.content){
evContainer.appendChild(
createSection('Coffee',evSummary.coffee.content)
);
}
if(evSummary.restaurant?.content){
evContainer.appendChild(
createSection('Food',evSummary.restaurant.content)
);
}
if(evSummary.store?.content){
evContainer.appendChild(
createSection('Shopping',evSummary.store.content)
);
}
// Only add the tab if the container has children
if(evContainer.hasChildNodes()){
createTab(
'EV Amenities',
evContainer,// Passing a Node instead of string
evSummary.disclosureText,
evSummary.flagContentURI
);
}
}
// Safely handle the empty state.
if(!firstTabActivated){
constmsg=document.createElement('em');
msg.textContent=
'No AI summaries are available for this specific location.';
summaryContent.replaceChildren(msg);
aiDisclosure.textContent='';
}
}
voidinit();

JavaScript

// Define DOM elements.
constmapElement=document.querySelector('gmp-map');
constplaceAutocomplete=document.querySelector('gmp-place-autocomplete');
constsummaryPanel=document.getElementById('summary-panel');
constplaceName=document.getElementById('place-name');
constplaceAddress=document.getElementById('place-address');
consttabContainer=document.getElementById('tab-container');
constsummaryContent=document.getElementById('summary-content');
constaiDisclosure=document.getElementById('ai-disclosure');
constflagContentLink=document.getElementById('flag-content-link');
letinnerMap;
letmarker;
asyncfunctioninit(){
// Request needed libraries.
const[{AdvancedMarkerElement}]=awaitPromise.all([
google.maps.importLibrary('marker'),
google.maps.importLibrary('places'),
]);
innerMap=mapElement.innerMap;
innerMap.setOptions({
mapTypeControl:false,
streetViewControl:false,
fullscreenControl:false,
});
// Bind autocomplete bounds to map bounds.
innerMap.addListener('bounds_changed',()=>{
placeAutocomplete.locationRestriction=innerMap.getBounds();
});
// Create the marker.
marker=newAdvancedMarkerElement({
map:innerMap,
});
// Handle selection of an autocomplete result.
placeAutocomplete.addEventListener(
'gmp-select',
async({placePrediction})=>{
constplace=placePrediction.toPlace();
// Fetch all summary fields.
awaitplace.fetchFields({
fields:[
'displayName',
'formattedAddress',
'location',
'generativeSummary',
'neighborhoodSummary',
'reviewSummary',
'evChargeAmenitySummary',
],
});
// Update the map viewport and position the marker.
if(place.viewport){
innerMap.fitBounds(place.viewport);
}else{
innerMap.setCenter(place.location);
innerMap.setZoom(17);
}
marker.position=place.location;
// Update the panel UI.
updateSummaryPanel(place);
}
);
}
functionupdateSummaryPanel(place){
// Reset UI
summaryPanel.classList.remove('hidden');
tabContainer.innerHTML='';// innerHTML is OK here since we're clearing known child elements.
summaryContent.textContent='';
aiDisclosure.textContent='';
placeName.textContent=place.displayName||'';
placeAddress.textContent=place.formattedAddress||'';
letfirstTabActivated=false;
/**
 * Safe Helper: Accepts either a text string or a DOM Node (like a div or DocumentFragment).
 */
constcreateTab=(label,content,disclosure,flagUrl)=>{
constbtn=document.createElement('button');
btn.className='tab-button';
btn.textContent=label;
btn.onclick=()=>{
// Do nothing if the tab is already active.
if(btn.classList.contains('active')){
return;
}
// Manage the active class state.
document.querySelectorAll('.tab-button').forEach((b)=>{
b.classList.remove('active');
});
btn.classList.add('active');
if(typeofcontent==='string'){
summaryContent.textContent=content;
}else{
summaryContent.replaceChildren(content.cloneNode(true));
}
// Set the disclosure text.
aiDisclosure.textContent=disclosure||'AI-generated content.';
// Add the content flag URI.
if(flagUrl){
flagContentLink.href=flagUrl;
flagContentLink.textContent='Report an issue';
}
};
tabContainer.appendChild(btn);
// Auto-select the first available summary.
if(!firstTabActivated){
btn.click();
firstTabActivated=true;
}
};
// --- 1. Generative Summary (Place) ---
if(place.generativeSummary?.overview){
createTab(
'Overview',
place.generativeSummary.overview,
place.generativeSummary.disclosureText,
place.generativeSummary.flagContentURI
);
}
// --- 2. Review Summary ---
if(place.reviewSummary?.text){
createTab(
'Reviews',
place.reviewSummary.text,
place.reviewSummary.disclosureText,
place.reviewSummary.flagContentURI
);
}
// --- 3. Neighborhood Summary ---
if(place.neighborhoodSummary?.overview?.content){
createTab(
'Neighborhood',
place.neighborhoodSummary.overview.content,
place.neighborhoodSummary.disclosureText,
place.neighborhoodSummary.flagContentURI
);
}
// --- 4. EV Amenity Summary (uses content blocks)) ---
if(place.evChargeAmenitySummary){
constevSummary=place.evChargeAmenitySummary;
constevContainer=document.createDocumentFragment();
// Helper to build a safe DOM section for EV categories.
constcreateSection=(title,text)=>{
constwrapper=document.createElement('div');
wrapper.style.marginBottom='15px';// Or use a CSS class
consttitleEl=document.createElement('strong');
titleEl.textContent=title;
consttextEl=document.createElement('div');
textEl.textContent=text;
wrapper.appendChild(titleEl);
wrapper.appendChild(textEl);
returnwrapper;
};
// Check and append each potential section
if(evSummary.overview?.content){
evContainer.appendChild(
createSection('Overview',evSummary.overview.content)
);
}
if(evSummary.coffee?.content){
evContainer.appendChild(
createSection('Coffee',evSummary.coffee.content)
);
}
if(evSummary.restaurant?.content){
evContainer.appendChild(
createSection('Food',evSummary.restaurant.content)
);
}
if(evSummary.store?.content){
evContainer.appendChild(
createSection('Shopping',evSummary.store.content)
);
}
// Only add the tab if the container has children
if(evContainer.hasChildNodes()){
createTab(
'EV Amenities',
evContainer,// Passing a Node instead of string
evSummary.disclosureText,
evSummary.flagContentURI
);
}
}
// Safely handle the empty state.
if(!firstTabActivated){
constmsg=document.createElement('em');
msg.textContent=
'No AI summaries are available for this specific location.';
summaryContent.replaceChildren(msg);
aiDisclosure.textContent='';
}
}
voidinit();

CSS

/* Reuse existing map height */
gmp-map{
height:100%;
}
html,
body{
height:100%;
margin:0;
padding:0;
}
/* Existing Autocomplete Card Style */
.place-autocomplete-card{
background-color:#fff;
border-radius:5px;
box-shadow:rgba(0,0,0,0.35)0px5px15px;
margin:10px;
padding:2px;
font-family:Roboto,sans-serif;
font-size:1rem;
}
gmp-place-autocomplete{
width:330px;
}
/* New: Summary Panel Styles */
.summary-card{
background-color:#fff;
border-radius:5px;
box-shadow:rgba(0,0,0,0.35)0px5px15px;
margin:10px;
padding:0;/* Padding handled by children */
font-family:Roboto,sans-serif;
width:350px;
max-height:80vh;/* Prevent overflow on small screens */
overflow-y:auto;
display:flex;
flex-direction:column;
}
.hidden{
display:none;
}
#place-header{
padding:15px;
background-color:#f8f9fa;
border-bottom:1pxsolid#ddd;
}
#place-headerh2{
margin:005px0;
font-size:1.2rem;
}
#place-address{
margin:0;
color:#555;
font-size:0.9rem;
}
/* Tab Navigation */
.tab-container{
display:flex;
border-bottom:1pxsolid#ddd;
background-color:#fff;
}
.tab-button{
flex:1;
background:none;
border:none;
padding:10px;
cursor:pointer;
font-weight:500;
color:#555;
border-bottom:3pxsolidtransparent;
}
.tab-button:hover{
background-color:#f1f1f1;
}
.tab-button.active{
font-weight:bold;
border-bottom:3pxsolid#000000;
}
.tab-button.active:hover{
background-color:#ffffff;
cursor:default;
}
/* Content Area */
.content-area{
padding:15px;
line-height:1.5;
font-size:0.95rem;
color:#333;
}
.disclosure-footer{
font-size:0.75rem;
color:#666;
padding:10px15px;
border-top:1pxsolid#eee;
font-style:italic;
}
.flag-content-link{
font-size:0.75rem;
color:#666;
padding:10px15px;
border-top:1pxsolid#eee;
}

HTML

<html>
 <head>
 <title>AI Place Summaries</title>
 <link rel="stylesheet" type="text/css" 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>
 <gmp-map center="37.80, -122.425" zoom="14" map-id="DEMO_MAP_ID">
 <!-- Search Input Card -->
 <div
 class="place-autocomplete-card"
 slot="control-inline-start-block-start">
 <gmp-place-autocomplete
 placeholder="Find places with AI summaries"></gmp-place-autocomplete>
 </div>
 <!-- Summary text panel (initially hidden) -->
 <div
 id="summary-panel"
 class="summary-card hidden"
 slot="control-inline-end-block-start">
 <div id="place-header">
 <h2 id="place-name"></h2>
 <p id="place-address"></p>
 </div>
 <!-- Tabs for toggling summary types -->
 <div class="tab-container" id="tab-container"></div>
 <!-- Content display area -->
 <div id="summary-content" class="content-area"></div>
 <!-- Legal/AI Disclosure -->
 <div id="ai-disclosure" class="disclosure-footer"></div>
 <!-- Flag content link -->
 <a id="flag-content-link" class="flag-content-link"></a>
 </div>
 </gmp-map>
 </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/ai-powered-summaries
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.