Wednesday, February 18, 2009

BruTile tiling library

Tiling is a way to store raster data as image tiles on a server so that they can be quickly retrieved by a client application. Examples of GIS clients that use tiling are Google Maps, Virtual Earth and OpenLayers. The tiles themselves are really just images sitting on a server, like this one.

A while back I added tiling functionality to SharpMapV1. Last month I extracted the tiling code to put it into a separate library, BruTile (LGPL), so it can be reused in other projects, like SharpMapV2.

With the BruTile library you can define the way the tiles are stored on the server (the TileSchema), request which tiles you need to fill a particular boundingbox up with tiles, and then retrieve those tiles from the server with a properly formatted request string. The rendering of the tiles is up to the client application. There are various protocols to store and retrieve tiles. The current implementation of BruTile only supports the Tms and WmsC protocol. I hope to support more protocols in the future like the OGC's Wmts and Microsoft's Virtual Earth protocol.

In the following example I describe how to draw a map onto a WinForms form using tiles.The sample uses the OpenStreetMap tile server which uses the Tms protocol. The result is the form below, no further interaction with the map is possible.

First some prerequisites. To do rendering in WinForms you need to override the OnPaint

protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawImage(buffer, 0, 0);
}

In it we draw a bitmap buffer onto the window. This bitmap is initialized to the window's size in the constructor.

buffer = new Bitmap(this.Width, this.Height);

Everything else is done in the Load method. First a Transform class is created. This is used to calculate from screen positions to world coordinates and back. It is initialized to a center point somewhere in the Netherlands, a resolution wide enough to show the whole of the netherlands, and the width and height of the window. The transform class is not part of BruTile itself because something similar is already part of most GIS libraries.

Transform transform = new Transform(new PointF(629816f, 6805085f), 1222.992452344f, this.Width, this.Height);

Next you need to define how the tiles on the server are stored. The tile scheme needs a lot of information. Luckily the Tms service publishes this information. In future versions of BruTile I would like to automatically parse this info so it need not be specified on the client. So here I wont go into detail about what it all means.

private ITileSchema CreateTileSchema()
{
  double[] resolutions = new double[] {     
156543.033900000, 78271.516950000, 39135.758475000, 19567.879237500, 9783.939618750,
4891.969809375, 2445.984904688, 1222.992452344, 611.496226172, 305.748113086,
152.874056543, 76.437028271, 38.218514136, 19.109257068, 9.554628534, 4.777314267,
2.388657133, 1.194328567, 0.597164283};
  TileSchema schema = new TileSchema();
  schema.Name = "OpenStreetMap";
foreach (float resolution in resolutions) schema.Resolutions.Add(resolution);
schema.OriginX = -20037508.342789;
schema.OriginY = 20037508.342789;
schema.Axis = AxisDirection.InvertedY;
schema.Extent = new Extent(-20037508.342789, -20037508.342789, 20037508.342789, 20037508.342789);
schema.Height = 256;
schema.Width = 256;
schema.Format = "png";
schema.Srs = "EPSG:900913";
return schema;
}

Now you can request which tiles you need to fill up the entire view

ITileSchema schema = CreateTileSchema();
IList<TileInfo> tiles = Tile.GetTiles(schema, transform.Extent, transform.Resolution);

Next you need to specify the RequestBuilder to use. In this case you need the RequestTms class because of the Tms protocol. As arguments you need to pass the url of the OpenStreetMap tile service and the format in which the tiles are stored.

IRequestBuilder requestBuilder = new RequestTms(new Uri("http://a.tile.openstreetmap.org"), "png");

Finally we can request all tiles from the server and render them to our buffer Bitmap.

Graphics graphics = Graphics.FromImage(buffer);
foreach (TileInfo tile in tiles)
{
Uri url = requestBuilder.GetUrl(tile);
byte[] bytes = ImageRequest.GetImageFromServer(url);
Bitmap bitmap = new Bitmap(new MemoryStream(bytes));
graphics.DrawImage(bitmap, transform.WorldToMap(tile.Extent.MinX, tile.Extent.MinY, tile.Extent.MaxX, tile.Extent.MaxY));
}

The sample code above is part of the BruTile project on codeplex. The solution also includes a WPF project which demonstrates how to get a nice user experience with tile requests on a background thread and rendering tiles at the moment they arrive.


4 comments:

yogesh said...

hi,
I am yogesh. I tried to built a basic brutile desktop application using the sample you have mentioned.. i got following errors. can you help me to fix those bugs.The errors are

1)Error 1 The name 'resolutions' does not exist in the current context C:\temp\Projects\BruTile Sample1\BruTile Sample1\Form1.cs 54 42 BruTile Sample1

2)Error 2 The type 'System.Uri' is defined in an assembly that is not referenced. You must add a reference to assembly 'System, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'. C:\temp\Projects\BruTile Sample1\BruTile Sample1\Form1.cs 78 13 BruTile Sample1

Paul den Dulk said...

hi Yogesh,

The sample is also part of the repository on codeplex as mentioned in the post. So you can just build that one.

The line with resolutions was missing. Thanks for mentioning. I added it in the post.

Paul

Vivek said...

Hi,

Just want to know how I can put markers on map. I mean one Image based upon latitude and longitude.

Please let me know any code sample. I want in windows forms.

Thanks for your time.

Vivek Kumar

Vivek said...
This comment has been removed by the author.