Commit 423a491e8003e4a53b3271664a868c02fdffcaf7
1 parent
8750b26f
Editable Polygons without proper typings
Showing
15 changed files
with
524 additions
and
28 deletions
... | ... | @@ -43,6 +43,8 @@ import { createTooltip, parseArray, safeExecute } from '@home/components/widget/ |
43 | 43 | import { WidgetContext } from '@home/models/widget-component.models'; |
44 | 44 | import { DatasourceData } from '@shared/models/widget.models'; |
45 | 45 | import { deepClone, isDefinedAndNotNull } from '@core/utils'; |
46 | +import {newArray} from "@angular/compiler/src/util"; | |
47 | +import {isArray} from "rxjs/internal-compatibility"; | |
46 | 48 | |
47 | 49 | export default abstract class LeafletMap { |
48 | 50 | |
... | ... | @@ -168,6 +170,75 @@ export default abstract class LeafletMap { |
168 | 170 | } |
169 | 171 | } |
170 | 172 | |
173 | + addPolygonControl() { | |
174 | + if (this.options.editablePolygon) { | |
175 | + let mousePositionOnMap: L.LatLng[]; | |
176 | + let addPolygon: L.Control; | |
177 | + this.map.on('mousemove', (e: L.LeafletMouseEvent) => { | |
178 | + let latlng1 = e.latlng; | |
179 | + let latlng2 = L.latLng(e.latlng.lat, e.latlng.lng + 10); | |
180 | + let latlng3 = L.latLng(e.latlng.lat-10, e.latlng.lng); | |
181 | + mousePositionOnMap = [latlng1,latlng2, latlng3 ]; | |
182 | + }); | |
183 | + const dragListener = (e: L.DragEndEvent) => { | |
184 | + if (e.type === 'dragend' && mousePositionOnMap) { | |
185 | + const icon = new L.Icon.Default(); | |
186 | + icon.options.shadowSize = [0, 0]; | |
187 | + const newPolygon = L.polygon(mousePositionOnMap).addTo(this.map); | |
188 | + const datasourcesList = document.createElement('div'); | |
189 | + const customLatLng = {coordinates: this.convertToPolygonFormat(mousePositionOnMap)}; | |
190 | + this.datasources.forEach(ds => { | |
191 | + const dsItem = document.createElement('p'); | |
192 | + dsItem.appendChild(document.createTextNode(ds.entityName)); | |
193 | + dsItem.setAttribute('style', 'font-size: 14px'); | |
194 | + dsItem.onclick = () => { | |
195 | + const updatedEnttity = { ...ds, ...customLatLng }; | |
196 | + this.savePolygonLocation(updatedEnttity).subscribe(() => { | |
197 | + this.map.removeLayer(newPolygon); | |
198 | + this.deletePolygon(ds.entityName); | |
199 | + // this.createPolygon(ds, this.datasources, this.options); | |
200 | + }); | |
201 | + } | |
202 | + datasourcesList.append(dsItem); | |
203 | + }); | |
204 | + const deleteBtn = document.createElement('a'); | |
205 | + deleteBtn.appendChild(document.createTextNode('Delete position')); | |
206 | + deleteBtn.setAttribute('color', 'red'); | |
207 | + deleteBtn.onclick = () => { | |
208 | + this.map.removeLayer(newPolygon); | |
209 | + } | |
210 | + datasourcesList.append(deleteBtn); | |
211 | + const popup = L.popup(); | |
212 | + popup.setContent(datasourcesList); | |
213 | + newPolygon.bindPopup(popup).openPopup(); | |
214 | + } | |
215 | + addPolygon.setPosition('topright') | |
216 | + } | |
217 | + L.Control.AddPolygon = L.Control.extend({ | |
218 | + onAdd() { | |
219 | + const img = L.DomUtil.create('img') as any; | |
220 | + img.src = `assets/add_polygon.svg`; | |
221 | + img.style.width = '32px'; | |
222 | + img.style.height = '32px'; | |
223 | + img.title = 'Drag and drop to add Polygon'; | |
224 | + img.onclick = this.dragPolygonVertex; | |
225 | + img.draggable = true; | |
226 | + const draggableImg = new L.Draggable(img); | |
227 | + draggableImg.enable(); | |
228 | + draggableImg.on('dragend', dragListener) | |
229 | + return img; | |
230 | + }, | |
231 | + onRemove() { | |
232 | + }, | |
233 | + dragPolygonVertex: this.dragPolygonVertex | |
234 | + } as any); | |
235 | + L.control.addPolygon = (opts) => { | |
236 | + return new L.Control.AddPolygon(opts); | |
237 | + } | |
238 | + addPolygon = L.control.addPolygon({ position: 'topright' }).addTo(this.map); | |
239 | + } | |
240 | + } | |
241 | + | |
171 | 242 | public setMap(map: L.Map) { |
172 | 243 | this.map = map; |
173 | 244 | if (this.options.useDefaultCenterPosition) { |
... | ... | @@ -178,6 +249,9 @@ export default abstract class LeafletMap { |
178 | 249 | if (this.options.draggableMarker) { |
179 | 250 | this.addMarkerControl(); |
180 | 251 | } |
252 | + if (this.options.editablePolygon) { | |
253 | + this.addPolygonControl(); | |
254 | + } | |
181 | 255 | this.map$.next(this.map); |
182 | 256 | } |
183 | 257 | |
... | ... | @@ -189,6 +263,10 @@ export default abstract class LeafletMap { |
189 | 263 | return of(null); |
190 | 264 | } |
191 | 265 | |
266 | + public savePolygonLocation(_e: FormattedData, coordinates?: Array<[number, number]>): Observable<any> { | |
267 | + return of(null); | |
268 | + } | |
269 | + | |
192 | 270 | createLatLng(lat: number, lng: number): L.LatLng { |
193 | 271 | return L.latLng(lat, lng); |
194 | 272 | } |
... | ... | @@ -255,10 +333,16 @@ export default abstract class LeafletMap { |
255 | 333 | return L.latLng(lat, lng) as L.LatLng; |
256 | 334 | } |
257 | 335 | |
258 | - convertPositionPolygon(expression: Array<[number, number]>): L.LatLngExpression[] { | |
259 | - return expression.map((el) => { | |
260 | - return el.length === 2 && !el.some(isNaN) ? el : null | |
261 | - }).filter(el => !!el) | |
336 | + convertPositionPolygon(expression: Array<[number, number]> | Array<Array<[number, number]>>) { | |
337 | + return (expression as Array<any>).map((el) => { | |
338 | + if (el.length === 2 && !el.some(isNaN)) { | |
339 | + return el; | |
340 | + } else if (isArray(el) && el.length) { | |
341 | + return this.convertPositionPolygon(el); | |
342 | + } else { | |
343 | + return null; | |
344 | + } | |
345 | + }).filter(el => !!el) | |
262 | 346 | } |
263 | 347 | |
264 | 348 | convertToCustomFormat(position: L.LatLng): object { |
... | ... | @@ -268,6 +352,26 @@ export default abstract class LeafletMap { |
268 | 352 | } |
269 | 353 | } |
270 | 354 | |
355 | + convertToPolygonFormat(points: Array<any>): Array<any> { | |
356 | + if (points.length) { | |
357 | + return points.map(point=> { | |
358 | + if (point.length) { | |
359 | + return this.convertToPolygonFormat(point); | |
360 | + } else { | |
361 | + return [point.lat, point.lng]; | |
362 | + } | |
363 | + }) | |
364 | + } else { | |
365 | + return [] | |
366 | + } | |
367 | + } | |
368 | + | |
369 | + convertPolygonToCustomFormat(expression: Array<Array<any>>): object { | |
370 | + return { | |
371 | + [this.options.polygonKeyName] : this.convertToPolygonFormat(expression) | |
372 | + } | |
373 | + } | |
374 | + | |
271 | 375 | updateData(data: DatasourceData[], formattedData: FormattedData[], drawRoutes: boolean, showPolygon: boolean) { |
272 | 376 | this.ready$.subscribe(() => { |
273 | 377 | if (drawRoutes) { |
... | ... | @@ -394,6 +498,15 @@ export default abstract class LeafletMap { |
394 | 498 | } |
395 | 499 | } |
396 | 500 | |
501 | + deletePolygon(key: string) { | |
502 | + let polygon = this.polygons.get(key)?.leafletPoly; | |
503 | + if (polygon) { | |
504 | + this.map.removeLayer(polygon); | |
505 | + this.polygons.delete(key); | |
506 | + polygon = null; | |
507 | + } | |
508 | + } | |
509 | + | |
397 | 510 | updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { |
398 | 511 | this.map$.subscribe(map => { |
399 | 512 | if (this.points) { |
... | ... | @@ -509,9 +622,14 @@ export default abstract class LeafletMap { |
509 | 622 | }); |
510 | 623 | } |
511 | 624 | |
512 | - createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) { | |
625 | + dragPolygonVertex = (e?, data = {} as FormattedData) => { | |
626 | + if (e == undefined || (e.type !== 'editable:vertex:dragend' && e.type !== 'editable:vertex:deleted')) return; | |
627 | + this.savePolygonLocation({ ...data, ...this.convertPolygonToCustomFormat(e.layer._latlngs) }).subscribe(); | |
628 | + } | |
629 | + | |
630 | + createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) { | |
513 | 631 | this.ready$.subscribe(() => { |
514 | - const polygon = new Polygon(this.map, polyData, dataSources, settings); | |
632 | + const polygon = new Polygon(this.map, polyData, dataSources, settings, this.dragPolygonVertex); | |
515 | 633 | if (updateBounds) { |
516 | 634 | const bounds = polygon.leafletPoly.getBounds(); |
517 | 635 | this.fitBounds(bounds); | ... | ... |
... | ... | @@ -34,6 +34,7 @@ export type PosFuncton = (origXPos, origYPos) => { x, y }; |
34 | 34 | |
35 | 35 | export type MapSettings = { |
36 | 36 | draggableMarker: boolean; |
37 | + editablePolygon: boolean; | |
37 | 38 | initCallback?: () => any; |
38 | 39 | posFunction: PosFuncton; |
39 | 40 | defaultZoomLevel?: number; |
... | ... | @@ -140,6 +141,7 @@ export type PolygonSettings = { |
140 | 141 | usePolygonColorFunction: boolean; |
141 | 142 | polygonTooltipFunction: GenericFunction; |
142 | 143 | polygonColorFunction?: GenericFunction; |
144 | + editablePolygon: boolean; | |
143 | 145 | } |
144 | 146 | |
145 | 147 | export type PolylineSettings = { |
... | ... | @@ -268,6 +270,7 @@ export const defaultSettings: any = { |
268 | 270 | credentials: '', |
269 | 271 | markerClusteringSetting: null, |
270 | 272 | draggableMarker: false, |
273 | + editablePolygon: false, | |
271 | 274 | fitMapBounds: true, |
272 | 275 | mapPageSize: DEFAULT_MAP_PAGE_SIZE |
273 | 276 | }; | ... | ... |
... | ... | @@ -79,6 +79,7 @@ export class MapWidgetController implements MapWidgetInterface { |
79 | 79 | this.map = new MapClass(this.ctx, $element, this.settings); |
80 | 80 | (this.ctx as any).mapInstance = this.map; |
81 | 81 | this.map.saveMarkerLocation = this.setMarkerLocation; |
82 | + this.map.savePolygonLocation = this.savePolygonLocation; | |
82 | 83 | this.pageLink = { |
83 | 84 | page: 0, |
84 | 85 | pageSize: this.settings.mapPageSize, |
... | ... | @@ -239,6 +240,56 @@ export class MapWidgetController implements MapWidgetInterface { |
239 | 240 | } |
240 | 241 | } |
241 | 242 | |
243 | + savePolygonLocation = (e: FormattedData, coordinates?: Array<any>) => { | |
244 | + const attributeService = this.ctx.$injector.get(AttributeService); | |
245 | + | |
246 | + const entityId: EntityId = { | |
247 | + entityType: e.$datasource.entityType, | |
248 | + id: e.$datasource.entityId | |
249 | + }; | |
250 | + const attributes = []; | |
251 | + const timeseries = []; | |
252 | + | |
253 | + const coordinatesProperties = this.settings.polygonKeyName; | |
254 | + e.$datasource.dataKeys.forEach(key => { | |
255 | + let value; | |
256 | + if (coordinatesProperties == key.name) { | |
257 | + value = { | |
258 | + key: key.name, | |
259 | + value: isDefined(coordinates) ? coordinates : e[key.name] | |
260 | + }; | |
261 | + } | |
262 | + if (value) { | |
263 | + if (key.type === DataKeyType.attribute) { | |
264 | + attributes.push(value) | |
265 | + } | |
266 | + if (key.type === DataKeyType.timeseries) { | |
267 | + timeseries.push(value) | |
268 | + } | |
269 | + } | |
270 | + }); | |
271 | + const observables: Observable<any>[] = []; | |
272 | + if (timeseries.length) { | |
273 | + observables.push(attributeService.saveEntityTimeseries( | |
274 | + entityId, | |
275 | + LatestTelemetry.LATEST_TELEMETRY, | |
276 | + timeseries | |
277 | + )); | |
278 | + } | |
279 | + if (attributes.length) { | |
280 | + observables.push(attributeService.saveEntityAttributes( | |
281 | + entityId, | |
282 | + AttributeScope.SERVER_SCOPE, | |
283 | + attributes | |
284 | + )); | |
285 | + } | |
286 | + if (observables.length) { | |
287 | + return forkJoin(observables); | |
288 | + } else { | |
289 | + return of(null); | |
290 | + } | |
291 | + } | |
292 | + | |
242 | 293 | initSettings(settings: UnitedMapSettings, isEditMap?: boolean): UnitedMapSettings { |
243 | 294 | const functionParams = ['data', 'dsData', 'dsIndex']; |
244 | 295 | this.provider = settings.provider || this.mapProvider; |
... | ... | @@ -270,6 +321,9 @@ export class MapWidgetController implements MapWidgetInterface { |
270 | 321 | if (isEditMap && !settings.hasOwnProperty('draggableMarker')) { |
271 | 322 | settings.draggableMarker = true; |
272 | 323 | } |
324 | + if (isEditMap && !settings.hasOwnProperty('editablePolygon')) { | |
325 | + settings.editablePolygon = true; | |
326 | + } | |
273 | 327 | return { ...defaultSettings, ...settings, ...customOptions, } |
274 | 328 | } |
275 | 329 | ... | ... |
... | ... | @@ -22,13 +22,13 @@ |
22 | 22 | background-repeat: no-repeat; |
23 | 23 | } |
24 | 24 | |
25 | -.leaflet-div-icon, | |
26 | -.tb-marker-label, | |
27 | -.tb-marker-label:before { | |
28 | - border: none; | |
29 | - background: none; | |
30 | - box-shadow: none; | |
31 | -} | |
25 | +//.leaflet-div-icon, | |
26 | +//.tb-marker-label, | |
27 | +//.tb-marker-label:before { | |
28 | +// border: none; | |
29 | +// background: none; | |
30 | +// box-shadow: none; | |
31 | +//} | |
32 | 32 | |
33 | 33 | .leaflet-container{ |
34 | 34 | background-color: white; | ... | ... |
... | ... | @@ -14,7 +14,8 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet'; | |
17 | +import L, { LatLngExpression, LeafletMouseEvent} from 'leaflet'; | |
18 | +import "leaflet-editable/src/Leaflet.Editable"; | |
18 | 19 | import { createTooltip, parseWithTranslation, safeExecute } from './maps-utils'; |
19 | 20 | import { FormattedData, PolygonSettings } from './map-models'; |
20 | 21 | |
... | ... | @@ -25,11 +26,10 @@ export class Polygon { |
25 | 26 | data: FormattedData; |
26 | 27 | dataSources: FormattedData[]; |
27 | 28 | |
28 | - constructor(public map, polyData: FormattedData, dataSources: FormattedData[], private settings: PolygonSettings) { | |
29 | + constructor(public map, polyData: FormattedData, dataSources: FormattedData[], private settings: PolygonSettings, onDragendListener?) { | |
29 | 30 | this.dataSources = dataSources; |
30 | 31 | this.data = polyData; |
31 | 32 | const polygonColor = this.getPolygonColor(settings); |
32 | - | |
33 | 33 | this.leafletPoly = L.polygon(polyData[this.settings.polygonKeyName], { |
34 | 34 | fill: true, |
35 | 35 | fillColor: polygonColor, |
... | ... | @@ -38,6 +38,14 @@ export class Polygon { |
38 | 38 | fillOpacity: settings.polygonOpacity, |
39 | 39 | opacity: settings.polygonStrokeOpacity |
40 | 40 | }).addTo(this.map); |
41 | + if (settings.editablePolygon) { | |
42 | + this.leafletPoly.enableEdit(this.map); | |
43 | + if (onDragendListener) { | |
44 | + this.leafletPoly.on("editable:vertex:dragend", e => onDragendListener(e, this.data)); | |
45 | + this.leafletPoly.on("editable:vertex:deleted", e => onDragendListener(e, this.data)); | |
46 | + } | |
47 | + } | |
48 | + | |
41 | 49 | |
42 | 50 | if (settings.showPolygonTooltip) { |
43 | 51 | this.tooltip = createTooltip(this.leafletPoly, settings, polyData.$datasource); |
... | ... | @@ -64,7 +72,13 @@ export class Polygon { |
64 | 72 | updatePolygon(data: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { |
65 | 73 | this.data = data; |
66 | 74 | this.dataSources = dataSources; |
75 | + if (settings.editablePolygon) { | |
76 | + this.leafletPoly.disableEdit(); | |
77 | + } | |
67 | 78 | this.leafletPoly.setLatLngs(data[this.settings.polygonKeyName]); |
79 | + if (settings.editablePolygon) { | |
80 | + this.leafletPoly.enableEdit(this.map); | |
81 | + } | |
68 | 82 | if (settings.showPolygonTooltip) |
69 | 83 | this.updateTooltip(this.data); |
70 | 84 | this.updatePolygonColor(settings); | ... | ... |
... | ... | @@ -36,7 +36,7 @@ export class GoogleMap extends LeafletMap { |
36 | 36 | super(ctx, $container, options); |
37 | 37 | this.resource = ctx.$injector.get(ResourcesService); |
38 | 38 | this.loadGoogle(() => { |
39 | - const map = L.map($container, {attributionControl: false}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); | |
39 | + const map = L.map($container, {attributionControl: false, editable: !!options.editablePolygon}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); | |
40 | 40 | (L.gridLayer as any).googleMutant({ |
41 | 41 | type: options?.gmDefaultMapType || 'roadmap' |
42 | 42 | }).addTo(map); | ... | ... |
... | ... | @@ -22,7 +22,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; |
22 | 22 | export class HEREMap extends LeafletMap { |
23 | 23 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { |
24 | 24 | super(ctx, $container, options); |
25 | - const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); | |
25 | + const map = L.map($container, {editable: !!options.editablePolygon}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); | |
26 | 26 | const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials); |
27 | 27 | tileLayer.addTo(map); |
28 | 28 | super.setMap(map); | ... | ... |
... | ... | @@ -24,7 +24,9 @@ import { WidgetContext } from '@home/models/widget-component.models'; |
24 | 24 | import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models'; |
25 | 25 | import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
26 | 26 | import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; |
27 | -import { isDefinedAndNotNull } from '@core/utils'; | |
27 | +import {isDefinedAndNotNull, isNumber} from '@core/utils'; | |
28 | +import "leaflet-editable/src/Leaflet.Editable"; | |
29 | +import {isArray} from "rxjs/internal-compatibility"; | |
28 | 30 | |
29 | 31 | const maxZoom = 4;// ? |
30 | 32 | |
... | ... | @@ -196,14 +198,15 @@ export class ImageMap extends LeafletMap { |
196 | 198 | initMap(updateImage?: boolean) { |
197 | 199 | if (!this.map && this.aspect > 0) { |
198 | 200 | const center = this.pointToLatLng(this.width / 2, this.height / 2); |
199 | - this.map = L.map(this.$container, { | |
201 | + this.map = L.map(this.$container, { | |
200 | 202 | minZoom: 1, |
201 | 203 | maxZoom, |
202 | 204 | scrollWheelZoom: !this.options.disableScrollZooming, |
203 | 205 | center, |
204 | 206 | zoom: 1, |
205 | 207 | crs: L.CRS.Simple, |
206 | - attributionControl: false | |
208 | + attributionControl: false, | |
209 | + editable: !!this.options.editablePolygon | |
207 | 210 | }); |
208 | 211 | this.updateBounds(updateImage); |
209 | 212 | } |
... | ... | @@ -221,14 +224,17 @@ export class ImageMap extends LeafletMap { |
221 | 224 | expression.y * this.height); |
222 | 225 | } |
223 | 226 | |
224 | - convertPositionPolygon(expression: Array<[number, number]>): L.LatLngExpression[] { | |
225 | - return expression.map((el) => { | |
227 | + convertPositionPolygon(expression: Array<[number, number]> | Array<Array<[number, number]>>) { | |
228 | + return (expression as Array<any>).map((el) => { | |
226 | 229 | if (el.length === 2 && !el.some(isNaN)) { |
227 | 230 | return this.pointToLatLng( |
228 | 231 | el[0] * this.width, |
229 | 232 | el[1] * this.height) |
233 | + } else if (isArray(el) && el.length) { | |
234 | + return this.convertPositionPolygon(el); | |
235 | + } else { | |
236 | + return null; | |
230 | 237 | } |
231 | - return null; | |
232 | 238 | }).filter(el => !!el) |
233 | 239 | } |
234 | 240 | |
... | ... | @@ -247,4 +253,25 @@ export class ImageMap extends LeafletMap { |
247 | 253 | [this.options.yPosKeyName]: calculateNewPointCoordinate(point.y, this.height) |
248 | 254 | } |
249 | 255 | } |
256 | + | |
257 | + convertToPolygonFormat(points: Array<any>): Array<any> { | |
258 | + if (points.length) { | |
259 | + return points.map(point=> { | |
260 | + if (point.length) { | |
261 | + return this.convertToPolygonFormat(point); | |
262 | + } else { | |
263 | + let pos = this.latLngToPoint(point); | |
264 | + return [calculateNewPointCoordinate(pos.x, this.width), calculateNewPointCoordinate(pos.y, this.height)]; | |
265 | + } | |
266 | + }) | |
267 | + } else { | |
268 | + return [] | |
269 | + } | |
270 | + } | |
271 | + | |
272 | + convertPolygonToCustomFormat(expression: Array<Array<any>>): object { | |
273 | + return { | |
274 | + [this.options.polygonKeyName] : this.convertToPolygonFormat(expression) | |
275 | + } | |
276 | + } | |
250 | 277 | } | ... | ... |
... | ... | @@ -22,7 +22,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; |
22 | 22 | export class OpenStreetMap extends LeafletMap { |
23 | 23 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { |
24 | 24 | super(ctx, $container, options); |
25 | - const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); | |
25 | + const map = new L.Map($container, {editable: !!options.editablePolygon}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); | |
26 | 26 | let tileLayer; |
27 | 27 | if (options.useCustomProvider) |
28 | 28 | tileLayer = L.tileLayer(options.customProviderTileUrl); | ... | ... |
... | ... | @@ -24,7 +24,7 @@ export class TencentMap extends LeafletMap { |
24 | 24 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { |
25 | 25 | super(ctx, $container, options); |
26 | 26 | const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0'; |
27 | - const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); | |
27 | + const map = L.map($container, {editable: !!options.editablePolygon}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); | |
28 | 28 | const txLayer = L.tileLayer(txUrl, { |
29 | 29 | subdomains: '0123', |
30 | 30 | tms: true, | ... | ... |
ui-ngx/src/assets/add_polygon.svg
0 → 100644
... | ... | @@ -20,9 +20,12 @@ declare module 'leaflet' { |
20 | 20 | |
21 | 21 | namespace Control { |
22 | 22 | class AddMarker extends L.Control { } |
23 | + class AddPolygon extends L.Control { } | |
23 | 24 | } |
24 | 25 | |
25 | 26 | namespace control { |
26 | 27 | function addMarker(options): Control.AddMarker; |
28 | + function addPolygon(options): Control.AddPolygon; | |
27 | 29 | } |
28 | -} | |
\ No newline at end of file | ||
30 | + | |
31 | +} | ... | ... |
ui-ngx/src/typings/leadflet-editable.d.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import * as Leaflet from 'leaflet'; | |
18 | + | |
19 | +declare module 'leaflet' { | |
20 | + | |
21 | + /** | |
22 | + * Make geometries editable in Leaflet. | |
23 | + * | |
24 | + * This is not a plug and play UI, and will not. This is a minimal, lightweight, and fully extendable API to | |
25 | + * control editing of geometries. So you can easily build your own UI with your own needs and choices. | |
26 | + */ | |
27 | + interface EditableStatic { | |
28 | + new (map: Map, options: EditOptions): Editable; | |
29 | + } | |
30 | + | |
31 | + /** | |
32 | + * Options to pass to L.Editable when instanciating. | |
33 | + */ | |
34 | + interface EditOptions extends Leaflet.MapOptions{ | |
35 | + /** | |
36 | + * Class to be used when creating a new Polyline. | |
37 | + */ | |
38 | + polylineClass?: object; | |
39 | + | |
40 | + /** | |
41 | + * Class to be used when creating a new Polygon. | |
42 | + */ | |
43 | + polygonClass?: object; | |
44 | + | |
45 | + /** | |
46 | + * Class to be used when creating a new Marker. | |
47 | + */ | |
48 | + markerClass?: object; | |
49 | + | |
50 | + /** | |
51 | + * CSS class to be added to the map container while drawing. | |
52 | + */ | |
53 | + drawingCSSClass?: string; | |
54 | + | |
55 | + /** | |
56 | + * Layer used to store edit tools (vertex, line guide…). | |
57 | + */ | |
58 | + editLayer?: LayerGroup<Leaflet.Layer>; | |
59 | + | |
60 | + /** | |
61 | + * Default layer used to store drawn features (marker, polyline…). | |
62 | + */ | |
63 | + featuresLayer?: LayerGroup<Polyline|Polygon|Marker>; | |
64 | + | |
65 | + /** | |
66 | + * Class to be used as vertex, for path editing. | |
67 | + */ | |
68 | + vertexMarkerClass?: object; | |
69 | + | |
70 | + /** | |
71 | + * Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path. | |
72 | + */ | |
73 | + middleMarkerClass?: object; | |
74 | + | |
75 | + /** | |
76 | + * Class to be used as Polyline editor. | |
77 | + */ | |
78 | + polylineEditorClass?: object; | |
79 | + | |
80 | + /** | |
81 | + * Class to be used as Polygon editor. | |
82 | + */ | |
83 | + polygonEditorClass?: object; | |
84 | + | |
85 | + /** | |
86 | + * Class to be used as Marker editor. | |
87 | + */ | |
88 | + markerEditorClass?: object; | |
89 | + | |
90 | + /** | |
91 | + * Options to be passed to the line guides. | |
92 | + */ | |
93 | + lineGuideOptions?: object; | |
94 | + | |
95 | + /** | |
96 | + * Set this to true if you don't want middle markers. | |
97 | + */ | |
98 | + skipMiddleMarkers?: boolean; | |
99 | + } | |
100 | + | |
101 | + /** | |
102 | + * Make geometries editable in Leaflet. | |
103 | + * | |
104 | + * This is not a plug and play UI, and will not. This is a minimal, lightweight, and fully extendable API to | |
105 | + * control editing of geometries. So you can easily build your own UI with your own needs and choices. | |
106 | + */ | |
107 | + interface Editable extends Leaflet.Evented { | |
108 | + /** | |
109 | + * Options to pass to L.Editable when instanciating. | |
110 | + */ | |
111 | + options: EditOptions; | |
112 | + | |
113 | + currentPolygon: Polyline|Polygon|Marker; | |
114 | + | |
115 | + /** | |
116 | + * Start drawing a polyline. If latlng is given, a first point will be added. In any case, continuing on user | |
117 | + * click. If options is given, it will be passed to the polyline class constructor. | |
118 | + */ | |
119 | + startPolyline(latLng?: LatLng, options?: PolylineOptions): Polyline; | |
120 | + | |
121 | + /** | |
122 | + * Start drawing a polygon. If latlng is given, a first point will be added. In any case, continuing on user | |
123 | + * click. If options is given, it will be passed to the polygon class constructor. | |
124 | + */ | |
125 | + startPolygon(latLng?: LatLng, options?: PolylineOptions): Polygon; | |
126 | + | |
127 | + /** | |
128 | + * Start adding a marker. If latlng is given, the marker will be shown first at this point. In any case, it | |
129 | + * will follow the user mouse, and will have a final latlng on next click (or touch). If options is given, | |
130 | + * it will be passed to the marker class constructor. | |
131 | + */ | |
132 | + startMarker(latLng?: LatLng, options?: MarkerOptions): Marker; | |
133 | + | |
134 | + /** | |
135 | + * When you need to stop any ongoing drawing, without needing to know which editor is active. | |
136 | + */ | |
137 | + stopDrawing(): void; | |
138 | + | |
139 | + /** | |
140 | + * When you need to commit any ongoing drawing, without needing to know which editor is active. | |
141 | + */ | |
142 | + commitDrawing(): void; | |
143 | + } | |
144 | + | |
145 | + let Editable: EditableStatic; | |
146 | + | |
147 | + /** | |
148 | + * EditableMixin is included to L.Polyline, L.Polygon and L.Marker. It adds the following methods to them. | |
149 | + * | |
150 | + * When editing is enabled, the editor is accessible on the instance with the editor property. | |
151 | + */ | |
152 | + interface EditableMixin { | |
153 | + /** | |
154 | + * Enable editing, by creating an editor if not existing, and then calling enable on it. | |
155 | + */ | |
156 | + enableEdit(map: L.Map): any; | |
157 | + | |
158 | + /** | |
159 | + * Disable editing, also remove the editor property reference. | |
160 | + */ | |
161 | + disableEdit(): void; | |
162 | + | |
163 | + /** | |
164 | + * Enable or disable editing, according to current status. | |
165 | + */ | |
166 | + toggleEdit(): void; | |
167 | + | |
168 | + /** | |
169 | + * Return true if current instance has an editor attached, and this editor is enabled. | |
170 | + */ | |
171 | + editEnabled(): boolean; | |
172 | + } | |
173 | + | |
174 | + interface Map { | |
175 | + /** | |
176 | + * Whether to create a L.Editable instance at map init or not. | |
177 | + */ | |
178 | + editable: boolean; | |
179 | + | |
180 | + /** | |
181 | + * Options to pass to L.Editable when instanciating. | |
182 | + */ | |
183 | + editOptions: EditOptions; | |
184 | + | |
185 | + /** | |
186 | + * L.Editable plugin instance. | |
187 | + */ | |
188 | + editTools: Editable; | |
189 | + } | |
190 | + | |
191 | + // tslint:disable-next-line:no-empty-interface | |
192 | + interface Polyline extends EditableMixin {} | |
193 | + | |
194 | + namespace Map { | |
195 | + interface MapOptions { | |
196 | + /** | |
197 | + * Whether to create a L.Editable instance at map init or not. | |
198 | + */ | |
199 | + editable?: boolean; | |
200 | + | |
201 | + /** | |
202 | + * Options to pass to L.Editable when instanciating. | |
203 | + */ | |
204 | + editOptions?: EditOptions; | |
205 | + } | |
206 | + } | |
207 | + | |
208 | + /** | |
209 | + * When editing a feature (marker, polyline…), an editor is attached to it. This editor basically knows | |
210 | + * how to handle the edition. | |
211 | + */ | |
212 | + interface BaseEditor { | |
213 | + /** | |
214 | + * Set up the drawing tools for the feature to be editable. | |
215 | + */ | |
216 | + enable(): MarkerEditor|PolylineEditor|PolygonEditor; | |
217 | + | |
218 | + /** | |
219 | + * Remove editing tools. | |
220 | + */ | |
221 | + disable(): MarkerEditor|PolylineEditor|PolygonEditor; | |
222 | + } | |
223 | + | |
224 | + /** | |
225 | + * Inherit from L.Editable.BaseEditor. | |
226 | + * Inherited by L.Editable.PolylineEditor and L.Editable.PolygonEditor. | |
227 | + */ | |
228 | + interface PathEditor extends BaseEditor { | |
229 | + /** | |
230 | + * Rebuild edit elements (vertex, middlemarker, etc.). | |
231 | + */ | |
232 | + reset(): void; | |
233 | + } | |
234 | + | |
235 | + /** | |
236 | + * Inherit from L.Editable.PathEditor. | |
237 | + */ | |
238 | + interface PolylineEditor extends PathEditor { | |
239 | + /** | |
240 | + * Set up drawing tools to continue the line forward. | |
241 | + */ | |
242 | + continueForward(): void; | |
243 | + | |
244 | + /** | |
245 | + * Set up drawing tools to continue the line backward. | |
246 | + */ | |
247 | + continueBackward(): void; | |
248 | + } | |
249 | + | |
250 | + /** | |
251 | + * Inherit from L.Editable.PathEditor. | |
252 | + */ | |
253 | + interface PolygonEditor extends PathEditor { | |
254 | + /** | |
255 | + * Set up drawing tools for creating a new hole on the polygon. If the latlng param is given, a first | |
256 | + * point is created. | |
257 | + */ | |
258 | + newHole(latlng: LatLng): void; | |
259 | + } | |
260 | + | |
261 | + /** | |
262 | + * Inherit from L.Editable.BaseEditor. | |
263 | + */ | |
264 | + // tslint:disable-next-line:no-empty-interface | |
265 | + interface MarkerEditor extends BaseEditor {} | |
266 | + | |
267 | + interface Marker extends EditableMixin, MarkerEditor {} | |
268 | + | |
269 | + interface Polyline extends EditableMixin, PolylineEditor {} | |
270 | + | |
271 | + interface Polygon extends EditableMixin, PolygonEditor {} | |
272 | +} | ... | ... |
... | ... | @@ -20,7 +20,8 @@ |
20 | 20 | "src/typings/jquery.flot.typings.d.ts", |
21 | 21 | "src/typings/jquery.jstree.typings.d.ts", |
22 | 22 | "src/typings/split.js.typings.d.ts", |
23 | - "src/typings/add-marker.d.ts" | |
23 | + "src/typings/add-marker.d.ts", | |
24 | + "src/typings/leaflet-editable.d.ts" | |
24 | 25 | ], |
25 | 26 | "paths": { |
26 | 27 | "@app/*": ["src/app/*"], | ... | ... |