mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
* Add and configure eslint-plugin-import * Fix the lint:ts npm command * Autofix + prettier all the files * Manually fix remaining files * Move jquery code in jest-setup to external file to safely reorder imports * Resolve issue caused by circular dependencies within Prometheus * Update .betterer.results * Fix missing // @ts-ignore * ignore iconBundle.ts * Fix missing // @ts-ignore
214 lines
5.0 KiB
TypeScript
214 lines
5.0 KiB
TypeScript
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;
|
|
}
|