mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Geomap: Fix route layer zoom behavior (#63409)
* Geomap: Fix Zoom Bug * Add handling for case where no segments created * Simplify segment checks and pull logic into utils * Rename pixel variables * Roll back change to raw data response json
This commit is contained in:
parent
adc8000b72
commit
1aadafe7d8
@ -11,7 +11,7 @@ import Map from 'ol/Map';
|
|||||||
import VectorLayer from 'ol/layer/Vector';
|
import VectorLayer from 'ol/layer/Vector';
|
||||||
import VectorSource from 'ol/source/Vector';
|
import VectorSource from 'ol/source/Vector';
|
||||||
import { Fill, Stroke, Style, Circle } from 'ol/style';
|
import { Fill, Stroke, Style, Circle } from 'ol/style';
|
||||||
import {Group as LayerGroup} from 'ol/layer';
|
import { Group as LayerGroup } from 'ol/layer';
|
||||||
import Feature from 'ol/Feature';
|
import Feature from 'ol/Feature';
|
||||||
import Point from 'ol/geom/Point';
|
import Point from 'ol/geom/Point';
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ export interface DayNightConfig {
|
|||||||
const defaultConfig: DayNightConfig = {
|
const defaultConfig: DayNightConfig = {
|
||||||
show: ShowTime.To,
|
show: ShowTime.To,
|
||||||
sun: false,
|
sun: false,
|
||||||
nightColor: '#a7a6ba4D'
|
nightColor: '#a7a6ba4D',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DAY_NIGHT_LAYER_ID = 'dayNight';
|
export const DAY_NIGHT_LAYER_ID = 'dayNight';
|
||||||
@ -72,9 +72,9 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// DayNight source
|
// DayNight source
|
||||||
const source = new DayNight({ });
|
const source = new DayNight({});
|
||||||
const sourceMethods = Object.getPrototypeOf(source);
|
const sourceMethods = Object.getPrototypeOf(source);
|
||||||
const sourceLine = new DayNight({ });
|
const sourceLine = new DayNight({});
|
||||||
const sourceLineMethods = Object.getPrototypeOf(sourceLine);
|
const sourceLineMethods = Object.getPrototypeOf(sourceLine);
|
||||||
|
|
||||||
// Night polygon
|
// Night polygon
|
||||||
@ -82,9 +82,9 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
source,
|
source,
|
||||||
style: new Style({
|
style: new Style({
|
||||||
fill: new Fill({
|
fill: new Fill({
|
||||||
color: theme.visualization.getColorByName(config.nightColor)
|
color: theme.visualization.getColorByName(config.nightColor),
|
||||||
})
|
}),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Night line (for crosshair sharing)
|
// Night line (for crosshair sharing)
|
||||||
@ -92,12 +92,12 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
source: new VectorSource({
|
source: new VectorSource({
|
||||||
features: [],
|
features: [],
|
||||||
}),
|
}),
|
||||||
style: new Style ({
|
style: new Style({
|
||||||
stroke: new Stroke({
|
stroke: new Stroke({
|
||||||
color: '#607D8B',
|
color: '#607D8B',
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
lineDash: [2, 3],
|
lineDash: [2, 3],
|
||||||
})
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,8 +113,8 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
style: new Style({
|
style: new Style({
|
||||||
image: new Circle({
|
image: new Circle({
|
||||||
radius: 13,
|
radius: 13,
|
||||||
fill: new Fill({color: 'rgb(253,184,19)'}),
|
fill: new Fill({ color: 'rgb(253,184,19)' }),
|
||||||
})
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,9 +128,9 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
radius: 13,
|
radius: 13,
|
||||||
stroke: new Stroke({
|
stroke: new Stroke({
|
||||||
color: 'rgb(253,184,19)',
|
color: 'rgb(253,184,19)',
|
||||||
width: 1.5
|
width: 1.5,
|
||||||
})
|
}),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const sunLineStyleDash = new Style({
|
const sunLineStyleDash = new Style({
|
||||||
@ -139,9 +139,9 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
stroke: new Stroke({
|
stroke: new Stroke({
|
||||||
color: '#607D8B',
|
color: '#607D8B',
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
lineDash: [2,3]
|
lineDash: [2, 3],
|
||||||
})
|
}),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const sunLineLayer = new VectorLayer({
|
const sunLineLayer = new VectorLayer({
|
||||||
@ -154,7 +154,7 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
// Build group of layers
|
// Build group of layers
|
||||||
// TODO: add blended night region to "connect" current night region to lines
|
// TODO: add blended night region to "connect" current night region to lines
|
||||||
const layer = new LayerGroup({
|
const layer = new LayerGroup({
|
||||||
layers: config.sun? [vectorLayer, sunLayer, sunLineLayer, nightLineLayer] : [vectorLayer, nightLineLayer]
|
layers: config.sun ? [vectorLayer, sunLayer, sunLineLayer, nightLineLayer] : [vectorLayer, nightLineLayer],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Crosshair sharing subscriptions
|
// Crosshair sharing subscriptions
|
||||||
@ -168,18 +168,20 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
const lineTime = new Date(time);
|
const lineTime = new Date(time);
|
||||||
const nightLinePoints = sourceLine.getCoordinates(lineTime.toString(), 'line');
|
const nightLinePoints = sourceLine.getCoordinates(lineTime.toString(), 'line');
|
||||||
nightLineLayer.getSource()?.clear();
|
nightLineLayer.getSource()?.clear();
|
||||||
const lineStringArray:Coordinate[][] = [];
|
const lineStringArray: Coordinate[][] = [];
|
||||||
for (let l = 0; l < nightLinePoints.length - 1; l++){
|
for (let l = 0; l < nightLinePoints.length - 1; l++) {
|
||||||
const x1:number = Object.values(nightLinePoints[l])[0];
|
const x1: number = Object.values(nightLinePoints[l])[0];
|
||||||
const y1:number = Object.values(nightLinePoints[l])[1];
|
const y1: number = Object.values(nightLinePoints[l])[1];
|
||||||
const x2:number = Object.values(nightLinePoints[l+1])[0];
|
const x2: number = Object.values(nightLinePoints[l + 1])[0];
|
||||||
const y2:number = Object.values(nightLinePoints[l+1])[1];
|
const y2: number = Object.values(nightLinePoints[l + 1])[1];
|
||||||
const lineString = [fromLonLat([x1, y1]),fromLonLat([x2, y2])];
|
const lineString = [fromLonLat([x1, y1]), fromLonLat([x2, y2])];
|
||||||
lineStringArray.push(lineString);
|
lineStringArray.push(lineString);
|
||||||
}
|
}
|
||||||
nightLineLayer.getSource()?.addFeature(new Feature({
|
nightLineLayer.getSource()?.addFeature(
|
||||||
|
new Feature({
|
||||||
geometry: new MultiLineString(lineStringArray),
|
geometry: new MultiLineString(lineStringArray),
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
|
|
||||||
let sunLinePos: number[] = [];
|
let sunLinePos: number[] = [];
|
||||||
sunLinePos = sourceLineMethods.getSunPosition(lineTime);
|
sunLinePos = sourceLineMethods.getSunPosition(lineTime);
|
||||||
@ -206,14 +208,14 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
let selectedTime: Date = new Date();
|
let selectedTime: Date = new Date();
|
||||||
let sunPos: number[] = [];
|
let sunPos: number[] = [];
|
||||||
// TODO: add option for "Both"
|
// TODO: add option for "Both"
|
||||||
if (config.show === ShowTime.From){
|
if (config.show === ShowTime.From) {
|
||||||
selectedTime = from;
|
selectedTime = from;
|
||||||
} else {
|
} else {
|
||||||
selectedTime = to;
|
selectedTime = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
source.setTime(selectedTime);
|
source.setTime(selectedTime);
|
||||||
if (config.sun){
|
if (config.sun) {
|
||||||
sunPos = sourceMethods.getSunPosition(selectedTime);
|
sunPos = sourceMethods.getSunPosition(selectedTime);
|
||||||
sunFeature.getGeometry()?.setCoordinates(fromLonLat(sunPos));
|
sunFeature.getGeometry()?.setCoordinates(fromLonLat(sunPos));
|
||||||
}
|
}
|
||||||
@ -221,12 +223,11 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
|
|
||||||
// Marker overlay options
|
// Marker overlay options
|
||||||
registerOptionsUI: (builder) => {
|
registerOptionsUI: (builder) => {
|
||||||
if(!options.config?.nightColor) {
|
if (!options.config?.nightColor) {
|
||||||
options.config = { ...defaultConfig, ...options.config}
|
options.config = { ...defaultConfig, ...options.config };
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder.addRadio({
|
||||||
.addRadio({
|
|
||||||
path: 'config.show',
|
path: 'config.show',
|
||||||
name: 'Show',
|
name: 'Show',
|
||||||
settings: {
|
settings: {
|
||||||
@ -242,7 +243,7 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
|||||||
name: 'Night region color',
|
name: 'Night region color',
|
||||||
description: 'Pick color of night region',
|
description: 'Pick color of night region',
|
||||||
defaultValue: defaultConfig.nightColor,
|
defaultValue: defaultConfig.nightColor,
|
||||||
settings: [{enableNamedColors: false}],
|
settings: [{ enableNamedColors: false }],
|
||||||
});
|
});
|
||||||
builder.addBooleanSwitch({
|
builder.addBooleanSwitch({
|
||||||
path: 'config.sun',
|
path: 'config.sun',
|
||||||
|
@ -10,10 +10,7 @@ import {
|
|||||||
TIME_SERIES_TIME_FIELD_NAME,
|
TIME_SERIES_TIME_FIELD_NAME,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
import {
|
import { MapLayerOptions, FrameGeometrySourceMode } from '@grafana/schema';
|
||||||
MapLayerOptions,
|
|
||||||
FrameGeometrySourceMode,
|
|
||||||
} from '@grafana/schema';
|
|
||||||
|
|
||||||
import Map from 'ol/Map';
|
import Map from 'ol/Map';
|
||||||
import { FeatureLike } from 'ol/Feature';
|
import { FeatureLike } from 'ol/Feature';
|
||||||
@ -31,10 +28,10 @@ import VectorSource from 'ol/source/Vector';
|
|||||||
import { Fill, Stroke, Style, Circle } from 'ol/style';
|
import { Fill, Stroke, Style, Circle } from 'ol/style';
|
||||||
import Feature from 'ol/Feature';
|
import Feature from 'ol/Feature';
|
||||||
import { alpha } from '@grafana/data/src/themes/colorManipulator';
|
import { alpha } from '@grafana/data/src/themes/colorManipulator';
|
||||||
import { LineString, SimpleGeometry } from 'ol/geom';
|
import { LineString, Point, SimpleGeometry } from 'ol/geom';
|
||||||
import FlowLine from 'ol-ext/style/FlowLine';
|
import FlowLine from 'ol-ext/style/FlowLine';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { getStyleDimension } from '../../utils/utils';
|
import { getStyleDimension, isSegmentVisible } from '../../utils/utils';
|
||||||
|
|
||||||
// Configuration options for Circle overlays
|
// Configuration options for Circle overlays
|
||||||
export interface RouteConfig {
|
export interface RouteConfig {
|
||||||
@ -114,9 +111,13 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
|
|||||||
if (geom instanceof SimpleGeometry) {
|
if (geom instanceof SimpleGeometry) {
|
||||||
const coordinates = geom.getCoordinates();
|
const coordinates = geom.getCoordinates();
|
||||||
if (coordinates) {
|
if (coordinates) {
|
||||||
|
let startIndex = 0; // Index start for segment optimization
|
||||||
|
const pixelTolerance = 2; // For segment to be visible, it must be > 2 pixels (due to round ends)
|
||||||
for (let i = 0; i < coordinates.length - 1; i++) {
|
for (let i = 0; i < coordinates.length - 1; i++) {
|
||||||
|
const segmentStartCoords = coordinates[startIndex];
|
||||||
|
const segmentEndCoords = coordinates[i + 1];
|
||||||
const color1 = tinycolor(
|
const color1 = tinycolor(
|
||||||
theme.visualization.getColorByName((dims.color && dims.color.get(i)) ?? style.base.color)
|
theme.visualization.getColorByName((dims.color && dims.color.get(startIndex)) ?? style.base.color)
|
||||||
)
|
)
|
||||||
.setAlpha(opacity)
|
.setAlpha(opacity)
|
||||||
.toString();
|
.toString();
|
||||||
@ -126,7 +127,7 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
|
|||||||
.setAlpha(opacity)
|
.setAlpha(opacity)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
const arrowSize1 = (dims.size && dims.size.get(i)) ?? style.base.size;
|
const arrowSize1 = (dims.size && dims.size.get(startIndex)) ?? style.base.size;
|
||||||
const arrowSize2 = (dims.size && dims.size.get(i + 1)) ?? style.base.size;
|
const arrowSize2 = (dims.size && dims.size.get(i + 1)) ?? style.base.size;
|
||||||
|
|
||||||
const flowStyle = new FlowLine({
|
const flowStyle = new FlowLine({
|
||||||
@ -134,7 +135,7 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
|
|||||||
lineCap: config.arrow == 0 ? 'round' : 'square',
|
lineCap: config.arrow == 0 ? 'round' : 'square',
|
||||||
color: color1,
|
color: color1,
|
||||||
color2: color2,
|
color2: color2,
|
||||||
width: (dims.size && dims.size.get(i)) ?? style.base.size,
|
width: (dims.size && dims.size.get(startIndex)) ?? style.base.size,
|
||||||
width2: (dims.size && dims.size.get(i + 1)) ?? style.base.size,
|
width2: (dims.size && dims.size.get(i + 1)) ?? style.base.size,
|
||||||
});
|
});
|
||||||
if (config.arrow) {
|
if (config.arrow) {
|
||||||
@ -147,9 +148,33 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
|
|||||||
flowStyle.setArrowSize((arrowSize1 ?? 0) * 1.5);
|
flowStyle.setArrowSize((arrowSize1 ?? 0) * 1.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const LS = new LineString([coordinates[i], coordinates[i + 1]]);
|
// Only render segment if change in pixel coordinates is significant enough
|
||||||
|
if (isSegmentVisible(map, pixelTolerance, segmentStartCoords, segmentEndCoords)) {
|
||||||
|
const LS = new LineString([segmentStartCoords, segmentEndCoords]);
|
||||||
flowStyle.setGeometry(LS);
|
flowStyle.setGeometry(LS);
|
||||||
styles.push(flowStyle);
|
styles.push(flowStyle);
|
||||||
|
startIndex = i + 1; // Because a segment was created, move onto the next one
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no segments created, render a single point
|
||||||
|
if (styles.length === 0) {
|
||||||
|
const P = new Point(coordinates[0]);
|
||||||
|
const radius = ((dims.size && dims.size.get(0)) ?? style.base.size ?? 10) / 2;
|
||||||
|
const color = tinycolor(
|
||||||
|
theme.visualization.getColorByName((dims.color && dims.color.get(0)) ?? style.base.color)
|
||||||
|
)
|
||||||
|
.setAlpha(opacity)
|
||||||
|
.toString();
|
||||||
|
const ZoomOutCircle = new Style({
|
||||||
|
image: new Circle({
|
||||||
|
radius: radius,
|
||||||
|
fill: new Fill({
|
||||||
|
color: color,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
ZoomOutCircle.setGeometry(P);
|
||||||
|
styles.push(ZoomOutCircle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return styles;
|
return styles;
|
||||||
|
@ -117,6 +117,25 @@ export const getNextLayerName = (panel: GeomapPanel) => {
|
|||||||
return `Layer ${Date.now()}`;
|
return `Layer ${Date.now()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isSegmentVisible(
|
||||||
|
map: OpenLayersMap,
|
||||||
|
pixelTolerance: number,
|
||||||
|
segmentStartCoords: number[],
|
||||||
|
segmentEndCoords: number[]
|
||||||
|
): boolean {
|
||||||
|
// For a segment, calculate x and y pixel lengths
|
||||||
|
//TODO: let's try to find a less intensive check
|
||||||
|
const pixelStart = map.getPixelFromCoordinate(segmentStartCoords);
|
||||||
|
const pixelEnd = map.getPixelFromCoordinate(segmentEndCoords);
|
||||||
|
const deltaX = Math.abs(pixelStart[0] - pixelEnd[0]);
|
||||||
|
const deltaY = Math.abs(pixelStart[1] - pixelEnd[1]);
|
||||||
|
// If greater than pixel tolerance in either direction, segment is visible
|
||||||
|
if (deltaX > pixelTolerance || deltaY > pixelTolerance) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export const isUrl = (url: string) => {
|
export const isUrl = (url: string) => {
|
||||||
try {
|
try {
|
||||||
const newUrl = new URL(url);
|
const newUrl = new URL(url);
|
||||||
|
Loading…
Reference in New Issue
Block a user