vgwortpixel
How i constantly track my location and display a web-map with all the locations

How i constantly track my location and display a web-map with all the locations

Inspired by Aaron Parecki who has been tracking his location since 2008 with an iPhone app (Overland) and a server side tracking API (Compass), i decided to go for a similar approach.

I wanted to track my position constantly with my Android smartphone, save the data to a Postgres database and use the data to display an map with all locations i have ever been to.

To be able to track my position i needed an stable android app and after some tinkering around with different tracking apps i found OwnTracks which basically has all i need (and a good documentation of it).

OwnTracks

The OwnTracks app runs in the background on your Android or iOS device and waits for the smart phone to tell it that the device has moved, whereupon OwnTracks sends out a message with its current coordinates (and a few other things we'll discuss in a moment).

With different settings for the app i could tailor the app exactly to my needs because i wanted the app to check my position every second and when i have moved more than two meters from my last known position it should send a POST-Request to my Strapi instance which will store the location data in a Postgres database.

In the following screenshot you can see the settings i am using for OwnTracks.

Sorry, somehow the image is not available :(

The most important seetings for me are the mode Significant location change mode, locatorDisplacement, locatorInterval, ignoreInaccurateLocations and the url for the POST-request..

The significant location change mode is a mode which will let you decide in which interval you want to track your position. I am using the locationInterval '1' which will track my position every second with an LocatorDisplacement of 2 which will check if i moved away two meters away from my last known position. If i did it will send an POST-request with the location data to the given url if the position data isn't more inaccurate than 100 meters.

Because the app has to run always in the background you probably have to switch off energy saving modes for your smartphone which might disable the background functionality. Be warned: You probably will have to charge your smartphone more often. Also sometimes the app has some tracking error if you are using parallel some other tracking app for example for activities like strava.

The url for the POST-request is an strapi API url which looks like https://strapi.url/locationEndpoint. Strapi will then automatically handle the request and store the data in whatever database and table you have configured for Strapi.

I am using the following attributes where the most important are lat (latitude), lon (longitude) and alt (altitude).

Sorry, somehow the image is not available :(

I am also saving the velocity, battery level of the smartphone and some more data which will be displayed at my /now page. The velocity and altitude are quite inaccurate though which probably also depends on the smartphone.

Web-Map

Edit 25.01.2022: Recently i switched to a server side approach to visualise the data because rendering about one million points on client side takes way too long. So i built a tile server which is serving PNG-tiles with the location data. Right now the /map is created with OpenLayers.

If you are curious how you can set up a tile server which is serving data from Postgres with Mapnik, Apache and Nginx you can skip the following content and head directly to the article Manually building a tile server (20.04 LTS) and follow all steps except Stylesheet configuration because in the Mapnik stylesheet you have to create your custom style and a connection with your PostgreSQL table where your data is stored. See How to create a Mapnik Stylesheet for displaying any data from PostgreSQL/PostGIS.

If you only want to render some 'small' amount of data you can continue with the following.

At the beginning i rendered the data on client side with deck.gl. The following steps describe how to display data from PostgreSQL with deck.gl and Next.js (which is outdated for the /map page).

Due to the fact that amount of data is growing and growing i use Prisma to get the data directly from postgres. If i would use the methods provided by Strapi it takes quite some time to generate the page with the map.

At the moment the /map page is built very simple:

import Layout from '@/components/layout/layout'
import SEO from '@/components/seo/seo'
import Title from '@/components/title/page-title'
import styled from 'styled-components';
import media from 'styled-media-query';
import { getLocationsCount } from "@/lib/data/external/cms"
import prisma from '@/lib/utils/prisma'
import dynamic from 'next/dynamic'

const Livemap = dynamic(
  () => import('@/components/maps/deckgl/livemap'),
)

const MapContainer = styled.div`
  margin: auto;
  max-width: 1200px;
  padding: var(--space);
  ${media.lessThan('medium')`
    padding: var(--space-sm);
  `}
`

const Description = styled.p`
  max-width: 1200px;
  margin: auto;
  padding: 0 var(--space) var(--space) var(--space);
  ${media.lessThan('medium')`
    padding-left: var(--space-sm);
    padding-right: var(--space-sm);
  `}
`

const InternalLink = styled.a`
  color: var(--text-color);
  border-bottom: 1px solid var(--thirdy-color);
  cursor: pointer;
  :hover {
    border-bottom: 1px solid transparent;
  }
`

export default function Map({ locations, locationsCount } ) {

  
  return (
    <Layout>
  
      <SEO   
        title="Map"
        slug="map"
      />
      <Title>Map</Title>
      
      <MapContainer>
        <Livemap data={locations} />
      </MapContainer>
      <Description>
        Since 2021-03-02 i am tracking my current location. Right now there are {locationsCount} locations displayed on the map. 
        The map is always centered at my last known position.
      </Description>
      <Description>
        If you are curious how that works have a look at the article <InternalLink href="/articles/how-i-track-my-location-and-display-the-data-on-my-website" title="How i track my location and display the data on my website">How i track my location and display the data on my website</InternalLink> where i am describing the details behind it.
      </Description>    
    </Layout>
  )
}

export async function getStaticProps() {
  const locationsCount = (await getLocationsCount()) || []
  
  const locations = await prisma.locations.findMany({
    select: {
      lat: true,
      lon: true,
      alt: true,
      vel: false,
    },
  });

  return {
    revalidate:  86400,
    props: {
      locations,
      locationsCount
    }
  }
}
  

Everyday it fetches all the location data and passes it to the map-component.

Performance wise i made some bad experience with the default mapping libraries Leaflet and OpenLayers because of the client side rendered data but after some research i decided to go with deck.gl which is made for visualizing large scale datasets.

import React, { useState } from 'react';
import {StaticMap, LinearInterpolator, WebMercatorViewport} from 'react-map-gl';
import DeckGL from '@deck.gl/react';
import {ScatterplotLayer} from '@deck.gl/layers';
import styled from 'styled-components'

const MapContainer = styled.div`
    position: relative;
    height: 600px;
`


export default function Livemap({
  data,
}) {
    const [viewport, setViewport] = useState({
        longitude: data.slice(-1)[0].lon,
        latitude: data.slice(-1)[0].lat,
        zoom: 12,
        bearing: 0,
        pitch: 0,
    });


    const layers = [
        new ScatterplotLayer({
          id: 'scatter-plot',
          data,
          opacity: 0.1,
          stroked: false,
          filled: true,
          radiusScale: 3,
          radiusMinPixels: 0.65,
          getPosition: d => [d.lon, d.lat, 0 /*d.alt*/],
          getFillColor: d => [Math.sqrt(d.vel*20), 184, 50],
        })
    ];

    
    
    return (
        <MapContainer>
            <DeckGL layers={layers} initialViewState={viewport} controller={true}>
                <StaticMap 
                    reuseMaps 
                    mapStyle="https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" 
                    preventStyleDiffing={true} 
    
                />
            </DeckGL>
        </MapContainer>
  )
}

I am using an scatterplot layer which will render every location as point and adjusts the color based on the velocity. The viewport is always centered at the last known location and as basemap i am using dark-matter for the both dark and light mode.

You can find more information about the deck.gl map at the deck.gl scatterplot example.

Table of contents

About The Author

Max Dietrich
Max Dietrich

Geospatial Developer

Hi, I'm Max (he/him). I am a geospatial developer, author and cyclist from Rosenheim, Germany.

0 Virtual Thanks Sent.

0 Webmentions

Have you published a response to this? Send me a webmention by letting me know the URL.

Found no Webmentions yet. Be the first!

Newsletter

Continue Reading

  1. Fetching and storing activities from Garmin Connect with Strapi and visualizing them with NextJS

    Step-by-step guide explaining how to fetch data from Garmin Connect, store it in Strapi and visualize it with NextJS and React-Leaflet.

    Continue reading...

  2. How to create a Mapnik stylesheet for displaying any data from PostgreSQL/PostGIS

    In this article i want to show you how you can build your own Mapnik stylesheet for displaying any data from PostgreSQL/PostGIS. The Mapnik Stylesheet XML can be used for a tile-server with your custom style.

    Continue reading...

  3. Hosting NextJS on a private server using PM2 and Github webhooks as CI/CD

    This article shows you how can host your Next.js site on a (virtual private) server with Nginx, a CI/CD pipeline via PM2 and Github Webhooks.

    Continue reading...