not good but not bad
This commit is contained in:
parent
5c82bfcab7
commit
4f0df3f6c5
11 changed files with 1284 additions and 622 deletions
|
|
@ -1076,125 +1076,6 @@ function buildSeededNaturalCompartments(prefectureMask, sea, elevation, slope, r
|
|||
|
||||
export function buildNaturalCompartments(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, crestCrossingScore = null, plain = null, agriculture = null, populationDensity = null, landuse = null, options = {}) {
|
||||
return buildSeededNaturalCompartments(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, crestCrossingScore, plain, agriculture, populationDensity, landuse, options);
|
||||
const naturalBarrierScore = buildNaturalBarrierScore(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, crestCrossingScore, plain, agriculture, populationDensity, landuse);
|
||||
const compartmentId = new Int32Array(SIZE);
|
||||
compartmentId.fill(-1);
|
||||
const cellClass = new Int16Array(SIZE);
|
||||
cellClass.fill(-1);
|
||||
for (let i = 0; i < SIZE; i++) if (prefectureMask[i] && !sea[i]) cellClass[i] = classifyLandscapeCell(i, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse);
|
||||
|
||||
const compartments = [];
|
||||
const queue = [];
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (cellClass[i] < 0 || compartmentId[i] >= 0) continue;
|
||||
const id = compartments.length;
|
||||
const startClass = cellClass[i];
|
||||
const cells = [];
|
||||
let sx = 0, sy = 0, pop = 0, urbanWeight = 0, ridgeExposure = 0, riverExposure = 0, coastalExposure = 0, basinIdentity = 0, valleyIdentity = 0;
|
||||
let minX = MAP_W, minY = MAP_H, maxX = 0, maxY = 0;
|
||||
queue.length = 0;
|
||||
queue.push(i);
|
||||
compartmentId[i] = id;
|
||||
for (let q = 0; q < queue.length; q++) {
|
||||
const cur = queue[q];
|
||||
const [x, y] = xyOf(cur);
|
||||
cells.push(cur);
|
||||
sx += x; sy += y; pop += populationDensity[cur];
|
||||
minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y);
|
||||
urbanWeight += urbanBoundaryPenalty(cur, populationDensity, landuse);
|
||||
ridgeExposure += ridgeField[cur];
|
||||
riverExposure += river[cur] + flowAccum[cur] * 0.45;
|
||||
coastalExposure += coastalLowland[cur];
|
||||
basinIdentity += basinField[cur];
|
||||
valleyIdentity += valleyField[cur];
|
||||
for (const [nx, ny] of neighbors4(x, y)) {
|
||||
const ni = indexOf(nx, ny);
|
||||
if (compartmentId[ni] >= 0 || cellClass[ni] < 0) continue;
|
||||
const edgeBarrier = (naturalBarrierScore[cur] + naturalBarrierScore[ni]) * 0.5;
|
||||
if (!canShareNaturalCompartment(cur, ni, startClass, cellClass[ni], edgeBarrier, river, flowAccum, valleyField, populationDensity, landuse)) continue;
|
||||
compartmentId[ni] = id;
|
||||
queue.push(ni);
|
||||
}
|
||||
}
|
||||
const area = cells.length;
|
||||
const unit = {
|
||||
id,
|
||||
cells,
|
||||
area,
|
||||
x: sx / area,
|
||||
y: sy / area,
|
||||
classId: startClass,
|
||||
dominantLandscapeClass: startClass,
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY,
|
||||
width: maxX - minX + 1,
|
||||
height: maxY - minY + 1,
|
||||
elongation: Math.max(maxX - minX + 1, maxY - minY + 1) / Math.max(1, Math.min(maxX - minX + 1, maxY - minY + 1)),
|
||||
population: pop,
|
||||
urbanWeight: urbanWeight / area,
|
||||
ridgeExposure: ridgeExposure / area,
|
||||
riverExposure: riverExposure / area,
|
||||
coastalExposure: coastalExposure / area,
|
||||
basinIdentity: basinIdentity / area,
|
||||
valleyIdentity: valleyIdentity / area,
|
||||
centerIds: [],
|
||||
adjacent: new Map(),
|
||||
};
|
||||
unit.lowlandFitness = cells.reduce((sum, ci) => sum + lowlandCompartmentFitness(ci, elevation, slope, ridgeField, valleyField, basinField, coastalLowland, plain, agriculture, populationDensity, landuse), 0) / area;
|
||||
unit.mountainFitness = cells.reduce((sum, ci) => sum + mountainCompartmentFitness(ci, elevation, slope, ridgeField, populationDensity, landuse), 0) / area;
|
||||
compartments.push(unit);
|
||||
}
|
||||
rebuildLandscapeUnitAdjacency(compartmentId, compartments, naturalBarrierScore, prefectureMask, sea);
|
||||
mergeTinyLandscapeUnits(compartmentId, compartments, 12);
|
||||
rebuildLandscapeUnitAdjacency(compartmentId, compartments, naturalBarrierScore, prefectureMask, sea);
|
||||
const targetCount = options.targetCompartmentCount || 0;
|
||||
if (targetCount > 0) {
|
||||
const fields = { elevation, slope, ridgeField, valleyField, basinField, coastalLowland, plain, agriculture, populationDensity, landuse, naturalBarrierScore, river, flowAccum };
|
||||
const landArea = compartments.reduce((sum, unit) => sum + (unit.area || 0), 0);
|
||||
const maxNaturalCompartmentArea = options.maxNaturalCompartmentArea || Math.max(34, Math.round(landArea / Math.max(1, targetCount) * 1.65));
|
||||
const splitScore = (unit) => {
|
||||
const elongated = Math.max(0, (unit.elongation || 1) - 2.1);
|
||||
const areaPressure = unit.area / Math.max(1, maxNaturalCompartmentArea);
|
||||
const settled = (unit.lowlandFitness || 0) * 0.65 + (unit.urbanWeight || 0) * 0.35;
|
||||
return areaPressure * 2.2 + elongated * 1.4 + settled - (unit.mountainFitness || 0) * 0.20;
|
||||
};
|
||||
let guard = Math.max(targetCount * 4, 80);
|
||||
while (compartments.filter((unit) => unit.area > 0).length < targetCount && guard-- > 0) {
|
||||
const candidates = compartments
|
||||
.filter((unit) => unit.area > 0 && unit.area >= 24 && ((unit.lowlandFitness || 0) > 0.18 || unit.area > maxNaturalCompartmentArea * 1.20 || (unit.elongation || 1) > 2.8))
|
||||
.sort((a, b) => splitScore(b) - splitScore(a));
|
||||
const target = candidates[0];
|
||||
if (!target) break;
|
||||
const newUnit = splitOneNaturalCompartment(target, compartments.length, compartmentId, fields, (options.seed || 0) + guard);
|
||||
if (!newUnit) {
|
||||
target._splitRejected = (target._splitRejected || 0) + 1;
|
||||
target.elongation = Math.max(1, (target.elongation || 1) * 0.72);
|
||||
if (target._splitRejected > 2) target.area = target.cells.length;
|
||||
continue;
|
||||
}
|
||||
compartments.push(newUnit);
|
||||
if (compartments.filter((unit) => unit.area > 0).length % 12 === 0) rebuildLandscapeUnitAdjacency(compartmentId, compartments, naturalBarrierScore, prefectureMask, sea);
|
||||
}
|
||||
|
||||
guard = Math.max(targetCount * 2, 60);
|
||||
while (guard-- > 0) {
|
||||
const target = compartments
|
||||
.filter((unit) => unit.area > 0 && unit.area >= 24 && (unit.area > maxNaturalCompartmentArea * 1.55 || ((unit.elongation || 1) > 3.2 && unit.area > maxNaturalCompartmentArea * 0.85)))
|
||||
.sort((a, b) => splitScore(b) - splitScore(a))[0];
|
||||
if (!target) break;
|
||||
const newUnit = splitOneNaturalCompartment(target, compartments.length, compartmentId, fields, (options.seed || 0) + guard + 991);
|
||||
if (!newUnit) {
|
||||
target.elongation = Math.max(1, (target.elongation || 1) * 0.70);
|
||||
break;
|
||||
}
|
||||
compartments.push(newUnit);
|
||||
if (compartments.filter((unit) => unit.area > 0).length % 12 === 0) rebuildLandscapeUnitAdjacency(compartmentId, compartments, naturalBarrierScore, prefectureMask, sea);
|
||||
}
|
||||
rebuildLandscapeUnitAdjacency(compartmentId, compartments, naturalBarrierScore, prefectureMask, sea);
|
||||
}
|
||||
return { compartmentId, compartments, naturalBarrierScore };
|
||||
}
|
||||
|
||||
function buildLandscapeUnits(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse) {
|
||||
|
|
@ -1502,7 +1383,11 @@ export function extractCompartmentBorders(compartmentId, prefectureMask, sea) {
|
|||
|
||||
export function assignAdminRegionsFromNaturalCompartments(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse, adminCenters = [], options = {}) {
|
||||
const progress = typeof options.progress === "function" ? options.progress : null;
|
||||
const { compartmentId, compartments, naturalBarrierScore } = buildNaturalCompartments(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, null, plain, agriculture, populationDensity, landuse, options);
|
||||
const sharedCompartments = options.naturalCompartmentId && options.naturalCompartments
|
||||
? { compartmentId: options.naturalCompartmentId, compartments: options.naturalCompartments, naturalBarrierScore: options.naturalBarrierScore || ridgeField }
|
||||
: null;
|
||||
const { compartmentId, compartments, naturalBarrierScore } = sharedCompartments ||
|
||||
buildNaturalCompartments(prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, null, plain, agriculture, populationDensity, landuse, options);
|
||||
progress?.("natural compartments built");
|
||||
const adminId = new Int16Array(SIZE);
|
||||
adminId.fill(-1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue