开发者

How to add your application to the "share this place" list in Google maps

开发者 https://www.devze.com 2023-01-12 03:02 出处:网络
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, cr

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".

0

精彩评论

暂无评论...
验证码 换一张
取 消