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",
|
"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}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
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",
|
"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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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,
|
||||||
markerClusteringSettingsSchema,
|
mapProviderSchema,
|
||||||
markerClusteringSettingsSchemaLeaflet,
|
markerClusteringSettingsSchema,
|
||||||
mapProviderSchema,
|
markerClusteringSettingsSchemaLeaflet,
|
||||||
mapPolygonSchema
|
routeMapSettingsSchema
|
||||||
} 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;
|
||||||
|
|||||||
@ -56,10 +56,23 @@ 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,
|
||||||
angle = angle * 180 / Math.PI;
|
oointB: FormattedData,
|
||||||
return parseInt(angle.toFixed(2), 10);
|
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 =
|
export const pointSchema =
|
||||||
{
|
{
|
||||||
schema: {
|
schema: {
|
||||||
title: 'Trip Animation Path Configuration',
|
title: 'Trip Animation Path Configuration',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
showPoints: {
|
showPoints: {
|
||||||
title: 'Show points',
|
title: 'Show points',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false
|
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
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
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: [
|
form: [
|
||||||
'showPoints', {
|
'showPoints',
|
||||||
key: 'pointColor',
|
{
|
||||||
type: 'color'
|
key: 'pointColor',
|
||||||
}, 'pointSize', 'usePointAsAnchor', {
|
type: 'color'
|
||||||
key: 'pointAsAnchorFunction',
|
},
|
||||||
type: 'javascript'
|
'pointSize',
|
||||||
}, 'pointTooltipOnRightPanel',
|
// 'usePointAsAnchor',
|
||||||
|
// {
|
||||||
|
// key: 'pointAsAnchorFunction',
|
||||||
|
// type: 'javascript'
|
||||||
|
// },
|
||||||
|
'pointTooltipOnRightPanel',
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mapProviderSchema =
|
export const mapProviderSchema =
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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"
|
||||||
(timeUpdated)="timeUpdated($event)"></tb-history-selector>
|
[settings]="settings"
|
||||||
|
[minTime]="minTime"
|
||||||
|
[maxTime]="maxTime"
|
||||||
|
[step]="normalizationStep"
|
||||||
|
(timeUpdated)="timeUpdated($event)"></tb-history-selector>
|
||||||
</div>
|
</div>
|
||||||
@ -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,17 +93,19 @@ 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) {
|
||||||
this.historicalData = parseArray(this.ctx.data);
|
subscription.callbacks.onDataUpdated = () => {
|
||||||
this.activeTrip = this.historicalData[0][0];
|
this.historicalData = parseArray(this.ctx.data);
|
||||||
this.calculateIntervals();
|
this.activeTrip = this.historicalData[0][0];
|
||||||
this.timeUpdated(this.intervals[0]);
|
this.calculateIntervals();
|
||||||
this.mapWidget.map.map?.invalidateSize();
|
this.timeUpdated(this.minTime);
|
||||||
this.cd.detectChanges();
|
this.mapWidget.map.map?.invalidateSize();
|
||||||
|
this.cd.detectChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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),
|
...originData[i],
|
||||||
getRatio(before.time, after.time, currentTime));
|
rotationAngle: this.settings.rotationAngle + findAngle(originData[i], originData[i + 1], latKeyName, lngKeyName)
|
||||||
result[currentTime] = ({
|
};
|
||||||
...originData[i],
|
} else {
|
||||||
rotationAngle: findAngle(before, after) + this.settings.rotationAngle,
|
result[normalizeTime] = {
|
||||||
latitude: interpolation.x,
|
...originData[i],
|
||||||
longitude: interpolation.y
|
rotationAngle: findAngle(originData[i - 1], originData[i], latKeyName, lngKeyName) + this.settings.rotationAngle
|
||||||
});
|
};
|
||||||
j++;
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export let TbTripAnimationWidget = TripAnimationComponent;
|
export let TbTripAnimationWidget = TripAnimationComponent;
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -126,7 +126,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.speed-select {
|
.speed-select {
|
||||||
width: 50px;
|
width: 70px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,30 +89,21 @@ 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) {
|
this.index++;
|
||||||
const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors)+1;
|
|
||||||
this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this.index++;
|
|
||||||
}
|
}
|
||||||
this.pause();
|
this.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
movePrev() {
|
movePrev() {
|
||||||
if (this.index > this.minTimeIndex) {
|
if (this.index > this.minTimeIndex) {
|
||||||
if (this.useAnchors) {
|
this.index--;
|
||||||
const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors) - 1;
|
|
||||||
this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user