• 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-manager.ts
1
import { LayerProperty, ILayerManager, ILayerInfo, IParseFeaturesFromFileOptions, IAddLayerFromParsedFeaturesOptions, SourceProperty, IWmsLayerExtensions, LayerExtensions, Dictionary } from './common';
×
2
import olSourceVector from "ol/source/Vector";
×
3
import olMap from "ol/Map";
4
import olLayerBase from "ol/layer/Base";
5
import olPoint from "ol/geom/Point";
6
import { setOLVectorLayerStyle } from './ol-style-helpers';
×
7
import olTileLayer from "ol/layer/Tile";
×
8
import olImageLayer from "ol/layer/Image";
×
9
import olWmsSource from "ol/source/ImageWMS";
×
10
import olTileWmsSource from "ol/source/TileWMS";
×
11
import olVectorLayer from "ol/layer/Vector";
×
12
import olHeatmapLayer from "ol/layer/Heatmap";
×
13
import { getFormatDrivers } from './layer-manager/driver-registry';
×
14
import { IFormatDriver } from './layer-manager/format-driver';
15
import { tr } from './i18n';
×
16
import { IParsedFeatures } from './layer-manager/parsed-features';
17
import { LayerSetGroupBase } from './layer-set-group-base';
18
import { IInitialExternalLayer } from '../actions/defs';
19
import { IVectorLayerStyle, DEFAULT_VECTOR_LAYER_STYLE, IClusterSettings, ClusterClickAction, DEFAULT_CLUSTERED_LAYER_STYLE, IBasicVectorPointStyle, IBasicVectorPolygonStyle, IBasicVectorLineStyle, IVectorLabelSettings, ExprOr, IVectorFeatureStyle, IHeatmapSettings } from './ol-style-contracts';
×
20
import { OLStyleMapSet } from './ol-style-map-set';
21
import { clusterSourceIfRequired } from '../components/external-layer-factory';
×
22
import colorbrewer from "colorbrewer";
×
23
import { strIsNullOrEmpty } from '../utils/string';
×
24
import { getMaxRamp } from '../components/layer-manager/color-brewer';
×
25

26
function cloneObject<T>(obj: T) {
×
27
    return JSON.parse(JSON.stringify(obj)) as T;
×
28
}
×
29

30
function clonePointWithFill(baseTemplate: IBasicVectorPointStyle | undefined, fillColor: string) {
×
31
    if (!baseTemplate) {
×
32
        return undefined;
×
33
    }
×
34
    const clone = cloneObject(baseTemplate);
×
35
    switch (clone.type) {
×
36
        case "Circle":
×
37
            clone.fill.color = fillColor;
×
38
            break;
×
39
    }
×
40
    return clone;
×
41
}
×
42
function cloneLineWithFill(baseTemplate: IBasicVectorLineStyle | undefined, fillColor: string) {
×
43
    if (!baseTemplate) {
×
44
        return undefined;
×
45
    }
×
46
    const clone = cloneObject(baseTemplate);
×
47
    clone.color = fillColor;
×
48
    return clone;
×
49
}
×
50
function clonePolyWithFill(baseTemplate: IBasicVectorPolygonStyle | undefined, fillColor: string) {
×
51
    if (!baseTemplate) {
×
52
        return undefined;
×
53
    }
×
54
    const clone = cloneObject(baseTemplate);
×
55
    clone.fill.color = fillColor;
×
56
    return clone;
×
57
}
×
58
function ensureLabelText(style: IVectorLabelSettings, expr: ExprOr<string>, isLine: boolean = false) {
×
59
    if (!style.label) {
×
60
        style.label = {
×
61
            text: expr,
×
62
            textAlign: "left",
×
63
            offsetX: 15,
×
64
            fill: {
×
65
                color: "#000000",
×
66
                alpha: 255
×
67
            },
×
68
            stroke: {
×
69
                color: "#ffffff",
×
70
                alpha: 255,
×
71
                width: 3
×
72
            }
×
73
        };
×
74
        if (isLine) {
×
75
            style.label.placement = "line";
×
76
        }
×
77
    } else {
×
78
        style.label.text = expr;
×
79
    }
×
80
}
×
81
function ensureLabelTextForStyle(fstyle: IVectorFeatureStyle, expr: ExprOr<string>) {
×
82
    if (fstyle.line) {
×
83
        ensureLabelText(fstyle.line, expr, true);
×
84
    }
×
85
    if (fstyle.point) {
×
86
        ensureLabelText(fstyle.point, expr);
×
87
    }
×
88
    if (fstyle.polygon) {
×
89
        ensureLabelText(fstyle.polygon, expr);
×
90
    }
×
91
}
×
92

93
export function getLayerInfo(layer: olLayerBase, isExternal: boolean): ILayerInfo {
×
94
    let vectorStyle: IVectorLayerStyle | undefined;
×
95
    let cs: IClusterSettings | undefined;
×
96
    let ext: LayerExtensions | undefined;
×
97
    let hs: IHeatmapSettings | undefined;
×
98
    if (layer instanceof olImageLayer || layer instanceof olTileLayer) {
×
99
        const source = layer.getSource();
×
100
        if (layer.get(LayerProperty.HAS_WMS_LEGEND) == true && (source instanceof olWmsSource || source instanceof olTileWmsSource)) {
×
101
            ext = {
×
102
                type: "WMS",
×
103
                getLegendUrl: (res?: number) => source.getLegendUrl(res)
×
104
            } as IWmsLayerExtensions;
×
105
        }
×
106
    }
×
107
    if (layer instanceof olVectorLayer) {
×
108
        const vs: OLStyleMapSet | undefined = layer.get(LayerProperty.VECTOR_STYLE);
×
109
        if (vs) {
×
110
            vectorStyle = vs.toVectorLayerStyle();
×
111
            cs = vs.toClusterSettings();
×
112
        }
×
113
    }
×
114
    if (layer instanceof olHeatmapLayer) {
×
NEW
115
        const blurExpr = layer.getBlur();
×
NEW
116
        const radiusExpr = layer.getRadius();
×
NEW
117
        if (!Array.isArray(blurExpr) && !Array.isArray(radiusExpr)) {
×
NEW
118
            hs = {
×
NEW
119
                blur: blurExpr,
×
NEW
120
                radius: radiusExpr
×
NEW
121
            };
×
NEW
122
        } else {
×
NEW
123
            if (Array.isArray(blurExpr)) {
×
NEW
124
                console.warn("Don't know how to evaluate blur", blurExpr);
×
NEW
125
            }
×
NEW
126
            if (Array.isArray(radiusExpr)) {
×
NEW
127
                console.warn("Don't know how to evaluate radius", radiusExpr);
×
NEW
128
            }
×
NEW
129
        }
×
130
    }
×
131
    return {
×
132
        visible: layer.getVisible(),
×
133
        selectable: layer.get(LayerProperty.IS_SELECTABLE) == true,
×
134
        name: layer.get(LayerProperty.LAYER_NAME),
×
135
        displayName: layer.get(LayerProperty.LAYER_DISPLAY_NAME) ?? layer.get(LayerProperty.LAYER_NAME),
×
136
        description: layer.get(LayerProperty.LAYER_DESCRIPTION),
×
137
        type: layer.get(LayerProperty.LAYER_TYPE),
×
138
        opacity: layer.getOpacity(),
×
139
        isExternal: isExternal,
×
140
        extensions: ext,
×
141
        vectorStyle,
×
142
        cluster: cs,
×
143
        heatmap: hs,
×
144
        busyWorkerCount: layer.get(LayerProperty.BUSY_WORKER_COUNT) ?? 0,
×
145
        metadata: layer.get(LayerProperty.LAYER_METADATA)
×
146
    }
×
147
}
×
148

149
export class LayerManager implements ILayerManager {
×
150
    private _olFormats: IFormatDriver[];
151
    constructor(private map: olMap, private layerSet: LayerSetGroupBase) {
×
152
        this._olFormats = getFormatDrivers();
×
153
    }
×
154
    tryGetSubjectLayer(): olLayerBase | undefined {
×
155
        return this.layerSet.tryGetSubjectLayer();
×
156
    }
×
157
    /**
158
     * INTERNAL API
159
     * @param {IInitialExternalLayer} extLayer
160
     * @returns
161
     * @memberof LayerManager
162
     */
163
    addExternalLayer(extLayer: IInitialExternalLayer, onlyAddIfNotExists: boolean, appSettings: Dictionary<string>) {
×
164
        if (onlyAddIfNotExists && this.hasLayer(extLayer.name)) {
×
165
            return undefined;
×
166
        }
×
167
        return this.layerSet.addExternalLayer(this.map, extLayer, appSettings);
×
168
    }
×
169
    getLayers(): ILayerInfo[] {
×
170
        return this.layerSet.getCustomLayers(this.map);
×
171
    }
×
172
    hasLayer(name: string): boolean {
×
173
        return this.layerSet.hasLayer(name);
×
174
    }
×
175
    addLayer<T extends olLayerBase>(name: string, layer: T, allowReplace?: boolean | undefined): ILayerInfo {
×
176
        return this.layerSet.addLayer(this.map, name, layer, allowReplace);
×
177
    }
×
178
    removeLayer(name: string): olLayerBase | undefined {
×
179
        return this.layerSet.removeLayer(this.map, name);
×
180
    }
×
181
    getLayer<T extends olLayerBase>(name: string): T | undefined {
×
182
        return this.layerSet.getLayer(name);
×
183
    }
×
184
    apply(layers: ILayerInfo[]): void {
×
185
        this.layerSet.apply(this.map, layers);
×
186
    }
×
187
    parseFeaturesFromFile(options: IParseFeaturesFromFileOptions): Promise<IParsedFeatures> {
×
188
        const { file, name: layerName, locale } = options;
×
189
        const that = this;
×
190
        return new Promise((resolve, reject) => {
×
191
            const reader = new FileReader();
×
192
            const handler = async function (e: ProgressEvent<FileReader>) {
×
193
                const result = e.target?.result;
×
194
                if (result && typeof (result) == 'string') {
×
195
                    const formats = that._olFormats;
×
196
                    if (formats.length == 0) {
×
197
                        reject(new Error(tr("ADD_LOCAL_FILE_LAYER_FAILURE_NO_FORMATS", locale)));
×
198
                    }
×
199
                    let loadedType: IParsedFeatures | undefined;
×
200
                    let bLoaded = false;
×
201
                    for (let i = 0, ii = formats.length; i < ii; ++i) {
×
202
                        const format = formats[i];
×
203
                        try {
×
204
                            loadedType = await format.tryParse(file.size, result);
×
205
                        } catch (e) {
×
206
                            //console.log(e);
207
                        }
×
208
                        if (loadedType && loadedType.hasFeatures()) {
×
209
                            loadedType.name = layerName;
×
210
                            bLoaded = true;
×
211
                            break;
×
212
                        }
×
213
                    }
×
214
                    if (bLoaded && loadedType) {
×
215
                        resolve(loadedType);
×
216
                    } else {
×
217
                        reject(new Error(tr("ADD_LOCAL_FILE_LAYER_FAILURE", locale)));
×
218
                    }
×
219
                } else {
×
220
                    reject(new Error(tr("ADD_LOCAL_FILE_LAYER_FAILURE_NOT_TEXT", locale)));
×
221
                }
×
222
            };
×
223
            reader.addEventListener("load", handler);
×
224
            reader.readAsText(file);
×
225
        });
×
226
    }
×
227
    async addLayerFromParsedFeatures(options: IAddLayerFromParsedFeaturesOptions): Promise<ILayerInfo> {
×
228
        const { features, projection, defaultStyle, extraOptions, labelOnProperty, selectedPopupTemplate, metadata, defn } = options;
×
229

230
        let proj = projection;
×
231
        if (!proj) {
×
232
            const view = this.map.getView();
×
233
            proj = view.getProjection();
×
234
        }
×
235
        const source = new olSourceVector();
×
236
        source.set(SourceProperty.SUPPRESS_LOAD_EVENTS, true);
×
237

238
        let csArgs;
×
239
        if (extraOptions?.kind == "Cluster") {
×
240
            csArgs = {
×
241
                distance: extraOptions.clusterDistance
×
242
            };
×
243
        }
×
244
        let layer: olLayerBase;
×
245
        if (extraOptions?.kind == "Heatmap") {
×
246
            layer = new olHeatmapLayer({
×
NEW
247
                source: source as olSourceVector,
×
248
                weight: extraOptions.weightProperty
×
249
            });
×
250
        } else {
×
251
            layer = new olVectorLayer({
×
252
                source: clusterSourceIfRequired(source, { cluster: csArgs }),
×
253
                className: "external-vector-layer", //This is to avoid false positives for map.forEachLayerAtPixel
×
254
                declutter: true
×
255
            });
×
256
        }
×
257
        await features.addTo(source, this.map.getView().getProjection(), proj);
×
258
        layer.set(LayerProperty.LAYER_NAME, features.name);
×
259
        layer.set(LayerProperty.LAYER_DISPLAY_NAME, features.name);
×
260
        layer.set(LayerProperty.LAYER_TYPE, features.type);
×
261
        layer.set(LayerProperty.LAYER_DEFN, defn);
×
262
        if (extraOptions?.kind == "Heatmap") {
×
263
            layer.set(LayerProperty.IS_HEATMAP, true);
×
264
        } else {
×
265
            layer.set(LayerProperty.IS_SELECTABLE, true);
×
266
        }
×
267
        layer.set(LayerProperty.IS_EXTERNAL, true);
×
268
        layer.set(LayerProperty.IS_GROUP, false);
×
269
        if (metadata) {
×
270
            layer.set(LayerProperty.LAYER_METADATA, metadata);
×
271
        }
×
272
        if (selectedPopupTemplate) {
×
273
            layer.set(LayerProperty.SELECTED_POPUP_CONFIGURATION, selectedPopupTemplate);
×
274
        }
×
275

276
        let clusterSettings: IClusterSettings | undefined;
×
277
        if (extraOptions?.kind == "Cluster") {
×
278
            clusterSettings = {
×
279
                distance: extraOptions.clusterDistance,
×
280
                onClick: extraOptions.onClusterClickAction ?? ClusterClickAction.ShowPopup,
×
281
                style: cloneObject(extraOptions.clusterStyle ?? defaultStyle ?? DEFAULT_CLUSTERED_LAYER_STYLE)
×
282
            };
×
283
            if (!strIsNullOrEmpty(labelOnProperty)) {
×
284
                for (const k in clusterSettings.style) {
×
285
                    ensureLabelTextForStyle(clusterSettings.style[k], { expr: `if (arr_size(features) == 1, feat_property(features[0], '${labelOnProperty}'), '')` });
×
286
                }
×
287
            }
×
288
        }
×
289
        // Delete irrelevant styles based on geometry types encountered
290
        const bStyle: IVectorLayerStyle = defaultStyle ?? cloneObject(DEFAULT_VECTOR_LAYER_STYLE);
×
291
        if (!features.geometryTypes.includes("Point")) {
×
292
            delete bStyle.default.point;
×
293
            delete clusterSettings?.style.default.point;
×
294
        }
×
295
        if (!features.geometryTypes.includes("LineString")) {
×
296
            delete bStyle.default.line;
×
297
            delete clusterSettings?.style.default.line;
×
298
        }
×
299
        if (!features.geometryTypes.includes("Polygon")) {
×
300
            delete bStyle.default.polygon;
×
301
            delete clusterSettings?.style.default.polygon;
×
302
        }
×
303

304
        if (!strIsNullOrEmpty(labelOnProperty)) {
×
305
            ensureLabelTextForStyle(bStyle.default, { expr: labelOnProperty });
×
306
        }
×
307

308
        if (extraOptions?.kind == "Theme") {
×
309
            const values = await features.getDistinctValues(extraOptions.themeOnProperty);
×
310
            let baseTemplatePoint = bStyle.default.point;
×
311
            let baseTemplateLine = bStyle.default.line;
×
312
            let baseTemplatePoly = bStyle.default.polygon;
×
313

314
            const th = extraOptions.colorBrewerTheme;
×
315
            let ramp = colorbrewer[th];
×
316
            if (!ramp) {
×
317
                ramp = colorbrewer.Blues;
×
318
            }
×
319
            const chosenRamp = getMaxRamp(ramp);
×
320
            const ruleCount = Math.min(values.length, chosenRamp.length);
×
321
            const palette = ramp[ruleCount];
×
322
            for (let i = 0; i < ruleCount; i++) {
×
323
                const v = values[i];
×
324
                const filter = `${extraOptions.themeOnProperty} == '${v}'`;
×
325
                const style = {
×
326
                    label: v,
×
327
                    point: clonePointWithFill(baseTemplatePoint, palette[i]),
×
328
                    line: cloneLineWithFill(baseTemplateLine, palette[i]),
×
329
                    polygon: clonePolyWithFill(baseTemplatePoly, palette[i]),
×
330
                };
×
331
                if (!strIsNullOrEmpty(labelOnProperty)) {
×
332
                    ensureLabelTextForStyle(style, { expr: labelOnProperty });
×
333
                }
×
334
                (bStyle as any)[filter] = style;
×
335
            }
×
336
        }
×
337
        if (layer instanceof olVectorLayer) {
×
338
            setOLVectorLayerStyle(layer, bStyle, clusterSettings);
×
339
        }
×
340
        const layerInfo = this.addLayer(features.name, layer);
×
341
        return layerInfo;
×
342
    }
×
343
}
×
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