From e8b2066fb6f38bcc5775ea5b51a0755dfb763df8 Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Mon, 13 Oct 2025 17:12:24 +0300 Subject: [PATCH] Added logic to create and edit polylines --- .../lib/maps/data-layer/map-data-layer.ts | 22 +- .../maps/data-layer/polygons-data-layer.ts | 19 +- .../maps/data-layer/polylines-data-layer.ts | 97 ++---- .../lib/maps/data-layer/shapes-data-layer.ts | 27 +- .../components/widget/lib/maps/geo-map.ts | 31 +- .../components/widget/lib/maps/image-map.ts | 3 +- .../home/components/widget/lib/maps/map.ts | 322 +++++++++--------- ui-ngx/src/app/shared/models/widget.models.ts | 6 +- .../shared/models/widget/maps/map.models.ts | 61 ++-- .../assets/locale/locale.constant-en_US.json | 5 +- 10 files changed, 307 insertions(+), 286 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts index 4380f36186..f3c13167ae 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts @@ -28,7 +28,6 @@ import { } from '@shared/models/widget/maps/map.models'; import { createLabelFromPattern, - guid, isDefined, isDefinedAndNotNull, isNumber, @@ -53,7 +52,8 @@ export class DataLayerPatternProcessor { private pattern: string; constructor(private dataLayer: TbMapDataLayer, - private settings: DataLayerPatternSettings) {} + private settings: DataLayerPatternSettings) { + } public setup(): Observable { if (this.settings.type === DataLayerPatternType.function) { @@ -91,7 +91,8 @@ export class DataLayerColorProcessor { private range: ColorRange[]; constructor(private dataLayer: TbMapDataLayer, - private settings: DataLayerColorSettings) {} + private settings: DataLayerColorSettings) { + } public setup(): Observable { this.color = this.settings.color; @@ -150,7 +151,8 @@ export abstract class TbDataLayerItem(); - protected groupsState: {[group: string]: boolean} = {}; + protected groupsState: { [group: string]: boolean } = {}; protected enabled = true; @@ -197,7 +199,7 @@ export abstract class TbMapDataLayer settings.type === DataLayerColorType.range && settings.rangeKey) - .map(settings => settings.rangeKey); + .map(settings => settings.rangeKey); dataKeys.push(...colorRangeKeys); dataKeys.push(...this.getDataKeys()); return dataKeys; @@ -316,9 +318,11 @@ export abstract class TbMapDataLayer, dsData: FormattedData[]): void { @@ -148,7 +148,7 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem this.editing = true); this.polygon.on('pm:markerdragend', () => setTimeout(() => { this.editing = false; - }) ); + })); this.polygon.on('pm:edit', () => this.savePolygonCoordinates()); this.polygon.pm.enable(); const map = this.dataLayer.getMap(); @@ -246,8 +246,10 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem { if (e.layer instanceof L.Polygon) { @@ -365,6 +367,7 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem, dsData: FormattedData[]): void { @@ -156,27 +153,13 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { - // const map = this.dataLayer.getMap().getMap(); - // if (!map.pm.globalCutModeEnabled()) { - // this.disablePolylineRotateMode(); - // this.disablePolylineEditMode(); - // } else { - // this.enablePolylineEditMode(); - // } - // } - // }, { id: 'rotate', - title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.rotate'), + title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polyline.rotate'), iconClass: 'tb-rotate', click: (e, button) => { if (!this.polyline.pm.rotateEnabled()) { @@ -195,8 +178,8 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { @@ -224,7 +207,7 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem this.editing = true); this.polyline.on('pm:markerdragend', () => setTimeout(() => { this.editing = false; - }) ); + })); this.polyline.on('pm:edit', () => this.savePolylineCoordinates()); this.polyline.pm.enable(); const map = this.dataLayer.getMap(); @@ -267,13 +250,6 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem boundsArray.find(boundPoint => boundPoint.equals(point as L.LatLng)) !== undefined)) { - coordinates = [bounds.getNorthWest(), bounds.getSouthEast()]; - } - } this.dataLayer.savePolylineCoordinates(this.data, coordinates).subscribe(); } @@ -283,24 +259,24 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { @@ -340,7 +311,7 @@ export class TbPolylineDataLayer extends TbShapesDataLayer): TbPolygonRawCoordinates { + public extractPolylineCoordinates(data: FormattedData): TbPolylineRawCoordinates { let rawPolyData = data[this.settings.polylineKey.label]; if (isString(rawPolyData)) { rawPolyData = JSON.parse(rawPolyData); @@ -349,7 +320,7 @@ export class TbPolylineDataLayer extends TbShapesDataLayer, coordinates: TbPolylineCoordinates): Observable { - const converted = coordinates ? this.map.coordinatesToPolygonData(coordinates) : null; + const converted = coordinates ? this.map.coordinatesToPolylineData(coordinates) : null; const polylineData = [ { dataKey: this.settings.polylineKey, @@ -366,7 +337,7 @@ export class TbPolylineDataLayer extends TbShapesDataLayer): Partial { - return defaultBasePolygonsDataLayerSettings(map.type()); + return defaultBasePolylinesDataLayerSettings(map.type()); } protected doSetup(): Observable { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts index c24d40c5bf..b2a42e5d35 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts @@ -37,10 +37,12 @@ import { isDefinedAndNotNull, objectHashCode, parseTbFunction, safeExecuteTbFunc import { CompiledTbFunction } from '@shared/models/js-function.models'; import { ImagePipe } from '@shared/pipe/image.pipe'; -export type ShapePatternStorage = {[id: string]: { - pattern: L.TB.Pattern; - refCount: number; -}}; +export type ShapePatternStorage = { + [id: string]: { + pattern: L.TB.Pattern; + refCount: number; + } +}; interface ShapePatternInfo { type: ShapeFillType; @@ -88,7 +90,8 @@ abstract class ShapePatternProcessor { } protected constructor(protected dataLayer: TbMapDataLayer, - protected settings: S) {} + protected settings: S) { + } public abstract setup(): Observable; @@ -119,8 +122,10 @@ abstract class ShapePatternProcessor { let pattern: L.TB.Pattern; if (patternInfo.type === ShapeFillType.color) { pattern = new L.TB.Pattern({width: 1, height: 1}); - const fillRect = new L.TB.PatternRect({x: 0, y: 0, width: 1, height: 1, - fillOpacity: 1, stroke: false, fill: true, fillColor: patternInfo.fillColor}); + const fillRect = new L.TB.PatternRect({ + x: 0, y: 0, width: 1, height: 1, + fillOpacity: 1, stroke: false, fill: true, fillColor: patternInfo.fillColor + }); pattern.addElement(fillRect); } else if (patternInfo.type === ShapeFillType.image) { const patternOptions: L.TB.PatternOptions = { @@ -128,7 +133,7 @@ abstract class ShapePatternProcessor { height: 1, patternUnits: 'objectBoundingBox', patternContentUnits: 'objectBoundingBox', - viewBox: [0,0,patternInfo.fillImage.width,patternInfo.fillImage.height] + viewBox: [0, 0, patternInfo.fillImage.width, patternInfo.fillImage.height] }; if (patternInfo.fillImage.preserveAspectRatio) { patternOptions.preserveAspectRatioAlign = 'xMidYMid'; @@ -301,7 +306,7 @@ class ShapeStripePatternProcessor extends ShapePatternProcessor> extends TbLatestMapDataLayer { +export abstract class TbShapesDataLayer> extends TbLatestMapDataLayer { private shapePatternProcessor: ShapePatternProcessor; private strokeColorProcessor: DataLayerColorProcessor; @@ -349,7 +354,7 @@ export abstract class TbShapesDataLayer { this.shapePatternProcessor = ShapePatternProcessor.fromSettings(this, this.settings); this.strokeColorProcessor = new DataLayerColorProcessor(this, this.settings.strokeColor); - return forkJoin([this.shapePatternProcessor.setup(), this.strokeColorProcessor.setup()]); + return forkJoin([this.shapePatternProcessor ? this.shapePatternProcessor.setup() : of([]), this.strokeColorProcessor.setup()]); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts index d28c0359f2..2fc1f54d81 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts @@ -61,16 +61,16 @@ export class TbGeoMap extends TbMap { return of(map); } - protected onResize(): void {} + protected onResize(): void { + } protected fitBounds(bounds: L.LatLngBounds) { if (bounds.isValid()) { if (!this.settings.fitMapBounds && this.settings.defaultZoomLevel) { - this.map.setZoom(this.settings.defaultZoomLevel, { animate: false }); + this.map.setZoom(this.settings.defaultZoomLevel, {animate: false}); if (this.settings.useDefaultCenterPosition) { - this.map.panTo(this.defaultCenterPosition, { animate: false }); - } - else { + this.map.panTo(this.defaultCenterPosition, {animate: false}); + } else { this.map.panTo(bounds.getCenter()); } } else { @@ -80,13 +80,13 @@ export class TbGeoMap extends TbMap { minZoom = Math.max(minZoom, this.settings.defaultZoomLevel); } if (this.map.getZoom() > minZoom) { - this.map.setZoom(minZoom, { animate: false }); + this.map.setZoom(minZoom, {animate: false}); } }); if (this.settings.useDefaultCenterPosition) { bounds = bounds.extend(this.defaultCenterPosition); } - this.map.fitBounds(bounds, { padding: [50, 50], animate: false }); + this.map.fitBounds(bounds, {padding: [50, 50], animate: false}); this.map.invalidateSize(); } } @@ -125,12 +125,15 @@ export class TbGeoMap extends TbMap { ); } - public locationDataToLatLng(position: {x: number; y: number}): L.LatLng { + public locationDataToLatLng(position: { x: number; y: number }): L.LatLng { return L.latLng(position.x, position.y) as L.LatLng; } - public latLngToLocationData(position: L.LatLng): {x: number; y: number} { - position = position ? latLngPointToBounds(position, this.southWest, this.northEast, 0) : {lat: null, lng: null} as L.LatLng; + public latLngToLocationData(position: L.LatLng): { x: number; y: number } { + position = position ? latLngPointToBounds(position, this.southWest, this.northEast, 0) : { + lat: null, + lng: null + } as L.LatLng; return { x: position.lat, y: position.lng @@ -187,11 +190,9 @@ export class TbGeoMap extends TbMap { return (expression).map((el: TbPolylineRawCoordinate) => { if (!Array.isArray(el[0]) && !Array.isArray(el[1]) && el.length === 2) { return el; - } - // else if (Array.isArray(el) && el.length) { - // return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinates; - // } - else { + } else if (Array.isArray(el) && el.length) { + return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinate; + } else { return null; } }).filter(el => !!el); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts index df5f9e34f6..753528f006 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts @@ -352,8 +352,7 @@ export class TbImageMap extends TbMap { el[1] * this.height ); return [latLng.lat, latLng.lng] as TbPolylineRawCoordinate; - } - else if (Array.isArray(el) && el.length) { + } else if (Array.isArray(el) && el.length) { return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinate; } else { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts index 24f88308ff..9f3c359a6e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts @@ -27,7 +27,9 @@ import { TbCircleData, TbMapDatasource, TbPolygonCoordinates, - TbPolygonRawCoordinates, TbPolylineCoordinates, TbPolylineRawCoordinates + TbPolygonRawCoordinates, + TbPolylineCoordinates, + TbPolylineRawCoordinates } from '@shared/models/widget/maps/map.models'; import { WidgetContext } from '@home/models/widget-component.models'; import { @@ -80,12 +82,12 @@ import { TbTripsDataLayer } from '@home/components/widget/lib/maps/data-layer/tr import { CompiledTbFunction } from '@shared/models/js-function.models'; import { TbMapDataLayer } from '@home/components/widget/lib/maps/data-layer/map-data-layer'; import { EntityType } from '@shared/models/entity-type.models'; -import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; -import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide; import { ShapePatternStorage } from '@home/components/widget/lib/maps/data-layer/shapes-data-layer'; import { TbPolylineDataLayer } from '@home/components/widget/lib/maps/data-layer/polylines-data-layer'; +import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; +import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide; -type TooltipInstancesData = {root: HTMLElement, instances: ITooltipsterInstance[]}; +type TooltipInstancesData = { root: HTMLElement, instances: ITooltipsterInstance[] }; export abstract class TbMap { @@ -327,7 +329,7 @@ export abstract class TbMap { let datasources: TbMapDatasource[]; for (const layerType of mapDataLayerTypes) { const typeDatasources = this.latestDataLayers.filter(dl => dl.dataLayerType() === layerType) - .map(dl => dl.getDataSources()).flat(); + .map(dl => dl.getDataSources()).flat(); if (!datasources) { datasources = typeDatasources; } else { @@ -427,100 +429,100 @@ export abstract class TbMap { private setupEditMode() { - this.editToolbar = L.TB.bottomToolbar({ - mapElement: $(this.mapElement), - closeTitle: this.ctx.translate.instant('action.cancel'), - onClose: () => { - return this.deselectItem(true); - } - }); - - this.map.on('click', () => { - this.deselectItem(); - }); - - if (this.latestDataLayers.some(dl => dl.isEditable())) { - this.map.pm.setGlobalOptions({ snappable: false }); - this.map.pm.applyGlobalOptions(); - } - - const dragSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isDragEnabled()); - const showDragModeButton = this.settings.dragModeButton && dragSupportedDataLayers.length; - const addSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isAddEnabled()); - - if (showDragModeButton || addSupportedDataLayers.length) { - const drawToolbar = L.TB.toolbar({ - position: this.settings.controlsPosition - }).addTo(this.map); - if (showDragModeButton) { - this.dragModeButton = drawToolbar.toolbarButton({ - id: 'dragMode', - title: this.ctx.translate.instant('widgets.maps.data-layer.drag-drop-mode'), - iconClass: 'tb-drag-mode', - click: (e, button) => { - this.toggleDragMode(e, button); - } - }); - } - this.addMarkerDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'markers'); - if (this.addMarkerDataLayers.length) { - this.addMarkerButton = drawToolbar.toolbarButton({ - id: 'addMarker', - title: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker'), - iconClass: 'tb-place-marker', - click: (e, button) => { - this.placeMarker(e, button); - } - }); - this.addMarkerButton.setDisabled(true); - this.setPlaceMarkerStyle(); - } - this.addPolygonDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polygons'); - if (this.addPolygonDataLayers.length) { - this.addRectangleButton = drawToolbar.toolbarButton({ - id: 'addRectangle', - title: this.ctx.translate.instant('widgets.maps.data-layer.polygon.draw-rectangle'), - iconClass: 'tb-draw-rectangle', - click: (e, button) => { - this.drawRectangle(e, button); - } - }); - this.addRectangleButton.setDisabled(true); - this.addPolygonButton = drawToolbar.toolbarButton({ - id: 'addPolygon', - title: this.ctx.translate.instant('widgets.maps.data-layer.polygon.draw-polygon'), - iconClass: 'tb-draw-polygon', - click: (e, button) => { - this.drawPolygon(e, button); - } - }); - this.addPolygonButton.setDisabled(true); - } - this.addCircleDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'circles'); - if (this.addCircleDataLayers.length) { - this.addCircleButton = drawToolbar.toolbarButton({ - id: 'addCircle', - title: this.ctx.translate.instant('widgets.maps.data-layer.circle.draw-circle'), - iconClass: 'tb-draw-circle', - click: (e, button) => { - this.drawCircle(e, button); - } - }); - this.addCircleButton.setDisabled(true); - } - this.addPolylineDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polylines'); - if (this.addPolylineDataLayers.length) { - this.addPolylineButton = drawToolbar.toolbarButton({ - id: 'addPolyline', - title: this.ctx.translate.instant('widgets.maps.data-layer.polyline.draw-polyline'), - iconClass: 'tb-draw-polyline', - click: (e, button) => { - this.drawPolyline(e, button); - } - }); - this.addPolylineButton.setDisabled(true); - } - } + this.editToolbar = L.TB.bottomToolbar({ + mapElement: $(this.mapElement), + closeTitle: this.ctx.translate.instant('action.cancel'), + onClose: () => { + return this.deselectItem(true); + } + }); + + this.map.on('click', () => { + this.deselectItem(); + }); + + if (this.latestDataLayers.some(dl => dl.isEditable())) { + this.map.pm.setGlobalOptions({snappable: false}); + this.map.pm.applyGlobalOptions(); + } + + const dragSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isDragEnabled()); + const showDragModeButton = this.settings.dragModeButton && dragSupportedDataLayers.length; + const addSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isAddEnabled()); + + if (showDragModeButton || addSupportedDataLayers.length) { + const drawToolbar = L.TB.toolbar({ + position: this.settings.controlsPosition + }).addTo(this.map); + if (showDragModeButton) { + this.dragModeButton = drawToolbar.toolbarButton({ + id: 'dragMode', + title: this.ctx.translate.instant('widgets.maps.data-layer.drag-drop-mode'), + iconClass: 'tb-drag-mode', + click: (e, button) => { + this.toggleDragMode(e, button); + } + }); + } + this.addMarkerDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'markers'); + if (this.addMarkerDataLayers.length) { + this.addMarkerButton = drawToolbar.toolbarButton({ + id: 'addMarker', + title: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker'), + iconClass: 'tb-place-marker', + click: (e, button) => { + this.placeMarker(e, button); + } + }); + this.addMarkerButton.setDisabled(true); + this.setPlaceMarkerStyle(); + } + this.addPolygonDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polygons'); + if (this.addPolygonDataLayers.length) { + this.addRectangleButton = drawToolbar.toolbarButton({ + id: 'addRectangle', + title: this.ctx.translate.instant('widgets.maps.data-layer.polygon.draw-rectangle'), + iconClass: 'tb-draw-rectangle', + click: (e, button) => { + this.drawRectangle(e, button); + } + }); + this.addRectangleButton.setDisabled(true); + this.addPolygonButton = drawToolbar.toolbarButton({ + id: 'addPolygon', + title: this.ctx.translate.instant('widgets.maps.data-layer.polygon.draw-polygon'), + iconClass: 'tb-draw-polygon', + click: (e, button) => { + this.drawPolygon(e, button); + } + }); + this.addPolygonButton.setDisabled(true); + } + this.addCircleDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'circles'); + if (this.addCircleDataLayers.length) { + this.addCircleButton = drawToolbar.toolbarButton({ + id: 'addCircle', + title: this.ctx.translate.instant('widgets.maps.data-layer.circle.draw-circle'), + iconClass: 'tb-draw-circle', + click: (e, button) => { + this.drawCircle(e, button); + } + }); + this.addCircleButton.setDisabled(true); + } + this.addPolylineDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polylines'); + if (this.addPolylineDataLayers.length) { + this.addPolylineButton = drawToolbar.toolbarButton({ + id: 'addPolyline', + title: this.ctx.translate.instant('widgets.maps.data-layer.polyline.draw-polyline'), + iconClass: 'tb-draw-polyline', + click: (e, button) => { + this.drawPolyline(e, button); + } + }); + this.addPolylineButton.setDisabled(true); + } + } } private toggleDragMode(_e: MouseEvent, button: L.TB.ToolbarButton): void { @@ -578,9 +580,9 @@ export abstract class TbMap { } private drawPolyline(e: MouseEvent, button: L.TB.ToolbarButton): void { - this.placeItem(e, button, this.addCircleDataLayers, (entity) => this.prepareDrawMode('Polyline', { - firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polyline.polyline-place-first-point-hint-with-entity', {entityName: entity.entity.entityDisplayName}), - continueLine: this.ctx.translate.instant('widgets.maps.data-layer.polyline.continue-polyline-hint-with-entity', {entityName: entity.entity.entityDisplayName}), + this.placeItem(e, button, this.addPolylineDataLayers, (entity) => this.prepareDrawMode('Line', { + startPolyline: this.ctx.translate.instant('widgets.maps.data-layer.polyline.polyline-place-first-point-hint-with-entity', {entityName: entity.entity.entityDisplayName}), + finishPolyline: this.ctx.translate.instant('widgets.maps.data-layer.polyline.finish-polyline-hint', {entityName: entity.entity.entityDisplayName}), })); } @@ -706,7 +708,7 @@ export abstract class TbMap { this.createCircle(actionData); break; case MapItemType.polyline: - // this.createPolyline(actionData); // TODO + this.createPolyline(actionData); break; } } @@ -755,6 +757,17 @@ export abstract class TbMap { })); } + private createPolyline(actionData: PlaceMapItemActionData): void { + this.createItem(actionData, () => this.prepareDrawMode('Line', { + startPolyline: actionData.action.mapItemTooltips.startPolyline + ? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.startPolyline) + : this.ctx.translate.instant(mapItemTooltipsTranslation.startPolyline), + finishPolyline: actionData.action.mapItemTooltips.finishPolyline + ? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.finishPolyline) + : this.ctx.translate.instant(mapItemTooltipsTranslation.finishPolyline), + })); + } + private createItem(actionData: PlaceMapItemActionData, prepareDrawMode: () => void) { const actionId = 'id' in actionData.action ? actionData.action.id : 'map-button'; if (this.createMapItemActionId === actionId) { @@ -795,7 +808,7 @@ export abstract class TbMap { this.createMapItemActionId = actionId; - const convertLayerToCoordinates = (type: MapItemType, layer: L.Layer): {x: number; y: number} | TbPolygonRawCoordinates | TbCircleData => { + const convertLayerToCoordinates = (type: MapItemType, layer: L.Layer): { x: number; y: number } | TbPolygonRawCoordinates | TbCircleData => { switch (type) { case MapItemType.marker: if (layer instanceof L.Marker) { @@ -852,8 +865,8 @@ export abstract class TbMap { this.editToolbar.close(); } - private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle' | 'Polyline', tooltipsTranslation: Record) { - this.map.pm.setLang('en', { tooltips: tooltipsTranslation }, 'en'); + private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle' | 'Line', tooltipsTranslation: Record) { + this.map.pm.setLang('en', {tooltips: tooltipsTranslation}, 'en'); this.map.pm.enableDraw(shape); // @ts-ignore L.DomUtil.addClass(this.map.pm.Draw[shape]._hintMarker.getTooltip()._container, 'tb-place-item-label'); @@ -887,46 +900,46 @@ export abstract class TbMap { tooltipData.instances = []; } $(root) - .find('a[role="button"]:not(.leaflet-pm-action)') - .each((_index, element) => { - let title: string; - if (element.title) { - title = element.title; - $(element).removeAttr('title'); - } else if (element.parentElement.title) { - title = element.parentElement.title; - $(element).parent().removeAttr('title'); - } - const tooltip = $(element).tooltipster( - { - content: title, - theme: 'tooltipster-shadow', - delay: 10, - triggerClose: { - click: true, - tap: true, - scroll: true, - mouseleave: true - }, - side, - distance: 2, - trackOrigin: true, - functionBefore: (_instance, helper) => { - if (helper.origin.ariaDisabled === 'true' || helper.origin.parentElement.classList.contains('active')) { - return false; - } - }, - } - ); - const instance = tooltip.tooltipster('instance'); - tooltipData.instances.push(instance); - instance.on('destroyed', () => { - const index = tooltipData.instances.indexOf(instance); - if (index > -1) { - tooltipData.instances.splice(index, 1); + .find('a[role="button"]:not(.leaflet-pm-action)') + .each((_index, element) => { + let title: string; + if (element.title) { + title = element.title; + $(element).removeAttr('title'); + } else if (element.parentElement.title) { + title = element.parentElement.title; + $(element).parent().removeAttr('title'); } + const tooltip = $(element).tooltipster( + { + content: title, + theme: 'tooltipster-shadow', + delay: 10, + triggerClose: { + click: true, + tap: true, + scroll: true, + mouseleave: true + }, + side, + distance: 2, + trackOrigin: true, + functionBefore: (_instance, helper) => { + if (helper.origin.ariaDisabled === 'true' || helper.origin.parentElement.classList.contains('active')) { + return false; + } + }, + } + ); + const instance = tooltip.tooltipster('instance'); + tooltipData.instances.push(instance); + instance.on('destroyed', () => { + const index = tooltipData.instances.indexOf(instance); + if (index > -1) { + tooltipData.instances.splice(index, 1); + } + }); }); - }); }); } @@ -938,6 +951,7 @@ export abstract class TbMap { this.updateTripsAnchors(); this.updateBounds(); this.updateEditButtonsStates(); + } private updateTrips(subscription: IWidgetSubscription) { @@ -982,6 +996,7 @@ export abstract class TbMap { private updateTripsAppearance() { this.tripDataLayers.forEach(dl => dl.updateAppearance()); } + private updateTripsTime() { this.tripDataLayers.forEach(dl => dl.updateCurrentTime()); } @@ -1034,8 +1049,7 @@ export abstract class TbMap { (!this.bounds || !this.bounds.isValid() || (!this.bounds.equals(bounds) || force) && this.settings.fitMapBounds) && !mapBounds.contains(bounds) ) - ) - { + ) { this.bounds = bounds; if (!this.ignoreUpdateBounds && !this.isPlacingItem) { this.fitBounds(bounds); @@ -1062,6 +1076,9 @@ export abstract class TbMap { if (this.addCircleButton && this.addCircleButton !== this.currentEditButton) { this.addCircleButton.setDisabled(true); } + if (this.addPolylineButton && this.addPolylineButton !== this.currentEditButton) { + this.addPolylineButton.setDisabled(true); + } this.customActionsToolbar?.setDisabled(true); } else { if (this.dragModeButton) { @@ -1079,7 +1096,9 @@ export abstract class TbMap { if (this.addCircleButton) { this.addCircleButton.setDisabled(!this.addCircleDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); } - // TODO + if (this.addPolylineButton) { + this.addPolylineButton.setDisabled(!this.addPolylineDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); + } this.customActionsToolbar?.setDisabled(false); } } @@ -1190,7 +1209,7 @@ export abstract class TbMap { $event.preventDefault(); $event.stopPropagation(); } - const { entityId, entityName, entityLabel, entityType } = data.$datasource; + const {entityId, entityName, entityLabel, entityType} = data.$datasource; this.ctx.actionsApi.handleWidgetAction($event, action, { entityType, id: entityId @@ -1259,7 +1278,7 @@ export abstract class TbMap { } } - public saveLocation(data: FormattedData, values: {[key: string]: any}): Observable { + public saveLocation(data: FormattedData, values: { [key: string]: any }): Observable { const datasource = data.$datasource; let dataKeys = datasource.dataKeys; if (datasource.latestDataKeys) { @@ -1369,9 +1388,9 @@ export abstract class TbMap { } } - public abstract locationDataToLatLng(position: {x: number; y: number}): L.LatLng; + public abstract locationDataToLatLng(position: { x: number; y: number }): L.LatLng; - public abstract latLngToLocationData(position: L.LatLng): {x: number; y: number}; + public abstract latLngToLocationData(position: L.LatLng): { x: number; y: number }; public abstract polygonDataToCoordinates(coordinates: TbPolygonRawCoordinates): TbPolygonRawCoordinates; @@ -1386,5 +1405,4 @@ export abstract class TbMap { public abstract coordinatesToCircleData(center: L.LatLng, radius: number): TbCircleData; - } diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index c27639e45c..9addfc7b18 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -827,6 +827,8 @@ export interface MapItemTooltips { finishRect?: string; startCircle?: string; finishCircle?: string; + startPolyline?: string; + finishPolyline?: string; } export const mapItemTooltipsTranslation: Required = Object.freeze({ @@ -837,7 +839,9 @@ export const mapItemTooltipsTranslation: Required = Object.free startRect: 'widgets.maps.data-layer.polygon.rectangle-place-first-point-hint', finishRect: 'widgets.maps.data-layer.polygon.finish-rectangle-hint', startCircle: 'widgets.maps.data-layer.circle.place-circle-center-hint', - finishCircle: 'widgets.maps.data-layer.circle.finish-circle-hint' + finishCircle: 'widgets.maps.data-layer.circle.finish-circle-hint', + startPolyline: 'widgets.maps.data-layer.polyline.polyline-place-first-point-hint', + finishPolyline: 'widgets.maps.data-layer.polyline.finish-polyline-hint' }) export interface WidgetActionDescriptor extends WidgetAction { diff --git a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts index 99a3bc499b..00a84b014e 100644 --- a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts +++ b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts @@ -79,7 +79,7 @@ export const mapDataSourceSettingsToDatasource = (settings: MapDataSourceSetting }; const mapDataLayerDatasourceDataKeys = (settings: MapDataLayerSettings, - dataLayerType: MapDataLayerType): DataKey[] => { + dataLayerType: MapDataLayerType): DataKey[] => { const dataKeys = settings.additionalDataKeys?.length ? deepClone(settings.additionalDataKeys) : []; switch (dataLayerType) { case 'trips': @@ -169,7 +169,7 @@ export interface MapDataLayerSettings extends MapDataSourceSettings { tooltip: DataLayerTooltipSettings; click: WidgetAction; groups?: string[]; - edit: DataLayerEditSettings; + edit: DataLayerEditSettings; } export const defaultBaseDataLayerSettings = (mapType: MapType): Partial => ({ @@ -185,7 +185,7 @@ export const defaultBaseDataLayerSettings = (mapType: MapType): Partial${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
Temperature: ${temperature} °C
See tooltip settings for details' - : '${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See tooltip settings for details', + : '${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See tooltip settings for details', offsetX: 0, offsetY: -1 }, @@ -225,7 +225,7 @@ export const mapDataLayerValid = (dataLayer: MapDataLayerSettings, type: MapData case 'markers': const markersDataLayer = dataLayer as MarkersDataLayerSettings; if (!markersDataLayer.xKey?.type || !markersDataLayer.xKey?.name || - !markersDataLayer.yKey?.type || !markersDataLayer.xKey?.name) { + !markersDataLayer.yKey?.type || !markersDataLayer.xKey?.name) { return false; } break; @@ -309,6 +309,7 @@ export interface MarkerIconSettings extends BaseMarkerShapeSettings { iconContainer?: MarkerIconContainer; icon: string; } + export interface MarkerClusteringSettings { enable: boolean; zoomOnClick: boolean; @@ -612,8 +613,11 @@ export const defaultBasePolygonsDataLayerSettings = (mapType: MapType): Partial< color: '#3388ff', }, strokeWeight: 3 -} as Partial, defaultBaseDataLayerSettings(mapType), - {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) + } as Partial, defaultBaseDataLayerSettings(mapType), + { + label: {show: false}, + tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'} + } as Partial) export interface CirclesDataLayerSettings extends ShapeDataLayerSettings { circleKey: DataKey; @@ -663,8 +667,11 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial, defaultBaseDataLayerSettings(mapType), - {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) + } as Partial, defaultBaseDataLayerSettings(mapType), + { + label: {show: false}, + tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'} + } as Partial) export interface PolylinesDataLayerSettings extends ShapeDataLayerSettings, PathDataLayerSettings { polylineKey: DataKey; @@ -683,6 +690,11 @@ export const defaultPolylinesDataLayerSettings = (mapType: MapType, functionsOnl } as PolylinesDataLayerSettings, defaultBasePolylinesDataLayerSettings(mapType) as PolylinesDataLayerSettings); export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial => mergeDeep({ + fillType: ShapeFillType.color, + fillColor: { + type: DataLayerColorType.constant, + color: 'rgba(51,136,255,0.2)', + }, strokeColor: { type: DataLayerColorType.constant, color: '#3388ff', @@ -713,7 +725,10 @@ export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial offsetY: -1 }, } as Partial, defaultBaseDataLayerSettings(mapType), - {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) + { + label: {show: false}, + tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'} + } as Partial) export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: MapDataLayerType, functionsOnly = false): MapDataLayerSettings => { @@ -793,13 +808,13 @@ export const additionalMapDataSourceValid = (dataSource: AdditionalMapDataSource }; export const additionalMapDataSourceValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const dataSource: AdditionalMapDataSourceSettings = control.value; - if (!additionalMapDataSourceValid(dataSource)) { - return { - dataSource: true - }; - } - return null; + const dataSource: AdditionalMapDataSourceSettings = control.value; + if (!additionalMapDataSourceValid(dataSource)) { + return { + dataSource: true + }; + } + return null; }; export const defaultAdditionalMapDataSourceSettings = (functionsOnly = false): AdditionalMapDataSourceSettings => { @@ -922,7 +937,7 @@ export const defaultBaseMapSettings: BaseMapSettings = { tripTimeline: { showTimelineControl: false, timeStep: 1000, - speedOptions: [1,5,10,15,25], + speedOptions: [1, 5, 10, 15, 25], showTimestamp: true, timestampFormat: simpleDateFormat('yyyy-MM-dd HH:mm:ss'), snapToRealLocation: false, @@ -1294,7 +1309,7 @@ export type MapStringFunction = (data: FormattedData, dsData: FormattedData[]) => string; export type MapBooleanFunction = (data: FormattedData, - dsData: FormattedData[]) => boolean; + dsData: FormattedData[]) => boolean; export type MarkerImageFunction = (data: FormattedData, markerImages: string[], dsData: FormattedData[]) => MarkerImageInfo; @@ -1316,7 +1331,7 @@ export type TbPolygonCoordinates = TbPolygonCoordinate[]; export type TbPolylineRawCoordinate = L.LatLngTuple | L.LatLngTuple[] | L.LatLngTuple[][]; export type TbPolylineRawCoordinates = TbPolylineRawCoordinate[]; -export type TbPolylineData = L.LatLngTuple[] | L.LatLngTuple[][]; +export type TbPolylineData = L.LatLngTuple[] | L.LatLngTuple[][] | L.LatLngTuple[][][]; export type TbPolylineCoordinate = L.LatLng | L.LatLng[] | L.LatLng[][]; export type TbPolylineCoordinates = TbPolylineCoordinate[]; @@ -1354,7 +1369,7 @@ export const isValidLatLng = (latitude: any, longitude: any): boolean => isValidLatitude(latitude) && isValidLongitude(longitude); export const isCutPolygon = (data: TbPolygonCoordinates | TbPolygonRawCoordinates): boolean => { - return data.length > 1 && Array.isArray(data[0]) && (Array.isArray(data[0][0]) || (isNumber((data[0][0] as any).lat) && isNumber((data[0][0] as any).lng)) ); + return data.length > 1 && Array.isArray(data[0]) && (Array.isArray(data[0][0]) || (isNumber((data[0][0] as any).lat) && isNumber((data[0][0] as any).lng))); } export const parseCenterPosition = (position: string | [number, number]): [number, number] => { @@ -1438,7 +1453,7 @@ const mergeMapDatasource = (target: TbMapDatasource, source: TbMapDatasource): T return target; } -const imageAspectMap: {[key: string]: ImageWithAspect} = {}; +const imageAspectMap: { [key: string]: ImageWithAspect } = {}; const imageLoader = (imageUrl: string): Observable => new Observable((observer: Observer) => { const image = document.createElement('img'); // support IE @@ -1485,7 +1500,7 @@ export const loadImageWithAspect = (imagePipe: ImagePipe, imageUrl: string): Obs url, width: size[0], height: size[1], - aspect: size[0]/size[1] + aspect: size[0] / size[1] }; imageAspectMap[hash] = imageWithAspect; return imageWithAspect; @@ -1563,7 +1578,7 @@ export const latLngPointToBounds = (point: L.LatLng, southWest: L.LatLng, northE return point; } -export type TripRouteData = {[time: number]: FormattedData}; +export type TripRouteData = { [time: number]: FormattedData }; export const calculateInterpolationRatio = (firsMoment: number, secondMoment: number, intermediateMoment: number): number => { return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0e13fda6be..4290f39c24 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -8638,11 +8638,12 @@ "polyline-configuration": "Polyline configuration", "remove-polyline": "Remove polyline", "edit": "Edit polyline", + "rotate": "Rotate polyline", "remove-polyline-for": "Remove polyline for '{{entityName}}'", "draw-polyline": "Draw polyline", + "polyline-place-first-point-hint-with-entity": "Polyline for '{{entityName}}': click to place first point", "polyline-place-first-point-hint": "Polyline: click to place first point", - "continue-polyline-hint-with-entity": "Polyline for '{{entityName}}': click to continue drawing", - "finish-polyline-hint": "Polyline: click to finish drawing" + "finish-polyline-hint": "Polyline for '{{entityName}}': click to finish drawing" }, "select-entity": "Select entity", "select-entity-hint": "Hint: after selection click at the map to set position"