You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@sedona.apache.org by "furqaankhan (via GitHub)" <gi...@apache.org> on 2023/11/17 18:11:37 UTC
[PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
furqaankhan opened a new pull request, #1124:
URL: https://github.com/apache/sedona/pull/1124
## Did you read the Contributor Guide?
- Yes, I have read [Contributor Rules](https://sedona.apache.org/latest-snapshot/community/rule/) and [Contributor Development Guide](https://sedona.apache.org/latest-snapshot/community/develop/)
## Is this PR related to a JIRA ticket?
- Yes, the URL of the associated JIRA ticket is https://issues.apache.org/jira/browse/SEDONA-428. The PR name follows the format `[SEDONA-XXX] my subject`.
## What changes were proposed in this PR?
- Add RS_ZonalStats
- Add RS_ZonalStatsAll
- Refactor asRaster implementation to have the ability to keep the reference raster's extent.
## How was this patch tested?
- Passed new and old tests
## Did this PR include necessary documentation updates?
- Yes, I have updated the documentation update.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
Posted by "jiayuasu (via GitHub)" <gi...@apache.org>.
jiayuasu commented on code in PR #1124:
URL: https://github.com/apache/sedona/pull/1124#discussion_r1401408332
##########
common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java:
##########
@@ -81,6 +88,190 @@ public static long getCount(GridCoverage2D raster, int band) {
// return getCount(raster, 1, excludeNoDataValue);
// }
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return An array with all the stats for the region
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ // order of stats
+ // count, sum, mean, median, mode, stddev, variance, min, max
+ double[] result = new double[9];
+ result[0] = stats.getN();
+ result[1] = stats.getSum();
+ result[2] = stats.getMean();
+ result[3] = stats.getPercentile(50);
+ result[4] = zonalMode(pixelData);
+ result[5] = stats.getStandardDeviation();
+ result[6] = stats.getVariance();
+ result[7] = stats.getMin();
+ result[8] = stats.getMax();
+
+ return result;
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @return An array with all the stats for the region, excludeNoData is set to true
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band) throws FactoryException {
+ return getZonalStatsAll(raster, roi, band, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @return An array with all the stats for the region, excludeNoData is set to true and band is set to 1
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi) throws FactoryException {
+ return getZonalStatsAll(raster, roi, 1, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType, boolean excludeNoData) throws FactoryException {
+
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ switch (statType.toLowerCase()) {
+ case "sum":
+ return stats.getSum();
+ case "average":
+ case "avg":
+ case "mean":
+ return stats.getMean();
+ case "count":
+ return stats.getN();
+ case "max":
+ return stats.getMax();
+ case "min":
+ return stats.getMin();
+ case "stddev":
+ case "sd":
+ return stats.getStandardDeviation();
+ case "median":
+ return stats.getPercentile(50);
+ case "mode":
+ return zonalMode(pixelData);
+ case "variance":
+ return stats.getVariance();
+ default:
+ throw new IllegalArgumentException("Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
+ }
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, band, statType, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true and band is set to 1.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, 1, statType, true);
+ }
+
+ /**
+ * @param pixelData An array of double with pixel values
+ * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest value will be returned.
+ */
+ private static double zonalMode(double[] pixelData) {
+ double[] modes = StatUtils.mode(pixelData);
+ return modes[modes.length - 1];
+ }
+
+ /**
+ * An intermediate function to compute zonal statistics
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return an object of DescriptiveStatistics and an array of double with pixel data.
+ * @throws FactoryException
+ */
+ private static List<Object> getStatObjects(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ RasterUtils.ensureBand(raster, band);
+
+ if(RasterAccessors.srid(raster) != roi.getSRID()) {
+ // implicitly converting roi geometry CRS to raster CRS
+ roi = RasterUtils.convertCRSIfNeeded(roi, raster.getCoordinateReferenceSystem());
+ // have to set the SRID as RasterUtils.convertCRSIfNeeded doesn't set it even though the geometry is in raster's CRS
+ roi = Functions.setSRID(roi, RasterAccessors.srid(raster));
+ }
+
+ // checking if the raster contains the geometry
+ if (!RasterPredicates.rsIntersects(raster, roi)) {
+ throw new IllegalArgumentException("The provided geometry is not intersecting the raster. Please provide a geometry that is in the raster's extent.");
+ }
+
+ Raster rasterData = RasterUtils.getRaster(raster.getRenderedImage());
+ String datatype = RasterBandAccessors.getBandType(raster, band);
+ Double noDataValue = RasterBandAccessors.getBandNoDataValue(raster, band);
+ // Adding an arbitrary value '150' for the pixels that are under the geometry.
+ GridCoverage2D rasterizedGeom = RasterConstructors.asRasterWithRasterExtent(roi, raster, datatype, 150, null);
Review Comment:
@furqaankhan Will it collide with existing values in a raster? If not, we are good.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
Posted by "jiayuasu (via GitHub)" <gi...@apache.org>.
jiayuasu commented on code in PR #1124:
URL: https://github.com/apache/sedona/pull/1124#discussion_r1400822083
##########
common/src/main/java/org/apache/sedona/common/raster/RasterConstructors.java:
##########
@@ -26,6 +26,7 @@
import org.geotools.gce.geotiff.GeoTiffReader;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.jts.JTS;
Review Comment:
Why does this PR involve changes related to AsRaster? Is this a API breaking change?
If this change is not related to zonal statistics. It must be placed in a separate PR.
##########
common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java:
##########
@@ -75,6 +81,113 @@ public void testBandNoDataValueIllegalBand() throws FactoryException, IOExceptio
assertEquals("Provided band index 2 is not present in the raster", exception.getMessage());
}
+ @Test
+ public void testZonalStats() throws FactoryException, ParseException, IOException {
+ GridCoverage2D raster = rasterFromGeoTiff(resourceFolder + "raster_geotiff_color/FAA_UTM18N_NAD83.tif");
+ String polygon = "POLYGON ((236722 4204770, 243900 4204770, 243900 4197590, 221170 4197590, 236722 4204770))";
+ Geometry geom = Constructors.geomFromWKT(polygon, RasterAccessors.srid(raster));
+
+ double actual = RasterBandAccessors.getZonalStats(raster, geom, 1, "sum", false);
+ double expected = 1.0690406E7;
+ assertEquals(expected, actual, 0d);
Review Comment:
Please set the allowed delta value as a parameter in the raster test base such as ` protected static final double FP_TOLERANCE = 1e--4`
`
##########
common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java:
##########
@@ -81,6 +88,190 @@ public static long getCount(GridCoverage2D raster, int band) {
// return getCount(raster, 1, excludeNoDataValue);
// }
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return An array with all the stats for the region
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ // order of stats
+ // count, sum, mean, median, mode, stddev, variance, min, max
+ double[] result = new double[9];
+ result[0] = stats.getN();
+ result[1] = stats.getSum();
+ result[2] = stats.getMean();
+ result[3] = stats.getPercentile(50);
+ result[4] = zonalMode(pixelData);
+ result[5] = stats.getStandardDeviation();
+ result[6] = stats.getVariance();
+ result[7] = stats.getMin();
+ result[8] = stats.getMax();
+
+ return result;
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @return An array with all the stats for the region, excludeNoData is set to true
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band) throws FactoryException {
+ return getZonalStatsAll(raster, roi, band, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @return An array with all the stats for the region, excludeNoData is set to true and band is set to 1
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi) throws FactoryException {
+ return getZonalStatsAll(raster, roi, 1, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType, boolean excludeNoData) throws FactoryException {
+
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ switch (statType.toLowerCase()) {
+ case "sum":
+ return stats.getSum();
+ case "average":
+ case "avg":
+ case "mean":
+ return stats.getMean();
+ case "count":
+ return stats.getN();
+ case "max":
+ return stats.getMax();
+ case "min":
+ return stats.getMin();
+ case "stddev":
+ case "sd":
+ return stats.getStandardDeviation();
+ case "median":
+ return stats.getPercentile(50);
+ case "mode":
+ return zonalMode(pixelData);
+ case "variance":
+ return stats.getVariance();
+ default:
+ throw new IllegalArgumentException("Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
+ }
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, band, statType, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true and band is set to 1.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, 1, statType, true);
+ }
+
+ /**
+ * @param pixelData An array of double with pixel values
+ * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest value will be returned.
+ */
+ private static double zonalMode(double[] pixelData) {
+ double[] modes = StatUtils.mode(pixelData);
+ return modes[modes.length - 1];
+ }
+
+ /**
+ * An intermediate function to compute zonal statistics
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return an object of DescriptiveStatistics and an array of double with pixel data.
+ * @throws FactoryException
+ */
+ private static List<Object> getStatObjects(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ RasterUtils.ensureBand(raster, band);
+
+ if(RasterAccessors.srid(raster) != roi.getSRID()) {
+ // implicitly converting roi geometry CRS to raster CRS
+ roi = RasterUtils.convertCRSIfNeeded(roi, raster.getCoordinateReferenceSystem());
+ // have to set the SRID as RasterUtils.convertCRSIfNeeded doesn't set it even though the geometry is in raster's CRS
+ roi = Functions.setSRID(roi, RasterAccessors.srid(raster));
+ }
+
+ // checking if the raster contains the geometry
+ if (!RasterPredicates.rsIntersects(raster, roi)) {
+ throw new IllegalArgumentException("The provided geometry is not intersecting the raster. Please provide a geometry that is in the raster's extent.");
+ }
+
+ Raster rasterData = RasterUtils.getRaster(raster.getRenderedImage());
+ String datatype = RasterBandAccessors.getBandType(raster, band);
+ Double noDataValue = RasterBandAccessors.getBandNoDataValue(raster, band);
+ // Adding an arbitrary value '150' for the pixels that are under the geometry.
+ GridCoverage2D rasterizedGeom = RasterConstructors.asRasterWithRasterExtent(roi, raster, datatype, 150, null);
Review Comment:
Why add `150` here?
##########
docs/api/sql/Raster-operators.md:
##########
@@ -937,6 +937,129 @@ Output:
14.0, 204.0, 14.571428571428571, 11.509091348732502, 1.0, 25.0
```
+### RS_ZonalStats
+
+Introduction: This returns a statistic value specified by `statType` over the region of interest defined by `zone`. It computes the statistic from the pixel values within the ROI geometry and returns the result. If the `excludeNoData` parameter is not specified, it will default to `true`. This excludes NoData values from the statistic calculation. Additionally, if the `band` parameter is not provided, band 1 will be used by default for the statistic computation. The valid options for `statType` are:
+
+- `count`: Number of pixels in the region.
+- `sum`: Sum of pixel values
+- `mean|average|avg`: Arithmetic mean.
+- `median`: Middle value in the region.
+- `mode`: Most occurring value, if there are multiple values with same occurrence then will return the largest number.
+- `stddev|sd`: Standard deviation.
+- `variance`: Variance.
+- `min`: Minimum value in the region.
+- `max`: Maximum value in the region.
+
+!!!note
+ The following conditions will throw an `IllegalArgumentException` if they are not met:
+
+ - The `raster` and `zone` geometry should be in the same CRS, refer to [RS_SRID](#rs_srid) for raster, and [ST_SRID](../Function/#st_srid) for geometry to get the spatial reference identifier.
+ - The provided `raster` and `zone` geometry should intersect.
+ - The option provided to `statType` should be valid.
+
+Format:
+
+```
+RS_ZonalStats(raster: Raster, zone: Geometry, band: Integer, statType: String, excludeNoData: Boolean)
+```
+
+```
+RS_ZonalStats(raster: Raster, zone: Geometry, band: Integer, statType: String)
+```
+
+```
+RS_ZonalStats(raster: Raster, zone: Geometry, statType: String)
+```
+
+Since: `v1.5.1`
+
+Spark SQL Example:
+
+```sql
+RS_ZonalStats(rast1, geom1, 1, 'sum', false)
+```
+
+Output:
+
+```
+10690406
+```
+
+Spark SQL Example:
+
+```sql
+RS_ZonalStats(rast2, geom2, 1, 'mean', true)
+```
+
+Output:
+
+```
+226.55992667794473
+```
+
+### RS_ZonalStatsAll
+
+Introduction: Returns an array of statistic values, where each statistic is computed over a region defined by the `zone` geometry. The array contains the following values:
+
+- 0: Count of the pixels.
+- 1: Sum of the pixel values.
+- 2: Arithmetic mean.
+- 3: Median.
+- 4: Mode.
+- 5: Standard deviation.
+- 6: Variance.
+- 7: Minimum value of the zone.
+- 8: Maximum value of the zone.
+
+!!!note
+ The following conditions will throw an `IllegalArgumentException` if they are not met:
+
+ - The `raster` and `zone` geometry should be in the same CRS, refer to [RS_SRID](#rs_srid) for raster, and [ST_SRID](../Function/#st_srid) for geometry to get the spatial reference identifier.
Review Comment:
Please explain what happens if either side does not have CRS or different CRS.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
Posted by "furqaankhan (via GitHub)" <gi...@apache.org>.
furqaankhan commented on code in PR #1124:
URL: https://github.com/apache/sedona/pull/1124#discussion_r1400854695
##########
common/src/main/java/org/apache/sedona/common/raster/RasterConstructors.java:
##########
@@ -26,6 +26,7 @@
import org.geotools.gce.geotiff.GeoTiffReader;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.jts.JTS;
Review Comment:
No, it doesn't affect the current `RS_AsRaster` function. This refactoring was done to remove the pixel coordinate translation and use the output from the `AsRasterWithRasterExtent` function instead. We talked about it in our tag-up last Thursday to improve performance.
It is related to Zonal statistics.
##########
common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java:
##########
@@ -81,6 +86,182 @@ public static long getCount(GridCoverage2D raster, int band) {
// return getCount(raster, 1, excludeNoDataValue);
// }
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return An array with all the stats for the region
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ // order of stats
+ // count, sum, mean, median, mode, stddev, variance, min, max
+ double[] result = new double[9];
+ result[0] = stats.getN();
+ result[1] = stats.getSum();
+ result[2] = stats.getMean();
+ result[3] = stats.getPercentile(50);
+ result[4] = zonalMode(pixelData);
+ result[5] = stats.getStandardDeviation();
+ result[6] = stats.getVariance();
+ result[7] = stats.getMin();
+ result[8] = stats.getMax();
+
+ return result;
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @return An array with all the stats for the region, excludeNoData is set to true
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band) throws FactoryException {
+ return getZonalStatsAll(raster, roi, band, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @return An array with all the stats for the region, excludeNoData is set to true and band is set to 1
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi) throws FactoryException {
+ return getZonalStatsAll(raster, roi, 1, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType, boolean excludeNoData) throws FactoryException {
+
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ switch (statType.toLowerCase()) {
+ case "sum":
+ return stats.getSum();
+ case "average":
+ case "avg":
+ case "mean":
+ return stats.getMean();
+ case "count":
+ return stats.getN();
+ case "max":
+ return stats.getMax();
+ case "min":
+ return stats.getMin();
+ case "stddev":
+ case "sd":
+ return stats.getStandardDeviation();
+ case "median":
+ return stats.getPercentile(50);
+ case "mode":
+ return zonalMode(pixelData);
+ case "variance":
+ return stats.getVariance();
+ default:
+ throw new IllegalArgumentException("Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
+ }
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, band, statType, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true and band is set to 1.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, 1, statType, true);
+ }
+
+ /**
+ * @param pixelData An array of double with pixel values
+ * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest value will be returned.
+ */
+ private static double zonalMode(double[] pixelData) {
+ double[] modes = StatUtils.mode(pixelData);
+ return modes[modes.length - 1];
+ }
+
+ /**
+ * An intermediate function to compute zonal statistics
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return an object of DescriptiveStatistics and an array of double with pixel data.
+ * @throws FactoryException
+ */
+ private static List<Object> getStatObjects(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ RasterUtils.ensureBand(raster, band);
+
+ if(RasterAccessors.srid(raster) != roi.getSRID()) {
+ throw new IllegalArgumentException("The raster and geometry don't have the same SRID. They should be in the same Coordinate Reference System.");
+ }
+
+ // checking if the raster contains the geometry
+ if (!RasterPredicates.rsIntersects(raster, roi)) {
+ throw new IllegalArgumentException("The provided geometry is not intersecting the raster. Please provide a geometry that is in the raster's extent.");
+ }
+
+ Raster rasterData = RasterUtils.getRaster(raster.getRenderedImage());
+ String datatype = RasterBandAccessors.getBandType(raster, band);
+ Double noDataValue = RasterBandAccessors.getBandNoDataValue(raster, band);
+ GridCoverage2D rasterizedGeom = RasterConstructors.asRasterWithRasterExtent(roi, raster, datatype, 150, null);
+ Raster rasterziedData = RasterUtils.getRaster(rasterizedGeom.getRenderedImage());
+ int width = RasterAccessors.getWidth(rasterizedGeom), height = RasterAccessors.getHeight(rasterizedGeom);
+ double[] rasterizedPixelData = rasterziedData.getSamples(0, 0, width, height, 0, (double[]) null);
+ double[] rasterPixelData = rasterData.getSamples(0, 0, width, height, band - 1, (double[]) null);
+
+ List<Double> pixelData = new ArrayList<>();
+
+ for (int k = 0; k < rasterPixelData.length; k++) {
+
+ if (rasterizedPixelData[k] == 0 || excludeNoData && noDataValue != null && rasterPixelData[k] == noDataValue) {
Review Comment:
https://github.com/apache/sedona/blob/b35886efccbcc70f4b36ef8ead0e7d236fa29ca3/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java#L237
If you look at that line. The output from asRaster is that it will set `150` to the pixels that are under the geometry and will set `0` to all the other pixels. So I am skipping the `0` is because those pixels are not under the geometry.
##########
common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java:
##########
@@ -81,6 +86,182 @@ public static long getCount(GridCoverage2D raster, int band) {
// return getCount(raster, 1, excludeNoDataValue);
// }
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return An array with all the stats for the region
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ // order of stats
+ // count, sum, mean, median, mode, stddev, variance, min, max
+ double[] result = new double[9];
+ result[0] = stats.getN();
+ result[1] = stats.getSum();
+ result[2] = stats.getMean();
+ result[3] = stats.getPercentile(50);
+ result[4] = zonalMode(pixelData);
+ result[5] = stats.getStandardDeviation();
+ result[6] = stats.getVariance();
+ result[7] = stats.getMin();
+ result[8] = stats.getMax();
+
+ return result;
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @return An array with all the stats for the region, excludeNoData is set to true
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band) throws FactoryException {
+ return getZonalStatsAll(raster, roi, band, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @return An array with all the stats for the region, excludeNoData is set to true and band is set to 1
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi) throws FactoryException {
+ return getZonalStatsAll(raster, roi, 1, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType, boolean excludeNoData) throws FactoryException {
+
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ switch (statType.toLowerCase()) {
+ case "sum":
+ return stats.getSum();
+ case "average":
+ case "avg":
+ case "mean":
+ return stats.getMean();
+ case "count":
+ return stats.getN();
+ case "max":
+ return stats.getMax();
+ case "min":
+ return stats.getMin();
+ case "stddev":
+ case "sd":
+ return stats.getStandardDeviation();
+ case "median":
+ return stats.getPercentile(50);
+ case "mode":
+ return zonalMode(pixelData);
+ case "variance":
+ return stats.getVariance();
+ default:
+ throw new IllegalArgumentException("Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
+ }
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, band, statType, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true and band is set to 1.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, 1, statType, true);
+ }
+
+ /**
+ * @param pixelData An array of double with pixel values
+ * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest value will be returned.
+ */
+ private static double zonalMode(double[] pixelData) {
+ double[] modes = StatUtils.mode(pixelData);
+ return modes[modes.length - 1];
+ }
+
+ /**
+ * An intermediate function to compute zonal statistics
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return an object of DescriptiveStatistics and an array of double with pixel data.
+ * @throws FactoryException
+ */
+ private static List<Object> getStatObjects(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ RasterUtils.ensureBand(raster, band);
+
+ if(RasterAccessors.srid(raster) != roi.getSRID()) {
Review Comment:
Sorry for taking long, I got confused. As there is another function called `convertCRSIfNeeded` in RasterUtils, which does exactly what we wanted, that is to convert the CRS of given geometry to raster's CRS. I was using the `convertCRSIfNeeded` in `RasterPredicates.java`.
##########
common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java:
##########
@@ -81,6 +88,190 @@ public static long getCount(GridCoverage2D raster, int band) {
// return getCount(raster, 1, excludeNoDataValue);
// }
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return An array with all the stats for the region
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ // order of stats
+ // count, sum, mean, median, mode, stddev, variance, min, max
+ double[] result = new double[9];
+ result[0] = stats.getN();
+ result[1] = stats.getSum();
+ result[2] = stats.getMean();
+ result[3] = stats.getPercentile(50);
+ result[4] = zonalMode(pixelData);
+ result[5] = stats.getStandardDeviation();
+ result[6] = stats.getVariance();
+ result[7] = stats.getMin();
+ result[8] = stats.getMax();
+
+ return result;
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @return An array with all the stats for the region, excludeNoData is set to true
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band) throws FactoryException {
+ return getZonalStatsAll(raster, roi, band, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @return An array with all the stats for the region, excludeNoData is set to true and band is set to 1
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi) throws FactoryException {
+ return getZonalStatsAll(raster, roi, 1, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType, boolean excludeNoData) throws FactoryException {
+
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ switch (statType.toLowerCase()) {
+ case "sum":
+ return stats.getSum();
+ case "average":
+ case "avg":
+ case "mean":
+ return stats.getMean();
+ case "count":
+ return stats.getN();
+ case "max":
+ return stats.getMax();
+ case "min":
+ return stats.getMin();
+ case "stddev":
+ case "sd":
+ return stats.getStandardDeviation();
+ case "median":
+ return stats.getPercentile(50);
+ case "mode":
+ return zonalMode(pixelData);
+ case "variance":
+ return stats.getVariance();
+ default:
+ throw new IllegalArgumentException("Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
+ }
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, band, statType, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true and band is set to 1.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, 1, statType, true);
+ }
+
+ /**
+ * @param pixelData An array of double with pixel values
+ * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest value will be returned.
+ */
+ private static double zonalMode(double[] pixelData) {
+ double[] modes = StatUtils.mode(pixelData);
+ return modes[modes.length - 1];
+ }
+
+ /**
+ * An intermediate function to compute zonal statistics
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return an object of DescriptiveStatistics and an array of double with pixel data.
+ * @throws FactoryException
+ */
+ private static List<Object> getStatObjects(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ RasterUtils.ensureBand(raster, band);
+
+ if(RasterAccessors.srid(raster) != roi.getSRID()) {
+ // implicitly converting roi geometry CRS to raster CRS
+ roi = RasterUtils.convertCRSIfNeeded(roi, raster.getCoordinateReferenceSystem());
+ // have to set the SRID as RasterUtils.convertCRSIfNeeded doesn't set it even though the geometry is in raster's CRS
+ roi = Functions.setSRID(roi, RasterAccessors.srid(raster));
+ }
+
+ // checking if the raster contains the geometry
+ if (!RasterPredicates.rsIntersects(raster, roi)) {
+ throw new IllegalArgumentException("The provided geometry is not intersecting the raster. Please provide a geometry that is in the raster's extent.");
+ }
+
+ Raster rasterData = RasterUtils.getRaster(raster.getRenderedImage());
+ String datatype = RasterBandAccessors.getBandType(raster, band);
+ Double noDataValue = RasterBandAccessors.getBandNoDataValue(raster, band);
+ // Adding an arbitrary value '150' for the pixels that are under the geometry.
+ GridCoverage2D rasterizedGeom = RasterConstructors.asRasterWithRasterExtent(roi, raster, datatype, 150, null);
Review Comment:
https://github.com/apache/sedona/blob/ac788aeeede5d2f1185437ae1eba337ae38c1a41/common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java#L253-L257
It is just a placeholder value to remove the pixels that aren't under the given geometry `roi`
##########
common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java:
##########
@@ -75,6 +81,113 @@ public void testBandNoDataValueIllegalBand() throws FactoryException, IOExceptio
assertEquals("Provided band index 2 is not present in the raster", exception.getMessage());
}
+ @Test
+ public void testZonalStats() throws FactoryException, ParseException, IOException {
+ GridCoverage2D raster = rasterFromGeoTiff(resourceFolder + "raster_geotiff_color/FAA_UTM18N_NAD83.tif");
+ String polygon = "POLYGON ((236722 4204770, 243900 4204770, 243900 4197590, 221170 4197590, 236722 4204770))";
+ Geometry geom = Constructors.geomFromWKT(polygon, RasterAccessors.srid(raster));
+
+ double actual = RasterBandAccessors.getZonalStats(raster, geom, 1, "sum", false);
+ double expected = 1.0690406E7;
+ assertEquals(expected, actual, 0d);
Review Comment:
Okay, will add that.
##########
docs/api/sql/Raster-operators.md:
##########
@@ -937,6 +937,129 @@ Output:
14.0, 204.0, 14.571428571428571, 11.509091348732502, 1.0, 25.0
```
+### RS_ZonalStats
+
+Introduction: This returns a statistic value specified by `statType` over the region of interest defined by `zone`. It computes the statistic from the pixel values within the ROI geometry and returns the result. If the `excludeNoData` parameter is not specified, it will default to `true`. This excludes NoData values from the statistic calculation. Additionally, if the `band` parameter is not provided, band 1 will be used by default for the statistic computation. The valid options for `statType` are:
+
+- `count`: Number of pixels in the region.
+- `sum`: Sum of pixel values
+- `mean|average|avg`: Arithmetic mean.
+- `median`: Middle value in the region.
+- `mode`: Most occurring value, if there are multiple values with same occurrence then will return the largest number.
+- `stddev|sd`: Standard deviation.
+- `variance`: Variance.
+- `min`: Minimum value in the region.
+- `max`: Maximum value in the region.
+
+!!!note
+ The following conditions will throw an `IllegalArgumentException` if they are not met:
+
+ - The `raster` and `zone` geometry should be in the same CRS, refer to [RS_SRID](#rs_srid) for raster, and [ST_SRID](../Function/#st_srid) for geometry to get the spatial reference identifier.
+ - The provided `raster` and `zone` geometry should intersect.
+ - The option provided to `statType` should be valid.
+
+Format:
+
+```
+RS_ZonalStats(raster: Raster, zone: Geometry, band: Integer, statType: String, excludeNoData: Boolean)
+```
+
+```
+RS_ZonalStats(raster: Raster, zone: Geometry, band: Integer, statType: String)
+```
+
+```
+RS_ZonalStats(raster: Raster, zone: Geometry, statType: String)
+```
+
+Since: `v1.5.1`
+
+Spark SQL Example:
+
+```sql
+RS_ZonalStats(rast1, geom1, 1, 'sum', false)
+```
+
+Output:
+
+```
+10690406
+```
+
+Spark SQL Example:
+
+```sql
+RS_ZonalStats(rast2, geom2, 1, 'mean', true)
+```
+
+Output:
+
+```
+226.55992667794473
+```
+
+### RS_ZonalStatsAll
+
+Introduction: Returns an array of statistic values, where each statistic is computed over a region defined by the `zone` geometry. The array contains the following values:
+
+- 0: Count of the pixels.
+- 1: Sum of the pixel values.
+- 2: Arithmetic mean.
+- 3: Median.
+- 4: Mode.
+- 5: Standard deviation.
+- 6: Variance.
+- 7: Minimum value of the zone.
+- 8: Maximum value of the zone.
+
+!!!note
+ The following conditions will throw an `IllegalArgumentException` if they are not met:
+
+ - The `raster` and `zone` geometry should be in the same CRS, refer to [RS_SRID](#rs_srid) for raster, and [ST_SRID](../Function/#st_srid) for geometry to get the spatial reference identifier.
Review Comment:
My bad, will change.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
Posted by "Kontinuation (via GitHub)" <gi...@apache.org>.
Kontinuation commented on PR #1124:
URL: https://github.com/apache/sedona/pull/1124#issuecomment-1818063891
> @Kontinuation All raster functions that take a raster and a geometry as an input, we must perform implicit CRS transformation. What do you think?
I think performing implicit CRS transformation could be better. Functions such as RS_Value, RS_Values may also need CRS transformation. The geometry needs to be transformed to the CRS of the raster. However, such transformations may be impossible or produce incorrect results due to the valid region of CRS. We have to assume that the geometry specified in the query is always able to be properly transformed to the raster's CRS.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
Posted by "jiayuasu (via GitHub)" <gi...@apache.org>.
jiayuasu merged PR #1124:
URL: https://github.com/apache/sedona/pull/1124
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
Posted by "jiayuasu (via GitHub)" <gi...@apache.org>.
jiayuasu commented on PR #1124:
URL: https://github.com/apache/sedona/pull/1124#issuecomment-1817464777
@Kontinuation All raster functions that take a raster and a geometry as an input, we must perform implicit CRS transformation. What do you think?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
Posted by "jiayuasu (via GitHub)" <gi...@apache.org>.
jiayuasu commented on code in PR #1124:
URL: https://github.com/apache/sedona/pull/1124#discussion_r1398179904
##########
common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java:
##########
@@ -81,6 +86,182 @@ public static long getCount(GridCoverage2D raster, int band) {
// return getCount(raster, 1, excludeNoDataValue);
// }
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return An array with all the stats for the region
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ // order of stats
+ // count, sum, mean, median, mode, stddev, variance, min, max
+ double[] result = new double[9];
+ result[0] = stats.getN();
+ result[1] = stats.getSum();
+ result[2] = stats.getMean();
+ result[3] = stats.getPercentile(50);
+ result[4] = zonalMode(pixelData);
+ result[5] = stats.getStandardDeviation();
+ result[6] = stats.getVariance();
+ result[7] = stats.getMin();
+ result[8] = stats.getMax();
+
+ return result;
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @return An array with all the stats for the region, excludeNoData is set to true
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band) throws FactoryException {
+ return getZonalStatsAll(raster, roi, band, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @return An array with all the stats for the region, excludeNoData is set to true and band is set to 1
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi) throws FactoryException {
+ return getZonalStatsAll(raster, roi, 1, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType, boolean excludeNoData) throws FactoryException {
+
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ switch (statType.toLowerCase()) {
+ case "sum":
+ return stats.getSum();
+ case "average":
+ case "avg":
+ case "mean":
+ return stats.getMean();
+ case "count":
+ return stats.getN();
+ case "max":
+ return stats.getMax();
+ case "min":
+ return stats.getMin();
+ case "stddev":
+ case "sd":
+ return stats.getStandardDeviation();
+ case "median":
+ return stats.getPercentile(50);
+ case "mode":
+ return zonalMode(pixelData);
+ case "variance":
+ return stats.getVariance();
+ default:
+ throw new IllegalArgumentException("Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
+ }
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, band, statType, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true and band is set to 1.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, 1, statType, true);
+ }
+
+ /**
+ * @param pixelData An array of double with pixel values
+ * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest value will be returned.
+ */
+ private static double zonalMode(double[] pixelData) {
+ double[] modes = StatUtils.mode(pixelData);
+ return modes[modes.length - 1];
+ }
+
+ /**
+ * An intermediate function to compute zonal statistics
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return an object of DescriptiveStatistics and an array of double with pixel data.
+ * @throws FactoryException
+ */
+ private static List<Object> getStatObjects(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ RasterUtils.ensureBand(raster, band);
+
+ if(RasterAccessors.srid(raster) != roi.getSRID()) {
Review Comment:
@furqaankhan For raster data, we need to allow implicit CRS transformation. Any RS functions that take a geometry as input should check the CRS of both side. Please use the `convertCRSIfNeeded` function in `RasterPredicates.java`. We need to make it public and put it in `RasterUtils`
##########
common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java:
##########
@@ -81,6 +86,182 @@ public static long getCount(GridCoverage2D raster, int band) {
// return getCount(raster, 1, excludeNoDataValue);
// }
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return An array with all the stats for the region
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ // order of stats
+ // count, sum, mean, median, mode, stddev, variance, min, max
+ double[] result = new double[9];
+ result[0] = stats.getN();
+ result[1] = stats.getSum();
+ result[2] = stats.getMean();
+ result[3] = stats.getPercentile(50);
+ result[4] = zonalMode(pixelData);
+ result[5] = stats.getStandardDeviation();
+ result[6] = stats.getVariance();
+ result[7] = stats.getMin();
+ result[8] = stats.getMax();
+
+ return result;
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @return An array with all the stats for the region, excludeNoData is set to true
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band) throws FactoryException {
+ return getZonalStatsAll(raster, roi, band, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @return An array with all the stats for the region, excludeNoData is set to true and band is set to 1
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi) throws FactoryException {
+ return getZonalStatsAll(raster, roi, 1, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType, boolean excludeNoData) throws FactoryException {
+
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ switch (statType.toLowerCase()) {
+ case "sum":
+ return stats.getSum();
+ case "average":
+ case "avg":
+ case "mean":
+ return stats.getMean();
+ case "count":
+ return stats.getN();
+ case "max":
+ return stats.getMax();
+ case "min":
+ return stats.getMin();
+ case "stddev":
+ case "sd":
+ return stats.getStandardDeviation();
+ case "median":
+ return stats.getPercentile(50);
+ case "mode":
+ return zonalMode(pixelData);
+ case "variance":
+ return stats.getVariance();
+ default:
+ throw new IllegalArgumentException("Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
+ }
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, band, statType, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true and band is set to 1.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, 1, statType, true);
+ }
+
+ /**
+ * @param pixelData An array of double with pixel values
+ * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest value will be returned.
+ */
+ private static double zonalMode(double[] pixelData) {
+ double[] modes = StatUtils.mode(pixelData);
+ return modes[modes.length - 1];
+ }
+
+ /**
+ * An intermediate function to compute zonal statistics
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return an object of DescriptiveStatistics and an array of double with pixel data.
+ * @throws FactoryException
+ */
+ private static List<Object> getStatObjects(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ RasterUtils.ensureBand(raster, band);
+
+ if(RasterAccessors.srid(raster) != roi.getSRID()) {
+ throw new IllegalArgumentException("The raster and geometry don't have the same SRID. They should be in the same Coordinate Reference System.");
+ }
+
+ // checking if the raster contains the geometry
+ if (!RasterPredicates.rsIntersects(raster, roi)) {
+ throw new IllegalArgumentException("The provided geometry is not intersecting the raster. Please provide a geometry that is in the raster's extent.");
+ }
+
+ Raster rasterData = RasterUtils.getRaster(raster.getRenderedImage());
+ String datatype = RasterBandAccessors.getBandType(raster, band);
+ Double noDataValue = RasterBandAccessors.getBandNoDataValue(raster, band);
+ GridCoverage2D rasterizedGeom = RasterConstructors.asRasterWithRasterExtent(roi, raster, datatype, 150, null);
+ Raster rasterziedData = RasterUtils.getRaster(rasterizedGeom.getRenderedImage());
+ int width = RasterAccessors.getWidth(rasterizedGeom), height = RasterAccessors.getHeight(rasterizedGeom);
+ double[] rasterizedPixelData = rasterziedData.getSamples(0, 0, width, height, 0, (double[]) null);
+ double[] rasterPixelData = rasterData.getSamples(0, 0, width, height, band - 1, (double[]) null);
+
+ List<Double> pixelData = new ArrayList<>();
+
+ for (int k = 0; k < rasterPixelData.length; k++) {
+
+ if (rasterizedPixelData[k] == 0 || excludeNoData && noDataValue != null && rasterPixelData[k] == noDataValue) {
Review Comment:
Where does `0` come from? Why skip it?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] [SEDONA-428] Add RS_ZonalStats & RS_ZonalStatsAll [sedona]
Posted by "furqaankhan (via GitHub)" <gi...@apache.org>.
furqaankhan commented on code in PR #1124:
URL: https://github.com/apache/sedona/pull/1124#discussion_r1401409457
##########
common/src/main/java/org/apache/sedona/common/raster/RasterBandAccessors.java:
##########
@@ -81,6 +88,190 @@ public static long getCount(GridCoverage2D raster, int band) {
// return getCount(raster, 1, excludeNoDataValue);
// }
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return An array with all the stats for the region
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ // order of stats
+ // count, sum, mean, median, mode, stddev, variance, min, max
+ double[] result = new double[9];
+ result[0] = stats.getN();
+ result[1] = stats.getSum();
+ result[2] = stats.getMean();
+ result[3] = stats.getPercentile(50);
+ result[4] = zonalMode(pixelData);
+ result[5] = stats.getStandardDeviation();
+ result[6] = stats.getVariance();
+ result[7] = stats.getMin();
+ result[8] = stats.getMax();
+
+ return result;
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @return An array with all the stats for the region, excludeNoData is set to true
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi, int band) throws FactoryException {
+ return getZonalStatsAll(raster, roi, band, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @return An array with all the stats for the region, excludeNoData is set to true and band is set to 1
+ * @throws FactoryException
+ */
+ public static double[] getZonalStatsAll(GridCoverage2D raster, Geometry roi) throws FactoryException {
+ return getZonalStatsAll(raster, roi, 1, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType, boolean excludeNoData) throws FactoryException {
+
+ List<Object> objects = getStatObjects(raster, roi, band, excludeNoData);
+ DescriptiveStatistics stats = (DescriptiveStatistics) objects.get(0);
+ double[] pixelData = (double[]) objects.get(1);
+
+ switch (statType.toLowerCase()) {
+ case "sum":
+ return stats.getSum();
+ case "average":
+ case "avg":
+ case "mean":
+ return stats.getMean();
+ case "count":
+ return stats.getN();
+ case "max":
+ return stats.getMax();
+ case "min":
+ return stats.getMin();
+ case "stddev":
+ case "sd":
+ return stats.getStandardDeviation();
+ case "median":
+ return stats.getPercentile(50);
+ case "mode":
+ return zonalMode(pixelData);
+ case "variance":
+ return stats.getVariance();
+ default:
+ throw new IllegalArgumentException("Please select from the accepted options. Some of the valid options are sum, mean, stddev, etc.");
+ }
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, int band, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, band, statType, true);
+ }
+
+ /**
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param statType Define the statistic to be computed
+ * @return A double precision floating point number representing the requested statistic calculated over the specified region. The excludeNoData is set to true and band is set to 1.
+ * @throws FactoryException
+ */
+ public static double getZonalStats(GridCoverage2D raster, Geometry roi, String statType) throws FactoryException {
+ return getZonalStats(raster, roi, 1, statType, true);
+ }
+
+ /**
+ * @param pixelData An array of double with pixel values
+ * @return Mode of the pixel values. If there is multiple with same occurrence, then the largest value will be returned.
+ */
+ private static double zonalMode(double[] pixelData) {
+ double[] modes = StatUtils.mode(pixelData);
+ return modes[modes.length - 1];
+ }
+
+ /**
+ * An intermediate function to compute zonal statistics
+ * @param raster Raster to use for computing stats
+ * @param roi Geometry to define the region of interest
+ * @param band Band to be used for computation
+ * @param excludeNoData Specifies whether to exclude no-data value or not
+ * @return an object of DescriptiveStatistics and an array of double with pixel data.
+ * @throws FactoryException
+ */
+ private static List<Object> getStatObjects(GridCoverage2D raster, Geometry roi, int band, boolean excludeNoData) throws FactoryException {
+ RasterUtils.ensureBand(raster, band);
+
+ if(RasterAccessors.srid(raster) != roi.getSRID()) {
+ // implicitly converting roi geometry CRS to raster CRS
+ roi = RasterUtils.convertCRSIfNeeded(roi, raster.getCoordinateReferenceSystem());
+ // have to set the SRID as RasterUtils.convertCRSIfNeeded doesn't set it even though the geometry is in raster's CRS
+ roi = Functions.setSRID(roi, RasterAccessors.srid(raster));
+ }
+
+ // checking if the raster contains the geometry
+ if (!RasterPredicates.rsIntersects(raster, roi)) {
+ throw new IllegalArgumentException("The provided geometry is not intersecting the raster. Please provide a geometry that is in the raster's extent.");
+ }
+
+ Raster rasterData = RasterUtils.getRaster(raster.getRenderedImage());
+ String datatype = RasterBandAccessors.getBandType(raster, band);
+ Double noDataValue = RasterBandAccessors.getBandNoDataValue(raster, band);
+ // Adding an arbitrary value '150' for the pixels that are under the geometry.
+ GridCoverage2D rasterizedGeom = RasterConstructors.asRasterWithRasterExtent(roi, raster, datatype, 150, null);
Review Comment:
No no. It just to determine which values to use for compute from the provided raster.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org