Create an offline map using ArcGIS (.mmpk map)

Viewed 3022

My goal is to create an Android app which download a map from ArcGIS portal when connected to internet, then use them offline. I would like to use service pattern, so later the app can have synchronization feature. I followed a tutorial from ArcGIS here.

I am currently stuck at downloading the map part. I expect the downloaded map is in mobile map package (.mmpk), but instead my download directory have a package.info file, and a folder of geodatabase and .mmap files as image shown here. Based on my understanding, I should have an .mmpk file to use them offline.

Following the tutorial steps, I am able to (1) create an offline map task, (2) specify the parameters, and (3) examine the offline capabilities. However in step (4) generate and download the offline map, I expect the downloaded map will be in mobile map package (.mmpk) but its not; as i mentioned above with image shown. In step (5) open and use the offline map, i am able to view offline map when using mobile map package (.mmpk) file that i transfer manually into the device. I also tried to open and use my downloaded (.mmap) file but no map showed up.

My full code by steps is shown below:

(1) create an offline map task

    // Load map from a portal item
    final Portal portal = new Portal("http://www.arcgis.com");
    final PortalItem webmapItem = new PortalItem(portal, "acc027394bc84c2fb04d1ed317aac674");

    // Create map and add it to the view
    myMap = new ArcGISMap(webmapItem);
    mMapView = (MapView) findViewById(R.id.mapView);
    mMapView.setMap(myMap);

    // Create task and set parameters
    final OfflineMapTask offlineMapTask = new OfflineMapTask(myMap);

(2) specify the parameters

    // Create default parameters
    final ListenableFuture<GenerateOfflineMapParameters> parametersFuture = offlineMapTask.createDefaultGenerateOfflineMapParametersAsync(areaOfInterest);
    parametersFuture.addDoneListener(new Runnable() {
        @Override
        public void run() {
            try {
                final GenerateOfflineMapParameters parameters = parametersFuture.get();

                // Update the parameters if needed
                // Limit maximum scale to 5000 but take all the scales above (use 0 as a MinScale)
                parameters.setMaxScale(5000);

                parameters.setIncludeBasemap(false);

                // Set attachment options
                parameters.setAttachmentSyncDirection(GenerateGeodatabaseParameters.AttachmentSyncDirection.UPLOAD);
                parameters.setReturnLayerAttachmentOption(GenerateOfflineMapParameters.ReturnLayerAttachmentOption.EDITABLE_LAYERS);

                // Request the table schema only (existing features won't be included)
                parameters.setReturnSchemaOnlyForEditableLayers(true);

                // Update the title to contain the region
                parameters.getItemInfo().setTitle(parameters.getItemInfo().getTitle() + " (Central)");

                // Create new item info
                final OfflineMapItemInfo itemInfo = new OfflineMapItemInfo();

                // Override thumbnail with the new image based on the extent
                final ListenableFuture<Bitmap> exportImageFuture = mMapView.exportImageAsync();
                exportImageFuture.addDoneListener(new Runnable() {
                    @Override
                    public void run() {
                        try {

                            Bitmap mapImage = exportImageFuture.get();
                            // Scale to thumbnail size
                            Bitmap thumbnailImage = Bitmap.createScaledBitmap(mapImage, 200, 133, false);
                            // Convert to byte[]
                            ByteArrayOutputStream stream = new ByteArrayOutputStream();
                            thumbnailImage.compress(Bitmap.CompressFormat.JPEG, 50, stream);
                            byte[] thumbnailBytes = stream.toByteArray();
                            stream.close();

                            // Set values to the itemInfo
                            itemInfo.setThumbnailData(thumbnailBytes);
                            itemInfo.setTitle("Water network (Central)");
                            itemInfo.setSnippet(webmapItem.getSnippet()); // Copy from the source map
                            itemInfo.setDescription(webmapItem.getDescription()); // Copy from the source map
                            itemInfo.setAccessInformation(webmapItem.getAccessInformation()); // Copy from the source map
                            itemInfo.getTags().add("Water network");
                            itemInfo.getTags().add("Data validation");

                            // Set metadata to parameters
                            parameters.setItemInfo(itemInfo);

                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });

(3) examine the offline capabilities

                final ListenableFuture<OfflineMapCapabilities> offlineMapCapabilitiesFuture =
                        offlineMapTask.getOfflineMapCapabilitiesAsync(parameters);
                offlineMapCapabilitiesFuture.addDoneListener(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            OfflineMapCapabilities offlineMapCapabilities = offlineMapCapabilitiesFuture.get();
                            if (offlineMapCapabilities.hasErrors()) {
                                // Handle possible errors with layers
                                for (java.util.Map.Entry<Layer, OfflineCapability> layerCapability :
                                        offlineMapCapabilities.getLayerCapabilities().entrySet()) {
                                    if (!layerCapability.getValue().isSupportsOffline()) {
                                        showMessage(layerCapability.getKey().getName() + " cannot be taken offline.");
                                        showMessage("Error : " + layerCapability.getValue().getError().getMessage());
                                    }
                                }

                                // Handle possible errors with tables
                                for (java.util.Map.Entry<FeatureTable, OfflineCapability> tableCapability :
                                        offlineMapCapabilities.getTableCapabilities().entrySet()) {
                                    if (!tableCapability.getValue().isSupportsOffline()) {
                                        showMessage(tableCapability.getKey().getTableName() + " cannot be taken offline.");
                                        showMessage("Error : " + tableCapability.getValue().getError().getMessage());
                                    }
                                }
                            } else {
                                // All layers and tables can be taken offline!
                                showMessage("All layers are good to go!");
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });

(4) generate and download the offline map

                String mExportPath = String.valueOf(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)) + File.separator + "New";
                showMessage(mExportPath);

                // Create and start a job to generate the offline map
                final GenerateOfflineMapJob generateOfflineJob =
                        offlineMapTask.generateOfflineMap(parameters, mExportPath);

                // Show that job started
                final ProgressBar progressBarOffline = (ProgressBar) findViewById(R.id.progressBarOffline);
                progressBarOffline.setVisibility(View.VISIBLE);

                generateOfflineJob.start();
                generateOfflineJob.addJobDoneListener(new Runnable() {
                    @Override
                    public void run() {

                        // Generate the offline map and download it
                        GenerateOfflineMapResult result = generateOfflineJob.getResult();

                        if (!result.hasErrors()) {

                            showMessage("no error");
                            mobileMapPackage = result.getMobileMapPackage();
                            // Job is finished and all content was generated
                            showMessage("Map " + mobileMapPackage.getItem().getTitle() +
                                    " saved to " + mobileMapPackage.getPath());

                            // Show offline map in a MapView
                            mMapView.setMap(result.getOfflineMap());

                            // Show that job completed
                            progressBarOffline.setVisibility(View.INVISIBLE);
                        } else {

                            showMessage("error");
                            // Job is finished but some of the layers/tables had errors
                            if (result.getLayerErrors().size() > 0) {
                                for (java.util.Map.Entry<Layer, ArcGISRuntimeException> layerError : result.getLayerErrors().entrySet()) {
                                    showMessage("Error occurred when taking " + layerError.getKey().getName() + " offline.");
                                    showMessage("Error : " + layerError.getValue().getMessage());
                                }
                            }
                            if (result.getTableErrors().size() > 0) {
                                for (java.util.Map.Entry<FeatureTable, ArcGISRuntimeException> tableError : result.getTableErrors().entrySet()) {
                                    showMessage("Error occurred when taking " + tableError.getKey().getTableName() + " offline.");
                                    showMessage("Error : " + tableError.getValue().getMessage());
                                }
                            }
                            // Show that job completed
                            progressBarOffline.setVisibility(View.INVISIBLE);
                        }
                    }
                });

(5) open and use the offline map

// Create the mobile map package
    final MobileMapPackage mapPackage = new MobileMapPackage(mobileMapPackage.getPath());
    // Load the mobile map package asynchronously
    mapPackage.loadAsync();

    // Add done listener which will invoke when mobile map package has loaded
    mapPackage.addDoneLoadingListener(new Runnable() {
        @Override
        public void run() {
            // Check load status and that the mobile map package has maps
            if(mapPackage.getLoadStatus() == LoadStatus.LOADED && mapPackage.getMaps().size() > 0){
                // Cdd the map from the mobile map package to the MapView
                mMapView.setMap(mapPackage.getMaps().get(0));
            }else{
                // Log an issue if the mobile map package fails to load
                showMessage(mapPackage.getLoadError().getMessage());
            }
        }
    });

showMessage() in my code is showing Toast.

public void showMessage(String message) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}

I worry if my .mmpk expectation is wrong, or my step goes wrong somewhere because I still not fully understand the whole process. This is my first time working with ArcGIS map in Android. I could not find much sample code to experiment, so really appreciate someone who could help.

Thank you!

2 Answers

Kinda late on the topic but i had several days working on this and found out something that may help some of you :

I created my mapData via this class : https://github.com/Esri/arcgis-runtime-samples-java/blob/master/src/main/java/com/esri/samples/map/generate_offline_map/GenerateOfflineMapSample.java

As you can see it creates a folder containing package.info + p13 (in which you find geodatabase file + mmap file)

WHen i tried offline to load this data, no errors appeared but the layer was empty and i could just see the carroying.

In fact after much more tries, i had to check that besides geodatabase and mmap file i could find a .tpk file (TilePackaged)

This one was never available (somehow due to network issues during the online download) and nothing alerted me.

Now that this tpk file is there, all items are clearly displayed like 'water network'

TL;DR; : check that tpk file is donwloaded during the online preparation.

Related