This commit is contained in:
33333-33333 2026-05-29 14:31:42 +09:00
commit f2d0306d96
13 changed files with 1434 additions and 516 deletions

View file

@ -10,12 +10,15 @@ import {
makeSpatialIndex,
makeUnionFind,
meanFieldAround as meanFieldAroundBase,
nearestNetworkPoint,
packDebugField,
pathCumulativeLengths,
pathAverageField,
pathEndpoints,
pathLengthCells,
pointAtPathDistance,
sampledNetworkCells as sampledNetworkCellsBase,
splitPathToValidCells as splitPathToValidCellsBase,
squaredDistance,
TRANSPORT_ROUTE_POLICIES,
} from "./mapTransportUtils.js";
@ -1332,55 +1335,20 @@ export function buildDensityFlowRoadTransportSystem(ctx) {
function stitchRasterNearContacts() {
const debug = { expressway: 0, national: 0, localToNational: 0, local: 0, broadNearMisses: 0 };
function sampledCells(paths, step = 1) {
const cells = [];
for (let pathId = 0; pathId < (paths || []).length; pathId++) {
const path = paths[pathId];
for (let k = 0; k < path.length; k += step) {
const [x, y] = path[k];
if (inside(x, y) && !sea[indexOf(x, y)]) cells.push({ x, y, pathId });
}
}
return cells;
}
function endpointListWithIds(paths) {
const out = [];
for (let pathId = 0; pathId < (paths || []).length; pathId++) {
const p = paths[pathId];
if (!p || p.length < 2) continue;
out.push({ x: p[0][0], y: p[0][1], pathId });
const q = p[p.length - 1];
out.push({ x: q[0], y: q[1], pathId });
}
return out;
}
function nearestWithin(source, targets, radius, excludeSamePath = true) {
let best = null;
let bestD = radius + 1;
for (const t of targets) {
if (excludeSamePath && source.pathId != null && t.pathId === source.pathId) continue;
const d = Math.hypot(source.x - t.x, source.y - t.y);
if (d > 0.01 && d < bestD) {
bestD = d;
best = t;
}
}
return best ? { target: best, d: bestD } : null;
}
function stitchEndpoints(paths, mode, targets, maxAdds, radius, probability = 1) {
let added = 0;
const endpoints = endpointListWithIds(paths);
const endpoints = pathEndpoints(paths);
for (const ep of endpoints) {
if (added >= maxAdds) break;
if (probability < 1 && hash2(ep.x, ep.y, seed + 18331 + added * 17) > probability) continue;
const near = nearestWithin(ep, targets, radius, true);
const near = nearestNetworkPoint(ep, targets, radius);
if (near && addShortConnector(paths, ep, near.target, mode)) added++;
}
return added;
}
const expresswayCells = sampledCells(expressways, 1);
const nationalCells = sampledCells([...nationalRoads, ...externalRoads], 1);
const localCells = sampledCells(minorRoads, 1);
const expresswayCells = sampledNetworkCellsBase(expressways, 1, sea);
const nationalCells = sampledNetworkCellsBase([...nationalRoads, ...externalRoads], 1, sea);
const localCells = sampledNetworkCellsBase(minorRoads, 1, sea);
debug.expressway += stitchEndpoints(expressways, "expressway", expresswayCells, 14, 46.0, 0.62);
debug.national += stitchEndpoints(nationalRoads, "national", nationalCells, 48, 14.0);
debug.localToNational += stitchEndpoints(minorRoads, "local", nationalCells, 240, 20.0);
@ -1390,11 +1358,11 @@ export function buildDensityFlowRoadTransportSystem(ctx) {
// not share a raster cell. This addresses line simplification and diagonal
// near-contact cases not caught by endpoint-only repair.
const allLocalTargets = [...nationalCells, ...localCells];
const candidates = sampledCells([...nationalRoads, ...minorRoads], 3)
const candidates = sampledNetworkCellsBase([...nationalRoads, ...minorRoads], 3, sea)
.sort((a, b) => hash2(a.x, a.y, seed + 18377) - hash2(b.x, b.y, seed + 18377));
for (const c of candidates) {
if (debug.broadNearMisses >= 260) break;
const near = nearestWithin(c, allLocalTargets, 4.75, true);
const near = nearestNetworkPoint(c, allLocalTargets, 4.75);
if (!near) continue;
const out = c.pathId < nationalRoads.length ? nationalRoads : minorRoads;
const mode = out === nationalRoads ? "national" : "local";
@ -1410,56 +1378,21 @@ export function buildDensityFlowRoadTransportSystem(ctx) {
function stitchLongLocalBranches() {
const debug = { localToTrunk: 0, localToLocal: 0 };
function cells(paths, step = 2) {
const out = [];
for (let pathId = 0; pathId < (paths || []).length; pathId++) {
const path = paths[pathId];
for (let k = 0; k < path.length; k += step) {
const [x, y] = path[k];
if (inside(x, y) && !sea[indexOf(x, y)]) out.push({ x, y, pathId });
}
}
return out;
}
function endpoints(paths) {
const out = [];
for (let pathId = 0; pathId < (paths || []).length; pathId++) {
const p = paths[pathId];
if (!p || p.length < 2) continue;
out.push({ x: p[0][0], y: p[0][1], pathId });
const q = p[p.length - 1];
out.push({ x: q[0], y: q[1], pathId });
}
return out;
}
function nearest(source, targets, radius, excludePath = null) {
let best = null;
let bestD = radius + 1;
for (const t of targets) {
if (excludePath != null && t.pathId === excludePath) continue;
const d = Math.hypot(source.x - t.x, source.y - t.y);
if (d > 0.01 && d < bestD) {
bestD = d;
best = t;
}
}
return best ? { ...best, d: bestD } : null;
}
const trunkCells = cells([...nationalRoads, ...externalRoads], 2);
const localCells = cells(minorRoads, 2);
const eps = endpoints(minorRoads)
const trunkCells = sampledNetworkCellsBase([...nationalRoads, ...externalRoads], 2, sea);
const localCells = sampledNetworkCellsBase(minorRoads, 2, sea);
const eps = pathEndpoints(minorRoads)
.sort((a, b) => hash2(a.x, a.y, seed + 18491) - hash2(b.x, b.y, seed + 18491));
for (const ep of eps) {
if (debug.localToTrunk >= 160) break;
const hit = nearest(ep, trunkCells, 20, null);
const hit = nearestNetworkPoint(ep, trunkCells, 20, { excludeSamePath: false });
if (!hit) continue;
if (addShortConnector(minorRoads, ep, hit, "local")) debug.localToTrunk++;
}
const eps2 = endpoints(minorRoads)
const eps2 = pathEndpoints(minorRoads)
.sort((a, b) => hash2(a.x, a.y, seed + 18493) - hash2(b.x, b.y, seed + 18493));
for (const ep of eps2) {
if (debug.localToLocal >= 150) break;
const hit = nearest(ep, localCells, 17, ep.pathId);
const hit = nearestNetworkPoint(ep, localCells, 17, { excludePath: ep.pathId });
if (!hit) continue;
if (addShortConnector(minorRoads, ep, hit, "local")) debug.localToLocal++;
}
@ -1929,33 +1862,11 @@ export function buildDensityFlowRoadTransportSystem(ctx) {
return transportFields.local;
}
function splitPathToValidCells(path, costField, minCells = 2) {
const chunks = [];
let cur = [];
function valid(x, y) {
return splitPathToValidCellsBase(path, (x, y) => {
if (!inside(x, y)) return false;
const i = indexOf(x, y);
return !sea[i] && !highAltitudeRoadClosed(i) && costField[i] < INF;
}
function pushPoint(x, y) {
if (!valid(x, y)) {
if (cur.length >= minCells) chunks.push(cur);
cur = [];
return;
}
if (!cur.length || cur[cur.length - 1][0] !== x || cur[cur.length - 1][1] !== y) cur.push([x, y]);
}
for (let k = 0; k < (path?.length || 0); k++) {
const a = path[k];
const b = path[Math.min(k + 1, path.length - 1)];
const steps = Math.max(1, Math.ceil(Math.hypot(b[0] - a[0], b[1] - a[1])));
for (let s = 0; s <= steps; s++) {
if (k > 0 && s === 0) continue;
const t = s / steps;
pushPoint(Math.round(a[0] + (b[0] - a[0]) * t), Math.round(a[1] + (b[1] - a[1]) * t));
}
}
if (cur.length >= minCells) chunks.push(cur);
return chunks;
}, minCells);
}
function normalizeRoadGroups() {
const groups = roadGroups();