{-# LANGUAGE OverloadedStrings #-} -- | Here we define providers for the Reverse Geocoding feature, -- namely how to format the url, the key under which the results are returned, -- and how to parse the retrived data. -- -- Currently only Open Stree Map is provided, feel free to add others. -- module Data.Geolocation.Reverse.Providers ( ReverseGeoJsonKey , ReverseGeoParser , ReverseGeoProvider , openStreetMap -- ^ Only one supported right now ) where import Control.Applicative import Data.Char import Data.Monoid import Data.Maybe import Data.Aeson import Data.Aeson.Types import Control.Monad (join) import qualified Data.Text as T import Data.Geolocation.Reverse.Types type ReverseGeoJsonKey = T.Text type ReverseGeoUrl = Latitude -> Longitude -> Maybe String type ReverseGeoParser = Object -> Parser ParsedLocationInfo type ReverseGeoProvider = (ReverseGeoJsonKey, ReverseGeoUrl, ReverseGeoParser) openStreetMap :: ReverseGeoProvider openStreetMap = ("address", openStreetMapUrl, openStreetMapParser) openStreetMapUrl :: Latitude -> Longitude -> Maybe String openStreetMapUrl (Latitude mlat) (Longitude mlon) = do lat <- mlat lon <- mlon return $ "http://nominatim.openstreetmap.org/reverse" <> "?format=json" <> "&zoom=18" <> "&lat=" <> show lat <> "&lon=" <> show lon getPostCodeText :: Suburb -> Maybe Suburb getPostCodeText (Suburb txt) = let rest = T.dropWhile (\c -> isDigit c || isSpace c || (c == '-')) txt in if T.null rest then Nothing else Just (Suburb rest) (<||>) :: Parser (Maybe a) -> Parser (Maybe a) -> Parser (Maybe a) pa <||> pb = do a <- pa if isJust a then pa else pb -- Not all records have suburb defined, but some have it written in the post code -- This function tries to extract if from the post code if available. openStreetMapParser :: Object -> Parser ParsedLocationInfo openStreetMapParser o = ParsedLocationInfo <$> o .: "country_code" <*> o .: "city" <*> ((o .:? "suburb") <||> fmap (join . fmap getPostCodeText) ( o .:? "postcode")) <*> ((o .:? "road") <|> (o .:? "street")) <*> o .:? "postcode"