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 <ahalushko@thingboards.io> Co-authored-by: Adsumus <artemtv42@gmail.com>
This commit is contained in:
parent
8d076c951f
commit
f4aa56462a
@ -454,47 +454,46 @@ export function aspectCache(imageUrl: string): Observable<number> {
|
|||||||
|
|
||||||
|
|
||||||
export function parseArray(input: any[]): any[] {
|
export function parseArray(input: any[]): any[] {
|
||||||
const alliases: any = _(input).groupBy(el => el?.datasource?.aliasName).values().value();
|
return _(input).groupBy(el => el?.datasource?.entityName)
|
||||||
return alliases.map((alliasArray, dsIndex) =>
|
.values().value().map((entityArray, dsIndex) =>
|
||||||
alliasArray[0].data.map((el, i) => {
|
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 = {
|
const obj = {
|
||||||
aliasName: alliasArray[0]?.datasource?.aliasName,
|
entityName: entityArray[0]?.datasource?.entityName,
|
||||||
entityName: alliasArray[0]?.datasource?.entityName,
|
$datasource: entityArray[0]?.datasource,
|
||||||
$datasource: alliasArray[0]?.datasource,
|
dsIndex: i,
|
||||||
dsIndex,
|
|
||||||
time: el[0],
|
|
||||||
deviceType: null
|
deviceType: null
|
||||||
};
|
};
|
||||||
alliasArray.forEach(el => {
|
entityArray.forEach(el => {
|
||||||
obj[el?.dataKey?.label] = el?.data[i][1];
|
obj[el?.dataKey?.label] = el?.data[0][1];
|
||||||
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
|
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
|
||||||
if (el?.dataKey?.label === 'type') {
|
if (el?.dataKey?.label === 'type') {
|
||||||
obj.deviceType = el?.data[0][1];
|
obj.deviceType = el?.data[0][1];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return obj;
|
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 = []) {
|
export function safeExecute(func: Function, params = []) {
|
||||||
|
|||||||
@ -14,26 +14,27 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import L from 'leaflet';
|
import L, { LatLngTuple } from 'leaflet';
|
||||||
|
|
||||||
import 'leaflet-providers';
|
import 'leaflet-providers';
|
||||||
import 'leaflet.markercluster/dist/MarkerCluster.css'
|
import 'leaflet.markercluster/dist/MarkerCluster.css'
|
||||||
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
|
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
|
||||||
import 'leaflet.markercluster/dist/leaflet.markercluster'
|
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 { Marker } from './markers';
|
||||||
import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
|
import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
import { Polyline } from './polyline';
|
import { Polyline } from './polyline';
|
||||||
import { Polygon } from './polygon';
|
import { Polygon } from './polygon';
|
||||||
|
import { DatasourceData } from '@app/shared/models/widget.models';
|
||||||
|
|
||||||
export default abstract class LeafletMap {
|
export default abstract class LeafletMap {
|
||||||
|
|
||||||
markers: Map<string, Marker> = new Map();
|
markers: Map<string, Marker> = new Map();
|
||||||
|
polylines: Map<string, Polyline> = new Map();
|
||||||
|
polygons: Map<string, Polygon> = new Map();
|
||||||
dragMode = true;
|
dragMode = true;
|
||||||
poly: Polyline;
|
|
||||||
polygon: Polygon;
|
|
||||||
map: L.Map;
|
map: L.Map;
|
||||||
map$: BehaviorSubject<L.Map> = new BehaviorSubject(null);
|
map$: BehaviorSubject<L.Map> = new BehaviorSubject(null);
|
||||||
ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map));
|
ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map));
|
||||||
@ -78,15 +79,14 @@ export default abstract class LeafletMap {
|
|||||||
const updatedEnttity = { ...ds, ...customLatLng };
|
const updatedEnttity = { ...ds, ...customLatLng };
|
||||||
this.saveMarkerLocation(updatedEnttity);
|
this.saveMarkerLocation(updatedEnttity);
|
||||||
this.map.removeLayer(newMarker);
|
this.map.removeLayer(newMarker);
|
||||||
this.deleteMarker(ds.aliasName);
|
this.deleteMarker(ds.entityName);
|
||||||
this.createMarker(ds.aliasName, updatedEnttity, this.datasources, this.options, false);
|
this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options, false);
|
||||||
}
|
}
|
||||||
datasourcesList.append(dsItem);
|
datasourcesList.append(dsItem);
|
||||||
})
|
})
|
||||||
const popup = L.popup();
|
const popup = L.popup();
|
||||||
popup.setContent(datasourcesList);
|
popup.setContent(datasourcesList);
|
||||||
newMarker.bindPopup(popup).openPopup();
|
newMarker.bindPopup(popup).openPopup();
|
||||||
|
|
||||||
}
|
}
|
||||||
addMarker.setPosition('topright')
|
addMarker.setPosition('topright')
|
||||||
}
|
}
|
||||||
@ -165,6 +165,7 @@ export default abstract class LeafletMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
convertPosition(expression: object): L.LatLng {
|
convertPosition(expression: object): L.LatLng {
|
||||||
|
if (!expression) return null;
|
||||||
const lat = expression[this.options.latKeyName];
|
const lat = expression[this.options.latKeyName];
|
||||||
const lng = expression[this.options.lngKeyName];
|
const lng = expression[this.options.lngKeyName];
|
||||||
if (isNaN(lat) || isNaN(lng))
|
if (isNaN(lat) || isNaN(lng))
|
||||||
@ -192,11 +193,11 @@ export default abstract class LeafletMap {
|
|||||||
else {
|
else {
|
||||||
this.options.icon = null;
|
this.options.icon = null;
|
||||||
}
|
}
|
||||||
if (this.markers.get(data.aliasName)) {
|
if (this.markers.get(data.entityName)) {
|
||||||
this.updateMarker(data.aliasName, data, markersData, this.options)
|
this.updateMarker(data.entityName, data, markersData, this.options)
|
||||||
}
|
}
|
||||||
else {
|
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) });
|
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(() => {
|
this.ready$.subscribe(() => {
|
||||||
const newMarker = new Marker(this.map, this.convertPosition(data), settings, data, dataSources, () => { }, this.dragMarker);
|
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.map.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()).pad(0.2));
|
||||||
this.markers.set(key, newMarker);
|
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 marker: Marker = this.markers.get(key);
|
||||||
const location = this.convertPosition(data)
|
const location = this.convertPosition(data)
|
||||||
if (!location.equals(marker.location)) {
|
if (!location.equals(marker.location)) {
|
||||||
@ -229,7 +230,7 @@ export default abstract class LeafletMap {
|
|||||||
marker.updateMarkerIcon(settings);
|
marker.updateMarkerIcon(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteMarker(key) {
|
deleteMarker(key: string) {
|
||||||
let marker = this.markers.get(key)?.leafletMarker;
|
let marker = this.markers.get(key)?.leafletMarker;
|
||||||
if (marker) {
|
if (marker) {
|
||||||
this.map.removeLayer(marker);
|
this.map.removeLayer(marker);
|
||||||
@ -240,12 +241,12 @@ export default abstract class LeafletMap {
|
|||||||
|
|
||||||
// Polyline
|
// Polyline
|
||||||
|
|
||||||
updatePolylines(polyData: Array<Array<any>>) {
|
updatePolylines(polyData: FormattedData[][]) {
|
||||||
polyData.forEach(data => {
|
polyData.forEach((data: FormattedData[]) => {
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
const dataSource = polyData.map(arr => arr[0]);
|
const dataSource = polyData.map(arr => arr[0]);
|
||||||
if (this.poly) {
|
if (this.polylines.get(data[0].entityName)) {
|
||||||
this.updatePolyline(data, dataSource, this.options);
|
this.updatePolyline(data[0].entityName, data, dataSource, this.options);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.createPolyline(data, dataSource, this.options);
|
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)
|
if (data.length)
|
||||||
this.ready$.subscribe(() => {
|
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);
|
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()) {
|
if (bounds.isValid()) {
|
||||||
this.map.fitBounds(bounds);
|
this.map.fitBounds(bounds);
|
||||||
this.bounds = 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.ready$.subscribe(() => {
|
||||||
this.poly.updatePolyline(settings, data, dataSources);
|
this.polylines.get(key).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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Polygon
|
// Polygon
|
||||||
|
|
||||||
updatePolygons(polyData: any[]) {
|
updatePolygons(polyData: DatasourceData[]) {
|
||||||
polyData.forEach((data: any) => {
|
polyData.forEach((data: DatasourceData) => {
|
||||||
if (data.data.length && data.dataKey.name === this.options.polygonKeyName) {
|
if (data.data.length && data.dataKey.name === this.options.polygonKeyName) {
|
||||||
if (typeof (data?.data[0][1]) === 'string') {
|
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) {
|
if (this.polygons.get(data.datasource.entityName)) {
|
||||||
this.updatePolygon(data.data, polyData, this.options);
|
this.updatePolygon(data.datasource.entityName, data.data, polyData, this.options);
|
||||||
}
|
}
|
||||||
else {
|
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.ready$.subscribe(() => {
|
||||||
this.polygon = new Polygon(this.map, data, dataSources, settings);
|
const polygon = new Polygon(this.map, data, dataSources, settings);
|
||||||
const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds().pad(0.2));
|
const bounds = this.bounds.extend(polygon.leafletPoly.getBounds().pad(0.2));
|
||||||
if (bounds.isValid()) {
|
if (bounds.isValid()) {
|
||||||
this.map.fitBounds(bounds);
|
this.map.fitBounds(bounds);
|
||||||
this.bounds = 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.ready$.subscribe(() => {
|
||||||
// this.polygon.updatePolygon(settings, data, dataSources);
|
this.polygons.get(key).updatePolygon(data, dataSources, settings);
|
||||||
const bounds = this.bounds.extend(this.polygon.leafletPoly.getBounds().pad(0.2));
|
|
||||||
if (bounds.isValid()) {
|
|
||||||
this.map.fitBounds(bounds);
|
|
||||||
this.bounds = bounds;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,4 +131,4 @@ export interface HistorySelectSettings {
|
|||||||
buttonColor: string;
|
buttonColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolygonSettings;
|
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;
|
||||||
@ -74,7 +74,8 @@ export class Marker {
|
|||||||
|
|
||||||
if (settings.showLabel) {
|
if (settings.showLabel) {
|
||||||
if (settings.useLabelFunction) {
|
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);
|
else settings.labelText = parseTemplate(settings.label, this.data);
|
||||||
this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
|
this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
|
||||||
|
|||||||
@ -14,14 +14,17 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import L, { LatLngExpression } from 'leaflet';
|
import L, { LatLngExpression, LatLngTuple } from 'leaflet';
|
||||||
import { createTooltip } from './maps-utils';
|
import { createTooltip } from './maps-utils';
|
||||||
import { PolygonSettings } from './map-models';
|
import { PolygonSettings } from './map-models';
|
||||||
|
import { DatasourceData } from '@app/shared/models/widget.models';
|
||||||
|
|
||||||
export class Polygon {
|
export class Polygon {
|
||||||
|
|
||||||
leafletPoly: L.Polygon;
|
leafletPoly: L.Polygon;
|
||||||
tooltip;
|
tooltip;
|
||||||
|
data;
|
||||||
|
dataSources;
|
||||||
|
|
||||||
constructor(public map, coordinates, dataSources, settings: PolygonSettings, onClickListener?) {
|
constructor(public map, coordinates, dataSources, settings: PolygonSettings, onClickListener?) {
|
||||||
this.leafletPoly = L.polygon(coordinates, {
|
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() {
|
removePolygon() {
|
||||||
this.map.removeLayer(this.leafletPoly);
|
this.map.removeLayer(this.leafletPoly);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -253,6 +253,11 @@ export const commonMapSettingsSchema =
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
draggableMarker: {
|
||||||
|
title: 'Draggable Marker',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false
|
||||||
|
},
|
||||||
disableScrollZooming: {
|
disableScrollZooming: {
|
||||||
title: 'Disable scroll zooming',
|
title: 'Disable scroll zooming',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
@ -371,11 +376,6 @@ export const commonMapSettingsSchema =
|
|||||||
title: 'Polygon Color function: f(data, dsData, dsIndex)',
|
title: 'Polygon Color function: f(data, dsData, dsIndex)',
|
||||||
type: 'string'
|
type: 'string'
|
||||||
},
|
},
|
||||||
draggableMarker: {
|
|
||||||
title: 'Draggable Marker',
|
|
||||||
type: 'boolean',
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
markerImage: {
|
markerImage: {
|
||||||
title: 'Custom marker image',
|
title: 'Custom marker image',
|
||||||
type: 'string'
|
type: 'string'
|
||||||
@ -410,13 +410,13 @@ export const commonMapSettingsSchema =
|
|||||||
'useDefaultCenterPosition',
|
'useDefaultCenterPosition',
|
||||||
'defaultCenterPosition',
|
'defaultCenterPosition',
|
||||||
'fitMapBounds',
|
'fitMapBounds',
|
||||||
|
'draggableMarker',
|
||||||
'disableScrollZooming',
|
'disableScrollZooming',
|
||||||
'latKeyName',
|
'latKeyName',
|
||||||
'lngKeyName',
|
'lngKeyName',
|
||||||
'showLabel',
|
'showLabel',
|
||||||
'label',
|
'label',
|
||||||
'useLabelFunction',
|
'useLabelFunction',
|
||||||
'draggableMarker',
|
|
||||||
{
|
{
|
||||||
key: 'labelFunction',
|
key: 'labelFunction',
|
||||||
type: 'javascript'
|
type: 'javascript'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user