geonetwork-ui 2.11.0-dev.c42282af5 → 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 (64) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1405 -1317
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +144 -75
  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 +29 -3
  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 +4 -1
  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 +7 -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 +1 -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 +56 -4
  36. package/src/libs/feature/search/src/lib/utils/service/fields.ts +33 -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/inputs/src/lib/autocomplete/autocomplete.component.css +0 -4
  40. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +12 -3
  41. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +11 -3
  42. package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +2 -1
  43. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +8 -2
  44. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +34 -5
  45. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  46. package/src/libs/util/app-config/src/lib/app-config.ts +34 -0
  47. package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
  48. package/src/libs/util/app-config/src/lib/model.ts +24 -1
  49. package/src/libs/util/shared/src/lib/utils/geojson.ts +16 -0
  50. package/translations/de.json +8 -2
  51. package/translations/en.json +10 -4
  52. package/translations/es.json +8 -2
  53. package/translations/fr.json +15 -9
  54. package/translations/it.json +8 -2
  55. package/translations/nl.json +8 -2
  56. package/translations/pt.json +8 -2
  57. package/translations/sk.json +8 -2
  58. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.css +0 -0
  59. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  60. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  61. package/src/libs/feature/map/src/lib/geocoding.service.ts +0 -57
  62. package/src/libs/feature/map/src/lib/style/index.ts +0 -2
  63. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  64. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
@@ -0,0 +1,317 @@
1
+ import { DatasetRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
2
+
3
+ export const MEL_FEATURECATALOG_DATASET_RECORD: DatasetRecord = {
4
+ uniqueIdentifier: '1f7c8d9f-3363-4512-ad3a-064fb6b851bc',
5
+ kind: 'dataset',
6
+ otherLanguages: [],
7
+ defaultLanguage: 'fr',
8
+ recordCreated: new Date('2025-11-04T15:47:22Z'),
9
+ recordUpdated: new Date('2026-10-05T08:40:46.464848Z'),
10
+ resourceCreated: new Date('2023-10-27T00:00:00'),
11
+ resourcePublished: new Date('2025-11-04'),
12
+ title: 'Schéma cyclable 2035 - liaisons - Plan de mobilité',
13
+ abstract:
14
+ 'L\'action n°35 du Plan de Mobilité de la Métropole Européenne de Lille présente le schéma cyclable métropolitain à horizon 2035. \n\nLa donnée proposée ici décrit la déclinaison en trois niveaux hiérarchiques des axes du schéma cyclable : \n- **le réseau Vélo+** pour la desserte des grandes destinations métropolitaines, \n- **le réseau intercommunal métropolitain** pour la desserte des pôles générateurs principaux, \n- **le réseau cyclable de proximité** qui assure la diffusion fine et permet de faire le lien avec les générateurs de trafic et les cœurs de quartiers. \n \n### Détail\nLes entités disposant du code INSEE "0000" sont situées en-dehors du périmètre MEL.\n \n### Donnée associée à consulter : \n*Schéma cyclable 2035 - points durs - Plan de mobilité - MEL - 2023-2035*\n \n### Contexte\nLe schéma directeur cyclable métropolitain 2035 s’ancre sur le schéma Métropole Cyclable 2020. Il en propose une vision renforcée. Il est le fruit d’une réflexion territorialisée dont l’objectif est de proposer un réseau intercommunal adapté à la géographie des territoires et aux pratiques cyclables qui ne sont pas les même en ville et à la campagne. \n\n- En milieu rural, les liaisons proposées permettront de relier les bourgs et les villages tout en organisant le rabattement vers les stations de transport collectif majeures : gares et terminus métro. Elles permettront d’accéder aux grands ensembles scolaires et zones d’emploi. Elles porteront également les déplacements longues distances vers les zones urbaines denses. \n- En zone urbaine, les liaisons inscrites au réseau intercommunal porteront les déplacements intercommunaux et inter-quartiers. Ce réseau s’articule avec le réseau de transport collectif urbain et s’appuie sur la hiérarchisation des voies. \n\n - Il permettra de desservir la totalité des communes de la Métropole Européenne de Lille. \n - Il assurera aussi une desserte directe de 90 % de la population, 94 % des emplois et 93 % des effectifs scolaires projetés à horizon 2035.\n\n### Le Schéma Directeur Cyclable Métropolitain\nLe schéma directeur cyclable métropolitain 2035 s’appuie en premier lieu sur un diagnostic complet ayant permis de mettre en lumière de nouveaux enjeux sur le territoire, notamment en regard des développements projetés et des évolutions comportementales à l’œuvre. \n \nCe nouveau schéma directeur est aussi et surtout le fruit d’une concertation avec les communes de la Métropole Européenne de Lille et d’une collaboration technique active de l’association droit au vélo. Il a été construit en interface avec les territoires limitrophes. \n \nSa compatibilité avec les nouvelles lignes de transports en site propre et le schéma cible des véloroutes et voies vertes a été considérée.',
15
+ ownerOrganization: {
16
+ name: 'Métropole Européenne de Lille',
17
+ translations: {},
18
+ },
19
+ contacts: [
20
+ {
21
+ email: 'opendata@lillemetropole.fr',
22
+ address: '2, boulevard des Cités Unies, Lille, 59040',
23
+ organization: {
24
+ name: 'Métropole Européenne de Lille',
25
+ translations: {},
26
+ },
27
+ role: 'point_of_contact',
28
+ },
29
+ ],
30
+ contactsForResource: [
31
+ {
32
+ email: 'opendata@lillemetropole.fr',
33
+ address: '2, boulevard des Cités Unies, Lille, 59040',
34
+ organization: {
35
+ name: 'Métropole Européenne de Lille',
36
+ translations: {},
37
+ },
38
+ role: 'originator',
39
+ },
40
+ {
41
+ email: 'opendata@lillemetropole.fr',
42
+ address: '2, boulevard des Cités Unies, Lille, 59040',
43
+ organization: {
44
+ name: 'Métropole Européenne de Lille',
45
+ translations: {},
46
+ },
47
+ role: 'distributor',
48
+ },
49
+ {
50
+ email: 'datamobilite@lillemetropole.fr, mfontaine@lillemetropole.fr',
51
+ address: '2, boulevard des Cités Unies, Lille, 59040',
52
+ organization: {
53
+ name: 'Métropole Européenne de Lille',
54
+ translations: {},
55
+ },
56
+ role: 'point_of_contact',
57
+ },
58
+ ],
59
+ keywords: [
60
+ {
61
+ thesaurus: {
62
+ id: 'geonetwork.thesaurus.local.place.mel',
63
+ name: 'Territoires MEL',
64
+ url: new URL(
65
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/local.place.mel'
66
+ ),
67
+ },
68
+ label: 'Métropole Européenne de Lille',
69
+ type: 'place',
70
+ translations: {},
71
+ },
72
+ {
73
+ thesaurus: {
74
+ id: 'geonetwork.thesaurus.external.theme.httpinspireeceuropaeutheme-theme',
75
+ name: 'GEMET - INSPIRE themes, version 1.0',
76
+ url: new URL(
77
+ 'http://localhost:8080/geonetwork/srv/api/registries/vocabularies/external.theme.httpinspireeceuropaeutheme-theme'
78
+ ),
79
+ },
80
+ label: '',
81
+ type: 'theme',
82
+ translations: {},
83
+ },
84
+ {
85
+ thesaurus: {
86
+ id: 'geonetwork.thesaurus.external.theme.gemet',
87
+ name: 'GEMET',
88
+ url: new URL(
89
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/external.theme.gemet'
90
+ ),
91
+ },
92
+ label: '',
93
+ type: 'theme',
94
+ translations: {},
95
+ },
96
+ {
97
+ thesaurus: {
98
+ id: 'geonetwork.thesaurus.external.theme.thesaurus_mot_cle_thematique_categories',
99
+ name: 'Catégories',
100
+ url: new URL(
101
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/external.theme.thesaurus_mot_cle_thematique_categories'
102
+ ),
103
+ },
104
+ label: 'Mobilité, transports, déplacement',
105
+ type: 'theme',
106
+ translations: {},
107
+ },
108
+ {
109
+ thesaurus: {
110
+ id: 'geonetwork.thesaurus.external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement',
111
+ name: 'Mobilité, Transports, Déplacement',
112
+ url: new URL(
113
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement'
114
+ ),
115
+ },
116
+ label: 'Pistes cyclables',
117
+ type: 'theme',
118
+ translations: {},
119
+ },
120
+ {
121
+ thesaurus: {
122
+ id: 'geonetwork.thesaurus.external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement',
123
+ name: 'Mobilité, Transports, Déplacement',
124
+ url: new URL(
125
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement'
126
+ ),
127
+ },
128
+ label: 'Schéma cyclable',
129
+ type: 'theme',
130
+ translations: {},
131
+ },
132
+ {
133
+ thesaurus: {
134
+ id: 'geonetwork.thesaurus.external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement',
135
+ name: 'Mobilité, Transports, Déplacement',
136
+ url: new URL(
137
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement'
138
+ ),
139
+ },
140
+ label: 'Vélos',
141
+ type: 'theme',
142
+ translations: {},
143
+ },
144
+ {
145
+ thesaurus: {
146
+ id: 'geonetwork.thesaurus.external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement',
147
+ name: 'Mobilité, Transports, Déplacement',
148
+ url: new URL(
149
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement'
150
+ ),
151
+ },
152
+ label: 'Plan de mobilité',
153
+ type: 'theme',
154
+ translations: {},
155
+ },
156
+ {
157
+ thesaurus: {
158
+ id: 'geonetwork.thesaurus.external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement',
159
+ name: 'Mobilité, Transports, Déplacement',
160
+ url: new URL(
161
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement'
162
+ ),
163
+ },
164
+ label: 'Cyclisme',
165
+ type: 'theme',
166
+ translations: {},
167
+ },
168
+ {
169
+ thesaurus: {
170
+ id: 'geonetwork.thesaurus.external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement',
171
+ name: 'Mobilité, Transports, Déplacement',
172
+ url: new URL(
173
+ 'https://data.lillemetropole.fr/geonetwork/srv/api/registries/vocabularies/external.theme.thesaurus_mot_cle_thematique_mobilite_transports_deplacement'
174
+ ),
175
+ },
176
+ label: 'Cyclable',
177
+ type: 'theme',
178
+ translations: {},
179
+ },
180
+ ],
181
+ topics: ['transportation'],
182
+ licenses: [],
183
+ legalConstraints: [
184
+ {
185
+ text: 'Licence Ouverte v2.0 (Etalab)',
186
+ translations: {},
187
+ },
188
+ {
189
+ text: 'Utilisation libre sous réserve de mentionner la source (a minima le nom du producteur) et la date de sa dernière mise à jour',
190
+ translations: {},
191
+ },
192
+ ],
193
+ securityConstraints: [],
194
+ otherConstraints: [],
195
+ overviews: [],
196
+ spatialExtents: [
197
+ {
198
+ bbox: [
199
+ 2.78913293567365, 50.4997300394295, 3.272498092970729, 50.7945765614518,
200
+ ],
201
+ translations: {},
202
+ },
203
+ ],
204
+ onlineResources: [
205
+ {
206
+ type: 'service',
207
+ url: new URL(
208
+ 'https://data.lillemetropole.fr/geoserver/ogc/features/v1/collections/mel_mobilite_et_transport:sc_schema_cyclable_pm35_2023/items'
209
+ ),
210
+ accessServiceProtocol: 'ogcFeatures',
211
+ identifierInService: 'Schéma cyclable 2035 - liaisons - Plan de mobilité',
212
+ name: 'Schéma cyclable 2035 - liaisons - Plan de mobilité',
213
+ description: 'Schéma cyclable 2035 - liaisons - Plan de mobilité',
214
+ translations: {},
215
+ },
216
+ {
217
+ type: 'service',
218
+ url: new URL('https://data.lillemetropole.fr/geoserver/ows'),
219
+ accessServiceProtocol: 'wfs',
220
+ identifierInService:
221
+ 'mel_mobilite_et_transport:sc_schema_cyclable_pm35_2023',
222
+ name: 'mel_mobilite_et_transport:sc_schema_cyclable_pm35_2023',
223
+ description: 'Schéma cyclable 2035 - liaisons - Plan de mobilité',
224
+ translations: {},
225
+ },
226
+ {
227
+ type: 'service',
228
+ url: new URL('https://data.lillemetropole.fr/geoserver/ows'),
229
+ accessServiceProtocol: 'wms',
230
+ identifierInService:
231
+ 'mel_mobilite_et_transport:sc_schema_cyclable_pm35_2023',
232
+ name: 'mel_mobilite_et_transport:sc_schema_cyclable_pm35_2023',
233
+ description: 'Schéma cyclable 2035 - liaisons - Plan de mobilité',
234
+ translations: {},
235
+ },
236
+ {
237
+ type: 'link',
238
+ url: new URL(
239
+ 'https://plandemobilite2035.lillemetropole.fr/schema-cyclable/'
240
+ ),
241
+ name: 'Documentation',
242
+ description: 'Lien vers la carte dynamique « schéma cyclable »',
243
+ translations: {},
244
+ },
245
+ ],
246
+ translations: {},
247
+ landingPage: new URL(
248
+ 'https://data.lillemetropole.fr/catalogue/dataset/1f7c8d9f-3363-4512-ad3a-064fb6b851bc'
249
+ ),
250
+ status: 'completed',
251
+ lineage: null,
252
+ sourceRecords: [],
253
+ associatedRecords: [],
254
+ temporalExtents: [
255
+ {
256
+ start: new Date('2023-10-27'),
257
+ end: new Date('2035-12-31'),
258
+ },
259
+ ],
260
+ updateFrequency: 'notPlanned',
261
+ featureTypeDescriptions: [
262
+ {
263
+ name: 'schéma cyclable - liaisons',
264
+ description: 'Liaisons du schéma directeur cyclable de la MEL',
265
+ attributes: [
266
+ {
267
+ name: 'objectid',
268
+ description: "Identifiant de l'objet (clé primaire)",
269
+ type: 'numeric(38)',
270
+ code: 'OBJECTID',
271
+ },
272
+ {
273
+ name: 'code_insee',
274
+ description: 'Code INSEE de la commune',
275
+ type: 'varchar(8)',
276
+ code: 'CODE_INSEE',
277
+ },
278
+ {
279
+ name: 'velo_plus',
280
+ description:
281
+ "Permet de savoir si l'objet décrit un tronçon faisant parti du réseau vélo + : Oui - le tronçon fait partie intégrante du réseau vélo+ /Non - le tronçon ne fait pas partie du réseau vélo+ /Option - le tronçon pourrait éventuellement faire partie du réseau réseau vélo+ / Variante - le tronçon pourrait être utilisé en lieu et place d'un autre pour répondre à un trajet équivalent",
282
+ type: 'varchar(256)',
283
+ },
284
+ {
285
+ name: 'r_ppal',
286
+ description:
287
+ "Permet de savoir si l'objet décrit un tronçon faisant parti du réseau Intercommunal : Oui - le tronçon fait partie intégrante du réseau intercommunal / Non - le tronçon ne fait pas partie du réseau intercommunal / Option : le tronçon pourrait éventuellement faire partie du réseau intercommunal / Variante : le tronçon pourrait être utilisé en lieu et place d'un autre pour répondre à un trajet équivalent",
288
+ type: 'varchar(256)',
289
+ },
290
+ {
291
+ name: 'r_scdr',
292
+ description:
293
+ "Permet de savoir si l'objet décrit un tronçon faisant parti du réseau de proximité : Oui - le tronçon fait partie intégrante du réseau cyclable de proximité / Non - le tronçon ne fait pas partie du réseau cyclable de proximité",
294
+ type: 'varchar(256)',
295
+ },
296
+ {
297
+ name: 'test_niveau_difficulte',
298
+ description:
299
+ '[Données pour tester valeurs de liste]: Donne une note de difficulté (1-5) du tronçon.',
300
+ type: 'varchar(256)',
301
+ values: [
302
+ { code: '1', label: 'Très facile' },
303
+ { code: '2', label: 'Facile' },
304
+ { code: '3', label: 'Moyen' },
305
+ { code: '4', label: 'Difficile' },
306
+ { code: '5', label: 'Très difficile' },
307
+ ],
308
+ },
309
+ {
310
+ name: 'geom',
311
+ description: 'Géométrie de type ligne simple',
312
+ type: 'geometry',
313
+ },
314
+ ],
315
+ },
316
+ ],
317
+ }
@@ -22,12 +22,13 @@ import {
22
22
  AssociatedRecord,
23
23
  CatalogRecord,
24
24
  Constraint,
25
+ DatasetFeatureCatalog,
25
26
  DatasetSpatialExtent,
26
27
  OnlineResource,
27
28
  OnlineResourceType,
28
29
  } from '../../../../../../libs/common/domain/src/lib/model/record'
29
30
  import { matchProtocol } from '../common/distribution.mapper'
30
- import { Thesaurus } from './types'
31
+ import { FeatureType, Thesaurus } from './types'
31
32
  import {
32
33
  getResourceType,
33
34
  getReusePresentationForm,
@@ -312,13 +313,14 @@ export class Gn4FieldMapper {
312
313
  },
313
314
  output
314
315
  ),
315
- featureTypes: (output, source) =>
316
- this.addExtra(
317
- {
318
- featureTypes: selectField(source, 'featureTypes'),
319
- },
320
- output
321
- ),
316
+ featureTypes: (output, source) => {
317
+ const featureTypes = getAsArray(selectField(source, 'featureTypes'))
318
+ if (!featureTypes.length) return output
319
+ return {
320
+ ...output,
321
+ featureTypeDescriptions: this.featureTypesField(featureTypes),
322
+ }
323
+ },
322
324
  related: (output, source) => {
323
325
  const related = <SourceWithUnknownProps>selectField(source, 'related')
324
326
  const fcatSource = selectField(
@@ -518,6 +520,31 @@ export class Gn4FieldMapper {
518
520
 
519
521
  private genericField = (output) => output
520
522
 
523
+ private featureTypesField = (
524
+ featureTypes: FeatureType[]
525
+ ): DatasetFeatureCatalog['featureTypes'] =>
526
+ featureTypes.map((featureType) => ({
527
+ name: featureType.typeName ?? '',
528
+ ...(featureType.definition && {
529
+ description: featureType.definition,
530
+ }),
531
+ attributes: getAsArray(featureType.attributeTable ?? []).map((attr) => {
532
+ const values = (attr.values ?? [])
533
+ .filter((value) => value.code || value.label)
534
+ .map((value) => ({
535
+ ...(value.code && { code: value.code }),
536
+ ...(value.label && { label: value.label }),
537
+ }))
538
+ return {
539
+ name: attr.name ?? '',
540
+ ...(attr.definition && { description: attr.definition }),
541
+ ...(attr.code && { code: attr.code }),
542
+ ...(attr.type && { type: attr.type }),
543
+ ...(values.length > 0 && { values }),
544
+ }
545
+ }),
546
+ }))
547
+
521
548
  private constraintField = (
522
549
  type: 'license' | 'legal' | 'security' | 'other',
523
550
  output: Partial<CatalogRecord>,
@@ -79,6 +79,24 @@ export interface Link {
79
79
  group?: number
80
80
  }
81
81
 
82
+ interface FeatureTypeAttribute {
83
+ name: string
84
+ code: string
85
+ link: string
86
+ definition: string
87
+ type: string
88
+ values?: Array<{ code?: string; label?: string; description?: string }>
89
+ }
90
+
91
+ export interface FeatureType {
92
+ typeName: string
93
+ definition: string
94
+ code: string
95
+ aliases: string
96
+ isAbstract: string
97
+ attributeTable: FeatureTypeAttribute[]
98
+ }
99
+
82
100
  export type MetadataObject = Partial<{
83
101
  MD_ConstraintsUseLimitationObject: MultilingualField[]
84
102
  MD_LegalConstraintsOtherConstraintsObject: MultilingualField[]
@@ -109,6 +127,7 @@ export type MetadataObject = Partial<{
109
127
  extentDescriptionObject: MultilingualField[]
110
128
  extra: string
111
129
  featureOfRecord: string
130
+ featureTypes: FeatureType[]
112
131
  feedbackCount: string
113
132
  geom: Geometry | Geometry[]
114
133
  groupOwner: string
@@ -4,6 +4,7 @@ import { Iso19139Converter } from '../iso19139'
4
4
  import { renameElements } from '../xml-utils'
5
5
  import {
6
6
  readAssociatedRecords,
7
+ readFeatureTypeDescriptions,
7
8
  readContacts,
8
9
  readContactsForResource,
9
10
  readDefaultLanguage,
@@ -59,6 +60,7 @@ export class Iso191153Converter extends Iso19139Converter {
59
60
  this.readers['lineage'] = readLineage
60
61
  this.readers['sourceRecords'] = readSourceRecords
61
62
  this.readers['associatedRecords'] = readAssociatedRecords
63
+ this.readers['featureTypeDescriptions'] = readFeatureTypeDescriptions
62
64
  this.readers['onlineResources'] = readOnlineResources
63
65
  this.readers['defaultLanguage'] = readDefaultLanguage
64
66
  this.readers['otherLanguages'] = readOtherLanguages
@@ -5,11 +5,13 @@ import {
5
5
  findNestedElements,
6
6
  findParent,
7
7
  readAttribute,
8
+ readText,
8
9
  XmlElement,
9
10
  } from '../xml-utils'
10
11
  import {
11
12
  ChainableFunction,
12
13
  combine,
14
+ fallback,
13
15
  filterArray,
14
16
  flattenArray,
15
17
  getAtIndex,
@@ -32,6 +34,9 @@ import {
32
34
  } from '../iso19139/read-parts'
33
35
  import {
34
36
  AssociatedRecord,
37
+ DatasetFeatureAttribute,
38
+ DatasetFeatureAttributeValue,
39
+ DatasetFeatureCatalog,
35
40
  Individual,
36
41
  LanguageCode,
37
42
  SourceRecord,
@@ -411,3 +416,96 @@ export function readOtherLanguages(rootEl: XmlElement): LanguageCode[] {
411
416
  )
412
417
  )(rootEl)
413
418
  }
419
+
420
+ function extractCharacterStringOrText(): ChainableFunction<XmlElement, string> {
421
+ return fallback(extractCharacterString(), readText())
422
+ }
423
+
424
+ function extractListedValue(): ChainableFunction<
425
+ XmlElement,
426
+ DatasetFeatureAttributeValue
427
+ > {
428
+ return pipe(
429
+ combine(
430
+ pipe(findChildElement('gfc:code', false), extractCharacterString()),
431
+ pipe(findChildElement('gfc:label', false), extractCharacterString())
432
+ ),
433
+ map(([code, label]) => ({
434
+ ...(code && { code }),
435
+ ...(label && { label }),
436
+ }))
437
+ )
438
+ }
439
+
440
+ function extractFeatureAttribute(): ChainableFunction<
441
+ XmlElement,
442
+ DatasetFeatureAttribute
443
+ > {
444
+ return pipe(
445
+ combine(
446
+ pipe(
447
+ findChildElement('gfc:memberName', false),
448
+ extractCharacterStringOrText()
449
+ ),
450
+ pipe(findChildElement('gfc:definition', false), extractCharacterString()),
451
+ pipe(findChildElement('gfc:code', false), extractCharacterString()),
452
+ pipe(
453
+ findNestedElement('gfc:valueType', 'gco:TypeName', 'gco:aName'),
454
+ extractCharacterString()
455
+ ),
456
+ pipe(
457
+ findNestedElements('gfc:listedValue', 'gfc:FC_ListedValue'),
458
+ mapArray(extractListedValue()),
459
+ filterArray((value) => !!(value.code || value.label))
460
+ )
461
+ ),
462
+ map(([name, description, code, type, values]) => ({
463
+ name: name ?? '',
464
+ ...(description && { description }),
465
+ ...(code && { code }),
466
+ ...(type && { type }),
467
+ ...(values.length > 0 && { values }),
468
+ }))
469
+ )
470
+ }
471
+
472
+ export function readFeatureTypeDescriptions(
473
+ rootEl: XmlElement
474
+ ): DatasetFeatureCatalog['featureTypes'] {
475
+ return pipe(
476
+ findNestedElements(
477
+ 'mdb:contentInfo',
478
+ 'mrc:MD_FeatureCatalogue',
479
+ 'mrc:featureCatalogue',
480
+ 'gfc:FC_FeatureCatalogue',
481
+ 'gfc:featureType',
482
+ 'gfc:FC_FeatureType'
483
+ ),
484
+ mapArray(
485
+ pipe(
486
+ combine(
487
+ pipe(
488
+ findChildElement('gfc:typeName', false),
489
+ extractCharacterStringOrText()
490
+ ),
491
+ pipe(
492
+ findChildElement('gfc:definition', false),
493
+ extractCharacterString()
494
+ ),
495
+ pipe(
496
+ findNestedElements(
497
+ 'gfc:carrierOfCharacteristics',
498
+ 'gfc:FC_FeatureAttribute'
499
+ ),
500
+ mapArray(extractFeatureAttribute())
501
+ )
502
+ ),
503
+ map(([name, description, attributes]) => ({
504
+ name: name ?? '',
505
+ ...(description && { description }),
506
+ attributes,
507
+ }))
508
+ )
509
+ )
510
+ )(rootEl)
511
+ }
@@ -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') {
@@ -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':