Geomap: Add alpha day/night layer (#50201)

Co-authored-by: drew08t <drew08@gmail.com>
This commit is contained in:
Ryan McKinley 2022-06-20 10:35:03 -07:00 committed by GitHub
parent a37b868da7
commit c2aee2b6da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 487 additions and 26 deletions

View File

@ -139,6 +139,7 @@
"@types/logfmt": "^1.2.1",
"@types/mousetrap": "1.6.9",
"@types/node": "16.11.36",
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@2.0.0",
"@types/papaparse": "5.3.2",
"@types/pluralize": "^0.0.29",
"@types/prismjs": "1.26.0",
@ -337,6 +338,7 @@
"mousetrap-global-bind": "1.1.0",
"moveable": "0.30.0",
"ol": "6.14.1",
"ol-ext": "^3.2.25",
"papaparse": "5.3.2",
"pluralize": "^8.0.0",
"prismjs": "1.28.0",

View File

@ -2,6 +2,7 @@ import { PluggableMap } from 'ol';
import BaseLayer from 'ol/layer/Base';
import { ReactNode } from 'react';
import { EventBus } from '../events';
import { GrafanaTheme2 } from '../themes';
import { MatcherConfig, PanelData } from '../types';
import { PanelOptionsEditorBuilder } from '../utils';
@ -79,6 +80,11 @@ export interface MapLayerHandler<TConfig = any> {
* The update function should only be implemented if the layer type makes use of query data
*/
update?: (data: PanelData) => void;
/** Optional callback to cleaup before getting removed */
dispose?: () => void;
/** return react node for the legend */
legend?: ReactNode;
/**
@ -112,5 +118,10 @@ export interface MapLayerRegistryItem<TConfig = MapLayerOptions> extends Registr
* Function that configures transformation and returns a transformer
* @param options
*/
create: (map: PluggableMap, options: MapLayerOptions<TConfig>, theme: GrafanaTheme2) => Promise<MapLayerHandler>;
create: (
map: PluggableMap,
options: MapLayerOptions<TConfig>,
eventBus: EventBus,
theme: GrafanaTheme2
) => Promise<MapLayerHandler>;
}

View File

@ -100,6 +100,13 @@ export class GeomapPanel extends Component<Props, State> {
this.panelContext = this.context as PanelContext;
}
componentWillUnmount() {
this.subs.unsubscribe();
for (const lyr of this.layers) {
lyr.handler.dispose?.();
}
}
shouldComponentUpdate(nextProps: Props) {
if (!this.map) {
return true; // not yet initialized
@ -404,7 +411,7 @@ export class GeomapPanel extends Component<Props, State> {
{
layerFilter: (l) => {
const hoverLayerState = (l as any).__state as MapLayerState;
return hoverLayerState.options.tooltip !== false;
return hoverLayerState?.options?.tooltip !== false;
},
}
);
@ -467,6 +474,7 @@ export class GeomapPanel extends Component<Props, State> {
const layers = this.layers.slice(0);
try {
const info = await this.initLayer(this.map, newOptions, current.isBasemap);
layers[layerIndex]?.handler.dispose?.();
layers[layerIndex] = info;
group.setAt(layerIndex, info.layer);
@ -504,7 +512,7 @@ export class GeomapPanel extends Component<Props, State> {
return Promise.reject('unknown layer: ' + options.type);
}
const handler = await item.create(map, options, config.theme2);
const handler = await item.create(map, options, this.props.eventBus, config.theme2);
const layer = handler.init();
if (options.opacity != null) {
layer.setOpacity(1 - options.opacity);

View File

@ -3,6 +3,7 @@ import { css } from '@emotion/react';
import { GrafanaTheme2 } from '@grafana/data';
import 'ol/ol.css';
import 'ol-ext/dist/ol-ext.css';
/**
* Will be loaded *after* the css above

View File

@ -2,7 +2,7 @@ import Map from 'ol/Map';
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2 } from '@grafana/data';
import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2, EventBus } from '@grafana/data';
// https://carto.com/help/building-maps/basemap-list/
@ -33,7 +33,7 @@ export const carto: MapLayerRegistryItem<CartoConfig> = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions<CartoConfig>, theme: GrafanaTheme2) => ({
create: async (map: Map, options: MapLayerOptions<CartoConfig>, eventBus: EventBus, theme: GrafanaTheme2) => ({
init: () => {
const cfg = { ...defaultCartoConfig, ...options.config };
let style = cfg.theme as string;

View File

@ -1,6 +1,6 @@
import Map from 'ol/Map';
import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2, RegistryItem, Registry } from '@grafana/data';
import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2, RegistryItem, Registry, EventBus } from '@grafana/data';
import { xyzTiles, defaultXYZConfig, XYZConfig } from './generic';
@ -60,7 +60,7 @@ export const esriXYZTiles: MapLayerRegistryItem<ESRIXYZConfig> = {
description: 'Add layer from an ESRI ArcGIS MapServer',
isBaseMap: true,
create: async (map: Map, options: MapLayerOptions<ESRIXYZConfig>, theme: GrafanaTheme2) => {
create: async (map: Map, options: MapLayerOptions<ESRIXYZConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
const cfg = { ...options.config };
const svc = publicServiceRegistry.getIfExists(cfg.server ?? DEFAULT_SERVICE)!;
if (svc.id !== CUSTOM_SERVICE) {
@ -69,7 +69,7 @@ export const esriXYZTiles: MapLayerRegistryItem<ESRIXYZConfig> = {
cfg.attribution = `Tiles © <a href="${base}${svc.slug}/MapServer">ArcGIS</a>`;
}
const opts = { ...options, config: cfg as XYZConfig };
return xyzTiles.create(map, opts, theme).then((xyz) => {
return xyzTiles.create(map, opts, eventBus, theme).then((xyz) => {
xyz.registerOptionsUI = (builder) => {
builder
.addSelect({

View File

@ -2,7 +2,7 @@ import Map from 'ol/Map';
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2 } from '@grafana/data';
import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2, EventBus } from '@grafana/data';
export interface XYZConfig {
url: string;
@ -23,7 +23,7 @@ export const xyzTiles: MapLayerRegistryItem<XYZConfig> = {
description: 'Add map from a generic tile layer',
isBaseMap: true,
create: async (map: Map, options: MapLayerOptions<XYZConfig>, theme: GrafanaTheme2) => ({
create: async (map: Map, options: MapLayerOptions<XYZConfig>, eventBus: EventBus, theme: GrafanaTheme2) => ({
init: () => {
const cfg = { ...options.config };
if (!cfg.url) {

View File

@ -2,7 +2,7 @@ import Map from 'ol/Map';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import { MapLayerRegistryItem, MapLayerOptions } from '@grafana/data';
import { MapLayerRegistryItem, MapLayerOptions, EventBus } from '@grafana/data';
export const standard: MapLayerRegistryItem = {
id: 'osm-standard',
@ -14,7 +14,7 @@ export const standard: MapLayerRegistryItem = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions) => ({
create: async (map: Map, options: MapLayerOptions, eventBus: EventBus) => ({
init: () => {
return new TileLayer({
source: new OSM(),

View File

@ -0,0 +1,261 @@
import {
MapLayerRegistryItem,
MapLayerOptions,
PanelData,
GrafanaTheme2,
EventBus,
DataHoverEvent,
DataHoverClearEvent,
PluginState,
} from '@grafana/data';
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 Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import DayNight from 'ol-ext/source/DayNight';
import { fromLonLat } from 'ol/proj';
import { Subscription } from 'rxjs';
import { MultiLineString } from 'ol/geom';
import { Coordinate } from 'ol/coordinate';
export enum ShowTime {
From = 'from',
To = 'to',
}
// Configuration options for Circle overlays
export interface DayNightConfig {
show: ShowTime;
sun: boolean;
nightColor: string;
}
const defaultConfig: DayNightConfig = {
show: ShowTime.To,
sun: false,
nightColor: '#a7a6ba4D'
};
export const DAY_NIGHT_LAYER_ID = 'dayNight';
// Used by default when nothing is configured
export const defaultDayNightConfig: MapLayerOptions<DayNightConfig> = {
type: DAY_NIGHT_LAYER_ID,
name: '', // will get replaced
config: defaultConfig,
tooltip: true,
};
/**
* Map layer configuration for circle overlay
*/
export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
id: DAY_NIGHT_LAYER_ID,
name: 'Night / Day',
description: 'Show day and night regions',
isBaseMap: false,
state: PluginState.alpha,
/**
* Function that configures transformation and returns a transformer
* @param map
* @param options
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<DayNightConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
// Assert default values
const config = {
...defaultConfig,
...options?.config,
};
// DayNight source
const source = new DayNight({ });
const sourceMethods = Object.getPrototypeOf(source);
const sourceLine = new DayNight({ });
const sourceLineMethods = Object.getPrototypeOf(sourceLine);
// Night polygon
const vectorLayer = new VectorLayer({
source,
style: new Style({
fill: new Fill({
color: theme.visualization.getColorByName(config.nightColor)
})
})
});
// Night line (for crosshair sharing)
const nightLineLayer = new VectorLayer({
source: new VectorSource({
features: [],
}),
style: new Style ({
stroke: new Stroke({
color: '#607D8B',
width: 1.5,
lineDash: [2, 3],
})
}),
});
// Sun circle
const sunFeature = new Feature({
geometry: new Point([]),
});
const sunLayer = new VectorLayer({
source: new VectorSource({
features: [sunFeature],
}),
style: new Style({
image: new Circle({
radius: 13,
fill: new Fill({color: 'rgb(253,184,19)'}),
})
}),
});
// Sun line (for crosshair sharing)
const sunLineFeature = new Feature({
geometry: new Point([]),
});
const sunLineStyle = new Style({
image: new Circle({
radius: 13,
stroke: new Stroke({
color: 'rgb(253,184,19)',
width: 1.5
})
})
});
const sunLineStyleDash = new Style({
image: new Circle({
radius: 15,
stroke: new Stroke({
color: '#607D8B',
width: 1.5,
lineDash: [2,3]
})
})
});
const sunLineLayer = new VectorLayer({
source: new VectorSource({
features: [sunLineFeature],
}),
style: [sunLineStyleDash, sunLineStyle],
});
// 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]
});
// Crosshair sharing subscriptions
const subscriptions = new Subscription();
if (false) {
subscriptions.add(
eventBus.subscribe(DataHoverEvent, (event) => {
const time = event.payload?.point?.time as number;
if (time) {
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])];
lineStringArray.push(lineString);
}
nightLineLayer.getSource()?.addFeature(new Feature({
geometry: new MultiLineString(lineStringArray),
}))
let sunLinePos: number[] = [];
sunLinePos = sourceLineMethods.getSunPosition(lineTime);
sunLineFeature.getGeometry()?.setCoordinates(fromLonLat(sunLinePos));
sunLineFeature.setStyle([sunLineStyle, sunLineStyleDash]);
}
})
);
subscriptions.add(
eventBus.subscribe(DataHoverClearEvent, (event) => {
nightLineLayer.getSource()?.clear();
sunLineFeature.setStyle(new Style({}));
})
);
}
return {
init: () => layer,
dispose: () => subscriptions.unsubscribe(),
update: (data: PanelData) => {
const from = new Date(data.timeRange.from.valueOf());
const to = new Date(data.timeRange.to.valueOf());
let selectedTime: Date = new Date();
let sunPos: number[] = [];
// TODO: add option for "Both"
if (config.show === ShowTime.From){
selectedTime = from;
} else {
selectedTime = to;
}
source.setTime(selectedTime);
if (config.sun){
sunPos = sourceMethods.getSunPosition(selectedTime);
sunFeature.getGeometry()?.setCoordinates(fromLonLat(sunPos));
}
},
// Marker overlay options
registerOptionsUI: (builder) => {
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.addColorPicker({
path: 'config.nightColor',
name: 'Night region color',
description: 'Pick color of night region',
defaultValue: defaultConfig.nightColor,
settings: [{enableNamedColors: false}],
});
builder.addBooleanSwitch({
path: 'config.sun',
name: 'Display sun',
description: 'Show the sun',
defaultValue: defaultConfig.sun,
});
},
};
},
// fill in the default values
defaultOptions: defaultConfig,
};

View File

@ -5,6 +5,7 @@ import {
GrafanaTheme2,
PluginState,
SelectableValue,
EventBus,
} from '@grafana/data';
import Map from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
@ -72,7 +73,7 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions<GeoJSONMapperConfig>, theme: GrafanaTheme2) => {
create: async (map: Map, options: MapLayerOptions<GeoJSONMapperConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
const config = { ...defaultOptions, ...options.config };
const source = new VectorSource({

View File

@ -1,4 +1,5 @@
import {
EventBus,
FieldType,
getFieldColorModeForField,
GrafanaTheme2,
@ -45,7 +46,7 @@ export const heatmapLayer: MapLayerRegistryItem<HeatmapConfig> = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions<HeatmapConfig>, theme: GrafanaTheme2) => {
create: async (map: Map, options: MapLayerOptions<HeatmapConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
const config = { ...defaultOptions, ...options.config };
const location = await getLocationMatchers(options.location);

View File

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

View File

@ -1,4 +1,4 @@
import { MapLayerRegistryItem, MapLayerOptions, PanelData, GrafanaTheme2, PluginState } from '@grafana/data';
import { MapLayerRegistryItem, MapLayerOptions, PanelData, GrafanaTheme2, PluginState, EventBus } from '@grafana/data';
import Map from 'ol/Map';
import Feature from 'ol/Feature';
import * as style from 'ol/style';
@ -26,7 +26,7 @@ export const lastPointTracker: MapLayerRegistryItem<LastPointConfig> = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions<LastPointConfig>, theme: GrafanaTheme2) => {
create: async (map: Map, options: MapLayerOptions<LastPointConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
const point = new Feature({});
const config = { ...defaultOptions, ...options.config };

View File

@ -5,6 +5,7 @@ import {
PanelData,
GrafanaTheme2,
FrameGeometrySourceMode,
EventBus,
} from '@grafana/data';
import Map from 'ol/Map';
import { FeatureLike } from 'ol/Feature';
@ -61,7 +62,7 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
* @param options
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<MarkersConfig>, theme: GrafanaTheme2) => {
create: async (map: Map, options: MapLayerOptions<MarkersConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
// Assert default values
const config = {
...defaultOptions,

View File

@ -5,6 +5,7 @@ import {
Registry,
MapLayerOptions,
GrafanaTheme2,
EventBus,
SelectableValue,
PluginState,
} from '@grafana/data';
@ -26,18 +27,18 @@ export const defaultBaseLayer: MapLayerRegistryItem = {
name: 'Default base layer',
isBaseMap: true,
create: (map: Map, options: MapLayerOptions, theme: GrafanaTheme2) => {
create: (map: Map, options: MapLayerOptions, eventBus: EventBus, theme: GrafanaTheme2) => {
const serverLayerType = config?.geomapDefaultBaseLayerConfig?.type;
if (serverLayerType) {
const layer = geomapLayerRegistry.getIfExists(serverLayerType);
if (!layer) {
throw new Error('Invalid basemap configuration on server');
}
return layer.create(map, config.geomapDefaultBaseLayerConfig!, theme);
return layer.create(map, config.geomapDefaultBaseLayerConfig!, eventBus, theme);
}
// For now use carto as our default basemap
return carto.create(map, options, theme);
return carto.create(map, options, eventBus, theme);
},
};
@ -57,16 +58,26 @@ interface RegistrySelectInfo {
function getLayersSelection(items: Array<MapLayerRegistryItem<any>>, current?: string): RegistrySelectInfo {
const res: RegistrySelectInfo = { options: [], current: [] };
const alpha: Array<SelectableValue<string>> = [];
for (const layer of items) {
if (layer.state === PluginState.alpha && !hasAlphaPanels) {
continue;
const opt: SelectableValue<string> = { label: layer.name, value: layer.id, description: layer.description };
if (layer.state === PluginState.alpha) {
if (!hasAlphaPanels) {
continue;
}
opt.label = `${layer.name} (Alpha)`;
opt.icon = 'bolt';
alpha.push(opt);
} else {
res.options.push(opt);
}
const opt = { label: layer.name, value: layer.id, description: layer.description };
res.options.push(opt);
if (layer.id === current) {
res.current.push(opt);
}
}
for (const p of alpha) {
res.options.push(p);
}
return res;
}

165
yarn.lock
View File

@ -3221,7 +3221,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.18.3":
"@babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.18.3":
version: 7.18.3
resolution: "@babel/runtime@npm:7.18.3"
dependencies:
@ -10734,6 +10734,15 @@ __metadata:
languageName: node
linkType: hard
"@types/ol-ext@npm:@siedlerchr/types-ol-ext@2.0.0":
version: 2.0.0
resolution: "@siedlerchr/types-ol-ext@npm:2.0.0"
dependencies:
jspdf: ^2.5.1
checksum: f97642cbd3fbc6501d17bc8118931eb00c12cb8b92a89d055508fd24e62e0c37263da18144cada421d8dc342d74e8198179905184b32b8c260bf30e4f5d7e4cb
languageName: node
linkType: hard
"@types/overlayscrollbars@npm:^1.12.0":
version: 1.12.1
resolution: "@types/overlayscrollbars@npm:1.12.1"
@ -10827,6 +10836,13 @@ __metadata:
languageName: node
linkType: hard
"@types/raf@npm:^3.4.0":
version: 3.4.0
resolution: "@types/raf@npm:3.4.0"
checksum: d93e9b5244a081c64708b8918ef7e56936d6ef0144925b189e67d34127c0cb3a73fcf6866ab312db156554a66c26609dd056da5f7302f6658c049d6a37ed5f56
languageName: node
linkType: hard
"@types/range-parser@npm:*":
version: 1.2.4
resolution: "@types/range-parser@npm:1.2.4"
@ -13833,6 +13849,13 @@ __metadata:
languageName: node
linkType: hard
"base64-arraybuffer@npm:^1.0.2":
version: 1.0.2
resolution: "base64-arraybuffer@npm:1.0.2"
checksum: 15e6400d2d028bf18be4ed97702b11418f8f8779fb8c743251c863b726638d52f69571d4cc1843224da7838abef0949c670bde46936663c45ad078e89fee5c62
languageName: node
linkType: hard
"base64-js@npm:^1.0.2, base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
@ -14299,6 +14322,15 @@ __metadata:
languageName: node
linkType: hard
"btoa@npm:^1.2.1":
version: 1.2.1
resolution: "btoa@npm:1.2.1"
bin:
btoa: bin/btoa.js
checksum: afbf004fb1b1d530e053ffa66ef5bd3878b101c59d808ac947fcff96810b4452abba2b54be687adadea2ba9efc7af48b04228742789bf824ef93f103767e690c
languageName: node
linkType: hard
"buffer-crc32@npm:~0.2.3":
version: 0.2.13
resolution: "buffer-crc32@npm:0.2.13"
@ -14634,6 +14666,22 @@ __metadata:
languageName: node
linkType: hard
"canvg@npm:^3.0.6":
version: 3.0.10
resolution: "canvg@npm:3.0.10"
dependencies:
"@babel/runtime": ^7.12.5
"@types/raf": ^3.4.0
core-js: ^3.8.3
raf: ^3.4.1
regenerator-runtime: ^0.13.7
rgbcolor: ^1.0.1
stackblur-canvas: ^2.0.0
svg-pathdata: ^6.0.3
checksum: 2cfd86bcb9b56b43a97745cc672e696169b4c09e8850fb4f27bec5ebf173179d16feb594224d643a32f1ce01e47b55d44e0058419114d48d34f12c2452c65927
languageName: node
linkType: hard
"capture-exit@npm:^2.0.0":
version: 2.0.0
resolution: "capture-exit@npm:2.0.0"
@ -15832,6 +15880,13 @@ __metadata:
languageName: node
linkType: hard
"core-js@npm:^3.6.0, core-js@npm:^3.8.3":
version: 3.23.1
resolution: "core-js@npm:3.23.1"
checksum: 460328e0b743900d48fc744aab475f6dabdae498cc73f6d038321b0ae1488cb5e73d1670b5914abfe7dd84d0843a828b5d4dd3eba0a5b6a4450f0d7094cda2a5
languageName: node
linkType: hard
"core-util-is@npm:1.0.2":
version: 1.0.2
resolution: "core-util-is@npm:1.0.2"
@ -16085,6 +16140,15 @@ __metadata:
languageName: node
linkType: hard
"css-line-break@npm:^2.1.0":
version: 2.1.0
resolution: "css-line-break@npm:2.1.0"
dependencies:
utrie: ^1.0.2
checksum: 37b1fe632b03be7a287cd394cef8b5285666343443125c510df9cfb6a4734a2c71e154ec8f7bbff72d7c339e1e5872989b1c52d86162aed27d6cc114725bb4d0
languageName: node
linkType: hard
"css-loader@npm:6.7.1, css-loader@npm:^6.7.1":
version: 6.7.1
resolution: "css-loader@npm:6.7.1"
@ -17644,6 +17708,13 @@ __metadata:
languageName: node
linkType: hard
"dompurify@npm:^2.2.0":
version: 2.3.8
resolution: "dompurify@npm:2.3.8"
checksum: dc7b32ee57a03fe5166a850071200897cc13fa069287a709e3b2138052d73ec09a87026b9e28c8d2f254a74eaa52ef30644e98e54294c30acbca2a53f1bbc5f4
languageName: node
linkType: hard
"domutils@npm:^2.5.2, domutils@npm:^2.6.0, domutils@npm:^2.7.0":
version: 2.8.0
resolution: "domutils@npm:2.8.0"
@ -19384,6 +19455,13 @@ __metadata:
languageName: node
linkType: hard
"fflate@npm:^0.4.8":
version: 0.4.8
resolution: "fflate@npm:0.4.8"
checksum: 29d8cbe44d5e7f53e7f5a160ac7f9cc025480c7b3bfd85c5f898cbe20dfa2dad4732daa534982664bf30b35896a90af44ea33ede5d94c5ffd1b8b0c0a0a56ca2
languageName: node
linkType: hard
"figgy-pudding@npm:^3.5.1":
version: 3.5.2
resolution: "figgy-pudding@npm:3.5.2"
@ -20720,6 +20798,7 @@ __metadata:
"@types/logfmt": ^1.2.1
"@types/mousetrap": 1.6.9
"@types/node": 16.11.36
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@2.0.0"
"@types/papaparse": 5.3.2
"@types/pluralize": ^0.0.29
"@types/prismjs": 1.26.0
@ -20850,6 +20929,7 @@ __metadata:
node-notifier: 10.0.1
nodemon: 2.0.16
ol: 6.14.1
ol-ext: ^3.2.25
papaparse: 5.3.2
pluralize: ^8.0.0
postcss: 8.4.14
@ -21472,6 +21552,16 @@ __metadata:
languageName: node
linkType: hard
"html2canvas@npm:^1.0.0-rc.5":
version: 1.4.1
resolution: "html2canvas@npm:1.4.1"
dependencies:
css-line-break: ^2.1.0
text-segmentation: ^1.0.3
checksum: c134324af57f3262eecf982e436a4843fded3c6cf61954440ffd682527e4dd350e0c2fafd217c0b6f9a455fe345d0c67b4505689796ab160d4ca7c91c3766739
languageName: node
linkType: hard
"htmlparser2@npm:^6.1.0":
version: 6.1.0
resolution: "htmlparser2@npm:6.1.0"
@ -24534,6 +24624,31 @@ __metadata:
languageName: node
linkType: hard
"jspdf@npm:^2.5.1":
version: 2.5.1
resolution: "jspdf@npm:2.5.1"
dependencies:
"@babel/runtime": ^7.14.0
atob: ^2.1.2
btoa: ^1.2.1
canvg: ^3.0.6
core-js: ^3.6.0
dompurify: ^2.2.0
fflate: ^0.4.8
html2canvas: ^1.0.0-rc.5
dependenciesMeta:
canvg:
optional: true
core-js:
optional: true
dompurify:
optional: true
html2canvas:
optional: true
checksum: 9ecdccc50678cd780f0995157618630ca0da65576835983232d48001aab0b29e51af765e078808526d5e5e2e1ebf3cee460e03eaf590f875d160f2e0cb614a1e
languageName: node
linkType: hard
"jsprim@npm:^1.2.2":
version: 1.4.1
resolution: "jsprim@npm:1.4.1"
@ -27247,6 +27362,15 @@ __metadata:
languageName: node
linkType: hard
"ol-ext@npm:^3.2.25":
version: 3.2.25
resolution: "ol-ext@npm:3.2.25"
peerDependencies:
ol: ">= 5.3.0"
checksum: ebdf354089975c36258c456c1bf7b65a898fddc578d8112ad17ad8c571df1405992662c3fcfe13a23c400a8f2b7d9deb5efff4545a526d9a03386f4bcef565bf
languageName: node
linkType: hard
"ol-mapbox-style@npm:^7.1.1":
version: 7.1.1
resolution: "ol-mapbox-style@npm:7.1.1"
@ -32192,6 +32316,13 @@ __metadata:
languageName: node
linkType: hard
"rgbcolor@npm:^1.0.1":
version: 1.0.1
resolution: "rgbcolor@npm:1.0.1"
checksum: bd062ac007a3e979e2f83dc69feb3cc4f9bca7d8631899548394160e30c47e4f7e52b31aa3f66a69061ad56e899e812ec52f5c33686c085d72c9b3d22faed1c8
languageName: node
linkType: hard
"rimraf@npm:3.0.2, rimraf@npm:^3.0.0, rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "rimraf@npm:3.0.2"
@ -33628,6 +33759,13 @@ __metadata:
languageName: node
linkType: hard
"stackblur-canvas@npm:^2.0.0":
version: 2.5.0
resolution: "stackblur-canvas@npm:2.5.0"
checksum: 52b0dc595d3dbed94c8563d8fe34e0c8ff65b85eaf8fcfe7b0cd8893e93ae42c28b688f71d792fbaedf2a9b677dc474a2e721d8245165a3a0c0d794c0f6f8bba
languageName: node
linkType: hard
"stackframe@npm:^1.1.1":
version: 1.2.0
resolution: "stackframe@npm:1.2.0"
@ -34267,6 +34405,13 @@ __metadata:
languageName: node
linkType: hard
"svg-pathdata@npm:^6.0.3":
version: 6.0.3
resolution: "svg-pathdata@npm:6.0.3"
checksum: f0e55be50c654be5d259d70945ed7e5354bf78e51c6039b4045d9f7c49d703a0c33dda36751815aec2824d046c417c35226e7491246ffff3e9164735ea428446
languageName: node
linkType: hard
"svg-tags@npm:^1.0.0":
version: 1.0.0
resolution: "svg-tags@npm:1.0.0"
@ -34619,6 +34764,15 @@ __metadata:
languageName: node
linkType: hard
"text-segmentation@npm:^1.0.3":
version: 1.0.3
resolution: "text-segmentation@npm:1.0.3"
dependencies:
utrie: ^1.0.2
checksum: 2e24632d59567c55ab49ac324815e2f7a8043e63e26b109636322ac3e30692cee8679a448fd5d0f0598a345f407afd0e34ba612e22524cf576d382d84058c013
languageName: node
linkType: hard
"text-table@npm:^0.2.0":
version: 0.2.0
resolution: "text-table@npm:0.2.0"
@ -36031,6 +36185,15 @@ __metadata:
languageName: node
linkType: hard
"utrie@npm:^1.0.2":
version: 1.0.2
resolution: "utrie@npm:1.0.2"
dependencies:
base64-arraybuffer: ^1.0.2
checksum: c96fbb7d4d8855a154327da0b18e39b7511cc70a7e4bcc3658e24f424bb884312d72b5ba777500b8858e34d365dc6b1a921dc5ca2f0d341182519c6b78e280a5
languageName: node
linkType: hard
"uuid-browser@npm:^3.1.0":
version: 3.1.0
resolution: "uuid-browser@npm:3.1.0"