style: format extracted place and route modules

This commit is contained in:
Sameh Khamis committed 2026-09-11 19:57:22 -07:00
1 parent 2793d7a8df
commit 7e6d89d8f5
9 files changed
+552 -309

No files matched your search

+106 -71
View File
@@ -9,92 +9,127 @@ function placeContextPriority(types) {
}
function approximateDistanceM(latA, lonA, latB, lonB) {
if (![latA, lonA, latB, lonB].every(Number.isFinite)) return Number.MAX_SAFE_INTEGER;
if (![latA, lonA, latB, lonB].every(Number.isFinite))
return Number.MAX_SAFE_INTEGER;
const latitudeScale = 111320;
const longitudeScale = latitudeScale * Math.cos((latA * Math.PI) / 180);
return Math.round(Math.hypot(
(latB - latA) * latitudeScale,
(lonB - lonA) * longitudeScale
));
return Math.round(
Math.hypot((latB - latA) * latitudeScale, (lonB - lonA) * longitudeScale),
);
}
export function projectNearbyPlaces(data, latitude, longitude) {
const seenPlaces = new Set();
const places = Array.isArray(data.places) ? data.places
.map((place) => {
const placeLatitude = place.location?.latitude ?? null;
const placeLongitude = place.location?.longitude ?? null;
const types = Array.isArray(place.types) ? place.types.slice(0, 8) : [];
return {
id: place.id || null,
name: place.displayName?.text || null,
address: place.shortFormattedAddress || place.formattedAddress || null,
latitude: placeLatitude,
longitude: placeLongitude,
distanceM: approximateDistanceM(latitude, longitude, placeLatitude, placeLongitude),
primaryType: place.primaryTypeDisplayName?.text || place.primaryType || null,
types,
contextPriority: placeContextPriority(types),
};
})
.filter((place) => {
const key = `${place.name}:${place.address || ''}`.toLowerCase();
if (!place.name || seenPlaces.has(key)) return false;
seenPlaces.add(key);
return true;
})
.sort((a, b) => b.contextPriority - a.contextPriority || a.distanceM - b.distanceM)
.map(({ contextPriority, ...place }) => place)
.slice(0, 20) : [];
const places = Array.isArray(data.places)
? data.places
.map((place) => {
const placeLatitude = place.location?.latitude ?? null;
const placeLongitude = place.location?.longitude ?? null;
const types = Array.isArray(place.types)
? place.types.slice(0, 8)
: [];
return {
id: place.id || null,
name: place.displayName?.text || null,
address:
place.shortFormattedAddress || place.formattedAddress || null,
latitude: placeLatitude,
longitude: placeLongitude,
distanceM: approximateDistanceM(
latitude,
longitude,
placeLatitude,
placeLongitude,
),
primaryType:
place.primaryTypeDisplayName?.text || place.primaryType || null,
types,
contextPriority: placeContextPriority(types),
};
})
.filter((place) => {
const key = `${place.name}:${place.address || ''}`.toLowerCase();
if (!place.name || seenPlaces.has(key)) return false;
seenPlaces.add(key);
return true;
})
.sort(
(a, b) =>
b.contextPriority - a.contextPriority || a.distanceM - b.distanceM,
)
.map(({ contextPriority, ...place }) => place)
.slice(0, 20)
: [];
return places;
}
export function projectTextSearchPlaces(data, latitude, longitude) {
const places = Array.isArray(data.places) ? data.places
.map((place) => {
const placeLatitude = place.location?.latitude ?? null;
const placeLongitude = place.location?.longitude ?? null;
const types = Array.isArray(place.types) ? place.types.slice(0, 8) : [];
// Places returns a lat/lng bounding box (low/high corners) framing the
// place — no polygon, but enough to SIZE a fallback grounds disc to the
// real feature instead of a blind constant. Normalize to plain numbers.
const vp = place.viewport;
const viewport = (
Number.isFinite(vp?.low?.latitude) && Number.isFinite(vp?.low?.longitude)
&& Number.isFinite(vp?.high?.latitude) && Number.isFinite(vp?.high?.longitude)
) ? {
low: { latitude: vp.low.latitude, longitude: vp.low.longitude },
high: { latitude: vp.high.latitude, longitude: vp.high.longitude },
} : null;
return {
id: place.id || null,
name: place.displayName?.text || null,
address: place.formattedAddress || null,
latitude: placeLatitude,
longitude: placeLongitude,
distanceM: approximateDistanceM(latitude, longitude, placeLatitude, placeLongitude),
primaryType: place.primaryType || null,
types,
viewport,
};
})
.filter((place) => place.name) : [];
const places = Array.isArray(data.places)
? data.places
.map((place) => {
const placeLatitude = place.location?.latitude ?? null;
const placeLongitude = place.location?.longitude ?? null;
const types = Array.isArray(place.types)
? place.types.slice(0, 8)
: [];
// Places returns a lat/lng bounding box (low/high corners) framing the
// place — no polygon, but enough to SIZE a fallback grounds disc to the
// real feature instead of a blind constant. Normalize to plain numbers.
const vp = place.viewport;
const viewport =
Number.isFinite(vp?.low?.latitude) &&
Number.isFinite(vp?.low?.longitude) &&
Number.isFinite(vp?.high?.latitude) &&
Number.isFinite(vp?.high?.longitude)
? {
low: {
latitude: vp.low.latitude,
longitude: vp.low.longitude,
},
high: {
latitude: vp.high.latitude,
longitude: vp.high.longitude,
},
}
: null;
return {
id: place.id || null,
name: place.displayName?.text || null,
address: place.formattedAddress || null,
latitude: placeLatitude,
longitude: placeLongitude,
distanceM: approximateDistanceM(
latitude,
longitude,
placeLatitude,
placeLongitude,
),
primaryType: place.primaryType || null,
types,
viewport,
};
})
.filter((place) => place.name)
: [];
return places;
}
export function normalizeRouteProfile(raw) {
return (raw === 'car' || raw === 'driving') ? 'car'
: (raw === 'bike' || raw === 'cycling' || raw === 'bicycle') ? 'bike'
: (raw === 'foot' || raw === 'walking' || raw === 'walk') ? 'foot'
: null;
return raw === 'car' || raw === 'driving'
? 'car'
: raw === 'bike' || raw === 'cycling' || raw === 'bicycle'
? 'bike'
: raw === 'foot' || raw === 'walking' || raw === 'walk'
? 'foot'
: null;
}
export function projectRouteResult(route, profile) {
return {
ok: true,
profile,
distanceM: Math.round(route.distance),
durationS: Math.round(route.duration),
geometry: route.geometry.coordinates,
};
return {
ok: true,
profile,
distanceM: Math.round(route.distance),
durationS: Math.round(route.duration),
geometry: route.geometry.coordinates,
};
}
+165 -29
View File
@@ -1,32 +1,97 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { googlePlacesContextProxy, installRouteMiddleware } from '../../server/providers/places.js';
import { projectNearbyPlaces, projectTextSearchPlaces } from '../../src/data/placeProviderPayloads.js';
import {
googlePlacesContextProxy,
installRouteMiddleware,
} from '../../server/providers/places.js';
import {
projectNearbyPlaces,
projectTextSearchPlaces,
} from '../../src/data/placeProviderPayloads.js';
function install(register) {
const routes = new Map();
register({ use(route, handler) { routes.set(route, handler); } });
register({
use(route, handler) {
routes.set(route, handler);
},
});
return async (route, url, method = 'GET', peer = 'fixture') => {
const res = { statusCode: 200, headers: {}, setHeader(name, value) { this.headers[name.toLowerCase()] = value; }, writeHead(status, headers) { this.statusCode = status; for (const [key, value] of Object.entries(headers)) this.setHeader(key, value); }, end(body) { this.body = JSON.parse(body); } };
await routes.get(route)({ url, method, socket: { remoteAddress: peer } }, res);
const res = {
statusCode: 200,
headers: {},
setHeader(name, value) {
this.headers[name.toLowerCase()] = value;
},
writeHead(status, headers) {
this.statusCode = status;
for (const [key, value] of Object.entries(headers))
this.setHeader(key, value);
},
end(body) {
this.body = JSON.parse(body);
},
};
await routes.get(route)(
{ url, method, socket: { remoteAddress: peer } },
res,
);
return res;
};
}
test('nearby labels rank landmarks, deduplicate names/addresses and bound the projection', () => {
const place = (name, types, longitude = 0) => ({ displayName: { text: name }, types, location: { latitude: 0, longitude }, formattedAddress: 'Street' });
const result = projectNearbyPlaces({ places: [place('Bathroom', ['public_bathroom']), place('Monument', ['monument'], 0.1), place('MONUMENT', ['monument']), ...Array.from({ length: 25 }, (_, i) => place(`POI ${i}`, ['point_of_interest'], i / 100)), { id: 'unnamed' }] }, 0, 0);
const place = (name, types, longitude = 0) => ({
displayName: { text: name },
types,
location: { latitude: 0, longitude },
formattedAddress: 'Street',
});
const result = projectNearbyPlaces(
{
places: [
place('Bathroom', ['public_bathroom']),
place('Monument', ['monument'], 0.1),
place('MONUMENT', ['monument']),
...Array.from({ length: 25 }, (_, i) =>
place(`POI ${i}`, ['point_of_interest'], i / 100),
),
{ id: 'unnamed' },
],
},
0,
0,
);
assert.equal(result.length, 20);
assert.equal(result[0].name, 'Monument');
assert.equal(result[0].distanceM, 11132);
assert.equal(result.filter(p => p.name.toLowerCase() === 'monument').length, 1);
assert.ok(result.every(p => !('contextPriority' in p)));
assert.equal(
result.filter((p) => p.name.toLowerCase() === 'monument').length,
1,
);
assert.ok(result.every((p) => !('contextPriority' in p)));
assert.deepEqual(projectNearbyPlaces({}, 0, 0), []);
});
test('text search preserves bounds, rejects malformed bounds and tolerates absent locations', () => {
const viewport = { low: { latitude: 1, longitude: 2 }, high: { latitude: 3, longitude: 4 } };
const result = projectTextSearchPlaces({ places: [{ displayName: { text: 'Museum' }, viewport, types: Array(12).fill('museum') }, { displayName: { text: 'Park' }, viewport: { low: viewport.low } }] }, 0, 0);
const viewport = {
low: { latitude: 1, longitude: 2 },
high: { latitude: 3, longitude: 4 },
};
const result = projectTextSearchPlaces(
{
places: [
{
displayName: { text: 'Museum' },
viewport,
types: Array(12).fill('museum'),
},
{ displayName: { text: 'Park' }, viewport: { low: viewport.low } },
],
},
0,
0,
);
assert.deepEqual(result[0].viewport, viewport);
assert.equal(result[0].distanceM, Number.MAX_SAFE_INTEGER);
assert.equal(result[0].latitude, null);
@@ -35,58 +100,129 @@ test('text search preserves bounds, rejects malformed bounds and tolerates absen
});
for (const preview of [false, true]) {
test(`Google middleware resolves credentials per request and preserves search responses (${preview ? 'preview' : 'dev'})`, async t => {
test(`Google middleware resolves credentials per request and preserves search responses (${preview ? 'preview' : 'dev'})`, async (t) => {
let key = '';
const plugin = googlePlacesContextProxy({ resolveApiKey: () => key });
const request = install(middlewares => plugin[preview ? 'configurePreviewServer' : 'configureServer']({ middlewares }));
const request = install((middlewares) =>
plugin[preview ? 'configurePreviewServer' : 'configureServer']({
middlewares,
}),
);
const calls = [];
t.mock.method(globalThis, 'fetch', async (url, options) => {
calls.push({ url, options });
assert.equal(options.headers['X-Goog-Api-Key'], key);
return Response.json({ places: [{ displayName: { text: 'Museum' }, location: { latitude: 30, longitude: -97 } }] });
return Response.json({
places: [
{
displayName: { text: 'Museum' },
location: { latitude: 30, longitude: -97 },
},
],
});
});
const nearby = '/api/google/nearby-places';
const search = '/api/google/text-search';
assert.equal((await request(nearby, '?lat=30&lon=-97')).body.configured, false);
assert.equal(
(await request(nearby, '?lat=30&lon=-97')).body.configured,
false,
);
assert.equal(calls.length, 0);
key = 'fixture-server-key';
const result = await request(nearby, '?lat=30&lon=-97&radiusM=9000');
assert.equal(result.statusCode, 200);
assert.equal(result.body.places[0].name, 'Museum');
assert.equal(JSON.parse(calls[0].options.body).locationRestriction.circle.radius, 5000);
assert.equal(
JSON.parse(calls[0].options.body).locationRestriction.circle.radius,
5000,
);
assert.equal(result.headers['cache-control'], 'private, max-age=300');
key = 'rotated-fixture-key';
assert.equal((await request(search, '?q=museum&lat=30&lon=-97&radiusM=1')).statusCode, 200);
assert.equal(JSON.parse(calls[1].options.body).locationBias.circle.radius, 50);
assert.equal(
(await request(search, '?q=museum&lat=30&lon=-97&radiusM=1')).statusCode,
200,
);
assert.equal(
JSON.parse(calls[1].options.body).locationBias.circle.radius,
50,
);
assert.equal((await request(search, '?lat=30&lon=-97')).statusCode, 400);
assert.equal((await request(nearby, '?lat=bad&lon=-97')).statusCode, 400);
assert.equal((await request(nearby, '', 'POST')).statusCode, 405);
t.mock.method(globalThis, 'fetch', async () => Response.json({ error: { message: 'Denied' } }, { status: 403 }));
assert.deepEqual((await request(search, '?q=museum&lat=30&lon=-97')).body, { places: [], error: 'Denied' });
t.mock.method(globalThis, 'fetch', async () =>
Response.json({ error: { message: 'Denied' } }, { status: 403 }),
);
assert.deepEqual((await request(search, '?q=museum&lat=30&lon=-97')).body, {
places: [],
error: 'Denied',
});
});
}
test('OSRM routing preserves aliases, cache, span guards and upstream failure behavior', async t => {
test('OSRM routing preserves aliases, cache, span guards and upstream failure behavior', async (t) => {
const request = install(installRouteMiddleware);
let calls = 0;
let now = Date.now();
t.mock.method(Date, 'now', () => now);
t.mock.method(globalThis, 'fetch', async url => {
t.mock.method(globalThis, 'fetch', async (url) => {
calls++;
assert.match(url, /routed-car\/route\/v1\/driving\//);
return Response.json({ code: 'Ok', routes: [{ distance: 123.6, duration: 80.2, geometry: { coordinates: [[-97, 30], [-97.01, 30.01]] } }] });
return Response.json({
code: 'Ok',
routes: [
{
distance: 123.6,
duration: 80.2,
geometry: {
coordinates: [
[-97, 30],
[-97.01, 30.01],
],
},
},
],
});
});
const route = '/api/route';
const query = '?profile=driving&coords=-97,30;-97.01,30.01';
const result = await request(route, query);
assert.deepEqual(result.body, { ok: true, profile: 'car', distanceM: 124, durationS: 80, geometry: [[-97, 30], [-97.01, 30.01]] });
assert.deepEqual((await request(route, query.replace('driving', 'car'))).body, result.body);
assert.deepEqual(result.body, {
ok: true,
profile: 'car',
distanceM: 124,
durationS: 80,
geometry: [
[-97, 30],
[-97.01, 30.01],
],
});
assert.deepEqual(
(await request(route, query.replace('driving', 'car'))).body,
result.body,
);
assert.equal(calls, 1);
for (const [url, error] of [['?profile=plane&coords=0,0;1,1', 'invalid profile'], ['?coords=0,91;1,1', 'invalid coordinate'], ['?coords=0,0;100,0', 'route leg too long'], ['?coords=0,0', 'need 2-12 coordinates']]) assert.equal((await request(route, url)).body.error, error);
for (const [url, error] of [
['?profile=plane&coords=0,0;1,1', 'invalid profile'],
['?coords=0,91;1,1', 'invalid coordinate'],
['?coords=0,0;100,0', 'route leg too long'],
['?coords=0,0', 'need 2-12 coordinates'],
])
assert.equal((await request(route, url)).body.error, error);
assert.equal(calls, 1);
now += 600001;
t.mock.method(globalThis, 'fetch', async () => new Response('offline', { status: 503 }));
assert.deepEqual((await request(route, query)).body, { ok: false, error: 'no route found' });
t.mock.method(globalThis, 'fetch', async () => new Response('x', { headers: { 'content-type': 'text/html' } }));
t.mock.method(
globalThis,
'fetch',
async () => new Response('offline', { status: 503 }),
);
assert.deepEqual((await request(route, query)).body, {
ok: false,
error: 'no route found',
});
t.mock.method(
globalThis,
'fetch',
async () => new Response('x', { headers: { 'content-type': 'text/html' } }),
);
assert.equal((await request(route, query)).body.error, 'no route found');
});