ThinkGeo.com    |     Documentation    |     Premium Support

Issue deserializing many rasters

Our product uses the DesktopEdition.WinformsMap control version 4.5.0.148 (the version number on the DLLs we use). After allowing the user to add a raster overlay, and a number of vector overlays, we save their work (what we call a ‘View’) to disk by serializing each of the Overlays in the overlay collection into a stream to file.  When the user wants the View, we do the reverse; reading each layer out of the file and adding them to the Overlays collection of a new WinformsMap. We use a tabbed interface and can load the same, or different, views in each tab. This has worked very well for us for over a year. 


Recently however, a customer has begun to load fairly large rasters into their views. When I say ‘fairly large’, I don’t mean the file size is all that large (only 4MB or so), I mean that the pixel dimensions are fairly large (4800 x 6700). The product can load one of these large views into about 5 or 6 simultaneous tabs (it varies) before the following error occurs. It can load 10 of them with a raster size of 3700x4500. You can see at the bottom of the stack trace that all I do is call  .Refresh() on the Map just as I have called Refresh() on the previous 5 or 6 or 10 other Maps on the other tabs without error. 


The machine is not out of memory when this happens; total memory usage is 1.7GB to 2.2GB out of 3GB installed. If views are loaded after the error, they too get the error. If the views do have vector layers in addition to the raster layer, the vector layers do load OK if the error is handled in a try/catch block and execution is allowed to continue. 


Any idea why MapSuite.Core thows this error after having successfully completed the code many times on the exact same data?


 


Parameter is not valid.


   at System.Drawing.Image.get_Width()



   at ThinkGeo.MapSuite.Core.GdiPlusRasterSource.GetImageWidthCore()


   at ThinkGeo.MapSuite.Core.GdiPlusRasterSource.GetBoundingBoxCore()


   at ThinkGeo.MapSuite.Core.RasterSource.GetBoundingBox()


   at ThinkGeo.MapSuite.Core.RasterLayer.GetBoundingBoxCore()


   at ThinkGeo.MapSuite.Core.Layer.GetBoundingBox()


   at ThinkGeo.MapSuite.Core.RasterLayer.DrawCore(GeoCanvas canvas, Collection`1 labelsInAllLayers)


   at ThinkGeo.MapSuite.Core.Layer.Draw(GeoCanvas canvas, Collection`1 labelsInAllLayers)


   at ThinkGeo.MapSuite.DesktopEdition.LayerOverlay.DrawCore(GeoCanvas canvas)


   at ThinkGeo.MapSuite.DesktopEdition.Overlay.MainDraw(GeoCanvas canvas)


   at ThinkGeo.MapSuite.DesktopEdition.Overlay.Draw(GeoCanvas canvas)


   at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.x03e3d48bcfe7bb6c(IEnumerable`1 xa6f0db4f183189f1)


   at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.xff5b27c00f9678c2(RectangleShape x178b193eec228e6e)


   at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.xe3cee4adb9c72451()


   at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.x9ac8c50f434f4b39(Int32 xb565f4681f05557a)


   at ThinkGeo.MapSuite.DesktopEdition.WinformsMap.Refresh()


   at Fusion.fusionMap.RedrawAll() in C:\Users\Dave\Documents\Visual Studio 2010\Projects\Fusion\C3Fusion Client\fusionMap.cs:line 1157


   at Fusion.fusionTab.UnpackageViewFromStorage(View _view) in C:\Users\Dave\Documents\Visual Studio 2010\Projects\Fusion\C3Fusion Client\Toolbar.cs:line 720


 


</Dave>


 



 I predict that you will want a sample app to help identify this issue, so I made one. It is about 5MB in size so I can't attach it here so I am emailing it.


Just run it and click the 'Load View from File' menu item about 9 times.


Dave



Hello David, 
  
 Thanks for your post and sample, please send it to support@thinkgeo.com and ask to forward to me. 
  
 Regards, 
  
 Gary

 It has been several days since I sent the sample...  any feedback?


Dave



David, 
  
 Thanks for your post, I checked the support@thinkgeo.com inbox and didn’t find any email from you.  Can you try it again and also cc forumsupport@thinkgeo.com
  
 Also if you like you can upload it via the custoemr portal at helpdesk.thinkgeo.com and we can take a look at it. 
  
 Thanks!

David, 
  
 We received your sample this morning and will take a look at it today. 
  
 Thanks!

David, 
  
 What steps do I need to do on your sample application to recreate the issue?  I have added about 15 tabs, removed some, saved some and loaded some from file and haven’t gotten any exceptions yet.  I have also zoomed and panned around on the different tabs and haven’t got an exception yet.  Is there anything special I need to do? 
  
 Thanks 
  
 Clint

Nevermind, I just recreated it by loading from file a bunch of times.

 David,


 
Thanks for the sample app it made it much easier to figure out what was happening.
 
The exception is being caused by the process running out of memory, since your application is running in 32 bit x86 mode you are limited to just a little over a gig of memory before your application will start crashing.  You can verify this assuming you have a 64 bit machine by compiling for X64 and running the app again and you will see it doesn't crash.  
 
After looking as to why the application was using so much memory we found an easy enhancement that will allow you to load several more tabs 20+ before you would start running into memory issues again.  In your routine PackageViewForStorage you need to add a layer.close, this will prevent the entire raster from being packaged up again in your view.  Below is the new routine with the line of code we added bolded:
 
 
        private View PackageViewForStorage()
        {
            View _view = new View();
            MapTab mt = (MapTab)tabControl1.SelectedTab;
            
            // there is no portable list of layers, so we will need to build one before serailizing the View
            List<InMemoryFeatureLayer> layers = new List<InMemoryFeatureLayer>();
            List<GdiPlusRasterLayer> rasters = new List<GdiPlusRasterLayer>();
            foreach (LayerOverlay o in mt.Map.Overlays.ToList<Overlay>())
            {
                List<Layer> listLayer = o.Layers.ToList<Layer>();
                foreach (Layer l in listLayer)
                {
                    l.Close();
                    if (l.GetType() == typeof(GdiPlusRasterLayer))
                    {
                        rasters.Add((GdiPlusRasterLayer)l);
                    }
 
                    if (l.GetType() == typeof(InMemoryFeatureLayer))
                    {
                        layers.Add((InMemoryFeatureLayer)l);
                    }
                }
            }
 
 
            _view.Layers = layers;
            _view.Rasters = rasters;
            return _view;
        }
 
I believe this change will get your application working the way you want.  There are possible other memory optimizations that can be made as well but this one had a signifigant impact and will most likely meet your needs. If you are still running into memory issues let us know and we can look at other possible optimizations.
 
One other thing that was interesting with your sample was that the PNG you were using was only 4Meg, it puzzeled us to why the app was loading in about 80 Meg each time a new tab was created but after some further investigation we found out why.  Since your raster image is mostly white it compresses down very well with the PNG Format, however when Map Suite utlizes the raster it must be converted to a Bitmap and if you save out your PNG as a Bitmap it is actually around 80 meg instead of 4 meg.
 
I hope this helps and thanks again for sending in the sample.
 
Thanks!
 
 

 " In your routine PackageViewForStorage you need to add a layer.close, this will prevent the entire raster from being packaged up again in your view.  "


I am not sure I want to do that... I WANT the bitmap in the view file so that it is portable and the user does not have to have a local copy of the file the raster is drawn from.  In our actual produce, the serialized view is not put in a file, but rather is stored in a SQL database as a BLOB.


Doing what you suggest would make the view dependent on the PNG being present on the users' box right?


Dave



David, 
  
 Thanks for the reply, you are correct by doing the layer.close it would make it dependent on having the raster locally.  I guess it’s a trade off of memory usage versus convenience of having the data local.   I’m not sure if you can dictate that your app run on 64 bit platforms in this scenario or not, if so that may be the easiest as you can just recompile the app and the current architecture will work. 
  
 You may also try an approach where you unload everything except for the current active tab but there may be some issues with that approach that I’m not thinking of. 
  
 Hope this helps. 
  
 Thanks! 
  


 I am confused by this...  why would the .Close() save memory?  I can see how it would save storage space by not allowing the bitmap to be stored when the class is serialized, but I don't see how it would save memory when the class was read back and deserialized. It seems like wether the bitmap came from the deserialization or from a re-read of a local PNG, it would use the same amount of memory. Can you explain that in more detail please?


You mentioned that "there are possible other memory optimizations that can be made as well... If you are still running into memory issues let us know and we can look at other possible optimizations."  I would like to try some of those please.


Dave



David, 
  
   I can help answer this.  By closing the layers before serialization you save the memory of all of the tab’s files being opened.  The best solution, memory wise, would be to close the layers of the tabs that are not active.  This way if the user is on a particular tab and does a bunch of operation then we can use the cache but when they switch away we can dump it.  To do this you would need to write a little logic on the tab change event to loop through close all of the layers on all of the maps on various tabs.  When the tab you are switching to draws again it will re-open the layers automatically.  This solution should scale to as many tab as you could ever need.  Clint, who has been helping you, is out today and though I saw your code yesterday i don’t have it or I would write this little bit of code.  If you can handle this I would appreciate it.  If you rather me do it you may have to wait until tomorrow. 
  
 David 
  
 David