geonetwork-ui 2.11.0-dev.c2c8dac56 → 2.11.0-dev.c830efcd7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/fesm2022/geonetwork-ui.mjs +2052 -1468
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +303 -124
  4. package/index.d.ts.map +1 -1
  5. package/package.json +7 -5
  6. package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +2 -0
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/mel.records.dataset+featurecatalog.ts +317 -0
  8. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +35 -8
  9. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +19 -0
  10. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +2 -0
  11. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +98 -0
  12. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +7 -0
  13. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +2 -1
  14. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +134 -35
  15. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +5 -34
  16. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +6 -13
  17. package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +2 -0
  18. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +24 -2
  19. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +1 -1
  20. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +42 -62
  21. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +32 -19
  22. package/src/libs/feature/map/src/index.ts +0 -2
  23. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  24. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  25. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  26. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
  27. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +3 -1
  28. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -1
  29. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  30. package/src/libs/feature/search/src/index.ts +2 -0
  31. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +18 -4
  32. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +80 -19
  33. package/src/libs/feature/search/src/lib/geocoding/geocoding.service.ts +105 -0
  34. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +18 -0
  35. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +114 -0
  36. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +19 -8
  37. package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
  38. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +64 -4
  39. package/src/libs/feature/search/src/lib/utils/service/fields.ts +92 -9
  40. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +2 -2
  41. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +2 -2
  42. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  43. package/src/libs/ui/inputs/src/index.ts +2 -0
  44. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.css +0 -4
  45. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +12 -3
  46. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +11 -3
  47. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  48. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  49. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  50. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  51. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  52. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  53. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  54. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  55. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  56. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  57. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  58. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  59. package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +2 -1
  60. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  61. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +244 -0
  62. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  63. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  64. package/src/libs/util/app-config/src/lib/app-config.ts +37 -0
  65. package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
  66. package/src/libs/util/app-config/src/lib/model.ts +25 -1
  67. package/src/libs/util/shared/src/index.ts +1 -0
  68. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  69. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  70. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  71. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  72. package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
  73. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  74. package/tailwind.base.css +5 -0
  75. package/translations/de.json +24 -4
  76. package/translations/en.json +23 -3
  77. package/translations/es.json +22 -2
  78. package/translations/fr.json +28 -8
  79. package/translations/it.json +22 -2
  80. package/translations/nl.json +22 -2
  81. package/translations/pt.json +22 -2
  82. package/translations/sk.json +22 -2
  83. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  84. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  85. package/src/libs/feature/map/src/lib/geocoding.service.ts +0 -57
  86. package/src/libs/feature/map/src/lib/style/index.ts +0 -2
  87. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  88. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
  89. /package/src/libs/{feature/map/src/lib/geocoding/geocoding.component.css → ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css} +0 -0
@@ -34,7 +34,12 @@ import {
34
34
  LanguageCode,
35
35
  } from '../../../../../../../libs/common/domain/src/lib/model/record'
36
36
  import { TranslateService } from '@ngx-translate/core'
37
- import { getGeometryBoundingBox } from '../../../../../../../libs/util/shared/src'
37
+ import {
38
+ bboxToPolygon,
39
+ BoundingBox,
40
+ getGeometryBoundingBox,
41
+ isBoundingBox,
42
+ } from '../../../../../../../libs/util/shared/src'
38
43
  import { getLength as getGeodesicLength } from 'ol/sphere.js'
39
44
  import { LineString } from 'ol/geom.js'
40
45
 
@@ -49,7 +54,12 @@ export class ElasticsearchService {
49
54
 
50
55
  // runtime fields are computed using a Painless script
51
56
  // see: https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-mapping-fields.html
52
- private runtimeFields: Record<string, string> = {}
57
+ private runtimeFields: Record<
58
+ string,
59
+ { script: string; type: 'keyword' | 'date' }
60
+ > = {}
61
+
62
+ private fieldAliases: Record<string, string> = {}
53
63
 
54
64
  // we're using getters in case the defined languages change over time
55
65
  private get metadataLang(): LanguageCode {
@@ -91,8 +101,8 @@ export class ElasticsearchService {
91
101
  const addMapping = (fieldName: string) => {
92
102
  if (!payload.runtime_mappings) payload.runtime_mappings = {}
93
103
  payload.runtime_mappings[fieldName] = {
94
- type: 'keyword',
95
- script: this.runtimeFields[fieldName],
104
+ type: this.runtimeFields[fieldName].type,
105
+ script: this.runtimeFields[fieldName].script,
96
106
  }
97
107
  }
98
108
  const lookForField = (node: unknown) => {
@@ -113,6 +123,14 @@ export class ElasticsearchService {
113
123
  ) {
114
124
  addMapping(runtimeField)
115
125
  }
126
+ if (
127
+ runtimeField in node &&
128
+ typeof node[runtimeField] === 'object' &&
129
+ node[runtimeField] !== null &&
130
+ ('gte' in node[runtimeField] || 'lte' in node[runtimeField])
131
+ ) {
132
+ addMapping(runtimeField)
133
+ }
116
134
  if (
117
135
  'query' in node &&
118
136
  typeof node.query === 'string' &&
@@ -131,8 +149,16 @@ export class ElasticsearchService {
131
149
  return payload
132
150
  }
133
151
 
134
- registerRuntimeField(fieldName: string, expression: string) {
135
- this.runtimeFields[fieldName] = expression
152
+ registerRuntimeField(
153
+ fieldName: string,
154
+ expression: string,
155
+ type: 'keyword' | 'date' = 'keyword'
156
+ ) {
157
+ this.runtimeFields[fieldName] = { script: expression, type }
158
+ }
159
+
160
+ registerFieldAlias(fieldIdentifier: string, esFieldName: string) {
161
+ this.fieldAliases[fieldIdentifier] = esFieldName
136
162
  }
137
163
 
138
164
  getMetadataByIdsPayload(uuids: string[]): EsSearchParams {
@@ -264,8 +290,68 @@ export class ElasticsearchService {
264
290
  return this.metadataLang === 'current'
265
291
  }
266
292
 
267
- private filtersToQuery(
293
+ private findSpatialFilterExtent(
268
294
  filters: FieldFilters | FiltersAggregationParams | string
295
+ ): BoundingBox | undefined {
296
+ if (typeof filters === 'string') {
297
+ return undefined
298
+ }
299
+ return Object.values(filters).find(isBoundingBox)
300
+ }
301
+
302
+ // builds a query matching records whose dates intersect the filter range
303
+ private buildDateRangeQuery(searchField: string, dateRange: DateRange) {
304
+ // fields ending with 'DateRange' maps as ES range type
305
+ if (searchField.endsWith('DateRange')) {
306
+ return {
307
+ range: {
308
+ [searchField]: {
309
+ ...(dateRange.start && { gte: formatDate(dateRange.start) }),
310
+ ...(dateRange.end && { lte: formatDate(dateRange.end) }),
311
+ format: 'yyyy-MM-dd',
312
+ relation: 'intersects',
313
+ },
314
+ },
315
+ }
316
+ }
317
+ const minField = `${searchField}Min`
318
+ const maxField = `${searchField}Max`
319
+ if (minField in this.runtimeFields && maxField in this.runtimeFields) {
320
+ // min/max runtime fields form an interval per record; match if it intersects the filter range
321
+ const filter = [
322
+ dateRange.start && {
323
+ range: {
324
+ [maxField]: {
325
+ gte: formatDate(dateRange.start),
326
+ format: 'yyyy-MM-dd',
327
+ },
328
+ },
329
+ },
330
+ dateRange.end && {
331
+ range: {
332
+ [minField]: {
333
+ lte: formatDate(dateRange.end),
334
+ format: 'yyyy-MM-dd',
335
+ },
336
+ },
337
+ },
338
+ ].filter(Boolean)
339
+ return { bool: { filter } }
340
+ }
341
+ return {
342
+ range: {
343
+ [searchField]: {
344
+ ...(dateRange.start && { gte: formatDate(dateRange.start) }),
345
+ ...(dateRange.end && { lte: formatDate(dateRange.end) }),
346
+ format: 'yyyy-MM-dd',
347
+ },
348
+ },
349
+ }
350
+ }
351
+
352
+ private filtersToQuery(
353
+ filters: FieldFilters | FiltersAggregationParams | string,
354
+ spatialFilterExtent = this.findSpatialFilterExtent(filters)
269
355
  ): FilterQuery {
270
356
  const addQuote = (key: string) => (/^\/.+\/$/.test(key) ? key : `"${key}"`)
271
357
  const makeQuery = (filter: FieldFilter): string => {
@@ -286,50 +372,49 @@ export class ElasticsearchService {
286
372
  ? filters
287
373
  : Object.keys(filters)
288
374
  .filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
375
+ .filter((fieldname) => !isBoundingBox(filters[fieldname]))
289
376
  .filter((fieldname) => !isDateRange(filters[fieldname]))
377
+ .filter((fieldname) => !Array.isArray(filters[fieldname]))
290
378
  .filter(
291
379
  (fieldname) =>
292
380
  filters[fieldname] &&
293
381
  JSON.stringify(filters[fieldname]) !== '{}'
294
382
  )
295
383
  .map(
296
- (fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`
384
+ (fieldname) =>
385
+ `${this.fieldAliases[fieldname] ?? fieldname}:(${makeQuery(filters[fieldname])})`
297
386
  )
298
387
  .join(' AND ')
299
388
  if (filters['gn-ui-crossFieldFilter']) {
300
389
  queryString = `${queryString} AND (${filters['gn-ui-crossFieldFilter']})`
301
390
  }
302
- const queryRange = Object.entries(filters)
303
- .filter(([, value]) => isDateRange(value))
304
- .map(([searchField, dateRange]) => {
305
- return {
306
- searchField,
307
- dateRange,
308
- } as {
309
- searchField: string
310
- dateRange: DateRange
311
- }
312
- })[0]
391
+ const queryRanges = Object.entries(filters).filter(([, value]) =>
392
+ isDateRange(value)
393
+ ) as [string, DateRange][]
313
394
  const queryParts = [
314
395
  queryString && {
315
396
  query_string: {
316
397
  query: queryString,
317
398
  },
318
399
  },
319
- queryRange &&
320
- queryRange.dateRange && {
321
- range: {
322
- [queryRange.searchField]: {
323
- ...(queryRange.dateRange.start && {
324
- gte: formatDate(queryRange.dateRange.start),
325
- }),
326
- ...(queryRange.dateRange.end && {
327
- lte: formatDate(queryRange.dateRange.end),
328
- }),
329
- format: 'yyyy-MM-dd',
400
+ ...queryRanges.map(([searchField, dateRange]) =>
401
+ this.buildDateRangeQuery(searchField, dateRange)
402
+ ),
403
+ spatialFilterExtent && {
404
+ geo_shape: {
405
+ geom: {
406
+ shape: {
407
+ type: 'envelope',
408
+ // spatialFilterExtent is [minX, minY, maxX, maxY]; envelope coordinates are [top-left, bottom-right]
409
+ coordinates: [
410
+ [spatialFilterExtent[0], spatialFilterExtent[3]],
411
+ [spatialFilterExtent[2], spatialFilterExtent[1]],
412
+ ],
330
413
  },
414
+ relation: 'intersects',
331
415
  },
332
416
  },
417
+ },
333
418
  ].filter(Boolean)
334
419
  return queryParts.length > 0 ? (queryParts as FilterQuery) : undefined
335
420
  }
@@ -368,7 +453,12 @@ export class ElasticsearchService {
368
453
  },
369
454
  })
370
455
  }
371
- const queryFilters = this.filtersToQuery(fieldSearchFilters)
456
+ // a spatial extent filter takes precedence over the preference geometry for boosting
457
+ const spatialFilterExtent = this.findSpatialFilterExtent(fieldSearchFilters)
458
+ const queryFilters = this.filtersToQuery(
459
+ fieldSearchFilters,
460
+ spatialFilterExtent
461
+ )
372
462
  if (queryFilters) {
373
463
  filter.push(...queryFilters)
374
464
  }
@@ -379,7 +469,10 @@ export class ElasticsearchService {
379
469
  },
380
470
  })
381
471
  }
382
- if (geometry) {
472
+ const boostGeometry = spatialFilterExtent
473
+ ? bboxToPolygon(spatialFilterExtent)
474
+ : geometry
475
+ if (boostGeometry) {
383
476
  // boosts applied using the filter geometry:
384
477
  // * records completely within the geometry receive a boost of 5
385
478
  // * records intersecting the geometry receive a boost of 2
@@ -389,7 +482,7 @@ export class ElasticsearchService {
389
482
  {
390
483
  geo_shape: {
391
484
  geom: {
392
- shape: geometry,
485
+ shape: boostGeometry,
393
486
  relation: 'within',
394
487
  },
395
488
  boost: 5.0,
@@ -398,7 +491,7 @@ export class ElasticsearchService {
398
491
  {
399
492
  geo_shape: {
400
493
  geom: {
401
- shape: geometry,
494
+ shape: boostGeometry,
402
495
  relation: 'intersects',
403
496
  },
404
497
  boost: 2.0,
@@ -409,7 +502,7 @@ export class ElasticsearchService {
409
502
  // this will boost the results variably depending on their distance from the given geometry
410
503
  // note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
411
504
  // combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
412
- const bbox = getGeometryBoundingBox(geometry)
505
+ const bbox = getGeometryBoundingBox(boostGeometry)
413
506
  const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]
414
507
  const northToCenter = new LineString([
415
508
  [center[0], bbox[3]],
@@ -635,6 +728,12 @@ export class ElasticsearchService {
635
728
  order: {
636
729
  [`_${aggregation.sort[1]}`]: aggregation.sort[0],
637
730
  },
731
+ ...(aggregation.includeValues && {
732
+ include: aggregation.includeValues,
733
+ }),
734
+ ...(aggregation.excludeValues && {
735
+ exclude: aggregation.excludeValues,
736
+ }),
638
737
  },
639
738
  }
640
739
  case 'histogram':
@@ -17,7 +17,6 @@ import {
17
17
  AssociatedRecord,
18
18
  CatalogRecord,
19
19
  DatasetFeatureCatalog,
20
- DatasetFeatureType,
21
20
  LanguageCode,
22
21
  LinkedRecord,
23
22
  RecordRelation,
@@ -182,43 +181,15 @@ export class Gn4Repository implements RecordsRepositoryInterface {
182
181
  )
183
182
  }
184
183
 
185
- private mapEmbeddedFeatureCatalog(
186
- featureTypes: Array<DatasetFeatureType>
187
- ): DatasetFeatureCatalog {
188
- return {
189
- featureTypes: featureTypes.map((featureType) => ({
190
- name: featureType.typeName || '',
191
- definition: featureType.definition || '',
192
- attributes: Array.isArray(featureType.attributeTable)
193
- ? featureType.attributeTable.map((attr) => {
194
- const values = attr.values
195
- ?.filter((v) => v.code || v.label)
196
- .map((v) => ({
197
- code: v.code,
198
- label: v.label,
199
- }))
200
- return {
201
- name: attr.name,
202
- code: attr.code,
203
- definition: attr.definition,
204
- type: attr.type,
205
- ...(values?.length > 0 ? { values } : {}),
206
- }
207
- })
208
- : [],
209
- })),
210
- }
211
- }
212
- getFeatureCatalog(
184
+ queryLinkedFeatureCatalog(
213
185
  record: CatalogRecord,
214
186
  visited: Set<string> = new Set() // prevent looping
215
187
  ): Observable<DatasetFeatureCatalog | null> {
216
188
  if (
217
- record.extras?.['featureTypes'] &&
218
- Array.isArray(record.extras['featureTypes']) &&
219
- record.extras['featureTypes'].length > 0
189
+ 'featureTypeDescriptions' in record &&
190
+ record.featureTypeDescriptions?.length > 0
220
191
  ) {
221
- return of(this.mapEmbeddedFeatureCatalog(record.extras['featureTypes']))
192
+ return of({ featureTypes: record.featureTypeDescriptions })
222
193
  }
223
194
 
224
195
  const featureCatalogIdentifier = record.extras[
@@ -228,7 +199,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
228
199
  visited.add(featureCatalogIdentifier)
229
200
  return this.getRecord(featureCatalogIdentifier).pipe(
230
201
  switchMap((record) =>
231
- record ? this.getFeatureCatalog(record, visited) : of(null)
202
+ record ? this.queryLinkedFeatureCatalog(record, visited) : of(null)
232
203
  )
233
204
  )
234
205
  }
@@ -316,6 +316,7 @@ export interface DatasetRecord extends BaseRecord {
316
316
  spatialExtents: Array<DatasetSpatialExtent>
317
317
  temporalExtents: Array<DatasetTemporalExtent>
318
318
  spatialRepresentation?: SpatialRepresentationType
319
+ featureTypeDescriptions?: DatasetFeatureCatalog['featureTypes']
319
320
  }
320
321
 
321
322
  export interface ServiceEndpoint {
@@ -350,30 +351,22 @@ export interface ReuseRecord extends BaseRecord {
350
351
  export type DatasetFeatureCatalog = {
351
352
  featureTypes: Array<{
352
353
  name: string
353
- definition: string
354
+ description?: string
354
355
  attributes: DatasetFeatureAttribute[]
355
356
  }>
356
357
  }
357
358
  export interface DatasetFeatureAttributeValue {
358
359
  code?: string
359
- definition?: string
360
+ description?: string
360
361
  label?: string
361
362
  }
362
363
  export interface DatasetFeatureAttribute {
363
364
  name: string
364
- definition: string
365
- type: string
366
- code: string
365
+ description?: string
366
+ type?: string
367
+ code?: string
367
368
  values?: Array<DatasetFeatureAttributeValue>
368
369
  }
369
- export interface DatasetFeatureType {
370
- aliases: string
371
- code: string
372
- isAbstract: string
373
- typeName: string
374
- definition: string
375
- attributeTable: Array<DatasetFeatureAttribute>
376
- }
377
370
 
378
371
  export type ReuseType = 'application' | 'map' | 'other'
379
372
 
@@ -7,6 +7,8 @@ export interface TermsAggregationParams {
7
7
  limit: number
8
8
  sort: AggregationSort
9
9
  filter?: string
10
+ includeValues?: string[]
11
+ excludeValues?: string[]
10
12
  }
11
13
  export interface HistogramAggregationParams {
12
14
  type: 'histogram'
@@ -6,17 +6,39 @@ export type FieldFilterByRange = {
6
6
  start?: Date
7
7
  end?: Date
8
8
  }
9
+ // matches util/shared's BoundingBox; duplicated here to avoid a circular
10
+ // dependency (util/shared already depends on common/domain)
11
+ export type FieldFilterByBoundingBox = [number, number, number, number]
9
12
 
10
13
  export type FieldFilter =
11
14
  | FieldFilterByExpression
12
15
  | FieldFilterByValues
13
16
  | FieldFilterByRange
17
+ | FieldFilterByBoundingBox
14
18
  export type FieldFilters = Record<FieldName, FieldFilter>
15
19
 
16
20
  export type QueryString = {
17
21
  query_string: string
18
22
  }
19
23
  export type QueryRange = {
20
- range: Record<string, { format: string; gte: string; lte: string }>
24
+ range: Record<
25
+ string,
26
+ { format: string; gte?: string; lte?: string; relation?: string }
27
+ >
21
28
  }
22
- export type FilterQuery = Array<QueryString | QueryRange>
29
+ export type QueryGeoShape = {
30
+ geo_shape: {
31
+ geom: {
32
+ shape: { type: string; coordinates: number[][] }
33
+ relation: string
34
+ }
35
+ }
36
+ }
37
+ export type QueryBool = {
38
+ bool: {
39
+ filter: QueryRange[]
40
+ }
41
+ }
42
+ export type FilterQuery = Array<
43
+ QueryString | QueryRange | QueryGeoShape | QueryBool
44
+ >
@@ -17,7 +17,7 @@ export abstract class RecordsRepositoryInterface {
17
17
  abstract search(params: SearchParams): Observable<SearchResults>
18
18
  abstract getMatchesCount(filters: FieldFilters): Observable<number>
19
19
  abstract getRecord(uniqueIdentifier: string): Observable<CatalogRecord | null>
20
- abstract getFeatureCatalog(
20
+ abstract queryLinkedFeatureCatalog(
21
21
  record: CatalogRecord
22
22
  ): Observable<DatasetFeatureCatalog | null>
23
23
  abstract aggregate(params: AggregationsParams): Observable<Aggregations>
@@ -164,69 +164,49 @@ As such, **it is not very interesting at all.**`,
164
164
  extras: {
165
165
  isPublishedToAll: true,
166
166
  edit: true,
167
- featureTypes: [
168
- {
169
- attributeTable: [
170
- {
171
- code: 'OBJECTID',
172
- name: 'OBJECTID',
173
- link: '',
174
- definition: 'Object identifier',
175
- type: 'OID',
176
- },
177
- {
178
- code: 'NOM',
179
- name: 'Nom',
180
- link: '',
181
- definition: 'Nom de la rue',
182
- type: 'String (48)',
183
- values: [
184
- {
185
- code: 'Pomme',
186
- label: 'Les Pommiers',
187
- description: 'Sous les pommiers',
188
- },
189
- {
190
- code: 'Cotton',
191
- label: 'Rue Cotton',
192
- description: 'Rue Cotton',
193
- },
194
- { code: "Passage de l'échiquier" },
195
- { description: 'Rue du Charlieu' },
196
- ],
197
- },
198
- {
199
- code: 'RUE',
200
- name: 'Rue',
201
- link: '',
202
- definition: '',
203
- type: 'String (50)',
204
- },
205
- ],
206
- code: '',
207
- aliases: '',
208
- typeName: "Catalogue d'attributs N°1",
209
- definition: 'Définition du catalogue d attributs N°1',
210
- isAbstract: 'false',
211
- },
212
- {
213
- attributeTable: [
214
- {
215
- code: 'UniqueObject',
216
- name: 'unique object ',
217
- link: '',
218
- definition: 'this is the only object of this catalog',
219
- type: 'String (50)',
220
- },
221
- ],
222
- code: '',
223
- aliases: '',
224
- typeName: "Catalogue d'attributs N°2",
225
- definition: 'Définition du catalogue d attributs N°2',
226
- isAbstract: 'false',
227
- },
228
- ],
229
167
  },
168
+ featureTypeDescriptions: [
169
+ {
170
+ name: "Catalogue d'attributs N°1",
171
+ description: 'Définition du catalogue d attributs N°1',
172
+ attributes: [
173
+ {
174
+ name: 'OBJECTID',
175
+ description: 'Object identifier',
176
+ code: 'OBJECTID',
177
+ type: 'OID',
178
+ },
179
+ {
180
+ name: 'Nom',
181
+ description: 'Nom de la rue',
182
+ code: 'NOM',
183
+ type: 'String (48)',
184
+ values: [
185
+ { code: 'Pomme', label: 'Les Pommiers' },
186
+ { code: 'Cotton', label: 'Rue Cotton' },
187
+ { code: "Passage de l'échiquier" },
188
+ ],
189
+ },
190
+ {
191
+ name: 'Rue',
192
+ code: 'RUE',
193
+ type: 'String (50)',
194
+ },
195
+ ],
196
+ },
197
+ {
198
+ name: "Catalogue d'attributs N°2",
199
+ description: 'Définition du catalogue d attributs N°2',
200
+ attributes: [
201
+ {
202
+ name: 'unique object ',
203
+ description: 'this is the only object of this catalog',
204
+ code: 'UniqueObject',
205
+ type: 'String (50)',
206
+ },
207
+ ],
208
+ },
209
+ ],
230
210
  },
231
211
  {
232
212
  uniqueIdentifier: '7d002c4c-92ef-4b9f-a568-d732f740b99e',
@@ -209,7 +209,8 @@ export class DataService {
209
209
  ogcApiLink: DatasetServiceDistribution
210
210
  ): Promise<DatasetOnlineResource[]> {
211
211
  const collectionInfo = await this.getDownloadUrlsFromOgcApi(
212
- ogcApiLink.url.href
212
+ ogcApiLink.url.href,
213
+ ogcApiLink.name
213
214
  )
214
215
 
215
216
  return Object.keys(collectionInfo.bulkDownloadLinks).map((mimeType) => {
@@ -229,31 +230,41 @@ export class DataService {
229
230
  })
230
231
  }
231
232
 
232
- async getDownloadUrlsFromOgcApi(url: string): Promise<OgcApiCollectionInfo> {
233
+ private async getTargetCollectionId(
234
+ endpoint: OgcApiEndpoint,
235
+ collectionId?: string
236
+ ): Promise<string | undefined> {
237
+ const collections = await endpoint.featureCollections
238
+ return collections.length === 1 && !collectionId
239
+ ? collections[0]
240
+ : collectionId
241
+ }
242
+
243
+ async getDownloadUrlsFromOgcApi(
244
+ url: string,
245
+ collectionId?: string
246
+ ): Promise<OgcApiCollectionInfo> {
233
247
  const endpoint = new OgcApiEndpoint(url)
234
- return await endpoint.featureCollections
235
- .then((collections) => {
236
- return endpoint.getCollectionInfo(collections[0])
237
- })
238
- .catch((error) => {
239
- throw new Error(`ogc.unreachable.unknown`)
240
- })
248
+ try {
249
+ const id = await this.getTargetCollectionId(endpoint, collectionId)
250
+ return await endpoint.getCollectionInfo(id)
251
+ } catch {
252
+ throw new Error('ogc.unreachable.unknown')
253
+ }
241
254
  }
242
255
 
243
256
  async getItemsFromOgcApi(
244
257
  url: string,
258
+ collectionId?: string,
245
259
  limit?: number
246
260
  ): Promise<OgcApiRecord[]> {
247
261
  const endpoint = new OgcApiEndpoint(url)
248
- return await endpoint.featureCollections
249
- .then((collections) => {
250
- return collections.length
251
- ? endpoint.getCollectionItems(collections[0], limit)
252
- : null
253
- })
254
- .catch(() => {
255
- throw new Error(`ogc.unreachable.unknown`)
256
- })
262
+ try {
263
+ const id = await this.getTargetCollectionId(endpoint, collectionId)
264
+ return await endpoint.getCollectionItems(id, limit)
265
+ } catch {
266
+ throw new Error('ogc.unreachable.unknown')
267
+ }
257
268
  }
258
269
 
259
270
  async getItemsFromStacApi(
@@ -381,7 +392,9 @@ export class DataService {
381
392
  link.type === 'service' &&
382
393
  link.accessServiceProtocol === 'ogcFeatures'
383
394
  ) {
384
- return from(this.getDownloadUrlsFromOgcApi(link.url.href)).pipe(
395
+ return from(
396
+ this.getDownloadUrlsFromOgcApi(link.url.href, link.name)
397
+ ).pipe(
385
398
  switchMap((collectionInfo) => {
386
399
  const isMimeTypeJson = (mimeType: string): boolean => {
387
400
  return mimeType.toLowerCase().indexOf('json') > -1
@@ -6,8 +6,6 @@ export * from './lib/feature-map.module'
6
6
  export * from './lib/map-state-container/map-state-container.component'
7
7
  export * from './lib/constant'
8
8
  export * from './lib/utils'
9
- export * from './lib/style'
10
9
  export * from './lib/layers-panel/layers-panel.component'
11
10
  export * from './lib/add-layer-from-catalog/add-layer-from-catalog.component'
12
11
  export * from './lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component'
13
- export * from './lib/geocoding/geocoding.component'
@@ -5,6 +5,7 @@
5
5
  (fileChange)="handleFileChange($event)"
6
6
  [accept]="acceptedMimeType.join(',')"
7
7
  [placeholder]="'map.addFromFile.placeholder' | translate"
8
+ textClass="text-gray-900 pl-2 py-2"
8
9
  class="placeholder-grey"
9
10
  ></gn-ui-drag-and-drop-file-input>
10
11
  </div>
@@ -2,18 +2,11 @@ import { NgModule } from '@angular/core'
2
2
  import { StoreModule } from '@ngrx/store'
3
3
  import * as fromMap from './+state/map.reducer'
4
4
  import { MapFacade } from './+state/map.facade'
5
- import { GEOCODING_PROVIDER, GeocodingProvider } from './geocoding.service'
6
5
 
7
6
  @NgModule({
8
7
  imports: [
9
8
  StoreModule.forFeature(fromMap.MAP_FEATURE_KEY, fromMap.mapReducer),
10
9
  ],
11
- providers: [
12
- MapFacade,
13
- {
14
- provide: GEOCODING_PROVIDER,
15
- useValue: ['geonames', { maxRows: 5 }] as GeocodingProvider,
16
- },
17
- ],
10
+ providers: [MapFacade],
18
11
  })
19
12
  export class FeatureMapModule {}