{-# LANGUAGE OverloadedStrings, FlexibleInstances #-} module Data.Locations.Postcodes (getPostcode, getPostcodesInRange, getPostcodeAtLocation, Postcode(..)) where import Control.Applicative ((<$>), (<*>), pure) import Data.Aeson import qualified Data.ByteString.Lazy.Char8 as BL import Data.Char (toUpper) import Network.HTTP data Postcode = Postcode {_postcode :: String, _latitude :: Float, _longitude :: Float, _constituency :: Maybe String, _district :: Maybe String} deriving (Show, Eq) instance FromJSON Postcode where parseJSON (Object v) = Postcode <$> v .: "postcode" <*> (read <$> getInP "lat" geo) <*> (read <$> getInP "lng" geo) <*> (Just <$> (getInP "title" . getInP "constituency" $ admin)) <*> (Just <$> (getInP "title" . getInP "district" $ admin)) where geo = v .: "geo" admin = v .: "administrative" getInP k p = flip (.:) k =<< p instance FromJSON (Float, Postcode) where parseJSON (Object v) = (,) <$> (read <$> v .: "distance") <*> (Postcode <$> v .: "postcode" <*> (read <$> v.: "lat") <*> (read <$> v.: "lng") <*> pure Nothing <*> pure Nothing) baseURL :: String baseURL = "http://uk-postcodes.com" -- | Return a Postcode for the postcode string. This uses the network getPostcode :: String -- ^ The postcode -> IO (Maybe Postcode) -- ^ Either Just a postcode or Nothing if it could not be found getPostcode pc = postcodeRequest ("/postcode/" ++ sanatisePostcode pc ++ ".json") -- | Find all the postcodes within a given range and return a postcode and the distance for each. This uses the network getPostcodesInRange :: String -- ^ The postcode to be the centre of the search -> Float -- ^ The radius to search in miles -> IO (Maybe [(Float, Postcode)]) -- ^ Either Just a list of tuples of distance and postcode or Nothing if the given postcode could not be found getPostcodesInRange pc range = postcodeRequest ("/distance.php?postcode=" ++ sanatisePostcode pc ++ "&distance=" ++ show range ++ "&format=json") -- | Find the postcode at the given latitude and longitude getPostcodeAtLocation :: Float -- ^ Latitude -> Float -- ^ Longitude -> IO (Maybe Postcode) -- ^ Either Just the postcode at the give location or Nothing if there is not a postcode there getPostcodeAtLocation lat lng = postcodeRequest ("/latlng/" ++ show lat ++ "," ++ show lng ++ ".json") postcodeRequest :: FromJSON a => String -> IO (Maybe a) postcodeRequest url = do response <- getResponseBody =<< simpleHTTP (getRequest $ baseURL ++ url) return (decode $ BL.pack response) sanatisePostcode :: String -> String sanatisePostcode = removeSpace . map toUpper removeSpace :: String -> String removeSpace = filter (/= ' ')