diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 9f21791ecd..ffd69b85c1 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -498,7 +498,7 @@ export function safeExecute(func: Function, params = []) { res = func(...params); } catch (err) { - console.log(err); + console.log('error in external function:', err); res = null; } } @@ -519,7 +519,7 @@ export function parseFunction(source: string, params: string[] = []): Function { return res; } -export function parseTemplate(template, data) { +export function parseTemplate(template: string, data: object) { let res = ''; try { let variables = ''; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index b2bc4951f5..0eee7e9caa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -27,7 +27,6 @@ import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; import { filter } from 'rxjs/operators'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; -import { string } from 'prop-types'; export default abstract class LeafletMap { @@ -43,7 +42,6 @@ export default abstract class LeafletMap { isMarketCluster; bounds: L.LatLngBounds; - constructor($container: HTMLElement, options: MapOptions) { this.options = options; } @@ -138,7 +136,7 @@ export default abstract class LeafletMap { } invalidateSize() { - this.map.invalidateSize(true); + this.map?.invalidateSize(true); } onResize() { @@ -160,7 +158,7 @@ export default abstract class LeafletMap { } } - ////Markers + //Markers updateMarkers(markersData) { markersData.forEach(data => { if (data.rotationAngle) { @@ -259,7 +257,6 @@ export default abstract class LeafletMap { createPolygon(data, dataSources, settings) { this.ready$.subscribe(() => { - //public map, coordinates, dataSources, settings, onClickListener? this.polygon = new Polygon(this.map, data, dataSources, settings); const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds()); if (bounds.isValid()) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index 3feaad9af6..e44d9d3c99 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -91,6 +91,15 @@ export class MapWidgetController implements MapWidgetInterface { initSettings(settings: any) { const functionParams = ['data', 'dsData', 'dsIndex']; this.provider = settings.provider ? settings.provider : this.mapProvider; + + function getDefCenterPosition(position) { + if (typeof (position) === 'string') + return position.split(','); + if (typeof (position) === 'object') + return position; + return [0, 0]; + } + const customOptions = { provider: this.provider, mapUrl: settings?.mapImageUrl, @@ -102,7 +111,7 @@ export class MapWidgetController implements MapWidgetInterface { labelColor: this.ctx.widgetConfig.color, tooltipPattern: settings.tooltipPattern || "${entityName}

Latitude: ${" + settings.latKeyName + ":7}
Longitude: ${" + settings.lngKeyName + ":7}", - defaultCenterPosition: settings?.defaultCenterPosition?.split(',') || [0, 0], + defaultCenterPosition: getDefCenterPosition(settings?.defaultCenterPosition), useDraggableMarker: true, currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? { url: settings.markerImage, @@ -137,7 +146,7 @@ export class MapWidgetController implements MapWidgetInterface { return {}; } - public static getProvidersSchema() { + public static getProvidersSchema() { return mergeSchemes([mapProviderSchema, ...Object.values(providerSets)?.map( setting => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]); @@ -246,5 +255,5 @@ const defaultSettings = { minZoomLevel: 16, credentials: '', markerClusteringSetting: null, - draggebleMarker: true + draggebleMarker: false } \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index ac1c756dbb..9bcdf758e8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -15,7 +15,6 @@ /// import L from 'leaflet'; -import { interpolateOnPointSegment } from 'leaflet-geometryutil'; import _ from 'lodash'; export function createTooltip(target, settings) { @@ -34,38 +33,3 @@ export function createTooltip(target, settings) { return popup; } - -export function interpolateArray(originData, interpolatedIntervals) { - - const getRatio = (firsMoment, secondMoment, intermediateMoment) => { - return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); - }; - - function findAngle(startPoint, endPoint) { - let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude); - angle = angle * 180 / Math.PI; - return parseInt(angle.toFixed(2)); - } - - const result = {}; - - for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) { - const currentTime = interpolatedIntervals[j]; - while (originData[i].time < currentTime) i++; - const before = originData[i - 1]; - const after = originData[i]; - const interpolation = interpolateOnPointSegment( - new L.Point(before.latitude, before.longitude), - new L.Point(after.latitude, after.longitude), - getRatio(before.time, after.time, currentTime)); - result[currentTime] = ({ - ...originData[i], - rotationAngle: findAngle(before, after), - latitude: interpolation.x, - longitude: interpolation.y - }); - j++; - } - - return result; -}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index 4f1dd78a50..0fef1a1046 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -56,7 +56,6 @@ export class Marker { if (onDragendListener) { this.leafletMarker.on('dragend', onDragendListener); } - } setDataSources(data, dataSources) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts index 6bbdfe1a54..29032c195f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts @@ -24,7 +24,6 @@ export class Polyline { data; constructor(private map: L.Map, locations, data, dataSources, settings) { - console.log("Polyline -> constructor -> data", data) this.dataSources = dataSources; this.data = data; this.leafletPoly = L.polyline(locations, @@ -34,7 +33,6 @@ export class Polyline { updatePolyline(settings, data, dataSources) { this.leafletPoly.setStyle(this.getPolyStyle(settings, data, dataSources)); - } getPolyStyle(settings, data, dataSources): L.PolylineOptions { diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html index 75f9a4017c..516c01d169 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html @@ -15,6 +15,23 @@ limitations under the License. --> -
- +
+
+ {{settings.label | tbParseTemplate: activeTrip}} +
+
+
+
+ +
+
+
+
+ +
\ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.scss b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.scss index c6405643d8..cf11bfd256 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.scss @@ -14,8 +14,76 @@ * limitations under the License. */ +.trip-animation-widget { + position: relative; + width: 100%; + height: 100%; + font-size: 16px; + line-height: 24px; + display: flex; + flex-direction: column; -.map{ + .trip-animation-label-container { + height: 24px; + } + + .trip-animation-container { + position: relative; + z-index: 1; + flex: 1; width: 100%; - height: calc(100% - 100px); -} \ No newline at end of file + + .map { + width: 100%; + height: 100%; + } + + .trip-animation-info-panel { + position: absolute; + top: 0; + right: 0; + pointer-events: none; + + .tooltip-button { + top: 0; + left: 0; + width: 32px; + min-width: 32px; + height: 32px; + min-height: 32px; + padding: 0 0 2px; + margin: 2px; + line-height: 24px; + z-index: 999; + + &::ng-deep .mat-button-wrapper { + padding: 0; + } + + mat-icon { + width: 24px; + height: 24px; + + svg { + width: inherit; + height: inherit; + } + } + } + } + + .trip-animation-tooltip { + position: absolute; + top: 38px; + right: 0; + z-index: 2; + padding: 10px; + background-color: #fff; + transition: 0.3s ease-in-out; + + &-hidden { + transform: translateX(110%); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts index 3b28a2f54d..7367319d47 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts @@ -14,15 +14,19 @@ /// limitations under the License. /// +import L from 'leaflet'; +import _ from 'lodash'; +import tinycolor from "tinycolor2"; +import { interpolateOnPointSegment } from 'leaflet-geometryutil'; + import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2'; import { MapProviders } from '../lib/maps/map-models'; import { parseArray } from '@app/core/utils'; -import { interpolateArray } from '../lib/maps/maps-utils'; -import tinycolor from "tinycolor2"; import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils'; import { tripAnimationSchema } from '../lib/maps/schemes'; + @Component({ selector: 'trip-animation', templateUrl: './trip-animation.component.html', @@ -41,6 +45,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { interpolatedData = []; widgetConfig; settings; + mainTooltip; + activeTrip; constructor(private cd: ChangeDetectorRef) { } @@ -57,19 +63,27 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]]; if (subscription) subscription.callbacks.onDataUpdated = (updated) => { this.historicalData = parseArray(this.ctx.data); + this.activeTrip = this.historicalData[0][0]; this.calculateIntervals(); this.timeUpdated(this.intervals[0]); - this.mapWidget.map.map.invalidateSize(); + this.mapWidget.map.map?.invalidateSize(); this.cd.detectChanges(); } } ngAfterViewInit() { - this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, this.ctx, this.mapContainer.nativeElement); + let ctxCopy = _.cloneDeep(this.ctx); + ctxCopy.settings.showLabel = false; + this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, ctxCopy, this.mapContainer.nativeElement); } timeUpdated(time) { const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]); + this.activeTrip = currentPosition[0]; + this.mapWidget.map.updatePolylines(this.interpolatedData); + if (this.settings.showPolygon) { + this.mapWidget.map.updatePolygons(this.interpolatedData); + } this.mapWidget.map.updateMarkers(currentPosition); } @@ -80,10 +94,47 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { this.intervals.push(time); } this.intervals.push(dataSource[dataSource.length - 1]?.time); - this.interpolatedData[index] = interpolateArray(dataSource, this.intervals); + this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals); }); } + showHideTooltip() { + } + + interpolateArray(originData, interpolatedIntervals) { + + const getRatio = (firsMoment, secondMoment, intermediateMoment) => { + return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); + }; + + function findAngle(startPoint, endPoint) { + let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude); + angle = angle * 180 / Math.PI; + return parseInt(angle.toFixed(2)); + } + + const result = {}; + + for (let i = 1, j = 0; i < originData.length, j < interpolatedIntervals.length;) { + const currentTime = interpolatedIntervals[j]; + while (originData[i].time < currentTime) i++; + const before = originData[i - 1]; + const after = originData[i]; + const interpolation = interpolateOnPointSegment( + new L.Point(before.latitude, before.longitude), + new L.Point(after.latitude, after.longitude), + getRatio(before.time, after.time, currentTime)); + result[currentTime] = ({ + ...originData[i], + rotationAngle: findAngle(before, after) + this.settings.rotationAngle, + latitude: interpolation.x, + longitude: interpolation.y + }); + j++; + } + return result; + }; + static getSettingsSchema() { let schema = initSchema(); addToSchema(schema, TbMapWidgetV2.getProvidersSchema()); diff --git a/ui-ngx/src/app/shared/pipe/public-api.ts b/ui-ngx/src/app/shared/pipe/public-api.ts index 4f801b7d51..72edfae70d 100644 --- a/ui-ngx/src/app/shared/pipe/public-api.ts +++ b/ui-ngx/src/app/shared/pipe/public-api.ts @@ -20,3 +20,4 @@ export * from './keyboard-shortcut.pipe'; export * from './milliseconds-to-time-string.pipe'; export * from './nospace.pipe'; export * from './truncate.pipe'; +export * from './template.pipe'; diff --git a/ui-ngx/src/app/shared/pipe/template.pipe.ts b/ui-ngx/src/app/shared/pipe/template.pipe.ts new file mode 100644 index 0000000000..1aa635e240 --- /dev/null +++ b/ui-ngx/src/app/shared/pipe/template.pipe.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Pipe, PipeTransform } from '@angular/core'; +import { parseTemplate } from '@app/core/utils'; + +@Pipe({ name: 'tbParseTemplate' }) +export class TbTemplatePipe implements PipeTransform { + transform(template, data): string { + console.log("TbTemplatePipe -> transform -> template, data", template, data) + return parseTemplate(template, data); + } +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index e4766e75b5..01f673d19e 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -128,6 +128,7 @@ import { LedLightComponent } from '@shared/components/led-light.component'; import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive"; import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component"; import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component'; +import { TbTemplatePipe } from './pipe/public-api'; @NgModule({ providers: [ @@ -208,6 +209,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his HighlightPipe, TruncatePipe, TbJsonPipe, + TbTemplatePipe, KeyboardShortcutPipe, TbJsonToStringDirective, JsonObjectEditDialogComponent, @@ -367,6 +369,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his HighlightPipe, TruncatePipe, TbJsonPipe, + TbTemplatePipe, KeyboardShortcutPipe, TranslateModule, JsonObjectEditDialogComponent,