Masking out particular areas of interest on Bing Maps

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:

image

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:

image

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:

image

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;
}
}

}
About these ads
This entry was posted in Bing Maps and tagged , , . Bookmark the permalink.

One Response to Masking out particular areas of interest on Bing Maps

  1. Giles Collingwood says:

    Hi Alistair

    Very interesting way of doing a mask layer – might be worth trying a buffer around the polygon too?

    Regards

    Giles

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s