Merge pull request #2785 from vvlladd28/improvement/3.0/trip-animation

Improvement trip animation
This commit is contained in:
Igor Kulikov 2020-05-18 11:08:33 +03:00 committed by GitHub
commit 5c667ba7fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 209 additions and 168 deletions

View File

@ -130,7 +130,7 @@
"controllerScript": " self.onInit = function() {\n var $scope = self.ctx.$scope;\n $scope.self = self;\n }\n \n \n self.actionSources = function () {\n return {\n 'tooltipAction': {\n name: 'widget-action.tooltip-tag-action',\n multiple: false\n }\n }\n };\n \n self.getSettingsSchema = function() {\n return TbTripAnimationWidget.getSettingsSchema();\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 0 : value + 2)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 1 : value + 2)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"history\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":500}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"mapProvider\":\"OpenStreetMap.Mapnik\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"showTooltip\":true,\"tooltipColor\":\"#fff\",\"tooltipFontColor\":\"#000\",\"tooltipOpacity\":1,\"tooltipPattern\":\"<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}<br/><b>End Time:</b> ${maxTime}<br/><b>Start Time:</b> ${minTime}\",\"strokeWeight\":2,\"strokeOpacity\":1,\"pointSize\":10,\"markerImageSize\":34,\"rotationAngle\":180,\"provider\":\"openstreet-map\",\"normalizationStep\":1000,\"polKeyName\":\"coordinates\",\"decoratorSymbol\":\"arrowHead\",\"decoratorSymbolSize\":10,\"decoratorCustomColor\":\"#000\",\"decoratorOffset\":\"20px\",\"endDecoratorOffset\":\"20px\",\"decoratorRepeat\":\"20px\",\"polygonTooltipPattern\":\"<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"pointTooltipOnRightPanel\":true,\"autocloseTooltip\":true},\"title\":\"Trip Animation\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":false,\"showLegend\":false,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false},\"displayTimewindow\":true}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 0 : (value + 2) % gpsData.length)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 1 : (value + 2) % gpsData.length)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"history\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":500}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"mapProvider\":\"OpenStreetMap.Mapnik\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"showTooltip\":true,\"tooltipColor\":\"#fff\",\"tooltipFontColor\":\"#000\",\"tooltipOpacity\":1,\"tooltipPattern\":\"<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}<br/><b>End Time:</b> ${maxTime}<br/><b>Start Time:</b> ${minTime}\",\"strokeWeight\":2,\"strokeOpacity\":1,\"pointSize\":10,\"markerImageSize\":34,\"rotationAngle\":180,\"provider\":\"openstreet-map\",\"normalizationStep\":1000,\"polKeyName\":\"coordinates\",\"decoratorSymbol\":\"arrowHead\",\"decoratorSymbolSize\":10,\"decoratorCustomColor\":\"#000\",\"decoratorOffset\":\"20px\",\"endDecoratorOffset\":\"20px\",\"decoratorRepeat\":\"20px\",\"polygonTooltipPattern\":\"<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"pointTooltipOnRightPanel\":true,\"autocloseTooltip\":true},\"title\":\"Trip Animation\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":false,\"showLegend\":false,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false},\"displayTimewindow\":true}"
}
},
{
@ -150,4 +150,4 @@
}
}
]
}
}

View File

@ -7969,14 +7969,6 @@
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.6.0.tgz",
"integrity": "sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ=="
},
"leaflet-geometryutil": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/leaflet-geometryutil/-/leaflet-geometryutil-0.9.3.tgz",
"integrity": "sha512-Wi6YvfNx/Xu9q35AEfXpsUXmIFLen/MO+C2qimxHRnjyeyOxBhdcZa6kSiReaOX0cGK7yQInqrzz0dkIqZ8Dpg==",
"requires": {
"leaflet": ">=0.7.0"
}
},
"leaflet-polylinedecorator": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.0.tgz",

View File

@ -58,7 +58,6 @@
"jstree-bootstrap-theme": "^1.0.1",
"jszip": "^3.4.0",
"leaflet": "^1.6.0",
"leaflet-geometryutil": "^0.9.3",
"leaflet-polylinedecorator": "^1.6.0",
"leaflet-providers": "^1.9.1",
"leaflet.gridlayer.googlemutant": "0.8.0",

View File

@ -14,22 +14,28 @@
/// limitations under the License.
///
import { MapProviders, UnitedMapSettings, providerSets, hereProviders, defaultSettings } from './map-models';
import { defaultSettings, hereProviders, MapProviders, providerSets, UnitedMapSettings } from './map-models';
import LeafletMap from './leaflet-map';
import {
commonMapSettingsSchema,
routeMapSettingsSchema,
markerClusteringSettingsSchema,
markerClusteringSettingsSchemaLeaflet,
mapProviderSchema,
mapPolygonSchema
commonMapSettingsSchema,
mapPolygonSchema,
mapProviderSchema,
markerClusteringSettingsSchema,
markerClusteringSettingsSchemaLeaflet,
routeMapSettingsSchema
} from './schemes';
import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils';
import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface';
import { addCondition, addGroupInfo, addToSchema, initSchema, mergeSchemes } from '@core/schema-utils';
import { of, Subject } from 'rxjs';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { getDefCenterPosition, parseArray, parseData, parseFunction, parseWithTranslation } from './maps-utils';
import { JsonSettingsSchema, WidgetActionDescriptor, DatasourceType, widgetType, Datasource } from '@shared/models/widget.models';
import {
Datasource,
DatasourceType,
JsonSettingsSchema,
WidgetActionDescriptor,
widgetType
} from '@shared/models/widget.models';
import { EntityId } from '@shared/models/id/entity-id';
import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
import { AttributeService } from '@core/http/attribute.service';
@ -39,7 +45,13 @@ import { UtilsService } from '@core/services/utils.service';
// @dynamic
export class MapWidgetController implements MapWidgetInterface {
constructor(public mapProvider: MapProviders, private drawRoutes: boolean, public ctx: WidgetContext, $element: HTMLElement, isEdit?) {
constructor(
public mapProvider: MapProviders,
private drawRoutes: boolean,
public ctx: WidgetContext,
$element: HTMLElement,
isEdit?: boolean
) {
if (this.map) {
this.map.map.remove();
delete this.map;

View File

@ -56,10 +56,23 @@ export function getRatio(firsMoment: number, secondMoment: number, intermediateM
return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
}
export 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), 10);
export function interpolateOnLineSegment(
pointA: FormattedData,
oointB: FormattedData,
latKeyName: string,
lngKeyName: string,
ratio: number
): { [key: string]: number } {
return {
[latKeyName]: (pointA[latKeyName] + (oointB[latKeyName] - pointA[latKeyName]) * ratio),
[lngKeyName]: (pointA[lngKeyName] + (oointB[lngKeyName] - pointA[lngKeyName]) * ratio)
};
}
export function findAngle(startPoint: FormattedData, endPoint: FormattedData, latKeyName: string, lngKeyName: string): number {
let angle = -Math.atan2(endPoint[latKeyName] - startPoint[latKeyName], endPoint[lngKeyName] - startPoint[lngKeyName]);
angle = angle * 180 / Math.PI;
return parseInt(angle.toFixed(2), 10);
}

View File

@ -841,52 +841,57 @@ export const pathSchema =
};
export const pointSchema =
{
{
schema: {
title: 'Trip Animation Path Configuration',
type: 'object',
properties: {
showPoints: {
title: 'Show points',
type: 'boolean',
default: false
},
pointColor: {
title: 'Point color',
type: 'string'
},
pointSize: {
title: 'Point size (px)',
type: 'number',
default: 10
},
usePointAsAnchor: {
title: 'Use point as anchor',
type: 'boolean',
default: false
},
pointAsAnchorFunction: {
title: 'Point as anchor function: f(data, dsData, dsIndex)',
type: 'string'
},
pointTooltipOnRightPanel: {
title: 'Independant point tooltip',
type: 'boolean',
default: true
},
title: 'Trip Animation Path Configuration',
type: 'object',
properties: {
showPoints: {
title: 'Show points',
type: 'boolean',
default: false
},
required: []
pointColor: {
title: 'Point color',
type: 'string'
},
pointSize: {
title: 'Point size (px)',
type: 'number',
default: 10
},
// usePointAsAnchor: {
// title: 'Use point as anchor',
// type: 'boolean',
// default: false
// },
// pointAsAnchorFunction: {
// title: 'Point as anchor function: f(data, dsData, dsIndex)',
// type: 'string'
// },
pointTooltipOnRightPanel: {
title: 'Independant point tooltip',
type: 'boolean',
default: true
},
},
required: []
},
form: [
'showPoints', {
key: 'pointColor',
type: 'color'
}, 'pointSize', 'usePointAsAnchor', {
key: 'pointAsAnchorFunction',
type: 'javascript'
}, 'pointTooltipOnRightPanel',
'showPoints',
{
key: 'pointColor',
type: 'color'
},
'pointSize',
// 'usePointAsAnchor',
// {
// key: 'pointAsAnchorFunction',
// type: 'javascript'
// },
'pointTooltipOnRightPanel',
]
};
};
export const mapProviderSchema =
{

View File

@ -32,6 +32,10 @@
[ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
</div>
</div>
<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals" [anchors]="anchors" [useAnchors]="useAnchors"
(timeUpdated)="timeUpdated($event)"></tb-history-selector>
</div>
<tb-history-selector *ngIf="historicalData"
[settings]="settings"
[minTime]="minTime"
[maxTime]="maxTime"
[step]="normalizationStep"
(timeUpdated)="timeUpdated($event)"></tb-history-selector>
</div>

View File

@ -14,21 +14,27 @@
/// limitations under the License.
///
import L from 'leaflet';
import _ from 'lodash';
import tinycolor from 'tinycolor2';
import { interpolateOnPointSegment } from 'leaflet-geometryutil';
import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, SecurityContext, ViewChild } from '@angular/core';
import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
import { MapProviders, FormattedData } from '../lib/maps/map-models';
import { initSchema, addToSchema, addGroupInfo, addCondition } from '@app/core/schema-utils';
import { tripAnimationSchema, mapPolygonSchema, pathSchema, pointSchema } from '../lib/maps/schemes';
import { FormattedData, MapProviders } from '../lib/maps/map-models';
import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils';
import { mapPolygonSchema, pathSchema, pointSchema, tripAnimationSchema } from '../lib/maps/schemes';
import { DomSanitizer } from '@angular/platform-browser';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { findAngle, getRatio, parseArray, parseWithTranslation, safeExecute } from '../lib/maps/maps-utils';
import {
findAngle,
getRatio,
interpolateOnLineSegment,
parseArray,
parseWithTranslation,
safeExecute
} from '../lib/maps/maps-utils';
import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
import moment from 'moment';
import { isUndefined } from '@core/utils';
@Component({
@ -46,20 +52,21 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
@ViewChild('map') mapContainer;
mapWidget: MapWidgetController;
historicalData;
intervals;
normalizationStep = 1000;
interpolatedData = [];
historicalData: FormattedData[][];
normalizationStep: number;
interpolatedTimeData = [];
widgetConfig: WidgetConfig;
settings;
mainTooltip = '';
visibleTooltip = false;
activeTrip;
activeTrip: FormattedData;
label;
minTime;
maxTime;
minTime: number;
minTimeFormat: string;
maxTime: number;
maxTimeFormat: string;
anchors = [];
useAnchors = false;
useAnchors: boolean;
static getSettingsSchema(): JsonSettingsSchema {
const schema = initSchema();
@ -86,17 +93,19 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
rotationAngle: 0
}
this.settings = { ...settings, ...this.ctx.settings };
this.useAnchors = this.settings.usePointAsAnchor && this.settings.showPoints;
this.useAnchors = this.settings.showPoints && this.settings.usePointAsAnchor;
this.settings.fitMapBounds = true;
this.normalizationStep = this.settings.normalizationStep;
const subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
if (subscription) subscription.callbacks.onDataUpdated = () => {
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.cd.detectChanges();
if (subscription) {
subscription.callbacks.onDataUpdated = () => {
this.historicalData = parseArray(this.ctx.data);
this.activeTrip = this.historicalData[0][0];
this.calculateIntervals();
this.timeUpdated(this.minTime);
this.mapWidget.map.map?.invalidateSize();
this.cd.detectChanges();
}
}
}
@ -106,23 +115,37 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
}
timeUpdated(time: number) {
const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]);
const currentPosition = this.interpolatedTimeData.map(dataSource => dataSource[time]);
if(isUndefined(currentPosition[0])){
const timePoints = Object.keys(this.interpolatedTimeData[0]).map(item => parseInt(item, 10));
for (let i = 1; i < timePoints.length; i++) {
if (timePoints[i - 1] < time && timePoints[i] > time) {
const beforePosition = this.interpolatedTimeData[0][timePoints[i - 1]];
const afterPosition = this.interpolatedTimeData[0][timePoints[i]];
const ratio = getRatio(timePoints[i - 1], timePoints[i], time);
currentPosition[0] = {
...beforePosition,
time,
...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio)
}
break;
}
}
}
this.activeTrip = currentPosition[0];
this.minTime = moment(this.intervals[this.intervals.length - 1]).format('YYYY-MM-DD HH:mm:ss')
this.maxTime = moment(this.intervals[0]).format('YYYY-MM-DD HH:mm:ss')
this.calcLabel();
this.calcTooltip();
if (this.mapWidget) {
this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds)));
this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)));
if (this.settings.showPolygon) {
this.mapWidget.map.updatePolygons(this.interpolatedData);
this.mapWidget.map.updatePolygons(this.interpolatedTimeData);
}
if (this.settings.showPoints) {
this.mapWidget.map.updatePoints(this.historicalData[0], this.calcTooltip);
this.anchors = this.historicalData[0]
.filter(data =>
this.settings.usePointAsAnchor ||
safeExecute(this.settings.pointAsAnchorFunction, [this.historicalData, data, data.dsIndex])).map(data => data.time);
this.mapWidget.map.updatePoints(this.interpolatedTimeData, this.calcTooltip);
// this.anchors = this.interpolatedTimeData
// .filter(data =>
// this.settings.usePointAsAnchor ||
// safeExecute(this.settings.pointAsAnchorFunction, [this.interpolatedTimeData, data, data.dsIndex])).map(data => data.time);
}
this.mapWidget.map.updateMarkers(currentPosition);
}
@ -133,12 +156,11 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
calculateIntervals() {
this.historicalData.forEach((dataSource, index) => {
this.intervals = [];
for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) {
this.intervals.push(time);
}
this.intervals.push(dataSource[dataSource.length - 1]?.time);
this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals);
this.minTime = dataSource[0]?.time || Infinity;
this.minTimeFormat = this.minTime !== Infinity ? moment(this.minTime).format('YYYY-MM-DD HH:mm:ss') : '';
this.maxTime = dataSource[dataSource.length - 1]?.time || -Infinity;
this.maxTimeFormat = this.maxTime !== -Infinity ? moment(this.maxTime).format('YYYY-MM-DD HH:mm:ss') : '';
this.interpolatedTimeData[index] = this.interpolateArray(dataSource);
});
}
@ -147,9 +169,13 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
if (!point) {
point = this.activeTrip;
}
const data = { ...point, maxTime: this.maxTime, minTime: this.minTime }
const data = {
...this.activeTrip,
maxTime: this.maxTimeFormat,
minTime: this.minTimeFormat
}
const tooltipPattern: string = this.settings.useTooltipFunction ?
safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern;
safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, point.dsIndex]) : this.settings.tooltipPattern;
const tooltipText = parseWithTranslation.parseTemplate(tooltipPattern, data, true);
if (setTooltip) {
this.mainTooltip = this.sanitizer.sanitize(
@ -160,34 +186,37 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
}
calcLabel() {
const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
const data = {
...this.activeTrip,
maxTime: this.maxTimeFormat,
minTime: this.minTimeFormat
}
const labelText: string = this.settings.useLabelFunction ?
safeExecute(this.settings.labelFunction, [data, this.historicalData, 0]) : this.settings.label;
safeExecute(this.settings.labelFunction, [data, this.historicalData, data.dsIndex]) : this.settings.label;
this.label = (parseWithTranslation.parseTemplate(labelText, data, true));
}
interpolateArray(originData, interpolatedIntervals) {
interpolateArray(originData: FormattedData[]) {
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++;
const latKeyName = this.settings.latKeyName;
const lngKeyName = this.settings.lngKeyName;
for (let i = 0; i < originData.length; i++) {
const currentTime = originData[i].time;
const normalizeTime = this.minTime + Math.ceil((currentTime - this.minTime) / this.normalizationStep) * this.normalizationStep;
if (i !== originData.length - 1) {
result[normalizeTime] = {
...originData[i],
rotationAngle: this.settings.rotationAngle + findAngle(originData[i], originData[i + 1], latKeyName, lngKeyName)
};
} else {
result[normalizeTime] = {
...originData[i],
rotationAngle: findAngle(originData[i - 1], originData[i], latKeyName, lngKeyName) + this.settings.rotationAngle
};
}
}
return result;
}
}
export let TbTripAnimationWidget = TripAnimationComponent;

View File

@ -27,8 +27,8 @@
<mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="changeIndex()">
</mat-slider>
<div class="panel-timer">
<span *ngIf="this.intervals[this.index]">{{ this.intervals[this.index] | date:'medium'}}</span>
<span *ngIf="!this.intervals[this.index]">{{ "widget.no-data-found" | translate}}</span>
<span *ngIf="this.currentTime">{{ this.currentTime | date:'medium'}}</span>
<span *ngIf="!this.currentTime">{{ "widget.no-data-found" | translate}}</span>
</div>
</div>
<button mat-icon-button class="mat-icon-button" aria-label="Next" (click)="moveNext()">
@ -47,8 +47,9 @@
pause_circle_outline
</mat-icon>
</button>
<mat-select matInput [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select"
<mat-select [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select"
aria-label="Speed selector">
<mat-option [value]="speedValue" *ngFor="let speedValue of speeds">{{speedValue}} </mat-option>
</mat-select>
</div>
</div>
</div>

View File

@ -126,7 +126,7 @@
}
.speed-select {
width: 50px;
width: 70px;
margin-left: 10px;
margin-top: 10px;
}

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { interval } from 'rxjs';
import { filter } from 'rxjs/operators';
import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/maps/map-models';
@ -27,13 +27,12 @@ import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/m
export class HistorySelectorComponent implements OnInit, OnChanges {
@Input() settings: HistorySelectSettings
@Input() intervals = [];
@Input() anchors = [];
@Input() useAnchors = false;
@Input() minTime: number;
@Input() maxTime: number;
@Input() step = 1000;
@Output() timeUpdated: EventEmitter<number> = new EventEmitter();
animationTime;
minTimeIndex = 0;
maxTimeIndex = 0;
speed = 1;
@ -41,6 +40,7 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
playing = false;
interval;
speeds = [1, 5, 10, 25];
currentTime = null;
constructor(private cd: ChangeDetectorRef) { }
@ -49,7 +49,8 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
}
ngOnChanges() {
this.maxTimeIndex = this.intervals?.length - 1;
this.maxTimeIndex = Math.ceil((this.maxTime - this.minTime) / this.step);
this.currentTime = this.minTime === Infinity ? null : this.minTime;
}
play() {
@ -59,17 +60,18 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
.pipe(
filter(() => this.playing)).subscribe(() => {
this.index++;
if (this.index < this.maxTimeIndex) {
this.currentTime = this.minTime + this.index * this.step;
if (this.index <= this.maxTimeIndex) {
this.cd.detectChanges();
this.timeUpdated.emit(this.intervals[this.index]);
this.timeUpdated.emit(this.currentTime);
}
else {
this.interval.complete();
}
}, err => {
console.log(err);
console.error(err);
}, () => {
this.index = this.minTimeIndex;
this.currentTime = this.index = this.minTimeIndex;
this.playing = false;
this.interval = null;
this.cd.detectChanges();
@ -87,30 +89,21 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
pause() {
this.playing = false;
this.currentTime = this.minTime + this.index * this.step;
this.cd.detectChanges();
this.timeUpdated.emit(this.intervals[this.index]);
this.timeUpdated.emit(this.currentTime);
}
moveNext() {
if (this.index < this.maxTimeIndex) {
if (this.useAnchors) {
const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors)+1;
this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
}
else
this.index++;
if (this.index <= this.maxTimeIndex) {
this.index++;
}
this.pause();
}
movePrev() {
if (this.index > this.minTimeIndex) {
if (this.useAnchors) {
const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors) - 1;
this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
}
else
this.index--;
this.index--;
}
this.pause();
}
@ -125,15 +118,8 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
this.pause();
}
findIndex(value, array: any[]) {
let i = 0;
while (array[i] < value) {
i++;
};
return i;
}
changeIndex() {
this.timeUpdated.emit(this.intervals[this.index]);
this.currentTime = this.minTime + this.index * this.step;
this.timeUpdated.emit(this.currentTime);
}
}