Map-Projections/src/apps/MapDesignerRaster.java
Justin Kunimune 0082af3ff5 I underestimated the pointiness of triangles
As it turns out, they are quite pointy. And creating an authagraphic
tetrahedral projection that has to shove landmasses into the pointy
corners doesn't work very well. Oh, well. I did some fun calculus to
make it work. I don't know how AuthaGraph does it. Probably by not being
authagraphic and lying to its followers. I'll probably make something
interrupted now.
2017-11-21 19:23:11 -05:00

251 lines
8.4 KiB
Java

/**
* 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 apps;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
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;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Separator;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import maps.Azimuthal;
import maps.Conic;
import maps.Cylindrical;
import maps.Misc;
import maps.MyProjections;
import maps.Projection;
import maps.Pseudocylindrical;
import maps.Robinson;
import maps.Tetrahedral;
import maps.Tobler;
import maps.WinkelTripel;
import utils.ImageUtils;
import utils.PixelMap;
import utils.Procedure;
/**
* An application to make raster oblique aspects of map projections
*
* @author Justin Kunimune
*/
public class MapDesignerRaster extends MapApplication {
public static final void main(String[] args) {
launch(args);
}
private static final FileChooser.ExtensionFilter[] READABLE_TYPES = {
new FileChooser.ExtensionFilter("All image types", "*.png","*.jpg","*.jpeg","*.jpe","*.jfif","*.gif"),
new FileChooser.ExtensionFilter("PNG", "*.png"),
new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif"),
new FileChooser.ExtensionFilter("GIF", "*.gif") };
private static final FileChooser.ExtensionFilter[] RASTER_TYPES = {
new FileChooser.ExtensionFilter("PNG", "*.png"),
new FileChooser.ExtensionFilter("JPG", "*.jpg"),
new FileChooser.ExtensionFilter("GIF", "*.gif") };
private static final Projection[] PROJ_ARR = { Cylindrical.MERCATOR,
Cylindrical.EQUIRECTANGULAR, Cylindrical.EQUAL_AREA, Cylindrical.GALL,
Azimuthal.STEREOGRAPHIC, Azimuthal.POLAR, Azimuthal.EQUAL_AREA, Azimuthal.GNOMONIC,
Azimuthal.PERSPECTIVE, Conic.LAMBERT, Conic.EQUIDISTANT, Conic.ALBERS, Tetrahedral.LEE,
Tetrahedral.TETRAGRAPH, Tetrahedral.ACTAAUTHAGRAPH, Pseudocylindrical.SINUSOIDAL,
Pseudocylindrical.MOLLWEIDE, Tobler.TOBLER, Misc.AITOFF, Misc.VAN_DER_GRINTEN,
Robinson.ROBINSON, WinkelTripel.WINKEL_TRIPEL, Misc.PEIRCE_QUINCUNCIAL,
Misc.TWO_POINT_EQUIDISTANT, Misc.HAMMER_RETROAZIMUTHAL, Pseudocylindrical.LEMONS,
MyProjections.EXPERIMENT, MyProjections.PSEUDOSTEREOGRAPHIC,
MyProjections.HYPERELLIPOWER, Tetrahedral.TETRAPOWER, Tetrahedral.TETRAFILLET,
MyProjections.TWO_POINT_EQUALIZED };
private Node aspectSelector;
private Button updateBtn, saveMapBtn;
private double[] aspect;
private PixelMap input;
private ImageView display;
private MapConfigurationDialog configDialog;
public MapDesignerRaster() {
super("Map Designer");
}
@Override
public void start(Stage root) {
super.start(root);
new Thread(() -> {
setInput(new File("input/Basic.jpg")); //TODO: this should cause the buttons to grey out
updateMap();
}).start();
}
@Override
protected Node makeWidgets() {
this.aspect = new double[3];
final Node inputSelector = buildInputSelector(READABLE_TYPES,
RASTER_TYPES[0], this::setInput);
final Node projectionSelector = buildProjectionSelector(PROJ_ARR,
this::hideAspect);
this.aspectSelector = buildAspectSelector(this.aspect, Procedure.NONE);
final Node parameterSelector = buildParameterSelector(Procedure.NONE);
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);
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);
layout.setAlignment(Pos.CENTER);
layout.setPrefWidth(GUI_WIDTH);
this.display = new ImageView();
this.display.setFitWidth(IMG_WIDTH);
this.display.setFitHeight(IMG_WIDTH);
this.display.setPreserveRatio(true);
final StackPane pane = new StackPane(display);
pane.setMinWidth(IMG_WIDTH);
pane.setMinHeight(IMG_WIDTH);
final HBox gui = new HBox(10, layout, pane);
gui.setAlignment(Pos.CENTER);
StackPane.setMargin(gui, new Insets(10));
return gui;
}
private void setInput(File file) {
updateBtn.setDisable(true);
saveMapBtn.setDisable(true);
try {
input = new PixelMap(file);
} catch (IOException e) {
final Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("File not found!");
alert.setContentText("Couldn't find "+file.getAbsolutePath()+".");
Platform.runLater(alert::showAndWait);
} finally {
updateBtn.setDisable(false);
saveMapBtn.setDisable(false);
}
}
private void hideAspect() {
aspectSelector.setVisible(this.getProjection().hasAspect());
}
private void updateMap() {
loadParameters();
display.setImage(SwingFXUtils.toFXImage(makeImage(), null));
}
private boolean collectFinalSettings() {
loadParameters();
final double ratio = getProjection().getAspectRatio();
this.configDialog = new MapConfigurationDialog(ratio);
this.configDialog.showAndWait();
return this.configDialog.getResult();
}
private void calculateAndSaveMap(File file, ProgressBarDialog pBar) {
final int[] outDims = configDialog.getDims();
final int smoothing = configDialog.getSmoothing();
BufferedImage theMap = makeImage(outDims[0], outDims[1], smoothing, pBar); //calculate
pBar.setProgress(-1);
final String filename = file.getName();
final String extension = filename.substring(filename.lastIndexOf('.')+1);
try {
ImageIO.write(theMap, extension, file); //save
} catch (IOException e) {
showError("Failure!",
"Could not access "+file.getAbsolutePath()+". It's possible that another program has it open.");
}
}
private BufferedImage makeImage() {
final double aspRat = this.getProjection().getAspectRatio();
if (aspRat >= 1)
return makeImage(IMG_WIDTH, (int)Math.max(IMG_WIDTH/aspRat,1), 1, null);
else
return makeImage((int)Math.max(IMG_WIDTH*aspRat,1), IMG_WIDTH, 1, null);
}
private BufferedImage makeImage(int width, int height, int step, ProgressBarDialog pBar) {
final double[] pole = aspect.clone();
final Projection pjc = this.getProjection();
final 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());
for (int x = 0; x < out.getWidth(); x ++) {
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);
if (coords != null) //if it is null, the default (0:transparent) is used
colors[step*dy+dx] = input.getArgb(coords[0], coords[1]);
}
}
out.setRGB(x, y, ImageUtils.blend(colors));
}
}
return out;
}
}