I hate google maps

So i decided to do something about it

Google maps is ubiquitous nowadays: Everyone is using it. It is the de-facto1 standard in world-wide digital mapping.

This, is obviously not without praise: for years it has lead and innovated the space of online mapping providing lots of useful features, like the ability to switch on-the-fly between a political, topographic and satellite view, providing opening/closing times of locations to users, and MANY more, that i cannot possibly list here.

On the other end, your part of the deal has always been getting your every movement tracked and stored in some google server on the other side of the world. Most people are fine with all of this, as i've been for the greater part of my life.

But, as with anything in this techno-feudalist system, simply having access to everyone's whereabouts at any moment in time is not enough to feed the shareholders, they started selling products and advertisements live on the map.

The alternatives

With everyone else using it, and its «convenience», it is not very simple to ditch google maps and start using alternative cartography software, such as OpenStreetMap-based alternatives.

It is so ubiquitous that despite there being an open URI format2 that is supported on Android, virtually all applications use google maps URLs to share locations. Manually chopping up URLs to extract the coordinates is not fun at all, and recently became impossible after google laid some changes forcing users to be tracked from within their social circles.

This is a regular google maps URL3:

https://maps.google.com/maps/@46.2246019,9.5408571,153m/data=!3m1!1e3?entry=ttu&g_ep=EgoydDI2MDUAuS5wIKXMaZoASAFQAw%3D%3D

You can clearly see the coordinates in the URL above.

A while ago, they started redirecting users from maps.google.com to google.com. This makes it so if you're on the website and have shared your location to google maps through your browser, now anytime you're browsing the web, google maps has the permission to snoop on your location.

https://www.google.com/maps/@46.2246019,9.5408571,153m/data=!3m1!1e3?entry=ttu&g_ep=EgoydDI2MDUAuS5wIKXMaZoASAFQAw%3D%3D

These are the new share google maps URLs (anonimized):

https://maps.app.goo.gl/teQpuRxSWa8MCLkw6

Notice how these last URL formats do not contain the coordinates, but are instead a shortened link on google's servers.

Ignoring the longevity concerns in using link shorteners, this makes it impossible to retrieve the location that got shared without connecting to google's servers, and given that it's a de-facto standard, users of alternative cartography apps have to interface with it.

Or do they?

What to do

On Android at least, any application can register an intent receiver for any URI, allowing it to launch when the user clicks on one.

Since Android 6 though, applications can register as an "official" app for any given domain. This makes it a bit more complicated for us, but there is a way to do this.

There's a menu in Settings -> Apps -> Other settings -> Default apps -> Opening links that allows one to choose which applications open on any given URI.

We can now make an app that opens on any of these «proprietary» map URIs, extracts the coordinates from it and opens the application picker using a geo: URI2, to let the user pick which app they want to use.

Let's briefly go over how it works in practice:

We define in the application's manifest, a blank hidden Activity that listens on the URI we need:

<activity
    android:name=".UriHandlerActivity"
    android:exported="true"
    android:label="@string/title_activity_uri_handler"
    android:theme="@android:style/Theme.NoDisplay"
    android:excludeFromRecents="true">

    <intent-filter android:label="@string/filter_view_http">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        
        <data android:scheme="https" android:host="google.com" android:pathPrefix="/maps"/>
        
        <data android:scheme="https" android:host="openstreetmap.org" android:pathPrefix="/"/>
    </intent-filter>
</activity>

Then, when our activity starts, we grab the URI that the user clicked on, extract the coordinates and create a geo: URI:

class UriHandlerActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        val action: String? = intent?.action
        var data: Uri? = intent?.data

        val coords = parseURI(URLDecoder.decode(data.toString()))
        // if we were successful, generate the geo URI
        if (coords != null) {
            val geouri = "geo:${coords.first},${coords.second}".toUri()

            val i = Intent(Intent.ACTION_VIEW).setData(geouri)
            finish()
	        startActivity(i)
            // close the activity so there isn't a transparent activity lingering
            return
        }
}

To grab the coordinates from the URIs, some regex will do:

fun parseURI(uri: String): Pair<Number, Number>? {
    val patterns = listOf(
        "google\\.com/@(?<lat>-?\\d+\\.\\d+),(?<lon>-?\\d+\\.\\d+)",
        
        "openstreetmap\\.org/search\\?.*lat=(?<lat>-?\\d+\\.\\d+)&lon=(?<lon>-?\\d+\\.\\d+)"
    )

    for (patternStr in patterns) {
        val regex = patternStr.toRegex()

        val match = regex.find(uri)
        if (match != null) {
            val lat = match.groups["lat"]?.value
            val lon = match.groups["lon"]?.value

            if (!(lat.isNullOrEmpty() && lon.isNullOrEmpty())) {
                return try {
                    Pair(lat!!.toDouble(), lon!!.toDouble())
                } catch (ex: NumberFormatException) {
                    null
                } catch (ex: AssertionError) {
                    null
                }
            }
        }
    }

    return null
}

Expanding from this, we can add as many URIs as we want, provided we add them to our manifest and create a regex for them.

As for the google tracking URI, currently there's no other way but to connect to their server, wait to be redirected and grab the new URI:

suspend fun fetchRedirectUrl(data: String): Uri? {
    try {
        val connection = URL(data).openConnection()
        val originalUrl = connection.url

        connection.connect()
        val inputStream = connection.getInputStream()
        // close the input stream, we dont need it anymore
        inputStream.close()

        val currentUrl = connection.url

        if (currentUrl == originalUrl) {
            return null
        } else {
            // weird URL -> URI conversion
	    return currentUrl.toString().toUri()
        }
    } catch (e: Exception) {
        e.printStackTrace()
        return null
    }
}

After all this, if we enable the URIs in system settings, the app works, turning any «proprietary» map URI into an open one.

If you want to read the full source code, you can find it here.

I've added URIs from virtually all apps and websites, but it is still possible i'm missing some.

As soon as i get around to making a proper release for the app, i'll update this page accordingly.


  1. Virtually all apps use google URIs, even privacy-minded ones (e.g. Signal)

  2. geo: URI is defined as RFC5870 2

  3. For unknown reasons, there are a few other URL types that are virtually identical to the first one (because of course, it's google)