import math import random from .shared import point_has_streetview, PointSource from ..scoring import mean_earth_radius_km initialized = False urban_centers_usa = [] urban_centers_non_usa = [] def init(): """ Read in the urban centers data files. Should be called before trying to generate points. """ global initialized if initialized: return with open("./data/urban-centers-usa.csv") as infile: for line in infile: lat, lng = line.split(",") urban_centers_usa.append((float(lat.strip()), float(lng.strip()))) with open("./data/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()))) initialized = True 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 generate at most retries_per_point points around an urban center, and will try at most max_retries urban centers. If none of the generated points have street view data, this will return None. Otherwise, it will exit as soon as suitable point is found. This function calls the streetview metadata endpoint - there is no quota consumed. """ 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 # start in a city (city_lat, city_lng) = random.choice(src) city_lat_rad = math.radians(city_lat) sin_lat = math.sin(city_lat_rad) cos_lat = math.cos(city_lat_rad) city_lng_rad = math.radians(city_lng) for _ in range(retries_per_point): # turn a random direction, and go random distance dist_km = random.random() * max_dist_km angle_rad = random.random() * 2 * math.pi d_over_radius = dist_km / mean_earth_radius_km sin_dor = math.sin(d_over_radius) cos_dor = math.cos(d_over_radius) pt_lat_rad = math.asin(sin_lat * cos_dor + cos_lat * sin_dor * math.cos(angle_rad)) 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)) pt_lat = math.degrees(pt_lat_rad) pt_lng = math.degrees(pt_lng_rad) if point_has_streetview(pt_lat, pt_lng): return (pt_lat, pt_lng) 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 if not initialized: init() 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) return points