mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-12-24 00:00:03 -05:00
New Map Projection
I created a new tetrahedral map projection. It's alright, but so far that pseudocylindrical hyper-ellipse one has been the best.
This commit is contained in:
parent
5d6f1b1d55
commit
4080a8be6d
BIN
MapAnalyzer.jar
BIN
MapAnalyzer.jar
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -12,6 +12,7 @@ import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.stage.Stage;
|
||||
import mapAnalyzer.MapAnalyzer.Projection;
|
||||
import util.Stat;
|
||||
import vectormaps.MapProjections;
|
||||
|
||||
/**
|
||||
* An application to compare and optimize map projections
|
||||
@ -21,9 +22,9 @@ import util.Stat;
|
||||
public class MapOptimizer extends Application {
|
||||
|
||||
|
||||
private static final String[] EXISTING_PROJECTIONS = { "Hobo-Dyer", "Mollweide", "Winkel Tripel", "Robinson", "Lee" };
|
||||
private static final String[] EXISTING_PROJECTIONS = { "Hobo-Dyer", "Mollweide", "TetraGraph", "Robinson", "Lee" };
|
||||
private static final double[] WEIGHTS = { .11, .43, 1.0, 2.3, 9.0 };
|
||||
private static final int NUM_DESCENT = 40;
|
||||
private static final int NUM_DESCENT = 10;//40;
|
||||
private ScatterChart<Number, Number> chart;
|
||||
|
||||
|
||||
@ -43,8 +44,8 @@ public class MapOptimizer extends Application {
|
||||
double[][][] globe = MapAnalyzer.globe(0.02);
|
||||
|
||||
chart.getData().add(analyzeAll(globe, EXISTING_PROJECTIONS));
|
||||
chart.getData().add(optimizeHyperelliptical(globe));
|
||||
//chart.getData().add(optimizeTetrapower(globe));
|
||||
//chart.getData().add(optimizeHyperelliptical(globe));
|
||||
chart.getData().add(optimizeTetrapower(globe));
|
||||
//chart.getData().add(optimizeTetrafilllet(globe));
|
||||
|
||||
System.out.println("Total time elapsed: "+
|
||||
@ -151,6 +152,12 @@ public class MapOptimizer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeTetrapower(double[][][] points) { //optimize and plot some hyperelliptical maps
|
||||
return optimizeFamily(MapOptimizer::tetrapower, "Tetrapower",
|
||||
new double[][] {{0.25,2.25}, {0.25,2.25}}, points);
|
||||
}
|
||||
|
||||
|
||||
private static Data<Number, Number> plotDistortion(double[][][] pts, Projection proj) {
|
||||
double[][][] distortion = MapAnalyzer.calculateDistortion(pts, proj);
|
||||
return new Data<Number, Number>(
|
||||
@ -169,6 +176,24 @@ public class MapOptimizer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private static final double[] tetrapower(double[] coords, double[] params) { //a tetragraph projection using a few power functions to spice it up
|
||||
final double k1 = params[0], k2 = params[0];
|
||||
|
||||
return MapProjections.tetrahedralProjection(coords[0], coords[1], (coordR) -> {
|
||||
final double t0 = Math.floor(coordR[1]/(2*Math.PI/3))*(2*Math.PI/3) + Math.PI/3;
|
||||
final double tht = coordR[1] - 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);
|
||||
final double rmax = .5/Math.cos(thtP); //the max radius of this triangle (in the plane)
|
||||
final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; //normalized tetragraph radius
|
||||
return new double[] {
|
||||
(1 - Math.pow(1-rtgf,k2))/(1 - Math.pow(1-rmax,k2))*rmax*2*Math.PI/3,
|
||||
//rtgf*2*Math.PI/3,
|
||||
thtP + t0
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static double[] avgDistortion(BinaryOperator<double[]> projFam,
|
||||
double[] params, double[][][] points) {
|
||||
final double[][][] distDist = MapAnalyzer.calculateDistortion(points,
|
||||
|
||||
@ -520,6 +520,39 @@ public class MapProjections extends Application {
|
||||
}
|
||||
|
||||
|
||||
private static double[] tetrahedralProjection(double x, double y) { // a function to help with tetrahedral projections
|
||||
if (y < x-1) {
|
||||
return new double[] {
|
||||
-Math.PI/2, 0, 0,
|
||||
-Math.PI/2, Math.sqrt(3)*(x-2/3.), y+1 };
|
||||
}
|
||||
else if (y < -x-1) {
|
||||
return new double[] {
|
||||
-Math.PI/2, 0, 0,
|
||||
Math.PI/2, Math.sqrt(3)*(x+2/3.), y+1 };
|
||||
}
|
||||
else if (y > -x+1) {
|
||||
return new double[] {
|
||||
Math.PI/2-Math.asin(Math.sqrt(8)/3), Math.PI, 0,
|
||||
-Math.PI/2, Math.sqrt(3)*(x-2/3.), y-1 };
|
||||
}
|
||||
else if (y > x+1) {
|
||||
return new double[] {
|
||||
Math.PI/2-Math.asin(Math.sqrt(8)/3), Math.PI, 0,
|
||||
Math.PI/2, Math.sqrt(3)*(x+2/3.), y-1 };
|
||||
}
|
||||
else if (x < 0)
|
||||
return new double[] {
|
||||
Math.PI/2-Math.asin(Math.sqrt(8)/3), -Math.PI/3, 0,
|
||||
Math.PI/6, Math.sqrt(3)*(x+1/3.), y };
|
||||
else
|
||||
return new double[] {
|
||||
Math.PI/2-Math.asin(Math.sqrt(8)/3), Math.PI/3, 0,
|
||||
-Math.PI/6, Math.sqrt(3)*(x-1/3.), y };
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static double[] quincuncial(double x, double y) { // a tessalatable square map
|
||||
Complex u = new Complex(1.854 * (x+1), 1.854 * y); // 1.854 is approx K(sqrt(1/2)
|
||||
Complex k = new Complex(Math.sqrt(0.5)); // the rest comes from some fancy complex calculus
|
||||
@ -624,73 +657,19 @@ public class MapProjections extends Application {
|
||||
}
|
||||
|
||||
private static double[] lee(double x, double y) { //a tessalatable rectangle map
|
||||
final double[] faceCenter = new double[3];
|
||||
final double rot, localX, localY;
|
||||
if (y < x-1) {
|
||||
faceCenter[0] = -Math.PI/2;
|
||||
faceCenter[1] = 0;
|
||||
rot = -Math.PI/2;
|
||||
localX = Math.sqrt(3)*(x-2/3.);
|
||||
localY = y+1;
|
||||
}
|
||||
else if (y < -x-1) {
|
||||
faceCenter[0] = -Math.PI/2;
|
||||
faceCenter[1] = 0;
|
||||
rot = Math.PI/2;
|
||||
localX = Math.sqrt(3)*(x+2/3.);
|
||||
localY = y+1;
|
||||
}
|
||||
else if (y > -x+1) {
|
||||
faceCenter[0] = Math.PI/2-Math.asin(Math.sqrt(8)/3);
|
||||
faceCenter[1] = Math.PI;
|
||||
rot = -Math.PI/2;
|
||||
localX = Math.sqrt(3)*(x-2/3.);
|
||||
localY = y-1;
|
||||
}
|
||||
else if (y > x+1) {
|
||||
faceCenter[0] = Math.PI/2-Math.asin(Math.sqrt(8)/3);
|
||||
faceCenter[1] = Math.PI;
|
||||
rot = Math.PI/2;
|
||||
localX = Math.sqrt(3)*(x+2/3.);
|
||||
localY = y-1;
|
||||
}
|
||||
else if (x < 0) {
|
||||
faceCenter[0] = Math.PI/2-Math.asin(Math.sqrt(8)/3);
|
||||
faceCenter[1] = -Math.PI/3;
|
||||
rot = Math.PI/6;
|
||||
localX = Math.sqrt(3)*(x+1/3.);
|
||||
localY = y;
|
||||
}
|
||||
else {
|
||||
faceCenter[0] = Math.PI/2-Math.asin(Math.sqrt(8)/3);
|
||||
faceCenter[1] = Math.PI/3;
|
||||
rot = -Math.PI/6;
|
||||
localX = Math.sqrt(3)*(x-1/3.);
|
||||
localY = y;
|
||||
}
|
||||
faceCenter[2] = 0;
|
||||
final double[] doubles = tetrahedralProjection(x,y);
|
||||
final double[] faceCenter = { doubles[0], doubles[1], doubles[2] };
|
||||
final double tht = doubles[3], xp = doubles[4], yp = doubles[5];
|
||||
|
||||
final Complex w = Complex.fromPolar(
|
||||
Math.hypot(localX, localY)*1.53,
|
||||
Math.atan2(localY, localX)+rot - Math.PI/2);
|
||||
Math.hypot(xp, yp)*1.53,
|
||||
Math.atan2(yp, xp)+tht - Math.PI/2);
|
||||
final Complex ans = Dixon.leeFunc(w).times(Math.pow(2, -5/6.));
|
||||
final double[] triCoords = {
|
||||
Math.PI/2 - 2*Math.atan(ans.abs()),
|
||||
ans.arg() + Math.PI };
|
||||
/*final double t = Math.atan2(localY, localX) + rot;
|
||||
final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2;
|
||||
final double dt = t-t0;
|
||||
|
||||
double[] triCoords = {
|
||||
Math.PI/2 - Math.atan(Math.tan(Math.atan(Math.sqrt(2))/(Math.sqrt(3)/3)*Math.hypot(localX, localY)*Math.cos(dt))/Math.cos(dt)),
|
||||
Math.PI/2 + t0 + dt};*/
|
||||
return obliquify(faceCenter, triCoords);
|
||||
/*Complex w = new Complex(x1, y1/Math.sqrt(3)).times(2.655);
|
||||
Complex ans = Dixon.leeFunc(w).times(Math.pow(2, -5/6.));
|
||||
double p = 2 * Math.atan(ans.abs());
|
||||
double theta = ans.arg();
|
||||
double lambda = p - Math.PI/2;
|
||||
return new double[] {lambda, theta};*/
|
||||
}
|
||||
|
||||
private static double[] mollweide(double x, double y) {
|
||||
@ -813,74 +792,32 @@ public class MapProjections extends Application {
|
||||
|
||||
final double t = Math.atan2(localY, localX) + rot;
|
||||
final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2;
|
||||
final double z = 2.49*Math.hypot(localX, localY)*Math.cos(t-t0);
|
||||
final double dt = t-t0;
|
||||
final double z = 2.49*Math.hypot(localX, localY)*Math.cos(dt);
|
||||
|
||||
final double g = 0.03575*z*z*z + 0.0219*z*z + 0.4441*z;
|
||||
|
||||
double[] triCoords = {
|
||||
Math.PI/2 - Math.atan(Math.tan(g)/Math.cos(t-t0)),
|
||||
Math.PI/2 + t};
|
||||
Math.PI/2 - Math.atan(Math.tan(g)/Math.cos(dt)),
|
||||
Math.PI/2 + t0 + dt };
|
||||
return obliquify(faceCenter, triCoords);
|
||||
}
|
||||
|
||||
private static double[] tetragraph(double x, double y) { // a tetrahedral compromise
|
||||
final double[] faceCenter = new double[3];
|
||||
final double rot, localX, localY;
|
||||
if (y < x-1) {
|
||||
faceCenter[0] = -Math.PI/2;
|
||||
faceCenter[1] = 0;
|
||||
rot = -Math.PI/2;
|
||||
localX = Math.sqrt(3)*(x-2/3.0);
|
||||
localY = y+1;
|
||||
}
|
||||
else if (y < -x-1) {
|
||||
faceCenter[0] = -Math.PI/2;
|
||||
faceCenter[1] = 0;
|
||||
rot = Math.PI/2;
|
||||
localX = Math.sqrt(3)*(x+2/3.0);
|
||||
localY = y+1;
|
||||
}
|
||||
else if (y > -x+1) {
|
||||
faceCenter[0] = Math.PI/2-Math.asin(Math.sqrt(8)/3);
|
||||
faceCenter[1] = Math.PI;
|
||||
rot = -Math.PI/2;
|
||||
localX = Math.sqrt(3)*(x-2/3.0);
|
||||
localY = y-1;
|
||||
}
|
||||
else if (y > x+1) {
|
||||
faceCenter[0] = Math.PI/2-Math.asin(Math.sqrt(8)/3);
|
||||
faceCenter[1] = Math.PI;
|
||||
rot = Math.PI/2;
|
||||
localX = Math.sqrt(3)*(x+2/3.0);
|
||||
localY = y-1;
|
||||
}
|
||||
else if (x < 0) {
|
||||
faceCenter[0] = Math.PI/2-Math.asin(Math.sqrt(8)/3);
|
||||
faceCenter[1] = -Math.PI/3;
|
||||
rot = Math.PI/6;
|
||||
localX = Math.sqrt(3)*(x+1/3.0);
|
||||
localY = y;
|
||||
}
|
||||
else {
|
||||
faceCenter[0] = Math.PI/2-Math.asin(Math.sqrt(8)/3);
|
||||
faceCenter[1] = Math.PI/3;
|
||||
rot = -Math.PI/6;
|
||||
localX = Math.sqrt(3)*(x-1/3.0);
|
||||
localY = y;
|
||||
}
|
||||
faceCenter[2] = 0;
|
||||
private static double[] tetragraph(double x, double y) {
|
||||
final double[] doubles = tetrahedralProjection(x,y);
|
||||
final double[] faceCenter = { doubles[0], doubles[1], doubles[2] };
|
||||
final double tht = doubles[3], xp = doubles[4], yp = doubles[5];
|
||||
|
||||
final double t = Math.atan2(localY, localX) + rot;
|
||||
final double t = Math.atan2(yp, xp) + tht;
|
||||
final double t0 = Math.floor((t+Math.PI/2)/(2*Math.PI/3)+0.5)*(2*Math.PI/3) - Math.PI/2;
|
||||
final double dt = t-t0;
|
||||
|
||||
double[] triCoords = {
|
||||
Math.PI/2 - Math.atan(Math.tan(Math.atan(Math.sqrt(2))/(Math.sqrt(3)/3)*Math.hypot(localX, localY)*Math.cos(dt))/Math.cos(dt)),
|
||||
Math.PI/2 - Math.atan(Math.tan(Math.atan(Math.sqrt(2))/(Math.sqrt(3)/3)*Math.hypot(xp,yp)*Math.cos(dt))/Math.cos(dt)),
|
||||
Math.PI/2 + t0 + dt};
|
||||
return obliquify(faceCenter, triCoords);
|
||||
}
|
||||
|
||||
|
||||
private static double[] edConic(double x, double y) {
|
||||
y = (y-1)/2;
|
||||
final double r = Math.hypot(x, y);
|
||||
@ -890,7 +827,6 @@ public class MapProjections extends Application {
|
||||
2*Math.atan2(y, x)+Math.PI};
|
||||
}
|
||||
|
||||
|
||||
private static double[] albers(double x, double y) {
|
||||
y = (y-1)/2;
|
||||
final double r = Math.hypot(x, y);
|
||||
@ -900,7 +836,6 @@ public class MapProjections extends Application {
|
||||
2*Math.atan2(y, x)};
|
||||
}
|
||||
|
||||
|
||||
private static double[] robinson(double x, double y) {
|
||||
return new double[] {
|
||||
Robinson.latFromPdfe(Math.abs(y))*Math.signum(y),
|
||||
|
||||
@ -5,15 +5,16 @@ import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.apache.commons.math3.complex.Complex;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
@ -32,6 +33,7 @@ import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
@ -101,6 +103,7 @@ public class MapProjections extends Application {
|
||||
private ComboBox<String> projectionChooser;
|
||||
private Text projectionDesc;
|
||||
private Slider[] aspectSliders;
|
||||
private Spinner<Double>[] aspectSpinners;
|
||||
private Button saveMap;
|
||||
private List<String> format;
|
||||
private List<List<double[]>> input;
|
||||
@ -115,6 +118,7 @@ public class MapProjections extends Application {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
stage = primaryStage;
|
||||
@ -196,24 +200,48 @@ public class MapProjections extends Application {
|
||||
new Slider(-180,180,0),
|
||||
new Slider(-180,180,0)
|
||||
};
|
||||
final Spinner<Double> latSpinner = new Spinner<Double>(-90, 90, 90.0);
|
||||
aspectSpinners = (Spinner<Double>[]) Array.newInstance(latSpinner.getClass(), 3);
|
||||
aspectSpinners[0] = latSpinner;
|
||||
aspectSpinners[1] = new Spinner<Double>(-180, 180, 0.0);
|
||||
aspectSpinners[2] = new Spinner<Double>(-180, 180, 0.0);
|
||||
|
||||
GridPane grid = new GridPane();
|
||||
grid.addRow(0, new Text("Latitude:"), aspectSliders[0]);
|
||||
grid.addRow(1, new Text("Longitude:"), aspectSliders[1]);
|
||||
grid.addRow(2, new Text("Orientation:"), aspectSliders[2]);
|
||||
for (Slider s: aspectSliders) {
|
||||
GridPane.setHgrow(s, Priority.ALWAYS);
|
||||
s.setTooltip(new Tooltip("Change the aspect of the map"));
|
||||
s.valueChangingProperty().addListener(new ChangeListener<Boolean>() {
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean then, Boolean now) {
|
||||
if (!now) updateMap();
|
||||
grid.addRow(0, new Text("Latitude:"), aspectSliders[0], aspectSpinners[0]);
|
||||
grid.addRow(1, new Text("Longitude:"), aspectSliders[1], aspectSpinners[1]);
|
||||
grid.addRow(2, new Text("Orientation:"), aspectSliders[2], aspectSpinners[2]);
|
||||
|
||||
for (int i = 0; i < 3; i ++) {
|
||||
final Slider sld = aspectSliders[i];
|
||||
final Spinner<Double> spn = aspectSpinners[i];
|
||||
GridPane.setHgrow(sld, Priority.ALWAYS);
|
||||
sld.setTooltip(new Tooltip("Change the aspect of the map"));
|
||||
sld.valueChangingProperty().addListener(
|
||||
(observable, then, now) -> {
|
||||
if (!now) updateMap();
|
||||
if (spn.getValue() != sld.getValue())
|
||||
spn.getEditor().textProperty().set(Double.toString(sld.getValue()));
|
||||
});
|
||||
|
||||
spn.setTooltip(new Tooltip("Change the aspect of the map"));
|
||||
spn.setPrefWidth(100);
|
||||
spn.setEditable(true);
|
||||
spn.getEditor().textProperty().addListener((ov, pv, nv) -> { // link the Spinners
|
||||
if (spn.getEditor().textProperty().isEmpty().get()) return;
|
||||
try {
|
||||
Double.parseDouble(nv);
|
||||
spn.increment(0); // forces the spinner to commit its value
|
||||
if (spn.getValue() != sld.getValue())
|
||||
sld.setValue(spn.getValue());
|
||||
} catch (NumberFormatException e) {
|
||||
spn.getEditor().textProperty().set(pv); //yeah, this is all pretty jank. JavaFX spinners are just weird by default
|
||||
}
|
||||
});
|
||||
/*s.valueProperty().addListener(new ChangeListener<Number>() {
|
||||
public void changed(ObservableValue<? extends Number> observable, Number old, Number now) {
|
||||
updateMap(true);
|
||||
}
|
||||
});*/ // Java doesn't like this; it can't handle that many threads
|
||||
spn.setOnKeyPressed((event) -> {
|
||||
System.out.println(event);
|
||||
if (event.getCode() == KeyCode.ENTER)
|
||||
updateMap();
|
||||
});
|
||||
}
|
||||
layout.getChildren().add(grid);
|
||||
|
||||
@ -422,8 +450,8 @@ public class MapProjections extends Application {
|
||||
g.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void saveToSVG(List<List<double[]>> curves, ProgressBarDialog pBar) { // call from the main thread!
|
||||
pBar.close();
|
||||
final File f = saver.showSaveDialog(stage);
|
||||
@ -550,7 +578,7 @@ public class MapProjections extends Application {
|
||||
else if (p.equals("Robinson"))
|
||||
return robinson(lat, lon);
|
||||
else if (p.equals("Test"))
|
||||
return hyperelliptical(lat, lon);
|
||||
return tetrapower(lat, lon);
|
||||
else if (p.equals("Lee"))
|
||||
return lee(lat, lon);
|
||||
else
|
||||
@ -612,6 +640,52 @@ public class MapProjections extends Application {
|
||||
}
|
||||
|
||||
|
||||
public static double[] tetrahedralProjection(double lat, double lon, UnaryOperator<double[]> func) { //a helper function for projections like tetragraph and lee
|
||||
final double[][] centrums = {{-Math.PI/2, 0, Math.PI/3},
|
||||
{Math.asin(1/3.0), Math.PI, Math.PI/3},
|
||||
{Math.asin(1/3.0), Math.PI/3, Math.PI/3},
|
||||
{Math.asin(1/3.0), -Math.PI/3, -Math.PI/3}};
|
||||
double latR = Double.NaN;
|
||||
double lonR = Double.NaN;
|
||||
byte poleIdx = -1;
|
||||
for (byte i = 0; i < 4; i++) {
|
||||
final double[] relCoords = obliquify(centrums[i], new double[] { lat, lon });
|
||||
if (Double.isNaN(latR) || relCoords[0] > latR) {
|
||||
latR = relCoords[0]; // pick the centrum that maxes out your latitude
|
||||
lonR = relCoords[1];
|
||||
poleIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
final double[] rtht = func.apply(new double[] { latR, lonR });
|
||||
|
||||
switch (poleIdx) {
|
||||
case 0:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + rtht[0]*Math.sin(rtht[1]-Math.PI/6), -Math.PI/Math.sqrt(3) - rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // lower left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - rtht[0]*Math.sin(rtht[1]-Math.PI/6), -Math.PI/Math.sqrt(3) + rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // lower right
|
||||
case 1:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + rtht[0]*Math.sin(rtht[1]-Math.PI/6), Math.PI/Math.sqrt(3) - rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // upper left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - rtht[0]*Math.sin(rtht[1]-Math.PI/6), Math.PI/Math.sqrt(3) + rtht[0]*Math.cos(rtht[1]-Math.PI/6)}; // upper right
|
||||
case 2:
|
||||
return new double[]
|
||||
{Math.PI/3 + rtht[0]*Math.cos(rtht[1]), rtht[0]*Math.sin(rtht[1])}; // right
|
||||
case 3:
|
||||
return new double[]
|
||||
{-Math.PI/3 - rtht[0]*Math.cos(rtht[1]), -rtht[0]*Math.sin(rtht[1])}; // left
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static double[] quincuncial(double lat, double lon) { // a tessalatable square map
|
||||
final double alat = Math.abs(lat);
|
||||
final double wMag = Math.tan(Math.PI/4-alat/2);
|
||||
@ -644,7 +718,6 @@ public class MapProjections extends Application {
|
||||
Complex z = w.asin();
|
||||
if (z.isInfinite() || z.isNaN())
|
||||
z = new Complex(0);
|
||||
|
||||
return new double[] {z.getReal(), z.getImaginary()};
|
||||
}
|
||||
|
||||
@ -768,152 +841,41 @@ public class MapProjections extends Application {
|
||||
Math.sin(lat)*a/Math.sin(a)};
|
||||
}
|
||||
|
||||
|
||||
private static double[] tetragraph(double lat, double lon) { // a tetrahedral compromise
|
||||
final double[][] centrums = {{-Math.PI/2, 0, Math.PI/3},
|
||||
{Math.asin(1/3.0), Math.PI, Math.PI/3},
|
||||
{Math.asin(1/3.0), Math.PI/3, Math.PI/3},
|
||||
{Math.asin(1/3.0), -Math.PI/3, -Math.PI/3}};
|
||||
double latR = Double.NaN;
|
||||
double lonR = Double.NaN;
|
||||
byte poleIdx = -1;
|
||||
for (byte i = 0; i < 4; i ++) {
|
||||
final double[] relCoords = obliquify(centrums[i],
|
||||
new double[] {lat,lon});
|
||||
if (Double.isNaN(latR) || relCoords[0] > latR) {
|
||||
latR = relCoords[0]; // pick the centrum that maxes out your latitude
|
||||
lonR = relCoords[1];
|
||||
poleIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
double tht = lonR - Math.floor(lonR/(2*Math.PI/3))*(2*Math.PI/3) - Math.PI/3;
|
||||
double r = Math.atan(1/Math.tan(latR)*Math.cos(tht))/Math.cos(tht)*Math.PI/3/Math.atan(Math.sqrt(2));
|
||||
|
||||
switch (poleIdx) {
|
||||
case 0:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + r*Math.sin(lonR-Math.PI/6), -Math.PI/Math.sqrt(3) - r*Math.cos(lonR-Math.PI/6)}; // lower left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - r*Math.sin(lonR-Math.PI/6), -Math.PI/Math.sqrt(3) + r*Math.cos(lonR-Math.PI/6)}; // lower right
|
||||
case 1:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + r*Math.sin(lonR-Math.PI/6), Math.PI/Math.sqrt(3) - r*Math.cos(lonR-Math.PI/6)}; // upper left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - r*Math.sin(lonR-Math.PI/6), Math.PI/Math.sqrt(3) + r*Math.cos(lonR-Math.PI/6)}; // upper right
|
||||
case 2:
|
||||
return new double[]
|
||||
{Math.PI/3 + r*Math.cos(lonR), r*Math.sin(lonR)}; // right
|
||||
case 3:
|
||||
return new double[]
|
||||
{-Math.PI/3 - r*Math.cos(lonR), -r*Math.sin(lonR)}; // left
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return tetrahedralProjection(lat, lon, (coordR) -> {
|
||||
final double tht = coordR[1] - Math.floor(coordR[1]/(2*Math.PI/3))*(2*Math.PI/3) - Math.PI/3;
|
||||
return new double[] {
|
||||
Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.cos(tht)*Math.PI/3/Math.atan(Math.sqrt(2)),
|
||||
coordR[1]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static double[] lee(double lat, double lon) { //a tesselatable rectangle map
|
||||
final double[][] centrums = {{-Math.PI/2, 0, Math.PI/3},
|
||||
{Math.asin(1/3.0), Math.PI, Math.PI/3},
|
||||
{Math.asin(1/3.0), Math.PI/3, Math.PI/3},
|
||||
{Math.asin(1/3.0), -Math.PI/3, -Math.PI/3}};
|
||||
double latR = Double.NaN;
|
||||
double lonR = Double.NaN;
|
||||
byte poleIdx = -1;
|
||||
for (byte i = 0; i < 4; i ++) {
|
||||
final double[] relCoords = obliquify(centrums[i], new double[] {lat,lon});
|
||||
if (Double.isNaN(latR) || relCoords[0] > latR) {
|
||||
latR = relCoords[0]; // pick the centrum that maxes out your latitude
|
||||
lonR = relCoords[1];
|
||||
poleIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
final mfc.field.Complex z = mfc.field.Complex.fromPolar(Math.pow(2, 5/6.)*Math.tan(Math.PI/4-latR/2), lonR);
|
||||
final mfc.field.Complex w = Dixon.invFunc(z);
|
||||
double tht = w.arg();
|
||||
double r = w.abs()*1.186;
|
||||
|
||||
switch (poleIdx) {
|
||||
case 0:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + r*Math.sin(tht-Math.PI/6), -Math.PI/Math.sqrt(3) - r*Math.cos(tht-Math.PI/6)}; // lower left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - r*Math.sin(tht-Math.PI/6), -Math.PI/Math.sqrt(3) + r*Math.cos(tht-Math.PI/6)}; // lower right
|
||||
case 1:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + r*Math.sin(tht-Math.PI/6), Math.PI/Math.sqrt(3) - r*Math.cos(tht-Math.PI/6)}; // upper left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - r*Math.sin(tht-Math.PI/6), Math.PI/Math.sqrt(3) + r*Math.cos(tht-Math.PI/6)}; // upper right
|
||||
case 2:
|
||||
return new double[]
|
||||
{Math.PI/3 + r*Math.cos(tht), r*Math.sin(tht)}; // right
|
||||
case 3:
|
||||
return new double[]
|
||||
{-Math.PI/3 - r*Math.cos(tht), -r*Math.sin(tht)}; // left
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
private static double[] lee(double lat, double lon) { //a tesselatable rectangle conformal map
|
||||
return tetrahedralProjection(lat, lon, (coordR) -> {
|
||||
final mfc.field.Complex z = mfc.field.Complex.fromPolar(Math.pow(2, 5/6.)*Math.tan(Math.PI/4-coordR[0]/2), coordR[1]);
|
||||
final mfc.field.Complex w = Dixon.invFunc(z);
|
||||
return new double[] { w.abs()*1.186, w.arg() };
|
||||
});
|
||||
}
|
||||
|
||||
/*private static double[] tetrapower(double lat, double lon) { // a tetrahedral parametric map
|
||||
final double k1 = 1.5;
|
||||
private static double[] tetrapower(double lat, double lon) { // a tetrahedral parametric map
|
||||
final double k1 = .5;
|
||||
final double k2 = .8;
|
||||
|
||||
final double[][] centrums = {{-Math.PI/2, 0, Math.PI/3},
|
||||
{Math.asin(1/3.0), Math.PI, Math.PI/3},
|
||||
{Math.asin(1/3.0), Math.PI/3, Math.PI/3},
|
||||
{Math.asin(1/3.0), -Math.PI/3, -Math.PI/3}};
|
||||
double latR = Double.NaN;
|
||||
double lonR = Double.NaN;
|
||||
byte poleIdx = -1;
|
||||
for (byte i = 0; i < 4; i ++) {
|
||||
final double[] relCoords = obliquify(centrums[i],
|
||||
new double[] {lat,lon});
|
||||
if (Double.isNaN(latR) || relCoords[0] > latR) {
|
||||
latR = relCoords[0]; // pick the centrum that maxes out your latitude
|
||||
lonR = relCoords[1];
|
||||
poleIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
double lonM = lonR - Math.floor(lonR/(2*Math.PI/3))*(2*Math.PI/3) - Math.PI/3;
|
||||
double tht = (1 - Math.pow(1-Math.abs(lonM)/(Math.PI/2), k1))/normalization;
|
||||
double r = 1.1*Math.atan(1/Math.tan(latR)*Math.cos(tht))/Math.cos(tht);
|
||||
|
||||
switch (poleIdx) {
|
||||
case 0:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + r*Math.sin(lonR-Math.PI/6), -Math.PI/Math.sqrt(3) - r*Math.cos(lonR-Math.PI/6)}; // lower left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - r*Math.sin(lonR-Math.PI/6), -Math.PI/Math.sqrt(3) + r*Math.cos(lonR-Math.PI/6)}; // lower right
|
||||
case 1:
|
||||
if (Math.sin(lon) < 0)
|
||||
return new double[]
|
||||
{-2*Math.PI/3 + r*Math.sin(lonR-Math.PI/6), Math.PI/Math.sqrt(3) - r*Math.cos(lonR-Math.PI/6)}; // upper left
|
||||
else
|
||||
return new double[]
|
||||
{2*Math.PI/3 - r*Math.sin(lonR-Math.PI/6), Math.PI/Math.sqrt(3) + r*Math.cos(lonR-Math.PI/6)}; // upper right
|
||||
case 2:
|
||||
return new double[]
|
||||
{Math.PI/3 + r*Math.cos(lonR), r*Math.sin(lonR)}; // right
|
||||
case 3:
|
||||
return new double[]
|
||||
{-Math.PI/3 - r*Math.cos(lonR), -r*Math.sin(lonR)}; // left
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}*/
|
||||
return tetrahedralProjection(lat, lon, (coordR) -> {
|
||||
final double t0 = Math.floor(coordR[1]/(2*Math.PI/3))*(2*Math.PI/3) + Math.PI/3;
|
||||
final double tht = coordR[1] - 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);
|
||||
final double rmax = .5/Math.cos(thtP); //the max radius of this triangle (in the plane)
|
||||
final double rtgf = Math.atan(1/Math.tan(coordR[0])*Math.cos(tht))/Math.atan(Math.sqrt(2))*rmax; //normalized tetragraph radius
|
||||
return new double[] {
|
||||
(1 - Math.pow(1-rtgf,k2))/(1 - Math.pow(1-rmax,k2))*rmax*2*Math.PI/3,
|
||||
//rtgf*2*Math.PI/3,
|
||||
thtP + t0
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static double[] edConic(double lat, double lon) {
|
||||
final double r = 3*Math.PI/5 - 4/5.*lat;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user