@senx/warpview
Version:
WarpView Elements
720 lines • 143 kB
JavaScript
/*
* 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