|
@@ -1,6 +1,8 @@
|
|
|
import json
|
|
|
import math
|
|
|
import random
|
|
|
+import threading
|
|
|
+import collections
|
|
|
|
|
|
import requests
|
|
|
import haversine
|
|
@@ -20,7 +22,6 @@ with open("./urban-centers-non-usa.csv") as infile:
|
|
|
for line in infile:
|
|
|
lat, lng = line.split(",")
|
|
|
urban_centers_non_usa.append((float(lat.strip()), float(lng.strip())))
|
|
|
-urban_usa_chance = 0.1
|
|
|
|
|
|
|
|
|
def point_has_streetview(lat, lng):
|
|
@@ -93,7 +94,7 @@ def random_street_view_generator(only_america=False):
|
|
|
yield points.pop()
|
|
|
|
|
|
|
|
|
-def urban_coord(max_retries=10, retries_per_point=30, max_dist_km=25, only_america=False):
|
|
|
+def urban_coord(max_retries=10, retries_per_point=30, max_dist_km=25, usa_chance=0.1):
|
|
|
"""
|
|
|
Returns (latitude, longitude) of usable coord (where google has data) that is near
|
|
|
a known urban center. Points will be at most max_dist_km kilometers away. This function will
|
|
@@ -104,7 +105,7 @@ def urban_coord(max_retries=10, retries_per_point=30, max_dist_km=25, only_ameri
|
|
|
This function calls the streetview metadata endpoint - there is no quota consumed.
|
|
|
"""
|
|
|
|
|
|
- src = urban_centers_usa if only_america or random.random() <= urban_usa_chance else urban_centers_non_usa
|
|
|
+ src = urban_centers_usa if random.random() <= usa_chance else urban_centers_non_usa
|
|
|
|
|
|
for _ in range(max_retries):
|
|
|
# logic adapted from https://stackoverflow.com/a/7835325
|
|
@@ -129,6 +130,93 @@ def urban_coord(max_retries=10, retries_per_point=30, max_dist_km=25, only_ameri
|
|
|
return (pt_lat, pt_lng)
|
|
|
|
|
|
|
|
|
+class PointSource:
|
|
|
+ def __init__(self, stock_target):
|
|
|
+ self.stock = collections.deque()
|
|
|
+ self.stock_target = stock_target
|
|
|
+
|
|
|
+ def _restock_impl(self, n):
|
|
|
+ """
|
|
|
+ Returns a list of new points to add to the stock.
|
|
|
+ Implementations of this method should try to return at least n points for performance.
|
|
|
+ """
|
|
|
+ raise NotImplementedError("Subclasses must implement this")
|
|
|
+
|
|
|
+ def restock(self, n=None):
|
|
|
+ n = n if n is not None else self.stock_target - len(self.stock)
|
|
|
+ if n > 0:
|
|
|
+ pts = self._restock_impl(n)
|
|
|
+ self.stock.extend(pts)
|
|
|
+ diff = n - len(pts)
|
|
|
+ if diff > 0:
|
|
|
+ # if implementations of _restock_impl are well behaved, this will
|
|
|
+ # never actually need to recurse to finish the job.
|
|
|
+ self.restock(n=diff)
|
|
|
+
|
|
|
+ def get_points(self, n=1):
|
|
|
+ if len(self.stock) >= n:
|
|
|
+ pts = []
|
|
|
+ for _ in range(n):
|
|
|
+ pts.append(self.stock.popleft())
|
|
|
+ threading.Thread(target=self.restock).start()
|
|
|
+ return pts
|
|
|
+ self.restock(n=n)
|
|
|
+ # this is safe as long as restock does actually add enough new points.
|
|
|
+ # unless this object is being rapidly drained by another thread,
|
|
|
+ # this will recur at most once.
|
|
|
+ return self.get_points(n=n)
|
|
|
+
|
|
|
+
|
|
|
+class MapCrunchPointSource(PointSource):
|
|
|
+ def __init__(self, stock_target=20, max_retries=100, only_america=False):
|
|
|
+ super().__init__(stock_target=stock_target)
|
|
|
+ self.max_retries = max_retries
|
|
|
+ self.only_america = only_america
|
|
|
+
|
|
|
+ def _restock_impl(self, n):
|
|
|
+ points = []
|
|
|
+ while len(points) < n:
|
|
|
+ pt = generate_coord(
|
|
|
+ max_retries=self.max_retries,
|
|
|
+ only_america=self.only_america
|
|
|
+ )
|
|
|
+ if pt is not None:
|
|
|
+ points.append(pt)
|
|
|
+
|
|
|
+
|
|
|
+class RSVPointSource(PointSource):
|
|
|
+ def __init__(self, stock_target=20, only_america=False):
|
|
|
+ super().__init__(stock_target=stock_target)
|
|
|
+ self.only_america = only_america
|
|
|
+
|
|
|
+ def _restock_impl(self, n):
|
|
|
+ points = []
|
|
|
+ while len(points) < n:
|
|
|
+ points.extend(call_random_street_view(only_america=self.only_america))
|
|
|
+ return points
|
|
|
+
|
|
|
+
|
|
|
+class UrbanPointSource(PointSource):
|
|
|
+ def __init__(self, stock_target=20, max_retries=10, retries_per_point=30, max_dist_km=25, usa_chance=0.1):
|
|
|
+ super().__init__(stock_target=stock_target)
|
|
|
+ self.max_retries = max_retries
|
|
|
+ self.retries_per_point = retries_per_point
|
|
|
+ self.max_dist_km = max_dist_km
|
|
|
+ self.usa_chance = usa_chance
|
|
|
+
|
|
|
+ def _restock_impl(self, n):
|
|
|
+ points = []
|
|
|
+ while len(points) < n:
|
|
|
+ pt = urban_coord(
|
|
|
+ max_retries=self.max_retries,
|
|
|
+ retries_per_point=self.retries_per_point,
|
|
|
+ max_dist_km=self.max_dist_km,
|
|
|
+ usa_chance=self.usa_chance
|
|
|
+ )
|
|
|
+ if pt is not None:
|
|
|
+ points.append(pt)
|
|
|
+
|
|
|
+
|
|
|
mean_earth_radius_km = (6378 + 6357) / 2
|
|
|
|
|
|
# if you're more than 1/4 of the Earth's circumfrence away, you get 0
|