Luigi Cavalieri - Authoring Open Source Code

How to Give a Greyscale Touch to an Interactive or Static Google Map

Giving a greyscale touch to an interactive or static Google map with React is not an ordinary task, but with the help of this guide it will look really as such.

How to Give a Greyscale Touch to an Interactive or Static Google Map

A greyscale Google map makes markers, polylines and whatever you might want to draw on it, to stand out naturally and with style. There's no doubt about that. Perhaps, you might think that with React repainting a Google map in greyscale is a tricky affair... Trust me, with a little help provided from an online tool and a handful of custom code, you can add to your React app an interactive or static greyscale Google map in really no time. Let's see how!

The JSON Styling Code

The online tool I'm talking about is the "Legacy JSON Styling Wizard" you can find at mapstyle.withgoogle.com. Through this tool we can get a collection of styling presets in the form of a JSON array — exactly the data type we need — which lets us give a greyscale touch to whichever kind of Google map we want to display into a React app, may it an interactive or a static one.

As I don't know for how much longer the JSON styling code will be publicly available, below is a copy — let's assume we have saved it to a file named gmap-styles.json:

1[
2  {
3    "elementType": "geometry",
4    "stylers": [{ "color": "#f5f5f5" }]
5  },
6  {
7    "elementType": "labels.icon",
8    "stylers": [{ "visibility": "off" }]
9  },
10  {
11    "elementType": "labels.text.fill",
12    "stylers": [{ "color": "#616161" }]
13  },
14  {
15    "elementType": "labels.text.stroke",
16    "stylers": [{ "color": "#f5f5f5" }]
17  },
18  {
19    "featureType": "administrative.land_parcel",
20    "elementType": "labels.text.fill",
21    "stylers": [{ "color": "#bdbdbd" }]
22  },
23  {
24    "featureType": "poi",
25    "elementType": "geometry",
26    "stylers": [{ "color": "#eeeeee" }]
27  },
28  {
29    "featureType": "poi",
30    "elementType": "labels.text.fill",
31    "stylers": [{ "color": "#757575" }]
32  },
33  {
34    "featureType": "poi.park",
35    "elementType": "geometry",
36    "stylers": [{ "color": "#e5e5e5" }]
37  },
38  {
39    "featureType": "poi.park",
40    "elementType": "labels.text.fill",
41    "stylers": [{ "color": "#9e9e9e" }]
42  },
43  {
44    "featureType": "road",
45    "elementType": "geometry",
46    "stylers": [{ "color": "#ffffff" }]
47  },
48  {
49    "featureType": "road.arterial",
50    "elementType": "labels.text.fill",
51    "stylers": [{ "color": "#757575" }]
52  },
53  {
54    "featureType": "road.highway",
55    "elementType": "geometry",
56    "stylers": [{ "color": "#dadada" }]
57  },
58  {
59    "featureType": "road.highway",
60    "elementType": "labels.text.fill",
61    "stylers": [{ "color": "#616161" }]
62  },
63  {
64    "featureType": "road.local",
65    "elementType": "labels.text.fill",
66    "stylers": [{ "color": "#9e9e9e" }]
67  },
68  {
69    "featureType": "transit.line",
70    "elementType": "geometry",
71    "stylers": [{ "color": "#e5e5e5" }]
72  },
73  {
74    "featureType": "transit.station",
75    "elementType": "geometry",
76    "stylers": [{ "color": "#eeeeee" }]
77  },
78  {
79    "featureType": "water",
80    "elementType": "geometry",
81    "stylers": [{ "color": "#c9c9c9" }]
82  },
83  {
84    "featureType": "water",
85    "elementType": "labels.text.fill",
86    "stylers": [{ "color": "#9e9e9e" }]
87  }
88]

Interactive Greyscale Google Map

If you haven't already chosen a React tool to add an interactive Google map to your app, I would suggest the @react-google-maps/api package, it's a well coded and supported project built on top of the official JavaScript Google Maps API.

Once installed, writing a reusable Map component is something that doesn't require a steep learning curve — eventually, you can help yourself through the extensive documentation available on this legacy website hosted by GitHub Pages.

As my aim is to provide you with just a brief how-to, a minimal component like the following can serve our purpose:

1import { GoogleMap, useJsApiLoader } from "@react-google-maps/api";
2
3
4function Map({ center }: { center: google.maps.LatLngLiteral }) {
5  const { isLoaded } = useJsApiLoader({
6    id: "google-map",
7    googleMapsApiKey: "YOUR_GMAPS_API_KEY"
8  });
9
10  return ( isLoaded && (
11    <GoogleMap
12      mapContainerStyle={{
13        width: "100%",
14        height: "100%",
15      }}
16      zoom={10}
17      center={center}
18    />
19  ));
20};
21
22export default Map;

Now, all we have to do in order to display our interactive Google map in greyscale is to import the JSON array of stylers, and appropriately pass it as-is to the GoogleMap component through its options prop, like this:

1import { GoogleMap, useJsApiLoader } from "@react-google-maps/api";
2import mapStyles from "../../config/gmap-styles.json";
3
4
5function Map( /* ... */ ) {
6  // ...
7
8  return ( isLoaded && (
9    <GoogleMap
10      // ...
11      options={{ styles: mapStyles }}
12    />
13  ));
14};
15
16export default Map;

Done!

Yes, that's all what was needed to let the users of our React app to interact with a greyscale Google map. Just to give you a visual feedback, the following is the Map component in action in an example application:

The 'Map' component in action in an example app.
The 'Map' component in action in an example app.

Static Greyscale Google Map

In the case of a static Google map, to show it in a React app we don't need a React-specific package, and that keeps being true even if we were dealing with an app developed with Angular, Vue.js, or any other front-end framework or library. What we need instead, is to have access to the Google Maps Static API — your API key will have to be allowed, to use the service — the rest of the work can be easily done with some vanilla TypeScript.

The key point in styling a static Google map is stated in the 'Style syntax' section of the official documentation, which I report here for convenience:

To create a customized styled map, include one or more style parameters in the request URL.

So, in order for the Google Maps Static service to return a greyscale map, we have to convert our JSON array of stylers into a set of style query parameters whose values must follow this pattern:

1style=feature:myFeatureArgument|element:myElementArgument|myRule1:myRule1Argument|myRule2:myRule2Argument

Here we are, the handful of custom code which I was referring to in the introduction of this post it's exactly what we need now, a block of code to which to delegate all the encoding work. I encapsulated such code into a function I named getEncodedMapStyles(), here is the implementation:

1import mapStyles from "../config/gmap-styles.json";
2
3
4const getEncodedMapStyles = () => {
5  const styles: string[] = [];
6
7  for ( const style of mapStyles ) {
8    const parts = [];
9
10    if ( !( "elementType" in style && "stylers" in style ) ) {
11      continue;
12    }
13
14    if ( "featureType" in style ) {
15      parts.push( "feature:" + style.featureType );
16    }
17
18    parts.push( "element:" + style.elementType );
19
20    const styler      = style.stylers[0];
21    const stylerKey   = Object.keys( styler )[0] as keyof typeof styler;
22    const stylerValue = String( styler[stylerKey] ).replace( "#", "0x" );
23
24    parts.push( stylerKey + ":" + stylerValue );
25    styles.push( parts.join( "|" ) );
26  }
27
28  return styles.join( "&style=" );
29};

Comments on the Code

  1. The type cast Object.keys( styler )[0] as keyof typeof styler is necessary only to prevent TypeScript from throwing an error when we try to use stylerKey to dynamically access the properties of the styler object.
  2. As per the documentation, colours must be sent to the Google Maps Static service in their standard hexadecimal form, where the HEX number is prefixed by the "zero-x notation" (0x) — hence the instruction replace( '#', '0x' ).

How to Use getEncodedMapStyles()

We can exemplify the use of getEncodedMapStyles() with the following code, where we put together a request URL, something we definitely need in order to fetch through the Google Maps Static API the image file that we want our app to show to the user:

1import mapStyles from "../config/gmap-styles.json";
2
3
4const getEncodedMapStyles = () => { /* ... */ };
5
6export const getGoogleMapStaticUrl = ( center: { lat: number, lng: number } ) => {
7  const baseUrl      = "https://maps.googleapis.com/maps/api/staticmap?";
8  const queryParams  = [
9    "format=png",
10    "maptype=roadmap",
11    "size=640x640",
12    "scale=1",
13    "zoom=10",
14    "center=" + ( center.lat + "," + center.lng ),
15    "style=" + getEncodedMapStyles(),
16    "key=" + "YOUR_GMAPS_API_KEY",
17  ];
18
19  return baseUrl + queryParams.join( "&" );
20};

Such a straightforward function is more than enough to allow our React app to fetch the static greyscale Google map depicted in the screenshot below:

A static greyscale Google map
A static greyscale Google map fetched through our example code.

That's all! Now it's your turn, happy coding!