ThinkGeo.com    |     Documentation    |     Premium Support

TileCache strategy

I'm trying to improve the map rendering performance using tile cache.


I'm thinking about generating tiles for some zoom levels in advance. In my map, I configured 10 zoom levels. Then, I could generate tiles for the first say 5 zoom levels. How can I do that?


After the zoom level 5 (6, 7, 8, 9 and 10), I would like to use TileCache, but I want to limit the size of this type cache. Let's say that I want to use just 100MB for those levels. How can I configure a cache size limit? Is there a property that limits the cache size?


Thank you



Hi Gustavo,



I'm sorry that I cannot spend much time to work on your exact requirement. But here I wrote a method for you to help generating the tiles; you will know more about our cache system after you reading this code; I'm sure you can implement your requirement by your logic later. It's a long reply here;


this method generates cache for one zoomlevel.
private static void GenerateCacheFiles(IEnumerable<Layer> layers, double scale, RectangleShape restrictedExtent, GeographyUnit mapUnit, string cacheDirectory, string cacheId)
{
    // create a MapSuite default tile matrix object.
    MapSuiteTileMatrix matrix = new MapSuiteTileMatrix(scale, 256, 256, mapUnit);

    // get cells in the specified extent.
    Collection<TileMatrixCell> cells = matrix.GetIntersectingCells(restrictedExtent);

    // create a tile cache object with the cache directory and cache id.
    FileBitmapTileCache tileCache = new FileBitmapTileCache(cacheDirectory, cacheId);

    foreach (TileMatrixCell cell in cells)
    {
        // create a bitmap whoes size equals to the tile size.
        using (Bitmap bitmap = new Bitmap(256, 256))
        {
            // create a GeoCanvas which for drawing the passed layers.
            GdiPlusGeoCanvas geoCanvas = new GdiPlusGeoCanvas();
            geoCanvas.BeginDrawing(bitmap, cell.BoundingBox, mapUnit);
            foreach (Layer layer in layers)
            {
                lock (layer)
                {
                    if (!layer.IsOpen) { layer.Open(); }
                    layer.Draw(geoCanvas, new Collection<SimpleCandidate>());
                }
            }

            // you can commend this line out because it's just for mark a cache label in order to see if it works with our samples.
            geoCanvas.DrawText("Cache", new GeoFont("Arial", 12), new GeoSolidBrush(GeoColor.SimpleColors.Black), new Collection<ScreenPointF>() { new ScreenPointF(128f, 128f) }, DrawingLevel.LabelLevel);
            geoCanvas.EndDrawing();

            // create tile object to maintain current tile information.
            BitmapTile tile = new BitmapTile(bitmap, cell.BoundingBox, scale);

            // save tile.
            tileCache.SaveTile(tile);
        }
    }
}


The following code indicates how to use the method above to generate cache files from zoomlevel01 to 03.
string cacheDirectory = @"c:\share\new";
string cacheId = "Cache1";

ZoomLevelSet zoomLevelSet = new ZoomLevelSet();
Collection<Layer> layers = new Collection<Layer>() { worldLayer };
RectangleShape restrictedExtent = new RectangleShape(-180, 90, 180, -90);
GenerateCacheFiles(layers, zoomLevelSet.ZoomLevel01.Scale, restrictedExtent, GeographyUnit.DecimalDegree, cacheDirectory, cacheId);
GenerateCacheFiles(layers, zoomLevelSet.ZoomLevel02.Scale, restrictedExtent, GeographyUnit.DecimalDegree, cacheDirectory, cacheId);
GenerateCacheFiles(layers, zoomLevelSet.ZoomLevel03.Scale, restrictedExtent, GeographyUnit.DecimalDegree, cacheDirectory, cacheId);
MessageBox.Show("Done");


The following code indicates how to use the cache files.
Map1.MapUnit = GeographyUnit.DecimalDegree;
Map1.CurrentExtent = new RectangleShape(-155.733, 95.60, 104.42, -81.9);

ShapeFileFeatureLayer worldLayer = new ShapeFileFeatureLayer(@"..\..\SampleData\Data\Countries02.shp");
worldLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.CreateSimpleAreaStyle(GeoColor.SimpleColors.Transparent, GeoColor.FromArgb(100, GeoColor.SimpleColors.Green));
worldLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

string cacheDirectory = @"c:\share\new";
string cacheId = "Cache1";

LayerOverlay layerOverlay = new LayerOverlay();
layerOverlay.Layers.Add(worldLayer);
layerOverlay.TileCache = new FileBitmapTileCache(cacheDirectory, cacheId);
Map1.Overlays.Add(layerOverlay);

Map1.Refresh();


Please let me know if you have any queries. We adjusted the matrix in the WpfDesktop Edition few days ago, please download the latest build if this sample doesn't work.



Thanks,

Howard



That seems to work for me Howard. I’ll try that soon. 
  
 However, how can I limit the cache size? Instead of the cache automatically always put things in the file system, it could delete some tiles in order to cache others if the cache reaches a certain size. Otherwise, it can waste too much disk storage.

I apologize if I should have posted a new topic for this, but is there a way to reverse the process?  If given a bounding box, can I delete the tiles in each zoom level that intersect with it?  I can get the tilematrix cells for each layer, but what do I do to delete those?  I would use this to update features without having to invalidate the entire cache.

Hi Gustavo,



As I thought, to override the FileBitmapTileCache.SaveTileCore method is the best way. The method raises when every tile that saves into the disk. You can check the size from your own disk and remove the out-of-limit files. 



Here attaches a simple class for you; please see the logic and fill your removing files logic in that class. Also to limit the tile count is another option for you; it’s just an idea.



Hope it makes sense.



Thanks,

Howard 

 



SizeLimitedFileBitmapTileCache.txt (3.02 KB)

Hi Jake,



Please see the attached sample above; there might be two useful methods for you. One is getting cell in the matrix by a specified bounding box; while another one is the private method GetTileImageFileName that helps you find the files back. For example:

Collection<TileMatrixCell> cells = tileMatrix.GetIntersectingCells(worldExtent);
foreach (TileMatrixCell cell in cells)
{
    BitmapTile tile = new BitmapTile(cell.BoundingBox, tileMatrix.Scale);
    tileCache.DeleteTile(tile);
}



TileMatrix has many useful method for you to find the cells information, please have a try and let me know if you have any queries.



Thanks,

Howard



Thanks, I wasn’t sure if the BitmapTile would retrieve the correct tile, I will give it a try.

Jake,  
  
 It will do because we are using this way. If you have any problem of this issue, just let me know. 
  
 Thanks, 
 Howard

Great Tanx.

Hi Ben, 
  
 You are welcome, just let me know if you have any more queries. 
  
 Thanks, 
 Howard

Howard, 



I am not seeing the results I hoped for.  I believe the tile is being found correctly, I see the row/column combination points to the cached file.  I call tileCache.DeleteTile(tile) as shown above, but the file remains.  Below is the code I am using.  The cacheDir and CacheID are the same as the ones used to create the files.  Does anything else need to be done to have the file deleted successfully? 




<layer><tilematrixcell><tilematrixcell></tilematrixcell></tilematrixcell></layer>




public void DeleteCacheFiles(double scale, RectangleShape restrictedExtent, GeographyUnit mapUnit, string cacheDirectory, string cacheId)
        {
            ZoomLevelSet zmls = new ZoomLevelSet();
            // create a MapSuite default tile matrix object.
            MapSuiteTileMatrix matrix = new MapSuiteTileMatrix(scale, 256, 256, mapUnit);

            // get cells in the specified extent.
            Collection<TileMatrixCell> cells = matrix.GetIntersectingCells(restrictedExtent);
            //Collection<TileMatrixCell> cells2 = matrix.GetIntersectingRowColumnRange(restrictedExtent);

            // create a tile cache object with the cache directory and cache id.
            FileBitmapTileCache tileCache = new FileBitmapTileCache(cacheDirectory, cacheId);
            tileCache.TileAccessMode = TileAccessMode.ReadAddDelete;
            //tileCache.DeleteTiles(restrictedExtent);
            foreach (TileMatrixCell cell in cells)
            {
                // create a bitmap whoes size equals to the tile size.
                BitmapTile tile = new BitmapTile(cell.BoundingBox, matrix.Scale);
                tileCache.DeleteTile(tile);

            }
        }
 


Hi Jake,



I checked your code and found one thing missing. I think that's my fault that forget to tell you to set the scale on the cache before calling the tileCache.DeleteTile; sorry. Here I add only one line in your method that set the scale on the cache and it works fine.

ZoomLevelSet zmls = new ZoomLevelSet();
// create a MapSuite default tile matrix object.
MapSuiteTileMatrix matrix = new MapSuiteTileMatrix(scale, 256, 256, mapUnit);

// get cells in the specified extent.
Collection<TileMatrixCell> cells = matrix.GetIntersectingCells(restrictedExtent);

// create a tile cache object with the cache directory and cache id.
FileBitmapTileCache tileCache = new FileBitmapTileCache(cacheDirectory, cacheId);
tileCache.TileAccessMode = TileAccessMode.ReadAddDelete;
tileCache.TileMatrix.Scale = scale;
foreach (TileMatrixCell cell in cells)
{
    // create a bitmap whoes size equals to the tile size.
    BitmapTile tile = new BitmapTile(cell.BoundingBox, scale);
    tileCache.DeleteTile(tile);
}



Thanks and let me how it works,

Howard



Thank you, that works great.

Jake,  
  
 You are welcome. Also thanks for sharing your method. Just let us know if you have more queries. 
  
 Thanks, 
 Howard

Hi,



I've implemented your code for creating tile cache, which works almost flawlessly.  The only issue I run into is when my tile displays labels from multiple layers, they can appear on top of each other despite specifically setting AllowOverlap = false in the style of the layer.  I haven't been able to reproduce this in tiles generated using my mapping program, just the tiling program.  I think it may have something to do with the line 



layer.Draw(geoCanvas, new Collection<SimpleCandidate>());



in that the layer has no idea what labels other layers wants to draw... at least that is my guess looking at the code.  I've included a screenshot of one of the tiles so you can see what I mean.


Any help would be appreciated.


 


Ryan


 




Ryan, 
  
 I am afraid I didn’t get your point so that I can not reproduce your problem.  Could you provide more information or some sample code to us? 
  
 If possible, the most straightforward way is to send the data to us so we can completed recreate your issue. You can ask support@thinkgeo.com to create an FTP account for you to upload. 
  
 Thanks, 
 James 
  


I’ll see if I can come up with a sample later today.  My issue is that as you can see in the image I posted, labels are being placed on top of each other.  I have like 5 different labels that store these labels.  I haven’t noticed this when the map renders the tiles, only when I’m using my cacher using the above posted code to create the tiles. 
  
 I’ve ran into another issue where I get an ArguementOutOfRangeException in layer.Draw(…) of a layer that has labels.  The weird thing is I’ve cached zoom levels 1-15 just fine, but zoom level 16 fails.  The style that I used starts at level 15, so its not like its something with my style.  Anyhow, I’ll post the stacktrace in case you can figure something out from that and hopefully get a sample up later today. 
  
 .Ryan. 
  
    at System.ThrowHelper.ThrowArgumentOutOfRangeException() 
    at System.Collections.Generic.List`1.get_Item(Int32 index) 
    at System.Collections.ObjectModel.Collection`1.get_Item(Int32 index) 
    at ThinkGeo.MapSuite.Core.PositionStyle.GetLineLabelAdjuster(MultilineShape multilineShape, RectangleShape screenExtent, Boolean allowLineLabelingMoving) 
    at ThinkGeo.MapSuite.Core.PositionStyle.GetLabelingCandidateForMultiline(MultilineShape multilineShape, String text, GeoCanvas canvas) 
    at ThinkGeo.MapSuite.Core.PositionStyle.GetLabelingCandidateCore(Feature feature, GeoCanvas canvas) 
    at ThinkGeo.MapSuite.Core.PositionStyle.GetLabelingCandidates(Feature feature, GeoCanvas canvas) 
    at ThinkGeo.MapSuite.Core.PositionStyle.DrawOneFeature(Feature feature, GeoCanvas canvas, Collection`1 labelsInThisLayer, Collection`1 labelsInAllLayers) 
    at ThinkGeo.MapSuite.Core.PositionStyle.DrawCore(IEnumerable`1 features, GeoCanvas canvas, Collection`1 labelsInThisLayer, Collection`1 labelsInAllLayers) 
    at ThinkGeo.MapSuite.Core.Style.Draw(IEnumerable`1 features, GeoCanvas canvas, Collection`1 labelsInThisLayer, Collection`1 labelsInAllLayers) 
    at ThinkGeo.MapSuite.Core.ZoomLevel.DrawCore(GeoCanvas canvas, IEnumerable`1 features, Collection`1 currentLayerLabels, Collection`1 allLayerLabels) 
    at ThinkGeo.MapSuite.Core.ZoomLevel.Draw(GeoCanvas canvas, IEnumerable`1 features, Collection`1 currentLayerLabels, Collection`1 allLayerLabels) 
    at ThinkGeo.MapSuite.Core.FeatureLayer.DrawCore(GeoCanvas canvas, Collection`1 labelsInAllLayers) 
    at ThinkGeo.MapSuite.Core.Layer.Draw(GeoCanvas canvas, Collection`1 labelsInAllLayers) 
    at CreateTileCache.Program.GenerateCacheFiles(IEnumerable`1 layers, Double scale, RectangleShape restrictedExtent, GeographyUnit mapUnit, String cacheDirectory, String cacheId)

Ryan,


 


For the first issue, we’ve successfully recreated it and it’s caused by the code line you mentioned, the following code snippet shows how to fix it.


 


  foreach (TileMatrixCell cell in cells)


            {


                // create a bitmap whoes size equals to the tile size.


                using (Bitmap bitmap = new Bitmap(256, 256))


                {


                    // create a GeoCanvas which for drawing the passed in layers.


                    GdiPlusGeoCanvas geoCanvas = new GdiPlusGeoCanvas();


                    geoCanvas.BeginDrawing(bitmap, cell.BoundingBox, mapUnit);


                    GeoCollection<SimpleCandidate> candidates = new GeoCollection<SimpleCandidate>();


                    foreach (Layer layer in layers)


                    {


                        lock (layer)


                        {


                            if (!layer.IsOpen) { layer.Open(); }


                            layer.Draw(geoCanvas,candidates);


                        }


                    }


                    // you can commend this line out because it's just for mark a cache label in order to see if it works with our samples.


                    geoCanvas.DrawText("Cache", new GeoFont("Arial", 12), new GeoSolidBrush(GeoColor.SimpleColors.Black), new Collection<ScreenPointF>() { new ScreenPointF(128f, 128f) }, DrawingLevel.LabelLevel);


                    geoCanvas.EndDrawing();


                    // create tile object to maintain current tile information.


                    BitmapTile tile = new BitmapTile(bitmap, cell.BoundingBox, scale);


                    // save tile.


                    tileCache.SaveTile(tile);


                }


            }


 


You just need to replace the original foreach loop in the GenerateCacheFiles method with it.

For the second one, we’re still investigating it, any updates we’ll let you know.


 


Thanks.

 


James


 




Hi James,


The code for the labels worked perfectly.  Thanks!  Were you able to reproduce the crash at zoom level 16 or do you need a sample app for that?


Thanks,


.Ryan.