hm
This commit is contained in:
parent
f2d0306d96
commit
27ceb6568a
7 changed files with 940 additions and 132 deletions
80
mapOutput.js
80
mapOutput.js
|
|
@ -1,6 +1,7 @@
|
|||
import { createNameDebug } from "./names.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 { reconcileMunicipalMetadata } from "./mapMunicipalCoherence.js";
|
||||
import { routeQualityAcceptable } from "./mapTransport.js";
|
||||
|
||||
const MUNICIPAL_SUFFIX_RE = /[市町村区]$/u;
|
||||
|
|
@ -30,15 +31,33 @@ function municipalityNameFromRoot(root, center, fields, seed, ordinal = 0) {
|
|||
}
|
||||
|
||||
|
||||
function centerMunicipalityId(center, fallback = -1) {
|
||||
for (const key of ["adminId", "municipalityId", "adminNumericId"]) {
|
||||
const value = center?.[key];
|
||||
if (Number.isFinite(value) && value >= 0) return Math.floor(value);
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function assignMunicipalityPopulations(adminCenters, adminId, fields, settlementFeatures = []) {
|
||||
if (!adminCenters?.length || !adminId) return;
|
||||
const totals = new Float64Array(adminCenters.length);
|
||||
const settlementTotals = new Float64Array(adminCenters.length);
|
||||
const landCells = new Uint32Array(adminCenters.length);
|
||||
const inhabitedCells = new Uint32Array(adminCenters.length);
|
||||
const centerById = new Map();
|
||||
let maxId = -1;
|
||||
for (const center of adminCenters) {
|
||||
const id = centerMunicipalityId(center);
|
||||
if (id >= 0 && !centerById.has(id)) {
|
||||
centerById.set(id, center);
|
||||
maxId = Math.max(maxId, id);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < adminId.length; i++) if (adminId[i] >= 0) maxId = Math.max(maxId, adminId[i]);
|
||||
const totals = new Float64Array(maxId + 1);
|
||||
const settlementTotals = new Float64Array(maxId + 1);
|
||||
const landCells = new Uint32Array(maxId + 1);
|
||||
const inhabitedCells = new Uint32Array(maxId + 1);
|
||||
for (let i = 0; i < adminId.length; i++) {
|
||||
const id = adminId[i];
|
||||
if (id < 0 || id >= totals.length || fields.sea?.[i]) continue;
|
||||
if (id < 0 || fields.sea?.[i]) continue;
|
||||
landCells[id]++;
|
||||
const density = fields.populationDensity?.[i] || 0;
|
||||
const lu = fields.landuse?.[i] ?? 0;
|
||||
|
|
@ -68,7 +87,7 @@ function assignMunicipalityPopulations(adminCenters, adminId, fields, settlement
|
|||
let skippedDuplicateSettlementPopulation = 0;
|
||||
for (const { feature, i } of uniqueSettlementByCell.values()) {
|
||||
const id = adminId[i];
|
||||
if (id < 0 || id >= totals.length) continue;
|
||||
if (id < 0 || id >= settlementTotals.length) continue;
|
||||
settlementTotals[id] += feature.population;
|
||||
}
|
||||
for (const feature of settlementFeatures || []) {
|
||||
|
|
@ -77,7 +96,7 @@ function assignMunicipalityPopulations(adminCenters, adminId, fields, settlement
|
|||
const kept = uniqueSettlementByCell.get(key)?.feature;
|
||||
if (kept && kept !== feature) skippedDuplicateSettlementPopulation += feature.population || 0;
|
||||
}
|
||||
for (let id = 0; id < adminCenters.length; id++) {
|
||||
for (const [id, center] of centerById) {
|
||||
const raw = (totals[id] || 0) + (settlementTotals[id] || 0);
|
||||
const minimumResidentPopulation = landCells[id] > 0
|
||||
? Math.round(Math.max(100, Math.min(3800, 80 + landCells[id] * 9 + inhabitedCells[id] * 16)) / 100) * 100
|
||||
|
|
@ -85,13 +104,13 @@ function assignMunicipalityPopulations(adminCenters, adminId, fields, settlement
|
|||
const adjustedRaw = Math.max(raw, minimumResidentPopulation);
|
||||
const rounded = adjustedRaw >= 10000 ? Math.round(adjustedRaw / 1000) * 1000 : Math.max(minimumResidentPopulation, Math.round(adjustedRaw / 100) * 100);
|
||||
const safePopulation = Math.max(landCells[id] > 0 ? 100 : 0, rounded);
|
||||
adminCenters[id].municipalityPopulation = safePopulation;
|
||||
center.municipalityPopulation = safePopulation;
|
||||
// Some consumers still read the generic `population` field from municipal
|
||||
// centers. Mirror the municipality total there so no municipality is shown
|
||||
// as 0人 merely because it is not a canonical city/market entity.
|
||||
adminCenters[id].population = Math.max(adminCenters[id].population || 0, safePopulation);
|
||||
adminCenters[id].municipalitySettlementPopulation = Math.max(0, Math.round((settlementTotals[id] || 0) / 100) * 100);
|
||||
adminCenters[id].municipalitySkippedDuplicateSettlementPopulation = Math.max(0, Math.round(skippedDuplicateSettlementPopulation / 100) * 100);
|
||||
center.population = Math.max(center.population || 0, safePopulation);
|
||||
center.municipalitySettlementPopulation = Math.max(0, Math.round((settlementTotals[id] || 0) / 100) * 100);
|
||||
center.municipalitySkippedDuplicateSettlementPopulation = Math.max(0, Math.round(skippedDuplicateSettlementPopulation / 100) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -467,7 +486,20 @@ export function finishMapOutput({
|
|||
castleRuins = attachIdsAndNames(tagInsidePrefecture(castleRuins, prefectureMask), "castleRuin", seed, null, nameFields, usedNames, nameDebug);
|
||||
externalGateways = attachIdsAndNames(tagInsidePrefecture(externalGateways, prefectureMask), "gateway", seed, "External Gateway", nameFields, usedNames, nameDebug);
|
||||
outputProgress("municipality naming");
|
||||
const adminCenters = attachIdsAndNames(tagInsidePrefecture(adminCentersRaw, humanRegionMask), "admin", seed, "Municipal Center", nameFields, usedNames, nameDebug);
|
||||
const municipalCoherence = reconcileMunicipalMetadata({
|
||||
adminId,
|
||||
prefectureRegionId,
|
||||
sea,
|
||||
adminCenters: adminCentersRaw,
|
||||
municipalityToPrefectureId,
|
||||
fields: nameFields,
|
||||
width: MAP_W,
|
||||
height: MAP_H,
|
||||
seed,
|
||||
});
|
||||
if (adminDebug) adminDebug.municipalCoherence = municipalCoherence.debug;
|
||||
const coherentMunicipalityToPrefectureId = municipalCoherence.municipalityToPrefectureId || municipalityToPrefectureId;
|
||||
const adminCenters = attachIdsAndNames(tagInsidePrefecture(municipalCoherence.adminCenters, humanRegionMask), "admin", seed, "Municipal Center", nameFields, usedNames, nameDebug);
|
||||
const representativeFeatures = [
|
||||
...modernCities.map((p) => ({ ...p, representativeWeight: 5.0 + (p.population || 0) / 180000 })),
|
||||
...markets.map((p) => ({ ...p, representativeWeight: 3.2 })),
|
||||
|
|
@ -515,25 +547,27 @@ export function finishMapOutput({
|
|||
}
|
||||
const usedAdminNames = new Set();
|
||||
for (const [index, center] of adminCenters.entries()) {
|
||||
center.adminNumericId = index;
|
||||
center.municipalityId = index;
|
||||
let candidate = municipalityNameFromRoot(center.municipalityRootName || center.canonicalSettlementName || center.generatedMunicipalityName || center.name, center, nameFields, seed, index);
|
||||
const generated = municipalityNameFromRoot(center.generatedMunicipalityName || center.name, center, nameFields, seed + 177, index);
|
||||
const municipalId = centerMunicipalityId(center, index);
|
||||
center.adminId = municipalId;
|
||||
center.adminNumericId = municipalId;
|
||||
center.municipalityId = municipalId;
|
||||
let candidate = municipalityNameFromRoot(center.municipalityRootName || center.canonicalSettlementName || center.generatedMunicipalityName || center.name, center, nameFields, seed, municipalId);
|
||||
const generated = municipalityNameFromRoot(center.generatedMunicipalityName || center.name, center, nameFields, seed + 177, municipalId);
|
||||
if (usedAdminNames.has(candidate) && Array.from(generated).length >= 2 && !usedAdminNames.has(generated)) {
|
||||
candidate = generated;
|
||||
}
|
||||
if (usedAdminNames.has(candidate)) {
|
||||
const suffix = municipalitySuffixForCenter(center, nameFields, seed + 313, index);
|
||||
const rootSource = String(center.generatedMunicipalityName || center.name || center.municipalityRootName || `自治${index + 1}`).replace(/[市町村区駅港城跡宿]$/gu, "");
|
||||
const suffix = municipalitySuffixForCenter(center, nameFields, seed + 313, municipalId);
|
||||
const rootSource = String(center.generatedMunicipalityName || center.name || center.municipalityRootName || `自治${municipalId + 1}`).replace(/[市町村区駅港城跡宿]$/gu, "");
|
||||
const chars = Array.from(rootSource || "里郷");
|
||||
const alternates = [
|
||||
chars.slice(0, 2).join(""),
|
||||
chars.slice(-2).join(""),
|
||||
`${chars[0] || "里"}${["里", "郷", "野", "田", "川", "原", "浜", "浦"][(seed + index) % 8]}`,
|
||||
`${["東", "西", "南", "北", "上", "下", "中"][(seed + index) % 7]}${chars[0] || "里"}`,
|
||||
`${chars[0] || "里"}${["里", "郷", "野", "田", "川", "原", "浜", "浦"][(seed + municipalId) % 8]}`,
|
||||
`${["東", "西", "南", "北", "上", "下", "中"][(seed + municipalId) % 7]}${chars[0] || "里"}`,
|
||||
].filter((v) => Array.from(v).length >= 2);
|
||||
for (let attempt = 0; attempt < alternates.length + 12 && usedAdminNames.has(candidate); attempt++) {
|
||||
const root = attempt < alternates.length ? alternates[attempt] : `第${(index + attempt) % 10}`;
|
||||
const root = attempt < alternates.length ? alternates[attempt] : `第${(municipalId + attempt) % 10}`;
|
||||
candidate = `${root}${suffix}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -584,7 +618,7 @@ export function finishMapOutput({
|
|||
return bs - as;
|
||||
})
|
||||
.filter((p) => {
|
||||
const prefId = municipalityToPrefectureId?.[p.municipalityId] ?? -1;
|
||||
const prefId = coherentMunicipalityToPrefectureId?.[p.municipalityId] ?? -1;
|
||||
const used = perPrefectureQuota.get(prefId) || 0;
|
||||
if (used >= 18) return false;
|
||||
perPrefectureQuota.set(prefId, used + 1);
|
||||
|
|
@ -1233,7 +1267,7 @@ export function finishMapOutput({
|
|||
humanRegionMask,
|
||||
prefectureBorder: regionalPrefectureBorders.length ? regionalPrefectureBorders : prefectureBorder,
|
||||
prefectureRegionId,
|
||||
municipalityToPrefectureId,
|
||||
municipalityToPrefectureId: coherentMunicipalityToPrefectureId,
|
||||
prefectureRegions,
|
||||
regionalDebug,
|
||||
terrainDebug,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue