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

jumpinjackie / mapguide-react-layout / 26217425474

21 May 2026 09:24AM UTC coverage: 60.54% (-0.3%) from 60.795%
26217425474

Pull #1651

github

web-flow
Merge 1c5de0250 into 44e26a5b7
Pull Request #1651: Implement spy mode

3289 of 4035 branches covered (81.51%)

178 of 582 new or added lines in 16 files covered. (30.58%)

7 existing lines in 4 files now uncovered.

16218 of 26789 relevant lines covered (60.54%)

14.3 hits per line

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

37.89
/src/components/map-providers/mapguide.ts
1

2
import { Client } from '../../api/client';
1✔
3
import { SessionKeepAlive } from '../session-keep-alive';
1✔
4
import { Bounds, GenericEvent, ActiveMapTool, ImageFormat, RefreshMode, SelectionVariant, ClientKind, LayerTransparencySet, Size, BLANK_SIZE, IMapGuideViewerSupport, Dictionary, ILayerManager } from '../../api/common';
1✔
5
import { IQueryMapFeaturesOptions } from '../../api/request-builder';
6
import { QueryMapFeaturesResponse, FeatureSet } from '../../api/contracts/query';
7
import WKTFormat from "ol/format/WKT";
1✔
8
import Polygon, { fromExtent } from 'ol/geom/Polygon';
1✔
9
import Geometry from 'ol/geom/Geometry';
10
import { queryMapFeatures, setMouseCoordinates, setFeatureTooltipsEnabled, setComparisonMode } from '../../actions/map';
1✔
11
import View from 'ol/View';
1✔
12
import debounce from 'lodash.debounce';
1✔
13
import { layerTransparencyChanged, areViewsCloseToEqual } from '../../utils/viewer-state';
1✔
14
import { areArraysDifferent } from '../../utils/array';
1✔
15
import { MgLayerSetGroup } from "../../api/mg-layer-set-group";
1✔
16
import { FeatureQueryTooltip } from '../tooltips/feature';
1✔
17
import { RuntimeMap } from '../../api/contracts/runtime-map';
18
import { debug, warn } from '../../utils/logger';
1✔
19
import { getSiteVersion, canUseQueryMapFeaturesV4 } from '../../utils/site-version';
1✔
20
import { BLANK_GIF_DATA_URI } from '../../constants';
1✔
21
import { isSessionExpiredError } from '../../api/error';
1✔
22
import { BaseMapProviderContext, IMapProviderState, IViewerComponent, IMapProviderStateExtras, recursiveFindLayer } from './base';
1✔
23
import { assertIsDefined } from '../../utils/assert';
1✔
24
import { strIsNullOrEmpty, STR_EMPTY } from '../../utils/string';
1✔
25
import { ensureParameters } from '../../utils/url';
1✔
26
import { ActionType } from '../../constants/actions';
1✔
27
import { buildSelectionXml, getActiveSelectedFeatureXml } from '../../api/builders/deArrayify';
1✔
28
import { MapGuideMockMode } from '../mapguide-debug-context';
29
import { useViewerImageFormat, useConfiguredAgentUri, useConfiguredAgentKind, useViewerPointSelectionBuffer, useViewerFeatureTooltipsEnabled, useConfiguredManualFeatureTooltips, useViewerSelectionColor, useViewerSelectionImageFormat, useViewerActiveFeatureSelectionColor, useActiveMapSelectionSet, useConfiguredLoadIndicatorPositioning, useConfiguredLoadIndicatorColor, useViewerActiveTool, useActiveMapView, useViewerViewRotation, useViewerViewRotationEnabled, useActiveMapName, useViewerLocale, useActiveMapExternalBaseLayers, useConfiguredCancelDigitizationKey, useConfiguredUndoLastPointKey, useActiveMapLayers, useActiveMapInitialExternalLayers, useViewerIsStateless, useCustomAppSettings, useViewerBusyCount } from '../../containers/hooks';
1✔
30
import { useActiveMapState, useActiveMapSessionId, useActiveMapSelectableLayerNames, useActiveMapLayerTransparency, useActiveMapShowGroups, useActiveMapHideGroups, useActiveMapShowLayers, useActiveMapHideLayers, useActiveMapActiveSelectedFeature } from '../../containers/hooks-mapguide';
1✔
31
import { useReduxDispatch } from './context';
1✔
32
import { UTFGridTrackingTooltip } from '../tooltips/utfgrid';
1✔
33
import olTileLayer from "ol/layer/Tile";
1✔
34
import olUtfGridSource from "ol/source/UTFGrid";
1✔
35
import TileSource from 'ol/source/Tile';
36
import { useActiveMapSubjectLayer } from '../../containers/hooks-generic';
1✔
37
import { IGenericSubjectMapLayer } from '../../actions/defs';
38
import { isRuntimeMap } from '../../utils/type-guards';
1✔
39

40
export function isMapGuideProviderState(arg: any): arg is IMapGuideProviderState {
1✔
41
    return typeof (arg.agentUri) == 'string'
×
42
        && typeof (arg.agentKind) == 'string';
×
43
}
×
44

45
function useMapGuideViewerState() {
×
46
    const activeTool = useViewerActiveTool();
×
47
    const view = useActiveMapView();
×
48
    const viewRotation = useViewerViewRotation();
×
49
    const viewRotationEnabled = useViewerViewRotationEnabled();
×
50
    const mapName = useActiveMapName();
×
51
    const locale = useViewerLocale();
×
52
    const externalBaseLayers = useActiveMapExternalBaseLayers();
×
53
    const cancelDigitizationKey = useConfiguredCancelDigitizationKey();
×
54
    const undoLastPointKey = useConfiguredUndoLastPointKey();
×
55
    const layers = useActiveMapLayers();
×
56
    const initialExternalLayers = useActiveMapInitialExternalLayers();
×
57
    const dispatch = useReduxDispatch();
×
58
    const busyWorkers = useViewerBusyCount();
×
59
    const appSettings = useCustomAppSettings();
×
60
    // ============== Generic ============== //
61
    const subject = useActiveMapSubjectLayer();
×
62
    // ============== MapGuide-specific ================== //
63
    const stateless = useViewerIsStateless();
×
64
    const imageFormat = useViewerImageFormat();
×
65
    const agentUri = useConfiguredAgentUri();
×
66
    const agentKind = useConfiguredAgentKind();
×
67
    const map = useActiveMapState();
×
68
    const pointSelectionBuffer = useViewerPointSelectionBuffer();
×
69
    const featureTooltipsEnabled = useViewerFeatureTooltipsEnabled();
×
70
    const manualFeatureTooltips = useConfiguredManualFeatureTooltips();
×
71
    const sessionId = useActiveMapSessionId();
×
72
    const selectionColor = useViewerSelectionColor();
×
73
    const selectionImageFormat = useViewerSelectionImageFormat();
×
74
    const selectableLayerNames = useActiveMapSelectableLayerNames();
×
75
    const layerTransparency = useActiveMapLayerTransparency();
×
76
    const showGroups = useActiveMapShowGroups();
×
77
    const hideGroups = useActiveMapHideGroups();
×
78
    const showLayers = useActiveMapShowLayers();
×
79
    const hideLayers = useActiveMapHideLayers();
×
80
    const activeSelectedFeature = useActiveMapActiveSelectedFeature();
×
81
    const activeSelectedFeatureColor = useViewerActiveFeatureSelectionColor();
×
82
    const selection = useActiveMapSelectionSet();
×
83

84
    let bgColor: string | undefined;
×
85
    if (map) {
×
86
        bgColor = `#${map.BackgroundColor.substring(2)}`;
×
87
    }
×
88
    let activeSelectedFeatureXml;
×
89
    if (activeSelectedFeature && selection && selection.FeatureSet) {
×
90
        activeSelectedFeatureXml = getActiveSelectedFeatureXml(selection.FeatureSet, activeSelectedFeature);
×
91
    }
×
92

93
    let theMap = map ?? subject;
×
94
    let isReady = false;
×
95
    // Regardless of inferred readiness, the map/subject must be set
96
    if (!theMap) {
×
97
        isReady = false;
×
98
        theMap = {} as IGenericSubjectMapLayer;
×
99
    } else {
×
100
        if (subject && layerTransparency) {
×
101
            isReady = true;
×
102
        } else if (agentUri && theMap && layerTransparency) {
×
103
            if (!stateless) {
×
104
                if (isRuntimeMap(theMap) && sessionId) {
×
105
                    isReady = true;
×
106
                }
×
107
            } else {
×
108
                isReady = true;
×
109
            }
×
110
        }
×
111
    }
×
112

113
    const nextState: IMapGuideProviderState & IMapProviderStateExtras = {
×
114
        stateless,
×
115
        activeTool,
×
116
        busyWorkers,
×
117
        view,
×
118
        viewRotation,
×
119
        viewRotationEnabled,
×
120
        mapName,
×
121
        locale,
×
122
        externalBaseLayers,
×
123
        cancelDigitizationKey,
×
124
        undoLastPointKey,
×
125
        initialExternalLayers,
×
126
        appSettings: appSettings ?? {},
×
127
        // ========== IMapProviderStateExtras ========== //
128
        isReady,
×
129
        bgColor,
×
130
        layers,
×
131
        // =========== MapGuide-specific ============== //
132
        imageFormat,
×
133
        agentUri,
×
134
        agentKind,
×
135
        map: theMap,
×
136
        pointSelectionBuffer,
×
137
        featureTooltipsEnabled,
×
138
        manualFeatureTooltips,
×
139
        sessionId,
×
140
        selectionColor,
×
141
        selectionImageFormat,
×
142
        selectableLayerNames,
×
143
        layerTransparency,
×
144
        showGroups: showGroups ?? [],
×
145
        hideGroups: hideGroups ?? [],
×
146
        showLayers: showLayers ?? [],
×
147
        hideLayers: hideLayers ?? [],
×
148
        activeSelectedFeatureXml: activeSelectedFeatureXml ?? STR_EMPTY,
×
149
        activeSelectedFeatureColor,
×
150
        selection
×
151
    };
×
152
    return nextState;
×
153
}
×
154

155
export interface IMapGuideProviderState extends IMapProviderState {
156
    /**
157
     * @since 0.14
158
     */
159
    stateless: boolean;
160
    imageFormat: ImageFormat;
161
    /**
162
     * @since 0.14
163
     */
164
    appSettings: Dictionary<string>;
165
    agentUri: string | undefined;
166
    agentKind: ClientKind;
167
    map: RuntimeMap | IGenericSubjectMapLayer;
168
    pointSelectionBuffer: number;
169
    manualFeatureTooltips: boolean;
170
    featureTooltipsEnabled: boolean;
171
    sessionId: string | undefined;
172
    selectionColor: string;
173
    selectionImageFormat: ImageFormat;
174
    selectableLayerNames: string[];
175
    layerTransparency: LayerTransparencySet | undefined;
176
    showGroups: string[];
177
    hideGroups: string[];
178
    showLayers: string[];
179
    hideLayers: string[];
180
    activeSelectedFeatureXml: string;
181
    activeSelectedFeatureColor: string;
182
    selection: QueryMapFeaturesResponse | null;
183
}
184

185
export class MapGuideMapProviderContext extends BaseMapProviderContext<IMapGuideProviderState, MgLayerSetGroup> implements IMapGuideViewerSupport {
1✔
186
    /**
187
     * This is a throttled version of _refreshOnStateChange(). Call this on any
188
     * modifications to pendingStateChanges
189
     *
190
     * @private
191
     */
192
    private refreshOnStateChange: (mapName: string,
193
        showGroups: string[] | undefined,
194
        showLayers: string[] | undefined,
195
        hideGroups: string[] | undefined,
196
        hideLayers: string[] | undefined) => void;
197

198

199

200
    // ============= MapGuide-specific private state ============== //
201
    private _client: Client;
202
    private _keepAlive: SessionKeepAlive | undefined;
203
    private _featureTooltip: FeatureQueryTooltip | undefined;
204
    private _utfGridTooltip: UTFGridTrackingTooltip | undefined;
205
    private _wktFormat: WKTFormat;
206
    // ============================================================= //
207

208
    constructor(public mockMode: MapGuideMockMode | undefined = undefined) {
1✔
209
        super();
24✔
210
        this._wktFormat = new WKTFormat();
24✔
211
        this.refreshOnStateChange = debounce(this._refreshOnStateChange.bind(this), 500);
24✔
212
    }
24✔
213

214
    /**
215
     * @override
216
     */
217
    public getHookFunction(): () => IMapProviderState & IMapProviderStateExtras {
1✔
218
        return useMapGuideViewerState;
1✔
219
    }
1✔
220

221
    public setMockMode(mode: MapGuideMockMode | undefined): void {
1✔
222
        this.mockMode = mode;
2✔
223
    }
2✔
224

225
    /**
226
     * @override
227
     */
228
    public hideAllPopups() {
1✔
229
        super.hideAllPopups();
1✔
230
        this._featureTooltip?.setEnabled(false);
1✔
231
    }
1✔
232

233
    /**
234
     *
235
     * @override
236
     * @protected
237
     * @returns {(MapGuideMockMode | undefined)}
238
     *
239
     */
240
    protected getMockMode(): MapGuideMockMode | undefined { return this.mockMode; }
1✔
241

242
    protected getInitialProviderState(): Omit<IMapGuideProviderState, keyof IMapProviderState> {
1✔
243
        return {
25✔
244
            stateless: false,
25✔
245
            imageFormat: "PNG8",
25✔
246
            agentUri: undefined,
25✔
247
            agentKind: "mapagent",
25✔
248
            map: {} as IGenericSubjectMapLayer,
25✔
249
            pointSelectionBuffer: 2,
25✔
250
            featureTooltipsEnabled: true,
25✔
251
            manualFeatureTooltips: false,
25✔
252
            sessionId: undefined,
25✔
253
            selectionColor: "0000FF",
25✔
254
            selectionImageFormat: "PNG8",
25✔
255
            selectableLayerNames: [],
25✔
256
            layerTransparency: {},
25✔
257
            appSettings: {},
25✔
258
            showGroups: [],
25✔
259
            hideGroups: [],
25✔
260
            showLayers: [],
25✔
261
            hideLayers: [],
25✔
262
            activeSelectedFeatureXml: STR_EMPTY,
25✔
263
            activeSelectedFeatureColor: "FF0000",
25✔
264
            selection: null
25✔
265
        }
25✔
266
    }
25✔
267

268
    public getProviderName(): string { return "MapGuide"; }
1✔
269

270
    /**
271
     * @override
272
     * @returns {(IMapGuideViewerSupport | undefined)}
273
     *
274
     */
275
    mapguideSupport(): IMapGuideViewerSupport | undefined {
1✔
276
        return this;
1✔
277
    }
1✔
278

279
    //#region IMapGuideViewerSupport
280
    getSelection(): QueryMapFeaturesResponse | null {
1✔
281
        return this._state.selection;
1✔
282
    }
1✔
283
    getSelectionXml(selection: FeatureSet, layerIds?: string[] | undefined): string {
1✔
284
        return buildSelectionXml(selection, layerIds);
×
285
    }
×
286
    getSessionId(): string {
1✔
287
        return this._state.sessionId!;
1✔
288
    }
1✔
289
    setFeatureTooltipEnabled(enabled: boolean): void {
1✔
290
        this._comp?.onDispatch(setFeatureTooltipsEnabled(enabled));
1✔
291
    }
1✔
292
    //#endregion
293

294
    //#region IMapViewerContextCallback
295
    private onSessionExpired() {
1✔
296

297
    }
×
298
    protected onProviderMapClick(px: [number, number]): void {
1✔
299
        if (this._state.mapName && this._state.sessionId) {
×
NEW
300
            if (!this.isSpyComparisonActive() && this._state.manualFeatureTooltips && this._state.featureTooltipsEnabled) {
×
301
                this.queryFeatureTooltip(px);
×
302
            } else if (this._state.activeTool === ActiveMapTool.Select) {
×
303
                const ptBuffer = this._state.pointSelectionBuffer ?? 2;
×
304
                const box = this.getPointSelectionBox(px, ptBuffer);
×
305
                const geom = fromExtent(box);
×
306
                const options = this.buildDefaultQueryOptions(geom);
×
307
                options.maxfeatures = 1;
×
308
                this.sendSelectionQuery(options);
×
309
            }
×
310
        }
×
311
    }
×
312
    //#endregion
313

314
    //#region Map Context
315
    /**
316
     * @override
317
     * @protected
318
     * @param {GenericEvent} e
319
     * @returns
320
     */
321
    protected onMouseMove(e: GenericEvent) {
1✔
322
        if (this._comp) {
×
323
            this.handleMouseTooltipMouseMove(e);
×
324
            if (this._state.activeTool == ActiveMapTool.Select) {
×
325
                this.handleHighlightHover(e);
×
326
            }
×
327
            if (this._comp.isContextMenuOpen()) {
×
328
                return;
×
329
            }
×
NEW
330
            if (!this._state.manualFeatureTooltips && !this.isSpyComparisonActive()) {
×
331
                this.handleFeatureTooltipMouseMove(e);
×
332
            }
×
333
            if (this._utfGridTooltip) {
×
334
                this._utfGridTooltip.onMouseMove(e);
×
335
            }
×
336
            if (this._state.mapName) {
×
337
                this._comp.onDispatch?.(setMouseCoordinates(this._state.mapName, e.coordinate));
×
338
            }
×
339
        }
×
340
    }
×
341
    private queryFeatureTooltip(pixel: [number, number]) {
1✔
342
        if (!this._state.stateless && this._featureTooltip && this._featureTooltip.isEnabled()) {
×
343
            this._featureTooltip.raiseQueryFromPoint(pixel);
×
344
        }
×
345
    }
×
346
    private handleFeatureTooltipMouseMove(e: GenericEvent) {
1✔
347
        if (!this._state.stateless && this._featureTooltip && this._featureTooltip.isEnabled()) {
×
348
            this._featureTooltip.onMouseMove(e);
×
349
        }
×
350
    }
×
351
    private enableFeatureTooltips(enabled: boolean): void {
1✔
NEW
352
        this._featureTooltip?.setEnabled(enabled && !this.isSpyComparisonActive());
×
353
    }
×
354
    private refreshMapInternal(name: string, mode: RefreshMode = RefreshMode.LayersOnly | RefreshMode.SelectionOnly): void {
1✔
355
        const layerSet = this.getLayerSetGroup(name);
×
356
        layerSet?.refreshMap(mode);
×
357
    }
×
358
    private async showSelectedFeature(mapExtent: Bounds, size: Size, map: RuntimeMap, selectionColor: string, featureXml: string | undefined) {
1✔
359
        const sv = getSiteVersion(map);
×
360
        // This operation requires v4.0.0 QUERYMAPFEATURES, so bail if this ain't the right version
361
        if (!canUseQueryMapFeaturesV4(sv)) {
×
362
            return;
×
363
        }
×
364
        const layerSet = this.getLayerSetGroup(map.Name);
×
365
        try {
×
366
            if (featureXml) {
×
367
                const r = await this._client.queryMapFeatures_v4({
×
368
                    mapname: map.Name,
×
369
                    session: map.SessionId,
×
370
                    selectionformat: "PNG",
×
371
                    featurefilter: featureXml,
×
372
                    selectioncolor: selectionColor,
×
373
                    requestdata: 2, //Inline selection
×
374
                    layerattributefilter: 0,
×
375
                    persist: 0 //IMPORTANT: This is a transient selection
×
376
                });
×
377
                if (r.InlineSelectionImage) {
×
378
                    const dataUri = `data:${r.InlineSelectionImage.MimeType};base64,${r.InlineSelectionImage.Content}`;
×
379
                    layerSet?.showActiveSelectedFeature(mapExtent, size, dataUri);
×
380
                } else {
×
381
                    layerSet?.showActiveSelectedFeature(mapExtent, BLANK_SIZE, BLANK_GIF_DATA_URI);
×
382
                }
×
383
            } else {
×
384
                layerSet?.showActiveSelectedFeature(mapExtent, BLANK_SIZE, BLANK_GIF_DATA_URI);
×
385
            }
×
386
        } catch (e) {
×
387
            layerSet?.showActiveSelectedFeature(mapExtent, BLANK_SIZE, BLANK_GIF_DATA_URI);
×
388
        }
×
389
    }
×
390
    //#endregion
391

392
    /**
393
     * DO NOT CALL DIRECTLY, call this.refreshOnStateChange() instead, which is a throttled version
394
     * of this method
395
     * @private
396
     */
397
    private _refreshOnStateChange(mapName: string,
1✔
398
        showGroups: string[] | undefined,
×
399
        showLayers: string[] | undefined,
×
400
        hideGroups: string[] | undefined,
×
401
        hideLayers: string[] | undefined) {
×
402
        if (showGroups || showLayers || hideGroups || hideLayers) {
×
403
            //this.refreshOnStateChange(map, showGroups, showLayers, hideGroups, hideLayers);
404
            const layerSet = this.getLayerSetGroup(mapName);
×
405
            if (layerSet instanceof MgLayerSetGroup) {
×
406
                layerSet.setMapGuideMocking(this.getMockMode());
×
407
                layerSet.update(showGroups, showLayers, hideGroups, hideLayers);
×
408
            }
×
409
        }
×
410
    }
×
411
    /**
412
     * @override
413
     * @protected
414
     * @param {GenericEvent} e
415
     *
416
     */
417
    protected onImageError(e: GenericEvent) {
1✔
418
        this._keepAlive?.lastTry().catch(err => {
×
419
            if (isSessionExpiredError(err)) {
×
420
                this.onSessionExpired();
×
421
            }
×
422
        });
×
423
    }
×
424

425

426
    private getSelectableLayers(): string[] {
1✔
427
        return this._state.selectableLayerNames ?? [];
4!
428
    }
4✔
429
    private buildDefaultQueryOptions(geom: Geometry | string, reqQueryFeatures: number = 1 /* Attributes */): IQueryMapFeaturesOptions {
1✔
430
        assertIsDefined(this._state.sessionId);
3✔
431
        assertIsDefined(this._state.mapName);
3✔
432
        const names = this.getSelectableLayers();
3✔
433
        let wkt: string;
3✔
434
        if (typeof geom === 'string') {
3✔
435
            wkt = geom;
1✔
436
        } else {
3✔
437
            wkt = this._wktFormat.writeGeometry(geom);
2✔
438
        }
2✔
439
        return {
3✔
440
            mapname: this._state.mapName,
3✔
441
            session: this._state.sessionId,
3✔
442
            geometry: wkt,
3✔
443
            requestdata: reqQueryFeatures,
3✔
444
            layernames: names.length > 0 ? names.join(",") : undefined,
3✔
445
            persist: 1
3✔
446
        };
3✔
447
    }
3✔
448

449
    private ensureMapLayerSetGroup(mapName: string): void {
1✔
450
        if (this._layerSetGroups[mapName] || !this._reduxStore) {
2!
451
            return;
×
452
        }
×
453
        const appState = this._reduxStore.getState();
2✔
454
        const mapState = appState?.mapState?.[mapName];
2✔
455
        if (!mapState) {
2✔
456
            return;
1✔
457
        }
1✔
458
        const targetMap = mapState.generic?.subject ?? mapState.mapguide?.runtimeMap;
2!
459
        if (!targetMap) {
2!
460
            return;
×
461
        }
✔
462
        const targetState: IMapGuideProviderState = {
1✔
463
            ...this._state,
1✔
464
            mapName,
1✔
465
            map: targetMap,
1✔
466
            externalBaseLayers: mapState.externalBaseLayers ?? [],
2!
467
            initialExternalLayers: mapState.initialExternalLayers ?? [],
2!
468
            showGroups: [],
2✔
469
            hideGroups: [],
2✔
470
            showLayers: [],
2✔
471
            hideLayers: [],
2✔
472
            layerTransparency: {},
2✔
473
            selection: null,
2✔
474
            activeSelectedFeatureXml: ""
2✔
475
        };
2✔
476
        this.initLayerSet(targetState);
2✔
477
    }
2✔
478

479
    /**
480
     * @virtual
481
     * @protected
482
     * @param {Polygon} geom
483
     *
484
     */
485
    protected selectFeaturesByExtent(geom: Polygon) {
1✔
486
        if (!this._state.mapName || !this._comp || !this._state.sessionId || !this._state.selectionColor) {
×
487
            return;
×
488
        }
×
489
        this.sendSelectionQuery(this.buildDefaultQueryOptions(geom));
×
490
    }
×
491

492
    private onOpenTooltipLink = (url: string) => {
1✔
493
        let fixedUrl = url;
×
494
        if (this._state.mapName && this._state.sessionId) {
×
495
            fixedUrl = ensureParameters(url, this._state.mapName, this._state.sessionId, this._state.locale);
×
496
        }
×
497
        this._comp?.onDispatch({
×
498
            type: ActionType.TASK_INVOKE_URL,
×
499
            payload: {
×
500
                url: fixedUrl
×
501
            }
×
502
        });
×
503
    };
×
504

505
    /**
506
     * @override
507
     * @protected
508
     */
509
    protected initLayerSet(nextState: IMapGuideProviderState): MgLayerSetGroup {
1✔
510
        const { mapName } = nextState;
×
511
        assertIsDefined(mapName);
×
512
        assertIsDefined(this._state.map);
×
513
        const layerSet = new MgLayerSetGroup(nextState, {
×
514
            getImageLoaders: () => super.getImageSourceLoaders(mapName),
×
515
            getTileLoaders: () => super.getTileSourceLoaders(mapName),
×
516
            getBaseTileLoaders: () => super.getBaseTileSourceLoaders(mapName),
×
517
            getMockMode: () => this.getMockMode(),
×
518
            incrementBusyWorker: () => this.incrementBusyWorker(),
×
519
            decrementBusyWorker: () => this.decrementBusyWorker(),
×
520
            addImageLoading: () => this._comp?.addImageLoading(),
×
521
            addImageLoaded: () => this._comp?.addImageLoaded(),
×
522
            onImageError: (e) => this.onImageError(e),
×
523
            onSessionExpired: () => this.onSessionExpired(),
×
524
            getSelectableLayers: () => this.getSelectableLayers(),
×
525
            getClient: () => this._client,
×
526
            isContextMenuOpen: () => this._comp?.isContextMenuOpen() ?? false,
×
527
            getAgentUri: () => this._state.agentUri!,
×
528
            getAgentKind: () => this._state.agentKind,
×
529
            getMapName: () => this._state.mapName!,
×
530
            getSessionId: () => this._state.sessionId!,
×
531
            getLocale: () => this._state.locale,
×
532
            isFeatureTooltipEnabled: () => this.isFeatureTooltipEnabled(),
×
533
            getPointSelectionBox: (pt) => this.getPointSelectionBox(pt, this._state.pointSelectionBuffer),
×
534
            openTooltipLink: (url) => this.onOpenTooltipLink(url),
×
535
            addFeatureToHighlight: (feat, bAppend) => this.addFeatureToHighlight(feat, bAppend)
×
536
        });
×
537
        this._layerSetGroups[mapName] = layerSet;
×
538
        layerSet.update(nextState.showGroups, nextState.showLayers, nextState.hideGroups, nextState.hideLayers);
×
539
        return layerSet;
×
540
    }
×
541

542
    /**
543
     * Override activateMapSwipe to eagerly initialize the secondary map's layer set group
544
     * if it hasn't been visited yet. The Redux store reference (stored by MapContextProvider)
545
     * is used to read the secondary map's configuration from state.
546
     *
547
     * @override
548
     * @since 0.15
549
     */
550
    public override activateMapComparisonSwipe(secondaryMapName: string, position: number): boolean {
1✔
551
        this.ensureMapLayerSetGroup(secondaryMapName);
×
NEW
552
        return super.activateMapComparisonSwipe(secondaryMapName, position);
×
NEW
553
    }
×
554

555
    public override activateMapComparisonSpy(secondaryMapName: string, radius: number): boolean {
1✔
NEW
556
        this.ensureMapLayerSetGroup(secondaryMapName);
×
NEW
557
        const activated = super.activateMapComparisonSpy(secondaryMapName, radius);
×
NEW
558
        if (activated) {
×
NEW
559
            this.enableFeatureTooltips(false);
×
NEW
560
        }
×
NEW
561
        return activated;
×
NEW
562
    }
×
563

564
    public override deactivateMapComparison(): void {
1✔
NEW
565
        super.deactivateMapComparison();
×
NEW
566
        this.enableFeatureTooltips(this._state.featureTooltipsEnabled);
×
UNCOV
567
    }
×
568

569
    public override getLayerManager(mapName?: string): ILayerManager {
1✔
570
        if (mapName && mapName !== this._state.mapName) {
2✔
571
            this.ensureMapLayerSetGroup(mapName);
2✔
572
        }
2✔
573
        return super.getLayerManager(mapName);
2✔
574
    }
2✔
575

576
    /**
577
     * @override
578
     * @readonly
579
     *
580
     */
581
    public isMouseOverTooltip() { return this._featureTooltip?.isMouseOver == true || this._selectTooltip?.isMouseOver == true; }
1✔
582

583
    /**
584
     * @override
585
     */
586
    public detachFromComponent(): void {
1✔
587
        this._keepAlive?.dispose();
1✔
588
        this._keepAlive = undefined;
1✔
589
        this._featureTooltip?.dispose();
1✔
590
        this._featureTooltip = undefined;
1✔
591
        super.detachFromComponent();
1✔
592
    }
1✔
593

594
    /**
595
     * @override
596
     * @param {HTMLElement} el
597
     * @param {IViewerComponent} comp
598
     *
599
     */
600
    public attachToComponent(el: HTMLElement, comp: IViewerComponent): void {
1✔
601
        super.attachToComponent(el, comp);
×
602
        const bCheckSession = (this._state.map && isRuntimeMap(this._state.map)) ?? false;
×
603
        this._keepAlive = new SessionKeepAlive(() => this._state.sessionId!, this._client, this.onSessionExpired.bind(this), bCheckSession);
×
604

605
        const utfGridLayer = recursiveFindLayer(this._map!.getLayers(), oll => {
×
606
            if (oll instanceof olTileLayer) {
×
607
                const source = oll.getSource();
×
608
                if (source instanceof olUtfGridSource) {
×
609
                    return true;
×
610
                }
×
611
            }
×
612
            return false;
×
613
        });
×
614
        if (utfGridLayer) {
×
615
            const source = (utfGridLayer as olTileLayer<TileSource>).getSource() as olUtfGridSource;
×
616
            this._utfGridTooltip = new UTFGridTrackingTooltip(this._map!, source, this._comp?.isContextMenuOpen ?? (() => false));
×
617
        }
×
618

619
        const bEnable = (this._state.map && isRuntimeMap(this._state.map)) ?? false;
×
620
        if (bEnable) {
×
621
            this._featureTooltip = new FeatureQueryTooltip(this._map!, {
×
622
                incrementBusyWorker: () => this.incrementBusyWorker(),
×
623
                decrementBusyWorker: () => this.decrementBusyWorker(),
×
624
                onSessionExpired: () => this.onSessionExpired(),
×
625
                getAgentUri: () => this._state.agentUri!,
×
626
                getAgentKind: () => this._state.agentKind,
×
627
                getMapName: () => this._state.mapName!,
×
628
                getSessionId: () => this._state.sessionId!,
×
629
                getLocale: () => this._state.locale,
×
630
                getPointSelectionBox: (pt) => this.getPointSelectionBox(pt, this._state.pointSelectionBuffer),
×
631
                openTooltipLink: (url) => this.onOpenTooltipLink(url)
×
632
            });
×
NEW
633
            this._featureTooltip.setEnabled(this._state.featureTooltipsEnabled && !this.isSpyComparisonActive());
×
634
        }
×
635
    }
×
636
    /**
637
     * @override
638
     * @param {RefreshMode} [mode=RefreshMode.LayersOnly | RefreshMode.SelectionOnly]
639
     *
640
     */
641
    public refreshMap(mode: RefreshMode = RefreshMode.LayersOnly | RefreshMode.SelectionOnly): void {
1✔
642
        assertIsDefined(this._state.mapName);
×
643
        this.refreshMapInternal(this._state.mapName, mode);
×
644
    }
×
645

646
    /**
647
     * @override
648
     * @protected
649
     * @param {MgLayerSetGroup} layerSetGroup
650
     *
651
     */
652
    protected onBeforeAttachingLayerSetGroup(layerSetGroup: MgLayerSetGroup): void {
1✔
653
        layerSetGroup.setMapGuideMocking(this.getMockMode());
×
654
    }
×
655

656
    /**
657
     *
658
     * @virtual
659
     * @param {IMapGuideProviderState} nextState
660
     *
661
     */
662
    public setProviderState(nextState: IMapGuideProviderState): void {
1✔
663
        // If viewer not mounted yet, just accept the next state and bail
664
        if (!this._comp || !this._map) {
3✔
665
            if (nextState.agentUri) {
1✔
666
                this._client = new Client(nextState.agentUri, nextState.agentKind);
1✔
667
            }
1✔
668
            this._state = nextState;
1✔
669
            return;
1✔
670
        }
1✔
671
        //
672
        // React (no pun intended) to prop changes
673
        //
674
        if (nextState.imageFormat != this._state.imageFormat) {
3✔
675
            warn(`Unsupported change of props: imageFormat`);
1✔
676
        }
1✔
677
        if (nextState.agentUri && nextState.agentUri != this._state.agentUri) {
3✔
678
            warn(`Unsupported change of props: agentUri`);
1✔
679
            this._client = new Client(nextState.agentUri, nextState.agentKind);
1✔
680
        }
1✔
681
        if (nextState.agentUri && nextState.agentKind != this._state.agentKind) {
3✔
682
            warn(`Unsupported change of props: agentKind`);
1✔
683
            this._client = new Client(nextState.agentUri, nextState.agentKind);
1✔
684
        }
1✔
685
        let bChangedView = false;
2✔
686
        //map
687
        if (nextState.mapName != this._state.mapName && this._map && this._ovMap) {
3!
688
            // If swipe is currently active, deactivate it before switching maps.
689
            // The swipe activation adds secondary map layers directly to the OL map,
690
            // and if we don't remove them now, `attach` will throw a "Duplicate item"
691
            // error when it tries to add those same layers again for the new active map.
NEW
692
            const comparisonMode = this._reduxStore?.getState()?.config?.comparisonMode ?? "none";
×
NEW
693
            if (comparisonMode !== "none") {
×
NEW
694
                this.deactivateMapComparison();
×
NEW
695
                this._reduxStore?.dispatch(setComparisonMode("none"));
×
696
            }
×
697
            this.hideAllPopups();
×
698
            const oldLayerSet = this.getLayerSetGroup(this._state.mapName);
×
699
            const newLayerSet = this.ensureAndGetLayerSetGroup(nextState);
×
700

701
            //Clear any stray hover highlighted features as part of switch
702
            oldLayerSet?.clearHighlightedFeatures();
×
703
            newLayerSet.clearHighlightedFeatures();
×
704

705
            oldLayerSet?.detach(this._map, this._ovMap);
×
706
            newLayerSet.setMapGuideMocking(this.getMockMode());
×
707
            newLayerSet.attach(this._map, this._ovMap);
×
708
            //This would happen if we switch to a map we haven't visited yet
709
            if (!nextState.view) {
×
710
                newLayerSet.fitViewToExtent();
×
711
                bChangedView = true;
×
712
            } else {
×
713
                const layerSet = this.getLayerSetGroup(nextState.mapName);
×
714
                if (layerSet) {
×
715
                    this.applyView(layerSet, nextState.view);
×
716
                }
×
717
            }
×
718
        }
✔
719
        //selectionColor
720
        if (nextState.selectionColor && nextState.selectionColor != this._state.selectionColor) {
3!
721
            const layerSet = this.getLayerSetGroup(nextState.mapName);
×
722
            layerSet?.updateSelectionColor(nextState.selectionColor);
×
723
        }
✔
724
        //featureTooltipsEnabled
725
        if (nextState.featureTooltipsEnabled != this._state.featureTooltipsEnabled) {
3!
726
            this.enableFeatureTooltips(nextState.featureTooltipsEnabled);
×
727
        }
✔
728
        //externalBaseLayers
729
        if (nextState.externalBaseLayers != null &&
2✔
730
            nextState.externalBaseLayers.length > 0) {
3!
731
            const layerSet = this.getLayerSetGroup(nextState.mapName);
×
732
            layerSet?.updateExternalBaseLayers(nextState.externalBaseLayers);
×
733
        }
✔
734
        //Layer transparency
735
        if (nextState.layerTransparency && layerTransparencyChanged(nextState.layerTransparency, this._state.layerTransparency)) {
3!
736
            const layerSet = this.getLayerSetGroup(nextState.mapName);
×
737
            layerSet?.updateTransparency(nextState.layerTransparency);
×
738
        }
✔
739
        //Layer/Group visibility
740
        if (nextState.mapName && (areArraysDifferent(nextState.showGroups, this._state.showGroups) ||
2✔
741
            areArraysDifferent(nextState.hideGroups, this._state.hideGroups) ||
2✔
742
            areArraysDifferent(nextState.showLayers, this._state.showLayers) ||
2✔
743
            areArraysDifferent(nextState.hideLayers, this._state.hideLayers))) {
3!
744
            this.refreshOnStateChange(nextState.mapName, nextState.showGroups, nextState.showLayers, nextState.hideGroups, nextState.hideLayers);
×
745
        }
✔
746
        //view
747
        let bViewChanged = false;
2✔
748
        if (!areViewsCloseToEqual(nextState.view, this._state.view)) {
2✔
749
            const vw = nextState.view;
2✔
750
            if (vw != null && !bChangedView) {
2!
751
                const layerSet = this.ensureAndGetLayerSetGroup(nextState);
×
752
                this.applyView(layerSet, vw);
×
753
                bViewChanged = true;
×
754
            } else {
2✔
755
                debug(`Skipping zoomToView as next/current views are close enough or target view is null`);
2✔
756
            }
2✔
757
        }
2✔
758
        //overviewMapElement
759
        if (nextState.overviewMapElementSelector) {
3!
760
            this.updateOverviewMapElement(nextState.overviewMapElementSelector);
×
761
        }
✔
762
        //viewRotation
763
        if (this._state.viewRotation != nextState.viewRotation) {
3!
764
            this._map?.getView().setRotation(nextState.viewRotation);
×
765
        }
✔
766
        //viewRotationEnabled
767
        if (this._state.viewRotationEnabled != nextState.viewRotationEnabled) {
3!
768
            if (this._map) {
×
769
                const view = this._map.getView();
×
770
                const newView = new View({
×
771
                    enableRotation: nextState.viewRotationEnabled,
×
772
                    rotation: nextState.viewRotation,
×
773
                    center: view.getCenter(),
×
774
                    resolution: view.getResolution(),
×
775
                    resolutions: view.getResolutions(),
×
776
                    minResolution: view.getMinResolution(),
×
777
                    maxResolution: view.getMaxResolution(),
×
778
                    maxZoom: view.getMaxZoom(),
×
779
                    minZoom: view.getMinZoom(),
×
780
                    //constrainRotation: view.constrainRotation(),
781
                    projection: view.getProjection(),
×
782
                    zoom: view.getZoom()
×
783
                });
×
784
                this._map.setView(newView);
×
785
            }
×
786
        }
✔
787
        //activeSelectedFeatureXml
788
        const bDiffSelectionXml = this._state.activeSelectedFeatureXml != nextState.activeSelectedFeatureXml;
2✔
789
        const bRefreshActiveFeatureSelection = !strIsNullOrEmpty(nextState.activeSelectedFeatureXml) && bViewChanged;
3!
790
        if (bDiffSelectionXml || bRefreshActiveFeatureSelection) {
3!
791
            if (this._map && nextState.map) {
×
792
                const ms = this._map.getSize();
×
793
                if (ms && isRuntimeMap(nextState.map)) {
×
794
                    const nmap = nextState.map;
×
795
                    // We don't want to request for an updated feature selection while there are still rendering operations in progress
796
                    // otherwise we may request a feature selection for a map whose internal view has been changed by the in-progress rendering
797
                    // operation
798
                    const checkReady = () => {
×
799
                        if (this._busyWorkers == 0) {
×
800
                            //console.log("Ready to request updated feature selection");
801
                            const view = this._map?.getView();
×
802
                            if (!view) return;
×
803
                            const me: any = view.calculateExtent(ms);
×
804
                            const size = { w: ms[0], h: ms[1] };
×
805
                            this.showSelectedFeature(me, size, nmap, nextState.activeSelectedFeatureColor, nextState.activeSelectedFeatureXml);
×
806
                        } else {
×
807
                            //console.log("Still busy. Hold off on request for updated feature selection");
808
                            window.setTimeout(checkReady, 100);
×
809
                        }
×
810
                    };
×
811
                    checkReady();
×
812
                }
×
813
            }
×
814
        }
✔
815

816
        this._state = nextState;
2✔
817
    }
3✔
818

819
    public setSelectionXml(xml: string, queryOpts?: Partial<IQueryMapFeaturesOptions>, success?: (res: QueryMapFeaturesResponse) => void, failure?: (err: Error) => void): void {
1✔
820
        if (!this._state.mapName || !this._comp || !this._state.sessionId || !this._state.selectionColor) {
1!
821
            return;
1✔
822
        }
1!
823
        //NOTE: A quirk of QUERYMAPFEATURES is that when passing in selection XML (instead of geometry),
824
        //you must set the layerattributefilter to the full bit mask otherwise certain features in the
825
        //selection XML will not be rendered because they may not pass the layer attribute filter
826
        const reqQueryFeatures = 1; //Attributes
×
827
        this.incrementBusyWorker();
×
828
        const mapName = this._state.mapName;
×
829
        const qOrig = {
×
830
            mapname: mapName,
×
831
            session: this._state.sessionId,
×
832
            persist: 1,
×
833
            featurefilter: xml,
×
834
            selectioncolor: this._state.selectionColor,
×
835
            selectionformat: this._state.selectionImageFormat ?? "PNG8",
×
836
            maxfeatures: -1,
1✔
837
            requestdata: reqQueryFeatures
1✔
838
        };
1✔
839
        const queryOptions = { ...qOrig, ...queryOpts };
1✔
840
        const action = queryMapFeatures(mapName, {
1✔
841
            options: queryOptions,
1✔
842
            callback: res => {
1✔
843
                this.decrementBusyWorker();
×
844
                if (success) {
×
845
                    success(res);
×
846
                }
×
847
            },
×
848
            errBack: err => {
1✔
849
                this.decrementBusyWorker();
×
850
                if (failure) {
×
851
                    failure(err);
×
852
                }
×
853
            }
×
854
        });
1✔
855
        this._comp.onDispatch(action);
1✔
856
    }
1✔
857

858
    public clearSelection(): void {
1✔
859
        this.setSelectionXml("");
1✔
860
    }
1✔
861

862
    public selectByGeometry(geom: Geometry, selectionMethod?: SelectionVariant): void {
1✔
863
        const options = this.buildDefaultQueryOptions(geom);
1✔
864
        if (selectionMethod) {
1!
865
            options.selectionvariant = selectionMethod;
×
866
        }
×
867
        this.sendSelectionQuery(options);
1✔
868
    }
1✔
869
    public queryMapFeatures(options: IQueryMapFeaturesOptions, success?: (res: QueryMapFeaturesResponse) => void, failure?: (err: Error) => void): void {
1✔
870
        this.sendSelectionQuery(options, success, failure);
1✔
871
    }
1✔
872
    public isFeatureTooltipEnabled(): boolean {
1✔
873
        return this._featureTooltip?.isEnabled() == true;
2✔
874
    }
2✔
875

876
    // ================= MapGuide-specific =================== //
877

878

879

880
    private sendSelectionQuery(queryOpts?: IQueryMapFeaturesOptions, success?: (res: QueryMapFeaturesResponse) => void, failure?: (err: Error) => void) {
1✔
881
        if (!this._state.mapName || !this._comp || !this._state.sessionId || !this._state.selectionColor || (queryOpts != null && (queryOpts.layernames ?? []).length == 0)) {
2!
882
            return;
2✔
883
        }
2!
884
        this.incrementBusyWorker();
×
885
        const mapName = this._state.mapName;
×
886
        const qOrig: Partial<IQueryMapFeaturesOptions> = {
×
887
            mapname: mapName,
×
888
            session: this._state.sessionId,
×
889
            persist: 1,
×
890
            selectionvariant: "INTERSECTS",
×
891
            selectioncolor: this._state.selectionColor,
×
892
            selectionformat: this._state.selectionImageFormat ?? "PNG8",
×
893
            maxfeatures: -1
2✔
894
        };
2✔
895
        const queryOptions: IQueryMapFeaturesOptions = { ...qOrig, ...queryOpts } as IQueryMapFeaturesOptions;
2✔
896
        const action = queryMapFeatures(mapName, {
2✔
897
            options: queryOptions,
2✔
898
            callback: res => {
2✔
899
                this.decrementBusyWorker();
×
900
                if (success) {
×
901
                    success(res);
×
902
                }
×
903
            },
×
904
            errBack: err => {
2✔
905
                this.decrementBusyWorker();
×
906
                if (failure) {
×
907
                    failure(err);
×
908
                }
×
909
            },
×
910
            append: this._comp.isShiftKeyDown()
2✔
911
        });
2✔
912
        this._comp.onDispatch(action);
2✔
913
    }
2✔
914
}
1✔
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