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

View File

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

View File

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