import QtQuick 2.0
import QtLocation 5.0
import QtLocation.examples 5.0
Map {
id: map
zoomLevel: (maximumZoomLevel - minimumZoomLevel)/2
center: nokiaCoordinate
pinch.activeGestures: MapPinchArea.ZoomGesture
pinch.enabled: true
flick.enabled: true
flick.deceleration: 3000
property variant markers
property variant mapItems
property int markerCounter: 0
property int currentMarker
signal resetState()
property int lastX : -1
property int lastY : -1
property int pressX : -1
property int pressY : -1
property int jitterThreshold : 30
property bool followme: false
property variant scaleLengths: [5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000]
signal showDistance(string distance);
signal requestLocale()
Coordinate {
id: nokiaCoordinate
latitude: -27.5796
longitude: 153.1003
}
PositionSource{
id: positionSource
active: followme
onPositionChanged: {
map.center = positionSource.position.coordinate
}
}
MapCircle {
id: poiEightMilePlains
center: Coordinate { latitude: -27.5758; longitude: 153.0881 }
radius: 1800
color: "green"
border.width: 2
border.color: "#242424"
opacity: 0.7
}
MapQuickItem {
sourceItem: Text{
text: "Eight Mile Plains"
color:"#242424"
font.bold: true
styleColor: "#ECECEC"
style: Text.Outline
}
coordinate: Coordinate { latitude: -27.59; longitude: 153.084 }
anchorPoint.x: 0
anchorPoint.y: 0
}
MapQuickItem {
id: poiNokia
sourceItem: Rectangle { width: 14; height: 14; color: "#1c94fc"; border.width: 2; border.color: "#242424"; smooth: true; radius: 7 }
coordinate: Coordinate { latitude: -27.5796; longitude: 153.1003 }
opacity:0.7
anchorPoint.x: sourceItem.width/2
anchorPoint.y: sourceItem.height/2
}
MapQuickItem {
sourceItem: Text{
text: "Nokia"
color:"#242424"
font.bold: true
styleColor: "#ECECEC"
style: Text.Outline
}
coordinate: poiNokia.coordinate
anchorPoint.x: -poiNokia.sourceItem.width * 0.5
anchorPoint.y: poiNokia.sourceItem.height * 1.5
}
Slider {
id: zoomSlider;
minimum: map.minimumZoomLevel;
maximum: map.maximumZoomLevel;
opacity: 1
visible: parent.visible
z: map.z + 3
anchors {
bottom: parent.bottom;
bottomMargin: 56; rightMargin: 10; leftMargin: 10
left: parent.left
}
width: parent.width - anchors.rightMargin - anchors.leftMargin
value: map.zoomLevel
Binding {
target: zoomSlider; property: "value"; value: map.zoomLevel
}
onReleasedChanged: {
if (released == true){
map.cameraStopped()
released = false
}
}
onValueChanged: {
map.zoomLevel = value
map.state=""
map.resetState()
}
}
Button {
id: languageButton
text: "en"
width: 30
height: 30
z: map.z + 2
anchors.bottom: zoomSlider.top
anchors.bottomMargin: 8
anchors.right: zoomSlider.right
onClicked: {
map.state = "LanguageMenu"
}
}
Menu {
id:languageMenu
horizontalOrientation: false
autoWidth: true
opacity: 0
z: map.z + 4
anchors.bottom: languageButton.top
anchors.right: languageButton.left
onClicked: {
switch (button) {
case "en":
case "fr": {
setLanguage(button);
break;
}
case "Other": {
map.requestLocale()
}
}
map.state = ""
}
Component.onCompleted: {
addItem("en")
addItem("fr")
addItem("Other")
}
}
property RouteQuery routeQuery: RouteQuery {}
property RouteModel routeModel: RouteModel {
plugin : map.plugin
query: routeQuery
onStatusChanged: {
if (status == RouteModel.Ready) {
switch (count) {
case 0:
clearAll()
map.routeError()
break
case 1:
routeInfoModel.update()
break
}
}
else if (status == RouteModel.Error) {
clearAll()
map.routeError()
}
}
function clearAll() {
clear()
routeInfoModel.update()
}
}
property GeocodeModel geocodeModel: GeocodeModel {
plugin : map.plugin;
onStatusChanged:{
if ((status == GeocodeModel.Ready) || (status == GeocodeModel.Error)) map.geocodeFinished()
}
onLocationsChanged:
{
if (count == 1) {
map.center.latitude = get(0).coordinate.latitude
map.center.longitude = get(0).coordinate.longitude
}
}
}
signal showGeocodeInfo()
signal moveMarker()
signal geocodeFinished()
signal routeError()
signal coordinatesCaptured(double latitude, double longitude)
Component.onCompleted: {
markers = new Array();
mapItems = new Array();
}
Component {
id: routeDelegate
MapRoute {
route: routeData
line.color: routeMouseArea.containsMouse ? "lime" : "red"
line.width: 5
smooth: true
opacity: 0.8
MapMouseArea {
id: routeMouseArea
anchors.fill: parent
hoverEnabled: false
onPressed : {
map.resetState();
map.state = ""
map.lastX = mouse.x + parent.x
map.lastY = mouse.y + parent.y
map.pressX = mouse.x + parent.x
map.pressY = mouse.y + parent.y
}
onPositionChanged: {
if (map.state != "RoutePopupMenu" ||
Math.abs(map.pressX - parent.x- mouse.x ) > map.jitterThreshold ||
Math.abs(map.pressY - parent.y -mouse.y ) > map.jitterThreshold) {
map.state = ""
}
if ((mouse.button == Qt.LeftButton) & (map.state == "")) {
map.lastX = mouse.x + parent.x
map.lastY = mouse.y + parent.y
}
}
onPressAndHold:{
if (Math.abs(map.pressX - parent.x- mouse.x ) < map.jitterThreshold
&& Math.abs(map.pressY - parent.y - mouse.y ) < map.jitterThreshold) {
map.state = "RoutePopupMenu"
}
}
}
}
}
Component {
id: pointDelegate
MapCircle {
radius: 1000
color: circleMouseArea.containsMouse ? "lime" : "red"
opacity: 0.6
center: locationData.coordinate
MapMouseArea {
anchors.fill:parent
id: circleMouseArea
hoverEnabled: false
onPressed : {
map.resetState();
map.state = ""
map.lastX = mouse.x + parent.x
map.lastY = mouse.y + parent.y
map.pressX = mouse.x + parent.x
map.pressY = mouse.y + parent.y
}
onPositionChanged: {
if (map.state != "PointPopupMenu" ||
Math.abs(map.pressX - parent.x- mouse.x ) > map.jitterThreshold ||
Math.abs(map.pressY - parent.y -mouse.y ) > map.jitterThreshold) {
map.state = ""
if (pressed) parent.radius = parent.center.distanceTo(mouseToCoordinate(mouse))
}
if ((mouse.button == Qt.LeftButton) & (map.state == "")) {
map.lastX = mouse.x + parent.x
map.lastY = mouse.y + parent.y
}
}
onPressAndHold:{
if (Math.abs(map.pressX - parent.x- mouse.x ) < map.jitterThreshold
&& Math.abs(map.pressY - parent.y - mouse.y ) < map.jitterThreshold) {
map.state = "PointPopupMenu"
}
}
}
}
}
Component {
id: routeInfoDelegate
Row {
spacing: 10
Text {
id: indexText
text: index + 1
color: "#242424"
font.bold: true
font.pixelSize: 14
}
Text {
text: instruction
color: "#242424"
wrapMode: Text.Wrap
width: textArea.width - indexText.width - distanceText.width - spacing*4
font.pixelSize: 14
}
Text {
id: distanceText
text: distance
color: "#242424"
font.bold: true
font.pixelSize: 14
}
}
}
Component{
id: routeInfoHeader
Item {
width: textArea.width
height: travelTime.height + line.anchors.topMargin + line.height
Text {
id: travelTime
text: routeInfoModel.travelTime
color: "#242424"
font.bold: true
font.pixelSize: 14
anchors.left: parent.left
}
Text {
id: distance
text: routeInfoModel.distance
color: "#242424"
font.bold: true
font.pixelSize: 14
anchors.right: parent.right
}
Rectangle {
id: line
color: "#242424"
width: parent.width
height: 2
anchors.left: parent.left
anchors.topMargin: 1
anchors.top: distance.bottom
}
}
}
ListModel {
id: routeInfoModel
property string travelTime
property string distance
function update() {
clear()
if (routeModel.count > 0) {
for (var i = 0; i < routeModel.get(0).segments.length; i++) {
append({
"instruction": routeModel.get(0).segments[i].maneuver.instructionText,
"distance": formatDistance(routeModel.get(0).segments[i].maneuver.distanceToNextInstruction)
});
}
}
travelTime = routeModel.count == 0 ? "" : formatTime(routeModel.get(0).travelTime)
distance = routeModel.count == 0 ? "" : formatDistance(routeModel.get(0).distance)
}
}
MapItemView {
model: routeModel
delegate: routeDelegate
}
MapItemView {
model: geocodeModel
delegate: pointDelegate
}
Item {
id: infoTab
parent: scale.parent
z: map.z + 2
height: parent.height - 180
width: parent.width
x: -5 - infoRect.width
y: 60
visible: (routeInfoModel.count > 0)
Image {
id: catchImage
source: "../../resources/catch.png"
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
MouseArea {
anchors.fill: parent
onClicked: {
if (infoTab.x == -5) infoTab.x -= infoRect.width
else infoTab.x = -5
map.state = ""
}
}
}
Behavior on x {
PropertyAnimation { properties: "x"; duration: 300; easing.type: Easing.InOutQuad }
}
Rectangle {
id: infoRect
width: parent.width - catchImage.sourceSize.width
height: parent.height
color: "#ECECEC"
opacity: 1
radius: 5
MouseArea {
anchors.fill: parent
hoverEnabled: false
}
Item {
id: textArea
anchors.left: parent.left
anchors.leftMargin: 10
anchors.top: parent.top
anchors.topMargin: 10
width: parent.width -15
height: parent.height - 20
ListView {
id: routeInfoView
model: routeInfoModel
delegate: routeInfoDelegate
header: routeInfoHeader
anchors.fill: parent
clip: true
}
}
}
}
Item {
id: scale
parent: zoomSlider.parent
visible: scaleText.text != "0 m"
z: map.z + 2
opacity: 0.6
anchors {
bottom: zoomSlider.top;
bottomMargin: 8;
leftMargin: 20
left: zoomSlider.left
}
Image {
id: scaleImageLeft
source: "../../resources/scale_end.png"
anchors.bottom: parent.bottom
anchors.left: parent.left
}
Image {
id: scaleImage
source: "../../resources/scale.png"
anchors.bottom: parent.bottom
anchors.left: scaleImageLeft.right
}
Image {
id: scaleImageRight
source: "../../resources/scale_end.png"
anchors.bottom: parent.bottom
anchors.left: scaleImage.right
}
Text {
id: scaleText
color: "#004EAE"
horizontalAlignment: Text.AlignHCenter
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.bottomMargin: 3
text: "0 m"
font.pixelSize: 14
}
Component.onCompleted: {
map.calculateScale();
}
}
Timer {
id: scaleTimer
interval: 100
running: false
repeat: false
onTriggered: {
map.calculateScale()
}
}
onCenterChanged:{
scaleTimer.restart()
if (map.followme)
if (map.center != positionSource.position.coordinate) map.followme = false
}
onZoomLevelChanged:{
scaleTimer.restart()
if (map.followme) map.center = positionSource.position.coordinate
}
onWidthChanged:{
scaleTimer.restart()
}
onHeightChanged:{
scaleTimer.restart()
}
Menu {
id: markerMenu
horizontalOrientation: false
autoWidth: true
z: map.z + 4
opacity: 0
width: 150
x: 0
y: 0
onClicked: {
map.state = ""
switch (button) {
case "Delete": {
map.deleteMarker(currentMarker)
break;
}
case "Move to": {
map.moveMarker()
break;
}
case "Coordinates": {
map.coordinatesCaptured(markers[currentMarker].coordinate.latitude, markers[currentMarker].coordinate.longitude)
break;
}
case "Distance to next point": {
showDistance(formatDistance(map.markers[currentMarker].coordinate.distanceTo(map.markers[currentMarker+1].coordinate)));
break;
}
case "Route to next points":
case "Route to next point": {
map.calculateRoute()
break;
}
case "Draw...": {
map.drawItemPopup()
break;
}
}
}
}
Menu {
id: drawMenu
horizontalOrientation: false
autoWidth: true
z: map.z + 4
opacity: 0
width: 150
x: 0
y: 0
onClicked: {
map.state = ""
switch (button) {
case "Polyline": {
addGeoItem("PolylineItem")
break;
}
case "Rectangle": {
addGeoItem("RectangleItem")
break;
}
case "Circle": {
addGeoItem("CircleItem")
break;
}
case "Polygon": {
addGeoItem("PolygonItem")
break;
}
case "Image": {
addGeoItem("ImageItem")
break;
}
case "Video": {
addGeoItem("VideoItem")
break;
}
case "3D QML Item": {
addGeoItem("3dItem")
break;
}
}
}
}
Menu {
id: popupMenu
horizontalOrientation: false
autoWidth: true
z: map.z + 4
opacity: 0
width: 150
x: 0
y: 0
onClicked: {
switch (button) {
case "Add Marker": {
addMarker()
break;
}
case "Get coordinate": {
map.coordinatesCaptured(mouseArea.lastCoordinate.latitude, mouseArea.lastCoordinate.longitude)
break;
}
case "Fit Viewport To Map Items": {
map.fitViewportToMapItems()
break;
}
case "Delete all markers": {
deleteMarkers()
break;
}
case "Delete all items": {
deleteMapItems()
break;
}
}
map.state = ""
}
}
Menu {
id: routeMenu
horizontalOrientation: false
autoWidth: true
z: map.z + 4
opacity: 0
width: 150
x: 0
y: 0
onClicked: {
switch (button) {
case "Delete": {
routeModel.reset()
routeInfoModel.update()
break;
}
}
map.state = ""
}
Component.onCompleted: {
addItem("Delete")
}
}
Menu {
id: pointMenu
horizontalOrientation: false
autoWidth: true
z: map.z + 4
opacity: 0
width: 150
x: 0
y: 0
onClicked: {
switch (button) {
case "Info": {
map.showGeocodeInfo()
break;
}
case "Delete": {
geocodeModel.reset()
break;
}
}
map.state = ""
}
Component.onCompleted: {
addItem("Info")
addItem("Delete")
}
}
Rectangle {
id: infoLabel
width: backgroundRect.width + 10
height: infoText.height + 5
y: 440
anchors.left: map.left
z: map.z + 1
color: "dimgrey"
opacity: (infoText.text !="") ? 0.8 : 0
Behavior on opacity {
NumberAnimation { duration: 200 }
}
Text {
id: infoText
width: parent.width
elide: Text.ElideLeft
maximumLineCount: 4
wrapMode: Text.Wrap
font.bold: true
font.pixelSize: 14
style: Text.Raised;
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 5
anchors.rightMargin: 5
color: "white"
}
}
MapMouseArea {
id: mouseArea
property Coordinate lastCoordinate: Coordinate { latitude : 0; longitude : 0}
anchors.fill: parent
onPressed : {
map.resetState();
map.state = ""
map.lastX = mouse.x
map.lastY = mouse.y
map.pressX = mouse.x
map.pressY = mouse.y
lastCoordinate = mouseArea.mouseToCoordinate(mouse)
}
onPositionChanged: {
if (map.state != "PopupMenu" ||
Math.abs(map.pressX - mouse.x ) > map.jitterThreshold ||
Math.abs(map.pressY - mouse.y ) > map.jitterThreshold) {
map.state = ""
}
if ((mouse.button == Qt.LeftButton) & (map.state == "")) {
map.lastX = mouse.x
map.lastY = mouse.y
}
}
onDoubleClicked: {
map.center = mouseArea.mouseToCoordinate(mouse)
if (mouse.button == Qt.LeftButton){
map.zoomLevel += 1
} else if (mouse.button == Qt.RightButton) map.zoomLevel -= 1
lastX = -1
lastY = -1
}
onPressAndHold:{
if (Math.abs(map.pressX - mouse.x ) < map.jitterThreshold
&& Math.abs(map.pressY - mouse.y ) < map.jitterThreshold) {
popupMenu.clear()
popupMenu.addItem("Add Marker")
popupMenu.addItem("Get coordinate")
popupMenu.addItem("Fit Viewport To Map Items")
if (map.markers.length>0) {
popupMenu.addItem("Delete all markers")
}
if (map.mapItems.length>0) {
popupMenu.addItem("Delete all items")
}
map.state = "PopupMenu"
}
}
}
Keys.onPressed: {
if ((event.key == Qt.Key_Plus) || (event.key == Qt.Key_VolumeUp)) {
map.zoomLevel += 1
} else if ((event.key == Qt.Key_Minus) || (event.key == Qt.Key_VolumeDown)){
map.zoomLevel -= 1
}
}
function calculateScale(){
var coord1, coord2, dist, text, f
f = 0
coord1 = map.toCoordinate(Qt.point(0,scale.y))
coord2 = map.toCoordinate(Qt.point(0+scaleImage.sourceSize.width,scale.y))
dist = Math.round(coord1.distanceTo(coord2))
if (dist === 0) {
} else {
for (var i = 0; i < scaleLengths.length-1; i++) {
if (dist < (scaleLengths[i] + scaleLengths[i+1]) / 2 ) {
f = scaleLengths[i] / dist
dist = scaleLengths[i]
break;
}
}
if (f === 0) {
f = dist / scaleLengths[i]
dist = scaleLengths[i]
}
}
text = formatDistance(dist)
scaleImage.width = (scaleImage.sourceSize.width * f) - 2 * scaleImageLeft.sourceSize.width
scaleText.text = text
}
function deleteMarkers(){
var count = map.markers.length
for (var i = 0; i<count; i++){
map.removeMapItem(map.markers[i])
map.markers[i].destroy()
}
map.markers = []
markerCounter = 0
}
function deleteMapItems(){
var count = map.mapItems.length
for (var i = 0; i<count; i++){
map.removeMapItem(map.mapItems[i])
map.mapItems[i].destroy()
}
map.mapItems = []
}
function addMarker(){
var count = map.markers.length
markerCounter++
var marker = Qt.createQmlObject ('Marker {}', map)
map.addMapItem(marker)
marker.z = map.z+1
var myArray = new Array()
for (var i = 0; i<count; i++){
myArray.push(markers[i])
}
myArray.push(marker)
markers = myArray
}
function addGeoItem(item){
var count = map.mapItems.length
var co = Qt.createComponent(item+'.qml')
if (co.status == Component.Ready) {
var o = co.createObject(map)
o.setGeometry(map.markers, currentMarker)
map.addMapItem(o)
var myArray = new Array()
for (var i = 0; i<count; i++){
myArray.push(mapItems[i])
}
myArray.push(o)
mapItems = myArray
} else {
console.log(item + " is not supported right now, please call us later.")
}
}
function deleteMarker(index){
var myArray = new Array()
var count = map.markers.length
for (var i = 0; i<count; i++){
if (index != i) myArray.push(map.markers[i])
}
map.removeMapItem(map.markers[index])
map.markers[index].destroy()
map.markers = myArray
if (markers.length == 0) markerCounter = 0
}
function markerPopup(){
var array
var length = map.markers.length
markerMenu.clear()
markerMenu.addItem("Delete")
markerMenu.addItem("Coordinates")
markerMenu.addItem("Move to")
markerMenu.addItem("Draw...")
if (currentMarker == length-2){
markerMenu.addItem("Route to next point")
markerMenu.addItem("Distance to next point")
}
if (currentMarker < length-2){
markerMenu.addItem("Route to next points")
markerMenu.addItem("Distance to next point")
}
map.state = "MarkerPopupMenu"
}
function drawItemPopup(){
var array
var length = map.markers.length
drawMenu.clear()
drawMenu.addItem("Image")
drawMenu.addItem("Video")
drawMenu.addItem("3D QML Item")
if (currentMarker <= length-2){
drawMenu.addItem("Rectangle")
drawMenu.addItem("Circle")
drawMenu.addItem("Polyline")
}
if (currentMarker < length-2){
drawMenu.addItem("Polygon")
}
map.state = "DrawItemMenu"
}
function calculateRoute(){
routeQuery.clearWaypoints();
for (var i = currentMarker; i< map.markers.length; i++){
routeQuery.addWaypoint(markers[i].coordinate)
}
routeQuery.travelModes = RouteQuery.CarTravel
routeQuery.routeOptimizations = RouteQuery.ShortestRoute
routeQuery.setFeatureWeight(0, 0)
routeModel.update();
}
function roundNumber(number, digits) {
var multiple = Math.pow(10, digits);
return Math.round(number * multiple) / multiple;
}
function formatTime(sec){
var value = sec
var seconds = value % 60
value /= 60
value = (value > 1) ? Math.round(value) : 0
var minutes = value % 60
value /= 60
value = (value > 1) ? Math.round(value) : 0
var hours = value
if (hours > 0) value = hours + "h:"+ minutes + "m"
else value = minutes + "min"
return value
}
function formatDistance(meters)
{
var dist = Math.round(meters)
if (dist > 1000 ){
if (dist > 100000){
dist = Math.round(dist / 1000)
}
else{
dist = Math.round(dist / 100)
dist = dist / 10
}
dist = dist + " km"
}
else{
dist = dist + " m"
}
return dist
}
function setLanguage(lang) {
map.plugin.locales = lang;
if (map.plugin.locales.length > 0) {
languageButton.text = map.plugin.locales[0];
}
}
states: [
State {
name: "PopupMenu"
PropertyChanges { target: popupMenu; opacity: 1}
PropertyChanges { target: popupMenu; x: ((map.lastX + popupMenu.width > map.width) ? map.width - popupMenu.width : map.lastX)}
PropertyChanges { target: popupMenu; y: ((map.lastY + popupMenu.height > map.height - 40) ? map.height - popupMenu.height - 40 : map.lastY)}
},
State {
name: "MarkerPopupMenu"
PropertyChanges { target: markerMenu; opacity: 1}
PropertyChanges { target: markerMenu; x: ((markers[currentMarker].lastMouseX + markerMenu.width > map.width) ? map.width - markerMenu.width : markers[currentMarker].lastMouseX )}
PropertyChanges { target: markerMenu; y: ((markers[currentMarker].lastMouseY + markerMenu.height > map.height - 40) ? map.height - markerMenu.height - 40 : markers[currentMarker].lastMouseY)}
},
State {
name: "DrawItemMenu"
PropertyChanges { target: drawMenu; opacity: 1}
PropertyChanges { target: drawMenu; x: ((markers[currentMarker].lastMouseX + drawMenu.width > map.width) ? map.width - drawMenu.width : markers[currentMarker].lastMouseX )}
PropertyChanges { target: drawMenu; y: ((markers[currentMarker].lastMouseY + drawMenu.height > map.height - 40) ? map.height - drawMenu.height - 40 : markers[currentMarker].lastMouseY)}
},
State {
name: "RoutePopupMenu"
PropertyChanges { target: routeMenu; opacity: 1}
PropertyChanges { target: routeMenu; x: ((map.lastX + routeMenu.width > map.width) ? map.width - routeMenu.width : map.lastX)}
PropertyChanges { target: routeMenu; y: ((map.lastY + routeMenu.height > map.height - 40) ? map.height - routeMenu.height - 40 : map.lastY)}
},
State {
name: "PointPopupMenu"
PropertyChanges { target: pointMenu; opacity: 1}
PropertyChanges { target: pointMenu; x: ((map.lastX + pointMenu.width > map.width) ? map.width - pointMenu.width : map.lastX)}
PropertyChanges { target: pointMenu; y: ((map.lastY + pointMenu.height > map.height - 40) ? map.height - pointMenu.height - 40 : map.lastY)}
},
State {
name: "LanguageMenu"
PropertyChanges { target: languageMenu; opacity: 1}
}
]
}