Geomap: Route/path visualization (#43554)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
pull/51493/head
Alexander Zobnin 3 years ago committed by GitHub
parent 0c0cf36ab8
commit 0e1f0dd8f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      public/app/features/geo/utils/frameVectorSource.ts
  2. 3
      public/app/plugins/panel/geomap/layers/data/index.ts
  3. 147
      public/app/plugins/panel/geomap/layers/data/routeLayer.tsx
  4. 20
      public/app/plugins/panel/geomap/style/markers.ts

@ -1,8 +1,8 @@
import { Feature } from 'ol';
import { Geometry } from 'ol/geom';
import { Geometry, LineString, Point } from 'ol/geom';
import VectorSource from 'ol/source/Vector';
import { DataFrame } from '@grafana/data';
import { DataFrame, Field } from '@grafana/data';
import { getGeometryField, LocationFieldMatchers } from './location';
@ -34,4 +34,26 @@ export class FrameVectorSource<T extends Geometry = Geometry> extends VectorSour
// only call this at the end
this.changed();
}
updateLineString(frame: DataFrame) {
this.clear(true);
const info = getGeometryField(frame, this.location);
if (!info.field) {
this.changed();
return;
}
const field = info.field as Field<Point>;
const geometry = new LineString(field.values.toArray().map((p) => p.getCoordinates())) as Geometry;
this.addFeatureInternal(
new Feature({
frame,
rowIndex: 0,
geometry: geometry as T,
})
);
// only call this at the end
this.changed();
}
}

@ -2,9 +2,10 @@ import { markersLayer } from './markersLayer';
import { geojsonLayer } from './geojsonLayer';
import { heatmapLayer } from './heatMap';
import { lastPointTracker } from './lastPointTracker';
import { routeLayer } from './routeLayer';
import { dayNightLayer } from './dayNightLayer';
/**
* Registry for layer handlers
*/
export const dataLayers = [markersLayer, heatmapLayer, lastPointTracker, geojsonLayer, dayNightLayer];
export const dataLayers = [markersLayer, heatmapLayer, lastPointTracker, geojsonLayer, dayNightLayer, routeLayer];

@ -0,0 +1,147 @@
import {
MapLayerRegistryItem,
MapLayerOptions,
PanelData,
GrafanaTheme2,
FrameGeometrySourceMode,
PluginState,
EventBus,
} from '@grafana/data';
import Map from 'ol/Map';
import { FeatureLike } from 'ol/Feature';
import { getLocationMatchers } from 'app/features/geo/utils/location';
import { getColorDimension } from 'app/features/dimensions';
import { defaultStyleConfig, StyleConfig, StyleDimensions } from '../../style/types';
import { StyleEditor } from './StyleEditor';
import { getStyleConfigState } from '../../style/utils';
import VectorLayer from 'ol/layer/Vector';
import { isNumber } from 'lodash';
import { routeStyle } from '../../style/markers';
import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource';
// Configuration options for Circle overlays
export interface RouteConfig {
style: StyleConfig;
}
const defaultOptions: RouteConfig = {
style: {
...defaultStyleConfig,
opacity: 1,
lineWidth: 2,
},
};
export const ROUTE_LAYER_ID = 'route';
// Used by default when nothing is configured
export const defaultRouteConfig: MapLayerOptions<RouteConfig> = {
type: ROUTE_LAYER_ID,
name: '', // will get replaced
config: defaultOptions,
location: {
mode: FrameGeometrySourceMode.Auto,
},
tooltip: false,
};
/**
* Map layer configuration for circle overlay
*/
export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
id: ROUTE_LAYER_ID,
name: 'Route',
description: 'Render data points as a route',
isBaseMap: false,
showLocation: true,
state: PluginState.alpha,
/**
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions<RouteConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
// Assert default values
const config = {
...defaultOptions,
...options?.config,
};
const style = await getStyleConfigState(config.style);
const location = await getLocationMatchers(options.location);
const source = new FrameVectorSource(location);
const vectorLayer = new VectorLayer({
source,
});
if (!style.fields) {
// Set a global style
vectorLayer.setStyle(routeStyle(style.base));
} else {
vectorLayer.setStyle((feature: FeatureLike) => {
const idx = feature.get('rowIndex') as number;
const dims = style.dims;
if (!dims || !isNumber(idx)) {
return routeStyle(style.base);
}
const values = { ...style.base };
if (dims.color) {
values.color = dims.color.get(idx);
}
return routeStyle(values);
});
}
return {
init: () => vectorLayer,
update: (data: PanelData) => {
if (!data.series?.length) {
return; // ignore empty
}
for (const frame of data.series) {
if (style.fields) {
const dims: StyleDimensions = {};
if (style.fields.color) {
dims.color = getColorDimension(frame, style.config.color ?? defaultStyleConfig.color, theme);
}
style.dims = dims;
}
source.updateLineString(frame);
break; // Only the first frame for now!
}
},
// Route layer options
registerOptionsUI: (builder) => {
builder
.addCustomEditor({
id: 'config.style',
path: 'config.style',
name: 'Style',
editor: StyleEditor,
settings: {
simpleFixedValues: true,
},
defaultValue: defaultOptions.style,
})
.addSliderInput({
path: 'config.style.lineWidth',
name: 'Line width',
defaultValue: defaultOptions.style.lineWidth,
settings: {
min: 1,
max: 10,
step: 1,
},
});
},
};
},
// fill in the default values
defaultOptions,
};

@ -42,6 +42,18 @@ export function getFillColor(cfg: StyleConfigValues) {
return undefined;
}
export function getStrokeStyle(cfg: StyleConfigValues) {
const opacity = cfg.opacity == null ? 0.8 : cfg.opacity;
if (opacity === 1) {
return new Stroke({ color: cfg.color, width: cfg.lineWidth ?? 1 });
}
if (opacity > 0) {
const color = tinycolor(cfg.color).setAlpha(opacity).toRgbString();
return new Stroke({ color, width: cfg.lineWidth ?? 1 });
}
return undefined;
}
const textLabel = (cfg: StyleConfigValues) => {
if (!cfg.text) {
return undefined;
@ -88,6 +100,14 @@ export const polyStyle = (cfg: StyleConfigValues) => {
});
};
export const routeStyle = (cfg: StyleConfigValues) => {
return new Style({
fill: getFillColor(cfg),
stroke: getStrokeStyle(cfg),
text: textLabel(cfg),
});
};
// Square and cross
const errorMarker = (cfg: StyleConfigValues) => {
const radius = cfg.size ?? DEFAULT_SIZE;

Loading…
Cancel
Save