import * as d3 from "d3"; import {ERROR, TIME} from "config/logging"; import {clipPoly} from "utils/lineUtils"; import {round} from "utils/stringUtils"; import {Ruler} from "modules/measurers"; // Detect and draw the coastline export function drawCoastline() { TIME && console.time("drawCoastline"); const {cells, vertices, features} = pack; const n = cells.i.length; const used = new Uint8Array(features.length); // store connected features const largestLand = d3.scan( features.map(f => (f.land ? f.cells : 0)), (a, b) => b - a ); const landMask = defs.select("#land"); const waterMask = defs.select("#water"); const lineGen = d3.line().curve(d3.curveBasisClosed); for (const i of cells.i) { const startFromEdge = !i && cells.h[i] >= 20; if (!startFromEdge && cells.t[i] !== -1 && cells.t[i] !== 1) continue; // non-edge cell const f = cells.f[i]; if (used[f]) continue; // already connected if (features[f].type === "ocean") continue; // ocean cell const type = features[f].type === "lake" ? 1 : -1; // type value to search for const start = findStart(i, type); if (start === -1) continue; // cannot start here let vchain = connectVertices(start, type); if (features[f].type === "lake") relax(vchain, 1.2); used[f] = 1; let points = clipPoly(vchain.map(v => vertices.p[v])); const area = d3.polygonArea(points); // area with lakes/islands if (area > 0 && features[f].type === "lake") { points = points.reverse(); vchain = vchain.reverse(); } features[f].area = Math.abs(area); features[f].vertices = vchain; const path = round(lineGen(points)); if (features[f].type === "lake") { landMask .append("path") .attr("d", path) .attr("fill", "black") .attr("id", "land_" + f); // waterMask.append("path").attr("d", path).attr("fill", "white").attr("id", "water_"+id); // uncomment to show over lakes lakes .select("#freshwater") .append("path") .attr("d", path) .attr("id", "lake_" + f) .attr("data-f", f); // draw the lake } else { landMask .append("path") .attr("d", path) .attr("fill", "white") .attr("id", "land_" + f); waterMask .append("path") .attr("d", path) .attr("fill", "black") .attr("id", "water_" + f); const g = features[f].group === "lake_island" ? "lake_island" : "sea_island"; coastline .select("#" + g) .append("path") .attr("d", path) .attr("id", "island_" + f) .attr("data-f", f); // draw the coastline } // draw ruler to cover the biggest land piece if (f === largestLand) { const from = points[d3.scan(points, (a, b) => a[0] - b[0])]; const to = points[d3.scan(points, (a, b) => b[0] - a[0])]; rulers.create(Ruler, [from, to]); } } // find cell vertex to start path detection function findStart(i, t) { if (t === -1 && cells.b[i]) return cells.v[i].find(v => vertices.c[v].some(c => c >= n)); // map border cell const filtered = cells.c[i].filter(c => cells.t[c] === t); const index = cells.c[i].indexOf(d3.min(filtered)); return index === -1 ? index : cells.v[i][index]; } // connect vertices to chain function connectVertices(start, t) { const chain = []; // vertices chain to form a path for (let i = 0, current = start; i === 0 || (current !== start && i < 50000); i++) { const prev = chain[chain.length - 1]; // previous vertex in chain chain.push(current); // add current vertex to sequence const c = vertices.c[current]; // cells adjacent to vertex const v = vertices.v[current]; // neighboring vertices const c0 = c[0] >= n || cells.t[c[0]] === t; const c1 = c[1] >= n || cells.t[c[1]] === t; const c2 = c[2] >= n || cells.t[c[2]] === t; if (v[0] !== prev && c0 !== c1) current = v[0]; else if (v[1] !== prev && c1 !== c2) current = v[1]; else if (v[2] !== prev && c0 !== c2) current = v[2]; if (current === chain[chain.length - 1]) { ERROR && console.error("Next vertex is not found"); break; } } return chain; } // move vertices that are too close to already added ones function relax(vchain, r) { const p = vertices.p, tree = d3.quadtree(); for (let i = 0; i < vchain.length; i++) { const v = vchain[i]; let [x, y] = [p[v][0], p[v][1]]; if (i && vchain[i + 1] && tree.find(x, y, r) !== undefined) { const v1 = vchain[i - 1]; const v2 = vchain[i + 1]; const [x1, y1] = [p[v1][0], p[v1][1]]; const [x2, y2] = [p[v2][0], p[v2][1]]; [x, y] = [(x1 + x2) / 2, (y1 + y2) / 2]; p[v] = [x, y]; } tree.add([x, y]); } } TIME && console.timeEnd("drawCoastline"); }