-
Notifications
You must be signed in to change notification settings - Fork 26
/
rgb-timeseries.js
216 lines (198 loc) · 7.63 KB
/
rgb-timeseries.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
/**
* @license
* Copyright 2021 Justin Braaten
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.version = '0.1.3';
/**
* Converts RGB component integer to hex string.
*
* @param {Number} c An integer between 0 and 255 that represents color
* intensity.
* @returns {String}
* @ignore
*/
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? '0' + hex : hex;
}
/**
* Converts RGB integer set to hex string.
*
* @param {Array} rgb Array of three integers with range [0, 255] that
* respectively represent red, green, and blue intensity.
* @returns {String}
* @ignore
*/
function rgbToHex(rgb) {
return "#" +
componentToHex(rgb[0]) +
componentToHex(rgb[1]) +
componentToHex(rgb[2]);
}
/**
* Scales input number to 8-bit range.
*
* @param {Number} val A value to clamp between a given range and scale to
* 8-bit representation.
* @param {Number} min The minimum value to clamp the input number to.
* @param {Number} max The maximum value to clamp the input number to.
* @returns {Number}
* @ignore
*/
function scaleToByte(val, min, max) {
val = ee.Number(val).clamp(min, max);
return ee.Number.expression({
expression: 'round((val - min) / (max - min) * 255)',
vars: {
val: val,
min: min,
max: max
}
});
}
/**
* Plots a chart to a ui.Panel or the Code Editor Console for a multi-band image
* time series. Observations are represented as circles whose color is the
* stretched RGB representation of three selected bands.
*
* @param {ee.ImageCollection} col An image collection representing a time
* series of multi-band images. Each image must have a 'system:time_start'
* property formatted as milliseconds since the 1970-01-01T00:00:00Z (UTC).
* @param {ee.Geometry} aoi The region over which to reduce the image data.
* @param {String} yAxisBand The name of the image band whose region reduction
* will be plot along the chart's y-axis.
* @param {Object} visParams Visualization parameters that assign bands to
* red, green, and blue and the range to stretch color intensity over.
* @param {Array} visParams.bands An array of three band names to respectively
* assign to red, green, and blue for RGB visualization.
* @param {Array} visParams.min An array of three band-specific values that
* define the minimum value to clamp the color stretch range to. Arrange the
* values in the same order as visParams.bands band names. Use units of the
* input image data.
* @param {Array} visParams.max An array of three band-specific values that
* define the maximum value to clamp the color stretch range to. Arrange the
* values in the same order as visParams.bands band names. Use units of the
* input image data.
* @param {ui.Panel|String} plotHere Either a ui.Panel to add the chart to or
* 'console' to print the chart to the Code Editor console.
* @param {Object} [optionalParams] Optional. A set of optional parameters to set for
* controling region reduction and stying the chart.
* @param {ee.Reducer} [optionalParams.reducer] Optional. The region over which
* to reduce data. If unspecified, ee.Reducer.first is used.
* @param {String} [optionalParams.crs] Optional. The projection to work in. If
* unspecified, the projection of the first image is used.
* @param {Number} [optionalParams.scale] Optional. A nominal scale in meters of
* the projection to work in. If unspecified, the nominal scale of the first
* image is used.
* @param {Object} [optionalParams.chartParams] Optional. ui.Chart parameters
* accepected by ui.Chart.setOptions. See
* https://developers.google.com/earth-engine/guides/charts_style for
* more details.
*/
function rgbTimeSeriesChart(
col, aoi, yAxisBand, visParams, plotHere, optionalParams) {
// Since using evaluate, indicate that things are working.
var message = '⚙️ Processing, please wait.';
var gears = 'https://fonts.gstatic.com/s/i/short-term/release/materialsymbolssharp/settings/wght100grad200fill1/24px.svg';
var waitMsgImgPanel = ui.Panel([ui.Label({imageUrl: gears}), ui.Label({
value: 'Processing, please wait.',
style: {fontSize: '16px', fontWeight: 'bold', margin: '10px 0px 0px 0px'}
})], ui.Panel.Layout.flow('horizontal'), {stretch: 'horizontal'});
if(plotHere != 'console') {
plotHere.clear();
plotHere.add(waitMsgImgPanel); // ui.Label(message)
} else {
print(message);
}
// Define default filter parameters.
var proj = col.first().projection();
var _params = {
reducer: ee.Reducer.first(),
crs: proj.crs(),
scale: proj.nominalScale(),
chartParams: {
pointSize: 10,
legend: {position: 'none'},
hAxis: {title: 'Date', titleTextStyle: {italic: false, bold: true}},
vAxis: {title: yAxisBand, titleTextStyle: {italic: false, bold: true}},
interpolateNulls: true,
},
chartStyle: {
height: null
}
};
// Replace default params with provided params.
if (optionalParams) {
for (var param in optionalParams) {
_params[param] = optionalParams[param] || _params[param];
}
}
// Perform reduction.
print('col', col);
var fc = col.map(function(img) {
var reduction = img.reduceRegion({
reducer: _params.reducer,
geometry: aoi,
scale: _params.scale,
crs: _params.crs,
bestEffort: true,
maxPixels: 1e13,
});
return ee.Feature(null, reduction).set({
'system:time_start': img.get('system:time_start'),
label: ee.String(yAxisBand+' ').cat(img.date().format('YYYY-MM-dd'))
});
})
//.filter(ee.Filter.notNull(col.first().bandNames()));
print('fc', fc)
print('visParams', visParams)
// Add 3-band RGB color as a feature property.
var fcRgb = fc.map(function(ft) {
var rgb = ee.List([
scaleToByte(ft.get(visParams.bands[0]), visParams.min[0], visParams.max[0]),
scaleToByte(ft.get(visParams.bands[1]), visParams.min[1], visParams.max[1]),
scaleToByte(ft.get(visParams.bands[2]), visParams.min[2], visParams.max[2])
]);
return ft.set({rgb: rgb, 'system:time_start': ft.get('system:time_start')});
});
// Filter out observations with no data.
fcRgb = fcRgb.filter(ee.Filter.notNull(fcRgb.first().propertyNames()))
.sort('system:time_start');
// Get the list of RGB colors.
var rgbColors = fcRgb.aggregate_array('rgb');
// Make a chart.
rgbColors.evaluate(function(rgbColors) {
var rgbList = [];
for(var i=0; i<rgbColors.length; i++) {
rgbList.push(rgbToHex(rgbColors[i]));
}
_params.chartParams['colors'] = rgbList;
var chart = ui.Chart.feature.groups(
fcRgb, 'system:time_start', yAxisBand, 'label')
.setChartType('ScatterChart')
.setOptions(_params.chartParams);
chart.style().set(_params.chartStyle);
if(plotHere != 'console'){
plotHere.clear();
plotHere.add(chart);
} else {
print(chart);
}
});
}
exports.rgbTimeSeriesChart = rgbTimeSeriesChart;
// Example points
var geometry1 = ee.Geometry.Point([-122.167503, 44.516868]).buffer(45); // forest harvest
var geometry2 = ee.Geometry.Point([-122.201595, 44.511052]).buffer(45); // cool stream bank