mirror of
https://github.com/csharpee/Map-Projections.git
synced 2025-12-10 00:00:19 -05:00
Strength in unity
All map-based Applications have been changed to make use of MapApplication. Yay. I also fixed equal-area cylindrical to be real. Now if only Tobler and the conic maps were real.
This commit is contained in:
parent
9590f6e3a8
commit
eaeaee9a5e
@ -24,39 +24,32 @@
|
||||
package apps;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import dialogs.ProgressBarDialog;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.chart.BarChart;
|
||||
import javafx.scene.chart.CategoryAxis;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
@ -64,24 +57,32 @@ import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import maps.Projection;
|
||||
import util.Math2;
|
||||
import util.Procedure;
|
||||
|
||||
/**
|
||||
* An application to analyze the characteristics of map projections
|
||||
*
|
||||
* @author Justin Kunimune
|
||||
*/
|
||||
public class MapAnalyzer extends Application {
|
||||
public class MapAnalyzer extends MapApplication {
|
||||
|
||||
private static final int CONT_WIDTH = 300;
|
||||
private static final int IMG_WIDTH = 500;
|
||||
public static final void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static final int CHART_WIDTH = 400;
|
||||
private static final int ROUGH_SAMP_NUM = 250;
|
||||
private static final int FINE_SAMP_NUM = 1000;
|
||||
private static final double GLOBE_RES = .02;
|
||||
|
||||
private static final FileChooser.ExtensionFilter[] RASTER_TYPES = {
|
||||
new FileChooser.ExtensionFilter("PNG", "*.png"),
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif") };
|
||||
|
||||
private static final KeyCombination ctrlS = new KeyCodeCombination(KeyCode.S, KeyCodeCombination.CONTROL_DOWN);
|
||||
|
||||
|
||||
private static final Projection[] PROJ_ARR = { Projection.MERCATOR, Projection.PLATE_CARREE,
|
||||
Projection.GALL_PETERS, Projection.HOBO_DYER, Projection.BEHRMANN, Projection.LAMBERT_CYLIND,
|
||||
private static final Projection[] PROJ_ARR = { Projection.MERCATOR, Projection.PLATE_CARREE, Projection.GALL_PETERS,
|
||||
Projection.HOBO_DYER, Projection.BEHRMANN, Projection.LAMBERT_CYLIND, Projection.E_A_CYLIND,
|
||||
Projection.GALL, Projection.STEREOGRAPHIC, Projection.POLAR, Projection.E_A_AZIMUTH,
|
||||
Projection.ORTHOGRAPHIC, Projection.GNOMONIC, Projection.LAMBERT_CONIC, Projection.E_D_CONIC,
|
||||
Projection.ALBERS, Projection.LEE, Projection.TETRAGRAPH, Projection.SINUSOIDAL, Projection.MOLLWEIDE,
|
||||
@ -90,198 +91,173 @@ public class MapAnalyzer extends Application {
|
||||
Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET };
|
||||
|
||||
|
||||
private Stage stage;
|
||||
private FileChooser saver;
|
||||
private ComboBox<Projection> projectionChooser;
|
||||
private Text projectionDesc;
|
||||
private Button calculate, saveMap, saveCharts;
|
||||
private Label avgSizeDistort, avgShapeDistort;
|
||||
private ImageView output;
|
||||
private VBox charts;
|
||||
private BarChart<String, Number> sizeChart, shapeChart;
|
||||
private Button updateBtn;
|
||||
private Text avgSizeDistort, avgShapeDistort;
|
||||
private ImageView mapDisplay;
|
||||
private Region charts;
|
||||
private BarChart<String, Number> sizeChart;
|
||||
private BarChart<String, Number> shapeChart;
|
||||
|
||||
|
||||
|
||||
public static final void main(String[] args) {
|
||||
launch(args);
|
||||
public MapAnalyzer() {
|
||||
super("Map Analyzer");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
stage = primaryStage;
|
||||
stage.setTitle("Map Analyzer");
|
||||
public void start(Stage root) {
|
||||
super.start(root);
|
||||
new Thread(() -> {
|
||||
calculateAndUpdate();
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Node makeWidgets() {
|
||||
final Node projectionSelector = buildProjectionSelector(PROJ_ARR, Projection.MERCATOR, Procedure.NONE);
|
||||
final Node parameterSelector = buildParameterSelector(Procedure.NONE);
|
||||
final Node textDisplay = buildTextDisplay();
|
||||
this.updateBtn = buildUpdateButton(this::calculateAndUpdate);
|
||||
this.updateBtn.setText("Calculate"); //I don't need to follow your darn conventions!
|
||||
final Button saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES,
|
||||
Procedure.NONE, this::calculateAndSaveMap);
|
||||
final Button savePltBtn = buildSaveButton(true, "plots", RASTER_TYPES,
|
||||
Procedure.NONE, this::calculateAndSavePlot);
|
||||
final HBox buttons = new HBox(5, updateBtn, saveMapBtn, savePltBtn);
|
||||
buttons.setAlignment(Pos.CENTER);
|
||||
|
||||
final VBox layout = new VBox();
|
||||
layout.setSpacing(5);
|
||||
final VBox layout = new VBox(5,
|
||||
projectionSelector, parameterSelector, new Separator(),
|
||||
buttons, new Separator(), textDisplay);
|
||||
layout.setAlignment(Pos.CENTER);
|
||||
layout.setPrefWidth(CONT_WIDTH);
|
||||
layout.setPrefWidth(GUI_WIDTH);
|
||||
|
||||
Label lbl = new Label("Projection:");
|
||||
ObservableList<Projection> items = FXCollections.observableArrayList(PROJ_ARR);
|
||||
projectionChooser = new ComboBox<Projection>(items);
|
||||
projectionChooser.setOnAction(new EventHandler<ActionEvent>() {
|
||||
public void handle(ActionEvent event) {
|
||||
projectionDesc.setText(projectionChooser.getValue().getDescription());
|
||||
}
|
||||
});
|
||||
projectionChooser.setValue(Projection.MERCATOR);
|
||||
layout.getChildren().add(new HBox(3, lbl, projectionChooser));
|
||||
this.mapDisplay = new ImageView();
|
||||
this.mapDisplay.setFitWidth(IMG_WIDTH);
|
||||
this.mapDisplay.setFitHeight(IMG_WIDTH);
|
||||
this.mapDisplay.setPreserveRatio(true);
|
||||
final StackPane pane = new StackPane(mapDisplay);
|
||||
pane.setMinWidth(IMG_WIDTH);
|
||||
|
||||
projectionDesc = new Text(projectionChooser.getValue().getDescription());
|
||||
projectionDesc.setWrappingWidth(CONT_WIDTH);
|
||||
layout.getChildren().add(projectionDesc);
|
||||
this.charts = buildDistortionHistograms();
|
||||
|
||||
calculate = new Button("Calculate");
|
||||
calculate.setOnAction(new EventHandler<ActionEvent>() {
|
||||
public void handle(ActionEvent event) {
|
||||
calculateMap();
|
||||
}
|
||||
});
|
||||
calculate.setTooltip(new Tooltip(
|
||||
"Calculate the distortion for this map."));
|
||||
calculate.setDefaultButton(true);
|
||||
final HBox gui = new HBox(10, layout, pane, charts);
|
||||
gui.setAlignment(Pos.CENTER);
|
||||
StackPane.setMargin(gui, new Insets(10));
|
||||
|
||||
saver = new FileChooser();
|
||||
saver.setInitialDirectory(new File("output"));
|
||||
saver.setInitialFileName("myMap.jpg");
|
||||
saver.setTitle("Save Image");
|
||||
saver.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("JPG", "*.jpg"),
|
||||
new FileChooser.ExtensionFilter("PNG", "*.png"));
|
||||
return gui;
|
||||
}
|
||||
|
||||
|
||||
private Node buildTextDisplay() {
|
||||
this.avgSizeDistort = new Text("...");
|
||||
this.avgShapeDistort = new Text("...");
|
||||
final Text txt = new Text("Blue areas are dilated, red areas are compressed, and black areas are stretched.");
|
||||
txt.setWrappingWidth(GUI_WIDTH);
|
||||
|
||||
saveMap = new Button("Save Image...");
|
||||
saveMap.setOnAction(new EventHandler<ActionEvent>() {
|
||||
public void handle(ActionEvent event) {
|
||||
startFinalizingMap();
|
||||
}
|
||||
});
|
||||
saveMap.setTooltip(new Tooltip("Save the distortion graphic."));
|
||||
stage.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() { //ctrl-S saves
|
||||
public void handle(KeyEvent event) {
|
||||
if (ctrlS.match(event)) saveMap.fire();
|
||||
}
|
||||
});
|
||||
|
||||
saveCharts = new Button("Save Chart...");
|
||||
saveCharts.setOnAction(new EventHandler<ActionEvent>() {
|
||||
public void handle(ActionEvent event) {
|
||||
saveImage(charts.snapshot(null, null), null);
|
||||
}
|
||||
});
|
||||
|
||||
HBox box = new HBox(5, calculate, saveMap, saveCharts);
|
||||
box.setAlignment(Pos.CENTER);
|
||||
layout.getChildren().add(box);
|
||||
|
||||
layout.getChildren().add(new Separator());
|
||||
|
||||
avgSizeDistort = new Label("...");
|
||||
avgShapeDistort = new Label("...");
|
||||
lbl = new Label("Blue areas are dilated, red areas are compressed, and black areas are stretched.");
|
||||
lbl.setWrapText(true);
|
||||
|
||||
VBox bxo = new VBox(3,
|
||||
VBox box = new VBox(3,
|
||||
new HBox(new Label("Average size distortion: "),avgSizeDistort),
|
||||
new HBox(new Label("Average shape distortion: "),avgShapeDistort),
|
||||
lbl);
|
||||
bxo.setAlignment(Pos.CENTER_LEFT);
|
||||
layout.getChildren().add(bxo);
|
||||
|
||||
output = new ImageView();
|
||||
output.setFitWidth(IMG_WIDTH);
|
||||
output.setFitHeight(IMG_WIDTH);
|
||||
output.setPreserveRatio(true);
|
||||
|
||||
sizeChart = new BarChart<String, Number>(new CategoryAxis(), new NumberAxis());
|
||||
sizeChart.setPrefWidth(CHART_WIDTH);
|
||||
sizeChart.setPrefHeight(IMG_WIDTH/2);
|
||||
sizeChart.getXAxis().setLabel("Scale factor");
|
||||
sizeChart.setBarGap(0);
|
||||
sizeChart.setCategoryGap(0);
|
||||
sizeChart.setAnimated(false);
|
||||
sizeChart.setLegendVisible(false);
|
||||
|
||||
shapeChart = new BarChart<String, Number>(new CategoryAxis(), new NumberAxis());
|
||||
shapeChart.setPrefWidth(CHART_WIDTH);
|
||||
shapeChart.setPrefHeight(IMG_WIDTH/2);
|
||||
shapeChart.getXAxis().setLabel("Stretch factor");
|
||||
shapeChart.setBarGap(0);
|
||||
shapeChart.setCategoryGap(0);
|
||||
shapeChart.setAnimated(false);
|
||||
shapeChart.setLegendVisible(false);
|
||||
|
||||
charts = new VBox(sizeChart, shapeChart);
|
||||
|
||||
final HBox gui = new HBox(layout, output, charts);
|
||||
|
||||
new Thread(() -> {
|
||||
calculate.fire();
|
||||
}).start();
|
||||
|
||||
gui.setAlignment(Pos.CENTER);
|
||||
gui.setSpacing(10);
|
||||
StackPane.setMargin(gui, new Insets(10));
|
||||
stage.setScene(new Scene(new StackPane(gui)));
|
||||
stage.show();
|
||||
txt);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
return box;
|
||||
}
|
||||
|
||||
|
||||
private void calculateMap() {
|
||||
calculate.setDisable(true);
|
||||
new Thread(new Task<Void>() {
|
||||
protected Void call() {
|
||||
try {
|
||||
Platform.runLater(() -> {
|
||||
sizeChart.getData().clear();
|
||||
shapeChart.getData().clear();
|
||||
|
||||
avgSizeDistort.setText("...");
|
||||
avgShapeDistort.setText("...");
|
||||
});
|
||||
|
||||
final Projection proj = projectionChooser.getValue();
|
||||
final double[] params = proj.getDefaultParameters(); //TODO: get real parameters
|
||||
final double[][][] distortionM =
|
||||
proj.calculateDistortion(proj.map(250,params), params);
|
||||
|
||||
output.setImage(makeGraphic(distortionM));
|
||||
|
||||
final double[][][] distortionG =
|
||||
proj.calculateDistortion(Projection.globe(0.02), params);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
sizeChart.getData().add(histogram(distortionG[0],
|
||||
-2,2,14, true));
|
||||
shapeChart.getData().add(histogram(distortionG[1],
|
||||
0,1.6,14, false));
|
||||
|
||||
avgSizeDistort.setText(format(Math2.stdDev(distortionG[0])));
|
||||
avgShapeDistort.setText(format(Math2.mean(distortionG[1])));
|
||||
});
|
||||
calculate.setDisable(false);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
private Region buildDistortionHistograms() {
|
||||
this.sizeChart = new BarChart<String, Number>(new CategoryAxis(), new NumberAxis());
|
||||
this.sizeChart.setPrefWidth(CHART_WIDTH);
|
||||
this.sizeChart.setPrefHeight(IMG_WIDTH/2);
|
||||
this.sizeChart.getXAxis().setLabel("Scale factor");
|
||||
this.sizeChart.setBarGap(0);
|
||||
this.sizeChart.setCategoryGap(0);
|
||||
this.sizeChart.setAnimated(false);
|
||||
this.sizeChart.setLegendVisible(false);
|
||||
|
||||
this.shapeChart = new BarChart<String, Number>(new CategoryAxis(), new NumberAxis());
|
||||
this.shapeChart.setPrefWidth(CHART_WIDTH);
|
||||
this.shapeChart.setPrefHeight(IMG_WIDTH/2);
|
||||
this.shapeChart.getXAxis().setLabel("Stretch factor");
|
||||
this.shapeChart.setBarGap(0);
|
||||
this.shapeChart.setCategoryGap(0);
|
||||
this.shapeChart.setAnimated(false);
|
||||
this.shapeChart.setLegendVisible(false);
|
||||
|
||||
return new VBox(5, sizeChart, shapeChart);
|
||||
}
|
||||
|
||||
|
||||
private void startFinalizingMap() {
|
||||
final Projection p = projectionChooser.getValue();
|
||||
final double[] params = p.getDefaultParameters(); //TODO: get real parameters
|
||||
private void calculateAndUpdate() {
|
||||
Platform.runLater(() -> {
|
||||
sizeChart.getData().clear();
|
||||
shapeChart.getData().clear();
|
||||
|
||||
avgSizeDistort.setText("...");
|
||||
avgShapeDistort.setText("...");
|
||||
});
|
||||
|
||||
ProgressBarDialog pBar = new ProgressBarDialog();
|
||||
pBar.show();
|
||||
new Thread(() -> {
|
||||
final double[][][] distortion = p.calculateDistortion(
|
||||
p.map(1000,params), params, pBar);
|
||||
Image graphic = makeGraphic(distortion);
|
||||
Platform.runLater(() -> saveImage(graphic, pBar));
|
||||
}).start();
|
||||
final Projection proj = this.getProjection();
|
||||
final double[] params = this.getParams();
|
||||
final double[][][] distortionM = proj.calculateDistortion(
|
||||
proj.map(ROUGH_SAMP_NUM,params), params);
|
||||
|
||||
mapDisplay.setImage(makeGraphic(distortionM));
|
||||
|
||||
final double[][][] distortionG = proj.calculateDistortion(
|
||||
Projection.globe(GLOBE_RES), params);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
sizeChart.getData().add(histogram(distortionG[0],
|
||||
-2, 2, 14, Math::exp));
|
||||
shapeChart.getData().add(histogram(distortionG[1],
|
||||
0, 2.8, 14, (x) -> 1/(x*x/2+1-x*Math.sqrt(x*x/4+1))));
|
||||
|
||||
avgSizeDistort.setText(format(Math2.stdDev(distortionG[0])));
|
||||
avgShapeDistort.setText(format(Math2.mean(distortionG[1])));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void calculateAndSavePlot(File file, ProgressBarDialog pBar) {
|
||||
pBar.setProgress(-1);
|
||||
final String filename = file.getName();
|
||||
final String extension = filename.substring(filename.lastIndexOf('.')+1);
|
||||
try {
|
||||
final WritableImage out = new WritableImage(
|
||||
(int) charts.getWidth(), (int) charts.getHeight());
|
||||
Platform.runLater(() -> charts.snapshot(null,out));
|
||||
while (out.getProgress() < 1) {}
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(out, null), extension, file); //save
|
||||
} catch (IOException e) {
|
||||
final Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setHeaderText("Failure!");
|
||||
alert.setContentText("Could not access "+file.getAbsolutePath()+". It's possible that another program has it open.");
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void calculateAndSaveMap(File file, ProgressBarDialog pBar) {
|
||||
final Projection proj = this.getProjection();
|
||||
final double[] params = this.getParams();
|
||||
final double[][][] distortion = proj.calculateDistortion(
|
||||
proj.map(FINE_SAMP_NUM,params), params, pBar); //calculate
|
||||
Image graphic = makeGraphic(distortion);
|
||||
|
||||
pBar.setProgress(-1);
|
||||
|
||||
final String filename = file.getName();
|
||||
final String extension = filename.substring(filename.lastIndexOf('.')+1);
|
||||
try {
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(graphic,null), extension, file); //save
|
||||
} catch (IOException e) {
|
||||
final Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setHeaderText("Failure!");
|
||||
alert.setContentText("Could not access "+file.getAbsolutePath()+". It's possible that another program has it open.");
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -317,25 +293,8 @@ public class MapAnalyzer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private void saveImage(Image img, ProgressBarDialog pBar) { // call from the main thread!
|
||||
if (pBar != null)
|
||||
pBar.close();
|
||||
|
||||
final File f = saver.showSaveDialog(stage);
|
||||
if (f != null) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
saveMap.setDisable(true);
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(img,null), "png", f);
|
||||
saveMap.setDisable(false);
|
||||
} catch (IOException e) {}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final Series<String, Number> histogram(double[][] values,
|
||||
double min, double max, int num, boolean logarithmic) {
|
||||
double min, double max, int num, DoubleUnaryOperator labeler) {
|
||||
int[] hist = new int[num+1]; //this array is the histogram values for min, min+dx, ..., max-dx, max
|
||||
int tot = 0;
|
||||
for (double[] row: values) {
|
||||
@ -350,9 +309,7 @@ public class MapAnalyzer extends Application {
|
||||
}
|
||||
Series<String, Number> output = new Series<String, Number>();
|
||||
for (int i = 0; i <= num; i ++) {
|
||||
double x = i*(max-min)/num+min;
|
||||
if (logarithmic) x = Math.exp(x);
|
||||
else x = 1/(1-Math.sin(x)); //this is a bit nonsensical and sketch. Don't worry about it.
|
||||
double x = labeler.applyAsDouble(i*(max-min)/num+min);
|
||||
output.getData().add(new Data<String, Number>(
|
||||
Double.toString(Math.round(100*x)/100.),
|
||||
(double)hist[i]/tot*100));
|
||||
|
||||
@ -54,7 +54,7 @@ import util.Procedure;
|
||||
*/
|
||||
public abstract class MapApplication extends Application {
|
||||
|
||||
protected static final int CONT_WIDTH = 350;
|
||||
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);
|
||||
@ -156,7 +156,7 @@ public abstract class MapApplication extends Application {
|
||||
projectionChooser.setValue(defProj);
|
||||
|
||||
final Text description = new Text(projectionChooser.getValue().getDescription());
|
||||
description.setWrappingWidth(CONT_WIDTH);
|
||||
description.setWrappingWidth(GUI_WIDTH);
|
||||
|
||||
projectionChooser.setOnAction(new EventHandler<ActionEvent>() {
|
||||
public void handle(ActionEvent event) {
|
||||
@ -399,7 +399,9 @@ public abstract class MapApplication extends Application {
|
||||
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");
|
||||
final Tooltip tt = new Tooltip(
|
||||
"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]);
|
||||
|
||||
@ -29,7 +29,6 @@ import javax.imageio.ImageIO;
|
||||
|
||||
import dialogs.MapConfigurationDialog;
|
||||
import dialogs.ProgressBarDialog;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@ -118,16 +117,15 @@ public class MapDesignerRaster extends MapApplication {
|
||||
new Separator(), aspectSelector, parameterSelector,
|
||||
new Separator(), buttons);
|
||||
layout.setAlignment(Pos.CENTER);
|
||||
layout.setPrefWidth(CONT_WIDTH);
|
||||
layout.setPrefWidth(GUI_WIDTH);
|
||||
|
||||
this.display = new ImageView();
|
||||
this.display.setFitWidth(IMG_WIDTH);
|
||||
this.display.setFitHeight(IMG_WIDTH);
|
||||
this.display.setPreserveRatio(true);
|
||||
|
||||
final HBox gui = new HBox(layout, this.display);
|
||||
final HBox gui = new HBox(10, layout, this.display);
|
||||
gui.setAlignment(Pos.CENTER);
|
||||
gui.setSpacing(10);
|
||||
StackPane.setMargin(gui, new Insets(10));
|
||||
|
||||
return gui;
|
||||
@ -167,10 +165,9 @@ public class MapDesignerRaster extends MapApplication {
|
||||
protected void calculateAndSaveMap(File file, ProgressBarDialog pBar) {
|
||||
Image theMap = map(
|
||||
configDialog.getDims(), configDialog.getSmoothing(), pBar); //calculate
|
||||
Platform.runLater(() -> pBar.setProgress(-1));
|
||||
pBar.setProgress(-1);
|
||||
final String filename = file.getName();
|
||||
final String extension;
|
||||
extension = filename.substring(filename.lastIndexOf('.')+1);
|
||||
final String extension = filename.substring(filename.lastIndexOf('.')+1);
|
||||
try {
|
||||
ImageIO.write(SwingFXUtils.fromFXImage(theMap,null), extension, file); //save
|
||||
} catch (IOException e) {
|
||||
|
||||
@ -117,13 +117,12 @@ public class MapDesignerVector extends MapApplication {
|
||||
new Separator(), saveBtn);
|
||||
|
||||
layout.setAlignment(Pos.CENTER);
|
||||
layout.setPrefWidth(CONT_WIDTH);
|
||||
layout.setPrefWidth(GUI_WIDTH);
|
||||
|
||||
viewer = new Canvas(IMG_WIDTH, IMG_WIDTH);
|
||||
|
||||
final HBox gui = new HBox(layout, viewer);
|
||||
final HBox gui = new HBox(10, layout, viewer);
|
||||
gui.setAlignment(Pos.CENTER);
|
||||
gui.setSpacing(10);
|
||||
StackPane.setMargin(gui, new Insets(10));
|
||||
|
||||
return gui;
|
||||
|
||||
@ -45,8 +45,7 @@ import maps.Projection;
|
||||
* @author Justin Kunimune
|
||||
*/
|
||||
public class MapOptimizer extends Application {
|
||||
|
||||
|
||||
|
||||
private static final Projection[] EXISTING_PROJECTIONS = { Projection.HOBO_DYER, Projection.ROBINSON,
|
||||
Projection.PLATE_CARREE, Projection.LEE };
|
||||
private static final double[] WEIGHTS = { .083, .20, .33, .50, .71, 1.0, 1.4, 2.0, 3.0, 5.0, 12. };
|
||||
@ -73,10 +72,10 @@ public class MapOptimizer extends Application {
|
||||
PrintStream log = new PrintStream(new File("output/parameters.txt"));
|
||||
|
||||
chart.getData().add(analyzeAll(globe, EXISTING_PROJECTIONS));
|
||||
chart.getData().add(optimizeHyperelliptical(globe, log));
|
||||
// chart.getData().add(optimizeEACylinder(globe, log));
|
||||
chart.getData().add(optimizeTetrapower(globe, log));
|
||||
chart.getData().add(optimizeTetrafillet(globe, log));
|
||||
chart.getData().add(optimizeFamily(Projection.HYPERELLIPOWER, globe, log));
|
||||
chart.getData().add(optimizeFamily(Projection.WINKEL_TRIPEL, globe, log));
|
||||
chart.getData().add(optimizeFamily(Projection.TETRAPOWER, globe, log));
|
||||
chart.getData().add(optimizeFamily(Projection.TETRAFILLET, globe, log));
|
||||
|
||||
System.out.println("Total time elapsed: "+
|
||||
(System.currentTimeMillis()-startTime)/1000.+"s");
|
||||
@ -108,18 +107,19 @@ public class MapOptimizer extends Application {
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeFamily(
|
||||
Projection projectionFam, double[][] bounds, double[][][] points, PrintStream log) { // optimize and plot some maps of a given family maps
|
||||
System.out.println("Optimizing "+projectionFam.getName());
|
||||
final double[][] currentBest = new double[WEIGHTS.length][3+bounds.length]; //the 0-3 cols are the min distortions for each weight, the other cols are the values of k and n that caused that
|
||||
Projection proj, double[][][] points, PrintStream log) { // optimize and plot some maps of a given family maps
|
||||
System.out.println("Optimizing "+proj.getName());
|
||||
final double[][] currentBest = new double[WEIGHTS.length][3+proj.getNumParameters()]; //the 0-3 cols are the min distortions for each weight, the other cols are the values of k and n that caused that
|
||||
for (int k = 0; k < WEIGHTS.length; k ++)
|
||||
currentBest[k][0] = Integer.MAX_VALUE;
|
||||
|
||||
final double[] params = new double[bounds.length];
|
||||
for (int i = 0; i < params.length; i ++) params[i] = bounds[i][0]; //initialize params
|
||||
final double[][] bounds = proj.getParameterValues();
|
||||
final double[] params = new double[proj.getNumParameters()];
|
||||
for (int i = 0; i < params.length; i ++) params[i] = bounds[i][0]; //initialize params
|
||||
|
||||
while (true) { //start with brute force
|
||||
System.out.println(Arrays.toString(params));
|
||||
double[] distortions = projectionFam.avgDistortion(points, params);
|
||||
double[] distortions = proj.avgDistortion(points, params);
|
||||
for (int k = 0; k < WEIGHTS.length; k ++) {
|
||||
final double avgDist = Math.pow(distortions[0],1.5) +
|
||||
WEIGHTS[k]*Math.pow(distortions[1],1.5);
|
||||
@ -153,26 +153,26 @@ public class MapOptimizer extends Application {
|
||||
for (int i = 0; i < NUM_DESCENT; i ++) {
|
||||
if (i > 0)
|
||||
fr0 = weighDistortion(WEIGHTS[k],
|
||||
projectionFam.avgDistortion(points, params)); //calculate the distortion here
|
||||
proj.avgDistortion(points, params)); //calculate the distortion here
|
||||
System.out.println(Arrays.toString(params)+" -> "+fr0);
|
||||
for (int j = 0; j < params.length; j ++) {
|
||||
params[j] += h;
|
||||
frd[j] = weighDistortion(WEIGHTS[k],
|
||||
projectionFam.avgDistortion(points, params)); //and the distortion nearby
|
||||
proj.avgDistortion(points, params)); //and the distortion nearby
|
||||
params[j] -= h;
|
||||
}
|
||||
for (int j = 0; j < params.length; j ++)
|
||||
params[j] -= (frd[j]-fr0)/h*delX; //use that to approximate the gradient and go in that direction
|
||||
}
|
||||
System.arraycopy(params,0, currentBest[k],3, params.length);
|
||||
System.arraycopy(projectionFam.avgDistortion(points, params), 0,
|
||||
System.arraycopy(proj.avgDistortion(points, params), 0,
|
||||
currentBest[k], 1, 2);
|
||||
}
|
||||
|
||||
final Series<Number, Number> output = new Series<Number, Number>();
|
||||
output.setName(projectionFam.getName());
|
||||
output.setName(proj.getName());
|
||||
|
||||
log.println("We got the best "+projectionFam.getName()+" projections using:");
|
||||
log.println("We got the best "+proj.getName()+" projections using:");
|
||||
for (double[] best: currentBest) {
|
||||
log.print("\t");
|
||||
for (int i = 0; i < params.length; i ++)
|
||||
@ -191,27 +191,6 @@ public class MapOptimizer extends Application {
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeHyperelliptical(
|
||||
double[][][] points, PrintStream log) { //optimize and plot some hyperelliptical maps
|
||||
return optimizeFamily(Projection.HYPERELLIPOWER,
|
||||
new double[][] {{2.5,5}, {0.5,1.75}, {1.0,2.0}}, points, log);
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeTetrapower(
|
||||
double[][][] points, PrintStream log) { //optimize and plot some hyperelliptical maps
|
||||
return optimizeFamily(Projection.TETRAPOWER,
|
||||
new double[][] {{0.25,2.25}, {0.25,2.25}, {.25,2.25}}, points, log);
|
||||
}
|
||||
|
||||
|
||||
private static Series<Number, Number> optimizeTetrafillet(
|
||||
double[][][] points, PrintStream log) { //optimize and plot some hyperelliptical maps
|
||||
return optimizeFamily(Projection.TETRAFILLET,
|
||||
new double[][] {{0.25,2.25}, {0.25,2.25}, {.25,2.25}}, points, log);
|
||||
}
|
||||
|
||||
|
||||
private static Data<Number, Number> plotDistortion(
|
||||
double[][][] pts, Projection proj, double[] params) {
|
||||
double[] distortion = proj.avgDistortion(pts, params);
|
||||
|
||||
@ -83,10 +83,13 @@ public class ProgressBarDialog extends Dialog<Void> {
|
||||
Platform.runLater(() -> {
|
||||
if (p >= 0)
|
||||
this.words.setText((Math.round(1000*p)/10.0)+"%");
|
||||
else
|
||||
else {
|
||||
this.bar.setProgress(-1);
|
||||
this.words.setText("...");
|
||||
}
|
||||
});
|
||||
this.bar.setProgress(p);
|
||||
if (p >= 0)
|
||||
this.bar.setProgress(p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -108,17 +108,18 @@ public enum Projection {
|
||||
}
|
||||
},
|
||||
|
||||
EA_CYLIND("Equal-area cylindrical", "A generalized equal-area cylindrical projection",
|
||||
E_A_CYLIND("Equal-area cylindrical", "A generalized equal-area cylindrical projection",
|
||||
Math.PI, 0b1111, "cylindrical", "equal-area",
|
||||
new String[]{"Std. parallel"}, new double[][]{{0, 90, 37.5}}) {
|
||||
new String[]{"Std. parallel"}, new double[][]{{0, 75, 37.5}}) {
|
||||
public double[] project(double lat, double lon, double[] params) {
|
||||
return new double[] {lon, Math.sin(lat)/params[0]};
|
||||
final double a = Math.pow(Math.cos(Math.toRadians(params[0])), 2);
|
||||
return new double[] {lon, Math.sin(lat)/a};
|
||||
}
|
||||
public double[] inverse(double x, double y, double[] params) {
|
||||
return new double[] { Math.asin(y), x*Math.PI };
|
||||
}
|
||||
public double getAspectRatio(double[] params) {
|
||||
return 1/Math.cos(params[0]);
|
||||
return Math.PI*Math.pow(Math.cos(Math.toRadians(params[0])), 2);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -119,6 +119,7 @@ public class NumericalAnalysis {
|
||||
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
public interface VectorFunction {
|
||||
public double evaluate(double x, double y, double[] constants);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user