# Compass

[![Maven Central Version](https://img.shields.io/maven-central/v/dev.jordond.compass/core)](https://central.sonatype.com/namespace/dev.jordond.compass)

## Overview

Compass is a Kotlin Multiplatform library location toolkit. It provides a set of tools for working with location data, including geocoding, reverse geocoding, and more. The library is built with a focus on simplicity and ease of use, providing a straightforward API for geocoding operations.

### Features

* [https://github.com/jordond/compass/tree/main/docs/broken-reference/README.md](https://github.com/jordond/compass/tree/main/docs/broken-reference/README.md "mention")
  * Native support for Android and iOS
  * Support for other platforms by using web based APIs
    * Included support for [Google Maps ](https://developers.google.com/maps/documentation/geocoding)and [Mapbox](https://docs.mapbox.com/#search)
    * See [Web API Service](/geocoding/web-api-service)
* [https://github.com/jordond/compass/tree/main/docs/broken-reference/README.md](https://github.com/jordond/compass/tree/main/docs/broken-reference/README.md "mention")
  * Mobile support (Android/iOS)
  * Browser [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API)
  * Built-in permission handling
* [https://github.com/jordond/compass/tree/main/docs/broken-reference/README.md](https://github.com/jordond/compass/tree/main/docs/broken-reference/README.md "mention")
  * Mobile support (Android/iOS) via Geocoder
  * Support for other platforms by using web based APIs
    * Included support for [Google Maps ](https://developers.google.com/maps/documentation/geocoding)and [Mapbox](https://docs.mapbox.com/#search) via the Decoder API
    * Google Places API support is planned

### Easy to use

Compass has a simple API:

```kotlin
suspend fun lookupCoordinates(latitude: Double, longitude: Double): Place? {
    val geocoder = Geocoder()
    val result: GeocoderResult<Place> = geocoder.reverse(latitude, longitude)
    return result.getOrNull()
}
```

The above `Geocoder()` function is one of many extension functions included to make your life easier. Behind the scenes a `PlatformGeocoder` is used to do all the heavy lifting, and can be created and provided on a per-platform basis.

You can learn more about geocoding here: [Geocoding Overview](/geocoding/overview)

{% hint style="info" %}
The above `Geocoder()`extension function is from the Android/iOS only artifact. If you plan on supporting other platforms, check out [Mixed platforms](/setup/mixed-platforms).
{% endhint %}

### Get Started

Here is a few good starting points to start using Compass!

{% content-ref url="<https://github.com/jordond/compass/tree/main/docs/broken-reference/README.md>" %}
<https://github.com/jordond/compass/tree/main/docs/broken-reference/README.md>
{% endcontent-ref %}

{% content-ref url="/pages/dmKKSyWOfobgscWGJb6V" %}
[Artifacts](/supported-platforms)
{% endcontent-ref %}

{% content-ref url="/pages/UzCc7xHBXvIeztkHBalu" %}
[All Dependencies](/setup/add-dependencies)
{% endcontent-ref %}


# Artifacts

Compass publishes the following artifacts that support these platforms:

<table data-full-width="true"><thead><tr><th width="448">Artifact</th><th width="95" data-type="checkbox">Android</th><th width="60" data-type="checkbox">iOS</th><th width="67" data-type="checkbox">JVM</th><th width="92" data-type="checkbox">MacOS</th><th data-type="checkbox">JS/WASM</th></tr></thead><tbody><tr><td><code>geocoder</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>geocoder-mobile</code></td><td>true</td><td>true</td><td>false</td><td>false</td><td>false</td></tr><tr><td><code>geocoder-web</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>geocoder-web-mapbox</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>geocoder-web-googlemaps</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>geolocation</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>geolocation-mobile</code></td><td>true</td><td>true</td><td>false</td><td>false</td><td>false</td></tr><tr><td><code>geolocation-android-gms</code></td><td>true</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td><code>geolocation-browser</code></td><td>false</td><td>false</td><td>false</td><td>false</td><td>true</td></tr><tr><td><code>autocomplete</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>autocomplete-mobile</code></td><td>true</td><td>true</td><td>false</td><td>false</td><td>false</td></tr><tr><td><code>autocomplete-geocoder-mapbox</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>autocomplete-geocoder-googlemaps</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>autocomplete-web</code></td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><code>permissions-mobile</code></td><td>true</td><td>true</td><td>false</td><td>false</td><td>false</td></tr></tbody></table>

{% hint style="info" %}
If you plan on using Compass in a project that targets both mobile and non-mobile platforms (desktop, browser, etc). Then you will need to make sure use `expect/actual` to provide an implementation for each platform. See [Mixed platforms](/setup/mixed-platforms).
{% endhint %}

Go to the [All Dependencies](/setup/add-dependencies) page to learn how to add them to your project.


# Roadmap

These items are in no particular order.

* Add support for different languages
  * Currently only English is supported
* Autocomplete support from third party APIs like Google's Places API


# All Dependencies

### Add Maven

If you don't already have Maven Central in your repositories yet, add the following:

```kts
// settings.gradle.kts

pluginManagement {
    repositories {
        mavenCentral()
        // etc
    }
}

dependencyResolutionManagement {
    repositories {
        mavenCentral()
        // etc
    }
}
```

### Add Compass dependencies

[![Maven Central Version](https://img.shields.io/maven-central/v/dev.jordond.compass/core)](https://central.sonatype.com/namespace/dev.jordond.compass)

{% tabs %}
{% tab title="Version Catalog" %}

1. Declare the dependencies in your `libs.versions.toml`:

{% code fullWidth="false" %}

```toml
[versions]
compass = "4.0.0"

[libraries]
compass-autocomplete = { module = "dev.jordond.compass:autocomplete", version.ref = "compass" }
compass-autocomplete-mobile = { module = "dev.jordond.compass:autocomplete-mobile", version.ref = "compass" }
compass-autocomplete-web = { module = "dev.jordond.compass:autocomplete-web", version.ref = "compass" }
compass-autocomplete-geocoder-googlemaps = { module = "dev.jordond.compass:autocomplete-geocoder-googlemaps", version.ref = "compass" }
compass-autocomplete-geocoder-mapbox = { module = "dev.jordond.compass:autocomplete-geocoder-mapbox", version.ref = "compass" }
compass-geocoder = { module = "dev.jordond.compass:geocoder", version.ref = "compass" }
compass-geocoder-mobile = { module = "dev.jordond.compass:geocoder-mobile", version.ref = "compass" }
compass-geocoder-web = { module = "dev.jordond.compass:geocoder-web", version.ref = "compass" }
compass-geocoder-web-googlemaps = { module = "dev.jordond.compass:geocoder-web-googlemaps", version.ref = "compass" }
compass-geocoder-web-mapbox = { module = "dev.jordond.compass:geocoder-web-mapbox", version.ref = "compass" }
compass-geocoder-web-opencage = { module = "dev.jordond.compass:geocoder-web-opencage", version.ref = "compass" }
compass-geolocation = { module = "dev.jordond.compass:geolocation", version.ref = "compass" }
compass-geolocation-mobile = { module = "dev.jordond.compass:geolocation-mobile", version.ref = "compass" }
compass-geolocation-android-gms = { module = "dev.jordond.compass:geolocation-android-gms", version.ref = "compass" }
compass-geolocation-browser = { module = "dev.jordond.compass:geolocation-browser", version.ref = "compass" }
compass-permissions-mobile = { module = "dev.jordond.compass:permissions-mobile", version.ref = "compass" }
```

{% endcode %}

2. Add the dependencies to your `build.gradle.kts`:

{% code fullWidth="false" %}

```kts
kotlin {
    sourceSets {
        commonMain {
            dependencies {
                // Geocoding
                implementation(libs.compass.geocoder)

                // To use geocoding you need to use one or more of the following

                // Optional - Geocoder support for only iOS and Android
                implementation(libs.compass.geocoder.mobile)

                // Optional - Geocoder support for all platforms, but requires an API key from the service
                implementation(libs.compass.geocoder.web.googlemaps)
                implementation(libs.compass.geocoder.web.mapbox)
                implementation(libs.compass.geocoder.web.opencage)

                // Optional - If you want to create your own geocoder implementation
                implementation(libs.compass.geocoder.web)
                
                // Geolocation
                implementation(libs.compass.geolocation)

                // To use geolocation you need to use one or more of the following
                
                // Optional - Geolocation support for only iOS and Android
                implementation(libs.compass.geolocation.mobile)
                
                // Optional - Geolocation support for JS/WASM Browser Geolocation API
                implementation(libs.compass.geolocation.browser)

                // Autocomplete
                implementation(libs.compass.autocomplete)

                // Optional - Autocomplete support for only iOS and Android using native Geocoder
                implementation(libs.compass.autocomplete.mobile)

                // Optional - Autocomplete support for all platforms, using services Geocoder APIs
                implementation(libs.compass.autocomplete.geocoder.googlemaps)
                implementation(libs.compass.autocomplete.geocoder.mapbox)

                // Optional - If you want to create your own geocoder implementation
                implementation(libs.compass.autocomplete.web)

                // Optional - Location permissions for mobile
                implementation(libs.compass.permissions.mobile)
            }
        }

        androidMain {
            dependencies {
                // Optional - Use Google Play Services fused location on Android.
                // Requires the proprietary Play Services, see the note below.
                implementation(libs.compass.geolocation.android.gms)
            }
        }
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Dependencies" %}

```kts
kotlin {
    sourceSets {
        commonMain {
            dependencies {
                val compassVersion = "4.0.0"

                // Geocoding
                implementation("dev.jordond.compass:geocoder:$compassVersion")

                // To use geocoding you need to use one or more of the following

                // Optional - Geocoder support for only iOS and Android
                implementation("dev.jordond.compass:geocoder-mobile:$compassVersion")

                // Optional - Geocoder support for all platforms, but requires an API key from the service
                implementation("dev.jordond.compass:geocoder-web-googlemaps:$compassVersion")
                implementation("dev.jordond.compass:geocoder-web-mapbox:$compassVersion")
                implementation("dev.jordond.compass:geocoder-web-opencage:$compassVersion")

                // Optional - If you want to create your own geocoder implementation
                implementation("dev.jordond.compass:geocoder-web:$compassVersion")
                
                // Geolocation
                implementation("dev.jordond.compass:geolocation:$compassVersion")

                // To use geolocation you need to use one or more of the following
                
                // Optional - Geolocation support for only iOS and Android
                implementation("dev.jordond.compass:geolocation-mobile:$compassVersion")
                
                // Optional - Geolocation support for JS/WASM Browser Geolocation API
                implementation("dev.jordond.compass:geolocation-browser:$compassVersion")

                // Autocomplete
                implementation("dev.jordond.compass:autocomplete:$compassVersion")

                // Optional - Autocomplete support for only iOS and Android using native Geocoder
                implementation("dev.jordond.compass:autocomplete-mobile:$compassVersion")

                // Optional - Autocomplete support for all platforms, using services Geocoder APIs
                implementation("dev.jordond.compass:autocomplete-geocoder-googlemaps:$compassVersion")
                implementation("dev.jordond.compass:autocomplete-geocoder-mapbox:$compassVersion")

                // Optional - If you want to create your own geocoder implementation
                implementation("dev.jordond.compass:autocomplete-web:$compassVersion")

                // Optional - Location permissions for mobile (Android/iOS)
                implementation("dev.jordond.compass:permissions-mobile:$compassVersion")
            }
        }

        androidMain {
            dependencies {
                // Optional - Use Google Play Services fused location on Android.
                // Requires the proprietary Play Services, see the note below.
                implementation("dev.jordond.compass:geolocation-android-gms:$compassVersion")
            }
        }
    }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
If you plan on using Compass in a project that targets both mobile and non-mobile platforms (desktop, browser, etc). Then you will need to make sure use `expect/actual` to provide the implementation for each platform. See [Mixed platforms](/setup/mixed-platforms).
{% endhint %}

{% hint style="warning" %}
`geolocation-android-gms` depends on Google Play Services, and it is the only one that goes in an Android source set rather than `commonMain`. See [Android / iOS](/geolocation/android-ios) for which one you want.
{% endhint %}


# Android / iOS

### Dependencies

If you only need to support mobile (Android and iOS) add the following dependencies:

```toml
# gradle/libs.versions.toml

[libraries]
compass-geocoder = { module = "dev.jordond.compass:geocoder", version.ref = "compass" }
compass-geocoder-mobile = { module = "dev.jordond.compass:geocoder-mobile", version.ref = "compass" }
compass-geolocation = { module = "dev.jordond.compass:geolocation", version.ref = "compass" }
compass-geolocation-mobile = { module = "dev.jordond.compass:geolocation-mobile", version.ref = "compass" }
```

Then you can add them to your `build.gradle.kts`:

```kotlin
kotlin {
    sourceSets {
        commonMain {
            dependencies {
                // Geocoding
                implementation(libs.compass.geocoder)
                implementation(libs.compass.geocoder.mobile)
                
                // Geolocation
                implementation(libs.compass.geolocation)
                implementation(libs.compass.geolocation.mobile)
            }
        }  
    }
}
```

### Geocoding Setup

No location permissions are needed for geocoding operations. Compass will use the built-in Android or iOS services. These services can be rate-limited, so make sure you aren't calling them too frequently. Check out the [Android documentation](https://developer.android.com/reference/android/location/Geocoder) as well as the [iOS documentation](https://developer.apple.com/documentation/corelocation/clgeocoder) for more information.

### Geolocation setup

Location permissions are required for using geolocation on Android and iOS. Compass can handle the permission requesting automatically for you.

#### Android

On Android you don't need to declare any location permission in your manifest since Compass includes those in the `geolocation-mobile` artifact.

#### iOS

On iOS you are required to edit your `info.plist` and add the following entry:

```xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>Add a description for why you need this permission</string>
```

{% hint style="info" %}
Make sure you change the description for the permission. You are required to explain to your users why you need their location. Check out the [Documentation](https://developer.apple.com/documentation/corelocation/requesting_authorization_to_use_location_services#3385302\)).
{% endhint %}

If you don't add this key to your `info.plist`, you will encounter a runtime exception when requesting permissions.


# Mixed platforms

If you are targeting Mobile (Android/iOS) and other platforms like Desktop, or the Browser. You will need to do some additional setup since the Mobile Geocoder/Geolocation services aren't available.

## Geocoding example

This example is for a Compose Multiplatform app that targets mobile and Desktop, using Google Maps on non-mobile platforms. This is just an example, be sure to customize to your needs.

### Declare Dependencies

First you will need to add the necessary dependencies:

```toml
# gradle/libs.versions.toml

[libraries]
compass-geocoder = { module = "dev.jordond.compass:geocoder", version.ref = "compass" }
compass-geocoder-mobile = { module = "dev.jordond.compass:geocoder-mobile", version.ref = "compass" }
compass-geocoder-web = { module = "dev.jordond.compass:geocoder-web", version.ref = "compass" }
compass-geocoder-web-googlemaps = { module = "dev.jordond.compass:geocoder-googlemaps", version.ref = "compass" }
compass-geocoder-web-mapbox = { module = "dev.jordond.compass:geocoder-mapbox", version.ref = "compass" }
```

{% hint style="info" %}
Compass includes out of the box support for [Google Maps](https://developers.google.com/maps/documentation/geocoding/overview) and [Mapbox](https://docs.mapbox.com/api/search/geocoding-v6/). If you need support for another service you can use the `geocoder-web` to implement your own, or [Request a Geocoder API](/geocoding/request-a-geocoder-api)
{% endhint %}

### Add dependencies & create source-set

Now you need to edit the `build.gradle.kts` for your application module and add the following dependencies to the proper source-sets.

{% hint style="info" %}
We will be creating a `mobileMain` and `nonMobileMain` source-set to minimize boilerplate. You can read about source-sets [here](https://kotlinlang.org/docs/multiplatform-advanced-project-structure.html#declaring-custom-source-sets).
{% endhint %}

```kts
// composeApp/build.gradle.kts

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.compass.geocoder)
            implementation(libs.compass.geocoder.web.googlemaps)
        }
        
        val mobileMain by creating {
            dependsOn(commonMain.get())
            androidMain.get().dependsOn(this)
            iosMain.get().dependsOn(this)
            dependencies {
                implementation(libs.compass.geocoder.mobile)
            }
        }
        
        val desktopMain by getting
        val nonMobileMain by creating {
            dependsOn(commonMain.get())
            desktopMain.dependsOn(this)
            dependencies {
                implementation(libs.compass.geocoder.web.googlemaps)
            }
        }
    }
}
```

### Add platform expect/actual

This will produce the following folder structure, which you can then define a `expect` function to create a `Geocoder` instance:

```
composeApp
|
|----commonMain
|        |---kotlin/src/Platform.kt
|
|----mobileMain
|        |---kotlin/src/Platform.mobileMain.kt
|
|----nonMobileMain
|        |---kotlin/src/Platform.nonMobileMain.kt
```

Inside of `commonMain/kotlin/src/Platform.kt` you need to define an `expect` function that creates a `Geocoder` instance:

```kotlin
expect fun createGeocoder(): Geocoder
```

Then you need to create the `actual` functions:

```kotlin
// mobileMain/kotlin/src/Platform.mobileMain.kt

actual fun createGeocoder(): Geocoder {
    return Geocoder.mobile()
}
```

And for the `nonMobile` source set:

```kotlin
// mobileMain/kotlin/src/Platform.nonMobileMain.kt

actual fun createGeocoder(): Geocoder {
    // There are other optional parameters to customize the API request
    return Geocoder.googleMaps(apiKey = "my-google-maps-api-key")
}
```

{% hint style="info" %}
The Google Maps and Mapbox integration require API keys in order to use. If you want to customize the `HttpClient` or the arguments passed to the API service, you can use the optional parameters. See [Web API Service](/geocoding/web-api-service).
{% endhint %}

### Using the Geocoder

Now that you are all setup you can use your `createGeocoder()` function in the `commonMain` source-set:

```kotlin
class MyRepository {
    private val geocoder: Geocoder = createGeocoder()
    
    suspend fun reverseGeocode(lat: Long, lng: Long): Location? {
        return geocoder.reverse(lat, lng).getOrNull()
    }
}
```


# Migrating to 4.0.0

Compass 4.0.0 removes the Google Play Services dependency from `geolocation-mobile` and `permissions-mobile`, and moves the fused location provider into a new opt-in artifact, `geolocation-android-gms`.

## Do I need to do anything?

These breaking changes *only* affect Android use.

| Your situation                      | What to do                                      |
| ----------------------------------- | ----------------------------------------------- |
| Android and iOS, want Play Services | [Add one artifact](#keeping-the-fused-provider) |
| Android and iOS, no Play Services   | [Nothing](#dropping-play-services)              |

## Keeping the fused provider

Preserves 3.x behaviour. Add `geolocation-android-gms` to your Android source set:

{% tabs %}
{% tab title="Version catalog" %}

```toml
[libraries]
compass-geolocation-android-gms = { module = "dev.jordond.compass:geolocation-android-gms", version.ref = "compass" }
```

```kts
kotlin {
    sourceSets {
        androidMain.dependencies {
            implementation(libs.compass.geolocation.android.gms)
        }
    }
}
```

{% endtab %}

{% tab title="Gradle" %}

```kts
kotlin {
    sourceSets {
        androidMain.dependencies {
            implementation("dev.jordond.compass:geolocation-android-gms:$compassVersion")
        }
    }
}
```

{% endtab %}
{% endtabs %}

Your common code does not change:

```kotlin
// commonMain, identical to 3.x
val geolocator: Geolocator = Geolocator.mobile()
```

The artifact registers itself through an `androidx.startup` initializer, so `MobileLocator()` and `Geolocator.mobile()` pick the fused provider up on their own. On a device where Play Services is missing or out of date they fall back to the built-in `LocationManager`.

## Dropping Play Services

Do nothing. `geolocation-mobile` now uses the platform `LocationManager` and pulls in no Play Services code, so it works on devices without Play Services.

The two sources are not identical, see [what changes at runtime](#what-changes-at-runtime) before you decide.

## What changes at runtime

Only relevant if you take the [no Play Services](#dropping-play-services) path. Location still works, but the two sources differ in three places.

**Accuracy.** On API 31 and above the platform has a fused provider of its own, which Compass uses, and it is close to the Play Services one. Below API 31 there is no fused source, so Compass selects GPS or network from the request `priority`. Expect slower first locations and more variance there.

**`isAvailable()`.** Now reports whether location services are switched on. The Play Services version also validated the request settings, so a device with location on but unable to satisfy the priority you asked for reported `false` in 3.x and reports `true` now.

**Fallback.** 3.x failed on a device without Play Services. 4.0.0 does not, the platform `LocationManager` is always present.

`interval`, `maximumAge` and permission handling are unchanged.

## Checking which source you got

`geolocation-android-gms` exposes a check, useful if you want to warn about reduced accuracy:

```kotlin
if (isPlayServicesAvailable()) {
    // fused provider
} else {
    // platform LocationManager
}
```

It also exposes entry points pinned to the fused provider, which throw rather than fall back:

```kotlin
val locator: Locator = GmsLocator()
val geolocator: Geolocator = Geolocator.gms()
```

## See also

* [Android / iOS](/geolocation/android-ios) covers both providers in detail.
* [All Dependencies](/setup/add-dependencies) lists every artifact.


# Geocoding Overview

Geocoding is the process of turning coordinates into a Place (reverse), or turning an address name into coordinates.

Compass offers the following features:

* Reverse: Turn coordinates (latitude, longitude) into a Place object.
* Forward: Turn an address search query into coordinates.
* Android and iOS support, using built-in services.
* Support for web based API services.
  * Currently support for [Google Maps ](https://developers.google.com/maps/documentation/geocoding)and [Mapbox](https://docs.mapbox.com/#search) is included.
  * Easily create a wrapper for your own API.

{% hint style="info" %}
Built-in services are used for Android and iOS. That means that there is restrictions to its usage. You can be throttled or blocked from using the geocoding services if you make too many requests in a short period of time.
{% endhint %}

### Quick start

```kotlin
fun getPlaceFromCoordinates(lat: Long, lng: Long): Place? {
    val geocoder = Geocoder()
    return geocoder.placeOrNull()
}
```

In this quick example the `Geocoder()` function is a convenience function for creating a `Geocoder` object. Each of the `geocoder-*` artifacts provide one.


# Web API Service

If you need to support more platforms other than Android/iOS, you can use a web based API service. Out of the box Compass supports [Google Maps](/geocoding/web-api-service/google-maps) and [Broken mention](broken://pages/r0lxsyHuMsfJU0Q4Hg4f).

{% hint style="info" %}
In order to use these third party API's you will need to get an API key and also be aware of the costs associated with using their service.
{% endhint %}

### Custom API

If you aren't using the provided services, you can create your own by implementing your own `HttpApiPlatformGeocoder`.

#### Import the library

First you need to import the `geocoding-web` artifact:

```kts
implementation(libs.compass.geocoding.web)
```

#### Create your endpoints

Now you need to implement a `ForwardEndpoint` and `ReverseEndpoint` which are defined as:

```kotlin
public typealias ForwardEndpoint = HttpApiEndpoint<String, List<Coordinates>>
public typealias ReverseEndpoint = HttpApiEndpoint<Coordinates, List<Place>>
```

Here is an example `ForwardEndpoint`:

```kotlin
public class MyForwardEndpoint : ForwardEndpoint {

    override fun url(param: String): String {
        return "https://my-api.com/api/geocode?query=$param"
    }

    override suspend fun mapResponse(response: HttpResponse): List<Coordinates> {
        val result = response.body<MyAPIResponse>().resultsOrThrow()
        // Map the response to the Coordinates object
        return result.toCoordinates()
    }
}
```

Here you can use any API you want by creating the URL and mapping the response.

You can also use the helper extension function for more concise code:

```kotlin
val forwardEndpoint = ForwardEndpoint(
    url = { param -> "https://my-api.com/api/geocode?query=$param" },
    mapResponse = { response -> 
        response.body<MyAPIResponse>().resultsOrThrow().toCoordinates() 
    },
)
```

Now you can do the same for the `ReverseEndpoint` as well.

{% hint style="info" %}
The HTTP request is handled automatically for you. You can customize (to add authentication, headers, etc) this when you go to create the `HttpApiPlatformGeocoder` object, see below for more.
{% endhint %}

#### Create the Geocoder

In order to create the `Geocoder` object you need a `PlatformGeocoder` that can make the requests. In this case we need to create a `HttpApiPlatformGeocoder` this can be done like so:

```kotlin
val platformGeocoder = HttpApiPlatformGeocoder(
    forwardEndpoint = forwardEndpoint, // created above
    reverseEndpoint = reverseEndpoint,
)
```

Then you can finally create the `Geocoder`:

```kotlin
val geocoder = Geocoder(platformGeocoder)
geocoder.forward("London UK")
```

You can skip the `HttpApiPlatformGeocoder` step by using another extension function:

```kotlin
val geocoder = Geocoder(
    forwardEndpoint = forwardEndpoint, // created above
    reverseEndpoint = reverseEndpoint,
)
geocoder.forward("London UK")
```

#### Customizing the HTTP request

If you need to customize the HTTP request to your API then you can do so by passing a `HttpClient` or `Json` object to the above functions.\
\
Here is the signature for the above `Geocoder` function:

```kotlin
public fun Geocoder(
    forwardEndpoint: ForwardEndpoint,
    reverseEndpoint: ReverseEndpoint,
    json: Json = HttpApiEndpoint.json(),
    httpClient: HttpClient = HttpApiEndpoint.httpClient(json),
    dispatcher: CoroutineDispatcher = Dispatchers.Default,
): Geocoder 
```

Notice that a default `HttpClient` is used, but you can pass your own in.


# Google Maps

To use[ Google Maps API](https://developers.google.com/maps/documentation/geocoding/overview) as your geocoding backend you will need to do the following:

### Obtain an API key

1. First you will need to [create a cloud project and enable the API](https://developers.google.com/maps/documentation/geocoding/cloud-setup)
2. Create the [API key](https://developers.google.com/maps/documentation/geocoding/get-api-key)
3. Copy your key

### Create the Geocoder

Simple pass your API key to the `Geocoder` function:

```kotlin
val geocoder = GoogleMapsGeocoder(apiKey = "your-api-key")

// Or this helper function
val geocoder = Geocoder(apiKey = "your-api-key")
```

#### Customizing Google Maps request

The Google Maps geocoding API has some [optional parameters](https://developers.google.com/maps/documentation/geocoding/requests-geocoding). You can modify these by passing them into the `Geocoder` function:

```kotlin
val geocoder = GoogleMapsGeocoder(apiKey = "your-api-key") {
    locationTypes(GoogleMapsLocationType.GeometricCenter)
}
```


# Mapbox

To use [Mapbox API ](https://docs.mapbox.com/api/search/geocoding/)as your geocoding backend you will need to do the following:

### Obtain an API key

Create an account for [Mapbox](https://account.mapbox.com/), then copy your access token.

### Create the Geocoder

Simple pass your API key to the `Geocoder` function:

```kotlin
val geocoder = MapboxGeocoder(apiKey = "your-api-key")

// Or this helper function
val geocoder = Geocoder(apiKey = "your-api-key")
```

#### Customizing Google Maps request

The Mapbox geocoding API has some [optional parameters.](https://docs.mapbox.com/api/search/geocoding/#forward-geocoding-with-search-text-input) You can modify these by passing them into the `Geocoder` function:

```kotlin
val geocoder = MapboxGeocoder(apiKey = "your-api-key") {
    limit = 3
}
```


# Request a Geocoder API

If there is a Web based Geocoder API that you would like to be added to Compass.\
\
You can either [create an issue](https://github.com/jordond/compass/issues/new), or implement it yourself and open up a PR. If you plan on going that way, make sure you read [Contributing](/misc/contributing)first.


# Geolocation Overview

Geolocation includes the following features:

* Track a user's location
* Get the current location
* Customize the accuracy of the tracking
* Android and iOS support, using built-in services.
* Built-in Location permission handling

{% hint style="info" %}
Built-in services are used for Android and iOS. That means that there is restrictions to its usage.
{% endhint %}

## Quickstart

Include the required dependencies for your targets, see [All Dependencies](/setup/add-dependencies).

#### Create the Geolocator

Creating a geolocator depends on your platform, but in the simplest use-case of an Android & iOS only targets. You can do something like this:

```kotlin
val geolocator: Geolocator = Geolocator.mobile()
```

#### Get current location

Getting the current location is simple, just call:

```kotlin
val result: GeolocatorResult = geolocator.current()
// Handle the result:

when (result) {
    is GeolocatorResult.Success -> {
        // Do something with result.location
    }
    is GeolocatorResult.Error -> when(result) {
        is GeolocatorResult.NotSupported -> TODO()
        is GeolocatorResult.NotFound -> TODO()
        is GeolocatorResult.PermissionError -> TODO()
        is GeolocatorResult.GeolocationFailed -> TODO()
    }
}
```

Or if you only care about the result:

```kotlin
val location: Location? = geolocator.currentOrNull()
```

{% hint style="info" %}
Compass will handle asking the user for while-in-use location permissions. If they deny the permission then the result will be `PermissionError.` If the user has denied the permission permanently, you will need to prompt the user to open the settings.
{% endhint %}


# Geolocator

The `compass-geolocation` artifact provides an interface `Geolocator` that has members for:

* Getting the current location
* Starting and stopping tracking the location
* Check if location services are available

## Creating a Geolocator

To get an instance of `Geolocator` you can use the factory function:

```kotlin
public fun Geolocator(
    locator: Locator,
    dispatcher: CoroutineDispatcher = Dispatchers.Default,
): Geolocator
```

### Locator

A `Locator` is interface that can be implemented to provide all of the geolocation functionality.

Compass provides `Locator`'s for [Android / iOS](/geolocation/android-ios) and the [Browser](/geolocation/browser):

```kotlin
// Android and iOS
val locator = Locator.mobile()

// Or for the browser
val locator = Locator.browser()

// Create the geolocator
val geolocator: Geolocator = Geolocator(locator)
```

There are also included extension functions for each artifact:

```kotlin
// Android and iOS
val geolocator: Geolocator = Geolocator(MobileLocator)
val geolocator: Geolocator = MobileGeolocator()
val geolocator: Geolocator = Geolocator.mobile()
```

There are similar extension functions for the [Browser](/geolocation/browser)artifact.

{% hint style="info" %}
Make sure you add the proper dependencies, see [All Dependencies](/setup/add-dependencies).
{% endhint %}

### Custom Locator

If you would like to provide your own you can implement the object yourself:

```kotlin
val locator = object : Locator {
    // implement Locator
}
val geolocator: Geolocator = Geolocator(locator)
```

## Permissions

Compass will handle the permissions for Android and iOS automatically for you, see [Android / iOS](/geolocation/android-ios).

## Current location

To get the current location of the device call `Geolocator.current()`. It will return a `GeolocatorResult`, or you can use one of the extension functions:

```kotlin
val location: Location? = geolocator.current().getOrNull()
val location: Location? = geolocator.currentOrNull()
```

### GeolocatorResult

When you call `Geolocator.current()` you will receive a `GeolocationResult.` It can be one of the following:

* `GeolocatorResult.Success`
  * This has contains a `Location` object of the current location details.
* `GeolocatorResult.Error`
  * `NotSupported`: Geolocation is not supported on this device
  * `GeolocationFailed`: Geolocation failed for an unknown reason
  * `PermissionError`: We don't have permission to use the geolocation services

## Tracking location

You can subscribe to location updates by collecting the `Geolocator.locationUpdates`, or to get updates to the status of tracking (errors, permissions, updates) use `Geolocator.trackingStatus`:

```kotlin
val geolocator: Geolocator

val status = geolocator.trackingStatus.map { status ->
    when (status) {
        is TrackingStatus.Idle -> println("Not Tracking")
        is TrackingStatus.Update -> println(status.location.coordinates)
        is TrackingStatus.Error -> {
            val error: GeolocatorResult.Error = status.cause

            // Show the permissions settings screen
            val permissionDeniedForever = error.isPermissionDeniedForever
            
            // Handle other errors
            when (error) {
                // TODO
            }
        }
    }
}

suspend fun start() {
    geolocator.startTracking()
}

suspend fun stop() {
    geolocator.stopTracking()
}
```

## Location details

You can read about what kind of data is in the `Location` object by reading [Location](/geolocation/location).


# Location

The result from Geolocation operations will return a `Location` object, which looks like this:

```kotlin
class Location(
    public val coordinates: Coordinates,
    public val accuracy: Double,
    public val azimuth: Azimuth?,
    public val speed: Speed?,
    public val altitude: Altitude?,
    public val timestampMillis: Long,
)
```

You can read the [KDocs here](https://docs.compass.jordond.dev/compass-core/dev.jordond.compass/-location/index.html).

### Detailed location data

Depending on the `Priority` you use to request the location, determines what kind of data is present.

All location results will return the coordinates and the accuracy of those coordinates in meters. In order to get the other values you will need to use a more accurate `Priority`, like so:

```kotlin
val location: Location = geolocator.current(Priority.HighAccuracy)
```

There is still a chance those values will be `null`, since it is up to the device to determine what data is returned.

{% hint style="info" %}
On Android the `Priority.HighAccuracy` requires the `ACCESS_FINE_LOCATION` permission. This will be requested automatically by Compass.
{% endhint %}


# Android / iOS

Compass supports Geolocation on Android and iOS by using the built in location services.

To use the Geolocator, it requires the user to provide permission. This is handled automatically when you start tracking or attempt to get the current location.

Make sure you read the [Android](https://developer.android.com/develop/sensors-and-location/location) and [iOS](https://developer.apple.com/documentation/corelocation) documentation to fully understand how the location services work.

{% hint style="info" %}
If your project supports both Mobile and other targets, you need to configure your setup to provide a `Geolocator` for each platform. Check out [Mixed platforms](/setup/mixed-platforms)for more information.
{% endhint %}

## Usage

Follow these steps to create a `Geolocator`

### Create Locator

Geolocator is powered by the `Locator` object:

```kotlin
val locator: Locator = MobileLocator()
val locator: Locator = Locator.mobile()
```

### Create Geolocator

Then you can use that to create the `Geolocator` object:

```kotlin
val geolocator: Geolocator = Geolocator(locator)
```

Or you can use an extension function to skip the `Locator` step:

```kotlin
val geolocator: Geolocator = Geolocator()
val geolocator: Geolocator = MobileGeolocator()
val geolocator: Geolocator = Geolocator.mobile()
```

## Permissions

The user needs to grant permission in order to get location data.

On Android there are no further steps required, but on iOS you need to edit your `info.plist`, see [Android / iOS](/setup/android-ios) to learn more.

When you attempt to access the location, Compass will automatically ask the user for permission.

### Precise and approximate location

A granted permission does not mean the app can read a precise location. The user can allow location while withholding precision, on Android by answering "Approximate" and on iOS by turning Precise Location off, and the fixes that follow are accurate to kilometres rather than metres. `Priority.HighAccuracy` does not override that, the system clamps the request.

`LocationPermissionController.grantedAccuracy()` reports which of the two the app currently holds. It reads the current state and never prompts.

```kotlin
val controller = LocationPermissionController.mobile()

when (controller.grantedAccuracy()) {
    LocationAccuracy.Full -> preselectTheNearestCafe()
    // Kilometre scale, so a 150m proximity check can never pass. Ask the user to
    // turn precision on in Settings instead of silently failing.
    LocationAccuracy.Reduced -> promptForPreciseLocation()
    LocationAccuracy.Unknown -> requestPermission()
}
```

{% hint style="warning" %}
The two platforms differ in what `priority` does when requesting. On Android `Priority.HighAccuracy` requests `ACCESS_FINE_LOCATION`, so `PermissionState.Granted` does mean precise location was granted. On iOS the priority is ignored, because `CLLocationManager` has no way to ask for precision up front, the user chooses it inside the one system prompt. `Granted` there means only that a location can be read. Use `grantedAccuracy()` rather than assuming.
{% endhint %}

## Google Play Services on Android

On Android there are two ways to read a location, and you can pick which one you want.

`geolocation-mobile` uses the built-in `LocationManager` and doesn't depend on Google Play Services. It works on every Android device, including ones without Play Services, and it's safe to use in a copyleft licensed app.

`geolocation-android-gms` adds the Play Services fused location provider, which combines GPS, Wi-Fi, cell and sensors to get better locations while using less battery. It pulls in the closed source `play-services-location` library.

### Using the fused provider

Add the artifact to your Android source set, that's the only step:

```kts
kotlin {
    sourceSets {
        androidMain.dependencies {
            implementation("dev.jordond.compass:geolocation-android-gms:$compassVersion")
        }
    }
}
```

Your common code doesn't change. `MobileLocator()` and `Geolocator.mobile()` will prefer the fused provider, and fall back to `LocationManager` on devices where Play Services is missing or out of date:

```kotlin
// commonMain, unchanged
val geolocator: Geolocator = Geolocator.mobile()
```

If you want the fused provider and nothing else, so that a device without Play Services fails instead of falling back, the artifact also has Android-only entry points:

```kotlin
val locator: Locator = GmsLocator()
val locator: Locator = Locator.gms()

val geolocator: Geolocator = GmsGeolocator()
val geolocator: Geolocator = Geolocator.gms()

// Throws IllegalStateException when Play Services is unavailable, so check first
if (isPlayServicesAvailable()) Geolocator.gms() else Geolocator.mobile()
```

### Which one to use

* You want the best accuracy and battery usage, and depending on Play Services is fine, then add `geolocation-android-gms`.
* You can't have proprietary dependencies, maybe you're shipping to F-Droid or your app is GPL licensed, then you don't need to do anything since `geolocation-mobile` already covers you.
* You want to support devices without Play Services, but still get the fused locations where they exist, then add `geolocation-android-gms` and the fallback is handled for you.

{% hint style="warning" %}
Before Compass 4.0.0 the `geolocation-mobile` and `permissions-mobile` artifacts depended on `play-services-location` and always used the fused provider. They no longer do. To get the old behaviour back, add `geolocation-android-gms` to your Android source set. Without it your app keeps working, using the built-in `LocationManager`. See [Migrating to 4.0.0](/migration/4.0.0).
{% endhint %}

### What changes without Play Services

Location still works, but the two sources aren't identical.

The accuracy will differ. On API 31 and above the platform has a fused provider of its own, which Compass uses, and it's close to the Play Services one. Below API 31 there is no fused source, so Compass picks GPS or network based on the request `priority`. Expect slower first locations and more variance there.

`isAvailable()` reports whether location services are turned on, while the Play Services version also checks the request settings.

The `interval` option is honoured by both, see below.

## Get location

Now you can follow the steps in [Geolocator](/geolocation/geolocator)

## Request options

`LocationRequest` carries more than the priority. Two of its options behave in ways worth knowing about on mobile.

### `interval`

The gap between location updates while tracking, defaulting to 5 seconds.

On Android this is passed to the location provider, and the minimum update interval is held to the same value. Without that the provider is free to deliver updates at twice the rate you asked for whenever another app is already using location. That applies to both the built-in `LocationManager` and the fused provider.

CoreLocation has no time based equivalent, it reports a fix whenever it has one, so on iOS the interval is applied to the updates as they come out. The first update after `track` always arrives, later ones are dropped until the interval has elapsed. The rate CoreLocation itself runs at is unchanged, so this costs no extra battery, and it does not make updates arrive any sooner than CoreLocation produces them.

```kotlin
// Roughly one update per second on both platforms
geolocator.track(LocationRequest(interval = 1_000))
```

{% hint style="warning" %}
Tracking again while tracking is already running applies the new `interval` but keeps the original `priority`. Call `stopTracking()` first if you need to change the priority.
{% endhint %}

### `maximumAge`

How old a cached location may be and still satisfy `current()`, in milliseconds. It defaults to `0`, which always requests a fresh fix.

Set it and `current()` will hand back the last known location when that location is young enough, which is immediate rather than the seconds a fresh fix takes. Age is the only criteria, matching the [W3C Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions/maximumAge) option it is modelled on, so a cached fix that is less accurate than you asked for still counts. If nothing suitable is cached, a fresh location is requested as usual.

```kotlin
// Reuse a fix from the last minute, otherwise go and get one
geolocator.current(LocationRequest(maximumAge = 60_000))
```


# Browser

Compass supports [HTML5 Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API) with the `compass-geolocation-browser` artifact. The artifact supports JS and WASM targets.

Geolocation in the browser requires the user to provide permission. This is handled automatically when you start tracking or attempt to get the current location.

{% hint style="info" %}
If your project supports both Browser and other targets, you need to configure your setup to provide a `Geolocator` for each platform. Check out [Mixed platforms](/setup/mixed-platforms)for more information.
{% endhint %}

## Usage

Follow these steps to create a `Geolocator`

### Create Locator

Geolocator is powered by the `Locator` object:

```kotlin
val locator: Locator = createBrowserLocator()
val locator: Locator = Locator.browser()
```

### Create Geolocator

Then you can use that to create the `Geolocator` object:

```kotlin
val geolocator: Geolocator = Geolocator(locator)
```

Or you can use an extension function to skip the `Locator` step:

```kotlin
val geolocator: Geolocator = Geolocator()
val geolocator: Geolocator = BrowserGeolocator()
val geolocator: Geolocator = Geolocator.browser()
```

## Permissions

Nothing extra is required to setup permissions for the browser.

When you attempt to get the location, the browser will show a prompt to the user. They can allow, deny, or do one of those permanently.

## Get location

Now you can follow the steps in [Geolocator](/geolocation/geolocator)


# Autocomplete Overview

Autocomplete is used to provide real-time suggestions for locations as the user types in a query.

Compass currently provides an interface for interacting with a geocoding service, such as Mapbox. The autocomplete feature provides real-time suggestions to the user as they type an address or location into a search field.

The library is structured around several key components:

1. `AutocompleteService`: This is an interface that represents a service providing autocomplete suggestions. It has a `search(query: String)` function, which searches for autocomplete suggestions based on the provided query.
2. `AutocompleteResult`: This is a sealed class that represents the result of an autocomplete operation. It can either be a `Success` with a list of data or an `Error` with a specific error message.
3. `Autocomplete`: This is a function that creates a new instance of `AutocompleteService` that uses a HTTP service to provide autocomplete suggestions.

{% hint style="info" %}
The current implementations of the `AutocompleteService` use the Geocoding artifacts from Compass and perform a forward geocode operation. That means that the results may not be the highest of quality.
{% endhint %}

### Quick start

```kotlin
suspend fun autocomplete(query: String): List<Place> {
   val autocomplete = AutoComplete.mobile()
   return autocomplete.search("London").getOrNull()
}
```

In this quick example the `AutoComplete.mobile()()` function is a convenience function for creating a `AutoComplete<Place>` object. Each of the `autocomplete-*` artifacts provide one.


# Via Geocoding

Currently Autocomplete is powered by the existing Geocoder functionality of Compass. In the future, we plan to integrate with a dedicated autocomplete API service such as Google Places API.


# Android / iOS

Compass supports Autocomplete natively on Android and iOS by using the built in Geocoding services.

{% hint style="info" %}
If your project supports both Mobile and other targets, you need to configure your setup to provide a `Autocomplete<Place>` for each platform. Check out [https://github.com/jordond/compass/blob/main/docs/autocomplete/usage/mixed-platforms.md](https://github.com/jordond/compass/blob/main/docs/autocomplete/usage/mixed-platforms.md "mention")for more information.
{% endhint %}

## Getting started

To get started with Autocomplete on Android and iOS, you need to create an instance of `Autocomplete<Place>`:

```kotlin
val autocomplete = Autocomplete.mobile()
```

Then you can use the `autocomplete` instance:

```kotlin
val results = mutableListOf<String>()

suspend fun search(query: String) {
    val result = autocomplete.getOrNull() ?: return
    if (result.isNotEmpty()) {
        results.clear()
        results.addAll(result)
    }
}
```

{% hint style="info" %}
The Mobile Autocomplete uses the device's built-in Geocoding services to provide the results. These results can vary in quality and quantity.
{% endhint %}


# Web API Service

Compass supports Autocomplete on all platforms using the Geocoding services provided by Mapbox and Google Maps.

## Getting started

To get started you first need to create an autocomplete instance:

```kotlin
val autocomplete = Autocomplete.googleMapsGeocoder(apiKey = "your-api-key")
```

Then you can use the `autocomplete` instance:

```kotlin
val results = mutableListOf<String>()

suspend fun search(query: String) {
    val result = autocomplete.getOrNull() ?: return
    if (result.isNotEmpty()) {
        results.clear()
        results.addAll(result)
    }
}
```


# Demo

A demo is available in the `/demo` folder.

### Before running

* Check your system with [KDoctor](https://github.com/Kotlin/kdoctor)
* Install the [Kotlin Multiplatform Mobile plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile)

### Running

Clone the repository and open it in Android Studio. In the "Run configuration" drop-down you should see the following:

* demo.composeApp
* demo.browser
* demo.desktop
* demo.ios

Select the target you want and click Run.

### Manual

To manually run the demo you can do the following:

#### Android

To run the application on android device/emulator:

* open project in Android Studio and run imported android run configuration

To build the application bundle:

* run `./gradlew :demo:composeApp:assembleDebug`
* find `.apk` file in `demo/composeApp/build/outputs/apk/debug/composeApp-debug.apk`

#### iOS

To run the application on iPhone device/simulator:

* Open `iosApp/iosApp.xcworkspace` in Xcode and run standard configuration


# Contributing

Compass is open to contributions, if there is a bug or feature. Feel free to open up a PR or an issue.

Please make every effort to follow existing conventions and style in order to keep the code as readable as possible.

Contribute code changes through GitHub by forking the repository and sending a pull request. We squash all pull requests on merge.

If you add, remove, or change `public` objects, make sure you update the binary-compatibility definitions by running the following command:

```bash
./gradlew apiDump
```

Then commit and push the changed files.

### Custom Geocoder API

Compass includes a template module: `compass-geocoder-web-template`

If there is a Geocoding Web API that you would wish to add to Compass, follow these steps:

1. Clone the repo: `git clone git@github.com:jordond/compass`
2. Make a copy of the `compass-geocoder-web-template` module
3. Rename the copy to match the web service you're adding:
   1. ex: `compass-geocoder-web-myapi`
4. Search for the word "Template" in the module and replace it with your service name
5. Implement the `HttpApiPlatformGeocoder` interface
6. Customize the URL, query parameters, and the response models
7. Implement the `ForwardEndpoint` and `ReverseEndpoint`
8. Finish the implementation
9. Run `./gradlew apiDump` and commit the changes
10. Push & open a Pull Request


