Put the scissors down!

I (finally) made it automatically snip paths that are obviously crossing
interruptions. This should make vector maps look world better straight
out of the program, and will be crucial to mass-producing vector maps.
There's some room for improvement regarding the handling of closepaths,
but all in all, this is a big step up.

I also got a new input, fixed some minor issues with the new
Cahill-Keyes (did you know it's pronounced /kais/?), and might have
fixed the sporadic BufferOverflowExceptions, but I'm not sure about the
last one, as that problem is extremely difficult to reproduce, but I
have a strong suspicion it has something to do with threads, and I added
a bunch of Platform.runLater()s, so we'll see.
This commit is contained in:
Justin Kunimune 2018-02-09 22:43:58 -10:00
parent 027347071c
commit 9ae6c2a668
5 changed files with 72 additions and 18 deletions

BIN
input/Night.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

View File

@ -543,14 +543,18 @@ public abstract class MapApplication extends Application {
protected void disable(ButtonType... buttons) {
for (ButtonType bt: buttons)
this.buttons.get(bt).setDisable(true);
Platform.runLater(() -> {
for (ButtonType bt: buttons)
this.buttons.get(bt).setDisable(true);
});
}
protected void enable(ButtonType... buttons) {
for (ButtonType bt: buttons)
this.buttons.get(bt).setDisable(false);
Platform.runLater(() -> {
for (ButtonType bt: buttons)
this.buttons.get(bt).setDisable(false);
});
}

View File

@ -33,6 +33,7 @@ import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import dialogs.ProgressBarDialog;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.canvas.Canvas;
@ -160,14 +161,18 @@ public class MapDesignerVector extends MapApplication {
final Iterable<Path> transformed = map(input, input.length()/maxVtx+1, aspect.clone(), null);
if (this.getProjection().getWidth() > this.getProjection().getHeight()) {
viewer.setWidth(IMG_WIDTH);
viewer.setHeight(IMG_WIDTH/this.getProjection().getAspectRatio());
Platform.runLater(() -> {
viewer.setWidth(IMG_WIDTH);
viewer.setHeight(IMG_WIDTH/this.getProjection().getAspectRatio());
});
}
else {
viewer.setWidth(IMG_WIDTH*this.getProjection().getAspectRatio());
viewer.setHeight(IMG_WIDTH);
Platform.runLater(() -> {
viewer.setWidth(IMG_WIDTH*this.getProjection().getAspectRatio());
viewer.setHeight(IMG_WIDTH);
});
}
drawImage(transformed, viewer);
Platform.runLater(() -> drawImage(transformed, viewer));
enable(ButtonType.SAVE_MAP);
}
@ -228,7 +233,7 @@ public class MapDesignerVector extends MapApplication {
private void drawImage(
Iterable<Path> paths, Canvas c) { //parse the SVG path, with a few modifications
Iterable<Path> paths, Canvas c) { //parse the SVG path, with a few modifications (run this from the GUI thread!)
final double mX = this.getProjection().getWidth()/2;
final double mY = this.getProjection().getHeight()/2;
GraphicsContext g = c.getGraphicsContext2D();

View File

@ -307,17 +307,19 @@ public class Octohedral {
}
public double[] inverse(double x, double y) {
if (Math.hypot(x-southPoleX, y-southPoleY) < 0.325) { //do the special Antarctica thing
if (Math.hypot(x-southPoleX, y-southPoleY) < 0.324) { //do the special Antarctica thing
double tht = Math.atan2(southPoleX-x, y-southPoleY);
double centralAngle =
Math.floor((tht+Math.PI/12)/(Math.PI/2))*Math.PI/2 + Math.PI/6;
if (centralAngle == 7*Math.PI/6)
centralAngle = -5*Math.PI/6;
double maxLat = (centralAngle != Math.PI/6) ? -Math.PI/3 : Math.PI/2;
return new double[] {
southPoleX - (2-cutRatio)*Math.sin(centralAngle),
southPoleY + (2-cutRatio)*Math.cos(centralAngle),
centralAngle, -Math.PI/12 - centralAngle, -Math.PI/2, maxLat};
}
else {
else { //everything besides Antarctica is pretty straightforward
double tht = Math.atan2(Math.abs(x)-1, -y);
if (tht < -Math.PI/3) return null;
double centralAngle = Math.floor(tht/(Math.PI/3))*Math.PI/3 + Math.PI/6;

View File

@ -31,6 +31,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@ -279,7 +280,7 @@ public class SVGMap implements Iterable<SVGMap.Path> {
out.write(replacePlaceholders(formatIterator.next(), inWidth/inHeight));
while (curveIterator.hasNext()) {
out.write(curveIterator.next().toString(inMinX, inMaxY, vbMinX, vbMinY,
out.write(breakWraps(curveIterator.next()).toString(inMinX, inMaxY, vbMinX, vbMinY,
Math.max(vbWidth, vbHeight)/Math.max(inWidth, inHeight)));
out.write(formatIterator.next());
tracker.accept((double)i/paths.size());
@ -312,6 +313,32 @@ public class SVGMap implements Iterable<SVGMap.Path> {
}
private Path breakWraps(Path continuous) { //break excessively long commands, as they are likely wrapping over a discontinuity
if (continuous.size() <= 2) return continuous;
Path broken = new Path();
double[] lens = {Double.NaN, Double.NaN, Double.NaN}; //the revolving array of command lengths
for (int i = 0; i < continuous.size(); i ++) {
if (i < continuous.size()-1 && continuous.get(i+1).type != 'M')
lens[2] = Math.hypot( //compute this next length
continuous.get(i+1).args[0] - continuous.get(i).args[0],
continuous.get(i+1).args[1] - continuous.get(i).args[1]);
else
lens[2] = Double.NaN;
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';
broken.add(new Command(type, continuous.get(i).args.clone()));
lens[0] = lens[1];
lens[1] = lens[2];
}
return broken;
}
private static boolean isNonELetter(char c) {
return (c >= 'A' && c <= 'Z' && c != 'E') || (c >= 'a' && c <= 'z' && c != 'e');
}
@ -337,6 +364,11 @@ public class SVGMap implements Iterable<SVGMap.Path> {
super();
}
public Path(Command... commands) {
this();
this.addAll(Arrays.asList(commands));
}
public Path(String d, double vbWidth, double vbHeight) throws Exception {
this(d, new double[] {1,1,0,0}, 0, 0, vbWidth, vbHeight);
}
@ -346,7 +378,8 @@ public class SVGMap implements Iterable<SVGMap.Path> {
super();
int i = 0;
double[] last = new double[] {0,0}; //for relative coordinates
double[] lastMove = {0, 0}; //for closepaths
double[] last = {0, 0}; //for relative coordinates
while (i < d.length()) {
char type = d.charAt(i);
String argString = "";
@ -374,6 +407,10 @@ public class SVGMap implements Iterable<SVGMap.Path> {
last[direcIdx] = args[direcIdx];
type = 'L';
}
else if (type == 'z' || type == 'Z') { //change this to 'L', too
args = new double[] {lastMove[0], lastMove[1]};
type = 'L';
}
else {
args = new double[argStrings.length];
for (int j = 0; j < args.length; j ++) {
@ -386,6 +423,10 @@ public class SVGMap implements Iterable<SVGMap.Path> {
if (type >= 'a') //make all letters uppercase
type -= 32;
}
if (type == 'M') { //make note, so we can interpret closepaths properly
lastMove[0] = args[args.length-2];
lastMove[1] = args[args.length-1];
}
for (int j = 0; j < args.length; j ++) {
if (!Double.isFinite(args[j]))
@ -406,10 +447,8 @@ public class SVGMap implements Iterable<SVGMap.Path> {
}
}
public boolean addAll(Path p) {
for (Command c: p)
add(c);
return true;
public Path(Command command) {
// TODO: Implement this
}
public String toString(
@ -435,6 +474,10 @@ public class SVGMap implements Iterable<SVGMap.Path> {
this.args = args;
}
public Command(Command command) {
this(command.type, command.args.clone());
}
public String toString() {
return this.toString(-1, -1, 0, 0, 1);
}