Merge pull request #595 from patrickbranch/TSP

Brian [2016-07-31 09:55:44]
Merge pull request #595 from patrickbranch/TSP

Added 2-Opt algorithm to order PokeStops before visiting.
Filename
PokemonGo/RocketAPI/Client.cs
PokemonGo/RocketAPI/Extensions/LatLongExtensions.cs
PokemonGo/RocketAPI/ILatLong.cs
PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
PokemonGo/RocketAPI/ProtoAdditions.cs
PokemonGo/RocketAPI/Window/LocationManager.cs
PokemonGo/RocketAPI/Window/MainForm.cs
PokemonGo/RocketAPI/Window/MapForm.cs
PokemonGo/RocketAPI/Window/PokeStopOptimizer.cs
PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
PokemonGo/RocketAPI/Window/RouteOptimizer.cs
diff --git a/PokemonGo/RocketAPI/Client.cs b/PokemonGo/RocketAPI/Client.cs
index e682118..cc3dc8e 100644
--- a/PokemonGo/RocketAPI/Client.cs
+++ b/PokemonGo/RocketAPI/Client.cs
@@ -422,14 +422,20 @@ namespace PokemonGo.RocketAPI
                         releasePokemonRequest);
         }

-        public double getCurrentLat()
+        public double CurrentLatitude
         {
-            return this._currentLat;
+            get
+            {
+                return this._currentLat;
+            }
         }

-        public double getCurrentLong()
+        public double CurrentLongitude
         {
-            return this._currentLng;
+            get
+            {
+                return this._currentLng;
+            }
         }

         public async Task<PlayerUpdateResponse> UpdatePlayerLocation(double lat, double lng)
diff --git a/PokemonGo/RocketAPI/Extensions/LatLongExtensions.cs b/PokemonGo/RocketAPI/Extensions/LatLongExtensions.cs
new file mode 100644
index 0000000..94c98cd
--- /dev/null
+++ b/PokemonGo/RocketAPI/Extensions/LatLongExtensions.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PokemonGo.RocketAPI.Extensions
+{
+    public static class LatLongExtensions
+    {
+        public static double distanceFrom(this ILatLong c1, ILatLong c2)
+        {
+            double R = 6371e3;
+            Func<double, float> toRad = x => (float)(x * (Math.PI / 180));
+            float lat1 = toRad(c1.Latitude);
+            float lat2 = toRad(c2.Latitude);
+            float dLat = toRad(c2.Latitude - c1.Latitude);
+            float dLng = toRad(c2.Longitude - c1.Longitude);
+            double h = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Sin(dLng / 2) * Math.Sin(dLng / 2);
+            double c = 2 * Math.Atan2(Math.Sqrt(h), Math.Sqrt(1 - h));
+            return R * c;
+        }
+    }
+}
+
diff --git a/PokemonGo/RocketAPI/ILatLong.cs b/PokemonGo/RocketAPI/ILatLong.cs
new file mode 100644
index 0000000..c0fd3fc
--- /dev/null
+++ b/PokemonGo/RocketAPI/ILatLong.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PokemonGo.RocketAPI
+{
+    public interface ILatLong
+    {
+        double Latitude { get; }
+        double Longitude { get; }
+    }
+
+    public class LatLong : Tuple<double, double>, ILatLong
+    {
+        public LatLong(double item1, double item2) : base(item1, item2)
+        {
+        }
+
+        public double Latitude
+        {
+            get
+            {
+                return this.Item1;
+            }
+        }
+        public double Longitude
+        {
+            get
+            {
+                return this.Item2;
+            }
+        }
+    }
+}
diff --git a/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj b/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
index 8760094..8a6404d 100644
--- a/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
+++ b/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
@@ -68,6 +68,8 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Extensions\LatLongExtensions.cs" />
+    <Compile Include="ILatLong.cs" />
     <Compile Include="Enums\AuthType.cs" />
     <Compile Include="Enums\MiscEnums.cs" />
     <Compile Include="Enums\RequestType.cs" />
@@ -92,6 +94,7 @@
     <Compile Include="Extensions\HttpClientExtensions.cs" />
     <Compile Include="Helpers\RandomHelper.cs" />
     <Compile Include="Helpers\RequestBuilder.cs" />
+    <Compile Include="ProtoAdditions.cs" />
     <Compile Include="Resources.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
diff --git a/PokemonGo/RocketAPI/ProtoAdditions.cs b/PokemonGo/RocketAPI/ProtoAdditions.cs
new file mode 100644
index 0000000..ea0ecf2
--- /dev/null
+++ b/PokemonGo/RocketAPI/ProtoAdditions.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PokemonGo.RocketAPI.GeneratedCode
+{
+    public partial class FortData : ILatLong { }
+
+    public partial class MapPokemon : ILatLong { }
+
+    public partial class WildPokemon : ILatLong { }
+
+    public partial class SpawnPoint : ILatLong { }
+
+}
diff --git a/PokemonGo/RocketAPI/Window/LocationManager.cs b/PokemonGo/RocketAPI/Window/LocationManager.cs
index 6896d41..6b6f3b5 100644
--- a/PokemonGo/RocketAPI/Window/LocationManager.cs
+++ b/PokemonGo/RocketAPI/Window/LocationManager.cs
@@ -3,59 +3,34 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using PokemonGo.RocketAPI;
+using PokemonGo.RocketAPI.Extensions;

 namespace PokemonGo.RocketAPI.Window
 {
     public class LocationManager
     {
         private Client client;
-        private double kilometersPerMillisecond;
+        private double metersPerMillisecond;

         public LocationManager(Client client, double speed)
         {
             this.client = client;
-            this.kilometersPerMillisecond = speed / 3600000;
+            this.metersPerMillisecond = speed / 3600;
         }

         public double getDistance(double lat, double lng)
         {
-            Coordinate currentLoc = new Coordinate(client.getCurrentLat(), client.getCurrentLong());
-            return currentLoc.distanceFrom(new Coordinate(lat, lng));
+            LatLong currentLoc = new LatLong(client.CurrentLatitude, client.CurrentLongitude);
+            return currentLoc.distanceFrom(new LatLong(lat, lng));
         }

         public async Task update(double lat, double lng)
         {
-            double waitTime = getDistance(lat, lng) / this.kilometersPerMillisecond;
+            double waitTime = getDistance(lat, lng) / this.metersPerMillisecond;
             await Task.Delay((int)Math.Ceiling(waitTime));
             await client.UpdatePlayerLocation(lat, lng);
         }
-
-        public struct Coordinate
-        {
-
-            public Coordinate(double lat, double lng)
-            {
-                this.latitude = lat;
-                this.longitude = lng;
-            }
-            public double latitude;
-            public double longitude;
-
-            //returns distance in kilometers
-            public double distanceFrom(Coordinate c2)
-            {
-                double R = 6371;
-                Func<double, double> toRad = x => x * (Math.PI / 180);
-                double dLat = toRad(c2.latitude - this.latitude);
-                double dLong = toRad(c2.longitude - c2.longitude);
-                double lat1 = toRad(this.latitude);
-                double lat2 = toRad(c2.latitude);
-                double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
-                    Math.Sin(dLong / 2) * Math.Sin(dLong / 2) * Math.Cos(lat1) * Math.Cos(lat2);
-                double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
-                return R * c;
-            }
-        }
     }

 }
\ No newline at end of file
diff --git a/PokemonGo/RocketAPI/Window/MainForm.cs b/PokemonGo/RocketAPI/Window/MainForm.cs
index 2d1f167..96eeb1b 100644
--- a/PokemonGo/RocketAPI/Window/MainForm.cs
+++ b/PokemonGo/RocketAPI/Window/MainForm.cs
@@ -575,13 +575,16 @@ namespace PokemonGo.RocketAPI.Window
             var mapObjects = await client.GetMapObjects();

             FortData[] rawPokeStops = mapObjects.MapCells.SelectMany(i => i.Forts).Where(i => i.Type == FortType.Checkpoint && i.CooldownCompleteTimestampMs < DateTime.UtcNow.ToUnixTime()).ToArray();
-            pokeStops = PokeStopOptimizer.Optimize(rawPokeStops, ClientSettings.DefaultLatitude, ClientSettings.DefaultLongitude, pokestopsOverlay);
+            pokeStops = rawPokeStops;
+            UpdateMap();
+            ColoredConsoleWrite(Color.Cyan, $"Finding fastest route through all PokeStops..");
+            LatLong startingLatLong = new LatLong(ClientSettings.DefaultLatitude, ClientSettings.DefaultLongitude);
+            pokeStops = RouteOptimizer<FortData>.Optimize(rawPokeStops, startingLatLong, pokestopsOverlay);
             wildPokemons = mapObjects.MapCells.SelectMany(i => i.WildPokemons);
             if (!ForceUnbanning && !Stopping)
                 ColoredConsoleWrite(Color.Cyan, $"Visiting {pokeStops.Count()} PokeStops");

             UpdateMap();
-
             foreach (var pokeStop in pokeStops)
             {
                 if (ForceUnbanning || Stopping)
diff --git a/PokemonGo/RocketAPI/Window/MapForm.cs b/PokemonGo/RocketAPI/Window/MapForm.cs
index 595a80b..cc49274 100644
--- a/PokemonGo/RocketAPI/Window/MapForm.cs
+++ b/PokemonGo/RocketAPI/Window/MapForm.cs
@@ -50,7 +50,7 @@ namespace PokemonGo.RocketAPI.Window

         private void tmrUpdate_Tick(object sender, EventArgs e)
         {
-            gMapControl1.Position = new PointLatLng(Client.getCurrentLat(), Client.getCurrentLong());
+            gMapControl1.Position = new PointLatLng(Client.CurrentLatitude, Client.CurrentLongitude);
         }
     }
 }
diff --git a/PokemonGo/RocketAPI/Window/PokeStopOptimizer.cs b/PokemonGo/RocketAPI/Window/PokeStopOptimizer.cs
deleted file mode 100644
index a7768af..0000000
--- a/PokemonGo/RocketAPI/Window/PokeStopOptimizer.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using GMap.NET.WindowsForms;
-using PokemonGo.RocketAPI.GeneratedCode;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PokemonGo.RocketAPI.Window
-{
-    public static class PokeStopOptimizer
-    {
-        public static List<FortData> Optimize(FortData[] pokeStops, double cLatitude, double cLongitude, GMapOverlay routeOverlay)
-        {
-            List<FortData> optimizedRoute = new List<FortData>(pokeStops);
-
-            // NN
-            FortData NN = FindNN(optimizedRoute, cLatitude, cLongitude);
-            optimizedRoute.Remove(NN);
-            optimizedRoute.Insert(0, NN);
-            for (int i=1; i<pokeStops.Length; i++)
-            {
-                NN = FindNN(optimizedRoute.Skip(i), NN.Latitude, NN.Longitude);
-                optimizedRoute.Remove(NN);
-                optimizedRoute.Insert(i, NN);
-            }
-
-            // 2-Opt
-
-
-            return optimizedRoute;
-        }
-
-        private static List<FortData> Optimize2Opt(List<FortData> pokeStops)
-        {
-            List<FortData> optimizedRoute = new List<FortData>();
-
-            int n = pokeStops.Count;
-
-            for (int ai = 0; ai < n; ai++)
-            {
-                for (int ci = 0; ci < n; ci++)
-                {
-                    int bi = (ai + 1) % n;
-                    int di = (ci + 1) % n;
-                }
-            }
-
-            return optimizedRoute;
-        }
-
-        private static FortData FindNN(IEnumerable<FortData> pokeStops, double cLatitude, double cLongitude)
-        {
-            return pokeStops.OrderBy(p => GetDistance(cLatitude, cLongitude, p.Latitude, p.Longitude)).First();
-        }
-
-        private static float GetDistance(double lat1, double lng1, double lat2, double lng2)
-        {
-            double R = 6371e3;
-            Func<double, float> toRad = x => (float)(x * (Math.PI / 180));
-            lat1 = toRad(lat1);
-            lat2 = toRad(lat2);
-            float dLng = toRad(lng2 - lng1);
-
-            return (float)(Math.Acos(Math.Sin(lat1)*Math.Sin(lat2) + Math.Cos(lat1)*Math.Cos(lat2)*Math.Cos(dLng)) * R);
-        }
-    }
-}
diff --git a/PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj b/PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
index bfc3390..cb0252d 100644
--- a/PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
+++ b/PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
@@ -92,7 +92,7 @@
     <Compile Include="PokemonForm.Designer.cs">
       <DependentUpon>PokemonForm.cs</DependentUpon>
     </Compile>
-    <Compile Include="PokeStopOptimizer.cs" />
+    <Compile Include="RouteOptimizer.cs" />
     <Compile Include="PokeUi.cs">
       <SubType>Form</SubType>
     </Compile>
@@ -109,6 +109,7 @@
     <Compile Include="SettingsForm.Designer.cs">
       <DependentUpon>SettingsForm.cs</DependentUpon>
     </Compile>
+    <Compile Include="TSP.cs" />
     <EmbeddedResource Include="MainForm.resx">
       <DependentUpon>MainForm.cs</DependentUpon>
     </EmbeddedResource>
diff --git a/PokemonGo/RocketAPI/Window/RouteOptimizer.cs b/PokemonGo/RocketAPI/Window/RouteOptimizer.cs
new file mode 100644
index 0000000..b6f1bd4
--- /dev/null
+++ b/PokemonGo/RocketAPI/Window/RouteOptimizer.cs
@@ -0,0 +1,93 @@
+using GMap.NET.WindowsForms;
+using PokemonGo.RocketAPI.GeneratedCode;
+using PokemonGo.RocketAPI.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PokemonGo.RocketAPI.Window
+{
+    public static class RouteOptimizer<T> where T: ILatLong
+    {
+        public static List<T> Optimize(T[] stops, ILatLong startingPosition, GMapOverlay routeOverlay)
+        {
+            List<T> optimizedRoute = new List<T>(stops);
+
+            // NN
+            T NN = FindNN(optimizedRoute, startingPosition);
+            optimizedRoute.Remove(NN);
+            optimizedRoute.Insert(0, NN);
+            for (int i=1; i< stops.Length; i++)
+            {
+                NN = FindNN(optimizedRoute.Skip(i), NN);
+                optimizedRoute.Remove(NN);
+                optimizedRoute.Insert(i, NN);
+            }
+
+            // 2-Opt
+            optimizedRoute = Optimize2Opt(optimizedRoute);
+
+            return optimizedRoute;
+        }
+
+        public static float routeCost(List<T> stops)
+        {
+            return Enumerable.Range(0, stops.Count - 1).Aggregate<int, float>(0, (sum,i) =>
+            {
+                return sum + (float)stops[i].distanceFrom(stops[i + 1]);
+            });
+        }
+
+        private static List<T> reverseSublist(List<T> stops, int startIndex, int endIndex)
+        {
+            return stops
+                .Take(startIndex)
+                .Concat(
+                    stops
+                    .Skip(startIndex)
+                    .Take(endIndex - startIndex)
+                    .Reverse()
+                ).Concat(stops.Skip(endIndex)).ToList();
+        }
+
+        private static List<T> Optimize2Opt(List<T> stops)
+        {
+            List<T> optimizedRoute = stops;
+
+            int n = stops.Count;
+            bool foundCheaperRoute;
+            float minCost = routeCost(optimizedRoute);
+            do
+            {
+                foundCheaperRoute = false;
+                for (int i = 0; i < n - 1; i++)
+                {
+                    for (int j = i + 1; j < n ; j++)
+                    {
+                        List<T> newRoute = reverseSublist(optimizedRoute, i, j);
+                        float newCost = routeCost(newRoute);
+                        if (newCost < minCost)
+                        {
+                            minCost = newCost;
+                            optimizedRoute = newRoute;
+                            foundCheaperRoute = true;
+                            break;
+                        }
+                    }
+                    if (foundCheaperRoute)
+                        break;
+                }
+            }
+            while (foundCheaperRoute);
+            return optimizedRoute;
+        }
+
+        private static T FindNN(IEnumerable<T> stops, ILatLong fromPosition)
+        {
+            return stops.OrderBy(p => fromPosition.distanceFrom(p)).First();
+        }
+
+    }
+}
You may download the files in Public Git.