diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
index f4b264cabc..982c29105d 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
@@ -14,18 +14,18 @@
/// limitations under the License.
///
-import L, { LatLngBounds, LatLngTuple, markerClusterGroup, MarkerClusterGroupOptions } from 'leaflet';
+import L, { LatLngBounds, LatLngTuple, markerClusterGroup, MarkerClusterGroupOptions, FeatureGroup, LayerGroup } from 'leaflet';
import 'leaflet-providers';
import 'leaflet.markercluster/dist/leaflet.markercluster';
import {
- FormattedData,
- MapSettings,
- MarkerSettings,
- PolygonSettings,
- PolylineSettings,
- UnitedMapSettings
+ FormattedData,
+ MapSettings,
+ MarkerSettings,
+ PolygonSettings,
+ PolylineSettings,
+ UnitedMapSettings
} from './map-models';
import { Marker } from './markers';
import { BehaviorSubject, Observable } from 'rxjs';
@@ -33,7 +33,7 @@ import { filter } from 'rxjs/operators';
import { Polyline } from './polyline';
import { Polygon } from './polygon';
import { DatasourceData } from '@app/shared/models/widget.models';
-import { safeExecute } from '@home/components/widget/lib/maps/maps-utils';
+import { safeExecute, createTooltip } from '@home/components/widget/lib/maps/maps-utils';
export default abstract class LeafletMap {
@@ -47,6 +47,8 @@ export default abstract class LeafletMap {
bounds: L.LatLngBounds;
datasources: FormattedData[];
markersCluster;
+ points: FeatureGroup;
+ markersData = [];
protected constructor(public $container: HTMLElement, options: UnitedMapSettings) {
this.options = options;
@@ -157,9 +159,9 @@ export default abstract class LeafletMap {
this.map = map;
if (this.options.useDefaultCenterPosition) {
this.map.panTo(this.options.defaultCenterPosition);
- this.bounds = map.getBounds();
+ this.bounds = map.getBounds();
}
- else this.bounds = new L.LatLngBounds(null, null);
+ else this.bounds = new L.LatLngBounds(null, null);
if (this.options.draggableMarker) {
this.addMarkerControl();
}
@@ -200,9 +202,9 @@ export default abstract class LeafletMap {
return this.map.getCenter();
}
- fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) {
+ fitBounds(bounds: LatLngBounds, padding?: LatLngTuple) {
if (bounds.isValid()) {
- this.bounds = this.bounds.extend(bounds);
+ this.bounds = !!this.bounds ? this.bounds.extend(bounds) : bounds;
if (!this.options.fitMapBounds && this.options.defaultZoomLevel) {
this.map.setZoom(this.options.defaultZoomLevel, { animate: false });
if (this.options.useDefaultCenterPosition) {
@@ -218,9 +220,9 @@ export default abstract class LeafletMap {
}
});
if (this.options.useDefaultCenterPosition) {
- bounds = bounds.extend(this.options.defaultCenterPosition);
+ this.bounds = this.bounds.extend(this.options.defaultCenterPosition);
}
- this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false });
+ this.map.fitBounds(this.bounds, { padding: padding || [50, 50], animate: false });
}
}
}
@@ -252,11 +254,10 @@ export default abstract class LeafletMap {
const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
this.options.icon = L.divIcon({
html: `
`
- })
+ style="transform: translate(-10px, -10px)
+ rotate(${data.rotationAngle}deg);
+ ${style}">
`
+ });
}
else {
this.options.icon = null;
@@ -268,6 +269,7 @@ export default abstract class LeafletMap {
this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings);
}
});
+ this.markersData = markersData;
}
dragMarker = (e, data?) => {
@@ -278,7 +280,8 @@ export default abstract class LeafletMap {
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
this.ready$.subscribe(() => {
const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
- this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), settings.draggableMarker && this.markers.size < 2);
+ if (this.bounds)
+ this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
this.markers.set(key, newMarker);
if (this.options.useClusterMarkers) {
this.markersCluster.addLayer(newMarker.leafletMarker);
@@ -313,6 +316,29 @@ export default abstract class LeafletMap {
}
}
+ updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) {
+ this.map$.subscribe(map => {
+ if (this.points) {
+ map.removeLayer(this.points);
+ }
+ this.points = new FeatureGroup();
+ pointsData.filter(pdata => !!this.convertPosition(pdata)).forEach(data => {
+ const point = L.circleMarker(this.convertPosition(data), {
+ color: this.options.pointColor,
+ radius: this.options.pointSize
+ });
+ if (!this.options.pointTooltipOnRightPanel) {
+ point.on('click', () => getTooltip(data));
+ }
+ else {
+ createTooltip(point, this.options, pointsData, getTooltip(data, false));
+ }
+ this.points.addLayer(point);
+ });
+ map.addLayer(this.points);
+ });
+ }
+
setImageAlias(alias: Observable
) {
}
@@ -337,15 +363,17 @@ export default abstract class LeafletMap {
this.ready$.subscribe(() => {
const poly = new Polyline(this.map,
data.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
- const bounds = this.bounds.extend(poly.leafletPoly.getBounds());
- this.fitBounds(bounds)
- this.polylines.set(data[0].entityName, poly)
+ const bounds = poly.leafletPoly.getBounds();
+ this.fitBounds(bounds);
+ this.polylines.set(data[0].entityName, poly);
});
}
updatePolyline(key: string, data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) {
this.ready$.subscribe(() => {
- this.polylines.get(key).updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources);
+ const poly = this.polylines.get(key);
+ poly.updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources);
+ const bounds = poly.leafletPoly.getBounds();
});
}
@@ -370,7 +398,7 @@ export default abstract class LeafletMap {
createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) {
this.ready$.subscribe(() => {
const polygon = new Polygon(this.map, polyData, dataSources, settings);
- const bounds = this.bounds.extend(polygon.leafletPoly.getBounds());
+ const bounds = polygon.leafletPoly.getBounds();
this.fitBounds(bounds);
this.polygons.set(polyData.datasource.entityName, polygon);
});
@@ -380,7 +408,6 @@ export default abstract class LeafletMap {
this.ready$.subscribe(() => {
const poly = this.polygons.get(polyData.datasource.entityName);
poly.updatePolygon(polyData.data, dataSources, settings);
- this.fitBounds(poly.leafletPoly.getBounds());
});
}
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
index 16e1c809ce..28e9686bc1 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
@@ -15,13 +15,19 @@
///
import { LatLngTuple } from 'leaflet';
-import { Datasource } from '@app/shared/models/widget.models';
+import { Datasource, JsonSettingsSchema } from '@app/shared/models/widget.models';
+import { Type } from '@angular/core';
+import LeafletMap from './leaflet-map';
+import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
+import {
+ openstreetMapSettingsSchema, tencentMapSettingsSchema,
+ googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema
+} from './schemes';
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
export type MapSettings = {
- polygonKeyName: any;
draggableMarker: boolean;
initCallback?: () => any;
posFunction: (origXPos, origYPos) => { x, y };
@@ -108,7 +114,8 @@ export interface FormattedData {
export type PolygonSettings = {
showPolygon: boolean;
- showTooltip: any;
+ polygonKeyName: string;
+ polKeyName: string;// deprecated
polygonStrokeOpacity: number;
polygonOpacity: number;
polygonStrokeWeight: number;
@@ -116,12 +123,13 @@ export type PolygonSettings = {
polygonColor: string;
showPolygonTooltip: boolean;
autocloseTooltip: boolean;
- tooltipFunction: GenericFunction;
showTooltipAction: string;
tooltipAction: { [name: string]: actionsHandler };
- tooltipPattern: string;
- useTooltipFunction: boolean;
+ polygonTooltipPattern: string;
+ usePolygonTooltipFunction: boolean;
polygonClick: { [name: string]: actionsHandler };
+ usePolygonColorFunction: boolean;
+ polygonTooltipFunction: GenericFunction;
polygonColorFunction?: GenericFunction;
}
@@ -154,6 +162,88 @@ export interface HistorySelectSettings {
buttonColor: string;
}
+export type TripAnimationSttings = {
+ pointColor: string;
+ pointSize: number;
+ pointTooltipOnRightPanel: boolean;
+}
+
export type actionsHandler = ($event: Event, datasource: Datasource) => void;
-export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;
+export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings & TripAnimationSttings;
+
+interface IProvider {
+ MapClass: Type,
+ schema: JsonSettingsSchema,
+ name: string
+}
+
+export const providerSets: { [key: string]: IProvider } = {
+ 'openstreet-map': {
+ MapClass: OpenStreetMap,
+ schema: openstreetMapSettingsSchema,
+ name: 'openstreet-map',
+ },
+ 'tencent-map': {
+ MapClass: TencentMap,
+ schema: tencentMapSettingsSchema,
+ name: 'tencent-map'
+ },
+ 'google-map': {
+ MapClass: GoogleMap,
+ schema: googleMapSettingsSchema,
+ name: 'google-map'
+ },
+ here: {
+ MapClass: HEREMap,
+ schema: hereMapSettingsSchema,
+ name: 'here'
+ },
+ 'image-map': {
+ MapClass: ImageMap,
+ schema: imageMapSettingsSchema,
+ name: 'image-map'
+ }
+};
+
+export const defaultSettings: any = {
+ xPosKeyName: 'xPos',
+ yPosKeyName: 'yPos',
+ markerOffsetX: 0.5,
+ markerOffsetY: 1,
+ latKeyName: 'latitude',
+ lngKeyName: 'longitude',
+ polygonKeyName: 'coordinates',
+ showLabel: false,
+ label: '${entityName}',
+ showTooltip: false,
+ useDefaultCenterPosition: false,
+ showTooltipAction: 'click',
+ autocloseTooltip: false,
+ showPolygon: false,
+ labelColor: '#000000',
+ color: '#FE7569',
+ polygonColor: '#0000ff',
+ polygonStrokeColor: '#fe0001',
+ polygonOpacity: 0.5,
+ polygonStrokeOpacity: 1,
+ polygonStrokeWeight: 1,
+ useLabelFunction: false,
+ markerImages: [],
+ strokeWeight: 2,
+ strokeOpacity: 1.0,
+ initCallback: () => { },
+ defaultZoomLevel: 8,
+ disableScrollZooming: false,
+ minZoomLevel: 16,
+ credentials: '',
+ markerClusteringSetting: null,
+ draggableMarker: false,
+ fitMapBounds: true
+};
+
+export const hereProviders = [
+ 'HERE.normalDay',
+ 'HERE.normalNight',
+ 'HERE.hybridDay',
+ 'HERE.terrainDay']
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.interface.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.interface.ts
index 9900d98e0b..be618128dd 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.interface.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.interface.ts
@@ -26,7 +26,7 @@ export interface MapWidgetInterface {
export interface MapWidgetStaticInterface {
settingsSchema(mapProvider?: MapProviders, drawRoutes?: boolean): JsonSettingsSchema;
- getProvidersSchema(mapProvider?: MapProviders): JsonSettingsSchema
+ getProvidersSchema(mapProvider?: MapProviders, ignoreImageMap?: boolean): JsonSettingsSchema
dataKeySettingsSchema(): object;
actionSources(): object;
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
index 5c14ac80c3..fb3e6662f3 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
@@ -14,23 +14,17 @@
/// limitations under the License.
///
-import { MapProviders, UnitedMapSettings } from './map-models';
+import { MapProviders, UnitedMapSettings, providerSets, hereProviders, defaultSettings } from './map-models';
import LeafletMap from './leaflet-map';
import {
- openstreetMapSettingsSchema,
- googleMapSettingsSchema,
- imageMapSettingsSchema,
- tencentMapSettingsSchema,
commonMapSettingsSchema,
routeMapSettingsSchema,
markerClusteringSettingsSchema,
markerClusteringSettingsSchemaLeaflet,
- hereMapSettingsSchema,
mapProviderSchema,
mapPolygonSchema
} from './schemes';
import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
-import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils';
import { of, Subject } from 'rxjs';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
@@ -39,7 +33,6 @@ import { JsonSettingsSchema, WidgetActionDescriptor, DatasourceType, widgetType,
import { EntityId } from '@shared/models/id/entity-id';
import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
import { AttributeService } from '@core/http/attribute.service';
-import { Type } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@core/services/utils.service';
@@ -85,11 +78,19 @@ export class MapWidgetController implements MapWidgetInterface {
return {};
}
- public static getProvidersSchema(mapProvider: MapProviders) {
- mapProviderSchema.schema.properties.provider.default = mapProvider;
- return mergeSchemes([mapProviderSchema,
+ public static getProvidersSchema(mapProvider: MapProviders, ignoreImageMap = false) {
+ if (mapProvider)
+ mapProviderSchema.schema.properties.provider.default = mapProvider;
+ const providerSchema = mapProviderSchema;
+ if (ignoreImageMap) {
+ providerSchema.form[0].items = providerSchema.form[0]?.items.filter(item => item.value !== 'image-map');
+ }
+ return mergeSchemes([providerSchema,
...Object.keys(providerSets)?.map(
- (key: string) => { const setting = providerSets[key]; return addCondition(setting?.schema, `model.provider === '${setting.name}'`) })]);
+ (key: string) => {
+ const setting = providerSets[key];
+ return addCondition(setting?.schema, `model.provider === '${setting.name}'`);
+ })]);
}
public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema {
@@ -218,6 +219,7 @@ export class MapWidgetController implements MapWidgetInterface {
polygonColorFunction: parseFunction(settings.polygonColorFunction, functionParams),
markerImageFunction: parseFunction(settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex']),
labelColor: this.ctx.widgetConfig.color,
+ polygonKeyName: settings.polKeyName ? settings.polKeyName : settings.polygonKeyName,
tooltipPattern: settings.tooltipPattern ||
'${entityName}
Latitude: ${' +
settings.latKeyName + ':7}
Longitude: ${' + settings.lngKeyName + ':7}',
@@ -295,78 +297,4 @@ export class MapWidgetController implements MapWidgetInterface {
export let TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController;
-interface IProvider {
- MapClass: Type,
- schema: JsonSettingsSchema,
- name: string
-}
-
-export const providerSets: { [key: string]: IProvider } = {
- 'openstreet-map': {
- MapClass: OpenStreetMap,
- schema: openstreetMapSettingsSchema,
- name: 'openstreet-map',
- },
- 'tencent-map': {
- MapClass: TencentMap,
- schema: tencentMapSettingsSchema,
- name: 'tencent-map'
- },
- 'google-map': {
- MapClass: GoogleMap,
- schema: googleMapSettingsSchema,
- name: 'google-map'
- },
- here: {
- MapClass: HEREMap,
- schema: hereMapSettingsSchema,
- name: 'here'
- },
- 'image-map': {
- MapClass: ImageMap,
- schema: imageMapSettingsSchema,
- name: 'image-map'
- }
-};
-
-export const defaultSettings: any = {
- xPosKeyName: 'xPos',
- yPosKeyName: 'yPos',
- markerOffsetX: 0.5,
- markerOffsetY: 1,
- latKeyName: 'latitude',
- lngKeyName: 'longitude',
- polygonKeyName: 'coordinates',
- showLabel: false,
- label: '${entityName}',
- showTooltip: false,
- useDefaultCenterPosition: false,
- showTooltipAction: 'click',
- autocloseTooltip: false,
- showPolygon: false,
- labelColor: '#000000',
- color: '#FE7569',
- polygonColor: '#0000ff',
- polygonStrokeColor: '#fe0001',
- polygonOpacity: 0.5,
- polygonStrokeOpacity: 1,
- polygonStrokeWeight: 1,
- useLabelFunction: false,
- markerImages: [],
- strokeWeight: 2,
- strokeOpacity: 1.0,
- initCallback: () => { },
- defaultZoomLevel: 8,
- disableScrollZooming: false,
- minZoomLevel: 16,
- credentials: '',
- markerClusteringSetting: null,
- draggableMarker: false,
- fitMapBounds: true
-};
-export const hereProviders = [
- 'HERE.normalDay',
- 'HERE.normalNight',
- 'HERE.hybridDay',
- 'HERE.terrainDay']
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
index 2e3927fdc4..7c8f50e13f 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
@@ -120,7 +120,6 @@ export class Marker {
[this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;
const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction,
[this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex();
-
if (currentImage && currentImage.url) {
aspectCache(currentImage.url).subscribe(
(aspect) => {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts
index 4f1fc9dd72..6954d122c2 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts
@@ -53,8 +53,9 @@ export class Polygon {
}
updateTooltip(data: DatasourceData) {
- const pattern = this.settings.useTooltipFunction ?
- safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
+ const pattern = this.settings.usePolygonTooltipFunction ?
+ safeExecute(this.settings.polygonTooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) :
+ this.settings.polygonTooltipPattern;
this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
}
@@ -71,10 +72,12 @@ export class Polygon {
this.map.removeLayer(this.leafletPoly);
}
- updatePolygonColor(settings) {
+ updatePolygonColor(settings: PolygonSettings) {
+ const color = settings.usePolygonColorFunction ?
+ safeExecute(settings.polygonColorFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.polygonColor;
const style: L.PathOptions = {
fill: true,
- fillColor: settings.polygonColor,
+ fillColor: color,
color: settings.polygonStrokeColor,
weight: settings.polygonStrokeWeight,
fillOpacity: settings.polygonOpacity,
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
index fce2fb3644..ac46c2772b 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
@@ -109,13 +109,13 @@ export class ImageMap extends LeafletMap {
lastCenterPos.y /= prevHeight;
this.updateBounds(updateImage, lastCenterPos);
this.map.invalidateSize(true);
- // TODO: need add update marker position
+ this.updateMarkers(this.markersData);
}
}
}
}
- fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { }
+ fitBounds(bounds: LatLngBounds, padding?: LatLngTuple) { }
initMap(updateImage?) {
if (!this.map && this.aspect > 0) {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts
index de155bf2b4..4f8808e3e4 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts
@@ -546,6 +546,20 @@ export const mapPolygonSchema =
type: 'boolean',
default: false
},
+ polygonTooltipPattern: {
+ title: 'Tooltip (for ex. \'Text ${keyName} units.\' or Link text\')',
+ type: 'string',
+ default: '${entityName}
TimeStamp: ${ts:7}'
+ },
+ usePolygonTooltipFunction: {
+ title: 'Use polygon tooltip function',
+ type: 'boolean',
+ default: false
+ },
+ polygonTooltipFunction: {
+ title: 'Polygon tooltip function: f(data, dsData, dsIndex)',
+ type: 'string'
+ },
usePolygonColorFunction: {
title: 'Use polygon color function',
type: 'boolean',
@@ -570,7 +584,15 @@ export const mapPolygonSchema =
key: 'polygonStrokeColor',
type: 'color'
},
- 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', 'showPolygonTooltip',
+ 'polygonStrokeOpacity', 'polygonStrokeWeight', 'showPolygonTooltip',
+ {
+ key: 'polygonTooltipPattern',
+ type: 'textarea'
+ }, 'usePolygonTooltipFunction', {
+ key: 'polygonTooltipFunction',
+ type: 'javascript'
+ },
+ 'usePolygonColorFunction',
{
key: 'polygonColorFunction',
type: 'javascript'
@@ -710,6 +732,161 @@ export const imageMapSettingsSchema =
]
};
+export const pathSchema =
+{
+ schema: {
+ title: 'Trip Animation Path Configuration',
+ type: 'object',
+ properties: {
+ color: {
+ title: 'Path color',
+ type: 'string'
+ },
+ strokeWeight: {
+ title: 'Stroke weight',
+ type: 'number',
+ default: 2
+ },
+ strokeOpacity: {
+ title: 'Stroke opacity',
+ type: 'number',
+ default: 1
+ },
+ useColorFunction: {
+ title: 'Use path color function',
+ type: 'boolean',
+ default: false
+ },
+ colorFunction: {
+ title: 'Path color function: f(data, dsData, dsIndex)',
+ type: 'string'
+ },
+ usePolylineDecorator: {
+ title: 'Use path decorator',
+ type: 'boolean',
+ default: false
+ },
+ decoratorSymbol: {
+ title: 'Decorator symbol',
+ type: 'string',
+ default: 'arrowHead'
+ },
+ decoratorSymbolSize: {
+ title: 'Decorator symbol size (px)',
+ type: 'number',
+ default: 10
+ },
+ useDecoratorCustomColor: {
+ title: 'Use path decorator custom color',
+ type: 'boolean',
+ default: false
+ },
+ decoratorCustomColor: {
+ title: 'Decorator custom color',
+ type: 'string',
+ default: '#000'
+ },
+ decoratorOffset: {
+ title: 'Decorator offset',
+ type: 'string',
+ default: '20px'
+ },
+ endDecoratorOffset: {
+ title: 'End decorator offset',
+ type: 'string',
+ default: '20px'
+ },
+ decoratorRepeat: {
+ title: 'Decorator repeat',
+ type: 'string',
+ default: '20px'
+ }
+ },
+ required: []
+ },
+ form: [
+ {
+ key: 'color',
+ type: 'color'
+ }, 'useColorFunction', {
+ key: 'colorFunction',
+ type: 'javascript'
+ }, 'strokeWeight', 'strokeOpacity',
+ 'usePolylineDecorator', {
+ key: 'decoratorSymbol',
+ type: 'rc-select',
+ multiple: false,
+ items: [{
+ value: 'arrowHead',
+ label: 'Arrow'
+ }, {
+ value: 'dash',
+ label: 'Dash'
+ }]
+ }, 'decoratorSymbolSize', 'useDecoratorCustomColor', {
+ key: 'decoratorCustomColor',
+ type: 'color'
+ }, {
+ key: 'decoratorOffset',
+ type: 'textarea'
+ }, {
+ key: 'endDecoratorOffset',
+ type: 'textarea'
+ }, {
+ key: 'decoratorRepeat',
+ type: 'textarea'
+ }
+ ]
+};
+
+export const pointSchema =
+{
+ schema: {
+ title: 'Trip Animation Path Configuration',
+ type: 'object',
+ properties: {
+ showPoints: {
+ title: 'Show points',
+ type: 'boolean',
+ default: false
+ },
+ pointColor: {
+ title: 'Point color',
+ type: 'string'
+ },
+ pointSize: {
+ title: 'Point size (px)',
+ type: 'number',
+ default: 10
+ },
+ usePointAsAnchor: {
+ title: 'Use point as anchor',
+ type: 'boolean',
+ default: false
+ },
+ pointAsAnchorFunction: {
+ title: 'Point as anchor function: f(data, dsData, dsIndex)',
+ type: 'string'
+ },
+ pointTooltipOnRightPanel: {
+ title: 'Independant point tooltip',
+ type: 'boolean',
+ default: true
+ },
+ },
+ required: []
+ },
+ form: [
+ 'showPoints', {
+ key: 'pointColor',
+ type: 'color'
+ }, 'pointSize', 'usePointAsAnchor', {
+ key: 'pointAsAnchorFunction',
+ type: 'javascript'
+ }, 'pointTooltipOnRightPanel',
+ ]
+};
+
export const mapProviderSchema =
{
schema: {
@@ -755,7 +932,6 @@ export const mapProviderSchema =
]
};
-
export const tripAnimationSchema = {
schema: {
title: 'Openstreet Map Configuration',
@@ -776,11 +952,6 @@ export const tripAnimationSchema = {
type: 'string',
default: 'longitude'
},
- polKeyName: {
- title: 'Polygon key name',
- type: 'string',
- default: 'coordinates'
- },
showLabel: {
title: 'Show label',
type: 'boolean',
@@ -834,148 +1005,6 @@ export const tripAnimationSchema = {
title: 'Tooltip function: f(data, dsData, dsIndex)',
type: 'string'
},
- color: {
- title: 'Path color',
- type: 'string'
- },
- strokeWeight: {
- title: 'Stroke weight',
- type: 'number',
- default: 2
- },
- strokeOpacity: {
- title: 'Stroke opacity',
- type: 'number',
- default: 1
- },
- useColorFunction: {
- title: 'Use path color function',
- type: 'boolean',
- default: false
- },
- colorFunction: {
- title: 'Path color function: f(data, dsData, dsIndex)',
- type: 'string'
- },
- usePolylineDecorator: {
- title: 'Use path decorator',
- type: 'boolean',
- default: false
- },
- decoratorSymbol: {
- title: 'Decorator symbol',
- type: 'string',
- default: 'arrowHead'
- },
- decoratorSymbolSize: {
- title: 'Decorator symbol size (px)',
- type: 'number',
- default: 10
- },
- useDecoratorCustomColor: {
- title: 'Use path decorator custom color',
- type: 'boolean',
- default: false
- },
- decoratorCustomColor: {
- title: 'Decorator custom color',
- type: 'string',
- default: '#000'
- },
- decoratorOffset: {
- title: 'Decorator offset',
- type: 'string',
- default: '20px'
- },
- endDecoratorOffset: {
- title: 'End decorator offset',
- type: 'string',
- default: '20px'
- },
- decoratorRepeat: {
- title: 'Decorator repeat',
- type: 'string',
- default: '20px'
- },
- showPolygon: {
- title: 'Show polygon',
- type: 'boolean',
- default: false
- },
- polygonTooltipPattern: {
- title: 'Tooltip (for ex. \'Text ${keyName} units.\' or Link text\')',
- type: 'string',
- default: '${entityName}
TimeStamp: ${ts:7}'
- },
- usePolygonTooltipFunction: {
- title: 'Use polygon tooltip function',
- type: 'boolean',
- default: false
- },
- polygonTooltipFunction: {
- title: 'Polygon tooltip function: f(data, dsData, dsIndex)',
- type: 'string'
- },
- polygonColor: {
- title: 'Polygon color',
- type: 'string'
- },
- polygonOpacity: {
- title: 'Polygon opacity',
- type: 'number',
- default: 0.5
- },
- polygonStrokeColor: {
- title: 'Polygon border color',
- type: 'string'
- },
- polygonStrokeOpacity: {
- title: 'Polygon border opacity',
- type: 'number',
- default: 1
- },
- polygonStrokeWeight: {
- title: 'Polygon border weight',
- type: 'number',
- default: 1
- },
- usePolygonColorFunction: {
- title: 'Use polygon color function',
- type: 'boolean',
- default: false
- },
- polygonColorFunction: {
- title: 'Polygon Color function: f(data, dsData, dsIndex)',
- type: 'string'
- },
- showPoints: {
- title: 'Show points',
- type: 'boolean',
- default: false
- },
- pointColor: {
- title: 'Point color',
- type: 'string'
- },
- pointSize: {
- title: 'Point size (px)',
- type: 'number',
- default: 10
- },
- usePointAsAnchor: {
- title: 'Use point as anchor',
- type: 'boolean',
- default: false
- },
- pointAsAnchorFunction: {
- title: 'Point as anchor function: f(data, dsData, dsIndex)',
- type: 'string'
- },
- pointTooltipOnRightPanel: {
- title: 'Independant point tooltip',
- type: 'boolean',
- default: true
- },
autocloseTooltip: {
title: 'Auto-close point popup',
type: 'boolean',
@@ -1015,111 +1044,35 @@ export const tripAnimationSchema = {
},
required: []
},
- form: [{
- key: 'mapProvider',
- type: 'rc-select',
- multiple: false,
- items: [{
- value: 'OpenStreetMap.Mapnik',
- label: 'OpenStreetMap.Mapnik (Default)'
- }, {
- value: 'OpenStreetMap.BlackAndWhite',
- label: 'OpenStreetMap.BlackAndWhite'
- }, {
- value: 'OpenStreetMap.HOT',
- label: 'OpenStreetMap.HOT'
- }, {
- value: 'Esri.WorldStreetMap',
- label: 'Esri.WorldStreetMap'
- }, {
- value: 'Esri.WorldTopoMap',
- label: 'Esri.WorldTopoMap'
- }, {
- value: 'CartoDB.Positron',
- label: 'CartoDB.Positron'
- }, {
- value: 'CartoDB.DarkMatter',
- label: 'CartoDB.DarkMatter'
- }]
- }, 'normalizationStep', 'latKeyName', 'lngKeyName', 'polKeyName', 'showLabel', 'label', 'useLabelFunction', {
+ form: ['normalizationStep', 'latKeyName', 'lngKeyName', 'showLabel', 'label', 'useLabelFunction', {
key: 'labelFunction',
type: 'javascript'
}, 'showTooltip', {
- key: 'tooltipColor',
- type: 'color'
- }, {
- key: 'tooltipFontColor',
- type: 'color'
- }, 'tooltipOpacity', {
- key: 'tooltipPattern',
- type: 'textarea'
- }, 'useTooltipFunction', {
- key: 'tooltipFunction',
- type: 'javascript'
- }, {
- key: 'color',
- type: 'color'
- }, 'useColorFunction', {
- key: 'colorFunction',
- type: 'javascript'
- }, 'usePolylineDecorator', {
- key: 'decoratorSymbol',
- type: 'rc-select',
- multiple: false,
- items: [{
- value: 'arrowHead',
- label: 'Arrow'
+ key: 'tooltipColor',
+ type: 'color'
+ }, {
+ key: 'tooltipFontColor',
+ type: 'color'
+ }, 'tooltipOpacity', {
+ key: 'tooltipPattern',
+ type: 'textarea'
+ }, 'useTooltipFunction', {
+ key: 'tooltipFunction',
+ type: 'javascript'
+ }, 'autocloseTooltip', {
+ key: 'markerImage',
+ type: 'image'
+ }, 'markerImageSize', 'rotationAngle', 'useMarkerImageFunction',
+ {
+ key: 'markerImageFunction',
+ type: 'javascript'
}, {
- value: 'dash',
- label: 'Dash'
+ key: 'markerImages',
+ items: [
+ {
+ key: 'markerImages[]',
+ type: 'image'
+ }
+ ]
}]
- }, 'decoratorSymbolSize', 'useDecoratorCustomColor', {
- key: 'decoratorCustomColor',
- type: 'color'
- }, {
- key: 'decoratorOffset',
- type: 'textarea'
- }, {
- key: 'endDecoratorOffset',
- type: 'textarea'
- }, {
- key: 'decoratorRepeat',
- type: 'textarea'
- }, 'strokeWeight', 'strokeOpacity', 'showPolygon', {
- key: 'polygonTooltipPattern',
- type: 'textarea'
- }, 'usePolygonTooltipFunction', {
- key: 'polygonTooltipFunction',
- type: 'javascript'
- }, {
- key: 'polygonColor',
- type: 'color'
- }, 'polygonOpacity', {
- key: 'polygonStrokeColor',
- type: 'color'
- }, 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', {
- key: 'polygonColorFunction',
- type: 'javascript'
- }, 'showPoints', {
- key: 'pointColor',
- type: 'color'
- }, 'pointSize', 'usePointAsAnchor', {
- key: 'pointAsAnchorFunction',
- type: 'javascript'
- }, 'pointTooltipOnRightPanel', 'autocloseTooltip', {
- key: 'markerImage',
- type: 'image'
- }, 'markerImageSize', 'rotationAngle', 'useMarkerImageFunction',
- {
- key: 'markerImageFunction',
- type: 'javascript'
- }, {
- key: 'markerImages',
- items: [
- {
- key: 'markerImages[]',
- type: 'image'
- }
- ]
- }]
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html
index b973284b53..9449e3aba9 100644
--- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html
@@ -32,6 +32,6 @@
[ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
-
\ No newline at end of file
diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
index 47b7e7f743..7dfec08c9f 100644
--- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
@@ -21,9 +21,9 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil';
import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, SecurityContext, ViewChild } from '@angular/core';
import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
-import { MapProviders } from '../lib/maps/map-models';
-import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils';
-import { tripAnimationSchema } from '../lib/maps/schemes';
+import { MapProviders, FormattedData } from '../lib/maps/map-models';
+import { initSchema, addToSchema, addGroupInfo, addCondition } from '@app/core/schema-utils';
+import { tripAnimationSchema, mapPolygonSchema, pathSchema, pointSchema } from '../lib/maps/schemes';
import { DomSanitizer } from '@angular/platform-browser';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { findAngle, getRatio, parseArray, parseWithTranslation, safeExecute } from '../lib/maps/maps-utils';
@@ -58,13 +58,21 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
label;
minTime;
maxTime;
+ anchors = [];
+ useAnchors = false;
static getSettingsSchema(): JsonSettingsSchema {
const schema = initSchema();
- addToSchema(schema, TbMapWidgetV2.getProvidersSchema());
+ addToSchema(schema, TbMapWidgetV2.getProvidersSchema(null, true));
addGroupInfo(schema, 'Map Provider Settings');
addToSchema(schema, tripAnimationSchema);
addGroupInfo(schema, 'Trip Animation Settings');
+ addToSchema(schema, pathSchema);
+ addGroupInfo(schema, 'Path Settings');
+ addToSchema(schema, addCondition(pointSchema, 'model.showPoints === true', ['showPoints']));
+ addGroupInfo(schema, 'Path Points Settings');
+ addToSchema(schema, addCondition(mapPolygonSchema, 'model.showPolygon === true', ['showPolygon']));
+ addGroupInfo(schema, 'Polygon Settings');
return schema;
}
@@ -78,14 +86,15 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
rotationAngle: 0
}
this.settings = { ...settings, ...this.ctx.settings };
+ this.useAnchors = this.settings.usePointAsAnchor && this.settings.showPoints;
+ this.settings.fitMapBounds = true;
+ this.normalizationStep = this.settings.normalizationStep;
const subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
- if (subscription) subscription.callbacks.onDataUpdated = (updated) => {
+ if (subscription) subscription.callbacks.onDataUpdated = () => {
this.historicalData = parseArray(this.ctx.data);
this.activeTrip = this.historicalData[0][0];
this.calculateIntervals();
this.timeUpdated(this.intervals[0]);
- this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds)));
-
this.mapWidget.map.map?.invalidateSize();
this.cd.detectChanges();
}
@@ -104,9 +113,17 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
this.calcLabel();
this.calcTooltip();
if (this.mapWidget) {
+ this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds)));
if (this.settings.showPolygon) {
this.mapWidget.map.updatePolygons(this.interpolatedData);
}
+ if (this.settings.showPoints) {
+ this.mapWidget.map.updatePoints(this.historicalData[0], this.calcTooltip);
+ this.anchors = this.historicalData[0]
+ .filter(data =>
+ this.settings.usePointAsAnchor ||
+ safeExecute(this.settings.pointAsAnchorFunction, [this.historicalData, data, data.dsIndex])).map(data => data.time);
+ }
this.mapWidget.map.updateMarkers(currentPosition);
}
}
@@ -117,23 +134,29 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
calculateIntervals() {
this.historicalData.forEach((dataSource, index) => {
this.intervals = [];
-
for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) {
this.intervals.push(time);
}
-
this.intervals.push(dataSource[dataSource.length - 1]?.time);
this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals);
});
}
- calcTooltip() {
- const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
- const tooltipText: string = this.settings.useTooltipFunction ?
+ calcTooltip = (point?: FormattedData, setTooltip = true) => {
+ if (!point) {
+ point = this.activeTrip;
+ }
+ const data = { ...point, maxTime: this.maxTime, minTime: this.minTime }
+ const tooltipPattern: string = this.settings.useTooltipFunction ?
safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern;
- this.mainTooltip = this.sanitizer.sanitize(
- SecurityContext.HTML, (parseWithTranslation.parseTemplate(tooltipText, data, true)));
+ const tooltipText = parseWithTranslation.parseTemplate(tooltipPattern, data, true);
+ if (setTooltip) {
+ this.mainTooltip = this.sanitizer.sanitize(
+ SecurityContext.HTML, tooltipText);
+ this.cd.detectChanges();
+ }
+ return tooltipText;
}
calcLabel() {
diff --git a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts
index 252720033c..a15b892349 100644
--- a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts
+++ b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.ts
@@ -28,6 +28,8 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
@Input() settings: HistorySelectSettings
@Input() intervals = [];
+ @Input() anchors = [];
+ @Input() useAnchors = false;
@Output() timeUpdated: EventEmitter = new EventEmitter();
@@ -56,7 +58,7 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
this.interval = interval(1000 / this.speed)
.pipe(
filter(() => this.playing)).subscribe(() => {
- this.index++;
+ this.index++;
if (this.index < this.maxTimeIndex) {
this.cd.detectChanges();
this.timeUpdated.emit(this.intervals[this.index]);
@@ -91,14 +93,24 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
moveNext() {
if (this.index < this.maxTimeIndex) {
- this.index++;
+ if (this.useAnchors) {
+ const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors)+1;
+ this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
+ }
+ else
+ this.index++;
}
this.pause();
}
movePrev() {
if (this.index > this.minTimeIndex) {
- this.index++;
+ if (this.useAnchors) {
+ const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors) - 1;
+ this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
+ }
+ else
+ this.index--;
}
this.pause();
}
@@ -113,6 +125,14 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
this.pause();
}
+ findIndex(value, array: any[]) {
+ let i = 0;
+ while (array[i] < value) {
+ i++;
+ };
+ return i;
+ }
+
changeIndex() {
this.timeUpdated.emit(this.intervals[this.index]);
}