Commit f39d90e6068c17e06645704f2f102d47aa985a59
1 parent
f15a589072
Exists in
master
and in
1 other branch
Select/deselect MMSI
Showing 14 changed files with 76 additions and 120 deletions Inline Diff
- src/main/kotlin/application/App.kt
- src/main/kotlin/application/Logger.kt
- src/main/kotlin/application/VisualisationChart.kt
- src/main/kotlin/application/controller/DataPanelController.kt
- src/main/kotlin/application/controller/MapPanelController.kt
- src/main/kotlin/application/controller/VesselListPanelController.kt
- src/main/kotlin/application/model/Message.kt
- src/main/kotlin/application/model/MessageData.kt
- src/main/kotlin/application/model/MessageListener.kt
- src/main/kotlin/application/model/ObservableVessel.kt
- src/main/kotlin/application/model/Vessel.kt
- src/main/kotlin/application/model/VesselGenerator.kt
- src/main/kotlin/map/MapDisplayer.kt
- src/main/resources/gui/vesselListPanel.fxml
src/main/kotlin/application/App.kt
View file @
f39d90e
| 1 | @file:JvmName("App") | |||
| 2 | ||||
| package application | 1 | 3 | package application | |
| 2 | 4 | |||
| import javafx.application.Application | 3 | 5 | import javafx.application.Application | |
| import javafx.application.Platform | 4 | 6 | import javafx.application.Platform | |
| import javafx.event.EventHandler | 5 | 7 | import javafx.event.EventHandler | |
| import javafx.fxml.FXMLLoader | 6 | 8 | import javafx.fxml.FXMLLoader | |
| import javafx.scene.Parent | 7 | 9 | import javafx.scene.Parent | |
| import javafx.scene.Scene | 8 | 10 | import javafx.scene.Scene | |
| import javafx.stage.Stage | 9 | 11 | import javafx.stage.Stage | |
| import javafx.stage.WindowEvent | 10 | |||
| import jfxtras.styles.jmetro.JMetro | 11 | 12 | import jfxtras.styles.jmetro.JMetro | |
| import jfxtras.styles.jmetro.Style | 12 | 13 | import jfxtras.styles.jmetro.Style | |
| import kotlin.system.exitProcess | 13 | 14 | import kotlin.system.exitProcess | |
| 14 | 15 | |||
| class App : Application() { | 15 | 16 | class App : Application() { | |
| var style : Style = Style.LIGHT | 16 | 17 | var style: Style = Style.LIGHT | |
| 17 | 18 | |||
| override fun start(primaryStage: Stage?) { | 18 | 19 | override fun start(primaryStage: Stage?) { | |
| 19 | 20 | |||
| val fxmlLoader = FXMLLoader(App::class.java.getResource("/gui/windows.fxml")) | 20 | 21 | val fxmlLoader = FXMLLoader(App::class.java.getResource("/gui/windows.fxml")) | |
| val parent: Parent = fxmlLoader.load() | 21 | 22 | val parent: Parent = fxmlLoader.load() | |
| val scene = Scene(parent) | 22 | 23 | val scene = Scene(parent) | |
| JMetro(scene, style) | 23 | 24 | JMetro(scene, style) | |
| primaryStage!!.scene = scene | 24 | 25 | primaryStage!!.scene = scene | |
| primaryStage.title = "Maritime Visualisation" | 25 | 26 | primaryStage.title = "Maritime Visualisation" | |
| primaryStage.onCloseRequest = EventHandler { closeApplication() } | 26 | 27 | primaryStage.onCloseRequest = EventHandler { closeApplication() } | |
| primaryStage.show() | 27 | 28 | primaryStage.show() | |
| } | 28 | 29 | } | |
| 29 | 30 | |||
| private fun closeApplication() { | 30 | 31 | private fun closeApplication() { | |
| Platform.exit() | 31 | 32 | Platform.exit() | |
| exitProcess(0) | 32 | 33 | exitProcess(0) | |
| } | 33 | 34 | } | |
| 34 | 35 | |||
| companion object { | 35 | 36 | companion object { | |
| @JvmStatic | 36 | 37 | @JvmStatic |
src/main/kotlin/application/Logger.kt
View file @
f39d90e
| package application | 1 | File was deleted | ||
| 2 | ||||
| import org.slf4j.Logger | 3 | |||
| import org.slf4j.LoggerFactory | 4 |
src/main/kotlin/application/VisualisationChart.kt
View file @
f39d90e
| package application | 1 | File was deleted | ||
| 2 | ||||
| import javafx.scene.chart.Axis | 3 | |||
| import javafx.scene.chart.ScatterChart | 4 | |||
| 5 |
src/main/kotlin/application/controller/DataPanelController.kt
View file @
f39d90e
| package application.controller | 1 | 1 | package application.controller | |
| 2 | 2 | |||
| import application.model.* | 3 | 3 | import application.model.* | |
| import javafx.collections.FXCollections | 4 | 4 | import javafx.collections.FXCollections | |
| import javafx.collections.ObservableList | 5 | 5 | import javafx.collections.ObservableList | |
| import javafx.fxml.FXML | 6 | 6 | import javafx.fxml.FXML | |
| import javafx.fxml.Initializable | 7 | 7 | import javafx.fxml.Initializable | |
| import javafx.scene.control.ListCell | 8 | 8 | import javafx.scene.control.ListCell | |
| import javafx.scene.control.ListView | 9 | 9 | import javafx.scene.control.ListView | |
| import javafx.scene.layout.StackPane | 10 | |||
| import org.charts.dataviewer.api.config.DataViewerConfiguration | 11 | 10 | import org.charts.dataviewer.api.config.DataViewerConfiguration | |
| import org.charts.dataviewer.api.data.PlotData | 12 | 11 | import org.charts.dataviewer.api.data.PlotData | |
| import org.charts.dataviewer.api.trace.LineTrace | 13 | |||
| import org.charts.dataviewer.api.trace.ScatterTrace | 14 | 12 | import org.charts.dataviewer.api.trace.ScatterTrace | |
| import org.charts.dataviewer.javafx.JavaFxDataViewer | 15 | 13 | import org.charts.dataviewer.javafx.JavaFxDataViewer | |
| import org.charts.dataviewer.utils.TraceColour | 16 | 14 | import org.charts.dataviewer.utils.TraceColour | |
| import org.charts.dataviewer.utils.TraceMode | 17 | |||
| import org.charts.dataviewer.utils.TraceVisibility | 18 | 15 | import org.charts.dataviewer.utils.TraceVisibility | |
| import java.net.URL | 19 | 16 | import java.net.URL | |
| import java.util.* | 20 | 17 | import java.util.* | |
| 21 | 18 | |||
| 22 | 19 | |||
| class DataPanelController : Initializable, SelectedVesselListener { | 23 | 20 | class DataPanelController : Initializable, SelectedVesselListener { | |
| private var dataList: ObservableList<Pair<String, ArrayList<MessageData?>>> = FXCollections.observableArrayList() | 24 | 21 | private var dataList: ObservableList<Pair<String, ArrayList<MessageData?>>> = FXCollections.observableArrayList() | |
| private lateinit var timeData: ArrayList<MessageData?> | 25 | 22 | private lateinit var timeData: ArrayList<MessageData?> | |
| 26 | 23 | |||
| 27 | 24 | |||
| @FXML | 28 | 25 | @FXML | |
| var dataListView = ListView<Pair<String, ArrayList<MessageData?>>>() | 29 | 26 | var dataListView = ListView<Pair<String, ArrayList<MessageData?>>>() | |
| 30 | 27 | |||
| // @FXML | 31 | |||
| // var stackPanePlot = StackPane() | 32 | |||
| 33 | ||||
| @FXML | 34 | 28 | @FXML | |
| var dataViewer = JavaFxDataViewer() | 35 | 29 | var dataViewer = JavaFxDataViewer() | |
| 36 | 30 | |||
| // @FXML | 37 | |||
| // var xaxisNumber: CategoryAxis = CategoryAxis() | 38 | |||
| // | 39 | |||
| // @FXML | 40 | |||
| // var yaxisNumber: NumberAxis = NumberAxis() | 41 | |||
| // | 42 | |||
| // @FXML | 43 | |||
| // lateinit var scatterChartNumber: ScatterChart<String, Number> | 44 | |||
| // | 45 | |||
| // @FXML | 46 | |||
| // var xaxisCategory: CategoryAxis = CategoryAxis() | 47 | |||
| // | 48 | |||
| // @FXML | 49 | |||
| // var yaxisCategory: CategoryAxis = CategoryAxis() | 50 | |||
| // | 51 | |||
| // @FXML | 52 | |||
| // lateinit var scatterChartCategory: ScatterChart<String, String> | 53 | |||
| 54 | 31 | |||
| override fun initialize(location: URL?, resources: ResourceBundle?) { | 55 | 32 | override fun initialize(location: URL?, resources: ResourceBundle?) { | |
| // xaxisNumber.animated = false | 56 | |||
| // yaxisNumber.animated = false | 57 | |||
| // xaxisCategory.animated = false | 58 | |||
| // yaxisCategory.animated = false | 59 | |||
| setObservableSelectedVesselListener() | 60 | 33 | setObservableSelectedVesselListener() | |
| dataListView.items = dataList | 61 | 34 | dataListView.items = dataList | |
| 62 | 35 | |||
| 63 | 36 | |||
| val plotData = PlotData() | 64 | 37 | val plotData = PlotData() | |
| plotData.addTrace(createScatterTrace()) | 65 | |||
| val config = DataViewerConfiguration() | 66 | 38 | val config = DataViewerConfiguration() | |
| config.showLegend(true) | 67 | 39 | config.showLegend(true) | |
| config.setLegendInsidePlot(false) | 68 | 40 | config.setLegendInsidePlot(false) | |
| 69 | 41 | |||
| dataListView.setCellFactory { | 70 | 42 | dataListView.setCellFactory { | |
| object : ListCell<Pair<String, ArrayList<MessageData?>>?>() { | 71 | 43 | object : ListCell<Pair<String, ArrayList<MessageData?>>?>() { | |
| override fun updateItem(item: Pair<String, ArrayList<MessageData?>>?, empty: Boolean) { | 72 | 44 | override fun updateItem(item: Pair<String, ArrayList<MessageData?>>?, empty: Boolean) { | |
| super.updateItem(item, empty) | 73 | 45 | super.updateItem(item, empty) | |
| text = if (empty) { | 74 | 46 | text = if (empty) { | |
| null | 75 | 47 | null | |
| } else { | 76 | 48 | } else { | |
| item?.first | 77 | 49 | item?.first | |
| } | 78 | 50 | } | |
| } | 79 | 51 | } | |
| } | 80 | 52 | } | |
| } | 81 | 53 | } | |
| 82 | 54 | |||
| dataListView.selectionModel.selectedItemProperty().addListener { _, _, newValue -> | 83 | 55 | dataListView.selectionModel.selectedItemProperty().addListener { _, _, newValue -> | |
| if (newValue == null) { | 84 | 56 | if (newValue == null) { | |
| plotData.allTraces.clear() | 85 | 57 | plotData.allTraces.clear() | |
| config.setxAxisTitle("") | 86 | 58 | config.setxAxisTitle("") | |
| config.setyAxisTitle("") | 87 | 59 | config.setyAxisTitle("") | |
| 60 | dataViewer.updateConfiguration(config) | |||
| 61 | ||||
| dataViewer.resetPlot() | 88 | 62 | dataViewer.resetPlot() | |
| 89 | 63 | |||
| return@addListener | 90 | 64 | return@addListener | |
| } | 91 | 65 | } | |
| 92 | 66 | |||
| val getValueVisitorX = GetValueVisitor() | 93 | 67 | val getValueVisitorX = GetValueVisitor() | |
| val getValueVisitorY = GetValueVisitor() | 94 | 68 | val getValueVisitorY = GetValueVisitor() | |
| 95 | 69 | |||
| val arrayListStringX = arrayListOf<String>() | 96 | 70 | val arrayListStringX = arrayListOf<String>() | |
| val arrayListDoubleX = arrayListOf<Double>() | 97 | 71 | val arrayListDoubleX = arrayListOf<Double>() | |
| val arrayListStringY = arrayListOf<String>() | 98 | 72 | val arrayListStringY = arrayListOf<String>() | |
| val arrayListDoubleY = arrayListOf<Double>() | 99 | 73 | val arrayListDoubleY = arrayListOf<Double>() | |
| 100 | 74 | |||
| for (x in 0 until newValue.second.size) { | 101 | 75 | for (x in 0 until newValue.second.size) { | |
| timeData[x]?.accept(getValueVisitorX) | 102 | 76 | timeData[x]?.accept(getValueVisitorX) | |
| newValue.second[x]?.accept(getValueVisitorY) | 103 | 77 | newValue.second[x]?.accept(getValueVisitorY) | |
| 104 | 78 | |||
| if (getValueVisitorY.value.toDoubleOrNull() == null) { | 105 | 79 | if (getValueVisitorY.value.toDoubleOrNull() == null) { | |
| arrayListStringX.add(getValueVisitorX.value) | 106 | 80 | arrayListStringX.add(getValueVisitorX.value) | |
| arrayListStringY.add(getValueVisitorY.value) | 107 | 81 | arrayListStringY.add(getValueVisitorY.value) | |
| } else { | 108 | 82 | } else { | |
| arrayListStringX.add(getValueVisitorX.value) | 109 | 83 | arrayListStringX.add(getValueVisitorX.value) | |
| arrayListDoubleY.add(getValueVisitorY.value.toDouble()) | 110 | 84 | arrayListDoubleY.add(getValueVisitorY.value.toDouble()) | |
| } | 111 | 85 | } | |
| } | 112 | 86 | } | |
| 113 | 87 | |||
| val scatterTrace = ScatterTrace<Any>() | 114 | 88 | val scatterTrace = ScatterTrace<Any>() | |
| scatterTrace.traceColour = TraceColour.RED | 115 | 89 | scatterTrace.traceColour = TraceColour.RED | |
| scatterTrace.traceVisibility = TraceVisibility.TRUE | 116 | 90 | scatterTrace.traceVisibility = TraceVisibility.TRUE | |
| 117 | 91 | |||
| val serieStringX: Array<String> = arrayListStringX.toTypedArray() | 118 | 92 | val serieStringX: Array<String> = arrayListStringX.toTypedArray() | |
| // val serieDoubleX: Array<Double> = arrayListDoubleX.toTypedArray() | 119 | 93 | // val serieDoubleX: Array<Double> = arrayListDoubleX.toTypedArray() | |
| val serieStringY: Array<String> = arrayListStringY.toTypedArray() | 120 | 94 | val serieStringY: Array<String> = arrayListStringY.toTypedArray() | |
| val serieDoubleY: Array<Double> = arrayListDoubleY.toTypedArray() | 121 | 95 | val serieDoubleY: Array<Double> = arrayListDoubleY.toTypedArray() | |
| 122 | 96 | |||
| if (getValueVisitorY.value.toDoubleOrNull() == null) { | 123 | 97 | if (getValueVisitorY.value.toDoubleOrNull() == null) { | |
| scatterTrace.setxArray(serieStringX) | 124 | 98 | scatterTrace.setxArray(serieStringX) | |
| scatterTrace.setyArray(serieStringY) | 125 | 99 | scatterTrace.setyArray(serieStringY) | |
| } else { | 126 | 100 | } else { | |
| scatterTrace.setxArray(serieStringX) | 127 | 101 | scatterTrace.setxArray(serieStringX) | |
| scatterTrace.setyArray(serieDoubleY) | 128 | 102 | scatterTrace.setyArray(serieDoubleY) | |
| } | 129 | 103 | } | |
| 130 | 104 | |||
| config.setxAxisTitle("Date") | 131 | 105 | config.setxAxisTitle("Date") | |
| config.setyAxisTitle(newValue.first) | 132 | 106 | config.setyAxisTitle(newValue.first) | |
| dataViewer.resetPlot() | 133 | 107 | dataViewer.resetPlot() | |
| plotData.allTraces.clear() | 134 | 108 | plotData.allTraces.clear() | |
| plotData.addTrace(scatterTrace) | 135 | 109 | plotData.addTrace(scatterTrace) | |
| dataViewer.updateConfiguration(config) | 136 | 110 | dataViewer.updateConfiguration(config) | |
| dataViewer.updatePlot(plotData) | 137 | 111 | dataViewer.updatePlot(plotData) | |
| 138 | 112 | |||
| } | 139 | 113 | } | |
| 140 | 114 | |||
| plotData.allTraces.clear() | 141 | 115 | plotData.allTraces.clear() | |
| // plotData.addTrace(createScatterTrace()) | 142 | |||
| config.setxAxisTitle("") | 143 | 116 | config.setxAxisTitle("") | |
| config.setyAxisTitle("") | 144 | 117 | config.setyAxisTitle("") | |
| config.plotTitle = "No data selected" | 145 | |||
| dataViewer.updateConfiguration(config) | 146 | 118 | dataViewer.updateConfiguration(config) | |
| dataViewer.updatePlot(plotData) | 147 | 119 | dataViewer.updatePlot(plotData) | |
| 148 | 120 | |||
| // stackPanePlot.children.add(dataViewer) | 149 | |||
| 150 | ||||
| } | 151 | 121 | } | |
| 152 | ||||
| private fun createScatterTrace(): ScatterTrace<*>? { | 153 | |||
| val scatterTrace = ScatterTrace<Any>() | 154 | |||
| scatterTrace.setxArray(arrayOf("asdf", "fdsa", "asdfffe", "asdfe", "asfee3")) | 155 | |||
| scatterTrace.setyArray(arrayOf("pppp", "koojoi", "pp", "ii", "rty", "ert")) | 156 | |||
| scatterTrace.traceName = "MyScatterTrace" | 157 | |||
| scatterTrace.traceColour = TraceColour.PURPLE | 158 | |||
| scatterTrace.traceMode = TraceMode.MARKERS | 159 | |||
| return scatterTrace | 160 | |||
| } | 161 | |||
| 162 | ||||
| fun createLineTrace(): LineTrace<*>? { | 163 | |||
| val lineTrace = LineTrace<Any>() | 164 | |||
| lineTrace.setxArray(arrayOf("asdf", "fdsa", "asdfffe", "asdfe", "asfee3")) | 165 | |||
| lineTrace.setyArray(arrayOf(0.0, 1.0, 2.0, 3.0, 4.0, 5.0)) | 166 | |||
| lineTrace.traceName = "MyLineTrace" | 167 | |||
| lineTrace.traceColour = TraceColour.PURPLE | 168 | |||
| return lineTrace | 169 | |||
| } | 170 | |||
| 171 | ||||
| // private fun setChartCategoryVisible() { | 172 | |||
| // scatterChartCategory.isVisible = true | 173 | |||
| // scatterChartNumber.isVisible = false | 174 | |||
| // } | 175 | |||
| // | 176 | |||
| // private fun setChartNumberVisible() { | 177 | |||
| // scatterChartCategory.isVisible = false | 178 | |||
| // scatterChartNumber.isVisible = true | 179 | |||
| // } | 180 | |||
| 181 | 122 | |||
| private fun setObservableSelectedVesselListener() { | 182 | 123 | private fun setObservableSelectedVesselListener() { | |
| observableSelectedVessel.listeners.add(this) | 183 | 124 | observableSelectedVessel.listeners.add(this) | |
| } | 184 | 125 | } | |
| 185 | 126 | |||
| private fun populateTime(vessel: Vessel): ArrayList<MessageData?> { | 186 | 127 | private fun populateTime(vessel: Vessel): ArrayList<MessageData?> { | |
| val allTime: ArrayList<MessageData?> = vessel.getAllTime() | 187 | 128 | val allTime: ArrayList<MessageData?> = vessel.getAllTime() | |
| allTime.sortBy { (it as Time).value } | 188 | 129 | allTime.sortBy { (it as Time).value } | |
| 189 | 130 | |||
| return allTime | 190 | 131 | return allTime | |
| } | 191 | 132 | } | |
| 192 | 133 | |||
| private fun populateLatitude(vessel: Vessel): ArrayList<MessageData?> { | 193 | 134 | private fun populateLatitude(vessel: Vessel): ArrayList<MessageData?> { | |
| val allLatitude: ArrayList<MessageData?> = vessel.getAllLatitude() | 194 | 135 | val allLatitude: ArrayList<MessageData?> = vessel.getAllLatitude() | |
| allLatitude.sortBy { (it as Latitude).value } | 195 | 136 | allLatitude.sortBy { (it as Latitude).value } | |
| 196 | 137 | |||
| return allLatitude | 197 | 138 | return allLatitude | |
| } | 198 | 139 | } | |
| 199 | 140 | |||
| private fun populateLongitude(vessel: Vessel): ArrayList<MessageData?> { | 200 | 141 | private fun populateLongitude(vessel: Vessel): ArrayList<MessageData?> { | |
| val allLongitude: ArrayList<MessageData?> = vessel.getAllLongitude() | 201 | 142 | val allLongitude: ArrayList<MessageData?> = vessel.getAllLongitude() | |
| allLongitude.sortBy { (it as Longitude).value } | 202 | 143 | allLongitude.sortBy { (it as Longitude).value } | |
| 203 | 144 | |||
| return allLongitude | 204 | 145 | return allLongitude | |
| } | 205 | 146 | } | |
| 206 | 147 | |||
| private fun populateSpeedOverGround(vessel: Vessel): ArrayList<MessageData?> { | 207 | 148 | private fun populateSpeedOverGround(vessel: Vessel): ArrayList<MessageData?> { | |
| val allSpeedOverGround: ArrayList<MessageData?> = vessel.getAllSpeedOverGround() | 208 | 149 | val allSpeedOverGround: ArrayList<MessageData?> = vessel.getAllSpeedOverGround() | |
| allSpeedOverGround.sortBy { (it as SpeedOverGround).value } | 209 | 150 | allSpeedOverGround.sortBy { (it as SpeedOverGround).value } | |
| 210 | 151 | |||
| return allSpeedOverGround | 211 | 152 | return allSpeedOverGround | |
| } | 212 | 153 | } | |
| 213 | 154 | |||
| private fun populateCourseOverGround(vessel: Vessel): ArrayList<MessageData?> { | 214 | 155 | private fun populateCourseOverGround(vessel: Vessel): ArrayList<MessageData?> { | |
| val allCourseOverGround: ArrayList<MessageData?> = vessel.getAllCourseOverGround() | 215 | 156 | val allCourseOverGround: ArrayList<MessageData?> = vessel.getAllCourseOverGround() | |
| allCourseOverGround.sortBy { (it as CourseOverGround).value } | 216 | 157 | allCourseOverGround.sortBy { (it as CourseOverGround).value } | |
| 217 | 158 | |||
| return allCourseOverGround | 218 | 159 | return allCourseOverGround | |
| } | 219 | 160 | } | |
| 220 | 161 | |||
| private fun populateHeading(vessel: Vessel): ArrayList<MessageData?> { | 221 | 162 | private fun populateHeading(vessel: Vessel): ArrayList<MessageData?> { | |
| val allHeading: ArrayList<MessageData?> = vessel.getAllHeading() | 222 | 163 | val allHeading: ArrayList<MessageData?> = vessel.getAllHeading() | |
| allHeading.sortBy { (it as Heading).value } | 223 | 164 | allHeading.sortBy { (it as Heading).value } | |
| 224 | 165 | |||
| return allHeading | 225 | 166 | return allHeading | |
| } | 226 | 167 | } | |
| 227 | 168 | |||
| private fun populateVesselName(vessel: Vessel): ArrayList<MessageData?> { | 228 | 169 | private fun populateVesselName(vessel: Vessel): ArrayList<MessageData?> { | |
| val allVesselName: ArrayList<MessageData?> = vessel.getAllVesselName() | 229 | 170 | val allVesselName: ArrayList<MessageData?> = vessel.getAllVesselName() | |
| allVesselName.sortBy { (it as VesselName).value } | 230 | 171 | allVesselName.sortBy { (it as VesselName).value } | |
| 231 | 172 | |||
| return allVesselName | 232 | 173 | return allVesselName | |
| } | 233 | 174 | } | |
| 234 | 175 | |||
| private fun populateIMO(vessel: Vessel): ArrayList<MessageData?> { | 235 | 176 | private fun populateIMO(vessel: Vessel): ArrayList<MessageData?> { | |
| val allIMO: ArrayList<MessageData?> = vessel.getAllIMO() | 236 | 177 | val allIMO: ArrayList<MessageData?> = vessel.getAllIMO() | |
| allIMO.sortBy { (it as IMO).value } | 237 | 178 | allIMO.sortBy { (it as IMO).value } | |
| 238 | 179 | |||
| return allIMO | 239 | 180 | return allIMO | |
| } | 240 | 181 | } | |
| 241 | 182 | |||
| private fun populateCallSign(vessel: Vessel): ArrayList<MessageData?> { | 242 | 183 | private fun populateCallSign(vessel: Vessel): ArrayList<MessageData?> { | |
| val allCallSign: ArrayList<MessageData?> = vessel.getAllCallSign() | 243 | 184 | val allCallSign: ArrayList<MessageData?> = vessel.getAllCallSign() | |
| allCallSign.sortBy { (it as CallSign).value } | 244 | 185 | allCallSign.sortBy { (it as CallSign).value } | |
| 245 | 186 | |||
| return allCallSign | 246 | 187 | return allCallSign | |
| } | 247 | 188 | } | |
| 248 | 189 | |||
| private fun populateVesselType(vessel: Vessel): ArrayList<MessageData?> { | 249 | 190 | private fun populateVesselType(vessel: Vessel): ArrayList<MessageData?> { | |
| val allVesselType: ArrayList<MessageData?> = vessel.getAllVesselType() | 250 | 191 | val allVesselType: ArrayList<MessageData?> = vessel.getAllVesselType() | |
| allVesselType.sortBy { (it as VesselType).value } | 251 | 192 | allVesselType.sortBy { (it as VesselType).value } | |
| 252 | 193 | |||
| return allVesselType | 253 | 194 | return allVesselType |
src/main/kotlin/application/controller/MapPanelController.kt
View file @
f39d90e
| package application.controller | 1 | 1 | package application.controller | |
| 2 | 2 | |||
| import application.model.* | 3 | 3 | import application.model.* | |
| import application.model.State.* | 4 | 4 | import application.model.State.* | |
| import javafx.fxml.FXML | 5 | 5 | import javafx.fxml.FXML | |
| import javafx.fxml.Initializable | 6 | 6 | import javafx.fxml.Initializable | |
| import javafx.scene.layout.StackPane | 7 | 7 | import javafx.scene.layout.StackPane | |
| import map.* | 8 | 8 | import map.* | |
| import java.net.URL | 9 | 9 | import java.net.URL | |
| import java.util.* | 10 | 10 | import java.util.* | |
| 11 | 11 | |||
| class MapPanelController : Initializable { | 12 | 12 | class MapPanelController : Initializable { | |
| 13 | 13 | |||
| @FXML | 14 | 14 | @FXML | |
| private lateinit var map: StackPane | 15 | 15 | private lateinit var map: StackPane | |
| 16 | 16 | |||
| private val mapView = LeafletMapView() | 17 | 17 | private val mapView = LeafletMapView() | |
| 18 | 18 | |||
| 19 | 19 | |||
| override fun initialize(location: URL?, resources: ResourceBundle?) { | 20 | 20 | override fun initialize(location: URL?, resources: ResourceBundle?) { | |
| mapView.displayMap(MapConfig()) | 21 | 21 | mapView.displayMap(MapConfig()) | |
| setObservableVesselListener() | 22 | 22 | setObservableVesselListener() | |
| setObservableSelectedVesselListener() | 23 | 23 | setObservableSelectedVesselListener() | |
| setStateListener() | 24 | 24 | setStateListener() | |
| /*val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig()) | 25 | 25 | /*val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig()) | |
| completeFutureMap.whenComplete{ | 26 | 26 | completeFutureMap.whenComplete{ | |
| workerState, _ -> | 27 | 27 | workerState, _ -> | |
| if (workerState == Worker.State.SUCCEEDED) { | 28 | 28 | if (workerState == Worker.State.SUCCEEDED) { | |
| } | 29 | 29 | } | |
| }*/ | 30 | 30 | }*/ | |
| map.children.add(mapView) | 31 | 31 | map.children.add(mapView) | |
| map.children | 32 | 32 | map.children | |
| } | 33 | 33 | } | |
| 34 | 34 | |||
| private fun setStateListener() { | 35 | 35 | private fun setStateListener() { | |
| observableState.listeners.add(object : StateListener { | 36 | 36 | observableState.listeners.add(object : StateListener { | |
| override fun onValueChanged(newValue: State) { | 37 | 37 | override fun onValueChanged(newValue: State) { | |
| if (observableSelectedVessel.vessel.mmsi != null) { | 38 | 38 | if (observableSelectedVessel.vessel.mmsi != null) { | |
| updateMap(observableSelectedVessel.vessel.mmsi!!) | 39 | 39 | updateMap(observableSelectedVessel.vessel.mmsi!!) | |
| } else { | 40 | 40 | } else { | |
| updateMap() | 41 | 41 | updateMap() | |
| } | 42 | 42 | } | |
| } | 43 | 43 | } | |
| }) | 44 | 44 | }) | |
| } | 45 | 45 | } | |
| 46 | 46 | |||
| private fun updateMap() { | 47 | 47 | private fun updateMap() { | |
| when (observableState.state) { | 48 | 48 | when (observableState.state) { | |
| ALL_MESSAGES -> displayAllMessageOnMap(mapView) | 49 | 49 | ALL_MESSAGES -> displayAllMessageOnMap(mapView) | |
| CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) | 50 | 50 | CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) | |
| HEAT_MAP -> displayHeatMapOnMap(mapView) | 51 | 51 | HEAT_MAP -> displayHeatMapOnMap(mapView) | |
| } | 52 | 52 | } | |
| } | 53 | 53 | } | |
| 54 | 54 | |||
| private fun updateMap(selectedMMSI: Int) { | 55 | 55 | private fun updateMap(selectedMMSI: String) { | |
| when (observableState.state) { | 56 | 56 | when (observableState.state) { | |
| ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) | 57 | 57 | ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) | |
| CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI) | 58 | 58 | CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI) | |
| HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI) | 59 | 59 | HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI) | |
| } | 60 | 60 | } | |
| } | 61 | 61 | } | |
| 62 | 62 | |||
| private fun setObservableVesselListener() { | 63 | 63 | private fun setObservableVesselListener() { | |
| observableVessel.listeners.add(object : MessageListener { | 64 | 64 | observableVessel.listeners.add(object : MessageListener { | |
| override fun onValueChanged(newValue: MutableMap<Int?, Vessel>) { | 65 | 65 | override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | |
| updateMap() | 66 | 66 | updateMap() | |
| } | 67 | 67 | } | |
| }) | 68 | 68 | }) | |
| } | 69 | 69 | } | |
| 70 | 70 | |||
| private fun setObservableSelectedVesselListener() { | 71 | 71 | private fun setObservableSelectedVesselListener() { | |
| observableSelectedVessel.listeners.add(object : SelectedVesselListener { | 72 | 72 | observableSelectedVessel.listeners.add(object : SelectedVesselListener { | |
| override fun onValueChanged(newValue: Vessel) { | 73 | 73 | override fun onValueChanged(newValue: Vessel) { |
src/main/kotlin/application/controller/VesselListPanelController.kt
View file @
f39d90e
| package application.controller | 1 | 1 | package application.controller | |
| 2 | 2 | |||
| import application.model.MessageListener | 3 | 3 | import application.model.MessageListener | |
| import application.model.Vessel | 4 | 4 | import application.model.Vessel | |
| import application.model.observableVessel | 5 | |||
| import application.model.observableSelectedVessel | 6 | 5 | import application.model.observableSelectedVessel | |
| 6 | import application.model.observableVessel | |||
| import javafx.collections.FXCollections | 7 | 7 | import javafx.collections.FXCollections | |
| import javafx.collections.ObservableList | 8 | 8 | import javafx.collections.ObservableList | |
| 9 | import javafx.event.EventHandler | |||
| import javafx.fxml.FXML | 9 | 10 | import javafx.fxml.FXML | |
| import javafx.fxml.Initializable | 10 | 11 | import javafx.fxml.Initializable | |
| import javafx.scene.control.ListView | 11 | 12 | import javafx.scene.control.* | |
| 13 | import javafx.scene.input.MouseEvent | |||
| import java.net.URL | 12 | 14 | import java.net.URL | |
| import java.util.* | 13 | 15 | import java.util.* | |
| 14 | 16 | |||
| 17 | ||||
| class VesselListPanelController : Initializable, MessageListener { | 15 | 18 | class VesselListPanelController : Initializable, MessageListener { | |
| @FXML | 16 | 19 | @FXML | |
| var shipListView: ListView<Int> = ListView() | 17 | 20 | var shipListView: ListView<String?> = ListView() | |
| 18 | 21 | |||
| 22 | private var shipList: ObservableList<String?> = FXCollections.observableArrayList() | |||
| 19 | 23 | |||
| private var shipList: ObservableList<Int> = FXCollections.observableArrayList() | 20 | |||
| 21 | ||||
| override fun initialize(location: URL?, resources: ResourceBundle?) { | 22 | 24 | override fun initialize(location: URL?, resources: ResourceBundle?) { | |
| 25 | ||||
| 26 | ||||
| shipListView.items = shipList | 23 | 27 | shipListView.items = shipList | |
| observableVessel.listeners.add(this) | 24 | 28 | observableVessel.listeners.add(this) | |
| shipListView.selectionModel.selectedItemProperty().addListener { _, _, newValue -> | 25 | 29 | shipListView.selectionModel.selectedItemProperty().addListener { _, _, newValue -> | |
| observableSelectedVessel.vessel = observableVessel.vessels[newValue]!! | 26 | 30 | if (newValue == null) { | |
| 31 | observableSelectedVessel.vessel = Vessel(null) | |||
| 32 | } else { | |||
| 33 | observableSelectedVessel.vessel = observableVessel.vessels[newValue]!! | |||
| 34 | } | |||
| } | 27 | 35 | } | |
| 36 | setCellFactory() | |||
| } | 28 | 37 | } | |
| 29 | 38 | |||
| override fun onValueChanged(newValue: MutableMap<Int?, Vessel>) { | 30 | 39 | override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | |
| shipList.clear() | 31 | 40 | shipList.clear() | |
| shipList.addAll(newValue.keys) | 32 | 41 | shipList.addAll(newValue.keys) | |
| } | 33 | 42 | } | |
| 34 | 43 | |||
| 44 | private fun setCellFactory() { | |||
| 45 | val selectionModel: MultipleSelectionModel<String?>? = shipListView.selectionModel | |||
| 46 | selectionModel?.selectionMode = SelectionMode.SINGLE | |||
| 47 | shipListView.setCellFactory { | |||
| 48 | val cell = ListCell<String?>() | |||
| 49 | cell.textProperty().bind(cell.itemProperty()) | |||
| 50 | cell.addEventFilter(MouseEvent.MOUSE_PRESSED) { event: MouseEvent -> | |||
| 51 | shipListView.requestFocus() | |||
| 52 | if (!cell.isEmpty) { | |||
| 53 | val index = cell.index | |||
| 54 | if (selectionModel!!.selectedIndices.contains(index)) { | |||
| 55 | selectionModel.clearSelection() | |||
| 56 | } else { | |||
| 57 | selectionModel.select(index) | |||
| 58 | } | |||
| 59 | event.consume() | |||
| 60 | } | |||
| 61 | } | |||
| 62 | cell |
src/main/kotlin/application/model/Message.kt
View file @
f39d90e
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| import java.time.LocalDateTime | 3 | 3 | import java.time.LocalDateTime | |
| 4 | 4 | |||
| class Message(split: List<String>) { | 5 | 5 | class Message(split: List<String>) { | |
| val mmsi = MMSI(split[0].toIntOrNull()) | 6 | 6 | val mmsi = MMSI(if (split[0] == "") null else split[0]) | |
| val time = Time(LocalDateTime.parse(split[1])) | 7 | 7 | val time = Time(LocalDateTime.parse(split[1])) | |
| val latitude = Latitude(split[2].toDoubleOrNull()) | 8 | 8 | val latitude = Latitude(split[2].toDoubleOrNull()) | |
| val longitude = Longitude(split[3].toDoubleOrNull()) | 9 | 9 | val longitude = Longitude(split[3].toDoubleOrNull()) | |
| val speedOverGround = SpeedOverGround(split[4].toDoubleOrNull()) | 10 | 10 | val speedOverGround = SpeedOverGround(split[4].toDoubleOrNull()) | |
| val courseOverGround = CourseOverGround(split[5].toDoubleOrNull()) | 11 | 11 | val courseOverGround = CourseOverGround(split[5].toDoubleOrNull()) | |
| val heading = Heading(split[6].toDoubleOrNull()) | 12 | 12 | val heading = Heading(split[6].toDoubleOrNull()) | |
| val vesselName = VesselName(if (split[7] == "") null else split[7]) | 13 | 13 | val vesselName = VesselName(if (split[7] == "") null else split[7]) | |
| val imo = IMO(if (split[8] == "") null else split[8]) | 14 | 14 | val imo = IMO(if (split[8] == "") null else split[8]) | |
| val callSign = CallSign(if (split[9] == "") null else split[9]) | 15 | 15 | val callSign = CallSign(if (split[9] == "") null else split[9]) | |
| val vesselType = VesselType(split[10].toIntOrNull()) | 16 | 16 | val vesselType = VesselType(split[10].toIntOrNull()) | |
| val status = Status(if (split[11] == "") null else split[11]) | 17 | 17 | val status = Status(if (split[11] == "") null else split[11]) | |
| val length = Length(split[12].toDoubleOrNull()) | 18 | 18 | val length = Length(split[12].toDoubleOrNull()) | |
| val width = Width(split[13].toDoubleOrNull()) | 19 | 19 | val width = Width(split[13].toDoubleOrNull()) | |
| val draft = Draft(split[14].toDoubleOrNull()) | 20 | 20 | val draft = Draft(split[14].toDoubleOrNull()) | |
| val cargo = Cargo(split[15].toIntOrNull()) | 21 | 21 | val cargo = Cargo(split[15].toIntOrNull()) | |
| 22 | 22 | |||
| 23 | ||||
| fun getHexColorStroke(): String { | 24 | 23 | fun getHexColorStroke(): String { | |
| var hex = Integer.toHexString(this.mmsi.value!!) | 25 | 24 | var hex = Integer.toHexString(this.mmsi.value?.toInt()!!) | |
| if (hex.length > 6) { | 26 | 25 | if (hex.length > 6) { | |
| hex = hex.substring(hex.length - 6) | 27 | 26 | hex = hex.substring(hex.length - 6) | |
| } | 28 | 27 | } | |
| return hex | 29 | 28 | return hex |
src/main/kotlin/application/model/MessageData.kt
View file @
f39d90e
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| import java.time.LocalDateTime | 3 | 3 | import java.time.LocalDateTime | |
| 4 | 4 | |||
| interface MessageDataVisitor { | 5 | 5 | interface MessageDataVisitor { | |
| fun visit(messageData: MMSI) | 6 | 6 | fun visit(messageData: MMSI) | |
| fun visit(messageData: Time) | 7 | 7 | fun visit(messageData: Time) | |
| fun visit(messageData: Latitude) | 8 | 8 | fun visit(messageData: Latitude) | |
| fun visit(messageData: Longitude) | 9 | 9 | fun visit(messageData: Longitude) | |
| fun visit(messageData: SpeedOverGround) | 10 | 10 | fun visit(messageData: SpeedOverGround) | |
| fun visit(messageData: CourseOverGround) | 11 | 11 | fun visit(messageData: CourseOverGround) | |
| fun visit(messageData: Heading) | 12 | 12 | fun visit(messageData: Heading) | |
| fun visit(messageData: VesselName) | 13 | 13 | fun visit(messageData: VesselName) | |
| fun visit(messageData: IMO) | 14 | 14 | fun visit(messageData: IMO) | |
| fun visit(messageData: CallSign) | 15 | 15 | fun visit(messageData: CallSign) | |
| fun visit(messageData: VesselType) | 16 | 16 | fun visit(messageData: VesselType) | |
| fun visit(messageData: Status) | 17 | 17 | fun visit(messageData: Status) | |
| fun visit(messageData: Length) | 18 | 18 | fun visit(messageData: Length) | |
| fun visit(messageData: Width) | 19 | 19 | fun visit(messageData: Width) | |
| fun visit(messageData: Draft) | 20 | 20 | fun visit(messageData: Draft) | |
| fun visit(messageData: Cargo) | 21 | 21 | fun visit(messageData: Cargo) | |
| } | 22 | 22 | } | |
| 23 | 23 | |||
| interface MessageData { | 24 | 24 | interface MessageData { | |
| 25 | 25 | |||
| fun accept(visitor: MessageDataVisitor) | 26 | 26 | fun accept(visitor: MessageDataVisitor) | |
| 27 | 27 | |||
| } | 28 | 28 | } | |
| 29 | 29 | |||
| data class MMSI(val value: Int?) : MessageData { | 30 | 30 | data class MMSI(val value: String?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 31 | 31 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 32 | 32 | } | |
| 33 | 33 | |||
| data class Time(val value: LocalDateTime) : MessageData { | 34 | 34 | data class Time(val value: LocalDateTime) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 35 | 35 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 36 | 36 | } | |
| 37 | 37 | |||
| data class Latitude(val value: Double?) : MessageData { | 38 | 38 | data class Latitude(val value: Double?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 39 | 39 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 40 | 40 | } | |
| 41 | 41 | |||
| data class Longitude(val value: Double?) : MessageData { | 42 | 42 | data class Longitude(val value: Double?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 43 | 43 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 44 | 44 | } | |
| 45 | 45 | |||
| data class SpeedOverGround(val value: Double?) : MessageData { | 46 | 46 | data class SpeedOverGround(val value: Double?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 47 | 47 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 48 | 48 | } | |
| 49 | 49 | |||
| data class CourseOverGround(val value: Double?) : MessageData { | 50 | 50 | data class CourseOverGround(val value: Double?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 51 | 51 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 52 | 52 | } | |
| 53 | 53 | |||
| data class Heading(val value: Double?) : MessageData { | 54 | 54 | data class Heading(val value: Double?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 55 | 55 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 56 | 56 | } | |
| 57 | 57 | |||
| data class VesselName(val value: String?) : MessageData { | 58 | 58 | data class VesselName(val value: String?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 59 | 59 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 60 | 60 | } | |
| 61 | 61 | |||
| data class IMO(val value: String?) : MessageData { | 62 | 62 | data class IMO(val value: String?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 63 | 63 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 64 | 64 | } | |
| 65 | 65 | |||
| data class CallSign(val value: String?) : MessageData { | 66 | 66 | data class CallSign(val value: String?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 67 | 67 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 68 | 68 | } | |
| 69 | 69 | |||
| data class VesselType(val value: Int?) : MessageData { | 70 | 70 | data class VesselType(val value: Int?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 71 | 71 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 72 | 72 | } | |
| 73 | 73 | |||
| data class Status(val value: String?) : MessageData { | 74 | 74 | data class Status(val value: String?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 75 | 75 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 76 | 76 | } | |
| 77 | 77 | |||
| data class Length(val value: Double?) : MessageData { | 78 | 78 | data class Length(val value: Double?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 79 | 79 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 80 | 80 | } | |
| 81 | 81 | |||
| data class Width(val value: Double?) : MessageData { | 82 | 82 | data class Width(val value: Double?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 83 | 83 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 84 | 84 | } | |
| 85 | 85 | |||
| data class Draft(val value: Double?) : MessageData { | 86 | 86 | data class Draft(val value: Double?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 87 | 87 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 88 | 88 | } | |
| 89 | 89 | |||
| data class Cargo(val value: Int?) : MessageData { | 90 | 90 | data class Cargo(val value: Int?) : MessageData { | |
| override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | 91 | 91 | override fun accept(visitor: MessageDataVisitor) = visitor.visit(messageData = this) | |
| } | 92 | 92 | } | |
| 93 | 93 | |||
| class GetValueVisitor() : MessageDataVisitor { | 94 | 94 | class GetValueVisitor() : MessageDataVisitor { | |
| var value: String = "" | 95 | 95 | var value: String = "" | |
| 96 | 96 | |||
| override fun visit(messageData: MMSI) { | 97 | 97 | override fun visit(messageData: MMSI) { | |
| value = messageData.value.toString() | 98 | 98 | value = messageData.value.toString() | |
| } | 99 | 99 | } | |
| 100 | 100 | |||
| override fun visit(messageData: Time) { | 101 | 101 | override fun visit(messageData: Time) { | |
| value = messageData.value.toString() | 102 | 102 | value = messageData.value.toString() | |
| } | 103 | 103 | } | |
| 104 | 104 | |||
| override fun visit(messageData: Latitude) { | 105 | 105 | override fun visit(messageData: Latitude) { | |
| value = messageData.value.toString() | 106 | 106 | value = messageData.value.toString() | |
| } | 107 | 107 | } | |
| 108 | 108 | |||
| override fun visit(messageData: Longitude) { | 109 | 109 | override fun visit(messageData: Longitude) { | |
| value = messageData.value.toString() | 110 | 110 | value = messageData.value.toString() | |
| } | 111 | 111 | } | |
| 112 | 112 | |||
| override fun visit(messageData: SpeedOverGround) { | 113 | 113 | override fun visit(messageData: SpeedOverGround) { | |
| value = messageData.value.toString() | 114 | 114 | value = messageData.value.toString() | |
| } | 115 | 115 | } | |
| 116 | 116 | |||
| override fun visit(messageData: CourseOverGround) { | 117 | 117 | override fun visit(messageData: CourseOverGround) { | |
| value = messageData.value.toString() | 118 | 118 | value = messageData.value.toString() | |
| } | 119 | 119 | } | |
| 120 | 120 | |||
| override fun visit(messageData: Heading) { | 121 | 121 | override fun visit(messageData: Heading) { | |
| value = messageData.value.toString() | 122 | 122 | value = messageData.value.toString() | |
| } | 123 | 123 | } |
src/main/kotlin/application/model/MessageListener.kt
View file @
f39d90e
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| interface MessageListener { | 3 | 3 | interface MessageListener { | |
| fun onValueChanged(newValue: MutableMap<Int?, Vessel>) | 4 | 4 | fun onValueChanged(newValue: MutableMap<String?, Vessel>) | |
| } | 5 | 5 | } |
src/main/kotlin/application/model/ObservableVessel.kt
View file @
f39d90e
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| import kotlin.properties.Delegates | 3 | 3 | import kotlin.properties.Delegates | |
| 4 | 4 | |||
| class ObservableVessel { | 5 | 5 | class ObservableVessel { | |
| val listeners: MutableList<MessageListener> = mutableListOf() | 6 | 6 | val listeners: MutableList<MessageListener> = mutableListOf() | |
| 7 | 7 | |||
| var vessels: MutableMap<Int?, Vessel> by Delegates.observable( | 8 | 8 | var vessels: MutableMap<String?, Vessel> by Delegates.observable( | |
| initialValue = mutableMapOf(), | 9 | 9 | initialValue = mutableMapOf(), | |
| onChange = { _, _, new -> | 10 | 10 | onChange = { _, _, new -> | |
| run { | 11 | 11 | run { | |
| observableSelectedVessel.vessel = Vessel(null) | 12 | 12 | observableSelectedVessel.vessel = Vessel(null) | |
| listeners.forEach { | 13 | 13 | listeners.forEach { | |
| it.onValueChanged(new) | 14 | 14 | it.onValueChanged(new) | |
| } | 15 | 15 | } | |
| } | 16 | 16 | } | |
| } | 17 | 17 | } |
src/main/kotlin/application/model/Vessel.kt
View file @
f39d90e
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| import java.time.LocalDateTime | 3 | 3 | import java.time.LocalDateTime | |
| import java.time.ZoneOffset | 4 | 4 | import java.time.ZoneOffset | |
| import java.util.* | 5 | 5 | import java.util.* | |
| 6 | 6 | |||
| 7 | 7 | |||
| class Vessel(val mmsi: Int?) { | 8 | 8 | class Vessel(val mmsi: String?) { | |
| val messages: SortedMap<LocalDateTime, Message> = sortedMapOf() | 9 | 9 | val messages: SortedMap<LocalDateTime, Message> = sortedMapOf() | |
| 10 | ||||
| fun getAllTimeInMilliSeconde(): ArrayList<Long> { | 11 | |||
| val timeList = arrayListOf<Long>() | 12 | |||
| var timeInMilliSeconde: Long | 13 | |||
| messages.forEach { | 14 | |||
| timeInMilliSeconde = it.value.time.value.toEpochSecond(ZoneOffset.UTC).toInt().toLong() | 15 | |||
| timeList.add(timeInMilliSeconde) | 16 | |||
| } | 17 | |||
| return timeList | 18 | |||
| } | 19 | |||
| 20 | 10 | |||
| fun getAllTime(): ArrayList<MessageData?> { | 21 | 11 | fun getAllTime(): ArrayList<MessageData?> { | |
| val timeList = arrayListOf<MessageData?>() | 22 | 12 | val timeList = arrayListOf<MessageData?>() | |
| messages.forEach { | 23 | 13 | messages.forEach { | |
| timeList.add(it.value.time) | 24 | 14 | timeList.add(it.value.time) | |
| } | 25 | 15 | } | |
| 26 | 16 | |||
| return timeList | 27 | 17 | return timeList | |
| } | 28 | 18 | } | |
| 29 | 19 | |||
| fun getAllLatitude(): ArrayList<MessageData?> { | 30 | 20 | fun getAllLatitude(): ArrayList<MessageData?> { | |
| val latitudeList = arrayListOf<MessageData?>() | 31 | 21 | val latitudeList = arrayListOf<MessageData?>() | |
| messages.forEach { | 32 | 22 | messages.forEach { | |
| latitudeList.add(it.value.latitude) | 33 | 23 | latitudeList.add(it.value.latitude) | |
| } | 34 | 24 | } | |
| 35 | 25 | |||
| return latitudeList | 36 | 26 | return latitudeList | |
| } | 37 | 27 | } | |
| 38 | 28 | |||
| fun getAllLongitude(): ArrayList<MessageData?> { | 39 | 29 | fun getAllLongitude(): ArrayList<MessageData?> { | |
| val longitudeList = arrayListOf<MessageData?>() | 40 | 30 | val longitudeList = arrayListOf<MessageData?>() | |
| messages.forEach { | 41 | 31 | messages.forEach { | |
| longitudeList.add(it.value.longitude) | 42 | 32 | longitudeList.add(it.value.longitude) | |
| } | 43 | 33 | } | |
| 44 | 34 | |||
| return longitudeList | 45 | 35 | return longitudeList | |
| } | 46 | 36 | } | |
| 47 | 37 | |||
| fun getAllSpeedOverGround(): ArrayList<MessageData?> { | 48 | 38 | fun getAllSpeedOverGround(): ArrayList<MessageData?> { | |
| val speedOverGroundList = arrayListOf<MessageData?>() | 49 | 39 | val speedOverGroundList = arrayListOf<MessageData?>() | |
| messages.forEach { | 50 | 40 | messages.forEach { | |
| speedOverGroundList.add(it.value.speedOverGround) | 51 | 41 | speedOverGroundList.add(it.value.speedOverGround) | |
| } | 52 | 42 | } | |
| 53 | 43 | |||
| return speedOverGroundList | 54 | 44 | return speedOverGroundList | |
| } | 55 | 45 | } | |
| 56 | 46 | |||
| fun getAllCourseOverGround(): ArrayList<MessageData?> { | 57 | 47 | fun getAllCourseOverGround(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 58 | 48 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 59 | 49 | messages.forEach { | |
| res.add(it.value.courseOverGround) | 60 | 50 | res.add(it.value.courseOverGround) | |
| } | 61 | 51 | } | |
| 62 | 52 | |||
| return res | 63 | 53 | return res | |
| } | 64 | 54 | } | |
| 65 | 55 | |||
| fun getAllHeading(): ArrayList<MessageData?> { | 66 | 56 | fun getAllHeading(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 67 | 57 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 68 | 58 | messages.forEach { | |
| res.add(it.value.heading) | 69 | 59 | res.add(it.value.heading) | |
| } | 70 | 60 | } | |
| 71 | 61 | |||
| return res | 72 | 62 | return res | |
| } | 73 | 63 | } | |
| 74 | 64 | |||
| fun getAllVesselName(): ArrayList<MessageData?> { | 75 | 65 | fun getAllVesselName(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 76 | 66 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 77 | 67 | messages.forEach { | |
| res.add(it.value.vesselName) | 78 | 68 | res.add(it.value.vesselName) | |
| } | 79 | 69 | } | |
| return res | 80 | 70 | return res | |
| } | 81 | 71 | } | |
| 82 | 72 | |||
| fun getAllIMO(): ArrayList<MessageData?> { | 83 | 73 | fun getAllIMO(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 84 | 74 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 85 | 75 | messages.forEach { | |
| res.add(it.value.imo) | 86 | 76 | res.add(it.value.imo) | |
| } | 87 | 77 | } | |
| return res | 88 | 78 | return res | |
| } | 89 | 79 | } | |
| 90 | 80 | |||
| fun getAllCallSign(): ArrayList<MessageData?> { | 91 | 81 | fun getAllCallSign(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 92 | 82 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 93 | 83 | messages.forEach { | |
| res.add(it.value.callSign) | 94 | 84 | res.add(it.value.callSign) | |
| } | 95 | 85 | } | |
| return res | 96 | 86 | return res | |
| } | 97 | 87 | } | |
| 98 | 88 | |||
| fun getAllVesselType(): ArrayList<MessageData?> { | 99 | 89 | fun getAllVesselType(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 100 | 90 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 101 | 91 | messages.forEach { | |
| res.add(it.value.vesselType) | 102 | 92 | res.add(it.value.vesselType) | |
| } | 103 | 93 | } | |
| return res | 104 | 94 | return res | |
| } | 105 | 95 | } | |
| 106 | 96 | |||
| fun getAllStatus(): ArrayList<MessageData?> { | 107 | 97 | fun getAllStatus(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 108 | 98 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 109 | 99 | messages.forEach { | |
| res.add(it.value.status) | 110 | 100 | res.add(it.value.status) | |
| } | 111 | 101 | } | |
| return res | 112 | 102 | return res | |
| } | 113 | 103 | } | |
| 114 | 104 | |||
| fun getAllLength(): ArrayList<MessageData?> { | 115 | 105 | fun getAllLength(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 116 | 106 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 117 | 107 | messages.forEach { | |
| res.add(it.value.length) | 118 | 108 | res.add(it.value.length) | |
| } | 119 | 109 | } | |
| return res | 120 | 110 | return res | |
| } | 121 | 111 | } | |
| 122 | 112 |
src/main/kotlin/application/model/VesselGenerator.kt
View file @
f39d90e
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| import java.io.File | 3 | 3 | import java.io.File | |
| import java.util.* | 4 | 4 | import java.util.* | |
| import kotlin.collections.ArrayList | 5 | 5 | import kotlin.collections.ArrayList | |
| 6 | 6 | |||
| fun createVesselCollection(file: File) : SortedMap<Int, Vessel> { | 7 | 7 | fun createVesselCollection(file: File) : SortedMap<String, Vessel> { | |
| val messages : ArrayList<Message> = arrayListOf() | 8 | 8 | val messages : ArrayList<Message> = arrayListOf() | |
| val vessels: SortedMap<Int, Vessel> = sortedMapOf() | 9 | 9 | val vessels: SortedMap<String, Vessel> = sortedMapOf() | |
| 10 | 10 | |||
| file.forEachLine { | 11 | 11 | file.forEachLine { | |
| val arrayMessage = it.split(",") | 12 | 12 | val arrayMessage = it.split(",") | |
| if (arrayMessage[0].toIntOrNull() !== null) { | 13 | 13 | if (arrayMessage[0].toIntOrNull() !== null) { | |
| val message = Message(arrayMessage) | 14 | 14 | val message = Message(arrayMessage) | |
| messages.add(message) | 15 | 15 | messages.add(message) | |
| if (!vessels.containsKey(message.mmsi.value)){ | 16 | 16 | if (!vessels.containsKey(message.mmsi.value)){ | |
| vessels[message.mmsi.value] = Vessel(message.mmsi.value!!) | 17 | 17 | vessels[message.mmsi.value] = Vessel(message.mmsi.value!!) | |
| } | 18 | 18 | } | |
| vessels[message.mmsi.value]?.messages?.set(message.time.value, message) | 19 | 19 | vessels[message.mmsi.value]?.messages?.set(message.time.value, message) | |
| } | 20 | 20 | } | |
| 21 | 21 | |||
| } | 22 | 22 | } |
src/main/kotlin/map/MapDisplayer.kt
View file @
f39d90e
| package map | 1 | 1 | package map | |
| 2 | 2 | |||
| import application.model.observableVessel | 3 | 3 | import application.model.observableVessel | |
| 4 | 4 | |||
| fun clearMap(map: LeafletMapView) { | 5 | 5 | fun clearMap(map: LeafletMapView) { | |
| clearMapCanvas(map) | 6 | 6 | clearMapCanvas(map) | |
| clearMapCluster(map) | 7 | 7 | clearMapCluster(map) | |
| clearHeatMap(map) | 8 | 8 | clearHeatMap(map) | |
| } | 9 | 9 | } | |
| 10 | 10 | |||
| fun clearMapCluster(map: LeafletMapView) { | 11 | 11 | fun clearMapCluster(map: LeafletMapView) { | |
| map.execScript( | 12 | 12 | map.execScript( | |
| """ | 13 | 13 | """ | |
| |myMap.removeLayer(markerClusters); | 14 | 14 | |myMap.removeLayer(markerClusters); | |
| |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); | 15 | 15 | |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); | |
| """.trimMargin() | 16 | 16 | """.trimMargin() | |
| ) | 17 | 17 | ) | |
| } | 18 | 18 | } | |
| 19 | 19 | |||
| fun clearMapCanvas(map: LeafletMapView) { | 20 | 20 | fun clearMapCanvas(map: LeafletMapView) { | |
| map.execScript( | 21 | 21 | map.execScript( | |
| """ | 22 | 22 | """ | |
| |myRenderer.removeFrom(myMap); | 23 | 23 | |myRenderer.removeFrom(myMap); | |
| |var myRenderer = L.canvas({ padding: 0.5 }); | 24 | 24 | |var myRenderer = L.canvas({ padding: 0.5 }); | |
| """.trimMargin() | 25 | 25 | """.trimMargin() | |
| ) | 26 | 26 | ) | |
| } | 27 | 27 | } | |
| 28 | 28 | |||
| fun clearHeatMap(map: LeafletMapView) { | 29 | 29 | fun clearHeatMap(map: LeafletMapView) { | |
| map.execScript( | 30 | 30 | map.execScript( | |
| """ | 31 | 31 | """ | |
| |heatLayer.removeFrom(myMap); | 32 | 32 | |heatLayer.removeFrom(myMap); | |
| |var heatLayer = L.heatLayer([]).addTo(myMap); | 33 | 33 | |var heatLayer = L.heatLayer([]).addTo(myMap); | |
| """.trimMargin() | 34 | 34 | """.trimMargin() | |
| ) | 35 | 35 | ) | |
| } | 36 | 36 | } | |
| 37 | 37 | |||
| fun displayAllMessageOnMap(map: LeafletMapView) { | 38 | 38 | fun displayAllMessageOnMap(map: LeafletMapView) { | |
| clearMap(map) | 39 | 39 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 40 | 40 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 41 | 41 | value.messages.forEach { (_, message) -> | |
| map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | 42 | 42 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | |
| } | 43 | 43 | } | |
| } | 44 | 44 | } | |
| } | 45 | 45 | } | |
| 46 | 46 | |||
| fun displayAllMessageOnMap(map: LeafletMapView, selectedMMSI: Int) { | 47 | 47 | fun displayAllMessageOnMap(map: LeafletMapView, selectedMMSI: String) { | |
| clearMap(map) | 48 | 48 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 49 | 49 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 50 | 50 | value.messages.forEach { (_, message) -> | |
| if (selectedMMSI == message.mmsi.value) { | 51 | 51 | if (selectedMMSI == message.mmsi.value) { | |
| map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap)") | 52 | 52 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap)") | |
| } else { | 53 | 53 | } else { | |
| map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | 54 | 54 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | |
| } | 55 | 55 | } | |
| } | 56 | 56 | } | |
| } | 57 | 57 | } | |
| } | 58 | 58 | } | |
| 59 | 59 | |||
| fun displayClusterMessageOnMap(map: LeafletMapView) { | 60 | 60 | fun displayClusterMessageOnMap(map: LeafletMapView) { | |
| clearMap(map) | 61 | 61 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 62 | 62 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 63 | 63 | value.messages.forEach { (_, message) -> | |
| map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | 64 | 64 | map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |
| } | 65 | 65 | } | |
| } | 66 | 66 | } | |
| map.execScript("myMap.addLayer(markerClusters);") | 67 | 67 | map.execScript("myMap.addLayer(markerClusters);") | |
| } | 68 | 68 | } | |
| 69 | 69 | |||
| fun displayClusterMessageOnMap(map: LeafletMapView, selectedMMSI: Int) { | 70 | 70 | fun displayClusterMessageOnMap(map: LeafletMapView, selectedMMSI: String) { | |
| clearMap(map) | 71 | 71 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 72 | 72 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 73 | 73 | value.messages.forEach { (_, message) -> | |
| if (selectedMMSI == message.mmsi.value) { | 74 | 74 | if (selectedMMSI == message.mmsi.value) { | |
| map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") | 75 | 75 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") | |
| } else { | 76 | 76 | } else { | |
| map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | 77 | 77 | map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |
| } | 78 | 78 | } | |
| } | 79 | 79 | } | |
| } | 80 | 80 | } | |
| map.execScript("myMap.addLayer(markerClusters);") | 81 | 81 | map.execScript("myMap.addLayer(markerClusters);") | |
| } | 82 | 82 | } | |
| 83 | 83 | |||
| fun displayHeatMapOnMap(map: LeafletMapView) { | 84 | 84 | fun displayHeatMapOnMap(map: LeafletMapView) { | |
| clearMap(map) | 85 | 85 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 86 | 86 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 87 | 87 | value.messages.forEach { (_, message) -> | |
| map.execScript("heatLayer.addLatLng([${message.latitude.value}, ${message.longitude.value}]);") | 88 | 88 | map.execScript("heatLayer.addLatLng([${message.latitude.value}, ${message.longitude.value}]);") | |
| } | 89 | 89 | } | |
| } | 90 | 90 | } | |
| } | 91 | 91 | } | |
| 92 | 92 | |||
| fun displayHeatMapOnMap(map: LeafletMapView, selectedMMSI: Int) { | 93 | 93 | fun displayHeatMapOnMap(map: LeafletMapView, selectedMMSI: String) { | |
| clearMap(map) | 94 | 94 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 95 | 95 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 96 | 96 | value.messages.forEach { (_, message) -> | |
| if (selectedMMSI == message.mmsi.value) { | 97 | 97 | if (selectedMMSI == message.mmsi.value) { |
src/main/resources/gui/vesselListPanel.fxml
View file @
f39d90e
| <?xml version="1.0" encoding="UTF-8"?> | 1 | 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | 2 | |||
| 3 | <?import javafx.geometry.*?> | |||
| <?import javafx.scene.control.*?> | 3 | 4 | <?import javafx.scene.control.*?> | |
| <?import javafx.scene.layout.*?> | 4 | 5 | <?import javafx.scene.layout.*?> | |
| 6 | <?import javafx.scene.text.*?> | |||
| 5 | 7 | |||
| <BorderPane prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.controller.VesselListPanelController"> | 6 | 8 | <BorderPane prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.controller.VesselListPanelController"> | |
| 7 | ||||
| <top> | 8 | |||
| <AnchorPane prefHeight="33.0" prefWidth="200.0" BorderPane.alignment="CENTER"> | 9 | |||
| <children> | 10 | |||
| <Button layoutX="74.0" layoutY="2.0" mnemonicParsing="false" text="Button" fx:id="button"/> | 11 | |||
| </children></AnchorPane> | 12 | |||
| </top> | 13 | |||
| <center> | 14 | 9 | <center> | |
| <ListView fx:id="shipListView" prefHeight="105.0" prefWidth="200.0" /> | 15 | 10 | <ListView fx:id="shipListView" prefHeight="50.0" prefWidth="200.0" /> | |
| </center> | 16 | 11 | </center> | |
| 12 | <top> | |||
| 13 | <TextFlow prefHeight="28.0" prefWidth="200.0" style="-fx-font-weight: bold;" textAlignment="CENTER" BorderPane.alignment="CENTER"> | |||
| 14 | <children> | |||
| 15 | <Text scaleX="1.5" scaleY="1.5" strokeType="OUTSIDE" strokeWidth="0.0" text="MMSI" textAlignment="CENTER" /> | |||
| 16 | </children> | |||
| 17 | <BorderPane.margin> | |||
| 18 | <Insets /> | |||
| 19 | </BorderPane.margin> | |||
| 20 | <padding> | |||
| 21 | <Insets top="5.0" /> | |||
| 22 | </padding> | |||
| 23 | </TextFlow> | |||
| 24 | </top> |