Commit 423a491e8003e4a53b3271664a868c02fdffcaf7
1 parent
8750b26f
Editable Polygons without proper typings
Showing
15 changed files
with
524 additions
and
28 deletions
@@ -58,6 +58,7 @@ | @@ -58,6 +58,7 @@ | ||
58 | "jstree-bootstrap-theme": "^1.0.1", | 58 | "jstree-bootstrap-theme": "^1.0.1", |
59 | "jszip": "^3.4.0", | 59 | "jszip": "^3.4.0", |
60 | "leaflet": "^1.6.0", | 60 | "leaflet": "^1.6.0", |
61 | + "leaflet-editable": "^1.2.0", | ||
61 | "leaflet-polylinedecorator": "^1.6.0", | 62 | "leaflet-polylinedecorator": "^1.6.0", |
62 | "leaflet-providers": "^1.10.1", | 63 | "leaflet-providers": "^1.10.1", |
63 | "leaflet.gridlayer.googlemutant": "0.10.0", | 64 | "leaflet.gridlayer.googlemutant": "0.10.0", |
@@ -43,6 +43,8 @@ import { createTooltip, parseArray, safeExecute } from '@home/components/widget/ | @@ -43,6 +43,8 @@ import { createTooltip, parseArray, safeExecute } from '@home/components/widget/ | ||
43 | import { WidgetContext } from '@home/models/widget-component.models'; | 43 | import { WidgetContext } from '@home/models/widget-component.models'; |
44 | import { DatasourceData } from '@shared/models/widget.models'; | 44 | import { DatasourceData } from '@shared/models/widget.models'; |
45 | import { deepClone, isDefinedAndNotNull } from '@core/utils'; | 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 | export default abstract class LeafletMap { | 49 | export default abstract class LeafletMap { |
48 | 50 | ||
@@ -168,6 +170,75 @@ export default abstract class LeafletMap { | @@ -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 | public setMap(map: L.Map) { | 242 | public setMap(map: L.Map) { |
172 | this.map = map; | 243 | this.map = map; |
173 | if (this.options.useDefaultCenterPosition) { | 244 | if (this.options.useDefaultCenterPosition) { |
@@ -178,6 +249,9 @@ export default abstract class LeafletMap { | @@ -178,6 +249,9 @@ export default abstract class LeafletMap { | ||
178 | if (this.options.draggableMarker) { | 249 | if (this.options.draggableMarker) { |
179 | this.addMarkerControl(); | 250 | this.addMarkerControl(); |
180 | } | 251 | } |
252 | + if (this.options.editablePolygon) { | ||
253 | + this.addPolygonControl(); | ||
254 | + } | ||
181 | this.map$.next(this.map); | 255 | this.map$.next(this.map); |
182 | } | 256 | } |
183 | 257 | ||
@@ -189,6 +263,10 @@ export default abstract class LeafletMap { | @@ -189,6 +263,10 @@ export default abstract class LeafletMap { | ||
189 | return of(null); | 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 | createLatLng(lat: number, lng: number): L.LatLng { | 270 | createLatLng(lat: number, lng: number): L.LatLng { |
193 | return L.latLng(lat, lng); | 271 | return L.latLng(lat, lng); |
194 | } | 272 | } |
@@ -255,10 +333,16 @@ export default abstract class LeafletMap { | @@ -255,10 +333,16 @@ export default abstract class LeafletMap { | ||
255 | return L.latLng(lat, lng) as L.LatLng; | 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 | convertToCustomFormat(position: L.LatLng): object { | 348 | convertToCustomFormat(position: L.LatLng): object { |
@@ -268,6 +352,26 @@ export default abstract class LeafletMap { | @@ -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 | updateData(data: DatasourceData[], formattedData: FormattedData[], drawRoutes: boolean, showPolygon: boolean) { | 375 | updateData(data: DatasourceData[], formattedData: FormattedData[], drawRoutes: boolean, showPolygon: boolean) { |
272 | this.ready$.subscribe(() => { | 376 | this.ready$.subscribe(() => { |
273 | if (drawRoutes) { | 377 | if (drawRoutes) { |
@@ -394,6 +498,15 @@ export default abstract class LeafletMap { | @@ -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 | updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { | 510 | updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { |
398 | this.map$.subscribe(map => { | 511 | this.map$.subscribe(map => { |
399 | if (this.points) { | 512 | if (this.points) { |
@@ -509,9 +622,14 @@ export default abstract class LeafletMap { | @@ -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 | this.ready$.subscribe(() => { | 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 | if (updateBounds) { | 633 | if (updateBounds) { |
516 | const bounds = polygon.leafletPoly.getBounds(); | 634 | const bounds = polygon.leafletPoly.getBounds(); |
517 | this.fitBounds(bounds); | 635 | this.fitBounds(bounds); |
@@ -34,6 +34,7 @@ export type PosFuncton = (origXPos, origYPos) => { x, y }; | @@ -34,6 +34,7 @@ export type PosFuncton = (origXPos, origYPos) => { x, y }; | ||
34 | 34 | ||
35 | export type MapSettings = { | 35 | export type MapSettings = { |
36 | draggableMarker: boolean; | 36 | draggableMarker: boolean; |
37 | + editablePolygon: boolean; | ||
37 | initCallback?: () => any; | 38 | initCallback?: () => any; |
38 | posFunction: PosFuncton; | 39 | posFunction: PosFuncton; |
39 | defaultZoomLevel?: number; | 40 | defaultZoomLevel?: number; |
@@ -140,6 +141,7 @@ export type PolygonSettings = { | @@ -140,6 +141,7 @@ export type PolygonSettings = { | ||
140 | usePolygonColorFunction: boolean; | 141 | usePolygonColorFunction: boolean; |
141 | polygonTooltipFunction: GenericFunction; | 142 | polygonTooltipFunction: GenericFunction; |
142 | polygonColorFunction?: GenericFunction; | 143 | polygonColorFunction?: GenericFunction; |
144 | + editablePolygon: boolean; | ||
143 | } | 145 | } |
144 | 146 | ||
145 | export type PolylineSettings = { | 147 | export type PolylineSettings = { |
@@ -268,6 +270,7 @@ export const defaultSettings: any = { | @@ -268,6 +270,7 @@ export const defaultSettings: any = { | ||
268 | credentials: '', | 270 | credentials: '', |
269 | markerClusteringSetting: null, | 271 | markerClusteringSetting: null, |
270 | draggableMarker: false, | 272 | draggableMarker: false, |
273 | + editablePolygon: false, | ||
271 | fitMapBounds: true, | 274 | fitMapBounds: true, |
272 | mapPageSize: DEFAULT_MAP_PAGE_SIZE | 275 | mapPageSize: DEFAULT_MAP_PAGE_SIZE |
273 | }; | 276 | }; |
@@ -79,6 +79,7 @@ export class MapWidgetController implements MapWidgetInterface { | @@ -79,6 +79,7 @@ export class MapWidgetController implements MapWidgetInterface { | ||
79 | this.map = new MapClass(this.ctx, $element, this.settings); | 79 | this.map = new MapClass(this.ctx, $element, this.settings); |
80 | (this.ctx as any).mapInstance = this.map; | 80 | (this.ctx as any).mapInstance = this.map; |
81 | this.map.saveMarkerLocation = this.setMarkerLocation; | 81 | this.map.saveMarkerLocation = this.setMarkerLocation; |
82 | + this.map.savePolygonLocation = this.savePolygonLocation; | ||
82 | this.pageLink = { | 83 | this.pageLink = { |
83 | page: 0, | 84 | page: 0, |
84 | pageSize: this.settings.mapPageSize, | 85 | pageSize: this.settings.mapPageSize, |
@@ -239,6 +240,56 @@ export class MapWidgetController implements MapWidgetInterface { | @@ -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 | initSettings(settings: UnitedMapSettings, isEditMap?: boolean): UnitedMapSettings { | 293 | initSettings(settings: UnitedMapSettings, isEditMap?: boolean): UnitedMapSettings { |
243 | const functionParams = ['data', 'dsData', 'dsIndex']; | 294 | const functionParams = ['data', 'dsData', 'dsIndex']; |
244 | this.provider = settings.provider || this.mapProvider; | 295 | this.provider = settings.provider || this.mapProvider; |
@@ -270,6 +321,9 @@ export class MapWidgetController implements MapWidgetInterface { | @@ -270,6 +321,9 @@ export class MapWidgetController implements MapWidgetInterface { | ||
270 | if (isEditMap && !settings.hasOwnProperty('draggableMarker')) { | 321 | if (isEditMap && !settings.hasOwnProperty('draggableMarker')) { |
271 | settings.draggableMarker = true; | 322 | settings.draggableMarker = true; |
272 | } | 323 | } |
324 | + if (isEditMap && !settings.hasOwnProperty('editablePolygon')) { | ||
325 | + settings.editablePolygon = true; | ||
326 | + } | ||
273 | return { ...defaultSettings, ...settings, ...customOptions, } | 327 | return { ...defaultSettings, ...settings, ...customOptions, } |
274 | } | 328 | } |
275 | 329 |
@@ -22,13 +22,13 @@ | @@ -22,13 +22,13 @@ | ||
22 | background-repeat: no-repeat; | 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 | .leaflet-container{ | 33 | .leaflet-container{ |
34 | background-color: white; | 34 | background-color: white; |
@@ -14,7 +14,8 @@ | @@ -14,7 +14,8 @@ | ||
14 | /// limitations under the License. | 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 | import { createTooltip, parseWithTranslation, safeExecute } from './maps-utils'; | 19 | import { createTooltip, parseWithTranslation, safeExecute } from './maps-utils'; |
19 | import { FormattedData, PolygonSettings } from './map-models'; | 20 | import { FormattedData, PolygonSettings } from './map-models'; |
20 | 21 | ||
@@ -25,11 +26,10 @@ export class Polygon { | @@ -25,11 +26,10 @@ export class Polygon { | ||
25 | data: FormattedData; | 26 | data: FormattedData; |
26 | dataSources: FormattedData[]; | 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 | this.dataSources = dataSources; | 30 | this.dataSources = dataSources; |
30 | this.data = polyData; | 31 | this.data = polyData; |
31 | const polygonColor = this.getPolygonColor(settings); | 32 | const polygonColor = this.getPolygonColor(settings); |
32 | - | ||
33 | this.leafletPoly = L.polygon(polyData[this.settings.polygonKeyName], { | 33 | this.leafletPoly = L.polygon(polyData[this.settings.polygonKeyName], { |
34 | fill: true, | 34 | fill: true, |
35 | fillColor: polygonColor, | 35 | fillColor: polygonColor, |
@@ -38,6 +38,14 @@ export class Polygon { | @@ -38,6 +38,14 @@ export class Polygon { | ||
38 | fillOpacity: settings.polygonOpacity, | 38 | fillOpacity: settings.polygonOpacity, |
39 | opacity: settings.polygonStrokeOpacity | 39 | opacity: settings.polygonStrokeOpacity |
40 | }).addTo(this.map); | 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 | if (settings.showPolygonTooltip) { | 50 | if (settings.showPolygonTooltip) { |
43 | this.tooltip = createTooltip(this.leafletPoly, settings, polyData.$datasource); | 51 | this.tooltip = createTooltip(this.leafletPoly, settings, polyData.$datasource); |
@@ -64,7 +72,13 @@ export class Polygon { | @@ -64,7 +72,13 @@ export class Polygon { | ||
64 | updatePolygon(data: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { | 72 | updatePolygon(data: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) { |
65 | this.data = data; | 73 | this.data = data; |
66 | this.dataSources = dataSources; | 74 | this.dataSources = dataSources; |
75 | + if (settings.editablePolygon) { | ||
76 | + this.leafletPoly.disableEdit(); | ||
77 | + } | ||
67 | this.leafletPoly.setLatLngs(data[this.settings.polygonKeyName]); | 78 | this.leafletPoly.setLatLngs(data[this.settings.polygonKeyName]); |
79 | + if (settings.editablePolygon) { | ||
80 | + this.leafletPoly.enableEdit(this.map); | ||
81 | + } | ||
68 | if (settings.showPolygonTooltip) | 82 | if (settings.showPolygonTooltip) |
69 | this.updateTooltip(this.data); | 83 | this.updateTooltip(this.data); |
70 | this.updatePolygonColor(settings); | 84 | this.updatePolygonColor(settings); |
@@ -36,7 +36,7 @@ export class GoogleMap extends LeafletMap { | @@ -36,7 +36,7 @@ export class GoogleMap extends LeafletMap { | ||
36 | super(ctx, $container, options); | 36 | super(ctx, $container, options); |
37 | this.resource = ctx.$injector.get(ResourcesService); | 37 | this.resource = ctx.$injector.get(ResourcesService); |
38 | this.loadGoogle(() => { | 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 | (L.gridLayer as any).googleMutant({ | 40 | (L.gridLayer as any).googleMutant({ |
41 | type: options?.gmDefaultMapType || 'roadmap' | 41 | type: options?.gmDefaultMapType || 'roadmap' |
42 | }).addTo(map); | 42 | }).addTo(map); |
@@ -22,7 +22,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; | @@ -22,7 +22,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; | ||
22 | export class HEREMap extends LeafletMap { | 22 | export class HEREMap extends LeafletMap { |
23 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { | 23 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { |
24 | super(ctx, $container, options); | 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 | const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials); | 26 | const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials); |
27 | tileLayer.addTo(map); | 27 | tileLayer.addTo(map); |
28 | super.setMap(map); | 28 | super.setMap(map); |
@@ -24,7 +24,9 @@ import { WidgetContext } from '@home/models/widget-component.models'; | @@ -24,7 +24,9 @@ import { WidgetContext } from '@home/models/widget-component.models'; | ||
24 | import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models'; | 24 | import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models'; |
25 | import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; | 25 | import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
26 | import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; | 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 | const maxZoom = 4;// ? | 31 | const maxZoom = 4;// ? |
30 | 32 | ||
@@ -196,14 +198,15 @@ export class ImageMap extends LeafletMap { | @@ -196,14 +198,15 @@ export class ImageMap extends LeafletMap { | ||
196 | initMap(updateImage?: boolean) { | 198 | initMap(updateImage?: boolean) { |
197 | if (!this.map && this.aspect > 0) { | 199 | if (!this.map && this.aspect > 0) { |
198 | const center = this.pointToLatLng(this.width / 2, this.height / 2); | 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 | minZoom: 1, | 202 | minZoom: 1, |
201 | maxZoom, | 203 | maxZoom, |
202 | scrollWheelZoom: !this.options.disableScrollZooming, | 204 | scrollWheelZoom: !this.options.disableScrollZooming, |
203 | center, | 205 | center, |
204 | zoom: 1, | 206 | zoom: 1, |
205 | crs: L.CRS.Simple, | 207 | crs: L.CRS.Simple, |
206 | - attributionControl: false | 208 | + attributionControl: false, |
209 | + editable: !!this.options.editablePolygon | ||
207 | }); | 210 | }); |
208 | this.updateBounds(updateImage); | 211 | this.updateBounds(updateImage); |
209 | } | 212 | } |
@@ -221,14 +224,17 @@ export class ImageMap extends LeafletMap { | @@ -221,14 +224,17 @@ export class ImageMap extends LeafletMap { | ||
221 | expression.y * this.height); | 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 | if (el.length === 2 && !el.some(isNaN)) { | 229 | if (el.length === 2 && !el.some(isNaN)) { |
227 | return this.pointToLatLng( | 230 | return this.pointToLatLng( |
228 | el[0] * this.width, | 231 | el[0] * this.width, |
229 | el[1] * this.height) | 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 | }).filter(el => !!el) | 238 | }).filter(el => !!el) |
233 | } | 239 | } |
234 | 240 | ||
@@ -247,4 +253,25 @@ export class ImageMap extends LeafletMap { | @@ -247,4 +253,25 @@ export class ImageMap extends LeafletMap { | ||
247 | [this.options.yPosKeyName]: calculateNewPointCoordinate(point.y, this.height) | 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,7 +22,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; | ||
22 | export class OpenStreetMap extends LeafletMap { | 22 | export class OpenStreetMap extends LeafletMap { |
23 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { | 23 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { |
24 | super(ctx, $container, options); | 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 | let tileLayer; | 26 | let tileLayer; |
27 | if (options.useCustomProvider) | 27 | if (options.useCustomProvider) |
28 | tileLayer = L.tileLayer(options.customProviderTileUrl); | 28 | tileLayer = L.tileLayer(options.customProviderTileUrl); |
@@ -24,7 +24,7 @@ export class TencentMap extends LeafletMap { | @@ -24,7 +24,7 @@ export class TencentMap extends LeafletMap { | ||
24 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { | 24 | constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { |
25 | super(ctx, $container, options); | 25 | super(ctx, $container, options); |
26 | const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0'; | 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 | const txLayer = L.tileLayer(txUrl, { | 28 | const txLayer = L.tileLayer(txUrl, { |
29 | subdomains: '0123', | 29 | subdomains: '0123', |
30 | tms: true, | 30 | tms: true, |
ui-ngx/src/assets/add_polygon.svg
0 → 100644
@@ -20,9 +20,12 @@ declare module 'leaflet' { | @@ -20,9 +20,12 @@ declare module 'leaflet' { | ||
20 | 20 | ||
21 | namespace Control { | 21 | namespace Control { |
22 | class AddMarker extends L.Control { } | 22 | class AddMarker extends L.Control { } |
23 | + class AddPolygon extends L.Control { } | ||
23 | } | 24 | } |
24 | 25 | ||
25 | namespace control { | 26 | namespace control { |
26 | function addMarker(options): Control.AddMarker; | 27 | function addMarker(options): Control.AddMarker; |
28 | + function addPolygon(options): Control.AddPolygon; | ||
27 | } | 29 | } |
28 | -} | ||
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,7 +20,8 @@ | ||
20 | "src/typings/jquery.flot.typings.d.ts", | 20 | "src/typings/jquery.flot.typings.d.ts", |
21 | "src/typings/jquery.jstree.typings.d.ts", | 21 | "src/typings/jquery.jstree.typings.d.ts", |
22 | "src/typings/split.js.typings.d.ts", | 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 | "paths": { | 26 | "paths": { |
26 | "@app/*": ["src/app/*"], | 27 | "@app/*": ["src/app/*"], |