//Check if the lines are interescting in 2d space //Alternative version from http://thirdpartyninjas.com/blog/2008/10/07/line-segment-intersection/ public static bool LineLineIntersection(out GeoPoint intersection, GeoSegment line1, GeoSegment line2) { bool isIntersecting = false; intersection = GeoPoint.Zero; //3d -> 2d double p1_x = line1.Start.Longitude; double p1_y = line1.Start.Latitude; double p2_x = line1.End.Longitude; double p2_y = line1.End.Latitude; double p3_x = line2.Start.Longitude; double p3_y = line2.Start.Latitude; double p4_x = line2.End.Longitude; double p4_y = line2.End.Latitude; double denominator = (p4_y - p3_y) * (p2_x - p1_x) - (p4_x - p3_x) * (p2_y - p1_y); //Make sure the denominator is > 0, if so the lines are parallel if (denominator != 0) { double u_a = ((p4_x - p3_x) * (p1_y - p3_y) - (p4_y - p3_y) * (p1_x - p3_x)) / denominator; double u_b = ((p2_x - p1_x) * (p1_y - p3_y) - (p2_y - p1_y) * (p1_x - p3_x)) / denominator; //Is intersecting if u_a and u_b are between 0 and 1 if (u_a >= 0 && u_a <= 1 && u_b >= 0 && u_b <= 1) { intersection = new GeoPoint(p1_y + u_a * (p2_y - p1_y), p1_x + u_a * (p2_x - p1_x)); isIntersecting = true; } } return(isIntersecting); }
public BottomProfile(int numberOfPointsInTransect, GeoSegment geoSegment , Bathymetry bathymetry) { MaxDepth = double.MinValue; Profile = new List<BottomProfilePoint>(); Length = Geo.RadiansToMeters(geoSegment.LengthRadians); //Length = transect.StartPoint.DistanceKilometers(transect.EndPoint) * 1000; var stepLength = Length / (numberOfPointsInTransect - 1); var stepFraction = 1.0 / numberOfPointsInTransect; //var currentPoint = transect.StartPoint; var currentPoint = geoSegment[0]; var curRange = 0.0; for (var i = 0; i < numberOfPointsInTransect; i++) { var curDepth = Math.Round(-1.0 * TwoDBilinearApproximation(bathymetry, currentPoint), 2); Profile.Add(new BottomProfilePoint { Depth = curDepth, Range = (curRange / 1000.0) }); if (MaxDepth < curDepth) { MaxDepth = curDepth; DeepestPoint = currentPoint; } currentPoint = geoSegment.Slerp(stepFraction * i); //currentPoint = currentPoint.Offset(Geo.KilometersToRadians(stepLength / 1000f), Geo.DegreesToRadians(transect.Bearing)); curRange += stepLength; } //Profile = profile.ToList(); }
public IEnumerable <GeoSegment> GetDEMNorthSouthLines(List <FileMetadata> segTiles, GeoPoint westernSegPoint, GeoPoint easternSegPoint) { // Get the first north west tile and last south east tile. // The lines are bounded by those tiles foreach (var tilesByX in segTiles.GroupBy(t => t.StartLon).OrderBy(g => g.Key)) { List <FileMetadata> NSTilesOrdered = tilesByX.OrderByDescending(t => t.StartLat).ToList(); FileMetadata top = NSTilesOrdered.First(); FileMetadata bottom = NSTilesOrdered.Last(); // TIP: can optimize here starting with min(westernSegPoint, startlon) but careful ! GeoPoint curPoint = new GeoPoint(top.StartLat, top.StartLon); // X Index in tile coords int curIndex = (int)Math.Ceiling((curPoint.Longitude - top.StartLon) / top.PixelScaleX); while (IsPointInTile(top, curPoint)) { if (curIndex >= top.Width) { break; } curPoint.Longitude = top.StartLon + (top.pixelSizeX * curIndex); if (curPoint.Longitude > easternSegPoint.Longitude) { break; } GeoSegment line = new GeoSegment(new GeoPoint(top.OriginLatitude, curPoint.Longitude), new GeoPoint(bottom.EndLatitude, curPoint.Longitude)); curIndex++; yield return(line); } } }
public IEnumerable <GeoSegment> GetDEMWestEastLines(List <FileMetadata> segTiles, GeoPoint northernSegPoint, GeoPoint southernSegPoint) { // Get the first north west tile and last south east tile. // The lines are bounded by those tiles foreach (var tilesByY in segTiles.GroupBy(t => t.StartLat).OrderByDescending(g => g.Key)) { List <FileMetadata> WETilesOrdered = tilesByY.OrderBy(t => t.StartLon).ToList(); FileMetadata left = WETilesOrdered.First(); FileMetadata right = WETilesOrdered.Last(); GeoPoint curPoint = new GeoPoint(left.StartLat, left.StartLon); // Y Index in tile coords int curIndex = (int)Math.Ceiling((left.StartLat - curPoint.Latitude) / left.PixelScaleY); while (IsPointInTile(left, curPoint)) { if (curIndex >= left.Height) { break; } curPoint.Latitude = left.StartLat + (left.pixelSizeY * curIndex); if (curPoint.Latitude < southernSegPoint.Latitude) { break; } GeoSegment line = new GeoSegment(new GeoPoint(curPoint.Latitude, left.OriginLongitude), new GeoPoint(curPoint.Latitude, right.EndLongitude)); curIndex++; yield return(line); } } }
private static void ValidateGeoSegment(GeoSegment segment, SectorElementCollection sectorElements, IEventLogger events) { if (!ColourValid(segment.Colour, sectorElements)) { string errorMessage = $"Invalid colour value {segment.Colour} in GEO segment {segment.GetCompileData(sectorElements)}"; events.AddEvent(new ValidationRuleFailure(errorMessage, segment)); } }
private static void ValidateGeoSegment(GeoSegment segment, SectorElementCollection sectorElements, IEventLogger events) { if (!PointsValid(segment.FirstPoint, segment.SecondPoint, sectorElements)) { string message = $"Invalid waypoint on GEO segment: {segment.GetCompileData(sectorElements)}"; events.AddEvent( new ValidationRuleFailure(message, segment) ); } }
public void FullEarthRectContainsAverageSegment() { Rect fullEarth = GetFullEarthRect(); GeoCoordinates segA = new GeoCoordinates(0, -10); GeoCoordinates segB = new GeoCoordinates(10, 10); GeoSegment seg = new GeoSegment(segA, segB); Assert.True(GeoRectUtils.RectNearSegment(seg, fullEarth, 0)); }
public void FullEarthRectContainsSegmentCrossingPrimeMeridian() { Rect fullEarth = GetFullEarthRect(); GeoCoordinates segA = new GeoCoordinates(-10, 170); GeoCoordinates segB = new GeoCoordinates(10, 190); GeoSegment seg = new GeoSegment(segA, segB); Assert.True(GeoRectUtils.RectNearSegment(seg, fullEarth, 0)); }
public GeoSegmentTest() { this.segment = new GeoSegment( new Point(new Coordinate("abc", "def")), new Point(new Coordinate("ghi", "jkl")), "red", DefinitionFactory.Make(), DocblockFactory.Make(), CommentFactory.Make() ); }
public void DistanceToPointForSmallSegmentNotCrossingPrimeMeridian() { Vector2 point = new Vector2(1, 1); Vector2 segA = new Vector2(-1, 0); Vector2 segB = new Vector2(1, 0); GeoCoordinates geoPoint = new GeoCoordinates(point.y, point.x); GeoSegment geoSegment = new GeoSegment( new GeoCoordinates(segA.y, segA.x), new GeoCoordinates(segB.y, segB.x)); Assert.Equal( Geometry2d.PointToSegmentDistance(segA, segB, point), geoSegment.DistanceToPoint(geoPoint), 6); }
public void RectIsntNearSegment() { Rect rect = new Rect { xMin = -10, xMax = 10, yMin = -10, yMax = 10 }; GeoCoordinates segA = new GeoCoordinates(11, -20); GeoCoordinates segB = new GeoCoordinates(11, 20); GeoSegment seg = new GeoSegment(segA, segB); Assert.False(GeoRectUtils.RectNearSegment(seg, rect, 0.5)); }
public void RectNearSegmentWorksWithRectCrossingPrimeMeridian2() { Rect rect = new Rect { xMin = -185, xMax = -175, yMin = -10, yMax = 10 }; GeoCoordinates segA = new GeoCoordinates(-30, -177); GeoCoordinates segB = new GeoCoordinates(30, -177); GeoSegment seg = new GeoSegment(segA, segB); Assert.True(GeoRectUtils.RectNearSegment(seg, rect, 0)); }
public void GeoRectIsNearSegmentItContains() { Rect rect = new Rect { xMin = -10, xMax = 10, yMin = -10, yMax = 10 }; GeoCoordinates segA = new GeoCoordinates(-1, 0); GeoCoordinates segB = new GeoCoordinates(1, 0); GeoSegment seg = new GeoSegment(segA, segB); Assert.True(GeoRectUtils.RectNearSegment(seg, rect, 0)); }
public void DistanceToPointForSegmentCrossingPrimeMeridian() { Vector2 point = new Vector2(-180, 0); Vector2 segA = new Vector2(175, -10); Vector2 segB = new Vector2(185, 10); GeoCoordinates geoPoint = new GeoCoordinates(point.y, point.x); GeoSegment geoSegment = new GeoSegment( new GeoCoordinates(segA.y, segA.x), new GeoCoordinates(segB.y, segB.x)); Assert.Equal( 0, geoSegment.DistanceToPoint(geoPoint), 6); }
public void GeoRectIsNearSegmentThatIntersectsItsEdge() { Rect rect = new Rect { xMin = -10, xMax = 10, yMin = -10, yMax = 10 }; GeoCoordinates segA = new GeoCoordinates(0, -20); GeoCoordinates segB = new GeoCoordinates(0, 20); GeoSegment seg = new GeoSegment(segA, segB); Assert.True(GeoRectUtils.RectNearSegment(seg, rect, 0)); }
public void GeoRectIsWithinDistanceOfSegment() { Rect rect = new Rect { xMin = -10, xMax = 10, yMin = -10, yMax = 10 }; GeoCoordinates segA = new GeoCoordinates(11, -20); GeoCoordinates segB = new GeoCoordinates(11, 20); GeoSegment seg = new GeoSegment(segA, segB); Assert.True(GeoRectUtils.RectNearSegment(seg, rect, 1 + 1e-10)); }
public void SmallRectContainsSegmentCrossingPrimeMeridianOnOppositeSide() { Rect rect = new Rect { xMin = -181, xMax = -179, yMin = 10, yMax = 13 }; GeoCoordinates segA = new GeoCoordinates(11, 170); GeoCoordinates segB = new GeoCoordinates(11, 190); GeoSegment seg = new GeoSegment(segA, segB); Assert.True(GeoRectUtils.RectNearSegment(seg, rect, 0)); }
/// <summary> /// Compute the result of a specular reflection between incidentSegment and edgeSegment. /// If incidentSegment and edgeSegment do not intersect somewhere along both of their /// lengths, null is returned. The returned segment is one meter long, originating /// at the point of reflection and terminating one meter along the reflected path. /// </summary> /// <param name="incidentSegment"></param> /// <param name="edgeSegment"></param> /// <returns>Angle of reflection, in radians</returns> public static GeoSegment Reflect(this GeoSegment incidentSegment, GeoSegment edgeSegment) { var intersectionPoint = incidentSegment.Intersection(edgeSegment); if (intersectionPoint == null) return null; var intersectionFraction1 = new GeoSegment(incidentSegment[0], intersectionPoint).LengthRadians / incidentSegment.LengthRadians; var intersectionFraction2 = new GeoSegment(edgeSegment[0], intersectionPoint).LengthRadians / edgeSegment.LengthRadians; var fiftyCm1 = 1 / (Geo.RadiansToKilometers(incidentSegment.LengthRadians) * 2000); var fiftyCm2 = 1 / (Geo.RadiansToKilometers(edgeSegment.LengthRadians) * 2000); // Create two one-meter-long segments, centered on the intersection point so we can pretend we're doing geometry on a plane var incidentUnit = incidentSegment.SubSegment(intersectionFraction1 - fiftyCm1, intersectionFraction1 + fiftyCm1); var edgeUnit = edgeSegment.SubSegment(intersectionFraction2 - fiftyCm2, intersectionFraction2 + fiftyCm2); var incidentVector = new SurfaceVector(incidentUnit); var edgeVector = new SurfaceVector(edgeUnit); var rightNormal = edgeVector.Rotate(-MoreMath.PiOverTwo); var isRightNormal = incidentVector.Dot(rightNormal) <= 0; var normal = isRightNormal ? rightNormal : edgeVector.Rotate(MoreMath.PiOverTwo); var reflectionVector = incidentVector - normal.Scale(2 * normal.Dot(incidentVector)); return new GeoSegment(intersectionPoint, intersectionPoint.Offset(Geo.KilometersToRadians(0.001), reflectionVector.GeoAzimuth)); }
/// <summary> /// Bounce a platform around inside a given perimeter /// </summary> /// <param name="perimeter">A GeoArray of points in the perimeter, which must satisfy certain conditions (no segments may cross and the polygon /// must be closed)</param> /// <param name="startPoint">A Geo containing the starting point. If this is null, a point inside the perimeter is chosen randomly</param> /// <param name="initialCourse">The initial course, in radians from true north. If startPoint is null, or if initialCourse is NaN, this is /// chosen randomly</param> /// <param name="minimumCourseLength">The minimum length of the returned course, in meters</param> /// <returns>A GeoArray containing the start location and each point where the course bounces off the perimeter</returns> public static GeoArray PerimeterBounce(this GeoArray perimeter, Geo startPoint, double initialCourse, double minimumCourseLength) { if (!perimeter.IsClosed) throw new InvalidPerimeterException("Perimeter is not closed"); if (perimeter.HasCrossingSegments) throw new InvalidPerimeterException("Perimeter is not a simple polygon (segments cross each other)"); var points = new List<Geo>(); while ((startPoint == null) || (!startPoint.IsInside(perimeter))) { var distance = Random.NextDouble() * perimeter.BoundingCircle.Radius; var azimuth = Random.NextDouble() * MoreMath.TwoPi; startPoint = perimeter.Center.Offset(distance, azimuth); } points.Add(startPoint); if (double.IsNaN(initialCourse)) initialCourse = Random.NextDouble()*MoreMath.TwoPi; var courseSegment = new GeoSegment(startPoint, startPoint.Offset(Geo.KilometersToRadians(0.001), initialCourse)).Scale(1000 * Geo.RadiansToKilometers(MoreMath.PiOverTwo)); var bounceCount = 1; while (minimumCourseLength > 0) { var minIntersectionRange = double.MaxValue; GeoSegment firstIntersectingSegment = null; Geo firstIntersection = null; foreach (var segment in perimeter.Segments) { var intersection = courseSegment.Intersection(segment); if (intersection == null) continue; var curIntersectionRange = startPoint.DistanceRadians(intersection); if (curIntersectionRange < Geo.KilometersToRadians(0.001) || curIntersectionRange >= minIntersectionRange) continue; minIntersectionRange = curIntersectionRange; firstIntersectingSegment = segment; firstIntersection = intersection; } if (firstIntersection == null) throw new PerimeterBounceException(string.Format("Course segment failed to intersect the perimeter on bounce {0}", bounceCount)); var actualCourseSegment = new GeoSegment(points.Last(), firstIntersection); minimumCourseLength -= Geo.RadiansToKilometers(actualCourseSegment.LengthRadians) * 1000; points.Add(firstIntersection); var reflectedSegment = courseSegment.Reflect(firstIntersectingSegment); var reflectionAzimuth = reflectedSegment[0].Azimuth(reflectedSegment[1]); courseSegment = new GeoSegment(reflectedSegment[1], 1000 * Geo.RadiansToKilometers(MoreMath.PiOverTwo), Geo.RadiansToDegrees(reflectionAzimuth)); bounceCount++; } return new GeoArray(points); }
public void LineLineIntersectionTest() { string wkt1 = "LINESTRING(-5.888671875 47.90161354142077,3.4716796875 44.11914151643737)"; string wkt2 = "LINESTRING(-2.8564453125 44.30812668488613,5.625 48.166085419012525)"; Geometry geom1 = GeometryService.ParseWKTAsGeometry(wkt1); Geometry geom2 = GeometryService.ParseWKTAsGeometry(wkt2); Geometry intersection = geom1.Intersection(geom2); GeoSegment seg1 = geom1.Segments().First(); GeoSegment seg2 = geom2.Segments().First(); GeoPoint intersectionResult = GeoPoint.Zero; bool intersects = GeometryService.LineLineIntersection(out intersectionResult, seg1, seg2); double dist = intersection.Coordinate.ToGeoPoint().DistanceTo(intersectionResult); Assert.True(dist < 0.05d, "Problem in intersection calculation."); }
public void LineLineIntersectionTest() { string wkt1 = "LINESTRING(-5.888671875 47.90161354142077,3.4716796875 44.11914151643737)"; string wkt2 = "LINESTRING(-2.8564453125 44.30812668488613,5.625 48.166085419012525)"; SqlGeometry geom1 = GeometryService.ParseWKTAsGeometry(wkt1); SqlGeometry geom2 = GeometryService.ParseWKTAsGeometry(wkt2); SqlGeometry intersection = geom1.STIntersection(geom2); GeoSegment seg1 = new GeoSegment(new GeoPoint(geom1.STStartPoint().STY.Value, geom1.STStartPoint().STX.Value), new GeoPoint(geom1.STEndPoint().STY.Value, geom1.STEndPoint().STX.Value)); GeoSegment seg2 = new GeoSegment(new GeoPoint(geom2.STStartPoint().STY.Value, geom2.STStartPoint().STX.Value), new GeoPoint(geom2.STEndPoint().STY.Value, geom2.STEndPoint().STX.Value)); GeoPoint intersectionResult = GeoPoint.Zero; bool intersects = GeometryService.LineLineIntersection(out intersectionResult, seg1, seg2); SqlGeography geog1 = null; intersection.TryToGeography(out geog1); SqlGeography geog2 = SqlGeography.Point(intersectionResult.Latitude, intersectionResult.Longitude, 4326); double dist = geog1.STDistance(geog2).Value; Assert.IsTrue(dist < 0.05d, "Problem in intersection calculation."); }
/// <summary> /// Is geo on the specified segment? /// </summary> /// <param name="segment"></param> /// <param name="geo"></param> /// <returns>true if geo is on segment, false otherwise</returns> public static bool IsOn(this Geo geo, GeoSegment segment) { return (MoreMath.IsApproximatelyEqual(Math.Abs(segment[0].Cross(segment[1]).Normalized.Dot(geo)), 0.0) && (segment[0].DistanceRadians(geo) <= segment[0].DistanceRadians(segment[1])) && (segment[1].DistanceRadians(geo) <= segment[1].DistanceRadians(segment[0]))); }
/// <summary> /// Calculates the great circle distance from geo to the great circle described by segment. /// </summary> /// <param name="geo"></param> /// <param name="segment"></param> /// <returns>distance, in radians</returns> public static double DistanceToGreatCircle(this Geo geo, GeoSegment segment) { var cosTheta = segment.Normal.Dot(geo.Normalized); var theta = Math.Acos(cosTheta); return Math.Abs(Math.PI / 2 - theta); }
/// <summary> /// Recursively calculates the nearest sound speed profiles along a given radial using a binary search-like algorithm /// 1. If start and end points are provided, use them, otherwise find the nearest SSP to each of those points /// 2. If the start point was calculated, add the SSP closest to the calculated start point to the enumerable /// 2. If the SSPs closest to the start and end points are within 10m of each other they are considered identical and there are /// assumed to be no more intervening points /// 3. If the SSPs closest to the start and end points are NOT within 10m of each other, calculate the midpoint of the segment /// and find the nearest SSP to that point. /// 4. If the SSP nearest the midpoint is not within 10m of the SSP nearest to the start point, recursively call this function to /// find the new midpoint between the start point and the current midpoint /// 5. Return the /// </summary> /// <param name="segment"></param> /// <param name="startDistance"></param> /// <param name="startProfile"></param> /// <param name="endProfile"></param> /// <param name="bottomProfile"></param> /// <param name="soundSpeedData"></param> /// <param name="deepestProfile"></param> /// <returns></returns> static IEnumerable<Tuple<double, SoundSpeedProfile>> ProfilesAlongRadial(GeoSegment segment, double startDistance, SoundSpeedProfile startProfile, SoundSpeedProfile endProfile, BottomProfile bottomProfile, EnvironmentData<SoundSpeedProfile> soundSpeedData, SoundSpeedProfile deepestProfile) { var returnStartProfile = false; var returnEndProfile = false; if (startProfile == null) { returnStartProfile = true; startProfile = soundSpeedData.IsFast2DLookupAvailable ? soundSpeedData.GetNearestPointAsync(segment[0]).Result.Extend(deepestProfile) : soundSpeedData.GetNearestPoint(segment[0]).Extend(deepestProfile); } if (endProfile == null) { returnEndProfile = true; endProfile = soundSpeedData.IsFast2DLookupAvailable ? soundSpeedData.GetNearestPointAsync(segment[1]).Result.Extend(deepestProfile) : soundSpeedData.GetNearestPoint(segment[1]).Extend(deepestProfile); } if (returnStartProfile) yield return Tuple.Create(NearestBottomProfileDistanceTo(bottomProfile, startDistance), startProfile); // If the start and end profiles are the same, we're done if (startProfile.DistanceKilometers(endProfile) <= 0.01) yield break; // If not, create a middle profile var middleProfile = soundSpeedData.IsFast2DLookupAvailable ? soundSpeedData.GetNearestPointAsync(segment.Center).Result.Extend(deepestProfile) : soundSpeedData.GetNearestPoint(segment.Center).Extend(deepestProfile); // If the center profile is different from BOTH endpoints if (startProfile.DistanceKilometers(middleProfile) > 0.01 && middleProfile.DistanceKilometers(endProfile) > 0.01) { // Recursively create and return any new sound speed profiles between the start and the center var firstHalfSegment = new GeoSegment(segment[0], segment.Center); foreach (var tuple in ProfilesAlongRadial(firstHalfSegment, startDistance, startProfile, middleProfile, bottomProfile, soundSpeedData, deepestProfile)) yield return tuple; var centerDistance = startDistance + Geo.RadiansToKilometers(segment[0].DistanceRadians(segment.Center)); // return the center profile yield return Tuple.Create(NearestBottomProfileDistanceTo(bottomProfile, centerDistance), middleProfile); // Recursively create and return any new sound speed profiles between the center and the end var secondHalfSegment = new GeoSegment(segment.Center, segment[1]); foreach (var tuple in ProfilesAlongRadial(secondHalfSegment, centerDistance, middleProfile, endProfile, bottomProfile, soundSpeedData, deepestProfile)) yield return tuple; } var endDistance = startDistance + Geo.RadiansToKilometers(segment.LengthRadians); // return the end profile if (returnEndProfile) yield return Tuple.Create(NearestBottomProfileDistanceTo(bottomProfile, endDistance), endProfile); }
public void Add(Scenarios.TransmissionLoss transmissionLoss, double bearing) { var geoRect = (GeoRect)transmissionLoss.AnalysisPoint.Scenario.Location.GeoRect; var segment = new GeoSegment(transmissionLoss.AnalysisPoint.Geo, transmissionLoss.Modes[0].MaxPropagationRadius, bearing); if (!geoRect.Contains(segment[0]) || !geoRect.Contains(segment[1])) { //radial.Errors.Add("This radial extends beyond the location boundaries"); return; } //Debug.WriteLine("{0}: Queueing calculation of transmission loss for radial bearing {1} degrees, of mode {2} in analysis point {3}", DateTime.Now, radial.Bearing, radial.TransmissionLoss.Mode.ModeName, (Geo)radial.TransmissionLoss.AnalysisPoint.Geo); Radial outRadial; if (WorkQueue.TryGetValue(radial.Guid, out outRadial)) return; WorkQueue.Add(radial.Guid, radial); _calculatorQueue.Post(radial); }
/// <summary> /// Wrap a fixed-distance corridor around an (open) path, as specified by a GeoArray /// </summary> /// <param name="path">Open path, must not have repeated points or consecutive antipodes</param> /// <param name="radius">Distance from path to widen corridor, in angular radians.</param> /// <param name="err">maximum angle of rounded edges, in radians. If 0, will directly cut outside bends.</param> /// <param name="roundEndCaps">if true, will round end caps</param> /// <returns>a closed polygon representing the specified corridor around the path.</returns> public static GeoArray ComputeCorridor(this IGeoArray path, double radius, double err, bool roundEndCaps) { if (radius < 0) throw new ArgumentException("Must be non negative", "radius"); if (path == null) throw new ArgumentException("Must be non null", "path"); if (path.IsClosed) throw new ArgumentException("Must not be closed", "path"); var pl = path.Length; if (pl < 2) return null; // polygon will be right[0],...,right[n],left[m],...,left[0] var right = new List<Geo>(); var left = new List<Geo>(); Geo g0 = null; // previous point Geo n0 = null; // previous normal vector Geo l0 = null; Geo r0 = null; var g1 = path[0]; // current point int j; for (var i = 1; i < pl; i++) { var g2 = path[i]; // next point var n1 = g1.Cross(g2).Normalized; // n is perpendicular to the vector // from g1 to g2 n1 = n1.Scale(radius); // normalize to radius // these are the offsets on the g2 side at g1 var r1b = g1 + n1; var l1b = g1 - n1; if (n0 == null) { if (roundEndCaps && err > 0) { // start cap var arc = g1.ApproximateArc(l1b, r1b, err); for (j = arc.Length - 1; j >= 0; j--) { right.Add(arc[j]); } } else { // no previous point - we'll just be square right.Add(l1b); left.Add(r1b); } // advance normals l0 = l1b; r0 = r1b; } else { // otherwise, compute a more complex shape // these are the right and left on the g0 side of g1 var r1a = g1 + n0; var l1a = g1 - n0; var handed = g0.Cross(g1).Dot(g2); // right or left handed // divergence if (handed > 0) { // left needs two points, right needs 1 if (err > 0) { var arc = g1.ApproximateArc(l1b, l1a, err); for (j = arc.Length - 1; j >= 0; j--) { right.Add(arc[j]); } } else { right.Add(l1a); right.Add(l1b); } l0 = l1b; var ip = new GeoSegment(r0, r1a).GreatCircleIntersection(new GeoSegment(r1b, g2 + n1)); // if they intersect, take the intersection, else use the // points and punt if (ip != null) { left.Add(ip); } else { left.Add(r1a); left.Add(r1b); } r0 = ip; } else { var ip = new GeoSegment(l0, l1a).GreatCircleIntersection(new GeoSegment(l1b, g2 - n1)); // if they intersect, take the intersection, else use the // points and punt if (ip != null) { right.Add(ip); } else { right.Add(l1a); right.Add(l1b); } l0 = ip; if (err > 0) { var arc = g1.ApproximateArc(r1a, r1b, err); for (j = 0; j < arc.Length; j++) { left.Add(arc[j]); } } else { left.Add(r1a); left.Add(r1b); } r0 = r1b; } } // advance points g0 = g1; n0 = n1; g1 = g2; } // finish it off var rn = g1 - n0; var ln = g1 + n0; if (roundEndCaps && err > 0) { // end cap var arc = g1.ApproximateArc(ln, rn, err); for (j = arc.Length - 1; j >= 0; j--) { right.Add(arc[j]); } } else { right.Add(rn); left.Add(ln); } var ll = right.Count; var rl = left.Count; var result = new List<Geo>(); for (var i = 0; i < ll; i++) { result.Add(right[i]); } for (var i = rl - 1; i >= 0; i--) { result.Add(left[i]); } return new GeoArray(result); }
/// <summary> /// Finds all intersections between given segment and DEM grid /// </summary> /// <param name="startLon">Segment start longitude</param> /// <param name="startLat">Segment start latitude</param> /// <param name="endLon">Segment end longitude</param> /// <param name="endLat">Segment end latitude</param> /// <param name="segTiles">Metadata files <see cref="GeoTiffService.GetCoveringFiles"/> to see how to get them relative to segment geometry</param> /// <param name="returnStartPoint">If true, the segment starting point will be returned. Useful when processing a line segment by segment.</param> /// <param name="returnEndPoind">If true, the segment end point will be returned. Useful when processing a line segment by segment.</param> /// <returns></returns> public List <GeoPoint> FindSegmentIntersections(double startLon, double startLat, double endLon, double endLat, List <FileMetadata> segTiles, bool returnStartPoint, bool returnEndPoind) { List <GeoPoint> segmentPointsWithDEMPoints; // Find intersections with north/south lines, // starting form segment western point to easternmost point GeoPoint westernSegPoint = startLon < endLon ? new GeoPoint(startLat, startLon) : new GeoPoint(endLat, endLon); GeoPoint easternSegPoint = startLon > endLon ? new GeoPoint(startLat, startLon) : new GeoPoint(endLat, endLon); GeoSegment inputSegment = new GeoSegment(westernSegPoint, easternSegPoint); if (segTiles.Any()) { int estimatedCapacity = (segTiles.Select(t => t.OriginLongitude).Distinct().Count() // num horizontal tiles * width * segTiles.First().Width) + (segTiles.Select(t => t.OriginLatitude).Distinct().Count() // num vertical tiles * height * segTiles.First().Height); segmentPointsWithDEMPoints = new List <GeoPoint>(estimatedCapacity); bool yAxisDown = segTiles.First().pixelSizeY < 0; if (yAxisDown == false) { throw new NotImplementedException("DEM with y axis upwards not supported."); } foreach (GeoSegment demSegment in this.GetDEMNorthSouthLines(segTiles, westernSegPoint, easternSegPoint)) { GeoPoint intersectionPoint = null; if (GeometryService.LineLineIntersection(out intersectionPoint, inputSegment, demSegment)) { segmentPointsWithDEMPoints.Add(intersectionPoint); } } // Find intersections with west/east lines, // starting form segment northernmost point to southernmost point GeoPoint northernSegPoint = startLat > endLat ? new GeoPoint(startLat, startLon) : new GeoPoint(endLat, endLon); GeoPoint southernSegPoint = startLat < endLat ? new GeoPoint(startLat, startLon) : new GeoPoint(endLat, endLon); inputSegment = new GeoSegment(northernSegPoint, southernSegPoint); foreach (GeoSegment demSegment in this.GetDEMWestEastLines(segTiles, northernSegPoint, southernSegPoint)) { GeoPoint intersectionPoint = null; if (GeometryService.LineLineIntersection(out intersectionPoint, inputSegment, demSegment)) { segmentPointsWithDEMPoints.Add(intersectionPoint); } } } else { // No DEM coverage segmentPointsWithDEMPoints = new List <GeoPoint>(2); } // add start and/or end point if (returnStartPoint) { segmentPointsWithDEMPoints.Add(inputSegment.Start); } if (returnEndPoind) { segmentPointsWithDEMPoints.Add(inputSegment.End); } // sort points in segment order // segmentPointsWithDEMPoints.Sort(new DistanceFromPointComparer(new GeoPoint(startLat, startLon))); return(segmentPointsWithDEMPoints); }
public void ParseData(AbstractSectorDataFile data) { bool foundFirst = false; // Set up some variables for the first declaration line string name = ""; Point initialFirstPoint = new Point(""); Point initialSecondPoint = new Point(""); string initialColour = "0"; Definition initialDefinition = new Definition("", 1); Comment initialComment = new Comment(""); Docblock initialDocblock = new Docblock(); List <GeoSegment> segments = new List <GeoSegment>(); foreach (SectorData line in data) { // If not found the first item, we should check it's a name. if (!foundFirst) { if (!this.IsNameSegment(line)) { this.eventLogger.AddEvent( new SyntaxError("Invalid start to geo segment, expected a name", line) ); return; } foundFirst = true; // Set up the segment int nameEndIndex = this.GetEndOfNameIndex(line); name = string.Join(' ', line.dataSegments.GetRange(0, nameEndIndex)); line.dataSegments.RemoveRange(0, nameEndIndex); try { GeoSegment firstSegment = this.ParseGeoSegment(line); initialFirstPoint = firstSegment.FirstPoint; initialSecondPoint = firstSegment.SecondPoint; initialColour = firstSegment.Colour; initialDefinition = firstSegment.GetDefinition(); initialDocblock = firstSegment.Docblock; initialComment = firstSegment.InlineComment; } catch (ArgumentException) { // Syntax errors dealt with in segment parsing method return; } continue; } // If it's a name segment, we should save our progress and start afresh if (this.IsNameSegment(line)) { // Add the full geo element this.elements.Add( new Geo( name, initialFirstPoint, initialSecondPoint, initialColour, segments, initialDefinition, initialDocblock, initialComment ) ); // Reset the segments array segments = new List <GeoSegment>(); // Set up the segment int nameEndIndex = this.GetEndOfNameIndex(line); name = string.Join(' ', line.dataSegments.GetRange(0, nameEndIndex)); line.dataSegments.RemoveRange(0, nameEndIndex); try { GeoSegment firstSegment = this.ParseGeoSegment(line); initialFirstPoint = firstSegment.FirstPoint; initialSecondPoint = firstSegment.SecondPoint; initialColour = firstSegment.Colour; initialDefinition = firstSegment.GetDefinition(); initialDocblock = firstSegment.Docblock; initialComment = firstSegment.InlineComment; } catch (ArgumentException) { // Syntax errors dealt with in segment parsing method return; } continue; } // Otherwise, process the segment try { segments.Add(this.ParseGeoSegment(line)); } catch { // Syntax errors dealt with in segment parsing method return; } } // Add final geo element this.elements.Add( new Geo( name, initialFirstPoint, initialSecondPoint, initialColour, segments, initialDefinition, initialDocblock, initialComment ) ); }
/// <summary> /// Treating the passed in GeoSegments as planar vectors, return the dot product /// </summary> /// <param name="segment1"></param> /// <param name="segment2"></param> /// <returns></returns> public static double Dot(this GeoSegment segment1, GeoSegment segment2) { // Using Geo only for convenience, these are NOT Geos, really! // X is delta longitude, Y is delta latitude, Z is always zero. var vec1 = new Geo(segment1[1].Longitude - segment1[0].Longitude, segment1[1].Latitude - segment1[0].Latitude, 0).Normalized; var vec2 = new Geo(segment2[1].Longitude - segment2[0].Longitude, segment2[1].Latitude - segment2[0].Latitude, 0).Normalized; return vec1.Dot(vec2); }
/// <summary> /// Find the intersection of the great circle described by segment1 and the great circle described /// by segment2. We assume the segments both subtend less than 180 degrees. If either segment /// happens to subtend exactly 180 degrees, the antipode of the returned point is also a valid /// intersection. Note that the returned point may not actually lie on either segment, if the /// intersection point lies at a different location along their respective great circles than that /// subtended by the segments themselves. To check if the returned point is actually on the segment, /// use the Intersection extension method for the segment. /// </summary> /// <param name="segment1"> </param> /// <param name="segment2"> </param> /// <returns></returns> public static Geo GreatCircleIntersection(this GeoSegment segment1, GeoSegment segment2) { return segment1[0].CrossNormalize(segment1[1]).CrossNormalize(segment2[0].CrossNormalize(segment2[1])); }
/// <summary> /// Find the intersection of the great circle described by segment1 and the great circle described /// by segment2, if that intersection point is contained within segment both segments. If it is not /// contained within both segments, null is returned. /// </summary> /// <param name="segment1"> </param> /// <param name="segment2"> </param> /// <returns></returns> public static Geo Intersection(this GeoSegment segment1, GeoSegment segment2) { var geo = segment1.GreatCircleIntersection(segment2); return geo.IsOn(segment1) && geo.IsOn(segment2) ? geo : geo.Antipode.IsOn(segment1) && geo.Antipode.IsOn(segment2) ? geo.Antipode : null; }
/// <summary> /// Is geo on the specified segment or within a specified radius of it? /// </summary> /// <param name="segment"></param> /// <param name="geo"></param> /// <param name="withinRadians"> /// Non-negative radius (in radians) that the geo must be within in order to return true. /// Defaults to zero (geo must be exactly on the segment to return true) /// </param> /// <returns>true if geo is near segment, false otherwise</returns> public static bool IsNear(this Geo geo, GeoSegment segment, double withinRadians) { if (withinRadians < 0) throw new ArgumentException("Must be non-negative", "withinRadians"); return ((Math.Abs(segment.Normal.Dot(geo)) <= withinRadians) && (segment[0].DistanceRadians(geo) <= segment[0].DistanceRadians(segment[1])) && (segment[1].DistanceRadians(geo) <= segment[1].DistanceRadians(segment[0]))); }
public static bool Intersects(this GeoSegment segment1, GeoSegment segment2) { return segment1.GreatCircleIntersection(segment2) != null; }
/// <summary> /// Returns a Geo location if the great circle segments come within the range /// (r, radians) of each other. The angles between the segments must be less /// than PI or the results are ambiguous. /// </summary> /// <param name="segment1"> </param> /// <param name="segment2"> </param> /// <param name="r"></param> /// <returns>null if the segments don't intersect within the range.</returns> public static Geo Intersection(this GeoSegment segment1, GeoSegment segment2, double r) { if (segment2 == null) throw new ArgumentNullException("segment2"); if (r < 0) throw new ArgumentException("Must be non-negative", "r"); // ac and bc are the unit vectors normal to the two great // circles defined by the segments var ac = segment1[0].Cross(segment1[1]).Normalized; var bc = segment2[0].Cross(segment2[1]).Normalized; // aL and bL are the lengths (in radians) of the segments var aL = segment1[0].DistanceRadians(segment1[1]) + r; var bL = segment2[0].DistanceRadians(segment2[1]) + r; // i is one of the two points where the two great circles // intersect. var i = ac.Cross(bc).Normalized; // if i is not on A if (!(i.DistanceRadians(segment1[0]) <= aL && i.DistanceRadians(segment1[1]) <= aL)) { i = i.Antipode; // switch to the antipode instead // check again if (!(i.DistanceRadians(segment1[0]) <= aL && i.DistanceRadians(segment1[1]) <= aL)) { // nope - neither i nor i' is on A, so we'll bail out return null; } } // i is intersection or anti-intersection point now. // Now see if it intersects with b if (i.DistanceRadians(segment2[0]) <= bL && i.DistanceRadians(segment2[1]) <= bL) return i; return null; }
/// <summary> /// Is Geo p inside the time bubble along the great circle segment from /// this to v2 looking forward forwardRadius and backward backwardRadius. /// </summary> /// <param name="segment"> </param> /// <param name="forwardRadius"></param> /// <param name="backRadius"></param> /// <param name="p"></param> /// <returns></returns> public static bool IsInBubble(this Geo p, GeoSegment segment, double forwardRadius, double backRadius) { return segment[0].DistanceRadians(p) <= (((segment[1] - segment[0]).Normalized.Dot(p - segment[0]) > 0.0) ? forwardRadius : backRadius); }
GetRide(UserRideOffer offer, ConcurrentGeoQuadtree <MatchableRideRequest> origins, ConcurrentGeoQuadtree <MatchableRideRequest> destinations) { RouteInfo driverRoute = await GetRoute(offer); var originsTask = GetElementsInsideAsync(origins, NearRoute); var destinationsTask = GetElementsInsideAsync(destinations, NearRoute); // Only consider passengers whose origins and destinations are near // the driver's route. var potentialPassengers = new HashSet <MatchableRideRequest>( from element in await originsTask select element.Data); potentialPassengers.IntersectWith( from element in await destinationsTask select element.Data); // Find a passenger going in the same direction as the driver such that // picking up the passenger does not put the driver too far out of their way. foreach (var passenger in potentialPassengers.Where(GoingInDriversDirection)) { RouteInfo routeWithPassenger = await GetRouteWithPassenger(offer, passenger); // Reject route if it's too far out of the way according to // the driver's settings. if (driverRoute.drivingTime.HasValue && routeWithPassenger.drivingTime.HasValue) { TimeSpan originalTime = driverRoute.drivingTime.Value; TimeSpan newTime = routeWithPassenger.drivingTime.Value; TimeSpan maxTime = originalTime + TimeSpan.FromMinutes(offer.RideOffer.MaxTimeOutOfWay); if (newTime > maxTime) { // Output debug info for demos. Program.LogError($"Matched {offer.User.UserInfo.UserId} with {passenger.Request.User.UserInfo.UserId}" + " but resulting route was too long." + $" Original trip duration: {originalTime.Minutes} mins." + $" Matched trip duration: {newTime.Minutes} mins." + $" Driver's max time out of way: {offer.RideOffer.MaxTimeOutOfWay} mins."); continue; } } return(RideWithPassenger(offer, passenger)); } return(EmptyRide(offer, driverRoute)); /// <summary> /// Tests whether any point in the rect is close enough to <see cref="route"/>. /// </summary> bool NearRoute(Rect rect) { // Ignore passengers more than approximately 1km of the route. // TODO Take large max-time-out-of-way values into account when choosing max-dist-out-of-way. double maxDistMeters = 1000; double maxDistDegrees = offer.RideOffer.Trip.Source.DegreesUpperBound(maxDistMeters); GeoPolyline route = driverRoute.overviewPolyline; return(route.RectWithinDistance(rect, maxDistDegrees)); } /// <summary> /// Tests whether this passenger is going in the same direction as the driver. /// </summary> bool GoingInDriversDirection(MatchableRideRequest request) { var driverDest = offer.RideOffer.Trip.Destination; var driverOrig = offer.RideOffer.Trip.Source; var driverSeg = new GeoSegment(driverOrig, driverDest); var passDest = request.Request.RideRequest.Trip.Destination; var passOrig = request.Request.RideRequest.Trip.Source; var passSeg = new GeoSegment(passOrig, passDest); // Use GeoSegments so that this works near prime meridian. var passDiff = passSeg.Point2Representative - passSeg.Point1Representative; var driverDiff = driverSeg.Point2Representative - driverSeg.Point1Representative; // Compute the dot product of the vectors. This is a pretty rough // estimate and doesn't take into account the Earth's curvature. return(passDiff.Dot(driverDiff) > 0); } }