Map-Projections/src/maps/MyProjections.java
Justin Kunimune c1eb163af6 Hah! Aitoff _does_ have an inverse solution!
I tweaked MapExplainer to be a little bit better, corrected some minor issues with the Projection metadata, added real descriptions to my custom parameterised projections, and fixed a critical bug with WinkelTripel's inverse solution.
2017-08-28 19:43:54 -10:00

182 lines
6.5 KiB
Java

/**
* 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 org.apache.commons.math3.complex.Complex;
import maps.Projection.Property;
import maps.Projection.Type;
/**
* All of the projections I invented, save the tetrahedral ones, because
* those have so much in common with other tetrahedral projections.
*
* @author jkunimune
*/
public class MyProjections {
public static final Projection MAGNIFIER =
new Projection("Magnifier",
"A novelty map projection that blows up the center way out of proportion",
1., 0b1011, Type.AZIMUTHAL, Property.POINTLESS) {
public double[] project(double lat, double lon) {
final double p = 1/2.0+lat/Math.PI;
final double fp = 1 - 0.1*p - 0.9*Math.pow(p,7);
return new double[] { fp*Math.sin(lon), -fp*Math.cos(lon) };
}
public double[] inverse(double x, double y) {
double R = Math.hypot(x, y);
if (R <= 1)
return new double[] {
Math.PI/2 * (1 - R*.2 - R*R*R*1.8),
Math.atan2(y, x) + Math.PI/2};
else
return null;
}
};
public static final Projection EXPERIMENT =
new Projection("Experiment",
"What happens when you apply a complex differentiable function to a stereographic projection?",
1., 0b0000, Type.OTHER, Property.CONFORMAL) {
public double[] project(double lat, double lon) {
final double wMag = Math.tan(Math.PI/4-lat/2);
final Complex w = new Complex(wMag*Math.sin(lon), -wMag*Math.cos(lon));
Complex z = w.asin().divide(3);
if (z.isInfinite() || z.isNaN()) z = new Complex(0);
return new double[] { z.getReal(), z.getImaginary() };
}
public double[] inverse(double x, double y) {
Complex z = new Complex(3*x+3, y*3);
Complex ans = z.sin();
double p = 2 * Math.atan(ans.abs());
double theta = ans.getArgument() - Math.PI/2;
double lambda = Math.PI/2 - p;
return new double[] {lambda, theta};
}
};
public static final Projection PSEUDOSTEREOGRAPHIC =
new Projection(
"Pseudostereographic", "The logical next step after Aitoff and Hammer",
2, 0b1111, Type.PSEUDOAZIMUTHAL, Property.COMPROMISE) {
public double[] project(double lat, double lon) {
final double a = Math.PI - Math.acos(Math.cos(lat)*Math.cos(lon/2));
final double b = Math.acos(Math.sin(lat)/Math.sin(a));
return new double[] {
Math.signum(lon)/Math.tan(a/2)*Math.sin(b),
Math.cos(b)/Math.tan(a/2)/2 };
}
public double[] inverse(double x, double y) {
double[] transverse = obliquifyPlnr(
Azimuthal.STEREOGRAPHIC.inverse(x/2, y/2), new double[] {0,0,0});
if (transverse == null) return null;
else return new double[] {transverse[0], 2*transverse[1]};
}
};
public static final Projection HYPERELLIPOWER =
new Projection(
"Hyperellipower", "A parametrised pseudocylindrical projection that I invented",
2., 0b1111, Type.PSEUDOCYLINDRICAL, Property.COMPROMISE,
new String[] {"k","n","a"},
new double[][] {{1,3,5},{.5,2.,1.20},{.5,2.,1.13}}) {
private double k, n, a;
public void setParameters(double... params) {
this.k = params[0];
this.n = params[1];
this.a = params[2];
this.aspectRatio = 2*Math.sqrt(n)/a;
}
public double[] project(double lat, double lon) {
final double ynorm = (1-Math.pow(1-Math.abs(lat/(Math.PI/2)), n));
return new double[] {
Math.pow(1 - Math.pow(ynorm, k),1/k)*lon/Math.PI,
ynorm/2/Math.sqrt(n)*a*Math.signum(lat) };
}
public double[] inverse(double x, double y) {
return new double[] {
(1 - Math.pow(1-Math.abs(y), 1/n))*Math.PI/2*Math.signum(y),
x/Math.pow(1 - Math.pow(Math.abs(y),k),1/k)*Math.PI };
}
};
public static final Projection TWO_POINT_EQUALIZED =
new Projection("Two-Point Equalized",
"A projection I invented specifically for viewing small elliptical regions of the Earth",
0, 0b1111, Type.OTHER, Property.EQUIDISTANT, new String[] {"Width"},
new double[][] { {0, 180, 120} }) {
private double theta, a, b;
public void setParameters(double... params) {
theta = Math.toRadians(params[0])/2;
this.a = Math.PI - theta; //semimajor axis
this.b = Math.sqrt(Math.pow(Math.PI-theta, 2) - Math.pow(theta, 2)); //semiminor axis
if (theta == 0) this.aspectRatio = 1;
else if (theta == Math.PI/2) this.aspectRatio = 1.3;
else this.aspectRatio = b/a*Math.sqrt(Math.tan(theta)/theta);
}
public double[] project(double lat, double lon) {
if (theta == 0) return Azimuthal.POLAR.project(lat, lon);
final double d1 = Math.acos(
Math.sin(lat)*Math.cos(theta) - Math.cos(lat)*Math.sin(theta)*Math.cos(lon));
final double d2 = Math.acos(
Math.sin(lat)*Math.cos(theta) + Math.cos(lat)*Math.sin(theta)*Math.cos(lon));
final double k = Math.signum(lon)*Math.sqrt(Math.tan(theta)/theta);
final double s = 1/(Math.PI-theta/2);
return new double[] {
s*k*Math.sqrt(d1*d1 - Math.pow((d1*d1-d2*d2+4*theta*theta)/(4*theta), 2)),
s*(d2*d2-d1*d1)/(4*theta) };
}
public double[] inverse(double x, double y) {
if (theta == 0) return Azimuthal.POLAR.inverse(x, y);
final double d1 = Math.hypot(b*x, a*y - theta);
final double d2 = Math.hypot(b*x, a*y + theta);
if (d1 + d2 > 2*a) return null;
final double phi = Math.asin((Math.cos(d1)+Math.cos(d2))/(2*Math.cos(theta)));
final double lam = Math.signum(x)*
Math.acos((Math.sin(phi)*Math.cos(theta) - Math.cos(d1))/(Math.cos(phi)*Math.sin(theta)));
return new double[] { phi, lam };
}
};
}