def get_crs_coordinates(dataset: gdal.Dataset, x_idx: float, y_idx: float) -> Coordinate2D: geotransform = dataset.GetGeoTransform() origin_x = geotransform[0] # origin_x/y is the CRS coordinate at the top left corner of the (0,0) pixel origin_y = geotransform[3] dx = geotransform[1] dy = geotransform[5] x = x_idx * dx + origin_x y = y_idx * dy + origin_y return Coordinate2D(x=x, y=y)
def create_index_dict(dataset: gdal.Dataset, tilesize_x: int, tilesize_y: int, ysize_pad: int, tile_bdr: int, filename_digits: int, is_categorical: bool, units: Optional[str]=None, description: Optional[str]=None, strict_datum: bool=True) -> Tuple[Dict[str, Any], DatumMismatch, Optional[float], int, Optional[float]]: ''' Returns a dictionary that can be used for writing a WPS Binary format index file. If the given dataset has a CRS or data type unsupported by WRF then an error is raised. See also :func:`write_index_file`. ''' band = dataset.GetRasterBand(1) # type: gdal.Band dtype = band.DataType if dtype in DTYPE_INT: no_data_value = band.GetNoDataValue() # type: Optional[float] scale_factor = band.GetScale() inv_scale_factor = None if band.GetOffset() != 0: raise UnsupportedError('Integer data with offset not supported') elif dtype in DTYPE_FLOAT: if is_categorical: raise UserError('Categorical data must have integer-type data but is float') assert band.GetOffset() == 0 assert band.GetScale() == 1 # WPS binary doesn't support floating point data. # Floating point data must be converted to integers by scaling and rounding. inv_scale_factor, min_max = compute_inv_scale_factor(read_blocks(band)) scale_factor = 1/inv_scale_factor min_, max_ = min_max min_scaled = round(min_ * inv_scale_factor) max_scaled = round(max_ * inv_scale_factor) dtype = get_optimal_dtype(min_scaled, max_scaled) if band.GetNoDataValue() is None: no_data_value = None else: # TODO may fail if value range equals dtype range # adjusting the scaling factor slightly to make room for a no-data value may help no_data_value = get_no_data_value(dtype, min_scaled, max_scaled) #print('Scale factor: {}'.format(scale_factor)) #print('Min/max: {}'.format(min_max)) #print('Min/max scaled: {}'.format((min_scaled, max_scaled))) #print('No data: {}'.format(no_data_value)) else: assert False, "Unsupported data type: {}".format(gdal.GetDataTypeName(dtype)) signed = gdal_dtype_is_signed(dtype) wordsize = gdal.GetDataTypeSize(dtype) // 8 wkt = dataset.GetProjection() srs = osr.SpatialReference(wkt) truelat1 = truelat2 = stand_lon = None geotransform = dataset.GetGeoTransform() dx = geotransform[1] dy = geotransform[5] assert dx > 0 # dy can be negative, see below projection = None datum_mismatch = None if srs.IsGeographic(): if srs.EPSGTreatsAsLatLong(): raise UnsupportedError("Unsupported axis order: Lat/Lon, must be Lon/Lat") if not CRS.is_wrf_sphere_datum(srs): datum_mismatch = DatumMismatch( expected='WRF Sphere (6370km)', actual='a={}m b={}m'.format(srs.GetSemiMajor(), srs.GetSemiMinor())) if datum_mismatch and strict_datum: raise UnsupportedError("Unsupported datum, must be based on a sphere with " + "radius {}m, but is an ellipsoid with a={}m b={}m".format( WRF_EARTH_RADIUS, srs.GetSemiMajor(), srs.GetSemiMinor())) projection = 'regular_ll' elif srs.IsProjected(): proj = srs.GetAttrValue('projection') datum = srs.GetAttrValue('datum') if proj in ['Albers_Conic_Equal_Area', 'Lambert_Conformal_Conic_2SP', 'Mercator_2SP']: truelat1 = srs.GetNormProjParm('standard_parallel_1') if proj == 'Polar_Stereographic': truelat1 = srs.GetNormProjParm('latitude_of_origin') if proj in ['Albers_Conic_Equal_Area', 'Lambert_Conformal_Conic_2SP']: truelat2 = srs.GetNormProjParm('standard_parallel_2') if proj == 'Albers_Conic_Equal_Area': stand_lon = srs.GetNormProjParm('longitude_of_center') if proj in ['Lambert_Conformal_Conic_2SP', 'Mercator_2SP', 'Polar_Stereographic']: stand_lon = srs.GetNormProjParm('central_meridian') if proj == "Albers_Conic_Equal_Area": if datum != "North_American_Datum_1983": datum_mismatch = DatumMismatch(expected='NAD83', actual=datum) projection = 'albers_nad83' elif proj == "Lambert_Conformal_Conic_2SP": if not CRS.is_wrf_sphere_datum(srs): datum_mismatch = DatumMismatch(expected='WRF Sphere (6370km)', actual=datum) projection = 'lambert' elif proj == "Mercator_2SP": if not CRS.is_wrf_sphere_datum(srs): datum_mismatch = DatumMismatch(expected='WRF Sphere (6370km)', actual=datum) projection = 'mercator' # For polar stereographic we don't allow datum mismatch in non-strict mode # as it would be ambiguous which WPS projection ID to choose. elif proj == "Polar_Stereographic" and datum == 'WGS_1984': projection = 'polar_wgs84' elif proj == "Polar_Stereographic" and CRS.is_wrf_sphere_datum(srs): projection = 'polar' if projection is None or (datum_mismatch and strict_datum): raise UnsupportedError("Unsupported projection/datum: {}; {}".format(proj, datum)) else: raise UnsupportedError("Unsupported SRS type, must be geographic or projected") if units is None and is_categorical: units = 'category' # gdal always uses system byte order when creating ENVI files is_little_endian = sys.byteorder == 'little' # WPS does not support the concept of negative dy and requires that # the highest y coordinate corresponds to the highest y index. # If row_order=top_bottom (which we use), then the highest y index corresponds to # the row that is stored first in the file. # If row_order=bottom_top, then the highest y index corresponds to # the row that is stored last in the file. # Index coordinates in WPS do not start from 0 but from 1 where (1,1) # corresponds to the center of the cell. GDAL (0,0) corresponds to the corner of the cell. # See also http://www2.mmm.ucar.edu/wrf/users/FAQ_files/FAQ_wps_intermediate_format.html. half_cell = 0.5 # WPS index coordinates known_x = known_y = 1.0 # GDAL index coordinates x_idx = known_x - half_cell if dy < 0: y_idx = ysize_pad - known_y + half_cell else: y_idx = known_y - half_cell known_lonlat = CRS(srs=srs).to_lonlat(get_crs_coordinates(dataset, x_idx, y_idx)) metadata = { 'type': 'categorical' if is_categorical else 'continuous', 'endian': 'little' if is_little_endian else 'big', 'signed': 'yes' if signed else 'no', 'wordsize': wordsize, 'row_order': 'top_bottom', 'projection': projection, 'dx': dx, 'dy': abs(dy), 'known_x': known_x, 'known_y': known_y, 'known_lat': known_lonlat.lat, 'known_lon': known_lonlat.lon, 'tile_x': tilesize_x, 'tile_y': tilesize_y, 'tile_z': 1, 'tile_bdr': tile_bdr } if filename_digits > 5: metadata['filename_digits'] = filename_digits if scale_factor != 1: metadata['scale_factor'] = scale_factor if no_data_value is not None: metadata['missing_value'] = float(no_data_value) if is_categorical: # Note that ComputeRasterMinMax ignores pixels with no-data value. band_min, band_max = band.ComputeRasterMinMax() assert band_min == int(band_min) assert band_max == int(band_max) metadata['category_min'] = int(band_min) metadata['category_max'] = int(band_max) if truelat1 is not None: metadata['truelat1'] = truelat1 if truelat2 is not None: metadata['truelat2'] = truelat2 if stand_lon is not None: metadata['stdlon'] = stand_lon if units is not None: metadata['units'] = units if description is not None: metadata['description'] = description return metadata, datum_mismatch, inv_scale_factor, dtype, no_data_value