Browse Source

trip-animation points & anchors

pull/2738/head
Artem Halushko 6 years ago
parent
commit
4890125423
  1. 77
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  2. 8
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  3. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
  4. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  5. 38
      ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts
  6. 2
      ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html
  7. 42
      ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
  8. 26
      ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts

77
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: `<div class="arrow"
style="transform: translate(-10px, -10px);
${style}
rotate(${data.rotationAngle}deg);
"><div>`
})
style="transform: translate(-10px, -10px)
rotate(${data.rotationAngle}deg);
${style}"><div>`
});
}
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<any>) {
}
@ -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());
});
}
}

8
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<LeafletMap>,

1
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) => {

2
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) {

38
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',
}
]
};

2
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}">
</div>
</div>
<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals"
<tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals" [anchors]="anchors" [useAnchors]="useAnchors"
(timeUpdated)="timeUpdated($event)"></tb-history-selector>
</div>

42
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() {

26
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<number> = 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]);
}

Loading…
Cancel
Save