def _osm_api_query(item, bbox): """format query such that it can be passed to OSM api via overpass api Parameters: item (str): query feature for OSM bbox (array): Bounding box for query Returns: result_NodesFromWays (overpy result object) result_NodesWaysFromRels (overpy result object) """ query_clause_NodesFromWays = "way[%s](%f6, %f6, %f6, %f6);(._;>;);out geom;" \ % (item, bbox[0], bbox[1], bbox[2], bbox[3]) query_clause_NodesWaysFromRels = "rel[%s][type=multipolygon](%f6, %f6, %f6, %f6);(._;>;);out;"\ % (item, bbox[0], bbox[1], bbox[2], bbox[3]) api = overpy.Overpass(read_chunk_size=100000) try: result_NodesFromWays = api.query(query_clause_NodesFromWays) print('Nodes from Ways query for %s: done.' % item) except Exception as e: print(' WARNING: !!!! \n %s - try again in a few moments \n !!!!' % e) api = overpy.Overpass(read_chunk_size=100000) try: result_NodesWaysFromRels = api.query(query_clause_NodesWaysFromRels) print('Nodes and Ways from Relations query for %s: done.' % item) except Exception as e: print(' WARNING: !!!! \n %s - try again in a few moments \n !!!!' % e) return result_NodesFromWays, result_NodesWaysFromRels
def get_street(street, areacode, api=None): """ Retrieve streets in a given bounding area :param overpy.Overpass api: First street of intersection :param String street: Name of street :param String areacode: The OSM id of the bounding area :return: Parsed result :raises overpy.exception.OverPyException: If something bad happens. """ if api is None: api = overpy.Overpass() query = """ area(%s)->.location; ( way[highway][name="%s"](area.location); - ( way[highway=service](area.location); way[highway=track](area.location); ); ); out body; >; out skel qt; """ data = api.query(query % (areacode, street)) return data
def returnFootpathsPoint(bb, filename): slat = bb.lowery; slon = bb.lowerx; nlat = bb.uppery; nlon = bb.upperx; api = overpy.Overpass() result = api.query(" [bbox: " + str(slat) +", " + str(slon) + ", " + str(nlat) + ", " + str(nlon) + "]; (way[highway=footway]; way[highway=pedestrian]; way[foot=yes]; way[footway=sidewalk] ); /*added by auto repair*/ (._;>;); /*end of auto repair*/ out;") footpaths = result.ways f = open(filename, "w+") f.write("{\n") f.write("\"type\": \"FeatureCollection\",\n") f.write("\"features\": [\n") count = 0; for way in footpaths: f.write("{\n") f.write("\"type\": \"Feature\",\n") f.write("\"geometry\": {\n") f.write("\"type\": \"Point\",\n") f.write("\"coordinates\": [" + str(way.nodes[0].lon) + ", " + str(way.nodes[0].lat) + "]\n") #come back to this, not finished f.write("},\n") f.write("\"properties\": {}\n") if count == len(footpaths) - 1: f.write("}\n") #take out last one's comma else: f.write("},\n") f.write("\n") count = count + 1 f.write("]\n") f.write("}\n")
def query_osm_solar(polygons, decimal_places_to_round=5): api = overpy.Overpass() solar_node_lon_lats = set() for polygon in polygons: # little bit of python magic to massage the polygon into a space separated list of coordinates polygon_coords_string = " ".join([" ".join(map(str, coord)) for coord in zip(*reversed(polygon.boundary.xy))]) # I've read that querying within a polygon is a lot slower than querying within a bounding box, so if this # ends up being too slow, feel free to just replace with the bounding box of the polygon query_string = \ """ [out:json][timeout:2500]; ( node["generator:source"="solar"](poly:"{poly}"); way["generator:source"="solar"](poly:"{poly}"); relation["generator:source"="solar"](poly:"{poly}"); ); out body; >; out skel qt; """.format(poly=polygon_coords_string) result = api.query(query_string) for node in result.nodes: solar_node_lon_lats.add( (round(node.lon, decimal_places_to_round), round(node.lat, decimal_places_to_round))) return solar_node_lon_lats
def mock_get_public_transport_stops_query(monkeypatch, current_location): api = overpy.Overpass() monkeypatch.setattr( overpass_service.API, "query", lambda query: api.parse_json( utils.get_file( _get_public_transport_stops_result_filename(current_location), 'overpass')))
def _query_stops(self): """Helper function to query OpenStreetMap stops Returns raw data on stops from OpenStreetMap """ # Query stops with platform role from selected relations api = overpy.Overpass() query_str = """( /* Obtain route variants based on tags and bounding box */ relation%s(%s); /* Query for relation elements with role platform */ node(r:"platform")->.nodes; way(r:"platform"); node(w); /* Select all result sets */ ( .nodes;._; ); /* Return tags for elements */ );out body; /* Select stop area relations */ foreach.nodes( rel(bn:"platform")["public_transport"="stop_area"]; out body; );""" % (self.tags, self.bbox) return api.query(query_str)
def overpass(query): """ OSM's Overpass API service. \f :param query: An input OSM overpass query. more info canbe found `here <http://www.overpass-api.de/>_`. :return: None. """ api = overpy.Overpass() result = api.query(query) if result.nodes: click.secho("Nodes:", fg="green") for node in result.nodes: node.tags["latitude"] = node.lat node.tags["longitude"] = node.lon node.tags["id"] = node.id click.secho(json.dumps(node.tags, indent=2, ensure_ascii=False), fg="green") if result.ways: click.secho("Ways:", fg="green") for way in result.ways: way.tags["id"] = way.id click.secho(json.dumps(way.tags, indent=2, ensure_ascii=False), fg="green") if result.relations: click.secho("Relations:", fg="green") for relation in result.relations: relation.tags["id"] = relation.id click.secho(json.dumps(relation.tags, indent=2, ensure_ascii=False), fg="green")
def __init__(self, lat_b, lat_t, lon_l, lon_r, land_use, hdd, cdd): ''' Parameters: ----------- lat_b, lat_t, lon_l, lon_r: float lat lon coords of the grid cell land_use: int CORINE land use code hdd: float heating degree days cdd: float cooling degree days ''' self.land_use = land_use self.api = overpy.Overpass() self.query = """[out:json][timeout:25]; ( way[building]({},{},{},{}); node(w); ); out body; >; """.format(lat_b, lon_l, lat_t, lon_r) self.buildings = self.api.query(self.query) self.way_ids = self.buildings.get_way_ids() self.hdd = hdd self.cdd = cdd self.res_buildings, self.com_buildings = self.make_building_list()
def querySettlements(countryCode): overpassQuery = 'area["ISO3166-1"="' + countryCode + '"];\n' overpassQuery += '(node["place"~"^city$|^village$"](area);\n rel["place"~"^city$|^village$"](area););\n' overpassQuery += 'out meta;' api = overpy.Overpass() r = api.query(overpassQuery) return r
def __init__(self, coordinates, origin, radius): self.origin = origin self.location = coordinates # It's actually not a radius, but a bounding box. Making a bounding box that is broader by the meters asked: # https://en.wikipedia.org/wiki/Decimal_degrees self.radius = radius self.north_east_point = self.coordinate_for_startpoint_and_bearing(self.location, 45, self.radius) self.south_west_point = self.coordinate_for_startpoint_and_bearing(self.location, 225, self.radius) # Now we have two points that span a bounding box with the drone-coordinate in the middle # Find the closest house to these coordinates self.api = overpy.Overpass() self.house_nodes_positions = [] self.house_impression_positions = [] self.house_corner_positions = [] self.house_impression_direction = [] self.building_height = 2 # TODO: Take input from pilot self.wall_lengths = [] self.wall_distances = [] self.overpass_building = [] self.house_midpoint = []
def run(self, bounding_box): """Run scraper for a given bounding_box.""" api = overpy.Overpass() num_retries = 0 with tqdm(self.sub_areas(bounding_box), unit="sub areas", total=self.num_lat * self.num_lng) as boxes: for bb in boxes: query = self.build_query(bb) sleep_time = 2 while True: try: self.handle_response(api.query(query)) except (overpy.exception.OverpassTooManyRequests, overpy.exception.OverpassGatewayTimeout): # Sleep, then retry sleep(sleep_time) sleep_time *= 2 num_retries += 1 else: break postfix = {"places": len(self.places), "retries": num_retries} boxes.set_postfix(postfix) return list(self.places.values())
def request_germany(csvfilename): api = overpy.Overpass() r = api.query(""" area["ISO3166-1"="DE"][admin_level=2]; (node["amenity"="biergarten"](area); way["amenity"="biergarten"](area); rel["amenity"="biergarten"](area); ); out center; """) coords = [] coords += [(float(node.lon), float(node.lat)) for node in r.nodes] coords += [(float(way.center_lon), float(way.center_lat)) for way in r.ways] coords += [(float(rel.center_lon), float(rel.center_lat)) for rel in r.relations] header_name = ['lon', 'lat'] with open(csvfilename, 'w') as csv_coords_w: coords_wr = csv.writer(csv_coords_w) coords_wr.writerow(header_name) for coord in coords: coords_wr.writerow(coord)
def get_street(street: str, areacode: str, api: Optional[overpy.Overpass] = None) -> overpy.Result: """ Retrieve streets in a given bounding area :param street: Name of street :param areacode: The OSM id of the bounding area :param api: API object to fetch missing elements :return: Parsed result :raises overpy.exception.OverPyException: If something bad happens. """ if api is None: api = overpy.Overpass() query = """ area(%s)->.location; ( way[highway][name="%s"](area.location); - ( way[highway=service](area.location); way[highway=track](area.location); ); ); out body; >; out skel qt; """ data = api.query(query % (areacode, street)) return data
def get_intersection( street1: str, street2: str, areacode: str, api: Optional[overpy.Overpass] = None) -> List[overpy.Node]: """ Retrieve intersection of two streets in a given bounding area :param street1: Name of first street of intersection :param street2: Name of second street of intersection :param areacode: The OSM id of the bounding area :param api: API object to fetch missing elements :return: List of intersections :raises overpy.exception.OverPyException: If something bad happens. """ if api is None: api = overpy.Overpass() query = """ area(%s)->.location; ( way[highway][name="%s"](area.location); node(w)->.n1; way[highway][name="%s"](area.location); node(w)->.n2; ); node.n1.n2; out meta; """ data = api.query(query % (areacode, street1, street2)) return data.get_nodes()
def poiData(self, areaId): api = overpy.Overpass() for category in self._categoryLink: elements = [] query = '[timeout:{}][out:json];' \ 'area({})->.searchArea;' \ 'node(area.searchArea);' \ 'node._["name"]{};(._;>;);out center;'.format(self._timeout, areaId, category['selector']) result = api.query(query) elements.extend(result.nodes) query = '[timeout:{}][out:json];' \ 'area({})->.searchArea;' \ 'way(area.searchArea);' \ 'way._["name"]{};(._;>;);out center;'.format(self._timeout, areaId, category['selector']) result = api.query(query) elements.extend(result.ways) query = '[timeout:{}][out:json];' \ 'area({})->.searchArea;' \ 'rel(area.searchArea);' \ 'rel._["name"]{};(._;>;);out center;'.format(self._timeout, areaId, category['selector']) result = api.query(query) elements.extend(result.relations) self._saveInfo(elements, category['type'])
def get_building_coords(building_ids): building_coords = [] api = overpy.Overpass() for building, id in building_ids: result = api.query('''way(''' + id + ''');out geom;''') node = result.ways[0].get_nodes(True) lat = [] lon = [] for n in node: lat.append(float(n.lat)) lon.append(float(n.lon)) print(building + " done") building_coords.append({ "name": building, "entrance_lat": [0.0], "entrance_lon": [0.0], "boundary_lat": lat, "boundary_long": lon }) # troubleshooting (remove break when done) # break return building_coords
def get_lot_coords(lot_ids): lot_coords = [] api = overpy.Overpass() for lot, types, id in lot_ids: result = api.query('''way(''' + id + ''');out geom;''') node = result.ways[0].get_nodes(True) lat = [] lon = [] for n in node: lat.append(float(n.lat)) lon.append(float(n.lon)) print(lot + " Lot done") lot_coords.append({ "name": lot, "type": types, "boundary_lat": lat, "boundary_long": lon, "capacity": 0, "available_spots": 0, "available_times": ["0:00"] }) # troubleshooting (remove break when done) # break return lot_coords
def hours(): center = request.args.get('center', '43.5744,7.0192').replace("%2C", ",") radius = 0.01 rect = makeRect(center, radius) when = request.args.get('when', 'now') pinned = getPinned() if when == "now": now = datetime.datetime.today() # TODO tz awareness day, hour = DAYS[now.weekday()], now.strftime("%H:%M") else: day, hour = when.split(" ") osm = osmapi.OsmApi() pinnedResults = [] for p in pinned: node = osm.NodeGet(p) pinnedResults.append( makeNode(node['tag']['name'], node['lat'], node['lon'], node['id'], getTypeFromTags(node['tag']), node['tag']['opening_hours'], day, hour, center)) o = overpy.Overpass() q = f"node[opening_hours]({rect});out;" # TODO retrieve also ways (eg. a whole building is a shop) r = o.query(q) results = [] for node in r.nodes: results.append( makeNode(node.tags.get('name'), node.lat, node.lon, node.id, getTypeFromTags(node.tags), node.tags['opening_hours'], day, hour, center)) return render_template('tabla.html', nodes=results, pinned=pinnedResults)
def maxspeed(coordinates, radius): lat, lon = coordinates api = overpy.Overpass() ####################### # Query for Open Maps # ####################### result = api.query(""" way(around:""" + radius + """,""" + lat + """,""" + lon + """) ["maxspeed"]; (._;>;); out body; """) results_list = [] for way in result.ways: road = {} road["name"] = way.tags.get("name", "n/a") road["speed_limit"] = way.tags.get("maxspeed", "n/a") nodes = [] for node in way.nodes: nodes.append((node.lat, node.lon)) road["nodes"] = nodes results_list.append(road) return results_list
def find_area_intersections(coordinate, speed=15): """ coordinate: center return the intersections in the bounding box based on current location return type: get a list of Node objects representing each intersection: [<overpy.Node id=42800986 lat=40.7681152 lon=-73.9038760>, <overpy.Node id=42865836 lat=40.7689047 lon=-73.9069026>] """ # Parameter 1 radius = 0.25 ## If use speed to define radius, uncomment the following two lines # acc = 5 # m/s^2 # radius = max(0.2, (speed * 1.60934) ** 2 / (2 * 1000 * acc)) # in km center_lat = coordinate[0] center_lon = coordinate[1] north = pointRadialDistance(center_lat, center_lon, 0, radius) west = pointRadialDistance(center_lat, center_lon, 90, radius) south = pointRadialDistance(center_lat, center_lon, 180, radius) east = pointRadialDistance(center_lat, center_lon, 270, radius) # Use overpass api to extract intersection coordinates in the bounding box api = overpy.Overpass() query_result = api.query(""" [bbox: """ + str(south[0]) + "," + str(west[1]) + "," + str(north[0]) + ',' + str(east[1]) + """]; way[highway~"^(motorway|trunk|primary|secondary|tertiary|(motorway|trunk|primary|secondary)_link)$"]->.major; way[highway~"^(motorway|trunk|primary|secondary|tertiary|(motorway|trunk|primary|secondary)_link)$"]->.minor; node(w.major)(w.minor)[highway=traffic_signals]; out; """) ret = query_result.get_nodes() if len(ret) == 0: return None else: return ret
def _query_routes(self): """Helper function to query OpenStreetMap routes Returns raw data on routes from OpenStreetMap """ # Query relations of route variants, their masters and geometry api = overpy.Overpass() query_str = """( /* Obtain route variants based on tags and bounding box */ relation%s(%s)->.routes; /* Query for related route masters */ relation[type=route_master](br.routes)->.masters; /* Query for routes' geometry (ways and it's nodes) */ way(r.routes); node(w); /* Select all result sets */ ( .routes;.masters;._; ); /* Return tags for elements and roles for relation members. */ );out body;""" % (self.tags, self.bbox) return api.query(query_str)
def query_thread(): global last_query_result, last_query_pos, cache_valid api = overpy.Overpass(url=OVERPASS_API_URL, headers=OVERPASS_HEADERS, timeout=10.) while True: time.sleep(1) if last_gps is not None: fix_ok = last_gps.flags & 1 if not fix_ok: continue if last_query_pos is not None: cur_ecef = geodetic2ecef( (last_gps.latitude, last_gps.longitude, last_gps.altitude)) prev_ecef = geodetic2ecef( (last_query_pos.latitude, last_query_pos.longitude, last_query_pos.altitude)) dist = np.linalg.norm(cur_ecef - prev_ecef) if dist < 1000: continue if dist > 3000: cache_valid = False q = build_way_query(last_gps.latitude, last_gps.longitude, radius=3000) try: new_result = api.query(q) # Build kd-tree nodes = [] real_nodes = [] node_to_way = defaultdict(list) for n in new_result.nodes: nodes.append((float(n.lat), float(n.lon), 0)) real_nodes.append(n) for way in new_result.ways: for n in way.nodes: node_to_way[n.id].append(way) nodes = np.asarray(nodes) nodes = geodetic2ecef(nodes) tree = spatial.cKDTree(nodes) query_lock.acquire() last_query_result = new_result, tree, real_nodes, node_to_way last_query_pos = last_gps cache_valid = True query_lock.release() except Exception as e: print e query_lock.acquire() last_query_result = None query_lock.release()
def get_all_parks(self): parks = [] # all osm parks at 10/07/2016 api = overpy.Overpass() request = '[timeout:620][date:"2016-07-17T00:00:00Z"];(way["leisure"="park"];way["landuse"="recreation_ground"];way["leisure"="recreation_ground"];way["leisure"="pitch"];way["leisure"="garden"];way["leisure"="golf_course"];way["leisure"="playground"];way["landuse"="meadow"];way["landuse"="grass"];way["landuse"="greenfield"];way["natural"="scrub"];way["natural"="heath"];way["natural"="grassland"];way["landuse"="farmyard"];way["landuse"="vineyard"];way[landuse=farmland];way[landuse=orchard];);out;>;out skel qt;' request = '[bbox:{},{},{},{}]{}'.format(south, west, north, east, request) response = api.query(request) for w in response.ways: name = w.tags.get("name", None) leisure = w.tags.get("leisure", None) landuse = w.tags.get("landuse", None) natural = w.tags.get("natural", None) if name: area_name = name[:1].upper() + name[1:] elif leisure: area_name = leisure[:1].upper() + leisure[1:] elif landuse: area_name = landuse[:1].upper() + landuse[1:] elif natural: area_name = natural[:1].upper() + natural[1:] else: area_name = 'Error' parks.append({ 'id': w.id, 'name': area_name, 'coords': [[float(c.lat), float(c.lon)] for c in w.nodes] # json.dumps and json.loads }) return parks
def query_OSM(coords): """ Sends a request to OSM server and returns an array of all the building nodes in the area specified by [coords] Returns: [[building1_node, ...], [building2_node, ...], ...] where each building_node is in (lat,lon) format. """ api = overpy.Overpass() query_result = api.query((""" way ({}, {}, {}, {}) ["building"]; (._;>;); out body; """).format(coords[0], coords[1], coords[2], coords[3])) # Unprocessed building data from the query buildings = query_result.ways # The list of each building's coordinates. # Each item in this list is a list of points in (lat,lon) for each building's nodes. building_coords = [] for building in buildings: points = [(float(str(n.lat)), float(str(n.lon))) for n in building.nodes] building_coords.append(points) return building_coords
def ex_query(s, w, n, e): # Query Overpass for known gym areas api = overpy.Overpass(xml_parser=0) result = api.query(""" [out:json] [date:"2016-07-10T00:00:00Z"] [timeout:620] [bbox:{},{},{},{}]; ( //Tags that are confirmed to classify gyms as 'parks' for EX Raids way[leisure=park]; way[landuse=recreation_ground]; way[leisure=recreation_ground]; way[leisure=pitch]; way[leisure=garden]; way[leisure=golf_course]; way[leisure=playground]; way[landuse=meadow]; way[landuse=grass]; way[landuse=greenfield]; way[natural=scrub]; way[natural=grassland]; way[landuse=farmyard]; ); out body; >; out skel qt; """.format(s, w, n, e)) return result
def maxspeed(latN, lonN, radius): lon = "{:.7}".format(lonN) lat = str(latN) print(lon) #lon = str(lonN) api = overpy.Overpass() # fetch all ways and nodes result = api.query(""" way(around:""" + radius + """,""" + lat + """,""" + lon + """) ["maxspeed"]; (._;>;); out body; """) #print("result len: " +str( len(result.ways))) #if result.ways is not None: try: way = result.ways[0] speedWay = way.tags.get("maxspeed", "n/a") speedArr[0] = speedWay[0] speedArr[1] = speedWay[1] speedLimit = magic(speedArr) print("Speed Limit: " + str(speedLimit)) return speedLimit except IndexError: return 0
def _insistent_osm_api_query(query_clause, read_chunk_size=100000, end_of_patience=127): """Runs a single Overpass API query through overpy.Overpass.query. In case of failure it tries again after an ever increasing waiting period. If the waiting period surpasses a given limit an exception is raised. Parameters: query_clause (str): the query read_chunk_size (int): paramter passed over to overpy.Overpass.query end_of_patience (int): upper limit for the next waiting period to proceed. Returns: result as returned by overpy.Overpass.query """ api = overpy.Overpass(read_chunk_size=read_chunk_size) waiting_period = 1 while True: try: return api.query(query_clause) except overpy.exception.OverpassTooManyRequests: if waiting_period < end_of_patience: print( ' WARNING: too many Overpass API requests - try again in {} seconds' .format(waiting_period)) else: raise Exception("Overpass API is consistently unavailable") except Exception as exc: if waiting_period < end_of_patience: print(' WARNING: !!!!\n {}\n try again in {} seconds'.format( exc, waiting_period)) else: raise Exception("The Overpass API is consistently unavailable") time.sleep(waiting_period) waiting_period *= 2
def _query_overpass_api(sw_coord, ne_coord, nest_parks=False): parks = [] api = overpy.Overpass() request = _build_overpass_query(sw_coord, ne_coord, nest_parks) while True: try: start = default_timer() log.debug('Overpass API request: `%s`', request) response = api.query(request) break except overpy.exception.OverpassTooManyRequests: log.warning( 'Overpass API quota reached. Trying again in 5 minutes...') time.sleep(300) duration = default_timer() - start log.info('Overpass API park response received in %.2fs.', duration) for way in response.ways: parks.append( [[float(node.lat), float(node.lon)] for node in way.nodes]) return parks
def query_thread(): global last_query_result, last_query_pos, cache_valid api = overpy.Overpass(url=OVERPASS_API_URL, headers=OVERPASS_HEADERS, timeout=10.) while True: time.sleep(1) if last_gps is not None: fix_ok = last_gps.flags & 1 if not fix_ok: continue if last_query_pos is not None: cur_ecef = geodetic2ecef((last_gps.latitude, last_gps.longitude, last_gps.altitude)) prev_ecef = geodetic2ecef((last_query_pos.latitude, last_query_pos.longitude, last_query_pos.altitude)) dist = np.linalg.norm(cur_ecef - prev_ecef) if dist < 1000: #updated when we are 1km from the edge of the downloaded circle continue if dist > 3000: cache_valid = False q = build_way_query(last_gps.latitude, last_gps.longitude, radius=3000) try: new_result = api.query(q) # Build kd-tree nodes = [] real_nodes = [] node_to_way = defaultdict(list) location_info = {} for n in new_result.nodes: nodes.append((float(n.lat), float(n.lon), 0)) real_nodes.append(n) for way in new_result.ways: for n in way.nodes: node_to_way[n.id].append(way) for area in new_result.areas: if area.tags.get('admin_level', '') == "2": location_info['country'] = area.tags.get('ISO3166-1:alpha2', '') if area.tags.get('admin_level', '') == "4": location_info['region'] = area.tags.get('name', '') nodes = np.asarray(nodes) nodes = geodetic2ecef(nodes) tree = spatial.cKDTree(nodes) query_lock.acquire() last_query_result = new_result, tree, real_nodes, node_to_way, location_info last_query_pos = last_gps cache_valid = True query_lock.release() except Exception as e: print(e) query_lock.acquire() last_query_result = None query_lock.release()
def get_osm_data(coor_query): """ :param coor_query: [x_min, min_z, x_max, y_max] :return: OSM query result """ api = overpy.Overpass() query_buildings = QUERY_BASE.format("building", coor_query[1], coor_query[0], coor_query[3], coor_query[2]) query_successful = False wait_duration = 2 #wait_duration = 60 result = None while not query_successful: try: result = api.query(query_buildings) query_successful = True except overpy.exception.OverpassGatewayTimeout or overpy.exception.OverpassTooManyRequests or ConnectionResetError: print( "OSM server overload. Waiting for {} seconds before querying again..." .format(wait_duration)) time.sleep(wait_duration) wait_duration *= 2 # Multiply wait time by 2 for the next time return result