urban_centers.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import math
  2. import random
  3. from .shared import point_has_streetview, PointSource
  4. from ..scoring import mean_earth_radius_km
  5. initialized = False
  6. urban_centers_usa = []
  7. urban_centers_non_usa = []
  8. def init():
  9. """
  10. Read in the urban centers data files. Should be called before trying to generate points.
  11. """
  12. global initialized
  13. if initialized:
  14. return
  15. with open("./data/urban-centers-usa.csv") as infile:
  16. for line in infile:
  17. lat, lng = line.split(",")
  18. urban_centers_usa.append((float(lat.strip()), float(lng.strip())))
  19. with open("./data/urban-centers-non-usa.csv") as infile:
  20. for line in infile:
  21. lat, lng = line.split(",")
  22. urban_centers_non_usa.append((float(lat.strip()), float(lng.strip())))
  23. initialized = True
  24. def urban_coord(max_retries=10, retries_per_point=30, max_dist_km=25, usa_chance=0.1):
  25. """
  26. Returns (latitude, longitude) of usable coord (where google has data) that is near
  27. a known urban center. Points will be at most max_dist_km kilometers away. This function will
  28. generate at most retries_per_point points around an urban center, and will try at most
  29. max_retries urban centers. If none of the generated points have street view data,
  30. this will return None. Otherwise, it will exit as soon as suitable point is found.
  31. This function calls the streetview metadata endpoint - there is no quota consumed.
  32. """
  33. src = urban_centers_usa if random.random() <= usa_chance else urban_centers_non_usa
  34. for _ in range(max_retries):
  35. # logic adapted from https://stackoverflow.com/a/7835325
  36. # start in a city
  37. (city_lat, city_lng) = random.choice(src)
  38. city_lat_rad = math.radians(city_lat)
  39. sin_lat = math.sin(city_lat_rad)
  40. cos_lat = math.cos(city_lat_rad)
  41. city_lng_rad = math.radians(city_lng)
  42. for _ in range(retries_per_point):
  43. # turn a random direction, and go random distance
  44. dist_km = random.random() * max_dist_km
  45. angle_rad = random.random() * 2 * math.pi
  46. d_over_radius = dist_km / mean_earth_radius_km
  47. sin_dor = math.sin(d_over_radius)
  48. cos_dor = math.cos(d_over_radius)
  49. pt_lat_rad = math.asin(sin_lat * cos_dor + cos_lat * sin_dor * math.cos(angle_rad))
  50. pt_lng_rad = city_lng_rad + math.atan2(math.sin(angle_rad) * sin_dor * cos_lat, cos_dor - sin_lat * math.sin(pt_lat_rad))
  51. pt_lat = math.degrees(pt_lat_rad)
  52. pt_lng = math.degrees(pt_lng_rad)
  53. if point_has_streetview(pt_lat, pt_lng):
  54. return (pt_lat, pt_lng)
  55. class UrbanPointSource(PointSource):
  56. def __init__(self, stock_target=20, max_retries=10, retries_per_point=30, max_dist_km=25, usa_chance=0.1):
  57. super().__init__(stock_target=stock_target)
  58. self.max_retries = max_retries
  59. self.retries_per_point = retries_per_point
  60. self.max_dist_km = max_dist_km
  61. self.usa_chance = usa_chance
  62. if not initialized:
  63. init()
  64. def _restock_impl(self, n):
  65. points = []
  66. while len(points) < n:
  67. pt = urban_coord(
  68. max_retries=self.max_retries,
  69. retries_per_point=self.retries_per_point,
  70. max_dist_km=self.max_dist_km,
  71. usa_chance=self.usa_chance
  72. )
  73. if pt is not None:
  74. points.append(pt)
  75. return points