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

jumpinjackie / mapguide-react-layout / 15160437878

21 May 2025 11:00AM UTC coverage: 21.631% (-42.6%) from 64.24%
15160437878

Pull #1552

github

web-flow
Merge 8b7153d9e into 236e2ea07
Pull Request #1552: Feature/package updates 2505

839 of 1165 branches covered (72.02%)

11 of 151 new or added lines in 25 files covered. (7.28%)

1332 existing lines in 50 files now uncovered.

4794 of 22163 relevant lines covered (21.63%)

6.89 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 } 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';
×
NEW
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

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

35
const scopedId = new ScopedId();
×
36

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

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

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

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

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

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

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

302
        //Previously, we register proj4 with OpenLayers on the bootstrap phase way before this init
303
        //process is started. This no longer works for OL6 where it doesn't seem to pick up the extra
304
        //projections we've registered with proj4 after linking proj4 to OpenLayers. So that registration
305
        //step has been relocated here, after all the custom projections have been fetched and registered
306
        //with proj4
307
        debug(`Register proj4 with OpenLayers`);
×
308
        register(proj4);
×
309

310
        //Build the Dictionary<MgSubjectLayerType> from loaded maps
311
        const mapsByName: Dictionary<SubjectLayerType> = {};
×
312
        for (const map of maps) {
×
313
            mapsByName[map.Name] = map;
×
314
        }
×
315
        for (const gs of mapDefs) {
×
316
            if (!isMapDefinition(gs)) {
×
317
                mapsByName[gs.name] = gs;
×
318
            }
×
319
        }
×
320
        return [mapsByName, warnings];
×
321
    }
×
322
    private async describeRuntimeMapStateless(client: Client, siteVersion: string, m: MapToLoad): Promise<RuntimeMap> {
×
323
        const { name, mapDef, metadata } = m;
×
324
        const mdf = await this.client?.getResource<MapDefinition>(mapDef, { username: "Anonymous" });
×
325
        if (!mdf)
×
326
            throw new Error("Failed to fetch map def");
×
327

328
        const rt: RuntimeMap = {
×
329
            SessionId: "",
×
330
            Extents: {
×
331
                LowerLeftCoordinate: {
×
332
                    X: mdf.Extents.MinX,
×
333
                    Y: mdf.Extents.MinY
×
334
                },
×
335
                UpperRightCoordinate: {
×
336
                    X: mdf.Extents.MaxX,
×
337
                    Y: mdf.Extents.MaxY
×
338
                }
×
339
            },
×
340
            SiteVersion: siteVersion,
×
341
            Name: name,
×
342
            DisplayDpi: 96,
×
343
            BackgroundColor: mdf.BackgroundColor,
×
344
            MapDefinition: mapDef,
×
345
            CoordinateSystem: {
×
346
                // We are assuming the app def specifies this data in each <Map> entry as extension properties
347
                // beginning with "Meta_" (eg. Meta_MentorCode, Meta_EpsgCode, etc)
348
                MentorCode: metadata.MentorCode,
×
349
                EpsgCode: metadata.EpsgCode,
×
350
                MetersPerUnit: metadata.MetersPerUnit,
×
351
                Wkt: mdf.CoordinateSystem
×
352
            },
×
353
            IconMimeType: "image/png",
×
354
        };
×
355

356
        const groups = [] as MapGroup[];
×
357
        const layers = [] as MapLayer[];
×
358

359
        if (mdf.TileSetSource) {
×
360
            rt.TileSetDefinition = mdf.TileSetSource.ResourceId;
×
361
            const tsd = await client.getResource<TileSetDefinition>(mdf.TileSetSource.ResourceId);
×
362
            if (tsd.TileStoreParameters.TileProvider == "Default") {
×
363
                const sTileWidth = tsd.TileStoreParameters.Parameter.find(p => p.Name == "TileWidth")?.Value;
×
364
                const sTileHeight = tsd.TileStoreParameters.Parameter.find(p => p.Name == "TileHeight")?.Value;
×
365
                if (!strIsNullOrEmpty(sTileWidth) && !strIsNullOrEmpty(sTileHeight)) {
×
366
                    rt.TileWidth = parseInt(sTileWidth, 10);
×
367
                    rt.TileHeight = parseInt(sTileHeight, 10);
×
368
                }
×
369
            } else if (tsd.TileStoreParameters.TileProvider == "XYZ") {
×
370
                rt.TileHeight = 256;
×
371
                rt.TileHeight = 256;
×
372
            }
×
373

374
            for (const bg of tsd.BaseMapLayerGroup) {
×
375
                groups.push({
×
376
                    Name: bg.Name,
×
377
                    DisplayInLegend: bg.ShowInLegend,
×
378
                    LegendLabel: bg.LegendLabel,
×
379
                    ObjectId: bg.Name,
×
380
                    ExpandInLegend: bg.ExpandInLegend,
×
381
                    Visible: bg.Visible,
×
382
                    ActuallyVisible: bg.Visible,
×
383
                    Type: 3 /* BaseMapFromTileSet */
×
384
                });
×
385

386
                for (const lyr of bg.BaseMapLayer) {
×
387
                    layers.push({
×
388
                        Name: lyr.Name,
×
389
                        DisplayInLegend: lyr.ShowInLegend,
×
390
                        // We don't have stateless QUERYMAPFEATURES (yet), so there is no point actually respecting this flag
391
                        Selectable: false, //lyr.Selectable,
×
392
                        LegendLabel: lyr.LegendLabel,
×
393
                        ExpandInLegend: lyr.ExpandInLegend,
×
394
                        Visible: true,
×
395
                        ParentId: bg.Name,
×
396
                        ActuallyVisible: true,
×
397
                        LayerDefinition: lyr.ResourceId,
×
398
                        ObjectId: lyr.Name,
×
399
                        Type: 2 /* BaseMap */
×
400
                    });
×
401
                }
×
402
            }
×
403
        }
×
404

405
        for (const grp of mdf.MapLayerGroup) {
×
406
            groups.push({
×
407
                Name: grp.Name,
×
408
                DisplayInLegend: grp.ShowInLegend,
×
409
                LegendLabel: grp.LegendLabel,
×
410
                ObjectId: grp.Name,
×
411
                ExpandInLegend: grp.ExpandInLegend,
×
412
                Visible: grp.Visible,
×
413
                ActuallyVisible: grp.Visible,
×
414
                Type: 1 /* Normal */
×
415
            });
×
416
        }
×
417

418
        for (const lyr of mdf.MapLayer) {
×
419
            layers.push({
×
420
                Name: lyr.Name,
×
421
                DisplayInLegend: lyr.ShowInLegend,
×
422
                // We don't have stateless QUERYMAPFEATURES (yet), so there is no point actually respecting this flag
423
                Selectable: false, // lyr.Selectable,
×
424
                LegendLabel: lyr.LegendLabel,
×
425
                ExpandInLegend: lyr.ExpandInLegend,
×
426
                Visible: true,
×
427
                ParentId: lyr.Group,
×
428
                ActuallyVisible: true,
×
429
                LayerDefinition: lyr.ResourceId,
×
430
                ObjectId: lyr.Name,
×
431
                Type: 1 /* Dynamic */
×
432
            })
×
433
        }
×
434

435
        rt.Group = groups;
×
436
        rt.Layer = layers;
×
437

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

474
                        //NOTE: Although non-sensical, if the same map definition exists across multiple
475
                        //MapGroups, we might be matching the wrong one. We just assume such non-sensical
476
                        //AppDefs won't exist
477
                        for (const name in mapsByName) {
×
478
                            const mapDef = mapsByName[name];
×
479
                            if (isRuntimeMap(mapDef) && mapDef.MapDefinition == map.Extension.ResourceId) {
×
480
                                mapName = name;
×
481
                                subject = mapDef;
×
482
                                break;
×
483
                            }
×
484
                        }
×
485
                    }
×
486
                }
×
487
                const isArbitrary = this.isArbitraryCoordSys(subject);
×
488
                //2nd pass to process non-MG maps
489
                for (const map of mGroup.Map) {
×
490
                    if (map.Type == "MapGuide") {
×
491
                        continue;
×
492
                    }
×
493
                    if (map.Type == TYPE_SUBJECT) {
×
494
                        mapName = mGroup["@id"];
×
495
                    } else {
×
496
                        if (isArbitrary) {
×
497
                            warnings.push(tr("INIT_WARNING_ARBITRARY_COORDSYS_INCOMPATIBLE_LAYER", locale, { mapId: mGroup["@id"], type: map.Type }));
×
498
                        } else {
×
499
                            if (map.Type == TYPE_EXTERNAL) {
×
500
                                initExternalLayers.push(buildSubjectLayerDefn(map.Extension.layer_name, map));
×
501
                            } else {
×
502
                                processLayerInMapGroup(map, warnings, config, appDef, externalBaseLayers);
×
503
                            }
×
504
                        }
×
505
                    }
×
506
                }
×
507

508
                if (isArbitrary) {
×
509
                    //Check for incompatible widgets
510
                    for (const wset of appDef.WidgetSet) {
×
511
                        for (const widget of wset.Widget) {
×
512
                            switch (widget.Type) {
×
513
                                case "CoordinateTracker":
×
514
                                    warnings.push(tr("INIT_WARNING_ARBITRARY_COORDSYS_UNSUPPORTED_WIDGET", locale, { mapId: mGroup["@id"], widget: widget.Type }));
×
515
                                    break;
×
516
                            }
×
517
                        }
×
518
                    }
×
519
                }
×
520

521
                applyInitialBaseLayerVisibility(externalBaseLayers);
×
522

523
                //Setup initial view
524
                let initialView: IMapView | undefined;
×
525
                if (mGroup.InitialView) {
×
526
                    initialView = {
×
527
                        x: mGroup.InitialView.CenterX,
×
528
                        y: mGroup.InitialView.CenterY,
×
529
                        scale: mGroup.InitialView.Scale
×
530
                    };
×
531
                }
×
532

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

644
            }
×
645
        }
×
646

647
        return payload;
×
648
    }
×
649
}
×
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