Changing basemap from service to tile cache does not work

2103
7
07-03-2017 06:08 AM
JoeHershman
MVP Regular Contributor

I have a situation where we have an offline tile cache used for a basemap for imagery.  We also want to be able to use the online streets basemap if connected.  This seems straightforward, however, does not work.  If I initially load a tiles layer as basemap I am unable to switch the basemap to a basemap that uses service layers and vice-versa.

So if I initially load tiles:

var tileCache = new TileCache(@"D:\Mobile\Imagery\Layers");
ArcGISTiledLayer imageLayer = new ArcGISTiledLayer(tileCache);
map = new Esri.ArcGISRuntime.Mapping.Map(new Basemap(imageLayer)) { InitialViewpoint = new Viewpoint(extent) };‍‍‍

The offline cache loads correctly.  However, when I try to switch the reference to a service based basemap

RuntimeControls.MapView.Map.Basemap = Basemap.CreateStreets();

The base map simply disappears (in the above RuntimeControls is used to share the MapView across the application.

If instead of the first code block I start with:

map = new Esri.ArcGISRuntime.Mapping.Map(Basemap.CreateImageryWithLabelsVector()) { InitialViewpoint = new Viewpoint(extent) };

Switching basemaps in the second block works fine.  The opposite behavior is also observed.  If I initially start with a Streets layer for basemap, I am unable to switch the basemap to the TileCache.  However, when I switch back to the TileCache after the failed attempt to change to streets the basemap is displayed.

The only thing that works is to create a new Map instance.  The problem with this is the OperationalLayers are lost.  I tried a workaround to see if I can recreate the map.  Along these lines

RuntimeControls.MapView.Map.Basemap = Basemap.CreateStreets();

LayerCollection operationalLayers = RuntimeControls.MapView.Map.OperationalLayers;
Envelope extent = RuntimeControls.MapView.VisibleArea.Extent;
RuntimeControls.MapView.Map = null;

Map map = new Map(Basemap.CreateStreets()) { InitialViewpoint = new Viewpoint(extent) };
foreach (var layer in operationalLayers.OfType<FeatureLayer>())
{
     FeatureLayer featureLayer = new FeatureLayer(layer.FeatureTable);

     map.OperationalLayers.Add(featureLayer);
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍

This, though, throws an exception: 

Esri.ArcGISRuntime.ArcGISRuntimeException was unhandled
ErrorCode=24
HResult=-2146233088
Message=Object already owned.: Already owned.
Source=Esri.ArcGISRuntime

I put the line setting the map to null in there hoping it would release the layer ownership, I even tried to force a garbage collection to see if that would release it, but no luck.

Any thoughts?  This is a pretty critical aspect to our application users need to be able to see a streets map when they are connected

Thanks

-Joe

Tags (1)
0 Kudos
7 Replies
GregDeStigter
Esri Contributor

Hi Joe,

Basemap switching can be a struggle. I notice two things here.

1) If the spatial references of the offline TileCache and the online basemap are not the same you won't be able to switch between them using the same map - I think this might be what you're seeing (can you confirm?). Once Map.SpatialReference is established you can't change it and most basemap tiles cannot be reprojected on the fly. To fix this you either need to find offline and online basemaps with the same spatial reference or swap out the map as you suggest.

2) Regarding the operational layer cloning, a layer can only be owned by one collection. So you need to remove the layer from the original map's layer collection before adding it to the new one. I'm not sure why setting 'Map = null' and forcing GC didn't work, but you should be able to call Map.OperationalLayers.Clear() to clear out the original map's layer collection which should free up the layers so they can be added to the new map.

I hope that helps, let us know if it works.

0 Kudos
JoeHershman
MVP Regular Contributor

The former would seem to be the issue.  The tile cache is projected into the local reference system used by the company.  This is purchased product will have to look into if we can get a copy in 3857.  The latter is turned out to be a little more complicated than it would seem.  I cannot just set another variable to Map.OperationalLayers because this is just a pointer to the same LayerCollection so calling :Clear clears out the other variable.

I ended up this this, which does seem to be working:

private void ResetMapProperties(Map map)
{
     LayerCollection operationalLayers = new LayerCollection();
     Layer[] tempLayers = new Layer[RuntimeControls.MapView.Map.OperationalLayers.Count];
     RuntimeControls.MapView.Map.OperationalLayers.CopyTo(tempLayers, 0);

     foreach (var operationalLayer in tempLayers)
     {
          RuntimeControls.MapView.Map.OperationalLayers.Remove(operationalLayer);
          operationalLayers.Add(operationalLayer);
     }
     RuntimeControls.MapView.Map.OperationalLayers.Clear(); //just in case

     Envelope extent = RuntimeControls.MapView.VisibleArea.Extent;

     map.InitialViewpoint = new Viewpoint(extent);
     map.OperationalLayers = operationalLayers;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

 

This is giving the TOC control in the toolkit some fits and throws an unhandled exceptions in ObservableLayerContentList trying to refresh the list but for now that is a problem for another day.

Thanks for the help

-joe

0 Kudos
dotMorten_esri
Esri Frequent Contributor

This is giving the TOC control in the toolkit some fits and throws an unhandled exceptions in ObservableLayerContentList trying to refresh the list but for now that is a problem for another day.

When you get to that other day, please log this issue in the GitHub repo

https://github.com/Esri/arcgis-toolkit-dotnet/issues/new

0 Kudos
dotMorten_esri
Esri Frequent Contributor

I'm not sure why setting 'Map = null' and forcing GC didn't work

Setting the map to null, doesn't mean the layer isn't still associated with that map any longer. It just means you removed one reference to the map instance. You'd have to move the layer out of the old map, before inserting it into a new map.

0 Kudos
ReedHunter
New Contributor III

I had the identical error when migrating an app from 10.2.5 to the 100.2 SDK.  Apparently a layer already owned by the operationalLayers of an AGSMap cannot be added to the operationalLayers of another AGSMap, and MMPKs provide layers only within a loaded AGSMap.

I previously used two .geodatabase files, one for stable offline basemap data that is never synced, and another with more volatile operational layers that can be synced by many users at any time.  I encountered this error after migrating my offline basemap data to be loaded from a mobile map package.  I resolved the error by making my own array variable holding the layers and then clearing them from the AGSMobileMapPackage.maps[0].operationalLayers.

I have requested that the SDK docs be updated to mention restrictions on access to layers owned by another AGSMap.  As an aside, I ran into this working with the iOS SDK, not the .Net SDK.  I suspect this will be common across platforms.

I hope this helps anyone else with this problem.

-Reed

0 Kudos
JoeHershman
MVP Regular Contributor

I find the solution I documented above to be a lot of code for something I think should be pretty simple.  It is also unstable and at times switching basemaps will freeze.  The best fix would be to have basemaps in the same projection, but in our case that is not possible. We have purchased tile imagery that is delivered only in the local projection, and would like to also be able to use online basemaps, only available in specific projections

0 Kudos
dotMorten_esri
Esri Frequent Contributor

It is also unstable and at times switching basemaps will freeze. 

Can you share a little app that reproduces this, so we can address it or at least provide you with a workaround?

0 Kudos