mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-12-24 00:00:03 -05:00
Got a dead lawn but to lazy to do anything?
Hire a Water Man! Get it? Waterman? I implemented the Waterman projection. Well, mostly. The raster part doesn't actually work for what would correspond to Gene Keyes's "equatorial supple zone". The math got too complicated, so I want to push what I have. It also doesn't detatch Antarctica, but I probably just won't do that; I think it detracts from the value of the map. I renamed Tetrahedral to Polyhedral in the process, because I had originally intended to use that framework before I decided Waterman needed its own class. I still might put Dymaxion in Polyhedral. I also fixed a bug with Cahill-Keyes that was cutting off the top and bottom and removed the Butterfly variant of Cahill-Keyes.
This commit is contained in:
parent
f8dcf34d94
commit
d5a792cdb7
@ -79,8 +79,9 @@ import maps.Projection;
|
||||
import maps.Pseudocylindrical;
|
||||
import maps.Arbitrary;
|
||||
import maps.Snyder;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Polyhedral;
|
||||
import maps.Tobler;
|
||||
import maps.Waterman;
|
||||
import maps.WinkelTripel;
|
||||
import utils.Flag;
|
||||
import utils.Math2;
|
||||
@ -106,12 +107,12 @@ 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 = { Waterman.BUTTERFLY, 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,
|
||||
Tetrahedral.LEE_TETRAHEDRAL_RECTANGULAR, Tetrahedral.ACTUAUTHAGRAPH,
|
||||
Tetrahedral.AUTHAPOWER, CahillKeyes.M_MAP, Pseudocylindrical.SINUSOIDAL,
|
||||
Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR, Polyhedral.ACTUAUTHAGRAPH,
|
||||
Polyhedral.AUTHAPOWER, CahillKeyes.M_MAP, Pseudocylindrical.SINUSOIDAL,
|
||||
Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Lenticular.AITOFF,
|
||||
Lenticular.VAN_DER_GRINTEN, Arbitrary.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
|
||||
Misc.PEIRCE_QUINCUNCIAL, Misc.TWO_POINT_EQUIDISTANT, Pseudocylindrical.LEMONS }; //the set of featured projections for the ComboBox
|
||||
@ -125,9 +126,8 @@ public abstract class MapApplication extends Application {
|
||||
{ Azimuthal.EQUAL_AREA, Azimuthal.POLAR, Azimuthal.GNOMONIC, Azimuthal.ORTHOGRAPHIC,
|
||||
Azimuthal.PERSPECTIVE, Azimuthal.STEREOGRAPHIC },
|
||||
{ Conic.ALBERS, Conic.LAMBERT, Conic.EQUIDISTANT },
|
||||
{ Tetrahedral.AUTHAGRAPH, CahillKeyes.BUTTERFLY, CahillKeyes.M_MAP,
|
||||
CahillKeyes.HALF_OCTANT, Tetrahedral.LEE_TETRAHEDRAL_RECTANGULAR,
|
||||
Tetrahedral.LEE_TETRAHEDRAL_TRIANGULAR },
|
||||
{ Polyhedral.AUTHAGRAPH, CahillKeyes.M_MAP, Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR,
|
||||
Polyhedral.LEE_TETRAHEDRAL_TRIANGULAR, Waterman.BUTTERFLY },
|
||||
{ Pseudocylindrical.ECKERT_IV, Pseudocylindrical.KAVRAYSKIY_VII,
|
||||
Pseudocylindrical.MOLLWEIDE, Arbitrary.NATURAL_EARTH, Arbitrary.ROBINSON,
|
||||
Pseudocylindrical.SINUSOIDAL, Tobler.TOBLER },
|
||||
@ -135,9 +135,9 @@ public abstract class MapApplication extends Application {
|
||||
Lenticular.VAN_DER_GRINTEN, WinkelTripel.WINKEL_TRIPEL },
|
||||
{ Snyder.GS50, Misc.GUYOU, Misc.HAMMER_RETROAZIMUTHAL, Pseudocylindrical.LEMONS,
|
||||
Misc.PEIRCE_QUINCUNCIAL, Misc.TWO_POINT_EQUIDISTANT, Misc.FLAT_EARTH },
|
||||
{ MyProjections.EXPERIMENT, Tetrahedral.AUTHAPOWER, Tetrahedral.ACTUAUTHAGRAPH,
|
||||
{ MyProjections.EXPERIMENT, Polyhedral.AUTHAPOWER, Polyhedral.ACTUAUTHAGRAPH,
|
||||
MyProjections.MAGNIFIER, MyProjections.PSEUDOSTEREOGRAPHIC,
|
||||
Tetrahedral.TETRAGRAPH, Tetrahedral.TETRAPOWER,
|
||||
Polyhedral.TETRAGRAPH, Polyhedral.TETRAPOWER,
|
||||
MyProjections.TWO_POINT_EQUALIZED } }; // every projection I have programmed
|
||||
|
||||
private static final String[] ASPECT_NAMES = { "Standard", "Transverse", "Cassini", "Atlantis",
|
||||
|
||||
@ -31,7 +31,6 @@ import java.io.PrintStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import maps.Azimuthal;
|
||||
import maps.CahillKeyes;
|
||||
import maps.Conic;
|
||||
import maps.Cylindrical;
|
||||
import maps.Lenticular;
|
||||
@ -41,8 +40,9 @@ import maps.Projection;
|
||||
import maps.Pseudocylindrical;
|
||||
import maps.Arbitrary;
|
||||
import maps.Snyder;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Polyhedral;
|
||||
import maps.Tobler;
|
||||
import maps.Waterman;
|
||||
import maps.WinkelTripel;
|
||||
import utils.ImageUtils;
|
||||
import utils.PixelMap;
|
||||
@ -63,14 +63,14 @@ public class MapExplainer {
|
||||
Azimuthal.STEREOGRAPHIC.transverse(), Azimuthal.POLAR.transverse(),
|
||||
Azimuthal.EQUAL_AREA.transverse(), Azimuthal.GNOMONIC.transverse(),
|
||||
Azimuthal.ORTHOGRAPHIC.transverse(), Conic.LAMBERT, Conic.EQUIDISTANT, Conic.ALBERS,
|
||||
Tetrahedral.LEE_TETRAHEDRAL_RECTANGULAR, Tetrahedral.AUTHAGRAPH,
|
||||
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, CahillKeyes.BUTTERFLY, Misc.PEIRCE_QUINCUNCIAL.transverse(),
|
||||
WinkelTripel.WINKEL_TRIPEL, Waterman.BUTTERFLY, Misc.PEIRCE_QUINCUNCIAL.transverse(),
|
||||
Snyder.GS50, Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Misc.FLAT_EARTH },
|
||||
|
||||
{ MyProjections.PSEUDOSTEREOGRAPHIC, Tetrahedral.TETRAGRAPH, Tetrahedral.AUTHAPOWER,
|
||||
Tetrahedral.TETRAPOWER, MyProjections.TWO_POINT_EQUALIZED.transverse() } };
|
||||
{ MyProjections.PSEUDOSTEREOGRAPHIC, Polyhedral.TETRAGRAPH, Polyhedral.AUTHAPOWER,
|
||||
Polyhedral.TETRAPOWER, MyProjections.TWO_POINT_EQUALIZED.transverse() } };
|
||||
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
@ -43,7 +43,7 @@ import maps.Lenticular;
|
||||
import maps.Misc;
|
||||
import maps.Projection;
|
||||
import maps.Arbitrary;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Polyhedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
|
||||
@ -57,7 +57,7 @@ public class MapOptimizer extends Application {
|
||||
private static final Projection[] EXISTING_PROJECTIONS = { Cylindrical.HOBO_DYER,
|
||||
Arbitrary.ROBINSON, Lenticular.VAN_DER_GRINTEN, Misc.PEIRCE_QUINCUNCIAL };
|
||||
private static final Projection[] PROJECTIONS_TO_OPTIMIZE = { Tobler.TOBLER,
|
||||
WinkelTripel.WINKEL_TRIPEL, Tetrahedral.TETRAPOWER, Tetrahedral.AUTHAPOWER };
|
||||
WinkelTripel.WINKEL_TRIPEL, Polyhedral.TETRAPOWER, Polyhedral.AUTHAPOWER };
|
||||
private static final double[] WEIGHTS = { 0., .125, .25, .375, .5, .625, .75, .875, 1. };
|
||||
private static final int NUM_BRUTE_FORCE = 100;
|
||||
private static final int NUM_DESCENT = 30;
|
||||
|
||||
@ -55,7 +55,7 @@ import maps.MyProjections;
|
||||
import maps.Projection;
|
||||
import maps.Pseudocylindrical;
|
||||
import maps.Arbitrary;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Polyhedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
|
||||
@ -76,8 +76,8 @@ public class MapPlotter extends Application {
|
||||
Arbitrary.NATURAL_EARTH, Pseudocylindrical.KAVRAYSKIY_VII, Tobler.TOBLER };
|
||||
private static final Projection[] PSEUDOAZM = { Lenticular.AITOFF,
|
||||
MyProjections.PSEUDOSTEREOGRAPHIC };
|
||||
private static final Projection[] TETRAHEDRAL = { Tetrahedral.LEE_TETRAHEDRAL_RECTANGULAR,
|
||||
Tetrahedral.ACTUAUTHAGRAPH, Tetrahedral.AUTHAGRAPH, Tetrahedral.TETRAPOWER };
|
||||
private static final Projection[] TETRAHEDRAL = { Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR,
|
||||
Polyhedral.ACTUAUTHAGRAPH, Polyhedral.AUTHAGRAPH, Polyhedral.TETRAPOWER };
|
||||
private static final Projection[] CHEATY = { Pseudocylindrical.LEMONS,
|
||||
CahillKeyes.HALF_OCTANT };
|
||||
private static final Projection[] OTHER = { Lenticular.VAN_DER_GRINTEN,
|
||||
|
||||
@ -29,9 +29,8 @@ import utils.Math2;
|
||||
import utils.NumericalAnalysis;
|
||||
|
||||
/**
|
||||
* A truncated octohedral map. The projection is Cahill-Keyes, because out of it, Cahill, and Waterman,
|
||||
* it was by far the best-documented, and it's the newest of them so it's presumably best (not that
|
||||
* Tobler World in a Square wasn't invented after Lambert EAC).
|
||||
* A truncated octohedral map. The projection is Cahill-Keyes, because it was invented after Cahill,
|
||||
* so it is presumably better (not that Tobler World in a Square wasn't invented after Lambert EAC).
|
||||
* http://www.genekeyes.com/CKOG-OOo/7-CKOG-illus-&-coastline.html
|
||||
*
|
||||
* @author jkunimune
|
||||
@ -41,7 +40,7 @@ public class CahillKeyes {
|
||||
private static final double lMA = 940; //the distance from triangle vertex M to octant vertex A (the red length)
|
||||
private static final double lMG = 10000; //the altitude of the triangle
|
||||
private static final double lNG = lMG/Math.sqrt(3); //the height of the triangle
|
||||
private static final double lENy = lMA*(Math.sqrt(3)+1)/Math.sqrt(8); //the height difference between the triangle and the octant
|
||||
private static final double lENy = lMA*Math.sqrt(3)/2; //the height difference between the triangle and the octant
|
||||
private static final double lMB = lMA*2/(Math.sqrt(3)-1); //the distance from triangle vertex M to octant vertex B (the blue length)
|
||||
private static final double lGF = lMG/Math.sqrt(3) - lMB; //the distance from triangle vertex G to octant vertex F
|
||||
private static final double lFE = lMA*Math.sqrt(2)/(Math.sqrt(3)-1); //the distance from octant vertex A to octant vertex B (the green length)
|
||||
@ -61,60 +60,15 @@ public class CahillKeyes {
|
||||
private static final double TOLERANCE = 10; //this is a reasonable tolerance when you recall that we're operating on the order of 10,000 units
|
||||
|
||||
|
||||
public static final Projection BUTTERFLY =
|
||||
new Projection(
|
||||
"Cahill-Keyes Butterfly", "An aesthetically pleasing octohedral map arrangement.",
|
||||
4*lMG-2*lMA, 4*lNG-2*lENy, 0b1110, Type.TETRADECAHEDRAL, Property.COMPROMISE) {
|
||||
|
||||
private final double OFFSET_Y = 10000/Math.sqrt(3);
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final double centralLon = Math.floor(lon/(Math.PI/2))*Math.PI/2 + Math.PI/4; //get the octant longitude
|
||||
final double[] mjCoords = innerProjectD(
|
||||
Math.toDegrees(Math.abs(lat)), Math.toDegrees(Math.abs(lon - centralLon)));
|
||||
double mjX = mjCoords[0];
|
||||
double mjY = mjCoords[1];
|
||||
|
||||
if (lat < 0) //if it's in the souther hemisphere
|
||||
mjX = 2*lMG - mjX; //flip it around
|
||||
if (lon < centralLon) //if the relative longitude is negative
|
||||
mjY = -mjY; //flip it the other way
|
||||
return new double[] {
|
||||
mjX*Math.sin(centralLon*2/3) + mjY*Math.cos(centralLon*2/3),
|
||||
-mjX*Math.cos(centralLon*2/3) + mjY*Math.sin(centralLon*2/3) + OFFSET_Y };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
y -= OFFSET_Y;
|
||||
double tht = Math.atan2(x, -y);
|
||||
if (Math.abs(tht) > 5*Math.PI/6) return null; //only show a little bit of extra
|
||||
|
||||
double quadrAngle = (Math.floor(tht/(Math.PI/3))+2)*Math.PI/3; //the angle of the centre of the quadrant, measured widdershins from -y
|
||||
double centralLon = quadrAngle*1.5 - 3*Math.PI/4; //the central meridian of this quadrant
|
||||
double mjX = -x*Math.cos(quadrAngle) - y*Math.sin(quadrAngle);
|
||||
double mjY = x*Math.sin(quadrAngle) - y*Math.cos(quadrAngle);
|
||||
|
||||
double[] relCoords = innerInverseD(Math.min(mjX, 2*lMG-mjX), Math.abs(mjY));
|
||||
if (relCoords == null)
|
||||
return null;
|
||||
|
||||
if (mjY < 0) relCoords[1] *= -1; //the left half of the octant gets shifted west
|
||||
if (mjX > lMG) relCoords[0] *= -1; //the outer rim of the map is the southern hemisphere
|
||||
return new double[] { Math.toRadians(relCoords[0]),
|
||||
Math.toRadians(relCoords[1]) + centralLon };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final Projection M_MAP =
|
||||
new Projection(
|
||||
"Cahill-Keyes M-Profile", "A horizontally arranged octohedral map designed to end all maps.",
|
||||
4*lMG, 3*lNG-2*lENy, 0b1110, Type.TETRADECAHEDRAL, Property.COMPROMISE) {
|
||||
"Cahill-Keyes", "A horizontally arranged octohedral map designed to end all maps.",
|
||||
4*lMG, 3*lNG-2*lENy, 0b1010, Type.TETRADECAHEDRAL, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final int octantNum = (int)Math.floor(lon/(Math.PI/2));
|
||||
final double centralLon = octantNum*Math.PI/2 + Math.PI/4; //get the octant longitude
|
||||
final double[] mjCoords = innerProjectD(
|
||||
final double[] mjCoords = faceProjectD(
|
||||
Math.toDegrees(Math.abs(lat)), Math.toDegrees(Math.abs(lon - centralLon)));
|
||||
double mjX = 10000 - mjCoords[0];
|
||||
double mjY = mjCoords[1];
|
||||
@ -141,7 +95,7 @@ public class CahillKeyes {
|
||||
double diagX = rotDirec*cosRot*(x-offsetX) + sinRot*y;
|
||||
double diagY = sinRot*(x-offsetX) - rotDirec*cosRot*y;
|
||||
|
||||
double[] relCoords = innerInverseD(lMG - Math.abs(diagX), Math.abs(diagY));
|
||||
double[] relCoords = faceInverseD(lMG - Math.abs(diagX), Math.abs(diagY));
|
||||
if (relCoords == null)
|
||||
return null;
|
||||
|
||||
@ -156,19 +110,19 @@ public class CahillKeyes {
|
||||
public static final Projection HALF_OCTANT =
|
||||
new Projection(
|
||||
"Cahill-Keyes Half-Octant", "A single sixteenth of a Cahill-Keyes projection.",
|
||||
lMG-lMA, lNG, 0b0111, Type.TETRADECAHEDRAL, Property.COMPROMISE) {
|
||||
lMG-lMA, lNG, 0b0011, Type.TETRADECAHEDRAL, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
lat = Math.max(0, lat);
|
||||
lon = Math.max(0, Math.min(Math.PI/4, lon));
|
||||
final double[] mjCoords = innerProjectD(
|
||||
final double[] mjCoords = faceProjectD(
|
||||
Math2.round(Math.toDegrees(Math.abs(lat)),9), //I apply the round so that graticule lines exactly on the thing get done properly
|
||||
Math2.round(Math.toDegrees(Math.abs(lon)),9));
|
||||
return new double[] { mjCoords[0] - (lMG+lMA)/2, mjCoords[1] - lNG/2 };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
double[] coordsD = innerInverseD(x + (lMG+lMA)/2, y + lNG/2);
|
||||
double[] coordsD = faceInverseD(x + (lMG+lMA)/2, y + lNG/2);
|
||||
if (coordsD == null)
|
||||
return null;
|
||||
else
|
||||
@ -178,7 +132,7 @@ public class CahillKeyes {
|
||||
|
||||
|
||||
|
||||
private static final double[] innerProjectD(double latD, double lonD) { //convert adjusted lat and lon in degrees to Mary Jo's coordinates
|
||||
private static final double[] faceProjectD(double latD, double lonD) { //convert adjusted lat and lon in degrees to Mary Jo's coordinates
|
||||
final double[][] mer = meridian(lonD);
|
||||
|
||||
if (latD >= 75) { //zone c (frigid zone)
|
||||
@ -208,7 +162,7 @@ public class CahillKeyes {
|
||||
}
|
||||
|
||||
|
||||
private static final double[] innerInverseD(double x, double y) { //convert Mary Jo's coordinates to relative lat and lon in degrees
|
||||
private static final double[] faceInverseD(double x, double y) { //convert Mary Jo's coordinates to relative lat and lon in degrees
|
||||
if (y > x-lMA || y > x/Math.sqrt(3) || y > x*(2-Math.sqrt(3))+bDE ||
|
||||
y > (lMG-x)*(2+Math.sqrt(3))+lGF || x > lMG) //this describes the footprint of the octant
|
||||
return null;
|
||||
|
||||
@ -29,27 +29,27 @@ import utils.Math2;
|
||||
import utils.NumericalAnalysis;
|
||||
|
||||
/**
|
||||
* Projections created by projecting onto and then unfolding a regular tetrahedron
|
||||
* Projections created by projecting onto and then unfolding some kind of polyhedron
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class Tetrahedral {
|
||||
public class Polyhedral {
|
||||
|
||||
private static final double ASIN_ONE_THD = Math.asin(1/3.); //the complement of the angular radius of a face
|
||||
|
||||
public static final TetrahedralProjection LEE_TETRAHEDRAL_RECTANGULAR =
|
||||
new TetrahedralProjection(
|
||||
"Lee Tetrahedral", 0b1001, Configuration.WIDE_FACE, Property.CONFORMAL,
|
||||
public static final PolyhedralProjection LEE_TETRAHEDRAL_RECTANGULAR =
|
||||
new PolyhedralProjection(
|
||||
"Lee Tetrahedral", 0b1001, Configuration.TETRAHEDRON_WIDE_FACE, Property.CONFORMAL,
|
||||
null, "that really deserves more attention") {
|
||||
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
public double[] faceProject(double lat, double lon) {
|
||||
final de.jtem.mfc.field.Complex z = de.jtem.mfc.field.Complex.fromPolar(
|
||||
Math.pow(2, 5/6.)*Math.tan(Math.PI/4-lat/2), lon);
|
||||
final de.jtem.mfc.field.Complex w = Dixon.invFunc(z);
|
||||
return new double[] { w.abs()*1.132, w.arg() }; //I don't understand Dixon functions well enough to say whence the 1.132 comes
|
||||
}
|
||||
|
||||
public double[] innerInverse(double r, double tht) {
|
||||
public double[] faceInverse(double r, double tht) {
|
||||
final de.jtem.mfc.field.Complex w = de.jtem.mfc.field.Complex.fromPolar(
|
||||
r/1.132, tht);
|
||||
final de.jtem.mfc.field.Complex ans = Dixon.leeFunc(w).times(Math.pow(2, -5/6.));
|
||||
@ -60,34 +60,34 @@ public class Tetrahedral {
|
||||
};
|
||||
|
||||
|
||||
public static final TetrahedralProjection LEE_TETRAHEDRAL_TRIANGULAR =
|
||||
new TetrahedralProjection(
|
||||
public static final PolyhedralProjection LEE_TETRAHEDRAL_TRIANGULAR =
|
||||
new PolyhedralProjection(
|
||||
"Lee Tetrahedral (triangular)", 0b1001, Configuration.TRIANGLE_FACE, Property.CONFORMAL,
|
||||
null, "in a triangle, because this is the form in which is was published, even though the rectangle is clearly better") {
|
||||
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
return LEE_TETRAHEDRAL_RECTANGULAR.innerProject(lat, lon);
|
||||
public double[] faceProject(double lat, double lon) {
|
||||
return LEE_TETRAHEDRAL_RECTANGULAR.faceProject(lat, lon);
|
||||
}
|
||||
|
||||
public double[] innerInverse(double r, double tht) {
|
||||
return LEE_TETRAHEDRAL_RECTANGULAR.innerInverse(r, tht);
|
||||
public double[] faceInverse(double r, double tht) {
|
||||
return LEE_TETRAHEDRAL_RECTANGULAR.faceInverse(r, tht);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final TetrahedralProjection TETRAGRAPH =
|
||||
new TetrahedralProjection(
|
||||
"TetraGraph", 0b1111, Configuration.WIDE_FACE, Property.EQUIDISTANT,
|
||||
public static final PolyhedralProjection TETRAGRAPH =
|
||||
new PolyhedralProjection(
|
||||
"TetraGraph", 0b1111, Configuration.TETRAHEDRON_WIDE_FACE, Property.EQUIDISTANT,
|
||||
null, "that I invented") {
|
||||
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
public double[] faceProject(double lat, double lon) {
|
||||
final double tht = lon - Math.floor((lon+Math.PI/3)/(2*Math.PI/3))*(2*Math.PI/3);
|
||||
return new double[] {
|
||||
Math.atan(1/Math.tan(lat)*Math.cos(tht))/Math.cos(tht) /Math.atan(Math.sqrt(2)),
|
||||
lon };
|
||||
}
|
||||
|
||||
public double[] innerInverse(double r, double tht) {
|
||||
public double[] faceInverse(double r, double tht) {
|
||||
final double t0 = Math.floor((tht+Math.PI/3)/(2*Math.PI/3))*(2*Math.PI/3);
|
||||
final double dt = tht-t0;
|
||||
return new double[] {
|
||||
@ -97,8 +97,8 @@ public class Tetrahedral {
|
||||
};
|
||||
|
||||
|
||||
public static final TetrahedralProjection AUTHAGRAPH =
|
||||
new TetrahedralProjection(
|
||||
public static final PolyhedralProjection AUTHAGRAPH =
|
||||
new PolyhedralProjection(
|
||||
"AuthaGraph", "A hip new Japanese map that is almost equal-area.",
|
||||
0b1011, Configuration.AUTHAGRAPH, Property.COMPROMISE) {
|
||||
|
||||
@ -116,14 +116,14 @@ public class Tetrahedral {
|
||||
}
|
||||
|
||||
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
public double[] faceProject(double lat, double lon) {
|
||||
final double lam = lon - (Math.floor(lon/(2*Math.PI/3))*(2*Math.PI/3)+Math.PI/3);
|
||||
final double tht = Math.atan((lam - Math.asin(Math.sin(lam)/Math.sqrt(3)))/Math.PI*Math.sqrt(12));
|
||||
final double p = (Math.PI/2 - lat) / Math.atan(Math.sqrt(2)/Math.cos(lam));
|
||||
return new double[] { Math.pow(p,.707)*Math.sqrt(3)/Math.cos(tht), (lon - lam)/2 + tht };
|
||||
}
|
||||
|
||||
protected double[] innerInverse(double r, double th) {
|
||||
protected double[] faceInverse(double r, double th) {
|
||||
final double dt = th - (Math.floor(th/(Math.PI/3))*(Math.PI/3)+Math.PI/6);
|
||||
final double dl = NumericalAnalysis.newtonRaphsonApproximation(dt, dt*2,
|
||||
(l) -> Math.atan((l - Math.asin(Math.sin(l)/Math.sqrt(3)))/Math.PI*Math.sqrt(12)),
|
||||
@ -137,10 +137,10 @@ public class Tetrahedral {
|
||||
};
|
||||
|
||||
|
||||
public static final TetrahedralProjection AUTHAPOWER =
|
||||
new TetrahedralProjection(
|
||||
public static final PolyhedralProjection AUTHAPOWER =
|
||||
new PolyhedralProjection(
|
||||
"AuthaPower", "A parametrised, rearranged version of my AuthaGraph approximation.",
|
||||
0b1011, Configuration.WIDE_VERTEX, Property.COMPROMISE,
|
||||
0b1011, Configuration.TETRAHEDRON_WIDE_VERTEX, Property.COMPROMISE,
|
||||
new String[] {"Power"}, new double[][] {{.25,1,.7}}) {
|
||||
|
||||
private double k;
|
||||
@ -149,14 +149,14 @@ public class Tetrahedral {
|
||||
this.k = params[0];
|
||||
}
|
||||
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
public double[] faceProject(double lat, double lon) {
|
||||
final double lam = lon - (Math.floor(lon/(2*Math.PI/3))*(2*Math.PI/3)+Math.PI/3);
|
||||
final double tht = Math.atan((lam - Math.asin(Math.sin(lam)/Math.sqrt(3)))/Math.PI*Math.sqrt(12));
|
||||
final double p = (Math.PI/2 - lat) / Math.atan(Math.sqrt(2)/Math.cos(lam));
|
||||
return new double[] { Math.pow(p,k)*Math.sqrt(3)/Math.cos(tht), (lon - lam)/2 + tht };
|
||||
}
|
||||
|
||||
protected double[] innerInverse(double r, double th) {
|
||||
protected double[] faceInverse(double r, double th) {
|
||||
final double dt = th - (Math.floor(th/(Math.PI/3))*(Math.PI/3)+Math.PI/6);
|
||||
final double dl = NumericalAnalysis.newtonRaphsonApproximation(dt, dt*2,
|
||||
(l) -> Math.atan((l - Math.asin(Math.sin(l)/Math.sqrt(3)))/Math.PI*Math.sqrt(12)),
|
||||
@ -170,10 +170,10 @@ public class Tetrahedral {
|
||||
};
|
||||
|
||||
|
||||
public static final TetrahedralProjection ACTUAUTHAGRAPH =
|
||||
new TetrahedralProjection(
|
||||
public static final PolyhedralProjection ACTUAUTHAGRAPH =
|
||||
new PolyhedralProjection(
|
||||
"EquaHedral", "A holey authagraphic tetrahedral projection to put AuthaGraph to shame.",
|
||||
0b1010, Configuration.WIDE_VERTEX, Property.EQUAL_AREA,
|
||||
0b1010, Configuration.TETRAHEDRON_WIDE_VERTEX, Property.EQUAL_AREA,
|
||||
new String[] {"Rho"}, new double[][] {{0,.5,.25}}) {
|
||||
|
||||
private double r02;
|
||||
@ -182,7 +182,7 @@ public class Tetrahedral {
|
||||
this.r02 = 3*Math.pow(params[0], 2);
|
||||
}
|
||||
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
public double[] faceProject(double lat, double lon) {
|
||||
final double lam = lon - (Math.floor(lon/(2*Math.PI/3))*(2*Math.PI/3)+Math.PI/3);
|
||||
final double tht = Math.atan((lam - Math.asin(Math.sin(lam)/Math.sqrt(3)))/Math.PI*Math.sqrt(12));
|
||||
return new double[] {
|
||||
@ -190,7 +190,7 @@ public class Tetrahedral {
|
||||
(lon - lam)/2 + tht };
|
||||
}
|
||||
|
||||
public double[] innerInverse(double r, double th) {
|
||||
public double[] faceInverse(double r, double th) {
|
||||
final double dt = th - (Math.floor(th/(Math.PI/3))*(Math.PI/3)+Math.PI/6);
|
||||
final double R2 = Math.pow(r*Math.cos(dt), 2);
|
||||
if (R2 < r02) return null;
|
||||
@ -204,10 +204,10 @@ public class Tetrahedral {
|
||||
};
|
||||
|
||||
|
||||
public static final TetrahedralProjection TETRAPOWER =
|
||||
new TetrahedralProjection(
|
||||
public static final PolyhedralProjection TETRAPOWER =
|
||||
new PolyhedralProjection(
|
||||
"TetraPower", "A parameterised tetrahedral projection that I invented.", 0b1111,
|
||||
Configuration.WIDE_FACE, Property.COMPROMISE, new String[] {"k1","k2","k3"},
|
||||
Configuration.TETRAHEDRON_WIDE_FACE, Property.COMPROMISE, new String[] {"k1","k2","k3"},
|
||||
new double[][] {{.01,2.,.98},{.01,2.,1.2},{.01,2.,.98}}) {
|
||||
|
||||
private double k1, k2, k3;
|
||||
@ -218,7 +218,7 @@ public class Tetrahedral {
|
||||
this.k3 = params[2];
|
||||
}
|
||||
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
public double[] faceProject(double lat, double lon) {
|
||||
final double t0 = Math.floor((lon+Math.PI/3)/(2*Math.PI/3))*(2*Math.PI/3);
|
||||
final double tht = lon - t0;
|
||||
final double thtP = Math.PI/3*(1 - Math.pow(1-Math.abs(tht)/(Math.PI/2),k1))/(1 - 1/Math.pow(3,k1))*Math.signum(tht);
|
||||
@ -230,7 +230,7 @@ public class Tetrahedral {
|
||||
thtP + t0 };
|
||||
}
|
||||
|
||||
public double[] innerInverse(double r, double tht) {
|
||||
public double[] faceInverse(double r, double tht) {
|
||||
final double t0 = Math.floor((tht+Math.PI/3)/(2*Math.PI/3))*(2*Math.PI/3);
|
||||
final double thtP = tht-t0;
|
||||
final double lamS = (1-Math.pow(1-Math.abs(thtP)*(1-1/Math.pow(3,k1))/(Math.PI/3), 1/k1))*Math.PI/2*Math.signum(thtP);
|
||||
@ -250,12 +250,12 @@ public class Tetrahedral {
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
private static abstract class TetrahedralProjection extends Projection {
|
||||
private static abstract class PolyhedralProjection extends Projection {
|
||||
|
||||
private final Configuration configuration;
|
||||
|
||||
|
||||
public TetrahedralProjection(
|
||||
public PolyhedralProjection(
|
||||
String name, int fisc, Configuration config, Property property,
|
||||
String adjective, String addendum) {
|
||||
super(name, config.width, config.height, fisc, Type.TETRAHEDRAL, property,
|
||||
@ -263,13 +263,13 @@ public class Tetrahedral {
|
||||
this.configuration = config;
|
||||
}
|
||||
|
||||
public TetrahedralProjection(
|
||||
public PolyhedralProjection(
|
||||
String name, String description, int fisc, Configuration config, Property property) {
|
||||
super(name, description, config.width, config.height, fisc, Type.TETRAHEDRAL, property);
|
||||
this.configuration = config;
|
||||
}
|
||||
|
||||
public TetrahedralProjection(
|
||||
public PolyhedralProjection(
|
||||
String name, String description, int fisc, Configuration config, Property property,
|
||||
String[] paramNames, double[][] paramValues) {
|
||||
super(name, description, config.width, config.height, fisc, Type.TETRAHEDRAL, property,
|
||||
@ -278,9 +278,9 @@ public class Tetrahedral {
|
||||
}
|
||||
|
||||
|
||||
protected abstract double[] innerProject(double lat, double lon); //the projection from spherical to polar within a face
|
||||
protected abstract double[] faceProject(double lat, double lon); //the projection from spherical to polar within a face
|
||||
|
||||
protected abstract double[] innerInverse(double x, double y); //I think you can guess
|
||||
protected abstract double[] faceInverse(double x, double y); //I think you can guess
|
||||
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
@ -297,7 +297,7 @@ public class Tetrahedral {
|
||||
}
|
||||
}
|
||||
|
||||
final double[] rth = innerProject(latR, lonR); //apply the projection to the relative coordinates
|
||||
final double[] rth = faceProject(latR, lonR); //apply the projection to the relative coordinates
|
||||
final double r = rth[0];
|
||||
final double th = rth[1] + centrum[3];
|
||||
final double x0 = centrum[4];
|
||||
@ -329,7 +329,7 @@ public class Tetrahedral {
|
||||
final double y0 = centrum[5];
|
||||
|
||||
double[] relCoords =
|
||||
innerInverse(Math.hypot(x-x0, y-y0), Math.atan2(y-y0, x-x0) - th0);
|
||||
faceInverse(Math.hypot(x-x0, y-y0), Math.atan2(y-y0, x-x0) - th0);
|
||||
|
||||
if (relCoords == null)
|
||||
return null;
|
||||
@ -351,7 +351,7 @@ public class Tetrahedral {
|
||||
private static enum Configuration {
|
||||
|
||||
/* LATITUDE, LONGITUDE, STD_PRLL, PLANE_ROT, X, Y */
|
||||
WIDE_FACE(6, 2*Math.sqrt(3), new double[][] { // [<|>] arrangement, face-centred
|
||||
TETRAHEDRON_WIDE_FACE(6, 2*Math.sqrt(3), new double[][] { // [<|>] arrangement, face-centred
|
||||
{ ASIN_ONE_THD, Math.PI, -2*Math.PI/3, -2*Math.PI/3, 2, Math.sqrt(3) },
|
||||
{ ASIN_ONE_THD, Math.PI, 2*Math.PI/3, -Math.PI/3, -2, Math.sqrt(3) },
|
||||
{-Math.PI/2, 0, 2*Math.PI/3, 2*Math.PI/3, 2, -Math.sqrt(3) },
|
||||
@ -371,7 +371,7 @@ public class Tetrahedral {
|
||||
return y > Math.sqrt(3)*Math.abs(x) - 3;
|
||||
}
|
||||
},
|
||||
WIDE_VERTEX(6, 2*Math.sqrt(3), new double[][] { // [<|>] arrangement, vertex-centred
|
||||
TETRAHEDRON_WIDE_VERTEX(6, 2*Math.sqrt(3), new double[][] { // [<|>] arrangement, vertex-centred
|
||||
{ Math.PI/2, 0, Math.PI/3, -Math.PI/3, 0, Math.sqrt(3) },
|
||||
{-ASIN_ONE_THD, 0, 2*Math.PI/3, Math.PI/3, 0, -Math.sqrt(3) },
|
||||
{-ASIN_ONE_THD, 2*Math.PI/3, -2*Math.PI/3, Math.PI, 3, 0 },
|
||||
@ -382,23 +382,35 @@ public class Tetrahedral {
|
||||
},
|
||||
AUTHAGRAPH(4*Math.sqrt(3), 3, new double[][] { // |\/\/`| arrangement, vertex-centred
|
||||
{-ASIN_ONE_THD, Math.PI, 0, -Math.PI/2,-2*Math.sqrt(3)-.6096, 1.5 },
|
||||
{-ASIN_ONE_THD,-Math.PI/3,-2*Math.PI/3, Math.PI/2,-Math.sqrt(3)-.6096, -1.5 },
|
||||
{ Math.PI/2, 0, 0, -Math.PI/2, 0-.6096, 1.5 },
|
||||
{-ASIN_ONE_THD, Math.PI/3, 2*Math.PI/3, Math.PI/2, Math.sqrt(3)-.6096, -1.5 },
|
||||
{-ASIN_ONE_THD,-Math.PI/3, -2*Math.PI/3, Math.PI/2,-Math.sqrt(3)-.6096, -1.5 },
|
||||
{ Math.PI/2, 0, 0, -Math.PI/2, 0-.6096, 1.5 },
|
||||
{-ASIN_ONE_THD, Math.PI/3, 2*Math.PI/3, Math.PI/2, Math.sqrt(3)-.6096, -1.5 },
|
||||
{-ASIN_ONE_THD, Math.PI, 0, -Math.PI/2, 2*Math.sqrt(3)-.6096, 1.5 },
|
||||
{-ASIN_ONE_THD,-Math.PI/3,-2*Math.PI/3, Math.PI/2, 3*Math.sqrt(3)-.6096, -1.5}}) {
|
||||
{-ASIN_ONE_THD,-Math.PI/3, -2*Math.PI/3, Math.PI/2, 3*Math.sqrt(3)-.6096, -1.5}}) {
|
||||
@Override public double[] rotateOOB(double[] oob) {
|
||||
return new double[] { (oob[0]+3*width/2)%width-width/2, oob[1] };
|
||||
}
|
||||
};
|
||||
/* LATITUDE, LONGITUDE, STD_PRLL, PLANE_ROT, X, Y */
|
||||
},
|
||||
WATERMAN_5(-2*Math.sqrt(3), 2*Math.sqrt(3), -6, 2, new double[][] { // <vv> butterfly arrangement, W5 polyhedron
|
||||
{ Math.PI/2, 0, -3*Math.PI/4, -Math.PI, 0, 0 },
|
||||
{ Math.PI/2, 0, -Math.PI/4, -2*Math.PI/3, 0, 0 },
|
||||
{ Math.PI/2, 0, Math.PI/4, -Math.PI/3, 0, 0 },
|
||||
{ Math.PI/2, 0, 3*Math.PI/4, 0, 0, 0 }});
|
||||
/* LATITUDE, LONGITUDE, STD_PRLL, PLANE_ROT, X, Y */
|
||||
|
||||
public final double width, height; //the width and height of a map with this configuration
|
||||
public final double xMin, yMin, width, height; //the width and height of a map with this configuration
|
||||
public final double[][] centrumSet; //the mathematical information about this configuration
|
||||
|
||||
private Configuration(double width, double height, double[][] centrumSet) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this(-width/2, width/2, -height/2, height/2, centrumSet);
|
||||
}
|
||||
|
||||
private Configuration(
|
||||
double xMin, double xMax, double yMin, double yMax, double[][] centrumSet) {
|
||||
this.xMin = xMin;
|
||||
this.yMin = yMin;
|
||||
this.width = xMax - xMin;
|
||||
this.height = yMax - yMin;
|
||||
this.centrumSet = centrumSet;
|
||||
}
|
||||
|
||||
@ -561,8 +561,8 @@ public abstract class Projection {
|
||||
CYLINDRICAL("Cylindrical"), CONIC("Conic"), AZIMUTHAL("Azimuthal"),
|
||||
PSEUDOCYLINDRICAL("Pseudocylindrical"), PSEUDOCONIC("Pseudoconic"),
|
||||
PSEUDOAZIMUTHAL("Pseudoazimuthal"), QUASIAZIMUTHAL("Quasiazimuthal"),
|
||||
TETRAHEDRAL("Tetrahedral"), TETRADECAHEDRAL("Tetradecahedral"), POLYNOMIAL("Polynomial"),
|
||||
STREBE("Strebe"), PLANAR("Planar"), OTHER("Other");
|
||||
TETRAHEDRAL("Tetrahedral"), TETRADECAHEDRAL("Truncated Octohedral"),
|
||||
POLYNOMIAL("Polynomial"), STREBE("Strebe Blend"), PLANAR("Planar"), OTHER("Other");
|
||||
|
||||
private String name;
|
||||
|
||||
|
||||
168
src/maps/Waterman.java
Normal file
168
src/maps/Waterman.java
Normal file
@ -0,0 +1,168 @@
|
||||
package maps;
|
||||
import java.util.Arrays;
|
||||
|
||||
import maps.Projection.Property;
|
||||
import maps.Projection.Type;
|
||||
import utils.Math2;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A class devoted to the Waterman butterfly, because it doesn't really fit into Polyhedral.java or
|
||||
* CahillKeyes.java, and Misc.java has too many projections already.
|
||||
* http://www.watermanpolyhedron.com/methodp.html
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class Waterman {
|
||||
|
||||
private static final double[] xELD =
|
||||
{(Math.sqrt(3)-1)/2, .5*Math.sqrt(3), 1.5*Math.sqrt(3), Double.NaN};
|
||||
private static final double[] dYdL = {0, 2/Math.PI, 6/Math.PI, (4+Math.sqrt(8))/Math.PI};
|
||||
|
||||
private static final double lossX = (Math.sqrt(3)-1)/2; //horizontal dimension interruption loss
|
||||
private static final double lossY = lossX*Math.sqrt(3)/2; //vertical dimension interruption loss
|
||||
private static final double lonDiag = Math.PI/(4+Math.sqrt(8));
|
||||
private static final double sin15 = (Math.sqrt(3)-1)/Math.sqrt(8);
|
||||
private static final double cos15 = (Math.sqrt(3)+1)/Math.sqrt(8);
|
||||
|
||||
|
||||
public static final Projection BUTTERFLY =
|
||||
new Projection(
|
||||
"Waterman", "An aesthetically pleasing octohedral map arrangement.",
|
||||
8*Math.sqrt(3)-2*lossX, 8-2*lossY, 0b1010,
|
||||
Type.TETRADECAHEDRAL, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
double centralLon = Math.floor(lon/(Math.PI/2))*Math.PI/2 + Math.PI/4; //get the octant longitude
|
||||
if (lon == Math.PI) centralLon = 3*Math.PI/4; //a hack to fix a minor issue with the IDL
|
||||
|
||||
double[] mjCoords = faceProject(Math.abs(lat), Math.abs(lon - centralLon));
|
||||
double mjX = mjCoords[0]; //"mj" stands for "Mary Jo Graca". I know she had nothing to do with the Waterman projection, but it means the same thing here as it does in Cahill-Keyes
|
||||
double mjY = mjCoords[1];
|
||||
|
||||
if (lat < 0) //if it's in the southern hemisphere
|
||||
mjX = 4*Math.sqrt(3) - mjX; //flip it around
|
||||
if (lon < centralLon) //if the relative longitude is negative
|
||||
mjY = -mjY; //flip it the other way
|
||||
return new double[] {
|
||||
mjX*Math.sin(centralLon*2/3) + mjY*Math.cos(centralLon*2/3),
|
||||
-mjX*Math.cos(centralLon*2/3) + mjY*Math.sin(centralLon*2/3) + 2 };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
y -= 2;
|
||||
double tht = Math.atan2(x, -y);
|
||||
if (Math.abs(tht) > 5*Math.PI/6) return null; //only show a little bit of extra
|
||||
|
||||
double quadrAngle = (Math.floor(tht/(Math.PI/3))+2)*Math.PI/3; //the angle of the centre of the quadrant, measured widdershins from -y
|
||||
double centralLon = quadrAngle*1.5 - 3*Math.PI/4; //the central meridian of this quadrant
|
||||
double mjX = -x*Math.cos(quadrAngle) - y*Math.sin(quadrAngle);
|
||||
double mjY = x*Math.sin(quadrAngle) - y*Math.cos(quadrAngle);
|
||||
|
||||
double[] relCoords = faceInverse(Math.min(mjX, 4*Math.sqrt(3)-mjX), Math.abs(mjY));
|
||||
if (relCoords == null)
|
||||
return null;
|
||||
|
||||
if (mjY < 0) relCoords[1] *= -1; //the left half of the octant gets shifted west
|
||||
if (mjX > 2*Math.sqrt(3)) relCoords[0] *= -1; //the outer rim of the map is the southern hemisphere
|
||||
return new double[] { relCoords[0], relCoords[1] + centralLon };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
private static double[] faceProject(double lat, double lon) {
|
||||
double[] yELD = jointHeights(lon);
|
||||
double desirLength = Math2.linInterp(lat, Math.PI/2, 0, 0, totalLength(xELD, yELD));
|
||||
|
||||
for (int i = 1; i < xELD.length; i ++) { //now that we've established the meridian, lets place our point on it
|
||||
double l = Math.hypot(xELD[i]-xELD[i-1], yELD[i]-yELD[i-1]); //inch up the meridian
|
||||
if (l >= desirLength || i == xELD.length-1) //if it fits on this segment
|
||||
return new double[] {
|
||||
Math2.linInterp(desirLength, 0, l, xELD[i-1], xELD[i]),
|
||||
Math2.linInterp(desirLength, 0, l, yELD[i-1], yELD[i]) }; //interpolate and return
|
||||
else //if we have further to go
|
||||
desirLength -= l; //mark off this length and continue
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static double[] faceInverse(double x, double y) {
|
||||
if (y > x-(Math.sqrt(3)-1)/2 || y > x/Math.sqrt(3) || y > (2-Math.sqrt(3))*(x+3) ||
|
||||
y > (7+4*Math.sqrt(3))-(2+Math.sqrt(3))*x || x > 2*Math.sqrt(3)) //this describes the footprint of the octant
|
||||
return null;
|
||||
|
||||
double longitude;
|
||||
int i = 0;
|
||||
while (i < xELD.length-1 && xELD[i] < x)
|
||||
i ++; //figure out in which segment it is
|
||||
if (i <= 2) { //well-behaved segments?
|
||||
longitude = y/Math2.linInterp(x, xELD[i-1], xELD[i], dYdL[i-1], dYdL[i]);
|
||||
}
|
||||
else { //the well-behaved part of the last segment?
|
||||
longitude = y/Math2.linInterp(x, xELD[i-1], 2*Math.sqrt(3), dYdL[i-1], dYdL[i]);
|
||||
if (longitude > lonDiag) { //the diagonal part of the last segment?
|
||||
double a = dYdL[2]*dYdL[3]*sin15; //surprisingly, the equation is quadratic here
|
||||
double b = (dYdL[3]*cos15-dYdL[2])*(1.5*Math.sqrt(3)-x) - dYdL[3]*sin15*y - Math.sqrt(3)/2*dYdL[2];
|
||||
double c = Math.sqrt(3)/2*y - x + 1.5*Math.sqrt(3);
|
||||
longitude = (-b - Math.sqrt(b*b - 4*a*c))/(2*a);
|
||||
}
|
||||
}
|
||||
|
||||
double[] yELD = jointHeights(longitude);
|
||||
double totalLength = totalLength(xELD, yELD);
|
||||
double pointLength = Math.hypot(x-xELD[i-1], y-yELD[i-1]);
|
||||
for (int j = 1; j < i; j ++) //add in the lengths of all previous segments
|
||||
pointLength += Math.hypot(xELD[j]-xELD[j-1], yELD[j]-yELD[j-1]);
|
||||
double latitude = Math2.linInterp(pointLength, 0, totalLength, Math.PI/2, 0);
|
||||
|
||||
return new double[] {latitude, longitude};
|
||||
}
|
||||
|
||||
|
||||
private static double[] jointHeights(double lon) { //POSTCONDITION: this also sets the value of xELD[3] based on the longitude. An odd place for a side-effect? Bad coding practice to manipulate static variables? meh. I documented it, didn't I?
|
||||
double[] yELD = new double[xELD.length]; //the height of the intersections of this meridian with the Equal Line Delineations
|
||||
for (int i = 0; i < xELD.length; i ++)
|
||||
yELD[i] = dYdL[i]*lon;
|
||||
if (Math.abs(lon) <= lonDiag) { //if it hits the equatorial ELD on the vertical (hexagon) part
|
||||
xELD[3] = 2*Math.sqrt(3);
|
||||
}
|
||||
else { //if it hits it on the diagonal (square) part
|
||||
xELD[3] = -(Math.abs(lon)-lonDiag)*dYdL[3]*sin15 + 2*Math.sqrt(3);
|
||||
yELD[3] = (Math.abs(lon)-lonDiag)*dYdL[3]*cos15 + 1;
|
||||
}
|
||||
return yELD;
|
||||
}
|
||||
|
||||
|
||||
private static double totalLength(double[] xs, double[] ys) {
|
||||
double totalLength = 0; //compute meridian length
|
||||
for (int i = 1; i < xELD.length; i ++)
|
||||
totalLength += Math.hypot(xs[i]-xs[i-1], ys[i]-ys[i-1]);
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user