123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- import math
- import random
- import csv
- import logging
- from collections import defaultdict
- from .shared import point_has_streetview
- from ..scoring import mean_earth_radius_km
- logger = logging.getLogger(__name__)
- URBAN_CENTERS = defaultdict(list)
- _found_countries = defaultdict(int)
- _urban_center_count = 0
- with open("./data/worldcities.csv") as infile:
- reader = csv.reader(infile, delimiter=",", quotechar='"')
- next(reader) # skip header
- for _, name, lat, lng, _, code, *_ in reader:
- code = code.lower()
- URBAN_CENTERS[code].append((name, float(lat), float(lng)))
- _found_countries[code] += 1
- _urban_center_count += 1
- logger.info(f"Read {_urban_center_count} urban centers from {len(_found_countries)} countries.")
- # only keep countries with more than 10 known cities
- VALID_COUNTRIES = tuple(k for k,v in _found_countries.items() if v > 10)
- async def urban_coord(country_lock, city_retries=10, point_retries=10, max_dist_km=25):
- """
- 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 use country_lock to determine the country from which to pull a known urban center,
- generate at most point_retries points around that urban center, and try at most
- city_retries urban centers in that country. 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.
- """
- country_lock = country_lock.lower()
- cities = URBAN_CENTERS[country_lock]
- src = random.sample(cities, k=min(city_retries, len(cities)))
- logger.info(f"Trying {len(src)} centers in {country_lock}")
- for (name, city_lat, city_lng) in src:
- # logic adapted from https://stackoverflow.com/a/7835325
- # start in a city
- logger.info(f"Trying at most {point_retries} points around {name}")
- 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(point_retries):
- # 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 await point_has_streetview(pt_lat, pt_lng):
- logger.info("Point found!")
- return (country_lock, pt_lat, pt_lng)
|