A Whole New WAAAAAAAAAAAAAAAterman!

I've used that reference before, haven't I?
In any case, I have successfully transfered Waterman to a new Octohedral
class that is far more generalisable than the last one. I now intend to
start moving Cahill-Keyes, and coding in Cahill-Rus, Cahill-Concialdi,
and the detachment of Antarctica. The end goal: more and better
octohedral maps in my collection.
This commit is contained in:
Justin Kunimune 2018-02-05 22:57:37 -10:00
parent b50e63d19d
commit 9d85e98492
4 changed files with 208 additions and 59 deletions

View File

@ -81,12 +81,12 @@ import maps.Cylindrical;
import maps.Lenticular;
import maps.Misc;
import maps.MyProjections;
import maps.Octohedral;
import maps.Polyhedral;
import maps.Projection;
import maps.Pseudocylindrical;
import maps.Snyder;
import maps.Tobler;
import maps.Waterman;
import maps.WinkelTripel;
import utils.Flag;
import utils.Math2;
@ -114,7 +114,7 @@ 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 = { Octohedral.WATERMAN, 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,
@ -135,7 +135,7 @@ public abstract class MapApplication extends Application {
{ Conic.ALBERS, Conic.LAMBERT, Conic.EQUIDISTANT },
{ Polyhedral.AUTHAGRAPH, CahillKeyes.M_MAP, Polyhedral.DYMAXION,
Polyhedral.LEE_TETRAHEDRAL_RECTANGULAR, Polyhedral.LEE_TETRAHEDRAL_TRIANGULAR,
Waterman.BUTTERFLY },
Octohedral.WATERMAN },
{ Pseudocylindrical.ECKERT_IV, Pseudocylindrical.KAVRAYSKIY_VII,
Pseudocylindrical.MOLLWEIDE, Arbitrary.NATURAL_EARTH, Arbitrary.ROBINSON,
Pseudocylindrical.SINUSOIDAL, Tobler.TOBLER },

View File

@ -30,19 +30,19 @@ import java.io.PrintStream;
import javax.imageio.ImageIO;
import maps.Arbitrary;
import maps.Azimuthal;
import maps.Conic;
import maps.Cylindrical;
import maps.Lenticular;
import maps.Misc;
import maps.MyProjections;
import maps.Octohedral;
import maps.Polyhedral;
import maps.Projection;
import maps.Pseudocylindrical;
import maps.Arbitrary;
import maps.Snyder;
import maps.Polyhedral;
import maps.Tobler;
import maps.Waterman;
import maps.WinkelTripel;
import utils.ImageUtils;
import utils.PixelMap;
@ -66,7 +66,7 @@ public class MapExplainer {
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, Waterman.BUTTERFLY, Misc.PEIRCE_QUINCUNCIAL.transverse(),
WinkelTripel.WINKEL_TRIPEL, Octohedral.WATERMAN, Misc.PEIRCE_QUINCUNCIAL.transverse(),
Snyder.GS50, Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Misc.FLAT_EARTH },
{ MyProjections.PSEUDOSTEREOGRAPHIC, Polyhedral.TETRAGRAPH, Polyhedral.AUTHAPOWER,

198
src/maps/Octohedral.java Normal file
View File

@ -0,0 +1,198 @@
/**
* 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 maps.Projection.Property;
/**
* A class of maps that use octohedral octants. Very similar to Polyhedral, but much faster since
* it takes advantage of the fact that everything is orthogonal.
*
* @author jkunimune
*/
public class Octohedral {
public static final Projection WATERMAN = new OctohedralProjection(
"Waterman Butterfly", "A simple Cahill-esque octohedral map arrangement, with Antarctica left on.",
2*Math.sqrt(3), (Math.sqrt(3)-1)/2, 0b1010, Property.COMPROMISE, 3,
Configuration.BUTTERFLY) {
protected double[] faceProject(double lat, double lon) {
return Waterman.faceProject(lat, lon);
}
protected double[] faceInverse(double x, double y) {
return Waterman.faceInverse(x, y);
}
};
private static abstract class OctohedralProjection extends Projection {
private final double size;
private Configuration config;
public OctohedralProjection(String name, String desc, double altitude, double cutSize,
int fisc, Property property, int rating, Configuration config) {
super(name, desc,
config.fullWidth*altitude-config.cutWidth*cutSize,
config.fullHeight*altitude-config.cutHeight*cutSize, fisc,
(cutSize == 0) ? Type.OCTOHEDRAL : Type.TETRADECAHEDRAL, property, rating);
this.size = altitude;
this.config = config;
}
protected abstract double[] faceProject(double lat, double lon);
protected abstract double[] faceInverse(double x, double y);
public double[] project(double lat, double lon) {
double[] octant = config.project(lat, lon); //octant properties
double x0 = octant[0]*size, y0 = octant[1]*size, tht0 = octant[2], lon0 = octant[3];
double[] coords = this.faceProject(Math.abs(lat), Math.abs(lon-lon0));
double xMj = coords[0], yMj = coords[1]; //relative octant coordinates (Mj stands for "Mary Jo Graca")
if (lat < 0) //reflect the southern hemisphere over the equator
xMj = 2*size - xMj;
if (lon-lon0 < 0)
yMj = -yMj;
return new double[] {
x0 + Math.sin(tht0)*xMj + Math.cos(tht0)*yMj,
y0 - Math.cos(tht0)*xMj + Math.sin(tht0)*yMj + config.fullHeight*size/2 };
}
public double[] inverse(double x, double y) {
y = y - config.fullHeight*size/2; //measure from extrapolated top of map, not centre
double[] octant = config.inverse(x/size, y/size);
if (octant == null) return null;
double lon0 = octant[0], x0 = size*octant[1], y0 = size*octant[2], tht0 = octant[3];
double xMj = Math.sin(tht0)*(x-x0) - Math.cos(tht0)*(y-y0);
double yMj = Math.cos(tht0)*(x-x0) + Math.sin(tht0)*(y-y0);
double[] coords = this.faceInverse(Math.min(xMj, 2*size-xMj), Math.abs(yMj));
if (coords == null) return null;
double lat = coords[0], lon = coords[1];
return new double[] { Math.signum(size-xMj)*lat, Math.signum(yMj)*lon + lon0 };
}
}
private enum Configuration {
BUTTERFLY(4, 2, 4/Math.sqrt(3), Math.sqrt(3)) { //the classic four octants splayed out in a nice butterfly shape, with Antarctica divided and attached
private final double Y_OFFSET = -1/Math.sqrt(3);
public double[] project(double lat, double lon) {
if (Math.abs(lon) > Math.PI && lat < 0) {
double sign = Math.signum(lon);
return new double[] {sign, 2/Math.sqrt(3), sign*Math.PI/6, sign*Math.PI};
}
double centralMerid = Math.floor(lon/(Math.PI/2))*Math.PI/2 + Math.PI/4;
return new double[] { 0, Y_OFFSET, centralMerid*2/3., centralMerid };
}
public double[] inverse(double x, double y) {
if (y > (1-Math.abs(x))/Math.sqrt(3)) {
double sign = Math.signum(x);
return new double[] { sign*5*Math.PI/4, sign, 2/Math.sqrt(3), sign*Math.PI/6 };
}
double tht = Math.atan2(x, -y+Y_OFFSET);
if (Math.abs(tht) > 5*Math.PI/6)
return null;
double centralAngle = Math.floor(tht/(Math.PI/3))*Math.PI/3 + Math.PI/6;
return new double[] { centralAngle*3/2., 0, Y_OFFSET, centralAngle };
}
},
M_PROFILE(4, 0, Math.sqrt(3), Math.sqrt(3)) {
public double[] project(double lat, double lon) {
// TODO: Implement this
return null;
}
public double[] inverse(double x, double y) {
// TODO: Implement this
return null;
} //The more compact zigzag configuration with Antarctica divided and attached
},
M_W_S_POLE(4, 0, 3.5/Math.sqrt(3), 1.5*Math.sqrt(3)) {
public double[] project(double lat, double lon) {
// TODO: Implement this
return null;
}
public double[] inverse(double x, double y) {
// TODO: Implement this
return null;
} //Keyes's current configuration, with Antarctica reassembled in the center
},
BAT_SHAPE(2*Math.sqrt(3), 0, 2, 0) {
public double[] project(double lat, double lon) {
// TODO: Implement this
return null;
}
public double[] inverse(double x, double y) {
// TODO: Implement this
return null;
} //Luca Concialdi's obscure "Bat" arrangement that I liked. I don't think it's the best map possible as Luca does, but I do think it's quite neat
};
public final double fullWidth, cutWidth, fullHeight, cutHeight;
private Configuration(double fullWidth, double cutWidth,
double fullHeight, double cutHeight) {
this.fullWidth = fullWidth;
this.cutWidth = cutWidth;
this.fullHeight = fullHeight;
this.cutHeight = cutHeight;
}
public abstract double[] project(double lat, double lon); //calculate the x, y, rotation, and central meridian for this quadrant
public abstract double[] inverse(double x, double y); //calculate the central meridian, x, y, and rotation for this quadrant
}
}

View File

@ -1,6 +1,4 @@
package maps;
import maps.Projection.Property;
import maps.Projection.Type;
import utils.Math2;
/**
@ -28,8 +26,7 @@ import utils.Math2;
*/
/**
* 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.
* A class of methods pertaining to the Waterman projection.
* http://www.watermanpolyhedron.com/methodp.html
*
* @author jkunimune
@ -40,58 +37,12 @@ public class Waterman {
{(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, 3) {
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) {
public static double[] faceProject(double lat, double lon) {
double[] yELD = jointHeights(lon);
double desirLength = Math2.linInterp(lat, Math.PI/2, 0, 0, totalLength(xELD, yELD));
@ -107,7 +58,7 @@ public class Waterman {
return null;
}
private static double[] faceInverse(double x, double y) {
public 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;