GPS Traces Using World Wind

I have a lot of GPX files lying around from various biking trips. I don't have much use for them other than loading them in to Google Earth and brag about being there and done that, so I thought I would parse the file myself and paint the route on the World Wind.

gpx world wind

GPX files are XML files, containing the coordinates you have been to, if you draw a line through these points you can rebuild the route you have taken.

(ns gpx
  (:require [clojure.zip :as zip]
            [clojure.xml :as xml])
  (:use clojure.contrib.zip-filter.xml)
  (:import (java.util ArrayList)
           (java.awt Dimension)
           (gov.nasa.worldwind.layers RenderableLayer)
           (gov.nasa.worldwind.geom LatLon Position)
           (gov.nasa.worldwind.render Polyline SurfacePolyline)
           (gov.nasa.worldwind.avlist AVKey)
           (gov.nasa.worldwind.awt WorldWindowGLCanvas)
           (gov.nasa.worldwind Configuration WorldWind)))

(defn points [f]
  (let [data (zip/xml-zip (xml/parse f))
        trkpt (xml-> data :trk :trkseg :trkpt)]
    (map #(vector (attr % :lat) 
                  (attr % :lon) 
                  (first (xml-> % :ele text))) trkpt)))
gpx=> (take 2 (points "data.gpx"))
(["41.011934" "29.196977" "154.909546"] 
 ["41.010155" "29.195925" "150.583618"])

This will return a sequence of vectors, each containing three items, latitude, longitude and elevation of the point. To draw a path on to the map World Wind provides two classes SurfacePolyline and Polyline. SurfacePolyline will draw a flat line on to the surface between coordinates where as Polyline draws a GL line between positions, which includes elevation data.

(defn world []
  (Configuration/setValue AVKey/INITIAL_LATITUDE 39.3113)
  (Configuration/setValue AVKey/INITIAL_LONGITUDE 32.8038)
  (Configuration/setValue AVKey/INITIAL_ALTITUDE 1000000)
  (doto (WorldWindowGLCanvas.)
    (.setModel (WorldWind/createConfigurationComponent 
                AVKey/MODEL_CLASS_NAME))))

(defn surface-polyline [points]
  (let [list (ArrayList.)]
    (doseq [p points] 
      (.add list (LatLon/fromDegrees (Double. (p 0)) (Double. (p 1)))))
    (doto (RenderableLayer.)
      (.addRenderable (SurfacePolyline. list)))))

To create a SurfacePolyline, we need to create a list of LatLon objects and pass that to the SurfacePolyline constructor at this point you can also set a color for the line then we create a RenderableLayer and add our SurfacePolyline to it, this final layer is what will be added to the World Wind canvas.

(defn polyline [points]
  (let [list (ArrayList.)]
    (doseq [p points] 
      (.add  list (Position. 
                   (LatLon/fromDegrees (Double. (p 0)) (Double. (p 1)))
                   (Double. (p 2)))))
    (doto (RenderableLayer.) 
      (.addRenderable (Polyline. list)))))

(defn frame []
  (let [world (world)
        layers (.getLayers (.getModel world))]
    (.add layers (surface-polyline (points "data.gpx")))
    ;;(.add layers (polyline (points "data.gpx")))
    (doto (javax.swing.JFrame.)
      (.add world)
      (.setSize (Dimension. 300 300))
      (.setAlwaysOnTop true)
      (.setVisible true))))

Process of creating a Polyline is the same, the only difference is instead of building a list of LatLon objects, we build a list of Position objects which includes the elevation data, again we get a layer that is ready to be added to the world.