packages feed

gps2htmlReport 0.1 → 0.2

raw patch · 9 files changed

+357/−81 lines, 9 filesdep +bytestringdep +cmdargsdep +download-curldep ~gpsbinary-added

Dependencies added: bytestring, cmdargs, download-curl, hsmagick

Dependency ranges changed: gps

Files

+ Data/GPS/Gps2HtmlReport/DrawOsm.hs view
@@ -0,0 +1,241 @@++module Data.GPS.Gps2HtmlReport.DrawOsm where++import Prelude+import Data.GPS+import Graphics.Transform.Magick.Types hiding (Image, minimum, maximum)+import Network.Curl.Download+import Data.Bits+import Graphics.GD+import Data.Maybe++import Data.GPS.Gps2HtmlReport.JourneyStats++baseurl = "http://tile.openstreetmap.org"+tilesprefix = "tile"+tilesourcename = "osmrender"+-- zoom = 16::Int++data TileCoords = TileCoords { minX :: Int+                     , maxX :: Int  +                     , minY :: Int  +                     , maxY :: Int +                     } +++tileNumbers :: Double -> Double -> Int -> [(Int,Int)]+tileNumbers latitude longitude zoom = +             let xtile = ((longitude+180) / 360) * fromInteger (shift (1::Integer) zoom)+                 tmp = log (tan (latitude*pi / 180) + secant (latitude * pi / 180))+                 ytile = ((1-tmp / pi) / 2.0) * fromInteger (shift (1::Integer) zoom)+                 bounds x = [ceiling x, floor x]+             in [(xt,yt) | xt <- bounds xtile, yt <- bounds ytile]++maxTile :: [(Int,Int)] -> (Int,Int)+maxTile [] = error "There is no max tile of an empty list"+maxTile (x:xs) = go x xs+  where+    go a [] = a+    go a (y:ys) = if fst y >= fst a && snd y >= snd a then go y ys else go a ys++secant :: Floating a => a -> a+secant a = 1 / cos a++initCoords :: TileCoords+initCoords = TileCoords {minX = 1000000, maxX = -1000, minY = 1000000, maxY = -1000}++-- | Determines the minimum and maximum of the X and Y tiles+-- to be downloaded from OSM+determineTiles :: [WptType] -> TileCoords -> Int -> TileCoords+determineTiles [] _ _ = initCoords+determineTiles [wpt] tCoords zoom =+       let curMinX = minX tCoords+           curMaxX = maxX tCoords+           curMinY = minY tCoords+           curMaxY = maxY tCoords+           tiles = tileNumbers (value (lat wpt)) (value (lon wpt)) zoom+           newMaxX = maximum $ curMaxX : map fst tiles+           newMinX = minimum $ curMinX : map fst tiles+           newMaxY = maximum $ curMaxY : map snd tiles+           newMinY = minimum $ curMinY : map snd tiles +       in tCoords {minX = newMinX, maxX = newMaxX, minY = newMinY, maxY = newMaxY}++determineTiles (wpt:wpts) tCoords zoom = +       let tileScope = determineTiles [wpt] tCoords zoom+       in determineTiles wpts tileScope zoom++deltaLat :: TileCoords -> Int+deltaLat tCoords = maxX tCoords - minX tCoords++deltaLong :: TileCoords -> Int+deltaLong tCoords = maxY tCoords - minY tCoords++maxNumAutoTiles = 32++zoom :: TileCoords -> Int+zoom = zoomCalc++zoomCalc :: TileCoords -> Int+zoomCalc tCoords = +   let numxtiles = maxX tCoords - minX tCoords + 1+       numytiles = maxY tCoords - minY tCoords + 1+       div = getZoomDiv numxtiles numytiles 0+   in 16 - div++getZoomDiv x y i+   | (x+1)*(y+1) > maxNumAutoTiles = getZoomDiv (shiftR x 1) (shiftR y 1) (i+1)+   | otherwise = i++-- | Takes the boundaries of the OSM tiles, and generates+-- [(Int,Int)] containing a list of all OSM tiles that+-- need downloading+selectedTiles :: TileCoords -> [(Int,Int)]+selectedTiles tCoords = +         let minx = minX tCoords+             maxx = maxX tCoords+             miny = minY tCoords+             maxy = maxY tCoords+         in [(i,j) | i <- [minx..maxx], j <- [miny..maxy]]+       +-- | Formats the filename string+filenameStr :: (Show a, Show a1) => a -> a1 -> String+filenameStr xTile yTile = tilesprefix ++ tilesourcename ++ "-z"++show zoom++"-x"++show xTile++"-y"++show yTile++".png"++-- | Formats the URL string+urlStr xTile yTile zoom = baseurl ++"/"++show zoom++"/"++show xTile++"/"++show yTile++".png"+++rectangle :: Int -> Int -> Graphics.Transform.Magick.Types.Rectangle+rectangle x' y' = Rectangle {width=256, height=256, x = x'*256, y = y'*256}++-- | Takes the URL of a given OSM tile and uses curl to download it+downloadFile :: String -> IO Image+downloadFile url = do+  response <- openURI url+  case response of+    Left err  -> error err+    Right img -> loadPngByteString img++-- | Takes the boundaries of the OSM tiles covering the+-- the 'Trail', uses 'placeTile' to download the tile+-- and to place each tile on the background layer+makeOSMLayer :: TileCoords -> Int -> IO Image+makeOSMLayer tCoords zoom = do+        backgroundImg <- newImage (((maxX tCoords - minX tCoords)+1)*256,((maxY tCoords - minY tCoords)+1)*256)+        mapM_ (\(a,b) -> placeTile a b backgroundImg tCoords zoom) (selectedTiles tCoords)+        return backgroundImg++-- | Used to create a mosaic of all downloaded OSM tiles to generate+-- the background layer for plotting the 'Trail' onto the 'Image'+placeTile :: Int -> Int -> Graphics.GD.Image -> TileCoords -> Int -> IO ()+placeTile x y backgroundImg tCoords zoom = do+          img <- downloadFile $ urlStr x y zoom+          copyRegion (0,0) (256,256) img (256*(x-minX tCoords),256*(y-minY tCoords)) backgroundImg++projectMercToLat :: Floating a => a -> a+projectMercToLat rely = (180 / pi) * atan (sinh rely)++-- | Used by @pixelPosForCoord@ for N,S,E,W coordinates for (x,y) values+project :: Int -> Int -> Int -> (Double,Double,Double,Double)+project x y zoom = +  let unit = 1.0 / (2.0 ** fromIntegral zoom)+      rely1 = fromIntegral y * unit+      rely2 = rely1 + unit+      limity = pi+      rangey = 2.0 * limity+      rely1' = limity - rangey * rely1+      rely2' = limity - rangey * rely2+      lat1 = projectMercToLat rely1'+      lat2 = projectMercToLat rely2'+      unit' = 360.0 / (2.0 ** fromIntegral zoom)+      long1 = (-180.0) + fromIntegral x * unit'+  in (lat2,long1,lat1,long1+unit') -- S,W,N,E+  +-- | Takes a WptType, and the OSM tile boundaries+-- and generates (x,y) points to be placed on the 'Image'+pixelPosForCoord :: (Lon a, Lat a, Integral t, Integral t1) => [a] -> TileCoords -> Int -> (t, t1)+pixelPosForCoord [] _ _ = (0,0)+pixelPosForCoord [wpt] tCoord zoom =+             let lat' = value $ lat wpt+                 lon' = value $ lon wpt+                 tile = maxTile $ tileNumbers lat' lon' zoom+                 xoffset = (fst tile - minX tCoord) * 256+                 yoffset = (snd tile - minY tCoord) * 256+                 (south,west,north,east) = (uncurry project tile zoom)+                 x = round $ (lon' - west) * 256.0 / (east - west) + fromIntegral xoffset+                 y = round $ (lat' - north) * 256.0 / (south - north) + fromIntegral yoffset+             in (x,y)++-- | Takes the 'WptType' and draws lines between every+-- point to point connection in the 'Trail'+drawLines :: [WptType] -> TileCoords -> Image -> Int -> IO Image+drawLines [] _ img _ = return img+drawLines [_] _ img _ = return img+drawLines (wpt:wpts) tCoord img zoom = do+       let start = pixelPosForCoord [wpt] tCoord zoom+           end = pixelPosForCoord [head wpts] tCoord zoom+           minEle = fromMaybe 0 $ fmap snd $ findPoint wpts wpt ele (<)+           maxEle = fromMaybe 0 $ fmap snd $ findPoint wpts wpt ele (>)+       drawLine' start end img (minEle,fromJust $ ele wpt,maxEle) 0+       drawLines wpts tCoord img zoom++-- | This is a fix on the fact that the 'drawLine' function+-- provided by the GD bindings do not provid a `width' parameter+drawLine' :: Point -> Point -> Image -> (Double,Double,Double) -> Int -> IO ()+drawLine' start end img (minEle,ele',maxEle) i+      | i < 6 = do+    drawLine (fst start+(i-1),snd start-(i-1)) (fst end+(i-1),snd end-(i-1)) color' img+    drawLine (fst start+(i+1),snd start-(i-1)) (fst end+(i+1),snd end-(i-1)) color' img+    drawLine (fst start+(i+1),snd start-(i+1)) (fst end+(i+1),snd end-(i+1)) color' img+    drawLine (fst start+(i-1),snd start-(i+1)) (fst end+(i-1),snd end-(i+1)) color' img+    drawLine' start end img (minEle,ele',maxEle) (i+1)+      | otherwise = return ()+     where range = maxEle - minEle+           x' = ele' - minEle+           x'' = x' / range+           color' = lineColor $ round $ 255.0*x''++-- | Uses a sliding scale for the red value in the RGB Color+-- to show a sliding color from green to yellow in accordance+-- with the relative elevation of a given WptType in the Trail+lineColor :: Int -> Graphics.GD.Color+lineColor redVal = rgb redVal 255 0++-- | Adds the copyright text in accordance with+-- http://wiki.openstreetmap.org/wiki/Legal_FAQ+addCopyright :: Graphics.GD.Image -> IO (Graphics.GD.Point, Graphics.GD.Point, Graphics.GD.Point, Graphics.GD.Point)+addCopyright img = do+         size <- imageSize img+         let copyrightS = "Tile images © OpenStreetMap (and) contributors, CC-BY-SA"+             pos = (10,snd size-10)+             black = rgb 0 0 0+         useFontConfig True+         drawString "monospace" 6.0 0.0 pos copyrightS black img++-- | If the generated OSM image has a greater width than 800 pixels, it is scaled to have a width of 800 pixels.+fitToWidth :: Image -> IO Image+fitToWidth img = do+   dimensions <- imageSize img+   let width = fst dimensions+   if width > 800 then resizeImg img dimensions else return img++-- | Uses the GraphicsMagick bindings the resize the image+resizeImg :: Image -> (Int,Int) -> IO Image  +resizeImg img dimensions = do+   let resizeRatio = fromIntegral (fst dimensions) / 800.0+       height = round (fromIntegral (snd dimensions) / resizeRatio)+   resizeImage 800 height img++-- | Takes the destination directory for the web content,+-- the (Trail WptType), and uses the DrawOsm functions+-- to generate an `osm.png' file showing the trail.+generateOsmMap :: String -> [WptType] -> IO ()+generateOsmMap webDir points = do+  let tiles = determineTiles points initCoords 16+      zoom = zoomCalc tiles+      tiles' = determineTiles points initCoords zoom+  backgroundImg <- makeOSMLayer tiles' zoom+  imgWithLines <- drawLines points tiles' backgroundImg zoom+  resizedImg <- fitToWidth imgWithLines+  addCopyright resizedImg+  savePngFile (webDir++"/osm.png") resizedImg
Data/GPS/Gps2HtmlReport/HTMLGenerator.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE ScopedTypeVariables  #-}  -- | The `HTMLGenerator' module generates the HTML content for the index.html generated module Data.GPS.Gps2HtmlReport.HTMLGenerator where
Data/GPS/Gps2HtmlReport/JourneyCharts.hs view
@@ -9,6 +9,7 @@ import Data.Accessor import Data.Colour import Data.Colour.Names+import Data.Time.LocalTime  import Data.GPS.Gps2HtmlReport.JourneyStats @@ -35,12 +36,13 @@                               ]            $ setLayout1Foreground fg            defaultLayout1+         lineStyle c = line_width ^= 1 * chooseLineWidth otype                 $ line_color ^= c                 $ defaultPlotLines ^. plot_lines_style -    theSpeeds = [(theTime,spd) | (theTime,spd) <- speedAtPoints points]+    theSpeeds = [(utcToLocalTime utc theTime,spd) | (theTime,spd) <- avgSpeeds 10 points]      speedLine = plot_lines_style ^= lineStyle (opaque blue)            $ plot_lines_values ^= [[ (theTime,speed) | (theTime,speed) <- theSpeeds]]@@ -90,7 +92,7 @@            $ plot_fillbetween_title ^= "Distance"            $ defaultPlotFillBetween -    spotMaxPoint = let point = findPoint points (head points) ele (>)+    spotMaxPoint = let point = findPoint points (head points) ele (>)                     in (if isJust point then Just (fst $ fromJust point, snd $ fromJust point, 5 :: Double) else Nothing)      spotMinPoint = let point = findPoint points (head points) ele (<)
Data/GPS/Gps2HtmlReport/JourneyStats.hs view
@@ -2,7 +2,7 @@ -- modules with statistics for the charts, and the journey statistics module Data.GPS.Gps2HtmlReport.JourneyStats where -import Data.GPS hiding (speed)+import Data.GPS -- hiding (speed) import Data.Maybe import Data.Time.LocalTime import Data.Time.Clock@@ -39,7 +39,7 @@ accumDistance [x] _ = [(lclTime $ fromJust (time x),0.0)] accumDistance (x:xs) acc =     let dist = distance x (head xs)           -   in (lclTime $ fromJust (time x), dist + acc ) : accumDistance (tail xs) (dist + acc)+   in (lclTime $ fromJust (time x), dist + acc ) : accumDistance xs (dist + acc)  -- | Takes all WayPoints, an element in wptType, and an Eq function, returning a single WayPoint findPoint :: [WptType] -> WptType -> (WptType -> Maybe Double) -> (Double -> Double -> Bool) -> Maybe (LocalTime,Double)@@ -57,8 +57,8 @@ journeyDistance (point:points) = distance point (head points) + journeyDistance points  -- | Calculates the average speed of the journey-meanJourneySpeed :: (Lat a, Lon a) => [a] -> Distance-meanJourneySpeed points = journeyDistance points / fromIntegral (length points)+meanJourneySpeed :: (Lat a, Lon a, Time a) => [a] -> Distance+meanJourneySpeed points = journeyDistance points / realToFrac (journeyTime points)  -- | Calculates the maximum speed maxSpeed :: [WptType] -> Speed@@ -95,13 +95,3 @@  dfltTZ :: TimeZone dfltTZ = TimeZone {timeZoneMinutes=0,timeZoneSummerOnly=False,timeZoneName="GMT"}---- | Overides the `speed' function in the `gps' package-speed :: (Lat loc, Lon loc, Time loc) => loc -> loc -> Maybe Speed-speed a b = -	case (getUTCTime b, getUTCTime a) of-		(Just x, Just y) -> Just $ distance a b / realToFrac (diffUTCTime x y) -		_ -> Nothing--getUTCTime :: (Lat a, Lon a, Time a) => a -> Maybe UTCTime-getUTCTime = fmap toUTCTime . time
Data/GPS/Gps2HtmlReport/Main.hs view
@@ -1,23 +1,71 @@++{-# LANGUAGE RecordWildCards,DeriveDataTypeable  #-}+ module Main where -import Data.GPS+import Data.GPS hiding (name, id) import System.FilePath import System.Directory-import Text.Html+import Text.Html hiding (name)+import System.Console.CmdArgs -import Data.GPS.Gps2HtmlReport.OsmChart import Data.GPS.Gps2HtmlReport.HTMLGenerator import Data.GPS.Gps2HtmlReport.JourneyCharts+import Data.GPS.Gps2HtmlReport.DrawOsm --- | Reads the current directory for all .gpx files, then maps to `generateReport' for each one+data MyOptions = MyOptions+    { imageOnly :: Bool+    } deriving (Data, Typeable, Show, Eq)++-- Customize your options, including help messages, shortened names, etc.+myProgOpts :: MyOptions+myProgOpts = MyOptions+    { imageOnly = def &= help "Generates only an image of the track overlay on an OpenStreetMap layer"+    }++getOpts :: IO MyOptions+getOpts = cmdArgs $ myProgOpts+    &= verbosityArgs [explicit, name "Verbose", name "V"] []+    &= versionArg [explicit, name "version", name "v", summary _PROGRAM_INFO]+    &= summary (_PROGRAM_INFO ++ ", " ++ _COPYRIGHT)+    &= help _PROGRAM_ABOUT+    &= helpArg [explicit, name "help", name "h"]+    &= program _PROGRAM_NAME++_PROGRAM_NAME = "gps2HtmlReport"+_PROGRAM_VERSION = "0.2"+_PROGRAM_INFO = _PROGRAM_NAME ++ " version " ++ _PROGRAM_VERSION+_PROGRAM_ABOUT = "A Haskell utility to generate HTML page reports of GPS Tracks and Track overlays on OpenStreetMap tiles"+_COPYRIGHT = "(C) Rob Stewart 2011"+ main :: IO [()] main = do+    opts <- getOpts+    optionHandler opts++-- Before directly calling your main program, you should warn your user about incorrect arguments, if any.+optionHandler :: MyOptions -> IO [()]+optionHandler opts@MyOptions{..}  =+    exec opts++exec :: MyOptions -> IO [()]+exec MyOptions{..} =+    if imageOnly+            then processGps False+            else processGps True+    ++-- | Reads the current directory for all .gpx files, then maps to `generateReport' for each one+processGps :: Bool -> IO [()]+processGps fullReport = do    curDir <- getCurrentDirectory    allFiles <- getDirectoryContents curDir    let allFilesSplit = map splitExtension allFiles    let gpxFiles = filter (\(_,b) -> b==".gpx") allFilesSplit    putStr ("Processing "++show (length gpxFiles)++" file(s)...\n")-   mapM (\(a,b) -> generateReport (curDir++"/"++a) (a++b)) gpxFiles+   if fullReport+     then mapM (\(a,b) -> generateReport (curDir++"/"++a) (a++b)) gpxFiles+     else mapM (\(a,b) -> generateImgOnly (curDir++"/"++a) (a++b)) gpxFiles  -- | Creates empty directory for each web report createEmptyDir :: FilePath -> IO ()@@ -31,11 +79,26 @@          points <- readGPX gpxFile          case length points of           0 -> putStr "Unable to parse GPX file. Skipping..."-          otherwise -> do+          _ -> do            createEmptyDir webDir+           putStr "Generating statistical charts...\n"            renderToPng (chart1 points) (webDir++"/chart1.png")            renderToPng (chart2 points) (webDir++"/chart2.png")            writeFile (webDir++"/index.html") $ renderHtml $ generateHtmlPage points-           createOsmMap webDir gpxFile+           putStr "Downloading OpenStreetMap tiles...\n"+           generateOsmMap webDir points            putStr $ "Processing '"++gpxFile++"' complete. Report saved in: "++webDir++"/index.html\n"+           return ()++-- | Generates the HTML report for each .gpx file+generateImgOnly :: FilePath -> FilePath -> IO ()+generateImgOnly webDir gpxFile  = do+         points <- readGPX gpxFile+         case length points of+          0 -> putStr "Unable to parse GPX file. Skipping..."+          _ -> do+           createEmptyDir webDir+           putStr "Downloading OpenStreetMap tiles...\n"+           generateOsmMap webDir points+           putStr $ "Processing '"++gpxFile++"' complete. Image saved in: "++webDir++"/osm.png\n"            return ()
− Data/GPS/Gps2HtmlReport/OsmChart.hs
@@ -1,40 +0,0 @@--- {-# LANGUAGE ScopedTypeVariables #-}---- | This module utilizes the perl gpx2png program--- to download the OSM tiles, and the GraphicsMagick bindings--- to resize the OSM image if necessary-module Data.GPS.Gps2HtmlReport.OsmChart where--import System.FilePath-import System.Directory-import System.Process-import Graphics.GD-import Control.Monad---- | If the generated OSM image has a greater width than 800 pixels, it is scaled to have a width of 800 pixels.-fitToWidth :: FilePath -> IO ()-fitToWidth imgPath = do-   img <- loadPngFile imgPath-   dimensions <- imageSize img-   let width = fst dimensions-   when (width > 800) $ resizeImg imgPath img dimensions---- | Uses the GraphicsMagick bindings the resize the image-resizeImg :: FilePath -> Image -> (Int,Int) -> IO ()  -resizeImg imgPath img dimensions = do-   let resizeRatio = fromIntegral (fst dimensions) / 800.0-       height = round (fromIntegral (snd dimensions) / resizeRatio)-   resizedImg <- resizeImage 800 height img-   savePngFile imgPath resizedImg-       --- | Calls the perl `gpx2png' utility to download the relevant OSM tiles-createOsmMap :: String -> String -> IO [()]-createOsmMap webDir gpxFile = do-   (_,_,_,pid) <- createProcess (shell ("perl gpx2png/gpx2png.pl -q -o "++webDir++"/osm.png "++ gpxFile))-   waitForProcess pid-   fitToWidth $ webDir++"/osm.png"-   curDir <- getCurrentDirectory-   allFiles <- getDirectoryContents curDir-   let allFilesSplit = map splitExtension allFiles-   let tmpPngFiles = filter (\(_,b) -> b==".png") allFilesSplit-   mapM (\(a,b) -> removeFile (a++b) ) tmpPngFiles
+ LiberationMono-Bold.ttf view

binary file changed (absent → 105428 bytes)

README.md view
@@ -24,12 +24,12 @@ cabal install ``` -This Haskell program also makes use of the bindings to **GraphcsMagick** and **Cairo**, and so the necessary system packages need to be installed, via a *nix package manager.+This Haskell program also makes use of the bindings to **GraphcsMagick** and **Cairo**, and so the necessary system packages need to be installed, via a Linux package manager.  On an RPM-based package manager, run this command as root:  ```-yum install GraphicsMagick cairo+yum install GraphicsMagick cairo alex happy gtk2hs-buildtools ```  Prerequisites@@ -43,14 +43,24 @@ ``` $ cd $location_of_gpx_files $ ls-1.gpx 2.gpx 3.gpx+1.gpx $ gps2HtmlReport-Processing 3 file(s)...-Processing '1.gpx' complete. Report saved in: /home/foo/gps_tracks/1/index.html-Processing '2.gpx' complete. Report saved in: /home/foo/gps_tracks/2/index.html-Processing '3.gpx' complete. Report saved in: /home/foo/gps_tracks/3/index.html+Processing 1 file(s)...+Generating statistical charts...+Downloading OpenStreetMap tiles...+Processing '1.gpx' complete. Report saved in: /home/foo/bar/1/index.html ``` +Alternatively, you can you this program to simply generate an image of your track on top of an OpenStreetMap layer.++```+$ gps2htmlReport --imageonly+Processing 1 file(s)...+Downloading OpenStreetMap tiles...+Processing '1.gpx' complete. Image saved in: /home/foo/bar/1/osm.png+```++ Notes ----- This project requires testing!@@ -61,14 +71,26 @@  Either way, get in touch! -To Do++Problems ----- -* It would be great to port the perl `gpx2png' utility to Haskell, eliminating this dependency.-* This Haskell program currently makes use of elevation, latitude and longitude. There are many other attributes possibly available in WptType. Ideas for what to do with these attributes [here](http://hackage.haskell.org/packages/archive/GPX/0.4.8/doc/html/Data-Geo-GPX-WptType.html#t:WptType) most welcome.+If you receive this error when trying to run the program:+```+can't load .so/.DLL for: stdc++ (libstdc++.so: cannot open shared object file: No such file or directory)+``` +... then you are experiencing this bug: [#5289](http://hackage.haskell.org/trac/ghc/ticket/5289).++To fix this++* Fedora 32bit:  $# ln -vs $(gcc --print-file-name=libstdc++.so) /usr/lib/+* Fedora 64bit:  $# ln -vs $(gcc --print-file-name=libstdc++.so) /usr/lib64/+* Ubuntu 32bit:  $# ln -vs $(gcc --print-file-name=libstdc++.so) /usr/local/lib/+* Ubuntu 64bit:  $# ln -vs $(gcc --print-file-name=libstdc++.so) /usr/local/lib64/++ Credits --------Thanks goes to the developers of the [gpx2png project](http://wiki.openstreetmap.org/wiki/Gpx2png), which is used to generate the OpenStreetMap image. -In addition, thanks to [Thomas DuBuisson](http://www.haskellers.com/user/TomMD), for implementing the `gps' package and contributing it to Hackage.+Thanks to [Thomas DuBuisson](http://www.haskellers.com/user/TomMD), for implementing the `gps' package and contributing it to Hackage.
gps2htmlReport.cabal view
@@ -1,5 +1,5 @@ Name:                gps2htmlReport-Version:             0.1+Version:             0.2 Cabal-Version:       >=1.2 Description:         Generate a HTML summary report of GPS tracks synopsis:            GPS to HTML Summary Report@@ -12,13 +12,12 @@ Build-Type:          Simple stability:	     alpha category:	     Data-extra-source-files:  README.md+extra-source-files:  README.md LiberationMono-Bold.ttf tested-with:         GHC ==7.0.2  Executable gps2htmlReport   Main-is:	     Data/GPS/Gps2HtmlReport/Main.hs  library-  Build-Depends:     base >= 4 && < 5, html, gps, time, cairo, Chart, random, data-accessor, colour, xsd, filepath, directory, process, gd-  Extensions:        ScopedTypeVariables-  Exposed-Modules:   Data.GPS.Gps2HtmlReport.HTMLGenerator, Data.GPS.Gps2HtmlReport.JourneyStats, Data.GPS.Gps2HtmlReport.JourneyCharts, Data.GPS.Gps2HtmlReport.OsmChart+  Build-Depends:     base >= 4 && < 5, html, gps >= 0.8.1, time, cairo, Chart, random, data-accessor, colour, xsd, filepath, directory, process, gd, bytestring, download-curl, hsmagick, cmdargs+  Exposed-Modules:   Data.GPS.Gps2HtmlReport.HTMLGenerator, Data.GPS.Gps2HtmlReport.JourneyStats, Data.GPS.Gps2HtmlReport.JourneyCharts, Data.GPS.Gps2HtmlReport.DrawOsm