packages feed

gps (empty) → 0.2.4

raw patch · 4 files changed

+395/−0 lines, 4 filesdep +basedep +binarydep +bytestringsetup-changed

Dependencies added: base, binary, bytestring, pretty, prettyclass, time, xml

Files

+ Data/GPS.hs view
@@ -0,0 +1,334 @@+{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}+-- |Author: Thomas DuBuisson+-- Copyright: Thomas DuBuisson+-- License: BSD3+--+-- A basic GPS library with calculations for distance and speed along+-- with helper functions for filtering/smoothing trails.  All distances+-- are in meters and time is in seconds.  Speed is thus meters/second+--+-- The current intent of this library is to+--   1) Provide a standard type class interface for coordinates+--   2) Include fleshed out support for relevant libraries, a task+--      too often neglected in modern Hackage packages.+--+--  Integration includes KML support via the xml package, pretty printing,+--  anb binary.++module Data.GPS+	( -- * Types and Classes+	  Coordinate(..)+	, Location(..)+	, DMSCoordinate(..)+	, DMS(..)+	, Heading+	, Speed+	, Vector+	, Trail+	-- * Helper Functions+	, smoothTrails+	, longestSmoothTrail+	, restLocations+	, closestDistance+	, normalizeDMS+	-- * KML Operations (Open format used by GoogleEarth, among others)+	, KML+	, trailToKML+	, pointsToKML+	, kmlToString+	) where++import Data.Ord (comparing)+import Data.List (sort, mapAccumL, maximumBy)+import Data.Binary+import Data.Binary.Put+import Data.Binary.Get++import Text.PrettyPrint.HughesPJClass+import Text.PrettyPrint+import Numeric (showFFloat)++import Data.Time+import Data.Maybe (listToMaybe)++import Text.XML.Light++-- |The KML type and operations might be moved into a Data.KML module in the future+type KML = Element++-- |Converts the KML elements to a string and prepends the proper XML header,+-- thus making it the correct format for saving as a file and opening it+-- with other programs such as GoogleEarth.+kmlToString :: KML -> String+kmlToString = (++) xml_header . ppElement++kmlTop = Element (QName "kml" Nothing Nothing) [Attr (basicName "xmlns") "http://www.opengis.net/kml/2.2"] [] Nothing++addElem :: Element -> Element -> Element+addElem top child = top { elContent = Elem child : elContent top }++basicName :: String -> QName+basicName n = QName n Nothing Nothing++basicElem :: String -> Element+basicElem n = Element (basicName n) [] [] Nothing++filledElem :: String -> String -> Element+filledElem name val = Element (basicName name) [] [Text (CData CDataText val Nothing)] Nothing++-- |converts a given set of coordinates to a trail in KML format.+-- Useful for saving as a file.+trailToKML :: Coordinate a => String -> Trail a -> KML+trailToKML name ps = addElem kmlTop doc+ where+  doc = foldl addElem (basicElem "Document") (nameElem : trailElem : [])+  nameElem = filledElem "name" name+  trailName = filledElem "name" (name ++ " trail")+  trailElem = foldl addElem (basicElem "Placemark") [trailName, pointsElem]+  pointsElem = addElem (basicElem "LineString") (filledElem "coordinates" points)+  points :: String+  points = concatMap packCoordinate ps+  packCoordinate :: Coordinate a => a -> String+  packCoordinate x =+	let (lat, lon) = getDegreeLatLon x+	in concat [lon, ",", lat, ",0 "]++-- |converts a given set of coordinates to points in KML format.+-- Useful for saving as a file.+pointsToKML :: Coordinate a => String -> [a] -> [String] -> KML+pointsToKML name ps pointNames = addElem kmlTop doc+ where+  doc = foldl addElem (basicElem "Document") (nameElem : placemarkElems)+  nameElem = filledElem "name" name+  placemarkElems = map buildPoint (zip ps pointNames)+  buildPoint (p,n) =+	let (t,g) = getDegreeLatLon p+	in foldl addElem (basicElem "Placemark")+	[ filledElem "name" n+	, foldl addElem (basicElem "LookAt")+		[ filledElem "longitude" g+		, filledElem "latitude" t+		, filledElem "altitude" "0"+		, filledElem "range" "500"+		]+	, addElem (basicElem "Point") (filledElem "coordinates" (g ++ "," ++ t))+	]++-- |Gets a pair of lat lon bytestrings (in degrees)+getDegreeLatLon :: Coordinate a => a -> (String,String)+getDegreeLatLon x =+	let (lat,lon) = dmsToLatLon . toDMS $ x+	    f = show . toDecimal+	in (f lat, f lon)++-- |radius of the earth in meters+radiusOfEarth :: Double+radiusOfEarth = 6378700++-- |A coordinate is a place on earths surface.  While twoDMS and fromDMS are the only+-- required functions, coordinate systems may provide more accurate calculations+-- of heading, distance, and vectors without the intermediate translation.+class Coordinate a where+	toDMS      :: a -> DMSCoordinate+	fromDMS	:: DMSCoordinate -> a+	distance        :: a -> a -> Distance+	distance a b =+		3963000 * acos( sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1) )+	 where+	  (lat1, lon1) = dmsToLatLon (toDMS a)+	  (lat2, lon2) = dmsToLatLon (toDMS b)+	heading         :: a -> a -> Heading	-- 0 = North, pi/2 = West...+	heading a b = dmsHeading (toDMS a) (toDMS b)+	getVector       :: a -> a -> Vector+	getVector a b = (distance a b, heading a b)++-- |A location is a coordinate at a specific time+class (Coordinate coord) => Location loc coord time | loc -> coord, loc -> time where+	getCoordinate   :: loc -> coord+	getTime		:: loc -> time+	getUTC		:: loc -> UTCTime+	speed           :: loc -> loc -> Speed		-- meters per second+	speed a b = distMeter / timeSec+	 where+	  timeSec = realToFrac $ diffUTCTime (getUTC b) (getUTC a)+	  distMeter = distance (getDMS a) (getDMS b)++toDecimal = (*) (180 / pi)++-- |Extract a lat/lon from a location.+getDMS :: (Location loc x y) => loc -> DMSCoordinate+getDMS = toDMS . getCoordinate++-- |provides a heading in radians from North+dmsHeading :: DMSCoordinate -> DMSCoordinate -> Heading+dmsHeading a b =+	atan2	(sin (diffLon) * cos (lat2)) +		(cos(lat1) * sin (lat2) - sin(lat1) * cos lat2 * cos (diffLon))+ where+  (lat1, lon1) = dmsToLatLon a+  (lat2, lon2) = dmsToLatLon b+  diffLon = lon1 - lon2++-- provides a lat/lon pair in radians+dmsToLatLon :: DMSCoordinate -> (Double, Double)+dmsToLatLon (DMSCoord (DMS latD latM latS) (DMS lonD lonM lonS)) = (lat, lon)+  where+    lon = toRadians lonD lonM lonS+    lat = toRadians latD latM latS+    toRadians d m s = (d + m / 60 + s / 3600) * (pi / 180)++-- |Typically useful for printing, normalizes degrees/minutes/seconds+-- into just degrees and decimal minutes:+--     DMSCoord (DMS 45 36.938455 0) (DMS ...)+normalizeDMS :: DMSCoordinate -> DMSCoordinate+normalizeDMS (DMSCoord lat lon) =+	DMSCoord lat' lon'+  where+   lat' = normalize lat+   lon' = normalize lon+   normalize (DMS d m s) =+	let all = d + (m / 60) + (s / 3600)+	    d'  = fromIntegral (floor all)+	    m'  = (all-d') * 60+	in DMS d' m' 0+   +-- |Distances are expressed in meters+type Distance = Double++-- |Angles are expressed in radians from North.+-- 	0	== North+-- 	pi/2	== West+-- 	pi 	== South+-- 	(3/2)pi	== East    == - (pi / 2)+type Heading = Double++-- |Speed is hard coded as meters per second+type Speed = Double++type Vector = (Distance, Heading)+type Trail a = [a]++-- |DMSCoordinate is the typical degree minute second+-- for latitude/longitude used by most civilian GPS devices.+data DMSCoordinate = DMSCoord+			{ latitude	:: DMS+			, longitude	:: DMS+		} deriving (Eq, Ord, Show, Read)++-- |DMS is the degree, minute, seconds used for both latitude and longitude+data DMS = DMS	{ degrees	::	Double+		, minutes	::	Double+		, seconds	::	Double }+	deriving (Eq, Ord, Show, Read)++instance Coordinate DMSCoordinate where+	toDMS = id+	fromDMS = id++instance (Coordinate c) => Location (c, UTCTime) c UTCTime where+	getCoordinate = fst+	getTime = snd+	getUTC  = snd++instance (Coordinate c) =>  Coordinate (c,UTCTime) where+	toDMS = toDMS . getCoordinate+	fromDMS d = (fromDMS d , UTCTime (toEnum 0) 0)++data TempTrail a = T (Trail a) a++fromTemp :: TempTrail a -> Trail a+fromTemp (T ps p) = reverse (p:ps)++-- |'smoothTrails speed trail' should separate points that would mandate a speed+-- in excess of the given rate into separate Trails.  No point are deleted;+-- if there is only one 'correct' trail expected then the largest resulting+-- trail will likely be the one your looking for.+smoothTrails :: Location a b c => Speed -> Trail a -> [Trail a]+smoothTrails s ps =+	let (trails, _) = mapAccumL go [] (linearTime ps)+	in map fromTemp trails+  where+   go acc p =+	let (i,trails) = mapAccumL (add p) 0 acc+	in if i == 0+		then (T [] p : trails, ())+		else (trails, ())+   add p i (T ts l) =+	if (speed l p) <= s+		then (i + 1, T (l:ts) p)+		else (i, T ts l)++-- |longestSmoothTrail is ust 'smoothTrails' returning only the longest smooth trail.+longestSmoothTrail :: Location a b c => Speed -> Trail a -> Trail a+longestSmoothTrail metersPerSec trail = maximumBy (comparing length) ([] : smoothTrails metersPerSec trail)++linearTime :: Location a b c => Trail a -> Trail a+linearTime [] = []+linearTime (p:ps) = go (getUTC p) ps+  where+  go _ [] = []+  go t (p:ps) = if getUTC p < t then go t ps else p : go (getUTC p) ps++-- |'restLocation dist timeDiff' will create a list of trails, all of which are+--  within 'distance' of each other spanning atleast the given amount of time.+--+--  For example:+--  > restLocations 50 600+-- +-- Would return lists of all points that are within 50 meters of each other and+-- span at least 10 minutes (600 seconds).+--+-- Note this gives points within fifty meters of the earliest point - wandering+-- in a rest area with a 50 meter radius could result in several rest points+-- ([a,b..]) or even none if the distance between individual points exceeds 50m.+restLocations :: Location a b c => Distance -> NominalDiffTime -> Trail a -> [Trail a]+restLocations d s = filter timeSpan . span2+  where+   span2 :: Location a b c => [a] -> [[a]]+   span2 [] = []+   span2 (a:as) = let (x,y) = span ((>) d . distance (getCoordinate a) . getCoordinate) as in (a : x) : (span2 y)+   timeSpan :: Location a b c => Trail a -> Bool+   timeSpan [] = False+   timeSpan t  = s <= diffUTCTime (getUTC (last t)) (getUTC (head t))++-- |Returns the closest distance between two trails (or Nothing if a trail is empty)+-- O( (n * m) * log (n * m) )+closestDistance :: Coordinate a => Trail a -> Trail a -> Maybe Distance+closestDistance as bs = listToMaybe $ sort [distance a b | a <- as, b <- bs]++instance Pretty DMSCoordinate where+	pPrint c =+		let (DMSCoord (DMS d' m' _) (DMS d m _)) = normalizeDMS c+		    showF = showFFloat (Just 6)+		    showX = shows . floor+		in text $ showX d' (' ' : showF m' ('\t' : showX d (' ' : showF m "")))++instance Binary DMSCoordinate where+	put (DMSCoord (DMS tD tM tS) (DMS gD gM gS)) = do+		put tD >> put tM >> put tS >> put gD >> put gM >> put gS+	get = do+		tD <- get+		tM <- get+		tS <- get+		gD <- get+		gM <- get+		gS <- get+		return (DMSCoord (DMS tD tM tS) (DMS gD gM gS))++putLocation :: Binary c => (c, UTCTime) -> Put+putLocation (c,t) = put c >> put t++getLocation :: Binary c => Get (c, UTCTime)+getLocation = do+	c <- get+	t <- get+	return (c,t)++instance Binary UTCTime where+	get = do+		day <- get+		diff <- get+		return (UTCTime (toEnum day) (fromRational diff))+	put (UTCTime day diff) = do+		put (fromEnum day)+		put (toRational diff)
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) Thomas DuBuisson++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ gps.cabal view
@@ -0,0 +1,29 @@+name:		gps+version:	0.2.4+license:	BSD3+license-file:	LICENSE+author:		Thomas DuBuisson <thomas.dubuisson@gmail.com>+maintainer:	Thomas DuBuisson+description:	Useful for manipulating GPS coordinages (in various forms), building paths, and performing basic computations+synopsis:	For manipulating GPS coordinates and trails.+category:	Data+stability:	stable+build-type:	Simple+cabal-version:	>= 1.2+tested-with:	GHC == 6.10.3+extra-source-files: ++Flag small_base+  Description: Choose the split-up base package.++Library+  if flag(small_base)+    Build-Depends: base >= 3 && < 5, bytestring >= 0.9 && < 1.0, binary >= 0.4.0 && < 0.6.0,+                   pretty >= 1.0 && < 1.1 , prettyclass >= 1.0 && < 1.1, xml >= 1.3 && < 1.4,+                   time >= 1.1.2 && < 1.2+  else+    Build-Depends: base >= 3 && < 5, bytestring >= 0.9 && < 1.0, binary >= 0.4.0 && < 0.6.0,+                   pretty >= 1.0 && < 1.1 , prettyclass >= 1.0 && < 1.1, xml >= 1.3 && < 1.4+  hs-source-dirs:+  exposed-modules: Data.GPS+  ghc-options: