The Web service requires two namespace references for image-processing, as follows.
using System.Drawing;
using System.IO;
The ProcessRequest method fetches the URL-parameters for the quad-key and the WMS-layer, as follows:
// Fetch URL-parameters
string tileIdParam = Context.Request.Params("ID");
string layerParam = Context.Request.Params("layer");
The zoom-level relates directly to the length of the quad-key. If the quad-key is 120202113, the length is 9 digits and thus the zoom-level for this tile is 9.
// Get zoom level
int zoomLevel = requestParam.Length;
The area covered by this tile can be determined by calculating the binary value of the quad-key. Given the quad-key 120202113, the binary representation is as follows:
1 2 0 2 0 2 1 1 3
01 10 00 10 00 10 01 01 11
The conversion code is very simple. It just takes each character in the quad-key and translates that into a two-character output code, as follows.
// Convert Quadkey to binary value
string myQuadKeyBin = "";
char [] myKeyCharArray = requestParam.ToCharArray();
for (int i = 0; i < zoomLevel; i++)
{
switch (myKeyCharArray[i])
{
case '0': myQuadKeyBin += "00";
break;
case '1': myQuadKeyBin += "01";
break;
case '2': myQuadKeyBin += "10";
break;
default: myQuadKeyBin += "11";
break;
}
}
Now that the binary quad-key value is converted to binary, the next step is to determine the binary X and Y numbers for the tile. Note that these values are not the pixel X and Y of the tile yet but the X and Y number of the whole tile itself in the coordinate system.
Figure 13. Virtual Earth coordinates at zoom level 3
Each tile is given an XY coordinates ranging from (0, 0) in the upper left to (2ZoomLevel - 1, 2ZoomLevel - 1) in the lower right. For example, at level 3 the tile coordinates range from (0, 0) to (7, 7) as shown in Figure 13. For further reference, see the article on the Virtual Earth Tile System, listed in Resources.
The first digit of the binary quad-key is the first digit of the binary TileY-coordinate, the second digit of the quad-key is the first digit of the binary TileX-coordinate and so on. Therefore, the example this would resolve as follows.
Quad-key: 1 2 0 2 0 2 1 1 3
Binary Quad-key: 01 10 00 10 00 10 01 01 11
Binary TileY: 0 1 0 1 0 1 0 0 1
Binary TileX: 1 0 0 0 0 0 1 1 1
The easiest way to accomplish this is to modify the previous for loop to create these values, as follows.
// Convert Quadkey to binary values
int zoomLevel = tileIdParam.Length;
string myQuadKeyBin = "";
string myXBinValues = "";
string myYBinValues = "";
char [] myKeyCharArray = tileIdParam.ToCharArray();
for (int i = 0; i < zoomLevel; i++)
{
switch (myKeyCharArray[i])
{
case '0': myQuadKeyBin += "00";
myXBinValues += "0";
myYBinValues += "0";
break;
case '1': myQuadKeyBin += "01";
myYBinValues += "0";
myXBinValues += "1";
break;
case '2': myQuadKeyBin += "10";
myYBinValues += "1";
myXBinValues += "0";
break;
default: myQuadKeyBin += "11";
myYBinValues += "1";
myXBinValues += "1";
break;
}
}
The next step is to convert the binary TileX and TileY to a decimal TileX and TileY coordinate using the following formula.
Binary TileY: 010101001
Decimal TileY: 0∙2
ZoomLevel−1
+ 1∙2
ZoomLevel−2
+ ⋯ + 1∙20 = 169
Binary TileX: 100000111
Decimal TileY: 1∙2
ZoomLevel−1
+ 0∙2
ZoomLevel−2
+ ⋯ + 1∙20 = 263
Once again, the easiest way to accomplish this is to modify the previous for loop to create these values, as follows.
// Convert Quadkey to binary values
int zoomLevel = tileIdParam.Length;
string myQuadKeyBin = "";
string myYBinValues = "";
string myXBinValues = "";
int myXDecValue = 0;
int myYDecValue = 0;
char [] myKeyCharArray = tileIdParam.ToCharArray();
for (int i = 0; i < zoomLevel; i++)
{
int tmpVal = (int)Math.Pow(2, zoomLevel - (i + 1));
switch (myKeyCharArray[i])
{
case '0': myQuadKeyBin += "00";
myYBinValues += "0";
myXBinValues += "0";
break;
case '1': myQuadKeyBin += "01";
myYBinValues += "0";
myXBinValues += "1";
myXDecValue += tmpVal;
break;
case '2': myQuadKeyBin += "10";
myYBinValues += "1";
myXBinValues += "0";
myYDecValue += tmpVal;
break;
default: myQuadKeyBin += "11";
myYBinValues += "1";
myXBinValues += "1";
myXDecValue += tmpVal;
myYDecValue += tmpVal;
break;
}
}
To determine the latitude and longitude of the upper left and lower right corner of this tile, PixelX and PixelY coordinates of the tile must first be calculated. Since each tile has a size of 256 x 256 pixels and we the decimal TileX and TileY coordinates are known, this is a simple calculation. For the upper left corner, the formula is as follows:
PixelYMin: TileY
dec
∙ 256 = 169 ∙ 256 = 43264
PixelXMin: TileX
dec
∙ 256 = 263 ∙ 256 = 67328
PixelYMax: (TileY
dec
+ 1) ∙ 256 − 1 = (170 ∙ 256) − 1 = 43519
PixelXMax: TileX
dec
+ 1 ∙ 256 − 1 = 264 ∙ 256 − 1 = 67583
Here is the code to make these calculations.
// Convert to pixels
int PixelXMin = myXDecValue * 256;
int PixelXMax = ((myXDecValue + 1) * 256) - 1;
int PixelYMin = myYDecValue * 256;
int PixelYMax = ((myYDecValue + 1) * 256) - 1;
The next step is to calculate the latitude and longitude of the upper left and the lower right corner. The mathematics is a bit complicated since the tile is a flat object but the latitude and longitude are in the WGS 84. The formula to calculate the longitude is as follows.
Longitude = ((PixelX * 360) / (256 * 2
ZoomLevel
)) - 180
In the example, these become:
LongMin = ((67328 * 360) / (256 * 2
9
)) - 180 ~= 4.921875
LongMax = ((67583 * 360) / (256 * 2
9
)) - 180 ~= 5.622253
Before the latitude is calculated, here some intermediate calculations to help refactor the code.
denom = 256 * 2
ZoomLevel
efactor = e
(0.5 - ((PixelY) / (denom) )* 4 * pi
The latitude is then calculated as the following.
Latitude = asin((efactor - 1) / (efactor + 1)) * (180 / pi)
The example becomes:
denom = 256 * 2
9
= 131072
efactor
min
= e
(0.5 - ((43264) / (denom) )* 4 * pi ~= 8.459595
efactor
max
= e
(0.5 - ((43519) / (denom) )* 4 * pi ~= 8.255283
Latitude
min
= asin((efactor - 1) / (efactor + 1)) * (180 / pi) ~= 52.052490
Latitude
max
= asin((efactor - 1) / (efactor + 1)) * (180 / pi) ~= 51.619721
The code to do all of this looks like this:
// Convert to latitude/longitude
// longitude
float LongMin = (float)(((PixelXMin * 360) / (256 * Math.Pow(2, zoomLevel))) - 180);
float LongMax = (float)(((PixelXMax * 360) / (256 * Math.Pow(2, zoomLevel))) - 180);
// Latitude
float denom = (float)(256 * Math.Pow(2, zoomLevel));
float eMinNum = (float)(PixelYMin / denom);
float eMinFactor = (float)(Math.Pow(Math.E, (0.5 - eMinNum) * 4 * Math.PI));
float minLat = (float)(Math.Asin((eMinFactor - 1) / (eMinFactor + 1)) * (180 / Math.PI));
float eMaxNum = (float)(PixelYMax / denom);
float eMaxFactor = (float)(Math.Pow(Math.E, (0.5 - eMaxNum) * 4 * Math.PI));
float maxLat = (float)(Math.Asin((eMaxFactor - 1) / (eMaxFactor + 1)) * (180 / Math.PI));
All the information is now available to prepare and execute the request to the WMS-server. The following code builds the URL, executes the request, and returns the response to the Virtual Earth map control.
// URL to WMS
string myURL = "http://www.bis.bayern.de/wms/gla/gk500_wms?REQUEST=GetMap&SERVICE=WMS"
+ "&VERSION=1.1.1&SRS=EPSG:4326&FORMAT=image/png&LAYERS="
+ layerParam + "&WIDTH=256&HEIGHT=256&BBOX="
+ LongMin + ","
+ maxLat + ","
+ LongMax + ","
+ minLat;
System.Net.WebRequest myRequest = System.Net.WebRequest.Create(myURL);
System.Net.WebResponse myResponse = myRequest.GetResponse();
Bitmap myImage = new Bitmap(Image.FromStream(myResponse.GetResponseStream()));
WritePngToStream(myImage, Context.Response.OutputStream);
The WritePngToStream method looks like the following.
void WritePngToStream(Bitmap image, Stream outStream)
{
MemoryStream writeStream = new MemoryStream();
image.Save(writeStream, System.Drawing.Imaging.ImageFormat.Png);
writeStream.WriteTo(outStream);
image.Dispose();
}
If you have been paying attention, you probably have noticed that we do not use the variables myQuadKeyBin, myYBinValues, and myXBinValues after the switch statement. Therefore, the for loop should be modified as follows.
// Convert Quadkey to binary values
int zoomLevel = tileIdParam.Length;
int myXDecValue = 0;
int myYDecValue = 0;
char [] myKeyCharArray = tileIdParam.ToCharArray();
for (int i = 0; i < zoomLevel; i++)
{
int tmpVal = (int)Math.Pow(2, zoomLevel - (i + 1));
switch (myKeyCharArray[i])
{
case '0': break;
case '1': myXDecValue += tmpVal;
break;
case '2': myYDecValue += tmpVal;
break;
default: myXDecValue += tmpVal;
myYDecValue += tmpVal;
break;
}
}
Here are a few examples of the resulting tile layers.
Figure 14. WMS tile layer on top of Virtual Earth
Figure 15. Ortho-Images as WMS tile layer on top of Virtual Earth
Figure 16. Development plan as WMS tile layer on top of Virtual Earth
Figure 17. Maps as WMS tile layer on top of Virtual Earth