* 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 <ahalushko@thingboards.io>
Co-authored-by: Adsumus <artemtv42@gmail.com>
This commit is contained in:
ArtemHalushko 2020-03-23 17:18:37 +02:00 committed by GitHub
parent 8d076c951f
commit f4aa56462a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 84 deletions

View File

@ -454,22 +454,21 @@ export function aspectCache(imageUrl: string): Observable<number> {
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 = {
aliasName: alliasArray[0]?.datasource?.aliasName,
entityName: alliasArray[0]?.datasource?.entityName,
$datasource: alliasArray[0]?.datasource,
entityName: entityArray[0]?.datasource?.entityName,
$datasource: entityArray[0]?.datasource,
dsIndex,
time: el[0],
deviceType: null
};
alliasArray.forEach(el => {
obj[el?.dataKey?.label] = el?.data[i][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
if (el?.dataKey?.label === 'type') {
obj.deviceType = el?.data[0][1];
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;
@ -478,15 +477,15 @@ export function parseArray(input: any[]): any[] {
}
export function parseData(input: any[]): any[] {
return _(input).groupBy(el => el?.datasource?.aliasName).values().value().map((alliasArray, i) => {
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,
entityName: entityArray[0]?.datasource?.entityName,
$datasource: entityArray[0]?.datasource,
dsIndex: i,
deviceType: null
};
alliasArray.forEach(el => {
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') {

View File

@ -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<string, Marker> = new Map();
polylines: Map<string, Polyline> = new Map();
polygons: Map<string, Polygon> = new Map();
dragMode = true;
poly: Polyline;
polygon: Polygon;
map: L.Map;
map$: BehaviorSubject<L.Map> = new BehaviorSubject(null);
ready$: Observable<L.Map> = 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<Array<any>>) {
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);
});
}
}

View File

@ -131,4 +131,4 @@ export interface HistorySelectSettings {
buttonColor: string;
}
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolygonSettings;
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;

View File

@ -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(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,

View File

@ -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);
}

View File

@ -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'