souplesse/lib/Track.hs

123 lines
3.1 KiB
Haskell
Raw Normal View History

2024-10-28 23:35:36 +00:00
{-# LANGUAGE OverloadedStrings #-}
2024-10-30 21:03:11 +00:00
module Track
( Track,
Pos (..),
pos,
elevation,
cadence,
power,
time,
parse,
Track.length,
)
where
import Control.Exception
import Data.List as List
import Data.List qualified
import Data.Map as Map
import Data.Maybe
2024-10-30 21:03:11 +00:00
import Data.Text qualified
import Data.Text.Lazy as T
2024-10-27 23:13:39 +00:00
import Data.Time
2024-10-30 21:03:11 +00:00
import Data.Time.ISO8601 qualified
import Debug.Trace (trace, traceShow)
2024-10-28 23:35:36 +00:00
import Text.XML
import Text.XML.Cursor as Cursor
2024-10-31 00:38:35 +00:00
import Data.Functor((<&>))
2024-10-28 23:35:36 +00:00
data Pos = Pos Float Float deriving (Show, Eq)
2024-10-30 21:03:11 +00:00
2024-10-28 23:35:36 +00:00
type Power = Maybe Int
2024-10-30 21:03:11 +00:00
2024-10-28 23:35:36 +00:00
type Cadence = Maybe Int
2024-10-30 21:03:11 +00:00
2024-10-28 23:35:36 +00:00
type HeartRate = Maybe Int
2024-10-30 21:03:11 +00:00
data Point = Point
{ pos :: Pos,
elevation :: Maybe Float,
time :: UTCTime,
cadence :: Cadence,
power :: Power,
heartRate :: HeartRate
}
deriving (Show)
2024-10-27 23:13:39 +00:00
2024-10-29 19:35:17 +00:00
-- TODO do we even need this type?
2024-10-27 23:13:39 +00:00
type Track = [Point]
2024-10-30 17:17:48 +00:00
gpxNS localName =
Name localName (Just "http://www.topografix.com/GPX/1/1") Nothing
tpxNS localName =
Name localName (Just "http://www.garmin.com/xmlschemas/TrackPointExtension/v2") Nothing
2024-10-30 13:30:33 +00:00
data BadFile = BadFile deriving (Show)
instance Exception BadFile
elToPoint :: Cursor -> Either SomeException Point
2024-10-29 21:22:49 +00:00
elToPoint c =
case node c of
2024-10-28 23:35:36 +00:00
NodeElement (Element _ attrs _) ->
2024-10-30 21:03:11 +00:00
let lat = getAttr "lat"
lon = getAttr "lon"
ele = child c >>= element (gpxNS "ele") >>= child >>= content
ts = child c >>= element (gpxNS "time") >>= child >>= content
gpxtpx =
child c
>>= element (gpxNS "extensions")
>>= child
>>= element (tpxNS "TrackPointExtension")
>>= child
cadence =
gpxtpx
>>= element (tpxNS "cad")
>>= child
>>= content
power =
gpxtpx
>>= element (Name "PowerInWatts" (Just "http://www.garmin.com/xmlschemas/PowerExtension/v1") Nothing)
>>= child
>>= content
parsedTime =
listToMaybe ts
>>= (Data.Time.ISO8601.parseISO8601 . Data.Text.unpack)
in case parsedTime of
Nothing -> Left (toException BadFile)
Just utime ->
Right $
Point
(Pos lat lon)
2024-10-31 00:38:35 +00:00
(listToMaybe ele <&> asFloat)
utime
2024-10-31 00:38:35 +00:00
(listToMaybe cadence <&> asInt)
(listToMaybe power <&> asInt)
Nothing
2024-10-29 21:22:49 +00:00
where
asFloat v = (read (Data.Text.unpack v) :: Float)
2024-10-30 17:17:48 +00:00
asInt v = (read (Data.Text.unpack v) :: Int)
getAttr name = maybe 0 asFloat (Map.lookup name attrs)
_ -> Left (toException BadFile)
2024-10-28 23:35:36 +00:00
getPoints :: Cursor -> Either SomeException [Point]
2024-10-28 23:35:36 +00:00
getPoints c =
2024-10-30 21:03:11 +00:00
let trkpts =
element (gpxNS "gpx") c
>>= child
>>= element (gpxNS "trk")
>>= descendant
>>= element (gpxNS "trkpt")
in traverse elToPoint trkpts
2024-10-28 23:35:36 +00:00
parse :: String -> Either SomeException [Point]
parse str = do
gpx <- parseText def (T.pack str)
getPoints (fromDocument gpx)
2024-10-28 23:35:36 +00:00
2024-10-27 23:13:39 +00:00
length :: Track -> Int
2024-10-30 14:25:54 +00:00
length = Data.List.length