• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geonetwork / geonetwork-ui / 12417033329

19 Dec 2024 04:44PM UTC coverage: 84.621% (-1.2%) from 85.834%
12417033329

Pull #1069

github

web-flow
Merge 692832e0a into 23c6e8257
Pull Request #1069: [Datahub] Added dynamic legend generation based on map context

3401 of 4499 branches covered (75.59%)

Branch coverage included in aggregate %.

19 of 21 new or added lines in 3 files covered. (90.48%)

8 existing lines in 2 files now uncovered.

9640 of 10912 relevant lines covered (88.34%)

283.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.31
/libs/feature/record/src/lib/map-view/map-view.component.ts
1
import {
1✔
2
  AfterViewInit,
3
  ChangeDetectionStrategy,
4
  ChangeDetectorRef,
5
  Component,
6
  ViewChild,
7
} from '@angular/core'
8
import { MapUtilsService } from '@geonetwork-ui/feature/map'
1✔
9
import { getLinkLabel } from '@geonetwork-ui/util/shared'
1✔
10
import {
1✔
11
  BehaviorSubject,
12
  combineLatest,
13
  from,
14
  Observable,
15
  of,
16
  startWith,
17
  throwError,
18
  withLatestFrom,
19
} from 'rxjs'
20
import {
1✔
21
  catchError,
22
  distinctUntilChanged,
23
  finalize,
24
  map,
25
  shareReplay,
26
  switchMap,
27
  tap,
28
} from 'rxjs/operators'
29
import { MdViewFacade } from '../state/mdview.facade'
1✔
30
import { DataService } from '@geonetwork-ui/feature/dataviz'
1✔
31
import { DatasetOnlineResource } from '@geonetwork-ui/common/domain/model/record'
32
import {
1✔
33
  createViewFromLayer,
34
  MapContext,
35
  MapContextLayer,
36
} from '@geospatial-sdk/core'
37
import {
1✔
38
  FeatureDetailComponent,
39
  MapContainerComponent,
40
  prioritizePageScroll,
41
  MapLegendComponent,
42
} from '@geonetwork-ui/ui/map'
43
import { Feature } from 'geojson'
44
import { NgIconComponent, provideIcons } from '@ng-icons/core'
1✔
45
import { matClose } from '@ng-icons/material-icons/baseline'
1✔
46
import { CommonModule } from '@angular/common'
1✔
47
import {
1✔
48
  ButtonComponent,
49
  DropdownSelectorComponent,
50
} from '@geonetwork-ui/ui/inputs'
51
import { TranslateModule } from '@ngx-translate/core'
1✔
52
import { ExternalViewerButtonComponent } from '../external-viewer-button/external-viewer-button.component'
1✔
53
import {
1✔
54
  LoadingMaskComponent,
55
  PopupAlertComponent,
56
} from '@geonetwork-ui/ui/widgets'
57

58
@Component({
59
  selector: 'gn-ui-map-view',
60
  templateUrl: './map-view.component.html',
61
  styleUrls: ['./map-view.component.css'],
62
  changeDetection: ChangeDetectionStrategy.OnPush,
63
  standalone: true,
64
  imports: [
65
    CommonModule,
66
    DropdownSelectorComponent,
67
    MapContainerComponent,
68
    FeatureDetailComponent,
69
    PopupAlertComponent,
70
    TranslateModule,
71
    LoadingMaskComponent,
72
    NgIconComponent,
73
    ExternalViewerButtonComponent,
74
    ButtonComponent,
75
    MapLegendComponent,
76
  ],
77
  viewProviders: [provideIcons({ matClose })],
78
})
79
export class MapViewComponent implements AfterViewInit {
1✔
80
  @ViewChild('mapContainer') mapContainer: MapContainerComponent
81

82
  selection: Feature
83
  showLegend = true
38✔
84
  legendExists = false
38✔
85

86
  toggleLegend() {
NEW
87
    this.showLegend = !this.showLegend
×
88
  }
89

90
  onLegendStatusChange(status: boolean) {
91
    this.legendExists = status
1✔
92
    if (!status) {
1!
NEW
93
      this.showLegend = false
×
94
    }
95
  }
96

97
  compatibleMapLinks$ = combineLatest([
38✔
98
    this.mdViewFacade.mapApiLinks$,
99
    this.mdViewFacade.geoDataLinksWithGeometry$,
100
  ]).pipe(
101
    map(([mapApiLinks, geoDataLinksWithGeometry]) => {
102
      return [...mapApiLinks, ...geoDataLinksWithGeometry]
108✔
103
    })
104
  )
105

106
  dropdownChoices$ = this.compatibleMapLinks$.pipe(
38✔
107
    map((links) =>
108
      links.length
36✔
109
        ? links.map((link, index) => ({
52✔
110
            label: getLinkLabel(link),
111
            value: index,
112
          }))
113
        : [{ label: 'No preview layer', value: 0 }]
114
    )
115
  )
116
  selectedLinkIndex$ = new BehaviorSubject(0)
38✔
117

118
  loading = false
38✔
119
  error = null
38✔
120

121
  selectedLink$ = combineLatest([
38✔
122
    this.compatibleMapLinks$,
123
    this.selectedLinkIndex$.pipe(distinctUntilChanged()),
124
  ]).pipe(map(([links, index]) => links[index]))
90✔
125

126
  currentLayers$ = this.selectedLink$.pipe(
38✔
127
    switchMap((link) => {
128
      if (!link) {
45✔
129
        return of([])
4✔
130
      }
131
      this.loading = true
41✔
132
      this.error = null
41✔
133
      return this.getLayerFromLink(link).pipe(
41✔
134
        map((layer) => [layer]),
35✔
135
        catchError((e) => {
136
          this.error = e.message
1✔
137
          console.warn(e.stack || e.message)
1!
138
          return of([])
1✔
139
        }),
140
        finalize(() => (this.loading = false))
39✔
141
      )
142
    })
143
  )
144

145
  mapContext$: Observable<MapContext> = this.currentLayers$.pipe(
38✔
146
    switchMap((layers) =>
147
      from(createViewFromLayer(layers[0])).pipe(
40✔
148
        catchError(() => of(null)), // could not zoom on the layer: use the record extent
2✔
149
        map((view) => ({
27✔
150
          layers,
151
          view,
152
        })),
153
        tap(() => {
154
          this.resetSelection()
27✔
155
        })
156
      )
157
    ),
158
    startWith({
159
      layers: [],
160
      view: null,
161
    }),
162
    withLatestFrom(this.mdViewFacade.metadata$),
163
    map(([context, metadata]) => {
164
      if (context.view) return context
65✔
165
      const extent = this.mapUtils.getRecordExtent(metadata)
62✔
166
      const view = extent ? { extent } : null
62✔
167
      return {
62✔
168
        ...context,
169
        view,
170
      }
171
    }),
172
    shareReplay(1)
173
  )
174

175
  constructor(
176
    private mdViewFacade: MdViewFacade,
38!
177
    private mapUtils: MapUtilsService,
38!
178
    private dataService: DataService,
38!
179
    private changeRef: ChangeDetectorRef
38✔
180
  ) {}
181

182
  async ngAfterViewInit() {
183
    const map = await this.mapContainer.openlayersMap
38✔
184
    prioritizePageScroll(map.getInteractions())
38✔
185
  }
186

187
  onMapFeatureSelect(features: Feature[]): void {
188
    this.resetSelection()
3✔
189
    this.selection = features?.length > 0 && features[0]
3✔
190
    if (this.selection) {
3✔
191
      // FIXME: restore styling of selected feature
192
      // this.selection.setStyle(this.selectionStyle)
193
    }
194
    this.changeRef.detectChanges()
3✔
195
  }
196

197
  resetSelection(): void {
198
    if (this.selection) {
31✔
199
      // FIXME: restore styling of selected feature
200
      // this.selection.setStyle(null)
201
    }
202
    this.selection = null
31✔
203
  }
204

205
  getLayerFromLink(link: DatasetOnlineResource): Observable<MapContextLayer> {
206
    if (link.type === 'service' && link.accessServiceProtocol === 'wms') {
41✔
207
      return of({
28✔
208
        url: link.url.toString(),
209
        type: 'wms',
210
        name: link.name,
211
      })
212
    } else if (
13✔
213
      link.type === 'service' &&
19✔
214
      link.accessServiceProtocol === 'wmts'
215
    ) {
216
      return of({
1✔
217
        url: link.url.toString(),
218
        type: 'wmts',
219
        name: link.name,
220
      })
221
    } else if (
12✔
222
      (link.type === 'service' &&
29✔
223
        (link.accessServiceProtocol === 'wfs' ||
224
          link.accessServiceProtocol === 'esriRest' ||
225
          link.accessServiceProtocol === 'ogcFeatures')) ||
226
      link.type === 'download'
227
    ) {
228
      return this.dataService.readAsGeoJson(link).pipe(
12✔
229
        map((data) => ({
6✔
230
          type: 'geojson',
231
          data,
232
        }))
233
      )
234
    }
235
    return throwError(() => 'protocol not supported')
×
236
  }
237

238
  selectLinkToDisplay(link: number) {
239
    this.selectedLinkIndex$.next(link)
9✔
240
  }
241
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc