mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-12-24 00:00:03 -05:00
So long, Runge's Phenomenon!
I implemented some smarter Robinson projections that are a bit faster and smoother.
This commit is contained in:
parent
fe1ede6a50
commit
382eddceb0
BIN
output/graph.png
BIN
output/graph.png
Binary file not shown.
|
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 13 KiB |
@ -34,6 +34,7 @@ import javafx.application.Application;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.LineChart.SortingPolicy;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
@ -51,7 +52,7 @@ public class MapOptimizer extends Application {
|
||||
|
||||
private static final Projection[] EXISTING_PROJECTIONS = { Projection.HOBODYER, Projection.ROBINSON,
|
||||
Projection.EQUIRECTANGULAR, Projection.LEE };
|
||||
private static final double[] WEIGHTS = { .125, .5, 1.0, 2, 8 };
|
||||
private static final double[] WEIGHTS = { .20, .33, .50, .71, 1.0, 1.4, 2.0, 3.0, 5.0 };
|
||||
private static final int NUM_DESCENT = 40;
|
||||
private LineChart<Number, Number> chart;
|
||||
|
||||
@ -70,10 +71,12 @@ public class MapOptimizer extends Application {
|
||||
new NumberAxis("Size distortion", 0, 1, 0.2),
|
||||
new NumberAxis("Shape distortion", 0, 1, 0.2));
|
||||
chart.setCreateSymbols(true);
|
||||
chart.setAxisSortingPolicy(SortingPolicy.NONE);
|
||||
double[][][] globe = MapAnalyzer.globe(0.02);
|
||||
|
||||
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));
|
||||
|
||||
@ -125,7 +128,7 @@ public class MapOptimizer extends Application {
|
||||
}
|
||||
}
|
||||
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));
|
||||
// System.out.println(Arrays.toString(params));
|
||||
|
||||
double[] distortions = avgDistortion(projectionFam, params, points);
|
||||
for (int k = 0; k < WEIGHTS.length; k ++) {
|
||||
@ -144,14 +147,14 @@ public class MapOptimizer extends Application {
|
||||
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);
|
||||
// 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
|
||||
@ -160,7 +163,6 @@ public class MapOptimizer extends Application {
|
||||
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.out.println(Arrays.toString(params));
|
||||
System.arraycopy(params,0, currentBest[k],3, params.length);
|
||||
}
|
||||
|
||||
@ -172,7 +174,7 @@ public class MapOptimizer extends Application {
|
||||
System.out.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+")");
|
||||
System.out.println("\t("+best[1]+", "+best[2]+")");
|
||||
output.getData().add(new Data<Number, Number>(best[1], best[2]));
|
||||
}
|
||||
return output;
|
||||
@ -185,6 +187,12 @@ public class MapOptimizer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeEACylinder(double[][][] points) { //optimize and plot some cylinder maps
|
||||
return optimizeFamily(MapOptimizer::eaCylinder, "E.A. Cylindrical",
|
||||
new double[][] {{1,Math.PI}}, points);
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeTetrapower(double[][][] points) { //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);
|
||||
@ -215,6 +223,13 @@ public class MapOptimizer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private static final double[] eaCylinder(double[] coords, double[] params) { //a equal-area cylindrical map projection with hyperellipse order k and lattitudinal spacind described by x^n/n
|
||||
final double lat = coords[0], lon = coords[1];
|
||||
final double a = params[0];
|
||||
return new double[] {lon, Math.sin(lat)*Math.PI/a};
|
||||
}
|
||||
|
||||
|
||||
private static final double[] tetrapower(double[] coords, double[] params) { //a tetragraph projection using a few power functions to spice it up
|
||||
final double k1 = params[0], k2 = params[1], k3 = params[2];
|
||||
|
||||
|
||||
@ -77,6 +77,15 @@ public enum Projection {
|
||||
}
|
||||
},
|
||||
|
||||
BEHRMANN("Behrmann", 2.356, 0b1111, "cylindrical", "equal-area", null, "with least distortion at 30.5°") {
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {lon, Math.sin(lat)*Math.PI/2.356};
|
||||
}
|
||||
public double[] inverse(double x, double y) {
|
||||
return new double[] { Math.asin(y), x*Math.PI };
|
||||
}
|
||||
},
|
||||
|
||||
LAMBERT_CYLIND("Lambert cylindrical", Math.PI, 0b1111, "cylindrical", "equal-area",
|
||||
null, "with least distortion along the equator") {
|
||||
public double[] project(double lat, double lon) {
|
||||
@ -385,14 +394,12 @@ public enum Projection {
|
||||
ROBINSON("Robinson", "A visually pleasing piecewise compromise map",
|
||||
1.9716, 0b1111, "pseudocylindrical", "compromise") {
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {
|
||||
Robinson.plenFromLat(Math.abs(lat))*lon,
|
||||
Robinson.pdfeFromLat(Math.abs(lat))*Math.signum(lat)*Math.PI/2};
|
||||
return new double[] { Robinson.plenFromLat(lat)*lon,
|
||||
Robinson.pdfeFromLat(lat)*Math.PI/2};
|
||||
}
|
||||
public double[] inverse(double x, double y) {
|
||||
return new double[] {
|
||||
Robinson.latFromPdfe(Math.abs(y))*Math.signum(y),
|
||||
x/Robinson.plenFromPdfe(Math.abs(y))*Math.PI };
|
||||
return new double[] { Robinson.latFromPdfe(y),
|
||||
x/Robinson.plenFromPdfe(y)*Math.PI };
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -34,69 +34,62 @@ import java.util.Arrays;
|
||||
public class Robinson {
|
||||
|
||||
private static final double[][] TABLE = {
|
||||
{ 00, 05, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90,
|
||||
-05,-10,-15,-20,-25,-30 },
|
||||
{ 1.000, 0.9986, 0.9954, 0.990, 0.9822, 0.9730, 0.960, 0.9427, 0.9216, 0.8962, 0.8679, 0.8350, 0.7986, 0.7597, 0.7186, 0.6732, 0.6213, 0.5722, 0.5322,
|
||||
0.9986, 0.9954, 0.990, 0.9822, 0.9730, 0.960 },
|
||||
{ 0.000, 0.0620, 0.1240, 0.186, 0.2480, 0.3100, 0.372, 0.4340, 0.4958, 0.5571, 0.6176, 0.6769, 0.7346, 0.7903, 0.8435, 0.8936, 0.9394, 0.9761, 1.0000,
|
||||
-0.0620,-0.1240,-0.186,-0.2480,-0.3100,-0.372 }
|
||||
{ -90,-85,-80,-75,-70,-65,-60,-55,-50,-45,-40,-35,-30,-25,-20,-15,-10,-05, 00,
|
||||
05, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90 },
|
||||
{ 0.5322, 0.5722, 0.6213, 0.6732, 0.7186, 0.7597, 0.7986, 0.8350, 0.8679, 0.8962, 0.9216, 0.9427, 0.9600, 0.9730, 0.9822, 0.9900, 0.9954, 0.9986, 1.0000,
|
||||
0.9986, 0.9954, 0.9900, 0.9822, 0.9730, 0.9600, 0.9427, 0.9216, 0.8962, 0.8679, 0.8350, 0.7986, 0.7597, 0.7186, 0.6732, 0.6213, 0.5722, 0.5322 },
|
||||
{ -1.0000,-0.9761,-0.9394,-0.8936,-0.8435,-0.7903,-0.7346,-0.6769,-0.6176,-0.5571,-0.4958,-0.4340,-0.3720,-0.3100,-0.2480,-0.1860,-0.1240,-0.0620, 0.0000,
|
||||
0.0620, 0.1240, 0.1860, 0.2480, 0.3100, 0.3720, 0.4340, 0.4958, 0.5571, 0.6176, 0.6769, 0.7346, 0.7903, 0.8435, 0.8936, 0.9394, 0.9761, 1.0000 }
|
||||
};
|
||||
|
||||
private static final int LEN = 19;
|
||||
private static final int ORDER = 2; //half the order of the polynomials used
|
||||
|
||||
|
||||
|
||||
public static final double plenFromLat(double lat) {
|
||||
if (Math.abs(lat) < Math.PI/3)
|
||||
return aitkenInterpolate(Math.toDegrees(lat), TABLE[0], TABLE[1]);
|
||||
else
|
||||
return aitkenInterpolate(Math.toDegrees(lat),
|
||||
Arrays.copyOfRange(TABLE[0], LEN-10,LEN-1),
|
||||
Arrays.copyOfRange(TABLE[1], LEN-10, LEN-1));
|
||||
return smartInterpolate(Math.toDegrees(lat), TABLE[0], TABLE[1], ORDER);
|
||||
}
|
||||
|
||||
|
||||
public static final double pdfeFromLat(double lat) {
|
||||
if (Math.abs(lat) < Math.PI/3)
|
||||
return aitkenInterpolate(Math.toDegrees(lat), TABLE[0], TABLE[2]);
|
||||
else
|
||||
return aitkenInterpolate(Math.toDegrees(lat),
|
||||
Arrays.copyOfRange(TABLE[0], LEN-10,LEN-1),
|
||||
Arrays.copyOfRange(TABLE[2], LEN-10, LEN-1));
|
||||
return smartInterpolate(Math.toDegrees(lat), TABLE[0], TABLE[2], ORDER);
|
||||
}
|
||||
|
||||
|
||||
public static final double latFromPdfe(double pdfe) {
|
||||
if (pdfe < TABLE[2][12])
|
||||
return Math.toRadians(aitkenInterpolate(pdfe, TABLE[2], TABLE[0]));
|
||||
else
|
||||
return Math.toRadians(aitkenInterpolate(pdfe,
|
||||
Arrays.copyOfRange(TABLE[2], LEN-10,LEN-1),
|
||||
Arrays.copyOfRange(TABLE[0], LEN-10, LEN-1)));
|
||||
return Math.toRadians(smartInterpolate(pdfe, TABLE[2], TABLE[0], ORDER));
|
||||
}
|
||||
|
||||
|
||||
public static final double plenFromPdfe(double pdfe) {
|
||||
if (pdfe < TABLE[2][12])
|
||||
return aitkenInterpolate(pdfe, TABLE[2], TABLE[1]);
|
||||
else
|
||||
return aitkenInterpolate(pdfe,
|
||||
Arrays.copyOfRange(TABLE[2], LEN-15,LEN-1),
|
||||
Arrays.copyOfRange(TABLE[1], LEN-15, LEN-1));
|
||||
return smartInterpolate(pdfe, TABLE[2], TABLE[1], ORDER);
|
||||
}
|
||||
|
||||
|
||||
public static final double aitkenInterpolate(double x, //TODO: smart interpolation
|
||||
double[] X, double[] f) { //map from ith column to jth using aitken interpolation
|
||||
final int N = X.length;
|
||||
public static final double smartInterpolate(double x, double[] X, double[] f, int k) {
|
||||
int i = Arrays.binarySearch(X, x);
|
||||
if (i < 0) i = -i - 1; //if you couldn't find it, don't worry about it
|
||||
return aitkenInterpolate(x, X, f,
|
||||
Math.max(i-k,0), Math.min(i+k,X.length)); //just call aitken with the correct bounds
|
||||
}
|
||||
|
||||
|
||||
public static final double aitkenInterpolate(double x, double[] X, double[] f) {
|
||||
return aitkenInterpolate(x, X, f, 0, X.length);
|
||||
}
|
||||
|
||||
public static final double aitkenInterpolate(double x,
|
||||
double[] X, double[] f, int from, int to) { //map from X to f using elements start to end
|
||||
final int N = to - from;
|
||||
final double[][] fx = new double[N][]; // the table of successive approximations
|
||||
|
||||
fx[0] = f; //fill in the zeroth row
|
||||
fx[0] = Arrays.copyOfRange(f, from, to); //fill in the zeroth row
|
||||
|
||||
for (int i = 1; i < N; i ++) { //i+1 is the number of points interpolated on
|
||||
fx[i] = new double[N];
|
||||
for (int j = i; j < N; j ++) { //the points will be 0, ..., i-1, j
|
||||
fx[i][j] = 1/(X[j] - X[i-1])*determ(fx[i-1][i-1], fx[i-1][j],
|
||||
X[i-1] - x, X[j] - x); //I won't bother to explain this; go look up Aitken interpolation
|
||||
fx[i][j] = 1/(X[from+j] - X[from+i-1])*determ(fx[i-1][i-1], fx[i-1][j],
|
||||
X[from+i-1] - x, X[from+j] - x); //I won't bother to explain this; go look up Aitken interpolation
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,5 +113,4 @@ public class Robinson {
|
||||
for (double x = -1; x <= 1; x += 1/1024.)
|
||||
System.out.println(x+", "+aitkenInterpolate(x, X, Y)+";");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user