/// <summary> /// Confirms that a potential link is valid. For a link to be valid, the /// midpoint of the link must fall inside the specified polygon, and the /// line connecting the 2 points cannot intersect anything. /// </summary> /// <param name="other">The end of the proposed point to link to.</param> /// <param name="pol">The polygon the link must fall completely inside.</param> /// <returns>True if link is valid.</returns> internal bool IsLinkValid(PolygonLink other, Polygon pol) { // 22-MAR-00 There may be no point at a corner. if (m_Point == null || other.Point == null) { return(false); } // Get the midpoint of the link. IPosition mid = Geom.MidPoint(m_Point, other.Point); // Confirm the midpoint falls inside the right polygon. if (!pol.IsRingEnclosing(mid)) { return(false); } // Locate any intersections formed by the proposed link (considering // only the currently active theme). LineGeometry seg = new SegmentGeometry(m_Point, other.m_Point); IntersectionFinder xsect = new IntersectionFinder(seg, true); // The proposed link is not valid if it intersects anything // along the length of the segment, or has any grazes. if (xsect.IsGrazing || xsect.IsSplitNeeded) { return(false); } return(true); }
/// <summary> /// Returns the angle between the ideal bearing for this link, and the /// position of another link point. The caller can test the angle against /// an angular tolerance to see if a link is suitable. /// /// Note that this function only makes a one-way test, making use only of /// the POSITION of the other link point. The caller may also want to do the /// reverse test as well, to see if the link is within tolerance from the /// perspective of both link points. /// </summary> /// <param name="other">The other link point.</param> /// <returns>The angle between this link point's reference bearing, and a /// line radiating to the other link point. The angle will always be less /// (or equal to) PI radians.</returns> internal double GetAngle(PolygonLink other) { // 22-MAR-00 There may be no point at a corner. if (m_Point == null || other.Point == null) { return(Constants.PIDIV2); } // Treat the direction to the other link point as the reference direction. Turn reference = new Turn(this.Point, other.Point); // Get the clockwise angle formed by the reference direction and the // ideal bearing for this link point. double angle = reference.GetAngleInRadians(m_Bearing); // Force the angle to be less than 180 degrees. if (angle > Constants.PI) { angle = Constants.PIMUL2 - angle; if (angle < Constants.TINY) { angle = 0.0; } } return(angle); }
/// <summary> /// Returns the distance (squared) between 2 link points. /// </summary> /// <param name="other">The other link to get the distance to.</param> /// <returns></returns> internal double DistanceSquared(PolygonLink other) { // 22-MAR-00 There may be no point at a corner. if (m_Point == null || other.Point == null) { return(0.0); } else { return(Geom.DistanceSquared(m_Point, other.Point)); } }
/// <summary> /// Assigns initial values to everything /// </summary> void ResetContent() { m_Point = null; m_Link = null; m_SideNumber = 0; m_Angle = 0.0; m_Bearing = 0.0; m_IsCurveEnd = false; m_IsEnd = false; m_IsRadial = false; m_LinkAngle = Constants.PIMUL2; m_NumIntersect = 0; }
/// <summary> /// Checks whether this link object intersects another one. /// </summary> /// <param name="other">The other link to check.</param> /// <returns> /// True if there is an intersection. False if: /// /// 1. The other link is THIS link. /// 2. This link is the end of a link. /// 3. This link has no link. /// 4. The other link is the end of any link. /// 5. The other link is not linked. /// 6. There is no intersection. /// </returns> internal bool IsIntersected(PolygonLink other) { // The other object can't be THIS object. if (Object.ReferenceEquals(other, this)) { return(false); } // The other can't be linked to this. if (Object.ReferenceEquals(other, m_Link)) { return(false); } // Both objects must be linked. if (m_Link == null || other.Link == null) { return(false); } // It must be the START of both links. if (m_IsEnd || other.IsEnd) { return(false); } // Get the position of this link. double xk = m_Point.X; double yk = m_Point.Y; double xl = m_Link.Point.X; double yl = m_Link.Point.Y; // Get the position of the other link. double xm = other.Point.X; double ym = other.Point.Y; double xn = other.Link.Point.X; double yn = other.Link.Point.Y; // Return if there is any intersection along the length of the test segments. double xi, yi; return(Geom.CalcIntersect(xk, yk, xl, yl, xm, ym, xn, yn, out xi, out yi, true) != 0); }
/// <summary> /// Refers this object to another one. Once done, the <c>Link</c> function will /// return the link when called for THIS object (but not the other object). /// </summary> /// <param name="link">The object to link to. Must not point to this object. Note that /// if the object to link to already has a link, that link will be severed; you can /// only link to ONE other object.</param> /// <param name="thisdiff">The value to assign to <c>this.LinkAngle</c></param> /// <param name="othrdiff">The value to assign to <c>link.LinkAngle</c></param> internal void SetLink(PolygonLink link, double thisdiff, double othrdiff) { // CAN'T point to itself. if (Object.ReferenceEquals(link, this)) { throw new ArgumentException("PolygonLink.SetLink: Attempt to form null connection"); } // The link shouldn't have been made already. if (link != null && (Object.ReferenceEquals(m_Link, link) || Object.ReferenceEquals(link.Link, this))) { throw new ArgumentException("PolygonLink.SetLink: Attempt to re-form link"); } // If this object already has a link, update the object that pointed back. if (m_Link != null) { m_Link.Link = null; m_Link.IsEnd = false; m_Link.LinkAngle = Constants.PIMUL2; } // If the object we are linking to has a link, break it too. if (link != null && link.Link != null) { link.SetLink(null); } // Define the link. m_Link = link; m_IsEnd = false; m_LinkAngle = thisdiff; // If the link is real, point the other object back, marking it as a back pointer. if (link != null) { link.Link = this; link.IsEnd = true; link.LinkAngle = othrdiff; } }
/// <summary> /// Sets the number of intersections for one of the polygon link points. /// </summary> /// <param name="index">The array index of the link point to process.</param> /// <returns>The number of intersections assigned.</returns> uint SetIntersectionCount(uint index) { // No intersections so far uint nx = 0; // Point to the thing we want to set the count for. PolygonLink link = m_Links[index]; // Go through all link points searching for intersections. for (uint i = 0; i < m_Links.Length; i++) { if (link.IsIntersected(m_Links[i])) { nx++; } } // Set the intersect count. link.NumIntersect = nx; return(nx); }
internal void SetLink(PolygonLink link) { SetLink(link, Constants.PIMUL2, Constants.PIMUL2); }
/// <summary> /// Defines links for this face. A prior call to <c>SetLine</c> is required. /// </summary> /// <param name="prev">The preceding face (after it has been processed via a call to /// <c>SetLine</c>, Must refer to the same polygon as this face.</param> /// <returns>The links (the number of array elements will equal the value that came /// back from the <c>SetLine</c> call)</returns> internal PolygonLink[] CreateLinks(PolygonFace prev) { Debug.Assert(m_Polygon != null); Debug.Assert(m_Divider != null); Debug.Assert(prev.Divider != null); Debug.Assert(Object.ReferenceEquals(prev.Polygon, m_Polygon)); // Try to obtain a point feature at the beginning of this face. ISpatialIndex index = CadastralMapModel.Current.Index; PointFeature beginPoint = new FindPointQuery(index, m_Begin).Result; // If the polygon to the left of the this face? (if so, curve-related // things may need to be reversed below) bool isPolLeft = (m_Divider.Left == m_Polygon); // Default geometric info we need to define bool isCurveEnd = false; bool isRadial = false; double bearing = 0.0; double angle = 0.0; // Is the position at the beginning of this face the start // or end of a circular arc (points between 2 curves (on // compound curve) are NOT considered to be curve ends). Note // that the line could either be a CircularArc, or a topological // section based on a circular arc. ICircularArcGeometry prevArc = (prev.Divider.LineGeometry as ICircularArcGeometry); ICircularArcGeometry thisArc = (m_Divider.LineGeometry as ICircularArcGeometry); bool isPrevCurve = (prevArc != null); bool isThisCurve = (thisArc != null); isCurveEnd = (isPrevCurve != isThisCurve); // If we are dealing with a point between two curves try to // set the curve parameters and, if successful, we will treat // the point as a radial point. if (isPrevCurve && isThisCurve) { isRadial = true; // Get the curve parameters... IPosition prevcen = prevArc.Circle.Center; double prevrad = prevArc.Circle.Radius; IPosition thiscen = thisArc.Circle.Center; double thisrad = thisArc.Circle.Radius; // We only need to know the sense for one of the arcs bool iscw = thisArc.IsClockwise; // If the 2 curves do not have the same centre and radius, // see if they are really close (to the nearest centimeter // on the ground). In that case, use the average values for // the 2 curves. If they really are different curves, don't // treat this as a radial curve point -- treat it as a // curve end instead. IPosition centre = null; // The centre to actually use if (!(prevcen == thiscen && Math.Abs(prevrad - thisrad) < Constants.TINY)) { double xc1 = prevcen.X; double yc1 = prevcen.Y; double xc2 = thiscen.X; double yc2 = thiscen.Y; double dxc = xc2 - xc1; double dyc = yc2 - yc1; if (Math.Abs(dxc) < 0.01 && Math.Abs(dyc) < 0.01 && Math.Abs(prevrad - thisrad) < 0.01) { // Close enough centre = new Position(xc1 + dxc * 0.5, yc1 + dyc * 0.5); } else { isRadial = false; isCurveEnd = true; } } else { centre = prevcen; } // If the centre and radius were the same (or close enough), // define the radial bearing from the centre of the circle. // If the polygon is actually on the other side, reverse the // bearing so that it's directed INTO the polygon. if (isRadial) { Debug.Assert(centre != null); bearing = Geom.BearingInRadians(centre, m_Begin); angle = 0.0; if (iscw != isPolLeft) { bearing += Constants.PI; } } } // If we're not dealing with a radial curve (or we have curves that // don't appear to be radial) if (!isRadial) { // Get the clockwise angle from the last position of the // preceding face to the first position after the start // of this face. Since info is held in a clockwise cycle // around the polygon, this will always give us the exterior // angle. Turn reference = new Turn(m_Begin, prev.TailReference); angle = reference.GetAngleInRadians(this.HeadReference); // Define the bearing to use for projecting the point. It's // in the middle of the angle, but projected into the polygon. bearing = reference.BearingInRadians + angle * 0.5 + Constants.PI; } // Initialize the link at the start of the face List <PolygonLink> links = new List <PolygonLink>(); PolygonLink link = new PolygonLink(beginPoint, isCurveEnd, isRadial, bearing, angle); links.Add(link); // Initialize links for any extra points if (m_ExtraPoints != null) { // Intermediate points can never be curve ends isCurveEnd = false; // If the face is a curve, they're radial points isRadial = isThisCurve; // Note any curve info double radius; IPosition centre = null; bool iscw = false; if (isRadial) { Debug.Assert(m_Divider.Line.LineGeometry is ICircularArcGeometry); ICircularArcGeometry arc = (m_Divider.Line.LineGeometry as ICircularArcGeometry); centre = arc.Circle.Center; radius = arc.Circle.Radius; iscw = arc.IsClockwise; angle = 0.0; } for (uint i = 0; i < m_ExtraPoints.Length; i++) { //IPointGeometry loc = m_ExtraPoints[i].Geometry; PointFeature loc = m_ExtraPoints[i]; // Figure out the orientation bearing for the point if (isRadial) { Debug.Assert(centre != null); bearing = Geom.BearingInRadians(centre, loc); if (iscw != isPolLeft) { bearing += Constants.PI; } } else { // Get the exterior clockwise angle IPointGeometry back; IPointGeometry fore; if (i == 0) { back = m_Begin; } else { back = m_ExtraPoints[i - 1].Geometry; } if (i == (m_ExtraPoints.Length - 1)) { fore = m_End; } else { fore = m_ExtraPoints[i + 1].Geometry; } Turn reference = new Turn(loc, back); angle = reference.GetAngleInRadians(fore); // Define the bearing to use for projecting the point. It's // in the middle of the angle, but projected into the polygon. bearing = reference.BearingInRadians + angle * 0.5 + Constants.PI; } link = new PolygonLink(m_ExtraPoints[i], isCurveEnd, isRadial, bearing, angle); links.Add(link); } } return(links.ToArray()); }
/// <summary> /// Constructor (that does a lot of stuff) /// </summary> /// <param name="polygon">The polygon being subdivided.</param> internal PolygonSub(Polygon polygon) { m_Polygon = polygon; m_Faces = null; m_Links = null; // Get the boundaries defining the exterior edge of the polygon IDivider[] lines = polygon.Edge; if (lines == null || lines.Length == 0) { return; } // Allocate an array for holding info on each polygon face, // and associate each one with the divider that acts as the primary face. uint numLink = 0; m_Faces = new PolygonFace[lines.Length]; for (int i = 0; i < lines.Length; i++) { m_Faces[i] = new PolygonFace(); numLink += m_Faces[i].SetDivider(m_Polygon, lines[i]); } // Create an array of link objects (one for each point) if (numLink == 0) { return; } m_Links = new PolygonLink[numLink]; // Initialize the links for first face int nDone = 0; PolygonLink[] links = m_Faces[0].CreateLinks(m_Faces[m_Faces.Length - 1]); Array.Copy(links, m_Links, links.Length); nDone += links.Length; // Initialize links for each subsequent face for (int i = 1; i < m_Faces.Length; i++) { links = m_Faces[i].CreateLinks(m_Faces[i - 1]); for (int j = 0; j < links.Length; j++) { m_Links[nDone + j] = links[j]; } nDone += links.Length; } Debug.Assert(nDone == m_Links.Length); // Assign a side number to each link position uint nside = 0; // No side number assigned so far. uint ncorn = 0; // Number of corners for (int i = 0; i < m_Links.Length; i++) { // Skip if we are dealing with a corner. if (m_Links[i].IsCorner()) { ncorn++; continue; } // Skip if the side number has already been assigned. if (m_Links[i].Side != 0) { continue; } // Assign next side number. nside++; // Continue till we reach a corner, assigning intermediate // faces the same side number. for (int j = i; j < m_Links.Length; j++) { if (m_Links[j].IsCorner()) { break; } m_Links[j].Side = nside; } // If we just assigned a side number to the very first face, // loop back from the end of the array. if (i == 0) { for (int k = m_Links.Length - 1; k > 0; k--) { if (m_Links[k].IsCorner()) { break; } m_Links[k].Side = nside; } } } // Return if we only have one side (or less) // if ( nside<2 ) return; // Define the max angular difference (10 degrees, but in radians) double ANGTOL = (10.0 * Constants.DEGTORAD); // Process all the radial faces first. for (uint i = 0; i < m_Links.Length; i++) { if (m_Links[i].IsRadial) { this.SetLink(i, ANGTOL); } } // For each point that is not a corner or a curve end, cycle // through all the subsequent points, looking for a point to // connect to. Don't try to do curve end points just yet. for (uint i = 0; i < m_Links.Length; i++) { if (!m_Links[i].IsRadial && !m_Links[i].IsCurveEnd) { this.SetLink(i, ANGTOL); } } // Process any points at the end of curves. for (uint i = 0; i < m_Links.Length; i++) { if (m_Links[i].IsCurveEnd) { this.SetLink(i, ANGTOL); } } // Eliminate self-intersections ... // Start by counting the number of intersections each link has. uint maxnx = 0; for (uint i = 0; i < m_Links.Length; i++) { uint nx = this.SetIntersectionCount(i); maxnx = Math.Max(maxnx, nx); } uint nloop = 0; // Number of loops (just in case of infinite loop). while (maxnx > 0) { // Find a link with the max number of intersections. int rem = -1; for (int i = 0; i < m_Links.Length; i++) { if (m_Links[i].NumIntersect == maxnx) { rem = i; break; } } // We SHOULD have found something. if (rem < 0) { throw new Exception("PolygonSub: Unexpected intersection count"); } // If the count was greater than one, just remove the link. Otherwise get the // thing that was intersected. If one is a radial or a curve end, & the other isn't, // drop the radial. Otherwise drop the one with the poorer link angle. if (maxnx > 1) { m_Links[rem].SetLink(null); } else { PolygonLink pLink1 = m_Links[rem]; PolygonLink pLink2 = null; for (int i = 0; pLink2 != null && i < m_Links.Length; i++) { if (pLink1.IsIntersected(m_Links[i])) { pLink2 = m_Links[i]; } } if (pLink2 == null) { throw new Exception("PolygonSub: Intersection not found"); } // Check if radial. bool rad1 = pLink1.IsRadial || pLink1.Link.IsRadial; bool rad2 = pLink2.IsRadial || pLink2.Link.IsRadial; // Check if end of curves. bool end1 = pLink1.IsCurveEnd || pLink1.Link.IsCurveEnd; bool end2 = pLink2.IsCurveEnd || pLink2.Link.IsCurveEnd; // We treat radials and end of curves the same. bool curve1 = (rad1 || end1); bool curve2 = (rad2 || end2); // If one is a curve-related and the other isn't, drop // the one that's curve-related. if (curve1 != curve2) { if (curve1) { pLink1.SetLink(null); } else { pLink2.SetLink(null); } } else { // Neither is curve related, or both are. Drop the // one with the poorer link angle. if (pLink1.LinkAngle > pLink2.LinkAngle) { pLink1.SetLink(null); } else { pLink2.SetLink(null); } } } // Rework the intersect counts where necessary. maxnx = 0; for (uint i = 0; i < m_Links.Length; i++) { if (m_Links[i].NumIntersect > 0) { maxnx = Math.Max(maxnx, this.SetIntersectionCount(i)); } } // Make sure we don't go into an infinite loop. nloop++; if (nloop > m_Links.Length) { throw new Exception("PolygonSub: Breaking from infinite loop"); } } // end while }
/// <summary> /// Tries to set the link for one of the link points. /// </summary> /// <param name="start">The array index of the link point to process.</param> /// <param name="angtol">Angular tolerance (in radians) for seeing if a link /// candidate has a bearing that's close enough to the desired bearing.</param> /// <returns>True if a link was set.</returns> bool SetLink(uint start, double angtol) { // Note our lucky contestant and it's side number. PolygonLink from = m_Links[start]; uint fromside = from.Side; // Nothing to do if no side number (corner point). if (fromside == 0) { return(false); } // Skip if already linked. if (from.IsLinked) { return(false); } // Are we doing a connection for a radial point? In that case, we are // allowed to connect to corner points. We do radial points FIRST. bool radial = from.IsRadial; // Are we doing a curve end point? We do curve end points LAST. bool curveend = from.IsCurveEnd; // Initialize info on best link so far. PolygonLink to = null; double bestangle = Constants.PIMUL2; double thisdiff = Constants.PIMUL2; double othrdiff = Constants.PIMUL2; double MINDSQ = 4.0; // Min distance (squared) between points. // Loop through every link point, looking for a match. for (uint j = 0; j < m_Links.Length; j++) { // Skip if this is the main candidate. if (j == start) { continue; } // Skip if no side number (corners), or it's the same side // as the main candidate. We do permit an extension to a corner // if we're doing a radial link. uint curside = m_Links[j].Side; // if ( curside==0 || curside==fromside ) continue; if (curside == 0 && !radial) { continue; } // Skip if already linked. // if (m_Links[j].IsLinked) continue; // Only consider curve end points if the main candidate is a // curve end point too, or a radial. if (m_Links[j].IsCurveEnd && !(curveend || radial)) { continue; } // Skip if the distance to the other point is smaller // than 2 meters (squared) on the ground (the number's just a stab) if (from.DistanceSquared(m_Links[j]) < MINDSQ) { continue; } // Get the angle formed between the candidate's reference // bearing, and the position of the current link point. If // it exceeds the angular tolerance, skip to next. double angle1 = from.GetAngle(m_Links[j]); if (angle1 > angtol) { continue; } // Do the reverse test so long as the main candidate is // not a radial (when a radial hits a straight edge, the // ideal bearing for that point is likely to be close to // a perpendicular to the straight edge, so it probably // won't be within tolerance of the radial). double angle2; if (radial || m_Links[j].IsRadial) { angle2 = angle1; } else { angle2 = m_Links[j].GetAngle(from); } if (angle2 > angtol) { continue; } // Skip if the candidate was previously linked, and the angle // we have now is worse. if (m_Links[j].IsLinked && angle2 > m_Links[j].LinkAngle) { continue; } // If we are NOT doing a radial link, confirm that the proposed // link does not cross any radial links we may have formed (we // did all radial links first). // If the main candidate is a curve end point, it can't intersect // ANYTHING else. If it's not a curve end point or a radial, all // we should check are radials. We process data in the order: // 1. radials // 2. not radials or curve ends // 3. curve ends // if ( curveend ) { // if ( this->IsIntersected(*pFrom,m_Links[j],FALSE) ) continue; // } // else if ( !radial ) { // if ( this->IsIntersected(*pFrom,m_Links[j],TRUE) ) continue; // } // Use the best angular deviation. double angle = Math.Min(angle1, angle2); // If the deviation is better than anything we already have, // and the link is valid, remember the current link point // as the best one so far. if (angle < bestangle && from.IsLinkValid(m_Links[j], m_Polygon)) { bestangle = angle; thisdiff = angle1; othrdiff = angle2; to = m_Links[j]; } } // next point // Set the link if we got one. if (to != null) { from.SetLink(to, thisdiff, othrdiff); return(true); } return(false); }