Browse Source

Merge pull request #3280 from MrKartoshka/editable-polygons

Editable polygons
pull/3285/head
Igor Kulikov 6 years ago
committed by GitHub
parent
commit
a1f2ef40e5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  2. 1
      ui-ngx/package.json
  3. 128
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  4. 3
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  5. 54
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  6. 14
      ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.scss
  7. 18
      ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts
  8. 6
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts
  9. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts
  10. 37
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  11. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts
  12. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts
  13. 6
      ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts
  14. 3
      ui-ngx/src/assets/add_polygon.svg
  15. 5
      ui-ngx/src/typings/add-marker.d.ts
  16. 275
      ui-ngx/src/typings/leadflet-editable.d.ts
  17. 3
      ui-ngx/tsconfig.json

6
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

1
ui-ngx/package.json

@ -58,6 +58,7 @@
"jstree-bootstrap-theme": "^1.0.1", "jstree-bootstrap-theme": "^1.0.1",
"jszip": "^3.4.0", "jszip": "^3.4.0",
"leaflet": "^1.6.0", "leaflet": "^1.6.0",
"leaflet-editable": "^1.2.0",
"leaflet-polylinedecorator": "^1.6.0", "leaflet-polylinedecorator": "^1.6.0",
"leaflet-providers": "^1.10.1", "leaflet-providers": "^1.10.1",
"leaflet.gridlayer.googlemutant": "0.10.0", "leaflet.gridlayer.googlemutant": "0.10.0",

128
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -179,6 +179,74 @@ export default abstract class LeafletMap {
} }
} }
addPolygonControl() {
if (this.options.editablePolygon) {
let mousePositionOnMap: L.LatLng[];
let addPolygon: L.Control;
this.map.on('mousemove', (e: L.LeafletMouseEvent) => {
const latlng1 = e.latlng;
const latlng2 = L.latLng(e.latlng.lat, e.latlng.lng + 10);
const latlng3 = L.latLng(e.latlng.lat-10, e.latlng.lng);
mousePositionOnMap = [latlng1,latlng2, latlng3 ];
});
const dragListener = (e: L.DragEndEvent) => {
if (e.type === 'dragend' && mousePositionOnMap) {
const icon = new L.Icon.Default();
icon.options.shadowSize = [0, 0];
const newPolygon = L.polygon(mousePositionOnMap).addTo(this.map);
const datasourcesList = document.createElement('div');
const customLatLng = {coordinates: this.convertToPolygonFormat(mousePositionOnMap)};
this.datasources.forEach(ds => {
const dsItem = document.createElement('p');
dsItem.appendChild(document.createTextNode(ds.entityName));
dsItem.setAttribute('style', 'font-size: 14px');
dsItem.onclick = () => {
const updatedEnttity = { ...ds, ...customLatLng };
this.savePolygonLocation(updatedEnttity).subscribe(() => {
this.map.removeLayer(newPolygon);
this.deletePolygon(ds.entityName);
});
}
datasourcesList.append(dsItem);
});
const deleteBtn = document.createElement('a');
deleteBtn.appendChild(document.createTextNode('Delete position'));
deleteBtn.setAttribute('color', 'red');
deleteBtn.onclick = () => {
this.map.removeLayer(newPolygon);
}
datasourcesList.append(deleteBtn);
const popup = L.popup();
popup.setContent(datasourcesList);
newPolygon.bindPopup(popup).openPopup();
}
addPolygon.setPosition('topright')
}
L.Control.AddPolygon = L.Control.extend({
onAdd() {
const img = L.DomUtil.create('img') as any;
img.src = `assets/add_polygon.svg`;
img.style.width = '32px';
img.style.height = '32px';
img.title = 'Drag and drop to add Polygon';
img.onclick = this.dragPolygonVertex;
img.draggable = true;
const draggableImg = new L.Draggable(img);
draggableImg.enable();
draggableImg.on('dragend', dragListener)
return img;
},
onRemove() {
},
dragPolygonVertex: this.dragPolygonVertex
} as any);
L.control.addPolygon = (opts) => {
return new L.Control.AddPolygon(opts);
}
addPolygon = L.control.addPolygon({ position: 'topright' }).addTo(this.map);
}
}
public setLoading(loading: boolean) { public setLoading(loading: boolean) {
if (this.loading !== loading) { if (this.loading !== loading) {
this.loading = loading; this.loading = loading;
@ -205,6 +273,9 @@ export default abstract class LeafletMap {
if (this.options.draggableMarker) { if (this.options.draggableMarker) {
this.addMarkerControl(); this.addMarkerControl();
} }
if (this.options.editablePolygon) {
this.addPolygonControl();
}
if (this.options.useClusterMarkers) { if (this.options.useClusterMarkers) {
this.map.addLayer(this.markersCluster); this.map.addLayer(this.markersCluster);
} }
@ -218,6 +289,10 @@ export default abstract class LeafletMap {
return of(null); return of(null);
} }
public savePolygonLocation(_e: FormattedData, coordinates?: Array<[number, number]>): Observable<any> {
return of(null);
}
createLatLng(lat: number, lng: number): L.LatLng { createLatLng(lat: number, lng: number): L.LatLng {
return L.latLng(lat, lng); return L.latLng(lat, lng);
} }
@ -284,10 +359,16 @@ export default abstract class LeafletMap {
return L.latLng(lat, lng) as L.LatLng; return L.latLng(lat, lng) as L.LatLng;
} }
convertPositionPolygon(expression: Array<[number, number]>): L.LatLngExpression[] { convertPositionPolygon(expression: (LatLngTuple | LatLngTuple[] | LatLngTuple[][])[]) {
return expression.map((el) => { return (expression).map((el) => {
return el.length === 2 && !el.some(isNaN) ? el : null if (!Array.isArray(el[0]) && el.length === 2) {
}).filter(el => !!el) return el;
} else if (Array.isArray(el) && el.length) {
return this.convertPositionPolygon(el as LatLngTuple[] | LatLngTuple[][]);
} else {
return null;
}
}).filter(el => !!el)
} }
convertToCustomFormat(position: L.LatLng): object { convertToCustomFormat(position: L.LatLng): object {
@ -297,6 +378,26 @@ export default abstract class LeafletMap {
} }
} }
convertToPolygonFormat(points: Array<any>): Array<any> {
if (points.length) {
return points.map(point=> {
if (point.length) {
return this.convertToPolygonFormat(point);
} else {
return [point.lat, point.lng];
}
})
} else {
return []
}
}
convertPolygonToCustomFormat(expression: any[][]): object {
return {
[this.options.polygonKeyName] : this.convertToPolygonFormat(expression)
}
}
updateData(drawRoutes: boolean, showPolygon: boolean) { updateData(drawRoutes: boolean, showPolygon: boolean) {
this.drawRoutes = drawRoutes; this.drawRoutes = drawRoutes;
this.showPolygon = showPolygon; this.showPolygon = showPolygon;
@ -311,7 +412,7 @@ export default abstract class LeafletMap {
} }
this.updateMarkers(formattedData, false); this.updateMarkers(formattedData, false);
this.updateBoundsInternal(); this.updateBoundsInternal();
if (this.options.draggableMarker) { if (this.options.draggableMarker || this.options.editablePolygon) {
this.datasources = formattedData; this.datasources = formattedData;
} }
} else { } else {
@ -449,6 +550,16 @@ export default abstract class LeafletMap {
return marker; return marker;
} }
deletePolygon(key: string) {
let polygon = this.polygons.get(key)?.leafletPoly;
if (polygon) {
this.map.removeLayer(polygon);
this.polygons.delete(key);
polygon = null;
}
return polygon;
}
updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) {
if (this.points) { if (this.points) {
this.map.removeLayer(this.points); this.map.removeLayer(this.points);
@ -558,8 +669,13 @@ export default abstract class LeafletMap {
}); });
} }
dragPolygonVertex = (e?, data = {} as FormattedData) => {
if (e === undefined || (e.type !== 'editable:vertex:dragend' && e.type !== 'editable:vertex:deleted')) return;
this.savePolygonLocation({ ...data, ...this.convertPolygonToCustomFormat(e.layer._latlngs) }).subscribe();
}
createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) { createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) {
const polygon = new Polygon(this.map, polyData, dataSources, settings); const polygon = new Polygon(this.map, polyData, dataSources, settings, this.dragPolygonVertex);
if (updateBounds) { if (updateBounds) {
const bounds = polygon.leafletPoly.getBounds(); const bounds = polygon.leafletPoly.getBounds();
this.fitBounds(bounds); this.fitBounds(bounds);

3
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts

@ -27,6 +27,7 @@ export type PosFuncton = (origXPos, origYPos) => { x, y };
export type MapSettings = { export type MapSettings = {
draggableMarker: boolean; draggableMarker: boolean;
editablePolygon: boolean;
posFunction: PosFuncton; posFunction: PosFuncton;
defaultZoomLevel?: number; defaultZoomLevel?: number;
disableScrollZooming?: boolean; disableScrollZooming?: boolean;
@ -139,6 +140,7 @@ export type PolygonSettings = {
usePolygonColorFunction: boolean; usePolygonColorFunction: boolean;
polygonTooltipFunction: GenericFunction; polygonTooltipFunction: GenericFunction;
polygonColorFunction?: GenericFunction; polygonColorFunction?: GenericFunction;
editablePolygon: boolean;
} }
export type PolylineSettings = { export type PolylineSettings = {
@ -233,6 +235,7 @@ export const defaultSettings: any = {
credentials: '', credentials: '',
markerClusteringSetting: null, markerClusteringSetting: null,
draggableMarker: false, draggableMarker: false,
editablePolygon: false,
fitMapBounds: true, fitMapBounds: true,
mapPageSize: DEFAULT_MAP_PAGE_SIZE mapPageSize: DEFAULT_MAP_PAGE_SIZE
}; };

54
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -78,6 +78,7 @@ export class MapWidgetController implements MapWidgetInterface {
this.map = new MapClass(this.ctx, $element, this.settings); this.map = new MapClass(this.ctx, $element, this.settings);
(this.ctx as any).mapInstance = this.map; (this.ctx as any).mapInstance = this.map;
this.map.saveMarkerLocation = this.setMarkerLocation; this.map.saveMarkerLocation = this.setMarkerLocation;
this.map.savePolygonLocation = this.savePolygonLocation;
this.pageLink = { this.pageLink = {
page: 0, page: 0,
pageSize: this.settings.mapPageSize, pageSize: this.settings.mapPageSize,
@ -239,6 +240,56 @@ export class MapWidgetController implements MapWidgetInterface {
} }
} }
savePolygonLocation = (e: FormattedData, coordinates?: Array<any>) => {
const attributeService = this.ctx.$injector.get(AttributeService);
const entityId: EntityId = {
entityType: e.$datasource.entityType,
id: e.$datasource.entityId
};
const attributes = [];
const timeseries = [];
const coordinatesProperties = this.settings.polygonKeyName;
e.$datasource.dataKeys.forEach(key => {
let value;
if (coordinatesProperties == key.name) {
value = {
key: key.name,
value: isDefined(coordinates) ? coordinates : e[key.name]
};
}
if (value) {
if (key.type === DataKeyType.attribute) {
attributes.push(value)
}
if (key.type === DataKeyType.timeseries) {
timeseries.push(value)
}
}
});
const observables: Observable<any>[] = [];
if (timeseries.length) {
observables.push(attributeService.saveEntityTimeseries(
entityId,
LatestTelemetry.LATEST_TELEMETRY,
timeseries
));
}
if (attributes.length) {
observables.push(attributeService.saveEntityAttributes(
entityId,
AttributeScope.SERVER_SCOPE,
attributes
));
}
if (observables.length) {
return forkJoin(observables);
} else {
return of(null);
}
}
initSettings(settings: UnitedMapSettings, isEditMap?: boolean): UnitedMapSettings { initSettings(settings: UnitedMapSettings, isEditMap?: boolean): UnitedMapSettings {
const functionParams = ['data', 'dsData', 'dsIndex']; const functionParams = ['data', 'dsData', 'dsIndex'];
this.provider = settings.provider || this.mapProvider; this.provider = settings.provider || this.mapProvider;
@ -270,6 +321,9 @@ export class MapWidgetController implements MapWidgetInterface {
if (isEditMap && !settings.hasOwnProperty('draggableMarker')) { if (isEditMap && !settings.hasOwnProperty('draggableMarker')) {
settings.draggableMarker = true; settings.draggableMarker = true;
} }
if (isEditMap && !settings.hasOwnProperty('editablePolygon')) {
settings.editablePolygon = true;
}
return { ...defaultSettings, ...settings, ...customOptions, } return { ...defaultSettings, ...settings, ...customOptions, }
} }

14
ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.scss

@ -22,13 +22,13 @@
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.leaflet-div-icon, //.leaflet-div-icon,
.tb-marker-label, //.tb-marker-label,
.tb-marker-label:before { //.tb-marker-label:before {
border: none; // border: none;
background: none; // background: none;
box-shadow: none; // box-shadow: none;
} //}
.leaflet-container{ .leaflet-container{
background-color: white; background-color: white;

18
ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts

@ -16,6 +16,7 @@
import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet'; import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet';
import { createTooltip, functionValueCalculator, parseWithTranslation, safeExecute } from './maps-utils'; import { createTooltip, functionValueCalculator, parseWithTranslation, safeExecute } from './maps-utils';
import 'leaflet-editable/src/Leaflet.Editable';
import { FormattedData, PolygonSettings } from './map-models'; import { FormattedData, PolygonSettings } from './map-models';
export class Polygon { export class Polygon {
@ -25,11 +26,10 @@ export class Polygon {
data: FormattedData; data: FormattedData;
dataSources: FormattedData[]; dataSources: FormattedData[];
constructor(public map, polyData: FormattedData, dataSources: FormattedData[], private settings: PolygonSettings) { constructor(public map, polyData: FormattedData, dataSources: FormattedData[], private settings: PolygonSettings, onDragendListener?) {
this.dataSources = dataSources; this.dataSources = dataSources;
this.data = polyData; this.data = polyData;
const polygonColor = this.getPolygonColor(settings); const polygonColor = this.getPolygonColor(settings);
this.leafletPoly = L.polygon(polyData[this.settings.polygonKeyName], { this.leafletPoly = L.polygon(polyData[this.settings.polygonKeyName], {
fill: true, fill: true,
fillColor: polygonColor, fillColor: polygonColor,
@ -38,6 +38,14 @@ export class Polygon {
fillOpacity: settings.polygonOpacity, fillOpacity: settings.polygonOpacity,
opacity: settings.polygonStrokeOpacity opacity: settings.polygonStrokeOpacity
}).addTo(this.map); }).addTo(this.map);
if (settings.editablePolygon) {
this.leafletPoly.enableEdit(this.map);
if (onDragendListener) {
this.leafletPoly.on('editable:vertex:dragend', e => onDragendListener(e, this.data));
this.leafletPoly.on('editable:vertex:deleted', e => onDragendListener(e, this.data));
}
}
if (settings.showPolygonTooltip) { if (settings.showPolygonTooltip) {
this.tooltip = createTooltip(this.leafletPoly, settings, polyData.$datasource); this.tooltip = createTooltip(this.leafletPoly, settings, polyData.$datasource);
@ -64,7 +72,13 @@ export class Polygon {
updatePolygon(data: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { updatePolygon(data: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) {
this.data = data; this.data = data;
this.dataSources = dataSources; this.dataSources = dataSources;
if (settings.editablePolygon) {
this.leafletPoly.disableEdit();
}
this.leafletPoly.setLatLngs(data[this.settings.polygonKeyName]); this.leafletPoly.setLatLngs(data[this.settings.polygonKeyName]);
if (settings.editablePolygon) {
this.leafletPoly.enableEdit(this.map);
}
if (settings.showPolygonTooltip) if (settings.showPolygonTooltip)
this.updateTooltip(this.data); this.updateTooltip(this.data);
this.updatePolygonColor(settings); this.updatePolygonColor(settings);

6
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts

@ -20,7 +20,6 @@ import LeafletMap from '../leaflet-map';
import { UnitedMapSettings } from '../map-models'; import { UnitedMapSettings } from '../map-models';
import 'leaflet.gridlayer.googlemutant'; import 'leaflet.gridlayer.googlemutant';
import { ResourcesService } from '@core/services/resources.service'; import { ResourcesService } from '@core/services/resources.service';
import { Injector } from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
const gmGlobals: GmGlobal = {}; const gmGlobals: GmGlobal = {};
@ -37,7 +36,10 @@ export class GoogleMap extends LeafletMap {
this.resource = ctx.$injector.get(ResourcesService); this.resource = ctx.$injector.get(ResourcesService);
super.initSettings(options); super.initSettings(options);
this.loadGoogle(() => { this.loadGoogle(() => {
const map = L.map($container, {attributionControl: false}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); const map = L.map($container, {
attributionControl: false,
editable: !!options.editablePolygon
}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
(L.gridLayer as any).googleMutant({ (L.gridLayer as any).googleMutant({
type: options?.gmDefaultMapType || 'roadmap' type: options?.gmDefaultMapType || 'roadmap'
}).addTo(map); }).addTo(map);

4
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts

@ -22,7 +22,9 @@ import { WidgetContext } from '@home/models/widget-component.models';
export class HEREMap extends LeafletMap { export class HEREMap extends LeafletMap {
constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) {
super(ctx, $container, options); super(ctx, $container, options);
const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); const map = L.map($container, {
editable: !!options.editablePolygon
}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials); const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
tileLayer.addTo(map); tileLayer.addTo(map);
super.initSettings(options); super.initSettings(options);

37
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -196,14 +196,15 @@ export class ImageMap extends LeafletMap {
initMap(updateImage?: boolean) { initMap(updateImage?: boolean) {
if (!this.map && this.aspect > 0) { if (!this.map && this.aspect > 0) {
const center = this.pointToLatLng(this.width / 2, this.height / 2); const center = this.pointToLatLng(this.width / 2, this.height / 2);
this.map = L.map(this.$container, { this.map = L.map(this.$container, {
minZoom: 1, minZoom: 1,
maxZoom, maxZoom,
scrollWheelZoom: !this.options.disableScrollZooming, scrollWheelZoom: !this.options.disableScrollZooming,
center, center,
zoom: 1, zoom: 1,
crs: L.CRS.Simple, crs: L.CRS.Simple,
attributionControl: false attributionControl: false,
editable: !!this.options.editablePolygon
}); });
this.updateBounds(updateImage); this.updateBounds(updateImage);
} }
@ -221,14 +222,17 @@ export class ImageMap extends LeafletMap {
expression.y * this.height); expression.y * this.height);
} }
convertPositionPolygon(expression: Array<[number, number]>): L.LatLngExpression[] { convertPositionPolygon(expression: (LatLngTuple | LatLngTuple[] | LatLngTuple[][])[]){
return expression.map((el) => { return (expression).map((el) => {
if (el.length === 2 && !el.some(isNaN)) { if (!Array.isArray(el[0]) && !Array.isArray(el[1]) && el.length === 2) {
return this.pointToLatLng( return this.pointToLatLng(
el[0] * this.width, el[0] * this.width,
el[1] * this.height) el[1] * this.height)
} else if (Array.isArray(el) && el.length) {
return this.convertPositionPolygon(el as LatLngTuple[] | LatLngTuple[][]);
} else {
return null;
} }
return null;
}).filter(el => !!el) }).filter(el => !!el)
} }
@ -247,4 +251,25 @@ export class ImageMap extends LeafletMap {
[this.options.yPosKeyName]: calculateNewPointCoordinate(point.y, this.height) [this.options.yPosKeyName]: calculateNewPointCoordinate(point.y, this.height)
} }
} }
convertToPolygonFormat(points: Array<any>): Array<any> {
if (points.length) {
return points.map(point=> {
if (point.length) {
return this.convertToPolygonFormat(point);
} else {
const pos = this.latLngToPoint(point);
return [calculateNewPointCoordinate(pos.x, this.width), calculateNewPointCoordinate(pos.y, this.height)];
}
})
} else {
return []
}
}
convertPolygonToCustomFormat(expression: any[][]): object {
return {
[this.options.polygonKeyName] : this.convertToPolygonFormat(expression)
}
}
} }

4
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts

@ -22,7 +22,9 @@ import { WidgetContext } from '@home/models/widget-component.models';
export class OpenStreetMap extends LeafletMap { export class OpenStreetMap extends LeafletMap {
constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) {
super(ctx, $container, options); super(ctx, $container, options);
const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); const map = L.map($container, {
editable: !!options.editablePolygon
}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
let tileLayer; let tileLayer;
if (options.useCustomProvider) if (options.useCustomProvider)
tileLayer = L.tileLayer(options.customProviderTileUrl); tileLayer = L.tileLayer(options.customProviderTileUrl);

4
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts

@ -24,7 +24,9 @@ export class TencentMap extends LeafletMap {
constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) {
super(ctx, $container, options); super(ctx, $container, options);
const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0'; const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0';
const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); const map = L.map($container, {
editable: !!options.editablePolygon
}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
const txLayer = L.tileLayer(txUrl, { const txLayer = L.tileLayer(txUrl, {
subdomains: '0123', subdomains: '0123',
tms: true, tms: true,

6
ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts

@ -524,6 +524,11 @@ export const mapPolygonSchema =
type: 'string', type: 'string',
default: 'coordinates' default: 'coordinates'
}, },
editablePolygon: {
title: 'Enable polygon edit',
type: 'boolean',
default: false
},
polygonColor: { polygonColor: {
title: 'Polygon color', title: 'Polygon color',
type: 'string' type: 'string'
@ -581,6 +586,7 @@ export const mapPolygonSchema =
form: [ form: [
'showPolygon', 'showPolygon',
'polygonKeyName', 'polygonKeyName',
'editablePolygon',
{ {
key: 'polygonColor', key: 'polygonColor',
type: 'color' type: 'color'

3
ui-ngx/src/assets/add_polygon.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<polygon points="2,46 46,46 24,2" style="fill:transparent;stroke:black;stroke-width:2"/>
</svg>

After

Width:  |  Height:  |  Size: 182 B

5
ui-ngx/src/typings/add-marker.d.ts

@ -20,9 +20,12 @@ declare module 'leaflet' {
namespace Control { namespace Control {
class AddMarker extends L.Control { } class AddMarker extends L.Control { }
class AddPolygon extends L.Control { }
} }
namespace control { namespace control {
function addMarker(options): Control.AddMarker; function addMarker(options): Control.AddMarker;
function addPolygon(options): Control.AddPolygon;
} }
}
}

275
ui-ngx/src/typings/leadflet-editable.d.ts

@ -0,0 +1,275 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import * as leaflet from 'leaflet';
declare module 'leaflet' {
/**
* Make geometries editable in Leaflet.
*
* This is not a plug and play UI, and will not. This is a minimal, lightweight, and fully extendable API to
* control editing of geometries. So you can easily build your own UI with your own needs and choices.
*/
interface EditableStatic {
new (map: Map, options: EditOptions): Editable;
}
/**
* Options to pass to L.Editable when instanciating.
*/
interface EditOptions extends leaflet.MapOptions{
/**
* Whether to create a L.Editable instance at map init or not.
*/
editable: boolean;
/**
* Class to be used when creating a new Polyline.
*/
polylineClass?: object;
/**
* Class to be used when creating a new Polygon.
*/
polygonClass?: object;
/**
* Class to be used when creating a new Marker.
*/
markerClass?: object;
/**
* CSS class to be added to the map container while drawing.
*/
drawingCSSClass?: string;
/**
* Layer used to store edit tools (vertex, line guide…).
*/
editLayer?: LayerGroup<leaflet.Layer>;
/**
* Default layer used to store drawn features (marker, polyline…).
*/
featuresLayer?: LayerGroup<Polyline|Polygon|Marker>;
/**
* Class to be used as vertex, for path editing.
*/
vertexMarkerClass?: object;
/**
* Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path.
*/
middleMarkerClass?: object;
/**
* Class to be used as Polyline editor.
*/
polylineEditorClass?: object;
/**
* Class to be used as Polygon editor.
*/
polygonEditorClass?: object;
/**
* Class to be used as Marker editor.
*/
markerEditorClass?: object;
/**
* Options to be passed to the line guides.
*/
lineGuideOptions?: object;
/**
* Set this to true if you don't want middle markers.
*/
skipMiddleMarkers?: boolean;
}
/**
* Make geometries editable in Leaflet.
*
* This is not a plug and play UI, and will not. This is a minimal, lightweight, and fully extendable API to
* control editing of geometries. So you can easily build your own UI with your own needs and choices.
*/
interface Editable extends leaflet.Evented {
/**
* Options to pass to L.Editable when instanciating.
*/
options: EditOptions;
currentPolygon: Polyline|Polygon|Marker;
/**
* Start drawing a polyline. If latlng is given, a first point will be added. In any case, continuing on user
* click. If options is given, it will be passed to the polyline class constructor.
*/
startPolyline(latLng?: LatLng, options?: PolylineOptions): Polyline;
/**
* Start drawing a polygon. If latlng is given, a first point will be added. In any case, continuing on user
* click. If options is given, it will be passed to the polygon class constructor.
*/
startPolygon(latLng?: LatLng, options?: PolylineOptions): Polygon;
/**
* Start adding a marker. If latlng is given, the marker will be shown first at this point. In any case, it
* will follow the user mouse, and will have a final latlng on next click (or touch). If options is given,
* it will be passed to the marker class constructor.
*/
startMarker(latLng?: LatLng, options?: MarkerOptions): Marker;
/**
* When you need to stop any ongoing drawing, without needing to know which editor is active.
*/
stopDrawing(): void;
/**
* When you need to commit any ongoing drawing, without needing to know which editor is active.
*/
commitDrawing(): void;
}
let Editable: EditableStatic;
/**
* EditableMixin is included to L.Polyline, L.Polygon and L.Marker. It adds the following methods to them.
*
* When editing is enabled, the editor is accessible on the instance with the editor property.
*/
interface EditableMixin {
/**
* Enable editing, by creating an editor if not existing, and then calling enable on it.
*/
enableEdit(map: L.Map): any;
/**
* Disable editing, also remove the editor property reference.
*/
disableEdit(): void;
/**
* Enable or disable editing, according to current status.
*/
toggleEdit(): void;
/**
* Return true if current instance has an editor attached, and this editor is enabled.
*/
editEnabled(): boolean;
}
interface Map {
/**
* Options to pass to L.Editable when instanciating.
*/
editOptions: MapOptions;
/**
* L.Editable plugin instance.
*/
editTools: Editable;
}
// tslint:disable-next-line:no-empty-interface
interface Polyline extends EditableMixin {}
namespace Map {
interface MapOptions {
/**
* Whether to create a L.Editable instance at map init or not.
*/
editable?: boolean;
/**
* Options to pass to L.Editable when instanciating.
*/
editOptions?: MapOptions;
}
}
/**
* When editing a feature (marker, polyline…), an editor is attached to it. This editor basically knows
* how to handle the edition.
*/
interface BaseEditor {
/**
* Set up the drawing tools for the feature to be editable.
*/
enable(): MarkerEditor|PolylineEditor|PolygonEditor;
/**
* Remove editing tools.
*/
disable(): MarkerEditor|PolylineEditor|PolygonEditor;
}
/**
* Inherit from L.Editable.BaseEditor.
* Inherited by L.Editable.PolylineEditor and L.Editable.PolygonEditor.
*/
interface PathEditor extends BaseEditor {
/**
* Rebuild edit elements (vertex, middlemarker, etc.).
*/
reset(): void;
}
/**
* Inherit from L.Editable.PathEditor.
*/
interface PolylineEditor extends PathEditor {
/**
* Set up drawing tools to continue the line forward.
*/
continueForward(): void;
/**
* Set up drawing tools to continue the line backward.
*/
continueBackward(): void;
}
/**
* Inherit from L.Editable.PathEditor.
*/
interface PolygonEditor extends PathEditor {
/**
* Set up drawing tools for creating a new hole on the polygon. If the latlng param is given, a first
* point is created.
*/
newHole(latlng: LatLng): void;
}
/**
* Inherit from L.Editable.BaseEditor.
*/
// tslint:disable-next-line:no-empty-interface
interface MarkerEditor extends BaseEditor {}
interface Marker extends EditableMixin, MarkerEditor {}
interface Polyline extends EditableMixin, PolylineEditor {}
interface Polygon extends EditableMixin, PolygonEditor {}
function map(element: string | HTMLElement, options?: EditOptions): Map;
}

3
ui-ngx/tsconfig.json

@ -20,7 +20,8 @@
"src/typings/jquery.flot.typings.d.ts", "src/typings/jquery.flot.typings.d.ts",
"src/typings/jquery.jstree.typings.d.ts", "src/typings/jquery.jstree.typings.d.ts",
"src/typings/split.js.typings.d.ts", "src/typings/split.js.typings.d.ts",
"src/typings/add-marker.d.ts" "src/typings/add-marker.d.ts",
"src/typings/leaflet-editable.d.ts"
], ],
"paths": { "paths": {
"@app/*": ["src/app/*"], "@app/*": ["src/app/*"],

Loading…
Cancel
Save