Skip to content
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

feat!: improve google maps integration #191

Merged
merged 8 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
harlan-zw marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we link to the source, where the amounts are coming from?
This might get outdated as time goes by, maybe we could add a disclaimer along the lines: "The example cost is based on this source. Actual cost may change. For more accurate cost estimation, please visit ... "


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
harlan-zw marked this conversation as resolved.
Show resolved Hide resolved
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