add label to trip animation

This commit is contained in:
Artem Halushko 2020-03-17 16:56:18 +02:00
parent 78c0e519d1
commit c04a86e914
12 changed files with 193 additions and 60 deletions

View File

@ -498,7 +498,7 @@ export function safeExecute(func: Function, params = []) {
res = func(...params); res = func(...params);
} }
catch (err) { catch (err) {
console.log(err); console.log('error in external function:', err);
res = null; res = null;
} }
} }
@ -519,7 +519,7 @@ export function parseFunction(source: string, params: string[] = []): Function {
return res; return res;
} }
export function parseTemplate(template, data) { export function parseTemplate(template: string, data: object) {
let res = ''; let res = '';
try { try {
let variables = ''; let variables = '';

View File

@ -27,7 +27,6 @@ import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { Polyline } from './polyline'; import { Polyline } from './polyline';
import { Polygon } from './polygon'; import { Polygon } from './polygon';
import { string } from 'prop-types';
export default abstract class LeafletMap { export default abstract class LeafletMap {
@ -43,7 +42,6 @@ export default abstract class LeafletMap {
isMarketCluster; isMarketCluster;
bounds: L.LatLngBounds; bounds: L.LatLngBounds;
constructor($container: HTMLElement, options: MapOptions) { constructor($container: HTMLElement, options: MapOptions) {
this.options = options; this.options = options;
} }
@ -138,7 +136,7 @@ export default abstract class LeafletMap {
} }
invalidateSize() { invalidateSize() {
this.map.invalidateSize(true); this.map?.invalidateSize(true);
} }
onResize() { onResize() {
@ -160,7 +158,7 @@ export default abstract class LeafletMap {
} }
} }
////Markers //Markers
updateMarkers(markersData) { updateMarkers(markersData) {
markersData.forEach(data => { markersData.forEach(data => {
if (data.rotationAngle) { if (data.rotationAngle) {
@ -259,7 +257,6 @@ export default abstract class LeafletMap {
createPolygon(data, dataSources, settings) { createPolygon(data, dataSources, settings) {
this.ready$.subscribe(() => { this.ready$.subscribe(() => {
//public map, coordinates, dataSources, settings, onClickListener?
this.polygon = new Polygon(this.map, data, dataSources, settings); this.polygon = new Polygon(this.map, data, dataSources, settings);
const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds()); const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds());
if (bounds.isValid()) { if (bounds.isValid()) {

View File

@ -91,6 +91,15 @@ export class MapWidgetController implements MapWidgetInterface {
initSettings(settings: any) { initSettings(settings: any) {
const functionParams = ['data', 'dsData', 'dsIndex']; const functionParams = ['data', 'dsData', 'dsIndex'];
this.provider = settings.provider ? settings.provider : this.mapProvider; 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 = { const customOptions = {
provider: this.provider, provider: this.provider,
mapUrl: settings?.mapImageUrl, mapUrl: settings?.mapImageUrl,
@ -102,7 +111,7 @@ export class MapWidgetController implements MapWidgetInterface {
labelColor: this.ctx.widgetConfig.color, labelColor: this.ctx.widgetConfig.color,
tooltipPattern: settings.tooltipPattern || tooltipPattern: settings.tooltipPattern ||
"<b>${entityName}</b><br/><br/><b>Latitude:</b> ${" + settings.latKeyName + ":7}<br/><b>Longitude:</b> ${" + settings.lngKeyName + ":7}", "<b>${entityName}</b><br/><br/><b>Latitude:</b> ${" + settings.latKeyName + ":7}<br/><b>Longitude:</b> ${" + settings.lngKeyName + ":7}",
defaultCenterPosition: settings?.defaultCenterPosition?.split(',') || [0, 0], defaultCenterPosition: getDefCenterPosition(settings?.defaultCenterPosition),
useDraggableMarker: true, useDraggableMarker: true,
currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? { currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? {
url: settings.markerImage, url: settings.markerImage,
@ -246,5 +255,5 @@ const defaultSettings = {
minZoomLevel: 16, minZoomLevel: 16,
credentials: '', credentials: '',
markerClusteringSetting: null, markerClusteringSetting: null,
draggebleMarker: true draggebleMarker: false
} }

View File

@ -15,7 +15,6 @@
/// ///
import L from 'leaflet'; import L from 'leaflet';
import { interpolateOnPointSegment } from 'leaflet-geometryutil';
import _ from 'lodash'; import _ from 'lodash';
export function createTooltip(target, settings) { export function createTooltip(target, settings) {
@ -34,38 +33,3 @@ export function createTooltip(target, settings) {
return popup; 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;
};

View File

@ -56,7 +56,6 @@ export class Marker {
if (onDragendListener) { if (onDragendListener) {
this.leafletMarker.on('dragend', onDragendListener); this.leafletMarker.on('dragend', onDragendListener);
} }
} }
setDataSources(data, dataSources) { setDataSources(data, dataSources) {

View File

@ -24,7 +24,6 @@ export class Polyline {
data; data;
constructor(private map: L.Map, locations, data, dataSources, settings) { constructor(private map: L.Map, locations, data, dataSources, settings) {
console.log("Polyline -> constructor -> data", data)
this.dataSources = dataSources; this.dataSources = dataSources;
this.data = data; this.data = data;
this.leafletPoly = L.polyline(locations, this.leafletPoly = L.polyline(locations,
@ -34,7 +33,6 @@ export class Polyline {
updatePolyline(settings, data, dataSources) { updatePolyline(settings, data, dataSources) {
this.leafletPoly.setStyle(this.getPolyStyle(settings, data, dataSources)); this.leafletPoly.setStyle(this.getPolyStyle(settings, data, dataSources));
} }
getPolyStyle(settings, data, dataSources): L.PolylineOptions { getPolyStyle(settings, data, dataSources): L.PolylineOptions {

View File

@ -15,6 +15,23 @@
limitations under the License. limitations under the License.
--> -->
<div class="map" #map ></div> <div class="trip-animation-widget">
<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals" <div class="trip-animation-label-container" *ngIf="settings.showLabel">
{{settings.label | tbParseTemplate: activeTrip}}
</div>
<div class="trip-animation-container" layout="column">
<div class="map" #map></div>
<div class="trip-animation-info-panel" layout="row">
<button class="tooltip-button" mat-mini-fab color="primary" aria-label="tooltip"
*ngIf="settings.showTooltip" (click)="showHideTooltip()">
<mat-icon>info_outline</mat-icon>
</button>
</div>
<div class="trip-animation-tooltip md-whiteframe-z4" layout="column"
[ngClass]="{ 'trip-animation-tooltip-hidden':!settings.showTooltip}" [innerHTML]="mainTooltip"
[ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
</div>
</div>
<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals"
(onTimeUpdated)="timeUpdated($event)"></tb-history-selector> (onTimeUpdated)="timeUpdated($event)"></tb-history-selector>
</div>

View File

@ -14,8 +14,76 @@
* limitations under the License. * limitations under the License.
*/ */
.trip-animation-widget {
.map{ position: relative;
width: 100%; width: 100%;
height: calc(100% - 100px); height: 100%;
font-size: 16px;
line-height: 24px;
display: flex;
flex-direction: column;
.trip-animation-label-container {
height: 24px;
}
.trip-animation-container {
position: relative;
z-index: 1;
flex: 1;
width: 100%;
.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%);
}
}
}
} }

View File

@ -14,15 +14,19 @@
/// limitations under the License. /// 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 { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core';
import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2'; import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
import { MapProviders } from '../lib/maps/map-models'; import { MapProviders } from '../lib/maps/map-models';
import { parseArray } from '@app/core/utils'; 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 { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils';
import { tripAnimationSchema } from '../lib/maps/schemes'; import { tripAnimationSchema } from '../lib/maps/schemes';
@Component({ @Component({
selector: 'trip-animation', selector: 'trip-animation',
templateUrl: './trip-animation.component.html', templateUrl: './trip-animation.component.html',
@ -41,6 +45,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
interpolatedData = []; interpolatedData = [];
widgetConfig; widgetConfig;
settings; settings;
mainTooltip;
activeTrip;
constructor(private cd: ChangeDetectorRef) { } 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]]; let subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
if (subscription) subscription.callbacks.onDataUpdated = (updated) => { if (subscription) subscription.callbacks.onDataUpdated = (updated) => {
this.historicalData = parseArray(this.ctx.data); this.historicalData = parseArray(this.ctx.data);
this.activeTrip = this.historicalData[0][0];
this.calculateIntervals(); this.calculateIntervals();
this.timeUpdated(this.intervals[0]); this.timeUpdated(this.intervals[0]);
this.mapWidget.map.map.invalidateSize(); this.mapWidget.map.map?.invalidateSize();
this.cd.detectChanges(); this.cd.detectChanges();
} }
} }
ngAfterViewInit() { 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) { timeUpdated(time) {
const currentPosition = this.interpolatedData.map(dataSource => dataSource[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); this.mapWidget.map.updateMarkers(currentPosition);
} }
@ -80,10 +94,47 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
this.intervals.push(time); this.intervals.push(time);
} }
this.intervals.push(dataSource[dataSource.length - 1]?.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() { static getSettingsSchema() {
let schema = initSchema(); let schema = initSchema();
addToSchema(schema, TbMapWidgetV2.getProvidersSchema()); addToSchema(schema, TbMapWidgetV2.getProvidersSchema());

View File

@ -20,3 +20,4 @@ export * from './keyboard-shortcut.pipe';
export * from './milliseconds-to-time-string.pipe'; export * from './milliseconds-to-time-string.pipe';
export * from './nospace.pipe'; export * from './nospace.pipe';
export * from './truncate.pipe'; export * from './truncate.pipe';
export * from './template.pipe';

View File

@ -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);
}
}

View File

@ -128,6 +128,7 @@ import { LedLightComponent } from '@shared/components/led-light.component';
import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive"; import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive";
import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component"; import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component";
import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component'; import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component';
import { TbTemplatePipe } from './pipe/public-api';
@NgModule({ @NgModule({
providers: [ providers: [
@ -208,6 +209,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his
HighlightPipe, HighlightPipe,
TruncatePipe, TruncatePipe,
TbJsonPipe, TbJsonPipe,
TbTemplatePipe,
KeyboardShortcutPipe, KeyboardShortcutPipe,
TbJsonToStringDirective, TbJsonToStringDirective,
JsonObjectEditDialogComponent, JsonObjectEditDialogComponent,
@ -367,6 +369,7 @@ import { HistorySelectorComponent } from './components/time/history-selector/his
HighlightPipe, HighlightPipe,
TruncatePipe, TruncatePipe,
TbJsonPipe, TbJsonPipe,
TbTemplatePipe,
KeyboardShortcutPipe, KeyboardShortcutPipe,
TranslateModule, TranslateModule,
JsonObjectEditDialogComponent, JsonObjectEditDialogComponent,