Enhance population counting accuracy and add comprehensive geoJSON exports

Population System Fixes:
- Fix province, culture, and religion population calculations to exclude rural population from cells without burgs
- Burgs now represent ALL population for their cells (both urban and rural components)
- Eliminate double-counting where cell populations were incorrectly added to burg populations

Export Enhancements:
- Add complete geoJSON export for burgs with all settlement properties
- Enhance routes geoJSON export to include type and feature metadata
- Add missing length and width properties to rivers geoJSON export
- Fix burg coordinate system to match CSV export format with xWorld/yWorld fields

UI Improvements:
- Add burgs export button to geoJSON export interface
- Fix vite module loading issue by adding type="module" to notes-editor.js script tag

Documentation:
- Create comprehensive QGIS Style Conversion guide with route types, burg features, and relief rendering methods
- Add WKT coordinate reference system definition for Fantasy Map Cartesian CRS
- Include rule-based styling examples and data processing workflows
This commit is contained in:
barrulus 2025-08-15 19:00:03 +01:00
parent 9e8bc6e689
commit 83573c8936
6 changed files with 695 additions and 13 deletions

View file

@ -113,10 +113,8 @@ function culturesCollectStatistics() {
if (burgId) {
// Burg represents ALL population for this cell (stored in thousands)
cultures[cultureId].urban += burgs[burgId].population;
} else {
// Only count cells.pop for unsettled areas (no burg present)
cultures[cultureId].rural += cells.pop[i];
}
// No population in cells without burgs - all population is in burgs
}
}

View file

@ -123,10 +123,8 @@ function religionsCollectStatistics() {
if (burgId) {
// Burg represents ALL population for this cell (stored in thousands)
religions[religionId].urban += burgs[burgId].population;
} else {
// Only count cells.pop for unsettled areas (no burg present)
religions[religionId].rural += cells.pop[i];
}
// No population in cells without burgs - all population is in burgs
}
}

View file

@ -652,12 +652,12 @@ function saveGeoJsonCells() {
function saveGeoJsonRoutes() {
const metersPerPixel = getMetersPerPixel();
const features = pack.routes.map(({i, points, group, name = null}) => {
const features = pack.routes.map(({i, points, group, name = null, type, feature}) => {
const coordinates = points.map(([x, y]) => getFantasyCoordinates(x, y, 2));
return {
type: "Feature",
geometry: {type: "LineString", coordinates},
properties: {id: i, group, name}
properties: {id: i, group, name, type, feature}
};
});
@ -682,14 +682,14 @@ function saveGeoJsonRoutes() {
function saveGeoJsonRivers() {
const metersPerPixel = getMetersPerPixel();
const features = pack.rivers.map(
({i, cells, points, source, mouth, parent, basin, widthFactor, sourceWidth, discharge, name, type}) => {
({i, cells, points, source, mouth, parent, basin, widthFactor, sourceWidth, discharge, length, width, name, type}) => {
if (!cells || cells.length < 2) return;
const meanderedPoints = Rivers.addMeandering(cells, points);
const coordinates = meanderedPoints.map(([x, y]) => getFantasyCoordinates(x, y, 2));
return {
type: "Feature",
geometry: {type: "LineString", coordinates},
properties: {id: i, source, mouth, parent, basin, widthFactor, sourceWidth, discharge, name, type}
properties: {id: i, source, mouth, parent, basin, widthFactor, sourceWidth, discharge, length, width, name, type}
};
}
).filter(f => f); // Remove undefined entries
@ -749,4 +749,69 @@ function saveGeoJsonMarkers() {
const fileName = getFileName("Markers") + ".geojson";
downloadFile(JSON.stringify(json), fileName, "application/json");
}
function saveGeoJsonBurgs() {
const metersPerPixel = getMetersPerPixel();
const valid = pack.burgs.filter(b => b.i && !b.removed);
const features = valid.map(b => {
const coordinates = getFantasyCoordinates(b.x, b.y, 2);
const province = pack.cells.province[b.cell];
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
// Calculate world coordinates same as CSV export
const xWorld = b.x * metersPerPixel;
const yWorld = -b.y * metersPerPixel;
return {
type: "Feature",
geometry: {type: "Point", coordinates},
properties: {
id: b.i,
name: b.name,
province: province ? pack.provinces[province].name : null,
provinceFull: province ? pack.provinces[province].fullName : null,
state: pack.states[b.state].name,
stateFull: pack.states[b.state].fullName,
culture: pack.cultures[b.culture].name,
religion: pack.religions[pack.cells.religion[b.cell]].name,
population: rn(b.population * populationRate * urbanization),
populationRaw: b.population,
xWorld: rn(xWorld, 2),
yWorld: rn(yWorld, 2),
xPixel: b.x,
yPixel: b.y,
elevation: parseInt(getHeight(pack.cells.h[b.cell])),
temperature: convertTemperature(temperature),
temperatureLikeness: getTemperatureLikeness(temperature),
capital: !!b.capital,
port: !!b.port,
citadel: !!b.citadel,
walls: !!b.walls,
plaza: !!b.plaza,
temple: !!b.temple,
shanty: !!b.shanty,
emblem: b.coa || null,
cell: b.cell
}
};
});
const json = {
type: "FeatureCollection",
features,
metadata: {
crs: "Fantasy Map Cartesian (meters)",
mapName: mapName.value,
scale: {
distance: distanceScale,
unit: distanceUnitInput.value,
meters_per_pixel: metersPerPixel
}
}
};
const fileName = getFileName("Burgs") + ".geojson";
downloadFile(JSON.stringify(json), fileName, "application/json");
}

View file

@ -91,10 +91,8 @@ function editProvinces() {
// Burg represents ALL population for this cell (stored in thousands)
provinces[p].urban += burgs[cells.burg[i]].population;
provinces[p].burgs.push(cells.burg[i]);
} else {
// Only count cells.pop for unsettled areas (no burg present)
provinces[p].rural += cells.pop[i];
}
// No population in cells without burgs - all population is in burgs
}
provinces.forEach(p => {