mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge upstream/main into fix/363-geocode-proxy
Resolve SECURITY.md against the Street View provider (#966): the browser key now loads the Maps JavaScript API as well as Map Tiles, so the 'Map Tiles only' guidance becomes 'Map Tiles + Maps JavaScript API' here and in CHANGELOG, CURRENT-STATE and google-key.js. The CHANGELOG entry moves to [Unreleased]; its old section shipped in 0.2.0. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
1fffdf7600
446 files changed
+72653
-2261
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import { findNaturalRegion, pointInRing } from '../data/naturalEarthRegions.js';
|
||||
|
||||
/**
|
||||
* The unqualified Alps ask names the European range, not a same-named peak
|
||||
* or local POI. Keep this correction exact and Alps-only: other ranges and
|
||||
* geographic qualifiers retain the caller's configured geocoder semantics.
|
||||
*/
|
||||
export function createAlpsGeocoder({ lookupRegion = findNaturalRegion } = {}) {
|
||||
return {
|
||||
async geocode(query, { signal } = {}) {
|
||||
signal?.throwIfAborted();
|
||||
const name = String(query ?? '')
|
||||
.trim()
|
||||
.replace(/\s+/g, ' ');
|
||||
if (!/^(?:the )?alps(?: mountains)?$/i.test(name))
|
||||
return { place: null, answered: true };
|
||||
|
||||
const region = await lookupRegion('Alps');
|
||||
signal?.throwIfAborted();
|
||||
if (
|
||||
region?.name !== 'Alps' ||
|
||||
region.kind !== 'natural' ||
|
||||
!Array.isArray(region.polygons) ||
|
||||
region.polygons.length === 0 ||
|
||||
!region.polygons.every(
|
||||
(ring) =>
|
||||
Array.isArray(ring) &&
|
||||
ring.length >= 3 &&
|
||||
ring.every(
|
||||
(point) =>
|
||||
Array.isArray(point) &&
|
||||
point.length === 2 &&
|
||||
point.every(Number.isFinite) &&
|
||||
Math.abs(point[0]) <= 180 &&
|
||||
Math.abs(point[1]) <= 90,
|
||||
),
|
||||
)
|
||||
)
|
||||
throw new Error('Bundled Alps geometry unavailable');
|
||||
const vertices = region.polygons.flat();
|
||||
const west = Math.min(...vertices.map(([lon]) => lon));
|
||||
const east = Math.max(...vertices.map(([lon]) => lon));
|
||||
const south = Math.min(...vertices.map(([, lat]) => lat));
|
||||
const north = Math.max(...vertices.map(([, lat]) => lat));
|
||||
const lat = (south + north) / 2;
|
||||
const lng = (west + east) / 2;
|
||||
if (
|
||||
![west, east, south, north, lat, lng].every(Number.isFinite) ||
|
||||
!region.polygons.some((ring) => pointInRing(ring, lat, lng))
|
||||
) {
|
||||
// A broken pack is not permission to cache an unrelated network peak
|
||||
// as the canonical range. Existing search error/retry handling owns it.
|
||||
throw new Error('Bundled Alps geometry unavailable');
|
||||
}
|
||||
return {
|
||||
answered: true,
|
||||
place: {
|
||||
lat,
|
||||
lng,
|
||||
name: region.name,
|
||||
label: region.name,
|
||||
types: ['natural_feature'],
|
||||
exact: true,
|
||||
viewport: {
|
||||
southwest: { lat: south, lng: west },
|
||||
northeast: { lat: north, lng: east },
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseCoordinateQuery } from './coordinateParser.js';
|
||||
test('search refuses omitted components and repeated separators', () => {
|
||||
for (const q of ['30,, -97', ',30,-97', '30,-97,', '30;; -97', '30,; -97'])
|
||||
assert.equal(parseCoordinateQuery(q), null, q);
|
||||
for (const q of ['30,-97', '30; -97', '30 -97', 'N 30, W 97'])
|
||||
assert.deepEqual(
|
||||
[parseCoordinateQuery(q).lat, parseCoordinateQuery(q).lon],
|
||||
[30, -97],
|
||||
);
|
||||
});
|
||||
@@ -49,8 +49,8 @@ function splitComponents(query) {
|
||||
const parts = /[,;]/.test(text)
|
||||
? text.split(/[,;]/)
|
||||
: text.split(/\s+(?=[+-]?[.\d]|[NSEW]\s*[+-]?[.\d])/i);
|
||||
const trimmed = parts.map((part) => part.trim()).filter(Boolean);
|
||||
return trimmed.length === 2 ? trimmed : null;
|
||||
const trimmed = parts.map((part) => part.trim());
|
||||
return trimmed.length === 2 && trimmed.every(Boolean) ? trimmed : null;
|
||||
}
|
||||
|
||||
/** Present a parsed pair the way a coordinate is normally written. */
|
||||
|
||||
@@ -6,9 +6,10 @@ import { createGoogleGeocoder } from './google.js';
|
||||
import { createPhotonGeocoder } from '../keylessGeocoder.js';
|
||||
import { createCoordinateGeocoder } from './coordinateGeocoder.js';
|
||||
import { createPresetGeocoder } from './presetGeocoder.js';
|
||||
import { createAlpsGeocoder } from './alpsGeocoder.js';
|
||||
|
||||
/**
|
||||
* Coordinates and bundled names first — both answer offline and with no key —
|
||||
* Coordinates, bundled names and the unqualified Alps first — all offline —
|
||||
* then Google when configured, then keyless Photon, then the local Nominatim
|
||||
* route as a last resort. Transport stays local to setup.
|
||||
*
|
||||
@@ -37,6 +38,7 @@ export function createDefaultPlaceSearch({
|
||||
providers: providers.geocode || [
|
||||
createCoordinateGeocoder(),
|
||||
...(presets ? [createPresetGeocoder({ presets })] : []),
|
||||
createAlpsGeocoder(),
|
||||
...(selected
|
||||
? [selected]
|
||||
: [
|
||||
|
||||
Reference in new issue
Block a user