Track.hs (3012B)
1 {-# LANGUAGE OverloadedStrings #-} 2 3 module Track 4 ( Track, 5 module Point, 6 BadFile, 7 parse, 8 parseFile, 9 parseBS, 10 Track.length, 11 ) 12 where 13 14 import Control.Exception 15 import Data.Aeson 16 import Data.ByteString.Lazy.Char8 qualified as L 17 import Data.Either 18 import Data.Functor ((<&>)) 19 import Data.List as List 20 import Data.List qualified 21 import Data.Map as Map 22 import Data.Maybe 23 import Data.Text qualified 24 import Data.Text.Lazy as T 25 import Data.Time 26 import Data.Time.ISO8601 qualified 27 import Debug.Trace (trace, traceShow) 28 import Point 29 import Text.Read (readMaybe) 30 import Text.XML 31 import Text.XML.Cursor as Cursor 32 33 -- TODO do we even need this type? 34 type Track = [Point] 35 36 gpxNS localName = 37 Name localName (Just "http://www.topografix.com/GPX/1/1") Nothing 38 39 tpxNS localName = 40 Name localName (Just "http://www.garmin.com/xmlschemas/TrackPointExtension/v2") Nothing 41 42 data BadFile = BadFile String deriving (Show) 43 44 instance Exception BadFile 45 46 elToPoint :: Cursor -> Either SomeException Point 47 elToPoint c = 48 let lat = listToMaybe (attribute "lat" c) >>= asDouble 49 lon = listToMaybe (attribute "lon" c) >>= asDouble 50 ts = 51 listToMaybe (child c >>= element (gpxNS "time") >>= child >>= content) 52 >>= (Data.Time.ISO8601.parseISO8601 . Data.Text.unpack) 53 ele = listToMaybe (child c >>= element (gpxNS "ele") >>= child >>= content) >>= asDouble 54 gpxtpx = 55 child c 56 >>= element (gpxNS "extensions") 57 >>= child 58 >>= element (tpxNS "TrackPointExtension") 59 >>= child 60 extn n = 61 gpxtpx >>= element n >>= child >>= content 62 63 cadence = extn (tpxNS "cad") 64 hr = extn (tpxNS "hr") 65 power = extn "{http://www.garmin.com/xmlschemas/PowerExtension/v1}PowerInWatts" 66 in if isJust lat && isJust lon && isJust ts 67 then 68 Right $ 69 Point 70 (Pos (fromJust lat) (fromJust lon) ele) 71 (fromJust ts) 72 (listToMaybe cadence >>= asInt) 73 (listToMaybe power >>= asInt) 74 (listToMaybe hr >>= asInt) 75 else Left (toException (BadFile "missing a required attribute")) 76 where 77 asDouble v = (readMaybe :: String -> Maybe Double) (Data.Text.unpack v) 78 asInt v = (readMaybe :: String -> Maybe Int) (Data.Text.unpack v) 79 80 getPoints :: Cursor -> Either SomeException [Point] 81 getPoints c = 82 let trkpts = 83 element (gpxNS "gpx") c 84 >>= child 85 >>= element (gpxNS "trk") 86 >>= descendant 87 >>= element (gpxNS "trkpt") 88 in traverse elToPoint trkpts 89 90 parse :: String -> Either SomeException [Point] 91 parse str = do 92 gpx <- parseText def (T.pack str) 93 getPoints (fromDocument gpx) 94 95 length :: Track -> Int 96 length = Data.List.length 97 98 -- parseFile :: FilePath -> IO [Point] 99 parseFile name = do 100 gpx <- Text.XML.readFile def name 101 return $ case getPoints (fromDocument gpx) of 102 Left err -> [] 103 Right points -> points 104 105 parseBS bs = do 106 gpx <- parseLBS def bs 107 getPoints (fromDocument gpx)