I am using a custom edit overlay to control the shape of the multipolygons in the overlay when the user resizes it. I have three issues and I'm not sure if they are related to the custom overlay or EditOverlay in general.
1) When I have a feature in the edit overlay and I pan the map by dragging it, the labels that I put on the control points disappear. They don't reappear until a refresh is done.
2) My users complained about the size of the control points being too small and that they sometimes panned the map instead of drug the control points. I tried to resolve this by increasing the symbol size of ValueStyle)customEditOverlay.ExistingControlPointsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles [0] ).ValueItems [0].DefaultPointStyle;
but the symptom still remains. Sometimes it is very difficult to grab the control points even if the mouse appears to be on one...is there a way to increase the 'hot spot' for the control points?
3) When there is no feature in the EditOverlay, if I click to drag the map in a spot where the control points WOULD be if the feature was in the EditOverlay, the map pans but it doesn't stop panning...it follows my mouse after I stop the drag attempt. This continues until I click and drag again at which point it stops once I complete my drag.
Here is my custom edit overlay:
/// <summary></summary>/// Define our custom edit overlay for modifying approach maps /// [ComVisible(false)] public class ApproachInteractiveEditOverlay : EditInteractiveOverlay { // // Point reference for PolygonShapes (Channel Segments) // // // | target CP | // ---0----------------------------------------------0---------0--- // | | // | V0 side1 |V1 // O---------------------------O----------------------------O // f | CP | b // r | | a // o | | c // n | | k // t | | // | V3 CP |V2 // O---------------------------O----------------------------O // | side2 | // | | // #region Events public event EventHandler ApproachChanged; #endregion Events #region Properties public ResizeMode ResizeMode { get { return m_resizeMode; } set { m_resizeMode = value; } } private ResizeMode m_resizeMode; public RotationProjection Projection { get { return m_projection; } set { m_projection = value; } } private RotationProjection m_projection = new RotationProjection(); #endregion Properties #region Constructors public ApproachInteractiveEditOverlay() { } #endregion Constructors #region overrides /// <summary></summary>/// Kludge to prevent MapSuite from adding vertex when editing /// ///
<param name="targetFeature" />///
<param name="targetPointShape" />///
<param name="searchingTolerance" />/// <returns>protected override Feature AddVertexCore( Feature targetFeature, PointShape targetPointShape, double searchingTolerance ) { return new Feature(); } /// <summary></summary>/// Calculate control points of feature. /// ///
<param name="feature" />Feature needing control points /// <returns>Collection of control point featues (PointShape) protected override IEnumerable<feature> CalculateVertexControlPointsCore( Feature feature ) { if ( feature.GetShape() is MultipolygonShape ) { return GetVertexControlPoints(feature); } return base.CalculateVertexControlPointsCore(feature); } /// <summary></summary>/// Create Vertex Control Points for editing approach /// ///
<param name="feature" />/// <returns>protected Collection<feature> GetVertexControlPoints( Feature feature ) { Collection<feature> controlPoints = new Collection<feature>(); if ( feature.GetShape() is MultipolygonShape ) { MultipolygonShape shape = feature.GetShape() as MultipolygonShape; Collection<vertex> lineEnds = new Collection<vertex>(); int idx = 0; foreach ( PolygonShape ps in shape.Polygons ) { lineEnds.Clear(); lineEnds.Add(ps.OuterRing.Vertices [0]); lineEnds.Add(ps.OuterRing.Vertices [3]); Feature frontPoint = new Feature(new LineShape(lineEnds).GetCenterPoint()); frontPoint.ColumnValues.Add("PointId", idx.ToString()); frontPoint.ColumnValues.Add("Label", "[" + (idx + 1).ToString() + "]" ); controlPoints.Add(frontPoint); idx++; } if ( shape.Polygons.Count > 0 ) { PolygonShape last = shape.Polygons [shape.Polygons.Count - 1]; lineEnds.Clear(); lineEnds.Add(last.OuterRing.Vertices [1]); lineEnds.Add(last.OuterRing.Vertices [2]); Feature tailPoint = new Feature(new LineShape(lineEnds).GetCenterPoint()); tailPoint.ColumnValues.Add("PointId", "Tail"); tailPoint.ColumnValues.Add("Label", "[" + (idx + 1).ToString() + "]"); controlPoints.Add(tailPoint); } if ( feature.ColumnValues.ContainsKey("PolygonIndex") ) { idx = Convert.ToInt32(feature.ColumnValues ["PolygonIndex"]); if ( idx < shape.Polygons.Count ) { PolygonShape ps = shape.Polygons [idx]; lineEnds.Clear(); lineEnds.Add(ps.OuterRing.Vertices [0]); lineEnds.Add(ps.OuterRing.Vertices [1]); Feature side1Point = new Feature(new LineShape(lineEnds).GetCenterPoint()) ; side1Point.ColumnValues.Add("PointId", "Side1"); side1Point.ColumnValues.Add("Label", ""); controlPoints.Add(side1Point); lineEnds.Clear(); lineEnds.Add(ps.OuterRing.Vertices [3]); lineEnds.Add(ps.OuterRing.Vertices [2]); Feature side2Point = new Feature(new LineShape(lineEnds).GetCenterPoint()); side2Point.ColumnValues.Add("PointId", "Side2"); side2Point.ColumnValues.Add("Label", ""); controlPoints.Add(side2Point); } } } return controlPoints; } /// <summary></summary>/// Handle reshaping of approach maps. User is dragging a control point. /// ///
<param name="sourceFeature" />feature being reshaped ///
<param name="sourceControlPoint" />control point used to reshape ///
<param name="targetControlPoint" />destination point /// <returns>protected override Feature MoveVertexCore( Feature sourceFeature, PointShape sourceControlPoint, PointShape targetControlPoint ) { Feature returnFeature; try { sourceControlPoint = (PointShape)Projection.ConvertToInternalProjection(sourceControlPoint); targetControlPoint = (PointShape)Projection.ConvertToInternalProjection(targetControlPoint); // // Kludge to find out which source control point is being used // Feature sourceCPFeature = SelectControlPointFeature; string ptId = string.Empty; if ( sourceCPFeature.ColumnValues.ContainsKey("PointId") ) { ptId = sourceCPFeature.ColumnValues ["PointId"]; } // BaseShape baseShape = sourceFeature.GetShape(); if ( baseShape is MultipolygonShape ) { ApproachMap approach = (ApproachMap)sourceFeature.Tag; MultipolygonShape mpShape = Projection.ConvertToInternalProjection(sourceFeature).GetShape() as MultipolygonShape; int polygonIndex = Convert.ToInt32(sourceFeature.ColumnValues ["PolygonIndex"]); Collection<vertex> lineEnds = new Collection<vertex>(); PolygonShape shape = mpShape.Polygons [polygonIndex]; RingShape rs = shape.OuterRing; if ( ptId == "Side1" || ptId == "Side2" ) { // // One of the side control points is selected to adjust width of a segment // // Extend side lines out to more accurately measure distance of target point from side of source CP lineEnds.Add(rs.Vertices [0]); lineEnds.Add(rs.Vertices [1]); LineShape side1Line = new LineShape(lineEnds); lineEnds.Clear(); lineEnds.Add(rs.Vertices [3]); lineEnds.Add(rs.Vertices [2]); LineShape side2Line = new LineShape(lineEnds); for ( int idx = 0; idx < 3; idx++ ) { side1Line.ScaleUp(100); side2Line.ScaleUp(100); } // Get closest points on extended lines PointShape psSide1 = side1Line.GetClosestPointTo(targetControlPoint, GeographyUnit.DecimalDegree); PointShape psSide2 = side2Line.GetClosestPointTo(targetControlPoint, GeographyUnit.DecimalDegree); // double distance = 0; bool addDistance = true; double sourceToSide1 = sourceControlPoint.GetDistanceTo(side1Line, GeographyUnit.DecimalDegree, DistanceUnit.Meter); double sourceToSide2 = sourceControlPoint.GetDistanceTo(side2Line, GeographyUnit.DecimalDegree, DistanceUnit.Meter); double targetToSide1 = targetControlPoint.GetDistanceTo(side1Line, GeographyUnit.DecimalDegree, DistanceUnit.Meter); double targetToSide2 = targetControlPoint.GetDistanceTo(side2Line, GeographyUnit.DecimalDegree, DistanceUnit.Meter); double distanceBetweenLines = Math.Round(side1Line.GetDistanceTo(side2Line, GeographyUnit.DecimalDegree, DistanceUnit.Meter)); if ( sourceToSide1 < sourceToSide2 ) { // // Source Control Point is on Side 1 // distance = psSide1.GetDistanceTo(targetControlPoint, GeographyUnit.DecimalDegree, DistanceUnit.Meter); double distanceToOpposite = psSide2.GetDistanceTo(targetControlPoint, GeographyUnit.DecimalDegree, DistanceUnit.Meter); if ( ( distance > distanceToOpposite ) || ( distanceToOpposite < distanceBetweenLines ) ) { addDistance = false; } } else { // // Source Control Point is on Side 2 // distance = psSide2.GetDistanceTo(targetControlPoint, GeographyUnit.DecimalDegree, DistanceUnit.Meter); double distanceToOpposite = psSide1.GetDistanceTo(targetControlPoint, GeographyUnit.DecimalDegree, DistanceUnit.Meter); if ( ( distance > distanceToOpposite ) || ( distanceToOpposite < distanceBetweenLines ) ) { addDistance = false; } } // // Restore lines to original size // lineEnds.Clear(); lineEnds.Add(rs.Vertices [0]); lineEnds.Add(rs.Vertices [1]); side1Line = new LineShape(lineEnds); lineEnds.Clear(); lineEnds.Add(rs.Vertices [3]); lineEnds.Add(rs.Vertices [2]); side2Line = new LineShape(lineEnds); distance = Math.Round(distance); if ( distance > 0 ) { // slope of side lines double rise = rs.Vertices [1].Y - rs.Vertices [0].Y; double run = rs.Vertices [1].X - rs.Vertices [0].X; double angle = ( Math.Atan2(rise, run) ) * ( 180 / Math.PI ); // Ensure positive angle if ( angle < 0 ) { angle = angle + 180; } if ( addDistance ) { distance = Math.Min(distance, ( Coordinate.MAXTOLLERANCE - ( distanceBetweenLines - Coordinate.BUFFERLENGTH ) ) / 2); if ( distance > 0 ) { // // If going away from side, increase width // side1Line.TranslateByDegree(distance, ( ( 180 - angle ) % 360 ), GeographyUnit.DecimalDegree, DistanceUnit.Meter); side2Line.TranslateByDegree(distance, ( ( 360 - angle ) % 360 ), GeographyUnit.DecimalDegree, DistanceUnit.Meter); } } else { // // If shrinking approach, limit it to 30 meters and reduce width // if ( distanceBetweenLines - ( distance * 2 ) >= Coordinate.BUFFERLENGTH ) { side1Line.TranslateByDegree(distance, ( ( 360 - angle ) % 360 ), GeographyUnit.DecimalDegree, DistanceUnit.Meter); side2Line.TranslateByDegree(distance, ( ( 180 - angle ) % 360 ), GeographyUnit.DecimalDegree, DistanceUnit.Meter); } else { distance = Math.Round(( distanceBetweenLines - Coordinate.BUFFERLENGTH ) / 2); if ( distance > 0 ) { side1Line.TranslateByDegree(distance, ( ( 360 - angle ) % 360 ), GeographyUnit.DecimalDegree, DistanceUnit.Meter); side2Line.TranslateByDegree(distance, ( ( 180 - angle ) % 360 ), GeographyUnit.DecimalDegree, DistanceUnit.Meter); } } } } // // Save new size // Vertex [] newPoints = new Vertex [4]; newPoints [0] = new Vertex(side1Line.GetPointOnALine(StartingPoint.FirstPoint, 0)); newPoints [1] = new Vertex(side1Line.GetPointOnALine(StartingPoint.LastPoint, 0)); newPoints [2] = new Vertex(side2Line.GetPointOnALine(StartingPoint.LastPoint, 0)); newPoints [3] = new Vertex(side2Line.GetPointOnALine(StartingPoint.FirstPoint, 0)); PolygonShape ps = new PolygonShape(new RingShape(newPoints)); // Update approach shape mpShape.Polygons [polygonIndex] = ps; // // Update approach data // PointShape p1 = new PointShape(newPoints [0]); PointShape p2 = new PointShape(newPoints [3]); ( (Coordinate)approach.Coordinates [polygonIndex] ).Tolerance = Math.Min(Coordinate.MAXTOLLERANCE, (int) Math.Max(0, Math.Round(p1.GetDistanceTo(p2, GeographyUnit.DecimalDegree, DistanceUnit.Meter)) - Coordinate.BUFFERLENGTH)); } else if ( ptId.Equals("Tail") || ptId.Equals("0") ) { // // Control point on the front or tail is adjusted...move position of segment // PointShape front = null; PointShape tail = null; if ( ptId.Equals("Tail") ) { polygonIndex = mpShape.Polygons.Count - 1; shape = mpShape.Polygons [polygonIndex]; rs = shape.OuterRing; lineEnds.Clear(); lineEnds.Add(rs.Vertices [0]); lineEnds.Add(rs.Vertices [3]); front = new LineShape(lineEnds).GetCenterPoint(); tail = targetControlPoint; } else if ( ptId.Equals("0") ) { polygonIndex = 0; shape = mpShape.Polygons [polygonIndex]; rs = shape.OuterRing; lineEnds.Clear(); lineEnds.Add(rs.Vertices [1]); lineEnds.Add(rs.Vertices [2]); front = targetControlPoint; tail = new LineShape(lineEnds).GetCenterPoint(); } mpShape.Polygons [polygonIndex] = UpdateSegment(polygonIndex, front, tail, approach); } else // should be an index item { // // Control point between segments is being moved // // Adjust the first of the two altered segments // polygonIndex = Convert.ToInt32(ptId) - 1; shape = mpShape.Polygons [polygonIndex]; rs = shape.OuterRing; lineEnds.Clear(); lineEnds.Add(rs.Vertices [0]); lineEnds.Add(rs.Vertices [3]); mpShape.Polygons [polygonIndex] = UpdateSegment(polygonIndex, new LineShape(lineEnds).GetCenterPoint(), targetControlPoint, approach); // // Adjust the second of the two altered segments // polygonIndex = Convert.ToInt32(ptId); shape = mpShape.Polygons [polygonIndex]; rs = shape.OuterRing; lineEnds.Clear(); lineEnds.Add(rs.Vertices [1]); lineEnds.Add(rs.Vertices [2]); mpShape.Polygons [polygonIndex] = UpdateSegment(polygonIndex, targetControlPoint, new LineShape(lineEnds).GetCenterPoint(), approach); } mpShape = (MultipolygonShape)Projection.ConvertToExternalProjection(mpShape); ClearAllControlPoints(); // Update tooltip sourceFeature.ColumnValues ["Tip"] = approach.TipText; sourceFeature.ColumnValues ["EditTip"] = approach.EditTipText; returnFeature = new Feature(mpShape.GetWellKnownBinary(), sourceFeature.Id, sourceFeature.ColumnValues); returnFeature.Tag = sourceFeature.Tag; // Notify listeners OnApproachChanged(); return returnFeature; } returnFeature = base.MoveVertexCore(sourceFeature, sourceControlPoint, targetControlPoint); } catch ( Exception ) { returnFeature = sourceFeature; } return returnFeature; } /// <summary></summary>/// Update the segment according to the new location /// ///
<param name="polygonIndex" />Index into MultipolygonShape Polygons (segment index) ///
<param name="front" />PointShape of front of segment ///
<param name="tail" />PointShape of rear of segment ///
<param name="approach" />Approach being adjusted /// <returns>New PolygonShape representing the segment private PolygonShape UpdateSegment( int polygonIndex, PointShape front, PointShape tail, ApproachMap approach ) { Collection<vertex> lineEnds = new Collection<vertex>(); // slope of side lines double rise = tail.Y - front.Y; double run = tail.X - front.X; double angle = ( Math.Atan2(rise, run) ) * ( 180 / Math.PI ); // Ensure positive angle if ( angle < 0 ) { angle = angle + 180; } lineEnds.Clear(); lineEnds.Add(new Vertex(front.X, front.Y)); lineEnds.Add(new Vertex(tail.X, tail.Y)); LineShape centerLine = new LineShape(lineEnds); int tollerance = ((Coordinate)approach.Coordinates [polygonIndex]).Tolerance; double bufferWidth = Math.Truncate((double)( tollerance + Coordinate.BUFFERLENGTH + 1 ) / 2); LineShape side1 = (LineShape)centerLine.CloneDeep(); side1.TranslateByDegree(bufferWidth, ( ( 180 - angle ) % 360 ), GeographyUnit.DecimalDegree, DistanceUnit.Meter); LineShape side2 = (LineShape)centerLine.CloneDeep(); side2.TranslateByDegree(bufferWidth, ( ( 360 - angle ) % 360 ), GeographyUnit.DecimalDegree, DistanceUnit.Meter); // // Save new size // Vertex [] newPoints = new Vertex [4]; newPoints [0] = new Vertex(side1.GetPointOnALine(StartingPoint.FirstPoint, 0)); newPoints [1] = new Vertex(side1.GetPointOnALine(StartingPoint.LastPoint, 0)); newPoints [2] = new Vertex(side2.GetPointOnALine(StartingPoint.LastPoint, 0)); newPoints [3] = new Vertex(side2.GetPointOnALine(StartingPoint.FirstPoint, 0)); // // Update segment X,Y offsets in approach data // foreach ( Feature intRadio in EditShapesLayer.InternalFeatures ) { if ( intRadio.ColumnValues.ContainsKey("Type") && intRadio.ColumnValues ["Type"].Equals("IntersectionRadio") ) { GeoPt offsets = MapHelper.PositionToOffset((PointShape)intRadio.GetShape(), front); ((Coordinate)approach.Coordinates [polygonIndex]).X = offsets.X; ((Coordinate)approach.Coordinates [polygonIndex]).Y = offsets.Y; offsets = MapHelper.PositionToOffset((PointShape)intRadio.GetShape(), tail); ((Coordinate)approach.Coordinates [polygonIndex+1]).X = offsets.X; ((Coordinate)approach.Coordinates [polygonIndex+1]).Y = offsets.Y; break; } } // // Update computed length of the segment // ( (Coordinate)approach.Coordinates [polygonIndex] ).MeterLength = Math.Round(centerLine.GetLength(GeographyUnit.DecimalDegree, DistanceUnit.Meter)); return new PolygonShape(new RingShape(newPoints)); } /// <summary></summary>/// Workaround so multipolygon will paint "invalid polygons" /// (overlapping sections sometimes don't paint) /// ///
<param name="canvas" />protected override void DrawCore( GeoCanvas canvas ) { // Draw 6 inmemoryLayers. Collection<simplecandidate> labelsInAllLayers = new Collection<simplecandidate>(); EditShapesLayer.Open(); Collection<feature> backedUpFeatures = new Collection<feature>(); // EditShapesLayer.QueryTools.GetAllFeatures(ReturningColumnsType.AllColumns); foreach ( Feature feature in EditShapesLayer.InternalFeatures ) { backedUpFeatures.Add(feature); } EditShapesLayer.InternalFeatures.Clear(); foreach ( Feature feature in backedUpFeatures ) { BaseShape baseShape = feature.GetShape(); if ( baseShape is MultipolygonShape ) { MultipolygonShape multiPolygonShape = (MultipolygonShape)baseShape; // // Paint individual polygons instead so that 'invalid' multipolygons paint // foreach ( PolygonShape polygonShape in multiPolygonShape.Polygons ) { Feature polygonFeature = new Feature(polygonShape, feature.ColumnValues); EditShapesLayer.InternalFeatures.Add(polygonFeature); } } else { EditShapesLayer.InternalFeatures.Add(feature); } } EditShapesLayer.Draw(canvas, labelsInAllLayers); canvas.Flush(); EditShapesLayer.InternalFeatures.Clear(); foreach ( Feature feature in backedUpFeatures ) { EditShapesLayer.InternalFeatures.Add(feature); } //EditShapesLayer.Close(); ExistingControlPointsLayer.Open(); ExistingControlPointsLayer.Draw(canvas, labelsInAllLayers); canvas.Flush(); //ExistingControlPointsLayer.Close(); //ResizeControlPointsLayer.Open(); //ResizeControlPointsLayer.Draw(canvas, labelsInAllLayers); //canvas.Flush(); ////ResizingControlPointsLayer.Close(); //RotateControlPointsLayer.Open(); //RotateControlPointsLayer.Draw(canvas, labelsInAllLayers); //canvas.Flush(); ////RotatingControlPointsLayer.Close(); DragControlPointsLayer.Open(); DragControlPointsLayer.Draw(canvas, labelsInAllLayers); canvas.Flush(); } #endregion overrides #region Protected methods protected void OnApproachChanged() { if ( ApproachChanged != null ) { ApproachChanged(this, new EventArgs()); } } #endregion Protected methods } //use in map... // // Set up edit overlay for editing of approach maps // ApproachInteractiveEditOverlay customEditOverlay = new ApproachInteractiveEditOverlay(); customEditOverlay.ResizeMode = ResizeMode.OnePointFixed; customEditOverlay.Projection = m_projection; customEditOverlay.ApproachChanged += new EventHandler(customEditOverlay_ApproachChanged); customEditOverlay.CanAddVertex = false; customEditOverlay.CanRemoveVertex = false; customEditOverlay.CanDrag = false; customEditOverlay.CanRotate = false; customEditOverlay.CanResize = false; customEditOverlay.ResizeControlPointsLayer.IsVisible = false; customEditOverlay.RotateControlPointsLayer.IsVisible = false; // Set up editing area style customEditOverlay.EditShapesLayer.Open(); customEditOverlay.EditShapesLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = AreaStyles.CreateHatchStyle( GeoHatchStyle.LightDownwardDiagonal, GeoColor.FromArgb(200, GeoColor.StandardColors.Magenta), GeoColor.FromArgb(150, GeoColor.StandardColors.Magenta), GeoColor.FromArgb(200, GeoColor.StandardColors.DarkBlue), 1, DashStyleToLineDashStyle(DashStyle.Dash), 0, 0); // Remove blue dot customEditOverlay.EditShapesLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle = PointStyles.CreateSimplePointStyle( PointSymbolType.Circle, GeoColor.SimpleColors.Black, 1); customEditOverlay.EditShapesLayer.Columns.Add(new FeatureSourceColumn("Name")); customEditOverlay.EditShapesLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20; customEditOverlay.EditShapesLayer.Close(); // Set up control point label style customEditOverlay.ExistingControlPointsLayer.Open(); customEditOverlay.ExistingControlPointsLayer.Columns.Add(new FeatureSourceColumn("Label")); TextStyle controlPtText = TextStyles.CreateSimpleTextStyle("Label", "Arial", 14, DrawingFontStyles.Regular, GeoColor.StandardColors.Blue, GeoColor.StandardColors.Green, 0, 0, 5); controlPtText.PointPlacement = PointPlacement.UpperCenter; // Add text style for displaying map point numbers customEditOverlay.ExistingControlPointsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(controlPtText); // Change control point style to a larger point PointStyle ptStyle = ( (ValueStyle)customEditOverlay.ExistingControlPointsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles [0] ).ValueItems [0].DefaultPointStyle; ptStyle.SymbolSize = 12; //ptStyle.SymbolType = PointSymbolType.Circle; //PointStyle valuePt2 = ( (ValueStyle)customEditOverlay.ExistingControlPointsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles [0] ).ValueItems [1].DefaultPointStyle; //valuePt2.SymbolSize = 12; //valuePt2.SymbolType = PointSymbolType.Square; //valuePt2.PointType = PointType.Symbol; customEditOverlay.ExistingControlPointsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20; customEditOverlay.VertexAdding += new EventHandler<vertexaddingeditinteractiveoverlayeventargs>(EditOverlay_VertexAdding); m_map.EditOverlay = customEditOverlay;