import java.awt.*; import java.awt.Label; import java.awt.event.MouseEvent; import java.beans.EventHandler; import java.io.File; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.embed.swing.SwingFXUtils; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.geometry.Orientation; import javafx.geometry.VPos; import javafx.scene.canvas.Canvas; import javafx.scene.control.*; import javafx.scene.control.Button; import javafx.scene.image.WritableImage; import javafx.scene.layout.*; import javafx.scene.paint.*; import javafx.scene.canvas.*; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.shape.TriangleMesh; import javafx.scene.text.*; import javafx.scene.image.Image; import javafx.scene.shape.Line; import javafx.scene.Scene; import javafx.scene.SnapshotParameters; import javafx.scene.text.Font; import javafx.stage.FileChooser; import javafx.stage.Stage; import javax.imageio.ImageIO; import javax.swing.filechooser.FileSystemView; import static java.lang.Math.sqrt; public class RunwayGraphics { Theme theme; //used for when we clear the current runway, will draw a runway which is clearly not meant to be looked at AffectedRunway placeHolderRunway; //where the graphics happen Stage stage; AnchorPane runwayDisplayAnchor; StackPane topView, sideView; Canvas topViewCanvas, sideViewCanvas; GraphicsContext topGc, sideGc; SplitPane splitView; Canvas currentView; //buttons HBox filtersGridPaneContainer; GridPane filtersGridPane; Text displacedThresholdLabel, TORALabel, LDALabel, ASDALabel, TODALabel; Text obstacleLabel, obstacleDistancesLabel, clearwayLabel, stopwayLabel, RESALabel; Text stripEndLabel, blastProtLabel, ALSLabel, TOCSLabel, runwayStripLabel; CheckBox displacedThresholdBox, TORABox, LDABox, ASDABox, TODABox; CheckBox obstacleBox, obstacleDistancesBox, clearwayBox, stopwayBox, RESABox; CheckBox stripEndBox, blastProtBox, ALSBox, TOCSBox, runwayStripBox; ChoiceBox viewSelect; Obstruction obstruction; Text olddt, oldtora, oldlda, oldasda, oldtoda; //used as reference when applying real life distances to the drawing, picked solely for clarity, each margin is taken, added from the previous margin final int SIDE_VIEW_THICKNESS = 30, CLEARWAY_MARGIN = 40, STOPWAY_MARGIN = 30, RESA_MARGIN = 40; final int BLAST_ALLOWANCE_MARGIN = 30, CENTERLINE_MARGIN = 80, THRESHOLD_MARGIN = 2, THRESHOLD_LENGTH = 30; AffectedRunway affectedRunway; final int CLEARED_AREA = 75 * 2, CANVAS_WIDTH = 800; //don't change CANVAS_WIDTH unless you really have to, not hard-coded yet //used as reference throughout the code, but fluid, prone to change depending on settings. int runwayPL; int posMargin = 0; //the margin before the runway starts int negMargin = 0; //the margin before the runway ends int spacing = 25; //color palette final Color ROAD_COLOR = Color.LIGHTGRAY, STRIPE_COLOR = Color.WHITE, WARNING_COLOR = Color.GOLDENROD; final Color CLEARED_AREA_COLOR = Color.DODGERBLUE, HUD_COLOR = Color.LIGHTSTEELBLUE, BACKGROUND_COLOR = Color.LIGHTCYAN; final Color OUTLINE_COLOR = Color.BLACK, OBSTACLE_COLOR1 = Color.FIREBRICK, OBSTACLE_COLOR2 = Color.CRIMSON; final Color GRASS_COLOR = Color.DARKSEAGREEN, SAFE_COLOR = Color.GREEN, DANGER_COLOR = Color.RED; Background paneBackground, hudBackground; //color coding palette Color displacedThresholdColor = Color.NAVY, TORAColor = Color.ORANGERED, LDAColor = Color.DARKMAGENTA, ASDAColor = Color.MAROON, TODAColor = Color.SIENNA; Color clearwayColor, stopwayColor, RESAColor = Color.DEEPPINK; Color stripEndColor, blastProtColor, ALSColor, TOCSColor, runwayStripColor; double orgSceneX, orgSceneY; double orgTranslateX, orgTranslateY; public AnchorPane getRunwayGraphics() { return runwayDisplayAnchor; } public void setAffectedRunway(AffectedRunway affectedRunway) { this.affectedRunway = affectedRunway; } public RunwayGraphics(Stage stage, Theme theme) { this.theme = theme; this.stage = stage; placeHolderRunway = new AffectedRunway(new Runway("N/A","N/A",10,0,0,0,0,0,0,1,1,1,1),new Obstruction("N/A", 0, 0, 0, 0)); affectedRunway = placeHolderRunway; runwayDisplayAnchor = new AnchorPane(); paneBackground = new Background(new BackgroundFill(BACKGROUND_COLOR, CornerRadii.EMPTY, Insets.EMPTY)); hudBackground = new Background(new BackgroundFill(HUD_COLOR, CornerRadii.EMPTY, Insets.EMPTY)); currentView = topViewCanvas; setupSplitView(); setupButtons(); //where the graphics happen runwayDisplayAnchor.setBorder(new Border(new BorderStroke(theme.getHUDColor(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))); runwayDisplayAnchor.setBackground(new Background(new BackgroundFill(theme.getBackgroundColor(), CornerRadii.EMPTY, Insets.EMPTY))); } public void draw(AffectedRunway affectedRunway) { this.affectedRunway = affectedRunway; draw(); } public void clearRunway() { this.affectedRunway = placeHolderRunway; draw(); } public void drawRotated(AffectedRunway affectedRunway) { this.affectedRunway = affectedRunway; // draw(); // System.out.println(topViewCanvas.rotateProperty().getValue()); if(topViewCanvas.rotateProperty().getValue() == 0.0) { int degrees; String name = affectedRunway.getOriginalRunway().getName(); if(name.length() > 2) { degrees = Integer.parseInt(name.substring(0, 2) + "0") + 90; } else { degrees = Integer.parseInt(name + "0") + 90; } // System.out.println(degrees); topViewCanvas.rotateProperty().setValue(degrees); } else { // System.out.println(topViewCanvas.rotateProperty().getValue()); topViewCanvas.rotateProperty().setValue(0.0); // System.out.println(topViewCanvas.rotateProperty().getValue()); } } public void saveCanvasImage(String exporttarget) { /**FileChooser fileChooser = new FileChooser(); fileChooser.setInitialDirectory(new File(FileSystemView.getFileSystemView().getDefaultDirectory().getPath())); fileChooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter("PNG Files", "*.png")); fileChooser.setTitle("Export runway calculations to PNG"); File exporttarget = fileChooser.showSaveDialog(stage); **/ if(exporttarget == null) { Alert errorMessage = new Alert(Alert.AlertType.ERROR); errorMessage.setContentText("Create a file to save runway into."); errorMessage.show(); } else { String filename = exporttarget; //String filename = exporttarget.getAbsolutePath(); String fileType = filename.substring(filename.length() - 3); File out = new File(filename); try { if(currentView == null) { Alert errorMessage = new Alert(Alert.AlertType.ERROR); errorMessage.setTitle("No runway to export"); errorMessage.setContentText("Please render a runway and try again"); errorMessage.show(); } else { WritableImage wim = new WritableImage((int) Math.round(currentView.getWidth()), (int) Math.round(currentView.getHeight())); currentView.snapshot(null, wim); ImageIO.write(SwingFXUtils.fromFXImage(wim, null), fileType, out); } /**Alert success = new Alert(Alert.AlertType.INFORMATION); success.setTitle("Image Exported Successfully"); success.setContentText("Image has been saved to " + filename); success.show();**/ } catch (IOException e) { Alert errorMessage = new Alert(Alert.AlertType.ERROR); errorMessage.setTitle("Error Creating File"); errorMessage.setContentText(e.getMessage()); errorMessage.show(); } } } public void saveCanvasImage() { FileChooser fileChooser = new FileChooser(); fileChooser.setInitialDirectory(new File(FileSystemView.getFileSystemView().getDefaultDirectory().getPath())); fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("PNG Files", "*.png"), new FileChooser.ExtensionFilter("JPG Files", "*.jpg"), new FileChooser.ExtensionFilter("GIF Files", "*.gif")); fileChooser.setTitle("Export runway calculations"); File exporttarget = fileChooser.showSaveDialog(stage); if(exporttarget == null) { Alert errorMessage = new Alert(Alert.AlertType.ERROR); errorMessage.setContentText("Create a file to save runway into."); errorMessage.show(); } else { String filename = exporttarget.getAbsolutePath(); /**if (!filename.substring(filename.length() - 4).equals(".png")) { filename = filename.concat(".png"); }**/ String fileType = filename.substring(filename.length() - 3); File out = new File(filename); try { if(currentView == null) { Alert errorMessage = new Alert(Alert.AlertType.ERROR); errorMessage.setTitle("No runway to export"); errorMessage.setContentText("Please render a runway and try again"); errorMessage.show(); } else { WritableImage wim = new WritableImage((int) Math.round(currentView.getWidth()), (int) Math.round(currentView.getHeight())); SnapshotParameters params = new SnapshotParameters(); params.setFill(theme.getHUDColor()); currentView.snapshot(params, wim); ImageIO.write(SwingFXUtils.fromFXImage(wim, null), fileType, out); Alert success = new Alert(Alert.AlertType.INFORMATION); success.setTitle("Image Exported Successfully"); success.setContentText("Image has been saved to " + filename); success.show(); } } catch (IOException e) { Alert errorMessage = new Alert(Alert.AlertType.ERROR); errorMessage.setTitle("Error Creating File"); errorMessage.setContentText(e.getMessage()); errorMessage.show(); } } } //draws all elements from start, taking parameters/runway into account public void draw() { obstruction = affectedRunway.getObstruction(); filtersGridPaneContainer.setBackground(new Background(new BackgroundFill(theme.getHUDColor(), CornerRadii.EMPTY, Insets.EMPTY))); runwayDisplayAnchor.setBackground(new Background(new BackgroundFill(theme.getBackgroundColor(), CornerRadii.EMPTY, Insets.EMPTY))); splitView.setBackground(new Background(new BackgroundFill(theme.getBackgroundColor(), CornerRadii.EMPTY, Insets.EMPTY))); drawTopView(CANVAS_WIDTH, 400, false); drawSideView(CANVAS_WIDTH, 400); setupButtons(); //centers elements runwayDisplayAnchor.setTopAnchor(topView, 0.0); runwayDisplayAnchor.setBottomAnchor(topView, 0.0); runwayDisplayAnchor.setLeftAnchor(topView, 0.0); runwayDisplayAnchor.setRightAnchor(topView, 0.0); runwayDisplayAnchor.setTopAnchor(sideView, 0.0); runwayDisplayAnchor.setBottomAnchor(sideView, 0.0); runwayDisplayAnchor.setLeftAnchor(sideView, 0.0); runwayDisplayAnchor.setRightAnchor(sideView, 0.0); runwayDisplayAnchor.setTopAnchor(splitView, 0.0); runwayDisplayAnchor.setBottomAnchor(splitView, 0.0); runwayDisplayAnchor.setLeftAnchor(splitView, 0.0); runwayDisplayAnchor.setRightAnchor(splitView, 0.0); //puts the appropriate pane on, according to the viewSelect choicebox runwayDisplayAnchor.getChildren().clear(); switch (viewSelect.getSelectionModel().getSelectedIndex()) { case 0: runwayDisplayAnchor.getChildren().addAll(topView, viewSelect, filtersGridPaneContainer); currentView = topViewCanvas; break; case 1: runwayDisplayAnchor.getChildren().addAll(sideView, viewSelect, filtersGridPaneContainer); currentView = sideViewCanvas; break; case 3: splitView.getItems().clear(); setupSplitView(); runwayDisplayAnchor.setTopAnchor(splitView, 0.0); runwayDisplayAnchor.setBottomAnchor(splitView, 0.0); runwayDisplayAnchor.setLeftAnchor(splitView, 0.0); runwayDisplayAnchor.setRightAnchor(splitView, 0.0); splitView.getItems().addAll(topView, sideView); currentView = topViewCanvas; runwayDisplayAnchor.getChildren().addAll(splitView, viewSelect, filtersGridPaneContainer); break; } showMeasurements(); } public void showMeasurements() { showTitle(); if (obstacleBox.isSelected()) { showObstacle(); } // if (obstacleDistancesBox.isSelected()) { // showObstacleDistances(); // } topGc.translate(0, spacing); sideGc.translate(0, spacing); int spaceCount = 1; if (displacedThresholdBox.isSelected()) { showDisplacedThreshold(); spaceCount ++; } if (TORABox.isSelected()) { showTORA(); spaceCount ++; } if (LDABox.isSelected()) { showLDA(); spaceCount ++; } if (ASDABox.isSelected()) { showASDA(); spaceCount ++; } if (TODABox.isSelected()) { showTODA(); spaceCount ++; } if (RESABox.isSelected() && !affectedRunway.isUnchanged()) { showRESA(); spaceCount++; } topGc.translate(0, -spaceCount * spacing); } public void showObstacle() { obstruction = affectedRunway.getObstruction(); //affectedRunway.recalculate(obstruction); drawObstacle(obstruction.getDistanceFromThreshold() + affectedRunway.getOriginalRunway().getDisplacedThreshold(), obstruction.getDistanceFromCentre(), obstruction.getLength(), obstruction.getHeight()); double OrigTODA = affectedRunway.getOriginalRunway().getTODA(); double NewTODA = affectedRunway.getTODA(); double OrigTORA = affectedRunway.getOriginalRunway().getTORA(); double NewTORA = affectedRunway.getTORA(); double OrigASDA = affectedRunway.getOriginalRunway().getASDA(); double NewASDA = affectedRunway.getASDA(); double OrigLDA = affectedRunway.getOriginalRunway().getLDA(); double NewLDA = affectedRunway.getLDA(); int runwayLength = affectedRunway.getOriginalRunway().getTORA(); int obDistance = obstruction.getDistanceFromThreshold(); int height = obstruction.getHeight(); double red = runwayLength - NewLDA; double green = NewLDA; int threshold = affectedRunway.getOriginalRunway().getDisplacedThreshold(); int b = (int) getLengthRelativeToRunway(threshold); if (obDistance + threshold > (runwayLength/2)) { drawDangerZones(0 + threshold, 200 ,green, 50, theme.getSafeColor(), "LDA", obDistance); drawDangerZones(green + threshold, 200, red, 50, theme.getDangerColor(), "ALS/TOCS", obDistance); drawTriangle(obstruction.getDistanceFromThreshold() + affectedRunway.getOriginalRunway().getDisplacedThreshold(), obstruction.getHeight(), 2); } if (obDistance + threshold <= (runwayLength/2)) { drawDangerZones(0 + threshold, 200, red - threshold, 50, theme.getDangerColor(), "ALS/TOCS", obDistance); drawDangerZones((0 + threshold) + (red - threshold), 200, green, 50, theme.getSafeColor(), "LDA", obDistance); drawTriangle(obstruction.getDistanceFromThreshold() + affectedRunway.getOriginalRunway().getDisplacedThreshold(), obstruction.getHeight(), 1); } } // public void showObstacleDistances() { // // obstruction = affectedRunway.getObstruction(); // // } public void drawTriangle(double distanceFromThreshold, double height, double unsafeArea) { double x1 = getLengthRelativeToRunway(distanceFromThreshold); double y1 = getLengthRelativeToRunway(0); double x2 = getLengthRelativeToRunway(distanceFromThreshold); double y2 = - getLengthRelativeToRunway(height); double x3 = getLengthRelativeToRunway(distanceFromThreshold + (height*50)); double y3 = getLengthRelativeToRunway(0); if (unsafeArea == 1) { sideGc.setStroke(theme.getOutlineColor()); sideGc.setFill(theme.getDangerColor()); sideGc.beginPath(); sideGc.translate(posMargin + THRESHOLD_MARGIN, (sideGc.getCanvas().getHeight() - SIDE_VIEW_THICKNESS) / 2); sideGc.moveTo(x1, y1); sideGc.lineTo(x2, y2); sideGc.lineTo(x3, y3); sideGc.lineTo(x1, y1); sideGc.translate(-(posMargin + THRESHOLD_MARGIN), -(sideGc.getCanvas().getHeight() - SIDE_VIEW_THICKNESS) / 2); sideGc.fill(); sideGc.closePath(); } if (unsafeArea == 2) { sideGc.setStroke(theme.getOutlineColor()); sideGc.setFill(theme.getDangerColor()); sideGc.beginPath(); sideGc.translate(posMargin + THRESHOLD_MARGIN, (sideGc.getCanvas().getHeight() - SIDE_VIEW_THICKNESS) / 2); sideGc.moveTo(x1, y1); sideGc.lineTo(x2, y2); double x4 = getLengthRelativeToRunway(distanceFromThreshold - (height*50)); sideGc.lineTo(x4, y3); sideGc.lineTo(x1, y1); sideGc.translate(-(posMargin + THRESHOLD_MARGIN), -(sideGc.getCanvas().getHeight() - SIDE_VIEW_THICKNESS) / 2); sideGc.fill(); sideGc.closePath(); } } public void showTitle() { String title = affectedRunway.getOriginalRunway().getName() + " - " + affectedRunway.getOriginalRunway().getAirport(); Text text = new Text(title); topGc.setFont(new Font(20)); topGc.setTextAlign(TextAlignment.CENTER); topGc.setLineWidth(1.7); //topGc.setStroke(theme.getRESAColor()); topGc.strokeText(title, CANVAS_WIDTH / 2, 50); topGc.setFont(new Font(15)); topGc.setTextAlign(TextAlignment.LEFT); topGc.setLineWidth(1.2); sideGc.setFont(new Font(20)); sideGc.setTextAlign(TextAlignment.CENTER); sideGc.setLineWidth(1.7); // sideGc.setStroke(theme.getRESAColor()); sideGc.strokeText(title, CANVAS_WIDTH / 2, 50); sideGc.setFont(new Font(15)); sideGc.setTextAlign(TextAlignment.LEFT); sideGc.setLineWidth(1.2); } public void showTORA() { double TORA = affectedRunway.getTORA(); int runwayLength = affectedRunway.getOriginalRunway().getTORA(); int obDistance = obstruction.getDistanceFromThreshold(); int threshold = affectedRunway.getOriginalRunway().getDisplacedThreshold(); int b = (int) getLengthRelativeToRunway(threshold); //System.out.println(threshold); if (obDistance + threshold > (runwayLength/2)) { drawMeasurement(topGc, "TORA: " + TORA, posMargin + THRESHOLD_MARGIN + b, 0, getLengthRelativeToRunway(TORA), Orientation.HORIZONTAL, theme.getTORAColor()); drawMeasurement(sideGc, "TORA: " + TORA, posMargin + THRESHOLD_MARGIN + b, 0, getLengthRelativeToRunway(TORA), Orientation.HORIZONTAL, theme.getTORAColor()); } if (obDistance + threshold <= (runwayLength/2)) { drawMeasurement(topGc, "TORA: " + TORA, CANVAS_WIDTH - negMargin, 0, -getLengthRelativeToRunway(TORA), Orientation.HORIZONTAL, theme.getTORAColor()); drawMeasurement(sideGc, "TORA: " + TORA, CANVAS_WIDTH - negMargin, 0, -getLengthRelativeToRunway(TORA), Orientation.HORIZONTAL, theme.getTORAColor()); } topGc.translate(0, spacing); sideGc.translate(0, spacing); } public void showLDA() { double LDA = affectedRunway.getLDA(); int runwayLength = affectedRunway.getOriginalRunway().getTORA(); int obDistance = obstruction.getDistanceFromThreshold(); int threshold = affectedRunway.getOriginalRunway().getDisplacedThreshold(); int b = (int) getLengthRelativeToRunway(threshold); if (obDistance + threshold> (runwayLength/2)) { drawMeasurement(topGc, "LDA: " + LDA, posMargin + THRESHOLD_MARGIN + b, 0, getLengthRelativeToRunway(LDA), Orientation.HORIZONTAL, theme.getLDAColor()); drawMeasurement(sideGc, "LDA: " + LDA, posMargin + THRESHOLD_MARGIN + b, 0, getLengthRelativeToRunway(LDA), Orientation.HORIZONTAL, theme.getLDAColor()); } if (obDistance + threshold <= (runwayLength/2)) { drawMeasurement(topGc, "LDA: " + LDA, CANVAS_WIDTH - negMargin, 0, -getLengthRelativeToRunway(LDA), Orientation.HORIZONTAL, theme.getLDAColor()); drawMeasurement(sideGc, "LDA: " + LDA, CANVAS_WIDTH - negMargin, 0, -getLengthRelativeToRunway(LDA), Orientation.HORIZONTAL, theme.getLDAColor()); } topGc.translate(0, spacing); sideGc.translate(0, spacing); } public void showASDA() { double ASDA = affectedRunway.getASDA(); int offset = 0; int margin = 0; if (hasClearway()) { margin += CLEARWAY_MARGIN; } if (hasStopway()) { offset += STOPWAY_MARGIN; } int runwayLength = affectedRunway.getOriginalRunway().getTORA(); int obDistance = obstruction.getDistanceFromThreshold(); int threshold = affectedRunway.getOriginalRunway().getDisplacedThreshold(); int b = (int) getLengthRelativeToRunway(threshold); if (obDistance + threshold > (runwayLength/2)) { drawMeasurement(topGc, "ASDA: " + ASDA, posMargin + THRESHOLD_MARGIN + b, 0, getLengthRelativeToRunway(ASDA) , Orientation.HORIZONTAL, theme.getASDAColor()); drawMeasurement(sideGc, "ASDA: " + ASDA, posMargin + THRESHOLD_MARGIN + b, 0, getLengthRelativeToRunway(ASDA) , Orientation.HORIZONTAL, theme.getASDAColor()); } if (obDistance + threshold <= (runwayLength/2)) { drawMeasurement(topGc, "ASDA: " + ASDA, CANVAS_WIDTH - margin, 0, -getLengthRelativeToRunway(ASDA) - offset , Orientation.HORIZONTAL, theme.getASDAColor()); drawMeasurement(sideGc, "ASDA: " + ASDA, CANVAS_WIDTH - margin, 0, -getLengthRelativeToRunway(ASDA) - offset, Orientation.HORIZONTAL, theme.getASDAColor()); } topGc.translate(0, spacing); sideGc.translate(0, spacing); } public void showTODA() { double TODA = affectedRunway.getTODA(); int offset = 0; //assumes that if there is a clearway/stopway, their length is too small to be taken in count, proned to change if (hasClearway()) { offset += CLEARWAY_MARGIN; } if (hasStopway()) { offset += STOPWAY_MARGIN; } int runwayLength = affectedRunway.getOriginalRunway().getTORA(); int obDistance = obstruction.getDistanceFromThreshold(); int threshold = affectedRunway.getOriginalRunway().getDisplacedThreshold(); int b = (int) getLengthRelativeToRunway(threshold); if (obDistance + threshold > (runwayLength/2)) { drawMeasurement(topGc, "TODA: " + TODA, posMargin + THRESHOLD_MARGIN + b, 0, getLengthRelativeToRunway(TODA), Orientation.HORIZONTAL, theme.getTODAColor()); drawMeasurement(sideGc, "TODA: " + TODA, posMargin + THRESHOLD_MARGIN + b, 0, getLengthRelativeToRunway(TODA), Orientation.HORIZONTAL, theme.getTODAColor()); } if (obDistance + threshold <= (runwayLength/2)) { drawMeasurement(topGc, "TODA: " + TODA, CANVAS_WIDTH, 0, -getLengthRelativeToRunway(TODA) - offset, Orientation.HORIZONTAL, theme.getTODAColor()); drawMeasurement(sideGc, "TODA: " + TODA, CANVAS_WIDTH, 0, -getLengthRelativeToRunway(TODA) - offset, Orientation.HORIZONTAL, theme.getTODAColor()); } topGc.translate(0, spacing); sideGc.translate(0, spacing); } public void showRESA() { Obstruction obst = affectedRunway.getObstruction(); double dist = getLengthRelativeToRunway(obst.getDistanceFromThreshold()); double NewTORA = affectedRunway.getTORA(); double NewLDA = affectedRunway.getLDA(); double RESA = affectedRunway.getOriginalRunway().getRESA(); int threshold = affectedRunway.getOriginalRunway().getDisplacedThreshold(); int c = (int) getLengthRelativeToRunway(threshold); double a = CANVAS_WIDTH - negMargin - (getLengthRelativeToRunway(NewTORA)); double b = getLengthRelativeToRunway(NewLDA + 60); int runwayLength = affectedRunway.getOriginalRunway().getTORA(); int obDistance = obstruction.getDistanceFromThreshold(); if (obDistance + threshold > (runwayLength/2)) { drawMeasurement(topGc, "RESA: " + RESA, posMargin + THRESHOLD_MARGIN + b + c, 0, getLengthRelativeToRunway(RESA), Orientation.HORIZONTAL, theme.getRESAColor()); drawMeasurement(sideGc, "RESA: " + RESA, posMargin + THRESHOLD_MARGIN + b + c, 0, getLengthRelativeToRunway(RESA), Orientation.HORIZONTAL, theme.getRESAColor()); } if (obDistance + threshold<= (runwayLength/2)) { drawMeasurement(topGc, "RESA: " + RESA, CANVAS_WIDTH - b, 0, -getLengthRelativeToRunway(RESA), Orientation.HORIZONTAL, theme.getRESAColor()); drawMeasurement(sideGc, "RESA: " + RESA, CANVAS_WIDTH - b, 0, -getLengthRelativeToRunway(RESA), Orientation.HORIZONTAL, theme.getRESAColor()); } topGc.translate(0, spacing); sideGc.translate(0, spacing); } public void showDisplacedThreshold() { double displacedThreshold = affectedRunway.getOriginalRunway().getDisplacedThreshold(); drawMeasurement(topGc, "DT: " + displacedThreshold, posMargin + THRESHOLD_MARGIN, 0, getLengthRelativeToRunway(displacedThreshold), Orientation.HORIZONTAL, theme.getDisplacedThresholdColor()); drawMeasurement(sideGc, "DT: " + displacedThreshold, posMargin + THRESHOLD_MARGIN, 0, getLengthRelativeToRunway(displacedThreshold), Orientation.HORIZONTAL, theme.getDisplacedThresholdColor()); topGc.translate(0, spacing); sideGc.translate(0, spacing); } public void drawDangerZones(double xStart, double yStart, double length, double height, Color fill, String label, int distance) { double x = getLengthRelativeToRunway(xStart); double y = getLengthRelativeToRunway(yStart); double newLength = getLengthRelativeToRunway(height*50); double newHeight = getLengthRelativeToRunway(height); double length2 = getLengthRelativeToRunway(length); int runwayLength = affectedRunway.getOriginalRunway().getTORA(); if (fill == theme.getDangerColor()) { topGc.setTextAlign(TextAlignment.CENTER); if (length2 > 0) topGc.strokeText(label, x + posMargin + THRESHOLD_MARGIN + (length2 / 2), (topGc.getCanvas().getHeight() / 2) -y - 5); topGc.setStroke(theme.getOutlineColor()); topGc.setFill(createGrid(fill, fill)); topGc.translate(posMargin + THRESHOLD_MARGIN, topGc.getCanvas().getHeight() / 2); topGc.fillRect(x, -y, length2, newHeight); topGc.strokeRect(x, -y, length2, newHeight); topGc.translate(-(posMargin + THRESHOLD_MARGIN), -topGc.getCanvas().getHeight() / 2); } if (fill == theme.getSafeColor()) { topGc.setTextAlign(TextAlignment.CENTER); if (length2 > 0) topGc.strokeText(label, x + posMargin + THRESHOLD_MARGIN + (length2 / 2), (topGc.getCanvas().getHeight() / 2) -y - 5); topGc.setStroke(theme.getOutlineColor()); topGc.setFill(createGrid(fill, fill)); topGc.translate(posMargin + THRESHOLD_MARGIN, topGc.getCanvas().getHeight() / 2); topGc.fillRect(x, -y, length2, newHeight); topGc.strokeRect(x, -y, length2, newHeight); topGc.translate(-(posMargin + THRESHOLD_MARGIN), -topGc.getCanvas().getHeight() / 2); } } public void drawObstacle(double arg1, double arg2, double arg3, double arg4) { double x = getLengthRelativeToRunway(arg1); double y = getLengthRelativeToRunway(arg2); double length = getLengthRelativeToRunway(arg3); double height = getLengthRelativeToRunway(arg4); topGc.setStroke(theme.getOutlineColor()); topGc.setFill(createGrid(theme.getObstacleColor2(), theme.getObstacleColor1())); topGc.translate(posMargin + THRESHOLD_MARGIN, topGc.getCanvas().getHeight() / 2); topGc.fillRect(x, -y - length, length, length); topGc.strokeRect(x, -y - length, length, length); topGc.translate(-(posMargin + THRESHOLD_MARGIN), -topGc.getCanvas().getHeight() / 2); sideGc.setStroke(theme.getOutlineColor()); sideGc.setFill(createGrid(theme.getObstacleColor2(), theme.getObstacleColor1())); sideGc.translate(posMargin + THRESHOLD_MARGIN, (sideGc.getCanvas().getHeight() - SIDE_VIEW_THICKNESS) / 2); sideGc.fillRect(x, -height, length, height); sideGc.strokeRect(x, -height, length, height); sideGc.translate(-(posMargin + THRESHOLD_MARGIN), -(sideGc.getCanvas().getHeight() - SIDE_VIEW_THICKNESS) / 2); } //draws a line between 2 points with a measure/label displayed next to it public GraphicsContext drawMeasurement(GraphicsContext gc, String label, double x, double y, double length, Orientation o, Color color) { gc.translate(0, gc.getCanvas().getHeight()/2); gc.setStroke(color); gc.setLineWidth(2.0); double delimiter = 5; switch (o) { case HORIZONTAL : //main line gc.setLineDashes(5,5); gc.strokeLine(x, y, x + length, y); //side lines gc.setLineDashes(1); if (length >= 0) { gc.strokeLine(x, y, x + delimiter, y - delimiter); gc.strokeLine(x, y, x + delimiter, y + delimiter); gc.strokeLine(x + length, y, x + length - delimiter, y - delimiter); gc.strokeLine(x + length, y, x + length - delimiter, y + delimiter); } else { gc.strokeLine(x, y, x - delimiter, y - delimiter); gc.strokeLine(x, y, x - delimiter, y + delimiter); gc.strokeLine(x + length, y, x + length + delimiter, y - delimiter); gc.strokeLine(x + length, y, x + length + delimiter, y + delimiter); } gc.setTextBaseline(VPos.TOP); gc.setTextAlign(TextAlignment.CENTER); gc.setLineWidth(1.0); gc.strokeText(label, x + (length/2), y); break; case VERTICAL : //main line gc.setLineDashes(2, 2, 1, 2); gc.strokeLine(x, y, x, y + length); //side lines gc.setLineDashes(1); gc.strokeLine(x, y, x + delimiter, y + delimiter); gc.strokeLine(x, y, x - delimiter, y + delimiter); gc.strokeLine(x, y + length, x + delimiter, y + length - delimiter); gc.strokeLine(x, y + length, x - delimiter, y + length - delimiter); Text text = new Text(label); gc.setTextBaseline(VPos.CENTER); gc.setTextAlign(TextAlignment.LEFT); gc.setLineWidth(1.0); gc.strokeText(label, x + 4, y + (length/2)); break; } gc.translate(0, -gc.getCanvas().getHeight()/2); return gc; } private double getLengthRelativeToRunway(double meters) { Runway runway = affectedRunway.getOriginalRunway(); //simple rule of 3 return ((meters * runwayPL) / runway.getTORA()); } public void setupButtons() { filtersGridPane = new GridPane(); ColumnConstraints col1 = new ColumnConstraints(); ColumnConstraints col2 = new ColumnConstraints(); ColumnConstraints col3 = new ColumnConstraints(); ColumnConstraints col4 = new ColumnConstraints(); col1.setPercentWidth(35); col2.setPercentWidth(25); col3.setPercentWidth(20); col4.setPercentWidth(20); filtersGridPane.getColumnConstraints().addAll(col1, col2, col3, col4); filtersGridPane.setVgap(10); filtersGridPane.setHgap(40); Text showLabel = new Text("Show"); Text oldLabel = new Text("Original Values"); Text newLabel = new Text("New Values"); Text typeLabel = new Text("Measurement"); showLabel.setFill(theme.getOutlineColor()); oldLabel.setFill(theme.getOutlineColor()); newLabel.setFill(theme.getOutlineColor()); typeLabel.setFill(theme.getOutlineColor()); if(affectedRunway != null && affectedRunway != placeHolderRunway) { displacedThresholdLabel = new Text(String.valueOf(affectedRunway.getOriginalRunway().getDisplacedThreshold()) + "m"); TORALabel = new Text(String.valueOf(affectedRunway.getTORA()) + "m"); LDALabel = new Text(String.valueOf(affectedRunway.getLDA()) + "m"); ASDALabel = new Text(String.valueOf(affectedRunway.getASDA()) + "m"); TODALabel = new Text(String.valueOf(affectedRunway.getTODA()) + "m"); obstacleLabel = new Text(" obstacle "); obstacleDistancesLabel = new Text(" obstacle distances "); RESALabel = new Text(String.valueOf(affectedRunway.getOriginalRunway().getRESA()) + "m"); oldtoda = new Text(affectedRunway.getOriginalRunway().getTODA() + "m"); oldtora = new Text(affectedRunway.getOriginalRunway().getTORA() + "m"); oldlda = new Text(affectedRunway.getOriginalRunway().getLDA() + "m"); oldasda = new Text(affectedRunway.getOriginalRunway().getASDA() + "m"); } else { displacedThresholdLabel = new Text("N/A"); TORALabel = new Text("N/A"); LDALabel = new Text("N/A"); ASDALabel = new Text("N/A"); TODALabel = new Text("N/A"); obstacleLabel = new Text(" obstacle "); obstacleDistancesLabel = new Text(" obstacle distances "); RESALabel = new Text("N/A"); oldtoda = new Text("N/A"); oldtora = new Text("N/A"); oldlda = new Text("N/A"); oldasda = new Text("N/A"); // checkboxes ArrayList filtersList = new ArrayList(); displacedThresholdBox = new CheckBox(); TORABox = new CheckBox(); LDABox = new CheckBox(); ASDABox = new CheckBox(); TODABox = new CheckBox(); obstacleBox = new CheckBox(); obstacleDistancesBox = new CheckBox(); RESABox = new CheckBox(); filtersList.add(displacedThresholdBox); filtersList.add(TORABox); filtersList.add(LDABox); filtersList.add(ASDABox); filtersList.add(TODABox); filtersList.add(obstacleBox); filtersList.add(RESABox); for(CheckBox checkBox : filtersList) { checkBox.setSelected(true); checkBox.selectedProperty().addListener( (ObservableValue ov, Boolean oldVal, Boolean newVal) -> { draw(); } ); } } //colors the text with appropriate colors displacedThresholdLabel.setFill(theme.getDisplacedThresholdColor()); TORALabel.setFill(theme.getTORAColor()); oldtora.setFill(theme.getTORAColor()); LDALabel.setFill(theme.getLDAColor()); oldlda.setFill(theme.getLDAColor()); ASDALabel.setFill(theme.getASDAColor()); oldasda.setFill(theme.getASDAColor()); TODALabel.setFill(theme.getTODAColor()); oldtoda.setFill(theme.getTODAColor()); RESALabel.setFill(theme.getRESAColor()); obstacleLabel.setFill(theme.getDangerColor()); // obstacleDistancesLabel.setFill(dangerColor); filtersGridPane.add(typeLabel, 0, 0); filtersGridPane.add(oldLabel, 1, 0); filtersGridPane.add(newLabel, 2, 0); filtersGridPane.add(showLabel, 3, 0); Text TODAText = new Text("TODA:"); TODAText.setFill(theme.getOutlineColor()); filtersGridPane.add(TODAText, 0, 1); Text TORAText = new Text("TORA:"); TORAText.setFill(theme.getOutlineColor()); filtersGridPane.add(TORAText, 0, 2); Text LDAText = new Text("LDA:"); LDAText.setFill(theme.getOutlineColor()); filtersGridPane.add(LDAText, 0, 3); Text ASDAText = new Text("ASDA"); ASDAText.setFill(theme.getOutlineColor()); filtersGridPane.add(ASDAText, 0, 4); Text displacedThresholdText = new Text("Displaced Threshold:"); displacedThresholdText.setFill(theme.getOutlineColor()); filtersGridPane.add(displacedThresholdText, 0, 5); Text RESAText = new Text("RESA:"); RESAText.setFill(theme.getOutlineColor()); filtersGridPane.add(RESAText, 0, 6); Text obstacleText = new Text("obstacle:"); obstacleText.setFill(theme.getOutlineColor()); filtersGridPane.add(obstacleText, 0, 7); filtersGridPane.add(oldtoda, 1, 1); filtersGridPane.add(oldtora, 1, 2); filtersGridPane.add(oldlda, 1, 3); filtersGridPane.add(oldasda, 1, 4); filtersGridPane.add(displacedThresholdLabel, 1, 5); filtersGridPane.add(RESALabel, 1, 6); filtersGridPane.add(TODALabel, 2, 1); filtersGridPane.add(TORALabel, 2, 2); filtersGridPane.add(LDALabel, 2, 3); filtersGridPane.add(ASDALabel, 2, 4); filtersGridPane.add(TODABox, 3, 1); filtersGridPane.add(TORABox, 3, 2); filtersGridPane.add(LDABox, 3, 3); filtersGridPane.add(ASDABox, 3, 4); filtersGridPane.add(displacedThresholdBox, 3, 5); filtersGridPane.add(RESABox, 3, 6); filtersGridPane.add(obstacleBox, 3, 7); Button compass = new Button("Match Bearing Rotation"); compass.setOnAction((event) -> { drawRotated(affectedRunway); }); filtersGridPane.add(compass, 0, 8, 2, 1); Button resetView = new Button("Reset View"); resetView.setOnAction((event) -> { draw(); }); filtersGridPane.add(resetView, 2, 8, 2, 1); Button export = new Button("Export"); export.setOnAction((actionEvent -> { draw(); saveCanvasImage(); })); filtersGridPane.add(export, 3, 8, 1, 1); Button showCalc = new Button("View Calculations"); showCalc.setOnAction((event) -> { Stage stage2 = new Stage(); GridPane calcs = affectedRunway.getCalculationDisplay(); Scene scene2 = new Scene(calcs, 720, 300); stage2.setScene(scene2); stage2.show(); }); filtersGridPane.add(showCalc, 1, 8, 2, 1); // //only for styling purposes filtersGridPaneContainer = new HBox(); filtersGridPaneContainer.getChildren().add(filtersGridPane); filtersGridPaneContainer.setMargin(filtersGridPane, new Insets(10.0, 10.0, 10.0, 10.0)); filtersGridPaneContainer.setBackground(new Background(new BackgroundFill(theme.getHUDColor(), CornerRadii.EMPTY, Insets.EMPTY))); runwayDisplayAnchor.setTopAnchor(filtersGridPane, 0.0); runwayDisplayAnchor.setRightAnchor(filtersGridPaneContainer, 0.0); if (affectedRunway == null || affectedRunway == placeHolderRunway) { //----------VIEW SELECT---------- //changing views viewSelect = new ChoiceBox(); viewSelect.getItems().addAll("Top View", "Side View", new Separator(), "Split View"); viewSelect.setValue("Top View"); viewSelect.setBackground(new Background(new BackgroundFill(Color.LIGHTBLUE, CornerRadii.EMPTY, Insets.EMPTY))); runwayDisplayAnchor.setTopAnchor(viewSelect, 0.0); runwayDisplayAnchor.setLeftAnchor(viewSelect, 0.0); viewSelect.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener(){ @Override public void changed(ObservableValue ov, Number oldSelected, Number newSelected) { //required to avoid duplicates runwayDisplayAnchor.getChildren().clear(); switch (newSelected.intValue()) { case 0: runwayDisplayAnchor.getChildren().addAll(topView, viewSelect, filtersGridPaneContainer); currentView = topViewCanvas; break; case 1: runwayDisplayAnchor.getChildren().addAll(sideView, viewSelect, filtersGridPaneContainer); currentView = sideViewCanvas; break; case 3: splitView.getItems().clear(); setupSplitView(); runwayDisplayAnchor.setTopAnchor(splitView, 0.0); runwayDisplayAnchor.setBottomAnchor(splitView, 0.0); runwayDisplayAnchor.setLeftAnchor(splitView, 0.0); runwayDisplayAnchor.setRightAnchor(splitView, 0.0); splitView.getItems().addAll(topView, sideView); currentView = topViewCanvas; runwayDisplayAnchor.getChildren().addAll(splitView, viewSelect, filtersGridPaneContainer); break; } } }); } } public void drawTopView(int canvasWidth, int canvasHeight, boolean rotate) { topViewCanvas = new Canvas (canvasWidth, canvasHeight); topGc = topViewCanvas.getGraphicsContext2D(); topView = new StackPane(); topView.setAlignment(Pos.CENTER); topView.getChildren().add(topViewCanvas); //--------ZOOMING AND PANNING--------- topViewCanvas.setOnMousePressed(e -> { orgSceneX = e.getSceneX(); //orgSceneY = e.getSceneY(); orgTranslateX = ((Canvas) (e.getSource())).getTranslateX(); //orgTranslateY = ((Canvas)(e.getSource())).getTranslateY(); }); topViewCanvas.setOnMouseDragged(e -> { double offsetX = e.getSceneX() - orgSceneX; //double offsetY = e.getSceneY() - orgSceneY; double newTranslateX = orgTranslateX + offsetX; //double newTranslateY = orgTranslateY + offsetY; ((Canvas) (e.getSource())).setTranslateX(newTranslateX); runwayDisplayAnchor.toBack(); //((Canvas) (e.getSource())).setTranslateY(newTranslateY); }); topView.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); topView.setOnScroll( e -> { if (e.isShortcutDown() && e.getDeltaY() != 0) { if (e.getDeltaY() < 0) { topView.setScaleX(Math.max(topView.getScaleX() - 0.1, 0.5)); } else { topView.setScaleX(Math.min(topView.getScaleX() + 0.1, 5.0)); } topView.setScaleY(topView.getScaleX()); this.runwayDisplayAnchor.toBack(); e.consume(); // prevents ScrollEvent from reaching ScrollPane } }); //-------DRAW CANVAS------- posMargin = 0; negMargin = 0; //main square topGc.setFill(theme.getGrassColor()); topGc.fillRect(0, 0, canvasWidth, canvasHeight); //cleared area topGc.translate(0, canvasHeight / 2); topGc.setFill(theme.getClearedAreaColor()); //keep it so that the clearedAreaWidth = 120p; topGc.beginPath(); topGc.moveTo(0, -60); topGc.lineTo(85, -60); topGc.lineTo(120, -90); topGc.lineTo(canvasWidth - 125, -90); topGc.lineTo(canvasWidth - 85, -60); topGc.lineTo(canvasWidth, -60); topGc.lineTo(canvasWidth, 60); topGc.lineTo(canvasWidth - 85, 60); topGc.lineTo(canvasWidth - 125, 90); topGc.lineTo(120, 90); topGc.lineTo(85, 60); topGc.lineTo(0, 60); topGc.fill(); topGc.closePath(); //draws RESA if the original runway has one(simply leaves a blank space) if (hasRESA()) { posMargin += RESA_MARGIN; } //draws clearway if original runway has one(simply leaves a blank space) if (hasClearway()) { negMargin += CLEARWAY_MARGIN; } topGc.setFill(createHatch(theme.getWarningColor(), theme.getRoadColor())); //draws blast allowance if original runway has one if (hasBlastAllowance()) { topGc.fillRect(posMargin, -25, BLAST_ALLOWANCE_MARGIN, 50); posMargin += BLAST_ALLOWANCE_MARGIN; } //draws stopway if original runway has one if (hasStopway()) { topGc.fillRect(canvasWidth - negMargin - CLEARWAY_MARGIN, -25, CLEARWAY_MARGIN, 50); negMargin += CLEARWAY_MARGIN; } //drawsRunway runwayPL = canvasWidth - (posMargin + negMargin); topGc.setFill(theme.getRoadColor()); topGc.fillRect(posMargin, -20, runwayPL, 40); if(affectedRunway != null) { topGc.setFill(theme.getStripeColor()); topGc.rotate(90); topGc.setTextAlign(TextAlignment.CENTER); topGc.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, 20)); topGc.fillText(affectedRunway.getOriginalRunway().getName(), 0, -100); topGc.rotate(-90); topGc.setFont(new Font(12)); } //threshold // for (int i = 0; i < 3; i ++) { // topGc.setFill(stripeColor); // topGc.fillRect(posMargin + THRESHOLD_MARGIN, -15 + (i * 12), THRESHOLD_LENGTH, 6); // topGc.fillRect(canvasWidth - negMargin - THRESHOLD_LENGTH - THRESHOLD_MARGIN, -15 + (i * 12), THRESHOLD_LENGTH, 6); // } topGc.setStroke(theme.getStripeColor()); //centerline topGc.setLineWidth(3.0); topGc.setLineDashes(6, 8, 1, 8); topGc.strokeLine(posMargin + CENTERLINE_MARGIN, 0, canvasWidth - negMargin - CENTERLINE_MARGIN, 0); //outline topGc.translate(0, -canvasHeight / 2); topGc.setStroke(theme.getOutlineColor()); topGc.setLineWidth(1); topGc.setLineDashes(1); topGc.strokeRect(0, 0, canvasWidth, canvasHeight); topView.setScaleX(1); topView.setScaleY(1); } public void drawSideView(int canvasWidth, int canvasHeight) { sideViewCanvas = new Canvas (canvasWidth, canvasHeight); sideGc = sideViewCanvas.getGraphicsContext2D(); sideViewCanvas.setOnMousePressed(e -> { orgSceneX = e.getSceneX(); //orgSceneY = e.getSceneY(); orgTranslateX = ((Canvas) (e.getSource())).getTranslateX(); //orgTranslateY = ((Canvas)(e.getSource())).getTranslateY(); }); sideViewCanvas.setOnMouseDragged(e -> { double offsetX = e.getSceneX() - orgSceneX; //double offsetY = e.getSceneY() - orgSceneY; double newTranslateX = orgTranslateX + offsetX; //double newTranslateY = orgTranslateY + offsetY; ((Canvas) (e.getSource())).setTranslateX(newTranslateX); runwayDisplayAnchor.toBack(); //((Canvas) (e.getSource())).setTranslateY(newTranslateY); }); sideView = new StackPane(); sideView.setAlignment(Pos.CENTER); sideView.getChildren().add(sideViewCanvas); sideView.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); // OR sideView sideViewCanvas.setOnScroll( e -> { if (e.isShortcutDown() && e.getDeltaY() != 0) { if (e.getDeltaY() < 0) { sideView.setScaleX(Math.max(sideView.getScaleX() - 0.1, 0.5)); } else { sideView.setScaleX(Math.min(sideView.getScaleX() + 0.1, 5.0)); } sideView.setScaleY(sideView.getScaleX()); this.runwayDisplayAnchor.toBack(); e.consume(); // prevents ScrollEvent from reaching ScrollPane } }); //-------DRAW CANVAS------- posMargin = 0; negMargin = 0; sideGc.translate(0, (canvasHeight - SIDE_VIEW_THICKNESS) / 2); //cleared area sideGc.setFill(theme.getClearedAreaColor()); sideGc.fillRect(0, 0, canvasWidth, SIDE_VIEW_THICKNESS); //draws RESA if the original runway has one(simply leaves a blank space) if (hasRESA()) { posMargin += RESA_MARGIN; } //draws clearway if original runway has one(simply leaves a blank space) if (hasClearway()) { negMargin += CLEARWAY_MARGIN; } sideGc.setFill(createHatch(theme.getWarningColor(), theme.getRoadColor())); //draws blast allowance if original runway has one if (hasBlastAllowance()) { sideGc.fillRect(posMargin, 0, BLAST_ALLOWANCE_MARGIN, 10); posMargin += BLAST_ALLOWANCE_MARGIN; } //draws stopway if original runway has one if (hasStopway()) { sideGc.fillRect(canvasWidth - negMargin - CLEARWAY_MARGIN, 0, CLEARWAY_MARGIN, 10); negMargin += CLEARWAY_MARGIN; } //draws runway runwayPL = canvasWidth - (posMargin + negMargin); sideGc.setFill(theme.getRoadColor()); sideGc.fillRect(posMargin, 0, runwayPL, 20); //threshold sideGc.setFill(theme.getStripeColor()); sideGc.fillRect(posMargin + THRESHOLD_MARGIN, 0, THRESHOLD_LENGTH, 8); sideGc.fillRect(canvasWidth - negMargin - THRESHOLD_LENGTH - THRESHOLD_MARGIN, 0, THRESHOLD_LENGTH, 8); //centerline sideGc.setStroke(theme.getStripeColor()); sideGc.setLineWidth(4.0); sideGc.setLineDashes(8, 10, 2, 10); sideGc.strokeLine(posMargin + CENTERLINE_MARGIN, 2, canvasWidth - negMargin - CENTERLINE_MARGIN, 2); sideGc.strokeLine(posMargin + CENTERLINE_MARGIN, 4, canvasWidth - negMargin - CENTERLINE_MARGIN, 4); //outline sideGc.setStroke(theme.getOutlineColor()); sideGc.setLineWidth(1); sideGc.setLineDashes(1); sideGc.strokeRect(0, 0, canvasWidth, SIDE_VIEW_THICKNESS); sideGc.translate(0, -(canvasHeight - SIDE_VIEW_THICKNESS) / 2); sideView.setScaleX(1); sideView.setScaleY(1); } public void setupSplitView() { splitView = new SplitPane(); splitView.setOrientation(Orientation.VERTICAL); splitView.setBackground(new Background(new BackgroundFill(theme.getBackgroundColor(), CornerRadii.EMPTY, Insets.EMPTY))); } //taken from https://stackoverflow.com/questions/39297719/javafx-create-hatchstyles-something-like-of-c-sharp-net private ImagePattern createHatch(Color hashColor, Color backColor) { Canvas canvas = new Canvas(20, 20); GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setFill(backColor); gc.fillRect(0, 0, 20, 20); gc.setStroke(hashColor); gc.setLineWidth(3.0); gc.strokeLine(-5, -5, 25, 25); gc.strokeLine(-5, 25, 25, -5); Image snapshot = canvas.snapshot(null, null); ImagePattern imagePattern = new ImagePattern(snapshot, 0, 0, 20, 20, false); return imagePattern; } private ImagePattern createGrid(Color gridColor, Color backColor) { Canvas canvas = new Canvas(10, 10); GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setFill(backColor); gc.fillRect(0, 0, 10, 10); gc.setFill(gridColor); gc.fillRect(0, 0, 5, 5); gc.fillRect(5, 5, 5, 5); Image snapshot = canvas.snapshot(null, null); ImagePattern imagePattern = new ImagePattern(snapshot, 0, 0, 10, 10, false); return imagePattern; } public void changeTheme(Theme theme) { this.theme = theme; draw(); } public Theme getTheme() { return theme; } //important boolean functions boolean hasClearway() { return (affectedRunway.getOriginalRunway().getClearway() != 0); } boolean hasStopway() { return (affectedRunway.getOriginalRunway().getStopway() != 0); } boolean hasBlastAllowance() { return (affectedRunway.getOriginalRunway().getBlastAllowance() != 0); } boolean hasRESA() { return (affectedRunway.getOriginalRunway().getRESA() != 0); } }