mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-12-10 00:00:19 -05:00
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:
parent
027347071c
commit
9ae6c2a668
BIN
input/Night.png
Normal file
BIN
input/Night.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 MiB |
@ -543,14 +543,18 @@ public abstract class MapApplication extends Application {
|
|||||||
|
|
||||||
|
|
||||||
protected void disable(ButtonType... buttons) {
|
protected void disable(ButtonType... buttons) {
|
||||||
for (ButtonType bt: buttons)
|
Platform.runLater(() -> {
|
||||||
this.buttons.get(bt).setDisable(true);
|
for (ButtonType bt: buttons)
|
||||||
|
this.buttons.get(bt).setDisable(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void enable(ButtonType... buttons) {
|
protected void enable(ButtonType... buttons) {
|
||||||
for (ButtonType bt: buttons)
|
Platform.runLater(() -> {
|
||||||
this.buttons.get(bt).setDisable(false);
|
for (ButtonType bt: buttons)
|
||||||
|
this.buttons.get(bt).setDisable(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import javax.xml.parsers.ParserConfigurationException;
|
|||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
import dialogs.ProgressBarDialog;
|
import dialogs.ProgressBarDialog;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.canvas.Canvas;
|
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);
|
final Iterable<Path> transformed = map(input, input.length()/maxVtx+1, aspect.clone(), null);
|
||||||
|
|
||||||
if (this.getProjection().getWidth() > this.getProjection().getHeight()) {
|
if (this.getProjection().getWidth() > this.getProjection().getHeight()) {
|
||||||
viewer.setWidth(IMG_WIDTH);
|
Platform.runLater(() -> {
|
||||||
viewer.setHeight(IMG_WIDTH/this.getProjection().getAspectRatio());
|
viewer.setWidth(IMG_WIDTH);
|
||||||
|
viewer.setHeight(IMG_WIDTH/this.getProjection().getAspectRatio());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
viewer.setWidth(IMG_WIDTH*this.getProjection().getAspectRatio());
|
Platform.runLater(() -> {
|
||||||
viewer.setHeight(IMG_WIDTH);
|
viewer.setWidth(IMG_WIDTH*this.getProjection().getAspectRatio());
|
||||||
|
viewer.setHeight(IMG_WIDTH);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
drawImage(transformed, viewer);
|
Platform.runLater(() -> drawImage(transformed, viewer));
|
||||||
|
|
||||||
enable(ButtonType.SAVE_MAP);
|
enable(ButtonType.SAVE_MAP);
|
||||||
}
|
}
|
||||||
@ -228,7 +233,7 @@ public class MapDesignerVector extends MapApplication {
|
|||||||
|
|
||||||
|
|
||||||
private void drawImage(
|
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 mX = this.getProjection().getWidth()/2;
|
||||||
final double mY = this.getProjection().getHeight()/2;
|
final double mY = this.getProjection().getHeight()/2;
|
||||||
GraphicsContext g = c.getGraphicsContext2D();
|
GraphicsContext g = c.getGraphicsContext2D();
|
||||||
|
|||||||
@ -307,17 +307,19 @@ public class Octohedral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double[] inverse(double x, double y) {
|
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 tht = Math.atan2(southPoleX-x, y-southPoleY);
|
||||||
double centralAngle =
|
double centralAngle =
|
||||||
Math.floor((tht+Math.PI/12)/(Math.PI/2))*Math.PI/2 + Math.PI/6;
|
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;
|
double maxLat = (centralAngle != Math.PI/6) ? -Math.PI/3 : Math.PI/2;
|
||||||
return new double[] {
|
return new double[] {
|
||||||
southPoleX - (2-cutRatio)*Math.sin(centralAngle),
|
southPoleX - (2-cutRatio)*Math.sin(centralAngle),
|
||||||
southPoleY + (2-cutRatio)*Math.cos(centralAngle),
|
southPoleY + (2-cutRatio)*Math.cos(centralAngle),
|
||||||
centralAngle, -Math.PI/12 - centralAngle, -Math.PI/2, maxLat};
|
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);
|
double tht = Math.atan2(Math.abs(x)-1, -y);
|
||||||
if (tht < -Math.PI/3) return null;
|
if (tht < -Math.PI/3) return null;
|
||||||
double centralAngle = Math.floor(tht/(Math.PI/3))*Math.PI/3 + Math.PI/6;
|
double centralAngle = Math.floor(tht/(Math.PI/3))*Math.PI/3 + Math.PI/6;
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import java.io.FileWriter;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -279,7 +280,7 @@ public class SVGMap implements Iterable<SVGMap.Path> {
|
|||||||
|
|
||||||
out.write(replacePlaceholders(formatIterator.next(), inWidth/inHeight));
|
out.write(replacePlaceholders(formatIterator.next(), inWidth/inHeight));
|
||||||
while (curveIterator.hasNext()) {
|
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)));
|
Math.max(vbWidth, vbHeight)/Math.max(inWidth, inHeight)));
|
||||||
out.write(formatIterator.next());
|
out.write(formatIterator.next());
|
||||||
tracker.accept((double)i/paths.size());
|
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) {
|
private static boolean isNonELetter(char c) {
|
||||||
return (c >= 'A' && c <= 'Z' && c != 'E') || (c >= 'a' && c <= 'z' && c != 'e');
|
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();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path(Command... commands) {
|
||||||
|
this();
|
||||||
|
this.addAll(Arrays.asList(commands));
|
||||||
|
}
|
||||||
|
|
||||||
public Path(String d, double vbWidth, double vbHeight) throws Exception {
|
public Path(String d, double vbWidth, double vbHeight) throws Exception {
|
||||||
this(d, new double[] {1,1,0,0}, 0, 0, vbWidth, vbHeight);
|
this(d, new double[] {1,1,0,0}, 0, 0, vbWidth, vbHeight);
|
||||||
}
|
}
|
||||||
@ -346,7 +378,8 @@ public class SVGMap implements Iterable<SVGMap.Path> {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
int i = 0;
|
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()) {
|
while (i < d.length()) {
|
||||||
char type = d.charAt(i);
|
char type = d.charAt(i);
|
||||||
String argString = "";
|
String argString = "";
|
||||||
@ -374,6 +407,10 @@ public class SVGMap implements Iterable<SVGMap.Path> {
|
|||||||
last[direcIdx] = args[direcIdx];
|
last[direcIdx] = args[direcIdx];
|
||||||
type = 'L';
|
type = 'L';
|
||||||
}
|
}
|
||||||
|
else if (type == 'z' || type == 'Z') { //change this to 'L', too
|
||||||
|
args = new double[] {lastMove[0], lastMove[1]};
|
||||||
|
type = 'L';
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
args = new double[argStrings.length];
|
args = new double[argStrings.length];
|
||||||
for (int j = 0; j < args.length; j ++) {
|
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
|
if (type >= 'a') //make all letters uppercase
|
||||||
type -= 32;
|
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 ++) {
|
for (int j = 0; j < args.length; j ++) {
|
||||||
if (!Double.isFinite(args[j]))
|
if (!Double.isFinite(args[j]))
|
||||||
@ -406,10 +447,8 @@ public class SVGMap implements Iterable<SVGMap.Path> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addAll(Path p) {
|
public Path(Command command) {
|
||||||
for (Command c: p)
|
// TODO: Implement this
|
||||||
add(c);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString(
|
public String toString(
|
||||||
@ -435,6 +474,10 @@ public class SVGMap implements Iterable<SVGMap.Path> {
|
|||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Command(Command command) {
|
||||||
|
this(command.type, command.args.clone());
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return this.toString(-1, -1, 0, 0, 1);
|
return this.toString(-1, -1, 0, 0, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user