From d7b1526f68029ae2ee7c0f7b281d034ef47adf53 Mon Sep 17 00:00:00 2001 From: versx Date: Mon, 28 Dec 2020 17:55:24 -0800 Subject: [PATCH] Check if any part of weather cell touches geofence instead of center --- src/Geofence/GeofenceItem.cs | 15 +-- src/Geofence/GeofenceService.cs | 147 ++++++++++++++++++++++++++ src/Net/Webhooks/WebhookController.cs | 2 +- 3 files changed, 156 insertions(+), 8 deletions(-) diff --git a/src/Geofence/GeofenceItem.cs b/src/Geofence/GeofenceItem.cs index 37dab80b..9385098d 100644 --- a/src/Geofence/GeofenceItem.cs +++ b/src/Geofence/GeofenceItem.cs @@ -1,16 +1,17 @@ -using NetTopologySuite.Features; -using NetTopologySuite.Geometries; -using NetTopologySuite.IO; -using Newtonsoft.Json; -using WhMgr.Utilities; - -namespace WhMgr.Geofence +namespace WhMgr.Geofence { using System; using System.Collections.Generic; using System.Linq; using System.IO; + using NetTopologySuite.Features; + using NetTopologySuite.Geometries; + using NetTopologySuite.IO; + using Newtonsoft.Json; + + using WhMgr.Utilities; + /// /// Geofence class /// diff --git a/src/Geofence/GeofenceService.cs b/src/Geofence/GeofenceService.cs index 90e5aa46..e2fde0ae 100644 --- a/src/Geofence/GeofenceService.cs +++ b/src/Geofence/GeofenceService.cs @@ -1,10 +1,15 @@ namespace WhMgr.Geofence { + using System; using System.Collections.Generic; using System.Linq; + using WhMgr.Osm.Models; + public static class GeofenceService { + const double EquityTolerance = 0.000000001d; + public static IEnumerable GetGeofences(IEnumerable geofences, Location point) { // Order descending by priority so that when we iterate forwards using FirstOrDefault, higher-priority @@ -18,5 +23,147 @@ public static IEnumerable GetGeofences(IEnumerable g public static GeofenceItem GetGeofence(IEnumerable geofences, Location point) => GetGeofences(geofences, point).FirstOrDefault(); + + // Taken from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html + public static bool IsPointInsidePoly(Location point, List poly) + { + var result = false; + for (int i = 0, j = poly.Count - 1; i < poly.Count; j = i++) + { + if ((poly[i].Longitude > point.Longitude) != (poly[j].Longitude > point.Longitude) && + (point.Latitude < (poly[j].Latitude - poly[i].Latitude) * (point.Longitude - poly[i].Longitude) / (poly[j].Longitude - poly[i].Longitude) + poly[i].Latitude)) + { + result = !result; + } + } + return result; + } + + public static Location[] GetIntersectionPoints(Location l1p1, Location l1p2, List poly) + { + var intersectionPoints = new List(); + for (var i = 0; i < poly.Count; i++) + { + var next = (i + 1 == poly.Count) ? 0 : i + 1; + var ip = GetIntersectionPoint(l1p1, l1p2, poly[i], poly[next]); + if (ip != null) intersectionPoints.Add(ip); + } + return intersectionPoints.ToArray(); + } + + // Math logic from http://www.wyrmtale.com/blog/2013/115/2d-line-intersection-in-c + public static Location GetIntersectionPoint(Location l1p1, Location l1p2, Location l2p1, Location l2p2) + { + var A1 = l1p2.Longitude - l1p1.Longitude; + var B1 = l1p1.Latitude - l1p2.Latitude; + var C1 = A1 * l1p1.Latitude + B1 * l1p1.Longitude; + + var A2 = l2p2.Longitude - l2p1.Longitude; + var B2 = l2p1.Latitude - l2p2.Latitude; + var C2 = A2 * l2p1.Latitude + B2 * l2p1.Longitude; + + //lines are parallel + var det = A1 * B2 - A2 * B1; + if (IsEqual(det, 0d)) + return null; //parallel lines + + var x = (B2 * C1 - B1 * C2) / det; + var y = (A1 * C2 - A2 * C1) / det; + var online1 = ((Math.Min(l1p1.Latitude, l1p2.Latitude) < x || IsEqual(Math.Min(l1p1.Latitude, l1p2.Latitude), x)) + && (Math.Max(l1p1.Latitude, l1p2.Latitude) > x || IsEqual(Math.Max(l1p1.Latitude, l1p2.Latitude), x)) + && (Math.Min(l1p1.Longitude, l1p2.Longitude) < y || IsEqual(Math.Min(l1p1.Longitude, l1p2.Longitude), y)) + && (Math.Max(l1p1.Longitude, l1p2.Longitude) > y || IsEqual(Math.Max(l1p1.Longitude, l1p2.Longitude), y)) + ); + var online2 = ((Math.Min(l2p1.Latitude, l2p2.Latitude) < x || IsEqual(Math.Min(l2p1.Latitude, l2p2.Latitude), x)) + && (Math.Max(l2p1.Latitude, l2p2.Latitude) > x || IsEqual(Math.Max(l2p1.Latitude, l2p2.Latitude), x)) + && (Math.Min(l2p1.Longitude, l2p2.Longitude) < y || IsEqual(Math.Min(l2p1.Longitude, l2p2.Longitude), y)) + && (Math.Max(l2p1.Longitude, l2p2.Longitude) > y || IsEqual(Math.Max(l2p1.Longitude, l2p2.Longitude), y)) + ); + + if (online1 && online2) + return new Location(x, y); + + return null; //intersection is at out of at least one segment. + } + + public static List GetIntersectionOfPolygons(List poly1, List poly2) + { + var clippedCorners = new List(); + + // Add the corners of poly1 which are inside poly2 + for (var i = 0; i < poly1.Count; i++) + { + if (IsPointInsidePoly(poly1[i], poly2)) + AddPoints(clippedCorners, new Location[] { poly1[i] }); + } + + // Add the corners of poly2 which are inside poly1 + for (var i = 0; i < poly2.Count; i++) + { + if (IsPointInsidePoly(poly2[i], poly1)) + AddPoints(clippedCorners, new Location[] { poly2[i] }); + } + + // Add the intersection points + for (int i = 0, next = 1; i < poly1.Count; i++, next = (i + 1 == poly1.Count) ? 0 : i + 1) + { + AddPoints(clippedCorners, GetIntersectionPoints(poly1[i], poly1[next], poly2)); + } + + return OrderClockwise(clippedCorners.ToArray()).ToList(); + } + + public static GeofenceItem PolygonIntersectsWithPolygon(List geofences, MultiPolygon poly) + { + var polygon = poly.Select(x => new Location(x[0], x[1])).ToList(); + foreach (var geofence in geofences) + { + var coordinates = geofence.Feature.Geometry.Coordinates + .Select(x => new Location(x.X, x.Y)) + .ToList(); + if (GetIntersectionOfPolygons(coordinates, polygon)?.Count > 0) + { + return geofence; + } + } + return null; + } + + private static void AddPoints(List pool, Location[] newpoints) + { + foreach (var np in newpoints) + { + var found = false; + foreach (var p in pool) + { + if (IsEqual(p.Latitude, np.Latitude) && IsEqual(p.Longitude, np.Longitude)) + { + found = true; + break; + } + } + if (!found) pool.Add(np); + } + } + + private static Location[] OrderClockwise(Location[] points) + { + double mX = 0; + double my = 0; + foreach (var p in points) + { + mX += p.Latitude; + my += p.Longitude; + } + mX /= points.Length; + my /= points.Length; + + return points.OrderBy(v => Math.Atan2(v.Longitude - my, v.Latitude - mX)).ToArray(); + } + + private static bool IsEqual(double d1, double d2) + { + return Math.Abs(d1 - d2) <= EquityTolerance; + } } } \ No newline at end of file diff --git a/src/Net/Webhooks/WebhookController.cs b/src/Net/Webhooks/WebhookController.cs index 0b872741..07c7fb80 100644 --- a/src/Net/Webhooks/WebhookController.cs +++ b/src/Net/Webhooks/WebhookController.cs @@ -1038,7 +1038,7 @@ private void ProcessWeather(WeatherData weather) continue; } - var geofence = GeofenceService.GetGeofence(alarm.GeofenceItems, new Location(weather.Latitude, weather.Longitude)); + var geofence = GeofenceService.PolygonIntersectsWithPolygon(alarm.GeofenceItems, weather.Polygon); if (geofence == null) { //_logger.Info($"[{alarm.Name}] Skipping gym details GymId={gymDetails.GymId}, GymName={gymDetails.GymName}: not in geofence.");