1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24**   * Redistributions of source code must retain the above copyright
25**     notice, this list of conditions and the following disclaimer.
26**   * Redistributions in binary form must reproduce the above copyright
27**     notice, this list of conditions and the following disclaimer in
28**     the documentation and/or other materials provided with the
29**     distribution.
30**   * Neither the name of The Qt Company Ltd nor the names of its
31**     contributors may be used to endorse or promote products derived
32**     from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51import QtQuick 2.5
52import QtQuick.Controls 1.4
53import QtLocation 5.6
54import QtPositioning 5.5
55import "map"
56import "menus"
57import "helper.js" as Helper
58
59ApplicationWindow {
60    id: appWindow
61    property variant map
62    property variant minimap
63    property variant parameters
64
65    //defaults
66    //! [routecoordinate]
67    property variant fromCoordinate: QtPositioning.coordinate(59.9483, 10.7695)
68    property variant toCoordinate: QtPositioning.coordinate(59.9645, 10.671)
69    //! [routecoordinate]
70
71    function createMap(provider)
72    {
73        var plugin
74
75        if (parameters && parameters.length>0)
76            plugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin{ name:"' + provider + '"; parameters: appWindow.parameters}', appWindow)
77        else
78            plugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin{ name:"' + provider + '"}', appWindow)
79
80        if (minimap) {
81            minimap.destroy()
82            minimap = null
83        }
84
85        var zoomLevel = null
86        var tilt = null
87        var bearing = null
88        var fov = null
89        var center = null
90        var panelExpanded = null
91        if (map) {
92            zoomLevel = map.zoomLevel
93            tilt = map.tilt
94            bearing = map.bearing
95            fov = map.fieldOfView
96            center = map.center
97            panelExpanded = map.slidersExpanded
98            map.destroy()
99        }
100
101        map = mapComponent.createObject(page);
102        map.plugin = plugin;
103
104        if (zoomLevel != null) {
105            map.tilt = tilt
106            map.bearing = bearing
107            map.fieldOfView = fov
108            map.zoomLevel = zoomLevel
109            map.center = center
110            map.slidersExpanded = panelExpanded
111        } else {
112            // Use an integer ZL to enable nearest interpolation, if possible.
113            map.zoomLevel = Math.floor((map.maximumZoomLevel - map.minimumZoomLevel)/2)
114            // defaulting to 45 degrees, if possible.
115            map.fieldOfView = Math.min(Math.max(45.0, map.minimumFieldOfView), map.maximumFieldOfView)
116        }
117
118        map.forceActiveFocus()
119    }
120
121    function getPlugins()
122    {
123        var plugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin {}', appWindow)
124        var myArray = new Array()
125        for (var i = 0; i<plugin.availableServiceProviders.length; i++) {
126            var tempPlugin = Qt.createQmlObject ('import QtLocation 5.6; Plugin {name: "' + plugin.availableServiceProviders[i]+ '"}', appWindow)
127            if (tempPlugin.supportsMapping())
128                myArray.push(tempPlugin.name)
129        }
130        myArray.sort()
131        return myArray
132    }
133
134    function initializeProviders(pluginParameters)
135    {
136        var parameters = new Array()
137        for (var prop in pluginParameters){
138            var parameter = Qt.createQmlObject('import QtLocation 5.6; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}',appWindow)
139            parameters.push(parameter)
140        }
141        appWindow.parameters = parameters
142        var plugins = getPlugins()
143        mainMenu.providerMenu.createMenu(plugins)
144        for (var i = 0; i<plugins.length; i++) {
145            if (plugins[i] === "osm")
146                mainMenu.selectProvider(plugins[i])
147        }
148    }
149
150    title: qsTr("Mapviewer")
151    height: 640
152    width: 360
153    visible: true
154    menuBar: mainMenu
155
156    //! [geocode0]
157    Address {
158        id :fromAddress
159        street: "Sandakerveien 116"
160        city: "Oslo"
161        country: "Norway"
162        state : ""
163        postalCode: "0484"
164    }
165    //! [geocode0]
166
167    Address {
168        id: toAddress
169        street: "Holmenkollveien 140"
170        city: "Oslo"
171        country: "Norway"
172        postalCode: "0791"
173    }
174
175    MainMenu {
176        id: mainMenu
177
178        function toggleMiniMapState()
179        {
180            if (minimap) {
181                minimap.destroy()
182                minimap = null
183            } else {
184                minimap = Qt.createQmlObject ('import "map"; MiniMap{ z: map.z + 2 }', map)
185            }
186        }
187
188        function setLanguage(lang)
189        {
190            map.plugin.locales = lang;
191            stackView.pop(page)
192        }
193
194        onSelectProvider: {
195            stackView.pop()
196            for (var i = 0; i < providerMenu.items.length; i++) {
197                providerMenu.items[i].checked = providerMenu.items[i].text === providerName
198            }
199
200            createMap(providerName)
201            if (map.error === Map.NoError) {
202                selectMapType(map.activeMapType)
203                toolsMenu.createMenu(map);
204            } else {
205                mapTypeMenu.clear();
206                toolsMenu.clear();
207            }
208        }
209
210        onSelectMapType: {
211            stackView.pop(page)
212            for (var i = 0; i < mapTypeMenu.items.length; i++) {
213                mapTypeMenu.items[i].checked = mapTypeMenu.items[i].text === mapType.name
214            }
215            map.activeMapType = mapType
216        }
217
218
219        onSelectTool: {
220            switch (tool) {
221            case "AddressRoute":
222                stackView.pop({item:page, immediate: true})
223                stackView.push({ item: Qt.resolvedUrl("forms/RouteAddress.qml") ,
224                                   properties: { "plugin": map.plugin,
225                                       "toAddress": toAddress,
226                                       "fromAddress": fromAddress}})
227                stackView.currentItem.showRoute.connect(map.calculateCoordinateRoute)
228                stackView.currentItem.showMessage.connect(stackView.showMessage)
229                stackView.currentItem.closeForm.connect(stackView.closeForm)
230                break
231            case "CoordinateRoute":
232                stackView.pop({item:page, immediate: true})
233                stackView.push({ item: Qt.resolvedUrl("forms/RouteCoordinate.qml") ,
234                                   properties: { "toCoordinate": toCoordinate,
235                                       "fromCoordinate": fromCoordinate}})
236                stackView.currentItem.showRoute.connect(map.calculateCoordinateRoute)
237                stackView.currentItem.closeForm.connect(stackView.closeForm)
238                break
239            case "Geocode":
240                stackView.pop({item:page, immediate: true})
241                stackView.push({ item: Qt.resolvedUrl("forms/Geocode.qml") ,
242                                   properties: { "address": fromAddress}})
243                stackView.currentItem.showPlace.connect(map.geocode)
244                stackView.currentItem.closeForm.connect(stackView.closeForm)
245                break
246            case "RevGeocode":
247                stackView.pop({item:page, immediate: true})
248                stackView.push({ item: Qt.resolvedUrl("forms/ReverseGeocode.qml") ,
249                                   properties: { "coordinate": fromCoordinate}})
250                stackView.currentItem.showPlace.connect(map.geocode)
251                stackView.currentItem.closeForm.connect(stackView.closeForm)
252                break
253            case "Language":
254                stackView.pop({item:page, immediate: true})
255                stackView.push({ item: Qt.resolvedUrl("forms/Locale.qml") ,
256                                   properties: { "locale":  map.plugin.locales[0]}})
257                stackView.currentItem.selectLanguage.connect(setLanguage)
258                stackView.currentItem.closeForm.connect(stackView.closeForm)
259                break
260            case "Clear":
261                map.clearData()
262                break
263            case "Prefetch":
264                map.prefetchData()
265                break
266            default:
267                console.log("Unsupported operation")
268            }
269        }
270
271        onToggleMapState: {
272            stackView.pop(page)
273            switch (state) {
274            case "FollowMe":
275                map.followme = !map.followme
276                break
277            case "MiniMap":
278                toggleMiniMapState()
279                isMiniMap = minimap
280                break
281            default:
282                console.log("Unsupported operation")
283            }
284        }
285    }
286
287    MapPopupMenu {
288        id: mapPopupMenu
289
290        function show(coordinate)
291        {
292            stackView.pop(page)
293            mapPopupMenu.coordinate = coordinate
294            mapPopupMenu.markersCount = map.markers.length
295            mapPopupMenu.mapItemsCount = map.mapItems.length
296            mapPopupMenu.update()
297            mapPopupMenu.popup()
298        }
299
300        onItemClicked: {
301            stackView.pop(page)
302            switch (item) {
303            case "addMarker":
304                map.addMarker()
305                break
306            case "getCoordinate":
307                map.coordinatesCaptured(coordinate.latitude, coordinate.longitude)
308                break
309            case "fitViewport":
310                map.fitViewportToMapItems()
311                break
312            case "deleteMarkers":
313                map.deleteMarkers()
314                break
315            case "deleteItems":
316                map.deleteMapItems()
317                break
318            default:
319                console.log("Unsupported operation")
320            }
321        }
322    }
323
324    MarkerPopupMenu {
325        id: markerPopupMenu
326
327        function show(coordinate)
328        {
329            stackView.pop(page)
330            markerPopupMenu.markersCount = map.markers.length
331            markerPopupMenu.update()
332            markerPopupMenu.popup()
333        }
334
335        function askForCoordinate()
336        {
337            stackView.push({ item: Qt.resolvedUrl("forms/ReverseGeocode.qml") ,
338                               properties: { "title": qsTr("New Coordinate"),
339                                   "coordinate":   map.markers[map.currentMarker].coordinate}})
340            stackView.currentItem.showPlace.connect(moveMarker)
341            stackView.currentItem.closeForm.connect(stackView.closeForm)
342        }
343
344        function moveMarker(coordinate)
345        {
346            map.markers[map.currentMarker].coordinate = coordinate;
347            map.center = coordinate;
348            stackView.pop(page)
349        }
350
351        onItemClicked: {
352            stackView.pop(page)
353            switch (item) {
354            case "deleteMarker":
355                map.deleteMarker(map.currentMarker)
356                break;
357            case "getMarkerCoordinate":
358                map.coordinatesCaptured(map.markers[map.currentMarker].coordinate.latitude, map.markers[map.currentMarker].coordinate.longitude)
359                break;
360            case "moveMarkerTo":
361                askForCoordinate()
362                break;
363            case "routeToNextPoint":
364            case "routeToNextPoints":
365                map.calculateMarkerRoute()
366                break
367            case "distanceToNextPoint":
368                var coordinate1 = map.markers[currentMarker].coordinate;
369                var coordinate2 = map.markers[currentMarker+1].coordinate;
370                var distance = Helper.formatDistance(coordinate1.distanceTo(coordinate2));
371                stackView.showMessage(qsTr("Distance"),"<b>" + qsTr("Distance:") + "</b> " + distance)
372                break
373            case "drawImage":
374                map.addGeoItem("ImageItem")
375                break
376            case "drawRectangle":
377                map.addGeoItem("RectangleItem")
378                break
379            case "drawCircle":
380                map.addGeoItem("CircleItem")
381                break;
382            case "drawPolyline":
383                map.addGeoItem("PolylineItem")
384                break;
385            case "drawPolygonMenu":
386                map.addGeoItem("PolygonItem")
387                break
388            default:
389                console.log("Unsupported operation")
390            }
391        }
392    }
393
394    ItemPopupMenu {
395        id: itemPopupMenu
396
397        function show(type,coordinate)
398        {
399            stackView.pop(page)
400            itemPopupMenu.type = type
401            itemPopupMenu.update()
402            itemPopupMenu.popup()
403        }
404
405        onItemClicked: {
406            stackView.pop(page)
407            switch (item) {
408            case "showRouteInfo":
409                stackView.showRouteListPage()
410                break;
411            case "deleteRoute":
412                map.routeModel.reset();
413                break;
414            case "showPointInfo":
415                map.showGeocodeInfo()
416                break;
417            case "deletePoint":
418                map.geocodeModel.reset()
419                break;
420            default:
421                console.log("Unsupported operation")
422            }
423        }
424    }
425
426    StackView {
427        id: stackView
428        anchors.fill: parent
429        focus: true
430        initialItem: Item {
431            id: page
432
433            Text {
434                visible: !supportsSsl && map && map.activeMapType && activeMapType.metadata.isHTTPS
435                text: "The active map type\n
436requires (missing) SSL\n
437support"
438                horizontalAlignment: Text.AlignHCenter
439                font.pixelSize: appWindow.width / 12
440                font.bold: true
441                color: "grey"
442                anchors.centerIn: parent
443                z: 12
444            }
445        }
446
447        function showMessage(title,message,backPage)
448        {
449            push({ item: Qt.resolvedUrl("forms/Message.qml") ,
450                               properties: {
451                                   "title" : title,
452                                   "message" : message,
453                                   "backPage" : backPage
454                               }})
455            currentItem.closeForm.connect(closeMessage)
456        }
457
458        function closeMessage(backPage)
459        {
460            pop(backPage)
461        }
462
463        function closeForm()
464        {
465            pop(page)
466        }
467
468        function showRouteListPage()
469        {
470            push({ item: Qt.resolvedUrl("forms/RouteList.qml") ,
471                               properties: {
472                                   "routeModel" : map.routeModel
473                               }})
474            currentItem.closeForm.connect(closeForm)
475        }
476    }
477
478    Component {
479        id: mapComponent
480
481        MapComponent{
482            width: page.width
483            height: page.height
484            onFollowmeChanged: mainMenu.isFollowMe = map.followme
485            onSupportedMapTypesChanged: mainMenu.mapTypeMenu.createMenu(map)
486            onCoordinatesCaptured: {
487                var text = "<b>" + qsTr("Latitude:") + "</b> " + Helper.roundNumber(latitude,4) + "<br/><b>" + qsTr("Longitude:") + "</b> " + Helper.roundNumber(longitude,4)
488                stackView.showMessage(qsTr("Coordinates"),text);
489            }
490            onGeocodeFinished:{
491                if (map.geocodeModel.status == GeocodeModel.Ready) {
492                    if (map.geocodeModel.count == 0) {
493                        stackView.showMessage(qsTr("Geocode Error"),qsTr("Unsuccessful geocode"))
494                    } else if (map.geocodeModel.count > 1) {
495                        stackView.showMessage(qsTr("Ambiguous geocode"), map.geocodeModel.count + " " +
496                                              qsTr("results found for the given address, please specify location"))
497                    } else {
498                        stackView.showMessage(qsTr("Location"), geocodeMessage(),page)
499                    }
500                } else if (map.geocodeModel.status == GeocodeModel.Error) {
501                    stackView.showMessage(qsTr("Geocode Error"),qsTr("Unsuccessful geocode"))
502                }
503            }
504            onRouteError: stackView.showMessage(qsTr("Route Error"),qsTr("Unable to find a route for the given points"),page)
505
506            onShowGeocodeInfo: stackView.showMessage(qsTr("Location"),geocodeMessage(),page)
507
508            onErrorChanged: {
509                if (map.error != Map.NoError) {
510                    var title = qsTr("ProviderError")
511                    var message =  map.errorString + "<br/><br/><b>" + qsTr("Try to select other provider") + "</b>"
512                    if (map.error == Map.MissingRequiredParameterError)
513                        message += "<br/>" + qsTr("or see") + " \'mapviewer --help\' "
514                                + qsTr("how to pass plugin parameters.")
515                    stackView.showMessage(title,message);
516                }
517            }
518            onShowMainMenu: mapPopupMenu.show(coordinate)
519            onShowMarkerMenu: markerPopupMenu.show(coordinate)
520            onShowRouteMenu: itemPopupMenu.show("Route",coordinate)
521            onShowPointMenu: itemPopupMenu.show("Point",coordinate)
522            onShowRouteList: stackView.showRouteListPage()
523        }
524    }
525}
526