mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
address the final review of the voice outline fixes
- A building word ("Burj Khalifa Tower", "Sagrada Familia Church") passes
the last-word rule on a building-like result; enclosures stay out.
- The anchor chooses only among the best-named outlines, so a worse name
never wins by containment alone.
- An ask without a quadrant takes only the quadrant of the matching street
nearest the point; a leading quadrant ("SE Main St") may go unsaid too.
- On an unbuffered tile edge a fragment shows as an edge along the
boundary, so a whole building touching it at a corner is not cut.
- Drop two orphaned comments and correct the place-name key wording.
This commit is contained in:
1 parent
a61d0f4c2a
commit
0712db9891
8 files changed
+194
-42
No files matched your search
@@ -204,14 +204,28 @@ function decodePolygons(
|
||||
}
|
||||
const [westCut, northCut] = pixelToLonLat(minX, minY, extent, z, x, y);
|
||||
const [eastCut, southCut] = pixelToLonLat(maxX, maxY, extent, z, x, y);
|
||||
// A side with no buffer was cut at the tile edge itself, so a polygon that
|
||||
// touches it is a fragment too.
|
||||
// Past a buffer, any vertex on the cut line marks a fragment. A side with
|
||||
// no buffer was cut at the tile edge itself, where a whole building may
|
||||
// also just touch the edge: there a fragment shows as an edge running
|
||||
// along the boundary, not a single vertex on it.
|
||||
const eps = 1e-9;
|
||||
const onCut = ([lon, lat]) =>
|
||||
Math.abs(lon - westCut) < eps ||
|
||||
Math.abs(lon - eastCut) < eps ||
|
||||
Math.abs(lat - northCut) < eps ||
|
||||
Math.abs(lat - southCut) < eps;
|
||||
const near = (a, b) => Math.abs(a - b) < eps;
|
||||
const onBufferedCut = ([lon, lat]) =>
|
||||
(minX < 0 && near(lon, westCut)) ||
|
||||
(maxX > extent && near(lon, eastCut)) ||
|
||||
(minY < 0 && near(lat, northCut)) ||
|
||||
(maxY > extent && near(lat, southCut));
|
||||
const alongEdge = ([lonA, latA], [lonB, latB]) =>
|
||||
(minX === 0 && near(lonA, westCut) && near(lonB, westCut)) ||
|
||||
(maxX === extent && near(lonA, eastCut) && near(lonB, eastCut)) ||
|
||||
(minY === 0 && near(latA, northCut) && near(latB, northCut)) ||
|
||||
(maxY === extent && near(latA, southCut) && near(latB, southCut));
|
||||
const isCut = (ring) =>
|
||||
ring.some(
|
||||
(point, index) =>
|
||||
onBufferedCut(point) ||
|
||||
(index > 0 && alongEdge(ring[index - 1], point)),
|
||||
);
|
||||
const out = [];
|
||||
for (let i = 0; i < layer.length; i++) {
|
||||
const feature = layer.feature(i);
|
||||
@@ -245,7 +259,7 @@ function decodePolygons(
|
||||
),
|
||||
);
|
||||
const clippedParts = polygons.map(
|
||||
(rings, part) => incompleteParts[part] || rings[0].some(onCut),
|
||||
(rings, part) => incompleteParts[part] || isCut(rings[0]),
|
||||
);
|
||||
for (let part = 0; part < polygons.length; part++) {
|
||||
const rings = polygons[part];
|
||||
@@ -364,14 +378,24 @@ export function streetNameMatches(candidate, asked) {
|
||||
};
|
||||
const have = normalizeStreetName(candidate);
|
||||
if (answers(have)) return true;
|
||||
const quadrant = streetQuadrant(have);
|
||||
if (!quadrant || streetQuadrant(want)) return false;
|
||||
const words = have.split(' ');
|
||||
return (
|
||||
QUADRANTS.has(words.at(-1)) &&
|
||||
!QUADRANTS.has(want.split(' ').at(-1)) &&
|
||||
answers(words.slice(0, -1).join(' '))
|
||||
return answers(
|
||||
(quadrant.leading ? words.slice(1) : words.slice(0, -1)).join(' '),
|
||||
);
|
||||
}
|
||||
|
||||
/** A normalized street name's quadrant ("e street northwest", "southeast main street"). */
|
||||
function streetQuadrant(name) {
|
||||
const words = name.split(' ');
|
||||
if (words.length < 2) return null;
|
||||
if (QUADRANTS.has(words.at(-1)))
|
||||
return { name: words.at(-1), leading: false };
|
||||
if (QUADRANTS.has(words[0])) return { name: words[0], leading: true };
|
||||
return null;
|
||||
}
|
||||
|
||||
const lengthM = (line) => {
|
||||
let total = 0;
|
||||
for (let i = 1; i < line.length; i++)
|
||||
@@ -399,23 +423,38 @@ export function streetFromTiles(
|
||||
const pieces = [];
|
||||
let matchedName = null;
|
||||
const extent = boxAround(lat, lon, radiusM);
|
||||
const matches = [];
|
||||
for (const tile of tiles || []) {
|
||||
for (const street of tile?.streets || []) {
|
||||
if (
|
||||
!streetNameMatches(street.name, name) &&
|
||||
!(street.nameEn && streetNameMatches(street.nameEn, name))
|
||||
)
|
||||
continue;
|
||||
const spelled = streetNameMatches(street.name, name)
|
||||
? street.name
|
||||
: street.nameEn && streetNameMatches(street.nameEn, name)
|
||||
? street.nameEn
|
||||
: null;
|
||||
if (!spelled) continue;
|
||||
// Segment distance, not vertex distance: a long straight piece through
|
||||
// the point has no vertex near it.
|
||||
if (distanceToLineM(lon, lat, street.coordinates) > radiusM) continue;
|
||||
// Only what lies within the search extent is drawn.
|
||||
const clipped = clipTileLine(street.coordinates, extent);
|
||||
if (!clipped.length) continue;
|
||||
matchedName ||= street.name;
|
||||
pieces.push(...clipped);
|
||||
const distanceM = distanceToLineM(lon, lat, street.coordinates);
|
||||
if (distanceM > radiusM) continue;
|
||||
const quadrant =
|
||||
streetQuadrant(normalizeStreetName(spelled))?.name || null;
|
||||
matches.push({ street, distanceM, quadrant });
|
||||
}
|
||||
}
|
||||
// An ask without a quadrant ("E Street" where E St NW and E St NE both
|
||||
// run) means the one beside the point, not every quadrant's street.
|
||||
const nearest = matches.reduce(
|
||||
(best, match) => (!best || match.distanceM < best.distanceM ? match : best),
|
||||
null,
|
||||
);
|
||||
for (const { street, quadrant } of matches) {
|
||||
if (quadrant !== nearest.quadrant) continue;
|
||||
// Only what lies within the search extent is drawn.
|
||||
const clipped = clipTileLine(street.coordinates, extent);
|
||||
if (!clipped.length) continue;
|
||||
matchedName ||= street.name;
|
||||
pieces.push(...clipped);
|
||||
}
|
||||
if (!pieces.length) return null;
|
||||
const chains = stitchLines(
|
||||
pieces.filter((piece) => piece.length >= 2),
|
||||
|
||||
@@ -84,6 +84,36 @@ test('a lone letter is a direction only beside a name; quadrants may go unsaid',
|
||||
assert.ok(!streetNameMatches('E St NE', 'E Street Northwest'));
|
||||
assert.ok(!streetNameMatches('E Street', 'E Street Northwest'));
|
||||
assert.ok(!streetNameMatches('F Street Northwest', 'E Street'));
|
||||
// A leading quadrant may go unsaid too (Portland's "SE Main St").
|
||||
assert.ok(streetNameMatches('SE Main St', 'Main Street'));
|
||||
assert.ok(!streetNameMatches('SE Main St', 'NE Main Street'));
|
||||
});
|
||||
|
||||
test('an ask without a quadrant takes the quadrant beside the point', () => {
|
||||
const street = (name, lat) => ({
|
||||
name,
|
||||
coordinates: [
|
||||
[-77.03, lat],
|
||||
[-77.02, lat],
|
||||
],
|
||||
});
|
||||
const tiles = [
|
||||
{
|
||||
streets: [
|
||||
street('E Street Northwest', 38.8962),
|
||||
street('E Street Northeast', 38.8972),
|
||||
street('E Street Southeast', 38.8822),
|
||||
],
|
||||
},
|
||||
];
|
||||
const near = streetFromTiles(tiles, {
|
||||
name: 'E Street',
|
||||
lat: 38.8963,
|
||||
lon: -77.025,
|
||||
});
|
||||
assert.equal(near.name, 'E Street Northwest');
|
||||
assert.equal(near.lines.length, 1, 'one quadrant, not three streets');
|
||||
assert.ok(near.lines[0].every(([, lat]) => lat === 38.8962));
|
||||
});
|
||||
|
||||
test('same-name segments merge into polylines around the point (Congress Avenue)', () => {
|
||||
|
||||
@@ -217,6 +217,17 @@ test('grounds touching an unbuffered tile edge are cut, not complete', () => {
|
||||
const inside = decode([square(-179.999, 0, 0.01)]);
|
||||
assert.equal(inside.areas[0].clipped, false);
|
||||
assert.ok(enclosingAreaFromTiles([inside], ask));
|
||||
// A whole shape that only touches the edge at one corner is not cut.
|
||||
const tangent = decode([
|
||||
[
|
||||
[-180, 0.005],
|
||||
[-179.995, 0],
|
||||
[-179.99, 0.005],
|
||||
[-179.995, 0.01],
|
||||
[-180, 0.005],
|
||||
],
|
||||
]);
|
||||
assert.equal(tangent.areas[0].clipped, false);
|
||||
});
|
||||
|
||||
test('a point in a courtyard is not inside the building', () => {
|
||||
|
||||
@@ -1347,6 +1347,40 @@ test('a landmark ask is not answered by the place around it', () => {
|
||||
landmark('Royal Botanic Park'),
|
||||
);
|
||||
assert.equal(every.outline.name, 'Royal Botanic Historic Park');
|
||||
// A building word passes on a building-like result, not on a park.
|
||||
for (const [query, result] of [
|
||||
[
|
||||
'Burj Khalifa Tower',
|
||||
at('Burj Khalifa', 'Burj Khalifa, Downtown Dubai, Dubai', {
|
||||
category: 'building',
|
||||
type: 'yes',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'Sagrada Familia Church',
|
||||
at('Basílica de la Sagrada Família', 'Basílica, Barcelona, Spain', {
|
||||
category: 'amenity',
|
||||
type: 'place_of_worship',
|
||||
}),
|
||||
],
|
||||
])
|
||||
assert.ok(selectOutlineResult([result], landmark(query)).outline, query);
|
||||
assert.equal(
|
||||
selectOutlineResult([park('Central Park')], landmark('Central Park Tower'))
|
||||
.outline,
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('a worse name never wins the anchor by containment alone', () => {
|
||||
const building = {
|
||||
osm: 'way/1',
|
||||
polygons: [square(0, 0, 0.0001)],
|
||||
};
|
||||
const plaza = { osm: 'way/2', polygons: [square(-0.001, -0.001, 0.003)] };
|
||||
const anchor = [0.0002, 0.0002];
|
||||
assert.equal(outlineAt([building, plaza], ...anchor, [3, 2]).osm, 'way/1');
|
||||
assert.equal(outlineAt([building, plaza], ...anchor, [3, 3]).osm, 'way/2');
|
||||
});
|
||||
|
||||
test('outlineAt reads a ring crossing the antimeridian in its own frame', () => {
|
||||
|
||||
Reference in new issue
Block a user