Sunday, July 5, 2009

Geoserver WMS Tiles in Silverlight Map Control

While Randy (see previous blog) showed how to use GeoWebCache to display tiles in the Silverlight map control, we needed to display WMS tiles that are not cached. The reason was that GWC doesn't play nice with GeoServer password secured layers. This would also be useful for layers that change frequently and cannot be cached.

I put together this TileSource class in VB.NET to do this:

'Normal WMS (non GWC)
'Translated from http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/
'
Public Class NormalWMSTileSource
Inherits Microsoft.VirtualEarth.MapControl.TileSource

Sub New()
MyBase.UriFormat = "http://ogi.state.ok.us/geoserver/wms?LAYERS=ogi:okcounties&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A900913&BBOX={0},{1},{2},{3}&WIDTH=256&HEIGHT=256&TRANSPARENT=TRUE"
End Sub

Sub New(ByVal WMSLayerName As String)
MyBase.UriFormat = "http://ogi.state.ok.us/geoserver/wms?LAYERS=" & WMSLayerName & "&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A900913&BBOX={0},{1},{2},{3}&WIDTH=256&HEIGHT=256&TRANSPARENT=TRUE"
End Sub


Public Overrides Function GetUri(ByVal x As Integer, ByVal y As Integer, ByVal zoomLevel As Integer) As System.Uri
'Example:
'http://ogi.state.ok.us/geoserver/wms?LAYERS=ogi%3Aokcounties&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A900913&BBOX=-11271098.44125,4070118.8815625,-10958012.373437,4383204.949375&WIDTH=256&HEIGHT=256


'Convert x,y,zoom to 900913 coords and return a wms URI
Dim R As Rect = TileBounds(x, y, zoomLevel)

Return New Uri(String.Format(Me.UriFormat, R.Left, R.Top, R.Right, R.Bottom))
End Function


'def(__init__(self, tileSize = 256))
'"Initialize the TMS Global Mercator pyramid"
'self.tileSize = tileSize
'self.initialResolution = 2 * Math.PI * 6378137 / self.tileSize
'# 156543.03392804062 for tileSize 256 pixels
'self.originShift = 2 * Math.PI * 6378137 / 2.0
'# 20037508.342789244


Dim tileSize = 256
'"Initialize the TMS Global Mercator pyramid"
Dim initialResolution = 2 * Math.PI * 6378137 / tileSize
'# 156543.03392804062 for tileSize 256 pixels
Dim originShift = 2 * Math.PI * 6378137 / 2.0
'# 20037508.342789244


'def(TileBounds(self, tx, ty, zoom))
'"Returns bounds of the given tile in EPSG:900913 coordinates"

'minx, miny = self.PixelsToMeters(tx * self.tileSize, ty * self.tileSize, zoom)
'maxx, maxy = self.PixelsToMeters((tx + 1) * self.tileSize, (ty + 1) * self.tileSize, zoom)
'return ( minx, miny, maxx, maxy )

Private Function TileBounds(ByVal tx, ByVal ty, ByVal zoom) As Rect

'"Returns bounds of the given tile in EPSG:900913 coordinates"

Dim MinP As Point = PixelsToMeters(tx * tileSize, ty * tileSize, zoom)
Dim MaxP As Point = PixelsToMeters((tx + 1) * tileSize, (ty + 1) * tileSize, zoom)

Dim R As New Rect(MinP, MaxP)

Return R

End Function

'def(PixelsToMeters(self, px, py, zoom))
'"Converts pixel coordinates in given zoom level of pyramid to EPSG:900913"

'res = self.Resolution(zoom)
'mx = px * res - self.originShift
'my = py * res - self.originShift
'return mx, my

Private Function PixelsToMeters(ByVal px, ByVal py, ByVal zoom) As Point

Dim P As New Point

Dim res = Resolution(zoom)
P.X = px * res - originShift

'Strange, this come out negative? Should be positive.
P.Y = -(py * res - originShift)

Return P
End Function

'def(Resolution(self, zoom))
'"Resolution (meters/pixel) for given zoom level (measured at Equator)"

'# return (2 * math.pi * 6378137) / (self.tileSize * 2**zoom)
'return self.initialResolution / (2**zoom)

Private Function Resolution(ByVal zoom)
'"Resolution (meters/pixel) for given zoom level (measured at Equator)"

'# return (2 * math.pi * 6378137) / (self.tileSize * 2**zoom)
Return initialResolution / (Math.Pow(2, zoom))

End Function



End Class