Converting Garmin FIT activities into GeoJSON

My demo accomplished what I wanted to do with Leaflet regarding Bing, OSM, and Google base maps. The demo is at The only base map plumbing that remains is to figure out how to add Google’s bicycle path layer.

The next step is to display the activity polyline as a Leaflet layer instead of adding it directly to Google, Bing, or OSM maps via the vendors’ map APIs. With Leaflet, the base map is simply the background for the vector graphics displayed on a Leaflet map layer in the foreground.

Using GeoJSON with Leaflet

First, I needed to produce GeoJSON as described here: GeoJSON is a JSON standard for geometry types (see The geometry types are:

  • Point
  • LineString
  • Polygon
  • MultiPoint
  • MultiLineString
  • MultiPolygon

Geometry types may be combined with additional properties using Feature or FeatureCollection. Leaflet has a map layer type that can consume Feature JSON or FeatureCollection JSON.

Garmin Activity FIT File in –> GeoJSON out

Modern Garmin fitness devices store data in a compressed binary format called the FIT protocol (see

My first task was to create a utility to read Garmin activity FIT files and produce Feature GeoJSON. The code is on GitHub:

I used my 2012 Little Rock Marathon activity to test the GeoJSON utility. The FIT SDK decodes the activity FIT file. The GeoTools FeatureJSON class outputs the GeoJSON.

There are two steps to creating GeoJSON using the GeoTools API:

  1. Define a feature type (schema) for the feature properties.
  2. Build the feature following the feature type definition.

Defining a Simple Feature Type

The SimpleFeatureTypeBuilder class is used to layout the schema for the Feature.

public SimpleFeatureType getFeatureSchema() {
    final SimpleFeatureTypeBuilder simpleFeatureType = new SimpleFeatureTypeBuilder();
    simpleFeatureType.add("geom", LineString.class, DefaultGeographicCRS.WGS84);
    simpleFeatureType.add("name", String.class);
    simpleFeatureType.add("activityId", Long.class);
    simpleFeatureType.add("activityName", String.class);
    simpleFeatureType.add("sport", String.class);
    simpleFeatureType.add("startTime", String.class);
    simpleFeatureType.add("totalMeters", Double.class);
    simpleFeatureType.add("totalSeconds", Double.class);
    simpleFeatureType.add("minLat", Double.class);
    simpleFeatureType.add("minLon", Double.class);
    simpleFeatureType.add("maxLat", Double.class);
    simpleFeatureType.add("maxLon", Double.class);
    return simpleFeatureType.buildFeatureType();

Building the Feature

The SimpleFeatureBuilder builds the SimpleFeature following the feature schema definition.

public SimpleFeature buildSimpleFeature(final FitActivity fitActivity) {
    final SimpleFeatureType featureSchema = getFeatureSchema();
    final SimpleFeatureBuilder builder = new SimpleFeatureBuilder(featureSchema);
    builder.set("activityId", fitActivity.getActivityId());
    builder.set("sport", fitActivity.getSport());
    builder.set("startTime", fitActivity.getStartTime());
    builder.set("totalMeters", fitActivity.getTotalMeters());
    builder.set("totalSeconds", fitActivity.getTotalSeconds());
    final Coordinate[] polyline = fitActivity.getPolyline().toArray(
        new Coordinate[fitActivity.getPolyline().size()]);
    final Geometry geometry = simplifyLineString(polyline);
    final Coordinate[] boundingBox = generateBoundingBox(geometry);
    builder.set("minLat", boundingBox[0].y);
    builder.set("minLon", boundingBox[0].x);
    builder.set("maxLat", boundingBox[1].y);
    builder.set("maxLon", boundingBox[1].x);
    return builder.buildFeature("0");

The final result, formatted for readability, can be found here:

This is a partial listing:

  "type": "Feature",
  "geometry": {
    "type": "LineString",
    "coordinates": [

... many points omitted.

  "properties": {
    "activityId": 155155867,
    "sport": "RUNNING",
    "startTime": "2012-03-04T14:02Z",
    "totalMeters": 42453.58984375,
    "totalSeconds": 15162.140625,
    "minLat": 34.73176879808307,
    "minLon": -92.34434505924582,
    "maxLat": 34.78602569550276,
    "maxLon": -92.25817699916661
  "id": "0"

Now I’m ready to create a Leaflet GeoJSON layer to display the polyline!


2 thoughts on “Converting Garmin FIT activities into GeoJSON

  1. Pingback: Rendering a GeoJSON layer in Leaflet | Exploring Spatial

  2. Pingback: How to Display Feature Collection GeoJson with Leaflet’s Marker Clusters | Exploring Spatial

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s