add Solid Earth projection

there was also a bug where the Liquid Earth projection threw an error if you tried to use a map with points on the pole or antimeridian.  but that's fixd now.  I also fixed a minor issue where it said that the Equal Earth projection had a closed-form inverse when it in fact does not.  I also renamed the liquid earth mesh files.
This commit is contained in:
Justin Kunimune 2025-01-16 21:07:53 -05:00
parent 08ca187e7b
commit 4bd6f3c2c9
10 changed files with 62 additions and 24 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -75,7 +75,7 @@ import maps.EqualEarth;
import maps.Gyorffy;
import maps.HammerRetroazimuthal;
import maps.Lenticular;
import maps.Liquid;
import maps.Sargent;
import maps.Misc;
import maps.Octahedral;
import maps.Polyhedral;
@ -161,7 +161,7 @@ public abstract class MapApplication extends Application {
Lenticular.HAMMER, Lenticular.LAGRANGE, Lenticular.STREBE_95,
Lenticular.VAN_DER_GRINTEN, Lenticular.WAGNER_VIII, WinkelTripel.WINKEL_TRIPEL },
{ Elastic.ELASTIC_I, Elastic.ELASTIC_II, Elastic.ELASTIC_III,
Liquid.LIQUID_EARTH,
Sargent.LIQUID_EARTH, Sargent.SOLID_EARTH,
Danseiji.DANSEIJI_N, Danseiji.DANSEIJI_I, Danseiji.DANSEIJI_II, Danseiji.DANSEIJI_III,
Danseiji.DANSEIJI_IV, Danseiji.DANSEIJI_V, Danseiji.DANSEIJI_VI },
{ Misc.BONNE, Misc.CASSINI, Snyder.GS50, Misc.GUYOU, HammerRetroazimuthal.FULL,

View File

@ -49,8 +49,8 @@ public class EqualEarth {
public static final Projection EQUAL_EARTH = new Projection(
"Equal Earth", "B. Savric, T. Patterson, and B. Jenny", "An equal-area pseudocylindrical projection specifically designed to woo Gall-Peters supporters away from that horrid thing",
null, true, true, true, true, Type.PSEUDOCYLINDRICAL, Property.EQUAL_AREA, 3) {
"Equal Earth", "B. Savric, Tom Patterson, and B. Jenny", "An equal-area pseudocylindrical projection specifically designed to woo Gall-Peters supporters away from that horrid thing",
null, true, true, true, false, Type.PSEUDOCYLINDRICAL, Property.EQUAL_AREA, 3) {
public double[] project(double lat, double lon) {
double th = asin(B*sin(lat));
@ -69,6 +69,31 @@ public class EqualEarth {
};
public static final Projection SARGENT_EQUIVALENT_EARTH = new Projection(
"Equivalent Earth", "R. Sargent", "An equal-area pseudocylindrical projection based on the Equal Earth projection and the cylindrical equal area projection, used as the base of the Solid Earth projection",
null, true, true, true, false, Type.PSEUDOCYLINDRICAL, Property.EQUAL_AREA, 2) {
public double[] project(double lat, double lon) {
double th = asin(B*sin(lat));
return new double[] {
lon/(0.3*B*poly8(th)/cos(th) + 0.7*1.4),
0.3*poly9(th) + 0.7*1.4*sin(th)/B };
}
public double[] inverse(double x, double y) {
double th = NumericalAnalysis.newtonRaphsonApproximation(
y, y/Y_SCALE,
(double th_) -> 0.3*poly9(th_) + 0.7*1.4*sin(th_)/B,
(double th_) -> 0.3*poly8(th_) + 0.7*1.4*cos(th_)/B, 1e-6);
return new double[] { asin(sin(th)/B), x*(0.3*B*poly8(th)/cos(th) + 0.7*1.4) };
}
public void initialize(double... params) throws IllegalArgumentException {
this.shape = Shape.meridianEnvelope(this);
}
};
private static double poly9(double x) {
return A[3]*pow(x,9) + A[2]*pow(x,7) + A[1]*pow(x,3) + A[0]*x;

View File

@ -606,9 +606,6 @@ public class Misc {
// for polar latitudes, switch to an azimuthal projection centered on the pole
else {
double sign = signum(φ0);
if (φ0 < -φMax) {
System.out.println(sign);
}
return new double[] {
sign*(PI/2 - hypot(x - sign*xPole, y - sign*yPole)),
sign*coerceAngle(θPole - atan2(xPole - sign*x, yPole - sign*y)) };

View File

@ -56,16 +56,19 @@ import static utils.Math2.coerceAngle;
*
* @author Justin Kunimune
*/
public class Liquid {
public static final LiquidProjection LIQUID_EARTH = new LiquidProjection(
public class Sargent {
public static final SargentProjection LIQUID_EARTH = new SargentProjection(
"Liquid Earth", "An equal-area map in the shape of the Equal Earth projection, optimized around the continents",
EqualEarth.EQUAL_EARTH, toRadians(11), true, "liquid");
private static class LiquidProjection extends Projection {
EqualEarth.EQUAL_EARTH, toRadians(11), true);
public static final SargentProjection SOLID_EARTH = new SargentProjection(
"Solid Earth", "A map optimised to show off the continents by compressing the oceans",
EqualEarth.SARGENT_EQUIVALENT_EARTH, toRadians(11), true);
private static class SargentProjection extends Projection {
private final String filename; // the data filename
private final Projection baseProjection;
private final double centralMeridian;
private int[][] triangles;
@ -77,14 +80,13 @@ public class Liquid {
private List<int[]>[][] lookupTableTransformed;
public LiquidProjection(
public SargentProjection(
String title, String description, Projection baseProjection,
double centralMeridian, boolean based_on_land, String filename) {
double centralMeridian, boolean based_on_land) {
super(title, "Robert C. Sargent", description, null,
baseProjection.isContinuous(), baseProjection.isComprehensive(), true, true,
Type.OTHER, baseProjection.getProperty(), 4,
new String[0], new double[0][], !based_on_land);
this.filename = filename;
this.baseProjection = baseProjection;
this.centralMeridian = centralMeridian;
}
@ -142,13 +144,17 @@ public class Liquid {
}
/**
* find the triangle (represented as an array of three vertex indices) that contains the given point, using a
* table of cached triangle locations to do so efficiently.
*/
private static int[] findContainingTriangle(
double[] фBins, double[] λBins, List<int[]>[][] lookupTable,
Vector[] vertices, double ф, double λ) {
λ = coerceAngle(λ);
Vector point = new Vector(cos(ф)*cos(λ), cos(ф)*sin(λ), sin(ф));
int i = (int) floor((ф - фBins[0])/(фBins[1] - фBins[0]));
int j = (int) floor((λ - λBins[0])/(λBins[1] - λBins[0]));
int i = min((int) floor((ф - фBins[0])/(фBins[1] - фBins[0])), фBins.length - 2);
int j = min((int) floor((λ - λBins[0])/(λBins[1] - λBins[0])), λBins.length - 2);
triangleLoop:
for (int[] triangle: lookupTable[i][j]) {
for (int k = 0; k < 3; k ++) {
@ -216,14 +222,15 @@ public class Liquid {
return;
// load the files
String filename = "res/" + this.getName().replace(" ", "_") + "_mesh";
double[][] verticesInitial, verticesTransformed;
int[][] triangles;
try {
verticesInitial = readNumpyFloatArray("res/" + this.filename + "_vertices_initial.npy");
verticesTransformed = readNumpyFloatArray("res/" + this.filename + "_vertices_transformed.npy");
triangles = readNumpyIntArray("res/" + this.filename + "_triangles.npy");
verticesInitial = readNumpyFloatArray(filename + "_verts_initial.npy");
verticesTransformed = readNumpyFloatArray(filename + "_verts_transformed.npy");
triangles = readNumpyIntArray(filename + "_tris.npy");
} catch (IOException e) {
throw new IllegalArgumentException("couldn't read the config file for this Liquid Projection: " + e);
throw new IllegalArgumentException("couldn't read the config file for this Sargent Projection: " + e);
}
if (verticesInitial.length != verticesTransformed.length)
throw new IllegalArgumentException("the initial vertices file and the transformed vertices files don't match.");
@ -251,6 +258,15 @@ public class Liquid {
}
/**
* build a table that will allow us to efficiently determine which triangles might contain a given pixel.
* the output will be a table with фBins.length - 1 rows and λBins.length - 1 collums. each entry i,j will be
* a list of triangles that contact at least one point in the range
* фBins[i] <= ф <= фBins[i + 1] && λBins[j] <= λ <= λBins[j]
* thus, if you need to find the triangle that contains a given point, you can find a cell of the table that
* contains that point, and then you only need to check the contained triangles rather than every triangle in
* the mesh.
*/
private static List<int[]>[][] cacheTriangleLocations(
int[][] triangles, Vector[] vertices, double[] фBins, double[] λBins) {
@SuppressWarnings("unchecked")