diff --git a/build.gradle b/build.gradle index 5ee9a83..a3b9534 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,10 @@ repositories { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation 'org.jfxtras:jmetro:8.6.9' + implementation 'org.slf4j:slf4j-api:1.7.30' testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' + compile group: 'ch.qos.logback', name: 'logback-core', version: '1.2.3' } compileKotlin { diff --git a/src/main/kotlin/application/Logger.kt b/src/main/kotlin/application/Logger.kt new file mode 100644 index 0000000..b71dfec --- /dev/null +++ b/src/main/kotlin/application/Logger.kt @@ -0,0 +1,6 @@ +package application + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +fun getLogger(): Logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) \ No newline at end of file diff --git a/src/main/kotlin/application/controller/MapPanelController.kt b/src/main/kotlin/application/controller/MapPanelController.kt index 2862ab3..001cc88 100644 --- a/src/main/kotlin/application/controller/MapPanelController.kt +++ b/src/main/kotlin/application/controller/MapPanelController.kt @@ -2,14 +2,12 @@ package application.controller import application.model.* import application.model.State.* -import javafx.concurrent.Worker import javafx.fxml.FXML import javafx.fxml.Initializable import javafx.scene.layout.StackPane import map.* import java.net.URL import java.util.* -import java.util.concurrent.CompletableFuture class MapPanelController : Initializable { @@ -20,12 +18,12 @@ class MapPanelController : Initializable { override fun initialize(location: URL?, resources: ResourceBundle?) { - val completeFutureMap: CompletableFuture = mapView.displayMap(MapConfig()) - + mapView.displayMap(MapConfig()) setObservableVesselListener() setObservableSelectedVesselListener() setStateListener() - /*completeFutureMap.whenComplete{ + /*val completeFutureMap: CompletableFuture = mapView.displayMap(MapConfig()) + completeFutureMap.whenComplete{ workerState, _ -> if (workerState == Worker.State.SUCCEEDED) { } @@ -37,22 +35,26 @@ class MapPanelController : Initializable { private fun setStateListener() { observableState.listeners.add(object : StateListener { override fun onValueChanged(newValue: State) { - updateMap() + if (observableSelectedVessel.vessel.mmsi != null) { + updateMap(observableSelectedVessel.vessel.mmsi!!) + } else { + updateMap() + } } }) } private fun updateMap() { - when(observableState.state){ - ALL_MESSAGES -> displayAllMessageOnMap(mapView) + when (observableState.state) { + ALL_MESSAGES -> displayAllMessageOnMap(mapView) CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView) HEAT_MAP -> displayHeatMapOnMap(mapView) } } private fun updateMap(selectedMMSI: Int) { - when(observableState.state){ - ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) + when (observableState.state) { + ALL_MESSAGES -> displayAllMessageOnMap(mapView, selectedMMSI) CLUSTERED_MESSAGES -> displayClusterMessageOnMap(mapView, selectedMMSI) HEAT_MAP -> displayHeatMapOnMap(mapView, selectedMMSI) } @@ -69,7 +71,9 @@ class MapPanelController : Initializable { private fun setObservableSelectedVesselListener() { observableSelectedVessel.listeners.add(object : SelectedVesselListener { override fun onValueChanged(newValue: Vessel) { - updateMap(newValue.mmsi!!) + if (newValue.mmsi != null){ + updateMap(newValue.mmsi) + } } }) } diff --git a/src/main/kotlin/application/controller/MenuBarController.kt b/src/main/kotlin/application/controller/MenuBarController.kt index 0c8899c..84dfe7d 100644 --- a/src/main/kotlin/application/controller/MenuBarController.kt +++ b/src/main/kotlin/application/controller/MenuBarController.kt @@ -1,6 +1,5 @@ package application.controller -import application.model.State import application.model.State.* import application.model.createVesselCollection import application.model.observableState @@ -39,7 +38,10 @@ class MenuBarController : Initializable { setOnActionAllMessageButton() setOnActionClusteredMessageButton() setOnActionHeatMapButton() + observableState.state = CLUSTERED_MESSAGES + allMessages.isSelected = false clusteredMessage.isSelected = true + heatMap.isSelected = false } @@ -49,9 +51,13 @@ class MenuBarController : Initializable { fileChooser.title = "Choose a file to import" val window = menuBar.scene.window val file = fileChooser.showOpenDialog(window) - val vessels = createVesselCollection(file) - observableVessel.vessels.clear() - observableVessel.vessels = vessels + try { + val vessels = createVesselCollection(file) + observableVessel.vessels.clear() + observableVessel.vessels = vessels + } catch (ignore: IllegalStateException){ + + } } } diff --git a/src/main/kotlin/application/model/Message.kt b/src/main/kotlin/application/model/Message.kt index ec918b5..cbaeec2 100644 --- a/src/main/kotlin/application/model/Message.kt +++ b/src/main/kotlin/application/model/Message.kt @@ -20,11 +20,19 @@ class Message(split: List) { val draft: Double? = split[14].toDoubleOrNull() val cargo: Int? = split[15].toIntOrNull() - fun getHexColor(): String{ + fun getHexColorStroke(): String{ var hex = Integer.toHexString(this.mmsi!!) if (hex.length > 6){ hex = hex.substring(hex.length - 6) } return hex } + + fun getHexColorFill(): String{ + var hex = Integer.toHexString(this.mmsi!! - 50) + if (hex.length > 6){ + hex = hex.substring(hex.length - 6) + } + return hex + } } \ No newline at end of file diff --git a/src/main/kotlin/application/model/ObservableVessel.kt b/src/main/kotlin/application/model/ObservableVessel.kt index 050ff8b..ebce912 100644 --- a/src/main/kotlin/application/model/ObservableVessel.kt +++ b/src/main/kotlin/application/model/ObservableVessel.kt @@ -9,6 +9,7 @@ class ObservableVessel { initialValue = mutableMapOf(), onChange = { _, _, new -> run { + observableSelectedVessel.vessel = Vessel(null) listeners.forEach { it.onValueChanged(new) } diff --git a/src/main/kotlin/map/LeafletMapView.kt b/src/main/kotlin/map/LeafletMapView.kt index d825515..745d073 100644 --- a/src/main/kotlin/map/LeafletMapView.kt +++ b/src/main/kotlin/map/LeafletMapView.kt @@ -88,13 +88,14 @@ class LeafletMapView : StackPane() { """ |var myMap = L.map('map', { | center: new L.LatLng(${mapConfig.initialCenter.latitude}, ${mapConfig.initialCenter.longitude}), - | zoom: 5, + | zoom: 1, | zoomControl: false, | layers: [layer1] |}); |L.control.scale().addTo(myMap); |var myRenderer = L.canvas({ padding: 0.5 }); - |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10});""".trimMargin() + |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); + |var heatLayer = L.heatLayer([]).addTo(myMap);""".trimMargin() ) // eventZoomChangeIcon() diff --git a/src/main/kotlin/map/MapDisplayer.kt b/src/main/kotlin/map/MapDisplayer.kt index 44cf8f0..859acc9 100644 --- a/src/main/kotlin/map/MapDisplayer.kt +++ b/src/main/kotlin/map/MapDisplayer.kt @@ -5,13 +5,14 @@ import application.model.observableVessel fun clearMap(map: LeafletMapView) { clearMapCanvas(map) clearMapCluster(map) + clearHeatMap(map) } fun clearMapCluster(map: LeafletMapView) { map.execScript( """ - |myMap.removeLayer(markerClusters) - |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 9}); + |myMap.removeLayer(markerClusters); + |var markerClusters = L.markerClusterGroup({spiderfyOnMaxZoom: false, disableClusteringAtZoom: 10}); """.trimMargin() ) } @@ -19,17 +20,26 @@ fun clearMapCluster(map: LeafletMapView) { fun clearMapCanvas(map: LeafletMapView) { map.execScript( """ - |myRenderer.removeFrom(myMap) + |myRenderer.removeFrom(myMap); |var myRenderer = L.canvas({ padding: 0.5 }); """.trimMargin() ) } +fun clearHeatMap(map: LeafletMapView) { + map.execScript( + """ + |heatLayer.removeFrom(myMap); + |var heatLayer = L.heatLayer([]).addTo(myMap); + """.trimMargin() + ) +} + fun displayAllMessageOnMap(map: LeafletMapView) { clearMap(map) observableVessel.vessels.forEach { (_, value) -> value.messages.forEach { (_, message) -> - map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColor()}'}).addTo(myMap)") + map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") } } } @@ -39,10 +49,58 @@ fun displayAllMessageOnMap(map: LeafletMapView, selectedMMSI: Int) { observableVessel.vessels.forEach { (_, value) -> value.messages.forEach { (_, message) -> if (selectedMMSI == message.mmsi) { - map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff001e'}).addTo(myMap)") + map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap)") + } else { + map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}).addTo(myMap)") + } + } + } +} + +fun displayClusterMessageOnMap(map: LeafletMapView) { + clearMap(map) + observableVessel.vessels.forEach { (_, value) -> + value.messages.forEach { (_, message) -> + map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") + } + } + map.execScript("myMap.addLayer(markerClusters);") +} + +fun displayClusterMessageOnMap(map: LeafletMapView, selectedMMSI: Int) { + clearMap(map) + observableVessel.vessels.forEach { (_, value) -> + value.messages.forEach { (_, message) -> + if (selectedMMSI == message.mmsi) { + map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") + } else { + map.execScript("markerClusters.addLayer(L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColorStroke()}'}));") + } + } + } + map.execScript("myMap.addLayer(markerClusters);") +} + +fun displayHeatMapOnMap(map: LeafletMapView) { + clearMap(map) + observableVessel.vessels.forEach { (_, value) -> + value.messages.forEach { (_, message) -> + map.execScript("heatLayer.addLatLng([${message.latitude}, ${message.longitude}]);") + } + } + +} + +fun displayHeatMapOnMap(map: LeafletMapView, selectedMMSI: Int) { + clearMap(map) + observableVessel.vessels.forEach { (_, value) -> + value.messages.forEach { (_, message) -> + if (selectedMMSI == message.mmsi) { + map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 2, color: '#ff4040'}).addTo(myMap);") } else { - map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01, color: '#${message.getHexColor()}'}).addTo(myMap)") + map.execScript("heatLayer.addLatLng([${message.latitude}, ${message.longitude}]);") } } } + map.execScript("myMap.addLayer(markerClusters);") } \ No newline at end of file diff --git a/src/main/resources/leafletmap/Leaflet.heat/LICENSE b/src/main/resources/leafletmap/Leaflet.heat/LICENSE new file mode 100644 index 0000000..91a7b5e --- /dev/null +++ b/src/main/resources/leafletmap/Leaflet.heat/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2014, Vladimir Agafonkin +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/main/resources/leafletmap/Leaflet.heat/README.md b/src/main/resources/leafletmap/Leaflet.heat/README.md new file mode 100644 index 0000000..1e1a7c6 --- /dev/null +++ b/src/main/resources/leafletmap/Leaflet.heat/README.md @@ -0,0 +1,94 @@ +Leaflet.heat +========== + +A tiny, simple and fast [Leaflet](http://leafletjs.com) heatmap plugin. +Uses [simpleheat](https://github.com/mourner/simpleheat) under the hood, +additionally clustering points into a grid for performance. + + +## Demos + +- [10,000 points →](http://leaflet.github.io/Leaflet.heat/demo) +- [Adding points dynamically →](http://leaflet.github.io/Leaflet.heat/demo/draw.html) + + +## Basic Usage + +```js +var heat = L.heatLayer([ + [50.5, 30.5, 0.2], // lat, lng, intensity + [50.6, 30.4, 0.5], + ... +], {radius: 25}).addTo(map); +``` + +To include the plugin, just use `leaflet-heat.js` from the `dist` folder: + +```html + +``` + +## Building +To build the dist files run: +```npm install && npm run prepublish``` + + +## Reference + +#### L.heatLayer(latlngs, options) + +Constructs a heatmap layer given an array of points and an object with the following options: +- **minOpacity** - the minimum opacity the heat will start at +- **maxZoom** - zoom level where the points reach maximum intensity (as intensity scales with zoom), + equals `maxZoom` of the map by default +- **max** - maximum point intensity, `1.0` by default +- **radius** - radius of each "point" of the heatmap, `25` by default +- **blur** - amount of blur, `15` by default +- **gradient** - color gradient config, e.g. `{0.4: 'blue', 0.65: 'lime', 1: 'red'}` + +Each point in the input array can be either an array like `[50.5, 30.5, 0.5]`, +or a [Leaflet LatLng object](http://leafletjs.com/reference.html#latlng). + +Optional third argument in each `LatLng` point (`altitude`) represents point intensity. +Unless `max` option is specified, intensity should range between `0.0` and `1.0`. + + +#### Methods + +- **setOptions(options)**: Sets new heatmap options and redraws it. +- **addLatLng(latlng)**: Adds a new point to the heatmap and redraws it. +- **setLatLngs(latlngs)**: Resets heatmap data and redraws it. +- **redraw()**: Redraws the heatmap. + +## Changelog + +### 0.2.0 — Oct 26, 2015 + +- Fixed intensity to work properly with `max` option. +- Fixed zoom animation on Leaflet 1.0 beta 2. +- Fixed tiles and point intensity in demos. + +#### 0.1.3 — Nov 25, 2015 + +- Fixed some edge cases when handling point intensity. +- Added `minOpacity` option. + +#### 0.1.2 — Nov 5, 2014 + +- Added compatibility with Leaflet 0.8-dev. + +#### 0.1.1 — Apr 22, 2014 + +- Fixed overlaying two heatmaps on top of each other. +- Fixed rare animation issues. + +#### 0.1.0 — Feb 3, 2014 + +- Added `addLatLng`, `setLatlngs`, `setOptions` and `redraw` methods. +- Added `max` option and support for different point intensity values (through `LatLng` third argument). +- Added `gradient` option to customize colors. + +#### 0.0.1 — Jan 31, 2014 + +- Initial release. + diff --git a/src/main/resources/leafletmap/Leaflet.heat/demo/draw.html b/src/main/resources/leafletmap/Leaflet.heat/demo/draw.html new file mode 100644 index 0000000..fe77ef1 --- /dev/null +++ b/src/main/resources/leafletmap/Leaflet.heat/demo/draw.html @@ -0,0 +1,55 @@ + + + + Leaflet.heat demo + + + + + + +

+ A dynamic demo of Leaflet.heat, a tiny and fast Leaflet heatmap plugin. + +

+ +
+ + + + + + + + diff --git a/src/main/resources/leafletmap/Leaflet.heat/demo/index.html b/src/main/resources/leafletmap/Leaflet.heat/demo/index.html new file mode 100644 index 0000000..d070be7 --- /dev/null +++ b/src/main/resources/leafletmap/Leaflet.heat/demo/index.html @@ -0,0 +1,44 @@ + + + + Leaflet.heat demo + + + + + + +

+ A 10,000-point demo of Leaflet.heat, a tiny and fast Leaflet heatmap plugin. + +

+ +
+ + + + + + + + + diff --git a/src/main/resources/leafletmap/Leaflet.heat/dist/leaflet-heat.js b/src/main/resources/leafletmap/Leaflet.heat/dist/leaflet-heat.js new file mode 100644 index 0000000..aa8031a --- /dev/null +++ b/src/main/resources/leafletmap/Leaflet.heat/dist/leaflet-heat.js @@ -0,0 +1,11 @@ +/* + (c) 2014, Vladimir Agafonkin + simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas + https://github.com/mourner/simpleheat +*/ +!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}(),/* + (c) 2014, Vladimir Agafonkin + Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. + https://github.com/Leaflet/Leaflet.heat +*/ +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)}; \ No newline at end of file diff --git a/src/main/resources/leafletmap/Leaflet.heat/package.json b/src/main/resources/leafletmap/Leaflet.heat/package.json new file mode 100644 index 0000000..372d687 --- /dev/null +++ b/src/main/resources/leafletmap/Leaflet.heat/package.json @@ -0,0 +1,54 @@ +{ + "name": "leaflet.heat", + "version": "0.2.0", + "description": "A tiny and fast Leaflet heatmap plugin.", + "homepage": "https://github.com/Leaflet/Leaflet.heat", + "keywords": [ + "heatmap", + "canvas", + "visualization", + "gis", + "leaflet", + "plugin" + ], + "author": "Vladimir Agafonkin", + "repository": { + "type": "git", + "url": "git://github.com/Leaflet/Leaflet.heat.git" + }, + "main": "dist/leaflet-heat.js", + "devDependencies": { + "eslint": "^1.7.3", + "eslint-config-mourner": "^1.0.1", + "simpleheat": "~0.2.0", + "uglify-js": "^2.5.0" + }, + "eslintConfig": { + "extends": "mourner", + "globals": { + "L": false, + "simpleheat": false + } + }, + "scripts": { + "test": "eslint src", + "prepublish": "uglifyjs node_modules/simpleheat/simpleheat.js src/HeatLayer.js -c -m -o dist/leaflet-heat.js" + }, + "license": "BSD-2-Clause", + "jshintConfig": { + "quotmark": "single", + "globals": { + "L": true, + "simpleheat": true + }, + "trailing": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "noempty": true, + "nonbsp": true, + "undef": true, + "unused": true, + "browser": true + } +} diff --git a/src/main/resources/leafletmap/Leaflet.heat/src/HeatLayer.js b/src/main/resources/leafletmap/Leaflet.heat/src/HeatLayer.js new file mode 100644 index 0000000..9c020da --- /dev/null +++ b/src/main/resources/leafletmap/Leaflet.heat/src/HeatLayer.js @@ -0,0 +1,213 @@ +'use strict'; + +L.HeatLayer = (L.Layer ? L.Layer : L.Class).extend({ + + // options: { + // minOpacity: 0.05, + // maxZoom: 18, + // radius: 25, + // blur: 15, + // max: 1.0 + // }, + + initialize: function (latlngs, options) { + this._latlngs = latlngs; + L.setOptions(this, options); + }, + + setLatLngs: function (latlngs) { + this._latlngs = latlngs; + return this.redraw(); + }, + + addLatLng: function (latlng) { + this._latlngs.push(latlng); + return this.redraw(); + }, + + setOptions: function (options) { + L.setOptions(this, options); + if (this._heat) { + this._updateOptions(); + } + return this.redraw(); + }, + + redraw: function () { + if (this._heat && !this._frame && this._map && !this._map._animating) { + this._frame = L.Util.requestAnimFrame(this._redraw, this); + } + return this; + }, + + onAdd: function (map) { + this._map = map; + + if (!this._canvas) { + this._initCanvas(); + } + + if (this.options.pane) { + this.getPane().appendChild(this._canvas); + }else{ + map._panes.overlayPane.appendChild(this._canvas); + } + + map.on('moveend', this._reset, this); + + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on('zoomanim', this._animateZoom, this); + } + + this._reset(); + }, + + onRemove: function (map) { + if (this.options.pane) { + this.getPane().removeChild(this._canvas); + }else{ + map.getPanes().overlayPane.removeChild(this._canvas); + } + + map.off('moveend', this._reset, this); + + if (map.options.zoomAnimation) { + map.off('zoomanim', this._animateZoom, this); + } + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + _initCanvas: function () { + var canvas = this._canvas = L.DomUtil.create('canvas', 'leaflet-heatmap-layer leaflet-layer'); + + var originProp = L.DomUtil.testProp(['transformOrigin', 'WebkitTransformOrigin', 'msTransformOrigin']); + canvas.style[originProp] = '50% 50%'; + + var size = this._map.getSize(); + canvas.width = size.x; + canvas.height = size.y; + + var animated = this._map.options.zoomAnimation && L.Browser.any3d; + L.DomUtil.addClass(canvas, 'leaflet-zoom-' + (animated ? 'animated' : 'hide')); + + this._heat = simpleheat(canvas); + this._updateOptions(); + }, + + _updateOptions: function () { + this._heat.radius(this.options.radius || this._heat.defaultRadius, this.options.blur); + + if (this.options.gradient) { + this._heat.gradient(this.options.gradient); + } + if (this.options.max) { + this._heat.max(this.options.max); + } + }, + + _reset: function () { + var topLeft = this._map.containerPointToLayerPoint([0, 0]); + L.DomUtil.setPosition(this._canvas, topLeft); + + var size = this._map.getSize(); + + if (this._heat._width !== size.x) { + this._canvas.width = this._heat._width = size.x; + } + if (this._heat._height !== size.y) { + this._canvas.height = this._heat._height = size.y; + } + + this._redraw(); + }, + + _redraw: function () { + if (!this._map) { + return; + } + var data = [], + r = this._heat._r, + size = this._map.getSize(), + bounds = new L.Bounds( + L.point([-r, -r]), + size.add([r, r])), + + max = this.options.max === undefined ? 1 : this.options.max, + maxZoom = this.options.maxZoom === undefined ? this._map.getMaxZoom() : this.options.maxZoom, + v = 1 / Math.pow(2, Math.max(0, Math.min(maxZoom - this._map.getZoom(), 12))), + cellSize = r / 2, + grid = [], + panePos = this._map._getMapPanePos(), + offsetX = panePos.x % cellSize, + offsetY = panePos.y % cellSize, + i, len, p, cell, x, y, j, len2, k; + + // console.time('process'); + for (i = 0, len = this._latlngs.length; i < len; i++) { + p = this._map.latLngToContainerPoint(this._latlngs[i]); + if (bounds.contains(p)) { + x = Math.floor((p.x - offsetX) / cellSize) + 2; + y = Math.floor((p.y - offsetY) / cellSize) + 2; + + var alt = + this._latlngs[i].alt !== undefined ? this._latlngs[i].alt : + this._latlngs[i][2] !== undefined ? +this._latlngs[i][2] : 1; + k = alt * v; + + grid[y] = grid[y] || []; + cell = grid[y][x]; + + if (!cell) { + grid[y][x] = [p.x, p.y, k]; + + } else { + cell[0] = (cell[0] * cell[2] + p.x * k) / (cell[2] + k); // x + cell[1] = (cell[1] * cell[2] + p.y * k) / (cell[2] + k); // y + cell[2] += k; // cumulated intensity value + } + } + } + + for (i = 0, len = grid.length; i < len; i++) { + if (grid[i]) { + for (j = 0, len2 = grid[i].length; j < len2; j++) { + cell = grid[i][j]; + if (cell) { + data.push([ + Math.round(cell[0]), + Math.round(cell[1]), + Math.min(cell[2], max) + ]); + } + } + } + } + // console.timeEnd('process'); + + // console.time('draw ' + data.length); + this._heat.data(data).draw(this.options.minOpacity); + // console.timeEnd('draw ' + data.length); + + this._frame = null; + }, + + _animateZoom: function (e) { + var scale = this._map.getZoomScale(e.zoom), + offset = this._map._getCenterOffset(e.center)._multiplyBy(-scale).subtract(this._map._getMapPanePos()); + + if (L.DomUtil.setTransform) { + L.DomUtil.setTransform(this._canvas, offset, scale); + + } else { + this._canvas.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ')'; + } + } +}); + +L.heatLayer = function (latlngs, options) { + return new L.HeatLayer(latlngs, options); +}; diff --git a/src/main/resources/leafletmap/leafletmap.html b/src/main/resources/leafletmap/leafletmap.html index 512c9bc..2835c41 100644 --- a/src/main/resources/leafletmap/leafletmap.html +++ b/src/main/resources/leafletmap/leafletmap.html @@ -28,6 +28,7 @@ +