diff --git a/README.md b/README.md index 79fe5b7..906da97 100644 --- a/README.md +++ b/README.md @@ -249,8 +249,8 @@ showLocation({ latitude: 38.8976763, longitude: -77.0387185, sourceLatitude: -8.0870631, // optionally specify starting location for directions - sourceLongitude: -34.8941619, // not optional if sourceLatitude is specified - title: 'The White House', // optional + sourceLongitude: -34.8941619, // required if sourceLatitude is specified + title: 'The White House', // optional googleForceLatLon: false, // optionally force GoogleMaps to use the latlon for the query instead of the title googlePlaceId: 'ChIJGVtI4by3t4kRr51d_Qm_x58', // optionally specify the google-place-id alwaysIncludeGoogle: true, // optional, true will always add Google Maps to iOS and open in Safari, even if app is not installed (default: false) @@ -265,12 +265,23 @@ showLocation({ }); ``` +Alternatively you can specify the `address` field and leave the latitude and longitude properties as empty strings + +```js +import {showLocation} from 'react-native-map-link'; + +showLocation({ + address: '1600 Pennsylvania Avenue NW, Washington, DC 20500', // Required if replacing latitude and longitude + app: 'comgooglemaps', // optionally specify specific app to use +}); +``` + Notes: - The `sourceLatitude` / `sourceLongitude` options only work if you specify both. Currently supports all apps except Waze. - `directionsMode` works on google-maps, apple-maps and sygic (on apple-maps, `bike` mode will not work, while on sygic, only `walk` and `car` will work). Without setting it, the app will decide based on its own settings. - If you set `directionsMode` but do not set `sourceLatitude` and `sourceLongitude`, google-maps and apple-maps will still enter directions mode, and use the current location as starting point. -- +- If you want to query an address instead of passing the `latitude` and `longitude` fields, you can do this by leaving those fields off and provide a full address to be queried with the `address` field. Just be aware that not all applications support this. ### Or @@ -296,6 +307,7 @@ const Demo = () => { const result = await getApps({ latitude: 38.8976763, longitude: -77.0387185, + address: '1600 Pennsylvania Avenue NW, Washington, DC 20500', // optional title: 'The White House', // optional googleForceLatLon: false, // optionally force GoogleMaps to use the latlon for the query instead of the title alwaysIncludeGoogle: true, // optional, true will always add Google Maps to iOS and open in Safari, even if app is not installed (default: false) diff --git a/package.json b/package.json index ee91db2..4a95e05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-map-link", - "version": "3.0.1", + "version": "3.1.0", "description": "Open the map app of the user's choice with a specific location.", "source": "src/index", "main": "lib/index.js", diff --git a/src/index.ts b/src/index.ts index 1494f10..f08bb1e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,7 @@ export type {PopupProps} from './components/popup/Popup'; export const showLocation = async ({ latitude, longitude, + address, sourceLatitude, sourceLongitude, appleIgnoreLatLon, @@ -49,6 +50,7 @@ export const showLocation = async ({ checkOptions({ latitude, longitude, + address, googleForceLatLon, googlePlaceId, title: customTitle, @@ -62,6 +64,7 @@ export const showLocation = async ({ let sourceLat; let sourceLng; let sourceLatLng; + let fullAddress; if (sourceLatitude != null && sourceLongitude != null) { useSourceDestiny = true; @@ -76,6 +79,10 @@ export const showLocation = async ({ sourceLatLng = `${sourceLat},${sourceLng}`; } + if (address) { + fullAddress = encodeURIComponent(address); + } + const lat = typeof latitude === 'string' ? parseFloat(latitude) : latitude; const lng = typeof longitude === 'string' ? parseFloat(longitude) : longitude; const latlng = `${lat},${lng}`; @@ -121,6 +128,7 @@ export const showLocation = async ({ sourceLat, sourceLng, sourceLatLng, + address: fullAddress, title, encodedTitle, prefixes, diff --git a/src/type.ts b/src/type.ts index 9a1eb65..370f44b 100644 --- a/src/type.ts +++ b/src/type.ts @@ -45,8 +45,11 @@ export type GetAppsResponse = { }; export interface ShowLocationProps { - latitude: number | string; - longitude: number | string; + latitude?: number | string; + longitude?: number | string; + /** optionally you can enter a full address that will be queried against the map app's API and return the initial results if not the actual matched result. */ + /** latitude and longitude will be ignored if the address field is set */ + address?: string | null; sourceLatitude?: number | null; sourceLongitude?: number | null; appleIgnoreLatLon?: boolean; diff --git a/src/utils.ts b/src/utils.ts index 1c9c068..e5a2387 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -166,6 +166,7 @@ export const getDirectionsModeSygic = ( export const checkOptions = ({ latitude, longitude, + address, googleForceLatLon, googlePlaceId, title, @@ -174,8 +175,9 @@ export const checkOptions = ({ appTitles, appsWhiteList, }: { - latitude: number | string; - longitude: number | string; + latitude?: number | string; + longitude?: number | string; + address?: string | null; googleForceLatLon?: boolean | null | undefined; googlePlaceId?: number | string | null | undefined; title?: string | null | undefined; @@ -189,6 +191,14 @@ export const checkOptions = ({ '`showLocation` should contain keys `latitude` and `longitude`.', ); } + if (address && typeof address !== 'string') { + throw new MapsException('Option `address` should be of type `string`.'); + } + if (!latitude && !longitude && !address) { + throw new MapsException( + '`latitude` & `longitude` or `address` is required. Both cannot be undefined.', + ); + } if (title && typeof title !== 'string') { throw new MapsException('`title` should be of type `string`.'); } @@ -230,6 +240,7 @@ export const generateMapUrl = ({ sourceLat, sourceLng, sourceLatLng, + address, title, encodedTitle, prefixes, @@ -241,12 +252,13 @@ export const generateMapUrl = ({ googleForceLatLon?: boolean; googlePlaceId: string | number | null | undefined; naverCallerName: string | null | undefined; - lat: number; - lng: number; + lat?: number; + lng?: number; latlng: string; sourceLat?: number; sourceLng?: number; sourceLatLng?: string; + address?: string | null; title?: string | null; encodedTitle?: string; prefixes: Record; @@ -258,14 +270,20 @@ export const generateMapUrl = ({ case 'apple-maps': const appleDirectionMode = getDirectionsModeAppleMaps(directionsMode); url = prefixes['apple-maps']; - if (useSourceDestiny || directionsMode) { - url = `${url}?daddr=${latlng}`; - url += sourceLatLng ? `&saddr=${sourceLatLng}` : ''; - } else if (!appleIgnoreLatLon) { - url = `${url}?ll=${latlng}`; + if (address) { + url = `${url}?address=${address}`; + } else { + if (useSourceDestiny || directionsMode) { + url = `${url}?daddr=${latlng}`; + url += sourceLatLng ? `&saddr=${sourceLatLng}` : ''; + } else if (!appleIgnoreLatLon) { + url = `${url}?ll=${latlng}`; + } } url += - useSourceDestiny || directionsMode || !appleIgnoreLatLon ? '&' : '?'; + useSourceDestiny || directionsMode || address || !appleIgnoreLatLon + ? '&' + : '?'; url += `q=${title ? encodedTitle : 'Location'}`; url += appleDirectionMode ? `&dirflg=${appleDirectionMode}` : ''; break; @@ -286,162 +304,252 @@ export const generateMapUrl = ({ url += googleDirectionMode ? `&travelmode=${googleDirectionMode}` : ''; } else { - // Use "search" as this will open up a single marker - url = 'https://www.google.com/maps/search/?api=1'; - - if (!googleForceLatLon && title) { - url += `&query=${encodedTitle}`; + if (address) { + url = `https://www.google.com/maps/search/?q=${address}`; } else { - url += `&query=${latlng}`; - } + // Use "search" as this will open up a single marker + url = 'https://www.google.com/maps/search/?api=1'; - url += googlePlaceId ? `&query_place_id=${googlePlaceId}` : ''; + if (!googleForceLatLon && title) { + url += `&query=${encodedTitle}`; + } else { + url += `&query=${latlng}`; + } + + url += googlePlaceId ? `&query_place_id=${googlePlaceId}` : ''; + } } break; case 'citymapper': - url = `${prefixes.citymapper}directions?endcoord=${latlng}`; + if (address) { + url = `${prefixes.citymapper}directions?endname=${address}`; + } else { + url = `${prefixes.citymapper}directions?endcoord=${latlng}`; - if (title) { - url += `&endname=${encodedTitle}`; - } + if (title) { + url += `&endname=${encodedTitle}`; + } - if (useSourceDestiny) { - url += `&startcoord=${sourceLatLng}`; + if (useSourceDestiny) { + url += `&startcoord=${sourceLatLng}`; + } } break; case 'uber': - url = `${prefixes.uber}?action=setPickup&dropoff[latitude]=${lat}&dropoff[longitude]=${lng}`; - - if (title) { - url += `&dropoff[nickname]=${encodedTitle}`; - } + if (address) { + url = `${prefixes.uber}?action=setPickup&pickup=my_location&dropoff=${address}`; + } else { + url = `${prefixes.uber}?action=setPickup&dropoff[latitude]=${lat}&dropoff[longitude]=${lng}`; - url += useSourceDestiny - ? `&pickup[latitude]=${sourceLat}&pickup[longitude]=${sourceLng}` - : '&pickup=my_location'; + if (title) { + url += `&dropoff[nickname]=${encodedTitle}`; + } + url += useSourceDestiny + ? `&pickup[latitude]=${sourceLat}&pickup[longitude]=${sourceLng}` + : '&pickup=my_location'; + } break; case 'lyft': - url = `${prefixes.lyft}ridetype?id=lyft&destination[latitude]=${lat}&destination[longitude]=${lng}`; + if (address) { + url = `${prefixes.lyft}ridetype?id=lyft&destination[address]=${address}`; + } else { + url = `${prefixes.lyft}ridetype?id=lyft&destination[latitude]=${lat}&destination[longitude]=${lng}`; - if (useSourceDestiny) { - url += `&pickup[latitude]=${sourceLat}&pickup[longitude]=${sourceLng}`; + if (useSourceDestiny) { + url += `&pickup[latitude]=${sourceLat}&pickup[longitude]=${sourceLng}`; + } } - break; case 'transit': - url = `${prefixes.transit}directions?to=${latlng}`; + if (address) { + url = `${prefixes.transit}directions?destination=${address}`; + } else { + url = `${prefixes.transit}directions?to=${latlng}`; + } if (useSourceDestiny) { url += `&from=${sourceLatLng}`; } break; case 'truckmap': - url = `https://truckmap.com/place/${lat},${lng}`; + if (address) { + // Constructed from documentation from https://truckmap.com/solutions/developer + url = `https://truckmap.com/place/${address}`; + } else { + url = `https://truckmap.com/place/${lat},${lng}`; - if (useSourceDestiny) { - url = `https://truckmap.com/route/${sourceLat},${sourceLng}/${lat},${lng}`; + if (useSourceDestiny) { + url = `https://truckmap.com/route/${sourceLat},${sourceLng}/${lat},${lng}`; + } } break; case 'waze': - url = `${prefixes.waze}?ll=${latlng}&navigate=yes`; - if (title) { - url += `&q=${encodedTitle}`; + if (address) { + url = `${prefixes.waze}?q=${address}`; + } else { + url = `${prefixes.waze}?ll=${latlng}&navigate=yes`; + if (title) { + url += `&q=${encodedTitle}`; + } } break; case 'yandex': - url = `${prefixes.yandex}build_route_on_map?lat_to=${lat}&lon_to=${lng}`; + if (address) { + url = `${prefixes.yandex}?text=${address}`; + } else { + url = `${prefixes.yandex}build_route_on_map?lat_to=${lat}&lon_to=${lng}`; - if (useSourceDestiny) { - url += `&lat_from=${sourceLat}&lon_from=${sourceLng}`; + if (useSourceDestiny) { + url += `&lat_from=${sourceLat}&lon_from=${sourceLng}`; + } } break; case 'moovit': - url = `${prefixes.moovit}?dest_lat=${lat}&dest_lon=${lng}`; + if (address) { + url = `${prefixes.moovit}?dest_name=${address}`; + } else { + url = `${prefixes.moovit}?dest_lat=${lat}&dest_lon=${lng}`; - if (title) { - url += `&dest_name=${encodedTitle}`; - } + if (title) { + url += `&dest_name=${encodedTitle}`; + } - if (useSourceDestiny) { - url += `&orig_lat=${sourceLat}&orig_lon=${sourceLng}`; + if (useSourceDestiny) { + url += `&orig_lat=${sourceLat}&orig_lon=${sourceLng}`; + } } break; case 'yandex-taxi': - url = `${prefixes['yandex-taxi']}route?end-lat=${lat}&end-lon=${lng}&appmetrica_tracking_id=1178268795219780156`; - + if (address) { + throw new MapsException( + 'yandex-taxi does not support passing the address or has not been implemented yet.', + ); + } else { + url = `${prefixes['yandex-taxi']}route?end-lat=${lat}&end-lon=${lng}&appmetrica_tracking_id=1178268795219780156`; + } break; case 'yandex-maps': - url = `${prefixes['yandex-maps']}?pt=${lng},${lat}`; - + if (address) { + url = `${prefixes['yandex-maps']}?text=${address}`; + } else { + url = `${prefixes['yandex-maps']}?pt=${lng},${lat}`; + } break; case 'kakaomap': - url = `${prefixes.kakaomap}look?p=${latlng}`; + if (address) { + url = `${prefixes.kakaomap}route?ep=${address}`; + } else { + url = `${prefixes.kakaomap}look?p=${latlng}`; - if (useSourceDestiny) { - url = `${prefixes.kakaomap}route?sp=${sourceLat},${sourceLng}&ep=${latlng}&by=CAR`; + if (useSourceDestiny) { + url = `${prefixes.kakaomap}route?sp=${sourceLat},${sourceLng}&ep=${latlng}&by=CAR`; + } } break; case 'tmap': - url = `${prefixes.tmap}viewmap?x=${lng}&y=${lat}`; + if (address) { + url = `${prefixes.tmap}search?name=${address}`; + } else { + url = `${prefixes.tmap}viewmap?x=${lng}&y=${lat}`; - if (useSourceDestiny) { - url = `${prefixes.tmap}route?startx=${sourceLng}&starty=${sourceLat}&goalx=${lng}&goaly=${lat}`; + if (useSourceDestiny) { + url = `${prefixes.tmap}route?startx=${sourceLng}&starty=${sourceLat}&goalx=${lng}&goaly=${lat}`; + } } break; case 'mapycz': - url = `${prefixes.mapycz}www.mapy.cz/zakladni?x=${lng}&y=${lat}&source=coor&id=${lng},${lat}`; - + if (address) { + url = `${prefixes.mapycz}www.mapy.cz/zakladni?q=${address}`; + } else { + url = `${prefixes.mapycz}www.mapy.cz/zakladni?x=${lng}&y=${lat}&source=coor&id=${lng},${lat}`; + } break; case 'maps-me': - url = `${prefixes['maps-me']}route?sll=${sourceLat},${sourceLng}&saddr= &dll=${lat},${lng}&daddr=${title}&type=vehicle`; - + if (address) { + url = `${prefixes['maps-me']}?q=${address}`; + } else { + url = `${prefixes['maps-me']}route?sll=${sourceLat},${sourceLng}&saddr= &dll=${lat},${lng}&daddr=${title}&type=vehicle`; + } break; case 'osmand': - url = isIOS - ? `${prefixes.osmand}?lat=${lat}&lon=${lng}` - : `${prefixes.osmand}?q=${lat},${lng}`; - + if (address) { + url = `${prefixes.osmand}show_map?addr=${address}`; + } else { + url = isIOS + ? `${prefixes.osmand}?lat=${lat}&lon=${lng}` + : `${prefixes.osmand}?q=${lat},${lng}`; + } break; case 'gett': - url = `${prefixes.gett}order?pickup=my_location&dropoff_latitude=${lat}&dropoff_longitude=${lng}`; - + if (address) { + throw new MapsException( + 'gett does not support passing the address or has not been implemented yet.', + ); + } else { + url = `${prefixes.gett}order?pickup=my_location&dropoff_latitude=${lat}&dropoff_longitude=${lng}`; + } break; case 'navermap': - url = `${prefixes.navermap}map?lat=${lat}&lng=${lng}&appname=${naverCallerName}`; + if (address) { + url = `${prefixes.navermap}search?query=${address}`; + } else { + url = `${prefixes.navermap}map?lat=${lat}&lng=${lng}&appname=${naverCallerName}`; - if (useSourceDestiny) { - url = `${prefixes.navermap}route?slat=${sourceLat}&slng=${sourceLng}&dlat=${lat}&dlng=${lng}&appname=${naverCallerName}`; + if (useSourceDestiny) { + url = `${prefixes.navermap}route?slat=${sourceLat}&slng=${sourceLng}&dlat=${lat}&dlng=${lng}&appname=${naverCallerName}`; + } } break; case 'dgis': - url = `${prefixes.dgis}routeSearch/to/${lng},${lat}/go`; + if (address) { + url = `${prefixes.dgis}?q=${address}`; + } else { + url = `${prefixes.dgis}routeSearch/to/${lng},${lat}/go`; - if (useSourceDestiny) { - url = `${prefixes.dgis}routeSearch/to/${lng},${lat}/from/${sourceLng},${sourceLat}/go`; + if (useSourceDestiny) { + url = `${prefixes.dgis}routeSearch/to/${lng},${lat}/from/${sourceLng},${sourceLat}/go`; + } } break; case 'liftago': - url = `${prefixes.liftago}order?destinationLat=${lat}&destinationLon=${lng}`; + if (address) { + throw new MapsException( + 'liftago does not support passing the address or has not been implemented yet.', + ); + } else { + url = `${prefixes.liftago}order?destinationLat=${lat}&destinationLon=${lng}`; - if (title) { - url += `&destinationName=${encodedTitle}`; - } + if (title) { + url += `&destinationName=${encodedTitle}`; + } - if (useSourceDestiny) { - url += `&pickupLat=${sourceLat}&pickupLon=${sourceLng}`; + if (useSourceDestiny) { + url += `&pickupLat=${sourceLat}&pickupLon=${sourceLng}`; + } } break; case 'petalmaps': - url = `${prefixes.petalmaps}navigation?daddr=${lat},${lng}`; + if (address) { + // Got this from this documentation https://developer.huawei.com/consumer/en/doc/HMSCore-Guides/petal-maps-introduction-0000001059189679 + url = `${prefixes.petalmaps}textSearch?text=${address}`; + } else { + url = `${prefixes.petalmaps}navigation?daddr=${lat},${lng}`; - if (useSourceDestiny) { - url += `&saddr=${sourceLat},${sourceLng}`; + if (useSourceDestiny) { + url += `&saddr=${sourceLat},${sourceLng}`; + } } break; case 'sygic': const sygicDirectionsMode = getDirectionsModeSygic(directionsMode); - url = `${prefixes.sygic}coordinate|${lng}|${lat}|`; + if (address) { + throw new MapsException( + 'sygic does not support passing the address or has not been implemented yet.', + ); + } else { + url = `${prefixes.sygic}coordinate|${lng}|${lat}|`; + } url += sygicDirectionsMode ? `${sygicDirectionsMode}` : ''; break; }