geonetwork-ui 2.11.0-dev.bc1a6d4e2 → 2.11.0-dev.bc1acb627

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 (69) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1446 -1300
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +170 -100
  4. package/index.d.ts.map +1 -1
  5. package/package.json +6 -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 +90 -31
  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 +12 -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/feature-map.module.ts +1 -8
  24. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
  25. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +3 -1
  26. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -1
  27. package/src/libs/feature/search/src/index.ts +2 -0
  28. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +8 -1
  29. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +26 -4
  30. package/src/libs/feature/search/src/lib/geocoding/geocoding.service.ts +105 -0
  31. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +18 -0
  32. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +114 -0
  33. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +2 -0
  34. package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
  35. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +62 -4
  36. package/src/libs/feature/search/src/lib/utils/service/fields.ts +67 -9
  37. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +2 -2
  38. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +2 -2
  39. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  40. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.css +0 -4
  41. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +12 -3
  42. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +11 -3
  43. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  44. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  45. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  46. package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +2 -1
  47. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +8 -2
  48. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +34 -5
  49. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  50. package/src/libs/util/app-config/src/lib/app-config.ts +34 -0
  51. package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
  52. package/src/libs/util/app-config/src/lib/model.ts +24 -1
  53. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  54. package/src/libs/util/shared/src/lib/utils/geojson.ts +16 -0
  55. package/translations/de.json +10 -2
  56. package/translations/en.json +12 -4
  57. package/translations/es.json +10 -2
  58. package/translations/fr.json +17 -9
  59. package/translations/it.json +10 -2
  60. package/translations/nl.json +10 -2
  61. package/translations/pt.json +10 -2
  62. package/translations/sk.json +10 -2
  63. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.css +0 -0
  64. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  65. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  66. package/src/libs/feature/map/src/lib/geocoding.service.ts +0 -57
  67. package/src/libs/feature/map/src/lib/style/index.ts +0 -2
  68. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  69. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
@@ -54,7 +54,12 @@ export class ElasticsearchService {
54
54
 
55
55
  // runtime fields are computed using a Painless script
56
56
  // see: https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-mapping-fields.html
57
- 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> = {}
58
63
 
59
64
  // we're using getters in case the defined languages change over time
60
65
  private get metadataLang(): LanguageCode {
@@ -96,8 +101,8 @@ export class ElasticsearchService {
96
101
  const addMapping = (fieldName: string) => {
97
102
  if (!payload.runtime_mappings) payload.runtime_mappings = {}
98
103
  payload.runtime_mappings[fieldName] = {
99
- type: 'keyword',
100
- script: this.runtimeFields[fieldName],
104
+ type: this.runtimeFields[fieldName].type,
105
+ script: this.runtimeFields[fieldName].script,
101
106
  }
102
107
  }
103
108
  const lookForField = (node: unknown) => {
@@ -118,6 +123,14 @@ export class ElasticsearchService {
118
123
  ) {
119
124
  addMapping(runtimeField)
120
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
+ }
121
134
  if (
122
135
  'query' in node &&
123
136
  typeof node.query === 'string' &&
@@ -136,8 +149,16 @@ export class ElasticsearchService {
136
149
  return payload
137
150
  }
138
151
 
139
- registerRuntimeField(fieldName: string, expression: string) {
140
- 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
141
162
  }
142
163
 
143
164
  getMetadataByIdsPayload(uuids: string[]): EsSearchParams {
@@ -278,6 +299,56 @@ export class ElasticsearchService {
278
299
  return Object.values(filters).find(isBoundingBox)
279
300
  }
280
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
+
281
352
  private filtersToQuery(
282
353
  filters: FieldFilters | FiltersAggregationParams | string,
283
354
  spatialFilterExtent = this.findSpatialFilterExtent(filters)
@@ -310,43 +381,25 @@ export class ElasticsearchService {
310
381
  JSON.stringify(filters[fieldname]) !== '{}'
311
382
  )
312
383
  .map(
313
- (fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`
384
+ (fieldname) =>
385
+ `${this.fieldAliases[fieldname] ?? fieldname}:(${makeQuery(filters[fieldname])})`
314
386
  )
315
387
  .join(' AND ')
316
388
  if (filters['gn-ui-crossFieldFilter']) {
317
389
  queryString = `${queryString} AND (${filters['gn-ui-crossFieldFilter']})`
318
390
  }
319
- const queryRange = Object.entries(filters)
320
- .filter(([, value]) => isDateRange(value))
321
- .map(([searchField, dateRange]) => {
322
- return {
323
- searchField,
324
- dateRange,
325
- } as {
326
- searchField: string
327
- dateRange: DateRange
328
- }
329
- })[0]
391
+ const queryRanges = Object.entries(filters).filter(([, value]) =>
392
+ isDateRange(value)
393
+ ) as [string, DateRange][]
330
394
  const queryParts = [
331
395
  queryString && {
332
396
  query_string: {
333
397
  query: queryString,
334
398
  },
335
399
  },
336
- queryRange &&
337
- queryRange.dateRange && {
338
- range: {
339
- [queryRange.searchField]: {
340
- ...(queryRange.dateRange.start && {
341
- gte: formatDate(queryRange.dateRange.start),
342
- }),
343
- ...(queryRange.dateRange.end && {
344
- lte: formatDate(queryRange.dateRange.end),
345
- }),
346
- format: 'yyyy-MM-dd',
347
- },
348
- },
349
- },
400
+ ...queryRanges.map(([searchField, dateRange]) =>
401
+ this.buildDateRangeQuery(searchField, dateRange)
402
+ ),
350
403
  spatialFilterExtent && {
351
404
  geo_shape: {
352
405
  geom: {
@@ -675,6 +728,12 @@ export class ElasticsearchService {
675
728
  order: {
676
729
  [`_${aggregation.sort[1]}`]: aggregation.sort[0],
677
730
  },
731
+ ...(aggregation.includeValues && {
732
+ include: aggregation.includeValues,
733
+ }),
734
+ ...(aggregation.excludeValues && {
735
+ exclude: aggregation.excludeValues,
736
+ }),
678
737
  },
679
738
  }
680
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'
@@ -21,7 +21,10 @@ export type QueryString = {
21
21
  query_string: string
22
22
  }
23
23
  export type QueryRange = {
24
- range: Record<string, { format: string; gte: string; lte: string }>
24
+ range: Record<
25
+ string,
26
+ { format: string; gte?: string; lte?: string; relation?: string }
27
+ >
25
28
  }
26
29
  export type QueryGeoShape = {
27
30
  geo_shape: {
@@ -31,4 +34,11 @@ export type QueryGeoShape = {
31
34
  }
32
35
  }
33
36
  }
34
- export type FilterQuery = Array<QueryString | QueryRange | QueryGeoShape>
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'
@@ -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 {}
@@ -33,7 +33,6 @@ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain
33
33
  import { NOT_KNOWN_CONSTRAINT } from '../../../../../../libs/feature/editor/src'
34
34
  import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
35
35
  import { ButtonComponent, TextInputComponent } from '../../../../../../libs/ui/inputs/src'
36
- import { getOptionalEditorConfig } from '../../../../../../libs/util/app-config/src'
37
36
  import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
38
37
  import { iconoirAppWindow, iconoirPlusCircle } from '@ng-icons/iconoir'
39
38
  import { matCloseOutline } from '@ng-icons/material-icons/outline'
@@ -48,6 +47,10 @@ marker('notify.reuse.form.error.title')
48
47
  marker('notify.reuse.form.error.body')
49
48
 
50
49
  export const REUSE_FORM_URL = new InjectionToken<string>('reuseFormUrl')
50
+ // language assigned to the created reuse record; falls back to the current UI language
51
+ export const NEW_RECORD_DEFAULT_LANGUAGE = new InjectionToken<string>(
52
+ 'newRecordDefaultLanguage'
53
+ )
51
54
 
52
55
  @Component({
53
56
  selector: 'gn-ui-notify-reuse-form',
@@ -86,6 +89,9 @@ export class NotifyReuseFormComponent implements OnDestroy {
86
89
  private readonly notificationsService = inject(NotificationsService)
87
90
  private readonly platformServiceInterface = inject(PlatformServiceInterface)
88
91
  reuseFormUrl = inject(REUSE_FORM_URL, { optional: true })
92
+ private newRecordDefaultLanguage = inject(NEW_RECORD_DEFAULT_LANGUAGE, {
93
+ optional: true,
94
+ })
89
95
  private locationStrategy = inject(LocationStrategy)
90
96
 
91
97
  me = toSignal(this.platformServiceInterface.getMe())
@@ -158,8 +164,7 @@ export class NotifyReuseFormComponent implements OnDestroy {
158
164
  submit() {
159
165
  if (!this.isFormValid()) return
160
166
  const defaultLang =
161
- getOptionalEditorConfig()?.NEW_RECORD_DEFAULT_LANGUAGE ??
162
- this.translate.currentLang
167
+ this.newRecordDefaultLanguage ?? this.translate.currentLang
163
168
  const onlineResource: OnlineLinkResource = {
164
169
  type: 'link',
165
170
  url: new URL(this.url()),
@@ -40,7 +40,9 @@ export class MdViewEffects {
40
40
  this.actions$.pipe(
41
41
  ofType(MdViewActions.loadFullMetadataSuccess),
42
42
  filter(({ full }) => full !== undefined),
43
- switchMap(({ full }) => this.recordsRepository.getFeatureCatalog(full)),
43
+ switchMap(({ full }) =>
44
+ this.recordsRepository.queryLinkedFeatureCatalog(full)
45
+ ),
44
46
  map((featureCatalog) =>
45
47
  MdViewActions.loadFeatureCatalogSuccess({
46
48
  datasetCatalog: featureCatalog,
@@ -162,7 +162,7 @@ export class MdViewFacade {
162
162
  link.accessServiceProtocol === 'ogcFeatures'
163
163
  ) {
164
164
  return from(
165
- this.dataService.getItemsFromOgcApi(link.url.href, 1)
165
+ this.dataService.getItemsFromOgcApi(link.url.href, link.name, 1)
166
166
  ).pipe(
167
167
  map((collectionRecords: OgcApiRecord[]) => {
168
168
  return collectionRecords &&
@@ -1,4 +1,6 @@
1
1
  export * from './lib/feature-search.module'
2
+ export * from './lib/geocoding/geocoding.service'
3
+ export * from './lib/location-search/location-search.component'
2
4
  export * from './lib/filter-geometry.token'
3
5
  export * from './lib/record-url.token'
4
6
  export * from './lib/state/actions'
@@ -3,6 +3,7 @@
3
3
  [title]="title"
4
4
  [dateRange]="(selectedDateRange$ | async) ?? {}"
5
5
  (dateRangeChange)="onDateRangeChange($event)"
6
+ [attr.data-cy-field]="fieldName"
6
7
  ></gn-ui-date-range-dropdown>
7
8
  } @else if (fieldType === 'spatialExtent') {
8
9
  <gn-ui-spatial-extent-dropdown
@@ -12,7 +13,13 @@
12
13
  (bboxChange)="onBboxChange($event)"
13
14
  (errorChange)="onSpatialExtentError($event)"
14
15
  [attr.data-cy-field]="fieldName"
15
- ></gn-ui-spatial-extent-dropdown>
16
+ >
17
+ <gn-ui-location-search
18
+ [placeholder]="'search.filters.spatialExtent.searchLocation' | translate"
19
+ (bboxSelected)="onLocationSelected($event)"
20
+ data-cy="locationSearch"
21
+ ></gn-ui-location-search>
22
+ </gn-ui-spatial-extent-dropdown>
16
23
  } @else {
17
24
  <gn-ui-dropdown-multiselect
18
25
  class="w-full"