Added 2-opt implementation to optimizer. Generalized optimizer code to work with any objects

Patrick Branch [2016-07-30 18:28:17]
Added 2-opt implementation to optimizer.  Generalized optimizer code to work with any objects
posessing latitude and longitude information.
Filename
PokemonGo/RocketAPI/Extensions/LatLongExtensions.cs
PokemonGo/RocketAPI/ICoordinate.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/PokeStopOptimizer.cs
PokemonGo/RocketAPI/Window/PokemonGo.RocketAPI.Window.csproj
PokemonGo/RocketAPI/Window/RouteOptimizer.cs
PokemonGo/RocketAPI/Window/TSP.cs
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/ICoordinate.cs b/PokemonGo/RocketAPI/ICoordinate.cs
deleted file mode 100644
index 14c6ae6..0000000
--- a/PokemonGo/RocketAPI/ICoordinate.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PokemonGo.RocketAPI
-{
-    public interface ICoordinate
-    {
-        double Latitude { get; }
-        double Longitude { get; }
-    }
-    public interface MetricSpace<T>
-    {
-        double distance(T t1, T t2);
-    }
-    public class CoordinateMetric : MetricSpace<ICoordinate>
-    {
-        public double distance(ICoordinate c1, ICoordinate c2)
-        {
-            double R = 6371;
-            Func<double, double> toRad = x => x * (Math.PI / 180);
-            double dLat = toRad(c2.Latitude - c1.Latitude);
-            double dLong = toRad(c2.Longitude - c2.Longitude);
-            double lat1 = toRad(c1.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;
-        }
-    }
-}
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 b60f39c..8a6404d 100644
--- a/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
+++ b/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
@@ -68,7 +68,8 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="ICoordinate.cs" />
+    <Compile Include="Extensions\LatLongExtensions.cs" />
+    <Compile Include="ILatLong.cs" />
     <Compile Include="Enums\AuthType.cs" />
     <Compile Include="Enums\MiscEnums.cs" />
     <Compile Include="Enums\RequestType.cs" />
diff --git a/PokemonGo/RocketAPI/ProtoAdditions.cs b/PokemonGo/RocketAPI/ProtoAdditions.cs
index fc5ce6d..ea0ecf2 100644
--- a/PokemonGo/RocketAPI/ProtoAdditions.cs
+++ b/PokemonGo/RocketAPI/ProtoAdditions.cs
@@ -6,12 +6,12 @@ using System.Threading.Tasks;

 namespace PokemonGo.RocketAPI.GeneratedCode
 {
-    public partial class FortData : ICoordinate { }
+    public partial class FortData : ILatLong { }

-    public partial class MapPokemon : ICoordinate { }
+    public partial class MapPokemon : ILatLong { }

-    public partial class WildPokemon : ICoordinate { }
+    public partial class WildPokemon : ILatLong { }

-    public partial class SpawnPoint : ICoordinate { }
+    public partial class SpawnPoint : ILatLong { }

 }
diff --git a/PokemonGo/RocketAPI/Window/LocationManager.cs b/PokemonGo/RocketAPI/Window/LocationManager.cs
index 1bffc25..6b6f3b5 100644
--- a/PokemonGo/RocketAPI/Window/LocationManager.cs
+++ b/PokemonGo/RocketAPI/Window/LocationManager.cs
@@ -4,61 +4,33 @@ 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.CurrentLatitude, client.CurrentLongitude);
-            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 class Coordinate : Tuple<double, double>, ICoordinate
-    {
-        public Coordinate(double item1, double item2) : base(item1, item2)
-        {
-        }
-
-        public double Latitude
-        {
-            get
-            {
-                return this.Item1;
-            }
-        }
-        public double Longitude
-        {
-            get
-            {
-                return this.Item2;
-            }
-        }
-
-        public double distanceFrom(ICoordinate c2)
-        {
-            return new CoordinateMetric().distance(this, c2);
-        }
-
-
-    }
-
 }
\ No newline at end of file
diff --git a/PokemonGo/RocketAPI/Window/MainForm.cs b/PokemonGo/RocketAPI/Window/MainForm.cs
index db448ce..b2c4c38 100644
--- a/PokemonGo/RocketAPI/Window/MainForm.cs
+++ b/PokemonGo/RocketAPI/Window/MainForm.cs
@@ -575,11 +575,15 @@ 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");
-            pokeStops = (IEnumerable<FortData>)TSP<ICoordinate>.getMinimumTour(pokeStops, (new CoordinateMetric()).distance);
+
             UpdateMap();
             foreach (var pokeStop in pokeStops)
             {
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 e09572a..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>
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();
+        }
+
+    }
+}
diff --git a/PokemonGo/RocketAPI/Window/TSP.cs b/PokemonGo/RocketAPI/Window/TSP.cs
deleted file mode 100644
index b47c966..0000000
--- a/PokemonGo/RocketAPI/Window/TSP.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace PokemonGo.RocketAPI.Window
-{
-    /* Copied from here, with modifications to make more generic
-     * http://stackoverflow.com/questions/2927469/traveling-salesman-problem-2-opt-algorithm-c-sharp-implementation
-     There's probably a much better implementation of TSP's solution */
-    public static class TSP<T>
-    {
-        private static Func<T, T, double> distance;
-        public static IEnumerable<T> getMinimumTour(IEnumerable<T> nodes, Func<T,T,double> distance)
-        {
-            TSP<T>.distance = distance;
-            if (nodes.Count() < 2)
-                return nodes;
-
-            //create an initial tour out of nearest neighbors
-            var stops = nodes
-                        .Select(i => new Stop(i))
-                        .NearestNeighbors()
-                        .ToList();
-
-            //create next pointers between them
-            stops.Connect(true);
-
-            //wrap in a tour object
-            Tour startingTour = new Tour(stops);
-
-            //the actual algorithm
-            while (true)
-            {
-                var newTour = startingTour.GenerateMutations()
-                                            .MinBy(tour => tour.Cost());
-                if (newTour.Cost() < startingTour.Cost()) startingTour = newTour;
-                else break;
-            }
-            return startingTour.Cycle().Select(s => s.City);
-        }
-
-        internal class Stop
-        {
-            public Stop(T city)
-            {
-                City = city;
-            }
-
-
-            public Stop Next { get; set; }
-
-            public T City { get; set; }
-
-
-            public Stop Clone()
-            {
-                return new Stop(City);
-            }
-
-
-            public static double Distance(Stop first, Stop other)
-            {
-                return TSP<T>.distance(first.City, other.City);
-            }
-
-
-            //list of nodes, including this one, that we can get to
-            public IEnumerable<Stop> CanGetTo()
-            {
-                var current = this;
-                while (true)
-                {
-                    yield return current;
-                    current = current.Next;
-                    if (current == this) break;
-                }
-            }
-
-
-            public override bool Equals(object obj)
-            {
-                return EqualityComparer<T>.Default.Equals(City,((Stop)obj).City);
-            }
-
-
-            public override int GetHashCode()
-            {
-                return City.GetHashCode();
-            }
-
-        }
-
-
-        internal class Tour
-        {
-            public Tour(IEnumerable<Stop> stops)
-            {
-                Anchor = stops.First();
-            }
-
-
-            //the set of tours we can make with 2-opt out of this one
-            public IEnumerable<Tour> GenerateMutations()
-            {
-                for (Stop stop = Anchor; stop.Next != Anchor; stop = stop.Next)
-                {
-                    //skip the next one, since you can't swap with that
-                    Stop current = stop.Next.Next;
-                    while (current != Anchor)
-                    {
-                        yield return CloneWithSwap(stop.City, current.City);
-                        current = current.Next;
-                    }
-                }
-            }
-
-
-            public Stop Anchor { get; set; }
-
-
-            public Tour CloneWithSwap(T firstCity, T secondCity)
-            {
-                Stop firstFrom = null, secondFrom = null;
-                var stops = UnconnectedClones();
-                stops.Connect(true);
-
-                foreach (Stop stop in stops)
-                {
-                    if (EqualityComparer<T>.Default.Equals(stop.City, firstCity)) firstFrom = stop;
-
-                    if (EqualityComparer<T>.Default.Equals(stop.City, secondCity)) secondFrom = stop;
-                }
-
-                //the swap part
-                var firstTo = firstFrom.Next;
-                var secondTo = secondFrom.Next;
-
-                //reverse all of the links between the swaps
-                firstTo.CanGetTo()
-                        .TakeWhile(stop => stop != secondTo)
-                        .Reverse()
-                        .Connect(false);
-
-                firstTo.Next = secondTo;
-                firstFrom.Next = secondFrom;
-
-                var tour = new Tour(stops);
-                return tour;
-            }
-
-
-            public IList<Stop> UnconnectedClones()
-            {
-                return Cycle().Select(stop => stop.Clone()).ToList();
-            }
-
-
-            public double Cost()
-            {
-                return Cycle().Aggregate(
-                    0.0,
-                    (sum, stop) =>
-                    sum + Stop.Distance(stop, stop.Next));
-            }
-
-
-            public IEnumerable<Stop> Cycle()
-            {
-                return Anchor.CanGetTo();
-            }
-
-
-            public override string ToString()
-            {
-                string path = String.Join(
-                    "->",
-                    Cycle().Select(stop => stop.ToString()).ToArray());
-                return String.Format("Cost: {0}, Path:{1}", Cost(), path);
-            }
-
-        }
-    }
-    public static class ExtensionMethods
-    {
-
-        //take an ordered list of nodes and set their next properties
-        internal static void Connect<T>(this IEnumerable<TSP<T>.Stop> stops, bool loop)
-
-        {
-            TSP<T>.Stop prev = null, first = null;
-            foreach (var stop in stops)
-            {
-                if (first == null) first = stop;
-                if (prev != null) prev.Next = stop;
-                prev = stop;
-            }
-
-            if (loop)
-            {
-                prev.Next = first;
-            }
-        }
-
-
-        //T with the smallest func(T)
-        internal static T MinBy<T, TComparable> (
-            this IEnumerable<T> xs,
-            Func<T, TComparable> func)
-            where TComparable : IComparable<TComparable>
-        {
-            return xs.DefaultIfEmpty().Aggregate(
-                (maxSoFar, elem) =>
-                func(elem).CompareTo(func(maxSoFar)) > 0 ? maxSoFar : elem);
-        }
-
-
-        //return an ordered nearest neighbor set
-        internal static IEnumerable<TSP<T>.Stop> NearestNeighbors<T>(this IEnumerable<TSP<T>.Stop> stops)
-        {
-            var stopsLeft = stops.ToList();
-            for (var stop = stopsLeft.First();
-                    stop != null;
-                    stop = stopsLeft.MinBy(s => TSP<T>.Stop.Distance(stop, s)))
-            {
-                stopsLeft.Remove(stop);
-                yield return stop;
-            }
-        }
-    }
-}
You may download the files in Public Git.