From f4aa56462a5a9a9479bc50f92ad71fff29767de7 Mon Sep 17 00:00:00 2001 From: ArtemHalushko <61501795+ArtemHalushko@users.noreply.github.com> Date: Mon, 23 Mar 2020 17:18:37 +0200 Subject: [PATCH] Map/3.0 (#2543) * add base map infrastructure * add leaflet css * add tencent map * add google maps support * added image map support * refactor schemes * here maps support && WIP on markers * add simple marker suppor * data update & polyline support * map bouds support * add some settings support * add map provider select to settings * labels support * WIP on trip animation widget * WIP on history control and route interpolation * trip-animation map provider & custom markers * comleted track marker & history controls * add license headers * label fix & tooltips support * WIP on polygons * marker dropping support * add polygon support * add label to trip animation * WIP on tooltips * lint anf typed leaflet AddMarker * some typing and poly improvements * add typing * add marker creation * update proxy * save position fix * add bounds padding * update map widget bendle && bugfixes * update marker placement widget * add licenses * reomove log * fix sizes * entity and map fixes Co-authored-by: Artem Halushko Co-authored-by: Adsumus --- ui-ngx/src/app/core/utils.ts | 61 +++++++------- .../components/widget/lib/maps/leaflet-map.ts | 81 +++++++++---------- .../components/widget/lib/maps/map-models.ts | 2 +- .../components/widget/lib/maps/markers.ts | 3 +- .../components/widget/lib/maps/polygon.ts | 12 ++- .../components/widget/lib/maps/schemes.ts | 12 +-- 6 files changed, 87 insertions(+), 84 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 8297a80206..39241c10d1 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -454,47 +454,46 @@ export function aspectCache(imageUrl: string): Observable { export function parseArray(input: any[]): any[] { - const alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value(); - return alliases.map((alliasArray, dsIndex) => - alliasArray[0].data.map((el, i) => { + return _(input).groupBy(el => el?.datasource?.entityName) + .values().value().map((entityArray, dsIndex) => + entityArray[0].data.map((el, i) => { + const obj = { + entityName: entityArray[0]?.datasource?.entityName, + $datasource: entityArray[0]?.datasource, + dsIndex, + time: el[0], + deviceType: null + }; + entityArray.forEach(entity => { + obj[entity?.dataKey?.label] = entity?.data[i][1]; + obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0]; + if (entity?.dataKey?.label === 'type') { + obj.deviceType = entity?.data[0][1]; + } + }); + return obj; + }) + ); +} + +export function parseData(input: any[]): any[] { + return _(input).groupBy(el => el?.datasource?.entityName) + .values().value().map((entityArray, i) => { const obj = { - aliasName: alliasArray[0]?.datasource?.aliasName, - entityName: alliasArray[0]?.datasource?.entityName, - $datasource: alliasArray[0]?.datasource, - dsIndex, - time: el[0], + entityName: entityArray[0]?.datasource?.entityName, + $datasource: entityArray[0]?.datasource, + dsIndex: i, deviceType: null }; - alliasArray.forEach(el => { - obj[el?.dataKey?.label] = el?.data[i][1]; + entityArray.forEach(el => { + obj[el?.dataKey?.label] = el?.data[0][1]; obj[el?.dataKey?.label + '|ts'] = el?.data[0][0]; if (el?.dataKey?.label === 'type') { obj.deviceType = el?.data[0][1]; } }); return obj; - }) - ); -} - -export function parseData(input: any[]): any[] { - return _(input).groupBy(el => el?.datasource?.aliasName).values().value().map((alliasArray, i) => { - const obj = { - aliasName: alliasArray[0]?.datasource?.aliasName, - entityName: alliasArray[0]?.datasource?.entityName, - $datasource: alliasArray[0]?.datasource, - dsIndex: i, - deviceType: null - }; - alliasArray.forEach(el => { - obj[el?.dataKey?.label] = el?.data[0][1]; - obj[el?.dataKey?.label + '|ts'] = el?.data[0][0]; - if (el?.dataKey?.label === 'type') { - obj.deviceType = el?.data[0][1]; - } }); - return obj; - }); } export function safeExecute(func: Function, params = []) { 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 842524cd8b..9e22bdc8e3 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,26 +14,27 @@ /// limitations under the License. /// -import L from 'leaflet'; +import L, { LatLngTuple } from 'leaflet'; import 'leaflet-providers'; import 'leaflet.markercluster/dist/MarkerCluster.css' import 'leaflet.markercluster/dist/MarkerCluster.Default.css' import 'leaflet.markercluster/dist/leaflet.markercluster' -import { MapSettings, MarkerSettings, FormattedData, UnitedMapSettings, PolygonSettings } from './map-models'; +import { MapSettings, MarkerSettings, FormattedData, UnitedMapSettings, PolygonSettings, PolylineSettings } from './map-models'; import { Marker } from './markers'; import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; import { filter } from 'rxjs/operators'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; +import { DatasourceData } from '@app/shared/models/widget.models'; export default abstract class LeafletMap { markers: Map = new Map(); + polylines: Map = new Map(); + polygons: Map = new Map(); dragMode = true; - poly: Polyline; - polygon: Polygon; map: L.Map; map$: BehaviorSubject = new BehaviorSubject(null); ready$: Observable = this.map$.pipe(filter(map => !!map)); @@ -78,15 +79,14 @@ export default abstract class LeafletMap { const updatedEnttity = { ...ds, ...customLatLng }; this.saveMarkerLocation(updatedEnttity); this.map.removeLayer(newMarker); - this.deleteMarker(ds.aliasName); - this.createMarker(ds.aliasName, updatedEnttity, this.datasources, this.options, false); + this.deleteMarker(ds.entityName); + this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options, false); } datasourcesList.append(dsItem); }) const popup = L.popup(); popup.setContent(datasourcesList); newMarker.bindPopup(popup).openPopup(); - } addMarker.setPosition('topright') } @@ -165,6 +165,7 @@ export default abstract class LeafletMap { } convertPosition(expression: object): L.LatLng { + if (!expression) return null; const lat = expression[this.options.latKeyName]; const lng = expression[this.options.lngKeyName]; if (isNaN(lat) || isNaN(lng)) @@ -192,11 +193,11 @@ export default abstract class LeafletMap { else { this.options.icon = null; } - if (this.markers.get(data.aliasName)) { - this.updateMarker(data.aliasName, data, markersData, this.options) + if (this.markers.get(data.entityName)) { + this.updateMarker(data.entityName, data, markersData, this.options) } else { - this.createMarker(data.aliasName, data, markersData, this.options as MarkerSettings); + this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings); } } }); @@ -207,16 +208,16 @@ export default abstract class LeafletMap { this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) }); } - private createMarker(key, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, setFocus = true) { + private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, setFocus = true) { this.ready$.subscribe(() => { const newMarker = new Marker(this.map, this.convertPosition(data), settings, data, dataSources, () => { }, this.dragMarker); - if (setFocus && settings.fitMapBounds) + if (setFocus /*&& settings.fitMapBounds*/) this.map.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()).pad(0.2)); this.markers.set(key, newMarker); }); } - private updateMarker(key, data, dataSources, settings: MarkerSettings) { + private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { const marker: Marker = this.markers.get(key); const location = this.convertPosition(data) if (!location.equals(marker.location)) { @@ -229,7 +230,7 @@ export default abstract class LeafletMap { marker.updateMarkerIcon(settings); } - deleteMarker(key) { + deleteMarker(key: string) { let marker = this.markers.get(key)?.leafletMarker; if (marker) { this.map.removeLayer(marker); @@ -240,12 +241,12 @@ export default abstract class LeafletMap { // Polyline - updatePolylines(polyData: Array>) { - polyData.forEach(data => { + updatePolylines(polyData: FormattedData[][]) { + polyData.forEach((data: FormattedData[]) => { if (data.length) { const dataSource = polyData.map(arr => arr[0]); - if (this.poly) { - this.updatePolyline(data, dataSource, this.options); + if (this.polylines.get(data[0].entityName)) { + this.updatePolyline(data[0].entityName, data, dataSource, this.options); } else { this.createPolyline(data, dataSource, this.options); @@ -254,67 +255,59 @@ export default abstract class LeafletMap { }) } - createPolyline(data: any[], dataSources, settings) { + createPolyline(data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) { if (data.length) this.ready$.subscribe(() => { - this.poly = new Polyline(this.map, + const poly = new Polyline(this.map, data.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); - const bounds = this.bounds.extend(this.poly.leafletPoly.getBounds().pad(0.2)); + const bounds = this.bounds.extend(poly.leafletPoly.getBounds().pad(0.2)); if (bounds.isValid()) { this.map.fitBounds(bounds); this.bounds = bounds; } + this.polylines.set(data[0].entityName, poly) }); } - updatePolyline(data, dataSources, settings) { + updatePolyline(key: string, data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) { this.ready$.subscribe(() => { - this.poly.updatePolyline(settings, data, dataSources); - const bounds = this.bounds.extend(this.poly.leafletPoly.getBounds().pad(0.2)); - if (bounds.isValid()) { - this.map.fitBounds(bounds); - this.bounds = bounds; - } + this.polylines.get(key).updatePolyline(settings, data, dataSources); }); } // Polygon - updatePolygons(polyData: any[]) { - polyData.forEach((data: any) => { + updatePolygons(polyData: DatasourceData[]) { + polyData.forEach((data: DatasourceData) => { if (data.data.length && data.dataKey.name === this.options.polygonKeyName) { if (typeof (data?.data[0][1]) === 'string') { - data.data = JSON.parse(data.data[0][1]); + data.data = JSON.parse(data.data[0][1]) as LatLngTuple[]; } - if (this.polygon) { - this.updatePolygon(data.data, polyData, this.options); + if (this.polygons.get(data.datasource.entityName)) { + this.updatePolygon(data.datasource.entityName, data.data, polyData, this.options); } else { - this.createPolygon(data.data, polyData, this.options); + this.createPolygon(data.datasource.entityName, data.data, polyData, this.options); } } }); } - createPolygon(data: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { + createPolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { this.ready$.subscribe(() => { - this.polygon = new Polygon(this.map, data, dataSources, settings); - const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds().pad(0.2)); + const polygon = new Polygon(this.map, data, dataSources, settings); + const bounds = this.bounds.extend(polygon.leafletPoly.getBounds().pad(0.2)); if (bounds.isValid()) { this.map.fitBounds(bounds); this.bounds = bounds; } + this.polygons.set(key, polygon); }); } - updatePolygon(data, dataSources, settings) { + updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { this.ready$.subscribe(() => { - // this.polygon.updatePolygon(settings, data, dataSources); - const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds().pad(0.2)); - if (bounds.isValid()) { - this.map.fitBounds(bounds); - this.bounds = bounds; - } + this.polygons.get(key).updatePolygon(data, dataSources, settings); }); } } \ No newline at end of file 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 b7816d8986..1d2c993ef1 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 @@ -131,4 +131,4 @@ export interface HistorySelectSettings { buttonColor: string; } -export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolygonSettings; \ No newline at end of file +export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings; \ No newline at end of file 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 ca5400d4f6..0903c5cd77 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 @@ -74,7 +74,8 @@ export class Marker { if (settings.showLabel) { if (settings.useLabelFunction) { - settings.labelText = safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) + settings.labelText = parseTemplate( + safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]), this.data) } else settings.labelText = parseTemplate(settings.label, this.data); this.leafletMarker.bindTooltip(`
${settings.labelText}
`, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts index 80d0bce7f8..ca601e3e15 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts @@ -14,14 +14,17 @@ /// limitations under the License. /// -import L, { LatLngExpression } from 'leaflet'; +import L, { LatLngExpression, LatLngTuple } from 'leaflet'; import { createTooltip } from './maps-utils'; import { PolygonSettings } from './map-models'; +import { DatasourceData } from '@app/shared/models/widget.models'; export class Polygon { leafletPoly: L.Polygon; tooltip; + data; + dataSources; constructor(public map, coordinates, dataSources, settings: PolygonSettings, onClickListener?) { this.leafletPoly = L.polygon(coordinates, { @@ -41,6 +44,13 @@ export class Polygon { } } + updatePolygon(data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) { + this.data = data; + this.dataSources = dataSources; + this.leafletPoly.setLatLngs(data); + this.updatePolygonColor(settings); + } + removePolygon() { this.map.removeLayer(this.leafletPoly); } 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 f2bcf35f15..4222ad11fd 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 @@ -253,6 +253,11 @@ export const commonMapSettingsSchema = type: 'boolean', default: true }, + draggableMarker: { + title: 'Draggable Marker', + type: 'boolean', + default: false + }, disableScrollZooming: { title: 'Disable scroll zooming', type: 'boolean', @@ -371,11 +376,6 @@ export const commonMapSettingsSchema = title: 'Polygon Color function: f(data, dsData, dsIndex)', type: 'string' }, - draggableMarker: { - title: 'Draggable Marker', - type: 'boolean', - default: false - }, markerImage: { title: 'Custom marker image', type: 'string' @@ -410,13 +410,13 @@ export const commonMapSettingsSchema = 'useDefaultCenterPosition', 'defaultCenterPosition', 'fitMapBounds', + 'draggableMarker', 'disableScrollZooming', 'latKeyName', 'lngKeyName', 'showLabel', 'label', 'useLabelFunction', - 'draggableMarker', { key: 'labelFunction', type: 'javascript'