Browse Source

Added polylines to map: init

pull/14155/head
LeoMorgan113 11 months ago
parent
commit
c3c33100fd
  1. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts
  2. 458
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts
  3. 32
      ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts
  4. 40
      ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts
  5. 24
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
  6. 18
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html
  7. 24
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.ts
  8. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.ts
  9. 7
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html
  10. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts
  11. 4
      ui-ngx/src/app/shared/models/widget.models.ts
  12. 78
      ui-ngx/src/app/shared/models/widget/maps/map.models.ts

1
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts

@ -1,4 +1,5 @@
///
///
/// Copyright © 2016-2025 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");

458
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts

@ -0,0 +1,458 @@
///
/// Copyright © 2016-2025 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
defaultBasePolygonsDataLayerSettings,
isCutPolygon,
isJSON,
MapDataLayerType,
PolygonsDataLayerSettings,
PolylinesDataLayerSettings,
TbMapDatasource,
TbPolyData,
TbPolygonCoordinates,
TbPolygonRawCoordinates,
TbPolylineCoordinates, TbPolylineData,
TbPolylineRawCoordinates
} from '@shared/models/widget/maps/map.models';
import L from 'leaflet';
import { DataKey, FormattedData } from '@shared/models/widget.models';
import { ShapeStyleInfo, TbShapesDataLayer } from '@home/components/widget/lib/maps/data-layer/shapes-data-layer';
import { TbMap } from '@home/components/widget/lib/maps/map';
import { Observable } from 'rxjs';
import { isNotEmptyStr, isString } from '@core/utils';
import {
TbLatestDataLayerItem,
UnplacedMapDataItem
} from '@home/components/widget/lib/maps/data-layer/latest-map-data-layer';
import { map } from 'rxjs/operators';
class TbPolylineDataLayerItem extends TbLatestDataLayerItem<PolylinesDataLayerSettings, TbPolylineDataLayer> {
private polylineContainer: L.FeatureGroup;
private polyline: L.Polyline;
private polylineStyleInfo: ShapeStyleInfo;
private editing = false;
constructor(data: FormattedData<TbMapDatasource>,
dsData: FormattedData<TbMapDatasource>[],
protected settings: PolylinesDataLayerSettings,
protected dataLayer: TbPolylineDataLayer) {
super(data, dsData, settings, dataLayer);
}
public isEditing() {
return this.editing;
}
public updateBubblingMouseEvents() {
this.polyline.options.bubblingMouseEvents = !this.dataLayer.isEditMode();
}
public remove() {
super.remove();
if (this.polylineStyleInfo?.patternId) {
this.dataLayer.getMap().unUseShapePattern(this.polylineStyleInfo.patternId);
}
}
protected create(data: FormattedData<TbMapDatasource>, dsData: FormattedData<TbMapDatasource>[]): L.Layer {
const polyData = this.dataLayer.extractPolylineCoordinates(data);
const polyConstructor = L.polyline;
this.polyline = polyConstructor(polyData as (TbPolygonRawCoordinates & L.LatLngTuple[]), {
// noClip: true,
// snapIgnore: !this.dataLayer.isSnappable(),
bubblingMouseEvents: !this.dataLayer.isEditMode()
});
this.dataLayer.getShapeStyle(data, dsData, this.polylineStyleInfo?.patternId).subscribe((styleInfo) => {
this.polylineStyleInfo = styleInfo;
if (this.polyline) {
this.polyline.setStyle(this.polylineStyleInfo.style);
}
});
this.polylineContainer = L.featureGroup();
this.polyline.addTo(this.polylineContainer);
this.updateLabel(data, dsData);
return this.polylineContainer;
}
protected unbindLabel() {
this.polylineContainer.unbindTooltip();
}
protected bindLabel(content: L.Content): void {
this.polylineContainer.bindTooltip(content, {className: 'tb-polyline-label', permanent: true, direction: 'center'})
.openTooltip(this.polylineContainer.getBounds().getCenter());
}
protected doUpdate(data: FormattedData<TbMapDatasource>, dsData: FormattedData<TbMapDatasource>[]): void {
this.dataLayer.getShapeStyle(data, dsData, this.polylineStyleInfo?.patternId).subscribe((styleInfo) => {
this.polylineStyleInfo = styleInfo;
this.updatePolylineShape(data);
this.updateTooltip(data, dsData);
this.updateLabel(data, dsData);
if (!this.editing || !this.dataLayer.getMap().getMap().pm.globalCutModeEnabled()) {
this.polyline.setStyle(this.polylineStyleInfo.style);
}
});
}
protected doInvalidateCoordinates(data: FormattedData<TbMapDatasource>, _dsData: FormattedData<TbMapDatasource>[]): void {
this.updatePolylineShape(data);
}
protected addItemClass(clazz: string): void {
if ((this.polyline as any)._path) {
L.DomUtil.addClass((this.polyline as any)._path, clazz);
}
}
protected removeItemClass(clazz: string): void {
if ((this.polyline as any)._path) {
L.DomUtil.removeClass((this.polyline as any)._path, clazz);
}
}
protected enableDrag(): void {
this.polyline.pm.setOptions({
snappable: this.dataLayer.isSnappable()
});
this.polyline.pm.enableLayerDrag();
this.polyline.on('pm:dragstart', () => {
this.editing = true;
});
this.polyline.on('pm:drag', () => {
if (this.tooltip?.isOpen()) {
this.tooltip.setLatLng(this.polyline.getBounds().getCenter());
}
});
this.polyline.on('pm:dragend', () => {
this.savePolygonCoordinates();
this.editing = false;
});
}
protected disableDrag(): void {
this.polyline.pm.disableLayerDrag();
this.polyline.off('pm:dragstart');
this.polyline.off('pm:dragend');
}
protected onSelected(): L.TB.ToolbarButtonOptions[] {
const buttons: L.TB.ToolbarButtonOptions[] = [];
if (this.dataLayer.isEditEnabled()) {
// this.enablePolygonEditMode();
buttons.push(
{
id: 'cut',
title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.cut'),
iconClass: 'tb-cut',
click: (e, button) => {
const map = this.dataLayer.getMap().getMap();
// if (!map.pm.globalCutModeEnabled()) {
// this.disablePolygonRotateMode();
// this.disablePolygonEditMode();
// this.enablePolygonCutMode(button);
// } else {
// this.disablePolygonCutMode(button);
// this.enablePolygonEditMode();
// }
}
},
{
id: 'rotate',
title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.rotate'),
iconClass: 'tb-rotate',
click: (e, button) => {
if (!this.polyline.pm.rotateEnabled()) {
// this.disablePolygonCutMode();
// this.disablePolygonEditMode();
// this.enablePolygonRotateMode(button);
} else {
// this.disablePolygonRotateMode(button);
// this.enablePolygonEditMode();
}
}
}
);
}
return buttons;
}
protected onDeselected(): void {
if (this.dataLayer.isEditEnabled()) {
// this.disablePolygonEditMode();
// this.disablePolygonCutMode();
// this.disablePolygonRotateMode();
}
}
protected canDeselect(cancel = false): boolean {
const map = this.dataLayer.getMap().getMap();
if (map.pm.globalCutModeEnabled()) {
if (cancel) {
// this.disablePolygonCutMode();
}
return false;
} else if (this.polyline.pm.rotateEnabled()) {
if (cancel) {
// this.disablePolygonRotateMode();
}
return false;
} else if (this.editing) {
return false;
}
return true;
}
protected removeDataItemTitle(): string {
return this.dataLayer.getCtx().translate.instant('widgets.maps.data-layer.polygon.remove-polygon-for', {entityName: this.data.entityName});
}
protected removeDataItem(): Observable<any> {
return this.dataLayer.savePolylineCoordinates(this.data, null);
}
// private enablePolygonEditMode() {
// this.polyline.on('pm:markerdragstart', () => this.editing = true);
// this.polyline.on('pm:markerdragend', () => setTimeout(() => {
// this.editing = false;
// }) );
// this.polyline.on('pm:edit', () => this.savePolygonCoordinates());
// this.polyline.pm.enable();
// const map = this.dataLayer.getMap();
// map.getEditToolbar().getButton('remove')?.setDisabled(false);
// }
// private disablePolygonEditMode() {
// this.polyline.pm.disable();
// this.polyline.off('pm:markerdragstart');
// this.polyline.off('pm:markerdragend');
// this.polyline.off('pm:edit');
// const map = this.dataLayer.getMap();
// map.getEditToolbar().getButton('remove')?.setDisabled(true);
// }
// private enablePolygonCutMode(cutButton?: L.TB.ToolbarButton) {
// this.polylineContainer.closePopup();
// this.editing = true;
// this.polyline.options.bubblingMouseEvents = true;
// this.polyline.setStyle({...this.polylineStyleInfo.style, dashArray: '5 5', weight: 3,
// color: '#3388ff', opacity: 1, fillColor: '#3388ff', fillOpacity: 0.2});
// this.addItemClass('tb-cut-mode');
// this.polyline.once('pm:cut', (e) => {
// if (e.layer instanceof L.Polygon) {
// if (this.polyline instanceof L.Rectangle) {
// this.polylineContainer.removeLayer(this.polyline);
// this.polyline = L.polyline(e.layer.getLatLngs(), {
// ...this.polylineStyleInfo.style,
// snapIgnore: !this.dataLayer.isSnappable(),
// bubblingMouseEvents: !this.dataLayer.isEditMode()
// });
// this.polyline.addTo(this.polylineContainer);
// } else {
// this.polyline.setLatLngs(e.layer.getLatLngs());
// }
// }
// // @ts-ignore
// e.layer._pmTempLayer = true;
// e.layer.remove();
// this.polylineContainer.removeLayer(this.polyline);
// // @ts-ignore
// this.polyline._pmTempLayer = false;
// this.polyline.addTo(this.polylineContainer);
// this.updateSelectedState();
// cutButton?.setActive(false);
// this.savePolygonCoordinates()
// });
// const map = this.dataLayer.getMap().getMap();
// map.pm.setLang('en', {
// tooltips: {
// firstVertex: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.polygon-place-first-point-cut-hint'),
// continueLine: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.continue-polygon-cut-hint'),
// finishPoly: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.finish-polygon-cut-hint')
// }
// }, 'en');
// map.pm.enableGlobalCutMode({
// // @ts-ignore
// layersToCut: [this.polyline]
// });
// // @ts-ignore
// L.DomUtil.addClass(map.pm.Draw.Cut._hintMarker.getTooltip()._container, 'tb-place-item-label');
// cutButton?.setActive(true);
// map.once('pm:globalcutmodetoggled', (e) => {
// // if (!e.enabled) {
// // this.disablePolygonCutMode(cutButton);
// // this.enablePolygonEditMode();
// // }
// });
// }
// private disablePolygonCutMode(cutButton?: L.TB.ToolbarButton) {
// this.editing = false;
// this.polyline.options.bubblingMouseEvents = !this.dataLayer.isEditMode();
// this.polyline.setStyle({...this.polylineStyleInfo.style, dashArray: null});
// this.removeItemClass('tb-cut-mode');
// this.polyline.off('pm:cut');
// const map = this.dataLayer.getMap().getMap();
// map.pm.disableGlobalCutMode();
// cutButton?.setActive(false);
// }
// private enablePolygonRotateMode(rotateButton?: L.TB.ToolbarButton) {
// this.polylineContainer.closePopup();
// this.editing = true;
// this.polyline.on('pm:rotateend', () => {
// this.savePolygonCoordinates();
// });
// this.polyline.pm.enableRotate();
// rotateButton?.setActive(true);
// this.polyline.on('pm:rotatedisable', () => {
// this.disablePolygonRotateMode(rotateButton);
// this.enablePolygonEditMode();
// });
// }
//
// private disablePolygonRotateMode(rotateButton?: L.TB.ToolbarButton) {
// this.editing = false;
// this.polyline.pm.disableRotate();
// this.polyline.off('pm:rotateend');
// this.polyline.off('pm:rotatedisable');
// rotateButton?.setActive(false);
// }
private savePolygonCoordinates() {
let coordinates: TbPolygonCoordinates = this.polyline.getLatLngs();
if (coordinates.length === 1) {
coordinates = coordinates[0] as TbPolygonCoordinates;
}
if (this.polyline instanceof L.Rectangle && !isCutPolygon(coordinates)) {
const bounds = this.polyline.getBounds();
const boundsArray = [bounds.getNorthWest(), bounds.getNorthEast(), bounds.getSouthWest(), bounds.getSouthEast()];
if (coordinates.every(point => boundsArray.find(boundPoint => boundPoint.equals(point as L.LatLng)) !== undefined)) {
coordinates = [bounds.getNorthWest(), bounds.getSouthEast()];
}
}
this.dataLayer.savePolylineCoordinates(this.data, coordinates).subscribe();
}
private updatePolylineShape(data: FormattedData<TbMapDatasource>) {
if (this.editing) {
return;
}
const polyData = this.dataLayer.extractPolylineCoordinates(data) as TbPolylineData;
if (isCutPolygon(polyData) || polyData.length !== 2) {
if (this.polyline instanceof L.Rectangle) {
this.polylineContainer.removeLayer(this.polyline);
this.polyline = L.polyline(polyData, {
...this.polylineStyleInfo.style,
snapIgnore: !this.dataLayer.isSnappable(),
bubblingMouseEvents: !this.dataLayer.isEditMode(),
noClip: true
});
this.polyline.addTo(this.polylineContainer);
this.editModeUpdated();
} else {
this.polyline.setLatLngs(polyData);
}
} else if (polyData.length === 2) {
const bounds = new L.LatLngBounds(polyData as L.LatLngTuple[]);
// (this.polyline as L.Rectangle).setBounds(bounds);
}
}
}
export class TbPolylineDataLayer extends TbShapesDataLayer<PolylinesDataLayerSettings, TbPolylineDataLayer> {
constructor(protected map: TbMap<any>,
inputSettings: PolylinesDataLayerSettings) {
super(map, inputSettings);
}
public dataLayerType(): MapDataLayerType {
return 'polylines';
}
public placeItem(item: UnplacedMapDataItem, layer: L.Layer): void {
if (layer instanceof L.Polygon) {
let coordinates: TbPolylineCoordinates;
if (layer instanceof L.Rectangle) {
const bounds = layer.getBounds();
coordinates = [bounds.getNorthWest(), bounds.getSouthEast()];
} else {
coordinates = layer.getLatLngs();
if (coordinates.length === 1) {
coordinates = coordinates[0] as TbPolygonCoordinates;
}
}
this.savePolylineCoordinates(item.entity, coordinates).subscribe(
(converted) => {
item.entity[this.settings.polylineKey.label] = JSON.stringify(converted);
this.createItemFromUnplaced(item);
}
);
} else {
console.warn('Unable to place item, layer is not a polygon.');
}
}
public extractPolylineCoordinates(data: FormattedData<TbMapDatasource>): TbPolygonRawCoordinates {
let rawPolyData = data[this.settings.polylineKey.label];
if (isString(rawPolyData)) {
rawPolyData = JSON.parse(rawPolyData);
}
return this.map.polylineDataToCoordinates(rawPolyData);
}
public savePolylineCoordinates(data: FormattedData<TbMapDatasource>, coordinates: TbPolylineCoordinates): Observable<TbPolylineRawCoordinates> {
const converted = coordinates ? this.map.coordinatesToPolygonData(coordinates) : null;
const polylineData = [
{
dataKey: this.settings.polylineKey,
value: converted
}
];
return this.map.saveItemData(data.$datasource, polylineData, this.settings.edit?.attributeScope).pipe(
map(() => converted)
);
}
protected getDataKeys(): DataKey[] {
return [this.settings.polylineKey];
}
protected defaultBaseSettings(map: TbMap<any>): Partial<PolylinesDataLayerSettings> {
return defaultBasePolygonsDataLayerSettings(map.type());
}
protected doSetup(): Observable<any> {
return super.doSetup();
}
protected isValidLayerData(layerData: FormattedData<TbMapDatasource>): boolean {
return layerData && ((isNotEmptyStr(layerData[this.settings.polylineKey.label]) && !isJSON(layerData[this.settings.polylineKey.label])
|| Array.isArray(layerData[this.settings.polylineKey.label])));
}
protected createLayerItem(data: FormattedData<TbMapDatasource>, dsData: FormattedData<TbMapDatasource>[]): TbPolylineDataLayerItem {
return new TbPolylineDataLayerItem(data, dsData, this.settings, this);
}
}

32
ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts

@ -24,7 +24,11 @@ import {
TbPolygonCoordinate,
TbPolygonCoordinates,
TbPolygonRawCoordinate,
TbPolygonRawCoordinates
TbPolygonRawCoordinates,
TbPolylineCoordinate,
TbPolylineCoordinates,
TbPolylineRawCoordinate,
TbPolylineRawCoordinates
} from '@shared/models/widget/maps/map.models';
import { WidgetContext } from '@home/models/widget-component.models';
import { DeepPartial } from '@shared/models/common';
@ -179,5 +183,31 @@ export class TbGeoMap extends TbMap<GeoMapSettings> {
return circleData;
}
public polylineDataToCoordinates(expression: TbPolylineRawCoordinates): TbPolylineRawCoordinates {
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 {
return null;
}
}).filter(el => !!el);
}
public coordinatesToPolylineData(coordinates: TbPolylineCoordinates): TbPolylineRawCoordinates {
if (coordinates.length) {
return coordinates.map((point: TbPolylineCoordinate) => {
if (Array.isArray(point)) {
return this.coordinatesToPolylineData(point) as TbPolylineRawCoordinate;
} else {
const convertPoint = latLngPointToBounds(point, this.southWest, this.northEast);
return [convertPoint.lat, convertPoint.lng];
}
});
}
return [];
}
}

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

@ -23,7 +23,12 @@ import {
ImageSourceType,
loadImageWithAspect,
MapZoomAction,
TbCircleData, TbPolygonCoordinate, TbPolygonCoordinates, TbPolygonRawCoordinate, TbPolygonRawCoordinates
TbCircleData,
TbPolygonCoordinate,
TbPolygonCoordinates,
TbPolygonRawCoordinate,
TbPolygonRawCoordinates, TbPolylineCoordinate, TbPolylineCoordinates, TbPolylineRawCoordinate,
TbPolylineRawCoordinates
} from '@shared/models/widget/maps/map.models';
import { WidgetContext } from '@home/models/widget-component.models';
import { DeepPartial } from '@shared/models/common';
@ -339,4 +344,37 @@ export class TbImageMap extends TbMap<ImageMapSettings> {
);
}
public polylineDataToCoordinates(expression: TbPolylineRawCoordinates): TbPolylineRawCoordinates{
return expression.map((el: TbPolylineRawCoordinate) => {
if (!Array.isArray(el[0]) && !Array.isArray(el[1]) && el.length === 2) {
const latLng = this.pointToLatLng(
el[0] * this.width,
el[1] * this.height
);
return [latLng.lat, latLng.lng] as TbPolylineRawCoordinate;
}
else if (Array.isArray(el) && el.length) {
return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinate;
}
else {
return null;
}
}).filter(el => !!el);
}
public coordinatesToPolylineData(coordinates: TbPolylineCoordinates): TbPolylineRawCoordinates{
if (coordinates.length) {
return coordinates.map((point: TbPolylineCoordinate) => {
if (Array.isArray(point)) {
return this.coordinatesToPolylineData(point) as TbPolylineRawCoordinate;
} else {
const pos = this.latLngToPoint(point);
return [calculateNewPointCoordinate(pos.x, this.width), calculateNewPointCoordinate(pos.y, this.height)];
}
});
} else {
return [];
}
}
}

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

@ -27,7 +27,7 @@ import {
TbCircleData,
TbMapDatasource,
TbPolygonCoordinates,
TbPolygonRawCoordinates
TbPolygonRawCoordinates, TbPolylineCoordinates, TbPolylineRawCoordinates
} from '@shared/models/widget/maps/map.models';
import { WidgetContext } from '@home/models/widget-component.models';
import {
@ -83,6 +83,7 @@ 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';
type TooltipInstancesData = {root: HTMLElement, instances: ITooltipsterInstance[]};
@ -274,6 +275,11 @@ export abstract class TbMap<S extends BaseMapSettings> {
this.dataLayers.push(...circlesDataLayers);
this.latestDataLayers.push(...circlesDataLayers);
}
if (this.settings.polylines) {
const polylinesDataLayers = this.settings.polylines.map(settings => new TbPolylineDataLayer(this, settings));
this.dataLayers.push(...polylinesDataLayers);
this.latestDataLayers.push(...polylinesDataLayers);
}
if (this.settings.trips) {
const tripsDataLayers = this.settings.trips.map(settings => new TbTripsDataLayer(this, settings));
this.dataLayers.push(...tripsDataLayers);
@ -792,6 +798,15 @@ export abstract class TbMap<S extends BaseMapSettings> {
return this.coordinatesToCircleData(layer.getLatLng(), layer.getRadius());
}
return null;
case MapItemType.polyline:
if (layer instanceof L.Polyline) {
let coordinates: any = layer.getLatLngs();
if (coordinates.length === 1) {
coordinates = coordinates[0];
}
return this.coordinatesToPolylineData(coordinates);
}
return null;
}
}
}
@ -813,7 +828,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
this.editToolbar.close();
}
private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle', tooltipsTranslation: Record<string, string>) {
private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle' | 'Polyline', tooltipsTranslation: Record<string, string>) {
this.map.pm.setLang('en', { tooltips: tooltipsTranslation }, 'en');
this.map.pm.enableDraw(shape);
// @ts-ignore
@ -1040,6 +1055,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
if (this.addCircleButton) {
this.addCircleButton.setDisabled(!this.addCircleDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems()));
}
// TODO
this.customActionsToolbar?.setDisabled(false);
}
}
@ -1337,6 +1353,10 @@ export abstract class TbMap<S extends BaseMapSettings> {
public abstract coordinatesToPolygonData(coordinates: TbPolygonCoordinates): TbPolygonRawCoordinates;
public abstract polylineDataToCoordinates(coordinates: TbPolylineRawCoordinates): TbPolylineRawCoordinates;
public abstract coordinatesToPolylineData(coordinates: TbPolylineCoordinates): TbPolylineRawCoordinates;
public abstract circleDataToCoordinates(circle: TbCircleData): TbCircleData;
public abstract coordinatesToCircleData(center: L.LatLng, radius: number): TbCircleData;

18
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html

@ -116,6 +116,24 @@
(keyEdit)="editKey('circleKey')"
formControlName="circleKey">
</tb-data-key-input>
<tb-data-key-input
class="tb-key-field"
*ngIf="dataLayerType === 'polylines'"
required
requiredText="widgets.maps.data-layer.polyline.polyline-key-required"
[datasourceType]="dataLayerFormGroup.get('dsType').value"
[entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value"
[deviceId]="dataLayerFormGroup.get('dsDeviceId').value"
[aliasController]="context.aliasController"
[widgetType]="widgetType.latest"
[dataKeyType]="context.functionsOnly ? DataKeyType.function : null"
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]"
[callbacks]="context.callbacks"
[generateKey]="context.generateDataKey"
(keyEdit)="editKey('polylineKey')"
formControlName="polylineKey">
</tb-data-key-input>
<div class="tb-form-table-row-cell-buttons">
<button type="button"
mat-icon-button

24
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.ts

@ -40,7 +40,7 @@ import {
MapDataLayerType,
MapType,
MarkersDataLayerSettings,
PolygonsDataLayerSettings,
PolygonsDataLayerSettings, PolylinesDataLayerSettings,
TripsDataLayerSettings,
updateDataKeyToNewDsType
} from '@shared/models/widget/maps/map.models';
@ -150,6 +150,11 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
this.removeDataLayerText = 'widgets.maps.data-layer.circle.remove-circle';
this.dataLayerFormGroup.addControl('circleKey', this.fb.control(null, Validators.required));
break;
case 'polylines':
this.editDataLayerText = 'widgets.maps.data-layer.polylines.polyline-configuration';
this.removeDataLayerText = 'widgets.maps.data-layer.polylines.remove-polyline';
this.dataLayerFormGroup.addControl('polylineKey', this.fb.control(null, Validators.required));
break;
}
this.dataLayerFormGroup.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
@ -225,12 +230,20 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
}, {emitEvent: false}
);
break;
case 'polylines':
const polylinesDataLayer = value as PolylinesDataLayerSettings;
this.dataLayerFormGroup.patchValue(
{
polylineKey: polylinesDataLayer?.polylineKey
}, {emitEvent: false}
);
break;
}
this.updateValidators();
this.cd.markForCheck();
}
editKey(keyType: 'xKey' | 'yKey' | 'polygonKey' | 'circleKey') {
editKey(keyType: 'xKey' | 'yKey' | 'polygonKey' | 'circleKey' | 'polylineKey') {
const targetDataKey: DataKey = this.dataLayerFormGroup.get(keyType).value;
this.context.editKey(targetDataKey,
this.dataLayerFormGroup.get('dsDeviceId').value, this.dataLayerFormGroup.get('dsEntityAliasId').value,
@ -296,6 +309,13 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
updateModel = true;
}
break;
case 'polylines':
const polylineKey: DataKey = this.dataLayerFormGroup.get('polylineKey').value;
if (updateDataKeyToNewDsType(polylineKey, newDsType)) {
this.dataLayerFormGroup.get('polylineKey').patchValue(polylineKey, {emitEvent: false});
updateModel = true;
}
break;
}
this.updateValidators();
if (updateModel) {

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.ts

@ -104,6 +104,10 @@ export class MapDataLayersComponent implements ControlValueAccessor, OnInit, Val
this.addDataLayerText = 'widgets.maps.data-layer.circle.add-circle';
this.noDataLayersText = 'widgets.maps.data-layer.circle.no-circles';
break;
case 'polylines': // todo translation
this.addDataLayerText = 'widgets.maps.data-layer.circle.add-polylines';
this.noDataLayersText = 'widgets.maps.data-layer.circle.no-polylines';
break;
}
this.dataLayersFormGroup = this.fb.group({
dataLayers: [this.fb.array([]), []]

7
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html

@ -45,6 +45,8 @@
<tb-toggle-option value="markers" [error]="mapSettingsFormGroup.get('markers').invalid ? ('common.required-fields' | translate) : null">{{ 'widgets.maps.overlays.markers' | translate }}</tb-toggle-option>
<tb-toggle-option value="polygons" [error]="mapSettingsFormGroup.get('polygons').invalid ? ('common.required-fields' | translate) : null">{{ 'widgets.maps.overlays.polygons' | translate }}</tb-toggle-option>
<tb-toggle-option value="circles" [error]="mapSettingsFormGroup.get('circles').invalid ? ('common.required-fields' | translate) : null">{{ 'widgets.maps.overlays.circles' | translate }}</tb-toggle-option>
//todo translation
<tb-toggle-option value="polylines" [error]="mapSettingsFormGroup.get('polylines').invalid ? ('common.required-fields' | translate) : null"> Polylines </tb-toggle-option>
</tb-toggle-select>
</div>
<tb-map-data-layers *ngIf="trip"
@ -68,6 +70,11 @@
dataLayerType="circles"
[context]="context"
[mapType]="mapSettingsFormGroup.get('mapType').value"></tb-map-data-layers>
<tb-map-data-layers [class.!hidden]="dataLayerMode !== 'polylines'"
formControlName="polylines"
dataLayerType="polylines"
[context]="context"
[mapType]="mapSettingsFormGroup.get('mapType').value"></tb-map-data-layers>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" tb-hint-tooltip-icon="{{ 'widgets.maps.data-layer.additional-datasources-hint' | translate }}">

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts

@ -145,6 +145,7 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
markers: [null, []],
polygons: [null, []],
circles: [null, []],
polylines: [null, []],
additionalDataSources: [null, []],
controlsPosition: [null, []],
zoomActions: [null, []],
@ -180,7 +181,8 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
});
merge(this.mapSettingsFormGroup.get('markers').valueChanges,
this.mapSettingsFormGroup.get('polygons').valueChanges,
this.mapSettingsFormGroup.get('circles').valueChanges
this.mapSettingsFormGroup.get('circles').valueChanges,
this.mapSettingsFormGroup.get('polylines').valueChanges
).pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {
@ -281,6 +283,10 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
const polygons: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('polygons').value;
dragModeButtonSettingsEnabled = polygons.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move));
}
if (!dragModeButtonSettingsEnabled) {
const polylines: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('polylines').value;
dragModeButtonSettingsEnabled = polylines.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move));
}
if (!dragModeButtonSettingsEnabled) {
const circles: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('circles').value;
dragModeButtonSettingsEnabled = circles.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move));

4
ui-ngx/src/app/shared/models/widget.models.ts

@ -659,7 +659,8 @@ export enum MapItemType {
marker = 'marker',
polygon = 'polygon',
rectangle = 'rectangle',
circle = 'circle'
circle = 'circle',
polyline = 'polyline'
}
export const widgetActionTypes = Object.keys(WidgetActionType)
@ -1145,5 +1146,4 @@ export abstract class WidgetSettingsComponent extends PageComponent implements
protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) {
}
}

78
ui-ngx/src/app/shared/models/widget/maps/map.models.ts

@ -96,6 +96,9 @@ const mapDataLayerDatasourceDataKeys = (settings: MapDataLayerSettings,
case 'circles':
dataKeys.push((settings as CirclesDataLayerSettings).circleKey);
break;
case 'polylines':
dataKeys.push((settings as PolylinesDataLayerSettings).polylineKey);
break;
}
return dataKeys;
};
@ -196,9 +199,9 @@ export const defaultBaseDataLayerSettings = (mapType: MapType): Partial<MapDataL
}
})
export type MapDataLayerType = 'trips' | 'markers' | 'polygons' | 'circles';
export type MapDataLayerType = 'trips' | 'markers' | 'polygons' | 'circles' | 'polylines';
export const mapDataLayerTypes: MapDataLayerType[] = ['trips', 'markers', 'polygons', 'circles'];
export const mapDataLayerTypes: MapDataLayerType[] = ['trips', 'markers', 'polygons', 'circles', 'polylines'];
export const mapDataLayerValid = (dataLayer: MapDataLayerSettings, type: MapDataLayerType): boolean => {
if (!dataLayer.dsType || ![DatasourceType.function, DatasourceType.device, DatasourceType.entity].includes(dataLayer.dsType)) {
@ -232,6 +235,12 @@ export const mapDataLayerValid = (dataLayer: MapDataLayerSettings, type: MapData
return false;
}
break;
case 'polylines':
const polylinesDataLayer = dataLayer as PolylinesDataLayerSettings;
if (!polylinesDataLayer.polylineKey?.type || !polylinesDataLayer.polylineKey?.name) {
return false;
}
break;
case 'circles':
const circlesDataLayer = dataLayer as CirclesDataLayerSettings;
if (!circlesDataLayer.circleKey?.type || !circlesDataLayer.circleKey?.name) {
@ -654,6 +663,58 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial<C
} as Partial<CirclesDataLayerSettings>, defaultBaseDataLayerSettings(mapType),
{label: {show: false}, tooltip: {show: false, pattern: '<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}'}} as Partial<CirclesDataLayerSettings>)
export interface PolylinesDataLayerSettings extends ShapeDataLayerSettings {
polylineKey: DataKey;
}
export const defaultPolylinesDataLayerSettings = (mapType: MapType, functionsOnly = false): PolylinesDataLayerSettings => mergeDeep({
dsType: functionsOnly ? DatasourceType.function : DatasourceType.entity,
dsLabel: functionsOnly ? 'First polyline' : '',
polylineKey: {
name: functionsOnly ? 'f(x)' : 'perimeter',
label: 'perimeter',
type: functionsOnly ? DataKeyType.function : DataKeyType.attribute,
settings: {},
color: materialColors[0].value
}
} as PolylinesDataLayerSettings, defaultBasePolylinesDataLayerSettings(mapType) as PolylinesDataLayerSettings);
export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial<PolylinesDataLayerSettings> => mergeDeep({
fillType: ShapeFillType.color,
// fillColor: {
// type: DataLayerColorType.constant,
// color: 'rgba(51,136,255,0.2)',
// },
// fillImage: {
// type: ShapeFillImageType.image,
// image: '/assets/widget-preview-empty.svg',
// preserveAspectRatio: true,
// opacity: 1,
// angle: 0,
// scale: 1
// },
// fillStripe: {
// weight: 3,
// color: {
// type: DataLayerColorType.constant,
// color: '#8f8f8f'
// },
// spaceWeight: 9,
// spaceColor: {
// type: DataLayerColorType.constant,
// color: 'rgba(143,143,143,0)',
// },
// angle: 45
// },
strokeColor: {
type: DataLayerColorType.constant,
color: '#3388ff',
},
strokeWeight: 3
} as Partial<PolylinesDataLayerSettings>, defaultBaseDataLayerSettings(mapType),
{label: {show: false}, tooltip: {show: false, pattern: '<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}'}} as Partial<PolylinesDataLayerSettings>)
export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: MapDataLayerType, functionsOnly = false): MapDataLayerSettings => {
switch (dataLayerType) {
case 'trips':
@ -664,6 +725,8 @@ export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: Map
return defaultPolygonsDataLayerSettings(mapType, functionsOnly);
case 'circles':
return defaultCirclesDataLayerSettings(mapType, functionsOnly);
case 'polylines':
return defaultPolylinesDataLayerSettings(mapType, functionsOnly);
}
};
@ -677,6 +740,8 @@ export const defaultBaseMapDataLayerSettings = <T extends MapDataLayerSettings>(
return defaultBasePolygonsDataLayerSettings(mapType) as T;
case 'circles':
return defaultBaseCirclesDataLayerSettings(mapType) as T;
case 'polylines':
return defaultBasePolylinesDataLayerSettings(mapType) as T;
}
}
@ -815,6 +880,7 @@ export interface BaseMapSettings {
markers: MarkersDataLayerSettings[];
polygons: PolygonsDataLayerSettings[];
circles: CirclesDataLayerSettings[];
polylines: PolylinesDataLayerSettings[];
additionalDataSources: AdditionalMapDataSourceSettings[];
controlsPosition: MapControlsPosition;
zoomActions: MapZoomAction[];
@ -839,6 +905,7 @@ export const defaultBaseMapSettings: BaseMapSettings = {
markers: [],
polygons: [],
circles: [],
polylines: [],
additionalDataSources: [],
controlsPosition: MapControlsPosition.topleft,
zoomActions: [MapZoomAction.scroll, MapZoomAction.doubleClick, MapZoomAction.controlButtons],
@ -1245,6 +1312,13 @@ export type TbPolyData = L.LatLngTuple[] | L.LatLngTuple[][] | L.LatLngTuple[][]
export type TbPolygonCoordinate = L.LatLng | L.LatLng[] | L.LatLng[][];
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 TbPolylineCoordinate = L.LatLng | L.LatLng[] | L.LatLng[][];
export type TbPolylineCoordinates = TbPolylineCoordinate[];
export interface TbCircleData {
latitude: number;
longitude: number;

Loading…
Cancel
Save