ThinkGeo.com    |     Documentation    |     Premium Support

Moving PointShapes using mouse

I suspect this has been covered, but searching the forum is too slow to dig through, and the terms I come up with are too non-specific to produce good filtered results.



Anyway, I have a largish number of PointShapes (potentially thousands).  These are grouped by rendering requirements among a collection of InMemoryFeatureLayer instances.  These layers in turn are housed in in Overlays that group based on domain specific “Type” of object (think Cars, People, Equipment, etc;, grouping things that are similar and edited/used together).



Now, all this is working just fine so far.  But I have a requirement that the user must be able to “edit” (add/remove/move) objects within a given Overlay.  It seems the Add and Remove are easy enough (though still yet to do).  



But “move” is another issue.  What we want to do is enable “moving” by type/overlay.  For instance, when in “Car” edit mode, you can move any points in any layer within the “Cars” overlay, but not People (etc).  And while in this mode, to move any PointShape of the indicated type, all you should have to do is click on it and drag to desired location.  Ideally this would look like a drag/drop operation where the moving point would follow the mouse cursor.  And other than our own modal feedback (external to the map), the map itself wouldn’t look any different when moving objects is enabled, though other “type” overlays may be hidden at that time to reduce potential confusion.



I have a feeling there is an easier way than reloading everything in TrackInteractiveOverlay and trying to keep things straight.  



Can you please point me to a sample that will provide proper direction? 

Hi Russ, 
  
 I think your operation should be looks like this: 
  
 1. User select type overlay (for example “cars”) 
 2. User click on source point (for example “a green car”) 
 3. User drag the point and drop it on target position 
 4. The source point updated to the target position 
  
 Because InmemoryFeatureLayer only work for render shapes, and our EditOverlay is work for edit shapes, so you can implement that like this: 
 For 1. You can hide other overlays if you want here 
 For 2. Loop all layers in selected overlay, user QueryTools in layer to get what point you are clicked 
  
 For 3. 4. 
    a. Remove this point from source layer, add it to winformsMap1.EditOverlay.EditShapesLayer 
    b. User can move the point to any point and you can put the new pointshape back to original layer, as below is the sample code for that 
      

 winformsMap1.EditOverlay.EditShapesLayer.InternalFeatures.Add(new Feature(1, 1));
            winformsMap1.EditOverlay.CanResize = false;
            winformsMap1.EditOverlay.CanRotate = false;
            winformsMap1.EditOverlay.CanReshape = false;
            winformsMap1.EditOverlay.CanRemoveVertex = false;
            winformsMap1.EditOverlay.CanAddVertex = false;

            winformsMap1.EditOverlay.CanDrag = true;
            winformsMap1.EditOverlay.CalculateAllControlPoints();

            winformsMap1.EditOverlay.FeatureDragged += new EventHandler<FeatureDraggedEditInteractiveOverlayEventArgs>(EditOverlay_FeatureDragged);            

 
  
 Wish that’s helpful. 
  
 Regards, 
  
 Don

I had thought it would be necessary to add all “Cars” (from all layers of CarsOverlay) to the EditOverlay when we enter the “edit mode”.  I thought this would be necessary to ensure smooth user operations.  For instance, “user clicks arbitrary “car” point, drags, releases, point is moved to new location”.  And they move on one after another for 100 or more, so I want this to be smooth and natural for them.  But it seems that will be problematic since they (desired set of cars to move) may exist in (many) different layers with specific styles.   
  
 And I’m concerned (haven’t tested yet) that your suggested approach will yield an extra undesired step.  For instance, “user clicks car, car becomes “enabled for moving” (i.e. moved to EditOverlay), user must now click a second time, drag, and release, point is in a new location”.  That extra “click to select” step added to each point moved would get old fast, making a tedious job far worse. 
  
 If the 2-click initiation can be avoided, I’m all for the simple one point at a time in the EditOverlay approach, so I’ll give it a try later today.  But if not, then I’m back to a real mess because the EditOverlay does not support multiple layers.  And other than complex value styles used to pack/unpack (flatten) all the disparate layers into the one EditOverlay layer, I don’t see a good path in that scenario.

The obvious implementation using MapClick as a trigger seems to work just like I was afraid it would.  User clicks a point and when they release the button the event fires.  And the point gets added to the EditOverlay. And then the user has to click the new manipulation handle to drag it somewhere else.  I could move the code to the winform map MouseDown event, and then convert screen to world, but I don’t see how that would help when the EditOverlay is relying on it’s own MouseDown event to start the drag operation. 
  
 Again, the goal is that when we are in “car move mode”, the user can simply click and drag any one of potentially thousands of cars (not all visible at once of course, depending on extent/scale), with many different styles and so tracked in many different layers. One click, then drag.  Not click to select, then drag.  And clicking things that are not part of that move mode (in this case things that are not cars) are still locked in place until the user configures for moving other things by type. 
  
 If the Edit Overlay was an actual overlay that supported Layers, this would be fairly straight forward, and I probably would never have started this thread.  But given what is provided, I don’t see how to make this work in the simple and efficient manner desired.

Hi Russ, 
  
 Sorry I think I don’t have better idea about that, at first I think we can dynamic add Multiply EditInteractiveOverlay, then set different styles for that, but I did a quickly test and found if we have multiply EditInteractiveOverlay, we cannot drag any feature. 
  
 So I think our exsiting layers won’t be helpful for your requirement, what you want to do is build custom layer. It should works like this, any shape in this layer will follow mouse, then when MapClick you put target car in it so when mouse move it looks like “drag”, when mouse release, put the shape back, you can also try to set dynamic style so make the car keep its style in original layer. 
  
 Wish this thinking is helpful. 
  
 Regards, 
  
 Don

I had come to something of the same conclusion.  Thank you for helping.

There is something I’m missing.  I keep getting ‘System.Collections.Generic.KeyNotFoundException’ after move operations.



The edit overlay always works the same basic way all the time, so I run the following once at initialization.


private void ConfigureEditOverlay( )
{
    ThinkGeoMap.EditOverlay.CanResize = false;
    ThinkGeoMap.EditOverlay.CanRotate = false;
    ThinkGeoMap.EditOverlay.CanReshape = false;
    ThinkGeoMap.EditOverlay.CanRemoveVertex = false;
    ThinkGeoMap.EditOverlay.CanAddVertex = false; 
    ThinkGeoMap.EditOverlay.CanDrag = true;
 
    ThinkGeoMap.EditOverlay.FeatureDragged += EditOverlay_FeatureDragged;
}



When in move mode, I find the closes object to the click location within a suitable “close enough” buffer.  This is the “activeFeature”.  If there is such an object close enough, then I do the following.




movedFeature = activeFeature.CloneDeep( );
 
activeLayer.Open( );
activeLayer.FeatureSource.BeginTransaction( );
activeLayer.FeatureSource.DeleteFeature( activeFeature.Id );
activeLayer.FeatureSource.CommitTransaction( );
activeLayer.Close( );
 
ThinkGeoMap.EditOverlay.EditShapesLayer.InternalFeatures.Add( movedFeature );
ThinkGeoMap.EditOverlay.EditShapesLayer.ZoomLevelSet = activeLayer.ZoomLevelSet;
ThinkGeoMap.EditOverlay.CalculateAllControlPoints( );
 
ThinkGeoMap.Refresh( activeOverlay );



This seems to work, and the user can then click the cross arrows to drag the point around as desired.  When he releases to “drop” the point, my event handler as follows runs.  However, if I run that commented out refresh, I get the


activeFeature = e.DraggedFeature.CloneDeep( );
 
activeLayer.Open( );
activeLayer.FeatureSource.BeginTransaction( );
activeLayer.FeatureSource.AddFeature( activeFeature );
activeLayer.FeatureSource.CommitTransaction( );
activeLayer.Close( );
         
ThinkGeoMap.EditOverlay.EditShapesLayer.InternalFeatures.Clear(  );
 
ThinkGeoMap.Refresh( activeOverlay );



But if I then continue working with the map, particularly if I try again to move the point already moved, things don’t work.  The newly moved point won’t be found even though I see it and click directly on it.  And the EditOverlay won’t work again even when I click on other acceptable objects.  My code is finding them and adding them to the overlay, but it doesn’t display the cross arrows, or let me drag points around.  And eventually I get the missing key exception, right away if I leave the Refresh lines not commented out.  And I added the cloning of features, thinking it might be some sort of ownership problem bouncing between layers/overlays, but that didn’t change anything.



What am I missing?

Oh, I’m trying to use your initial suggestion in order to get past a demo where edit features are supposed to be functional.  I already know the feedback will be to pursue the more fluid solution, but I thought this would be an easy “at least the feature works” intermediate step.  And I’m sure it is, if I knew better how to do this.

No suggestions on what might be causing the exception?  No instructions on how to do this so that the WinForm map works as intended?

Hi Russ, 
  
 Sorry I haven’t noticed your reply at Friday, it looks you are using ShapeFileFeatureLayer but not InmemoryFeatuerLayer? 
  
 I think you get this exception should because when you delete the point shape and add it back, you haven’t rebuild index. 
  
 Could you please try to rebuild index each time you modify the data? 
  
 Regards, 
  
 Don

Not sure why it would appear I’m using ShapeFileFeatureLayer here.  I am using them, but not here.  This code is completely dependent on multiple InMemoryFeatureLayer instances.  Which means it couldn’t be the index.   
  
 Is there a sample that demonstrates simply taking features from IMFL, putting it in for Edit move, and then putting it back in IMFL when done?  I’m sure it’s just something minor I’m overlooking.

Hi Russ, 
  
 I did a small sample, and found even I don’t build index it won’t throw exception, please view my sample as below and see where is the different. 
  
 using System;
using System.Windows.Forms;
using ThinkGeo.MapSuite.Core;
using ThinkGeo.MapSuite.DesktopEdition;


namespace CSharpWinformsSamples
{
    public class DisplayShapeMap : UserControl
    {
        public DisplayShapeMap()
        {
            InitializeComponent();
        }

        private void DisplayMap_Load(object sender, EventArgs e)
        {
            winformsMap1.MapUnit = GeographyUnit.DecimalDegree;

            InMemoryFeatureLayer layer = new InMemoryFeatureLayer();
            layer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle = PointStyles.City1;
            layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
            layer.InternalFeatures.Add(new Feature(0, 0));


            LayerOverlay overlay = new LayerOverlay();
            overlay.Layers.Add(layer);

            winformsMap1.CenterAt(new Feature(0, 0));

            winformsMap1.Overlays.Add(overlay);

            winformsMap1.Refresh();
        }

        private WinformsMap winformsMap1;
        private Button button1;
        private Button button2;

        #region Component Designer generated code

        private System.ComponentModel.IContainer components = null;

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name=“disposing”>true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.winformsMap1 = new ThinkGeo.MapSuite.DesktopEdition.WinformsMap();
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // winformsMap1
            // 
            this.winformsMap1.BackColor = System.Drawing.Color.Gray;
            this.winformsMap1.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.Default;
            this.winformsMap1.CurrentScale = 590591790D;
            this.winformsMap1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.winformsMap1.DrawingQuality = ThinkGeo.MapSuite.Core.DrawingQuality.Default;
            this.winformsMap1.Location = new System.Drawing.Point(0, 0);
            this.winformsMap1.MapFocusMode = ThinkGeo.MapSuite.DesktopEdition.MapFocusMode.Default;
            this.winformsMap1.MapResizeMode = ThinkGeo.MapSuite.Core.MapResizeMode.PreserveScale;
            this.winformsMap1.MapUnit = ThinkGeo.MapSuite.Core.GeographyUnit.DecimalDegree;
            this.winformsMap1.MaximumScale = 80000000000000D;
            this.winformsMap1.MinimumScale = 200D;
            this.winformsMap1.Name = “winformsMap1”;
            this.winformsMap1.Size = new System.Drawing.Size(740, 528);
            this.winformsMap1.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default;
            this.winformsMap1.TabIndex = 0;
            this.winformsMap1.Text = “winformsMap1”;
            this.winformsMap1.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            this.winformsMap1.ThreadingMode = ThinkGeo.MapSuite.DesktopEdition.MapThreadingMode.Default;
            this.winformsMap1.ZoomLevelSnapping = ThinkGeo.MapSuite.DesktopEdition.ZoomLevelSnappingMode.Default;
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(9, 19);
            this.button1.Name = “button1”;
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 1;
            this.button1.Text = “Take to edit”;
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(9, 48);
            this.button2.Name = “button2”;
            this.button2.Size = new System.Drawing.Size(75, 23);
            this.button2.TabIndex = 2;
            this.button2.Text = “Save back”;
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // 
            // DisplayShapeMap
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.winformsMap1);
            this.Name = “DisplayShapeMap”;
            this.Size = new System.Drawing.Size(740, 528);
            this.Load += new System.EventHandler(this.DisplayMap_Load);
            this.ResumeLayout(false);

        }

        #endregion

        private void button1_Click(object sender, EventArgs e)
        {
            InMemoryFeatureLayer inMemoryFeatureLayer = (winformsMap1.Overlays[0] as LayerOverlay).Layers[0] as InMemoryFeatureLayer;

            winformsMap1.EditOverlay.EditShapesLayer.InternalFeatures.Add(inMemoryFeatureLayer.InternalFeatures[0]);
            inMemoryFeatureLayer.Clear();

            winformsMap1.Refresh();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            InMemoryFeatureLayer inMemoryFeatureLayer = (winformsMap1.Overlays[0] as LayerOverlay).Layers[0] as InMemoryFeatureLayer;

            inMemoryFeatureLayer.InternalFeatures.Add(winformsMap1.EditOverlay.EditShapesLayer.InternalFeatures[0]);
            winformsMap1.EditOverlay.EditShapesLayer.Clear();
            winformsMap1.Refresh();
        }
    }
}
 
 
  
 Regards, 
  
 Don

Thanks for your response. 
  
 As far as I know, index does not apply to IMFL, right?   
  
 Anyway, it’s very late here, but a quick scan looks like a simplified version of what I did.  Obviously I have to deal with mouse hit test, and I can’t just “clear” the IMFL, but I don’t see anything that looks significant.  I suppose the next step is to create another prototype similar to yours, perhaps evens starting with yours and building up to something that either works the way I need, or demonstrates the issue I’m having.  I should have time to investigate further soon, though I may just punt on this and wait for the proper time for a solution more to our actual requirements.  But figuring out why something that seems so easy doesn’t work for me has me curious now…

I just realized I had completely misunderstood what I thought I was seeing.  It didn’t seem I could delete the post, so I edited it with this instead.

Hi Russ,






As you requested in the previous reply, here is a sample that using ShapeFile as source layer, hand hit test to edit a clicked points. Then save it back. In the sample, Using “FeatureIdsToExclude” property is better than clear or remove method from the source layer.



Thanks,
Howard





Actually, it was the opposite.  I have no current requirement for the user manipulating a shape file.  However, I very much appreciate the sample you provided, it may prove useful in the future.  Thank you. 
  
 Regarding the previous ongoing thread about moving features of the IMFL using the mouse, I took the information provided and created a function complete prototype that worked (multiple features in IMFL, mouse selection and move, using a feature source instead of layer directly, etc) that worked properly.  It wasn’t much different from what I had been doing, and I didn’t have time to figure out the exact previous problem, but porting the working prototype into our product got the move feature working. Unfortunately the time for that interim demo has passed, but I hated to leave that step incomplete. 
  
 Again, thanks for all the help.

Hi Russ,



Actually, you could simply replace my ShapeFileFeatureLayer in my provided code to InMemoryFeatureLayer, and it gonna be working fine as well; and any FeatureLayer(s) that is editable should work with this code.



What I guess the exception you have is because of this: In your app, you called InMemoryFeatureLayer.Clear(); In this method, MapSuite will build the index for this layer. Once the index is built, we will keep using the index as higher priority in the later work. In this case, we need to call BuildIndex after we making changes on this layer. For example the first three lines code as below, they will get around the building index, so that we need to build it manually. Or else, the index returns us an id while the feature is removed from the layer so that the exception throws.


inMemoryFeatureLayer.InternalFeatures.Add(“NewFeatureId”, newFeature);
inMemoryFeatureLayer.InternalFeatures.Remove(“OldFeatureId”);
inMemoryFeatureLayer.InternalFeatures.Clear();
inMemoryFeatureLayer.BuildIndex();


What we recommend to do is using EditTools instead.


inMemoryFeatureLayer.EditTools.BeginTransaction();
// newFeature.Id will be used in the index.
inMemoryFeatureLayer.EditTools.Add(newFeature);
// the feature with the same anotherFeature’s id will be updated.
inMemoryFeatureLayer.EditTools.Update(anotherFeature);
// the anotherFeature will be removed.
inMemoryFeatureLayer.EditTools.Delete(anotherFeature.Id);
// Commit the changes and build index for current feature set.
inMemoryFeatureLayer.EditTools.CommitTransaction();


The advantage is that, all the operation will sync the latest features to the index. And the code above could apply the editing logic to all MapSuite editable FeatureLayers. 



An opposite way is not to use Index at all. 
For example use inMemoryFeatureLayer.InternalFeatures.Clear() instead of inMemoryFeatureLayer.Clear().



Hope it makes sense. 



Thanks,
Howard