From 7bcc659750a4f8cd549f15eba00f29b16d89d99f Mon Sep 17 00:00:00 2001 From: Justin Kunimune Date: Mon, 12 Feb 2018 11:32:25 -1000 Subject: [PATCH] Finally! Edcent-looking maps! I got the coastlines to correctly rebind to themselves when crossing a neatline. My projection code finally produces decent maps without post-processing! Now to fulfill my dream of replacing all the Tissot's Indicatrices images on Wikipedia! --- src/apps/MapDesignerVector.java | 27 ++++++++++------- src/image/SVGMap.java | 51 +++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/apps/MapDesignerVector.java b/src/apps/MapDesignerVector.java index 1f3aef5..4392507 100644 --- a/src/apps/MapDesignerVector.java +++ b/src/apps/MapDesignerVector.java @@ -183,16 +183,13 @@ public class MapDesignerVector extends MapApplication { List theMap = new LinkedList(); int i = 0; - for (Path path0: input) { + for (Path pathS: input) { updateProgress(i, input.numCurves()); - if (path0.size() <= step) continue; //don't bother drawing singular points - Path path1 = new Path(); - int counter = 0; - for (Command cmdS: path0) { - counter --; //skip the requisite number of 'L's - if (counter > 0 && cmdS.type != 'M' && cmdS.type != 'Z') continue; - counter = step; - + if (pathS.size() <= step) continue; //don't bother drawing singular points + Path pathP = new Path(); + int j = 0; + while (j < pathS.size()) { + Command cmdS = pathS.get(j); Command cmdP = new Command(cmdS.type, new double[cmdS.args.length]); for (int k = 0; k < cmdS.args.length; k += 2) { double[] coords = proj.project(cmdS.args[k+1], cmdS.args[k], aspect); @@ -203,9 +200,17 @@ public class MapDesignerVector extends MapApplication { if (Double.isNaN(cmdP.args[k]) || Double.isNaN(cmdP.args[k+1])) System.err.println(getProjection()+" returns "+cmdP.args[k]+","+cmdP.args[k+1]+" at "+cmdS.args[k+1]+","+cmdS.args[k]+"!"); } - path1.add(cmdP); //TODO: if I was smart, I would divide landmasses that hit an interruption so that I didn't get those annoying lines that cross the map, and then run adaptive resampling to make sure the cuts look clean and not polygonal (e.g. so Antarctica extends all the way to the bottom), but that sounds really hard. + pathP.add(cmdP); //TODO: if I was smart, I would divide landmasses that hit an interruption so that I didn't get those annoying lines that cross the map, and then run adaptive resampling to make sure the cuts look clean and not polygonal (e.g. so Antarctica extends all the way to the bottom), but that sounds really hard. + + for (int k = 0; k < step; k ++) { //increment j by at least 1 and at most step + if (k != 0 && (j >= pathS.size() - 1 || pathS.get(j).type == 'M' + || pathS.get(j).type == 'Z')) + break; //but pause for every moveto and closepath, and for the last command in the path + else + j ++; + } } - theMap.add(path1); + theMap.add(pathP); i ++; } diff --git a/src/image/SVGMap.java b/src/image/SVGMap.java index fe5e6ad..a84face 100644 --- a/src/image/SVGMap.java +++ b/src/image/SVGMap.java @@ -280,7 +280,8 @@ public class SVGMap implements Iterable { out.write(replacePlaceholders(formatIterator.next(), inWidth/inHeight)); while (curveIterator.hasNext()) { - out.write(breakWraps(curveIterator.next()).toString(inMinX, inMaxY, vbMinX, vbMinY, + out.write(closePaths(breakWraps(curveIterator.next())).toString( + inMinX, inMaxY, vbMinX, vbMinY, Math.max(vbWidth, vbHeight)/Math.max(inWidth, inHeight))); out.write(formatIterator.next()); } @@ -325,8 +326,8 @@ public class SVGMap implements Iterable { char type = continuous.get(i).type; if ((Double.isNaN(lens[0]) || lens[1] > 20*lens[0]) //and compare it to the last two lengths - && (Double.isNaN(lens[2]) || lens[1] > 20*lens[2])) - type = 'M'; + && (Double.isNaN(lens[2]) || lens[1] > 20*lens[2])) //if both sides are far longer or nonexistent + type = 'M'; //break this line broken.add(new Command(type, continuous.get(i).args.clone())); lens[0] = lens[1]; @@ -337,6 +338,44 @@ public class SVGMap implements Iterable { } + private Path closePaths(Path open) { //replace plain loops with 'Z's and combine connected parts + List parts = new ArrayList(); + Path currentPart = null; + for (Command cmd: open) { //start by breaking the Path into parts, + if (cmd.type == 'M') { //separated by movetos + if (currentPart != null) + parts.add(currentPart); + currentPart = new Path(); + } + currentPart.add(cmd); + } + parts.add(currentPart); + + Path closed = new Path(); + for (int i = 0; i < parts.size(); i ++) { //now look through those parts + Path partI = parts.get(i); + if (partI.size() > 1 + && Arrays.equals(partI.get(0).args, partI.get(partI.size()-1).args)) { //if it is self-enclosing + partI.set(partI.size()-1, new Command('Z', new double[0])); //give it a closepath and send it on its way + } + else { //if it is open + for (int j = i+1; j < parts.size(); j ++) { //look to see if there is anything that completes it + Path partJ = parts.get(j); + if (Arrays.equals(partI.get(0).args, partJ.get(partJ.size()-1).args)) { //if so, + partI.remove(0); //remove the useless moveto + partJ.addAll(partI); //combine them + partI = partJ; + parts.remove(j); //don't look at J anymone; it has been absorbed. + break; + } + } + } + closed.addAll(partI); //now turn in whatever you've got + } + return closed; + } + + private static boolean isNonELetter(char c) { return (c >= 'A' && c <= 'Z' && c != 'E') || (c >= 'a' && c <= 'z' && c != 'e'); } @@ -445,10 +484,6 @@ public class SVGMap implements Iterable { } } - public Path(Command command) { - // TODO: Implement this - } - public String toString( double inMinX, double inMaxY, double outMinX, double outMinY, double outScale) { String s = ""; @@ -489,7 +524,7 @@ public class SVGMap implements Iterable { else s += formatDouble(outMinY + (inMaxY-args[i])*outScale)+","; } - return s.substring(0, s.length()-1); + return s.substring(0, Math.max(1, s.length()-1)); } }