Point.hs (4342B)
1 {-# LANGUAGE DataKinds #-} 2 {-# LANGUAGE DerivingStrategies #-} 3 {-# LANGUAGE EmptyDataDecls #-} 4 {-# LANGUAGE FlexibleContexts #-} 5 {-# LANGUAGE FlexibleInstances #-} 6 {-# LANGUAGE GADTs #-} 7 {-# LANGUAGE GeneralizedNewtypeDeriving #-} 8 {-# LANGUAGE MultiParamTypeClasses #-} 9 {-# LANGUAGE OverloadedStrings #-} 10 {-# LANGUAGE QuasiQuotes #-} 11 {-# LANGUAGE RecordWildCards #-} 12 {-# LANGUAGE StandaloneDeriving #-} 13 {-# LANGUAGE TemplateHaskell #-} 14 {-# LANGUAGE TypeFamilies #-} 15 {-# LANGUAGE UndecidableInstances #-} 16 17 module Point 18 ( Pos (..), 19 Point (..), 20 save, 21 fetch, 22 migration, 23 ) 24 where 25 26 import Control.Exception 27 import Control.Monad.IO.Class (MonadIO, liftIO) 28 import Control.Monad.Trans.Reader (ReaderT) 29 import Data.Aeson 30 import Data.ByteString.Lazy.Char8 qualified as L 31 import Data.Either 32 import Data.Functor ((<&>)) 33 import Data.List as List 34 import Data.List qualified 35 import Data.Map as Map 36 import Data.Maybe (isJust) 37 import Data.Text qualified 38 import Data.Text.Lazy as T 39 import Data.Time 40 import Data.Time.Clock (DiffTime, NominalDiffTime, UTCTime, addUTCTime) 41 import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds) 42 import Data.Time.ISO8601 qualified 43 import Database.Persist 44 import Database.Persist.Class 45 import Database.Persist.Postgresql 46 ( ConnectionString, 47 SqlBackend, 48 createPostgresqlPool, 49 pgConnStr, 50 pgPoolSize, 51 rawExecute, 52 runMigration, 53 runSqlPool, 54 ) 55 import Database.Persist.TH 56 import Debug.Trace (trace, traceShow) 57 import Session qualified 58 import Text.Read (readMaybe) 59 import Text.XML 60 import Text.XML.Cursor as Cursor 61 62 -- import Track (Point (..), Pos (..)) 63 -- import Track as T 64 65 data Pos = Pos Double Double (Maybe Double) deriving (Show, Eq) 66 67 type Power = Maybe Int 68 69 type Cadence = Maybe Int 70 71 type HeartRate = Maybe Int 72 73 data Point = Point 74 { pos :: Pos, 75 time :: UTCTime, 76 cadence :: Cadence, 77 power :: Power, 78 heartRate :: HeartRate 79 } 80 deriving (Show) 81 82 instance ToJSON Pos where 83 toJSON (Pos lat lon ele) = 84 case ele of 85 Just e -> object ["lat" .= lat, "lon" .= lon, "ele" .= e] 86 Nothing -> object ["lat" .= lat, "lon" .= lon, "ele" .= Null] 87 88 instance ToJSON Point where 89 toJSON Point {..} = 90 object 91 [ "pos" .= pos, 92 "time" .= utcTimeToPOSIXSeconds time, 93 "cadence" .= cadence, 94 "power" .= power, 95 "heartRate" .= heartRate 96 ] 97 98 share 99 [mkPersist sqlSettings, mkMigrate "migration"] 100 [persistLowerCase| 101 Trkpt 102 lat Double 103 lon Double 104 ele Double Maybe 105 time UTCTime 106 cadence Int Maybe 107 power Int Maybe 108 heartRate Int Maybe 109 |] 110 111 fromPoint :: Point -> Trkpt 112 fromPoint p = 113 let Pos lat lon ele = pos p 114 in Trkpt lat lon ele (time p) (cadence p) (power p) (heartRate p) 115 116 toPoint :: Entity Trkpt -> Point 117 toPoint entity = 118 let tkp = (\(Entity _ tkp) -> tkp) entity 119 pos = Pos (trkptLat tkp) (trkptLon tkp) (trkptEle tkp) 120 in Point 121 pos 122 (trkptTime tkp) 123 (trkptCadence tkp) 124 (trkptPower tkp) 125 (trkptHeartRate tkp) 126 127 data OverlapExists = OverlapExists String deriving (Show) 128 129 instance Exception OverlapExists 130 131 fetch :: (MonadIO m) => UTCTime -> NominalDiffTime -> ReaderT SqlBackend m [Point] 132 fetch start duration = do 133 let finish = addUTCTime duration start 134 trkpts <- selectList [TrkptTime >. start, TrkptTime <. finish] [] 135 return $ List.map toPoint trkpts 136 137 -- any :: (MonadIO m) => UTCTime -> NominalDiffTime -> ReaderT SqlBackend m Bool 138 any start duration = do 139 let finish = addUTCTime duration start 140 exists <- selectFirst [TrkptTime >. start, TrkptTime <. finish] [] 141 return $ isJust exists 142 143 startTime :: [Point] -> UTCTime 144 startTime (p : ps) = List.foldr (\a b -> min b (time a)) (time p) ps 145 146 duration :: [Point] -> NominalDiffTime 147 duration track = 148 case track of 149 [] -> 0 150 (p : ps) -> 151 let start = startTime track 152 finish = List.foldr (\a b -> max b (time a)) (time p) ps 153 in diffUTCTime finish start 154 155 save :: (MonadIO m) => [Point] -> ReaderT SqlBackend m (Either OverlapExists [Point]) 156 save track = do 157 let start = startTime track 158 priors <- Point.any start (duration track) 159 if priors 160 then return $ Left (OverlapExists "track overlaps with existing data") 161 else do 162 mapM_ (Database.Persist.Class.insert . fromPoint) track 163 Session.refreshDrafts 164 return $ Right track