mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-11-19 00:00:13 -05:00
All of the projections!
I added a nifty projection selection dialog so that the user now has access to every projection I have programmed, while the combobox list is now shorter. It was more difficult than I anticipated, but it works and looks great!
This commit is contained in:
parent
91b71314f9
commit
da163a45bc
@ -53,18 +53,7 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Azimuthal;
|
||||
import maps.Conic;
|
||||
import maps.Cylindrical;
|
||||
import maps.Misc;
|
||||
import maps.MyProjections;
|
||||
import maps.Projection;
|
||||
import maps.Pseudocylindrical;
|
||||
import maps.Robinson;
|
||||
import maps.Snyder;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
import utils.Math2;
|
||||
import utils.Procedure;
|
||||
|
||||
@ -93,20 +82,6 @@ public class MapAnalyzer extends MapApplication {
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif"),
|
||||
new FileChooser.ExtensionFilter("GIF", "*.gif") };
|
||||
|
||||
private static final Projection[] PROJ_ARR = { Cylindrical.MERCATOR, Cylindrical.PLATE_CARREE,
|
||||
Cylindrical.EQUIRECTANGULAR, Cylindrical.GALL_PETERS, Cylindrical.HOBO_DYER,
|
||||
Cylindrical.BEHRMANN, Cylindrical.LAMBERT, Cylindrical.EQUAL_AREA, Cylindrical.GALL,
|
||||
Azimuthal.STEREOGRAPHIC, Azimuthal.POLAR, Azimuthal.EQUAL_AREA, Azimuthal.GNOMONIC,
|
||||
Azimuthal.ORTHOGRAPHIC, Azimuthal.PERSPECTIVE, Conic.LAMBERT, Conic.EQUIDISTANT,
|
||||
Conic.ALBERS, Tetrahedral.LEE, Tetrahedral.TETRAGRAPH, Tetrahedral.ACTUAUTHAGRAPH,
|
||||
Tetrahedral.AUTHAGRAPH, Tetrahedral.AUTHAPOWER, Pseudocylindrical.SINUSOIDAL,
|
||||
Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Misc.HAMMER, Misc.AITOFF,
|
||||
Misc.VAN_DER_GRINTEN, Robinson.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
|
||||
Misc.PEIRCE_QUINCUNCIAL, Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Snyder.GS50,
|
||||
Pseudocylindrical.LEMONS, MyProjections.EXPERIMENT, MyProjections.PSEUDOSTEREOGRAPHIC,
|
||||
MyProjections.TWO_POINT_EQUALIZED };
|
||||
|
||||
|
||||
private Button updateBtn;
|
||||
private Text avgSizeDistort, avgShapeDistort;
|
||||
private ImageView mapDisplay;
|
||||
@ -132,7 +107,7 @@ public class MapAnalyzer extends MapApplication {
|
||||
|
||||
@Override
|
||||
protected Node makeWidgets() {
|
||||
final Node projectionSelector = buildProjectionSelector(PROJ_ARR, Procedure.NONE);
|
||||
final Node projectionSelector = buildProjectionSelector(Procedure.NONE);
|
||||
final Node parameterSelector = buildParameterSelector(Procedure.NONE);
|
||||
final Node textDisplay = buildTextDisplay();
|
||||
this.updateBtn = buildUpdateButton(this::calculateAndUpdate);
|
||||
|
||||
@ -5,12 +5,14 @@ package apps;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
import dialogs.ProgressBarDialog;
|
||||
import dialogs.ProjectionSelectionDialog;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
@ -43,8 +45,18 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Azimuthal;
|
||||
import maps.Conic;
|
||||
import maps.Cylindrical;
|
||||
import maps.Lenticular;
|
||||
import maps.Misc;
|
||||
import maps.MyProjections;
|
||||
import maps.Projection;
|
||||
import maps.Pseudocylindrical;
|
||||
import maps.Robinson;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
import utils.Math2;
|
||||
import utils.Procedure;
|
||||
|
||||
@ -59,9 +71,19 @@ public abstract class MapApplication extends Application {
|
||||
protected static final int GUI_WIDTH = 350;
|
||||
protected static final int IMG_WIDTH = 500;
|
||||
|
||||
private static final KeyCombination ctrlO = new KeyCodeCombination(KeyCode.O, KeyCodeCombination.CONTROL_DOWN);
|
||||
private static final KeyCombination ctrlS = new KeyCodeCombination(KeyCode.S, KeyCodeCombination.CONTROL_DOWN);
|
||||
private static final KeyCombination ctrlEnter = new KeyCodeCombination(KeyCode.ENTER, KeyCodeCombination.CONTROL_DOWN);
|
||||
private static final KeyCombination CTRL_O = new KeyCodeCombination(KeyCode.O, KeyCodeCombination.CONTROL_DOWN);
|
||||
private static final KeyCombination CTRL_S = new KeyCodeCombination(KeyCode.S, KeyCodeCombination.CONTROL_DOWN);
|
||||
private static final KeyCombination CTRL_ENTER = new KeyCodeCombination(KeyCode.ENTER, KeyCodeCombination.CONTROL_DOWN);
|
||||
|
||||
|
||||
protected static final Projection[] PROJECTIONS = { Cylindrical.MERCATOR,
|
||||
Cylindrical.EQUIRECTANGULAR, Cylindrical.EQUAL_AREA, Cylindrical.GALL,
|
||||
Azimuthal.STEREOGRAPHIC, Azimuthal.POLAR, Azimuthal.EQUAL_AREA, Azimuthal.GNOMONIC,
|
||||
Azimuthal.PERSPECTIVE, Conic.LAMBERT, Conic.EQUIDISTANT, Conic.ALBERS, Tetrahedral.LEE,
|
||||
Tetrahedral.ACTUAUTHAGRAPH, Tetrahedral.AUTHAGRAPH, Pseudocylindrical.SINUSOIDAL,
|
||||
Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Lenticular.AITOFF,
|
||||
Lenticular.VAN_DER_GRINTEN, Robinson.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
|
||||
Misc.PEIRCE_QUINCUNCIAL, Misc.TWO_POINT_EQUIDISTANT, Pseudocylindrical.LEMONS }; //the set of featured projections for the ComboBox
|
||||
|
||||
private static final String[] ASPECT_NAMES = { "Standard", "Transverse", "Cassini",
|
||||
"Atlantis", "AuthaGraph", "Jerusalem", "Point Nemo", "Longest Line",
|
||||
@ -82,7 +104,7 @@ public abstract class MapApplication extends Application {
|
||||
private double[] currentParams;
|
||||
|
||||
private Flag isChanging = new Flag();
|
||||
private Flag suppressListeners = new Flag();
|
||||
private Flag suppressListeners = new Flag(); //a flag to prevent events from triggering projection setting
|
||||
|
||||
|
||||
|
||||
@ -148,7 +170,7 @@ public abstract class MapApplication extends Application {
|
||||
loadButton.setTooltip(new Tooltip(
|
||||
"Change the input image"));
|
||||
root.addEventHandler(KeyEvent.KEY_PRESSED, (event) -> { // ctrl-O opens
|
||||
if (ctrlO.match(event)) {
|
||||
if (CTRL_O.match(event)) {
|
||||
loadButton.requestFocus();
|
||||
loadButton.fire();
|
||||
}
|
||||
@ -162,25 +184,30 @@ public abstract class MapApplication extends Application {
|
||||
|
||||
/**
|
||||
* Create a set of widgets to choose a Projection
|
||||
* @param projections The Projections from which the user may choose
|
||||
* @param defProj The default projection, before the user chooses anything
|
||||
* @return the full formatted Region
|
||||
*/
|
||||
protected Region buildProjectionSelector(Projection[] projections, Procedure projectionSetter) {
|
||||
protected Region buildProjectionSelector(Procedure projectionSetter) {
|
||||
final Label label = new Label("Projection:");
|
||||
projectionChooser =
|
||||
new ComboBox<Projection>(FXCollections.observableArrayList(projections));
|
||||
new ComboBox<Projection>(FXCollections.observableArrayList(PROJECTIONS));
|
||||
projectionChooser.getItems().add(Projection.NULL_PROJECTION);
|
||||
projectionChooser.setPrefWidth(210);
|
||||
|
||||
final Text description = new Text();
|
||||
description.setWrappingWidth(GUI_WIDTH);
|
||||
|
||||
projectionChooser.setOnAction((event) -> {
|
||||
final boolean suppressedListeners = suppressListeners.isSet(); //save this value, because
|
||||
description.setText(projectionChooser.getValue().getDescription()); //revealParameters
|
||||
revealParameters(projectionChooser.getValue()); //clears suppressListeners. That's fine,
|
||||
if (!suppressedListeners) //because suppressListeners is only needed here for that one case.
|
||||
projectionSetter.execute();
|
||||
projectionChooser.valueProperty().addListener((observable, old, now) -> {
|
||||
final boolean suppressedListeners = suppressListeners.isSet(); //save this value, because revealParameters()...
|
||||
if (projectionChooser.getValue() == Projection.NULL_PROJECTION) {
|
||||
chooseProjectionFromExpandedList(old); //<aside>NULL_PROJECTION is the "More..." button. It triggers the expanded list</aside>
|
||||
}
|
||||
else {
|
||||
description.setText(projectionChooser.getValue().getDescription());
|
||||
revealParameters(projectionChooser.getValue()); //...clears suppressListeners. That's fine,
|
||||
if (!suppressedListeners) //because suppressListeners is only needed here for that one case.
|
||||
projectionSetter.execute();
|
||||
}
|
||||
});
|
||||
|
||||
return new VBox(5, new HBox(3, label, projectionChooser), description);
|
||||
@ -286,6 +313,12 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
|
||||
|
||||
protected Node buildOptionPane(Flag cropAtIDL, Flag graticule) {
|
||||
//TODO: checkboxes for cropping and graticule
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a default button that will update the map
|
||||
* @return the button
|
||||
@ -300,7 +333,7 @@ public abstract class MapApplication extends Application {
|
||||
|
||||
updateButton.setDefaultButton(true);
|
||||
root.addEventHandler(KeyEvent.KEY_PRESSED, (event) -> {
|
||||
if (ctrlEnter.match(event)) {
|
||||
if (CTRL_ENTER.match(event)) {
|
||||
updateButton.requestFocus();
|
||||
updateButton.fire();
|
||||
}
|
||||
@ -362,7 +395,7 @@ public abstract class MapApplication extends Application {
|
||||
|
||||
if (bindCtrlS) // ctrl+S saves
|
||||
root.addEventHandler(KeyEvent.KEY_PRESSED, (event) -> {
|
||||
if (ctrlS.match(event)) {
|
||||
if (CTRL_S.match(event)) {
|
||||
saveButton.requestFocus();
|
||||
saveButton.fire();
|
||||
}
|
||||
@ -387,6 +420,29 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
|
||||
|
||||
private void chooseProjectionFromExpandedList(Projection lastProjection) {
|
||||
final ProjectionSelectionDialog selectDialog = new ProjectionSelectionDialog();
|
||||
|
||||
do {
|
||||
final Optional<Projection> result = selectDialog.showAndWait();
|
||||
|
||||
if (result.isPresent()) {
|
||||
if (result.get() == Projection.NULL_PROJECTION) {
|
||||
showError("No projection chosen", "Please select a projection.");
|
||||
}
|
||||
else {
|
||||
Platform.runLater(() -> projectionChooser.setValue(result.get()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Platform.runLater(() -> projectionChooser.setValue(lastProjection)); //I need these runLater()s because JavaFX is dumb and throws an obscure error on recursive edits to ComboBoxes
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
|
||||
private void setAspectByPreset(String presetName,
|
||||
Slider[] sliders, Spinner<Double>[] spinners) {
|
||||
this.suppressListeners.set();
|
||||
@ -432,8 +488,7 @@ public abstract class MapApplication extends Application {
|
||||
paramSpinVF.setMax(paramValues[i][1]);
|
||||
paramSpinVF.setValue(paramValues[i][2]);
|
||||
final Tooltip tt = new Tooltip(
|
||||
"Change the "+paramNames[i]+" of the map (default is "+
|
||||
paramValues[i][2]+")");
|
||||
"Change the "+paramNames[i]+" of the map (default is " + paramValues[i][2]+")");
|
||||
paramSliders[i].setTooltip(tt);
|
||||
paramSpinners[i].setTooltip(tt);
|
||||
paramGrid.addRow(i, paramLabels[i], paramSliders[i], paramSpinners[i]);
|
||||
|
||||
@ -45,18 +45,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Azimuthal;
|
||||
import maps.Conic;
|
||||
import maps.Cylindrical;
|
||||
import maps.Misc;
|
||||
import maps.MyProjections;
|
||||
import maps.Projection;
|
||||
import maps.Pseudocylindrical;
|
||||
import maps.Robinson;
|
||||
import maps.Snyder;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
import utils.ImageUtils;
|
||||
import utils.PixelMap;
|
||||
import utils.Procedure;
|
||||
@ -84,18 +73,6 @@ public class MapDesignerRaster extends MapApplication {
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg"),
|
||||
new FileChooser.ExtensionFilter("GIF", "*.gif") };
|
||||
|
||||
private static final Projection[] PROJ_ARR = { Cylindrical.MERCATOR,
|
||||
Cylindrical.EQUIRECTANGULAR, Cylindrical.EQUAL_AREA, Cylindrical.GALL,
|
||||
Azimuthal.STEREOGRAPHIC, Azimuthal.POLAR, Azimuthal.EQUAL_AREA, Azimuthal.GNOMONIC,
|
||||
Azimuthal.PERSPECTIVE, Conic.LAMBERT, Conic.EQUIDISTANT, Conic.ALBERS, Tetrahedral.LEE,
|
||||
Tetrahedral.ACTUAUTHAGRAPH, Tetrahedral.AUTHAPOWER, Tetrahedral.AUTHAGRAPH,
|
||||
Pseudocylindrical.SINUSOIDAL, Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Misc.AITOFF,
|
||||
Misc.VAN_DER_GRINTEN, Robinson.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
|
||||
Misc.PEIRCE_QUINCUNCIAL, Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Snyder.GS50,
|
||||
Pseudocylindrical.LEMONS, MyProjections.EXPERIMENT, MyProjections.PSEUDOSTEREOGRAPHIC,
|
||||
MyProjections.TWO_POINT_EQUALIZED };
|
||||
|
||||
|
||||
private Node aspectSelector;
|
||||
private Button updateBtn, saveMapBtn;
|
||||
private double[] aspect;
|
||||
@ -126,8 +103,7 @@ public class MapDesignerRaster extends MapApplication {
|
||||
this.aspect = new double[3];
|
||||
final Node inputSelector = buildInputSelector(READABLE_TYPES,
|
||||
RASTER_TYPES[0], this::setInput);
|
||||
final Node projectionSelector = buildProjectionSelector(PROJ_ARR,
|
||||
this::hideAspect);
|
||||
final Node projectionSelector = buildProjectionSelector(this::hideAspect);
|
||||
this.aspectSelector = buildAspectSelector(this.aspect, Procedure.NONE);
|
||||
final Node parameterSelector = buildParameterSelector(Procedure.NONE);
|
||||
this.updateBtn = buildUpdateButton(this::updateMap);
|
||||
|
||||
@ -45,19 +45,6 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Azimuthal;
|
||||
import maps.CahillKeyes;
|
||||
import maps.Conic;
|
||||
import maps.Cylindrical;
|
||||
import maps.Misc;
|
||||
import maps.MyProjections;
|
||||
import maps.Projection;
|
||||
import maps.Pseudocylindrical;
|
||||
import maps.Robinson;
|
||||
import maps.Snyder;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
import utils.Math2;
|
||||
import utils.Procedure;
|
||||
import utils.SVGMap;
|
||||
@ -77,23 +64,11 @@ public class MapDesignerVector extends MapApplication {
|
||||
|
||||
|
||||
|
||||
private static final FileChooser.ExtensionFilter[] VECTOR_TYPES = {
|
||||
new FileChooser.ExtensionFilter("SVG", "*.svg") };
|
||||
|
||||
private static final Projection[] PROJ_ARR = { Cylindrical.MERCATOR,
|
||||
Cylindrical.EQUIRECTANGULAR, Cylindrical.EQUAL_AREA, Cylindrical.GALL,
|
||||
Azimuthal.STEREOGRAPHIC, Azimuthal.POLAR, Azimuthal.EQUAL_AREA, Azimuthal.GNOMONIC,
|
||||
Azimuthal.PERSPECTIVE, Conic.LAMBERT, Conic.EQUIDISTANT, Conic.ALBERS, Tetrahedral.LEE,
|
||||
Tetrahedral.ACTUAUTHAGRAPH, Tetrahedral.AUTHAPOWER, Tetrahedral.AUTHAGRAPH,
|
||||
Pseudocylindrical.SINUSOIDAL, Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Misc.AITOFF,
|
||||
Misc.VAN_DER_GRINTEN, Robinson.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
|
||||
Misc.PEIRCE_QUINCUNCIAL, CahillKeyes.BUTTERFLY, Misc.TWO_POINT_EQUIDISTANT,
|
||||
Misc.HAMMER_RETROAZIMUTHAL, Snyder.GS50, Pseudocylindrical.LEMONS, MyProjections.EXPERIMENT,
|
||||
MyProjections.PSEUDOSTEREOGRAPHIC, MyProjections.TWO_POINT_EQUALIZED };
|
||||
|
||||
private static final int DEF_MAX_VTX = 5000;
|
||||
private static final int FAST_MAX_VTX = 2000;
|
||||
|
||||
private static final FileChooser.ExtensionFilter[] VECTOR_TYPES = {
|
||||
new FileChooser.ExtensionFilter("SVG", "*.svg") };
|
||||
|
||||
private Node aspectSelector;
|
||||
private Button saveBtn;
|
||||
@ -123,7 +98,7 @@ public class MapDesignerVector extends MapApplication {
|
||||
this.aspect = new double[3];
|
||||
final Node inputSelector = buildInputSelector(VECTOR_TYPES,
|
||||
VECTOR_TYPES[0], this::setInput);
|
||||
final Node projectionSelector = buildProjectionSelector(PROJ_ARR,
|
||||
final Node projectionSelector = buildProjectionSelector(
|
||||
Procedure.concat(this::updateMap, this::hideAspect));
|
||||
this.aspectSelector = buildAspectSelector(this.aspect, this::updateMap);
|
||||
final Node parameterSelector = buildParameterSelector(this::updateMap);
|
||||
|
||||
@ -34,6 +34,7 @@ import maps.Azimuthal;
|
||||
import maps.CahillKeyes;
|
||||
import maps.Conic;
|
||||
import maps.Cylindrical;
|
||||
import maps.Lenticular;
|
||||
import maps.Misc;
|
||||
import maps.MyProjections;
|
||||
import maps.Projection;
|
||||
@ -64,8 +65,8 @@ public class MapExplainer {
|
||||
Azimuthal.GNOMONIC.transverse(), Azimuthal.ORTHOGRAPHIC.transverse(),
|
||||
Conic.LAMBERT, Conic.EQUIDISTANT, Conic.ALBERS, Tetrahedral.LEE,
|
||||
Tetrahedral.AUTHAGRAPH, Pseudocylindrical.SINUSOIDAL,
|
||||
Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Misc.HAMMER, Misc.AITOFF,
|
||||
Misc.VAN_DER_GRINTEN, Robinson.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
|
||||
Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Lenticular.HAMMER, Lenticular.AITOFF,
|
||||
Lenticular.VAN_DER_GRINTEN, Robinson.ROBINSON, WinkelTripel.WINKEL_TRIPEL,
|
||||
CahillKeyes.BUTTERFLY, Misc.PEIRCE_QUINCUNCIAL.transverse(), Snyder.GS50,
|
||||
Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Misc.FLAT_EARTH },
|
||||
|
||||
@ -100,7 +101,7 @@ public class MapExplainer {
|
||||
out.println(" <dt>Property: </dt>");
|
||||
out.println(" <dd>"+proj.getProperty().getName()+"</dd>");
|
||||
out.println(" <dt>Uninterrupted: </dt>");
|
||||
out.println(" <dd>"+ (proj.isConvex() ? "Yes":"No") +"</dd>");
|
||||
out.println(" <dd>"+ (proj.isContinuous() ? "Yes":"No") +"</dd>");
|
||||
out.println(" <dt>Shows entire world: </dt>");
|
||||
out.println(" <dd>"+ (proj.isFinite() ? "Yes":"No") +"</dd>");
|
||||
out.println(" <dt>Closed-form solution: </dt>");
|
||||
|
||||
@ -39,6 +39,7 @@ import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Cylindrical;
|
||||
import maps.Lenticular;
|
||||
import maps.Misc;
|
||||
import maps.Projection;
|
||||
import maps.Robinson;
|
||||
@ -54,7 +55,7 @@ import maps.WinkelTripel;
|
||||
public class MapOptimizer extends Application {
|
||||
|
||||
private static final Projection[] EXISTING_PROJECTIONS = { Cylindrical.HOBO_DYER,
|
||||
Robinson.ROBINSON, Misc.VAN_DER_GRINTEN, Misc.PEIRCE_QUINCUNCIAL };
|
||||
Robinson.ROBINSON, Lenticular.VAN_DER_GRINTEN, Misc.PEIRCE_QUINCUNCIAL };
|
||||
private static final Projection[] PROJECTIONS_TO_OPTIMIZE = { Tobler.TOBLER,
|
||||
WinkelTripel.WINKEL_TRIPEL, Tetrahedral.TETRAPOWER, Tetrahedral.AUTHAPOWER };
|
||||
private static final double[] WEIGHTS = { 0., .125, .25, .375, .5, .625, .75, .875, 1. };
|
||||
|
||||
@ -49,6 +49,7 @@ import javafx.stage.Stage;
|
||||
import maps.Azimuthal;
|
||||
import maps.CahillKeyes;
|
||||
import maps.Cylindrical;
|
||||
import maps.Lenticular;
|
||||
import maps.Misc;
|
||||
import maps.MyProjections;
|
||||
import maps.Projection;
|
||||
@ -73,13 +74,13 @@ public class MapPlotter extends Application {
|
||||
private static final Projection[] AZIMUTHAL = { Azimuthal.POLAR };
|
||||
private static final Projection[] PSEUDOCYL = { Pseudocylindrical.MOLLWEIDE, Robinson.ROBINSON,
|
||||
Tobler.TOBLER };
|
||||
private static final Projection[] PSEUDOAZM = { Misc.AITOFF,
|
||||
private static final Projection[] PSEUDOAZM = { Lenticular.AITOFF,
|
||||
MyProjections.PSEUDOSTEREOGRAPHIC };
|
||||
private static final Projection[] TETRAHEDRAL = { Tetrahedral.LEE, Tetrahedral.ACTUAUTHAGRAPH,
|
||||
Tetrahedral.AUTHAGRAPH, Tetrahedral.TETRAPOWER };
|
||||
private static final Projection[] CHEATY = { Pseudocylindrical.LEMONS,
|
||||
CahillKeyes.OCTANT };
|
||||
private static final Projection[] OTHER = { Misc.VAN_DER_GRINTEN, WinkelTripel.WINKEL_TRIPEL,
|
||||
private static final Projection[] OTHER = { Lenticular.VAN_DER_GRINTEN, WinkelTripel.WINKEL_TRIPEL,
|
||||
Misc.PEIRCE_QUINCUNCIAL };
|
||||
|
||||
|
||||
|
||||
@ -96,8 +96,7 @@ public class MapConfigurationDialog extends Dialog<Boolean> {
|
||||
this.smoothBox.setValue("Low");
|
||||
this.smoothBox.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
this.gui = new VBox();
|
||||
this.gui.setSpacing(20);
|
||||
this.gui = new VBox(20);
|
||||
|
||||
pane.contentTextProperty().addListener((arg0) -> { // set it to refresh the gui when... the content texts?
|
||||
this.updateGUI();
|
||||
@ -107,12 +106,8 @@ public class MapConfigurationDialog extends Dialog<Boolean> {
|
||||
pane.getButtonTypes().addAll(new ButtonType[] { ButtonType.OK, ButtonType.CANCEL }); // add buttons
|
||||
this.updateGUI();
|
||||
|
||||
this.setResultConverter((btn) -> {
|
||||
if (btn != null && btn.getButtonData() == ButtonData.OK_DONE)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
this.setResultConverter(
|
||||
(btn) -> (btn != null && btn.getButtonData() == ButtonData.OK_DONE));
|
||||
|
||||
realEdit = true;
|
||||
}
|
||||
@ -127,12 +122,9 @@ public class MapConfigurationDialog extends Dialog<Boolean> {
|
||||
grid.setMaxWidth(Double.MAX_VALUE);
|
||||
grid.setAlignment(Pos.CENTER_LEFT);
|
||||
grid.getChildren().clear();
|
||||
grid.add(new Label("Width:"), 0, 0);
|
||||
grid.add(this.widthBox, 1, 0);
|
||||
grid.add(new Label("Height:"), 0, 1);
|
||||
grid.add(this.heightBox, 1, 1);
|
||||
grid.add(new Label("Smoothing:"), 0, 2);
|
||||
grid.add(this.smoothBox, 1, 2);
|
||||
grid.addRow(0, new Label("Width:"), this.widthBox);
|
||||
grid.addRow(1, new Label("Height:"), this.heightBox);
|
||||
grid.addRow(2, new Label("Smoothing:"), this.smoothBox);
|
||||
this.gui.getChildren().add(grid);
|
||||
|
||||
this.getDialogPane().setContent(this.gui);
|
||||
|
||||
183
src/dialogs/ProjectionSelectionDialog.java
Normal file
183
src/dialogs/ProjectionSelectionDialog.java
Normal file
@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 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 dialogs;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ButtonBar.ButtonData;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.DialogPane;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.control.cell.TextFieldTreeCell;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import maps.Azimuthal;
|
||||
import maps.CahillKeyes;
|
||||
import maps.Conic;
|
||||
import maps.Cylindrical;
|
||||
import maps.Lenticular;
|
||||
import maps.Misc;
|
||||
import maps.MyProjections;
|
||||
import maps.Projection;
|
||||
import maps.Pseudocylindrical;
|
||||
import maps.Robinson;
|
||||
import maps.Snyder;
|
||||
import maps.Tetrahedral;
|
||||
import maps.Tobler;
|
||||
import maps.WinkelTripel;
|
||||
|
||||
|
||||
/**
|
||||
* A dialog for perusing all of the map projections I have to offer in an ordered drop-down-type
|
||||
* thing.
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class ProjectionSelectionDialog extends Dialog<Projection> {
|
||||
|
||||
|
||||
private static final double MENU_WIDTH = 250;
|
||||
private static final double TEXT_WIDTH = 300;
|
||||
|
||||
private static final String[] CATEGORIES = { "Cylindrical", "Azimuthal", "Conic", "Polyhedral",
|
||||
"Pseudocylindrical", "Lenticular", "Other", "Invented by Justin" };
|
||||
private static final Projection[][] PROJECTIONS = {
|
||||
{ Cylindrical.MERCATOR, Cylindrical.PLATE_CARREE, Cylindrical.EQUIRECTANGULAR,
|
||||
Cylindrical.GALL_PETERS, Cylindrical.HOBO_DYER, Cylindrical.BEHRMANN,
|
||||
Cylindrical.LAMBERT, Cylindrical.EQUAL_AREA, Cylindrical.GALL,
|
||||
Cylindrical.MILLER },
|
||||
{ Azimuthal.STEREOGRAPHIC, Azimuthal.POLAR, Azimuthal.EQUAL_AREA, Azimuthal.GNOMONIC,
|
||||
Azimuthal.ORTHOGRAPHIC, Azimuthal.PERSPECTIVE },
|
||||
{ Conic.LAMBERT, Conic.EQUIDISTANT, Conic.ALBERS },
|
||||
{ Tetrahedral.LEE, Tetrahedral.AUTHAGRAPH, CahillKeyes.M_MAP, CahillKeyes.BUTTERFLY },
|
||||
{ Pseudocylindrical.SINUSOIDAL, Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER,
|
||||
Robinson.ROBINSON },
|
||||
{ Lenticular.HAMMER, Lenticular.AITOFF, Lenticular.VAN_DER_GRINTEN,
|
||||
WinkelTripel.WINKEL_TRIPEL },
|
||||
{ Misc.PEIRCE_QUINCUNCIAL, Misc.GUYOU, Pseudocylindrical.LEMONS,
|
||||
Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Snyder.GS50,
|
||||
Misc.FLAT_EARTH },
|
||||
{ Tetrahedral.AUTHAPOWER, Tetrahedral.ACTUAUTHAGRAPH, Tetrahedral.TETRAGRAPH,
|
||||
Tetrahedral.TETRAPOWER, MyProjections.EXPERIMENT,
|
||||
MyProjections.PSEUDOSTEREOGRAPHIC, MyProjections.TWO_POINT_EQUALIZED,
|
||||
MyProjections.MAGNIFIER } };
|
||||
|
||||
|
||||
private Map<TreeItem<String>, Projection> projMap;
|
||||
private TreeView<String> menu;
|
||||
private TextFlow flow;
|
||||
private GridPane text;
|
||||
|
||||
|
||||
|
||||
public ProjectionSelectionDialog() {
|
||||
projMap = new HashMap<TreeItem<String>, Projection>();
|
||||
|
||||
final TreeItem<String> root = new TreeItem<String>();
|
||||
menu = new TreeView<String>(root);
|
||||
menu.setShowRoot(false); //create and configure the TreeView of options
|
||||
menu.setPrefWidth(MENU_WIDTH);
|
||||
|
||||
flow = new TextFlow(); //create and configure the description area
|
||||
flow.setPrefWidth(TEXT_WIDTH);
|
||||
text = new GridPane();
|
||||
text.setHgap(10);
|
||||
|
||||
menu.getSelectionModel().selectedItemProperty().addListener((observable, old, now) -> {
|
||||
if (projMap.containsKey(now)) //selection callback to describe each projection
|
||||
describe(projMap.get(now));
|
||||
else if (now != null) {
|
||||
now.setExpanded(!now.isExpanded());
|
||||
Platform.runLater(() -> menu.getSelectionModel().select(old)); //prevent them from selecting headers
|
||||
}
|
||||
});
|
||||
menu.setCellFactory((tView) -> { //factoring cells to detect double-clicks
|
||||
final TreeCell<String> cell = new TextFieldTreeCell<String>();
|
||||
cell.setOnMouseClicked((event) -> { //on double click, close dialog
|
||||
if (event.getClickCount() >= 2 && projMap.containsKey(cell.getTreeItem())) {
|
||||
this.setResult(projMap.get(cell.getTreeItem()));
|
||||
}
|
||||
});
|
||||
return cell;
|
||||
});
|
||||
|
||||
for (int i = 0; i < CATEGORIES.length; i ++) {
|
||||
final TreeItem<String> header = new TreeItem<String>(CATEGORIES[i]);
|
||||
root.getChildren().add(header);
|
||||
for (int j = 0; j < PROJECTIONS[i].length; j ++) {
|
||||
final TreeItem<String> leaf = new TreeItem<String>(PROJECTIONS[i][j].getName());
|
||||
projMap.put(leaf, PROJECTIONS[i][j]);
|
||||
header.getChildren().add(leaf);
|
||||
}
|
||||
}
|
||||
|
||||
this.setTitle("Projection selection"); //set general properties for the dialog
|
||||
final DialogPane pane = this.getDialogPane();
|
||||
pane.setHeaderText("Choose a projection from the list below.");
|
||||
pane.getButtonTypes().addAll(new ButtonType[] { ButtonType.OK, ButtonType.CANCEL }); //add buttons
|
||||
pane.setContent(new HBox(10, menu, new VBox(10, flow, text)));
|
||||
|
||||
this.setResultConverter((btn) -> { //how to return a result:
|
||||
if (btn != null && btn.getButtonData() == ButtonData.OK_DONE) {
|
||||
final TreeItem<String> selection = menu.getSelectionModel().getSelectedItem();
|
||||
return projMap.getOrDefault(selection, Projection.NULL_PROJECTION); //return the corresponding projection
|
||||
} //or NULL_PROJECTION if the user never chose anything
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void describe(Projection p) {
|
||||
final Text head = new Text(p.getName()+"\n");
|
||||
head.setFont(Font.font(head.getFont().getFamily(), FontWeight.BOLD, 18));
|
||||
final Text body = new Text(p.getDescription());
|
||||
flow.getChildren().setAll(head, body);
|
||||
|
||||
text.getChildren().clear();
|
||||
text.addRow(0, new Label("Geometry:"), new Label(p.getType().getName()));
|
||||
text.addRow(1, new Label("Property:"), new Label(p.getProperty().getName()));
|
||||
text.addRow(2, new Label("Uninterrupted:"), new Label(p.isContinuous() ? "Yes" : "No"));
|
||||
text.addRow(3, new Label("Shows entire world:"), new Label(p.isFinite() ? "Yes" : "No"));
|
||||
text.addRow(4, new Label("Closed-form solution:"), new Label(p.isSolveable() ? "Yes" : "No"));
|
||||
text.addRow(5, new Label("Closed-form inverse:"), new Label(p.isInvertable() ? "Yes" : "No"));
|
||||
for (Node label: text.getChildren())
|
||||
((Label)label).setFont(body.getFont());
|
||||
}
|
||||
|
||||
}
|
||||
117
src/maps/Lenticular.java
Normal file
117
src/maps/Lenticular.java
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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;
|
||||
import maps.Projection.Type;
|
||||
|
||||
/**
|
||||
* TODO: Write description
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class Lenticular {
|
||||
|
||||
public static final Projection AITOFF =
|
||||
new Projection("Aitoff", "A compromise projection shaped like an ellipse.",
|
||||
2*Math.PI, Math.PI, 0b1111, Type.PSEUDOAZIMUTHAL, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final double a = Math.acos(Math.cos(lat)*Math.cos(lon/2));
|
||||
return new double[] {
|
||||
2*Math.cos(lat)*Math.sin(lon/2)*a/Math.sin(a),
|
||||
Math.sin(lat)*a/Math.sin(a)};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
final double[] intermediate = Azimuthal.POLAR.inverse(x/2, y);
|
||||
double[] transverse = obliquifyPlnr(intermediate, new double[] {0,0,0});
|
||||
if (transverse != null) transverse[1] *= 2;
|
||||
return transverse;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final Projection HAMMER =
|
||||
new Projection("Hammer", "An equal-area projection shaped like an ellipse.",
|
||||
4, 2, 0b1111, Type.PSEUDOAZIMUTHAL, Property.EQUAL_AREA) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {
|
||||
2*Math.cos(lat)*Math.sin(lon/2)/Math.sqrt(1+Math.cos(lat)*Math.cos(lon/2)),
|
||||
Math.sin(lat)/Math.sqrt(1+Math.cos(lat)*Math.cos(lon/2)) };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
final double z = Math.sqrt(1 - x*x/8 - y*y/2);
|
||||
final double shift = (Math.hypot(x/2, y) > 1) ? 2*Math.PI*Math.signum(x) : 0;
|
||||
return new double[] {
|
||||
Math.asin(z*y*Math.sqrt(2)),
|
||||
2*Math.atan(Math.sqrt(.5)*z*x / (2*z*z - 1)) + shift};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final Projection VAN_DER_GRINTEN =
|
||||
new Projection(
|
||||
"Van der Grinten", "A circular compromise map that is popular for some reason.",
|
||||
2, 2, 0b1111, Type.OTHER, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
if (lat == 0) //special case 1: equator
|
||||
return new double[] {lon/Math.PI, 0};
|
||||
if (lon == 0 || lat >= Math.PI/2 || lat <= -Math.PI/2) //special case 3: prime meridian
|
||||
return new double[] {0, Math.tan(Math.asin(2*lat/Math.PI)/2)};
|
||||
|
||||
final double t = Math.abs(Math.asin(2*lat/Math.PI));
|
||||
final double A = Math.abs(Math.PI/lon - lon/Math.PI)/2;
|
||||
final double G = Math.cos(t)/(Math.sin(t)+Math.cos(t)-1);
|
||||
final double P = G*(2/Math.sin(t) - 1);
|
||||
final double Q = A*A + G;
|
||||
return new double[] {
|
||||
Math.signum(lon)*(A*(G-P*P)+Math.sqrt(A*A*(G-P*P)*(G-P*P)-(P*P+A*A)*(G*G-P*P)))/(P*P+A*A),
|
||||
Math.signum(lat)*(P*Q-A*Math.sqrt((A*A+1)*(P*P+A*A)-Q*Q))/(P*P+A*A)};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
if (y == 0) // special case 1: equator
|
||||
return new double[] {0, x*Math.PI};
|
||||
if (x == 0) // special case 3: prime meridian
|
||||
return new double[] {Math.PI/2 * Math.sin(2*Math.atan(y)), 0};
|
||||
|
||||
double c1 = -Math.abs(y) * (1 + x*x + y*y);
|
||||
double c2 = c1 - 2*y*y + x*x;
|
||||
double c3 = -2 * c1 + 1 + 2*y*y + Math.pow(x*x + y*y, 2);
|
||||
double d = y*y / c3 + 1 / 27.0 * (2*Math.pow(c2 / c3, 3) - 9*c1*c2 / (c3*c3));
|
||||
double a1 = 1 / c3*(c1 - c2*c2 / (3*c3));
|
||||
double m1 = 2 * Math.sqrt(-a1 / 3);
|
||||
double t1 = Math.acos(3*d / (a1 * m1)) / 3;
|
||||
return new double[] {
|
||||
Math.signum(y) * Math.PI * (-m1 * Math.cos(t1 + Math.PI/3) - c2 / (3*c3)),
|
||||
Math.PI*(x*x + y*y - 1 + Math.sqrt(1 + 2*(x*x - y*y) + Math.pow(x*x + y*y, 2)))
|
||||
/ (2*x)};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@ -37,88 +37,6 @@ import utils.Elliptic;
|
||||
*/
|
||||
public class Misc {
|
||||
|
||||
public static final Projection AITOFF =
|
||||
new Projection("Aitoff", "A compromise projection shaped like an ellipse.",
|
||||
2*Math.PI, Math.PI, 0b1111, Type.PSEUDOAZIMUTHAL, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
final double a = Math.acos(Math.cos(lat)*Math.cos(lon/2));
|
||||
return new double[] {
|
||||
2*Math.cos(lat)*Math.sin(lon/2)*a/Math.sin(a),
|
||||
Math.sin(lat)*a/Math.sin(a)};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
final double[] intermediate = Azimuthal.POLAR.inverse(x/2, y);
|
||||
double[] transverse = obliquifyPlnr(intermediate, new double[] {0,0,0});
|
||||
if (transverse != null) transverse[1] *= 2;
|
||||
return transverse;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final Projection HAMMER =
|
||||
new Projection("Hammer", "An equal-area projection shaped like an ellipse.",
|
||||
4, 2, 0b1111, Type.PSEUDOAZIMUTHAL, Property.EQUAL_AREA) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return new double[] {
|
||||
2*Math.cos(lat)*Math.sin(lon/2)/Math.sqrt(1+Math.cos(lat)*Math.cos(lon/2)),
|
||||
Math.sin(lat)/Math.sqrt(1+Math.cos(lat)*Math.cos(lon/2)) };
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
final double z = Math.sqrt(1 - x*x/8 - y*y/2);
|
||||
final double shift = (Math.hypot(x/2, y) > 1) ? 2*Math.PI*Math.signum(x) : 0;
|
||||
return new double[] {
|
||||
Math.asin(z*y*Math.sqrt(2)),
|
||||
2*Math.atan(Math.sqrt(.5)*z*x / (2*z*z - 1)) + shift};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final Projection VAN_DER_GRINTEN =
|
||||
new Projection(
|
||||
"Van der Grinten", "A circular compromise map that is popular for some reason.",
|
||||
2, 2, 0b1111, Type.OTHER, Property.COMPROMISE) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
if (lat == 0) //special case 1: equator
|
||||
return new double[] {lon/Math.PI, 0};
|
||||
if (lon == 0 || lat >= Math.PI/2 || lat <= -Math.PI/2) //special case 3: prime meridian
|
||||
return new double[] {0, Math.tan(Math.asin(2*lat/Math.PI)/2)};
|
||||
|
||||
final double t = Math.abs(Math.asin(2*lat/Math.PI));
|
||||
final double A = Math.abs(Math.PI/lon - lon/Math.PI)/2;
|
||||
final double G = Math.cos(t)/(Math.sin(t)+Math.cos(t)-1);
|
||||
final double P = G*(2/Math.sin(t) - 1);
|
||||
final double Q = A*A + G;
|
||||
return new double[] {
|
||||
Math.signum(lon)*(A*(G-P*P)+Math.sqrt(A*A*(G-P*P)*(G-P*P)-(P*P+A*A)*(G*G-P*P)))/(P*P+A*A),
|
||||
Math.signum(lat)*(P*Q-A*Math.sqrt((A*A+1)*(P*P+A*A)-Q*Q))/(P*P+A*A)};
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
if (y == 0) // special case 1: equator
|
||||
return new double[] {0, x*Math.PI};
|
||||
if (x == 0) // special case 3: prime meridian
|
||||
return new double[] {Math.PI/2 * Math.sin(2*Math.atan(y)), 0};
|
||||
|
||||
double c1 = -Math.abs(y) * (1 + x*x + y*y);
|
||||
double c2 = c1 - 2*y*y + x*x;
|
||||
double c3 = -2 * c1 + 1 + 2*y*y + Math.pow(x*x + y*y, 2);
|
||||
double d = y*y / c3 + 1 / 27.0 * (2*Math.pow(c2 / c3, 3) - 9*c1*c2 / (c3*c3));
|
||||
double a1 = 1 / c3*(c1 - c2*c2 / (3*c3));
|
||||
double m1 = 2 * Math.sqrt(-a1 / 3);
|
||||
double t1 = Math.acos(3*d / (a1 * m1)) / 3;
|
||||
return new double[] {
|
||||
Math.signum(y) * Math.PI * (-m1 * Math.cos(t1 + Math.PI/3) - c2 / (3*c3)),
|
||||
Math.PI*(x*x + y*y - 1 + Math.sqrt(1 + 2*(x*x - y*y) + Math.pow(x*x + y*y, 2)))
|
||||
/ (2*x)};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final Projection PEIRCE_QUINCUNCIAL =
|
||||
new Projection(
|
||||
"Peirce Quincuncial", "A conformal projection that uses complex elliptic functions.",
|
||||
@ -324,4 +242,5 @@ public class Misc {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ public class MyProjections {
|
||||
|
||||
public static final Projection EXPERIMENT =
|
||||
new Projection(
|
||||
"Complex Sine", "What happens when you apply a complex differentiable function to a stereographic projection?",
|
||||
"Complex Arcsine", "What happens when you apply a complex differentiable function to a stereographic projection?",
|
||||
6, 6, 0b0000, Type.OTHER, Property.CONFORMAL) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
|
||||
@ -47,7 +47,7 @@ public abstract class Projection {
|
||||
private final boolean finite; //does it display the entire world?
|
||||
private final boolean invertable; //is the inverse solution closed-form?
|
||||
private final boolean solveable; //is the solution closed-form?
|
||||
private final boolean convex; //is it convex?
|
||||
private final boolean continuous; //does a random continuous path cross outside of the map?
|
||||
private final Type type; //the geometry of the projection
|
||||
private final Property property; //what it is good for
|
||||
|
||||
@ -93,7 +93,7 @@ public abstract class Projection {
|
||||
|
||||
protected Projection(Projection base) {
|
||||
this( base.name, base.description, base.width, base.height, base.finite, base.invertable,
|
||||
base.solveable, base.convex, base.type, base.property, base.paramNames,
|
||||
base.solveable, base.continuous, base.type, base.property, base.paramNames,
|
||||
base.paramValues, base.hasAspect);
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ public abstract class Projection {
|
||||
this.finite = f;
|
||||
this.invertable = i;
|
||||
this.solveable = s;
|
||||
this.convex = c;
|
||||
this.continuous = c;
|
||||
this.type = type;
|
||||
this.property = property;
|
||||
}
|
||||
@ -398,8 +398,8 @@ public abstract class Projection {
|
||||
return this.solveable;
|
||||
}
|
||||
|
||||
public final boolean isConvex() {
|
||||
return this.convex;
|
||||
public final boolean isContinuous() {
|
||||
return this.continuous;
|
||||
}
|
||||
|
||||
public final Type getType() {
|
||||
@ -432,6 +432,21 @@ public abstract class Projection {
|
||||
|
||||
|
||||
|
||||
public static final Projection NULL_PROJECTION = //this exists solely for the purpose of a "More..." option at the end of menus
|
||||
new Projection("More...", null, 0, 0, 0, null, null) {
|
||||
|
||||
public double[] project(double lat, double lon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The most common geometric configurations of projections
|
||||
* @author jkunimune
|
||||
@ -439,8 +454,7 @@ public abstract class Projection {
|
||||
public static enum Type {
|
||||
CYLINDRICAL("Cylindrical"), CONIC("Conic"), AZIMUTHAL("Azimuthal"),
|
||||
PSEUDOCYLINDRICAL("Pseudocylindrical"), PSEUDOAZIMUTHAL("Pseudoazimuthal"),
|
||||
QUASIAZIMUTHAL("Quasiazimuthal"), TETRAHEDRAL("Tetrahedral"), POLYHEDRAL("Polyhedral"),
|
||||
OTHER("Other");
|
||||
QUASIAZIMUTHAL("Quasiazimuthal"), POLYHEDRAL("Polyhedral"), OTHER("Other");
|
||||
|
||||
private String name;
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ public class Tetrahedral {
|
||||
|
||||
public static final Projection AUTHAGRAPH =
|
||||
new TetrahedralProjection(
|
||||
"AuthaGraph", "A hip new Japanese map that is not authagraphic.",
|
||||
"AuthaGraph", "A hip new Japanese map that is almost equal-area.",
|
||||
0b1011, Configuration.AUTHAGRAPH, Property.COMPROMISE) {
|
||||
|
||||
public double[] innerProject(double lat, double lon) {
|
||||
@ -109,7 +109,7 @@ public class Tetrahedral {
|
||||
|
||||
public static final Projection AUTHAPOWER =
|
||||
new TetrahedralProjection(
|
||||
"AuthaPower", "A parametrised, rearranged version of my AuthaGraph approximation",
|
||||
"AuthaPower", "A parametrised, rearranged version of my AuthaGraph approximation.",
|
||||
0b1011, Configuration.WIDE_VERTEX, Property.COMPROMISE,
|
||||
new String[] {"Power"}, new double[][] {{.25,1,.7}}) {
|
||||
|
||||
@ -142,7 +142,7 @@ public class Tetrahedral {
|
||||
|
||||
public static final Projection ACTUAUTHAGRAPH =
|
||||
new TetrahedralProjection(
|
||||
"EquaHedral", "A holey authagraphic tetrahedral projection to put AuthaGraph to shame",
|
||||
"EquaHedral", "A holey authagraphic tetrahedral projection to put AuthaGraph to shame.",
|
||||
0b1010, Configuration.WIDE_VERTEX, Property.EQUAL_AREA,
|
||||
new String[] {"Rho"}, new double[][] {{0,.5,.25}}) {
|
||||
|
||||
@ -228,21 +228,21 @@ public class Tetrahedral {
|
||||
public TetrahedralProjection(
|
||||
String name, int fisc, Configuration config, Property property,
|
||||
String adjective, String addendum) {
|
||||
super(name, config.width, config.height, fisc, Type.TETRAHEDRAL, property,
|
||||
super(name, config.width, config.height, fisc, Type.POLYHEDRAL, property,
|
||||
adjective, addendum);
|
||||
this.configuration = config;
|
||||
}
|
||||
|
||||
public TetrahedralProjection(
|
||||
String name, String description, int fisc, Configuration config, Property property) {
|
||||
super(name, description, config.width, config.height, fisc, Type.TETRAHEDRAL, property);
|
||||
super(name, description, config.width, config.height, fisc, Type.POLYHEDRAL, property);
|
||||
this.configuration = config;
|
||||
}
|
||||
|
||||
public TetrahedralProjection(
|
||||
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,
|
||||
super(name, description, config.width, config.height, fisc, Type.POLYHEDRAL, property,
|
||||
paramNames, paramValues);
|
||||
this.configuration = config;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user