Analysing annual deforestation for multiple polygons in google earth engine - arrays

I want to analyse and extract the total annual deforestation between 2001-2021 using the Hansen forest change dataset in Google Earth Engine.
For that, I use a feature collection containing (let's say) sub-national boundaries. The code has referred to as Shape, which has about 8-9k polygons.
The code that analyses annual deforestation (area in km2) for each shape (subset of Feature Collection Shapes) and saves it as an array is pasted below (this part works fine):
var annualforestloss = function(ID)
{
// Get a feature collection with just the custom shapefile feature.
// Shape contains multiple features (polygons)
// Use any shapefile, if mine doesn't work
var geometry = Shape.filter(ee.Filter.eq('ID', ID));
//print(geometry);
Map.addLayer(geometry);
// Get the loss image.
// This dataset is updated yearly, so check for the latest version.
var gfc2021 = ee.Image('UMD/hansen/global_forest_change_2021_v1_9');
var lossImage = gfc2021.select(['loss']);
//Converting m2 to km2
var lossAreaImage = lossImage.multiply(ee.Image.pixelArea()).divide(1000000);
var lossYear = gfc2021.select(['lossyear']);
var lossByYear = lossAreaImage.addBands(lossYear).reduceRegion({
reducer: ee.Reducer.sum().group({
groupField: 1
}),
geometry: geometry,
scale: 30,
maxPixels: 1e9
});
//print(lossByYear);
var statsFormatted = ee.List(lossByYear.get('groups'))
.map(function(el) {
var d = ee.Dictionary(el);
return [ee.Number(d.get('group')).format("20%02d"), d.get('sum')];
});
var statsDictionary = ee.Dictionary(statsFormatted.flatten());
//print(statsFormatted)
//print(statsDictionary)
// Since there has been no forest loss for some years, the code above does not include those years.
// To remedy that, we use another dictionary with values as zero and later combine them without overlap
var dict1 = ee.Dictionary({
"2001": 0, "2002": 0, "2003": 0, "2004": 0, "2005": 0, "2006": 0, "2007": 0, "2008": 0,
"2009": 0, "2010": 0, "2011": 0, "2012": 0, "2013": 0, "2014": 0, "2015": 0, "2016": 0,
"2017": 0, "2018": 0, "2019": 0, "2020": 0, "2021": 0,});
statsDictionary = statsDictionary.combine(dict1, false);
//print(statsDictionary)
// Platting chart
/*
var chart = ui.Chart.array.values({
array: (statsDictionary.values()),
axis: 0,
xLabels: statsDictionary.keys()
}).setChartType('LineChart')
.setOptions({
title: 'Yearly Forest Loss (ID-'+ID+')',
hAxis: {title: 'Year', format: '####'},
vAxis: {title: 'Area (square kilometer)'},
legend: { position: "none" },
lineWidth: 1,
pointSize: 3
});
print(chart)
*/
// Adding 'ID' for seamless data analysis in python
return statsDictionary.values().add(ID)
};
However, the problem I am facing is: Looping through the features in Shapes: Although I know that 'for loops' are not recommended, I am limited by my ability to use map() function in GEE, which returns some client/server error.
// This function combines results from arrays in a loop
var combinearray = function(array1, array2)
{
var arraynew = (ee.Array.cat([array1, array2], 0))
return arraynew
}
// Size of the features (total shapes about 8664)
print(Shape.size())
//**Limited by skill to run it with 'map' function. ########## Please help!**!!!!!!!############
// Works fine for less loops. Hangs for double digit loops
var list = (annualforestloss(0))
for (var ID = 1; ID <= 6; ID++) {
var listnew = (annualforestloss(ID))
var list = (combinearray(list, listnew))
}
print(list)
//reshaping the array for export
var combinedarray = (list.reshape([7,22]))
print('--')
print(combinedarray)
Any help is greatly appreciated.
The following link can be used to see the code editor: https://code.earthengine.google.com/cd335cd1356dc69e3c9adc4ba9710bcb

I think using ReduceRegions() should do the trick. Here is a sample code (https://code.earthengine.google.com/?scriptPath=users%2Fdvps95%2Fdefault%3AHansen_Annual_ForestLoss)
The reduceRegion() maps the functions across multiple polygons. I hope this answers your question. I would suggest you follow the tutorials from "Spatial thoughts"- Ujwal gandhi does an excellent job in breaking down the concepts.
Good luck!

Related

Updating an object impacts an array, after I've pushed the obejct into the array?

I'm writing code in JavaScript running on nodejs.
The intent is to capture a trading candles worth of data into an object, then push that object into an array, where I can store and reference multiple candle's worth of data. I'm getting stuck on the simplest of first steps (not a pro).
My simplified code is below. I've removed a lot just to make the example without adding more code than necessary.
var bitcoinCandleHistoryArray = [];
var candleObject = {
open: 0,
high: 0,
low: 0,
close: 0,
volume: 0
};
candleObject.open = 100;
candleObject.high = 100;
candleObject.low = 100;
candleObject.close = 100;
candleObject.volume = 10000;
bitcoinCandleHistoryArray.push(candleObject);
candleObject.open = 0;
candleObject.high = 0;
candleObject.low = 0;
candleObject.close = 0;
candleObject.volume = 0;
bitcoinCandleHistoryArray.push(candleObject);
Essentially, if I update my candleObject, then push the first candleObject values to the array, I get a an array with my first candle in it. Then if I update my candle object, prior to pushing a second candle in, the array's first candle entry will update.....before I've pushed it....
So if I set candleObject.open = 0, then [bitcoinCandleHistoryArray[0].open also immediately becomes 0, without a push.
I was expecting the CandleObject's values to be independent of any values in my array.
I was expecting the CandleObject's values to be independent of any values in my array.
They are not. After you push candleObject into the array, the variable candleObject still references the same object that is now also in the array (referenced by the array).
For the data to be independent, you need to create a new object for each element in the array.
You could either do so by inlining the creation of the objects like this:
var bitcoinCandleHistoryArray = [];
bitcoinCandleHistoryArray.push({
open: 0,
high: 0,
low: 0,
close: 0,
volume: 0
});
bitcoinCandleHistoryArray.push({
open: 100,
high: 100,
low: 100,
close: 100,
volume: 10000
});
Or, in case you want to stay closer to your original example, you can look into Object.assign, which allows you do make a copy of candleObject that can then be pushed into the array.

SceneKit skeleton 3D animation

i am implementing an app for iPhone6S that helps disabled people to gain back their limbs control by training using a virtual trainer.
This virtual trainer performs simple movements such as raising and lowering an arm, raising and lowering a leg, and then combines them in various rhythmic patterns.
Due to the target platform, I cannot make use of the powerful ARKit, and therefore I have to have to rely on SceneeKit alone.
My code is currently able to load a dae, and applying multiple actions to the skeleton. However I am facing problems in understanding the behavior of SCNAction.rotateTo and SCNAction.rotateBy
let DegreesPerRadians = Float(Double.pi/180)
func convertToRadians(angle:Float) -> Float {
return angle * DegreesPerRadians
}
func convertToRadians(angle:CGFloat) -> CGFloat {
return CGFloat(CGFloat(angle) * CGFloat(DegreesPerRadians))
}
var x45 = SCNAction.rotateBy( x: convertToRadians(angle: 45), y: 0, z: 0, duration: 0.2)
var x45r = SCNAction.rotateBy( x: convertToRadians(angle: -45), y: 0, z: 0, duration: 0.2)
func FOL()->SCNAction! {
let human = getHuman()
let bodyOfHuman = human?.childNode(withName: "Body", recursively: true)
let bodyRealSkeleton = bodyOfHuman?.skinner?.skeleton
let leftLegBone = bodyRealSkeleton?.childNode(withName: "mixamorig_LeftLeg", recursively: true)
let leftLegKick = SCNAction.customAction(duration: 0.1) { [self]
(node, elapsedTime) in leftLegBone?.runAction(x45)
}
return leftLegKick
}
func FOLrev()->SCNAction! {
let human = getHuman()
let bodyOfHuman = human?.childNode(withName: "Body", recursively: true)
let bodyRealSkeleton = bodyOfHuman?.skinner?.skeleton
let leftLegBone = bodyRealSkeleton?.childNode(withName: "mixamorig_LeftLeg", recursively: true)
let leftLegKick = SCNAction.customAction(duration: 0.1) { [self]
(node, elapsedTime) in leftLegBone?.runAction(x45r)
}
return leftLegKick
}
var actionSequence=[FOL(),FOLrev()]
The above code is then called with
_scene?.rootNode.runAction(SCNAction.sequence(actionSequence))
It results into the model properly bending the left leg and straightening it.
However when using instead "rotateTo" it goes into the opposite direction and does not "come back".
screenshot-rotateTo
I watched all DevCon SceneKit materials and sample code but could not find any explanation or guidance.
My real goal is to add more complex actions, such as clapping hands, and when the same approach is tried on those patterns (that require 3-4 rotations) then the results are less predictable.
The answer to this question is that when using rotateTo, in order to go "back" one should use the starting position of the specific angle.
In the specific example, using rotateTo means to use following code
var x45 = SCNAction.rotateTo( x: convertToRadians(angle: 45), y: 0, z: 0, duration: 0.2)
var x45r = SCNAction.rotateTo( x: 0, y: 0, z: 0, duration: 0.2)

Google Earth Engine - error 'Cannot export array bands.' How to export map layer of cross-covariance as Geotiff?

I have computed and displayed as a map layer the cross-covariance of Landsat-derived NDVI and CHIRPS precipitation data.
I now want to export this as an image, clipped to my region of interest, but am getting the following error:
Error 'Cannot export array bands'
I have not managed to find a solution. Is there a way to export this map layer as geotiff? I think perhaps I need to flatten the array but am unsure how to do this.
Here is the code below:
l8toa = ee.ImageCollection("LANDSAT/LC08/C01/T1_TOA")
//Define a region of interest - Baringo county, kenya
var Baringo2 = /* color: #98ff00 */ee.Geometry.Polygon(
[[[35.69382363692023, 1.4034169899773616],
[35.69382363692023, 1.2606333558875118],
[35.61691934004523, 1.0079975313237526],
[35.58945351973273, 0.6509798625215468],
[35.71030312910773, 0.35436075019447294],
[35.72128945723273, 0.18956774160826206],
[35.61691934004523, 0.18407460674896256],
[35.58945351973273, 0.13463632293582842],
[35.71030312910773, 0.04125265421470341],
[35.68283730879523, -0.0466379620709295],
[35.74875527754523, -0.18945988757796725],
[35.96848184004523, 0.05223897866641199],
[36.09482461348273, 0.002800509340276178],
[36.27060586348273, 0.2719645271288622],
[36.23215371504523, 0.45872822561768967],
[36.32004434004523, 0.6509798625215468],
[36.47934609785773, 0.8651943843139164],
[36.32004434004523, 0.9915205478901427],
[36.18271523848273, 1.1672705367627716],
[36.08933144942023, 1.1892385469740003],
[35.79270059004523, 1.6944479915417494]]]);
//print (Baringo2);
//Add Baringo
Map.addLayer(ee.Image().paint(Baringo2, 0, 2), {}, 'Baringo_county');
Map.centerObject(Baringo2);
//B) Filtering, masking and preparing bands of interest
//preprocess the Landsat 8 imagery by filtering it to the location of interest, masking clouds,
//and adding the variables in the model:
// This field contains UNIX time in milliseconds.
var timeField = 'system:time_start';
// Use this function to mask clouds in all Landsat imagery.
var maskClouds = function(image) {
var quality = image.select('BQA');
var cloud01 = quality.eq(61440);
var cloud02 = quality.eq(53248);
var cloud03 = quality.eq(28672);
var mask = cloud01.or(cloud02).or(cloud03).not();
return image.updateMask(mask);
};
// Use this function to add variables for NDVI, time and a constant
// to Landsat 8 imagery.
var addVariablesl8 = function(image) {
// Compute time in fractional years since the epoch.
var date = ee.Date(image.get(timeField));
var years = date.difference(ee.Date('1970-01-01'), 'year');
// Return the image with the added bands.
return image
// Add an NDVI band.
.addBands(image.normalizedDifference(['B5', 'B4']).rename('NDVI'))
.float()
// Add a time band.
.addBands(ee.Image(years).rename('t').float())
// Add a constant band.
.addBands(ee.Image.constant(1));
};
// Remove clouds, add variables and filter to the area of interest - landsat 8.
var filteredLandsatl8 = l8toa
.filterDate('2013-02-07', '2018-08-25')
.filterBounds(Baringo2)
.map(maskClouds)
.map(addVariablesl8);
// Cross-covariance is measuring the correspondence between a variable and a covariate at a lag.
//Create a lagged ImageCollection
var lag = function(leftCollection, rightCollection, lagDays) {
var filter = ee.Filter.and(
ee.Filter.maxDifference({
difference: 1000 * 60 * 60 * 24 * lagDays,
leftField: timeField,
rightField: timeField
}),
ee.Filter.greaterThan({
leftField: timeField,
rightField: timeField
}));
return ee.Join.saveAll({
matchesKey: 'images',
measureKey: 'delta_t',
ordering: timeField,
ascending: false, // Sort reverse chronologically
}).apply({
primary: leftCollection,
secondary: rightCollection,
condition: filter
});
};
//This function joins a collection to itself, using a filter that gets all the images before but within a specified time difference (in days) of each image.
//That list of previous images within the lag time is stored in a property of the image called images, sorted reverse chronologically.
//Compute cross covariance
//i) The covariance reducer expects a set of one-dimensional arrays as input.
//So pixel values corresponding to time t need to be stacked with pixel values at time t ? l as multiple bands in the same image.
var merge = function(image) {
// Function to be passed to iterate.
var merger = function(current, previous) {
return ee.Image(previous).addBands(current);
};
return ee.ImageCollection.fromImages(
image.get('images')).iterate(merger, image);
};
//...use that function to merge the bands from the lagged collection:
//Use a function to convert the merged bands to arrays with bands pt and ph, then reduce with the covariance reducer:
var covariance = function(mergedCollection, band, lagBand) {
return mergedCollection.select([band, lagBand]).map(function(image) {
return image.toArray();
}).reduce(ee.Reducer.covariance(), 8);
};
//is NDVI related in some way to the precipitation before the NDVI was observed?
//To estimate the strength of this relationship (in every pixel),
//load precipitation, join, merge, and reduce as previously:
// Load Precipitation data (covariate)
var chirps = ee.ImageCollection('UCSB-CHG/CHIRPS/PENTAD');
// Join the t-l (l=1 pentad) precipitation images to the Landsat.
var lag1PrecipNDVI = lag(filteredLandsatl8, chirps, 5);
// rainfall 5 days previous - aimed at annual grasses that respond quickly
// Add the precipitation images as bands.
var merged1PrecipNDVI = ee.ImageCollection(lag1PrecipNDVI.map(merge));
// Compute, visualise and display cross-covariance.
var cov1PrecipNDVI = covariance(merged1PrecipNDVI, 'NDVI', 'precipitation');
// create vizualization parameters
var viz = {min:-0.5, max:0.5, palette:['0000FF', '008000', 'FF0000']};
Map.addLayer(cov1PrecipNDVI.arrayGet([0, 1]).clip(Baringo2), viz, 'NDVI - PRECIP cov (lag = 5), Baringo');
//red is high cross covariance and blue is low covariance between NDVI and precipitation 5 days previously
// Export the cov1PrecipNDVI image, specifying scale and region.
Export.image.toDrive({
folder: 'Baringo_Remote_Sensing',
image: cov1PrecipNDVI,
description: 'NDVI - PRECIP cov (lag = 5)',
scale: 30,
region: Baringo2,
maxPixels: 1e10
});
Can anyone help me please?
Thank you.

as3 combine elements of array that match, use math to count the numbers

My head is spinning around for not finding the solution to my problem. I've searched all over the web to find an answer or solution to get it done. I thought what I want is simple to achieve, but I can't get it right. Maybe it is a lot more difficult or I am just looking wrong.
I've made a as3 project where content will be writen to a .txt file. Each day there will be made a new file and on this txt file will be saved al the data of the actions of the day.
The txt file looks like this:
79, 2-1-2015, Orange,1,4.00, 15:59:43
79, 2-1-2015, Blue,1,1.00, 15:59:43
80, 2-1-2015, Orange,1,4.00, 16:2:52
80, 2-1-2015, Black,1,1.00, 16:2:52
(actionumber, date, article, amount, cost, time)
I now want to read this data in my project and look for the same articles, count the amounts and costs and put it back as one row in a new file. For example, based on above txt file:
2, Orange, 8.00
1, Blue, 1.00
1, Black, 1.00
total: 4, , 10.00
I know I can read and write files via Filestream.
After I read the file, I've put the content into a array and split each row in a different element. But then, when I need to compare, and do the math, I'm stuck.
I tried to use IndexOf to search in the array,but I don't know what to do next. I read about .concat and saw examples of duplicates being removed from an array. but I want duplicates based on the article (amount and cost may vary) combine and count instead of deleting. And I think that is a different cup of tea?!
Has some one a good example or can point me in the right direction? Thank you so much
You can use an Object ( or associative array ) to stock your articles with their names as keys like this :
var url_request:URLRequest = new URLRequest('data.txt')
var url_loader:URLLoader = new URLLoader()
url_loader.addEventListener(
Event.COMPLETE,
function(e:Event){
load_data(e.target.data);
}
)
url_loader.load(url_request);
function load_data(data:String):void {
var actions:Object = {};
// convert all data to an Array
var temp_arr:Array = data.split(String.fromCharCode(13));
for(var i = 0; i < temp_arr.length; i++){
var action_line:String = temp_arr[i];
// convert every line to an Array
var action_arr:Array = action_line.split(', ');
var article:String = action_arr[2];
var amount:int = action_arr[3];
var cost:int = action_arr[4];
if(actions[article]){
var t_a:Array = actions[article]
t_a[0] += int(amount); // cumulate amount
t_a[2] += int(cost); // cumulate cost
// update article
actions[article] = t_a;
// example : actions['Orange'] = [1, 'Orange', 4]
// => actions['Orange'] = [2, 'Orange', 8]
} else {
// create new article as an Array
actions[article] = [int(amount), article, int(cost)];
// example : actions['Orange'] = [1, 'Orange', 4]
}
}
var amounts:int = 0;
var costs:int = 0;
for(var action:String in actions){
amounts += actions[action][0];
costs += actions[action][2];
trace(actions[action][0]+', '+actions[action][1]+', '+actions[action][2].toFixed(2));
}
trace('total : '+amounts+', '+costs.toFixed(2));
// gives :
//
// 1, Black, 1.00
// 2, Orange, 8.00
// 1, Blue, 1.00
// total : 4, 10.00
}
Of course you have to add the part that will save these data into a text file.
Hope that can help you.

How to sum an array with meteor/mongo

I've 18 documents in my collection movie. For each movie for example:
{
title: "Test Movie 2",
date: [20130808, 20130606],
score: [ {"pete": 1, "mary": 1, "simon": 1, "pat": 2, "mike": 0},
{"pete": 5, "mary": 5, "simon": 5, "pat": 0, "mike": 5}]
}
Now, I want to show the date and sum of the second document in the array 'score' on the client, like:
<div class="details">
Test Movie 2: 20 points 20130606
</div>
Have somebody an idea how I can do that?
You could use a transform, it might be better to define each document with a name and explicitly defining the points as name/value pair instead of points being the value for each persons name.
But this should work:
Movies.find({}, {transform: function(doc) {
var total_points = 0;
var people = doc.score[1]; //Second in array
for(point in people) {
total_points += people[point]
}
doc.points = total_points;
return doc;
}});
This should give you:
{
title: "Test Movie 2",
points: 20,
date: [20130808, 20130606],
score: [ {"pete": 1, "mary": 1, "simon": 1, "pat": 2, "mike": 0},
{"pete": 5, "mary": 5, "simon": 5, "pat": 0, "mike": 5}]
}
Mongo can likely do this outright, but you're not going to be able to do this directly by querying a collection due to limitation of the Mongo livedata package as of 0.6.5. Aggregation framework is also off-limits, and they seem to have pulled the 'hidden' method that allowed direct access to Mongo.
So your options are:
Use transform as per Akshat's answer - the best of both worlds
Aggregate manually in the client in a template helper. I recommend using _.js which comes 'free' with Meteor (again this might change, but you could always pull the library in manually later).
var sum = _.reduce(score[1], function(memo, num){ return memo + num; }, 0);
(I didn't test the above, but it should send you on the right track).
Aggregate upstream, during the insert/update/deletes, likely by observing changes on the collection and 'feeding in' the sum() of the elements you are inserting in either the same collection or an aggregate one.
Which method you use depends on where performance matters most to you, usually doing aggregates before you insert tend to avoid issues later on, and lightens load.
Good luck and let us know how you get on.

Resources