![Offline mapping app with a data layer](/documentation/static/b23a8755094daf8c522d23706f02d0f1/4cdf7/web-map-with-layer-preplanned.png)
In this workflow, you will learn how to prepare a feature service and a web map, integrate the service into your web map, define areas in the map for offline use, then use it to create a partially offline app. This is also known as the ahead-of-time method because you are defining the offline areas in the map ahead of time.
Prerequisites
You need an account for ArcGIS Online or ArcGIS Enterprise to host data for offline use and get an access token. If you need an account, go to Get started.
Steps
1. Prepare the map and data
Import data to create a feature service
For this workflow, you will use the Santa Monica Parcels dataset to create a hosted feature layer for your web map.
-
In your web browser, go to the Santa Monica Parcels item.
-
Click the Download button to download the zip file locally. Do not unzip this file.
-
Import the shapefile into ArcGIS using either a data management tool or a scripting API.
In your web browser, go to ArcGIS.com and sign in with your ArcGIS Online account.
-
In the top navigation bar, click Content.
-
Click New item. To upload the Santa Monica Parcels shapefile, you can either:
- Drag and drop the file.
- Or, click Your device and navigate to the file path.
-
Select Add Santa Monica Parcels.zip to publish the file as a hosted feature layer.
-
In Fields, leave all fields at their default settings and click Next.
-
In Location settings, leave the default settings and click Next.
-
Set the following information in the item details pane:
- Title:
Santa Monica Parcels
- Tags:
Santa Monica
Parcels
. - Summary:
Parcels in the Santa Monica Mountains.
- Title:
-
Click Next to create the new feature layer and feature service.
In your web browser, go to your ArcGIS Enterprise portal website and sign in with your ArcGIS Enterprise account.
-
In the top navigation bar, click Content.
-
Click New item. To upload the Santa Monica Parcels shapefile, you can either:
- Drag and drop the file.
- Or, click Your device and navigate to the file path.
-
Select Add Santa Monica Parcels.zip to publish the file as a hosted feature layer.
-
In Fields, leave all fields at their default settings and click Next.
-
In Location settings, leave the default settings and click Next.
-
Set the following information in the item details pane:
- Title:
Santa Monica Parcels
- Tags:
Santa Monica
Parcels
. - Summary:
Parcels in the Santa Monica Mountains.
- Title:
-
Click Next to create the new feature layer and feature service.
You can use ArcGIS Pro to import shapefile data.
- Unzip the Santa Monica parcels zip file.
- The zip file contains the Parcels_Public.shp file.
- Launch ArcGIS Pro.
- Create a new map project.
- A map with
World Topographic Map
andWorld Hillshade
basemap layers will be created.
- In the Map ribbon, click on Add Data > Data and select Parcels_Public.shp file.
The Parcels_Public.shp file is added as a feature layer to the map.
- Import the required libraries.
- Provide an access token.
- Create and publish a portal item.
- Handle the results.
Use dark colors for code blocks Copy # local path to shapefile zip file input_file_path = str( Path(__file__).parent / "Santa_Monica_Public_Parcels.zip" ) # add the zip file as an item to portal shp_item = portal.content.add( { "title": "Santa Monica Public Parcels", "description": "Santa Monica public parcels", "tags": "Santa, Monica, public, parcels", "type": "Shapefile", }, input_file_path, ) # publish the item to create a hosted featurelayer shp_service = shp_item.publish(None) print(f"New item id : {shp_service.id, }, url: {shp_service.layers[0].url}")
Use dark colors for code blocks Copy // publish parameters // https://developers.arcgis.com/rest/users-groups-and-items/publish-item.htm#ESRI_SECTION1_1016F32E313240D38E8CF741BDBC0BE8 const publishParams = { name: 'Santa Monica parcels', maxRecordCount: 2000, hasStaticData: true, layerInfo: { capabilities: 'Query' }, }; // create item const zipItem = await createItem({ authentication: auth, item: { title: 'Santa Monica parcels', description: 'Santa Monica parcels', tags: 'Santa, Monica, parcels', type: 'Shapefile', }, file: f, }); // execute publish operation const publishedService = await request(publishURL, { authentication: auth, params: { itemid: zipItem.id, filetype: 'shapefile', publishParameters: publishParams, }, }); // handle results console.log('New service created: '); publishedService.services.forEach((s) => { console.log(`\tid: ${s.serviceItemId} \n\turl:${s.serviceurl}`); });
-
The feature layer will look something like this:
![Santa monica parcels feature layer](/documentation/static/fa6004a99079c0b3098748b3f46f6821/4cdf7/default-parcels-styles.png)
Set the feature styles
-
Style the parcels based on the
usetype
field.Use the Visualization tab in the item page in ArcGIS.com to style the Santa Monica parcels layer.
-
Go back to the item page > Visualization.
-
In the left panel, click the Layers and select the Santa Monica parcels layer. In the right panel, click Styles.
-
Click + Field, and add
usetype
. -
Under Pick a style, click Style options, update Types (unique symbols)
-
Click on the symbol next to each of the use type values to update the symbol color.
-
Set each of the
usetype
values to the following properties:- Fill Color:
- Residential:
#ffde3e
- Commercial:
#c29ed7
- Industrial:
#004c73
- Government:
#fc921f
- Institutional:
#149ece
- Recreational:
#267300
- Miscellaneous:
#b7814a
- Residential:
- Fill Color:
-
Click the X to exit out of Symbol style.
-
Click the pencil icon next to symbol style. Set the following properties for all the use types:
- Fill transparency:
30%
- Outline color:
#ffffff
- Outline transparency:
65%
- Outline width:
1
- Adjust width automatically: false
- Fill transparency:
-
Click the X to exit out of Symbol style. Then click Done twice.
-
Click Save to save the style in the feature layer item.
Use the Visualization tab in the item page in your ArcGIS Enterprise portal to style the Santa Monica parcels layer.
-
Go back to the item page > Visualization.
-
In the left panel, click the Layers and select the Santa Monica parcels layer. In the right panel, click Styles.
-
Click + Field, and add
usetype
. -
Under Pick a style, click Style options, update Types (unique symbols)
-
Click on the symbol next to each of the use type values to update the symbol color.
-
Set each of the
usetype
values to the following properties:- Fill Color:
- Residential:
#ffde3e
- Commercial:
#c29ed7
- Industrial:
#004c73
- Government:
#fc921f
- Institutional:
#149ece
- Recreational:
#267300
- Miscellaneous:
#b7814a
- Residential:
- Fill Color:
-
Click the X to exit out of Symbol style.
-
Click the pencil icon next to symbol style. Set the following properties for all the use types:
- Fill transparency:
30%
- Outline color:
#ffffff
- Outline transparency:
65%
- Outline width:
1
- Adjust width automatically: false
- Fill transparency:
-
Click the X to exit out of Symbol style. Then click Done twice.
-
Click Save to save the style in the feature layer item.
You can use ArcGIS Pro to change the visualization of the Santa Monica parcels layer.
-
In the Content pane, right-click on the feature service and click Symbology.
-
In the Symbology pane, set the following properties:
- Primary symbology:
Unique values
- Field 1:
use
Type
- Primary symbology:
-
In the Classes tab, click on the symbol for Commercial > Properties.
-
Set the fill colors for each
use
:Type -
In the Appearance section, click the color button > Color Properties... to display the color palette. Set the following property:
- HEX#:
#c29ed7
- Transparency:
30%
- HEX#:
-
Repeat the above step for the following
use
:Types - Fill Color:
- Residential:
#ffde3e
- Industrial:
#004c73
- Government:
#fc921f
- Institutional:
#149ece
- Recreational:
#267300
- Miscellaneous:
#b7814a
- Residential:
- Fill Color:
-
-
Set the outline colors for each
use
:Type - In the Appearance section, click the outline color button > Color Properties... to display the color palette. Set the following property for
Commercial
:- HEX#:
#ffffff
- Transparency:
65%
- HEX#:
- In the Appearance section, set the Outline width to
1.0 pt
. - Repeat the above steps for each
use
.Type
- In the Appearance section, click the outline color button > Color Properties... to display the color palette. Set the following property for
Publish as web layer
Before you can share the feature layer as a hosted feature layer, you will need to enable Allow assignment of unique numeric IDs for sharing web layers option. By default, this option is not enabled. Enabling this option will allow you to assign static IDs to feature layers in your project.
- In the Contents pane, right-click the Map > Properties
- In the General section, enable Allow assignment of unique numeric IDs for sharing web layers.
With that option enabled, you can now publish the Santa Monica Parcels polygon feature layer as a hosted feature layer.
- Right-click on feature layer and click Sharing > Share as Web Layer.
- The Share As Web Layer pane will be displayed.
- Set the following required information in the General tab:
- Name:
Santa Monica Parcels
- Summary:
Parcels in the Santa Monica Mountains
- Tags:
Santa Monica Parcels
- Layer Type:
Feature
- Set a location in your organization in Location
- Name:
- Click Analyze
- Any errors or warnings during the analyze process will be displayed in the Messages tab.
- Click Publish
A success message will be displayed with Manage the web layer link allowing you to visit the item page details of the hosted feature layer.
-
The styled layer will look something like this:
![Santa monica parcels feature layer](/documentation/static/c8dcf80a72d39e6a7ed617fdd29cd38d/4cdf7/parcels-feature-layer.png)
Manage service settings
In your portal, use the Settings tab to configure the service edit settings.
- In the item settings pane, scroll down to the Editing section.
- Click the Enable editing check box.
- Under the setting What kind of editing is allowed?, uncheck the Add and Delete privilege.
- This will only allow editors to update existing features.
- Under the Update privilege, click Attributes only.
- In our case, we only want editors to manage the feature attributes. They will be unable to remove or create new features.
- Click the Enable sync (required for offline use and collaboration) check box.
- Click Save.
Your edit settings should look like this:
![Manage service settings](/documentation/static/d60f6c7a5bc8da4425cbae9c1e174d51/4cdf7/manage-service-settings.png)
Create a web map with data layer
-
In the item page of your feature layer, go to the Overview tab in the top menu bar.
-
In the right panel, click Open in Map Viewer.
You should see a web map that looks something like this.
Save the web map
Now that you have a web map, you need to save it to your portal.
-
In the left panel, click Save > Save as. At minimum, you need to provide a title and tags for your web map.
-
In the left panel, click Share map.
-
Select Everyone (public). Then, click Save.
Enable web map for offline use
-
In your web browser, find the item ID at the end of the URL for the web map you just saved.
- For example:
8ab76e9c5352400d87ca3315db9ba08e
- For example:
-
In a new browser tab, copy and paste the following URL and insert your item ID. This will return the item page for your web map, where you can configure it for offline use.
https:
//www.arcgis.com/home/item.html?id=< ITEM_ ID> - For example: https://www.arcgis.com/home/item.html?id=8ab76e9c5352400d87ca3315db9ba08e
-
Click Settings on the top bar of the item page.
-
Scroll down to the Offline menu.
-
If you don't see the Offline enabled icon at the top right corner, click Check Compatibility and turn on Offline.
-
Optionally, you can also manage advanced offline settings for your web map by clicking Options. These settings allow you to choose what data or updates mobile devices get and use a tile package for your offline basemap.
Under Features and attachments, you can configure download behaviors for both editable and read-only features. Adjusting how attachments and features are delivered can reduce the time it takes to synchronize edits, especially in areas of poor connectivity, as well as the amount of cellular data used.
Under Basemap and tile package, you can choose which basemap you want to use on your map. If you choose to use a local tile package, you will provide the filename of the package on your device. If you choose to use a tile package from your organization, you will browse a tile package from your ArcGIS organization.
-
Click Save. Your web map is enabled for offline use now.
Draw offline map areas
Now, you are going to define areas in your web map that you want to take offline, also known as offline map areas or preplanned map areas.
-
Under Offline Map Areas, click Manage Offline Areas.
-
In the left panel, click Create offline area.
-
Draw an offline area on the map. You have two options to do this:
- To draw a box around the area to take offline, click the Sketch rectangular map area button and draw a box on the map.
- To draw a polygon to define the area you need to take offline, click the Sketch polygon map area button and draw the polygon on the map.
Manage offline map area settings
After defining your offline map areas, you can configure settings such as name, level of detail, and packaging schedule. Then, you will save the offline map area and an offline map package will be generated.
-
In the Name field in the left panel, type a descriptive name for the offline map area.
-
Open the Level of detail setting to change the quality and resolution of the downloaded raster or vector tile layers. You can move the slider to the right for more detail or to the left for less detail. Make sure that the small triangle icon is included in the scope of your slider.
Set a level of detail that is appropriate for the offline map area you create. If the level of detail is too great (for example, you set the level of detail to Streets but your offline map area is the size of Spain), you'll receive a warning indicating you must either reduce the offline map area or decrease the level of detail.
-
Open the Packaging schedule section to set the interval and time that you want the offline map area packages to be refreshed to capture changes to feature layer data. If you don't want to set an automatic refresh schedule, select Never from the drop-down menu.
-
Open the Optimizations section to create offline map area packages for read-only use. Then, switch on the Enable packaged updates toggle button. Read-only packages are created based on the schedule you set in the previous step.
-
Click Save.
The new offline map area appears in the Offline map areas list with a badge indicating the area is being packaged. Packaging can take several seconds or minutes, depending on the size of the data being packaged.
Packaging will fail if the size of the offline map area, when packaged, is larger than 2.5 GB. If packaging fails, try sketching a smaller area to include in the offline map package. If the map contains feature layers that have attachments, you can exclude attachments from the offline package to decrease the package size. If the map includes tile layers, you can decrease the size of the offline map area by adjusting the level of detail to include less detail.
-
To create more offline areas for this map, click Create offline area and repeat the previous steps starting from Draw offline map areas. You can create up to 16 offline map areas per web map.
2. Build the app
Setup offline app
-
Install and setup the ArcGIS Maps SDK for .NET on your development machine. Make sure you fulfill the SDK's system requirements.
-
Follow the Display a map tutorial or download the completed solution to get a starter code for this workflow.
-
Open the
.sln
file in Visual Studio.The Visual Studio solution, project, and the namespace for all classes currently use the name
Display
. Follow the steps below if you prefer the name to reflect the current tutorial. These steps are not required, your code will still work if you keep the original name.AMap -
Update the name for the solution and the project.
- In Visual Studio, in the Solution Explorer, right-click the solution name and choose Rename. Provide the new name for your solution.
- In the Solution Explorer, right-click the project name and choose Rename. Provide the new name for your project.
-
Rename the namespace used by classes in the project.
- In the Solution Explorer, expand the project node.
- Double-click MapViewModel.cs in the Solution Explorer to open the file.
- In the
Map
class, double-click the namespace name (View Model Display
) to select it, and then right-click and choose Rename....AMap - Provide the new name for the namespace.
- Click Apply in the Rename: DisplayAMap window that appears in the upper-right of the code window. This will rename the namespace throughout your project.
-
Build the project.
- Choose Build > Build solution (or press F6).
-
Install and setup the ArcGIS Maps SDK for Kotlin on your development machine. Make sure you fulfill the SDK's system requirements.
-
Complete the Display a map tutorial. Or download and unzip the Display a map solution in a new folder.
-
Modify the old project for use in this new tutorial. Expand More info for instructions.
-
On your file system, delete the .idea folder, if present, at the top level of your project.
-
In the Android tool window, open app > res > values > strings.xml.
In the
<string name="app_
element, change the text content to a title that suits your app objective.name"> -
In the Android tool window, open Gradle Scripts > settings.gradle.kts.
Change the value of
root
to a title that suits your app objective.Project.name -
The UI theme composable in Display a map tutorial was
Display
. Rename the theme composable throughout the tutorial by refactoringAMap Theme Display
.AMap Theme Right-click the function name
Display
and select Refactor -> Rename. Replace the name with a name that suits your app objective.AMap Theme -
Click File > Sync Project with Gradle files. Android Studio will recognize your changes and create a new .idea folder.
-
-
Install and setup the ArcGIS Maps SDK for Swift on your development machine. Make sure you fulfill the SDK's system requirements.
-
Follow the Display a map tutorial or download the completed solution to get a starter code for this workflow.
-
Install and setup the ArcGIS Maps SDK for Qt on your development machine. Make sure you fulfill the SDK's system requirements.
-
Start Qt Creator.
-
Click File > New File or Project. Under Projects, select ArcGIS.
-
Select the ArcGIS Maps 200.4.0 Qt Quick C++ app project template (or a later version) and click Choose.
You may have several selections for the ArcGIS project type. Be sure to select ArcGIS Maps 200.4.0 Qt Quick C++ app (or a later version).
-
In the Project Location dialog, name your project something that fits your scenario, e.g. Display_an_offline_map. Click Next.
-
In the Define Build System dialog, select qmake for your build system. Click Next.
-
In the Define Project Details dialog, give this app a description or leave as is. Leave the rest of this dialog as is.
-
Leave the 3D project box unchecked. At the ArcGIS Online Basemap dropdown menu, select Streets. Then click Next.
-
On the Kit Selection dialog, check the kit(s) you previously set up when you installed the API. You should select a Desktop kit to run this tutorial. Then click Next.
-
At the Project Management dialog, the option to Add as a subproject to root project is only available if you have already created a root project. Ignore this dialog for this tutorial. Click Next.
Download and display the offline map
-
In the Visual Studio > Solution Explorer, double-click MapViewModel.cs to open the file.
The project uses the Model-View-ViewModel (MVVM) design pattern to separate the application logic (view model) from the user interface (view).
Map
contains the view model class for the application, calledView Model.cs Map
. See the Microsoft documentation for more information about the Model-View-ViewModel pattern.View Model -
Add additional required
using
statements at the top of the class.MapViewModel.csUse dark colors for code blocks Add line. Add line. Add line. using System; using System.Collections.Generic; using Esri.ArcGISRuntime.Portal; using System.Text; using System.Threading.Tasks; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Linq; using Esri.ArcGISRuntime.Mapping; using Esri.ArcGISRuntime.Tasks.Offline;
-
Create an empty global variable
_download
to store the path to the downloaded map area later.Location MapViewModel.csUse dark colors for code blocks Add line. private string _downloadLocation; private Map? _map; public Map? Map { get { return _map; } set { _map = value; OnPropertyChanged(); } }
-
Modify the
Setup
function signature toMap private async
returning aTask
.MapViewModel.csUse dark colors for code blocks Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line Change line private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Remove the
Map
initializer from theSetup
function. Because the map area will be downloaded from the web map, this is no longer needed.Map MapViewModel.csUse dark colors for code blocks private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Inside
Main
, remove the viewpoint configuration.Window.xaml.cs MainWindow.xaml.csUse dark colors for code blocks Remove line Remove line public MainWindow() { InitializeComponent(); MapPoint mapCenterPoint = new MapPoint(-118.805, 34.027, SpatialReferences.Wgs84); MainMapView.SetViewpoint(new Viewpoint(mapCenterPoint, 100000)); }
-
Get the web map item ID from your ArcGIS Online, for example
8ab76e9c5352400d87ca3315db9ba08e
. -
Create a
Map
from the web map using its item ID.MapViewModel.csUse dark colors for code blocks private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Create an
Offline
referencing the web map. This task object reads the offline properties of the web map and manages downloading an offline map from it.M a p Task MapViewModel.csUse dark colors for code blocks private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Use the
Offline
to list any available ahead-of-time offline maps. In the ArcGIS Maps SDKs for Native Apps the ahead-of-time offline map areas are referred to as preplanned areas.M a p Task MapViewModel.csUse dark colors for code blocks private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Get a set of default
Download
. These parameters describe how the owner of the web map configured the offline map to be downloaded by default. You can modify the parameters to fine tune the downloaded offline map.Preplanned Offline M a p Parameters MapViewModel.csUse dark colors for code blocks private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Define a path to store the downloaded map area and store it in the global variable
_download
.Location MapViewModel.csUse dark colors for code blocks private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Use the parameters to create a
Download
to download the offline map, specifying the download location.Preplanned Offline M a p Job MapViewModel.csUse dark colors for code blocks private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Start the job and wait for it to complete, returning a
Map
that is ready for use.MapViewModel.csUse dark colors for code blocks private async Task GetOfflinePreplannedMap() { var portal = await ArcGISPortal.CreateAsync(); var portalItem = await PortalItem.CreateAsync(portal, "YOUR_ITEM_ID"); // Replace with your web map ID var map = new Map(portalItem); OfflineMapTask offlineMapTask = await OfflineMapTask.CreateAsync(map); IReadOnlyList<PreplannedMapArea> availableAreas = await offlineMapTask.GetPreplannedMapAreasAsync(); if (availableAreas?.FirstOrDefault() is PreplannedMapArea area) { DownloadPreplannedOfflineMapParameters downloadParameters = await offlineMapTask.CreateDefaultDownloadPreplannedOfflineMapParametersAsync(area); string documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); _downloadLocation = System.IO.Path.Combine(documentsFolder, "OfflineMap"); DownloadPreplannedOfflineMapJob job = offlineMapTask.DownloadPreplannedOfflineMap(downloadParameters, _downloadLocation); DownloadPreplannedOfflineMapResult result = await job.GetResultAsync(); if (result?.OfflineMap is Map offlineMap) { this.Map = offlineMap; } } }
-
Click Debug > Start Debugging (or press F5 on the keyboard) to run the app.
-
In the Android tool window, open app > kotlin+java > com.example.app > screens > MainScreen.kt. Expand the code block below, and replace the existing import statements in your project with the imports needed for this tutorial.
MainScreen.ktUse dark colors for code blocks import com.arcgismaps.geometry.Point import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.ArcGISMap import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.MobileMapPackage import com.arcgismaps.mapping.PortalItem import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.portal.Portal import com.arcgismaps.tasks.geodatabase.SyncDirection import com.arcgismaps.tasks.offlinemaptask.OfflineMapSyncTask import com.arcgismaps.tasks.offlinemaptask.OfflineMapTask import com.arcgismaps.toolkit.geoviewcompose.MapView
-
Delete code from the Display a map tutorial that will not be used in this workflow.
Display a Map tutorialUse dark colors for code blocks @Composable fun MainScreen() { val map = remember { createMap() } Scaffold( topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) } ) { MapView( modifier = Modifier.fillMaxSize().padding(it), arcGISMap = map ) } } fun createMap(): ArcGISMap { return ArcGISMap(BasemapStyle.ArcGISTopographic).apply { initialViewpoint = Viewpoint( latitude = 34.0270, longitude = -118.8050, scale = 72000.0 ) } }
-
Create a
Map
class. Inside it, create the properties shown in the code below.View Model map
, which is initialized using theby
keyword and a mutable state ofArcGISMap
.context
for the application context.download
to hold the path where the downloaded map will be stored on the file system of your device.Location download
, which creates a directory using the download location and deletes the contents, if any, of the directory.Directory TAG
to pass to the logging functions.
MapViewModelUse dark colors for code blocks class MapViewModel( private val application: Application, private val coroutineScope: CoroutineScope, ): AndroidViewModel(application) { var map by mutableStateOf(ArcGISMap(BasemapStyle.ArcGISStreets).apply { initialViewpoint = Viewpoint( Point( y = 34.0200, x = -118.4685, spatialReference = SpatialReference.wgs84(), ), scale = 36_111.909643 ) }) val context = application.applicationContext private val downloadLocation by lazy { context.getExternalFilesDir(null)?.path + File.separator + "offlineMapPreplanned" } // Use the download path to create a directory. If the directory already exists, clear its contents. val downloadDirectory = File(downloadLocation).apply { // ensure the directory is empty before starting job if (isDirectory && exists()) { deleteRecursively() } } private val TAG = this.javaClass.name }
-
In
Main
, create the variables shown below. Note that theScreen map
variable instantiates theView Model Map
class.View Model MainScreenUse dark colors for code blocks @Composable fun MainScreen() { val application = LocalContext.current.applicationContext as Application val coroutineScope = rememberCoroutineScope() val mapViewModel = remember { MapViewModel(application, coroutineScope) } }
-
In
Main
, callScreen Scaffold
, passing a top bar that displays the name of the app. Inside the content block, callColumn
, which will hold the map view and three buttons.MainScreenUse dark colors for code blocks @Composable fun MainScreen() { val application = LocalContext.current.applicationContext as Application val coroutineScope = rememberCoroutineScope() val mapViewModel = remember { MapViewModel(application, coroutineScope) } Scaffold( topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) } ) { Column( modifier = Modifier .fillMaxSize() .padding(it) ) { } } }
-
Inside the content block for
Column
, callMap
and pass in the map from the map view model.View MainScreenUse dark colors for code blocks Scaffold( topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) } ) { Column( modifier = Modifier .fillMaxSize() .padding(it) ) { MapView( modifier = Modifier .fillMaxSize() .weight(1f), arcGISMap = mapViewModel.map, ) } }
-
Add three buttons and call the appropriate map view model functions in the
o
callback. We will code those two functions in the rest of this workflow.n Click MainScreenUse dark colors for code blocks MapView( modifier = Modifier .fillMaxSize() .weight(1f), arcGISMap = mapViewModel.map, ) Button( onClick = { coroutineScope.launch { mapViewModel.downloadOfflineMapArea() } } ) { Text(text = "Download preplanned offline map area") } Button( onClick = { coroutineScope.launch { mapViewModel.accessOfflineMap() } } ) { Text(text= "Access offline map from local file") } Button( onClick = { coroutineScope.launch { mapViewModel.syncOfflineMap() } } ) { Text(text= "Synchronize") } } } }
-
The first button will download an area of interest from the Santa Monica web map.
-
Get the web map item ID from your ArcGIS Online, e.g.
4c4c3a28388446a19278b3a17051730e
. -
In the
Map
class, create a suspend function namedView Model download
. Inside the function, create the variables shown in the code below.Offline M a p Area() - An arcgis.com
Portal
. - A
Portal
with portal and the item ID.Item - An
Offline
with the portal item.M a p Task
MapViewModelUse dark colors for code blocks suspend fun downloadOfflineMapArea() { val portal = Portal( url = "https://www.arcgis.com", connection = Portal.Connection.Anonymous ) val portalItem = PortalItem( portal = portal, itemId = "YOUR_ITEM_ID" // Replace with your web map ID. ) val offlineMapTask = OfflineMapTask(portalItem) }
- An arcgis.com
-
-
Download a list of preplanned map areas from the Santa Monica web map. And get the first preplanned map area.
MapViewModelUse dark colors for code blocks val offlineMapTask = OfflineMapTask(portalItem) coroutineScope.launch { val preplannedMapAreas = offlineMapTask.getPreplannedMapAreas().getOrElse { error -> return@launch showMessage( context, "Failed to get preplanned map areas: ${error.message}" ) } val selectedArea = preplannedMapAreas.first()
-
Create default parameters for downloading a preplanned offline map area to the file system of your device.
MapViewModelUse dark colors for code blocks val selectedArea = preplannedMapAreas.first() val params = offlineMapTask.createDefaultDownloadPreplannedOfflineMapParameters(selectedArea).getOrElse { error -> return@launch showMessage(context, "No default parameters for download preplanned offline map were created: ${error.message}") }
-
Create a job that will download the preplanned offline map area.
- Call
create
and pass the default parameters and the path of the download directory.Generate Offline M a p Job - In a coroutine scope, set up logging for the progress of the job.
- Start the job.
MainViewModelUse dark colors for code blocks val params = offlineMapTask.createDefaultDownloadPreplannedOfflineMapParameters(selectedArea).getOrElse { error -> return@launch showMessage(context, "No default parameters for download preplanned offline map were created: ${error.message}") } // Create a job with DownloadPreplannedOfflineMapParameters and a download directory path val downloadPreplannedOfflineMapJob = offlineMapTask.createDownloadPreplannedOfflineMapJob( parameters = params, downloadDirectoryPath = downloadDirectory.path.toString() ) coroutineScope.launch { downloadPreplannedOfflineMapJob.progress.collect { progress -> Log.i(TAG, "Downloading preplanned offline map. Job progress: $progress%") } } // Start the job to download the offline map downloadPreplannedOfflineMapJob.start()
- Call
-
From the result of the download preplanned offline map job, get the downloaded offline map. Assign it to the
map
property ofMap
.View Model MapViewModelUse dark colors for code blocks val jobResult = downloadPreplannedOfflineMapJob.result().getOrElse { error -> return@launch showMessage(context, "Download preplanned offline map job failed: ${error.message}") } map = jobResult.offlineMap } } }
-
(Optional) The code in this workflow displays messages to the user and logs activity in Logcat. Here is a possible implementation.
MapViewModelUse dark colors for code blocks private fun showMessage(context: Context, message: String) { Toast.makeText(context, message, Toast.LENGTH_LONG).show() Log.i(TAG, message) }
-
Click Run > Run > app to run the app.
Initially, you should see the web map centered centered on an inland area on Santa Monica, CA.
Click the Download preplanned offline map area button. The download might take from a few seconds to a few minutes, depending on your internet connection and the load on the server.
After the offline map area is downloaded to the Android device, you will see an area of San Monica near the beach, zoomed in closer than the original view point.
-
In Xcode, in the Project Navigator, click ContentView.swift.
-
Create a
Model
and reference it from theView
.ContentView.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. private class Model: ObservableObject { } struct ContentView: View { @StateObject private var model = Model() @State private var map = { let map = Map(basemapStyle: .arcGISTopographic) map.initialViewpoint = Viewpoint(latitude: 34.02700, longitude: -118.80500, scale: 72_000) return map }() var body: some View { } }
-
Move
map
toModel
and create aMap
with it. Change theView Viewpoint
of the map to a latitude of34.0200
, a longitude of-118.4685
, and a scale of36_111.909643
.ContentView.swiftUse dark colors for code blocks Change line Change line Change line Change line Change line Remove line Remove line Remove line Remove line Remove line Change line private class Model: ObservableObject { var map = { let map = Map(basemapStyle: .arcGISTopographic) map.initialViewpoint = Viewpoint(latitude: 34.0200, longitude: -118.4685, scale: 36_111.909643) return map }() } struct ContentView: View { @StateObject private var model = Model() @State private var map = { let map = Map(basemapStyle: .arcGISTopographic) map.initialViewpoint = Viewpoint(latitude: 34.02700, longitude: -118.80500, scale: 72_000) return map }() var body: some View { MapView(map: model.map) } }
-
Create a function called
run
which accepts a file path as a parameter for storing the downloaded offline map.Offline M a p Task ContentView.swiftUse dark colors for code blocks Add line. Add line. Add line. private class Model: ObservableObject { var map = { let map = Map(basemapStyle: .arcGISTopographic) map.initialViewpoint = Viewpoint(latitude: 34.0200, longitude: -118.4685, scale: 36_111.909643) return map }() func runOfflineMapTask(offlineMapURL: URL) async throws { } }
-
Create a portal item representing the web map you just created, e.g.
8ab76e9c5352400d87ca3315db9ba08e
.ContentView.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. private let santaMonicaWebMap = PortalItem( portal: .arcGISOnline(connection: .anonymous), id: PortalItem.ID("YOUR_ITEM_ID")! // Replace with your web map ID )
-
Inside the function, create an
Offline
referencing the portal item variable. This task object reads the offline properties of the web map and manages downloading an offline map from it.M a p Task ContentView.swiftUse dark colors for code blocks Add line. func runOfflineMapTask(offlineMapURL: URL) async throws { let offlineMapTask = OfflineMapTask(portalItem: santaMonicaWebMap) }
-
Use the
Offline
to list any available ahead-of-time offline maps. In the ArcGIS Maps SDKs for Native Apps, the ahead-of-time offline map areas are referred to as preplanned areas.M a p Task ContentView.swiftUse dark colors for code blocks Add line. Add line. func runOfflineMapTask(offlineMapURL: URL) async throws { let offlineMapTask = OfflineMapTask(portalItem: santaMonicaWebMap) let areas = try await offlineMapTask.preplannedMapAreas guard let area = areas.first else { return } }
-
Get a set of default
Download Preplanned Offline Map Parameters
. These parameters describe how the owner of the web map configured the offline map to be downloaded by default. You can modify the parameters to fine tune the downloaded offline map.Options include whether to include the basemap or use one that has already been downloaded, whether to opt into update packages, and whether to disable offline editing even if the source web map supports editing.
ContentView.swiftUse dark colors for code blocks Add line. func runOfflineMapTask(offlineMapURL: URL) async throws { let offlineMapTask = OfflineMapTask(portalItem: santaMonicaWebMap) let areas = try await offlineMapTask.preplannedMapAreas guard let area = areas.first else { return } let parameters = try await offlineMapTask.makeDefaultDownloadPreplannedOfflineMapParameters(preplannedMapArea: area) }
-
Use the parameters to create a
Download Preplanned Offline Map Job
to download the offline map, specifying the download location.ContentView.swiftUse dark colors for code blocks Add line. func runOfflineMapTask(offlineMapURL: URL) async throws { let offlineMapTask = OfflineMapTask(portalItem: santaMonicaWebMap) let areas = try await offlineMapTask.preplannedMapAreas guard let area = areas.first else { return } let parameters = try await offlineMapTask.makeDefaultDownloadPreplannedOfflineMapParameters(preplannedMapArea: area) let job = offlineMapTask.makeDownloadPreplannedOfflineMapJob(parameters: parameters, downloadDirectory: offlineMapURL) }
-
Start the job and wait for it to complete, returning a
map
, which is ready for use.ContentView.swiftUse dark colors for code blocks Add line. Add line. Add line. func runOfflineMapTask(offlineMapURL: URL) async throws { let offlineMapTask = OfflineMapTask(portalItem: santaMonicaWebMap) let areas = try await offlineMapTask.preplannedMapAreas guard let area = areas.first else { return } let parameters = try await offlineMapTask.makeDefaultDownloadPreplannedOfflineMapParameters(preplannedMapArea: area) let job = offlineMapTask.makeDownloadPreplannedOfflineMapJob(parameters: parameters, downloadDirectory: offlineMapURL) job.start() let output = try await job.output map = output.offlineMap }
-
The function is now ready to be called to download an offline map area. You can trigger this function from a button click or from the main function.
-
In Projects, double-click Headers > Display_an_offline_map.h to open the file. Add the following classes to the header file.
Display_an_offline_map.hUse dark colors for code blocks Add line. Add line. Add line. Add line. class Map; class MapQuickView; class PortalItem; class OfflineMapTask; class PreplannedMapArea;
-
Add the
download
function declaration and member variablesPreplanned M a p Area m_
,portal Item m_
, andoffline M a p Task m_
. Save and close the file.preplanned M a p Area Display_an_offline_map.hUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. private: Esri::ArcGISRuntime::MapQuickView* mapView() const; void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView); Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; void downloadPreplannedMapArea(); Esri::ArcGISRuntime::PortalItem* m_portalItem = nullptr; Esri::ArcGISRuntime::OfflineMapTask* m_offlineMapTask = nullptr; Esri::ArcGISRuntime::PreplannedMapArea* m_preplannedMapArea; };
-
In Projects, double-click Sources > Display_an_offline_map.cpp to open the file. Remove the
#include
statement forMap
because the map will be provided by the offline map.Types.h Display_an_offline_map.cppUse dark colors for code blocks Remove line #include "Display_an_offline_map.h" #include "Map.h" #include "MapTypes.h" #include "MapQuickView.h"
-
Add the following
#include
statements. These classes are needed for this application.Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. #include "Display_an_offline_map.h" #include "Map.h" #include "MapQuickView.h" #include <QFuture> #include <QStandardPaths> #include <QDir> #include "Error.h" #include "Portal.h" #include "PortalItem.h" #include "PreplannedMapArea.h" #include "OfflineMapTask.h" #include "MobileMapPackage.h" #include "DownloadPreplannedOfflineMapParameters.h" #include "DownloadPreplannedOfflineMapJob.h" #include "DownloadPreplannedOfflineMapResult.h"
-
Delete the comma after
QObject(parent)
and remove theMap
from the constructor.Display_an_offline_map.cppUse dark colors for code blocks Remove line using namespace Esri::ArcGISRuntime; Display_an_mmpk::Display_an_mmpk(QObject* parent /* = nullptr */): QObject(parent), m_map(new Map(BasemapStyle::ArcGISStreets, this))
-
Get the web map item ID from your ArcGIS Online, e.g.
8ab76e9c5352400d87ca3315db9ba08e
-
Instantiate a
Portal
and from that, aPortal
using the web map item ID. Use that to create a newItem Map
. Then create a newOffline
referencing that portal item.M a p Task Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Display_an_offline_map::Display_an_offline_map(QObject* parent /* = nullptr */): QObject(parent) { Portal* portal = new Portal(false, this); m_portalItem = new PortalItem(portal, "YOUR_ITEM_ID", this); // Replace with your web map ID m_map = new Map(m_portalItem, this); m_offlineMapTask = new OfflineMapTask(m_map, this); }
-
In the
set
function, add a call toM a p View download
.Preplanned M a p Area() Display_an_offline_map.cppUse dark colors for code blocks Add line. void Display_an_offline_map::setMapView(MapQuickView* mapView) { if (!mapView || mapView == m_mapView) { return; } m_mapView = mapView; m_mapView->setMap(m_map); downloadPreplannedMapArea(); emit mapViewChanged(); }
-
In the
download
, retrieve a list of the preplanned map areas from the web map using thePreplanned M a p Area() preplanned
method on theM a p Areas Async() Offline
. Store the area you want to download to the member variableM a p Task m_
.preplanned M a p Area Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. void Display_an_offline_map::downloadPreplannedMapArea() { m_offlineMapTask->preplannedMapAreasAsync().then(this, [this] (QList<PreplannedMapArea*> preplannedMapAreas) { for (PreplannedMapArea* preplannedMapArea : preplannedMapAreas) { preplannedMapArea->load(); } m_preplannedMapArea = preplannedMapAreas.at(0); }); }
-
To download an offline map to your device, you should provide a set of
Download
. To obtain a set of default parameters, pass in thePreplanned Offline M a p Parameters m_
topreplanned M a p Area Offline
`.M a p Task: :create Default Download Preplanned Offline M a p Parameters Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. void Display_an_offline_map::downloadPreplannedMapArea() { m_offlineMapTask->preplannedMapAreasAsync().then(this, [this] (QList<PreplannedMapArea*> preplannedMapAreas) { for (PreplannedMapArea* preplannedMapArea : preplannedMapAreas) { preplannedMapArea->load(); } m_preplannedMapArea = preplannedMapAreas.at(0); m_offlineMapTask->createDefaultDownloadPreplannedOfflineMapParametersAsync (m_preplannedMapArea).then(this, [this] (const DownloadPreplannedOfflineMapParameters& downloadPreplannedOfflineMapParameters) { }); }); }
-
Create a directory to store the downloaded map area. You need to keep note of this directory path to retrieve the downloaded areas in the future.
Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. void Display_an_offline_map::downloadPreplannedMapArea() { m_offlineMapTask->preplannedMapAreasAsync().then(this, [this] (QList<PreplannedMapArea*> preplannedMapAreas) { for (PreplannedMapArea* preplannedMapArea : preplannedMapAreas) { preplannedMapArea->load(); } m_preplannedMapArea = preplannedMapAreas.at(0); m_offlineMapTask->createDefaultDownloadPreplannedOfflineMapParametersAsync (m_preplannedMapArea).then(this, [this] (const DownloadPreplannedOfflineMapParameters& downloadPreplannedOfflineMapParameters) { const QString packageDirPath = QStandardPaths::writableLocation( QStandardPaths::DownloadLocation)+"/offlinedata/"; if (QDir(packageDirPath).exists()) { QDir dir(packageDirPath); dir.removeRecursively(); } QDir().mkdir(packageDirPath); const QString packagePath = packageDirPath + m_preplannedMapArea->portalItem()->title(); }); }); }
-
To download the offline map to your device, create a
Download
andPreplanned Offline M a p Job start()
it. You may also print debugging information as the job occurs.Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. void Display_an_offline_map::downloadPreplannedMapArea() { m_offlineMapTask->preplannedMapAreasAsync().then(this, [this] (QList<PreplannedMapArea*> preplannedMapAreas) { for (PreplannedMapArea* preplannedMapArea : preplannedMapAreas) { preplannedMapArea->load(); } m_preplannedMapArea = preplannedMapAreas.at(0); m_offlineMapTask->createDefaultDownloadPreplannedOfflineMapParametersAsync (m_preplannedMapArea).then(this, [this] (const DownloadPreplannedOfflineMapParameters& downloadPreplannedOfflineMapParameters) { const QString packageDirPath = QStandardPaths::writableLocation( QStandardPaths::DownloadLocation)+"/offlinedata/"; if (QDir(packageDirPath).exists()) { QDir dir(packageDirPath); dir.removeRecursively(); } QDir().mkdir(packageDirPath); const QString packagePath = packageDirPath + m_preplannedMapArea->portalItem()->title(); DownloadPreplannedOfflineMapJob* downloadPreplannedOfflineMapJob = m_offlineMapTask->downloadPreplannedOfflineMap( downloadPreplannedOfflineMapParameters, packagePath); connect(downloadPreplannedOfflineMapJob, &DownloadPreplannedOfflineMapJob::progressChanged, this, [downloadPreplannedOfflineMapJob]() { qDebug() << downloadPreplannedOfflineMapJob->progress(); }); downloadPreplannedOfflineMapJob->start(); }); }); }
-
If the job completes without errors, it will return an instance of
Download
. If you want to display the map immediately, usePreplanned Offline M a p Result Download
.Preplanned Offline M a p Result: :offline Map() Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. void Display_an_offline_map::downloadPreplannedMapArea() { m_offlineMapTask->preplannedMapAreasAsync().then(this, [this] (QList<PreplannedMapArea*> preplannedMapAreas) { for (PreplannedMapArea* preplannedMapArea : preplannedMapAreas) { preplannedMapArea->load(); } m_preplannedMapArea = preplannedMapAreas.at(0); m_offlineMapTask->createDefaultDownloadPreplannedOfflineMapParametersAsync (m_preplannedMapArea).then(this, [this] (const DownloadPreplannedOfflineMapParameters& downloadPreplannedOfflineMapParameters) { const QString packageDirPath = QStandardPaths::writableLocation( QStandardPaths::DownloadLocation)+"/offlinedata/"; if (QDir(packageDirPath).exists()) { QDir dir(packageDirPath); dir.removeRecursively(); } QDir().mkdir(packageDirPath); const QString packagePath = packageDirPath + m_preplannedMapArea->portalItem()->title(); DownloadPreplannedOfflineMapJob* downloadPreplannedOfflineMapJob = m_offlineMapTask->downloadPreplannedOfflineMap( downloadPreplannedOfflineMapParameters, packagePath); connect(downloadPreplannedOfflineMapJob, &DownloadPreplannedOfflineMapJob::progressChanged, this, [downloadPreplannedOfflineMapJob]() { qDebug() << downloadPreplannedOfflineMapJob->progress(); }); connect(downloadPreplannedOfflineMapJob, &DownloadPreplannedOfflineMapJob::jobDone, this, [downloadPreplannedOfflineMapJob, this]() { DownloadPreplannedOfflineMapResult* downloadPreplannedOfflineMapResult = downloadPreplannedOfflineMapJob->result(); if (!downloadPreplannedOfflineMapResult->hasErrors()) { MobileMapPackage* mobileMapPackage = downloadPreplannedOfflineMapResult->mobileMapPackage(); qDebug() << "Map " + mobileMapPackage->item()->title() + " was saved to " + mobileMapPackage->path(); Map* map = downloadPreplannedOfflineMapResult->offlineMap(); m_mapView->setMap(map); } }); downloadPreplannedOfflineMapJob->start(); }); }); }
-
Press Ctrl + R to run the app.
Access the offline map
-
Inside
Map
, create aView Model.cs private async
function returning aTask
calledAccess
.Map() MapViewModel.csUse dark colors for code blocks Copy private async Task AccessMap() { }
-
Inside the function, create a
Mobile
that references the path to the downloaded offline map area.M a p Package MapViewModel.csUse dark colors for code blocks Add line. private async Task AccessMap() { var mobileMapPackage = await MobileMapPackage.OpenAsync(_downloadLocation); }
-
Load the mobile map package and read the
Map
from it.MapViewModel.csUse dark colors for code blocks Add line. Add line. private async Task AccessMap() { var mobileMapPackage = await MobileMapPackage.OpenAsync(_downloadLocation); await mobileMapPackage.LoadAsync(); this.Map = mobileMapPackage.Maps.First(); }
-
The function is now ready to be called to access a previously downloaded offline map area. You can trigger this function from a UI button click or by calling it directly from the constructor.
-
Click Debug > Start Debugging (or press F5 on the keyboard) to run the app.
-
Create a function called
access
. Inside it, check if the offline map package exists on the device file system.Offline Map() MapViewModelUse dark colors for code blocks suspend fun accessOfflineMap() { if (!File(downloadLocation).exists()) { return showMessage(context, "No offline map!") } }
-
Create a
Mobile
referencing the path to the offline map. Then load the mobile map package.M a p Package MapViewModelUse dark colors for code blocks suspend fun accessOfflineMap() { if (!File(downloadLocation).exists()) { return showMessage(context, "No offline map!") } val mobileMapPackage = MobileMapPackage(downloadLocation) mobileMapPackage.load().onFailure { error -> return showMessage(context, "Mobile map package failed to load: ${error.message}") } }
-
Read the first
map
from the mobile map package.MapViewModelUse dark colors for code blocks suspend fun accessOfflineMap() { if (!File(downloadLocation).exists()) { return showMessage(context, "No offline map!") } val mobileMapPackage = MobileMapPackage(downloadLocation) mobileMapPackage.load().onFailure { error -> return showMessage(context, "Mobile map package failed to load: ${error.message}") } map = mobileMapPackage.maps.first() }
-
Create a function called
make
which accepts a file path of the downloaded offline map as a parameter.Mobile M a p Package ContentView.swiftUse dark colors for code blocks Add line. Add line. Add line. func makeMobileMapPackage(offlineMapURL: URL) async throws { }
-
Create a
Mobile
referencing the downloaded offline map.M a p Package ContentView.swiftUse dark colors for code blocks Add line. func makeMobileMapPackage(offlineMapURL: URL) async throws { let mobileMapPackage = MobileMapPackage(fileURL: offlineMapURL) }
-
Load the mobile map package and read the
map
from it.ContentView.swiftUse dark colors for code blocks Add line. Add line. func makeMobileMapPackage(offlineMapURL: URL) async throws { let mobileMapPackage = MobileMapPackage(fileURL: offlineMapURL) try await mobileMapPackage.load() let map = mobileMapPackage.maps.first }
-
The function is now ready to be called to access a previously downloaded offline map area. You can trigger this function from a button click or from the main function.
-
In Projects, double-click Headers > Display_an_offline_map.h to open the file. Add the
access
function declaration.Offline Map() Display_an_offline_map.hUse dark colors for code blocks Add line. private: Esri::ArcGISRuntime::MapQuickView* mapView() const; void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView); Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; void downloadPreplannedMapArea(); Esri::ArcGISRuntime::PortalItem* m_portalItem = nullptr; Esri::ArcGISRuntime::OfflineMapTask* m_offlineMapTask = nullptr; Esri::ArcGISRuntime::PreplannedMapArea* m_preplannedMapArea; void accessOfflineMap(); };
-
Create a
Mobile
that references the path where the downloaded web map area is located.M a p Package Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. void Display_an_offline_map::accessOfflineMap() { MobileMapPackage* mmpk = new MobileMapPackage("path//to//mmpk", this); return; }
-
Call
load()
on the mobile map package and display the map area once the load finishes.Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. void Display_an_offline_map::accessOfflineMap() { MobileMapPackage* mmpk = new MobileMapPackage("path//to//mmpk", this); connect(mmpk, &MobileMapPackage::doneLoading, this, [this, mmpk](const Error& e) { if (e.isEmpty()) { m_map = mmpk->maps().at(0); m_mapView->setMap(m_map); } else { qDebug() << e.message() << e.additionalMessage(); } }); mmpk->load(); return; }
-
The function is now ready to be called to access a previously downloaded offline map area. You can trigger this function from a UI button click or by calling it from another function.
-
Press Ctrl + R to run the app.
Synchronize the offline map
-
Inside
Map
, create aView Model.cs private async
function returning aTask
calledSynchronize
.Map() MapViewModel.csUse dark colors for code blocks Copy private async Task SynchronizeMap() { }
-
Create an
Offline
referencing the offline map.M a p Sync Task MapViewModel.csUse dark colors for code blocks Add line. private async Task SynchronizeMap() { OfflineMapSyncTask offlineMapSyncTask = await OfflineMapSyncTask.CreateAsync(Map); }
-
Use the
Offline
to get defaultM a p Sync Task Offline
. Optionally modify these (for example, by specifying a synchronization direction).M a p Sync Parameters MapViewModel.csUse dark colors for code blocks Add line. private async Task SynchronizeMap() { OfflineMapSyncTask offlineMapSyncTask = await OfflineMapSyncTask.CreateAsync(Map); OfflineMapSyncParameters syncParameters = await offlineMapSyncTask.CreateDefaultOfflineMapSyncParametersAsync(); }
-
Use the parameters to create an
Offline
.M a p Sync Job MapViewModel.csUse dark colors for code blocks Add line. private async Task SynchronizeMap() { OfflineMapSyncTask offlineMapSyncTask = await OfflineMapSyncTask.CreateAsync(Map); OfflineMapSyncParameters syncParameters = await offlineMapSyncTask.CreateDefaultOfflineMapSyncParametersAsync(); OfflineMapSyncJob syncJob = offlineMapSyncTask.SyncOfflineMap(syncParameters); }
-
Start the
Offline
. If it completes without errors, the offline map and web map will be in sync.M a p Sync Job MapViewModel.csUse dark colors for code blocks Add line. private async Task SynchronizeMap() { OfflineMapSyncTask offlineMapSyncTask = await OfflineMapSyncTask.CreateAsync(Map); OfflineMapSyncParameters syncParameters = await offlineMapSyncTask.CreateDefaultOfflineMapSyncParametersAsync(); OfflineMapSyncJob syncJob = offlineMapSyncTask.SyncOfflineMap(syncParameters); OfflineMapSyncResult syncResult = await syncJob.GetResultAsync(); }
-
The function is now ready to be called to synchronize an offline map. You can trigger this function from a UI button click or by calling it directly from the constructor.
-
Click Debug > Start Debugging (or press F5 on the keyboard) to run the app.
-
Create a function called
sync
. Inside it, check if the offline map package exists on the device file system.Offline Map() MapViewModelUse dark colors for code blocks suspend fun syncOfflineMap() { if (!File(downloadLocation).exists()) { return showMessage(context, "No offline map!") } }
-
Create a
Mobile
referencing the path to the offline map. Then load the mobile map package and get the first map from it.M a p Package MapViewModelUse dark colors for code blocks suspend fun syncOfflineMap() { if (!File(downloadLocation).exists()) { return showMessage(context, "No offline map!") } val offlineMapPackage = MobileMapPackage(downloadLocation) coroutineScope.launch { offlineMapPackage.load().onFailure { error -> return@launch showMessage(context, "Mobile map package failed to load: ${error.message}") } val offlineMap = offlineMapPackage.maps.first() }
-
Create an
Offline
referencing the offline map.M a p Sync Task MapViewModelUse dark colors for code blocks suspend fun syncOfflineMap() { if (!File(downloadLocation).exists()) { return showMessage(context, "No offline map!") } val offlineMapPackage = MobileMapPackage(downloadLocation) coroutineScope.launch { offlineMapPackage.load().onFailure { error -> return@launch showMessage(context, "Mobile map package failed to load: ${error.message}") } val offlineMap = offlineMapPackage.maps.first() val offlineMapSyncTask = OfflineMapSyncTask(offlineMap) }
-
Use the
Offline
to get defaultM a p Sync Task Offline
. Optionally, you can modify these, e.g. specifying a synchronization direction.M a p Sync Parameters MapViewModelUse dark colors for code blocks val offlineMapSyncTask = OfflineMapSyncTask(offlineMap) val offlineMapSyncParameters = offlineMapSyncTask.createDefaultOfflineMapSyncParameters().getOrElse { error -> return@launch showMessage( context, "Unable to generate default offline map sync parameters. ${error.message}" ) } offlineMapSyncParameters.syncDirection = SyncDirection.Bidirectional }
-
Use the parameters to create an
Offline Map Sync Job
. Then start the job.If job completes without errors, the offline map and web map will be in sync. You can also log out the sync results.
MapViewModelUse dark colors for code blocks offlineMapSyncParameters.syncDirection = SyncDirection.Bidirectional val syncJob = offlineMapSyncTask.createOfflineMapSyncJob(offlineMapSyncParameters) syncJob.start() val offlineMapSyncResult = syncJob.result().getOrElse { error -> return@launch showMessage(context, "The sync job failed: ${error.message}") } Log.i( TAG, "The offlineMapSyncResult.layerResults.size is ${offlineMapSyncResult.layerResults.size}") Log.i( TAG, "offlineMapSyncResult.layerResults.hasError: ${offlineMapSyncResult.hasErrors}") offlineMapSyncResult.layerResults.forEach { layer, offlineMapSyncLayerResult -> Log.i( TAG, "Layer name: ${layer.name}") Log.i( TAG, " has errors: ${offlineMapSyncLayerResult.hasErrors} ") Log.i( TAG, "Offline map sync result error: ${offlineMapSyncLayerResult.error?.message}") } } }
-
Create a function called
make
that takes a web map as a parameter.Offline Sync Task ContentView.swiftUse dark colors for code blocks Add line. Add line. Add line. func makeOfflineSyncTask(offlineMap: Map) async throws { }
-
Create an
Offline
referencing the offline map.M a p Sync Task ContentView.swiftUse dark colors for code blocks Add line. func makeOfflineSyncTask(offlineMap: Map) async throws { let offlineMapSyncTask = OfflineMapSyncTask(map: offlineMap) }
-
Use the
Offline
to get defaultM a p Sync Task Offline
. Optionally, you can modify these, e.g. specifying a synchronization direction.M a p Sync Parameters ContentView.swiftUse dark colors for code blocks Add line. Add line. func makeOfflineSyncTask(offlineMap: Map) async throws { let offlineMapSyncTask = OfflineMapSyncTask(map: offlineMap) let parameters = try await offlineMapSyncTask.makeDefaultOfflineMapSyncParameters() // Modify parameters if necessary, e.g. sync direction. }
-
Use the parameters to create an
Offline Map Sync Job
.ContentView.swiftUse dark colors for code blocks Add line. func makeOfflineSyncTask(offlineMap: Map) async throws { let offlineMapSyncTask = OfflineMapSyncTask(map: offlineMap) let parameters = try await offlineMapSyncTask.makeDefaultOfflineMapSyncParameters() // Modify parameters if necessary, e.g. sync direction. let offlineMapSyncJob = offlineMapSyncTask.makeSyncOfflineMapJob(parameters: parameters) }
-
Start the
Offline Map Sync Job
. If it completes without errors, the offline map and web map will be in sync.ContentView.swiftUse dark colors for code blocks Add line. func makeOfflineSyncTask(offlineMap: Map) async throws { let offlineMapSyncTask = OfflineMapSyncTask(map: offlineMap) let parameters = try await offlineMapSyncTask.makeDefaultOfflineMapSyncParameters() // Modify parameters if necessary, e.g. sync direction. let offlineMapSyncJob = offlineMapSyncTask.makeSyncOfflineMapJob(parameters: parameters) offlineMapSyncJob.start() }
-
The function is now ready to be called to synchronize an offline map. You can trigger this function from a UI button click or by calling it directly in the main function.
-
In Projects, double-click Headers > Display_an_offline_map.h to open the file. Add the following classes to the header file.
Display_an_offline_map.hUse dark colors for code blocks Add line. Add line. class Map; class MapQuickView; class PortalItem; class OfflineMapTask; class PreplannedMapArea; class OfflineMapSyncTask; class OfflineMapSyncJob;
-
Add the
synchronize
function declaration and the necessary member variables. Save and close the file.Offline Map() Display_an_offline_map.hUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. private: Esri::ArcGISRuntime::MapQuickView* mapView() const; void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView); Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; void downloadPreplannedMapArea(); Esri::ArcGISRuntime::PortalItem* m_portalItem = nullptr; Esri::ArcGISRuntime::OfflineMapTask* m_offlineMapTask = nullptr; Esri::ArcGISRuntime::PreplannedMapArea* m_preplannedMapArea; void accessOfflineMap(); void synchronizeOfflineMap(); Esri::ArcGISRuntime::OfflineMapSyncTask* m_offlineSyncTask = nullptr; Esri::ArcGISRuntime::OfflineMapSyncJob* m_syncJob = nullptr; QMetaObject::Connection m_syncJobConnection; };
-
In Projects, double-click Sources > Display_an_offline_map.cpp to open the file.
-
Add the following
#include
statements.Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. #include "Display_an_offline_map.h" #include "Map.h" #include "MapQuickView.h" #include <QFuture> #include <QStandardPaths> #include <QDir> #include "Error.h" #include "Portal.h" #include "PortalItem.h" #include "PreplannedMapArea.h" #include "OfflineMapTask.h" #include "MobileMapPackage.h" #include "DownloadPreplannedOfflineMapParameters.h" #include "DownloadPreplannedOfflineMapJob.h" #include "DownloadPreplannedOfflineMapResult.h" #include "OfflineMapSyncTask.h" #include "OfflineMapSyncParameters.h" #include "OfflineMapSyncJob.h"
-
Start implementing the
synchronize
. Create anOffline Map() Offline
referencing the offline map and store it into the member variableM a p Sync Task m_
.offline Sync Task Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. void Display_an_offline_map::synchronizeOfflineMap() { m_offlineSyncTask = new OfflineMapSyncTask(m_map, this); if (!m_offlineSyncTask) return; }
-
Use the
Offline
to get defaultM a p Sync Task: :create Default Offline M a p Sync Parameters Async() Offline
. Optionally modify these (for example, by specifying a synchronization direction).M a p Sync Parameters Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. void Display_an_offline_map::synchronizeOfflineMap() { m_offlineSyncTask = new OfflineMapSyncTask(m_map, this); if (!m_offlineSyncTask) return; m_offlineSyncTask->createDefaultOfflineMapSyncParametersAsync() .then(this, [this](OfflineMapSyncParameters parameters) { }); }
-
Use the parameters to create an
Offline
and start the job.M a p Sync Job Display_an_offline_map.cppUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. void Display_an_offline_map::synchronizeOfflineMap() { m_offlineSyncTask = new OfflineMapSyncTask(m_map, this); if (!m_offlineSyncTask) return; m_offlineSyncTask->createDefaultOfflineMapSyncParametersAsync() .then(this, [this](OfflineMapSyncParameters parameters) { if (m_syncJob) { delete m_syncJob; disconnect(m_syncJobConnection); } m_syncJob = m_offlineSyncTask->syncOfflineMap(parameters); m_syncJobConnection = connect(m_syncJob, &OfflineMapSyncJob::jobDone, this, []() { qDebug() << "Job complete"; }); m_syncJob->start(); }); }
-
The function is now ready to be called to access a previously downloaded offline map area. You can trigger this function from a UI button click or by calling it from another function.
-
Press Ctrl + R to run the app.