geonetwork-ui 2.11.0-dev.5c2e5295e → 2.11.0-dev.6804f3b43
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 +2533 -1984
- package/fesm2022/geonetwork-ui.mjs.map +1 -1
- package/index.d.ts +323 -153
- package/index.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
- package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +135 -35
- package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
- package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
- 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/repository/records-repository.interface.ts +2 -2
- package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
- package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
- package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
- 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.actions.ts +4 -8
- package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
- package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
- package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
- package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
- 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 +12 -4
- package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +63 -19
- package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
- package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
- package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -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 +58 -4
- package/src/libs/feature/search/src/lib/utils/service/fields.ts +92 -9
- package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
- package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
- package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.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.html +10 -1
- package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
- 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/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
- package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +221 -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 +25 -0
- package/src/libs/util/app-config/src/lib/fixtures.ts +33 -1
- package/src/libs/util/app-config/src/lib/model.ts +15 -1
- package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
- package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
- package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
- package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
- package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
- package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
- package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
- package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
- package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
- package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
- package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
- package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
- package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
- package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
- 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 +22 -4
- package/translations/en.json +21 -3
- package/translations/es.json +19 -1
- package/translations/fr.json +26 -8
- package/translations/it.json +20 -2
- package/translations/nl.json +19 -1
- package/translations/pt.json +19 -1
- package/translations/sk.json +20 -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/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/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
- /package/src/libs/{feature/map/src/lib/geocoding/geocoding.component.css → ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css} +0 -0
|
@@ -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 {
|
|
@@ -142,6 +168,7 @@ export class ElasticsearchService {
|
|
|
142
168
|
values: uuids,
|
|
143
169
|
},
|
|
144
170
|
},
|
|
171
|
+
size: uuids.length,
|
|
145
172
|
}
|
|
146
173
|
}
|
|
147
174
|
|
|
@@ -263,8 +290,68 @@ export class ElasticsearchService {
|
|
|
263
290
|
return this.metadataLang === 'current'
|
|
264
291
|
}
|
|
265
292
|
|
|
266
|
-
private
|
|
293
|
+
private findSpatialFilterExtent(
|
|
267
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)
|
|
268
355
|
): FilterQuery {
|
|
269
356
|
const addQuote = (key: string) => (/^\/.+\/$/.test(key) ? key : `"${key}"`)
|
|
270
357
|
const makeQuery = (filter: FieldFilter): string => {
|
|
@@ -285,50 +372,49 @@ export class ElasticsearchService {
|
|
|
285
372
|
? filters
|
|
286
373
|
: Object.keys(filters)
|
|
287
374
|
.filter((fieldname) => fieldname !== 'gn-ui-crossFieldFilter')
|
|
375
|
+
.filter((fieldname) => !isBoundingBox(filters[fieldname]))
|
|
288
376
|
.filter((fieldname) => !isDateRange(filters[fieldname]))
|
|
377
|
+
.filter((fieldname) => !Array.isArray(filters[fieldname]))
|
|
289
378
|
.filter(
|
|
290
379
|
(fieldname) =>
|
|
291
380
|
filters[fieldname] &&
|
|
292
381
|
JSON.stringify(filters[fieldname]) !== '{}'
|
|
293
382
|
)
|
|
294
383
|
.map(
|
|
295
|
-
(fieldname) =>
|
|
384
|
+
(fieldname) =>
|
|
385
|
+
`${this.fieldAliases[fieldname] ?? fieldname}:(${makeQuery(filters[fieldname])})`
|
|
296
386
|
)
|
|
297
387
|
.join(' AND ')
|
|
298
388
|
if (filters['gn-ui-crossFieldFilter']) {
|
|
299
389
|
queryString = `${queryString} AND (${filters['gn-ui-crossFieldFilter']})`
|
|
300
390
|
}
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
searchField,
|
|
306
|
-
dateRange,
|
|
307
|
-
} as {
|
|
308
|
-
searchField: string
|
|
309
|
-
dateRange: DateRange
|
|
310
|
-
}
|
|
311
|
-
})[0]
|
|
391
|
+
const queryRanges = Object.entries(filters).filter(([, value]) =>
|
|
392
|
+
isDateRange(value)
|
|
393
|
+
) as [string, DateRange][]
|
|
312
394
|
const queryParts = [
|
|
313
395
|
queryString && {
|
|
314
396
|
query_string: {
|
|
315
397
|
query: queryString,
|
|
316
398
|
},
|
|
317
399
|
},
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
+
],
|
|
329
413
|
},
|
|
414
|
+
relation: 'intersects',
|
|
330
415
|
},
|
|
331
416
|
},
|
|
417
|
+
},
|
|
332
418
|
].filter(Boolean)
|
|
333
419
|
return queryParts.length > 0 ? (queryParts as FilterQuery) : undefined
|
|
334
420
|
}
|
|
@@ -367,7 +453,12 @@ export class ElasticsearchService {
|
|
|
367
453
|
},
|
|
368
454
|
})
|
|
369
455
|
}
|
|
370
|
-
|
|
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
|
+
)
|
|
371
462
|
if (queryFilters) {
|
|
372
463
|
filter.push(...queryFilters)
|
|
373
464
|
}
|
|
@@ -378,7 +469,10 @@ export class ElasticsearchService {
|
|
|
378
469
|
},
|
|
379
470
|
})
|
|
380
471
|
}
|
|
381
|
-
|
|
472
|
+
const boostGeometry = spatialFilterExtent
|
|
473
|
+
? bboxToPolygon(spatialFilterExtent)
|
|
474
|
+
: geometry
|
|
475
|
+
if (boostGeometry) {
|
|
382
476
|
// boosts applied using the filter geometry:
|
|
383
477
|
// * records completely within the geometry receive a boost of 5
|
|
384
478
|
// * records intersecting the geometry receive a boost of 2
|
|
@@ -388,7 +482,7 @@ export class ElasticsearchService {
|
|
|
388
482
|
{
|
|
389
483
|
geo_shape: {
|
|
390
484
|
geom: {
|
|
391
|
-
shape:
|
|
485
|
+
shape: boostGeometry,
|
|
392
486
|
relation: 'within',
|
|
393
487
|
},
|
|
394
488
|
boost: 5.0,
|
|
@@ -397,7 +491,7 @@ export class ElasticsearchService {
|
|
|
397
491
|
{
|
|
398
492
|
geo_shape: {
|
|
399
493
|
geom: {
|
|
400
|
-
shape:
|
|
494
|
+
shape: boostGeometry,
|
|
401
495
|
relation: 'intersects',
|
|
402
496
|
},
|
|
403
497
|
boost: 2.0,
|
|
@@ -408,7 +502,7 @@ export class ElasticsearchService {
|
|
|
408
502
|
// this will boost the results variably depending on their distance from the given geometry
|
|
409
503
|
// note: this takes into account the `location` field of a record; this is generally the center of all spatial extents
|
|
410
504
|
// combined, and thus the actual size/coverage of the record spatial extent isn't relevant here
|
|
411
|
-
const bbox = getGeometryBoundingBox(
|
|
505
|
+
const bbox = getGeometryBoundingBox(boostGeometry)
|
|
412
506
|
const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]
|
|
413
507
|
const northToCenter = new LineString([
|
|
414
508
|
[center[0], bbox[3]],
|
|
@@ -634,6 +728,12 @@ export class ElasticsearchService {
|
|
|
634
728
|
order: {
|
|
635
729
|
[`_${aggregation.sort[1]}`]: aggregation.sort[0],
|
|
636
730
|
},
|
|
731
|
+
...(aggregation.includeValues && {
|
|
732
|
+
include: aggregation.includeValues,
|
|
733
|
+
}),
|
|
734
|
+
...(aggregation.excludeValues && {
|
|
735
|
+
exclude: aggregation.excludeValues,
|
|
736
|
+
}),
|
|
637
737
|
},
|
|
638
738
|
}
|
|
639
739
|
case 'histogram':
|
|
@@ -14,10 +14,13 @@ import {
|
|
|
14
14
|
} from '../../../../../../libs/api/metadata-converter/src'
|
|
15
15
|
import { PublicationVersionError } from '../../../../../../libs/common/domain/src/lib/model/error'
|
|
16
16
|
import {
|
|
17
|
+
AssociatedRecord,
|
|
17
18
|
CatalogRecord,
|
|
18
19
|
DatasetFeatureCatalog,
|
|
19
20
|
DatasetFeatureType,
|
|
20
21
|
LanguageCode,
|
|
22
|
+
LinkedRecord,
|
|
23
|
+
RecordRelation,
|
|
21
24
|
} from '../../../../../../libs/common/domain/src/lib/model/record'
|
|
22
25
|
import {
|
|
23
26
|
Aggregations,
|
|
@@ -145,7 +148,7 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
145
148
|
return this.gn4SearchApi
|
|
146
149
|
.search(
|
|
147
150
|
'bucket',
|
|
148
|
-
['fcats', 'hassources'],
|
|
151
|
+
['fcats', 'hassources', 'siblings', 'associated'],
|
|
149
152
|
JSON.stringify(
|
|
150
153
|
this.gn4SearchHelper.getMetadataByIdsPayload([uniqueIdentifier])
|
|
151
154
|
)
|
|
@@ -249,22 +252,43 @@ export class Gn4Repository implements RecordsRepositoryInterface {
|
|
|
249
252
|
)
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
255
|
+
getLinkedRecords(record: CatalogRecord): Observable<LinkedRecord[]> {
|
|
256
|
+
const siblings = (record.extras?.['siblings'] ?? []) as AssociatedRecord[]
|
|
257
|
+
const relations: Array<[RecordRelation, string[]]> = [
|
|
258
|
+
['source', (record.extras?.['sourcesIdentifiers'] ?? []) as string[]],
|
|
259
|
+
['sourceOf', (record.extras?.['sourceOfIdentifiers'] ?? []) as string[]],
|
|
260
|
+
['sibling', siblings.map(({ uniqueIdentifier }) => uniqueIdentifier)],
|
|
261
|
+
[
|
|
262
|
+
'associated',
|
|
263
|
+
(record.extras?.['associatedIdentifiers'] ?? []) as string[],
|
|
264
|
+
],
|
|
265
|
+
]
|
|
266
|
+
const requested = relations.filter(
|
|
267
|
+
([, identifiers]) => identifiers.length > 0
|
|
268
|
+
)
|
|
269
|
+
if (requested.length === 0) {
|
|
270
|
+
return of([])
|
|
266
271
|
}
|
|
267
|
-
return
|
|
272
|
+
return forkJoin(
|
|
273
|
+
requested.map(([relation, identifiers]) =>
|
|
274
|
+
this.getMultipleRecords(identifiers).pipe(
|
|
275
|
+
map((records) =>
|
|
276
|
+
(records ?? []).map((record) => ({
|
|
277
|
+
record,
|
|
278
|
+
relation,
|
|
279
|
+
associationType:
|
|
280
|
+
relation === 'sibling'
|
|
281
|
+
? siblings.find(
|
|
282
|
+
({ uniqueIdentifier }) =>
|
|
283
|
+
uniqueIdentifier === record.uniqueIdentifier
|
|
284
|
+
)?.associationType
|
|
285
|
+
: undefined,
|
|
286
|
+
}))
|
|
287
|
+
),
|
|
288
|
+
catchError(() => of([]))
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
).pipe(map((groups) => groups.flat()))
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
aggregate(params: AggregationsParams): Observable<Aggregations> {
|
|
@@ -294,6 +294,18 @@ export interface AssociatedRecord {
|
|
|
294
294
|
associationType: AssociationType
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Nature of the link between a record and another one; the association type is
|
|
299
|
+
* only known for 'sibling' relations, as it is held by the declaring record.
|
|
300
|
+
*/
|
|
301
|
+
export type RecordRelation = 'source' | 'sourceOf' | 'sibling' | 'associated'
|
|
302
|
+
|
|
303
|
+
export interface LinkedRecord {
|
|
304
|
+
record: CatalogRecord
|
|
305
|
+
relation: RecordRelation
|
|
306
|
+
associationType?: AssociationType
|
|
307
|
+
}
|
|
308
|
+
|
|
297
309
|
export interface DatasetRecord extends BaseRecord {
|
|
298
310
|
kind: 'dataset'
|
|
299
311
|
status: RecordStatus
|
|
@@ -6,17 +6,39 @@ export type FieldFilterByRange = {
|
|
|
6
6
|
start?: Date
|
|
7
7
|
end?: Date
|
|
8
8
|
}
|
|
9
|
+
// matches util/shared's BoundingBox; duplicated here to avoid a circular
|
|
10
|
+
// dependency (util/shared already depends on common/domain)
|
|
11
|
+
export type FieldFilterByBoundingBox = [number, number, number, number]
|
|
9
12
|
|
|
10
13
|
export type FieldFilter =
|
|
11
14
|
| FieldFilterByExpression
|
|
12
15
|
| FieldFilterByValues
|
|
13
16
|
| FieldFilterByRange
|
|
17
|
+
| FieldFilterByBoundingBox
|
|
14
18
|
export type FieldFilters = Record<FieldName, FieldFilter>
|
|
15
19
|
|
|
16
20
|
export type QueryString = {
|
|
17
21
|
query_string: string
|
|
18
22
|
}
|
|
19
23
|
export type QueryRange = {
|
|
20
|
-
range: Record<
|
|
24
|
+
range: Record<
|
|
25
|
+
string,
|
|
26
|
+
{ format: string; gte?: string; lte?: string; relation?: string }
|
|
27
|
+
>
|
|
21
28
|
}
|
|
22
|
-
export type
|
|
29
|
+
export type QueryGeoShape = {
|
|
30
|
+
geo_shape: {
|
|
31
|
+
geom: {
|
|
32
|
+
shape: { type: string; coordinates: number[][] }
|
|
33
|
+
relation: string
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export type QueryBool = {
|
|
38
|
+
bool: {
|
|
39
|
+
filter: QueryRange[]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export type FilterQuery = Array<
|
|
43
|
+
QueryString | QueryRange | QueryGeoShape | QueryBool
|
|
44
|
+
>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
CatalogRecord,
|
|
11
11
|
DatasetFeatureCatalog,
|
|
12
12
|
LanguageCode,
|
|
13
|
+
LinkedRecord,
|
|
13
14
|
} from '../model/record'
|
|
14
15
|
|
|
15
16
|
export abstract class RecordsRepositoryInterface {
|
|
@@ -23,8 +24,7 @@ export abstract class RecordsRepositoryInterface {
|
|
|
23
24
|
abstract getSimilarRecords(
|
|
24
25
|
similarTo: CatalogRecord
|
|
25
26
|
): Observable<CatalogRecord[]>
|
|
26
|
-
abstract
|
|
27
|
-
abstract getSourceOf(record: CatalogRecord): Observable<CatalogRecord[]>
|
|
27
|
+
abstract getLinkedRecords(record: CatalogRecord): Observable<LinkedRecord[]>
|
|
28
28
|
abstract fuzzySearch(query: string): Observable<SearchResults>
|
|
29
29
|
abstract canDuplicate(record: CatalogRecord): boolean
|
|
30
30
|
abstract canDelete(record: CatalogRecord): Observable<boolean>
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
ChangeDetectorRef,
|
|
4
4
|
Component,
|
|
5
|
+
inject,
|
|
5
6
|
Input,
|
|
6
7
|
Output,
|
|
7
|
-
inject,
|
|
8
8
|
} from '@angular/core'
|
|
9
9
|
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
10
10
|
import {
|
|
@@ -183,7 +183,7 @@ export class ChartViewComponent {
|
|
|
183
183
|
yChoices$ = this.properties$.pipe(
|
|
184
184
|
map((properties) =>
|
|
185
185
|
properties
|
|
186
|
-
.filter((prop) => prop.type === 'number'
|
|
186
|
+
.filter((prop) => prop.type === 'number')
|
|
187
187
|
.map((prop) => ({ value: prop.name, label: prop.label || prop.name }))
|
|
188
188
|
),
|
|
189
189
|
tap((choices) => {
|
|
@@ -222,6 +222,7 @@ export class ChartViewComponent {
|
|
|
222
222
|
switchMap(([dataset, xProp, yProp, aggregation]) => {
|
|
223
223
|
const fieldAgg: FieldAggregation =
|
|
224
224
|
aggregation === 'count' ? ['count'] : [aggregation, yProp]
|
|
225
|
+
this.loading = true
|
|
225
226
|
return dataset
|
|
226
227
|
.groupBy(['distinct', xProp])
|
|
227
228
|
.aggregate(fieldAgg)
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
ChangeDetectorRef,
|
|
4
4
|
Component,
|
|
5
|
+
inject,
|
|
5
6
|
Input,
|
|
6
7
|
OnDestroy,
|
|
7
8
|
OnInit,
|
|
8
9
|
ViewChild,
|
|
9
|
-
inject,
|
|
10
10
|
} from '@angular/core'
|
|
11
11
|
import {
|
|
12
12
|
DataTableComponent,
|
|
@@ -75,6 +75,7 @@ export class GeoTableViewComponent implements OnInit, OnDestroy {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
private async initMapContext(): Promise<MapContext> {
|
|
78
|
+
this.dataset.load()
|
|
78
79
|
this.dataset.selectAll()
|
|
79
80
|
return {
|
|
80
81
|
layers: [
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inject, Injectable } from '@angular/core'
|
|
2
2
|
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
|
|
3
3
|
import {
|
|
4
|
+
GetCollectionItemsOptions,
|
|
4
5
|
OgcApiCollectionInfo,
|
|
5
6
|
OgcApiEndpoint,
|
|
6
7
|
OgcApiRecord,
|
|
7
|
-
WfsEndpoint,
|
|
8
|
-
WfsVersion,
|
|
9
|
-
TmsEndpoint,
|
|
10
8
|
StacEndpoint,
|
|
11
|
-
GetCollectionItemsOptions,
|
|
12
9
|
StacItemsDocument,
|
|
10
|
+
TmsEndpoint,
|
|
11
|
+
WfsEndpoint,
|
|
12
|
+
WfsVersion,
|
|
13
13
|
} from '@camptocamp/ogc-client'
|
|
14
14
|
import {
|
|
15
15
|
BaseReader,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from '../../../../../../libs/util/shared/src'
|
|
27
27
|
import type { FeatureCollection } from 'geojson'
|
|
28
28
|
import { from, Observable, throwError } from 'rxjs'
|
|
29
|
-
import { catchError, map, switchMap
|
|
29
|
+
import { catchError, map, switchMap } from 'rxjs/operators'
|
|
30
30
|
import {
|
|
31
31
|
DatasetOnlineResource,
|
|
32
32
|
DatasetServiceDistribution,
|
|
@@ -347,14 +347,11 @@ export class DataService {
|
|
|
347
347
|
if (link.type === 'service' && link.accessServiceProtocol === 'wfs') {
|
|
348
348
|
const wfsUrlEndpoint = this.proxy.getProxiedUrl(link.url.toString())
|
|
349
349
|
return from(
|
|
350
|
-
openDataset(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
},
|
|
356
|
-
cacheActive
|
|
357
|
-
)
|
|
350
|
+
openDataset(wfsUrlEndpoint, {
|
|
351
|
+
typeHint: 'wfs',
|
|
352
|
+
wfsFeatureType: link.name,
|
|
353
|
+
enableCache: cacheActive,
|
|
354
|
+
})
|
|
358
355
|
)
|
|
359
356
|
} else if (link.type === 'download') {
|
|
360
357
|
const linkProxifiedUrl = this.proxy.getProxiedUrl(link.url.toString())
|
|
@@ -364,7 +361,10 @@ export class DataService {
|
|
|
364
361
|
? (format as SupportedType)
|
|
365
362
|
: undefined
|
|
366
363
|
return from(
|
|
367
|
-
openDataset(linkProxifiedUrl,
|
|
364
|
+
openDataset(linkProxifiedUrl, {
|
|
365
|
+
typeHint: supportedType,
|
|
366
|
+
enableCache: cacheActive,
|
|
367
|
+
})
|
|
368
368
|
).pipe()
|
|
369
369
|
} else if (
|
|
370
370
|
link.type === 'service' &&
|
|
@@ -374,7 +374,9 @@ export class DataService {
|
|
|
374
374
|
link.url.toString(),
|
|
375
375
|
'geojson'
|
|
376
376
|
)
|
|
377
|
-
return from(
|
|
377
|
+
return from(
|
|
378
|
+
openDataset(url, { typeHint: 'geojson', enableCache: cacheActive })
|
|
379
|
+
).pipe()
|
|
378
380
|
} else if (
|
|
379
381
|
link.type === 'service' &&
|
|
380
382
|
link.accessServiceProtocol === 'ogcFeatures'
|
|
@@ -395,12 +397,10 @@ export class DataService {
|
|
|
395
397
|
}
|
|
396
398
|
const urlWithoutLimit = new URL(geojsonUrl)
|
|
397
399
|
urlWithoutLimit.searchParams.delete('limit')
|
|
398
|
-
return openDataset(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
cacheActive
|
|
403
|
-
)
|
|
400
|
+
return openDataset(urlWithoutLimit.toString(), {
|
|
401
|
+
typeHint: 'geojson',
|
|
402
|
+
enableCache: cacheActive,
|
|
403
|
+
})
|
|
404
404
|
})
|
|
405
405
|
)
|
|
406
406
|
}
|
|
@@ -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 {}
|
|
@@ -18,10 +18,12 @@ export class NotificationsService {
|
|
|
18
18
|
error && console.error(error)
|
|
19
19
|
const id = Math.floor(Math.random() * 1000000)
|
|
20
20
|
this.notifications$.next([...this.notifications$.value, { ...content, id }])
|
|
21
|
-
if (typeof timeoutMs
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
if (typeof timeoutMs !== 'undefined') {
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
this.removeNotificationById(id)
|
|
24
|
+
}, timeoutMs)
|
|
25
|
+
}
|
|
26
|
+
return id
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
removeNotificationById(id: number) {
|