All files / src/services/gfs gfs.service.js

66.67% Statements 36/54
58.82% Branches 10/17
63.64% Functions 7/11
66.04% Lines 35/53

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 1101x 1x 1x 1x 1x 1x 1x           3x 3x 3x 3x                           3x         3x             3x     3x   3x 3x                         3x         18x           6x     6x   6x 11x   6x 6x   6x 6x       6x   6x       6x     6x   6x 6x 6x     6x            
import path from 'path'
import fs from 'fs-extra'
import errors from '@feathersjs/errors'
import grib2json from 'weacast-grib2json'
import logger from 'winston'
import makeDebug from 'debug'
const debug = makeDebug('weacast:weacast-gfs')
 
export default {
 
  // Perform conversion from input TIFF to JSON
  convertForecastTime (runTime, forecastTime) {
    let promise = new Promise((resolve, reject) => {
      const filePath = this.getForecastTimeFilePath(runTime, forecastTime)
      const convertedFilePath = this.getForecastTimeConvertedFilePath(runTime, forecastTime)
      Iif (fs.existsSync(convertedFilePath)) {
        logger.verbose('Already converted ' + this.forecast.name + '/' + this.element.name + ' forecast at ' + forecastTime.format() + ' for run ' + runTime.format())
        fs.readJson(convertedFilePath, 'utf8')
        .then(grid => {
          resolve(grid)
        })
        .catch(error => {
          logger.error('Cannot read converted ' + this.forecast.name + '/' + this.element.name + ' forecast at ' + forecastTime.format() + ' for run ' + runTime.format())
          debug('Input JSON file was : ' + convertedFilePath)
          reject(error)
        })
        return
      }
 
      grib2json(filePath, {
        data: true,
        bufferSize: 1024 * 1024 * 1024
      })
      .then(json => {
        Iif (json.length === 0 || !json[0].data || !json[0].data.length > 0) {
          const errorMessage = 'Converted ' + this.forecast.name + '/' + this.element.name + ' forecast data at ' + forecastTime.format() + ' for run ' + runTime.format() + ' is invalid or empty'
          logger.error(errorMessage)
          debug('Output JSON file was : ' + convertedFilePath)
          reject(new errors.Unprocessable(errorMessage))
          return
        } else {
          logger.verbose('Converted ' + this.forecast.name + '/' + this.element.name + ' forecast at ' + forecastTime.format() + ' for run ' + runTime.format())
        }
        // Change extension from tiff to json
        fs.outputJson(convertedFilePath, json[0].data, 'utf8')
        .then(_ => {
          logger.verbose('Written ' + this.forecast.name + '/' + this.element.name + ' converted forecast at ' + forecastTime.format() + ' for run ' + runTime.format())
          resolve(json[0].data)
        })
        .catch(error => {
          logger.error('Cannot write converted ' + this.forecast.name + '/' + this.element.name + ' forecast at ' + forecastTime.format() + ' for run ' + runTime.format())
          debug('Output JSON file was : ' + convertedFilePath)
          reject(error)
        })
      })
      .catch(error => {
        reject(error)
      })
    })
 
    return promise
  },
 
  // Generate file name to store temporary input data with the right format extension
  getForecastTimeFilePath (runTime, forecastTime) {
    return path.join(this.app.get('forecastPath'), this.forecast.name, this.element.name, runTime.format('YYYY-MM-DD[_]HH-mm-ss') + '_' + forecastTime.format('YYYY-MM-DD[_]HH-mm-ss') + '.grib')
  },
 
  // Build the request options to download given forecast time from input WCS data source
  getForecastTimeRequest (runTime, forecastTime) {
    // Directories are organized by run time
    let subDirectory = '/gfs.' + runTime.format('YYYYMMDD/HH') + '/atmos'
    // Then we need to target the right file for forecast time
    // Get offset from run time
    let hours = forecastTime.diff(runTime, 'hours')
    // Convert to string with zero padding like 006
    hours = hours.toFixed(0)
    while (hours.length < 3) hours = '0' + hours
    // Get resolution expressed as XpXX
    let resolution = this.forecast.resolution.length > 0 ? this.forecast.resolution[0] : 0.5
    resolution = resolution.toFixed(2).replace('.', 'p')
    // Specific case of 0.5° model
    Eif (resolution === '0p50') {
      resolution = 'full.' + resolution
    } else {
      resolution = '.' + resolution
    }
    let file = 'gfs.t' + runTime.format('HH') + 'z.pgrb2' + resolution + '.f' + hours
    // Setup request with URL, variable, level parameters for HTTP filter
    let queryParameters = {
      dir: subDirectory,
      file
    }
    Iif (this.element.variable.startsWith('var_')) {
      queryParameters[this.element.variable] = 'on'
    } else {
      queryParameters['var_' + this.element.variable.toUpperCase()] = 'on'
    }
    Eif (this.element.levels) {
      this.element.levels.forEach(level => {
        queryParameters[level.startsWith('lev_') ? level : 'lev_' + level] = 'on'
      })
    }
    return {
      url: this.forecast.baseUrl,
      qs: queryParameters
    }
  }
}