Skip to content

Commit

Permalink
[SEDONA-426] Change raster cloning to be able to include metadata. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
iGN5117 authored Nov 17, 2023
1 parent 1ed0580 commit a649720
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,16 @@ public static GridCoverage2D mapAlgebra(GridCoverage2D gridCoverage2D, String pi
int rasterDataType = pixelType != null? RasterUtils.getDataTypeCode(pixelType) : renderedImage.getSampleModel().getDataType();
int width = renderedImage.getWidth();
int height = renderedImage.getHeight();
ColorModel originalColorModel = renderedImage.getColorModel();
// ImageUtils.createConstantImage is slow, manually constructing a buffered image proved to be faster.
// It also eliminates the data-copying overhead when converting raster data types after running jiffle script.
WritableRaster resultRaster = RasterFactory.createBandedRaster(DataBuffer.TYPE_DOUBLE, width, height, 1, null);
ColorModel cm = PlanarImage.createColorModel(resultRaster.getSampleModel());
ColorModel cm;
if (originalColorModel.isCompatibleRaster(resultRaster)) {
cm = originalColorModel;
}else {
cm = PlanarImage.createColorModel(resultRaster.getSampleModel());
}
WritableRenderedImage resultImage = new BufferedImage(cm, resultRaster, false, null);
try {
String prevScript = previousScript.get();
Expand Down Expand Up @@ -164,10 +170,10 @@ public static GridCoverage2D mapAlgebra(GridCoverage2D gridCoverage2D, String pi
WritableRaster convertedRaster = RasterFactory.createBandedRaster(rasterDataType, width, height, 1, null);
double[] samples = resultRaster.getSamples(0, 0, width, height, 0, (double[]) null);
convertedRaster.setSamples(0, 0, width, height, 0, samples);
return RasterUtils.create(convertedRaster, gridCoverage2D.getGridGeometry(), null, noDataValue);
return RasterUtils.clone(convertedRaster,null, gridCoverage2D, noDataValue, false);
} else {
// build a new GridCoverage2D from the resultImage
return RasterUtils.create(resultImage, gridCoverage2D.getGridGeometry(), null, noDataValue);
return RasterUtils.clone(resultImage, null, gridCoverage2D, noDataValue, false);
}
} catch (Exception e) {
throw new RuntimeException("Failed to run map algebra", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package org.apache.sedona.common.raster;

import org.apache.sedona.common.utils.RasterUtils;
import org.geotools.coverage.grid.GridCoordinates2D;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.geometry.DirectPosition2D;
import org.locationtech.jts.geom.Coordinate;
Expand Down Expand Up @@ -67,7 +66,7 @@ public static GridCoverage2D setValues(GridCoverage2D raster, int band, int colX
for (int j = rowY; j < rowY + height; j++) {
for (int i = colX; i < colX + width; i++) {
double[] pixel = rasterCopied.getPixel(i, j, (double[]) null);
if (keepNoData && noDataValue == pixel[band - 1]) {
if (keepNoData && noDataValue != null && noDataValue == pixel[band - 1]) {
iterator++;
continue;
} else {
Expand All @@ -77,7 +76,7 @@ public static GridCoverage2D setValues(GridCoverage2D raster, int band, int colX
iterator++;
}
}
return RasterUtils.create(rasterCopied, raster.getGridGeometry(), raster.getSampleDimensions());
return RasterUtils.clone(rasterCopied, raster.getSampleDimensions(), raster, null, true); //Keep metadata since this is essentially the same raster
}

/**
Expand Down Expand Up @@ -160,7 +159,7 @@ public static GridCoverage2D setValues(GridCoverage2D raster, int band, Geometry
double[] pixel = rasterCopied.getPixel(i, j, (double[]) null);
// [0] as only one band in the rasterized Geometry
double pixelNew = rasterizedGeomData.getPixel(k, l, (double[]) null)[0];
if (pixelNew == 0 || keepNoData && noDataValue == pixel[band - 1]) {
if (keepNoData && noDataValue != null && noDataValue == pixel[band - 1]) {
continue;
} else {
pixel[band - 1] = pixelNew;
Expand All @@ -169,8 +168,7 @@ public static GridCoverage2D setValues(GridCoverage2D raster, int band, Geometry
}
}
}

return RasterUtils.create(rasterCopied, raster.getGridGeometry(), raster.getSampleDimensions());
return RasterUtils.clone(rasterCopied, raster.getSampleDimensions(), raster, null, true); // keep metadata since this is essentially the same raster
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import org.apache.sedona.common.utils.RasterUtils;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoordinates2D;
import org.geotools.coverage.grid.GridCoverage2D;
import org.opengis.referencing.FactoryException;

Expand Down Expand Up @@ -184,8 +183,7 @@ public static GridCoverage2D getBand(GridCoverage2D rasterGeom, int[] bandIndexe
sampleDimensionsResult[i] = RasterUtils.createSampleDimensionWithNoDataValue(sampleDimension, noDataValue);
}
}

return RasterUtils.create(wr, resultRaster.getGridGeometry(), sampleDimensionsResult);
return RasterUtils.clone(wr, sampleDimensionsResult, rasterGeom, null, false); //do not keep meta-data since this will most probably be a new raster
}

public static String getBandType(GridCoverage2D raster, int band) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static GridCoverage2D setBandNoDataValue(GridCoverage2D raster, int bandI
}
GridSampleDimension[] sampleDimensions = raster.getSampleDimensions();
sampleDimensions [bandIndex - 1] = RasterUtils.removeNoDataValue(sampleDimensions[bandIndex - 1]);
return RasterUtils.create(raster.getRenderedImage(), raster.getGridGeometry(), sampleDimensions, null);
return RasterUtils.clone(raster.getRenderedImage(), null, sampleDimensions, raster, null, true);
}

if ( !(rasterNoData == null) && rasterNoData.equals(noDataValue)) {
Expand All @@ -74,8 +74,7 @@ public static GridCoverage2D setBandNoDataValue(GridCoverage2D raster, int bandI
PixelOrientation.UPPER_LEFT,
affine, raster.getCoordinateReferenceSystem2D(), null
);

return RasterUtils.create(raster.getRenderedImage(), gridGeometry2D, bands, null);
return RasterUtils.clone(raster.getRenderedImage(), null, bands, raster, null, true);
}

/**
Expand Down Expand Up @@ -247,10 +246,9 @@ public static GridCoverage2D clip(GridCoverage2D raster, int band, Geometry geom
}
}
}
newRaster = RasterUtils.create(resultRaster, raster.getGridGeometry(), newRaster.getSampleDimensions(), noDataValue);
newRaster = RasterUtils.clone(resultRaster, raster.getGridGeometry(), newRaster.getSampleDimensions(), newRaster, noDataValue, true);
} else {
// to add no-data value
newRaster = RasterUtils.create(newRaster.getRenderedImage(), newRaster.getGridGeometry(), newRaster.getSampleDimensions(), noDataValue);
newRaster = RasterUtils.clone(newRaster.getRenderedImage(), newRaster.getGridGeometry(), newRaster.getSampleDimensions(), newRaster, noDataValue, true);
}

return newRaster;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import javax.media.jai.RasterFactory;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.Arrays;

public class RasterConstructors
{
Expand Down Expand Up @@ -119,7 +118,7 @@ public static GridCoverage2D asRaster(Geometry geom, GridCoverage2D raster, Stri
double [] samples = RasterUtils.getRaster(rasterized.getRenderedImage()).getSamples(0, 0, width, height, 0, (double[]) null);
writableRaster.setSamples(0, 0, width, height, 0, samples);

return RasterUtils.create(writableRaster, rasterized.getGridGeometry(), rasterized.getSampleDimensions(), noDataValue);
return RasterUtils.clone(writableRaster, rasterized.getSampleDimensions(), rasterized, noDataValue, false); //no need to original raster metadata since this is a new raster.
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,7 @@ public static GridCoverage2D setGeoReference(GridCoverage2D raster, String geoRe
PixelOrientation.UPPER_LEFT,
affine, raster.getCoordinateReferenceSystem(), null
);

return RasterUtils.create(raster.getRenderedImage(), gridGeometry2D, raster.getSampleDimensions(),
null);
return RasterUtils.clone(raster.getRenderedImage(), gridGeometry2D, raster.getSampleDimensions(), raster, null, true);
}

public static GridCoverage2D setGeoReference(GridCoverage2D raster, String geoRefCoords) {
Expand Down
114 changes: 109 additions & 5 deletions common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;

/**
* Utility functions for working with GridCoverage2D objects.
Expand All @@ -80,11 +78,117 @@ public static GridCoverage2D create(WritableRaster raster, GridGeometry2D gridGe
return create(raster, gridGeometry, bands, null);
}

/**
*
* @param raster WriteableRaster to be used while creating the new raster.
* @param gridGeometry2D gridGeometry2D to be used while cloning
* @param bands bands to be used while cloning
* @param referenceRaster referenceRaster to clone from
* @param noDataValue noDataValue (if any) to be applied to the bands. If provided null. bands are unchanged.
* @param keepMetadata if passed true, clone all possible metadata from the referenceRaster.
keepMetaData controls the presence/absence of the following metadata of the referenceRasterObject:
raster name: Name of the raster (GridCoverage2D level)
raster properties: A Map of raster and image properties combined.
raster sources
* @return A cloned raster
*/
public static GridCoverage2D clone(WritableRaster raster, GridGeometry2D gridGeometry2D, GridSampleDimension[] bands, GridCoverage2D referenceRaster, Double noDataValue, boolean keepMetadata) {
Map propertyMap = null;
if (keepMetadata) {
propertyMap = referenceRaster.getProperties();
}
ColorModel originalColorModel = referenceRaster.getRenderedImage().getColorModel();
if (Objects.isNull(gridGeometry2D)) {
gridGeometry2D = referenceRaster.getGridGeometry();
}
int numBand = raster.getNumBands();
int rasterDataType = raster.getDataBuffer().getDataType();
ColorModel colorModel;
if (originalColorModel.isCompatibleRaster(raster)) {
colorModel = originalColorModel;
}else {
final ColorSpace cs = new BogusColorSpace(numBand);
final int[] nBits = new int[numBand];
Arrays.fill(nBits, DataBuffer.getDataTypeSize(rasterDataType));
colorModel = new ComponentColorModel(cs, nBits, false, true, Transparency.OPAQUE, rasterDataType);
}
if (noDataValue != null) {
GridSampleDimension[] newBands = new GridSampleDimension[numBand];
for (int k = 0; k < numBand; k++) {
if (bands != null) {
newBands[k] = createSampleDimensionWithNoDataValue(bands[k], noDataValue);
} else {
newBands[k] = createSampleDimensionWithNoDataValue("band_" + k, noDataValue);
}
}
bands = newBands;
}

GridCoverage2D[] referenceRasterSources = keepMetadata ? referenceRaster.getSources().toArray(new GridCoverage2D[0]) : null;
CharSequence rasterName = keepMetadata ? referenceRaster.getName() : "genericCoverage";

final RenderedImage image = new BufferedImage(colorModel, raster, false, null);
return gridCoverageFactory.create(rasterName, image, gridGeometry2D, bands, referenceRasterSources, propertyMap);

}

public static GridCoverage2D clone(WritableRaster raster, GridSampleDimension[] bands, GridCoverage2D referenceRaster, Double noDataValue, boolean keepMetadata) {
return RasterUtils.clone(raster, null, bands, referenceRaster, noDataValue, keepMetadata);
}

public static GridCoverage2D clone(RenderedImage image, GridSampleDimension[] bands, GridCoverage2D referenceRaster, Double noDataValue, boolean keepMetadata) {
return RasterUtils.clone(image, null, bands, referenceRaster, noDataValue, keepMetadata);
}

/**
*
* @param image Rendered image to create the raster from
* @param gridGeometry2D gridGeometry2D to be used while cloning
* @param bands bands to be used while cloning
* @param referenceRaster referenceRaster to clone from
* @param noDataValue noDataValue (if any) to be applied to the bands. If provided null. bands are unchanged.
* @param keepMetadata if passed true, clone all possible metadata from the referenceRaster.
keepMetaData controls the presence/absence of the following metadata of the referenceRasterObject:
raster name: Name of the raster (GridCoverage2D level)
raster properties: A Map of raster and image properties combined.
raster sources
* @return A cloned raster
*/
public static GridCoverage2D clone(RenderedImage image, GridGeometry2D gridGeometry2D, GridSampleDimension[] bands, GridCoverage2D referenceRaster, Double noDataValue, boolean keepMetadata) {
int numBand = image.getSampleModel().getNumBands();
if (Objects.isNull(gridGeometry2D)) {
gridGeometry2D = referenceRaster.getGridGeometry();
}
if (noDataValue != null) {
GridSampleDimension[] newBands = new GridSampleDimension[numBand];
for (int k = 0; k < numBand; k++) {
if (bands != null) {
newBands[k] = createSampleDimensionWithNoDataValue(bands[k], noDataValue);
} else {
newBands[k] = createSampleDimensionWithNoDataValue("band_" + k, noDataValue);
}
}
bands = newBands;
}
GridCoverage2D[] referenceRasterSources = keepMetadata ? referenceRaster.getSources().toArray(new GridCoverage2D[0]) : null;
Map propertyMap = null;
if (keepMetadata) {
propertyMap = referenceRaster.getProperties();
}
CharSequence rasterName = keepMetadata ? referenceRaster.getName() : "genericCoverage";
return gridCoverageFactory.create(rasterName, image, gridGeometry2D, bands, referenceRasterSources, propertyMap);
}

/**
* Create a new empty raster from the given WritableRaster object.
* @param raster The raster object to be wrapped as an image.
* @param gridGeometry The grid geometry of the raster.
* @param bands The bands of the raster.
* @param noDataValue the noDataValue (if any) to be applied to all bands. If provided null, bands are unchanged.
* keepMetaData controls the presence/absence of the following metadata of the referenceRasterObject:
* raster name: Name of the raster (GridCoverage2D level)
* raster properties: A Map of raster and image properties combined.
* raster sources
* @return A new GridCoverage2D object.
*/
public static GridCoverage2D create(WritableRaster raster, GridGeometry2D gridGeometry, GridSampleDimension[] bands, Double noDataValue) {
Expand Down Expand Up @@ -421,7 +525,7 @@ public static GridCoverage2D copyRasterAndAppendBand(GridCoverage2D gridCoverage
sampleDimensions[numBand - 1] = new GridSampleDimension("band" + numBand);
}
// Construct a GridCoverage2D with the copied image.
return create(wr, gridCoverage2D.getGridGeometry(), sampleDimensions);
return clone(wr, gridCoverage2D.getGridGeometry(), sampleDimensions, gridCoverage2D, null, true);
}

public static GridCoverage2D copyRasterAndAppendBand(GridCoverage2D gridCoverage2D, Number[] bandValues) {
Expand Down Expand Up @@ -456,7 +560,7 @@ public static GridCoverage2D copyRasterAndReplaceBand(GridCoverage2D gridCoverag
} else if (noDataValue != null) {
sampleDimensions[bandIndex - 1] = createSampleDimensionWithNoDataValue(sampleDimension, noDataValue);
}
return create(wr, gridCoverage2D.getGridGeometry(), sampleDimensions);
return clone(wr, gridCoverage2D.getGridGeometry(), sampleDimensions, gridCoverage2D, null, true);
}

public static GridCoverage2D copyRasterAndReplaceBand(GridCoverage2D gridCoverage2D, int bandIndex, Number[] bandValues) {
Expand Down

0 comments on commit a649720

Please sign in to comment.