allan.adair.io

About

CV

Miscellaneous Code

Rendering Map Tiles with Mapnik

In a previous blog entry I shared a map that displayed the GPS data collected from a run around White Rock Lake in Dallas, and today I intend to share some details on how that was accomplished.

  1. Handling Garmin FIT format

    To my surprise, my new super cool Garmin Forerunner 920XT GPS/GLONASS watch does not record data in GPX format. It uses a binary format called FIT, which seems to have limited support with open source tools like gpsbabel. Not being able to use gpsbabel was a bit of a pain, but the garmin connect website allows a person to publish data in FIT format and then export it to GPX. From that point one can begin to work with the data. Although there are perhaps more elegant approaches, I decided to then convert the GPX data to shapefiles using GDAL's ogr2ogr tool. It's as simple as entering this in a terminal:

    ogr2ogr new_folder my_activity.gpx
    

    The above command creates the new_folder directory and outputs shapefiles that represent the various spatial data that are defined in GPX (see the output file structure below). Note that some shapefiles might not contain any data.

    |-- new_folder
        |-- route_points.dbf
        |-- route_points.prj
        |-- route_points.shp
        |-- route_points.shx
        |-- routes.dbf
        |-- routes.prj
        |-- routes.shp
        |-- routes.shx
        |-- track_points.dbf
        |-- track_points.prj
        |-- track_points.shp
        |-- track_points.shx
        |-- tracks.dbf
        |-- tracks.prj
        |-- tracks.shp
        |-- tracks.shx
        |-- waypoints.dbf
        |-- waypoints.prj
        |-- waypoints.shp
        `-- waypoints.shx
    

    In my case, I'm only interested in the polyline data contained in the tracks shapefile.

  2. Generating tiles the OSM way

    Being somebody who lives mostly in the esri world, I am somewhat lost when it comes to generating tiles using open source tools. I knew beforehand that I wanted to use Mapnik to render tiles, but I had no idea where to begin. I eventually stumbled across generate_tiles.py, which is a part of the OpenStreetMap project. Inside the tool's source code are some examples. I decided to just replace the examples with a similar function call that points to my own data:

    if __name__ == '__main__':
    
        bbox = (-96.74, 32.81, -96.70, 32.86)  # bounding box coords for White Rock lake
        mapfile = 'map.xml'
        tile_dir = 'tiles/'
        render_tiles(bbox, mapfile, tile_dir)
    

    In order for this to work, we need a Mapnik configuration XML. I called mine map.xml, and it looks like this:

    <?xml version="1.0" encoding="utf-8"?>
    <Map background-color="transparent"
     srs="+proj=merc +a=6378137 +b=6378137
          +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0
          +k=1.0 +units=m +nadgrids=@null +no_defs"
          minimum-version="2.0.0">
      <Style name="redline">
        <Rule>
          <LineSymbolizer stroke="rgb(255,0,0)" stroke-width="2.0" />
        </Rule>
      </Style>
      <Layer name="whiterock" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
        <StyleName>redline</StyleName>
          <Datasource>
            <Parameter name="type">shape</Parameter>
            <Parameter name="file">tracks.shp</Parameter>
          </Datasource>
      </Layer>
    </Map>
    

    There's quite a bit to ingest here, but the main points are that there's a Map element that contains several sub elements that define the map's data sources and styles. Because the GPS data needs to overlay a basemap with a spatial reference of Web Mercator, we need to define the map as having a Web Mercator spatial reference, and define the GPS data as being WGS 84 using proj4 definition strings.

    Running generate_tiles.py will create a tiles/ directory and fill it with images in a directory structure which is compatible with other tile-serving services. Placing that directory in a web-accessible location will make it usable in a leaflet map application, as in the previous blog entry.

  3. Using leaflet as a presentation layer

    Here is an example of how to layer custom tiles over an OSM basemap using leaflet:

    <!DOCTYPE html>
    <html>
    <head>
    <title>Example</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
    <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
    </head>
    <body>
    <div id="map" style="width: 600px; height: 400px"></div>
    <script type="text/javascript">
    var map = L.map('map').setView([32.835, -96.72], 13)
    
    L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 18
    }).addTo(map);
    
    L.tileLayer('tiles/{z}/{x}/{y}.png', {
        maxZoom: 18
    }).addTo(map);
    </script>
    </body>
    </html>
    

    See my maptiles-testing repository on GitHub for the complete source code.

Comments