ThinkGeo.com    |     Documentation    |     Premium Support

JPEG2000 Image pan and zoom VERY slow

We have a very large Jp2 image (100MB in size) our pan and zoom is very slow. Is there any approach to make this better? What if we changed image formats to JPG? Or is there a way to tile and stitch this image together to make the speed relatively fast? How about some inside baseball. How does a program like Google maps work? Our current speed on the desktop creates a multi second delay in pan and zoom.



More background. I loaded up Corel Paint Shop Pro and loaded the same image. After it loaded it rendered very well on the same hardware. So are there any options to speed up the painting of JP2 files?

More background I converted the image format to JPG (it scrunched down to 20MB in size)  
 I then used the GdiPlusRasterLayer and it is just as slow. Pan zoom etc big delays. Are using external images just slow?  
 Is there a way to render them faster? Thanks 


Bob,


Thanks for your posts and questions.
 
If I am not making any mistake, what you are trying to use is Map Suite 3.x Desktop product even thought I found this post in 2.x discussion forum.
 
I think the Google etc is so faster because it has pre-generate the tiles in advance, so what it did is just get those tiles back and draw them on the screen, of course, it is absolutely using the much faster machines etc.
 
One way you could try is to set the TileCache for the Overlay and it should be much faster the second times because it won’t load data again. Code snippet is as following:

EcwRasterLayer ecwImageLayer = new EcwRasterLayer(@"..\..\SampleData\Data\World.ecw");
ecwImageLayer.UpperThreshold = double.MaxValue;
ecwImageLayer.LowerThreshold = 0;
 
LayerOverlay ImageOverlay = new LayerOverlay();
ImageOverlay.Layers.Add("EcwImageLayer", ecwImageLayer);
ImageOverlay.TileCache = new FileBitmapTileCache(@"C:\temp", "BaseImage");

 
Any more questions just feel free to let me know.
 
Thanks.
 
Yale

Yes this is for 3.0 Sorry wrong forum is there a way to move the thread?  
 I am a new to this so I will have to look up the tile caching. What is a good X/Y pixel size fora tile.   
 The image format we are using is JPEG2000. The actual pixel size of the original is 39500x8630.  
 I can break up the image up if need be. Is there any utility applications that can do this?  
 I am having one more problem. When I pan the left of right I get a large white space for redraw a 3 to 4 second pause then  
 it fills in the space. Is there any way to deal with that? 
 Thanks, 
 Bob

Yes this is for 3.0 Sorry wrong forum is there a way to move the thread?  

I am a new to this so I will have to look up the tile caching. What is a good X/Y pixel size fora tile.   

The image format we are using is JPEG2000. The actual pixel size of the original is 39500x8630.  

I can break up the image up if need be. Is there any utility applications that can do this?  

I am having one more problem. I reduced the size of the image by a factor of ten to 3950x863 to try and increase performance. It loads much quicker and displays ok but when I pan the left of right I get a large white space for redraw a 3 to 4 second pause then  

it pains/fills in the space. Is there any way to deal with that? 

Thanks, 

Bob



Bob, 
  
 Thanks for your post and you are here now, Map Suite 3.x discussion forum. 
  
 If you are using the Jpeg2000, just replace the EcwRasterLayer to GdiPlusRasterLayer, you do not need to break up the image, the component will do it automatically after you set the TileCache for the overlay. 
  
 Please have a try on the tile cache first, I think after that , it would not take 3-4 seconds to redraw. Any all, if you want to fill the space, we can do it, you have to set the thread to multi-thread instead of single thread by default, besides, you have to set the PreviewTileCache too, the way is similar as previous code. 
  
 If you have anything unclear, just feel free to let me know. 
  
 Thanks. 
  
 Yale 


Hi Yale,  
 Here is what I have so far. I am not sure its right. It only draws one tile. It does not draw the rest of the image?  
 Also I could not find the  
 I tried a JPG image as well with the GdiPulsRasterLayer and it did the same thing and I am not sure what you want me to  
 set to multi threaded.  
  
               string sMapFileName = sMapFolderName + "\" +  @"Image.jp2";                 
                AccuViewWpfMap.MapUnit = GeographyUnit.DecimalDegree; 
                Jpeg2000RasterLayer worldLayer = new Jpeg2000RasterLayer(sMapFileName);                 
                 worldLayer.UpperThreshold = double.MaxValue; 
                 worldLayer.LowerThreshold = 0; 
                 // you have to set the thread to multi-thread instead of single thread by  
                 // default, besides, you have to set the PreviewTileCache too, the way is similar as previous code.  
  
                LayerOverlay layerOverlay = new LayerOverlay(); 
                  
                layerOverlay.Layers.Add(worldLayer); 
                layerOverlay.TileCache = new FileBitmapTileCache(sMapFolderName, "BaseImage");   
                layerOverlay.PreviewTileCache = new FileBitmapTileCache(sMapFolderName, "PreviewImage");   
                AccuViewWpfMap.Overlays.Add(layerOverlay); 
                   
     If I can get over the refresh issue it would be great.  
    thanks, 
   bob 


I should add to this we draw using  
 AccuViewWpfMap.Refresh();  
 The issue is only one tile draws.  If I remove the tiles it draws the entire image very slowly.

Adding more information to the thread I found and altered the threading to MultiThreaded. I hope thats is what you were speaking of earlier.  
 AccuViewWpfMap.ThreadingMode = MapThreadingMode.Multithreaded; 
 But alas the  
 Tiles dont work and page still scrolls very poorly but if I use the MapSuiteExplorer (not sure what thats written in) the application performs well.

Bob,


Merry Christmas and happy New Year.
 
Thanks for your posts and codes, your code is exactly what we want.
 
I have to admit that I messed things up. Let us clean all these stuff today.
 
1) All these messed stuff is from a bug probably in GdiPlusRasterLayer & Jpeg2000RasterLayer which used to load a jpg or jp2 files.
 
I did not notice it until I use a jpg file for testing this morning(I always use the ecw file for test), it works fine if using the ecw, sid, wms etc RasterLayers, while it did have some problem when using tile cache for jpg file loading. I have submitted this to our working track system and hope it can be fixed soon.
 
2) I want to clear up the Multithread and PreviewTileCache stuff.
 
In fact, if you only want to enhance the performance, then nothing is needed to do about the thread and PreviewTileCache stuff, what you only need to do is trying to set the TileCache.
 
While, in your previous posts, you mentioned “When I pan the left of right I get a large white space for redraw a 3 to 4 second pause then it fills in the space. Is there any way to deal with that?”. Then the answer is setting PreviewTileCache. While the PreviewTileCache only works in multithreaded mode, it cannot work in Single threaded mode, so before setting preview tile cache, you have to change the thread mode.
 
So for now, during we are trying to solve the bug in loading jpg files, if you want, if you want, you could use an ecw or sid files to have a try to see the performance difference with and without tile cache. Hope it can satisfy your requirement.
 
Sorry for the inconvenience now.
 
Any more questions just feel free to let me know.
 
Thanks.
 
Yale

Bob,


We did some investigations on this, and found that this is not a bug.


Please try to set the Map Unit to Meter instead of DecimalDegree.



winformsMap1.MapUnit = GeographyUnit.Meter;

Sorry for the confusing.

Any more questions just feel free to let me know.


Thanks.


Yale

 



Hey Merry Christmas to you too.  
 Questions I have a BUNCH there seems to be a lot of information but I cant seem to connect the dots.  
  
 winformsMap1.MapUnit = GeographyUnit.Meter; 
  
 I did this and now the tiles seem to be created.  
 In fact with the help of a support ticket I create them once then read them then I load them up using  
  
  
 FileBitmapTileCache fileBitmapTileCache = new FileBitmapTileCache(sCacheFolderName + "\ImageCache", string.Empty); 
 // then set the tile size  
  
         fileBitmapTileCache.TileMatrix.TileHeight = 512; 
         fileBitmapTileCache.TileMatrix.TileWidth = 512; 
 
 
  
 I have NO clue as to how this mechanism really works the pan and scale seems quicker but not as good as I thought it would be.  
 I had a look a the images that were created by the TileCache  and its not exactly what I expected but what do I know.  
  
 Trying hard to make it faster I looked at something called ZoomLevels.  
 I figured that choosing zoom levels would help reduce the size of the image and make things move faster.  
 So I put the code below into place and tried to use ZoomLevels to change the zoom levels on a button click.  
  
 ZoomLevelSet zoomLevelSet = new ZoomLevelSet(); 
 ViewWpfMap.ZoomToScale(zoomLevelSet.ZoomLevel04.Scale); 
 ViewWpfMap.Refresh(); 
 
 
 It crashes with a null reference exception  
  
 While we are on the ZoomToScale is going from   
  
 zoomLevelSet.ZoomLevel03.Scale to  
 zoomLevelSet.ZoomLevel04.Scale  
  
 zooming the image up or zooming it down?  
  
 So far things are getting better but in need of some more tweeks.  
  


 


Bob,
 
I am glad to hear your progress.
 
ZoomLevel01 to ZoomLevel20 will go deeper and deeper, just like from very high sky to ground, via versa from ZoomLevel20 to ZoomLevel01.
 
About the null reference exception, could you send me a small sample application to recreate this problem? The ZoomToScale should work exactly as your code, following is a very simple code snippet worked perfectly in my application:

wpfMap1.MapUnit = GeographyUnit.DecimalDegree;
wpfMap1.BackgroundOverlay.BackgroundBrush = new GeoSolidBrush(GeoColor.GeographicColors.ShallowOcean);
 
WorldMapKitWmsDesktopOverlay worldMapKitDesktopOverlay = new WorldMapKitWmsDesktopOverlay();
wpfMap1.Overlays.Add(worldMapKitDesktopOverlay);
 
wpfMap1.CurrentExtent = new RectangleShape(-133.2515625, 89.2484375, 126.9046875, -88.290625);
 
ZoomLevelSet zoomLevelSet = new ZoomLevelSet();
wpfMap1.ZoomToScale(zoomLevelSet.ZoomLevel10.Scale);
 
wpfMap1.Refresh();

 
 
 
Any more questions just feel free to let me know.
 
Thanks.
 
Yale

Still have troubles but making progress.  I think the Null reference exception has something to do with the buiilding and using the tiles.  
 Here is how I am bulding tiles. My image is wide and long. I really dont how to build these tiles but proved that once they are built correctly 
 they will do what I want them to do. I figured out that the 0,1,2,3 subfolders mean rows and 0.png,1.png… x.png are the columns.  
 But alas the actual pictures generated are not right. I started with an example TestGenerateTilingImages Updated version. The context of the generated tiles were not correct. For instance the 2 columns generated 2 pictures but they were not a split of the original.  Here is the code I started with.  
  
 ////////////////////////////////////////// 
         private void btnGenerate_Click(object sender, EventArgs e) 
         { 
             Collection<ZoomLevel> zoomLevels = GetAllZoomLevels(); 
             int tileWidth = 512; 
             int tileHeight = 512; 
  
             EcwRasterLayer ecwImageLayer = new EcwRasterLayer(ecwFileName); 
             ecwImageLayer.UpperThreshold = double.MaxValue; 
             ecwImageLayer.LowerThreshold = 0; 
  
             MapEngine mapEngine = new MapEngine(); 
             mapEngine.StaticLayers.Add(ecwImageLayer); 
             GeographyUnit mapUnit = GeographyUnit.Meter; 
  
             ecwImageLayer.Open(); 
             RectangleShape cacheExtent = ecwImageLayer.GetBoundingBox(); 
             ecwImageLayer.Close(); 
  
             for (int i = 0; i < zoomLevels.Count; i++) 
             { 
                 MapSuiteTileMatrix tileMatrix = new MapSuiteTileMatrix(zoomLevels.Scale, tileWidth, tileHeight, mapUnit); 
                 BitmapTileCache tileCache = new FileBitmapTileCache(txtCachePath.Text, string.Empty, TileImageFormat.Png, tileMatrix); 
                 RowColumnRange rowColumnRange = tileCache.TileMatrix.GetIntersectingRowColumnRange(cacheExtent); 
  
                 for (long rowIndex = rowColumnRange.MinRowIndex; rowIndex < rowColumnRange.MaxRowIndex; rowIndex++) 
                 { 
                     for (long columnIndex = rowColumnRange.MinColumnIndex; columnIndex < rowColumnRange.MaxColumnIndex; columnIndex++) 
                     { 
                         Bitmap bitmap = new Bitmap(tileCache.TileMatrix.TileWidth, tileCache.TileMatrix.TileHeight); 
                         mapEngine.CurrentExtent = tileCache.TileMatrix.GetCell(rowIndex, columnIndex).BoundingBox; 
                         mapEngine.ShowLogo = false; 
                         mapEngine.OpenAllLayers(); 
                         mapEngine.DrawStaticLayers(bitmap, mapUnit); 
                         mapEngine.DrawDynamicLayers(bitmap, mapUnit); 
                         mapEngine.CloseAllLayers(); 
  
                         tileCache.SaveTiles(bitmap, mapEngine.CurrentExtent); 
                         bitmap.Dispose(); 
                     } 
                 } 
             } 
  
         } 
  
  
 I then started playing with the context bounding box etc and spent the entire day trying to reverse engineer how it worked…  
 I cant find any real informaiton on how this relates the original image.  
 Things I looked at are the GetVerticalResolution and GetHorizontalResolution returned 0.25   
 So when I looked at the cacheExtent rectangle the rectangle was 1/4 the size of the image.  
  
 Also I had to set this  to zero… 
    rowColumnRange.MinColumnIndex = 0; 
         rowColumnRange.MinRowIndex = 0;  
 The MinColumnIndex would come in with all kinds of values and wasnt zeroed out.  
  
 If i did not do this  
             if (rowColumnRange.MinRowIndex == 0 && rowColumnRange.MaxColumnIndex == 0) 
             { 
               mapEngine.CurrentExtent = cacheExtent; 
             } 
 The tile created for full size was only a small corner of the original image.  
 Anyway here is some code if you can PLEASE explain how the contexts controls and cuts up the tiles I can then  
 finish and generate the tiles.   
  
 Thanks for all the help so far.  
 bob 
  
  
  
    //      
  private void BuildTileCache(Jpeg2000RasterLayer worldLayer) 
     { 
       Collection<ZoomLevel> zoomLevels = GetAllZoomLevels(); 
       int tileWidth = 512; 
       int tileHeight = 512; 
  
       RectangleShape cacheExtent = worldLayer.GetBoundingBox();        
       MapEngine mapEngine = new MapEngine(); 
       mapEngine.CurrentExtent = cacheExtent; 
       mapEngine.StaticLayers.Add(worldLayer); 
       
       GeographyUnit mapUnit = GeographyUnit.DecimalDegree; 
       // GeographyUnit mapUnit = GeographyUnit.Meter; 
       string ImgCache = sMapFolderName + "\ImageCache"; 
       worldLayer.Open(); 
        
       RectangleShape RS = worldLayer.GetBoundingBox(); 
       float y = worldLayer.GetVerticalResolution(); 
       float x = worldLayer.GetHorizontalResolution();        
       worldLayer.Close(); 
  
       for (int i = 0; i < zoomLevels.Count; i++) 
       { 
         MapSuiteTileMatrix tileMatrix = new MapSuiteTileMatrix(zoomLevels.Scale, tileWidth, tileHeight, mapUnit); 
         BitmapTileCache tileCache = new FileBitmapTileCache(ImgCache, string.Empty, TileImageFormat.Png, tileMatrix); 
         RowColumnRange rowColumnRange = tileCache.TileMatrix.GetIntersectingRowColumnRange(cacheExtent); 
         RectangleShape tileExtent = tileCache.TileMatrix.GetCell(0, 0).BoundingBox; 
         rowColumnRange.MinColumnIndex = 0; 
         rowColumnRange.MinRowIndex = 0; 
         for (long rowIndex = rowColumnRange.MinRowIndex; rowIndex <= rowColumnRange.MaxRowIndex; rowIndex++) 
         { 
           for (long columnIndex = rowColumnRange.MinColumnIndex; columnIndex <= rowColumnRange.MaxColumnIndex; columnIndex++) 
           { 
             Bitmap bitmap = new Bitmap(tileWidth, tileHeight); 
             if (rowColumnRange.MinRowIndex == 0 && rowColumnRange.MaxColumnIndex == 0) 
             { 
               mapEngine.CurrentExtent = cacheExtent; 
             } 
             else 
             {             
               RectangleShape tileExt = tileCache.TileMatrix.GetCell(rowIndex, columnIndex).BoundingBox; 
               double deltax, deltay; 
               if (rowColumnRange.MaxColumnIndex > 0) 
                 deltax = cacheExtent.Width / ((double)rowColumnRange.MaxColumnIndex + 1.0); 
               else 
                 deltax = cacheExtent.Width; 
  
               if (rowColumnRange.MaxRowIndex > 0) 
                 deltay = cacheExtent.Height / ((double)rowColumnRange.MaxRowIndex + 1.0); 
               else 
                 deltay = cacheExtent.Height; 
  
               double MinX = cacheExtent.LowerLeftPoint.X + (deltax * (double)columnIndex); 
               double MaxX = MinX + deltax; 
               double MinY = cacheExtent.LowerLeftPoint.Y + (deltay * (double)rowIndex); 
               double MaxY = MinY + deltay; 
               RectangleShape rc = new RectangleShape(MinX, MaxY, 
                                                      MaxX, MinY); 
                                            
               tileCache.TileMatrix.BoundingBox = rc;            
               //tileExt.CurrentExtent = cacheExtent;                                                      
                mapEngine.CurrentExtent = cacheExtent; 
               //mapEngine.CurrentExtent = rc; 
  
             } 
  
             mapEngine.ShowLogo = false; 
             mapEngine.OpenAllLayers(); 
             mapEngine.DrawStaticLayers(bitmap, mapUnit); 
             mapEngine.DrawDynamicLayers(bitmap, mapUnit); 
             mapEngine.CloseAllLayers(); 
  
             tileCache.SaveTiles(bitmap, mapEngine.CurrentExtent); 
             bitmap.Dispose(); 
           } 
         } 
       } 
  
  


To be clearer the problem I am having is the tiles are not cutting up the original image into into sections. The image tiles are all starting from the upper left corner and I am not able to figure out how to make this function cut the image up from left to right into individual pieces. I am sure it has something to do with the CurrentExtent or the BoundingBox but I am not sure what or how.  
 Thanks for any help.

Bob,


I made a sample for you in attachment.


You can see from the sample that how to split tiles by saving it to folder and then use it with use of FileBitmapTileCache, also, I set the tile with and tile height to 512, hope it was what we are trying to achieve.


If you still have problem, could you send me your data? We will try to make a sample exactly for you. You can contact our support(support@thinkgeo.com) to see how you can upload your data to us if it its size is too large.


Any more questions just feel free to let me know.


Thanks.


Yale



1594-TestGenerateTilingImages.zip (180 KB)

I will see about sending you a sample. I dont know if I can send you the image. I will have to check to make sure I am not breaking any internal contracts.

Uploaded the image to support and I hope I can get a better understanding on how to implement the tiles. 

 


Thanks



Bob, 
  
 Thanks for your post. I will reply in more detail and send you a demo about it in your ticket (ticket2443). 
  
 Thanks for your response. 
  
 Yale