From edb0cb820df02e851c46529661763aeff9828227 Mon Sep 17 00:00:00 2001
From: Lucas
Date: Fri, 26 Apr 2019 14:29:25 +0100
Subject: [PATCH] geogen release
---
.gitignore | 30 +
index.html | 4 +
libs/NDSFutility.js | 1164 ++++++++++++++++++++++++++++++++++++++
modules/geogen.js | 812 ++++++++++++++++++++++++++
modules/save-and-load.js | 42 ++
modules/ui/general.js | 2 +
modules/ui/options.js | 2 +
7 files changed, 2056 insertions(+)
create mode 100644 .gitignore
create mode 100644 libs/NDSFutility.js
create mode 100644 modules/geogen.js
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..417f7d39
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# =========================
+# Operating System Files
+# =========================
+
+
+# =========================
+# Editor Temp Files
+# =========================
+
+.vs/
diff --git a/index.html b/index.html
index 5fc64660..8ae53f3b 100644
--- a/index.html
+++ b/index.html
@@ -1078,6 +1078,8 @@
@@ -2006,6 +2008,7 @@
+
@@ -2032,6 +2035,7 @@
+
+//
+// 3. BE SURE to call resizingWindowEndOfBody() before you
+// close your element:
+//
+//
+//
+//
+// And that's all it takes!
+//
+// WARNINGS:
+//
+// 1. In my tests, the very latest version of Opera doesn't allow
+// JavaScript to resize the browser window AT ALL, even if the
+// window resizing option is enabled under
+// Tools->Advanced->JavaScript Options. There's not much to
+// be done about that. However the code should work correctly if
+// your copy of Opera does allow resizing. Note that there is
+// also a small fudge factor to allow for a vertical scrollbar in
+// Opera, because Opera is the only browser that can't be
+// convinced to report the true interior usable space not wasted
+// by a scrollbar, and we never, ever want to force a
+// horizontal scrollbar unnecessarily.
+//
+// 2. Users with JavaScript disabled won't get the resizing behavior.
+// Hey, there's no miracle cure for that! Design your page layout to
+// cope adequately if the browser window is not the expected size.
+
+function resizingWindowIsIE()
+{
+ if (navigator.appName == 'Microsoft Internet Explorer') {
+ return true;
+ }
+ return false;
+}
+
+function resizingWindowIsOpera()
+{
+ if (navigator.appName == 'Opera') {
+ return true;
+ }
+ return false;
+}
+
+// We resize a maximum of three times. This allows
+// the code to try to resolve any boundary conditions,
+// such as scrollbars appearing or disappearing,
+// in the browser's reaction to the first resize - but
+// also prevents an infinite loop.
+
+var resizingWindowMaxResizes = 3;
+var resizingWindowResizes = 0;
+
+var dwidth;
+var dheight;
+
+function resizingWindowLoaded(width, height)
+{
+ dwidth = width;
+ dheight = height;
+ resizingWindowResizes = 0;
+ resizingWindowGo();
+}
+
+function resizingWindowEndOfBody()
+{
+ document.write("\n");
+}
+
+function resizingWindowResized()
+{
+ resizingWindowGo();
+}
+
+function resizingWindowGo()
+{
+ // We're in "standards mode," so we must use
+ // document.documentElement, not document.body, in IE.
+ var width;
+ var height;
+ var x, y, w, h;
+ if (resizingWindowResizes == resizingWindowMaxResizes) {
+ return;
+ }
+ resizingWindowResizes++;
+ // Get browser window inner dimensions
+ if (resizingWindowIsIE()) {
+ // All modern versions of IE, including 7, give the
+ // usable page dimensions here.
+ width = parseInt(document.documentElement.clientWidth);
+ height = parseInt(document.documentElement.clientHeight);
+ } else if (resizingWindowIsOpera()) {
+ // This is slightly off: the width and height will include
+ // scrollbar space we can't really use. Compensate by
+ // subtracting 16 pixels of scrollbar space from the width
+ // (standard in Opera). Fortunately, in Firefox and Safari,
+ // we can use a third method that gives accurate results
+ // (see below).
+ width = parseInt(window.innerWidth) - 16;
+ // If there is a horizontal scrollbar this will be
+ // 16 pixels off in Opera. I can live with that.
+ // You don't design layouts on purpose with
+ // horizontal scrollbars, do you? (Shudder)
+ height = parseInt(window.innerHeight);
+ } else {
+ // Other non-IE browsers give the usable page dimensions here.
+ // We grab the info by discovering the visible dimensions
+ // of a hidden 100% x 100% div. Opera doesn't like this
+ // method any more than IE does. Fun!
+ testsize = document.getElementById('resizingWindowTestSizeDiv');
+ width = testsize.scrollWidth;
+ height = testsize.scrollHeight;
+ }
+ // Compute the difference and add or subtract
+ // space as required. Notice that we don't have to
+ // know the dimensions of the toolbar, status bar, etc.
+ // All we have to do is make a relative adjustment.
+ if ((dwidth == width) && (dheight == height)) {
+ // Don't resize anymore now that it's right!
+ // We don't want to interfere with manual resize
+ resizingWindowResizes = resizingWindowMaxResizes;
+ return;
+ }
+ var xchange = dwidth - width;
+ var ychange = dheight - height;
+ window.resizeBy(xchange, ychange);
+}
+
+
+
+
diff --git a/modules/geogen.js b/modules/geogen.js
new file mode 100644
index 00000000..20bc8a5d
--- /dev/null
+++ b/modules/geogen.js
@@ -0,0 +1,812 @@
+units = { //units, their name and conversion ratio to Kilometers
+ "mm": { name: "Millimeters", conv: 1000000 },
+ "cm": { name: "Centimeters", conv: 100000 },
+ "m": { name: "Meters", conv: 1000 },
+ "km": { name: "Kilometers", conv: 1 },
+ "in": { name: "Inches", conv: 39370.07874 },
+ "ft": { name: "Feet", conv: 3280.839895 },
+ "yd": { name: "Yards", conv: 1093.613298 },
+ "mi": { name: "Miles", conv: 0.621371192 },
+ "nmi": { name: "Nautical Miles", conv: 0.539956803 },
+ "lg": { name: "Leagues", conv: 0.207123730 }, //this is just one possible value, meaning of league differs majorly
+ "vr": { name: "Versta", conv: 0.937382827 },
+}
+const type_names = {
+ "continent": ["Acai", "Adroya", "Aehesus", "Aeplaes", "Aetiveth", "Afeutoris", "Ahera", "Ahux", "Aifin", "Aipodux", "Ames", "Areth", "Aseugeon", "Atane", "Aufokica", "Auluqoth", "Aupicul", "Auquan", "Aweapari", "Baeyis",
+ "Beawath", "Blateron", "Bleumebela", "Blifin", "Braiyias", "Breizoth", "Brezoqall", "Bruanosia", "Buadrea", "Ceiphizuan", "Cewriograi", "Chaqon", "Chialuin", "Ciclone", "Cleikoxoa", "Cleiyezand", "Cleobuxune", "Clipuzoya",
+ "Clowos", "Cluamisoa", "Clulath", "Codni", "Cowane", "Cracend", "Cuphiashoth", "Cusux", "Deblas", "Dreasuin", "Dreozai", "Edax", "Eduqith", "Eheiqeon", "Ehox", "Eifugias", "Eilolish", "Eiyuwax", "Eizunai", "Ekox", "Eleuvax",
+ "Eobekaes", "Eodraes", "Eolovin", "Eqoqor", "Eseizela", "Esune", "Etroa", "Eubrax", "Euclax", "Eufuxura", "Eukolaes", "Euweon", "Feusuin", "Fliutazias", "Flucone", "Fuazath", "Geiwis", "Gliuboya", "Grigeucax", "Gruacitane",
+ "Guashushea", "Helane", "Holira", "Hoqish", "Iapath", "Iawaqul", "Iboth", "Idia", "Ipeth", "Iqall", "Irend", "Iuqenax", "Iuzoa", "Iwor", "Iworis", "Jaepixari", "Jaukroran", "Jichul", "Jubrios", "Kaphox", "Kasuin", "Kerura",
+ "Klikesh", "Kliocane", "Klumane", "Kraeputul", "Krahonan", "Krainura", "Kreomixias", "Krereudas", "Krifera", "Kuadox", "Kuyiocroa", "Limux", "Lopall", "Miozegrera", "Nauphuan", "Niudrica", "Nodeayuin", "Nophea", "Nuawasoa",
+ "Odon", "Odrus", "Ogane", "Okaecox", "Oqai", "Osesh", "Osune", "Oyune", "Peoclosend", "Phuwuan", "Pleawos", "Pleidrand", "Prerai", "Priulone", "Qeawagaes", "Qeiqastrea", "Qephiasura", "Qiagreon", "Qiopath", "Reyath", "Riaploa",
+ "Sainacas", "Sateron", "Serihoya", "Shonios", "Shuaditish", "Shuyun", "Slaedreth", "Slaeqabios", "Slicen", "Sliperon", "Sluraetuan", "Slutax", "Suchos", "Tegreinane", "Teophiduin", "Tibrura", "Tiotavoth", "Tizeudia",
+ "Trepeocai", "Treutoya", "Triqos", "Uabroris", "Uahakia", "Uawiwios", "Ubenios", "Uhios", "Upiozan", "Urai", "Uwesux", "Uwush", "Vledath", "Vleowiyush", "Vlifica", "Vragrux", "Vraikequth", "Vuyiagren", "Waiqesh", "Wetrune",
+ "Witrari", "Wreikucix", "Wrugith", "Xearutuin", "Xidren", "Xohix", "Xualuvax", "Yaqox", "Yegos", "Yeustror", "Yucrari", "Zaeshela", "Zaiplecaes", "Zeamuin", "Zephush"],
+ "isle": ["Amtara Reef", "Balcaster Island", "Belleby Enclave", "Berksea Island", "Beversby Isle", "Birmingtague Ait", "Birtown Key", "Bolnach Haven", "Brightside Ait", "Brismark Islet", "Briswater Islet",
+ "Brommer Chain", "Calenigan Isle", "Campbo Skerry", "Camptague Island", "Canterliers Enclave", "Cartwater Peninsula", "Casneau Holm", "Cauborough Islands", "Chiboubron Haven", "Chide Islands", "Clarenside Chain",
+ "Cliffsomin Ait", "Cumberchester Chain", "Derwin Isles", "Digwood Cay", "Eastgue Isles", "Eatowe Atoll", "Elcola Key", "Emgough Key", "Fairdown Enclave", "Gamsons Isle", "Ganmond Haven", "Gatileche Isle",
+ "Gilminster Holm", "Glenheim Archipelago", "Gracewaki Reef", "Granhead Isles", "Hadway Peninsula", "Hamliers Reef", "Haspids Enclave", "Hillsline Cay", "Hingchester Isle", "Irochill Islet", "Irridown Ait",
+ "Killinggamau Cay", "Killingside Cay", "Kirlet Island", "Langbiens Islet", "Lashcana Island", "Leamingshaw Cay", "Limingnear Chain", "Menbour Atoll", "Milburns Refuge", "Miniris Cay", "Morintane Isle", "Petasack Holm",
+ "Portgeo Archipelago", "Portterel Island", "Reidby Isles", "Reidfail Isles", "Repenmark Island", "Robmont Island", "Saglodge Holm", "Salisgeo Reef", "Sedgeree Skerry", "Shawris Archipelago", "Stafcouche Cay",
+ "Staflams Skerry", "Stokemiota Refuge", "Susduff Ait", "Susleche Refuge", "Taunnet Islet", "The Arching Isle", "The Arid Islands", "The Burning Isles", "The Castaway Islet", "The Colossal Key", "The Deep Skerry",
+ "The Defeated Haven", "The Diamond Isle", "The Distant Ait", "The Dread Atoll", "The Ethereal Enclave", "The Faraway Enclave", "The Fiery Holm", "The Fiery Island", "The Flowing Isles", "The Frozen Peninsula",
+ "The Glowing Islet", "The Grim Reef", "The Heartless Haven", "The Hollow Islet", "The Jellyfish Holm", "The Laughing Ait", "The Light Chain", "The Lost Enclave", "The Mysterious Archipelago", "The Mysterious Key",
+ "The Pain Key", "The Peaceful Haven", "The Pearl Chain", "The Penguin Refuge", "The Raging Archipelago", "The Relentless Chain", "The Sad Peninsula", "The Sanctum Chain", "The Shadowed Ait", "The Shaking Isles",
+ "The Shark Enclave", "The Shrine Cay", "The Silver Peninsula", "The Skeleton Ait", "The Skeleton Cay", "The Skeleton Refuge", "The Starfish Haven", "The Sunny Isles", "The Torpedo Island", "The Virgin Skerry",
+ "The Wasting Ait", "The Waterless Skerry", "The Waveless Isle", "The Windy Island", "The Yelling Ait", "Tisliers Island", "Turgonie Chain", "Ventmiota Isles", "Virworth Peninsula", "Wallsevain Refuge", "Wilwin Reef",
+ "Windminster Islet", "Wynrial Chain"],
+ "island": ["Lighthill", "Strongmill", "Whitebridge", "Woodwall", "Deepsnow", "Irondell", "Eriwynne", "Fairwinter", "Deepwater", "Waterton", "Fallmage", "Rosedell", "Westerdragon", "Oldfield", "Morton", "Ironshade", "Merridale",
+ "Westcliff", "Flowercliff", "Vertkeep", "Clearmont", "Witchlyn", "Deerdell", "Hollowcastle", "Janwynne", "Clearden", "Oldburn", "Flowermeadow", "Linmoor", "Westerbeach", "Deepwolf", "Oakgate", "Southgrass", "Moormeadow",
+ "Qauar", "Jipon", "Venela", "Martique", "Ugada", "Maurania", "Turnistan", "Russip", "Conada", "Svalbard", "Jan Mayen", "Mari Island", "Angua", "Baruda", "Monolia", "Ameri", "Samoa"],
+ "freshwater": ["Shaded Lake", "Vast Expanse", "Neglected Gorge", "Glistening Loch", "Blythedows Lake", "Chamterre Lake", "Winterlan Gorge", "Wadelem Loch", "Ridgelis Expanse", "Bradbron Pond", "Calm Lake", "Mirrored Waters",
+ "Unstable Basin", "Barren Reservoir", "Buchstino Lagoon", "Ferquet Expanse", "Cardpids Reservoir", "Gatilin Cove", "Bloomslet Expanse", "Antitos Cove", "Western Waters", "Pleasant Depths", "Vast Reservoir",
+ "Peaceful Reservoir", "Harvern Basin", "Limingmeda Depths", "Wellingronto Lagoon", "Causahead Gorge", "Warming Reservoir", "Limingside Reservoir", "New Basin", "Cursed Basin", "Crystal Waters", "Ugly Lagoon",
+ "Croyville Pond", "Landare Lake", "Franram Loch", "Hillsval Domain", "Smithrial Basin", "Suntrie Gorge", "Serene Lagoon", "Cobalt Loch", "Arrowhead Lake", "Iris Lagoon", "Manirood Shallows", "Appleware Shallows",
+ "Buckinglam Basin", "Amespids Waters", "Gaulden Pond", "Margar Lake", "Coral Depths", "Boiling Gorge", "Peaceful Waters", "Flowing Shallows", "Walhurst Domain", "Wolffail Basin", "Stratsevain Lagoon", "Davellin Lake",
+ "Morinneau Cove", "Huntbalt Expanse"],
+ "salt": ["Gray Domain", "Wasor Basin", "Orocastle Lake", "Cowantrie Shallows", "Belldare Waters", "Bruderley Gorge", "Eckpond Cove", "Eastern Cove", "Coral Pond", "Shaded Waters", "Crocodile Depths", "Cottlecam Lake",
+ "Midalants Lake", "Onofolk Reservoir", "Herewin Depths", "Grimrial Waters", "Sherden Lagoon", "Wasteful Shallows", "Cursed Reservoir", "Uncanny Pond", "Wrinkled Cove", "Rossoll Lagoon", "Huntingpond Basin",
+ "Grandburn Waters", "Barnrey Domain", "Stokeleche Pond", "Kapusgus Cove", "Furthest Gorge", "Hungry Expanse", "Cursed Domain", "Narrow Depths", "Corngan Domain", "Estou Pond", "Bridgediac Loch", "Liverbiens Gorge",
+ "Burstry Waters", "Caubiens Waters"]
+}
+
+
+
+
+
+const natural_types =
+{
+ land: {
+ area: [
+ "scrub", "heath", "moor", "wetland", "grassland", "fell", "bare_rock", "scree", "shingle", "sand", "mud", "cave_entrance", "sinkhole", "rock", "hill", "valley", "peninsula"
+ ], features: [
+ "wood", "tree_row", "tree"
+ ], points: [
+ "volcano", "cape", "peak", "spring", "hot_spring", "geyser", "stone ", "saddle", "tree", "cave_entrance", "sinkhole", "rock"
+ ], lines: [
+ "river_terrace", "ridge", "arete", "cliff"
+ ]
+ }, water: {
+ area: [
+ "water", "glacier", "bay", "strait", "beach", "reef"
+ ], lines: [
+ "coastline"
+ ]
+ }
+}
+
+
+burgs_groups = [
+ { type: "city", pop: 100000 },
+ { type: "town", pop: 2000 },
+ { type: "village", pop: 100 },
+ { type: "hamlet", pop: 10 },
+ { type: "isolated_dwelling", pop: -1 }
+]
+
+numRegExp = new RegExp("[0-9]+");
+
+const ln = "\r\n";
+
+const cen_lat = 51.50265085;
+const cen_lon = -3.16268235;
+
+const rotation_angle_degs = 1;
+
+
+// using the data on
+// https://en.wikipedia.org/wiki/List_of_United_States_cities_by_area
+// the average of the square meter per person in city cant to 46.4906806
+avg_sqm_pp = 46.5;
+
+function gen_geodata(type) {
+
+
+ let data = "";
+ let ext = "";
+ let dset = {};
+
+ //work out the scale in the units chosen by using the conversion ratio times by 1000 for meters
+ const scale = (distanceScale.value * unit_to_km(distanceUnit.value)) * 1000;
+ console.log(scale + " meters per pixel");
+
+ dset.settlements = dataget_settlements();
+ dset.points = dataget_points();
+ dset.states = dataget_states();
+ dset.coastlines = dataget_bodies("#coastline > *");
+ dset.lakes = dataget_bodies("#freshwater > *, #salt > *");
+
+ if (type === "osm") {
+ data = gen_osm(dset, scale);
+ ext = ".osm";
+ } else if (type === "json") {
+ data = gen_json(dset, scale);
+ ext = ".geo.json";
+ }
+ return { data: data, ext: ext }
+}
+
+function dataget_settlements() {
+ let settlements = [];
+ for (let i = 0, c = 0; i < pack.burgs.length; i++) {
+ let burg = pack.burgs[i];
+ let type = "";
+ if ("i" in burg) {
+ settlements[c] = burg;
+ //replace population with actual value, scale * 1000
+ pop = settlements[c].population * populationRate.value * 100;
+ settlements[c].population = pop;
+ for (let t = 0; t < burgs_groups.length; t++) {
+ if (pop > burgs_groups[t].pop) {
+ type = burgs_groups[t].type;
+ break;
+ }
+ }
+ settlements[c].type = type;
+ burgs_groups
+ c++;
+ }
+ }
+ return settlements;
+}
+
+function dataget_points() {
+ let points = [];
+ for (let i = 0, c = 0; i < grid.points.length; i++) {
+ let point = grid.points[i];
+ if (point.length > 0) {
+ points[c] = point;
+ c++;
+ }
+ }
+ return points;
+}
+
+function dataget_states() {
+ statesCollectStatistics();
+ const node_states = document.getElementById("statesHalo").childNodes;
+ let states = [];
+ for (var i = 0; i < node_states.length; i++) {
+ states[i] = pack.states[i];
+ if (states[i].center) {
+ states[i].x = pack.cells.p[pack.states[i].center][0];
+ states[i].y = pack.cells.p[pack.states[i].center][1];
+ }
+ states[i].path = node_states[i];
+ }
+ return states;
+
+ function statesCollectStatistics() {
+ const cells = pack.cells, states = pack.states;
+ states.forEach(s => s.cells = s.area = s.burgs = s.rural = s.urban = 0);
+
+ for (const i of cells.i) {
+ if (cells.h[i] < 20) continue;
+ const s = cells.state[i];
+ states[s].cells += 1;
+ states[s].area += cells.area[i];
+ states[s].rural += cells.pop[i];
+ if (cells.burg[i]) {
+ states[s].urban += pack.burgs[cells.burg[i]].population;
+ states[s].burgs++;
+ }
+ }
+ }
+}
+
+function dataget_bodies(query) {
+ let nodes = document.querySelectorAll(query);
+ let bodies = [];
+ for (var i = 0; i < nodes.length; i++) {
+ let match = nodes[i].id.match(numRegExp);
+ let f_id = Number(match[0]);
+ if (f_id in pack.features) {
+ bodies[i] = pack.features[f_id];
+ if (type_names[bodies[i].group] === undefined) {
+ console.log(bodies[i]);
+ }
+ bodies["name"] = type_names[bodies[i].group][i];
+ bodies[i].path = nodes[i];
+ } else {
+ console.log("no feature by with id: " + f_id);
+ }
+ }
+ return bodies;
+}
+
+function gen_osm(data, scale) {
+ let cur_id = 1;
+
+ let mpoints = osm_settlements_to_features(data.settlements, cur_id, scale);
+ cur_id = mpoints.last_id;
+ let spoints = osm_states_to_features(data.states, cur_id, scale);
+ cur_id = spoints.last_id;
+ let cpoints = osm_coastlines_to_features(data.coastlines, cur_id, scale);
+ cur_id = cpoints.last_id;
+ let lpoints = osm_lakes_to_features(data.lakes, cur_id, scale);
+ cur_id = lpoints.last_id;
+
+ let osm = "" + ln;
+ osm += "" + ln;
+ osm += mpoints.nodes;
+ osm += spoints.nodes;
+ osm += cpoints.nodes;
+ osm += lpoints.nodes;
+ osm += mpoints.ways
+ osm += spoints.ways;
+ osm += cpoints.ways;
+ osm += lpoints.ways;
+ osm += mpoints.relations
+ osm += spoints.relations;
+ osm += cpoints.relations;
+ osm += lpoints.relations;
+ osm += "";
+
+ return osm;
+}
+
+function gen_json(data, scale) {
+ let cur_id = 1;
+ let features = [];
+ //fpoints = json_points_to_features(data.points, cur_id, scale);
+ //cur_id = fpoints.last_id;
+ let mpoints = json_settlements_to_features(data.settlements, cur_id, scale);
+ cur_id = mpoints.last_id;
+ let spoints = json_states_to_features(data.states, cur_id, scale);
+ cur_id = spoints.last_id;
+ let cpoints = json_coastlines_to_features(data.coastlines, cur_id, scale);
+ cur_id = cpoints.last_id;
+ let lpoints = json_lakes_to_features(data.lakes, cur_id, scale);
+ cur_id = lpoints.last_id;
+
+ //features = features.concat(fpoints.json);
+ features = features.concat(mpoints.json);
+ features = features.concat(spoints.json);
+ features = features.concat(cpoints.json);
+ features = features.concat(lpoints.json);
+
+ const json_data = { "type": "FeatureCollection", "features": features };
+
+ return JSON.stringify(json_data, null, 2);
+}
+
+
+//--------------osm functions-------------------
+
+function osm_settlements_to_features(data, id, scale) {
+ let nodes = "";
+ let ways = "";
+ let relations = "";
+ let tagcats = {};
+ for (let i = 0; i < data.length; i++) {
+ let members = [];
+ id++;
+ boundry = xy_to_boundry(data[i].x, data[i].y, data[i].population, scale);
+ start_id = id;
+
+ nodes += latlongs_to_nodes(boundry, id);
+ id += boundry.length + 1;
+ tagcats = {
+ type: "boundry", boundary: "administrative", admin_level: "6",
+ name: data[i].name
+ };
+ tags = gen_tags(tagcats);
+ ways += gen_way(id, start_id, boundry.length, tags);
+ members.push({ id: id, type: "way", role: "outer" });
+ id += boundry.length + 1;
+ start_id = id;
+ tagcats = {
+ type: "place", place: data[i].type, name: data[i].name,
+ cell: data[i].cell, region: data[i].region, area: (Math.PI * data.population ^ 2), culture: data[i].culture,
+ population: data[i].population, feature: data[i].feature,
+ capital: data[i].capital, port: data[i].port, settlement_id: data[i].i, x: data[i].x, y: data[i].y
+ };
+ tags = gen_tags(tagcats);
+ nodes += latlong_to_node(xy_to_coords(data[i].x * scale, data[i].y * scale), id, tags);
+ members.push({ id: id, type: "node", role: "admin_centre" });
+ id++;
+
+ tagcats = {
+ type: "boundry", boundary: "administrative", border_type: "city", admin_level: "6", designation: "principal_area",
+ name: data[i].name, long_name: data[i].long_name, "is_in:country": data[i].country, "is_in:country_code": data[i].country_code,
+ cell: data[i].cell, region: data[i].region, area: (Math.PI * data.population ^ 2), culture: data[i].culture,
+ population: data[i].population, feature: data[i].feature,
+ capital: data[i].capital, port: data[i].port, settlement_id: data[i].i, x: data[i].x, y: data[i].y
+ };
+ tags = gen_tags(tagcats);
+ relations += gen_relation(id++, tags, members);
+ }
+ return { nodes: nodes, ways: ways, relations: relations, last_id: id };
+}
+
+function osm_states_to_features(data, id, scale) {
+ let nodes = "";
+ let ways = "";
+ let relations = "";
+ let tagcats = {};
+ let tags = "";
+ for (let i = 0; i < data.length; i++) {
+ let boundries = flatten_multi_svg(data[i].path, 1000);
+
+ let members = [];
+ for (let b = 0; b < boundries.length; b++) {
+ if (boundries[b].length > 0) {
+ id++;
+
+ start_id = id;
+ tagcats = {
+ type: "place", place: "region",
+ region_id: b
+ };
+ let region_tags = gen_tags(tagcats);
+ nodes += latlongs_to_nodes(xypath_to_latlong(boundries[b], scale), id);
+ id += boundries[b].length + 1;
+ members.push({ id: id, type: "way", role: "outer" });
+ ways += gen_way(id, start_id, boundries[b].length, region_tags);
+ }
+ }
+ if (data[i].center) {
+ id++;
+ tagcats = {
+ type: "place", place: "country", name: data[i].name,
+ cell: data[i].cell, region: data[i].region, area: (Math.PI * data.population ^ 2), culture: data[i].culture,
+ x: data[i].x, y: data[i].y, center: data[i].center, state_id: data[i].i
+ };
+
+ tags = gen_tags(tagcats);
+ nodes += latlong_to_node(xy_to_coords(data[i].x * scale, data[i].y * scale), id, tags);
+ members.push({ id: id, type: "node", role: "admin_centre" });
+ id++;
+ }
+ tagcats = {
+ type: "multipolygon", boundary: "administrative", border_type: "city", admin_level: "6", designation: "principal_area",
+ name: data[i].name, long_name: data[i].long_name, "country": data[i].name, "country_code": data[i].i,
+
+ state_id: data[i].i, color: data[i].color, expansionism: data[i].expansionism, capital: data[i].capital,
+ state_type: data[i].type, center: data[i].center, culture: data[i].culture, cells: data[i].cells, area: data[i].area,
+ rural: data[i].rural, urban: data[i].urban, cities: data[i].burgs, x: data[i].x, y: data[i].y
+ };
+ tags = gen_tags(tagcats);
+ relations += gen_relation(id++, tags, members)
+
+ }
+ return { nodes: nodes, ways: ways, relations: relations, last_id: id };
+}
+
+function osm_coastlines_to_features(data, id, scale) {
+ let nodes = "";
+ let ways = "";
+ let relations = "";
+ let tagcats = {};
+ for (let i = 0; i < data.length; i++) {
+ let members = [];
+ id++;
+ boundry = xypath_to_latlong(flatten_svg(data[i].path, 1000), scale);
+ start_id = id;
+ nodes += latlongs_to_nodes(boundry, id);
+ id += boundry.length + 1;
+ tagcats = {
+ type: "natural", natural: "coastline", feature_id: data[i].i
+ };
+ tags = gen_tags(tagcats);
+ ways += gen_way(id, start_id, boundry.length, tags);
+ members.push({ id: id, type: "way", role: "outer" });
+ id++
+ let type = natural_types.land.area[Math.floor((Math.random() * natural_types.land.area.length))];
+ tagcats = {
+ type: "natural", natural: type, feature_id: data[i].i
+ };
+ tags = gen_tags(tagcats);
+
+ ways += gen_way(id, start_id, boundry.length, tags);
+ members.push({ id: id, type: "way", role: "outer" });
+
+ let name = type_names[data[i].group][i];
+ tagcats = {
+ type: "place", place: data[i].group,
+ feature_id: data[i].i, name: name, body: data[i].body, border: data[i].border,
+ cells: data[i].cells, land: data[i].land, mtype: data[i].type
+ };
+ tags = gen_tags(tagcats);
+
+ relations += gen_relation(id++, tags, members);
+
+
+ }
+ return { nodes: nodes, ways: ways, relations: relations, last_id: id };
+}
+
+function osm_lakes_to_features(data, id, scale) {
+ let nodes = "";
+ let ways = "";
+ let relations = "";
+ let tagcats = {};
+ for (let i = 0; i < data.length; i++) {
+ id++;
+ boundry = xypath_to_latlong(flatten_svg(data[i].path, 1000), scale);
+ start_id = id;
+ nodes += latlongs_to_nodes(boundry, id);
+ id += boundry.length + 1;
+ let name = type_names[data[i].group][i];
+ tagcats = {
+ type: "natural", natural: "water", water: "lake",
+ name: name, body: data[i].body, border: data[i].border,
+ cells: data[i].cells, land: data[i].land, mtype: data[i].mtype,
+ feature_id: data[i].i
+ };
+ if (data.group === "salt") {
+ tagcats.salt = "yes";
+ }
+ tags = gen_tags(tagcats);
+ ways += gen_way(id, start_id, boundry.length, tags);
+ id++;
+ }
+ return { nodes: nodes, ways: ways, relations: relations, last_id: id };
+}
+
+//--------------osm functions-------------------
+
+function gen_way(way_id, start_id, num, tags) {
+ way = "\t" + ln;
+
+ way += gen_nd(start_id, num);
+ way += tags;
+ way += "\t" + ln;
+ return way;
+}
+
+function gen_relation(id, tags, members, tagcats) {
+ relation = "";
+ relation += "\t" + ln;
+ relation += gen_members(members);
+ relation += tags;
+ relation += "\t" + ln;
+ return relation;
+}
+
+function gen_members(ids) {
+ members = "";
+ for (i = 0; i < ids.length; ++i) {
+ members += "\t\t" + ln;
+ }
+ return members;
+}
+
+function gen_nd(id, num) {
+ last_nd = "\t\t" + ln;
+ nd = "";
+ for (i = 0; i < num; ++i) {
+ nd += "\t\t" + ln;
+ }
+ nd += last_nd;
+ return nd;
+}
+
+function latlongs_to_nodes(latlongs, id) {
+ nodes = "";
+ for (k in latlongs) {
+ ll = latlongs[k];
+ nodes += "\t" + ln;
+
+ }
+ return nodes;
+}
+
+function latlong_to_node(ll, id, tags) {
+ node = "";
+ node += "\t" + ln;
+ node += tags;
+ node += "\t" + ln;
+ return node;
+}
+
+
+function gen_tags(tagcats) {
+ let tags = "";
+ const blk = "Unimplemented";
+ for (k in tagcats) {
+ tags += "\t\t" + ln;
+ }
+ //tags += "\t\t" + ln;
+ //tags += "\t\t" + ln;
+ return tags;
+}
+
+//--------------json functions-------------------
+
+function json_settlements_to_features(data, id, scale) {
+ allPoints = [];
+ for (let i = 0; i < data.length; i++) {
+ id++;
+
+ pop = data[i].population;
+ let bound = xy_to_boundry(data[i].x, data[i].y, pop, scale);
+ let polygon = latlongs_to_polygon(bound);
+ allPoints[i] = {
+ "type": "Feature",
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [polygon]
+ },
+ "properties": {
+ "name": data[i].name, "settlement_id": data[i].i, "feature": data[i].feature, "capital": data[i].capital, "port": data[i].port, "AREA_CODE": data[i].cell, "POLYGON_ID": id, "UNIT_ID": data[i].region, "HECTARES": (Math.PI * pop ^ 2), "DESCRIPT0": "CIVIL ADMINISTRATION AREA", "CULTURE": data[i].culture, "POPULATION": pop
+ }
+ }
+ }
+ return { json: allPoints, last_id: id };
+
+}
+
+function json_states_to_features(data, id, scale) {
+ allPoints = [];
+ for (let i = 0; i < data.length; i++) {
+ id++;
+
+ pop = data[i].population;
+ let boundries = flatten_multi_svg(data[i].path, 1000);
+ let multiPolygon = []
+ for (let b = 1; b < boundries.length; b++) {
+ multiPolygon[b] = latlongs_to_polygon(xypath_to_latlong(boundries[b], scale));
+ }
+ allPoints[i] = {
+ "type": "Feature",
+ "geometry": {
+ "type": "MultiPolygon",
+ "coordinates": [multiPolygon]
+ },
+ "properties": {
+ "name": data[i].name, "state_id": data[i].i, "feature": data[i].feature, "capital": data[i].capital, "port": data[i].port, "AREA_CODE": data[i].cell, "POLYGON_ID": id, "UNIT_ID": data[i].region, "HECTARES": (Math.PI * pop ^ 2), "DESCRIPT0": "CIVIL ADMINISTRATION AREA", "CULTURE": data[i].culture, "POPULATION": pop
+ }
+ }
+ }
+ return { json: allPoints, last_id: id };
+
+}
+
+function json_coastlines_to_features(data, id, scale) {
+ allPoints = [];
+ for (let i = 0; i < data.length; i++) {
+ id++;
+
+ pop = data[i].population;
+ let bound = flatten_svg(data[i].path, 1000);
+ let polygon = latlongs_to_polygon(xypath_to_latlong(bound, scale));
+ allPoints[i] = {
+ "type": "Feature",
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [polygon]
+ },
+ "properties": {
+ "name": data[i].name, "feature_id": data[i].i, "feature": data[i].feature, "capital": data[i].capital, "port": data[i].port, "AREA_CODE": data[i].cell, "POLYGON_ID": id, "UNIT_ID": data[i].region, "HECTARES": (Math.PI * pop ^ 2), "DESCRIPT0": "CIVIL ADMINISTRATION AREA", "CULTURE": data[i].culture, "POPULATION": pop
+ }
+ }
+ }
+ return { json: allPoints, last_id: id };
+
+}
+
+function json_points_to_features(data, id, scale) {
+ allPoints = [];
+ for (let i = 0; i < data.length; i++) {
+ id++;
+ [x, y] = data[i];
+ ll = xy_to_coords(x * scale, y * scale);
+ allPoints[c] = {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [ll.slon, ll.slat] //geojson is [lon,lat]
+ },
+ "properties": {
+ "name": "Point: " + (c + 1),
+ "POINT_ID": id
+ }
+ }
+ }
+ return { json: allPoints, last_id: id };
+}
+
+//--------------json functions-------------------
+
+function gen_settlements_props(data, it) {
+ let props = {
+ "name": data[i].name,
+ "settlement_id": data[i].i,
+ "feature": data[i].feature,
+ "capital": data[i].capital,
+ "port": data[i].port,
+ "AREA_CODE": data[i].cell,
+ "POLYGON_ID": it,
+ "UNIT_ID": data[i].region,
+ "HECTARES": (Math.PI * data.population ^ 2),
+ "DESCRIPT0": "CIVIL ADMINISTRATION AREA",
+ "CULTURE": data[i].culture,
+ "POPULATION": data.population
+ }
+ return props;
+}
+
+function gen_state_props(data, it) {
+ let props = {
+ "name": data.name,
+ "state_id": data.i,
+ "color": data.color,
+ "capital": data.capital,
+ "expansionism": data.expansionism,
+ "type": data.type,
+ "expansionism": data.expansionism,
+ "center": data.center,
+ "culture": data.culture,
+ "cells": data.cells,
+ "area": data.area,
+ "rural": data.rural,
+ "urban": data.urban,
+ "cities": data.burgs,
+ "AREA_CODE": data.cell,
+ "POLYGON_ID": it,
+ "HECTARES": (Math.PI * data.population ^ 2),
+ "DESCRIPT0": "CIVIL ADMINISTRATION AREA",
+ "POPULATION": data.population
+ }
+ return props;
+}
+
+function gen_coastline_props(data) {
+ let props = {
+ "name": data.name,
+ "state_id": data.i,
+ }
+ return props;
+}
+
+//--------------generic functions-------------------
+
+function flatten_multi_svg(path, num) {
+ const len = path.getTotalLength();
+ const elem = path.pathSegList.numberOfItems;
+ var paths = [];
+ var nums = 0;
+ var poly = 0;
+ paths[poly] = [];
+ for (var i = 0; i < elem; i++) {
+ item = path.pathSegList.getItem(i);
+ if (item.pathSegType === 2) {
+ if (poly > 0 && i > 0) {
+ let fp = paths[poly][0];
+ paths[poly][nums] = { x: fp.x, y: fp.y };
+ }
+ poly++;
+ paths[poly] = [];
+ nums = 0;
+ }
+ paths[poly][nums] = { x: item.x, y: item.y };
+ nums++;
+ }
+ return paths;
+}
+
+function flatten_multi_svg2(path, num) {
+ var len = path.getTotalLength();
+ var d = [];
+ var c = 0;
+ var poly = 0;
+ poly++;
+ c = 0;
+ d[poly] = [];
+ p = path.getPointAtLength(0);
+ d[poly][c] = { x: p.x, y: p.y };
+ incr = (len / num);
+
+ for (var i = incr; i <= len; i += incr) {
+ item = path.getPathSegAtLength(i);
+ switch (item.pathSegType) {
+ case 2:
+ poly++;
+ c = 0;
+ d[poly] = [];
+ d[poly][c] = { x: item.x, y: item.y };
+ case 4:
+ var skip = "";
+ }
+ p = path.getPointAtLength(i);
+ d[poly][c] = { x: p.x, y: p.y };
+ c++;
+ }
+ return d;
+}
+
+function flatten_svg(path, num) {
+ var len = path.getTotalLength();
+ var d = [];
+ var c = 0;
+ c = 0;
+ d = [];
+ p = path.getPointAtLength(0);
+ d[c] = { x: p.x, y: p.y };
+ //d[poly] = [];
+ //var p = path.getPointAtLength(0);
+ //d[poly][c] = {x:p.x, y:p.y};
+ incr = (len / num);
+
+ for (var i = incr; i <= len; i += incr) {
+ p = path.getPointAtLength(i);
+ d[c] = { x: p.x, y: p.y };
+ c++;
+ }
+ d[c] = { x: d[0].x, y: d[0].y };
+ return d;
+}
+
+
+
+function unit_to_km(unit) {
+ conv = 1;
+ name = "Unknown";
+ if (unit in units) { //ignoring custom units atm
+ name = units[unit].name;
+ conv = units[unit].conv;
+ }
+ return conv;
+}
+
+function xy_to_coords(x, y) {
+ porg = {};
+ porg.x = x;
+ porg.y = -y;
+ porg.rotation_angle_degs = rotation_angle_degs;
+ porg.xoffset_mtrs = 0;
+ porg.yoffset_mtrs = 0;
+ porg.olon = 0.1;
+ porg.olat = 0.1;
+ return translate_coordinates(1, porg);
+}
+
+function dict_to_array(dict) {
+ return Object.keys(dict).map(function (key) {
+ return dict[key];
+ })
+}
+
+function xy_to_boundry(x, y, pop, scale) {
+ let n = 20;
+
+ // The radius of circle, which area is the places's area
+ // which is worked out by place's population * average m^2 per person
+ let r = Math.sqrt((avg_sqm_pp * pop) / Math.PI);
+
+ if (r < 20) {
+ r = 20;
+ }
+ let boundry = [];
+ x *= scale;
+ y *= scale;
+ for (let i = 0; i < n; i++) {
+ x_new = x + Math.cos((2 * Math.PI / n) * i) * r;
+ y_new = y + Math.sin((2 * Math.PI / n) * i) * r;
+ boundry[i] = xy_to_coords(x_new, y_new);
+ }
+ boundry[boundry.length] = boundry[0];
+ return boundry;
+}
+
+function xypath_to_latlong(xys, scale) {
+ latlongs = []
+ for (let i = 0; i < xys.length; i++) {
+ let xy = xys[i];
+ x = xy.x * scale;
+ y = xy.y * scale;
+ latlongs[i] = xy_to_coords(x, y);
+ }
+ return latlongs;
+
+}
+
+function latlongs_to_polygon(latlongs) {
+ polygon = [];
+ for (k in latlongs) {
+ ll = latlongs[k];
+ polygon[k] = [ll.slon, ll.slat]; //geojson is [lon,lat]
+ }
+ return polygon;
+}
\ No newline at end of file
diff --git a/modules/save-and-load.js b/modules/save-and-load.js
index 6a3a1233..8998e77b 100644
--- a/modules/save-and-load.js
+++ b/modules/save-and-load.js
@@ -183,6 +183,48 @@ function saveMap() {
console.timeEnd("saveMap");
}
+// Save in a geo-format, an lan long global coordinate format
+function saveGeo(type) {
+ if (customization) {tip("Map cannot be saved when is in edit mode, please exit the mode and re-try", false, "error"); return;}
+ console.time("saveOSM");
+ const date = new Date();
+ const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
+ const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
+ const params = [version, license, dateString, seed, graphWidth, graphHeight].join("|");
+ const options = [distanceUnit.value, distanceScale.value, areaUnit.value, heightUnit.value, heightExponent.value, temperatureScale.value,
+ barSize.value, barLabel.value, barBackOpacity.value, barBackColor.value, barPosX.value, barPosY.value, populationRate.value, urbanization.value,
+ equatorOutput.value, equidistanceOutput.value, temperatureEquatorOutput.value, temperaturePoleOutput.value, precOutput.value, JSON.stringify(winds, null, 2)].join("|");
+ const coords = JSON.stringify(mapCoordinates, null, 2);
+ const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
+ const notesData = JSON.stringify(notes, null, 2);
+
+ // set transform values to default
+ svg.attr("width", graphWidth).attr("height", graphHeight);
+ const transform = d3.zoomTransform(svg.node());
+ viewbox.attr("transform", null);
+ const svg_xml = (new XMLSerializer()).serializeToString(svg.node());
+
+ let geodata = gen_geodata(type);
+
+ let data = geodata.data;
+ let ext = geodata.ext;
+
+ const dataBlob = new Blob([data], {type: "text/plain"});
+ const dataURL = window.URL.createObjectURL(dataBlob);
+ const link = document.createElement("a");
+ link.download = "fantasy_map_" + Date.now() + ext;
+ link.href = dataURL;
+ document.body.appendChild(link);
+ link.click();
+
+ // restore initial values
+ svg.attr("width", svgWidth).attr("height", svgHeight);
+ zoom.transform(svg, transform);
+
+ window.setTimeout(function() {window.URL.revokeObjectURL(dataURL);}, 2000);
+ console.timeEnd("saveOSM");
+}
+
function uploadFile(file, callback) {
console.time("loadMap");
const fileReader = new FileReader();
diff --git a/modules/ui/general.js b/modules/ui/general.js
index 6597ac15..b588f034 100644
--- a/modules/ui/general.js
+++ b/modules/ui/general.js
@@ -211,6 +211,8 @@ document.addEventListener("keydown", function(event) {
if (key === 118) regenerateMap(); // "F7" for new map
else if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
else if (key === 9) {toggleOptions(event); event.preventDefault();} // Tab to toggle options
+ else if (shift && key === 79) saveGeo("osm"); // Shift + "O" to save as OSM
+ else if (shift && key === 74) saveGeo("json"); // Shift + "J" to save as JSON
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
diff --git a/modules/ui/options.js b/modules/ui/options.js
index a9455569..55a44f09 100644
--- a/modules/ui/options.js
+++ b/modules/ui/options.js
@@ -903,6 +903,8 @@ document.getElementById("sticked").addEventListener("click", function(event) {
else if (id === "loadMap") mapToLoad.click();
else if (id === "zoomReset") resetZoom(1000);
else if (id === "saveMap") saveMap();
+ else if (id === "saveOSM") saveGeo("osm");
+ else if (id === "saveJSON") saveGeo("json");
else if (id === "saveSVG") saveAsImage("svg");
else if (id === "savePNG") saveAsImage("png");
if (id === "saveMap" || id === "saveSVG" || id === "savePNG") toggleSavePane();