ThinkGeo.com    |     Documentation    |     Premium Support

Tile Cache Generator Questions and Issues Related to MVC Project

We are currently using Map Suite MVC 8, versions 8.0.336.0.
We are using OpenStreetMap (OSM) as the base layer for our maps. We have a
number of static layers that are based on shapefile data. These seem like a
good candidate for server-side caching to supplement the client-side caching
already in place. I have been doing some testing and figured I would look into
generating the tile caches beforehand using the Cache Generator tool ThinkGeo
has provided. Unfortunately, I am having some issues using it. Before I go into
specifics, I have several general questions regarding the Cache Generator and
its use.



Note
: Our maps
use a default extent defined as “DefaultExtent = new
RectangleShape(-80.4014, 40.7301, -79.5431, 40.1972);” that then gets projected to the OSM projection of EPSG 3857 since our maps our using OSM for the base layer.  I am using
in place of the values in the Cache Generator form’s textboxes.




        
  1. Since we are using OSM we are projecting all our data in our
        project.   Should the extent in the Cache
        Generator be projected to the OSM projection (EPSG: 3857)? I tried doing this
        and am not getting the expected results.

  2.     
  3. Our shapefiles use NAD83 and NAD27 projections.  Should I be projecting the layers when I set
        them up in the Cache Generator to the projection of the base map layer(EPSG: 3857)? Again, I am not getting the expected results
        from doing this.

  4.     
  5. What is the source of the OSM Overlay and OSM Layers data? I
        know ThinkGeo has their own OSM Server that you update weekly, but I do not
        know if the OSM Overlay/Layer request from this server or the OSM servers in the MVC Edition. This
        relates to my following question.

  6.     
  7. Is it possible to generate the tile cache for the OSM layer,
        and if so would we see a performance benefit in doing so? I am not certain, but
        it seems like this layer is taking the longest to render on our maps. We could
        always periodically dump and regenerate the cache for this layer to account for
        the changing nature of OSM data.

  8.     
  9. Can you explain the directory structure and naming for the
        tiles generated? It looks to be related to the tile coordinates, but I cannot quite figure it out.



I have been doing some testing with enabling server-side
caching on our project’s maps. A number of our map layers are based on static
data, or data that gets updated biannually at most. I figured that these would
be good candidates for server-side caching in addition to the client-side
caching already implemented.   That seems
to be working in my limited testing, and I figured that I would go ahead and
look into pregenerating the tiles using the Cache Generator tool.  Unfortunately I am finding that the cached
tiles generated from the utility and those generated from our maps are not the
same.  The directory structure is not the same either which leads me to believe that something is wrong with the tile coordinates. I am using one layer for testing, with our default extent as I listed above, and  limited to ZoomLevel11 to simplify things. 



 Here is the code
being used to setup the layer in our project:



private LayerOverlay SetupZipCodeOverlay(bool visible, bool visibleInOverlaySwitcher)
{
    string overlayID = “zipCodes”, overlayName = “ZIP Codes”, fileName = “Zips.shp”, layerKey = “zipCodes”;
 
    TextStyle textStyle = new TextStyle(“ZIP”, DefaultAreaFeatureFont, new GeoSolidBrush(GeoColor.StandardColors.Red));
 
    //Setup Overlay to hold and display our data
    LayerOverlay zipsOverlay = new LayerOverlay(overlayID, false, TileType.MultipleTile);
 
    //Set the Overlay’s name, which will be displayed in the Overlay Switcher.
    zipsOverlay.Name = overlayName;
 
    //Load the features from the shapefile
    ShapeFileFeatureLayer zipsLayer = new ShapeFileFeatureLayer(GeoDataDirectory + fileName);
 
    //Setup Styles determining how the layer will be displayed
 
    //Display the data from the shapefile, with each feature defined in the file outlined on the map
    zipsLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.CreateSimpleAreaStyle(GeoColor.StandardColors.Transparent, GeoColor.StandardColors.Red);
 
    //Label features as defined in a column value in the .dbf file associated with the shapefile, located in the center of the feature by default
    zipsLayer.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = textStyle;
 
    //Setup layer to apply the styles used to all zoom levels, consider revising or creating custom zoom levels in the future if client desires
    zipsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
 
    //Apply projection to layer, the projection must be opened before this is done and should be closed afterwards
    try
    {
        NAD83toSphMercator.Open();
        zipsLayer.FeatureSource.Projection = NAD83toSphMercator;
    }
    finally
    {
        NAD83toSphMercator.Close();
    }
 
    //Add our layer to the overlay, apply caching to the overlay, set visibility, and return the constructed overlay
    zipsOverlay.Layers.Add(layerKey, zipsLayer);
    zipsOverlay.ClientCache = MapClientCache;
    zipsOverlay.IsVisible = visible;
    zipsOverlay.IsVisibleInOverlaySwitcher = visibleInOverlaySwitcher;
 
    return zipsOverlay;
}

Here is the code I am using to try to setup the layer in the Cache Generator, which I then add to the layersToCache Collection:


public static ShapeFileFeatureLayer SetupZonesLayer()
{
    string fileName = “Zones.shp”;
 
    TextStyle textStyle = new TextStyle(“ZONE_NAME”, DefaultAreaFeatureFont, new GeoSolidBrush(GeoColor.StandardColors.Blue));
 
    //Load the features from the shapefile
    ShapeFileFeatureLayer zonesLayer = new ShapeFileFeatureLayer(@"…\GeoData" + fileName);
 
    //Display the data from the shapefile, with each feature defined in the file outlined on the map
    zonesLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.CreateSimpleAreaStyle(GeoColor.StandardColors.Transparent, GeoColor.StandardColors.Blue);
 
    //Label features as defined in a column value in the .dbf file associated with the shapefile, located in the center of the feature by default
    zonesLayer.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = textStyle;
 
    //Setup layer to apply the styles used to all zoom levels, consider revising or creating custom zoom levels in the future if client desires
    zonesLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
 
    //Apply projection to layer, the projection must be opened before this is done and should be closed afterwards
    try
    {
        Debug.WriteLine("Zones ZoomLevel11 Scale: " + zonesLayer.ZoomLevelSet.ZoomLevel11.Scale);
 
        NAD27toSphMercator.Open();
        zonesLayer.FeatureSource.Projection = NAD27toSphMercator;
    }
    catch (Exception e)
    {
        Debug.WriteLine(e.Message);
    }
    finally
    {
        NAD27toSphMercator.Close();
    }
 
    return zonesLayer;
}



Hi Michael,



For question #1: In order to make it use meter in EPSG 3857, we need to do the below changes:
1. Changing the map GeographyUnit as Meter in MainForm.cs.
2. Fill the layers into layerCollection with projection 3857 in LayerProvider.cs’s GetLayersToCache method.



Proj4Projection prj4 = new Proj4Projection()
            {
                InternalProjectionParametersString = Proj4Projection.GetDecimalDegreesParametersString(),
                ExternalProjectionParametersString = Proj4Projection.GetEpsgParametersString(3857)
            };
            prj4.Open();
            shapeFileLayer.FeatureSource.Projection = prj4;

3. In GetScalesToCache method, using the SphericalMercatorZoomLevelSet rather than ZoomLevelSet.



Then, run the project and make sure all the input are based on epsg 3857.



#2, Please following the step 2, just changing the InternalProjection as yours, keep the ExternalProjection as epsg 3857.
#3, The OSM Overlay or OMS layer’s data source is from OpenStreetMap. The OSM Overlay/layer will request the tiles from their servers. We created a custom OSM layer service and the WorldMapKitWmsWebOverlay is requesting from it.
#4, Yes, The Cache Generator supports to generate tiles from OSM layer, you can simply add the OSM layer into the layer collection in #1 steps 2.
#5, The first folder name is the same the current zoom scale value, then the second depth folder name is the tile row’s index, the third depth folder name is the tile column’s index. The whole map will be splited by a grid in each zoom and so each tile will be identified with a row and column index.



Btw, your codes looks good and I didn’t find some unexpected codes.



Thanks,



Troy

I noticed an extra folder before the zoom scale value folder for the OSM layer, what exactly does this correspond to? 
  
 Also, I am receiving some errors on the call layer.draw() in the DrawToBitmap() method when trying to draw multiple ZoomLevels. This especially seems to occur with trying to increase the thread count. The exceptions being thrown are regarding the projection not being open, or sometimes the point not forming a closed line.

Hi Michael, 
  
 Could you please let us know what’s the extra folder? 
  
 When you initialization any projection like ManagedProj4Projection, you should want to open it for avoid the issue. About “the point not forming a closed line” issue, I hadn’t met that before, if possible please provide more detail information about that. 
  
 Regards, 
  
 Don

I sorry for the delay I have been busy working on various things.



I managed to get most of the issues straightened out (minor issues in my code) except for generating the OpenStreetMap layer.



I am attempting to set this up as follows, though I am not certain this is the proper way to set this up:


//Create the OpenStreetMapLayer.
OpenStreetMapLayer osmLayer = new OpenStreetMapLayer();
 
osmLayer.Name = "OpenStreetMap";
 
//Set the caching properties for the Layer.
osmLayer.CachePictureFormat = OpenStreetMapLayerPictureFormat.Png32;
 
osmLayer.TileCache = new FileBitmapTileCache(cacheDir);
osmLayer.TileCache.CacheId = "OSM";




When trying to generate it I am getting the following error from layer.Draw() in DrawOnBitMap method of TileGenerator:



System.ArgumentException was unhandled
  HResult=-2147024809
  Message=Parameter is not valid.
  Source=System.Drawing
  StackTrace:
       at System.Drawing.Bitmap…ctor(Int32 width, Int32 height, PixelFormat format)
       at ThinkGeo.MapSuite.Core.ExtentHelper.rWQ=(Bitmap rmQ=, Rectangle r2Q=, RectangleF sGQ=)
       at ThinkGeo.MapSuite.Core.OpenStreetMapLayer.DrawCore(GeoCanvas canvas, Collection`1 labelsInAllLayers)
       at ThinkGeo.MapSuite.Core.Layer.Draw(GeoCanvas canvas, Collection`1 labelsInAllLayers)
       at CacheGenerator.TileCacheGenerator.DrawOnBitmap(IEnumerable`1 layersTmp, GeographyUnit mapUnit, RectangleShape extent, Bitmap bitmap) in c:\Users\deleo_000\Desktop\ServicesEditionSample_CacheGenerator\CacheGenerator\TileCacheGenerator.cs:line 321
       at CacheGenerator.TileCacheGenerator.CreateTiles(FileBitmapTileCache tileCache, RectangleShape extent, Int32 bitmapWidth, Int32 bitmapHeight, Bitmap watermarkBitmap, ShapeFileFeatureLayer restrictionLayer) in c:\Users\deleo_000\Desktop\ServicesEditionSample_CacheGenerator\CacheGenerator\TileCacheGenerator.cs:line 304
       at CacheGenerator.TileCacheGenerator.GenerateTilesForOneScale(Object scale) in c:\Users\deleo_000\Desktop\ServicesEditionSample_CacheGenerator\CacheGenerator\TileCacheGenerator.cs:line 195
       at CacheGenerator.TileCacheGenerator.GenerateTiles(Object scales) in c:\Users\deleo_000\Desktop\ServicesEditionSample_CacheGenerator\CacheGenerator\TileCacheGenerator.cs:line 142
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart(Object obj)
  InnerException: 

I should point out that I am working from a heavily modified version of the Tile Generator (what I was working on the last several weeks that kept me from getting back to this thread).



In order to meet our needs I had to gut and rework major aspects of the application, though the low level stuff that actually generates the map tiles is virtually untouched. Most notably, the TileGenerator now acts as master class that handles updating the UI thread, performing the tile count calculations, and manages and coordinates instances of the new LayerGenerator class that actually generate the tiles. The code that actually generates the tiles was moved to this new LayerGenerator class.  I did  switch over to using the Tasks from the Task Parralel Library in order to leverage the ThreadPool, and each layer is generating its own tiles as we need the ability to toggle different layers on and off on our maps while still maintaining the benefits of using cached tiles. What I built works quite nicely for all of our shapefile based layers. It is just the OpenStreetMap layer that is not working. I will need to check to be certain, but I should be able to provide full source code for the modified tile generator, as well as our shapefile data, if needed.



I have tested generating the OpenStreetMapLayer on the original Tile Generator application, and it is working. I would, however, like to get this working on our custom Tile Generator so that there is only one application that will need to be used. I suspect that the underlying problem is related to the difference in the implementation of the  DrawCore() methods between  the OpenStreetMapLayer and the ShapeFileFeatureLayer.



I am now getting the following exception thrown from DrawOnBitmap method when calling layer.Draw() for the OpenStreetMapLayer:



System.NullReferenceException was unhandled by user code
  HResult=-2147467261
  Message=Object reference not set to an instance of an object.
  Source=MapSuiteCore
  StackTrace:
       at ThinkGeo.MapSuite.Core.OpenStreetMapLayer.DrawCore(GeoCanvas canvas, Collection`1 labelsInAllLayers)
       at ThinkGeo.MapSuite.Core.Layer.Draw(GeoCanvas canvas, Collection`1 labelsInAllLayers)
       at MapTileCacheGenerator.LayerGenerator.DrawOnBitmap(RectangleShape extent, Bitmap bitmap) in c:\Users\deleo_000\Documents\Work\Access_Infinity_2_0\ACCESS2.0\ACCESSWeb\MapTileCacheGenerator\TileCacheGenerator.cs:line 515
       at MapTileCacheGenerator.LayerGenerator.CreateTiles(FileBitmapTileCache tileCache, RectangleShape extent, Int32 bitmapWidth, Int32 bitmapHeight, Bitmap watermarkBitmap, ShapeFileFeatureLayer restrictionLayer) in c:\Users\deleo_000\Documents\Work\Access_Infinity_2_0\ACCESS2.0\ACCESSWeb\MapTileCacheGenerator\TileCacheGenerator.cs:line 448
       at MapTileCacheGenerator.LayerGenerator.CreateTiles(CancellationToken ct) in c:\Users\deleo_000\Documents\Work\Access_Infinity_2_0\ACCESS2.0\ACCESSWeb\MapTileCacheGenerator\TileCacheGenerator.cs:line 419
       at MapTileCacheGenerator.LayerGenerator.<generatetiles>b__0(Object taskName) in c:\Users\deleo_000\Documents\Work\Access_Infinity_2_0\ACCESS2.0\ACCESSWeb\MapTileCacheGenerator\TileCacheGenerator.cs:line 299</generatetiles>
       at System.Threading.Tasks.Task.Execute()
  InnerException: 




Here is the code generating the exception:

Note 1: This method is a member of the LayerGenerator class, though it almost identical to the corresponding method in the original TileGenerator.

Note 2: Layer is a property of the LayerGenerator class that stores a reference to the OpenStreetMapLayer defined in the LayerProvider.


protected void DrawOnBitmap(RectangleShape extent, Bitmap bitmap)
{
    GeoCanvas canvas = new GdiPlusGeoCanvas();
 
    Layer workingLayer = Layer.CloneDeep();
     
    try
    {
        canvas.BeginDrawing(bitmap, extent, MapUnit);
 
        if (!workingLayer.IsOpen)
        {
            workingLayer.Open();
        }
 
        workingLayer.Draw(canvas, new Collection<simplecandidate>());
    }
    finally
    {
        workingLayer.Close();
 
        canvas.EndDrawing();
    }
}





Hi Michael,


I can’t reproduce this exception
issue, so, I make a simple sample for you, can you help me to reproduce this
issue base on my sample? 


Thanks,



post12491.zip (9.31 KB)

The supplied project is a a WPFDesktopEdition project, which I do not have the dlls for. Looking at the code, I am not entirely sure how this applies toward my issue with the Tile Generator. Was this perhaps meant for someone else?

Hi Michael,



Apology for Don’s fault.



I double checked your issues and have some guesses: 

1. Would you let us know why you added a TileCache for the OSMlayer? Normally, the TileCache for OpenStreetMapLayer is only when we want to cache the tiles on the fly, not for pre-generated files. So, you may comment it out and let Generator class to generate them.

2. You are using Layer.CloneDeep() in your DrawOnBitmap method, but as I know the layers which depends on online service are not fit for Clone. So, I guess the non-references exception is because under multi-thread they are still sharing one the OpenStreetMapLayer instance. Would you please try to lock the layer rather than Clone the layer or create an new instance layer for each drawing?



If the issue still persists, Would you mind send us your custom TileCacheGenerator file and how to use this class?



Thanks,



Troy