signalk-race-control 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +60 -0
- package/package.json +1 -1
- package/public/app.js +34 -21
package/index.js
CHANGED
|
@@ -2172,6 +2172,66 @@ module.exports = function (app) {
|
|
|
2172
2172
|
res.json({ currentRaceId: state.currentRaceId, races });
|
|
2173
2173
|
});
|
|
2174
2174
|
|
|
2175
|
+
// Proxies an Overpass API query (used by the course editor's OpenSeaMap
|
|
2176
|
+
// nav-mark search/snap) through this server rather than the browser
|
|
2177
|
+
// calling a public Overpass mirror directly. Two problems forced this:
|
|
2178
|
+
// overpass-api.de — the one mirror actually verified to carry real,
|
|
2179
|
+
// complete seamark data — sends no Access-Control-Allow-Origin header,
|
|
2180
|
+
// so a browser fetch to it is blocked outright regardless of whether
|
|
2181
|
+
// the request itself would succeed; and of the two mirrors that do
|
|
2182
|
+
// support CORS, one has been unreachable and the other has been
|
|
2183
|
+
// observed returning a confident HTTP 200 with zero elements for real,
|
|
2184
|
+
// densely-tagged areas. A server-to-server request isn't subject to
|
|
2185
|
+
// CORS at all, so this can freely use the mirror that actually works.
|
|
2186
|
+
const OVERPASS_ENDPOINTS = [
|
|
2187
|
+
'https://overpass-api.de/api/interpreter',
|
|
2188
|
+
'https://overpass.kumi.systems/api/interpreter',
|
|
2189
|
+
'https://overpass.osm.ch/api/interpreter'
|
|
2190
|
+
];
|
|
2191
|
+
router.get('/overpass', async (req, res) => {
|
|
2192
|
+
const query = req.query.data;
|
|
2193
|
+
if (!query) return res.status(400).json({ error: 'Missing data query parameter' });
|
|
2194
|
+
// overpass-api.de has the best data but is also the most rate-
|
|
2195
|
+
// limited/overloaded public instance — it can 504 one moment and
|
|
2196
|
+
// work fine a couple of seconds later. A valid-but-empty response
|
|
2197
|
+
// from a worse mirror is accepted only as a last resort (remembered,
|
|
2198
|
+
// not returned immediately), and the whole mirror list gets a second
|
|
2199
|
+
// pass after a short pause before actually giving up — otherwise a
|
|
2200
|
+
// single transient overload on the good mirror would get cached by
|
|
2201
|
+
// the client as "nothing charted here" for the rest of the session.
|
|
2202
|
+
let emptyResult = null;
|
|
2203
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
2204
|
+
for (const base of OVERPASS_ENDPOINTS) {
|
|
2205
|
+
try {
|
|
2206
|
+
const controller = new AbortController();
|
|
2207
|
+
const timer = setTimeout(() => controller.abort(), 12000);
|
|
2208
|
+
let r;
|
|
2209
|
+
try {
|
|
2210
|
+
// overpass-api.de specifically 406s a generic/default
|
|
2211
|
+
// User-Agent (confirmed: it rejects Node's own default one)
|
|
2212
|
+
// — identify the app explicitly, as any well-behaved
|
|
2213
|
+
// Overpass client should.
|
|
2214
|
+
r = await fetch(`${base}?data=${encodeURIComponent(query)}`, {
|
|
2215
|
+
signal: controller.signal,
|
|
2216
|
+
headers: { 'User-Agent': 'signalk-race-control (+https://github.com/joabakk/signalk-race-control)' }
|
|
2217
|
+
});
|
|
2218
|
+
} finally {
|
|
2219
|
+
clearTimeout(timer);
|
|
2220
|
+
}
|
|
2221
|
+
if (!r.ok) continue;
|
|
2222
|
+
const data = await r.json();
|
|
2223
|
+
if (!data || !Array.isArray(data.elements)) continue;
|
|
2224
|
+
if (data.elements.length) return res.json(data);
|
|
2225
|
+
emptyResult = data;
|
|
2226
|
+
} catch (e) {
|
|
2227
|
+
// try the next mirror
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
if (pass === 0) await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2231
|
+
}
|
|
2232
|
+
res.json(emptyResult || { elements: [] });
|
|
2233
|
+
});
|
|
2234
|
+
|
|
2175
2235
|
router.post('/races', (req, res) => {
|
|
2176
2236
|
const name = ((req.body && req.body.name) || '').trim();
|
|
2177
2237
|
if (!name) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-race-control",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "SignalK plugin and webapp for tracking elapsed and handicap-corrected (Time-on-Time) race time, with a per-boat editable TCF and boat names pulled from AIS when available.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Joachim Bakke <github@heiamoss.com>",
|
package/public/app.js
CHANGED
|
@@ -1412,32 +1412,29 @@
|
|
|
1412
1412
|
// autocomplete and the pick-on-map snap. Silently returns nothing on
|
|
1413
1413
|
// any failure or timeout, same spirit as the reverse-geocode name
|
|
1414
1414
|
// suggestion.
|
|
1415
|
-
//
|
|
1416
|
-
//
|
|
1417
|
-
//
|
|
1418
|
-
//
|
|
1419
|
-
//
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
// click the way this used to run), so the extra latency is worth it.
|
|
1423
|
-
const OVERPASS_ENDPOINTS = ['https://overpass.kumi.systems/api/interpreter', 'https://overpass.osm.ch/api/interpreter'];
|
|
1415
|
+
// Goes through this plugin's own server (see the /overpass route)
|
|
1416
|
+
// rather than calling a public Overpass mirror directly from here: the
|
|
1417
|
+
// mirror with real, complete seamark data (overpass-api.de) sends no
|
|
1418
|
+
// CORS headers, so a browser fetch to it is blocked outright — a
|
|
1419
|
+
// server-to-server request isn't subject to CORS at all. The server
|
|
1420
|
+
// also handles falling back across mirrors, so this stays a single
|
|
1421
|
+
// same-origin call.
|
|
1424
1422
|
async function queryOverpass(overpassQuery) {
|
|
1425
|
-
|
|
1426
|
-
for (const base of OVERPASS_ENDPOINTS) {
|
|
1423
|
+
try {
|
|
1427
1424
|
const controller = new AbortController();
|
|
1428
|
-
const timer = setTimeout(() => controller.abort(),
|
|
1425
|
+
const timer = setTimeout(() => controller.abort(), 90000);
|
|
1426
|
+
let res;
|
|
1429
1427
|
try {
|
|
1430
|
-
|
|
1431
|
-
if (!res.ok) continue;
|
|
1432
|
-
const data = await res.json();
|
|
1433
|
-
if (data && Array.isArray(data.elements)) elements.push(...data.elements);
|
|
1434
|
-
} catch (e) {
|
|
1435
|
-
// try the next endpoint
|
|
1428
|
+
res = await fetch(`${API}/overpass?data=${encodeURIComponent(overpassQuery)}`, { signal: controller.signal });
|
|
1436
1429
|
} finally {
|
|
1437
1430
|
clearTimeout(timer);
|
|
1438
1431
|
}
|
|
1432
|
+
if (!res.ok) return [];
|
|
1433
|
+
const data = await res.json();
|
|
1434
|
+
return data && Array.isArray(data.elements) ? data.elements : [];
|
|
1435
|
+
} catch (e) {
|
|
1436
|
+
return [];
|
|
1439
1437
|
}
|
|
1440
|
-
return elements;
|
|
1441
1438
|
}
|
|
1442
1439
|
|
|
1443
1440
|
function seamarkElementsToResults(elements) {
|
|
@@ -1465,8 +1462,24 @@
|
|
|
1465
1462
|
if (seamarkCacheBounds && seamarkCacheBounds.contains(bounds)) return;
|
|
1466
1463
|
}
|
|
1467
1464
|
const padded = bounds.pad(0.5);
|
|
1468
|
-
|
|
1469
|
-
|
|
1465
|
+
// The actual Overpass query is clamped to a sane max span around the
|
|
1466
|
+
// padded area's center — a chart zoomed out to show a whole region
|
|
1467
|
+
// (easy to end up at after an auto-fit spanning far-apart boat tracks)
|
|
1468
|
+
// would otherwise turn this into a practically unbounded query, whose
|
|
1469
|
+
// out-body cap then gets consumed by whatever Overpass returns first
|
|
1470
|
+
// rather than the marks actually near what's being searched/picked.
|
|
1471
|
+
// seamarkCacheBounds still tracks the full (unclamped) padded area
|
|
1472
|
+
// below so a wide view isn't re-queried on every subsequent call.
|
|
1473
|
+
const MAX_SPAN_DEG = 1; // ~110km of latitude — generous for any single race course
|
|
1474
|
+
const center = padded.getCenter();
|
|
1475
|
+
const latSpan = Math.min(padded.getNorth() - padded.getSouth(), MAX_SPAN_DEG);
|
|
1476
|
+
const lonSpan = Math.min(padded.getEast() - padded.getWest(), MAX_SPAN_DEG);
|
|
1477
|
+
const queryBounds = L.latLngBounds(
|
|
1478
|
+
[center.lat - latSpan / 2, center.lng - lonSpan / 2],
|
|
1479
|
+
[center.lat + latSpan / 2, center.lng + lonSpan / 2]
|
|
1480
|
+
);
|
|
1481
|
+
const bbox = `${queryBounds.getSouth()},${queryBounds.getWest()},${queryBounds.getNorth()},${queryBounds.getEast()}`;
|
|
1482
|
+
const overpassQuery = `[out:json][timeout:15];node["seamark:type"](${bbox});out body 1000;`;
|
|
1470
1483
|
seamarkCacheFetch = queryOverpass(overpassQuery);
|
|
1471
1484
|
let elements;
|
|
1472
1485
|
try {
|