Merge remote-tracking branch 'origin/main' into upstream/geocode-place-region

# Conflicts:
#	docs/CURRENT-STATE.md
#	src/keylessGeocoder.test.mjs
This commit is contained in:
quanticsoul4772 committed 2026-10-25 17:32:07 -07:00
commit c13c2fc1f0
16 files changed
+300 -14

No files matched your search

+20 -5
View File
@@ -206,12 +206,27 @@ export function photonSearchUrl(
url.searchParams.set('limit', String(limit));
const corners = String(bias ?? '').split('|');
if (corners.length === 2) {
const [swLat, swLng] = corners[0].split(',').map(Number);
const [neLat, neLng] = corners[1].split(',').map(Number);
if ([swLat, swLng, neLat, neLng].every(Number.isFinite)) {
const tokens = corners.flatMap((corner) => corner.split(','));
if (
corners.length === 2 &&
corners.every((corner) => corner.split(',').length === 2) &&
tokens.every((token) => token.trim() !== '')
) {
const [swLat, swLng, neLat, neLng] = tokens.map(Number);
if (
[swLat, swLng, neLat, neLng].every(Number.isFinite) &&
Math.abs(swLat) <= 90 &&
Math.abs(neLat) <= 90 &&
swLat <= neLat &&
Math.abs(swLng) <= 180 &&
Math.abs(neLng) <= 180
) {
const midpoint = (swLng + neLng + (swLng > neLng ? 360 : 0)) / 2;
url.searchParams.set('lat', String((swLat + neLat) / 2));
url.searchParams.set('lon', String((swLng + neLng) / 2));
url.searchParams.set(
'lon',
String(midpoint > 180 ? midpoint - 360 : midpoint),
);
}
}
return url.toString();
+11
View File
@@ -583,3 +583,14 @@ test('a nearby place whose own name has a comma still wins when nothing matches
assert.match(result.label, /Denver/);
assert.equal(fetchImpl.urls.length, 2, 'the region checks ran first');
});
test('wrapped view bias stays near the antimeridian and malformed coordinates are omitted', () => {
for (const [bias, expected] of [['-20,170|-10,-171', 179.5], ['50,175|60,-165', -175], ['0,180|10,-180', 180], ['0,-180|10,180', 0]]) {
const params = new URL(photonSearchUrl('island', { bias })).searchParams;
assert.equal(Number(params.get('lon')), expected);
}
for (const bias of ['-91,0|0,1', '0,181|1,0', '10,0|0,1', ',|,', '0,1,2|3,4', '0,1|Infinity,2']) {
assert.equal(new URL(photonSearchUrl('island', { bias })).searchParams.has('lon'), false);
}
});
+3 -1
View File
@@ -166,6 +166,8 @@ export function nominatimViewboxFromBounds(bounds) {
const neLat = nominatimLatitude(match[3]);
const neLng = nominatimLongitude(match[4]);
if ([swLat, swLng, neLat, neLng].some((value) => value === null)) return null;
if (swLat >= neLat) return null;
// One Nominatim viewbox cannot express a wrapped interval. Omit this soft
// preference rather than biasing the search toward the opposite hemisphere.
if (swLat >= neLat || swLng > neLng) return null;
return `${swLng},${neLat},${neLng},${swLat}`;
}
+9
View File
@@ -173,3 +173,12 @@ test('a viewport hint is sent only when every edge is a real coordinate', () =>
`${JSON.stringify(bounds)} must not become a viewbox`,
);
});
test('wrapped viewports omit the Nominatim bias without rejecting the search', () => {
assert.equal(nominatimViewboxFromBounds('-20,170|-10,-171'), null);
assert.equal(nominatimViewboxFromBounds('50,175|60,-165'), null);
assert.equal(
nominatimViewboxFromBounds('-20,170|-10,180'),
'170,-10,180,-20',
);
});
+12 -2
View File
@@ -307,10 +307,20 @@ export function createGroundSnap({ groundFloor }) {
}
const now = Date.now();
const entry = entries.get(icao);
if (entry && now < entry.nextRetryMs) return heldSnapM(entry, surfacePos); // backoff in force
// Backoff in force, unless it ends further off than any backoff can:
// a retry stamped before the clock stepped back would otherwise wait for
// the clock to catch up.
if (
entry &&
now < entry.nextRetryMs &&
entry.nextRetryMs - now <= RETRY_MAX_MS
)
return heldSnapM(entry, surfacePos);
// Per-window budget: deny WITHOUT a retry stamp so the overflow simply
// tries again next tick instead of waiting out a backoff it didn't earn.
if (now - windowStartMs > SAMPLE_WINDOW_MS) {
// A clock that stepped back also opens a new window, or none would open
// until it caught up.
if (now < windowStartMs || now - windowStartMs > SAMPLE_WINDOW_MS) {
windowStartMs = now;
windowCount = 0;
}
+62
View File
@@ -0,0 +1,62 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import * as Cesium from 'cesium';
import { createGroundSnap } from './groundSnap.js';
const groundFloor = {
cachedMeshFloor: () => null,
meshFloorPreferred: () => false,
reportValidatedMeshFloorCell: () => {},
};
/** A viewer whose ground is a loaded 3D tileset, counting height samples. */
function makeViewer() {
const calls = { samples: 0 };
const tileset = { show: true, tilesLoaded: true };
return {
calls,
scene: {
primitives: { length: 1, get: () => tileset },
sampleHeight() {
calls.samples += 1;
return 1650;
},
globe: { getHeight: () => 1600 },
},
};
}
const atDenverAirport = Cesium.Cartesian3.fromDegrees(-104.6737, 39.8561, 0);
test('a clock that steps back opens a new sample window instead of denying samples until it catches up', (t) => {
t.mock.timers.enable({ apis: ['Date'], now: 1_000_000 });
const viewer = makeViewer();
const snap = createGroundSnap({ groundFloor });
const icaos = ['a00001', 'a00002', 'a00003', 'a00004', 'a00005', 'a00006'];
for (const icao of icaos) snap.heightFor(viewer, icao, atDenverAirport);
assert.equal(viewer.calls.samples, 4, 'four samples per 250 ms window');
t.mock.timers.setTime(1_000_000 - 60_000);
for (const icao of icaos) snap.heightFor(viewer, icao, atDenverAirport);
assert.equal(
viewer.calls.samples,
6,
'the two denied aircraft sample in the new window',
);
});
test('a retry backoff stamped before the clock stepped back does not wait for it to catch up', (t) => {
t.mock.timers.enable({ apis: ['Date'], now: 10_000_000 });
const viewer = makeViewer();
const tileset = viewer.scene.primitives.get(0);
const snap = createGroundSnap({ groundFloor });
// Tiles still streaming: a miss, backed off for 2 s.
tileset.tilesLoaded = false;
assert.equal(snap.heightFor(viewer, 'a00001', atDenverAirport), null);
tileset.tilesLoaded = true;
assert.equal(snap.heightFor(viewer, 'a00001', atDenverAirport), null);
assert.equal(viewer.calls.samples, 0, 'the backoff holds');
// The clock steps back an hour: the stamp is an hour off, past any backoff.
t.mock.timers.setTime(10_000_000 - 3_600_000);
assert.equal(snap.heightFor(viewer, 'a00001', atDenverAirport), 1650);
assert.equal(viewer.calls.samples, 1);
});
+17 -3
View File
@@ -98,6 +98,13 @@ function dockCapacity(raw) {
return capacity - geofenced;
}
/** Read a numeric coordinate without treating blank text or other JSON types as zero. */
function coordinateNumber(value) {
if (typeof value !== 'number' && (typeof value !== 'string' || !value.trim()))
return NaN;
return Number(value);
}
/**
* Parse a GBFS station_information payload into a Map of station metadata.
* Handles field-name variations across different GBFS providers
@@ -113,9 +120,16 @@ export function parseStationInformation(payload) {
for (const raw of stations) {
// Accept both GBFS (station_id) and alternate (id) field names
const stationId = String(raw?.station_id ?? raw?.id ?? '').trim();
const lat = Number(raw?.lat ?? raw?.latitude);
const lon = Number(raw?.lon ?? raw?.longitude);
if (!stationId || !Number.isFinite(lat) || !Number.isFinite(lon)) continue;
const lat = coordinateNumber(raw?.lat ?? raw?.latitude);
const lon = coordinateNumber(raw?.lon ?? raw?.longitude);
if (
!stationId ||
!Number.isFinite(lat) ||
!Number.isFinite(lon) ||
Math.abs(lat) > 90 ||
Math.abs(lon) > 180
)
continue;
stationMap.set(stationId, {
stationId,
+67
View File
@@ -19,6 +19,73 @@ const parseCapacity = (fields) =>
parseStationInformation({ data: { stations: [station(fields)] } }).get('s1')
.capacity;
test('station information skips missing, blank and malformed coordinates', () => {
for (const field of ['lat', 'lon', 'latitude', 'longitude']) {
for (const value of [
undefined,
null,
'',
' \t ',
false,
true,
[],
[0],
{},
'bad',
NaN,
Infinity,
]) {
const raw = { station_id: 'bad', lat: 0, lon: 0 };
if (field === 'latitude') delete raw.lat;
if (field === 'longitude') delete raw.lon;
raw[field] = value;
const parsed = parseStationInformation({
data: { stations: [raw, station({ station_id: 'good' })] },
});
assert.deepEqual(
[...parsed.keys()],
['good'],
`${field} = ${JSON.stringify(value)}`,
);
}
}
});
test('station information skips coordinates outside latitude and longitude bounds', () => {
for (const fields of [
{ lat: 90.001 },
{ lat: -90.001 },
{ lon: 180.001 },
{ lon: -180.001 },
{ lat: undefined, latitude: '91' },
{ lon: undefined, longitude: '-181' },
]) {
const parsed = parseStationInformation({
data: { stations: [station(fields)] },
});
assert.equal(parsed.size, 0, JSON.stringify(fields));
}
});
test('station information preserves zero, boundary coordinates and numeric aliases', () => {
for (const [fields, expected] of [
[{ lat: 0, lon: 0 }, [0, 0]],
[{ lat: ' 0 ', lon: '0' }, [0, 0]],
[{ lat: 90, lon: 180 }, [90, 180]],
[{ lat: '-90', lon: '-180' }, [-90, -180]],
[
{ lat: null, lon: undefined, latitude: '30.2681', longitude: '-97.7469' },
[30.2681, -97.7469],
],
]) {
const parsed = parseStationInformation({
data: { stations: [station(fields)] },
}).get('s1');
assert.ok(parsed, JSON.stringify(fields));
assert.deepEqual([parsed.lat, parsed.lon], expected);
}
});
test('capacity counts docks, not the geofenced area PBSC adds to it', () => {
// Bilbao Bizi, BiciMAD and Dbizi as published on 2026-10-17.
assert.equal(parseCapacity({ capacity: 839, geofenced_capacity: 800 }), 39);
+40
View File
@@ -171,6 +171,46 @@ test('bike share reports uncovered areas and failed systems honestly', async ()
);
});
test('bike share excludes blank coordinates without losing stations on the prime meridian', async () => {
const stations = [
{ station_id: 'blank', lat: 51.5, lon: ' ' },
{ station_id: 'zero', lat: 51.5, lon: 0 },
];
const catalog = composeCatalog({
tools: coreTools,
services: {
bikeshare: {
systems: [system('fixture', 'Fixture Bikes', 51.5, 0)],
async getStations(url) {
return {
data: {
stations: url.endsWith('station_information.json')
? stations
: stations.map(({ station_id }) => ({
station_id,
num_bikes_available: 4,
})),
},
};
},
},
},
});
const result = await catalog.call('get_bike_share', {
area: { lat: 51.5, lon: 0, radius_km: 2 },
});
assert.deepEqual(
result.data.rows.map((row) => row.station_id),
['zero'],
);
assert.equal(result.data.total, 1);
assert.equal(result.data.rows[0].lon, 0);
assert.equal(
result.summary,
'1 bike-share station in 2 km around 51.500, 0.000 with 4 bikes available.',
);
});
const feed = {
id: 'capmetro',
name: 'CapMetro',
+13 -1
View File
@@ -159,18 +159,30 @@ export class VisualEffects {
this.requestRender('bloom');
}
/**
* Sharpen runs only when it is on and its slider is above 0, as the style
* stages do: the pass costs the same at any amount (~1.5 ms GPU per frame
* on the Arc, 2026-10-24), and `0.1 + value * 2` still sharpened at 0.
*/
syncSharpenEnabled() {
if (this.stopped || !this.sharpenStage) return;
this.sharpenStage.enabled =
this.sharpenEnabled && this.sharpenIntensity > 0.001;
}
applySharpenIntensity(value) {
if (this.stopped) return;
this.sharpenIntensity = value;
if (this.sharpenStage)
this.sharpenStage.uniforms.amount = 0.1 + value * 2.0;
this.syncSharpenEnabled();
this.requestRender('sharpen');
}
setSharpenEnabled(enabled) {
if (this.stopped) return;
this.sharpenEnabled = !!enabled;
if (this.sharpenStage) this.sharpenStage.enabled = this.sharpenEnabled;
this.syncSharpenEnabled();
this.requestRender('sharpen');
}
+18
View File
@@ -224,6 +224,24 @@ test('sharpen uses the existing uniform scale and keeps values while disabled',
f.effects.destroy();
});
test('sharpen at 0 runs no pass, and comes back with the slider', () => {
const f = fixture();
f.effects.initPostProcess(0.49);
f.effects.setSharpenEnabled(true);
assert.equal(f.effects.sharpenStage.enabled, true);
f.effects.applySharpenIntensity(0);
assert.equal(
f.effects.sharpenStage.enabled,
false,
'no residual sharpen at 0',
);
f.effects.setSharpenEnabled(true);
assert.equal(f.effects.sharpenStage.enabled, false, 'on at 0 is still off');
f.effects.applySharpenIntensity(0.2);
assert.equal(f.effects.sharpenStage.enabled, true);
f.effects.destroy();
});
test('stop revokes pending work immediately but retains stages until final destruction', () => {
const f = fixture();
f.effects.initStyles();