-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWallpaper_Maker
More file actions
255 lines (246 loc) · 11.7 KB
/
Wallpaper_Maker
File metadata and controls
255 lines (246 loc) · 11.7 KB
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
/**** Start of imports. If edited, may not auto-convert in the playground. ****/
var Screen1992x1121Ratio16x9 = /* color: #e2e2e2 */ee.Geometry.Polygon(
[[[-3.3220212403222416, 51.5531770278373],
[-3.3220212403222416, 51.42790920999614],
[-2.964278930751929, 51.42790920999614],
[-2.964278930751929, 51.5531770278373]]], null, false);
/***** End of imports. If edited, may not auto-convert in the playground. *****/
// #### SCRIPT START ####
// ##################
// ### PARAMETERS ###
// ##################
// These apply the parameters to all imagery, to stop redundant data entry
// ## CLOUD ##
// Format: %'0-000'. Numbers only without quotes
// # MIN CLOUD PERCENT #
var MIN_CLOUD_PERCENT = 10;
/* Deals with TILES - Tiles with >X% of cloud by area are filtered out
* This reduces the frequency of available images, by removing ones that might not be useful for further analysis
* Set to 100 to not filter at all, and put the heavy lifting onto Masking cloud probability instead
*/
// # MAX CLOUD PROBABILITY #
var MAX_CLOUD_PROB = 100;
/* Deals with clouds IN-IMAGE - Areas that have >Y% probability of being a cloud are masked out within the tile
* This maximises frequency of images, but at the cost of requiring more processing to analyse every available image
* Lower numbers increases the range of criteria that match 'clouds' to mask
* E.g. 100% will miss cloud edges & cirrus, 10% will start masking out roofing and water bodies. 20 is a compromise
*/
// ## START/END DATE ##
// Format: 'YYYY-MM-DD', include the quotes. This for the time series range.
var START_DATE = '2018-01-01';
var END_DATE = '2025-01-01';
// ## BOUNDS ##
var BOUNDS = Screen1992x1121Ratio16x9;
// Format: Either be a drawn geometry shape within GEE, or an imported SHP file
// See: https://developers.google.com/earth-engine/guides/table_upload#upload-a-shapefile
// ## ZOOM ##
// Based on GEE's 1-24 level system. Larger number = More Zoom
var ZOOM = 11;
// ## PREVIEW DATES ##
var FRST_PRVW_DATE = '2023-01-01';
var LAST_PRVW_DATE = '2025-01-01';
/* For displaying a particular date on the map, as otherwise the Layers don't match
* To display a particular image on a particular time, select the day before and after it
* i.e. "2022-03-31" requires the filter to be set between 03-30 and 04-01
* This ONLY applies to displayed imagery on the map - The entire time series data is still exportable
*/
// PARAMETERS END - You don't need to change anything below this line to make the script function
// ############
// ### MISC ###
// ############
// Prints out the Parameters set
print('Running parameters set on Lines 10-50:');
print('Filtering available Sentinel 2 imagery between ' + START_DATE + ' & ' + END_DATE + '.');
print(' Tiles are filtered by '+ MIN_CLOUD_PERCENT + '%, and pixels matching >' + MAX_CLOUD_PROB + '% of being clouds, are masked' );
print('Map will display imagery from between ' + FRST_PRVW_DATE + ' & ' + LAST_PRVW_DATE + '.');
// Centre based on the Geometry (Region of Interest, Zoom Level)
Map.centerObject(BOUNDS, ZOOM, print('Map centered on Region of Interest'));
// Sets the default Map to Terrain mode (Roadmap overlain with hillsahde)
Map.setOptions("TERRAIN");
// #################################
// ### PALETTES & VISUALISATIONS ###
// #################################
// ## EEPALETTES ##
/* Required for most layer visualisations
* See https://github.com/gee-community/ee-palettes for more information
* IF IT FAILS TO LOAD the PALETTES, LOAD THIS URL FIRST, THEN REFRESH THE PAGE:
* (https://code.earthengine.google.com/?accept_repo=users/gena/packages)
*/
var palettes = require('users/gena/packages:palettes');
// NDWI palette
var NDWIPalette = palettes.cmocean.Ice[7].reverse();
// NDVI palette
var NDVIPalette = palettes.colorbrewer.RdYlGn[10];
// ## VISUALISATIONS ##
// # UNMODIFIABLE #
// Don't change these
// Truecolour (R-G-B) Visualisation (for Sentinel 2 Imagery only)
var RGBVis = {
min: 0,
max: 0.3,
bands: ['B4', 'B3', 'B2'],
};
// Falsecolour (IR-G-B) Visualisation (for S2 - Not in use, but available)
var NIRVis = {
min: 0,
max: 0.5,
bands: ['B8', 'B3', 'B2'],
};
// NDWI Visualisation
var NDWIVis = {
min: -1,
max: 1,
bands: ['NDWI'],
palette: NDWIPalette
};
// NDVI Visualisation
var NDVIVis = {
min: -1,
max: 1,
bands: ['NDVI'],
palette: NDVIPalette
};
// #####################
// ### CLOUD MASKING ###
// #####################
/* Sentinel 2 Cloud Masking Function using the QA_60m Cloud Mask Band
* @param {ee.Image} image Sentinel-2 image
* @return {ee.Image} cloud masked Sentinel-2 image
* (Applies to L1C and L2A Imagery)
*/
function maskS2clouds(image) {
var qa = image.select('QA60');
// Bits 10 and 11 are clouds and cirrus, respectively.
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
// Both flags should be set to zero, indicating clear conditions.
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(qa.bitwiseAnd(cirrusBitMask).eq(0));
// Applies the cloud masking conditions to the IC, and adds the date of acquisition into each image for easy timeseries organising later
return image.updateMask(mask).divide(10000).copyProperties(image, ["system:time_start"]);
}
print('Sentinel 2 Cloud Mask Function Complete');
// #########################
// ### IMAGE COLLECTIONS ###
// #########################
//Load and Map L1C/L2A imagery with the filter parameters applied
/*
* Load Sentinel-2 'Harmonized' Bottom Of Atomsphere (L2A) data
* Dataset details: https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S2_SR_HARMONIZED
* HARMONIZED makes sure scenes after 25 January 2022 have the same DN ranges as older L2A scenes.
* Data is limited to 2017-03-28 onwards. (i.e. older data has to come from non-harmonised datasets)
*/
var S2_L2A = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
.select('B2','B3', 'B4', 'B8','B8A','B9', 'QA60')
// Filter by Date Period (YYYY-MM-DD)
.filterDate(START_DATE, END_DATE)
/*
* Pre-filter to get less cloudy granules
* 'Default' is aiming for 10% cloud
* Dependent on availability of cloud-free imagery in the time period set
* Longer periods will take longer to load
*/
.filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', MIN_CLOUD_PERCENT))
// Select only image tiles that fall within the Geometry (Region of Interest) to reduce processing time
.filterBounds(BOUNDS)
// Applies the S2 Cloud Masking Function to each image in the IC
.map(maskS2clouds)
// Clips each image in the IC by the Bounds to reduce processing time further
.map(function(image) {
return image.clip(BOUNDS);
});
// ## ALTERNATE L2A MASKING ##
var S2_CloudProb = ee.ImageCollection('COPERNICUS/S2_CLOUD_PROBABILITY');
function maskClouds(image) {
var clouds = ee.Image(image.get('cloud_mask')).select('probability');
var isNotCloud = clouds.lt(MAX_CLOUD_PROB);
return image.updateMask(isNotCloud);//.divide(10000).copyProperties(image, ["system:time_start"]);
}
// The masks for the 10m bands sometimes do not exclude bad data at
// scene edges, so this applies masks from the 20m and 60m bands as well.
function maskEdges(s2_image) {
return s2_image.updateMask(
s2_image.select('B8A').mask().updateMask(s2_image.select('B9').mask()));
}
// Filter input collections by desired data range and region.
var criteria = ee.Filter.and(
ee.Filter.bounds(BOUNDS), ee.Filter.date(START_DATE, END_DATE));
S2_L2A = S2_L2A.filter(criteria).map(maskEdges);
S2_CloudProb = S2_CloudProb.filter(criteria);
// Join S2 SR with cloud probability dataset to add cloud mask.
var S2SRWithCloudMask = ee.Join.saveFirst('cloud_mask').apply({
primary: S2_L2A,
secondary: S2_CloudProb,
condition:
ee.Filter.equals({leftField: 'system:index', rightField: 'system:index'})
});
var S2CloudMasked =
ee.ImageCollection(S2SRWithCloudMask).map(maskClouds);
print('Time, Date, Bounding, Clipping, and Cloud Tile Filtering parameters set for Imagery');
// Map Collections to View
print('L2A Collection',S2_L2A);
print('L2A Collection Cloud Masked',S2CloudMasked);
Map.addLayer(S2CloudMasked.filterDate(FRST_PRVW_DATE, LAST_PRVW_DATE), RGBVis,'L2A ClearSkies',false);
Map.addLayer(S2CloudMasked.filterDate(FRST_PRVW_DATE, LAST_PRVW_DATE), NIRVis,'L2A Aerochrome',false);
// ###############
// ### BUTTONS ###
// ###############
// Create a panel for Buttons with vertical flow layout.
var ButtonPanel = ui.Panel({
layout: ui.Panel.Layout.flow('vertical'),
style: {width: '10%'}
});
// ## EXPORT LAYERS BUTTON ##
ButtonPanel.add(ui.Button({
label: 'Export Image',
style: {stretch: 'horizontal', Color: '#1e90ff'},
onClick: function() {
// # PROPERTY EXTRACTION #
// For naming exported files by the system:index (identifier)
// Filtering the timeseries range to just a particular day (i.e. 2022-03-31)
var S2_L2A_Filt = S2_L2A.filterDate(FRST_PRVW_DATE, LAST_PRVW_DATE);
// Pulls out the System:Index Property of every element in the Filtered IC, puts it in a dictionary, and inserts it into the Properties
var SystemIndex = S2_L2A_Filt.map(function (image) {
return image.set('SysIndex', image.toDictionary(['system:index']));
});
// Aggregates the Dictionary into an Array (List)
var list = SystemIndex.aggregate_array('SysIndex');
// Gets only the first Element of the List
var list_1st = list.get(0);
// # MOSAICING #
// Mosaics the current filterered view into a single image, for exporting
var S2_Median = S2CloudMasked.reduce(ee.Reducer.median()).set(list_1st);
print(S2_Median);
var S2_L2A_Mosaic = S2_Median
.select(['B2_median','B3_median','B4_median','B8_median','B8A_median','B9_median','QA60_median'],
['B2','B3','B4','B8','B8A','B9','QA60']);
//var S2_L2A_Mosaic = S2CloudMasked.mosaic().set(list_1st);
// Display the Layers that are to be exported (for final confirmation of download)
Map.addLayer(S2_L2A_Mosaic, RGBVis, 'L2A Export');
Map.addLayer(S2_L2A_Mosaic, NIRVis, 'L2A Aerochrome Export');
print('L2A Mosaic',S2_L2A_Mosaic);
// # PROPERTY APPLICATION #
// Gets the System:Index of the Mosaic as a String
var index = S2_L2A_Mosaic.get('system:index');
/* Sentinel 2 Imagery follows this naming convention:
* START TIME:'(YYYYMMDD)T(HHMMSS)_' + END TIME:'(YYYYMMDD)T(HHMMSS)_' + UTM MGRS ID: 'T00ABC'
* So slicing it to the first 13 characters gives you the swath start time, to the minute
* This is extracted using getInfo() which can then be put into the image name.
* Only has to be done on one of the Mosaic layers, as the system:index will be the same for all of them
*/
var index = index.getInfo().slice(0,13);
// # EXPORTS #
Export.image.toDrive({
image: S2_L2A_Mosaic.visualize(RGBVis), // Sets the image to keep the current Visualisation parameters
region: BOUNDS, // The area of interest
description: 'LOCATION_' + index, // Sets the name of the image(s) with the date
folder: 'GEE_Wallpapers', // The destination folder for the imagery in your Account's Google Drive
scale: '20', // scale in m/px. 'Nominal' returns requested image AT TRUE SCALE
crs: 'EPSG:3857', // CRS - This is the standard GEE Web Mercator projection, WGS84.
maxPixels: 1e13, // Max Pixel amount - always required as the default limit is very small
fileFormat: 'GeoTIFF'
});
print('Please check the Tasks tab to confirm Export');
}
}));
// Adds the Button Panel to the Map
ui.root.add(ButtonPanel);