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> |