Commit 79b001037b8d42da03b5857adf1243176060e734

Authored by lsagona
1 parent 9e952e84e1
Exists in master and in 1 other branch dev

heat map

Showing 16 changed files with 605 additions and 24 deletions Side-by-side Diff

build.gradle View file @ 79b0010
... ... @@ -13,7 +13,10 @@
13 13 dependencies {
14 14 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
15 15 implementation 'org.jfxtras:jmetro:8.6.9'
  16 + implementation 'org.slf4j:slf4j-api:1.7.30'
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  
19 22 compileKotlin {
src/main/kotlin/application/Logger.kt View file @ 79b0010
  1 +package application
  2 +
  3 +import org.slf4j.Logger
  4 +import org.slf4j.LoggerFactory
  5 +
  6 +fun getLogger(): Logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)
src/main/kotlin/application/controller/MapPanelController.kt View file @ 79b0010
... ... @@ -2,14 +2,12 @@
2 2  
3 3 import application.model.*
4 4 import application.model.State.*
5   -import javafx.concurrent.Worker
6 5 import javafx.fxml.FXML
7 6 import javafx.fxml.Initializable
8 7 import javafx.scene.layout.StackPane
9 8 import map.*
10 9 import java.net.URL
11 10 import java.util.*
12   -import java.util.concurrent.CompletableFuture
13 11  
14 12 class MapPanelController : Initializable {
15 13  
16 14  
... ... @@ -20,12 +18,12 @@
20 18  
21 19  
22 20 override fun initialize(location: URL?, resources: ResourceBundle?) {
23   - val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig())
24   -
  21 + mapView.displayMap(MapConfig())
25 22 setObservableVesselListener()
26 23 setObservableSelectedVesselListener()
27 24 setStateListener()
28   - /*completeFutureMap.whenComplete{
  25 + /*val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig())
  26 + completeFutureMap.whenComplete{
29 27 workerState, _ ->
30 28 if (workerState == Worker.State.SUCCEEDED) {
31 29 }
32 30  
33 31  
... ... @@ -37,22 +35,26 @@
37 35 private fun setStateListener() {
38 36 observableState.listeners.add(object : StateListener {
39 37 override fun onValueChanged(newValue: State) {
40   - updateMap()
  38 + if (observableSelectedVessel.vessel.mmsi != null) {
  39 + updateMap(observableSelectedVessel.vessel.mmsi!!)
  40 + } else {
  41 + updateMap()
  42 + }
41 43 }
42 44 })
43 45 }
44 46  
45 47 private fun updateMap() {
46   - when(observableState.state){
47   - ALL_MESSAGES -> displayAllMessageOnMap(mapView)
  48 + when (observableState.state) {
  49 + ALL_MESSAGES -> displayAllMessageOnMap(mapView)
48 50 CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView)
49 51 HEAT_MAP -> displayHeatMapOnMap(mapView)
50 52 }
51 53 }
52 54  
53 55 private fun updateMap(selectedMMSI: Int) {
54   - when(observableState.state){
55   - ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI)
  56 + when (observableState.state) {
  57 + ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI)
56 58 CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI)
57 59 HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI)
58 60 }
... ... @@ -69,7 +71,9 @@
69 71 private fun setObservableSelectedVesselListener() {
70 72 observableSelectedVessel.listeners.add(object : SelectedVesselListener {
71 73 override fun onValueChanged(newValue: Vessel) {
72   - updateMap(newValue.mmsi!!)
  74 + if (newValue.mmsi != null){
  75 + updateMap(newValue.mmsi)
  76 + }
73 77 }
74 78 })
75 79 }
src/main/kotlin/application/controller/MenuBarController.kt View file @ 79b0010
1 1 package application.controller
2 2  
3   -import application.model.State
4 3 import application.model.State.*
5 4 import application.model.createVesselCollection
6 5 import application.model.observableState
7 6  
... ... @@ -39,7 +38,10 @@
39 38 setOnActionAllMessageButton()
40 39 setOnActionClusteredMessageButton()
41 40 setOnActionHeatMapButton()
  41 + observableState.state = CLUSTERED_MESSAGES
  42 + allMessages.isSelected = false
42 43 clusteredMessage.isSelected = true
  44 + heatMap.isSelected = false
43 45  
44 46 }
45 47  
... ... @@ -49,9 +51,13 @@
49 51 fileChooser.title = "Choose a file to import"
50 52 val window = menuBar.scene.window
51 53 val file = fileChooser.showOpenDialog(window)
52   - val vessels = createVesselCollection(file)
53   - observableVessel.vessels.clear()
54   - observableVessel.vessels = vessels
  54 + try {
  55 + val vessels = createVesselCollection(file)
  56 + observableVessel.vessels.clear()
  57 + observableVessel.vessels = vessels
  58 + } catch (ignore: IllegalStateException){
  59 +
  60 + }
55 61 }
56 62 }
57 63  
src/main/kotlin/application/model/Message.kt View file @ 79b0010
... ... @@ -20,8 +20,16 @@
20 20 val draft: Double? = split[14].toDoubleOrNull()
21 21 val cargo: Int? = split[15].toIntOrNull()
22 22  
23   - fun getHexColor(): String{
  23 + fun getHexColorStroke(): String{
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)
25 33 if (hex.length > 6){
26 34 hex = hex.substring(hex.length - 6)
27 35 }
src/main/kotlin/application/model/ObservableVessel.kt View file @ 79b0010
... ... @@ -9,6 +9,7 @@
9 9 initialValue = mutableMapOf(),
10 10 onChange = { _, _, new ->
11 11 run {
  12 + observableSelectedVessel.vessel = Vessel(null)
12 13 listeners.forEach {
13 14 it.onValueChanged(new)
14 15 }
src/main/kotlin/map/LeafletMapView.kt View file @ 79b0010
... ... @@ -88,13 +88,14 @@
88 88 """
89 89 |var myMap = L.map('map', {
90 90 | center: new L.LatLng(${mapConfig.initialCenter.latitude}, ${mapConfig.initialCenter.longitude}),
91   - | zoom: 5,
  91 + | zoom: 1,
92 92 | zoomControl: false,
93 93 | layers: [layer1]
94 94 |});
95 95 |L.control.scale().addTo(myMap);
96 96 |var myRenderer = L.canvas({ padding: 0.5 });
97   - |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10});""".trimMargin()
  97 + |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10});
  98 + |var heatLayer = L.heatLayer([]).addTo(myMap);""".trimMargin()
98 99 )
99 100  
100 101 // eventZoomChangeIcon()
src/main/kotlin/map/MapDisplayer.kt View file @ 79b0010
... ... @@ -5,13 +5,14 @@
5 5 fun clearMap(map: LeafletMapView) {
6 6 clearMapCanvas(map)
7 7 clearMapCluster(map)
  8 + clearHeatMap(map)
8 9 }
9 10  
10 11 fun clearMapCluster(map: LeafletMapView) {
11 12 map.execScript(
12 13 """
13   - |myMap.removeLayer(markerClusters)
14   - |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 9});
  14 + |myMap.removeLayer(markerClusters);
  15 + |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10});
15 16 """.trimMargin()
16 17 )
17 18 }
18 19  
19 20  
... ... @@ -19,17 +20,26 @@
19 20 fun clearMapCanvas(map: LeafletMapView) {
20 21 map.execScript(
21 22 """
22   - |myRenderer.removeFrom(myMap)
  23 + |myRenderer.removeFrom(myMap);
23 24 |var myRenderer = L.canvas({ padding: 0.5 });
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 +
28 38 fun displayAllMessageOnMap(map: LeafletMapView) {
29 39 clearMap(map)
30 40 observableVessel.vessels.forEach { (_, value) ->
31 41 value.messages.forEach { (_, message) ->
32   - map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColor()}'}).addTo(myMap)")
  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  
37 47  
... ... @@ -39,11 +49,59 @@
39 49 observableVessel.vessels.forEach { (_, value) ->
40 50 value.messages.forEach { (_, message) ->
41 51 if (selectedMMSI == message.mmsi) {
42   - map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff001e'}).addTo(myMap)")
  52 + map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap)")
43 53 } else {
44   - map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColor()}'}).addTo(myMap)")
  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
  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
  19 +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  20 +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  21 +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  22 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
src/main/resources/leafletmap/Leaflet.heat/README.md View file @ 79b0010
  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 &rarr;](http://leaflet.github.io/Leaflet.heat/demo)
  12 +- [Adding points dynamically &rarr;](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.
  62 +
  63 +## Changelog
  64 +
  65 +### 0.2.0 &mdash; Oct 26, 2015
  66 +
  67 +- Fixed intensity to work properly with `max` option.
  68 +- Fixed zoom animation on Leaflet 1.0 beta 2.
  69 +- Fixed tiles and point intensity in demos.
  70 +
  71 +#### 0.1.3 &mdash; Nov 25, 2015
  72 +
  73 +- Fixed some edge cases when handling point intensity.
  74 +- Added `minOpacity` option.
  75 +
  76 +#### 0.1.2 &mdash; Nov 5, 2014
  77 +
  78 +- Added compatibility with Leaflet 0.8-dev.
  79 +
  80 +#### 0.1.1 &mdash; Apr 22, 2014
  81 +
  82 +- Fixed overlaying two heatmaps on top of each other.
  83 +- Fixed rare animation issues.
  84 +
  85 +#### 0.1.0 &mdash; Feb 3, 2014
  86 +
  87 +- Added `addLatLng`, `setLatlngs`, `setOptions` and `redraw` methods.
  88 +- Added `max` option and support for different point intensity values (through `LatLng` third argument).
  89 +- Added `gradient` option to customize colors.
  90 +
  91 +#### 0.0.1 &mdash; Jan 31, 2014
  92 +
  93 +- Initial release.
src/main/resources/leafletmap/Leaflet.heat/demo/draw.html View file @ 79b0010
  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&amp;repo=Leaflet.heat&amp;type=watch&amp;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: '&copy; <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; },
  46 + mousemove: function (e) {
  47 + if (draw) {
  48 + heat.addLatLng(e.latlng);
  49 + }
  50 + }
  51 +})
  52 +
  53 +</script>
  54 +</body>
  55 +</html>
src/main/resources/leafletmap/Leaflet.heat/demo/index.html View file @ 79b0010
  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&amp;repo=Leaflet.heat&amp;type=watch&amp;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', {
  35 + attribution: '&copy; <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 +
  42 +</script>
  43 +</body>
  44 +</html>
src/main/resources/leafletmap/Leaflet.heat/dist/leaflet-heat.js View file @ 79b0010
  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
  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
  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 + }
  172 + }
  173 + }
  174 +
  175 + for (i = 0, len = grid.length; i < len; i++) {
  176 + if (grid[i]) {
  177 + for (j = 0, len2 = grid[i].length; j < len2; j++) {
  178 + cell = grid[i][j];
  179 + if (cell) {
  180 + data.push([
  181 + Math.round(cell[0]),
  182 + Math.round(cell[1]),
  183 + Math.min(cell[2], max)
  184 + ]);
  185 + }
  186 + }
  187 + }
  188 + }
  189 + // console.timeEnd('process');
  190 +
  191 + // console.time('draw ' + data.length);
  192 + this._heat.data(data).draw(this.options.minOpacity);
  193 + // console.timeEnd('draw ' + data.length);
  194 +
  195 + this._frame = null;
  196 + },
  197 +
  198 + _animateZoom: function (e) {
  199 + var scale = this._map.getZoomScale(e.zoom),
  200 + offset = this._map._getCenterOffset(e.center)._multiplyBy(-scale).subtract(this._map._getMapPanePos());
  201 +
  202 + if (L.DomUtil.setTransform) {
  203 + L.DomUtil.setTransform(this._canvas, offset, scale);
  204 +
  205 + } else {
  206 + this._canvas.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ')';
  207 + }
  208 + }
  209 +});
  210 +
  211 +L.heatLayer = function (latlngs, options) {
  212 + return new L.HeatLayer(latlngs, options);
  213 +};
src/main/resources/leafletmap/leafletmap.html View file @ 79b0010
... ... @@ -28,6 +28,7 @@
28 28 <script src="leaflet/leaflet.js"></script>
29 29 <script src="leaflet/leaflet.rotatedMarker.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  
32 33 <script src="leaflet-color-markers/leaflet-color-markers.js"></script>
33 34 </head>