UNPKG

@senx/warpview

Version:
720 lines 143 kB
/* * Copyright 2021 SenX S.A.S. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import { Component, ElementRef, EventEmitter, Input, Output, Renderer2, ViewChild, ViewEncapsulation } from '@angular/core'; import { Param } from '../../model/param'; import { Logger } from '../../utils/logger'; import Leaflet from 'leaflet'; import 'leaflet.heat'; import 'leaflet.markercluster'; import { ColorLib } from '../../utils/color-lib'; import { ChartLib } from '../../utils/chart-lib'; import { DataModel } from '../../model/dataModel'; import { MapLib } from '../../utils/map-lib'; import { GTSLib } from '../../utils/gts.lib'; import moment from 'moment-timezone'; import deepEqual from 'deep-equal'; import { SizeService } from '../../services/resize.service'; import { Timsort } from '../../utils/timsort'; import { antPath } from 'leaflet-ant-path'; import * as i0 from "@angular/core"; import * as i1 from "../../services/resize.service"; import * as i2 from "../warp-view-slider/warp-view-slider.component"; import * as i3 from "../warp-view-range-slider/warp-view-range-slider.component"; import * as i4 from "./warp-view-heatmap-sliders/warp-view-heatmap-sliders.component"; import * as i5 from "angular-resize-event"; import * as i6 from "@angular/common"; import * as i7 from "@angular/forms"; /** * */ export class WarpViewMapComponent { constructor(el, sizeService, renderer) { this.el = el; this.sizeService = sizeService; this.renderer = renderer; this.heatData = []; this.responsive = false; this.width = ChartLib.DEFAULT_WIDTH; this.height = ChartLib.DEFAULT_HEIGHT; // tslint:disable-next-line:no-output-native this.change = new EventEmitter(); this.chartDraw = new EventEmitter(); this.divider = 1; this._options = new Param(); this._firstDraw = true; this._debug = false; this.defOptions = ChartLib.mergeDeep(new Param(), { map: { heatControls: false, tiles: [], dotsLimit: 1000, animate: false }, timeMode: 'date', showRangeSelector: false, gridLineColor: '#8e8e8e', showDots: false, timeZone: 'UTC', timeUnit: 'us', bounds: {} }); this.pointslayer = []; this.annotationsMarkers = []; this.positionArraysMarkers = []; this._iconAnchor = [20, 38]; this._popupAnchor = [0, -50]; this.pathData = []; this.positionData = []; this.geoJson = []; this.firstDraw = true; this.finalHeight = 0; // Layers this.pathDataLayer = Leaflet.featureGroup(); this.positionDataLayer = Leaflet.featureGroup(); this.tileLayerGroup = Leaflet.featureGroup(); this.geoJsonLayer = Leaflet.featureGroup(); this.displayInProgress = false; this.LOG = new Logger(WarpViewMapComponent, this.debug); this.LOG.debug(['constructor'], this.debug); this.sizeService.sizeChanged$.subscribe(() => { if (this._map) { this.resizeMe(); } }); } set debug(debug) { this._debug = debug; this.LOG.setDebug(debug); } get debug() { return this._debug; } set options(options) { this.LOG.debug(['onOptions'], options); if (!deepEqual(this._options, options, { strict: true })) { const reZoom = options.map.startZoom !== this._options.map.startZoom || options.map.startLat !== this._options.map.startLat || options.map.startLong !== this._options.map.startLong; this._options = { ...options }; this.currentLat = this.currentLat || this._options.map.startLat || 0; this.currentLong = this.currentLong || this._options.map.startLong || 0; this.divider = GTSLib.getDivider(this._options.timeUnit); this.drawMap(reZoom); } } set data(data) { this.LOG.debug(['onData'], data); this.pointslayer = []; this.annotationsMarkers = []; this.positionArraysMarkers = []; if (!!data) { this._data = data; this.drawMap(true); } } get data() { return this._data; } set hiddenData(hiddenData) { this._hiddenData = hiddenData; this.drawMap(false); } get hiddenData() { return this._hiddenData; } ngOnInit() { this._options = ChartLib.mergeDeep(this.defOptions, this._options); } resizeMe() { this.LOG.debug(['resizeMe'], this.wrapper.nativeElement.parentElement.getBoundingClientRect()); let height = this.wrapper.nativeElement.parentElement.getBoundingClientRect().height; const width = this.wrapper.nativeElement.parentElement.getBoundingClientRect().width; if (this._options.map.showTimeSlider && this.timeSlider && this.timeSlider.nativeElement) { height -= this.timeSlider.nativeElement.getBoundingClientRect().height; } if (this._options.map.showTimeRange && this.timeRangeSlider && this.timeRangeSlider.nativeElement) { height -= this.timeRangeSlider.nativeElement.getBoundingClientRect().height; } this.finalHeight = height; this.renderer.setStyle(this.mapDiv.nativeElement, 'width', 'calc(' + width + 'px - ' + getComputedStyle(this.wrapper.nativeElement).getPropertyValue('--warp-view-map-margin').trim() + ' - ' + getComputedStyle(this.wrapper.nativeElement).getPropertyValue('--warp-view-map-margin').trim() + ')'); this.renderer.setStyle(this.mapDiv.nativeElement, 'height', 'calc(' + height + 'px - ' + getComputedStyle(this.wrapper.nativeElement).getPropertyValue('--warp-view-map-margin').trim() + ' - ' + getComputedStyle(this.wrapper.nativeElement).getPropertyValue('--warp-view-map-margin').trim() + ')'); this.width = width; this.height = height; if (!!this._map) { setTimeout(() => this._map.invalidateSize()); } } heatRadiusDidChange(event) { this._heatLayer.setOptions({ radius: event.detail.valueAsNumber }); this.LOG.debug(['heatRadiusDidChange'], event.detail.valueAsNumber); } heatBlurDidChange(event) { this._heatLayer.setOptions({ blur: event.detail.valueAsNumber }); this.LOG.debug(['heatBlurDidChange'], event.detail.valueAsNumber); } heatOpacityDidChange(event) { const minOpacity = event.detail.valueAsNumber / 100; this._heatLayer.setOptions({ minOpacity }); this.LOG.debug(['heatOpacityDidChange'], event.detail.valueAsNumber); } drawMap(reZoom) { this.LOG.debug(['drawMap'], this._data); this._options = ChartLib.mergeDeep(this.defOptions, this._options); this.timeStart = this._options.map.timeStart; moment.tz.setDefault(this._options.timeZone); let gts = this._data; if (!gts) { return; } if (typeof gts === 'string') { try { gts = JSON.parse(gts); } catch (error) { return; } } if (GTSLib.isArray(gts) && gts[0] && (gts[0] instanceof DataModel || gts[0].hasOwnProperty('data'))) { gts = gts[0]; } if (!!this._map) { this._map.invalidateSize(true); } let dataList; let params; this.LOG.debug(['drawMap', 'this._options'], { ...this._options }); if (gts.data) { dataList = gts.data; this._options = ChartLib.mergeDeep(this._options, gts.globalParams || {}); this.timeSpan = this.timeSpan || this._options.map.timeSpan; params = gts.params; } else { dataList = gts; params = []; } this.divider = GTSLib.getDivider(this._options.timeUnit); this.LOG.debug(['drawMap'], dataList, this._options, gts.globalParams); const flattenGTS = GTSLib.flatDeep(dataList); const size = flattenGTS.length; for (let i = 0; i < size; i++) { const item = flattenGTS[i]; if (GTSLib.isGts(item)) { Timsort.sort(item.v, (a, b) => a[0] - b[0]); item.i = i; i++; } } this.LOG.debug(['GTSLib.flatDeep(dataList)'], flattenGTS); this.displayMap({ gts: flattenGTS, params }, reZoom); } icon(color, marker = '') { const c = `${color.slice(1)}`; const m = marker !== '' ? marker : 'circle'; return Leaflet.icon({ // tslint:disable-next-line:max-line-length iconUrl: `https://cdn.mapmarker.io/api/v1/font-awesome/v5/pin?icon=fa-${m}&iconSize=17&size=40&hoffset=${m === 'circle' ? 0 : -1}&voffset=-4&color=fff&background=${c}`, iconAnchor: this._iconAnchor, popupAnchor: this._popupAnchor }); } patchMapTileGapBug() { // Workaround for 1px lines appearing in some browsers due to fractional transforms // and resulting anti-aliasing. adapted from @cmulders' solution: // https://github.com/Leaflet/Leaflet/issues/3575#issuecomment-150544739 // @ts-ignore const originalInitTile = Leaflet.GridLayer.prototype._initTile; if (originalInitTile.isPatched) { return; } Leaflet.GridLayer.include({ _initTile(tile) { originalInitTile.call(this, tile); const tileSize = this.getTileSize(); tile.style.width = tileSize.x + 1.5 + 'px'; tile.style.height = tileSize.y + 1 + 'px'; } }); // @ts-ignore Leaflet.GridLayer.prototype._initTile.isPatched = true; } displayMap(data, reDraw = false) { if (this.displayInProgress) { return; } this.displayInProgress = true; this.pointslayer = []; this.LOG.debug(['displayMap'], data, this._options, this._hiddenData || []); if (!this.lowerTimeBound) { this.lowerTimeBound = this._options.map.timeSliderMin / this.divider; this.upperTimeBound = this._options.map.timeSliderMax / this.divider; } let height = this.height || ChartLib.DEFAULT_HEIGHT; const width = this.width || ChartLib.DEFAULT_WIDTH; if (this.responsive && this.finalHeight === 0) { this.resizeMe(); } else { if (this._options.map.showTimeSlider && this.timeSlider && this.timeSlider.nativeElement) { height -= this.timeSlider.nativeElement.getBoundingClientRect().height; } if (this._options.map.showTimeRange && this.timeRangeSlider && this.timeRangeSlider.nativeElement) { height -= this.timeRangeSlider.nativeElement.getBoundingClientRect().height; } } this.width = width; this.height = height; if (data.gts.length === 0) { this.displayInProgress = false; return; } this.pathData = MapLib.toLeafletMapPaths(data, this._hiddenData || [], this._options.scheme) || []; this.LOG.debug(['displayMap'], 'this.pathData', this.pathData); this.positionData = MapLib.toLeafletMapPositionArray(data, this._hiddenData || [], this._options.scheme) || []; this.LOG.debug(['displayMap'], 'this.positionData', this.positionData); this.geoJson = MapLib.toGeoJSON(data); this.LOG.debug(['displayMap'], 'this.geoJson', this.geoJson); this.LOG.debug(['displayMap'], 'mapType', this._options.map.mapType); if (this._options.map.mapType !== 'NONE') { const map = MapLib.mapTypes[this._options.map.mapType || 'DEFAULT'] || MapLib.mapTypes['DEFAULT']; this.LOG.debug(['displayMap'], 'map', map); const mapOpts = { maxNativeZoom: this._options.map.maxNativeZoom || 19, maxZoom: this._options.map.maxZoom || 40 }; if (map.attribution) { mapOpts.attribution = map.attribution; } if (map.subdomains) { mapOpts.subdomains = map.subdomains; } this.tilesLayer = Leaflet.tileLayer(map.link, mapOpts); } if (!!this._map) { this.LOG.debug(['displayMap'], 'map exists'); this.pathDataLayer.clearLayers(); this.positionDataLayer.clearLayers(); this.geoJsonLayer.clearLayers(); this.tileLayerGroup.clearLayers(); this.tilesLayer.addTo(this.tileLayerGroup); } else { this.LOG.debug(['displayMap'], 'build map'); this._map = Leaflet.map(this.mapDiv.nativeElement, { preferCanvas: true, layers: [this.tileLayerGroup, this.geoJsonLayer, this.pathDataLayer, this.positionDataLayer], zoomAnimation: true, maxZoom: this._options.map.maxZoom || 19 }); this.geoJsonLayer.bringToBack(); if (this.tilesLayer) { this.tilesLayer.bringToBack(); // TODO: tester this.tilesLayer.addTo(this.tileLayerGroup); } this._map.on('load', () => this.LOG.debug(['displayMap', 'load'], this._map.getCenter().lng, this.currentLong, this._map.getZoom())); this._map.on('zoomend', () => { this.LOG.debug(['displayMap'], 'zoomend', this.firstDraw, this._map.getZoom()); if (!this.firstDraw) { this.currentZoom = this._map.getZoom(); } }); this._map.on('moveend', () => { this.LOG.debug(['displayMap'], 'moveend', this.firstDraw, this._map.getCenter()); if (!this.firstDraw) { this.currentLat = this._map.getCenter().lat; this.currentLong = this._map.getCenter().lng; } }); if (!this._options.map.hideScale) { Leaflet.control.scale().addTo(this._map); } } this.LOG.debug(['displayMap'], 'build map', this.tilesLayer); // For each path const pathDataSize = (this.pathData || []).length; for (let i = 0; i < pathDataSize; i++) { const path = this.pathData[i]; if (!!path) { this.updateGtsPath(path); } } // For each position const positionsSize = (this.positionData || []).length; for (let i = 0; i < positionsSize; i++) { this.updatePositionArray(this.positionData[i]); } (this._options.map.tiles || []).forEach((t) => { this.LOG.debug(['displayMap'], t); const tile = { subdomains: 'abcd', maxZoom: this._options.map.maxZoom || 19, maxNativeZoom: this._options.map.maxNativeZoom || 19 }; if (typeof t === 'string') { tile.url = t; } else if (typeof t === 'object') { tile.url = t.url; tile.maxZoom = this._options.map.maxZoom || 19; tile.maxNativeZoom = t.maxNativeZoom || this._options.map.maxNativeZoom || 19; } if (!!this._options.map.showTimeRange) { this.tileLayerGroup.addLayer(Leaflet.tileLayer(tile.url .replace('{start}', moment(this.timeStart).toISOString()) .replace('{end}', moment(this.timeEnd).toISOString()), { subdomains: tile.subdomains, maxNativeZoom: tile.maxNativeZoom || 19, maxZoom: this._options.map.maxZoom || 19 })); } else { this.tileLayerGroup.addLayer(Leaflet.tileLayer(tile.url, { subdomains: tile.subdomains, maxNativeZoom: tile.maxNativeZoom || 19, maxZoom: this._options.map.maxZoom || 19 })); } }); this.LOG.debug(['displayMap', 'geoJson'], this.geoJson); const size = (this.geoJson || []).length; for (let i = 0; i < size; i++) { const m = this.geoJson[i]; const color = ColorLib.getColor(i, this._options.scheme); const opts = { style: () => ({ color: (data.params && data.params[i]) ? data.params[i].color || color : color, fillColor: (data.params && data.params[i]) ? ColorLib.transparentize(data.params[i].fillColor || color) : ColorLib.transparentize(color), }) }; if (m.geometry.type === 'Point') { opts.pointToLayer = (geoJsonPoint, latlng) => Leaflet.marker(latlng, { icon: this.icon(color, (data.params && data.params[i]) ? data.params[i].marker : 'circle'), opacity: 1, }); } let display = ''; const geoShape = Leaflet.geoJSON(m, opts); if (m.properties) { Object.keys(m.properties).forEach(k => display += `<b>${k}</b>: ${m.properties[k]}<br />`); geoShape.bindPopup(display); } geoShape.addTo(this.geoJsonLayer); } setTimeout(() => { if (this.pathData.length > 0 || this.positionData.length > 0 || this.geoJson.length > 0) { // Fit map to curves const group = Leaflet.featureGroup([this.geoJsonLayer, this.positionDataLayer, this.pathDataLayer]); this.LOG.debug(['displayMap', 'setView'], 'fitBounds', group.getBounds()); this.LOG.debug(['displayMap', 'setView'], { lat: this.currentLat, lng: this.currentLong }, { lat: this._options.map.startLat, lng: this._options.map.startLong }); this.bounds = group.getBounds(); this.currentLat = this.currentLat || this._options.map.startLat || 0; this.currentLong = this.currentLong || this._options.map.startLong || 0; this.currentZoom = this.currentZoom || this._options.map.startZoom; if (!!this.bounds && this.bounds.isValid()) { if (!!this.currentLat && !!this.currentLong) { this.LOG.debug(['displayMap', 'setView'], 'fitBounds', 'already have bounds'); this._map.setView({ lat: this.currentLat || this._options.map.startLat || 0, lng: this.currentLong || this._options.map.startLong || 0 }, this.currentZoom || this._options.map.startZoom || 10, { animate: false, duration: 0 }); } else { this.LOG.debug(['displayMap', 'setView'], 'fitBounds', 'this.bounds', this.bounds, this.currentLat, this.currentLat && !!this.currentLong); this._map.fitBounds(this.bounds, { padding: [1, 1], animate: false, duration: 0 }); this.currentLat = this._map.getCenter().lat; this.currentLong = this._map.getCenter().lng; this.currentZoom = this._map.getZoom(); } } else { this.LOG.debug(['displayMap', 'setView'], 'invalid bounds', { lat: this.currentLat, lng: this.currentLong }); this._map.setView({ lat: this.currentLat || this._options.map.startLat || 0, lng: this.currentLong || this._options.map.startLong || 0 }, this.currentZoom || this._options.map.startZoom || 10, { animate: false, duration: 500 }); } this.displayInProgress = false; } else { this.LOG.debug(['displayMap', 'lost'], 'lost', this.currentZoom, this._options.map.startZoom); this._map.setView([ this.currentLat || this._options.map.startLat || 0, this.currentLong || this._options.map.startLong || 0 ], this.currentZoom || this._options.map.startZoom || 2, { animate: false, duration: 0 }); this.displayInProgress = false; } if (this.heatData && this.heatData.length > 0) { this._heatLayer = Leaflet.heatLayer(this.heatData, { radius: this._options.map.heatRadius, blur: this._options.map.heatBlur, minOpacity: this._options.map.heatOpacity }); this._heatLayer.addTo(this._map); } this.firstDraw = false; this.resizeMe(); this.patchMapTileGapBug(); this.chartDraw.emit(true); }, 10); } getGTSDots(gts) { const dots = []; let icon; let size; switch (gts.render) { case 'marker': icon = this.icon(gts.color, gts.marker); size = (gts.path || []).length; for (let i = 0; i < size; i++) { const g = gts.path[i]; const marker = Leaflet.marker(g, { icon, opacity: 1 }); this.addPopup(gts, g.val, g.ts, marker); dots.push(marker); } break; case 'weightedDots': size = (gts.path || []).length; for (let i = 0; i < size; i++) { const p = gts.path[i]; if ((this._hiddenData || []).filter(h => h === gts.key).length === 0) { let v = parseInt(p.val, 10); if (isNaN(v)) { v = 0; } const radius = 50 * v / ((gts.maxValue || 1) - (gts.minValue || 0)); const marker = Leaflet.circleMarker(p, { radius: radius === 0 ? 1 : radius, color: gts.borderColor || 'transparent', fillColor: gts.color, fillOpacity: 0.5, weight: 1 }); this.addPopup(gts, p.val, p.ts, marker); dots.push(marker); } } break; case 'dots': default: size = (gts.path || []).length; for (let i = 0; i < size; i++) { const g = gts.path[i]; const marker = Leaflet.circleMarker(g, { radius: gts.baseRadius || MapLib.BASE_RADIUS, color: gts.color, fillColor: gts.color, fillOpacity: 1 }); this.addPopup(gts, g.val, g.ts, marker); dots.push(marker); } break; } return dots; } updateGtsPath(gts) { const path = MapLib.pathDataToLeaflet(gts.path); const group = Leaflet.featureGroup(); if ((path || []).length > 1 && !!gts.line && gts.render === 'dots') { if (!!this._options.map.animate) { group.addLayer(antPath(path || [], { delay: 800, dashArray: [10, 100], weight: 5, color: ColorLib.transparentize(gts.color, 0.5), pulseColor: gts.color, paused: false, reverse: false, hardwareAccelerated: true, hardwareAcceleration: true })); } else { group.addLayer(Leaflet.polyline(path || [], { color: gts.color, opacity: 0.5 })); } } const dots = this.getGTSDots(gts); const size = (dots || []).length; for (let i = 0; i < size; i++) { group.addLayer(dots[i]); } this.pathDataLayer.addLayer(group); } addPopup(positionData, value, ts, marker) { if (!!positionData) { let date; if (ts && !this._options.timeMode || this._options.timeMode !== 'timestamp') { date = (GTSLib.toISOString(ts, this.divider, this._options.timeZone) || '') .replace('Z', this._options.timeZone === 'UTC' ? 'Z' : ''); } let content = ''; content = `<p>${date}</p><p><b>${positionData.key}</b>: ${value || 'na'}</p>`; Object.keys(positionData.properties || []).forEach(k => content += `<b>${k}</b>: ${positionData.properties[k]}<br />`); marker.bindPopup(content); } } updatePositionArray(positionData) { const group = Leaflet.featureGroup(); if ((this._hiddenData || []).filter(h => h === positionData.key).length === 0) { const path = MapLib.updatePositionArrayToLeaflet(positionData.positions); if ((positionData.positions || []).length > 1 && !!positionData.line) { if (!!this._options.map.animate) { group.addLayer(antPath(path || [], { delay: 800, dashArray: [10, 100], weight: 5, color: ColorLib.transparentize(positionData.color, 0.5), pulseColor: positionData.color, paused: false, reverse: false, hardwareAccelerated: true, hardwareAcceleration: true })); } else { group.addLayer(Leaflet.polyline(path || [], { color: positionData.color, opacity: 0.5 })); } } let icon; let result; let inStep; let size; this.LOG.debug(['updatePositionArray'], positionData); switch (positionData.render) { case 'marker': icon = this.icon(positionData.color, positionData.marker); size = (positionData.positions || []).length; for (let i = 0; i < size; i++) { const p = positionData.positions[i]; const marker = Leaflet.marker({ lat: p[0], lng: p[1] }, { icon, opacity: 1 }); this.addPopup(positionData, p[2], undefined, marker); group.addLayer(marker); } this.LOG.debug(['updatePositionArray', 'build marker'], icon); break; case 'coloredWeightedDots': this.LOG.debug(['updatePositionArray', 'coloredWeightedDots'], positionData); result = []; inStep = []; for (let j = 0; j < positionData.numColorSteps; j++) { result[j] = 0; inStep[j] = 0; } size = (positionData.positions || []).length; for (let i = 0; i < size; i++) { const p = positionData.positions[i]; const radius = (parseInt(p[2], 10) - (positionData.minValue || 0)) * 50 / (positionData.maxValue || 50); this.LOG.debug(['updatePositionArray', 'coloredWeightedDots', 'radius'], positionData.baseRadius * p[4]); const marker = Leaflet.circleMarker({ lat: p[0], lng: p[1] }, { radius, color: positionData.borderColor || positionData.color, fillColor: ColorLib.rgb2hex(positionData.colorGradient[p[5]].r, positionData.colorGradient[p[5]].g, positionData.colorGradient[p[5]].b), fillOpacity: 0.3, }); this.addPopup(positionData, p[2], undefined, marker); group.addLayer(marker); } break; case 'weightedDots': size = (positionData.positions || []).length; for (let i = 0; i < size; i++) { const p = positionData.positions[i]; const radius = (parseInt(p[2], 10) - (positionData.minValue || 0)) * 50 / (positionData.maxValue || 50); const marker = Leaflet.circleMarker({ lat: p[0], lng: p[1] }, { radius, color: positionData.borderColor || positionData.color, fillColor: positionData.color, weight: 2, fillOpacity: 0.3, }); this.addPopup(positionData, p[2], undefined, marker); group.addLayer(marker); } break; case 'dots': default: size = (positionData.positions || []).length; for (let i = 0; i < size; i++) { const p = positionData.positions[i]; const marker = Leaflet.circleMarker({ lat: p[0], lng: p[1] }, { radius: positionData.baseRadius || MapLib.BASE_RADIUS, color: positionData.borderColor || positionData.color, fillColor: positionData.color, weight: 2, fillOpacity: 0.7, }); this.addPopup(positionData, p[2] || 'na', undefined, marker); group.addLayer(marker); } break; } } this.positionDataLayer.addLayer(group); } resize() { return new Promise(resolve => { this.resizeMe(); resolve(true); }); } onRangeSliderChange(event) { this.LOG.debug(['onRangeSliderChange'], event); this.timeStart = event.value || moment().valueOf(); this.timeEnd = event.highValue || moment().valueOf(); this.drawMap(true); } onRangeSliderWindowChange(event) { this.LOG.debug(['onRangeSliderWindowChange'], event); if (this.lowerTimeBound !== event.min || this.upperTimeBound !== event.max) { this.lowerTimeBound = event.min; this.upperTimeBound = event.max; } } onSliderChange(event) { this.LOG.debug(['onSliderChange'], event, moment(event.value).toISOString()); this._firstDraw = false; if (this.timeEnd !== event.value) { this.timeSpan = this.timeSpan || this._options.map.timeSpan; this.timeEnd = event.value || moment().valueOf(); this.timeStart = (event.value || moment().valueOf()) - this.timeSpan / this.divider; this.LOG.debug(['onSliderChange'], moment(this.timeStart).toISOString(), moment(this.timeEnd).toISOString()); this.change.emit(this.timeStart); this.drawMap(true); } } updateTimeSpan(event) { this.LOG.debug(['updateTimeSpan'], event.target.value); if (this.timeSpan !== event.target.value) { this.timeSpan = event.target.value; this.timeStart = (this.timeEnd || moment().valueOf()) - this.timeSpan / this.divider; this.drawMap(true); } } } WarpViewMapComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: WarpViewMapComponent, deps: [{ token: i0.ElementRef }, { token: i1.SizeService }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); WarpViewMapComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.1", type: WarpViewMapComponent, selector: "warpview-map", inputs: { heatData: "heatData", responsive: "responsive", width: "width", height: "height", debug: "debug", options: "options", data: "data", hiddenData: "hiddenData" }, outputs: { change: "change", chartDraw: "chartDraw" }, viewQueries: [{ propertyName: "mapDiv", first: true, predicate: ["mapDiv"], descendants: true, static: true }, { propertyName: "wrapper", first: true, predicate: ["wrapper"], descendants: true, static: true }, { propertyName: "timeSlider", first: true, predicate: ["timeSlider"], descendants: true }, { propertyName: "timeRangeSlider", first: true, predicate: ["timeRangeSlider"], descendants: true }], ngImport: i0, template: "<!--\n ~ Copyright 2021 SenX S.A.S.\n ~\n ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ http://www.apache.org/licenses/LICENSE-2.0\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n ~\n -->\n\n<div class=\"wrapper\" #wrapper (resized)=\"resizeMe()\">\n <div class=\"map-container\">\n <div #mapDiv></div>\n <div *ngIf=\"_options.map.showTimeSlider && !_options.map.showTimeRange\" #timeSlider>\n <warpview-slider\n [min]=\"_options.map.timeSliderMin / divider\" [max]=\"_options.map.timeSliderMax / divider\"\n [value]=\"minTimeValue / divider\"\n [step]=\"_options.map.timeSliderStep\" [mode]=\"_options.map.timeSliderMode\"\n (change)=\"onSliderChange($event)\"\n [debug]=\"debug\"\n ></warpview-slider>\n </div>\n <div *ngIf=\"_options.map.showTimeSlider && _options.map.showTimeRange\" #timeRangeSlider>\n <!-- <warpview-range-slider *ngIf=\"!_options.map.timeSpan && lowerTimeBound\"\n [min]=\"lowerTimeBound\" [max]=\"upperTimeBound\"\n [minValue]=\"minTimeValue / divider\"\n [maxValue]=\"maxTimeValue / divider\"\n [step]=\"_options.map.timeSliderStep\"\n [mode]=\"_options.map.timeSliderMode\"\n [debug]=\"_debug\"\n (change)=\"onRangeSliderChange($event)\"\n ></warpview-range-slider>-->\n\n <warpview-range-slider\n [min]=\"(_options.map.timeSliderMin / divider)\" [max]=\"(_options.map.timeSliderMax / divider)\"\n [minValue]=\"minTimeValue / divider\"\n [maxValue]=\"maxTimeValue / divider\"\n [mode]=\"_options.map.timeSliderMode\"\n [debug]=\"debug\"\n (change)=\"onRangeSliderWindowChange($event)\"\n ></warpview-range-slider>\n <warpview-slider *ngIf=\"_options.map.timeSpan && lowerTimeBound\"\n [min]=\"lowerTimeBound\" [max]=\"upperTimeBound\"\n [step]=\"(this.timeSpan || this._options.map.timeSpan) / divider\"\n [mode]=\"_options.map.timeSliderMode\"\n [debug]=\"debug\"\n (change)=\"onSliderChange($event)\"\n ></warpview-slider>\n <div *ngIf=\"_options.map?.timeSpan\">\n <label for=\"timeSpan\">Timespan: </label>\n <select id=\"timeSpan\" (change)=\"updateTimeSpan($event)\">\n <option *ngFor=\"let ts of _options.map.timeSpanList\" [value]=\"ts.value\">{{ts.label}}</option>\n </select>\n </div>\n </div>\n <warpview-heatmap-sliders\n *ngIf=\"_options.map.heatControls\"\n (heatRadiusDidChange)=\"heatRadiusDidChange($event)\"\n (heatBlurDidChange)=\"heatBlurDidChange($event)\"\n (heatOpacityDidChange)=\"heatOpacityDidChange($event)\"\n ></warpview-heatmap-sliders>\n </div>\n\n</div>\n", styles: ["/*!\n * Copyright 2021 SenX S.A.S.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */:root{--warp-view-chart-width: 100%;--warp-view-chart-height: 100%;--warp-view-datagrid-cell-padding: 5px;--warp-view-map-margin: 0;--warp-view-switch-height: 30px;--warp-view-switch-width: 100px;--warp-view-switch-radius: 18px;--warp-view-plot-chart-height: 100%;--warp-view-slider-pointer-size: 65px;--warp-view-resize-handle-height: 10px;--warp-view-tile-width: 100%;--warp-view-tile-height: 100%;--warp-view-font-color: #000000;--warp-view-bar-color: #dc3545;--warp-view-datagrid-odd-bg-color: #ffffff;--warp-view-datagrid-odd-color: #404040;--warp-view-datagrid-even-bg-color: #c0c0c0;--warp-view-datagrid-even-color: #000000;--warp-view-pagination-border-color: #c0c0c0;--warp-view-pagination-bg-color: #ffffff;--warp-view-pagination-active-bg-color: #4CAF50;--warp-view-pagination-active-color: #ffffff;--warp-view-pagination-active-border-color: #4CAF50;--warp-view-pagination-hover-bg-color: #c0c0c0;--warp-view-pagination-hover-color: #000000;--warp-view-pagination-hover-border-color: #c0c0c0;--warp-view-pagination-disabled-color: #c0c0c0;--warp-view-switch-inset-color: #c0c0c0;--warp-view-switch-inset-checked-color: #00cd00;--warp-view-switch-handle-color: radial-gradient(#ffffff 15%, #c0c0c0 100%);--warp-view-switch-handle-checked-color: radial-gradient(#ffffff 15%, #00cd00 100%);--warp-view-resize-handle-color: #c0c0c0;--warp-view-chart-legend-bg: #ffffff;--warp-view-chart-legend-color: #404040;--gts-classname-font-color: #004eff;--gts-labelname-font-color: #19A979;--gts-attrname-font-color: #ED4A7B;--gts-separator-font-color: #a0a0a0;--gts-labelvalue-font-color: #000000;--gts-attrvalue-font-color: #000000;--gts-stack-font-color: #000000;--gts-tree-expanded-icon: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA7klEQVQ4T82TMW7CQBBF/0g+QOpINEkVCmpaLoBm5COk5QYoaeAY3MDSei2LGu4QKakiBA1tCpTK8kS2sLVe2xSh8XSrnf9m/s4s4c6gO/UYGEBEXlT1bK396bFGIjIJguA7iqJLkVNbYOZXItoQ0QHAzBhz9CCFeAVgCeAjy7Jpmqa/NUBEEgDzktqGuOKKO47j+KsGhGH4lOf5HsDIg5ycyqVYVd+steuGheLAzM9EtPMgW1VdVGWJ6N0YU1gpozVGH+K+gy/uBHR1crXUqNzbQXXhduJ69sd7cxOZ+UFVH5Mk+exb+YGt8n9+5h8up1sReYC0WAAAAABJRU5ErkJggg==);--gts-tree-collapsed-icon: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA0UlEQVQ4T6WTUW7CQAxEPQdozxYb9Qb94Aj9gQSoVCp6lMr21doDZFCQiFCU3YDY//d2PeOFPHnwJC+zAlVdA/jp+/6YmZ+1S0qCPxF5HUAAO3fvSpKS4ENEvm6gfUS0c5JiBma2Ibm/QiQPmbmdSqohquoA7GqSxRaapmkBjBkAeHP336t0UWBmHcnb+VcR4XcJpjDJLjPHkS4tleqZubmNiDHU6gumDQDYuvvh7hpV9V9EXgaA5Ka2jbMjmNk7yZOIfEfE8eFVfuSDLda4JDsD3FNdEckTC0YAAAAASUVORK5CYII=);--warp-view-popup-bg-color: #ffffff;--warp-view-popup-border-color: rgba(0, 0, 0, .2);--warp-view-popup-header-bg-color: #c0c0c0;--warp-view-popup-title-color: #404040;--warp-view-popup-close-color: #404040;--warp-view-popup-body-bg-color: #ffffff;--warp-view-popup-body-color: #000000;--warp-view-annotationtooltip-value-font-color: #004eff;--warp-view-annotationtooltip-font-color: #404040;--warp-view-spinner-color: #ff9900;--warp-view-tooltip-bg: #ffffff;--warp-view-tooltip-color: #000000;--warp-slider-connect-color: #ff9900;--warp-slider-handle-bg-color: #ffffff;--warp-slider-handle-color: #004eff;--warp-slider-handle-shadow: inset 0 0 1px #ffffff, inset 0 1px 7px #c0c0c0, 0 3px 6px -3px #a0a0a0}.noData{width:100%;text-align:center;color:var(--warp-view-chart-legend-color);position:relative}.js-plotly-plot .plotly .cursor-ew-resize{cursor:default!important}:host,warpview-map,warp-view-map{width:100%;height:100%;min-height:100px}:host .status,warpview-map .status,warp-view-map .status{bottom:0}:host div.wrapper,warpview-map div.wrapper,warp-view-map div.wrapper{width:100%;height:100%;min-height:100px;padding:var(--warp-view-map-margin);overflow:hidden}:host div.wrapper div.map-container,:host div.wrapper div.leaflet-container,warpview-map div.wrapper div.map-container,warpview-map div.wrapper div.leaflet-container,warp-view-map div.wrapper div.map-container,warp-view-map div.wrapper div.leaflet-container{min-width:100%;min-height:100px;width:100%;height:100%;position:relative;overflow:hidden}:host div.wrapper .leaflet-container,warpview-map div.wrapper .leaflet-container,warp-view-map div.wrapper .leaflet-container{min-width:100%;min-height:100%;width:100%;height:100%;@import \"~leaflet/dist/leaflet.css\";@import \"~leaflet.markercluster/dist/MarkerCluster.css\";@import \"~leaflet.markercluster/dist/MarkerCluster.Default.css\";}\n"], components: [{ type: i2.WarpViewSliderComponent, selector: "warpview-slider", inputs: ["min", "max", "value", "step", "mode", "debug"], outputs: ["change"] }, { type: i3.WarpViewRangeSliderComponent, selector: "warpview-range-slider", inputs: ["minValue", "maxValue"] }, { type: i4.WarpViewHeatmapSlidersComponent, selector: "warpview-heatmap-sliders", inputs: ["radiusValue", "minRadiusValue", "maxRadiusValue", "blurValue", "minBlurValue", "maxBlurValue"], outputs: ["heatRadiusDidChange", "heatBlurDidChange", "heatOpacityDidChange"] }], directives: [{ type: i5.ResizedDirective, selector: "[resized]", outputs: ["resized"] }, { type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i7.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { type: i7.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }], encapsulation: i0.ViewEncapsulation.None }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.1", ngImport: i0, type: WarpViewMapComponent, decorators: [{ type: Component, args: [{ selector: 'warpview-map', encapsulation: ViewEncapsulation.None, template: "<!--\n ~ Copyright 2021 SenX S.A.S.\n ~\n ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ http://www.apache.org/licenses/LICENSE-2.0\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n ~\n -->\n\n<div class=\"wrapper\" #wrapper (resized)=\"resizeMe()\">\n <div class=\"map-container\">\n <div #mapDiv></div>\n <div *ngIf=\"_options.map.showTimeSlider && !_options.map.showTimeRange\" #timeSlider>\n <warpview-slider\n [min]=\"_options.map.timeSliderMin / divider\" [max]=\"_options.map.timeSliderMax / divider\"\n [value]=\"minTimeValue / divider\"\n [step]=\"_options.map.timeSliderStep\" [mode]=\"_options.map.timeSliderMode\"\n (change)=\"onSliderChange($event)\"\n [debug]=\"debug\"\n ></warpview-slider>\n </div>\n <div *ngIf=\"_options.map.showTimeSlider && _options.map.showTimeRange\" #timeRangeSlider>\n <!-- <warpview-range-slider *ngIf=\"!_options.map.timeSpan && lowerTimeBound\"\n [min]=\"lowerTimeBound\" [max]=\"upperTimeBound\"\n [minValue]=\"minTimeValue / divider\"\n [maxValue]=\"maxTimeValue / divider\"\n [step]=\"_options.map.timeSliderStep\"\n [mode]=\"_options.map.timeSliderMode\"\n [debug]=\"_debug\"\n (change)=\"onRangeSliderChange($event)\"\n ></warpview-range-slider>-->\n\n <warpview-range-slider\n [min]=\"(_options.map.timeSliderMin / divider)\" [max]=\"(_options.map.timeSliderMax / divider)\"\n [minValue]=\"minTimeValue / divider\"\n [maxValue]=\"maxTimeValue / divider\"\n [mode]=\"_options.map.timeSliderMode\"\n [debug]=\"debug\"\n (change)=\"onRangeSliderWindowChange($event)\"\n ></warpview-range-slider>\n <warpview-slider *ngIf=\"_options.map.timeSpan && lowerTimeBound\"\n [min]=\"lowerTimeBound\" [max]=\"upperTimeBound\"\n [step]=\"(this.timeSpan || this._options.map.timeSpan) / divider\"\n [mode]=\"_options.map.timeSliderMode\"\n [debug]=\"debug\"\n (change)=\"onSliderChange($event)\"\n ></warpview-slider>\n <div *ngIf=\"_options.map?.timeSpan\">\n <label for=\"timeSpan\">Timespan: </label>\n <select id=\"timeSpan\" (change)=\"updateTimeSpan($event)\">\n <option *ngFor=\"let ts of _options.map.timeSpanList\" [value]=\"ts.value\">{{ts.label}}</option>\n </select>\n </div>\n </div>\n <warpview-heatmap-sliders\n *ngIf=\"_options.map.heatControls\"\n (heatRadiusDidChange)=\"heatRadiusDidChange($event)\"\n (heatBlurDidChange)=\"heatBlurDidChange($event)\"\n (heatOpacityDidChange)=\"heatOpacityDidChange($event)\"\n ></warpview-heatmap-sliders>\n </div>\n\n</div>\n", styles: ["/*!\n * Copyright 2021 SenX S.A.S.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */:root{--warp-view-chart-width: 100%;--warp-view-chart-height: 100%;--warp-view-datagrid-cell-padding: 5px;--warp-view-map-margin: 0;--warp-view-switch-height: 30px;--warp-view-switch-width: 100px;--warp-view-switch-radius: 18px;--warp-view-plot-chart-height: 100%;--warp-view-slider-pointer-size: 65px;--warp-view-resize-handle-height: 10px;--warp-view-tile-width: 100%;--warp-view-tile-height: 100%;--warp-view-font-color: #000000;--warp-view-bar-color: #dc3545;--warp-view-datagrid-odd-bg-color: #ffffff;--warp-view-datagrid-odd-color: #404040;--warp-view-datagrid-even-bg-color: #c0c0c0;--warp-view-datagrid-even-color: #000000;--warp-view-pagination-border-color: #c0c0c0;--warp-view-pagination-bg-color: #ffffff;--warp-view-pagination-active-bg-color: #4CAF50;--warp-view-pagination-active-color: #ffffff;--warp-view-pagination-active-border-color: #4CAF50;--warp-view-pagination-hover-bg-color: #c0c0c0;--warp-view-pagination-hover-color: #000000;--warp-view-pagination-hover-border-color: #c0c0c0;--warp-view-pagination-disabled-color: #c0c0c0;--warp-view-switch-inset-color: #c0c0c0;--warp-view-switch-inset-checked-color: #00cd00;--warp-view-switch-handle-color: radial-gradient(#ffffff 15%, #c0c0c0 100%);--warp-view-switch-handle-checked-color: radial-gradient(#ffffff 15%, #00cd00 100%);--warp-view-resize-handle-color: #c0c0c0;--warp-view-chart-legend-bg: #ffffff;--warp-view-chart-legend-color: #404040;--gts-classname-font-color: #004eff;--gts-labelname-font-color: #19A979;--gts-attrname-font-color: #ED4A7B;--gts-separator-font-color: #a0a0a0;--gts-labelvalue-font-color: #000000;--gts-attrvalue-font-color: #000000;--gts-stack-font-color: #000000;--gts-tree-expanded-icon: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA7klEQVQ4T82TMW7CQBBF/0g+QOpINEkVCmpaLoBm5COk5QYoaeAY3MDSei2LGu4QKakiBA1tCpTK8kS2sLVe2xSh8XSrnf9m/s4s4c6gO/UYGEBEXlT1bK396bFGIjIJguA7iqJLkVNbYOZXItoQ0QHAzBhz9CCFeAVgCeAjy7Jpmqa/NUBEEgDzktqGuOKKO47j+KsGhGH4lOf5HsDIg5ycyqVYVd+steuGheLAzM9EtPMgW1VdVGWJ6N0YU1gpozVGH+K+gy/uBHR1crXUqNzbQXXhduJ69sd7cxOZ+UFVH5Mk+exb+YGt8n9+5h8up1sReYC0WAAAAABJRU5ErkJggg==);--gts-tree-collapsed-icon: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA0UlEQVQ4T6WTUW7CQAxEPQdozxYb9Qb94Aj9gQSoVCp6lMr21doDZFCQiFCU3YDY//d2PeOFPHnwJC+zAlVdA/jp+/6YmZ+1S0qCPxF5HUAAO3fvSpKS4ENEvm6gfUS0c5JiBma2Ibm/QiQPmbmdSqohquoA7GqSxRaapmkBjBkAeHP336t0UWBmHcnb+VcR4XcJpjDJLjPHkS4tleqZubmNiDHU6gumDQDYuvvh7hpV9V9EXgaA5Ka2jbMjmNk