geonetwork-ui 2.11.0-dev.5c2e5295e → 2.11.0-dev.80a8b0bfa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/fesm2022/geonetwork-ui.mjs +624 -495
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +80 -48
  4. package/index.d.ts.map +1 -1
  5. package/package.json +11 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +1 -0
  8. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  9. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  10. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  11. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  12. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  13. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  14. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  15. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  16. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  17. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  18. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  19. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  20. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  21. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  22. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  23. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  24. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  25. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  26. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  27. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  28. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  29. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  30. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  31. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  32. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  33. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  34. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  35. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  36. package/translations/de.json +2 -1
  37. package/translations/en.json +2 -1
  38. package/translations/es.json +1 -0
  39. package/translations/fr.json +2 -1
  40. package/translations/it.json +2 -1
  41. package/translations/nl.json +1 -0
  42. package/translations/pt.json +1 -0
  43. package/translations/sk.json +2 -1
  44. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -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
- getSources(record: CatalogRecord): Observable<CatalogRecord[]> {
253
- const sourcesIdentifiers = record.extras?.['sourcesIdentifiers'] as string[]
254
- if (sourcesIdentifiers && sourcesIdentifiers.length > 0) {
255
- return this.getMultipleRecords(sourcesIdentifiers)
256
- }
257
- return of(null)
258
- }
259
-
260
- getSourceOf(record: CatalogRecord): Observable<CatalogRecord[]> {
261
- const sourceOfIdentifiers = record.extras?.[
262
- 'sourceOfIdentifiers'
263
- ] as string[]
264
- if (sourceOfIdentifiers && sourceOfIdentifiers.length > 0) {
265
- return this.getMultipleRecords(sourceOfIdentifiers)
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 of(null)
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
@@ -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 getSources(record: CatalogRecord): Observable<CatalogRecord[]>
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' || prop.type === 'date')
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 { Injectable, inject } from '@angular/core'
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, tap } from 'rxjs/operators'
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
- wfsUrlEndpoint,
352
- 'wfs',
353
- {
354
- wfsFeatureType: link.name,
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, supportedType, undefined, cacheActive)
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(openDataset(url, 'geojson', undefined, cacheActive)).pipe()
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
- urlWithoutLimit.toString(),
400
- 'geojson',
401
- undefined,
402
- cacheActive
403
- )
400
+ return openDataset(urlWithoutLimit.toString(), {
401
+ typeHint: 'geojson',
402
+ enableCache: cacheActive,
403
+ })
404
404
  })
405
405
  )
406
406
  }
@@ -3,6 +3,7 @@ import { createAction, props } from '@ngrx/store'
3
3
  import {
4
4
  CatalogRecord,
5
5
  DatasetFeatureCatalog,
6
+ LinkedRecord,
6
7
  UserFeedback,
7
8
  } from '../../../../../../libs/common/domain/src/lib/model/record'
8
9
 
@@ -54,14 +55,9 @@ export const setRelated = createAction(
54
55
  props<{ related: CatalogRecord[] }>()
55
56
  )
56
57
 
57
- export const setSources = createAction(
58
- '[Metadata view] Set sources',
59
- props<{ sources: CatalogRecord[] }>()
60
- )
61
-
62
- export const setSourceOf = createAction(
63
- '[Metadata view] Set has sources',
64
- props<{ sourceOf: CatalogRecord[] }>()
58
+ export const setLinkedRecords = createAction(
59
+ '[Metadata view] Set associated records',
60
+ props<{ linkedRecords: LinkedRecord[] }>()
65
61
  )
66
62
 
67
63
  /*
@@ -70,25 +70,16 @@ export class MdViewEffects {
70
70
  )
71
71
  )
72
72
 
73
- loadSources$ = createEffect(() =>
73
+ loadLinkedRecords$ = createEffect(() =>
74
74
  this.actions$.pipe(
75
75
  ofType(MdViewActions.loadFullMetadataSuccess),
76
- switchMap(({ full }) => this.recordsRepository.getSources(full)),
77
- map((sources) => {
78
- return MdViewActions.setSources({ sources })
76
+ switchMap(({ full }) => this.recordsRepository.getLinkedRecords(full)),
77
+ map((linkedRecords) => {
78
+ return MdViewActions.setLinkedRecords({ linkedRecords })
79
79
  }),
80
- catchError(() => of(MdViewActions.setSources({ sources: null })))
81
- )
82
- )
83
-
84
- loadSourceOf$ = createEffect(() =>
85
- this.actions$.pipe(
86
- ofType(MdViewActions.loadFullMetadataSuccess),
87
- switchMap(({ full }) => this.recordsRepository.getSourceOf(full)),
88
- map((sourceOf) => {
89
- return MdViewActions.setSourceOf({ sourceOf })
90
- }),
91
- catchError(() => of(MdViewActions.setSourceOf({ sourceOf: null })))
80
+ catchError(() =>
81
+ of(MdViewActions.setLinkedRecords({ linkedRecords: null }))
82
+ )
92
83
  )
93
84
  )
94
85
 
@@ -74,9 +74,7 @@ export class MdViewFacade {
74
74
 
75
75
  related$ = this.store.pipe(select(MdViewSelectors.getRelated))
76
76
 
77
- sources$ = this.store.pipe(select(MdViewSelectors.getSources))
78
-
79
- sourceOf$ = this.store.pipe(select(MdViewSelectors.getSourceOf))
77
+ linkedRecords$ = this.store.pipe(select(MdViewSelectors.getLinkedRecords))
80
78
 
81
79
  chartConfig$ = this.store.pipe(select(MdViewSelectors.getChartConfig))
82
80
 
@@ -4,6 +4,7 @@ import { DatavizChartConfigModel } from '../../../../../../libs/common/domain/sr
4
4
  import {
5
5
  CatalogRecord,
6
6
  DatasetFeatureCatalog,
7
+ LinkedRecord,
7
8
  UserFeedback,
8
9
  } from '../../../../../../libs/common/domain/src/lib/model/record'
9
10
 
@@ -14,8 +15,7 @@ export interface MetadataViewState {
14
15
  error: { notFound?: boolean; otherError?: string } | null
15
16
  metadata?: Partial<CatalogRecord>
16
17
  related?: CatalogRecord[]
17
- sources?: CatalogRecord[]
18
- sourceOf?: CatalogRecord[]
18
+ linkedRecords?: LinkedRecord[]
19
19
  userFeedbacks?: UserFeedback[]
20
20
  allUserFeedbacksLoading: boolean
21
21
  addUserFeedbackLoading: boolean
@@ -77,14 +77,9 @@ const metadataViewReducer = createReducer(
77
77
  related,
78
78
  })),
79
79
 
80
- on(MetadataViewActions.setSources, (state, { sources }) => ({
80
+ on(MetadataViewActions.setLinkedRecords, (state, { linkedRecords }) => ({
81
81
  ...state,
82
- sources,
83
- })),
84
-
85
- on(MetadataViewActions.setSourceOf, (state, { sourceOf }) => ({
86
- ...state,
87
- sourceOf,
82
+ linkedRecords,
88
83
  })),
89
84
 
90
85
  /*
@@ -41,14 +41,9 @@ export const getRelated = createSelector(
41
41
  (state: MetadataViewState) => state.related
42
42
  )
43
43
 
44
- export const getSources = createSelector(
44
+ export const getLinkedRecords = createSelector(
45
45
  getMdViewState,
46
- (state: MetadataViewState) => state.sources
47
- )
48
-
49
- export const getSourceOf = createSelector(
50
- getMdViewState,
51
- (state: MetadataViewState) => state.sourceOf
46
+ (state: MetadataViewState) => state.linkedRecords
52
47
  )
53
48
  /*
54
49
  Metadata selectors
@@ -6,33 +6,32 @@
6
6
  matSort
7
7
  (matSortChange)="setSort($event)"
8
8
  [matSortDisableClear]="true"
9
- *ngrxLet="properties$ as properties"
10
9
  >
11
- @for (attr of _featureAttributes; track attr) {
12
- <ng-container [matColumnDef]="attr.value">
10
+ @for (column of columns; track column) {
11
+ <ng-container [matColumnDef]="column.name">
13
12
  <th
14
13
  mat-header-cell
15
14
  *matHeaderCellDef
16
15
  mat-sort-header
17
16
  class="text-sm text-black bg-white"
18
17
  >
19
- {{ attr.label }}
18
+ {{ column.label }}
20
19
  </th>
21
20
  <td
22
21
  mat-cell
23
22
  *matCellDef="let element"
24
23
  class="whitespace-nowrap pr-1 truncate"
25
24
  >
26
- {{ element[attr.value] }}
25
+ {{ element[column.name] }}
27
26
  </td>
28
27
  </ng-container>
29
28
  }
30
29
 
31
- <tr mat-header-row *matHeaderRowDef="properties; sticky: true"></tr>
30
+ <tr mat-header-row *matHeaderRowDef="columnNames; sticky: true"></tr>
32
31
  <tr
33
32
  [id]="getRowEltId(row.id)"
34
33
  mat-row
35
- *matRowDef="let row; columns: properties"
34
+ *matRowDef="let row; columns: columnNames"
36
35
  (click)="selected.emit(row)"
37
36
  [class.active]="
38
37
  activeId !== undefined && activeId !== null && row.id === activeId
@@ -6,14 +6,14 @@ import {
6
6
  Component,
7
7
  ElementRef,
8
8
  EventEmitter,
9
+ inject,
9
10
  Input,
10
11
  OnChanges,
11
12
  OnInit,
12
13
  Output,
13
14
  ViewChild,
14
- inject,
15
15
  } from '@angular/core'
16
- import { MatSort, MatSortModule } from '@angular/material/sort'
16
+ import { MatSort, MatSortModule, Sort } from '@angular/material/sort'
17
17
  import { MatTableModule } from '@angular/material/table'
18
18
  import {
19
19
  TranslateDirective,
@@ -29,12 +29,11 @@ import {
29
29
  } from '@angular/material/paginator'
30
30
  import { CustomMatPaginatorIntl } from './custom.mat.paginator.intl'
31
31
  import { CommonModule } from '@angular/common'
32
- import { BehaviorSubject, filter, firstValueFrom } from 'rxjs'
32
+ import { BehaviorSubject } from 'rxjs'
33
33
  import {
34
34
  LoadingMaskComponent,
35
35
  PopupAlertComponent,
36
36
  } from '../../../../../../libs/ui/widgets/src'
37
- import { LetDirective } from '@ngrx/component'
38
37
 
39
38
  const rowIdPrefix = 'table-item-'
40
39
 
@@ -46,6 +45,11 @@ export interface TableItemModel {
46
45
  [key: string]: TableItemType
47
46
  }
48
47
 
48
+ interface TableColumn {
49
+ name: string
50
+ label: string
51
+ }
52
+
49
53
  @Component({
50
54
  standalone: true,
51
55
  imports: [
@@ -56,7 +60,6 @@ export interface TableItemModel {
56
60
  CommonModule,
57
61
  LoadingMaskComponent,
58
62
  PopupAlertComponent,
59
- LetDirective,
60
63
  TranslatePipe,
61
64
  TranslateDirective,
62
65
  ],
@@ -71,15 +74,21 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
71
74
  private cdr = inject(ChangeDetectorRef)
72
75
  private translateService = inject(TranslateService)
73
76
 
74
- _featureAttributes = []
77
+ columnsFromFeatureCatalog: TableColumn[] = null
75
78
  @Input() set featureAttributes(value: { value: string; label: string }[]) {
76
- this._featureAttributes = value
77
- this.properties$.next(value.map((attr) => attr.value))
79
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
80
+ name: attrs.value,
81
+ label: attrs.label,
82
+ }))
78
83
  }
84
+ columnsFromDataset: TableColumn[] = []
79
85
  @Input() set dataset(value: BaseReader) {
80
86
  this.dataset_ = value
81
87
  this.dataset_.load()
82
- this.dataset_.info.then((info) => (this.count = info.itemsCount))
88
+ this.dataset_.info.then((info) => {
89
+ this.count = info.itemsCount
90
+ this.cdr.detectChanges()
91
+ })
83
92
  }
84
93
  @Input() activeId: TableItemId
85
94
  @Output() selected = new EventEmitter<any>()
@@ -88,13 +97,19 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
88
97
  @ViewChild(MatPaginator) paginator: MatPaginator
89
98
 
90
99
  dataset_: BaseReader
91
- properties$ = new BehaviorSubject<string[]>(null)
92
100
  dataSource: DataTableDataSource
93
101
  headerHeight: number
94
102
  count: number
95
103
  loading$ = new BehaviorSubject<boolean>(false)
96
104
  error = null
97
105
 
106
+ get columns() {
107
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset
108
+ }
109
+ get columnNames() {
110
+ return this.columns.map((c) => c.name)
111
+ }
112
+
98
113
  ngOnInit() {
99
114
  this.dataSource = new DataTableDataSource()
100
115
  }
@@ -110,7 +125,7 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
110
125
  this.setPagination()
111
126
  }
112
127
 
113
- setSort(sort: MatSort) {
128
+ setSort(sort: Sort) {
114
129
  if (!this.dataset_) return
115
130
  if (!sort.active) {
116
131
  this.dataset_.orderBy()
@@ -132,15 +147,12 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
132
147
 
133
148
  async readData() {
134
149
  this.loading$.next(true)
135
- // wait for properties to be read
136
- const properties = await firstValueFrom(
137
- this.properties$.pipe(filter((p) => !!p))
138
- )
139
- const propsWithoutGeom = properties.filter(
140
- (p) => !p.toLowerCase().startsWith('geom')
141
- )
142
- this.dataset_.select(...propsWithoutGeom)
143
150
  try {
151
+ // wait for properties to be read
152
+ if (!this.columnsFromFeatureCatalog) {
153
+ this.columnsFromDataset = await this.dataset_.properties
154
+ }
155
+ this.dataset_.select(...this.columnNames)
144
156
  await this.dataSource.showData(this.dataset_.read())
145
157
  this.error = null
146
158
  } catch (error) {
@@ -25,8 +25,8 @@ export const tableItemsFixture = {
25
25
  geometry: null,
26
26
  properties: {
27
27
  id: '0003',
28
- firstName: 'Claude',
29
- lastName: 'François',
28
+ firstName: 'Lady',
29
+ lastName: 'Gaga',
30
30
  },
31
31
  },
32
32
  ] as DataItem[],
@@ -7,20 +7,19 @@ import { DataItem, DatasetHeaders, FetchError, SupportedType } from './model'
7
7
  import { inferDatasetType } from './utils'
8
8
  import { BaseReader } from './readers/base'
9
9
  import { GmlReader } from './readers/gml'
10
- import { WfsVersion } from '@camptocamp/ogc-client'
11
10
  import { WfsReader } from './readers/wfs'
12
11
 
12
+ interface OpenDatasetOptions {
13
+ typeHint?: SupportedType
14
+ wfsFeatureType?: string
15
+ enableCache?: boolean // by default cache will be used if this is not set to false
16
+ }
17
+
13
18
  export async function openDataset(
14
19
  url: string,
15
- typeHint?: SupportedType,
16
- options?: {
17
- namespace?: string
18
- wfsVersion?: WfsVersion
19
- wfsFeatureType?: string
20
- },
21
- cacheActive?: boolean
20
+ options?: OpenDatasetOptions
22
21
  ): Promise<BaseReader> {
23
- const fileType = await inferDatasetType(url, typeHint)
22
+ const fileType = await inferDatasetType(url, options?.typeHint)
24
23
  let reader:
25
24
  | CsvReader
26
25
  | JsonReader
@@ -43,19 +42,19 @@ export async function openDataset(
43
42
  reader = new ExcelReader(url)
44
43
  break
45
44
  case 'gml':
46
- reader = new GmlReader(url, options.namespace, options.wfsVersion)
45
+ reader = new GmlReader(url)
47
46
  break
48
47
  case 'wfs':
49
- reader = await WfsReader.createReader(url, options.wfsFeatureType)
48
+ reader = new WfsReader(url, options?.wfsFeatureType)
50
49
  break
51
50
  }
52
- reader.setCacheActive(cacheActive)
51
+ reader.enableCache(options?.enableCache ?? true)
53
52
  reader.load()
54
53
  return reader
55
- } catch (e: any) {
56
- //WfsReader may already raise a FetchError
54
+ } catch (e: unknown) {
55
+ // WfsReader may already raise a FetchError
57
56
  if (e instanceof FetchError) throw e
58
- else throw FetchError.parsingFailed(e.message)
57
+ else throw FetchError.parsingFailed((e as Error).message)
59
58
  }
60
59
  }
61
60
 
@@ -63,22 +62,20 @@ export async function openDataset(
63
62
  * This fetches the full dataset at the given URL and parses it according to its mime type.
64
63
  * All items in the dataset are converted to GeoJSON features, even if they do not bear any spatial geometry.
65
64
  * File type can be either inferred (from the HTTP headers or the URL), or hinted using the 2nd argument
66
- * File type is determined liked so:
65
+ * File type is determined like so:
67
66
  * 1. if a type hint is given, use it
68
67
  * 2. otherwise, look for a Content-Type header in the response with a supported mime type
69
68
  * 3. if no valid mime type was found, look for an explicit file extension in the url (.csv, .geojson etc.)
70
69
  */
71
70
  export async function readDataset(
72
71
  url: string,
73
- typeHint?: SupportedType,
74
- options?: any,
75
- cacheActive = true
72
+ options?: OpenDatasetOptions
76
73
  ): Promise<DataItem[]> {
77
- const reader = await openDataset(url, typeHint, options, cacheActive)
74
+ const reader = await openDataset(url, options)
78
75
  try {
79
76
  return await reader.read()
80
- } catch (e: any) {
81
- throw FetchError.parsingFailed(e.message)
77
+ } catch (e: unknown) {
78
+ throw FetchError.parsingFailed((e as Error).message)
82
79
  }
83
80
  }
84
81