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", "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": "", "settingsSchema": "",
"dataKeySettingsSchema": "{}", "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}"
} }
}, },
{ {

View File

@ -7969,14 +7969,6 @@
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.6.0.tgz", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.6.0.tgz",
"integrity": "sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==" "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": { "leaflet-polylinedecorator": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/leaflet-polylinedecorator/-/leaflet-polylinedecorator-1.6.0.tgz", "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", "jstree-bootstrap-theme": "^1.0.1",
"jszip": "^3.4.0", "jszip": "^3.4.0",
"leaflet": "^1.6.0", "leaflet": "^1.6.0",
"leaflet-geometryutil": "^0.9.3",
"leaflet-polylinedecorator": "^1.6.0", "leaflet-polylinedecorator": "^1.6.0",
"leaflet-providers": "^1.9.1", "leaflet-providers": "^1.9.1",
"leaflet.gridlayer.googlemutant": "0.8.0", "leaflet.gridlayer.googlemutant": "0.8.0",

View File

@ -14,22 +14,28 @@
/// limitations under the License. /// 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 LeafletMap from './leaflet-map';
import { import {
commonMapSettingsSchema, commonMapSettingsSchema,
routeMapSettingsSchema, mapPolygonSchema,
mapProviderSchema,
markerClusteringSettingsSchema, markerClusteringSettingsSchema,
markerClusteringSettingsSchemaLeaflet, markerClusteringSettingsSchemaLeaflet,
mapProviderSchema, routeMapSettingsSchema
mapPolygonSchema
} from './schemes'; } from './schemes';
import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface';
import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils'; import { addCondition, addGroupInfo, addToSchema, initSchema, mergeSchemes } from '@core/schema-utils';
import { of, Subject } from 'rxjs'; import { of, Subject } from 'rxjs';
import { WidgetContext } from '@app/modules/home/models/widget-component.models'; import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { getDefCenterPosition, parseArray, parseData, parseFunction, parseWithTranslation } from './maps-utils'; 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 { EntityId } from '@shared/models/id/entity-id';
import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
import { AttributeService } from '@core/http/attribute.service'; import { AttributeService } from '@core/http/attribute.service';
@ -39,7 +45,13 @@ import { UtilsService } from '@core/services/utils.service';
// @dynamic // @dynamic
export class MapWidgetController implements MapWidgetInterface { 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) { if (this.map) {
this.map.map.remove(); this.map.map.remove();
delete this.map; delete this.map;

View File

@ -56,8 +56,21 @@ export function getRatio(firsMoment: number, secondMoment: number, intermediateM
return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
} }
export function findAngle(startPoint, endPoint) { export function interpolateOnLineSegment(
let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude); 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; angle = angle * 180 / Math.PI;
return parseInt(angle.toFixed(2), 10); return parseInt(angle.toFixed(2), 10);
} }

View File

@ -860,15 +860,15 @@ export const pointSchema =
type: 'number', type: 'number',
default: 10 default: 10
}, },
usePointAsAnchor: { // usePointAsAnchor: {
title: 'Use point as anchor', // title: 'Use point as anchor',
type: 'boolean', // type: 'boolean',
default: false // default: false
}, // },
pointAsAnchorFunction: { // pointAsAnchorFunction: {
title: 'Point as anchor function: f(data, dsData, dsIndex)', // title: 'Point as anchor function: f(data, dsData, dsIndex)',
type: 'string' // type: 'string'
}, // },
pointTooltipOnRightPanel: { pointTooltipOnRightPanel: {
title: 'Independant point tooltip', title: 'Independant point tooltip',
type: 'boolean', type: 'boolean',
@ -878,13 +878,18 @@ export const pointSchema =
required: [] required: []
}, },
form: [ form: [
'showPoints', { 'showPoints',
{
key: 'pointColor', key: 'pointColor',
type: 'color' type: 'color'
}, 'pointSize', 'usePointAsAnchor', { },
key: 'pointAsAnchorFunction', 'pointSize',
type: 'javascript' // 'usePointAsAnchor',
}, 'pointTooltipOnRightPanel', // {
// key: 'pointAsAnchorFunction',
// type: 'javascript'
// },
'pointTooltipOnRightPanel',
] ]
}; };

View File

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

View File

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

View File

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

View File

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

View File

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