Overlaying shapefiles on OSM tiles

Topics: WinForms Controls
Apr 28, 2014 at 6:49 AM

My application works with OSM tiles, and I want to overlay shapefiles using SharpMap. I stitch together OSM tiles to create my map window in WinForms. For various reasons, my design is to generate overlay "tiles" corresponding to standard OSM tiles containing the relevant portion of the Shapefile image, and cache them to avoid redrawing, and then overlay as desired.

I would have assumed the following code would work, but it doesn't :(

Bitmap GetShapeImage(int tileX, int tileY, int zoom, string shapefile)
// Calculate bounds of my tile in lat/long - I am confident this bit is right
int n = (int)Math.Pow(2, zoom);
double nwLong = (double)(tileX) / n * 360 - 180;
double nwLat = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * (double)(tileY) / n))) * 180 / Math.PI;
double seLong = (tileX + 1.0) / n * 360 - 180;
double seLat = Math.Atan(Math.Sinh(Math.PI * (1 - 2 * (tileY + 1.0) / n))) * 180 / Math.PI;

NetTopologySuite.Geometries.Point nw = new NetTopologySuite.Geometries.Point(nwLong, nwLat);
NetTopologySuite.Geometries.Point se = new NetTopologySuite.Geometries.Point(seLong, seLat);

var mapBox = new Map();
GeoAPI.Geometries.Envelope bbox = new GeoAPI.Geometries.Envelope(nw.X, se.X, nw.Y, se.Y);

using (Graphics g = Graphics.FromImage(bmp))
var layer = new SharpMap.Layers.VectorLayer(file);
layer.DataSource = new SharpMap.Data.Providers.ShapeFile(file, true);
return bmp;

What I find is that the data drawn into the bitmap does not correctly match the correct part of the shapefile and is at the wrong zoom level.

Is there anything obvious I am doing wrong in my code?
Thanks in advance for any advice...!

Apr 28, 2014 at 9:40 AM
OSM tiles use the WebMercator projection (EPSG: 3857), and it looks like you are using lat long values (EPSG:4326).

you'll need to convert your values to 3857 before rendering.

This is quite easy with sharpmap. The way I do it is to set the coordinate transformation property on the layer to be 4326 -> 3857.

You can create the tranformation like this:

private static readonly CoordinateTransformationFactory CoordinateTransformationFactory = new CoordinateTransformationFactory();

public static ICoordinateTransformation Wgs84ToGoogle()
        return CoordinateTransformationFactory.CreateFromCoordinateSystems(GeographicCoordinateSystem.WGS84, ProjectedCoordinateSystem.WebMercator);
Apr 28, 2014 at 10:06 AM
Thanks Robert - that made sense so I added a line along with your code snippet, thus:
var layer = new SharpMap.Layers.VectorLayer(file);
layer.CoordinateTransformation = Wgs84ToGoogle();
layer.DataSource = new SharpMap.Data.Providers.ShapeFile(file, true);
However sadly I now get no shapefile imagery at all.... :(
Swapping the order of the last two lines makes no difference.
So I wonder if I misunderstood you?

Apr 28, 2014 at 11:11 AM
You need to transform the coordinates of your viewport, too.
Apr 28, 2014 at 12:12 PM
Ok I guess I'm out of my depth here! I'm an experienced C# programmer but this mapping stuff is not my specialty :)

I thought this would do the transform in the viewport:
NetTopologySuite.Geometries.Point nw = new NetTopologySuite.Geometries.Point(nwLong, nwLat);
NetTopologySuite.Geometries.Point se = new NetTopologySuite.Geometries.Point(seLong, seLat);
But I'm obviously missing the point - can you help me out please? :)
Apr 28, 2014 at 1:09 PM
From the code you posted first I assume that xLong and xLat are WGS84 ordinates, while you transformed your ShapeFile into WebMercator.
You need to transform your xLong and xLat to WebMercator, too.
var box = GeoAPI.CoordinateSystems.Transformations.GeometryTransform.TransformBox(
    new GeoAPI.Geometries.Envelope(nw.X, se.X, nw.Y, se.Y), 
Apr 28, 2014 at 1:36 PM
Well that brings me back to the same image artifacts I originally posted about!
I guess I didn't need to do either projection then....? They cancel out?
I wish there was a way to post an image easily, I could make it so much clearer...

Apr 28, 2014 at 1:55 PM
The tiles you want to get are probably not 256x256 pixels large. Create your Map instance by providing the size
var map = new SharpMap.Map(new System.Drawing.Size(256, 256));
Marked as answer by timdavey on 4/28/2014 at 7:55 AM
Apr 28, 2014 at 2:54 PM
Oh of course! That did it :)
Many thanks for your patience and help :)