Map-Projections/src/apps/MapDesignerRaster.java
jkunimune e9e9721598 Whew!
That was... a lot. I decided to put some inheritance into the project,
and what a rabbit hole that turned out to be!
Huh. I think that may be the first time I've ever actually used an
"exclamatory statement". What a useless structure that is!
Anyway, both MapDesigners now inherit from the same abstract super
class. Eventually, I'll get MapAnalyzer to link to that, too. I also
changed a whole lot of other things, and started inserting parametric
map capacities. That should be a fun GUI thing to figure out. I also
changed a lot of other things that I can't remember off the top of my
head. I've just been dragging chunks of code around and swimming in
errors for the past week.
2017-07-01 20:55:46 -04:00

263 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.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.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.HOBO_DYER,
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 };
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, this::setInput);
final Node projectionSelector = buildProjectionSelector(PROJ_ARR, Projection.MERCATOR, Procedure.NONE);
final Node aspectSelector = buildAspectSelector(this.aspect, Procedure.NONE);
this.updateBtn = buildUpdateButton(this::updateMap);
this.saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES,
this::collectFinalParameters, 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);
layout.setAlignment(Pos.CENTER);
layout.setPrefWidth(CONT_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);
gui.setAlignment(Pos.CENTER);
gui.setSpacing(10);
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(map());
}
protected void collectFinalParameters() {
final double ratio = getProjection().getAspectRatio(getProjection().getDefaultParameters()); //TODO: get real parameters
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...");
try {
ImageIO.write(SwingFXUtils.fromFXImage(theMap,null), "jpg", 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();
}
}
public Image map() {
final double a = this.getProjection().getAspectRatio(getProjection().getDefaultParameters()); //TODO: get real parameters
return map(new int[] { IMG_WIDTH, (int)(IMG_WIDTH/a) }, 1);
}
public Image map(int[] outputDims, int smoothing) {
return map(outputDims,smoothing, null);
}
public Image map(int[] outDims, int smoothing,
ProgressBarDialog pbar) {
final Projection proj = this.getProjection();
final double[] params = proj.getDefaultParameters(); //TODO: get real parameters
final PixelReader ref = input.getPixelReader();
final int[] refDims = {(int)input.getWidth(), (int)input.getHeight()};
WritableImage img = new WritableImage(outDims[0], outDims[1]);
for (int x = 0; x < outDims[0]; x ++) {
for (int y = 0; y < outDims[1]; y ++) {
int[] colors = new int[smoothing*smoothing];
int i = 0;
for (double dx = 0.5/smoothing; dx < 1; dx += 1.0/smoothing) {
for (double dy = .5/smoothing; dy < 1; dy += 1.0/smoothing) {
colors[i] = getArgb(x+dx, y+dy,
proj,params,aspect,ref,refDims,outDims);
i ++;
}
}
img.getPixelWriter().setArgb(x, y, blend(colors));
}
if (pbar != null) pbar.setProgress((double)(x+1)/outDims[0]);
}
return img;
}
public static int getArgb(double x, double y, Projection proj, double[] params, double[] pole,
PixelReader ref, int[] refDims, int[] outDims) {
final double[] coords = proj.inverse(
2.*x/outDims[0]-1, 1-2.*y/outDims[1], params, pole);
if (coords != null)
return getColorAt(coords, ref, refDims);
else
return 0;
}
public static int getColorAt(double[] coords, PixelReader ref, int[] refDims) { // returns the color of any coordinate on earth
double x = 1/2.0 + coords[1]/(2*Math.PI);
x = (x - Math.floor(x)) * refDims[0];
double y = refDims[1]/2.0 - coords[0]*refDims[1]/(Math.PI);
if (y < 0)
y = 0;
else if (y >= refDims[1])
y = refDims[1] - 1;
return ref.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);
}
}