-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcombineIceData.js
224 lines (186 loc) · 7.2 KB
/
combineIceData.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
const csvtojson = require('csvtojson');
const fs = require('fs');
const path = require('path');
const outputPath = path.join(__dirname, "../../../src/Data/ArcticIceData.json");
const MinAreaThreshold = 6.0;
/**
* @typedef {Object} IceDataItem
* @property {number} year data year
* @property {number} month data month
* @property {number} extent extent, in million sq km
* @property {number} area area, in million sq km
*/
/**
* Callback for filterYearlyMinMaxByProperty
*
* @callback filterByYearCallback
* @param {IceDataItem} min - the minimum data item
* @param {IceDataItem} max - the maximum data item
*/
/**
* Traverses input .csv files, combines the values in to a single sorted array
* then saves to `outputPath`
*/
const process = async () => {
let arcticIceData = {
data: [],
yearlyMinMaxArea: [], // The min and max entries for each year, sorted by time
yearlyMinMaxExtent: [],
yearlyMaxArea: [],
yearlyMaxExtent: [],
yearlyMinArea: [],
yearlyMinExtent: [],
minExtent: Number.MAX_VALUE,
maxExtent: Number.MIN_VALUE,
minArea: Number.MAX_VALUE,
maxArea: Number.MIN_VALUE,
};
arcticIceData.data = await getCsvArray();
filterYearlyMinMaxByProperty(arcticIceData.data, "area", (min, max) => {
if (min.area < MinAreaThreshold) {
arcticIceData.yearlyMinArea.push(min);
}
arcticIceData.yearlyMaxArea.push(max);
arcticIceData.yearlyMinMaxArea.push(min, max);
});
filterYearlyMinMaxByProperty(arcticIceData.data, "extent", (min, max) => {
if (min.extent < MinAreaThreshold) {
arcticIceData.yearlyMinExtent.push(min);
}
arcticIceData.yearlyMaxExtent.push(max);
arcticIceData.yearlyMinMaxExtent.push(min, max);
});
arcticIceData.yearlyMinArea.sort((a, b) => getDateHash(a.year, a.month) - getDateHash(b.year, b.month));
arcticIceData.yearlyMaxArea.sort((a, b) => getDateHash(a.year, a.month) - getDateHash(b.year, b.month));
arcticIceData.yearlyMinMaxArea.sort((a, b) => getDateHash(a.year, a.month) - getDateHash(b.year, b.month));
arcticIceData.yearlyMinExtent.sort((a, b) => getDateHash(a.year, a.month) - getDateHash(b.year, b.month));
arcticIceData.yearlyMaxExtent.sort((a, b) => getDateHash(a.year, a.month) - getDateHash(b.year, b.month));
arcticIceData.yearlyMinMaxExtent.sort((a, b) => getDateHash(a.year, a.month) - getDateHash(b.year, b.month));
// Find min max extent and area
for (let i = 0; i < arcticIceData.data.length; i++) {
const dataItem = arcticIceData.data[i];
arcticIceData.minExtent = Math.min(dataItem.extent, arcticIceData.minExtent);
arcticIceData.maxExtent = Math.max(dataItem.extent, arcticIceData.maxExtent);
arcticIceData.minArea = Math.min(dataItem.area, arcticIceData.minArea);
arcticIceData.maxArea = Math.max(dataItem.area, arcticIceData.maxArea);
}
assertOrder(arcticIceData.yearlyMinArea);
assertOrder(arcticIceData.yearlyMaxArea);
assertOrder(arcticIceData.yearlyMinMaxArea);
assertOrder(arcticIceData.yearlyMinExtent);
assertOrder(arcticIceData.yearlyMaxExtent);
assertOrder(arcticIceData.yearlyMinMaxExtent);
await fs.promises.writeFile(outputPath, JSON.stringify(arcticIceData));
}
/**
* Returns a string format for a given month
* @param {Number} month as a number, e.g 1, 2.. 12
*/
const getMonth = (month) => String("0" + month).slice(-2);
/**
* Reads Arctic Snow/Ice data CSV files and combines in to a single, sorted array
*
* @returns CSV data array, sorted by chronologically
*/
const getCsvArray = async () => {
let dataArray = [];
// Read CSV and combine in to a single array
for (let i = 1; i <= 12; i++) {
const month = getMonth(i);
const filepath = path.join(__dirname, `./N_${month}_extent_v3.0.csv`);
const json = await csvtojson().fromFile(filepath);
// Stick all data points in to a single array and then sort later
dataArray = dataArray.concat(json);
}
// Convert year, mo(nth), area and extent to numbers
dataArray = dataArray.map(dataItem => ({
year: parseInt(dataItem.year),
month: parseInt(dataItem.mo),
extent: parseFloat(dataItem.extent),
area: parseFloat(dataItem.area)
}));
// Sort
dataArray.sort((a, b) => getDateHash(a.year, a.month) - getDateHash(b.year, b.month));
return dataArray;
}
// /**
// * Traverses {dataArray} for min/max entries of a {property} for a given year.
// * @param {*} dataArray
// * @param {*} property
// *
// * @returns Sorted array (by time) containing min/max entries for a given year
// */
// const getMinMaxForProperty = (dataArray, property) => {
// const minMaxarray = [];
// // Find min/max entries for each year
// let prevDataItem = dataArray[0];
// let minAreaData = prevDataItem;
// let maxAreaData = prevDataItem;
// for (let i = 1; i < dataArray.length; i++) {
// const dataItem = dataArray[i];
// if (prevDataItem.year !== dataItem.year) {
// minMaxarray.push(maxAreaData, minAreaData);
// minAreaData = dataItem;
// maxAreaData = dataItem;
// }
// if (dataItem[property] < minAreaData[property]) {
// minAreaData = dataItem;
// }
// if (dataItem[property] > maxAreaData[property]) {
// maxAreaData = dataItem;
// }
// prevDataItem = dataItem;
// }
// minMaxarray.sort((a, b) => getDateHash(a.year, a.month) - getDateHash(b.year, b.month));
// return minMaxarray;
// }
/**
* Traverses data array for min / max of a given property.
* Callback is called for each year traversed with ({min, max})
* @param {*} dataArray
* @param {*} property
* @param {filterByYearCallback} filterCallback
* @returns
*/
const filterYearlyMinMaxByProperty = (dataArray, property, filterCallback) => {
// Find min/max entries for each year
let prevDataItem = dataArray[0];
let minData = prevDataItem;
let maxData = prevDataItem;
for (let i = 1; i < dataArray.length; i++) {
const dataItem = dataArray[i];
if (prevDataItem.year !== dataItem.year) {
filterCallback(minData, maxData);
minData = dataItem;
maxData = dataItem;
}
if (dataItem[property] < minData[property]) {
minData = dataItem;
}
if (dataItem[property] > maxData[property]) {
maxData = dataItem;
}
prevDataItem = dataItem;
}
filterCallback(minData, maxData);
}
/**
* Returns a year + month hash for comparison.
*
* e.g '1979', '1' becomes 197901
* @param {Number} year
* @param {Number} month
* @returns year/month Number for comparison
*/
const getDateHash = (year, month) => year * 100 + month;
const assertOrder = (dataArrar) => {
// Assert order
for (let i = 0; i < dataArrar.length - 1; i++) {
const a = dataArrar[i];
const b = dataArrar[i + 1];
const compNumA = getDateHash(a.year, a.month);
const compNumB = getDateHash(b.year, b.month);
console.assert(compNumA <= compNumB, `a: ${compNumA}, b: ${compNumB}`);
}
}
process();