def test_for_errors( name: str, lat_lon_to_x_z: Transformer, x_z_to_lat_lon: Transformer, coords: Coordinates, ) -> bool: errors = False x, z = lat_lon_to_x_z.transform(coords.latitude, coords.longitude) if not math.isclose(x, coords.x) or not math.isclose(z, coords.z): error_x = x - coords.x error_z = z - coords.z error_pct_x = error_x / coords.x * 100 error_pct_z = error_z / coords.z * 100 print(f"{name} has error of {error_pct_x}% {error_pct_z}%") errors = True lat, lon = x_z_to_lat_lon.transform(coords.x, coords.z) if not math.isclose(lat, coords.latitude) or not math.isclose( lon, coords.longitude): error_lat = lat - coords.latitude error_lon = lon - coords.longitude error_pct_lon = error_lat / coords.latitude * 100 error_pct_lat = error_lon / coords.longitude * 100 print(f"{name} has error of {error_pct_lat}% {error_pct_lon}%") errors = True return errors
def _split_points_along_anti_meridian( wgs84_to_proj: Transformer, points: List[Tuple[float, float]], threshold=0.01) -> List[List[Tuple[float, float]]]: total_points = len(points) groups: List = [] group: List[Tuple[float, float]] = [] for i in range(len(points)): current_point = points[i] next_point = points[(i + 1) % total_points] group.append(current_point) if abs(current_point[1] - next_point[1]) > 90: longitude = _sign(points[i][1]) * 180 anti_meridian_at_current_lat = wgs84_to_proj.transform( current_point[0], 180) anti_meridian_at_next_lat = wgs84_to_proj.transform( next_point[0], 180) if anti_meridian_at_current_lat[0] == float('inf') and \ anti_meridian_at_next_lat[0] == float('inf'): error = ( 'The anti-meridian at between the latitudes ' + "%f and %f " % (current_point[0], next_point[0]) + 'could not be transformed with the projection. This is ' + 'unexpected. Please fix the algorithm!') raise RuntimeError(error) if anti_meridian_at_current_lat[0] == float('inf'): latitude = _binary_search_edge_wgs84( wgs84_to_proj, next_point[0], # Valid latitude current_point[0], # Invalid latitude threshold) elif anti_meridian_at_next_lat[0] == float('inf'): latitude = _binary_search_edge_wgs84( wgs84_to_proj, current_point[0], # Valid latitude next_point[0], # Invalid latitude threshold) else: latitude = next_point[0] if not math.isclose(current_point[1], longitude, abs_tol=0.0001): group.append((latitude, longitude)) groups.append(group) group = [] if not math.isclose(next_point[1], -longitude, abs_tol=0.0001): group.append((latitude, -longitude)) if len(groups) != 0 and groups[0][0] == next_point: group.extend(groups[0]) groups[0] = group elif i == total_points - 1: groups.append(group) return groups
def _covers_hemisphere(wgs84_to_proj: Transformer) -> bool: covers_northern = wgs84_to_proj.transform(90, 0)[0] != float('inf') covers_southern = wgs84_to_proj.transform(-90, 0)[0] != float('inf') # We don't really support projections like this, where the north and south # poles are visible at the same, but it is possible in azimuthal # projections where the lat_0 is 0. For our purposes, we don't consider # this projection as covering both hemisphere; it merely touches it. if covers_northern and covers_southern: return False return covers_northern or covers_southern
def calculate_raster_coordinate( longitude: float, latitude: float, padf_transform: List[float], transformer: Transformer): """ From a given longitude and latitude, calculate the raster coordinate corresponding to the top left point of the grid surrounding the given geographic coordinate. """ # Because not all model types use EPSG:4269 projection, we first convert longitude and latitude # to whichever projection and coordinate system the grib file is using raster_long, raster_lat = transformer.transform(longitude, latitude) # Calculate the j index for point i,j in the grib file x_numerator = (raster_long - padf_transform[0] - raster_lat/padf_transform[5] * padf_transform[2] + padf_transform[3] / padf_transform[5] * padf_transform[2]) / padf_transform[1] y_numerator = (raster_lat - padf_transform[3] - raster_long/padf_transform[1] * padf_transform[4] + padf_transform[0] / padf_transform[1] * padf_transform[4]) / padf_transform[5] denominator = 1 - \ padf_transform[4]/padf_transform[5]*padf_transform[2]/padf_transform[1] i_index = math.floor(x_numerator/denominator) j_index = math.floor(y_numerator/denominator) return (i_index, j_index)
def calculate_geographic_coordinate( point: Tuple[int], padf_transform: List[float], transformer: Transformer): """ Calculate the geographic coordinates for a given points """ x_coordinate = padf_transform[0] + point[0] * \ padf_transform[1] + point[1]*padf_transform[2] y_coordinate = padf_transform[3] + point[0] * \ padf_transform[4] + point[1]*padf_transform[5] lon, lat = transformer.transform(x_coordinate, y_coordinate) return (lon, lat)
def _binary_search_edge_crs(transformer: Transformer, angle: float, threshold=1) -> float: min_r = 0 crs = transformer.source_crs if crs.coordinate_operation.method_name == 'Orthographic': # Speeds up binary search a bit min_r = min(crs.ellipsoid.semi_minor_metre, crs.ellipsoid.semi_major_metre) * 0.9 max_r = max(crs.ellipsoid.semi_minor_metre, crs.ellipsoid.semi_major_metre) while max_r - min_r > threshold: pivot_r = (min_r + max_r) / 2 x = math.cos(angle) * pivot_r y = math.sin(angle) * pivot_r if (transformer.transform(x, y))[0] == float('inf'): max_r = pivot_r else: min_r = pivot_r return min_r
def _binary_search_edge_wgs84( wgs84_to_proj: Transformer, valid_lat: float, invalid_lat: float, threshold=0.01, ) -> float: """ :param wgs84_to_proj: :param valid_lat: :param invalid_lat: :param threshold: Defaults to 0.01, since this level of precision usually is the size of a neighbourhood. See https://xkcd.com/2170/ :return: """ while abs(valid_lat - invalid_lat) > threshold: pivot_lat = (invalid_lat + valid_lat) / 2 if (wgs84_to_proj.transform(pivot_lat, 180))[0] == float('inf'): invalid_lat = pivot_lat else: valid_lat = pivot_lat return valid_lat
def transform(tf: Transformer, cell): return tf.transform(cell[0], cell[1])