Take a web map offline with additional options for each layer.

Use case
When taking a web map offline, you may adjust the data (such as layers or tiles) that is downloaded by using custom parameter overrides. This can be used to reduce the extent of the map or the download size of the offline map. It can also be used to highlight specific data by removing irrelevant data. Additionally, this workflow allows you to take features offline that don’t have a geometry - for example, features whose attributes have been populated in the office, but still need a site survey for their geometry.
How to use the sample
- Click on “Generate Offline Map (Overrides)”.
- Use the range slider to adjust the min/max levelIds to be taken offline for the Streets basemap.
- Use the spin-box to set the buffer radius for the streets basemap.
- Click the buttons to skip the System Valves and Service Connections layers.
- Use the combo-box to select the maximum flow rate for the features from the Hydrant layer.
- Use the check-box to skip the geometry filter for the water pipes features.
- Click “Start Job”
- Wait for the progress view to indicate that the task has completed.
- You should see that the basemap does not display when you zoom out past a certain range and is padded around the original area of interest. The network dataset should extend beyond the target area. The System Valves and Service Connections should be omitted from the offline map and the Hydrants layer should contain a subset of the original features.
How it works
The sample creates a PortalItem object using a web map’s ID. Create a Map with this portal item. Use the Map to initialize an OfflineMapTask object. When the button is clicked, the sample requests the default parameters for the task, with the selected extent, by calling OfflineMapTask::createDefaultGenerateOfflineMapParameters. Once the parameters are retrieved, they are used to create a set of GenerateOfflineMapParameterOverrides by calling OfflineMapTask::createGenerateOfflineMapParameterOverrides. The overrides are then adjusted so that specific layers will be taken offline using custom settings.
Streets basemap (adjust scale range)
In order to minimize the download size for offline map, this sample reduces the scale range for the “World Streets Basemap” layer by adjusting the relevant ExportTileCacheParameters in the GenerateOfflineMapParameterOverrides. The basemap layer is used to contsruct an OfflineMapParametersKeyobject. The key is then used to retrieve the specific ExportTileCacheParameters for the basemap and the levelIds are updated to skip unwanted levels of detail (based on the values selected in the UI). Note that the original “Streets” basemap is swapped for the “for export” version of the service - see https://www.arcgis.com/home/item.html?id=e384f5aa4eb1433c92afff09500b073d.
Streets Basemap (buffer extent)
To provide context beyond the study area, the extent for streets basemap is padded. Again, the key for the basemap layer is used to obtain the key and the default extent Geometry is retrieved. This extent is then padded (by the distance specified in the UI) using the GeoemetryEngine::bufferGeodesic function and applied to the ExportTileCacheParameters.
System Valves and Service Connections (skip layers)
In this example, the survey is primarily concerned with the Hydrants layer, so other information is not taken offline: this keeps the download smaller and reduces clutter in the offline map. The two layers “System Valves” and “Service Connections” are retrieved from the operational layers list of the map. They are then used to construct an OfflineMapParametersKey. This key is used to obtain the relevant GenerateGeodatabaseParameters from the GenerateOfflineMapParameterOverrides::generateGeodatabaseParameters property. The GenerateLayerOption for each of the layers is removed from the geodatabse parameters layerOptions by checking for the FeatureLayer::serviceLayerId. Note, that you could also choose to download only the schema for these layers by setting the GenerateLayerOption::queryOption to GenerateLayerQueryOption::None.
Hydrant Layer (filter features)
Next, the hydrant layer is filtered to exclude certain features. This approach could be taken if the offline map is intended for use with only certain data - for example, where a re-survey is required. To achieve this, a whereClause (for example, “Flow Rate (GPM) < 500”) needs to be applied to the hydrant’s GenerateLayerOption in the GenerateGeodatabaseParameters. The minimum flow rate value is obtained from the UI setting. The sample constructs a key object from the hydrant layer as in the previous step, and iterates over the available GenerateGeodatabaseParameters until the correct one is found and the GenerateLayerOption can be updated.
Water Pipes Dataset (skip geometry filter)
Lastly, the water network dataset is adjusted so that the features are downloaded for the entire dataset - rather than clipped to the area of interest. Again, the key for the layer is constructed using the layer and the relevant GenerateGeodatabaseParameters are obtained from the overrides dictionary. The layer options are then adjusted to set useGeometry to false.
Having adjusted the GenerateOfflineMapParameterOverrides to reflect the custom requirements for the offline map, the original parameters and the custom overrides, along with the download path for the offline map, are then used to create a GenerateOfflineMapJob object from the offline map task. This job is then started and on successful completion the offline map is added to the map view. To provide feedback to the user, the progress property of GenerateOfflineMapJob is displayed in a window.
Relevant API
- ExportTileCacheParameters
- GenerateGeodatabaseParameters
- GenerateLayerOption
- GenerateOfflineMapJob
- GenerateOfflineMapParameterOverrides
- GenerateOfflineMapParameters
- GenerateOfflineMapResult
- OfflineMapParametersKey
- OfflineMapTask
Additional information
For applications where you just need to take all layers offline, use the standard workflow (using only GenerateOfflineMapParameters). For a simple example of how you take a map offline, please consult the “Generate offline map” sample.
Tags
adjust, download, extent, filter, LOD, offline, override, parameters, reduce, scale range, setting
Sample Code
// Copyright 2018 Esri.
// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.
import QtQuick
// Create the download button to export the tile cacheRectangle { property bool pressed: false signal buttonClicked()
width: 265 height: 35 color: pressed ? "#959595" : "#D6D6D6" radius: 5 border { color: "#585858" width: 1 }
Row { anchors.fill: parent spacing: 5 Image { width: 38 height: width source: "qrc:/Samples/Maps/GenerateOfflineMap_Overrides/download.png" } Text { anchors.verticalCenter: parent.verticalCenter text: "Generate Offline Map (Overrides)" font.pixelSize: 14 color: "#474747" } }
MouseArea { anchors.fill: parent onPressed: downloadButton.pressed = true onReleased: downloadButton.pressed = false onClicked: { buttonClicked(); } }}// [WriteFile Name=GenerateOfflineMap_Overrides, Category=Maps]// [Legal]// Copyright 2018 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
#ifdef PCH_BUILD#include "pch.hpp"#endif // PCH_BUILD
// sample headers#include "GenerateOfflineMap_Overrides.h"
// ArcGIS Maps SDK headers#include "ArcGISFeatureLayerInfo.h"#include "Basemap.h"#include "Envelope.h"#include "Error.h"#include "ExportTileCacheParameters.h"#include "FeatureLayer.h"#include "GenerateGeodatabaseParameters.h"#include "GenerateLayerOption.h"#include "GenerateOfflineMapJob.h"#include "GenerateOfflineMapParameterOverrides.h"#include "GenerateOfflineMapResult.h"#include "GeometryEngine.h"#include "LayerListModel.h"#include "Map.h"#include "MapQuickView.h"#include "MapTypes.h"#include "OfflineMapParametersKey.h"#include "OfflineMapTask.h"#include "OfflineMapTypes.h"#include "Point.h"#include "Polygon.h"#include "Portal.h"#include "PortalItem.h"#include "ServiceFeatureTable.h"#include "SpatialReference.h"#include "TaskTypes.h"
// Qt headers#include <QFuture>#include <QUuid>
using namespace Esri::ArcGISRuntime;
const QString GenerateOfflineMap_Overrides::s_webMapId = QStringLiteral("acc027394bc84c2fb04d1ed317aac674");
QString GenerateOfflineMap_Overrides::webMapId(){ return s_webMapId;}
GenerateOfflineMap_Overrides::GenerateOfflineMap_Overrides(QQuickItem* parent /* = nullptr */): QQuickItem(parent){}
void GenerateOfflineMap_Overrides::init(){ // Register the map view for QML qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<GenerateOfflineMap_Overrides>("Esri.Samples", 1, 0, "GenerateOfflineMap_OverridesSample");}
void GenerateOfflineMap_Overrides::componentComplete(){ QQuickItem::componentComplete();
// find QML MapView component m_mapView = findChild<MapQuickView*>("mapView");
// Create a Portal Item for use by the Map and OfflineMapTask const bool loginRequired = false; Portal* portal = new Portal(loginRequired, this); m_portalItem = new PortalItem(portal, webMapId(), this);
// Create a map from the Portal Item m_map = new Map(m_portalItem, this);
// Update property once map is done loading connect(m_map, &Map::doneLoading, this, [this](const Error& e) { if (!e.isEmpty()) return;
m_mapLoaded = true; emit mapLoadedChanged(); });
// Set map to map view m_mapView->setMap(m_map);
// Create the OfflineMapTask with the online map m_offlineMapTask = new OfflineMapTask(m_map, this);
// connect to the error signal connect(m_offlineMapTask, &OfflineMapTask::errorOccurred, this, [](const Error& e) { if (e.isEmpty()) return;
qDebug() << e.message() << e.additionalMessage(); });}
void GenerateOfflineMap_Overrides::setAreaOfInterest(double xCorner1, double yCorner1, double xCorner2, double yCorner2){ // create an envelope from the QML rectangle corners const Point corner1 = m_mapView->screenToLocation(xCorner1, yCorner1); const Point corner2 = m_mapView->screenToLocation(xCorner2, yCorner2); const Envelope extent(corner1, corner2); const Envelope mapExtent = geometry_cast<Envelope>(GeometryEngine::project(extent, SpatialReference::webMercator()));
// generate parameters m_offlineMapTask->createDefaultGenerateOfflineMapParametersAsync(mapExtent).then(this, [this](const GenerateOfflineMapParameters& params) { m_parameters = params; m_offlineMapTask->createGenerateOfflineMapParameterOverridesAsync(params).then(this, [this](GenerateOfflineMapParameterOverrides* parameterOverrides) { m_parameterOverrides = parameterOverrides; emit overridesReadyChanged(); setBusy(false); emit taskBusyChanged(); }); });
setBusy(true); emit taskBusyChanged();}
void GenerateOfflineMap_Overrides::setBasemapLOD(int min, int max){ if (!overridesReady()) return;
LayerListModel* layers = m_map->basemap()->baseLayers(); if (!layers || layers->isEmpty()) return;
// Obtain a key for the given basemap-layer. OfflineMapParametersKey keyForTiledLayer(layers->at(0));
// Check that the key is valid. if (keyForTiledLayer.isEmpty() || keyForTiledLayer.type() != OfflineMapParametersType::ExportTileCache) return;
// Obtain the dictionary of parameters for taking the basemap offline. QMap<OfflineMapParametersKey, ExportTileCacheParameters> dictionary = m_parameterOverrides->exportTileCacheParameters(); if (!dictionary.contains(keyForTiledLayer)) return;
// Create a new sublist of LODs in the range requested by the user. QList<int> newLODs; for (int i = min; i < max; ++i ) newLODs.append(i);
// Apply the sublist as the LOD level in tilecache parameters for the given // service. ExportTileCacheParameters& exportTileCacheParam = dictionary[keyForTiledLayer]; exportTileCacheParam.setLevelIds(newLODs);
m_parameterOverrides->setExportTileCacheParameters(dictionary);}
void GenerateOfflineMap_Overrides::setBasemapBuffer(int bufferMeters){ if (!overridesReady()) return;
LayerListModel* layers = m_map->basemap()->baseLayers(); if (!layers || layers->isEmpty()) return;
OfflineMapParametersKey keyForTiledLayer(layers->at(0));
if (keyForTiledLayer.isEmpty() || keyForTiledLayer.type() != OfflineMapParametersType::ExportTileCache) return;
// Obtain the dictionary of parameters for taking the basemap offline. QMap<OfflineMapParametersKey, ExportTileCacheParameters> dictionary = m_parameterOverrides->exportTileCacheParameters(); if (!dictionary.contains(keyForTiledLayer)) return;
// Create a new geometry around the original area of interest. auto bufferGeom = GeometryEngine::buffer(m_parameters.areaOfInterest(), bufferMeters);
// Apply the geometry to the ExportTileCacheParameters. ExportTileCacheParameters& exportTileCacheParam = dictionary[keyForTiledLayer];
// Set the parameters back into the dictionary. exportTileCacheParam.setAreaOfInterest(bufferGeom);
// Store the dictionary. m_parameterOverrides->setExportTileCacheParameters(dictionary);}
void GenerateOfflineMap_Overrides::removeSystemValves(){ removeFeatureLayer(QStringLiteral("System Valve"));}
void GenerateOfflineMap_Overrides::removeServiceConnection(){ removeFeatureLayer(QStringLiteral("Service Connection"));}
void GenerateOfflineMap_Overrides::setHydrantWhereClause(const QString& whereClause){ if (!overridesReady()) return;
FeatureLayer* targetLayer = getFeatureLayerByName(QStringLiteral("Hydrant"));
if (!targetLayer) return;
// Get the parameter key for the layer. OfflineMapParametersKey keyForTargetLayer(targetLayer);
if (keyForTargetLayer.isEmpty() || keyForTargetLayer.type() != OfflineMapParametersType::GenerateGeodatabase) return;
// Get the dictionary of GenerateGeoDatabaseParameters. QMap<OfflineMapParametersKey, GenerateGeodatabaseParameters> dictionary = m_parameterOverrides->generateGeodatabaseParameters();
auto keyIt = dictionary.find(keyForTargetLayer); if (keyIt == dictionary.end()) return;
// Grab the GenerateGeoDatabaseParameters associated with the given key. GenerateGeodatabaseParameters& generateGdbParam = keyIt.value();
ServiceFeatureTable* table = qobject_cast<ServiceFeatureTable*>(targetLayer->featureTable()); if (!table) return;
// Get the service layer id for the given layer. const qint64 targetLayerId = table->layerInfo().serviceLayerId();
// Set the whereClause on the required layer option. QList<GenerateLayerOption> layerOptions = generateGdbParam.layerOptions(); for (auto& it : layerOptions) { GenerateLayerOption& option = it; if (option.layerId() == targetLayerId) { option.setWhereClause(whereClause); option.setQueryOption(GenerateLayerQueryOption::UseFilter); break; } }
// Add layer options back to parameters and re-add to the dictionary. generateGdbParam.setLayerOptions(layerOptions);
dictionary[keyForTargetLayer] = generateGdbParam; m_parameterOverrides->setGenerateGeodatabaseParameters(dictionary);}
void GenerateOfflineMap_Overrides::setClipWaterPipesAOI(bool clip){ if (!overridesReady()) return;
FeatureLayer* targetLayer = getFeatureLayerByName(QStringLiteral("Main"));
if (!targetLayer) return;
// Get the parameter key for the layer. OfflineMapParametersKey keyForTargetLayer(targetLayer);
if (keyForTargetLayer.isEmpty() || keyForTargetLayer.type() != OfflineMapParametersType::GenerateGeodatabase) return;
// Get the dictionary of GenerateGeoDatabaseParameters. QMap<OfflineMapParametersKey, GenerateGeodatabaseParameters> dictionary = m_parameterOverrides->generateGeodatabaseParameters();
auto keyIt = dictionary.find(keyForTargetLayer); if (keyIt == dictionary.end()) return;
// Grab the GenerateGeoDatabaseParameters associated with the given key. GenerateGeodatabaseParameters& generateGdbParam = keyIt.value();
ServiceFeatureTable* table = qobject_cast<ServiceFeatureTable*>(targetLayer->featureTable()); if (!table) return;
// Get the service layer id for the given layer. const qint64 targetLayerId = table->layerInfo().serviceLayerId();
// Set whether to use the geometry filter to clip the waterpipes. // If not then we get all the features. QList<GenerateLayerOption> layerOptions = generateGdbParam.layerOptions(); for (auto& it : layerOptions) { GenerateLayerOption& option = it; if (option.layerId() == targetLayerId) { option.setUseGeometry(clip); break; } }
// Add layer options back to parameters and re-add to the dictionary. generateGdbParam.setLayerOptions(layerOptions);
dictionary[keyForTargetLayer] = generateGdbParam; m_parameterOverrides->setGenerateGeodatabaseParameters(dictionary);}
void GenerateOfflineMap_Overrides::takeMapOffline(){ if (!overridesReady()) return;
// create temp data path for offline mmpk const QString dataPath = m_tempPath.path() + "/offlinemap_overrides.mmpk";
// Take the map offline with the original paramaters and the customized overrides. GenerateOfflineMapJob* generateJob = m_offlineMapTask->generateOfflineMap(m_parameters, dataPath, m_parameterOverrides);
// check if there is a valid job if (!generateJob) return;
// connect to the job's status changed signal connect(generateJob, &GenerateOfflineMapJob::statusChanged, this, [this, generateJob](JobStatus jobStatus) { // connect to the job's status changed signal to know once it is done switch (jobStatus) { case JobStatus::Failed: emit updateStatus("Generate failed"); emit hideWindow(5000, false); break; case JobStatus::NotStarted: emit updateStatus("Job not started"); break; case JobStatus::Paused: emit updateStatus("Job paused"); break; case JobStatus::Started: emit updateStatus("In progress"); break; case JobStatus::Succeeded: // show any layer errors if (generateJob->result()->hasErrors()) { QString layerErrors = ""; const QMap<Layer*, Error>& layerErrorsMap = generateJob->result()->layerErrors(); for (auto it = layerErrorsMap.cbegin(); it != layerErrorsMap.cend(); ++it) { layerErrors += it.key()->name() + ": " + it.value().message() + "\n"; } emit showLayerErrors(layerErrors); }
// show the map emit updateStatus("Complete"); emit hideWindow(1500, true); m_mapView->setMap(generateJob->result()->offlineMap(this)); break; default: // do nothing break; } });
// connect to progress changed signal connect(generateJob, &GenerateOfflineMapJob::progressChanged, this, [this, generateJob]() { emit updateProgress(generateJob->progress()); });
// connect to the error signal connect(generateJob, &GenerateOfflineMapJob::errorOccurred, this, [](const Error& e) { if (e.isEmpty()) return;
qDebug() << e.message() << e.additionalMessage(); });
// start the generate job generateJob->start();}
bool GenerateOfflineMap_Overrides::taskBusy() const{ return m_taskBusy;}
bool GenerateOfflineMap_Overrides::overridesReady() const{ return m_parameterOverrides;}
void GenerateOfflineMap_Overrides::removeFeatureLayer(const QString& layerName){ if (!overridesReady()) return;
FeatureLayer* targetLayer = getFeatureLayerByName(layerName); if (!targetLayer) return;
// Get the parameter key for the layer. OfflineMapParametersKey keyForTargetLayer(targetLayer);
if (keyForTargetLayer.isEmpty() || keyForTargetLayer.type() != OfflineMapParametersType::GenerateGeodatabase) return;
// Get the dictionary of GenerateGeoDatabaseParameters. QMap<OfflineMapParametersKey, GenerateGeodatabaseParameters> dictionary = m_parameterOverrides->generateGeodatabaseParameters();
auto keyIt = dictionary.find(keyForTargetLayer); if (keyIt == dictionary.end()) return;
// Grab the GenerateGeoDatabaseParameters associated with the given key. GenerateGeodatabaseParameters& generateGdbParam = keyIt.value();
ServiceFeatureTable* table = qobject_cast<ServiceFeatureTable*>(targetLayer->featureTable()); if (!table) return;
// Get the service layer id for the given layer. const qint64 targetLayerId = table->layerInfo().serviceLayerId();
// Remove the layer option from the list. QList<GenerateLayerOption> layerOptions = generateGdbParam.layerOptions(); for (auto it = layerOptions.begin(); it != layerOptions.end(); ++it) { if (it->layerId() == targetLayerId) { layerOptions.erase(it); break; } }
// Add layer options back to parameters and re-add to the dictionary. generateGdbParam.setLayerOptions(layerOptions);
dictionary[keyForTargetLayer] = generateGdbParam; m_parameterOverrides->setGenerateGeodatabaseParameters(dictionary);}
FeatureLayer* GenerateOfflineMap_Overrides::getFeatureLayerByName(const QString& layerName){ // Find the feature layer with the given name LayerListModel* opLayers = m_map->operationalLayers(); for (int i = 0; i < opLayers->rowCount(); ++i) { Layer* candidateLayer = opLayers->at(i); if (!candidateLayer) continue;
if (candidateLayer->layerType() == LayerType::FeatureLayer && candidateLayer->name().contains(layerName, Qt::CaseInsensitive)) { return qobject_cast<FeatureLayer*>(candidateLayer); } } return nullptr;}
void GenerateOfflineMap_Overrides::setBusy(bool busy){ m_taskBusy = busy; emit taskBusyChanged();}
bool GenerateOfflineMap_Overrides::mapLoaded() const{ return m_mapLoaded;}// [WriteFile Name=GenerateOfflineMap_Overrides, Category=Maps]// [Legal]// Copyright 2018 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
#ifndef GENERATEOFFLINEMAP_OVERRIDES_H#define GENERATEOFFLINEMAP_OVERRIDES_H
// ArcGIS Maps SDK headers#include "GenerateOfflineMapParameters.h"
// Qt headers#include <QQuickItem>#include <QTemporaryDir>
namespace Esri::ArcGISRuntime{class FeatureLayer;class GenerateOfflineMapParameterOverrides;class Map;class MapQuickView;class OfflineMapTask;class PortalItem;}
class GenerateOfflineMap_Overrides: public QQuickItem{ Q_OBJECT
Q_PROPERTY(bool mapLoaded READ mapLoaded NOTIFY mapLoadedChanged) Q_PROPERTY(bool overridesReady READ overridesReady NOTIFY overridesReadyChanged) Q_PROPERTY(bool taskBusy READ taskBusy NOTIFY taskBusyChanged)
public: explicit GenerateOfflineMap_Overrides(QQuickItem* parent = nullptr); ~GenerateOfflineMap_Overrides() = default;
void componentComplete() override; static void init();
public: Q_INVOKABLE void setAreaOfInterest(double xCorner1, double yCorner1, double xCorner2, double yCorner2); Q_INVOKABLE void setBasemapLOD(int min, int max); Q_INVOKABLE void setBasemapBuffer(int bufferMetres); Q_INVOKABLE void removeSystemValves(); Q_INVOKABLE void removeServiceConnection(); Q_INVOKABLE void setHydrantWhereClause(const QString& whereClause); Q_INVOKABLE void setClipWaterPipesAOI(bool clip); Q_INVOKABLE void takeMapOffline();
signals: void taskBusyChanged(); void mapLoadedChanged(); void hideWindow(int time, bool success); void updateStatus(const QString& status); void updateProgress(int progress); void showLayerErrors(const QString& error); void overridesReadyChanged();
private: static QString webMapId(); bool taskBusy() const; bool mapLoaded() const; bool overridesReady() const; void removeFeatureLayer(const QString& layerName); Esri::ArcGISRuntime::FeatureLayer* getFeatureLayerByName(const QString& layerName); void setBusy(bool busy);
private: bool m_taskBusy = false; Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; Esri::ArcGISRuntime::PortalItem* m_portalItem = nullptr; Esri::ArcGISRuntime::OfflineMapTask* m_offlineMapTask = nullptr; Esri::ArcGISRuntime::GenerateOfflineMapParameters m_parameters; Esri::ArcGISRuntime::GenerateOfflineMapParameterOverrides* m_parameterOverrides = nullptr; static const QString s_webMapId; bool m_mapLoaded = false; QTemporaryDir m_tempPath;};
#endif // GENERATEOFFLINEMAP_OVERRIDES_H// [WriteFile Name=GenerateOfflineMap, Category=Maps]// [Legal]// Copyright 2018 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
import QtQuickimport QtQuick.Controlsimport QtQuick.Layoutsimport Esri.Samples
GenerateOfflineMap_OverridesSample { id: offlineMapOverridesSample clip: true width: 800 height: 600
onUpdateStatus: status => generateWindow.statusText = status; onUpdateProgress: progress => generateWindow.progressText = progress; onHideWindow: (time, success) => { generateWindow.hideWindow(time);
if (success) { extentRectangle.visible = false; downloadButton.visible = false; } }
onShowLayerErrors: { msgDialog.detailedText = error; msgDialog.open(); }
// add a mapView component MapView { id: mapView anchors.fill: parent objectName: "mapView"
Component.onCompleted: { // Set the focus on MapView to initially enable keyboard navigation forceActiveFocus(); }
// Create a button and anchor it to the attribution top DownloadButton { id: downloadButton anchors { horizontalCenter: parent.horizontalCenter bottom: mapView.attributionTop margins: 5 } visible: mapLoaded
onButtonClicked: { setAreaOfInterest(extentRectangle.x, extentRectangle.y, (extentRectangle.x + extentRectangle.width), (extentRectangle.y + extentRectangle.height)); } } }
// Create an extent rectangle for selecting the offline area Rectangle { id: extentRectangle anchors.centerIn: parent width: parent.width - 50 height: parent.height - 125 color: "transparent" visible: mapLoaded border { color: "red" width: 3 } }
OverridesWindow { id: overridesWindow anchors.fill: parent visible: overridesReady
onBasemapLODSelected: (min, max) => setBasemapLOD(min, max); onBasemapBufferChanged: (buffer) => setBasemapBuffer(buffer); onRemoveSystemValvesChanged: removeSystemValves(); onRemoveServiceConnectionChanged: removeServiceConnection(); onHydrantWhereClauseChanged: (whereClause) => setHydrantWhereClause(whereClause); onClipWaterPipesAOIChanged: clip => setClipWaterPipesAOI(clip); onOverridesAccepted: { generateWindow.visible = true; takeMapOffline(); } }
GenerateWindow { id: generateWindow anchors.fill: parent }
Dialog { id: msgDialog modal: true x: Math.round(parent.width - width) / 2 y: Math.round(parent.height - height) / 2 standardButtons: Dialog.Ok title: "Layer Errors" property alias text : textLabel.text property alias detailedText : detailsLabel.text ColumnLayout { Text { id: textLabel text: "Some layers could not be taken offline." } Text { id: detailsLabel } } }
BusyIndicator { anchors.centerIn: parent running: taskBusy }}// Copyright 2018 Esri.
// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.
import QtQuickimport QtQuick.Controls
Rectangle { id: exportWindow color: "transparent" visible: false clip: true
property string statusText: "Starting" property string progressText: "0"
Rectangle { anchors.fill: parent color: "#60000000" }
MouseArea { anchors.fill: parent onClicked: mouse => mouse.accepted = true onWheel: wheel => wheel.accepted = true }
Rectangle { anchors.centerIn: parent width: 135 height: 100 color: "lightgrey" opacity: 0.8 radius: 5 border { color: "#4D4D4D" width: 1 }
Column { anchors { fill: parent margins: 10 } spacing: 10
BusyIndicator { anchors.horizontalCenter: parent.horizontalCenter }
Text { anchors.horizontalCenter: parent.horizontalCenter text: "%1: %2%".arg(statusText).arg(progressText) font.pixelSize: 16 } } }
Timer { id: hideWindowTimer
onTriggered: parent.visible = false; }
function hideWindow(time) { hideWindowTimer.interval = time; hideWindowTimer.restart(); }}// Copyright 2018 Esri.
// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.
import QtQuickimport QtQuick.Controls
Rectangle { id: overridesPanel visible: overridesReady signal basemapLODSelected(real min, real max) signal basemapBufferChanged(real buffer) signal removeSystemValvesChanged() signal removeServiceConnectionChanged() signal hydrantWhereClauseChanged(string whereClause) signal clipWaterPipesAOIChanged(bool clip) signal overridesAccepted()
color: "#D6D6D6"
Text { id: title anchors { top: parent.top horizontalCenter: parent.horizontalCenter margins: 16 } text: "Layer specific overrides" font { bold: true underline: true pixelSize: 18 } color: "#474747" }
ScrollView { id: scrollView anchors { top: title.bottom left: overridesPanel.left right: overridesPanel.right bottom: takeOfflineButton.top margins: 16 } clip: true ScrollBar.vertical.interactive: true ScrollBar.vertical.policy: ScrollBar.AsNeeded
Rectangle { implicitHeight: childrenRect.height implicitWidth: overridesPanel.width color: "transparent"
Text { id: lodTitle text: "Basemap Levels of Detail:" anchors { topMargin: 16 horizontalCenter: parent.horizontalCenter } font { pixelSize: 14 } color: "#474747" }
Row { id: lodRange anchors { top: lodTitle.bottom topMargin: 8 horizontalCenter: parent.horizontalCenter } Text { text: "(Least detail)" font { pixelSize: 12 } color: "#474747" }
RangeSlider { id: lodsSlider from: 0 to: 24 width: overridesPanel.width * 0.5 first.value: 0 second.value: 23 first.onPressedChanged: { if (first.pressed) return; basemapLODSelected(first.value, second.value); }
second.onPressedChanged: { if (second.pressed) return; basemapLODSelected(first.value, second.value); } }
Text { text: "(Most detail)" font { pixelSize: 12 } color: "#474747" } }
Text { id: basemapBufferLabel text: "Basemap Buffer (m):" anchors { top: lodRange.bottom topMargin: 32 horizontalCenter: parent.horizontalCenter } font { pixelSize: 14 } color: "#474747" }
SpinBox { id: basemapBufferSB anchors { top: basemapBufferLabel.bottom topMargin: 8 horizontalCenter: parent.horizontalCenter } from: 0 to: 500 stepSize: 50
font.pixelSize: 12 onValueChanged: basemapBufferChanged(value); }
Button { id: systemVavlesCB text: "Remove System Valves" anchors { top: basemapBufferSB.bottom topMargin: 32 horizontalCenter: parent.horizontalCenter } font { pixelSize: 14 }
onClicked: { removeSystemValvesChanged(); enabled = false; } }
Button { id: serviceConnCB text: "Remove Service Connection" anchors { top: systemVavlesCB.bottom topMargin: 32 horizontalCenter: parent.horizontalCenter } font { pixelSize: 14 }
onClicked: { removeServiceConnectionChanged(); enabled = false; } }
Text { id: filterLabel text: "Filter Hydrants:" anchors { top: serviceConnCB.bottom topMargin: 32 horizontalCenter: parent.horizontalCenter } font { pixelSize: 14 } color: "#474747" }
ComboBox { id: filterComboBox anchors { top: filterLabel.bottom topMargin: 8 horizontalCenter: parent.horizontalCenter } property int modelWidth: 0 width: modelWidth + leftPadding + rightPadding + (indicator ? indicator.width : 10) model: [ "No filter", "FLOW < 500", "FLOW < 300", "FLOW < 100" ]
onCurrentTextChanged: { // 1=1 equivelent to select all in a WHERE clause. hydrantWhereClauseChanged(currentText === "No filter" ? "1=1" : currentText) }
Component.onCompleted : { for (let i = 0; i < model.length; ++i) { metrics.text = model[i]; modelWidth = Math.max(modelWidth, metrics.width); } } TextMetrics { id: metrics font: filterComboBox.font } }
CheckBox { id: clipCB text: "Clip Water Pipes to AOI" anchors { top: filterComboBox.bottom topMargin: 32 horizontalCenter: parent.horizontalCenter } font { pixelSize: 14 }
checked: true
onCheckedChanged: clipWaterPipesAOIChanged(checked) } } }
Button { id: takeOfflineButton anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.margins: 16 text: "Start Job" font { bold: true pixelSize: 18 }
onClicked: { overridesAccepted(); parent.visible = false; } }}// [Legal]// Copyright 2018 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
// sample headers#include "GenerateOfflineMap_Overrides.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QCommandLineParser>#include <QDir>#include <QGuiApplication>#include <QQmlEngine>#include <QQuickView>
// Platform specific headers#ifdef Q_OS_WIN#include <Windows.h>#endif
#define STRINGIZE(x) #x#define QUOTE(x) STRINGIZE(x)
int main(int argc, char *argv[]){ Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setUseLegacyAuthentication(false); QGuiApplication app(argc, argv); app.setApplicationName(QString("GenerateOfflineMap_Overrides"));
// Use of ArcGIS location services, such as basemap styles, geocoding, and routing services, // requires an access token. For more information see // https://links.esri.com/arcgis-runtime-security-auth.
// The following methods grant an access token:
// 1. User authentication: Grants a temporary access token associated with a user's ArcGIS account. // To generate a token, a user logs in to the app with an ArcGIS account that is part of an // organization in ArcGIS Online or ArcGIS Enterprise.
// 2. API key authentication: Get a long-lived access token that gives your application access to // ArcGIS location services. Go to the tutorial at https://links.esri.com/create-an-api-key. // Copy the API Key access token.
const QString accessToken = QString("");
if (accessToken.isEmpty()) { qWarning() << "Use of ArcGIS location services, such as the basemap styles service, requires" << "you to authenticate with an ArcGIS account or set the API Key property."; } else { Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setApiKey(accessToken); }
// Initialize the sample GenerateOfflineMap_Overrides::init();
// Initialize application view QQuickView view; view.setResizeMode(QQuickView::SizeRootObjectToView);
QString arcGISRuntimeImportPath = QUOTE(ARCGIS_RUNTIME_IMPORT_PATH);
#if defined(LINUX_PLATFORM_REPLACEMENT) // on some linux platforms the string 'linux' is replaced with 1 // fix the replacement paths which were created QString replaceString = QUOTE(LINUX_PLATFORM_REPLACEMENT); arcGISRuntimeImportPath = arcGISRuntimeImportPath.replace(replaceString, "linux", Qt::CaseSensitive);#endif
// Add the import Path view.engine()->addImportPath(QDir(QCoreApplication::applicationDirPath()).filePath("qml")); // Add the Runtime and Extras path view.engine()->addImportPath(arcGISRuntimeImportPath);
// Set the source view.setSource(QUrl("qrc:/Samples/Maps/GenerateOfflineMap_Overrides/GenerateOfflineMap_Overrides.qml"));
view.show();
return app.exec();}