• 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

12.88
/app/controller/button/StreetViewTool.js
1
/**
2
 * This class is the controller of the button to open the Street View tool.
3
 */
4
Ext.define('CpsiMapview.controller.button.StreetViewTool', {
1✔
5
    extend: 'Ext.app.ViewController',
6

7
    alias: 'controller.cmv_streetview_tool',
8

9
    requires: ['CpsiMapview.view.window.MinimizableWindow', 'BasiGX.util.Map'],
10

11
    /**
12
     * The OL map work / sync with this tool.
13
     *
14
     * @property {ol.Map}
15
     * @readonly
16
     */
17
    map: null,
18

19
    /**
20
     * The Street View Service instance.
21
     *
22
     * @property {google.maps.StreetViewService}
23
     * @readonly
24
     */
25
    streetViewService: null,
26

27
    /**
28
     * The vector layer to draw the Street View position feature.
29
     *
30
     * @cfg {ol.layer.Vector}
31
     * @property {ol.layer.Vector}
32
     */
33
    vectorLayer: null,
34

35
    /**
36
     * The identificator for the layer to draw the Street View position feature.
37
     *
38
     * @property {String}
39
     * @private
40
     */
41
    vectorLayerName: 'StreetViewLayer',
42

43
    /**
44
     * Listener object for the 'pov_changed' event.
45
     * Used to unregister the event.
46
     *
47
     * @property {Object}
48
     * @private
49
     */
50
    svPovChangedListener: null,
51

52
    /**
53
     * Listener object for the 'position_changed' event.
54
     * Used to unregister the event.
55
     *
56
     * @property {Object}
57
     * @private
58
     */
59
    svPositionChangedListener: null,
60

61
    constructor: function () {
62
        const me = this;
2✔
63
        me.onMapClick = me.onMapClick.bind(me);
2✔
64
        me.callParent(arguments);
2✔
65
    },
66

67
    /**
68
     * @private
69
     */
70
    init: function () {
71
        const me = this;
2✔
72
        const view = me.getView();
2✔
73

74
        // helper function to disable tool when GMaps API is not available
75
        const reactOnMissingGmapsApi = function () {
2✔
76
            Ext.Logger.warn(
×
77
                'No Google Maps JS-API available. ' +
78
                    'The Street View tool will be deactivated.'
79
            );
80
            view.setDisabled(true);
×
81
            return;
×
82
        };
83
        // GMaps API missing at all
84
        if (!Ext.isObject(window.google)) {
2!
85
            reactOnMissingGmapsApi();
×
86
            return;
×
87
        }
88
        // GMaps API not usable due to missing API key or similar
89
        window.gm_authFailure = reactOnMissingGmapsApi;
2✔
90

91
        // detect the map instance we work on
92
        if (view.map && view.map instanceof ol.Map) {
2!
93
            me.map = view.map;
×
94
        } else {
95
            // guess map as fallback
96
            me.map = BasiGX.util.Map.getMapComponent().map;
2✔
97
        }
98

99
        // create a vector layer for position if not passed in
100
        if (!me.vectorLayer) {
2!
101
            const style =
102
                view.vectorLayerStyle ||
2✔
103
                new ol.style.Style({
104
                    image: new ol.style.Icon({
105
                        anchor: [0.5, 46],
106
                        anchorXUnits: 'fraction',
107
                        anchorYUnits: 'pixels',
108
                        src: view.vectorIcon
109
                    })
110
                });
111

112
            me.vectorLayer = new ol.layer.Vector({
2✔
113
                name: me.vectorLayerName,
114
                source: new ol.source.Vector(),
115
                style: style
116
            });
117
        }
118

119
        // init the Street View Service instance
120
        me.streetViewService = new google.maps.StreetViewService();
2✔
121
    },
122

123
    /**
124
     * Handles the 'toggle' event of the button (view).
125
     *
126
     * @param  {Ext.button.Button} btn The toggled button
127
     * @param  {Boolean}           pressed New pressed state
128
     * @private
129
     */
130
    onToggle: function (btn, pressed) {
131
        const me = this;
×
132

133
        if (pressed) {
×
134
            // add layer and raise layer to top of stack
135
            me.map.addLayer(me.vectorLayer);
×
136
        } else {
137
            me.map.removeLayer(me.vectorLayer);
×
138
            // cleanup
139
            me.vectorLayer.getSource().clear();
×
140
            if (me.streetViewWin) {
×
141
                me.streetViewWin.close();
×
142
            }
143
        }
144

145
        // activate / deactivate click
146
        me.registerMapListeners(pressed);
×
147
    },
148

149
    /**
150
     * Handles the 'beforedestroy' event of the view.
151
     * Performs several cleanup steps.
152
     */
153
    onBeforeDestroy: function () {
154
        const me = this;
×
155
        const btn = me.getView();
×
156

157
        // detoggle button, forces clearing layer
158
        me.onToggle(btn, false);
×
159
        // remove GMaps events
160
        me.unregisterGmapsEvents();
×
161
        // clean-up window
162
        if (me.streetViewWin) {
×
163
            me.streetViewWin.close();
×
164
            me.streetViewWin = null;
×
165
        }
166
    },
167

168
    /**
169
     * Registers listeners on the map we need for this tool.
170
     *
171
     * @param  {Boolean} activate Flag to activate / deactivate the listeners
172
     */
173
    registerMapListeners: function (activate) {
174
        const me = this;
×
175

176
        if (activate) {
×
177
            me.map.on('singleclick', me.onMapClick);
×
178
        } else {
179
            me.map.un('singleclick', me.onMapClick);
×
180
        }
181
    },
182

183
    /**
184
     * Handles 'singleclick' event on the map.
185
     * Show window with Street View panorama for clicked position (if existing)
186
     *
187
     * @param  {ol.MapBrowserEvent} evt OL event object
188
     * @private
189
     */
190
    onMapClick: function (evt) {
191
        const me = this;
×
192
        const clickCoord = evt.coordinate;
×
193

194
        const gmapsLatLng = me.olCoord2GmapsLatLng(clickCoord);
×
195
        me.showStreetViewWindow(gmapsLatLng);
×
196
    },
197

198
    /**
199
     * Shows the window with the Street View panorama at the given position or
200
     * fires an event 'missingpanorama' on the view instance of this controller.
201
     *
202
     * @param  {google.maps.LatLng} latLng Position to show SV panorama at
203
     * @private
204
     */
205
    showStreetViewWindow: function (latLng) {
206
        const me = this;
×
207
        const view = me.getView();
×
208

209
        if (!me.streetViewWin) {
×
210
            me.streetViewWin = Ext.create(
×
211
                'CpsiMapview.view.window.MinimizableWindow',
212
                {
213
                    title: view.svWinTitlePrefix,
214
                    width: view.svWinWidth,
215
                    height: view.svWinHeight,
216
                    closeAction: 'destroy',
217
                    autoShow: false,
218
                    listeners: {
219
                        afterrender: function (win) {
220
                            // render SV panorama to body of the window
221
                            me.svDiv = Ext.getDom(win.body);
×
222
                        },
223
                        resize: function () {
224
                            // reload the panorama to fit the new window size
225
                            if (me.svPanorama) {
×
226
                                // for an open window use the current position
227
                                me.showStreetViewWindow(
×
228
                                    me.svPanorama.getPosition()
229
                                );
230
                            } else {
231
                                // for a new window use the new position
232
                                me.showStreetViewWindow(latLng);
×
233
                            }
234
                        },
235
                        destroy: function () {
236
                            me.unregisterGmapsEvents();
×
237
                            me.svPanorama = null;
×
238
                            me.streetViewWin = null;
×
239
                            me.vectorLayer.getSource().clear();
×
240
                        }
241
                    }
242
                }
243
            );
244
        }
245

246
        // load the panorama for the given position
247
        me.streetViewService.getPanoramaByLocation(
×
248
            latLng,
249
            50,
250
            function (data, status) {
251
                if (status === google.maps.StreetViewStatus.OK) {
×
252
                    const win = me.streetViewWin;
×
253
                    const title =
254
                        view.svWinTitlePrefix +
×
255
                        view.svWinTitleDateLabel +
256
                        data.imageDate;
257

258
                    win.setTitle(title);
×
259
                    win.show();
×
260

261
                    // create new Street View panorama
262
                    const panoConf = {
×
263
                        position: latLng,
264
                        pov: view.svDefaultPov
265
                    };
266
                    me.svPanorama = new google.maps.StreetViewPanorama(
×
267
                        me.svDiv
268
                    );
269
                    me.svPanorama.setPov(panoConf.pov);
×
270
                    me.svPanorama.setPosition(panoConf.position);
×
271

272
                    // initially draw the position on the map and set heading
273
                    me.drawPositionFeature(me.gmapsLatLng2olCoord(latLng));
×
274
                    me.updatePositionFeature();
×
275

276
                    me.registerGmapsEvents();
×
277
                } else {
278
                    // inform subscribers that there was no panorama for clicked pos
279
                    me.getView().fireEvent('missingpanorama', {
×
280
                        msg: 'No StreetView panorama available at clicked location'
281
                    });
282

283
                    if (view.showNoPanoramaWarning) {
×
284
                        Ext.MessageBox.alert(
×
285
                            view.noPanoramaWarningTitle,
286
                            view.noPanoramaWarningText
287
                        );
288
                    }
289
                }
290
            }
291
        );
292
    },
293

294
    /**
295
     * Draws the position feature on the map. Clears an eventually existing
296
     * position feature before adding the new one.
297
     *
298
     * @param  {ol.Coordinate} coord Position to draw on the map
299
     * @private
300
     */
301
    drawPositionFeature: function (coord) {
302
        const me = this;
×
303
        const vectorSource = me.vectorLayer.getSource();
×
304

305
        // remove existing position feature
306
        vectorSource.clear();
×
307
        // add new position feature
308
        const posFeat = new ol.Feature({
×
309
            geometry: new ol.geom.Point(coord)
310
        });
311
        vectorSource.addFeature(posFeat);
×
312
    },
313

314
    /**
315
     * Updates an existing position feature by applying the current position and
316
     * heading of the currently opened SV panorama.
317
     */
318
    updatePositionFeature: function () {
319
        const me = this;
×
320

321
        if (!me.svPanorama) {
×
322
            return;
×
323
        }
324

325
        const newHeading = me.getHeadingRad();
×
326
        const newPosCoord = me.getPositionCoord();
×
327
        const vectorSource = me.vectorLayer.getSource();
×
328
        const feat = vectorSource.getFeatures()[0];
×
329

330
        if (feat) {
×
331
            // move position feature to current position
332
            feat.getGeometry().setCoordinates(newPosCoord);
×
333

334
            // rotate position feature to current heading
335
            me.vectorLayer.getStyle().getImage().setRotation(newHeading);
×
336
        }
337
    },
338

339
    /**
340
     * Handles 'pov_changed' event of the SV panorama.
341
     * Triggers #updatePositionFeature.
342
     *
343
     * @private
344
     */
345
    handlePovChanged: function () {
346
        const me = this;
×
347
        me.updatePositionFeature();
×
348
    },
349

350
    /**
351
     * Handles 'position_changed' event of the SV panorama.
352
     * Triggers #updatePositionFeature.
353
     *
354
     * @private
355
     */
356
    handlePositionChanged: function () {
357
        const me = this;
×
358
        me.updatePositionFeature();
×
359
    },
360

361
    /**
362
     * Returns the current heading of the SV panorama as radiant.
363
     *
364
     * @return {Number} Heading in radiant
365
     */
366
    getHeadingRad: function () {
367
        const me = this;
×
368
        let heading = me.svPanorama.getPov().heading;
×
369

370
        // normalize and convert to radiant
371
        heading = me.normaliseDegrees(heading);
×
372
        heading = (heading / 180) * Math.PI;
×
373

374
        return heading;
×
375
    },
376

377
    /**
378
     * Returns the current position of the SV panorama as OL coordinate.
379
     *
380
     * @return {ol.Coordinate} SV panorama position
381
     */
382
    getPositionCoord: function () {
383
        const me = this;
×
384
        const pos = me.svPanorama.getPosition();
×
385
        return me.gmapsLatLng2olCoord(pos);
×
386
    },
387

388
    /**
389
     * Normalizes the given degree value.
390
     *
391
     * @param  {Number} value Degree value to normalize
392
     * @return {Number}       Normalized degree value
393
     * @private
394
     */
395
    normaliseDegrees: function (value) {
396
        if (value < 0) {
×
397
            value += 360;
×
398
        } else {
399
            if (value > 360) {
×
400
                value -= 360;
×
401
            }
402
        }
403
        return Number(value);
×
404
    },
405

406
    /**
407
     * Converts an OpenLayers coordinate to a Google Maps LatLng object.
408
     * Transforms the coordinates to EPSG:4326.
409
     *
410
     * @param  {ol.Coordinate} coords OL coordinate to transform
411
     * @return {google.maps.LatLng}   Google Maps LatLng object
412
     */
413
    olCoord2GmapsLatLng: function (coords) {
414
        const me = this;
×
415
        const latLonProj = 'EPSG:4326';
×
416
        const mapProj = me.map.getView().getProjection().getCode();
×
417

418
        let latLonCoord;
419
        if (mapProj !== latLonProj) {
×
420
            latLonCoord = ol.proj.transform(coords, mapProj, latLonProj);
×
421
        } else {
422
            latLonCoord = coords;
×
423
        }
424

425
        return new google.maps.LatLng(latLonCoord[1], latLonCoord[0]);
×
426
    },
427

428
    /**
429
     * Converts an Google Maps LatLng object to a OpenLayers coordinate.
430
     * Transforms the coordinates to the map projection.
431
     *
432
     * @param  {google.maps.LatLng} latLng Google Maps LatLng object
433
     * @return {ol.Coordinate}             OL coordinate in map projection
434
     */
435
    gmapsLatLng2olCoord: function (latLng) {
436
        const me = this;
×
437
        const latLonProj = 'EPSG:4326';
×
438
        const mapProj = me.map.getView().getProjection().getCode();
×
439
        const latLonCoord = [latLng.lng(), latLng.lat()];
×
440

441
        let mapCoord;
442
        if (mapProj !== latLonProj) {
×
443
            mapCoord = ol.proj.transform(latLonCoord, latLonProj, mapProj);
×
444
        } else {
445
            mapCoord = latLonCoord;
×
446
        }
447

448
        return mapCoord;
×
449
    },
450

451
    /**
452
     * Registers the 'pov_changed' and the 'position_changed' events for the
453
     * SV panorama.
454
     */
455
    registerGmapsEvents: function () {
456
        const me = this;
×
457
        me.svPovChangedListener = google.maps.event.addListener(
×
458
            me.svPanorama,
459
            'pov_changed',
460
            me.handlePovChanged.bind(me)
461
        );
462
        me.svPositionChangedListener = google.maps.event.addListener(
×
463
            me.svPanorama,
464
            'position_changed',
465
            me.handlePositionChanged.bind(me)
466
        );
467
    },
468

469
    /**
470
     * Unregisters the 'pov_changed' and the 'position_changed' events for the
471
     * SV panorama.
472
     */
473
    unregisterGmapsEvents: function () {
474
        const me = this;
×
475
        google.maps.event.removeListener(me.svPovChangedListener);
×
476
        google.maps.event.removeListener(me.svPositionChangedListener);
×
477
    }
478
});
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