diff --git a/src/apps/MapApplication.java b/src/apps/MapApplication.java index f12c7ce..48450a4 100644 --- a/src/apps/MapApplication.java +++ b/src/apps/MapApplication.java @@ -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 }, diff --git a/src/apps/MapExplainer.java b/src/apps/MapExplainer.java index 46f1e40..dec6b02 100644 --- a/src/apps/MapExplainer.java +++ b/src/apps/MapExplainer.java @@ -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, diff --git a/src/maps/Octohedral.java b/src/maps/Octohedral.java new file mode 100644 index 0000000..2a1c238 --- /dev/null +++ b/src/maps/Octohedral.java @@ -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 + } + +} diff --git a/src/maps/Waterman.java b/src/maps/Waterman.java index 593ac24..0ed6c22 100644 --- a/src/maps/Waterman.java +++ b/src/maps/Waterman.java @@ -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;