The Rubber Earth

I integrated this with my other map projection repository, and laid teh groundwork for the best map projections ever! Check out that Danseiji III!
This commit is contained in:
Justin Kunimune 2018-12-23 12:11:54 -10:00
parent 1a89415e7e
commit 33fedc06f8
8 changed files with 1475 additions and 61 deletions

View File

@ -76,6 +76,7 @@ import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import maps.Arbitrary;
import maps.ArbitraryPseudocylindrical;
import maps.Azimuthal;
import maps.Conic;
import maps.Cylindrical;
@ -114,14 +115,14 @@ public abstract class MapApplication extends Application {
private static final KeyCombination CTRL_ENTER = new KeyCodeCombination(KeyCode.ENTER, KeyCodeCombination.CONTROL_DOWN);
public static final Projection[] FEATURED_PROJECTIONS = { Cylindrical.MERCATOR,
public static final Projection[] FEATURED_PROJECTIONS = { Arbitrary.DANSEIJI_III, Cylindrical.MERCATOR,
Cylindrical.EQUIRECTANGULAR, Cylindrical.EQUAL_AREA, Cylindrical.GALL_STEREOGRAPHIC,
Azimuthal.STEREOGRAPHIC, Azimuthal.POLAR, Azimuthal.EQUAL_AREA, Azimuthal.GNOMONIC,
Azimuthal.PERSPECTIVE, Conic.LAMBERT, Conic.EQUIDISTANT, Conic.ALBERS,
Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR, Polyhedral.ACTUAUTHAGRAPH,
Polyhedral.AUTHAPOWER, Octohedral.KEYES_BASIC_M, Pseudocylindrical.SINUSOIDAL,
Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Lenticular.AITOFF,
Lenticular.VAN_DER_GRINTEN, Arbitrary.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
Lenticular.VAN_DER_GRINTEN, ArbitraryPseudocylindrical.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
Misc.PEIRCE_QUINCUNCIAL, Misc.TWO_POINT_EQUIDISTANT, Pseudocylindrical.LEMONS }; //the set of featured projections for the ComboBox
public static final String[] PROJECTION_CATEGORIES = { "Cylindrical", "Azimuthal", "Conic",
@ -138,8 +139,8 @@ public abstract class MapApplication extends Application {
Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR, Polyhedral.LEE_TETRAHEDRAL_TRIANGULAR,
Octohedral.WATERMAN },
{ Pseudocylindrical.ECKERT_IV, Pseudocylindrical.KAVRAYSKIY_VII,
Pseudocylindrical.MOLLWEIDE, Arbitrary.NATURAL_EARTH, Arbitrary.ROBINSON,
Pseudocylindrical.SINUSOIDAL, Tobler.TOBLER },
Pseudocylindrical.MOLLWEIDE, ArbitraryPseudocylindrical.NATURAL_EARTH,
ArbitraryPseudocylindrical.ROBINSON, Pseudocylindrical.SINUSOIDAL, Tobler.TOBLER },
{ Lenticular.AITOFF, Lenticular.HAMMER, Lenticular.STREBE_95,
Lenticular.VAN_DER_GRINTEN, WinkelTripel.WINKEL_TRIPEL },
{ Snyder.GS50, Misc.GUYOU, Misc.HAMMER_RETROAZIMUTHAL, Pseudocylindrical.LEMONS,

View File

@ -32,7 +32,7 @@ import image.SavableImage;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import maps.Arbitrary;
import maps.ArbitraryPseudocylindrical;
import maps.Azimuthal;
import maps.Conic;
import maps.Cylindrical;
@ -67,7 +67,7 @@ public class MapExplainer extends Application {
Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR, Polyhedral.AUTHAGRAPH,
Pseudocylindrical.SINUSOIDAL, Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER,
Lenticular.HAMMER, Lenticular.AITOFF, Lenticular.VAN_DER_GRINTEN,
Arbitrary.ROBINSON, WinkelTripel.WINKEL_TRIPEL, Octohedral.CAHILL_KEYES,
ArbitraryPseudocylindrical.ROBINSON, WinkelTripel.WINKEL_TRIPEL, Octohedral.CAHILL_KEYES,
Octohedral.CAHILL_CONCIALDI, Misc.PEIRCE_QUINCUNCIAL.transverse(), Snyder.GS50,
Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Misc.FLAT_EARTH },
{ MyProjections.PSEUDOSTEREOGRAPHIC, Polyhedral.TETRAGRAPH, Polyhedral.AUTHAPOWER,

View File

@ -39,7 +39,7 @@ import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
import maps.Arbitrary;
import maps.ArbitraryPseudocylindrical;
import maps.Cylindrical;
import maps.Misc;
import maps.Polyhedral;
@ -57,7 +57,7 @@ import utils.linalg.Vector;
public class MapOptimizer extends Application {
private static final Projection[] EXISTING_PROJECTIONS = { Cylindrical.BEHRMANN,
Arbitrary.ROBINSON, Cylindrical.PLATE_CARREE, Cylindrical.GALL_STEREOGRAPHIC,
ArbitraryPseudocylindrical.ROBINSON, Cylindrical.PLATE_CARREE, Cylindrical.GALL_STEREOGRAPHIC,
Misc.PEIRCE_QUINCUNCIAL };
private static final Projection[] PROJECTIONS_TO_OPTIMIZE = { Tobler.TOBLER,
WinkelTripel.WINKEL_TRIPEL, Polyhedral.AUTHAPOWER };

View File

@ -47,7 +47,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import maps.Arbitrary;
import maps.ArbitraryPseudocylindrical;
import maps.Azimuthal;
import maps.Cylindrical;
import maps.Lenticular;
@ -74,8 +74,8 @@ public class MapPlotter extends Application {
Cylindrical.PLATE_CARREE, Cylindrical.GALL_ORTHOGRAPHIC, Cylindrical.BEHRMANN,
Cylindrical.GALL_STEREOGRAPHIC };
private static final Projection[] AZIMUTHAL = { Azimuthal.POLAR };
private static final Projection[] PSEUDOCYL = { Pseudocylindrical.MOLLWEIDE, Arbitrary.ROBINSON,
Arbitrary.NATURAL_EARTH, Pseudocylindrical.KAVRAYSKIY_VII, Tobler.TOBLER };
private static final Projection[] PSEUDOCYL = { Pseudocylindrical.MOLLWEIDE, ArbitraryPseudocylindrical.ROBINSON,
ArbitraryPseudocylindrical.NATURAL_EARTH, Pseudocylindrical.KAVRAYSKIY_VII, Tobler.TOBLER };
private static final Projection[] LENTICULAR = { Lenticular.AITOFF,
Lenticular.VAN_DER_GRINTEN, WinkelTripel.WINKEL_TRIPEL };
private static final Projection[] TETRAHEDRAL = { Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR,

View File

@ -8,7 +8,7 @@ import image.SavableImage;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import maps.Arbitrary;
import maps.ArbitraryPseudocylindrical;
import maps.Azimuthal;
import maps.Conic;
import maps.Cylindrical;
@ -62,7 +62,7 @@ public class MapProducer extends Application {
Cylindrical.BEHRMANN, Cylindrical.HOBO_DYER, Cylindrical.GALL_ORTHOGRAPHIC,
Pseudocylindrical.SINUSOIDAL, Pseudocylindrical.MOLLWEIDE,
Pseudocylindrical.ECKERT_IV, Pseudocylindrical.KAVRAYSKIY_VII,
Arbitrary.ROBINSON, Arbitrary.NATURAL_EARTH, Tobler.TOBLER, Lenticular.AITOFF,
ArbitraryPseudocylindrical.ROBINSON, ArbitraryPseudocylindrical.NATURAL_EARTH, Tobler.TOBLER, Lenticular.AITOFF,
Lenticular.HAMMER, WinkelTripel.WINKEL_TRIPEL, Lenticular.VAN_DER_GRINTEN,
Conic.EQUIDISTANT, Conic.LAMBERT, Conic.ALBERS, Azimuthal.POLAR,
Azimuthal.GNOMONIC, Azimuthal.EQUAL_AREA, Azimuthal.STEREOGRAPHIC,

1300
src/data/danseijiIII.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/**
* MIT License
*
* Copyright (c) 2017 Justin Kunimune
* Copyright (c) 2018 Justin Kunimune
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -23,74 +23,91 @@
*/
package maps;
import java.util.Arrays;
import utils.NumericalAnalysis;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import maps.Projection.Property;
import maps.Projection.Type;
import utils.Math2;
/**
* A class specifically for projections that use arbitrary tables of numbers.
* A class for completely arbitrary projections, where every square degree can be specified anywhere on the plane.
*
* @author jkunimune
* @author Justin Kunimune
*/
public class Arbitrary {
private static final int ORDER = 2; //half the order of the polynomials used
private static final int NE = 0, NW = 1, SW = 2, SE = 3;
private static final int X = 0, Y = 1;
public static final Projection ROBINSON = new ArbitraryProjection(
"Robinson", "Arthur Robinson", 0.5072, new double[][] {
{ -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 }, //Latitude
{ 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 }, //PLEN
{ -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 } //PDFE
});
public static final Projection NATURAL_EARTH = new ArbitraryProjection(
"Natural Earth", "Tom Patterson", 0.520, new double[][] {
{ -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 }, //Latitude
{ 0.5630, 0.6270, 0.6754, 0.7160, 0.7525, 0.7874, 0.8196, 0.8492, 0.8763, 0.9006, 0.9222, 0.9409, 0.9570, 0.9703, 0.9811, 0.9894, 0.9953, 0.9988, 1.0000,
0.9988, 0.9953, 0.9894, 0.9811, 0.9703, 0.9570, 0.9409, 0.9222, 0.9006, 0.8763, 0.8492, 0.8196, 0.7874, 0.7525, 0.7160, 0.6754, 0.6270, 0.5630 }, //PLEN
{ -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.06200, 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 } //PDFE
});
public static final ArbitraryProjection DANSEIJI_III = new ArbitraryProjection(
"Danseiji III", "A map optimised to move distortion from the continents into the oceans",
true, Type.OTHER, Property.COMPROMISE, "danseijiIII.csv");
private static class ArbitraryProjection extends Projection {
private final double yMax;
private final double[][] table;
private double[][] vertices; // the vertex x-y coordinates
private int[][][] cells; // the indices of the corner of each cell
public ArbitraryProjection(String title, String inventor, double aspectRatio, double[][] table) {
super(title, 2, 2*aspectRatio, 0b1111, Type.PSEUDOCYLINDRICAL, Property.COMPROMISE, 2,
null, "designed by "+inventor);
this.yMax = aspectRatio;
this.table = table;
public ArbitraryProjection(String title, String description, boolean interrupted, Type type, Property property, String filename) {
super(title, description, 0, 0, interrupted ? 0b1010 : 0b1011, type, property, 4);
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(String.format("src/data/%s", filename))); // parsing the input mesh is pretty simple
String[] row = in.readLine().split(","); // get the header
vertices = new double[Integer.parseInt(row[0])][2];
cells = new int[Integer.parseInt(row[1])][Integer.parseInt(row[2])][4];
width = Double.parseDouble(row[3]);
height = Double.parseDouble(row[4]);
for (int i = 0; i < vertices.length; i ++) { // do the vertex coordinates
row = in.readLine().split(",");
for (int j = 0; j < vertices[i].length; j ++)
vertices[i][j] = Double.parseDouble(row[j]);
}
for (int i = 0; i < cells.length; i ++) { // get the cell vertices
for (int j = 0; j < cells[i].length; j ++) {
row = in.readLine().split(",");
for (int k = 0; k < cells[i][j].length; k ++)
cells[i][j][k] = Integer.parseInt(row[k]);
}
} // and skip the edge; it's not relevant here
} catch (IOException e) {
System.err.println("Could not load mesh: "+e);
width = 2;
height = 2;
vertices = new double[][] {{1,1},{-1,1},{-1,-1},{1,-1}};
cells = new int[][][] {{{0,1,2,3}}};
} finally {
try {
if (in != null) in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public double[] project(double lat, double lon) {
int i = (int)Math2.floorMod((Math.PI/2-lat)/Math.PI*cells.length, cells.length);
int j = (int)Math2.floorMod((lon+Math.PI)/(2*Math.PI)*cells[i].length, cells[i].length);
double cs = Math2.floorMod((Math.PI/2-lat)/Math.PI*cells.length, 1);
double cw = 1 - Math2.floorMod((lon+Math.PI)/(2*Math.PI)*cells[i].length, 1);
double cn = 1 - cs;
double ce = 1 - cw;
double[] ne = vertices[cells[i][j][NE]], nw = vertices[cells[i][j][NW]],
sw = vertices[cells[i][j][SW]], se = vertices[cells[i][j][SE]];
return new double[] {
lon/Math.PI*smartInterpolate(Math.toDegrees(lat), table[0], table[1], ORDER),
yMax*smartInterpolate(Math.toDegrees(lat), table[0], table[2], ORDER) };
cs*cw*sw[X] + cs*ce*se[X] + cn*cw*nw[X] + cn*ce*ne[X],
cs*cw*sw[Y] + cs*ce*se[Y] + cn*cw*nw[Y] + cn*ce*ne[Y] };
}
public double[] inverse(double x, double y) {
return new double[] {
Math.toRadians(smartInterpolate(y/yMax, table[2], table[0], ORDER)),
Math.PI*x/smartInterpolate(y/yMax, table[2], table[1], ORDER) };
return null; // TODO
}
private static 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 NumericalAnalysis.aitkenInterpolate(x, X, f,
Math.max(i-k,0), Math.min(i+k,X.length)); //just call aitken with the correct bounds
}
}
}

View File

@ -0,0 +1,96 @@
/**
* MIT License
*
* Copyright (c) 2017 Justin Kunimune
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package maps;
import java.util.Arrays;
import utils.NumericalAnalysis;
/**
* A class specifically for pseudocylindrical projections that use arbitrary tables of numbers.
*
* @author jkunimune
*/
public class ArbitraryPseudocylindrical {
private static final int ORDER = 2; //half the order of the polynomials used
public static final Projection ROBINSON = new ArbitraryProjection(
"Robinson", "Arthur Robinson", 0.5072, new double[][] {
{ -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 }, //Latitude
{ 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 }, //PLEN
{ -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 } //PDFE
});
public static final Projection NATURAL_EARTH = new ArbitraryProjection(
"Natural Earth", "Tom Patterson", 0.520, new double[][] {
{ -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 }, //Latitude
{ 0.5630, 0.6270, 0.6754, 0.7160, 0.7525, 0.7874, 0.8196, 0.8492, 0.8763, 0.9006, 0.9222, 0.9409, 0.9570, 0.9703, 0.9811, 0.9894, 0.9953, 0.9988, 1.0000,
0.9988, 0.9953, 0.9894, 0.9811, 0.9703, 0.9570, 0.9409, 0.9222, 0.9006, 0.8763, 0.8492, 0.8196, 0.7874, 0.7525, 0.7160, 0.6754, 0.6270, 0.5630 }, //PLEN
{ -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.06200, 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 } //PDFE
});
private static class ArbitraryProjection extends Projection {
private final double yMax;
private final double[][] table;
public ArbitraryProjection(String title, String inventor, double aspectRatio, double[][] table) {
super(title, 2, 2*aspectRatio, 0b1111, Type.PSEUDOCYLINDRICAL, Property.COMPROMISE, 2,
null, "designed by "+inventor);
this.yMax = aspectRatio;
this.table = table;
}
public double[] project(double lat, double lon) {
return new double[] {
lon/Math.PI*smartInterpolate(Math.toDegrees(lat), table[0], table[1], ORDER),
yMax*smartInterpolate(Math.toDegrees(lat), table[0], table[2], ORDER) };
}
public double[] inverse(double x, double y) {
return new double[] {
Math.toRadians(smartInterpolate(y/yMax, table[2], table[0], ORDER)),
Math.PI*x/smartInterpolate(y/yMax, table[2], table[1], ORDER) };
}
private static 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 NumericalAnalysis.aitkenInterpolate(x, X, f,
Math.max(i-k,0), Math.min(i+k,X.length)); //call aitken with the correct bounds
}
}
}