How to flatten FeatureCollection to a table when reducing Regions - google-earth-engine

All this gee is new for me.
I'm trying to flatten and export a table resulting from reduceRegions. The resulting json is a FeatureCollection but trying to .flatten() will thrown an error.
// Import WDPA dataset
var dataset = ee.FeatureCollection('WCMC/WDPA/current/polygons');
//var roi = dataset.filter(ee.Filter.eq('WDPAID', 33046)); // Cacheu
var roi = dataset.filter(ee.Filter.eq('PARENT_ISO', 'GNB')).select('WDPAID'); // all PA in GNB
// Import Global Forest Change dataset.
var dataset = ee.Image('UMD/hansen/global_forest_change_2019_v1_7').clip(roi);
// Subset the loss year layer; make units absolute (instead of years since 2000).
var treeLoss = dataset.select('lossyear').add(2000).selfMask();
// Display year of forest loss detection to the map.
Map.setOptions('SATELLITE');
Map.addLayer(treeLoss, {
min: 2001,
max: 2019,
palette: ['0D0887', '5B02A3', '9A179B', 'CB4678',
'EB7852', 'FBB32F', 'F0F921']
}, 'Tree loss year');
var forestloss = treeLoss.reduceRegions({
'collection': roi,
'reducer': ee.Reducer.frequencyHistogram(),
'scale': 100,
'crs': 'EPSG:5070'})
.select('histogram');
This went well with a single feature in my roi but but when I try to use a featurecollection and add a .flatten() at this point, I get an error
"the input collection must be a collection of collections but the
element ... was feature, which is not a collection."
print(forestloss, 'forestloss');
Map.setOptions('SATELLITE');
Map.centerObject(roi)
Map.addLayer(roi, {}, 'WDPA GB', true);
link to code.
Any help will be much appreciated.
[EDITED] works fine with a single feature but not with a collection of features

.flatten() does only one thing: convert a feature collection of feature collections into a feature collection of those collections. In your case, you have a feature collection (the output of reduceRegions) which contains plain features, but each of those features has a property which is a dictionary.
In order to convert that to multiple features (rows in your exported table), you need to first map over the collection to convert the dictionary to a collection of features, and then flatten the collection.
var forestLossCollection =
treeLoss.reduceRegions({
'collection': roi,
'reducer': ee.Reducer.frequencyHistogram(),
'scale': 100,
'crs': 'EPSG:5070'
})
.map(function (lossInRegionFeature) {
// Get the histogram dictionary from the feature produced by reduceRegions.
var forestLossDict = ee.Dictionary(lossInRegionFeature.get('histogram'));
// Make a FeatureCollection out of the dictionary.
return ee.FeatureCollection(
forestLossDict
.map(function (key, value) {
// Construct a feature from the dictionary entry.
return ee.Feature(null, {
'system:index': key,
'WDPAID': lossInRegionFeature.get('WDPAID'),
'year': key,
'loss': value});
})
// dict.map() returns a dictionary with newly computed values;
// we just want the values in a list, to make a collection of.
.values());
})
// Flatten the collection of collections returned by the map().
.flatten();
print(forestLossCollection);
Export.table.toDrive({
collection: forestLossCollection,
fileNamePrefix: 'forestLoss',
fileFormat: 'csv',
selectors: ['WDPAID', 'year', 'loss'],
});
https://code.earthengine.google.com/2bcfd3d34fed5255e25d5a553558de36

Related

Exception: Bad value (line 9, file "Code")

I want to read my data from my firebase and write it in google sheet. When i run this Apps Script I got the error
Exception: Bad value (line 9, file "Code")
var ss= SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var range=sheet.getRange(1,1,4,2);
var data= getFirebaseData("Entries");
Logger.log(data)
range.setValues(JSON.parse(data))
}
function getFirebaseData(data){
var firebaseUrl = "**";
var secret = "**";
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl,secret);
var result = base.getData(data);
return result;
}
the data in Firebase is like this:
Entries
date:"09/09/2020"
docAmount:88
docNo:55
partyName: "ffg"
Solutions that I might accept,(solve this issue)or (show a better way to do the process)
You want to write an object's (data) key/value pairs to two columns in a sheet. setValues accepts a 2D array, not an object, so you should first transform data to a 2D array:
const array = Object.keys(data).map(key => [key, data[key]]);
sheet.getRange(1, 1, array.length, array[0].length).setValues(array);
Note:
Making the range dimensions dependent on the array dimensions (array.length, array[0].length) will give you more flexibility (what if the object has 5 properties instead of 4?).
This will only work for simple objects, not if they have nested properties.
Reference:
setValues(values)
Object.keys()
getData(path, optQueryParameters)

Kotlin Map<String, List<Object>> values are changed after toSortedMap call

I got such a map
Map<String, List<Object>>
when I use
map.toSortedMap(comparator)
and comparing values by keys I got sorted map but amount of objects in each value list is also changed (reduced). It's super strange. Under the hood toSortedMap method creates TreeMap with the given comparator, I don't know TreeMap internals but I assume that any map should not change values but only manipulate with the keys. Is this something that is ok for TreeMap or is it a bug in Kotlin library?
Edit
Code I get this issue on:
val categoriesKeys = listOf("key1", "key2", etc)
val categoriesKeysOrdersMap = mapOf("key1" to 0, "key2" to 1, etc)
val model = listOf(MyModel(..), MyModel(..), etc)
val categoryKeyToModelsMap = categoriesKeys
.asSequence()
.map { key ->
key to model.filter {
it.categories?.contains(key) == true
}
}
.filter { it.second.isNotEmpty() }
.toMap()
.toSortedMap(compareBy { categoriesKeysOrdersMap[it] })
On toMap step I have the map I want but not sorted, but after toSortedMap my data is messed up. Comparator compares by int value from categoriesKeysOrdersMap using key from category.

How to separate multiple columns from a range in an array?

I have a range of data in a Google Sheet and I want to store that data into an array using the app script. At the moment I can bring in the data easily enough and put it into an array with this code:
var sheetData = sheet.getSheetByName('Fruit').getRange('A1:C2').getValues()
However, this puts each row into an array. For example, [[Apple,Red,Round],[Banana,Yellow,Long]].
How can I arrange the array by columns so it would look: [[Apple,Banana],[Red,Yellow],[Round,Long]].
Thanks.
It looks like you have to transpose the array. You can create a function
function transpose(data) {
return (data[0] || []).map (function (col , colIndex) {
return data.map (function (row) {
return row[colIndex];
});
});
}
and then pass the values obtained by .getValues() to that function..
var sheetData = transpose(sheet.getSheetByName('Fruit').getRange('A1:C2').getValues())
and check the log. See if that works for you?
Use the Google Sheets API, which allows you to specify the primary dimension of the response. To do so, first you must enable the API and the advanced service
To acquire values most efficiently, use the spreadsheets.values endpoints, either get or batchGet as appropriate. You are able to supply optional arguments to both calls, and one of which controls the orientation of the response:
const wb = SpreadsheetApp.getActive();
const valService = Sheets.Spreadsheets.Values;
const asColumn2D = { majorDimension: SpreadsheetApp.Dimension.COLUMNS };
const asRow2D = { majorDimension: SpreadsheetApp.Dimension.ROWS }; // this is the default
var sheet = wb.getSheetByName("some name");
var rgPrefix = "'" + sheet.getName() + "'!";
// spreadsheetId, range string, {optional arguments}
var single = valService.get(wb.getId(), rgPrefix + "A1:C30");
var singleAsCols = valService.get(wb.getId(), rgPrefix + "A1:C30", asColumn2D);
// spreadsheetId, {other arguments}
var batchAsCols = valService.batchGet(wb.getId(), {
ranges: [
rgPrefix + "A1:C30",
rgPrefix + "J8",
...
],
majorDimension: SpreadsheetApp.Dimension.COLUMNS
});
console.log({rowResp: single, colResp: singleAsCols, batchResponse: batchAsCols});
The reply will either be a ValueRange (using get) or an object wrapping several ValueRanges (if using batchGet). You can access the data (if any was present) at the ValueRange's values property. Note that trailing blanks are omitted.
You can find more information in the Sheets API documentation, and other relevant Stack Overflow questions such as this one.

Immutable JS - merge creates a list of map objects from array

I am using redux and immutablejs, and I am trying to create a reducer function.
I have come across some behaviour I did not expect
const a = new Immutable.Map({
numbers: [{id: 1},{id: 2}]
});
const b = a.merge(
{
numbers: [{id: 4},{id: 5}]
}
);
Here are the values of a and b
a.get("numbers");
[Object, Object]
b.get("numbers");
List {size: 2, _origin: 0, _capacity: 2, _level: 5, _root: null…}
b.get("numbers").get(0);
Map {size: 1, _root: ArrayMapNode, __ownerID: undefined, __hash: undefined, __altered: false}
I did not expect numbers to be an immutable List of Map objects.
In my application, using redux, I set the initial state to:
const initialState = new Immutable.Map({
error: null,
isBusy: false,
films: []
});
I the reducer, when I fetch films I try to merge them as follows:
return state.merge({
isBusy: false,
films: action.payload,
error: null
});
This causes issues in the react component, as films are initially an array of objects, and then they become an Immutable List of Maps.
Should I create a different initial state, or should I be using a different type of merge? Or something else?
Thanks
I think what you are trying to do is not merge of whole map object, at least should not be in the case you say, it should be update + ( concat or merge ):
const a = new Immutable.Map({
numbers: [{id: 1},{id: 2}]
});
const b = a.update("numbers", numbers =>
numbers.concat([{id: 4},{id: 5}])
// or
numbers.merge([{id: 4},{id: 5}])
);
by doing merge in your code, you are overriding the existing ones due to the nature of "merge" because the keys are the same in the merge; "numbers".

How should I structure my data so it works with firebase?

I want to update some data in the forms of
wordBank = {
{word:"aprobi", translation:"to approve", count:2},
{word:"bati", translation:"to hit, to beat, to strike", count:1},
{word:"da", translation:"of", count:1}
}
the goal is to able to extract and display all the values of all the keys in each JSON object. How do I create this format on firebase? do I use .update? or something else?
currently I could only get firebase .update() to work with an array but it gives me data like this
wordBank = [
{word:"aprobi", translation:"to approve", count:2},
{word:"bati", translation:"to hit, to beat, to strike", count:1},
{word:"da", translation:"of", count:1}
];
where each word-object is an index in the array.
Here's how I construct my wordObjects:
function getWords() {
if (document.getElementsByClassName("vortarobobelo").length != 0){
var words;
words = document.getElementsByClassName("vortarobobelo")[0].children[0].children;
for (var i =0; i < words.length; i++) {
var localBank = {} //creating the local variable to store the word
var newWord = words[i].children[0].innerText; // getting the word from the DOM
var newTranslation = words[i].children[1].innerText; // getting the translation from the DOM
localBank.word = newWord;
localBank.translation = newTranslation;
localBank.count = 0 //assuming this is the first time the user has clicked on the word
console.log(localBank);
wordBank[localBank.word] = localBank;
fireBank.update(localBank);
}
}
}
If you want to store the items within an object, you need to pick keys to store them against.
You can't store unkeyed values inside an object in Javascript. This would result in a syntax error:
wordBank = {
{word:"aprobi", translation:"to approve", count:2},
{word:"bati", translation:"to hit, to beat, to strike", count:1},
{word:"da", translation:"of", count:1}
}
The other option is to store them in an array, in which case the keys will be automatically assigned as array indices. Just like your second example.
Maybe you want to store the word objects, using the word itself as a key?
wordBank = {
aprobi: {word:"aprobi", translation:"to approve", count:2},
bati: {word:"bati", translation:"to hit, to beat, to strike", count:1},
da: {word:"da", translation:"of", count:1}
}
This would be easy to do with Firebase. Let's say you have all of your word objects as a list.
var ref = new Firebase("your-firebase-url");
wordObjects.forEach(function(wordObject) {
ref.child(wordObject.word).set(wordObject);
});
Or you could create the object with Javascript, then add it to Firebase using .update.
var wordMap = {};
wordObjects.forEach(function(wordObject) {
wordMap[wordObject.word] = wordObject;
});
ref.update(wordMap);

Resources