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****************************************************************************/
50import QtQuick 2.5
51import QtQuick.Controls 1.4
52import QtLocation 5.9
53import QtPositioning 5.5
54import "../helper.js" as Helper
55
56//! [top]
57Map {
58    id: map
59//! [top]
60    property variant markers
61    property variant mapItems
62    property int markerCounter: 0 // counter for total amount of markers. Resets to 0 when number of markers = 0
63    property int currentMarker
64    property int lastX : -1
65    property int lastY : -1
66    property int pressX : -1
67    property int pressY : -1
68    property int jitterThreshold : 30
69    property bool followme: false
70    property variant scaleLengths: [5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000]
71    property alias routeQuery: routeQuery
72    property alias routeModel: routeModel
73    property alias geocodeModel: geocodeModel
74    property alias slidersExpanded: sliders.expanded
75
76    signal showGeocodeInfo()
77    signal geocodeFinished()
78    signal routeError()
79    signal coordinatesCaptured(double latitude, double longitude)
80    signal showMainMenu(variant coordinate)
81    signal showMarkerMenu(variant coordinate)
82    signal showRouteMenu(variant coordinate)
83    signal showPointMenu(variant coordinate)
84    signal showRouteList()
85
86    function geocodeMessage()
87    {
88        var street, district, city, county, state, countryCode, country, postalCode, latitude, longitude, text
89        latitude = Math.round(geocodeModel.get(0).coordinate.latitude * 10000) / 10000
90        longitude =Math.round(geocodeModel.get(0).coordinate.longitude * 10000) / 10000
91        street = geocodeModel.get(0).address.street
92        district = geocodeModel.get(0).address.district
93        city = geocodeModel.get(0).address.city
94        county = geocodeModel.get(0).address.county
95        state = geocodeModel.get(0).address.state
96        countryCode = geocodeModel.get(0).address.countryCode
97        country = geocodeModel.get(0).address.country
98        postalCode = geocodeModel.get(0).address.postalCode
99
100        text = "<b>Latitude:</b> " + latitude + "<br/>"
101        text +="<b>Longitude:</b> " + longitude + "<br/>" + "<br/>"
102        if (street) text +="<b>Street: </b>"+ street + " <br/>"
103        if (district) text +="<b>District: </b>"+ district +" <br/>"
104        if (city) text +="<b>City: </b>"+ city + " <br/>"
105        if (county) text +="<b>County: </b>"+ county + " <br/>"
106        if (state) text +="<b>State: </b>"+ state + " <br/>"
107        if (countryCode) text +="<b>Country code: </b>"+ countryCode + " <br/>"
108        if (country) text +="<b>Country: </b>"+ country + " <br/>"
109        if (postalCode) text +="<b>PostalCode: </b>"+ postalCode + " <br/>"
110        return text
111    }
112
113    function calculateScale()
114    {
115        var coord1, coord2, dist, text, f
116        f = 0
117        coord1 = map.toCoordinate(Qt.point(0,scale.y))
118        coord2 = map.toCoordinate(Qt.point(0+scaleImage.sourceSize.width,scale.y))
119        dist = Math.round(coord1.distanceTo(coord2))
120
121        if (dist === 0) {
122            // not visible
123        } else {
124            for (var i = 0; i < scaleLengths.length-1; i++) {
125                if (dist < (scaleLengths[i] + scaleLengths[i+1]) / 2 ) {
126                    f = scaleLengths[i] / dist
127                    dist = scaleLengths[i]
128                    break;
129                }
130            }
131            if (f === 0) {
132                f = dist / scaleLengths[i]
133                dist = scaleLengths[i]
134            }
135        }
136
137        text = Helper.formatDistance(dist)
138        scaleImage.width = (scaleImage.sourceSize.width * f) - 2 * scaleImageLeft.sourceSize.width
139        scaleText.text = text
140    }
141
142    function deleteMarkers()
143    {
144        var count = map.markers.length
145        for (var i = 0; i<count; i++){
146            map.removeMapItem(map.markers[i])
147            map.markers[i].destroy()
148        }
149        map.markers = []
150        markerCounter = 0
151    }
152
153    function deleteMapItems()
154    {
155        var count = map.mapItems.length
156        for (var i = 0; i<count; i++){
157            map.removeMapItem(map.mapItems[i])
158            map.mapItems[i].destroy()
159        }
160        map.mapItems = []
161    }
162
163    function addMarker()
164    {
165        var count = map.markers.length
166        markerCounter++
167        var marker = Qt.createQmlObject ('Marker {}', map)
168        map.addMapItem(marker)
169        marker.z = map.z+1
170        marker.coordinate = mouseArea.lastCoordinate
171
172        //update list of markers
173        var myArray = new Array()
174        for (var i = 0; i<count; i++){
175            myArray.push(markers[i])
176        }
177        myArray.push(marker)
178        markers = myArray
179    }
180
181    function addGeoItem(item)
182    {
183        var count = map.mapItems.length
184        var co = Qt.createComponent(item+'.qml')
185        if (co.status == Component.Ready) {
186            var o = co.createObject(map)
187            o.setGeometry(map.markers, currentMarker)
188            map.addMapItem(o)
189            //update list of items
190            var myArray = new Array()
191            for (var i = 0; i<count; i++){
192                myArray.push(mapItems[i])
193            }
194            myArray.push(o)
195            mapItems = myArray
196
197        } else {
198            console.log(item + " is not supported right now, please call us later.")
199        }
200    }
201
202    function deleteMarker(index)
203    {
204        //update list of markers
205        var myArray = new Array()
206        var count = map.markers.length
207        for (var i = 0; i<count; i++){
208            if (index != i) myArray.push(map.markers[i])
209        }
210
211        map.removeMapItem(map.markers[index])
212        map.markers[index].destroy()
213        map.markers = myArray
214        if (markers.length == 0) markerCounter = 0
215    }
216
217    function calculateMarkerRoute()
218    {
219        routeQuery.clearWaypoints();
220        for (var i = currentMarker; i< map.markers.length; i++){
221            routeQuery.addWaypoint(markers[i].coordinate)
222        }
223        routeQuery.travelModes = RouteQuery.CarTravel
224        routeQuery.routeOptimizations = RouteQuery.ShortestRoute
225        routeQuery.setFeatureWeight(0, 0)
226        routeModel.update();
227    }
228
229    function calculateCoordinateRoute(startCoordinate, endCoordinate)
230    {
231        //! [routerequest0]
232        // clear away any old data in the query
233        routeQuery.clearWaypoints();
234
235        // add the start and end coords as waypoints on the route
236        routeQuery.addWaypoint(startCoordinate)
237        routeQuery.addWaypoint(endCoordinate)
238        routeQuery.travelModes = RouteQuery.CarTravel
239        routeQuery.routeOptimizations = RouteQuery.FastestRoute
240
241        //! [routerequest0]
242
243        //! [routerequest0 feature weight]
244        for (var i=0; i<9; i++) {
245            routeQuery.setFeatureWeight(i, 0)
246        }
247        //for (var i=0; i<routeDialog.features.length; i++) {
248        //    map.routeQuery.setFeatureWeight(routeDialog.features[i], RouteQuery.AvoidFeatureWeight)
249        //}
250        //! [routerequest0 feature weight]
251
252        //! [routerequest1]
253        routeModel.update();
254
255        //! [routerequest1]
256        //! [routerequest2]
257        // center the map on the start coord
258        map.center = startCoordinate;
259        //! [routerequest2]
260    }
261
262    function geocode(fromAddress)
263    {
264        //! [geocode1]
265        // send the geocode request
266        geocodeModel.query = fromAddress
267        geocodeModel.update()
268        //! [geocode1]
269    }
270
271
272//! [coord]
273    zoomLevel: (maximumZoomLevel - minimumZoomLevel)/2
274    center {
275        // The Qt Company in Oslo
276        latitude: 59.9485
277        longitude: 10.7686
278    }
279//! [coord]
280
281//! [mapnavigation]
282    // Enable pan, flick, and pinch gestures to zoom in and out
283    gesture.acceptedGestures: MapGestureArea.PanGesture | MapGestureArea.FlickGesture | MapGestureArea.PinchGesture | MapGestureArea.RotationGesture | MapGestureArea.TiltGesture
284    gesture.flickDeceleration: 3000
285    gesture.enabled: true
286//! [mapnavigation]
287    focus: true
288    onCopyrightLinkActivated: Qt.openUrlExternally(link)
289
290    onCenterChanged:{
291        scaleTimer.restart()
292        if (map.followme)
293            if (map.center != positionSource.position.coordinate) map.followme = false
294    }
295
296    onZoomLevelChanged:{
297        scaleTimer.restart()
298        if (map.followme) map.center = positionSource.position.coordinate
299    }
300
301    onWidthChanged:{
302        scaleTimer.restart()
303    }
304
305    onHeightChanged:{
306        scaleTimer.restart()
307    }
308
309    Component.onCompleted: {
310        markers = new Array();
311        mapItems = new Array();
312    }
313
314    Keys.onPressed: {
315        if (event.key === Qt.Key_Plus) {
316            map.zoomLevel++;
317        } else if (event.key === Qt.Key_Minus) {
318            map.zoomLevel--;
319        } else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right ||
320                   event.key === Qt.Key_Up   || event.key === Qt.Key_Down) {
321            var dx = 0;
322            var dy = 0;
323
324            switch (event.key) {
325
326            case Qt.Key_Left: dx = map.width / 4; break;
327            case Qt.Key_Right: dx = -map.width / 4; break;
328            case Qt.Key_Up: dy = map.height / 4; break;
329            case Qt.Key_Down: dy = -map.height / 4; break;
330
331            }
332
333            var mapCenterPoint = Qt.point(map.width / 2.0 - dx, map.height / 2.0 - dy);
334            map.center = map.toCoordinate(mapCenterPoint);
335        }
336    }
337
338    /* @todo
339    Binding {
340        target: map
341        property: 'center'
342        value: positionSource.position.coordinate
343        when: followme
344    }*/
345
346    PositionSource{
347        id: positionSource
348        active: followme
349
350        onPositionChanged: {
351            map.center = positionSource.position.coordinate
352        }
353    }
354
355    MapQuickItem {
356        id: poiTheQtComapny
357        sourceItem: Rectangle { width: 14; height: 14; color: "#e41e25"; border.width: 2; border.color: "white"; smooth: true; radius: 7 }
358        coordinate {
359            latitude: 59.9485
360            longitude: 10.7686
361        }
362        opacity: 1.0
363        anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2)
364    }
365
366    MapQuickItem {
367        sourceItem: Text{
368            text: "The Qt Company"
369            color:"#242424"
370            font.bold: true
371            styleColor: "#ECECEC"
372            style: Text.Outline
373        }
374        coordinate: poiTheQtComapny.coordinate
375        anchorPoint: Qt.point(-poiTheQtComapny.sourceItem.width * 0.5,poiTheQtComapny.sourceItem.height * 1.5)
376    }
377
378    MapSliders {
379        id: sliders
380        z: map.z + 3
381        mapSource: map
382        edge: Qt.LeftEdge
383    }
384
385    Item {
386        id: scale
387        z: map.z + 3
388        visible: scaleText.text != "0 m"
389        anchors.bottom: parent.bottom;
390        anchors.right: parent.right
391        anchors.margins: 20
392        height: scaleText.height * 2
393        width: scaleImage.width
394
395        Image {
396            id: scaleImageLeft
397            source: "../resources/scale_end.png"
398            anchors.bottom: parent.bottom
399            anchors.right: scaleImage.left
400        }
401        Image {
402            id: scaleImage
403            source: "../resources/scale.png"
404            anchors.bottom: parent.bottom
405            anchors.right: scaleImageRight.left
406        }
407        Image {
408            id: scaleImageRight
409            source: "../resources/scale_end.png"
410            anchors.bottom: parent.bottom
411            anchors.right: parent.right
412        }
413        Label {
414            id: scaleText
415            color: "#004EAE"
416            anchors.centerIn: parent
417            text: "0 m"
418        }
419        Component.onCompleted: {
420            map.calculateScale();
421        }
422    }
423
424    //! [routemodel0]
425    RouteModel {
426        id: routeModel
427        plugin : map.plugin
428        query:  RouteQuery {
429            id: routeQuery
430        }
431        onStatusChanged: {
432            if (status == RouteModel.Ready) {
433                switch (count) {
434                case 0:
435                    // technically not an error
436                    map.routeError()
437                    break
438                case 1:
439                    map.showRouteList()
440                    break
441                }
442            } else if (status == RouteModel.Error) {
443                map.routeError()
444            }
445        }
446    }
447    //! [routemodel0]
448
449    //! [routedelegate0]
450    Component {
451        id: routeDelegate
452
453        MapRoute {
454            id: route
455            route: routeData
456            line.color: "#46a2da"
457            line.width: 5
458            smooth: true
459            opacity: 0.8
460     //! [routedelegate0]
461            MouseArea {
462                id: routeMouseArea
463                anchors.fill: parent
464                hoverEnabled: false
465                property variant lastCoordinate
466
467                onPressed : {
468                    map.lastX = mouse.x + parent.x
469                    map.lastY = mouse.y + parent.y
470                    map.pressX = mouse.x + parent.x
471                    map.pressY = mouse.y + parent.y
472                    lastCoordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
473                }
474
475                onPositionChanged: {
476                    if (mouse.button == Qt.LeftButton) {
477                        map.lastX = mouse.x + parent.x
478                        map.lastY = mouse.y + parent.y
479                    }
480                }
481
482                onPressAndHold:{
483                    if (Math.abs(map.pressX - parent.x- mouse.x ) < map.jitterThreshold
484                            && Math.abs(map.pressY - parent.y - mouse.y ) < map.jitterThreshold) {
485                        showRouteMenu(lastCoordinate);
486                    }
487                }
488
489            }
490    //! [routedelegate1]
491        }
492    }
493    //! [routedelegate1]
494
495    //! [geocodemodel0]
496    GeocodeModel {
497        id: geocodeModel
498        plugin: map.plugin
499        onStatusChanged: {
500            if ((status == GeocodeModel.Ready) || (status == GeocodeModel.Error))
501                map.geocodeFinished()
502        }
503        onLocationsChanged:
504        {
505            if (count == 1) {
506                map.center.latitude = get(0).coordinate.latitude
507                map.center.longitude = get(0).coordinate.longitude
508            }
509        }
510    }
511    //! [geocodemodel0]
512
513    //! [pointdel0]
514    Component {
515        id: pointDelegate
516
517        MapCircle {
518            id: point
519            radius: 1000
520            color: "#46a2da"
521            border.color: "#190a33"
522            border.width: 2
523            smooth: true
524            opacity: 0.25
525            center: locationData.coordinate
526            //! [pointdel0]
527            MouseArea {
528                anchors.fill:parent
529                id: circleMouseArea
530                hoverEnabled: false
531                property variant lastCoordinate
532
533                onPressed : {
534                    map.lastX = mouse.x + parent.x
535                    map.lastY = mouse.y + parent.y
536                    map.pressX = mouse.x + parent.x
537                    map.pressY = mouse.y + parent.y
538                    lastCoordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
539                }
540
541                onPositionChanged: {
542                    if (Math.abs(map.pressX - parent.x- mouse.x ) > map.jitterThreshold ||
543                            Math.abs(map.pressY - parent.y -mouse.y ) > map.jitterThreshold) {
544                        if (pressed) parent.radius = parent.center.distanceTo(
545                                         map.toCoordinate(Qt.point(mouse.x, mouse.y)))
546                    }
547                    if (mouse.button == Qt.LeftButton) {
548                        map.lastX = mouse.x + parent.x
549                        map.lastY = mouse.y + parent.y
550                    }
551                }
552
553                onPressAndHold:{
554                    if (Math.abs(map.pressX - parent.x- mouse.x ) < map.jitterThreshold
555                            && Math.abs(map.pressY - parent.y - mouse.y ) < map.jitterThreshold) {
556                        showPointMenu(lastCoordinate);
557                    }
558                }
559            }
560    //! [pointdel1]
561        }
562    }
563    //! [pointdel1]
564
565    //! [routeview0]
566    MapItemView {
567        model: routeModel
568        delegate: routeDelegate
569    //! [routeview0]
570        autoFitViewport: true
571    //! [routeview1]
572    }
573    //! [routeview1]
574
575    //! [geocodeview]
576    MapItemView {
577        model: geocodeModel
578        delegate: pointDelegate
579    }
580    //! [geocodeview]
581
582    Timer {
583        id: scaleTimer
584        interval: 100
585        running: false
586        repeat: false
587        onTriggered: {
588            map.calculateScale()
589        }
590    }
591
592    MouseArea {
593        id: mouseArea
594        property variant lastCoordinate
595        anchors.fill: parent
596        acceptedButtons: Qt.LeftButton | Qt.RightButton
597
598        onPressed : {
599            map.lastX = mouse.x
600            map.lastY = mouse.y
601            map.pressX = mouse.x
602            map.pressY = mouse.y
603            lastCoordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
604        }
605
606        onPositionChanged: {
607            if (mouse.button == Qt.LeftButton) {
608                map.lastX = mouse.x
609                map.lastY = mouse.y
610            }
611        }
612
613        onDoubleClicked: {
614            var mouseGeoPos = map.toCoordinate(Qt.point(mouse.x, mouse.y));
615            var preZoomPoint = map.fromCoordinate(mouseGeoPos, false);
616            if (mouse.button === Qt.LeftButton) {
617                map.zoomLevel = Math.floor(map.zoomLevel + 1)
618            } else if (mouse.button === Qt.RightButton) {
619                map.zoomLevel = Math.floor(map.zoomLevel - 1)
620            }
621            var postZoomPoint = map.fromCoordinate(mouseGeoPos, false);
622            var dx = postZoomPoint.x - preZoomPoint.x;
623            var dy = postZoomPoint.y - preZoomPoint.y;
624
625            var mapCenterPoint = Qt.point(map.width / 2.0 + dx, map.height / 2.0 + dy);
626            map.center = map.toCoordinate(mapCenterPoint);
627
628            lastX = -1;
629            lastY = -1;
630        }
631
632        onPressAndHold:{
633            if (Math.abs(map.pressX - mouse.x ) < map.jitterThreshold
634                    && Math.abs(map.pressY - mouse.y ) < map.jitterThreshold) {
635                showMainMenu(lastCoordinate);
636            }
637        }
638    }
639//! [end]
640}
641//! [end]
642