Mix layers with Mobile Map Package layers

1897
10
10-10-2018 04:38 PM
NathanMellor
Occasional Contributor

I've been looking for an answer to this. 

Suppose I load a Mobile Map Package. 

It has layers in it. 

If I do either of these things:

  • Open the ArcgisMap and add more layers to it
  • Take the layers and add them to a different ArcGISMap

The result is that I have an invalid map that shows nothing. 

Is it still all or nothing for Mobile Map Packages?

This way, I can't distribute them as .mmpk - I have to extract the individual .geodatabases and use them that way. 

0 Kudos
10 Replies
ShellyGill1
Esri Contributor

Hi,

You should be able to open a map from an MMPK and add new layers to it just fine. Note that if the layer is not reprojectable, and it's spatial reference does not match that of the map you're adding them too, then you might not be able to see the data.

For adding layers from one map to another map - if a layer already belongs to one map, you cannot add that to another map (you should get an exception to this effect); in this case you can try using the `copy` method to create a copy of the layer, and add that copy to the other map. This would be the same regardless of whether the layers are from a map loaded from an mmpk, or if the layers and maps were created programmatically from data directly.

If you're still having issues, post your code and details about your data, and perhaps we can reproduce any problems you're seeing.

Regards,
Shelly

0 Kudos
NathanMellor
Occasional Contributor

Where is the 'copy' method?

I don't see that method on the layer class.

0 Kudos
ShellyGill1
Esri Contributor

Good point Nathan, apologies for not being clear here. We have implemented the copy method on the specific types of Layer class, not up at the Layer superclass level. There was a few types of layer that we didn't support the copy method on, but I believe we're filling in this support in the upcoming release. Note however if you get an 'UnsupportedLayer' or 'UnknownLayer' (for example from a web map) then you won't be able to copy these I think.

0 Kudos
NathanMellor
Occasional Contributor

I've done everything you have asked and this is still the result. In the code, i can debug and see that one MobileBaseMapLayer is added to the basemap and four feature layers are added to the operational layers.

But none of that helps because I see absolutely nothing on screen. Not even a grid. No basemap, no feature layers. Nothing.

This is with your very own Yellowstone.mmpk example.

MobileMapPackage.

Here is the code.

/* Copyright 2016 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.
 *
 */

package com.esri.arcgisruntime.sample.openmobilemappackage;

import java.io.File;
import java.util.List;
import java.util.concurrent.ExecutionException;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;

import com.esri.arcgisruntime.ArcGISRuntimeEnvironment;
import com.esri.arcgisruntime.concurrent.ListenableFuture;
import com.esri.arcgisruntime.geometry.Point;
import com.esri.arcgisruntime.geometry.SpatialReference;
import com.esri.arcgisruntime.geometry.SpatialReferences;
import com.esri.arcgisruntime.layers.ArcGISTiledLayer;
import com.esri.arcgisruntime.layers.ArcGISVectorTiledLayer;
import com.esri.arcgisruntime.layers.FeatureLayer;
import com.esri.arcgisruntime.layers.ImageTiledLayer;
import com.esri.arcgisruntime.layers.Layer;
import com.esri.arcgisruntime.layers.LegendInfo;
import com.esri.arcgisruntime.layers.MobileBasemapLayer;
import com.esri.arcgisruntime.layers.WebTiledLayer;
import com.esri.arcgisruntime.loadable.LoadStatus;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.Basemap;
import com.esri.arcgisruntime.mapping.MobileMapPackage;
import com.esri.arcgisruntime.mapping.Viewpoint;
import com.esri.arcgisruntime.mapping.view.MapView;

public class MainActivity extends AppCompatActivity {

  private static final String TAG = MainActivity.class.getSimpleName();
  private static final String FILE_EXTENSION = ".mmpk";
  private static File extStorDir;
  private static String extSDCardDirName;
  private static String filename;
  private static String mmpkFilePath;
  // define permission to request
  String[] reqPermission = new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE };
  private MapView mMapView;
  private MobileMapPackage mapPackage;
  private int requestCode = 2718;
  private ArcGISMap mMap;

  /**
   * Create the mobile map package file location and name structure
   */
  private static String createMobileMapPackageFilePath() {
    return extStorDir.getAbsolutePath() + File.separator + "agis" + File.separator + filename
        + FILE_EXTENSION;
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ArcGISRuntimeEnvironment.setLicense("runtimelite,1000,rud8488948081,none,TRB3LNBHPD0H4P7EJ008");
    // get sdcard resource name
    extStorDir = Environment.getExternalStorageDirectory();
    // get the directory
    extSDCardDirName = this.getResources().getString(R.string.config_data_sdcard_offline_dir);
    // get mobile map package filename
    filename = "Yellowstone";//this.getResources().getString(R.string.config_mmpk_name);
    // create the full path to the mobile map package file
    mmpkFilePath = createMobileMapPackageFilePath();

    // retrieve the MapView from layout
    mMapView = (MapView) findViewById(R.id.mapView);
    mMap = new ArcGISMap(Basemap.Type.TOPOGRAPHIC_VECTOR,44.417,-112.08,11);
    mMapView.setMap(mMap);


    // For API level 23+ request permission at runtime
    if (ContextCompat.checkSelfPermission(MainActivity.this, reqPermission[0]) == PackageManager.PERMISSION_GRANTED) {
      loadMobileMapPackage(mmpkFilePath);
    } else {
      // request permission
      ActivityCompat.requestPermissions(MainActivity.this, reqPermission, requestCode);
    }

  }

  /**
   * Handle the permissions request response
   */
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
      loadMobileMapPackage(mmpkFilePath);
    } else {
      // report to user that permission was denied
      Toast.makeText(MainActivity.this, getResources().getString(R.string.location_permission_denied),
          Toast.LENGTH_SHORT).show();
    }
  }

  /**
   * Load a mobile map package into a MapView
   *
   * @param mmpkFile Full path to mmpk file
   */
  private void loadMobileMapPackage(String mmpkFile) {
    //[DocRef: Name=Open Mobile Map Package-android, Category=Work with maps, Topic=Create an offline map]
    // create the mobile map package

    File mfile = new File(mmpkFile);
    boolean exists = mfile.exists();
    Log.i(TAG,"File " + mmpkFile + " exists=" + exists);

    mapPackage = new MobileMapPackage(mmpkFile);
    // 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) {
          // add the map from the mobile map package to the MapView

          ArcGISMap amap = mapPackage.getMaps().get(0);

          //mMap.setBasemap(Basemap.createNavigationVector());

          //ArcGISMap defaultMap = new ArcGISMap(Basemap.Type.TOPOGRAPHIC_VECTOR,44.417,-112.08,11);



         // Basemap basemap = Basemap.createNavigationVector();
          //mMap.setBasemap(basemap);
         //mMap.getBasemap().getBaseLayers().add(new ArcGISTiledLayer("https://www.arcgis.com/home/item.html?id=30e5fe3149c34df1ba922e6f5bbf808f"));
          ///mMapView.setMap(defaultMap);





          for(int i=0;i<amap.getBasemap().getBaseLayers().size(); i++)
          {
            Layer l = amap.getBasemap().getBaseLayers().get(i);

            Layer layerCopy = copyLayer(l);

            if(layerCopy!=null)
              mMap.getBasemap().getBaseLayers().add(layerCopy);
          }
          for(int j=0;j<amap.getOperationalLayers().size(); j++)
          {
            Layer o = amap.getOperationalLayers().get(j);

            Layer layerCopy = copyLayer(o);

            if(layerCopy!=null)
              mMap.getOperationalLayers().add(layerCopy);
          }





          //Log.i(TAG,"Mobile Map SR: "  + mMap.getSpatialReference().getWKText());

          //ArcGISTiledLayer tiledLayerBaseMap = new ArcGISTiledLayer("http://services.arcgisonline.com/arcgis/rest/services/World_Topo_Map/MapServer");




        //mMap.getBasemap().getBaseLayers().add(tiledLayerBaseMap);

  //        Log.i(TAG,"Mobile Map SR: "  + tiledLayerBaseMap.getSpatialReference().getWKText());

         // mMapView.setMap(mMap);
/*
          for (final Layer layer:mMap.getOperationalLayers() ) {
            SpatialReference sr =layer.getSpatialReference();
            if(sr== null)
              Log.i(TAG,"Layer "+ layer.getName() + " : SR==null" );
            else
              Log.i(TAG,"Layer "+ layer.getName() + " : " + sr.getWKText() );

            final ListenableFuture<List<LegendInfo>> future = layer.fetchLegendInfosAsync();
            future.addDoneListener(new Runnable() {
              @Override
              public void run() {

                Log.i(TAG,"Layer "+ layer.getName() + " : "+ future.isDone());
                if(future.isDone())
                {
                  try {
                    List<LegendInfo> legendInfos = future.get();
                    for(LegendInfo linfo:legendInfos)
                    {
                      Log.i(TAG,"info: " + linfo.getName() + "  : " +  linfo.getSymbol().toJson() );
                    }
                  } catch (InterruptedException e) {
                    e.printStackTrace();
                  } catch (ExecutionException e) {
                    e.printStackTrace();
                  }

                }
              }
            });
          }
*/
          Log.i("mpackage","mMapView" + mMapView.getCurrentViewpoint(Viewpoint.Type.CENTER_AND_SCALE).toJson());
//45.872546, -121.976170 Wind River Road
          //-110.3626,46.8797

         mMapView.setViewpointCenterAsync(new Point(-121.44,45.115, SpatialReferences.getWgs84()),144000);
          Log.i("mpackage","mMapView" + mMapView.getCurrentViewpoint(Viewpoint.Type.CENTER_AND_SCALE).toJson());
        } else {
          // Log an issue if the mobile map package fails to load
          Log.e(TAG, mapPackage.getLoadError().getMessage());
        }
      }
    });
    //[DocRef: END]
  }


  Layer copyLayer(Layer l)
  {
    if(l instanceof FeatureLayer)
    {
      return ((FeatureLayer) l).copy();
    }
    if(l instanceof ImageTiledLayer)
    {
      return((WebTiledLayer)l).copy();
    }
    if(l instanceof ArcGISTiledLayer)
    {
      return ((ArcGISTiledLayer)l).copy();
    }
    if(l instanceof ArcGISVectorTiledLayer)
    {
      return ((ArcGISVectorTiledLayer)l).copy();
    }
    if(l instanceof MobileBasemapLayer)
    {
      return ((MobileBasemapLayer)l).copy();
    }
    return null;
  }


  @Override
  protected void onPause() {
    super.onPause();
    mMapView.pause();
  }

  @Override
  protected void onResume() {
    super.onResume();
    mMapView.resume();
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    mMapView.dispose();
  }



}
0 Kudos
by Anonymous User
Not applicable

Hi Nathan,

Apologies for the delay. I had a look at your code and I believe I've got it working.

The problem is one of spatial references, I believe. mMap is being constructed with a base map type whose spatial reference is different to that of the mobile map package.

So to make your code run, change the constructor for mMap from:

mMap = new ArcGISMap(Basemap.Type.TOPOGRAPHIC_VECTOR,44.417,-112.08,11);

to

mMap = new ArcGISMap();

I'd also recommend commenting out 

mMapView.setViewpointCenterAsync(new Point(-121.44,45.115, SpatialReferences.getWgs84()),144000);

to begin with, since it points slightly west of the data.

Hope this helps! Please let me know if you have any more issues.

Trevor

0 Kudos
NathanMellor
Occasional Contributor

Okay, but if I take out that line, I am no longer mixing layers, am I?

I still don't have any example of mixing layers from a mobile map package.

Your yellowstone map had this SR:

SR= {"wkid":26912,"wkt":"PROJCS[\"NAD_1983_UTM_Zone_12N\",GEOGCS[\"GCS_North_American_1983\",DATUM[\"D_North_American_1983\",SPHEROID[\"GRS_1980\",6378137.0,298.257222101]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"False_Easting\",500000.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",-111.0],PARAMETER[\"Scale_Factor\",0.9996],PARAMETER[\"Latitude_Of_Origin\",0.0],UNIT[\"Meter\",1.0]]"}

So maybe that's not compatible with the basemap.

I then tried one of my own with this SR:

SR= {"wkid":102100,"latestWkid":3857,"wkt":"PROJCS[\"WGS_1984_Web_Mercator_Auxiliary_Sphere\",GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator_Auxiliary_Sphere\"],PARAMETER[\"False_Easting\",0.0],PARAMETER[\"False_Northing\",0.0],PARAMETER[\"Central_Meridian\",0.0],PARAMETER[\"Standard_Parallel_1\",0.0],PARAMETER[\"Auxiliary_Sphere_Type\",0.0],UNIT[\"Meter\",1.0]]"}

Seems like that should be compatible with BaseMap Topographic, right? In fact it should be compatible with just about any map layers I plan to use.

I tried putting a basemap back in.

mMap = new ArcGISMap(Basemap.Type.TOPOGRAPHIC,44.417,-112.08,11);

Screen is blank. No grid. Nothing.

Back where I started.

If it is the spatial reference that's the problem, how do I make an mmpk that will be compatible with standard tiled basemaps?

If it is not the spatial reference, then what is the problem?

0 Kudos
by Anonymous User
Not applicable

Hi Again,

I've dug into this and you're right its doesn't seem to be a spatial reference issue and there doesn't seem to be a reason why it shouldn't work. So I've logged this as a bug.

In the meantime, it sounds like you know the work around?

yourMobileMapPackage.unpackAsync(pathToMMPK, destinationFolder)

When its done unpacking, get the path to the geodatabase locally on the device. Use the geodatabase to access feature tables. Build feature layers from the feature tables.

Let me know how you get on.

Cheers,

Trevor

0 Kudos
NathanMellor
Occasional Contributor

Support helped me and we narrowed it down to a problem with the sample.

It had little to do with the mappackage itself.

The sample didn't have Internet permission in the manifest so it choked when trying to load the map layer, without any meaningful messages that that was the case.

Internet permission is default on 6.0, but it stills need to be in the manifest.

I can't believe it was that easy.

Nathan

NathanMellor
Occasional Contributor

Confirmed that I still get zero results if I try this on 100.4.

Should I file a support ticket on this?

If this isn't going to work, I can't keep wasting time on it. I will have to extract the pieces, .geodatabase or whatever, from the .mmpk, and distribute them that way. However silly that is, it seems like the only option. 

0 Kudos