location.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import { Geometry } from 'ol/geom';
  2. import {
  3. FrameGeometrySource,
  4. FrameGeometrySourceMode,
  5. FieldMatcher,
  6. getFieldMatcher,
  7. FieldMatcherID,
  8. DataFrame,
  9. Field,
  10. getFieldDisplayName,
  11. FieldType,
  12. } from '@grafana/data';
  13. import { getGeoFieldFromGazetteer, pointFieldFromGeohash, pointFieldFromLonLat } from '../format/utils';
  14. import { getGazetteer, Gazetteer } from '../gazetteer/gazetteer';
  15. export type FieldFinder = (frame: DataFrame) => Field | undefined;
  16. function getFieldFinder(matcher: FieldMatcher): FieldFinder {
  17. return (frame: DataFrame) => {
  18. for (const field of frame.fields) {
  19. if (matcher(field, frame, [])) {
  20. return field;
  21. }
  22. }
  23. return undefined;
  24. };
  25. }
  26. function matchLowerNames(names: Set<string>): FieldFinder {
  27. return (frame: DataFrame) => {
  28. for (const field of frame.fields) {
  29. if (names.has(field.name.toLowerCase())) {
  30. return field;
  31. }
  32. const disp = getFieldDisplayName(field, frame);
  33. if (names.has(disp)) {
  34. return field;
  35. }
  36. }
  37. return undefined;
  38. };
  39. }
  40. export interface LocationFieldMatchers {
  41. mode: FrameGeometrySourceMode;
  42. // Field mappings
  43. geohash: FieldFinder;
  44. latitude: FieldFinder;
  45. longitude: FieldFinder;
  46. h3: FieldFinder;
  47. wkt: FieldFinder;
  48. lookup: FieldFinder;
  49. geo: FieldFinder;
  50. gazetteer?: Gazetteer;
  51. }
  52. const defaultMatchers: LocationFieldMatchers = {
  53. mode: FrameGeometrySourceMode.Auto,
  54. geohash: matchLowerNames(new Set(['geohash'])),
  55. latitude: matchLowerNames(new Set(['latitude', 'lat'])),
  56. longitude: matchLowerNames(new Set(['longitude', 'lon', 'lng'])),
  57. h3: matchLowerNames(new Set(['h3'])),
  58. wkt: matchLowerNames(new Set(['wkt'])),
  59. lookup: matchLowerNames(new Set(['lookup'])),
  60. geo: (frame: DataFrame) => frame.fields.find((f) => f.type === FieldType.geo),
  61. };
  62. export async function getLocationMatchers(src?: FrameGeometrySource): Promise<LocationFieldMatchers> {
  63. const info: LocationFieldMatchers = {
  64. ...defaultMatchers,
  65. mode: src?.mode ?? FrameGeometrySourceMode.Auto,
  66. };
  67. switch (info.mode) {
  68. case FrameGeometrySourceMode.Geohash:
  69. if (src?.geohash) {
  70. info.geohash = getFieldFinder(getFieldMatcher({ id: FieldMatcherID.byName, options: src.geohash }));
  71. }
  72. break;
  73. case FrameGeometrySourceMode.Lookup:
  74. if (src?.lookup) {
  75. info.lookup = getFieldFinder(getFieldMatcher({ id: FieldMatcherID.byName, options: src.lookup }));
  76. }
  77. info.gazetteer = await getGazetteer(src?.gazetteer);
  78. break;
  79. case FrameGeometrySourceMode.Coords:
  80. if (src?.latitude) {
  81. info.latitude = getFieldFinder(getFieldMatcher({ id: FieldMatcherID.byName, options: src.latitude }));
  82. }
  83. if (src?.longitude) {
  84. info.longitude = getFieldFinder(getFieldMatcher({ id: FieldMatcherID.byName, options: src.longitude }));
  85. }
  86. break;
  87. }
  88. return info;
  89. }
  90. export interface LocationFields {
  91. mode: FrameGeometrySourceMode;
  92. // Field mappings
  93. geohash?: Field;
  94. latitude?: Field;
  95. longitude?: Field;
  96. h3?: Field;
  97. wkt?: Field;
  98. lookup?: Field;
  99. geo?: Field<Geometry>;
  100. }
  101. export function getLocationFields(frame: DataFrame, location: LocationFieldMatchers): LocationFields {
  102. const fields: LocationFields = {
  103. mode: location.mode ?? FrameGeometrySourceMode.Auto,
  104. };
  105. // Find the best option
  106. if (fields.mode === FrameGeometrySourceMode.Auto) {
  107. fields.geo = location.geo(frame);
  108. if (fields.geo) {
  109. return fields;
  110. }
  111. fields.latitude = location.latitude(frame);
  112. fields.longitude = location.longitude(frame);
  113. if (fields.latitude && fields.longitude) {
  114. fields.mode = FrameGeometrySourceMode.Coords;
  115. return fields;
  116. }
  117. fields.geohash = location.geohash(frame);
  118. if (fields.geohash) {
  119. fields.mode = FrameGeometrySourceMode.Geohash;
  120. return fields;
  121. }
  122. fields.lookup = location.geohash(frame);
  123. if (fields.lookup) {
  124. fields.mode = FrameGeometrySourceMode.Lookup;
  125. return fields;
  126. }
  127. }
  128. switch (fields.mode) {
  129. case FrameGeometrySourceMode.Coords:
  130. fields.latitude = location.latitude(frame);
  131. fields.longitude = location.longitude(frame);
  132. break;
  133. case FrameGeometrySourceMode.Geohash:
  134. fields.geohash = location.geohash(frame);
  135. break;
  136. case FrameGeometrySourceMode.Lookup:
  137. fields.lookup = location.lookup(frame);
  138. break;
  139. }
  140. return fields;
  141. }
  142. export interface FrameGeometryField {
  143. field?: Field<Geometry | undefined>;
  144. warning?: string;
  145. derived?: boolean;
  146. }
  147. export function getGeometryField(frame: DataFrame, location: LocationFieldMatchers): FrameGeometryField {
  148. const fields = getLocationFields(frame, location);
  149. switch (fields.mode) {
  150. case FrameGeometrySourceMode.Auto:
  151. if (fields.geo) {
  152. return {
  153. field: fields.geo,
  154. };
  155. }
  156. return {
  157. warning: 'Unable to find location fields',
  158. };
  159. case FrameGeometrySourceMode.Coords:
  160. if (fields.latitude && fields.longitude) {
  161. return {
  162. field: pointFieldFromLonLat(fields.longitude, fields.latitude),
  163. derived: true,
  164. };
  165. }
  166. return {
  167. warning: 'Missing latitude/longitude fields',
  168. };
  169. case FrameGeometrySourceMode.Geohash:
  170. if (fields.geohash) {
  171. return {
  172. field: pointFieldFromGeohash(fields.geohash),
  173. derived: true,
  174. };
  175. }
  176. return {
  177. warning: 'Missing geohash field',
  178. };
  179. case FrameGeometrySourceMode.Lookup:
  180. if (fields.lookup) {
  181. if (location.gazetteer) {
  182. return {
  183. field: getGeoFieldFromGazetteer(location.gazetteer, fields.lookup),
  184. derived: true,
  185. };
  186. }
  187. return {
  188. warning: 'Gazetteer not found',
  189. };
  190. }
  191. return {
  192. warning: 'Missing lookup field',
  193. };
  194. }
  195. return { warning: 'unable to find geometry' };
  196. }