feat: edit routes - main

This commit is contained in:
Azgaar 2024-05-04 12:04:45 +02:00
parent d6c01c8995
commit 68b4cfd370
9 changed files with 401 additions and 357 deletions

View file

@ -258,7 +258,6 @@ i.icon-lock {
cursor: pointer;
}
#routeEditor > *,
#labelEditor div {
display: inline-block;
}
@ -1610,6 +1609,7 @@ div.states > .riverType {
#burgBody > div > div,
#riverBody > div,
#routeBody > div,
#lakeBody > div {
padding: 0.1em;
}
@ -1617,6 +1617,7 @@ div.states > .riverType {
#riverBody div.label,
#riverBody input,
#riverBody select,
#routeBody div.label,
#lakeBody div.label,
#lakeBody input,
#lakeBody select {
@ -1624,6 +1625,12 @@ div.states > .riverType {
width: 7em;
}
#routeBody input,
#routeBody select {
display: inline-block;
width: 10em;
}
#stateNameEditor div.label,
#provinceNameEditor div.label,
#regimentBody div.label,

View file

@ -2897,35 +2897,51 @@
</div>
<div id="routeEditor" class="dialog" style="display: none">
<button id="routeGroupsShow" data-tip="Show the group selection" class="icon-tags"></button>
<div id="routeGroupsSelection" style="display: none">
<button id="routeGroupsHide" data-tip="Hide the group section" class="icon-tags"></button>
<select id="routeGroup" data-tip="Select a group for this route" style="width: 12em"></select>
<input
id="routeGroupName"
placeholder="new group name"
data-tip="Provide a name for the new group"
style="display: none; width: 12em"
/>
<span id="routeGroupAdd" data-tip="Create new group for this route" class="icon-plus pointer"></span>
<div id="routeBody" style="padding-bottom: 0.3em">
<div>
<div class="label" style="width: 4.8em">Name:</div>
<span
id="routeGroupRemove"
data-tip="Remove all routes of this group"
class="icon-trash-empty pointer"
id="routeNameCulture"
data-tip="Generate culture-specific name for the route"
class="icon-book pointer"
></span>
<span id="routeNameRandom" data-tip="Generate random name for the route" class="icon-globe pointer"></span>
<input id="routeName" data-tip="Type to rename the route" autocorrect="off" spellcheck="false" />
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
</div>
<button id="routeEditStyle" data-tip="Edit route group style in Style Editor" class="icon-brush"></button>
<button id="routeLength" data-tip="Route length in selected units">0</button>
<div data-tip="Select route group">
<div class="label">Group:</div>
<select id="routeGroup"></select>
<span id="routeGroupEdit" data-tip="Edit route groups" class="icon-pencil pointer"></span>
</div>
<div data-tip="Route length in selected units">
<div class="label">Length:</div>
<input id="routeLength" disabled />
</div>
</div>
<div id="routeBottom">
<button
id="routeCreateSelectingCells"
data-tip="Create new route selecting route cells"
class="icon-map-pin"
></button>
<button id="routeEditStyle" data-tip="Edit style for all routes in Style Editor" class="icon-brush"></button>
<button
id="routeElevationProfile"
data-tip="Show the elevation profile for the route"
class="icon-chart-area"
></button>
<button id="routeSplit" data-tip="Click on a control point to split the route" class="icon-unlink"></button>
<button id="routeLegend" data-tip="Edit free text notes (legend) for the route" class="icon-edit"></button>
<button id="routeNew" data-tip="Create new route clicking on map" class="icon-map-pin"></button>
<button id="routeRemove" data-tip="Remove route" data-shortcut="Delete" class="icon-trash fastDelete"></button>
<button
id="routeRemove"
data-tip="Remove route"
data-shortcut="Delete"
class="icon-trash fastDelete"
></button>
</div>
</div>
<div id="iceEditor" class="dialog" style="display: none">
@ -8008,9 +8024,9 @@
<script src="versioning.js"></script>
<script src="libs/d3.min.js"></script>
<script src="libs/priority-queue.min.js"></script>
<script src="libs/flatqueue.js"></script>
<script src="libs/delaunator.min.js"></script>
<script src="libs/indexedDB.js?v=1.91.01"></script>
<script src="https://cdn.jsdelivr.net/npm/flatqueue"></script>
<script src="utils/shorthands.js"></script>
<script src="utils/commonUtils.js?v=1.89.29"></script>

57
libs/flatqueue.js Normal file
View file

@ -0,0 +1,57 @@
!(function (t, s) {
"object" == typeof exports && "undefined" != typeof module
? (module.exports = s())
: "function" == typeof define && define.amd
? define(s)
: ((t = "undefined" != typeof globalThis ? globalThis : t || self).FlatQueue = s());
})(this, function () {
"use strict";
return class {
constructor() {
(this.ids = []), (this.values = []), (this.length = 0);
}
clear() {
this.length = 0;
}
push(t, s) {
let i = this.length++;
for (; i > 0; ) {
const t = (i - 1) >> 1,
e = this.values[t];
if (s >= e) break;
(this.ids[i] = this.ids[t]), (this.values[i] = e), (i = t);
}
(this.ids[i] = t), (this.values[i] = s);
}
pop() {
if (0 === this.length) return;
const t = this.ids[0];
if ((this.length--, this.length > 0)) {
const t = (this.ids[0] = this.ids[this.length]),
s = (this.values[0] = this.values[this.length]),
i = this.length >> 1;
let e = 0;
for (; e < i; ) {
let t = 1 + (e << 1);
const i = t + 1;
let h = this.ids[t],
l = this.values[t];
const n = this.values[i];
if ((i < this.length && n < l && ((t = i), (h = this.ids[i]), (l = n)), l >= s)) break;
(this.ids[e] = h), (this.values[e] = l), (e = t);
}
(this.ids[e] = t), (this.values[e] = s);
}
return t;
}
peek() {
if (0 !== this.length) return this.ids[0];
}
peekValue() {
if (0 !== this.length) return this.values[0];
}
shrink() {
this.ids.length = this.values.length = this.length;
}
};
});

View file

@ -465,16 +465,11 @@ function saveGeoJSON_Cells() {
}
function saveGeoJSON_Routes() {
const json = {type: "FeatureCollection", features: []};
routes.selectAll("g > path").each(function () {
const coordinates = getRoutePoints(this);
const id = this.id;
const type = this.parentElement.id;
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}};
json.features.push(feature);
const features = pack.routes.map(({id, points, cells, group}) => {
const coordinates = points || getRoutePoints(cells, group);
return {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, group}};
});
const json = {type: "FeatureCollection", features};
const fileName = getFileName("Routes") + ".geojson";
downloadFile(JSON.stringify(json), fileName, "application/json");
@ -519,17 +514,6 @@ function getCellCoordinates(vertices) {
return [coordinates.concat([coordinates[0]])];
}
function getRoutePoints(node) {
let points = [];
const l = node.getTotalLength();
const increment = l / Math.ceil(l / 2);
for (let i = 0; i <= l; i += increment) {
const p = node.getPointAtLength(i);
points.push(getCoordinates(p.x, p.y, 4));
}
return points;
}
function getRiverPoints(node) {
let points = [];
const l = node.getTotalLength() / 2; // half-length

View file

@ -22,7 +22,7 @@ function clicked() {
if (grand.id === "emblems") editEmblem();
else if (parent.id === "rivers") editRiver(el.id);
else if (grand.id === "routes") editRoute({node: el});
else if (grand.id === "routes") editRoute(el.id);
else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel();
else if (grand.id === "burgLabels") editBurg();
else if (grand.id === "burgIcons") editBurg();

View file

@ -1633,29 +1633,23 @@ function toggleRoutes(event) {
}
}
function drawRoutes() {
TIME && console.time("drawRoutes");
const {cells, burgs} = pack;
const points = adjustBurgPoints(); // mutable array of points
const routePaths = {};
const lineGen = d3.line();
const curves = {
const ROUTE_CURVES = {
roads: d3.curveCatmullRom.alpha(0.1),
trails: d3.curveCatmullRom.alpha(0.1),
searoutes: d3.curveCatmullRom.alpha(0.5),
default: d3.curveCatmullRom.alpha(0.1)
};
const SHARP_ANGLE = 135;
const VERY_SHARP_ANGLE = 115;
for (const {i, group, cells} of pack.routes) {
if (group !== "searoutes") straightenPathAngles(cells); // mutates points
const pathPoints = cells.map(cellId => points[cellId]);
function drawRoutes() {
TIME && console.time("drawRoutes");
const routePaths = {};
const lineGen = d3.line();
lineGen.curve(curves[group] || curves.default);
const path = round(lineGen(pathPoints), 1);
for (const route of pack.routes) {
const {i, group} = route;
lineGen.curve(ROUTE_CURVES[group] || ROUTE_CURVES.default);
const routePoints = getRoutePoints(route);
const path = round(lineGen(routePoints), 1);
if (!routePaths[group]) routePaths[group] = [];
routePaths[group].push(`<path id="route${i}" d="${path}"/>`);
@ -1667,27 +1661,33 @@ function drawRoutes() {
}
TIME && console.timeEnd("drawRoutes");
function adjustBurgPoints() {
const points = Array.from(cells.p);
for (const burg of burgs) {
if (burg.i === 0) continue;
const {cell, x, y} = burg;
points[cell] = [x, y];
}
return points;
const ROUTES_SHARP_ANGLE = 135;
const ROUTES_VERY_SHARP_ANGLE = 115;
function getRoutePoints({points, cells: cellIds, group}) {
if (points) return points;
const {cells, burgs} = pack;
const routePoints = cellIds.map(cellId => {
const burgId = cells.burg[cellId];
if (burgId) {
const {x, y} = burgs[burgId];
return [x, y];
}
function straightenPathAngles(cellIds) {
return cells.p[cellId];
});
if (group !== "searoutes") {
for (let i = 1; i < cellIds.length - 1; i++) {
const cellId = cellIds[i];
if (cells.burg[cellId]) continue;
const prev = points[cellIds[i - 1]];
const that = points[cellId];
const next = points[cellIds[i + 1]];
const prev = routePoints[i - 1];
const that = routePoints[i];
const next = routePoints[i + 1];
const dAx = prev[0] - that[0];
const dAy = prev[1] - that[1];
@ -1695,25 +1695,29 @@ function drawRoutes() {
const dBy = next[1] - that[1];
const angle = (Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy) * 180) / Math.PI;
if (Math.abs(angle) < SHARP_ANGLE) {
if (Math.abs(angle) < ROUTES_SHARP_ANGLE) {
const middleX = (prev[0] + next[0]) / 2;
const middleY = (prev[1] + next[1]) / 2;
if (Math.abs(angle) < VERY_SHARP_ANGLE) {
if (Math.abs(angle) < ROUTES_VERY_SHARP_ANGLE) {
const newX = (that[0] + middleX * 2) / 3;
const newY = (that[1] + middleY * 2) / 3;
points[cellId] = [newX, newY];
routePoints[i] = [newX, newY];
continue;
}
const newX = (that[0] + middleX) / 2;
const newY = (that[1] + middleY) / 2;
points[cellId] = [newX, newY];
routePoints[i] = [newX, newY];
}
}
}
return routePoints;
}
function drawRoute() {}
function toggleMilitary() {
if (!layerIsOn("toggleMilitary")) {
turnButtonOn("toggleMilitary");

View file

@ -10,7 +10,10 @@ function editRiver(id) {
elSelected = d3.select("#" + id).on("click", addControlPoint);
tip("Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead", true);
tip(
"Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead",
true
);
debug.append("g").attr("id", "controlCells");
debug.append("g").attr("id", "controlPoints");

View file

@ -1,306 +1,280 @@
"use strict";
function editRoute({node, mode}) {
function editRoute(id) {
if (customization) return;
if (elSelected && id === elSelected.attr("id")) return;
closeDialogs(".stable");
if (!layerIsOn("toggleRoutes")) toggleRoutes();
byId("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
if (!layerIsOn("toggleCells")) toggleCells();
elSelected = d3.select("#" + id).on("click", addControlPoint);
tip(
"Drag control points to change the route. Click on point to remove it. Click on the route to add additional control point. For major changes please create a new route instead",
true
);
debug.append("g").attr("id", "controlCells");
debug.append("g").attr("id", "controlPoints");
updateRouteData();
drawControlPoints(getRoutePoints(getRoute()));
drawCells();
$("#routeEditor").dialog({
title: "Edit Route",
resizable: false,
position: {my: "center top+60", at: "top", of: d3.event, collision: "fit"},
close: closeRoutesEditor
position: {my: "left top", at: "left+10 top+10", of: "#map"},
close: closeRouteEditor
});
debug.append("g").attr("id", "controlPoints");
d3.select(node).on("click", addInterimControlPoint);
drawControlPoints(node);
selectRouteGroup(node);
viewbox.on("touchmove mousemove", showEditorTips);
if (mode === "onclick") toggleRouteCreationMode();
if (modules.editRoute) return;
modules.editRoute = true;
// add listeners
document.getElementById("routeGroupsShow").addEventListener("click", showGroupSection);
document.getElementById("routeGroup").addEventListener("change", changeRouteGroup);
document.getElementById("routeGroupAdd").addEventListener("click", toggleNewGroupInput);
document.getElementById("routeGroupName").addEventListener("change", createNewGroup);
document.getElementById("routeGroupRemove").addEventListener("click", removeRouteGroup);
document.getElementById("routeGroupsHide").addEventListener("click", hideGroupSection);
document.getElementById("routeElevationProfile").addEventListener("click", showElevationProfile);
byId("routeCreateSelectingCells").on("click", createRoute);
byId("routeEditStyle").on("click", () => editStyle("routes"));
byId("routeElevationProfile").on("click", showElevationProfile);
byId("routeLegend").on("click", editRouteLegend);
byId("routeRemove").on("click", removeRoute);
byId("routeName").on("input", changeName);
byId("routeGroup").on("input", changeGroup);
byId("routeNameCulture").on("click", generateNameCulture);
byId("routeNameRandom").on("click", generateNameRandom);
document.getElementById("routeEditStyle").addEventListener("click", editGroupStyle);
document.getElementById("routeSplit").addEventListener("click", toggleRouteSplitMode);
document.getElementById("routeLegend").addEventListener("click", editRouteLegend);
document.getElementById("routeNew").addEventListener("click", toggleRouteCreationMode);
document.getElementById("routeRemove").addEventListener("click", removeRoute);
function showEditorTips() {
showMainTip();
if (routeNew.classList.contains("pressed")) return;
if (d3.event.target.id === node.getAttribute("id")) tip("Click to add a control point");
else if (d3.event.target.parentNode.id === "controlPoints") tip("Drag to move, click to delete the control point");
function getRoute() {
const routeId = +elSelected.attr("id").slice(5);
const route = pack.routes.find(r => r.i === routeId);
return route;
}
function drawControlPoints(node) {
const totalLength = node.getTotalLength();
const CONTROL_POINST_DISTANCE = 10;
const increment = totalLength / Math.ceil(totalLength / CONTROL_POINST_DISTANCE);
for (let i = 0; i <= totalLength; i += increment) {
const point = node.getPointAtLength(i);
addControlPoint([point.x, point.y]);
}
routeLength.innerHTML = rn(totalLength * distanceScaleInput.value) + " " + distanceUnitInput.value;
function updateRouteData() {
const route = getRoute();
route.name = route.name || generateRouteName(route);
byId("routeName").value = route.name;
const routeGroup = byId("routeGroup");
routeGroup.options.length = 0;
routes.selectAll("g").each(function () {
routeGroup.options.add(new Option(this.id, this.id, false, this.id === route.group));
});
updateRouteLength(route);
}
function addControlPoint(point, before = null) {
debug
.select("#controlPoints")
.insert("circle", before)
.attr("cx", point[0])
.attr("cy", point[1])
.attr("r", 0.6)
.call(d3.drag().on("drag", dragControlPoint))
.on("click", clickControlPoint);
function generateRouteName(route) {
const {cells, burgs} = pack;
const burgName = (() => {
const priority = [route.cells.at(-1), route.cells.at(0), route.cells.slice(1, -1).reverse()];
for (const cellId of priority) {
const burgId = cells.burg[cellId];
if (burgId) return burgs[burgId].name;
}
})();
const type = route.group.replace(/s$/, "");
if (burgName) return `${getAdjective(burgName)} ${type}`;
return "Unnamed route";
}
function addInterimControlPoint() {
const point = d3.mouse(this);
const controls = document.getElementById("controlPoints").querySelectorAll("circle");
const points = Array.from(controls).map(circle => [+circle.getAttribute("cx"), +circle.getAttribute("cy")]);
const index = getSegmentId(points, point, 2);
addControlPoint(point, ":nth-child(" + (index + 1) + ")");
redrawRoute();
function updateRouteLength(route) {
route.length = rn(elSelected.node().getTotalLength() / 2, 2);
const lengthUI = `${rn(route.length * distanceScaleInput.value)} ${distanceUnitInput.value}`;
byId("routeLength").value = lengthUI;
}
function dragControlPoint() {
this.setAttribute("cx", d3.event.x);
this.setAttribute("cy", d3.event.y);
redrawRoute();
}
function redrawRoute() {
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
const points = [];
function drawControlPoints(points) {
debug
.select("#controlPoints")
.selectAll("circle")
.each(function () {
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
.data(points)
.join("circle")
.attr("cx", d => d[0])
.attr("cy", d => d[1])
.attr("r", 0.6)
.call(d3.drag().on("start", dragControlPoint))
.on("click", removeControlPoint);
}
function drawCells() {
const {cells} = getRoute();
debug.select("#controlCells").selectAll("polygon").data(cells).join("polygon").attr("points", getPackPolygon);
}
function dragControlPoint() {
const initCell = findCell(d3.event.x, d3.event.y);
const route = getRoute();
const cellIndex = route.cells.indexOf(initCell);
d3.event.on("drag", function () {
this.setAttribute("cx", d3.event.x);
this.setAttribute("cy", d3.event.y);
this.__data__ = [rn(d3.event.x, 2), rn(d3.event.y, 2)];
redrawRoute();
drawCells();
});
node.setAttribute("d", round(lineGen(points)));
routeLength.innerHTML = rn(node.getTotalLength() * distanceScaleInput.value) + " " + distanceUnitInput.value;
d3.event.on("end", () => {
const movedToCell = findCell(d3.event.x, d3.event.y);
if (modules.elevation) showEPForRoute(node);
if (movedToCell !== initCell) {
route.cells[cellIndex] = movedToCell;
const prevCell = route.cells[cellIndex - 1];
if (prevCell) {
removeConnection(initCell, prevCell);
addConnection(movedToCell, prevCell, route.i);
}
const nextCell = route.cells[cellIndex + 1];
if (nextCell) {
removeConnection(initCell, nextCell);
addConnection(movedToCell, nextCell, route.i);
}
}
});
}
function redrawRoute() {
const route = getRoute();
route.points = debug.selectAll("#controlPoints > *").data();
route.cells = unique(route.points.map(([x, y]) => findCell(x, y)));
const lineGen = d3.line();
lineGen.curve(ROUTE_CURVES[route.group] || ROUTE_CURVES.default);
const path = round(lineGen(route.points), 1);
elSelected.attr("d", path);
updateRouteLength(route);
if (modules.elevation) showEPForRoute(elSelected.node());
}
function addControlPoint() {
const [x, y] = d3.mouse(this);
const route = getRoute();
if (!route.points) route.points = debug.selectAll("#controlPoints > *").data();
const point = [rn(x, 2), rn(y, 2)];
const index = getSegmentId(route.points, point, 2);
route.points.splice(index, 0, point);
const cellId = findCell(x, y);
if (!route.cells.includes(cellId)) {
route.cells = unique(route.points.map(([x, y]) => findCell(x, y)));
const cellIndex = route.cells.indexOf(cellId);
const prev = route.cells[cellIndex - 1];
const next = route.cells[cellIndex + 1];
removeConnection(prev, next);
addConnection(prev, cellId, route.i);
addConnection(cellId, next, route.i);
drawCells();
}
drawControlPoints(route.points);
redrawRoute();
}
function drawConnections() {
debug.selectAll("line").remove();
for (const [fromCellId, connections] of Object.entries(pack.cells.routes)) {
const from = pack.cells.p[fromCellId];
for (const toCellId of Object.keys(connections)) {
const to = pack.cells.p[toCellId];
debug
.append("line")
.attr("x1", from[0])
.attr("y1", from[1])
.attr("x2", to[0])
.attr("y2", to[1])
.attr("stroke", "red")
.attr("stroke-width", 0.4)
.attr("opacity", 0.5);
}
}
}
function removeConnection(from, to) {
const routes = pack.cells.routes;
if (routes[from]) delete routes[from][to];
if (routes[to]) delete routes[to][from];
}
function addConnection(from, to, routeId) {
const routes = pack.cells.routes;
if (!routes[from]) routes[from] = {};
routes[from][to] = routeId;
if (!routes[to]) routes[to] = {};
routes[to][from] = routeId;
}
function removeControlPoint() {
this.remove();
redrawRoute();
drawCells();
}
function changeName() {
getRoute().name = this.value;
}
function changeGroup() {
const group = this.value;
byId(group).appendChild(elSelected.node());
getRoute().group = group;
}
function generateNameCulture() {
const route = getRoute();
const cell = ra(route.cells);
const cultureId = pack.cells.culture[cell];
route.name = routeName.value = Names.getCulture(cultureId);
}
function generateNameRandom() {
const route = getRoute();
route.name = routeName.value = Names.getBase(rand(nameBases.length - 1));
}
function showElevationProfile() {
modules.elevation = true;
showEPForRoute(node);
}
function showGroupSection() {
document.querySelectorAll("#routeEditor > button").forEach(el => (el.style.display = "none"));
document.getElementById("routeGroupsSelection").style.display = "inline-block";
}
function hideGroupSection() {
document.querySelectorAll("#routeEditor > button").forEach(el => (el.style.display = "inline-block"));
document.getElementById("routeGroupsSelection").style.display = "none";
document.getElementById("routeGroupName").style.display = "none";
document.getElementById("routeGroupName").value = "";
document.getElementById("routeGroup").style.display = "inline-block";
}
function selectRouteGroup(node) {
const group = node.parentNode.id;
const select = document.getElementById("routeGroup");
select.options.length = 0; // remove all options
routes.selectAll("g").each(function () {
select.options.add(new Option(this.id, this.id, false, this.id === group));
});
}
function changeRouteGroup() {
document.getElementById(this.value).appendChild(node);
}
function toggleNewGroupInput() {
if (routeGroupName.style.display === "none") {
routeGroupName.style.display = "inline-block";
routeGroupName.focus();
routeGroup.style.display = "none";
} else {
routeGroupName.style.display = "none";
routeGroup.style.display = "inline-block";
}
}
function createNewGroup() {
if (!this.value) {
tip("Please provide a valid group name");
return;
}
const group = this.value
.toLowerCase()
.replace(/ /g, "_")
.replace(/[^\w\s]/gi, "");
if (document.getElementById(group)) {
tip("Element with this id already exists. Please provide a unique name", false, "error");
return;
}
if (Number.isFinite(+group.charAt(0))) {
tip("Group name should start with a letter", false, "error");
return;
}
// just rename if only 1 element left
const oldGroup = node.parentNode;
const basic = ["roads", "trails", "searoutes"].includes(oldGroup.id);
if (!basic && oldGroup.childElementCount === 1) {
document.getElementById("routeGroup").selectedOptions[0].remove();
document.getElementById("routeGroup").options.add(new Option(group, group, false, true));
oldGroup.id = group;
toggleNewGroupInput();
document.getElementById("routeGroupName").value = "";
return;
}
const newGroup = node.parentNode.cloneNode(false);
document.getElementById("routes").appendChild(newGroup);
newGroup.id = group;
document.getElementById("routeGroup").options.add(new Option(group, group, false, true));
document.getElementById(group).appendChild(node);
toggleNewGroupInput();
document.getElementById("routeGroupName").value = "";
}
function removeRouteGroup() {
const group = node.parentNode.id;
const basic = ["roads", "trails", "searoutes"].includes(group);
const count = node.parentNode.childElementCount;
alertMessage.innerHTML = /* html */ `Are you sure you want to remove ${
basic ? "all elements in the group" : "the entire route group"
}? <br /><br />Routes to be
removed: ${count}`;
$("#alert").dialog({
resizable: false,
title: "Remove route group",
buttons: {
Remove: function () {
$(this).dialog("close");
$("#routeEditor").dialog("close");
hideGroupSection();
if (basic)
routes
.select("#" + group)
.selectAll("path")
.remove();
else routes.select("#" + group).remove();
},
Cancel: function () {
$(this).dialog("close");
}
}
});
}
function editGroupStyle() {
const g = node.parentNode.id;
editStyle("routes", g);
}
function toggleRouteSplitMode() {
document.getElementById("routeNew").classList.remove("pressed");
this.classList.toggle("pressed");
}
function clickControlPoint() {
if (routeSplit.classList.contains("pressed")) splitRoute(this);
else {
this.remove();
redrawRoute();
}
}
function splitRoute(clicked) {
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
const group = d3.select(node.parentNode);
routeSplit.classList.remove("pressed");
const points1 = [];
const points2 = [];
let points = points1;
debug
.select("#controlPoints")
.selectAll("circle")
.each(function () {
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
if (this === clicked) {
points = points2;
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
}
this.remove();
});
node.setAttribute("d", round(lineGen(points1)));
const id = getNextId("route");
group.append("path").attr("id", id).attr("d", lineGen(points2));
debug.select("#controlPoints").selectAll("circle").remove();
drawControlPoints(node);
}
function toggleRouteCreationMode() {
document.getElementById("routeSplit").classList.remove("pressed");
document.getElementById("routeNew").classList.toggle("pressed");
if (document.getElementById("routeNew").classList.contains("pressed")) {
tip("Click on map to add control points", true);
viewbox.on("click", addPointOnClick).style("cursor", "crosshair");
d3.select(node).on("click", null);
} else {
clearMainTip();
viewbox.on("click", clicked).style("cursor", "default");
d3.select(node).on("click", addInterimControlPoint).attr("data-new", null);
}
}
function addPointOnClick() {
// create new route
if (!node.dataset.new) {
debug.select("#controlPoints").selectAll("circle").remove();
const parent = node.parentNode;
const id = getNextId("route");
const newRoute = d3.select(parent).append("path").attr("id", id).attr("data-new", 1);
node = newRoute.node();
}
addControlPoint(d3.mouse(this));
redrawRoute();
showEPForRoute(elSelected.node());
}
function editRouteLegend() {
const id = node.getAttribute("id");
editNotes(id, id);
const id = elSelected.attr("id");
const route = getRoute();
editNotes(id, route.name);
}
function createRoute() {
// TODO: white the code :)
}
function removeRoute() {
alertMessage.innerHTML = "Are you sure you want to remove the route?";
alertMessage.innerHTML = "Are you sure you want to remove the route and all its tributaries";
$("#alert").dialog({
resizable: false,
title: "Remove route",
width: "22em",
title: "Remove route and tributaries",
buttons: {
Remove: function () {
$(this).dialog("close");
node.remove();
const route = +elSelected.attr("id").slice(5);
Routes.remove(route);
elSelected.remove();
$("#routeEditor").dialog("close");
},
Cancel: function () {
@ -310,13 +284,16 @@ function editRoute({node, mode}) {
});
}
function closeRoutesEditor() {
node.data.new = null;
d3.select(node).on("click", null);
clearMainTip();
routeSplit.classList.remove("pressed");
routeNew.classList.remove("pressed");
function closeRouteEditor() {
debug.select("#controlPoints").remove();
debug.select("#controlCells").remove();
elSelected.on("click", null);
unselect();
clearMainTip();
const forced = +byId("toggleCells").dataset.forced;
byId("toggleCells").dataset.forced = 0;
if (forced && layerIsOn("toggleCells")) toggleCells();
}
}

View file

@ -793,13 +793,9 @@ function addRouteOnClick() {
unpressClickToAddButton();
const [x, y] = d3.mouse(this);
const newRoute = routes
.select("g")
.append("path")
.attr("id", getNextId("route"))
.attr("data-new", 1)
.attr("d", `M${x},${y}`);
editRoute({node: newRoute.node(), mode: "onclick"});
const id = getNextId("route");
routes.select("g").append("path").attr("id", id).attr("data-new", 1).attr("d", `M${x},${y}`);
editRoute(id);
}
function toggleAddMarker() {