using System; using System.Collections.Generic; using System.Linq; using GeoAPI.Extensions.Feature; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Geometries; using GisSharpBlog.NetTopologySuite.Operation.Overlay; namespace NetTopologySuite.Extensions.Geometries { public static class GeometryHelper { // TODO: check if we can use IGeometry methods here (OGC) /// /// The SharpMap implementation of Collection.IndexOf(IGeometry) will use /// bool Geometry::Equals(IGeometry g) which returns true if geometries are of equal shape. /// In many cases this is not desired behaviour. For example if we are looking for a BranchSegmentBoundary /// it is possible the Node at the same location is returned. /// IndexOfGeometry only compares the geometry references /// /// NOTE: performance tips: /// If possible minimize calls to /// - IGeometry.Coordinates converts an internal ICoordinateSequence to an ICoordinate array /// not expensive as such but may be called many times: /// - ILineString.Length is expensive; move outsize loops as much as possible /// /// /// /// static public int IndexOfGeometry(IList geometries, IGeometry geometry) { for (int i = 0; i < geometries.Count; i++) { if (ReferenceEquals(geometries[i], geometry)) return i; } return -1; } /// /// Return the coordinate of the point nearest worldPos at lineString. /// TODO: Is it distance along the polyline????!!! NAME IT CORRECTLY /// TODO: can we merge it with the next method? /// /// /// /// public static double Distance(ILineString lineString, ICoordinate worldPos) { // Distance along linestring double minDistance = Double.MaxValue; if (lineString == null || worldPos == null) return minDistance; ICoordinate min_c1 = null; ICoordinate min_c2 = null; int index = -1; ICoordinate c1; ICoordinate c2; double pointDistance = 0; ICoordinate[] coordinates = lineString.Coordinates; for (int i = 1; i < coordinates.Length; i++) { c1 = coordinates[i - 1]; c2 = coordinates[i]; double distance = LinePointDistance(c1.X, c1.Y, c2.X, c2.Y, worldPos.X, worldPos.Y); if (distance < minDistance) { minDistance = distance; min_c1 = c1; min_c2 = c2; index = i; } } if (-1 != index) { ICoordinate location = NearestPointAtSegment(min_c1.X, min_c1.Y, min_c2.X, min_c2.Y, worldPos.X, worldPos.Y); for (int i = 1; i < index; i++) { c1 = coordinates[i - 1]; c2 = coordinates[i]; pointDistance += Distance(c1.X, c1.Y, c2.X, c2.Y); } pointDistance += Distance(min_c1.X, min_c1.Y, location.X, location.Y); } return pointDistance; } /// /// Returns the minimum distance of to . /// /// /// /// static public double Distance(ILineString lineString, IGeometry geometry) { double minDistance = Double.MaxValue; if (lineString == null) return minDistance; ICoordinate c1; ICoordinate c2; ICoordinate[] coordinates = lineString.Coordinates; if (geometry is IPoint) { var point = (IPoint)geometry; for (int i = 1; i < coordinates.Length; i++) { c1 = coordinates[i - 1]; c2 = coordinates[i]; double distance = LinePointDistance(c1.X, c1.Y, c2.X, c2.Y, point.X, point.Y); if (distance < minDistance) { minDistance = distance; } } } else if (geometry is ILineString) { return LineStringFirstIntersectionOffset(lineString, (ILineString)geometry); } else if(geometry != null) { return lineString.Distance(geometry); /* TODO: check if above code doesn't work good for line string - use trickery trick below // trickery trick someone changed cross section geometry from point to linestring // non geometry based cross sections are represented using a 2 point linestring. // Use the center of this linestring IPoint crossSectionCenter = GeometryFactory.CreatePoint(CrossSectionHelper.CrossSectionCoordinate(crossSection)); double distance = GeometryHelper.Distance((ILineString)branch.Geometry, crossSectionCenter); float limit; if (map != null) { limit = (float)MapControlHelper.ImageToWorld(map, 1); } else { limit = (float)(0.1 * Math.Max(branch.Geometry.EnvelopeInternal.Width, branch.Geometry.EnvelopeInternal.Height)); } if (distance < limit) { crossSection.Branch = branch; CalculateCrossSectionOffset(crossSection); CrossSectionHelper.UpdateDefaultGeometry(crossSection, crossSection.Geometry.Length / 2); } */ } return minDistance; } //http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry1 //Line-Point Distance = (AB x AC)/|AB|. // //Compute the distance from A to B // double distance(int[] A, int[] B){ // int d1 = A[0] - B[0]; // int d2 = A[1] - B[1]; // return sqrt(d1*d1+d2*d2); // } static public double Distance(double x1, double y1, double X2, double Y2) { return Math.Sqrt((x1 - X2)*(x1 - X2) + (y1 - Y2)*(y1 - Y2)); } //http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry1 //Line-Point Distance = (AB x AC)/|AB|. // //Compute the cross product AB x AC // int cross(int[] A, int[] B, int[] C){ // AB = new int[2]; // AC = new int[2]; // AB[0] = B[0]-A[0]; // AB[1] = B[1]-A[1]; // AC[0] = C[0]-A[0]; // AC[1] = C[1]-A[1]; // int cross = AB[0] * AC[1] - AB[1] * AC[0]; // return cross; // } static public double CrossProduct(double Ax, double Ay, double Bx, double By, double cx , double cy ) { return (Bx - Ax)*(cy - Ay) - (By - Ay)*(cx - Ax); } //http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry1 //Line-Point Distance = (AB x AC)/|AB|. ////Compute the dot product AB · BC // int dot(int[] A, int[] B, int[] C){ // AB = new int[2]; // BC = new int[2]; // AB[0] = B[0]-A[0]; // AB[1] = B[1]-A[1]; // BC[0] = C[0]-B[0]; // BC[1] = C[1]-B[1]; // int dot = AB[0] * BC[0] + AB[1] * BC[1]; // return dot; // } static public double Dot(double Ax, double Ay, double Bx, double By, double cx, double cy) { return (Bx - Ax)*(cx - Bx) + (By - Ay)*(cy - By); } //http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry1 //Line-Point Distance = (AB x AC)/|AB|. ////Compute the distance from AB to C // //if isSegment is true, AB is a segment, not a line. // double linePointDist(int[] A, int[] B, int[] C, boolean isSegment){ // double dist = cross(A,B,C) / distance(A,B); // if(isSegment){ // int dot1 = dot(A,B,C); // if(dot1 > 0)return distance(B,C); // int dot2 = dot(B,A,C); // if(dot2 > 0)return distance(A,C); // } // return abs(dist); // } static public double LinePointDistance(double Ax, double Ay, double Bx, double By, double cx, double cy) { double dist, dot1, dot2; dist = Distance(Ax, Ay, Bx, By); if (dist < 0.000001) { return Double.MaxValue; } dist = CrossProduct(Ax, Ay, Bx, By, cx, cy)/dist; // if (isSegment) always true dot1 = Dot(Ax, Ay, Bx, By, cx, cy); if (dot1 > 0) return Distance(Bx, By, cx, cy); dot2 = Dot(Bx, By, Ax, Ay, cx, cy); if (dot2 > 0) return Distance(Ax, Ay, cx, cy); return Math.Abs(dist); } static public ICoordinate NearestPointAtSegment(double Ax, double Ay, double Bx, double By, double cx, double cy) { // if (AB . BC) > 0) if (Dot(Ax, Ay, Bx, By, cx, cy) > 0) { return GeometryFactory.CreateCoordinate(Bx, By); } // else if ((BA . AC) > 0) else if (Dot(Bx, By, Ax, Ay, cx, cy) > 0) { return GeometryFactory.CreateCoordinate(Ax, Ay); } else // both dot products < 0 -> point between A and B { double AC = Distance(Ax, Ay, cx, cy); double BC = Distance(Bx, By, cx, cy); return GeometryFactory.CreateCoordinate(Ax + ((AC) / (AC + BC))*(Bx-Ax), Ay + ((AC) / (AC + BC)) * (By - Ay)); } } public static IGeometry SetCoordinate(IGeometry geometry, int coordinateIndex, ICoordinate coordinate) { var newGeometry = (IGeometry)geometry.Clone(); newGeometry.Coordinates[coordinateIndex].X = coordinate.X; newGeometry.Coordinates[coordinateIndex].Y = coordinate.Y; newGeometry.GeometryChangedAction(); return newGeometry; } public static void MoveCoordinate(IGeometry geometry, int coordinateIndex, double deltaX, double deltaY) { geometry.Coordinates[coordinateIndex].X += deltaX; geometry.Coordinates[coordinateIndex].Y += deltaY; geometry.GeometryChangedAction(); } public static void MoveCoordinate(IGeometry targetGeometry, IGeometry sourceGeometry, int coordinateIndex, double deltaX, double deltaY) { MoveCoordinate(targetGeometry.Coordinates, sourceGeometry.Coordinates, coordinateIndex, deltaX, deltaY); targetGeometry.GeometryChangedAction(); } public static void MoveCoordinate(ICoordinate[] targetCoordinates, ICoordinate[] sourceCoordinates, int coordinateIndex, double deltaX, double deltaY) { targetCoordinates[coordinateIndex].X = sourceCoordinates[coordinateIndex].X + deltaX; targetCoordinates[coordinateIndex].Y = sourceCoordinates[coordinateIndex].Y + deltaY; } /// /// Returns the coordinate at an offset of the lineString /// /// /// /// /// /// Be aware that this function can suffer from double precision issue, like: 100.0*0.55 evaluates to 55.000000000000007 /// public static ICoordinate LineStringCoordinate(ILineString lineString, double distance) { double partialDistance = 0; ICoordinate[] coordinates = lineString.Coordinates; for (int i = 1; i < coordinates.Length; i++) { ICoordinate c1 = coordinates[i - 1]; ICoordinate c2 = coordinates[i]; double segmentDistance = Distance(c1.X, c1.Y, c2.X, c2.Y); if ((partialDistance + segmentDistance) > distance) { double factor = (distance - partialDistance)/(segmentDistance); return GeometryFactory.CreateCoordinate( coordinates[i - 1].X + factor * (coordinates[i].X - coordinates[i - 1].X), coordinates[i - 1].Y + factor * (coordinates[i].Y - coordinates[i - 1].Y)); } partialDistance += segmentDistance; } return (ICoordinate) lineString.Coordinates[lineString.Coordinates.Length-1].Clone(); } /// /// Returns the offset to the first intersection of lineString by cutLineString. /// /// /// /// offset to the first intersection, -1 if there is no intersection -1 public static double LineStringFirstIntersectionOffset(ILineString lineString, ILineString cutLineString) { if(!lineString.Intersects(cutLineString)) { return -1; } IGeometry intersection = lineString.Difference(cutLineString); if (intersection is IMultiLineString) { var result = (IMultiLineString) lineString.Difference(cutLineString); return result.Geometries[0].Length; } return intersection.Length; } public static ICoordinate GetNearestPointAtLine(ILineString lineString, ICoordinate coordinate, double tolerance, out int snapVertexIndex) { snapVertexIndex = -1; ICoordinate nearestPoint = null; ICoordinate minC1; ICoordinate minC2; for (var i = 1; i < lineString.Coordinates.Length; i++) { var c1 = lineString.Coordinates[i - 1]; var c2 = lineString.Coordinates[i]; var distance = LinePointDistance(c1.X, c1.Y, c2.X, c2.Y, coordinate.X, coordinate.Y); if (distance >= tolerance) { continue; } tolerance = distance; minC1 = c1; minC2 = c2; nearestPoint = NearestPointAtSegment(minC1.X, minC1.Y, minC2.X, minC2.Y, coordinate.X, coordinate.Y); snapVertexIndex = i; } return nearestPoint; } public static IFeature GetNearestFeature(ICoordinate coordinate, IEnumerable features, double tolerance) { var minDistance = tolerance; IFeature minDistanceFeature = null; var point = new Point(coordinate); foreach (var feature in features) { if(feature.Geometry == null) continue; var distance = point.Distance(feature.Geometry); if (distance <= minDistance) { minDistance = distance; minDistanceFeature = feature; } } return minDistanceFeature; } public static IEnumerable GetFeaturesInRange(ICoordinate coordinate, IEnumerable features, double tolerance) { var minDistance = tolerance; var point = new Point(coordinate); return (from feature in features where feature.Geometry != null // Distance method requires a defined Geometry let distance = point.Distance(feature.Geometry) where distance <= minDistance select feature).ToList(); } /// /// Splits the polygon at splitPointX and returns a left and right half /// /// /// /// public static DelftTools.Utils.Tuple SplitGeometryVerticalAt(IGeometry geometry, double splitPointX) { ThrowIfArgumentInvalid(geometry, splitPointX); var rightHalf = GetRightHalfGeometry(geometry, splitPointX); var leftHalf = GetLeftHalfGeometry(geometry, splitPointX); return new DelftTools.Utils.Tuple(leftHalf, rightHalf); } private static void ThrowIfArgumentInvalid(IGeometry polygon, double splitPointX) { if (polygon.EnvelopeInternal.MinX > splitPointX || polygon.EnvelopeInternal.MaxX < splitPointX) throw new ArgumentOutOfRangeException("splitPointX",string.Format("Splitpoint at x {0:0.00} not within polygon. ", splitPointX)); } private static IGeometry GetLeftHalfGeometry(IGeometry geometry, double splitPointX) { double minXValue = geometry.EnvelopeInternal.MinX - 1; double minYValue = geometry.EnvelopeInternal.MinY - 1; double maxYValue = geometry.EnvelopeInternal.MaxY + 1; var coordinatesLeft= new[] { new Coordinate(minXValue, maxYValue), new Coordinate(splitPointX, maxYValue), new Coordinate(splitPointX, minYValue), new Coordinate(minXValue, minYValue), new Coordinate(minXValue, maxYValue) }; // var leftRectangle = new Polygon(new LinearRing(coordinatesLeft)); return geometry.Intersection(leftRectangle); } private static IGeometry GetRightHalfGeometry(IGeometry geometry, double splitPointX) { double maxXValue = geometry.EnvelopeInternal.MaxX + 1; double minYValue = geometry.EnvelopeInternal.MinY - 1; double maxYValue = geometry.EnvelopeInternal.MaxY + 1; var coordinatesRight = new[] { new Coordinate(splitPointX, maxYValue), new Coordinate(splitPointX, minYValue), new Coordinate(maxXValue, minYValue), new Coordinate(maxXValue, maxYValue), new Coordinate(splitPointX, maxYValue) }; // var leftRectangle = new Polygon(new LinearRing(coordinatesRight)); return geometry.Intersection(leftRectangle); } public static IGeometry NormalizeGeometry(IGeometry geometryToNormalize) { if (geometryToNormalize is IPoint) { return geometryToNormalize; } if (geometryToNormalize is IGeometryCollection) { var geometries = (IGeometryCollection)geometryToNormalize.Clone(); for (int i=0;i resultCoordinates) { var removed = false; int count = resultCoordinates.Count; for (int i = 0; i < count;i++ ) { var nextPoint = resultCoordinates[(i + 1) %count]; //make sure the index is always positive int previousPointIndex = ((i+count) - 1) % count; var previousPoint = resultCoordinates[previousPointIndex]; var point = resultCoordinates[i]; if ((PointIsSame(point, nextPoint)) || (PointIsOnLineBetweenPreviousAndNext(previousPoint, point, nextPoint))) { resultCoordinates.RemoveAt(i%count); removed = true; break; } } return removed; } public static bool PointIsSame(ICoordinate point,ICoordinate previousPoint) { return point.X == previousPoint.X && point.Y == previousPoint.Y; } public static bool PointIsOnLineBetweenPreviousAndNext(ICoordinate previousPoint, ICoordinate point, ICoordinate nextPoint) { //vertical if (nextPoint.X == point.X && point.X == previousPoint.X) return true; //one part is vertical..so not redundant if (nextPoint.X == point.X || point.X == previousPoint.X) return false; //check the x is monotonous and the point.x if between the other points bool monotonous = (previousPoint.X < point.X && point.X < nextPoint.X ) || (previousPoint.X > point.X && point.X > nextPoint.X ); if (!monotonous) return false; var nextRiCo = (nextPoint.Y - point.Y)/(nextPoint.X - point.X); var previousRiCo = (point.Y - previousPoint.Y)/(point.X - previousPoint.X); //the point slope is equal after and before the point return (Math.Abs(nextRiCo - previousRiCo) < 0.0001); } public static double GetIntersectionArea(IGeometry geometry, IGeometry other) { if (geometry is IMultiPolygon) { return IntersectionArea(geometry as IMultiPolygon, other); } return IntersectionArea(geometry, other); } private static double IntersectionArea(IMultiPolygon multiPolygon, IGeometry other) { var area = 0.0; foreach (var polygon in multiPolygon.Geometries.Cast()) { area += IntersectionArea(polygon, other); } return area; } private static double IntersectionArea(IGeometry geometry, IGeometry other) { if (geometry.Intersects(other.Envelope)) { OverlayOp.NodingValidatorDisabled = true; double area; try { area = geometry.Intersection(other).Area; } catch (Exception) { area = GetSampledIntersectionArea(geometry, other); } OverlayOp.NodingValidatorDisabled = false; return area; } return 0.0; } public static double GetSampledIntersectionArea(IGeometry geometry, IGeometry other) { const int steps = 10; var area = 0.0; var envelopeInternal = geometry.EnvelopeInternal; var width = envelopeInternal.Width; var height = envelopeInternal.Height; var widthStep = width/steps; var heightStep = height/steps; var minX = envelopeInternal.MinX + widthStep/2.0; var minY = envelopeInternal.MinY + heightStep/2.0; var boxArea = width*height; var cellArea = boxArea/(steps*steps); for (var i = 0; i < steps; i++) { var x = minX + i*widthStep; for(var j = 0; j < steps; j++) { var y = minY + j*heightStep; if (geometry.Contains(new Point(x, y)) && other.Contains(new Point(x,y))) { area += cellArea; } } } return area; } public static IGeometry InsertCurvePoint(IGeometry geometry, ICoordinate coordinate, int index) { var vertices = new List(geometry.Coordinates); vertices.Insert(index, coordinate); var geometryFactory = new GeometryFactory(); if (geometry is ILineString) { return geometryFactory.CreateLineString(vertices.ToArray()); } if (geometry is IPolygon) { return geometryFactory.CreatePolygon(geometryFactory.CreateLinearRing(vertices.ToArray()), null); } return geometry.Union(geometryFactory.CreatePoint(coordinate)); } public static IGeometry RemoveCurvePoint(IGeometry geometry, int index, bool keepLineStringEndPoints=false) { var vertices = new List(geometry.Coordinates); vertices.RemoveAt(index); var geometryFactory = new GeometryFactory(); var lastIndex = geometry.Coordinates.Length - 1; if (geometry is ILineString) { if (vertices.Count < 2) { return null; } if (keepLineStringEndPoints && (index == 0 || index == lastIndex)) { return null; } return geometryFactory.CreateLineString(vertices.ToArray()); } if (geometry is IPolygon) { // If first or last index is removed -> remove corresponding duplicate at the other end and close the ring. if (index == lastIndex) { vertices[0] = vertices[lastIndex]; } if (index == 0) { vertices[lastIndex - 1] = vertices[0]; } if (vertices.Count < 4) { return null; } return geometryFactory.CreatePolygon(geometryFactory.CreateLinearRing(vertices.ToArray()), null); } if (index < geometry.Coordinates.Length) { var coordinate = geometry.Coordinates[index]; var point = geometryFactory.CreatePoint(coordinate); return geometry.Difference(point); } return geometry; } } }