geonetwork-ui 2.11.0-dev.80a8b0bfa → 2.11.0-dev.840dcc932
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.
- package/fesm2022/geonetwork-ui.mjs +2273 -1474
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +342 -136
- package/index.d.ts.map +1 -1
- package/package.json +7 -5
- package/src/libs/api/metadata-converter/src/lib/dcat-ap/dcat-ap.converter.ts +2 -0
- package/src/libs/api/metadata-converter/src/lib/fixtures/mel.records.dataset+featurecatalog.ts +395 -0
- package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +35 -8
- package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +19 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +17 -6
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +110 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +131 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +9 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +2 -1
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +134 -35
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +83 -35
- package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +11 -0
- package/src/libs/common/domain/src/lib/model/record/flags.model.ts +1 -0
- package/src/libs/common/domain/src/lib/model/record/index.ts +1 -0
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +14 -18
- package/src/libs/common/domain/src/lib/model/search/aggregation.model.ts +2 -0
- package/src/libs/common/domain/src/lib/model/search/filter.model.ts +24 -2
- package/src/libs/common/domain/src/lib/platform.service.interface.ts +1 -0
- package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +20 -1
- package/src/libs/common/fixtures/src/lib/records.fixtures.ts +42 -62
- package/src/libs/feature/dataviz/src/lib/service/data.service.ts +32 -19
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints/form-field-constraints.component.ts +3 -5
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +2 -2
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.html +18 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.ts +86 -0
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +100 -88
- package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +11 -4
- package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +18 -4
- package/src/libs/feature/editor/src/lib/expressions.ts +67 -0
- package/src/libs/feature/editor/src/lib/fields.config.ts +13 -0
- package/src/libs/feature/editor/src/lib/models/editor-config.model.ts +10 -4
- package/src/libs/feature/map/src/index.ts +0 -2
- package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
- package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
- package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
- package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
- package/src/libs/feature/record/src/lib/state/mdview.effects.ts +3 -1
- package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -1
- package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
- package/src/libs/feature/search/src/index.ts +2 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +18 -4
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +80 -19
- package/src/libs/feature/search/src/lib/geocoding/geocoding.service.ts +105 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.html +18 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +114 -0
- package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +19 -8
- package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +64 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +92 -9
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +3 -3
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +2 -2
- package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
- package/src/libs/ui/inputs/src/index.ts +2 -0
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.css +0 -4
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +12 -3
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +11 -3
- package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
- package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
- package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
- package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
- package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
- package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
- package/src/libs/ui/inputs/src/lib/search-feature-catalog/search-feature-catalog.component.ts +2 -1
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +244 -0
- package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
- package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
- package/src/libs/util/app-config/src/lib/app-config.ts +37 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
- package/src/libs/util/app-config/src/lib/model.ts +25 -1
- package/src/libs/util/shared/src/index.ts +1 -0
- package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
- package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
- package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
- package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
- package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
- package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
- package/tailwind.base.css +5 -0
- package/translations/de.json +28 -4
- package/translations/en.json +27 -3
- package/translations/es.json +26 -2
- package/translations/fr.json +32 -8
- package/translations/it.json +26 -2
- package/translations/nl.json +26 -2
- package/translations/pt.json +26 -2
- package/translations/sk.json +26 -2
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
- package/src/libs/feature/map/src/lib/geocoding.service.ts +0 -57
- package/src/libs/feature/map/src/lib/style/index.ts +0 -2
- package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
- package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
- /package/src/libs/feature/{map/src/lib/geocoding/geocoding.component.css → editor/src/lib/components/record-form/form-field/form-field-record-flags/form-field-record-flag-component.css} +0 -0
|
@@ -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
|
)
|
|
@@ -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 {
|
|
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<
|
|
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:
|
|
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(
|
|
135
|
-
|
|
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
|
|
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) =>
|
|
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
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
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,9 +17,9 @@ import {
|
|
|
17
17
|
AssociatedRecord,
|
|
18
18
|
CatalogRecord,
|
|
19
19
|
DatasetFeatureCatalog,
|
|
20
|
-
DatasetFeatureType,
|
|
21
20
|
LanguageCode,
|
|
22
21
|
LinkedRecord,
|
|
22
|
+
RecordFlag,
|
|
23
23
|
RecordRelation,
|
|
24
24
|
} from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
25
25
|
import {
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
LanguagesApiService,
|
|
38
38
|
RecordsApiService,
|
|
39
39
|
SearchApiService,
|
|
40
|
+
TagsApiService,
|
|
40
41
|
} from '../../../../../../libs/data-access/gn4/src'
|
|
41
42
|
import {
|
|
42
43
|
combineLatest,
|
|
@@ -49,7 +50,13 @@ import {
|
|
|
49
50
|
switchMap,
|
|
50
51
|
throwError,
|
|
51
52
|
} from 'rxjs'
|
|
52
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
catchError,
|
|
55
|
+
map,
|
|
56
|
+
shareReplay,
|
|
57
|
+
tap,
|
|
58
|
+
withLatestFrom,
|
|
59
|
+
} from 'rxjs/operators'
|
|
53
60
|
import { lt } from 'semver'
|
|
54
61
|
import { ElasticsearchService } from './elasticsearch'
|
|
55
62
|
import { toLang2 } from '../../../../../../libs/util/i18n/src'
|
|
@@ -81,6 +88,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
81
88
|
private platformService = inject(PlatformServiceInterface)
|
|
82
89
|
private gn4LanguagesApi = inject(LanguagesApiService)
|
|
83
90
|
private settingsService = inject(Gn4SettingsService)
|
|
91
|
+
private gn4TagsApi = inject(TagsApiService)
|
|
84
92
|
private disableDraft = inject(DISABLE_DRAFT, { optional: true }) ?? false
|
|
85
93
|
private defaultConverter = inject(DEFAULT_RECORD_CONVERTER)
|
|
86
94
|
|
|
@@ -182,43 +190,15 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
182
190
|
)
|
|
183
191
|
}
|
|
184
192
|
|
|
185
|
-
|
|
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(
|
|
193
|
+
queryLinkedFeatureCatalog(
|
|
213
194
|
record: CatalogRecord,
|
|
214
195
|
visited: Set<string> = new Set() // prevent looping
|
|
215
196
|
): Observable<DatasetFeatureCatalog | null> {
|
|
216
197
|
if (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
record.extras['featureTypes'].length > 0
|
|
198
|
+
'featureTypeDescriptions' in record &&
|
|
199
|
+
record.featureTypeDescriptions?.length > 0
|
|
220
200
|
) {
|
|
221
|
-
return of(
|
|
201
|
+
return of({ featureTypes: record.featureTypeDescriptions })
|
|
222
202
|
}
|
|
223
203
|
|
|
224
204
|
const featureCatalogIdentifier = record.extras[
|
|
@@ -228,7 +208,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
228
208
|
visited.add(featureCatalogIdentifier)
|
|
229
209
|
return this.getRecord(featureCatalogIdentifier).pipe(
|
|
230
210
|
switchMap((record) =>
|
|
231
|
-
record ? this.
|
|
211
|
+
record ? this.queryLinkedFeatureCatalog(record, visited) : of(null)
|
|
232
212
|
)
|
|
233
213
|
)
|
|
234
214
|
}
|
|
@@ -704,4 +684,72 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
704
684
|
private removeRecordFromLocalStorage(recordId: string): void {
|
|
705
685
|
window.localStorage.removeItem(this.getLocalStorageKeyForRecord(recordId))
|
|
706
686
|
}
|
|
687
|
+
|
|
688
|
+
/*
|
|
689
|
+
* FLAGS
|
|
690
|
+
* Note: in GN4 API, categories are named "tags"
|
|
691
|
+
*/
|
|
692
|
+
|
|
693
|
+
categoriesIdentifierForFlags: Observable<Record<RecordFlag, number>> =
|
|
694
|
+
this.gn4TagsApi.getTags().pipe(
|
|
695
|
+
map((categories) =>
|
|
696
|
+
categories.reduce(
|
|
697
|
+
(acc, category) => {
|
|
698
|
+
if (category.name.startsWith('geonetwork-ui-flag:')) {
|
|
699
|
+
acc[
|
|
700
|
+
category.name.replace(/^geonetwork-ui-flag:/, '') as RecordFlag
|
|
701
|
+
] = category.id
|
|
702
|
+
}
|
|
703
|
+
return acc
|
|
704
|
+
},
|
|
705
|
+
{} as Record<RecordFlag, number>
|
|
706
|
+
)
|
|
707
|
+
),
|
|
708
|
+
shareReplay(1)
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
private getCategoryIdForFlag(
|
|
712
|
+
flagName: RecordFlag
|
|
713
|
+
): Observable<number | null> {
|
|
714
|
+
return this.categoriesIdentifierForFlags.pipe(
|
|
715
|
+
map((categories) => {
|
|
716
|
+
const categoryId = categories[flagName]
|
|
717
|
+
if (categoryId === undefined) {
|
|
718
|
+
console.warn(
|
|
719
|
+
`The record flag ${flagName} is not created on GeoNetwork backend; please create a category with the name geonetwork-ui-flag:${flagName} for this to work.`
|
|
720
|
+
)
|
|
721
|
+
return null
|
|
722
|
+
}
|
|
723
|
+
return categoryId
|
|
724
|
+
})
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
setRecordFlag(
|
|
729
|
+
uniqueIdentifier: string,
|
|
730
|
+
flagName: RecordFlag,
|
|
731
|
+
value: boolean
|
|
732
|
+
): Observable<void> {
|
|
733
|
+
return this.getCategoryIdForFlag(flagName).pipe(
|
|
734
|
+
switchMap((categoryId) => {
|
|
735
|
+
if (categoryId === null) return of()
|
|
736
|
+
return value
|
|
737
|
+
? this.gn4RecordsApi.tagRecord(uniqueIdentifier, [categoryId], false)
|
|
738
|
+
: this.gn4RecordsApi.deleteTags(uniqueIdentifier, [categoryId])
|
|
739
|
+
})
|
|
740
|
+
)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
getRecordFlag(
|
|
744
|
+
uniqueIdentifier: string,
|
|
745
|
+
flagName: RecordFlag
|
|
746
|
+
): Observable<boolean> {
|
|
747
|
+
return this.gn4RecordsApi.getRecordTags(uniqueIdentifier).pipe(
|
|
748
|
+
withLatestFrom(this.getCategoryIdForFlag(flagName)),
|
|
749
|
+
map(([tags, categoryId]) => {
|
|
750
|
+
if (categoryId === null) return false
|
|
751
|
+
return Array.from(tags).some((tag) => tag.id === categoryId)
|
|
752
|
+
})
|
|
753
|
+
)
|
|
754
|
+
}
|
|
707
755
|
}
|
|
@@ -92,6 +92,13 @@ export class Gn4PlatformService implements PlatformServiceInterface {
|
|
|
92
92
|
switchMap((apiUser) => this.mapper.userFromMeApi(apiUser)),
|
|
93
93
|
shareReplay({ bufferSize: 1, refCount: true })
|
|
94
94
|
)
|
|
95
|
+
private isAdmin$ = this.disableAuth
|
|
96
|
+
? of(false)
|
|
97
|
+
: of(true).pipe(
|
|
98
|
+
switchMap(() => this.meApi.getMe()),
|
|
99
|
+
map((apiUser) => !!apiUser?.admin),
|
|
100
|
+
shareReplay({ bufferSize: 1, refCount: true })
|
|
101
|
+
)
|
|
95
102
|
|
|
96
103
|
/**
|
|
97
104
|
* A map of already loaded thesauri (groups of keywords); the key is a URI
|
|
@@ -146,6 +153,10 @@ export class Gn4PlatformService implements PlatformServiceInterface {
|
|
|
146
153
|
return this.isUserAnonymous$
|
|
147
154
|
}
|
|
148
155
|
|
|
156
|
+
isAdministrator(): Observable<boolean> {
|
|
157
|
+
return this.isAdmin$
|
|
158
|
+
}
|
|
159
|
+
|
|
149
160
|
getOrganizations(): Observable<Organization[]> {
|
|
150
161
|
return undefined
|
|
151
162
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type RecordFlag = 'IS_REFERENCE_DATASET'
|