UNPKG

11.1 kBJavaScriptView Raw
1import { COVERAGE, DOMAIN } from '../constants.js'
2import { checkCoverage } from '../validate.js'
3import { shallowcopy } from '../util.js'
4import { addLoadRangesFunction } from './create.js'
5
6/**
7 * Returns a copy of the given Coverage object with the parameters
8 * replaced by the supplied ones.
9 *
10 * Note that this is a low-level function and no checks are done on the supplied parameters.
11 */
12export function withParameters (cov, params) {
13 let newcov = {
14 type: COVERAGE,
15 domainType: cov.domainType,
16 parameters: params,
17 loadDomain: () => cov.loadDomain(),
18 loadRange: key => cov.loadRange(key),
19 loadRanges: keys => cov.loadRanges(keys),
20 subsetByIndex: constraints => cov.subsetByIndex(constraints).then(sub => withParameters(sub, params)),
21 subsetByValue: constraints => cov.subsetByValue(constraints).then(sub => withParameters(sub, params))
22 }
23 return newcov
24}
25
26/**
27 * Returns a copy of the given Coverage object with the categories
28 * of a given parameter replaced by the supplied ones and the encoding
29 * adapted to the given mapping from old to new.
30 *
31 * @param {Coverage} cov The Coverage object.
32 * @param {String} key The key of the parameter to work with.
33 * @param {object} observedProperty The new observed property including the new array of category objects
34 * that will be part of the returned coverage.
35 * @param {Map<String,String>} mapping A mapping from source category id to destination category id.
36 * @returns {Coverage}
37 */
38export function withCategories (cov, key, observedProperty, mapping) {
39 /* check breaks with Babel, see https://github.com/jspm/jspm-cli/issues/1348
40 if (!(mapping instanceof Map)) {
41 throw new Error('mapping parameter must be a Map from/to category ID')
42 }
43 */
44 checkCoverage(cov)
45 if (observedProperty.categories.some(c => !c.id)) {
46 throw new Error('At least one category object is missing the "id" property')
47 }
48 let newparams = shallowcopy(cov.parameters)
49 let newparam = shallowcopy(newparams.get(key))
50 newparams.set(key, newparam)
51 newparams.get(key).observedProperty = observedProperty
52
53 let fromCatEnc = cov.parameters.get(key).categoryEncoding
54 let catEncoding = new Map()
55 let categories = observedProperty.categories
56 for (let category of categories) {
57 let vals = []
58 for (let [fromCatId, toCatId] of mapping) {
59 if (toCatId === category.id && fromCatEnc.has(fromCatId)) {
60 vals.push(...fromCatEnc.get(fromCatId))
61 }
62 }
63 if (vals.length > 0) {
64 catEncoding.set(category.id, vals)
65 }
66 }
67 newparams.get(key).categoryEncoding = catEncoding
68
69 let newcov = withParameters(cov, newparams)
70 return newcov
71}
72
73/**
74 * Returns a new coverage where the domainType field of the coverage and the domain
75 * is set to the given one.
76 *
77 * @param {Coverage} cov The Coverage object.
78 * @param {String} domainType The new domain type.
79 * @returns {Coverage}
80 */
81export function withDomainType (cov, domainType) {
82 checkCoverage(cov)
83
84 let domainWrapper = domain => {
85 let newdomain = {
86 type: DOMAIN,
87 domainType,
88 axes: domain.axes,
89 referencing: domain.referencing
90 }
91 return newdomain
92 }
93
94 let newcov = {
95 type: COVERAGE,
96 domainType,
97 parameters: cov.parameters,
98 loadDomain: () => cov.loadDomain().then(domainWrapper),
99 loadRange: key => cov.loadRange(key),
100 loadRanges: keys => cov.loadRanges(keys),
101 subsetByIndex: constraints => cov.subsetByIndex(constraints).then(sub => withDomainType(sub, domainType)),
102 subsetByValue: constraints => cov.subsetByValue(constraints).then(sub => withDomainType(sub, domainType))
103 }
104 return newcov
105}
106
107/**
108 * Tries to transform the given Coverage object into a new one that
109 * conforms to one of the CovJSON domain types.
110 * If multiple domain types match, then the "smaller" one is preferred,
111 * for example, Point instead of Grid.
112 *
113 * The transformation consists of:
114 * - Setting domainType in coverage and domain object
115 * - Renaming domain axes
116 *
117 * @see https://github.com/Reading-eScience-Centre/coveragejson/blob/master/domain-types.md
118 *
119 * @param {Coverage} cov The Coverage object.
120 * @returns {Promise<Coverage>}
121 * A Promise succeeding with the transformed coverage,
122 * or failing if no CovJSON domain type matched the input coverage.
123 */
124export function asCovJSONDomainType (cov) {
125 return cov.loadDomain().then(domain => {
126
127 // TODO implement me
128
129 })
130}
131
132/**
133 * @example
134 * var cov = ...
135 * var mapping = new Map()
136 * mapping.set('lat', 'y').set('lon', 'x')
137 * var newcov = CovUtils.renameAxes(cov, mapping)
138 *
139 * @param {Coverage} cov The coverage.
140 * @param {Map<String,String>} mapping
141 * @returns {Coverage}
142 */
143export function renameAxes (cov, mapping) {
144 checkCoverage(cov)
145 mapping = new Map(mapping)
146 for (let axisName of cov.axes.keys()) {
147 if (!mapping.has(axisName)) {
148 mapping.set(axisName, axisName)
149 }
150 }
151
152 let domainWrapper = domain => {
153 let newaxes = new Map()
154 for (let [from, to] of mapping) {
155 let {dataType, coordinates, values, bounds} = domain.axes.get(from)
156 let newaxis = {
157 key: to,
158 dataType,
159 coordinates: coordinates.map(c => mapping.has(c) ? mapping.get(c) : c),
160 values,
161 bounds
162 }
163 newaxes.set(to, newaxis)
164 }
165
166 let newreferencing = domain.referencing.map(({coordinates, system}) => ({
167 coordinates: coordinates.map(c => mapping.has(c) ? mapping.get(c) : c),
168 system
169 }))
170
171 let newdomain = {
172 type: DOMAIN,
173 domainType: domain.domainType,
174 axes: newaxes,
175 referencing: newreferencing
176 }
177 return newdomain
178 }
179
180 // pre-compile for efficiency
181 // get({['lat']: obj['y'], ['lon']: obj['x']})
182 let getObjStr = [...mapping].map(([from, to]) => `['${from}']:obj['${to}']`).join(',')
183
184 let rangeWrapper = range => {
185 let get = new Function('range', 'return function get (obj){return range.get({' + getObjStr + '})}')(range) // eslint-disable-line
186 let newrange = {
187 shape: new Map([...range.shape].map(([name, len]) => [mapping.get(name), len])),
188 dataType: range.dataType,
189 get
190 }
191 return newrange
192 }
193
194 let loadRange = paramKey => cov.loadRange(paramKey).then(rangeWrapper)
195
196 let loadRanges = paramKeys => cov.loadRanges(paramKeys)
197 .then(ranges => new Map([...ranges].map(([paramKey, range]) => [paramKey, rangeWrapper(range)])))
198
199 let newcov = {
200 type: COVERAGE,
201 domainType: cov.domainType,
202 parameters: cov.parameters,
203 loadDomain: () => cov.loadDomain().then(domainWrapper),
204 loadRange,
205 loadRanges,
206 subsetByIndex: constraints => cov.subsetByIndex(constraints).then(sub => renameAxes(sub, mapping)),
207 subsetByValue: constraints => cov.subsetByValue(constraints).then(sub => renameAxes(sub, mapping))
208 }
209
210 return newcov
211}
212
213/**
214 * @param {Coverage} cov The coverage.
215 * @param {String} key The key of the parameter for which the mapping should be applied.
216 * @param {Function} fn A function getting called as fn(obj, range) where obj is the axis indices object
217 * and range is the original range object.
218 * @param {String} [dataType] The new data type to use for the range. If omitted, the original type is used.
219 * @returns {Coverage}
220 */
221export function mapRange (cov, key, fn, dataType) {
222 checkCoverage(cov)
223
224 let rangeWrapper = range => {
225 let newrange = {
226 shape: range.shape,
227 dataType: dataType || range.dataType,
228 get: obj => fn(obj, range)
229 }
230 return newrange
231 }
232
233 let loadRange = paramKey => key === paramKey ? cov.loadRange(paramKey).then(rangeWrapper) : cov.loadRange(paramKey)
234
235 let loadRanges = paramKeys => cov.loadRanges(paramKeys)
236 .then(ranges => new Map([...ranges].map(([paramKey, range]) => [paramKey, key === paramKey ? rangeWrapper(range) : range])))
237
238 let newcov = {
239 type: COVERAGE,
240 domainType: cov.domainType,
241 parameters: cov.parameters,
242 loadDomain: () => cov.loadDomain(),
243 loadRange,
244 loadRanges,
245 subsetByIndex: constraints => cov.subsetByIndex(constraints).then(sub => mapRange(sub, key, fn, dataType)),
246 subsetByValue: constraints => cov.subsetByValue(constraints).then(sub => mapRange(sub, key, fn, dataType))
247 }
248
249 return newcov
250}
251
252/**
253 *
254 * @example
255 * var cov = ... // has parameters 'NIR', 'red', 'green', 'blue'
256 * var newcov = CovUtils.withDerivedParameter(cov, {
257 * parameter: {
258 * key: 'NDVI',
259 * observedProperty: {
260 * label: { en: 'Normalized Differenced Vegetation Index' }
261 * }
262 * },
263 * inputParameters: ['NIR','red'],
264 * dataType: 'float',
265 * fn: function (obj, nirRange, redRange) {
266 * var nir = nirRange.get(obj)
267 * var red = redRange.get(obj)
268 * if (nir === null || red === null) return null
269 * return (nir - red) / (nir + red)
270 * }
271 * })
272 */
273export function withDerivedParameter (cov, options) {
274 checkCoverage(cov)
275 let {parameter, inputParameters, dataType = 'float', fn} = options
276
277 let parameters = new Map(cov.parameters)
278 parameters.set(parameter.key, parameter)
279
280 let loadDerivedRange = () => cov.loadRanges(inputParameters).then(inputRanges => {
281 let inputRangesArr = inputParameters.map(key => inputRanges.get(key))
282 let shape = inputRangesArr[0].shape
283 let range = {
284 shape,
285 dataType,
286 get: obj => fn(obj, ...inputRangesArr)
287 }
288 return range
289 })
290
291 let loadRange = paramKey => parameter.key === paramKey ? loadDerivedRange() : cov.loadRange(paramKey)
292
293 let newcov = {
294 type: COVERAGE,
295 domainType: cov.domainType,
296 parameters,
297 loadDomain: () => cov.loadDomain(),
298 loadRange,
299 subsetByIndex: constraints => cov.subsetByIndex(constraints).then(sub => withDerivedParameter(sub, options)),
300 subsetByValue: constraints => cov.subsetByValue(constraints).then(sub => withDerivedParameter(sub, options))
301 }
302 addLoadRangesFunction(newcov)
303
304 return newcov
305}
306
307/**
308 *
309 * @example
310 * var cov = ... // has parameters 'NIR', 'red', 'green', 'blue'
311 * var newcov = CovUtils.withSimpleDerivedParameter(cov, {
312 * parameter: {
313 * key: 'NDVI',
314 * observedProperty: {
315 * label: { en: 'Normalized Differenced Vegetation Index' }
316 * }
317 * },
318 * inputParameters: ['NIR','red'],
319 * dataType: 'float',
320 * fn: function (nir, red) {
321 * return (nir - red) / (nir + red)
322 * }
323 * })
324 */
325export function withSimpleDerivedParameter (cov, options) {
326 let {parameter, inputParameters, dataType, fn} = options
327 let options_ = {
328 parameter,
329 inputParameters,
330 dataType,
331 // TODO pre-compile if too slow
332 fn: (obj, ...ranges) => {
333 let vals = inputParameters.map((_, i) => ranges[i].get(obj))
334 if (vals.some(val => val === null)) {
335 return null
336 }
337 return fn(...vals)
338 }
339 }
340 return withDerivedParameter(cov, options_)
341}