commit
3ec87582d3
@ -0,0 +1,152 @@
|
|||||||
|
package visualiser.Controllers;
|
||||||
|
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
import shared.model.Wind;
|
||||||
|
import visualiser.model.VisualiserRace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the arrow.fxml view.
|
||||||
|
*/
|
||||||
|
public class ArrowController {
|
||||||
|
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Pane compass;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private StackPane arrowStackPane;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ImageView arrowImage;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Circle circle;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label northLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label windLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label speedLabel;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the property our arrow control binds to.
|
||||||
|
*/
|
||||||
|
private Property<Wind> wind;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public ArrowController() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets which wind property the arrow control should bind to.
|
||||||
|
* @param wind The wind property to bind to.
|
||||||
|
*/
|
||||||
|
public void setWindProperty(Property<Wind> wind) {
|
||||||
|
this.wind = wind;
|
||||||
|
|
||||||
|
wind.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue != null) {
|
||||||
|
Platform.runLater(() -> updateWind(newValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the control to use the new wind value.
|
||||||
|
* This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed).
|
||||||
|
* @param wind The wind value to use.
|
||||||
|
*/
|
||||||
|
private void updateWind(Wind wind) {
|
||||||
|
updateWindBearing(wind.getWindDirection());
|
||||||
|
updateWindSpeed(wind.getWindSpeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the control to account for the new wind speed.
|
||||||
|
* This changes the length (height) of the wind arrow, and updates the speed label.
|
||||||
|
* @param speedKnots The new wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private void updateWindSpeed(double speedKnots) {
|
||||||
|
updateWindArrowLength(speedKnots);
|
||||||
|
updateWindSpeedLabel(speedKnots);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the length of the wind arrow according to the specified wind speed.
|
||||||
|
* @param speedKnots Wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private void updateWindArrowLength(double speedKnots) {
|
||||||
|
|
||||||
|
//At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height.
|
||||||
|
double minKnots = 2;
|
||||||
|
double maxKnots = 30;
|
||||||
|
double deltaKnots = maxKnots - minKnots;
|
||||||
|
|
||||||
|
double minHeight = 25;
|
||||||
|
double maxHeight = 75;
|
||||||
|
double deltaHeight = maxHeight - minHeight;
|
||||||
|
|
||||||
|
//Clamp speed.
|
||||||
|
if (speedKnots > maxKnots) {
|
||||||
|
speedKnots = maxKnots;
|
||||||
|
} else if (speedKnots < minKnots) {
|
||||||
|
speedKnots = minKnots;
|
||||||
|
}
|
||||||
|
|
||||||
|
//How far between the knots bounds is the current speed?
|
||||||
|
double currentDeltaKnots = speedKnots - minKnots;
|
||||||
|
double currentKnotsScalar = currentDeltaKnots / deltaKnots;
|
||||||
|
|
||||||
|
//Thus, how far between the pixel height bounds should the arrow height be?
|
||||||
|
double newHeight = minHeight + (currentKnotsScalar * deltaHeight);
|
||||||
|
|
||||||
|
arrowImage.setFitHeight(newHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the wind speed label according to the specified wind speed.
|
||||||
|
* @param speedKnots Wind speed, in knots.
|
||||||
|
*/
|
||||||
|
private void updateWindSpeedLabel(double speedKnots) {
|
||||||
|
speedLabel.setText(String.format("%.1fkn", speedKnots));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the control to account for a new wind bearing.
|
||||||
|
* This rotates the arrow according to the bearing.
|
||||||
|
* @param bearing The bearing to use to rotate arrow.
|
||||||
|
*/
|
||||||
|
private void updateWindBearing(Bearing bearing) {
|
||||||
|
|
||||||
|
//We need to display wind-from, so add 180 degrees.
|
||||||
|
Bearing fromBearing = Bearing.fromDegrees(bearing.degrees() + 180d);
|
||||||
|
|
||||||
|
//Rotate the wind arrow.
|
||||||
|
arrowStackPane.setRotate(fromBearing.degrees());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 23 KiB |
@ -1,34 +1,58 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.paint.*?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.text.*?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.shape.*?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.image.*?>
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
<?import java.lang.*?>
|
<?import javafx.scene.layout.GridPane?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.Pane?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<Pane fx:id="compass" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="125.0" prefWidth="125.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
|
||||||
|
<GridPane fx:id="arrowGridPane" maxHeight="-Infinity" maxWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.ArrowController">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<StackPane fx:id="arrow" prefHeight="125.0" prefWidth="125.0">
|
<Pane fx:id="compass" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="125.0" prefWidth="125.0">
|
||||||
<children>
|
<children>
|
||||||
<ImageView fitHeight="75.0" fitWidth="75.0">
|
<StackPane fx:id="arrowStackPane" prefHeight="125.0" prefWidth="125.0">
|
||||||
<image>
|
<children>
|
||||||
<Image url="@../images/arrow.png" />
|
<ImageView fx:id="arrowImage" fitHeight="75.0" fitWidth="75.0">
|
||||||
</image>
|
<image>
|
||||||
</ImageView>
|
<Image url="@../images/arrow.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</children>
|
||||||
|
</StackPane>
|
||||||
|
<Circle fx:id="circle" fill="#1f93ff00" layoutX="63.0" layoutY="63.0" radius="60.0" stroke="BLACK" strokeType="INSIDE" strokeWidth="3.0" />
|
||||||
|
<Label fx:id="northLabel" layoutX="55.0" layoutY="1.0" text="N">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="18.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="windLabel" layoutX="42.0" layoutY="95.0" text="Wind">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
</children>
|
</children>
|
||||||
</StackPane>
|
</Pane>
|
||||||
<Circle fill="#1f93ff00" layoutX="63.0" layoutY="63.0" radius="60.0" stroke="BLACK" strokeType="INSIDE" strokeWidth="3.0" />
|
<Label fx:id="speedLabel" text="SPEED" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="1">
|
||||||
<Label layoutX="55.0" layoutY="1.0" text="N">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="18.0" />
|
|
||||||
</font>
|
|
||||||
</Label>
|
|
||||||
<Label layoutX="42.0" layoutY="99.0" text="Wind">
|
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="16.0" />
|
<Font name="System Bold" size="16.0" />
|
||||||
</font>
|
</font>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets />
|
||||||
|
</GridPane.margin>
|
||||||
</Label>
|
</Label>
|
||||||
</children>
|
</children>
|
||||||
</Pane>
|
</GridPane>
|
||||||
|
|||||||
@ -0,0 +1,111 @@
|
|||||||
|
package mock.model;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import shared.model.Bearing;
|
||||||
|
import shared.model.Wind;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class WindGeneratorTest {
|
||||||
|
|
||||||
|
|
||||||
|
private WindGenerator windGenerator;
|
||||||
|
|
||||||
|
private Bearing windBaselineBearing;
|
||||||
|
private Bearing windBearingLowerBound;
|
||||||
|
private Bearing windBearingUpperBound;
|
||||||
|
private double windBaselineSpeed;
|
||||||
|
private double windSpeedLowerBound;
|
||||||
|
private double windSpeedUpperBound;
|
||||||
|
|
||||||
|
private double speedKnotsEpsilon;
|
||||||
|
private double bearingDegreeEpsilon;
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
|
|
||||||
|
//Bounds.
|
||||||
|
this.windBaselineBearing = Bearing.fromDegrees(88.3);
|
||||||
|
this.windBearingLowerBound = Bearing.fromDegrees(66.5);
|
||||||
|
this.windBearingUpperBound = Bearing.fromDegrees(248.6);
|
||||||
|
this.windBaselineSpeed = 13;
|
||||||
|
this.windSpeedLowerBound = 7;
|
||||||
|
this.windSpeedUpperBound = 20;
|
||||||
|
|
||||||
|
this.windGenerator = new WindGenerator(
|
||||||
|
windBaselineBearing,
|
||||||
|
windBearingLowerBound,
|
||||||
|
windBearingUpperBound,
|
||||||
|
windBaselineSpeed,
|
||||||
|
windSpeedLowerBound,
|
||||||
|
windSpeedUpperBound );
|
||||||
|
|
||||||
|
this.bearingDegreeEpsilon = 0.001;
|
||||||
|
this.speedKnotsEpsilon = 0.001;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the baseline wind generated it accurate.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void generateBaselineWindTest() {
|
||||||
|
|
||||||
|
Wind wind = windGenerator.generateBaselineWind();
|
||||||
|
|
||||||
|
assertEquals(windBaselineSpeed, wind.getWindSpeed(), speedKnotsEpsilon);
|
||||||
|
assertEquals(windBaselineBearing.degrees(), wind.getWindDirection().degrees(), bearingDegreeEpsilon);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the random wind generated is inside the bounds.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void generateRandomWindTest() {
|
||||||
|
|
||||||
|
int randomWindCount = 1000;
|
||||||
|
|
||||||
|
for (int i = 0; i < randomWindCount; i++) {
|
||||||
|
|
||||||
|
Wind wind = windGenerator.generateRandomWind();
|
||||||
|
|
||||||
|
assertTrue(wind.getWindSpeed() >= windSpeedLowerBound);
|
||||||
|
assertTrue(wind.getWindSpeed() <= windSpeedUpperBound);
|
||||||
|
|
||||||
|
assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees());
|
||||||
|
assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the next wind generated is inside the bounds.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void generateNextWindTest() {
|
||||||
|
|
||||||
|
Wind wind = windGenerator.generateBaselineWind();
|
||||||
|
|
||||||
|
int randomWindCount = 1000;
|
||||||
|
|
||||||
|
for (int i = 0; i < randomWindCount; i++) {
|
||||||
|
|
||||||
|
wind = windGenerator.generateNextWind(wind);
|
||||||
|
|
||||||
|
assertTrue(wind.getWindSpeed() >= windSpeedLowerBound);
|
||||||
|
assertTrue(wind.getWindSpeed() <= windSpeedUpperBound);
|
||||||
|
|
||||||
|
assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees());
|
||||||
|
assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue