Keep map in user view

Hello everyone,

I’m looking for a way to keep my map ‘in view’. In other words that the user is not able to lose sight of the shapes by panning left, right up or down.
I think about some boundary so the user cannot pan further in that direction, no matter on which zoom level the user is on.

Any ideas?

Kind regards,

Remco

Hello @Rem97

You should be able to handle the ExtentsChanged event, comparing the new extents to your predefined maximum extents. And if the new extents are beyond the limits, you can change them. You do have to be careful since changing the extents will generate another ExtentsChanged event, so you may have to manage a flag to prevent looping.

I’ve done it before, for a customer, changing it internally to the OCX itself, so that it could be handled at a lower level. If you have the source code and are building your own OCX, I can send some code snippets. If there’s enough interest as a feature, I could at some point integrate the changes back into the ‘develop’ branch.

Regards,
Jerry.

Hello Jerry,

Thank you for your fast reply :slight_smile:

I can show some code but not all of it I think. At least not by copy+paste since my whole file is at the moment like 1500 lines of code and comments…
Anyway i’am trying some event handler/trigger which acts on the ExtensChanged event for now and see how that goes. I will post feedback here with the used code (in C#).

By the way, i forgot to mention but I found the function “bounds”. I doubt it will, but isn’t there a way to used this function to keep the map in a certain field of view?

Kind regards,

Remco

Hello everyone,

I just did some testing. First I started with getting the coordinates by a button press and noted those down:

        private void button1_Click(object sender, EventArgs e)
    {
        axMap1.ZoomToMaxVisibleExtents();
        double bound_min_x = 0.0;
        double bound_min_y = 0.0;
        double bound_min_z = 0.0;
        double bound_max_x = 0.0;
        double bound_max_y = 0.0;
        double bound_max_z = 0.0;

        axMap1.MaxExtents.GetBounds(out bound_min_x, out bound_min_y, out bound_min_z, out bound_max_x, out bound_max_y, out bound_max_z);
        
        MessageBox.Show("Minimal x: " + bound_min_x.ToString() + "\n" +
                        "Minimal y: " + bound_min_y + "\n" +
                        "Maximum x: " + bound_max_x + "\n" +
                        "Maximum y: " + bound_max_y + "\n");
    }

Testing went well but I found out the numbers where round up. When using the ‘break function’ in Visual studio and checking the local variables it seems there where some more decimal numbers there (so copied those instead of the ones in the message box to make sure I got the right ones).

Having this I can use it for some boundry limit.
I started off with the ExtentsChanged event which only triggered my function on zooming and giving me asap a ‘System.StackOverflowException’ :

axMap1.ExtentsChanged += CheckBoundry; //Event caller/handler in main program.

        private void CheckBoundry(object sender, EventArgs e)
    {
        double x_min = 229254.96613337973;
        double y_min = 5536193.4593695318;
        double x_max = 823447.5919120179;
        double y_max = 6503478.9018099029;

        Extents ext = new Extents();
        ext.SetBounds(x_min, y_min, 0.0, x_max, y_max, 0.0);

        axMap1.Extents = ext;
    }

So I decided to bind if to a MouseUp event which worked better. It sent me back when I clicked outside the given extents, but still not satisfied… Im looking for some way that it will block the panning, asif you hit an invisible wall.

Any ideas on that maybe?

Kind regards,

Remco

Yes, this is where I was cautioning you that changing the Extents within the ExtentsChanged event will cause another ExtentsChanged event, resulting in an infinite loop.

You can use a Boolean flag to indicate that the ExtentsChanged event is the one you’re causing, and return immediately rather than calling the Extents method again.

Try something like:

    private bool bChangingExtents = false;
    private void CheckBoundry(object sender, EventArgs e)
    {
        if (bChangingExtents) return;

        double x_min = 229254.96613337973;
        double y_min = 5536193.4593695318;
        double x_max = 823447.5919120179;
        double y_max = 6503478.9018099029;

        Extents ext = new Extents();
        ext.SetBounds(x_min, y_min, 0.0, x_max, y_max, 0.0);

        // event is fired while changing extents
        bChangingExtents = true;
        axMap1.Extents = ext;
        bChangingExtents = false;
    }

There is more you could also do beyond this, like checking whether or not you are outside of your predefined bounds. What you’re doing here is ALWAYS setting your extents to a predefined box. You should instead check whether or not the users movement has caused you to more outside of your predefined bounds, and only in that case, nudge it back.

Jerry

Hello Jerry,

sorry, i did not give enough taught to that. I taught it was something else was causing the stack error.
I will try some more and tomorrow and meanwhile think about some way to check when the view has moved to the borders instead of checking in which extents i’am. Though one for me, but I will think and try some more :slight_smile:
Thanks for your support!

Kind regards,

Remco

Hello Jerry,

I havn’t been too much around to work at my program but it seems to keep me inside the set boundries. So that works :slight_smile:

I also noticed that I can no longer zoom into the map after implementing this Extents function. I’am able to change layers (so going from countries to provinces view for example) but besides that im not “getting close to the ground” you could say.

I have tried to set Z coordinates in the extent but that doesn’t seem to do anything. I also found: “LockWindow” and set it to “tkLockMode.lmUnlock”, thinking that extents had changed it to that mode. But after placing that in the second last line of the function didn’t work either.

Any idea what might be causing this and, maybe know how to fix this?

Kind regards,

Remco

Hello @Rem97

Please post your most recent CheckBoundary method. The one I last saw would do exactly as you are saying, so that no matter what you do to change the extents (like zoom in) you will always be pushed back out to your predefined maximum extents.

Instead, you want to compare your new extents with your maximum boundaries, and as long as it’s no bigger than your maximum, or outside of your maximum, then you can leave it alone; let it go through.

Dear Jerry,

I have found out that my “axMap1.Extents = ext” was still active. This caused it to stay at one perticiular layer/zoom level.
Been testing a bit and it seems that for some reason my extents, during panning around do, not change. This way my if statements does not detect that the value has change. It only seems to work when im outside the set border and zooming in and out. Is there a way around it or am I doing something wrong?

On that topic, in the if statement I tried to use “Extents.MoveTo()” function to get back at the latest location. The X and Y coordinates of the center are updated after finishing the methode. Any idea or I can do this with the “ZoomTo” function without it changing layers? Cause at the moment the “ZoomToPrev” does work but is not ideal I think.

        private void CheckBoundry(object sender, EventArgs e) //Gets fired on "ExtentsChanged" event.
        {
        if (bChangingExtents) return;

        /*double x_min = 229254.96613337973;
        double y_min = 5536193.4593695318;
        double x_max = 823447.5919120179;
        double y_max = 6503478.9018099029;*/
        

        Extents ext = shapefile5.Extents; //Getting extents of shapefie itself.; //Might move to main program so it will only be executed once instead of on every call.
        var center = (ext as Extents).Center; //Calculates center of map. //To main program.

        //Calculating borders from center point of the shapefile.
        double borderNorth = (center.y);// + 1000000.0);// + ext.yMax) * 1.1; //Test: added multiplication so the border is not directly 'touching' the top shape.
        double borderSouth = (center.y - 1000000.0);// ext.yMin);
        double borderWest = (center.y - 1000000.0);//ext.xMin);
        double borderEast = (center.y + 1000000.0);//ext.xMax);

        ext.SetBounds(borderNorth, borderSouth, 0.0, borderWest, borderEast, 0.0);
        
        bChangingExtents = true;
        
        //axMap1.ExtentHistory = 1;

        ///axMap1.Extents = ext;

        if (axMap1.Extents.yMax > borderNorth)
        {
            axMap1.ZoomToPrev();
        }
        if (axMap1.Extents.yMin > borderSouth)
        {
            ///axMap1.ZoomToPrev();
        }
        if (axMap1.Extents.xMin > borderWest)
        {
            ///axMap1.ZoomToPrev();
        }
        if (axMap1.Extents.xMax > borderEast)
        {
            ///axMap1.ZoomToPrev();
        }
        
        bChangingExtents = false;
        last_known_ext_x = axMap1.Extents.Center.x; //Updating the last known extents after successfully executing the program.
        last_known_ext_y = axMap1.Extents.Center.y;


    }

Please let me know your taughts. Been coding +3 hours, so im probably missing something obvious :slight_smile:

Kind regards,

Remco

Hello @jerryfaus

did you maybe had some time to look over my two problems?
Especially the first one bothers me atm. Where the program does not detect that I moved out of my set borders, only when zooming. I did fildle around and let an event fire on Extents_Changed, but neither this was the solution.

Let me know your thoughts.

Kind regards,

Remco

Hello @Rem97

I haven’t forgotten about this; I’ve just had some project deadlines that have kept me occupied. I’d like to write up an algorithm for you based on work I’ve done in the past. I’ll try to do that today.

Regards,
Jerry.

Hello @jerryfaust,

thank you for your quick reply.
No problem, take your time. I will meanwhile fidle around with the extents or work on some of my other points which I still got to implement or get working ^^

Kind regards,

Remco

Hello again.

Ok, it’s in VB.NET, so you’ll have to translate, but here’s what I think works.

Private Sub ButtonLoad_Click(sender As Object, e As EventArgs) Handles ButtonLoad.Click
    Dim dlg As New OpenFileDialog()
    dlg.DefaultExt = "shp"
    If dlg.ShowDialog() = DialogResult.OK Then
        Dim fullpath As String = dlg.FileName
        AxMap1.AddLayerFromFilename(fullpath, tkFileOpenStrategy.fosAutoDetect, True)
    End If
    ' save maximum extents
    maxExtents = AxMap1.Extents
End Sub

Private bChangingExtents As Boolean = False
Private maxExtents As Extents = Nothing
Private Sub AxMap1_ExtentsChanged(sender As Object, e As EventArgs) Handles AxMap1.ExtentsChanged
    If bChangingExtents Then Return

    ' new extents to set (overriding current extents)
    Dim newExtents As New Extents()
    Dim needsUpdate As Boolean = False

    If maxExtents IsNot Nothing Then
        ' get the current extents
        Dim currentExtents As Extents = AxMap1.Extents
        ' if zoomed out beyond max extents (allowing for error), put it back
        If CInt(currentExtents.Width) > CInt(maxExtents.Width) OrElse CInt(currentExtents.Height) > CInt(maxExtents.Height) Then
            newExtents = maxExtents
            needsUpdate = True
        Else
            ' if anywhere within the max extents, we only have to worry about panning outside of max extents
            With currentExtents
                ' initialize to current extents
                newExtents.SetBounds(.xMin, .yMin, 0, .xMax, .yMax, 0)
            End With

            ' if panned to the right
            If currentExtents.xMin < maxExtents.xMin Then
                ' take current extents and push it to the left, but maintain width
                newExtents.SetBounds(maxExtents.xMin, newExtents.yMin, 0, maxExtents.xMin + newExtents.Width, newExtents.yMax, 0)
                needsUpdate = True
            End If
            ' if panned to the left
            If currentExtents.xMax > maxExtents.xMax Then
                ' take current extents and push it to the right, but maintain width
                newExtents.SetBounds(maxExtents.xMax - newExtents.Width, currentExtents.yMin, 0, maxExtents.xMax, newExtents.yMax, 0)
                needsUpdate = True
            End If
            ' if panned up
            If currentExtents.yMin < maxExtents.yMin Then
                ' take current extents and push it back down, but maintain height
                newExtents.SetBounds(newExtents.xMin, maxExtents.yMin, 0, newExtents.xMax, newExtents.yMin + newExtents.Height, 0)
                needsUpdate = True
            End If
            ' if panned down
            If currentExtents.yMax > maxExtents.yMax Then
                ' take current extents and push it back up, but maintain height
                newExtents.SetBounds(newExtents.xMin, maxExtents.yMax - newExtents.Height, 0, newExtents.xMax, maxExtents.yMax, 0)
                needsUpdate = True
            End If
        End If

        ' do we need to update the extents?
        If needsUpdate Then
            bChangingExtents = True
            AxMap1.Extents = newExtents
            bChangingExtents = False
        End If
    End If
End Sub
  1. I start by loading a layer (in ButtonLoad_Click) and set my max extents based on that layer. You can set your max extents as you see fit for your application.
  2. In the ExtentsChanged event, I first check for being zoomed out too much, and if so, bring it back to the max extents. If it’s zoomed in, then we never have to change the zoom - we will just maintain the current width and height, and only adjust the position if they pan outside of the max extents.
  3. I start by copying the current map extents to my so-called newExtents. This creates my initial viewport.
  4. I then check for each of the 4 conditions, panning left, right, up, and down. If they have moved the view outside of the max extents, we adjust the position, maintaining the width and height, but making sure the view stays inside of the max extents.

The formatting of the code above seems to get confused by the VB comment character ’ (single quote). You can just translate this into your c# code, and it should work for you.

Let me know how it goes.

Regards,
Jerry.

@jerryfaust

Thanks a lot! I tried to get it working but still got to figure some VB to C# coding.
Will get back with the progress/results of it :slight_smile:

Kind regards,

Remco

Hello @jerryfaust,

I have tried to recreate the methode which you showed in your last post. One thing I did not get fully was the “With” function and how to translate it to C#. As far as I see it fills the variables with “currentExtents”, correct me if im wrong.
Further more I cannot get near the result as we would like to see. It does still not trigger when panning around, only at zooming (as mentioned before).
Any idea about it?:

//Somewere in main program:
axMap1.ExtentsChanged += CheckBoundry; //Triggers when extents change.


//The "CheckBoundry" methode:
private void CheckBoundry(object sender, EventArgs e)
    {
        Extents maxExtents = shapefile5.Extents; //Maximum extents of biggest shapefile. Has to become +10% of current extents.

        if (bChangingExtents) return;

        Extents newExtents = new Extents(); //Current viewpoint.
        bool needsUpdate = false;

        Extents currentExtents = axMap1.Extents;

        if(currentExtents.Width > maxExtents.Width || currentExtents.Height > maxExtents.Height)
        {
            newExtents = maxExtents;
            needsUpdate = true;
        }
        
            newExtents.SetBounds(currentExtents.xMin, currentExtents.yMin, 0, currentExtents.xMax, currentExtents.yMax, 0); // If inside maxExtents then newExtents = currentExtents


        if (currentExtents.xMin > maxExtents.xMin) //If panned right/east
        {
            newExtents.SetBounds(maxExtents.xMin, maxExtents.yMin, 0, maxExtents.xMin + newExtents.Width, newExtents.yMax, 0);
            needsUpdate = true;
        }

        if (currentExtents.xMax > maxExtents.xMax) //If panned left/west
        {
            newExtents.SetBounds(maxExtents.xMax - newExtents.Width, currentExtents.yMin, 0, maxExtents.yMax, newExtents.yMax, 0);
            needsUpdate = true;
        }

        if (currentExtents.yMin > maxExtents.yMin) //If panned up/north
        {
            newExtents.SetBounds(newExtents.xMin, maxExtents.yMin, 0, newExtents.xMax, newExtents.yMin + maxExtents.Height, 0);
            needsUpdate = true;
        }

        if (currentExtents.yMax > maxExtents.yMax) //If panned down/south
        {
            newExtents.SetBounds(newExtents.xMin, maxExtents.yMax - newExtents.Height, 0, newExtents.xMax, maxExtents.yMax, 0);
            needsUpdate = true;
        }


        if (needsUpdate == true) //Update current extents
        {
            bChangingExtents = true;
            axMap1.Extents = newExtents;
            bChangingExtents = false;
        }


    }

Kind regards,

Remco

Hello @Rem97

I’ve made just a few modifications to your code (and my original code needed a couple fixes as well).
Here’s my c# version (untested):

//Somewere in main program:
axMap1.ExtentsChanged += CheckBoundry; //Triggers when extents change.

//The "CheckBoundry" methode:
private void CheckBoundry(object sender, EventArgs e)
{
    if (bChangingExtents) return;

    Extents maxExtents = shapefile5.Extents; //Maximum extents of biggest shapefile. Has to become +10% of current extents.

    Extents newExtents = new Extents(); //Current viewpoint.
    bool needsUpdate = false;

    Extents currentExtents = axMap1.Extents;

    if(currentExtents.Width > maxExtents.Width || currentExtents.Height > maxExtents.Height)
    {
        newExtents = maxExtents;
        needsUpdate = true;
    }
    else
    {
        newExtents.SetBounds(currentExtents.xMin, currentExtents.yMin, 0, currentExtents.xMax, currentExtents.yMax, 0); // If inside maxExtents then newExtents = currentExtents

        if (currentExtents.xMin < maxExtents.xMin) //If panned right/east
        {
            newExtents.SetBounds(maxExtents.xMin, newExtents.yMin, 0, maxExtents.xMin + newExtents.Width, newExtents.yMax, 0);
            needsUpdate = true;
        }

        if (currentExtents.xMax > maxExtents.xMax) //If panned left/west
        {
            newExtents.SetBounds(maxExtents.xMax - newExtents.Width, newExtents.yMin, 0, maxExtents.xMax, newExtents.yMax, 0);
            needsUpdate = true;
        }

        if (currentExtents.yMin < maxExtents.yMin) //If panned up/north
        {
            newExtents.SetBounds(newExtents.xMin, maxExtents.yMin, 0, newExtents.xMax, maxExtents.yMin + newExtents.Height, 0);
            needsUpdate = true;
        }

        if (currentExtents.yMax > maxExtents.yMax) //If panned down/south
        {
            newExtents.SetBounds(newExtents.xMin, maxExtents.yMax - newExtents.Height, 0, newExtents.xMax, maxExtents.yMax, 0);
            needsUpdate = true;
        }
    }

    if (needsUpdate == true) //Update current extents
    {
        bChangingExtents = true;
        axMap1.Extents = newExtents;
        bChangingExtents = false;
    }
}

As an aside, I don’t know why you wouldn’t be getting this event when panning. It should happen just as with a zoom.

Let me know how this goes.
Jerry.

Follow-up:

Every scenario I have tried regarding the settings, I always get the ExtentsChanged event when panning.

Are you using the standard setting?

axMap1.CursorMode = tkCursorMode.cmPan;

Jerry.

Hello @jerryfaust ,

thank you again. I got a little further this time :slight_smile:
It seems I can now zoom at times, but when near/outsite the extents on the last or second last layer it does no longer work on the scroll wheel. Only on double click, where I bound a “zoom-in function” into which zooms 1 level in.
About the “ExtentsChanged do not trigger on panning” topic I have been thinking a little and taught it might be a setting. But when checking I saw nothing was really off/wrong.
Made some printscreen:
Printscreen link

No, I do not use cmPan. At least not directly. This so I would be able to select icons at the map with another curser mode. Will test cmPan this evening.

Kind regards,

Remco

Hello @jerryfaust,

little update, it seems indeed to be the cmPanm setting which needs to be active. I now call the “CheckBoundry” on the end of my cursor methode. So, besides zooming, it will check on every pan now :slight_smile:

About the problem of zooming at the top levels I changed the OR function to AND:

if(currentExtents.Width > maxExtents.Width || currentExtents.Height > maxExtents.Height)

I think the program got a little ‘confused’ and locked at the higher level by a loophole since it taught it kept going out of the right extents and pulled it back.

I think so far all works well :slight_smile:
Just want somehow increase the maximum extents with like 10%, but will figure I guess.

Thanks a lot Jerry, it helped me a lot to get in the right direction.

Kind regards,

Remco