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

jumpinjackie / mapguide-react-layout / 23089838058

14 Mar 2026 02:27PM UTC coverage: 39.095% (+1.3%) from 37.763%
23089838058

Pull #1605

github

web-flow
Merge 4461c6a28 into 0c1832fbe
Pull Request #1605: Fix Fusion API shim `setExtents` to accept all 3 calling conventions

1749 of 2261 branches covered (77.36%)

24 of 24 new or added lines in 1 file covered. (100.0%)

180 existing lines in 5 files now uncovered.

9620 of 24607 relevant lines covered (39.09%)

9.09 hits per line

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

0.0
/src/actions/init-mapguide.ts
1
import { ApplicationDefinition } from '../api/contracts/fusion';
1✔
2
import { MapGroup, MapLayer, RuntimeMap } from '../api/contracts/runtime-map';
3
import { Dictionary, IExternalBaseLayer, ReduxDispatch, ActiveMapTool, IMapView } from '../api/common';
×
4
import { MapInfo, IInitAppActionPayload, IRestoredSelectionSets, IGenericSubjectMapLayer, GenericSubjectLayerType } from './defs';
×
5
import { tr, DEFAULT_LOCALE } from '../api/i18n';
×
6
import { isResourceId, strEndsWith, strIsNullOrEmpty } from '../utils/string';
×
7
import { Client } from '../api/client';
×
8
import { applyInitialBaseLayerVisibility, IInitAsyncOptions, processLayerInMapGroup } from './init';
×
9
import { ICreateRuntimeMapOptions, IDescribeRuntimeMapOptions, RuntimeMapFeatureFlags } from '../api/request-builder';
×
10
import { info, debug } from '../utils/logger';
×
11
import { MgError } from '../api/error';
×
12
import { resolveProjectionFromEpsgCodeAsync } from '../api/registry/projections';
×
13
import { register } from 'ol/proj/proj4';
×
14
import proj4 from "proj4";
×
15
import { buildSubjectLayerDefn, getMapDefinitionsFromFlexLayout, isMapDefinition, isStateless, MapToLoad, ViewerInitCommand } from './init-command';
×
16
import { WebLayout } from '../api/contracts/weblayout';
17
import { convertWebLayoutUIItems, parseCommandsInWebLayout, prepareSubMenus, ToolbarConf } from '../api/registry/command-spec';
×
18
import { clearSessionStore, retrieveSelectionSetFromLocalStorage } from '../api/session-store';
×
19
import { WEBLAYOUT_CONTEXTMENU, WEBLAYOUT_TASKMENU, WEBLAYOUT_TOOLBAR } from "../constants";
×
20
import { registerCommand } from '../api/registry/command';
×
21
import { ensureParameters } from '../utils/url';
×
22
import { assertIsDefined } from '../utils/assert';
×
23
import { MapDefinition } from '../api/contracts/map-definition';
24
import { TileSetDefinition } from '../api/contracts/tile-set-definition';
25
import { AsyncLazy } from '../api/lazy';
×
26
import { SiteVersionResponse } from '../api/contracts/common';
27
import { isRuntimeMap } from '../utils/type-guards';
×
28
import { tryParseArbitraryCs } from '../utils/units';
×
29
import { ScopedId } from '../utils/scoped-id';
×
30
import { canUseQueryMapFeaturesV4, parseSiteVersion } from '../utils/site-version';
×
31
import { supportsWebGL } from '../utils/browser-support';
×
32

33
const TYPE_SUBJECT = "SubjectLayer";
×
34
const TYPE_EXTERNAL = "External";
×
35

36
const scopedId = new ScopedId();
×
37

38
/**
39
 * @since 0.14
40
 */
41
export type SubjectLayerType = RuntimeMap | IGenericSubjectMapLayer;
42

43
/**
44
 * Default viewer init commmand
45
 * 
46
 * @since 0.14
47
 */
48
export class DefaultViewerInitCommand extends ViewerInitCommand<SubjectLayerType> {
×
49
    private client: Client | undefined;
50
    private options: IInitAsyncOptions;
51
    constructor(dispatch: ReduxDispatch) {
×
52
        super(dispatch);
×
53
    }
×
54
    public attachClient(client: Client): void {
×
55
        this.client = client;
×
56
    }
×
57
    protected isArbitraryCoordSys(subject: SubjectLayerType | undefined) {
×
58
        if (subject) {
×
59
            if (isRuntimeMap(subject)) {
×
60
                const arbCs = tryParseArbitraryCs(subject.CoordinateSystem.MentorCode);
×
61
                return arbCs != null;
×
62
            }
×
63
        }
×
64
        return false;
×
65
    }
×
66
    /**
67
     * @override
68
     * @protected
69
     * @param {Dictionary<RuntimeMap>} mapsByName
70
     *
71
     */
72
    protected establishInitialMapNameAndSession(mapsByName: Dictionary<SubjectLayerType>): [string, string] {
×
73
        let firstMapName = "";
×
74
        let firstSessionId = "";
×
75
        for (const mapName in mapsByName) {
×
76
            if (!firstMapName && !firstSessionId) {
×
77
                const map = mapsByName[mapName];
×
78
                if (isRuntimeMap(map)) {
×
79
                    firstMapName = map.Name;
×
80
                    firstSessionId = map.SessionId;
×
81
                    break;
×
82
                }
×
83
            }
×
84
        }
×
85
        return [firstMapName, firstSessionId];
×
86
    }
×
87
    private getDesiredTargetMapName(mapDef: string) {
×
88
        const lastSlash = mapDef.lastIndexOf("/");
×
89
        const lastDot = mapDef.lastIndexOf(".");
×
90
        if (lastSlash >= 0 && lastDot >= 0 && lastDot > lastSlash) {
×
91
            return `${mapDef.substring(lastSlash + 1, lastDot)}`;
×
92
        } else {
×
93
            return `Map_${scopedId.next()}`;
×
94
        }
×
95
    }
×
96
    private async initFromWebLayoutAsync(webLayout: WebLayout, session: AsyncLazy<string>, sessionWasReused: boolean): Promise<IInitAppActionPayload> {
×
97
        const [mapsByName, , warnings] = await this.createRuntimeMapsAsync(session, webLayout, false, wl => [{ name: this.getDesiredTargetMapName(wl.Map.ResourceId), mapDef: wl.Map.ResourceId, metadata: {} }], () => [], sessionWasReused);
×
98
        const { locale, featureTooltipsEnabled, externalBaseLayers } = this.options;
×
99
        const cmdsByKey = parseCommandsInWebLayout(webLayout, registerCommand);
×
100
        const mainToolbar = (webLayout.ToolBar.Visible
×
101
            ? convertWebLayoutUIItems(webLayout.ToolBar.Button, cmdsByKey, locale)
×
102
            : []);
×
103
        const taskBar = (webLayout.TaskPane.TaskBar.Visible
×
104
            ? convertWebLayoutUIItems(webLayout.TaskPane.TaskBar.MenuButton, cmdsByKey, locale, false)
×
105
            : []);
×
106
        const contextMenu = (webLayout.ContextMenu.Visible
×
107
            ? convertWebLayoutUIItems(webLayout.ContextMenu.MenuItem, cmdsByKey, locale, false)
×
108
            : []);
×
109
        const config: any = {};
×
110
        if (webLayout.SelectionColor != null) {
×
111
            config.selectionColor = webLayout.SelectionColor;
×
112
        }
×
113
        if (webLayout.MapImageFormat != null) {
×
114
            config.imageFormat = webLayout.MapImageFormat;
×
115
        }
×
116
        if (webLayout.SelectionImageFormat != null) {
×
117
            config.selectionImageFormat = webLayout.SelectionImageFormat;
×
118
        }
×
119
        if (webLayout.PointSelectionBuffer != null) {
×
120
            config.pointSelectionBuffer = webLayout.PointSelectionBuffer;
×
121
        }
×
122
        let initialView: IMapView | null = null;
×
123
        if (webLayout.Map.InitialView != null) {
×
124
            initialView = {
×
125
                x: webLayout.Map.InitialView.CenterX,
×
126
                y: webLayout.Map.InitialView.CenterY,
×
127
                scale: webLayout.Map.InitialView.Scale
×
128
            };
×
129
        }
×
130

131
        if (webLayout.Title != "") {
×
132
            document.title = webLayout.Title || document.title;
×
133
        }
×
134

135
        const maps: any = {};
×
136
        const [firstMapName, firstSessionId] = this.establishInitialMapNameAndSession(mapsByName);
×
137

138
        for (const mapName in mapsByName) {
×
139
            const map = mapsByName[mapName];
×
140
            maps[mapName] = {
×
141
                mapGroupId: mapName,
×
142
                map: map,
×
143
                externalBaseLayers: this.options.externalBaseLayers ?? [],
×
144
                initialView: initialView
×
145
            };
×
146
        }
×
147

148
        const menus: Dictionary<ToolbarConf> = {};
×
149
        menus[WEBLAYOUT_TOOLBAR] = {
×
150
            items: mainToolbar
×
151
        };
×
152
        menus[WEBLAYOUT_TASKMENU] = {
×
153
            items: taskBar
×
154
        };
×
155
        menus[WEBLAYOUT_CONTEXTMENU] = {
×
156
            items: contextMenu
×
157
        };
×
158

159
        const tb = prepareSubMenus(menus)[0];
×
160
        return {
×
161
            activeMapName: firstMapName,
×
162
            featureTooltipsEnabled: featureTooltipsEnabled,
×
163
            initialUrl: ensureParameters(webLayout.TaskPane.InitialTask || "server/TaskPane.html", firstMapName, firstSessionId, locale),
×
164
            initialTaskPaneWidth: webLayout.TaskPane.Width,
×
165
            initialInfoPaneWidth: webLayout.InformationPane.Width,
×
166
            maps: maps,
×
167
            locale: locale,
×
168
            config: config,
×
169
            capabilities: {
×
170
                hasTaskPane: webLayout.TaskPane.Visible,
×
171
                hasTaskBar: webLayout.TaskPane.TaskBar.Visible,
×
172
                hasStatusBar: webLayout.StatusBar.Visible,
×
173
                hasNavigator: webLayout.ZoomControl.Visible,
×
174
                hasSelectionPanel: webLayout.InformationPane.Visible && webLayout.InformationPane.PropertiesVisible,
×
175
                hasLegend: webLayout.InformationPane.Visible && webLayout.InformationPane.LegendVisible,
×
176
                hasToolbar: webLayout.ToolBar.Visible,
×
177
                hasViewSize: webLayout.StatusBar.Visible
×
178
            },
×
179
            toolbars: tb,
×
180
            warnings: warnings,
×
181
            initialActiveTool: ActiveMapTool.Pan
×
182
        };
×
183
    }
×
184
    private async createRuntimeMap(options: ICreateRuntimeMapOptions, siteVersion: AsyncLazy<SiteVersionResponse>): Promise<RuntimeMap> {
×
185
        assertIsDefined(this.client);
×
186
        let map: RuntimeMap;
×
187
        const sv = await siteVersion.getValueAsync();
×
188
        if (canUseQueryMapFeaturesV4(parseSiteVersion(sv.Version))) {
×
189
            map = await this.client.createRuntimeMap_v4(options);
×
190
        } else {
×
191
            map = await this.client.createRuntimeMap(options);
×
192
        }
×
193
        return map;
×
194
    }
×
195
    private async describeRuntimeMap(options: IDescribeRuntimeMapOptions, siteVersion: AsyncLazy<SiteVersionResponse>): Promise<RuntimeMap> {
×
196
        assertIsDefined(this.client);
×
197
        let map: RuntimeMap;
×
198
        const sv = await siteVersion.getValueAsync();
×
199
        if (canUseQueryMapFeaturesV4(parseSiteVersion(sv.Version))) {
×
200
            map = await this.client.describeRuntimeMap_v4(options);
×
201
        } else {
×
202
            map = await this.client.describeRuntimeMap(options);
×
203
        }
×
204
        return map;
×
205
    }
×
206
    private async tryDescribeRuntimeMapAsync(mapName: string, session: AsyncLazy<string>, mapDef: string, siteVersion: AsyncLazy<SiteVersionResponse>) {
×
207
        assertIsDefined(this.client);
×
208
        try {
×
209
            const map = await this.describeRuntimeMap({
×
210
                mapname: mapName,
×
211
                requestedFeatures: RuntimeMapFeatureFlags.LayerFeatureSources | RuntimeMapFeatureFlags.LayerIcons | RuntimeMapFeatureFlags.LayersAndGroups,
×
212
                session: await session.getValueAsync()
×
213
            }, siteVersion);
×
214
            return map;
×
215
        } catch (e) {
×
216
            if (e.message === "MgResourceNotFoundException") {
×
217
                const map = await this.createRuntimeMap({
×
218
                    mapDefinition: mapDef,
×
219
                    requestedFeatures: RuntimeMapFeatureFlags.LayerFeatureSources | RuntimeMapFeatureFlags.LayerIcons | RuntimeMapFeatureFlags.LayersAndGroups,
×
220
                    session: await session.getValueAsync(),
×
221
                    targetMapName: mapName
×
222
                }, siteVersion);
×
223
                return map;
×
224
            }
×
225
            throw e;
×
226
        }
×
227
    }
×
228
    private async createRuntimeMapsAsync<TLayout>(session: AsyncLazy<string>, res: TLayout, isStateless: boolean, mapDefSelector: (res: TLayout) => (MapToLoad | IGenericSubjectMapLayer)[], projectionSelector: (res: TLayout) => string[], sessionWasReused: boolean): Promise<[Dictionary<SubjectLayerType>, Dictionary<MapToLoad>, string[]]> {
×
229
        const mapDefs = mapDefSelector(res);
×
230
        const mapPromises: Promise<RuntimeMap>[] = [];
×
231
        const warnings = [] as string[];
×
232
        const { locale } = this.options;
×
233
        const subjectLayers: Dictionary<IGenericSubjectMapLayer> = {};
×
234
        const fetchEpsgs: { epsg: string, mapDef: string }[] = [];
×
UNCOV
235
        const pendingMapDefs: Dictionary<MapToLoad> = {};
×
236
        // We use an AsyncLazy because we only want to fetch the site version *iff* we are required to
237
        const siteVersion = new AsyncLazy<SiteVersionResponse>(async () => {
×
238
            assertIsDefined(this.client);
×
239
            const sv = await this.client.getSiteVersion();
×
240
            return sv;
×
241
        });
×
242
        // Collect only the MapDefinition entries for lazy-load eligibility check
243
        const mapDefItems = mapDefs.filter(isMapDefinition);
×
244
        // Lazy creation only applies when: not stateless, not reusing session, and there are multiple MapGuide maps
245
        const canLazyLoad = !isStateless && !sessionWasReused && mapDefItems.length > 1;
×
246
        if (isStateless) { 
×
247
            for (const m of mapDefs) {
×
248
                if (isMapDefinition(m)) {
×
249
                    const siteVer = await siteVersion.getValueAsync();
×
UNCOV
250
                    assertIsDefined(this.client);
×
251
                    mapPromises.push(this.describeRuntimeMapStateless(this.client, siteVer.Version, m));
×
252
                } else {
×
253
                    const proj = m.meta?.projection;
×
254
                    if (!strIsNullOrEmpty(proj)) {
×
255
                        //Must be registered to proj4js if not 4326 or 3857
256
                        const [_, epsg] = proj.split(':');
×
257
                        if (!proj4.defs[`EPSG:${epsg}`]) {
×
258
                            fetchEpsgs.push({ epsg: epsg, mapDef: m.name });
×
259
                        }
×
260
                    }
×
UNCOV
261
                }
×
262
            }
×
UNCOV
263
        } else {
×
264
            let isFirstMapDef = true;
×
265
            for (const m of mapDefs) {
×
266
                if (isMapDefinition(m)) {
×
267
                    //sessionWasReused is a hint whether to create a new runtime map, or recover the last runtime map state from the given map name
268
                    if (sessionWasReused) {
×
269
                        //FIXME: If the map state we're recovering has a selection, we need to re-init the selection client-side
270
                        info(`Session ID re-used. Attempting recovery of map state of: ${m.name}`);
×
271
                        mapPromises.push(this.tryDescribeRuntimeMapAsync(m.name, session, m.mapDef, siteVersion));
×
272
                    } else if (canLazyLoad && !isFirstMapDef) {
×
273
                        // Defer creation of non-first maps in a multi-map layout to avoid loading all maps upfront
274
                        info(`Deferring lazy creation of runtime map (${m.name}) for: ${m.mapDef}`);
×
275
                        pendingMapDefs[m.name] = m;
×
276
                    } else {
×
277
                        info(`Creating runtime map state (${m.name}) for: ${m.mapDef}`);
×
278
                        assertIsDefined(this.client);
×
279
                        mapPromises.push(this.createRuntimeMap({
×
UNCOV
280
                            mapDefinition: m.mapDef,
×
281
                            requestedFeatures: RuntimeMapFeatureFlags.LayerFeatureSources | RuntimeMapFeatureFlags.LayerIcons | RuntimeMapFeatureFlags.LayersAndGroups,
×
282
                            session: await session.getValueAsync(),
×
283
                            targetMapName: m.name
×
284
                        }, siteVersion));
×
285
                    }
×
286
                    isFirstMapDef = false;
×
287
                }
×
288
            }
×
UNCOV
289
        }
×
290
        const maps = await Promise.all(mapPromises);
×
291
        //All must be non-zero
292
        for (const m of maps) {
×
293
            const epsg = m.CoordinateSystem.EpsgCode;
×
294
            const mapDef = m.MapDefinition;
×
295
            const arbCs = tryParseArbitraryCs(m.CoordinateSystem.MentorCode);
×
296
            if (!arbCs) {
×
297
                if (epsg == "0") {
×
298
                    throw new MgError(tr("INIT_ERROR_UNSUPPORTED_COORD_SYS", locale || DEFAULT_LOCALE, { mapDefinition: mapDef }));
×
299
                }
×
300
                //Must be registered to proj4js if not 4326 or 3857
301
                if (!proj4.defs[`EPSG:${epsg}`]) {
×
UNCOV
302
                    fetchEpsgs.push({ epsg: epsg, mapDef: mapDef });
×
UNCOV
303
                }
×
UNCOV
304
            }
×
UNCOV
305
        }
×
UNCOV
306
        const extraEpsgs = projectionSelector(res);
×
UNCOV
307
        for (const e of extraEpsgs) {
×
308
            if (!proj4.defs[`EPSG:${e}`]) {
×
309
                fetchEpsgs.push({ epsg: e, mapDef: "" });
×
UNCOV
310
            }
×
UNCOV
311
        }
×
312
        const epsgs = await Promise.all(fetchEpsgs.filter(fe => !strIsNullOrEmpty(fe.epsg)).map(f => resolveProjectionFromEpsgCodeAsync(f.epsg, locale, f.mapDef)));
×
313

314
        //Previously, we register proj4 with OpenLayers on the bootstrap phase way before this init
315
        //process is started. This no longer works for OL6 where it doesn't seem to pick up the extra
316
        //projections we've registered with proj4 after linking proj4 to OpenLayers. So that registration
317
        //step has been relocated here, after all the custom projections have been fetched and registered
318
        //with proj4
319
        debug(`Register proj4 with OpenLayers`);
×
320
        register(proj4);
×
321

322
        //Build the Dictionary<MgSubjectLayerType> from loaded maps
323
        const mapsByName: Dictionary<SubjectLayerType> = {};
×
324
        for (const map of maps) {
×
325
            mapsByName[map.Name] = map;
×
326
        }
×
327
        for (const gs of mapDefs) {
×
UNCOV
328
            if (!isMapDefinition(gs)) {
×
329
                mapsByName[gs.name] = gs;
×
330
            }
×
331
        }
×
332
        return [mapsByName, pendingMapDefs, warnings];
×
333
    }
×
334
    private async describeRuntimeMapStateless(client: Client, siteVersion: string, m: MapToLoad): Promise<RuntimeMap> {
×
335
        const { name, mapDef, metadata } = m;
×
336
        const mdf = await this.client?.getResource<MapDefinition>(mapDef, { username: "Anonymous" });
×
337
        if (!mdf)
×
338
            throw new Error("Failed to fetch map def");
×
339

340
        const rt: RuntimeMap = {
×
341
            SessionId: "",
×
342
            Extents: {
×
343
                LowerLeftCoordinate: {
×
344
                    X: mdf.Extents.MinX,
×
345
                    Y: mdf.Extents.MinY
×
346
                },
×
UNCOV
347
                UpperRightCoordinate: {
×
UNCOV
348
                    X: mdf.Extents.MaxX,
×
349
                    Y: mdf.Extents.MaxY
×
350
                }
×
351
            },
×
352
            SiteVersion: siteVersion,
×
353
            Name: name,
×
354
            DisplayDpi: 96,
×
355
            BackgroundColor: mdf.BackgroundColor,
×
UNCOV
356
            MapDefinition: mapDef,
×
357
            CoordinateSystem: {
×
358
                // We are assuming the app def specifies this data in each <Map> entry as extension properties
359
                // beginning with "Meta_" (eg. Meta_MentorCode, Meta_EpsgCode, etc)
360
                MentorCode: metadata.MentorCode,
×
361
                EpsgCode: metadata.EpsgCode,
×
362
                MetersPerUnit: metadata.MetersPerUnit,
×
363
                Wkt: mdf.CoordinateSystem
×
364
            },
×
365
            IconMimeType: "image/png",
×
366
        };
×
367

368
        const groups = [] as MapGroup[];
×
369
        const layers = [] as MapLayer[];
×
370

371
        if (mdf.TileSetSource) {
×
372
            rt.TileSetDefinition = mdf.TileSetSource.ResourceId;
×
373
            const tsd = await client.getResource<TileSetDefinition>(mdf.TileSetSource.ResourceId);
×
UNCOV
374
            if (tsd.TileStoreParameters.TileProvider == "Default") {
×
375
                const sTileWidth = tsd.TileStoreParameters.Parameter.find(p => p.Name == "TileWidth")?.Value;
×
376
                const sTileHeight = tsd.TileStoreParameters.Parameter.find(p => p.Name == "TileHeight")?.Value;
×
377
                if (!strIsNullOrEmpty(sTileWidth) && !strIsNullOrEmpty(sTileHeight)) {
×
378
                    rt.TileWidth = parseInt(sTileWidth, 10);
×
379
                    rt.TileHeight = parseInt(sTileHeight, 10);
×
380
                }
×
381
            } else if (tsd.TileStoreParameters.TileProvider == "XYZ") {
×
382
                rt.TileHeight = 256;
×
383
                rt.TileHeight = 256;
×
384
            }
×
385

UNCOV
386
            for (const bg of tsd.BaseMapLayerGroup) {
×
387
                groups.push({
×
388
                    Name: bg.Name,
×
389
                    DisplayInLegend: bg.ShowInLegend,
×
390
                    LegendLabel: bg.LegendLabel,
×
UNCOV
391
                    ObjectId: bg.Name,
×
392
                    ExpandInLegend: bg.ExpandInLegend,
×
393
                    Visible: bg.Visible,
×
394
                    ActuallyVisible: bg.Visible,
×
395
                    Type: 3 /* BaseMapFromTileSet */
×
396
                });
×
397

398
                for (const lyr of bg.BaseMapLayer) {
×
399
                    layers.push({
×
400
                        Name: lyr.Name,
×
401
                        DisplayInLegend: lyr.ShowInLegend,
×
402
                        // We don't have stateless QUERYMAPFEATURES (yet), so there is no point actually respecting this flag
403
                        Selectable: false, //lyr.Selectable,
×
404
                        LegendLabel: lyr.LegendLabel,
×
UNCOV
405
                        ExpandInLegend: lyr.ExpandInLegend,
×
406
                        Visible: true,
×
407
                        ParentId: bg.Name,
×
408
                        ActuallyVisible: true,
×
409
                        LayerDefinition: lyr.ResourceId,
×
410
                        ObjectId: lyr.Name,
×
411
                        Type: 2 /* BaseMap */
×
412
                    });
×
413
                }
×
414
            }
×
415
        }
×
416

417
        for (const grp of mdf.MapLayerGroup) {
×
UNCOV
418
            groups.push({
×
419
                Name: grp.Name,
×
420
                DisplayInLegend: grp.ShowInLegend,
×
421
                LegendLabel: grp.LegendLabel,
×
422
                ObjectId: grp.Name,
×
UNCOV
423
                ExpandInLegend: grp.ExpandInLegend,
×
424
                Visible: grp.Visible,
×
425
                ActuallyVisible: grp.Visible,
×
426
                Type: 1 /* Normal */
×
427
            });
×
428
        }
×
429

430
        for (const lyr of mdf.MapLayer) {
×
431
            layers.push({
×
432
                Name: lyr.Name,
×
433
                DisplayInLegend: lyr.ShowInLegend,
×
434
                // We don't have stateless QUERYMAPFEATURES (yet), so there is no point actually respecting this flag
UNCOV
435
                Selectable: false, // lyr.Selectable,
×
436
                LegendLabel: lyr.LegendLabel,
×
437
                ExpandInLegend: lyr.ExpandInLegend,
×
UNCOV
438
                Visible: true,
×
439
                ParentId: lyr.Group,
×
440
                ActuallyVisible: true,
×
UNCOV
441
                LayerDefinition: lyr.ResourceId,
×
UNCOV
442
                ObjectId: lyr.Name,
×
UNCOV
443
                Type: 1 /* Dynamic */
×
UNCOV
444
            })
×
UNCOV
445
        }
×
446

UNCOV
447
        rt.Group = groups;
×
UNCOV
448
        rt.Layer = layers;
×
449

UNCOV
450
        return rt;
×
451
    }
×
452
    /**
453
     * @override
454
     * @protected
455
     * @param {ApplicationDefinition} appDef
456
     * @param {Dictionary<SubjectLayerType>} mapsByName
457
     * @param {*} config
458
     * @param {string[]} warnings
459
     * @param {string} locale
460
     * @param {Dictionary<MapToLoad>} [pendingMapDefs]
461
     * @returns {Dictionary<MapInfo>}
462
     *
463
     */
UNCOV
464
    protected setupMaps(appDef: ApplicationDefinition, mapsByName: Dictionary<SubjectLayerType>, config: any, warnings: string[], locale: string, pendingMapDefs?: Dictionary<MapToLoad>): Dictionary<MapInfo> {
×
465
        const dict: Dictionary<MapInfo> = {};
×
466
        if (appDef.MapSet) {
×
467
            for (const mGroup of appDef.MapSet.MapGroup) {
×
468
                let mapName: string | undefined;
×
469
                //Setup external layers
470
                const initExternalLayers = [] as IGenericSubjectMapLayer[];
×
471
                const externalBaseLayers = [] as IExternalBaseLayer[];
×
472
                let subject: SubjectLayerType | undefined;
×
473
                //Need to do this in 2 passes. 1st pass to try and get the MG map
UNCOV
474
                for (const map of mGroup.Map) {
×
UNCOV
475
                    if (map.Type === "MapGuide") {
×
476
                        //TODO: Based on the schema, different MG map groups could have different
477
                        //settings here and our redux tree should reflect that. Currently the first one "wins"
478
                        if (!config.selectionColor && map.Extension.SelectionColor != null) {
×
479
                            config.selectionColor = map.Extension.SelectionColor;
×
480
                        }
×
481
                        if (!config.imageFormat && map.Extension.ImageFormat != null) {
×
482
                            config.imageFormat = map.Extension.ImageFormat;
×
483
                        }
×
484
                        if (!config.selectionImageFormat && map.Extension.SelectionFormat != null) {
×
485
                            config.selectionImageFormat = map.Extension.SelectionFormat;
×
486
                        }
×
487

488
                        //NOTE: Although non-sensical, if the same map definition exists across multiple
489
                        //MapGroups, we might be matching the wrong one. We just assume such non-sensical
490
                        //AppDefs won't exist
491
                        for (const name in mapsByName) {
×
492
                            const mapDef = mapsByName[name];
×
493
                            if (isRuntimeMap(mapDef) && mapDef.MapDefinition == map.Extension.ResourceId) {
×
494
                                mapName = name;
×
495
                                subject = mapDef;
×
496
                                break;
×
497
                            }
×
498
                        }
×
499
                        // If not found in the eagerly-loaded maps, check if it is a pending lazy map
500
                        if (!mapName && pendingMapDefs) {
×
501
                            const groupId = mGroup["@id"];
×
502
                            if (pendingMapDefs[groupId]) {
×
503
                                mapName = groupId;
×
504
                                // subject remains undefined for pending maps
505
                            }
×
506
                        }
×
507
                    }
×
508
                }
×
509
                const isArbitrary = this.isArbitraryCoordSys(subject);
×
510
                //2nd pass to process non-MG maps
511
                for (const map of mGroup.Map) {
×
UNCOV
512
                    if (map.Type == "MapGuide") {
×
513
                        continue;
×
UNCOV
514
                    }
×
515
                    if (map.Type == TYPE_SUBJECT) {
×
516
                        mapName = mGroup["@id"];
×
517
                    } else {
×
518
                        if (isArbitrary) {
×
519
                            warnings.push(tr("INIT_WARNING_ARBITRARY_COORDSYS_INCOMPATIBLE_LAYER", locale, { mapId: mGroup["@id"], type: map.Type }));
×
520
                        } else {
×
521
                            if (map.Type == TYPE_EXTERNAL) {
×
522
                                const layer = buildSubjectLayerDefn(map.Extension.layer_name, map);
×
523
                                if (layer.type == GenericSubjectLayerType.GeoTIFF && !supportsWebGL()) {
×
524
                                    warnings.push(tr("INIT_WARNING_WEBGL_UNSUPPORTED", locale));
×
UNCOV
525
                                }
×
526
                                initExternalLayers.push(layer);
×
UNCOV
527
                            } else {
×
UNCOV
528
                                processLayerInMapGroup(map, warnings, config, appDef, externalBaseLayers);
×
529
                            }
×
530
                        }
×
531
                    }
×
532
                }
×
533

534
                if (isArbitrary) {
×
535
                    //Check for incompatible widgets
536
                    for (const wset of appDef.WidgetSet) {
×
UNCOV
537
                        for (const widget of wset.Widget) {
×
538
                            switch (widget.Type) {
×
539
                                case "CoordinateTracker":
×
540
                                    warnings.push(tr("INIT_WARNING_ARBITRARY_COORDSYS_UNSUPPORTED_WIDGET", locale, { mapId: mGroup["@id"], widget: widget.Type }));
×
541
                                    break;
×
542
                            }
×
543
                        }
×
544
                    }
×
545
                }
×
546

547
                applyInitialBaseLayerVisibility(externalBaseLayers);
×
548

549
                //Setup initial view
550
                let initialView: IMapView | undefined;
×
551
                if (mGroup.InitialView) {
×
552
                    initialView = {
×
553
                        x: mGroup.InitialView.CenterX,
×
554
                        y: mGroup.InitialView.CenterY,
×
555
                        scale: mGroup.InitialView.Scale
×
556
                    };
×
557
                }
×
558

559
                if (mapName) {
×
560
                    const pendingEntry = pendingMapDefs?.[mapName];
×
561
                    dict[mapName] = {
×
562
                        mapGroupId: mGroup["@id"],
×
563
                        map: mapsByName[mapName],
×
564
                        initialView: initialView,
×
565
                        externalBaseLayers: externalBaseLayers,
×
566
                        initialExternalLayers: initExternalLayers,
×
567
                        // If this map is pending lazy creation, store the mapDef for later use
568
                        ...(pendingEntry ? { mapDef: pendingEntry.mapDef, metadata: pendingEntry.metadata } : {})
×
569
                    };
×
570
                }
×
571
            }
×
572
        }
×
573
        return dict;
×
574
    }
×
575
    private async initFromAppDefAsync(appDef: ApplicationDefinition, session: AsyncLazy<string>, sessionWasReused: boolean): Promise<IInitAppActionPayload> {
×
576
        if (Array.isArray(appDef.Extension?.CustomProjections?.Projection)) {
×
UNCOV
577
            for (const pd of appDef.Extension.CustomProjections.Projection) {
×
578
                let k, v;
×
579
                if (typeof (pd.epsg) === 'string' && typeof (pd.text) === 'string') { // appdef json form
×
580
                    k = pd.epsg;
×
581
                    v = pd.text;
×
582
                } else { // appdef xml translated form
×
583
                    const [epsg] = pd["@epsg"];
×
584
                    const [projStr] = pd["#text"];
×
585
                    k = epsg;
×
586
                    v = projStr;
×
587
                }
×
588
                if (!strIsNullOrEmpty(k) && !strIsNullOrEmpty(v)) {
×
589
                    proj4.defs(`EPSG:${k}`, v);
×
590
                    debug(`Registered proj4 defn from appdef for EPSG:${k}`, v);
×
591
                }
×
592
            }
×
593
            register(proj4);
×
594
        }
×
595
        const [mapsByName, pendingMapDefs, warnings] = await this.createRuntimeMapsAsync(session, appDef, isStateless(appDef), fl => getMapDefinitionsFromFlexLayout(fl), fl => this.getExtraProjectionsFromFlexLayout(fl), sessionWasReused);
×
596
        return await this.initFromAppDefCoreAsync(appDef, this.options, mapsByName, warnings, pendingMapDefs);
×
597
    }
×
598
    private async sessionAcquiredAsync(session: AsyncLazy<string>, sessionWasReused: boolean): Promise<IInitAppActionPayload> {
×
UNCOV
599
        const { resourceId, locale } = this.options;
×
600
        if (!resourceId) {
×
601
            //Try assumed default location of appdef.json that we are assuming sits in the same place as the viewer html files
UNCOV
602
            const cl = new Client("", "mapagent");
×
603
            try {
×
604
                const fl = await cl.get<ApplicationDefinition>("appdef.json");
×
605
                return await this.initFromAppDefAsync(fl, session, sessionWasReused);
×
606
            } catch (e) { //The appdef.json doesn't exist at the assumed default location?
×
607
                throw new MgError(tr("INIT_ERROR_MISSING_RESOURCE_PARAM", locale));
×
608
            }
×
609
        } else {
×
610
            if (typeof (resourceId) == 'string') {
×
611
                if (strEndsWith(resourceId, "WebLayout")) {
×
612
                    assertIsDefined(this.client);
×
613
                    const wl = await this.client.getResource<WebLayout>(resourceId, { SESSION: await session.getValueAsync() });
×
614
                    return await this.initFromWebLayoutAsync(wl, session, sessionWasReused);
×
615
                } else if (strEndsWith(resourceId, "ApplicationDefinition")) {
×
616
                    assertIsDefined(this.client);
×
617
                    const fl = await this.client.getResource<ApplicationDefinition>(resourceId, { SESSION: await session.getValueAsync() });
×
618
                    return await this.initFromAppDefAsync(fl, session, sessionWasReused);
×
619
                } else {
×
620
                    if (isResourceId(resourceId)) {
×
621
                        throw new MgError(tr("INIT_ERROR_UNKNOWN_RESOURCE_TYPE", locale, { resourceId: resourceId }));
×
622
                    } else {
×
623
                        //Assume URL to a appdef json document
624
                        let fl: ApplicationDefinition;
×
625
                        if (!this.client) {
×
626
                            // This wasn't set up with a mapagent URI (probably a non-MG viewer template), so make a new client on-the-fly
627
                            const cl = new Client("", "mapagent");
×
628
                            fl = await cl.get<ApplicationDefinition>(resourceId);
×
629
                        } else {
×
630
                            fl = await this.client.get<ApplicationDefinition>(resourceId);
×
631
                        }
×
632
                        return await this.initFromAppDefAsync(fl, session, sessionWasReused);
×
633
                    }
×
634
                }
×
635
            } else {
×
636
                const fl = await resourceId();
×
637
                return await this.initFromAppDefAsync(fl, session, sessionWasReused);
×
638
            }
×
639
        }
×
640
    }
×
641
    public async runAsync(options: IInitAsyncOptions): Promise<IInitAppActionPayload> {
×
642
        this.options = options;
×
643
        await this.initLocaleAsync(this.options);
×
UNCOV
644
        let sessionWasReused = false;
×
UNCOV
645
        let session: AsyncLazy<string>;
×
646
        if (!this.options.session) {
×
647
            session = new AsyncLazy<string>(async () => {
×
UNCOV
648
                assertIsDefined(this.client);
×
649
                const sid = await this.client.createSession("Anonymous", "");
×
650
                return sid;
×
UNCOV
651
            });
×
652
        } else {
×
653
            info(`Re-using session: ${this.options.session}`);
×
654
            sessionWasReused = true;
×
UNCOV
655
            session = new AsyncLazy<string>(() => Promise.resolve(this.options.session!));
×
UNCOV
656
        }
×
UNCOV
657
        const payload = await this.sessionAcquiredAsync(session, sessionWasReused);
×
UNCOV
658
        if (sessionWasReused) {
×
UNCOV
659
            let initSelections: IRestoredSelectionSets = {};
×
UNCOV
660
            for (const mapName in payload.maps) {
×
UNCOV
661
                const sset = await retrieveSelectionSetFromLocalStorage(session, mapName);
×
UNCOV
662
                if (sset) {
×
UNCOV
663
                    initSelections[mapName] = sset;
×
UNCOV
664
                }
×
UNCOV
665
            }
×
UNCOV
666
            payload.initialSelections = initSelections;
×
UNCOV
667
            try {
×
668
                //In the interest of being a responsible citizen, clean up all selection-related stuff from
669
                //session store
UNCOV
670
                await clearSessionStore();
×
UNCOV
671
            } catch (e) {
×
672

UNCOV
673
            }
×
UNCOV
674
        }
×
675

UNCOV
676
        return payload;
×
UNCOV
677
    }
×
UNCOV
678
}
×
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