mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
qgis additions
This commit is contained in:
parent
fecbae826c
commit
20dfb7cfcb
17 changed files with 969 additions and 17 deletions
27
TODO.md
Normal file
27
TODO.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
## GeoJSON Exports (RFC 7946 compliance)
|
||||||
|
|
||||||
|
- Geometry in WGS84: Output `geometry.coordinates` as `[lon, lat]` (degrees). Do not include a top-level `crs` member (deprecated in RFC 7946).
|
||||||
|
- Move custom coords to properties: Keep fantasy/cartesian meters and pixel positions under `properties` (e.g., `fantasy_coordinates: [x_m, y_m]`, `x_px`, `y_px`, `meters_per_pixel`).
|
||||||
|
- Preserve fields: Continue exporting `id`, `type`, `name`, `icon` (where applicable), style fields (`size`, `fill`, `stroke`), and `note` (legend) if present.
|
||||||
|
- Update exporters: Apply to all GeoJSON exporters in `modules/io/export.js`:
|
||||||
|
- `saveGeoJsonMarkers`
|
||||||
|
- `saveGeoJsonRivers`
|
||||||
|
- `saveGeoJsonBurgs`
|
||||||
|
- `saveGeoJsonRoutes`
|
||||||
|
- `saveGeoJsonCells`
|
||||||
|
- `saveGeoJsonRegiments`
|
||||||
|
- Geometry specifics:
|
||||||
|
- Points (markers/burgs): `[lon, lat]` via `getLongitude(x)`, `getLatitude(y)`.
|
||||||
|
- Lines (rivers/routes): arrays of `[lon, lat]`; keep width/length and any fantasy metrics in `properties`.
|
||||||
|
- Polygons (cells): rings in `[lon, lat]`; move fantasy/cartesian vertices to `properties` if needed.
|
||||||
|
- Metadata: Keep projection info only as a custom field (e.g., `metadata.projection: "Fantasy Map Cartesian (meters)"`). Avoid reintroducing `crs`.
|
||||||
|
- Acceptance criteria:
|
||||||
|
- Files validate without CRS/projection warnings in common validators.
|
||||||
|
- QGIS/geojson.io load geometries correctly as WGS84.
|
||||||
|
- Internal consumers retain access to fantasy coords via `properties`.
|
||||||
|
- Backward compatibility: Consider a toggle to export in either WGS84 or fantasy-cartesian for users relying on previous behavior; otherwise bump export format version in `metadata`.
|
||||||
|
|
||||||
|
Note: `saveGeoJsonMarkers` now includes `name` (mirrors CSV). Ensure other exporters include analogous name fields where applicable.
|
||||||
|
|
||||||
1
Tourland Markers 2025-09-02-10-19.geojson
Normal file
1
Tourland Markers 2025-09-02-10-19.geojson
Normal file
File diff suppressed because one or more lines are too long
19
index.html
19
index.html
|
|
@ -6053,6 +6053,14 @@
|
||||||
<button onclick="saveGeoJsonMarkers()" data-tip="Download markers data in GeoJSON format">markers</button>
|
<button onclick="saveGeoJsonMarkers()" data-tip="Download markers data in GeoJSON format">markers</button>
|
||||||
<button onclick="saveGeoJsonBurgs()" data-tip="Download burgs data in GeoJSON format">burgs</button>
|
<button onclick="saveGeoJsonBurgs()" data-tip="Download burgs data in GeoJSON format">burgs</button>
|
||||||
<button onclick="saveGeoJsonRegiments()" data-tip="Download regiments data in GeoJSON format">regiments</button>
|
<button onclick="saveGeoJsonRegiments()" data-tip="Download regiments data in GeoJSON format">regiments</button>
|
||||||
|
<br />
|
||||||
|
<button onclick="saveGeoJsonStates()" data-tip="Download states in GeoJSON format">states</button>
|
||||||
|
<button onclick="saveGeoJsonProvinces()" data-tip="Download provinces in GeoJSON format">provinces</button>
|
||||||
|
<button onclick="saveGeoJsonCultures()" data-tip="Download cultures in GeoJSON format">cultures</button>
|
||||||
|
<button onclick="saveGeoJsonReligions()" data-tip="Download religions in GeoJSON format">religions</button>
|
||||||
|
<button onclick="saveGeoJsonZones()" data-tip="Download zones in GeoJSON format">zones</button>
|
||||||
|
<br />
|
||||||
|
<button onclick="saveAllGeoJson()" data-tip="Download all GeoJSON datasets as a ZIP">all (zip)</button>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
GeoJSON format is used in GIS tools such as QGIS. Check out
|
GeoJSON format is used in GIS tools such as QGIS. Check out
|
||||||
|
|
@ -6060,6 +6068,17 @@
|
||||||
for guidance.
|
for guidance.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div style="margin: 1em 0 0.3em; font-weight: bold">Export height raster (QGIS)</div>
|
||||||
|
<div>
|
||||||
|
<button onclick="saveAsciiGridHeightmap()" data-tip="Export heightmap as ESRI ASCII Grid (.asc) for QGIS">
|
||||||
|
height (.asc)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Load the .asc in QGIS as a raster layer. Use Raster → Extraction → Contour to make contour lines,
|
||||||
|
and Raster → Analysis → Hillshade for shaded relief. CRS: Fantasy Map Cartesian (meters).
|
||||||
|
</p>
|
||||||
|
|
||||||
<div style="margin: 1em 0 0.3em; font-weight: bold">Export To JSON</div>
|
<div style="margin: 1em 0 0.3em; font-weight: bold">Export To JSON</div>
|
||||||
<div>
|
<div>
|
||||||
<button onclick="exportToJson('Full')" data-tip="Download full data in JSON">full</button>
|
<button onclick="exportToJson('Full')" data-tip="Download full data in JSON">full</button>
|
||||||
|
|
|
||||||
|
|
@ -561,7 +561,7 @@ function getFantasyCoordinates(x, y, decimals = 2) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGeoJsonCells() {
|
function buildGeoJsonCells() {
|
||||||
const {cells, vertices} = pack;
|
const {cells, vertices} = pack;
|
||||||
|
|
||||||
// Calculate meters per pixel based on unit
|
// Calculate meters per pixel based on unit
|
||||||
|
|
@ -646,11 +646,16 @@ function saveGeoJsonCells() {
|
||||||
json.features.push(feature);
|
json.features.push(feature);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonCells() {
|
||||||
|
const json = buildGeoJsonCells();
|
||||||
const fileName = getFileName("Cells") + ".geojson";
|
const fileName = getFileName("Cells") + ".geojson";
|
||||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGeoJsonRoutes() {
|
function buildGeoJsonRoutes() {
|
||||||
const metersPerPixel = getMetersPerPixel();
|
const metersPerPixel = getMetersPerPixel();
|
||||||
const features = pack.routes.map(({i, points, group, name = null, type, feature}) => {
|
const features = pack.routes.map(({i, points, group, name = null, type, feature}) => {
|
||||||
const coordinates = points.map(([x, y]) => getFantasyCoordinates(x, y, 2));
|
const coordinates = points.map(([x, y]) => getFantasyCoordinates(x, y, 2));
|
||||||
|
|
@ -674,12 +679,16 @@ function saveGeoJsonRoutes() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonRoutes() {
|
||||||
|
const json = buildGeoJsonRoutes();
|
||||||
const fileName = getFileName("Routes") + ".geojson";
|
const fileName = getFileName("Routes") + ".geojson";
|
||||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGeoJsonRivers() {
|
function buildGeoJsonRivers() {
|
||||||
const metersPerPixel = getMetersPerPixel();
|
const metersPerPixel = getMetersPerPixel();
|
||||||
const features = pack.rivers.map(
|
const features = pack.rivers.map(
|
||||||
({i, cells, points, source, mouth, parent, basin, widthFactor, sourceWidth, discharge, length, width, name, type}) => {
|
({i, cells, points, source, mouth, parent, basin, widthFactor, sourceWidth, discharge, length, width, name, type}) => {
|
||||||
|
|
@ -707,21 +716,27 @@ function saveGeoJsonRivers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonRivers() {
|
||||||
|
const json = buildGeoJsonRivers();
|
||||||
const fileName = getFileName("Rivers") + ".geojson";
|
const fileName = getFileName("Rivers") + ".geojson";
|
||||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGeoJsonMarkers() {
|
function buildGeoJsonMarkers() {
|
||||||
const metersPerPixel = getMetersPerPixel();
|
const metersPerPixel = getMetersPerPixel();
|
||||||
const features = pack.markers.map(marker => {
|
const features = pack.markers.map(marker => {
|
||||||
const {i, type, icon, x, y, size, fill, stroke} = marker;
|
const {i, type, icon, x, y, size, fill, stroke} = marker;
|
||||||
const coordinates = getFantasyCoordinates(x, y, 2);
|
const coordinates = getFantasyCoordinates(x, y, 2);
|
||||||
// Find the associated note if it exists
|
// Find the associated note if it exists
|
||||||
const note = notes.find(note => note.id === `marker${i}`);
|
const note = notes.find(note => note.id === `marker${i}`);
|
||||||
|
const name = note ? note.name : "Unknown";
|
||||||
const properties = {
|
const properties = {
|
||||||
id: i,
|
id: i,
|
||||||
type,
|
type,
|
||||||
|
name,
|
||||||
icon,
|
icon,
|
||||||
x_px: x,
|
x_px: x,
|
||||||
y_px: y,
|
y_px: y,
|
||||||
|
|
@ -746,24 +761,28 @@ function saveGeoJsonMarkers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonMarkers() {
|
||||||
|
const json = buildGeoJsonMarkers();
|
||||||
const fileName = getFileName("Markers") + ".geojson";
|
const fileName = getFileName("Markers") + ".geojson";
|
||||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGeoJsonBurgs() {
|
function buildGeoJsonBurgs() {
|
||||||
const metersPerPixel = getMetersPerPixel();
|
const metersPerPixel = getMetersPerPixel();
|
||||||
const valid = pack.burgs.filter(b => b.i && !b.removed);
|
const valid = pack.burgs.filter(b => b.i && !b.removed);
|
||||||
|
|
||||||
const features = valid.map(b => {
|
const features = valid.map(b => {
|
||||||
const coordinates = getFantasyCoordinates(b.x, b.y, 2);
|
const coordinates = getFantasyCoordinates(b.x, b.y, 2);
|
||||||
const province = pack.cells.province[b.cell];
|
const province = pack.cells.province[b.cell];
|
||||||
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
|
const temperature = grid.cells.temp[pack.cells.g[b.cell]];
|
||||||
|
|
||||||
// Calculate world coordinates same as CSV export
|
// Calculate world coordinates same as CSV export
|
||||||
const xWorld = b.x * metersPerPixel;
|
const xWorld = b.x * metersPerPixel;
|
||||||
const yWorld = -b.y * metersPerPixel;
|
const yWorld = -b.y * metersPerPixel;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
geometry: {type: "Point", coordinates},
|
geometry: {type: "Point", coordinates},
|
||||||
|
|
@ -811,15 +830,19 @@ function saveGeoJsonBurgs() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonBurgs() {
|
||||||
|
const json = buildGeoJsonBurgs();
|
||||||
const fileName = getFileName("Burgs") + ".geojson";
|
const fileName = getFileName("Burgs") + ".geojson";
|
||||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGeoJsonRegiments() {
|
function buildGeoJsonRegiments() {
|
||||||
const metersPerPixel = getMetersPerPixel();
|
const metersPerPixel = getMetersPerPixel();
|
||||||
const allRegiments = [];
|
const allRegiments = [];
|
||||||
|
|
||||||
// Collect all regiments from all states
|
// Collect all regiments from all states
|
||||||
for (const s of pack.states) {
|
for (const s of pack.states) {
|
||||||
if (!s.i || s.removed || !s.military.length) continue;
|
if (!s.i || s.removed || !s.military.length) continue;
|
||||||
|
|
@ -827,23 +850,23 @@ function saveGeoJsonRegiments() {
|
||||||
allRegiments.push({regiment: r, state: s});
|
allRegiments.push({regiment: r, state: s});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const features = allRegiments.map(({regiment: r, state: s}) => {
|
const features = allRegiments.map(({regiment: r, state: s}) => {
|
||||||
const coordinates = getFantasyCoordinates(r.x, r.y, 2);
|
const coordinates = getFantasyCoordinates(r.x, r.y, 2);
|
||||||
const baseCoordinates = getFantasyCoordinates(r.bx, r.by, 2);
|
const baseCoordinates = getFantasyCoordinates(r.bx, r.by, 2);
|
||||||
|
|
||||||
// Calculate world coordinates same as CSV export
|
// Calculate world coordinates same as CSV export
|
||||||
const xWorld = r.x * metersPerPixel;
|
const xWorld = r.x * metersPerPixel;
|
||||||
const yWorld = -r.y * metersPerPixel;
|
const yWorld = -r.y * metersPerPixel;
|
||||||
const bxWorld = r.bx * metersPerPixel;
|
const bxWorld = r.bx * metersPerPixel;
|
||||||
const byWorld = -r.by * metersPerPixel;
|
const byWorld = -r.by * metersPerPixel;
|
||||||
|
|
||||||
// Collect military unit data
|
// Collect military unit data
|
||||||
const units = {};
|
const units = {};
|
||||||
options.military.forEach(u => {
|
options.military.forEach(u => {
|
||||||
units[u.name] = r.u[u.name] || 0;
|
units[u.name] = r.u[u.name] || 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
geometry: {type: "Point", coordinates},
|
geometry: {type: "Point", coordinates},
|
||||||
|
|
@ -885,7 +908,401 @@ function saveGeoJsonRegiments() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonRegiments() {
|
||||||
|
const json = buildGeoJsonRegiments();
|
||||||
const fileName = getFileName("Regiments") + ".geojson";
|
const fileName = getFileName("Regiments") + ".geojson";
|
||||||
downloadFile(JSON.stringify(json), fileName, "application/json");
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export heightmap as ESRI ASCII Grid (.asc) for QGIS
|
||||||
|
function saveAsciiGridHeightmap() {
|
||||||
|
if (!grid?.cells?.h || !grid.cellsX || !grid.cellsY) {
|
||||||
|
tip("Height grid is not available", false, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ncols = grid.cellsX;
|
||||||
|
const nrows = grid.cellsY;
|
||||||
|
const metersPerPixel = getMetersPerPixel();
|
||||||
|
const cellsize = (graphWidth / ncols) * metersPerPixel; // meters per grid cell
|
||||||
|
|
||||||
|
// Lower-left origin in world meters matches other exports
|
||||||
|
const xllcorner = 0;
|
||||||
|
const yllcorner = -(graphHeight * metersPerPixel);
|
||||||
|
const NODATA = -9999;
|
||||||
|
|
||||||
|
// Convert FMG height (0..100, 20 sea level) to meters (signed)
|
||||||
|
const exp = +heightExponentInput.value;
|
||||||
|
function elevationInMeters(h) {
|
||||||
|
if (h >= 20) return Math.pow(h - 18, exp); // above sea level
|
||||||
|
if (h > 0) return ((h - 20) / h) * 50; // below sea level (negative)
|
||||||
|
return 0; // treat 0 as 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let lines = [];
|
||||||
|
lines.push(`ncols ${ncols}`);
|
||||||
|
lines.push(`nrows ${nrows}`);
|
||||||
|
lines.push(`xllcorner ${xllcorner}`);
|
||||||
|
lines.push(`yllcorner ${yllcorner}`);
|
||||||
|
lines.push(`cellsize ${cellsize}`);
|
||||||
|
lines.push(`NODATA_value ${NODATA}`);
|
||||||
|
|
||||||
|
// ESRI ASCII expects rows from top (north) to bottom (south)
|
||||||
|
for (let row = 0; row < nrows; row++) {
|
||||||
|
const vals = new Array(ncols);
|
||||||
|
for (let col = 0; col < ncols; col++) {
|
||||||
|
const i = col + row * ncols;
|
||||||
|
const h = grid.cells.h[i];
|
||||||
|
const z = elevationInMeters(h);
|
||||||
|
vals[col] = Number.isFinite(z) ? rn(z, 2) : NODATA;
|
||||||
|
}
|
||||||
|
lines.push(vals.join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = lines.join("\n");
|
||||||
|
const fileName = getFileName("Heightmap") + ".asc";
|
||||||
|
downloadFile(content, fileName, "text/plain");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers to build MultiPolygons from cell sets
|
||||||
|
function getCellPolygonCoordinates(cellVertices) {
|
||||||
|
const {vertices} = pack;
|
||||||
|
const coordinates = cellVertices.map(vertex => {
|
||||||
|
const [x, y] = vertices.p[vertex];
|
||||||
|
return getFantasyCoordinates(x, y, 2);
|
||||||
|
});
|
||||||
|
// Close the ring
|
||||||
|
return [[...coordinates, coordinates[0]]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMultiPolygonFromCells(cellIds) {
|
||||||
|
const {cells} = pack;
|
||||||
|
const polygons = cellIds.map(i => getCellPolygonCoordinates(cells.v[i]));
|
||||||
|
// polygons is an array of [ [ ring ] ] — wrap for MultiPolygon
|
||||||
|
return polygons;
|
||||||
|
}
|
||||||
|
|
||||||
|
function aggregatePopulationByCells(cellIds) {
|
||||||
|
// Follow editor logic: population lives in burgs; rural is accounted for via small burgs only
|
||||||
|
// Return values in absolute people, matching CSV exports
|
||||||
|
let ruralK = 0; // thousands-equivalent for rural (as tracked in states)
|
||||||
|
let urbanK = 0; // thousands for urban from burgs
|
||||||
|
for (const i of cellIds) {
|
||||||
|
const burgId = pack.cells.burg[i];
|
||||||
|
if (!burgId) continue;
|
||||||
|
const k = pack.burgs[burgId].population; // in thousands
|
||||||
|
// Mirror states stats split: <= 0.1k as rural, otherwise urban
|
||||||
|
if (k > 0.1) urbanK += k; else ruralK += k;
|
||||||
|
}
|
||||||
|
const rural = Math.round(ruralK * populationRate);
|
||||||
|
const urban = Math.round(urbanK * 1000 * urbanization);
|
||||||
|
return {rural, urban, total: rural + urban};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sumAreaByCells(cellIds) {
|
||||||
|
const sum = cellIds.reduce((acc, i) => acc + (pack.cells.area[i] || 0), 0);
|
||||||
|
return getArea(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCellsFor(type, id) {
|
||||||
|
const {cells} = pack;
|
||||||
|
switch (type) {
|
||||||
|
case "state":
|
||||||
|
return cells.i.filter(i => cells.h[i] >= 20 && cells.state[i] === id);
|
||||||
|
case "province":
|
||||||
|
return cells.i.filter(i => cells.h[i] >= 20 && cells.province[i] === id);
|
||||||
|
case "culture":
|
||||||
|
return cells.i.filter(i => cells.h[i] >= 20 && cells.culture[i] === id);
|
||||||
|
case "religion":
|
||||||
|
return cells.i.filter(i => cells.h[i] >= 20 && cells.religion[i] === id);
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGeoJsonCultures() {
|
||||||
|
const metersPerPixel = getMetersPerPixel();
|
||||||
|
const features = pack.cultures
|
||||||
|
.filter(c => c.i && !c.removed)
|
||||||
|
.map(c => {
|
||||||
|
const cellIds = getCellsFor("culture", c.i);
|
||||||
|
if (!cellIds.length) return null;
|
||||||
|
const geometry = {type: "MultiPolygon", coordinates: buildMultiPolygonFromCells(cellIds)};
|
||||||
|
const {total} = aggregatePopulationByCells(cellIds);
|
||||||
|
const area = sumAreaByCells(cellIds);
|
||||||
|
const namesbase = nameBases[c.base]?.name;
|
||||||
|
const origins = (c.origins || []).filter(o => o).map(o => pack.cultures[o]?.name).filter(Boolean);
|
||||||
|
const properties = {
|
||||||
|
id: c.i,
|
||||||
|
name: c.name,
|
||||||
|
color: c.color,
|
||||||
|
cells: cellIds.length,
|
||||||
|
expansionism: c.expansionism,
|
||||||
|
type: c.type,
|
||||||
|
area,
|
||||||
|
population: rn(total),
|
||||||
|
namesbase: namesbase || "",
|
||||||
|
emblemsShape: c.emblemsShape || "",
|
||||||
|
origins
|
||||||
|
};
|
||||||
|
return {type: "Feature", geometry, properties};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const json = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features,
|
||||||
|
metadata: {
|
||||||
|
crs: "Fantasy Map Cartesian (meters)",
|
||||||
|
mapName: mapName.value,
|
||||||
|
scale: {
|
||||||
|
distance: distanceScale,
|
||||||
|
unit: distanceUnitInput.value,
|
||||||
|
meters_per_pixel: metersPerPixel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonCultures() {
|
||||||
|
const json = buildGeoJsonCultures();
|
||||||
|
const fileName = getFileName("Cultures") + ".geojson";
|
||||||
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGeoJsonReligions() {
|
||||||
|
const metersPerPixel = getMetersPerPixel();
|
||||||
|
const features = pack.religions
|
||||||
|
.filter(r => r.i && !r.removed)
|
||||||
|
.map(r => {
|
||||||
|
const cellIds = getCellsFor("religion", r.i);
|
||||||
|
if (!cellIds.length) return null;
|
||||||
|
const geometry = {type: "MultiPolygon", coordinates: buildMultiPolygonFromCells(cellIds)};
|
||||||
|
const {total} = aggregatePopulationByCells(cellIds);
|
||||||
|
const area = sumAreaByCells(cellIds);
|
||||||
|
const origins = (r.origins || []).filter(o => o).map(o => pack.religions[o]?.name).filter(Boolean);
|
||||||
|
const properties = {
|
||||||
|
id: r.i,
|
||||||
|
name: r.name,
|
||||||
|
color: r.color,
|
||||||
|
type: r.type,
|
||||||
|
form: r.form,
|
||||||
|
deity: r.deity || "",
|
||||||
|
area,
|
||||||
|
believers: rn(total),
|
||||||
|
origins,
|
||||||
|
potential: r.expansion,
|
||||||
|
expansionism: r.expansionism
|
||||||
|
};
|
||||||
|
return {type: "Feature", geometry, properties};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const json = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features,
|
||||||
|
metadata: {
|
||||||
|
crs: "Fantasy Map Cartesian (meters)",
|
||||||
|
mapName: mapName.value,
|
||||||
|
scale: {
|
||||||
|
distance: distanceScale,
|
||||||
|
unit: distanceUnitInput.value,
|
||||||
|
meters_per_pixel: metersPerPixel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonReligions() {
|
||||||
|
const json = buildGeoJsonReligions();
|
||||||
|
const fileName = getFileName("Religions") + ".geojson";
|
||||||
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGeoJsonStates() {
|
||||||
|
const metersPerPixel = getMetersPerPixel();
|
||||||
|
const features = pack.states
|
||||||
|
.filter(s => s.i && !s.removed)
|
||||||
|
.map(s => {
|
||||||
|
const cellIds = getCellsFor("state", s.i);
|
||||||
|
if (!cellIds.length) return null;
|
||||||
|
const geometry = {type: "MultiPolygon", coordinates: buildMultiPolygonFromCells(cellIds)};
|
||||||
|
const {rural, urban, total} = aggregatePopulationByCells(cellIds);
|
||||||
|
const area = sumAreaByCells(cellIds);
|
||||||
|
const properties = {
|
||||||
|
id: s.i,
|
||||||
|
name: s.name,
|
||||||
|
fullName: s.fullName || "",
|
||||||
|
form: s.form || "",
|
||||||
|
color: s.color,
|
||||||
|
capital: s.capital || 0,
|
||||||
|
culture: s.culture,
|
||||||
|
type: s.type,
|
||||||
|
expansionism: s.expansionism,
|
||||||
|
cells: cellIds.length,
|
||||||
|
burgs: s.burgs || 0,
|
||||||
|
area,
|
||||||
|
totalPopulation: total,
|
||||||
|
ruralPopulation: rural,
|
||||||
|
urbanPopulation: urban
|
||||||
|
};
|
||||||
|
return {type: "Feature", geometry, properties};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const json = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features,
|
||||||
|
metadata: {
|
||||||
|
crs: "Fantasy Map Cartesian (meters)",
|
||||||
|
mapName: mapName.value,
|
||||||
|
scale: {
|
||||||
|
distance: distanceScale,
|
||||||
|
unit: distanceUnitInput.value,
|
||||||
|
meters_per_pixel: metersPerPixel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonStates() {
|
||||||
|
const json = buildGeoJsonStates();
|
||||||
|
const fileName = getFileName("States") + ".geojson";
|
||||||
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGeoJsonProvinces() {
|
||||||
|
const metersPerPixel = getMetersPerPixel();
|
||||||
|
const features = pack.provinces
|
||||||
|
.filter(p => p.i && !p.removed)
|
||||||
|
.map(p => {
|
||||||
|
const cellIds = getCellsFor("province", p.i);
|
||||||
|
if (!cellIds.length) return null;
|
||||||
|
const geometry = {type: "MultiPolygon", coordinates: buildMultiPolygonFromCells(cellIds)};
|
||||||
|
const {rural, urban, total} = aggregatePopulationByCells(cellIds);
|
||||||
|
const area = sumAreaByCells(cellIds);
|
||||||
|
const properties = {
|
||||||
|
id: p.i,
|
||||||
|
name: p.name,
|
||||||
|
fullName: p.fullName || "",
|
||||||
|
form: p.form || "",
|
||||||
|
state: p.state,
|
||||||
|
color: p.color,
|
||||||
|
capital: p.burg || 0,
|
||||||
|
area,
|
||||||
|
totalPopulation: total,
|
||||||
|
ruralPopulation: rural,
|
||||||
|
urbanPopulation: urban,
|
||||||
|
burgs: (p.burgs && p.burgs.length) || 0
|
||||||
|
};
|
||||||
|
return {type: "Feature", geometry, properties};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const json = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features,
|
||||||
|
metadata: {
|
||||||
|
crs: "Fantasy Map Cartesian (meters)",
|
||||||
|
mapName: mapName.value,
|
||||||
|
scale: {
|
||||||
|
distance: distanceScale,
|
||||||
|
unit: distanceUnitInput.value,
|
||||||
|
meters_per_pixel: metersPerPixel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonProvinces() {
|
||||||
|
const json = buildGeoJsonProvinces();
|
||||||
|
const fileName = getFileName("Provinces") + ".geojson";
|
||||||
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGeoJsonZones() {
|
||||||
|
const metersPerPixel = getMetersPerPixel();
|
||||||
|
const features = (pack.zones || [])
|
||||||
|
.map(z => {
|
||||||
|
if (!z || z.hidden) return null;
|
||||||
|
const cellIds = (z.cells || []).filter(i => pack.cells.h[i] >= 20);
|
||||||
|
if (!cellIds.length) return null;
|
||||||
|
const geometry = {type: "MultiPolygon", coordinates: buildMultiPolygonFromCells(cellIds)};
|
||||||
|
const {total} = aggregatePopulationByCells(cellIds);
|
||||||
|
const area = sumAreaByCells(cellIds);
|
||||||
|
const properties = {
|
||||||
|
id: z.i,
|
||||||
|
color: z.color,
|
||||||
|
description: z.name,
|
||||||
|
type: z.type,
|
||||||
|
cells: cellIds.length,
|
||||||
|
area,
|
||||||
|
population: rn(total)
|
||||||
|
};
|
||||||
|
return {type: "Feature", geometry, properties};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const json = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features,
|
||||||
|
metadata: {
|
||||||
|
crs: "Fantasy Map Cartesian (meters)",
|
||||||
|
mapName: mapName.value,
|
||||||
|
scale: {
|
||||||
|
distance: distanceScale,
|
||||||
|
unit: distanceUnitInput.value,
|
||||||
|
meters_per_pixel: metersPerPixel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGeoJsonZones() {
|
||||||
|
const json = buildGeoJsonZones();
|
||||||
|
const fileName = getFileName("Zones") + ".geojson";
|
||||||
|
downloadFile(JSON.stringify(json), fileName, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience: export all GeoJSONs into a single ZIP
|
||||||
|
async function saveAllGeoJson() {
|
||||||
|
await import("../../libs/jszip.min.js");
|
||||||
|
const zip = new window.JSZip();
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
{name: getFileName("Cells") + ".geojson", json: buildGeoJsonCells()},
|
||||||
|
{name: getFileName("Routes") + ".geojson", json: buildGeoJsonRoutes()},
|
||||||
|
{name: getFileName("Rivers") + ".geojson", json: buildGeoJsonRivers()},
|
||||||
|
{name: getFileName("Markers") + ".geojson", json: buildGeoJsonMarkers()},
|
||||||
|
{name: getFileName("Burgs") + ".geojson", json: buildGeoJsonBurgs()},
|
||||||
|
{name: getFileName("Regiments") + ".geojson", json: buildGeoJsonRegiments()},
|
||||||
|
{name: getFileName("States") + ".geojson", json: buildGeoJsonStates()},
|
||||||
|
{name: getFileName("Provinces") + ".geojson", json: buildGeoJsonProvinces()},
|
||||||
|
{name: getFileName("Cultures") + ".geojson", json: buildGeoJsonCultures()},
|
||||||
|
{name: getFileName("Religions") + ".geojson", json: buildGeoJsonReligions()},
|
||||||
|
{name: getFileName("Zones") + ".geojson", json: buildGeoJsonZones()}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const f of files) {
|
||||||
|
try {
|
||||||
|
zip.file(f.name, JSON.stringify(f.json));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to add", f.name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await zip.generateAsync({type: "blob"});
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = getFileName("GeoJSON") + ".zip";
|
||||||
|
link.click();
|
||||||
|
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||||
|
}
|
||||||
|
|
|
||||||
39
qgis/README.md
Normal file
39
qgis/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# QGIS Styles for Fantasy Map GeoJSON
|
||||||
|
|
||||||
|
This folder contains ready-to-use QGIS (.qml) styles that match the Fantasy Map Generator exports.
|
||||||
|
|
||||||
|
How to use
|
||||||
|
- Import each GeoJSON into QGIS.
|
||||||
|
- Right‑click the layer → Properties → Symbology → Style → Load Style… → pick the matching .qml from `qgis/styles`.
|
||||||
|
- Set layer CRS to the custom Fantasy Map Cartesian CRS:
|
||||||
|
|
||||||
|
```
|
||||||
|
ENGCRS["Fantasy Map Cartesian (meters)",
|
||||||
|
EDATUM["Fantasy Map Datum"],
|
||||||
|
CS[Cartesian,2],
|
||||||
|
AXIS["easting (X)",east,
|
||||||
|
ORDER[1],
|
||||||
|
LENGTHUNIT["metre",1]],
|
||||||
|
AXIS["northing (Y)",north,
|
||||||
|
ORDER[2],
|
||||||
|
LENGTHUNIT["metre",1]]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Included styles
|
||||||
|
- cells.qml: Graduated fill by `height` (water → mountains).
|
||||||
|
- rivers.qml: Blue lines, width driven by `width` attribute.
|
||||||
|
- routes.qml: Rule-based by `type`/`group` (sea routes dashed blue; roads brown; trails dashed, etc.).
|
||||||
|
- markers.qml: Simple point symbols, categorized by `type` where present.
|
||||||
|
- burgs.qml: Rule-based (capitals, ports, fortified, towns).
|
||||||
|
- regiments.qml: Square markers with label = `totalUnits`.
|
||||||
|
- states.qml: Polygon fill color from `color` attribute, labeled with `name`.
|
||||||
|
- provinces.qml: Polygon fill color from `color`, labeled with `name`.
|
||||||
|
- cultures.qml: Polygon fill color from `color`, labeled with `name`.
|
||||||
|
- religions.qml: Polygon fill color from `color`, labeled with `name`.
|
||||||
|
- zones.qml: Polygon fill color from `color`, labeled with `description`.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- Color fields for polygons use data-defined overrides; make sure your exported GeoJSON includes a `color` property (added by the new exporters).
|
||||||
|
- You can tweak line widths and colors per project scale.
|
||||||
|
- For cells, you can switch to a categorized style by `biome` if you prefer; this style uses elevation for a generic land scheme.
|
||||||
|
- For `markers.qml` font icons: ensure an emoji-capable font is installed and available to QGIS (e.g., `Noto Color Emoji` on Linux, `Segoe UI Emoji` on Windows, `Apple Color Emoji` on macOS). The style binds the Font Marker’s character directly to the `icon` attribute; the `icon` field should contain the desired glyph (e.g., 🏰, ⛏️). Some QGIS/Qt builds may render emoji as monochrome.
|
||||||
45
qgis/styles/burgs.qml
Normal file
45
qgis/styles/burgs.qml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="RuleRenderer">
|
||||||
|
<rules>
|
||||||
|
<rule filter="\"capital\" = 1" symbol="0" label="Capital"/>
|
||||||
|
<rule filter="\"port\" = 1" symbol="1" label="Port town"/>
|
||||||
|
<rule filter="\"citadel\" = 1 OR \"walls\" = 1" symbol="2" label="Fortified"/>
|
||||||
|
<rule filter="ELSE" symbol="3" label="Town"/>
|
||||||
|
</rules>
|
||||||
|
<symbols>
|
||||||
|
<symbol type="marker" name="0">
|
||||||
|
<layer class="SimpleMarker">
|
||||||
|
<prop k="name" v="circle"/>
|
||||||
|
<prop k="color" v="30,30,30,255"/>
|
||||||
|
<prop k="outline_color" v="255,255,255,255"/>
|
||||||
|
<prop k="size" v="3"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="marker" name="1">
|
||||||
|
<layer class="SimpleMarker">
|
||||||
|
<prop k="name" v="circle"/>
|
||||||
|
<prop k="color" v="30,30,30,255"/>
|
||||||
|
<prop k="outline_color" v="0,137,202,255"/>
|
||||||
|
<prop k="size" v="2.5"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="marker" name="2">
|
||||||
|
<layer class="SimpleMarker">
|
||||||
|
<prop k="name" v="square"/>
|
||||||
|
<prop k="color" v="30,30,30,255"/>
|
||||||
|
<prop k="outline_color" v="140,90,50,255"/>
|
||||||
|
<prop k="size" v="2.3"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="marker" name="3">
|
||||||
|
<layer class="SimpleMarker">
|
||||||
|
<prop k="name" v="circle"/>
|
||||||
|
<prop k="color" v="30,30,30,220"/>
|
||||||
|
<prop k="outline_color" v="255,255,255,180"/>
|
||||||
|
<prop k="size" v="2"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
</qgis>
|
||||||
|
|
||||||
49
qgis/styles/cells.qml
Normal file
49
qgis/styles/cells.qml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="graduatedSymbol" attr="height" graduatedMethod="GraduatedColor" symbollevels="0">
|
||||||
|
<ranges>
|
||||||
|
<range symbol="0" lower="0" upper="20" label="Water (h < 20)"/>
|
||||||
|
<range symbol="1" lower="20" upper="40" label="Lowlands (20-40)"/>
|
||||||
|
<range symbol="2" lower="40" upper="60" label="Hills (40-60)"/>
|
||||||
|
<range symbol="3" lower="60" upper="80" label="Highlands (60-80)"/>
|
||||||
|
<range symbol="4" lower="80" upper="200" label="Mountains (80+)"/>
|
||||||
|
</ranges>
|
||||||
|
<symbols>
|
||||||
|
<symbol type="fill" name="0">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="180,210,243,255"/>
|
||||||
|
<prop k="outline_color" v="120,120,120,100"/>
|
||||||
|
<prop k="outline_width" v="0.1"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="fill" name="1">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="196,230,188,255"/>
|
||||||
|
<prop k="outline_color" v="120,120,120,60"/>
|
||||||
|
<prop k="outline_width" v="0.1"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="fill" name="2">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="161,207,148,255"/>
|
||||||
|
<prop k="outline_color" v="120,120,120,60"/>
|
||||||
|
<prop k="outline_width" v="0.1"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="fill" name="3">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="196,183,151,255"/>
|
||||||
|
<prop k="outline_color" v="120,120,120,80"/>
|
||||||
|
<prop k="outline_width" v="0.1"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="fill" name="4">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="180,170,160,255"/>
|
||||||
|
<prop k="outline_color" v="120,120,120,120"/>
|
||||||
|
<prop k="outline_width" v="0.1"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
</qgis>
|
||||||
|
|
||||||
36
qgis/styles/cultures.qml
Normal file
36
qgis/styles/cultures.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="singleSymbol">
|
||||||
|
<symbols>
|
||||||
|
<symbol type="fill" name="0">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="220,220,220,160"/>
|
||||||
|
<prop k="outline_color" v="60,60,60,180"/>
|
||||||
|
<prop k="outline_width" v="0.4"/>
|
||||||
|
<data_defined_properties>
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="properties" type="Map">
|
||||||
|
<Option name="fillColor" type="Map">
|
||||||
|
<Option name="active" type="bool" value="true"/>
|
||||||
|
<Option name="expression" type="QString" value="attribute('color')"/>
|
||||||
|
<Option name="type" type="int" value="3"/>
|
||||||
|
</Option>
|
||||||
|
</Option>
|
||||||
|
<Option name="type" type="int" value="2"/>
|
||||||
|
</Option>
|
||||||
|
</data_defined_properties>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
<labeling type="rule-based">
|
||||||
|
<rules>
|
||||||
|
<rule>
|
||||||
|
<settings>
|
||||||
|
<text-style field="name" fontSize="9" namedStyle="Normal" isExpression="0"/>
|
||||||
|
<text-buffer bufferDraw="1" bufferColor="255,255,255,255" bufferSize="1"/>
|
||||||
|
</settings>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</labeling>
|
||||||
|
</qgis>
|
||||||
|
|
||||||
56
qgis/styles/markers.qml
Normal file
56
qgis/styles/markers.qml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="RuleRenderer">
|
||||||
|
<rules>
|
||||||
|
<rule filter="regexp_match("icon", '^https?://|^data:')" symbol="0" label="Image icon"/>
|
||||||
|
<rule filter="NOT regexp_match("icon", '^https?://|^data:')" symbol="1" label="Emoji/character icon"/>
|
||||||
|
</rules>
|
||||||
|
<symbols>
|
||||||
|
<!-- Image-based icons (URL or data URI). QGIS will try to load the image path from the 'icon' attribute. -->
|
||||||
|
<symbol type="marker" name="0">
|
||||||
|
<layer class="RasterImageMarker">
|
||||||
|
<prop k="size" v="3"/>
|
||||||
|
<data_defined_properties>
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="properties" type="Map">
|
||||||
|
<Option name="imageFile" type="Map">
|
||||||
|
<Option name="active" type="bool" value="true"/>
|
||||||
|
<Option name="expression" type="QString" value="attribute('icon')"/>
|
||||||
|
<Option name="type" type="int" value="3"/>
|
||||||
|
</Option>
|
||||||
|
<Option name="size" type="Map">
|
||||||
|
<Option name="active" type="bool" value="true"/>
|
||||||
|
<Option name="expression" type="QString" value="coalesce(attribute('size'), 3)"/>
|
||||||
|
<Option name="type" type="int" value="3"/>
|
||||||
|
</Option>
|
||||||
|
</Option>
|
||||||
|
<Option name="type" type="int" value="2"/>
|
||||||
|
</Option>
|
||||||
|
</data_defined_properties>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
|
||||||
|
<!-- Emoji/character-based icons: use label rendering for full emoji support.
|
||||||
|
Keep symbol invisible to avoid double-drawing; labels will show the emoji. -->
|
||||||
|
<symbol type="marker" name="1">
|
||||||
|
<layer class="SimpleMarker">
|
||||||
|
<prop k="name" v="circle"/>
|
||||||
|
<prop k="size" v="0"/>
|
||||||
|
<prop k="color" v="0,0,0,0"/>
|
||||||
|
<prop k="outline_color" v="0,0,0,0"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
<!-- Rule-based labels to render emojis from the 'icon' attribute.
|
||||||
|
This path supports multi-codepoint and non-BMP emoji (e.g., 💧). -->
|
||||||
|
<labeling type="rule-based">
|
||||||
|
<rules>
|
||||||
|
<rule filter="NOT regexp_match("icon", '^https?://|^data:')">
|
||||||
|
<settings>
|
||||||
|
<text-style field="icon" fontFamily="Noto Color Emoji" fontSize="9" isExpression="0"/>
|
||||||
|
<text-buffer bufferDraw="0"/>
|
||||||
|
</settings>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</labeling>
|
||||||
|
</qgis>
|
||||||
36
qgis/styles/provinces.qml
Normal file
36
qgis/styles/provinces.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="singleSymbol">
|
||||||
|
<symbols>
|
||||||
|
<symbol type="fill" name="0">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="230,230,230,120"/>
|
||||||
|
<prop k="outline_color" v="80,80,80,200"/>
|
||||||
|
<prop k="outline_width" v="0.4"/>
|
||||||
|
<data_defined_properties>
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="properties" type="Map">
|
||||||
|
<Option name="fillColor" type="Map">
|
||||||
|
<Option name="active" type="bool" value="true"/>
|
||||||
|
<Option name="expression" type="QString" value="attribute('color')"/>
|
||||||
|
<Option name="type" type="int" value="3"/>
|
||||||
|
</Option>
|
||||||
|
</Option>
|
||||||
|
<Option name="type" type="int" value="2"/>
|
||||||
|
</Option>
|
||||||
|
</data_defined_properties>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
<labeling type="rule-based">
|
||||||
|
<rules>
|
||||||
|
<rule>
|
||||||
|
<settings>
|
||||||
|
<text-style field="name" fontSize="8" namedStyle="Normal" isExpression="0"/>
|
||||||
|
<text-buffer bufferDraw="1" bufferColor="255,255,255,255" bufferSize="1"/>
|
||||||
|
</settings>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</labeling>
|
||||||
|
</qgis>
|
||||||
|
|
||||||
25
qgis/styles/regiments.qml
Normal file
25
qgis/styles/regiments.qml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="singleSymbol">
|
||||||
|
<symbols>
|
||||||
|
<symbol type="marker" name="0">
|
||||||
|
<layer class="SimpleMarker">
|
||||||
|
<prop k="name" v="square"/>
|
||||||
|
<prop k="color" v="200,0,0,220"/>
|
||||||
|
<prop k="outline_color" v="0,0,0,255"/>
|
||||||
|
<prop k="size" v="3"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
<labeling type="rule-based">
|
||||||
|
<rules>
|
||||||
|
<rule>
|
||||||
|
<settings>
|
||||||
|
<text-style field="totalUnits" fontSize="8" namedStyle="Normal" isExpression="0"/>
|
||||||
|
<text-buffer bufferDraw="1" bufferColor="255,255,255,255" bufferSize="1"/>
|
||||||
|
</settings>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</labeling>
|
||||||
|
</qgis>
|
||||||
|
|
||||||
36
qgis/styles/religions.qml
Normal file
36
qgis/styles/religions.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="singleSymbol">
|
||||||
|
<symbols>
|
||||||
|
<symbol type="fill" name="0">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="220,220,220,140"/>
|
||||||
|
<prop k="outline_color" v="60,60,60,200"/>
|
||||||
|
<prop k="outline_width" v="0.3"/>
|
||||||
|
<data_defined_properties>
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="properties" type="Map">
|
||||||
|
<Option name="fillColor" type="Map">
|
||||||
|
<Option name="active" type="bool" value="true"/>
|
||||||
|
<Option name="expression" type="QString" value="attribute('color')"/>
|
||||||
|
<Option name="type" type="int" value="3"/>
|
||||||
|
</Option>
|
||||||
|
</Option>
|
||||||
|
<Option name="type" type="int" value="2"/>
|
||||||
|
</Option>
|
||||||
|
</data_defined_properties>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
<labeling type="rule-based">
|
||||||
|
<rules>
|
||||||
|
<rule>
|
||||||
|
<settings>
|
||||||
|
<text-style field="name" fontSize="9" namedStyle="Normal" isExpression="0"/>
|
||||||
|
<text-buffer bufferDraw="1" bufferColor="255,255,255,255" bufferSize="1"/>
|
||||||
|
</settings>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</labeling>
|
||||||
|
</qgis>
|
||||||
|
|
||||||
26
qgis/styles/rivers.qml
Normal file
26
qgis/styles/rivers.qml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="singleSymbol">
|
||||||
|
<symbols>
|
||||||
|
<symbol type="line" name="0">
|
||||||
|
<layer class="SimpleLine">
|
||||||
|
<prop k="line_color" v="0,137,202,255"/>
|
||||||
|
<prop k="line_width" v="0.6"/>
|
||||||
|
<prop k="capstyle" v="round"/>
|
||||||
|
<prop k="joinstyle" v="round"/>
|
||||||
|
<data_defined_properties>
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="properties" type="Map">
|
||||||
|
<Option name="width" type="Map">
|
||||||
|
<Option name="active" type="bool" value="true"/>
|
||||||
|
<Option name="expression" type="QString" value="coalesce("width", 1)"/>
|
||||||
|
<Option name="type" type="int" value="3"/>
|
||||||
|
</Option>
|
||||||
|
</Option>
|
||||||
|
<Option name="type" type="int" value="2"/>
|
||||||
|
</Option>
|
||||||
|
</data_defined_properties>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
</qgis>
|
||||||
68
qgis/styles/routes.qml
Normal file
68
qgis/styles/routes.qml
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="RuleRenderer">
|
||||||
|
<rules>
|
||||||
|
<rule filter=""type" IN ('majorSea','regional') OR "group" = 'searoutes'" symbol="0" label="Sea routes"/>
|
||||||
|
<rule filter=""type" = 'royal' OR ("group" = 'roads' AND coalesce("type",'')='')" symbol="1" label="Royal road"/>
|
||||||
|
<rule filter=""type" = 'market'" symbol="2" label="Market road"/>
|
||||||
|
<rule filter=""type" = 'local' OR "group" = 'secondary'" symbol="3" label="Local road"/>
|
||||||
|
<rule filter=""type" = 'footpath' OR "group" = 'trails'" symbol="4" label="Footpath"/>
|
||||||
|
<rule else="1" symbol="5" label="Other"/>
|
||||||
|
</rules>
|
||||||
|
<symbols>
|
||||||
|
<symbol type="line" name="0">
|
||||||
|
<layer class="SimpleLine">
|
||||||
|
<prop k="line_color" v="0,137,202,200"/>
|
||||||
|
<prop k="line_width" v="0.8"/>
|
||||||
|
<prop k="customdash" v="6;2"/>
|
||||||
|
<prop k="use_custom_dash" v="1"/>
|
||||||
|
<prop k="capstyle" v="round"/>
|
||||||
|
<prop k="joinstyle" v="round"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="line" name="1">
|
||||||
|
<layer class="SimpleLine">
|
||||||
|
<prop k="line_color" v="159,81,34,255"/>
|
||||||
|
<prop k="line_width" v="1.2"/>
|
||||||
|
<prop k="capstyle" v="round"/>
|
||||||
|
<prop k="joinstyle" v="round"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="line" name="2">
|
||||||
|
<layer class="SimpleLine">
|
||||||
|
<prop k="line_color" v="159,81,34,220"/>
|
||||||
|
<prop k="line_width" v="1.0"/>
|
||||||
|
<prop k="customdash" v="4;2"/>
|
||||||
|
<prop k="use_custom_dash" v="1"/>
|
||||||
|
<prop k="capstyle" v="round"/>
|
||||||
|
<prop k="joinstyle" v="round"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="line" name="3">
|
||||||
|
<layer class="SimpleLine">
|
||||||
|
<prop k="line_color" v="159,81,34,180"/>
|
||||||
|
<prop k="line_width" v="0.8"/>
|
||||||
|
<prop k="customdash" v="2;2"/>
|
||||||
|
<prop k="use_custom_dash" v="1"/>
|
||||||
|
<prop k="capstyle" v="round"/>
|
||||||
|
<prop k="joinstyle" v="round"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="line" name="4">
|
||||||
|
<layer class="SimpleLine">
|
||||||
|
<prop k="line_color" v="120,120,120,200"/>
|
||||||
|
<prop k="line_width" v="0.5"/>
|
||||||
|
<prop k="customdash" v="1;2"/>
|
||||||
|
<prop k="use_custom_dash" v="1"/>
|
||||||
|
<prop k="capstyle" v="round"/>
|
||||||
|
<prop k="joinstyle" v="round"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
<symbol type="line" name="5">
|
||||||
|
<layer class="SimpleLine">
|
||||||
|
<prop k="line_color" v="0,0,0,150"/>
|
||||||
|
<prop k="line_width" v="0.6"/>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
</qgis>
|
||||||
36
qgis/styles/states.qml
Normal file
36
qgis/styles/states.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="singleSymbol">
|
||||||
|
<symbols>
|
||||||
|
<symbol type="fill" name="0">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="220,220,220,160"/>
|
||||||
|
<prop k="outline_color" v="60,60,60,220"/>
|
||||||
|
<prop k="outline_width" v="0.6"/>
|
||||||
|
<data_defined_properties>
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="properties" type="Map">
|
||||||
|
<Option name="fillColor" type="Map">
|
||||||
|
<Option name="active" type="bool" value="true"/>
|
||||||
|
<Option name="expression" type="QString" value="attribute('color')"/>
|
||||||
|
<Option name="type" type="int" value="3"/>
|
||||||
|
</Option>
|
||||||
|
</Option>
|
||||||
|
<Option name="type" type="int" value="2"/>
|
||||||
|
</Option>
|
||||||
|
</data_defined_properties>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
<labeling type="rule-based">
|
||||||
|
<rules>
|
||||||
|
<rule>
|
||||||
|
<settings>
|
||||||
|
<text-style field="name" fontSize="10" namedStyle="Bold" isExpression="0"/>
|
||||||
|
<text-buffer bufferDraw="1" bufferColor="255,255,255,255" bufferSize="1.5"/>
|
||||||
|
</settings>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</labeling>
|
||||||
|
</qgis>
|
||||||
|
|
||||||
36
qgis/styles/zones.qml
Normal file
36
qgis/styles/zones.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<qgis styleCategories="Symbology" version="3.28.0">
|
||||||
|
<renderer-v2 type="singleSymbol">
|
||||||
|
<symbols>
|
||||||
|
<symbol type="fill" name="0">
|
||||||
|
<layer class="SimpleFill">
|
||||||
|
<prop k="color" v="255,255,0,80"/>
|
||||||
|
<prop k="outline_color" v="0,0,0,180"/>
|
||||||
|
<prop k="outline_width" v="0.3"/>
|
||||||
|
<data_defined_properties>
|
||||||
|
<Option type="Map">
|
||||||
|
<Option name="properties" type="Map">
|
||||||
|
<Option name="fillColor" type="Map">
|
||||||
|
<Option name="active" type="bool" value="true"/>
|
||||||
|
<Option name="expression" type="QString" value="attribute('color')"/>
|
||||||
|
<Option name="type" type="int" value="3"/>
|
||||||
|
</Option>
|
||||||
|
</Option>
|
||||||
|
<Option name="type" type="int" value="2"/>
|
||||||
|
</Option>
|
||||||
|
</data_defined_properties>
|
||||||
|
</layer>
|
||||||
|
</symbol>
|
||||||
|
</symbols>
|
||||||
|
</renderer-v2>
|
||||||
|
<labeling type="rule-based">
|
||||||
|
<rules>
|
||||||
|
<rule>
|
||||||
|
<settings>
|
||||||
|
<text-style field="description" fontSize="8" namedStyle="Italic" isExpression="0"/>
|
||||||
|
<text-buffer bufferDraw="1" bufferColor="255,255,255,255" bufferSize="0.8"/>
|
||||||
|
</settings>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</labeling>
|
||||||
|
</qgis>
|
||||||
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
start chrome.exe http://localhost:8000/
|
start chrome.exe http://localhost:9000/
|
||||||
@echo off
|
@echo off
|
||||||
python -m http.server 8000
|
python -m http.server 9000
|
||||||
Loading…
Add table
Add a link
Reference in a new issue