mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-12-24 00:00:03 -05:00
Looks like I just optimized the optimizer
I made MapOptimizer print parameters to a file so it's harder to lose them and made a few other tweaks for the better.
This commit is contained in:
parent
d5503de6ca
commit
2e33ee97fd
BIN
output/graph.png
BIN
output/graph.png
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 13 KiB |
@ -87,7 +87,7 @@ public class MapAnalyzer extends Application {
|
||||
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.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET };
|
||||
|
||||
|
||||
private Stage stage;
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
package apps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.UnaryOperator;
|
||||
@ -40,7 +41,6 @@ import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Projection;
|
||||
import util.Math2;
|
||||
|
||||
/**
|
||||
* An application to compare and optimize map projections
|
||||
@ -52,8 +52,8 @@ public class MapOptimizer extends Application {
|
||||
|
||||
private static final Projection[] EXISTING_PROJECTIONS = { Projection.HOBODYER, Projection.ROBINSON,
|
||||
Projection.EQUIRECTANGULAR, Projection.LEE };
|
||||
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 static final double[] WEIGHTS = {.20, .50, 1.0, 2.0, 5.0};//{ .083, .20, .33, .50, .71, 1.0, 1.4, 2.0, 3.0, 5.0, 12. };
|
||||
private static final int NUM_DESCENT = 5;//40;
|
||||
private LineChart<Number, Number> chart;
|
||||
|
||||
|
||||
@ -73,12 +73,13 @@ public class MapOptimizer extends Application {
|
||||
chart.setCreateSymbols(true);
|
||||
chart.setAxisSortingPolicy(SortingPolicy.NONE);
|
||||
double[][][] globe = Projection.globe(0.02);
|
||||
PrintStream log = new PrintStream(new File("output/parameters.txt"));
|
||||
|
||||
chart.getData().add(analyzeAll(globe, EXISTING_PROJECTIONS));
|
||||
chart.getData().add(optimizeHyperelliptical(globe));
|
||||
// chart.getData().add(optimizeEACylinder(globe));
|
||||
chart.getData().add(optimizeTetrapower(globe));
|
||||
chart.getData().add(optimizeTetrafillet(globe));
|
||||
// chart.getData().add(optimizeHyperelliptical(globe, log));
|
||||
// chart.getData().add(optimizeEACylinder(globe, log));
|
||||
chart.getData().add(optimizeTetrapower(globe, log));
|
||||
// chart.getData().add(optimizeTetrafillet(globe, log));
|
||||
|
||||
System.out.println("Total time elapsed: "+
|
||||
(System.currentTimeMillis()-startTime)/1000.+"s");
|
||||
@ -91,6 +92,7 @@ public class MapOptimizer extends Application {
|
||||
"png", new File("output/graph.png"));
|
||||
|
||||
stage.show();
|
||||
log.close();
|
||||
}
|
||||
|
||||
|
||||
@ -108,7 +110,7 @@ public class MapOptimizer extends Application {
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeFamily(BinaryOperator<double[]> projectionFam, String name,
|
||||
double[][] bounds, double[][][] points) { // optimize and plot some maps of a given family maps
|
||||
double[][] bounds, double[][][] points, PrintStream log) { // optimize and plot some maps of a given family maps
|
||||
System.out.println("Optimizing "+name);
|
||||
final double[][] currentBest = new double[WEIGHTS.length][3+bounds.length]; //the 0-3 cols are the min distortions for each weight, the other cols are the values of k and n that caused that
|
||||
for (int k = 0; k < WEIGHTS.length; k ++)
|
||||
@ -118,19 +120,9 @@ 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
|
||||
int i;
|
||||
for (i = 0; i < params.length; i ++) { //iterate the parameters
|
||||
if (params[i] < bounds[i][1]) {
|
||||
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))*.99999999;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == params.length) break; //if you made it through the for loop without breaking (finding a parameter to increment), you're done!
|
||||
// System.out.println(Arrays.toString(params));
|
||||
|
||||
double[] distortions = avgDistortion(projectionFam, params, points);
|
||||
System.out.println(Arrays.toString(params));
|
||||
double[] distortions = Projection.avgDistortion(
|
||||
(coords) -> projectionFam.apply(coords,params), points);
|
||||
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);
|
||||
@ -141,71 +133,99 @@ public class MapOptimizer extends Application {
|
||||
System.arraycopy(params,0, currentBest[k],3, params.length);
|
||||
}
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < params.length; i ++) { //iterate the parameters
|
||||
if (params[i] < 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));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == params.length) break; //if you made it through the for loop without breaking (finding a parameter to increment), you're done!
|
||||
}
|
||||
|
||||
final double h = 1e-7;
|
||||
final double delX = 5e-2;
|
||||
for (int k = 0; k < WEIGHTS.length; k ++) { //now do gradient descent
|
||||
System.arraycopy(currentBest[k],3, params,0, params.length);
|
||||
// System.out.println("Starting gradient descent with weight "+WEIGHTS[k]+" and initial parameters "+Arrays.toString(params));
|
||||
System.out.println("Starting gradient descent with weight "+WEIGHTS[k]+" and initial parameters "+Arrays.toString(params));
|
||||
double fr0 = currentBest[k][0];
|
||||
double[] frd = new double[params.length];
|
||||
for (int i = 0; i < NUM_DESCENT; i ++) {
|
||||
if (i > 0)
|
||||
fr0 = avgDistortion( //calculate the distortion here
|
||||
projectionFam, params, points, WEIGHTS[k]);
|
||||
// System.out.println(Arrays.toString(params)+" -> "+fr0);
|
||||
fr0 = weighDistortion(WEIGHTS[k], Projection.avgDistortion(
|
||||
parametrize(projectionFam,params), points)); //calculate the distortion here
|
||||
System.out.println(Arrays.toString(params)+" -> "+fr0);
|
||||
for (int j = 0; j < params.length; j ++) {
|
||||
params[j] += h;
|
||||
frd[j] = avgDistortion(projectionFam, params, points, WEIGHTS[k]); //and the distortion nearby
|
||||
frd[j] = weighDistortion(WEIGHTS[k], Projection.avgDistortion(
|
||||
parametrize(projectionFam,params), points)); //and the distortion nearby
|
||||
params[j] -= h;
|
||||
}
|
||||
for (int j = 0; j < params.length; j ++)
|
||||
params[j] -= (frd[j]-fr0)/h*delX; //use that to approximate the gradient and go in that direction
|
||||
}
|
||||
System.arraycopy(params,0, currentBest[k],3, params.length);
|
||||
System.arraycopy(avgDistortion(projectionFam, params, points), 0,
|
||||
currentBest[k], 1, 2);
|
||||
System.arraycopy(
|
||||
Projection.avgDistortion(parametrize(projectionFam,params), points),
|
||||
0, currentBest[k], 1, 2);
|
||||
}
|
||||
|
||||
final Series<Number, Number> output = new Series<Number, Number>();
|
||||
output.setName(name);
|
||||
|
||||
System.out.println("We got the best "+name+" projections using:");
|
||||
log.println("We got the best "+name+" projections using:");
|
||||
for (double[] best: currentBest) {
|
||||
System.out.print("\t");
|
||||
log.print("\t");
|
||||
for (int i = 0; i < params.length; i ++)
|
||||
System.out.print("t"+i+"="+best[3+i]+"; ");
|
||||
System.out.println("\t("+best[1]+", "+best[2]+")");
|
||||
log.print("t"+i+"="+best[3+i]+"; ");
|
||||
log.println("\t("+best[1]+", "+best[2]+")");
|
||||
output.getData().add(new Data<Number, Number>(best[1], best[2]));
|
||||
}
|
||||
log.println();
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeHyperelliptical(double[][][] points) { //optimize and plot some hyperelliptical maps
|
||||
private static final double weighDistortion(double weight, double... distortions) {
|
||||
return Math.pow(distortions[0], 1.5) +
|
||||
weight*Math.pow(distortions[1], 1.5);
|
||||
}
|
||||
|
||||
|
||||
private static final UnaryOperator<double[]> parametrize(
|
||||
BinaryOperator<double[]> projFam, double[] params) {
|
||||
return (coords) -> projFam.apply(coords, params);
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeHyperelliptical(
|
||||
double[][][] points, PrintStream log) { //optimize and plot some hyperelliptical maps
|
||||
return optimizeFamily(MapOptimizer::hyperelliptical, "Hyperelliptic",
|
||||
new double[][] {{2.5,5}, {0.5,1.75}, {1.0,2.0}}, points);
|
||||
new double[][] {{2.5,5}, {0.5,1.75}, {1.0,2.0}}, points, log);
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeTetrapower(double[][][] points) { //optimize and plot some hyperelliptical maps
|
||||
private static Series<Number, Number> optimizeTetrapower(
|
||||
double[][][] points, PrintStream log) { //optimize and plot some hyperelliptical maps
|
||||
return optimizeFamily(MapOptimizer::tetrapower, "Tetrapower",
|
||||
new double[][] {{0.25,2.25}, {0.25,2.25}, {.25,2.25}}, points);
|
||||
new double[][] {{0.25,2.25}, {0.25,2.25}, {.25,2.25}}, points, log);
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeTetrafillet(double[][][] points) { //optimize and plot some hyperelliptical maps
|
||||
private static Series<Number, Number> optimizeTetrafillet(
|
||||
double[][][] points, PrintStream log) { //optimize and plot some hyperelliptical maps
|
||||
return optimizeFamily(MapOptimizer::tetrafillet, "Tetrafillet",
|
||||
new double[][] {{0.25,2.25}, {0.25,2.25}, {.25,2.25}}, points);
|
||||
new double[][] {{0.25,2.25}, {0.25,2.25}, {.25,2.25}}, points, log);
|
||||
}
|
||||
|
||||
|
||||
private static Data<Number, Number> plotDistortion(double[][][] pts, UnaryOperator<double[]> xform) {
|
||||
double[][][] distortion = Projection.calculateDistortion(pts, xform);
|
||||
return new Data<Number, Number>(
|
||||
Math2.stdDev(distortion[0]),
|
||||
Math2.mean(distortion[1]));
|
||||
private static Data<Number, Number> plotDistortion(
|
||||
double[][][] pts, UnaryOperator<double[]> xform) {
|
||||
double[] distortion = Projection.avgDistortion(xform, pts);
|
||||
return new Data<Number, Number>(distortion[0], distortion[1]);
|
||||
}
|
||||
|
||||
|
||||
@ -231,8 +251,7 @@ public class MapOptimizer extends Application {
|
||||
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
|
||||
};
|
||||
thtP + t0 };
|
||||
});
|
||||
}
|
||||
|
||||
@ -253,20 +272,5 @@ public class MapOptimizer extends Application {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static double[] avgDistortion(BinaryOperator<double[]> projFam,
|
||||
double[] params, double[][][] points) {
|
||||
final double[][][] distDist = Projection.calculateDistortion(points,
|
||||
(coords) -> projFam.apply(coords, params)); //distortion distribution
|
||||
return new double[] { Math2.stdDev(distDist[0]), Math2.mean(distDist[1]) };
|
||||
}
|
||||
|
||||
|
||||
private static double avgDistortion(BinaryOperator<double[]> projFam,
|
||||
double[] params, double[][][] points, double weight) {
|
||||
final double[] distortions = avgDistortion(projFam, params, points);
|
||||
return Math.pow(distortions[0],1.5) + weight*Math.pow(distortions[1],1.5);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -592,7 +592,8 @@ public enum Projection {
|
||||
public double[] project(double lat, double lon) {
|
||||
final double k1 = 1.4586;
|
||||
final double k2 = 1.2891;
|
||||
final double k3 = 1.9158;
|
||||
final double k3 = 1.9583;
|
||||
|
||||
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;
|
||||
@ -602,8 +603,7 @@ public enum Projection {
|
||||
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
|
||||
};
|
||||
thtP + t0 };
|
||||
});
|
||||
}
|
||||
public double[] inverse(double x, double y) {
|
||||
@ -836,7 +836,7 @@ public enum Projection {
|
||||
}
|
||||
|
||||
|
||||
public static double[] obliquifySphc(double[] coords, double[] pole) { // go from polar coordinates to relative
|
||||
public static final double[] obliquifySphc(double[] coords, double[] pole) { // go from polar coordinates to relative
|
||||
final double lat0 = pole[0];
|
||||
final double lon0 = pole[1];
|
||||
final double tht0 = pole[2];
|
||||
@ -905,6 +905,44 @@ public enum Projection {
|
||||
}
|
||||
|
||||
|
||||
public double[][][] map(int size) {
|
||||
return map(size, this);
|
||||
}
|
||||
|
||||
public static double[][][] map(int size, Projection proj) { //generate a matrix of coordinates based on a map projection
|
||||
final int w = size, h = (int)(size/proj.getAspectRatio());
|
||||
double[][][] output = new double[h][w][2]; //the coordinate matrix
|
||||
|
||||
for (int y = 0; y < output.length; y ++)
|
||||
for (int x = 0; x < output[y].length; x ++)
|
||||
output[y][x] = proj.inverse(2.*(x+.5)/w - 1, 1 - 2.*(y+.5)/h); //s0 is this point on the sphere
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public static double[][][] globe(double dt) { //generate a matrix of coordinates based on the sphere
|
||||
List<double[]> points = new ArrayList<double[]>();
|
||||
for (double phi = -Math.PI/2+dt/2; phi < Math.PI/2; phi += dt) { // make sure phi is never exactly +-tau/4
|
||||
for (double lam = -Math.PI; lam < Math.PI; lam += dt/Math.cos(phi)) {
|
||||
points.add(new double[] {phi, lam});
|
||||
}
|
||||
}
|
||||
return new double[][][] {points.toArray(new double[0][])};
|
||||
}
|
||||
|
||||
|
||||
public double[] avgDistortion(double[][][] points) {
|
||||
return avgDistortion(this::project, points);
|
||||
}
|
||||
|
||||
public static double[] avgDistortion(
|
||||
UnaryOperator<double[]> xform, double[][][] points) {
|
||||
final double[][][] distDist = calculateDistortion(points, xform);
|
||||
return new double[] { Math2.stdDev(distDist[0]), Math2.mean(distDist[1]) };
|
||||
}
|
||||
|
||||
|
||||
public double[][][] calculateDistortion(double[][][] points) {
|
||||
return calculateDistortion(points, this::project);
|
||||
}
|
||||
@ -970,32 +1008,5 @@ public enum Projection {
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public double[][][] map(int size) {
|
||||
return map(size, this);
|
||||
}
|
||||
|
||||
public static double[][][] map(int size, Projection proj) { //generate a matrix of coordinates based on a map projection
|
||||
final int w = size, h = (int)(size/proj.getAspectRatio());
|
||||
double[][][] output = new double[h][w][2]; //the coordinate matrix
|
||||
|
||||
for (int y = 0; y < output.length; y ++)
|
||||
for (int x = 0; x < output[y].length; x ++)
|
||||
output[y][x] = proj.inverse(2.*(x+.5)/w - 1, 1 - 2.*(y+.5)/h); //s0 is this point on the sphere
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public static double[][][] globe(double dt) { //generate a matrix of coordinates based on the sphere
|
||||
List<double[]> points = new ArrayList<double[]>();
|
||||
for (double phi = -Math.PI/2+dt/2; phi < Math.PI/2; phi += dt) { // make sure phi is never exactly +-tau/4
|
||||
for (double lam = -Math.PI; lam < Math.PI; lam += dt/Math.cos(phi)) {
|
||||
points.add(new double[] {phi, lam});
|
||||
}
|
||||
}
|
||||
return new double[][][] {points.toArray(new double[0][])};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user