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 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.