gps 0.2.4 → 0.3.0
raw patch · 4 files changed
+193/−166 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Data.GPS: instance Eq DMS
- Data.GPS: instance Ord DMS
- Data.GPS: instance Read DMS
- Data.GPS: instance Show DMS
- Data.GPS: kmlToString :: KML -> String
- Data.GPS: pointsToKML :: (Coordinate a) => String -> [a] -> [String] -> KML
- Data.GPS: trailToKML :: (Coordinate a) => String -> Trail a -> KML
- Data.GPS: type KML = Element
+ Data.GPS: addVector :: Vector -> DMSCoordinate -> DMSCoordinate
+ Data.GPS: data Latitude
+ Data.GPS: data Longitude
+ Data.GPS: degreePairToDMS :: (Double, Double) -> DMSCoordinate
+ Data.GPS: dmsToDegreePair :: DMSCoordinate -> (Double, Double)
+ Data.GPS: dmsToRadianPair :: DMSCoordinate -> (Double, Double)
+ Data.GPS: east :: Double
+ Data.GPS: instance Eq (DMS a)
+ Data.GPS: instance Ord (DMS a)
+ Data.GPS: instance Read (DMS a)
+ Data.GPS: instance Show (DMS a)
+ Data.GPS: north :: Integer
+ Data.GPS: radiusOfEarth :: Double
+ Data.GPS: south :: Double
+ Data.GPS: west :: Double
+ Data.GPS.KML: kmlToString :: KML -> String
+ Data.GPS.KML: pointsToKML :: (Coordinate a) => String -> [a] -> [String] -> KML
+ Data.GPS.KML: trailToKML :: (Coordinate a) => String -> Trail a -> KML
+ Data.GPS.KML: type KML = Element
- Data.GPS: DMS :: Double -> Double -> Double -> DMS
+ Data.GPS: DMS :: Double -> Double -> Double -> DMS a
- Data.GPS: DMSCoord :: DMS -> DMS -> DMSCoordinate
+ Data.GPS: DMSCoord :: DMS Latitude -> DMS Longitude -> DMSCoordinate
- Data.GPS: data DMS
+ Data.GPS: data DMS a
- Data.GPS: degrees :: DMS -> Double
+ Data.GPS: degrees :: DMS a -> Double
- Data.GPS: latitude :: DMSCoordinate -> DMS
+ Data.GPS: latitude :: DMSCoordinate -> DMS Latitude
- Data.GPS: longitude :: DMSCoordinate -> DMS
+ Data.GPS: longitude :: DMSCoordinate -> DMS Longitude
- Data.GPS: minutes :: DMS -> Double
+ Data.GPS: minutes :: DMS a -> Double
- Data.GPS: seconds :: DMS -> Double
+ Data.GPS: seconds :: DMS a -> Double
Files
- Data/GPS.hs +107/−129
- Data/GPS/KML.hs +83/−0
- LICENSE +0/−30
- gps.cabal +3/−7
Data/GPS.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, EmptyDataDecls #-} -- |Author: Thomas DuBuisson -- Copyright: Thomas DuBuisson -- License: BSD3@@ -21,21 +21,28 @@ , Location(..) , DMSCoordinate(..) , DMS(..)+ , Latitude+ , Longitude , Heading , Speed , Vector , Trail+ -- * Constants+ , north+ , south+ , east+ , west+ , radiusOfEarth -- * Helper Functions , smoothTrails , longestSmoothTrail , restLocations , closestDistance , normalizeDMS- -- * KML Operations (Open format used by GoogleEarth, among others)- , KML- , trailToKML- , pointsToKML- , kmlToString+ , dmsToDegreePair+ , dmsToRadianPair+ , degreePairToDMS+ , addVector ) where import Data.Ord (comparing)@@ -51,79 +58,41 @@ 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 }+-- |Distances are expressed in meters+type Distance = Double -basicName :: String -> QName-basicName n = QName n Nothing Nothing+-- |Angles are expressed in radians from North.+-- 0 == North+-- pi/2 == West+-- pi == South+-- (3/2)pi == East == - (pi / 2)+type Heading = Double -basicElem :: String -> Element-basicElem n = Element (basicName n) [] [] Nothing+-- |Speed is hard coded as meters per second+type Speed = Double -filledElem :: String -> String -> Element-filledElem name val = Element (basicName name) [] [Text (CData CDataText val Nothing)] Nothing+type Vector = (Distance, Heading)+type Trail a = [a] --- |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 "]+-- |Empty declaration for phantom type+data Latitude --- |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))- ]+-- |Empty declaration for phantom type+data Longitude --- |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)+-- |DMSCoordinate is the typical degree minute second+-- for latitude/longitude used by most civilian GPS devices.+data DMSCoordinate = DMSCoord+ { latitude :: DMS Latitude+ , longitude :: DMS Longitude+ } deriving (Eq, Ord, Show, Read) --- |radius of the earth in meters-radiusOfEarth :: Double-radiusOfEarth = 6378700+-- |DMS is the degrees, minutes, seconds used for both latitude and longitude+data DMS a = DMS+ { degrees :: Double+ , minutes :: Double+ , seconds :: Double }+ deriving (Eq, Ord, Show, Read) -- |A coordinate is a place on earths surface. While twoDMS and fromDMS are the only -- required functions, coordinate systems may provide more accurate calculations@@ -133,10 +102,10 @@ fromDMS :: DMSCoordinate -> a distance :: a -> a -> Distance distance a b =- 3963000 * acos( sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1) )+ radiusOfEarth * acos( sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1) ) where- (lat1, lon1) = dmsToLatLon (toDMS a)- (lat2, lon2) = dmsToLatLon (toDMS b)+ (lat1, lon1) = dmsToRadianPair (toDMS a)+ (lat2, lon2) = dmsToRadianPair (toDMS b) heading :: a -> a -> Heading -- 0 = North, pi/2 = West... heading a b = dmsHeading (toDMS a) (toDMS b) getVector :: a -> a -> Vector@@ -151,32 +120,84 @@ speed a b = distMeter / timeSec where timeSec = realToFrac $ diffUTCTime (getUTC b) (getUTC a)- distMeter = distance (getDMS a) (getDMS b)+ distMeter = distance (getCoordinate a) (getCoordinate b) -toDecimal = (*) (180 / pi)+instance Coordinate DMSCoordinate where+ toDMS = id+ fromDMS = id --- |Extract a lat/lon from a location.-getDMS :: (Location loc x y) => loc -> DMSCoordinate-getDMS = toDMS . getCoordinate+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++-- |radius of the earth in meters+radiusOfEarth :: Double+radiusOfEarth = 6378700++-- |North is 0 radians+north = 0++-- |South, being 180 degrees from North, is pi.+south = pi++-- |East is 270 degrees from North+east = (3 / 2) * pi++-- |West is 90 degrees (pi/2)+west = pi / 2++toDecimal = (*) (180 / pi)+ -- |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+ (lat1, lon1) = dmsToRadianPair a+ (lat2, lon2) = dmsToRadianPair 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)+-- |Given a vector and coordinate, computes a new DMS coordinate.+-- within some epsilon it should hold that if+-- dest = addVector (dist,heading) start+-- then+-- heading == dmsHeading start dest+-- dist == distance start dest+addVector :: Vector -> DMSCoordinate -> DMSCoordinate+addVector (d,h) dms = degreePairToDMS (toDecimal lat2, toDecimal lon2) where+ (lat,lon) = dmsToRadianPair dms+ lat2 = lat + (cos h) * (d / radiusOfEarth)+ lon2 = lon + acos ( (cos (d/radiusOfEarth) - sin lat * sin lat2) / (cos lat * cos lat2))++-- |Provides a lat/lon pair of doubles in radians+dmsToRadianPair :: DMSCoordinate -> (Double, Double)+dmsToRadianPair (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) +-- |Provides a lat/lon pair of doubles in degrees+dmsToDegreePair :: DMSCoordinate -> (Double,Double)+dmsToDegreePair (DMSCoord (DMS latD latM latS) (DMS lonD lonM lonS)) = (lat, lon)+ where+ lon = toDegrees lonD lonM lonS+ lat = toDegrees latD latM latS+ toDegrees d m s = (d + m / 60 + s / 3600)++-- Given a lat/long pair of doubles in degrees, produces a DMSCoordinate+degreePairToDMS :: (Double,Double) -> DMSCoordinate+degreePairToDMS (lat,lon) = DMSCoord (DMS lat 0 0) (DMS lon 0 0)+ -- |Typically useful for printing, normalizes degrees/minutes/seconds -- into just degrees and decimal minutes: -- DMSCoord (DMS 45 36.938455 0) (DMS ...)@@ -191,51 +212,7 @@ 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) @@ -262,6 +239,7 @@ longestSmoothTrail :: Location a b c => Speed -> Trail a -> Trail a longestSmoothTrail metersPerSec trail = maximumBy (comparing length) ([] : smoothTrails metersPerSec trail) +-- |Filters out any points that go backward in time (thus must not be valid if this is a trail) linearTime :: Location a b c => Trail a -> Trail a linearTime [] = [] linearTime (p:ps) = go (getUTC p) ps
+ Data/GPS/KML.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, EmptyDataDecls #-}+-- |Author: Thomas DuBuisson+-- Copyright: Thomas DuBuisson+-- License: BSD3+module Data.GPS.KML+ ( -- * KML Operations (Open format used by GoogleEarth, among others)+ KML+ , trailToKML+ , pointsToKML+ , kmlToString+ ) where++import Text.XML.Light+import Data.GPS++-- |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) = dmsToRadianPair . toDMS $ x+ f = show . (*) (180 / pi)+ in (f lat, f lon)+
LICENSE view
@@ -1,30 +0,0 @@-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.
gps.cabal view
@@ -1,5 +1,5 @@ name: gps-version: 0.2.4+version: 0.3.0 license: BSD3 license-file: LICENSE author: Thomas DuBuisson <thomas.dubuisson@gmail.com>@@ -17,13 +17,9 @@ 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,+ 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+ exposed-modules: Data.GPS, Data.GPS.KML ghc-options: