UI: Improve image map position conversion function
This commit is contained in:
parent
fe5ec14871
commit
9bb4497f58
@ -188,7 +188,7 @@ export default abstract class LeafletMap {
|
|||||||
entities = this.datasources.filter(pData => !this.isValidPolygonPosition(pData));
|
entities = this.datasources.filter(pData => !this.isValidPolygonPosition(pData));
|
||||||
break;
|
break;
|
||||||
case 'Marker':
|
case 'Marker':
|
||||||
entities = this.datasources.filter(mData => !this.convertPosition(mData));
|
entities = this.datasources.filter(mData => !this.extractPosition(mData));
|
||||||
break;
|
break;
|
||||||
case 'Circle':
|
case 'Circle':
|
||||||
entities = this.datasources.filter(mData => !this.isValidCircle(mData));
|
entities = this.datasources.filter(mData => !this.isValidCircle(mData));
|
||||||
@ -616,16 +616,29 @@ export default abstract class LeafletMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
convertPosition(expression: object): L.LatLng {
|
extractPosition(data: FormattedData): {x: number, y: number} {
|
||||||
if (!expression) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const lat = expression[this.options.latKeyName];
|
const lat = data[this.options.latKeyName];
|
||||||
const lng = expression[this.options.lngKeyName];
|
const lng = data[this.options.lngKeyName];
|
||||||
if (!isDefinedAndNotNull(lat) || isString(lat) || isNaN(lat) || !isDefinedAndNotNull(lng) || isString(lng) || isNaN(lng)) {
|
if (!isDefinedAndNotNull(lat) || isString(lat) || isNaN(lat) || !isDefinedAndNotNull(lng) || isString(lng) || isNaN(lng)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return L.latLng(lat, lng) as L.LatLng;
|
return {x: lat, y: lng};
|
||||||
|
}
|
||||||
|
|
||||||
|
positionToLatLng(position: {x: number, y: number}): L.LatLng {
|
||||||
|
return L.latLng(position.x, position.y) as L.LatLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertPosition(data: FormattedData, dsData: FormattedData[]): L.LatLng {
|
||||||
|
const position = this.extractPosition(data);
|
||||||
|
if (position) {
|
||||||
|
return this.positionToLatLng(position);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
convertPositionPolygon(expression: (LatLngTuple | LatLngTuple[] | LatLngTuple[][])[]) {
|
convertPositionPolygon(expression: (LatLngTuple | LatLngTuple[] | LatLngTuple[][])[]) {
|
||||||
@ -707,7 +720,7 @@ export default abstract class LeafletMap {
|
|||||||
if (this.options.draggableMarker && !this.options.hideDrawControlButton && !this.options.hideAllControlButton) {
|
if (this.options.draggableMarker && !this.options.hideDrawControlButton && !this.options.hideAllControlButton) {
|
||||||
let foundEntityWithoutLocation = false;
|
let foundEntityWithoutLocation = false;
|
||||||
for (const mData of formattedData) {
|
for (const mData of formattedData) {
|
||||||
const position = this.convertPosition(mData);
|
const position = this.extractPosition(mData);
|
||||||
if (!position) {
|
if (!position) {
|
||||||
foundEntityWithoutLocation = true;
|
foundEntityWithoutLocation = true;
|
||||||
} else if (!!position) {
|
} else if (!!position) {
|
||||||
@ -836,7 +849,7 @@ export default abstract class LeafletMap {
|
|||||||
|
|
||||||
// Markers
|
// Markers
|
||||||
updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) {
|
updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) {
|
||||||
const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata));
|
const rawMarkers = markersData.filter(mdata => !!this.extractPosition(mdata));
|
||||||
const toDelete = new Set(Array.from(this.markers.keys()));
|
const toDelete = new Set(Array.from(this.markers.keys()));
|
||||||
const createdMarkers: Marker[] = [];
|
const createdMarkers: Marker[] = [];
|
||||||
const updatedMarkers: Marker[] = [];
|
const updatedMarkers: Marker[] = [];
|
||||||
@ -900,7 +913,7 @@ export default abstract class LeafletMap {
|
|||||||
|
|
||||||
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>,
|
private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>,
|
||||||
updateBounds = true, callback?, snappable = false): Marker {
|
updateBounds = true, callback?, snappable = false): Marker {
|
||||||
const newMarker = new Marker(this, this.convertPosition(data), settings, data, dataSources, this.dragMarker, snappable);
|
const newMarker = new Marker(this, this.convertPosition(data, dataSources), settings, data, dataSources, this.dragMarker, snappable);
|
||||||
if (callback) {
|
if (callback) {
|
||||||
newMarker.leafletMarker.on('click', () => {
|
newMarker.leafletMarker.on('click', () => {
|
||||||
callback(data, true);
|
callback(data, true);
|
||||||
@ -921,7 +934,7 @@ export default abstract class LeafletMap {
|
|||||||
|
|
||||||
private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>): Marker {
|
private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: Partial<WidgetMarkersSettings>): Marker {
|
||||||
const marker: Marker = this.markers.get(key);
|
const marker: Marker = this.markers.get(key);
|
||||||
const location = this.convertPosition(data);
|
const location = this.convertPosition(data, dataSources);
|
||||||
marker.updateMarkerPosition(location);
|
marker.updateMarkerPosition(location);
|
||||||
marker.setDataSources(data, dataSources);
|
marker.setDataSources(data, dataSources);
|
||||||
if (settings.showTooltip) {
|
if (settings.showTooltip) {
|
||||||
@ -964,12 +977,12 @@ export default abstract class LeafletMap {
|
|||||||
for (const pointsList of pointsData) {
|
for (const pointsList of pointsData) {
|
||||||
for (let tsIndex = 0; tsIndex < pointsList.length; tsIndex++) {
|
for (let tsIndex = 0; tsIndex < pointsList.length; tsIndex++) {
|
||||||
const pdata = pointsList[tsIndex];
|
const pdata = pointsList[tsIndex];
|
||||||
if (!!this.convertPosition(pdata)) {
|
if (!!this.extractPosition(pdata)) {
|
||||||
const dsData = pointsData.map(ds => ds[tsIndex]);
|
const dsData = pointsData.map(ds => ds[tsIndex]);
|
||||||
if (this.options.useColorPointFunction) {
|
if (this.options.useColorPointFunction) {
|
||||||
pointColor = safeExecute(this.options.parsedColorPointFunction, [pdata, dsData, pdata.dsIndex]);
|
pointColor = safeExecute(this.options.parsedColorPointFunction, [pdata, dsData, pdata.dsIndex]);
|
||||||
}
|
}
|
||||||
const point = L.circleMarker(this.convertPosition(pdata), {
|
const point = L.circleMarker(this.convertPosition(pdata, dsData), {
|
||||||
color: pointColor,
|
color: pointColor,
|
||||||
radius: this.options.pointSize
|
radius: this.options.pointSize
|
||||||
});
|
});
|
||||||
@ -1017,7 +1030,7 @@ export default abstract class LeafletMap {
|
|||||||
createPolyline(data: FormattedData, tsData: FormattedData[], dsData: FormattedData[],
|
createPolyline(data: FormattedData, tsData: FormattedData[], dsData: FormattedData[],
|
||||||
settings: Partial<WidgetPolylineSettings>, updateBounds = true) {
|
settings: Partial<WidgetPolylineSettings>, updateBounds = true) {
|
||||||
const poly = new Polyline(this.map,
|
const poly = new Polyline(this.map,
|
||||||
tsData.map(el => this.convertPosition(el)).filter(el => !!el), data, dsData, settings);
|
tsData.map(el => this.extractPosition(el)).filter(el => !!el).map(el => this.positionToLatLng(el)), data, dsData, settings);
|
||||||
if (updateBounds) {
|
if (updateBounds) {
|
||||||
const bounds = poly.leafletPoly.getBounds();
|
const bounds = poly.leafletPoly.getBounds();
|
||||||
this.fitBounds(bounds);
|
this.fitBounds(bounds);
|
||||||
@ -1029,7 +1042,8 @@ export default abstract class LeafletMap {
|
|||||||
settings: Partial<WidgetPolylineSettings>, updateBounds = true) {
|
settings: Partial<WidgetPolylineSettings>, updateBounds = true) {
|
||||||
const poly = this.polylines.get(data.entityName);
|
const poly = this.polylines.get(data.entityName);
|
||||||
const oldBounds = poly.leafletPoly.getBounds();
|
const oldBounds = poly.leafletPoly.getBounds();
|
||||||
poly.updatePolyline(tsData.map(el => this.convertPosition(el)).filter(el => !!el), data, dsData, settings);
|
poly.updatePolyline(tsData.map(el => this.extractPosition(el)).filter(el => !!el)
|
||||||
|
.map(el => this.positionToLatLng(el)), data, dsData, settings);
|
||||||
const newBounds = poly.leafletPoly.getBounds();
|
const newBounds = poly.leafletPoly.getBounds();
|
||||||
if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
|
if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
|
||||||
this.fitBounds(newBounds);
|
this.fitBounds(newBounds);
|
||||||
|
|||||||
@ -48,8 +48,10 @@ export interface CircleData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
|
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
|
||||||
export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => MarkerImageInfo;
|
export type MarkerImageFunction = (data: FormattedData, markerImages: string[],
|
||||||
export type PosFuncton = (origXPos, origYPos) => { x, y };
|
dsData: FormattedData[], dsIndex: number) => MarkerImageInfo;
|
||||||
|
export type PosFunction = (origXPos, origYPos, data: FormattedData,
|
||||||
|
dsData: FormattedData[], dsIndex: number, aspect: number) => { x: number, y: number };
|
||||||
export type MarkerIconReadyFunction = (icon: MarkerIconInfo) => void;
|
export type MarkerIconReadyFunction = (icon: MarkerIconInfo) => void;
|
||||||
|
|
||||||
export enum GoogleMapType {
|
export enum GoogleMapType {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import {
|
|||||||
CircleData,
|
CircleData,
|
||||||
defaultImageMapProviderSettings,
|
defaultImageMapProviderSettings,
|
||||||
MapImage,
|
MapImage,
|
||||||
PosFuncton,
|
PosFunction,
|
||||||
WidgetUnitedMapSettings
|
WidgetUnitedMapSettings
|
||||||
} from '../map-models';
|
} from '../map-models';
|
||||||
import { Observable, ReplaySubject } from 'rxjs';
|
import { Observable, ReplaySubject } from 'rxjs';
|
||||||
@ -30,7 +30,7 @@ import {
|
|||||||
calculateNewPointCoordinate
|
calculateNewPointCoordinate
|
||||||
} from '@home/components/widget/lib/maps/common-maps-utils';
|
} from '@home/components/widget/lib/maps/common-maps-utils';
|
||||||
import { WidgetContext } from '@home/models/widget-component.models';
|
import { WidgetContext } from '@home/models/widget-component.models';
|
||||||
import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models';
|
import { DataSet, DatasourceType, FormattedData, widgetType } from '@shared/models/widget.models';
|
||||||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
||||||
import { WidgetSubscriptionOptions } from '@core/api/widget-api.models';
|
import { WidgetSubscriptionOptions } from '@core/api/widget-api.models';
|
||||||
import { isDefinedAndNotNull, isEmptyStr, isNotEmptyStr, parseFunction } from '@core/utils';
|
import { isDefinedAndNotNull, isEmptyStr, isNotEmptyStr, parseFunction } from '@core/utils';
|
||||||
@ -45,11 +45,12 @@ export class ImageMap extends LeafletMap {
|
|||||||
width = 0;
|
width = 0;
|
||||||
height = 0;
|
height = 0;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
posFunction: PosFuncton;
|
posFunction: PosFunction;
|
||||||
|
|
||||||
constructor(ctx: WidgetContext, $container: HTMLElement, options: WidgetUnitedMapSettings) {
|
constructor(ctx: WidgetContext, $container: HTMLElement, options: WidgetUnitedMapSettings) {
|
||||||
super(ctx, $container, options);
|
super(ctx, $container, options);
|
||||||
this.posFunction = parseFunction(options.posFunction, ['origXPos', 'origYPos']) as PosFuncton;
|
this.posFunction = parseFunction(options.posFunction,
|
||||||
|
['origXPos', 'origYPos', 'data', 'dsData', 'dsIndex', 'aspect']) as PosFunction;
|
||||||
this.mapImage(options).subscribe((mapImage) => {
|
this.mapImage(options).subscribe((mapImage) => {
|
||||||
this.imageUrl = mapImage.imageUrl;
|
this.imageUrl = mapImage.imageUrl;
|
||||||
this.aspect = mapImage.aspect;
|
this.aspect = mapImage.aspect;
|
||||||
@ -248,16 +249,32 @@ export class ImageMap extends LeafletMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
convertPosition(expression): L.LatLng {
|
extractPosition(data: FormattedData): {x: number, y: number} {
|
||||||
const xPos = expression[this.options.xPosKeyName];
|
if (!data) {
|
||||||
const yPos = expression[this.options.yPosKeyName];
|
return null;
|
||||||
|
}
|
||||||
|
const xPos = data[this.options.xPosKeyName];
|
||||||
|
const yPos = data[this.options.yPosKeyName];
|
||||||
if (!isDefinedAndNotNull(xPos) || isEmptyStr(xPos) || isNaN(xPos) || !isDefinedAndNotNull(yPos) || isEmptyStr(yPos) || isNaN(yPos)) {
|
if (!isDefinedAndNotNull(xPos) || isEmptyStr(xPos) || isNaN(xPos) || !isDefinedAndNotNull(yPos) || isEmptyStr(yPos) || isNaN(yPos)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Object.assign(expression, this.posFunction(xPos, yPos));
|
return {x: xPos, y: yPos};
|
||||||
|
}
|
||||||
|
|
||||||
|
positionToLatLng(position: {x: number, y: number}): L.LatLng {
|
||||||
return this.pointToLatLng(
|
return this.pointToLatLng(
|
||||||
expression.x * this.width,
|
position.x * this.width,
|
||||||
expression.y * this.height);
|
position.y * this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
convertPosition(data, dsData: FormattedData[]): L.LatLng {
|
||||||
|
const position = this.extractPosition(data);
|
||||||
|
if (position) {
|
||||||
|
const converted = this.posFunction(position.x, position.y, data, dsData, data.dsIndex, this.aspect) || {x: 0, y: 0};
|
||||||
|
return this.positionToLatLng(converted);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
convertPositionPolygon(expression: (LatLngTuple | LatLngTuple[] | LatLngTuple[][])[]){
|
convertPositionPolygon(expression: (LatLngTuple | LatLngTuple[] | LatLngTuple[][])[]){
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
formControlName="posFunction"
|
formControlName="posFunction"
|
||||||
minHeight="100px"
|
minHeight="100px"
|
||||||
[globalVariables]="functionScopeVariables"
|
[globalVariables]="functionScopeVariables"
|
||||||
[functionArgs]="['origXPos', 'origYPos']"
|
[functionArgs]="['origXPos', 'origYPos', 'data', 'dsData', 'dsIndex', 'aspect']"
|
||||||
functionTitle="{{ 'widgets.maps.position-function' | translate }}"
|
functionTitle="{{ 'widgets.maps.position-function' | translate }}"
|
||||||
helpId="widget/lib/map/position_fn">
|
helpId="widget/lib/map/position_fn">
|
||||||
</tb-js-func>
|
</tb-js-func>
|
||||||
|
|||||||
@ -3,14 +3,18 @@
|
|||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
*function (origXPos, origYPos): {x: number, y: number}*
|
*function (origXPos, origYPos, data, dsData, dsIndex, aspect): {x: number, y: number}*
|
||||||
|
|
||||||
A JavaScript function used to convert original relative x, y coordinates of the marker.
|
A JavaScript function used to convert original relative x, y coordinates of the marker.
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
|
||||||
- **origXPos:** <code>number</code> - original relative x coordinate as double from 0 to 1;
|
<ul>
|
||||||
- **origYPos:** <code>number</code> - original relative y coordinate as double from 0 to 1;
|
<li><b>origXPos:</b> <code>number</code> - original relative x coordinate as double from 0 to 1.</li>
|
||||||
|
<li><b>origYPos:</b> <code>number</code> - original relative y coordinate as double from 0 to 1.</li>
|
||||||
|
{% include widget/lib/map/map_fn_args %}
|
||||||
|
<li><b>aspect:</b> <code>number</code> - image map aspect ratio.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
@ -37,5 +41,25 @@ return {x: origXPos / 2, y: origYPos / 2};
|
|||||||
{:copy-code}
|
{:copy-code}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* Detect markers with same positions and place them with minimum overlap:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var xPos = data.xPos;
|
||||||
|
var yPos = data.yPos;
|
||||||
|
var locationGroup = dsData.filter((item) => item.xPos === xPos && item.yPos === yPos);
|
||||||
|
if (locationGroup.length > 1) {
|
||||||
|
const count = locationGroup.length;
|
||||||
|
const index = locationGroup.indexOf(data);
|
||||||
|
const radius = 0.035;
|
||||||
|
const angle = (360 / count) * index - 45;
|
||||||
|
const x = xPos + radius * Math.sin(angle*Math.PI/180) / aspect;
|
||||||
|
const y = yPos + radius * Math.cos(angle*Math.PI/180);
|
||||||
|
return {x: x, y: y};
|
||||||
|
} else {
|
||||||
|
return {x: xPos, y: yPos};
|
||||||
|
}
|
||||||
|
{:copy-code}
|
||||||
|
```
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user