diff --git a/process_status.md b/process_status.md index 9af0542..eec8bf9 100644 --- a/process_status.md +++ b/process_status.md @@ -1,21 +1,77 @@ # Process implementation status -The following processes have a fully GEE-based implementation: +The following processes have a fully GEE-based implementation or are not relevant for GEE directly: +- [x] absolute +- [x] add +- [x] add_dimension +- [ ] aggregate_temporal_period +- [ ] anomaly +- [x] apply +- [x] arccos +- [x] arcosh +- [ ] array_element +- [x] arsinh +- [x] arcsin +- [x] artanh +- [x] arctan +- [x] ceil +- [ ] climatological_normal +- [x] clip +- [x] cos +- [x] cosh +- [x] create_cube +- [ ] dimension_labels +- [x] divide +- [ ] drop_dimension - [x] e +- [x] exp +- [ ] filter_bands +- [ ] filter_bbox +- [ ] filter_spatial +- [ ] filter_temporal +- [ ] first +- [x] floor +- [x] if +- [x] inspect +- [x] int +- [x] linear_scale_range +- [x] ln +- [ ] load_collection +- [x] log +- [ ] mask +- [ ] mean +- [ ] median +- [ ] merge_cubes +- [ ] min +- [x] multiply - [x] nan +- [x] normalized_difference - [x] pi +- [x] power +- [ ] product +- [ ] reduce_dimension +- [x] rename_dimension +- [ ] rename_labels +- [x] round +- [ ] save_result +- [ ] sd +- [x] sgn +- [x] sin +- [x] sinh +- [x] sqrt +- [x] subtract +- [ ] sum +- [x] tan +- [x] tanh - [x] text_begins - [x] text_concat - [x] text_contains - [x] text_ends - - +- [ ] variance # OUTDATED: openEO v1.0.0 process status -- Mapping from an array to an image collection (preserve metadata) (important for e.g. sort) -- Properly read the default values from the JSON files ## Aggregate & Resample - [ ] aggregate_spatial * convert GeoJson geometry to GEE geometry @@ -48,10 +104,10 @@ The following processes have a fully GEE-based implementation: * process is missing in GEE * one could only do it for JS arrays - [ ] array_labels -- [ ] count +- [ ] count * could be theoretically implemented with existing processes: * `ee.ImageCollection.count()` or `sum()` as a reducer (mask needed) - * `ee.Image.updateMask(mask)` + * `ee.Image.updateMask(mask)` - [X] first * Only available as simple reducer * implementation for arrays needed @@ -59,7 +115,7 @@ The following processes have a fully GEE-based implementation: * Only available as simple reducer * implementation for arrays needed - [ ] order - * process is missing in GEE + * process is missing in GEE * maybe conversion to array? * one could only do it for JS arrays - [ ] rearrange @@ -67,7 +123,7 @@ The following processes have a fully GEE-based implementation: * maybe conversion to array? * one could only do it for JS arrays - [ ] sort - * conversion to array necessary. + * conversion to array necessary. ## Comparison - [ ] between * conversion to array necessary @@ -75,15 +131,15 @@ The following processes have a fully GEE-based implementation: - [ ] eq * conversion to array necessary - [ ] neq - * conversion to array necessary. -- [ ] gt - * conversion to array necessary. + * conversion to array necessary. +- [ ] gt + * conversion to array necessary. - [ ] lt - * conversion to array necessary. + * conversion to array necessary. - [ ] gte - * conversion to array necessary. + * conversion to array necessary. - [ ] lte - * conversion to array necessary. + * conversion to array necessary. - [ ] is_nan * one could only do it for numbers * process is missing in GEE @@ -93,15 +149,10 @@ The following processes have a fully GEE-based implementation: - [ ] is_valid * one could only do it for numbers * process is missing in GEE -- [X] if - * usage of `ee.Algorithms.If()` missing, only JS supported atm # Cubes -- [X] add_dimension -- [X] apply - [ ] apply_dimension - [ ] apply_kernel * `ee.Image.convolve(kernel)` with `ee.Kernel..` could be used -- [X] create_raster_cube - [X] dimension_labels - [X] drop_dimension - [ ] filter_labels @@ -119,8 +170,6 @@ The following processes have a fully GEE-based implementation: * filter metadata by properties is missing * usage of common_bands metadata is missing * bbox WKT implementation missing -- [ ] load_result -- [ ] load_uploaded_files - [X] merge_cubes * overlap resolver is missing - [X] reduce_dimension @@ -129,13 +178,6 @@ The following processes have a fully GEE-based implementation: - [X] save_result - [ ] trim_cube * is not possible to be implemented at the moment -# Development -- [ ] debug -# Import -- [ ] run_udf - * is not possible to be implemented at the moment -- [ ] run_udf_externally - * is not possible to be implemented at the moment # Logic - [ ] all * one could only do it for JS arrays @@ -162,64 +204,21 @@ The following processes have a fully GEE-based implementation: * create `ee.Array` mask from the polygon in JS * then apply it in `ee.Image.arrayMask` # Math -- [X] absolute -- [X] add -- [X] clip -- [X] divide - [ ] extrema * multiple return values need to be handled -- [X] int -- [X] linear_scale_range - [X] max - [X] mean - [X] median - [X] min - [ ] mod * available for arrays and numbers -- [X] multiply -- [X] power - [X] product - [ ] quantiles * multiple return values need to be handled - [X] sd -- [ ] sgn -- [X] sqrt -- [X] subtract - [X] sum - [X] variance -- [ ] cummax - * process accum for arrays and reducer max - * complex apply could do the main work -- [ ] cummin - * process accum for arrays and reducer min - * complex apply could do the main work -- [ ] cumsum - * process accum for arrays and reducer sum (default) - * complex apply could do the main work -- [ ] cumproduct - * process accum for arrays and reducer product - * complex apply could do the main work -- [X] exp -- [X] ln -- [X] log -- [X] normalized_difference - [ ] ndvi -- [X] floor -- [X] ceil -- [X] int -- [X] round -- [X] cos -- [X] sin -- [X] tan -- [X] cosh -- [X] sinh -- [X] tanh -- [X] arccos -- [X] arcsin -- [X] arctan -- [X] arcosh -- [X] arsinh -- [X] artanh - [ ] arctan2 * needs 2 arrays, i.e. it needs a complex apply diff --git a/src/processes/absolute.js b/src/processes/absolute.js index 1ae3f3c..8187b28 100644 --- a/src/processes/absolute.js +++ b/src/processes/absolute.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class absolute extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.abs()); + return GeeUtils.applyNumFunction(node, data => data.abs()); } } diff --git a/src/processes/add.js b/src/processes/add.js index 812d73d..695536f 100644 --- a/src/processes/add.js +++ b/src/processes/add.js @@ -1,13 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class add extends GeeProcess { executeSync(node) { - return Commons.reduceBinaryInCallback( - node, - (a, b) => a.add(b) - ); + return GeeUtils.applyBinaryNumFunction(node, (x, y) => x.add(y)); } } diff --git a/src/processes/add_dimension.js b/src/processes/add_dimension.js index c5cc05c..61fdd00 100644 --- a/src/processes/add_dimension.js +++ b/src/processes/add_dimension.js @@ -4,10 +4,10 @@ import GeeProcess from '../processgraph/process.js'; export default class add_dimension extends GeeProcess { executeSync(node) { - const dc = node.getDataCube("data"); + const dc = node.getDataCube('data'); const name = node.getArgument('name'); const label = node.getArgument('label'); - const type = node.getArgument("type"); + const type = node.getArgument('type'); if (dc.hasDimension(name)) { throw new Errors.DimensionExists({ @@ -16,13 +16,11 @@ export default class add_dimension extends GeeProcess { }); } - const dimension = dc.addDimension(name, type); + // We can't add x and y spatial dimensions. + const axis = (type === 'spatial') ? 'z' : null; + const dimension = dc.addDimension(name, type, axis); dimension.addValue(label); - // Todo processes: A Number value for label causes problems - if (!Number.isInteger(label)) { - dc.renameLabels(dimension, [label], ["#"]); - } return dc; } diff --git a/src/processes/add_dimension.json b/src/processes/add_dimension.json index 6b5f2c4..390097e 100644 --- a/src/processes/add_dimension.json +++ b/src/processes/add_dimension.json @@ -35,19 +35,16 @@ }, { "name": "type", - "description": "The type of dimension, defaults to `other`.", + "description": "The type of dimension.\n\nIf the type is `spatial`, the axis is always `z`, spatial dimensions for `x` and `y` can't be added through this process.", "schema": { "type": "string", "enum": [ "bands", "geometry", "spatial", - "temporal", - "other" + "temporal" ] - }, - "default": "other", - "optional": true + } } ], "returns": { diff --git a/src/processes/arccos.js b/src/processes/arccos.js index 00f0d32..52e4d02 100644 --- a/src/processes/arccos.js +++ b/src/processes/arccos.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class arccos extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.acos()); + return GeeUtils.applyNumFunction(node, data => data.acos()); } } diff --git a/src/processes/arcosh.js b/src/processes/arcosh.js index 208b785..30a656b 100644 --- a/src/processes/arcosh.js +++ b/src/processes/arcosh.js @@ -1,20 +1,11 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class arcosh extends GeeProcess { executeSync(node) { - return Commons.applyInCallback( - node, - image => { - // Using arcosh formula for calculation (see wikipedia) - let img_p2 = image.pow(2); - img_p2 = img_p2.subtract(1); - img_p2 = img_p2.sqrt(); - const result = image.add(img_p2); - return result.log(); - } - ); + // Using arcosh formula for calculation (see wikipedia) + return GeeUtils.applyNumFunction(node, data => data.add(data.pow(2).subtract(1).sqrt()).log()); } } diff --git a/src/processes/arcsin.js b/src/processes/arcsin.js index ea1f9bd..a8fb97f 100644 --- a/src/processes/arcsin.js +++ b/src/processes/arcsin.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class arcsin extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.asin()); + return GeeUtils.applyNumFunction(node, data => data.asin()); } } diff --git a/src/processes/arctan.js b/src/processes/arctan.js index 231407b..dd423ef 100644 --- a/src/processes/arctan.js +++ b/src/processes/arctan.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class arctan extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.atan()); + return GeeUtils.applyNumFunction(node, data => data.atan()); } } diff --git a/src/processes/arsinh.js b/src/processes/arsinh.js index 1dd32dd..aad0bde 100644 --- a/src/processes/arsinh.js +++ b/src/processes/arsinh.js @@ -1,20 +1,11 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class arsinh extends GeeProcess { executeSync(node) { - return Commons.applyInCallback( - node, - image => { - // Using arsinh formula for calculation (see wikipedia ;) - let img_p2 = image.pow(2); - img_p2 = img_p2.add(1); - img_p2 = img_p2.sqrt(); - const result = image.add(img_p2); - return result.log(); - } - ); + // Using arsinh formula for calculation (see wikipedia) + return GeeUtils.applyNumFunction(node, data => data.add(data.pow(2).add(1).sqrt()).log()); } } diff --git a/src/processes/artanh.js b/src/processes/artanh.js index 7875214..5ae35c3 100644 --- a/src/processes/artanh.js +++ b/src/processes/artanh.js @@ -1,21 +1,11 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class artanh extends GeeProcess { executeSync(node) { - return Commons.applyInCallback( - node, - image => { - // Using artanh formula for calculation (see wikipedia ;) - const img_p1 = image.add(1); - let img_p2 = image.subtract(1); - img_p2 = img_p2.multiply(-1); - let result = img_p1.divide(img_p2); - result = result.log(); - return result.multiply(0.5); - } - ); + // Using artanh formula for calculation (see wikipedia) + return GeeUtils.applyNumFunction(node, data => data.add(1).divide(data.multiply(-1).add(1)).log().multiply(0.5)); } } diff --git a/src/processes/ceil.js b/src/processes/ceil.js index ce02324..486fe53 100644 --- a/src/processes/ceil.js +++ b/src/processes/ceil.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class ceil extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.ceil()); + return GeeUtils.applyNumFunction(node, data => data.ceil()); } } diff --git a/src/processes/clip.js b/src/processes/clip.js index b1c73ac..b01399f 100644 --- a/src/processes/clip.js +++ b/src/processes/clip.js @@ -1,15 +1,21 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class clip extends GeeProcess { + static process(ee, data, min, max) { + if (data instanceof ee.Array) { + return data.min(max).max(min); + } + else { + return data.clamp(min, max); + } + } + executeSync(node) { - const min = node.getArgument('min'); - const max = node.getArgument('max'); - return Commons.applyInCallback( - node, - image => image.clamp(min, max) - ); + const min = node.getArgumentAsNumberEE('min'); + const max = node.getArgumentAsNumberEE('max'); + return GeeUtils.applyNumFunction(node, data => clip.process(node.ee, data, min, max)); } } diff --git a/src/processes/cos.js b/src/processes/cos.js index e3e357f..6da8da8 100644 --- a/src/processes/cos.js +++ b/src/processes/cos.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class cos extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.cos()); + return GeeUtils.applyNumFunction(node, data => data.cos()); } } diff --git a/src/processes/cosh.js b/src/processes/cosh.js index c7cbeaa..7ee2c78 100644 --- a/src/processes/cosh.js +++ b/src/processes/cosh.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class cosh extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.cosh()); + return GeeUtils.applyNumFunction(node, data => data.cosh()); } } diff --git a/src/processes/create_cube.js b/src/processes/create_cube.js index 574afb1..d013126 100644 --- a/src/processes/create_cube.js +++ b/src/processes/create_cube.js @@ -6,6 +6,7 @@ export default class create_cube extends GeeProcess { executeSync(node) { const dc = new DataCube(node.ee); dc.setLogger(node.getLogger()); + dc.setData(node.ee.Image()); return dc; } diff --git a/src/processes/divide.js b/src/processes/divide.js index 4506ea5..1acd560 100644 --- a/src/processes/divide.js +++ b/src/processes/divide.js @@ -1,14 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class divide extends GeeProcess { - //TODO processes: Introducing DivisionByZero error executeSync(node) { - return Commons.reduceBinaryInCallback( - node, - (a, b) => a.divide(b) - ); + return GeeUtils.applyBinaryNumFunction(node, (x, y) => x.divide(y)); } } diff --git a/src/processes/exp.js b/src/processes/exp.js index ddad98d..ead6199 100644 --- a/src/processes/exp.js +++ b/src/processes/exp.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class exp extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.exp()); + return GeeUtils.applyNumFunction(node, data => data.exp()); } } diff --git a/src/processes/first.js b/src/processes/first.js index 25470f3..80bb677 100644 --- a/src/processes/first.js +++ b/src/processes/first.js @@ -2,19 +2,16 @@ import GeeProcess from '../processgraph/process.js'; export default class first extends GeeProcess { - geeReducer(node) { + reducer(node) { return node.getArgument('ignore_nodata', true) ? 'firstNonNull' : 'first'; } executeSync(node) { const ee = node.ee; - const data = node.getArgument('data'); + const data = node.getArgumentAsEE('data'); - if (Array.isArray(data)) { - return data[0]; - } - else if (data instanceof ee.Array) { - return data.toList().get(0); + if (data instanceof ee.Array) { + return data.first(); } else if (data instanceof ee.ImageCollection) { return data.first(); diff --git a/src/processes/floor.js b/src/processes/floor.js index 298ff56..db19e51 100644 --- a/src/processes/floor.js +++ b/src/processes/floor.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class floor extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.floor()); + return GeeUtils.applyNumFunction(node, data => data.floor()); } } diff --git a/src/processes/if.js b/src/processes/if.js index 927fde1..0d29df2 100644 --- a/src/processes/if.js +++ b/src/processes/if.js @@ -3,17 +3,10 @@ import GeeProcess from '../processgraph/process.js'; export default class If extends GeeProcess { executeSync(node) { - const value = node.getArgument('value'); - const accept = node.getArgument('accept'); - const reject = node.getArgument('reject'); - + const value = node.getArgumentAsEE('value'); + const accept = node.getArgumentAsEE('accept'); + const reject = node.getArgumentAsEE('reject', null); return node.ee.Algorithms.If(value, accept, reject); - //if (value === true) { - // return accept; - //} - //else { - // return reject; - //} } } diff --git a/src/processes/inspect.js b/src/processes/inspect.js index 1df4d7e..0f4b105 100644 --- a/src/processes/inspect.js +++ b/src/processes/inspect.js @@ -25,6 +25,7 @@ export default class inspect extends GeeProcess { data instanceof ee.Geometry || data instanceof ee.GeometryCollection || data instanceof ee.ComputedObject) { + node.warn('Inspecting GEE objects via getInfo() is slow. Do not use this in production.'); data = data.getInfo(); } diff --git a/src/processes/int.js b/src/processes/int.js index c3c3358..4c53af2 100644 --- a/src/processes/int.js +++ b/src/processes/int.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class int extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.int()); + return GeeUtils.applyNumFunction(node, data => data.int()); } } diff --git a/src/processes/linear_scale_range.js b/src/processes/linear_scale_range.js index aef767c..e274e54 100644 --- a/src/processes/linear_scale_range.js +++ b/src/processes/linear_scale_range.js @@ -1,23 +1,18 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; +import clip from './clip.js'; export default class linear_scale_range extends GeeProcess { executeSync(node) { - const inputMin = node.getArgument('inputMin'); - const inputMax = node.getArgument('inputMax'); - const outputMin = node.getArgument('outputMin', 0); - const outputMax = node.getArgument('outputMax', 1); - return Commons.applyInCallback( - node, - image => { - const numerator = image.subtract(inputMin); - const denominator = inputMax - inputMin; - const ratio = numerator.divide(denominator); - const scaleFactor = outputMax - outputMin; - return ratio.multiply(scaleFactor).add(outputMin); - } - ); + const inputMin = node.getArgumentAsNumberEE('inputMin'); + const inputMax = node.getArgumentAsNumberEE('inputMax'); + const outputMin = node.getArgumentAsNumberEE('outputMin', 0); + const outputMax = node.getArgumentAsNumberEE('outputMax', 1); + return GeeUtils.applyNumFunction(node, data => { + const clipped = clip.process(node.ee, data, inputMin, inputMax); + return clipped.subtract(inputMin).divide(inputMax.subtract(inputMin)).multiply(outputMax.subtract(outputMin)).add(outputMin); + }); } } diff --git a/src/processes/ln.js b/src/processes/ln.js index 16d93c2..6d3e93a 100644 --- a/src/processes/ln.js +++ b/src/processes/ln.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class ln extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.log()); + return GeeUtils.applyNumFunction(node, data => data.log()); } } diff --git a/src/processes/log.js b/src/processes/log.js index 532b495..d1f46b0 100644 --- a/src/processes/log.js +++ b/src/processes/log.js @@ -1,21 +1,16 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class log extends GeeProcess { executeSync(node) { - const base = node.getArgument('base'); - return Commons.applyInCallback( - node, - image => { - switch (base) { - case 10: - return image.log10(); - default: - return image.log().divide(node.ee.Image(base).log()); - } - } - ); + const ee = node.ee; + const base = node.getArgumentAsNumberEE('base'); + return GeeUtils.applyNumFunction(node, data => ee.Algorithms.If( + base.eq(10), + data.log10(), + data.log().divide(base.log()) + )); } } diff --git a/src/processes/multiply.js b/src/processes/multiply.js index 776129c..9bfc290 100644 --- a/src/processes/multiply.js +++ b/src/processes/multiply.js @@ -1,13 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class multiply extends GeeProcess { executeSync(node) { - return Commons.reduceBinaryInCallback( - node, - (a, b) => a.multiply(b) - ); + return GeeUtils.applyBinaryNumFunction(node, (x, y) => x.multiply(y)); } } diff --git a/src/processes/normalized_difference.js b/src/processes/normalized_difference.js index f77a034..934456c 100644 --- a/src/processes/normalized_difference.js +++ b/src/processes/normalized_difference.js @@ -1,13 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class normalized_difference extends GeeProcess { executeSync(node) { - return Commons.reduceBinaryInCallback( - node, - (x, y) => x.subtract(y).divide(x.add(y)) - ); + return GeeUtils.applyBinaryNumFunction(node, (x, y) => x.subtract(y).divide(x.add(y))); } } diff --git a/src/processes/power.js b/src/processes/power.js index 1650aa9..a68e641 100644 --- a/src/processes/power.js +++ b/src/processes/power.js @@ -1,15 +1,11 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class power extends GeeProcess { executeSync(node) { - const p = node.getArgument('p'); - return Commons.applyInCallback( - node, - image => image.pow(p), - "base" - ); + const p = node.getArgumentAsNumberEE('p'); + return GeeUtils.applyNumFunction(node, data => data.pow(p), 'base'); } } diff --git a/src/processes/reduce_dimension.js b/src/processes/reduce_dimension.js index 29d1a6d..9c9d969 100644 --- a/src/processes/reduce_dimension.js +++ b/src/processes/reduce_dimension.js @@ -1,14 +1,38 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; export default class reduce_dimension extends GeeProcess { + static process(dc, reducer, dimensionName, context) { + const dimension = dc.getDimension(dimensionName); + + let data; + const ic = dc.imageCollection(); + if (dimension.type === 'temporal' || dimension.type === 'other') { + data = ic.toList(ic.size()); + } + else if (dimension.type === 'bands') { + data = dimension.getValues().map(band => ic.select(band)); + } + reducer.setArguments({ data, context }); + const resultNode = reducer.executeSync(); + dc.setData(resultNode.getResult()); + return dc; + } + async execute(node) { - let dc = node.getDataCube("data"); - dc = await Commons.reduce(node, dc, this.id); - // ToDo processes: We don't know at this point how the bands in the GEE images/imagecollections are called. - const dimensionName = node.getArgument("dimension"); - dc.dropDimension(dimensionName); + let dc = node.getDataCube('data'); + const reducer = node.getCallback('reducer'); + const dimName = node.getArgument('dimension'); + const context = node.getArgument('context'); + + const dimension = dc.getDimension(dimName); + if (["temporal", "bands"].includes(dimension.type)) { + throw this.invalidArgument('dimension', `Cannot reduce dimension of type ${dimension.type}`); + } + + dc = reduce_dimension.process(dc, reducer, dimName, context); + dc.getDimension(dimName).drop(); + return dc; } diff --git a/src/processes/round.js b/src/processes/round.js index 4c5ef9c..7821037 100644 --- a/src/processes/round.js +++ b/src/processes/round.js @@ -1,23 +1,33 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class round extends GeeProcess { - // ToDo processes: Check whether GEE and JS really follow IEEE 754 rounding behavior - executeSync(node) { - const p = node.getArgument("p"); - const scaleFactor = p !== null ? 10 ** p : null; - return Commons.applyInCallback( - node, - image => { - if (p === null) { - return image.round(); - } - else { - return image.multiply(scaleFactor).round().divide(scaleFactor); - } - } + static bankersRounding(ee, number) { + const rounded = number.round(); + const diff = rounded.subtract(number).abs(); + + // Check if the number is halfway between two integers + return ee.Algorithms.If( + diff.eq(0.5), + // If the number is halfway, round it to the nearest even number + rounded.divide(2).floor().multiply(2), + // Otherwise, use the standard rounding + rounded ); } + executeSync(node) { + const ee = node.ee; + const p = node.getArgumentAsNumberEE("p", 0); + const scaleFactor = ee.Number(10).pow(p); + return GeeUtils.applyNumFunction(node, data => ee.Algorithms.If( + p.eq(0), + // Normal integer rounding + round.bankersRounding(ee, data), + // Rounding to decimal precision, ten, hundred, etc. + round.bankersRounding(ee, data.multiply(scaleFactor)).divide(scaleFactor) + )); + } + } diff --git a/src/processes/sgn.js b/src/processes/sgn.js new file mode 100644 index 0000000..1a61f1f --- /dev/null +++ b/src/processes/sgn.js @@ -0,0 +1,10 @@ +import GeeProcess from '../processgraph/process.js'; +import GeeUtils from '../processgraph/utils.js'; + +export default class sgn extends GeeProcess { + + executeSync(node) { + return GeeUtils.applyNumFunction(node, data => data.signum()); + } + +} diff --git a/src/processes/sgn.json b/src/processes/sgn.json new file mode 100644 index 0000000..ecdbd9d --- /dev/null +++ b/src/processes/sgn.json @@ -0,0 +1,113 @@ +{ + "id": "sgn", + "summary": "Signum", + "description": "The signum (also known as *sign*) of `x` is defined as:\n\n* *1* if *x > 0*\n* *0* if *x = 0*\n* *-1* if *x < 0*\n\nThe no-data value `null` is passed through and therefore gets propagated.", + "categories": [ + "math" + ], + "parameters": [ + { + "name": "x", + "description": "A number.", + "schema": { + "type": [ + "number", + "null" + ] + } + } + ], + "returns": { + "description": "The computed signum value of `x`.", + "schema": { + "type": [ + "number", + "null" + ], + "enum": [ + -1, + 0, + 1, + null + ] + } + }, + "examples": [ + { + "arguments": { + "x": -2 + }, + "returns": -1 + }, + { + "arguments": { + "x": 3.5 + }, + "returns": 1 + }, + { + "arguments": { + "x": 0 + }, + "returns": 0 + }, + { + "arguments": { + "x": null + }, + "returns": null + } + ], + "links": [ + { + "rel": "about", + "href": "http://mathworld.wolfram.com/Sign.html", + "title": "Sign explained by Wolfram MathWorld" + } + ], + "process_graph": { + "gt0": { + "process_id": "gt", + "arguments": { + "x": { + "from_parameter": "x" + }, + "y": 0 + } + }, + "lt0": { + "process_id": "lt", + "arguments": { + "x": { + "from_parameter": "x" + }, + "y": 0 + } + }, + "if_gt0": { + "process_id": "if", + "arguments": { + "value": { + "from_node": "gt0" + }, + "accept": 1, + "reject": { + "from_parameter": "x" + } + } + }, + "if_lt0": { + "process_id": "if", + "arguments": { + "value": { + "from_node": "lt0" + }, + "accept": -1, + "reject": { + "from_node": "if_gt0" + } + }, + "result": true + } + } +} diff --git a/src/processes/sin.js b/src/processes/sin.js index 06e2127..b2ac352 100644 --- a/src/processes/sin.js +++ b/src/processes/sin.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class sin extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.sin()); + return GeeUtils.applyNumFunction(node, data => data.sin()); } } diff --git a/src/processes/sinh.js b/src/processes/sinh.js index ecdfb37..0503ffe 100644 --- a/src/processes/sinh.js +++ b/src/processes/sinh.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class sinh extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.sinh()); + return GeeUtils.applyNumFunction(node, data => data.sinh()); } } diff --git a/src/processes/sqrt.js b/src/processes/sqrt.js index b466c3d..b572cb1 100644 --- a/src/processes/sqrt.js +++ b/src/processes/sqrt.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class sqrt extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.sqrt()); + return GeeUtils.applyNumFunction(node, data => data.sqrt()); } } diff --git a/src/processes/subtract.js b/src/processes/subtract.js index ea78b4c..19a64f8 100644 --- a/src/processes/subtract.js +++ b/src/processes/subtract.js @@ -1,13 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class subtract extends GeeProcess { executeSync(node) { - return Commons.reduceBinaryInCallback( - node, - (a, b) => a.subtract(b) - ); + return GeeUtils.applyBinaryNumFunction(node, (x, y) => x.subtract(y)); } } diff --git a/src/processes/tan.js b/src/processes/tan.js index 09e1ada..3592d4f 100644 --- a/src/processes/tan.js +++ b/src/processes/tan.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class tan extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.tan()); + return GeeUtils.applyNumFunction(node, data => data.tan()); } } diff --git a/src/processes/tanh.js b/src/processes/tanh.js index daf2ceb..50ab18c 100644 --- a/src/processes/tanh.js +++ b/src/processes/tanh.js @@ -1,10 +1,10 @@ import GeeProcess from '../processgraph/process.js'; -import Commons from '../processgraph/commons.js'; +import GeeUtils from '../processgraph/utils.js'; export default class tanh extends GeeProcess { executeSync(node) { - return Commons.applyInCallback(node, image => image.tanh()); + return GeeUtils.applyNumFunction(node, data => data.tanh()); } } diff --git a/src/processes/text_concat.js b/src/processes/text_concat.js index e2c2141..3040192 100644 --- a/src/processes/text_concat.js +++ b/src/processes/text_concat.js @@ -4,15 +4,8 @@ import GeeUtils from '../processgraph/utils.js'; export default class text_concat extends GeeProcess { executeSync(node) { - const ee = node.ee; - const data = node.getArgumentAsListEE('data', element => GeeUtils.toString(ee, element)); - let separator = node.getArgument('separator', null); - if (separator === null) { - separator = ee.String(""); - } - else { - separator = GeeUtils.toString(ee, separator); - } + const data = node.getArgumentAsListEE('data', element => GeeUtils.toString(node.ee, element)); + const separator = node.getArgumentAsStringEE('separator', ''); return data.join(separator); } diff --git a/src/processgraph/commons.js b/src/processgraph/commons.js index 5a84b80..a5017c5 100644 --- a/src/processgraph/commons.js +++ b/src/processgraph/commons.js @@ -199,29 +199,6 @@ export default class Commons { return result; } - static applyInCallback(node, eeImgProcess, dataArg = "x") { - const ee = node.ee; - const data = node.getArgument(dataArg); - const dc = new DataCube(ee, data); - dc.setLogger(node.getLogger()); - const imgProcess = a => eeImgProcess(a).copyProperties({source: a, properties: a.propertyNames()}); - if (dc.isNull()) { - return null; - } - else if (dc.isNumber()) { - return ee.Number(data); - } - else if (dc.isImage()) { - return dc.image(imgProcess); - } - else if (dc.isImageCollection()) { - return dc.imageCollection(img => img.map(imgProcess)); - } - else { - throw new Error("Applying " + node.process_id + " not supported for given data type: " + dc.objectType()); - } - } - static restrictToSpatialExtent(node, dc) { const bbox = dc.getSpatialExtent(); const geom = node.ee.Geometry.Rectangle([bbox.west, bbox.south, bbox.east, bbox.north], Utils.crsToString(bbox.crs, 4326)); diff --git a/src/processgraph/node.js b/src/processgraph/node.js index f32a66d..81ef868 100644 --- a/src/processgraph/node.js +++ b/src/processgraph/node.js @@ -82,8 +82,8 @@ export default class GeeProcessGraphNode extends ProcessGraphNode { return result; } - getArgumentAsStringEE(name) { - const data = this.getArgument(name); + getArgumentAsStringEE(name, defaultValue = undefined) { + const data = this.getArgument(name, defaultValue); const result = GeeUtils.toString(this.ee, data); if (result === null) { throw this.invalidArgument(name, 'Conversion to string not supported'); @@ -91,9 +91,45 @@ export default class GeeProcessGraphNode extends ProcessGraphNode { return result; } - getArgumentAsEE(name) { + getArgumentAsNumberEE(name, defaultValue = undefined) { + const data = this.getArgument(name, defaultValue); + const result = GeeUtils.toNumber(this.ee, data); + if (result === null) { + throw this.invalidArgument(name, 'Conversion to number not supported'); + } + return result; + } + + getArgumentAsEE(name, defaultValue = undefined) { const ee = this.ee; - const data = this.getArgument(name); + let data = this.getArgument(name, defaultValue); + + if (data instanceof ee.ComputedObject) { + this.warn('Inspecting a ComputedObject via getInfo() is slow. Please report this issue.'); + const info = data.getInfo(); + if (typeof info === 'boolean' || typeof info === 'number' || typeof info === 'string') { + this.debug(`ComputedObject is a scalar value: ${info}`); + data = info; + } + else if (Array.isArray(info)) { + this.debug(`ComputedObject is an array of length ${info.length}`); + data = info; + } + else if (Utils.isObject(info)) { + if (typeof info.type === 'string' && typeof ee[info.type] !== 'undefined') { + this.debug(`Casting from ComputedObject to ${info.type}`); + return ee[info.type](data); + } + else { + this.debug(`ComputedObject is an object with the following keys: ${Object.keys(info)}`); + data = info; + } + } + else { + this.warn(`Can't cast ComputedObject to native GEE type.`, info); + } + } + if (typeof data === 'boolean') { this.warn("Implicit conversion of a boolean value to an integer."); return data ? ee.Number(1) : ee.Number(0); @@ -105,7 +141,10 @@ export default class GeeProcessGraphNode extends ProcessGraphNode { return ee.String(data); } else if (typeof data === 'object') { - if (Array.isArray(data)) { + if (data === null && defaultValue === null) { + return null; + } + else if (Array.isArray(data)) { if (data.length === 0) { return ee.Array([], ee.PixelType.float()); } diff --git a/src/processgraph/utils.js b/src/processgraph/utils.js index ec15c2c..15d9459 100644 --- a/src/processgraph/utils.js +++ b/src/processgraph/utils.js @@ -1,5 +1,123 @@ +import Utils from "../utils/utils.js"; + const GeeUtils = { + isEarthEngineType(ee, obj) { + return Utils.isObject(obj) && ( + obj instanceof ee.ComputedObject || + obj instanceof ee.Array || + obj instanceof ee.Blob || + obj instanceof ee.Date || + obj instanceof ee.DateRange || + obj instanceof ee.Dictionary || + obj instanceof ee.Feature || + obj instanceof ee.FeatureCollection || + obj instanceof ee.Geometry || + obj instanceof ee.Image || + obj instanceof ee.ImageCollection || + obj instanceof ee.List || + obj instanceof ee.Number || + obj instanceof ee.String + ); + }, + + isNumType(ee, obj) { + return Utils.isObject(obj) && ( + obj instanceof ee.Number || + obj instanceof ee.Image || + obj instanceof ee.Array + ); + }, + + isNumArrayType(ee, obj) { + return Utils.isObject(obj) && ( + obj instanceof ee.Image || + obj instanceof ee.Array + ); + }, + + isSameNumType(ee, a, b) { + return Utils.isObject(a) && Utils.isObject(b) ( + (a instanceof ee.Number && b instanceof ee.Number) || + (a instanceof ee.Image && b instanceof ee.Image) || + (a instanceof ee.Array && b instanceof ee.Array) + ); + }, + + applyBinaryNumFunction(node, func, xParameter = "x", yParameter = "y") { + const ee = node.ee; + const x = node.getArgumentAsEE(xParameter); + const y = node.getArgumentAsEE(yParameter); + + const eeFunc = (a, b) => { + if (this.isSameNumType(a, b)) { + return func(a, b); + } + else if (this.isNumType(a) && b instanceof ee.Number) { + return func(a, b); + } + else if (a instanceof ee.Image) { + return func(a, ee.Image(b)); + } + else if (a instanceof ee.Array) { + return func(a, b.toArray()); + } + else if (a instanceof ee.Number && b instanceof ee.Image) { + a = ee.Image(a).copyProperties({ source: b, properties: b.propertyNames() }); + return func(a, b); + } + else if (a instanceof ee.Number && b instanceof ee.Array) { + a = ee.Array(ee.List.repeat(a, b.toList().length())); + return func(a, b); + } + + throw node.invalidArgument(yParameter, "Combination of unsupported data types."); + }; + + if (x instanceof ee.ImageCollection && y instanceof ee.ImageCollection) { + throw node.invalidArgument(yParameter, "Can't apply binary function to two image collections."); + } + else if (x instanceof ee.ImageCollection && this.isNumType(y)) { + return x.map(img => eeFunc(img, y)); + } + else if (this.isNumType(x) && y instanceof ee.ImageCollection) { + return y.map(img => eeFunc(x, img)); + } + else if (this.isNumType(x) && this.isNumType(y)) { + return eeFunc(x, y); + } + else { + const param = this.isNumType(x) ? yParameter : xParameter; + throw node.invalidArgument(param, "Combination of unsupported data types."); + } + }, + + applyNumFunction(node, func, dataParameter = "x") { + const ee = node.ee; + const data = node.getArgumentAsEE(dataParameter); + if (this.isNumType(data)) { + return func(data); + } + else if (data instanceof ee.ImageCollection) { + return data.map(img => func(img)); + } + + throw node.invalidArgument(dataParameter, "Unsupported data type."); + }, + + reduceNumFunction(node, func, dataParameter = "data") { + const ee = node.ee; + const data = node.getArgumentAsEE(dataParameter); + if (this.isNumArrayType(data)) { + return func(data); + } + else if (data instanceof ee.ImageCollection) { + return data.map(img => func(img)); + } + + throw node.invalidArgument(dataParameter, "Unsupported data type."); + }, + toList(ee, data, converter = null) { if (Array.isArray(data)) { if (typeof converter === 'function') { @@ -16,6 +134,9 @@ const GeeUtils = { else if (data instanceof ee.Dictionary) { return data.values(); } + else if (data instanceof ee.ComputedObject) { + return ee.List(data); + } return null; }, @@ -27,28 +148,32 @@ const GeeUtils = { else if (data instanceof ee.String) { return data; } - else if (this.isEarthEngineType(data)) { + else if (data instanceof ee.Date) { + return data.format().cat("Z"); + } + else if (data instanceof ee.ComputedObject || data instanceof ee.Number) { return ee.String(data); } + return null; }, - isEarthEngineType(ee, obj) { - return obj instanceof ee.ComputedObject || - obj instanceof ee.Array || - obj instanceof ee.Blob || - obj instanceof ee.Date || - obj instanceof ee.DateRange || - obj instanceof ee.Dictionary || - obj instanceof ee.Feature || - obj instanceof ee.FeatureCollection || - obj instanceof ee.Geometry || - obj instanceof ee.Image || - obj instanceof ee.ImageCollection || - obj instanceof ee.List || - obj instanceof ee.Number || - obj instanceof ee.String; + toNumber(ee, data) { + if (typeof data === 'number' || data instanceof ee.ComputedObject) { + return ee.Number(data); + } + else if (typeof data === 'boolean') { + return ee.Number(data ? 1 : 0); + } + else if (data instanceof ee.Number) { + return data; + } + else if (data instanceof ee.Date) { + return data.millis(); + } + + return null; }, tropicalSeasons(node) {