shared.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import collections
  2. import logging
  3. import requests
  4. # Google API key, with access to Street View Static API
  5. google_api_key = "AIzaSyAqjCYR6Szph0X0H_iD6O1HenFhL9jySOo"
  6. metadata_url = "https://maps.googleapis.com/maps/api/streetview/metadata"
  7. logger = logging.getLogger(__name__)
  8. def point_has_streetview(lat, lng):
  9. """
  10. Returns True if the streetview metadata endpoint says a given point has
  11. data available, and False otherwise.
  12. This function calls the streetview metadata endpoint - there is no quota consumed.
  13. """
  14. return requests.get(metadata_url, params={
  15. "key": google_api_key,
  16. "location": f"{lat},{lng}",
  17. }).json()["status"] == "OK"
  18. class PointSource:
  19. """
  20. Base class to handle the logic of managing a cache of (lat, lng) points.
  21. """
  22. def __init__(self, stock_target):
  23. self.stock = collections.deque()
  24. self.stock_target = stock_target
  25. def _restock_impl(self, n):
  26. """
  27. Returns a list of new points to add to the stock.
  28. Implementations of this method should try to return at least n points for performance.
  29. """
  30. raise NotImplementedError("Subclasses must implement this")
  31. def restock(self, n=None):
  32. """
  33. Restock at least n points into this source.
  34. If n is not provided, it will default to stock_target, as set during the
  35. construction of this point source.
  36. """
  37. n = n if n is not None else self.stock_target - len(self.stock)
  38. if n > 0:
  39. logger.info(f"Restocking {type(self).__name__} with {n} points")
  40. pts = self._restock_impl(n)
  41. self.stock.extend(pts)
  42. diff = n - len(pts)
  43. if diff > 0:
  44. # if implementations of _restock_impl are well behaved, this will
  45. # never actually need to recurse to finish the job.
  46. self.restock(n=diff)
  47. logger.info(f"Finished restocking {type(self).__name__}")
  48. def get_points(self, n=1):
  49. """
  50. Pull n points from the current stock.
  51. It is recommended to call PointSource.restock after this, to ensure the
  52. stock is not depleted. If possible, calling restock in another thread is
  53. recommended, as it can be a long operation depending on implementation.
  54. """
  55. if len(self.stock) >= n:
  56. pts = []
  57. for _ in range(n):
  58. pts.append(self.stock.popleft())
  59. return pts
  60. self.restock(n=n)
  61. # this is safe as long as restock does actually add enough new points.
  62. # unless this object is being rapidly drained by another thread,
  63. # this will recur at most once.
  64. return self.get_points(n=n)