Move Point on a Map

Jan 26, 2011 at 3:56 AM

Hi everybody,

I am a brand new user of Dot Spatial and I am a bit stuck in my project.

To put it in a nutshell, I would like to display, on a world map, GPS points and then manually move them (by drag-and-dropping them on the world map) in order to put them at the right place, and so get the right coordinates.

 

Right now I have my first layer with .shp representing the part of the world I am interested in and I have another layer with my points.

//---Add the background----            

FeatureSet MyFeatureSet = new FeatureSet();               

MyFeatureSet.Open("C:\\_dev\\VmsComparator\\countries_pac.shp");            

MyFeatureSet.Projection = KnownCoordinateSystems.Geographic.World.WGS1984;            

map1.Layers.Add(MyFeatureSet);

//---Add the points        

ArrayList LogsheetTripPoints = Util.SqlToArrayList(MyConnection, query);      //to get the coordinates from my database      

MyFeatureSet = new FeatureSet(FeatureType.Point);            

MyFeatureSet.Projection = KnownCoordinateSystems.Geographic.World.WGS1984;            

for (int i = 0; i < LogsheetTripPoints.Count; i++)          

{                

DotSpatial.Topology.Point p = new DotSpatial.Topology.Point();                

p.X = (double)(((ArrayList)LogsheetTripPoints[i])[1]);                

p.Y = (double)((ArrayList)LogsheetTripPoints[i])[0];                

MyFeatureSet.AddFeature(p);            

}            

MyFeatureSet.Name = "Trip Logsheet";            

IMapPointLayer MyMapPointLayer= map1.Layers.Add(MyFeatureSet) as IMapPointLayer;

 

I have been looking around the forum and I don't really know where to look. I am not asking for a full solution, but if you could put me in the right direction, you would make my day :p

Thanks for your help

Bruno

Developer
Jan 26, 2011 at 4:15 PM

You can use events on the Map itself to track the mouse-down event.  You can take advantage of the Map's PixelToProj and ProjToPixel functions to translate between client pixel coordinates and world coordinates.  When clicking on the map, it's usually a good idea to expand the X, Y location by the radius of your point symbol.  So if you are using something with a point size of 10, you should be testing for intersection with anything that is +/- 5 in X or Y from the place clicked.  If you design a Rectangular bounds, you can use PixelToProj to get a geographic envelope, and then use the envelope to test each of your geographic coordinates.  If you find a coordinates.  If you find a point inside the event, keep track of the index of that point.  Once you have identified the point index, I recommend not removing it from your point dataset yet, or even changing it's location.  What you do instead, is use a class level boolean variable to indicate that you are dragging a point like "_dragging", a System.Drawing.Point like "_previousLocation", and "_currentLocation" which both start out as the original point location. 

You can handle the MouseMove event.  In that event, if "_dragging" is true, then you can update the _previousLocation to be the "_currentLocation" and update the _currentLocation to be the e.Location on your move event args.  Then you invalidate the rectangular area containing both locations, extended to account for the pixel radius of the symbols.  Invalidating a rectangle ultimately fires the paint event.  You have the ability to intercept the "Paint" event on the map.  In that event, you are handed a Graphics object.  In the Paint event handler, draw the same symbol that your point set us using, but in the _currentlocation occupied by the mouse.  You can draw a different symbol on top of the old symbol that helps remind users which symbol is being moved, but this doesn't have to be re-drawn each time the mouse moves.  Finally, on the mouse-up event, you can use PixelToProj again, but this time, you use it with a System.Drawing.Point to get a Coordinate.  Then use the coordinate to update the point with the index you identified in the mouse down event with the new X and Y positions.  Finally, since you have modified the vertices, you will probably have to call myFeatureSet.InitializeVertices (or myFeatureSet.InvalidateVertices I can't remember which), call Map.MapFrame.Invalidate() in order to update the buffer image, and finally Map.Invalidate() to actually redraw the buffer to the map control.

Anyway, hopefully this is what you were asking.

Ted

 

Jan 26, 2011 at 10:07 PM

Hi Ted,

thanks for your quick reply, I will work on it and share the code if I find a solution

Bruno

Jan 27, 2011 at 3:27 AM

Ted,

I am stuck at this step of your explanation :  If you find a point inside the event, keep track of the index of that point.

I Have created a method to catch the event raised by the MapPointLayer.SelectionChanged.

I only want to work with one point selected, that's why I use an if.

I tried with both IndexSelection and Selection but in both cases the feature index ends ut to be -1.

I checked on the forum and found this topic, but I don't really understand how you solved the issue ...

Any lead ?

The call of the event handler : 

MyMapPointLayer.SelectionChanged += new System.EventHandler(SelectionModified);

The EventHandler : 

private void SelectionModified(object sender, EventArgs e)        

{            

IMapPointLayer MyMapPointLayer = (IMapPointLayer) sender;            

IndexSelection MySelection = (IndexSelection)MyMapPointLayer.Selection;

        if (MySelection.Count == 1)            

{                

IFeature MyFeature = MySelection.ToFeatureList()[0];                

int MyFeatureIndex=MyMapPointLayer.DataSet.Features.IndexOf(MyFeature);                

MessageBox.Show(MyFeatureIndex.ToString());            

}            

else            

MyMapPointLayer.UnSelectAll();                                    

}    

Thanks

Bruno

 

Developer
Jan 27, 2011 at 3:33 PM

So I have a question.  If you are using the Selection behavior for the map, how will that behavior interfere with what you want to do with regards to moving the point?  It's something I didn't think about when I posted my previous advice, but basically you probably shouldn't be trying to use the mouse like this for moving simultaneously with the FunctionSelect MapFunction active at the same time.  You can change the Map.FunctionMode to none and then handle the entire behavior using the event handlers like we discussed.

I wasn't thinking about you using the Selection (Index or otherwise).  I was assuming you would cycle through either the features in myFeatureSet.Features, or else cycle through the Vertices array itself (it is organized like X1, Y1, X2, Y2) until you find an Xn, Yn pair that is contained by the envelope.  Then the index is simply tracked by you and incremented as you move through the vertices.  The vertex method will be notably faster than working with Features unless you have built the featureset from scratch by adding features to the feature list, and even then the vertex method will still be as fast.

Another technique for doing this is that if you are essentially modifying the way the "Selection" tool works.  You can start with the source code for the FunctionSelect MapFunction.  This has a combination of mouse event handling methods and a draw method that accomplish exactly what you need.  The benefit if creating your own MapFunction is that MapFunctions automatically handle disabling other competing functions that would interfere with your function, so you don't have the map trying to do two things at once.  You could start with the FunctionSelect and then tweak it with the behavior I described in my earlier post so that once you have a selection, you can set a flag so that your function goes into the "move" mode for the tool.

The Features.IndexOf(myFeature) only works if you are not in index mode.  Now, the first time you access the Features list it will put you you in edit mode instead of index mode, but before that, if you loaded your points from a shapefile or something, you are probably in index mode, which is faster and takes up less memory.  But the trade off is you can't easily go re-arranging points while in index mode.  So edit mode allows you to use the Features list.  If you got a feature from the Selection while in index mode, the feature was created on the fly, and is not a part of any existing list.  Then when you access the "Features" property, the first access generates the list.  So the feature created on the fly might not be in that list.  You can fix that by simply doing something like int count = myFeatureSet.Features.Count; first.  This forces the creation of the Features list so that it would behave more or less as you expect as far as using IndexOf.  That's the only reason I could think of off the top of my head why the IndexOf wouldn't work correctly.

Good luck,

Ted

 

Feb 1, 2011 at 9:25 PM

Hi Ted,

I started from scratch without using at all the select mode ... and now everything works like a charm. Actually I am not modifying the point layer with the new coordinates but I'm dropping it and re-create it with the new positions (I had to because somehow I was losing the Datarow of my points containing their names.)

The only problem I could not solve is to display the symbol of the point during the dragging. I just could not find how to modify the graphic object received in the paint event of my map. But I found a workaround and I'm actually drawing a rectangle of the same size of my symbol.

If I have some time I will try to re-do it following your second idea of modifying the select tool.

Anyway thanks for your help !

Bruno

//move to custom select mode
        private void button1_Click(object sender, EventArgs e)
        {
            SelectMode = true;
            ((ToolStripButton)spatialToolStrip1.Items[0]).Checked = false;
            ((ToolStripButton)spatialToolStrip1.Items[1]).Checked = false;
            ((ToolStripButton)spatialToolStrip1.Items[2]).Checked = false;
            map1.FunctionMode = FunctionMode.None;
        }

        private void map1_MouseDown(object sender, MouseEventArgs e)
        {
            if (SelectMode&&!Dragging)
            {
                double MyDouble, dx, dy;

                System.Drawing.Point MyClientPoint = new System.Drawing.Point(e.X, e.Y);
                Coordinate MyCoordinate = map1.PixelToProj(MyClientPoint);
                //All the points are stored as a member list of the class
                for (int i = 0; i < LogsheetPoints.Count; i++)
                {
                    Coordinate Temp = LogsheetPoints[i].Coordinate;
                    dx = Temp.X - MyCoordinate.X;
                    dy = Temp.Y - MyCoordinate.Y;
                    MyDouble = Math.Sqrt((dx * dx) + (dy * dy));
                    if (MyDouble < 0.1)
                    {
                        this.PointId = i;
                        this.CurrentLocation = map1.ProjToPixel(Temp);
                        this.PreviousLocation = map1.ProjToPixel(Temp);
                        this.Dragging = true;
                        break;
                    }
                }
            }
        }

        private void map1_MouseMove(object sender, MouseEventArgs e)
        {
            System.Drawing.Point MyPoint = new System.Drawing.Point(e.X, e.Y);
            latitude.Text = map1.PixelToProj(MyPoint).X.ToString();
            if (SelectMode && Dragging)
            {
                PreviousLocation.X = CurrentLocation.X;
                PreviousLocation.Y = CurrentLocation.Y;
                CurrentLocation.X = e.X;
                CurrentLocation.Y = e.Y;
                
                map1.Invalidate();
            }
        }

        private void map1_Paint(object sender, PaintEventArgs e)
        {
            if (SelectMode && Dragging)
            {
                Pen MyPen = new Pen(Color.Black);
                //Here, I draw a rectangle, I don't now how to draw the point symbol
                Rectangle MyRectangle = new Rectangle(CurrentLocation.X-5,CurrentLocation.Y-5,5,5);
                Graphics MyGraphic = e.Graphics;
                MyGraphic.DrawRectangle(MyPen,MyRectangle);
            }
        }

        
        private void map1_MouseUp(object sender, MouseEventArgs e)
        {
            if (SelectMode && Dragging)
            {
                //TODO : test if the point is not inland
                //TODO : test if the point is not too far from previous and next position
                
                System.Drawing.Point MyScreenPoint = new System.Drawing.Point(e.X,e.Y);
                LogsheetPoints.ElementAt(PointId).X = map1.PixelToProj(MyScreenPoint).X;
                LogsheetPoints.ElementAt(PointId).Y = map1.PixelToProj(MyScreenPoint).Y;
                DropLayer("Trip Logsheet");
                CreateLogsheetPointLayer();
                Dragging = false;
                this.CurrentLocation = new System.Drawing.Point();
                this.PreviousLocation = new System.Drawing.Point();     
            }

        }

Sep 13, 2011 at 9:41 AM

After you have moved the point to the new place,can you select it on the new place? I just know that it can be selected on the original place!