data/PlaceGazetteer.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "PlaceGazetteer.h" | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | |||
| 5 | #include <QRegularExpression> | ||
| 6 | |||
| 7 | namespace PlaceGazetteer { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | 1253 | Place placeAt(const GeoData &data, qint32 lat, qint32 lon, const QString &precision, int country) { | |
| 12 | 1253 | Place place; | |
| 13 |
1/1✓ Branch 4 → 5 taken 1253 times.
|
1253 | place.position = GeoPoint::at(GeoData::degrees(lat), GeoData::degrees(lon)); |
| 14 | 1253 | place.precision = precision; | |
| 15 |
3/6✓ Branch 6 → 7 taken 1253 times.
✗ Branch 6 → 11 not taken.
✓ Branch 9 → 10 taken 1253 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 1253 times.
✗ Branch 12 → 22 not taken.
|
1253 | if (country >= 0 && country < data.countries().size()) { |
| 16 | 1253 | const GeoData::Country &c = data.countries().at(country); | |
| 17 |
1/1✓ Branch 15 → 16 taken 1253 times.
|
1253 | place.countryIso = QString::fromLatin1(c.iso2); |
| 18 |
1/1✓ Branch 18 → 19 taken 1253 times.
|
1253 | place.timeZone = data.string(c.timeZone); |
| 19 | } | ||
| 20 | 1253 | return place; | |
| 21 | ✗ | } | |
| 22 | |||
| 23 | 1122 | Place cityPlace(const GeoData &data, int index) { | |
| 24 |
1/1✓ Branch 2 → 3 taken 1122 times.
|
1122 | const GeoData::City city = data.city(index); |
| 25 |
1/1✓ Branch 6 → 7 taken 1122 times.
|
2244 | Place place = placeAt(data, city.lat, city.lon, QStringLiteral("city"), city.country); |
| 26 |
1/1✓ Branch 9 → 10 taken 1122 times.
|
1122 | place.placeName = data.string(city.name); |
| 27 |
2/3✓ Branch 12 → 13 taken 1122 times.
✓ Branch 14 → 15 taken 1122 times.
✗ Branch 14 → 16 not taken.
|
1122 | if (const QString zone = data.timeZoneName(city.timeZone); !zone.isEmpty()) |
| 28 | 1122 | place.timeZone = zone; | |
| 29 | 1122 | return place; | |
| 30 | ✗ | } | |
| 31 | |||
| 32 | 22 | Place countryPlace(const GeoData &data, int country) { | |
| 33 | 22 | const GeoData::Country &c = data.countries().at(country); | |
| 34 |
1/1✓ Branch 7 → 8 taken 22 times.
|
22 | Place place = placeAt(data, c.labelLat, c.labelLon, QStringLiteral("country"), country); |
| 35 |
1/1✓ Branch 10 → 11 taken 22 times.
|
22 | place.placeName = data.string(c.nameEn); |
| 36 | 22 | return place; | |
| 37 | ✗ | } | |
| 38 | |||
| 39 | // The best city of that name: in the country when known, in the region when | ||
| 40 | // it matches, else the most populous one (cities are sorted by population). | ||
| 41 | 1203 | int bestCity(const GeoData &data, const QString &name, int country, int region) { | |
| 42 |
1/1✓ Branch 2 → 3 taken 1203 times.
|
1203 | const QString key = GeoData::key(name); |
| 43 |
2/2✓ Branch 4 → 5 taken 23 times.
✓ Branch 4 → 6 taken 1180 times.
|
1203 | if (key.isEmpty()) |
| 44 | 23 | return -1; | |
| 45 |
1/1✓ Branch 6 → 7 taken 1180 times.
|
1180 | QList<int> hits = data.citiesNamed(key, 64); |
| 46 | // A city called so beats one that has the name only as an alternate | ||
| 47 | // ("Porto" is an alternate name of Bordeaux); population decides within each. | ||
| 48 |
3/3✓ Branch 7 → 8 taken 1180 times.
✓ Branch 8 → 9 taken 1180 times.
✓ Branch 9 → 10 taken 1180 times.
|
1180 | std::stable_partition(hits.begin(), hits.end(), [&data, &key](int index) { |
| 49 |
3/3✓ Branch 2 → 3 taken 5098 times.
✓ Branch 3 → 4 taken 5098 times.
✓ Branch 4 → 5 taken 5098 times.
|
5098 | return GeoData::key(data.string(data.city(index).name)) == key; |
| 50 | }); | ||
| 51 | 1180 | int inCountry = -1; | |
| 52 |
4/4✓ Branch 10 → 11 taken 1180 times.
✓ Branch 11 → 12 taken 1180 times.
✓ Branch 26 → 13 taken 5073 times.
✓ Branch 26 → 27 taken 1178 times.
|
6251 | for (int index : hits) { |
| 53 |
1/1✓ Branch 14 → 15 taken 5073 times.
|
5073 | const GeoData::City city = data.city(index); |
| 54 |
4/4✓ Branch 15 → 16 taken 2357 times.
✓ Branch 15 → 18 taken 2716 times.
✓ Branch 16 → 17 taken 1790 times.
✓ Branch 16 → 18 taken 567 times.
|
5073 | if (country >= 0 && city.country != country) |
| 55 | 1790 | continue; | |
| 56 |
4/4✓ Branch 18 → 19 taken 3 times.
✓ Branch 18 → 21 taken 3280 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 21 taken 1 time.
|
3283 | if (region >= 0 && city.admin1 == region) |
| 57 | 2 | return index; | |
| 58 |
2/2✓ Branch 21 → 22 taken 1121 times.
✓ Branch 21 → 23 taken 2160 times.
|
3281 | if (inCountry < 0) |
| 59 | 1121 | inCountry = index; | |
| 60 | } | ||
| 61 |
2/2✓ Branch 27 → 28 taken 1120 times.
✓ Branch 27 → 29 taken 58 times.
|
1178 | if (inCountry >= 0) |
| 62 | 1120 | return inCountry; | |
| 63 |
3/5✓ Branch 29 → 30 taken 20 times.
✓ Branch 29 → 34 taken 38 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 34 taken 20 times.
✗ Branch 32 → 33 not taken.
|
58 | return country < 0 && !hits.isEmpty() ? hits.first() : -1; |
| 64 | 1203 | } | |
| 65 | |||
| 66 | // Forms of a locality to try: as written, without a leading postal code, | ||
| 67 | // before a district separator ("Berlin-Mitte", "Wien, 1. Bezirk", | ||
| 68 | // "Frankfurt am Main (Höchst)"). | ||
| 69 | 1090 | QStringList localityForms(const QString &locality) { | |
| 70 | 1090 | QStringList forms; | |
| 71 |
1/1✓ Branch 2 → 3 taken 1090 times.
|
1090 | QString text = locality.trimmed(); |
| 72 |
4/7✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 16 taken 1085 times.
✓ Branch 5 → 6 taken 5 times.
✗ Branch 5 → 16 not taken.
✓ Branch 10 → 11 taken 5 times.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
|
1095 | static const QRegularExpression leadingCode(QStringLiteral("^[A-Za-z]{0,2}-?\\d{3,6}\\s+")); |
| 73 |
1/1✓ Branch 16 → 17 taken 1090 times.
|
1090 | text.remove(leadingCode); |
| 74 |
1/1✓ Branch 17 → 18 taken 1090 times.
|
1090 | forms << text; |
| 75 |
2/2✓ Branch 30 → 20 taken 4360 times.
✓ Branch 30 → 31 taken 1090 times.
|
5450 | for (const QChar separator : {QLatin1Char('('), QLatin1Char(','), QLatin1Char('/'), QLatin1Char('-')}) { |
| 76 | 4360 | const int at = text.indexOf(separator); | |
| 77 |
2/2✓ Branch 22 → 23 taken 8 times.
✓ Branch 22 → 29 taken 4352 times.
|
4360 | if (at > 1) |
| 78 |
3/3✓ Branch 23 → 24 taken 8 times.
✓ Branch 24 → 25 taken 8 times.
✓ Branch 25 → 26 taken 8 times.
|
8 | forms << text.left(at).trimmed(); |
| 79 | } | ||
| 80 |
1/1✓ Branch 31 → 32 taken 1090 times.
|
1090 | forms.removeDuplicates(); |
| 81 | 1090 | return forms; | |
| 82 | 1090 | } | |
| 83 | |||
| 84 | 1133 | int countryOf(const GeoData &data, const QString &countryText, const QString &defaultCountry) { | |
| 85 |
3/3✓ Branch 2 → 3 taken 1133 times.
✓ Branch 5 → 6 taken 456 times.
✓ Branch 5 → 8 taken 677 times.
|
1133 | if (!countryText.trimmed().isEmpty()) |
| 86 | 456 | return data.countryIndex(countryText); | |
| 87 |
2/2✓ Branch 9 → 10 taken 561 times.
✓ Branch 9 → 11 taken 116 times.
|
677 | return defaultCountry.isEmpty() ? -1 : data.countryIndex(defaultCountry); |
| 88 | } | ||
| 89 | |||
| 90 | } // namespace | ||
| 91 | |||
| 92 | 1137 | QString normalizedPostalCode(const QString &countryIso, const QString &input) { | |
| 93 |
2/2✓ Branch 2 → 3 taken 1137 times.
✓ Branch 3 → 4 taken 1137 times.
|
1137 | QString code = input.trimmed().toUpper(); |
| 94 |
4/7✓ Branch 5 → 6 taken 4 times.
✓ Branch 5 → 18 taken 1133 times.
✓ Branch 7 → 8 taken 4 times.
✗ Branch 7 → 18 not taken.
✓ Branch 12 → 13 taken 4 times.
✗ Branch 92 → 93 not taken.
✗ Branch 92 → 94 not taken.
|
1141 | static const QRegularExpression prefix(QStringLiteral("^(D|A|CH|DE|AT)[- ]")); |
| 95 |
1/1✓ Branch 18 → 19 taken 1137 times.
|
1137 | code.remove(prefix); |
| 96 |
1/1✓ Branch 21 → 22 taken 1137 times.
|
1137 | code.remove(QLatin1Char(' ')); |
| 97 |
4/7✓ Branch 22 → 23 taken 4 times.
✓ Branch 22 → 35 taken 1133 times.
✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 35 not taken.
✓ Branch 29 → 30 taken 4 times.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
|
1141 | static const QRegularExpression digits5(QStringLiteral("^\\d{5}$")); |
| 98 |
4/7✓ Branch 35 → 36 taken 4 times.
✓ Branch 35 → 48 taken 1133 times.
✓ Branch 37 → 38 taken 4 times.
✗ Branch 37 → 48 not taken.
✓ Branch 42 → 43 taken 4 times.
✗ Branch 114 → 115 not taken.
✗ Branch 114 → 116 not taken.
|
1141 | static const QRegularExpression digits4(QStringLiteral("^\\d{4}$")); |
| 99 |
2/2✓ Branch 50 → 51 taken 220 times.
✓ Branch 50 → 60 taken 917 times.
|
1137 | if (countryIso == QLatin1String("DE")) |
| 100 |
4/4✓ Branch 52 → 53 taken 220 times.
✓ Branch 53 → 54 taken 220 times.
✓ Branch 54 → 55 taken 174 times.
✓ Branch 54 → 56 taken 46 times.
|
220 | return digits5.match(code).hasMatch() ? code : QString(); |
| 101 |
6/6✓ Branch 62 → 63 taken 902 times.
✓ Branch 62 → 66 taken 15 times.
✓ Branch 65 → 66 taken 17 times.
✓ Branch 65 → 67 taken 885 times.
✓ Branch 68 → 69 taken 32 times.
✓ Branch 68 → 78 taken 885 times.
|
917 | if (countryIso == QLatin1String("AT") || countryIso == QLatin1String("CH")) |
| 102 |
4/4✓ Branch 70 → 71 taken 32 times.
✓ Branch 71 → 72 taken 32 times.
✓ Branch 72 → 73 taken 26 times.
✓ Branch 72 → 74 taken 6 times.
|
32 | return digits4.match(code).hasMatch() ? code : QString(); |
| 103 | 885 | return code; | |
| 104 | 1137 | } | |
| 105 | |||
| 106 | 1019 | QString addressKey(const ContactAddress &address) { | |
| 107 | 1019 | QStringList parts; | |
| 108 |
2/2✓ Branch 13 → 9 taken 5095 times.
✓ Branch 13 → 14 taken 1019 times.
|
11209 | for (const QString &part : {address.street, address.postalCode, address.locality, address.region, address.country}) |
| 109 |
4/6✓ Branch 9 → 10 taken 5095 times.
✓ Branch 10 → 11 taken 5095 times.
✓ Branch 15 → 16 taken 5095 times.
✓ Branch 15 → 19 taken 1019 times.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 35 not taken.
|
11209 | parts << GeoData::key(part); |
| 110 |
1/1✓ Branch 22 → 23 taken 1019 times.
|
2038 | return parts.join(QLatin1Char('|')); |
| 111 | 1019 | } | |
| 112 | |||
| 113 | 1133 | Place locate(const GeoData &data, const ContactAddress &address, const QString &defaultCountry) { | |
| 114 |
1/1✓ Branch 2 → 3 taken 1133 times.
|
1133 | const int country = countryOf(data, address.country, defaultCountry); |
| 115 |
3/3✓ Branch 3 → 4 taken 564 times.
✓ Branch 3 → 7 taken 569 times.
✓ Branch 6 → 8 taken 564 times.
|
1133 | const QString iso = country >= 0 ? QString::fromLatin1(data.countries().at(country).iso2) : QString(); |
| 116 | // 1. Postal code (DE/AT/CH are bundled). | ||
| 117 |
7/7✓ Branch 8 → 9 taken 1133 times.
✓ Branch 10 → 11 taken 778 times.
✓ Branch 10 → 13 taken 355 times.
✓ Branch 11 → 12 taken 223 times.
✓ Branch 11 → 13 taken 555 times.
✓ Branch 14 → 15 taken 223 times.
✓ Branch 14 → 36 taken 910 times.
|
1133 | if (const QString code = normalizedPostalCode(iso, address.postalCode); !code.isEmpty() && country >= 0) { |
| 118 |
3/3✓ Branch 15 → 16 taken 223 times.
✓ Branch 16 → 17 taken 106 times.
✓ Branch 16 → 36 taken 117 times.
|
223 | if (const int postal = data.postalIndex(country, code); postal >= 0) { |
| 119 |
1/1✓ Branch 17 → 18 taken 106 times.
|
106 | const GeoData::Postal entry = data.postal(postal); |
| 120 |
1/3✓ Branch 21 → 22 taken 106 times.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 113 not taken.
|
106 | Place place = placeAt(data, entry.lat, entry.lon, QStringLiteral("postal"), country); |
| 121 |
1/1✓ Branch 24 → 25 taken 106 times.
|
106 | place.placeName = data.string(entry.place); |
| 122 |
1/1✓ Branch 27 → 28 taken 106 times.
|
106 | const int city = bestCity(data, place.placeName, country, -1); |
| 123 |
2/2✓ Branch 28 → 29 taken 92 times.
✓ Branch 28 → 33 taken 14 times.
|
106 | if (city >= 0) |
| 124 |
1/1✓ Branch 29 → 30 taken 92 times.
|
92 | place.timeZone = cityPlace(data, city).timeZone; |
| 125 |
1/2✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 106 times.
|
106 | return place; |
| 126 | ✗ | } | |
| 127 | 1133 | } | |
| 128 | // 2. City (with the region as a tie-breaker). | ||
| 129 |
2/2✓ Branch 43 → 44 taken 5 times.
✓ Branch 43 → 47 taken 453 times.
|
458 | const int region = country >= 0 && !address.region.isEmpty() |
| 130 |
4/6✓ Branch 40 → 42 taken 458 times.
✓ Branch 40 → 47 taken 569 times.
✓ Branch 44 → 45 taken 5 times.
✓ Branch 45 → 46 taken 5 times.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 120 not taken.
|
1490 | ? data.admin1Index(country, GeoData::key(address.region)) |
| 131 |
2/2✓ Branch 48 → 49 taken 5 times.
✓ Branch 48 → 50 taken 1022 times.
|
1027 | : -1; |
| 132 |
5/5✓ Branch 50 → 51 taken 1027 times.
✓ Branch 51 → 52 taken 1027 times.
✓ Branch 52 → 53 taken 1027 times.
✓ Branch 61 → 54 taken 1032 times.
✓ Branch 61 → 62 taken 58 times.
|
1090 | for (const QString &form : localityForms(address.locality)) |
| 133 |
3/3✓ Branch 55 → 56 taken 1032 times.
✓ Branch 56 → 57 taken 969 times.
✓ Branch 56 → 59 taken 63 times.
|
1032 | if (const int city = bestCity(data, form, country, region); city >= 0) |
| 134 |
3/3✓ Branch 57 → 58 taken 969 times.
✓ Branch 64 → 65 taken 58 times.
✓ Branch 64 → 67 taken 969 times.
|
1027 | return cityPlace(data, city); |
| 135 | // 3. Region. | ||
| 136 |
2/2✓ Branch 66 → 68 taken 3 times.
✓ Branch 66 → 88 taken 55 times.
|
58 | if (region >= 0) { |
| 137 | 3 | const GeoData::Admin1 &admin = data.admin1().at(region); | |
| 138 |
1/1✓ Branch 73 → 74 taken 3 times.
|
3 | Place place = placeAt(data, admin.lat, admin.lon, QStringLiteral("region"), country); |
| 139 |
2/2✓ Branch 76 → 77 taken 3 times.
✓ Branch 80 → 81 taken 3 times.
|
6 | place.placeName = data.string(admin.names).section(QLatin1Char('|'), 0, 0); |
| 140 | 3 | return place; | |
| 141 | 3 | } | |
| 142 | // 4. Country — only when the address names one (a default country alone | ||
| 143 | // places nothing: an address of only a street is not "in Germany"). | ||
| 144 |
9/11✓ Branch 88 → 89 taken 44 times.
✓ Branch 88 → 93 taken 11 times.
✓ Branch 89 → 90 taken 44 times.
✓ Branch 91 → 92 taken 20 times.
✓ Branch 91 → 93 taken 24 times.
✓ Branch 94 → 95 taken 44 times.
✓ Branch 94 → 96 taken 11 times.
✓ Branch 96 → 97 taken 20 times.
✓ Branch 96 → 99 taken 35 times.
✗ Branch 141 → 142 not taken.
✗ Branch 141 → 143 not taken.
|
55 | if (country >= 0 && !address.country.trimmed().isEmpty()) |
| 145 |
1/1✓ Branch 97 → 98 taken 20 times.
|
20 | return countryPlace(data, country); |
| 146 | 35 | return {}; | |
| 147 | 1133 | } | |
| 148 | |||
| 149 | 65 | Place locateText(const GeoData &data, const QString &text, const QString &defaultCountry) { | |
| 150 | 65 | QStringList parts; | |
| 151 |
5/5✓ Branch 5 → 6 taken 65 times.
✓ Branch 6 → 7 taken 65 times.
✓ Branch 7 → 8 taken 65 times.
✓ Branch 20 → 9 taken 78 times.
✓ Branch 20 → 21 taken 65 times.
|
143 | for (const QString &part : text.split(QLatin1Char(','), Qt::SkipEmptyParts)) |
| 152 |
2/3✓ Branch 10 → 11 taken 78 times.
✓ Branch 13 → 14 taken 78 times.
✗ Branch 13 → 18 not taken.
|
78 | if (!part.trimmed().isEmpty()) |
| 153 |
2/2✓ Branch 14 → 15 taken 78 times.
✓ Branch 15 → 16 taken 78 times.
|
143 | parts << part.trimmed(); |
| 154 |
1/2✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 65 times.
|
65 | if (parts.isEmpty()) |
| 155 | ✗ | return {}; | |
| 156 | 65 | int country = -1; | |
| 157 |
2/2✓ Branch 26 → 27 taken 13 times.
✓ Branch 26 → 31 taken 52 times.
|
65 | if (parts.size() > 1) { |
| 158 |
2/2✓ Branch 27 → 28 taken 13 times.
✓ Branch 28 → 29 taken 13 times.
|
13 | country = data.countryIndex(parts.last()); |
| 159 |
2/2✓ Branch 29 → 30 taken 1 time.
✓ Branch 29 → 31 taken 12 times.
|
13 | if (country >= 0) |
| 160 | 1 | parts.removeLast(); | |
| 161 | } | ||
| 162 |
10/10✓ Branch 31 → 32 taken 64 times.
✓ Branch 31 → 38 taken 1 time.
✓ Branch 33 → 34 taken 52 times.
✓ Branch 33 → 38 taken 12 times.
✓ Branch 34 → 35 taken 52 times.
✓ Branch 35 → 36 taken 52 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 38 taken 50 times.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 39 → 44 taken 63 times.
|
65 | if (country < 0 && parts.size() == 1 && data.countryIndex(parts.first()) >= 0) |
| 163 |
3/3✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
|
2 | return countryPlace(data, data.countryIndex(parts.first())); |
| 164 |
2/2✓ Branch 75 → 46 taken 63 times.
✓ Branch 75 → 76 taken 2 times.
|
65 | for (int i = int(parts.size()) - 1; i >= 0; --i) { |
| 165 |
5/5✓ Branch 47 → 48 taken 63 times.
✓ Branch 48 → 49 taken 63 times.
✓ Branch 49 → 50 taken 63 times.
✓ Branch 68 → 51 taken 65 times.
✓ Branch 68 → 69 taken 2 times.
|
67 | for (const QString &form : localityForms(parts.at(i))) { |
| 166 |
1/1✓ Branch 52 → 53 taken 65 times.
|
65 | int city = bestCity(data, form, country, -1); |
| 167 |
5/8✓ Branch 53 → 54 taken 4 times.
✓ Branch 53 → 58 taken 61 times.
✓ Branch 54 → 55 taken 4 times.
✗ Branch 54 → 58 not taken.
✗ Branch 56 → 57 not taken.
✓ Branch 56 → 58 taken 4 times.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 63 taken 65 times.
|
65 | if (city < 0 && country < 0 && !defaultCountry.isEmpty()) |
| 168 | ✗ | city = bestCity(data, form, data.countryIndex(defaultCountry), -1); | |
| 169 |
2/2✓ Branch 63 → 64 taken 61 times.
✓ Branch 63 → 66 taken 4 times.
|
65 | if (city >= 0) |
| 170 |
1/1✓ Branch 64 → 65 taken 61 times.
|
61 | return cityPlace(data, city); |
| 171 | 63 | } | |
| 172 | } | ||
| 173 |
1/3✗ Branch 76 → 77 not taken.
✓ Branch 76 → 78 taken 2 times.
✗ Branch 77 → 79 not taken.
|
2 | return country >= 0 ? countryPlace(data, country) : Place{}; |
| 174 | 65 | } | |
| 175 | |||
| 176 | } // namespace PlaceGazetteer | ||
| 177 |