737 lines
24 KiB
Java
737 lines
24 KiB
Java
/*
|
|
* Author: Cedric Holzl - Mohamed Khadri
|
|
* Sciper: 257844 - 261203
|
|
*/
|
|
package ch.epfl.alpano.gui;
|
|
|
|
import static ch.epfl.alpano.Math2.sq;
|
|
import static ch.epfl.alpano.PanoramaComputer.NATURAL_VARIATION;
|
|
import static ch.epfl.alpano.summit.GazetteerParser.readSummitsFrom;
|
|
import static java.awt.Desktop.getDesktop;
|
|
import static java.lang.Math.abs;
|
|
import static java.lang.Math.atan;
|
|
import static java.lang.Math.toDegrees;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.EnumMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
import ch.epfl.alpano.Azimuth;
|
|
import ch.epfl.alpano.GeoPoint;
|
|
import ch.epfl.alpano.dem.CompositElevationProfile;
|
|
import ch.epfl.alpano.summit.Summit;
|
|
import javafx.application.Application;
|
|
import javafx.application.Platform;
|
|
import javafx.beans.binding.Bindings;
|
|
import javafx.beans.property.ObjectProperty;
|
|
import javafx.beans.property.SimpleObjectProperty;
|
|
import javafx.collections.FXCollections;
|
|
import javafx.embed.swing.SwingFXUtils;
|
|
import javafx.geometry.HPos;
|
|
import javafx.geometry.Insets;
|
|
import javafx.geometry.Pos;
|
|
import javafx.scene.Node;
|
|
import javafx.scene.Scene;
|
|
import javafx.scene.canvas.Canvas;
|
|
import javafx.scene.canvas.GraphicsContext;
|
|
import javafx.scene.control.Button;
|
|
import javafx.scene.control.ChoiceBox;
|
|
import javafx.scene.control.Label;
|
|
import javafx.scene.control.Menu;
|
|
import javafx.scene.control.MenuBar;
|
|
import javafx.scene.control.MenuItem;
|
|
import javafx.scene.control.RadioMenuItem;
|
|
import javafx.scene.control.ScrollPane;
|
|
import javafx.scene.control.TextArea;
|
|
import javafx.scene.control.TextField;
|
|
import javafx.scene.control.TextFormatter;
|
|
import javafx.scene.control.ToggleGroup;
|
|
import javafx.scene.image.ImageView;
|
|
import javafx.scene.input.MouseEvent;
|
|
import javafx.scene.layout.BorderPane;
|
|
import javafx.scene.layout.ColumnConstraints;
|
|
import javafx.scene.layout.GridPane;
|
|
import javafx.scene.layout.Pane;
|
|
import javafx.scene.layout.StackPane;
|
|
import javafx.scene.text.Text;
|
|
import javafx.stage.Stage;
|
|
import javafx.util.StringConverter;
|
|
|
|
/**
|
|
* Main class used for GUI Controls the program through events
|
|
*/
|
|
public class Alpano extends Application {
|
|
|
|
private static final String ALPANO_WINDOW_TITLE = "Alpano";
|
|
private static final PanoramaUserParameters DEFAULT_PARAMS = PredefinedPanoramas.JURA_ALPS.get();
|
|
private static final String SUMMIT_FILENAME = "alps.txt";
|
|
private static final List<Summit> SUMMIT_LIST;
|
|
private static final PanoramaComputerBean pcbean;
|
|
private static final PanoramaParametersBean ppbean;
|
|
private static final MapViewBean mvbean;
|
|
private static final CursorInfo cursor = new CursorInfo();
|
|
|
|
static final String REPAINT_NOTICE_TEXT = "CLICK HERE TO REPAINT PANORAMA";
|
|
static final String UPDATE_NOTICE_TEXT = "CLICK HERE TO UPDATE PANORAMA";
|
|
|
|
static {
|
|
List<Summit> slist;
|
|
try {
|
|
slist = readSummitsFrom(new File(SUMMIT_FILENAME));
|
|
} catch (IOException e) {
|
|
slist = Collections.emptyList();
|
|
// No summits found in file, empty list
|
|
}
|
|
SUMMIT_LIST = slist;
|
|
pcbean = new PanoramaComputerBean(SUMMIT_LIST);
|
|
ppbean = new PanoramaParametersBean(DEFAULT_PARAMS);
|
|
mvbean = new MapViewBean(pcbean);
|
|
}
|
|
|
|
/**
|
|
* Main Fuction ran at the start of the program
|
|
* @param args : program arguments
|
|
*/
|
|
public static void main(String[] args) {
|
|
Application.launch(args);
|
|
}
|
|
|
|
@Override
|
|
public void start(Stage primaryStage) throws Exception {
|
|
|
|
BorderPane root = new BorderPane();
|
|
root.setCenter(makePanoPane());
|
|
root.setBottom(makeParamsGrid());
|
|
root.setTop(makeMenu());
|
|
Scene scene = new Scene(root, 1100, 700);
|
|
|
|
primaryStage.setTitle(ALPANO_WINDOW_TITLE);
|
|
primaryStage.setScene(scene);
|
|
primaryStage.show();
|
|
primaryStage.setOnHidden(e -> {
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (InterruptedException e1) {
|
|
|
|
} finally {
|
|
Platform.exit();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Builds a stack pane containing the panogroup and all the notices
|
|
* @return a stack pane for the panorama group and notices
|
|
*/
|
|
private Node makePanoPane() {
|
|
|
|
ScrollPane panoScrollPane = new ScrollPane(makePanoGroup());
|
|
|
|
return new StackPane(panoScrollPane, makePainterNotice(),
|
|
makeDownloadNotice(), makeUpdateNotice());
|
|
}
|
|
|
|
|
|
/**
|
|
* Builds a grid of nodes and binds the input fields to the computable
|
|
* variables in PanoramaParameterBean
|
|
*
|
|
* @return node grid displaying the parameters
|
|
*/
|
|
private Node makeParamsGrid() {
|
|
GridPane grid = new GridPane();
|
|
|
|
Map<UserParameter, Label> labels = new EnumMap<UserParameter, Label>(
|
|
UserParameter.class);
|
|
Map<UserParameter, Node> fields = new EnumMap<UserParameter, Node>(
|
|
UserParameter.class);
|
|
|
|
int nbParams = 0;
|
|
for (UserParameter parameter : UserParameter.values()) {
|
|
Label label = new Label(parameter.label());
|
|
labels.put(parameter, label);
|
|
GridPane.setHalignment(label, HPos.RIGHT);
|
|
fields.put(parameter, makeField(parameter));
|
|
|
|
nbParams++;
|
|
if (nbParams % 3 == 0) {
|
|
int nbRow = nbParams / 3 - 1;
|
|
grid.addRow(nbRow,
|
|
labels.get(UserParameter.values()[3 * nbRow]),
|
|
fields.get(UserParameter.values()[3 * nbRow]),
|
|
labels.get(UserParameter.values()[3 * nbRow + 1]),
|
|
fields.get(UserParameter.values()[3 * nbRow + 1]),
|
|
labels.get(UserParameter.values()[3 * nbRow + 2]),
|
|
fields.get(UserParameter.values()[3 * nbRow + 2]));
|
|
}
|
|
}
|
|
grid.addColumn(6, makeCursorInfo());
|
|
ColumnConstraints colL = new ColumnConstraints();
|
|
colL.setPrefWidth(150);
|
|
ColumnConstraints colT = new ColumnConstraints();
|
|
colT.setPrefWidth(80);
|
|
ColumnConstraints colA = new ColumnConstraints();
|
|
colA.setPrefWidth(300);
|
|
grid.getColumnConstraints().setAll(colL, colT, colL, colT, colL, colT,
|
|
colA);
|
|
|
|
return grid;
|
|
}
|
|
|
|
/**
|
|
* Makes a field for the params grid
|
|
* @param parameter : parameter of the field
|
|
* @return the field
|
|
*/
|
|
private static Node makeField(UserParameter parameter) {
|
|
if (!parameter.equals(UserParameter.SUPER_SAMPLING_EXPONENT)) {
|
|
return makeTextField(parameter);
|
|
} else {
|
|
return makeSuperSamplingField(parameter);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Makes a text field for the param
|
|
* @param parameter : parameter to use
|
|
* @return a text field
|
|
*/
|
|
private static Node makeTextField(UserParameter parameter) {
|
|
TextField field = new TextField();
|
|
StringConverter<Integer> stringConverter = new FixedPointStringConverter(
|
|
parameter.scale());
|
|
TextFormatter<Integer> formatter = new TextFormatter<>(stringConverter);
|
|
field.setTextFormatter(formatter);
|
|
field.setAlignment(Pos.CENTER_RIGHT);
|
|
formatter.valueProperty()
|
|
.bindBidirectional(ppbean.getProperty(parameter));
|
|
return field;
|
|
}
|
|
|
|
/**
|
|
* Makes a Choice box for the supersampling parameter
|
|
* @param parameter : parameter
|
|
* @return a choice box field for the supersampling parameter
|
|
*/
|
|
private static Node makeSuperSamplingField(UserParameter parameter) {
|
|
ChoiceBox<Integer> field = new ChoiceBox<Integer>(
|
|
FXCollections.observableArrayList(0, 1, 2));
|
|
field.setConverter(new LabeledListStringConverter("none", "2x", "4x"));
|
|
field.valueProperty().bindBidirectional(ppbean.getProperty(parameter));
|
|
return field;
|
|
}
|
|
|
|
/**
|
|
* Makes a cursor info text area
|
|
* @return a text area holding the cursor info
|
|
*/
|
|
private static Node makeCursorInfo() {
|
|
TextArea cursorInfo = new TextArea();
|
|
cursorInfo.setWrapText(true);
|
|
cursorInfo.textProperty().bind(cursor.textProperty());
|
|
cursorInfo.setMinWidth(100);
|
|
cursorInfo.setPrefRowCount(2);
|
|
cursorInfo.setEditable(false);
|
|
GridPane.setMargin(cursorInfo, new Insets(0, 0, 0, 20));
|
|
GridPane.setRowSpan(cursorInfo, 3);
|
|
return cursorInfo;
|
|
}
|
|
|
|
/**
|
|
* Makes a pane holding the update notice
|
|
* @return a pane when clicked updates the pcbean
|
|
*/
|
|
private static Node makeUpdateNotice() {
|
|
Label updateText = new Label(UPDATE_NOTICE_TEXT);
|
|
StackPane updateNotice = new StackPane(updateText);
|
|
updateNotice.setOpacity(0.9);
|
|
updateNotice.setStyle("-fx-background-color: #FFFFFF;");
|
|
updateNotice.visibleProperty().bind(ppbean.ParametersProperty()
|
|
.isNotEqualTo(pcbean.parametersProperty()));
|
|
updateNotice.setOnMouseClicked((MouseEvent event) -> {
|
|
pcbean.setPainter(ppbean.painterProperty().get());
|
|
pcbean.setParameters(ppbean.ParametersProperty().get());
|
|
});
|
|
return updateNotice;
|
|
}
|
|
|
|
/**
|
|
* Makes a pane holding the repaint notice
|
|
* @return a pane when clicked repaints the panorama
|
|
*/
|
|
private static Node makePainterNotice() {
|
|
Label updateText = new Label(REPAINT_NOTICE_TEXT);
|
|
StackPane updateNotice = new StackPane(updateText);
|
|
updateNotice.setOpacity(0.9);
|
|
updateNotice.setStyle("-fx-background-color: #FFFFFF;");
|
|
updateNotice.visibleProperty()
|
|
.bind(ppbean.painterProperty()
|
|
.isNotEqualTo(pcbean.painterProperty())
|
|
.and(ppbean.ParametersProperty()
|
|
.isEqualTo(pcbean.parametersProperty())));
|
|
updateNotice.setOnMouseClicked((MouseEvent event) -> {
|
|
pcbean.setPainter(ppbean.painterProperty().get());
|
|
});
|
|
return updateNotice;
|
|
}
|
|
|
|
/**
|
|
* Makes a pane holding the download notice
|
|
* @return a pane dispkaying the MapzenManager download info
|
|
*/
|
|
private static Node makeDownloadNotice() {
|
|
Label dlText = new Label();
|
|
StackPane dlNotice = new StackPane(dlText);
|
|
dlNotice.setOpacity(0.9);
|
|
dlNotice.setStyle("-fx-background-color: #FFFFFF;");
|
|
dlNotice.visibleProperty().bind(pcbean.downloadProperty());
|
|
dlText.textProperty().bind(pcbean.downloadTextProperty());
|
|
dlText.setAlignment(Pos.CENTER);
|
|
return dlNotice;
|
|
}
|
|
|
|
/**
|
|
* Makes a ImageView holding the image of the panorama
|
|
* @return an ImageView with bound to the panorama image
|
|
*/
|
|
private static Node makePanoView() {
|
|
ImageView panoView = new ImageView();
|
|
panoView.imageProperty().bind(pcbean.imageProperty());
|
|
panoView.fitWidthProperty().bind(ppbean.WidthProperty());
|
|
panoView.setPreserveRatio(true);
|
|
panoView.setSmooth(true);
|
|
return panoView;
|
|
}
|
|
|
|
/**
|
|
* Makes a pane holding the labels of the panorama
|
|
* @return a pane holding the labels
|
|
*/
|
|
private static Node makeLabelsPane() {
|
|
Pane labelsPane = new Pane();
|
|
labelsPane.prefWidthProperty().bind(ppbean.WidthProperty());
|
|
labelsPane.prefHeightProperty().bind(ppbean.HeightProperty());
|
|
Bindings.bindContent(labelsPane.getChildren(), pcbean.getLabels());
|
|
return labelsPane;
|
|
}
|
|
|
|
/**
|
|
* Makes an ImageView holding the map image
|
|
* @return ImageView holding the map image
|
|
*/
|
|
private static ImageView makeMapView() {
|
|
ImageView panoView = new ImageView();
|
|
|
|
panoView.imageProperty().bind(mvbean.mapView());
|
|
panoView.setFitWidth(720);
|
|
panoView.setPreserveRatio(true);
|
|
panoView.setSmooth(true);
|
|
return panoView;
|
|
}
|
|
|
|
/**
|
|
* Builds the trekking stage
|
|
* @param ps : stage
|
|
* @return the trekking stage
|
|
*/
|
|
private static Stage makeTrekkingStage(Stage ps) {
|
|
|
|
BorderPane p = new BorderPane();
|
|
|
|
///////////////////////////////// makeMapPan
|
|
|
|
///////////////////////////////// makeMapGroup
|
|
|
|
ImageView im = makeMapView();
|
|
Pane lines = new Pane();
|
|
lines.setPrefWidth(720);
|
|
lines.setPrefHeight(720);
|
|
StackPane panoGroup = new StackPane(im, lines);
|
|
Bindings.bindContent(lines.getChildren(), mvbean.tl().get());
|
|
|
|
panoGroup.setOnMouseClicked((MouseEvent e) -> {
|
|
switch(e.getClickCount()){
|
|
case 1:
|
|
mvbean.addTs(e.getX(),e.getY());
|
|
break;
|
|
case 2:
|
|
mvbean.zoom(e.getButton());
|
|
break;
|
|
}
|
|
});
|
|
|
|
/////////////////////////////////
|
|
|
|
Pane panoScrollPane = new Pane(panoGroup);
|
|
StackPane mapPane = new StackPane(panoScrollPane);
|
|
////////////////////////////////
|
|
Button compute = new Button("Compute");
|
|
compute.setOnAction(e -> makeElevationProfileStage().show());
|
|
Button clear = new Button("clear");
|
|
p.setCenter(mapPane);
|
|
|
|
Button save = new Button("Save");
|
|
|
|
GridPane grid = new GridPane();
|
|
grid.add(compute, 0, 0);
|
|
grid.add(clear, (int) compute.getScaleX(), 0);
|
|
grid.add(save, 50, 0);
|
|
|
|
Button path = new Button("path");
|
|
path.setOnAction(e -> mvbean.buildEndToEndPath());
|
|
|
|
grid.add(path, 100, 0);
|
|
|
|
p.setBottom(grid);
|
|
Stage s = new Stage();
|
|
|
|
Scene scene = new Scene(p);
|
|
|
|
s.setScene(scene);
|
|
|
|
clear.setOnAction(e -> mvbean.clearTs());
|
|
return s;
|
|
|
|
}
|
|
|
|
/**
|
|
* Builds a view of the composite elevation profile
|
|
* @return a ImageView holding the elevation profile
|
|
*/
|
|
private static Node makeElevationView() {
|
|
ImageView elevationView = new ImageView();
|
|
ArrayList<GeoPoint> pts = new ArrayList<GeoPoint>();
|
|
pts.addAll(mvbean.cts().get());
|
|
pts.addAll(mvbean.ts().get());
|
|
|
|
elevationView.imageProperty()
|
|
.set(ElevationProfileRenderer.renderElevation(
|
|
new CompositElevationProfile(pcbean.cDem().get(),pts)));
|
|
// MapViewRenderer.trekkingProfile(pcbean.cDem())));
|
|
//
|
|
elevationView.setFitWidth(700);
|
|
elevationView.setPreserveRatio(true);
|
|
elevationView.setSmooth(true);
|
|
return elevationView;
|
|
}
|
|
|
|
/**
|
|
* Builds a label holding the elevation profile max height data
|
|
* @return a label with the max height
|
|
*/
|
|
private static Node makeElevationProfileData() {
|
|
Label t = new Label();
|
|
Long d = Math.round(ElevationProfileRenderer.currentMaxHeight.get());
|
|
t.setText(d.toString() + " m");
|
|
return t;
|
|
}
|
|
|
|
/**
|
|
* Builds a pane holding the bottom information for the elevation profile
|
|
* @return a pane for the bottom of the elevation profile
|
|
*/
|
|
private static Node makeElevationBottomPane() {
|
|
|
|
Text t0 = new Text(-100, 0, "0");
|
|
|
|
int d = (int)Math.round(
|
|
ElevationProfileRenderer.currentElevationProfileLength.get());
|
|
|
|
GridPane pp = new GridPane();
|
|
|
|
StackPane p = new StackPane();
|
|
p.setPrefWidth(ElevationProfileRenderer.WIDTH);
|
|
// p.setPrefHeight(ElevationProfileRenderer.HEIGHT);
|
|
|
|
Text t1 = new Text(p.widthProperty().get() - 20, 0,
|
|
new FixedPointStringConverter(3).toString(d)+ " km");
|
|
pp.add(t0, 0, 0);
|
|
pp.add(t1, 1, 0);
|
|
ColumnConstraints colN = new ColumnConstraints();
|
|
colN.setMinWidth(700);
|
|
ColumnConstraints colL = new ColumnConstraints();
|
|
colL.setPrefWidth(700);
|
|
pp.getColumnConstraints().setAll(colN, colL);
|
|
p.getChildren().add(pp);
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Builds a pane holding the elevation profile group
|
|
* @return a stack pane with the elevation group
|
|
*/
|
|
private static Node makeElevationPane() {
|
|
|
|
Pane scrollPane = new Pane(makeElevationGroup());
|
|
return new StackPane(scrollPane);
|
|
};
|
|
|
|
/**
|
|
* Builds a window holding the elevation profile stuff
|
|
* @return a stage for the elevation profile
|
|
*/
|
|
private static Stage makeElevationProfileStage() {
|
|
BorderPane p = new BorderPane();
|
|
p.setCenter(makeElevationPane());
|
|
p.setLeft(makeElevationProfileData());
|
|
p.setBottom(makeElevationBottomPane());
|
|
Stage s = new Stage();
|
|
|
|
Scene scene = new Scene(p);
|
|
|
|
s.setScene(scene);
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Builds a pane holding the elevation profile group
|
|
* @return a stack pane for the elevation profile
|
|
*/
|
|
private static Node makeElevationGroup() {
|
|
|
|
StackPane panoGroup = new StackPane(makeElevationView());
|
|
return panoGroup;
|
|
}
|
|
|
|
/**
|
|
* Builds a pane holding the panorama group
|
|
* @return a stack pane for the panorama group
|
|
*/
|
|
private static Node makePanoGroup() {
|
|
|
|
StackPane panoGroup = new StackPane(makePanoView(), makeLabelsPane());
|
|
panoGroup.setOnMouseMoved((MouseEvent e) -> {
|
|
int x = (int) e.getX();
|
|
int y = (int) e.getY();
|
|
cursor.update(x, y);
|
|
});
|
|
panoGroup.setOnMouseClicked((MouseEvent e) -> {
|
|
String qy = String.format((Locale) null, "mlat=%.4f&mlon=%.4f",
|
|
toDegrees(cursor.lat()), toDegrees(cursor.lon()));// ;
|
|
String fg = String.format((Locale) null, "map=15/%.4f/%.4f",
|
|
toDegrees(cursor.lat()), toDegrees(cursor.lon()));
|
|
URI osmURI;
|
|
try {
|
|
osmURI = new URI("http", "www.openstreetmap.org", "/", qy, fg);
|
|
getDesktop().browse(osmURI);
|
|
} catch (IOException e1) {
|
|
// Do nothing
|
|
} catch (URISyntaxException e1) {
|
|
// Do Nothing
|
|
}
|
|
|
|
});
|
|
return panoGroup;
|
|
}
|
|
|
|
/**
|
|
* Menu Button used for the trecking profile
|
|
* @return a button opening the mapview window
|
|
*/
|
|
private static Menu trekkingProfile() {
|
|
|
|
Label settingsL = new Label("Trekking Profile");
|
|
settingsL.setOnMouseClicked(e -> {
|
|
makeTrekkingStage(null).show();
|
|
});
|
|
Menu settings = new Menu();
|
|
settings.setGraphic(settingsL);
|
|
settings.setDisable(true);
|
|
pcbean.cDem().addListener(e -> settings.setDisable(false));
|
|
return settings;
|
|
}
|
|
|
|
/**
|
|
* Builds the menu top bar
|
|
* @return the menu bar
|
|
*/
|
|
private static MenuBar makeMenu() {
|
|
MenuBar menu = new MenuBar();
|
|
menu.getMenus().addAll(saveMenu(), predefinedPanoramaMenu(),
|
|
painterMenu(), trekkingProfile(), quitMenu());
|
|
return menu;
|
|
}
|
|
|
|
/**
|
|
* Button that saves the current panorama without labels
|
|
* @return a button that saves the panorama
|
|
*/
|
|
private static Menu saveMenu() {
|
|
Label saveL = new Label("Save");
|
|
saveL.setOnMouseClicked(e -> {
|
|
if (pcbean.getImage() != null) {
|
|
String name = Integer
|
|
.toString(pcbean.getParameters().hashCode()) + ".png";
|
|
try {
|
|
ImageIO.write(
|
|
SwingFXUtils.fromFXImage(pcbean.getImage(), null),
|
|
"png", new File(name));
|
|
} catch (IOException e1) {
|
|
e1.printStackTrace();
|
|
}
|
|
}
|
|
});
|
|
Menu save = new Menu();
|
|
save.setGraphic(saveL);
|
|
return save;
|
|
}
|
|
|
|
/**
|
|
* Menu holding all the predefined Panoramas
|
|
* @return a menu with all predefined panoramas
|
|
*/
|
|
private static Menu predefinedPanoramaMenu() {
|
|
Menu ppMenu = new Menu("Predefined Panorama");
|
|
for (PredefinedPanoramas pp : PredefinedPanoramas.values()) {
|
|
MenuItem ppItem = new MenuItem(pp.toString());
|
|
ppItem.setOnAction(e -> {
|
|
ppbean.updateProperties(pp.get());
|
|
});
|
|
ppMenu.getItems().add(ppItem);
|
|
}
|
|
return ppMenu;
|
|
}
|
|
|
|
/**
|
|
* Menu holding all custom painters
|
|
* @return a menu with all custom painters
|
|
*/
|
|
private static Menu painterMenu() {
|
|
Menu cpMenu = new Menu("Painter");
|
|
Menu gradItem = new Menu("Gradient Painter");
|
|
cpMenu.getItems().add(gradItem);
|
|
ToggleGroup toggleGroup = new ToggleGroup();
|
|
for (CustomPainters cp : CustomPainters.values()) {
|
|
|
|
RadioMenuItem cpItem = new RadioMenuItem(cp.displayText());
|
|
cpItem.setToggleGroup(toggleGroup);
|
|
|
|
cpItem.setOnAction(e -> {
|
|
ppbean.setPainter(cp);
|
|
});
|
|
if (pcbean.painterProperty().equals(cp))
|
|
cpItem.setSelected(true);
|
|
|
|
if (cp.isGradient()) {
|
|
Canvas cv = new Canvas(20, 20);
|
|
GraphicsContext gc = cv.getGraphicsContext2D();
|
|
gc.setFill(cp.gradient().get());
|
|
gc.fillRect(0, 0, 20, 20);
|
|
cv.prefHeight(20);
|
|
cv.setRotate(180);
|
|
cpItem.setGraphic(cv);
|
|
|
|
gradItem.getItems().add(cpItem);
|
|
} else {
|
|
cpMenu.getItems().add(cpItem);
|
|
}
|
|
}
|
|
return cpMenu;
|
|
}
|
|
|
|
/**
|
|
* Quit button that closes the program
|
|
* @return a quit button
|
|
*/
|
|
private static Menu quitMenu() {
|
|
Label quitL = new Label("Quit");
|
|
quitL.setOnMouseClicked(e -> Platform.exit());
|
|
Menu quit = new Menu();
|
|
quit.setGraphic(quitL);
|
|
return quit;
|
|
}
|
|
|
|
/**
|
|
* Class used to manage the cursor on the panorama pane
|
|
*/
|
|
private static final class CursorInfo {
|
|
private ObjectProperty<String> textProperty = new SimpleObjectProperty<String>();
|
|
private double lon, lat, dist = Double.POSITIVE_INFINITY;
|
|
|
|
/**
|
|
* Updates the x,y position of the cursor
|
|
* @param x
|
|
* @param y
|
|
*/
|
|
public void update(int x, int y) {
|
|
int ss = pcbean.getParameters().superSamplingExponent();
|
|
x *= Math.pow(2, ss);
|
|
y *= Math.pow(2, ss);
|
|
if (isValid(x, y)) {
|
|
lat = pcbean.getPanorama().latitudeAt(x, y);
|
|
lon = pcbean.getPanorama().longitudeAt(x, y);
|
|
dist = pcbean.getPanorama().distanceAt(x, y);
|
|
double alt = pcbean.getPanorama().elevationAt(x, y);
|
|
GeoPoint point = new GeoPoint(lon, lat);
|
|
double azi = pcbean.getParameters().panoramaDisplayParameters()
|
|
.observerPosition().azimuthTo(point);
|
|
double slope = (alt
|
|
- pcbean.getParameters().panoramaDisplayParameters()
|
|
.observerElevation()
|
|
- (sq(dist) * NATURAL_VARIATION)) / dist;
|
|
double ele = toDegrees(atan(slope));
|
|
String slat = lat < 0 ? "S" : "N";
|
|
String slon = lon < 0 ? "W" : "E";
|
|
textProperty.set(String.format((Locale) null,
|
|
"Position : %.4f°%s %.4f°%s \nDistance : %.1f km \nAltitude : %d m \nAzimut : %.1f° (%s) Elévation : %.1f°",
|
|
abs(toDegrees(lat)), slat, abs(toDegrees(lon)), slon,
|
|
(dist / 1_000.0), (int) alt, toDegrees(azi),
|
|
Azimuth.toOctantString(azi, "N", "S", "E", "W"), ele));
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return the textProperty object to display the cursor information
|
|
*/
|
|
public ObjectProperty<String> textProperty() {
|
|
return textProperty;
|
|
}
|
|
|
|
/**
|
|
* Returns the longitude at the cursors position
|
|
*
|
|
* @return longitude at cursors last valid position
|
|
*/
|
|
public double lon() {
|
|
return lon;
|
|
}
|
|
|
|
/**
|
|
* Returns the latitude at the cursors position
|
|
*
|
|
* @return latitude at cursors last valid position
|
|
*/
|
|
public double lat() {
|
|
return lat;
|
|
}
|
|
|
|
/**
|
|
* Returns if the x,y index is valid (not too far away)
|
|
*
|
|
* @param x
|
|
* : x index
|
|
* @param y
|
|
* : y index
|
|
* @return if the position at x,y is valid
|
|
*/
|
|
public boolean isValid(int x, int y) {
|
|
return pcbean.getPanorama() != null
|
|
&& pcbean.getPanorama().distanceAt(x, y,
|
|
(float) Double.POSITIVE_INFINITY) < Double.POSITIVE_INFINITY;
|
|
}
|
|
}
|
|
|
|
}
|