An amalgamation beyond what Tobler ever intended

I just added a standard parallel slider to Tobler. I think it's much
better this way. It'll give the optimizer more room to play. I think
this is a good thing.
This commit is contained in:
jkunimune 2017-07-12 22:13:08 -04:00
parent 8f9af45190
commit 18d2d8266f
7 changed files with 63 additions and 85 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 376 KiB

View File

@ -1,52 +1,13 @@
We got the best Tobler projections using:
t0=0.20689580721572343; t1=1.0518739973059956; (0.2278882297188225, 0.7351995813152631)
t0=-0.013873876034131705; t1=1.1964036325935448; (0.10329633230643703, 0.8159488460892629)
t0=0.07848706830962371; t1=1.250712275634557; (0.19906825204375778, 0.6909385426069267)
t0=-0.16247243342371043; t1=2.663854628635912; (0.3764474762459828, 0.39497938818029654)
t0=-0.12574252228336746; t1=2.68730952077553; (0.38377128187389, 0.38934754071673555)
t0=-0.07960733577894175; t1=2.7154408517069104; (0.3922805350432534, 0.38301260966955725)
t0=-0.02312390412484433; t1=2.747964441000864; (0.40181996234881234, 0.376245876308442)
t0=0.22979176258322553; t1=2.7429178312596534; (0.4307734747048179, 0.35886916600298147)
t0=0.2986403319843731; t1=2.7680744355593454; (0.4390973632047321, 0.3549598860878104)
t0=0.4897643027102489; t1=2.731577724739168; (0.4570064132077315, 0.3489329746625129)
t0=0.576706686336081; t1=2.727168425147056; (0.4652189203666443, 0.3475514461065558)
We got the best Hyperellipower projections using:
t0=7.000030714857738; t1=1.7501736136613912; t2=1.2424301333532883; (0.05789920062472334, 0.5401983547787428)
t0=2.9689994981677543; t1=1.5546706682635625; t2=1.202488975475673; (0.04998147976348523, 0.4916068765524706)
t0=2.976171401808167; t1=1.5161903399231447; t2=1.1782665110594825; (0.06530293128069009, 0.4761748256585911)
t0=2.9861817083085525; t1=1.4593728821289664; t2=1.1537120674283408; (0.09184394727824656, 0.45451796394264804)
t0=2.9991221156792065; t1=1.3843375298085148; t2=1.1329489918084068; (0.12960786131042348, 0.4268390954046906)
t0=3.01752604900096; t1=1.2792897036902051; t2=1.1175589617802562; (0.18431069667203306, 0.3888711249418354)
t0=4.980007215738542; t1=1.1761278423590138; t2=1.1389472742347184; (0.2951326973404997, 0.32344075041891057)
t0=4.980064919080647; t1=0.9783263517000584; t2=1.1487412229145555; (0.39837308894141954, 0.2505546973545707)
t0=4.9866040560903; t1=0.6584785807029476; t2=1.2323013996369525; (0.5663045417767244, 0.13457680020557403)
t0=5.006169745214724; t1=0.5618372179572724; t2=1.333381969495644; (0.6173994095669276, 0.10739463432525206)
t0=5.077803445908131; t1=0.519820523555758; t2=1.375324645883218; (0.6404971339042698, 0.09702292792121305)
We got the best Tetrapower projections using:
t0=1.9742544935127513; t1=0.3165825132944997; t2=1.7104482063781243; (0.12685011883033045, 0.6998556205587817)
t0=1.962981467437631; t1=0.33675183192641267; t2=1.6361592952928836; (0.1209476582717595, 0.6640588882232411)
t0=1.9480908277857543; t1=0.35849753079300584; t2=1.5582492283949376; (0.12325154012287963, 0.6256441904705873)
t0=1.9234978011011936; t1=0.3847357740932864; t2=1.4604806712420046; (0.13799869866070721, 0.5762718610462976)
t0=0.6124388919820642; t1=1.917086087357923; t2=0.9484044889912777; (0.41309531025079965, 0.2327393768086944)
t0=1.23401828454997; t1=1.876873726139273; t2=1.0828451736039355; (0.2637145876836078, 0.31014464183255075)
t0=0.9572598729729318; t1=1.7817153953686002; t2=1.0224033818379272; (0.32826397272609387, 0.24202139324489627)
t0=0.8587884543704014; t1=1.6710977448846762; t2=0.9949103223977627; (0.3522709832093798, 0.21319182309649015)
t0=0.9435163850418924; t1=1.55144565212313; t2=0.9983573057631698; (0.33448713206948405, 0.21021946056729113)
t0=0.835223125904033; t1=1.3334457018926003; t2=0.9615459374484878; (0.36632736621112577, 0.1690016499137683)
t0=0.7695503630650578; t1=1.0466006829884087; t2=0.7748410161473593; (0.4180555104706251, 0.19100158435703396)
We got the best Tetrafillet projections using:
t0=1.697301652074613; t1=0.29231443192365125; t2=2.0833144221215747; (0.14923380124858912, 0.4443773760510159)
t0=1.6502750223731497; t1=0.2914626537478813; t2=2.066348607583602; (0.1388325981140953, 0.43085162515261666)
t0=1.600743232418834; t1=0.29090527877531613; t2=2.047318416001321; (0.12942585741109294, 0.41660202879376756)
t0=0.7919132930728487; t1=0.42770091877145067; t2=1.9993410514340235; (0.2116646805720328, 0.2949663045438834)
t0=0.8080673553412518; t1=0.44850426928591514; t2=1.934057053185013; (0.20930284601445454, 0.2806330954457315)
t0=0.8235072145657272; t1=0.4737730344356592; t2=1.8515915590155538; (0.20896755329277447, 0.2637740962243038)
t0=0.8337917661788428; t1=0.5030437079595327; t2=1.7520032593115307; (0.21265929624368232, 0.2452639646963364)
t0=0.5603843826957053; t1=1.6235135259584208; t2=1.668817985780958; (0.2694541626361741, 0.2673610849383188)
t0=0.5452329420102817; t1=1.471056278853867; t2=1.5625466788876228; (0.2730604696794315, 0.23924324368133712)
t0=0.5584240611794797; t1=1.2338383431980802; t2=1.4331167922737151; (0.2772746999471374, 0.2084441050746002)
t0=0.6263498318543057; t1=0.8844338247687276; t2=1.3078296521667596; (0.285312021308777, 0.1878269571175769)
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

@ -126,8 +126,10 @@ public class MapDesignerRaster extends MapApplication {
this.display.setFitWidth(IMG_WIDTH);
this.display.setFitHeight(IMG_WIDTH);
this.display.setPreserveRatio(true);
final StackPane pane = new StackPane(display);
pane.setMinWidth(IMG_WIDTH);
final HBox gui = new HBox(10, layout, this.display);
final HBox gui = new HBox(10, layout, pane);
gui.setAlignment(Pos.CENTER);
StackPane.setMargin(gui, new Insets(10));

View File

@ -47,7 +47,7 @@ import maps.Projection;
public class MapOptimizer extends Application {
private static final Projection[] EXISTING_PROJECTIONS = { Projection.HOBO_DYER, Projection.ROBINSON,
Projection.PLATE_CARREE, Projection.LEE };
Projection.PLATE_CARREE, Projection.PEIRCE_QUINCUNCIAL };
private static final double[] WEIGHTS = { .083, .20, .33, .50, .71, 1.0, 1.4, 2.0, 3.0, 5.0, 12. };
private static final int NUM_DESCENT = 40;
private LineChart<Number, Number> chart;
@ -68,15 +68,15 @@ public class MapOptimizer extends Application {
new NumberAxis("Shape distortion", 0, 1, 0.2));
chart.setCreateSymbols(true);
chart.setAxisSortingPolicy(SortingPolicy.NONE);
double[][][] globe = Projection.globe(0.02);
double[][][] globe = Projection.globe(0.01);
PrintStream log = new PrintStream(new File("output/parameters.txt"));
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.HYPERELLIPOWER, globe, log));
chart.getData().add(optimizeFamily(Projection.TETRAPOWER, globe, log));
chart.getData().add(optimizeFamily(Projection.TETRAFILLET, 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));
System.out.println("Total time elapsed: "+
(System.currentTimeMillis()-startTime)/1000.+"s");
@ -119,11 +119,10 @@ public class MapOptimizer extends Application {
for (int i = 0; i < params.length; i ++) params[i] = bounds[i][0]; //initialize params
while (true) { //start with brute force
System.out.println(Arrays.toString(params));
double[] distortions = proj.avgDistortion(points, params);
System.out.println(Arrays.toString(params)+": "+Arrays.toString(distortions));
for (int k = 0; k < WEIGHTS.length; k ++) {
final double avgDist = Math.pow(distortions[0],1.5) +
WEIGHTS[k]*Math.pow(distortions[1],1.5);
final double avgDist = weighDistortion(WEIGHTS[k], distortions);
if (avgDist < currentBest[k][0]) {
currentBest[k][0] = avgDist;
currentBest[k][1] = distortions[0];
@ -134,10 +133,11 @@ public class MapOptimizer extends Application {
int i;
for (i = 0; i < params.length; i ++) { //iterate the parameters
if (params[i] < bounds[i][1]+1e-5) {
final double step = (bounds[i][1]-bounds[i][0])/Math.floor(Math.pow(16, 1./params.length));
if (params[i]+step < bounds[i][1]+1e-5) {
for (int j = 0; j < i; j ++)
params[j] = bounds[j][0];
params[i] += (bounds[i][1]-bounds[i][0])/Math.floor(Math.pow(16, 1./params.length));
params[i] += step;
break;
}
}

View File

@ -65,7 +65,7 @@ public enum Projection {
}
},
PLATE_CARREE("Plate Carrée", 2., 0b1111, "cylindrical", "equidistant") {
PLATE_CARREE("Plate Carrée", 2., 0b1111, "cylindrical", "equidistant", null, "focused on the equator") {
public double[] project(double lat, double lon, double[] params) {
return new double[] {lon, lat};
}
@ -74,6 +74,20 @@ public enum Projection {
}
},
EQUIRECTANGULAR("Equirectangular", "A linear mapping from longitude and latitude to x and y",
2, 0b1111, "cylindrical", "equidistant",
new String[]{"Std. parallel"}, new double[][]{{0, 85, 0}}) {
public double[] project(double lat, double lon, double[] params) {
return new double[] {lon, lat/Math.cos(Math.toRadians(params[0]))};
}
public double[] inverse(double x, double y, double[] params) {
return new double[] {y*Math.PI/2, x*Math.PI};
}
public double getAspectRatio(double[] params) {
return Math.PI*Math.pow(Math.cos(Math.toRadians(params[0])), 2);
}
},
GALL_PETERS("Gall-Peters", 1.571, 0b1111, "cylindrical", "equal-area", "somewhat controversial") {
public double[] project(double lat, double lon, double[] params) {
return new double[] {lon, Math.sin(lat)*Math.PI/1.571};
@ -83,7 +97,8 @@ public enum Projection {
}
},
HOBO_DYER("Hobo-Dyer", 1.977, 0b1111, "cylindrical", "equal-area", "with least distortion at 37.5°") {
HOBO_DYER("Hobo-Dyer", 1.977, 0b1111, "cylindrical", "equal-area",
null, "with least distortion at 37.5°") {
public double[] project(double lat, double lon, double[] params) {
return new double[] {lon, Math.sin(lat)*Math.PI/1.977};
}
@ -92,7 +107,8 @@ public enum Projection {
}
},
BEHRMANN("Behrmann", 2.356, 0b1111, null, "cylindrical", "equal-area", "with least distortion at 30°") {
BEHRMANN("Behrmann", 2.356, 0b1111, "cylindrical", "equal-area",
null, "with least distortion at 30°") {
public double[] project(double lat, double lon, double[] params) {
return new double[] {lon, Math.sin(lat)*Math.PI/2.356};
}
@ -398,46 +414,45 @@ public enum Projection {
},
TOBLER("Tobler", "An equal-area projection shaped like a hyperellipse (in case you're wondering about gamma, it's calculated automatically)",
2., 0b1001, "pseudocylindrical", "equal-area",new String[]{"alpha","K"},
new double[][] {{0,1,0}, {1,8,2.5}}) {
2., 0b1001, "pseudocylindrical", "equal-area",new String[]{"Std. Parallel","alpha","K"},
new double[][] {{0,85,37.5}, {0,1,0}, {1,8,2.5}}) {
private double[] Z; //Z[i] = sin(phi) when y = i/(Z.length-1)
private double[] lastParams;
private void generateZ(int n, double[] params) {
final double e = NumericalAnalysis.simpsonIntegrate(0, 1,
Tobler::hyperEllipse, .0001, params);
Z = NumericalAnalysis.simpsonODESolve(1, n,
Tobler::dZdY, Math.min(1./n,.0001),
params[0], params[1], e);
}
public List<List<double[]>> transform(List<List<double[]>> curves, int step,
double[] params, double[] pole, DoubleConsumer tracker) {
generateZ(10000, params);
return super.transform(curves, step, params, pole, tracker); //sneak z in where params used to be
e, params[1], params[2]);
lastParams = params.clone();
}
public double[] project(double lat, double lon, double[] params) {
if (Z == null) generateZ(10000, params);
if (Z == null || !Arrays.equals(params, lastParams))
generateZ(10000, params);
final double z0 = Math.abs(Math.sin(lat));
final int i = Arrays.binarySearch(Z, z0);
final double y;
if (i >= 0)
y = i/(Z.length-1.);
else if (-i-1 >= Z.length)
y = Z[Z.length-1];
else
y = Math2.linInterp(z0, Z[-i-2], Z[-i-1], -i-2, -i-1)/
(Z.length-1.);
final double ar = Math.pow(Math.cos(Math.toRadians(params[0])),2);
return new double[] {
Tobler.X(y, lon, params), Math.PI/2*y*Math.signum(lat) };
}
public double[][][] map(double w, double h, double[] params, double[] pole, DoubleConsumer tracker) { //generate a matrix of coordinates based on a map projection
final int n = (int) h; //just so I don't forget
generateZ(n, params);
final double[][][] output = super.map(w, h, params, pole, tracker);
Z = null; // set this to null so no one else accidentally uses it
return output;
Tobler.X(y, lon, params), y*Math.signum(lat)/ar };
}
public double[] inverse(double x, double y, double[] params) {
if (Z == null || !Arrays.equals(params, lastParams))
generateZ(10000, params);
return new double[] {
Math.asin(Z[(int)Math.round(Math.abs(y)*(Z.length-1))])*Math.signum(y),
Tobler.lam(x,y,params) };
}
public double getAspectRatio(double[] params) {
return Math.pow(Math.cos(Math.toRadians(params[0])),2) * Math.PI;
}
},
VAN_DER_GRINTEN("Van der Grinten", "A circular compromise map that is popular for some reason",

View File

@ -31,26 +31,26 @@ package maps;
public class Tobler {
public static final double lam(double x, double y, double[] params) {
final double a = params[0];
final double a = params[1];
return Math.PI * x / Math.abs(a + (1-a)*hyperEllipse(y, params));
}
public static final double X(double y, double lam, double[] params) {
final double a = params[0];
final double a = params[1];
return lam * Math.abs(a + (1-a)*hyperEllipse(y, params));
}
public static final double dZdY(double y, double[] params) {
final double a = params[0], e = params[2];
final double a = params[1], e = params[0];
return Math.abs((a + (1-a)*hyperEllipse(y, params))/
(a + (1-a)*e));
}
public static final double hyperEllipse(double y, double[] params) {
final double k = params[1];
final double k = params[2];
return Math.pow(1 - Math.pow(Math.abs(y),k), 1/k);
}