How to keep overlay text box in place while zooming in or out on GMap while using mission planner plugin?

Hi everyone,

I’m currently working on creating a plugin for Mission Planner where I need to overlay a text box rectangle on the right side of the GMap. However, I’m facing an issue where the text box moves whenever I zoom in or out, although the view area center point is not changing. if I drag the screen, text box is on the right side well.

I have inherited the GMapOverlay class and created a custom class to draw the rectangle using the OnRender method. Here is a snippet of my code:

 public class MyOverlay : GMapOverlay
    {
        private GPoint mapBottomRightGPoint;
        private PointLatLng mapBottomRightLatLng;
        private GPoint mapBottomRight;
        private RectangleF rect; 
        private int text_box_width = 100;
        private int text_box_height = 380;
        private GMapControl MyFDGMapControl;
        private GPoint _previousCenter;
        private long deltaX = 0;
        private long deltaY = 0;
        private static long deltaX_accumulated = 0;
        private static long deltaY_accumulated = 0;

        public MyOverlay(GMapControl FDGMapControl)
        {
            MyFDGMapControl = FDGMapControl;
            //FDGMapControl.OnMapZoomChanged += GMapControl_MapZoomChanged;
            //FDGMapControl.OnPositionChanged += GMapControl_PositionChanged;
        }

        private void GMapControl_MapZoomChanged()
        {
        }

        private void GMapControl_PositionChanged(PointLatLng point)
        {
        }

        public override void OnRender(IGraphics g) 
        {
            base.OnRender(g); 

            mapBottomRight = new GPoint(Control.Width/2, Control.Height/2);
            RectangleF rect = new RectangleF(mapBottomRight.X - text_box_width, mapBottomRight.Y - text_box_height, text_box_width, text_box_height);

            Color lightGray = Color.FromArgb(128, 211, 211, 211);
            using (SolidBrush brush = new SolidBrush(lightGray)) 
            {
                float radius = 10f;
                GraphicsPath path = new GraphicsPath();
                path.AddLine(rect.X + radius, rect.Y, rect.X + rect.Width - (radius * 2), rect.Y);
                path.AddArc(rect.X + rect.Width - (radius * 2), rect.Y, radius * 2, radius * 2, 270, 90);
                path.AddLine(rect.X + rect.Width, rect.Y + radius, rect.X + rect.Width, rect.Y + rect.Height - (radius * 2));
                path.AddArc(rect.X + rect.Width - (radius * 2), rect.Y + rect.Height - (radius * 2), radius * 2, radius * 2, 0, 90);
                path.AddLine(rect.X + rect.Width - (radius * 2), rect.Y + rect.Height, rect.X + radius, rect.Y + rect.Height);
                path.AddArc(rect.X, rect.Y + rect.Height - (radius * 2), radius * 2, radius * 2, 90, 90);
                path.AddLine(rect.X, rect.Y + rect.Height - (radius * 2), rect.X, rect.Y + radius);
                path.AddArc(rect.X, rect.Y, radius * 2, radius * 2, 180, 90);
                path.CloseFigure();

                g.FillPath(brush, path);
            }

            //creat color for font
            
            Color modifiedDarkGray = ControlPaint.DarkDark(Color.DarkGray); // get a darker version of DarkGray
            float brightness = 0.2f; // increase brightness by 20%
            float saturation = 0.3f; // saturation at 0 firstly(grayscale)
            float hue = 0.3f; // keep hue at 0 (gray)
            float alpha = 1.0f; // keep alpha at 1 (opaque)
            modifiedDarkGray = FromAhsb(alpha, hue, saturation, brightness);

            // Add text to the rectangle
            using (Font font = new Font("Arial", 12))
            {
                using (SolidBrush textBrush = new SolidBrush(modifiedDarkGray)) 
                {
                    string text = "Control Position : " + Control.Position + "\n" + "Control Width : " + Control.Width + "\n" + "Control Position To GPoint : " + Control.FromLatLngToLocal(Control.Position)
                        + "\n" + "Visible Area : " + Control.ViewArea;
                    
                    SizeF textSize = g.MeasureString(text, font);
                    PointF textLocation = new PointF(rect.X - text_box_width, rect.Y - text_box_height);
                    g.DrawString(text, font, textBrush, textLocation);
                }
            }
            
        }

I tried to use offset value from center like this code below, but it didn’t worked. :

public override void OnRender(IGraphics g)
        {
            base.OnRender(g);

            // Get the current center point in pixel coordinates
            GPoint currentCenter = Control.FromLatLngToLocal(Control.Position);

            // Calculate the difference in pixel coordinates between the current and previous center points
            deltaX = currentCenter.X - _previousCenter.X;
            deltaY = currentCenter.Y - _previousCenter.Y;

            // Calculate the position of your rectangle based on the size of your rectangle and the offset
            float rectWidth = 600;
            float rectHeight = 680;
            GPoint rectPosition = new GPoint((Control.Width / 2), (Control.Height / 2));
            //GPoint rectPosition = new GPoint(Control.Width - rectWidth, Control.Height - rectHeight);
            deltaX_accumulated += deltaX;
            deltaY_accumulated += deltaY;
            rect = new RectangleF(rectPosition.X + deltaX_accumulated - rectWidth, rectPosition.Y + deltaY_accumulated - rectHeight, rectWidth, rectHeight);
            _previousCenter = currentCenter;

            // Custom rendering logic here
            Color lightGray = Color.FromArgb(128, 211, 211, 211);
            using (SolidBrush brush = new SolidBrush(lightGray))
            {
                float radius = 10f; 
                GraphicsPath path = new GraphicsPath();
                path.AddLine(rect.X + radius, rect.Y, rect.X + rect.Width - (radius * 2), rect.Y);
                path.AddArc(rect.X + rect.Width - (radius * 2), rect.Y, radius * 2, radius * 2, 270, 90);
                path.AddLine(rect.X + rect.Width, rect.Y + radius, rect.X + rect.Width, rect.Y + rect.Height - (radius * 2));
                path.AddArc(rect.X + rect.Width - (radius * 2), rect.Y + rect.Height - (radius * 2), radius * 2, radius * 2, 0, 90);
                path.AddLine(rect.X + rect.Width - (radius * 2), rect.Y + rect.Height, rect.X + radius, rect.Y + rect.Height);
                path.AddArc(rect.X, rect.Y + rect.Height - (radius * 2), radius * 2, radius * 2, 90, 90);
                path.AddLine(rect.X, rect.Y + rect.Height - (radius * 2), rect.X, rect.Y + radius);
                path.AddArc(rect.X, rect.Y, radius * 2, radius * 2, 180, 90);
                path.CloseFigure();

                g.FillPath(brush, path);
            }

            //creat color for font
            
            Color modifiedDarkGray = ControlPaint.DarkDark(Color.DarkGray); // get a darker version of DarkGray
            float brightness = 0.2f; // increase brightness by 20%
            float saturation = 0.3f; // saturation at 0 firstly(grayscale)
            float hue = 0.3f; // keep hue at 0 (gray)
            float alpha = 1.0f; // keep alpha at 1 (opaque)
            modifiedDarkGray = FromAhsb(alpha, hue, saturation, brightness);

            // Add text to the rectangle
            using (Font font = new Font("Arial", 12))
            {
                using (SolidBrush textBrush = new SolidBrush(modifiedDarkGray))
                {
                    string text = "Control Position : " + Control.Position + "\n" + "Control Width : " + Control.Width + "\n" + "Control Position To GPoint : " + Control.FromLatLngToLocal(Control.Position)
                        + "\n" + "Visible Area : " + Control.ViewArea;
                    
                    SizeF textSize = g.MeasureString(text, font);
                    PointF textLocation = new PointF(rectPosition.X + deltaX_accumulated - rectWidth, rectPosition.Y + deltaY_accumulated - rectHeight);
                    g.DrawString(text, font, textBrush, textLocation);
                }
            }
            
        }

Thank you in advance for any help!

The problem has been resolved. In order to achieve the solution, a deep understanding of the GMap coordinate system is necessary. Debugging into the ViewArea, GPoint, and LatLng system can assist in overlaying the textbox onto the map. However, the solution code was quite complex.

I think you overcomplicate the issue :slightly_smiling_face:
All you need is :

public class myOverlay : GMapOverlay
{

    public override void OnRender(IGraphics g)

    {
        
        g.DrawRectangle(new Pen(Color.Pink, 4), Control.Width/2-100, Control.Height/2-100, 100, 100);
        base.OnRender(g);
    }

}

And to nudge the map control to redraw when zoom, add to the OnMapZoomChanged this

    private void FDGMapControl_OnMapZoomChanged()
    {
        Host.FDGMapControl.Position = Host.FDGMapControl.Position;
    }

Thank you for taking the time to respond. However, I think the issue may be more complex than you initially thought.

I sincerely appreciate your open-source work.

Attached is a screenshot of a result of the code you gave, which produces the rectangle you see in the bottom right corner. when the mission planner is turned on, the rectangle is on the right-bottom side well, but when the user zooms in on a location other than the center with their mouse, the rectangle moves like screenshot.

I believe this problem may not be straightforward due to the following reasons:

  1. Your code uses Host, but in the plugin, we are unable to access Host. To access Host in the class, I utilized the following code:

    public MyOverlay(GMapControl FDGMapControl) { MyFDGMapControl = FDGMapControl; }

  2. The line of code “Host.FDGMapControl.Position = Host.FDGMapControl.Position;” simply assigns the same value to the same value.

  3. base.OnRender(g); should be at the front of function, isn’t it?

Regardless, I respect your expertise. anyway, I was able to solve my problem with complex calculations.

I would greatly appreciate it if an expert in mission planner like you could assist me with a question.

Specifically, I am wondering if there is a way to create a button on an overlaying text box.

I would like the button to be transparent and aesthetically pleasing.

While I believe using a click event could be a solution, I would like to hear from an expert like you

  1. You can have control handling in within the plugin class, and Host is available from there. See the code below.
  2. Assigning the same position did not move the map but calls a ForceUpdateOverlays() which is otherwise not accessible. This solves the shifting overlay coordinate system during zooming.
  3. In this case the OnRender even can be omitted unless you plan to add markers or routes onto the overlay.

I put together a proof of concept GitHub - EosBandi/OverlayControlPlugin: Proof of concept of user drawn controls on GMapOverlay

It appears that I had omitted the following line of code when you first provided code to me:

Host.FDGMapControl.OnMapZoomChanged += FDGMapControl_OnMapZoomChanged;

Instead, I had only written the OnMapZoomChanged function without assigning it to anything.

Thanks to your help, your code now works great. I also appreciate your insights regarding the clicking system.

Initially, your code seemed very simple, and I had hoped that following it would simplify my own code. However, my attempts to achieve transparent color, rounded corners, custom font, and custom font color have once again made my code complex.

Nonetheless, your help has been invaluable to me. Thank you very much!