diff --git a/QGIS/Style_Biomes.qml b/QGIS/Style_Biomes.qml
deleted file mode 100644
index c482cfe3..00000000
--- a/QGIS/Style_Biomes.qml
+++ /dev/null
@@ -1,361 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
- 0
- 2
-
diff --git a/QGIS/add_random_points.php b/QGIS/add_random_points.php
deleted file mode 100644
index 46452498..00000000
--- a/QGIS/add_random_points.php
+++ /dev/null
@@ -1,81 +0,0 @@
-\n" );
-}
-
-// FIXME: This script created a few cases of self-intersection that must be fixed manually
-// in QGIS: Vector -> Geometry Tools -> Check Validity
-
-$cells = json_decode(file_get_contents($argv[1]), true);
-
-
-for ($i=0; $i<$iterations; $i++) {
- $lookup = array();
-
- foreach ($cells['features'] as &$cell) {
- $points = $cell['geometry']['coordinates'][0];
-
- $prev = null;
- $newpoints = array();
-
- foreach ($points as $point) {
- if ($prev) {
- $distance = sqrt(pow($prev[0] - $point[0], 2) + pow($prev[1] - $point[1], 2));
- if ($distance == 0) continue;
-
- if ($distance > $min_distance) {
- $id_a = $prev[0]."-".$prev[1];
- $id_b = $point[0]."-".$point[1];
-
- if (isset($lookup[$id_a."--".$id_b])) {
- $newpoints[] = $lookup[$id_a."--".$id_b];
- } elseif (isset($lookup[$id_b."--".$id_a])) {
- $newpoints[] = $lookup[$id_b."--".$id_a];
- } else {
- $x = ($prev[0]+$point[0])/2.0;
- $y = ($prev[1]+$point[1])/2.0;
-
- $r = mt_rand() / mt_getrandmax(); // 0-1
- $r = ($r+1) / 2; // 0.5 - 1.0
-
- // if we define dx=x2-x1 and dy=y2-y1, then the normals are (-dy, dx) and (dy, -dx).
- $dx = $point[0] - $x;
- $dy = $point[1] - $y;
-
- if (mt_rand() / mt_getrandmax() < 0.5) {
- $x_off = -$dy;
- $y_off = $dx;
- } else {
- $x_off = $dy;
- $y_off = -$dx;
- }
-
- $x_off *= $r * $max_deviation;
- $x_off = max(min($x_off, $max_abs), $max_abs*-1);
-
- $y_off *= $r * $max_deviation;
- $y_off = max(min($y_off, $max_abs), $max_abs*-1);
-
- $p = array($x + $x_off, $y + $y_off);
- $lookup[$id_a."--".$id_b] = $p;
- $newpoints[] = $p;
- }
- }
- }
- $newpoints[] = $point;
- $prev = $point;
- }
- $cell['geometry']['coordinates'][0] = $newpoints;
- }
-}
-
-echo json_encode($cells, JSON_PRETTY_PRINT);
-
-?>
diff --git a/index.html b/index.html
index e54a2cd2..4def49d8 100644
--- a/index.html
+++ b/index.html
@@ -1838,7 +1838,7 @@
.map
.svg
.png
- .json
+ .json
storage
diff --git a/modules/save-and-load.js b/modules/save-and-load.js
index 373da683..36cf9505 100644
--- a/modules/save-and-load.js
+++ b/modules/save-and-load.js
@@ -1,176 +1,6 @@
// Functions to save and load the map
"use strict";
-// download map data as GeoJSON
-function saveGeoJSON() {
- saveGeoJSON_Cells();
- saveGeoJSON_Roads();
- saveGeoJSON_Rivers();
-}
-
-function saveGeoJSON_Roads() {
- // this is work-in-progress
- roads = routes.select("#roads");
- trails = routes.select("#trails");
- searoutes = routes.select("#searoutes");
-
- let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
-
- routes._groups[0][0].childNodes.forEach(n => {
- //console.log(n.id);
- 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;
- const v = pack.vertices;
-
- /*
- my guesses on the cells structure:
-
- cells.h = height
- cells.p = coordinates of center point (of the voronoi cell)
- cells.pop = population
-
- // from voronoi.js:
- const cells = {v: [], c: [], b: []}; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
- const vertices = {p: [], v: [], c: []}; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
-
-
- */
-
- 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";
- });
-
-/*
- cells.i.forEach(i => {
- let x = (cells.p[i][0] / graphWidth) * mapCoordinates.lonT + mapCoordinates.lonW;
- let y = mapCoordinates.latN - (cells.p[i][1] / graphHeight) * mapCoordinates.lonT; // inverted in QGIS otherwise
- let height = parseInt(getFriendlyHeight(cells.h[i]));
-
- data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Point\", \"coordinates\": ["+x+", "+y+", "+height+"] },\n \"properties\": {\n";
- data += " \"id\": \""+i+"\",\n";
- data += " \"biome\": \""+cells.biome[i]+"\",\n";
- data += " \"height\": \""+cells.h[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);
-}
-
// download map as SVG or PNG file
function saveAsImage(type) {
console.time("saveAsImage");
@@ -387,6 +217,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 wiki-page 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();
diff --git a/modules/ui/general.js b/modules/ui/general.js
index 64ab81f3..929a9ece 100644
--- a/modules/ui/general.js
+++ b/modules/ui/general.js
@@ -242,6 +242,7 @@ document.addEventListener("keydown", function(event) {
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
diff --git a/modules/ui/options.js b/modules/ui/options.js
index cf10282c..6729fc20 100644
--- a/modules/ui/options.js
+++ b/modules/ui/options.js
@@ -1027,12 +1027,10 @@ function toggleSavePane() {
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");
+ }}
});
}
}