ThinkGeo.com    |     Documentation    |     Premium Support

Drawing ellipse around moving line in trackoverlay

I’m using a custom TrackInteractiveOverlay for a measuring tool which let’s the user click and move the mouse, and as the mouse moves, the line grows so that its last vertex is at the mouse location and an ellipse appears around it, with the center of it being the first vertex of the line and the last vertex of the line being at the edge of the vertex. This works fine in wgs84, but in other projections (like mercator), the ellipse is smaller than the line. This is more noticeable near the poles.

Here’s an example of wgs84 where it is working correctly:

Here is mercator where it is not working:

Here is the related code in mouse move function:

 private const string LineFeatureKey = "lineFeature";
 private const string EllipseFeatureKey = "ellipseFeature";
 private const string FullLineKey = "fullLineFeature";
 private LineShape _rulerLineShape;
 private LineShape _fullLineShape;

protected override InteractiveResult MouseMoveCore(InteractionArguments interactionArguments)
{
            InteractiveResult interactiveResult = new InteractiveResult();

            var worldPoint = new PointShape(interactionArguments.WorldX, interactionArguments.WorldY);

            _rulerLineShape.Vertices[_rulerLineShape.Vertices.Count - 1] = new Vertex(worldPoint.X, worldPoint.Y);

            TrackShapeLayer.InternalFeatures[LineFeatureKey] = new Feature(_rulerLineShape);

            var lengthForLabel = _rulerLineShape.GetLength(FromWgs84ToCurrentProjectionConverter.ExternalProjection,
                SelectedDistanceUnit, DistanceCalculationMode.Haversine);

            var totalLineLength = _clicks == 0 ? lengthForLabel : _fullLineShape.GetLength(FromWgs84ToCurrentProjectionConverter.ExternalProjection, SelectedDistanceUnit, DistanceCalculationMode.Haversine) + lengthForLabel;

            // need the vertices in wgs84 for the GetDegreeBearing() below to work
            var firstVertex = (PointShape)ToWgs84ProjectionConverter.ConvertToExternalProjection(new PointShape(_rulerLineShape.Vertices[0]));
            var lastVertex = (PointShape)ToWgs84ProjectionConverter.ConvertToExternalProjection(new PointShape(_rulerLineShape.Vertices[1]));

            var angle = GetDegreeBearing(firstVertex.Y, firstVertex.X, lastVertex.Y, lastVertex.X);

            TrackShapeLayer.InternalFeatures[LineFeatureKey].ColumnValues["length"] = string.Format("{0:0.0}", lengthForLabel) + RulerLabel + " " + string.Format("{0:0.0}", angle) + "°";

            var lengthForEllipse = lengthForLabel;
            var es = new EllipseShape(new PointShape(_rulerLineShape.Vertices[0]), lengthForEllipse, CurrentMapUnit, SelectedDistanceUnit);

            ShapeValidationResult res = es.Validate(ShapeValidationMode.Simple);
            if (res.IsValid)
            {
                TrackShapeLayer.InternalFeatures[EllipseFeatureKey] = new Feature(es);

                interactiveResult.InteractiveOverlayUpdateMode = InteractiveOverlayUpdateMode.Update;
                interactiveResult.ProcessOtherOverlaysMode = ProcessOtherOverlaysMode.DoNotProcessOtherOverlays;
            }

            return interactiveResult;
}

Is there something I’m missing? I confirmed the worldPoints used for the line are in the same units as the current MapUnit, and the ellipse is being created from those, so I’m not sure why it would appear wrong in certain projections. Or I guess I should say when the mapunit is something other than degrees.

Hi Dan,

This is working as designed - you’re just mixing two different distance spaces.

GetLength(…, Haversine) gives you a real ground distance (great-circle meters), which is perfect for the label. But new EllipseShape(center, radius, GeographyUnit.Meter, …) treats that radius as plain planar meters in the map’s coordinate system. Mercator isn’t equidistant, so away from the equator the projected line reaches farther than a ground-distance radius - which is why the ellipse looks too small, and worse near the poles. (WGS84 works because the DecimalDegree path corrects for the center latitude.)

Since your first and last vertices are already in map units and you just want the last vertex to sit on the edge, compute the radius as the planar distance between them and use the plain EllipseShape(center, radius) overload:

var v0 = _rulerLineShape.Vertices[0];
var vLast = _rulerLineShape.Vertices[_rulerLineShape.Vertices.Count - 1];
double dx = vLast.X - v0.X;
double dy = vLast.Y - v0.Y;
double radius = Math.Sqrt(dx * dx + dy * dy);

var es = new EllipseShape(new PointShape(v0), radius);

Keep using the Haversine length for the label - just don’t feed it to the ellipse. This lines up in every projection.

The rule of thumb: a shape whose vertices are already in map units needs no adjustment - it’s already in the same (distorted) space as the rest of the map, so it lines up automatically. You only need to account for the projection’s distortion when the shape is meant to represent a true ground size (e.g. a circle of a real X km radius), where X ground meters is not X projected meters and you’d scale by sec(latitude).

FYI: we’ve just reviewed and improved the xml documentation for all the GetLength method. They will be available in the next beta build.

Thanks,
Ben

1 Like

Thanks Ben! That worked perfect. Just a follow-up question for my curiosity, if I wanted to display I guess the “real” ellipse, where for example, the top of it would get stretched vertically up near the north pole (am I right about this?), would that be doable?

Hi Dan,

The shape you’re describing is a geodesic circle: every point on its boundary has the same ground distance from the center.

To create it, build the circle in WGS84 first. Starting from the WGS84 center, calculate destination points at the desired ground-distance radius for a series of bearings (for example, every 5 degrees). Then project those points into the map’s projection and use them to create a PolygonShape.

  var centerInMap = new PointShape(_rulerLineShape.Vertices[0]);
  var centerInWgs84 = (PointShape)ToWgs84ProjectionConverter.ConvertToExternalProjection(centerInMap);
  
  var vertices = new Collection<Vertex>();
  double radius = lengthForLabel;

  for (int bearing = 0; bearing < 360; bearing += 5)
  {
      var point = new PointShape(centerInWgs84.X, centerInWgs84.Y);
  
      point.TranslateByDegree(
          radius, 
          bearing,
          GeographyUnit.DecimalDegree,
          SelectedDistanceUnit);
  
      var projectedPoint = (PointShape)ToWgs84ProjectionConverter.ConvertToInternalProjection(point);
      vertices.Add(new Vertex(projectedPoint));
  }
  
  // Close the ring.
  vertices.Add(vertices[0]);
  var geodesicCircle = new PolygonShape(new RingShape(vertices));
  TrackShapeLayer.InternalFeatures[EllipseFeatureKey] = new Feature(geodesicCircle);

In Mercator, the projected result is generally not a mathematically exact ellipse. For a small circle it may still look nearly circular because Mercator is locally conformal. For a larger circle, especially at high latitudes, it becomes asymmetric and the pole-facing side is stretched more strongly.

Also keep in mind that Web Mercator cannot represent the poles themselves and is normally limited to about ±85.0511°. A circle that reaches that limit, crosses the antimeridian, or surrounds a pole needs additional clipping or longitude-wrapping handling.

Thanks,
Ben