From 8c6138a0404dfa8924323f206e323e666c4a040e Mon Sep 17 00:00:00 2001 From: "janhavi.soni" Date: Fri, 7 Apr 2023 18:53:55 +0530 Subject: [PATCH] backend/feat : #70 OSM Reverse Geocode API Integration --- lib/mobility-core/mobility-core.cabal | 6 ++- .../src/Kernel/External/Maps/Interface.hs | 35 +++++++++++++ .../src/Kernel/External/Maps/Interface/OSM.hs | 47 +++++++++++++++++ .../Kernel/External/Maps/Interface/Types.hs | 24 ++++++++- .../src/Kernel/External/Maps/OSM/Config.hs | 22 ++++++++ .../External/Maps/OSM/MapsClient/Types.hs | 38 ++++++++++++++ .../External/Maps/OSM/ReverseGeocode.hs | 50 +++++++++++++++++++ .../src/Kernel/External/Maps/Types.hs | 4 +- 8 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 lib/mobility-core/src/Kernel/External/Maps/Interface/OSM.hs create mode 100644 lib/mobility-core/src/Kernel/External/Maps/OSM/Config.hs create mode 100644 lib/mobility-core/src/Kernel/External/Maps/OSM/MapsClient/Types.hs create mode 100644 lib/mobility-core/src/Kernel/External/Maps/OSM/ReverseGeocode.hs diff --git a/lib/mobility-core/mobility-core.cabal b/lib/mobility-core/mobility-core.cabal index 2676a154f..28cd9cd49 100644 --- a/lib/mobility-core/mobility-core.cabal +++ b/lib/mobility-core/mobility-core.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.4. +-- This file has been generated from package.yaml by hpack version 0.35.1. -- -- see: https://github.com/sol/hpack @@ -50,6 +50,7 @@ library Kernel.External.Maps.Interface Kernel.External.Maps.Interface.Google Kernel.External.Maps.Interface.MMI + Kernel.External.Maps.Interface.OSM Kernel.External.Maps.Interface.OSRM Kernel.External.Maps.Interface.Types Kernel.External.Maps.MMI.AutoSuggest @@ -58,6 +59,9 @@ library Kernel.External.Maps.MMI.MapsClient.Types Kernel.External.Maps.MMI.MMIAuthToken Kernel.External.Maps.MMI.Routes + Kernel.External.Maps.OSM.Config + Kernel.External.Maps.OSM.MapsClient.Types + Kernel.External.Maps.OSM.ReverseGeocode Kernel.External.Maps.OSRM.Config Kernel.External.Maps.OSRM.RoadsClient Kernel.External.Maps.Types diff --git a/lib/mobility-core/src/Kernel/External/Maps/Interface.hs b/lib/mobility-core/src/Kernel/External/Maps/Interface.hs index c84c5bae2..c573c624f 100644 --- a/lib/mobility-core/src/Kernel/External/Maps/Interface.hs +++ b/lib/mobility-core/src/Kernel/External/Maps/Interface.hs @@ -27,6 +27,8 @@ module Kernel.External.Maps.Interface getPlaceDetails, getPlaceNameProvided, getPlaceName, + getPlaceDetailsFromLatLonProvided, + getPlaceDetailsFromLatLon, ) where @@ -35,6 +37,7 @@ import Kernel.External.Maps.Google.Config as Reexport import Kernel.External.Maps.HasCoordinates as Reexport (HasCoordinates (..)) import qualified Kernel.External.Maps.Interface.Google as Google import qualified Kernel.External.Maps.Interface.MMI as MMI +import qualified Kernel.External.Maps.Interface.OSM as OSM import qualified Kernel.External.Maps.Interface.OSRM as OSRM import Kernel.External.Maps.Interface.Types as Reexport import Kernel.External.Maps.MMI.Config as Reexport @@ -80,6 +83,7 @@ getDistancesProvided = \case Google -> True OSRM -> False MMI -> True + OSM -> False -- FIXME this logic is redundant, because we throw error always when getDistancesProvided service = False getDistances :: @@ -95,12 +99,14 @@ getDistances serviceConfig req = case serviceConfig of GoogleConfig cfg -> Google.getDistances cfg req OSRMConfig cfg -> OSRM.getDistances cfg req MMIConfig cfg -> MMI.getDistanceMatrix cfg req + OSMConfig _ -> throwNotProvidedError "autoComplete" OSM getRoutesProvided :: MapsService -> Bool getRoutesProvided = \case Google -> True OSRM -> False MMI -> False + OSM -> False getRoutes :: ( EncFlow m r, @@ -114,12 +120,14 @@ getRoutes serviceConfig req = case serviceConfig of GoogleConfig cfg -> Google.getRoutes cfg req OSRMConfig osrmCfg -> OSRM.getRoutes osrmCfg req MMIConfig cfg -> MMI.getRoutes cfg req + OSMConfig _ -> throwNotProvidedError "getRoutes" OSM snapToRoadProvided :: MapsService -> Bool snapToRoadProvided = \case Google -> True OSRM -> True MMI -> False + OSM -> False snapToRoad :: ( EncFlow m r, @@ -133,12 +141,14 @@ snapToRoad serviceConfig req = case serviceConfig of GoogleConfig cfg -> Google.snapToRoad cfg req OSRMConfig osrmCfg -> OSRM.callOsrmMatch osrmCfg req MMIConfig _ -> throwNotProvidedError "snapToRoad" MMI + OSMConfig _ -> throwNotProvidedError "snapToRoad" OSM autoCompleteProvided :: MapsService -> Bool autoCompleteProvided = \case Google -> True OSRM -> False MMI -> True + OSM -> False autoComplete :: ( EncFlow m r, @@ -152,12 +162,14 @@ autoComplete serviceConfig req = case serviceConfig of GoogleConfig cfg -> Google.autoComplete cfg req OSRMConfig _ -> throwNotProvidedError "autoComplete" OSRM MMIConfig cfg -> MMI.autoSuggest cfg req + OSMConfig _ -> throwNotProvidedError "autoComplete" OSM getPlaceDetailsProvided :: MapsService -> Bool getPlaceDetailsProvided = \case Google -> True OSRM -> False MMI -> False + OSM -> False getPlaceDetails :: ( EncFlow m r, @@ -170,12 +182,14 @@ getPlaceDetails serviceConfig req = case serviceConfig of GoogleConfig cfg -> Google.getPlaceDetails cfg req OSRMConfig _ -> throwNotProvidedError "getPlaceDetails" OSRM MMIConfig _ -> throwNotProvidedError "getPlaceDetails" MMI + OSMConfig _ -> throwNotProvidedError "getPlaceDetails" OSM getPlaceNameProvided :: MapsService -> Bool getPlaceNameProvided = \case Google -> True OSRM -> False MMI -> False + OSM -> False getPlaceName :: ( EncFlow m r, @@ -188,3 +202,24 @@ getPlaceName serviceConfig req = case serviceConfig of GoogleConfig cfg -> Google.getPlaceName cfg req OSRMConfig _ -> throwNotProvidedError "getPlaceName" OSRM MMIConfig _ -> throwNotProvidedError "getPlaceName" MMI + OSMConfig _ -> throwNotProvidedError "getPlaceName" OSM + +getPlaceDetailsFromLatLonProvided :: MapsService -> Bool +getPlaceDetailsFromLatLonProvided = \case + Google -> False + OSRM -> False + MMI -> False + OSM -> True + +getPlaceDetailsFromLatLon :: + ( EncFlow m r, + CoreMetrics m + ) => + MapsServiceConfig -> + GetPlaceDetailsFromLatLonReq -> + m GetPlaceDetailsFromLatLonResp +getPlaceDetailsFromLatLon serviceConfig req = case serviceConfig of + GoogleConfig _ -> throwNotProvidedError "getPlaceDetailsFromLatLon" OSM + OSRMConfig _ -> throwNotProvidedError "getPlaceDetailsFromLatLon" OSRM + MMIConfig _ -> throwNotProvidedError "getPlaceDetailsFromLatLon" MMI + OSMConfig cfg -> OSM.getPlaceDetailsFromLatLon cfg req diff --git a/lib/mobility-core/src/Kernel/External/Maps/Interface/OSM.hs b/lib/mobility-core/src/Kernel/External/Maps/Interface/OSM.hs new file mode 100644 index 000000000..4b4d9818b --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/Maps/Interface/OSM.hs @@ -0,0 +1,47 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} + +module Kernel.External.Maps.Interface.OSM where + +import Kernel.External.Maps.Interface.Types +import Kernel.External.Maps.OSM.Config +import qualified Kernel.External.Maps.OSM.ReverseGeocode as OSM +import Kernel.Prelude +import Kernel.Tools.Metrics.CoreMetrics (CoreMetrics) +import Kernel.Types.App + +getPlaceDetailsFromLatLon :: + ( CoreMetrics m, + MonadFlow m + ) => + OSMCfg -> + GetPlaceDetailsFromLatLonReq -> + m GetPlaceDetailsFromLatLonResp +getPlaceDetailsFromLatLon osmCfg req = do + reverseGeocodeRes <- OSM.getPlaceDetailsFromLatLon osmCfg.osmUrl req.responseFormat req.location.lat req.location.lon + return + GetPlaceDetailsFromLatLonResp + { placeId = reverseGeocodeRes.place_id, + displayName = reverseGeocodeRes.display_name, + address = + PlaceDetails + { city = reverseGeocodeRes.address.state_district, + state_ = reverseGeocodeRes.address.state, + country = reverseGeocodeRes.address.country, + street = reverseGeocodeRes.address.road, + building = Nothing, + areaCode = reverseGeocodeRes.address.postcode, + area = reverseGeocodeRes.address.neighbourhood + } + } diff --git a/lib/mobility-core/src/Kernel/External/Maps/Interface/Types.hs b/lib/mobility-core/src/Kernel/External/Maps/Interface/Types.hs index ae47502eb..d5e220252 100644 --- a/lib/mobility-core/src/Kernel/External/Maps/Interface/Types.hs +++ b/lib/mobility-core/src/Kernel/External/Maps/Interface/Types.hs @@ -31,13 +31,14 @@ import Deriving.Aeson import EulerHS.Prelude import qualified Kernel.External.Maps.Google.Config as Google import qualified Kernel.External.Maps.MMI.Config as MMI +import qualified Kernel.External.Maps.OSM.Config as OSM import qualified Kernel.External.Maps.OSRM.Config as OSRM import Kernel.External.Maps.Types import Kernel.External.Types (Language) import Kernel.Types.Common import Kernel.Utils.GenericPretty (PrettyShow) -data MapsServiceConfig = GoogleConfig Google.GoogleCfg | OSRMConfig OSRM.OSRMCfg | MMIConfig MMI.MMICfg +data MapsServiceConfig = GoogleConfig Google.GoogleCfg | OSRMConfig OSRM.OSRMCfg | MMIConfig MMI.MMICfg | OSMConfig OSM.OSMCfg deriving stock (Show, Eq, Generic) deriving (FromJSON, ToJSON) via CustomJSON '[SumTaggedObject "tag" "content"] MapsServiceConfig @@ -181,3 +182,24 @@ data AddressResp = AddressResp } deriving stock (Generic) deriving anyclass (ToJSON, FromJSON, ToSchema) + +data GetPlaceDetailsFromLatLonReq = GetPlaceDetailsFromLatLonReq + { location :: LatLong, + responseFormat :: Text + } + +data GetPlaceDetailsFromLatLonResp = GetPlaceDetailsFromLatLonResp + { placeId :: Text, + displayName :: Text, + address :: PlaceDetails + } + +data PlaceDetails = PlaceDetails + { city :: Text, + state_ :: Text, + country :: Text, + street :: Text, + building :: Maybe Text, + areaCode :: Text, + area :: Text + } diff --git a/lib/mobility-core/src/Kernel/External/Maps/OSM/Config.hs b/lib/mobility-core/src/Kernel/External/Maps/OSM/Config.hs new file mode 100644 index 000000000..9c75ba6c0 --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/Maps/OSM/Config.hs @@ -0,0 +1,22 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} + +module Kernel.External.Maps.OSM.Config where + +import Kernel.Prelude + +data OSMCfg = OSRMCfg + { osmUrl :: BaseUrl + } + deriving (Show, Eq, Generic, FromJSON, ToJSON) \ No newline at end of file diff --git a/lib/mobility-core/src/Kernel/External/Maps/OSM/MapsClient/Types.hs b/lib/mobility-core/src/Kernel/External/Maps/OSM/MapsClient/Types.hs new file mode 100644 index 000000000..c0b9132fd --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/Maps/OSM/MapsClient/Types.hs @@ -0,0 +1,38 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} +{-# LANGUAGE DerivingVia #-} + +module Kernel.External.Maps.OSM.MapsClient.Types where + +import Kernel.Prelude + +data ReverseGeocodeRes = ReverseGeocodeRes + { place_id :: Text, + lat :: Double, + lon :: Double, + display_name :: Text, + address :: AddressInfo + } + deriving (Generic, ToJSON, FromJSON, ToSchema) + +data AddressInfo = AddressInfo + { road :: Text, + neighbourhood :: Text, + state_district :: Text, + state :: Text, + postcode :: Text, + country :: Text, + country_code :: Maybe Text + } + deriving (Generic, ToJSON, FromJSON, ToSchema) diff --git a/lib/mobility-core/src/Kernel/External/Maps/OSM/ReverseGeocode.hs b/lib/mobility-core/src/Kernel/External/Maps/OSM/ReverseGeocode.hs new file mode 100644 index 000000000..bf08710de --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/Maps/OSM/ReverseGeocode.hs @@ -0,0 +1,50 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} + +module Kernel.External.Maps.OSM.ReverseGeocode where + +import EulerHS.Types (EulerClient, client) +import Kernel.External.Maps.OSM.MapsClient.Types as OSM +import Kernel.Prelude +import Kernel.Tools.Metrics.CoreMetrics (CoreMetrics) +import Kernel.Types.Common +import Kernel.Types.Error +import Kernel.Utils.Common +import Servant hiding (throwError) + +type OSMReverseGeocodeAPI = + "reverse" + :> MandatoryQueryParam "format" Text + :> MandatoryQueryParam "lat" Double + :> MandatoryQueryParam "lon" Double + :> Get '[JSON] OSM.ReverseGeocodeRes + +osmReverseGeocodeAPI :: Proxy OSMReverseGeocodeAPI +osmReverseGeocodeAPI = Proxy + +getPlaceDetailsFromLatLonClient :: Text -> Double -> Double -> EulerClient OSM.ReverseGeocodeRes +getPlaceDetailsFromLatLonClient = client osmReverseGeocodeAPI + +getPlaceDetailsFromLatLon :: + ( CoreMetrics m, + MonadFlow m + ) => + BaseUrl -> + Text -> + Double -> + Double -> + m OSM.ReverseGeocodeRes +getPlaceDetailsFromLatLon osmUrl format latitude longitude = do + callAPI osmUrl (getPlaceDetailsFromLatLonClient format latitude longitude) "osm-getPlaceDetails" + >>= fromEitherM (\err -> InternalError $ "Failed to call OSM Reverse Geocode API" <> show err) diff --git a/lib/mobility-core/src/Kernel/External/Maps/Types.hs b/lib/mobility-core/src/Kernel/External/Maps/Types.hs index d6448c359..f533f8d8c 100644 --- a/lib/mobility-core/src/Kernel/External/Maps/Types.hs +++ b/lib/mobility-core/src/Kernel/External/Maps/Types.hs @@ -31,11 +31,11 @@ import Kernel.Storage.Esqueleto (derivePersistField) import Kernel.Utils.GenericPretty (PrettyShow) import Servant.API (FromHttpApiData (..), ToHttpApiData (..)) -data MapsService = Google | OSRM | MMI +data MapsService = Google | OSRM | MMI | OSM deriving (Show, Read, Eq, Ord, Generic, ToJSON, FromJSON, ToSchema) availableMapsServices :: [MapsService] -availableMapsServices = [Google, OSRM, MMI] +availableMapsServices = [Google, OSRM, MMI, OSM] derivePersistField "MapsService"