Geomap: Add GeoJSON gazetteer (#40589)
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
1 parent
43878ff05f
commit
2eb88e1d1b
7 files changed
+189
-5
No files matched your search
@@ -7,7 +7,7 @@ import { css } from '@emotion/css';
|
||||
const paths: Array<SelectableValue<string>> = [
|
||||
{
|
||||
label: 'Countries',
|
||||
description: 'Lookup countries by name, two letter code, or three leter code',
|
||||
description: 'Lookup countries by name, two letter code, or three letter code',
|
||||
value: COUNTRIES_GAZETTEER_PATH,
|
||||
},
|
||||
{
|
||||
@@ -15,6 +15,11 @@ const paths: Array<SelectableValue<string>> = [
|
||||
description: 'Lookup states by name or 2 ',
|
||||
value: 'public/gazetteer/usa-states.json',
|
||||
},
|
||||
{
|
||||
label: 'Airports',
|
||||
description: 'Lookup airports by id or code',
|
||||
value: 'public/gazetteer/airports.geojson',
|
||||
},
|
||||
];
|
||||
|
||||
export const GazetteerPathEditor: FC<StandardEditorProps<string, any, any>> = ({ value, onChange, context }) => {
|
||||
@@ -60,8 +65,8 @@ export const GazetteerPathEditor: FC<StandardEditorProps<string, any, any>> = ({
|
||||
<b>({gaz.count})</b>
|
||||
{gaz.examples(10).map((k) => (
|
||||
<span key={k}>{k},</span>
|
||||
))}{' '}
|
||||
&ellipsis;
|
||||
))}
|
||||
{gaz.count > 10 && ' ...'}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { KeyValue } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { loadWorldmapPoints } from './worldmap';
|
||||
import { loadFromGeoJSON } from './geojson';
|
||||
|
||||
// http://geojson.xyz/
|
||||
|
||||
@@ -27,6 +28,12 @@ export function loadGazetteer(path: string, data: any): Gazetteer {
|
||||
}
|
||||
}
|
||||
|
||||
// try loading geojson
|
||||
const features = data?.features;
|
||||
if (Array.isArray(features) && data?.type === 'FeatureCollection') {
|
||||
return loadFromGeoJSON(path, data);
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
error: 'Unable to parse locations',
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { getGazetteer } from './gazetteer';
|
||||
|
||||
let backendResults: any = { hello: 'world' };
|
||||
|
||||
const geojsonObject = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
id: 'A',
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [0, 0],
|
||||
},
|
||||
properties: {
|
||||
hello: 'A',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [1, 1],
|
||||
},
|
||||
properties: {
|
||||
some_code: 'B',
|
||||
hello: 'B',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [2, 2],
|
||||
},
|
||||
properties: {
|
||||
an_id: 'C',
|
||||
hello: 'C',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...((jest.requireActual('@grafana/runtime') as unknown) as object),
|
||||
getBackendSrv: () => ({
|
||||
get: jest.fn().mockResolvedValue(backendResults),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Placename lookup from geojson format', () => {
|
||||
beforeEach(() => {
|
||||
backendResults = { hello: 'world' };
|
||||
});
|
||||
|
||||
it('can lookup by id', async () => {
|
||||
backendResults = geojsonObject;
|
||||
const gaz = await getGazetteer('local');
|
||||
expect(gaz.error).toBeUndefined();
|
||||
expect(gaz.find('A')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"coords": Array [
|
||||
0,
|
||||
0,
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
it('can look up by a code', async () => {
|
||||
backendResults = geojsonObject;
|
||||
const gaz = await getGazetteer('airports');
|
||||
expect(gaz.error).toBeUndefined();
|
||||
expect(gaz.find('B')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"coords": Array [
|
||||
1,
|
||||
1,
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('can look up by an id property', async () => {
|
||||
backendResults = geojsonObject;
|
||||
const gaz = await getGazetteer('airports');
|
||||
expect(gaz.error).toBeUndefined();
|
||||
expect(gaz.find('C')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"coords": Array [
|
||||
2,
|
||||
2,
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import { PlacenameInfo, Gazetteer } from './gazetteer';
|
||||
|
||||
export interface GeoJSONPoint {
|
||||
key?: string;
|
||||
keys?: string[]; // new in grafana 8.1+
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export function loadFromGeoJSON(path: string, body: any): Gazetteer {
|
||||
const data = new GeoJSON().readFeatures(body);
|
||||
let count = 0;
|
||||
const values = new Map<string, PlacenameInfo>();
|
||||
for (const f of data) {
|
||||
const coords = f.getGeometry().getFlatCoordinates(); //for now point, eventually geometry
|
||||
const info: PlacenameInfo = {
|
||||
coords: coords,
|
||||
};
|
||||
const id = f.getId();
|
||||
if (id) {
|
||||
if (typeof id === 'number') {
|
||||
values.set(id.toString(), info);
|
||||
} else {
|
||||
values.set(id, info);
|
||||
values.set(id.toUpperCase(), info);
|
||||
}
|
||||
}
|
||||
const properties = f.getProperties();
|
||||
if (properties) {
|
||||
for (const k of Object.keys(properties)) {
|
||||
if (k.includes('_code') || k.includes('_id')) {
|
||||
const value = properties[k];
|
||||
if (value) {
|
||||
if (typeof value === 'number') {
|
||||
values.set(value.toString(), info);
|
||||
} else {
|
||||
values.set(value, info);
|
||||
values.set(value.toUpperCase(), info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
find: (k) => {
|
||||
let v = values.get(k);
|
||||
if (!v && typeof k === 'string') {
|
||||
v = values.get(k.toUpperCase());
|
||||
}
|
||||
return v;
|
||||
},
|
||||
count,
|
||||
examples: (count) => {
|
||||
const first: string[] = [];
|
||||
if (values.size < 1) {
|
||||
first.push('no values found');
|
||||
} else {
|
||||
for (const key of values.keys()) {
|
||||
first.push(key);
|
||||
if (first.length >= count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return first;
|
||||
},
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -10,7 +10,7 @@ jest.mock('@grafana/runtime', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Placename lookups', () => {
|
||||
describe('Placename lookup from worldmap format', () => {
|
||||
beforeEach(() => {
|
||||
backendResults = { hello: 'world' };
|
||||
});
|
||||
@@ -37,7 +37,7 @@ export function loadWorldmapPoints(path: string, data: WorldmapPoint[]): Gazette
|
||||
path,
|
||||
find: (k) => {
|
||||
let v = values.get(k);
|
||||
if (!v) {
|
||||
if (!v && typeof k === 'string') {
|
||||
v = values.get(k.toUpperCase());
|
||||
}
|
||||
return v;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[39.797222,47.115858]},"properties":{"type":"mid","name":"Rostov S.W.","abbrev":null,"location":"runway","gps_code":null,"iata_code":null,"wikipedia":null,"natlscale":10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[21.628197,41.956455]},"properties":{"type":"mid","name":"Skopje","abbrev":"SKP","location":"terminal","gps_code":"LWSK","iata_code":"SKP","wikipedia":"http://en.wikipedia.org/wiki/Skopje_Airport","natlscale":10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[2.3838,6.358247]},"properties":{"type":"mid","name":"Cotonou Cadjehon","abbrev":"COO","location":"terminal","gps_code":"DBBB","iata_code":"COO","wikipedia":"http://en.wikipedia.org/wiki/Cadjehoun_Airport","natlscale":50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-130.445587,54.292]},"properties":{"type":"mid","name":"Prince Rupert","abbrev":"YPR","location":"terminal","gps_code":"CYPR","iata_code":"YPR","wikipedia":"http://en.wikipedia.org/wiki/Prince_Rupert_Airport","natlscale":50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[31.016249,27.050816]},"properties":{"type":"mid","name":"Asyut","abbrev":"ATZ","location":"terminal","gps_code":"HEAT","iata_code":"ATZ","wikipedia":"http://en.wikipedia.org/wiki/Assiut_Airport","natlscale":20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.441642,64.507221]},"properties":{"type":"mid","name":"Nome","abbrev":"OME","location":"terminal","gps_code":"PAOM","iata_code":"OME","wikipedia":"http://en.wikipedia.org/wiki/Nome_Airport","natlscale":50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[48.747107,31.343159]},"properties":{"type":"mid","name":"Ahwaz","abbrev":"AWZ","location":"terminal","gps_code":"OIAW","iata_code":"AWZ","wikipedia":"http://en.wikipedia.org/wiki/Ahwaz_Airport","natlscale":8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[75.758483,31.432942]},"properties":{"type":"military mid","name":"Adampur","abbrev":"VIAX","location":"runway","gps_code":"VIAX","iata_code":null,"wikipedia":null,"natlscale":8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[83.223522,17.727958]},"properties":{"type":"mid","name":"Vishakapatnam","abbrev":"VTZ","location":"terminal","gps_code":"VEVZ","iata_code":"VTZ","wikipedia":"http://en.wikipedia.org/wiki/Visakhapatnam_Airport","natlscale":10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[77.038894,11.030142]},"properties":{"type":"mid","name":"Coimbatore","abbrev":"CJB","location":"terminal","gps_code":"VOCB","iata_code":"CJB","wikipedia":"http://en.wikipedia.org/wiki/Coimbatore_Airport","natlscale":10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[114.246947,30.601714]},"properties":{"type":"military mid","name":"Wang-Chia Tun Airbase","abbrev":"ZHHH","location":"runway","gps_code":null,"iata_code":null,"wikipedia":null,"natlscale":20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-49.173709,-25.536]},"properties":{"type":"mid","name":"Afonso Pena Int'l","abbrev":"CWB","location":"terminal","gps_code":"SBCT","iata_code":"CWB","wikipedia":"http://en.wikipedia.org/wiki/Afonso_Pena_International_Airport","natlscale":50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[81.740378,21.185987]},"properties":{"type":"mid","name":"Raipur","abbrev":"RPR","location":"terminal","gps_code":"VARP","iata_code":"RPR","wikipedia":"http://en.wikipedia.org/wiki/Raipur_Airport","natlscale":20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[67.152128,24.898524]},"properties":{"type":"mid","name":"Karachi Civil","abbrev":"KHI","location":"terminal","gps_code":"OPKC","iata_code":"KHI","wikipedia":"http://en.wikipedia.org/wiki/Jinnah_International_Airport","natlscale":50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[8.562213,47.45239]},"properties":{"type":"major","name":"Zurich Int'l","abbrev":"ZRH","location":"terminal","gps_code":"LSZH","iata_code":"ZRH","wikipedia":"http://en.wikipedia.org/wiki/Z%C3%BCrich_Airport","natlscale":75}},{"type":"Feature","geometry":{"type":"Point","coordinates":[75.957072,30.85036]},"properties":{"type":"small","name":"Sahnewal","abbrev":"LUH","location":"terminal","gps_code":"VILD","iata_code":"LUH","wikipedia":"http://en.wikipedia.org/wiki/Sahnewal_Airport","natlscale":8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[75.93306,17.625415]},"properties":{"type":"mid","name":"Solapur","abbrev":"SSE","location":"terminal","gps_code":"VASL","iata_code":"SSE","wikipedia":"http://en.wikipedia.org/wiki/Solapur_Airport","natlscale":8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[85.323597,23.317725]},"properties":{"type":"mid","name":"Birsa Munda","abbrev":"IXR","location":"terminal","gps_code":"VERC","iata_code":"IXR","wikipedia":"http://en.wikipedia.org/wiki/Birsa_Munda_Airport","natlscale":8}},{"type":"Feature","geometry":Line truncated
|
||||
Reference in new issue
Block a user