mapbox / mapbox-search-android

Android SDK for Mapbox Search including preconfigured UI
https://docs.mapbox.com/android/search/guides/
Other
30 stars 7 forks source link

House/street address format based on country specific #112

Closed mtrakal closed 1 year ago

mtrakal commented 1 year ago

New Feature

Add regional/country specific house number / street number format. It's reflected on public API / playground, but not in SDK.

image

As you can see, there are 3 different types how address is formatted.

Most part of EU: Street HouseNumber, City (without any divider, only space) Other parts oz world (expect Russia): HouseNumber Street, City Russia: City, Street HouseNumber

/**
 * Format House number and Street name to the most used European format (not all EU countries).
 * https://qph.cf2.quoracdn.net/main-qimg-fed2c12535f9552efa214450c4c87881
 */
val SearchAddress.streetWithHouseNumber: String
    get() = "${street.orEmpty()} ${houseNumber.orEmpty()}".trim()

Why

Because not all of us live in USA. Because search suggestions should reflect local conventions.

kalbaxa commented 1 year ago

Thank you for your suggestion @mtrakal! We will have a look, for Android specifically after 15 Dec.

cc @DzmitryFomchyn @sarochych

DzmitryFomchyn commented 1 year ago

Hey @mtrakal,

It's reflected on public API / playground, but not in SDK.

Could you please clarify what do you mean here? Few examples will be helpful. Also, which API type you are using, v5 or SBS?

As you can see, there are 3 different types how address is formatted.

I'm not sure if this is official standard. Other sources say a slightly different formatting rules for some countries.

Anyway, I'm still not sure if this should be part of SDK unless we guarantee correct formatting. Search SDK provides address components and customers can format address in required order.

mtrakal commented 1 year ago

More info what we are using and what is inconsistency between SDK and API in some old issues sample here: https://github.com/mtrakal/mapbox-issues

Search in Readme for:

Another thing is, that formatter doesn't respect the national format of address (but API respects it). and below is description for reported issue here.

mtrakal commented 1 year ago

API: https://docs.mapbox.com/playground/geocoding/?search_text=Bo%C5%99ivojova%2085&country=cz%2Csk%2Cpl&limit=10&types=place%2Cpostcode%2Caddress%2Cpoi&language=cs&access_token=pk.xxx used as referrence.

Any address in Czech Republic.

mtrakal commented 1 year ago

Just provide consistent data (and preferred same data as API - like place_name_cs / place_name - a complete name with address and all will be fine. If it will return proper address in selected locale.

For now I must do this mess in code just to show proper / nice address name:

import com.mapbox.search.result.SearchAddress
import com.mapbox.search.result.SearchResult
import com.mapbox.search.result.SearchResultType
import com.mapbox.search.result.SearchSuggestion
import com.mapbox.search.result.SearchSuggestionType

val SearchResult.fullAddress: String
    get() = when (types.firstByType()) {
        SearchResultType.COUNTRY -> withName(address?.countryAddress) // usa
        SearchResultType.REGION -> withName(address?.regionAddress) // Liberecký kraj
        SearchResultType.POSTCODE,
        SearchResultType.BLOCK -> withName(address?.postcodeAddress) // 468 22
        SearchResultType.DISTRICT -> address?.districtAddress //
        SearchResultType.PLACE -> withName(address?.placeAddress) // Praha
        SearchResultType.LOCALITY -> withName(address?.localityAddress) // Manhattan / Písek Líšťany
        SearchResultType.NEIGHBORHOOD -> address?.neighborhoodAddress // ?
        SearchResultType.STREET -> address?.streetAddress // never used, SDK return error, if try to use STREET 🤦‍
        SearchResultType.ADDRESS -> address?.houseAddress // any czech street is returned as address 🤷‍
        SearchResultType.POI -> withName(address?.poiAddress) // Cross club
        null -> null
    }.orEmpty()
        .distinct()
        .filter { it.isNotBlank() }
        .joinToString(", ")
        .trim()

/**
 * Add name of suggestion, because address don't contains this info in some cases...
 */
private fun SearchResult.withName(address: List<String>?) = listOfNotNull(name) + address.orEmpty()

val SearchSuggestion.fullAddress: String
    get() = when (this.type.getSearchSuggestionType()) {
        SearchResultType.COUNTRY -> withName(address?.countryAddress) // usa
        SearchResultType.REGION -> withName(address?.regionAddress) // Liberecký kraj
        SearchResultType.POSTCODE,
        SearchResultType.BLOCK -> withName(address?.postcodeAddress) // 468 22
        SearchResultType.DISTRICT -> address?.districtAddress //
        SearchResultType.PLACE -> withName(address?.placeAddress) // Praha
        SearchResultType.LOCALITY -> withName(address?.localityAddress) // Manhattan / Písek Líšťany
        SearchResultType.NEIGHBORHOOD -> address?.neighborhoodAddress // ?
        SearchResultType.STREET -> address?.streetAddress // never used, SDK return error, if try to use STREET 🤦‍
        SearchResultType.ADDRESS -> address?.houseAddress // any czech street is returned as address 🤷‍
        SearchResultType.POI -> withName(address?.poiAddress) // Cross club
        null -> null
    }.orEmpty()
        .distinct()
        .filter { it.isNotBlank() }
        .joinToString(", ")
        .trim()

/**
 * Add name of suggestion, because address don't contains this info in some cases...
 */
private fun SearchSuggestion.withName(address: List<String>?) = listOfNotNull(name) + address.orEmpty()

/**
 * Generally recognized countries or, in some cases like Hong Kong, an area of quasi-national
 * administrative status that has been given a designated country code under ISO 3166-1.
 */
private val SearchAddress.countryAddress: List<String>
    get() = listOfNotNull(country)

/**
 * Top-level sub-national administrative features, such as states in the United States
 * or provinces in Canada or China.
 */
private val SearchAddress.regionAddress: List<String>
    get() = listOfNotNull(region, country)

/**
 * Postal codes used in country-specific national addressing systems.
 */
private val SearchAddress.postcodeAddress: List<String>
    get() = listOfNotNull(postcode, place, region)

/**
 * Features that are smaller than top-level administrative features but typically larger than cities,
 * in countries that use such an additional layer in postal addressing (for example, prefectures in China).
 */
private val SearchAddress.districtAddress: List<String>
    get() = listOfNotNull(district, postcode, region)

/**
 * Typically these are cities, villages, municipalities, etc.
 * They’re usually features used in postal addressing, and are suitable for display in ambient
 * end-user applications where current-location context is needed (for example, in weather displays).
 */
private val SearchAddress.placeAddress: List<String>
    get() = listOfNotNull(place, district, postcode, region)

/**
 * Official sub-city features present in countries where such an additional administrative layer
 * is used in postal addressing, or where such features are commonly referred to in local parlance.
 * Examples include city districts in Brazil and Chile and arrondissements in France.
 */
private val SearchAddress.localityAddress: List<String>
    get() = listOfNotNull(locality, place, district, postcode, region)

/**
 * Colloquial sub-city features often referred to in local parlance.
 * Unlike locality features, these typically lack official status and may lack universally agreed-upon boundaries.
 */
private val SearchAddress.neighborhoodAddress: List<String>
    get() = listOfNotNull(neighborhood, locality, place, district, postcode, region)

/**
 * Features that are smaller than places and that correspond to streets in cities, villages, etc.
 */
private val SearchAddress.streetAddress: List<String>
    get() = listOfNotNull(street, neighborhood, locality, place, district, postcode, region)

/**
 * Individual residential or business addresses.
 */
private val SearchAddress.houseAddress: List<String>
    get() = listOfNotNull(
        streetWithHouseNumber,
        neighborhood,
        locality,
        place,
        district,
        postcode,
        region
    )

/**
 * Points of interest.
 * These include restaurants, stores, concert venues, parks, museums, etc.
 */
private val SearchAddress.poiAddress: List<String>
    get() = listOfNotNull(
        streetWithHouseNumber,
        neighborhood,
        locality,
        place,
        district,
        postcode
    )

private fun SearchSuggestionType.getSearchSuggestionType(): SearchResultType? = when (this) {
    is SearchSuggestionType.SearchResultSuggestion -> this.types.firstByType()
    is SearchSuggestionType.Category, is SearchSuggestionType.Query -> null
    else -> error("Unknown SearchSuggestionType type: $this.")
}

/**
 * Get most weighted type from list of [SearchResultType].
 */
private fun List<SearchResultType>.firstByType(): SearchResultType? = this.maxByOrNull { it.weight }

/**
 * Ordering weight by priority (most precise address has highest weight).
 */
private val SearchResultType.weight: Int
    get() = when (this) {
        SearchResultType.COUNTRY -> 0
        SearchResultType.REGION -> 1
        SearchResultType.POSTCODE -> 2
        SearchResultType.BLOCK -> 3
        SearchResultType.PLACE -> 4
        SearchResultType.DISTRICT -> 5
        SearchResultType.LOCALITY -> 6
        SearchResultType.NEIGHBORHOOD -> 7
        SearchResultType.STREET -> 8
        SearchResultType.ADDRESS -> 9
        SearchResultType.POI -> 10
    }

/**
 * Format House number and Street name to most used European format (not at all EU countries).
 * https://qph.cf2.quoracdn.net/main-qimg-fed2c12535f9552efa214450c4c87881
 * Created issue on MapBox: https://github.com/mapbox/mapbox-search-android/issues/112
 */
private val SearchAddress.streetWithHouseNumber: String
    get() = "${street.orEmpty()} ${houseNumber.orEmpty()}".trim()

How it works:

DzmitryFomchyn commented 1 year ago

@mtrakal thanks for the details! We've fixed this issue in the 1.0.0-beta.46. Now SearchSuggestion.name/SearchResult.name return house number and street in a correct order. Besides that we've added a new field SearchSuggestion.fullAddress/SearchResult.fullAddress that returns full formatted address from the backend.

I'll close this issue for now. Feel free to reopen it or create an ew one if you have more questions.