• 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/api/layer-set.ts
1
import { RefreshMode, IExternalBaseLayer, Bounds, LayerTransparencySet, LayerProperty, MgBuiltInLayers, MgLayerType, MG_LAYER_TYPE_NAME, MG_BASE_LAYER_GROUP_NAME, ImageFormat, GenericEvent, ClientKind, Coordinate2D, Size, BLANK_SIZE, Dictionary, UnitOfMeasure } from './common';
×
2
import LayerGroup from "ol/layer/Group";
×
3
import TileGrid from "ol/tilegrid/TileGrid";
×
4
import { createXYZ } from "ol/tilegrid";
×
5
import AbstractSource from "ol/source/Source";
6
import TileImageSource from "ol/source/TileImage";
×
7
import XYZSource from "ol/source/XYZ";
×
8
import { createMapGuideSource } from "./ol-mapguide-source-factory";
×
9
import ImageStaticSource from "ol/source/ImageStatic";
×
10
import { restrictToRange } from "../utils/number";
×
11
import View from "ol/View";
×
12
import * as olExtent from "ol/extent";
×
13
import TileLayer from "ol/layer/Tile";
×
14
import ImageLayer from "ol/layer/Image";
×
15
import LayerBase from "ol/layer/Base";
16
import { RuntimeMap } from './contracts/runtime-map';
17
import { createExternalSource, createOLLayerFromSubjectDefn } from '../components/external-layer-factory';
×
18
import { strIsNullOrEmpty } from '../utils/string';
×
19
import { parseUrl } from '../utils/url';
×
20
import ImageWrapper from 'ol/Image';
21
import { DEFAULT_LOCALE, tr } from './i18n';
×
22
import * as olHas from "ol/has";
×
23
import Feature from "ol/Feature";
24
import { debug } from '../utils/logger';
×
25
import { Client } from './client';
26
import { ILayerSetOL, IImageLayerEvents } from './layer-set-contracts';
27
import Geometry from 'ol/geom/Geometry';
28
import UrlTile from 'ol/source/UrlTile';
×
29
import { LoadFunction as TileLoadFunction } from 'ol/Tile';
30
import { MapGuideMockMode } from '../components/mapguide-debug-context';
31
import { BLANK_GIF_DATA_URI, LAYER_ID_BASE, LAYER_ID_MG_BASE, LAYER_ID_MG_DYNAMIC_OVERLAY, LAYER_ID_MG_SEL_OVERLAY } from '../constants';
×
32
import { OLImageLayer, OLTileLayer } from './ol-types';
33
import { IGenericSubjectMapLayer } from '../actions/defs';
34
import { GenericLayerSetOL } from './generic-layer-set';
×
35
import { get, METERS_PER_UNIT, Projection, ProjectionLike } from "ol/proj";
×
36
import { isRuntimeMap } from '../utils/type-guards';
×
37
import { MgError } from './error';
×
38
import { tryParseArbitraryCs } from '../utils/units';
×
39

40
const DEFAULT_BOUNDS_3857: Bounds = [
×
41
    -20026376.39,
×
42
    -20048966.10,
×
43
    20026376.39,
×
44
    20048966.10
×
45
];
×
46

47
const DEFAULT_BOUNDS_4326: Bounds = [-180, -90, 180, 90];
×
48

49
function getMetersPerUnit(projection: string) {
×
50
    const proj = get(projection);
×
51
    return proj?.getMetersPerUnit();
×
52
}
×
53

54
function toMetersPerUnit(unit: UnitOfMeasure) {
×
55
    const u = toProjUnit(unit);
×
56
    // Use OL-provided mpu if available
NEW
57
    let mpu: number = (METERS_PER_UNIT as any)[u];
×
58
    if (mpu) {
×
59
        return mpu;
×
60
    } else {
×
61
        // Otherwise, compute ourselves
62
        switch (unit) {
×
63
            case UnitOfMeasure.Centimeters:
×
64
                return 0.01;
×
65
            case UnitOfMeasure.DMS:
×
66
            case UnitOfMeasure.DecimalDegrees:
×
67
            case UnitOfMeasure.Degrees:
×
68
                return (2 * Math.PI * 6370997) / 360;
×
69
            case UnitOfMeasure.Feet:
×
70
                return 0.3048;
×
71
            case UnitOfMeasure.Inches:
×
72
                return 0.0254;
×
73
            case UnitOfMeasure.Kilometers:
×
74
                return 1000;
×
75
            case UnitOfMeasure.Meters:
×
76
                return 1;
×
77
            case UnitOfMeasure.Miles:
×
78
                return 1609.344;
×
79
            case UnitOfMeasure.Millimeters:
×
80
                return 0.001;
×
81
            case UnitOfMeasure.NauticalMiles:
×
82
                return 1852;
×
83
            case UnitOfMeasure.Pixels:
×
84
                return 1;
×
85
            case UnitOfMeasure.Unknown:
×
86
                return 1;
×
87
            case UnitOfMeasure.Yards:
×
88
                return 0.9144;
×
89
        }
×
90
    }
×
91
}
×
92

93
export function toProjUnit(unit: UnitOfMeasure) {
×
94
    switch (unit) {
×
95
        case UnitOfMeasure.Meters:
×
96
            return "m";
×
97
        case UnitOfMeasure.Feet:
×
98
            return "ft";
×
99
        case UnitOfMeasure.Inches:
×
100
            return "in";
×
101
        case UnitOfMeasure.Centimeters:
×
102
            return "cm";
×
103
        case UnitOfMeasure.Kilometers:
×
104
            return "km";
×
105
        case UnitOfMeasure.Yards:
×
106
            return "yd";
×
107
        case UnitOfMeasure.Millimeters:
×
108
            return "mm";
×
109
        case UnitOfMeasure.Miles:
×
110
            return "mi";
×
111
        case UnitOfMeasure.NauticalMiles:
×
112
            return "nm";
×
113
        case UnitOfMeasure.Pixels:
×
114
            return "px";
×
115
        default:
×
116
            throw new MgError("Unsupported unit");
×
117
    }
×
118
}
×
119

120
export function blankImageLoadFunction(image: ImageWrapper) {
×
121
    (image.getImage() as any).src = BLANK_GIF_DATA_URI;
×
122
}
×
123

124
export function mockMapGuideImageLoadFunction(image: ImageWrapper, src: string) {
×
125
    let el = document.getElementById("mg-debug-text-canvas");
×
126
    if (!el) {
×
127
        el = document.createElement("canvas");
×
128
        el.style.visibility = "hidden";
×
129
        el.id = "mg-debug-text-canvas";
×
130
        document.body.append(el);
×
131
    }
×
132
    const tCtx = (el as HTMLCanvasElement).getContext("2d");
×
133
    if (tCtx) {
×
134
        tCtx.clearRect(0, 0, tCtx.canvas.width, tCtx.canvas.height);
×
135

136
        const strings = [];
×
137
        const parsed = parseUrl(src);
×
138
        strings.push("[Mock MapGuide Map Image Request]");
×
139
        strings.push(`Agent: ${parsed.url}`);
×
140

141
        const xoff = 10;
×
142
        const yoff = 30;
×
143
        const fontSize = 14;
×
144
        let mm = tCtx.measureText(strings[0])
×
145
        let maxSize = mm.width + xoff;
×
146

147
        let ch = yoff + fontSize + 2;
×
148

149
        maxSize = Math.max(tCtx.measureText(strings[1]).width + xoff, maxSize);
×
150
        ch += (fontSize + 2);
×
151

152
        const keys = Object.keys(parsed.query);
×
153
        for (const k of keys) {
×
154
            if (k == "MAPNAME" || k == "SETDISPLAYWIDTH" || k == "SETDISPLAYHEIGHT" || k == "SETVIEWCENTERX" || k == "SETVIEWCENTERY" || k == "SETVIEWSCALE") {
×
155
                if (!strIsNullOrEmpty(parsed.query[k])) {
×
156
                    const s = `${k}: ${parsed.query[k]}`;
×
157
                    strings.push(s);
×
158
                    maxSize = Math.max(tCtx.measureText(s).width + xoff, maxSize);
×
159
                    ch += (fontSize + 2);
×
160
                }
×
161
            }
×
162
        }
×
163

164
        tCtx.canvas.width = maxSize;
×
165
        tCtx.canvas.height = ch;
×
166
        tCtx.fillStyle = "rgba(255, 0, 0, 0.5)";
×
167
        tCtx.fillRect(0, 0, maxSize, ch);
×
168

169
        tCtx.fillStyle = "rgba(255, 255, 0, 1.0)";
×
170
        //console.log(`Canvas size: [${tCtx.canvas.width}, ${tCtx.canvas.height}]`);
171
        tCtx.font = `${fontSize}px sans-serif`;
×
172

173
        let y = yoff;
×
174
        for (const str of strings) {
×
175
            //console.log(`Draw (${str}) at [10, ${y}]`);
176
            tCtx.fillText(str, 10, y);
×
177
            y += (fontSize + 1);
×
178
        }
×
179
        (image.getImage() as any).src = tCtx.canvas.toDataURL();
×
180
    }
×
181
}
×
182

183
export enum MgLayerSetMode {
×
184
    Stateless,
×
185
    Stateful,
×
186
    OverviewMap
×
187
}
188

189
/**
190
 * @hidden
191
 */
192
export class MgLayerSetOL implements ILayerSetOL {
×
193
    constructor(public readonly mgTiledLayers: OLTileLayer[],
×
194
        public readonly externalBaseLayersGroup: LayerGroup | undefined,
×
195
        public readonly overlay: OLImageLayer,
×
196
        public readonly projection: ProjectionLike,
×
197
        public readonly dpi: number,
×
198
        public readonly extent: Bounds,
×
199
        private readonly inPerUnit: number,
×
200
        public readonly view: View) { }
×
201

202
    public selectionOverlay: OLImageLayer | undefined;
203
    public activeSelectedFeatureOverlay: OLImageLayer | undefined;
204

205
    public getMetersPerUnit(): number {
×
206
        return this.inPerUnit / 39.37
×
207
    }
×
208
    public scaleToResolution(scale: number): number {
×
209
        return (scale / this.inPerUnit / this.dpi) * olHas.DEVICE_PIXEL_RATIO;
×
210
    }
×
211
    public resolutionToScale(resolution: number): number {
×
212
        return (resolution * this.dpi * this.inPerUnit) / olHas.DEVICE_PIXEL_RATIO;
×
213
    }
×
214
    public getSourcesForProgressTracking(): AbstractSource[] {
×
215
        const sources: AbstractSource[] = [];
×
216
        if (this.externalBaseLayersGroup) {
×
217
            const bls = this.externalBaseLayersGroup.getLayersArray();
×
218
            for (const bl of bls) {
×
219
                if (bl instanceof ImageLayer || bl instanceof TileLayer) {
×
220
                    sources.push(bl.getSource());
×
221
                }
×
222
            }
×
223
        }
×
224
        for (let i = this.mgTiledLayers.length - 1; i >= 0; i--) {
×
225
            const s = this.mgTiledLayers[i].getSource();
×
226
            if (s) {
×
227
                sources.push();
×
228
            }
×
229
        }
×
230
        const ovs = this.overlay.getSource();
×
231
        if (ovs) {
×
232
            sources.push(ovs);
×
233
        }
×
234
        if (this.selectionOverlay) {
×
235
            const sovs = this.selectionOverlay.getSource();
×
236
            if (sovs) {
×
237
                sources.push(sovs);
×
238
            }
×
239
        }
×
240
        if (this.activeSelectedFeatureOverlay) {
×
241
            const asovs = this.activeSelectedFeatureOverlay.getSource();
×
242
            if (asovs) {
×
243
                sources.push(asovs);
×
244
            }
×
245
        }
×
246
        return sources;
×
247
    }
×
248
    public getLayers(): LayerBase[] {
×
249
        const layers: LayerBase[] = [];
×
250
        if (this.externalBaseLayersGroup) {
×
251
            layers.push(this.externalBaseLayersGroup);
×
252
        }
×
253
        for (let i = this.mgTiledLayers.length - 1; i >= 0; i--) {
×
254
            layers.push(this.mgTiledLayers[i]);
×
255
        }
×
256
        layers.push(this.overlay);
×
257
        if (this.selectionOverlay) {
×
258
            layers.push(this.selectionOverlay);
×
259
        }
×
260
        if (this.activeSelectedFeatureOverlay) {
×
261
            layers.push(this.activeSelectedFeatureOverlay);
×
262
        }
×
263
        return layers;
×
264
    }
×
265
    public update(showGroups: string[] | undefined, showLayers: string[] | undefined, hideGroups: string[] | undefined, hideLayers: string[] | undefined) {
×
266
        //Send the request
267
        const imgSource = this.overlay.getSource() as any; //olMapGuideSource;
×
268
        //NOTE: Even if these group ids being shown/hidden are MG base layer groups, it still has to be
269
        //done as the server-side snapshot of the runtime map needs to be aware as well. This will be
270
        //apparent if you were to plot a runtime-map server-side that has base layer groups.
271
        imgSource.updateParams({
×
272
            showlayers: showLayers,
×
273
            showgroups: showGroups,
×
274
            hidelayers: hideLayers,
×
275
            hidegroups: hideGroups
×
276
        });
×
277
        //As MG base layer groups are separate ol layer instances, we have to toggle them on the client-side as well
278
        if (showGroups && showGroups.length > 0) {
×
279
            for (const groupId of showGroups) {
×
280
                const match = this.mgTiledLayers.filter(l => l.get(LayerProperty.LAYER_NAME) === groupId);
×
281
                if (match.length == 1) {
×
282
                    match[0].setVisible(true);
×
283
                }
×
284
            }
×
285
        }
×
286
        if (hideGroups && hideGroups.length > 0) {
×
287
            for (const groupId of hideGroups) {
×
288
                const match = this.mgTiledLayers.filter(l => l.get(LayerProperty.LAYER_NAME) === groupId);
×
289
                if (match.length == 1) {
×
290
                    match[0].setVisible(false);
×
291
                }
×
292
            }
×
293
        }
×
294
    }
×
295
    public updateSelectionColor(color: string) {
×
296
        if (this.selectionOverlay) {
×
297
            const source = this.selectionOverlay.getSource() as any; // olMapGuideSource;
×
298
            source.updateParams({
×
299
                SELECTIONCOLOR: color
×
300
            });
×
301
        }
×
302
    }
×
303
    public updateExternalBaseLayers(externalBaseLayers: IExternalBaseLayer[]) {
×
304
        if (this.externalBaseLayersGroup) {
×
305
            const layers = this.externalBaseLayersGroup.getLayers();
×
306
            layers.forEach((l: LayerBase) => {
×
307
                const match = (externalBaseLayers || []).filter(el => el.name === l.get("title"));
×
308
                if (match.length == 1) {
×
309
                    l.setVisible(!!match[0].visible);
×
310
                } else {
×
311
                    l.setVisible(false);
×
312
                }
×
313
            });
×
314
        }
×
315
    }
×
316
    public updateTransparency(trans: LayerTransparencySet) {
×
317
        //If no external layers defined, this won't be set
318
        if (this.externalBaseLayersGroup) {
×
319
            if (LAYER_ID_BASE in trans) {
×
320
                this.externalBaseLayersGroup.setOpacity(restrictToRange(trans[LAYER_ID_BASE], 0, 1.0));
×
321
            } else {
×
322
                this.externalBaseLayersGroup.setOpacity(1.0);
×
323
            }
×
324
        }
×
325

326
        if (LAYER_ID_MG_DYNAMIC_OVERLAY in trans) {
×
327
            const opacity = restrictToRange(trans[LAYER_ID_MG_DYNAMIC_OVERLAY], 0, 1.0);
×
328
            this.overlay.setOpacity(opacity);
×
329
        } else {
×
330
            this.overlay.setOpacity(1.0);
×
331
        }
×
332

333
        if (LAYER_ID_MG_BASE in trans) {
×
334
            const opacity = restrictToRange(trans[LAYER_ID_MG_BASE], 0, 1.0);
×
335
            for (const group of this.mgTiledLayers) {
×
336
                group.setOpacity(opacity);
×
337
            }
×
338
        } else {
×
339
            for (const group of this.mgTiledLayers) {
×
340
                group.setOpacity(1.0);
×
341
            }
×
342
        }
×
343

344
        if (this.selectionOverlay) {
×
345
            if (LAYER_ID_MG_SEL_OVERLAY in trans) {
×
346
                this.selectionOverlay.setOpacity(restrictToRange(trans[LAYER_ID_MG_SEL_OVERLAY], 0, 1.0));
×
347
            } else {
×
348
                this.selectionOverlay.setOpacity(1.0);
×
349
            }
×
350
        }
×
351
    }
×
352
    public refreshMap(mode: RefreshMode = RefreshMode.LayersOnly | RefreshMode.SelectionOnly): void {
×
353
        if ((mode & RefreshMode.LayersOnly) == RefreshMode.LayersOnly) {
×
354
            const imgSource = this.overlay.getSource() as any; // olMapGuideSource;
×
355
            imgSource.updateParams({
×
356
                seq: (new Date()).getTime()
×
357
            });
×
358
        }
×
359
        if (this.selectionOverlay) {
×
360
            if ((mode & RefreshMode.SelectionOnly) == RefreshMode.SelectionOnly) {
×
361
                const imgSource = this.selectionOverlay.getSource() as any; // olMapGuideSource;
×
362
                imgSource.updateParams({
×
363
                    seq: (new Date()).getTime()
×
364
                });
×
365
            }
×
366
        }
×
367
    }
×
368
    private makeActiveSelectedFeatureSource(mapExtent: Bounds, size: Size, url: string = BLANK_GIF_DATA_URI) {
×
369
        return new ImageStaticSource({
×
370
            imageExtent: mapExtent,
×
371
            //imageSize: [size.w, size.h],
372
            url: url
×
373
        });
×
374
    }
×
375
    /**
376
     * 
377
     * @param mapExtent 
378
     * @param size @deprecated This parameter is no longer used and will be removed in a later release
379
     * @param uri 
380
     * 
381
     * @since 0.15 Deprecated size parameter
382
     */
383
    public showActiveSelectedFeature(mapExtent: Bounds, size: Size, uri: string) {
×
384
        if (this.activeSelectedFeatureOverlay) {
×
385
            this.activeSelectedFeatureOverlay.setSource(this.makeActiveSelectedFeatureSource(mapExtent, size, uri));
×
386
            this.activeSelectedFeatureOverlay.setVisible(true);
×
387
        }
×
388
    }
×
389
}
×
390

391
export interface IMapViewerContextCallback {
392
    getMockMode(): MapGuideMockMode | undefined;
393
    incrementBusyWorker(): void;
394
    decrementBusyWorker(): void;
395
    addImageLoading(): void;
396
    addImageLoaded(): void;
397
    onImageError(e: GenericEvent): void;
398
    onSessionExpired(): void;
399
    getSelectableLayers(): string[];
400
    getClient(): Client;
401
    isContextMenuOpen(): boolean;
402
    getAgentUri(): string;
403
    getAgentKind(): ClientKind;
404
    getMapName(): string;
405
    getSessionId(): string;
406
    getLocale(): string | undefined;
407
    isFeatureTooltipEnabled(): boolean;
408
    getPointSelectionBox(point: Coordinate2D): Bounds;
409
    openTooltipLink(url: string): void;
410
    addFeatureToHighlight(feat: Feature<Geometry> | undefined, bAppend: boolean): void;
411
    getBaseTileLoaders(): Dictionary<TileLoadFunction>;
412
}
413

414
export interface ILayerSetFactory {
415
    create(locale: string | undefined,
416
        externalBaseLayers: IExternalBaseLayer[] | undefined,
417
        mode: MgLayerSetMode,
418
        appSettings: Dictionary<string>): ILayerSetOL
419
}
420

421
export class MgInnerLayerSetFactory implements ILayerSetFactory {
×
422
    private dynamicOverlayParams: any;
423
    private staticOverlayParams: any;
424
    private selectionOverlayParams: any;
425
    constructor(private callback: IMapViewerContextCallback,
×
426
        private map: RuntimeMap | IGenericSubjectMapLayer,
×
427
        private agentUri: string | undefined,
×
428
        imageFormat: string,
×
429
        selectionImageFormat: string | undefined,
×
430
        selectionColor: string | undefined) {
×
431
        if (isRuntimeMap(map)) {
×
432
            this.dynamicOverlayParams = {
×
433
                MAPNAME: map.Name,
×
434
                FORMAT: imageFormat,
×
435
                SESSION: map.SessionId,
×
436
                BEHAVIOR: 2
×
437
            };
×
438
            this.staticOverlayParams = {
×
439
                MAPDEFINITION: map.MapDefinition,
×
440
                FORMAT: imageFormat,
×
441
                CLIENTAGENT: "ol.source.ImageMapGuide for OverviewMap",
×
442
                USERNAME: "Anonymous",
×
443
                VERSION: "3.0.0"
×
444
            };
×
445
            this.selectionOverlayParams = {
×
446
                MAPNAME: map.Name,
×
447
                FORMAT: selectionImageFormat || "PNG8",
×
448
                SESSION: map.SessionId,
×
449
                SELECTIONCOLOR: selectionColor,
×
450
                BEHAVIOR: 1 | 4 //selected features + include outside current scale
×
451
            };
×
452
        }
×
453
    }
×
454
    private getTileUrlFunctionForGroup(resourceId: string, groupName: string, zOrigin: number) {
×
455
        const urlTemplate = this.callback.getClient().getTileTemplateUrl(resourceId, groupName, '{x}', '{y}', '{z}', false);
×
456
        return function (tileCoord: [number, number, number]) {
×
457
            const z = tileCoord[0];
×
458
            const x = tileCoord[1];
×
459
            const y = tileCoord[2]; //NOTE: tileCoord format changed in OL 6.0, no longer need to negate and subtract by 1
×
460
            return urlTemplate
×
461
                .replace('{z}', (zOrigin - z).toString())
×
462
                .replace('{x}', x.toString())
×
463
                .replace('{y}', (y).toString());
×
464
        };
×
465
    }
×
466
    public create(locale: string | undefined,
×
467
        externalBaseLayers: IExternalBaseLayer[] | undefined,
×
468
        mode: MgLayerSetMode,
×
469
        appSettings: Dictionary<string>): ILayerSetOL {
×
470
        const { map, agentUri } = this;
×
471
        if (isRuntimeMap(map)) {
×
472
            if (strIsNullOrEmpty(agentUri)) {
×
473
                throw new MgError("Expected agentUri to be set");
×
474
            }
×
475
            //If a tile set definition is defined it takes precedence over the map definition, this enables
476
            //this example to work with older releases of MapGuide where no such resource type exists.
477
            const resourceId = map.TileSetDefinition || map.MapDefinition;
×
478
            //On MGOS 2.6 or older, tile width/height is never returned, so default to 300x300
479
            const tileWidth = map.TileWidth || 300;
×
480
            const tileHeight = map.TileHeight || 300;
×
481
            const metersPerUnit = map.CoordinateSystem.MetersPerUnit;
×
482
            const finiteScales = [] as number[];
×
483
            if (map.FiniteDisplayScale) {
×
484
                for (let i = map.FiniteDisplayScale.length - 1; i >= 0; i--) {
×
485
                    finiteScales.push(map.FiniteDisplayScale[i]);
×
486
                }
×
487
            }
×
488
            const extent: olExtent.Extent = [
×
489
                map.Extents.LowerLeftCoordinate.X,
×
490
                map.Extents.LowerLeftCoordinate.Y,
×
491
                map.Extents.UpperRightCoordinate.X,
×
492
                map.Extents.UpperRightCoordinate.Y
×
493
            ];
×
494
            const dpi = map.DisplayDpi;
×
495
            const inPerUnit = 39.37 * map.CoordinateSystem.MetersPerUnit;
×
496
            const resolutions = new Array(finiteScales.length);
×
497
            let projection: ProjectionLike;
×
498
            for (let i = 0; i < finiteScales.length; ++i) {
×
499
                resolutions[i] = finiteScales[i] / inPerUnit / dpi;
×
500
            }
×
501
            const parsedArb = tryParseArbitraryCs(map.CoordinateSystem.MentorCode);
×
502
            if (parsedArb) {
×
503
                projection = new Projection({
×
504
                    code: parsedArb.code,
×
505
                    // HACK: Going to take a risk and just jam the values in
NEW
506
                    units: toProjUnit(parsedArb.units) as any,
×
507
                    metersPerUnit: toMetersPerUnit(parsedArb.units)
×
NEW
508
                });
×
509
            } else {
×
510
                if (map.CoordinateSystem.EpsgCode.length > 0) {
×
511
                    projection = `EPSG:${map.CoordinateSystem.EpsgCode}`;
×
512
                }
×
513
            }
×
514
            const zOrigin = finiteScales.length - 1;
×
515
            const mgTiledLayers = [];
×
516
            //const groupLayers = [] as TileLayer[];
517
            if (map.Group) {
×
518
                for (let i = 0; i < map.Group.length; i++) {
×
519
                    const group = map.Group[i];
×
520
                    if (group.Type != 2 && group.Type != 3) { //BaseMap or LinkedTileSet
×
521
                        continue;
×
522
                    }
×
523
                    let tileLayer: TileLayer<any>;
×
524
                    if (group.Type === 3 && map.TileSetProvider === "XYZ") {
×
525
                        let retinaScale = 1;
×
526
                        if (typeof (map.TilePixelRatio) != 'undefined') {
×
527
                            retinaScale = map.TilePixelRatio;
×
528
                        }
×
529
                        const tileSource = new XYZSource({
×
530
                            tileSize: [256 * retinaScale, 256 * retinaScale],
×
531
                            tileGrid: createXYZ({ tileSize: [256, 256] }),
×
532
                            projection: projection,
×
533
                            // TODO: Should use tileUrlFunction for consistency with MG tiled layers below and to also faciliate 
534
                            // something like client-side tile caching in the future
535
                            url: this.callback.getClient().getTileTemplateUrl(resourceId, group.Name, '{x}', '{y}', '{z}', true),
×
536
                            wrapX: false
×
537
                        });
×
538
                        tileLayer = new TileLayer({
×
539
                            //name: group.Name,
540
                            source: tileSource
×
541
                        });
×
542
                    } else {
×
543
                        const tileGrid = new TileGrid({
×
544
                            origin: olExtent.getTopLeft(extent),
×
545
                            resolutions: resolutions,
×
546
                            tileSize: [tileWidth, tileHeight]
×
547
                        });
×
548
                        const tileSource = new TileImageSource({
×
549
                            tileGrid: tileGrid,
×
550
                            projection: projection,
×
551
                            tileUrlFunction: this.getTileUrlFunctionForGroup(resourceId, group.Name, zOrigin),
×
552
                            wrapX: false
×
553
                        });
×
554
                        tileLayer = new TileLayer({
×
555
                            //name: group.Name,
556
                            source: tileSource
×
557
                        });
×
558
                    }
×
559
                    tileLayer.set(LayerProperty.LAYER_NAME, group.ObjectId);
×
560
                    tileLayer.set(LayerProperty.LAYER_DISPLAY_NAME, group.ObjectId);
×
561
                    tileLayer.set(LayerProperty.LAYER_TYPE, MgLayerType.Tiled);
×
562
                    tileLayer.set(LayerProperty.IS_EXTERNAL, false);
×
563
                    tileLayer.set(LayerProperty.IS_GROUP, false);
×
564
                    tileLayer.setVisible(group.Visible);
×
565
                    //groupLayers.push(tileLayer);
566
                    mgTiledLayers.push(tileLayer);
×
567
                }
×
568
            }
×
569
            /*
570
            if (groupLayers.length > 0) {
571
                groupLayers.push(
572
                    new ol.layer.Tile({
573
                        source: new ol.source.TileDebug({
574
                            tileGrid: tileGrid,
575
                            projection: projection,
576
                            tileUrlFunction: function(tileCoord) {
577
                                const z = tileCoord[0];
578
                                const x = tileCoord[1];
579
                                const y = tileCoord[2]; //NOTE: tileCoord format changed in OL 6.0, no longer need to negate and subtract by 1
580
                                return urlTemplate
581
                                    .replace('{z}', (zOrigin - z).toString())
582
                                    .replace('{x}', x.toString())
583
                                    .replace('{y}', (y).toString());
584
                            },
585
                            wrapX: false
586
                        })
587
                    })
588
                );
589
            }
590
            */
591
            const overlay = this.createMgOverlayLayer(MgBuiltInLayers.Overlay, agentUri, locale, metersPerUnit, projection, mode == MgLayerSetMode.Stateful, mode == MgLayerSetMode.Stateful ? this.dynamicOverlayParams : this.staticOverlayParams);
×
592

593
            let selectionOverlay: OLImageLayer | undefined;
×
594
            let activeSelectedFeatureOverlay: OLImageLayer | undefined;
×
595
            if (mode == MgLayerSetMode.Stateful) {
×
596
                selectionOverlay = this.createMgOverlayLayer(MgBuiltInLayers.SelectionOverlay, agentUri, locale, metersPerUnit, projection, true, this.selectionOverlayParams);
×
597
            }
×
598
            if (mode == MgLayerSetMode.Stateful) {
×
599
                //NOTE: Not tracking this source atm
600
                activeSelectedFeatureOverlay = new ImageLayer({
×
601
                    //OL6: need to specify a source up-front otherwise it will error blindly
602
                    //trying to get a source out of this URL, so set up a source with an empty
603
                    //image data URI, it will be updated if we receive a request to show an
604
                    //active selected feature image
605
                    source: new ImageStaticSource({
×
606
                        imageExtent: extent,
×
607
                        //imageSize: [BLANK_SIZE.w, BLANK_SIZE.h],
608
                        url: BLANK_GIF_DATA_URI
×
609
                    })
×
610
                });
×
611
                activeSelectedFeatureOverlay.set(LayerProperty.LAYER_NAME, MgBuiltInLayers.ActiveFeatureSelectionOverlay);
×
612
                activeSelectedFeatureOverlay.set(LayerProperty.LAYER_DISPLAY_NAME, MgBuiltInLayers.ActiveFeatureSelectionOverlay);
×
613
                activeSelectedFeatureOverlay.set(LayerProperty.LAYER_TYPE, MG_LAYER_TYPE_NAME);
×
614
                activeSelectedFeatureOverlay.set(LayerProperty.IS_EXTERNAL, false)
×
615
                activeSelectedFeatureOverlay.set(LayerProperty.IS_GROUP, false);
×
616
            }
×
617
            let externalBaseLayersGroup: LayerGroup | undefined;
×
618
            //NOTE: Don't bother adding external base layers for overview map as the main map in the
619
            //overview is rendered with GETMAPIMAGE and not GETDYNAMICMAPOVERLAYIMAGE meaning the background
620
            //is opaque and you won't be able to see the base layers underneath anyways.
621
            if (mode != MgLayerSetMode.OverviewMap && externalBaseLayers != null) {
×
622
                const groupOpts: any = {
×
623
                    title: tr("EXTERNAL_BASE_LAYERS", locale),
×
624
                    layers: externalBaseLayers.map(ext => {
×
625
                        const tl = this.createExternalBaseLayer(ext);
×
626
                        return tl;
×
627
                    })
×
628
                };
×
629
                externalBaseLayersGroup = new LayerGroup(groupOpts);
×
630
                externalBaseLayersGroup.set(LayerProperty.LAYER_NAME, MG_BASE_LAYER_GROUP_NAME);
×
631
                externalBaseLayersGroup.set(LayerProperty.LAYER_DISPLAY_NAME, MG_BASE_LAYER_GROUP_NAME);
×
632
                externalBaseLayersGroup.set(LayerProperty.IS_EXTERNAL, false);
×
633
                externalBaseLayersGroup.set(LayerProperty.IS_GROUP, true);
×
634
            }
×
635

636
            debug(`Creating OL view with projection ${projection} and ${resolutions.length} resolutions`);
×
637
            let view: View;
×
638
            if (resolutions.length == 0) {
×
639
                view = new View({
×
640
                    projection: projection
×
641
                });
×
642
            } else {
×
643
                view = new View({
×
644
                    projection: projection,
×
645
                    resolutions: resolutions
×
646
                });
×
647
            }
×
648

649
            const layerSet = new MgLayerSetOL(mgTiledLayers,
×
650
                externalBaseLayersGroup,
×
651
                overlay,
×
652
                projection,
×
653
                dpi,
×
654
                extent as Bounds,
×
655
                inPerUnit,
×
656
                view);
×
657
            layerSet.selectionOverlay = selectionOverlay;
×
658
            layerSet.activeSelectedFeatureOverlay = activeSelectedFeatureOverlay;
×
659
            return layerSet;
×
660
        } else {
×
661
            let projection: ProjectionLike = map?.meta?.projection;
×
662
            let bounds: Bounds | undefined;
×
663
            let externalBaseLayersGroup: LayerGroup | undefined;
×
664
            if (externalBaseLayers != null) {
×
665
                const groupOpts: any = {
×
666
                    title: tr("EXTERNAL_BASE_LAYERS", locale),
×
667
                    layers: externalBaseLayers.map(ext => {
×
668
                        const tl = this.createExternalBaseLayer(ext);
×
669
                        return tl;
×
670
                    })
×
671
                };
×
672
                externalBaseLayersGroup = new LayerGroup(groupOpts);
×
673
                externalBaseLayersGroup.set(LayerProperty.LAYER_NAME, MG_BASE_LAYER_GROUP_NAME);
×
674
                externalBaseLayersGroup.set(LayerProperty.IS_EXTERNAL, false);
×
675
                externalBaseLayersGroup.set(LayerProperty.IS_GROUP, true);
×
676
                //projection = "EPSG:3857";
677
                //bounds = DEFAULT_BOUNDS_3857;
678
            }
×
679
            let subjectLayer;
×
680
            if (map) {
×
681
                subjectLayer = createOLLayerFromSubjectDefn(map, projection, false, appSettings);
×
682
                if (map.meta) {
×
683
                    projection = map.meta.projection;
×
684
                    bounds = map.meta.extents;
×
685
                }
×
686
            }
×
687
            if (!projection && !bounds) {
×
688
                projection = "EPSG:4326";
×
689
                bounds = DEFAULT_BOUNDS_4326;
×
690
            }
×
691

692
            const parsedArb = tryParseArbitraryCs(projection);
×
693
            let metersPerUnit: number | undefined = 1;
×
694
            if (parsedArb) {
×
695
                projection = new Projection({
×
696
                    code: parsedArb.code,
×
697
                    // HACK: Going to take a risk and just jam the values in
NEW
698
                    units: toProjUnit(parsedArb.units) as any,
×
699
                    metersPerUnit: toMetersPerUnit(parsedArb.units),
×
700
                    extent: bounds
×
701
                });
×
702
            } else {
×
703
                metersPerUnit = getMetersPerUnit(projection!);
×
704
            }
×
705
            const view = new View({
×
706
                projection: projection
×
707
            });
×
708
            return new GenericLayerSetOL(view, subjectLayer, bounds!, externalBaseLayersGroup, projection!, metersPerUnit);
×
709
        }
×
710
    }
×
711
    private createExternalBaseLayer(ext: IExternalBaseLayer) {
×
712
        const extSource = createExternalSource(ext);
×
713
        if (extSource instanceof UrlTile) {
×
714
            const loaders = this.callback.getBaseTileLoaders();
×
715
            if (loaders[ext.name])
×
716
                extSource.setTileLoadFunction(loaders[ext.name]);
×
717
        }
×
718
        const options: any = {
×
719
            title: ext.name,
×
720
            type: "base",
×
721
            visible: ext.visible === true,
×
722
            source: extSource
×
723
        };
×
724
        const tl = new TileLayer(options);
×
725
        tl.set(LayerProperty.LAYER_TYPE, ext.kind);
×
726
        tl.set(LayerProperty.LAYER_NAME, ext.name);
×
727
        tl.set(LayerProperty.LAYER_DISPLAY_NAME, ext.name);
×
728
        tl.set(LayerProperty.IS_EXTERNAL, false);
×
729
        tl.set(LayerProperty.IS_GROUP, false);
×
730
        return tl;
×
731
    }
×
732
    private createMgOverlayLayer(layerName: string, agentUri: string, locale: string | undefined, metersPerUnit: number, projection: ProjectionLike, useImageOverlayOp: boolean, params: any): OLImageLayer {
×
733
        const overlaySource = createMapGuideSource({
×
734
            projection: projection,
×
735
            url: agentUri,
×
736
            useOverlay: useImageOverlayOp,
×
737
            metersPerUnit: metersPerUnit,
×
738
            params: params,
×
739
            ratio: 1,
×
740
            // For mobile devices with retina/hidpi displays, the default 96 DPI produces
741
            // really low quality map images. For such devices, the DPI should be some
742
            // function of the device pixel ratio reported. As this value can be fractional
743
            // round it down to the nearest integer
744
            //
745
            // UPDATE 18/07/2023: But cap it to a minimum of 1, otherwise a sub-1 ratio floors to 0, making the
746
            // final DPI 0, which breaks everything
747
            displayDpi: Math.max(Math.floor(olHas.DEVICE_PIXEL_RATIO), 1) * 96
×
748
        });
×
749
        overlaySource.setAttributions(tr("PBMG", locale ?? DEFAULT_LOCALE));
×
750
        const layer = new ImageLayer({
×
751
            //name: "MapGuide Dynamic Overlay",
752
            source: overlaySource
×
753
        });
×
754
        layer.set(LayerProperty.LAYER_NAME, layerName);
×
755
        layer.set(LayerProperty.LAYER_DISPLAY_NAME, layerName);
×
756
        layer.set(LayerProperty.LAYER_TYPE, MG_LAYER_TYPE_NAME);
×
757
        layer.set(LayerProperty.IS_EXTERNAL, false);
×
758
        layer.set(LayerProperty.IS_GROUP, false);
×
759
        return layer;
×
760
    }
×
761
}
×
762

763
export interface IMgLayerSetProps {
764
    /**
765
     * @since 0.14 This property can now also be a IGenericSubjectMapLayer
766
     */
767
    map: RuntimeMap | IGenericSubjectMapLayer;
768
    /**
769
     * Use stateless GETMAP requests for map rendering
770
     * @since 0.14
771
     */
772
    stateless?: boolean;
773
    imageFormat: ImageFormat;
774
    selectionImageFormat?: ImageFormat;
775
    selectionColor?: string;
776
    /**
777
     * @since 0.14 Made optional
778
     */
779
    agentUri?: string;
780
    externalBaseLayers?: IExternalBaseLayer[];
781
    locale?: string;
782
    /**
783
     * @since 0.14
784
     */
785
    appSettings: Dictionary<string>;
786
}
787

788
export interface IMgLayerSetCallback extends IImageLayerEvents {
789
    getMockMode(): MapGuideMockMode | undefined;
790
    incrementBusyWorker(): void;
791
    decrementBusyWorker(): void;
792
    onSessionExpired(): void;
793
    getSelectableLayers(): string[];
794
    getClient(): Client;
795
    isContextMenuOpen(): boolean;
796
    getAgentUri(): string;
797
    getAgentKind(): ClientKind;
798
    getMapName(): string;
799
    getSessionId(): string;
800
    isFeatureTooltipEnabled(): boolean;
801
    getPointSelectionBox(point: Coordinate2D): Bounds;
802
    openTooltipLink(url: string): void;
803
    addFeatureToHighlight(feat: Feature<Geometry> | undefined, bAppend: boolean): void;
804
}
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