From 4890125423912e4f86104c294a38bef610ed8ba4 Mon Sep 17 00:00:00 2001 From: Artem Halushko Date: Wed, 6 May 2020 19:09:41 +0300 Subject: [PATCH] trip-animation points & anchors --- .../components/widget/lib/maps/leaflet-map.ts | 77 ++++++++++++------- .../components/widget/lib/maps/map-models.ts | 8 +- .../components/widget/lib/maps/markers.ts | 1 - .../widget/lib/maps/providers/image-map.ts | 2 +- .../components/widget/lib/maps/schemes.ts | 38 +-------- .../trip-animation.component.html | 2 +- .../trip-animation.component.ts | 42 ++++++---- .../history-selector.component.ts | 26 ++++++- 8 files changed, 113 insertions(+), 83 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 93477c1782..31db505122 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -14,18 +14,18 @@ /// limitations under the License. /// -import L, { LatLngBounds, LatLngTuple, markerClusterGroup, MarkerClusterGroupOptions } from 'leaflet'; +import L, { LatLngBounds, LatLngTuple, markerClusterGroup, MarkerClusterGroupOptions, FeatureGroup, LayerGroup } from 'leaflet'; import 'leaflet-providers'; import 'leaflet.markercluster/dist/leaflet.markercluster'; import { - FormattedData, - MapSettings, - MarkerSettings, - PolygonSettings, - PolylineSettings, - UnitedMapSettings + FormattedData, + MapSettings, + MarkerSettings, + PolygonSettings, + PolylineSettings, + UnitedMapSettings } from './map-models'; import { Marker } from './markers'; import { BehaviorSubject, Observable } from 'rxjs'; @@ -33,7 +33,7 @@ import { filter } from 'rxjs/operators'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; import { DatasourceData } from '@app/shared/models/widget.models'; -import { safeExecute } from '@home/components/widget/lib/maps/maps-utils'; +import { safeExecute, createTooltip } from '@home/components/widget/lib/maps/maps-utils'; export default abstract class LeafletMap { @@ -48,6 +48,7 @@ export default abstract class LeafletMap { bounds: L.LatLngBounds; datasources: FormattedData[]; markersCluster; + points: FeatureGroup; protected constructor(public $container: HTMLElement, options: UnitedMapSettings) { this.options = options; @@ -158,9 +159,9 @@ export default abstract class LeafletMap { this.map = map; if (this.options.useDefaultCenterPosition) { this.map.panTo(this.options.defaultCenterPosition); - this.bounds = map.getBounds(); + // this.bounds = map.getBounds(); } - else this.bounds = new L.LatLngBounds(null, null); + // else this.bounds = new L.LatLngBounds(null, null); if (this.options.draggableMarker) { this.addMarkerControl(); } @@ -201,9 +202,9 @@ export default abstract class LeafletMap { return this.map.getCenter(); } - fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { + fitBounds(bounds: LatLngBounds, padding?: LatLngTuple) { if (bounds.isValid()) { - this.bounds = this.bounds.extend(bounds); + this.bounds = !!this.bounds ? this.bounds.extend(bounds) : bounds; if (!this.options.fitMapBounds && this.options.defaultZoomLevel) { this.map.setZoom(this.options.defaultZoomLevel, { animate: false }); if (this.options.useDefaultCenterPosition) { @@ -219,9 +220,9 @@ export default abstract class LeafletMap { } }); if (this.options.useDefaultCenterPosition) { - bounds = bounds.extend(this.options.defaultCenterPosition); + this.bounds = this.bounds.extend(this.options.defaultCenterPosition); } - this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false }); + this.map.fitBounds(this.bounds, { padding: padding || [50, 50], animate: false }); } } } @@ -253,11 +254,10 @@ export default abstract class LeafletMap { const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; this.options.icon = L.divIcon({ html: `
` - }) + style="transform: translate(-10px, -10px) + rotate(${data.rotationAngle}deg); + ${style}">
` + }); } else { this.options.icon = null; @@ -279,7 +279,8 @@ export default abstract class LeafletMap { private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { this.ready$.subscribe(() => { const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); - this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), settings.draggableMarker && this.markers.size < 2); + if (this.bounds) + this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); this.markers.set(key, newMarker); if (this.options.useClusterMarkers) { this.markersCluster.addLayer(newMarker.leafletMarker); @@ -314,6 +315,29 @@ export default abstract class LeafletMap { } } + updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { + this.map$.subscribe(map => { + if (this.points) { + map.removeLayer(this.points); + } + this.points = new FeatureGroup(); + pointsData.filter(pdata => !!this.convertPosition(pdata)).forEach(data => { + const point = L.circleMarker(this.convertPosition(data), { + color: this.options.pointColor, + radius: this.options.pointSize + }); + if (!this.options.pointTooltipOnRightPanel) { + point.on('click', () => getTooltip(data)); + } + else { + createTooltip(point, this.options, pointsData, getTooltip(data, false)); + } + this.points.addLayer(point); + }); + map.addLayer(this.points); + }); + } + setImageAlias(alias: Observable) { } @@ -338,15 +362,17 @@ export default abstract class LeafletMap { this.ready$.subscribe(() => { const poly = new Polyline(this.map, data.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); - const bounds = this.bounds.extend(poly.leafletPoly.getBounds()); - this.fitBounds(bounds) - this.polylines.set(data[0].entityName, poly) + const bounds = poly.leafletPoly.getBounds(); + this.fitBounds(bounds); + this.polylines.set(data[0].entityName, poly); }); } updatePolyline(key: string, data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) { this.ready$.subscribe(() => { - this.polylines.get(key).updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources); + const poly = this.polylines.get(key); + poly.updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources); + const bounds = poly.leafletPoly.getBounds(); }); } @@ -371,7 +397,7 @@ export default abstract class LeafletMap { createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) { this.ready$.subscribe(() => { const polygon = new Polygon(this.map, polyData, dataSources, settings); - const bounds = this.bounds.extend(polygon.leafletPoly.getBounds()); + const bounds = polygon.leafletPoly.getBounds(); this.fitBounds(bounds); this.polygons.set(polyData.datasource.entityName, polygon); }); @@ -381,7 +407,6 @@ export default abstract class LeafletMap { this.ready$.subscribe(() => { const poly = this.polygons.get(polyData.datasource.entityName); poly.updatePolygon(polyData.data, dataSources, settings); - this.fitBounds(poly.leafletPoly.getBounds()); }); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 3532514a56..d05a2b4e87 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -159,9 +159,15 @@ export interface HistorySelectSettings { buttonColor: string; } +export type TripAnimationSttings = { + pointColor: string; + pointSize: number; + pointTooltipOnRightPanel: boolean; +} + export type actionsHandler = ($event: Event, datasource: Datasource) => void; -export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings; +export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings & TripAnimationSttings; interface IProvider { MapClass: Type, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index 388f08eda5..eb14f99570 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -113,7 +113,6 @@ export class Marker { [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage; const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex(); - if (currentImage && currentImage.url) { aspectCache(currentImage.url).subscribe( (aspect) => { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index bd12185ad6..bd6d3fe89f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -112,7 +112,7 @@ export class ImageMap extends LeafletMap { } } - fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { } + fitBounds(bounds: LatLngBounds, padding?: LatLngTuple) { } initMap(updateImage?) { if (!this.map && this.aspect > 0) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts index 2b6546f26b..1a293cc496 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts @@ -923,35 +923,7 @@ export const pathSchema = title: 'Decorator repeat', type: 'string', default: '20px' - }, - 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 - }, + } }, required: [] }, @@ -986,13 +958,7 @@ export const pathSchema = }, { key: 'decoratorRepeat', type: 'textarea' - }, 'showPoints', { - key: 'pointColor', - type: 'color' - }, 'pointSize', 'usePointAsAnchor', { - key: 'pointAsAnchorFunction', - type: 'javascript' - }, 'pointTooltipOnRightPanel', + } ] }; diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html index b973284b53..9449e3aba9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html @@ -32,6 +32,6 @@ [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
-
\ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts index b80fa1bada..7dfec08c9f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts @@ -21,7 +21,7 @@ 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 } from '../lib/maps/map-models'; +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 { DomSanitizer } from '@angular/platform-browser'; @@ -58,6 +58,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { label; minTime; maxTime; + anchors = []; + useAnchors = false; static getSettingsSchema(): JsonSettingsSchema { const schema = initSchema(); @@ -67,8 +69,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { addGroupInfo(schema, 'Trip Animation Settings'); addToSchema(schema, pathSchema); addGroupInfo(schema, 'Path Settings'); - addToSchema(schema, addCondition(pointSchema, 'model.showPoint === true', ['showPoint'])); - addGroupInfo(schema, 'Polygon Settings'); + addToSchema(schema, addCondition(pointSchema, 'model.showPoints === true', ['showPoints'])); + addGroupInfo(schema, 'Path Points Settings'); addToSchema(schema, addCondition(mapPolygonSchema, 'model.showPolygon === true', ['showPolygon'])); addGroupInfo(schema, 'Polygon Settings'); return schema; @@ -84,14 +86,15 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { rotationAngle: 0 } this.settings = { ...settings, ...this.ctx.settings }; + this.useAnchors = this.settings.usePointAsAnchor && this.settings.showPoints; + 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.updatePolylines(this.interpolatedData.map(ds => _.values(ds))); - this.mapWidget.map.map?.invalidateSize(); this.cd.detectChanges(); } @@ -110,11 +113,16 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { this.calcLabel(); this.calcTooltip(); if (this.mapWidget) { + this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds))); if (this.settings.showPolygon) { this.mapWidget.map.updatePolygons(this.interpolatedData); } - if(this.settings.showPoint){ - this.mapWidget.map.updateMarkers(this.interpolatedData) + 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.updateMarkers(currentPosition); } @@ -126,23 +134,29 @@ 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); }); } - calcTooltip() { - const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime } - const tooltipText: string = this.settings.useTooltipFunction ? + calcTooltip = (point?: FormattedData, setTooltip = true) => { + if (!point) { + point = this.activeTrip; + } + const data = { ...point, maxTime: this.maxTime, minTime: this.minTime } + const tooltipPattern: string = this.settings.useTooltipFunction ? safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern; - this.mainTooltip = this.sanitizer.sanitize( - SecurityContext.HTML, (parseWithTranslation.parseTemplate(tooltipText, data, true))); + const tooltipText = parseWithTranslation.parseTemplate(tooltipPattern, data, true); + if (setTooltip) { + this.mainTooltip = this.sanitizer.sanitize( + SecurityContext.HTML, tooltipText); + this.cd.detectChanges(); + } + return tooltipText; } calcLabel() { diff --git a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts index 252720033c..a15b892349 100644 --- a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts +++ b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts @@ -28,6 +28,8 @@ export class HistorySelectorComponent implements OnInit, OnChanges { @Input() settings: HistorySelectSettings @Input() intervals = []; + @Input() anchors = []; + @Input() useAnchors = false; @Output() timeUpdated: EventEmitter = new EventEmitter(); @@ -56,7 +58,7 @@ export class HistorySelectorComponent implements OnInit, OnChanges { this.interval = interval(1000 / this.speed) .pipe( filter(() => this.playing)).subscribe(() => { - this.index++; + this.index++; if (this.index < this.maxTimeIndex) { this.cd.detectChanges(); this.timeUpdated.emit(this.intervals[this.index]); @@ -91,14 +93,24 @@ export class HistorySelectorComponent implements OnInit, OnChanges { moveNext() { if (this.index < this.maxTimeIndex) { - this.index++; + 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.pause(); } movePrev() { if (this.index > this.minTimeIndex) { - this.index++; + 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.pause(); } @@ -113,6 +125,14 @@ 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]); }