The Complete Set

I made inverse solutions for all my invented projections, and even threw
in a new one, "Tetrachamfer", which kind of sucks, but I felt like I
should include it for completeness's sake.  I found a bug in
MapConfigurationDialog and squashed it, as well. And I added a couple
new maps to the output folder. Oh, I never explained what the problem
with Tobler was! There was an issue with the way Z was being generated,
so last commit, I cleaned that up and may have made it slightly slower,
but I don't really care given how well it works now and how much faster
it still is than Lee (seriously, what is the deal with that?). Did I do
anything else? Not really. Next step: conic projections!
This commit is contained in:
Justin Kunimune 2017-07-15 16:51:38 -04:00
parent 18d2d8266f
commit ab155f2c9e
14 changed files with 127 additions and 41 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

BIN
output/Compass Rose.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

View File

@ -1,13 +0,0 @@
We got the best Tobler projections using:
t0=0.25022057521781305; t1=4.49997342476493; (4.0724237830145623E-4, 0.5079777001726435)
t0=0.2505753087520266; t1=4.499956546953281; (4.064604422025349E-4, 0.5079765807787102)
t0=0.2508838353035556; t1=4.4999316499660384; (4.0573108482153483E-4, 0.5079755362982413)
t0=0.2508314520322239; t1=4.499728417979239; (4.0450171936633287E-4, 0.5079758925954643)
t0=0.2500693069768243; t1=4.499328429617362; (4.0750703627578564E-4, 0.5079785011865504)
t0=0.2600667982838587; t1=4.5008562930364775; (3.8249122842821693E-4, 0.5079640977617719)
t0=0.2558237748512546; t1=4.4998740643492035; (3.9365452439070936E-4, 0.5079694226751023)
t0=0.24889877394521065; t1=4.498227902031633; (4.100009439557956E-4, 0.507979411007307)
t0=0.259316034943474; t1=4.4995552660836395; (3.8333968917550053E-4, 0.5079662063081046)
t0=0.2759279673286983; t1=4.5009128718891915; (3.3514923608814645E-4, 0.5079628635806102)
t0=0.2554293245455028; t1=4.492372958335096; (3.945025957463458E-4, 0.5079708674912415)

View File

@ -90,7 +90,7 @@ public class MapAnalyzer extends MapApplication {
Projection.ALBERS, Projection.LEE, Projection.TETRAGRAPH, Projection.SINUSOIDAL, Projection.MOLLWEIDE,
Projection.HAMMER, Projection.TOBLER, Projection.AITOFF, Projection.VAN_DER_GRINTEN, Projection.ROBINSON,
Projection.WINKEL_TRIPEL, Projection.PEIRCE_QUINCUNCIAL, Projection.GUYOU, Projection.MAGNIFIER,
Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET };
Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET, Projection.TETRACHAMFER };
private Button updateBtn;

View File

@ -113,7 +113,7 @@ public abstract class MapApplication extends Application {
final Label label = new Label("Current input:");
final Text inputLabel = new Text("None");
final FileChooser inputChooser = new FileChooser();
final FileChooser inputChooser = new FileChooser(); //TODO: remember last directory
inputChooser.setInitialDirectory(new File("input"));
inputChooser.setTitle("Choose an input map");
inputChooser.getExtensionFilters().addAll(allowedExtensions);
@ -376,7 +376,7 @@ public abstract class MapApplication extends Application {
Slider[] sliders, Spinner<Double>[] spinners) {
if (presetName.equals("Antipode")) {
sliders[0].setValue(-sliders[0].getValue());
sliders[1].setValue((sliders[1].getValue()+180)%360);
sliders[1].setValue((sliders[1].getValue()+360)%360-180);
sliders[2].setValue(-sliders[2].getValue());
}
else if (presetName.equals("Random")) {

View File

@ -72,7 +72,7 @@ public class MapDesignerRaster extends MapApplication {
Projection.ALBERS, Projection.LEE, Projection.TETRAGRAPH, Projection.AUTHAGRAPH, Projection.SINUSOIDAL,
Projection.MOLLWEIDE, Projection.TOBLER, Projection.AITOFF, Projection.VAN_DER_GRINTEN, Projection.ROBINSON,
Projection.WINKEL_TRIPEL, Projection.PEIRCE_QUINCUNCIAL, Projection.GUYOU, Projection.LEMONS,
Projection.MAGNIFIER, Projection.EXPERIMENT };
Projection.MAGNIFIER, Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET, Projection.TETRACHAMFER };
private Button updateBtn, saveMapBtn;

View File

@ -69,7 +69,7 @@ public class MapDesignerVector extends MapApplication {
Projection.ALBERS, Projection.LEE, Projection.TETRAGRAPH, Projection.SINUSOIDAL, Projection.MOLLWEIDE,
Projection.TOBLER, Projection.AITOFF, Projection.VAN_DER_GRINTEN, Projection.ROBINSON,
Projection.WINKEL_TRIPEL, Projection.PEIRCE_QUINCUNCIAL, Projection.GUYOU, Projection.MAGNIFIER,
Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET };
Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET, Projection.TETRACHAMFER };
private static final int DEF_MAX_VTX = 5000;

View File

@ -73,10 +73,10 @@ public class MapOptimizer extends Application {
chart.getData().add(analyzeAll(globe, EXISTING_PROJECTIONS));
// chart.getData().add(optimizeFamily(Projection.WINKEL_TRIPEL, globe, log));
chart.getData().add(optimizeFamily(Projection.TOBLER, globe, log));
// chart.getData().add(optimizeFamily(Projection.TOBLER, globe, log));
// chart.getData().add(optimizeFamily(Projection.HYPERELLIPOWER, globe, log));
// chart.getData().add(optimizeFamily(Projection.TETRAPOWER, globe, log));
// chart.getData().add(optimizeFamily(Projection.TETRAFILLET, globe, log));
chart.getData().add(optimizeFamily(Projection.TETRAFILLET, globe, log));
System.out.println("Total time elapsed: "+
(System.currentTimeMillis()-startTime)/1000.+"s");

View File

@ -69,7 +69,7 @@ public class MapConfigurationDialog extends Dialog<Boolean> {
this.widthBox.valueProperty().addListener((observable, prev, now) -> { // link the Spinners
if (realEdit && maintainRatio.isSelected()) {
realEdit = false;
int prefHeight = (int)Math.floor(widthBox.getValue()/defaultRatio);
int prefHeight = (int)Math.round(widthBox.getValue()/defaultRatio);
heightBox.getValueFactory().setValue(prefHeight);
realEdit = true;
}
@ -77,7 +77,7 @@ public class MapConfigurationDialog extends Dialog<Boolean> {
this.heightBox.valueProperty().addListener((observable, prev, now) -> {
if (realEdit && maintainRatio.isSelected()) {
realEdit = false;
int prefWidth = (int)Math.ceil(heightBox.getValue()*defaultRatio);
int prefWidth = (int)Math.round(heightBox.getValue()*defaultRatio);
widthBox.getValueFactory().setValue(prefWidth);
realEdit = true;
}
@ -87,7 +87,7 @@ public class MapConfigurationDialog extends Dialog<Boolean> {
if (!now) widthBox.increment(0);
});
this.heightBox.focusedProperty().addListener((observable, prev,now) -> { //values when focus is lost
if (!now) heightBox.increment();
if (!now) heightBox.increment(0);
});
ObservableList<String> items = FXCollections.observableArrayList(

View File

@ -667,12 +667,21 @@ public enum Projection {
new double[][] {{1,5,3.7308},{.5,2.,1.2027},{.5,2.,1.1443}}) {
public double[] project(double lat, double lon, double[] params) {
final double k = params[0], n = params[1], a = params[2];
final double ynorm = (1-Math.pow(1-Math.abs(lat/(Math.PI/2)), n));
return new double[] {
Math.pow(1 - Math.pow(Math.abs(lat/(Math.PI/2)), k),1/k)*lon,
(1-Math.pow(1-Math.abs(lat/(Math.PI/2)), n))/Math.sqrt(n)*Math.signum(lat)*Math.PI/2*a};
Math.pow(1 - Math.pow(ynorm, k),1/k)*lon,
ynorm*Math.PI/2/Math.sqrt(n)*a*Math.signum(lat)
};
}
public double[] inverse(double x, double y, double[] params) {
return null;
final double k = params[0], n = params[1];
return new double[] {
(1 - Math.pow(1-Math.abs(y), 1/n))*Math.PI/2*Math.signum(y),
x/Math.pow(1 - Math.pow(Math.abs(y),k),1/k)*Math.PI };
}
public double getAspectRatio(double[] params) {
final double n = params[1], a = params[2];
return 2*Math.sqrt(n)/a;
}
},
@ -687,19 +696,34 @@ public enum Projection {
final double thtP = Math.PI/3*(1 - Math.pow(1-Math.abs(tht)/(Math.PI/2),k1))/(1 - 1/Math.pow(3,k1))*Math.signum(tht);
final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3));
final double rmax = .5/Math.cos(thtP); //the max normalized radius of this triangle (in the plane)
final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; //normalized tetragraph radius
final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax;
return new double[] {
(1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2*Math.PI/3,
thtP + t0 };
});
}
public double[] inverse(double x, double y, double[] params) {
return null;
final double k1 = params[0], k2 = params[1], k3 = params[2];
final double[] doubles = tetrahedralProjectionInverse(x,y);
final double[] faceCenter = { doubles[0], doubles[1], doubles[2] };
final double tht = doubles[3], xp = doubles[4], yp = doubles[5];
final double R = Math.hypot(xp, yp)*Math.sqrt(3)/2;
final double t = Math.atan2(yp, xp) + tht;
final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2;
final double thtP = t-t0;
final double lamS = (1-Math.pow(1-Math.abs(thtP)*(1-1/Math.pow(3,k1))/(Math.PI/3), 1/k1))*Math.PI/2*Math.signum(thtP);
final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3));
final double rmax = .5/Math.cos(thtP); //the max normalized radius of this triangle (in the plane)
final double rtgf = 1-Math.pow(1-R/rmax*(1-Math.pow(Math.abs(1-rmax), kRad)), 1/kRad); //normalized tetragraph radius
double[] triCoords = {
Math.atan(Math.cos(lamS)/Math.tan(rtgf/rmax*Math.atan(Math.sqrt(2)))),
Math.PI/2 + t0 + lamS };
return obliquifyPlnr(triCoords, faceCenter);
}
},
TETRAFILLET("Tetrafillet", "A parametric projection that I'm still testing",
2., 0b1111, "other", "compromise", new String[] {"k1","k2","k3"},
Math.sqrt(3), 0b1111, "other", "compromise", new String[] {"k1","k2","k3"},
new double[][] {{.25,4.,1.1598},{.25,4.,.36295},{.25,4.,1.9553}}) {
public double[] project(double lat, double lon, double[] params) {
final double k1 = params[0], k2 = params[1], k3 = params[2];
@ -708,7 +732,9 @@ public enum Projection {
final double tht = coordR[1] - t0;
final double thtP = Math.PI/3*(1 - Math.pow(1-Math.abs(tht)/(Math.PI/2),k1))/(1 - 1/Math.pow(3,k1))*Math.signum(tht);
final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3));
final double rmax = 1/2. + 1/4.*Math.pow(thtP,2) + 5/48.*Math.pow(thtP,4) - .132621*Math.pow(thtP,6); //the max normalized radius of this triangle (in the plane)
final double rmax; //the max normalized radius of this triangle (in the plane)
if (Math.abs(thtP) < .70123892) rmax = .5/Math.cos(thtP);
else rmax = .75 - 1.5972774*Math.pow(Math.PI/3-Math.abs(thtP),2)/2;
final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; //normalized tetragraph radius
return new double[] {
(1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2*Math.PI/3,
@ -717,7 +743,64 @@ public enum Projection {
});
}
public double[] inverse(double x, double y, double[] params) {
return null;
final double k1 = params[0], k2 = params[1], k3 = params[2];
final double[] doubles = tetrahedralProjectionInverse(x,y);
final double[] faceCenter = { doubles[0], doubles[1], doubles[2] };
final double tht = doubles[3], xp = doubles[4], yp = doubles[5];
final double R = Math.hypot(xp, yp)*Math.sqrt(3)/2;
final double t = Math.atan2(yp, xp) + tht;
final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2;
final double thtP = t-t0;
final double lamS = (1-Math.pow(1-Math.abs(thtP)*(1-1/Math.pow(3,k1))/(Math.PI/3), 1/k1))*Math.PI/2*Math.signum(thtP);
final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3));
final double rmax; //the max normalized radius of this triangle (in the plane)
if (Math.abs(thtP) < .70123892) rmax = .5/Math.cos(thtP);
else rmax = .75 - 1.5972774*Math.pow(Math.PI/3-Math.abs(thtP),2)/2;
final double rtgf = 1-Math.pow(1-R/rmax*(1-Math.pow(Math.abs(1-rmax), kRad)), 1/kRad); //normalized tetragraph radius
if (R > rmax) return null;
double[] triCoords = {
Math.atan(Math.cos(lamS)/Math.tan(rtgf/rmax*Math.atan(Math.sqrt(2)))),
Math.PI/2 + t0 + lamS };
return obliquifyPlnr(triCoords, faceCenter);
}
},
TETRACHAMFER("Tetrachamfer", "A parametric projection that I'm still testing",
Math.sqrt(3), 0b1111, "other", "compromise", new String[] {"k1","k2","k3"},
new double[][] {{.25,4.,1.1598},{.25,4.,.36295},{.25,4.,1.9553}}) {
public double[] project(double lat, double lon, double[] params) {
final double k1 = params[0], k2 = params[1], k3 = params[2];
return tetrahedralProjectionForward(lat, lon, (coordR) -> {
final double t0 = Math.floor(coordR[1]/(2*Math.PI/3))*(2*Math.PI/3) + Math.PI/3;
final double tht = coordR[1] - t0;
final double thtP = Math.PI/3*(1 - Math.pow(1-Math.abs(tht)/(Math.PI/2),k1))/(1 - 1/Math.pow(3,k1))*Math.signum(tht);
final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3));
final double rmax = Math.min(.5/Math.cos(thtP), .75/Math.cos(Math.PI/3-Math.abs(thtP))); //the max normalized radius of this triangle (in the plane)
final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; //normalized tetragraph radius
return new double[] {
(1 - Math.pow(1-rtgf,kRad))/(1 - Math.pow(1-rmax,kRad))*rmax*2*Math.PI/3,
thtP + t0
};
});
}
public double[] inverse(double x, double y, double[] params) {
final double k1 = params[0], k2 = params[1], k3 = params[2];
final double[] doubles = tetrahedralProjectionInverse(x,y);
final double[] faceCenter = { doubles[0], doubles[1], doubles[2] };
final double tht = doubles[3], xp = doubles[4], yp = doubles[5];
final double R = Math.hypot(xp, yp)*Math.sqrt(3)/2;
final double t = Math.atan2(yp, xp) + tht;
final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2;
final double thtP = t-t0;
final double lamS = (1-Math.pow(1-Math.abs(thtP)*(1-1/Math.pow(3,k1))/(Math.PI/3), 1/k1))*Math.PI/2*Math.signum(thtP);
final double kRad = k3*Math.abs(thtP)/(Math.PI/3) + k2*(1-Math.abs(thtP)/(Math.PI/3));
final double rmax = Math.min(.5/Math.cos(thtP), .75/Math.cos(Math.PI/3-Math.abs(thtP))); //the max normalized radius of this triangle (in the plane)
final double rtgf = 1-Math.pow(1-R/rmax*(1-Math.pow(Math.abs(1-rmax), kRad)), 1/kRad); //normalized tetragraph radius
if (R > rmax) return null;
double[] triCoords = {
Math.atan(Math.cos(lamS)/Math.tan(rtgf/rmax*Math.atan(Math.sqrt(2)))),
Math.PI/2 + t0 + lamS };
return obliquifyPlnr(triCoords, faceCenter);
}
};
@ -988,26 +1071,22 @@ public enum Projection {
private static double[] tetrahedralProjectionInverse(double x, double y) { // a function to help with tetrahedral projections
if (y < x-1) {
if (y < x-1)
return new double[] {
-Math.PI/2, 0, 0,
-Math.PI/2, Math.sqrt(3)*(x-2/3.), y+1 };
}
else if (y < -x-1) {
else if (y < -x-1)
return new double[] {
-Math.PI/2, 0, 0,
Math.PI/2, Math.sqrt(3)*(x+2/3.), y+1 };
}
else if (y > -x+1) {
else if (y > -x+1)
return new double[] {
Math.PI/2-Math.asin(Math.sqrt(8)/3), Math.PI, 0,
-Math.PI/2, Math.sqrt(3)*(x-2/3.), y-1 };
}
else if (y > x+1) {
else if (y > x+1)
return new double[] {
Math.PI/2-Math.asin(Math.sqrt(8)/3), Math.PI, 0,
Math.PI/2, Math.sqrt(3)*(x+2/3.), y-1 };
}
else if (x < 0)
return new double[] {
Math.PI/2-Math.asin(Math.sqrt(8)/3), -Math.PI/3, 0,
@ -1078,8 +1157,7 @@ public enum Projection {
lonf = lon0 -
Math.acos(innerFunc);
double thtf = 0;
thtf += pole[2];
double thtf = pole[2];
double[] output = {latf, lonf, thtf};
return output;
@ -1150,5 +1228,26 @@ public enum Projection {
public String getProperty() {
return this.property;
}
public static final void main(String[] args) {
double[] pole = {47, -173, 138};
System.out.println("The pole is at "+Arrays.toString(pole));
for (int i = 0; i < 3; i ++)
pole[i] = Math.toRadians(pole[i]);
for (double[] ref: new double[][] {{-Math.PI/2, 0, Math.PI/3},
{Math.asin(1/3.0), Math.PI, Math.PI/3},
{Math.asin(1/3.0), Math.PI/3, Math.PI/3},
{Math.asin(1/3.0), -Math.PI/3, -Math.PI/3}}) {
ref[0] *= -1;
ref[1] = (ref[1]+2*Math.PI)%(2*Math.PI)-Math.PI;
ref[2] *= -1;
System.out.println("The relative singularity is at "+Arrays.toString(ref));
final double[] coords = obliquifyPlnr(ref, pole);
for (int i = 0; i < 3; i ++)
coords[i] = Math.toDegrees(coords[i]);
System.out.println("That comes out to "+Arrays.toString(coords));
}
}
}