mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-10-16 00:00:11 -04:00
I don't care if it's the Survivor's bathrobe!
I added more inheritance. When I think of the word "inheritance", the first thing that comes to mind is the above quote. It's from one of the broadsheet articles in The Bands of Mourning. Actually, most of what I did in this commit had to do with parametrization, not inheritance. Oh, well. It's a good quote. Maps can have parameters now. Woo. It's an opportunity that Equal-Area Cylindrical (very simple), my made-up map projections (not that hard a leap), and Winkel Tripel (surprisingly difficult) currently take advantage of. I still need to make the conic projections be parametrized, and Tobler once I can make it be real. I also still need to make MapAnalyzer take advantage of this newfound inheritance.
This commit is contained in:
parent
e9e9721598
commit
9590f6e3a8
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
BIN
output/myMap.jpg
BIN
output/myMap.jpg
Binary file not shown.
Before Width: | Height: | Size: 2.7 MiB |
BIN
output/mymap.png
Normal file
BIN
output/mymap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
@ -7,6 +7,8 @@ import java.io.File;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
import dialogs.ProgressBarDialog;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
@ -18,16 +20,20 @@ import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.control.SpinnerValueFactory.DoubleSpinnerValueFactory;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
@ -48,7 +54,7 @@ import util.Procedure;
|
||||
*/
|
||||
public abstract class MapApplication extends Application {
|
||||
|
||||
protected static final int CONT_WIDTH = 300;
|
||||
protected static final int CONT_WIDTH = 350;
|
||||
protected static final int IMG_WIDTH = 500;
|
||||
|
||||
private static final KeyCombination ctrlO = new KeyCodeCombination(KeyCode.O, KeyCodeCombination.CONTROL_DOWN);
|
||||
@ -63,9 +69,14 @@ public abstract class MapApplication extends Application {
|
||||
{ 0., 0.,-32., -35., -45., 161.5, 137., 145., -150., 138.,-10. } };
|
||||
|
||||
|
||||
protected Stage root;
|
||||
final protected String name;
|
||||
final private String name;
|
||||
private Stage root;
|
||||
private ComboBox<Projection> projectionChooser;
|
||||
private GridPane paramGrid;
|
||||
private Label[] paramLabels;
|
||||
private Slider[] paramSliders;
|
||||
private Spinner<Double>[] paramSpinners;
|
||||
private double[] currentParams;
|
||||
|
||||
|
||||
|
||||
@ -150,6 +161,7 @@ public abstract class MapApplication extends Application {
|
||||
projectionChooser.setOnAction(new EventHandler<ActionEvent>() {
|
||||
public void handle(ActionEvent event) {
|
||||
description.setText(projectionChooser.getValue().getDescription());
|
||||
revealParameters(projectionChooser.getValue());
|
||||
projectionSetter.execute();
|
||||
}
|
||||
});
|
||||
@ -168,69 +180,50 @@ public abstract class MapApplication extends Application {
|
||||
presetChooser.setTooltip(new Tooltip(
|
||||
"Set the aspect sliders based on a preset"));
|
||||
|
||||
final String[] labels = { "Latitude:", "Longitude:", "Ctr. Meridian:" };
|
||||
final Slider[] sliders = new Slider[] {
|
||||
new Slider(-90, 90, 90),
|
||||
new Slider(-90, 90, 90), //TODO: can we call setAspectByPreset("Standard") instead of this?
|
||||
new Slider(-180,180,0),
|
||||
new Slider(-180,180,0)
|
||||
};
|
||||
copyToArray(sliders, aspectArr);
|
||||
|
||||
final Spinner<Double> latSpinner = new Spinner<Double>(-90, 90, 90.0); //yes, this is awkward. Java gets weird about arrays with generic types
|
||||
new Slider(-180,180,0) };
|
||||
final Spinner<Double> spin0 = new Spinner<Double>(-90, 90, 90.0); //yes, this is awkward. Java gets weird about arrays with generic types
|
||||
@SuppressWarnings("unchecked")
|
||||
final Spinner<Double>[] spinners = (Spinner<Double>[]) Array.newInstance(latSpinner.getClass(), 3);
|
||||
spinners[0] = latSpinner;
|
||||
final Spinner<Double>[] spinners = (Spinner<Double>[]) Array.newInstance(spin0.getClass(), 3);
|
||||
spinners[0] = spin0;
|
||||
spinners[1] = new Spinner<Double>(-180, 180, 0.0);
|
||||
spinners[2] = new Spinner<Double>(-180, 180, 0.0);
|
||||
|
||||
for (int i = 0; i < 3; i ++)
|
||||
aspectArr[i] = Math.toRadians(sliders[i].getValue());
|
||||
|
||||
for (String preset: ASPECT_NAMES) {
|
||||
MenuItem m = new MenuItem(preset);
|
||||
m.setOnAction(new EventHandler<ActionEvent>() {
|
||||
public void handle(ActionEvent event) {
|
||||
setAspectByPreset(((MenuItem) event.getSource()).getText(),
|
||||
sliders, spinners);
|
||||
copyToArray(sliders, aspectArr);
|
||||
for (int i = 0; i < 3; i ++)
|
||||
aspectArr[i] = Math.toRadians(sliders[i].getValue());
|
||||
aspectSetter.execute();
|
||||
}
|
||||
});
|
||||
presetChooser.getItems().add(m);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i ++) {
|
||||
final Slider sld = sliders[i];
|
||||
final Spinner<Double> spn = spinners[i];
|
||||
GridPane.setHgrow(sld, Priority.ALWAYS);
|
||||
sld.setTooltip(new Tooltip("Change the aspect of the map"));
|
||||
sld.valueChangingProperty().addListener(
|
||||
(observable, then, now) -> {
|
||||
if (spn.getValue() != sld.getValue()) //TODO the boxes need to change when the dropdown fires
|
||||
spn.getEditor().textProperty().set(Double.toString(sld.getValue()));
|
||||
copyToArray(sliders, aspectArr);
|
||||
aspectSetter.execute();
|
||||
});
|
||||
|
||||
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;
|
||||
if (nv.equals("-") || nv.equals(".")) return;
|
||||
try {
|
||||
Double.parseDouble(nv);
|
||||
spn.increment(0); // forces the spinner to commit its value
|
||||
if (spn.getValue() != sld.getValue())
|
||||
sld.setValue(spn.getValue());
|
||||
copyToArray(sliders, aspectArr);
|
||||
aspectSetter.execute();
|
||||
} catch (NumberFormatException e) {
|
||||
spn.getEditor().textProperty().set(pv); //yeah, this is all pretty jank. JavaFX spinners are just weird by default
|
||||
}
|
||||
});
|
||||
}
|
||||
link(sliders, spinners, aspectArr, Math::toRadians, aspectSetter);
|
||||
|
||||
final GridPane grid = new GridPane();
|
||||
grid.addRow(0, new Text("Latitude:"), sliders[0], spinners[0]);
|
||||
grid.addRow(1, new Text("Longitude:"), sliders[1], spinners[1]);
|
||||
grid.addRow(2, new Text("Ctr. Meridian:"), sliders[2], spinners[2]);
|
||||
grid.setVgap(5);
|
||||
grid.setHgap(3);
|
||||
grid.getColumnConstraints().addAll(
|
||||
new ColumnConstraints(92,Control.USE_COMPUTED_SIZE,Control.USE_COMPUTED_SIZE),
|
||||
new ColumnConstraints(), new ColumnConstraints(92));
|
||||
for (int i = 0; i < 3; i ++) {
|
||||
GridPane.setHgrow(sliders[i], Priority.ALWAYS);
|
||||
sliders[i].setTooltip(new Tooltip("Change the aspect of the map"));
|
||||
spinners[i].setTooltip(new Tooltip("Change the aspect of the map"));
|
||||
spinners[i].setEditable(true);
|
||||
grid.addRow(i, new Label(labels[i]), sliders[i], spinners[i]);
|
||||
}
|
||||
|
||||
VBox all = new VBox(5, presetChooser, grid);
|
||||
all.setAlignment(Pos.CENTER);
|
||||
@ -238,6 +231,42 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a grid of sliders and spinners not unlike the aspectSelector
|
||||
* @param parameterSetter The function to execute when the parameters change
|
||||
* @return the full formatted Region
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Node buildParameterSelector(Procedure parameterSetter) {
|
||||
currentParams = new double[3];
|
||||
paramLabels = new Label[] {
|
||||
new Label(), new Label(), new Label() };
|
||||
paramSliders = new Slider[] {
|
||||
new Slider(), new Slider(), new Slider() }; // I don't think any projection has more than three 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(), 3);
|
||||
paramSpinners[0] = spin0;
|
||||
paramSpinners[1] = new Spinner<Double>(0.,0.,0.);
|
||||
paramSpinners[2] = new Spinner<Double>(0.,0.,0.);
|
||||
|
||||
link(paramSliders, paramSpinners, currentParams, (d)->d, parameterSetter);
|
||||
|
||||
for (int i = 0; i < 3; i ++) {
|
||||
GridPane.setHgrow(paramSliders[i], Priority.ALWAYS);
|
||||
paramSpinners[i].setEditable(true);
|
||||
}
|
||||
|
||||
paramGrid = new GridPane();
|
||||
paramGrid.setVgap(5);
|
||||
paramGrid.setHgap(3);
|
||||
paramGrid.getColumnConstraints().addAll(
|
||||
new ColumnConstraints(92,Control.USE_COMPUTED_SIZE,Control.USE_COMPUTED_SIZE),
|
||||
new ColumnConstraints(), new ColumnConstraints(92));
|
||||
return paramGrid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a default button that will update the map
|
||||
* @return the button
|
||||
@ -250,7 +279,7 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
});
|
||||
updateButton.setTooltip(new Tooltip(
|
||||
"Update the current map with your parameters."));
|
||||
"Update the current map with your parameters"));
|
||||
|
||||
updateButton.setDefaultButton(true);
|
||||
root.addEventHandler(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
|
||||
@ -271,21 +300,22 @@ public abstract class MapApplication extends Application {
|
||||
*/
|
||||
protected Button buildSaveButton(boolean bindCtrlS, String savee,
|
||||
FileChooser.ExtensionFilter[] allowedExtensions,
|
||||
Procedure parameterCollector,
|
||||
Procedure settingCollector,
|
||||
BiConsumer<File, ProgressBarDialog> calculateAndSaver) {
|
||||
final FileChooser saver = new FileChooser();
|
||||
saver.setInitialDirectory(new File("output"));
|
||||
saver.setInitialFileName("my"+savee+allowedExtensions[0].getExtensions().get(0));
|
||||
saver.setInitialFileName("my"+savee+allowedExtensions[0].getExtensions().get(0).substring(1));
|
||||
saver.setTitle("Save "+savee);
|
||||
saver.getExtensionFilters().addAll(allowedExtensions);
|
||||
|
||||
final Button saveButton = new Button("Save "+savee+"...");
|
||||
saveButton.setOnAction(new EventHandler<ActionEvent>() {
|
||||
public void handle(ActionEvent event) {
|
||||
final File f = saver.showOpenDialog(root);
|
||||
final File f = saver.showSaveDialog(root);
|
||||
if (f != null) {
|
||||
parameterCollector.execute();
|
||||
settingCollector.execute();
|
||||
final ProgressBarDialog pBar = new ProgressBarDialog();
|
||||
pBar.setContentText("Finalizing "+savee+"...");
|
||||
pBar.show();
|
||||
trigger(saveButton, () -> {
|
||||
calculateAndSaver.accept(f, pBar);
|
||||
@ -293,7 +323,7 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
}
|
||||
});
|
||||
saveButton.setTooltip(new Tooltip("Save the "+savee+" with current settings."));
|
||||
saveButton.setTooltip(new Tooltip("Save the "+savee+" with current settings"));
|
||||
|
||||
if (bindCtrlS) // ctrl+S saves
|
||||
root.addEventHandler(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
|
||||
@ -303,7 +333,31 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
|
||||
|
||||
protected void setAspectByPreset(String presetName,
|
||||
protected Projection getProjection() {
|
||||
return projectionChooser.getValue();
|
||||
}
|
||||
|
||||
|
||||
protected double[] getParams() {
|
||||
return currentParams;
|
||||
}
|
||||
|
||||
|
||||
protected static final void trigger(Button btn, Runnable task) {
|
||||
btn.setDisable(true);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
task.run();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
btn.setDisable(false);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private void setAspectByPreset(String presetName,
|
||||
Slider[] sliders, Spinner<Double>[] spinners) {
|
||||
if (presetName.equals("Antipode")) {
|
||||
sliders[0].setValue(-sliders[0].getValue());
|
||||
@ -331,29 +385,51 @@ public abstract class MapApplication extends Application {
|
||||
}
|
||||
|
||||
|
||||
protected static final void trigger(Button btn, Runnable task) {
|
||||
btn.setDisable(true);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
task.run();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
btn.setDisable(false);
|
||||
}
|
||||
}).start();
|
||||
private void revealParameters(Projection proj) {
|
||||
final String[] paramNames = proj.getParameterNames();
|
||||
final double[][] paramValues = proj.getParameterValues();
|
||||
paramGrid.getChildren().clear();
|
||||
for (int i = 0; i < proj.getNumParameters(); i ++) {
|
||||
paramLabels[i].setText(paramNames[i]+":");
|
||||
paramSliders[i].setMin(paramValues[i][0]);
|
||||
paramSliders[i].setMax(paramValues[i][1]);
|
||||
paramSliders[i].setValue(paramValues[i][2]);
|
||||
final SpinnerValueFactory.DoubleSpinnerValueFactory paramSpinVF =
|
||||
(DoubleSpinnerValueFactory) paramSpinners[i].getValueFactory();
|
||||
paramSpinVF.setMin(paramValues[i][0]);
|
||||
paramSpinVF.setMax(paramValues[i][1]);
|
||||
paramSpinVF.setValue(paramValues[i][2]);
|
||||
final Tooltip tt = new Tooltip("Change the "+paramNames[i]+" of the map");
|
||||
paramSliders[i].setTooltip(tt);
|
||||
paramSpinners[i].setTooltip(tt);
|
||||
paramGrid.addRow(i, paramLabels[i], paramSliders[i], paramSpinners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Projection getProjection() {
|
||||
return projectionChooser.getValue();
|
||||
}
|
||||
|
||||
|
||||
private static void copyToArray(Slider[] sliders, double[] output) {
|
||||
assert sliders.length == 3; //ugh I can never remember how to accually activate these things. I'll just leave it here as a self-commenting thing
|
||||
for (int i = 0; i < 3; i ++)
|
||||
output[i] = Math.toRadians(sliders[i].getValue());
|
||||
private static void link(Slider[] sliders, Spinner<Double>[] spinners, double[] doubles,
|
||||
DoubleUnaryOperator converter, Procedure callback) {
|
||||
for (int i = 0; i < doubles.length; i ++) {
|
||||
final Slider sld = sliders[i];
|
||||
final Spinner<Double> spn = spinners[i];
|
||||
final int I = i;
|
||||
|
||||
sld.valueChangingProperty().addListener((observable, then, now) -> {
|
||||
if (then) {
|
||||
if (spn.getValue() != sld.getValue())
|
||||
spn.getValueFactory().setValue(sld.getValue());
|
||||
doubles[I] = converter.applyAsDouble(sld.getValue());
|
||||
callback.execute();
|
||||
}
|
||||
});
|
||||
|
||||
spn.valueProperty().addListener((observable, then, now) -> {
|
||||
if (spn.getValue() != sld.getValue())
|
||||
sld.setValue(spn.getValue());
|
||||
doubles[I] = converter.applyAsDouble(spn.getValue());
|
||||
callback.execute();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,8 +64,8 @@ public class MapDesignerRaster extends MapApplication {
|
||||
|
||||
|
||||
private static final FileChooser.ExtensionFilter[] RASTER_TYPES = {
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg; *.jpeg; *.jpe; *.jfif"),
|
||||
new FileChooser.ExtensionFilter("PNG", "*.png") };
|
||||
new FileChooser.ExtensionFilter("PNG", "*.png"),
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif") };
|
||||
|
||||
private static final Projection[] PROJ_ARR = { Projection.MERCATOR, Projection.PLATE_CARREE, Projection.HOBO_DYER,
|
||||
Projection.GALL, Projection.STEREOGRAPHIC, Projection.POLAR, Projection.E_A_AZIMUTH,
|
||||
@ -106,15 +106,17 @@ public class MapDesignerRaster extends MapApplication {
|
||||
final Node inputSelector = buildInputSelector(RASTER_TYPES, this::setInput);
|
||||
final Node projectionSelector = buildProjectionSelector(PROJ_ARR, Projection.MERCATOR, Procedure.NONE);
|
||||
final Node aspectSelector = buildAspectSelector(this.aspect, Procedure.NONE);
|
||||
final Node parameterSelector = buildParameterSelector(Procedure.NONE);
|
||||
this.updateBtn = buildUpdateButton(this::updateMap);
|
||||
this.saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES,
|
||||
this::collectFinalParameters, this::calculateAndSaveMap);
|
||||
this::collectFinalSettings, this::calculateAndSaveMap);
|
||||
final HBox buttons = new HBox(5, updateBtn, saveMapBtn);
|
||||
buttons.setAlignment(Pos.CENTER);
|
||||
|
||||
final VBox layout = new VBox(5,
|
||||
inputSelector, new Separator(), projectionSelector,
|
||||
new Separator(), aspectSelector, new Separator(), buttons);
|
||||
new Separator(), aspectSelector, parameterSelector,
|
||||
new Separator(), buttons);
|
||||
layout.setAlignment(Pos.CENTER);
|
||||
layout.setPrefWidth(CONT_WIDTH);
|
||||
|
||||
@ -155,21 +157,22 @@ public class MapDesignerRaster extends MapApplication {
|
||||
}
|
||||
|
||||
|
||||
protected void collectFinalParameters() {
|
||||
final double ratio = getProjection().getAspectRatio(getProjection().getDefaultParameters()); //TODO: get real parameters
|
||||
protected void collectFinalSettings() {
|
||||
final double ratio = getProjection().getAspectRatio(this.getParams());
|
||||
this.configDialog = new MapConfigurationDialog(ratio);
|
||||
this.configDialog.showAndWait();
|
||||
}
|
||||
|
||||
|
||||
protected void calculateAndSaveMap(File file, ProgressBarDialog pBar) {
|
||||
pBar.setContentText("Finalizing map...");
|
||||
Image theMap = map(
|
||||
configDialog.getDims(), configDialog.getSmoothing(), pBar); //calculate
|
||||
Platform.runLater(() -> pBar.setProgress(-1));
|
||||
pBar.setContentText("Saving...");
|
||||
final String filename = file.getName();
|
||||
final String extension;
|
||||
extension = filename.substring(filename.lastIndexOf('.')+1);
|
||||
try {
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(theMap,null), "jpg", file); //save
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(theMap,null), extension, file); //save
|
||||
} catch (IOException e) {
|
||||
final Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setHeaderText("Failure!");
|
||||
@ -180,7 +183,7 @@ public class MapDesignerRaster extends MapApplication {
|
||||
|
||||
|
||||
public Image map() {
|
||||
final double a = this.getProjection().getAspectRatio(getProjection().getDefaultParameters()); //TODO: get real parameters
|
||||
final double a = this.getProjection().getAspectRatio(this.getParams());
|
||||
return map(new int[] { IMG_WIDTH, (int)(IMG_WIDTH/a) }, 1);
|
||||
}
|
||||
|
||||
@ -191,7 +194,7 @@ public class MapDesignerRaster extends MapApplication {
|
||||
public Image map(int[] outDims, int smoothing,
|
||||
ProgressBarDialog pbar) {
|
||||
final Projection proj = this.getProjection();
|
||||
final double[] params = proj.getDefaultParameters(); //TODO: get real parameters
|
||||
final double[] params = this.getParams();
|
||||
final PixelReader ref = input.getPixelReader();
|
||||
final int[] refDims = {(int)input.getWidth(), (int)input.getHeight()};
|
||||
|
||||
|
@ -32,7 +32,6 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import dialogs.ProgressBarDialog;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
@ -107,13 +106,15 @@ public class MapDesignerVector extends MapApplication {
|
||||
this.aspect = new double[3];
|
||||
final Node inputSelector = buildInputSelector(VECTOR_TYPES, this::setInput);
|
||||
final Node projectionSelector = buildProjectionSelector(PROJ_ARR, Projection.MERCATOR, this::updateMap);
|
||||
final Node aspectSelector = buildAspectSelector(this.aspect, this::setAspect);
|
||||
final Node aspectSelector = buildAspectSelector(this.aspect, this::updateMap);
|
||||
final Node parameterSelector = buildParameterSelector(this::updateMap);
|
||||
this.saveBtn = buildSaveButton(true, "map", VECTOR_TYPES,
|
||||
Procedure.NONE, this::calculateAndSaveMap);
|
||||
|
||||
final VBox layout = new VBox(5,
|
||||
inputSelector, new Separator(), projectionSelector,
|
||||
new Separator(), aspectSelector, new Separator(), saveBtn);
|
||||
new Separator(), aspectSelector, parameterSelector,
|
||||
new Separator(), saveBtn);
|
||||
|
||||
layout.setAlignment(Pos.CENTER);
|
||||
layout.setPrefWidth(CONT_WIDTH);
|
||||
@ -210,11 +211,6 @@ public class MapDesignerVector extends MapApplication {
|
||||
}
|
||||
|
||||
|
||||
private void setAspect() {
|
||||
updateMap();
|
||||
}
|
||||
|
||||
|
||||
private void updateMap() {
|
||||
drawImage(map(DEF_MAX_VTX, null), viewer);
|
||||
}
|
||||
@ -237,11 +233,8 @@ public class MapDesignerVector extends MapApplication {
|
||||
|
||||
|
||||
private void calculateAndSaveMap(File file, ProgressBarDialog pBar) {
|
||||
pBar.setContentText("Finalizing map...");
|
||||
List<List<double[]>> map = map(0, pBar); //calculate
|
||||
Platform.runLater(() -> pBar.setProgress(-1));
|
||||
pBar.setContentText("Saving..."); //save
|
||||
saveToSVG(map, file, pBar);
|
||||
saveToSVG(map, file, pBar); //save
|
||||
}
|
||||
|
||||
|
||||
@ -276,7 +269,7 @@ public class MapDesignerVector extends MapApplication {
|
||||
private List<List<double[]>> map(int maxVtx,
|
||||
ProgressBarDialog pbar) {
|
||||
final Projection proj = this.getProjection();
|
||||
final double[] params = proj.getDefaultParameters(); //TODO: get real parameters
|
||||
final double[] params = this.getParams();
|
||||
|
||||
int step = maxVtx==0 ? 1 : numVtx/maxVtx+1;
|
||||
List<List<double[]>> output = new LinkedList<List<double[]>>();
|
||||
|
@ -25,6 +25,7 @@ package dialogs;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ButtonBar.ButtonData;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
@ -35,7 +36,7 @@ import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class MapConfigurationDialog extends Dialog<Void> {
|
||||
public class MapConfigurationDialog extends Dialog<Boolean> {
|
||||
|
||||
public final double DEF_SIZE = 1000;
|
||||
|
||||
@ -119,7 +120,12 @@ public class MapConfigurationDialog extends Dialog<Void> {
|
||||
pane.getButtonTypes().addAll(new ButtonType[] { ButtonType.OK, ButtonType.CANCEL }); // add buttons
|
||||
this.updateGUI();
|
||||
|
||||
this.setResultConverter((btn) -> { return null; }); //this needn't return anything
|
||||
this.setResultConverter((btn) -> {
|
||||
if (btn != null && btn.getButtonData() == ButtonData.OK_DONE)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
|
||||
realEdit = true;
|
||||
}
|
||||
|
@ -56,9 +56,6 @@ public class ProgressBarDialog extends Dialog<Void> {
|
||||
|
||||
this.box = new HBox(5);
|
||||
|
||||
pane.contentTextProperty().addListener((arg0) -> { // set it to refresh the gui when... the content texts?
|
||||
this.resetBar();
|
||||
});
|
||||
this.setTitle(""); // set the words on it
|
||||
pane.setHeaderText("Please wait.");
|
||||
pane.getButtonTypes().addAll(new ButtonType[] { ButtonType.CLOSE });
|
||||
@ -83,9 +80,13 @@ public class ProgressBarDialog extends Dialog<Void> {
|
||||
|
||||
|
||||
public void setProgress(double p) {
|
||||
Platform.runLater(() -> {
|
||||
if (p >= 0)
|
||||
this.words.setText((Math.round(1000*p)/10.0)+"%");
|
||||
else
|
||||
this.words.setText("...");
|
||||
});
|
||||
this.bar.setProgress(p);
|
||||
Platform.runLater(() ->
|
||||
this.words.setText((Math.round(1000*p)/10.0)+"%"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -393,8 +393,9 @@ public enum Projection {
|
||||
}
|
||||
},
|
||||
|
||||
TOBLER("Tobler", "An equal-area projection shaped like a 2.5th-order hyperellipse",
|
||||
2., 0b1001, "pseudocylindrical", "equal-area") {
|
||||
TOBLER("Tobler", "An equal-area projection shaped like a hyperellipse",
|
||||
2., 0b1001, "pseudocylindrical", "equal-area",new String[]{"alpha","gamma","k"},
|
||||
new double[][] {{0,1,0}, {1,5,2.5}, {1,2,1.831}}) {
|
||||
public double[] project(double lat, double lon, double[] params) {
|
||||
return new double[] {
|
||||
lon*Tobler.xfacFromLat(lat)*18,
|
||||
@ -402,7 +403,7 @@ public enum Projection {
|
||||
}
|
||||
public double[] inverse(double x, double y, double[] params) {
|
||||
return new double[] {
|
||||
Tobler.latFromY(5*y),
|
||||
Tobler.latFromY(5*y), //TODO: make this be real
|
||||
x/Tobler.xfacFromY(5*y)*Math.PI/18 };
|
||||
}
|
||||
},
|
||||
@ -456,16 +457,23 @@ public enum Projection {
|
||||
},
|
||||
|
||||
WINKEL_TRIPEL("Winkel Tripel", "National Geographic's compromise projection of choice",
|
||||
Math.PI/2, 0b1011, "other", "compromise") {
|
||||
0, 0b1011, "other", "compromise",
|
||||
new String[] {"Std. Parallel"}, new double[][] {{0,90,50.4598}}) {
|
||||
public double[] project(double lat, double lon, double[] params) {
|
||||
return new double[] { WinkelTripel.f1pX(lat,lon)/(.5+1/Math.PI),
|
||||
WinkelTripel.f2pY(lat,lon)/(.5+1/Math.PI) };
|
||||
final double cosphi0 = Math.cos(Math.toRadians(params[0]));
|
||||
return new double[] { WinkelTripel.f1pX(lat,lon,cosphi0)/(1+cosphi0),
|
||||
WinkelTripel.f2pY(lat,lon,cosphi0)/(1+cosphi0) };
|
||||
}
|
||||
public double[] inverse(double x, double y, double[] params) {
|
||||
final double cosphi0 = Math.cos(Math.toRadians(params[0]));
|
||||
return NumericalAnalysis.newtonRaphsonApproximation(
|
||||
x*(1 + Math.PI/2), y*Math.PI/2,
|
||||
x*Math.PI*(1 + cosphi0), y*Math.PI,
|
||||
WinkelTripel::f1pX, WinkelTripel::f2pY, WinkelTripel::df1dphi,
|
||||
WinkelTripel::df1dlam, WinkelTripel::df2dphi, WinkelTripel::df2dlam, .0625);
|
||||
WinkelTripel::df1dlam, WinkelTripel::df2dphi, WinkelTripel::df2dlam,
|
||||
.05, cosphi0);
|
||||
}
|
||||
public double getAspectRatio(double[] params) {
|
||||
return 1 + Math.cos(Math.toRadians(params[0]));
|
||||
}
|
||||
},
|
||||
|
||||
@ -607,11 +615,10 @@ public enum Projection {
|
||||
},
|
||||
|
||||
HYPERELLIPOWER("Hyperellipower", "A parametric projection that I'm still testing",
|
||||
2., 0b1111, "pseudocylindrical", "compromise") {
|
||||
2., 0b1111, "pseudocylindrical", "compromise", new String[] {"k","n","a"},
|
||||
new double[][] {{1,5,3.7308},{.5,2.,1.2027},{.5,2.,1.1443}}) {
|
||||
public double[] project(double lat, double lon, double[] params) {
|
||||
final double k = 3.7308;
|
||||
final double n = 1.2027;
|
||||
final double a = 1.1443;
|
||||
final double k = params[0], n = params[1], a = params[2];
|
||||
return new double[] {
|
||||
Math.pow(1 - Math.pow(Math.abs(lat/(Math.PI/2)), k),1/k)*lon,
|
||||
(1-Math.pow(1-Math.abs(lat/(Math.PI/2)), n))/Math.sqrt(n)*Math.signum(lat)*Math.PI/2*a};
|
||||
@ -622,12 +629,10 @@ public enum Projection {
|
||||
},
|
||||
|
||||
TETRAPOWER("Tetrapower", "A parametric projection that I'm still testing",
|
||||
Math.sqrt(3), 0b1111, "tetrahedral", "compromise") {
|
||||
Math.sqrt(3), 0b1111, "tetrahedral", "compromise", new String[] {"k1","k2","k3"},
|
||||
new double[][] {{.25,4.,1.4586},{.25,4.,1.2891},{.25,4.,1.9583}}) {
|
||||
public double[] project(double lat, double lon, double[] params) {
|
||||
final double k1 = 1.4586;
|
||||
final double k2 = 1.2891;
|
||||
final double k3 = 1.9583;
|
||||
|
||||
final double k1 = params[0], k2 = params[1], k3 = params[2];
|
||||
return tetrahedralProjectionForward(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;
|
||||
@ -646,11 +651,10 @@ public enum Projection {
|
||||
},
|
||||
|
||||
TETRAFILLET("Tetrafillet", "A parametric projection that I'm still testing",
|
||||
2., 0b1111, "other", "compromise") {
|
||||
2., 0b1111, "other", "compromise", new String[] {"k1","k2","k3"},
|
||||
new double[][] {{.25,4.,1.1598},{.25,4.,.36295},{.25,4.,1.9553}}) {
|
||||
public double[] project(double lat, double lon, double[] params) {
|
||||
final double k1 = 1.1598;
|
||||
final double k2 = .36295;
|
||||
final double k3 = 1.9553;
|
||||
final double k1 = params[0], k2 = params[1], k3 = params[2];
|
||||
return tetrahedralProjectionForward(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;
|
||||
@ -1027,6 +1031,10 @@ public enum Projection {
|
||||
return params;
|
||||
}
|
||||
|
||||
public double[][] getParameterValues() {
|
||||
return this.paramValues;
|
||||
}
|
||||
|
||||
public double getAspectRatio(double[] params) {
|
||||
return this.aspectRatio;
|
||||
}
|
||||
|
@ -25,48 +25,56 @@ package maps;
|
||||
|
||||
/**
|
||||
* All the useful Winkel Tripel equations and derivatives.
|
||||
* I tried solving for these equations myself, and I think I got them mostly
|
||||
* right, but the expressions were just too complicated. I got better results
|
||||
* by transcribing the below equations from Ipbüker and Bildirici's paper,
|
||||
* "A General Algorithm for the Inverse Transformation of Map Projections Using Jacobian Matrices"
|
||||
*
|
||||
* Ipbüker, Cengizhan; Bildirici, I.Öztug (2002). "A General Algorithm for the
|
||||
* Inverse Transformation of Map Projections Using Jacobian Matrices".
|
||||
* Proceedings of the Third International Symposium Mathematical &
|
||||
* Computational Applications. Third International Symposium Mathematical &
|
||||
* Computational Applications September 4-6, 2002. Konya, Turkey. Selcuk,
|
||||
* Turkey. pp. 175–182. Archived from the original on 20 October 2014.
|
||||
*
|
||||
* @author jkunimune
|
||||
*/
|
||||
public final class WinkelTripel {
|
||||
|
||||
private static final double COS_PHI0 = 2/Math.PI;
|
||||
|
||||
|
||||
public static final double f1pX(double phi, double lam) {
|
||||
public static final double f1pX(double phi, double lam, double... consts) { //consts should contain cos(phi_0) and only cos(phi_0)
|
||||
final double d = D(phi,lam);
|
||||
final double c = C(phi,lam);
|
||||
return (2*d/Math.sqrt(c)*Math.cos(phi)*Math.sin(lam/2) + lam*COS_PHI0)/2.;
|
||||
return 2*d/Math.sqrt(c)*Math.cos(phi)*Math.sin(lam/2) + lam*consts[0];
|
||||
}
|
||||
|
||||
public static final double f2pY(double phi, double lam) {
|
||||
public static final double f2pY(double phi, double lam, double... consts) {
|
||||
final double d = D(phi,lam);
|
||||
final double c = C(phi,lam);
|
||||
return (d/Math.sqrt(c)*Math.sin(phi) + phi)/2.0;
|
||||
return d/Math.sqrt(c)*Math.sin(phi) + phi;
|
||||
}
|
||||
|
||||
public static final double df1dphi(double phi, double lam) {
|
||||
public static final double df1dphi(double phi, double lam, double... consts) {
|
||||
final double d = D(phi,lam);
|
||||
final double c = C(phi,lam);
|
||||
return Math.sin(lam)*Math.sin(2*phi)/(4*c) - d/Math.pow(c,1.5)*Math.sin(phi)*Math.sin(lam/2);
|
||||
}
|
||||
|
||||
public static final double df1dlam(double phi, double lam) {
|
||||
public static final double df1dlam(double phi, double lam, double... consts) {
|
||||
final double d = D(phi,lam);
|
||||
final double c = C(phi,lam);
|
||||
return (Math.pow(Math.cos(phi)*Math.sin(lam/2), 2)/c + d/Math.pow(c,1.5)*Math.cos(phi)*Math.cos(lam/2)*Math.pow(Math.sin(phi),2) + COS_PHI0)/2.0;
|
||||
return Math.pow(Math.cos(phi)*Math.sin(lam/2), 2)/c + d/Math.pow(c,1.5)*Math.cos(phi)*Math.cos(lam/2)*Math.pow(Math.sin(phi),2) + consts[0];
|
||||
}
|
||||
|
||||
public static final double df2dphi(double phi, double lam) {
|
||||
public static final double df2dphi(double phi, double lam, double... consts) {
|
||||
final double d = D(phi,lam);
|
||||
final double c = C(phi,lam);
|
||||
return (Math.pow(Math.sin(phi),2)*Math.cos(lam/2)/c + d/Math.pow(c,1.5)*(1-Math.pow(Math.cos(lam/2),2))*Math.cos(phi) + 1)/2.0;
|
||||
return Math.pow(Math.sin(phi),2)*Math.cos(lam/2)/c + d/Math.pow(c,1.5)*(1-Math.pow(Math.cos(lam/2),2))*Math.cos(phi) + 1;
|
||||
}
|
||||
|
||||
public static final double df2dlam(double phi, double lam) {
|
||||
public static final double df2dlam(double phi, double lam, double... consts) {
|
||||
final double d = D(phi,lam);
|
||||
final double c = C(phi,lam);
|
||||
return (Math.sin(2*phi)*Math.sin(lam/2)/c - d/Math.pow(c,1.5)*Math.sin(phi)*Math.pow(Math.cos(phi),2)*Math.sin(lam))/8.0;
|
||||
return (Math.sin(2*phi)*Math.sin(lam/2)/c - d/Math.pow(c,1.5)*Math.sin(phi)*Math.pow(Math.cos(phi),2)*Math.sin(lam))/4.0;
|
||||
}
|
||||
|
||||
private static final double D(double phi, double lam) {
|
||||
|
@ -24,7 +24,6 @@
|
||||
package util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
/**
|
||||
* A whole class just for numeric approximation methods
|
||||
@ -45,23 +44,26 @@ public class NumericalAnalysis {
|
||||
* @param df2dp The partial derivative of y with respect to phi
|
||||
* @param df2dl The partial derivative of y with respect to lam
|
||||
* @param tolerance The maximum error that this can return
|
||||
* @param params Constant parameters for the functions
|
||||
* @return the values of phi and lam that put f1pX and f2pY near x0 and y0
|
||||
*/
|
||||
public static final double[] newtonRaphsonApproximation(double x0, double y0, BinaryOperator<Double> fxpX, BinaryOperator<Double> fypY, BinaryOperator<Double> dfxdp,
|
||||
BinaryOperator<Double> dfxdl, BinaryOperator<Double> dfydp, BinaryOperator<Double> dfydl, double tolerance) {
|
||||
public static final double[] newtonRaphsonApproximation(double x0, double y0,
|
||||
VectorFunction fxpX, VectorFunction fypY, VectorFunction dfxdp,
|
||||
VectorFunction dfxdl, VectorFunction dfydp, VectorFunction dfydl,
|
||||
double tolerance, double... params) {
|
||||
double x = x0;
|
||||
double y = y0;
|
||||
double phi = y;
|
||||
double lam = x; // I used equirectangular for my initial guess
|
||||
double phi = y/2;
|
||||
double lam = x/2; // I used equirectangular for my initial guess
|
||||
double error = Math.PI;
|
||||
|
||||
for (int i = 0; i < 5 && error > tolerance; i++) {
|
||||
final double f1 = fxpX.apply(phi, lam) - x;
|
||||
final double f2 = fypY.apply(phi, lam) - y;
|
||||
final double df1dP = dfxdp.apply(phi, lam);
|
||||
final double df1dL = dfxdl.apply(phi, lam);
|
||||
final double df2dP = dfydp.apply(phi, lam);
|
||||
final double df2dL = dfydl.apply(phi, lam);
|
||||
final double f1 = fxpX.evaluate(phi, lam, params) - x;
|
||||
final double f2 = fypY.evaluate(phi, lam, params) - y;
|
||||
final double df1dP = dfxdp.evaluate(phi, lam, params);
|
||||
final double df1dL = dfxdl.evaluate(phi, lam, params);
|
||||
final double df2dP = dfydp.evaluate(phi, lam, params);
|
||||
final double df2dL = dfydl.evaluate(phi, lam, params);
|
||||
|
||||
phi -= (f1*df2dL - f2*df1dL) / (df1dP*df2dL - df2dP*df1dL);
|
||||
lam -= (f2*df1dP - f1*df2dP) / (df1dP*df2dL - df2dP*df1dL);
|
||||
@ -114,5 +116,11 @@ public class NumericalAnalysis {
|
||||
|
||||
return fx[N-1][N-1];
|
||||
}
|
||||
|
||||
|
||||
|
||||
public interface VectorFunction {
|
||||
public double evaluate(double x, double y, double[] constants);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user