Google maps now offers a way to "share a place" with what appears to be a predefined list of sources. When users search for a place on Google Maps, wheth开发者_开发百科er it's a specific address, cross-street, or restaurant name, there's a new button called "share this place" that posts the location info to Google Buzz, Facebook, Twitter, or via e-mail or SMS. I would like to either have my application included in this list or determine how to obtain the lat/lon of that selected location. Does anyone have any ideas?
I figured it out. Here's a sample manifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yourapp" android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".YourApp" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"></action>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="4" />
</manifest>
Simply add the SEND intent filter just as it is above to your activity. The "share this place" simply performs a "SEND" intent with the mime type of "text/plain". If you register an intent filter for that type, then your app will show up in the list. Mine did. =)
Did you figure out how to get the GPS coordinates? How?
I would also want to know how you got the GPS coordinates from it.
Here is how you can try to get coordinates:
As @HenryAdamsJr said you need to declare an <intent-filter>
first, then with the intent the app will get some text which looks like this:
Letícia Bronzoni Fotografi
+46 73 255 28 32
https://maps.app.goo.gl/kijJBem2FGkj2S99A
As you can see there is an url which when is being invoked redirects to something like:
1. https://www.google.com/maps/place/Grodhavet,+Karlbergs+strand+12,+171+73+Solna,+Sweden/data=!4m2!3m1!1s0x465f9d8ec1ecc2c3:0xbc9f46e4d42faea4?utm_source=mstt_1&entry=gps&g_ep=IIgnKgA%3D&ucbcb=1
2. https://www.google.com/maps/place/Karlav%C3%A4gen+32,+114+31+Stockholm,+Sweden/data=!4m6!3m5!1s0x465f9d4383805daf:0xffd88b394b6ac3b8!7e2!8m2!3d59.33992!4d18.0729719?utm_source=mstt_1&entry=gps&g_ep=CAESCTExLjYwLjcwMxgAIIgnKgBCAlJV&ucbcb=1
3. https://www.google.com/maps/place/59.360982,18.069179/data=!4m6!3m5!1s0!7e2!8m2!3d59.360981499999994!4d18.0691788?utm_source=mstt_1&entry=gps&g_ep=CAESCTExLjYwLjcwMxgAIIgnKgBCAlJV&ucbcb=1
Case 1. The worst one.
It happens when a POI was selected on GMaps. The resulting url has only a more detailed address and it needs to be looked up with a Geocoder or so (see example below).
Case 2. The best one!
It happens when a random point was selected but then GMaps converted it to an address. The resulting url has "data=....!3dXXXX!4dYYYY" part with coordinates.
Case 3. The one which is ok.
It happens when a random point was selected and GMaps could not figure out any address. The resulting url has "place/XXXX,YYYY" part with coordinates.
Here is an how I was handling it:
// Somewhere in Activity::onCreate or ::onNewIntent
override fun onCreate(savedInstanceState: Bundle?) {
.....
val linkHash = IncomingShareFromGoogleMaps.getGoogleMapsShareLinkHash(intent)
}
// Then somewhere in a ViewModel or so
val shareHandler: IncomingShareFromGoogleMaps = ...
fun processShare(linkHash:String) {
viewModelScope.launch {
val coordinates = shareHandler.shortenedUrlToCoordinates(linkHash)
// Profit!
}
}
class IncomingShareFromGoogleMaps @Inject constructor(
private val errorReporter: ErrorReporter,
private val geocoder: Geocoder,
) {
companion object {
private const val TIMEOUT_CONNECT_MS = 5_000
private const val TIMEOUT_WRITE_MS = 5_000
private const val TIMEOUT_READ_MS = 5_000
private const val GOOGLE_SHORT_URL_PREFIX = "https://maps.app.goo.gl/"
fun getGoogleMapsShareLinkHash(intent: Intent): String? =
intent
.takeIf { it.action == "android.intent.action.SEND" }?.extras
?.getString("android.intent.extra.TEXT")
?.takeUnless { it.isBlank() }
?.split("\n")
?.lastOrNull()
?.takeIf { it.startsWith(GOOGLE_SHORT_URL_PREFIX) }
?.removePrefix(GOOGLE_SHORT_URL_PREFIX)
}
suspend fun shortenedUrlToCoordinates(shortenedUrl: String) = withContext(Dispatchers.IO) {
val resultingUrl =
createHttpClient()
.performGetRequest(GOOGLE_SHORT_URL_PREFIX + shortenedUrl)
.request.url.toString()
return@withContext tryToGetCoordinates(resultingUrl)
}
/**
* Takes a url which was returned by Google.Maps and tries to extract some coordinates.
* Example of the `resultingUrl`:
* https://www.google.com/maps/place/Grodhavet,+Karlbergs+strand+12,+171+73+Solna,+Sweden/data=!4m2!3m1!1s0x465f9d8ec1ecc2c3:0xbc9f46e4d42faea4?utm_source=mstt_1&entry=gps&g_ep=IIgnKgA%3D&ucbcb=1
* https://www.google.com/maps/place/Karlav%C3%A4gen+32,+114+31+Stockholm,+Sweden/data=!4m6!3m5!1s0x465f9d4383805daf:0xffd88b394b6ac3b8!7e2!8m2!3d59.33992!4d18.0729719?utm_source=mstt_1&entry=gps&g_ep=CAESCTExLjYwLjcwMxgAIIgnKgBCAlJV&ucbcb=1
* https://www.google.com/maps/place/59.360982,18.069179/data=!4m6!3m5!1s0!7e2!8m2!3d59.360981499999994!4d18.0691788?utm_source=mstt_1&entry=gps&g_ep=CAESCTExLjYwLjcwMxgAIIgnKgBCAlJV&ucbcb=1
* So it tries first to take coordinates from "data=....!3dXXX!4dYYY" part of the url,
* then from "place/XXX,YYY" part of the url,
* then it tries to look up the address from "place/" part with Mapbox.Geocoder SDK.
*/
private fun tryToGetCoordinates(resultingUrl: String) =
tryToGetCoordinatesFromDataSection(resultingUrl)
?: tryToGetCoordinatesFromPlaceSection(resultingUrl)
?: tryToGetCoordinatesByAddress(resultingUrl)
private fun tryToGetCoordinatesFromPlaceSection(resultingUrl: String) =
try {
"""place/(\d+\.\d+),(\d+\.\d+)""".toRegex()
.find(resultingUrl)?.groupValues
?.takeIf { it.size == 3 }
?.let { list ->
Coordinates(
longitude = list[2].toDouble(),
latitude = list[1].toDouble()
)
}
} catch (e: NumberFormatException) {
errorReporter.reportError(e, "shortenedUrlToCoordinates, failed")
null
}
private fun tryToGetCoordinatesFromDataSection(resultingUrl: String) =
try {
val lat = """data=\S*!3d(\d+\.\d+)""".toRegex()
.find(resultingUrl)?.groupValues
?.takeIf { it.size > 1 }
?.get(1)
?.toDouble()
val lng = """data=\S*!4d(\d+\.\d+)""".toRegex()
.find(resultingUrl)?.groupValues
?.takeIf { it.size > 1 }
?.get(1)
?.toDouble()
when {
lat != null && lng != null -> Coordinates(latitude = lat, longitude = lng)
else -> null
}
} catch (e: NumberFormatException) {
errorReporter.reportError(e, "tryToGetCoordinatesFromDataSection, failed")
null
}
private fun tryToGetCoordinatesByAddress(resultingUrl: String): Coordinates? =
"""place/(\S*)/data""".toRegex()
.find(resultingUrl)?.groupValues
?.takeIf { it.size == 2 }?.get(1)
?.let { geocoder.getCoordinatesForAddress(URLDecoder.decode(it, "UTF-8")) }
private fun createHttpClient() =
OkHttpClient.Builder()
.connectTimeout(TIMEOUT_CONNECT_MS.toLong(), TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT_WRITE_MS.toLong(), TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT_READ_MS.toLong(), TimeUnit.MILLISECONDS)
.build()
private fun OkHttpClient.performGetRequest(url: String): Response =
newCall(
Request.Builder()
.url(url)
.get()
.build()
).execute()
}
And I have been using Mapbox.Geocoder since my project is based on it. Here is my implementation of the Geocoder:
class GeocoderImpl @Inject constructor(
private val runtime: RuntimeMapInfo,
) : Geocoder {
override fun getCoordinatesForAddress(address: String): Coordinates? =
MapboxGeocoding.builder()
.accessToken(runtime.mapboxAccessToken())
.query(address)
.build()
.executeCall()
.body()
?.features()
?.preferPoiOrAddress()
?.geometry()
?.let { (it as? Point) }
?.toCoordinates()
/**
* Takes first "placeType=[poi]" or "placeType=[address]" otherwise any first feature.
*/
private fun List<CarmenFeature>.preferPoiOrAddress(): CarmenFeature? =
firstOrNull { feature ->
feature.placeType()?.let {
it.contains("address") || it.contains("poi")
} == true
} ?: firstOrNull()
}
Disclaim: I understad This is not the best solution, but it works. I have spent a day trying to figure out it. Maybe it will help someone to save a day :-)
PS:
Another, more hacky way could be ->
Ofc Ggoole.Maps knows the best how to unfold it's own shortened Urls. So if to rise a transparent webview and invoke there the shorted url then the one of redirects will be like "Case 3".
精彩评论