Day 15: Showing user's direction of movement on embedded Google Maps
Tags: geolocation, googlemaps, webdev, svg
Published: 2021-11-04 12:16:39

## TL;DR

Unlike Google Maps native app, a web app cannot show the direction at which the user's device points at on the map embedded in a page.

A work around is to calculate the direction of the user's movement from the current and previous location, by using trigonometry along with React's `useRef` hook and Geolocation API's `watchPosition()` method (Sections 2 to 4).

As a marker to indicate the user's direction of movement, an airplane icon can provide a nice user experience (Section 5).

## Introduction

In [**Day 14 of this blog series**](https://dev.to/masakudamatsu/day-14-tracking-user-location-on-embedded-google-maps-2ba5), I described how I've coded to implement a web app feature that keeps updating the user's location on embedded Google Maps.

To enhance this feature, I want to indicate the direction at which the user is moving. When the user gets out from a subway station, it's really hard to tell which direction they are facing. On such an occasion, it'll be very helpful if they can see a map shown with the direction of the user's movement. 

It turns out that this feature is not straightforward to implement with a web app. This article describes how I've tackled this challenge.

## 1. A web app's limitation

Google Maps iOS/Android app shows which direction the user's device points at:

![A white-rimmed blue dot emits blue flash light on a street map](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ck2gbstm8zprwij2i096.png) **Google Maps iOS/Android app shows the direction at which the user is heading in addition to the user's location** (image source: [Geo Awesomeness](https://geoawesomeness.com/google-maps-get-redesign-of-the-blue-dot-showing-your-position/)) 

As far as I know, however, a web app cannot implement this feature.

A web app relies on Geolocation API to learn where the user is. Its specification ([MDN Contributors 2021](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates/heading)) says that the direction of movement is available as the `GeolocationCoordinates` object's `heading` property, but its value is unavailable if the user is not moving. When I tested with my iPhone to see if the `heading` property is of any use, I didn't see any direction data on the map even when I walked around with the phone. 

Indeed, [the web app version of Google Maps](https://maps.google.com) does not show the “flashlight” emitted from the blue dot.

So I was about to give up showing the direction at which the user's moving.

## 2. A work around: using trigonometry 

However, I realized that I could calculate the direction of user movement from a pair of the current and previous location coordinates.

Chapter 3 of _The Nature Of Code_, a must-read book for computer-programming animation ([Shiffman 2012](https://natureofcode.com/book/chapter-3-oscillation/)), explains how we can get an angle of direction from two points of location: 

![A movement by x units to the east and by y units to the north yields the angle (measured anti-clockwise from the east direction) that satisfies the equation in which the tangent of the angle equals y over x](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ddqoz6a1grp4mxem7zh.png)

**How the angle of movement is calculated with trigonometry** (image source: Figure 3.6 of [Shiffman 2012](https://natureofcode.com/book/chapter-3-oscillation/))

In JavaScript, the formula goes like this:

```javascript
Math.atan2(diffLat, diffLng) * 180 / Math.PI
```

where `diffLat` is the change in north-south direction (negative if moving southwards) and `diffLng` is the change in east-west direction (negative if moving westwards). The `Math.PI` is JavaScript's way of returning the irrational number of π (3.14...).

The [`Math.atan2()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2) is the inverse function of the tangent (known as _arctangent_), with the first argument for the amount of northward movement and the second for eastward. It returns an angle in radians. To convert it into degrees, we need to multiply it with `180 / Math.PI` because the relationship between radians and degrees are given by

```
radians = 2π x (degrees / 360) 
```

![A full circle is 360 degrees or 2π in radians](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g7ld7h9wilot09xk6eod.png)

**Degrees vs. Radians** (image source: [101 Computing](https://www.101computing.net/radians-to-degrees-conversions/))

So I can use this formula to show the user's direction of movement on the embedded Google Maps.

## 3. Working with React and Geolocation API

I need the user's current location and previous location, to get the direction of movement. This can be done with Geolocation API and React as follows:

```javascript
const userLocation = useRef(null);

...
// Keep track of user location
navigator.geolocation.watchPosition(position => {
  // Record the previous user location
  const previousCoordinates = userLocation.current;
  // Update the user location
  userLocation.current = {
    lat: position.coords.latitude, 
    lng: position.coords.longitude
  };
  ...  
})
```

The `userLocation.current` is initially `null`. When the `watchPosition` method is run for the first time, therefore, the variable `previousCoordinates` will be `null`. However, the second time the `watchPosition` method runs (which happens whenever the user's device updates location data), the `previousCoordinates` points to the initial location. Then, `userLocation.current` gets updated with the current location. This will be repeated so that we always have a pair of coordinates, the previous one and the current one.    

Then we can obtain changes in north-south and east-west direction, respectively:

```javascript
const diffLat = userLocation.current.lat - previousCoordinates.lat;
const diffLng = userLocation.current.lng - previousCoordinates.lng;
```

 Now, apply the formula to get an angle of user movement in degrees:

```javascript
const userDirection = Math.atan2(diffLat, diffLng) * 180 / Math.PI
```

## 4. Working with Google Maps JavaScript API

So far so good. But there's one last complication due to how Google Maps JavaScript API handles the direction in degrees.

While the previous section's trigonometry calculation gives us an angle measured anti-clockwise from the east (e.g., 90 degrees for the north, 180 degrees for the west, etc.). Google Maps JavaScript API needs an angle measured _clockwise_ from the *north* (e.g., 90 degrees for the east, 180 degrees for the south, etc.). 

How can I make a conversion? Before starting to learn UI/UX design and web development, I was trained as an economist whose analysis requires mathematics, and I got a PhD in the end. So it must be easy for me. :-) 

It turns out that I need the following formula for conversion:

```js
const clockwiseAngleFromNorth = 90 - anticlockwiseAngleFromEast;
```

For the east, 0 degree turns into 90 degrees; for the north, 90 degrees turn into 0 degree. Plotting these two points on a graph where the x-axis is `anticlockwiseAngleFromEast` and the y-axis is `clockwiseAngleFromNorth`, I've figured out that the relationship is given by y = 90 - x. 

For the west, 180 degrees turn into 270 degrees, the latter of which can also be expressed as _minus_ 90 degrees. For the south, 270 degrees turn into 180 degrees, or _minus_ 180 degrees. So the “y = 90 - x” relationship still holds!

So I've written a little function as follows:

```js
function getCurrentDirection(previousCoordinates, currentCoordinates) {
  const diffLat = currentCoordinates.lat - previousCoordinates.lat;
  const diffLng = currentCoordinates.lng - previousCoordinates.lng;
  const anticlockwiseAngleFromEast = convertToDegrees(
    Math.atan2(diffLat, diffLng)
  );
  const clockwiseAngleFromNorth = 90 - anticlockwiseAngleFromEast;
  return clockwiseAngleFromNorth;
  // helper function
  function convertToDegrees(radian) {
    return (radian * 180) / Math.PI; 
  }
}
```

I did my best to make the code document itself, an important practice to keep the code readable and maintainable.

With this `getCurrentDirection()` function, I can construct a marker for the user's location on Google Maps as follows: 

```javascript
const userLocation = useRef(null);
const marker = useRef(null);       // ADDED
...
// Keep track of user location
navigator.geolocation.watchPosition(position => {
  // Record the previous user location
  const previousCoordinates = userLocation.current;
  // Update the user location
  userLocation.current = {
    lat: position.coords.latitude, 
    lng: position.coords.longitude
  };
  // Calculate the direction
  const userDirection = getCurrentDirection(   // ADDED
    previousCoordinates,                       // ADDED
    userLocation.current                       // ADDED
  );                                           // ADDED
  // Construct marker
  marker.current = new google.maps.Marker({
    icon: {
      fillColor: color['google-blue 100'],
      fillOpacity: 1,
      path: google.maps.SymbolPath.CIRCLE,
      rotation: userDirection,               // ADDED
      scale: 8,
      strokeColor: color['white 100'],
      strokeWeight: 2,
    },
    position: userLocation.current,
    title: 'You are here!',
  });
  // Mark the current location
  marker.current.setMap(mapObject);
});
```

where the `rotation` property specifies the angle at which the marker icon is tilted clockwise. (See [Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/markers) for how to construct and show a marker on embedded Google Maps.)

For why I use the `useRef` hook to define the marker, see Section 3.2 of [**Day 12 of this blog post series**](https://dev.to/masakudamatsu/day-12-showing-user-location-on-embedded-google-maps-with-geolocation-api-and-react-128j).

## 5. Airplane icon as user location marker

### 5.1 Need for a directional shape

There's one missing piece in the above code. As the user's location maker, the Google blue dot is used, which is obviously incapable of showing the direction due to its rotationally symmetric shape. We need to replace it with an icon that has a directional shape.

As I repeatedly mention in this blog series, a street map is like the view of a city from the sky. Seeing your own location on the map is like flying up into the sky and looking down to see where you are. 

With this fantasy in mind, the appropriate icon to mark the user location and their direction of movement is... 

I cannot think of anything but an airplane.

With an airplane icon on the map, it'll be like those inflight monitor screens that show the current location of your flight over the world map. 

![Airplane icon heading north-east, shown over Washington D.C. on a map of the northwestern part of the Atlantic Ocean](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vmhd3h81xpx6j4arzwq5.jpeg) **A screenshot of the moving map for a trans-Atlantic flight from Washington D.C.** (image source: [Nota Bene](https://eugene.kaspersky.com/2013/12/10/seafaring-trolling/))

### 5.2 Using SVG path to mark location

So I download an SVG file of [the Flight icon from Material Icons](https://fonts.google.com/icons?selected=Material%20Icons%20Outlined%3Aflight%3A), open it with text editor, copy the value of the `d` attribute of the `<path>` element in it, and paste the code onto the `path` property for the marker on Google Maps:

```javascript
    marker.current = new google.maps.Marker({
      icon: {
        fillColor: color['google-blue 100'],
        fillOpacity: 1,
        path: 'M21 16v-2l-8-5V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z', // REVISED
        rotation: userDirection,
        scale: 2, // REVISED
        strokeColor: color['white 100'],
        strokeWeight: 2,
      },
      position: userLocation.current,
      title: 'You are here!',
    });
```

I also change the `scale` property to 2, to make it look not absurdly large.

The above code has one problem, however. When using an SVG path to specify the marker shape, Google Maps's marker icon is by default pinned to the map at its top-left corner of the icon image's bounding box. This means that the marker icon will appear moving around when the user changes the zoom level of the map.

To solve this issue, we need to set the `anchor` property to be the center of the marker icon image. The downloaded SVG file of the airplane icon sets the icon image dimension with its `viewBox` attribute. It's given as:

```html
viewBox="0 0 24 24"
```

This means the width and height of the icon image is 24px and 24px. (The first two values refer to the coordinates of the top-left corner of the image's bounding box.)

So the anchor point should be set as (12, 12). To specify this, Google Maps JavaScript API requires a new instance of the Point object:

```javascript
    marker.current = new google.maps.Marker({
      icon: {
        anchor: new google.maps.Point(12, 12), // ADDED
        fillColor: color['google-blue 100'],
        fillOpacity: 1,
        path: 'M21 16v-2l-8-5V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z', 
        rotation: userDirection,
        scale: 2, 
        strokeColor: color['white 100'],
        strokeWeight: 2,
      },
      position: userLocation.current,
      title: 'You are here!',
    });
```

This code is kind of ugly. So instead I define a variable called `flightIcon` to store the SVG data:

```js
const flightIcon = {
  height: 24,
  path: 'M21 16v-2l-8-5V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z',
  width: 24,
};
```

Then refactor the above code as follows:

```js
    marker.current = new google.maps.Marker({
      icon: {
        anchor: new google.maps.Point(
          flightIcon.width/2, flightIcon.height/2 // REVISED
        ), 
        fillColor: color['google-blue 100'],
        fillOpacity: 1,
        path: flightIcon.path, // REVISED
        rotation: userDirection,
        scale: 2, 
        strokeColor: color['white 100'],
        strokeWeight: 2,
      },
      position: userLocation.current,
      title: 'You are here!',
    });
```

## Demo

With all the pieces of code in this article put together, _My Ideal Map App_ (a web app I'm making) can now track the user's location as shown in this GIF image:

![The airplane icon moves over a street on Google Maps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gh1069czeotcmzexfti1.gif)

**A screen record of _My Ideal Map App_, showing the user's location moving along a street** (screen-recorded by the author)

You can also see [a demo via Cloudflare Pages](https://75073730.mima.pages.dev/). Press the airplane take-off icon at bottom-right. You'll be prompted to permit the website to use your location data. If you press "Allow", you'll see your location on the map within a few seconds. If not, submit a bug report by posting a comment to this article. Thank you! :-)

## Next step

When asked to permit the app to use your location data, you can press "Don't Allow". In this case, you'll see an error dialog, explaining what happens. Currently, this dialog is not formatted at all. I need to choose fonts and all the typography parameters such as line height.

## Changelog

**Nov 5, 2021 (v1.0.1)**: Fix a typo.

## References

MDN Contributors (2021) “[GeolocationCoordinates.heading](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates/heading)”, _MDN Web Docs_, Sep 15, 2021 (last updated).

Shiffman, Daniel (2012)  [_The Nature of Code_](https://natureofcode.com/book/), natureofcode.com.