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!
This commit is contained in:
Justin Kunimune 2018-02-12 11:32:25 -10:00
parent 075ef87e99
commit 7bcc659750
2 changed files with 59 additions and 19 deletions

View File

@ -183,16 +183,13 @@ public class MapDesignerVector extends MapApplication {
List<Path> theMap = new LinkedList<Path>();
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 ++;
}

View File

@ -280,7 +280,8 @@ public class SVGMap implements Iterable<SVGMap.Path> {
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<SVGMap.Path> {
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<SVGMap.Path> {
}
private Path closePaths(Path open) { //replace plain loops with 'Z's and combine connected parts
List<Path> parts = new ArrayList<Path>();
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<SVGMap.Path> {
}
}
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<SVGMap.Path> {
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));
}
}