Commit 241edc1d2b8ec51f3e678d7857539086fb017cf1

Authored by ArtemHalushko
Committed by GitHub
1 parent 2c02406e

Map/3.0 (#2664)

* add base map infrastructure

* add leaflet css

* add tencent map

* add google maps support

* added image map support

* refactor schemes

* here maps support && WIP on markers

* add simple marker suppor

* data update & polyline support

* map bouds support

* add some settings support

* add map provider select to settings

* labels support

* WIP on trip animation widget

* WIP on history control and route interpolation

* trip-animation map provider & custom markers

* comleted track marker & history controls

* add license headers

* label fix & tooltips support

* WIP on polygons

* marker dropping support

* add polygon support

* add label to trip animation

* WIP on tooltips

* lint anf typed leaflet AddMarker

* some typing and poly improvements

* add typing

* add marker creation

* update proxy

* save position fix

* add bounds padding

* update map widget bendle && bugfixes

* update marker placement widget

* add licenses

* reomove log

* fix sizes

* entity and map fixes

* Fix tile server support  form OSM and zoom level fix

* add support custom actions

* map fixes

Co-authored-by: Artem Halushko <ahalushko@thingboards.io>
Co-authored-by: Adsumus <artemtv42@gmail.com>
@@ -39,7 +39,9 @@ @@ -39,7 +39,9 @@
39 "node_modules/rc-select/assets/index.less", 39 "node_modules/rc-select/assets/index.less",
40 "node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css", 40 "node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css",
41 "node_modules/leaflet/dist/leaflet.css", 41 "node_modules/leaflet/dist/leaflet.css",
42 - "src/app/modules/home/components/widget/lib/maps/markers.scss" 42 + "src/app/modules/home/components/widget/lib/maps/markers.scss",
  43 + "node_modules/leaflet.markercluster/dist/MarkerCluster.css",
  44 + "node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css"
43 ], 45 ],
44 "stylePreprocessorOptions": { 46 "stylePreprocessorOptions": {
45 "includePaths": [ 47 "includePaths": [
@@ -1816,8 +1816,7 @@ @@ -1816,8 +1816,7 @@
1816 "@types/geojson": { 1816 "@types/geojson": {
1817 "version": "7946.0.7", 1817 "version": "7946.0.7",
1818 "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz", 1818 "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
1819 - "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==",  
1820 - "dev": true 1819 + "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ=="
1821 }, 1820 },
1822 "@types/glob": { 1821 "@types/glob": {
1823 "version": "7.1.1", 1822 "version": "7.1.1",
@@ -1881,7 +1880,6 @@ @@ -1881,7 +1880,6 @@
1881 "version": "1.5.12", 1880 "version": "1.5.12",
1882 "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.12.tgz", 1881 "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.12.tgz",
1883 "integrity": "sha512-61HRMIng+bWvnnAIqUWLBlrd/TQZc4gU+gN1JL4K47EDtwIrcMEhWgi7PdcpbG1YmpH4F0EfOimkvV82gJIl9w==", 1882 "integrity": "sha512-61HRMIng+bWvnnAIqUWLBlrd/TQZc4gU+gN1JL4K47EDtwIrcMEhWgi7PdcpbG1YmpH4F0EfOimkvV82gJIl9w==",
1884 - "dev": true,  
1885 "requires": { 1883 "requires": {
1886 "@types/geojson": "*" 1884 "@types/geojson": "*"
1887 } 1885 }
@@ -1895,6 +1893,14 @@ @@ -1895,6 +1893,14 @@
1895 "@types/leaflet": "*" 1893 "@types/leaflet": "*"
1896 } 1894 }
1897 }, 1895 },
  1896 + "@types/leaflet.markercluster": {
  1897 + "version": "1.4.2",
  1898 + "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.4.2.tgz",
  1899 + "integrity": "sha512-QQ//hevAxMH2dlRQdRre7V/1G+TbtuDtZnZF/75TNwVIgklrsQVCIcS/cvLsl7UUryfPJ6xmoYHfFzK5iGVgpg==",
  1900 + "requires": {
  1901 + "@types/leaflet": "*"
  1902 + }
  1903 + },
1898 "@types/lodash": { 1904 "@types/lodash": {
1899 "version": "4.14.150", 1905 "version": "4.14.150",
1900 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.150.tgz", 1906 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.150.tgz",
@@ -8605,7 +8611,7 @@ @@ -8605,7 +8611,7 @@
8605 "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw==" 8611 "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw=="
8606 }, 8612 },
8607 "ngx-flowchart": { 8613 "ngx-flowchart": {
8608 - "version": "git://github.com/thingsboard/ngx-flowchart.git#a4157b0eef2eb3646ef920447c7b06b39d54f87f", 8614 + "version": "git://github.com/thingsboard/ngx-flowchart.git#97a77477ca8579becf0e3a07866046b4536fe30a",
8609 "from": "git://github.com/thingsboard/ngx-flowchart.git#master", 8615 "from": "git://github.com/thingsboard/ngx-flowchart.git#master",
8610 "requires": { 8616 "requires": {
8611 "tslib": "^1.10.0" 8617 "tslib": "^1.10.0"
@@ -108,6 +108,7 @@ @@ -108,6 +108,7 @@
108 "@types/jstree": "^3.3.39", 108 "@types/jstree": "^3.3.39",
109 "@types/jszip": "^3.1.7", 109 "@types/jszip": "^3.1.7",
110 "@types/leaflet": "^1.5.12", 110 "@types/leaflet": "^1.5.12",
  111 + "@types/leaflet.markercluster": "^1.4.2",
111 "@types/leaflet-polylinedecorator": "^1.6.0", 112 "@types/leaflet-polylinedecorator": "^1.6.0",
112 "@types/lodash": "^4.14.150", 113 "@types/lodash": "^4.14.150",
113 "@types/raphael": "^2.3.0", 114 "@types/raphael": "^2.3.0",
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import _ from 'lodash'; 17 import _ from 'lodash';
18 -import { Observable, Subject, from, fromEvent, of } from 'rxjs'; 18 +import { Observable, Subject, fromEvent, of } from 'rxjs';
19 import { finalize, share, map } from 'rxjs/operators'; 19 import { finalize, share, map } from 'rxjs/operators';
20 import base64js from 'base64-js'; 20 import base64js from 'base64-js';
21 21
@@ -430,7 +430,7 @@ export function getDescendantProp(obj: any, path: string): any { @@ -430,7 +430,7 @@ export function getDescendantProp(obj: any, path: string): any {
430 430
431 export function imageLoader(imageUrl: string): Observable<HTMLImageElement> { 431 export function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
432 const image = new Image(); 432 const image = new Image();
433 - const imageLoad$ = fromEvent(image, 'load').pipe(map(event => image)); 433 + const imageLoad$ = fromEvent(image, 'load').pipe(map(() => image));
434 image.src = imageUrl; 434 image.src = imageUrl;
435 return imageLoad$; 435 return imageLoad$;
436 } 436 }
@@ -524,36 +524,49 @@ export function parseFunction(source: any, params: string[] = []): Function { @@ -524,36 +524,49 @@ export function parseFunction(source: any, params: string[] = []): Function {
524 return res; 524 return res;
525 } 525 }
526 526
527 -export function parseTemplate(template: string, data: object) { 527 +export function parseTemplate(template: string, data: object, translateFn?: (key: string) => string) {
528 let res = ''; 528 let res = '';
  529 + let variables = '';
529 try { 530 try {
530 - let variables = '';  
531 - const expressions = template  
532 - .match(/\{(.*?)\}/g) // find expressions  
533 - .map(exp => exp.replace(/{|}/g, '')) // remove brackets  
534 - .map(exp => exp.split(':'))  
535 - .map(arr => {  
536 - variables += `let ${arr[0]} = ''; `;  
537 - return arr;  
538 - })  
539 - .filter(arr => !!arr[1]) // filter expressions without format  
540 - .reduce((res, current) => {  
541 - res[current[0]] = current[1];  
542 - return res;  
543 - }, {});  
544 -  
545 - for (const key in data) {  
546 - if (!key.includes('|'))  
547 - variables += `${key} = '${expressions[key] ? padValue(data[key], +expressions[key]) : data[key]}';`; 531 + if (template.match(/<link-act/g)) {
  532 + template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>').replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
  533 + }
  534 + if (template.includes('i18n')) {
  535 + const translateRegexp = /\{i18n:(.*?)\}/;
  536 + template.match(new RegExp(translateRegexp.source, translateRegexp.flags + 'g')).forEach(match => {
  537 + template = template.replace(match, translateFn(match.match(translateRegexp)[1]));
  538 + });
  539 + }
  540 + const expressions = template.match(/\{(.*?)\}/g);
  541 + if (expressions) {
  542 + const clearMatches = template.match(/(?<=\{)(.+?)(?=(\}|\:))/g);
  543 + for (const key in data) {
  544 + if (!key.includes('|'))
  545 + variables += `let ${key} = '${clearMatches[key] ? padValue(data[key], +clearMatches[key]) : data[key]}';`;
  546 + }
  547 + template = template.replace(/\:\d+\}/g, '}');
  548 + res = safeExecute(parseFunction(variables + ' return' + '`' + template + '`'));
548 } 549 }
549 - template = template.replace(/:\d+}/g, '}');  
550 - res = safeExecute(parseFunction(variables + 'return' + '`' + template + '`')); 550 + else res = template;
551 } 551 }
552 catch (ex) { 552 catch (ex) {
  553 + console.log(ex, variables, template)
553 } 554 }
554 return res; 555 return res;
555 } 556 }
556 557
  558 +export let parseWithTranslation = {
  559 + translate(): string {
  560 + throw console.error('Translate not assigned');
  561 + },
  562 + parseTemplate(template: string, data: object, forceTranslate = false): string {
  563 + return parseTemplate(forceTranslate ? this.translate(template) : template, data, this?.translate);
  564 + },
  565 + setTranslate(translateFn: (key: string, defaultTranslation?: string) => string) {
  566 + this.translate = translateFn;
  567 + }
  568 +}
  569 +
557 export function padValue(val: any, dec: number): string { 570 export function padValue(val: any, dec: number): string {
558 let strVal; 571 let strVal;
559 let n; 572 let n;
@@ -14,12 +14,12 @@ @@ -14,12 +14,12 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import L, { LatLngTuple } from 'leaflet'; 17 +import L, { LatLngTuple, LatLngBounds, Point } from 'leaflet';
18 18
19 import 'leaflet-providers'; 19 import 'leaflet-providers';
20 -import 'leaflet.markercluster/dist/MarkerCluster.css'  
21 -import 'leaflet.markercluster/dist/MarkerCluster.Default.css'  
22 -import 'leaflet.markercluster/dist/leaflet.markercluster' 20 +import 'leaflet.markercluster/dist/MarkerCluster.css';
  21 +import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
  22 +import LM from 'leaflet.markercluster/dist/leaflet.markercluster';
23 23
24 import { MapSettings, MarkerSettings, FormattedData, UnitedMapSettings, PolygonSettings, PolylineSettings } from './map-models'; 24 import { MapSettings, MarkerSettings, FormattedData, UnitedMapSettings, PolygonSettings, PolylineSettings } from './map-models';
25 import { Marker } from './markers'; 25 import { Marker } from './markers';
@@ -34,7 +34,7 @@ export default abstract class LeafletMap { @@ -34,7 +34,7 @@ export default abstract class LeafletMap {
34 markers: Map<string, Marker> = new Map(); 34 markers: Map<string, Marker> = new Map();
35 polylines: Map<string, Polyline> = new Map(); 35 polylines: Map<string, Polyline> = new Map();
36 polygons: Map<string, Polygon> = new Map(); 36 polygons: Map<string, Polygon> = new Map();
37 - dragMode = true; 37 + dragMode = false;
38 map: L.Map; 38 map: L.Map;
39 map$: BehaviorSubject<L.Map> = new BehaviorSubject(null); 39 map$: BehaviorSubject<L.Map> = new BehaviorSubject(null);
40 ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map)); 40 ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map));
@@ -43,6 +43,7 @@ export default abstract class LeafletMap { @@ -43,6 +43,7 @@ export default abstract class LeafletMap {
43 bounds: L.LatLngBounds; 43 bounds: L.LatLngBounds;
44 newMarker: L.Marker; 44 newMarker: L.Marker;
45 datasources: FormattedData[]; 45 datasources: FormattedData[];
  46 + markersCluster: LM.markerClusterGroup;
46 47
47 constructor(public $container: HTMLElement, options: UnitedMapSettings) { 48 constructor(public $container: HTMLElement, options: UnitedMapSettings) {
48 this.options = options; 49 this.options = options;
@@ -50,13 +51,38 @@ export default abstract class LeafletMap { @@ -50,13 +51,38 @@ export default abstract class LeafletMap {
50 51
51 public initSettings(options: MapSettings) { 52 public initSettings(options: MapSettings) {
52 const { initCallback, 53 const { initCallback,
53 - disableScrollZooming, }: MapSettings = options; 54 + disableScrollZooming,
  55 + useClusterMarkers,
  56 + zoomOnClick,
  57 + showCoverageOnHover,
  58 + removeOutsideVisibleBounds,
  59 + animate,
  60 + chunkedLoading,
  61 + maxClusterRadius,
  62 + maxZoom }: MapSettings = options;
54 if (disableScrollZooming) { 63 if (disableScrollZooming) {
55 this.map.scrollWheelZoom.disable(); 64 this.map.scrollWheelZoom.disable();
56 } 65 }
57 if (initCallback) { 66 if (initCallback) {
58 setTimeout(options.initCallback, 0); 67 setTimeout(options.initCallback, 0);
59 } 68 }
  69 + if (useClusterMarkers) {
  70 + const clusteringSettings: LM.MarkerClusterGroupOptions = {
  71 + zoomToBoundsOnClick: zoomOnClick,
  72 + showCoverageOnHover,
  73 + removeOutsideVisibleBounds,
  74 + animate,
  75 + chunkedLoading
  76 + };
  77 + if (maxClusterRadius && maxClusterRadius > 0) {
  78 + clusteringSettings.maxClusterRadius = Math.floor(maxClusterRadius);
  79 + }
  80 + if (maxZoom && maxZoom >= 0 && maxZoom < 19) {
  81 + clusteringSettings.disableClusteringAtZoom = Math.floor(maxZoom);
  82 + }
  83 + this.markersCluster = LM.markerClusterGroup(clusteringSettings);
  84 + this.ready$.subscribe(map => map.addLayer(this.markersCluster));
  85 + }
60 } 86 }
61 87
62 addMarkerControl() { 88 addMarkerControl() {
@@ -65,7 +91,7 @@ export default abstract class LeafletMap { @@ -65,7 +91,7 @@ export default abstract class LeafletMap {
65 let addMarker: L.Control; 91 let addMarker: L.Control;
66 this.map.on('mouseup', (e: L.LeafletMouseEvent) => { 92 this.map.on('mouseup', (e: L.LeafletMouseEvent) => {
67 mousePositionOnMap = e.latlng; 93 mousePositionOnMap = e.latlng;
68 - }) 94 + });
69 const dragListener = (e: L.DragEndEvent) => { 95 const dragListener = (e: L.DragEndEvent) => {
70 if (e.type === 'dragend' && mousePositionOnMap) { 96 if (e.type === 'dragend' && mousePositionOnMap) {
71 const newMarker = L.marker(mousePositionOnMap).addTo(this.map); 97 const newMarker = L.marker(mousePositionOnMap).addTo(this.map);
@@ -80,10 +106,17 @@ export default abstract class LeafletMap { @@ -80,10 +106,17 @@ export default abstract class LeafletMap {
80 this.saveMarkerLocation(updatedEnttity); 106 this.saveMarkerLocation(updatedEnttity);
81 this.map.removeLayer(newMarker); 107 this.map.removeLayer(newMarker);
82 this.deleteMarker(ds.entityName); 108 this.deleteMarker(ds.entityName);
83 - this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options, false); 109 + this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options);
84 } 110 }
85 datasourcesList.append(dsItem); 111 datasourcesList.append(dsItem);
86 - }) 112 + });
  113 + const deleteBtn = document.createElement('a');
  114 + deleteBtn.appendChild(document.createTextNode('Delete position'));
  115 + deleteBtn.setAttribute('color', 'red');
  116 + deleteBtn.onclick = () => {
  117 + this.map.removeLayer(newMarker);
  118 + }
  119 + datasourcesList.append(deleteBtn);
87 const popup = L.popup(); 120 const popup = L.popup();
88 popup.setContent(datasourcesList); 121 popup.setContent(datasourcesList);
89 newMarker.bindPopup(popup).openPopup(); 122 newMarker.bindPopup(popup).openPopup();
@@ -96,6 +129,7 @@ export default abstract class LeafletMap { @@ -96,6 +129,7 @@ export default abstract class LeafletMap {
96 img.src = `assets/add_location.svg`; 129 img.src = `assets/add_location.svg`;
97 img.style.width = '32px'; 130 img.style.width = '32px';
98 img.style.height = '32px'; 131 img.style.height = '32px';
  132 + img.title = 'Drag and drop to add marker';
99 img.onclick = this.dragMarker; 133 img.onclick = this.dragMarker;
100 img.draggable = true; 134 img.draggable = true;
101 const draggableImg = new L.Draggable(img); 135 const draggableImg = new L.Draggable(img);
@@ -106,13 +140,10 @@ export default abstract class LeafletMap { @@ -106,13 +140,10 @@ export default abstract class LeafletMap {
106 onRemove(map) { 140 onRemove(map) {
107 }, 141 },
108 dragMarker: this.dragMarker 142 dragMarker: this.dragMarker
109 -  
110 } as any); 143 } as any);
111 -  
112 L.control.addMarker = (opts) => { 144 L.control.addMarker = (opts) => {
113 return new L.Control.AddMarker(opts); 145 return new L.Control.AddMarker(opts);
114 } 146 }
115 -  
116 addMarker = L.control.addMarker({ position: 'topright' }).addTo(this.map); 147 addMarker = L.control.addMarker({ position: 'topright' }).addTo(this.map);
117 } 148 }
118 } 149 }
@@ -164,9 +195,9 @@ export default abstract class LeafletMap { @@ -164,9 +195,9 @@ export default abstract class LeafletMap {
164 return this.map.getCenter(); 195 return this.map.getCenter();
165 } 196 }
166 197
167 - fitBounds(bounds, useDefaultZoom = false) { 198 + fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) {
168 if (bounds.isValid()) { 199 if (bounds.isValid()) {
169 - if ((this.options.dontFitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) { 200 + if ((!this.options.fitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) {
170 this.map.setZoom(this.options.defaultZoomLevel, { animate: false }); 201 this.map.setZoom(this.options.defaultZoomLevel, { animate: false });
171 this.map.panTo(bounds.getCenter(), { animate: false }); 202 this.map.panTo(bounds.getCenter(), { animate: false });
172 } else { 203 } else {
@@ -175,7 +206,7 @@ export default abstract class LeafletMap { @@ -175,7 +206,7 @@ export default abstract class LeafletMap {
175 this.map.setZoom(this.options.minZoomLevel, { animate: false }); 206 this.map.setZoom(this.options.minZoomLevel, { animate: false });
176 } 207 }
177 }); 208 });
178 - this.map.fitBounds(bounds, { padding: [50, 50], animate: false }); 209 + this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false });
179 } 210 }
180 this.bounds = this.bounds.extend(bounds); 211 this.bounds = this.bounds.extend(bounds);
181 } 212 }
@@ -193,8 +224,8 @@ export default abstract class LeafletMap { @@ -193,8 +224,8 @@ export default abstract class LeafletMap {
193 224
194 convertToCustomFormat(position: L.LatLng): object { 225 convertToCustomFormat(position: L.LatLng): object {
195 return { 226 return {
196 - [this.options.latKeyName]: position.lat,  
197 - [this.options.lngKeyName]: position.lng 227 + [this.options.latKeyName]: position.lat % 180,
  228 + [this.options.lngKeyName]: position.lng % 180
198 } 229 }
199 } 230 }
200 231
@@ -225,11 +256,17 @@ export default abstract class LeafletMap { @@ -225,11 +256,17 @@ export default abstract class LeafletMap {
225 this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) }); 256 this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) });
226 } 257 }
227 258
228 - private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, setFocus = true) { 259 + private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
229 this.ready$.subscribe(() => { 260 this.ready$.subscribe(() => {
230 - const newMarker = new Marker(this.map, this.convertPosition(data), settings, data, dataSources, () => { }, this.dragMarker);  
231 - this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), setFocus); 261 + const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
  262 + this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), settings.draggableMarker && this.markers.size > 2);
232 this.markers.set(key, newMarker); 263 this.markers.set(key, newMarker);
  264 + if (this.options.useClusterMarkers) {
  265 + this.markersCluster.addLayer(newMarker.leafletMarker);
  266 + }
  267 + else {
  268 + this.map.addLayer(newMarker.leafletMarker);
  269 + }
233 }); 270 });
234 } 271 }
235 272
@@ -242,6 +279,8 @@ export default abstract class LeafletMap { @@ -242,6 +279,8 @@ export default abstract class LeafletMap {
242 if (settings.showTooltip) { 279 if (settings.showTooltip) {
243 marker.updateMarkerTooltip(data); 280 marker.updateMarkerTooltip(data);
244 } 281 }
  282 + if (settings.useClusterMarkers)
  283 + this.markersCluster.refreshClusters()
245 marker.setDataSources(data, dataSources); 284 marker.setDataSources(data, dataSources);
246 marker.updateMarkerIcon(settings); 285 marker.updateMarkerIcon(settings);
247 } 286 }
@@ -320,7 +359,9 @@ export default abstract class LeafletMap { @@ -320,7 +359,9 @@ export default abstract class LeafletMap {
320 359
321 updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { 360 updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
322 this.ready$.subscribe(() => { 361 this.ready$.subscribe(() => {
323 - this.polygons.get(key).updatePolygon(data, dataSources, settings); 362 + const poly = this.polygons.get(key);
  363 + poly.updatePolygon(data, dataSources, settings);
  364 + this.fitBounds(poly.leafletPoly.getBounds());
324 }); 365 });
325 } 366 }
326 } 367 }
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { LatLngTuple } from 'leaflet'; 17 +import { LatLngTuple, LeafletMouseEvent } from 'leaflet';
18 18
19 export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; 19 export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
20 export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; 20 export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
@@ -24,14 +24,15 @@ export type MapSettings = { @@ -24,14 +24,15 @@ export type MapSettings = {
24 draggableMarker: boolean; 24 draggableMarker: boolean;
25 initCallback?: () => any; 25 initCallback?: () => any;
26 defaultZoomLevel?: number; 26 defaultZoomLevel?: number;
27 - dontFitMapBounds?: boolean;  
28 disableScrollZooming?: boolean; 27 disableScrollZooming?: boolean;
29 minZoomLevel?: number; 28 minZoomLevel?: number;
  29 + useClusterMarkers: boolean;
30 latKeyName?: string; 30 latKeyName?: string;
31 lngKeyName?: string; 31 lngKeyName?: string;
32 xPosKeyName?: string; 32 xPosKeyName?: string;
33 yPosKeyName?: string; 33 yPosKeyName?: string;
34 mapProvider: MapProviders; 34 mapProvider: MapProviders;
  35 + mapProviderHere: string;
35 mapUrl?: string; 36 mapUrl?: string;
36 mapImageUrl?: string; 37 mapImageUrl?: string;
37 provider?: MapProviders; 38 provider?: MapProviders;
@@ -42,6 +43,13 @@ export type MapSettings = { @@ -42,6 +43,13 @@ export type MapSettings = {
42 gmDefaultMapType?: string; 43 gmDefaultMapType?: string;
43 useLabelFunction: string; 44 useLabelFunction: string;
44 icon?: any; 45 icon?: any;
  46 + zoomOnClick: boolean,
  47 + maxZoom: number,
  48 + showCoverageOnHover: boolean,
  49 + animate: boolean,
  50 + maxClusterRadius: number,
  51 + chunkedLoading: boolean,
  52 + removeOutsideVisibleBounds: boolean
45 } 53 }
46 54
47 export enum MapProviders { 55 export enum MapProviders {
@@ -54,6 +62,7 @@ export enum MapProviders { @@ -54,6 +62,7 @@ export enum MapProviders {
54 62
55 export type MarkerSettings = { 63 export type MarkerSettings = {
56 tooltipPattern?: any; 64 tooltipPattern?: any;
  65 + tooltipAction: { [name: string]: actionsHandler };
57 icon?: any; 66 icon?: any;
58 showLabel?: boolean; 67 showLabel?: boolean;
59 label: string; 68 label: string;
@@ -62,19 +71,19 @@ export type MarkerSettings = { @@ -62,19 +71,19 @@ export type MarkerSettings = {
62 useLabelFunction: boolean; 71 useLabelFunction: boolean;
63 draggableMarker: boolean; 72 draggableMarker: boolean;
64 showTooltip?: boolean; 73 showTooltip?: boolean;
  74 + useTooltipFunction: boolean;
  75 + useColorFunction: boolean;
65 color?: string; 76 color?: string;
66 autocloseTooltip: boolean; 77 autocloseTooltip: boolean;
67 - displayTooltipAction: string; 78 + showTooltipAction: string;
  79 + useClusterMarkers: boolean;
68 currentImage?: string; 80 currentImage?: string;
69 useMarkerImageFunction?: boolean; 81 useMarkerImageFunction?: boolean;
70 markerImages?: string[]; 82 markerImages?: string[];
71 - useMarkerImage: boolean;  
72 markerImageSize: number; 83 markerImageSize: number;
73 fitMapBounds: boolean; 84 fitMapBounds: boolean;
74 - markerImage: {  
75 - length: number  
76 - }  
77 - 85 + markerImage: string;
  86 + markerClick: { [name: string]: actionsHandler };
78 colorFunction: GenericFunction; 87 colorFunction: GenericFunction;
79 tooltipFunction: GenericFunction; 88 tooltipFunction: GenericFunction;
80 labelFunction: GenericFunction; 89 labelFunction: GenericFunction;
@@ -98,16 +107,18 @@ export type PolygonSettings = { @@ -98,16 +107,18 @@ export type PolygonSettings = {
98 polygonStrokeColor: string; 107 polygonStrokeColor: string;
99 polygonColor: string; 108 polygonColor: string;
100 autocloseTooltip: boolean; 109 autocloseTooltip: boolean;
101 - displayTooltipAction: string;  
102 - 110 + showTooltipAction: string;
  111 + tooltipAction: object;
  112 + polygonClick: { [name: string]: actionsHandler };
103 polygonColorFunction?: GenericFunction; 113 polygonColorFunction?: GenericFunction;
104 } 114 }
105 115
106 export type PolylineSettings = { 116 export type PolylineSettings = {
107 usePolylineDecorator: any; 117 usePolylineDecorator: any;
108 autocloseTooltip: boolean; 118 autocloseTooltip: boolean;
109 - displayTooltipAction: string; 119 + showTooltipAction: string;
110 useColorFunction: any; 120 useColorFunction: any;
  121 + tooltipAction: { [name: string]: actionsHandler };
111 color: string; 122 color: string;
112 useStrokeOpacityFunction: any; 123 useStrokeOpacityFunction: any;
113 strokeOpacity: number; 124 strokeOpacity: number;
@@ -131,4 +142,6 @@ export interface HistorySelectSettings { @@ -131,4 +142,6 @@ export interface HistorySelectSettings {
131 buttonColor: string; 142 buttonColor: string;
132 } 143 }
133 144
134 -export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;  
  145 +export type actionsHandler = ($event: Event | LeafletMouseEvent) => void;
  146 +
  147 +export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 /// 15 ///
16 16
17 import { JsonSettingsSchema } from '@shared/models/widget.models'; 17 import { JsonSettingsSchema } from '@shared/models/widget.models';
  18 +import { MapProviders } from './map-models';
18 19
19 export interface MapWidgetInterface { 20 export interface MapWidgetInterface {
20 resize(), 21 resize(),
@@ -24,8 +25,8 @@ export interface MapWidgetInterface { @@ -24,8 +25,8 @@ export interface MapWidgetInterface {
24 } 25 }
25 26
26 export interface MapWidgetStaticInterface { 27 export interface MapWidgetStaticInterface {
27 - settingsSchema(mapProvider?, drawRoutes?): JsonSettingsSchema;  
28 - getProvidersSchema():JsonSettingsSchema 28 + settingsSchema(mapProvider?: MapProviders, drawRoutes?: boolean): JsonSettingsSchema;
  29 + getProvidersSchema(mapProvider?: MapProviders): JsonSettingsSchema
29 dataKeySettingsSchema(): object; 30 dataKeySettingsSchema(): object;
30 actionSources(): object; 31 actionSources(): object;
31 } 32 }
@@ -26,20 +26,23 @@ import { @@ -26,20 +26,23 @@ import {
26 markerClusteringSettingsSchema, 26 markerClusteringSettingsSchema,
27 markerClusteringSettingsSchemaLeaflet, 27 markerClusteringSettingsSchemaLeaflet,
28 hereMapSettingsSchema, 28 hereMapSettingsSchema,
29 - mapProviderSchema 29 + mapProviderSchema,
  30 + mapPolygonSchema
30 } from './schemes'; 31 } from './schemes';
31 import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; 32 import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
32 import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers'; 33 import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
33 -import { parseFunction, parseArray, parseData } from '@core/utils'; 34 +import { parseFunction, parseArray, parseData, safeExecute, parseWithTranslation } from '@core/utils';
34 import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils'; 35 import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils';
35 import { forkJoin } from 'rxjs'; 36 import { forkJoin } from 'rxjs';
36 import { WidgetContext } from '@app/modules/home/models/widget-component.models'; 37 import { WidgetContext } from '@app/modules/home/models/widget-component.models';
37 import { getDefCenterPosition } from './maps-utils'; 38 import { getDefCenterPosition } from './maps-utils';
38 -import { JsonSettingsSchema } from '@shared/models/widget.models'; 39 +import { JsonSettingsSchema, WidgetActionDescriptor } from '@shared/models/widget.models';
39 import { EntityId } from '@shared/models/id/entity-id'; 40 import { EntityId } from '@shared/models/id/entity-id';
40 import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; 41 import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
41 import { AttributeService } from '@core/http/attribute.service'; 42 import { AttributeService } from '@core/http/attribute.service';
42 import { Type } from '@angular/core'; 43 import { Type } from '@angular/core';
  44 +import { TranslateService } from '@ngx-translate/core';
  45 +import { UtilsService } from '@app/core/public-api';
43 46
44 // @dynamic 47 // @dynamic
45 export class MapWidgetController implements MapWidgetInterface { 48 export class MapWidgetController implements MapWidgetInterface {
@@ -55,11 +58,16 @@ export class MapWidgetController implements MapWidgetInterface { @@ -55,11 +58,16 @@ export class MapWidgetController implements MapWidgetInterface {
55 $element = ctx.$container[0]; 58 $element = ctx.$container[0];
56 } 59 }
57 this.settings = this.initSettings(ctx.settings); 60 this.settings = this.initSettings(ctx.settings);
  61 + this.settings.tooltipAction = this.getDescriptors('tooltipAction');
  62 + this.settings.markerClick = this.getDescriptors('markerClick');
  63 + this.settings.polygonClick = this.getDescriptors('polygonClick');
58 64
  65 + // this.settings.
59 const MapClass = providerSets[this.provider]?.MapClass; 66 const MapClass = providerSets[this.provider]?.MapClass;
60 if (!MapClass) { 67 if (!MapClass) {
61 return; 68 return;
62 } 69 }
  70 + parseWithTranslation.setTranslate(this.translate);
63 this.map = new MapClass($element, this.settings); 71 this.map = new MapClass($element, this.settings);
64 this.map.saveMarkerLocation = this.setMarkerLocation; 72 this.map.saveMarkerLocation = this.setMarkerLocation;
65 } 73 }
@@ -74,7 +82,8 @@ export class MapWidgetController implements MapWidgetInterface { @@ -74,7 +82,8 @@ export class MapWidgetController implements MapWidgetInterface {
74 return {}; 82 return {};
75 } 83 }
76 84
77 - public static getProvidersSchema() { 85 + public static getProvidersSchema(mapProvider: MapProviders) {
  86 + mapProviderSchema.schema.properties.provider.default = mapProvider;
78 return mergeSchemes([mapProviderSchema, 87 return mergeSchemes([mapProviderSchema,
79 ...Object.values(providerSets)?.map( 88 ...Object.values(providerSets)?.map(
80 (setting: IProvider) => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]); 89 (setting: IProvider) => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]);
@@ -82,19 +91,20 @@ export class MapWidgetController implements MapWidgetInterface { @@ -82,19 +91,20 @@ export class MapWidgetController implements MapWidgetInterface {
82 91
83 public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema { 92 public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema {
84 const schema = initSchema(); 93 const schema = initSchema();
85 - addToSchema(schema, this.getProvidersSchema());  
86 - addGroupInfo(schema, 'Map Provider Settings');  
87 - addToSchema(schema, commonMapSettingsSchema); 94 + addToSchema(schema, this.getProvidersSchema(mapProvider));
  95 + if(mapProvider!=='image-map'){
  96 + addGroupInfo(schema, 'Map Provider Settings');
  97 + addToSchema(schema, mergeSchemes([commonMapSettingsSchema, addCondition(mapPolygonSchema, 'model.showPolygon === true')]));
88 addGroupInfo(schema, 'Common Map Settings'); 98 addGroupInfo(schema, 'Common Map Settings');
89 -  
90 if (drawRoutes) { 99 if (drawRoutes) {
91 addToSchema(schema, routeMapSettingsSchema); 100 addToSchema(schema, routeMapSettingsSchema);
92 addGroupInfo(schema, 'Route Map Settings'); 101 addGroupInfo(schema, 'Route Map Settings');
93 } else if (mapProvider !== 'image-map') { 102 } else if (mapProvider !== 'image-map') {
94 - const clusteringSchema = mergeSchemes([markerClusteringSettingsSchemaLeaflet, markerClusteringSettingsSchema]) 103 + const clusteringSchema = mergeSchemes([markerClusteringSettingsSchema,
  104 + addCondition(markerClusteringSettingsSchemaLeaflet, `model.useClusterMarkers === true`)])
95 addToSchema(schema, clusteringSchema); 105 addToSchema(schema, clusteringSchema);
96 addGroupInfo(schema, 'Markers Clustering Settings'); 106 addGroupInfo(schema, 'Markers Clustering Settings');
97 - } 107 + }}
98 return schema; 108 return schema;
99 } 109 }
100 110
@@ -115,9 +125,35 @@ export class MapWidgetController implements MapWidgetInterface { @@ -115,9 +125,35 @@ export class MapWidgetController implements MapWidgetInterface {
115 }; 125 };
116 } 126 }
117 127
  128 + translate = (key: string, defaultTranslation?: string):string => {
  129 + return (this.ctx.$injector.get(UtilsService).customTranslation(key, defaultTranslation || key)
  130 + || this.ctx.$injector.get(TranslateService).instant(key));
  131 + }
  132 +
  133 + getDescriptors(name: string): { [name: string]: ($event: Event) => void } {
  134 + const descriptors = this.ctx.actionsApi.getActionDescriptors(name);
  135 + const actions = {};
  136 + descriptors.forEach(descriptor => {
  137 + actions[descriptor.name] = ($event: Event) => this.onCustomAction(descriptor, $event);
  138 + }, actions);
  139 + return actions;
  140 + }
  141 +
118 onInit() { 142 onInit() {
119 } 143 }
120 144
  145 + private onCustomAction(descriptor: WidgetActionDescriptor, $event: any) {
  146 + if ($event & $event.stopPropagation) {
  147 + $event?.stopPropagation();
  148 + }
  149 + // safeExecute(parseFunction(descriptor.customFunction, ['$event', 'widgetContext']), [$event, this.ctx])
  150 + const entityInfo = this.ctx.actionsApi.getActiveEntityInfo();
  151 + const entityId = entityInfo ? entityInfo.entityId : null;
  152 + const entityName = entityInfo ? entityInfo.entityName : null;
  153 + const entityLabel = entityInfo ? entityInfo.entityLabel : null;
  154 + this.ctx.actionsApi.handleWidgetAction($event, descriptor, entityId, entityName, null, entityLabel);
  155 + }
  156 +
121 setMarkerLocation = (e) => { 157 setMarkerLocation = (e) => {
122 const attributeService = this.ctx.$injector.get(AttributeService); 158 const attributeService = this.ctx.$injector.get(AttributeService);
123 forkJoin( 159 forkJoin(
@@ -136,13 +172,17 @@ export class MapWidgetController implements MapWidgetInterface { @@ -136,13 +172,17 @@ export class MapWidgetController implements MapWidgetInterface {
136 }] 172 }]
137 ); 173 );
138 })).subscribe(res => { 174 })).subscribe(res => {
139 - console.log('MapWidgetController -> setMarkerLocation -> res', res)  
140 }); 175 });
141 } 176 }
142 177
143 initSettings(settings: UnitedMapSettings): UnitedMapSettings { 178 initSettings(settings: UnitedMapSettings): UnitedMapSettings {
144 const functionParams = ['data', 'dsData', 'dsIndex']; 179 const functionParams = ['data', 'dsData', 'dsIndex'];
145 this.provider = settings.provider || this.mapProvider; 180 this.provider = settings.provider || this.mapProvider;
  181 + if (!settings.mapProviderHere) {
  182 + if (settings.mapProvider && hereProviders.includes(settings.mapProvider))
  183 + settings.mapProviderHere = settings.mapProvider
  184 + else settings.mapProviderHere = hereProviders[0];
  185 + }
146 const customOptions = { 186 const customOptions = {
147 provider: this.provider, 187 provider: this.provider,
148 mapUrl: settings?.mapImageUrl, 188 mapUrl: settings?.mapImageUrl,
@@ -156,7 +196,7 @@ export class MapWidgetController implements MapWidgetInterface { @@ -156,7 +196,7 @@ export class MapWidgetController implements MapWidgetInterface {
156 '<b>${entityName}</b><br/><br/><b>Latitude:</b> ${' + 196 '<b>${entityName}</b><br/><br/><b>Latitude:</b> ${' +
157 settings.latKeyName + ':7}<br/><b>Longitude:</b> ${' + settings.lngKeyName + ':7}', 197 settings.latKeyName + ':7}<br/><b>Longitude:</b> ${' + settings.lngKeyName + ':7}',
158 defaultCenterPosition: getDefCenterPosition(settings?.defaultCenterPosition), 198 defaultCenterPosition: getDefCenterPosition(settings?.defaultCenterPosition),
159 - currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? { 199 + currentImage: (settings.markerImage?.length) ? {
160 url: settings.markerImage, 200 url: settings.markerImage,
161 size: settings.markerImageSize || 34 201 size: settings.markerImageSize || 34
162 } : null 202 } : null
@@ -194,7 +234,7 @@ interface IProvider { @@ -194,7 +234,7 @@ interface IProvider {
194 name: string 234 name: string
195 } 235 }
196 236
197 -export const providerSets: {[key: string]: IProvider} = { 237 +export const providerSets: { [key: string]: IProvider } = {
198 'openstreet-map': { 238 'openstreet-map': {
199 MapClass: OpenStreetMap, 239 MapClass: OpenStreetMap,
200 schema: openstreetMapSettingsSchema, 240 schema: openstreetMapSettingsSchema,
@@ -236,7 +276,7 @@ export const defaultSettings: any = { @@ -236,7 +276,7 @@ export const defaultSettings: any = {
236 useDefaultCenterPosition: false, 276 useDefaultCenterPosition: false,
237 showTooltipAction: 'click', 277 showTooltipAction: 'click',
238 autocloseTooltip: false, 278 autocloseTooltip: false,
239 - showPolygon: true, 279 + showPolygon: false,
240 labelColor: '#000000', 280 labelColor: '#000000',
241 color: '#FE7569', 281 color: '#FE7569',
242 polygonColor: '#0000ff', 282 polygonColor: '#0000ff',
@@ -250,10 +290,16 @@ export const defaultSettings: any = { @@ -250,10 +290,16 @@ export const defaultSettings: any = {
250 strokeOpacity: 1.0, 290 strokeOpacity: 1.0,
251 initCallback: () => { }, 291 initCallback: () => { },
252 defaultZoomLevel: 8, 292 defaultZoomLevel: 8,
253 - dontFitMapBounds: false,  
254 disableScrollZooming: false, 293 disableScrollZooming: false,
255 minZoomLevel: 16, 294 minZoomLevel: 16,
256 credentials: '', 295 credentials: '',
257 markerClusteringSetting: null, 296 markerClusteringSetting: null,
258 - draggableMarker: false 297 + draggableMarker: false,
  298 + fitMapBounds: true
259 }; 299 };
  300 +
  301 +export const hereProviders = [
  302 + 'HERE.normalDay',
  303 + 'HERE.normalNight',
  304 + 'HERE.hybridDay',
  305 + 'HERE.terrainDay']
@@ -17,20 +17,32 @@ @@ -17,20 +17,32 @@
17 import L from 'leaflet'; 17 import L from 'leaflet';
18 import _ from 'lodash'; 18 import _ from 'lodash';
19 import { MarkerSettings, PolylineSettings, PolygonSettings } from './map-models'; 19 import { MarkerSettings, PolylineSettings, PolygonSettings } from './map-models';
  20 +import { parseWithTranslation } from '@app/core/utils';
20 21
21 -export function createTooltip(target: L.Layer, settings: MarkerSettings | PolylineSettings | PolygonSettings): L.Popup { 22 +export function createTooltip(target: L.Layer,
  23 + settings: MarkerSettings | PolylineSettings | PolygonSettings,
  24 + content?: string | HTMLElement): L.Popup {
22 const popup = L.popup(); 25 const popup = L.popup();
23 - popup.setContent(''); 26 + popup.setContent(content);
24 target.bindPopup(popup, { autoClose: settings.autocloseTooltip, closeOnClick: false }); 27 target.bindPopup(popup, { autoClose: settings.autocloseTooltip, closeOnClick: false });
25 - if (settings.displayTooltipAction === 'hover') { 28 + if (settings.showTooltipAction === 'hover') {
26 target.off('click'); 29 target.off('click');
27 target.on('mouseover', function () { 30 target.on('mouseover', function () {
28 - this.openPopup(); 31 + target.openPopup();
29 }); 32 });
30 target.on('mouseout', function () { 33 target.on('mouseout', function () {
31 - this.closePopup(); 34 + target.closePopup();
32 }); 35 });
33 } 36 }
  37 + target.on('popupopen', () => {
  38 + const actions = document.getElementsByClassName('tb-custom-action');
  39 + Array.from(actions).forEach(
  40 + (element: HTMLElement) => {
  41 + if (element && settings.tooltipAction[element.id]) {
  42 + element.addEventListener('click', settings.tooltipAction[element.id])
  43 + }
  44 + });
  45 + });
34 return popup; 46 return popup;
35 } 47 }
36 48
@@ -51,5 +63,4 @@ export function getDefCenterPosition(position) { @@ -51,5 +63,4 @@ export function getDefCenterPosition(position) {
51 if (typeof (position) === 'object') 63 if (typeof (position) === 'object')
52 return position; 64 return position;
53 return [0, 0]; 65 return [0, 0];
54 -}  
55 - 66 +}
@@ -29,3 +29,7 @@ @@ -29,3 +29,7 @@
29 background: none; 29 background: none;
30 box-shadow: none; 30 box-shadow: none;
31 } 31 }
  32 +
  33 +.leaflet-container{
  34 + background-color: white;
  35 +}
@@ -14,13 +14,13 @@ @@ -14,13 +14,13 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import L from 'leaflet';  
18 -import { MarkerSettings, FormattedData } from './map-models';  
19 -import { aspectCache, safeExecute, parseTemplate } from '@app/core/utils'; 17 +import { aspectCache, parseWithTranslation, safeExecute } from '@app/core/utils';
  18 +import L, { LeafletMouseEvent } from 'leaflet';
  19 +import { FormattedData, MarkerSettings } from './map-models';
20 import { createTooltip } from './maps-utils'; 20 import { createTooltip } from './maps-utils';
  21 +import tinycolor from 'tinycolor2';
21 22
22 export class Marker { 23 export class Marker {
23 -  
24 leafletMarker: L.Marker; 24 leafletMarker: L.Marker;
25 tooltipOffset: [number, number]; 25 tooltipOffset: [number, number];
26 tooltip: L.Popup; 26 tooltip: L.Popup;
@@ -28,8 +28,8 @@ export class Marker { @@ -28,8 +28,8 @@ export class Marker {
28 data: FormattedData; 28 data: FormattedData;
29 dataSources: FormattedData[]; 29 dataSources: FormattedData[];
30 30
31 - constructor(private map: L.Map, location: L.LatLngExpression, public settings: MarkerSettings,  
32 - data?, dataSources?, onClickListener?, onDragendListener?) { 31 + constructor(location: L.LatLngExpression, public settings: MarkerSettings,
  32 + data?: FormattedData, dataSources?, onDragendListener?) {
33 this.setDataSources(data, dataSources); 33 this.setDataSources(data, dataSources);
34 this.leafletMarker = L.marker(location, { 34 this.leafletMarker = L.marker(location, {
35 draggable: settings.draggableMarker 35 draggable: settings.draggableMarker
@@ -39,16 +39,21 @@ export class Marker { @@ -39,16 +39,21 @@ export class Marker {
39 this.leafletMarker.setIcon(iconInfo.icon); 39 this.leafletMarker.setIcon(iconInfo.icon);
40 this.tooltipOffset = [0, -iconInfo.size[1] + 10]; 40 this.tooltipOffset = [0, -iconInfo.size[1] + 10];
41 this.updateMarkerLabel(settings); 41 this.updateMarkerLabel(settings);
42 - this.leafletMarker.addTo(map);  
43 }); 42 });
44 43
45 if (settings.showTooltip) { 44 if (settings.showTooltip) {
46 this.tooltip = createTooltip(this.leafletMarker, settings); 45 this.tooltip = createTooltip(this.leafletMarker, settings);
47 - this.tooltip.setContent(parseTemplate(this.settings.tooltipPattern, data)); 46 + this.updateMarkerTooltip(data);
48 } 47 }
49 48
50 - if (onClickListener) {  
51 - this.leafletMarker.on('click', onClickListener); 49 + if (this.settings.markerClick) {
  50 + this.leafletMarker.on('click', (event: LeafletMouseEvent) => {
  51 + for (const action in this.settings.markerClick) {
  52 + if (typeof (this.settings.markerClick[action]) === 'function') {
  53 + this.settings.markerClick[action](event);
  54 + }
  55 + }
  56 + });
52 } 57 }
53 58
54 if (onDragendListener) { 59 if (onDragendListener) {
@@ -62,7 +67,9 @@ export class Marker { @@ -62,7 +67,9 @@ export class Marker {
62 } 67 }
63 68
64 updateMarkerTooltip(data: FormattedData) { 69 updateMarkerTooltip(data: FormattedData) {
65 - this.tooltip.setContent(parseTemplate(this.settings.tooltipPattern, data)); 70 + const pattern = this.settings.useTooltipFunction ?
  71 + safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
  72 + this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
66 } 73 }
67 74
68 updateMarkerPosition(position: L.LatLngExpression) { 75 updateMarkerPosition(position: L.LatLngExpression) {
@@ -71,13 +78,10 @@ export class Marker { @@ -71,13 +78,10 @@ export class Marker {
71 78
72 updateMarkerLabel(settings: MarkerSettings) { 79 updateMarkerLabel(settings: MarkerSettings) {
73 this.leafletMarker.unbindTooltip(); 80 this.leafletMarker.unbindTooltip();
74 -  
75 if (settings.showLabel) { 81 if (settings.showLabel) {
76 - if (settings.useLabelFunction) {  
77 - settings.labelText = parseTemplate(  
78 - safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]), this.data)  
79 - }  
80 - else settings.labelText = parseTemplate(settings.label, this.data); 82 + const pattern = settings.useLabelFunction ?
  83 + safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label;
  84 + settings.labelText = parseWithTranslation.parseTemplate(pattern, this.data, true);
81 this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`, 85 this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
82 { className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset }); 86 { className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset });
83 } 87 }
@@ -98,7 +102,6 @@ export class Marker { @@ -98,7 +102,6 @@ export class Marker {
98 } 102 }
99 103
100 createMarkerIcon(onMarkerIconReady) { 104 createMarkerIcon(onMarkerIconReady) {
101 -  
102 if (this.settings.icon) { 105 if (this.settings.icon) {
103 onMarkerIconReady({ 106 onMarkerIconReady({
104 size: [30, 30], 107 size: [30, 30],
@@ -106,11 +109,11 @@ export class Marker { @@ -106,11 +109,11 @@ export class Marker {
106 }); 109 });
107 return; 110 return;
108 } 111 }
109 -  
110 const currentImage = this.settings.useMarkerImageFunction ? 112 const currentImage = this.settings.useMarkerImageFunction ?
111 safeExecute(this.settings.markerImageFunction, 113 safeExecute(this.settings.markerImageFunction,
112 [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage; 114 [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;
113 - 115 + const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction,
  116 + [this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex();
114 117
115 if (currentImage && currentImage.url) { 118 if (currentImage && currentImage.url) {
116 aspectCache(currentImage.url).subscribe( 119 aspectCache(currentImage.url).subscribe(
@@ -137,19 +140,19 @@ export class Marker { @@ -137,19 +140,19 @@ export class Marker {
137 }; 140 };
138 onMarkerIconReady(iconInfo); 141 onMarkerIconReady(iconInfo);
139 } else { 142 } else {
140 - this.createDefaultMarkerIcon(this.settings.color, onMarkerIconReady); 143 + this.createDefaultMarkerIcon(currentColor, onMarkerIconReady);
141 } 144 }
142 } 145 }
143 ); 146 );
144 } else { 147 } else {
145 - this.createDefaultMarkerIcon(this.settings.color, onMarkerIconReady); 148 + this.createDefaultMarkerIcon(currentColor, onMarkerIconReady);
146 } 149 }
147 } 150 }
148 151
149 createDefaultMarkerIcon(color, onMarkerIconReady) { 152 createDefaultMarkerIcon(color, onMarkerIconReady) {
150 const pinColor = color.substr(1); 153 const pinColor = color.substr(1);
151 const icon = L.icon({ 154 const icon = L.icon({
152 - iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor, 155 + iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color,
153 iconSize: [21, 34], 156 iconSize: [21, 34],
154 iconAnchor: [10, 34], 157 iconAnchor: [10, 34],
155 popupAnchor: [0, -34], 158 popupAnchor: [0, -34],
@@ -20,14 +20,9 @@ import { MapSettings, UnitedMapSettings } from '../map-models'; @@ -20,14 +20,9 @@ import { MapSettings, UnitedMapSettings } from '../map-models';
20 20
21 export class HEREMap extends LeafletMap { 21 export class HEREMap extends LeafletMap {
22 constructor($container, options: UnitedMapSettings) { 22 constructor($container, options: UnitedMapSettings) {
23 - const defaultCredentials =  
24 - {  
25 - app_id: 'AhM6TzD9ThyK78CT3ptx',  
26 - app_code: 'p6NPiITB3Vv0GMUFnkLOOg'  
27 - }  
28 super($container, options); 23 super($container, options);
29 const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); 24 const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
30 - const tileLayer = (L.tileLayer as any).provider(options.mapProvider || 'HERE.normalDay', options.credentials || defaultCredentials); 25 + const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
31 tileLayer.addTo(map); 26 tileLayer.addTo(map);
32 super.setMap(map); 27 super.setMap(map);
33 super.initSettings(options); 28 super.initSettings(options);
@@ -110,7 +110,7 @@ export const hereMapSettingsSchema = @@ -110,7 +110,7 @@ export const hereMapSettingsSchema =
110 title: 'HERE Map Configuration', 110 title: 'HERE Map Configuration',
111 type: 'object', 111 type: 'object',
112 properties: { 112 properties: {
113 - mapProvider: { 113 + mapProviderHere: {
114 title: 'Map layer', 114 title: 'Map layer',
115 type: 'string', 115 type: 'string',
116 default: 'HERE.normalDay' 116 default: 'HERE.normalDay'
@@ -120,11 +120,13 @@ export const hereMapSettingsSchema = @@ -120,11 +120,13 @@ export const hereMapSettingsSchema =
120 properties: { 120 properties: {
121 app_id: { 121 app_id: {
122 title: 'HERE app id', 122 title: 'HERE app id',
123 - type: 'string' 123 + type: 'string',
  124 + default: 'AhM6TzD9ThyK78CT3ptx'
124 }, 125 },
125 app_code: { 126 app_code: {
126 title: 'HERE app code', 127 title: 'HERE app code',
127 - type: 'string' 128 + type: 'string',
  129 + default: 'p6NPiITB3Vv0GMUFnkLOOg'
128 } 130 }
129 }, 131 },
130 required: ['app_id', 'app_code'] 132 required: ['app_id', 'app_code']
@@ -134,7 +136,7 @@ export const hereMapSettingsSchema = @@ -134,7 +136,7 @@ export const hereMapSettingsSchema =
134 }, 136 },
135 form: [ 137 form: [
136 { 138 {
137 - key: 'mapProvider', 139 + key: 'mapProviderHere',
138 type: 'rc-select', 140 type: 'rc-select',
139 multiple: false, 141 multiple: false,
140 items: [ 142 items: [
@@ -339,43 +341,6 @@ export const commonMapSettingsSchema = @@ -339,43 +341,6 @@ export const commonMapSettingsSchema =
339 type: 'boolean', 341 type: 'boolean',
340 default: false 342 default: false
341 }, 343 },
342 - polygonKeyName: {  
343 - title: 'Polygon key name',  
344 - type: 'string',  
345 - default: 'coordinates'  
346 - },  
347 - polygonColor: {  
348 - title: 'Polygon color',  
349 - type: 'string'  
350 - },  
351 - polygonOpacity: {  
352 - title: 'Polygon opacity',  
353 - type: 'number',  
354 - default: 0.5  
355 - },  
356 - polygonStrokeColor: {  
357 - title: 'Stroke color',  
358 - type: 'string'  
359 - },  
360 - polygonStrokeOpacity: {  
361 - title: 'Stroke opacity',  
362 - type: 'number',  
363 - default: 1  
364 - },  
365 - polygonStrokeWeight: {  
366 - title: 'Stroke weight',  
367 - type: 'number',  
368 - default: 1  
369 - },  
370 - usePolygonColorFunction: {  
371 - title: 'Use polygon color function',  
372 - type: 'boolean',  
373 - default: false  
374 - },  
375 - polygonColorFunction: {  
376 - title: 'Polygon Color function: f(data, dsData, dsIndex)',  
377 - type: 'string'  
378 - },  
379 markerImage: { 344 markerImage: {
380 title: 'Custom marker image', 345 title: 'Custom marker image',
381 type: 'string' 346 type: 'string'
@@ -455,20 +420,6 @@ export const commonMapSettingsSchema = @@ -455,20 +420,6 @@ export const commonMapSettingsSchema =
455 { 420 {
456 key: 'colorFunction', 421 key: 'colorFunction',
457 type: 'javascript' 422 type: 'javascript'
458 - }, 'showPolygon', 'polygonKeyName',  
459 - {  
460 - key: 'polygonColor',  
461 - type: 'color'  
462 - },  
463 - 'polygonOpacity',  
464 - {  
465 - key: 'polygonStrokeColor',  
466 - type: 'color'  
467 - },  
468 - 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction',  
469 - {  
470 - key: 'polygonColorFunction',  
471 - type: 'javascript'  
472 }, 423 },
473 { 424 {
474 key: 'markerImage', 425 key: 'markerImage',
@@ -488,7 +439,73 @@ export const commonMapSettingsSchema = @@ -488,7 +439,73 @@ export const commonMapSettingsSchema =
488 type: 'image' 439 type: 'image'
489 } 440 }
490 ] 441 ]
491 - } 442 + },
  443 + 'showPolygon',
  444 + ]
  445 +};
  446 +
  447 +export const mapPolygonSchema =
  448 +{
  449 + schema: {
  450 + title: 'Map Polygon Configuration',
  451 + type: 'object',
  452 + properties: {
  453 + polygonKeyName: {
  454 + title: 'Polygon key name',
  455 + type: 'string',
  456 + default: 'coordinates'
  457 + },
  458 + polygonColor: {
  459 + title: 'Polygon color',
  460 + type: 'string'
  461 + },
  462 + polygonOpacity: {
  463 + title: 'Polygon opacity',
  464 + type: 'number',
  465 + default: 0.5
  466 + },
  467 + polygonStrokeColor: {
  468 + title: 'Stroke color',
  469 + type: 'string'
  470 + },
  471 + polygonStrokeOpacity: {
  472 + title: 'Stroke opacity',
  473 + type: 'number',
  474 + default: 1
  475 + },
  476 + polygonStrokeWeight: {
  477 + title: 'Stroke weight',
  478 + type: 'number',
  479 + default: 1
  480 + },
  481 + usePolygonColorFunction: {
  482 + title: 'Use polygon color function',
  483 + type: 'boolean',
  484 + default: false
  485 + },
  486 + polygonColorFunction: {
  487 + title: 'Polygon Color function: f(data, dsData, dsIndex)',
  488 + type: 'string'
  489 + },
  490 + },
  491 + required: []
  492 + },
  493 + form: [
  494 + 'polygonKeyName',
  495 + {
  496 + key: 'polygonColor',
  497 + type: 'color'
  498 + },
  499 + 'polygonOpacity',
  500 + {
  501 + key: 'polygonStrokeColor',
  502 + type: 'color'
  503 + },
  504 + 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction',
  505 + {
  506 + key: 'polygonColorFunction',
  507 + type: 'javascript'
  508 + },
492 ] 509 ]
493 }; 510 };
494 511
@@ -527,23 +544,12 @@ export const markerClusteringSettingsSchema = @@ -527,23 +544,12 @@ export const markerClusteringSettingsSchema =
527 title: 'Use map markers clustering', 544 title: 'Use map markers clustering',
528 type: 'boolean', 545 type: 'boolean',
529 default: false 546 default: false
530 - },  
531 - zoomOnClick: {  
532 - title: 'Zoom when clicking on a cluster',  
533 - type: 'boolean',  
534 - default: true  
535 - },  
536 - maxZoom: {  
537 - title: 'The maximum zoom level when a marker can be part of a cluster (0 - 18)',  
538 - type: 'number'  
539 } 547 }
540 }, 548 },
541 required: [] 549 required: []
542 }, 550 },
543 form: [ 551 form: [
544 'useClusterMarkers', 552 'useClusterMarkers',
545 - 'zoomOnClick',  
546 - 'maxZoom'  
547 ] 553 ]
548 }; 554 };
549 555
@@ -571,12 +577,23 @@ export const markerClusteringSettingsSchemaGoogle = @@ -571,12 +577,23 @@ export const markerClusteringSettingsSchemaGoogle =
571 ] 577 ]
572 }; 578 };
573 579
  580 +
  581 +
574 export const markerClusteringSettingsSchemaLeaflet = 582 export const markerClusteringSettingsSchemaLeaflet =
575 { 583 {
576 schema: { 584 schema: {
577 title: 'Markers Clustering Configuration Leaflet', 585 title: 'Markers Clustering Configuration Leaflet',
578 type: 'object', 586 type: 'object',
579 properties: { 587 properties: {
  588 + zoomOnClick: {
  589 + title: 'Zoom when clicking on a cluster',
  590 + type: 'boolean',
  591 + default: true
  592 + },
  593 + maxZoom: {
  594 + title: 'The maximum zoom level when a marker can be part of a cluster (0 - 18)',
  595 + type: 'number'
  596 + },
580 showCoverageOnHover: { 597 showCoverageOnHover: {
581 title: 'Show the bounds of markers when mouse over a cluster', 598 title: 'Show the bounds of markers when mouse over a cluster',
582 type: 'boolean', 599 type: 'boolean',
@@ -606,6 +623,8 @@ export const markerClusteringSettingsSchemaLeaflet = @@ -606,6 +623,8 @@ export const markerClusteringSettingsSchemaLeaflet =
606 required: [] 623 required: []
607 }, 624 },
608 form: [ 625 form: [
  626 + 'zoomOnClick',
  627 + 'maxZoom',
609 'showCoverageOnHover', 628 'showCoverageOnHover',
610 'animate', 629 'animate',
611 'maxClusterRadius', 630 'maxClusterRadius',
@@ -17,17 +17,17 @@ @@ -17,17 +17,17 @@
17 --> 17 -->
18 <div class="trip-animation-widget"> 18 <div class="trip-animation-widget">
19 <div class="trip-animation-label-container" *ngIf="settings.showLabel"> 19 <div class="trip-animation-label-container" *ngIf="settings.showLabel">
20 - {{settings.label | tbParseTemplate: activeTrip}} 20 + {{label }}
21 </div> 21 </div>
22 - <div class="trip-animation-container" layout="column"> 22 + <div class="trip-animation-container" fxLayout="column">
23 <div class="map" #map></div> 23 <div class="map" #map></div>
24 - <div class="trip-animation-info-panel" layout="row"> 24 + <div class="trip-animation-info-panel" fxLayout="row">
25 <button class="tooltip-button" mat-mini-fab color="primary" aria-label="tooltip" 25 <button class="tooltip-button" mat-mini-fab color="primary" aria-label="tooltip"
26 - *ngIf="settings.showTooltip" (click)="showHideTooltip()"> 26 + *ngIf="settings.showTooltip" (click)="visibleTooltip = !visibleTooltip">
27 <mat-icon>info_outline</mat-icon> 27 <mat-icon>info_outline</mat-icon>
28 </button> 28 </button>
29 </div> 29 </div>
30 - <div class="trip-animation-tooltip md-whiteframe-z4" layout="column" 30 + <div class="trip-animation-tooltip md-whiteframe-z4" fxLayout="column"
31 [ngClass]="{'trip-animation-tooltip-hidden':!visibleTooltip}" [innerHTML]="mainTooltip" 31 [ngClass]="{'trip-animation-tooltip-hidden':!visibleTooltip}" [innerHTML]="mainTooltip"
32 [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}"> 32 [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
33 </div> 33 </div>
@@ -74,10 +74,10 @@ @@ -74,10 +74,10 @@
74 74
75 .trip-animation-tooltip { 75 .trip-animation-tooltip {
76 position: absolute; 76 position: absolute;
77 - top: 38px; 77 + top: 30px;
78 right: 0; 78 right: 0;
79 - z-index: 400;  
80 - padding: 10px; 79 + z-index: 1000;
  80 + padding: 5px;
81 background-color: #fff; 81 background-color: #fff;
82 transition: 0.3s ease-in-out; 82 transition: 0.3s ease-in-out;
83 83
@@ -86,4 +86,4 @@ @@ -86,4 +86,4 @@
86 } 86 }
87 } 87 }
88 } 88 }
89 -} 89 +}
@@ -22,13 +22,14 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil'; @@ -22,13 +22,14 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil';
22 import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef, SecurityContext } from '@angular/core'; 22 import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef, SecurityContext } from '@angular/core';
23 import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2'; 23 import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
24 import { MapProviders } from '../lib/maps/map-models'; 24 import { MapProviders } from '../lib/maps/map-models';
25 -import { parseArray, parseTemplate, safeExecute } from '@app/core/utils'; 25 +import { parseArray, parseWithTranslation, safeExecute, parseTemplate } from '@app/core/utils';
26 import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils'; 26 import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils';
27 import { tripAnimationSchema } from '../lib/maps/schemes'; 27 import { tripAnimationSchema } from '../lib/maps/schemes';
28 import { DomSanitizer } from '@angular/platform-browser'; 28 import { DomSanitizer } from '@angular/platform-browser';
29 import { WidgetContext } from '@app/modules/home/models/widget-component.models'; 29 import { WidgetContext } from '@app/modules/home/models/widget-component.models';
30 import { getRatio, findAngle } from '../lib/maps/maps-utils'; 30 import { getRatio, findAngle } from '../lib/maps/maps-utils';
31 import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models'; 31 import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
  32 +import moment from 'moment';
32 33
33 34
34 @Component({ 35 @Component({
@@ -55,6 +56,9 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -55,6 +56,9 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
55 mainTooltip = ''; 56 mainTooltip = '';
56 visibleTooltip = false; 57 visibleTooltip = false;
57 activeTrip; 58 activeTrip;
  59 + label;
  60 + minTime;
  61 + maxTime;
58 62
59 static getSettingsSchema(): JsonSettingsSchema { 63 static getSettingsSchema(): JsonSettingsSchema {
60 const schema = initSchema(); 64 const schema = initSchema();
@@ -90,48 +94,58 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -90,48 +94,58 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
90 94
91 ngAfterViewInit() { 95 ngAfterViewInit() {
92 const ctxCopy: WidgetContext = _.cloneDeep(this.ctx); 96 const ctxCopy: WidgetContext = _.cloneDeep(this.ctx);
93 - ctxCopy.settings.showLabel = false;  
94 - ctxCopy.settings.showTooltip = false;  
95 this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, ctxCopy, this.mapContainer.nativeElement); 97 this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, ctxCopy, this.mapContainer.nativeElement);
96 } 98 }
97 99
98 timeUpdated(time: number) { 100 timeUpdated(time: number) {
99 const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]); 101 const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]);
100 this.activeTrip = currentPosition[0]; 102 this.activeTrip = currentPosition[0];
101 - if (this.settings.showPolygon) {  
102 - this.mapWidget.map.updatePolygons(this.interpolatedData); 103 + this.minTime = moment(this.historicalData[0][this.historicalData.length - 1]?.time).format('YYYY-MM-DD HH:mm:ss')
  104 + this.maxTime = moment(this.historicalData[0][0]?.time).format('YYYY-MM-DD HH:mm:ss')
  105 + this.calcLabel();
  106 + this.calcTooltip();
  107 + if (this.mapWidget) {
  108 + if (this.settings.showPolygon) {
  109 + this.mapWidget.map.updatePolygons(this.interpolatedData);
  110 + }
  111 + this.mapWidget.map.updateMarkers(currentPosition);
103 } 112 }
104 - this.mapWidget.map.updateMarkers(currentPosition);  
105 } 113 }
106 114
107 setActiveTrip() { 115 setActiveTrip() {
108 -  
109 } 116 }
110 117
111 calculateIntervals() { 118 calculateIntervals() {
112 this.historicalData.forEach((dataSource, index) => { 119 this.historicalData.forEach((dataSource, index) => {
113 this.intervals = []; 120 this.intervals = [];
  121 +
114 for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) { 122 for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) {
115 this.intervals.push(time); 123 this.intervals.push(time);
116 } 124 }
  125 +
117 this.intervals.push(dataSource[dataSource.length - 1]?.time); 126 this.intervals.push(dataSource[dataSource.length - 1]?.time);
118 this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals); 127 this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals);
119 }); 128 });
  129 +
120 } 130 }
121 131
122 - showHideTooltip() { 132 + calcTooltip() {
  133 + const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
123 const tooltipText: string = this.settings.useTooltipFunction ? 134 const tooltipText: string = this.settings.useTooltipFunction ?
124 - safeExecute(this.settings.tooolTipFunction, [this.activeTrip, this.historicalData, 0])  
125 - : this.settings.tooltipPattern; 135 + safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern;
  136 + this.mainTooltip = this.sanitizer.sanitize(
  137 + SecurityContext.HTML, (parseWithTranslation.parseTemplate(tooltipText, data, true)));
  138 + }
126 139
127 - this.mainTooltip = this.sanitizer.sanitize(SecurityContext.HTML, parseTemplate(tooltipText, this.activeTrip))  
128 - this.visibleTooltip = !this.visibleTooltip; 140 + calcLabel() {
  141 + const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
  142 + const labelText: string = this.settings.useLabelFunction ?
  143 + safeExecute(this.settings.labelFunction, [data, this.historicalData, 0]) : this.settings.label;
  144 + this.label = (parseWithTranslation.parseTemplate(labelText, data, true));
129 } 145 }
130 146
131 interpolateArray(originData, interpolatedIntervals) { 147 interpolateArray(originData, interpolatedIntervals) {
132 -  
133 const result = {}; 148 const result = {};
134 -  
135 for (let i = 1, j = 0; i < originData.length && j < interpolatedIntervals.length;) { 149 for (let i = 1, j = 0; i < originData.length && j < interpolatedIntervals.length;) {
136 const currentTime = interpolatedIntervals[j]; 150 const currentTime = interpolatedIntervals[j];
137 while (originData[i].time < currentTime) i++; 151 while (originData[i].time < currentTime) i++;
@@ -16,15 +16,21 @@ @@ -16,15 +16,21 @@
16 16
17 --> 17 -->
18 <div class="trip-animation-control-panel"> 18 <div class="trip-animation-control-panel">
19 - <div> 19 + <div fxFlex fxLayout="row" fxFlexAlign="center">
20 <button mat-icon-button class="mat-icon-button" aria-label="Start" (click)="moveStart()"> 20 <button mat-icon-button class="mat-icon-button" aria-label="Start" (click)="moveStart()">
21 <mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">fast_rewind</mat-icon> 21 <mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">fast_rewind</mat-icon>
22 </button> 22 </button>
23 <button mat-icon-button class="mat-icon-button" aria-label="Previous" (click)="movePrev()"> 23 <button mat-icon-button class="mat-icon-button" aria-label="Previous" (click)="movePrev()">
24 <mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">skip_previous</mat-icon> 24 <mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">skip_previous</mat-icon>
25 </button> 25 </button>
26 - <mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="changeIndex()">  
27 - </mat-slider> 26 + <div fxLayout="column" fxFlex="100">
  27 + <mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="changeIndex()">
  28 + </mat-slider>
  29 + <div class="panel-timer">
  30 + <span *ngIf="this.intervals[this.index]">{{ this.intervals[this.index] | date:'medium'}}</span>
  31 + <span *ngIf="!this.intervals[this.index]">{{ "widget.no-data-found" | translate}}</span>
  32 + </div>
  33 + </div>
28 <button mat-icon-button class="mat-icon-button" aria-label="Next" (click)="moveNext()"> 34 <button mat-icon-button class="mat-icon-button" aria-label="Next" (click)="moveNext()">
29 <mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">skip_next</mat-icon> 35 <mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">skip_next</mat-icon>
30 </button> 36 </button>
@@ -41,11 +47,8 @@ @@ -41,11 +47,8 @@
41 pause_circle_outline 47 pause_circle_outline
42 </mat-icon> 48 </mat-icon>
43 </button> 49 </button>
44 - <mat-select matInput [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select" aria-label="Speed selector">  
45 - <mat-option [value]="speedValue" flex="1" *ngFor="let speedValue of speeds">{{speedValue}} </mat-option> 50 + <mat-select matInput [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select"
  51 + aria-label="Speed selector">
  52 + <mat-option [value]="speedValue" *ngFor="let speedValue of speeds">{{speedValue}} </mat-option>
46 </mat-select> 53 </mat-select>
47 - </div>  
48 - <div class="panel-timer">  
49 - <span *ngIf="this.intervals[this.index]">{{ this.intervals[this.index] | date:'medium'}}</span>  
50 - <span *ngIf="!this.intervals[this.index]">{{ "widget.no-data-found" | translate}}</span>  
51 - </div>  
  54 + </div>
@@ -13,128 +13,120 @@ @@ -13,128 +13,120 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -.trip-animation-widget { 16 +
  17 +.trip-animation-label-container {
  18 + height: 24px;
  19 +}
  20 +
  21 +.trip-animation-container {
17 position: relative; 22 position: relative;
  23 + z-index: 1;
  24 + flex: 1;
18 width: 100%; 25 width: 100%;
19 - height: 100%;  
20 - font-size: 16px;  
21 - line-height: 24px;  
22 26
23 - .trip-animation-label-container {  
24 - height: 24px;  
25 - }  
26 -  
27 - .trip-animation-container {  
28 - position: relative; 27 + #trip-animation-map {
29 z-index: 1; 28 z-index: 1;
30 - flex: 1;  
31 width: 100%; 29 width: 100%;
  30 + height: 100%;
32 31
33 - #trip-animation-map {  
34 - z-index: 1;  
35 - width: 100%;  
36 - height: 100%;  
37 -  
38 - .pointsLayerMarkerIcon {  
39 - border-radius: 50%;  
40 - } 32 + .pointsLayerMarkerIcon {
  33 + border-radius: 50%;
41 } 34 }
  35 + }
  36 +
  37 + .trip-animation-info-panel {
  38 + position: absolute;
  39 + top: 0;
  40 + right: 0;
  41 + z-index: 2;
  42 + pointer-events: none;
42 43
43 - .trip-animation-info-panel {  
44 - position: absolute; 44 + .mat-button {
45 top: 0; 45 top: 0;
46 - right: 0;  
47 - z-index: 2;  
48 - pointer-events: none;  
49 -  
50 - .md-button {  
51 - top: 0;  
52 - left: 0;  
53 - width: 32px;  
54 - min-width: 32px;  
55 - height: 32px;  
56 - min-height: 32px;  
57 - padding: 0 0 2px;  
58 - margin: 2px;  
59 - line-height: 24px;  
60 -  
61 - ng-md-icon {  
62 - width: 24px;  
63 - height: 24px;  
64 -  
65 - svg {  
66 - width: inherit;  
67 - height: inherit;  
68 - } 46 + left: 0;
  47 + width: 32px;
  48 + min-width: 32px;
  49 + height: 32px;
  50 + min-height: 32px;
  51 + padding: 0 0 2px;
  52 + margin: 2px;
  53 + line-height: 24px;
  54 +
  55 + ng-mat-icon {
  56 + width: 24px;
  57 + height: 24px;
  58 +
  59 + svg {
  60 + width: inherit;
  61 + height: inherit;
69 } 62 }
70 } 63 }
71 } 64 }
  65 + }
72 66
73 - .trip-animation-tooltip {  
74 - position: absolute;  
75 - top: 38px;  
76 - right: 0;  
77 - z-index: 2;  
78 - padding: 10px;  
79 - background-color: #fff;  
80 - transition: 0.3s ease-in-out;  
81 -  
82 - &-hidden {  
83 - transform: translateX(110%);  
84 - } 67 + .trip-animation-tooltip {
  68 + position: absolute;
  69 + top: 38px;
  70 + right: 0;
  71 + z-index: 2;
  72 + padding: 10px;
  73 + background-color: #fff;
  74 + transition: 0.3s ease-in-out;
  75 +
  76 + &-hidden {
  77 + transform: translateX(110%);
85 } 78 }
86 } 79 }
  80 +}
87 81
88 - .trip-animation-control-panel {  
89 - position: relative;  
90 - box-sizing: border-box;  
91 - width: 100%;  
92 - padding-bottom: 16px;  
93 - padding-left: 10px; 82 +.trip-animation-control-panel {
  83 + position: relative;
  84 + box-sizing: border-box;
  85 + width: 100%;
  86 + padding-bottom: 16px;
  87 + padding-left: 10px;
94 88
95 - md-slider-container {  
96 - md-slider {  
97 - min-width: 80px;  
98 - } 89 + mat-slider-container {
  90 + mat-slider {
  91 + min-width: 80px;
  92 + }
99 93
100 - button.md-button.md-icon-button {  
101 - width: 44px;  
102 - min-width: 44px;  
103 - height: 44px;  
104 - min-height: 44px;  
105 - margin: 0;  
106 - line-height: 28px;  
107 -  
108 - md-icon {  
109 - width: 28px;  
110 - height: 28px;  
111 - font-size: 28px;  
112 -  
113 - svg {  
114 - width: inherit;  
115 - height: inherit;  
116 - } 94 + button.mat-button.mat-icon-button {
  95 + width: 44px;
  96 + min-width: 44px;
  97 + height: 44px;
  98 + min-height: 44px;
  99 + margin: 0;
  100 + line-height: 28px;
  101 +
  102 + mat-icon {
  103 + width: 28px;
  104 + height: 28px;
  105 + font-size: 28px;
  106 +
  107 + svg {
  108 + width: inherit;
  109 + height: inherit;
117 } 110 }
118 } 111 }
119 -  
120 - md-select {  
121 - margin: 0;  
122 - }  
123 } 112 }
124 113
125 - .panel-timer {  
126 - max-width: none;  
127 - padding-right: 250px;  
128 - padding-left: 90px;  
129 - margin-top: -20px;  
130 - font-size: 12px;  
131 - font-weight: 500;  
132 - text-align: center; 114 + mat-select {
  115 + margin: 0;
133 } 116 }
134 } 117 }
  118 +
  119 + .panel-timer {
  120 + max-width: none;
  121 + margin-top: -20px;
  122 + font-size: 12px;
  123 + font-weight: 500;
  124 + text-align: center;
  125 + }
135 } 126 }
136 127
137 -.speed-select{ 128 +.speed-select {
138 width: 50px; 129 width: 50px;
139 - margin-left: 20px; 130 + margin-left: 10px;
  131 + margin-top: 10px;
140 } 132 }
@@ -15,8 +15,8 @@ @@ -15,8 +15,8 @@
15 /// 15 ///
16 16
17 import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core'; 17 import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
18 -import { interval, Subscription, Subscriber, SubscriptionLike, Observer } from 'rxjs';  
19 -import { filter, tap } from 'rxjs/operators'; 18 +import { interval } from 'rxjs';
  19 +import { filter } from 'rxjs/operators';
20 import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/maps/map-models'; 20 import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/maps/map-models';
21 21
22 @Component({ 22 @Component({
@@ -15,11 +15,11 @@ @@ -15,11 +15,11 @@
15 /// 15 ///
16 16
17 import { Pipe, PipeTransform } from '@angular/core'; 17 import { Pipe, PipeTransform } from '@angular/core';
18 -import { parseTemplate } from '@app/core/utils'; 18 +import { parseTemplate, parseWithTranslation } from '@app/core/utils';
19 19
20 @Pipe({ name: 'tbParseTemplate' }) 20 @Pipe({ name: 'tbParseTemplate' })
21 export class TbTemplatePipe implements PipeTransform { 21 export class TbTemplatePipe implements PipeTransform {
22 transform(template, data): string { 22 transform(template, data): string {
23 - return parseTemplate(template, data); 23 + return parseWithTranslation.parseTemplate(template, data);
24 } 24 }
25 } 25 }