tighten the remaining voice outline review items

- One place-name key for server and tiles, so "St. Paul" and "Saint Paul"
  agree in Nominatim name checks.
- Tile street names: a lone letter is a direction only beside another name
  word ("E Street" is not "East Street"), and a trailing quadrant ("E St NW")
  may go unsaid in the ask.
- A building cut at the tile edge that is nearer the point than any whole
  one leaves the point instead of a farther neighbour standing in; an edge
  without a tile buffer counts as a cut, so a cut area never answers grounds.
- A batch building install re-judges only results that installed; a late
  timeout during caption conversion keeps the pending point drawn; a failed
  plain recaption keeps the old mark addressable.
- An interrupted Nominatim dispatch is reclaimed by the next lock owner ten
  minutes after the start its marker names; node:sqlite loads on first use
  without its experimental warning.
- PHOTON_URL accepts only https, which the page's connect-src allows.
- Shared OpenFreeMap tiles decode each layer when a consumer first selects
  it; outline selection reuses the shared ring area and simplification, and
  ground probes the shared ring centroid; the grounds word list lives once.
This commit is contained in:
Sameh Khamis committed 2026-11-02 08:50:05 -08:00
1 parent a38a065c5c
commit a61d0f4c2a
24 files changed
+422 -128

No files matched your search

+7 -44
View File
@@ -3,6 +3,10 @@ import {
containsAnchor,
pointInRing,
} from '../../../src/data/adminBoundaries.js';
import {
douglasPeucker,
ringAreaM2,
} from '../../../src/sources/featureGeometry.js';
/**
* Which Nominatim results may answer which voice ask, and how their polygons
@@ -231,11 +235,6 @@ function adminLevelFits(kind, row) {
const BUILDING_MAX_AREA_M2 = 150_000;
/** Approximate area (m²) of a [lon, lat] ring. */
function ringAreaM2(ring) {
const lat0 = ring[0][1];
const k = 111_320 * 111_320 * Math.cos((lat0 * Math.PI) / 180);
return ringArea(ring) * k;
}
/** Whether a result's class/type suits the ask kind. */
export function acceptsOutlineClass(kind, osmClass, osmType) {
@@ -358,12 +357,6 @@ function cleanRing(ring) {
}
/** Planar ring area in squared degrees (only used to rank parts). */
function ringArea(ring) {
let sum = 0;
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++)
sum += (ring[j][0] - ring[i][0]) * (ring[j][1] + ring[i][1]);
return Math.abs(sum / 2);
}
/** Polygon/MultiPolygon GeoJSON → `[[outer, ...holes], …]`, largest first. */
function polygonsFromGeoJson(geometry) {
@@ -387,7 +380,7 @@ function polygonsFromGeoJson(geometry) {
if (!outer) return null;
const holes = polygon.slice(1).map(cleanRing);
if (holes.some((hole) => !hole)) return null;
polygons.push({ rings: [outer, ...holes], area: ringArea(outer) });
polygons.push({ rings: [outer, ...holes], area: ringAreaM2(outer) });
}
polygons.sort((a, b) => b.area - a.area);
return polygons.map((entry) => entry.rings);
@@ -396,37 +389,7 @@ function polygonsFromGeoJson(geometry) {
/** Iterative Douglas–Peucker on a closed ring (keeps it closed, ≥ 4 points). */
function simplifyClosedRing(ring, tolerance) {
if (ring.length <= 8 || tolerance <= 0) return ring;
const keep = new Uint8Array(ring.length);
keep[0] = keep[ring.length - 1] = 1;
const stack = [[0, ring.length - 1]];
const tol2 = tolerance * tolerance;
while (stack.length) {
const [start, end] = stack.pop();
const [ax, ay] = ring[start];
const [bx, by] = ring[end];
const dx = bx - ax;
const dy = by - ay;
const len2 = dx * dx + dy * dy;
let best = -1;
let bestD = tol2;
for (let i = start + 1; i < end; i++) {
const [px, py] = ring[i];
let t = len2 ? ((px - ax) * dx + (py - ay) * dy) / len2 : 0;
t = Math.max(0, Math.min(1, t));
const ex = ax + t * dx - px;
const ey = ay + t * dy - py;
const d = ex * ex + ey * ey;
if (d > bestD) {
bestD = d;
best = i;
}
}
if (best > 0) {
keep[best] = 1;
stack.push([start, best], [best, end]);
}
}
const out = ring.filter((_point, index) => keep[index]);
const out = douglasPeucker(ring, tolerance);
return out.length >= 4 ? out : ring;
}
@@ -480,7 +443,7 @@ function ringsIntersect(a, b) {
function simplifiedTopologyFits(polygons) {
for (const [outer, ...holes] of polygons) {
for (const ring of [outer, ...holes]) {
if (ringArea(ring) === 0) return false;
if (ringAreaM2(ring) === 0) return false;
for (let i = 1; i < ring.length; i++)
for (let j = i + 2; j < ring.length; j++) {
if (i === 1 && j === ring.length - 1) continue;