Refactor layers rendering (#1120)

* feat: render states - use global fn

* feat: render states - separate pole detection from layer render

* feat: render provinces

* chore: unify drawFillWithGap

* refactor: drawIce

* refactor: drawBorders

* refactor: drawHeightmap

* refactor: drawTemperature

* refactor: drawBiomes

* refactor: drawPrec

* refactor: drawPrecipitation

* refactor: drawPopulation

* refactor: drawCells

* refactor: geColor

* refactor: drawMarkers

* refactor: drawScaleBar

* refactor: drawScaleBar

* refactor: drawMilitary

* refactor: pump version to 1.104.00

* refactor: pump version to 1.104.00

* refactor: drawCoastline and createDefaultRuler

* refactor: drawCoastline

* refactor: Features module start

* refactor: features - define distance fields

* feat: drawFeatures

* feat: drawIce don't hide

* feat: detect coastline - fix issue with border feature

* feat: separate labels rendering from generation process

* feat: auto-update and restore layers

* refactor - change layers

* refactor - sort layers

* fix: regenerate burgs to re-render layers

* fix: getColor is not defined

* fix: burgs overview - don't auto-show labels on hover

* fix: redraw population on change

* refactor: improve tooltip logic for burg labels and icons

* chore: pump version to 1.104.0

* fefactor: edit coastline and lake

* fix: minot fixes

* fix: submap

---------

Co-authored-by: Azgaar <azgaar.fmg@yandex.com>
This commit is contained in:
Azgaar 2024-09-20 12:20:27 +02:00 committed by GitHub
parent ec993d1a9b
commit 05de284e02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 2473 additions and 2713 deletions

View file

@ -1,16 +1,16 @@
"use strict";
// get continuous paths for all cells at once based on getType(cellId) comparison
function getVertexPaths({getType, options}) {
const {cells, vertices} = pack;
const paths = {};
// get continuous paths (isolines) for all cells at once based on getType(cellId) comparison
function getIsolines(graph, getType, options = {polygons: false, fill: false, halo: false, waterGap: false}) {
const {cells, vertices} = graph;
const isolines = {};
const checkedCells = new Uint8Array(cells.c.length);
const checkedCells = new Uint8Array(cells.i.length);
const addToChecked = cellId => (checkedCells[cellId] = 1);
const isChecked = cellId => checkedCells[cellId] === 1;
for (let cellId = 0; cellId < cells.c.length; cellId++) {
if (isChecked(cellId) || getType(cellId) === 0) continue;
for (const cellId of cells.i) {
if (isChecked(cellId) || !getType(cellId)) continue;
addToChecked(cellId);
const type = getType(cellId);
@ -20,70 +20,73 @@ function getVertexPaths({getType, options}) {
const onborderCell = cells.c[cellId].find(ofDifferentType);
if (onborderCell === undefined) continue;
const feature = pack.features[cells.f[onborderCell]];
if (feature.type === "lake") {
if (!feature.shoreline) Lakes.getShoreline(feature);
if (feature.shoreline.every(ofSameType)) continue; // inner lake
}
// check if inner lake. Note there is no shoreline for grid features
const feature = graph.features[cells.f[onborderCell]];
if (feature.type === "lake" && feature.shoreline?.every(ofSameType)) continue;
const startingVertex = cells.v[cellId].find(v => vertices.c[v].some(ofDifferentType));
if (startingVertex === undefined) throw new Error(`Starting vertex for cell ${cellId} is not found`);
const vertexChain = connectVertices({startingVertex, ofSameType, addToChecked, closeRing: true});
const vertexChain = connectVertices({vertices, startingVertex, ofSameType, addToChecked, closeRing: true});
if (vertexChain.length < 3) continue;
addPath(type, vertexChain);
addIsoline(type, vertices, vertexChain);
}
return Object.entries(paths);
return isolines;
function getBorderPath(vertexChain, discontinue) {
let discontinued = true;
let lastOperation = "";
const path = vertexChain.map(vertex => {
if (discontinue(vertex)) {
discontinued = true;
return "";
}
function addIsoline(type, vertices, vertexChain) {
if (!isolines[type]) isolines[type] = {};
const operation = discontinued ? "M" : "L";
const command = operation === lastOperation ? "" : operation;
if (options.polygons) {
if (!isolines[type].polygons) isolines[type].polygons = [];
isolines[type].polygons.push(vertexChain.map(vertexId => vertices.p[vertexId]));
}
discontinued = false;
lastOperation = operation;
if (options.fill) {
if (!isolines[type].fill) isolines[type].fill = "";
isolines[type].fill += getFillPath(vertices, vertexChain);
}
return ` ${command}${getVertexPoint(vertex)}`;
});
if (options.waterGap) {
if (!isolines[type].waterGap) isolines[type].waterGap = "";
const isLandVertex = vertexId => vertices.c[vertexId].every(i => cells.h[i] >= 20);
isolines[type].waterGap += getBorderPath(vertices, vertexChain, isLandVertex);
}
return path.join("").trim();
}
function isBorderVertex(vertex) {
const adjacentCells = vertices.c[vertex];
return adjacentCells.some(i => cells.b[i]);
}
function isLandVertex(vertex) {
const adjacentCells = vertices.c[vertex];
return adjacentCells.every(i => cells.h[i] >= MIN_LAND_HEIGHT);
}
function addPath(index, vertexChain) {
if (!paths[index]) paths[index] = {fill: "", waterGap: "", halo: ""};
if (options.fill) paths[index].fill += getFillPath(vertexChain);
if (options.halo) paths[index].halo += getBorderPath(vertexChain, isBorderVertex);
if (options.waterGap) paths[index].waterGap += getBorderPath(vertexChain, isLandVertex);
if (options.halo) {
if (!isolines[type].halo) isolines[type].halo = "";
const isBorderVertex = vertexId => vertices.c[vertexId].some(i => cells.b[i]);
isolines[type].halo += getBorderPath(vertices, vertexChain, isBorderVertex);
}
}
}
function getVertexPoint(vertexId) {
return pack.vertices.p[vertexId];
}
function getFillPath(vertexChain) {
const points = vertexChain.map(getVertexPoint);
function getFillPath(vertices, vertexChain) {
const points = vertexChain.map(vertexId => vertices.p[vertexId]);
const firstPoint = points.shift();
return `M${firstPoint} L${points.join(" ")}`;
return `M${firstPoint} L${points.join(" ")} Z`;
}
function getBorderPath(vertices, vertexChain, discontinue) {
let discontinued = true;
let lastOperation = "";
const path = vertexChain.map(vertexId => {
if (discontinue(vertexId)) {
discontinued = true;
return "";
}
const operation = discontinued ? "M" : "L";
const command = operation === lastOperation ? "" : operation;
discontinued = false;
lastOperation = operation;
return ` ${command}${vertices.p[vertexId]}`;
});
return path.join("").trim();
}
// get single path for an non-continuous array of cells
@ -107,26 +110,36 @@ function getVertexPath(cellsArray) {
if (onborderCell === undefined) continue;
const feature = pack.features[cells.f[onborderCell]];
if (feature.type === "lake") {
if (!feature.shoreline) Lakes.getShoreline(feature);
if (feature.type === "lake" && feature.shoreline) {
if (feature.shoreline.every(ofSameType)) continue; // inner lake
}
const startingVertex = cells.v[cellId].find(v => vertices.c[v].some(ofDifferentType));
if (startingVertex === undefined) throw new Error(`Starting vertex for cell ${cellId} is not found`);
const vertexChain = connectVertices({startingVertex, ofSameType, addToChecked, closeRing: true});
const vertexChain = connectVertices({vertices, startingVertex, ofSameType, addToChecked, closeRing: true});
if (vertexChain.length < 3) continue;
path += getFillPath(vertexChain);
path += getFillPath(vertices, vertexChain);
}
return path;
}
function connectVertices({startingVertex, ofSameType, addToChecked, closeRing}) {
const vertices = pack.vertices;
const MAX_ITERATIONS = pack.cells.i.length;
function getPolesOfInaccessibility(graph, getType) {
const isolines = getIsolines(graph, getType, {polygons: true});
const poles = Object.entries(isolines).map(([id, isoline]) => {
const multiPolygon = isoline.polygons.sort((a, b) => b.length - a.length);
const [x, y] = polylabel(multiPolygon, 20);
return [id, [rn(x), rn(y)]];
});
return Object.fromEntries(poles);
}
function connectVertices({vertices, startingVertex, ofSameType, addToChecked, closeRing}) {
const MAX_ITERATIONS = vertices.c.length;
const chain = []; // vertices chain to form a path
let next = startingVertex;
@ -145,6 +158,11 @@ function connectVertices({startingVertex, ofSameType, addToChecked, closeRing})
else if (v2 !== previous && c2 !== c3) next = v2;
else if (v3 !== previous && c1 !== c3) next = v3;
if (!vertices.c[next]) {
ERROR && console.error("ConnectVertices: next vertex is out of bounds");
break;
}
if (next === current) {
ERROR && console.error("ConnectVertices: next vertex is not found");
break;