Files
gods-eye-view/src/data/neighborhoodPolygons.test.mjs
T

95 lines
4.8 KiB
JavaScript

// src/data/neighborhoodPolygons.test.mjs — pins the bundled DataSF "Analysis
// Neighborhoods" dataset (PDDL 1.0, see local_data/neighborhoods/SOURCE.md) and
// its resolution contract through the source-agnostic loader.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { lookupNeighborhoodRing } from './neighborhoodPolygons.js';
const FILE = new URL('./local_data/neighborhoods/san-francisco.json', import.meta.url);
// SF proper + Treasure Island; generous but excludes everything non-SF.
const SF_BOUNDS = { west: -122.55, south: 37.70, east: -122.35, north: 37.84 };
function eachRing(geometry, fn) {
const polys = geometry.type === 'Polygon' ? [geometry.coordinates] : geometry.coordinates;
for (const poly of polys) for (const ring of poly) fn(ring);
}
test('SF neighborhoods file parses with the expected DataSF shape', () => {
const fc = JSON.parse(readFileSync(FILE, 'utf8'));
assert.equal(fc.type, 'FeatureCollection');
assert.equal(fc.city, 'San Francisco');
// DataSF Analysis Neighborhoods is exactly 41 areas (dataset j2bu-swwd).
assert.equal(fc.features.length, 41);
for (const f of fc.features) {
const name = f.properties && f.properties.name;
assert.ok(typeof name === 'string' && name.trim().length > 0,
`every feature has a non-empty properties.name (got ${JSON.stringify(name)})`);
assert.ok(f.geometry && (f.geometry.type === 'Polygon' || f.geometry.type === 'MultiPolygon'),
`${name}: geometry is Polygon|MultiPolygon`);
eachRing(f.geometry, (ring) => {
assert.ok(ring.length >= 4, `${name}: ring has >= 4 points`);
const [f0, l0] = [ring[0], ring[ring.length - 1]];
assert.ok(f0[0] === l0[0] && f0[1] === l0[1], `${name}: ring is closed`);
for (const [lon, lat] of ring) {
assert.ok(lon >= SF_BOUNDS.west && lon <= SF_BOUNDS.east
&& lat >= SF_BOUNDS.south && lat <= SF_BOUNDS.north,
`${name}: coordinate [${lon}, ${lat}] inside SF bounds`);
}
});
}
});
test('the five demo neighborhoods resolve to real polygons through the loader', async () => {
const cases = [
// [lat, lon, geocoder-style query, expected dataset name]
[37.7941, -122.4078, 'Chinatown', 'Chinatown'],
[37.8021, -122.4369, 'Marina District', 'Marina'],
[37.7599, -122.4148, 'Mission District', 'Mission'],
[37.7989, -122.4662, 'Presidio', 'Presidio'],
[37.7785, -122.4056, 'South of Market', 'South of Market'],
];
for (const [lat, lon, query, expected] of cases) {
const hit = await lookupNeighborhoodRing(lat, lon, query);
assert.ok(hit, `${query} must resolve`);
assert.equal(hit.name, expected);
assert.ok(Array.isArray(hit.ring) && hit.ring.length >= 4,
`${query}: real ring, not a synthesized disc (got ${hit.ring && hit.ring.length} pts)`);
for (const [rlon, rlat] of hit.ring) {
assert.ok(rlon >= SF_BOUNDS.west && rlon <= SF_BOUNDS.east
&& rlat >= SF_BOUNDS.south && rlat <= SF_BOUNDS.north,
`${query}: ring stays inside SF bounds`);
}
}
});
test('name specificity: Presidio vs Presidio Heights, Mission vs Outer Mission', async () => {
// "Presidio Heights" must NOT collapse onto the (larger) Presidio.
const heights = await lookupNeighborhoodRing(37.7886, -122.4531, 'Presidio Heights');
assert.equal(heights?.name, 'Presidio Heights');
// Bare "Mission" query must not match "Outer Mission"/"Mission Bay".
const mission = await lookupNeighborhoodRing(37.7599, -122.4148, 'Mission');
assert.equal(mission?.name, 'Mission');
});
test('points outside covered cities / unmatched names return null', async () => {
// Austin, TX — outside every bundled city bbox.
assert.equal(await lookupNeighborhoodRing(30.2672, -97.7431, 'Downtown'), null);
// Inside SF but a name the dataset does not carry — no point-in-polygon fallback.
assert.equal(await lookupNeighborhoodRing(37.7793, -122.4193, 'Zilker Park'), null);
});
test('taxonomy aliases: old names reach the renamed DataSF polygon', async () => {
// DataSF renamed "Financial District" → "Financial District/South Beach";
// the word-subset matcher alone can never bridge that (P0-2 alias map).
const fidi = await lookupNeighborhoodRing(37.7946, -122.3999, 'Financial District');
assert.equal(fidi?.name, 'Financial District/South Beach');
// Colloquial "Downtown" (in SF) maps to the same polygon.
const downtown = await lookupNeighborhoodRing(37.7946, -122.3999, 'Downtown');
assert.equal(downtown?.name, 'Financial District/South Beach');
// "Downtown/Civic Center" deliberately has NO alias — the taxonomy split it,
// and a confident wrong polygon would block the live resolver ladder.
assert.equal(await lookupNeighborhoodRing(37.7793, -122.4193, 'Downtown/Civic Center'), null);
});