Commit 3b26be8f9997f5c644d7ffb3a76944f151e66325
1 parent
8108656dd1
Exists in
master
and in
1 other branch
controle the time with slider
Showing 19 changed files with 243 additions and 56 deletions Side-by-side Diff
- src/main/kotlin/application/controller/DataPanelController.kt
- src/main/kotlin/application/controller/MapPanelController.kt
- src/main/kotlin/application/controller/MenuBarController.kt
- src/main/kotlin/application/controller/TimePanel.kt
- src/main/kotlin/application/model/Context.kt
- src/main/kotlin/application/model/CurrentTime.kt
- src/main/kotlin/application/model/MapState.kt
- src/main/kotlin/application/model/ObservableCurrentTime.kt
- src/main/kotlin/application/model/ObservableMapState.kt
- src/main/kotlin/application/model/ObservableReplayState.kt
- src/main/kotlin/application/model/ObservableState.kt
- src/main/kotlin/application/model/ReplayState.kt
- src/main/kotlin/application/model/State.kt
- src/main/kotlin/application/model/StateListener.kt
- src/main/kotlin/application/model/Vessel.kt
- src/main/kotlin/application/model/VesselGenerator.kt
- src/main/kotlin/map/MapDisplayer.kt
- src/main/resources/gui/mapPanel.fxml
- src/main/resources/gui/timePanel.fxml
src/main/kotlin/application/controller/DataPanelController.kt
View file @
3b26be8
src/main/kotlin/application/controller/MapPanelController.kt
View file @
3b26be8
1 | 1 | package application.controller |
2 | 2 | |
3 | 3 | import application.model.* |
4 | -import application.model.State.* | |
4 | +import application.model.MapState.* | |
5 | 5 | import javafx.fxml.FXML |
6 | 6 | import javafx.fxml.Initializable |
7 | 7 | import javafx.scene.layout.StackPane |
... | ... | @@ -22,6 +22,8 @@ |
22 | 22 | setObservableVesselListener() |
23 | 23 | setObservableSelectedVesselListener() |
24 | 24 | setStateListener() |
25 | + observableCurrentTime() | |
26 | + | |
25 | 27 | /*val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig()) |
26 | 28 | completeFutureMap.whenComplete{ |
27 | 29 | workerState, _ -> |
... | ... | @@ -33,8 +35,8 @@ |
33 | 35 | } |
34 | 36 | |
35 | 37 | private fun setStateListener() { |
36 | - observableState.listeners.add(object : StateListener { | |
37 | - override fun onValueChanged(newValue: State) { | |
38 | + observableMapState.listeners.add(object : StateListener { | |
39 | + override fun onValueChanged(newValue: MapState) { | |
38 | 40 | if (observableSelectedVessel.vessel.mmsi != null) { |
39 | 41 | updateMap(observableSelectedVessel.vessel.mmsi!!) |
40 | 42 | } else { |
41 | 43 | |
42 | 44 | |
... | ... | @@ -44,16 +46,28 @@ |
44 | 46 | }) |
45 | 47 | } |
46 | 48 | |
49 | + private fun observableCurrentTime() { | |
50 | + observableCurrentTime.listeners.add(object : CurrentTime{ | |
51 | + override fun onValueChanged(newValue: Int) { | |
52 | + updateMap() | |
53 | + } | |
54 | + }) | |
55 | + } | |
56 | + | |
47 | 57 | private fun updateMap() { |
48 | - when (observableState.state) { | |
49 | - ALL_MESSAGES -> displayAllMessageOnMap(mapView) | |
50 | - CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) | |
51 | - HEAT_MAP -> displayHeatMapOnMap(mapView) | |
58 | + if (observableIsReplayState.value){ | |
59 | + displayTargetedVessels(mapView) | |
60 | + } else { | |
61 | + when (observableMapState.state) { | |
62 | + ALL_MESSAGES -> displayAllMessageOnMap(mapView) | |
63 | + CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) | |
64 | + HEAT_MAP -> displayHeatMapOnMap(mapView) | |
65 | + } | |
52 | 66 | } |
53 | 67 | } |
54 | 68 | |
55 | 69 | private fun updateMap(selectedMMSI: String) { |
56 | - when (observableState.state) { | |
70 | + when (observableMapState.state) { | |
57 | 71 | ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) |
58 | 72 | CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI) |
59 | 73 | HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI) |
src/main/kotlin/application/controller/MenuBarController.kt
View file @
3b26be8
1 | 1 | package application.controller |
2 | 2 | |
3 | -import application.model.State.* | |
3 | +import application.model.MapState.* | |
4 | 4 | import application.model.createVesselCollection |
5 | -import application.model.observableState | |
5 | +import application.model.observableMapState | |
6 | 6 | import application.model.observableVessel |
7 | 7 | import javafx.event.EventHandler |
8 | 8 | import javafx.fxml.FXML |
... | ... | @@ -39,7 +39,7 @@ |
39 | 39 | setOnActionAllMessageButton() |
40 | 40 | setOnActionClusteredMessageButton() |
41 | 41 | setOnActionHeatMapButton() |
42 | - observableState.state = CLUSTERED_MESSAGES | |
42 | + observableMapState.state = CLUSTERED_MESSAGES | |
43 | 43 | allMessages.isSelected = false |
44 | 44 | clusteredMessage.isSelected = true |
45 | 45 | heatMap.isSelected = false |
... | ... | @@ -71,7 +71,7 @@ |
71 | 71 | |
72 | 72 | private fun setOnActionAllMessageButton() { |
73 | 73 | allMessages.onAction = EventHandler { |
74 | - observableState.state = ALL_MESSAGES | |
74 | + observableMapState.state = ALL_MESSAGES | |
75 | 75 | allMessages.isSelected = true |
76 | 76 | clusteredMessage.isSelected = false |
77 | 77 | heatMap.isSelected = false |
... | ... | @@ -80,7 +80,7 @@ |
80 | 80 | |
81 | 81 | private fun setOnActionClusteredMessageButton() { |
82 | 82 | clusteredMessage.onAction = EventHandler { |
83 | - observableState.state = CLUSTERED_MESSAGES | |
83 | + observableMapState.state = CLUSTERED_MESSAGES | |
84 | 84 | heatMap.isSelected = false |
85 | 85 | allMessages.isSelected = false |
86 | 86 | clusteredMessage.isSelected = true |
... | ... | @@ -89,7 +89,7 @@ |
89 | 89 | |
90 | 90 | private fun setOnActionHeatMapButton() { |
91 | 91 | heatMap.onAction = EventHandler { |
92 | - observableState.state = HEAT_MAP | |
92 | + observableMapState.state = HEAT_MAP | |
93 | 93 | heatMap.isSelected = true |
94 | 94 | clusteredMessage.isSelected = false |
95 | 95 | allMessages.isSelected = false |
src/main/kotlin/application/controller/TimePanel.kt
View file @
3b26be8
1 | +package application.controller | |
2 | + | |
3 | +import application.model.* | |
4 | +import javafx.fxml.FXML | |
5 | +import javafx.fxml.Initializable | |
6 | +import javafx.scene.control.Button | |
7 | +import javafx.scene.control.Slider | |
8 | +import java.net.URL | |
9 | +import java.util.* | |
10 | + | |
11 | + | |
12 | +class TimePanel : Initializable { | |
13 | + | |
14 | + @FXML | |
15 | + var timeSlider = Slider() | |
16 | + | |
17 | + @FXML | |
18 | + var timeStop = Button() | |
19 | + | |
20 | + @FXML | |
21 | + var timePlay = Button() | |
22 | + | |
23 | + | |
24 | + override fun initialize(location: URL?, resources: ResourceBundle?) { | |
25 | + setSliderMinMax() | |
26 | + setSliderListener() | |
27 | + | |
28 | + | |
29 | + } | |
30 | + | |
31 | + private fun setSliderMinMax() { | |
32 | + observableVessel.listeners.add(object : MessageListener{ | |
33 | + override fun onValueChanged(newValue: MutableMap<String?, Vessel>) { | |
34 | + timeSlider.max = Vessel.maxTime.toDouble() | |
35 | + timeSlider.min = Vessel.minTime.toDouble() | |
36 | + } | |
37 | + }) | |
38 | + } | |
39 | + | |
40 | + private fun setSliderListener() { | |
41 | + timeSlider.valueProperty().addListener { _, _, newValue -> | |
42 | + observableCurrentTime.value = newValue.toInt() | |
43 | + } | |
44 | + } | |
45 | + | |
46 | +} |
src/main/kotlin/application/model/Context.kt
View file @
3b26be8
... | ... | @@ -4,5 +4,9 @@ |
4 | 4 | |
5 | 5 | val observableSelectedVessel: ObservableSelectedVessel = ObservableSelectedVessel() |
6 | 6 | |
7 | -val observableState: ObservableState = ObservableState() | |
7 | +val observableMapState: ObservableMapState = ObservableMapState() | |
8 | + | |
9 | +val observableIsReplayState: ObservableReplayState = ObservableReplayState() | |
10 | + | |
11 | +val observableCurrentTime: ObservableCurrentTime = ObservableCurrentTime() |
src/main/kotlin/application/model/CurrentTime.kt
View file @
3b26be8
src/main/kotlin/application/model/MapState.kt
View file @
3b26be8
src/main/kotlin/application/model/ObservableCurrentTime.kt
View file @
3b26be8
1 | +package application.model | |
2 | + | |
3 | +import kotlin.properties.Delegates | |
4 | + | |
5 | +class ObservableCurrentTime { | |
6 | + | |
7 | + val listeners: MutableList<CurrentTime> = mutableListOf() | |
8 | + | |
9 | + var value: Int by Delegates.observable( | |
10 | + initialValue = 0, | |
11 | + onChange = { _, _, new -> | |
12 | + run { | |
13 | + listeners.forEach { | |
14 | + it.onValueChanged(new) | |
15 | + } | |
16 | + } | |
17 | + } | |
18 | + ) | |
19 | + | |
20 | +} |
src/main/kotlin/application/model/ObservableMapState.kt
View file @
3b26be8
1 | +package application.model | |
2 | + | |
3 | +import kotlin.properties.Delegates | |
4 | + | |
5 | +class ObservableMapState { | |
6 | + val listeners: MutableList<StateListener> = mutableListOf() | |
7 | + | |
8 | + var state: MapState by Delegates.observable( | |
9 | + initialValue = MapState.CLUSTERED_MESSAGES, | |
10 | + onChange = { _, _, new -> | |
11 | + run { | |
12 | + listeners.forEach { | |
13 | + it.onValueChanged(new) | |
14 | + } | |
15 | + } | |
16 | + } | |
17 | + ) | |
18 | +} |
src/main/kotlin/application/model/ObservableReplayState.kt
View file @
3b26be8
1 | +package application.model | |
2 | + | |
3 | +import kotlin.properties.Delegates | |
4 | + | |
5 | +class ObservableReplayState { | |
6 | + | |
7 | + val listeners: MutableList<ReplayState> = mutableListOf() | |
8 | + | |
9 | + var value: Boolean by Delegates.observable( | |
10 | + initialValue = true, | |
11 | + onChange = { _, _, new -> | |
12 | + run { | |
13 | + listeners.forEach { | |
14 | + it.onValueChanged(new) | |
15 | + } | |
16 | + } | |
17 | + } | |
18 | + ) | |
19 | + | |
20 | +} |
src/main/kotlin/application/model/ObservableState.kt
View file @
3b26be8
1 | -package application.model | |
2 | - | |
3 | -import kotlin.properties.Delegates | |
4 | - | |
5 | -class ObservableState { | |
6 | - val listeners: MutableList<StateListener> = mutableListOf() | |
7 | - | |
8 | - var state: State by Delegates.observable( | |
9 | - initialValue = State.CLUSTERED_MESSAGES, | |
10 | - onChange = { _, _, new -> | |
11 | - run { | |
12 | - listeners.forEach { | |
13 | - it.onValueChanged(new) | |
14 | - } | |
15 | - } | |
16 | - } | |
17 | - ) | |
18 | -} |
src/main/kotlin/application/model/ReplayState.kt
View file @
3b26be8
src/main/kotlin/application/model/State.kt
View file @
3b26be8
src/main/kotlin/application/model/StateListener.kt
View file @
3b26be8
src/main/kotlin/application/model/Vessel.kt
View file @
3b26be8
1 | 1 | package application.model |
2 | 2 | |
3 | -import java.time.LocalDateTime | |
4 | -import java.time.ZoneOffset | |
5 | 3 | import java.util.* |
6 | 4 | |
7 | 5 | |
8 | 6 | class Vessel(val mmsi: String?) { |
9 | - val messages: SortedMap<LocalDateTime, Message> = sortedMapOf() | |
7 | + val messages: SortedMap<Long, Message> = sortedMapOf() | |
8 | + var messageToDisplay: Message? = null | |
9 | + get() { | |
10 | + // messages.forEach { (key, value) -> | |
11 | +// if(observableCurrentTime.value < key) { | |
12 | +// field = value | |
13 | +// return@forEach | |
14 | +// } | |
15 | +// } | |
16 | + field = messages.asSequence().map{ it }.firstOrNull {observableCurrentTime.value < it.key}.let { it?.value } | |
17 | + return field | |
18 | + } | |
10 | 19 | |
20 | + // val timesNormalized : SortedMap<Long, LocalDateTime> = sortedMapOf() | |
21 | + | |
22 | +// fun getAllNormalizedDate(): SortedMap<Long, LocalDateTime> { | |
23 | +// var offset: Long? = null | |
24 | +// if(timesNormalized.size == 0){ | |
25 | +// messages.keys.forEach { | |
26 | +// val currentTime = it.toEpochSecond(ZoneOffset.UTC) | |
27 | +// if(offset == null){ | |
28 | +// offset = currentTime | |
29 | +// } | |
30 | +// timesNormalized[currentTime - offset!!]= it | |
31 | +// } | |
32 | +// } | |
33 | +// return timesNormalized | |
34 | +// } | |
35 | + | |
11 | 36 | fun getAllTime(): ArrayList<MessageData?> { |
12 | 37 | val timeList = arrayListOf<MessageData?>() |
13 | 38 | messages.forEach { |
... | ... | @@ -132,6 +157,11 @@ |
132 | 157 | res.add(it.value.cargo) |
133 | 158 | } |
134 | 159 | return res |
160 | + } | |
161 | + | |
162 | + companion object{ | |
163 | + var maxTime: Long = 0 | |
164 | + var minTime: Long = 0 | |
135 | 165 | } |
136 | 166 | |
137 | 167 | } |
src/main/kotlin/application/model/VesselGenerator.kt
View file @
3b26be8
1 | 1 | package application.model |
2 | 2 | |
3 | 3 | import java.io.File |
4 | +import java.time.ZoneOffset | |
4 | 5 | import java.util.* |
5 | -import kotlin.collections.ArrayList | |
6 | 6 | |
7 | -fun createVesselCollection(file: File) : SortedMap<String, Vessel> { | |
8 | - val messages : ArrayList<Message> = arrayListOf() | |
7 | +fun createVesselCollection(file: File): SortedMap<String, Vessel> { | |
8 | + val messages: ArrayList<Message> = arrayListOf() | |
9 | 9 | val vessels: SortedMap<String, Vessel> = sortedMapOf() |
10 | + var maxTime: Long = 0 | |
11 | + var minTime: Long = Long.MAX_VALUE | |
10 | 12 | |
11 | 13 | file.forEachLine { |
12 | 14 | val arrayMessage = it.split(",") |
13 | 15 | if (arrayMessage[0].toIntOrNull() !== null) { |
14 | 16 | val message = Message(arrayMessage) |
15 | 17 | messages.add(message) |
16 | - if (!vessels.containsKey(message.mmsi.value)){ | |
18 | + if (!vessels.containsKey(message.mmsi.value)) { | |
17 | 19 | vessels[message.mmsi.value] = Vessel(message.mmsi.value!!) |
18 | 20 | } |
19 | - vessels[message.mmsi.value]?.messages?.set(message.time.value, message) | |
21 | + val time = message.time.value.toEpochSecond(ZoneOffset.UTC) | |
22 | + vessels[message.mmsi.value]?.messages?.set(time, message) | |
23 | + if (time > maxTime) { | |
24 | + maxTime = time | |
25 | + } | |
26 | + if (time < minTime){ | |
27 | + minTime = time | |
28 | + } | |
20 | 29 | } |
21 | - | |
22 | 30 | } |
31 | + | |
32 | + Vessel.maxTime = maxTime | |
33 | + Vessel.minTime = minTime | |
23 | 34 | |
24 | 35 | return vessels |
25 | 36 | } |
src/main/kotlin/map/MapDisplayer.kt
View file @
3b26be8
... | ... | @@ -67,6 +67,15 @@ |
67 | 67 | map.execScript("myMap.addLayer(markerClusters);") |
68 | 68 | } |
69 | 69 | |
70 | +fun displayTargetedVessels(map: LeafletMapView) { | |
71 | + clearMap(map) | |
72 | + observableVessel.vessels.forEach { (_, value) -> | |
73 | + val message = value.messageToDisplay ?: return@forEach | |
74 | + map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude.value}, ${message.longitude.value}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |
75 | + } | |
76 | + map.execScript("myMap.addLayer(markerClusters);") | |
77 | +} | |
78 | + | |
70 | 79 | fun displayClusterMessageOnMap(map: LeafletMapView, selectedMMSI: String) { |
71 | 80 | clearMap(map) |
72 | 81 | observableVessel.vessels.forEach { (_, value) -> |
src/main/resources/gui/mapPanel.fxml
View file @
3b26be8
1 | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | 2 | |
3 | -<?import javafx.scene.layout.StackPane?> | |
3 | +<?import javafx.scene.layout.*?> | |
4 | 4 | |
5 | -<StackPane prefHeight="150.0" prefWidth="200.0" xmlns="http://javafx.com/javafx" | |
6 | - xmlns:fx="http://javafx.com/fxml" | |
7 | - fx:controller="application.controller.MapPanelController" | |
8 | - fx:id="map"/> | |
5 | +<VBox prefHeight="150.0" prefWidth="371.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.controller.MapPanelController"> | |
6 | + <StackPane fx:id="map" /> | |
7 | + <fx:include source="timePanel.fxml" /> | |
8 | +</VBox> |
src/main/resources/gui/timePanel.fxml
View file @
3b26be8
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | + | |
3 | +<?import javafx.geometry.*?> | |
4 | +<?import javafx.scene.control.*?> | |
5 | +<?import javafx.scene.layout.*?> | |
6 | + | |
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"> | |
8 | + <children> | |
9 | + <Button fx:id="timePlay" alignment="CENTER" mnemonicParsing="false" text="Play"> | |
10 | + <HBox.margin> | |
11 | + <Insets left="5.0" right="5.0" /> | |
12 | + </HBox.margin></Button> | |
13 | + <Button fx:id="timeStop" mnemonicParsing="false" text="Stop"> | |
14 | + <HBox.margin> | |
15 | + <Insets left="5.0" right="5.0" /> | |
16 | + </HBox.margin></Button> | |
17 | + <Slider fx:id="timeSlider"> | |
18 | + <HBox.margin> | |
19 | + <Insets left="5.0" right="5.0" /> | |
20 | + </HBox.margin></Slider> | |
21 | + </children> | |
22 | +</HBox> |