Esempio n. 1
0
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)
Esempio n. 2
0
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