Get cut out of the picture
I added an option to remove the unnecessary stuff on the side of the map. It makes some pretty cool looking stuff. Not as good stuff, in my opinion, since it implies that the world has hard edges, but it works real well for the map analyser, where it prevents the distorted margins from distracting from the less distorted part that actually matters.
BIN
input/Basic.jpg
|
Before Width: | Height: | Size: 446 KiB After Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 43 KiB |
BIN
input/Squares.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
input/Tissot-alt3.jpg
Normal file
|
After Width: | Height: | Size: 544 KiB |
BIN
input/Tissot.jpg
|
Before Width: | Height: | Size: 735 KiB After Width: | Height: | Size: 783 KiB |
@ -54,7 +54,9 @@ import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Projection;
|
||||
import utils.Flag;
|
||||
import utils.Math2;
|
||||
import utils.MutableDouble;
|
||||
import utils.Procedure;
|
||||
|
||||
/**
|
||||
@ -82,6 +84,8 @@ public class MapAnalyzer extends MapApplication {
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif"),
|
||||
new FileChooser.ExtensionFilter("GIF", "*.gif") };
|
||||
|
||||
private Flag cropAtIDL;
|
||||
private MutableDouble graticuleSpacing;
|
||||
private Button updateBtn;
|
||||
private Text avgSizeDistort, avgShapeDistort;
|
||||
private ImageView mapDisplay;
|
||||
@ -107,8 +111,11 @@ public class MapAnalyzer extends MapApplication {
|
||||
|
||||
@Override
|
||||
protected Node makeWidgets() {
|
||||
this.cropAtIDL = new Flag(true);
|
||||
this.graticuleSpacing = new MutableDouble(15);
|
||||
final Node projectionSelector = buildProjectionSelector(Procedure.NONE);
|
||||
final Node parameterSelector = buildParameterSelector(Procedure.NONE);
|
||||
final Node optionPane = buildOptionPane(cropAtIDL, graticuleSpacing);
|
||||
final Node textDisplay = buildTextDisplay();
|
||||
this.updateBtn = buildUpdateButton(this::calculateAndUpdate);
|
||||
this.updateBtn.setText("Calculate"); //I don't need to follow your darn conventions!
|
||||
@ -121,7 +128,7 @@ public class MapAnalyzer extends MapApplication {
|
||||
|
||||
final VBox layout = new VBox(5,
|
||||
projectionSelector, parameterSelector, new Separator(),
|
||||
buttons, new Separator(), textDisplay);
|
||||
optionPane, new Separator(), buttons, new Separator(), textDisplay);
|
||||
layout.setAlignment(Pos.CENTER);
|
||||
layout.setPrefWidth(GUI_WIDTH);
|
||||
|
||||
@ -191,7 +198,7 @@ public class MapAnalyzer extends MapApplication {
|
||||
|
||||
loadParameters();
|
||||
final Projection proj = this.getProjection();
|
||||
final double[][][] distortionM = proj.calculateDistortion(proj.map(ROUGH_SAMP_NUM));
|
||||
final double[][][] distortionM = proj.calculateDistortion(proj.map(ROUGH_SAMP_NUM, cropAtIDL.isSet()));
|
||||
|
||||
mapDisplay.setImage(makeGraphic(distortionM));
|
||||
|
||||
|
||||
@ -16,11 +16,13 @@ import dialogs.ProjectionSelectionDialog;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
@ -45,19 +47,21 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.converter.DoubleStringConverter;
|
||||
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.Flag;
|
||||
import utils.Math2;
|
||||
import utils.MutableDouble;
|
||||
import utils.Procedure;
|
||||
|
||||
|
||||
@ -70,6 +74,9 @@ public abstract class MapApplication extends Application {
|
||||
|
||||
protected static final int GUI_WIDTH = 350;
|
||||
protected static final int IMG_WIDTH = 500;
|
||||
protected static final int V_SPACE = 5;
|
||||
protected static final int H_SPACE = 3;
|
||||
protected static final int SPINNER_WIDTH = 92;
|
||||
|
||||
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);
|
||||
@ -80,7 +87,7 @@ public abstract class MapApplication extends Application {
|
||||
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,
|
||||
Tetrahedral.ACTUAUTHAGRAPH, Tetrahedral.AUTHAPOWER, 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
|
||||
@ -176,7 +183,7 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
});
|
||||
|
||||
VBox output = new VBox(5, new HBox(3, label, inputLabel), loadButton);
|
||||
VBox output = new VBox(V_SPACE, new HBox(H_SPACE, label, inputLabel), loadButton);
|
||||
output.setAlignment(Pos.CENTER);
|
||||
return output;
|
||||
}
|
||||
@ -210,7 +217,7 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
});
|
||||
|
||||
return new VBox(5, new HBox(3, label, projectionChooser), description);
|
||||
return new VBox(V_SPACE, new HBox(H_SPACE, label, projectionChooser), description);
|
||||
}
|
||||
|
||||
|
||||
@ -226,7 +233,7 @@ public abstract class MapApplication extends Application {
|
||||
|
||||
final String[] labels = { "Latitude:", "Longitude:", "Ctr. Meridian:" };
|
||||
final Slider[] sliders = new Slider[] {
|
||||
new Slider(-90, 90, 0.), //TODO: can we call setAspectByPreset("Standard") instead of this?
|
||||
new Slider(-90, 90, 0.),
|
||||
new Slider(-180, 180, 0.),
|
||||
new Slider(-180, 180, 0.) };
|
||||
final Spinner<Double> spin0 = new Spinner<Double>(-90, 90, 0.); //yes, this is awkward. Java gets weird about arrays with generic types
|
||||
@ -257,11 +264,11 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
|
||||
final GridPane grid = new GridPane();
|
||||
grid.setVgap(5);
|
||||
grid.setHgap(3);
|
||||
grid.setVgap(V_SPACE);
|
||||
grid.setHgap(H_SPACE);
|
||||
grid.getColumnConstraints().addAll(
|
||||
new ColumnConstraints(92,Control.USE_COMPUTED_SIZE,Control.USE_COMPUTED_SIZE),
|
||||
new ColumnConstraints(), new ColumnConstraints(92));
|
||||
new ColumnConstraints(SPINNER_WIDTH,Control.USE_COMPUTED_SIZE,Control.USE_COMPUTED_SIZE),
|
||||
new ColumnConstraints(), new ColumnConstraints(SPINNER_WIDTH));
|
||||
for (int i = 0; i < 3; i ++) {
|
||||
GridPane.setHgrow(sliders[i], Priority.ALWAYS);
|
||||
sliders[i].setTooltip(new Tooltip("Change the aspect of the map"));
|
||||
@ -270,8 +277,9 @@ public abstract class MapApplication extends Application {
|
||||
grid.addRow(i, new Label(labels[i]), sliders[i], spinners[i]);
|
||||
}
|
||||
|
||||
VBox all = new VBox(5, presetChooser, grid);
|
||||
VBox all = new VBox(V_SPACE, presetChooser, grid);
|
||||
all.setAlignment(Pos.CENTER);
|
||||
all.managedProperty().bind(all.visibleProperty()); //make it hide when it is hiding
|
||||
return all;
|
||||
}
|
||||
|
||||
@ -282,12 +290,12 @@ public abstract class MapApplication extends Application {
|
||||
* @return the full formatted Region
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Node buildParameterSelector(Procedure parameterSetter) {
|
||||
protected Region buildParameterSelector(Procedure parameterSetter) {
|
||||
currentParams = new double[4];
|
||||
paramLabels = new Label[4];
|
||||
paramSliders = new Slider[4]; // I don't think any projection has more than four parameters
|
||||
final Spinner<Double> spin0 = new Spinner<Double>(0.,0.,0.); //yes, this is awkward. Java gets weird about arrays with generic types
|
||||
paramSpinners = (Spinner<Double>[]) Array.newInstance(spin0.getClass(), 4);
|
||||
paramSpinners = (Spinner<Double>[])Array.newInstance(spin0.getClass(), 4);
|
||||
paramSpinners[0] = spin0;
|
||||
|
||||
for (int i = 0; i < 4; i ++) {
|
||||
@ -304,18 +312,59 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
|
||||
paramGrid = new GridPane();
|
||||
paramGrid.setVgap(5);
|
||||
paramGrid.setHgap(3);
|
||||
paramGrid.setVgap(V_SPACE);
|
||||
paramGrid.setHgap(H_SPACE);
|
||||
paramGrid.getColumnConstraints().addAll(
|
||||
new ColumnConstraints(92,Control.USE_COMPUTED_SIZE,Control.USE_COMPUTED_SIZE),
|
||||
new ColumnConstraints(), new ColumnConstraints(92));
|
||||
new ColumnConstraints(SPINNER_WIDTH,Control.USE_COMPUTED_SIZE,Control.USE_COMPUTED_SIZE),
|
||||
new ColumnConstraints(), new ColumnConstraints(SPINNER_WIDTH));
|
||||
return paramGrid;
|
||||
}
|
||||
|
||||
|
||||
protected Node buildOptionPane(Flag cropAtIDL, Flag graticule) {
|
||||
//TODO: checkboxes for cropping and graticule
|
||||
return null;
|
||||
/**
|
||||
* Create a block of niche options - specifically International Dateline cropping and whether
|
||||
* there should be a graticule
|
||||
* @param cropAtIDL The mutable boolean value to which to bind the "Crop at Dateline" CheckBox
|
||||
* @param graticule The mutable double value to which to bind the "Graticule" Spinner
|
||||
* @return the full formatted Region
|
||||
*/
|
||||
protected Region buildOptionPane(Flag cropAtIDL, MutableDouble graticule) {
|
||||
final CheckBox cropBox = new CheckBox("Crop at International Dateline"); //the CheckBox for whether there should be shown imagery outside the International Dateline
|
||||
cropBox.setSelected(cropAtIDL.isSet());
|
||||
cropBox.setTooltip(new Tooltip("Show every point exactly once."));
|
||||
cropBox.selectedProperty().addListener((observable, old, now) -> {
|
||||
cropAtIDL.set(now);
|
||||
});
|
||||
|
||||
final ObservableList<Double> factorsOf360 = FXCollections.observableArrayList();
|
||||
for (double f = 1; f <= 45; f += 0.5)
|
||||
if (360%f == 0)
|
||||
factorsOf360.add((double)f);
|
||||
final Spinner<Double> gratSpinner = new Spinner<Double>(factorsOf360); //spinner for the graticule value
|
||||
gratSpinner.getValueFactory().setConverter(new DoubleStringConverter());
|
||||
gratSpinner.getValueFactory().setValue(graticule.get());
|
||||
gratSpinner.setDisable(graticule.isZero());
|
||||
gratSpinner.setEditable(true);
|
||||
gratSpinner.setTooltip(new Tooltip("The spacing in degrees between shown parallels and meridians."));
|
||||
gratSpinner.setPrefWidth(SPINNER_WIDTH);
|
||||
gratSpinner.valueProperty().addListener((observable, old, now) -> {
|
||||
graticule.set(now); //which is tied to the mutable graticule spacing variable
|
||||
});
|
||||
|
||||
final CheckBox gratBox = new CheckBox("Graticule: "); //the CheckBox for whether there should be a graticule
|
||||
gratBox.setSelected(!graticule.isZero());
|
||||
gratBox.setTooltip(new Tooltip("Overlay a mesh of parallels and meridians."));
|
||||
gratBox.selectedProperty().addListener((observable, old, now) -> {
|
||||
if (now)
|
||||
graticule.set(gratSpinner.getValue()); //set the value of graticule appropriately when checked
|
||||
else
|
||||
graticule.set(0); //when not checked, represent "no graticule" as a spacing of 0
|
||||
gratSpinner.setDisable(!now); //disable the graticule Spinner when appropriate
|
||||
});
|
||||
|
||||
final HBox gratRow = new HBox(H_SPACE, gratBox, gratSpinner);
|
||||
gratRow.setAlignment(Pos.CENTER_LEFT);
|
||||
return new VBox(V_SPACE, cropBox, gratRow);
|
||||
}
|
||||
|
||||
|
||||
@ -550,43 +599,5 @@ public abstract class MapApplication extends Application {
|
||||
alert.showAndWait();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Because Java apparently doesn't already have a mutable Bullion
|
||||
* @author jkunimune
|
||||
*/
|
||||
private class Flag {
|
||||
private boolean set;
|
||||
|
||||
public Flag() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public Flag(boolean set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return this.set;
|
||||
}
|
||||
|
||||
public void set(boolean set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
public void set() {
|
||||
this.set = true;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.set = false;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Flag("+this.set+")";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -46,7 +46,9 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Projection;
|
||||
import utils.Flag;
|
||||
import utils.ImageUtils;
|
||||
import utils.MutableDouble;
|
||||
import utils.PixelMap;
|
||||
import utils.Procedure;
|
||||
|
||||
@ -76,6 +78,8 @@ public class MapDesignerRaster extends MapApplication {
|
||||
private Node aspectSelector;
|
||||
private Button updateBtn, saveMapBtn;
|
||||
private double[] aspect;
|
||||
private Flag cropAtIDL;
|
||||
private MutableDouble graticuleSpacing;
|
||||
private PixelMap input;
|
||||
private ImageView display;
|
||||
private MapConfigurationDialog configDialog;
|
||||
@ -101,22 +105,24 @@ public class MapDesignerRaster extends MapApplication {
|
||||
@Override
|
||||
protected Node makeWidgets() {
|
||||
this.aspect = new double[3];
|
||||
this.cropAtIDL = new Flag(false);
|
||||
this.graticuleSpacing = new MutableDouble(15);
|
||||
final Node inputSelector = buildInputSelector(READABLE_TYPES,
|
||||
RASTER_TYPES[0], this::setInput);
|
||||
final Node projectionSelector = buildProjectionSelector(this::hideAspect);
|
||||
this.aspectSelector = buildAspectSelector(this.aspect, Procedure.NONE);
|
||||
final Node parameterSelector = buildParameterSelector(Procedure.NONE);
|
||||
final Node optionPane = buildOptionPane(cropAtIDL, graticuleSpacing);
|
||||
this.updateBtn = buildUpdateButton(this::updateMap);
|
||||
this.saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES,
|
||||
RASTER_TYPES[0], this::collectFinalSettings, this::calculateAndSaveMap);
|
||||
final HBox buttons = new HBox(5, updateBtn, saveMapBtn);
|
||||
final HBox buttons = new HBox(H_SPACE, updateBtn, saveMapBtn);
|
||||
buttons.setAlignment(Pos.CENTER);
|
||||
aspectSelector.managedProperty().bind(aspectSelector.visibleProperty());
|
||||
|
||||
final VBox layout = new VBox(5,
|
||||
inputSelector, new Separator(), projectionSelector,
|
||||
new Separator(), aspectSelector, parameterSelector,
|
||||
new Separator(), buttons);
|
||||
final VBox layout = new VBox(V_SPACE,
|
||||
inputSelector, new Separator(), projectionSelector, new Separator(),
|
||||
aspectSelector, parameterSelector, new Separator(), optionPane, new Separator(),
|
||||
buttons);
|
||||
layout.setAlignment(Pos.CENTER);
|
||||
layout.setPrefWidth(GUI_WIDTH);
|
||||
|
||||
@ -202,8 +208,9 @@ public class MapDesignerRaster extends MapApplication {
|
||||
|
||||
private BufferedImage makeImage(int width, int height, int step, ProgressBarDialog pBar) {
|
||||
final double[] pole = aspect.clone();
|
||||
final boolean crop = cropAtIDL.isSet();
|
||||
final Projection pjc = this.getProjection();
|
||||
final BufferedImage out = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
BufferedImage out = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
for (int y = 0; y < out.getHeight(); y ++) {
|
||||
if (pBar != null)
|
||||
pBar.setProgress((double) y/out.getHeight());
|
||||
@ -211,9 +218,9 @@ public class MapDesignerRaster extends MapApplication {
|
||||
int[] colors = new int[step*step];
|
||||
for (int dy = 0; dy < step; dy ++) {
|
||||
for (int dx = 0; dx < step; dx ++) {
|
||||
final double X = ((x+(dx+.5)/step)/out.getWidth() - 1/2.) *pjc.getWidth();
|
||||
final double Y = (1/2. - (y+(dy+.5)/step)/out.getHeight()) *pjc.getHeight();
|
||||
final double[] coords = this.getProjection().inverse(X, Y, pole);
|
||||
double X = ((x+(dx+.5)/step)/out.getWidth() - 1/2.) *pjc.getWidth();
|
||||
double Y = (1/2. - (y+(dy+.5)/step)/out.getHeight()) *pjc.getHeight();
|
||||
double[] coords = this.getProjection().inverse(X, Y, pole, crop);
|
||||
if (coords != null) //if it is null, the default (0:transparent) is used
|
||||
colors[step*dy+dx] = input.getArgb(coords[0], coords[1]);
|
||||
}
|
||||
|
||||
@ -104,7 +104,6 @@ public class MapDesignerVector extends MapApplication {
|
||||
final Node parameterSelector = buildParameterSelector(this::updateMap);
|
||||
this.saveBtn = buildSaveButton(true, "map", VECTOR_TYPES,
|
||||
VECTOR_TYPES[0], ()->true, this::calculateAndSaveMap);
|
||||
aspectSelector.managedProperty().bind(aspectSelector.visibleProperty());
|
||||
|
||||
final VBox layout = new VBox(5,
|
||||
inputSelector, new Separator(), projectionSelector,
|
||||
|
||||
@ -33,7 +33,6 @@ 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;
|
||||
|
||||
@ -38,9 +38,7 @@ public class Azimuthal {
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
return new double[] {
|
||||
Math.PI/2 - 2*Math.atan(Math.hypot(x, y)),
|
||||
Math.atan2(y, x) + Math.PI/2 };
|
||||
return new double[] { Math.PI/2 - 2*Math.atan(Math.hypot(x, y)), Math.atan2(x, -y) };
|
||||
}
|
||||
};
|
||||
|
||||
@ -58,7 +56,7 @@ public class Azimuthal {
|
||||
public double[] inverse(double x, double y) {
|
||||
double phi = Math.PI/2 - Math.hypot(x, y);
|
||||
if (phi > -Math.PI/2)
|
||||
return new double[] {phi, Math.atan2(y, x) + Math.PI/2};
|
||||
return new double[] {phi, Math.atan2(x, -y)};
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@ -77,7 +75,7 @@ public class Azimuthal {
|
||||
public double[] inverse(double x, double y) {
|
||||
double r = Math.hypot(x, y);
|
||||
if (r <= 1)
|
||||
return new double[] {Math.asin(1-2*r*r), Math.atan2(y,x)+Math.PI/2};
|
||||
return new double[] { Math.asin(1-2*r*r), Math.atan2(x, -y) };
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@ -97,7 +95,7 @@ public class Azimuthal {
|
||||
public double[] inverse(double x, double y) {
|
||||
double R = Math.hypot(x, y);
|
||||
if (R <= 1)
|
||||
return new double[] { Math.acos(R), Math.atan2(y, x) + Math.PI/2 };
|
||||
return new double[] { Math.acos(R), Math.atan2(x, -y) };
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@ -116,8 +114,7 @@ public class Azimuthal {
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
return new double[] { Math.PI/2 - Math.atan(Math.hypot(x, y)),
|
||||
Math.atan2(y, x) + Math.PI/2 };
|
||||
return new double[] { Math.PI/2 - Math.atan(Math.hypot(x, y)), Math.atan2(x, -y) };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ import de.jtem.ellipticFunctions.Jacobi;
|
||||
import maps.Projection.Property;
|
||||
import maps.Projection.Type;
|
||||
import utils.Elliptic;
|
||||
import utils.Math2;
|
||||
|
||||
/**
|
||||
* All the projections that don't fit into any of the other categories.
|
||||
@ -71,7 +72,7 @@ public class Misc {
|
||||
de.jtem.mfc.field.Complex k = new de.jtem.mfc.field.Complex(Math.sqrt(0.5)); //the rest comes from some fancy complex calculus
|
||||
de.jtem.mfc.field.Complex ans = Jacobi.cn(u, k);
|
||||
double p = 2 * Math.atan(ans.abs());
|
||||
double theta = ans.arg() - Math.PI/2;
|
||||
double theta = Math.atan2(-ans.getRe(), ans.getIm());
|
||||
double lambda = Math.PI/2 - p;
|
||||
return new double[] {lambda, theta};
|
||||
}
|
||||
@ -147,15 +148,17 @@ public class Misc {
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y) {
|
||||
final double phi1 = Math.PI/2 - Math.hypot(x, y);
|
||||
double phi1 = Math.PI/2 - Math.hypot(x, y);
|
||||
if (phi1 < -Math.PI/2) return null;
|
||||
final double lam1 = Math.atan2(x, -y);
|
||||
double lam1 = Math.atan2(x, -y);
|
||||
double phiP = Math.asin(Math.sin(phi0)/Math.hypot(Math.sin(phi1),Math.cos(phi1)*Math.cos(lam1))) - Math.atan2(Math.cos(phi1)*Math.cos(lam1),Math.sin(phi1));
|
||||
if (Math.abs(phiP) > Math.PI/2)
|
||||
phiP = Math.signum(phiP)*Math.PI - phiP;
|
||||
final double delL = Math.acos(Math.sin(phi1)/Math.cos(phiP)/Math.cos(phi0) - Math.tan(phiP)*Math.tan(phi0));
|
||||
final double lamP = lam0 + Math.signum(x)*delL;
|
||||
double delL = Math.acos(Math.sin(phi1)/Math.cos(phiP)/Math.cos(phi0) - Math.tan(phiP)*Math.tan(phi0));
|
||||
double lamP = lam0 + Math.signum(x)*delL;
|
||||
if (Double.isNaN(phiP) || Double.isNaN(lamP)) return null;
|
||||
if (lamP > Math.PI) lamP -= 2*Math.PI;
|
||||
if (lamP < -Math.PI) lamP += 2*Math.PI;
|
||||
return new double[] {phiP, lamP};
|
||||
}
|
||||
};
|
||||
@ -216,7 +219,7 @@ public class Misc {
|
||||
final double s1 = Math.signum(Math.sin(Math.acos(t1)-s0*Math.acos(t2)));
|
||||
final double PHI = Math.asin(Math.sin(lat1)*Math.cos(d1) - Math.cos(lat1)*Math.sin(d1)*casab);
|
||||
final double LAM = lon1 +s1* Math.acos((Math.cos(d1) - Math.sin(lat1)*Math.sin(PHI))/(Math.cos(lat1)*Math.cos(PHI)));
|
||||
return new double[] {PHI, LAM};
|
||||
return new double[] { PHI, Math2.floorMod(LAM+Math.PI, 2*Math.PI) - Math.PI };
|
||||
}
|
||||
|
||||
private double dist(double lat1, double lon1, double lat2, double lon2) {
|
||||
|
||||
@ -27,6 +27,7 @@ import org.apache.commons.math3.complex.Complex;
|
||||
|
||||
import maps.Projection.Property;
|
||||
import maps.Projection.Type;
|
||||
import utils.Math2;
|
||||
|
||||
/**
|
||||
* All of the projections I invented, save the tetrahedral ones, because
|
||||
@ -52,7 +53,7 @@ public class MyProjections {
|
||||
if (R <= 1)
|
||||
return new double[] {
|
||||
Math.PI/2 * (1 - R*.2 - R*R*R*1.8),
|
||||
Math.atan2(y, x) + Math.PI/2};
|
||||
Math.atan2(x, -y)};
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@ -76,9 +77,14 @@ public class MyProjections {
|
||||
Complex z = new Complex(x, y);
|
||||
Complex ans = z.sin();
|
||||
double p = 2 * Math.atan(ans.abs());
|
||||
double theta = ans.getArgument() + Math.PI/2;
|
||||
double theta = Math2.floorMod(ans.getArgument() + 3*Math.PI/2, 2*Math.PI) - Math.PI;
|
||||
double lambda = Math.PI/2 - p;
|
||||
return new double[] {lambda, theta};
|
||||
if (x < -Math.PI/2)
|
||||
return new double[] {lambda, theta-2*Math.PI};
|
||||
else if (x < Math.PI/2)
|
||||
return new double[] {lambda, theta};
|
||||
else
|
||||
return new double[] {lambda, theta+2*Math.PI};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -158,7 +158,15 @@ public abstract class Projection {
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y, double[] pole) {
|
||||
return obliquifyPlnr(inverse(x, y), pole);
|
||||
return inverse(x, y, pole, false);
|
||||
}
|
||||
|
||||
public double[] inverse(double x, double y, double[] pole, boolean cropAtPi) {
|
||||
final double[] relCoords = inverse(x, y);
|
||||
if (relCoords == null || (cropAtPi && Math.abs(relCoords[1]) > Math.PI))
|
||||
return null; //cropAtPi removes all points with longitudes outside +- PI
|
||||
else
|
||||
return obliquifyPlnr(relCoords, pole);
|
||||
}
|
||||
|
||||
|
||||
@ -167,22 +175,27 @@ public abstract class Projection {
|
||||
|
||||
|
||||
public double[][][] map(int size) {
|
||||
return map(size, new double[] {Math.PI/2,0,0});
|
||||
return map(size, false);
|
||||
}
|
||||
|
||||
public double[][][] map(int size, double[] pole) {
|
||||
public double[][][] map(int size, boolean cropAtPi) {
|
||||
return map(size, new double[] {Math.PI/2,0,0}, cropAtPi);
|
||||
}
|
||||
|
||||
public double[][][] map(int size, double[] pole, boolean cropAtPi) {
|
||||
if (width >= height)
|
||||
return map(size, Math.max(Math.round(size*height/width),1), pole, null);
|
||||
return map(size, Math.max(Math.round(size*height/width),1), pole, cropAtPi, null);
|
||||
else
|
||||
return map(Math.max(Math.round(size*width/height),1), size, pole, null);
|
||||
return map(Math.max(Math.round(size*width/height),1), size, pole, cropAtPi, null);
|
||||
}
|
||||
|
||||
public double[][][] map(double w, double h, double[] pole, DoubleConsumer tracker) { //generate a matrix of coordinates based on a map projection
|
||||
public double[][][] map(double w, double h, double[] pole, boolean cropAtPi,
|
||||
DoubleConsumer tracker) { //generate a matrix of coordinates based on a map projection
|
||||
final double[][][] output = new double[(int) h][(int) w][2];
|
||||
for (int y = 0; y < h; y ++) {
|
||||
for (int x = 0; x < w; x ++)
|
||||
output[y][x] = inverse(
|
||||
((x+0.5)/w-1/2.)*width, (1/2.-(y+0.5)/h)*height, pole);
|
||||
((x+0.5)/w-1/2.)*width, (1/2.-(y+0.5)/h)*height, pole, cropAtPi);
|
||||
if (tracker != null)
|
||||
tracker.accept((double)y / (int)h);
|
||||
}
|
||||
@ -295,7 +308,7 @@ public abstract class Projection {
|
||||
lon1 = 2*Math.PI-lon1;
|
||||
}
|
||||
lon1 = lon1-tht0;
|
||||
lon1 = Math2.mod(lon1+Math.PI, 2*Math.PI) - Math.PI;
|
||||
lon1 = Math2.floorMod(lon1+Math.PI, 2*Math.PI) - Math.PI;
|
||||
|
||||
return new double[] {lat1, lon1};
|
||||
}
|
||||
@ -308,8 +321,6 @@ public abstract class Projection {
|
||||
* @return { LAT, LON }
|
||||
*/
|
||||
protected static final double[] obliquifyPlnr(double[] coords, double[] pole) {
|
||||
if (coords == null) return null;
|
||||
|
||||
double lat1 = coords[0], lon1 = coords[1];
|
||||
final double lat0 = pole[0], lon0 = pole[1], tht0 = pole[2];
|
||||
lon1 += tht0;
|
||||
@ -333,6 +344,9 @@ public abstract class Projection {
|
||||
lonf = lon0 -
|
||||
Math.acos(innerFunc);
|
||||
|
||||
if (Math.abs(lonf) > Math.PI)
|
||||
lonf = Math2.floorMod(lonf+Math.PI, 2*Math.PI) - Math.PI;
|
||||
|
||||
double thtf = pole[2];
|
||||
|
||||
double[] output = {latf, lonf, thtf};
|
||||
|
||||
@ -47,7 +47,7 @@ public class Snyder {
|
||||
|
||||
private static final double TOLERANCE = 10e-4;
|
||||
private static final double[] LIMS = {
|
||||
Math.toRadians(10), Math.toRadians(85), Math.toRadians(-195), Math.toRadians(-50) }; //trims the outside unsightly portions
|
||||
Math.toRadians(5), Math.toRadians(85), Math.toRadians(-195), Math.toRadians(-50) }; //trims the outside unsightly portions
|
||||
|
||||
|
||||
public static final Projection GS50 =
|
||||
@ -78,12 +78,13 @@ public class Snyder {
|
||||
z = z.minus(error.divide(deriv));
|
||||
error = f(z).minus(p);
|
||||
}
|
||||
final double r = z.abs();
|
||||
final double phi = 2*Math.atan(r/2);
|
||||
final double lat = Math.asin(Math.cos(phi)*Math.sin(POLE[0]) + z.getIm()*Math.sin(phi)*Math.cos(POLE[0])/r);
|
||||
final double lon = POLE[1] + Math.atan(z.getRe()*Math.sin(phi)/(r*Math.cos(POLE[0])*Math.cos(phi)-z.getIm()*Math.sin(POLE[0]*Math.sin(phi))));
|
||||
double r = z.abs();
|
||||
double phi = 2*Math.atan(r/2);
|
||||
double lat = Math.asin(Math.cos(phi)*Math.sin(POLE[0]) + z.getIm()*Math.sin(phi)*Math.cos(POLE[0])/r);
|
||||
double lon = POLE[1] + Math.atan(z.getRe()*Math.sin(phi)/(r*Math.cos(POLE[0])*Math.cos(phi)-z.getIm()*Math.sin(POLE[0]*Math.sin(phi))));
|
||||
if (lat < LIMS[0] || lat > LIMS[1]) return null;
|
||||
if (lon < LIMS[2] || lon > LIMS[3]) return null;
|
||||
if (lon < -Math.PI) lon += 2*Math.PI;
|
||||
return new double[] {lat, lon};
|
||||
}
|
||||
};
|
||||
|
||||
@ -25,6 +25,7 @@ package maps;
|
||||
|
||||
import maps.Projection.Property;
|
||||
import utils.Dixon;
|
||||
import utils.Math2;
|
||||
import utils.NumericalAnalysis;
|
||||
|
||||
/**
|
||||
@ -296,8 +297,16 @@ public class Tetrahedral {
|
||||
final double x0 = centrum[4];
|
||||
final double y0 = centrum[5];
|
||||
|
||||
return obliquifyPlnr(
|
||||
innerInverse(Math.hypot(x-x0, y-y0), Math.atan2(y-y0, x-x0) - th0), centrum);
|
||||
double[] relCoords =
|
||||
innerInverse(Math.hypot(x-x0, y-y0), Math.atan2(y-y0, x-x0) - th0);
|
||||
|
||||
if (relCoords == null)
|
||||
return null;
|
||||
|
||||
double[] absCoords = obliquifyPlnr(relCoords, centrum);
|
||||
if (Math.abs(absCoords[1]) > Math.PI)
|
||||
absCoords[1] = Math2.floorMod(absCoords[1]+Math.PI, 2*Math.PI) - Math.PI;
|
||||
return absCoords;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ public final class WinkelTripel {
|
||||
public double[] inverse(double x, double y) {
|
||||
return NumericalAnalysis.newtonRaphsonApproximation(
|
||||
x, y,
|
||||
y/2, x*(1 + Math.cos(y*Math.PI/2))/(2 + 2*Math.cos(stdParallel)),
|
||||
y/2, x*(1 + Math.cos(y*Math.PI/2))/(2 + 2*Math.cos(stdParallel)), //inital guess is Eckert V
|
||||
this::f1pX, this::f2pY,
|
||||
this::df1dphi, this::df1dlam, this::df2dphi, this::df2dlam, .002);
|
||||
}
|
||||
|
||||
63
src/utils/Flag.java
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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 utils;
|
||||
|
||||
|
||||
/**
|
||||
* Because Java apparently doesn't already have a mutable Bullion
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class Flag {
|
||||
|
||||
private boolean set;
|
||||
|
||||
public Flag() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public Flag(boolean set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return this.set;
|
||||
}
|
||||
|
||||
public void set(boolean set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
public void set() {
|
||||
this.set = true;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.set = false;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Flag("+this.set+")";
|
||||
}
|
||||
}
|
||||
@ -83,13 +83,13 @@ public class Math2 {
|
||||
}
|
||||
|
||||
|
||||
public static final double determ(double a, double b, double c, double d) {
|
||||
return a*d - b*c;
|
||||
public static final double floorMod(double x, double y) {
|
||||
return x - Math.floor(x / y) * y;
|
||||
}
|
||||
|
||||
|
||||
public static final double mod(double x, double y) {
|
||||
return x - Math.floor(x/y)*y;
|
||||
public static final double determ(double a, double b, double c, double d) {
|
||||
return a*d - b*c;
|
||||
}
|
||||
|
||||
|
||||
|
||||
92
src/utils/MutableDouble.java
Normal file
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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 utils;
|
||||
|
||||
|
||||
/**
|
||||
* Because Java apparently doesn't already have a mutable Double
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class MutableDouble {
|
||||
|
||||
private double value;
|
||||
|
||||
public MutableDouble() {
|
||||
this(0.);
|
||||
}
|
||||
|
||||
public MutableDouble(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public double get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public boolean isFinite() {
|
||||
return Double.isFinite(this.value);
|
||||
}
|
||||
|
||||
public boolean isInfinite() {
|
||||
return Double.isInfinite(this.value);
|
||||
}
|
||||
|
||||
public boolean isNaN() {
|
||||
return Double.isNaN(this.value);
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return this.equals(0);
|
||||
}
|
||||
|
||||
public boolean equals(double value) {
|
||||
return this.value == value;
|
||||
}
|
||||
|
||||
public void set(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
this.increment(1);
|
||||
}
|
||||
|
||||
public void increment(double value) {
|
||||
this.value += value;
|
||||
}
|
||||
|
||||
public void decrement() {
|
||||
this.decrement(1);
|
||||
}
|
||||
|
||||
public void decrement(double value) {
|
||||
this.value -= value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MutableDouble("+value+")";
|
||||
}
|
||||
|
||||
}
|
||||
43
src/utils/Spinner.java
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 utils;
|
||||
|
||||
|
||||
/**
|
||||
* It's a JavaFX spinner, but with intuitive behaviour (i.e. it commits its value when it loses
|
||||
* focus, not just when the user presses enter)
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public class Spinner<T> extends javafx.scene.control.Spinner<T> {
|
||||
|
||||
public Spinner() {
|
||||
super();
|
||||
this.focusedProperty().addListener((observable, prev, now) -> {
|
||||
if (!now) //why I'm not allowed to call commitEditorText() is beyond me, since it clearly doesn't do that enough
|
||||
this.increment(0); //was that so hard?
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||