Skip to content

Commit 55acea2

Browse files
feat: add StaticMap component with SSR support (#633)
Co-authored-by: Martin Schuhfuss <[email protected]>
1 parent e8cbe09 commit 55acea2

28 files changed

+1272
-10
lines changed

docs/api-reference/components/map.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ Maps JavaScript API is based on map-views (effectively calls to the
8686
`google.maps.Map` constructor), this can quickly cause a problem.
8787

8888
The `Map` component can be configured to re-use already created maps with
89-
the [`reuseMaps`](#reusemaps-boolean) prop.
90-
When enabled, all `Map` components created with the same [`mapId`](#mapid-string),
91-
[`colorScheme`](#colorscheme-googlemapscolorschemegmp-color-scheme-type) and
92-
[`renderingType`](#renderingtype-googlemapsrenderingtypegmp-rendering-type) will reuse
89+
the [`reuseMaps`](#reusemaps-boolean) prop.
90+
When enabled, all `Map` components created with the same [`mapId`](#mapid-string),
91+
[`colorScheme`](#colorscheme-googlemapscolorscheme) and
92+
[`renderingType`](#renderingtype-googlemapsrenderingtype) will reuse
9393
previously created instances instead of creating new ones.
9494

9595
:::warning
@@ -162,7 +162,7 @@ of the [Cloud-based maps styling][gmp-map-styling].
162162

163163
The [color-scheme][gmp-color-scheme] to be used by the map. Can be
164164
`'LIGHT'`, `'DARK'`, `'FOLLOW_SYSTEM'` or one of the
165-
`ColorScheme` constants
165+
`ColorScheme` constants
166166
(`import {ColorScheme} from '@vis.gl/react-google-maps';`).
167167

168168
:::note
@@ -174,7 +174,7 @@ Custom styles that use Map IDs only apply to the light color scheme for roadmap
174174
#### `renderingType`: [google.maps.RenderingType][gmp-rendering-type]
175175

176176
The desired rendering type the renderer should use. Can be `'RASTER'` or
177-
`'VECTOR'` or one of the `RenderingType` constants
177+
`'VECTOR'` or one of the `RenderingType` constants
178178
(`import {RenderingType} from '@vis.gl/react-google-maps';`).
179179

180180
If not set, the cloud configuration for the map ID will determine the
@@ -196,7 +196,7 @@ style-prop is no longer applied.
196196
#### `reuseMaps`: boolean
197197

198198
Enable map-instance caching for this component. When caching is enabled,
199-
this component will reuse map instances created with the same `mapId`,
199+
this component will reuse map instances created with the same `mapId`,
200200
`colorScheme` and `renderingType`.
201201

202202
See also the section [Map Instance Caching](#map-instance-caching) above.
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# `<StaticMap>` Component
2+
3+
React component and utility function to create and render [Google Static Maps][gmp-static-map] images. This implementation provides both a React component for rendering and a URL generation utility that supports all Google Static Maps API features. The main purpose of the utility function is to enable 'url-signing' in various
4+
server environments.
5+
6+
The main parameters to control the map are `center`,
7+
`zoom`, `width` and `height`. With a plain map all of these are required for the map to show. There are cases where `center` and `zoom` can be omitted and the viewport can be automatically be determined from other data. This is the case when having markers, paths or other `visible` locations which can form an automatic bounding box for the map view.
8+
9+
Parameters that are always required are `apiKey`, `width` and `height`.
10+
11+
```tsx
12+
import {StaticMap, createStaticMapsUrl} from '@vis.gl/react-google-maps';
13+
14+
const App = () => {
15+
let staticMapsUrl = createStaticMapsUrl({
16+
apiKey: 'YOUR API KEY',
17+
width: 512,
18+
height: 512,
19+
center: {lat: 53.555570296010295, lng: 10.008892744638956},
20+
zoom: 15
21+
});
22+
23+
// Recommended url-signing when in a server environment.
24+
staticMapsUrl = someServerSigningCode(
25+
staticMapsUrl,
26+
process.env.MAPS_SIGNING_SECRET
27+
);
28+
29+
return <StaticMap url={staticMapsUrl} />;
30+
};
31+
```
32+
33+
More on URL signing and digital signatures [here](#digital-signature).
34+
35+
## Props
36+
37+
The `StaticMap` component only has one `url` prop. The recommended way to generate the url is to use the `createStaticMapsUrl` helper function.
38+
39+
### Required
40+
41+
#### `url`: string
42+
43+
An url which can be consumed by the Google Maps Static Api.
44+
45+
### Optional
46+
47+
#### `className`: string
48+
49+
A class name that will be attached to the `img` tag.
50+
51+
## `createStaticMapsUrl` options
52+
53+
:::note
54+
55+
Some explanations and syntax migh differ slighty from the official documentation since the Google documentation focuses on building and URL which has
56+
been abstracted here in the helper function for better developer experience
57+
58+
:::
59+
60+
For more details about API options see the [get started][get-started] guide in the Google documentation.
61+
62+
### Required
63+
64+
#### `apiKey`: string
65+
66+
The Google Maps Api key.
67+
68+
#### `width`: number
69+
70+
Width of the image. Maps smaller than 180 pixels in width will display a reduced-size Google logo. This parameter is affected by the scale parameter; the final output size is the product of the size and scale values.
71+
72+
#### `height`: number
73+
74+
Height of the image. This parameter is affected by the scale parameter; the final output size is the product of the size and scale values.
75+
76+
### Optional
77+
78+
#### `center`: [StaticMapsLocation](#staticmapslocation)
79+
80+
(required if no markers, paths or visible locations are present) Defines the center of the map, equidistant from all edges of the map. This parameter takes a location as either [`google.maps.LatLngLiteral`][gmp-ll] or a string address (e.g. "city hall, new york, ny") identifying a unique location on the face of the earth.
81+
82+
#### `zoom`: number
83+
84+
(required if no markers, paths or visible locations are present) Defines the zoom level of the map, which determines the magnification level of the map. This parameter takes a numerical value corresponding to the zoom level of the region desired.
85+
86+
#### `scale`: number
87+
88+
Affects the number of pixels that are returned. scale=2 returns twice as many pixels as scale=1 while retaining the same coverage area and level of detail (i.e. the contents of the map don't change). This is useful when developing for high-resolution displays. The default value is 1. Accepted values are 1 and 2
89+
90+
#### `format`: 'png' | 'png8' | 'png32' | 'gif' | 'jpg' | 'jpg-baseline'
91+
92+
Defines the format of the resulting image. By default, the Maps Static API creates PNG images. There are several possible formats including GIF, JPEG and PNG types. Which format you use depends on how you intend to present the image. JPEG typically provides greater compression, while GIF and PNG provide greater detail
93+
94+
#### `mapType`: [google.maps.MapTypeId][gmp-map-type-id]
95+
96+
Defines the type of map to construct. There are several possible maptype values, including roadmap, satellite, hybrid, and terrain.
97+
98+
#### `language`: string
99+
100+
Defines the language to use for display of labels on map tiles. Note that this parameter is only supported for some country tiles; if the specific language requested is not supported for the tile set, then the default language for that tileset will be used.
101+
102+
#### `region`: string
103+
104+
Defines the appropriate borders to display, based on geo-political sensitivities. Accepts a region code specified as a two-character ccTLD ('top-level domain') value
105+
106+
#### `mapId`: string
107+
108+
Specifies the identifier for a specific map. The Map ID associates a map with a particular style or feature, and must belong to the same project as the API key used to initialize the map.
109+
110+
#### `markers`: [StaticMapsMarker[]](#staticmapsmarker)
111+
112+
Defines markers that should be visible on the map.
113+
114+
#### `paths`: [StaticMapsPath[]](#staticmapspath)
115+
116+
Defines paths that should be shown on the map.
117+
118+
#### `visible`: [StaticMapsLocation[]](#staticmapslocation)
119+
120+
Specifies one or more locations that should remain visible on the map, though no markers or other indicators will be displayed. Use this parameter to ensure that certain features or map locations are shown on the Maps Static API.
121+
122+
#### `style`: [google.maps.MapTypeStyle[]][gmp-map-type-style]
123+
124+
Defines a custom style to alter the presentation of a specific feature (roads, parks, and other features) of the map. This parameter takes feature and element arguments identifying the features to style, and a set of style operations to apply to the selected features. See [style reference][gmp-style-ref] for more information.
125+
126+
## Digital Signature
127+
128+
:::warning
129+
130+
Please only use URL signing on the server and keep your URL signing secret secure. Do not pass it in any requests, store it on any websites, or post it to any public forum. Anyone obtaining your URL signing secret could spoof requests using your identity.
131+
132+
:::
133+
It is recommended to use a [digital signature][digital-signature] with your Static Maps Api requests.
134+
135+
Digital signatures are generated using a URL signing secret, which is available on the Google Cloud Console. This secret is essentially a private key, only shared between you and Google, and is unique to your project.
136+
137+
The signing process uses an encryption algorithm to combine the URL and your shared secret. The resulting unique signature allows our servers to verify that any site generating requests using your API key is authorized to do so.
138+
139+
- Step 1: [Get your URL signing secret][sign-secret]
140+
- Step 2: Construct an unsigned request with the `createStaticMapUrl` helper.
141+
- Step 3: [Generate the signed request][generate-signed] | [Sample code for URL signing][sample-code]
142+
143+
Google also provides a package [`@googlemaps/url-signature`][url-signature] for URL signing. Another example could look like this. Here in a Next.js environment.
144+
145+
```tsx
146+
import 'server-only';
147+
148+
import {signUrl} from '@googlemaps/url-signature';
149+
150+
export function signStaticMapsUrl(url: string, secret: string): string {
151+
return signUrl(url, secret).toString();
152+
}
153+
```
154+
155+
When the signing process is setup, you can then [limit the unsigned request][limit-unsigned] to prevent abuse of your api key
156+
157+
## Types
158+
159+
### StaticMapsLocation
160+
161+
Reference: [`google.maps.LatLngLiteral`][gmp-ll]
162+
163+
```tsx
164+
type StaticMapsLocation = google.maps.LatLngLiteral | string;
165+
```
166+
167+
### StaticMapsMarker
168+
169+
- For `color`, `size`, `label` see [marker styles][gmp-marker-styles].
170+
- For `icon`, `anchor` and `scaling` see [custom icons][gmp-custom-icons].
171+
172+
```tsx
173+
type StaticMapsMarker = {
174+
location: google.maps.LatLngLiteral | string;
175+
color?: string;
176+
size?: 'tiny' | 'mid' | 'small';
177+
label?: string;
178+
icon?: string;
179+
anchor?: string;
180+
scale?: 1 | 2 | 4;
181+
};
182+
```
183+
184+
### StaticMapsPath
185+
186+
For style options see [Path styles][gmp-path-styles].
187+
188+
`coordinates` can either bei an array of locations/addresses or it can be an [encoded polyline][gmp-encoded-polyline]. Note that the encoded polyline needs an `enc:` prefix.
189+
190+
```tsx
191+
type StaticMapsPath = {
192+
coordinates: Array<google.maps.LatLngLiteral | string> | string;
193+
weight?: number;
194+
color?: string;
195+
fillcolor?: string;
196+
geodesic?: boolean;
197+
};
198+
```
199+
200+
## Examples
201+
202+
Usage examples for many of the API options can be found [here][usage-examples]
203+
204+
## Source
205+
206+
[`./src/components/static-map`][static-map-source]\
207+
[`./src/libraries/create-static-maps-url/index`][create-static-map-url-source]\
208+
[`./src/libraries/create-static-maps-url/types`][create-static-map-url-types]
209+
210+
[gmp-static-map]: https://developers.google.com/maps/documentation/maps-static
211+
[static-map-source]: https://github.com/visgl/react-google-maps/tree/main/src/components/static-map
212+
[create-static-map-url-source]: https://github.com/visgl/react-google-maps/tree/main/src/libraries/create-static-maps-url/index.ts
213+
[create-static-map-url-types]: https://github.com/visgl/react-google-maps/tree/main/src/libraries/create-static-maps-url/types
214+
[gmp-map-type-id]: https://developers.google.com/maps/documentation/javascript/reference/map#MapTypeId
215+
[gmp-ll]: https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLngLiteral
216+
[gmp-map-type-style]: https://developers.google.com/maps/documentation/javascript/reference/map#MapTypeStyle
217+
[usage-examples]: https://github.com/visgl/react-google-maps/tree/main/examples/static-map
218+
[get-started]: https://developers.google.com/maps/documentation/maps-static/start
219+
[sign-secret]: https://developers.google.com/maps/documentation/maps-static/digital-signature#get-secret
220+
[gmp-encoded-polyline]: https://developers.google.com/maps/documentation/maps-static/start#EncodedPolylines
221+
[gmp-path-styles]: https://developers.google.com/maps/documentation/maps-static/start#PathStyles
222+
[gmp-marker-styles]: https://developers.google.com/maps/documentation/maps-static/start#MarkerStyles
223+
[gmp-custom-icons]: https://developers.google.com/maps/documentation/maps-static/start#CustomIcons
224+
[limit-unsigned]: https://developers.google.com/maps/documentation/maps-static/digital-signature#limit-unsigned-requests
225+
[url-signature]: https://www.npmjs.com/package/@googlemaps/url-signature
226+
[generate-signed]: https://developers.google.com/maps/documentation/maps-static/digital-signature#generate-signed-request
227+
[sample-code]: https://developers.google.com/maps/documentation/maps-static/digital-signature#sample-code-for-url-signing
228+
[digital-signature]: https://developers.google.com/maps/documentation/maps-static/digital-signature
229+
[gmp-style-ref]: https://developers.google.com/maps/documentation/maps-static/style-reference

docs/table-of-contents.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"api-reference/components/info-window",
4141
"api-reference/components/marker",
4242
"api-reference/components/advanced-marker",
43-
"api-reference/components/pin"
43+
"api-reference/components/pin",
44+
"api-reference/components/static-map"
4445
]
4546
},
4647
{

examples/examples.css

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,44 @@ html[data-theme='dark'] .gm-style {
126126
opacity: 0.5;
127127
cursor: default;
128128
}
129+
130+
.static-map-grid,
131+
.static-map-grid * {
132+
box-sizing: border-box;
133+
}
134+
135+
.static-map-grid {
136+
display: grid;
137+
grid-template-rows: 1fr 1fr;
138+
grid-template-columns: 1fr 1fr;
139+
height: 100%;
140+
width: 100%;
141+
gap: 16px;
142+
padding: 16px;
143+
background: darkgray;
144+
}
145+
146+
.static-map-grid .map-container {
147+
display: flex;
148+
position: relative;
149+
}
150+
151+
.static-map-grid .map-container:nth-child(1) {
152+
align-items: flex-end;
153+
justify-content: flex-end;
154+
}
155+
156+
.static-map-grid .map-container:nth-child(2) {
157+
align-items: flex-end;
158+
}
159+
.static-map-grid .map-container:nth-child(3) {
160+
justify-content: flex-end;
161+
}
162+
163+
.static-map-grid .map {
164+
object-fit: contain;
165+
position: absolute;
166+
max-height: 100%;
167+
width: auto;
168+
max-width: 100%;
169+
}

examples/static-map/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Static Map Example
2+
3+
This is an example to show how to use the `Static Map` component.
4+
5+
## Google Maps Platform API Key
6+
7+
This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform.
8+
See [the official documentation][get-api-key] on how to create and configure your own key.
9+
10+
The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a
11+
file named `.env` in the example directory with the following content:
12+
13+
```shell title=".env"
14+
GOOGLE_MAPS_API_KEY="<YOUR API KEY HERE>"
15+
```
16+
17+
If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets)
18+
19+
## Development
20+
21+
Go into the example-directory and run
22+
23+
```shell
24+
npm install
25+
```
26+
27+
To start the example with the local library run
28+
29+
```shell
30+
npm run start-local
31+
```
32+
33+
The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example)
34+
35+
[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key

examples/static-map/index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1.0, user-scalable=no" />
8+
<title>Static Map Example</title>
9+
<meta name="description" content="Static Map Example" />
10+
<style>
11+
body {
12+
margin: 0;
13+
font-family: sans-serif;
14+
}
15+
#app {
16+
width: 100vw;
17+
height: 100vh;
18+
}
19+
</style>
20+
</head>
21+
<body>
22+
<div id="app"></div>
23+
<script type="module">
24+
import '@vis.gl/react-google-maps/examples.css';
25+
import '@vis.gl/react-google-maps/examples.js';
26+
import {renderToDom} from './src/app';
27+
28+
renderToDom(document.querySelector('#app'));
29+
</script>
30+
</body>
31+
</html>

0 commit comments

Comments
 (0)