ThinkGeo.com    |     Documentation    |     Premium Support

How to zoom past level 20 getting XYZ tiles?

Can anyone help me figure out how to generate XYZ tiles with MapSuite beyond zoom level 20.



I’m overlaying a vector layer in an OpenLayers 3 client that allows zooming in very close - level 25 to 30. The vectors are being overlaid on hi-resolution geotagged photographs of plants, hence the need to zoom so far in.



Tiles are generated fine between zoom levels 0 - 19, but breaks whenever it hits zoom level 20 or above. The following webAPI method throws an index out of bounds exception trying to get the boundingBox on the following line (when z >= 20):


RectangleShape boundingBox = WebApiExtentHelper.GetBoundingBoxForXyz(x, y, z, GeographyUnit.Meter);

The code is pretty much taken directly from the ‘Draw and Edit Features’ webAPI demo:

[Route("{z}/{x}/{y}/{accessId}")]
        public HttpResponseMessage GetTile(int z, int x, int y, string accessId)
        {
            // Create the layerOverlay for displaying the map.
            LayerOverlay layerOverlay = new LayerOverlay();
 
            // Get the featureLayer including all drawn shapes by the access id for display.
            InMemoryFeatureLayer drawnShapesFeatureLayer = GetDrawnShapesFeatureLayer(accessId);
 
            if (drawnShapesFeatureLayer != null && drawnShapesFeatureLayer.InternalFeatures.Count > 0)
            {
                layerOverlay.Layers.Add(drawnShapesFeatureLayer);
            }
 
            // Draw the map and return the image back to client in an HttpResponseMessage. 
            using (Bitmap bitmap = new Bitmap(256, 256))
            {
                GdiPlusGeoCanvas geoCanvas = new GdiPlusGeoCanvas();
                RectangleShape boundingBox = WebApiExtentHelper.GetBoundingBoxForXyz(x, y, z, GeographyUnit.Meter);
 
                geoCanvas.BeginDrawing(bitmap, boundingBox, GeographyUnit.Meter);
                layerOverlay.Draw(geoCanvas);
                geoCanvas.EndDrawing();
 
                MemoryStream memoryStream = new MemoryStream();
                bitmap.Save(memoryStream, ImageFormat.Png);
 
                HttpResponseMessage responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
                responseMessage.Content = new ByteArrayContent(memoryStream.ToArray());
                responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(“image/png”);
 
                return responseMessage;
            }
        }

The doc’s for WebApiExtentHelper.GetBoundingBoxForXyz don’t offer much insight. There are a few overloads, but it’s not clear how to use them. 



Does anyone know how to solve or get the boundingBox some other way?



Thanks in advance.

Hi Scott,



Like you noticed, the exception is thrown is because this overload method is using default zoomlevelset which only supports 20 zoom. So, you may need to define a custom zoomlevelset to fit for your case, the WebApiExtentHelper supports the customer zoomlevelset, please following the below codes, the customZoomlevelset includes 25 zooms:




ZoomLevelSet customZoomLevelSet = new ZoomLevelSet();
            foreach (var item in new ZoomLevelSet().GetZoomLevels())
            {
                customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(item.Scale));
            }
            customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
            customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
            customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
            customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
            customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));

RectangleShape boundingBox = WebApiExtentHelper.GetBoundingBoxForXyz(x, y, z, GeographyUnit.Meter, customZoomLevelSet);

For the other overload methods with TileMatrix parameter, actually, you don’t have to specify a matrix if you give a zoomlevelset, the tilematrix can be calculated by the z value from Zoomlevel:



double zoomLevelScale = zoomLevelSet.GetZoomLevels()[z].Scale;
                tileMatrix = new RestServiceTileMatrix(“RestServiceTileMatrix”, zoomLevelScale, tileWidth, tileHeight, mapUnit);



Please let us know if you still have any questions.



Thanks,



Troy

Many thanks Troy, this allows me to zoom past level 20.



For anything to appear, I had to use SphericalMercatorZoomLevelSet rather than ZoomLevelSet and also assign the customZoomLevelSet to the FeatureLayer.



However when using CustomZoomLevels the features on the vector layer are drawn slightly offset (approx 100 feet South East of where they should be). Please see attached screenshot. When using the default 20 zoom levels everything is drawn in the correct place!?



The updated code is as follows:


// Create the layerOverlay for displaying the map.
LayerOverlay layerOverlay = new LayerOverlay();
 
// Get the featureLayer including all drawn shapes by the access id for display.
InMemoryFeatureLayer drawnShapesFeatureLayer = GetDrawnShapesFeatureLayer(accessId, z);
 
// ------------------------------------------------------------------------------------------------------------------------
// SphericalMercatorZoomLevelSet  Test
// ------------------------------------------------------------------------------------------------------------------------
SphericalMercatorZoomLevelSet customZoomLevelSet = new SphericalMercatorZoomLevelSet();
 
foreach (var item in new SphericalMercatorZoomLevelSet().GetZoomLevels())
{
    customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(item.Scale));
}
 
customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
customZoomLevelSet.CustomZoomLevels.Add(new ZoomLevel(customZoomLevelSet.CustomZoomLevels.Last().Scale / 2));
 
// ------------------------------------------------------------------------------------------------------------------------
// STYLING FOR CUSTOM SETS
// ------------------------------------------------------------------------------------------------------------------------
for (int tt = 0; tt <= customZoomLevelSet.CustomZoomLevels.Count() - 1; tt++)
{
    customZoomLevelSet.CustomZoomLevels[tt].DefaultAreaStyle = AreaStyles.CreateSimpleAreaStyle(GeoColor.StandardColors.Red, GeoColor.SimpleColors.Black);
    customZoomLevelSet.CustomZoomLevels[tt].DefaultTextStyle = TextStyles.CreateSimpleTextStyle("Name""Arial", 12, DrawingFontStyles.Bold, GeoColor.StandardColors.Black, GeoColor.StandardColors.White, 2);
}
 
// Had to add this line for vector to display
drawnShapesFeatureLayer.ZoomLevelSet = customZoomLevelSet;
 
if (drawnShapesFeatureLayer != null && drawnShapesFeatureLayer.InternalFeatures.Count > 0)
{
    layerOverlay.Layers.Add(drawnShapesFeatureLayer);
}
 
// Draw the map and return the image back to client in an HttpResponseMessage. 
using (Bitmap bitmap = new Bitmap(256, 256))
{
    GdiPlusGeoCanvas geoCanvas = new GdiPlusGeoCanvas();
 
    // For Custom ZoomSets
    RectangleShape boundingBox = WebApiExtentHelper.GetBoundingBoxForXyz(x, y, z, GeographyUnit.Meter, customZoomLevelSet);
 
    geoCanvas.BeginDrawing(bitmap, boundingBox, GeographyUnit.Meter);
    layerOverlay.Draw(geoCanvas);
    geoCanvas.EndDrawing();
 
    MemoryStream memoryStream = new MemoryStream();
    bitmap.Save(memoryStream, ImageFormat.Png);
 
    responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
    responseMessage.Content = new ByteArrayContent(memoryStream.ToArray());
    responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
 
}

Projection code being used looks like this:



// Original
Proj4Projection proj4 = new Proj4Projection();
proj4.InternalProjectionParametersString = Proj4Projection.GetWgs84ParametersString();
proj4.ExternalProjectionParametersString = Proj4Projection.GetSphericalMercatorParametersString();
         
shapesFeatureLayer.FeatureSource.Projection = proj4;

For info: Tiles are being drawn over a Bing basemap via OpenLayers 3.



Any help greatly appreciated. Thanks.





Hi Scott,



The codes looks good. For the offset issue, I think we may need to check your client side codes to make sure how the x,y,z is generated. The key is make sure the client side resolutions and the server side resolutions are the same. Would you mind to attach more codes here or sent it via email (mailto: forumsupport@thinkgeo.com)?



Thanks,

Troy

Thanks Troy,



We have a solution, kind of…



The problem was, as you thought, related to scale. However setting the scales on the XYZ layer in OpenLayers to match those being created by the SphericalMercatorZoomLevelSet did not completely fix the offset. In the end we used the standard ZoomLevelSet with an initial scale value set to 591659030 (obtained largely through trial and error).



Anyway, the working custom zoomset code for a Bing basemap in OpenLayers with an XYZ layer drawn via MapSuite with 25 zoom levels looks like this:



 
int intMaxZoomLevels = 25;
double dblInitialScale = 591659030.0;   // Scale required for correct position of drawn layer in client
 
for (int intZoomLevel = 0; intZoomLevel < intMaxZoomLevels; intZoomLevel++)
{
    // Define the next zoom level
    ZoomLevel objZoomLevel = new ZoomLevel(dblInitialScale);
 
    // Assign to ZoomLevelSet
    customZoomLevelSet.CustomZoomLevels.Add(objZoomLevel);
 
    // Reduce scale by half for next zoom step
    dblInitialScale = dblInitialScale / 2;
}
 
// Assign the ZoomLevelSet to the layer
objFeatureLayer.ZoomLevelSet = customZoomLevelSet;
 


And for drawing the tiles with the custom zoomset:




// Draw the tile and return image back to client. 
using (Bitmap objBitmap = new Bitmap(256, 256))
{
    GdiPlusGeoCanvas objGeoCanvas = new GdiPlusGeoCanvas();
 
    // For Custom ZoomSets
    RectangleShape boundingBox = WebApiExtentHelper.GetBoundingBoxForXyz(x, y, z, GeographyUnit.Meter, objFeatureLayer.ZoomLevelSet);
 
    objGeoCanvas.BeginDrawing(objBitmap, boundingBox, GeographyUnit.Meter);
    objLayerOverlay.Draw(objGeoCanvas);
    objGeoCanvas.EndDrawing();
 
    MemoryStream memoryStream = new MemoryStream();
    objBitmap.Save(memoryStream, ImageFormat.Png);
 
    objResponseMessage = new HttpResponseMessage(HttpStatusCode.OK);
    objResponseMessage.Content = new ByteArrayContent(memoryStream.ToArray());
    objResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");


}


Thanks for your help. 

Scott


Hi Scott, 
  
 I’m glad to hear that, the server side scales are bring into correspondence with “OpenLayers”. 
  
 Thanks,