From 679bab70c8164dc0c0e589126935c71bcd9a2994 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 24 Jun 2020 20:25:50 +0300 Subject: [PATCH] UI: Update maps to subscribe for paginated data --- ui-ngx/src/app/core/api/widget-api.models.ts | 3 + .../src/app/core/api/widget-subscription.ts | 7 + .../components/widget/lib/maps/leaflet-map.ts | 202 ++++++++++++------ .../components/widget/lib/maps/map-models.ts | 2 + .../components/widget/lib/maps/map-widget2.ts | 25 ++- .../trip-animation.component.ts | 4 +- 6 files changed, 171 insertions(+), 72 deletions(-) diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 3069b7445d..cc5cceb4c2 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -282,6 +282,9 @@ export interface IWidgetSubscription { subscribe(): void; + subscribeAllForPaginatedData(pageLink: EntityDataPageLink, + keyFilters: KeyFilter[]): void; + subscribeForPaginatedData(datasourceIndex: number, pageLink: EntityDataPageLink, keyFilters: KeyFilter[]): void; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 7dc932de8f..55f5bc4469 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -884,6 +884,13 @@ export class WidgetSubscription implements IWidgetSubscription { } } + subscribeAllForPaginatedData(pageLink: EntityDataPageLink, + keyFilters: KeyFilter[]): void { + this.configuredDatasources.forEach((datasource, datasourceIndex) => { + this.subscribeForPaginatedData(datasourceIndex, pageLink, keyFilters); + }); + } + subscribeForPaginatedData(datasourceIndex: number, pageLink: EntityDataPageLink, keyFilters: KeyFilter[]): void { 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 3d11b704c0..23316075db 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 @@ -32,8 +32,9 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { filter } from 'rxjs/operators'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; -import { createTooltip, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; +import { createTooltip, parseArray, parseData, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; import { WidgetContext } from '@home/models/widget-component.models'; +import { DatasourceData } from '@shared/models/widget.models'; export default abstract class LeafletMap { @@ -247,32 +248,75 @@ export default abstract class LeafletMap { } } - // Markers - updateMarkers(markersData: FormattedData[], callback?) { - markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => { + updateData(data: DatasourceData[], drawRoutes: boolean, showPolygon: boolean) { + this.ready$.subscribe(() => { + if (drawRoutes) { + this.updatePolylines(parseArray(data), false); + } + if (showPolygon) { + this.updatePolygons(parseData(data), false); + } + this.updateMarkers(parseData(data), false); + this.updateBoundsInternal(drawRoutes, showPolygon); + }); + } + + private updateBoundsInternal(drawRoutes: boolean, showPolygon: boolean) { + this.bounds = new L.LatLngBounds(null, null); + if (drawRoutes) { + this.polylines.forEach((polyline) => { + this.bounds.extend(polyline.leafletPoly.getBounds()); + }); + } + if (showPolygon) { + this.polygons.forEach((polygon) => { + this.bounds.extend(polygon.leafletPoly.getBounds()); + }); + } + this.markers.forEach((marker) => { + this.bounds.extend(marker.leafletMarker.getLatLng()); + }); + this.fitBounds(this.bounds); + } + + // Markers + updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) { + const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata)); + this.ready$.subscribe(() => { + const keys: string[] = []; + rawMarkers.forEach(data => { if (data.rotationAngle || data.rotationAngle === 0) { - const currentImage = this.options.useMarkerImageFunction ? - safeExecute(this.options.markerImageFunction, - [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage; - const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; - this.options.icon = L.divIcon({ - html: `
` - }); - } - else { - this.options.icon = null; + }); + } else { + this.options.icon = null; } if (this.markers.get(data.entityName)) { - this.updateMarker(data.entityName, data, markersData, this.options) + this.updateMarker(data.entityName, data, markersData, this.options) + } else { + this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); } - else { - this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, callback); + keys.push(data.entityName); + }); + const toDelete: string[] = []; + for (const mKey in this.markers.keys()) { + if (!keys.includes(mKey)) { + toDelete.push(mKey); } + } + toDelete.forEach((key) => { + this.deleteMarker(key); + }); + this.markersData = markersData; }); - this.markersData = markersData; } dragMarker = (e, data = {}) => { @@ -280,21 +324,20 @@ export default abstract class LeafletMap { this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) }); } - private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, callback?) { - this.ready$.subscribe(() => { - const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); - if (callback) - newMarker.leafletMarker.on('click', () => { callback(data, true) }); - 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); - } - else { - this.map.addLayer(newMarker.leafletMarker); - } - }); + private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, + updateBounds = true, callback?) { + const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); + if (callback) + newMarker.leafletMarker.on('click', () => { callback(data, true) }); + if (this.bounds && updateBounds) + this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); + this.markers.set(key, newMarker); + if (this.options.useClusterMarkers) { + this.markersCluster.addLayer(newMarker.leafletMarker); + } + else { + this.map.addLayer(newMarker.leafletMarker); + } } private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { @@ -306,8 +349,9 @@ export default abstract class LeafletMap { if (settings.showTooltip) { marker.updateMarkerTooltip(data); } - if (settings.useClusterMarkers) - this.markersCluster.refreshClusters() + if (settings.useClusterMarkers) { + this.markersCluster.refreshClusters() + } marker.setDataSources(data, dataSources); marker.updateMarkerIcon(settings); } @@ -315,7 +359,11 @@ export default abstract class LeafletMap { deleteMarker(key: string) { let marker = this.markers.get(key)?.leafletMarker; if (marker) { - this.map.removeLayer(marker); + if (this.options.useClusterMarkers) { + this.markersCluster.removeLayer(marker); + } else { + this.map.removeLayer(marker); + } this.markers.delete(key); marker = null; } @@ -346,41 +394,49 @@ export default abstract class LeafletMap { // Polyline - updatePolylines(polyData: FormattedData[][], data?: FormattedData) { + updatePolylines(polyData: FormattedData[][], updateBounds = true, data?: FormattedData) { + const keys: string[] = []; polyData.forEach((dataSource: FormattedData[]) => { data = data || dataSource[0]; if (dataSource.length && data.entityName === dataSource[0].entityName) { if (this.polylines.get(data.entityName)) { - this.updatePolyline(data, dataSource, this.options); + this.updatePolyline(data, dataSource, this.options, updateBounds); + } else { + this.createPolyline(data, dataSource, this.options, updateBounds); } - else { - this.createPolyline(data, dataSource, this.options); - } - } - else { - if (data) - this.removePolyline(dataSource[0]?.entityName) + keys.push(data.entityName); } - }) + }); + const toDelete: string[] = []; + for (const mKey in this.polylines.keys()) { + if (!keys.includes(mKey)) { + toDelete.push(mKey); + } + } + toDelete.forEach((key) => { + this.removePolyline(key); + }); } - createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) { + createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) { this.ready$.subscribe(() => { const poly = new Polyline(this.map, dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); - const bounds = poly.leafletPoly.getBounds(); - this.fitBounds(bounds); + if (updateBounds) { + const bounds = poly.leafletPoly.getBounds(); + this.fitBounds(bounds); + } this.polylines.set(data.entityName, poly); }); } - updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) { + updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) { this.ready$.subscribe(() => { const poly = this.polylines.get(data.entityName); const oldBounds = poly.leafletPoly.getBounds(); poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); const newBounds = poly.leafletPoly.getBounds(); - if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) { + if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) { this.fitBounds(newBounds); } }); @@ -399,40 +455,60 @@ export default abstract class LeafletMap { // Polygon - updatePolygons(polyData: FormattedData[]) { + updatePolygons(polyData: FormattedData[], updateBounds = true) { + const keys: string[] = []; polyData.forEach((data: FormattedData) => { if (data && data.hasOwnProperty(this.options.polygonKeyName)) { if (typeof (data[this.options.polygonKeyName]) === 'string') { data[this.options.polygonKeyName] = JSON.parse(data[this.options.polygonKeyName]) as LatLngTuple[]; } - if (this.polygons.get(data.$datasource.entityName)) { - this.updatePolygon(data, polyData, this.options); - } - else { - this.createPolygon(data, polyData, this.options); + if (this.polygons.get(data.entityName)) { + this.updatePolygon(data, polyData, this.options, updateBounds); + } else { + this.createPolygon(data, polyData, this.options, updateBounds); } + keys.push(data.entityName); } }); + const toDelete: string[] = []; + for (const mKey in this.polygons.keys()) { + if (!keys.includes(mKey)) { + toDelete.push(mKey); + } + } + toDelete.forEach((key) => { + this.removePolygon(key); + }); } - createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { + createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) { this.ready$.subscribe(() => { const polygon = new Polygon(this.map, polyData, dataSources, settings); - const bounds = polygon.leafletPoly.getBounds(); - this.fitBounds(bounds); - this.polygons.set(polyData.$datasource.entityName, polygon); + if (updateBounds) { + const bounds = polygon.leafletPoly.getBounds(); + this.fitBounds(bounds); + } + this.polygons.set(polyData.entityName, polygon); }); } - updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { + updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) { this.ready$.subscribe(() => { const poly = this.polygons.get(polyData.entityName); const oldBounds = poly.leafletPoly.getBounds(); poly.updatePolygon(polyData, dataSources, settings); const newBounds = poly.leafletPoly.getBounds(); - if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) { + if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) { this.fitBounds(newBounds); } }); } + + removePolygon(name: string) { + const poly = this.polygons.get(name); + if (poly) { + this.map.removeLayer(poly.leafletPoly); + this.polygons.delete(name); + } + } } 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 7bf1b8fb21..a6527a3ec2 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 @@ -24,6 +24,8 @@ import { googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema } from './schemes'; +export const DEFAULT_MAP_PAGE_SIZE = 1024; + export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; export type GetTooltip = (point: FormattedData, setTooltip?: boolean) => string; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index 60c7b42a85..156d299d2d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -14,7 +14,14 @@ /// limitations under the License. /// -import { defaultSettings, hereProviders, MapProviders, providerSets, UnitedMapSettings } from './map-models'; +import { + DEFAULT_MAP_PAGE_SIZE, + defaultSettings, + hereProviders, + MapProviders, + providerSets, + UnitedMapSettings +} from './map-models'; import LeafletMap from './leaflet-map'; import { commonMapSettingsSchema, @@ -35,6 +42,7 @@ import { AttributeService } from '@core/http/attribute.service'; import { TranslateService } from '@ngx-translate/core'; import { UtilsService } from '@core/services/utils.service'; import _ from 'lodash'; +import { EntityDataPageLink } from '@shared/models/query/query.models'; // @dynamic export class MapWidgetController implements MapWidgetInterface { @@ -73,6 +81,13 @@ export class MapWidgetController implements MapWidgetInterface { if (this.settings.draggableMarker) { this.map.setDataSources(parseData(this.data)); } + this.pageLink = { + page: 0, + pageSize: DEFAULT_MAP_PAGE_SIZE, + textSearch: null, + dynamic: true + }; + this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null); } map: LeafletMap; @@ -80,6 +95,7 @@ export class MapWidgetController implements MapWidgetInterface { schema: JsonSettingsSchema; data: DatasourceData[]; settings: UnitedMapSettings; + pageLink: EntityDataPageLink; public static dataKeySettingsSchema(): object { return {}; @@ -241,12 +257,7 @@ export class MapWidgetController implements MapWidgetInterface { } update() { - if (this.drawRoutes) - this.map.updatePolylines(parseArray(this.data)); - if (this.settings.showPolygon) { - this.map.updatePolygons(parseData(this.data)); - } - this.map.updateMarkers(parseData(this.data)); + this.map.updateData(this.data, this.drawRoutes, this.settings.showPolygon); } resize() { 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 bad22d6d47..8d16cdb929 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 @@ -161,14 +161,14 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy this.calcLabel(); this.calcTooltip(currentPosition.find(position => position.entityName === this.activeTrip.entityName)); if (this.mapWidget) { - this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), this.activeTrip); + this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), true, this.activeTrip); if (this.settings.showPolygon) { this.mapWidget.map.updatePolygons(this.interpolatedTimeData); } if (this.settings.showPoints) { this.mapWidget.map.updatePoints(_.values(_.union(this.interpolatedTimeData)[0]), this.calcTooltip); } - this.mapWidget.map.updateMarkers(currentPosition, (trip) => { + this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => { this.activeTrip = trip; this.timeUpdated(this.currentTime) });