add label to trip animation
This commit is contained in:
parent
78c0e519d1
commit
c04a86e914
@ -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 = '';
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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 ||
|
||||
"<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,
|
||||
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
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -56,7 +56,6 @@ export class Marker {
|
||||
if (onDragendListener) {
|
||||
this.leafletMarker.on('dragend', onDragendListener);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setDataSources(data, dataSources) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -15,6 +15,23 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div class="map" #map ></div>
|
||||
<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals"
|
||||
(onTimeUpdated)="timeUpdated($event)"></tb-history-selector>
|
||||
<div class="trip-animation-widget">
|
||||
<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>
|
||||
</div>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
.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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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';
|
||||
|
||||
26
ui-ngx/src/app/shared/pipe/template.pipe.ts
Normal file
26
ui-ngx/src/app/shared/pipe/template.pipe.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user