road tweak
This commit is contained in:
parent
a6e66f2838
commit
0359bb2445
5 changed files with 1266 additions and 200 deletions
166
mapOutput.js
166
mapOutput.js
|
|
@ -1,6 +1,7 @@
|
|||
import { createNameDebug } from "./names.js";
|
||||
import { CELL_SIZE, INF, MAP_H, MAP_W, clamp, indexOf, inside, rand } from "./mapUtils.js";
|
||||
import { CELL_SIZE, INF, MAP_H, MAP_W, MinHeap, clamp, indexOf, inside, rand, xyOf } from "./mapUtils.js";
|
||||
import { applyOutputOptions, attachIdsAndNames, influenceFromPaths, tagInsidePrefecture } from "./mapGeneratorHelpers.js";
|
||||
import { routeQualityAcceptable } from "./mapTransport.js";
|
||||
|
||||
const MUNICIPAL_SUFFIX_RE = /[市町村区]$/u;
|
||||
|
||||
|
|
@ -475,65 +476,133 @@ export function finishMapOutput({
|
|||
]);
|
||||
|
||||
function addMunicipalCenterLocalAccess() {
|
||||
if (!transportDebug) return;
|
||||
const debugLayers = transportDebug.layers || (transportDebug.layers = { repairedSegments: [], unservedSettlements: [] });
|
||||
const debugLayers = transportDebug ? (transportDebug.layers || (transportDebug.layers = { repairedSegments: [], unservedSettlements: [] })) : { repairedSegments: [], unservedSettlements: [] };
|
||||
debugLayers.repairedSegments ||= [];
|
||||
debugLayers.unservedSettlements ||= [];
|
||||
const accessInfluence = influenceFromPaths([...nationalRoads, ...railways, ...externalRoads, ...minorRoads], 7);
|
||||
const localPenalty = influenceFromPaths(minorRoads, 4);
|
||||
const influenceCache = new Map();
|
||||
const signature = (paths) => `${paths?.length || 0}:${(paths || []).reduce((sum, path) => sum + (path?.length || 0), 0)}`;
|
||||
const cachedInfluence = (paths, radius, label) => {
|
||||
const key = `${label}:${radius}:${signature(paths)}`;
|
||||
let grid = influenceCache.get(key);
|
||||
if (!grid) {
|
||||
grid = influenceFromPaths(paths, radius);
|
||||
influenceCache.set(key, grid);
|
||||
}
|
||||
return grid;
|
||||
};
|
||||
const accessInfluence = cachedInfluence([...nationalRoads, ...railways, ...externalRoads, ...minorRoads], 8, "municipal-access");
|
||||
const localPenalty = cachedInfluence(minorRoads, 4, "municipal-minor");
|
||||
const perPrefectureQuota = new Map();
|
||||
const candidates = adminCenters
|
||||
.filter((p) => inside(p.x, p.y) && !sea[indexOf(p.x, p.y)] && accessInfluence[indexOf(p.x, p.y)] < 0.16)
|
||||
.sort((a, b) => (b.municipalityPopulation || 0) - (a.municipalityPopulation || 0))
|
||||
.slice(0, 45);
|
||||
.filter((p) => {
|
||||
if (!inside(p.x, p.y) || sea[indexOf(p.x, p.y)]) return false;
|
||||
const i = indexOf(p.x, p.y);
|
||||
const meaningful = (p.municipalityPopulation || 0) >= 4500 || (populationDensity?.[i] || 0) > 0.08 || (p.representativeFeatureName && accessInfluence[i] < 0.22);
|
||||
return meaningful && accessInfluence[i] < 0.42;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const ai = indexOf(a.x, a.y);
|
||||
const bi = indexOf(b.x, b.y);
|
||||
const as = (a.municipalityPopulation || 0) + Math.max(0, 0.42 - accessInfluence[ai]) * 145000;
|
||||
const bs = (b.municipalityPopulation || 0) + Math.max(0, 0.42 - accessInfluence[bi]) * 145000;
|
||||
return bs - as;
|
||||
})
|
||||
.filter((p) => {
|
||||
const prefId = municipalityToPrefectureId?.[p.municipalityId] ?? -1;
|
||||
const used = perPrefectureQuota.get(prefId) || 0;
|
||||
if (used >= 18) return false;
|
||||
perPrefectureQuota.set(prefId, used + 1);
|
||||
return true;
|
||||
})
|
||||
.slice(0, 95);
|
||||
|
||||
function routeAccess(start) {
|
||||
const maxSteps = 72;
|
||||
const out = [];
|
||||
const visited = new Set([`${start.x},${start.y}`]);
|
||||
let x = start.x;
|
||||
let y = start.y;
|
||||
let lastKey = "";
|
||||
for (let step = 0; step < maxSteps; step++) {
|
||||
const i = indexOf(x, y);
|
||||
if (step > 3 && (accessInfluence[i] > 0.20 || localPenalty[i] > 0.10)) return out;
|
||||
let best = null;
|
||||
let bestScore = INF;
|
||||
for (let dy = -1; dy <= 1; dy++) {
|
||||
for (let dx = -1; dx <= 1; dx++) {
|
||||
if (!dx && !dy) continue;
|
||||
function addLocalPenalty(path, radius = 4, strength = 0.20) {
|
||||
for (const [x, y] of path || []) {
|
||||
for (let dy = -radius; dy <= radius; dy++) {
|
||||
for (let dx = -radius; dx <= radius; dx++) {
|
||||
const nx = x + dx;
|
||||
const ny = y + dy;
|
||||
if (!inside(nx, ny)) continue;
|
||||
const d = Math.hypot(dx, dy);
|
||||
if (d > radius) continue;
|
||||
const i = indexOf(nx, ny);
|
||||
if (sea[i]) continue;
|
||||
localPenalty[i] = Math.max(localPenalty[i], strength * (1 - d / radius));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function routeAccess(start) {
|
||||
const startIndex = indexOf(start.x, start.y);
|
||||
if (sea[startIndex]) return [];
|
||||
const score = new Float32Array(MAP_W * MAP_H);
|
||||
const cameFrom = new Int32Array(MAP_W * MAP_H);
|
||||
const closed = new Uint8Array(MAP_W * MAP_H);
|
||||
score.fill(INF);
|
||||
cameFrom.fill(-1);
|
||||
const heap = new MinHeap();
|
||||
score[startIndex] = 0;
|
||||
heap.push({ i: startIndex, f: 0 });
|
||||
const maxExpanded = Math.min(MAP_W * MAP_H, 24000);
|
||||
let goal = -1;
|
||||
let expanded = 0;
|
||||
while (heap.length && expanded++ < maxExpanded) {
|
||||
const current = heap.pop();
|
||||
if (!current || closed[current.i]) continue;
|
||||
closed[current.i] = 1;
|
||||
const [cx, cy] = xyOf(current.i);
|
||||
const straightDistance = Math.hypot(cx - start.x, cy - start.y);
|
||||
if (straightDistance > 3 && (accessInfluence[current.i] > 0.11 || localPenalty[current.i] > 0.06)) {
|
||||
goal = current.i;
|
||||
break;
|
||||
}
|
||||
if (straightDistance > 150) continue;
|
||||
for (let dy = -1; dy <= 1; dy++) {
|
||||
for (let dx = -1; dx <= 1; dx++) {
|
||||
if (!dx && !dy) continue;
|
||||
const nx = cx + dx;
|
||||
const ny = cy + dy;
|
||||
if (!inside(nx, ny)) continue;
|
||||
const ni = indexOf(nx, ny);
|
||||
if (sea[ni]) continue;
|
||||
if (visited.has(`${nx},${ny}`)) continue;
|
||||
const cost =
|
||||
1.1 +
|
||||
slope[ni] * 1.4 +
|
||||
ridgeField[ni] * 0.55 +
|
||||
Math.max(0, elevation[ni] - 0.70) * 1.2 -
|
||||
Math.max(roadInfluence[ni], railInfluence2[ni]) * 2.8 -
|
||||
plain[ni] * 0.28 -
|
||||
valleyField[ni] * 0.18 -
|
||||
coastalLowland[ni] * 0.16 +
|
||||
localPenalty[ni] * 0.55;
|
||||
if (cost < bestScore) {
|
||||
bestScore = cost;
|
||||
best = [nx, ny];
|
||||
if (closed[ni] || sea[ni]) continue;
|
||||
const step = Math.hypot(dx, dy);
|
||||
const highPenalty = Math.max(0, elevation[ni] - 0.64);
|
||||
const targetAttraction = Math.max(accessInfluence[ni] * 2.4, localPenalty[ni] * 1.25, roadInfluence[ni] * 1.8, railInfluence2[ni] * 1.2);
|
||||
const terrainCost =
|
||||
1.0 +
|
||||
slope[ni] * 1.20 +
|
||||
ridgeField[ni] * 0.52 +
|
||||
highPenalty * 1.55 -
|
||||
plain[ni] * 0.38 -
|
||||
valleyField[ni] * 0.42 -
|
||||
coastalLowland[ni] * 0.18 +
|
||||
Math.max(0, localPenalty[ni] - 0.18) * 0.38 -
|
||||
targetAttraction;
|
||||
const nd = score[current.i] + Math.max(0.16, terrainCost) * step;
|
||||
if (nd < score[ni]) {
|
||||
score[ni] = nd;
|
||||
cameFrom[ni] = current.i;
|
||||
heap.push({ i: ni, f: nd });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!best) break;
|
||||
x = best[0];
|
||||
y = best[1];
|
||||
const key = `${x},${y}`;
|
||||
visited.add(key);
|
||||
if (key !== lastKey) {
|
||||
out.push([x, y]);
|
||||
lastKey = key;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
if (goal < 0) return [];
|
||||
const path = [];
|
||||
for (let p = goal; p >= 0; p = cameFrom[p]) {
|
||||
path.push(xyOf(p));
|
||||
if (p === startIndex) break;
|
||||
}
|
||||
path.reverse();
|
||||
if (path.length < 4 || path.length > 112) return [];
|
||||
return routeQualityAcceptable(path, { sea, elevation, slope, potential: populationDensity, penalty: localPenalty, highElevationThreshold: 0.78, steepThreshold: 0.52 }, {
|
||||
minLength: 4,
|
||||
maxLength: 112,
|
||||
maxCompactness: 4.0,
|
||||
maxHighElevationShare: 0.22,
|
||||
maxSteepShare: 0.50,
|
||||
}) ? path : [];
|
||||
}
|
||||
|
||||
for (const center of candidates) {
|
||||
|
|
@ -541,6 +610,7 @@ export function finishMapOutput({
|
|||
debugLayers.unservedSettlements.push({ x: center.x, y: center.y, kind: "Municipal Center", mode: "municipal-access", repaired: path.length >= 4 });
|
||||
if (path.length < 4) continue;
|
||||
minorRoads.push(path);
|
||||
addLocalPenalty(path);
|
||||
debugLayers.repairedSegments.push({ mode: "municipal-local", path, from: "municipal-center", to: "network" });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue