import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import Esri.ArcGISRuntime 100.4
import Esri.ArcGISExtras 1.1
Rectangle {
width: 800
height: 600
property real scaleFactor: System.displayScaleFactor
property url dataPath: System.userHomePath + "/ArcGIS/Runtime/Data/"
property url outputGdb: System.temporaryFolder.url + "/WildfireQml_%1.geodatabase".arg(new Date().getTime().toString())
property string featureServiceUrl: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer"
property Envelope generateExtent: null
property string statusText: ""
property string featureLayerId: "0"
property string instructionText: ""
property bool isOffline: false
property var selectedFeature: null
property Geodatabase offlineGdb: null
MapView {
id: mapView
anchors.fill: parent
Map {
id: map
Basemap {
ArcGISTiledLayer {
TileCache {
path: dataPath + "tpk/SanFrancisco.tpk"
}
}
}
FeatureLayer {
ServiceFeatureTable {
url: featureServiceUrl + "/" + featureLayerId
}
}
// set an initial viewpoint
ViewpointExtent {
Envelope {
xMax: -122.43843016064368
xMin: -122.50017717584528
yMax: 37.81638388695054
yMin: 37.745000054347535
spatialReference: SpatialReference.createWgs84()
}
}
}
onMouseClicked: {
if (isOffline && !selectedFeature) {
// identify to select a feature
mapView.identifyLayer(map.operationalLayers.get(0), mouse.x, mouse.y, 5, false);
} else if (isOffline && selectedFeature) {
// connect to feature table signal
var featureTable = map.operationalLayers.get(0).featureTable;
featureTable.updateFeatureStatusChanged.connect(function() {
if (featureTable.updateFeatureStatus === Enums.TaskStatusCompleted) {
// clear selections
featureTable.featureLayer.clearSelection();
selectedFeature = null;
instructionText = "Tap the sync button";
syncButton.visible = true;
}
});
// update selected feature's geometry
selectedFeature.geometry = mouse.mapPoint;
featureTable.updateFeature(selectedFeature);
}
}
onIdentifyLayerStatusChanged: {
if (identifyLayerStatus === Enums.TaskStatusCompleted) {
var featureLayer = map.operationalLayers.get(0);
// clear any previous selections
featureLayer.clearSelection();
selectedFeature = null;
// select the feature
if (identifyLayerResult.geoElements.length > 0) {
var geoElement = identifyLayerResult.geoElements[0];
featureLayer.selectFeature(geoElement);
selectedFeature = geoElement;
instructionText = "Tap on map to move feature";
}
}
}
}
// create the GeodatabaseSyncTask to generate the local geodatabase
GeodatabaseSyncTask {
id: geodatabaseSyncTask
url: featureServiceUrl
property var generateJob
property var syncJob
function executeGenerate() {
// execute the asynchronous task and obtain the job
generateJob = generateGeodatabase(generateParameters, outputGdb);
// check if the job is valid
if (generateJob) {
// show the sync window
syncWindow.visible = true;
// connect to the job's status changed signal to know once it is done
generateJob.jobStatusChanged.connect(updateGenerateJobStatus);
// start the job
generateJob.start();
} else {
// a valid job was not obtained, so show an error
syncWindow.visible = true;
statusText = "Generate failed";
syncWindow.hideWindow(5000);
}
}
function updateGenerateJobStatus() {
switch(generateJob.jobStatus) {
case Enums.JobStatusFailed:
statusText = "Generate failed";
syncWindow.hideWindow(5000);
break;
case Enums.JobStatusNotStarted:
statusText = "Job not started";
break;
case Enums.JobStatusPaused:
statusText = "Job paused";
break;
case Enums.JobStatusStarted:
statusText = "In progress...";
break;
case Enums.JobStatusSucceeded:
statusText = "Complete";
syncWindow.hideWindow(1500);
offlineGdb = generateJob.geodatabase;
displayLayersFromGeodatabase();
isOffline = true;
break;
default:
break;
}
}
function executeSync() {
// execute the asynchronous task and obtain the job
syncJob = syncGeodatabase(syncParameters, offlineGdb);
// check if the job is valid
if (syncJob) {
// show the sync window
syncWindow.visible = true;
// connect to the job's status changed signal to know once it is done
syncJob.jobStatusChanged.connect(updateSyncJobStatus);
// start the job
syncJob.start();
} else {
// a valid job was not obtained, so show an error
syncWindow.visible = true;
statusText = "Sync failed";
syncWindow.hideWindow(5000);
}
}
function updateSyncJobStatus() {
switch(syncJob.jobStatus) {
case Enums.JobStatusFailed:
statusText = "Sync failed";
syncWindow.hideWindow(5000);
break;
case Enums.JobStatusNotStarted:
statusText = "Job not started";
break;
case Enums.JobStatusPaused:
statusText = "Job paused";
break;
case Enums.JobStatusStarted:
statusText = "In progress...";
break;
case Enums.JobStatusSucceeded:
statusText = "Complete";
syncWindow.hideWindow(1500);
isOffline = true;
break;
default:
break;
}
}
// ...
function displayLayersFromGeodatabase() {
// remove the original online feature layers
map.operationalLayers.clear();
// load the geodatabase to access the feature tables
offlineGdb.loadStatusChanged.connect(function() {
if (offlineGdb.loadStatus === Enums.LoadStatusLoaded) {
// create a feature layer from each feature table, and add to the map
for (var i = 0; i < offlineGdb.geodatabaseFeatureTables.length; i++) {
var featureTable = offlineGdb.geodatabaseFeatureTables[i];
var featureLayer = ArcGISRuntimeEnvironment.createObject("FeatureLayer");
featureLayer.featureTable = featureTable;
map.operationalLayers.append(featureLayer);
}
// hide the extent rectangle and download button
extentRectangle.visible = false;
syncButton.visible = false;
}
});
offlineGdb.load();
}
Component.onDestruction: {
generateJob.jobStatusChanged.disconnect(updateGenerateJobStatus);
syncJob.jobStatusChanged.disconnect(updateSyncJobStatus);
}
}
// create the generate geodatabase parameters
GenerateGeodatabaseParameters {
id: generateParameters
extent: generateExtent
outSpatialReference: SpatialReference { wkid: 3857 }
returnAttachments: false
// only generate a geodatabase with 1 layer
layerOptions: [
GenerateLayerOption {
layerId: featureLayerId
}
]
}
// create the sync geodatabase parameters
SyncGeodatabaseParameters {
id: syncParameters
// only sync the 1 layer
layerOptions: [
SyncLayerOption {
layerId: featureLayerId
}
]
// push up edits, and receive any new edits
geodatabaseSyncDirection: Enums.SyncDirectionBidirectional
}
// create an extent rectangle for the output geodatabase
Rectangle {
id: extentRectangle
anchors.centerIn: parent
width: parent.width - (50 * scaleFactor)
height: parent.height - (125 * scaleFactor)
color: "transparent"
border {
color: "red"
width: 3 * scaleFactor
}
}
// Create the button to generate/sync geodatabase
Rectangle {
id: syncButton
property bool pressed: false
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 23 * scaleFactor
}
width: isOffline ? 175 * scaleFactor : 200 * scaleFactor
height: 35 * scaleFactor
color: pressed ? "#959595" : "#D6D6D6"
radius: 8
border {
color: "#585858"
width: 1 * scaleFactor
}
Row {
anchors.fill: parent
spacing: 5
Image {
width: 38 * scaleFactor
height: width
source: isOffline ? "qrc:/Samples/EditData/EditAndSyncFeatures/sync.png" : "qrc:/Samples/EditData/EditAndSyncFeatures/download.png"
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: isOffline ? "Sync Geodatabase" : "Generate Geodatabase"
font.pixelSize: 14 * scaleFactor
color: "#474747"
}
}
MouseArea {
anchors.fill: parent
onPressed: syncButton.pressed = true
onReleased: syncButton.pressed = false
onClicked: {
if (isOffline) {
instructions.visible = false;
geodatabaseSyncTask.executeSync();
} else {
getRectangleEnvelope();
geodatabaseSyncTask.executeGenerate();
}
}
function getRectangleEnvelope() {
var corner1 = mapView.screenToLocation(extentRectangle.x, extentRectangle.y);
var corner2 = mapView.screenToLocation((extentRectangle.x + extentRectangle.width), (extentRectangle.y + extentRectangle.height));
var envBuilder = ArcGISRuntimeEnvironment.createObject("EnvelopeBuilder");
envBuilder.setCorners(corner1, corner2);
generateExtent = GeometryEngine.project(envBuilder.geometry, SpatialReference.createWebMercator());
}
}
}
// Create a bar to display editing instructions
Rectangle {
id: instructions
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: 25 * scaleFactor
color: "gray"
opacity: 0.9
visible: false
Text {
anchors.centerIn: parent
text: instructionText
font.pixelSize: 16 * scaleFactor
color: "white"
}
}
// Create a window to display the generate/sync window
Rectangle {
id: syncWindow
anchors.fill: parent
color: "transparent"
clip: true
visible: false
RadialGradient {
anchors.fill: parent
opacity: 0.7
gradient: Gradient {
GradientStop { position: 0.0; color: "lightgrey" }
GradientStop { position: 0.7; color: "black" }
}
}
MouseArea {
anchors.fill: parent
onClicked: mouse.accepted = true
onWheel: wheel.accepted = true
}
Rectangle {
anchors.centerIn: parent
width: 125 * scaleFactor
height: 100 * scaleFactor
color: "lightgrey"
opacity: 0.8
radius: 5
border {
color: "#4D4D4D"
width: 1 * scaleFactor
}
Column {
anchors {
fill: parent
margins: 10 * scaleFactor
}
spacing: 10
BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: statusText
font.pixelSize: 16 * scaleFactor
}
}
}
Timer {
id: hideWindowTimer
onTriggered: {
syncWindow.visible = false;
instructions.visible = true;
instructionText = "Tap on a feature";
}
}
function hideWindow(time) {
hideWindowTimer.interval = time;
hideWindowTimer.restart();
}
}
FileFolder {
url: dataPath
// create the data path if it does not yet exist
Component.onCompleted: {
if (!exists) {
makePath(dataPath);
}
}
}
Component.onDestruction: {
// unregister the replica
if (offlineGdb)
geodatabaseSyncTask.unregisterGeodatabase(offlineGdb)
}
}
Loading
Code