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:
Drew Slobodnjak 2023-03-03 16:06:14 -08:00 committed by GitHub
parent adc8000b72
commit 1aadafe7d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 101 additions and 56 deletions

View File

@ -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',

View File

@ -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;

View File

@ -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);