Search code examples
c#gissharpmapogcgeoapi

SharpMap WMTS / TMS Server implementation


Can anyone help me with the implementation of WMTS / TMS Server in SharpMap?

I've been trying out a lot from diff sources but I can't seem to come up with a working solution. Below is a handler I am using, it's just drawing the boxes instead of data from the database. I am using the same method I used for a WMS Server:

DatabaseUtil.SqlServer(ConnectionString(), Layers(), new Size(1, 1), bbox, "id");

to get data from SQL Server and it's working just fine for the WMS. The only difference is that the one below returns a specific layer instead of a list by using .FindAll(lyr => lyr.Table.Equals(layer)) query.

/// <summary>
/// Summary description for WMTS
/// </summary>
public class WMTS : IHttpHandler
{
    /// <summary>
    /// Defines the projection
    /// </summary>
    private readonly ICoordinateTransformation projection = ProjUtil.ToPseudoMercator();

    /// <summary>
    /// The ProcessRequest
    /// wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=layer_id&STYLE=default&TILEMATRIXSET=matrix_id&TILEMATRIX=3&TILEROW=2&TILECOL=0&FORMAT=image%2Fjpeg
    /// </summary>
    /// <param name="context">The <see cref="HttpContext"/></param>
    public void ProcessRequest(HttpContext context)
    {
        string layer = context.Request.Params["LAYER"];
        int tilematrix = int.Parse(context.Request.Params["TILEMATRIX"]);
        int tilerow = int.Parse(context.Request.Params["TILEROW"]);
        int tilecol = int.Parse(context.Request.Params["TILECOL"]);
        string service = context.Request.Params["SERVICE"];
        string request = context.Request.Params["REQUEST"];
        string version = context.Request.Params["VERSION"];
        string style = context.Request.Params["STYLE"];
        string tilematrixset = context.Request.Params["TILEMATRIXSET"];
        string format = context.Request.Params["FORMAT"];

        if (String.IsNullOrEmpty(layer))
            throw new ArgumentNullException("layer");

        Map map = Map(layer, tilecol, tilerow, tilematrix);

        var map_image = map.GetMap();

        //using (var memory_stream = new MemoryStream())
        //{
        //    map_image.Save(memory_stream, ImageFormat.Png);

        //    var wms = memory_stream.ToArray();

        //    WriteResponseInChunks(wms, context);
        //}
        byte[] buffer;
        using (var ms = new MemoryStream())
        {
            map_image.Save(ms, ImageFormat.Png);
            map_image.Dispose();
            buffer = ms.ToArray();
        }

        WriteResponseInChunks(buffer, context);
    }

    public static ImageCodecInfo GetEncoderInfo(String mimeType)
    {
        foreach (var encoder in ImageCodecInfo.GetImageEncoders())
            if (encoder.MimeType == mimeType)
                return encoder;

        return null;
    }

    /// <summary>
    /// The GetMap
    /// </summary>
    /// <returns>The <see cref="SharpMap.Map"/></returns>
    protected Map Map(string layer, int x, int y, int z)
    {
        Envelope bbox = GetBoundingBoxInLatLngWithMargin(x, y, z);

        return DatabaseUtil.SqlServer(ConnectionString(), Layers().FindAll(lyr => lyr.Table.Equals(layer)), new Size(1, 1), bbox, "id");
    }

    /// <summary>
    /// The Layers
    /// </summary>
    /// <returns>The <see cref="List{VectorLayerModel}"/></returns>
    private List<VectorLayerModel> Layers()
    {
        VectorLayerModel standsLayerModel = new VectorLayerModel()
        {
            Table = "cadastre",
            Style = new VectorStyle { Line = new Pen(Color.DarkGray, 2) }
        };

        VectorLayerModel roadsLayerModel = new VectorLayerModel()
        {
            Table = "townships",
            Style = new VectorStyle { Line = new Pen(Color.DarkRed, 2.5f) }
        };

        VectorLayerModel pipeLayerModel = new VectorLayerModel()
        {
            Table = "provinces",
            Style = new VectorStyle { Line = new Pen(Color.DarkBlue, 1.5f) }
        };

        return new List<VectorLayerModel>() { standsLayerModel, roadsLayerModel, pipeLayerModel };
    }

    /// <summary>
    /// The ConnectionString
    /// </summary>
    /// <returns>The <see cref="string"/></returns>
    private string ConnectionString()
    {
        return "Data Source=******;Initial Catalog=GCCIGO_V2;Integrated Security=SSPI;";
    }

    /// <summary>
    /// The GetBoundingBoxInLatLngWithMargin
    /// </summary>
    /// <param name="tileX">The <see cref="int"/></param>
    /// <param name="tileY">The <see cref="int"/></param>
    /// <param name="zoom">The <see cref="int"/></param>
    /// <returns>The <see cref="Envelope"/></returns>
    private Envelope GetBoundingBoxInLatLngWithMargin(int tileX, int tileY, int zoom)
    {
        Point px1 = new Point((tileX * 256), (tileY * 256));
        Point px2 = new Point(((tileX + 1) * 256), ((tileY + 1) * 256));

        PointF ll1 = TileSystemHelper.PixelXYToLatLong(px1, zoom);
        PointF ll2 = TileSystemHelper.PixelXYToLatLong(px2, zoom);

        double[] prj1 = projection.MathTransform.Transform(new double[] { ll1.X, ll1.Y });
        double[] prj2 = projection.MathTransform.Transform(new double[] { ll2.X, ll2.Y });

        Envelope bbox = new Envelope();
        bbox.ExpandToInclude(prj1[0], prj1[1]);
        bbox.ExpandToInclude(prj2[0], prj2[1]);

        return bbox;
    }

    /// <summary>
    /// The size of the chunks written to response.
    /// </summary>
    private const int ChunkSize = 2 * 8192;

    /// <summary>
    /// Method to write an array of bytes in chunks to a http response
    /// </summary>
    /// <remarks>
    /// The code was adopted from http://support.microsoft.com/kb/812406/en-us
    /// </remarks>
    /// <param name="buffer">The array of bytes</param>
    /// <param name="context">The response</param>
    private static void WriteResponseInChunks(byte[] buffer, HttpContext context)
    {
        try
        {
            bool _continue;

            context.Response.ClearContent();

            context.Response.ContentType = "image/png";

            using (var ms = new MemoryStream(buffer))
            {
                var dataToRead = buffer.Length;

                while (dataToRead > 0)
                {
                    if (context.Response.IsClientConnected)
                    {
                        {
                            var tmpBuffer = new byte[ChunkSize];
                            var length = ms.Read(tmpBuffer, 0, tmpBuffer.Length);
                            context.Response.OutputStream.Write(tmpBuffer, 0, length);
                            context.Response.Flush();
                            dataToRead -= length;
                        }
                    }
                    else
                    {
                        dataToRead = -1;
                    }
                }
                _continue = dataToRead > 0;

            }
        }
        catch (Exception ex)
        {
            context.Response.ClearContent();
            context.Response.ContentType = "text/plain";
            context.Response.Write(string.Format("Error     : {0}", ex.Message));
            context.Response.Write(string.Format("Source    : {0}", ex.Message));
            context.Response.Write(string.Format("StackTrace: {0}", ex.StackTrace));
        }
        finally
        {
            context.Response.End();
        }
    }

    /// <summary>
    /// Gets a value indicating whether IsReusable
    /// </summary>
    public bool IsReusable
    {
        get
        {
            return true;
        }
    }
}

Maybe it will help if i just put up the code for creating the SharpMap.Map:

public static Map SqlServer(string conn, List<VectorLayerModel> layers, Size map_size, Envelope bbox, string id_column = "ID")
{
    Map map = new Map(map_size);

    foreach (var layer in layers)
    {
        VectorLayer lyr = CreateSqlServerLayer(conn, layer.Table, layer.Style, id_column);
        lyr.IsQueryEnabled = true;
        lyr.Enabled = true;

        if (bbox != null)
        {
            var geometries = lyr.DataSource.GetGeometriesInView(bbox);
            lyr.DataSource = new GeometryFeatureProvider(geometries);
        }

        map.Layers.Add(lyr);
    }

    return map;
}

Solution

  • After some debugging i found out that my GetBoundingBoxInLatLngWithMargin(int tileX, int tileY, int zoom) was returning bounds that was way out of my data bounds. I realized i was applying a coordinate transformation to my bbox giving me a bbox in pseudo mercator yet my layer is in wgs84. I changed the GetBoundingBoxInLatLngWithMargin to:

    //code omited
    Envelope bbox = new Envelope();
    bbox.ExpandToInclude(ll1.X, ll1.Y );
    bbox.ExpandToInclude(ll2.X, ll2.Y);
    return bbox;
    

    Please find the full conversation on GitHub