ThinkGeo.com    |     Blog    |     Wiki    |     Support

Ruler Overlay ellipse doesn't draw correctly

In order to use a measure tool, I’m using the following class:

/// <summary>
/// This class acts as a measuring tool for the map.
/// Draws a dynamic line and a dynamic circle around the line along with a measurement readout.
/// </summary>
public class RulerTrackInteractiveOverlay : TrackInteractiveOverlay
{
    private const string LineFeatureKey = "lineFeature";
    private const string EllipseFeatureKey = "ellipseFeature";
    private LineShape _rulerLineShape;
    private int _mouseDown;
    public DistanceUnit SelectedDistanceUnit { get; set; } = DistanceUnit.Meter;
    public string RulerLabel { get; set; } = " meters";

    private TextStyle _rulerTextStyle;
    public TextStyle RulerTextStyle
    {
        get => _rulerTextStyle;
        set
        {
            _rulerTextStyle = value;
            TrackShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = value;
        }

    }
    
    /// <summary>
    /// Basic constructor
    /// </summary>
    public RulerTrackInteractiveOverlay()
    {
        RulerTextStyle = new TextStyle("length",
            new GeoFont("Arial", 12, DrawingFontStyles.Bold), new GeoSolidBrush(GeoColor.SimpleColors.Black))
        {
            TextLineSegmentRatio = 100, YOffsetInPixel = 10
        };

        TrackShapeLayer.Open(); //open the layer
        TrackShapeLayer.Columns.Add(new FeatureSourceColumn("length")); //make sure there is a length column
        //Sets the appearance of the ruler
        TrackShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultLineStyle = LineStyles.CreateSimpleLineStyle(GeoColor.FromArgb(150, GeoColor.StandardColors.DarkRed), 4, false);
        TrackShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.CreateSimpleAreaStyle(GeoColors.Transparent, GeoColors.Red, 2);
        TrackShapeLayer.ZoomLevelSet.ZoomLevel01.DefaultTextStyle = RulerTextStyle;
        TrackShapeLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
    }

    /// <summary>
    /// This handles the mousedown event for the map.
    /// Adds the measure features to the map.
    /// </summary>
    protected override InteractiveResult MouseDownCore(InteractionArguments interactionArguments)
    {
        //Sets the first and last point of the line where the user clicks.
        InteractiveResult interactiveResult = new InteractiveResult
        {
            DrawThisOverlay = InteractiveOverlayDrawType.Draw,
            ProcessOtherOverlaysMode = ProcessOtherOverlaysMode.DoNotProcessOtherOverlays
        };

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

        double x = worldPoint.X;
        double y = worldPoint.Y;

        if (x < -180 || x > 180)
        {
            x %= 180;
        }

        if (y < -90 || y > 90)
        {
            y %= 90;
        }

        worldPoint = new PointShape(x, y);

        _rulerLineShape = new LineShape(new Collection<Vertex> { new Vertex(worldPoint.X, worldPoint.Y), new Vertex(worldPoint.X, worldPoint.Y) });

        TrackShapeLayer.InternalFeatures.Add(LineFeatureKey, new Feature(_rulerLineShape));
        TrackShapeLayer.InternalFeatures.Add(EllipseFeatureKey, new Feature());

        _mouseDown++;
        return interactiveResult;
    }

    /// <summary>
    /// This handles the mousemove event for the map.
    /// Dynamically draws the line to the mouse location.
    /// </summary>
    protected override InteractiveResult MouseMoveCore(InteractionArguments interactionArguments)
    {
        InteractiveResult interactiveResult = new InteractiveResult();

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

        double x = worldPoint.X;
        double y = worldPoint.Y;

        if (worldPoint.X < -180 || worldPoint.X > 180)
        {
            x = worldPoint.X % 180;
        }
        if (worldPoint.Y < - 90 || worldPoint.Y > 90)
        {
            y = worldPoint.Y % 90;
        }

        worldPoint = new PointShape(x, y);

        if (_mouseDown <= 0) return interactiveResult;
        _rulerLineShape.Vertices[_rulerLineShape.Vertices.Count - 1] = new Vertex(worldPoint.X, worldPoint.Y);

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

        var lengthForLabel = _rulerLineShape.GetLength(GeographyUnit.DecimalDegree, SelectedDistanceUnit);
        var angle = 360 - GetAngleFromTwoVertices(_rulerLineShape.Vertices[1], _rulerLineShape.Vertices[0]);
        TrackShapeLayer.InternalFeatures[LineFeatureKey].ColumnValues["length"] = string.Format("{0:0.0}", lengthForLabel) + RulerLabel + " " + string.Format("{0:0.0}", angle) + "°";
        var lengthForEllipse = _rulerLineShape.GetLength(GeographyUnit.DecimalDegree, SelectedDistanceUnit);
        EllipseShape es = new EllipseShape(new PointShape(_rulerLineShape.Vertices[0]), lengthForEllipse);
        TrackShapeLayer.InternalFeatures[EllipseFeatureKey] = new Feature(es);

        interactiveResult.DrawThisOverlay = InteractiveOverlayDrawType.Draw;
        interactiveResult.ProcessOtherOverlaysMode = ProcessOtherOverlaysMode.DoNotProcessOtherOverlays;

        return interactiveResult;
    }

    /// <summary>
    /// This handles the mouse up event for the map.
    /// Removes the measure tool that was drawn.
    /// </summary>
    protected override InteractiveResult MouseUpCore(InteractionArguments interactionArguments)
    {
        //Removes the line of the ruler at finishing dragging (at mouse up event).
        var interactiveResult = new InteractiveResult
        {
            DrawThisOverlay = InteractiveOverlayDrawType.Draw,
            ProcessOtherOverlaysMode = ProcessOtherOverlaysMode.DoNotProcessOtherOverlays
        };


        if (_mouseDown != 1) return interactiveResult;
        _mouseDown = 0;
        TrackShapeLayer.InternalFeatures.Remove(LineFeatureKey);
        TrackShapeLayer.InternalFeatures.Remove(EllipseFeatureKey);
        Messenger.Default.Send(string.Empty, GlobalVars.ChangeExplainString);
        return interactiveResult;
    }

    /// <summary>
    /// Gets the angle 
    /// </summary>
    /// <param name="b"></param>
    /// <param name="c"></param>
    /// <returns>The angle between two vertices</returns>
    private static double GetAngleFromTwoVertices(Vertex b, Vertex c)
    {
        double alpha = 0;
        var tangentAlpha = (c.Y - b.Y) / (c.X - b.X);
        var peta = Math.Atan(tangentAlpha);

        if (c.X > b.X)
        {
            alpha = 90 + peta * (180 / Math.PI);
        }
        else if (c.X < b.X)
        {
            alpha = 270 + peta * (180 / Math.PI);
        }
        else
        {
            if (c.Y > b.Y) alpha = 0;
            if (c.Y < b.Y) alpha = 180;
        }
        return alpha;
    }
}

The ellipse draws fine only if I have the the Geography and Distance units set to Meters in the MouseMoveCore function when getting the length of the lineshape to be used for the ellipse:

_rulerLineShape.GetLength(GeographyUnit.DecimalDegree, SelectedDistanceUnit); //this causes the ellipse to not be drawn at all
_rulerLineShape.GetLength(GeographyUnit.Meter, DistanceUnit.Meter); // this works, but my map unit is never Meter

My map unit is always decimal degrees, and the distance unit can be changed.
Setting both units to meters works fine when near 0,0, but going towards the poles the ellipse should stretch instead of staying a perfect circle. This isn’t the case though.

Hi Dan,

If you want to make it works under decimal degree, you should want to modify the code to reproject the ellipse from Meter to DecimalDegree.

And because the line cannot works well with the ellipse(It’s not a standard ellipse after reproject), you should want to disable the line to get better effect.

As below it the modified code, wish that’s helpful.

9602.zip (103.7 KB)

Regards,

Ethan