Skip to content
/ hotpot Public

A spicy little heatmap renderer and tile server.

License

Notifications You must be signed in to change notification settings

erik/hotpot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hotpot

Render customizable activity heatmap images from GPS tracks extracted from GPX, TCX, and FIT files. There's also a built-in web server to serve up XYZ tiles, and endpoints to add new data via HTTP POST or Strava webhooks.

Designed to run locally or be self-hosted. It's lightweight and snappy enough to fit onto the free tier of pretty much anything that can run a Docker container. Even with 100,000 km of activity data, Fly.io's smallest instance can render tiles in ~1 ms.

Quick Start

To get started, use the import command to quickly process an entire directory of activities in parallel.

hotpot import [path/to/files/]

If importing activities from a Strava data export, use --join [path/to/activities.csv] to include metadata about your activities usually not stored in the GPX (title, which bike you used, the weather, ...)

hotpot import \
    strava_export/activities/ \
    --join strava_export/activities.csv

Another option is to drag and drop files into the browser UI, which can be enabled by running the server with --upload.

# If your server is accessible to the internet, make sure to set this
# environment variable. Without it, anonymous uploads are enabled!
export HOTPOT_UPLOAD_TOKEN=xyz...

hotpot serve --upload

# Open the browser and open the file upload dialog by clicking the "Add activity
# files" button
open http://localhost:8080

This method will be slower than on the command line as it can't parallelize as well. Activity metadata is also not supported.

After the initial import, you'll have a sqlite3 database, and can start creating heatmaps.

The render command can create images from a bounding box of coordinates (which can be generated by this tool).

hotpot render \
    --bounds='-120.7196,32.2459,-116.9234,35.1454' \
    --width 2000 \
    --height 2000 \
    --output heatmap.png

Alternatively, we can run a tile server with:

hotpot serve

Open http://127.0.0.1:8080/ in your browser to see a map view with the tile layer loaded.

See hotpot --help for more.

Customization

Gradients

There are several built in palettes available for converting the raw frequency data into colored pixels, which can be set via the ?color={...} query parameter. A list of these is available in the map view.

In addition to the presets, custom gradients can also be used via the ?gradient={...} parameter. With this, we specify a sequence of threshold values (how many times a particular pixel was visited) along with an associated color. Values falling between the thresholds will be smoothly interpolated to a reasonable color.

For example, if we want to display pure red when we've visited a pixel once, and white when we've visited 255 times (or more), we'd use 1:FF0000;255:FFFFFF.

Color codes are interpreted as hex RGBA values in RGB, RRGGBB or RRGGBBAA formats. If alpha values are not given, they are assumed to be FF (fully opaque).

Example Gradients
Gradient Rendered
1:000;10:fff
1:f00;5:ff0;10:ffff22;20:ffffff
1:322bb3;10:9894e5;20:fff

Filters

We can also choose which activities we're interested in visualizing dynamically through the ?filter={...} parameter.

Any properties available when the activity was added (either via webhook or bulk import) can be used in the filter expression, but the exact names will vary based on your data.

For example, we may want to generate different tiles for cycling vs hiking, exclude commutes, which gear we used, a minimum elevation gain, etc.

{
  // Basic numeric comparisons: <, <=, >, >=
  elevation_gain: { ">": 1000 },

  // Match/exclude multiple values
  bike: { any_of: ["gravel", "mtb"] },
  activity_type: { none_of: ["Run"] },

  // Substring match (e.g. match "morning commute" + "commute #9")
  title: { matches: "commute" },

  // Property key exists
  max_hr: { exists: true },

  // Multiple expressions can be applied (evaluated as an AND)
  distance: { ">": 100, "<": 200 },
}

Activity Uploads

Hotpot supports two mechanisms for adding new data to the sqlite3 database directly over HTTP:

  1. POST /upload: Manually upload a single GPX, TCX, or FIT file
  2. Strava webhook: Subscribe to new activity uploads automatically

POST /upload

To enable HTTP uploads, run the server with --upload. Any files that can be imported on the command line can be POSTed to the server via the /upload endpoint using multipart/form-data encoding.

curl -X POST \
  http://hotpot.example.com/upload \
  --header 'Authorization: Bearer MY_TOKEN_HERE' \
  --form file=@activity.gpx

Note that the Authorization header is only required when the environment variable HOTPOT_UPLOAD_TOKEN is set at server startup. When left unset, unauthenticated uploads are enabled.

Strava Webhook

If you're already uploading activity data to Strava, you can use their activity webhook to import new activities automatically.

To get started, follow the Strava API documentation to create your own application.

NOTE

Strava limits new APIs to only allow the owner of the API to authenticate. You won't be able to share this with multiple people.

Next, we can use oauth to authenticate our account and save the API tokens in the database.

export STRAVA_CLIENT_ID=... \
       STRAVA_CLIENT_SECRET=...\
       STRAVA_WEBHOOK_SECRET=...

hotpot strava-auth

# Grant permission to your app via OAuth
open http://127.0.0.1:8080/strava/auth

Once you've authenticated successfully, you'll need to register the callback URL of your server with Strava's API. Follow the curl commands shown on the success page to complete setup.

Deployment

To simplify things, a basic Dockerfile is included. Mount a volume at /data/ to persist the sqlite database between runs.

Since we're using sqlite as our data store, it's easy to first run the bulk import locally, then copy the database over to a remote host.

Fly Quick Start

Hotpot should comfortably fit within Fly.io's free tier, and handles the scale-to-zero behavior gracefully. Follow their setup instructions first.

Steps below assume you've cloned this repo locally and already created a local database.

# Create the application
fly launch --ha false

# Create a persistent volume for the DB
fly volumes create hotpot_db -a YOUR_APP_NAME --size 1

# Attach the volume
echo '
[mounts]
  source="hotpot_db"
  destination="/data"
' >> fly.toml

# If you're using the Strava webhook
fly secrets set \
    STRAVA_CLIENT_ID=... \
    STRAVA_CLIENT_SECRET=...\
    STRAVA_WEBHOOK_SECRET=...

# Deploy the app
fly deploy

# Copy local DB over to the app
fly proxy 10022:22 &
scp -P 10022 ./hotpot.sqlite3* root@localhost:/data/

# Restart the app, and we're done.
fly app restart

License

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

About

A spicy little heatmap renderer and tile server.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published