Country posters print fast on a phone: the sea-name scan and the label placement skip any land ring whose bbox cannot hold the point, and the scan tests the frame first. Exact: all 286 sheets are byte-identical; generation is 3.4-4.4x faster (China's 3x4 sheet 10.3 s -> 3.1 s).

This commit is contained in:
Doxa Maps 2026-10-03 13:48:34 +00:00
parent 3a6d392a1a
commit c9a7ef9368
2 changed files with 21 additions and 6 deletions

View file

@ -2,13 +2,13 @@
// The sha384 of library/packet/poster-render.js. The browser fetches the served copy of that file
// (<assets>/lib/poster-render.js), hashes it, and runs it only when it matches this pin: code loaded at
// print time is integrity-pinned like every embed, because it executes on partner pages.
export const POSTER_RENDER_SHA384 = 'sha384-BNFUrsdCCaEetmp5cSBzDklAUsB5XBEefQEcCd3+dNNDghxRuZNytNsVfl5mBOeP'
export const POSTER_RENDER_SHA384 = 'sha384-sR7a+BDeXAxY0eZS+crqAXtYtVySYAeDKSek4aHPFYzsUaq9pVi+v2RkmkwjadCV'
// The sha384 of library/scripts/posters/data/ne-places.json, served at <assets>/boundaries/ne-places.json.
// Data, not code, but pinned the same way: a sheet uses the places only when they are the pinned ones.
export const NE_PLACES_SHA384 = 'sha384-Qtz2q8mcnA9Ei8Kwa2MRMyX95hSbSnUXjXdtsy8rHTX++mgSCih4LO4Rdrb2MIQy'
// The sha384 of the classic-script copy (<assets>/lib/poster-render.global.js, globalVariant of the file above),
// loaded with <script integrity> where a page's policy forbids importing a blob: of the verified text.
export const POSTER_RENDER_GLOBAL_SHA384 = 'sha384-idoDSzFXNuBK27dktAJffZD6QhubGkNOk+g9vwPobzs0HWHfnUq3cSyX7FyzwvtQ'
export const POSTER_RENDER_GLOBAL_SHA384 = 'sha384-bc9b4cbOEA6LG0PxZhSYvOQPd+f7wptSbTT2knIUx+U7U+4KTHgttWKGPQDkJaoJ'
// The sha384 of library/scripts/posters/packet-strings.json, served at <assets>/lib/packet-strings.json. The
// packet, list and card text in every language, fetched at print time (packet/packetStrings.js) and used only
// when it matches; otherwise the print falls back to the bundled English block.

View file

@ -114,6 +114,13 @@ function geoToPath(geom, project) {
}
return d;
}
// A ring's own bbox [minX, minY, maxX, maxY] (one pass), for the point-in-ring skips.
function ringBox(ring) {
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
for (const [x, y] of ring) { if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y; }
return [x0, y0, x1, y1];
}
function bboxOf(geom) {
const rings = geom.type === 'Polygon' ? geom.coordinates
: geom.type === 'MultiPolygon' ? geom.coordinates.flat() : [];
@ -1205,12 +1212,14 @@ async function renderPoster(zone, pins, zoneFC) {
return hit;
};
const cand = [];
const R = ringsOf(f.geometry);
// Each ring with its bbox: a point outside a ring's bbox is never inside it (the parity test gives false), so
// skipping those rings gives exactly the same answers and spares the vertex walk (the mobile print speed).
const R = ringsOf(f.geometry).map((ring) => [ring, ringBox(ring)]);
for (let gx = 1; gx <= 7; gx++) {
for (let gy = 1; gy <= 7; gy++) {
const cxp = best[0] + ((best[2] - best[0]) * gx) / 8;
const cyp = best[1] + ((best[3] - best[1]) * gy) / 8;
if (!R.some((ring) => inRing(cxp, cyp, ring))) continue;
if (!R.some(([ring, b]) => cxp >= b[0] && cxp <= b[2] && cyp >= b[1] && cyp <= b[3] && inRing(cxp, cyp, ring))) continue;
// score = distance to the nearest edge of the clipped box (keeps it central)
const d = Math.min(cxp - best[0], best[2] - cxp, cyp - best[1], best[3] - cyp);
cand.push({ cxp, cyp, d });
@ -1356,7 +1365,13 @@ async function renderPoster(zone, pins, zoneFC) {
else if (g.type === 'MultiPolygon') for (const poly of g.coordinates) landRings.push(poly[0]);
}
}
const onLand = (lng, lat) => landRings.some((ring) => {
// ★ MOBILE PRINT SPEED: every ring carries its bbox, and a point outside it skips the vertex walk. Exact: outside the
// bbox the parity test is always false (no edge spans the latitude, or every crossing lies on one side of a closed
// ring). On a phone this scan was most of a country poster's time (China ~45 s at CPU x4).
const landBoxes = landRings.map(ringBox);
const onLand = (lng, lat) => landRings.some((ring, k) => {
const b = landBoxes[k];
if (lng < b[0] || lng > b[2] || lat < b[1] || lat > b[3]) return false;
let hit = false;
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
const [xi, yi] = ring[i], [xj, yj] = ring[j];
@ -1386,10 +1401,10 @@ async function renderPoster(zone, pins, zoneFC) {
const patches = [];
for (let lng = viewBbox[0]; lng <= viewBbox[2]; lng += stepX) {
for (let lat = viewBbox[1]; lat <= viewBbox[3]; lat += stepY) {
if (onLand(lng, lat)) continue;
const [px2, py2] = proj(lng, lat);
if (px2 < FRAME_X + 60 || px2 > FRAME_X + FRAME_W - 60) continue;
if (py2 < FRAME_Y + 40 || py2 > FRAME_Y + FRAME_H - 40) continue;
if (onLand(lng, lat)) continue; // after the frame test: a point outside the frame never needed it
patches.push({ lng, lat, px: px2, py: py2 });
}
}