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 |