Commit 79b001037b8d42da03b5857adf1243176060e734
1 parent
9e952e84e1
Exists in
master
and in
1 other branch
heat map
Showing 16 changed files with 605 additions and 24 deletions Inline Diff
- build.gradle
- src/main/kotlin/application/Logger.kt
- src/main/kotlin/application/controller/MapPanelController.kt
- src/main/kotlin/application/controller/MenuBarController.kt
- src/main/kotlin/application/model/Message.kt
- src/main/kotlin/application/model/ObservableVessel.kt
- src/main/kotlin/map/LeafletMapView.kt
- src/main/kotlin/map/MapDisplayer.kt
- src/main/resources/leafletmap/Leaflet.heat/LICENSE
- src/main/resources/leafletmap/Leaflet.heat/README.md
- src/main/resources/leafletmap/Leaflet.heat/demo/draw.html
- src/main/resources/leafletmap/Leaflet.heat/demo/index.html
- src/main/resources/leafletmap/Leaflet.heat/dist/leaflet-heat.js
- src/main/resources/leafletmap/Leaflet.heat/package.json
- src/main/resources/leafletmap/Leaflet.heat/src/HeatLayer.js
- src/main/resources/leafletmap/leafletmap.html
build.gradle
View file @
79b0010
| plugins { | 1 | 1 | plugins { | |
| id 'java' | 2 | 2 | id 'java' | |
| id 'org.jetbrains.kotlin.jvm' version '1.3.72' | 3 | 3 | id 'org.jetbrains.kotlin.jvm' version '1.3.72' | |
| } | 4 | 4 | } | |
| 5 | 5 | |||
| group 'marivisu' | 6 | 6 | group 'marivisu' | |
| version '1.0-SNAPSHOT' | 7 | 7 | version '1.0-SNAPSHOT' | |
| 8 | 8 | |||
| repositories { | 9 | 9 | repositories { | |
| mavenCentral() | 10 | 10 | mavenCentral() | |
| } | 11 | 11 | } | |
| 12 | 12 | |||
| dependencies { | 13 | 13 | dependencies { | |
| implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" | 14 | 14 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" | |
| implementation 'org.jfxtras:jmetro:8.6.9' | 15 | 15 | implementation 'org.jfxtras:jmetro:8.6.9' | |
| 16 | implementation 'org.slf4j:slf4j-api:1.7.30' | |||
| testCompile group: 'junit', name: 'junit', version: '4.12' | 16 | 17 | testCompile group: 'junit', name: 'junit', version: '4.12' | |
| 18 | testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' | |||
| 19 | compile group: 'ch.qos.logback', name: 'logback-core', version: '1.2.3' | |||
| } | 17 | 20 | } | |
| 18 | 21 | |||
| compileKotlin { | 19 | 22 | compileKotlin { | |
| kotlinOptions.jvmTarget = "1.8" | 20 | 23 | kotlinOptions.jvmTarget = "1.8" |
src/main/kotlin/application/Logger.kt
View file @
79b0010
| File was created | 1 | package application | ||
| 2 | ||||
| 3 | import org.slf4j.Logger | |||
| 4 | import org.slf4j.LoggerFactory |
src/main/kotlin/application/controller/MapPanelController.kt
View file @
79b0010
| package application.controller | 1 | 1 | package application.controller | |
| 2 | 2 | |||
| import application.model.* | 3 | 3 | import application.model.* | |
| import application.model.State.* | 4 | 4 | import application.model.State.* | |
| import javafx.concurrent.Worker | 5 | |||
| import javafx.fxml.FXML | 6 | 5 | import javafx.fxml.FXML | |
| import javafx.fxml.Initializable | 7 | 6 | import javafx.fxml.Initializable | |
| import javafx.scene.layout.StackPane | 8 | 7 | import javafx.scene.layout.StackPane | |
| import map.* | 9 | 8 | import map.* | |
| import java.net.URL | 10 | 9 | import java.net.URL | |
| import java.util.* | 11 | 10 | import java.util.* | |
| import java.util.concurrent.CompletableFuture | 12 | |||
| 13 | 11 | |||
| class MapPanelController : Initializable { | 14 | 12 | class MapPanelController : Initializable { | |
| 15 | 13 | |||
| @FXML | 16 | 14 | @FXML | |
| private lateinit var map: StackPane | 17 | 15 | private lateinit var map: StackPane | |
| 18 | 16 | |||
| private val mapView = LeafletMapView() | 19 | 17 | private val mapView = LeafletMapView() | |
| 20 | 18 | |||
| 21 | 19 | |||
| override fun initialize(location: URL?, resources: ResourceBundle?) { | 22 | 20 | override fun initialize(location: URL?, resources: ResourceBundle?) { | |
| val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig()) | 23 | 21 | mapView.displayMap(MapConfig()) | |
| 24 | ||||
| setObservableVesselListener() | 25 | 22 | setObservableVesselListener() | |
| setObservableSelectedVesselListener() | 26 | 23 | setObservableSelectedVesselListener() | |
| setStateListener() | 27 | 24 | setStateListener() | |
| /*completeFutureMap.whenComplete{ | 28 | 25 | /*val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig()) | |
| 26 | completeFutureMap.whenComplete{ | |||
| workerState, _ -> | 29 | 27 | workerState, _ -> | |
| if (workerState == Worker.State.SUCCEEDED) { | 30 | 28 | if (workerState == Worker.State.SUCCEEDED) { | |
| } | 31 | 29 | } | |
| }*/ | 32 | 30 | }*/ | |
| map.children.add(mapView) | 33 | 31 | map.children.add(mapView) | |
| map.children | 34 | 32 | map.children | |
| } | 35 | 33 | } | |
| 36 | 34 | |||
| private fun setStateListener() { | 37 | 35 | private fun setStateListener() { | |
| observableState.listeners.add(object : StateListener { | 38 | 36 | observableState.listeners.add(object : StateListener { | |
| override fun onValueChanged(newValue: State) { | 39 | 37 | override fun onValueChanged(newValue: State) { | |
| updateMap() | 40 | 38 | if (observableSelectedVessel.vessel.mmsi != null) { | |
| 39 | updateMap(observableSelectedVessel.vessel.mmsi!!) | |||
| 40 | } else { | |||
| 41 | updateMap() | |||
| 42 | } | |||
| } | 41 | 43 | } | |
| }) | 42 | 44 | }) | |
| } | 43 | 45 | } | |
| 44 | 46 | |||
| private fun updateMap() { | 45 | 47 | private fun updateMap() { | |
| when(observableState.state){ | 46 | 48 | when (observableState.state) { | |
| ALL_MESSAGES -> displayAllMessageOnMap(mapView) | 47 | 49 | ALL_MESSAGES -> displayAllMessageOnMap(mapView) | |
| CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) | 48 | 50 | CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) | |
| HEAT_MAP -> displayHeatMapOnMap(mapView) | 49 | 51 | HEAT_MAP -> displayHeatMapOnMap(mapView) | |
| } | 50 | 52 | } | |
| } | 51 | 53 | } | |
| 52 | 54 | |||
| private fun updateMap(selectedMMSI: Int) { | 53 | 55 | private fun updateMap(selectedMMSI: Int) { | |
| when(observableState.state){ | 54 | 56 | when (observableState.state) { | |
| ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) | 55 | 57 | ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) | |
| CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI) | 56 | 58 | CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI) | |
| HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI) | 57 | 59 | HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI) | |
| } | 58 | 60 | } | |
| } | 59 | 61 | } | |
| 60 | 62 | |||
| private fun setObservableVesselListener() { | 61 | 63 | private fun setObservableVesselListener() { | |
| observableVessel.listeners.add(object : MessageListener { | 62 | 64 | observableVessel.listeners.add(object : MessageListener { | |
| override fun onValueChanged(newValue: MutableMap<Int?, Vessel>) { | 63 | 65 | override fun onValueChanged(newValue: MutableMap<Int?, Vessel>) { | |
| updateMap() | 64 | 66 | updateMap() | |
| } | 65 | 67 | } | |
| }) | 66 | 68 | }) | |
| } | 67 | 69 | } | |
| 68 | 70 | |||
| private fun setObservableSelectedVesselListener() { | 69 | 71 | private fun setObservableSelectedVesselListener() { | |
| observableSelectedVessel.listeners.add(object : SelectedVesselListener { | 70 | 72 | observableSelectedVessel.listeners.add(object : SelectedVesselListener { | |
| override fun onValueChanged(newValue: Vessel) { | 71 | 73 | override fun onValueChanged(newValue: Vessel) { | |
| updateMap(newValue.mmsi!!) | 72 | 74 | if (newValue.mmsi != null){ | |
| 75 | updateMap(newValue.mmsi) | |||
| 76 | } |
src/main/kotlin/application/controller/MenuBarController.kt
View file @
79b0010
| package application.controller | 1 | 1 | package application.controller | |
| 2 | 2 | |||
| import application.model.State | 3 | |||
| import application.model.State.* | 4 | 3 | import application.model.State.* | |
| import application.model.createVesselCollection | 5 | 4 | import application.model.createVesselCollection | |
| import application.model.observableState | 6 | 5 | import application.model.observableState | |
| import application.model.observableVessel | 7 | 6 | import application.model.observableVessel | |
| import javafx.event.EventHandler | 8 | 7 | import javafx.event.EventHandler | |
| import javafx.fxml.FXML | 9 | 8 | import javafx.fxml.FXML | |
| import javafx.fxml.Initializable | 10 | 9 | import javafx.fxml.Initializable | |
| import javafx.scene.control.CheckMenuItem | 11 | 10 | import javafx.scene.control.CheckMenuItem | |
| import javafx.scene.control.MenuBar | 12 | 11 | import javafx.scene.control.MenuBar | |
| import javafx.scene.control.MenuItem | 13 | 12 | import javafx.scene.control.MenuItem | |
| import javafx.stage.FileChooser | 14 | 13 | import javafx.stage.FileChooser | |
| import java.net.URL | 15 | 14 | import java.net.URL | |
| import java.util.* | 16 | 15 | import java.util.* | |
| 17 | 16 | |||
| class MenuBarController : Initializable { | 18 | 17 | class MenuBarController : Initializable { | |
| 19 | 18 | |||
| @FXML | 20 | 19 | @FXML | |
| var menuBar: MenuBar = MenuBar() | 21 | 20 | var menuBar: MenuBar = MenuBar() | |
| 22 | 21 | |||
| @FXML | 23 | 22 | @FXML | |
| var import: MenuItem = MenuItem() | 24 | 23 | var import: MenuItem = MenuItem() | |
| 25 | 24 | |||
| @FXML | 26 | 25 | @FXML | |
| var allMessages: CheckMenuItem = CheckMenuItem() | 27 | 26 | var allMessages: CheckMenuItem = CheckMenuItem() | |
| 28 | 27 | |||
| @FXML | 29 | 28 | @FXML | |
| var clusteredMessage: CheckMenuItem = CheckMenuItem() | 30 | 29 | var clusteredMessage: CheckMenuItem = CheckMenuItem() | |
| 31 | 30 | |||
| @FXML | 32 | 31 | @FXML | |
| var heatMap: CheckMenuItem = CheckMenuItem() | 33 | 32 | var heatMap: CheckMenuItem = CheckMenuItem() | |
| 34 | 33 | |||
| override fun initialize(location: URL?, resources: ResourceBundle?) { | 35 | 34 | override fun initialize(location: URL?, resources: ResourceBundle?) { | |
| 36 | 35 | |||
| setOnActionImportButton() | 37 | 36 | setOnActionImportButton() | |
| 38 | 37 | |||
| setOnActionAllMessageButton() | 39 | 38 | setOnActionAllMessageButton() | |
| setOnActionClusteredMessageButton() | 40 | 39 | setOnActionClusteredMessageButton() | |
| setOnActionHeatMapButton() | 41 | 40 | setOnActionHeatMapButton() | |
| 41 | observableState.state = CLUSTERED_MESSAGES | |||
| 42 | allMessages.isSelected = false | |||
| clusteredMessage.isSelected = true | 42 | 43 | clusteredMessage.isSelected = true | |
| 44 | heatMap.isSelected = false | |||
| 43 | 45 | |||
| } | 44 | 46 | } | |
| 45 | 47 | |||
| private fun setOnActionImportButton() { | 46 | 48 | private fun setOnActionImportButton() { | |
| import.onAction = EventHandler { | 47 | 49 | import.onAction = EventHandler { | |
| val fileChooser = FileChooser() | 48 | 50 | val fileChooser = FileChooser() | |
| fileChooser.title = "Choose a file to import" | 49 | 51 | fileChooser.title = "Choose a file to import" | |
| val window = menuBar.scene.window | 50 | 52 | val window = menuBar.scene.window | |
| val file = fileChooser.showOpenDialog(window) | 51 | 53 | val file = fileChooser.showOpenDialog(window) | |
| val vessels = createVesselCollection(file) | 52 | 54 | try { | |
| observableVessel.vessels.clear() | 53 | 55 | val vessels = createVesselCollection(file) | |
| observableVessel.vessels = vessels | 54 | 56 | observableVessel.vessels.clear() | |
| 57 | observableVessel.vessels = vessels | |||
| 58 | } catch (ignore: IllegalStateException){ | |||
| 59 | ||||
| 60 | } | |||
| } | 55 | 61 | } | |
| } | 56 | 62 | } | |
| 57 | 63 | |||
| private fun setOnActionAllMessageButton() { | 58 | 64 | private fun setOnActionAllMessageButton() { | |
| allMessages.onAction = EventHandler { | 59 | 65 | allMessages.onAction = EventHandler { | |
| observableState.state = ALL_MESSAGES | 60 | 66 | observableState.state = ALL_MESSAGES | |
| allMessages.isSelected = true | 61 | 67 | allMessages.isSelected = true | |
| clusteredMessage.isSelected = false | 62 | 68 | clusteredMessage.isSelected = false | |
| heatMap.isSelected = false | 63 | 69 | heatMap.isSelected = false | |
| } | 64 | 70 | } | |
| } | 65 | 71 | } | |
| 66 | 72 | |||
| private fun setOnActionClusteredMessageButton() { | 67 | 73 | private fun setOnActionClusteredMessageButton() { | |
| clusteredMessage.onAction = EventHandler { | 68 | 74 | clusteredMessage.onAction = EventHandler { | |
| observableState.state = CLUSTERED_MESSAGES | 69 | 75 | observableState.state = CLUSTERED_MESSAGES | |
| heatMap.isSelected = false | 70 | 76 | heatMap.isSelected = false | |
| allMessages.isSelected = false | 71 | 77 | allMessages.isSelected = false | |
| clusteredMessage.isSelected = true | 72 | 78 | clusteredMessage.isSelected = true |
src/main/kotlin/application/model/Message.kt
View file @
79b0010
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| import java.time.LocalDateTime | 3 | 3 | import java.time.LocalDateTime | |
| 4 | 4 | |||
| class Message(split: List<String>) { | 5 | 5 | class Message(split: List<String>) { | |
| val mmsi: Int? = split[0].toIntOrNull() | 6 | 6 | val mmsi: Int? = split[0].toIntOrNull() | |
| val time: LocalDateTime = LocalDateTime.parse(split[1]) | 7 | 7 | val time: LocalDateTime = LocalDateTime.parse(split[1]) | |
| val latitude: Double? = split[2].toDoubleOrNull() | 8 | 8 | val latitude: Double? = split[2].toDoubleOrNull() | |
| val longitude: Double? = split[3].toDoubleOrNull() | 9 | 9 | val longitude: Double? = split[3].toDoubleOrNull() | |
| val speedOverGround: Double? = split[4].toDoubleOrNull() | 10 | 10 | val speedOverGround: Double? = split[4].toDoubleOrNull() | |
| val courseOverGround: Double? = split[5].toDoubleOrNull() | 11 | 11 | val courseOverGround: Double? = split[5].toDoubleOrNull() | |
| val heading: Int? = split[6].toIntOrNull() | 12 | 12 | val heading: Int? = split[6].toIntOrNull() | |
| val vesselName: String? = split[7] | 13 | 13 | val vesselName: String? = split[7] | |
| val imo: String? = split[8] | 14 | 14 | val imo: String? = split[8] | |
| val callSign: String? = split[9] | 15 | 15 | val callSign: String? = split[9] | |
| val vesselType: Int? = split[10].toIntOrNull() | 16 | 16 | val vesselType: Int? = split[10].toIntOrNull() | |
| val status: String? = split[11] | 17 | 17 | val status: String? = split[11] | |
| val length: Double? = split[12].toDoubleOrNull() | 18 | 18 | val length: Double? = split[12].toDoubleOrNull() | |
| val width: Double? = split[13].toDoubleOrNull() | 19 | 19 | val width: Double? = split[13].toDoubleOrNull() | |
| val draft: Double? = split[14].toDoubleOrNull() | 20 | 20 | val draft: Double? = split[14].toDoubleOrNull() | |
| val cargo: Int? = split[15].toIntOrNull() | 21 | 21 | val cargo: Int? = split[15].toIntOrNull() | |
| 22 | 22 | |||
| fun getHexColor(): String{ | 23 | 23 | fun getHexColorStroke(): String{ | |
| var hex = Integer.toHexString(this.mmsi!!) | 24 | 24 | var hex = Integer.toHexString(this.mmsi!!) | |
| 25 | if (hex.length > 6){ | |||
| 26 | hex = hex.substring(hex.length - 6) | |||
| 27 | } | |||
| 28 | return hex | |||
| 29 | } | |||
| 30 | ||||
| 31 | fun getHexColorFill(): String{ | |||
| 32 | var hex = Integer.toHexString(this.mmsi!! - 50) | |||
| if (hex.length > 6){ | 25 | 33 | if (hex.length > 6){ | |
| hex = hex.substring(hex.length - 6) | 26 | 34 | hex = hex.substring(hex.length - 6) | |
| } | 27 | 35 | } | |
| return hex | 28 | 36 | return hex |
src/main/kotlin/application/model/ObservableVessel.kt
View file @
79b0010
| package application.model | 1 | 1 | package application.model | |
| 2 | 2 | |||
| import kotlin.properties.Delegates | 3 | 3 | import kotlin.properties.Delegates | |
| 4 | 4 | |||
| class ObservableVessel { | 5 | 5 | class ObservableVessel { | |
| val listeners: MutableList<MessageListener> = mutableListOf() | 6 | 6 | val listeners: MutableList<MessageListener> = mutableListOf() | |
| 7 | 7 | |||
| var vessels: MutableMap<Int?, Vessel> by Delegates.observable( | 8 | 8 | var vessels: MutableMap<Int?, Vessel> by Delegates.observable( | |
| initialValue = mutableMapOf(), | 9 | 9 | initialValue = mutableMapOf(), | |
| onChange = { _, _, new -> | 10 | 10 | onChange = { _, _, new -> | |
| run { | 11 | 11 | run { | |
| 12 | observableSelectedVessel.vessel = Vessel(null) | |||
| listeners.forEach { | 12 | 13 | listeners.forEach { | |
| it.onValueChanged(new) | 13 | 14 | it.onValueChanged(new) | |
| } | 14 | 15 | } | |
| } | 15 | 16 | } |
src/main/kotlin/map/LeafletMapView.kt
View file @
79b0010
| 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: 5, | 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); | |
| |var myRenderer = L.canvas({ padding: 0.5 }); | 96 | 96 | |var myRenderer = L.canvas({ padding: 0.5 }); | |
| |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10});""".trimMargin() | 97 | 97 | |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); | |
| 98 | |var heatLayer = L.heatLayer([]).addTo(myMap);""".trimMargin() | |||
| ) | 98 | 99 | ) | |
| 99 | 100 | |||
| // eventZoomChangeIcon() | 100 | 101 | // eventZoomChangeIcon() | |
| 101 | 102 | |||
| // execute script for layer control definition if there are multiple layers | 102 | 103 | // execute script for layer control definition if there are multiple layers | |
| if (mapConfig.layers.size > 1) { | 103 | 104 | if (mapConfig.layers.size > 1) { | |
| execScript( | 104 | 105 | execScript( | |
| """ | 105 | 106 | """ | |
| |var overlayMaps = {}; | 106 | 107 | |var overlayMaps = {}; | |
| |L.control.layers(baseMaps, overlayMaps).addTo(myMap);""".trimMargin() | 107 | 108 | |L.control.layers(baseMaps, overlayMaps).addTo(myMap);""".trimMargin() | |
| ) | 108 | 109 | ) | |
| 109 | 110 | |||
| } | 110 | 111 | } | |
| 111 | 112 | |||
| // execute script for scale control definition | 112 | 113 | // execute script for scale control definition | |
| if (mapConfig.scaleControlConfig.show) { | 113 | 114 | if (mapConfig.scaleControlConfig.show) { | |
| execScript( | 114 | 115 | execScript( | |
| "L.control.scale({position: '${mapConfig.scaleControlConfig.position.positionName}', " + | 115 | 116 | "L.control.scale({position: '${mapConfig.scaleControlConfig.position.positionName}', " + | |
| "metric: ${mapConfig.scaleControlConfig.metric}, " + | 116 | 117 | "metric: ${mapConfig.scaleControlConfig.metric}, " + | |
| "imperial: ${!mapConfig.scaleControlConfig.metric}})" + | 117 | 118 | "imperial: ${!mapConfig.scaleControlConfig.metric}})" + | |
| ".addTo(myMap);" | 118 | 119 | ".addTo(myMap);" | |
| ) | 119 | 120 | ) | |
| } | 120 | 121 | } | |
| 121 | 122 | |||
| // execute script for zoom control definition | 122 | 123 | // execute script for zoom control definition | |
| if (mapConfig.zoomControlConfig.show) { | 123 | 124 | if (mapConfig.zoomControlConfig.show) { | |
| execScript( | 124 | 125 | execScript( | |
| "L.control.zoom({position: '${mapConfig.zoomControlConfig.position.positionName}'})" + | 125 | 126 | "L.control.zoom({position: '${mapConfig.zoomControlConfig.position.positionName}'})" + | |
| ".addTo(myMap);" | 126 | 127 | ".addTo(myMap);" | |
| ) | 127 | 128 | ) | |
| } | 128 | 129 | } | |
| } | 129 | 130 | } | |
| 130 | 131 | |||
| /** | 131 | 132 | /** | |
| * Sets the view of the map to the specified geographical center position and zoom level. | 132 | 133 | * Sets the view of the map to the specified geographical center position and zoom level. | |
| * | 133 | 134 | * | |
| * @param position map center position | 134 | 135 | * @param position map center position | |
| * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | 135 | 136 | * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | |
| */ | 136 | 137 | */ | |
| fun setView(position: LatLong, zoomLevel: Int) = | 137 | 138 | fun setView(position: LatLong, zoomLevel: Int) = | |
| execScript("myMap.setView([${position.latitude}, ${position.longitude}], $zoomLevel);") | 138 | 139 | execScript("myMap.setView([${position.latitude}, ${position.longitude}], $zoomLevel);") | |
| 139 | 140 | |||
| /** | 140 | 141 | /** | |
| * Pans the map to the specified geographical center position. | 141 | 142 | * Pans the map to the specified geographical center position. | |
| * | 142 | 143 | * | |
| * @param position map center position | 143 | 144 | * @param position map center position | |
| */ | 144 | 145 | */ | |
| fun panTo(position: LatLong) = | 145 | 146 | fun panTo(position: LatLong) = | |
| execScript("myMap.panTo([${position.latitude}, ${position.longitude}]);") | 146 | 147 | execScript("myMap.panTo([${position.latitude}, ${position.longitude}]);") | |
| 147 | 148 | |||
| /** | 148 | 149 | /** | |
| * Sets the zoom of the map to the specified level. | 149 | 150 | * Sets the zoom of the map to the specified level. | |
| * | 150 | 151 | * | |
| * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | 151 | 152 | * @param zoomLevel zoom level (0 - 19 for OpenStreetMap) | |
| */ | 152 | 153 | */ | |
| fun setZoom(zoomLevel: Int) = | 153 | 154 | fun setZoom(zoomLevel: Int) = | |
| execScript("myMap.setZoom([$zoomLevel]);") | 154 | 155 | execScript("myMap.setZoom([$zoomLevel]);") | |
| 155 | 156 | |||
| /** | 156 | 157 | /** | |
| * Adds a Marker Object to a map | 157 | 158 | * Adds a Marker Object to a map | |
| * | 158 | 159 | * | |
| * @param marker the Marker Object | 159 | 160 | * @param marker the Marker Object | |
| */ | 160 | 161 | */ | |
| fun addMarker(marker: Marker) { | 161 | 162 | fun addMarker(marker: Marker) { | |
| marker.addToMap(getNextMarkerName(), this) | 162 | 163 | marker.addToMap(getNextMarkerName(), this) | |
| } | 163 | 164 | } | |
| 164 | 165 | |||
| fun addCircle(circle: Circle) { | 165 | 166 | fun addCircle(circle: Circle) { | |
| circle.addToMap(this) | 166 | 167 | circle.addToMap(this) | |
| } | 167 | 168 | } | |
| 168 | 169 | |||
| fun addZone(zone: Zone) { | 169 | 170 | fun addZone(zone: Zone) { | |
| zone.addToMap(this) | 170 | 171 | zone.addToMap(this) | |
| } | 171 | 172 | } | |
| 172 | 173 | |||
| /** | 173 | 174 | /** | |
| * Removes an existing marker from the map | 174 | 175 | * Removes an existing marker from the map | |
| * | 175 | 176 | * | |
| * @param marker the Marker object | 176 | 177 | * @param marker the Marker object | |
| */ | 177 | 178 | */ | |
| fun removeMarker(marker: Marker) { | 178 | 179 | fun removeMarker(marker: Marker) { | |
| execScript("myMap.removeLayer(${marker.getName()});") | 179 | 180 | execScript("myMap.removeLayer(${marker.getName()});") | |
| } | 180 | 181 | } | |
| 181 | 182 | |||
| fun removeCircle(circle: Circle) { | 182 | 183 | fun removeCircle(circle: Circle) { | |
| circle.removeCircle(this) | 183 | 184 | circle.removeCircle(this) | |
| } | 184 | 185 | } | |
| 185 | 186 | |||
| fun removeZone(zone: Zone) { | 186 | 187 | fun removeZone(zone: Zone) { | |
| zone.removeZone() | 187 | 188 | zone.removeZone() | |
| } | 188 | 189 | } | |
| 189 | 190 | |||
| fun removeZone(id: String) { | 190 | 191 | fun removeZone(id: String) { | |
| val idSanitized = id.replace("-", "") | 191 | 192 | val idSanitized = id.replace("-", "") | |
| execScript("myMap.removeLayer(polygon$idSanitized);") | 192 | 193 | execScript("myMap.removeLayer(polygon$idSanitized);") | |
| } | 193 | 194 | } | |
| 194 | 195 | |||
| 195 | 196 | |||
| fun uppdateCircle(circle: Circle, latLong: LatLong, radius: Double) { | 196 | 197 | fun uppdateCircle(circle: Circle, latLong: LatLong, radius: Double) { | |
| circle.modifyCircle(latLong, radius) | 197 | 198 | circle.modifyCircle(latLong, radius) | |
| circle.uppdateMap() | 198 | 199 | circle.uppdateMap() | |
| } | 199 | 200 | } | |
| 200 | 201 | |||
| fun setEventMousePosition() { | 201 | 202 | fun setEventMousePosition() { | |
| execScript( | 202 | 203 | execScript( | |
| "var lat=0.0, lng=0.0;\n" + | 203 | 204 | "var lat=0.0, lng=0.0;\n" + | |
| "myMap.addEventListener('mousemove', function(ev) {\n" + | 204 | 205 | "myMap.addEventListener('mousemove', function(ev) {\n" + | |
| " lat = ev.latlng.lat;\n" + | 205 | 206 | " lat = ev.latlng.lat;\n" + | |
| " lng = ev.latlng.lng;\n" + | 206 | 207 | " lng = ev.latlng.lng;\n" + | |
| "});" | 207 | 208 | "});" | |
| ) | 208 | 209 | ) | |
| } | 209 | 210 | } | |
| 210 | 211 | |||
| fun getMousePosition(): LatLong { | 211 | 212 | fun getMousePosition(): LatLong { | |
| val lat = execScript("lat;") as Double | 212 | 213 | val lat = execScript("lat;") as Double | |
| val lng = execScript("lng;") as Double | 213 | 214 | val lng = execScript("lng;") as Double | |
| return LatLong(lat, lng) | 214 | 215 | return LatLong(lat, lng) | |
| } | 215 | 216 | } | |
| 216 | 217 | |||
| /** | 217 | 218 | /** | |
| * Adds a custom marker type | 218 | 219 | * Adds a custom marker type | |
| * | 219 | 220 | * | |
| * @param markerName the name of the marker type | 220 | 221 | * @param markerName the name of the marker type | |
| * @param iconUrl the url if the marker icon | 221 | 222 | * @param iconUrl the url if the marker icon | |
| */ | 222 | 223 | */ | |
| fun addCustomMarker(markerName: String, iconUrl: String): String { | 223 | 224 | fun addCustomMarker(markerName: String, iconUrl: String): String { | |
| execScript( | 224 | 225 | execScript( | |
| "var $markerName = L.icon({\n" + | 225 | 226 | "var $markerName = L.icon({\n" + | |
| "iconUrl: '${createImage(iconUrl, "png")}',\n" + | 226 | 227 | "iconUrl: '${createImage(iconUrl, "png")}',\n" + | |
| "iconSize: [24, 24],\n" + | 227 | 228 | "iconSize: [24, 24],\n" + | |
| "iconAnchor: [12, 12],\n" + | 228 | 229 | "iconAnchor: [12, 12],\n" + | |
| "});" | 229 | 230 | "});" | |
| ) | 230 | 231 | ) | |
| return markerName | 231 | 232 | return markerName | |
| } | 232 | 233 | } | |
| 233 | 234 | |||
| private fun createImage(path: String, type: String): String { | 234 | 235 | private fun createImage(path: String, type: String): String { | |
| val image = ImageIO.read(File(path)) | 235 | 236 | val image = ImageIO.read(File(path)) | |
| var imageString: String? = null | 236 | 237 | var imageString: String? = null | |
| val bos = ByteArrayOutputStream() | 237 | 238 | val bos = ByteArrayOutputStream() | |
| 238 | 239 | |||
| try { | 239 | 240 | try { | |
| ImageIO.write(image, type, bos) | 240 | 241 | ImageIO.write(image, type, bos) | |
| val imageBytes = bos.toByteArray() | 241 | 242 | val imageBytes = bos.toByteArray() | |
| 242 | 243 | |||
| val encoder = Base64.getEncoder() | 243 | 244 | val encoder = Base64.getEncoder() | |
| imageString = encoder.encodeToString(imageBytes) | 244 | 245 | imageString = encoder.encodeToString(imageBytes) | |
| 245 | 246 | |||
| bos.close() | 246 | 247 | bos.close() | |
| } catch (e: IOException) { | 247 | 248 | } catch (e: IOException) { | |
| e.printStackTrace() | 248 | 249 | e.printStackTrace() | |
| } | 249 | 250 | } | |
| return "data:image/$type;base64,$imageString" | 250 | 251 | return "data:image/$type;base64,$imageString" | |
| } | 251 | 252 | } | |
| 252 | 253 | |||
| /** | 253 | 254 | /** | |
| * Sets the onMarkerClickListener | 254 | 255 | * Sets the onMarkerClickListener | |
| * | 255 | 256 | * | |
| * @param listener the onMarerClickEventListener | 256 | 257 | * @param listener the onMarerClickEventListener | |
| */ | 257 | 258 | */ | |
| fun onMarkerClick(listener: MarkerClickEventListener) { | 258 | 259 | fun onMarkerClick(listener: MarkerClickEventListener) { | |
| val win = execScript("document") as JSObject | 259 | 260 | val win = execScript("document") as JSObject | |
| win.setMember("java", this) | 260 | 261 | win.setMember("java", this) | |
| markerClickEvent.addListener(listener) | 261 | 262 | markerClickEvent.addListener(listener) | |
| } | 262 | 263 | } | |
| 263 | 264 | |||
| /** | 264 | 265 | /** | |
| * Handles the callback from the markerClickEvent | 265 | 266 | * Handles the callback from the markerClickEvent | |
| */ | 266 | 267 | */ | |
| fun markerClick(title: String) { | 267 | 268 | fun markerClick(title: String) { | |
| markerClickEvent.MarkerClickEvent(title) | 268 | 269 | markerClickEvent.MarkerClickEvent(title) | |
| } | 269 | 270 | } | |
| 270 | 271 | |||
| /** | 271 | 272 | /** | |
| * Sets the onMapMoveListener | 272 | 273 | * Sets the onMapMoveListener | |
| * | 273 | 274 | * | |
| * @param listener the MapMoveEventListener | 274 | 275 | * @param listener the MapMoveEventListener | |
| */ | 275 | 276 | */ | |
| fun onMapMove(listener: MapMoveEventListener) { | 276 | 277 | fun onMapMove(listener: MapMoveEventListener) { | |
| val win = execScript("document") as JSObject | 277 | 278 | val win = execScript("document") as JSObject | |
| win.setMember("java", this) | 278 | 279 | win.setMember("java", this) | |
| execScript("myMap.on('moveend', function(e){ document.java.mapMove(myMap.getCenter().lat, myMap.getCenter().lng);});") | 279 | 280 | execScript("myMap.on('moveend', function(e){ document.java.mapMove(myMap.getCenter().lat, myMap.getCenter().lng);});") | |
| mapMoveEvent.addListener(listener) | 280 | 281 | mapMoveEvent.addListener(listener) | |
| } | 281 | 282 | } | |
| 282 | 283 | |||
| /** | 283 | 284 | /** | |
| * Handles the callback from the mapMoveEvent | 284 | 285 | * Handles the callback from the mapMoveEvent | |
| */ | 285 | 286 | */ | |
| fun mapMove(lat: Double, lng: Double) { | 286 | 287 | fun mapMove(lat: Double, lng: Double) { | |
| val latlng = LatLong(lat, lng) | 287 | 288 | val latlng = LatLong(lat, lng) | |
| mapMoveEvent.MapMoveEvent(latlng) | 288 | 289 | mapMoveEvent.MapMoveEvent(latlng) | |
| } | 289 | 290 | } | |
| 290 | 291 | |||
| /** | 291 | 292 | /** | |
| * Sets the onMapClickListener | 292 | 293 | * Sets the onMapClickListener | |
| * | 293 | 294 | * | |
| * @param listener the onMapClickEventListener | 294 | 295 | * @param listener the onMapClickEventListener | |
| */ | 295 | 296 | */ | |
| fun onMapClick(listener: MapClickEventListener) { | 296 | 297 | fun onMapClick(listener: MapClickEventListener) { | |
| val win = execScript("document") as JSObject | 297 | 298 | val win = execScript("document") as JSObject | |
| win.setMember("java", this) | 298 | 299 | win.setMember("java", this) | |
| execScript("myMap.on('click', function(e){ document.java.mapClick(e.latlng.lat, e.latlng.lng);});") | 299 | 300 | execScript("myMap.on('click', function(e){ document.java.mapClick(e.latlng.lat, e.latlng.lng);});") | |
| mapClickEvent.addListener(listener) | 300 | 301 | mapClickEvent.addListener(listener) | |
| } | 301 | 302 | } | |
| 302 | 303 | |||
| /** | 303 | 304 | /** | |
| * Handles the callback from the mapClickEvent | 304 | 305 | * Handles the callback from the mapClickEvent | |
| */ | 305 | 306 | */ | |
| fun mapClick(lat: Double, lng: Double) { | 306 | 307 | fun mapClick(lat: Double, lng: Double) { | |
| val latlng = LatLong(lat, lng) | 307 | 308 | val latlng = LatLong(lat, lng) | |
| mapClickEvent.MapClickEvent(latlng) | 308 | 309 | mapClickEvent.MapClickEvent(latlng) | |
| } | 309 | 310 | } | |
| 310 | 311 | |||
| /** | 311 | 312 | /** | |
| * Draws a track path along the specified positions. | 312 | 313 | * Draws a track path along the specified positions. | |
| * | 313 | 314 | * | |
| * @param positions list of track positions | 314 | 315 | * @param positions list of track positions | |
| */ | 315 | 316 | */ | |
| fun addTrack(positions: List<LatLong>) { | 316 | 317 | fun addTrack(positions: List<LatLong>) { | |
| 317 | 318 | |||
| val jsPositions = positions | 318 | 319 | val jsPositions = positions | |
| .map { " [${it.latitude}, ${it.longitude}]" } | 319 | 320 | .map { " [${it.latitude}, ${it.longitude}]" } | |
| .joinToString(", \n") | 320 | 321 | .joinToString(", \n") | |
| 321 | 322 | |||
| execScript( | 322 | 323 | execScript( | |
| """ | 323 | 324 | """ | |
| |var latLngs = [ | 324 | 325 | |var latLngs = [ | |
| |$jsPositions | 325 | 326 | |$jsPositions | |
| |]; | 326 | 327 | |]; | |
| |var polyline = L.polyline(latLngs, {color: 'red', weight: 2}).addTo(myMap);""".trimMargin() | 327 | 328 | |var polyline = L.polyline(latLngs, {color: 'red', weight: 2}).addTo(myMap);""".trimMargin() | |
| ) | 328 | 329 | ) | |
| } | 329 | 330 | } | |
| 330 | 331 | |||
| fun clearAllLayer() { | 331 | 332 | fun clearAllLayer() { | |
| execScript(""" | 332 | 333 | execScript(""" | |
| myMap.eachLayer(function (layer) { | 333 | 334 | myMap.eachLayer(function (layer) { | |
| map.removeLayer(layer); | 334 | 335 | map.removeLayer(layer); | |
| }); | 335 | 336 | }); | |
| """.trimIndent()) | 336 | 337 | """.trimIndent()) | |
| } | 337 | 338 | } | |
| 338 | 339 | |||
| fun addTrack(positions: List<LatLong>, id: String, color: Color, tooltip: String) { | 339 | 340 | fun addTrack(positions: List<LatLong>, id: String, color: Color, tooltip: String) { | |
| 340 | 341 | |||
| val jsPositions = positions | 341 | 342 | val jsPositions = positions | |
| .map { " [${it.latitude}, ${it.longitude}]" } | 342 | 343 | .map { " [${it.latitude}, ${it.longitude}]" } | |
| .joinToString(", \n") | 343 | 344 | .joinToString(", \n") | |
| 344 | 345 | |||
| val cleanTooltip = tooltip.replace("'", "'") | 345 | 346 | val cleanTooltip = tooltip.replace("'", "'") | |
| execScript( | 346 | 347 | execScript( | |
| """ | 347 | 348 | """ | |
| |var latLngs = [ | 348 | 349 | |var latLngs = [ | |
| |$jsPositions | 349 | 350 | |$jsPositions | |
| |]; | 350 | 351 | |]; | |
| |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255) | 351 | 352 | |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255) | |
| .toInt()},${Math.floor(color.getBlue() * 255).toInt()})"; | 352 | 353 | .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() | 353 | 354 | |var polyline$id = L.polyline(latLngs, {color: color, weight: 2, zIndexOffset: 200}).bindTooltip('$cleanTooltip', {sticky: true}).addTo(trackGroup)""".trimMargin() | |
| ) | 354 | 355 | ) | |
| } | 355 | 356 | } | |
| 356 | 357 | |||
| fun makeVesselTrackTransparent(id: String) { | 357 | 358 | fun makeVesselTrackTransparent(id: String) { | |
| execScript("polyline$id.setStyle({opacity: 0.5});") | 358 | 359 | execScript("polyline$id.setStyle({opacity: 0.5});") | |
| } | 359 | 360 | } | |
| 360 | 361 | |||
| fun highlightTrack(id: String) { | 361 | 362 | fun highlightTrack(id: String) { | |
| execScript("polyline$id.setStyle({weight: 4});") | 362 | 363 | execScript("polyline$id.setStyle({weight: 4});") | |
| } | 363 | 364 | } | |
| 364 | 365 | |||
| fun normalizeVesselTrack(id: String) { | 365 | 366 | fun normalizeVesselTrack(id: String) { | |
| execScript("polyline$id.setStyle({opacity: 1,weight: 2});") | 366 | 367 | execScript("polyline$id.setStyle({opacity: 1,weight: 2});") | |
| } | 367 | 368 | } | |
| 368 | 369 |
src/main/kotlin/map/MapDisplayer.kt
View file @
79b0010
| 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) | |
| 8 | clearHeatMap(map) | |||
| } | 8 | 9 | } | |
| 9 | 10 | |||
| fun clearMapCluster(map: LeafletMapView) { | 10 | 11 | fun clearMapCluster(map: LeafletMapView) { | |
| map.execScript( | 11 | 12 | map.execScript( | |
| """ | 12 | 13 | """ | |
| |myMap.removeLayer(markerClusters) | 13 | 14 | |myMap.removeLayer(markerClusters); | |
| |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 9}); | 14 | 15 | |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); | |
| """.trimMargin() | 15 | 16 | """.trimMargin() | |
| ) | 16 | 17 | ) | |
| } | 17 | 18 | } | |
| 18 | 19 | |||
| fun clearMapCanvas(map: LeafletMapView) { | 19 | 20 | fun clearMapCanvas(map: LeafletMapView) { | |
| map.execScript( | 20 | 21 | map.execScript( | |
| """ | 21 | 22 | """ | |
| |myRenderer.removeFrom(myMap) | 22 | 23 | |myRenderer.removeFrom(myMap); | |
| |var myRenderer = L.canvas({ padding: 0.5 }); | 23 | 24 | |var myRenderer = L.canvas({ padding: 0.5 }); | |
| """.trimMargin() | 24 | 25 | """.trimMargin() | |
| ) | 25 | 26 | ) | |
| } | 26 | 27 | } | |
| 27 | 28 | |||
| 29 | fun clearHeatMap(map: LeafletMapView) { | |||
| 30 | map.execScript( | |||
| 31 | """ | |||
| 32 | |heatLayer.removeFrom(myMap); | |||
| 33 | |var heatLayer = L.heatLayer([]).addTo(myMap); | |||
| 34 | """.trimMargin() | |||
| 35 | ) | |||
| 36 | } | |||
| 37 | ||||
| fun displayAllMessageOnMap(map: LeafletMapView) { | 28 | 38 | fun displayAllMessageOnMap(map: LeafletMapView) { | |
| clearMap(map) | 29 | 39 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 30 | 40 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 31 | 41 | value.messages.forEach { (_, message) -> | |
| map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColor()}'}).addTo(myMap)") | 32 | 42 | map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | |
| } | 33 | 43 | } | |
| } | 34 | 44 | } | |
| } | 35 | 45 | } | |
| 36 | 46 | |||
| fun displayAllMessageOnMap(map: LeafletMapView, selectedMMSI: Int) { | 37 | 47 | fun displayAllMessageOnMap(map: LeafletMapView, selectedMMSI: Int) { | |
| clearMap(map) | 38 | 48 | clearMap(map) | |
| observableVessel.vessels.forEach { (_, value) -> | 39 | 49 | observableVessel.vessels.forEach { (_, value) -> | |
| value.messages.forEach { (_, message) -> | 40 | 50 | value.messages.forEach { (_, message) -> | |
| if (selectedMMSI == message.mmsi) { | 41 | 51 | if (selectedMMSI == message.mmsi) { | |
| map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff001e'}).addTo(myMap)") | 42 | 52 | map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap)") | |
| } else { | 43 | 53 | } else { | |
| map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColor()}'}).addTo(myMap)") | 44 | 54 | map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") | |
| } | 45 | 55 | } | |
| } | 46 | 56 | } | |
| } | 47 | 57 | } | |
| 58 | } | |||
| 59 | ||||
| 60 | fun displayClusterMessageOnMap(map: LeafletMapView) { | |||
| 61 | clearMap(map) | |||
| 62 | observableVessel.vessels.forEach { (_, value) -> | |||
| 63 | value.messages.forEach { (_, message) -> | |||
| 64 | map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |||
| 65 | } | |||
| 66 | } | |||
| 67 | map.execScript("myMap.addLayer(markerClusters);") | |||
| 68 | } | |||
| 69 | ||||
| 70 | fun displayClusterMessageOnMap(map: LeafletMapView, selectedMMSI: Int) { | |||
| 71 | clearMap(map) | |||
| 72 | observableVessel.vessels.forEach { (_, value) -> | |||
| 73 | value.messages.forEach { (_, message) -> | |||
| 74 | if (selectedMMSI == message.mmsi) { | |||
| 75 | map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") | |||
| 76 | } else { | |||
| 77 | map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") | |||
| 78 | } | |||
| 79 | } | |||
| 80 | } | |||
| 81 | map.execScript("myMap.addLayer(markerClusters);") | |||
| 82 | } | |||
| 83 | ||||
| 84 | fun displayHeatMapOnMap(map: LeafletMapView) { | |||
| 85 | clearMap(map) | |||
| 86 | observableVessel.vessels.forEach { (_, value) -> | |||
| 87 | value.messages.forEach { (_, message) -> | |||
| 88 | map.execScript("heatLayer.addLatLng([${message.latitude}, ${message.longitude}]);") | |||
| 89 | } | |||
| 90 | } | |||
| 91 | ||||
| 92 | } | |||
| 93 | ||||
| 94 | fun displayHeatMapOnMap(map: LeafletMapView, selectedMMSI: Int) { | |||
| 95 | clearMap(map) | |||
| 96 | observableVessel.vessels.forEach { (_, value) -> | |||
| 97 | value.messages.forEach { (_, message) -> | |||
| 98 | if (selectedMMSI == message.mmsi) { | |||
| 99 | map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") | |||
| 100 | } else { | |||
| 101 | map.execScript("heatLayer.addLatLng([${message.latitude}, ${message.longitude}]);") | |||
| 102 | } | |||
| 103 | } | |||
| 104 | } | |||
| 105 | map.execScript("myMap.addLayer(markerClusters);") | |||
| } | 48 | 106 | } |
src/main/resources/leafletmap/Leaflet.heat/LICENSE
View file @
79b0010
| File was created | 1 | Copyright (c) 2014, Vladimir Agafonkin | ||
| 2 | All rights reserved. | |||
| 3 | ||||
| 4 | Redistribution and use in source and binary forms, with or without modification, are | |||
| 5 | permitted provided that the following conditions are met: | |||
| 6 | ||||
| 7 | 1. Redistributions of source code must retain the above copyright notice, this list of | |||
| 8 | conditions and the following disclaimer. | |||
| 9 | ||||
| 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list | |||
| 11 | of conditions and the following disclaimer in the documentation and/or other materials | |||
| 12 | provided with the distribution. | |||
| 13 | ||||
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY | |||
| 15 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |||
| 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |||
| 17 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |||
| 18 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
src/main/resources/leafletmap/Leaflet.heat/README.md
View file @
79b0010
| File was created | 1 | Leaflet.heat | ||
| 2 | ========== | |||
| 3 | ||||
| 4 | A tiny, simple and fast [Leaflet](http://leafletjs.com) heatmap plugin. | |||
| 5 | Uses [simpleheat](https://github.com/mourner/simpleheat) under the hood, | |||
| 6 | additionally clustering points into a grid for performance. | |||
| 7 | ||||
| 8 | ||||
| 9 | ## Demos | |||
| 10 | ||||
| 11 | - [10,000 points →](http://leaflet.github.io/Leaflet.heat/demo) | |||
| 12 | - [Adding points dynamically →](http://leaflet.github.io/Leaflet.heat/demo/draw.html) | |||
| 13 | ||||
| 14 | ||||
| 15 | ## Basic Usage | |||
| 16 | ||||
| 17 | ```js | |||
| 18 | var heat = L.heatLayer([ | |||
| 19 | [50.5, 30.5, 0.2], // lat, lng, intensity | |||
| 20 | [50.6, 30.4, 0.5], | |||
| 21 | ... | |||
| 22 | ], {radius: 25}).addTo(map); | |||
| 23 | ``` | |||
| 24 | ||||
| 25 | To include the plugin, just use `leaflet-heat.js` from the `dist` folder: | |||
| 26 | ||||
| 27 | ```html | |||
| 28 | <script src="leaflet-heat.js"></script> | |||
| 29 | ``` | |||
| 30 | ||||
| 31 | ## Building | |||
| 32 | To build the dist files run: | |||
| 33 | ```npm install && npm run prepublish``` | |||
| 34 | ||||
| 35 | ||||
| 36 | ## Reference | |||
| 37 | ||||
| 38 | #### L.heatLayer(latlngs, options) | |||
| 39 | ||||
| 40 | Constructs a heatmap layer given an array of points and an object with the following options: | |||
| 41 | - **minOpacity** - the minimum opacity the heat will start at | |||
| 42 | - **maxZoom** - zoom level where the points reach maximum intensity (as intensity scales with zoom), | |||
| 43 | equals `maxZoom` of the map by default | |||
| 44 | - **max** - maximum point intensity, `1.0` by default | |||
| 45 | - **radius** - radius of each "point" of the heatmap, `25` by default | |||
| 46 | - **blur** - amount of blur, `15` by default | |||
| 47 | - **gradient** - color gradient config, e.g. `{0.4: 'blue', 0.65: 'lime', 1: 'red'}` | |||
| 48 | ||||
| 49 | Each point in the input array can be either an array like `[50.5, 30.5, 0.5]`, | |||
| 50 | or a [Leaflet LatLng object](http://leafletjs.com/reference.html#latlng). | |||
| 51 | ||||
| 52 | Optional third argument in each `LatLng` point (`altitude`) represents point intensity. | |||
| 53 | Unless `max` option is specified, intensity should range between `0.0` and `1.0`. | |||
| 54 | ||||
| 55 | ||||
| 56 | #### Methods | |||
| 57 | ||||
| 58 | - **setOptions(options)**: Sets new heatmap options and redraws it. | |||
| 59 | - **addLatLng(latlng)**: Adds a new point to the heatmap and redraws it. | |||
| 60 | - **setLatLngs(latlngs)**: Resets heatmap data and redraws it. | |||
| 61 | - **redraw()**: Redraws the heatmap. |
src/main/resources/leafletmap/Leaflet.heat/demo/draw.html
View file @
79b0010
| File was created | 1 | <!DOCTYPE html> | ||
| 2 | <html> | |||
| 3 | <head> | |||
| 4 | <title>Leaflet.heat demo</title> | |||
| 5 | <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" /> | |||
| 6 | <script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script> | |||
| 7 | <style> | |||
| 8 | #map { width: 800px; height: 600px; } | |||
| 9 | body { font: 16px/1.4 "Helvetica Neue", Arial, sans-serif; } | |||
| 10 | .ghbtns { position: relative; top: 4px; margin-left: 5px; } | |||
| 11 | a { color: #0077ff; } | |||
| 12 | </style> | |||
| 13 | </head> | |||
| 14 | <body> | |||
| 15 | ||||
| 16 | <p> | |||
| 17 | A dynamic demo of <a href="https://github.com/Leaflet/Leaflet.heat">Leaflet.heat</a>, a tiny and fast Leaflet heatmap plugin. | |||
| 18 | <iframe class="ghbtns" src="http://ghbtns.com/github-btn.html?user=Leaflet&repo=Leaflet.heat&type=watch&count=true" | |||
| 19 | allowtransparency="true" frameborder="0" scrolling="0" width="90" height="20"></iframe> | |||
| 20 | </p> | |||
| 21 | ||||
| 22 | <div id="map"></div> | |||
| 23 | ||||
| 24 | <!-- <script src="../node_modules/simpleheat/simpleheat.js"></script> | |||
| 25 | <script src="../src/HeatLayer.js"></script> | |||
| 26 | --> | |||
| 27 | <script src="../dist/leaflet-heat.js"></script> | |||
| 28 | ||||
| 29 | <script src="http://leaflet.github.io/Leaflet.markercluster/example/realworld.388.js"></script> | |||
| 30 | <script> | |||
| 31 | ||||
| 32 | var map = L.map('map').setView([-37.82109, 175.2193], 16); | |||
| 33 | ||||
| 34 | var tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { | |||
| 35 | attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors', | |||
| 36 | }).addTo(map); | |||
| 37 | ||||
| 38 | addressPoints = addressPoints.map(function (p) { return [p[0], p[1]]; }); | |||
| 39 | ||||
| 40 | var heat = L.heatLayer(addressPoints).addTo(map), | |||
| 41 | draw = true; | |||
| 42 | ||||
| 43 | map.on({ | |||
| 44 | movestart: function () { draw = false; }, | |||
| 45 | moveend: function () { draw = true; }, |
src/main/resources/leafletmap/Leaflet.heat/demo/index.html
View file @
79b0010
| File was created | 1 | <!DOCTYPE html> | ||
| 2 | <html> | |||
| 3 | <head> | |||
| 4 | <title>Leaflet.heat demo</title> | |||
| 5 | <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" /> | |||
| 6 | <script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script> | |||
| 7 | <style> | |||
| 8 | #map { width: 800px; height: 600px; } | |||
| 9 | body { font: 16px/1.4 "Helvetica Neue", Arial, sans-serif; } | |||
| 10 | .ghbtns { position: relative; top: 4px; margin-left: 5px; } | |||
| 11 | a { color: #0077ff; } | |||
| 12 | </style> | |||
| 13 | </head> | |||
| 14 | <body> | |||
| 15 | ||||
| 16 | <p> | |||
| 17 | A 10,000-point demo of <a href="https://github.com/Leaflet/Leaflet.heat">Leaflet.heat</a>, a tiny and fast Leaflet heatmap plugin. | |||
| 18 | <iframe class="ghbtns" src="http://ghbtns.com/github-btn.html?user=Leaflet&repo=Leaflet.heat&type=watch&count=true" | |||
| 19 | allowtransparency="true" frameborder="0" scrolling="0" width="90" height="20"></iframe> | |||
| 20 | </p> | |||
| 21 | ||||
| 22 | <div id="map"></div> | |||
| 23 | ||||
| 24 | <!-- <script src="../node_modules/simpleheat/simpleheat.js"></script> | |||
| 25 | <script src="../src/HeatLayer.js"></script> --> | |||
| 26 | ||||
| 27 | <script src="../dist/leaflet-heat.js"></script> | |||
| 28 | ||||
| 29 | <script src="http://leaflet.github.io/Leaflet.markercluster/example/realworld.10000.js"></script> | |||
| 30 | <script> | |||
| 31 | ||||
| 32 | var map = L.map('map').setView([-37.87, 175.475], 12); | |||
| 33 | ||||
| 34 | var tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { |
src/main/resources/leafletmap/Leaflet.heat/dist/leaflet-heat.js
View file @
79b0010
| File was created | 1 | /* | ||
| 2 | (c) 2014, Vladimir Agafonkin | |||
| 3 | simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas | |||
| 4 | https://github.com/mourner/simpleheat | |||
| 5 | */ | |||
| 6 | !function(){"use strict";function t(i){return this instanceof t?(this._canvas=i="string"==typeof i?document.getElementById(i):i,this._ctx=i.getContext("2d"),this._width=i.width,this._height=i.height,this._max=1,void this.clear()):new t(i)}t.prototype={defaultRadius:25,defaultGradient:{.4:"blue",.6:"cyan",.7:"lime",.8:"yellow",1:"red"},data:function(t,i){return this._data=t,this},max:function(t){return this._max=t,this},add:function(t){return this._data.push(t),this},clear:function(){return this._data=[],this},radius:function(t,i){i=i||15;var a=this._circle=document.createElement("canvas"),s=a.getContext("2d"),e=this._r=t+i;return a.width=a.height=2*e,s.shadowOffsetX=s.shadowOffsetY=200,s.shadowBlur=i,s.shadowColor="black",s.beginPath(),s.arc(e-200,e-200,t,0,2*Math.PI,!0),s.closePath(),s.fill(),this},gradient:function(t){var i=document.createElement("canvas"),a=i.getContext("2d"),s=a.createLinearGradient(0,0,0,256);i.width=1,i.height=256;for(var e in t)s.addColorStop(e,t[e]);return a.fillStyle=s,a.fillRect(0,0,1,256),this._grad=a.getImageData(0,0,1,256).data,this},draw:function(t){this._circle||this.radius(this.defaultRadius),this._grad||this.gradient(this.defaultGradient);var i=this._ctx;i.clearRect(0,0,this._width,this._height);for(var a,s=0,e=this._data.length;e>s;s++)a=this._data[s],i.globalAlpha=Math.max(a[2]/this._max,t||.05),i.drawImage(this._circle,a[0]-this._r,a[1]-this._r);var n=i.getImageData(0,0,this._width,this._height);return this._colorize(n.data,this._grad),i.putImageData(n,0,0),this},_colorize:function(t,i){for(var a,s=3,e=t.length;e>s;s+=4)a=4*t[s],a&&(t[s-3]=i[a],t[s-2]=i[a+1],t[s-1]=i[a+2])}},window.simpleheat=t}(),/* | |||
| 7 | (c) 2014, Vladimir Agafonkin | |||
| 8 | Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. | |||
| 9 | https://github.com/Leaflet/Leaflet.heat | |||
| 10 | */ | |||
| 11 | L.HeatLayer=(L.Layer?L.Layer:L.Class).extend({initialize:function(t,i){this._latlngs=t,L.setOptions(this,i)},setLatLngs:function(t){return this._latlngs=t,this.redraw()},addLatLng:function(t){return this._latlngs.push(t),this.redraw()},setOptions:function(t){return L.setOptions(this,t),this._heat&&this._updateOptions(),this.redraw()},redraw:function(){return!this._heat||this._frame||this._map._animating||(this._frame=L.Util.requestAnimFrame(this._redraw,this)),this},onAdd:function(t){this._map=t,this._canvas||this._initCanvas(),t._panes.overlayPane.appendChild(this._canvas),t.on("moveend",this._reset,this),t.options.zoomAnimation&&L.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._canvas),t.off("moveend",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},_initCanvas:function(){var t=this._canvas=L.DomUtil.create("canvas","leaflet-heatmap-layer leaflet-layer"),i=L.DomUtil.testProp(["transformOrigin","WebkitTransformOrigin","msTransformOrigin"]);t.style[i]="50% 50%";var a=this._map.getSize();t.width=a.x,t.height=a.y;var s=this._map.options.zoomAnimation&&L.Browser.any3d;L.DomUtil.addClass(t,"leaflet-zoom-"+(s?"animated":"hide")),this._heat=simpleheat(t),this._updateOptions()},_updateOptions:function(){this._heat.radius(this.options.radius||this._heat.defaultRadius,this.options.blur),this.options.gradient&&this._heat.gradient(this.options.gradient),this.options.max&&this._heat.max(this.options.max)},_reset:function(){var t=this._map.containerPointToLayerPoint([0,0]);L.DomUtil.setPosition(this._canvas,t);var i=this._map.getSize();this._heat._width!==i.x&&(this._canvas.width=this._heat._width=i.x),this._heat._height!==i.y&&(this._canvas.height=this._heat._height=i.y),this._redraw()},_redraw:function(){var t,i,a,s,e,n,h,o,r,d=[],_=this._heat._r,l=this._map.getSize(),m=new L.Bounds(L.point([-_,-_]),l.add([_,_])),c=void 0===this.options.max?1:this.options.max,u=void 0===this.options.maxZoom?this._map.getMaxZoom():this.options.maxZoom,f=1/Math.pow(2,Math.max(0,Math.min(u-this._map.getZoom(),12))),g=_/2,p=[],v=this._map._getMapPanePos(),w=v.x%g,y=v.y%g;for(t=0,i=this._latlngs.length;i>t;t++)if(a=this._map.latLngToContainerPoint(this._latlngs[t]),m.contains(a)){e=Math.floor((a.x-w)/g)+2,n=Math.floor((a.y-y)/g)+2;var x=void 0!==this._latlngs[t].alt?this._latlngs[t].alt:void 0!==this._latlngs[t][2]?+this._latlngs[t][2]:1;r=x*f,p[n]=p[n]||[],s=p[n][e],s?(s[0]=(s[0]*s[2]+a.x*r)/(s[2]+r),s[1]=(s[1]*s[2]+a.y*r)/(s[2]+r),s[2]+=r):p[n][e]=[a.x,a.y,r]}for(t=0,i=p.length;i>t;t++)if(p[t])for(h=0,o=p[t].length;o>h;h++)s=p[t][h],s&&d.push([Math.round(s[0]),Math.round(s[1]),Math.min(s[2],c)]);this._heat.data(d).draw(this.options.minOpacity),this._frame=null},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),a=this._map._getCenterOffset(t.center)._multiplyBy(-i).subtract(this._map._getMapPanePos());L.DomUtil.setTransform?L.DomUtil.setTransform(this._canvas,a,i):this._canvas.style[L.DomUtil.TRANSFORM]=L.DomUtil.getTranslateString(a)+" scale("+i+")"}}),L.heatLayer=function(t,i){return new L.HeatLayer(t,i)}; |
src/main/resources/leafletmap/Leaflet.heat/package.json
View file @
79b0010
| File was created | 1 | { | ||
| 2 | "name": "leaflet.heat", | |||
| 3 | "version": "0.2.0", | |||
| 4 | "description": "A tiny and fast Leaflet heatmap plugin.", | |||
| 5 | "homepage": "https://github.com/Leaflet/Leaflet.heat", | |||
| 6 | "keywords": [ | |||
| 7 | "heatmap", | |||
| 8 | "canvas", | |||
| 9 | "visualization", | |||
| 10 | "gis", | |||
| 11 | "leaflet", | |||
| 12 | "plugin" | |||
| 13 | ], | |||
| 14 | "author": "Vladimir Agafonkin", | |||
| 15 | "repository": { | |||
| 16 | "type": "git", | |||
| 17 | "url": "git://github.com/Leaflet/Leaflet.heat.git" | |||
| 18 | }, | |||
| 19 | "main": "dist/leaflet-heat.js", | |||
| 20 | "devDependencies": { | |||
| 21 | "eslint": "^1.7.3", | |||
| 22 | "eslint-config-mourner": "^1.0.1", | |||
| 23 | "simpleheat": "~0.2.0", | |||
| 24 | "uglify-js": "^2.5.0" | |||
| 25 | }, | |||
| 26 | "eslintConfig": { | |||
| 27 | "extends": "mourner", | |||
| 28 | "globals": { | |||
| 29 | "L": false, | |||
| 30 | "simpleheat": false | |||
| 31 | } | |||
| 32 | }, | |||
| 33 | "scripts": { | |||
| 34 | "test": "eslint src", | |||
| 35 | "prepublish": "uglifyjs node_modules/simpleheat/simpleheat.js src/HeatLayer.js -c -m -o dist/leaflet-heat.js" | |||
| 36 | }, | |||
| 37 | "license": "BSD-2-Clause", | |||
| 38 | "jshintConfig": { | |||
| 39 | "quotmark": "single", | |||
| 40 | "globals": { | |||
| 41 | "L": true, | |||
| 42 | "simpleheat": true | |||
| 43 | }, | |||
| 44 | "trailing": true, | |||
| 45 | "camelcase": true, | |||
| 46 | "curly": true, | |||
| 47 | "eqeqeq": true, | |||
| 48 | "noempty": true, | |||
| 49 | "nonbsp": true, | |||
| 50 | "undef": true, | |||
| 51 | "unused": true, | |||
| 52 | "browser": true | |||
| 53 | } | |||
| 54 | } |
src/main/resources/leafletmap/Leaflet.heat/src/HeatLayer.js
View file @
79b0010
| File was created | 1 | 'use strict'; | ||
| 2 | ||||
| 3 | L.HeatLayer = (L.Layer ? L.Layer : L.Class).extend({ | |||
| 4 | ||||
| 5 | // options: { | |||
| 6 | // minOpacity: 0.05, | |||
| 7 | // maxZoom: 18, | |||
| 8 | // radius: 25, | |||
| 9 | // blur: 15, | |||
| 10 | // max: 1.0 | |||
| 11 | // }, | |||
| 12 | ||||
| 13 | initialize: function (latlngs, options) { | |||
| 14 | this._latlngs = latlngs; | |||
| 15 | L.setOptions(this, options); | |||
| 16 | }, | |||
| 17 | ||||
| 18 | setLatLngs: function (latlngs) { | |||
| 19 | this._latlngs = latlngs; | |||
| 20 | return this.redraw(); | |||
| 21 | }, | |||
| 22 | ||||
| 23 | addLatLng: function (latlng) { | |||
| 24 | this._latlngs.push(latlng); | |||
| 25 | return this.redraw(); | |||
| 26 | }, | |||
| 27 | ||||
| 28 | setOptions: function (options) { | |||
| 29 | L.setOptions(this, options); | |||
| 30 | if (this._heat) { | |||
| 31 | this._updateOptions(); | |||
| 32 | } | |||
| 33 | return this.redraw(); | |||
| 34 | }, | |||
| 35 | ||||
| 36 | redraw: function () { | |||
| 37 | if (this._heat && !this._frame && this._map && !this._map._animating) { | |||
| 38 | this._frame = L.Util.requestAnimFrame(this._redraw, this); | |||
| 39 | } | |||
| 40 | return this; | |||
| 41 | }, | |||
| 42 | ||||
| 43 | onAdd: function (map) { | |||
| 44 | this._map = map; | |||
| 45 | ||||
| 46 | if (!this._canvas) { | |||
| 47 | this._initCanvas(); | |||
| 48 | } | |||
| 49 | ||||
| 50 | if (this.options.pane) { | |||
| 51 | this.getPane().appendChild(this._canvas); | |||
| 52 | }else{ | |||
| 53 | map._panes.overlayPane.appendChild(this._canvas); | |||
| 54 | } | |||
| 55 | ||||
| 56 | map.on('moveend', this._reset, this); | |||
| 57 | ||||
| 58 | if (map.options.zoomAnimation && L.Browser.any3d) { | |||
| 59 | map.on('zoomanim', this._animateZoom, this); | |||
| 60 | } | |||
| 61 | ||||
| 62 | this._reset(); | |||
| 63 | }, | |||
| 64 | ||||
| 65 | onRemove: function (map) { | |||
| 66 | if (this.options.pane) { | |||
| 67 | this.getPane().removeChild(this._canvas); | |||
| 68 | }else{ | |||
| 69 | map.getPanes().overlayPane.removeChild(this._canvas); | |||
| 70 | } | |||
| 71 | ||||
| 72 | map.off('moveend', this._reset, this); | |||
| 73 | ||||
| 74 | if (map.options.zoomAnimation) { | |||
| 75 | map.off('zoomanim', this._animateZoom, this); | |||
| 76 | } | |||
| 77 | }, | |||
| 78 | ||||
| 79 | addTo: function (map) { | |||
| 80 | map.addLayer(this); | |||
| 81 | return this; | |||
| 82 | }, | |||
| 83 | ||||
| 84 | _initCanvas: function () { | |||
| 85 | var canvas = this._canvas = L.DomUtil.create('canvas', 'leaflet-heatmap-layer leaflet-layer'); | |||
| 86 | ||||
| 87 | var originProp = L.DomUtil.testProp(['transformOrigin', 'WebkitTransformOrigin', 'msTransformOrigin']); | |||
| 88 | canvas.style[originProp] = '50% 50%'; | |||
| 89 | ||||
| 90 | var size = this._map.getSize(); | |||
| 91 | canvas.width = size.x; | |||
| 92 | canvas.height = size.y; | |||
| 93 | ||||
| 94 | var animated = this._map.options.zoomAnimation && L.Browser.any3d; | |||
| 95 | L.DomUtil.addClass(canvas, 'leaflet-zoom-' + (animated ? 'animated' : 'hide')); | |||
| 96 | ||||
| 97 | this._heat = simpleheat(canvas); | |||
| 98 | this._updateOptions(); | |||
| 99 | }, | |||
| 100 | ||||
| 101 | _updateOptions: function () { | |||
| 102 | this._heat.radius(this.options.radius || this._heat.defaultRadius, this.options.blur); | |||
| 103 | ||||
| 104 | if (this.options.gradient) { | |||
| 105 | this._heat.gradient(this.options.gradient); | |||
| 106 | } | |||
| 107 | if (this.options.max) { | |||
| 108 | this._heat.max(this.options.max); | |||
| 109 | } | |||
| 110 | }, | |||
| 111 | ||||
| 112 | _reset: function () { | |||
| 113 | var topLeft = this._map.containerPointToLayerPoint([0, 0]); | |||
| 114 | L.DomUtil.setPosition(this._canvas, topLeft); | |||
| 115 | ||||
| 116 | var size = this._map.getSize(); | |||
| 117 | ||||
| 118 | if (this._heat._width !== size.x) { | |||
| 119 | this._canvas.width = this._heat._width = size.x; | |||
| 120 | } | |||
| 121 | if (this._heat._height !== size.y) { | |||
| 122 | this._canvas.height = this._heat._height = size.y; | |||
| 123 | } | |||
| 124 | ||||
| 125 | this._redraw(); | |||
| 126 | }, | |||
| 127 | ||||
| 128 | _redraw: function () { | |||
| 129 | if (!this._map) { | |||
| 130 | return; | |||
| 131 | } | |||
| 132 | var data = [], | |||
| 133 | r = this._heat._r, | |||
| 134 | size = this._map.getSize(), | |||
| 135 | bounds = new L.Bounds( | |||
| 136 | L.point([-r, -r]), | |||
| 137 | size.add([r, r])), | |||
| 138 | ||||
| 139 | max = this.options.max === undefined ? 1 : this.options.max, | |||
| 140 | maxZoom = this.options.maxZoom === undefined ? this._map.getMaxZoom() : this.options.maxZoom, | |||
| 141 | v = 1 / Math.pow(2, Math.max(0, Math.min(maxZoom - this._map.getZoom(), 12))), | |||
| 142 | cellSize = r / 2, | |||
| 143 | grid = [], | |||
| 144 | panePos = this._map._getMapPanePos(), | |||
| 145 | offsetX = panePos.x % cellSize, | |||
| 146 | offsetY = panePos.y % cellSize, | |||
| 147 | i, len, p, cell, x, y, j, len2, k; | |||
| 148 | ||||
| 149 | // console.time('process'); | |||
| 150 | for (i = 0, len = this._latlngs.length; i < len; i++) { | |||
| 151 | p = this._map.latLngToContainerPoint(this._latlngs[i]); | |||
| 152 | if (bounds.contains(p)) { | |||
| 153 | x = Math.floor((p.x - offsetX) / cellSize) + 2; | |||
| 154 | y = Math.floor((p.y - offsetY) / cellSize) + 2; | |||
| 155 | ||||
| 156 | var alt = | |||
| 157 | this._latlngs[i].alt !== undefined ? this._latlngs[i].alt : | |||
| 158 | this._latlngs[i][2] !== undefined ? +this._latlngs[i][2] : 1; | |||
| 159 | k = alt * v; | |||
| 160 | ||||
| 161 | grid[y] = grid[y] || []; | |||
| 162 | cell = grid[y][x]; | |||
| 163 | ||||
| 164 | if (!cell) { | |||
| 165 | grid[y][x] = [p.x, p.y, k]; | |||
| 166 | ||||
| 167 | } else { | |||
| 168 | cell[0] = (cell[0] * cell[2] + p.x * k) / (cell[2] + k); // x | |||
| 169 | cell[1] = (cell[1] * cell[2] + p.y * k) / (cell[2] + k); // y | |||
| 170 | cell[2] += k; // cumulated intensity value | |||
| 171 | } |
src/main/resources/leafletmap/leafletmap.html
View file @
79b0010
| <!DOCTYPE html> | 1 | 1 | <!DOCTYPE html> | |
| <html> | 2 | 2 | <html> | |
| <head> | 3 | 3 | <head> | |
| <title>Leaflet Map</title> | 4 | 4 | <title>Leaflet Map</title> | |
| 5 | 5 | |||
| <meta charset="utf-8" /> | 6 | 6 | <meta charset="utf-8" /> | |
| <!-- only needed for mobile browsers: disable unwanted scaling and use full width --> | 7 | 7 | <!-- only needed for mobile browsers: disable unwanted scaling and use full width --> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> | 8 | 8 | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> | |
| 9 | 9 | |||
| <!-- force the map to use the full available space --> | 10 | 10 | <!-- force the map to use the full available space --> | |
| <style> | 11 | 11 | <style> | |
| html, body, #map { | 12 | 12 | html, body, #map { | |
| height: 100%; | 13 | 13 | height: 100%; | |
| width: 100%; | 14 | 14 | width: 100%; | |
| padding: 0px; | 15 | 15 | padding: 0px; | |
| margin: 0px; | 16 | 16 | margin: 0px; | |
| } | 17 | 17 | } | |
| </style> | 18 | 18 | </style> | |
| 19 | 19 | |||
| <!-- for remote access of the Leaflet library | 20 | 20 | <!-- for remote access of the Leaflet library | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" /> | 21 | 21 | <link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" /> | |
| <script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script> --> | 22 | 22 | <script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script> --> | |
| 23 | 23 | |||
| <link rel="stylesheet" href="leaflet/leaflet.css" /> | 24 | 24 | <link rel="stylesheet" href="leaflet/leaflet.css" /> | |
| <link rel="stylesheet" href="Leaflet.markercluster-1.4.1/dist/MarkerCluster.css" /> | 25 | 25 | <link rel="stylesheet" href="Leaflet.markercluster-1.4.1/dist/MarkerCluster.css" /> | |
| <link rel="stylesheet" href="Leaflet.markercluster-1.4.1/dist/MarkerCluster.Default.css" /> | 26 | 26 | <link rel="stylesheet" href="Leaflet.markercluster-1.4.1/dist/MarkerCluster.Default.css" /> | |
| 27 | 27 | |||
| <script src="leaflet/leaflet.js"></script> | 28 | 28 | <script src="leaflet/leaflet.js"></script> | |
| <script src="leaflet/leaflet.rotatedMarker.js"></script> | 29 | 29 | <script src="leaflet/leaflet.rotatedMarker.js"></script> | |
| <script src="Leaflet.markercluster-1.4.1/dist/leaflet.markercluster-src.js"></script> | 30 | 30 | <script src="Leaflet.markercluster-1.4.1/dist/leaflet.markercluster-src.js"></script> | |
| 31 | <script src="Leaflet.heat/dist/leaflet-heat.js"></script> | |||
| 31 | 32 |