Météo France - Disambiguate country codes

This commit is contained in:
Julien Papasian 2025-08-16 18:01:51 +02:00
parent 5c422beb3e
commit a6ed49077c
5 changed files with 133 additions and 26 deletions

View file

@ -153,20 +153,7 @@ As a starting point, we will only implement weather part, but here is the full l
For most complex needs, always have a look at existing sources. If you need to add a new type of pollen for your source, please contact us first as it is a non-trivial change to the code.
Lets focus on the `requestWeather()` function now. You will need to adapt the existing converter class.
The goal of a converter class is to normalize the data we received into Breezy Weather data objects.
Here is the minimum code you need to put in your converter:
```kotlin
fun convert(
location: Location,
weatherResult: MySourceWeatherResult
): WeatherWrapper {
return WeatherWrapper()
}
```
Yes, of course, you wont have any data that way, but its just to show you that all data is non-mandatory. You can have a look at the non-mandatory parameters of the WeatherResultWrapper object and complete bit by bit the data as you feel.
In the `requestWeather()`, all properties of the `WeatherWrapper` are optional, so you can start implementing bit by bit, so you can easily test the first data.
Add your service in the constructor of the `SourceManager` class.

View file

@ -24,8 +24,89 @@ interface AddressSource : Source {
* Use this value if you have no idea what to do, or dont want to bother testing every territory and claim
* Use an empty array if the source respects all known ISO 3166-1 alpha-2 codes
* Specify each ISO 3166-1 alpha-2 code otherwise
* The list of all country codes known to be potentially ambiguous can be found in the companion object of this
* interface
*
* Below are cities / coordinates to test each ambiguity.
* You need to test EACH location to validate that a country code is not ambiguous. Dont assume that because
* the first ones succeed that the following will also. We have many evidences in the existing sources that
* exceptions are common
* AU:
* - CX: Flying Fish Cove -10.421667,105.678056
* - CC: Bantam -12.1178,96.8975
* - HM: Mawson Peak -53.1,73.516667
* - NF: Kingston -29.056,167.961
* CN:
* - HK: Hong Kong 22.38715,114.19534
* - MO: Macau 22.19,113.54
* - TW: Taipei 25.0375,121.5625
* DK:
* - FO: Tórshavn 62,-6.783333
* - GL: Nuuk 64.176667,-51.736111
* FI:
* - AX: Eckerö 60.216667,19.55
* FR:
* - GP (971): Pointe-à-Pitre 16.2411,-61.5331
* - MQ (972): Fort-de-France 14.6,-61.066667
* - GF (973): Cayenne 4.9372,-52.326
* - RE (974): Le Tampon -21.2781,55.5153
* - PM (975): Saint-Pierre 46.7778,-56.1778
* - YT (976): Mamoudzou -12.7806,45.2278
* - BL (977): Gustavia 17.897908,-62.850556
* - MF (978): Marigot 18.07,-63.01
* - TF (984): Port-aux-Français -49.35,70.218889
* - AQ (984): Dumont D'Urville -66.662778,140.001111
* - WF (986): Mata Utu -13.283333,-176.183333
* - PF (987): Papeete -17.566667,-149.6
* - NC (988): Nouméa -22.266667,166.466667
* - CP (ignore if you have no way to make it recognize as CP): Clipperton 10.3,-109.216667
* GB:
* - AI: The Valley 18.220833,-63.051667
* - BM: Hamilton 32.296111,-64.782778
* - IO: Diego Garcia -7.313333,72.411111
* - KY: George Town 19.296389,-81.381667
* - FK: Stanley -51.695278,-57.849444
* - GI: Gibraltar 36.14,-5.35
* - GG: St. Peter Port 49.4555,-2.5368
* - IM: Douglas 54.15,-4.48
* - JE: St Helier 49.19,-2.11
* - MS: Brades 16.792778,-62.210556
* - PN: Adamstown -25.066667,-130.1
* - SH:
* -- Ascension: Two Boats -7.937,-14.364
* -- Saint Helena: Half Tree Hollow -15.933333,-5.72
* -- Tristan da Cunha: Edinburgh of the Seven Seas -37.0675,-12.311111
* - GS: King Edward Point -54.283333,-36.5
* - TC: Grand Turk (Cockburn Town) 21.459,-71.139
* - VG: Road Town 18.431389,-64.623056
* IL:
* - PS: Gaza city 31.516667,34.45
* MA:
* - EH: Tifariti 26.158056,-10.566944
* NL:
* - AW: Oranjestad (not to be confused with BQ) 12.518611,-70.035833
* - BQ:
* -- Bonaire: Kralendijk 12.144444,-68.265556
* -- Sint Eustatius: Oranjestad (not to be confused with AW) 17.483333,-62.983333
* -- Saba: The Bottom 17.626111,-63.249167
* - CW: Willemstad 12.116667,-68.933333
* - SX: Lower Prince's Quarter 18.052778,-63.0425
* NO:
* - BV: Bouvet Island -54.42,3.36
* - SJ:
* -- Svalbard: Longyearbyen 78.22,15.65
* -- Jan Mayen: Olonkinbyen 70.922,-8.715
* NZ:
* - TK: Atafu -8.557222,-172.470833
* - CK: Avarua -21.206944,-159.770833
* - NU: Alofi -19.053889,-169.92
* RS:
* - XK: Pristina 42.663333,21.162222
* US:
* - AS: Tāfuna -14.335833,-170.72
* - GU: Dededo 13.509492,144.836528
* - MP: Saipan 15.183333,145.75
* - PR: Arecibo 18.375,-66.625
* - UM: Baker Island 0.195833,-176.479167
* - VI: Charlotte Amalie 18.35,-64.933333
*/
val knownAmbiguousCountryCodes: Array<String>?
@ -41,7 +122,7 @@ interface AddressSource : Source {
"CN", // Territories: HK, MO. Claims: TW
"DK", // Territories: FO, GL
"FI", // Territories: AX
"FR", // Territories: GF, PF, TF (uninhabited), GP, MQ, YT, NC, RE, BL, MF, PM, WF
"FR", // Territories: GF, PF, TF (uninhabited), GP, MQ, YT, NC, RE, BL, MF, PM, WF, CP. Claims: AQ
"GB", // Territories: AI, BM, IO, KY, FK, GI, GG, IM, JE, MS, PN, SH, GS (uninhabited), TC, VG
"IL", // Claims: PS
"MA", // Claims: EH

View file

@ -949,7 +949,7 @@ class OpenMeteoService @Inject constructor(
override val testingLocations: List<Location> = emptyList()
// Uses GeoNames database which has no ambiguous codes
override val knownAmbiguousCountryCodes: Array<String> = arrayOf()
override val knownAmbiguousCountryCodes: Array<String> = emptyArray()
companion object {
private const val OPEN_METEO_AIR_QUALITY_BASE_URL = "https://air-quality-api.open-meteo.com/"

View file

@ -411,7 +411,6 @@ class ImsService @Inject constructor(
// This will make locations in disputed areas appear as being in Israel, but I guess people using IMS as their
// address lookup source wouldn't be surprised by this kind of behavior
return LocationAddressInfo(
timeZoneId = "Asia/Jerusalem",
countryCode = "IL",
city = result.name
)

View file

@ -1016,10 +1016,42 @@ class MfService @Inject constructor(
): LocationAddressInfo? {
if (result.properties == null) return null
val countryCode = result.properties.country.substring(0, 2).let {
if (it == "FR") {
when (result.properties.frenchDepartment) {
"971" -> "GP"
"972" -> "MQ"
"973" -> "GF"
"974" -> "RE"
"975" -> "PM"
"976" -> "YT"
"977" -> "BL"
"978" -> "MF"
"984" -> if (result.properties.timezone == "Antarctica/DumontDUrville") "AQ" else "TF"
"986" -> "WF"
"987" -> "PF"
"988" -> "NC"
else -> when (result.properties.timezone) {
/**
* The nearest location is returned, so sometimes you end up in a different territory.
* HOWEVER, the timezone is always the one from the coordinates in parameter, so we can find the correct
* territory that way
*/
"Europe/Jersey" -> "JE"
"America/Lower_Princes" -> "SX"
else -> it
}
}
} else {
it
}
}
return LocationAddressInfo(
latitude = result.geometry?.coordinates?.getOrElse(1) { null },
longitude = result.geometry?.coordinates?.getOrElse(0) { null },
timeZoneId = result.properties.timezone,
country = result.properties.country,
countryCode = result.properties.country.substring(0, 2),
countryCode = countryCode,
admin2 = if (!result.properties.frenchDepartment.isNullOrEmpty()) {
frenchDepartments.getOrElse(result.properties.frenchDepartment) { null }
} else {
@ -1063,8 +1095,8 @@ class MfService @Inject constructor(
throw InvalidLocationException()
}
val domain = if (frenchDepartments.containsKey(it.properties!!.frenchDepartment!!)) {
it.properties.frenchDepartment!!
val domain = if (frenchDepartments.containsKey(it.properties.frenchDepartment)) {
it.properties.frenchDepartment
} else if (overseaTerritories.containsKey(it.properties.frenchDepartment)) {
if (it.properties.frenchDepartment in arrayOf("977", "978")) {
"VIGI978-977"
@ -1123,7 +1155,7 @@ class MfService @Inject constructor(
Jwts.SIG.HS256
)
}.compact()
} catch (ignored: Exception) {
} catch (_: Exception) {
BuildConfig.MF_WSFT_KEY
}
}
@ -1148,8 +1180,16 @@ class MfService @Inject constructor(
override val testingLocations: List<Location> = emptyList()
// TODO: At least FR is known to be ambiguous
override val knownAmbiguousCountryCodes: Array<String>? = null
/**
* The main issue on this source is that there are not many nearest locations recognized, so the resolutions are
* wrong on small regions like:
* - Macau (nearest on China side, making "CN" ambiguous)
* - For others, we were able to deduce from the timezone (no idea why it didn't work for Macau)
*
* When the nearest point is more than 50 km away, the app rejects the result, so we are safe for non-recognized
* uninhabited islands (if anyone actually need it)
*/
override val knownAmbiguousCountryCodes: Array<String> = arrayOf("CN") // Territories: MO
companion object {
private const val MF_BASE_URL = "https://webservice.meteofrance.com/"