geonetwork-ui 2.11.0-dev.0504d85cc → 2.11.0-dev.0559cfd9d

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 (58) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1347 -1152
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +117 -64
  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 +395 -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 +17 -6
  11. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +110 -0
  12. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +131 -0
  13. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +9 -0
  14. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +2 -1
  15. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +29 -3
  16. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +5 -34
  17. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +14 -18
  18. package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +2 -0
  19. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +4 -1
  20. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +1 -1
  21. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +42 -62
  22. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +32 -19
  23. package/src/libs/feature/map/src/index.ts +0 -1
  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/lib/filter-dropdown/filter-dropdown.component.html +7 -1
  28. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +26 -4
  29. package/src/libs/feature/search/src/lib/geocoding/geocoding.service.ts +52 -8
  30. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +11 -1
  31. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +77 -3
  32. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +1 -0
  33. package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
  34. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +56 -4
  35. package/src/libs/feature/search/src/lib/utils/service/fields.ts +33 -9
  36. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +3 -3
  37. package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +2 -2
  38. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.css +0 -4
  39. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +2 -2
  40. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +3 -3
  41. package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +2 -1
  42. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +7 -1
  43. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +26 -3
  44. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  45. package/src/libs/util/app-config/src/lib/app-config.ts +34 -0
  46. package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
  47. package/src/libs/util/app-config/src/lib/model.ts +24 -1
  48. package/translations/de.json +8 -2
  49. package/translations/en.json +8 -2
  50. package/translations/es.json +8 -2
  51. package/translations/fr.json +12 -6
  52. package/translations/it.json +8 -2
  53. package/translations/nl.json +8 -2
  54. package/translations/pt.json +8 -2
  55. package/translations/sk.json +8 -2
  56. package/src/libs/feature/map/src/lib/style/index.ts +0 -2
  57. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  58. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
@@ -1,5 +1,8 @@
1
1
  import {
2
2
  CatalogRecord,
3
+ DatasetFeatureAttribute,
4
+ DatasetFeatureAttributeValue,
5
+ DatasetFeatureType,
3
6
  DatasetRecord,
4
7
  Individual,
5
8
  LanguageCode,
@@ -616,3 +619,131 @@ export function writeAssociatedRecords(
616
619
  )
617
620
  )(rootEl)
618
621
  }
622
+
623
+ function createNilElement(name: string, nilReason: string) {
624
+ return pipe(createElement(name), writeAttribute('gco:nilReason', nilReason))
625
+ }
626
+
627
+ function createFeatureCatalogueShell() {
628
+ return pipe(
629
+ createNestedElement(
630
+ 'mdb:contentInfo',
631
+ 'mrc:MD_FeatureCatalogue',
632
+ 'mrc:featureCatalogue',
633
+ 'gfc:FC_FeatureCatalogue'
634
+ ),
635
+ // create mandatory elements which are not part of the model yet
636
+ appendChildren(
637
+ createNilElement('cat:name', 'missing'),
638
+ createNilElement('cat:scope', 'missing'),
639
+ createNilElement('cat:versionNumber', 'missing'),
640
+ createNilElement('cat:versionDate', 'missing'),
641
+ createNilElement('gfc:producer', 'missing')
642
+ )
643
+ )
644
+ }
645
+
646
+ function createListedValue(value: DatasetFeatureAttributeValue) {
647
+ return pipe(
648
+ createNestedElement('gfc:listedValue', 'gfc:FC_ListedValue'),
649
+ appendChildren(
650
+ value.label
651
+ ? pipe(createElement('gfc:label'), writeCharacterString(value.label))
652
+ : createNilElement('gfc:label', 'missing'),
653
+ value.value
654
+ ? pipe(createElement('gfc:code'), writeCharacterString(value.value))
655
+ : null,
656
+ value.description
657
+ ? pipe(
658
+ createElement('gfc:definition'),
659
+ writeCharacterString(value.description)
660
+ )
661
+ : null
662
+ )
663
+ )
664
+ }
665
+
666
+ function createFeatureAttribute(attribute: DatasetFeatureAttribute) {
667
+ return pipe(
668
+ createNestedElement(
669
+ 'gfc:carrierOfCharacteristics',
670
+ 'gfc:FC_FeatureAttribute'
671
+ ),
672
+ appendChildren(
673
+ pipe(createElement('gfc:memberName'), setTextContent(attribute.name)),
674
+ attribute.description
675
+ ? pipe(
676
+ createElement('gfc:definition'),
677
+ writeCharacterString(attribute.description)
678
+ )
679
+ : null,
680
+ createNilElement('gfc:cardinality', 'missing'),
681
+ attribute.label
682
+ ? pipe(
683
+ createElement('gfc:designation'),
684
+ writeCharacterString(attribute.label)
685
+ )
686
+ : null,
687
+ attribute.code
688
+ ? pipe(createElement('gfc:code'), writeCharacterString(attribute.code))
689
+ : null,
690
+ attribute.type
691
+ ? pipe(
692
+ createNestedElement('gfc:valueType', 'gco:TypeName', 'gco:aName'),
693
+ writeCharacterString(attribute.type)
694
+ )
695
+ : null,
696
+ ...(attribute.values ?? [])
697
+ .filter((value) => value.value || value.label)
698
+ .map(createListedValue)
699
+ )
700
+ )
701
+ }
702
+
703
+ function createFeatureType(featureType: DatasetFeatureType) {
704
+ return pipe(
705
+ createNestedElement('gfc:featureType', 'gfc:FC_FeatureType'),
706
+ appendChildren(
707
+ pipe(createElement('gfc:typeName'), setTextContent(featureType.name)),
708
+ featureType.description
709
+ ? pipe(
710
+ createElement('gfc:definition'),
711
+ writeCharacterString(featureType.description)
712
+ )
713
+ : null,
714
+ pipe(
715
+ createNestedElement('gfc:isAbstract', 'gco:Boolean'),
716
+ setTextContent('false')
717
+ ),
718
+ featureType.label
719
+ ? pipe(
720
+ createElement('gfc:designation'),
721
+ writeCharacterString(featureType.label)
722
+ )
723
+ : null,
724
+ ...featureType.attributes.map(createFeatureAttribute),
725
+ createNilElement('gfc:featureCatalogue', 'missing')
726
+ )
727
+ )
728
+ }
729
+
730
+ export function writeFeatureTypeDescriptions(
731
+ record: DatasetRecord,
732
+ rootEl: XmlElement
733
+ ) {
734
+ const featureTypes = record.featureTypeDescriptions ?? []
735
+ const catalogues = findNestedElements(
736
+ 'mdb:contentInfo',
737
+ 'mrc:MD_FeatureCatalogue',
738
+ 'mrc:featureCatalogue',
739
+ 'gfc:FC_FeatureCatalogue'
740
+ )(rootEl)
741
+ catalogues.forEach((catalogue) =>
742
+ removeChildrenByName('gfc:featureType')(catalogue)
743
+ )
744
+ if (!featureTypes.length) return
745
+
746
+ const catalogue =
747
+ catalogues[0] ?? appendChildTree(createFeatureCatalogueShell())(rootEl)
748
+ appendChildren(...featureTypes.map(createFeatureType))(catalogue)
749
+ }
@@ -118,6 +118,7 @@ export class Iso19139Converter extends BaseConverter<string> {
118
118
  lineage: readLineage,
119
119
  sourceRecords: readSourceRecords,
120
120
  associatedRecords: readAssociatedRecords,
121
+ featureTypeDescriptions: () => undefined,
121
122
  onlineResources: readOnlineResources,
122
123
  temporalExtents: readTemporalExtents,
123
124
  spatialExtents: readSpatialExtents,
@@ -161,6 +162,7 @@ export class Iso19139Converter extends BaseConverter<string> {
161
162
  lineage: writeLineage,
162
163
  sourceRecords: writeSourceRecords,
163
164
  associatedRecords: writeAssociatedRecords,
165
+ featureTypeDescriptions: () => undefined,
164
166
  onlineResources: writeOnlineResources,
165
167
  temporalExtents: writeTemporalExtents,
166
168
  spatialExtents: writeSpatialExtents,
@@ -306,6 +308,10 @@ export class Iso19139Converter extends BaseConverter<string> {
306
308
  const sourceRecords = this.readers['sourceRecords'](rootEl, tr)
307
309
  const associatedRecords = this.readers['associatedRecords'](rootEl, tr)
308
310
  const updateFrequency = this.readers['updateFrequency'](rootEl, tr)
311
+ const featureTypeDescriptions = this.readers['featureTypeDescriptions'](
312
+ rootEl,
313
+ tr
314
+ ) as DatasetRecord['featureTypeDescriptions']
309
315
 
310
316
  return this.afterRecordRead({
311
317
  ...this.readBaseRecord(rootEl, tr),
@@ -317,6 +323,7 @@ export class Iso19139Converter extends BaseConverter<string> {
317
323
  ...(spatialRepresentation && { spatialRepresentation }),
318
324
  temporalExtents,
319
325
  updateFrequency,
326
+ ...(featureTypeDescriptions?.length && { featureTypeDescriptions }),
320
327
  translations: tr,
321
328
  } as DatasetRecord)
322
329
  } else if (kind === 'reuse') {
@@ -416,6 +423,8 @@ export class Iso19139Converter extends BaseConverter<string> {
416
423
 
417
424
  if (record.kind === 'dataset') {
418
425
  fieldChanged('status') && this.writers['status'](record, rootEl)
426
+ fieldChanged('featureTypeDescriptions') &&
427
+ this.writers['featureTypeDescriptions'](record, rootEl)
419
428
  }
420
429
  if (record.kind === 'dataset' || record.kind === 'reuse') {
421
430
  fieldChanged('updateFrequency') &&
@@ -69,7 +69,8 @@ export function extractCharacterString(): ChainableFunction<
69
69
  fallback(
70
70
  findChildElement('gco:CharacterString', false),
71
71
  findChildElement('gmx:Anchor', false),
72
- findChildElement('gmx:MimeFileType', false)
72
+ findChildElement('gmx:MimeFileType', false),
73
+ findChildElement('gco:LocalName', false)
73
74
  ),
74
75
  readText()
75
76
  )
@@ -59,6 +59,8 @@ export class ElasticsearchService {
59
59
  { script: string; type: 'keyword' | 'date' }
60
60
  > = {}
61
61
 
62
+ private fieldAliases: Record<string, string> = {}
63
+
62
64
  // we're using getters in case the defined languages change over time
63
65
  private get metadataLang(): LanguageCode {
64
66
  const mdLangValue = this.injector.get(METADATA_LANGUAGE, null)
@@ -155,6 +157,10 @@ export class ElasticsearchService {
155
157
  this.runtimeFields[fieldName] = { script: expression, type }
156
158
  }
157
159
 
160
+ registerFieldAlias(fieldIdentifier: string, esFieldName: string) {
161
+ this.fieldAliases[fieldIdentifier] = esFieldName
162
+ }
163
+
158
164
  getMetadataByIdsPayload(uuids: string[]): EsSearchParams {
159
165
  return {
160
166
  query: {
@@ -293,12 +299,25 @@ export class ElasticsearchService {
293
299
  return Object.values(filters).find(isBoundingBox)
294
300
  }
295
301
 
296
- // if a field registered min/max runtime fields, its dates form an interval
297
- // the record matches if that interval intersects the filter range
302
+ // builds a query matching records whose dates intersect the filter range
298
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
+ }
299
317
  const minField = `${searchField}Min`
300
318
  const maxField = `${searchField}Max`
301
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
302
321
  const filter = [
303
322
  dateRange.start && {
304
323
  range: {
@@ -362,7 +381,8 @@ export class ElasticsearchService {
362
381
  JSON.stringify(filters[fieldname]) !== '{}'
363
382
  )
364
383
  .map(
365
- (fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`
384
+ (fieldname) =>
385
+ `${this.fieldAliases[fieldname] ?? fieldname}:(${makeQuery(filters[fieldname])})`
366
386
  )
367
387
  .join(' AND ')
368
388
  if (filters['gn-ui-crossFieldFilter']) {
@@ -708,6 +728,12 @@ export class ElasticsearchService {
708
728
  order: {
709
729
  [`_${aggregation.sort[1]}`]: aggregation.sort[0],
710
730
  },
731
+ ...(aggregation.includeValues && {
732
+ include: aggregation.includeValues,
733
+ }),
734
+ ...(aggregation.excludeValues && {
735
+ exclude: aggregation.excludeValues,
736
+ }),
711
737
  },
712
738
  }
713
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?: Array<DatasetFeatureType>
319
320
  }
320
321
 
321
322
  export interface ServiceEndpoint {
@@ -347,33 +348,28 @@ export interface ReuseRecord extends BaseRecord {
347
348
  temporalExtents: Array<DatasetTemporalExtent>
348
349
  }
349
350
 
351
+ export interface DatasetFeatureType {
352
+ name: string
353
+ label?: string
354
+ description?: string
355
+ attributes: DatasetFeatureAttribute[]
356
+ }
350
357
  export type DatasetFeatureCatalog = {
351
- featureTypes: Array<{
352
- name: string
353
- definition: string
354
- attributes: DatasetFeatureAttribute[]
355
- }>
358
+ featureTypes: Array<DatasetFeatureType>
356
359
  }
357
360
  export interface DatasetFeatureAttributeValue {
358
- code?: string
359
- definition?: string
361
+ value: string // empty string when the value has no code
360
362
  label?: string
363
+ description?: string
361
364
  }
362
365
  export interface DatasetFeatureAttribute {
363
366
  name: string
364
- definition: string
365
- type: string
366
- code: string
367
+ label?: string
368
+ description?: string
369
+ type?: string
370
+ code?: string
367
371
  values?: Array<DatasetFeatureAttributeValue>
368
372
  }
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
373
 
378
374
  export type ReuseType = 'application' | 'map' | 'other'
379
375
 
@@ -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: {
@@ -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
+ { value: 'Pomme', label: 'Les Pommiers' },
186
+ { value: 'Cotton', label: 'Rue Cotton' },
187
+ { value: "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,7 +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'
@@ -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 &&