Commit 78935bd622fe4fc1337b6cb6f8dc73d3cb031477
1 parent
3b26be8f99
Exists in
master
and in
1 other branch
slider bind to all type of map
Showing 7 changed files with 104 additions and 47 deletions Inline Diff
- src/main/kotlin/application/controller/MapPanelController.kt
 - src/main/kotlin/application/controller/TimePanel.kt
 - src/main/kotlin/application/controller/VesselListPanelController.kt
 - src/main/kotlin/application/model/Vessel.kt
 - src/main/kotlin/map/LeafletMapView.kt
 - src/main/kotlin/map/MapDisplayer.kt
 - src/main/resources/gui/timePanel.fxml
 
src/main/kotlin/application/controller/MapPanelController.kt
View file @
78935bd
| package application.controller | 1 | 1 | package application.controller | |
| 2 | 2 | |||
| import application.model.* | 3 | 3 | import application.model.* | |
| import application.model.MapState.* | 4 | 4 | import application.model.MapState.* | |
| 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() | |
| observableCurrentTime() | 25 | 25 | observableCurrentTime() | |
| 26 | 26 | |||
| /*val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig()) | 27 | |||
| completeFutureMap.whenComplete{ | 28 | |||
| workerState, _ -> | 29 | |||
| if (workerState == Worker.State.SUCCEEDED) { | 30 | |||
| } | 31 | |||
| }*/ | 32 | |||
| map.children.add(mapView) | 33 | 27 | map.children.add(mapView) | |
| map.children | 34 | 28 | map.children | |
| } | 35 | 29 | } | |
| 36 | 30 | |||
| private fun setStateListener() { | 37 | 31 | private fun setStateListener() { | |
| observableMapState.listeners.add(object : StateListener { | 38 | 32 | observableMapState.listeners.add(object : StateListener { | |
| override fun onValueChanged(newValue: MapState) { | 39 | 33 | override fun onValueChanged(newValue: MapState) { | |
| if (observableSelectedVessel.vessel.mmsi != null) { | 40 | 34 | if (observableSelectedVessel.vessel.mmsi != null) { | |
| updateMap(observableSelectedVessel.vessel.mmsi!!) | 41 | 35 | updateMap(observableSelectedVessel.vessel.mmsi!!) | |
| } else { | 42 | 36 | } else { | |
| updateMap() | 43 | 37 | updateMap() | |
| } | 44 | 38 | } | |
| } | 45 | 39 | } | |
| }) | 46 | 40 | }) | |
| } | 47 | 41 | } | |
| 48 | 42 | |||
| private fun observableCurrentTime() { | 49 | 43 | private fun observableCurrentTime() { | |
| observableCurrentTime.listeners.add(object : CurrentTime{ | 50 | 44 | observableCurrentTime.listeners.add(object : CurrentTime { | |
| override fun onValueChanged(newValue: Int) { | 51 | 45 | override fun onValueChanged(newValue: Int) { | |
| updateMap() | 52 | 46 | if (observableSelectedVessel.vessel.mmsi != null) { | |
| 47 | updateMap(observableSelectedVessel.vessel.mmsi!!) | |||
| 48 | } else { | |||
| 49 | updateMap() | |||
| 50 | } | |||
| } | 53 | 51 | } | |
| }) | 54 | 52 | }) | |
| } | 55 | 53 | } | |
| 56 | 54 | |||
| private fun updateMap() { | 57 | 55 | private fun updateMap() { | |
| if (observableIsReplayState.value){ | 58 | 56 | if (observableIsReplayState.value) { | |
| displayTargetedVessels(mapView) | 59 | 57 | when (observableMapState.state) { | |
| 58 | ALL_MESSAGES -> displayTimedAllMessageOnMap(mapView) | |||
| 59 | CLUSTERED_MESSAGES -> displayTimedClusterMessageOnMap(mapView) | |||
| 60 | HEAT_MAP -> displayTimedHeatMapOnMap(mapView) | |||
| 61 | } | |||
| } else { | 60 | 62 | } else { | |
| when (observableMapState.state) { | 61 | 63 | when (observableMapState.state) { | |
| ALL_MESSAGES -> displayAllMessageOnMap(mapView) | 62 | 64 | ALL_MESSAGES -> displayAllMessageOnMap(mapView) | |
| CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) | 63 | 65 | CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) | |
| HEAT_MAP -> displayHeatMapOnMap(mapView) | 64 | 66 | HEAT_MAP -> displayHeatMapOnMap(mapView) | |
| } | 65 | 67 | } | |
| } | 66 | 68 | } | |
| } | 67 | 69 | } | |
| 68 | 70 | |||
| private fun updateMap(selectedMMSI: String) { | 69 | 71 | private fun updateMap(selectedMMSI: String) { | |
| when (observableMapState.state) { | 70 | 72 | if (observableIsReplayState.value) { | |
| ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) | 71 | 73 | when (observableMapState.state) { | |
| CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI) | 72 | 74 | ALL_MESSAGES -> displayTimedAllMessageOnMap(mapView, selectedMMSI) | |
| HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI) | 73 | 75 | CLUSTERED_MESSAGES -> displayTimedClusterMessageOnMap(mapView, selectedMMSI) | |
| 76 | HEAT_MAP -> displayTimedHeatMapOnMap(mapView, selectedMMSI) | |||
| 77 | } | |||
| 78 | } else { | |||
| 79 | when (observableMapState.state) { | |||
| 80 | ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) | |||
| 81 | CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI) | |||
| 82 | HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI) | |||
| 83 | } | |||
| } | 74 | 84 | } | |
| } | 75 | 85 | } | |
| 76 | 86 | |||
| private fun setObservableVesselListener() { | 77 | 87 | private fun setObservableVesselListener() { | |
| observableVessel.listeners.add(object : MessageListener { | 78 | 88 | observableVessel.listeners.add(object : MessageListener { | |
| override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | 79 | 89 | override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | |
| updateMap() | 80 | 90 | updateMap() | |
| } | 81 | 91 | } | |
| }) | 82 | 92 | }) | |
| } | 83 | 93 | } | |
| 84 | 94 | |||
| private fun setObservableSelectedVesselListener() { | 85 | 95 | private fun setObservableSelectedVesselListener() { | |
| observableSelectedVessel.listeners.add(object : SelectedVesselListener { | 86 | 96 | observableSelectedVessel.listeners.add(object : SelectedVesselListener { | |
| override fun onValueChanged(newValue: Vessel) { | 87 | 97 | override fun onValueChanged(newValue: Vessel) { | |
| if (newValue.mmsi != null){ | 88 | 98 | if (newValue.mmsi != null) { | |
| updateMap(newValue.mmsi) | 89 | 99 | updateMap(newValue.mmsi) | |
| }else { | 90 | 100 | } else { | |
| updateMap() | 91 | 101 | updateMap() | |
| } | 92 | 102 | } | |
| } | 93 | 103 | } | 
src/main/kotlin/application/controller/TimePanel.kt
View file @
78935bd
| package application.controller | 1 | 1 | package application.controller | |
| 2 | 2 | |||
| import application.model.* | 3 | 3 | import application.model.* | |
| import javafx.fxml.FXML | 4 | 4 | import javafx.fxml.FXML | |
| import javafx.fxml.Initializable | 5 | 5 | import javafx.fxml.Initializable | |
| import javafx.scene.control.Button | 6 | 6 | import javafx.scene.control.Button | |
| import javafx.scene.control.Slider | 7 | 7 | import javafx.scene.control.Slider | |
| import java.net.URL | 8 | 8 | import java.net.URL | |
| import java.util.* | 9 | 9 | import java.util.* | |
| 10 | 10 | |||
| 11 | 11 | |||
| class TimePanel : Initializable { | 12 | 12 | class TimePanel : Initializable { | |
| 13 | 13 | |||
| @FXML | 14 | 14 | @FXML | |
| var timeSlider = Slider() | 15 | 15 | var timeSlider = Slider() | |
| 16 | 16 | |||
| @FXML | 17 | 17 | @FXML | |
| var timeStop = Button() | 18 | 18 | var timeStop = Button() | |
| 19 | 19 | |||
| @FXML | 20 | 20 | @FXML | |
| var timePlay = Button() | 21 | 21 | var timePlay = Button() | |
| 22 | 22 | |||
| 23 | 23 | |||
| override fun initialize(location: URL?, resources: ResourceBundle?) { | 24 | 24 | override fun initialize(location: URL?, resources: ResourceBundle?) { | |
| setSliderMinMax() | 25 | 25 | setSliderMinMax() | |
| setSliderListener() | 26 | 26 | setSliderListener() | |
| 27 | 27 | |||
| 28 | 28 | |||
| } | 29 | 29 | } | |
| 30 | 30 | |||
| private fun setSliderMinMax() { | 31 | 31 | private fun setSliderMinMax() { | |
| 32 | timeSlider.min = 0.0 | |||
| 33 | timeSlider.max = 0.0 | |||
| observableVessel.listeners.add(object : MessageListener{ | 32 | 34 | observableVessel.listeners.add(object : MessageListener{ | |
| override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | 33 | 35 | override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | 
src/main/kotlin/application/controller/VesselListPanelController.kt
View file @
78935bd
| 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.observableSelectedVessel | 5 | 5 | import application.model.observableSelectedVessel | |
| import application.model.observableVessel | 6 | 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 | |
| import javafx.event.EventHandler | 9 | |||
| import javafx.fxml.FXML | 10 | 9 | import javafx.fxml.FXML | |
| import javafx.fxml.Initializable | 11 | 10 | import javafx.fxml.Initializable | |
| import javafx.scene.control.* | 12 | 11 | import javafx.scene.control.ListCell | |
| 12 | import javafx.scene.control.ListView | |||
| 13 | import javafx.scene.control.MultipleSelectionModel | |||
| 14 | import javafx.scene.control.SelectionMode | |||
| import javafx.scene.input.MouseEvent | 13 | 15 | import javafx.scene.input.MouseEvent | |
| import java.net.URL | 14 | 16 | import java.net.URL | |
| import java.util.* | 15 | 17 | import java.util.* | |
| 16 | 18 | |||
| 17 | 19 | |||
| class VesselListPanelController : Initializable, MessageListener { | 18 | 20 | class VesselListPanelController : Initializable, MessageListener { | |
| @FXML | 19 | 21 | @FXML | |
| var shipListView: ListView<String?> = ListView() | 20 | 22 | var shipListView: ListView<String?> = ListView() | |
| 21 | 23 | |||
| private var shipList: ObservableList<String?> = FXCollections.observableArrayList() | 22 | 24 | private var shipList: ObservableList<String?> = FXCollections.observableArrayList() | |
| 23 | 25 | |||
| override fun initialize(location: URL?, resources: ResourceBundle?) { | 24 | 26 | override fun initialize(location: URL?, resources: ResourceBundle?) { | |
| 25 | 27 | |||
| 26 | 28 | |||
| shipListView.items = shipList | 27 | 29 | shipListView.items = shipList | |
| observableVessel.listeners.add(this) | 28 | 30 | observableVessel.listeners.add(this) | |
| shipListView.selectionModel.selectedItemProperty().addListener { _, _, newValue -> | 29 | 31 | shipListView.selectionModel.selectedItemProperty().addListener { _, _, newValue -> | |
| if (newValue == null) { | 30 | 32 | if (newValue == null) { | |
| observableSelectedVessel.vessel = Vessel(null) | 31 | 33 | observableSelectedVessel.vessel = Vessel(null) | |
| } else { | 32 | 34 | } else { | |
| observableSelectedVessel.vessel = observableVessel.vessels[newValue]!! | 33 | 35 | observableSelectedVessel.vessel = observableVessel.vessels[newValue]!! | |
| } | 34 | 36 | } | |
| } | 35 | 37 | } | |
| setCellFactory() | 36 | 38 | setCellFactory() | |
| } | 37 | 39 | } | |
| 38 | 40 | |||
| override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | 39 | 41 | override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | |
| shipList.clear() | 40 | 42 | shipList.clear() | |
| shipList.addAll(newValue.keys) | 41 | 43 | shipList.addAll(newValue.keys) | |
| } | 42 | 44 | } | |
| 43 | 45 | |||
| private fun setCellFactory() { | 44 | 46 | private fun setCellFactory() { | |
| val selectionModel: MultipleSelectionModel<String?>? = shipListView.selectionModel | 45 | 47 | val selectionModel: MultipleSelectionModel<String?>? = shipListView.selectionModel | |
| selectionModel?.selectionMode = SelectionMode.SINGLE | 46 | 48 | selectionModel?.selectionMode = SelectionMode.SINGLE | |
| shipListView.setCellFactory { | 47 | 49 | shipListView.setCellFactory { | |
| val cell = ListCell<String?>() | 48 | 50 | val cell = ListCell<String?>() | |
| cell.textProperty().bind(cell.itemProperty()) | 49 | 51 | cell.textProperty().bind(cell.itemProperty()) | |
| cell.addEventFilter(MouseEvent.MOUSE_PRESSED) { event: MouseEvent -> | 50 | 52 | cell.addEventFilter(MouseEvent.MOUSE_PRESSED) { event: MouseEvent -> | |
| shipListView.requestFocus() | 51 | 53 | shipListView.requestFocus() | |
| if (!cell.isEmpty) { | 52 | 54 | if (!cell.isEmpty) { | |
| val index = cell.index | 53 | 55 | val index = cell.index | |
| if (selectionModel!!.selectedIndices.contains(index)) { | 54 | 56 | if (selectionModel!!.selectedIndices.contains(index)) { | |
| selectionModel.clearSelection() | 55 | 57 | selectionModel.clearSelection() | |
| } else { | 56 | 58 | } else { | |
| selectionModel.select(index) | 57 | 59 | selectionModel.select(index) | 
src/main/kotlin/application/model/Vessel.kt
View file @
78935bd
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| import java.util.* | 3 | 3 | import java.util.* | |
| 4 | 4 | |||
| 5 | 5 | |||
| class Vessel(val mmsi: String?) { | 6 | 6 | class Vessel(val mmsi: String?) { | |
| val messages: SortedMap<Long, Message> = sortedMapOf() | 7 | 7 | val messages: SortedMap<Long, Message> = sortedMapOf() | |
| var messageToDisplay: Message? = null | 8 | 8 | var messageToDisplay: Message? = null | |
| get() { | 9 | 9 | get() { | |
| // messages.forEach { (key, value) -> | 10 | 10 | field = | |
| // if(observableCurrentTime.value < key) { | 11 | 11 | messages.asSequence().map { it }.firstOrNull { observableCurrentTime.value < it.key }.let { it?.value } | |
| // field = value | 12 | |||
| // return@forEach | 13 | |||
| // } | 14 | |||
| // } | 15 | |||
| field = messages.asSequence().map{ it }.firstOrNull {observableCurrentTime.value < it.key}.let { it?.value } | 16 | |||
| return field | 17 | 12 | return field | |
| } | 18 | 13 | } | |
| 19 | 14 | |||
| // val timesNormalized : SortedMap<Long, LocalDateTime> = sortedMapOf() | 20 | |||
| 21 | ||||
| // fun getAllNormalizedDate(): SortedMap<Long, LocalDateTime> { | 22 | |||
| // var offset: Long? = null | 23 | |||
| // if(timesNormalized.size == 0){ | 24 | |||
| // messages.keys.forEach { | 25 | |||
| // val currentTime = it.toEpochSecond(ZoneOffset.UTC) | 26 | |||
| // if(offset == null){ | 27 | |||
| // offset = currentTime | 28 | |||
| // } | 29 | |||
| // timesNormalized[currentTime - offset!!]= it | 30 | |||
| // } | 31 | |||
| // } | 32 | |||
| // return timesNormalized | 33 | |||
| // } | 34 | |||
| 35 | ||||
| fun getAllTime(): ArrayList<MessageData?> { | 36 | 15 | fun getAllTime(): ArrayList<MessageData?> { | |
| val timeList = arrayListOf<MessageData?>() | 37 | 16 | val timeList = arrayListOf<MessageData?>() | |
| messages.forEach { | 38 | 17 | messages.forEach { | |
| timeList.add(it.value.time) | 39 | 18 | timeList.add(it.value.time) | |
| } | 40 | 19 | } | |
| 41 | 20 | |||
| return timeList | 42 | 21 | return timeList | |
| } | 43 | 22 | } | |
| 44 | 23 | |||
| fun getAllLatitude(): ArrayList<MessageData?> { | 45 | 24 | fun getAllLatitude(): ArrayList<MessageData?> { | |
| val latitudeList = arrayListOf<MessageData?>() | 46 | 25 | val latitudeList = arrayListOf<MessageData?>() | |
| messages.forEach { | 47 | 26 | messages.forEach { | |
| latitudeList.add(it.value.latitude) | 48 | 27 | latitudeList.add(it.value.latitude) | |
| } | 49 | 28 | } | |
| 50 | 29 | |||
| return latitudeList | 51 | 30 | return latitudeList | |
| } | 52 | 31 | } | |
| 53 | 32 | |||
| fun getAllLongitude(): ArrayList<MessageData?> { | 54 | 33 | fun getAllLongitude(): ArrayList<MessageData?> { | |
| val longitudeList = arrayListOf<MessageData?>() | 55 | 34 | val longitudeList = arrayListOf<MessageData?>() | |
| messages.forEach { | 56 | 35 | messages.forEach { | |
| longitudeList.add(it.value.longitude) | 57 | 36 | longitudeList.add(it.value.longitude) | |
| } | 58 | 37 | } | |
| 59 | 38 | |||
| return longitudeList | 60 | 39 | return longitudeList | |
| } | 61 | 40 | } | |
| 62 | 41 | |||
| fun getAllSpeedOverGround(): ArrayList<MessageData?> { | 63 | 42 | fun getAllSpeedOverGround(): ArrayList<MessageData?> { | |
| val speedOverGroundList = arrayListOf<MessageData?>() | 64 | 43 | val speedOverGroundList = arrayListOf<MessageData?>() | |
| messages.forEach { | 65 | 44 | messages.forEach { | |
| speedOverGroundList.add(it.value.speedOverGround) | 66 | 45 | speedOverGroundList.add(it.value.speedOverGround) | |
| } | 67 | 46 | } | |
| 68 | 47 | |||
| return speedOverGroundList | 69 | 48 | return speedOverGroundList | |
| } | 70 | 49 | } | |
| 71 | 50 | |||
| fun getAllCourseOverGround(): ArrayList<MessageData?> { | 72 | 51 | fun getAllCourseOverGround(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 73 | 52 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 74 | 53 | messages.forEach { | |
| res.add(it.value.courseOverGround) | 75 | 54 | res.add(it.value.courseOverGround) | |
| } | 76 | 55 | } | |
| 77 | 56 | |||
| return res | 78 | 57 | return res | |
| } | 79 | 58 | } | |
| 80 | 59 | |||
| fun getAllHeading(): ArrayList<MessageData?> { | 81 | 60 | fun getAllHeading(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 82 | 61 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 83 | 62 | messages.forEach { | |
| res.add(it.value.heading) | 84 | 63 | res.add(it.value.heading) | |
| } | 85 | 64 | } | |
| 86 | 65 | |||
| return res | 87 | 66 | return res | |
| } | 88 | 67 | } | |
| 89 | 68 | |||
| fun getAllVesselName(): ArrayList<MessageData?> { | 90 | 69 | fun getAllVesselName(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 91 | 70 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 92 | 71 | messages.forEach { | |
| res.add(it.value.vesselName) | 93 | 72 | res.add(it.value.vesselName) | |
| } | 94 | 73 | } | |
| return res | 95 | 74 | return res | |
| } | 96 | 75 | } | |
| 97 | 76 | |||
| fun getAllIMO(): ArrayList<MessageData?> { | 98 | 77 | fun getAllIMO(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 99 | 78 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 100 | 79 | messages.forEach { | |
| res.add(it.value.imo) | 101 | 80 | res.add(it.value.imo) | |
| } | 102 | 81 | } | |
| return res | 103 | 82 | return res | |
| } | 104 | 83 | } | |
| 105 | 84 | |||
| fun getAllCallSign(): ArrayList<MessageData?> { | 106 | 85 | fun getAllCallSign(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 107 | 86 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 108 | 87 | messages.forEach { | |
| res.add(it.value.callSign) | 109 | 88 | res.add(it.value.callSign) | |
| } | 110 | 89 | } | |
| return res | 111 | 90 | return res | |
| } | 112 | 91 | } | |
| 113 | 92 | |||
| fun getAllVesselType(): ArrayList<MessageData?> { | 114 | 93 | fun getAllVesselType(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 115 | 94 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 116 | 95 | messages.forEach { | |
| res.add(it.value.vesselType) | 117 | 96 | res.add(it.value.vesselType) | |
| } | 118 | 97 | } | |
| return res | 119 | 98 | return res | |
| } | 120 | 99 | } | |
| 121 | 100 | |||
| fun getAllStatus(): ArrayList<MessageData?> { | 122 | 101 | fun getAllStatus(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 123 | 102 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 124 | 103 | messages.forEach { | |
| res.add(it.value.status) | 125 | 104 | res.add(it.value.status) | |
| } | 126 | 105 | } | |
| return res | 127 | 106 | return res | |
| } | 128 | 107 | } | |
| 129 | 108 | |||
| fun getAllLength(): ArrayList<MessageData?> { | 130 | 109 | fun getAllLength(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 131 | 110 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 132 | 111 | messages.forEach { | |
| res.add(it.value.length) | 133 | 112 | res.add(it.value.length) | |
| } | 134 | 113 | } | |
| return res | 135 | 114 | return res | |
| } | 136 | 115 | } | |
| 137 | 116 | |||
| fun getAllWidth(): ArrayList<MessageData?> { | 138 | 117 | fun getAllWidth(): ArrayList<MessageData?> { | |
| val res = arrayListOf<MessageData?>() | 139 | 118 | val res = arrayListOf<MessageData?>() | |
| messages.forEach { | 140 | 119 | messages.forEach { | |
| res.add(it.value.width) | 141 | 120 | res.add(it.value.width) | |
| } | 142 | 121 | } | 
src/main/kotlin/map/LeafletMapView.kt
View file @
78935bd
| package map | 1 | 1 | package map | |
| 2 | 2 | |||
| import javafx.concurrent.Worker | 3 | 3 | import javafx.concurrent.Worker | |
| import javafx.scene.layout.StackPane | 4 | 4 | import javafx.scene.layout.StackPane | |
| import javafx.scene.paint.Color | 5 | 5 | import javafx.scene.paint.Color | |
| import javafx.scene.shape.Polygon | 6 | 6 | import javafx.scene.shape.Polygon | |
| import javafx.scene.web.WebEngine | 7 | 7 | import javafx.scene.web.WebEngine | |
| import javafx.scene.web.WebView | 8 | 8 | import javafx.scene.web.WebView | |
| import map.events.* | 9 | 9 | import map.events.* | |
| import netscape.javascript.JSObject | 10 | 10 | import netscape.javascript.JSObject | |
| import java.io.ByteArrayOutputStream | 11 | 11 | import java.io.ByteArrayOutputStream | |
| import java.io.File | 12 | 12 | import java.io.File | |
| import java.io.IOException | 13 | 13 | import java.io.IOException | |
| import java.net.URL | 14 | 14 | import java.net.URL | |
| import java.util.* | 15 | 15 | import java.util.* | |
| import java.util.concurrent.CompletableFuture | 16 | 16 | import java.util.concurrent.CompletableFuture | |
| import javax.imageio.ImageIO | 17 | 17 | import javax.imageio.ImageIO | |
| 18 | 18 | |||
| 19 | 19 | |||
| /** | 20 | 20 | /** | |
| * JavaFX component for displaying OpenStreetMap based maps by using the Leaflet.js JavaScript library inside a WebView | 21 | 21 | * JavaFX component for displaying OpenStreetMap based maps by using the Leaflet.js JavaScript library inside a WebView | |
| * browser component.<br/> | 22 | 22 | * browser component.<br/> | |
| * This component can be embedded most easily by placing it inside a StackPane, the component uses then the size of the | 23 | 23 | * This component can be embedded most easily by placing it inside a StackPane, the component uses then the size of the | |
| * parent automatically. | 24 | 24 | * parent automatically. | |
| * | 25 | 25 | * | |
| * @author Stefan Saring | 26 | 26 | * @author Stefan Saring | |
| * @author Niklas Kellner | 27 | 27 | * @author Niklas Kellner | |
| */ | 28 | 28 | */ | |
| class LeafletMapView : StackPane() { | 29 | 29 | class LeafletMapView : StackPane() { | |
| 30 | 30 | |||
| private val webView = WebView() | 31 | 31 | private val webView = WebView() | |
| private val webEngine: WebEngine = webView.engine | 32 | 32 | private val webEngine: WebEngine = webView.engine | |
| 33 | 33 | |||
| private var varNameSuffix: Int = 1 | 34 | 34 | private var varNameSuffix: Int = 1 | |
| private val mapClickEvent = MapClickEventMaker() | 35 | 35 | private val mapClickEvent = MapClickEventMaker() | |
| private val markerClickEvent = MarkerClickEventMaker() | 36 | 36 | private val markerClickEvent = MarkerClickEventMaker() | |
| private val mapMoveEvent = MapMoveEventMaker() | 37 | 37 | private val mapMoveEvent = MapMoveEventMaker() | |
| internal val zoomLimitSmallMarker = 8 | 38 | 38 | internal val zoomLimitSmallMarker = 8 | |
| 39 | 39 | |||
| /** | 40 | 40 | /** | |
| * Creates the LeafletMapView component, it does not show any map yet. | 41 | 41 | * Creates the LeafletMapView component, it does not show any map yet. | |
| */ | 42 | 42 | */ | |
| init { | 43 | 43 | init { | |
| this.children.add(webView) | 44 | 44 | this.children.add(webView) | |
| } | 45 | 45 | } | |
| 46 | 46 | |||
| /** | 47 | 47 | /** | |
| * Displays the initial map in the web view. Needs to be called and complete before adding any markers or tracks. | 48 | 48 | * Displays the initial map in the web view. Needs to be called and complete before adding any markers or tracks. | |
| * The returned CompletableFuture will provide the final map load state, the map can be used when the load has | 49 | 49 | * The returned CompletableFuture will provide the final map load state, the map can be used when the load has | |
| * completed with state SUCCEEDED (use CompletableFuture#whenComplete() for waiting to complete). | 50 | 50 | * completed with state SUCCEEDED (use CompletableFuture#whenComplete() for waiting to complete). | |
| * | 51 | 51 | * | |
| * @param mapConfig configuration of the map layers and controls | 52 | 52 | * @param mapConfig configuration of the map layers and controls | |
| * @return the CompletableFuture which will provide the final map load state | 53 | 53 | * @return the CompletableFuture which will provide the final map load state | |
| */ | 54 | 54 | */ | |
| fun displayMap(mapConfig: MapConfig): CompletableFuture<Worker.State> { | 55 | 55 | fun displayMap(mapConfig: MapConfig): CompletableFuture<Worker.State> { | |
| val finalMapLoadState = CompletableFuture<Worker.State>() | 56 | 56 | val finalMapLoadState = CompletableFuture<Worker.State>() | |
| 57 | 57 | |||
| webEngine.loadWorker.stateProperty().addListener { _, _, newValue -> | 58 | 58 | webEngine.loadWorker.stateProperty().addListener { _, _, newValue -> | |
| 59 | 59 | |||
| if (newValue == Worker.State.SUCCEEDED) { | 60 | 60 | if (newValue == Worker.State.SUCCEEDED) { | |
| executeMapSetupScripts(mapConfig) | 61 | 61 | executeMapSetupScripts(mapConfig) | |
| } | 62 | 62 | } | |
| 63 | 63 | |||
| if (newValue == Worker.State.SUCCEEDED || newValue == Worker.State.FAILED) { | 64 | 64 | if (newValue == Worker.State.SUCCEEDED || newValue == Worker.State.FAILED) { | |
| finalMapLoadState.complete(newValue) | 65 | 65 | finalMapLoadState.complete(newValue) | |
| } | 66 | 66 | } | |
| } | 67 | 67 | } | |
| 68 | 68 | |||
| val localFileUrl: URL = LeafletMapView::class.java.getResource("/leafletmap/leafletmap.html") | 69 | 69 | val localFileUrl: URL = LeafletMapView::class.java.getResource("/leafletmap/leafletmap.html") | |
| webEngine.load(localFileUrl.toExternalForm()) | 70 | 70 | webEngine.load(localFileUrl.toExternalForm()) | |
| return finalMapLoadState | 71 | 71 | return finalMapLoadState | |
| } | 72 | 72 | } | |
| 73 | 73 | |||
| private fun executeMapSetupScripts(mapConfig: MapConfig) { | 74 | 74 | private fun executeMapSetupScripts(mapConfig: MapConfig) { | |
| 75 | 75 | |||
| // execute scripts for layer definition | 76 | 76 | // execute scripts for layer definition | |
| mapConfig.layers.forEachIndexed { i, layer -> | 77 | 77 | mapConfig.layers.forEachIndexed { i, layer -> | |
| execScript("var layer${i + 1} = ${layer.javaScriptCode};") | 78 | 78 | execScript("var layer${i + 1} = ${layer.javaScriptCode};") | |
| } | 79 | 79 | } | |
| 80 | 80 | |||
| val jsLayers = mapConfig.layers | 81 | 81 | val jsLayers = mapConfig.layers | |
| .mapIndexed { i, layer -> "'${layer.displayName}': layer${i + 1}" } | 82 | 82 | .mapIndexed { i, layer -> "'${layer.displayName}': layer${i + 1}" } | |
| .joinToString(", ") | 83 | 83 | .joinToString(", ") | |
| execScript("var baseMaps = { $jsLayers };") | 84 | 84 | execScript("var baseMaps = { $jsLayers };") | |
| 85 | 85 | |||
| // execute script for map view creation (Leaflet attribution must not be a clickable link) | 86 | 86 | // execute script for map view creation (Leaflet attribution must not be a clickable link) | |
| execScript( | 87 | 87 | execScript( | |
| """ | 88 | 88 | """ | |
| |var myMap = L.map('map', { | 89 | 89 | |var myMap = L.map('map', { | |
| | center: new L.LatLng(${mapConfig.initialCenter.latitude}, ${mapConfig.initialCenter.longitude}), | 90 | 90 | | center: new L.LatLng(${mapConfig.initialCenter.latitude}, ${mapConfig.initialCenter.longitude}), | |
| | zoom: 1, | 91 | 91 | | zoom: 1, | |
| | zoomControl: false, | 92 | 92 | | zoomControl: false, | |
| | layers: [layer1] | 93 | 93 | | layers: [layer1] | |
| |}); | 94 | 94 | |}); | |
| |L.control.scale().addTo(myMap); | 95 | 95 | |L.control.scale().addTo(myMap); | |
| 96 | |var markers = [] | |||
| |var myRenderer = L.canvas({ padding: 0.5 }); | 96 | 97 | |var myRenderer = L.canvas({ padding: 0.5 }); | |
| |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); | 97 | 98 | |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); | |
| |var heatLayer = L.heatLayer([]).addTo(myMap);""".trimMargin() | 98 | 99 | |var heatLayer = L.heatLayer([]).addTo(myMap);""".trimMargin() | |
| ) | 99 | 100 | ) | |
| 100 | 101 | |||
| // eventZoomChangeIcon() | 101 | 102 | // eventZoomChangeIcon() | |
| 102 | 103 | |||
| // execute script for layer control definition if there are multiple layers | 103 | 104 | // execute script for layer control definition if there are multiple layers | |
| if (mapConfig.layers.size > 1) { | 104 | 105 | if (mapConfig.layers.size > 1) { | |
| execScript( | 105 | 106 | execScript( | |
| """ | 106 | 107 | """ | |
| |var overlayMaps = {}; | 107 | 108 | |var overlayMaps = {}; | |
| |L.control.layers(baseMaps, overlayMaps).addTo(myMap);""".trimMargin() | 108 | 109 | |L.control.layers(baseMaps, overlayMaps).addTo(myMap);""".trimMargin() | |
| ) | 109 | 110 | ) | |
| 110 | 111 | |||
| } | 111 | 112 | } | |
| 112 | 113 | |||
| // execute script for scale control definition | 113 | 114 | // execute script for scale control definition | |
| if (mapConfig.scaleControlConfig.show) { | 114 | 115 | if (mapConfig.scaleControlConfig.show) { | |
| execScript( | 115 | 116 | execScript( | |
| "L.control.scale({position: '${mapConfig.scaleControlConfig.position.positionName}', " + | 116 | 117 | "L.control.scale({position: '${mapConfig.scaleControlConfig.position.positionName}', " + | |
| "metric: ${mapConfig.scaleControlConfig.metric}, " + | 117 | 118 | "metric: ${mapConfig.scaleControlConfig.metric}, " + | |
| "imperial: ${!mapConfig.scaleControlConfig.metric}})" + | 118 | 119 | "imperial: ${!mapConfig.scaleControlConfig.metric}})" + | |
| ".addTo(myMap);" | 119 | 120 | ".addTo(myMap);" | |
| ) | 120 | 121 | ) | |
| } | 121 | 122 | } | |
| 122 | 123 | |||
| // execute script for zoom control definition | 123 | 124 | // execute script for zoom control definition | |
| if (mapConfig.zoomControlConfig.show) { | 124 | 125 | if (mapConfig.zoomControlConfig.show) { | |
| execScript( | 125 | 126 | execScript( | |
| "L.control.zoom({position: '${mapConfig.zoomControlConfig.position.positionName}'})" + | 126 | 127 | "L.control.zoom({position: '${mapConfig.zoomControlConfig.position.positionName}'})" + | |
| ".addTo(myMap);" | 127 | 128 | ".addTo(myMap);" | |
| ) | 128 | 129 | ) | |
| } | 129 | 130 | } | |
| } | 130 | 131 | } | |
| 131 | 132 | |||
| /** | 132 | 133 | /** | |
| * Sets the view of the map to the specified geographical center position and zoom level. | 133 | 134 | * Sets the view of the map to the specified geographical center position and zoom level. | |
| * | 134 | 135 | * | |
| * @param position map center position | 135 | 136 | * @param position map center position | |
| * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | 136 | 137 | * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | |
| */ | 137 | 138 | */ | |
| fun setView(position: LatLong, zoomLevel: Int) = | 138 | 139 | fun setView(position: LatLong, zoomLevel: Int) = | |
| execScript("myMap.setView([${position.latitude}, ${position.longitude}], $zoomLevel);") | 139 | 140 | execScript("myMap.setView([${position.latitude}, ${position.longitude}], $zoomLevel);") | |
| 140 | 141 | |||
| /** | 141 | 142 | /** | |
| * Pans the map to the specified geographical center position. | 142 | 143 | * Pans the map to the specified geographical center position. | |
| * | 143 | 144 | * | |
| * @param position map center position | 144 | 145 | * @param position map center position | |
| */ | 145 | 146 | */ | |
| fun panTo(position: LatLong) = | 146 | 147 | fun panTo(position: LatLong) = | |
| execScript("myMap.panTo([${position.latitude}, ${position.longitude}]);") | 147 | 148 | execScript("myMap.panTo([${position.latitude}, ${position.longitude}]);") | |
| 148 | 149 | |||
| /** | 149 | 150 | /** | |
| * Sets the zoom of the map to the specified level. | 150 | 151 | * Sets the zoom of the map to the specified level. | |
| * | 151 | 152 | * | |
| * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | 152 | 153 | * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | |
| */ | 153 | 154 | */ | |
| fun setZoom(zoomLevel: Int) = | 154 | 155 | fun setZoom(zoomLevel: Int) = | |
| execScript("myMap.setZoom([$zoomLevel]);") | 155 | 156 | execScript("myMap.setZoom([$zoomLevel]);") | |
| 156 | 157 | |||
| /** | 157 | 158 | /** | |
| * Adds a Marker Object to a map | 158 | 159 | * Adds a Marker Object to a map | |
| * | 159 | 160 | * | |
| * @param marker the Marker Object | 160 | 161 | * @param marker the Marker Object | |
| */ | 161 | 162 | */ | |
| fun addMarker(marker: Marker) { | 162 | 163 | fun addMarker(marker: Marker) { | |
| marker.addToMap(getNextMarkerName(), this) | 163 | 164 | marker.addToMap(getNextMarkerName(), this) | |
| } | 164 | 165 | } | |
| 165 | 166 | |||
| fun addCircle(circle: Circle) { | 166 | 167 | fun addCircle(circle: Circle) { | |
| circle.addToMap(this) | 167 | 168 | circle.addToMap(this) | |
| } | 168 | 169 | } | |
| 169 | 170 | |||
| fun addZone(zone: Zone) { | 170 | 171 | fun addZone(zone: Zone) { | |
| zone.addToMap(this) | 171 | 172 | zone.addToMap(this) | |
| } | 172 | 173 | } | |
| 173 | 174 | |||
| /** | 174 | 175 | /** | |
| * Removes an existing marker from the map | 175 | 176 | * Removes an existing marker from the map | |
| * | 176 | 177 | * | |
| * @param marker the Marker object | 177 | 178 | * @param marker the Marker object | |
| */ | 178 | 179 | */ | |
| fun removeMarker(marker: Marker) { | 179 | 180 | fun removeMarker(marker: Marker) { | |
| execScript("myMap.removeLayer(${marker.getName()});") | 180 | 181 | execScript("myMap.removeLayer(${marker.getName()});") | |
| } | 181 | 182 | } | |
| 182 | 183 | |||
| fun removeCircle(circle: Circle) { | 183 | 184 | fun removeCircle(circle: Circle) { | |
| circle.removeCircle(this) | 184 | 185 | circle.removeCircle(this) | |
| } | 185 | 186 | } | |
| 186 | 187 | |||
| fun removeZone(zone: Zone) { | 187 | 188 | fun removeZone(zone: Zone) { | |
| zone.removeZone() | 188 | 189 | zone.removeZone() | |
| } | 189 | 190 | } | |
| 190 | 191 | |||
| fun removeZone(id: String) { | 191 | 192 | fun removeZone(id: String) { | |
| val idSanitized = id.replace("-", "") | 192 | 193 | val idSanitized = id.replace("-", "") | |
| execScript("myMap.removeLayer(polygon$idSanitized);") | 193 | 194 | execScript("myMap.removeLayer(polygon$idSanitized);") | |
| } | 194 | 195 | } | |
| 195 | 196 | |||
| 196 | 197 | |||
| fun uppdateCircle(circle: Circle, latLong: LatLong, radius: Double) { | 197 | 198 | fun uppdateCircle(circle: Circle, latLong: LatLong, radius: Double) { | |
| circle.modifyCircle(latLong, radius) | 198 | 199 | circle.modifyCircle(latLong, radius) | |
| circle.uppdateMap() | 199 | 200 | circle.uppdateMap() | |
| } | 200 | 201 | } | |
| 201 | 202 | |||
| fun setEventMousePosition() { | 202 | 203 | fun setEventMousePosition() { | |
| execScript( | 203 | 204 | execScript( | |
| "var lat=0.0, lng=0.0;\n" + | 204 | 205 | "var lat=0.0, lng=0.0;\n" + | |
| "myMap.addEventListener('mousemove', function(ev) {\n" + | 205 | 206 | "myMap.addEventListener('mousemove', function(ev) {\n" + | |
| " lat = ev.latlng.lat;\n" + | 206 | 207 | " lat = ev.latlng.lat;\n" + | |
| " lng = ev.latlng.lng;\n" + | 207 | 208 | " lng = ev.latlng.lng;\n" + | |
| "});" | 208 | 209 | "});" | |
| ) | 209 | 210 | ) | |
| } | 210 | 211 | } | |
| 211 | 212 | |||
| fun getMousePosition(): LatLong { | 212 | 213 | fun getMousePosition(): LatLong { | |
| val lat = execScript("lat;") as Double | 213 | 214 | val lat = execScript("lat;") as Double | |
| val lng = execScript("lng;") as Double | 214 | 215 | val lng = execScript("lng;") as Double | |
| return LatLong(lat, lng) | 215 | 216 | return LatLong(lat, lng) | |
| } | 216 | 217 | } | |
| 217 | 218 | |||
| /** | 218 | 219 | /** | |
| * Adds a custom marker type | 219 | 220 | * Adds a custom marker type | |
| * | 220 | 221 | * | |
| * @param markerName the name of the marker type | 221 | 222 | * @param markerName the name of the marker type | |
| * @param iconUrl the url if the marker icon | 222 | 223 | * @param iconUrl the url if the marker icon | |
| */ | 223 | 224 | */ | |
| fun addCustomMarker(markerName: String, iconUrl: String): String { | 224 | 225 | fun addCustomMarker(markerName: String, iconUrl: String): String { | |
| execScript( | 225 | 226 | execScript( | |
| "var $markerName = L.icon({\n" + | 226 | 227 | "var $markerName = L.icon({\n" + | |
| "iconUrl: '${createImage(iconUrl, "png")}',\n" + | 227 | 228 | "iconUrl: '${createImage(iconUrl, "png")}',\n" + | |
| "iconSize: [24, 24],\n" + | 228 | 229 | "iconSize: [24, 24],\n" + | |
| "iconAnchor: [12, 12],\n" + | 229 | 230 | "iconAnchor: [12, 12],\n" + | |
| "});" | 230 | 231 | "});" | |
| ) | 231 | 232 | ) | |
| return markerName | 232 | 233 | return markerName | |
| } | 233 | 234 | } | |
| 234 | 235 | |||
| private fun createImage(path: String, type: String): String { | 235 | 236 | private fun createImage(path: String, type: String): String { | |
| val image = ImageIO.read(File(path)) | 236 | 237 | val image = ImageIO.read(File(path)) | |
| var imageString: String? = null | 237 | 238 | var imageString: String? = null | |
| val bos = ByteArrayOutputStream() | 238 | 239 | val bos = ByteArrayOutputStream() | |
| 239 | 240 | |||
| try { | 240 | 241 | try { | |
| ImageIO.write(image, type, bos) | 241 | 242 | ImageIO.write(image, type, bos) | |
| val imageBytes = bos.toByteArray() | 242 | 243 | val imageBytes = bos.toByteArray() | |
| 243 | 244 | |||
| val encoder = Base64.getEncoder() | 244 | 245 | val encoder = Base64.getEncoder() | |
| imageString = encoder.encodeToString(imageBytes) | 245 | 246 | imageString = encoder.encodeToString(imageBytes) | |
| 246 | 247 | |||
| bos.close() | 247 | 248 | bos.close() | |
| } catch (e: IOException) { | 248 | 249 | } catch (e: IOException) { | |
| e.printStackTrace() | 249 | 250 | e.printStackTrace() | |
| } | 250 | 251 | } | |
| return "data:image/$type;base64,$imageString" | 251 | 252 | return "data:image/$type;base64,$imageString" | |
| } | 252 | 253 | } | |
| 253 | 254 | |||
| /** | 254 | 255 | /** | |
| * Sets the onMarkerClickListener | 255 | 256 | * Sets the onMarkerClickListener | |
| * | 256 | 257 | * | |
| * @param listener the onMarerClickEventListener | 257 | 258 | * @param listener the onMarerClickEventListener | |
| */ | 258 | 259 | */ | |
| fun onMarkerClick(listener: MarkerClickEventListener) { | 259 | 260 | fun onMarkerClick(listener: MarkerClickEventListener) { | |
| val win = execScript("document") as JSObject | 260 | 261 | val win = execScript("document") as JSObject | |
| win.setMember("java", this) | 261 | 262 | win.setMember("java", this) | |
| markerClickEvent.addListener(listener) | 262 | 263 | markerClickEvent.addListener(listener) | |
| } | 263 | 264 | } | |
| 264 | 265 | |||
| /** | 265 | 266 | /** | |
| * Handles the callback from the markerClickEvent | 266 | 267 | * Handles the callback from the markerClickEvent | |
| */ | 267 | 268 | */ | |
| fun markerClick(title: String) { | 268 | 269 | fun markerClick(title: String) { | |
| markerClickEvent.MarkerClickEvent(title) | 269 | 270 | markerClickEvent.MarkerClickEvent(title) | |
| } | 270 | 271 | } | |
| 271 | 272 | |||
| /** | 272 | 273 | /** | |
| * Sets the onMapMoveListener | 273 | 274 | * Sets the onMapMoveListener | |
| * | 274 | 275 | * | |
| * @param listener the MapMoveEventListener | 275 | 276 | * @param listener the MapMoveEventListener | |
| */ | 276 | 277 | */ | |
| fun onMapMove(listener: MapMoveEventListener) { | 277 | 278 | fun onMapMove(listener: MapMoveEventListener) { | |
| val win = execScript("document") as JSObject | 278 | 279 | val win = execScript("document") as JSObject | |
| win.setMember("java", this) | 279 | 280 | win.setMember("java", this) | |
| execScript("myMap.on('moveend', function(e){ document.java.mapMove(myMap.getCenter().lat, myMap.getCenter().lng);});") | 280 | 281 | execScript("myMap.on('moveend', function(e){ document.java.mapMove(myMap.getCenter().lat, myMap.getCenter().lng);});") | |
| mapMoveEvent.addListener(listener) | 281 | 282 | mapMoveEvent.addListener(listener) | |
| } | 282 | 283 | } | |
| 283 | 284 | |||
| /** | 284 | 285 | /** | |
| * Handles the callback from the mapMoveEvent | 285 | 286 | * Handles the callback from the mapMoveEvent | |
| */ | 286 | 287 | */ | |
| fun mapMove(lat: Double, lng: Double) { | 287 | 288 | fun mapMove(lat: Double, lng: Double) { | |
| val latlng = LatLong(lat, lng) | 288 | 289 | val latlng = LatLong(lat, lng) | |
| mapMoveEvent.MapMoveEvent(latlng) | 289 | 290 | mapMoveEvent.MapMoveEvent(latlng) | |
| } | 290 | 291 | } | |
| 291 | 292 | |||
| /** | 292 | 293 | /** | |
| * Sets the onMapClickListener | 293 | 294 | * Sets the onMapClickListener | |
| * | 294 | 295 | * | |
| * @param listener the onMapClickEventListener | 295 | 296 | * @param listener the onMapClickEventListener | |
| */ | 296 | 297 | */ | |
| fun onMapClick(listener: MapClickEventListener) { | 297 | 298 | fun onMapClick(listener: MapClickEventListener) { | |
| val win = execScript("document") as JSObject | 298 | 299 | val win = execScript("document") as JSObject | |
| win.setMember("java", this) | 299 | 300 | win.setMember("java", this) | |
| execScript("myMap.on('click', function(e){ document.java.mapClick(e.latlng.lat, e.latlng.lng);});") | 300 | 301 | execScript("myMap.on('click', function(e){ document.java.mapClick(e.latlng.lat, e.latlng.lng);});") | |
| mapClickEvent.addListener(listener) | 301 | 302 | mapClickEvent.addListener(listener) | |
| } | 302 | 303 | } | |
| 303 | 304 | |||
| /** | 304 | 305 | /** | |
| * Handles the callback from the mapClickEvent | 305 | 306 | * Handles the callback from the mapClickEvent | |
| */ | 306 | 307 | */ | |
| fun mapClick(lat: Double, lng: Double) { | 307 | 308 | fun mapClick(lat: Double, lng: Double) { | |
| val latlng = LatLong(lat, lng) | 308 | 309 | val latlng = LatLong(lat, lng) | |
| mapClickEvent.MapClickEvent(latlng) | 309 | 310 | mapClickEvent.MapClickEvent(latlng) | |
| } | 310 | 311 | } | |
| 311 | 312 | |||
| /** | 312 | 313 | /** | |
| * Draws a track path along the specified positions. | 313 | 314 | * Draws a track path along the specified positions. | |
| * | 314 | 315 | * | |
| * @param positions list of track positions | 315 | 316 | * @param positions list of track positions | |
| */ | 316 | 317 | */ | |
| fun addTrack(positions: List<LatLong>) { | 317 | 318 | fun addTrack(positions: List<LatLong>) { | |
| 318 | 319 | |||
| val jsPositions = positions | 319 | 320 | val jsPositions = positions | |
| .map { " [${it.latitude}, ${it.longitude}]" } | 320 | 321 | .map { " [${it.latitude}, ${it.longitude}]" } | |
| .joinToString(", \n") | 321 | 322 | .joinToString(", \n") | |
| 322 | 323 | |||
| execScript( | 323 | 324 | execScript( | |
| """ | 324 | 325 | """ | |
| |var latLngs = [ | 325 | 326 | |var latLngs = [ | |
| |$jsPositions | 326 | 327 | |$jsPositions | |
| |]; | 327 | 328 | |]; | |
| |var polyline = L.polyline(latLngs, {color: 'red', weight: 2}).addTo(myMap);""".trimMargin() | 328 | 329 | |var polyline = L.polyline(latLngs, {color: 'red', weight: 2}).addTo(myMap);""".trimMargin() | |
| ) | 329 | 330 | ) | |
| } | 330 | 331 | } | |
| 331 | 332 | |||
| fun clearAllLayer() { | 332 | 333 | fun clearAllLayer() { | |
| execScript(""" | 333 | 334 | execScript(""" | |
| myMap.eachLayer(function (layer) { | 334 | 335 | myMap.eachLayer(function (layer) { | |
| map.removeLayer(layer); | 335 | 336 | map.removeLayer(layer); | |
| }); | 336 | 337 | }); | |
| """.trimIndent()) | 337 | 338 | """.trimIndent()) | |
| } | 338 | 339 | } | |
| 339 | 340 | |||
| fun addTrack(positions: List<LatLong>, id: String, color: Color, tooltip: String) { | 340 | 341 | fun addTrack(positions: List<LatLong>, id: String, color: Color, tooltip: String) { | |
| 341 | 342 | |||
| val jsPositions = positions | 342 | 343 | val jsPositions = positions | |
| .map { " [${it.latitude}, ${it.longitude}]" } | 343 | 344 | .map { " [${it.latitude}, ${it.longitude}]" } | |
| .joinToString(", \n") | 344 | 345 | .joinToString(", \n") | |
| 345 | 346 | |||
| val cleanTooltip = tooltip.replace("'", "'") | 346 | 347 | val cleanTooltip = tooltip.replace("'", "'") | |
| execScript( | 347 | 348 | execScript( | |
| """ | 348 | 349 | """ | |
| |var latLngs = [ | 349 | 350 | |var latLngs = [ | |
| |$jsPositions | 350 | 351 | |$jsPositions | |
| |]; | 351 | 352 | |]; | |
| |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255) | 352 | 353 | |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255) | |
| .toInt()},${Math.floor(color.getBlue() * 255).toInt()})"; | 353 | 354 | .toInt()},${Math.floor(color.getBlue() * 255).toInt()})"; | |
| |var polyline$id = L.polyline(latLngs, {color: color, weight: 2, zIndexOffset: 200}).bindTooltip('$cleanTooltip', {sticky: true}).addTo(trackGroup)""".trimMargin() | 354 | 355 | |var polyline$id = L.polyline(latLngs, {color: color, weight: 2, zIndexOffset: 200}).bindTooltip('$cleanTooltip', {sticky: true}).addTo(trackGroup)""".trimMargin() | |
| ) | 355 | 356 | ) | |
| } | 356 | 357 | } | |
| 357 | 358 | |||
| fun makeVesselTrackTransparent(id: String) { | 358 | 359 | fun makeVesselTrackTransparent(id: String) { | |
| execScript("polyline$id.setStyle({opacity: 0.5});") | 359 | 360 | execScript("polyline$id.setStyle({opacity: 0.5});") | |
| } | 360 | 361 | } | |
| 361 | 362 | |||
| fun highlightTrack(id: String) { | 362 | 363 | fun highlightTrack(id: String) { | |
| execScript("polyline$id.setStyle({weight: 4});") | 363 | 364 | execScript("polyline$id.setStyle({weight: 4});") | |
| } | 364 | 365 | } | |
| 365 | 366 | |||
| fun normalizeVesselTrack(id: String) { | 366 | 367 | fun normalizeVesselTrack(id: String) { | |
| execScript("polyline$id.setStyle({opacity: 1,weight: 2});") | 367 | 368 | execScript("polyline$id.setStyle({opacity: 1,weight: 2});") | 
src/main/kotlin/map/MapDisplayer.kt
View file @
78935bd
| 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 | clearMarker(map) | |||
| } | 9 | 10 | } | |
| 10 | 11 | |||
| fun clearMapCluster(map: LeafletMapView) { | 11 | 12 | fun clearMapCluster(map: LeafletMapView) { | |
| map.execScript( | 12 | 13 | map.execScript( | |
| """ | 13 | 14 | """ | |
| |myMap.removeLayer(markerClusters); | 14 | 15 | |myMap.removeLayer(markerClusters); | |
| |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); | 15 | 16 | |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); | |
| """.trimMargin() | 16 | 17 | """.trimMargin() | |
| ) | 17 | 18 | ) | |
| } | 18 | 19 | } | |
| 19 | 20 | |||
| fun clearMapCanvas(map: LeafletMapView) { | 20 | 21 | fun clearMapCanvas(map: LeafletMapView) { | |
| map.execScript( | 21 | 22 | map.execScript( | |
| """ | 22 | 23 | """ | |
| |myRenderer.removeFrom(myMap); | 23 | 24 | |myRenderer.removeFrom(myMap); | |
| |var myRenderer = L.canvas({ padding: 0.5 }); | 24 | 25 | |var myRenderer = L.canvas({ padding: 0.5 }); | |
| """.trimMargin() | 25 | 26 | """.trimMargin() | |
| ) | 26 | 27 | ) | |
| } | 27 | 28 | } | |
| 28 | 29 | |||
| fun clearHeatMap(map: LeafletMapView) { | 29 | 30 | fun clearHeatMap(map: LeafletMapView) { | |
| map.execScript( | 30 | 31 | map.execScript( | |
| """ | 31 | 32 | """ | |
| |heatLayer.removeFrom(myMap); | 32 | 33 | |heatLayer.removeFrom(myMap); | |
| |var heatLayer = L.heatLayer([]).addTo(myMap); | 33 | 34 | |var heatLayer = L.heatLayer([]).addTo(myMap); | |
| """.trimMargin() | 34 | 35 | """.trimMargin() | |
| ) | 35 | 36 | ) | |
| } | 36 | 37 | } | |
| 37 | 38 | |||
| 39 | fun clearMarker(map: LeafletMapView) { | |||
| 40 | map.execScript( | |||
| 41 | """ | |||
| 42 | |for(var i = 0; i < markers.length; i++){ | |||
| 43 | |myMap.removeLayer(markers[i]); | |||
| 44 | |} | |||
| 45 | |markers = [] | |||
| 46 | """.trimMargin() | |||
| 47 | ) | |||
| 48 | } | |||
| 49 | ||||
| fun displayAllMessageOnMap(map: LeafletMapView) { | 38 | 50 | fun displayAllMessageOnMap(map: LeafletMapView) { | |
| clearMap(map) | 39 | 51 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 40 | 52 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 41 | 53 | 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 | 54 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | |
| } | 43 | 55 | } | |
| } | 44 | 56 | } | |
| } | 45 | 57 | } | |
| 46 | 58 | |||
| 59 | fun displayTimedAllMessageOnMap(map: LeafletMapView) { | |||
| 60 | clearMap(map) | |||
| 61 | observableVessel.vessels.forEach { (_, value) -> | |||
| 62 | val message = value.messageToDisplay ?: return@forEach | |||
| 63 | map.execScript("markers.push(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap))") | |||
| 64 | } | |||
| 65 | } | |||
| 66 | ||||
| fun displayAllMessageOnMap(map: LeafletMapView, selectedMMSI: String) { | 47 | 67 | fun displayAllMessageOnMap(map: LeafletMapView, selectedMMSI: String) { | |
| clearMap(map) | 48 | 68 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 49 | 69 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 50 | 70 | value.messages.forEach { (_, message) -> | |
| if (selectedMMSI == message.mmsi.value) { | 51 | 71 | if (selectedMMSI == message.mmsi.value) { | |
| map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap)") | 52 | 72 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap)") | |
| } else { | 53 | 73 | } else { | |
| map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | 54 | 74 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | |
| } | 55 | 75 | } | |
| } | 56 | 76 | } | |
| } | 57 | 77 | } | |
| } | 58 | 78 | } | |
| 59 | 79 | |||
| 80 | fun displayTimedAllMessageOnMap(map: LeafletMapView, selectedMMSI: String) { | |||
| 81 | clearMap(map) | |||
| 82 | observableVessel.vessels.forEach { (_, value) -> | |||
| 83 | val message = value.messageToDisplay ?: return@forEach | |||
| 84 | if (selectedMMSI == message.mmsi.value) { | |||
| 85 | map.execScript("markers.push(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap))") | |||
| 86 | } else { | |||
| 87 | map.execScript("markers.push(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap))") | |||
| 88 | } | |||
| 89 | } | |||
| 90 | } | |||
| 91 | ||||
| fun displayClusterMessageOnMap(map: LeafletMapView) { | 60 | 92 | fun displayClusterMessageOnMap(map: LeafletMapView) { | |
| clearMap(map) | 61 | 93 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 62 | 94 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 63 | 95 | 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 | 96 | map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |
| } | 65 | 97 | } | |
| } | 66 | 98 | } | |
| map.execScript("myMap.addLayer(markerClusters);") | 67 | 99 | map.execScript("myMap.addLayer(markerClusters);") | |
| } | 68 | 100 | } | |
| 69 | 101 | |||
| fun displayTargetedVessels(map: LeafletMapView) { | 70 | 102 | fun displayTimedClusterMessageOnMap(map: LeafletMapView) { | |
| clearMap(map) | 71 | 103 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 72 | 104 | observableVessel.vessels.forEach { (_, value) -> | |
| val message = value.messageToDisplay ?: return@forEach | 73 | 105 | val message = value.messageToDisplay ?: return@forEach | |
| map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | 74 | 106 | map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |
| } | 75 | 107 | } | |
| map.execScript("myMap.addLayer(markerClusters);") | 76 | 108 | map.execScript("myMap.addLayer(markerClusters);") | |
| } | 77 | 109 | } | |
| 78 | 110 | |||
| fun displayClusterMessageOnMap(map: LeafletMapView, selectedMMSI: String) { | 79 | 111 | fun displayClusterMessageOnMap(map: LeafletMapView, selectedMMSI: String) { | |
| clearMap(map) | 80 | 112 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 81 | 113 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 82 | 114 | value.messages.forEach { (_, message) -> | |
| if (selectedMMSI == message.mmsi.value) { | 83 | 115 | if (selectedMMSI == message.mmsi.value) { | |
| map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") | 84 | 116 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") | |
| } else { | 85 | 117 | } else { | |
| map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | 86 | 118 | map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |
| } | 87 | 119 | } | |
| } | 88 | 120 | } | |
| } | 89 | 121 | } | |
| map.execScript("myMap.addLayer(markerClusters);") | 90 | 122 | map.execScript("myMap.addLayer(markerClusters);") | |
| } | 91 | 123 | } | |
| 92 | 124 | |||
| 125 | fun displayTimedClusterMessageOnMap(map: LeafletMapView, selectedMMSI: String) { | |||
| 126 | clearMap(map) | |||
| 127 | observableVessel.vessels.forEach { (_, value) -> | |||
| 128 | val message = value.messageToDisplay ?: return@forEach | |||
| 129 | if (selectedMMSI == message.mmsi.value) { | |||
| 130 | map.execScript("markers.push(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {radius: 2, color: '#ff4040'}).addTo(myMap));") | |||
| 131 | } else { | |||
| 132 | map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |||
| 133 | } | |||
| 134 | } | |||
| 135 | map.execScript("myMap.addLayer(markerClusters);") | |||
| 136 | } | |||
| 137 | ||||
| fun displayHeatMapOnMap(map: LeafletMapView) { | 93 | 138 | fun displayHeatMapOnMap(map: LeafletMapView) { | |
| clearMap(map) | 94 | 139 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 95 | 140 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 96 | 141 | value.messages.forEach { (_, message) -> | |
| map.execScript("heatLayer.addLatLng([${message.latitude.value}, ${message.longitude.value}]);") | 97 | 142 | map.execScript("heatLayer.addLatLng([${message.latitude.value}, ${message.longitude.value}]);") | |
| } | 98 | 143 | } | |
| } | 99 | 144 | } | |
| } | 100 | 145 | } | |
| 101 | 146 | |||
| 147 | fun displayTimedHeatMapOnMap(map: LeafletMapView) { | |||
| 148 | clearMap(map) | |||
| 149 | observableVessel.vessels.forEach { (_, value) -> | |||
| 150 | val message = value.messageToDisplay ?: return@forEach | |||
| 151 | map.execScript("heatLayer.addLatLng([${message.latitude.value}, ${message.longitude.value}]);") | |||
| 152 | } | |||
| 153 | } | |||
| 154 | ||||
| fun displayHeatMapOnMap(map: LeafletMapView, selectedMMSI: String) { | 102 | 155 | fun displayHeatMapOnMap(map: LeafletMapView, selectedMMSI: String) { | |
| clearMap(map) | 103 | 156 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 104 | 157 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 105 | 158 | value.messages.forEach { (_, message) -> | |
| if (selectedMMSI == message.mmsi.value) { | 106 | 159 | if (selectedMMSI == message.mmsi.value) { | |
| map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") | 107 | 160 | map.execScript("L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") | |
| } else { | 108 | 161 | } else { | |
| map.execScript("heatLayer.addLatLng([${message.latitude.value}, ${message.longitude.value}]);") | 109 | 162 | map.execScript("heatLayer.addLatLng([${message.latitude.value}, ${message.longitude.value}]);") | |
| } | 110 | 163 | } | |
| } | 111 | 164 | } | |
| } | 112 | 165 | } | |
| map.execScript("myMap.addLayer(markerClusters);") | 113 | 166 | map.execScript("myMap.addLayer(markerClusters);") | |
| 167 | } | |||
| 168 | ||||
| 169 | fun displayTimedHeatMapOnMap(map: LeafletMapView, selectedMMSI: String) { | 
src/main/resources/gui/timePanel.fxml
View file @
78935bd
| <?xml version="1.0" encoding="UTF-8"?> | 1 | 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | 2 | |||
| <?import javafx.geometry.*?> | 3 | 3 | <?import javafx.geometry.*?> | |
| <?import javafx.scene.control.*?> | 4 | 4 | <?import javafx.scene.control.*?> | |
| <?import javafx.scene.layout.*?> | 5 | 5 | <?import javafx.scene.layout.*?> | |
| 6 | 6 | |||
| <HBox alignment="CENTER" prefHeight="65.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.controller.TimePanel"> | 7 | 7 | <HBox alignment="CENTER" prefHeight="65.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.controller.TimePanel"> | |
| <children> | 8 | 8 | <children> | |
| <Button fx:id="timePlay" alignment="CENTER" mnemonicParsing="false" text="Play"> | 9 | 9 | <Button fx:id="timePlay" alignment="CENTER" mnemonicParsing="false" text="Play"> | |
| <HBox.margin> | 10 | 10 | <HBox.margin> | |
| <Insets left="5.0" right="5.0" /> | 11 | 11 | <Insets left="5.0" right="5.0" /> | |
| </HBox.margin></Button> | 12 | 12 | </HBox.margin></Button> | |
| <Button fx:id="timeStop" mnemonicParsing="false" text="Stop"> | 13 | 13 | <Button fx:id="timeStop" mnemonicParsing="false" text="Stop"> | |
| <HBox.margin> | 14 | 14 | <HBox.margin> | |
| <Insets left="5.0" right="5.0" /> | 15 | 15 | <Insets left="5.0" right="5.0" /> | |
| </HBox.margin></Button> | 16 | 16 | </HBox.margin></Button> | |
| <Slider fx:id="timeSlider"> | 17 | 17 | <Slider fx:id="timeSlider" prefHeight="14.0" prefWidth="475.0"> | |
| <HBox.margin> | 18 | 18 | <HBox.margin> | |
| <Insets left="5.0" right="5.0" /> | 19 | 19 | <Insets left="5.0" right="5.0" /> | |
| </HBox.margin></Slider> | 20 | 20 | </HBox.margin></Slider> | |
| </children> | 21 | 21 | </children> |