Drawing a Curved Line with Bing Maps AJAX

I wanted to draw a curved line on a Bing Maps map. I couldn’t find a built-in way to do this, plus I wanted total programmatic control, so I wrote my own JavaScript function. It was a lot trickier than I thought it’d be. An alternative is to code up a Bezier curve function which I’ve done in the past and which I’ll post here at a later time. (Note: I am fine-tuning the Bezier approach and it is a lot simpler if you don’t need complete control over the curve shape). Calling my AddCurvedLine function looks like this:

map = new VEMap(‘myMap’);
map.LoadMap(new VELatLong(38.2740,140.8776), 7, VEMapStyle.Aerial);

var start = new VELatLong(38.2740,140.8776);
var finish = new VELatLong(38.2740,142.8776);
var arcHeight = 0.3;
var color = new VEColor(180,255,0,1.0);
var width = 3;
var numPoints = 360;

AddCurvedLine(start, finish, arcHeight, color, width, ‘up’, ‘smooth’,
  numPoints);

The arcHeight is degrees latitude and gives me control over how curved the line is. The ‘up’ parameter makes the curve hump upwards; the alternative is ‘down’. The ‘smooth’ parameter creates a curve that is smooth from start to finish; the alternative is ‘pointed’ which creates a curve that has a peaked shape. The other parameters should be easy to understand. See the image below.

The AddCurvedLine function first creates two sets, left and right, of points that follow the shape of the square root function. The ‘pointed’ option uses these points. If the style option is ‘smooth’, the points are processed using the quadratic least squares best fit algorithm to smooth them out. The function still has a few bugs and doesn’t handle every situation, but as a quick hack it works very well for me. By the way, you might also be interested in drawing a geodesic line (the shortest line between two places on a map). If so, see http://www.beginningspatial.com/ category/ platform/ virtual_earth_map_control/ for an excellent discussion.

<html>
<!– CurvedLine.html –>
<head>
<title>Bing Maps AJAX Curved Line</title>
<script type=”text/javascript”  src=”http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3“> </script>
<script type=”text/javascript”>

  var map = null; // global VEMap object

// ———————————————————————————————
function GetQuadradicYValues(xValues, yValues) // helper for AddCurvedLine()
  {
    // input is an array of x values and the corresponding array of y values
    // output is an array of new y values which are quadratic least squares best fit
    // see http://mathforum.org/library/drmath/view/72047.html for the math

    if (xValues.length == 1 || xValues.length == 2) { return yValues; }

    var S00 = xValues.length;
    var S10 = 0.0;  // sum of x s
    var S20 = 0.0;  // sum of x^2 s
    var S30 = 0.0;  // sum of x^3 s
    var S40 = 0.0;  // sum of x^4 s
    var S01 = 0.0;  // sum of y s
    var S11 = 0.0;  // sum of xy s
    var S21 = 0.0;  // sum of (x^2)y s

    for (i = 0; i < xValues.length; ++i) {  // compute sums
      S10 += xValues[i];
      S20 += xValues[i] * xValues[i];
      S30 += xValues[i] * xValues[i] * xValues[i];
      S40 += xValues[i] * xValues[i] * xValues[i] * xValues[i];
      S01 += yValues[i];
      S11 += xValues[i] * yValues[i];
      S21 += xValues[i] * xValues[i] * yValues[i];
    }

    var denom = (S00 * S20 * S40) – (S10 * S10 * S40) – (S00 * S30 * S30) + (2.0 * S10 *
      S20 * S30) – (S20 * S20 * S20);
    var a = ((S01 * S10 * S30) – (S11 * S00 * S30) – (S01 * S20 * S20) + (S11 * S10 * S20) +
     (S21 * S00 * S20) – (S21 * S10 * S10)) / denom;   
    var b = ((S11 * S00 * S40) – (S01 * S10 * S40) + (S01 * S20 * S30) – (S21 * S00 * S30) –
     (S11 * S20 * S20) + (S21 * S10 * S20)) / denom;
    var c = ((S01 * S20 * S40) – (S11 * S10 * S40) – (S01 * S30 * S30) + (S11 * S20 * S30) +
     (S21 * S10 * S30) – (S21 * S20 * S20)) / denom;

    var result = new Array();
    var newY = 0.0;
    for (i = 0; i < xValues.length; ++i) {
      var x = xValues[i];
      newY = (a * x * x) + (b * x) + c; // y = ax^2 + bx + c
      result.push(newY);
    }
 
    return result;
  } // GetQuadradicYValues()
// ———————————————————————————————
function AddCurvedLine(start, finish, arcHeight, color, width, upDown, style, numPoints)
{
  // for use with Bing Maps AJAX API (v 6.3)
  // assumes exisatence of a global VEMap object named ‘map’
  // assumes existence of helper function GetQuadradicYValues()
  // start & finish: VELatLong
  // arcHeight: double latitude degrees
  // color: VEColor for line
  // width: integer line width
  // upDown: string ‘up’ or ‘down’
  // style: string ‘smooth’ or ‘pointed’
  // numPoints: int number of points to use for line

  if (numPoints < 3) numPoints = 3;
  if (numPoints % 2 == 0) { ++numPoints; }   // make numPoints an odd value
  if (start.Longitude > finish.Longitude) {   // adjust so start is to the left of finish
    var temp = start; start = finish; finish = temp;
  }

  var deltaLat = Math.abs(start.Latitude – finish.Latitude);
  var deltaLon = Math.abs(start.Longitude – finish.Longitude);
  var numSegments = numPoints – 1;
  var latIncrement = deltaLat / (numSegments * 1.0);
  var lonIncrement = deltaLon / (numSegments * 1.0);

  var lats = new Array();
  var lons = new Array();
  lats.push(start.Latitude);
  lons.push(start.Longitude);

  for (i = 1; i < numPoints; ++i) {
    lats.push(lats[i-1] + latIncrement);
    lons.push(lons[i-1] + lonIncrement);
  }

  var multipliers = new Array();
  var midIndex = numSegments / 2;

  for (i = 0; i <= midIndex; ++i) { // left part of square root curve
    multipliers[i] = Math.sqrt( i / (midIndex * 1.0));
  }

  for (i = midIndex + 1; i < numPoints; ++i) { // right part is symmetric
    multipliers[i] = multipliers[numSegments – i];
  }

  for (i = 0; i < lats.length; ++i) {
    if (upDown == ‘up’) {
      lats[i] = lats[i] + (arcHeight * multipliers[i]);
    }
    else {
      lats[i] = lats[i] – (arcHeight * multipliers[i]);
    }
  }

  var smoothedLats = GetQuadradicYValues(lons, lats);
  var delta1 = smoothedLats[0] – start.Latitude;
  var delta2 = smoothedLats[smoothedLats.length-1] – finish.Latitude;
  var offset = (delta1 + delta2) / 2.0;

  for (i = 0; i < smoothedLats.length; ++i) {
    smoothedLats[i] -= offset;
  }
 
  var points = new Array();
  for (i = 0; i < numPoints; ++i) {
    if (style == ‘pointed’) {
      points.push(new VELatLong(lats[i], lons[i]));
    }
    else { // ‘smooth’
     points.push(new VELatLong(smoothedLats[i], lons[i]));
    }
  }
 
  var curve = new VEShape(VEShapeType.Polyline, points);
  curve.HideIcon();
  curve.SetLineColor(color);
  curve.SetLineWidth(width);
  map.AddShape(curve);

} // AddCurvedLine()
// ———————————————————————————————
function MakeMap()
{
  map = new VEMap(‘myMap’);
  // map.SetCredentials(“xxx”);
  map.LoadMap(new VELatLong(38.2740,140.8776), 7, VEMapStyle.Aerial);

  var start = new VELatLong(38.2740,140.8776);
  var finish = new VELatLong(38.2740,142.8776);
  var arcHeight = 0.3;
  var color = new VEColor(180,255,0,1.0);
  var width = 3;
  var numPoints = 360;

  AddCurvedLine(start, finish, arcHeight, color, width, ‘up’, ‘smooth’, numPoints);

} // MakeMap()
// ———————————————————————————————

</script>
</head>              
<body onload=”MakeMap();”>
<div id=’myMap’ style=”position:relative; width:600px; height:400px;”></div>
</body>
</html>

This entry was posted in Software Test Automation. Bookmark the permalink.