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

geonetwork / geonetwork-ui / 13990775729

21 Mar 2025 11:30AM UTC coverage: 84.854% (-0.03%) from 84.886%
13990775729

Pull #1150

github

web-flow
Merge eab415899 into b87edcffd
Pull Request #1150: Datahub: Display restricted access info for dataset previews

3403 of 4458 branches covered (76.33%)

Branch coverage included in aggregate %.

38 of 48 new or added lines in 8 files covered. (79.17%)

1 existing line in 1 file now uncovered.

9701 of 10985 relevant lines covered (88.31%)

379.79 hits per line

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

61.11
/libs/ui/map/src/lib/components/map-container/map-container.component.ts
1
import {
1✔
2
  AfterViewInit,
3
  ChangeDetectionStrategy,
4
  Component,
5
  ElementRef,
6
  EventEmitter,
7
  Inject,
8
  Input,
9
  OnChanges,
10
  Output,
11
  SimpleChanges,
12
  ViewChild,
13
} from '@angular/core'
14
import { fromEvent, merge, Observable, of, timer } from 'rxjs'
1✔
15
import { delay, map, startWith, switchMap } from 'rxjs/operators'
1✔
16
import { CommonModule } from '@angular/common'
1✔
17
import { TranslateModule } from '@ngx-translate/core'
1✔
18
import {
1✔
19
  computeMapContextDiff,
20
  Extent,
21
  FeaturesClickEvent,
22
  FeaturesClickEventType,
23
  FeaturesHoverEvent,
24
  FeaturesHoverEventType,
25
  MapClickEvent,
26
  MapClickEventType,
27
  MapContext,
28
  MapContextLayer,
29
  MapContextLayerXyz,
30
  MapContextView,
31
  SourceLoadErrorEvent,
32
  SourceLoadErrorType,
33
} from '@geospatial-sdk/core'
34
import {
1✔
35
  applyContextDiffToMap,
36
  createMapFromContext,
37
  listen,
38
} from '@geospatial-sdk/openlayers'
39
import type OlMap from 'ol/Map'
40
import type { Feature } from 'geojson'
41
import {
1✔
42
  BASEMAP_LAYERS,
43
  DO_NOT_USE_DEFAULT_BASEMAP,
44
  MAP_VIEW_CONSTRAINTS,
45
} from './map-settings.token'
46
import {
1✔
47
  NgIconComponent,
48
  provideIcons,
49
  provideNgIconsConfig,
50
} from '@ng-icons/core'
51
import { matSwipeOutline } from '@ng-icons/material-icons/outline'
1✔
52

53
const DEFAULT_BASEMAP_LAYER: MapContextLayerXyz = {
1✔
54
  type: 'xyz',
55
  url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
56
  attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
57
}
58

59
const DEFAULT_VIEW: MapContextView = {
1✔
60
  center: [0, 15],
61
  zoom: 2,
62
}
63

64
@Component({
65
  selector: 'gn-ui-map-container',
66
  templateUrl: './map-container.component.html',
67
  styleUrls: ['./map-container.component.css'],
68
  changeDetection: ChangeDetectionStrategy.OnPush,
69
  standalone: true,
70
  imports: [CommonModule, TranslateModule, NgIconComponent],
71
  providers: [
72
    provideIcons({ matSwipeOutline }),
73
    provideNgIconsConfig({
74
      size: '1.5em',
75
    }),
76
  ],
77
})
78
export class MapContainerComponent implements AfterViewInit, OnChanges {
1✔
79
  @Input() context: MapContext | null
80

81
  // these events only get registered on the map if they are used
82
  _featuresClick: EventEmitter<Feature[]>
83
  @Output() get featuresClick() {
84
    if (!this._featuresClick) {
×
85
      this.openlayersMap.then((olMap) => {
×
86
        listen(
×
87
          olMap,
88
          FeaturesClickEventType,
89
          ({ features }: FeaturesClickEvent) =>
90
            this._featuresClick.emit(features)
×
91
        )
92
      })
93
      this._featuresClick = new EventEmitter<Feature[]>()
×
94
    }
95
    return this._featuresClick
×
96
  }
97
  _featuresHover: EventEmitter<Feature[]>
98
  @Output() get featuresHover() {
99
    if (!this._featuresHover) {
×
100
      this.openlayersMap.then((olMap) => {
×
101
        listen(
×
102
          olMap,
103
          FeaturesHoverEventType,
104
          ({ features }: FeaturesHoverEvent) =>
105
            this._featuresHover.emit(features)
×
106
        )
107
      })
108
      this._featuresHover = new EventEmitter<Feature[]>()
×
109
    }
110
    return this._featuresHover
×
111
  }
112
  _mapClick: EventEmitter<[number, number]>
113
  @Output() get mapClick() {
114
    if (!this._mapClick) {
×
115
      this.openlayersMap.then((olMap) => {
×
116
        listen(olMap, MapClickEventType, ({ coordinate }: MapClickEvent) =>
×
117
          this._mapClick.emit(coordinate)
×
118
        )
119
      })
120
      this._mapClick = new EventEmitter<[number, number]>()
×
121
    }
122
    return this._mapClick
×
123
  }
124
  _sourceLoadError: EventEmitter<SourceLoadErrorEvent>
125
  @Output() get sourceLoadError() {
NEW
126
    if (!this._sourceLoadError) {
×
NEW
127
      this.openlayersMap.then((olMap) => {
×
NEW
128
        listen(olMap, SourceLoadErrorType, (error: SourceLoadErrorEvent) =>
×
NEW
129
          this._sourceLoadError.emit(error)
×
130
        )
131
      })
NEW
132
      this._sourceLoadError = new EventEmitter<SourceLoadErrorEvent>()
×
133
    }
NEW
134
    return this._sourceLoadError
×
135
  }
136

137
  @ViewChild('map') container: ElementRef
138
  displayMessage$: Observable<boolean>
139
  olMap: OlMap
140

141
  constructor(
142
    @Inject(DO_NOT_USE_DEFAULT_BASEMAP) private doNotUseDefaultBasemap: boolean,
12✔
143
    @Inject(BASEMAP_LAYERS) private basemapLayers: MapContextLayer[],
12✔
144
    @Inject(MAP_VIEW_CONSTRAINTS)
145
    private mapViewConstraints: {
12✔
146
      maxZoom?: number
147
      maxExtent?: Extent
148
    }
149
  ) {}
150

151
  private olMapResolver
152
  openlayersMap = new Promise<OlMap>((resolve) => {
12✔
153
    this.olMapResolver = resolve
12✔
154
  })
155

156
  async ngAfterViewInit() {
157
    this.olMap = await createMapFromContext(
18✔
158
      this.processContext(this.context),
159
      this.container.nativeElement
160
    )
161
    this.displayMessage$ = merge(
18✔
162
      fromEvent(this.olMap, 'mapmuted').pipe(map(() => true)),
2✔
163
      fromEvent(this.olMap, 'movestart').pipe(map(() => false)),
1✔
164
      fromEvent(this.olMap, 'singleclick').pipe(map(() => false))
1✔
165
    ).pipe(
166
      switchMap((muted) =>
167
        muted
4✔
168
          ? timer(2000).pipe(
169
              map(() => false),
1✔
170
              startWith(true),
171
              delay(400)
172
            )
173
          : of(false)
174
      )
175
    )
176
    this.olMapResolver(this.olMap)
18✔
177
  }
178

179
  async ngOnChanges(changes: SimpleChanges) {
180
    if ('context' in changes && !changes['context'].isFirstChange()) {
1✔
181
      const diff = computeMapContextDiff(
1✔
182
        this.processContext(changes['context'].currentValue),
183
        this.processContext(changes['context'].previousValue)
184
      )
185
      await applyContextDiffToMap(this.olMap, diff)
1✔
186
    }
187
  }
188

189
  // This will apply basemap layers & view constraints
190
  processContext(context: MapContext): MapContext {
191
    const processed = context
25✔
192
      ? { ...context, view: context.view ?? DEFAULT_VIEW }
10✔
193
      : { layers: [], view: DEFAULT_VIEW }
194
    if (this.basemapLayers.length) {
25✔
195
      processed.layers = [...this.basemapLayers, ...processed.layers]
1✔
196
    }
197
    if (!this.doNotUseDefaultBasemap) {
25✔
198
      processed.layers = [DEFAULT_BASEMAP_LAYER, ...processed.layers]
24✔
199
    }
200
    if (this.mapViewConstraints.maxZoom) {
25✔
201
      processed.view = {
1✔
202
        maxZoom: this.mapViewConstraints.maxZoom,
203
        ...processed.view,
204
      }
205
    }
206
    if (this.mapViewConstraints.maxExtent) {
25✔
207
      processed.view = {
1✔
208
        maxExtent: this.mapViewConstraints.maxExtent,
209
        ...processed.view,
210
      }
211
    }
212
    if (
25!
213
      processed.view &&
50!
214
      !('zoom' in processed.view) &&
215
      !('center' in processed.view)
216
    ) {
217
      if (this.mapViewConstraints.maxExtent) {
×
218
        processed.view = {
×
219
          extent: this.mapViewConstraints.maxExtent,
220
          ...processed.view,
221
        }
222
      } else {
223
        processed.view = { ...DEFAULT_VIEW, ...processed.view }
×
224
      }
225
    }
226
    return processed
25✔
227
  }
228
}
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