def unique_timezone_at(self, *, lng: float, lat: float) -> Optional[str]: """instantly returns the name of a unique zone within the corresponding shortcut :param lng: longitude of the point in degree (-180.0 to 180.0) :param lat: latitude in degree (90.0 to -90.0) :return: the timezone name of the unique zone or None if there are no or multiple zones in this shortcut """ lng, lat = rectify_coordinates(lng, lat) shortcut_id_x, shortcut_id_y = coord2shortcut(lng, lat) return self._get_unique_zone(shortcut_id_x, shortcut_id_y)
def certain_timezone_at(self, *, lng: float, lat: float) -> Optional[str]: """checks in which timezone polygon the point is certainly included in .. note:: this is only meaningful when you use timezone data WITHOUT oceans! Otherwise some timezone will always be matched, since ocean timezones span the whole globe. -> useless to actually test all polygons. .. note:: this is much slower than 'timezone_at'! :param lng: longitude of the point in degree :param lat: latitude in degree :return: the timezone name of the polygon the point is included in or None """ lng, lat = rectify_coordinates(lng, lat) shortcut_id_x, shortcut_id_y = coord2shortcut(lng, lat) timezone = self._get_unique_zone(shortcut_id_x, shortcut_id_y) if timezone is not None: # found perfect and fast match return timezone possible_polygons = self.polygon_ids_of_shortcut( shortcut_id_x, shortcut_id_y) # x = longitude y = latitude both converted to 8byte int x = coord2int(lng) y = coord2int(lat) # check if the point is actually included in one of the polygons for polygon_nr in possible_polygons: # get boundaries getattr(self, POLY_MAX_VALUES).seek(4 * NR_BYTES_I * polygon_nr) boundaries = self._fromfile( getattr(self, POLY_MAX_VALUES), dtype=DTYPE_FORMAT_SIGNED_I_NUMPY, count=4, ) if not (x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]): outside_all_holes = True # when the point is within a hole of the polygon this timezone doesn't need to be checked for hole_coordinates in self._holes_of_poly(polygon_nr): if inside_polygon(x, y, hole_coordinates): outside_all_holes = False break if outside_all_holes: if inside_polygon(x, y, self.coords_of(polygon_nr=polygon_nr)): return getattr(self, TIMEZONE_NAMES)[self.id_of(polygon_nr)] return None # no polygon has been matched
def timezone_at(self, *, lng: float, lat: float) -> str: """instantly returns the name of the most common zone within the corresponding shortcut :param lng: longitude of the point in degree (-180.0 to 180.0) :param lat: latitude in degree (90.0 to -90.0) :return: the timezone name of the most common zone or None if there are no timezone polygons in this shortcut """ lng, lat = rectify_coordinates(lng, lat) shortcut_id_x, shortcut_id_y = coord2shortcut(lng, lat) shortcut_direct_id = getattr(self, SHORTCUTS_DIRECT_ID) shortcut_direct_id.seek(NR_LAT_SHORTCUTS * NR_BYTES_H * shortcut_id_x + NR_BYTES_H * shortcut_id_y) try: return getattr(self, TIMEZONE_NAMES)[unpack( DTYPE_FORMAT_H, shortcut_direct_id.read(NR_BYTES_H))[0]] except IndexError: raise ValueError("timezone could not be found. index error.")
def certain_timezone_at(self, *, lng: float, lat: float) -> Optional[str]: """ looks up in which polygon the point certainly is included in .. note:: this is much slower than 'timezone_at'! :param lng: longitude of the point in degree :param lat: latitude in degree :return: the timezone name of the polygon the point is included in or None """ lng, lat = rectify_coordinates(lng, lat) shortcut_id_x, shortcut_id_y = coord2shortcut(lng, lat) possible_polygons = self.polygon_ids_of_shortcut( shortcut_id_x, shortcut_id_y) # x = longitude y = latitude both converted to 8byte int x = coord2int(lng) y = coord2int(lat) # check if the point is actually included in one of the polygons for polygon_nr in possible_polygons: # get boundaries getattr(self, POLY_MAX_VALUES).seek(4 * NR_BYTES_I * polygon_nr) boundaries = self._fromfile(getattr(self, POLY_MAX_VALUES), dtype=DTYPE_FORMAT_SIGNED_I_NUMPY, count=4) if not (x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]): outside_all_holes = True # when the point is within a hole of the polygon this timezone doesn't need to be checked for hole_coordinates in self._holes_of_poly(polygon_nr): if inside_polygon(x, y, hole_coordinates): outside_all_holes = False break if outside_all_holes: if inside_polygon(x, y, self.coords_of(polygon_nr=polygon_nr)): return getattr(self, TIMEZONE_NAMES)[self.id_of(polygon_nr)] return None # no polygon has been matched
def certain_timezone_at(self, *, lng, lat): """ this function looks up in which polygon the point certainly is included this is much slower than 'timezone_at'! :param lng: longitude of the point in degree :param lat: latitude in degree :return: the timezone name of the polygon the point is included in or None """ lng, lat = rectify_coordinates(lng, lat) shortcut_id_x, shortcut_id_y = coord2shortcut(lng, lat) possible_polygons = self.polygon_ids_of_shortcut(shortcut_id_x, shortcut_id_y) # x = longitude y = latitude both converted to 8byte int x = coord2int(lng) y = coord2int(lat) # check if the point is actually included in one of the polygons for polygon_nr in possible_polygons: # get boundaries self.poly_max_values.seek(4 * NR_BYTES_I * polygon_nr) boundaries = self.fromfile(self.poly_max_values, dtype=DTYPE_FORMAT_SIGNED_I_NUMPY, count=4) if not (x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]): outside_all_holes = True # when the point is within a hole of the polygon this timezone doesn't need to be checked for hole_coordinates in self._holes_of_line(polygon_nr): if inside_polygon(x, y, hole_coordinates): outside_all_holes = False break if outside_all_holes: if inside_polygon(x, y, self.coords_of(line=polygon_nr)): return self.timezone_names[self.id_of(polygon_nr)] # no polygon has been matched return None
def timezone_at(self, *, lng: float, lat: float) -> Optional[str]: """ looks up in which timezone the given coordinate is possibly included in to speed things up there are shortcuts being used (stored in a binary file) especially for large polygons it is expensive to check if a point is really included, so certain simplifications are made and even when you get a hit the point might actually not be inside the polygon (for example when there is only one timezone nearby) if you want to make sure a point is really inside a timezone use ``certain_timezone_at()`` :param lng: longitude of the point in degree (-180.0 to 180.0) :param lat: latitude in degree (90.0 to -90.0) :return: the timezone name of a matching polygon or None """ lng, lat = rectify_coordinates(lng, lat) shortcut_id_x, shortcut_id_y = coord2shortcut(lng, lat) getattr(self, SHORTCUTS_UNIQUE_ID).seek( (NR_LAT_SHORTCUTS * NR_BYTES_H * shortcut_id_x + NR_BYTES_H * shortcut_id_y)) try: # if there is just one possible zone in this shortcut instantly return its name return getattr(self, TIMEZONE_NAMES)[unpack( DTYPE_FORMAT_H, getattr(self, SHORTCUTS_UNIQUE_ID).read(NR_BYTES_H))[0]] except IndexError: possible_polygons = self.polygon_ids_of_shortcut( shortcut_id_x, shortcut_id_y) nr_possible_polygons = len(possible_polygons) if nr_possible_polygons == 0: return None if nr_possible_polygons == 1: # there is only one polygon in that area. return its timezone name without further checks return getattr(self, TIMEZONE_NAMES)[self.id_of( possible_polygons[0])] # create a list of all the timezone ids of all possible polygons ids = self.id_list(possible_polygons, nr_possible_polygons) # x = longitude y = latitude both converted to 8byte int x = coord2int(lng) y = coord2int(lat) # check until the point is included in one of the possible polygons for i in range(nr_possible_polygons): # when including the current polygon only polygons from the same zone remain, same_element = all_the_same(pointer=i, length=nr_possible_polygons, id_list=ids) if same_element != -1: # return the name of that zone return getattr(self, TIMEZONE_NAMES)[same_element] polygon_nr = possible_polygons[i] # get the boundaries of the polygon = (lng_max, lng_min, lat_max, lat_min) getattr(self, POLY_MAX_VALUES).seek(4 * NR_BYTES_I * polygon_nr) boundaries = self._fromfile(getattr(self, POLY_MAX_VALUES), dtype=DTYPE_FORMAT_SIGNED_I_NUMPY, count=4) # only run the expensive algorithm if the point is withing the boundaries if not (x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]): outside_all_holes = True # when the point is within a hole of the polygon, this timezone must not be returned for hole_coordinates in self._holes_of_poly(polygon_nr): if inside_polygon(x, y, hole_coordinates): outside_all_holes = False break if outside_all_holes: if inside_polygon( x, y, self.coords_of(polygon_nr=polygon_nr)): # the point is included in this polygon. return its timezone name without further checks return getattr(self, TIMEZONE_NAMES)[ids[i]] # the timezone name of the last polygon should always be returned # if no other polygon has been matched beforehand. raise ValueError( 'BUG: this statement should never be reached. Please open up an issue on Github!' )
def closest_timezone_at(self, *, lng: float, lat: float, delta_degree: int = 1, exact_computation: bool = False, return_distances: bool = False, force_evaluation: bool = False): """ Searches for the closest polygon in the surrounding shortcuts Computes the (approximate) distance to all the polygons within ``delta_degree`` degree lng and lat Make sure that the point does not lie within a polygon .. note:: the algorithm won't find the closest polygon when it's on the 'other end of earth' (it can't search beyond the 180 deg lng border!) .. note:: x degrees lat are not the same distance apart than x degree lng! This is also the reason why there could still be a closer polygon even though you got a result already. In order to make sure to get the closest polygon, you should increase the search radius until you get a result and then increase it once more (and take that result). This should however only make a difference in rare cases. :param lng: longitude in degree :param lat: latitude in degree :param delta_degree: the 'search radius' in degree. determines the polygons to be checked (shortcuts to include) :param exact_computation: when enabled the distance to every polygon edge is computed (=computationally more expensive!), instead of only evaluating the distances to all the vertices. NOTE: This only makes a real difference when polygons are very close. :param return_distances: when enabled the output looks like this: ``( 'tz_name_of_the_closest_polygon',[ distances to all polygons in km], [tz_names of all polygons])`` :param force_evaluation: whether all distances should be computed in any case :return: the timezone name of the closest found polygon, the list of distances or None """ def exact_routine(polygon_nr): coords = self.coords_of(polygon_nr) nr_points = len(coords[0]) empty_array = empty([2, nr_points], dtype=DTYPE_FORMAT_F_NUMPY) return distance_to_polygon_exact(lng, lat, nr_points, coords, empty_array) def normal_routine(polygon_nr): coords = self.coords_of(polygon_nr) nr_points = len(coords[0]) return distance_to_polygon(lng, lat, nr_points, coords) lng, lat = rectify_coordinates(lng, lat) # transform point X into cartesian coordinates current_closest_id = None central_x_shortcut, central_y_shortcut = coord2shortcut(lng, lat) lng = radians(lng) lat = radians(lat) possible_polygons = [] # there are 2 shortcuts per 1 degree lat, so to cover 1 degree two shortcuts (rows) have to be checked # the highest shortcut is 0 top = max(central_y_shortcut - NR_SHORTCUTS_PER_LAT * delta_degree, 0) # the lowest shortcut is 359 (= 2 shortcuts per 1 degree lat) bottom = min(central_y_shortcut + NR_SHORTCUTS_PER_LAT * delta_degree, 359) # the most left shortcut is 0 left = max(central_x_shortcut - NR_SHORTCUTS_PER_LNG * delta_degree, 0) # the most right shortcut is 359 (= 1 shortcuts per 1 degree lng) right = min(central_x_shortcut + NR_SHORTCUTS_PER_LAT * delta_degree, 359) # select all the polygons from the surrounding shortcuts for x in range(left, right + 1, 1): for y in range(top, bottom + 1, 1): for p in self.polygon_ids_of_shortcut(x, y): if p not in possible_polygons: possible_polygons.append(p) polygons_in_list = len(possible_polygons) if polygons_in_list == 0: return None # initialize the list of ids # this list is sorted (see documentation of compile_id_list() ) possible_polygons, ids, zones_are_equal = self.compile_id_list( possible_polygons, polygons_in_list) # if all the polygons in this shortcut belong to the same zone return it timezone_names = getattr(self, TIMEZONE_NAMES) if zones_are_equal: if not (return_distances or force_evaluation): return timezone_names[ids[0]] if exact_computation: routine = exact_routine else: routine = normal_routine min_distance = MAX_HAVERSINE_DISTANCE distances = empty(polygons_in_list, dtype=DTYPE_FORMAT_F_NUMPY) # [None for i in range(polygons_in_list)] if force_evaluation: for pointer, polygon_nr in enumerate(possible_polygons): distance = routine(polygon_nr) distances[pointer] = distance if distance < min_distance: min_distance = distance current_closest_id = ids[pointer] else: pointer = 0 # stores which polygons have been checked yet already_checked = [ False ] * polygons_in_list # initialize array with False while pointer < polygons_in_list: # only check a polygon when its id is not the closest a the moment and it has not been checked already! if already_checked[pointer] or ids[ pointer] == current_closest_id: # go to the next polygon pointer += 1 else: # this polygon has to be checked distance = routine(possible_polygons[pointer]) distances[pointer] = distance already_checked[pointer] = True if distance < min_distance: min_distance = distance current_closest_id = ids[pointer] # list of polygons has to be searched again, because closest zone has changed # set pointer to the beginning of the list # having a sorted list of polygon is beneficial here (less common zones come first) pointer = 1 if return_distances: return timezone_names[current_closest_id], distances, [ timezone_names[x] for x in ids ] return timezone_names[current_closest_id]
def timezone_at(self, *, lng: float, lat: float) -> str: """computes in which ocean OR land timezone a point is included in Especially for large polygons it is expensive to check if a point is really included. To speed things up there are "shortcuts" being used (stored in a binary file), which have been precomputed and store which timezone polygons have to be checked. In case there is only one possible zone this zone will instantly be returned without actually checking if the query point is included in this polygon -> speed up Since ocean timezones span the whole globe, some timezone will always be matched! :param lng: longitude of the point in degree (-180.0 to 180.0) :param lat: latitude in degree (90.0 to -90.0) :return: the timezone name of the matched timezone polygon. possibly "Etc/GMT+-XX" in case of an ocean timezone. """ lng, lat = rectify_coordinates(lng, lat) shortcut_id_x, shortcut_id_y = coord2shortcut(lng, lat) timezone = self._get_unique_zone(shortcut_id_x, shortcut_id_y) if timezone is not None: # found perfect and fast match return timezone # more thorough testing required: possible_polygons = self.polygon_ids_of_shortcut( shortcut_id_x, shortcut_id_y) nr_possible_polygons = len(possible_polygons) if nr_possible_polygons == 0: raise ValueError( "some timezone polygon should be present (ocean timezones exist everywhere)!" ) if nr_possible_polygons == 1: # there is only one polygon in that area. return its timezone name without further checks return getattr(self, TIMEZONE_NAMES)[self.id_of(possible_polygons[0])] # create a list of all the timezone ids of all possible polygons ids = self.id_list(possible_polygons, nr_possible_polygons) # x = longitude y = latitude both converted to 8byte int x = coord2int(lng) y = coord2int(lat) # check until the point is included in one of the possible polygons for i in range(nr_possible_polygons): # when including the current polygon only polygons from the same zone remain, same_element = all_the_same(pointer=i, length=nr_possible_polygons, id_list=ids) if same_element != -1: # return the name of that zone return getattr(self, TIMEZONE_NAMES)[same_element] polygon_nr = possible_polygons[i] # get the boundaries of the polygon = (lng_max, lng_min, lat_max, lat_min) getattr(self, POLY_MAX_VALUES).seek(4 * NR_BYTES_I * polygon_nr) boundaries = self._fromfile( getattr(self, POLY_MAX_VALUES), dtype=DTYPE_FORMAT_SIGNED_I_NUMPY, count=4, ) # only run the expensive algorithm if the point is withing the boundaries if not (x > boundaries[0] or x < boundaries[1] or y > boundaries[2] or y < boundaries[3]): outside_all_holes = True # when the point is within a hole of the polygon, this timezone must not be returned for hole_coordinates in self._holes_of_poly(polygon_nr): if inside_polygon(x, y, hole_coordinates): outside_all_holes = False break if outside_all_holes: if inside_polygon(x, y, self.coords_of(polygon_nr=polygon_nr)): # the point is included in this polygon. return its timezone name without further checks return getattr(self, TIMEZONE_NAMES)[ids[i]] # the timezone name of the last polygon should always be returned # if no other polygon has been matched beforehand. raise ValueError( "BUG: this statement should never be reached. Please open up an issue on Github!" )