-
Notifications
You must be signed in to change notification settings - Fork 6
Update Places UI Kit Nearby Search #880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,39 +7,67 @@ | |
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>Place List Nearby Search with Google Maps</title> | ||
| <meta charset="utf-8"> | ||
| <link rel="stylesheet" type="text/css" href="style.css"> | ||
| <script type="module" src="./index.js"></script> | ||
| <title>Place Search Nearby with Google Maps</title> | ||
| <meta charset="utf-8" /> | ||
| <link rel="stylesheet" type="text/css" href="style.css" /> | ||
| <script type="module" src="./index.js" defer></script> | ||
| <!-- prettier-ignore --> | ||
| <script> | ||
| (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: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "weekly", internalUsageAttributionIds: "gmp_mcp_codeassist_v0.1_github"}); | ||
| </script> | ||
| </head> | ||
| <body> | ||
| <!--[START maps_ui_kit_place_search_nearby_map] --> | ||
| <gmp-map center="-37.813,144.963" zoom="10" map-id="DEMO_MAP_ID"> | ||
| <div class="overlay" slot="control-inline-start-block-start"> | ||
| <div class="container"> | ||
| <gmp-map center="-37.813,144.963" zoom="16" map-id="DEMO_MAP_ID" disable-default-ui></gmp-map> | ||
| <div class="ui-panel"> | ||
| <div class="controls"> | ||
| <select name="types" class="type-select"> | ||
| <option value="">Select a place type</option> | ||
| <option value="cafe">Cafe</option> | ||
| <label for="type-select">Select a place type:</label> | ||
| <select id="type-select" class="type-select"> | ||
| <option value="cafe" selected>Cafe</option> | ||
| <option value="restaurant">Restaurant</option> | ||
| <option value="electric_vehicle_charging_station"> | ||
| EV charging station | ||
| </option> | ||
| <option value="electric_vehicle_charging_station" | ||
| >EV charging station</option | ||
| > | ||
|
Comment on lines
+26
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this may be simpler with label wrapping vs for-id. TBD when seeing the desired UI |
||
| </select> | ||
| </div> | ||
| <div class="list-container"> | ||
| <gmp-place-list selectable style="display: none;"></gmp-place-list> | ||
| <gmp-place-search orientation="vertical" selectable> | ||
| <gmp-place-all-content></gmp-place-all-content> | ||
| <gmp-place-nearby-search-request | ||
| location-restriction="1000@-37.813, 144.963"> | ||
| </gmp-place-nearby-search-request> | ||
| </gmp-place-search> | ||
| </div> | ||
| </div> | ||
| </gmp-map> | ||
| </div> | ||
|
|
||
| <gmp-place-details style="display: none;"> | ||
| <gmp-place-details-place-request></gmp-place-details-place-request> | ||
| <gmp-place-all-content></gmp-place-all-content> | ||
| </gmp-place-details> | ||
| <div id="details-container"> | ||
| <gmp-place-details-compact orientation="horizontal" | ||
| truncation-preferred | ||
| style="width: 400px; | ||
| padding: 0; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe add a comment for why this is inline style instead of stylesheet? |
||
| margin: 0; | ||
| border: none; | ||
| background-color: transparent; | ||
| color-scheme: light;"> | ||
| <gmp-place-details-place-request | ||
| place="places/ChIJ3S-JXmauEmsRUcIaWtf4MzE"></gmp-place-details-place-request> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could have no place id to start, and then have the rest of the init flow populate it from the first, auto-triggered, search results? |
||
| <gmp-place-content-config> | ||
| <gmp-place-media></gmp-place-media> | ||
| <gmp-place-rating></gmp-place-rating> | ||
| <gmp-place-price></gmp-place-price> | ||
| <gmp-place-accessible-entrance-icon></gmp-place-accessible-entrance-icon> | ||
| <gmp-place-open-now-status></gmp-place-open-now-status> | ||
| <gmp-place-attribution | ||
| light-scheme-color="gray" | ||
| dark-scheme-color="white"></gmp-place-attribution> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider a comment on why the attribution is getting color locked this way |
||
| </gmp-place-content-config> | ||
| </gmp-place-details-compact> | ||
| </div> | ||
|
|
||
| <!--[END maps_ui_kit_place_search_nearby_map] --> | ||
| <script>(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: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "alpha"});</script> | ||
| </body> | ||
| </html> | ||
| <!--[END maps_ui_kit_place_search_nearby] --> | ||
| <!--[END maps_ui_kit_place_search_nearby] --> | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,132 +6,166 @@ | |||||
| /* [START maps_ui_kit_place_search_nearby] */ | ||||||
|
|
||||||
| /* [START maps_ui_kit_place_search_nearby_query_selectors] */ | ||||||
| const map = document.querySelector("gmp-map") as any; | ||||||
| const placeList = document.querySelector("gmp-place-list") as any; | ||||||
| const typeSelect = document.querySelector(".type-select") as any; | ||||||
| const placeDetails = document.querySelector("gmp-place-details") as any; | ||||||
| const placeDetailsRequest = document.querySelector('gmp-place-details-place-request') as any; | ||||||
| // Query selectors for various elements in the HTML file. | ||||||
| const map = document.querySelector('gmp-map') as google.maps.MapElement | ||||||
| const placeSearch = document.querySelector('gmp-place-search') as any | ||||||
| const placeSearchQuery = document.querySelector( | ||||||
| 'gmp-place-nearby-search-request' | ||||||
| ) as any | ||||||
| const placeDetails = document.querySelector('gmp-place-details-compact') as any | ||||||
| const placeRequest = document.querySelector( | ||||||
| 'gmp-place-details-place-request' | ||||||
| ) as any | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| const typeSelect = document.querySelector('.type-select') as HTMLSelectElement | ||||||
| /* [END maps_ui_kit_place_search_nearby_query_selectors] */ | ||||||
| let markers = {}; | ||||||
| let infoWindow; | ||||||
|
|
||||||
| async function initMap(): Promise<void> { | ||||||
| await google.maps.importLibrary('places'); | ||||||
| const { LatLngBounds } = await google.maps.importLibrary('core') as google.maps.CoreLibrary; | ||||||
| const { InfoWindow } = await google.maps.importLibrary('maps') as google.maps.MapsLibrary; | ||||||
| const { spherical } = await google.maps.importLibrary('geometry') as google.maps.GeometryLibrary; | ||||||
|
|
||||||
| infoWindow = new InfoWindow; | ||||||
| let marker; | ||||||
|
|
||||||
| function getContainingCircle(bounds) { | ||||||
| const diameter = spherical.computeDistanceBetween( | ||||||
| bounds.getNorthEast(), | ||||||
| bounds.getSouthWest() | ||||||
| ); | ||||||
| const calculatedRadius = diameter / 2; | ||||||
| const cappedRadius = Math.min(calculatedRadius, 50000); // Radius cannot be more than 50000. | ||||||
| return { center: bounds.getCenter(), radius: cappedRadius }; | ||||||
| } | ||||||
|
|
||||||
| findCurrentLocation(); | ||||||
|
|
||||||
| // Global variables for the map, markers, and info window. | ||||||
| const markers: Map<string, google.maps.marker.AdvancedMarkerElement> = new Map() | ||||||
| let infoWindow | ||||||
|
|
||||||
| let AdvancedMarkerElement: typeof google.maps.marker.AdvancedMarkerElement | ||||||
| let LatLngBounds: typeof google.maps.LatLngBounds | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider - do we need all these to be globals? |
||||||
|
|
||||||
| // The init function is called when the page loads. | ||||||
| async function init(): Promise<void> { | ||||||
| // Import the necessary libraries from the Google Maps API. | ||||||
| const { InfoWindow } = (await google.maps.importLibrary( | ||||||
| 'maps' | ||||||
| )) as google.maps.MapsLibrary | ||||||
| await google.maps.importLibrary('places') | ||||||
| const markerLib = (await google.maps.importLibrary( | ||||||
| 'marker' | ||||||
| )) as google.maps.MarkerLibrary | ||||||
| const coreLib = (await google.maps.importLibrary( | ||||||
| 'core' | ||||||
| )) as google.maps.CoreLibrary | ||||||
|
Comment on lines
+32
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe have these load in parallel, instead of in sequence? |
||||||
|
|
||||||
| AdvancedMarkerElement = markerLib.AdvancedMarkerElement | ||||||
| LatLngBounds = coreLib.LatLngBounds | ||||||
|
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggest |
||||||
|
|
||||||
| // Create a new info window and set its content to the place details element. | ||||||
| infoWindow = new InfoWindow({ | ||||||
| content: placeDetails, | ||||||
| ariaLabel: 'Place Details', | ||||||
| headerDisabled: true, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's check with a11y - what's the impact of disabling the header without offering our a custom close button? |
||||||
| pixelOffset: { width: 0, height: -40 } as google.maps.Size, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. curious: why is a typecast needed here? |
||||||
| }) | ||||||
|
|
||||||
| // Set the map options. | ||||||
| map.innerMap.setOptions({ | ||||||
| mapTypeControl: false, | ||||||
| clickableIcons: false, | ||||||
| }); | ||||||
| mapTypeControl: false, | ||||||
| streetViewControl: false, | ||||||
| }) | ||||||
|
|
||||||
| // Add a click listener to the map to hide the info window when the map is clicked. | ||||||
| map.innerMap.addListener('click', () => { | ||||||
| hideInfoWindow() | ||||||
| }) | ||||||
| /* [START maps_ui_kit_place_search_nearby_event] */ | ||||||
| placeDetails.addEventListener('gmp-load', (event) => { | ||||||
| // Center the info window on the map. | ||||||
| map.innerMap.fitBounds(placeDetails.place.viewport, { top: 500, left: 400 }); | ||||||
| }); | ||||||
|
|
||||||
| typeSelect.addEventListener('change', (event) => { | ||||||
| // First remove all existing markers. | ||||||
| for(marker in markers){ | ||||||
| markers[marker].map = null; | ||||||
| // Add event listeners to the type select and place search elements. | ||||||
| typeSelect.addEventListener('change', searchPlaces) | ||||||
|
|
||||||
| placeSearch.addEventListener('gmp-select', (event: Event) => { | ||||||
| const { place } = event as any | ||||||
| if (markers.has(place.id)) { | ||||||
| markers.get(place.id)!.click() | ||||||
| } | ||||||
| markers = {}; | ||||||
|
|
||||||
| if (typeSelect.value) { | ||||||
| placeList.style.display = 'block'; | ||||||
| placeList.configureFromSearchNearbyRequest({ | ||||||
| locationRestriction: getContainingCircle( | ||||||
| map.innerMap.getBounds() | ||||||
| ), | ||||||
| includedPrimaryTypes: [typeSelect.value], | ||||||
| }).then(addMarkers); | ||||||
| // Handle user selection in Place Details. | ||||||
| placeList.addEventListener('gmp-placeselect', ({ place }) => { | ||||||
| markers[place.id].click(); | ||||||
| }); | ||||||
| }) | ||||||
| placeSearch.addEventListener( | ||||||
| 'gmp-load', | ||||||
| () => { | ||||||
| searchPlaces() | ||||||
| }, | ||||||
| { once: true } | ||||||
| ) | ||||||
| } | ||||||
| /* [END maps_ui_kit_place_search_nearby_event] */ | ||||||
| // The searchPlaces function is called when the user changes the type select or when the page loads. | ||||||
| function searchPlaces() { | ||||||
| // Get the map bounds and center. | ||||||
| const bounds = map.innerMap.getBounds()! | ||||||
| const cent = map.innerMap.getCenter()! | ||||||
| const ne = bounds.getNorthEast() | ||||||
| const sw = bounds.getSouthWest() | ||||||
| // Calculate the diameter of the map bounds and cap the radius at 50000. | ||||||
| const diameter = google.maps.geometry.spherical.computeDistanceBetween( | ||||||
| ne, | ||||||
| sw | ||||||
| ) | ||||||
| const cappedRadius = Math.min(diameter / 2, 50000) // Radius cannot be more than 50000. | ||||||
|
|
||||||
| // Close the info window and clear the markers. | ||||||
| infoWindow.close() | ||||||
|
|
||||||
| for (const marker of markers.values()) { | ||||||
| marker.map = null | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
tiny tiny bit better practice, I think |
||||||
| } | ||||||
| markers.clear() | ||||||
|
|
||||||
| // Set the place search query and add an event listener to the place search element. | ||||||
| if (typeSelect.value) { | ||||||
| // map.style.height = '75vh'; | ||||||
| placeSearch.style.visibility = 'visible' | ||||||
| placeSearchQuery.maxResultCount = 10 | ||||||
| placeSearchQuery.locationRestriction = { | ||||||
| center: { lat: cent.lat(), lng: cent.lng() }, | ||||||
| radius: cappedRadius, | ||||||
| } | ||||||
| }); | ||||||
| /* [END maps_ui_kit_place_search_nearby_event] */ | ||||||
| placeSearchQuery.includedTypes = [typeSelect.value] | ||||||
| placeSearch.addEventListener('gmp-load', () => addMarkers(), { | ||||||
| once: true, | ||||||
| }) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| async function addMarkers(){ | ||||||
| const { AdvancedMarkerElement } = await google.maps.importLibrary('marker') as google.maps.MarkerLibrary; | ||||||
| const { LatLngBounds } = await google.maps.importLibrary('core') as google.maps.CoreLibrary; | ||||||
|
|
||||||
| const bounds = new LatLngBounds(); | ||||||
| // The addMarkers function is called when the place search element loads. | ||||||
| async function addMarkers() { | ||||||
| // Create a new LatLngBounds object and make the place search element visible. | ||||||
| const bounds = new LatLngBounds() | ||||||
| placeSearch.style.visibility = 'visible' | ||||||
|
|
||||||
| if(placeList.places.length > 0){ | ||||||
| placeList.places.forEach((place) => { | ||||||
| let marker = new AdvancedMarkerElement({ | ||||||
| // Iterate over the places and create a marker for each place. | ||||||
| if (placeSearch.places.length > 0) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| placeSearch.places.forEach((place) => { | ||||||
| const marker = new AdvancedMarkerElement({ | ||||||
| map: map.innerMap, | ||||||
| position: place.location | ||||||
| }); | ||||||
| position: place.location, | ||||||
| collisionBehavior: | ||||||
| google.maps.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL, | ||||||
| }) | ||||||
|
|
||||||
| markers[place.id] = marker; | ||||||
| bounds.extend(place.location); | ||||||
| // Add the marker to the markers map and extend the bounds. | ||||||
| markers.set(place.id, marker) | ||||||
| bounds.extend(place.location) | ||||||
|
|
||||||
| /* [START maps_ui_kit_place_search_nearby_click_event] */ | ||||||
| marker.addListener('gmp-click', (event) => { | ||||||
| if(infoWindow.isOpen){ | ||||||
| infoWindow.close(); | ||||||
| // Add a click listener to the marker to show the info window. | ||||||
| marker.addListener('click', () => { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| if (place.viewport) { | ||||||
| map.innerMap.fitBounds(place.viewport) | ||||||
| } else { | ||||||
| map.innerMap.panTo(place.location) | ||||||
| map.innerMap.setZoom(18) | ||||||
| } | ||||||
|
|
||||||
| placeDetailsRequest.place = place.id; | ||||||
| placeDetails.style.display = 'block'; | ||||||
| placeDetails.style.width = '350px'; | ||||||
| infoWindow.setOptions({ | ||||||
| content: placeDetails, | ||||||
| }); | ||||||
| infoWindow.open({ | ||||||
| anchor: marker, | ||||||
| map: map.innerMap | ||||||
| }); | ||||||
| }); | ||||||
| placeRequest.place = place | ||||||
| infoWindow.setPosition(place.location) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider attaching the info window to the marker instead? I'd need to check, but I think it will look a tad nicer, taking certain offsets into account |
||||||
| placeDetails.style.visibility = 'visible' // Ensure place details are visible | ||||||
| infoWindow.open(map.innerMap) | ||||||
| }) | ||||||
| /* [END maps_ui_kit_place_search_nearby_click_event] */ | ||||||
|
|
||||||
| map.innerMap.setCenter(bounds.getCenter()); | ||||||
| map.innerMap.fitBounds(bounds); | ||||||
| }); | ||||||
| }) | ||||||
| // Set the map center and fit the bounds. | ||||||
| map.innerMap.setCenter(bounds.getCenter()) | ||||||
| map.innerMap.fitBounds(bounds) | ||||||
|
Comment on lines
+159
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. interesting - does this particular set of calls produce a particularly desired effect? (I usually just see |
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| async function findCurrentLocation(){ | ||||||
| const { LatLng } = await google.maps.importLibrary('core') as google.maps.CoreLibrary; | ||||||
| if (navigator.geolocation) { | ||||||
| navigator.geolocation.getCurrentPosition( | ||||||
| (position) => { | ||||||
| const pos = new LatLng(position.coords.latitude,position.coords.longitude); | ||||||
| map.innerMap.panTo(pos); | ||||||
| map.innerMap.setZoom(14); | ||||||
| }, | ||||||
| () => { | ||||||
| console.log('The Geolocation service failed.'); | ||||||
| map.innerMap.setZoom(14); | ||||||
| }, | ||||||
| ); | ||||||
| } else { | ||||||
| console.log('Your browser doesn\'t support geolocation'); | ||||||
| map.innerMap.setZoom(14); | ||||||
| } | ||||||
|
|
||||||
| // The hideInfoWindow function is called when the user clicks on the map. | ||||||
| function hideInfoWindow() { | ||||||
| infoWindow.close() | ||||||
| placeDetails.style.visibility = 'hidden' | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. once the placeDetails is moved to inside the IW, it will naturally only be shown when the IW is visible. |
||||||
| } | ||||||
|
|
||||||
| initMap(); | ||||||
| init() | ||||||
| /* [END maps_ui_kit_place_search_nearby] */ | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internalUsageAttributionIds maybe should be removed? (leave that to show external usage of MCP?)