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 Side-by-side 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
... | ... | @@ -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
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
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 →](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. | |
62 | + | |
63 | +## Changelog | |
64 | + | |
65 | +### 0.2.0 — 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 — Nov 25, 2015 | |
72 | + | |
73 | +- Fixed some edge cases when handling point intensity. | |
74 | +- Added `minOpacity` option. | |
75 | + | |
76 | +#### 0.1.2 — Nov 5, 2014 | |
77 | + | |
78 | +- Added compatibility with Leaflet 0.8-dev. | |
79 | + | |
80 | +#### 0.1.1 — Apr 22, 2014 | |
81 | + | |
82 | +- Fixed overlaying two heatmaps on top of each other. | |
83 | +- Fixed rare animation issues. | |
84 | + | |
85 | +#### 0.1.0 — 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 — 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&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; }, | |
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&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', { | |
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 | + | |
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> |