Map-Projections/src/apps/MapDesignerRaster.java
Justin Kunimune ab155f2c9e The Complete Set
I made inverse solutions for all my invented projections, and even threw
in a new one, "Tetrachamfer", which kind of sucks, but I felt like I
should include it for completeness's sake.  I found a bug in
MapConfigurationDialog and squashed it, as well. And I added a couple
new maps to the output folder. Oh, I never explained what the problem
with Tobler was! There was an issue with the way Z was being generated,
so last commit, I cleaned that up and may have made it slightly slower,
but I don't really care given how well it works now and how much faster
it still is than Lee (seriously, what is the deal with that?). Did I do
anything else? Not really. Next step: conic projections!
2017-07-15 16:51:38 -04:00

250 lines
8.1 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.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import dialogs.MapConfigurationDialog;
import dialogs.ProgressBarDialog;
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.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.Separator;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritableImage;
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.Projection;
import util.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[] RASTER_TYPES = {
new FileChooser.ExtensionFilter("JPG", "*.jpg","*.jpeg","*.jpe","*.jfif"),
new FileChooser.ExtensionFilter("PNG", "*.png") };
private static final Projection[] PROJ_ARR = { Projection.MERCATOR, Projection.PLATE_CARREE, Projection.BEHRMANN,
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.AUTHAGRAPH, Projection.SINUSOIDAL,
Projection.MOLLWEIDE, Projection.TOBLER, Projection.AITOFF, Projection.VAN_DER_GRINTEN, Projection.ROBINSON,
Projection.WINKEL_TRIPEL, Projection.PEIRCE_QUINCUNCIAL, Projection.GUYOU, Projection.LEMONS,
Projection.MAGNIFIER, Projection.EXPERIMENT, Projection.HYPERELLIPOWER, Projection.TETRAPOWER, Projection.TETRAFILLET, Projection.TETRACHAMFER };
private Button updateBtn, saveMapBtn;
private double[] aspect;
private Image 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(RASTER_TYPES,
RASTER_TYPES[0], 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,
RASTER_TYPES[1], 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, 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);
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 Image(file.toURI().toString());
} catch (IllegalArgumentException e) {
final Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("File not found!");
alert.setContentText("Couldn't find "+file.getAbsolutePath()+".");
alert.showAndWait();
} finally {
updateBtn.setDisable(false);
saveMapBtn.setDisable(false);
}
}
private void updateMap() {
display.setImage(makeImage(
this.getProjection().map(IMG_WIDTH, this.getParams(), aspect)));
}
private boolean collectFinalSettings() {
final double ratio = getProjection().getAspectRatio(this.getParams());
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();
double[][][] points =this.getProjection().map(
outDims[0]*smoothing, outDims[1]*smoothing, this.getParams(), aspect, pBar::setProgress);
pBar.setProgress(-1);
Image theMap = makeImage(points, smoothing); //calculate
final String filename = file.getName();
final String extension = filename.substring(filename.lastIndexOf('.')+1);
try {
ImageIO.write(SwingFXUtils.fromFXImage(theMap,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 Image makeImage(double[][][] points) {
return makeImage(points, 1);
}
private Image makeImage(double[][][] points, int step) {
final PixelReader in = input.getPixelReader();
final WritableImage out = new WritableImage(points[0].length/step, points.length/step);
for (int y = 0; y < out.getHeight(); y ++) {
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 ++) {
colors[step*dy+dx] = getArgb(points[step*y+dy][step*x+dx], in, input.getWidth(), input.getHeight());
}
}
out.getPixelWriter().setArgb(x, y, blend(colors));
}
}
return out;
}
private static int getArgb(double[] coords, PixelReader in,
double inWidth, double inHeight) { // returns the color of any coordinate on earth
if (coords == null) return 0;
double x = 1/2.0 + coords[1]/(2*Math.PI);
x = (x - Math.floor(x)) * inWidth;
double y = inHeight/2.0 - coords[0]*inHeight/(Math.PI);
if (y < 0)
y = 0;
else if (y >= inHeight)
y = inHeight - 1;
return in.getArgb((int) x, (int) y);
}
private static final int blend(int[] colors) {
int a_tot = 0;
int r_tot = 0;
int g_tot = 0;
int b_tot = 0;
for (int argb: colors) {
double a = ((argb >> 24) & 0xFF);
a_tot += a;
r_tot += a*((argb >> 16) & 0xFF);
g_tot += a*((argb >> 8) & 0xFF);
b_tot += a*((argb >> 0) & 0xFF);
}
if (a_tot == 0) return 0;
else
return (a_tot/colors.length<<24) +
(r_tot/a_tot<<16) + (g_tot/a_tot<<8) + (b_tot/a_tot);
}
}