Showing
12 changed files
with
193 additions
and
60 deletions
... | ... | @@ -498,7 +498,7 @@ export function safeExecute(func: Function, params = []) { |
498 | 498 | res = func(...params); |
499 | 499 | } |
500 | 500 | catch (err) { |
501 | - console.log(err); | |
501 | + console.log('error in external function:', err); | |
502 | 502 | res = null; |
503 | 503 | } |
504 | 504 | } |
... | ... | @@ -519,7 +519,7 @@ export function parseFunction(source: string, params: string[] = []): Function { |
519 | 519 | return res; |
520 | 520 | } |
521 | 521 | |
522 | -export function parseTemplate(template, data) { | |
522 | +export function parseTemplate(template: string, data: object) { | |
523 | 523 | let res = ''; |
524 | 524 | try { |
525 | 525 | let variables = ''; | ... | ... |
... | ... | @@ -27,7 +27,6 @@ import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; |
27 | 27 | import { filter } from 'rxjs/operators'; |
28 | 28 | import { Polyline } from './polyline'; |
29 | 29 | import { Polygon } from './polygon'; |
30 | -import { string } from 'prop-types'; | |
31 | 30 | |
32 | 31 | export default abstract class LeafletMap { |
33 | 32 | |
... | ... | @@ -43,7 +42,6 @@ export default abstract class LeafletMap { |
43 | 42 | isMarketCluster; |
44 | 43 | bounds: L.LatLngBounds; |
45 | 44 | |
46 | - | |
47 | 45 | constructor($container: HTMLElement, options: MapOptions) { |
48 | 46 | this.options = options; |
49 | 47 | } |
... | ... | @@ -138,7 +136,7 @@ export default abstract class LeafletMap { |
138 | 136 | } |
139 | 137 | |
140 | 138 | invalidateSize() { |
141 | - this.map.invalidateSize(true); | |
139 | + this.map?.invalidateSize(true); | |
142 | 140 | } |
143 | 141 | |
144 | 142 | onResize() { |
... | ... | @@ -160,7 +158,7 @@ export default abstract class LeafletMap { |
160 | 158 | } |
161 | 159 | } |
162 | 160 | |
163 | - ////Markers | |
161 | + //Markers | |
164 | 162 | updateMarkers(markersData) { |
165 | 163 | markersData.forEach(data => { |
166 | 164 | if (data.rotationAngle) { |
... | ... | @@ -259,7 +257,6 @@ export default abstract class LeafletMap { |
259 | 257 | |
260 | 258 | createPolygon(data, dataSources, settings) { |
261 | 259 | this.ready$.subscribe(() => { |
262 | - //public map, coordinates, dataSources, settings, onClickListener? | |
263 | 260 | this.polygon = new Polygon(this.map, data, dataSources, settings); |
264 | 261 | const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds()); |
265 | 262 | if (bounds.isValid()) { | ... | ... |
... | ... | @@ -91,6 +91,15 @@ export class MapWidgetController implements MapWidgetInterface { |
91 | 91 | initSettings(settings: any) { |
92 | 92 | const functionParams = ['data', 'dsData', 'dsIndex']; |
93 | 93 | this.provider = settings.provider ? settings.provider : this.mapProvider; |
94 | + | |
95 | + function getDefCenterPosition(position) { | |
96 | + if (typeof (position) === 'string') | |
97 | + return position.split(','); | |
98 | + if (typeof (position) === 'object') | |
99 | + return position; | |
100 | + return [0, 0]; | |
101 | + } | |
102 | + | |
94 | 103 | const customOptions = { |
95 | 104 | provider: this.provider, |
96 | 105 | mapUrl: settings?.mapImageUrl, |
... | ... | @@ -102,7 +111,7 @@ export class MapWidgetController implements MapWidgetInterface { |
102 | 111 | labelColor: this.ctx.widgetConfig.color, |
103 | 112 | tooltipPattern: settings.tooltipPattern || |
104 | 113 | "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${" + settings.latKeyName + ":7}<br/><b>Longitude:</b> ${" + settings.lngKeyName + ":7}", |
105 | - defaultCenterPosition: settings?.defaultCenterPosition?.split(',') || [0, 0], | |
114 | + defaultCenterPosition: getDefCenterPosition(settings?.defaultCenterPosition), | |
106 | 115 | useDraggableMarker: true, |
107 | 116 | currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? { |
108 | 117 | url: settings.markerImage, |
... | ... | @@ -137,7 +146,7 @@ export class MapWidgetController implements MapWidgetInterface { |
137 | 146 | return {}; |
138 | 147 | } |
139 | 148 | |
140 | - public static getProvidersSchema() { | |
149 | + public static getProvidersSchema() { | |
141 | 150 | return mergeSchemes([mapProviderSchema, |
142 | 151 | ...Object.values(providerSets)?.map( |
143 | 152 | setting => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]); |
... | ... | @@ -246,5 +255,5 @@ const defaultSettings = { |
246 | 255 | minZoomLevel: 16, |
247 | 256 | credentials: '', |
248 | 257 | markerClusteringSetting: null, |
249 | - draggebleMarker: true | |
258 | + draggebleMarker: false | |
250 | 259 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -15,7 +15,6 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 17 | import L from 'leaflet'; |
18 | -import { interpolateOnPointSegment } from 'leaflet-geometryutil'; | |
19 | 18 | import _ from 'lodash'; |
20 | 19 | |
21 | 20 | export function createTooltip(target, settings) { |
... | ... | @@ -34,38 +33,3 @@ export function createTooltip(target, settings) { |
34 | 33 | return popup; |
35 | 34 | } |
36 | 35 | |
37 | - | |
38 | -export function interpolateArray(originData, interpolatedIntervals) { | |
39 | - | |
40 | - const getRatio = (firsMoment, secondMoment, intermediateMoment) => { | |
41 | - return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); | |
42 | - }; | |
43 | - | |
44 | - function findAngle(startPoint, endPoint) { | |
45 | - let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude); | |
46 | - angle = angle * 180 / Math.PI; | |
47 | - return parseInt(angle.toFixed(2)); | |
48 | - } | |
49 | - | |
50 | - const result = {}; | |
51 | - | |
52 | - for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) { | |
53 | - const currentTime = interpolatedIntervals[j]; | |
54 | - while (originData[i].time < currentTime) i++; | |
55 | - const before = originData[i - 1]; | |
56 | - const after = originData[i]; | |
57 | - const interpolation = interpolateOnPointSegment( | |
58 | - new L.Point(before.latitude, before.longitude), | |
59 | - new L.Point(after.latitude, after.longitude), | |
60 | - getRatio(before.time, after.time, currentTime)); | |
61 | - result[currentTime] = ({ | |
62 | - ...originData[i], | |
63 | - rotationAngle: findAngle(before, after), | |
64 | - latitude: interpolation.x, | |
65 | - longitude: interpolation.y | |
66 | - }); | |
67 | - j++; | |
68 | - } | |
69 | - | |
70 | - return result; | |
71 | -}; | ... | ... |
... | ... | @@ -24,7 +24,6 @@ export class Polyline { |
24 | 24 | data; |
25 | 25 | |
26 | 26 | constructor(private map: L.Map, locations, data, dataSources, settings) { |
27 | - console.log("Polyline -> constructor -> data", data) | |
28 | 27 | this.dataSources = dataSources; |
29 | 28 | this.data = data; |
30 | 29 | this.leafletPoly = L.polyline(locations, |
... | ... | @@ -34,7 +33,6 @@ export class Polyline { |
34 | 33 | |
35 | 34 | updatePolyline(settings, data, dataSources) { |
36 | 35 | this.leafletPoly.setStyle(this.getPolyStyle(settings, data, dataSources)); |
37 | - | |
38 | 36 | } |
39 | 37 | |
40 | 38 | getPolyStyle(settings, data, dataSources): L.PolylineOptions { | ... | ... |
... | ... | @@ -15,6 +15,23 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div class="map" #map ></div> | |
19 | -<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals" | |
20 | - (onTimeUpdated)="timeUpdated($event)"></tb-history-selector> | |
18 | +<div class="trip-animation-widget"> | |
19 | + <div class="trip-animation-label-container" *ngIf="settings.showLabel"> | |
20 | + {{settings.label | tbParseTemplate: activeTrip}} | |
21 | + </div> | |
22 | + <div class="trip-animation-container" layout="column"> | |
23 | + <div class="map" #map></div> | |
24 | + <div class="trip-animation-info-panel" layout="row"> | |
25 | + <button class="tooltip-button" mat-mini-fab color="primary" aria-label="tooltip" | |
26 | + *ngIf="settings.showTooltip" (click)="showHideTooltip()"> | |
27 | + <mat-icon>info_outline</mat-icon> | |
28 | + </button> | |
29 | + </div> | |
30 | + <div class="trip-animation-tooltip md-whiteframe-z4" layout="column" | |
31 | + [ngClass]="{ 'trip-animation-tooltip-hidden':!settings.showTooltip}" [innerHTML]="mainTooltip" | |
32 | + [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}"> | |
33 | + </div> | |
34 | + </div> | |
35 | + <tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals" | |
36 | + (onTimeUpdated)="timeUpdated($event)"></tb-history-selector> | |
37 | +</div> | |
\ No newline at end of file | ... | ... |
... | ... | @@ -14,8 +14,76 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | |
17 | +.trip-animation-widget { | |
18 | + position: relative; | |
19 | + width: 100%; | |
20 | + height: 100%; | |
21 | + font-size: 16px; | |
22 | + line-height: 24px; | |
23 | + display: flex; | |
24 | + flex-direction: column; | |
17 | 25 | |
18 | -.map{ | |
26 | + .trip-animation-label-container { | |
27 | + height: 24px; | |
28 | + } | |
29 | + | |
30 | + .trip-animation-container { | |
31 | + position: relative; | |
32 | + z-index: 1; | |
33 | + flex: 1; | |
19 | 34 | width: 100%; |
20 | - height: calc(100% - 100px); | |
21 | -} | |
\ No newline at end of file | ||
35 | + | |
36 | + .map { | |
37 | + width: 100%; | |
38 | + height: 100%; | |
39 | + } | |
40 | + | |
41 | + .trip-animation-info-panel { | |
42 | + position: absolute; | |
43 | + top: 0; | |
44 | + right: 0; | |
45 | + pointer-events: none; | |
46 | + | |
47 | + .tooltip-button { | |
48 | + top: 0; | |
49 | + left: 0; | |
50 | + width: 32px; | |
51 | + min-width: 32px; | |
52 | + height: 32px; | |
53 | + min-height: 32px; | |
54 | + padding: 0 0 2px; | |
55 | + margin: 2px; | |
56 | + line-height: 24px; | |
57 | + z-index: 999; | |
58 | + | |
59 | + &::ng-deep .mat-button-wrapper { | |
60 | + padding: 0; | |
61 | + } | |
62 | + | |
63 | + mat-icon { | |
64 | + width: 24px; | |
65 | + height: 24px; | |
66 | + | |
67 | + svg { | |
68 | + width: inherit; | |
69 | + height: inherit; | |
70 | + } | |
71 | + } | |
72 | + } | |
73 | + } | |
74 | + | |
75 | + .trip-animation-tooltip { | |
76 | + position: absolute; | |
77 | + top: 38px; | |
78 | + right: 0; | |
79 | + z-index: 2; | |
80 | + padding: 10px; | |
81 | + background-color: #fff; | |
82 | + transition: 0.3s ease-in-out; | |
83 | + | |
84 | + &-hidden { | |
85 | + transform: translateX(110%); | |
86 | + } | |
87 | + } | |
88 | + } | |
89 | +} | ... | ... |
... | ... | @@ -14,15 +14,19 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | +import L from 'leaflet'; | |
18 | +import _ from 'lodash'; | |
19 | +import tinycolor from "tinycolor2"; | |
20 | +import { interpolateOnPointSegment } from 'leaflet-geometryutil'; | |
21 | + | |
17 | 22 | import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; |
18 | 23 | import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2'; |
19 | 24 | import { MapProviders } from '../lib/maps/map-models'; |
20 | 25 | import { parseArray } from '@app/core/utils'; |
21 | -import { interpolateArray } from '../lib/maps/maps-utils'; | |
22 | -import tinycolor from "tinycolor2"; | |
23 | 26 | import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils'; |
24 | 27 | import { tripAnimationSchema } from '../lib/maps/schemes'; |
25 | 28 | |
29 | + | |
26 | 30 | @Component({ |
27 | 31 | selector: 'trip-animation', |
28 | 32 | templateUrl: './trip-animation.component.html', |
... | ... | @@ -41,6 +45,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { |
41 | 45 | interpolatedData = []; |
42 | 46 | widgetConfig; |
43 | 47 | settings; |
48 | + mainTooltip; | |
49 | + activeTrip; | |
44 | 50 | |
45 | 51 | constructor(private cd: ChangeDetectorRef) { } |
46 | 52 | |
... | ... | @@ -57,19 +63,27 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { |
57 | 63 | let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]]; |
58 | 64 | if (subscription) subscription.callbacks.onDataUpdated = (updated) => { |
59 | 65 | this.historicalData = parseArray(this.ctx.data); |
66 | + this.activeTrip = this.historicalData[0][0]; | |
60 | 67 | this.calculateIntervals(); |
61 | 68 | this.timeUpdated(this.intervals[0]); |
62 | - this.mapWidget.map.map.invalidateSize(); | |
69 | + this.mapWidget.map.map?.invalidateSize(); | |
63 | 70 | this.cd.detectChanges(); |
64 | 71 | } |
65 | 72 | } |
66 | 73 | |
67 | 74 | ngAfterViewInit() { |
68 | - this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement); | |
75 | + let ctxCopy = _.cloneDeep(this.ctx); | |
76 | + ctxCopy.settings.showLabel = false; | |
77 | + this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, ctxCopy, this.mapContainer.nativeElement); | |
69 | 78 | } |
70 | 79 | |
71 | 80 | timeUpdated(time) { |
72 | 81 | const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]); |
82 | + this.activeTrip = currentPosition[0]; | |
83 | + this.mapWidget.map.updatePolylines(this.interpolatedData); | |
84 | + if (this.settings.showPolygon) { | |
85 | + this.mapWidget.map.updatePolygons(this.interpolatedData); | |
86 | + } | |
73 | 87 | this.mapWidget.map.updateMarkers(currentPosition); |
74 | 88 | } |
75 | 89 | |
... | ... | @@ -80,10 +94,47 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { |
80 | 94 | this.intervals.push(time); |
81 | 95 | } |
82 | 96 | this.intervals.push(dataSource[dataSource.length - 1]?.time); |
83 | - this.interpolatedData[index] = interpolateArray(dataSource, this.intervals); | |
97 | + this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals); | |
84 | 98 | }); |
85 | 99 | } |
86 | 100 | |
101 | + showHideTooltip() { | |
102 | + } | |
103 | + | |
104 | + interpolateArray(originData, interpolatedIntervals) { | |
105 | + | |
106 | + const getRatio = (firsMoment, secondMoment, intermediateMoment) => { | |
107 | + return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); | |
108 | + }; | |
109 | + | |
110 | + function findAngle(startPoint, endPoint) { | |
111 | + let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude); | |
112 | + angle = angle * 180 / Math.PI; | |
113 | + return parseInt(angle.toFixed(2)); | |
114 | + } | |
115 | + | |
116 | + const result = {}; | |
117 | + | |
118 | + for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) { | |
119 | + const currentTime = interpolatedIntervals[j]; | |
120 | + while (originData[i].time < currentTime) i++; | |
121 | + const before = originData[i - 1]; | |
122 | + const after = originData[i]; | |
123 | + const interpolation = interpolateOnPointSegment( | |
124 | + new L.Point(before.latitude, before.longitude), | |
125 | + new L.Point(after.latitude, after.longitude), | |
126 | + getRatio(before.time, after.time, currentTime)); | |
127 | + result[currentTime] = ({ | |
128 | + ...originData[i], | |
129 | + rotationAngle: findAngle(before, after) + this.settings.rotationAngle, | |
130 | + latitude: interpolation.x, | |
131 | + longitude: interpolation.y | |
132 | + }); | |
133 | + j++; | |
134 | + } | |
135 | + return result; | |
136 | + }; | |
137 | + | |
87 | 138 | static getSettingsSchema() { |
88 | 139 | let schema = initSchema(); |
89 | 140 | addToSchema(schema, TbMapWidgetV2.getProvidersSchema()); | ... | ... |
ui-ngx/src/app/shared/pipe/template.pipe.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Pipe, PipeTransform } from '@angular/core'; | |
18 | +import { parseTemplate } from '@app/core/utils'; | |
19 | + | |
20 | +@Pipe({ name: 'tbParseTemplate' }) | |
21 | +export class TbTemplatePipe implements PipeTransform { | |
22 | + transform(template, data): string { | |
23 | + console.log("TbTemplatePipe -> transform -> template, data", template, data) | |
24 | + return parseTemplate(template, data); | |
25 | + } | |
26 | +} | ... | ... |
... | ... | @@ -128,6 +128,7 @@ import { LedLightComponent } from '@shared/components/led-light.component'; |
128 | 128 | import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive"; |
129 | 129 | import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component"; |
130 | 130 | import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component'; |
131 | +import { TbTemplatePipe } from './pipe/public-api'; | |
131 | 132 | |
132 | 133 | @NgModule({ |
133 | 134 | providers: [ |
... | ... | @@ -208,6 +209,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his |
208 | 209 | HighlightPipe, |
209 | 210 | TruncatePipe, |
210 | 211 | TbJsonPipe, |
212 | + TbTemplatePipe, | |
211 | 213 | KeyboardShortcutPipe, |
212 | 214 | TbJsonToStringDirective, |
213 | 215 | JsonObjectEditDialogComponent, |
... | ... | @@ -367,6 +369,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his |
367 | 369 | HighlightPipe, |
368 | 370 | TruncatePipe, |
369 | 371 | TbJsonPipe, |
372 | + TbTemplatePipe, | |
370 | 373 | KeyboardShortcutPipe, |
371 | 374 | TranslateModule, |
372 | 375 | JsonObjectEditDialogComponent, | ... | ... |