From 9cd8e11c308264c82203c4bb82d7581cd407722b Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 21 Jul 2021 23:41:27 -0700 Subject: [PATCH] Geomap: add basic gazetteer support (#37082) --- packages/grafana-data/src/geo/layer.ts | 6 +- .../testdata/components/USAQueryEditor.tsx | 13 - .../app/plugins/panel/geomap/GeomapPanel.tsx | 19 +- .../geomap/editor/GazetteerPathEditor.tsx | 85 + .../panel/geomap/editor/LayerEditor.tsx | 18 + .../panel/geomap/gazetteer/gazetteer.test.ts | 36 + .../panel/geomap/gazetteer/gazetteer.ts | 69 + .../panel/geomap/gazetteer/worldmap.ts | 61 + .../panel/geomap/layers/basemaps/carto.ts | 2 +- .../panel/geomap/layers/basemaps/esri.ts | 24 +- .../panel/geomap/layers/basemaps/generic.ts | 2 +- .../panel/geomap/layers/basemaps/osm.ts | 2 +- .../panel/geomap/layers/data/geojsonMapper.ts | 4 +- .../panel/geomap/layers/data/heatMap.tsx | 5 +- .../geomap/layers/data/lastPointTracker.ts | 6 +- .../panel/geomap/layers/data/markersLayer.tsx | 68 +- .../panel/geomap/utils/location.test.ts | 8 +- .../plugins/panel/geomap/utils/location.ts | 29 +- .../panel/geomap/utils/regularShapes.ts | 30 +- public/gazetteer/countries.json | 1508 +++++++++++++++++ public/gazetteer/usa-states.json | 332 ++++ tsconfig.json | 3 +- 22 files changed, 2228 insertions(+), 102 deletions(-) create mode 100644 public/app/plugins/panel/geomap/editor/GazetteerPathEditor.tsx create mode 100644 public/app/plugins/panel/geomap/gazetteer/gazetteer.test.ts create mode 100644 public/app/plugins/panel/geomap/gazetteer/gazetteer.ts create mode 100644 public/app/plugins/panel/geomap/gazetteer/worldmap.ts create mode 100644 public/gazetteer/countries.json create mode 100644 public/gazetteer/usa-states.json diff --git a/packages/grafana-data/src/geo/layer.ts b/packages/grafana-data/src/geo/layer.ts index 51d645859fd..28701e6e092 100644 --- a/packages/grafana-data/src/geo/layer.ts +++ b/packages/grafana-data/src/geo/layer.ts @@ -33,8 +33,8 @@ export interface FrameGeometrySource { wkt?: string; lookup?: string; - // Path to a mappings file - lookupSrc?: string; + // Path to Gazetteer + gazetteer?: string; } /** @@ -97,7 +97,7 @@ export interface MapLayerRegistryItem extends Registr * Function that configures transformation and returns a transformer * @param options */ - create: (map: Map, options: MapLayerOptions, theme: GrafanaTheme2) => MapLayerHandler; + create: (map: Map, options: MapLayerOptions, theme: GrafanaTheme2) => Promise; /** * Show custom elements in the panel edit UI diff --git a/public/app/plugins/datasource/testdata/components/USAQueryEditor.tsx b/public/app/plugins/datasource/testdata/components/USAQueryEditor.tsx index e64d230bb81..d546ed8e3e6 100644 --- a/public/app/plugins/datasource/testdata/components/USAQueryEditor.tsx +++ b/public/app/plugins/datasource/testdata/components/USAQueryEditor.tsx @@ -22,19 +22,6 @@ export function USAQueryEditor({ query, onChange }: Props) { value={usaQueryModes.find((ep) => ep.value === query.mode)} /> - - - { if (options.layers !== oldOptions.layers) { console.log('layers changed'); - this.initLayers(options.layers ?? []); + this.initLayers(options.layers ?? []); // async layersChanged = true; } return layersChanged; @@ -119,7 +119,7 @@ export class GeomapPanel extends Component { this.overlayProps.bottomLeft = legends; } - initMapRef = (div: HTMLDivElement) => { + initMapRef = async (div: HTMLDivElement) => { if (this.map) { this.map.dispose(); } @@ -143,12 +143,11 @@ export class GeomapPanel extends Component { this.map.addInteraction(this.mouseWheelZoom); this.initControls(options.controls); this.initBasemap(options.basemap); - this.initLayers(options.layers); - this.dataChanged(this.props.data, options.controls.showLegend); + await this.initLayers(options.layers, options.controls?.showLegend); this.forceUpdate(); // first render }; - initBasemap(cfg: MapLayerOptions) { + async initBasemap(cfg: MapLayerOptions) { if (!this.map) { return; } @@ -157,7 +156,8 @@ export class GeomapPanel extends Component { cfg = DEFAULT_BASEMAP_CONFIG; } const item = geomapLayerRegistry.getIfExists(cfg.type) ?? defaultBaseLayer; - const layer = item.create(this.map, cfg, config.theme2).init(); + const handler = await item.create(this.map, cfg, config.theme2); + const layer = handler.init(); if (this.basemap) { this.map.removeLayer(this.basemap); this.basemap.dispose(); @@ -166,7 +166,7 @@ export class GeomapPanel extends Component { this.map.getLayers().insertAt(0, this.basemap); } - initLayers(layers: MapLayerOptions[]) { + async initLayers(layers: MapLayerOptions[], showLegend?: boolean) { // 1st remove existing layers for (const state of this.layers) { this.map!.removeLayer(state.layer); @@ -185,7 +185,7 @@ export class GeomapPanel extends Component { continue; // TODO -- panel warning? } - const handler = item.create(this.map!, overlay, config.theme2); + const handler = await item.create(this.map!, overlay, config.theme2); const layer = handler.init(); this.map!.addLayer(layer); this.layers.push({ @@ -194,6 +194,9 @@ export class GeomapPanel extends Component { handler, }); } + + // Update data after init layers + this.dataChanged(this.props.data); } initMapView(config: MapViewConfig): View { diff --git a/public/app/plugins/panel/geomap/editor/GazetteerPathEditor.tsx b/public/app/plugins/panel/geomap/editor/GazetteerPathEditor.tsx new file mode 100644 index 00000000000..4164d027fc0 --- /dev/null +++ b/public/app/plugins/panel/geomap/editor/GazetteerPathEditor.tsx @@ -0,0 +1,85 @@ +import React, { FC, useMemo, useState, useEffect } from 'react'; +import { StandardEditorProps, SelectableValue, GrafanaTheme2 } from '@grafana/data'; +import { Alert, Select, stylesFactory, useTheme2 } from '@grafana/ui'; +import { COUNTRIES_GAZETTEER_PATH, Gazetteer, getGazetteer } from '../gazetteer/gazetteer'; +import { css } from '@emotion/css'; + +const paths: Array> = [ + { + label: 'Countries', + description: 'Lookup countries by name, two letter code, or three leter code', + value: COUNTRIES_GAZETTEER_PATH, + }, + { + label: 'USA States', + description: 'Lookup states by name or 2 ', + value: 'public/gazetteer/usa-states.json', + }, +]; + +export const GazetteerPathEditor: FC> = ({ value, onChange, context }) => { + const styles = getStyles(useTheme2()); + const [gaz, setGaz] = useState(); + + useEffect(() => { + async function fetchData() { + const p = await getGazetteer(value); + setGaz(p); + } + fetchData(); + }, [value, setGaz]); + + const { current, options } = useMemo(() => { + let options = [...paths]; + let current = options.find((f) => f.value === gaz?.path); + if (!current && gaz) { + current = { + label: gaz.path, + value: gaz.path, + }; + options.push(current); + } + return { options, current }; + }, [gaz]); + + return ( + <> +