Merge pull request #2785 from vvlladd28/improvement/3.0/trip-animation
Improvement trip animation
This commit is contained in:
commit
5c667ba7fe
@ -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 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
8
ui-ngx/package-lock.json
generated
8
ui-ngx/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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 =
|
||||
{
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -126,7 +126,7 @@
|
||||
}
|
||||
|
||||
.speed-select {
|
||||
width: 50px;
|
||||
width: 70px;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user