Exemplo n.º 1
0
def stack(_input,
          output,
          tile_x_size,
          tile_y_size,
          config=None,
          attrs=None,
          bbox=None):
    with rasterio.open(_input) as src:
        profile = src.profile
        trans = Affine.to_gdal(src.transform)
        dt = np.dtype(src.dtypes[0])  # read first band data type

    # read initial image metadata
    profile['driver'] = 'TileDB'
    profile['blockxsize'] = tile_x_size
    profile['blockysize'] = tile_y_size
    if 'tiled' in profile:
        del profile['tiled']

    arr = xr.open_rasterio(_input, chunks={'x': tile_x_size, 'y': tile_y_size})

    if bbox is None:
        w = profile['width']
        h = profile['height']
        bbox = (0, 0, w, h)
    else:
        w = bbox[2] - bbox[0]
        h = bbox[3] - bbox[1]

    nBlocksX = math.ceil(w / (tile_x_size * 1.0))
    nBlocksY = math.ceil(h / (tile_y_size * 1.0))

    # GDAL TileDB driver writes/reads blocks so bypass rasterio
    dom = tiledb.Domain(
        tiledb.Dim(name='BANDS', domain=(0, profile['count'] - 1), tile=1),
        tiledb.Dim(name='Y',
                   domain=(0, (nBlocksY * tile_y_size) - 1),
                   tile=tile_y_size,
                   dtype=np.uint64),
        tiledb.Dim(name='X',
                   domain=(0, (nBlocksX * tile_x_size) - 1),
                   tile=tile_x_size,
                   dtype=np.uint64))

    cfg = tiledb.Config(config)
    ctx = tiledb.Ctx(config=cfg)
    schema = tiledb.ArraySchema(
        domain=dom,
        sparse=False,
        attrs=[tiledb.Attr(name="TDB_VALUES", dtype=dt)],
        ctx=ctx)

    tiledb.DenseArray.create(output, schema)
    with tiledb.DenseArray(output, 'w', ctx=ctx) as arr_output:
        arr[:, bbox[0]:bbox[2],
            bbox[1]:bbox[3]].data.to_tiledb(arr_output, storage_options=config)

    # write the GDAL metadata file from the source profile
    vfs = tiledb.VFS()
    meta = f"{output}/{os.path.basename(output)}.tdb.aux.xml"
    try:
        f = vfs.open(meta, "w")
        root = ET.Element('PAMDataset')
        geo = ET.SubElement(root, 'GeoTransform')
        geo.text = ', '.join(map(str, trans))
        meta = ET.SubElement(root, 'Metadata')
        meta.set('domain', 'IMAGE_STRUCTURE')
        t = ET.SubElement(meta, 'MDI')
        t.set('key', 'DATA_TYPE')
        t.text = _gdal_typename(np.complex128)
        nbits = ET.SubElement(meta, 'MDI')
        nbits.set('key', 'NBITS')
        nbits.text = str(dt.itemsize * 8)
        xsize = ET.SubElement(meta, 'MDI')
        xsize.set('key', 'X_SIZE')
        xsize.text = str(w)
        ysize = ET.SubElement(meta, 'MDI')
        ysize.set('key', 'Y_SIZE')
        ysize.text = str(h)
        vfs.write(f, ET.tostring(root))
    finally:
        vfs.close(f)
Exemplo n.º 2
0
    def pixel_to_world_coordinates(self,
                                   raster_data=None,
                                   filter_no_data_value=True,
                                   no_data_value=0,
                                   band_number=1):
        """
        Map the pixel coordinates to world coordinates. The affine transformation matrix is used for this purpose.
        The convention is to reference the pixel corner. To reference the pixel center instead, we translate each pixel by 50%.
        The "no value" pixels (cells) can be filtered out.

        A dataset's pixel coordinate system has its origin at the "upper left" (imagine it displayed on your screen).
        Column index increases to the right, and row index increases downward. The mapping of these coordinates to
        "world" coordinates in the dataset's reference system is done with an affine transformation matrix.

        param string raster_data: the raster data (2-dimensional array) to translate to world coordinates. If not provided, \
        it tries to load existing rasterized data about the RasterEnvironmentalLayer.

        :param int no_data_value: The pixel values depicting non-burned cells. Default is 0.

        :param bool filter_no_data_value: Whether to filter-out the no-data pixel values. Default is true. If set to \
        false, all pixels in a 2-dimensional array will be converted to world coordinates. Typically this option is used \
        to get a "base" map of the coordinates of all pixels in an image (map).

        :returns: A tuple of numpy ndarrays. The first array contains the latitude values for each \
        non-zero cell, the second array contains the longitude values for each non-zero cell.

        TODO: document raster-affine

        :rtype: tuple(np.ndarray, np.ndarray)

        """
        if raster_data is None:
            logger.info("No raster data provided, attempting to load default...")
            try:
                raster_data = self.load_data(self.file_path).read(band_number)  # we work on one layer, the first
                logger.info("Succesfully loaded existing raster data from %s." % self.file_path)
            except AttributeError as e:
                logger.error("Could not open raster file. %s " % str(e))
                raise AttributeError(e)

        logger.info("Transforming to world coordinates...")
        # first get the original Affine transformation matrix
        if hasattr(self, "raster_affine"):
            T0 = self.raster_affine
        else:   # default from arguments
            # try to deduce it
            logger.info("No Affine translation defined for this layer, trying to deduce it.")
            x_min, y_min, x_max, y_max = -180, -90, 180, 90
            pixel_size = (x_max - x_min) / raster_data.shape[1]
            if pixel_size != (y_max - y_min) / raster_data.shape[0]:
                logger.error("Could not deduce Affine transformation...possibly the pixel is not a square.")
                return
            T0 = Affine(pixel_size, 0.0, x_min, 0.0, -pixel_size, y_max)
        # convert it to gdal format (it is otherwise flipped)
        T0 = Affine(*reversed(T0.to_gdal()))
        logger.debug("Affine transformation T0:\n %s " % (T0,))
        # shift by 50% to get the pixel center
        T1 = T0 * Affine.translation(0.5, 0.5)
        # apply the shift, filtering out no_data_value values
        logger.debug("Raster data shape: %s " % (raster_data.shape,))
        logger.debug("Affine transformation T1:\n %s " % (T1,))
        if filter_no_data_value:
            logger.info("Filtering out no_data pixels.")
            raster_data = np.where(raster_data != no_data_value, raster_data, np.nan)
            coordinates = (T1 * np.where(~np.isnan(raster_data)))
        else:
            coordinates = (T1 * np.where(raster_data))
        logger.info("Transformation to world coordinates completed.")
        return coordinates