mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-12-24 00:00:03 -05:00
Imma lambda-n your face!
The MapOptimizer plots some things now. I rewrote a bunch of MapAnalyzer to work with functional interfaces instead of Strings in preparation for creating new parametric map projections that will be defined only with lambda functions.
This commit is contained in:
parent
ee5811bea8
commit
ef37ec361e
@ -42,9 +42,10 @@ import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import util.ProgressBarDialog;
|
||||
import util.Stat;
|
||||
|
||||
/**
|
||||
* An application to make raster oblique aspects of map projections
|
||||
* An application to analyze the characteristics of map projections
|
||||
*
|
||||
* @author Justin Kunimune
|
||||
*/
|
||||
@ -234,14 +235,15 @@ public class MapAnalyzer extends Application {
|
||||
avgShapeDistort.setText("...");
|
||||
});
|
||||
|
||||
final String p = projectionChooser.getValue();
|
||||
final String pName = projectionChooser.getValue();
|
||||
final Projection p = projFromName(pName);
|
||||
final double[][][] distortionM =
|
||||
calculateDistortion(map(250, p), p);
|
||||
calculateDistortion(map(250, pName), p);
|
||||
|
||||
output.setImage(makeGraphic(distortionM));
|
||||
|
||||
final double[][][] distortionG =
|
||||
calculateDistortion(globe(0.02, p), p);
|
||||
calculateDistortion(globe(0.02), p);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
sizeChart.getData().add(histogram(distortionG[0],
|
||||
@ -249,8 +251,8 @@ public class MapAnalyzer extends Application {
|
||||
shapeChart.getData().add(histogram(distortionG[1],
|
||||
0,1.6,14, false));
|
||||
|
||||
avgSizeDistort.setText(format(stdDev(distortionG[0])));
|
||||
avgShapeDistort.setText(format(average(distortionG[1])));
|
||||
avgSizeDistort.setText(format(Stat.stdDev(distortionG[0])));
|
||||
avgShapeDistort.setText(format(Stat.average(distortionG[1])));
|
||||
});
|
||||
calculate.setDisable(false);
|
||||
return null;
|
||||
@ -264,24 +266,26 @@ public class MapAnalyzer extends Application {
|
||||
|
||||
|
||||
private void startFinalizingMap() {
|
||||
final String p = projectionChooser.getValue();
|
||||
final String pName = projectionChooser.getValue();
|
||||
final Projection p = projFromName(projectionChooser.getValue());
|
||||
|
||||
ProgressBarDialog pBar = new ProgressBarDialog();
|
||||
pBar.show();
|
||||
new Thread(() -> {
|
||||
final double[][][] distortion = calculateDistortion(map(1000,p), p, pBar);
|
||||
final double[][][] distortion = calculateDistortion(
|
||||
map(1000,pName), p, pBar);
|
||||
Image graphic = makeGraphic(distortion);
|
||||
Platform.runLater(() -> saveImage(graphic, pBar));
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private double[][][] calculateDistortion(double[][][] points, String proj) {
|
||||
public static double[][][] calculateDistortion(double[][][] points, Projection proj) {
|
||||
return calculateDistortion(points, proj, null);
|
||||
}
|
||||
|
||||
|
||||
private double[][][] calculateDistortion(double[][][] points, String proj,
|
||||
public static double[][][] calculateDistortion(double[][][] points, Projection proj,
|
||||
ProgressBarDialog pBar) { //calculate both kinds of distortion over the given region
|
||||
double[][][] output = new double[2][points.length][points[0].length]; //the distortion matrix
|
||||
|
||||
@ -301,7 +305,7 @@ public class MapAnalyzer extends Application {
|
||||
pBar.setProgress((double)(y+1)/points.length);
|
||||
}
|
||||
|
||||
final double avgArea = average(output[0]); //don't forget to normalize output[0] so the average is zero
|
||||
final double avgArea = Stat.average(output[0]); //don't forget to normalize output[0] so the average is zero
|
||||
for (int y = 0; y < output[0].length; y ++)
|
||||
for (int x = 0; x < output[0][y].length; x ++)
|
||||
output[0][y][x] -= avgArea;
|
||||
@ -310,15 +314,15 @@ public class MapAnalyzer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private double[] getDistortionAt(double[] s0, String proj) { //calculate both kinds of distortion at the given point
|
||||
private static double[] getDistortionAt(double[] s0, Projection proj) { //calculate both kinds of distortion at the given point
|
||||
final double[] output = new double[2];
|
||||
final double dx = 1e-5;
|
||||
|
||||
final double[] s1 = { s0[0], s0[1]+dx/Math.cos(s0[0]) }; //consider a point slightly to the east
|
||||
final double[] s2 = { s0[0]+dx, s0[1] }; //and slightly to the north
|
||||
final double[] p0 = vectormaps.MapProjections.project(s0, proj);
|
||||
final double[] p1 = vectormaps.MapProjections.project(s1, proj);
|
||||
final double[] p2 = vectormaps.MapProjections.project(s2, proj);
|
||||
final double[] p0 = proj.project(s0);
|
||||
final double[] p1 = proj.project(s1);
|
||||
final double[] p2 = proj.project(s2);
|
||||
|
||||
final double dA =
|
||||
(p1[0]-p0[0])*(p2[1]-p0[1]) - (p1[1]-p0[1])*(p2[0]-p0[0]);
|
||||
@ -338,7 +342,7 @@ public class MapAnalyzer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private Image makeGraphic(double[][][] distortion) {
|
||||
private static Image makeGraphic(double[][][] distortion) {
|
||||
WritableImage output = new WritableImage(distortion[0][0].length, distortion[0].length);
|
||||
PixelWriter writer = output.getPixelWriter();
|
||||
for (int y = 0; y < distortion[0].length; y ++) {
|
||||
@ -370,7 +374,24 @@ public class MapAnalyzer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private double[][][] map(int size, String proj) { //generate a matrix of coordinates based on a map projection
|
||||
private void saveImage(Image img, ProgressBarDialog pBar) { // call from the main thread!
|
||||
if (pBar != null)
|
||||
pBar.close();
|
||||
|
||||
final File f = saver.showSaveDialog(stage);
|
||||
if (f != null) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
saveMap.setDisable(true);
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(img,null), "png", f);
|
||||
saveMap.setDisable(false);
|
||||
} catch (IOException e) {}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static double[][][] map(int size, String proj) { //generate a matrix of coordinates based on a map projection
|
||||
int projIdx = 0;
|
||||
for (int i = 0; i < PROJ_ARR.length; i ++) {
|
||||
if (PROJ_ARR[i].equals(proj)) {
|
||||
@ -390,7 +411,7 @@ public class MapAnalyzer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private double[][][] globe(double dt, String proj) { //generate a matrix of coordinates based on the sphere
|
||||
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)) {
|
||||
@ -401,24 +422,7 @@ public class MapAnalyzer extends Application {
|
||||
}
|
||||
|
||||
|
||||
public void saveImage(Image img, ProgressBarDialog pBar) { // call from the main thread!
|
||||
if (pBar != null)
|
||||
pBar.close();
|
||||
|
||||
final File f = saver.showSaveDialog(stage);
|
||||
if (f != null) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
saveMap.setDisable(true);
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(img,null), "png", f);
|
||||
saveMap.setDisable(false);
|
||||
} catch (IOException e) {}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final Series<String, Number> histogram(double[][] values,
|
||||
private static final Series<String, Number> histogram(double[][] values,
|
||||
double min, double max, int num, boolean logarithmic) {
|
||||
int[] hist = new int[num+1]; //this array is the histogram values for min, min+dx, ..., max-dx, max
|
||||
int tot = 0;
|
||||
@ -445,40 +449,25 @@ public class MapAnalyzer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private final double average(double[][] values) { //get the average
|
||||
double s = 0, n = 0;
|
||||
for (double[] row: values) {
|
||||
for (double x: row) {
|
||||
if (Double.isFinite(x)) { //ignore NaN values in the average
|
||||
s += x;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s/n;
|
||||
}
|
||||
|
||||
|
||||
private final double stdDev(double[][] values) {
|
||||
double s = 0, ss = 0, n = 0;
|
||||
for (double[] row: values) {
|
||||
for (double x: row) {
|
||||
if (Double.isFinite(x)) {
|
||||
s += x;
|
||||
ss += x*x;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.sqrt(ss/n - s*s/n*n);
|
||||
}
|
||||
|
||||
|
||||
private final String format(double d) {
|
||||
private static final String format(double d) {
|
||||
if (d < 1000)
|
||||
return Double.toString(Math.round(d*1000.)/1000.);
|
||||
else
|
||||
return "1000+";
|
||||
}
|
||||
|
||||
|
||||
public static final Projection projFromName(String s) {
|
||||
return (p) -> {
|
||||
return vectormaps.MapProjections.project(p[0], p[1], s);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
static interface Projection {
|
||||
public double[] project(double[] coords);
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,9 +4,17 @@ import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ScatterChart;
|
||||
import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.stage.Stage;
|
||||
import mapAnalyzer.MapAnalyzer.Projection;
|
||||
import util.Stat;
|
||||
|
||||
/**
|
||||
* An application to compare and optimize map projections
|
||||
*
|
||||
* @author Justin Kunimune
|
||||
*/
|
||||
public class MapOptimizer extends Application {
|
||||
|
||||
|
||||
@ -23,12 +31,13 @@ public class MapOptimizer extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws Exception {
|
||||
final Series<Number, Number> oldMaps = analyzeAll(EXISTING_PROJECTIONS);
|
||||
double[][][] globe = MapAnalyzer.globe(0.02);
|
||||
final Series<Number, Number> oldMaps = analyzeAll(globe, EXISTING_PROJECTIONS);
|
||||
final Series<Number, Number> hyperMaps = optimizeHyperelliptical();
|
||||
final Series<Number, Number> roundMaps = optimizeElliptihypercosine();
|
||||
|
||||
chart = new ScatterChart<Number, Number>(new NumberAxis("Size distortion", 0, 2, 0.5),
|
||||
new NumberAxis("Shape distortion", 0, Math.PI / 2, 0.5));
|
||||
chart = new ScatterChart<Number, Number>(new NumberAxis("Size distortion", 0, 1, 0.1),
|
||||
new NumberAxis("Shape distortion", 0, 0.5, 0.1));
|
||||
chart.getData().add(oldMaps);
|
||||
chart.getData().add(hyperMaps);
|
||||
chart.getData().add(roundMaps);
|
||||
@ -39,8 +48,15 @@ public class MapOptimizer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> analyzeAll(String... projs) { //analyze and plot the specified preexisting map projections
|
||||
return new Series<Number, Number>();
|
||||
private static Series<Number, Number> analyzeAll(double[][][] points,
|
||||
String... projs) { //analyze and plot the specified preexisting map projections
|
||||
Series<Number, Number> output = new Series<Number, Number>();
|
||||
output.setName("Basic Projections");
|
||||
|
||||
for (String name: projs)
|
||||
output.getData().add(plot(points, MapAnalyzer.projFromName(name)));
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@ -53,4 +69,12 @@ public class MapOptimizer extends Application {
|
||||
return new Series<Number, Number>();
|
||||
}
|
||||
|
||||
|
||||
private static Data<Number, Number> plot(double[][][] pts, Projection proj) {
|
||||
double[][][] distortion = MapAnalyzer.calculateDistortion(pts, proj);
|
||||
return new Data<Number, Number>(
|
||||
Stat.stdDev(distortion[0]),
|
||||
Stat.average(distortion[1]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
38
src/util/Stat.java
Normal file
38
src/util/Stat.java
Normal file
@ -0,0 +1,38 @@
|
||||
package util;
|
||||
|
||||
/**
|
||||
* A class of a few handy statistical methods
|
||||
*
|
||||
* @author Justin Kunimune
|
||||
*/
|
||||
public class Stat {
|
||||
|
||||
public static final double average(double[][] values) { //get the average
|
||||
double s = 0, n = 0;
|
||||
for (double[] row: values) {
|
||||
for (double x: row) {
|
||||
if (Double.isFinite(x)) { //ignore NaN values in the average
|
||||
s += x;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s/n;
|
||||
}
|
||||
|
||||
|
||||
public static final double stdDev(double[][] values) {
|
||||
double s = 0, ss = 0, n = 0;
|
||||
for (double[] row: values) {
|
||||
for (double x: row) {
|
||||
if (Double.isFinite(x)) {
|
||||
s += x;
|
||||
ss += x*x;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.sqrt(ss/n - s*s/n*n);
|
||||
}
|
||||
|
||||
}
|
||||
@ -492,8 +492,11 @@ public class MapProjections extends Application {
|
||||
|
||||
|
||||
public static double[] project(double[] latLon, String p) {
|
||||
double lat = latLon[0];
|
||||
double lon = latLon[1];
|
||||
return project(latLon[0], latLon[1], p);
|
||||
}
|
||||
|
||||
|
||||
public static double[] project(double lat, double lon, String p) {
|
||||
if (p.equals("Pierce Quincuncial"))
|
||||
return quincuncial(lat, lon);
|
||||
else if (p.equals("Equirectangular"))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user