Browse Source

UI: Trip data layer implementation.

pull/12866/head
Igor Kulikov 2 years ago
parent
commit
749a0b5c61
  1. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/data-layer-utils.ts
  2. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts
  3. 229
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts
  4. 33
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
  5. 20
      ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts
  6. 7
      ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.ts
  7. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html
  8. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/data-layer-utils.ts

@ -86,7 +86,7 @@ const bindTooltipActions = (map: TbMap<any>, tooltip: L.Popup, settings: DataLay
if (action) { if (action) {
element.onclick = ($event) => element.onclick = ($event) =>
{ {
map.dataItemClick($event, action, data.$datasource); map.dataItemClick($event, action, data);
return false; return false;
}; };
} }

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts

@ -108,7 +108,7 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
const clickAction = this.settings.click; const clickAction = this.settings.click;
if (clickAction && clickAction.type !== WidgetActionType.doNothing) { if (clickAction && clickAction.type !== WidgetActionType.doNothing) {
this.layer.on('click', (event) => { this.layer.on('click', (event) => {
this.dataLayer.getMap().dataItemClick(event.originalEvent, clickAction, this.data.$datasource); this.dataLayer.getMap().dataItemClick(event.originalEvent, clickAction, this.data);
}); });
} }
} }

229
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts

@ -17,19 +17,20 @@
import { import {
DataLayerColorProcessor, DataLayerColorProcessor,
DataLayerPatternProcessor, DataLayerPatternProcessor,
MapDataLayerType, TbDataLayerItem, MapDataLayerType,
TbDataLayerItem,
TbMapDataLayer, TbMapDataLayer,
UnplacedMapDataItem UnplacedMapDataItem
} from '@home/components/widget/lib/maps/data-layer/map-data-layer'; } from '@home/components/widget/lib/maps/data-layer/map-data-layer';
import { import {
BaseMarkerShapeSettings, BaseMarkerShapeSettings,
calculateInterpolationRatio, calculateLastPoints, calculateInterpolationRatio,
ClusterMarkerColorFunction, calculateLastPoints,
DataLayerColorType, DataLayerColorType,
defaultBaseMarkersDataLayerSettings,
defaultBaseTripsDataLayerSettings, defaultBaseTripsDataLayerSettings,
findRotationAngle, findRotationAngle,
interpolateLineSegment, isValidLatLng, interpolateLineSegment,
isValidLatLng,
loadImageWithAspect, loadImageWithAspect,
MapStringFunction, MapStringFunction,
MapType, MapType,
@ -40,14 +41,13 @@ import {
MarkerImageSettings, MarkerImageSettings,
MarkerImageType, MarkerImageType,
MarkerPositionFunction, MarkerPositionFunction,
MarkersDataLayerSettings,
MarkerShapeSettings, MarkerShapeSettings,
MarkerType, MarkerType,
TbMapDatasource, TbMapDatasource,
TripsDataLayerSettings TripsDataLayerSettings
} from '@home/components/widget/lib/maps/models/map.models'; } from '@home/components/widget/lib/maps/models/map.models';
import { forkJoin, Observable, of } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { FormattedData } from '@shared/models/widget.models'; import { FormattedData, WidgetActionType } from '@shared/models/widget.models';
import { import {
createColorMarkerIconElement, createColorMarkerIconElement,
createColorMarkerShapeURI, createColorMarkerShapeURI,
@ -57,7 +57,7 @@ import tinycolor from 'tinycolor2';
import { MatIconRegistry } from '@angular/material/icon'; import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { catchError, map, switchMap } from 'rxjs/operators'; import { catchError, map, switchMap } from 'rxjs/operators';
import L, { PolylineDecorator } from 'leaflet'; import L from 'leaflet';
import { CompiledTbFunction } from '@shared/models/js-function.models'; import { CompiledTbFunction } from '@shared/models/js-function.models';
import { import {
deepClone, deepClone,
@ -74,6 +74,7 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import moment from 'moment/moment'; import moment from 'moment/moment';
import { TbImageMap } from '@home/components/widget/lib/maps/image-map'; import { TbImageMap } from '@home/components/widget/lib/maps/image-map';
import { createTooltip, updateTooltip } from '@home/components/widget/lib/maps/data-layer/data-layer-utils'; import { createTooltip, updateTooltip } from '@home/components/widget/lib/maps/data-layer/data-layer-utils';
import _ from 'lodash';
type TripRouteData = {[time: number]: FormattedData<TbMapDatasource>}; type TripRouteData = {[time: number]: FormattedData<TbMapDatasource>};
@ -88,11 +89,12 @@ class TbTripDataItem {
private labelOffset: L.PointTuple; private labelOffset: L.PointTuple;
private polyline: L.Polyline; private polyline: L.Polyline;
private polylineDecorator: PolylineDecorator; private polylineDecorator: L.PolylineDecorator;
private points: L.FeatureGroup; private points: L.FeatureGroup;
private currentTime: number; private currentTime: number;
private currentPositionData: FormattedData<TbMapDatasource>; private currentPositionData: FormattedData<TbMapDatasource>;
private pointData: FormattedData<TbMapDatasource>;
constructor(private rawRouteData: FormattedData<TbMapDatasource>[], constructor(private rawRouteData: FormattedData<TbMapDatasource>[],
private latestData: FormattedData<TbMapDatasource>, private latestData: FormattedData<TbMapDatasource>,
@ -105,11 +107,13 @@ class TbTripDataItem {
private create() { private create() {
this.updateCurrentPosition(); this.updateCurrentPosition();
this.layer = L.featureGroup(); this.layer = L.featureGroup();
let pointData = this.currentPositionData; this.pointData = this.currentPositionData;
if (this.latestData) { if (this.latestData) {
pointData = {...pointData, ...this.latestData}; this.pointData = {...this.pointData, ...this.latestData};
} }
this.createMarker(pointData); this.createPath();
this.updatePoints();
this.createMarker();
try { try {
this.dataLayer.getDataLayerContainer().addLayer(this.layer); this.dataLayer.getDataLayerContainer().addLayer(this.layer);
} catch (e) { } catch (e) {
@ -121,38 +125,50 @@ class TbTripDataItem {
this.rawRouteData = rawRouteData; this.rawRouteData = rawRouteData;
this.tripRouteData = this.prepareTripRouteData(); this.tripRouteData = this.prepareTripRouteData();
this.updateCurrentPosition(true); this.updateCurrentPosition(true);
let pointData = this.currentPositionData; this.pointData = this.currentPositionData;
if (this.latestData) { if (this.latestData) {
pointData = {...pointData, ...this.latestData}; this.pointData = {...this.pointData, ...this.latestData};
} }
this.updateMarker(pointData); this.updatePath();
this.updatePoints();
this.updateMarker();
} }
public updateLatestData(latestData: FormattedData<TbMapDatasource>) { public updateLatestData(latestData: FormattedData<TbMapDatasource>) {
this.latestData = latestData; this.latestData = latestData;
this.pointData = this.currentPositionData;
if (this.latestData) {
this.pointData = {...this.pointData, ...this.latestData};
}
this.updateAppearance(); this.updateAppearance();
} }
public updateAppearance() { public updateAppearance() {
let data = this.currentPositionData; this.updatePathAppearance();
if (this.latestData) {
data = {...data, ...this.latestData};
}
const dsData = this.dataLayer.getMap().getData(); const dsData = this.dataLayer.getMap().getData();
if (this.settings.tooltip.show) { if (this.settings.tooltip.show) {
updateTooltip(this.dataLayer.getMap(), this.markerTooltip, updateTooltip(this.dataLayer.getMap(), this.markerTooltip,
this.settings.tooltip, this.dataLayer.dataLayerTooltipProcessor, data, dsData); this.settings.tooltip, this.dataLayer.dataLayerTooltipProcessor, this.pointData, dsData);
} }
this.updateMarkerIcon(data, dsData); this.updatePoints();
this.updateMarkerIcon(this.pointData, dsData);
} }
public updateCurrentTime() { public updateCurrentTime() {
this.updateCurrentPosition(); this.updateCurrentPosition();
let pointData = this.currentPositionData; this.pointData = this.currentPositionData;
if (this.latestData) { if (this.latestData) {
pointData = {...pointData, ...this.latestData}; this.pointData = {...this.pointData, ...this.latestData};
} }
this.updateMarker(pointData); this.updatePath();
this.updateMarker();
}
public invalidateCoordinates(): void {
this.tripRouteData = this.prepareTripRouteData();
this.updatePath();
this.updatePoints();
this.updateMarker();
} }
public remove() { public remove() {
@ -164,32 +180,117 @@ class TbTripDataItem {
return this.layer; return this.layer;
} }
private createMarker(data: FormattedData<TbMapDatasource>) { public calculateAnchors(): number[] {
const entries = Object.entries(this.tripRouteData);
const dsData = this.dataLayer.getMap().getData(); const dsData = this.dataLayer.getMap().getData();
const location = this.dataLayer.extractLocation(data, dsData); return entries.filter(data => this.dataLayer.getMap().getLocationSnapFilterFunction().execute(data[1], dsData))
.map(data => parseInt(data[0], 10));
}
private createMarker() {
const dsData = this.dataLayer.getMap().getData();
const location = this.dataLayer.extractLocation(this.pointData, dsData);
this.marker = L.marker(location, { this.marker = L.marker(location, {
tbMarkerData: data, tbMarkerData: this.pointData,
snapIgnore: true snapIgnore: true
}); });
this.marker.addTo(this.layer); this.marker.addTo(this.layer);
this.updateMarkerIcon(data, dsData); this.updateMarkerIcon(this.pointData, dsData);
if (this.settings.tooltip?.show) { if (this.settings.tooltip?.show) {
this.markerTooltip = createTooltip(this.dataLayer.getMap(), this.markerTooltip = createTooltip(this.dataLayer.getMap(),
this.marker, this.settings.tooltip, data, () => true); this.marker, this.settings.tooltip, this.pointData, () => true);
updateTooltip(this.dataLayer.getMap(), this.markerTooltip, updateTooltip(this.dataLayer.getMap(), this.markerTooltip,
this.settings.tooltip, this.dataLayer.dataLayerTooltipProcessor, data, dsData); this.settings.tooltip, this.dataLayer.dataLayerTooltipProcessor, this.pointData, dsData);
}
const clickAction = this.settings.click;
if (clickAction && clickAction.type !== WidgetActionType.doNothing) {
this.marker.on('click', (event) => {
this.dataLayer.getMap().dataItemClick(event.originalEvent, clickAction, this.pointData);
});
} }
} }
private updateMarker(data: FormattedData<TbMapDatasource>) { private updateMarker() {
const dsData = this.dataLayer.getMap().getData(); const dsData = this.dataLayer.getMap().getData();
this.marker.options.tbMarkerData = data; this.marker.options.tbMarkerData = this.pointData;
this.updateMarkerLocation(data, dsData); this.updateMarkerLocation(this.pointData, dsData);
if (this.settings.tooltip.show) { if (this.settings.tooltip.show) {
updateTooltip(this.dataLayer.getMap(), this.markerTooltip, updateTooltip(this.dataLayer.getMap(), this.markerTooltip,
this.settings.tooltip, this.dataLayer.dataLayerTooltipProcessor, data, dsData); this.settings.tooltip, this.dataLayer.dataLayerTooltipProcessor, this.pointData, dsData);
}
this.updateMarkerIcon(this.pointData, dsData);
}
private createPath() {
if (this.settings.showPath) {
const formattedRouteData = _.values(this.tripRouteData);
const dsData = this.dataLayer.getMap().getData();
const locations = formattedRouteData.map(data => this.dataLayer.extractLocation(data, dsData));
const pathStyle = this.dataLayer.getPathStyle(this.pointData, dsData);
this.polyline = L.polyline(locations, pathStyle);
this.polyline.addTo(this.layer);
if (this.settings.usePathDecorator) {
this.polylineDecorator = new L.PolylineDecorator(this.polyline, this.dataLayer.getPathDecoratorStyle(pathStyle.color));
this.polylineDecorator.addTo(this.layer);
}
}
}
private updatePath() {
if (this.settings.showPath) {
const formattedRouteData = _.values(this.tripRouteData);
const dsData = this.dataLayer.getMap().getData();
const locations = formattedRouteData.map(data => this.dataLayer.extractLocation(data, dsData));
this.polyline.setLatLngs(locations);
if (this.settings.usePathDecorator) {
this.polylineDecorator.setPaths(this.polyline);
}
this.updatePathAppearance();
}
}
private updatePoints() {
if (this.settings.showPoints) {
if (!this.points) {
this.points = L.featureGroup();
this.points.addTo(this.layer);
}
this.points.clearLayers();
const formattedRouteData = _.values(this.tripRouteData);
const dsData = this.dataLayer.getMap().getData();
const pointsList = formattedRouteData.filter(data => !!this.dataLayer.extractLocationData(data));
for (const pData of pointsList) {
let pointData = pData;
if (this.latestData) {
pointData = {...pointData, ...this.latestData};
}
const pointColor = this.dataLayer.pointColorProcessor.processColor(pointData, dsData);
const point = L.circleMarker(this.dataLayer.extractLocation(pointData, dsData), {
stroke: false,
fillOpacity: 1,
fillColor: pointColor,
radius: this.settings.pointSize
});
if (this.settings.pointTooltip?.show) {
const pointTooltip = createTooltip(this.dataLayer.getMap(),
point, this.settings.pointTooltip, pointData, () => true);
updateTooltip(this.dataLayer.getMap(), pointTooltip,
this.settings.pointTooltip, this.dataLayer.pointTooltipProcessor, pointData, dsData);
}
this.points.addLayer(point);
}
}
}
private updatePathAppearance() {
if (this.settings.showPath) {
const dsData = this.dataLayer.getMap().getData();
const pathStyle = this.dataLayer.getPathStyle(this.pointData, dsData);
this.polyline.setStyle(pathStyle);
if (this.settings.usePathDecorator) {
this.polylineDecorator.setPatterns(this.dataLayer.getPathDecoratorStyle(pathStyle.color).patterns)
}
} }
this.updateMarkerIcon(data, dsData);
} }
private updateMarkerLocation(data: FormattedData<TbMapDatasource>, dsData: FormattedData<TbMapDatasource>[]) { private updateMarkerLocation(data: FormattedData<TbMapDatasource>, dsData: FormattedData<TbMapDatasource>[]) {
@ -231,6 +332,7 @@ class TbTripDataItem {
const maxTime = this.dataLayer.getMap().getMaxTime(); const maxTime = this.dataLayer.getMap().getMaxTime();
const timeStep = this.dataLayer.getMap().getTimeStep(); const timeStep = this.dataLayer.getMap().getTimeStep();
const timeline = this.dataLayer.getMap().hasTimeline(); const timeline = this.dataLayer.getMap().hasTimeline();
const dsData = this.dataLayer.getMap().getData();
for (const data of this.rawRouteData) { for (const data of this.rawRouteData) {
const currentTime = data.time; const currentTime = data.time;
const normalizeTime = timeline ? (minTime const normalizeTime = timeline ? (minTime
@ -260,7 +362,9 @@ class TbTripDataItem {
} }
} }
if (this.settings.rotateMarker) { if (this.settings.rotateMarker) {
result[timeStamp[i]].rotationAngle += findRotationAngle(result[timeStamp[i]], result[timeStamp[i + 1]], xKey, yKey); const startPoint = this.dataLayer.extractLocation(result[timeStamp[i]], dsData);
const endPoint = this.dataLayer.extractLocation(result[timeStamp[i + 1]], dsData);
result[timeStamp[i]].rotationAngle += findRotationAngle(startPoint, endPoint);
} }
} }
} }
@ -584,10 +688,6 @@ export class TbTripsDataLayer extends TbMapDataLayer<TripsDataLayerSettings, TbT
} }
public updateTrips() { public updateTrips() {
const minTime = this.map.getMinTime();
const maxTime = this.map.getMaxTime();
const currentTime = this.map.getCurrentTime();
console.log(`Update trips: min(${minTime}), max(${maxTime}), current(${currentTime})`);
const toDelete = new Set(Array.from(this.tripItems.keys())); const toDelete = new Set(Array.from(this.tripItems.keys()));
this.rawTripsData.forEach(rawTripData => { this.rawTripsData.forEach(rawTripData => {
const entityId = rawTripData[0].entityId; const entityId = rawTripData[0].entityId;
@ -614,9 +714,12 @@ export class TbTripsDataLayer extends TbMapDataLayer<TripsDataLayerSettings, TbT
} }
} }
public invalidateCoordinates(): void {
this.tripItems.forEach(item => item.invalidateCoordinates());
}
public updateTripsLatestData(tripsLatestData: FormattedData<TbMapDatasource>[]) { public updateTripsLatestData(tripsLatestData: FormattedData<TbMapDatasource>[]) {
this.latestTripsData = tripsLatestData.filter(d => d.$datasource.mapDataIds.includes(this.mapDataId)); this.latestTripsData = tripsLatestData.filter(d => d.$datasource.mapDataIds.includes(this.mapDataId));
console.log(`Update trips latest data`);
this.tripItems.forEach((item, entityId) => { this.tripItems.forEach((item, entityId) => {
const latestData = this.latestTripsData.find(d => d.entityId === entityId); const latestData = this.latestTripsData.find(d => d.entityId === entityId);
item.updateLatestData(latestData); item.updateLatestData(latestData);
@ -624,22 +727,24 @@ export class TbTripsDataLayer extends TbMapDataLayer<TripsDataLayerSettings, TbT
} }
public updateCurrentTime() { public updateCurrentTime() {
const currentTime = this.map.getCurrentTime();
console.log(`Update trips current time: current(${currentTime})`);
this.tripItems.forEach(item => { this.tripItems.forEach(item => {
item.updateCurrentTime(); item.updateCurrentTime();
}); });
} }
public updateAppearance() { public updateAppearance() {
console.log(`Update trips appearance`);
this.tripItems.forEach(item => { this.tripItems.forEach(item => {
item.updateAppearance(); item.updateAppearance();
}); });
} }
public calculateAnchors(): number[] { public calculateAnchors(): number[] {
return []; let anchors: number[] = [];
this.tripItems.forEach(item => {
const tripAnchors = item.calculateAnchors();
anchors = [...new Set([...anchors, ...tripAnchors])];
});
return anchors;
} }
private clearIncorrectFirsLastDatapoint(dataSource: FormattedData<TbMapDatasource>[]): FormattedData<TbMapDatasource>[] { private clearIncorrectFirsLastDatapoint(dataSource: FormattedData<TbMapDatasource>[]): FormattedData<TbMapDatasource>[] {
@ -705,7 +810,7 @@ export class TbTripsDataLayer extends TbMapDataLayer<TripsDataLayerSettings, TbT
); );
} }
private extractLocationData(data: FormattedData<TbMapDatasource>): {x: number; y: number} { public extractLocationData(data: FormattedData<TbMapDatasource>): {x: number; y: number} {
if (data) { if (data) {
const xKeyVal = data[this.settings.xKey.label]; const xKeyVal = data[this.settings.xKey.label];
const yKeyVal = data[this.settings.yKey.label]; const yKeyVal = data[this.settings.yKey.label];
@ -740,4 +845,36 @@ export class TbTripsDataLayer extends TbMapDataLayer<TripsDataLayerSettings, TbT
} }
} }
public getPathStyle(data: FormattedData<TbMapDatasource>, dsData: FormattedData<TbMapDatasource>[]): L.PolylineOptions {
const pathStroke = this.pathStrokeColorProcessor.processColor(data, dsData);
return {
interactive: false,
color: pathStroke,
opacity: 1,
weight: this.settings.pathStrokeWeight,
pmIgnore: true
};
}
public getPathDecoratorStyle(pathStroke: string): L.PolylineDecoratorOptions {
const symbolConstructor = L.Symbol[this.settings.pathDecoratorSymbol];
return {
patterns: [
{
offset: this.settings.pathDecoratorOffset,
endOffset: this.settings.pathEndDecoratorOffset,
repeat: this.settings.pathDecoratorRepeat,
symbol: symbolConstructor({
pixelSize: this.settings.pathDecoratorSymbolSize,
polygon: false,
pathOptions: {
color: this.settings.pathDecoratorSymbolColor ? this.settings.pathDecoratorSymbolColor : pathStroke,
stroke: true
}
})
}
]
};
}
} }

33
ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts

@ -18,7 +18,7 @@ import {
additionalMapDataSourcesToDatasources, additionalMapDataSourcesToDatasources,
BaseMapSettings, BaseMapSettings,
CustomActionData, CustomActionData,
DataKeyValuePair, DataKeyValuePair, MapBooleanFunction, MapStringFunction,
MapType, MapType,
mergeMapDatasources, mergeMapDatasources,
mergeUnplacedDataItemsArrays, mergeUnplacedDataItemsArrays,
@ -34,12 +34,12 @@ import {
formattedDataFormDatasourceData, formattedDataFormDatasourceData,
isDefined, isDefined,
isDefinedAndNotNull, isUndefined, isDefinedAndNotNull, isUndefined,
mergeDeepIgnoreArray mergeDeepIgnoreArray, parseTbFunction
} from '@core/utils'; } from '@core/utils';
import { DeepPartial } from '@shared/models/common'; import { DeepPartial } from '@shared/models/common';
import L from 'leaflet'; import L from 'leaflet';
import { forkJoin, Observable, of } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators'; import { map, switchMap, tap } from 'rxjs/operators';
import '@home/components/widget/lib/maps/leaflet/leaflet-tb'; import '@home/components/widget/lib/maps/leaflet/leaflet-tb';
import { import {
MapDataLayerType, MapDataLayerType,
@ -71,6 +71,7 @@ import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide;
import { MapTimelinePanelComponent } from '@home/components/widget/lib/maps/panels/map-timeline-panel.component'; import { MapTimelinePanelComponent } from '@home/components/widget/lib/maps/panels/map-timeline-panel.component';
import { ComponentRef } from '@angular/core'; import { ComponentRef } from '@angular/core';
import { TbTripsDataLayer } from '@home/components/widget/lib/maps/data-layer/trips-data-layer'; import { TbTripsDataLayer } from '@home/components/widget/lib/maps/data-layer/trips-data-layer';
import { CompiledTbFunction } from '@shared/models/js-function.models';
type TooltipInstancesData = {root: HTMLElement, instances: ITooltipsterInstance[]}; type TooltipInstancesData = {root: HTMLElement, instances: ITooltipsterInstance[]};
@ -88,7 +89,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
protected dataLayers: TbMapDataLayer<any>[]; protected dataLayers: TbMapDataLayer<any>[];
protected tripDataLayers: TbTripsDataLayer[]; protected tripDataLayers: TbTripsDataLayer[];
protected dsData: FormattedData<TbMapDatasource>[]; protected dsData: FormattedData<TbMapDatasource>[] = [];
protected timeline = false; protected timeline = false;
protected minTime: number; protected minTime: number;
@ -113,6 +114,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
protected timeLineComponentRef: ComponentRef<MapTimelinePanelComponent>; protected timeLineComponentRef: ComponentRef<MapTimelinePanelComponent>;
protected timeLineComponent: MapTimelinePanelComponent; protected timeLineComponent: MapTimelinePanelComponent;
protected locationSnapFilterFunction: CompiledTbFunction<MapBooleanFunction>;
protected addMarkerDataLayers: TbMapDataLayer<any>[]; protected addMarkerDataLayers: TbMapDataLayer<any>[];
protected addPolygonDataLayers: TbMapDataLayer<any>[]; protected addPolygonDataLayers: TbMapDataLayer<any>[];
@ -183,7 +185,16 @@ export abstract class TbMap<S extends BaseMapSettings> {
if (this.map.zoomControl) { if (this.map.zoomControl) {
this.map.zoomControl.setPosition(this.settings.controlsPosition); this.map.zoomControl.setPosition(this.settings.controlsPosition);
} }
return this.doSetupControls(); const setup = [this.doSetupControls()];
if (this.timeline && this.settings.tripTimeline.snapToRealLocation) {
setup.push(parseTbFunction<MapBooleanFunction>(this.getCtx().http, this.settings.tripTimeline.locationSnapFilter, ['data', 'dsData']).pipe(
map((parsed) => {
this.locationSnapFilterFunction = parsed;
return null;
})
));
}
return forkJoin(setup);
} }
private initMap() { private initMap() {
@ -263,7 +274,6 @@ export abstract class TbMap<S extends BaseMapSettings> {
this.updateBounds(); this.updateBounds();
}); });
} }
const setup = allDataLayers.map(dl => dl.setup()); const setup = allDataLayers.map(dl => dl.setup());
forkJoin(setup).subscribe( forkJoin(setup).subscribe(
() => { () => {
@ -919,6 +929,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
protected invalidateDataLayersCoordinates(): void { protected invalidateDataLayersCoordinates(): void {
this.dataLayers.forEach(dl => dl.invalidateCoordinates()); this.dataLayers.forEach(dl => dl.invalidateCoordinates());
this.tripDataLayers.forEach(dl => dl.invalidateCoordinates());
} }
protected getSidebar(): L.TB.SidebarControl { protected getSidebar(): L.TB.SidebarControl {
@ -953,16 +964,16 @@ export abstract class TbMap<S extends BaseMapSettings> {
this.updateTripsAnchors(); this.updateTripsAnchors();
} }
public dataItemClick($event: Event, action: WidgetAction, entityInfo: TbMapDatasource) { public dataItemClick($event: Event, action: WidgetAction, data: FormattedData<TbMapDatasource>) {
if ($event) { if ($event) {
$event.preventDefault(); $event.preventDefault();
$event.stopPropagation(); $event.stopPropagation();
} }
const { entityId, entityName, entityLabel, entityType } = entityInfo; const { entityId, entityName, entityLabel, entityType } = data.$datasource;
this.ctx.actionsApi.handleWidgetAction($event, action, { this.ctx.actionsApi.handleWidgetAction($event, action, {
entityType, entityType,
id: entityId id: entityId
}, entityName, null, entityLabel); }, entityName, data, entityLabel);
} }
public selectItem(item: TbDataLayerItem, cancel = false, force = false): boolean { public selectItem(item: TbDataLayerItem, cancel = false, force = false): boolean {
@ -1063,6 +1074,10 @@ export abstract class TbMap<S extends BaseMapSettings> {
return this.currentTime; return this.currentTime;
} }
public getLocationSnapFilterFunction(): CompiledTbFunction<MapBooleanFunction> {
return this.locationSnapFilterFunction;
}
public destroy() { public destroy() {
if (this.mapResize$) { if (this.mapResize$) {
this.mapResize$.disconnect(); this.mapResize$.disconnect();

20
ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts

@ -23,7 +23,16 @@ import {
WidgetActionType WidgetActionType
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { guid, hashCode, isDefinedAndNotNull, isNotEmptyStr, isString, isUndefined, mergeDeep } from '@core/utils'; import {
guid,
hashCode,
isDefinedAndNotNull,
isNotEmptyStr,
isString,
isUndefined,
isUndefinedOrNull,
mergeDeep
} from '@core/utils';
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
import { materialColors } from '@shared/models/material.models'; import { materialColors } from '@shared/models/material.models';
import L from 'leaflet'; import L from 'leaflet';
@ -1040,6 +1049,9 @@ export interface CustomActionData {
export type MapStringFunction = (data: FormattedData<TbMapDatasource>, export type MapStringFunction = (data: FormattedData<TbMapDatasource>,
dsData: FormattedData<TbMapDatasource>[]) => string; dsData: FormattedData<TbMapDatasource>[]) => string;
export type MapBooleanFunction = (data: FormattedData<TbMapDatasource>,
dsData: FormattedData<TbMapDatasource>[]) => boolean;
export type MarkerImageFunction = (data: FormattedData<TbMapDatasource>, markerImages: string[], export type MarkerImageFunction = (data: FormattedData<TbMapDatasource>, markerImages: string[],
dsData: FormattedData<TbMapDatasource>[]) => MarkerImageInfo; dsData: FormattedData<TbMapDatasource>[]) => MarkerImageInfo;
@ -1322,11 +1334,11 @@ export const interpolateLineSegment = (
}; };
} }
export const findRotationAngle = (startPoint: FormattedData, endPoint: FormattedData, xKey: string, yKey: string): number => { export const findRotationAngle = (startPoint: L.LatLng, endPoint: L.LatLng): number => {
if (isUndefined(startPoint) || isUndefined(endPoint)) { if (isUndefinedOrNull(startPoint) || isUndefinedOrNull(endPoint)) {
return 0; return 0;
} }
let angle = -Math.atan2(endPoint[xKey] - startPoint[xKey], endPoint[yKey] - startPoint[yKey]); let angle = -Math.atan2(endPoint.lat - startPoint.lat, endPoint.lng - startPoint.lng);
angle = angle * 180 / Math.PI; angle = angle * 180 / Math.PI;
return parseInt(angle.toFixed(2), 10); return parseInt(angle.toFixed(2), 10);
} }

7
ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.ts

@ -27,7 +27,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { TripTimelineSettings } from '@home/components/widget/lib/maps/models/map.models'; import { TripTimelineSettings } from '@home/components/widget/lib/maps/models/map.models';
import { DateFormatProcessor } from '@shared/models/widget-settings.models'; import { DateFormatProcessor } from '@shared/models/widget-settings.models';
import { interval, Observable, Subscription } from 'rxjs'; import { interval, Subscription } from 'rxjs';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -75,6 +75,11 @@ export class MapTimelinePanelComponent implements OnInit {
set currentTime(time: number) { set currentTime(time: number) {
if (this.currentTimeValue !== time) { if (this.currentTimeValue !== time) {
this.currentTimeValue = time; this.currentTimeValue = time;
if (this.hasData) {
this.index = Math.ceil((this.currentTimeValue - this.minValue) / this.settings.timeStep);
} else {
this.index = 0;
}
this.updateTimestampDisplayValue(); this.updateTimestampDisplayValue();
this.cd.markForCheck(); this.cd.markForCheck();
} }

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html

@ -285,6 +285,7 @@
<span matSuffix>px</span> <span matSuffix>px</span>
</mat-form-field> </mat-form-field>
<tb-color-input asBoxInput <tb-color-input asBoxInput
colorClearButton
formControlName="pathDecoratorSymbolColor"> formControlName="pathDecoratorSymbolColor">
</tb-color-input> </tb-color-input>
</div> </div>

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts

@ -155,7 +155,7 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
this.dataLayerFormGroup.addControl('usePathDecorator', this.fb.control(tripsDataLayer.usePathDecorator)); this.dataLayerFormGroup.addControl('usePathDecorator', this.fb.control(tripsDataLayer.usePathDecorator));
this.dataLayerFormGroup.addControl('pathDecoratorSymbol', this.fb.control(tripsDataLayer.pathDecoratorSymbol, Validators.required)); this.dataLayerFormGroup.addControl('pathDecoratorSymbol', this.fb.control(tripsDataLayer.pathDecoratorSymbol, Validators.required));
this.dataLayerFormGroup.addControl('pathDecoratorSymbolSize', this.fb.control(tripsDataLayer.pathDecoratorSymbolSize, [Validators.required, Validators.min(0)])); this.dataLayerFormGroup.addControl('pathDecoratorSymbolSize', this.fb.control(tripsDataLayer.pathDecoratorSymbolSize, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('pathDecoratorSymbolColor', this.fb.control(tripsDataLayer.pathDecoratorSymbolColor, Validators.required)); this.dataLayerFormGroup.addControl('pathDecoratorSymbolColor', this.fb.control(tripsDataLayer.pathDecoratorSymbolColor));
this.dataLayerFormGroup.addControl('pathDecoratorOffset', this.fb.control(tripsDataLayer.pathDecoratorOffset, [Validators.required, Validators.min(0)])); this.dataLayerFormGroup.addControl('pathDecoratorOffset', this.fb.control(tripsDataLayer.pathDecoratorOffset, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('pathEndDecoratorOffset', this.fb.control(tripsDataLayer.pathEndDecoratorOffset, [Validators.required, Validators.min(0)])); this.dataLayerFormGroup.addControl('pathEndDecoratorOffset', this.fb.control(tripsDataLayer.pathEndDecoratorOffset, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('pathDecoratorRepeat', this.fb.control(tripsDataLayer.pathDecoratorRepeat, [Validators.required, Validators.min(0)])); this.dataLayerFormGroup.addControl('pathDecoratorRepeat', this.fb.control(tripsDataLayer.pathDecoratorRepeat, [Validators.required, Validators.min(0)]));

Loading…
Cancel
Save