map fixes
This commit is contained in:
parent
8460358f06
commit
8979a511a3
@ -39,7 +39,9 @@
|
||||
"node_modules/rc-select/assets/index.less",
|
||||
"node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css",
|
||||
"node_modules/leaflet/dist/leaflet.css",
|
||||
"src/app/modules/home/components/widget/lib/maps/markers.scss"
|
||||
"src/app/modules/home/components/widget/lib/maps/markers.scss",
|
||||
"node_modules/leaflet.markercluster/dist/MarkerCluster.css",
|
||||
"node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
|
||||
14
ui-ngx/package-lock.json
generated
14
ui-ngx/package-lock.json
generated
@ -1811,8 +1811,7 @@
|
||||
"@types/geojson": {
|
||||
"version": "7946.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
|
||||
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ=="
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.1",
|
||||
@ -1876,7 +1875,6 @@
|
||||
"version": "1.5.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.12.tgz",
|
||||
"integrity": "sha512-61HRMIng+bWvnnAIqUWLBlrd/TQZc4gU+gN1JL4K47EDtwIrcMEhWgi7PdcpbG1YmpH4F0EfOimkvV82gJIl9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
@ -1890,6 +1888,14 @@
|
||||
"@types/leaflet": "*"
|
||||
}
|
||||
},
|
||||
"@types/leaflet.markercluster": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.4.2.tgz",
|
||||
"integrity": "sha512-QQ//hevAxMH2dlRQdRre7V/1G+TbtuDtZnZF/75TNwVIgklrsQVCIcS/cvLsl7UUryfPJ6xmoYHfFzK5iGVgpg==",
|
||||
"requires": {
|
||||
"@types/leaflet": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.150",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.150.tgz",
|
||||
@ -8605,7 +8611,7 @@
|
||||
"integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw=="
|
||||
},
|
||||
"ngx-flowchart": {
|
||||
"version": "git://github.com/thingsboard/ngx-flowchart.git#a4157b0eef2eb3646ef920447c7b06b39d54f87f",
|
||||
"version": "git://github.com/thingsboard/ngx-flowchart.git#97a77477ca8579becf0e3a07866046b4536fe30a",
|
||||
"from": "git://github.com/thingsboard/ngx-flowchart.git#master",
|
||||
"requires": {
|
||||
"tslib": "^1.10.0"
|
||||
|
||||
@ -108,6 +108,7 @@
|
||||
"@types/jstree": "^3.3.39",
|
||||
"@types/jszip": "^3.1.7",
|
||||
"@types/leaflet": "^1.5.12",
|
||||
"@types/leaflet.markercluster": "^1.4.2",
|
||||
"@types/leaflet-polylinedecorator": "^1.6.0",
|
||||
"@types/lodash": "^4.14.150",
|
||||
"@types/raphael": "^2.3.0",
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
///
|
||||
|
||||
import _ from 'lodash';
|
||||
import { Observable, Subject, from, fromEvent, of } from 'rxjs';
|
||||
import { Observable, Subject, fromEvent, of } from 'rxjs';
|
||||
import { finalize, share, map } from 'rxjs/operators';
|
||||
import base64js from 'base64-js';
|
||||
|
||||
@ -430,7 +430,7 @@ export function getDescendantProp(obj: any, path: string): any {
|
||||
|
||||
export function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
|
||||
const image = new Image();
|
||||
const imageLoad$ = fromEvent(image, 'load').pipe(map(event => image));
|
||||
const imageLoad$ = fromEvent(image, 'load').pipe(map(() => image));
|
||||
image.src = imageUrl;
|
||||
return imageLoad$;
|
||||
}
|
||||
@ -524,37 +524,49 @@ export function parseFunction(source: any, params: string[] = []): Function {
|
||||
return res;
|
||||
}
|
||||
|
||||
export function parseTemplate(template: string, data: object) {
|
||||
export function parseTemplate(template: string, data: object, translateFn?: (key: string) => string) {
|
||||
let res = '';
|
||||
let variables = '';
|
||||
try {
|
||||
template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>').replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
|
||||
let variables = '';
|
||||
const expressions = template
|
||||
.match(/\{(.*?)\}/g) // find expressions
|
||||
.map(exp => exp.replace(/{|}/g, '')) // remove brackets
|
||||
.map(exp => exp.split(':'))
|
||||
.map(arr => {
|
||||
variables += `let ${arr[0]} = ''; `;
|
||||
return arr;
|
||||
})
|
||||
.filter(arr => !!arr[1]) // filter expressions without format
|
||||
.reduce((res, current) => {
|
||||
res[current[0]] = current[1];
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
for (const key in data) {
|
||||
if (!key.includes('|'))
|
||||
variables += `${key} = '${expressions[key] ? padValue(data[key], +expressions[key]) : data[key]}';`;
|
||||
if (template.match(/<link-act/g)) {
|
||||
template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>').replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
|
||||
}
|
||||
template = template.replace(/:\d+}/g, '}');
|
||||
res = safeExecute(parseFunction(variables + 'return' + '`' + template + '`'));
|
||||
if (template.includes('i18n')) {
|
||||
const translateRegexp = /\{i18n:(.*?)\}/;
|
||||
template.match(new RegExp(translateRegexp.source, translateRegexp.flags + 'g')).forEach(match => {
|
||||
template = template.replace(match, translateFn(match.match(translateRegexp)[1]));
|
||||
});
|
||||
}
|
||||
const expressions = template.match(/\{(.*?)\}/g);
|
||||
if (expressions) {
|
||||
const clearMatches = template.match(/(?<=\{)(.+?)(?=(\}|\:))/g);
|
||||
for (const key in data) {
|
||||
if (!key.includes('|'))
|
||||
variables += `let ${key} = '${clearMatches[key] ? padValue(data[key], +clearMatches[key]) : data[key]}';`;
|
||||
}
|
||||
template = template.replace(/\:\d+\}/g, '}');
|
||||
res = safeExecute(parseFunction(variables + ' return' + '`' + template + '`'));
|
||||
}
|
||||
else res = template;
|
||||
}
|
||||
catch (ex) {
|
||||
console.log(ex, variables, template)
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export let parseWithTranslation = {
|
||||
translate(): string {
|
||||
throw console.error('Translate not assigned');
|
||||
},
|
||||
parseTemplate(template: string, data: object, forceTranslate = false): string {
|
||||
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this?.translate);
|
||||
},
|
||||
setTranslate(translateFn: (key: string, defaultTranslation?: string) => string) {
|
||||
this.translate = translateFn;
|
||||
}
|
||||
}
|
||||
|
||||
export function padValue(val: any, dec: number): string {
|
||||
let strVal;
|
||||
let n;
|
||||
|
||||
@ -14,12 +14,12 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import L, { LatLngTuple } from 'leaflet';
|
||||
import L, { LatLngTuple, LatLngBounds, Point } from 'leaflet';
|
||||
|
||||
import 'leaflet-providers';
|
||||
import 'leaflet.markercluster/dist/MarkerCluster.css'
|
||||
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
|
||||
import 'leaflet.markercluster/dist/leaflet.markercluster'
|
||||
import 'leaflet.markercluster/dist/MarkerCluster.css';
|
||||
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
|
||||
import LM from 'leaflet.markercluster/dist/leaflet.markercluster';
|
||||
|
||||
import { MapSettings, MarkerSettings, FormattedData, UnitedMapSettings, PolygonSettings, PolylineSettings } from './map-models';
|
||||
import { Marker } from './markers';
|
||||
@ -34,7 +34,7 @@ export default abstract class LeafletMap {
|
||||
markers: Map<string, Marker> = new Map();
|
||||
polylines: Map<string, Polyline> = new Map();
|
||||
polygons: Map<string, Polygon> = new Map();
|
||||
dragMode = true;
|
||||
dragMode = false;
|
||||
map: L.Map;
|
||||
map$: BehaviorSubject<L.Map> = new BehaviorSubject(null);
|
||||
ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map));
|
||||
@ -43,6 +43,7 @@ export default abstract class LeafletMap {
|
||||
bounds: L.LatLngBounds;
|
||||
newMarker: L.Marker;
|
||||
datasources: FormattedData[];
|
||||
markersCluster: LM.markerClusterGroup;
|
||||
|
||||
constructor(public $container: HTMLElement, options: UnitedMapSettings) {
|
||||
this.options = options;
|
||||
@ -50,13 +51,38 @@ export default abstract class LeafletMap {
|
||||
|
||||
public initSettings(options: MapSettings) {
|
||||
const { initCallback,
|
||||
disableScrollZooming, }: MapSettings = options;
|
||||
disableScrollZooming,
|
||||
useClusterMarkers,
|
||||
zoomOnClick,
|
||||
showCoverageOnHover,
|
||||
removeOutsideVisibleBounds,
|
||||
animate,
|
||||
chunkedLoading,
|
||||
maxClusterRadius,
|
||||
maxZoom }: MapSettings = options;
|
||||
if (disableScrollZooming) {
|
||||
this.map.scrollWheelZoom.disable();
|
||||
}
|
||||
if (initCallback) {
|
||||
setTimeout(options.initCallback, 0);
|
||||
}
|
||||
if (useClusterMarkers) {
|
||||
const clusteringSettings: LM.MarkerClusterGroupOptions = {
|
||||
zoomToBoundsOnClick: zoomOnClick,
|
||||
showCoverageOnHover,
|
||||
removeOutsideVisibleBounds,
|
||||
animate,
|
||||
chunkedLoading
|
||||
};
|
||||
if (maxClusterRadius && maxClusterRadius > 0) {
|
||||
clusteringSettings.maxClusterRadius = Math.floor(maxClusterRadius);
|
||||
}
|
||||
if (maxZoom && maxZoom >= 0 && maxZoom < 19) {
|
||||
clusteringSettings.disableClusteringAtZoom = Math.floor(maxZoom);
|
||||
}
|
||||
this.markersCluster = LM.markerClusterGroup(clusteringSettings);
|
||||
this.ready$.subscribe(map => map.addLayer(this.markersCluster));
|
||||
}
|
||||
}
|
||||
|
||||
addMarkerControl() {
|
||||
@ -80,7 +106,7 @@ export default abstract class LeafletMap {
|
||||
this.saveMarkerLocation(updatedEnttity);
|
||||
this.map.removeLayer(newMarker);
|
||||
this.deleteMarker(ds.entityName);
|
||||
this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options, false);
|
||||
this.createMarker(ds.entityName, updatedEnttity, this.datasources, this.options);
|
||||
}
|
||||
datasourcesList.append(dsItem);
|
||||
});
|
||||
@ -103,7 +129,7 @@ export default abstract class LeafletMap {
|
||||
img.src = `assets/add_location.svg`;
|
||||
img.style.width = '32px';
|
||||
img.style.height = '32px';
|
||||
img.title = "Drag and drop to add marker";
|
||||
img.title = 'Drag and drop to add marker';
|
||||
img.onclick = this.dragMarker;
|
||||
img.draggable = true;
|
||||
const draggableImg = new L.Draggable(img);
|
||||
@ -115,11 +141,9 @@ export default abstract class LeafletMap {
|
||||
},
|
||||
dragMarker: this.dragMarker
|
||||
} as any);
|
||||
|
||||
L.control.addMarker = (opts) => {
|
||||
return new L.Control.AddMarker(opts);
|
||||
}
|
||||
|
||||
addMarker = L.control.addMarker({ position: 'topright' }).addTo(this.map);
|
||||
}
|
||||
}
|
||||
@ -171,9 +195,9 @@ export default abstract class LeafletMap {
|
||||
return this.map.getCenter();
|
||||
}
|
||||
|
||||
fitBounds(bounds, useDefaultZoom = false) {
|
||||
fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) {
|
||||
if (bounds.isValid()) {
|
||||
if ((this.options.dontFitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) {
|
||||
if ((!this.options.fitMapBounds || useDefaultZoom) && this.options.defaultZoomLevel) {
|
||||
this.map.setZoom(this.options.defaultZoomLevel, { animate: false });
|
||||
this.map.panTo(bounds.getCenter(), { animate: false });
|
||||
} else {
|
||||
@ -182,7 +206,7 @@ export default abstract class LeafletMap {
|
||||
this.map.setZoom(this.options.minZoomLevel, { animate: false });
|
||||
}
|
||||
});
|
||||
this.map.fitBounds(bounds, { padding: [50, 50], animate: false });
|
||||
this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false });
|
||||
}
|
||||
this.bounds = this.bounds.extend(bounds);
|
||||
}
|
||||
@ -232,11 +256,17 @@ export default abstract class LeafletMap {
|
||||
this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) });
|
||||
}
|
||||
|
||||
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, setFocus = true) {
|
||||
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
|
||||
this.ready$.subscribe(() => {
|
||||
const newMarker = new Marker(this.map, this.convertPosition(data), settings, data, dataSources, () => { }, this.dragMarker);
|
||||
this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), setFocus);
|
||||
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);
|
||||
this.markers.set(key, newMarker);
|
||||
if (this.options.useClusterMarkers) {
|
||||
this.markersCluster.addLayer(newMarker.leafletMarker);
|
||||
}
|
||||
else {
|
||||
this.map.addLayer(newMarker.leafletMarker);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -249,6 +279,8 @@ export default abstract class LeafletMap {
|
||||
if (settings.showTooltip) {
|
||||
marker.updateMarkerTooltip(data);
|
||||
}
|
||||
if (settings.useClusterMarkers)
|
||||
this.markersCluster.refreshClusters()
|
||||
marker.setDataSources(data, dataSources);
|
||||
marker.updateMarkerIcon(settings);
|
||||
}
|
||||
@ -327,7 +359,9 @@ export default abstract class LeafletMap {
|
||||
|
||||
updatePolygon(key: string, data: LatLngTuple[], dataSources: DatasourceData[], settings: PolygonSettings) {
|
||||
this.ready$.subscribe(() => {
|
||||
this.polygons.get(key).updatePolygon(data, dataSources, settings);
|
||||
const poly = this.polygons.get(key);
|
||||
poly.updatePolygon(data, dataSources, settings);
|
||||
this.fitBounds(poly.leafletPoly.getBounds());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { LatLngTuple } from 'leaflet';
|
||||
import { LatLngTuple, LeafletMouseEvent } from 'leaflet';
|
||||
|
||||
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
|
||||
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
|
||||
@ -24,14 +24,15 @@ export type MapSettings = {
|
||||
draggableMarker: boolean;
|
||||
initCallback?: () => any;
|
||||
defaultZoomLevel?: number;
|
||||
dontFitMapBounds?: boolean;
|
||||
disableScrollZooming?: boolean;
|
||||
minZoomLevel?: number;
|
||||
useClusterMarkers: boolean;
|
||||
latKeyName?: string;
|
||||
lngKeyName?: string;
|
||||
xPosKeyName?: string;
|
||||
yPosKeyName?: string;
|
||||
mapProvider: MapProviders;
|
||||
mapProviderHere: string;
|
||||
mapUrl?: string;
|
||||
mapImageUrl?: string;
|
||||
provider?: MapProviders;
|
||||
@ -42,6 +43,13 @@ export type MapSettings = {
|
||||
gmDefaultMapType?: string;
|
||||
useLabelFunction: string;
|
||||
icon?: any;
|
||||
zoomOnClick: boolean,
|
||||
maxZoom: number,
|
||||
showCoverageOnHover: boolean,
|
||||
animate: boolean,
|
||||
maxClusterRadius: number,
|
||||
chunkedLoading: boolean,
|
||||
removeOutsideVisibleBounds: boolean
|
||||
}
|
||||
|
||||
export enum MapProviders {
|
||||
@ -54,7 +62,7 @@ export enum MapProviders {
|
||||
|
||||
export type MarkerSettings = {
|
||||
tooltipPattern?: any;
|
||||
tooltipActions: object;
|
||||
tooltipAction: { [name: string]: actionsHandler };
|
||||
icon?: any;
|
||||
showLabel?: boolean;
|
||||
label: string;
|
||||
@ -63,19 +71,19 @@ export type MarkerSettings = {
|
||||
useLabelFunction: boolean;
|
||||
draggableMarker: boolean;
|
||||
showTooltip?: boolean;
|
||||
useTooltipFunction: boolean;
|
||||
useColorFunction: boolean;
|
||||
color?: string;
|
||||
autocloseTooltip: boolean;
|
||||
displayTooltipAction: string;
|
||||
showTooltipAction: string;
|
||||
useClusterMarkers: boolean;
|
||||
currentImage?: string;
|
||||
useMarkerImageFunction?: boolean;
|
||||
markerImages?: string[];
|
||||
useMarkerImage: boolean;
|
||||
markerImageSize: number;
|
||||
fitMapBounds: boolean;
|
||||
markerImage: {
|
||||
length: number
|
||||
}
|
||||
|
||||
markerImage: string;
|
||||
markerClick: { [name: string]: actionsHandler };
|
||||
colorFunction: GenericFunction;
|
||||
tooltipFunction: GenericFunction;
|
||||
labelFunction: GenericFunction;
|
||||
@ -99,18 +107,18 @@ export type PolygonSettings = {
|
||||
polygonStrokeColor: string;
|
||||
polygonColor: string;
|
||||
autocloseTooltip: boolean;
|
||||
displayTooltipAction: string;
|
||||
tooltipActions: object;
|
||||
|
||||
showTooltipAction: string;
|
||||
tooltipAction: object;
|
||||
polygonClick: { [name: string]: actionsHandler };
|
||||
polygonColorFunction?: GenericFunction;
|
||||
}
|
||||
|
||||
export type PolylineSettings = {
|
||||
usePolylineDecorator: any;
|
||||
autocloseTooltip: boolean;
|
||||
displayTooltipAction: string;
|
||||
showTooltipAction: string;
|
||||
useColorFunction: any;
|
||||
tooltipActions: object;
|
||||
tooltipAction: { [name: string]: actionsHandler };
|
||||
color: string;
|
||||
useStrokeOpacityFunction: any;
|
||||
strokeOpacity: number;
|
||||
@ -134,4 +142,6 @@ export interface HistorySelectSettings {
|
||||
buttonColor: string;
|
||||
}
|
||||
|
||||
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;
|
||||
export type actionsHandler = ($event: Event | LeafletMouseEvent) => void;
|
||||
|
||||
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
///
|
||||
|
||||
import { JsonSettingsSchema } from '@shared/models/widget.models';
|
||||
import { MapProviders } from './map-models';
|
||||
|
||||
export interface MapWidgetInterface {
|
||||
resize(),
|
||||
@ -24,8 +25,8 @@ export interface MapWidgetInterface {
|
||||
}
|
||||
|
||||
export interface MapWidgetStaticInterface {
|
||||
settingsSchema(mapProvider?, drawRoutes?): JsonSettingsSchema;
|
||||
getProvidersSchema():JsonSettingsSchema
|
||||
settingsSchema(mapProvider?: MapProviders, drawRoutes?: boolean): JsonSettingsSchema;
|
||||
getProvidersSchema(mapProvider?: MapProviders): JsonSettingsSchema
|
||||
dataKeySettingsSchema(): object;
|
||||
actionSources(): object;
|
||||
}
|
||||
|
||||
@ -26,11 +26,12 @@ import {
|
||||
markerClusteringSettingsSchema,
|
||||
markerClusteringSettingsSchemaLeaflet,
|
||||
hereMapSettingsSchema,
|
||||
mapProviderSchema
|
||||
mapProviderSchema,
|
||||
mapPolygonSchema
|
||||
} from './schemes';
|
||||
import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
|
||||
import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
|
||||
import { parseFunction, parseArray, parseData, safeExecute } from '@core/utils';
|
||||
import { parseFunction, parseArray, parseData, safeExecute, parseWithTranslation } from '@core/utils';
|
||||
import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils';
|
||||
import { forkJoin } from 'rxjs';
|
||||
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
|
||||
@ -40,12 +41,13 @@ import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { AttributeScope } 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 '@app/core/public-api';
|
||||
|
||||
// @dynamic
|
||||
export class MapWidgetController implements MapWidgetInterface {
|
||||
|
||||
constructor(public mapProvider: MapProviders, private drawRoutes: boolean, public ctx: WidgetContext, $element: HTMLElement) {
|
||||
console.log("MapWidgetController -> constructor -> ctx", ctx)
|
||||
if (this.map) {
|
||||
this.map.map.remove();
|
||||
delete this.map;
|
||||
@ -56,15 +58,16 @@ export class MapWidgetController implements MapWidgetInterface {
|
||||
$element = ctx.$container[0];
|
||||
}
|
||||
this.settings = this.initSettings(ctx.settings);
|
||||
const descriptors = this.ctx.actionsApi.getActionDescriptors('tooltipAction');
|
||||
this.settings.tooltipActions = {};
|
||||
descriptors.forEach(descriptor => {
|
||||
this.settings.tooltipActions[descriptor.name] = ($event) => this.onTooltipClick(descriptor, $event);
|
||||
}, this.settings.tooltipActions);
|
||||
this.settings.tooltipAction = this.getDescriptors('tooltipAction');
|
||||
this.settings.markerClick = this.getDescriptors('markerClick');
|
||||
this.settings.polygonClick = this.getDescriptors('polygonClick');
|
||||
|
||||
// this.settings.
|
||||
const MapClass = providerSets[this.provider]?.MapClass;
|
||||
if (!MapClass) {
|
||||
return;
|
||||
}
|
||||
parseWithTranslation.setTranslate(this.translate);
|
||||
this.map = new MapClass($element, this.settings);
|
||||
this.map.saveMarkerLocation = this.setMarkerLocation;
|
||||
}
|
||||
@ -74,13 +77,13 @@ export class MapWidgetController implements MapWidgetInterface {
|
||||
schema: JsonSettingsSchema;
|
||||
data;
|
||||
settings: UnitedMapSettings;
|
||||
actions: Map<string, Map<string, (widgetContext: WidgetContext) => void>>;
|
||||
|
||||
public static dataKeySettingsSchema(): object {
|
||||
return {};
|
||||
}
|
||||
|
||||
public static getProvidersSchema() {
|
||||
public static getProvidersSchema(mapProvider: MapProviders) {
|
||||
mapProviderSchema.schema.properties.provider.default = mapProvider;
|
||||
return mergeSchemes([mapProviderSchema,
|
||||
...Object.values(providerSets)?.map(
|
||||
(setting: IProvider) => addCondition(setting?.schema, `model.provider === '${setting.name}'`))]);
|
||||
@ -88,18 +91,20 @@ export class MapWidgetController implements MapWidgetInterface {
|
||||
|
||||
public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema {
|
||||
const schema = initSchema();
|
||||
addToSchema(schema, this.getProvidersSchema());
|
||||
addGroupInfo(schema, 'Map Provider Settings');
|
||||
addToSchema(schema, commonMapSettingsSchema);
|
||||
addToSchema(schema, this.getProvidersSchema(mapProvider));
|
||||
if(mapProvider!=='image-map'){
|
||||
addGroupInfo(schema, 'Map Provider Settings');
|
||||
addToSchema(schema, mergeSchemes([commonMapSettingsSchema, addCondition(mapPolygonSchema, 'model.showPolygon === true')]));
|
||||
addGroupInfo(schema, 'Common Map Settings');
|
||||
if (drawRoutes) {
|
||||
addToSchema(schema, routeMapSettingsSchema);
|
||||
addGroupInfo(schema, 'Route Map Settings');
|
||||
} else if (mapProvider !== 'image-map') {
|
||||
const clusteringSchema = mergeSchemes([markerClusteringSettingsSchemaLeaflet, markerClusteringSettingsSchema])
|
||||
const clusteringSchema = mergeSchemes([markerClusteringSettingsSchema,
|
||||
addCondition(markerClusteringSettingsSchemaLeaflet, `model.useClusterMarkers === true`)])
|
||||
addToSchema(schema, clusteringSchema);
|
||||
addGroupInfo(schema, 'Markers Clustering Settings');
|
||||
}
|
||||
}}
|
||||
return schema;
|
||||
}
|
||||
|
||||
@ -120,14 +125,28 @@ export class MapWidgetController implements MapWidgetInterface {
|
||||
};
|
||||
}
|
||||
|
||||
translate = (key: string, defaultTranslation?: string):string => {
|
||||
return (this.ctx.$injector.get(UtilsService).customTranslation(key, defaultTranslation || key)
|
||||
|| this.ctx.$injector.get(TranslateService).instant(key));
|
||||
}
|
||||
|
||||
getDescriptors(name: string): { [name: string]: ($event: Event) => void } {
|
||||
const descriptors = this.ctx.actionsApi.getActionDescriptors(name);
|
||||
const actions = {};
|
||||
descriptors.forEach(descriptor => {
|
||||
actions[descriptor.name] = ($event: Event) => this.onCustomAction(descriptor, $event);
|
||||
}, actions);
|
||||
return actions;
|
||||
}
|
||||
|
||||
onInit() {
|
||||
}
|
||||
|
||||
private onTooltipClick(descriptor: WidgetActionDescriptor, $event: any) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
private onCustomAction(descriptor: WidgetActionDescriptor, $event: any) {
|
||||
if ($event & $event.stopPropagation) {
|
||||
$event?.stopPropagation();
|
||||
}
|
||||
// safeExecute(parseFunction(descriptor.customFunction, ['$event', 'widgetContext']), [$event, this.ctx])
|
||||
// safeExecute(parseFunction(descriptor.customFunction, ['$event', 'widgetContext']), [$event, this.ctx])
|
||||
const entityInfo = this.ctx.actionsApi.getActiveEntityInfo();
|
||||
const entityId = entityInfo ? entityInfo.entityId : null;
|
||||
const entityName = entityInfo ? entityInfo.entityName : null;
|
||||
@ -153,13 +172,17 @@ export class MapWidgetController implements MapWidgetInterface {
|
||||
}]
|
||||
);
|
||||
})).subscribe(res => {
|
||||
console.log('MapWidgetController -> setMarkerLocation -> res', res)
|
||||
});
|
||||
}
|
||||
|
||||
initSettings(settings: UnitedMapSettings): UnitedMapSettings {
|
||||
const functionParams = ['data', 'dsData', 'dsIndex'];
|
||||
this.provider = settings.provider || this.mapProvider;
|
||||
if (!settings.mapProviderHere) {
|
||||
if (settings.mapProvider && hereProviders.includes(settings.mapProvider))
|
||||
settings.mapProviderHere = settings.mapProvider
|
||||
else settings.mapProviderHere = hereProviders[0];
|
||||
}
|
||||
const customOptions = {
|
||||
provider: this.provider,
|
||||
mapUrl: settings?.mapImageUrl,
|
||||
@ -173,7 +196,7 @@ export class MapWidgetController implements MapWidgetInterface {
|
||||
'<b>${entityName}</b><br/><br/><b>Latitude:</b> ${' +
|
||||
settings.latKeyName + ':7}<br/><b>Longitude:</b> ${' + settings.lngKeyName + ':7}',
|
||||
defaultCenterPosition: getDefCenterPosition(settings?.defaultCenterPosition),
|
||||
currentImage: (settings.useMarkerImage && settings.markerImage?.length) ? {
|
||||
currentImage: (settings.markerImage?.length) ? {
|
||||
url: settings.markerImage,
|
||||
size: settings.markerImageSize || 34
|
||||
} : null
|
||||
@ -253,7 +276,7 @@ export const defaultSettings: any = {
|
||||
useDefaultCenterPosition: false,
|
||||
showTooltipAction: 'click',
|
||||
autocloseTooltip: false,
|
||||
showPolygon: true,
|
||||
showPolygon: false,
|
||||
labelColor: '#000000',
|
||||
color: '#FE7569',
|
||||
polygonColor: '#0000ff',
|
||||
@ -267,10 +290,16 @@ export const defaultSettings: any = {
|
||||
strokeOpacity: 1.0,
|
||||
initCallback: () => { },
|
||||
defaultZoomLevel: 8,
|
||||
dontFitMapBounds: false,
|
||||
disableScrollZooming: false,
|
||||
minZoomLevel: 16,
|
||||
credentials: '',
|
||||
markerClusteringSetting: null,
|
||||
draggableMarker: false
|
||||
draggableMarker: false,
|
||||
fitMapBounds: true
|
||||
};
|
||||
|
||||
export const hereProviders = [
|
||||
'HERE.normalDay',
|
||||
'HERE.normalNight',
|
||||
'HERE.hybridDay',
|
||||
'HERE.terrainDay']
|
||||
|
||||
@ -17,34 +17,32 @@
|
||||
import L from 'leaflet';
|
||||
import _ from 'lodash';
|
||||
import { MarkerSettings, PolylineSettings, PolygonSettings } from './map-models';
|
||||
import { parseWithTranslation } from '@app/core/utils';
|
||||
|
||||
export function createTooltip(target: L.Layer,
|
||||
settings: MarkerSettings | PolylineSettings | PolygonSettings,
|
||||
content?: string | HTMLElement): L.Popup {
|
||||
const popup = L.popup();
|
||||
popup.setContent(content);
|
||||
console.log(settings);
|
||||
|
||||
target.bindPopup(popup, { autoClose: settings.autocloseTooltip, closeOnClick: false });
|
||||
target.on('popupopen', () => {
|
||||
let actions = document.getElementsByClassName('tb-custom-action');
|
||||
Array.from(actions).forEach(
|
||||
(element: HTMLElement) => {
|
||||
if (element && settings.tooltipActions[element.id]) {
|
||||
console.log(settings.tooltipActions[element.id]);
|
||||
element.addEventListener('click', settings.tooltipActions[element.id])
|
||||
}
|
||||
})
|
||||
})
|
||||
if (settings.displayTooltipAction === 'hover') {
|
||||
if (settings.showTooltipAction === 'hover') {
|
||||
target.off('click');
|
||||
target.on('mouseover', function () {
|
||||
this.openPopup();
|
||||
target.openPopup();
|
||||
});
|
||||
target.on('mouseout', function () {
|
||||
this.closePopup();
|
||||
target.closePopup();
|
||||
});
|
||||
}
|
||||
target.on('popupopen', () => {
|
||||
const actions = document.getElementsByClassName('tb-custom-action');
|
||||
Array.from(actions).forEach(
|
||||
(element: HTMLElement) => {
|
||||
if (element && settings.tooltipAction[element.id]) {
|
||||
element.addEventListener('click', settings.tooltipAction[element.id])
|
||||
}
|
||||
});
|
||||
});
|
||||
return popup;
|
||||
}
|
||||
|
||||
|
||||
@ -29,3 +29,7 @@
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.leaflet-container{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@ -14,13 +14,13 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import L from 'leaflet';
|
||||
import { MarkerSettings, FormattedData } from './map-models';
|
||||
import { aspectCache, safeExecute, parseTemplate } from '@app/core/utils';
|
||||
import { aspectCache, parseWithTranslation, safeExecute } from '@app/core/utils';
|
||||
import L, { LeafletMouseEvent } from 'leaflet';
|
||||
import { FormattedData, MarkerSettings } from './map-models';
|
||||
import { createTooltip } from './maps-utils';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
export class Marker {
|
||||
|
||||
leafletMarker: L.Marker;
|
||||
tooltipOffset: [number, number];
|
||||
tooltip: L.Popup;
|
||||
@ -28,8 +28,8 @@ export class Marker {
|
||||
data: FormattedData;
|
||||
dataSources: FormattedData[];
|
||||
|
||||
constructor(private map: L.Map, location: L.LatLngExpression, public settings: MarkerSettings,
|
||||
data?, dataSources?, onClickListener?, onDragendListener?) {
|
||||
constructor(location: L.LatLngExpression, public settings: MarkerSettings,
|
||||
data?: FormattedData, dataSources?, onDragendListener?) {
|
||||
this.setDataSources(data, dataSources);
|
||||
this.leafletMarker = L.marker(location, {
|
||||
draggable: settings.draggableMarker
|
||||
@ -39,15 +39,21 @@ export class Marker {
|
||||
this.leafletMarker.setIcon(iconInfo.icon);
|
||||
this.tooltipOffset = [0, -iconInfo.size[1] + 10];
|
||||
this.updateMarkerLabel(settings);
|
||||
this.leafletMarker.addTo(map);
|
||||
});
|
||||
|
||||
if (settings.showTooltip) {
|
||||
this.tooltip = createTooltip(this.leafletMarker, settings, parseTemplate(this.settings.tooltipPattern, data));
|
||||
this.tooltip = createTooltip(this.leafletMarker, settings);
|
||||
this.updateMarkerTooltip(data);
|
||||
}
|
||||
|
||||
if (onClickListener) {
|
||||
this.leafletMarker.on('click', onClickListener);
|
||||
if (this.settings.markerClick) {
|
||||
this.leafletMarker.on('click', (event: LeafletMouseEvent) => {
|
||||
for (const action in this.settings.markerClick) {
|
||||
if (typeof (this.settings.markerClick[action]) === 'function') {
|
||||
this.settings.markerClick[action](event);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (onDragendListener) {
|
||||
@ -61,7 +67,9 @@ export class Marker {
|
||||
}
|
||||
|
||||
updateMarkerTooltip(data: FormattedData) {
|
||||
this.tooltip.setContent(parseTemplate(this.settings.tooltipPattern, data));
|
||||
const pattern = this.settings.useTooltipFunction ?
|
||||
safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern;
|
||||
this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true));
|
||||
}
|
||||
|
||||
updateMarkerPosition(position: L.LatLngExpression) {
|
||||
@ -70,13 +78,10 @@ export class Marker {
|
||||
|
||||
updateMarkerLabel(settings: MarkerSettings) {
|
||||
this.leafletMarker.unbindTooltip();
|
||||
|
||||
if (settings.showLabel) {
|
||||
if (settings.useLabelFunction) {
|
||||
settings.labelText = parseTemplate(
|
||||
safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]), this.data)
|
||||
}
|
||||
else settings.labelText = parseTemplate(settings.label, this.data);
|
||||
const pattern = settings.useLabelFunction ?
|
||||
safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label;
|
||||
settings.labelText = parseWithTranslation.parseTemplate(pattern, this.data, true);
|
||||
this.leafletMarker.bindTooltip(`<div style="color: ${settings.labelColor};"><b>${settings.labelText}</b></div>`,
|
||||
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset });
|
||||
}
|
||||
@ -97,7 +102,6 @@ export class Marker {
|
||||
}
|
||||
|
||||
createMarkerIcon(onMarkerIconReady) {
|
||||
|
||||
if (this.settings.icon) {
|
||||
onMarkerIconReady({
|
||||
size: [30, 30],
|
||||
@ -105,11 +109,11 @@ export class Marker {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const currentImage = this.settings.useMarkerImageFunction ?
|
||||
safeExecute(this.settings.markerImageFunction,
|
||||
[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(
|
||||
@ -136,19 +140,19 @@ export class Marker {
|
||||
};
|
||||
onMarkerIconReady(iconInfo);
|
||||
} else {
|
||||
this.createDefaultMarkerIcon(this.settings.color, onMarkerIconReady);
|
||||
this.createDefaultMarkerIcon(currentColor, onMarkerIconReady);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.createDefaultMarkerIcon(this.settings.color, onMarkerIconReady);
|
||||
this.createDefaultMarkerIcon(currentColor, onMarkerIconReady);
|
||||
}
|
||||
}
|
||||
|
||||
createDefaultMarkerIcon(color, onMarkerIconReady) {
|
||||
const pinColor = color.substr(1);
|
||||
const icon = L.icon({
|
||||
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
|
||||
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color,
|
||||
iconSize: [21, 34],
|
||||
iconAnchor: [10, 34],
|
||||
popupAnchor: [0, -34],
|
||||
|
||||
@ -20,14 +20,9 @@ import { MapSettings, UnitedMapSettings } from '../map-models';
|
||||
|
||||
export class HEREMap extends LeafletMap {
|
||||
constructor($container, options: UnitedMapSettings) {
|
||||
const defaultCredentials =
|
||||
{
|
||||
app_id: 'AhM6TzD9ThyK78CT3ptx',
|
||||
app_code: 'p6NPiITB3Vv0GMUFnkLOOg'
|
||||
}
|
||||
super($container, options);
|
||||
const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
|
||||
const tileLayer = (L.tileLayer as any).provider(options.mapProvider || 'HERE.normalDay', options.credentials || defaultCredentials);
|
||||
const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials);
|
||||
tileLayer.addTo(map);
|
||||
super.setMap(map);
|
||||
super.initSettings(options);
|
||||
|
||||
@ -110,7 +110,7 @@ export const hereMapSettingsSchema =
|
||||
title: 'HERE Map Configuration',
|
||||
type: 'object',
|
||||
properties: {
|
||||
mapProvider: {
|
||||
mapProviderHere: {
|
||||
title: 'Map layer',
|
||||
type: 'string',
|
||||
default: 'HERE.normalDay'
|
||||
@ -120,11 +120,13 @@ export const hereMapSettingsSchema =
|
||||
properties: {
|
||||
app_id: {
|
||||
title: 'HERE app id',
|
||||
type: 'string'
|
||||
type: 'string',
|
||||
default: 'AhM6TzD9ThyK78CT3ptx'
|
||||
},
|
||||
app_code: {
|
||||
title: 'HERE app code',
|
||||
type: 'string'
|
||||
type: 'string',
|
||||
default: 'p6NPiITB3Vv0GMUFnkLOOg'
|
||||
}
|
||||
},
|
||||
required: ['app_id', 'app_code']
|
||||
@ -134,7 +136,7 @@ export const hereMapSettingsSchema =
|
||||
},
|
||||
form: [
|
||||
{
|
||||
key: 'mapProvider',
|
||||
key: 'mapProviderHere',
|
||||
type: 'rc-select',
|
||||
multiple: false,
|
||||
items: [
|
||||
@ -339,43 +341,6 @@ export const commonMapSettingsSchema =
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
polygonKeyName: {
|
||||
title: 'Polygon key name',
|
||||
type: 'string',
|
||||
default: 'coordinates'
|
||||
},
|
||||
polygonColor: {
|
||||
title: 'Polygon color',
|
||||
type: 'string'
|
||||
},
|
||||
polygonOpacity: {
|
||||
title: 'Polygon opacity',
|
||||
type: 'number',
|
||||
default: 0.5
|
||||
},
|
||||
polygonStrokeColor: {
|
||||
title: 'Stroke color',
|
||||
type: 'string'
|
||||
},
|
||||
polygonStrokeOpacity: {
|
||||
title: 'Stroke opacity',
|
||||
type: 'number',
|
||||
default: 1
|
||||
},
|
||||
polygonStrokeWeight: {
|
||||
title: 'Stroke 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'
|
||||
},
|
||||
markerImage: {
|
||||
title: 'Custom marker image',
|
||||
type: 'string'
|
||||
@ -455,20 +420,6 @@ export const commonMapSettingsSchema =
|
||||
{
|
||||
key: 'colorFunction',
|
||||
type: 'javascript'
|
||||
}, 'showPolygon', 'polygonKeyName',
|
||||
{
|
||||
key: 'polygonColor',
|
||||
type: 'color'
|
||||
},
|
||||
'polygonOpacity',
|
||||
{
|
||||
key: 'polygonStrokeColor',
|
||||
type: 'color'
|
||||
},
|
||||
'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction',
|
||||
{
|
||||
key: 'polygonColorFunction',
|
||||
type: 'javascript'
|
||||
},
|
||||
{
|
||||
key: 'markerImage',
|
||||
@ -488,7 +439,73 @@ export const commonMapSettingsSchema =
|
||||
type: 'image'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
'showPolygon',
|
||||
]
|
||||
};
|
||||
|
||||
export const mapPolygonSchema =
|
||||
{
|
||||
schema: {
|
||||
title: 'Map Polygon Configuration',
|
||||
type: 'object',
|
||||
properties: {
|
||||
polygonKeyName: {
|
||||
title: 'Polygon key name',
|
||||
type: 'string',
|
||||
default: 'coordinates'
|
||||
},
|
||||
polygonColor: {
|
||||
title: 'Polygon color',
|
||||
type: 'string'
|
||||
},
|
||||
polygonOpacity: {
|
||||
title: 'Polygon opacity',
|
||||
type: 'number',
|
||||
default: 0.5
|
||||
},
|
||||
polygonStrokeColor: {
|
||||
title: 'Stroke color',
|
||||
type: 'string'
|
||||
},
|
||||
polygonStrokeOpacity: {
|
||||
title: 'Stroke opacity',
|
||||
type: 'number',
|
||||
default: 1
|
||||
},
|
||||
polygonStrokeWeight: {
|
||||
title: 'Stroke 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'
|
||||
},
|
||||
},
|
||||
required: []
|
||||
},
|
||||
form: [
|
||||
'polygonKeyName',
|
||||
{
|
||||
key: 'polygonColor',
|
||||
type: 'color'
|
||||
},
|
||||
'polygonOpacity',
|
||||
{
|
||||
key: 'polygonStrokeColor',
|
||||
type: 'color'
|
||||
},
|
||||
'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction',
|
||||
{
|
||||
key: 'polygonColorFunction',
|
||||
type: 'javascript'
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
@ -527,23 +544,12 @@ export const markerClusteringSettingsSchema =
|
||||
title: 'Use map markers clustering',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
zoomOnClick: {
|
||||
title: 'Zoom when clicking on a cluster',
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
maxZoom: {
|
||||
title: 'The maximum zoom level when a marker can be part of a cluster (0 - 18)',
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
required: []
|
||||
},
|
||||
form: [
|
||||
'useClusterMarkers',
|
||||
'zoomOnClick',
|
||||
'maxZoom'
|
||||
]
|
||||
};
|
||||
|
||||
@ -571,12 +577,23 @@ export const markerClusteringSettingsSchemaGoogle =
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const markerClusteringSettingsSchemaLeaflet =
|
||||
{
|
||||
schema: {
|
||||
title: 'Markers Clustering Configuration Leaflet',
|
||||
type: 'object',
|
||||
properties: {
|
||||
zoomOnClick: {
|
||||
title: 'Zoom when clicking on a cluster',
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
maxZoom: {
|
||||
title: 'The maximum zoom level when a marker can be part of a cluster (0 - 18)',
|
||||
type: 'number'
|
||||
},
|
||||
showCoverageOnHover: {
|
||||
title: 'Show the bounds of markers when mouse over a cluster',
|
||||
type: 'boolean',
|
||||
@ -606,6 +623,8 @@ export const markerClusteringSettingsSchemaLeaflet =
|
||||
required: []
|
||||
},
|
||||
form: [
|
||||
'zoomOnClick',
|
||||
'maxZoom',
|
||||
'showCoverageOnHover',
|
||||
'animate',
|
||||
'maxClusterRadius',
|
||||
|
||||
@ -17,17 +17,17 @@
|
||||
-->
|
||||
<div class="trip-animation-widget">
|
||||
<div class="trip-animation-label-container" *ngIf="settings.showLabel">
|
||||
{{settings.label | tbParseTemplate: activeTrip}}
|
||||
{{label }}
|
||||
</div>
|
||||
<div class="trip-animation-container" layout="column">
|
||||
<div class="trip-animation-container" fxLayout="column">
|
||||
<div class="map" #map></div>
|
||||
<div class="trip-animation-info-panel" layout="row">
|
||||
<div class="trip-animation-info-panel" fxLayout="row">
|
||||
<button class="tooltip-button" mat-mini-fab color="primary" aria-label="tooltip"
|
||||
*ngIf="settings.showTooltip" (click)="showHideTooltip()">
|
||||
*ngIf="settings.showTooltip" (click)="visibleTooltip = !visibleTooltip">
|
||||
<mat-icon>info_outline</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="trip-animation-tooltip md-whiteframe-z4" layout="column"
|
||||
<div class="trip-animation-tooltip md-whiteframe-z4" fxLayout="column"
|
||||
[ngClass]="{'trip-animation-tooltip-hidden':!visibleTooltip}" [innerHTML]="mainTooltip"
|
||||
[ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
|
||||
</div>
|
||||
|
||||
@ -74,10 +74,10 @@
|
||||
|
||||
.trip-animation-tooltip {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
top: 30px;
|
||||
right: 0;
|
||||
z-index: 400;
|
||||
padding: 10px;
|
||||
z-index: 1000;
|
||||
padding: 5px;
|
||||
background-color: #fff;
|
||||
transition: 0.3s ease-in-out;
|
||||
|
||||
@ -86,4 +86,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,13 +22,14 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil';
|
||||
import { Component, OnInit, Input, ViewChild, AfterViewInit, ChangeDetectorRef, SecurityContext } from '@angular/core';
|
||||
import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
|
||||
import { MapProviders } from '../lib/maps/map-models';
|
||||
import { parseArray, parseTemplate, safeExecute } from '@app/core/utils';
|
||||
import { parseArray, parseWithTranslation, safeExecute, parseTemplate } from '@app/core/utils';
|
||||
import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils';
|
||||
import { tripAnimationSchema } from '../lib/maps/schemes';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
|
||||
import { getRatio, findAngle } from '../lib/maps/maps-utils';
|
||||
import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -55,6 +56,9 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
|
||||
mainTooltip = '';
|
||||
visibleTooltip = false;
|
||||
activeTrip;
|
||||
label;
|
||||
minTime;
|
||||
maxTime;
|
||||
|
||||
static getSettingsSchema(): JsonSettingsSchema {
|
||||
const schema = initSchema();
|
||||
@ -90,48 +94,58 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
|
||||
|
||||
ngAfterViewInit() {
|
||||
const ctxCopy: WidgetContext = _.cloneDeep(this.ctx);
|
||||
ctxCopy.settings.showLabel = false;
|
||||
ctxCopy.settings.showTooltip = false;
|
||||
this.mapWidget = new MapWidgetController(MapProviders.openstreet, false, ctxCopy, this.mapContainer.nativeElement);
|
||||
}
|
||||
|
||||
timeUpdated(time: number) {
|
||||
const currentPosition = this.interpolatedData.map(dataSource => dataSource[time]);
|
||||
this.activeTrip = currentPosition[0];
|
||||
if (this.settings.showPolygon) {
|
||||
this.mapWidget.map.updatePolygons(this.interpolatedData);
|
||||
this.minTime = moment(this.historicalData[0][this.historicalData.length - 1]?.time).format('YYYY-MM-DD HH:mm:ss')
|
||||
this.maxTime = moment(this.historicalData[0][0]?.time).format('YYYY-MM-DD HH:mm:ss')
|
||||
this.calcLabel();
|
||||
this.calcTooltip();
|
||||
if (this.mapWidget) {
|
||||
if (this.settings.showPolygon) {
|
||||
this.mapWidget.map.updatePolygons(this.interpolatedData);
|
||||
}
|
||||
this.mapWidget.map.updateMarkers(currentPosition);
|
||||
}
|
||||
this.mapWidget.map.updateMarkers(currentPosition);
|
||||
}
|
||||
|
||||
setActiveTrip() {
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
showHideTooltip() {
|
||||
calcTooltip() {
|
||||
const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
|
||||
const tooltipText: string = this.settings.useTooltipFunction ?
|
||||
safeExecute(this.settings.tooolTipFunction, [this.activeTrip, this.historicalData, 0])
|
||||
: this.settings.tooltipPattern;
|
||||
safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern;
|
||||
this.mainTooltip = this.sanitizer.sanitize(
|
||||
SecurityContext.HTML, (parseWithTranslation.parseTemplate(tooltipText, data, true)));
|
||||
}
|
||||
|
||||
this.mainTooltip = this.sanitizer.sanitize(SecurityContext.HTML, parseTemplate(tooltipText, this.activeTrip))
|
||||
this.visibleTooltip = !this.visibleTooltip;
|
||||
calcLabel() {
|
||||
const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }
|
||||
const labelText: string = this.settings.useLabelFunction ?
|
||||
safeExecute(this.settings.labelFunction, [data, this.historicalData, 0]) : this.settings.label;
|
||||
this.label = (parseWithTranslation.parseTemplate(labelText, data, true));
|
||||
}
|
||||
|
||||
interpolateArray(originData, interpolatedIntervals) {
|
||||
|
||||
const result = {};
|
||||
|
||||
for (let i = 1, j = 0; i < originData.length && j < interpolatedIntervals.length;) {
|
||||
const currentTime = interpolatedIntervals[j];
|
||||
while (originData[i].time < currentTime) i++;
|
||||
|
||||
@ -16,15 +16,21 @@
|
||||
|
||||
-->
|
||||
<div class="trip-animation-control-panel">
|
||||
<div>
|
||||
<div fxFlex fxLayout="row" fxFlexAlign="center">
|
||||
<button mat-icon-button class="mat-icon-button" aria-label="Start" (click)="moveStart()">
|
||||
<mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">fast_rewind</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button class="mat-icon-button" aria-label="Previous" (click)="movePrev()">
|
||||
<mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">skip_previous</mat-icon>
|
||||
</button>
|
||||
<mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="changeIndex()">
|
||||
</mat-slider>
|
||||
<div fxLayout="column" fxFlex="100">
|
||||
<mat-slider [(ngModel)]="index" [min]="minTimeIndex" [max]="maxTimeIndex" (change)="changeIndex()">
|
||||
</mat-slider>
|
||||
<div class="panel-timer">
|
||||
<span *ngIf="this.intervals[this.index]">{{ this.intervals[this.index] | date:'medium'}}</span>
|
||||
<span *ngIf="!this.intervals[this.index]">{{ "widget.no-data-found" | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button mat-icon-button class="mat-icon-button" aria-label="Next" (click)="moveNext()">
|
||||
<mat-icon class="material-icons" [ngStyle]="{'color': settings.buttonColor}">skip_next</mat-icon>
|
||||
</button>
|
||||
@ -41,11 +47,8 @@
|
||||
pause_circle_outline
|
||||
</mat-icon>
|
||||
</button>
|
||||
<mat-select matInput [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select" aria-label="Speed selector">
|
||||
<mat-option [value]="speedValue" flex="1" *ngFor="let speedValue of speeds">{{speedValue}} </mat-option>
|
||||
<mat-select matInput [(ngModel)]="speed" (selectionChange)="reeneble()" class="speed-select"
|
||||
aria-label="Speed selector">
|
||||
<mat-option [value]="speedValue" *ngFor="let speedValue of speeds">{{speedValue}} </mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
<div class="panel-timer">
|
||||
<span *ngIf="this.intervals[this.index]">{{ this.intervals[this.index] | date:'medium'}}</span>
|
||||
<span *ngIf="!this.intervals[this.index]">{{ "widget.no-data-found" | translate}}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -13,128 +13,120 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
.trip-animation-widget {
|
||||
|
||||
.trip-animation-label-container {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.trip-animation-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
|
||||
.trip-animation-label-container {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.trip-animation-container {
|
||||
position: relative;
|
||||
#trip-animation-map {
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
#trip-animation-map {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.pointsLayerMarkerIcon {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.pointsLayerMarkerIcon {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.trip-animation-info-panel {
|
||||
position: absolute;
|
||||
.trip-animation-info-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
|
||||
.mat-button {
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
left: 0;
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
padding: 0 0 2px;
|
||||
margin: 2px;
|
||||
line-height: 24px;
|
||||
|
||||
.md-button {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
padding: 0 0 2px;
|
||||
margin: 2px;
|
||||
line-height: 24px;
|
||||
ng-mat-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
ng-md-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
svg {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
svg {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trip-animation-tooltip {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
transition: 0.3s ease-in-out;
|
||||
|
||||
&-hidden {
|
||||
transform: translateX(110%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trip-animation-control-panel {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
padding-left: 10px;
|
||||
.trip-animation-tooltip {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
transition: 0.3s ease-in-out;
|
||||
|
||||
md-slider-container {
|
||||
md-slider {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
button.md-button.md-icon-button {
|
||||
width: 44px;
|
||||
min-width: 44px;
|
||||
height: 44px;
|
||||
min-height: 44px;
|
||||
margin: 0;
|
||||
line-height: 28px;
|
||||
|
||||
md-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 28px;
|
||||
|
||||
svg {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
md-select {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-timer {
|
||||
max-width: none;
|
||||
padding-right: 250px;
|
||||
padding-left: 90px;
|
||||
margin-top: -20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
&-hidden {
|
||||
transform: translateX(110%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.speed-select{
|
||||
.trip-animation-control-panel {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
padding-left: 10px;
|
||||
|
||||
mat-slider-container {
|
||||
mat-slider {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
button.mat-button.mat-icon-button {
|
||||
width: 44px;
|
||||
min-width: 44px;
|
||||
height: 44px;
|
||||
min-height: 44px;
|
||||
margin: 0;
|
||||
line-height: 28px;
|
||||
|
||||
mat-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 28px;
|
||||
|
||||
svg {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mat-select {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-timer {
|
||||
max-width: none;
|
||||
margin-top: -20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.speed-select {
|
||||
width: 50px;
|
||||
margin-left: 20px;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@
|
||||
///
|
||||
|
||||
import { Component, OnInit, OnChanges, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
||||
import { interval, Subscription, Subscriber, SubscriptionLike, Observer } from 'rxjs';
|
||||
import { filter, tap } from 'rxjs/operators';
|
||||
import { interval } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { HistorySelectSettings } from '@app/modules/home/components/widget/lib/maps/map-models';
|
||||
|
||||
@Component({
|
||||
|
||||
@ -15,11 +15,11 @@
|
||||
///
|
||||
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { parseTemplate } from '@app/core/utils';
|
||||
import { parseTemplate, parseWithTranslation } from '@app/core/utils';
|
||||
|
||||
@Pipe({ name: 'tbParseTemplate' })
|
||||
export class TbTemplatePipe implements PipeTransform {
|
||||
transform(template, data): string {
|
||||
return parseTemplate(template, data);
|
||||
return parseWithTranslation.parseTemplate(template, data);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user