handle missing lat/lon/time data

This commit is contained in:
Daniel Barlow 2024-10-31 16:18:40 +00:00
parent 10c7d68f31
commit 5b827ed6ed
2 changed files with 55 additions and 24 deletions

View File

@ -3,6 +3,7 @@
module Track module Track
( Track, ( Track,
Pos (..), Pos (..),
BadFile,
pos, pos,
elevation, elevation,
cadence, cadence,
@ -15,6 +16,7 @@ module Track
where where
import Control.Exception import Control.Exception
import Data.Functor ((<&>))
import Data.List as List import Data.List as List
import Data.List qualified import Data.List qualified
import Data.Map as Map import Data.Map as Map
@ -26,8 +28,7 @@ import Data.Time.ISO8601 qualified
import Debug.Trace (trace, traceShow) import Debug.Trace (trace, traceShow)
import Text.XML import Text.XML
import Text.XML.Cursor as Cursor import Text.XML.Cursor as Cursor
import Data.Functor((<&>)) import Text.Read (readMaybe)
data Pos = Pos Float Float deriving (Show, Eq) data Pos = Pos Float Float deriving (Show, Eq)
type Power = Maybe Int type Power = Maybe Int
@ -55,8 +56,7 @@ gpxNS localName =
tpxNS localName = tpxNS localName =
Name localName (Just "http://www.garmin.com/xmlschemas/TrackPointExtension/v2") Nothing Name localName (Just "http://www.garmin.com/xmlschemas/TrackPointExtension/v2") Nothing
data BadFile = BadFile String deriving (Show)
data BadFile = BadFile deriving (Show)
instance Exception BadFile instance Exception BadFile
@ -64,10 +64,12 @@ elToPoint :: Cursor -> Either SomeException Point
elToPoint c = elToPoint c =
case node c of case node c of
NodeElement (Element _ attrs _) -> NodeElement (Element _ attrs _) ->
let lat = getAttr "lat" let lat = (listToMaybe $ attribute "lat" c ) >>= asFloat
lon = getAttr "lon" lon = (listToMaybe $ attribute "lon" c ) >>= asFloat
ele = child c >>= element (gpxNS "ele") >>= child >>= content ele = listToMaybe $ child c >>= element (gpxNS "ele") >>= child >>= content
ts = child c >>= element (gpxNS "time") >>= child >>= content ts =
listToMaybe (child c >>= element (gpxNS "time") >>= child >>= content)
>>= (Data.Time.ISO8601.parseISO8601 . Data.Text.unpack)
gpxtpx = gpxtpx =
child c child c
>>= element (gpxNS "extensions") >>= element (gpxNS "extensions")
@ -80,25 +82,21 @@ elToPoint c =
cadence = extn (tpxNS "cad") cadence = extn (tpxNS "cad")
hr = extn (tpxNS "hr") hr = extn (tpxNS "hr")
power = extn "{http://www.garmin.com/xmlschemas/PowerExtension/v1}PowerInWatts" power = extn "{http://www.garmin.com/xmlschemas/PowerExtension/v1}PowerInWatts"
parsedTime = in if isJust lat && isJust lon && isJust ts
listToMaybe ts then
>>= (Data.Time.ISO8601.parseISO8601 . Data.Text.unpack)
in case parsedTime of
Nothing -> Left (toException BadFile)
Just utime ->
Right $ Right $
Point Point
(Pos lat lon) (Pos (fromJust lat) (fromJust lon))
(listToMaybe ele <&> asFloat) (ele >>= asFloat)
utime (fromJust ts)
(listToMaybe cadence <&> asInt) (listToMaybe cadence >>= asInt)
(listToMaybe power <&> asInt) (listToMaybe power >>= asInt)
(listToMaybe hr <&> asInt) (listToMaybe hr >>= asInt)
else Left (toException (BadFile "missing a required attribute"))
where where
asFloat v = (read (Data.Text.unpack v) :: Float) asFloat v = return (Data.Text.unpack v) >>= (readMaybe :: String -> Maybe Float)
asInt v = (read (Data.Text.unpack v) :: Int) asInt v = return (Data.Text.unpack v) >>= (readMaybe :: String -> Maybe Int)
getAttr name = maybe 0 asFloat (Map.lookup name attrs) _ -> Left (toException (BadFile "did not find trkpt"))
_ -> Left (toException BadFile)
getPoints :: Cursor -> Either SomeException [Point] getPoints :: Cursor -> Either SomeException [Point]
getPoints c = getPoints c =

View File

@ -5,6 +5,7 @@ module Main where
import Control.Exception import Control.Exception
import Data.Either import Data.Either
import Data.Time qualified import Data.Time qualified
import Data.List as List
import Debug.Trace (trace, traceShow) import Debug.Trace (trace, traceShow)
import System.Exit qualified as Exit import System.Exit qualified as Exit
import Test.HUnit import Test.HUnit
@ -30,6 +31,10 @@ xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/
wrap x = preamble ++ x ++ "</gpx>" wrap x = preamble ++ x ++ "</gpx>"
wrapPoint x =
let trk = "<trk><trkseg>" ++ x ++ "</trkseg></trk>"
in wrap trk
onepoint = onepoint =
wrap wrap
[r| [r|
@ -78,6 +83,33 @@ testMalformed =
let trk = Track.parse (wrap "<dgdsfg>>") let trk = Track.parse (wrap "<dgdsfg>>")
in assertBool "catches syntax error" (isLeft trk) in assertBool "catches syntax error" (isLeft trk)
testMissingAttrs =
let els = [
[r|
<trkpt lon="2" latsdf="51">
<time>2024-10-23T08:34:59.779+01:00</time>
</trkpt>
|],
[r|
<trkpt lon="dsfgsdfg" lat="51">
<time>2024-10-23T08:34:59.779+01:00</time>
</trkpt>
|],
[r|
<trkpt lon="2" lat="51">
<time>2024-10-23G87sdCfdfgsdfhg</time>
</trkpt>
|]
]
in
TestCase $
assertBool
"failed to catch missing/malformed attribute"
(List.all isLeft
(List.map (\ text -> Track.parse (wrapPoint text))
els))
test2 = test2 =
TestCase $ TestCase $
either either
@ -119,6 +151,7 @@ tests =
TestList TestList
[ test1, [ test1,
testMalformed, testMalformed,
testMissingAttrs,
test2, test2,
test3, test3,
test4 test4