Merge remote-tracking branch 'upstream/master' into geodata

This commit is contained in:
Tom Vogt 2019-09-08 20:22:55 +02:00
commit 1aa668bd20
17 changed files with 278 additions and 64 deletions

View file

@ -436,6 +436,8 @@
void function drawLabels() {
const g = labels.select("#states"), t = defs.select("#textPaths");
const displayed = layerIsOn("toggleLabels");
if (!displayed) toggleLabels();
if (!list) {
g.selectAll("text").remove();
@ -520,6 +522,7 @@
});
example.remove();
if (!displayed) toggleLabels();
}()
console.timeEnd("drawStateLabels");
@ -583,6 +586,7 @@
const generateDiplomacy = function() {
console.time("generateDiplomacy");
const cells = pack.cells, states = pack.states;
const chronicle = states[0].diplomacy = [];
const valid = states.filter(s => s.i && !states.removed);
if (valid.length < 2) return;
@ -592,7 +596,6 @@
const navals = {"Neutral":1, "Suspicion":2, "Rival":1, "Unknown":1}; // relations of naval powers
valid.forEach(s => s.diplomacy = new Array(states.length).fill("x")); // clear all relationships
const chronicle = states[0].diplomacy = [];
const areaMean = d3.mean(valid.map(s => s.area)); // avarage state area
// generic relations

View file

@ -387,6 +387,146 @@ async function saveMap() {
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
}
// download map data as GeoJSON
function saveGeoJSON() {
alertMessage.innerHTML = `You can export map data in GeoJSON format used in GIS tools such as QGIS.
Check out <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export" target="_blank">wiki-page</a> for guidance`;
$("#alert").dialog({title: "GIS data export", resizable: false, width: 320, position: {my: "center", at: "center", of: "svg"},
buttons: {
Cells: saveGeoJSON_Cells,
Routes: saveGeoJSON_Roads,
Rivers: saveGeoJSON_Rivers,
Close: function() {$(this).dialog("close");}
}
});
}
function saveGeoJSON_Roads() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
routes._groups[0][0].childNodes.forEach(n => {
n.childNodes.forEach(r => {
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"LineString\", \"coordinates\": ";
data += JSON.stringify(getRoadPoints(r));
data += " },\n \"properties\": {\n";
data += " \"id\": \""+r.id+"\",\n";
data += " \"type\": \""+n.id+"\"\n";
data +=" }\n},\n";
});
});
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const dataBlob = new Blob([data], {type: "application/json"});
const url = window.URL.createObjectURL(dataBlob);
const link = document.createElement("a");
document.body.appendChild(link);
link.download = "fmg_routes_" + Date.now() + ".geojson";
link.href = url;
link.click();
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
}
function saveGeoJSON_Rivers() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
rivers._groups[0][0].childNodes.forEach(n => {
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"LineString\", \"coordinates\": ";
data += JSON.stringify(getRiverPoints(n));
data += " },\n \"properties\": {\n";
data += " \"id\": \""+n.id+"\",\n";
data += " \"width\": \""+n.dataset.width+"\",\n";
data += " \"increment\": \""+n.dataset.increment+"\"\n";
data +=" }\n},\n";
});
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const dataBlob = new Blob([data], {type: "application/json"});
const url = window.URL.createObjectURL(dataBlob);
const link = document.createElement("a");
document.body.appendChild(link);
link.download = "fmg_rivers_" + Date.now() + ".geojson";
link.href = url;
link.click();
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
}
function getRoadPoints(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);
let x = mapCoordinates.lonW + (p.x / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (p.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
points.push([x,y]);
}
return points;
}
function getRiverPoints(node) {
let points = [];
const l = node.getTotalLength() / 2; // half-length
const increment = 0.25; // defines density of points
for (let i=l, c=i; i >= 0; i -= increment, c += increment) {
const p1 = node.getPointAtLength(i);
const p2 = node.getPointAtLength(c);
let x = mapCoordinates.lonW + (((p1.x+p2.x)/2) / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (((p1.y+p2.y)/2) / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
points.push([x,y]);
}
return points;
}
function saveGeoJSON_Cells() {
let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
const cells = pack.cells, v = pack.vertices;
cells.i.forEach(i => {
data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [[";
cells.v[i].forEach(n => {
let x = mapCoordinates.lonW + (v.p[n][0] / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (v.p[n][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
data += "["+x+","+y+"],";
});
// close the ring
let x = mapCoordinates.lonW + (v.p[cells.v[i][0]][0] / graphWidth) * mapCoordinates.lonT;
let y = mapCoordinates.latN - (v.p[cells.v[i][0]][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
data += "["+x+","+y+"]";
data += "]] },\n \"properties\": {\n";
let height = parseInt(getFriendlyHeight(cells.h[i]));
data += " \"id\": \""+i+"\",\n";
data += " \"height\": \""+height+"\",\n";
data += " \"biome\": \""+cells.biome[i]+"\",\n";
data += " \"population\": \""+cells.pop[i]+"\",\n";
data += " \"state\": \""+cells.state[i]+"\",\n";
data += " \"province\": \""+cells.province[i]+"\",\n";
data += " \"culture\": \""+cells.culture[i]+"\",\n";
data += " \"religion\": \""+cells.religion[i]+"\"\n";
data +=" }\n},\n";
});
data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
data += "]}";
const dataBlob = new Blob([data], {type: "application/json"});
const url = window.URL.createObjectURL(dataBlob);
const link = document.createElement("a");
document.body.appendChild(link);
link.download = "fmg_cells_" + Date.now() + ".geojson";
link.href = url;
link.click();
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
}
function uploadFile(file, callback) {
uploadFile.timeStart = performance.now();
@ -695,6 +835,7 @@ function parseLoadedData(data) {
// v 1.0 initially has Sympathy status then relaced with Friendly
for (const s of pack.states) {
if (!s.diplomacy) continue;
s.diplomacy = s.diplomacy.map(r => r === "Sympathy" ? "Friendly" : r);
}
}
@ -713,13 +854,9 @@ function parseLoadedData(data) {
console.error(error);
clearMainTip();
const regex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
const errorNoURL = error.stack.replace(regex, url => '<i>' + last(url.split("/")) + '</i>');
const errorParsed = errorNoURL.replace(/ at /ig, "<br>&nbsp;&nbsp;at ");
alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load,
<br>generate a new random map or cancel the loading
<p id="errorBox">${errorParsed}</p>`;
<p id="errorBox">${parseError(error)}</p>`;
$("#alert").dialog({
resizable: false, title: "Loading error", maxWidth:500, buttons: {
"Select file": function() {$(this).dialog("close"); mapToLoad.click();},

View file

@ -2,7 +2,7 @@
function editDiplomacy() {
if (customization) return;
if (pack.states.filter(s => s.i && !s.removed).length < 2) {
tip("There should be at least 2 states to edit the diplomacy", false, "Error");
tip("There should be at least 2 states to edit the diplomacy", false, "error");
return;
}

View file

@ -231,28 +231,50 @@ document.addEventListener("keydown", function(event) {
const active = document.activeElement.tagName;
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return; // don't trigger if user inputs a text
if (active === "DIV" && document.activeElement.contentEditable === "true") return; // don't trigger if user inputs a text
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey;
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey, meta = event.metaKey;
if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
else if (key === 9) {toggleOptions(event); event.preventDefault();} // Tab to toggle options
else if (key === 113) regeneratePrompt(); // "F2" for new map
else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element
else if (key === 117) quickSave(); // "F6" for quick save
else if (key === 120) quickLoad(); // "F9" for quick load
else if (ctrl && key === 80) saveAsImage("png"); // Ctrl + "P" to save as PNG
else if (ctrl && key === 83) saveAsImage("svg"); // Ctrl + "S" to save as SVG
else if (ctrl && key === 77) saveMap(); // Ctrl + "M" to save MAP file
else if (ctrl && key === 71) saveGeoJSON(); // Ctrl + "G" to save as GeoJSON
else if (ctrl && key === 85) mapToLoad.click(); // Ctrl + "U" to load MAP from URL
else if (ctrl && key === 76) mapToLoad.click(); // Ctrl + "L" to load MAP from local file
else if (ctrl && key === 81) toggleSaveReminder(); // Ctrl + "Q" to toggle save reminder
else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element
else if (undo.offsetParent && ctrl && key === 90) undo.click(); // Ctrl + "Z" to undo
else if (redo.offsetParent && ctrl && key === 89) redo.click(); // Ctrl + "Y" to redo
else if (shift && key === 192) console.log(pack.cells); // Shift + "`" to log cells data
else if (shift && key === 66) console.table(pack.burgs); // Shift + "B" to log burgs data
else if (shift && key === 83) console.table(pack.states); // Shift + "S" to log states data
else if (shift && key === 67) console.table(pack.cultures); // Shift + "C" to log cultures data
else if (shift && key === 82) console.table(pack.religions); // Shift + "R" to log religions data
else if (shift && key === 70) console.table(pack.features); // Shift + "F" to log features data
else if (shift && key === 72) editHeightmap(); // Shift + "H" to edit Heightmap
else if (shift && key === 66) editBiomes(); // Shift + "B" to edit Biomes
else if (shift && key === 83) editStates(); // Shift + "S" to edit States
else if (shift && key === 80) editProvinces(); // Shift + "P" to edit Provinces
else if (shift && key === 68) editDiplomacy(); // Shift + "D" to edit Diplomacy
else if (shift && key === 67) editCultures(); // Shift + "C" to edit Cultures
else if (shift && key === 78) editNamesbase(); // Shift + "N" to edit Namesbase
else if (shift && key === 90) editZones(); // Shift + "Z" to edit Zones
else if (shift && key === 82) editReligions(); // Shift + "R" to edit Religions
else if (shift && key === 84) editBurgs(); // Shift + "T" to edit Burgs
else if (shift && key === 85) editUnits(); // Shift + "U" to edit Units
else if (shift && key === 79) editNotes(); // Shift + "O" to edit Notes
else if (shift && key === 71) toggleAddBurg(); // Shift + "G" to click to add Burg
else if (shift && key === 65) toggleAddLabel(); // Shift + "A" to click to add Label
else if (shift && key === 73) toggleAddRiver(); // Shift + "I" to click to add River
else if (shift && key === 69) toggleAddRoute(); // Shift + "E" to click to add Route
else if (shift && key === 75) toggleAddMarker(); // Shift + "K" to click to add Marker
else if (meta && key === 192) console.log(pack.cells); // Metakey + "`" to log cells data
else if (meta && key === 66) console.table(pack.burgs); // Metakey + "B" to log burgs data
else if (meta && key === 83) console.table(pack.states); // Metakey + "S" to log states data
else if (meta && key === 67) console.table(pack.cultures); // Metakey + "C" to log cultures data
else if (meta && key === 82) console.table(pack.religions); // Metakey + "R" to log religions data
else if (meta && key === 70) console.table(pack.features); // Metakey + "F" to log features data
else if (key === 88) toggleTexture(); // "X" to toggle Texture layer
else if (key === 72) toggleHeight(); // "H" to toggle Heightmap layer
@ -294,10 +316,6 @@ document.addEventListener("keydown", function(event) {
else if (key === 55 || key === 103) zoom.scaleTo(svg, 7); // 7 to zoom to 7
else if (key === 56 || key === 104) zoom.scaleTo(svg, 8); // 8 to zoom to 8
else if (key === 57 || key === 105) zoom.scaleTo(svg, 9); // 9 to zoom to 9
else if (ctrl && key === 90) undo.click(); // Ctrl + "Z" to undo
else if (ctrl && key === 89) redo.click(); // Ctrl + "Y" to redo
else if (ctrl) pressControl(); // Control to toggle mode
});

View file

@ -403,7 +403,7 @@ function getHeight(h) {
updateStatistics();
if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened
if ($("#perspectivePanel").is(":visible")) drawPerspective(); // update perspective view if opened
if ($("#perspectivePanel").is(":visible")) drawPerspective(); // update perspective view if opened
}
// restart edits from 1st step
@ -1103,7 +1103,7 @@ function getHeight(h) {
terrs.selectAll("polygon").remove();
updateHeightmap();
}
}
}
@ -1117,7 +1117,8 @@ function getHeight(h) {
preview.width = grid.cellsX;
preview.height = grid.cellsY;
document.body.insertBefore(preview, optionsContainer);
preview.addEventListener("mouseover", () => tip("Heightmap preview. Right click and 'Save image as..' to download the image"));
preview.addEventListener("mouseover", () => tip("Heightmap preview. Click to download the image"));
preview.addEventListener("click", downloadPreview);
drawHeightmapPreview();
}
@ -1137,6 +1138,41 @@ function getHeight(h) {
ctx.putImageData(imageData, 0, 0);
}
function downloadPreview() {
const preview = document.getElementById("preview");
const dataURL = preview.toDataURL("image/png");
const img = new Image();
img.src = dataURL;
img.onload = function() {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = svgWidth;
canvas.height = svgHeight;
document.body.insertBefore(canvas, optionsContainer);
ctx.drawImage(img, 0, 0, svgWidth, svgHeight);
// const imageData = ctx.getImageData(0, 0, svgWidth, svgHeight);
// for (let i=0; i < imageData.data.length; i+=4) {
// const v = Math.min(rn(imageData.data[i] * gauss(1, .05, .9, 1.1, 3)), 255);
// imageData.data[i] = v;
// imageData.data[i+1] = v;
// imageData.data[i+2] = v;
// }
// ctx.putImageData(imageData, 0, 0);
const imgBig = canvas.toDataURL("image/png");
const link = document.createElement("a");
link.target = "_blank";
link.download = "heightmap_" + Date.now() + ".png";
link.href = imgBig;
document.body.appendChild(link);
link.click();
canvas.remove();
}
}
function openPerspectivePanel() {
if ($("#perspectivePanel").is(":visible")) return;
$("#perspectivePanel").dialog({

View file

@ -11,7 +11,7 @@ function editLabel() {
viewbox.on("touchmove mousemove", showEditorTips);
$("#labelEditor").dialog({
title: "Edit Label", resizable: false,
title: "Edit Label", resizable: false, width: fitContent(),
position: {my: "center top+10", at: "bottom", of: text, collision: "fit"},
close: closeLabelEditor
});

View file

@ -1011,11 +1011,11 @@ function toggleMarkers() {
function toggleLabels() {
if (!layerIsOn("toggleLabels")) {
turnButtonOn("toggleLabels");
$('#labels').fadeIn();
labels.attr("display", null)
invokeActiveZooming();
} else {
turnButtonOff("toggleLabels");
$('#labels').fadeOut();
labels.attr("display", "none");
}
}

View file

@ -1027,12 +1027,10 @@ function toggleSavePane() {
<br>Please check browser settings and turn off adBlocker if it is enabled`;
$("#alert").dialog({title: "File saver", resizable: false, position: {my: "center", at: "center", of: "svg"},
buttons: {
OK: function() {
localStorage.setItem("dns_allow_popup_message", true);
$(this).dialog("close");
}
}
buttons: {OK: function() {
localStorage.setItem("dns_allow_popup_message", true);
$(this).dialog("close");
}}
});
}
}

File diff suppressed because one or more lines are too long