Repository navigation
Expand file tree
/
Copy pathNominatim.cpp
More file actions
253 lines (210 loc) · 8.68 KB
/
Copy pathNominatim.cpp
File metadata and controls
253 lines (210 loc) · 8.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
// Reverse geocoding using Nominatim (OpenStreetMap)
// Replaces BigDataCloud with a completely free, keyless, worldwide service.
// Usage policy: https://operations.osmfoundation.org/policies/nominatim/
// - No bulk requests (we only call once at boot — fine)
// - Requires a descriptive User-Agent header (set below)
//
// Streaming JSON parser:
// https://github.com/Bodmer/JSON_Decoder
#include <WiFiClientSecure.h> // need an HTTPS connection
#include <HTTPClient.h>
#include <JSON_Listener.h>
#include <JSON_Decoder.h>
#include "Nominatim.h"
static const char *NOM_HOST = "nominatim.openstreetmap.org";
// Identify your app in the User-Agent as required by Nominatim's usage policy
static const char *NOM_USER_AGENT = "ESP32-WeatherDisplay/1.0";
/***************************************************************************************
** Function name: getBDCLocation
** Description: Request reverse geocode from Nominatim.
** Signature unchanged so sketch call needs no modification.
** 'language' parameter accepted but unused (Nominatim returns
** English names by default for most locations worldwide).
***************************************************************************************/
bool BDC_location::getBDCLocation(BDC_current *BDCcurrent, String latitude, String longitude, String language) {
BDCdata_set = "";
this->BDCcurrent = BDCcurrent;
// Nominatim reverse geocode endpoint
// zoom=14 returns neighbourhood/suburb level detail
// addressdetails=1 returns the structured address object we need
// String url = "https://" + String(NOM_HOST)
// + "/reverse?lat=" + latitude
// + "&lon=" + longitude
// + "&format=json"
// + "&addressdetails=1"
// + "&zoom=14"
// + "&accept-language=en";
String url = "https://" + String(NOM_HOST)
+ "/reverse?lat=" + latitude
+ "&lon=" + longitude
+ "&format=json"
+ "&addressdetails=1"
+ "&zoom=12" // city level instead of neighbourhood
+ "&accept-language=en";
bool result = parseBDCRequest(url);
this->BDCcurrent = nullptr;
return result;
}
/***************************************************************************************
** Function name: parseBDCRequest
** Description: Fetches the Nominatim JSON response and feeds to the parser.
** Switched from a hand-rolled WiFiClientSecure parser to HTTPClient — the old version
** had the same busy-wait bug the original Open-Meteo fetch had: the timeout check and
** yield() only lived inside the inner "while available()" loop, so a connection that
** stayed open but idle (no more data, no clean close) could spin loop() forever with
** no yield and no way out. HTTPClient::getString() removes that whole class of bug
** and correctly handles chunked transfer-encoding if Nominatim ever sends it.
***************************************************************************************/
bool BDC_location::parseBDCRequest(String url) {
uint32_t dt = millis();
WiFiClientSecure client;
client.setInsecure(); // skip cert validation
HTTPClient http;
http.setConnectTimeout(8000); // bounds TCP connect + TLS handshake
http.setTimeout(12000); // bounds the read phase
parseOK = false;
if (!http.begin(client, url)) {
Serial.println("Nominatim: HTTPClient begin() failed");
return false;
}
// Nominatim requires a User-Agent header identifying the application
http.addHeader("User-Agent", NOM_USER_AGENT);
// Force the connection closed after this response — same reasoning as
// the Open-Meteo fetch: the original hand-rolled request always sent
// "Connection: close", and leaving it to HTTPClient's default keep-alive
// could be leaving a socket lingering into the next fetch cycle.
http.addHeader("Connection", "close");
Serial.println("Sending GET request to nominatim.openstreetmap.org...");
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
Serial.printf("Nominatim HTTP error: %d\n", httpCode);
http.end();
return false;
}
String payload = http.getString(); // HTTPClient dechunks correctly here
http.end();
if (payload.length() == 0) {
Serial.println("Nominatim: empty response body");
return false;
}
JSON_Decoder parser;
parser.setListener(this);
#ifdef SHOW_BDCJSON
int ccount = 0;
#endif
size_t len = payload.length();
for (size_t i = 0; i < len; i++) {
char c = payload[i];
parser.parse(c);
#ifdef SHOW_BDCJSON
if (c == '{' || c == '[' || c == '}' || c == ']') Serial.println();
if (ccount++ > 100 && c == ',') {
ccount = 0;
Serial.println();
}
#endif
if ((i & 0x3F) == 0) yield(); // give the scheduler a turn periodically during the parse
}
parser.reset();
Serial.print("Nominatim done in ");
Serial.print(millis() - dt);
Serial.println(" ms");
return parseOK;
}
// ===========================================================================
// Streaming parser callbacks
// ===========================================================================
void BDC_location::key(const char *key) {
BDCcurrentKey = key;
#ifdef SHOW_BDCCALLBACK
Serial.println("\n>>> Key >>> " + (String)key);
#endif
}
void BDC_location::startDocument() {
BDCcurrentParent = BDCcurrentKey = BDCcurrentSet = "";
BDCobjectLevel = BDCarrayLevel = BDCarrayIndex = 0;
BDCvaluePath = "";
parseOK = true;
#ifdef SHOW_BDCCALLBACK
Serial.println("\n>>> Nominatim Start document >>>");
#endif
}
void BDC_location::endDocument() {
BDCcurrentParent = BDCcurrentKey = "";
BDCobjectLevel = 0;
BDCvaluePath = "";
#ifdef SHOW_BDCCALLBACK
Serial.println("\n<<< Nominatim End document <<<");
#endif
}
void BDC_location::startObject() {
// Track which sub-object we're in — "address" is the key one
if (BDCobjectLevel >= 1) BDCcurrentParent = BDCcurrentKey;
BDCcurrentSet = BDCcurrentKey;
BDCobjectLevel++;
#ifdef SHOW_BDCCALLBACK
Serial.println("\n>>> Start object level:" + (String)BDCobjectLevel + " parent:" + BDCcurrentParent);
#endif
}
void BDC_location::endObject() {
BDCobjectLevel--;
if (BDCobjectLevel <= 1) BDCcurrentParent = "";
#ifdef SHOW_BDCCALLBACK
Serial.println("\n<<< End object <<<");
#endif
}
void BDC_location::startArray() {
BDCarrayLevel++;
}
void BDC_location::endArray() {
if (BDCarrayLevel > 0) BDCarrayLevel--;
}
void BDC_location::whitespace(char c) {}
void BDC_location::error(const char *message) {
Serial.print("\nNominatim parse error: ");
Serial.println(message);
parseOK = false;
}
void BDC_location::value(const char *val) {
fullDataSet(val);
}
/***************************************************************************************
** Function name: fullDataSet
** Description: Map Nominatim JSON fields to BDC_current struct.
**
** Nominatim response structure (abbreviated):
** {
** "lat": "50.1660",
** "lon": "-132.7768",
** "display_name": "Surrey, Metro Vancouver, British Columbia, Canada",
** "address": {
** "suburb": "Whalley", → locality
** "city": "Surrey", → city
** "county": "Metro Vancouver", → (not stored)
** "state": "British Columbia", → principalSubdivision
** "country": "Canada", → countryName
** "country_code": "ca", → countryCode
** "postcode": "V3Y", → postcode
** }
** }
**
** Note: not all fields appear for every location. For towns without "city",
** Nominatim may use "town" or "village" instead — all are checked below.
***************************************************************************************/
void BDC_location::fullDataSet(const char *val) {
String value = val;
// Serial.print("parent: "); Serial.print(BDCcurrentParent);
// Serial.print(" key: "); Serial.print(BDCcurrentKey);
// Serial.print(" value: "); Serial.println(value);
if (BDCcurrentParent == "" || BDCcurrentParent == "reverse") {
if (BDCcurrentKey == "lat") BDCcurrent->latitude = value.toFloat();
else if (BDCcurrentKey == "lon") BDCcurrent->longitude = value.toFloat();
else if (BDCcurrentKey == "name" && !value.isEmpty()) BDCcurrent->locality = value;
}
if (BDCcurrentParent == "address") {
if (BDCcurrentKey == "city" && BDCcurrent->city.isEmpty()) BDCcurrent->city = value;
else if (BDCcurrentKey == "town" && BDCcurrent->city.isEmpty()) BDCcurrent->city = value;
else if (BDCcurrentKey == "village" && BDCcurrent->city.isEmpty()) BDCcurrent->city = value;
else if (BDCcurrentKey == "hamlet" && BDCcurrent->city.isEmpty()) BDCcurrent->city = value;
}
}