Skip to content

Commit a9774aa

Browse files
committed
Add Nominatim-based address resolution
1 parent abb7477 commit a9774aa

3 files changed

Lines changed: 579 additions & 69 deletions

File tree

builder/src/address_levels.hpp

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <cstring>
5+
#include <string>
6+
#include <unordered_map>
7+
#include <vector>
8+
9+
// Address level rank lookup, based on Nominatim's settings/address-levels.json.
10+
// https://github.com/osm-search/Nominatim/blob/master/settings/address-levels.json
11+
//
12+
// Ranks are mapped to semantic levels (country/state/county/city/suburb/postcode)
13+
// in the get_semantic() helper. Tag values for boundary=administrative use the form
14+
// "administrative<N>" where N is the admin_level.
15+
16+
namespace address_levels {
17+
18+
// Each rule lists (tag_key, tag_value) -> (polygon_rank, node_rank).
19+
// A rank of -1 means the tag doesn't produce an address entry in that form.
20+
struct Rule {
21+
const char* key;
22+
const char* value;
23+
int8_t polygon_rank;
24+
int8_t node_rank;
25+
};
26+
27+
// Default rules (used when no country override matches).
28+
static const Rule kDefaultRules[] = {
29+
// boundary=administrative
30+
{"boundary", "administrative2", 4, 4},
31+
{"boundary", "administrative3", 6, 6},
32+
{"boundary", "administrative4", 8, 8},
33+
{"boundary", "administrative5", 10, 10},
34+
{"boundary", "administrative6", 12, 12},
35+
{"boundary", "administrative7", 14, 14},
36+
{"boundary", "administrative8", 16, 16},
37+
{"boundary", "administrative9", 18, 18},
38+
{"boundary", "administrative10", 20, 20},
39+
{"boundary", "administrative11", 22, 22},
40+
{"boundary", "administrative12", 24, 24},
41+
// place=* (polygon rank, node rank)
42+
{"place", "country", 4, 4},
43+
{"place", "state", 8, 8},
44+
{"place", "province", 8, 8},
45+
{"place", "county", 12, 12},
46+
{"place", "city", 16, 16},
47+
{"place", "town", 18, 16},
48+
{"place", "village", 19, 16},
49+
{"place", "borough", 18, 18},
50+
{"place", "suburb", 19, 20},
51+
{"place", "hamlet", 20, 20},
52+
{"place", "neighbourhood", 24, 24},
53+
{"place", "quarter", 20, 22},
54+
{"place", "locality", 25, 25},
55+
};
56+
57+
// Country-specific overrides (sparse - only deltas from defaults).
58+
struct CountryOverride {
59+
const char* country_code; // lowercase ISO 3166-1 alpha-2
60+
const Rule* rules;
61+
size_t rule_count;
62+
};
63+
64+
static const Rule kAuRules[] = {{"boundary", "administrative6", 12, 0}};
65+
static const Rule kCaRules[] = {{"place", "county", 12, 0}};
66+
static const Rule kCzRules[] = {
67+
{"boundary", "administrative5", 12, 12},
68+
{"boundary", "administrative6", 13, 0},
69+
{"boundary", "administrative7", 14, 0},
70+
{"boundary", "administrative8", 14, 14},
71+
{"boundary", "administrative9", 15, 15},
72+
{"boundary", "administrative10", 16, 16},
73+
};
74+
static const Rule kDeRules[] = {
75+
{"place", "region", 10, 0},
76+
{"place", "county", 12, 0},
77+
{"boundary", "administrative5", 10, 0},
78+
};
79+
static const Rule kBeRules[] = {
80+
{"boundary", "administrative3", 5, 0},
81+
{"boundary", "administrative4", 6, 6},
82+
{"boundary", "administrative5", 7, 0},
83+
{"boundary", "administrative6", 8, 8},
84+
{"boundary", "administrative7", 12, 12},
85+
{"boundary", "administrative8", 14, 14},
86+
{"boundary", "administrative9", 16, 16},
87+
{"boundary", "administrative10", 18, 18},
88+
};
89+
static const Rule kBrRules[] = {
90+
{"boundary", "administrative5", 10, 0},
91+
{"boundary", "administrative6", 12, 0},
92+
{"boundary", "administrative7", 14, 0},
93+
};
94+
static const Rule kNordicRules[] = {
95+
{"boundary", "administrative3", 8, 8},
96+
{"boundary", "administrative4", 12, 12},
97+
};
98+
static const Rule kIdRules[] = {
99+
{"place", "municipality", 18, 18},
100+
{"boundary", "administrative5", 12, 12},
101+
{"boundary", "administrative6", 14, 14},
102+
{"boundary", "administrative7", 16, 16},
103+
{"boundary", "administrative8", 20, 20},
104+
{"boundary", "administrative9", 22, 22},
105+
{"boundary", "administrative10", 24, 24},
106+
};
107+
static const Rule kRuRules[] = {
108+
{"place", "municipality", 18, 18},
109+
{"boundary", "administrative5", 10, 0},
110+
{"boundary", "administrative7", 13, 0},
111+
{"boundary", "administrative8", 14, 14},
112+
};
113+
static const Rule kNlRules[] = {
114+
{"boundary", "administrative7", 13, 0},
115+
{"boundary", "administrative8", 14, 14},
116+
{"boundary", "administrative9", 15, 0},
117+
{"boundary", "administrative10", 16, 16},
118+
};
119+
static const Rule kEsRules[] = {
120+
{"place", "province", 10, 10},
121+
{"place", "civil_parish", 18, 18},
122+
{"boundary", "administrative5", 10, 0},
123+
{"boundary", "administrative6", 10, 10},
124+
{"boundary", "administrative7", 12, 12},
125+
{"boundary", "administrative10", 22, 22},
126+
};
127+
static const Rule kSaRules[] = {
128+
{"place", "province", 12, 12},
129+
{"place", "municipality", 18, 18},
130+
};
131+
static const Rule kJpRules[] = {
132+
{"boundary", "administrative7", 16, 16},
133+
{"boundary", "administrative8", 18, 18},
134+
{"boundary", "administrative9", 20, 20},
135+
{"boundary", "administrative10", 22, 22},
136+
{"boundary", "administrative11", 24, 24},
137+
};
138+
139+
static const CountryOverride kCountryOverrides[] = {
140+
{"au", kAuRules, sizeof(kAuRules)/sizeof(Rule)},
141+
{"ca", kCaRules, sizeof(kCaRules)/sizeof(Rule)},
142+
{"cz", kCzRules, sizeof(kCzRules)/sizeof(Rule)},
143+
{"de", kDeRules, sizeof(kDeRules)/sizeof(Rule)},
144+
{"be", kBeRules, sizeof(kBeRules)/sizeof(Rule)},
145+
{"br", kBrRules, sizeof(kBrRules)/sizeof(Rule)},
146+
{"se", kNordicRules, sizeof(kNordicRules)/sizeof(Rule)},
147+
{"no", kNordicRules, sizeof(kNordicRules)/sizeof(Rule)},
148+
{"id", kIdRules, sizeof(kIdRules)/sizeof(Rule)},
149+
{"ru", kRuRules, sizeof(kRuRules)/sizeof(Rule)},
150+
{"nl", kNlRules, sizeof(kNlRules)/sizeof(Rule)},
151+
{"es", kEsRules, sizeof(kEsRules)/sizeof(Rule)},
152+
{"sa", kSaRules, sizeof(kSaRules)/sizeof(Rule)},
153+
{"jp", kJpRules, sizeof(kJpRules)/sizeof(Rule)},
154+
};
155+
156+
// Semantic level enum stored in the index.
157+
enum class Semantic : uint8_t {
158+
None = 0,
159+
Country = 1,
160+
State = 2,
161+
County = 3,
162+
City = 4,
163+
Suburb = 5,
164+
Postcode = 6,
165+
};
166+
167+
// Look up rank for (country, tag_key, tag_value). is_polygon picks polygon vs node rank.
168+
// Returns -1 if no rule matches (caller should skip this entry).
169+
inline int8_t lookup_rank(const char* country_code, const char* key,
170+
const char* value, bool is_polygon) {
171+
auto find_in = [&](const Rule* rules, size_t n) -> int8_t {
172+
for (size_t i = 0; i < n; i++) {
173+
if (std::strcmp(rules[i].key, key) == 0 &&
174+
std::strcmp(rules[i].value, value) == 0) {
175+
int8_t r = is_polygon ? rules[i].polygon_rank : rules[i].node_rank;
176+
return r;
177+
}
178+
}
179+
return -1;
180+
};
181+
182+
if (country_code && country_code[0]) {
183+
char lower[3] = {static_cast<char>(std::tolower(country_code[0])),
184+
static_cast<char>(std::tolower(country_code[1])), 0};
185+
for (const auto& ov : kCountryOverrides) {
186+
if (std::strcmp(ov.country_code, lower) == 0) {
187+
int8_t r = find_in(ov.rules, ov.rule_count);
188+
if (r >= 0) return r;
189+
break;
190+
}
191+
}
192+
}
193+
return find_in(kDefaultRules, sizeof(kDefaultRules)/sizeof(Rule));
194+
}
195+
196+
// Map a Nominatim rank to our semantic level. Returns Semantic::None for
197+
// ranks outside our address chain (highway, building, etc.).
198+
inline Semantic rank_to_semantic(int8_t rank) {
199+
if (rank >= 4 && rank <= 7) return Semantic::Country;
200+
if (rank >= 8 && rank <= 11) return Semantic::State;
201+
if (rank >= 12 && rank <= 15) return Semantic::County;
202+
if (rank >= 16 && rank <= 17) return Semantic::City;
203+
if (rank >= 18 && rank <= 21) return Semantic::Suburb;
204+
return Semantic::None;
205+
}
206+
207+
} // namespace address_levels

0 commit comments

Comments
 (0)