-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathgee-classification.js
599 lines (441 loc) · 25.4 KB
/
gee-classification.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
//////////////////////////////////////////////////////////
// This code was written for cropland classification in South Sudan
// The code should be run twice, firstly to export a training sample, secondly to import that training sample
// and classify the AOI. This process allows for external feature selection and reduced GEE processing time (per run)
//// Feature selection string used to create a subset of the dataset prior to classification
//var feature_string = ee.List(['nir_12', 'nir_11', 'nir_10', 'NDVI_10', 'swir2_8', 'NDVI_12', 'swir2_12', 'ndwi', 'NDVI', 'red_12', 'NDVI_8', 'VH_2', 'red', 'ndwi_12', 'nir', 'NDVI_11', 'ndwi_8', 'NDVI_5', 'swir2', 'red_10', 'VH', 'ndwi_5', 'NDVI_7', 'ndwi_1', 'swir2_6', 'swir2_9', 'ndwi_7', 'NDVI_1', 'NDVI_2', 'swir2_7', 'VH_1', 'nir_8', 'swir2_11', 'std', 'blue_12', 'blue_5', 'swir2_10', 'ndwi_10', 'red_11', 'ndwi_11', 'nir_5', 'min', 'VV_2', 'VH_3', 'NDVI_6', 'swir2_1', 'blue', 'red_8', 'red_6', 'ndwi_6', 'max', 'swir2_2', 'swir2_5', 'blue_10', 'ndwi_2', 'red_5', 'VH_8', 'ndwi_4', 'NDVI_4', 'ndwi_9'])
//var layer_selection = feature_string
//var feature_selection = feature_string.add('class')
var clip_feature = geometry6
////////////////////////////Loading and labeling training polygons////////////////////////////
//Rice has been excluded
//Fallow
//var fallow_reclassed = fallow.map(function(feature) {
// return feature.set('class', 1);
// });
//Non-agri
var savanna_reclassed = non_agri.filterMetadata('label', 'equals', 'savanna').map(function(feature) {
return feature.set('class', 2);
});
var forest_reclassed = non_agri.filterMetadata('label', 'equals', 'forest').map(function(feature) {
return feature.set('class', 3);
});
var bare_reclassed = non_agri.filterMetadata('label', 'equals', 'bare').map(function(feature) {
return feature.set('class', 4);
});
var grassland_reclassed = non_agri.filterMetadata('label', 'equals', 'grassland').map(function(feature) {
return feature.set('class', 5);
});
var water_reclassed = non_agri.filterMetadata('label', 'equals', 'water').map(function(feature) {
return feature.set('class', 6);
});
var artificial_reclassed = non_agri.filterMetadata('label', 'equals', 'artificial').map(function(feature) {
return feature.set('class', 7);
});
/*
//Crops
var beans_reclassed = crop.filterMetadata('LC_3', 'equals', 'Beans').map(function(feature) {
return feature.set('class', 8);
});
var cassavaGr_reclassed = crop.filterMetadata('LC_3', 'equals', 'Cassava/Gr').map(function(feature) {
return feature.set('class', 9);
});
var maize_reclassed = crop.filterMetadata('LC_3', 'equals', 'Maize').map(function(feature) {
return feature.set('class', 10);
});
var millet_reclassed = crop.filterMetadata('LC_3', 'equals', 'Millet').map(function(feature) {
return feature.set('class', 11);
});
var sesame_reclassed = crop.filterMetadata('LC_3', 'equals', 'Sesame').map(function(feature) {
return feature.set('class', 12);
});
var sourghum_reclassed = crop.filterMetadata('LC_3', 'equals', 'Sourghum').map(function(feature) {
return feature.set('class', 13);
});
*/
// Combining selected polygons and removing unsupported geometries
var training_points = savanna_reclassed.merge(forest_reclassed)
.merge(bare_reclassed)
.merge(grassland_reclassed)
.merge(water_reclassed)
.merge(artificial_reclassed)
.merge(crop)
.filterBounds(clip_feature);
/////////////////////////// Loading Imagery //////////////////////////////////////////////
// Filters
var sen2_selection = clip_feature.buffer(40000);
var sen1_selection = clip_feature.buffer(60000);
var selection_bands = ['NDVI', 'blue', 'red', 'nir', 'swir2'];
var start = ee.Date('2018-01-01');
var end = ee.Date('2019-01-30');
var sentinel2 = ee.ImageCollection('COPERNICUS/S2')
.filterDate(start, end)
.filterBounds(sen2_selection)
.map(function(image) {
return image.addBands(image.normalizedDifference(['B8','B4']).select(['nd'], ['NDVI'])).addBands(image.metadata('system:time_start'));
})
.map(function(img) {
var t = img.select(['B1','B2','B3','B4', 'B8','B10', 'B11','B12']).divide(10000);//Rescale to 0-1
t = t.addBands(img.select(['QA60']));
t = t.addBands(img.select(['NDVI']));
var out = t.copyProperties(img).copyProperties(img,['system:time_start']);
return out;
})
.select(['NDVI', 'QA60', 'B1','B2','B3','B4', 'B8','B10', 'B11','B12'],
['NDVI', 'QA60','cb', 'blue', 'green', 'red', 'nir', 'cirrus','swir1', 'swir2'])
//.map(data_clipper)
.sort('system:time_start');
var sentinel1 = ee.ImageCollection('COPERNICUS/S1_GRD')
.sort('system:time_start')
.filterBounds(sen1_selection) //Geometry, not feature!! sen1_selection
.filterDate(start, end)
.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV'))
.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH'))
.filter(ee.Filter.eq('instrumentMode', 'IW'))
.select(['VV', 'VH'])
.map(function(image) {
return image.unitScale(-30, 30).copyProperties(image, ['system:time_start']); //No clamping performed, may be a problem for outliers
});
var landsat = ee.ImageCollection('LANDSAT/LC08/C01/T1_TOA')
.filterDate(start, end)
.filterBounds(sen2_selection)
.select(['B2', 'B3', 'B4','B5', 'B6', 'B7', 'BQA'],['blue', 'green', 'red', 'nir', 'swir1', 'swir2', 'BQA'])
.map(function(image) {
return image.addBands(image.normalizedDifference(['nir','red']).select(['nd'], ['NDVI']));
})
.sort('system:time_start');
var aqua = ee.ImageCollection('MODIS/006/MYD13Q1')
.filterDate(start, end)
.filterBounds(sen2_selection)
.select(['NDVI','sur_refl_b03', 'sur_refl_b01','sur_refl_b02', 'sur_refl_b07'], ['NDVI', 'blue', 'red', 'nir', 'swir2']) //error: ['NDVI','blue','green','red', 'nir'], error2: ['NDVI','sur_refl_b01','sur_refl_b02','sur_refl_b03', 'sur_refl_b07'], ['NDVI','red','nir','blue', 'swir2']
.sort('system:time_start');
var terra = ee.ImageCollection('MODIS/006/MOD13Q1')
.filterDate(start, end)
.filterBounds(sen2_selection)
.select(['NDVI','sur_refl_b03', 'sur_refl_b01','sur_refl_b02', 'sur_refl_b07'], ['NDVI', 'blue', 'red', 'nir', 'swir2']) //error: ['NDVI','blue','green','red', 'nir']
.sort('system:time_start');
var dem = ee.Image('USGS/SRTMGL1_003');
var elevation = dem.select('elevation');
var slope = ee.Terrain.slope(elevation);
//////////////////Cloud masking Landsat //////////////////////////
//Output: landsat_masked
// Cloud masking
// Inspiration: https://gis.stackexchange.com/questions/292835/landsat-8-bqa-using-cloud-confidence-to-create-a-cloud-mask
var l8_cloud_remover = function(image) {
var RADIX = 2; // Radix for binary (base 2) data.
// Extract the QA band.
var image_qa = image.select('BQA');
var extractQABits = function (qaBand, bitStart, bitEnd) {
var numBits = bitEnd - bitStart + 1;
var qaBits = qaBand.rightShift(bitStart).mod(Math.pow(RADIX, numBits));
return qaBits;
};
// Create a mask for the dual QA bit "Cloud Confidence".
var bitStartCloudConfidence = 5;
var bitEndCloudConfidence = 6;
var qaBitsCloudConfidence = extractQABits(image_qa, bitStartCloudConfidence, bitEndCloudConfidence);
// Test for clouds, based on the Cloud Confidence value.
var testCloudConfidence = qaBitsCloudConfidence.gte(2);
// Create a mask for the dual QA bit "Cloud Shadow Confidence".
var bitStartShadowConfidence = 7;
var bitEndShadowConfidence = 8;
var qaBitsShadowConfidence = extractQABits(image_qa, bitStartShadowConfidence, bitEndShadowConfidence);
// Test for shadows, based on the Cloud Shadow Confidence value.
var testShadowConfidence = qaBitsShadowConfidence.gte(2);
// Calculate a composite mask and apply it to the image.
var maskComposite = (testCloudConfidence.or(testShadowConfidence)).not();
return image.updateMask(maskComposite);
};
var landsat_masked = landsat.map(l8_cloud_remover).select(selection_bands); ///Add optical bands if needed
////////////////// Mosaicing Landsat //////////////////////////
//Output: landsat_mosaics
// Inspiration: https://code.earthengine.google.com/20ad3c83a17ca27b28640fb922819208
// Date range
var diff = end.difference(start, 'day');
var temporalResolution = 30; // days
var range = ee.List.sequence(0, diff.subtract(1), temporalResolution).map(function(day){return start.advance(day,'day')});
// Mosaic maker function (same code used for Sentinel 2) - Inspiration: https://code.earthengine.google.com/20ad3c83a17ca27b28640fb922819208
function mosaic_maker(imageCollection){
var temporal_composites = function(date, newlist) {
date = ee.Date(date);
newlist = ee.List(newlist);
var filtered = imageCollection.filterDate(date, date.advance(temporalResolution, 'day'));
var filtered_addedQA = filtered.map(function(image) {return image.addBands(image.metadata('system:time_start'))});
var image = ee.Image(filtered_addedQA.qualityMosaic('NDVI')).set('system:time_start', date).clip(clip_feature); //filtered_addedQA.first().get('system:time_start')); // date); qualityMosaic('system:time_start')) //Change to qualityMosaic()
return ee.List(ee.Algorithms.If(filtered.size(), newlist.add(image), newlist));
};
var imageCollection_unfiltered = ee.ImageCollection(ee.List(range.iterate(temporal_composites, ee.List([]))));
return imageCollection_unfiltered.limit(range.size().subtract(1), 'system:time_start');
}
//var inspectImage = 0
//print('sentinel2_mosaics', '', sentinel2_mosaics)
//Map.addLayer(ee.Image(landsat_mosaics.toList(landsat_mosaics.size()).get(inspectImage)), VisParamNDVI, 'landsat_mosaic')
var landsat_mosaics = mosaic_maker(landsat_masked);
//////////////////////// Mosaicing MODIS ////////////////////////////////
//Output: modis_mosaics
var modis = terra.merge(aqua).sort('system:time_start');
// Mosaic maker
var day_mosaics = function(date, newlist) {
date = ee.Date(date);
newlist = ee.List(newlist);
var filtered = modis.filterDate(date, date.advance(temporalResolution, 'day'));
var image = ee.Image(filtered.mosaic()).set('system:time_start', date); //latest image on top
return ee.List(ee.Algorithms.If(filtered.size(), newlist.add(image), newlist));
};
var modis_unfiltered = ee.ImageCollection(ee.List(range.iterate(day_mosaics, ee.List([]))));
var modis_list = modis_unfiltered.toList(range.size().subtract(1)); //removing last image
var modis_mosaics = ee.ImageCollection(modis_list.map(function(image) {
var scale_factor = ee.Image(0.0001);
var time_string = ee.Image(image).get('system:time_start');
return ee.Image(image).multiply(scale_factor).set('system:time_start', time_string).set('type', 'modis').clip(clip_feature);
}));
//////////////////////////// Cloud Masking Sentinel 2///////////////////////////////////
//Output: sentinel2_masked
// Note that the (superior) Sen2Cloudless algorithms has been made available after the completion of this project:
// https://developers.google.com/earth-engine/tutorials/community/sentinel-2-s2cloudless
//User Params
var cloudThresh =5;//Ranges from 1-100.Lower value will mask more pixels out. Generally 10-30 works well with 20 being used most commonly
var cloudHeights = ee.List.sequence(200,10000,250);//Height of clouds to use to project cloud shadows
var irSumThresh =0.35;//Sum of IR bands to include as shadows within TDOM and the shadow shift method (lower number masks out less)
var dilatePixels = 2; //Pixels to dilate around clouds
var contractPixels = 1;//Pixels to reduce cloud mask and dark shadows by to reduce inclusion of single-pixel comission errors
var rescale = function(img, exp, thresholds) {
return img.expression(exp, {img: img})
.subtract(thresholds[0]).divide(thresholds[1] - thresholds[0]);
};
//Cloud masking algorithm for Sentinel2
function sentinelCloudScore(img) {
// Compute several indicators of cloudyness and take the minimum of them.
var score = ee.Image(1);
// Clouds are reasonably bright in the blue and cirrus bands.
score = score.min(rescale(img, 'img.blue', [0.1, 0.5]));
score = score.min(rescale(img, 'img.cb', [0.1, 0.3]));
score = score.min(rescale(img, 'img.cb + img.cirrus', [0.15, 0.2]));
// Clouds are reasonably bright in all visible bands.
score = score.min(rescale(img, 'img.red + img.green + img.blue', [0.2, 0.8])); //[0.2, 0.8]
//Clouds are moist
var ndmi = img.normalizedDifference(['nir','swir1']);
score=score.min(rescale(ndmi, 'img', [-0.1, 0.1]));
// However, clouds are not snow.
//var ndsi = img.normalizedDifference(['green', 'swir1']);
//score=score.min(rescale(ndsi, 'img', [0.8, 0.6]));
score = score.multiply(100).byte();
return img.addBands(score.rename('cloudScore'));
}
//Function to bust clouds from S2 image
function bustClouds(img){
img = sentinelCloudScore(img);
img = img.updateMask(img.select(['cloudScore']).gt(cloudThresh).focal_min(contractPixels).focal_max(dilatePixels).not());
return img;
}
// QA60 cloud masking (additional cloud mask)
function maskS2clouds(image) {
var qa = image.select('QA60');
var cloudBitMask = ee.Number(2).pow(10).int(); // clouds
var cirrusBitMask = ee.Number(2).pow(11).int(); // cirrus
var date = image.get('system:time_start');
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0));
return image.updateMask(mask).set('system:time_start', date); //.divide(10000)
}
// simpleTDOM2
function simpleTDOM2(c){
var shadowSumBands = ['nir','swir1'];
var irSumThresh = 0.4;
var zShadowThresh = -1.2;
//Get some pixel-wise stats for the time series
//Extracts stdDev and mean for image in NIR and SWIR1
var irStdDev = c.select(shadowSumBands).reduce(ee.Reducer.stdDev());
var irMean = c.select(shadowSumBands).mean();
var bandNames = ee.Image(c.first()).bandNames();
//Mask out dark dark outliers
c = c.map(function(img){
//For each image, subtracts mean from NIR and SWIR1, then divides by stdDev
var z = img.select(shadowSumBands).subtract(irMean).divide(irStdDev);
//Gets sum of NIR and SWIR1
var irSum = img.select(shadowSumBands).reduce(ee.Reducer.sum());
//Gets area where z ????
var m = z.lt(zShadowThresh).reduce(ee.Reducer.sum()).eq(2).and(irSum.lt(irSumThresh)).not();
return img.updateMask(img.mask().and(m));
});
return c.select(bandNames);
}
// Applying the cloud mask to dataset
// Cloud masking with simpleTDOM2 and cloudScore and QA60band
//var sentinel2_masked = simpleTDOM2(sentinel2.map(maskS2clouds)).select(selection_bands);
var sentinel2_masked = simpleTDOM2(sentinel2.map(bustClouds)).map(maskS2clouds).select(selection_bands);
//////////////////////////// Mosaicking Sentinel 2/////////////////////////////////////////////////////////
//Output: sentinel2_mosaics
var sentinel2_mosaics = mosaic_maker(sentinel2_masked);
//////////////////////////////////// Correlating and adjusting values ///////////////////////
//Output: adjusted_landsat_mosaics
//Output: adjusted_modis_mosaics
var correlation_adjuster = function(image) {
var time = image.get('system:time_start');
var sen2_image = sentinel2_mosaics.filterMetadata('system:time_start', 'equals', time).first();
//var sen2_image = sentinel2_mosaics.filterMetadata('system:time_start', 'not_less_than', time).first()
//get sen2_mean where sen2 overlaps with filler
var sen2_mean = sen2_image.select(selection_bands).updateMask(image.select(selection_bands)).reduceRegion({
reducer: ee.Reducer.median(), ///Consider mean instead of median
geometry: clip_feature,
scale: 1000,
tileScale: 16,
bestEffort: true
//maxPixels: 1e9
});
//get filler_mean where filler overlaps with sen2
var filler_mean = image.select(selection_bands).updateMask(sen2_image.select(selection_bands)).reduceRegion({
reducer: ee.Reducer.median(),
geometry: clip_feature,
scale: 1000,
tileScale: 16,
//maxPixels: 1e9
bestEffort: true
});
var multiplication_factor_NDVI = ee.Number(sen2_mean.get('NDVI')).divide(ee.Number(filler_mean.get('NDVI')));
var multiplication_factor_red = ee.Number(sen2_mean.get('red')).divide(ee.Number(filler_mean.get('red')));
//var multiplication_factor_green = ee.Number(sen2_mean.get('green')).divide(ee.Number(filler_mean.get('green')))
var multiplication_factor_blue = ee.Number(sen2_mean.get('blue')).divide(ee.Number(filler_mean.get('blue')));
var multiplication_factor_nir = ee.Number(sen2_mean.get('nir')).divide(ee.Number(filler_mean.get('nir')));
var multiplication_factor_swir2 = ee.Number(sen2_mean.get('swir2')).divide(ee.Number(filler_mean.get('swir2')));
var ndvi = image.select('NDVI').multiply(ee.Image(multiplication_factor_NDVI)).set('system:time_start', time);
var red = image.select('red').multiply(ee.Image(multiplication_factor_red));
//var green = image.select('green').multiply(ee.Image(multiplication_factor_green))
var blue = image.select('blue').multiply(ee.Image(multiplication_factor_blue));
var nir = image.select('nir').multiply(ee.Image(multiplication_factor_nir));
var swir2 = image.select('swir2').multiply(ee.Image(multiplication_factor_swir2));
return ee.Algorithms.If(filler_mean.get('NDVI'), ee.Image(ndvi).addBands(ee.Image(blue)).addBands(ee.Image(red)).addBands(ee.Image(nir)).addBands(ee.Image(swir2)), ee.Algorithms.If(image.get('type'), image, null )); //OBS: type refers to a property specifying MODIS or nothing //.addBands(ee.Image(green))
};
var adjusted_landsat_mosaics = landsat_mosaics.map(correlation_adjuster, true);
var adjusted_modis_mosaics = modis_mosaics.map(correlation_adjuster, true);
////////////////////////////////// Gap filling Sentinel 2 //////////////////////////////////
//Output: sentinel2_optical_filled
//Output: sentinel2_NDVI_filled
// Filling gaps with Landsat
var sentinel2_mosaics_landsatFill = sentinel2_mosaics.map(function(image) {
var time_stamp = ee.Date(image.get('system:time_start'));
var landsat_filler = adjusted_landsat_mosaics.filterMetadata('system:time_start', 'equals', time_stamp).first();
return ee.Algorithms.If(landsat_filler, image.select(selection_bands).unmask(landsat_filler.select(selection_bands).toFloat()), image.select(selection_bands).toFloat());
}, true);
// Filling gaps with MODIS
var sentinel2_modisFill = sentinel2_mosaics_landsatFill.map(function(image) {
var time_stamp = ee.Date(image.get('system:time_start'));
var modis_filler = adjusted_modis_mosaics.filterMetadata('system:time_start', 'equals', time_stamp).first().toFloat(); //filterDate(time_stamp, end)
return image.select(selection_bands).unmask(modis_filler).select(selection_bands);
}); //, true
//////////////////////////// Processing Sentinel 1 /////////////////////////////////////////////////////////
//Output: sentinel1_mosaics
// Composites
var months = ee.List.sequence(1, 331, 30);
var sentinel1_mosaics_unfiltered = ee.ImageCollection(months.map(function(m) {
var time_period = sentinel1.filter(ee.Filter.calendarRange({
start: m,
end: ee.Number(m).add(30),
field: 'day_of_year'
}));
var composite = ee.Image(time_period.mean());
return composite.set('system:time_start', m).clip(clip_feature);
}));
//Filters out images outside of current temporal scope
var sentinel1_mosaics = sentinel1_mosaics_unfiltered.map(function(image) {
return ee.Algorithms.If(ee.Number(image.bandNames().size().gt(0)), image.set('approved','true'), image.set('approved','false'));
}).filterMetadata('approved', 'equals', 'true');
// Calculating VH/VV ratio
//var sen1_ratio = sentinel1_mosaics.map(function(image){
// return image.select('VH').divide(image.select('VV')).select(['VH'], ['ratio']).copyProperties(image, ['system:time_start']);
//});
//////////////////////////////////////// Seasonality metrics /////////////////////////////////////////
//output: metrics
// Optical metrics
var max = ee.Image(sentinel2_modisFill.select('NDVI').reduce(ee.Reducer.max()));
var min = ee.Image(sentinel2_modisFill.select('NDVI').reduce(ee.Reducer.min()));
var amp = max.subtract(min);
var std = ee.Image(sentinel2.select('NDVI').reduce(ee.Reducer.stdDev()));
// SAR metrics
var vv_max = ee.Image(sentinel1.select('VV').reduce(ee.Reducer.max()));
var vv_min = ee.Image(sentinel1.select('VV').reduce(ee.Reducer.min()));
var vv_amp = vv_max.subtract(vv_min);
var vv_std = ee.Image(sentinel1.select('VV').reduce(ee.Reducer.stdDev()));
var metrics = ee.Image.cat([max, min, amp, std, vv_max, vv_min, vv_amp, vv_std]).select(['NDVI_max', 'NDVI_min', 'NDVI_max_1', 'NDVI_stdDev', 'VV_max', 'VV_min', 'VV_max_1', 'VV_stdDev'],['max', 'min', 'amp', 'std', 'vv_max', 'vv_min', 'vv_amp', 'vv_std']);
/////////////////////////////// Texture ////////////////////////////// //not normalized!!
//sen1_texture
//sen1_entropy
var sen1_texture = sentinel1_mosaics.map(function(image){
var img = image.select('VV').multiply(ee.Image(100)).toInt();
var square = ee.Kernel.square({radius: 4});
var entropy = img.entropy(square).select(['VV'],['VV_entropy']); //entropy
var glcm = img.glcmTexture({size: 4});
var inertia = glcm.select('VV_inertia'); //inertia
return entropy.addBands(inertia);
});
/////////////////////////////////// NDWI /////////////////////////////////////////////////////
var ndwi = sentinel2_modisFill.map(function(image){
return image.normalizedDifference(['nir','swir2']).select(['nd'], ['ndwi']);
});
/////////////////////////////////// COPERNICUS LAND COVER PRODUCT //////////////////////////////
// Erosion followed by a dilation
var kernel = ee.Kernel.circle({radius: 5});
var opened = cop_lc
.focal_min({kernel: kernel, iterations: 2})
.focal_max({kernel: kernel, iterations: 2});
// Dilation followed by erosion
var open_closed = opened
.focal_max({kernel: kernel, iterations: 2})
.focal_min({kernel: kernel, iterations: 2});
var cop_lc_combined = ee.ImageCollection([cop_lc.select(['b1'],['cop_lc']), open_closed.select(['b1'],['cop_lc_morphed'])]);
/////////////////////////////// Layer Stacking ////////////////////////////////////////////
//Output: layer_stack
//Datasets to be combined:
//sentinel1_mosaics
//sentinel2_modisFill
//metrics
//sen1_ratios
//sen1_texture
//ndwi
var image_collection = sentinel1_mosaics.merge(sentinel2_modisFill).merge(metrics).merge(ndwi).merge(cop_lc_combined).merge(sen1_texture); //.select('VV') .merge(cop_lc_combined) .merge(sen1_texture)
// Layer stack maker
var stackCollection = function(image_collection) {
var first = ee.Image(image_collection.first()).select([]);
var appendBands = function(image, previous) { //Previous = result of previous iteration, NOT previous image
return ee.Image(previous).addBands(image);
};
return ee.Image(image_collection.iterate(appendBands, first)); //First is the starting image to be used as starting point for adding onto for each interation
};
var layer_stack = stackCollection(image_collection).clip(clip_feature); //.select(layer_selection)
///////////////////////////////////// Sampling //////////////////////////////////////
//Output: training_sample
// Run this section (Sampling) first, then Classification later
//var training_sample = sample_asset.select(feature_selection)
var training_sample = layer_stack.sampleRegions({
collection: training_points,
properties: ['class'],
scale: 10,
tileScale: 16
});
// Use the following if the sample is too large (slower but reduces memory requirements)
// var training_sample = training_points.map(function(feature) {
// return layer_stack.sample({
// region: ee.Feature(feature).geometry(),
// scale: 10,
// tileScale: 16,
// geometries: true //add geometries prior to exporting sample
// }).first().set('class', feature.get('class'))});
Export.table.toDrive(training_points.map(function(feature) {
return layer_stack.sample({
region: ee.Feature(feature).geometry(),
scale: 10,
tileScale: 16,
geometries: true
}).first().set('class', feature.get('class'))}));
///////////////////////////////////// CLASSIFICATION /////////////////////////////////
var classifier = ee.Classifier.randomForest(500);
var trained_classifier = classifier.train(training_sample, 'class', training_sample.first().propertyNames().remove('system:index'));
// Classify the layer_stack.
var classified = layer_stack.classify(trained_classifier).clip(clip_feature);
Export.image.toDrive({
image: classified,
description: '2017_D',
scale: 10,
region: clip_feature,
maxPixels: 2000000000000
});