geonetwork-ui 2.11.0-dev.1cf4ab0c6 → 2.11.0-dev.30db8c2d6
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 +1415 -1282
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +165 -90
- package/index.d.ts.map +1 -1
- package/package.json +6 -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 +317 -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 +2 -0
- package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +98 -0
- package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +7 -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 +67 -10
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +5 -34
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +6 -13
- 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 +12 -2
- package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +1 -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/map/src/index.ts +0 -2
- package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
- 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/search/src/index.ts +2 -0
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +7 -1
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +26 -4
- 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 +1 -0
- package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +56 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +58 -16
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.html +2 -2
- package/src/libs/ui/elements/src/lib/record-feature-catalog/feature-catalog-list/feature-catalog-list.component.ts +2 -2
- 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/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.html +8 -2
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +34 -5
- package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
- package/src/libs/util/app-config/src/lib/app-config.ts +34 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +37 -1
- package/src/libs/util/app-config/src/lib/model.ts +24 -1
- package/src/libs/util/shared/src/lib/utils/geojson.ts +16 -0
- package/translations/de.json +8 -2
- package/translations/en.json +10 -4
- package/translations/es.json +8 -2
- package/translations/fr.json +15 -9
- package/translations/it.json +8 -2
- package/translations/nl.json +8 -2
- package/translations/pt.json +8 -2
- package/translations/sk.json +8 -2
- package/src/libs/feature/map/src/lib/geocoding/geocoding.component.css +0 -0
- 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
|
@@ -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,6 +299,56 @@ export class ElasticsearchService {
|
|
|
293
299
|
return Object.values(filters).find(isBoundingBox)
|
|
294
300
|
}
|
|
295
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
|
+
|
|
296
352
|
private filtersToQuery(
|
|
297
353
|
filters: FieldFilters | FiltersAggregationParams | string,
|
|
298
354
|
spatialFilterExtent = this.findSpatialFilterExtent(filters)
|
|
@@ -325,7 +381,8 @@ export class ElasticsearchService {
|
|
|
325
381
|
JSON.stringify(filters[fieldname]) !== '{}'
|
|
326
382
|
)
|
|
327
383
|
.map(
|
|
328
|
-
(fieldname) =>
|
|
384
|
+
(fieldname) =>
|
|
385
|
+
`${this.fieldAliases[fieldname] ?? fieldname}:(${makeQuery(filters[fieldname])})`
|
|
329
386
|
)
|
|
330
387
|
.join(' AND ')
|
|
331
388
|
if (filters['gn-ui-crossFieldFilter']) {
|
|
@@ -340,15 +397,9 @@ export class ElasticsearchService {
|
|
|
340
397
|
query: queryString,
|
|
341
398
|
},
|
|
342
399
|
},
|
|
343
|
-
...queryRanges.map(([searchField, dateRange]) =>
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
...(dateRange.start && { gte: formatDate(dateRange.start) }),
|
|
347
|
-
...(dateRange.end && { lte: formatDate(dateRange.end) }),
|
|
348
|
-
format: 'yyyy-MM-dd',
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
})),
|
|
400
|
+
...queryRanges.map(([searchField, dateRange]) =>
|
|
401
|
+
this.buildDateRangeQuery(searchField, dateRange)
|
|
402
|
+
),
|
|
352
403
|
spatialFilterExtent && {
|
|
353
404
|
geo_shape: {
|
|
354
405
|
geom: {
|
|
@@ -677,6 +728,12 @@ export class ElasticsearchService {
|
|
|
677
728
|
order: {
|
|
678
729
|
[`_${aggregation.sort[1]}`]: aggregation.sort[0],
|
|
679
730
|
},
|
|
731
|
+
...(aggregation.includeValues && {
|
|
732
|
+
include: aggregation.includeValues,
|
|
733
|
+
}),
|
|
734
|
+
...(aggregation.excludeValues && {
|
|
735
|
+
exclude: aggregation.excludeValues,
|
|
736
|
+
}),
|
|
680
737
|
},
|
|
681
738
|
}
|
|
682
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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
record.extras['featureTypes'].length > 0
|
|
189
|
+
'featureTypeDescriptions' in record &&
|
|
190
|
+
record.featureTypeDescriptions?.length > 0
|
|
220
191
|
) {
|
|
221
|
-
return of(
|
|
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.
|
|
202
|
+
record ? this.queryLinkedFeatureCatalog(record, visited) : of(null)
|
|
232
203
|
)
|
|
233
204
|
)
|
|
234
205
|
}
|
|
@@ -316,6 +316,7 @@ export interface DatasetRecord extends BaseRecord {
|
|
|
316
316
|
spatialExtents: Array<DatasetSpatialExtent>
|
|
317
317
|
temporalExtents: Array<DatasetTemporalExtent>
|
|
318
318
|
spatialRepresentation?: SpatialRepresentationType
|
|
319
|
+
featureTypeDescriptions?: DatasetFeatureCatalog['featureTypes']
|
|
319
320
|
}
|
|
320
321
|
|
|
321
322
|
export interface ServiceEndpoint {
|
|
@@ -350,30 +351,22 @@ export interface ReuseRecord extends BaseRecord {
|
|
|
350
351
|
export type DatasetFeatureCatalog = {
|
|
351
352
|
featureTypes: Array<{
|
|
352
353
|
name: string
|
|
353
|
-
|
|
354
|
+
description?: string
|
|
354
355
|
attributes: DatasetFeatureAttribute[]
|
|
355
356
|
}>
|
|
356
357
|
}
|
|
357
358
|
export interface DatasetFeatureAttributeValue {
|
|
358
359
|
code?: string
|
|
359
|
-
|
|
360
|
+
description?: string
|
|
360
361
|
label?: string
|
|
361
362
|
}
|
|
362
363
|
export interface DatasetFeatureAttribute {
|
|
363
364
|
name: string
|
|
364
|
-
|
|
365
|
-
type
|
|
366
|
-
code
|
|
365
|
+
description?: string
|
|
366
|
+
type?: string
|
|
367
|
+
code?: string
|
|
367
368
|
values?: Array<DatasetFeatureAttributeValue>
|
|
368
369
|
}
|
|
369
|
-
export interface DatasetFeatureType {
|
|
370
|
-
aliases: string
|
|
371
|
-
code: string
|
|
372
|
-
isAbstract: string
|
|
373
|
-
typeName: string
|
|
374
|
-
definition: string
|
|
375
|
-
attributeTable: Array<DatasetFeatureAttribute>
|
|
376
|
-
}
|
|
377
370
|
|
|
378
371
|
export type ReuseType = 'application' | 'map' | 'other'
|
|
379
372
|
|
|
@@ -21,7 +21,10 @@ export type QueryString = {
|
|
|
21
21
|
query_string: string
|
|
22
22
|
}
|
|
23
23
|
export type QueryRange = {
|
|
24
|
-
range: Record<
|
|
24
|
+
range: Record<
|
|
25
|
+
string,
|
|
26
|
+
{ format: string; gte?: string; lte?: string; relation?: string }
|
|
27
|
+
>
|
|
25
28
|
}
|
|
26
29
|
export type QueryGeoShape = {
|
|
27
30
|
geo_shape: {
|
|
@@ -31,4 +34,11 @@ export type QueryGeoShape = {
|
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
|
-
export type
|
|
37
|
+
export type QueryBool = {
|
|
38
|
+
bool: {
|
|
39
|
+
filter: QueryRange[]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export type FilterQuery = Array<
|
|
43
|
+
QueryString | QueryRange | QueryGeoShape | QueryBool
|
|
44
|
+
>
|
|
@@ -17,7 +17,7 @@ export abstract class RecordsRepositoryInterface {
|
|
|
17
17
|
abstract search(params: SearchParams): Observable<SearchResults>
|
|
18
18
|
abstract getMatchesCount(filters: FieldFilters): Observable<number>
|
|
19
19
|
abstract getRecord(uniqueIdentifier: string): Observable<CatalogRecord | null>
|
|
20
|
-
abstract
|
|
20
|
+
abstract queryLinkedFeatureCatalog(
|
|
21
21
|
record: CatalogRecord
|
|
22
22
|
): Observable<DatasetFeatureCatalog | null>
|
|
23
23
|
abstract aggregate(params: AggregationsParams): Observable<Aggregations>
|
|
@@ -164,69 +164,49 @@ As such, **it is not very interesting at all.**`,
|
|
|
164
164
|
extras: {
|
|
165
165
|
isPublishedToAll: true,
|
|
166
166
|
edit: true,
|
|
167
|
-
featureTypes: [
|
|
168
|
-
{
|
|
169
|
-
attributeTable: [
|
|
170
|
-
{
|
|
171
|
-
code: 'OBJECTID',
|
|
172
|
-
name: 'OBJECTID',
|
|
173
|
-
link: '',
|
|
174
|
-
definition: 'Object identifier',
|
|
175
|
-
type: 'OID',
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
code: 'NOM',
|
|
179
|
-
name: 'Nom',
|
|
180
|
-
link: '',
|
|
181
|
-
definition: 'Nom de la rue',
|
|
182
|
-
type: 'String (48)',
|
|
183
|
-
values: [
|
|
184
|
-
{
|
|
185
|
-
code: 'Pomme',
|
|
186
|
-
label: 'Les Pommiers',
|
|
187
|
-
description: 'Sous les pommiers',
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
code: 'Cotton',
|
|
191
|
-
label: 'Rue Cotton',
|
|
192
|
-
description: 'Rue Cotton',
|
|
193
|
-
},
|
|
194
|
-
{ code: "Passage de l'échiquier" },
|
|
195
|
-
{ description: 'Rue du Charlieu' },
|
|
196
|
-
],
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
code: 'RUE',
|
|
200
|
-
name: 'Rue',
|
|
201
|
-
link: '',
|
|
202
|
-
definition: '',
|
|
203
|
-
type: 'String (50)',
|
|
204
|
-
},
|
|
205
|
-
],
|
|
206
|
-
code: '',
|
|
207
|
-
aliases: '',
|
|
208
|
-
typeName: "Catalogue d'attributs N°1",
|
|
209
|
-
definition: 'Définition du catalogue d attributs N°1',
|
|
210
|
-
isAbstract: 'false',
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
attributeTable: [
|
|
214
|
-
{
|
|
215
|
-
code: 'UniqueObject',
|
|
216
|
-
name: 'unique object ',
|
|
217
|
-
link: '',
|
|
218
|
-
definition: 'this is the only object of this catalog',
|
|
219
|
-
type: 'String (50)',
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
code: '',
|
|
223
|
-
aliases: '',
|
|
224
|
-
typeName: "Catalogue d'attributs N°2",
|
|
225
|
-
definition: 'Définition du catalogue d attributs N°2',
|
|
226
|
-
isAbstract: 'false',
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
167
|
},
|
|
168
|
+
featureTypeDescriptions: [
|
|
169
|
+
{
|
|
170
|
+
name: "Catalogue d'attributs N°1",
|
|
171
|
+
description: 'Définition du catalogue d attributs N°1',
|
|
172
|
+
attributes: [
|
|
173
|
+
{
|
|
174
|
+
name: 'OBJECTID',
|
|
175
|
+
description: 'Object identifier',
|
|
176
|
+
code: 'OBJECTID',
|
|
177
|
+
type: 'OID',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'Nom',
|
|
181
|
+
description: 'Nom de la rue',
|
|
182
|
+
code: 'NOM',
|
|
183
|
+
type: 'String (48)',
|
|
184
|
+
values: [
|
|
185
|
+
{ code: 'Pomme', label: 'Les Pommiers' },
|
|
186
|
+
{ code: 'Cotton', label: 'Rue Cotton' },
|
|
187
|
+
{ code: "Passage de l'échiquier" },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'Rue',
|
|
192
|
+
code: 'RUE',
|
|
193
|
+
type: 'String (50)',
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "Catalogue d'attributs N°2",
|
|
199
|
+
description: 'Définition du catalogue d attributs N°2',
|
|
200
|
+
attributes: [
|
|
201
|
+
{
|
|
202
|
+
name: 'unique object ',
|
|
203
|
+
description: 'this is the only object of this catalog',
|
|
204
|
+
code: 'UniqueObject',
|
|
205
|
+
type: 'String (50)',
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
],
|
|
230
210
|
},
|
|
231
211
|
{
|
|
232
212
|
uniqueIdentifier: '7d002c4c-92ef-4b9f-a568-d732f740b99e',
|
|
@@ -209,7 +209,8 @@ export class DataService {
|
|
|
209
209
|
ogcApiLink: DatasetServiceDistribution
|
|
210
210
|
): Promise<DatasetOnlineResource[]> {
|
|
211
211
|
const collectionInfo = await this.getDownloadUrlsFromOgcApi(
|
|
212
|
-
ogcApiLink.url.href
|
|
212
|
+
ogcApiLink.url.href,
|
|
213
|
+
ogcApiLink.name
|
|
213
214
|
)
|
|
214
215
|
|
|
215
216
|
return Object.keys(collectionInfo.bulkDownloadLinks).map((mimeType) => {
|
|
@@ -229,31 +230,41 @@ export class DataService {
|
|
|
229
230
|
})
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
async
|
|
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
|
-
|
|
235
|
-
.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
.
|
|
239
|
-
|
|
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
|
-
|
|
249
|
-
.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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(
|
|
395
|
+
return from(
|
|
396
|
+
this.getDownloadUrlsFromOgcApi(link.url.href, link.name)
|
|
397
|
+
).pipe(
|
|
385
398
|
switchMap((collectionInfo) => {
|
|
386
399
|
const isMimeTypeJson = (mimeType: string): boolean => {
|
|
387
400
|
return mimeType.toLowerCase().indexOf('json') > -1
|
|
@@ -6,8 +6,6 @@ export * from './lib/feature-map.module'
|
|
|
6
6
|
export * from './lib/map-state-container/map-state-container.component'
|
|
7
7
|
export * from './lib/constant'
|
|
8
8
|
export * from './lib/utils'
|
|
9
|
-
export * from './lib/style'
|
|
10
9
|
export * from './lib/layers-panel/layers-panel.component'
|
|
11
10
|
export * from './lib/add-layer-from-catalog/add-layer-from-catalog.component'
|
|
12
11
|
export * from './lib/add-layer-from-catalog/add-layer-record-preview/add-layer-record-preview.component'
|
|
13
|
-
export * from './lib/geocoding/geocoding.component'
|
|
@@ -2,18 +2,11 @@ import { NgModule } from '@angular/core'
|
|
|
2
2
|
import { StoreModule } from '@ngrx/store'
|
|
3
3
|
import * as fromMap from './+state/map.reducer'
|
|
4
4
|
import { MapFacade } from './+state/map.facade'
|
|
5
|
-
import { GEOCODING_PROVIDER, GeocodingProvider } from './geocoding.service'
|
|
6
5
|
|
|
7
6
|
@NgModule({
|
|
8
7
|
imports: [
|
|
9
8
|
StoreModule.forFeature(fromMap.MAP_FEATURE_KEY, fromMap.mapReducer),
|
|
10
9
|
],
|
|
11
|
-
providers: [
|
|
12
|
-
MapFacade,
|
|
13
|
-
{
|
|
14
|
-
provide: GEOCODING_PROVIDER,
|
|
15
|
-
useValue: ['geonames', { maxRows: 5 }] as GeocodingProvider,
|
|
16
|
-
},
|
|
17
|
-
],
|
|
10
|
+
providers: [MapFacade],
|
|
18
11
|
})
|
|
19
12
|
export class FeatureMapModule {}
|
package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts
CHANGED
|
@@ -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
|
-
|
|
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 }) =>
|
|
43
|
+
switchMap(({ full }) =>
|
|
44
|
+
this.recordsRepository.queryLinkedFeatureCatalog(full)
|
|
45
|
+
),
|
|
44
46
|
map((featureCatalog) =>
|
|
45
47
|
MdViewActions.loadFeatureCatalogSuccess({
|
|
46
48
|
datasetCatalog: featureCatalog,
|
|
@@ -162,7 +162,7 @@ export class MdViewFacade {
|
|
|
162
162
|
link.accessServiceProtocol === 'ogcFeatures'
|
|
163
163
|
) {
|
|
164
164
|
return from(
|
|
165
|
-
this.dataService.getItemsFromOgcApi(link.url.href, 1)
|
|
165
|
+
this.dataService.getItemsFromOgcApi(link.url.href, link.name, 1)
|
|
166
166
|
).pipe(
|
|
167
167
|
map((collectionRecords: OgcApiRecord[]) => {
|
|
168
168
|
return collectionRecords &&
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './lib/feature-search.module'
|
|
2
|
+
export * from './lib/geocoding/geocoding.service'
|
|
3
|
+
export * from './lib/location-search/location-search.component'
|
|
2
4
|
export * from './lib/filter-geometry.token'
|
|
3
5
|
export * from './lib/record-url.token'
|
|
4
6
|
export * from './lib/state/actions'
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
(bboxChange)="onBboxChange($event)"
|
|
14
14
|
(errorChange)="onSpatialExtentError($event)"
|
|
15
15
|
[attr.data-cy-field]="fieldName"
|
|
16
|
-
|
|
16
|
+
>
|
|
17
|
+
<gn-ui-location-search
|
|
18
|
+
[placeholder]="'search.filters.spatialExtent.searchLocation' | translate"
|
|
19
|
+
(bboxSelected)="onLocationSelected($event)"
|
|
20
|
+
data-cy="locationSearch"
|
|
21
|
+
></gn-ui-location-search>
|
|
22
|
+
</gn-ui-spatial-extent-dropdown>
|
|
17
23
|
} @else {
|
|
18
24
|
<gn-ui-dropdown-multiselect
|
|
19
25
|
class="w-full"
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
Component,
|
|
4
|
+
InjectionToken,
|
|
4
5
|
Input,
|
|
5
6
|
OnInit,
|
|
7
|
+
ViewChild,
|
|
6
8
|
inject,
|
|
7
9
|
} from '@angular/core'
|
|
8
10
|
import {
|
|
@@ -26,8 +28,20 @@ import { DateRange } from '../../../../../../libs/api/repository/src'
|
|
|
26
28
|
import { CommonModule } from '@angular/common'
|
|
27
29
|
import { BoundingBox } from '../../../../../../libs/util/shared/src'
|
|
28
30
|
import { NotificationsService } from '../../../../../../libs/feature/notifications/src'
|
|
29
|
-
import { TranslateService } from '@ngx-translate/core'
|
|
30
|
-
import {
|
|
31
|
+
import { TranslatePipe, TranslateService } from '@ngx-translate/core'
|
|
32
|
+
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
33
|
+
import {
|
|
34
|
+
LocationBbox,
|
|
35
|
+
LocationSearchComponent,
|
|
36
|
+
} from '../location-search/location-search.component'
|
|
37
|
+
|
|
38
|
+
marker('search.filters.spatialExtent.searchLocation')
|
|
39
|
+
|
|
40
|
+
// in MB, null means no limit
|
|
41
|
+
export const SPATIAL_EXTENT_MAX_FILE_SIZE = new InjectionToken<number | null>(
|
|
42
|
+
'spatialExtentMaxFileSize',
|
|
43
|
+
{ factory: () => null }
|
|
44
|
+
)
|
|
31
45
|
|
|
32
46
|
@Component({
|
|
33
47
|
selector: 'gn-ui-filter-dropdown',
|
|
@@ -40,6 +54,8 @@ import { getOptionalSearchConfig } from '../../../../../../libs/util/app-config/
|
|
|
40
54
|
DateRangeDropdownComponent,
|
|
41
55
|
DropdownMultiselectComponent,
|
|
42
56
|
SpatialExtentDropdownComponent,
|
|
57
|
+
LocationSearchComponent,
|
|
58
|
+
TranslatePipe,
|
|
43
59
|
],
|
|
44
60
|
})
|
|
45
61
|
export class FilterDropdownComponent implements OnInit {
|
|
@@ -52,8 +68,10 @@ export class FilterDropdownComponent implements OnInit {
|
|
|
52
68
|
@Input() fieldName: string
|
|
53
69
|
@Input() title: string
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
@ViewChild(SpatialExtentDropdownComponent)
|
|
72
|
+
spatialExtentDropdown: SpatialExtentDropdownComponent
|
|
73
|
+
|
|
74
|
+
spatialExtentMaxFileSize = inject(SPATIAL_EXTENT_MAX_FILE_SIZE)
|
|
57
75
|
|
|
58
76
|
fieldType: FieldType
|
|
59
77
|
choices$: Observable<Choice[]>
|
|
@@ -96,6 +114,10 @@ export class FilterDropdownComponent implements OnInit {
|
|
|
96
114
|
this.clearSpatialExtentErrorNotification()
|
|
97
115
|
}
|
|
98
116
|
|
|
117
|
+
onLocationSelected({ bbox, label }: LocationBbox) {
|
|
118
|
+
this.spatialExtentDropdown.setLocationBbox(bbox, label)
|
|
119
|
+
}
|
|
120
|
+
|
|
99
121
|
onSpatialExtentError(error: SpatialExtentDropdownError) {
|
|
100
122
|
this.clearSpatialExtentErrorNotification()
|
|
101
123
|
this.spatialExtentErrorNotificationId =
|