123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- import { getCenter } from 'ol/extent';
- import { Geometry, Point } from 'ol/geom';
- import { DataFrame, Field, FieldType, KeyValue, toDataFrame } from '@grafana/data';
- import { getBackendSrv } from '@grafana/runtime';
- import { frameFromGeoJSON } from '../format/geojson';
- import { pointFieldFromLonLat, pointFieldFromGeohash } from '../format/utils';
- import { loadWorldmapPoints } from './worldmap';
- export interface PlacenameInfo {
- point: () => Point | undefined; // lon, lat (WGS84)
- geometry: () => Geometry | undefined;
- frame?: DataFrame;
- index?: number;
- }
- export interface Gazetteer {
- path: string;
- error?: string;
- find: (key: string) => PlacenameInfo | undefined;
- examples: (count: number) => string[];
- frame?: () => DataFrame;
- count?: number;
- }
- // Without knowing the datatype pick a good lookup function
- export function loadGazetteer(path: string, data: any): Gazetteer {
- // try loading geojson
- let frame: DataFrame | undefined = undefined;
- if (Array.isArray(data)) {
- const first = data[0] as any;
- // Check for legacy worldmap syntax
- if (first.latitude && first.longitude && (first.key || first.keys)) {
- return loadWorldmapPoints(path, data);
- }
- } else {
- if (Array.isArray(data?.features) && data?.type === 'FeatureCollection') {
- frame = frameFromGeoJSON(data);
- }
- }
- if (!frame) {
- try {
- frame = toDataFrame(data);
- } catch (ex) {
- return {
- path,
- error: `${ex}`,
- find: (k) => undefined,
- examples: (v) => [],
- };
- }
- }
- return frameAsGazetter(frame, { path });
- }
- export function frameAsGazetter(frame: DataFrame, opts: { path: string; keys?: string[] }): Gazetteer {
- const keys: Field[] = [];
- let geo: Field<Geometry> | undefined = undefined;
- let lat: Field | undefined = undefined;
- let lng: Field | undefined = undefined;
- let geohash: Field | undefined = undefined;
- let firstString: Field | undefined = undefined;
- for (const f of frame.fields) {
- if (f.type === FieldType.geo) {
- geo = f;
- }
- if (!firstString && f.type === FieldType.string) {
- firstString = f;
- }
- if (f.name) {
- if (opts.keys && opts.keys.includes(f.name)) {
- keys.push(f);
- }
- const name = f.name.toUpperCase();
- switch (name) {
- case 'LAT':
- case 'LATITUTE':
- lat = f;
- break;
- case 'LON':
- case 'LNG':
- case 'LONG':
- case 'LONGITUE':
- lng = f;
- break;
- case 'GEOHASH':
- geohash = f;
- break;
- case 'ID':
- case 'UID':
- case 'KEY':
- case 'CODE':
- if (!opts.keys) {
- keys.push(f);
- }
- break;
- default: {
- if (!opts.keys) {
- if (name.endsWith('_ID') || name.endsWith('_CODE')) {
- keys.push(f);
- }
- }
- }
- }
- }
- }
- // Use the first string field
- if (!keys.length && firstString) {
- keys.push(firstString);
- }
- let isPoint = false;
- // Create a geo field from lat+lng
- if (!geo) {
- if (geohash) {
- geo = pointFieldFromGeohash(geohash);
- isPoint = true;
- } else if (lat && lng) {
- geo = pointFieldFromLonLat(lng, lat);
- isPoint = true;
- }
- } else {
- isPoint = geo.values.get(0).getType() === 'Point';
- }
- const lookup = new Map<string, number>();
- keys.forEach((f) => {
- f.values.toArray().forEach((k, idx) => {
- const str = `${k}`;
- lookup.set(str.toUpperCase(), idx);
- lookup.set(str, idx);
- });
- });
- return {
- path: opts.path,
- find: (k) => {
- const index = lookup.get(k);
- if (index != null) {
- const g = geo?.values.get(index);
- return {
- frame,
- index,
- point: () => {
- if (!g || isPoint) {
- return g as Point;
- }
- return new Point(getCenter(g.getExtent()));
- },
- geometry: () => g,
- };
- }
- return undefined;
- },
- examples: (v) => {
- const ex: string[] = [];
- for (let k of lookup.keys()) {
- ex.push(k);
- if (ex.length > v) {
- break;
- }
- }
- return ex;
- },
- frame: () => frame,
- count: frame.length,
- };
- }
- const registry: KeyValue<Gazetteer> = {};
- export const COUNTRIES_GAZETTEER_PATH = 'public/gazetteer/countries.json';
- /**
- * Given a path to a file return a cached lookup function
- */
- export async function getGazetteer(path?: string): Promise<Gazetteer> {
- // When not specified, use the default path
- if (!path) {
- path = COUNTRIES_GAZETTEER_PATH;
- }
- let lookup = registry[path];
- if (!lookup) {
- try {
- // block the async function
- const data = await getBackendSrv().get(path!);
- lookup = loadGazetteer(path, data);
- } catch (err) {
- console.warn('Error loading placename lookup', path, err);
- lookup = {
- path,
- error: 'Error loading URL',
- find: (k) => undefined,
- examples: (v) => [],
- };
- }
- registry[path] = lookup;
- }
- return lookup;
- }
|