Skip to content

Commit

Permalink
feat!: improve google maps integration (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw authored Aug 7, 2024
1 parent 0c7906e commit b189d54
Show file tree
Hide file tree
Showing 9 changed files with 665 additions and 121 deletions.
53 changes: 40 additions & 13 deletions docs/components/content/GoogleMapsDemo.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,39 @@
<script setup lang="ts">
import { ref } from 'vue'
import type { Ref } from 'vue'
const isLoaded = ref(false)
const center = ref()
const maps = ref()
const query = ref('Space+Needle,Seattle+WA')
function handleReady(_map: Ref<google.maps.Map>) {
const map = _map.value
center.value = map.getCenter()
map.addListener('center_changed', () => {
center.value = map.getCenter()
const query = ref({
lat: -37.7995487,
lng: 144.9867841,
})
const markers = ref([])
let increment = 1
function addMarker() {
// push to markers, we want to add a marker from the center but randomize the position by a bit
const _center = center.value || query.value
// lat and lng may be a function
const _lat = typeof _center.lat === 'function' ? _center.lat() : _center.lat
const _lng = typeof _center.lng === 'function' ? _center.lng() : _center.lng
const lat = (1000 * _lat + increment) / 1000
const lng = (1000 * _lng + increment) / 1000
increment += 1
markers.value.push(`${lat},${lng}`)
}
function removeMarkers() {
markers.value = []
increment = 0
}
function handleReady({ map }) {
center.value = map.value.getCenter()
map.value.addListener('center_changed', () => {
center.value = map.value.getCenter()
})
isLoaded.value = true
}
Expand All @@ -22,22 +44,27 @@ function handleReady(_map: Ref<google.maps.Map>) {
<div class="flex items-center justify-center p-5">
<ScriptGoogleMaps
ref="maps"
:query="query"
:center="query"
:markers="markers"
api-key="AIzaSyAOEIQ_xOdLx2dNwnFMzyJoswwvPCTcGzU"
width="600"
height="400"
class="group"
above-the-fold
@ready="handleReady"
/>
</div>
<div class="text-center">
<UAlert v-if="!isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Hover to load" description="Hover the map will load the Google Maps iframe." />
<UAlert v-if="isLoaded" class="mb-5" size="sm" color="blue" variant="soft">
<template #title>
<UAlert v-if="!isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Static Image: Hover to load interactive" description="Hovering the map will trigger the Google Maps script to load and init the map." />
<UAlert v-if="isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Interactive Map">
<template #description>
Center: {{ center }}
</template>
</UAlert>
<UButton type="button" class="" @click="addMarker">
Add Marker
</UButton>
<UButton v-if="markers.length" type="button" color="gray" variant="ghost" class="" @click="removeMarkers">
Remove Markers
</UButton>
</div>
</div>
</template>
195 changes: 155 additions & 40 deletions docs/content/scripts/content/google-maps.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ You'll need an API key with permissions to access the [Static Maps API](https://

Showing an interactive JS map requires the Maps JavaScript API, which is a paid service. If a user interacts with the map, the following costs will be incurred:
- $7 per 1000 loads for the Maps JavaScript API (default for using Google Maps)
- $5 per 1000 loads for the Geocoding API
- $2 per 1000 loads for the Static Maps API
- $2 per 1000 loads for the Static Maps API - You can avoid providing a `placeholder` slot.
- $5 per 1000 loads for the Geocoding API - You can avoid this by providing a `google.maps.LatLng` object instead of a string for the `center` prop

However, if the user never engages with the map, only the Static Maps API usage ($2 per 1000 loads) will be charged.

Expand All @@ -53,45 +53,73 @@ and are okay with a less interactive map.
```vue [Input]
<script setup lang="ts">
import { ref } from 'vue'
import type { Ref } from 'vue'
const isLoaded = ref(false)
const center = ref()
const maps = ref()
const query = ref('Space+Needle,Seattle+WA')
function handleReady(_map: Ref<google.maps.Map>) {
const map = _map.value
center.value = map.getCenter()
map.addListener('center_changed', () => {
center.value = map.getCenter()
const query = ref({
lat: -37.7995487,
lng: 144.9867841,
})
const markers = ref([])
let increment = 1
function addMarker() {
// push to markers, we want to add a marker from the center but randomize the position by a bit
const _center = center.value || query.value
// lat and lng may be a function
const _lat = typeof _center.lat === 'function' ? _center.lat() : _center.lat
const _lng = typeof _center.lng === 'function' ? _center.lng() : _center.lng
const lat = (1000 * _lat + increment) / 1000
const lng = (1000 * _lng + increment) / 1000
increment += 1
markers.value.push(`${lat},${lng}`)
}
function removeMarkers() {
markers.value = []
increment = 1
}
function handleReady({ map }) {
center.value = map.value.getCenter()
map.value.addListener('center_changed', () => {
center.value = map.value.getCenter()
})
isLoaded.value = true
}
</script>
<template>
<div class="not-prose">
<div class="flex items-center justify-center p-5">
<ScriptGoogleMaps
ref="maps"
:query="query"
api-key="AIzaSyAOEIQ_xOdLx2dNwnFMzyJoswwvPCTcGzU"
width="600"
height="400"
class="group"
@ready="handleReady"
/>
</div>
<div class="text-center">
<UAlert v-if="!isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Hover to load" description="Hover the map will load the Google Maps iframe." />
<UAlert v-if="isLoaded" class="mb-5" size="sm" color="blue" variant="soft">
<template #title>
Center: {{ center }}
</template>
</UAlert>
</div>
<div class="not-prose">
<div class="flex items-center justify-center p-5">
<ScriptGoogleMaps
ref="maps"
:center="query"
:markers="markers"
api-key="AIzaSyAOEIQ_xOdLx2dNwnFMzyJoswwvPCTcGzU"
class="group"
above-the-fold
@ready="handleReady"
/>
</div>
<div class="text-center">
<UAlert v-if="!isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Static Image: Hover to load interactive" description="Hovering the map will trigger the Google Maps script to load and init the map." />
<UAlert v-if="isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Interactive Map">
<template #description>
Center: {{ center }}
</template>
</UAlert>
<UButton @click="addMarker" type="button" class="">
Add Marker
</UButton>
<UButton v-if="markers.length" @click="removeMarkers" type="button" color="gray" variant="ghost" class="">
Remove Markers
</UButton>
</div>
</div>
</template>
```

Expand All @@ -101,16 +129,43 @@ function handleReady(_map: Ref<google.maps.Map>) {

The `ScriptGoogleMaps` component accepts the following props:

- `trigger`: The trigger event to load the Google Maps. Default is `mouseover`. See [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) for more information.
- `aboveTheFold`: Optimizes the placeholder image for above-the-fold content. Default is `false`.
**Map**

- `center`: Where to center the map. You can provide a string with the location or use a `{ lat: 0, lng: 0 }` object.
- `apiKey`: The Google Maps API key. Must have access to the Static Maps API as well. You can optionally provide this as runtime config using the `public.scripts.googleMaps.apiKey` key.
- `query`: Map marker location. You can provide a string with the location or use the `google.maps.LatLng` object.
- `options`: Options for the map. See [MapOptions](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions).
- `width`: The width of the map. Default is `600`.
- `height`: The height of the map. Default is `400`.
- `centerMarker`: Whether to display a marker at the center position. Default is `true`.
- `mapOptions`: Options for the map. See [MapOptions](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions).

**Placeholder**

You can customize the placeholder image using the following props, alternatively, you can use the `#placeholder` slot to customize the placeholder image.

- `placeholderOptions`: Customize the placeholder image attributes. See [Static Maps API](https://developers.google.com/maps/documentation/maps-static/start).
- `placeholderAttrs`: Customize the placeholder image attributes.

**Sizing**

If you want to render a map larger than 640x640 you should provide your own placeholder as the [Static Maps API](https://developers.google.com/maps/documentation/maps-static/start)
does not support rendering maps larger than this.

- `width`: The width of the map. Default is `640`.
- `height`: The height of the map. Default is `400`.

**Optimizations**

- `trigger`: The trigger event to load the Google Maps. Default is `mouseover`. See [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) for more information.
- `aboveTheFold`: Optimizes the placeholder image for above-the-fold content. Default is `false`.

**Markers**

You can add markers to the static and interactive map by providing an array of `MarkerOptions`. See [MarkerOptions](https://developers.google.com/maps/documentation/javascript/reference/marker#MarkerOptions).

- `markers`: An array of markers to display on the map.

See the [markers](https://github.com/nuxt/scripts/blob/main/playground/pages/third-parties/google-maps/markers.vue) example for more information.

### Guides

#### Eager Loading Placeholder

The Google Maps placeholder image is lazy-loaded by default. You should change this behavior if your map is above the fold
Expand All @@ -132,6 +187,57 @@ or consider using the `#placeholder` slot to customize the placeholder image.

::

#### Advanced Marker Control

If you need more control over the markers on the map, you can use the exposed `createAdvancedMapMarker` function which
will return the marker instance.

```vue
<script lang="ts" setup>
const googleMapsRef = ref()
onMounted(() => {
const marker = googleMapsRef.value.createAdvancedMapMarker({
position: { }
})
})
</script>
<template>
<ScriptGoogleMaps ref="googleMapsRef" />
</template>
```


#### Advanced Map Control

The component exposes all internal APIs, so you can customize your map as needed.

```vue
<script lang="ts" setup>
const googleMapsRef = ref()
onMounted(async () => {
const api = googleMapsRef.value
// Access internal APIs
const googleMaps = api.googleMaps.value // google.maps api
const mapInstance = api.map.value // google.maps.Map instance
// Convert a query to lat/lng
const query = await api.resolveQueryToLatLang('Space Needle, Seattle, WA') // { lat: 0, lng: 0 }
// Import a Google Maps library
const geometry = await api.importLibrary('geometry')
const distance = new googleMaps.geometry.spherical.computeDistanceBetween(
new googleMaps.LatLng(0, 0),
new googleMaps.LatLng(0, 0)
)
})
</script>
<template>
<ScriptGoogleMaps ref="googleMapsRef" />
</template>
```


### Component API

See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots.
Expand All @@ -150,7 +256,7 @@ To subscribe to Google Map events, you can use the `ready` event.

```vue
<script setup lang="ts">
function handleReady(map) {
function handleReady({ map }) {
map.addListener('center_changed', () => {
console.log('Center changed', map.getCenter())
})
Expand Down Expand Up @@ -256,14 +362,23 @@ Loading the Google Maps SDK and interacting with it programmatically.

```vue
<script setup lang="ts">
/// <reference types="google.maps" />
const { $script } = useScriptGoogleMaps({
apiKey: 'key'
})
$script.then(({ maps }) => {
const map = new maps.Map(document.getElementById('map'), {
center: { lat: -34.397, lng: 150.644 },
zoom: 8
const map = ref()
onMounted(() => {
$script.then(async (instance) => {
const maps = await instance.maps as any as typeof google.maps // upstream google type issue
new maps.Map(map.value, {
center: { lat: -34.397, lng: 150.644 },
zoom: 8
})
// Do something with the map
})
})
</script>
<template>
<div ref="map" />
</template>
```
57 changes: 57 additions & 0 deletions playground/pages/third-parties/google-maps/center.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<script setup>
import { ref } from 'vue'
const mapOptions = ref({
center: { lat: -34.397, lng: 150.644 },
})
function changeQuery() {
mapOptions.value = {
center: {
// move just a little
lat: mapOptions.value.center.lat + 0.01,
lng: mapOptions.value.center.lng + 0.01,
},
}
}
</script>

<template>
<div>
<div>
<ScriptGoogleMaps
ref="googleMapsRef"
api-key="AIzaSyAOEIQ_xOdLx2dNwnFMzyJoswwvPCTcGzU"
:width="1200"
:height="600"
:map-options="mapOptions"
above-the-fold
@init="setupGoogleMaps"
/>
</div>
<div class="button-container">
<button
class="button"
@click="changeQuery"
>
move center
</button>
</div>
</div>
</template>

<style>
.button-container {
margin: 20px 0;
}
.button {
background-color: orange;
border-radius: 8px;
padding: 4px 8px;
cursor: pointer;
}
.button:not(:last-child) {
margin-right: 8px;
}
</style>
Loading

0 comments on commit b189d54

Please sign in to comment.