1 | /* |
2 | SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | |
7 | #include "kcountrysubdivision.h" |
8 | #include "isocodes_p.h" |
9 | #include "isocodescache_p.h" |
10 | #include "kcountry.h" |
11 | #include "ki18n_logging.h" |
12 | #include "klocalizedstring.h" |
13 | #include "spatial_index_p.h" |
14 | #include "timezonedata_p.h" |
15 | |
16 | #include <cstring> |
17 | |
18 | static_assert(sizeof(KCountrySubdivision) == 4); |
19 | |
20 | KCountrySubdivision::KCountrySubdivision() |
21 | : d(0) |
22 | { |
23 | } |
24 | |
25 | KCountrySubdivision::KCountrySubdivision(const KCountrySubdivision &) = default; |
26 | KCountrySubdivision::~KCountrySubdivision() = default; |
27 | KCountrySubdivision &KCountrySubdivision::operator=(const KCountrySubdivision &) = default; |
28 | |
29 | bool KCountrySubdivision::operator==(const KCountrySubdivision &other) const |
30 | { |
31 | return d == other.d; |
32 | } |
33 | |
34 | bool KCountrySubdivision::operator!=(const KCountrySubdivision &other) const |
35 | { |
36 | return d != other.d; |
37 | } |
38 | |
39 | bool KCountrySubdivision::isValid() const |
40 | { |
41 | return d != 0; |
42 | } |
43 | |
44 | QString KCountrySubdivision::code() const |
45 | { |
46 | if (d == 0) { |
47 | return {}; |
48 | } |
49 | |
50 | QString code; |
51 | code.reserve(asize: 6); |
52 | code += country().alpha2(); |
53 | code += QLatin1Char('-'); |
54 | |
55 | auto key = d & 0xffff; |
56 | while (key) { |
57 | const auto c = IsoCodes::mapFromAlphaNumKey(key); |
58 | if (c) { |
59 | code.insert(i: 3, c: QLatin1Char(c)); |
60 | } |
61 | key /= IsoCodes::AlphaNumKeyFactor; |
62 | } |
63 | |
64 | return code; |
65 | } |
66 | |
67 | QString KCountrySubdivision::name() const |
68 | { |
69 | if (d == 0) { |
70 | return {}; |
71 | } |
72 | |
73 | auto cache = IsoCodesCache::instance(); |
74 | cache->loadIso3166_2(); |
75 | const auto it = std::lower_bound(first: cache->subdivisionNameMapBegin(), last: cache->subdivisionNameMapEnd(), val: d); |
76 | if (it != cache->subdivisionNameMapEnd() && (*it).key == d) { |
77 | return i18nd(domain: "iso_3166-2" , text: cache->subdivisionStringTableLookup(offset: (*it).value)); |
78 | } |
79 | return {}; |
80 | } |
81 | |
82 | KCountry KCountrySubdivision::country() const |
83 | { |
84 | KCountry c; |
85 | c.d = d >> 16; |
86 | return c; |
87 | } |
88 | |
89 | KCountrySubdivision KCountrySubdivision::parent() const |
90 | { |
91 | KCountrySubdivision s; |
92 | if (d == 0) { |
93 | return s; |
94 | } |
95 | |
96 | auto cache = IsoCodesCache::instance(); |
97 | cache->loadIso3166_2(); |
98 | |
99 | const auto it = std::lower_bound(first: cache->subdivisionParentMapBegin(), last: cache->subdivisionParentMapEnd(), val: d); |
100 | if (it != cache->subdivisionParentMapEnd() && (*it).key == d) { |
101 | s.d = (d & 0xffff0000) | (uint32_t)(*it).value; |
102 | } |
103 | |
104 | return s; |
105 | } |
106 | |
107 | QList<const char *> KCountrySubdivision::timeZoneIds() const |
108 | { |
109 | QList<const char *> tzs; |
110 | if (d == 0) { |
111 | return tzs; |
112 | } |
113 | |
114 | const auto [subdivBegin, subdivEnd] = std::equal_range(first: TimezoneData::subdivisionTimezoneMapBegin(), last: TimezoneData::subdivisionTimezoneMapEnd(), val: d); |
115 | if (subdivBegin != subdivEnd) { |
116 | tzs.reserve(asize: std::distance(first: subdivBegin, last: subdivEnd)); |
117 | for (auto it = subdivBegin; it != subdivEnd; ++it) { |
118 | tzs.push_back(t: TimezoneData::ianaIdLookup(offset: (*it).value)); |
119 | } |
120 | return tzs; |
121 | } |
122 | |
123 | const auto countryIt = std::lower_bound(first: TimezoneData::countryTimezoneMapBegin(), last: TimezoneData::countryTimezoneMapEnd(), val: uint16_t(d >> 16)); |
124 | if (countryIt != TimezoneData::countryTimezoneMapEnd() && (*countryIt).key == (d >> 16)) { |
125 | tzs.push_back(t: TimezoneData::ianaIdLookup(offset: (*countryIt).value)); |
126 | } |
127 | |
128 | return tzs; |
129 | } |
130 | |
131 | QList<KCountrySubdivision> KCountrySubdivision::subdivisions() const |
132 | { |
133 | if (d == 0) { |
134 | return {}; |
135 | } |
136 | |
137 | QList<KCountrySubdivision> l; |
138 | auto cache = IsoCodesCache::instance(); |
139 | cache->loadIso3166_2(); |
140 | // we don't have a parent->child map, instead we use the child->parent map |
141 | // that is sorted by country (due to the country being in the two most significant bytes of its key), |
142 | // so we don't need to do a full sequential search here |
143 | auto it = std::lower_bound(first: cache->subdivisionParentMapBegin(), last: cache->subdivisionParentMapEnd(), val: d >> 16, comp: [](auto lhs, auto rhs) { |
144 | return (lhs.key >> 16) < rhs; |
145 | }); |
146 | for (; it != cache->subdivisionParentMapEnd() && ((*it).key >> 16) == (d >> 16); ++it) { |
147 | if ((*it).value == (d & 0xffff)) { |
148 | KCountrySubdivision s; |
149 | s.d = (*it).key; |
150 | l.push_back(t: s); |
151 | } |
152 | } |
153 | |
154 | return l; |
155 | } |
156 | |
157 | static uint32_t validatedSubdivisionKey(uint32_t key) |
158 | { |
159 | if (!key) { |
160 | return 0; |
161 | } |
162 | |
163 | auto cache = IsoCodesCache::instance(); |
164 | cache->loadIso3166_2(); |
165 | |
166 | const auto it = std::lower_bound(first: cache->subdivisionNameMapBegin(), last: cache->subdivisionNameMapEnd(), val: key); |
167 | if (it != cache->subdivisionNameMapEnd() && (*it).key == key) { |
168 | return key; |
169 | } |
170 | return 0; |
171 | } |
172 | |
173 | KCountrySubdivision KCountrySubdivision::fromCode(QStringView code) |
174 | { |
175 | KCountrySubdivision s; |
176 | s.d = validatedSubdivisionKey(key: IsoCodes::subdivisionCodeToKey(code)); |
177 | return s; |
178 | } |
179 | |
180 | KCountrySubdivision KCountrySubdivision::fromCode(const char *code) |
181 | { |
182 | KCountrySubdivision s; |
183 | if (code) { |
184 | s.d = validatedSubdivisionKey(key: IsoCodes::subdivisionCodeToKey(code, size: std::strlen(s: code))); |
185 | } |
186 | return s; |
187 | } |
188 | |
189 | KCountrySubdivision KCountrySubdivision::fromLocation(float latitude, float longitude) |
190 | { |
191 | const auto entry = SpatialIndex::lookup(lat: latitude, lon: longitude); |
192 | KCountrySubdivision s; |
193 | if (entry.m_subdiv & 0xffff) { |
194 | s.d = entry.m_subdiv; |
195 | } |
196 | return s; |
197 | } |
198 | |
199 | QStringList KCountrySubdivision::timeZoneIdsStringList() const |
200 | { |
201 | const auto tzIds = timeZoneIds(); |
202 | QStringList l; |
203 | l.reserve(asize: tzIds.size()); |
204 | std::transform(first: tzIds.begin(), last: tzIds.end(), result: std::back_inserter(x&: l), unary_op: [](const char *tzId) { |
205 | return QString::fromUtf8(utf8: tzId); |
206 | }); |
207 | return l; |
208 | } |
209 | |
210 | #include "moc_kcountrysubdivision.cpp" |
211 | |