mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -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 VectorSource from 'ol/source/Vector';
|
||||
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 Point from 'ol/geom/Point';
|
||||
|
||||
@ -36,7 +36,7 @@ export interface DayNightConfig {
|
||||
const defaultConfig: DayNightConfig = {
|
||||
show: ShowTime.To,
|
||||
sun: false,
|
||||
nightColor: '#a7a6ba4D'
|
||||
nightColor: '#a7a6ba4D',
|
||||
};
|
||||
|
||||
export const DAY_NIGHT_LAYER_ID = 'dayNight';
|
||||
@ -72,9 +72,9 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
};
|
||||
|
||||
// DayNight source
|
||||
const source = new DayNight({ });
|
||||
const source = new DayNight({});
|
||||
const sourceMethods = Object.getPrototypeOf(source);
|
||||
const sourceLine = new DayNight({ });
|
||||
const sourceLine = new DayNight({});
|
||||
const sourceLineMethods = Object.getPrototypeOf(sourceLine);
|
||||
|
||||
// Night polygon
|
||||
@ -82,9 +82,9 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
source,
|
||||
style: new Style({
|
||||
fill: new Fill({
|
||||
color: theme.visualization.getColorByName(config.nightColor)
|
||||
})
|
||||
})
|
||||
color: theme.visualization.getColorByName(config.nightColor),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Night line (for crosshair sharing)
|
||||
@ -92,12 +92,12 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
source: new VectorSource({
|
||||
features: [],
|
||||
}),
|
||||
style: new Style ({
|
||||
style: new Style({
|
||||
stroke: new Stroke({
|
||||
color: '#607D8B',
|
||||
width: 1.5,
|
||||
lineDash: [2, 3],
|
||||
})
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -113,8 +113,8 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
style: new Style({
|
||||
image: new Circle({
|
||||
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,
|
||||
stroke: new Stroke({
|
||||
color: 'rgb(253,184,19)',
|
||||
width: 1.5
|
||||
})
|
||||
})
|
||||
width: 1.5,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const sunLineStyleDash = new Style({
|
||||
@ -139,9 +139,9 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
stroke: new Stroke({
|
||||
color: '#607D8B',
|
||||
width: 1.5,
|
||||
lineDash: [2,3]
|
||||
})
|
||||
})
|
||||
lineDash: [2, 3],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const sunLineLayer = new VectorLayer({
|
||||
@ -154,7 +154,7 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
// Build group of layers
|
||||
// TODO: add blended night region to "connect" current night region to lines
|
||||
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
|
||||
@ -168,18 +168,20 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
const lineTime = new Date(time);
|
||||
const nightLinePoints = sourceLine.getCoordinates(lineTime.toString(), 'line');
|
||||
nightLineLayer.getSource()?.clear();
|
||||
const lineStringArray:Coordinate[][] = [];
|
||||
for (let l = 0; l < nightLinePoints.length - 1; l++){
|
||||
const x1:number = Object.values(nightLinePoints[l])[0];
|
||||
const y1:number = Object.values(nightLinePoints[l])[1];
|
||||
const x2:number = Object.values(nightLinePoints[l+1])[0];
|
||||
const y2:number = Object.values(nightLinePoints[l+1])[1];
|
||||
const lineString = [fromLonLat([x1, y1]),fromLonLat([x2, y2])];
|
||||
const lineStringArray: Coordinate[][] = [];
|
||||
for (let l = 0; l < nightLinePoints.length - 1; l++) {
|
||||
const x1: number = Object.values(nightLinePoints[l])[0];
|
||||
const y1: number = Object.values(nightLinePoints[l])[1];
|
||||
const x2: number = Object.values(nightLinePoints[l + 1])[0];
|
||||
const y2: number = Object.values(nightLinePoints[l + 1])[1];
|
||||
const lineString = [fromLonLat([x1, y1]), fromLonLat([x2, y2])];
|
||||
lineStringArray.push(lineString);
|
||||
}
|
||||
nightLineLayer.getSource()?.addFeature(new Feature({
|
||||
nightLineLayer.getSource()?.addFeature(
|
||||
new Feature({
|
||||
geometry: new MultiLineString(lineStringArray),
|
||||
}))
|
||||
})
|
||||
);
|
||||
|
||||
let sunLinePos: number[] = [];
|
||||
sunLinePos = sourceLineMethods.getSunPosition(lineTime);
|
||||
@ -206,14 +208,14 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
let selectedTime: Date = new Date();
|
||||
let sunPos: number[] = [];
|
||||
// TODO: add option for "Both"
|
||||
if (config.show === ShowTime.From){
|
||||
if (config.show === ShowTime.From) {
|
||||
selectedTime = from;
|
||||
} else {
|
||||
selectedTime = to;
|
||||
}
|
||||
|
||||
source.setTime(selectedTime);
|
||||
if (config.sun){
|
||||
if (config.sun) {
|
||||
sunPos = sourceMethods.getSunPosition(selectedTime);
|
||||
sunFeature.getGeometry()?.setCoordinates(fromLonLat(sunPos));
|
||||
}
|
||||
@ -221,28 +223,27 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
|
||||
|
||||
// Marker overlay options
|
||||
registerOptionsUI: (builder) => {
|
||||
if(!options.config?.nightColor) {
|
||||
options.config = { ...defaultConfig, ...options.config}
|
||||
if (!options.config?.nightColor) {
|
||||
options.config = { ...defaultConfig, ...options.config };
|
||||
}
|
||||
|
||||
builder
|
||||
.addRadio({
|
||||
path: 'config.show',
|
||||
name: 'Show',
|
||||
settings: {
|
||||
options: [
|
||||
{ label: 'From', value: ShowTime.From },
|
||||
{ label: 'To', value: ShowTime.To },
|
||||
],
|
||||
},
|
||||
defaultValue: defaultConfig.show,
|
||||
});
|
||||
builder.addRadio({
|
||||
path: 'config.show',
|
||||
name: 'Show',
|
||||
settings: {
|
||||
options: [
|
||||
{ label: 'From', value: ShowTime.From },
|
||||
{ label: 'To', value: ShowTime.To },
|
||||
],
|
||||
},
|
||||
defaultValue: defaultConfig.show,
|
||||
});
|
||||
builder.addColorPicker({
|
||||
path: 'config.nightColor',
|
||||
name: 'Night region color',
|
||||
description: 'Pick color of night region',
|
||||
defaultValue: defaultConfig.nightColor,
|
||||
settings: [{enableNamedColors: false}],
|
||||
settings: [{ enableNamedColors: false }],
|
||||
});
|
||||
builder.addBooleanSwitch({
|
||||
path: 'config.sun',
|
||||
|
@ -10,10 +10,7 @@ import {
|
||||
TIME_SERIES_TIME_FIELD_NAME,
|
||||
} from '@grafana/data';
|
||||
|
||||
import {
|
||||
MapLayerOptions,
|
||||
FrameGeometrySourceMode,
|
||||
} from '@grafana/schema';
|
||||
import { MapLayerOptions, FrameGeometrySourceMode } from '@grafana/schema';
|
||||
|
||||
import Map from 'ol/Map';
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
@ -31,10 +28,10 @@ import VectorSource from 'ol/source/Vector';
|
||||
import { Fill, Stroke, Style, Circle } from 'ol/style';
|
||||
import Feature from 'ol/Feature';
|
||||
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 tinycolor from 'tinycolor2';
|
||||
import { getStyleDimension } from '../../utils/utils';
|
||||
import { getStyleDimension, isSegmentVisible } from '../../utils/utils';
|
||||
|
||||
// Configuration options for Circle overlays
|
||||
export interface RouteConfig {
|
||||
@ -114,9 +111,13 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
|
||||
if (geom instanceof SimpleGeometry) {
|
||||
const coordinates = geom.getCoordinates();
|
||||
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++) {
|
||||
const segmentStartCoords = coordinates[startIndex];
|
||||
const segmentEndCoords = coordinates[i + 1];
|
||||
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)
|
||||
.toString();
|
||||
@ -126,7 +127,7 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
|
||||
.setAlpha(opacity)
|
||||
.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 flowStyle = new FlowLine({
|
||||
@ -134,7 +135,7 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
|
||||
lineCap: config.arrow == 0 ? 'round' : 'square',
|
||||
color: color1,
|
||||
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,
|
||||
});
|
||||
if (config.arrow) {
|
||||
@ -147,9 +148,33 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
|
||||
flowStyle.setArrowSize((arrowSize1 ?? 0) * 1.5);
|
||||
}
|
||||
}
|
||||
const LS = new LineString([coordinates[i], coordinates[i + 1]]);
|
||||
flowStyle.setGeometry(LS);
|
||||
styles.push(flowStyle);
|
||||
// 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);
|
||||
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;
|
||||
|
@ -117,6 +117,25 @@ export const getNextLayerName = (panel: GeomapPanel) => {
|
||||
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) => {
|
||||
try {
|
||||
const newUrl = new URL(url);
|
||||
|
Loading…
Reference in New Issue
Block a user