diff --git a/public/app/plugins/panel/geomap/layers/data/dayNightLayer.tsx b/public/app/plugins/panel/geomap/layers/data/dayNightLayer.tsx index 0759b8ba6d4..15151aa6f61 100644 --- a/public/app/plugins/panel/geomap/layers/data/dayNightLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/dayNightLayer.tsx @@ -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 = { }; // 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { // 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 = { 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 = { 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 = { // 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', diff --git a/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx b/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx index 0b603506fe7..07157098386 100644 --- a/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx @@ -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 = { 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 = { .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 = { 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 = { 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; diff --git a/public/app/plugins/panel/geomap/utils/utils.ts b/public/app/plugins/panel/geomap/utils/utils.ts index ad9353f51e5..47aa8bbf310 100644 --- a/public/app/plugins/panel/geomap/utils/utils.ts +++ b/public/app/plugins/panel/geomap/utils/utils.ts @@ -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);