Introduction
Bing Maps Silverlight Control library has a MapPolyline
class for showing connected points on the map. I wanted my points to be smoothly connected, but there wasn't out-of-the-box support so I developed a custom control deriving from MapShapeBase
[^] class.
Background
Every developer who messed with Expression Blend or Gimp long enough knows by experimentation how a Bézier curve behaves. Basically a cubic Bézier curve has an initial point (P1), two control points (B1, B2) and a final point (P2).
The formula that defines a cubic Bézier curve is:
P(t) = (1-t)3P1 + 3(1-t)2tB1 + 3(1-t)t2B2 + t3P2
where t
is in the interval [0,1]
. Terms multiplying P1
, B1
, B2
, P2
are called the basis functions for the cubic Bézier. Our points determine how much of these basis functions the curve contains.
The thing is we need to calculate coordinates of the control points such that our points of interest are on the curve.
So how can we calculate these control points? After researching (read Googling), I have landed on cardinal splines [^] and Catmull-Rom splines [^] . It appears that every control point of a Catmull-Rom spline is on the curve and it is also a Bézier curve which means we can use it as PathGeometry
[^] with a Silverlight Path
object.
Calculating Control Points
If we rewrite formulas from the cardinal splines [^] page as the following, we can easily calculate control points.
Derivative:
P'0 = (P1 - P0) / a
P'i = (Pi+1 - Pi-1) / a where i in [1, n-1]
P'n = (Pn - Pn-1) / a
Control Points:
B1i = Pi + P'i / 3
B2i = Pi+1 - P'i+1 / 3
Using the Code
The method that calculates the Bézier Points is as follows. GetB1
and GetB2
are straight forward implementations of the aforementioned formulas.
private PointCollection GetBezierPoints(PointCollection pts, double tension)
{
PointCollection ret = new PointCollection();
for (int i = 0; i < pts.Count; i++)
{
if (i == 0)
{
ret.Add(pts[0]);
continue;
}
ret.Add(GetB1(pts, i - 1, tension));
ret.Add(GetB2(pts, i - 1, tension));
ret.Add(pts[i]);
}
return ret;
}
To use the PointCollection
returned from GetBezierPoints
method in Silverlight, we need to build a Path
with BezierSegments
in it.
private PathFigure GetBezierSegments(PointCollection pts, double tension)
{
PathFigure ret = new PathFigure();
ret.Segments.Clear();
ret.IsClosed = false;
var bzPoints = GetBezierPoints(_projectedPoints, tension);
ret.StartPoint = bzPoints[0];
for (int i = 1; i < bzPoints.Count; i += 3)
{
ret.Segments.Add(new BezierSegment()
{
Point1 = bzPoints[i], Point2 = bzPoints[i + 1], Point3 = bzPoints[i + 2] });
}
return ret;
}
And use this PathFigure
in the MapBezier
as:
var pGeo = new PathGeometry();
pGeo.Figures.Add(GetBezierSegments(_projectedPoints, Tension));
((Path)EncapsulatedShape).Data = pGeo;
You can use the MapBezier
class just like MapPolyline
and MapPolygon
classes in your silverlight XAML file. See the attached sample for an example Silverlight application.
<m:Map x:Name="myMap" CredentialsProvider="***">
<m:MapLayer x:Name="layerDurak" >
<local:MapBezier Tension="2" x:Name="plDurakGidis" Stroke="Orange"
StrokeThickness="3" Opacity=".6" Locations="{Binding MyLocations, Mode=OneWay}" />
</m:MapLayer>
</m:Map>
Note: Bing Maps Silverlight SDK [^] is needed for compiling the sample application.
Points of Interest
It was fun messing with the Bing Maps Silverlight Control toolkit and I hope MapBezier
is what you are looking for.
History
- Initial article. 30.08.2010 - This article is also available at my blog.