A recent question on the MSDN Bing Maps forums asked if it was possible to display a Bing Map in which only a particular country was visible, painting out everything else. There’s certainly nothing built into the API to do this – the tiles from which maps are constructed are pre-rendered images and know nothing about country outlines displayed on them.
However, if you have coordinate data representing the area of interest you want to display – be that a country or any other area of interest – I don’t think it should be too hard to achieve the effect desired using a dynamic tilelayer. The principle is exactly the same as if you were creating a raster tilehandler that dynamically drew polygons representing an area of interest. The only difference is that, instead of shading the polygon defined by your data, you invert the graphics to shade everything else, leaving the polygon of interest transparent.
If you’re using .NET to draw your tiles, what you’d do is create a graphics object representing the tile as usual, but instead of calling FillRegion() on the polygon drawn on the tile, you’d create a region covering the whole tile and then use Region.Exclude() to “remove” the area covered by the polygon, before filling the rest of the region.
As an example, I grabbed the coordinates for the island of Corsica from GADM, and put them into an array, as:
PointF[] selectedArea = { new PointF(9.17986f, 41.36541f), new PointF(9.22097f, 41.36625f), new PointF(9.2198... ... }
If drawn using a conventional tile rasteriser, this would look like:
However, the addition of the following two lines inverts the region to be shaded:
// Define a new region that spans the whole tile Region region = new Region(new Rectangle(0, 0, 256, 256)); // Exclude the polygon from the region region.Exclude(polygonPath);
Re-rendering the map (and changing the fill colour to blue) now gives:
And, if you tweak the shading options a bit and draw a line around the region you can create something a bit more aesthetically appealing. I’ve only added the masking layer with opacity 0.7 so that you can see the other countries slightly in the background, but you could make the whole rest of the map opaque if you wanted.
So here’s a Bing Map that masks everything other than Corsica. Although the screenshot below is static, this solution uses a normal tile handler so the map retains all the normal panning/zooming functionality, and only Corsica will remain in clear view:
Here’s the code for the HTML page shown above:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script> <script type="text/javascript"> function GetMap() { var map = new Microsoft.Maps.Map(document.getElementById("mapDiv"), { credentials: "ENTERYOURBINGMAPSKEYHERE", center: new Microsoft.Maps.Location(42, 9), mapTypeId: Microsoft.Maps.MapTypeId.road, zoom: 8 }); // Create the tile layer source var tileSource = new Microsoft.Maps.TileSource( { uriConstructor: 'TileHandler.ashx?q={quadkey}', width: 256, height: 256 }); // Construct the layer using the tile source var tilelayer = new Microsoft.Maps.TileLayer({ mercator: tileSource, opacity: 0.8 }); // Push the tile layer to the map map.entities.push(tilelayer); } </script> </head> <body onload="GetMap();"> <div id='mapDiv' style="position:relative; width:800px; height:600px;"></div> </body> </html>
And here’s the code for the TileHandler.ashx:
<%@ WebHandler Language="C#" Debug="true" %> using System; using System.Web; using System.Drawing; using System.Drawing.Drawing2D; using Microsoft.SqlServer.Types; using System.Data.SqlClient; public class Handler : IHttpHandler { public void ProcessRequest(HttpContext context) { // Retrieve the requested quadkey string quadKey = context.Request.QueryString["q"]; int zoomLevel; // Convert quadkey to tile coordinates int tileX, tileY; QuadKeyToTileXY(quadKey, out tileX, out tileY, out zoomLevel); // Calculate the pixel coordinates of the top left corner of this tile int TLpixelX, TLpixelY; TileXYToPixelXY(tileX, tileY, out TLpixelX, out TLpixelY); // Use the map image as a graphics object Bitmap mapImage = new System.Drawing.Bitmap(256, 256); System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(mapImage); // Define Long/Lats of an area of interest (Corsica, in this example) PointF[] selectedArea = {new PointF(9.17986f,41.36541f),new PointF(9.22097f,41.36625f),new PointF(9.21986f,41.37486f),new PointF(9.25958f,41.41347f),new PointF(9.26486f,41.42847f),new PointF(9.21986f,41.40514f),new PointF(9.22902f,41.42624f),new PointF(9.22152f,41.43986f),new PointF(9.21319f,41.44041f),new PointF(9.24347f,41.44652f),new PointF(9.26652f,41.46625f),new PointF(9.27791f,41.46374f),new PointF(9.26625f,41.47041f),new PointF(9.28430f,41.47735f),new PointF(9.28819f,41.49263f),new PointF(9.28347f,41.50069f),new PointF(9.26597f,41.49875f),new PointF(9.28486f,41.51652f),new PointF(9.27236f,41.52319f),new PointF(9.27347f,41.53125f),new PointF(9.28708f,41.52875f),new PointF(9.30291f,41.54569f),new PointF(9.30902f,41.54264f),new PointF(9.30874f,41.54958f),new PointF(9.34402f,41.55902f),new PointF(9.35069f,41.56652f),new PointF(9.34708f,41.57513f),new PointF(9.36763f,41.58930f),new PointF(9.36847f,41.59791f),new PointF(9.34069f,41.59208f),new PointF(9.33736f,41.60152f),new PointF(9.31958f,41.60514f),new PointF(9.29263f,41.58152f),new PointF(9.28152f,41.59791f),new PointF(9.28736f,41.60874f),new PointF(9.30736f,41.61569f),new PointF(9.30763f,41.62847f),new PointF(9.32597f,41.61875f),new PointF(9.32069f,41.61513f),new PointF(9.33402f,41.62152f),new PointF(9.35569f,41.61708f),new PointF(9.34847f,41.63791f),new PointF(9.35646f,41.64070f),new PointF(9.35569f,41.64097f),new PointF(9.36680f,41.64430f),new PointF(9.35646f,41.64070f),new PointF(9.37069f,41.63569f),new PointF(9.37735f,41.64847f),new PointF(9.38624f,41.64541f),new PointF(9.38180f,41.65152f),new PointF(9.38930f,41.65652f),new PointF(9.38125f,41.66402f),new PointF(9.39680f,41.66902f),new PointF(9.37624f,41.67013f),new PointF(9.37236f,41.67930f),new PointF(9.40124f,41.69235f),new PointF(9.39930f,41.70291f),new PointF(9.40819f,41.71374f),new PointF(9.40180f,41.71708f),new PointF(9.40847f,41.76653f),new PointF(9.39569f,41.77485f),new PointF(9.40041f,41.78902f),new PointF(9.39458f,41.79902f),new PointF(9.40652f,41.81708f),new PointF(9.40541f,41.85791f),new PointF(9.39735f,41.86125f),new PointF(9.39680f,41.87541f),new PointF(9.41597f,41.93097f),new PointF(9.40041f,41.94625f),new PointF(9.40708f,41.95152f),new PointF(9.39930f,41.95180f),new PointF(9.41125f,41.95375f),new PointF(9.41236f,41.93930f),new PointF(9.42069f,41.96736f),new PointF(9.54902f,42.10319f),new PointF(9.55486f,42.12430f),new PointF(9.55958f,42.19652f),new PointF(9.55208f,42.23208f),new PointF(9.56041f,42.28263f),new PointF(9.53236f,42.37041f),new PointF(9.54319f,42.42680f),new PointF(9.54180f,42.45902f),new PointF(9.52930f,42.49124f),new PointF(9.53319f,42.54597f),new PointF(9.51097f,42.58930f),new PointF(9.44736f,42.65652f),new PointF(9.44680f,42.68652f),new PointF(9.45819f,42.70264f),new PointF(9.46652f,42.76097f),new PointF(9.49180f,42.79374f),new PointF(9.49319f,42.80736f),new PointF(9.48041f,42.83708f),new PointF(9.48625f,42.85125f),new PointF(9.48208f,42.86680f),new PointF(9.47291f,42.87486f),new PointF(9.47819f,42.88208f),new PointF(9.46930f,42.93624f),new PointF(9.45208f,42.96152f),new PointF(9.46402f,42.98625f),new PointF(9.42013f,43.01208f),new PointF(9.41291f,43.00541f),new PointF(9.35736f,43.00708f),new PointF(9.34013f,42.99430f),new PointF(9.35041f,42.96791f),new PointF(9.34597f,42.95625f),new PointF(9.35875f,42.94541f),new PointF(9.34930f,42.92958f),new PointF(9.35958f,42.92375f),new PointF(9.32180f,42.89902f),new PointF(9.32958f,42.87208f),new PointF(9.34013f,42.86541f),new PointF(9.30958f,42.83319f),new PointF(9.34263f,42.79708f),new PointF(9.33708f,42.76458f),new PointF(9.34513f,42.73513f),new PointF(9.32208f,42.71652f),new PointF(9.32236f,42.69652f),new PointF(9.29791f,42.68152f),new PointF(9.30041f,42.67597f),new PointF(9.28541f,42.67541f),new PointF(9.27069f,42.70041f),new PointF(9.25430f,42.70513f),new PointF(9.25680f,42.71875f),new PointF(9.22902f,42.71819f),new PointF(9.23236f,42.72347f),new PointF(9.22208f,42.73569f),new PointF(9.19986f,42.72513f),new PointF(9.16763f,42.73708f),new PointF(9.13569f,42.72874f),new PointF(9.12347f,42.73263f),new PointF(9.09847f,42.71513f),new PointF(9.08513f,42.71569f),new PointF(9.08458f,42.70486f),new PointF(9.07013f,42.69291f),new PointF(9.05875f,42.69652f),new PointF(9.05986f,42.68291f),new PointF(9.05319f,42.68152f),new PointF(9.06013f,42.66125f),new PointF(9.02902f,42.65375f),new PointF(9.01486f,42.64014f),new PointF(9.00236f,42.64430f),new PointF(8.94652f,42.63319f),new PointF(8.93319f,42.64680f),new PointF(8.93625f,42.64152f),new PointF(8.91263f,42.62930f),new PointF(8.87958f,42.62930f),new PointF(8.86902f,42.60791f),new PointF(8.85041f,42.61236f),new PointF(8.83125f,42.60152f),new PointF(8.82708f,42.60680f),new PointF(8.79847f,42.60263f),new PointF(8.81041f,42.59486f),new PointF(8.79847f,42.58291f),new PointF(8.80513f,42.57208f),new PointF(8.78375f,42.55680f),new PointF(8.75986f,42.55847f),new PointF(8.76319f,42.56902f),new PointF(8.75569f,42.57180f),new PointF(8.72763f,42.56180f),new PointF(8.72430f,42.58458f),new PointF(8.70625f,42.57013f),new PointF(8.71791f,42.57069f),new PointF(8.71569f,42.56013f),new PointF(8.72264f,42.55569f),new PointF(8.71597f,42.55097f),new PointF(8.71874f,42.54013f),new PointF(8.70986f,42.53625f),new PointF(8.71958f,42.52541f),new PointF(8.69847f,42.52736f),new PointF(8.68402f,42.51514f),new PointF(8.66847f,42.51763f),new PointF(8.66430f,42.49319f),new PointF(8.64874f,42.47986f),new PointF(8.65041f,42.47319f),new PointF(8.67652f,42.47625f),new PointF(8.67986f,42.46958f),new PointF(8.66402f,42.45763f),new PointF(8.66764f,42.44569f),new PointF(8.64763f,42.44319f),new PointF(8.66319f,42.43402f),new PointF(8.65597f,42.41708f),new PointF(8.64569f,42.41347f),new PointF(8.62319f,42.42263f),new PointF(8.60791f,42.41764f),new PointF(8.60069f,42.40874f),new PointF(8.61041f,42.40486f),new PointF(8.60152f,42.39902f),new PointF(8.60847f,42.38625f),new PointF(8.57625f,42.38402f),new PointF(8.57819f,42.37486f),new PointF(8.56874f,42.37041f),new PointF(8.55402f,42.37180f),new PointF(8.54736f,42.38124f),new PointF(8.54541f,42.36986f),new PointF(8.53430f,42.37236f),new PointF(8.53847f,42.36458f),new PointF(8.55541f,42.36458f),new PointF(8.55097f,42.34513f),new PointF(8.55930f,42.34319f),new PointF(8.55291f,42.33291f),new PointF(8.57319f,42.33708f),new PointF(8.59152f,42.35291f),new PointF(8.61513f,42.34958f),new PointF(8.61763f,42.34124f),new PointF(8.62847f,42.34013f),new PointF(8.62736f,42.33180f),new PointF(8.60124f,42.32208f),new PointF(8.60124f,42.30875f),new PointF(8.62625f,42.31263f),new PointF(8.63958f,42.29902f),new PointF(8.66097f,42.30208f),new PointF(8.67458f,42.29375f),new PointF(8.67374f,42.28319f),new PointF(8.68874f,42.28013f),new PointF(8.69180f,42.26930f),new PointF(8.63513f,42.25291f),new PointF(8.60986f,42.25402f),new PointF(8.55958f,42.23569f),new PointF(8.53847f,42.23736f),new PointF(8.55125f,42.22680f),new PointF(8.57319f,42.22819f),new PointF(8.56735f,42.21874f),new PointF(8.57708f,42.21402f),new PointF(8.56819f,42.21347f),new PointF(8.56569f,42.20486f),new PointF(8.58180f,42.20680f),new PointF(8.57736f,42.18791f),new PointF(8.58624f,42.18097f),new PointF(8.56069f,42.17180f),new PointF(8.59375f,42.16903f),new PointF(8.55736f,42.14541f),new PointF(8.59097f,42.14902f),new PointF(8.59375f,42.14347f),new PointF(8.57930f,42.12791f),new PointF(8.61569f,42.13347f),new PointF(8.62291f,42.12208f),new PointF(8.65125f,42.11875f),new PointF(8.65819f,42.10069f),new PointF(8.68097f,42.10486f),new PointF(8.68986f,42.11541f),new PointF(8.70041f,42.11291f),new PointF(8.70986f,42.09791f),new PointF(8.70180f,42.08513f),new PointF(8.71069f,42.08708f),new PointF(8.72208f,42.07208f),new PointF(8.71569f,42.06347f),new PointF(8.73597f,42.06597f),new PointF(8.74847f,42.04958f),new PointF(8.74263f,42.04124f),new PointF(8.72486f,42.04319f),new PointF(8.72458f,42.03402f),new PointF(8.68569f,42.02763f),new PointF(8.65541f,42.01013f),new PointF(8.65874f,41.99152f),new PointF(8.67652f,41.98930f),new PointF(8.64958f,41.96930f),new PointF(8.61430f,41.97180f),new PointF(8.59208f,41.96402f),new PointF(8.59847f,41.95208f),new PointF(8.60736f,41.95208f),new PointF(8.60986f,41.93903f),new PointF(8.62347f,41.93569f),new PointF(8.60708f,41.89374f),new PointF(8.64125f,41.90986f),new PointF(8.71847f,41.90736f),new PointF(8.74124f,41.91541f),new PointF(8.74791f,41.93402f),new PointF(8.76458f,41.92180f),new PointF(8.78319f,41.92541f),new PointF(8.80430f,41.89569f),new PointF(8.79097f,41.88430f),new PointF(8.77847f,41.88430f),new PointF(8.78513f,41.87958f),new PointF(8.78069f,41.87319f),new PointF(8.79125f,41.86819f),new PointF(8.78902f,41.85097f),new PointF(8.74875f,41.84541f),new PointF(8.78402f,41.83263f),new PointF(8.77236f,41.82291f),new PointF(8.77430f,41.81236f),new PointF(8.74764f,41.81124f),new PointF(8.73736f,41.79680f),new PointF(8.71180f,41.80263f),new PointF(8.71013f,41.79458f),new PointF(8.73235f,41.77958f),new PointF(8.69569f,41.75152f),new PointF(8.66124f,41.75041f),new PointF(8.65930f,41.73847f),new PointF(8.70680f,41.73874f),new PointF(8.70236f,41.72569f),new PointF(8.70930f,41.72180f),new PointF(8.77597f,41.74125f),new PointF(8.78430f,41.73180f),new PointF(8.77097f,41.71347f),new PointF(8.78736f,41.70875f),new PointF(8.78125f,41.69930f),new PointF(8.81235f,41.71458f),new PointF(8.84319f,41.69652f),new PointF(8.91458f,41.69097f),new PointF(8.91375f,41.67986f),new PointF(8.88180f,41.67096f),new PointF(8.87208f,41.64625f),new PointF(8.85124f,41.64819f),new PointF(8.82041f,41.62958f),new PointF(8.80569f,41.64208f),new PointF(8.79402f,41.63263f),new PointF(8.79513f,41.62347f),new PointF(8.78513f,41.61791f),new PointF(8.79125f,41.61069f),new PointF(8.78652f,41.59708f),new PointF(8.77374f,41.59152f),new PointF(8.80402f,41.57541f),new PointF(8.78291f,41.56708f),new PointF(8.78736f,41.55680f),new PointF(8.81069f,41.55652f),new PointF(8.82097f,41.54375f),new PointF(8.84458f,41.54680f),new PointF(8.84541f,41.54013f),new PointF(8.85347f,41.54680f),new PointF(8.85597f,41.53513f),new PointF(8.84208f,41.52597f),new PointF(8.84236f,41.51819f),new PointF(8.87652f,41.52485f),new PointF(8.89069f,41.51597f),new PointF(8.88319f,41.50485f),new PointF(8.91930f,41.50541f),new PointF(8.92041f,41.48847f),new PointF(8.93264f,41.49624f),new PointF(8.93847f,41.48875f),new PointF(8.96041f,41.49180f),new PointF(8.96763f,41.48875f),new PointF(8.96513f,41.48208f),new PointF(8.97791f,41.48291f),new PointF(8.98097f,41.47347f),new PointF(8.99402f,41.48764f),new PointF(9.00263f,41.47486f),new PointF(9.01930f,41.47986f),new PointF(9.01402f,41.47208f),new PointF(9.02402f,41.46125f),new PointF(9.03791f,41.47152f),new PointF(9.04041f,41.45624f),new PointF(9.07902f,41.47652f),new PointF(9.06541f,41.45430f),new PointF(9.07347f,41.44374f),new PointF(9.09430f,41.44930f),new PointF(9.10847f,41.44013f),new PointF(9.12458f,41.44374f),new PointF(9.10541f,41.42847f),new PointF(9.10958f,41.42069f),new PointF(9.09458f,41.41236f),new PointF(9.09347f,41.39375f),new PointF(9.13347f,41.39930f),new PointF(9.12652f,41.39430f),new PointF(9.16958f,41.38375f),new PointF(9.17986f,41.36541f)}; // Convert shape from Lat/Lngs to pixel coordinates relative to this tile Point[] points = new Point[selectedArea.Length]; for (int k = 0; k < selectedArea.Length; k++) { int x = LongitudeToXAtZoom(selectedArea[k].X, zoomLevel); int y = LatitudeToYAtZoom(selectedArea[k].Y, zoomLevel); points[k] = new Point(x-TLpixelX, y-TLpixelY); } // Create a polygon from these points GraphicsPath polygonPath = new GraphicsPath(); polygonPath.AddPolygon(points); // Define a new region that spans this tile Region region = new Region(new Rectangle(0, 0, 256, 256)); // Exclude the polygon from the region region.Exclude(polygonPath); // Now fill everything but the polygon graphics.FillRegion(Brushes.Black, region); graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.DrawLines( new System.Drawing.Pen(System.Drawing.Color.FromArgb(255, 255, 255, 255), 4), points); // Send the tile image back to the client context.Response.ContentType = "image/png"; System.IO.MemoryStream memStream = new System.IO.MemoryStream(); mapImage.Save(memStream, System.Drawing.Imaging.ImageFormat.Png); memStream.WriteTo(context.Response.OutputStream); } /// /// COMMON UTILITY FUNCTIONS /// See http://msdn.microsoft.com/en-us/library/bb259689.aspx /// private const double earthRadius = 6378137; //The radius of the earth - should never change! private const double earthCircum = earthRadius * 2.0 * Math.PI; //calulated circumference of the earth private const double earthHalfCirc = earthCircum / 2; //calulated half circumference of the earth private const int pixelsPerTile = 256; public static void QuadKeyToTileXY(string quadKey, out int tileX, out int tileY, out int levelOfDetail) { tileX = tileY = 0; levelOfDetail = quadKey.Length; for (int i = levelOfDetail; i > 0; i--) { int mask = 1 << (i - 1); switch (quadKey[levelOfDetail - i]) { case '0': break; case '1': tileX |= mask; break; case '2': tileY |= mask; break; case '3': tileX |= mask; tileY |= mask; break; default: throw new ArgumentException("Invalid QuadKey digit sequence."); } } } public static void TileXYToPixelXY(int tileX, int tileY, out int pixelX, out int pixelY) { pixelX = tileX * 256; pixelY = tileY * 256; } public static int LatitudeToYAtZoom(double lat, int zoom) { int y; double arc = earthCircum / ((1 << zoom) * pixelsPerTile); double sinLat = Math.Sin(DegToRad(lat)); double metersY = earthRadius / 2 * Math.Log((1 + sinLat) / (1 - sinLat)); y = (int)Math.Round((earthHalfCirc - metersY) / arc); return y; } public static int LongitudeToXAtZoom(double lon, int zoom) { int x; double arc = earthCircum / ((1 << zoom) * pixelsPerTile); double metersX = earthRadius * DegToRad(lon); x = (int)Math.Round((earthHalfCirc + metersX) / arc); return x; } private static double DegToRad(double d) { return d * Math.PI / 180.0; } public bool IsReusable { get { return false; } } }
Hi Alistair
Very interesting way of doing a mask layer – might be worth trying a buffer around the polygon too?
Regards
Giles