urban_centers.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. import math
  2. import random
  3. import csv
  4. import logging
  5. from collections import defaultdict
  6. from .shared import point_has_streetview
  7. from ..scoring import mean_earth_radius_km
  8. logger = logging.getLogger(__name__)
  9. URBAN_CENTERS = defaultdict(list)
  10. _found_countries = defaultdict(int)
  11. _urban_center_count = 0
  12. with open("./data/worldcities.csv") as infile:
  13. reader = csv.reader(infile, delimiter=",", quotechar='"')
  14. next(reader) # skip header
  15. for _, name, lat, lng, _, code, *_ in reader:
  16. code = code.lower()
  17. URBAN_CENTERS[code].append((name, float(lat), float(lng)))
  18. _found_countries[code] += 1
  19. _urban_center_count += 1
  20. logger.info(f"Read {_urban_center_count} urban centers from {len(_found_countries)} countries.")
  21. # only keep countries with more than 10 known cities
  22. VALID_COUNTRIES = tuple(k for k,v in _found_countries.items() if v > 10)
  23. async def urban_coord(country_lock, city_retries=10, point_retries=10, max_dist_km=25):
  24. """
  25. Returns (latitude, longitude) of usable coord (where google has data) that is near
  26. a known urban center. Points will be at most max_dist_km kilometers away. This function
  27. will use country_lock to determine the country from which to pull a known urban center,
  28. generate at most point_retries points around that urban center, and try at most
  29. city_retries urban centers in that country. If none of the generated points have street
  30. view data, this will return None. Otherwise, it will exit as soon as suitable point is
  31. found.
  32. This function calls the streetview metadata endpoint - there is no quota consumed.
  33. """
  34. country_lock = country_lock.lower()
  35. cities = URBAN_CENTERS[country_lock]
  36. src = random.sample(cities, k=min(city_retries, len(cities)))
  37. logger.info(f"Trying {len(src)} centers in {country_lock}")
  38. for (name, city_lat, city_lng) in src:
  39. # logic adapted from https://stackoverflow.com/a/7835325
  40. # start in a city
  41. logger.info(f"Trying at most {point_retries} points around {name}")
  42. city_lat_rad = math.radians(city_lat)
  43. sin_lat = math.sin(city_lat_rad)
  44. cos_lat = math.cos(city_lat_rad)
  45. city_lng_rad = math.radians(city_lng)
  46. for _ in range(point_retries):
  47. # turn a random direction, and go random distance
  48. dist_km = random.random() * max_dist_km
  49. angle_rad = random.random() * 2 * math.pi
  50. d_over_radius = dist_km / mean_earth_radius_km
  51. sin_dor = math.sin(d_over_radius)
  52. cos_dor = math.cos(d_over_radius)
  53. pt_lat_rad = math.asin(sin_lat * cos_dor + cos_lat * sin_dor * math.cos(angle_rad))
  54. 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))
  55. pt_lat = math.degrees(pt_lat_rad)
  56. pt_lng = math.degrees(pt_lng_rad)
  57. if await point_has_streetview(pt_lat, pt_lng):
  58. logger.info("Point found!")
  59. return (country_lock, pt_lat, pt_lng)