Edit and sync features

Loading

Code

import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import Esri.ArcGISRuntime 100.2
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: "http://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)
    }
}


In this topic
  1. Code