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

compassinformatics / cpsi-mapview / 15022980938

14 May 2025 02:11PM UTC coverage: 26.333% (+0.04%) from 26.29%
15022980938

push

github

geographika
Move describe to test globals

492 of 2344 branches covered (20.99%)

Branch coverage included in aggregate %.

1464 of 5084 relevant lines covered (28.8%)

1.17 hits per line

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

36.12
/app/controller/button/SpatialQueryButtonController.js
1
/**
2
 * This class is the controller for the button 'SpatialQueryButton'
3
 */
4
Ext.define('CpsiMapview.controller.button.SpatialQueryButtonController', {
1✔
5
    extend: 'Ext.app.ViewController',
6

7
    requires: ['BasiGX.util.Layer'],
8

9
    alias: 'controller.cmv_spatial_query_btn',
10

11
    /**
12
     * The {ol.interaction.Draw} used to draw the geometry used in the spatial
13
     * query
14
     * @property{ol.interaction.Draw}
15
     */
16
    drawQueryInteraction: null,
17

18
    /**
19
     * The {ol.interaction.Modify} used to modify the geometry used in the spatial
20
     * query
21
     * @property{ol.interaction.Modify}
22
     */
23
    modifiyQueryInteraction: null,
24

25
    /**
26
     * The {ol.interaction.Snap} used to snap to the points of the geometry used
27
     * in the spatial query
28
     * @property{ol.interaction.Snap}
29
     */
30
    snapQueryInteraction: null,
31

32
    /**
33
     * The layer that contains the geometry used in the spatial query, if
34
     * displayPermanently is set to true
35
     * @property{ol.layer.Vector}
36
     */
37
    permanentLayer: null,
38

39
    /**
40
     * Flag indicating if the layer was created directly by the tool or
41
     * is an existing layer passed as a configuration option
42
     * @property {boolean}
43
     */
44
    permanentLayerCreatedByTool: false,
45

46
    /**
47
     * The OpenLayers map. If not given, will be auto-detected
48
     */
49
    map: null,
50

51
    /**
52
     * The BasiGX mapComponent. If not given, will be auto-detected
53
     */
54
    mapComponent: null,
55

56
    constructor: function () {
57
        const me = this;
12✔
58
        me.getFeaturesFromSourceAndTriggerWfs =
12✔
59
            me.getFeaturesFromSourceAndTriggerWfs.bind(me);
60
        me.getGeometryFromPolygonAndTriggerWfs =
12✔
61
            me.getGeometryFromPolygonAndTriggerWfs.bind(me);
62
        me.onQueryLayerVisibilityChange =
12✔
63
            me.onQueryLayerVisibilityChange.bind(me);
64
        me.callParent(arguments);
12✔
65
    },
66

67
    /**
68
     * Function to determine the query layer if not yet defined in class
69
     */
70
    findQueryLayer: function () {
71
        const me = this;
6✔
72
        const view = me.getView();
6✔
73
        if (!view.queryLayer && view.queryLayerName) {
6!
74
            view.queryLayer = BasiGX.util.Layer.getLayerByName(
×
75
                view.queryLayerName
76
            );
77
        }
78

79
        if (!view.queryLayer) {
6!
80
            Ext.Logger.warn(
6✔
81
                'No queryLayer found in the map for the SpatialQueryButton with the name: ' +
82
                    view.queryLayerName
83
            );
84
        }
85
    },
86

87
    /**
88
     * Activates #drawQueryInteraction on button toggle to draw polygon
89
     * selection geometry that will be used for filtering.
90
     *
91
     * @param {Ext.button.Button} btn The toggled select by polygon button.
92
     * @param {Boolean} pressed The toggle state.
93
     */
94
    onSpatialQueryBtnToggle: function (btn, pressed) {
95
        const me = this;
6✔
96
        const view = me.getView();
6✔
97

98
        if (view.map && view.map instanceof ol.Map) {
6!
99
            me.map = view.map;
×
100
        } else {
101
            // guess map as fallback
102
            me.map = BasiGX.util.Map.getMapComponent().map;
6✔
103
        }
104

105
        if (!view.queryLayer) {
6!
106
            me.findQueryLayer();
6✔
107
        }
108

109
        let geometryFunction;
110
        let type = view.drawGeometryType;
6✔
111
        if (view.spatialOperator === 'bbox') {
6!
112
            type = 'Circle';
×
113
            geometryFunction = ol.interaction.Draw.createBox();
×
114
        }
115

116
        const vectorLayerKey = view.getVectorLayerKey();
6✔
117
        me.permanentLayer =
6✔
118
            CpsiMapview.view.button.SpatialQueryButton.findAssociatedPermanentLayer(
119
                me.map,
120
                vectorLayerKey
121
            );
122
        if (me.permanentLayer === undefined) {
6!
123
            me.permanentLayerCreatedByTool = true; // add flag indicating the tool will handle the destruction of the layer
6✔
124
            me.permanentLayer = new ol.layer.Vector({
6✔
125
                source: new ol.source.Vector()
126
            });
127
            me.permanentLayer.set('associatedLayerKey', vectorLayerKey);
6✔
128
            me.permanentLayer.set('isSpatialQueryLayer', true);
6✔
129
            me.permanentLayer.set('name', vectorLayerKey + '_spatialfilter');
6✔
130
            // connect hide and show to query layer hide and show
131
            me.connectQueryLayer();
6✔
132
        }
133

134
        const permanentLayerSource = me.permanentLayer.getSource();
6✔
135
        if (!me.drawQueryInteraction) {
6!
136
            if (view.displayPermanently) {
6!
137
                me.map.addLayer(me.permanentLayer);
6✔
138
                me.drawQueryInteraction = new ol.interaction.Draw({
6✔
139
                    source: permanentLayerSource,
140
                    geometryFunction: geometryFunction,
141
                    type: type
142
                });
143

144
                me.modifiyQueryInteraction = new ol.interaction.Modify({
6✔
145
                    source: permanentLayerSource
146
                });
147
                me.snapQueryInteraction = new ol.interaction.Snap({
6✔
148
                    source: permanentLayerSource
149
                });
150
                me.map.addInteraction(me.modifiyQueryInteraction);
6✔
151
                me.map.addInteraction(me.snapQueryInteraction);
6✔
152
            } else {
153
                me.drawQueryInteraction = new ol.interaction.Draw({
×
154
                    features: view.queryFeatures,
155
                    geometryFunction: geometryFunction,
156
                    type: type
157
                });
158
            }
159
            me.map.addInteraction(me.drawQueryInteraction);
6✔
160
        }
161
        if (pressed) {
6!
162
            me.drawQueryInteraction.setActive(true);
×
163
            me.map
×
164
                .getViewport()
165
                .addEventListener('contextmenu', me.contextHandler);
166
            if (view.displayPermanently) {
×
167
                me.modifiyQueryInteraction.setActive(true);
×
168
                me.snapQueryInteraction.setActive(true);
×
169
                me.drawQueryInteraction.on(
×
170
                    'drawend',
171
                    me.getFeaturesFromSourceAndTriggerWfs
172
                );
173
                me.modifiyQueryInteraction.on(
×
174
                    'modifyend',
175
                    me.getFeaturesFromSourceAndTriggerWfs
176
                );
177
            } else {
178
                view.queryFeatures.on(
×
179
                    'add',
180
                    me.getGeometryFromPolygonAndTriggerWfs
181
                );
182
            }
183
        } else {
184
            me.drawQueryInteraction.setActive(false);
6✔
185
            me.map
6✔
186
                .getViewport()
187
                .removeEventListener('contextmenu', me.contextHandler);
188
            if (view.displayPermanently) {
6!
189
                me.modifiyQueryInteraction.setActive(false);
6✔
190
                me.snapQueryInteraction.setActive(false);
6✔
191
                me.drawQueryInteraction.un(
6✔
192
                    'drawend',
193
                    me.getFeaturesFromSourceAndTriggerWfs
194
                );
195
                me.modifiyQueryInteraction.un(
6✔
196
                    'modifyend',
197
                    me.getFeaturesFromSourceAndTriggerWfs
198
                );
199
            } else {
200
                view.queryFeatures.un(
×
201
                    'add',
202
                    me.getGeometryFromPolygonAndTriggerWfs
203
                );
204
            }
205
        }
206
    },
207

208
    /**
209
     * Method shows the context menu on mouse right click
210
     * @param {Event} evt The browser event
211
     */
212
    showContextMenu: function (evt) {
213
        // suppress default browser behaviour
214
        evt.preventDefault();
×
215

216
        const me = this.scope;
×
217

218
        const menu = Ext.create('Ext.menu.Menu', {
×
219
            width: 100,
220
            plain: true,
221
            renderTo: Ext.getBody(),
222
            items: [
223
                {
224
                    text: 'Clear Feature',
225
                    scope: me,
226
                    handler: function () {
227
                        const view = me.getView();
×
228
                        // remove the spatial filter on the layer by firing an event
229
                        view.fireEvent('cmv-spatial-query-filter', null);
×
230
                        // now remove the polygon from the layer
231
                        me.onClearAssociatedPermanentLayer();
×
232
                    }
233
                },
234
                {
235
                    text: 'Cancel Drawing',
236
                    scope: me,
237
                    handler: function () {
238
                        me.drawQueryInteraction.abortDrawing();
×
239
                    }
240
                }
241
            ]
242
        });
243
        menu.showAt(evt.pageX, evt.pageY);
×
244
    },
245
    /**
246
     * Connects the change:visible event of the query layer
247
     * to the permanent layer. Thereby, when the query layer
248
     * visibility is changed, the visibility of the permanent layer
249
     * changes accordingly.
250
     */
251
    connectQueryLayer: function () {
252
        const me = this;
6✔
253
        const view = me.getView();
6✔
254
        const layerKey = view.getVectorLayerKey();
6✔
255
        if (!layerKey) {
6!
256
            return;
6✔
257
        }
258
        if (view.queryLayer) {
×
259
            view.queryLayer.on(
×
260
                'change:visible',
261
                me.onQueryLayerVisibilityChange
262
            );
263
        }
264
    },
265

266
    /**
267
     * Event handler for the change:visible event of the
268
     * query layer.
269
     * @param {ol.Object.event} evt change:visible event of layer
270
     */
271
    onQueryLayerVisibilityChange: function (evt) {
272
        const me = this;
×
273
        const visible = evt.target.getVisible();
×
274
        if (visible) {
×
275
            me.onShowAssociatedPermanentLayer();
×
276
        } else {
277
            me.onHideAssociatedPermanentLayer();
×
278
        }
279
    },
280

281
    /**
282
     * Creates a Filter object from the passed geometry and queryLayer
283
     *
284
     * @param  {ol.geom.Geometry} geometry The geometry
285
     * @return {Ext.util.Filter}       A filter spatial
286
     * @private
287
     */
288
    createSpatialFilter: function (geometry) {
289
        const me = this;
1✔
290
        let filter = null;
1✔
291

292
        const view = me.getView();
1✔
293
        if (!view.queryLayer) {
1!
294
            return;
1✔
295
        }
296

297
        const mapComp = me.mapComponent || BasiGX.util.Map.getMapComponent();
×
298
        const projString = mapComp.getMap().getView().getProjection().getCode();
×
299
        const geomFieldName =
300
            view.queryLayer.get('geomFieldName') ||
×
301
            view.queryLayer.getSource().get('geomFieldName') ||
302
            'the_geom';
303

304
        if (!Ext.isEmpty(geometry)) {
×
305
            filter = GeoExt.util.OGCFilter.createSpatialFilter(
×
306
                view.spatialOperator,
307
                geomFieldName,
308
                geometry,
309
                projString
310
            );
311
        }
312

313
        return filter;
×
314
    },
315

316
    /**
317
     * Handles the modifyend and drawend events of the draw layer
318
     * @param {ol.Object.event} evt ol modifyend or drawend event
319
     */
320
    getFeaturesFromSourceAndTriggerWfs: function (evt) {
321
        const me = this;
×
322
        let feature;
323
        if (evt.type === 'modifyend') {
×
324
            // We expect to only have one existing feature in source.
325
            // In case multiple features exist, we only use the one
326
            // that was added last
327
            feature = evt.features.getArray()[evt.features.getLength() - 1];
×
328
        } else if (evt.type === 'drawend') {
×
329
            // clear previously drawn features so that only one feature exists
330
            me.permanentLayer.getSource().clear();
×
331
            feature = evt.feature;
×
332
        }
333
        const fakeEvent = { element: feature };
×
334
        me.getGeometryFromPolygonAndTriggerWfs(fakeEvent);
×
335
    },
336

337
    /**
338
     * Helper method to create a polygon geometry from drawn irregular polygon.
339
     *
340
     * @param {Ext.Event} evt The add-Event containing drawn feature
341
     */
342
    getGeometryFromPolygonAndTriggerWfs: function (evt) {
343
        const me = this;
×
344
        const geometry = evt.element.getGeometry();
×
345
        const view = me.getView();
×
346

347
        const filter = me.createSpatialFilter(geometry);
×
348
        view.fireEvent('cmv-spatial-query-filter', filter);
×
349
        if (view.triggerWfsRequest === true) {
×
350
            this.buildAndRequestQuery(geometry);
×
351
        }
352
    },
353

354
    /**
355
     * Build query / filter and call WFS
356
     *
357
     * @param {ol.geom.Geometry} geometry The geometry
358
     */
359
    buildAndRequestQuery: function (geometry) {
360
        const me = this;
×
361
        const view = me.getView();
×
362
        if (!view.queryLayer) {
×
363
            return;
×
364
        }
365

366
        const mapComp = me.mapComponent || BasiGX.util.Map.getMapComponent();
×
367

368
        const projString = mapComp.getMap().getView().getProjection().getCode();
×
369
        const geomFieldName =
370
            view.queryLayer.get('geomFieldName') ||
×
371
            view.queryLayer.getSource().get('geomFieldName') ||
372
            'the_geom';
373
        const url =
374
            view.queryLayer.get('url') ||
×
375
            view.queryLayer.getSource().getUrl() ||
376
            view.queryLayer.getSource().getUrls()[0];
377

378
        const featureType =
379
            view.queryLayer.get('featureType') ||
×
380
            BasiGX.util.Object.layersFromParams(
381
                view.queryLayer.getSource().getParams()
382
            );
383

384
        if (!Ext.isEmpty(geometry)) {
×
385
            const filter = GeoExt.util.OGCFilter.getOgcFilter(
×
386
                geomFieldName,
387
                view.spatialOperator,
388
                geometry,
389
                '1.1.0',
390
                projString
391
            );
392

393
            mapComp.setLoading(true);
×
394
            BasiGX.util.WFS.executeWfsGetFeature(
×
395
                url,
396
                view.queryLayer,
397
                projString,
398
                null,
399
                geomFieldName,
400
                filter,
401
                null,
402
                me.onWfsExecuteSuccess,
403
                me.onWfsExecuteFailure,
404
                me,
405
                null,
406
                featureType
407
            );
408
        }
409
    },
410

411
    /**
412
     * Handle a successful WFS request.
413
     *
414
     * @param {XMLHttpRequest.response} response The response of the AJAX call.
415
     */
416
    onWfsExecuteSuccess: function (response) {
417
        const me = this;
×
418
        const view = me.getView();
×
419
        const mapComp = me.mapComponent || BasiGX.util.Map.getMapComponent();
×
420
        mapComp.setLoading(false);
×
421
        const wfsResponse = response.responseText;
×
422
        if (wfsResponse.indexOf('Exception') > 0) {
×
423
            // something got wrong and we probably have an exception, that we
424
            // try to handle...
425
            BasiGX.util.WFS.handleWfsExecuteException(wfsResponse);
×
426
            view.fireEvent('cmv-spatial-query-error', wfsResponse);
×
427
        } else {
428
            const decodedResponse = Ext.decode(wfsResponse);
×
429
            view.fireEvent('cmv-spatial-query-success', decodedResponse);
×
430
        }
431
    },
432

433
    /**
434
     * Handle WFS GetFeature failure.
435
     *
436
     * @param {XMLHttpRequest.response} response The response of the AJAX call.
437
     */
438
    onWfsExecuteFailure: function (response) {
439
        const me = this;
×
440
        const view = me.getView();
×
441
        let responseTxt;
442
        if (response && response.responseText) {
×
443
            responseTxt = response.responseText;
×
444
        }
445
        const mapComp = me.mapComponent || BasiGX.util.Map.getMapComponent();
×
446
        mapComp.setLoading(false);
×
447
        view.fireEvent('cmv-spatial-query-error', responseTxt);
×
448
    },
449

450
    /**
451
     * Handles clearing the permanentLayer of the instance.
452
     */
453
    onClearAssociatedPermanentLayer: function () {
454
        const me = this;
1✔
455
        const layerKey = me.getView().getVectorLayerKey();
1✔
456
        if (!me.map) {
1!
457
            return;
1✔
458
        }
459
        if (!layerKey) {
×
460
            return;
×
461
        }
462
        CpsiMapview.view.button.SpatialQueryButton.clearAssociatedPermanentLayer(
×
463
            me.map,
464
            layerKey
465
        );
466
    },
467

468
    /**
469
     * Handles showing the permanentLayer of the instance.
470
     */
471
    onShowAssociatedPermanentLayer: function () {
472
        const me = this;
×
473
        const layerKey = me.getView().getVectorLayerKey();
×
474
        if (!me.map) {
×
475
            return;
×
476
        }
477
        if (!layerKey) {
×
478
            return;
×
479
        }
480
        CpsiMapview.view.button.SpatialQueryButton.showAssociatedPermanentLayer(
×
481
            me.map,
482
            layerKey
483
        );
484
    },
485

486
    /**
487
     * Handles hiding the permanentLayer of the instance.
488
     */
489
    onHideAssociatedPermanentLayer: function () {
490
        const me = this;
×
491
        const layerKey = me.getView().getVectorLayerKey();
×
492
        if (!me.map) {
×
493
            return;
×
494
        }
495
        if (!layerKey) {
×
496
            return;
×
497
        }
498
        CpsiMapview.view.button.SpatialQueryButton.hideAssociatedPermanentLayer(
×
499
            me.map,
500
            layerKey
501
        );
502
    },
503

504
    onBeforeDestroy: function () {
505
        const me = this;
6✔
506
        const view = me.getView();
6✔
507

508
        // detoggle button
509
        me.onSpatialQueryBtnToggle(view, false);
6✔
510

511
        if (me.modifiyQueryInteraction) {
6!
512
            me.map.removeInteraction(me.modifiyQueryInteraction);
6✔
513
        }
514

515
        if (me.snapQueryInteraction) {
6!
516
            me.map.removeInteraction(me.snapQueryInteraction);
6✔
517
        }
518

519
        if (me.drawQueryInteraction) {
6!
520
            me.map.removeInteraction(me.drawQueryInteraction);
6✔
521
        }
522

523
        if (me.permanentLayer && me.permanentLayerCreatedByTool) {
6!
524
            me.map.removeLayer(me.permanentLayer);
6✔
525
        }
526
    },
527

528
    init: function () {
529
        const me = this;
11✔
530

531
        // create an object for the contextmenu eventhandler
532
        // so it can be removed correctly
533
        me.contextHandler = {
11✔
534
            handleEvent: me.showContextMenu,
535
            scope: me
536
        };
537
    }
538
});
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