ThinkGeo.com    |     Documentation    |     Premium Support

Tile not correct

Hello,


I'm encoutering a little problem, and I cannot figure out where it comes from.


Actually, it seems that some times, the tile is not generated correctly, and either it is empty (white tile, while my backgroundcolor is white) or it contains another part of the part (tile with false coordinates). That's a little hard to explain by writting, so I joined screenshots.




I'm looking to this problem since a little time now, but because this appens only some times, and not always at the same coordinates, It's a bit difficult to reproduce and to understand why I get this error.


I use a server BitmapTileCache and a client BitmapTileCache. PreviewTileCache and Multithreading are activated too. The server use big GeoTiff files to generate 128x128 tiles and send a background picture to the client.


When I empty the ClientCache and restart my application, the picture is correct. But then I have some incorrect data in others places (where it was correct before deleting client caches).


When I deactivate the client cache and the preview cache, but not the server cache, I stilt have those problems.


When I deactivate server cache and use Client cache, I don't have any problem


When I deactivate both client and server cache, I don't have any problem.


 


I figures out that the problem probably comes either from my own algorithm, server side, that read each tile for an area, and paste them together to send the picture size asked by client, other from the way cached tiles are saved server side.


Here is the code I used to paste the tiles together server side :


 



 


            var data = bitmapCache.GetTiles(parameter.BoundingBox);



            var range = matrix.GetIntersectingRowColumnRange(parameter.BoundingBox);

            // parameter contains data requested within the URL

            var bmp = new Bitmap(parameter.Width, parameter.Height);

            

                var g = Graphics.FromImage(bmp);



                foreach (BitmapTile tile in data)

                {

                    long row = matrix.GetRowIndex(tile.BoundingBox.UpperLeftPoint); // Cached foldername

                    if (row == range.MinRowIndex)

                    {

                        // I don't know why, but the range contains too much tiles, and this is a problem for generating bitmap.

                        // We don't need the first row and the last column in cache.

                        continue;

                    }

                    long col = matrix.GetColumnIndex(tile.BoundingBox.UpperLeftPoint); // Cached filename

                    if (col == range.MaxColumnIndex)

                    {

                        // Same problem here.

                        // we don't need the last column index

                        continue;

                    }

                    int x = (int)((col - range.MinColumnIndex) * TileWidth);

                    int y = (int)((row - range.MinRowIndex - 1) * TileHeight);

                    g.DrawImage(tile.Bitmap, x, y);

                }



 


 I figured as well that server and client cache save not the picture the same way, although I used  the same FileBitmapTileCache. The server saves always ont row and one column more than client side (see my comments in the code above), so when I read the tiles, I must not read the first column and row for pictures.


 Ok, that's all for the moment, I hope that my explanations where fait enough, and my english not too bad :-)


Do you have some advices to help me solving this issue ?


Thanks,

Guillaume.


 



 


Hi Guillaume,
According to your description, it seems like the logic in your server side has some problem. To make sure the server works right at first, we have written a block of code to merge tiles to a big bitmap just based on your server side code which has been pasted on this post. You can try to use these codes to override your original code, and then take a look at if the result is better. 


// These parameters are contained in the URL.
            RectangleShape boundingBox = new RectangleShape(-139.228125, 92.4125, 120.928125, -93.2125);
            int width = 740;
            int height = 528;

            // You should make sure the tileCache.TileMatrix.Scale is snapped to a scale in some zoomlevel.
            boundingBox = ExtentHelper.SnapToZoomLevel(boundingBox, GeographyUnit.DecimalDegree, width, height, new ZoomLevelSet());
            double resultScale = ExtentHelper.GetScale(boundingBox, width, GeographyUnit.DecimalDegree);
            tileCache.TileMatrix.Scale = resultScale;

            Collection<BitmapTile> tiles = tileCache.GetTiles(boundingBox);

            Bitmap bitmap = null;
            Graphics graphics = null;
            try
            {
                bitmap = new Bitmap(width, height);
                graphics = Graphics.FromImage(bitmap);

                foreach (BitmapTile tile in tiles)
                {
                    if (tile.Bitmap != null)
                    {
                        ScreenPointF upperLeftPoint = ExtentHelper.ToScreenCoordinate(boundingBox, tile.BoundingBox.UpperLeftPoint, width, height);
                        int screenUpperLeftPointX = (int)Math.Round(upperLeftPoint.X);
                        int screenUpperLeftPointY = (int)Math.Round(upperLeftPoint.Y);

                        graphics.DrawImageUnscaled(tile.Bitmap, screenUpperLeftPointX, screenUpperLeftPointY);
                    }
                    else
                    {
                        // haven't gotten the bitmap, we should find out why this happens.
                    }
                }
            }
            finally
            {
                if (graphics != null)
                {
                    graphics.Dispose();
                    graphics = null;
                }
                if (bitmap != null)
                {
                    bitmap.Dispose();
                    bitmap = null;
                }
            }


Any more questions please let me know.
Thanks,
sun

Hello, 



Thank to your code I managed to solve a first problem : The scale was not correct (I was using a non snapped boudingbox to calculate the Scale). 



But I still have some problems with empty areas, and I noticed the following things : 



- When I create a new pictire from server with map.DrawStaticLayers(), then the picture send to client is correct. The tiles are then saved server side with a bitmapCache.SaveTiles(). Then, when I call a map.Refresh(overlay), the server use his cache to load the differents tiles. every tile exist and has a Bitmap object associated (tile.Bitmap != null), but some of them are totaly black. 



- In the directories for server caching, it append that complete black pictures are saved. For example, in the folder named "288374.897460938", in the sub-folder "102366", I have a picture "102457.png" that is correct, then a picture "102458.png" that is black, and then a picture "102459.png" that is correct. 



- For a same scale, it appends that the scale folder name in the cache differs from 0.000000001. For exemple, I have a folder named "288374.897460937", another named "288374.897460938", and a third named "288374.897460939".  However the scale is always calculated from a boudingbox that was snaped to zoom level. Is that normal ? 



Here is the complete code I use :


 
parameter.BoundingBox =ExtentHelper.SnapToZoomLevel(new RectangleShape(minX, maxY, maxX, minY), GeographyUnit.Meter, parameter.Width, parameter.Height, new ZoomLevelSet());
parameter.Scale = ExtentHelper.GetScale(parameter.BoundingBox, parameter.Width, GeographyUnit.Meter); 

var ms = new MemoryStream(); 
var matrix = new MapSuiteTileMatrix(parameter.Scale, TileWidth, TileHeight, GeographyUnit.Meter); 
var cacheId = GetCacheId(parameter.Layers); 
var bitmapCache = new FileBitmapTileCache(@"C:\Temp\GIS\Cache", cacheId, TileImageFormat.Png, matrix);

var tiles = bitmapCache.GetTiles(parameter.BoundingBox);

bool alreadyLoaded = (tiles.Count <= 0) ? false : true;
foreach (BitmapTile tile in tiles)

if (tile.Bitmap == null) 

alreadyLoaded = false; 
break; 



var bmp = new Bitmap(parameter.Width, parameter.Height);

if (alreadyLoaded) 

// All tiles already exist in cache 
var graphics = Graphics.FromImage(bmp);

foreach (BitmapTile tile in tiles) 
{
if (tile.Bitmap != null) 

ScreenPointF upperLeftPoint = ExtentHelper.ToScreenCoordinate(parameter.BoundingBox, tile.BoundingBox.UpperLeftPoint, parameter.Width, parameter.Height); 
int screenUpperLeftPointX = (int)Math.Round(upperLeftPoint.X); 
int screenUpperLeftPointY = (int)Math.Round(upperLeftPoint.Y);

graphics.DrawImageUnscaled(tile.Bitmap, screenUpperLeftPointX, screenUpperLeftPointY); 

else 

// haven't gotten the bitmap, we should find out why this happens. 
throw new Exception("Problem while retriving Server Tile Cache !"); 



graphics.Dispose(); 
graphics = null; 

else

// New image to create 

map.CurrentExtent = parameter.BoundingBox; 
OpenLayers(parameter); // Opens the right layers depending on the parameters 
map.DrawStaticLayers(bmp, GeographyUnit.Meter); 
CloseLayers(); 
bitmapCache.SaveTiles((Bitmap)bmp.Clone(), map.CurrentExtent); 


// Write in the memorystream 
bmp.Save(ms, parameter.ImageFormat); 
bmp.Dispose(); 
bmp = null; 
return ms;






Do you have any suggestion ? 



Many thanks, 

Guillaume.



Guillaume,


For you first two questions, can you debug the code to see if the ‘bmp’ is an empty bitmap (not null, just a transparent or black bitmap) before calling the save cache function “bitmapCache.SaveTiles((Bitmap)bmp.Clone(), map.CurrentExtent);”.
Another possibility is that you didn’t set the drawing extent to a grid extent, you can make some change to the drawing logic like this:


Collection<TileMatrixCell> cells = tileCache.TileMatrix.GetIntersectingCells(parameter.BoundingBox);
RectangleShape drawingExtent = GetExpandToIncludeExtent(cells);

int drawingBitmapWidth = tileCache.TileMatrix.TileWidth * (int)Math.Round(drawingExtent.Width / cells[0].BoundingBox.Width);
int drawingBitmapHeight = tileCache.TileMatrix.TileHeight * (int)Math.Round(drawingExtent.Height / cells[0].BoundingBox.Height);
Bitmap bmp = new Bitmap(drawingBitmapWidth, drawingBitmapHeight);


Then try to save the grid bitmap to the tile cache.
For the last question, this is a known issue of the DesktopEdition and we are trying to fix it.
Any more questions please let me know.
Thanks,
sun

Hum... sorry, but what is this GetExpandToIncludeExtent() method ? 


Otherwise the bmp picture seems to be correct before calling SaveTiles() method.




Thanks, 

Guillaume.



Hi,



I found some further informations for my Tiles problem.



I noticed that in server cache, some tiles were saved with an empty area. That is probably because the tile matrix does not match exactly with the bitmap picture that was asked from client. 

I mean : I ask for a picture with boundingbox=(10, 20, 20, 10) for example. The layer is loaded, the bitmap is correctly calculated, but when saving the tiles, it seems that the tiles matrix begins at (8, 20) and ends at (28, 10) for exemple. This means that my first tile will have a empty aera at the begining (from 8 to 10), and my last tile will have a empty area at the end (from 20 to 28).


Here is a capture of the generated tiles :




 


The problem is that when I pan left then, the picture is loaded from cache with this empty area, and I become a big picture with an empty column.


 


To solve this, I tried to match exactly my FullMapBoundingBox with the tiles coordinates. I somehow managed to do something, but I still have a problem. I don't have any more half-empty cached picture, because my first tile coordinates match exactly the boudingbox I ask from client. However, I now because an total empty tile for the last column.


Here is a capture :



 


The problem is the same : When I pan left then, the empty picture is loaded from cache, and I become a big picture with an empty column.


Now I really don't understand why the tilematrix contains always one extra column. I would really appreciate some advices, because I think that I've done all what I though, and I'm almost stuck now.




I hope you understood my problem.

Many thanks !

Guillaume.



Guillaume,


Sorry that Sun is currently on a holiday. Following codes is the code for the GetExpandToIncludeExtent missing in his post, hope it helps.

        private static RectangleShape GetExpandToIncludeExtent(IEnumerable<TileMatrixCell> cells)
        {
            RectangleShape totalExtent = null;
            foreach (TileMatrixCell cell in cells)
            {
                if (totalExtent == null)
                {
                    totalExtent = (RectangleShape)cell.BoundingBox.CloneDeep();
                }
                else
                {
                    totalExtent.ExpandToInclude(cell.BoundingBox);
                }
            }
 
            return totalExtent;
        }


 
Thanks.
 
 Yale

Thank you Yale, 
 This solved my problem. 
 I hope that sun is having a nice holiday :-) 
  
 Guillaume.

Guillaume, 
  
 Sorry for the missing method and glad to hear that it can solve your problem. 
  
 Any more questions please let us know. 
  
 Thanks, 
  
 sun