border tweak
This commit is contained in:
parent
4f0df3f6c5
commit
71b58cf033
7 changed files with 514 additions and 68 deletions
341
mapAdminStage.js
341
mapAdminStage.js
|
|
@ -105,9 +105,15 @@ function buildMunicipalityGraph(adminId, prefectureMask, sea, naturalBarrierScor
|
|||
for (let i = 0; i < SIZE; i++) {
|
||||
if (!prefectureMask[i] || sea[i] || adminId[i] < 0) continue;
|
||||
const id = adminId[i];
|
||||
if (!nodes.has(id)) nodes.set(id, { id, area: 0, population: 0, sx: 0, sy: 0 });
|
||||
if (!nodes.has(id)) nodes.set(id, { id, area: 0, population: 0, sx: 0, sy: 0, touchesOutside: false });
|
||||
const node = nodes.get(id);
|
||||
const [x, y] = xyOf(i);
|
||||
if (x === 0 || y === 0 || x === MAP_W - 1 || y === MAP_H - 1) node.touchesOutside = true;
|
||||
for (const [ox, oy] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) {
|
||||
if (!inside(ox, oy)) { node.touchesOutside = true; continue; }
|
||||
const oi = indexOf(ox, oy);
|
||||
if (!prefectureMask[oi] || sea[oi]) node.touchesOutside = true;
|
||||
}
|
||||
node.area++;
|
||||
node.population += populationDensity?.[i] || 0;
|
||||
node.sx += x;
|
||||
|
|
@ -141,7 +147,7 @@ function buildMunicipalityGraph(adminId, prefectureMask, sea, naturalBarrierScor
|
|||
function choosePrefectureMunicipalitySeeds(nodes, seed) {
|
||||
const active = [...nodes.values()].filter((node) => node.area > 0).sort((a, b) => b.area - a.area || a.id - b.id);
|
||||
const totalArea = active.reduce((sum, node) => sum + node.area, 0);
|
||||
const targetCount = clamp(Math.round(totalArea / 2300), 5, 12);
|
||||
const targetCount = clamp(Math.round(totalArea / 7200), 3, 6);
|
||||
const seeds = [];
|
||||
const first = active.sort((a, b) => (b.population * 1.8 + b.area * 0.08) - (a.population * 1.8 + a.area * 0.08) || a.id - b.id)[0];
|
||||
if (first) seeds.push(first);
|
||||
|
|
@ -242,6 +248,189 @@ function repairPrefectureMunicipalityConnectivity(nodes, owner) {
|
|||
return changed;
|
||||
}
|
||||
|
||||
function repairPrefectureMunicipalityEnclaves(nodes, owner, maxPasses = 6) {
|
||||
let changed = 0;
|
||||
for (let pass = 0; pass < maxPasses; pass++) {
|
||||
let passChanged = 0;
|
||||
const prefIds = [...new Set(owner.values())].filter((id) => id >= 0).sort((a, b) => a - b);
|
||||
for (const prefId of prefIds) {
|
||||
const members = [...nodes.keys()].filter((id) => owner.get(id) === prefId);
|
||||
const memberSet = new Set(members);
|
||||
const seen = new Set();
|
||||
for (const start of members) {
|
||||
if (seen.has(start)) continue;
|
||||
const queue = [start];
|
||||
const comp = [];
|
||||
seen.add(start);
|
||||
let touchesOutside = false;
|
||||
const boundaryPrefs = new Map();
|
||||
for (let q = 0; q < queue.length; q++) {
|
||||
const cur = queue[q];
|
||||
comp.push(cur);
|
||||
const node = nodes.get(cur);
|
||||
if (node?.touchesOutside) touchesOutside = true;
|
||||
for (const next of node?.adjacent.keys() || []) {
|
||||
const nextOwner = owner.get(next);
|
||||
if (nextOwner === prefId) {
|
||||
if (!seen.has(next)) { seen.add(next); queue.push(next); }
|
||||
} else if (nextOwner >= 0) {
|
||||
boundaryPrefs.set(nextOwner, (boundaryPrefs.get(nextOwner) || 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (touchesOutside || boundaryPrefs.size !== 1) continue;
|
||||
const [targetPref] = boundaryPrefs.keys();
|
||||
if (targetPref < 0 || targetPref === prefId) continue;
|
||||
for (const id of comp) owner.set(id, targetPref);
|
||||
passChanged += comp.length;
|
||||
}
|
||||
}
|
||||
changed += passChanged;
|
||||
if (!passChanged) break;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function repairPrefectureCellEnclavesFromGrid(adminId, owner, prefectureMask, sea, maxPasses = 5) {
|
||||
let changed = 0;
|
||||
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
|
||||
for (let pass = 0; pass < maxPasses; pass++) {
|
||||
const prefId = new Int16Array(SIZE);
|
||||
prefId.fill(-1);
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (!prefectureMask[i] || sea[i] || adminId[i] < 0) continue;
|
||||
prefId[i] = owner.get(adminId[i]) ?? -1;
|
||||
}
|
||||
const seen = new Uint8Array(SIZE);
|
||||
let passChanged = 0;
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (seen[i] || prefId[i] < 0) continue;
|
||||
const id = prefId[i];
|
||||
const queue = [i];
|
||||
const comp = [];
|
||||
seen[i] = 1;
|
||||
let touchesOutside = false;
|
||||
const boundaryCounts = new Map();
|
||||
const adminCounts = new Map();
|
||||
for (let q = 0; q < queue.length; q++) {
|
||||
const cur = queue[q];
|
||||
comp.push(cur);
|
||||
const aid = adminId[cur];
|
||||
if (aid >= 0) adminCounts.set(aid, (adminCounts.get(aid) || 0) + 1);
|
||||
const [x, y] = xyOf(cur);
|
||||
if (x === 0 || y === 0 || x === MAP_W - 1 || y === MAP_H - 1) touchesOutside = true;
|
||||
for (const [dx, dy] of dirs) {
|
||||
const nx = x + dx, ny = y + dy;
|
||||
if (!inside(nx, ny)) { touchesOutside = true; continue; }
|
||||
const ni = indexOf(nx, ny);
|
||||
if (!prefectureMask[ni] || sea[ni]) { touchesOutside = true; continue; }
|
||||
const nid = prefId[ni];
|
||||
if (nid === id) {
|
||||
if (!seen[ni]) { seen[ni] = 1; queue.push(ni); }
|
||||
} else if (nid >= 0) {
|
||||
boundaryCounts.set(nid, (boundaryCounts.get(nid) || 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (touchesOutside || boundaryCounts.size !== 1) continue;
|
||||
const [targetPref] = boundaryCounts.keys();
|
||||
if (targetPref < 0 || targetPref === id) continue;
|
||||
for (const aid of adminCounts.keys()) {
|
||||
if (owner.get(aid) !== targetPref) { owner.set(aid, targetPref); passChanged++; }
|
||||
}
|
||||
}
|
||||
changed += passChanged;
|
||||
if (!passChanged) break;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function repairAdminSingleOwnerEnclaves(adminId, prefectureMask, sea, maxPasses = 5) {
|
||||
let changed = 0;
|
||||
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
|
||||
for (let pass = 0; pass < maxPasses; pass++) {
|
||||
const seen = new Uint8Array(SIZE);
|
||||
let passChanged = 0;
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (seen[i] || !prefectureMask[i] || sea[i] || adminId[i] < 0) continue;
|
||||
const id = adminId[i];
|
||||
const queue = [i];
|
||||
const comp = [];
|
||||
seen[i] = 1;
|
||||
let touchesOutside = false;
|
||||
const boundaryCounts = new Map();
|
||||
for (let q = 0; q < queue.length; q++) {
|
||||
const cur = queue[q];
|
||||
comp.push(cur);
|
||||
const [x, y] = xyOf(cur);
|
||||
if (x === 0 || y === 0 || x === MAP_W - 1 || y === MAP_H - 1) touchesOutside = true;
|
||||
for (const [dx, dy] of dirs) {
|
||||
const nx = x + dx, ny = y + dy;
|
||||
if (!inside(nx, ny)) { touchesOutside = true; continue; }
|
||||
const ni = indexOf(nx, ny);
|
||||
if (!prefectureMask[ni] || sea[ni]) { touchesOutside = true; continue; }
|
||||
const nid = adminId[ni];
|
||||
if (nid === id) {
|
||||
if (!seen[ni]) { seen[ni] = 1; queue.push(ni); }
|
||||
} else if (nid >= 0) {
|
||||
boundaryCounts.set(nid, (boundaryCounts.get(nid) || 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (touchesOutside || boundaryCounts.size !== 1) continue;
|
||||
const [targetId] = boundaryCounts.keys();
|
||||
if (targetId < 0 || targetId === id) continue;
|
||||
for (const ci of comp) adminId[ci] = targetId;
|
||||
passChanged += comp.length;
|
||||
}
|
||||
changed += passChanged;
|
||||
if (!passChanged) break;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function lockCompactUrbanAreasToDominantAdmin(adminId, prefectureMask, sea, landuse, populationDensity, maxCells = 950) {
|
||||
if (!landuse || !populationDensity) return 0;
|
||||
const seen = new Uint8Array(SIZE);
|
||||
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
|
||||
let changed = 0;
|
||||
const isUrban = (i) => !sea[i] && prefectureMask[i] && (populationDensity[i] > 0.34 || [2, 3, 4, 7, 8].includes(landuse[i]));
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (seen[i] || !isUrban(i) || adminId[i] < 0) continue;
|
||||
const queue = [i];
|
||||
const comp = [];
|
||||
seen[i] = 1;
|
||||
const counts = new Map();
|
||||
for (let q = 0; q < queue.length; q++) {
|
||||
const cur = queue[q];
|
||||
comp.push(cur);
|
||||
const id = adminId[cur];
|
||||
if (id >= 0) counts.set(id, (counts.get(id) || 0) + 1 + (populationDensity[cur] || 0) * 2.0);
|
||||
const [x, y] = xyOf(cur);
|
||||
for (const [dx, dy] of dirs) {
|
||||
const nx = x + dx, ny = y + dy;
|
||||
if (!inside(nx, ny)) continue;
|
||||
const ni = indexOf(nx, ny);
|
||||
if (seen[ni] || !isUrban(ni)) continue;
|
||||
seen[ni] = 1;
|
||||
queue.push(ni);
|
||||
}
|
||||
}
|
||||
if (comp.length < 8 || comp.length > maxCells || counts.size <= 1 || counts.size > 7) continue;
|
||||
let best = -1, bestScore = -INF;
|
||||
let total = 0;
|
||||
for (const [id, score] of counts) {
|
||||
total += score;
|
||||
if (score > bestScore || (score === bestScore && id < best)) { best = id; bestScore = score; }
|
||||
}
|
||||
if (best < 0 || bestScore / Math.max(1, total) < 0.38) continue;
|
||||
for (const ci of comp) {
|
||||
if (adminId[ci] !== best) { adminId[ci] = best; changed++; }
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function mergeTinyMunicipalityPrefectures(nodes, owner) {
|
||||
let changed = 0;
|
||||
for (let pass = 0; pass < 6; pass++) {
|
||||
|
|
@ -277,6 +466,111 @@ function mergeTinyMunicipalityPrefectures(nodes, owner) {
|
|||
return changed;
|
||||
}
|
||||
|
||||
|
||||
function splitOversizedCompartmentMunicipalities(adminId, centers, compartments, prefectureMask, sea, fields = {}, seed = 0) {
|
||||
if (!compartments?.length) return { changedCells: 0, splitMunicipalities: 0, addedCenters: 0 };
|
||||
const compOwner = new Int16Array(compartments.length);
|
||||
compOwner.fill(-1);
|
||||
for (const comp of compartments) {
|
||||
if (!comp || !comp.cells?.length) continue;
|
||||
const counts = new Map();
|
||||
for (const i of comp.cells) {
|
||||
if (!prefectureMask[i] || sea[i]) continue;
|
||||
const id = adminId[i];
|
||||
if (id >= 0) counts.set(id, (counts.get(id) || 0) + 1);
|
||||
}
|
||||
let best = -1, bestCount = -1;
|
||||
for (const [id, count] of counts) if (count > bestCount || (count === bestCount && id < best)) { best = id; bestCount = count; }
|
||||
compOwner[comp.id] = best;
|
||||
}
|
||||
const byOwner = new Map();
|
||||
for (const comp of compartments) {
|
||||
if (!comp || !comp.cells?.length) continue;
|
||||
const owner = compOwner[comp.id];
|
||||
if (owner < 0) continue;
|
||||
if (!byOwner.has(owner)) byOwner.set(owner, []);
|
||||
byOwner.get(owner).push(comp);
|
||||
}
|
||||
const areas = [...byOwner.values()].map((list) => list.reduce((sum, comp) => sum + comp.area, 0)).sort((a, b) => a - b);
|
||||
if (!areas.length) return { changedCells: 0, splitMunicipalities: 0, addedCenters: 0 };
|
||||
const median = areas[Math.floor(areas.length / 2)] || 1;
|
||||
const total = areas.reduce((sum, value) => sum + value, 0);
|
||||
const maxArea = Math.max(360, Math.min(total * 0.11, Math.max(median * 2.65, total / Math.max(12, Math.round(total / 520)))));
|
||||
let changedCells = 0;
|
||||
let splitMunicipalities = 0;
|
||||
let addedCenters = 0;
|
||||
const elevation = fields.elevation;
|
||||
const slope = fields.slope;
|
||||
const ridgeField = fields.ridgeField;
|
||||
const plain = fields.plain;
|
||||
const agriculture = fields.agriculture;
|
||||
const basinField = fields.basinField;
|
||||
const coastalLowland = fields.coastalLowland;
|
||||
const populationDensity = fields.populationDensity;
|
||||
for (const [owner, list] of [...byOwner.entries()].sort((a, b) => a[0] - b[0])) {
|
||||
const area = list.reduce((sum, comp) => sum + comp.area, 0);
|
||||
if (area <= maxArea || list.length < 4) continue;
|
||||
const desiredParts = clamp(Math.ceil(area / Math.max(1, maxArea)), 2, 9);
|
||||
const splitCount = desiredParts - 1;
|
||||
if (splitCount <= 0) continue;
|
||||
const candidates = list.map((comp) => {
|
||||
let sx = 0, sy = 0, n = 0, score = 0, bestI = -1, bestScore = -INF;
|
||||
for (const i of comp.cells) {
|
||||
if (!prefectureMask[i] || sea[i]) continue;
|
||||
const [x, y] = xyOf(i);
|
||||
sx += x; sy += y; n++;
|
||||
const cellScore =
|
||||
(plain?.[i] || 0) * 0.22 +
|
||||
(agriculture?.[i] || 0) * 0.24 +
|
||||
(basinField?.[i] || 0) * 0.14 +
|
||||
(coastalLowland?.[i] || 0) * 0.10 +
|
||||
(populationDensity?.[i] || 0) * 0.24 -
|
||||
(slope?.[i] || 0) * 0.20 -
|
||||
(ridgeField?.[i] || 0) * 0.18 -
|
||||
Math.max(0, (elevation?.[i] || 0) - 0.62) * 0.38;
|
||||
score += cellScore;
|
||||
if (cellScore > bestScore) { bestScore = cellScore; bestI = i; }
|
||||
}
|
||||
const [x, y] = bestI >= 0 ? xyOf(bestI) : [Math.round(sx / Math.max(1, n)), Math.round(sy / Math.max(1, n))];
|
||||
return { comp, x, y, score: score / Math.max(1, n) + Math.sqrt(comp.area) * 0.025 + hash2(seed + owner, comp.id) * 0.03 };
|
||||
}).sort((a, b) => b.score - a.score || a.comp.id - b.comp.id);
|
||||
const newSeeds = [];
|
||||
for (const cand of candidates) {
|
||||
if (newSeeds.length >= splitCount) break;
|
||||
if (newSeeds.every((s) => Math.hypot(s.x - cand.x, s.y - cand.y) >= 9)) newSeeds.push(cand);
|
||||
}
|
||||
if (!newSeeds.length) continue;
|
||||
const seedIds = newSeeds.map((cand) => {
|
||||
const id = centers.length;
|
||||
centers.push({ x: cand.x, y: cand.y, score: cand.score, invisibleLowlandAdminSeed: true, seedKind: "oversizedMunicipalitySplit", splitFromAdminId: owner });
|
||||
addedCenters++;
|
||||
return id;
|
||||
});
|
||||
const oldCenter = centers[owner] || candidates[0] || { x: list[0].x || 0, y: list[0].y || 0, score: 0 };
|
||||
const owners = [{ id: owner, x: oldCenter.x, y: oldCenter.y, score: oldCenter.score || 0 }, ...newSeeds.map((cand, k) => ({ id: seedIds[k], x: cand.x, y: cand.y, score: cand.score }))];
|
||||
const targetArea = area / Math.max(1, owners.length);
|
||||
const claimedArea = new Map(owners.map((entry) => [entry.id, 0]));
|
||||
for (const cand of candidates) {
|
||||
let bestSeed = owner;
|
||||
let bestCost = INF;
|
||||
for (const entry of owners) {
|
||||
const d = Math.hypot(cand.x - entry.x, cand.y - entry.y);
|
||||
const pressure = Math.max(0, ((claimedArea.get(entry.id) || 0) + cand.comp.area - targetArea * 1.25) / Math.max(1, targetArea));
|
||||
const cost = d + pressure * 18 - cand.score * 2.5 + hash2(entry.id, cand.comp.id) * 0.05;
|
||||
if (cost < bestCost) { bestCost = cost; bestSeed = entry.id; }
|
||||
}
|
||||
claimedArea.set(bestSeed, (claimedArea.get(bestSeed) || 0) + cand.comp.area);
|
||||
if (bestSeed === owner) continue;
|
||||
for (const i of cand.comp.cells) {
|
||||
if (!prefectureMask[i] || sea[i]) continue;
|
||||
if (adminId[i] !== bestSeed) { adminId[i] = bestSeed; changedCells++; }
|
||||
}
|
||||
}
|
||||
splitMunicipalities++;
|
||||
}
|
||||
return { changedCells, splitMunicipalities, addedCenters, maxArea };
|
||||
}
|
||||
|
||||
function extractPrefectureBordersFromMunicipalities(adminId, municipalityToPrefectureId, prefectureMask, sea) {
|
||||
const segments = [];
|
||||
for (let y = 0; y < MAP_H; y++) {
|
||||
|
|
@ -306,7 +600,11 @@ function generatePrefecturesFromMunicipalities(context, adminResult) {
|
|||
const seeds = choosePrefectureMunicipalitySeeds(graph.nodes, seed + 91001);
|
||||
const owner = assignMunicipalitiesToPrefectures(graph.nodes, seeds);
|
||||
const changedForTinyMerge = mergeTinyMunicipalityPrefectures(graph.nodes, owner);
|
||||
const changedForConnectivity = repairPrefectureMunicipalityConnectivity(graph.nodes, owner);
|
||||
let changedForConnectivity = repairPrefectureMunicipalityConnectivity(graph.nodes, owner);
|
||||
let changedForEnclaveRepair = repairPrefectureMunicipalityEnclaves(graph.nodes, owner);
|
||||
changedForConnectivity += repairPrefectureMunicipalityConnectivity(graph.nodes, owner);
|
||||
changedForEnclaveRepair += repairPrefectureCellEnclavesFromGrid(adminId, owner, prefectureMask, sea);
|
||||
changedForConnectivity += repairPrefectureMunicipalityConnectivity(graph.nodes, owner);
|
||||
const maxAdminId = Math.max(-1, ...[...graph.nodes.keys()]);
|
||||
const municipalityToPrefectureId = new Int16Array(maxAdminId + 1);
|
||||
municipalityToPrefectureId.fill(-1);
|
||||
|
|
@ -338,6 +636,7 @@ function generatePrefecturesFromMunicipalities(context, adminResult) {
|
|||
prefectureMunicipalitySeedCount: seeds.length,
|
||||
prefectureTinyMergeChangedMunicipalities: changedForTinyMerge,
|
||||
prefectureConnectivityRepairChangedMunicipalities: changedForConnectivity,
|
||||
prefectureEnclaveRepairChangedMunicipalities: changedForEnclaveRepair,
|
||||
finalRegionalMaxAreaShare: totalArea ? Math.max(0, ...areaByPref.values()) / totalArea : 0,
|
||||
finalRegionalTinyPrefectureCount: [...areaByPref.values()].filter((area) => area < 520).length,
|
||||
finalRegionalPrefectureBorderCount: regionalPrefectureBorders.length,
|
||||
|
|
@ -601,13 +900,13 @@ function municipalityCountBoundsForRegion(landCells, meta = {}) {
|
|||
// Use the same administrative density curve for the highlighted prefecture
|
||||
// and neighboring prefectures. Only clipped slivers get a low floor.
|
||||
let min = 1;
|
||||
if (landCells >= 420) min = 2;
|
||||
if (landCells >= 850) min = 3;
|
||||
if (landCells >= 1500) min = 5;
|
||||
if (landCells >= 2500) min = 8;
|
||||
if (landCells >= 3800) min = 12;
|
||||
if (landCells >= 5600) min = 16;
|
||||
const max = clamp(Math.round(landCells / 230 + 4), Math.max(min, 3), 46);
|
||||
if (landCells >= 360) min = 2;
|
||||
if (landCells >= 750) min = 4;
|
||||
if (landCells >= 1400) min = 7;
|
||||
if (landCells >= 2400) min = 11;
|
||||
if (landCells >= 3800) min = 16;
|
||||
if (landCells >= 5600) min = 22;
|
||||
const max = clamp(Math.round(landCells / 160 + 6), Math.max(min, 4), 72);
|
||||
return { min, max };
|
||||
}
|
||||
|
||||
|
|
@ -635,11 +934,11 @@ function computeTargetMunicipalityCount({ prefectureMask, sea, elevation, slope,
|
|||
}
|
||||
}
|
||||
const independentSatellites = (satelliteCities || []).filter((p) => p.municipalityClass === "independentSatelliteMunicipality").length;
|
||||
const settlementWeight = modernCities.length * 1.6 + markets.length * 1.0 + ports.length * 0.8 + independentSatellites * 0.7 + villages.length * 0.25;
|
||||
const settlementWeight = modernCities.length * 1.7 + markets.length * 1.15 + ports.length * 0.8 + independentSatellites * 0.7 + villages.length * 0.32;
|
||||
const basinBonus = Math.min(8, [...basinField].filter((v, i) => prefectureMask[i] && !sea[i] && v > 0.34).length / 520);
|
||||
const mountainRatio = landCells ? mountainCells / landCells : 0;
|
||||
const lowlandBonus = Math.min(7, lowlandCells / 430);
|
||||
const rawTarget = Math.round(habitableCells / 230 + settlementWeight + coastlineComplexity * 0.03 + basinBonus * 0.55 + lowlandBonus - mountainRatio * 2.2);
|
||||
const rawTarget = Math.round(habitableCells / 175 + settlementWeight + coastlineComplexity * 0.03 + basinBonus * 0.65 + lowlandBonus * 1.15 - mountainRatio * 1.8);
|
||||
const { min, max } = municipalityCountBoundsForRegion(landCells, adminRegionMeta);
|
||||
return clamp(rawTarget, min, max);
|
||||
}
|
||||
|
|
@ -1076,7 +1375,12 @@ function generateAdminLayoutForMask({
|
|||
});
|
||||
const adminId = compartmentAssignment.adminId;
|
||||
if (naturalCompartmentId && naturalCompartments) {
|
||||
const oversizedCompartmentSplit = splitOversizedCompartmentMunicipalities(adminId, adminCentersRaw, compartmentAssignment.compartments, prefectureMask, sea, {
|
||||
elevation, slope, ridgeField: boundaryRidgeField, plain, agriculture, basinField, coastalLowland, populationDensity,
|
||||
}, seed + 21900);
|
||||
const changedAfterFinalCompartmentOwnership = enforceCompartmentMunicipalityOwnership(adminId, compartmentAssignment.compartmentId, compartmentAssignment.compartments, prefectureMask, sea);
|
||||
const changedAfterUrbanUnification = lockCompactUrbanAreasToDominantAdmin(adminId, prefectureMask, sea, landuse, populationDensity, 980);
|
||||
const changedAfterAdminEnclaveRepair = repairAdminSingleOwnerEnclaves(adminId, prefectureMask, sea, 6);
|
||||
const compacted = compactWholeCompartmentMunicipalities(adminId, adminCentersRaw, prefectureMask, sea, { populationDensity, plain, slope });
|
||||
const adminBorders = extractAdminBorderSegments(compacted.adminId, prefectureMask);
|
||||
const actualMunicipalityCount = compacted.activeMunicipalityCount;
|
||||
|
|
@ -1089,11 +1393,21 @@ function generateAdminLayoutForMask({
|
|||
finalMunicipalityCount: actualMunicipalityCount,
|
||||
candidateSeedCount: adminCentersRaw.length,
|
||||
municipalOfficePointCount: compacted.adminCentersRaw.length,
|
||||
seedCellRevivalCount: 0,
|
||||
survivedSeedCount: compacted.activeMunicipalityCount,
|
||||
pendingSeedCount: 0,
|
||||
absorbedSeedCount: Math.max(0, adminCentersRaw.length - compacted.activeMunicipalityCount),
|
||||
targetNaturalCompartmentCount: targetCompartmentCount,
|
||||
naturalCompartmentCount: compartmentAssignment.debug?.naturalCompartmentCount || naturalCompartments.filter((unit) => unit && unit.area > 0).length,
|
||||
compartmentCount: compartmentAssignment.debug?.naturalCompartmentCount || naturalCompartments.filter((unit) => unit && unit.area > 0).length,
|
||||
changedAfterCompartmentAssignment: compartmentAssignment.debug?.naturalCompartmentCount || 0,
|
||||
changedAfterFinalCompartmentOwnership,
|
||||
changedAfterUrbanUnification,
|
||||
changedAfterAdminEnclaveRepair,
|
||||
changedAfterOversizedCompartmentSplit: oversizedCompartmentSplit.changedCells,
|
||||
oversizedCompartmentMunicipalitiesSplit: oversizedCompartmentSplit.splitMunicipalities,
|
||||
oversizedCompartmentSplitAddedCenters: oversizedCompartmentSplit.addedCenters,
|
||||
oversizedCompartmentSplitMaxArea: oversizedCompartmentSplit.maxArea || 0,
|
||||
finalTinyMunicipalityCount: [...municipalityAreaById(compacted.adminId, prefectureMask, sea).values()].filter((area) => area > 0 && area < 8).length,
|
||||
compartmentBorders: compartmentAssignment.debug?.compartmentBorders || [],
|
||||
borderNaturalBarrierAverage: compartmentAssignment.debug?.finalBorderNaturalBarrierAverage || 0,
|
||||
|
|
@ -1318,6 +1632,9 @@ function generateAdminLayoutForMask({
|
|||
adminDebug.changedAfterFinalCompartmentOwnership = enforceCompartmentMunicipalityOwnership(adminId, compartmentAssignment.compartmentId, compartmentAssignment.compartments, prefectureMask, sea);
|
||||
removeMunicipalExclaves(adminId, prefectureMask, sea, adminCentersRaw, [...modernCities, ...activeAdminCenters()], 220);
|
||||
adminDebug.changedAfterPostCompartmentExclaveRemoval = enforceCompartmentMunicipalityOwnership(adminId, compartmentAssignment.compartmentId, compartmentAssignment.compartments, prefectureMask, sea);
|
||||
adminDebug.changedAfterUrbanUnification = lockCompactUrbanAreasToDominantAdmin(adminId, prefectureMask, sea, landuse, populationDensity, 980);
|
||||
adminDebug.changedAfterAdminEnclaveRepair = repairAdminSingleOwnerEnclaves(adminId, prefectureMask, sea, 6);
|
||||
adminDebug.changedAfterFinalCompartmentOwnership += enforceCompartmentMunicipalityOwnership(adminId, compartmentAssignment.compartmentId, compartmentAssignment.compartments, prefectureMask, sea);
|
||||
|
||||
const areaById = municipalityAreaById(adminId, prefectureMask, sea);
|
||||
const satelliteAreas = [];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue