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.

Screenshot_OwnTracks settings.jpg

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/location'. 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).

Screenshot Location Table.png

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. So far (27.09.2021) i tracked 678.806 locations which needs about 166MB of storage.

Web-Map

Due to the fact that the amount of data is growing and growing i decided to use Prisma to get the data directly from the database. 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 and 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.

  1. Copy

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!