app.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. var express = require("express");
  2. var moment = require("moment");
  3. var http = require('http');
  4. var request = require('postman-request');
  5. var fs = require('fs');
  6. var Q = require('q');
  7. var cors = require('cors');
  8. const iconv = require('iconv-lite');
  9. const { upload, uploadfileFn } = require("./src/tifupload");
  10. var app = express();
  11. var port = process.env.PORT || 7000;
  12. var baseDir = 'http://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_1p00.pl';
  13. // cors config
  14. var whitelist = [
  15. 'http://localhost:3000',
  16. 'http://localhost:5000',
  17. ];
  18. var corsOptions = {
  19. origin: function (origin, callback) {
  20. var originIsWhitelisted = whitelist.indexOf(origin) !== -1;
  21. callback(null, originIsWhitelisted);
  22. }
  23. };
  24. app.listen(port, function (err) {
  25. console.log(" running server on port " + port);
  26. });
  27. app.use('/public', express.static('./public'));
  28. app.get('/', cors(corsOptions), function (req, res) {
  29. res.send('hello wind-server.. go to /latest for wind data..');
  30. });
  31. app.post("/file/upload", upload.single('file'), uploadfileFn)
  32. app.get('/alive', cors(corsOptions), function (req, res) {
  33. res.send('wind-server is alive');
  34. });
  35. const LevelArray = ["lev_10_m_above_ground", 'lev_1000_mb', 'lev_100_mb', 'lev_250_mb', 'lev_500_mb', "lev_750_mb"];
  36. app.get('/latest', cors(corsOptions), function (req, res) {
  37. let { leavel } = req.query;
  38. if (!LevelArray.includes(leavel)) {
  39. res.send({
  40. code: 400,
  41. message: `参数需要为${LevelArray.join(',')}其一`
  42. })
  43. return
  44. }
  45. if (!leavel) {
  46. leavel = LevelArray[0]
  47. }
  48. /**
  49. * Find and return the latest available 6 hourly pre-parsed JSON data
  50. *
  51. * @param targetMoment {Object} UTC moment
  52. */
  53. function sendLatest(targetMoment, repeatTime) {
  54. var stamp = moment(targetMoment).format('YYYYMMDD') + '/' + roundHours(moment(targetMoment).hour(), 6) + '/atmos';
  55. var fileName = __dirname + "/json-data/" + stamp.replaceAll('/', '_').replace('/a', '\\a') + leavel + ".json";
  56. if (responseSent) {
  57. return;
  58. }
  59. res.setHeader('Access-Control-Allow-Origin', '*');
  60. res.setHeader('Content-Type', 'application/json');
  61. res.sendFile(fileName, {}, function (err) {
  62. if (err) {
  63. console.log(fileName + ' doesnt exist yet, trying previous interval..');
  64. if (!repeatTime) repeatTime = 0
  65. sendLatest(moment(targetMoment).subtract(6, 'hours'), repeatTime++);
  66. return
  67. } else {
  68. responseSent = true;
  69. }
  70. });
  71. return
  72. }
  73. let responseSent = false;
  74. sendLatest(moment().utc());
  75. });
  76. app.get('/nearest', cors(corsOptions), function (req, res, next) {
  77. var time = req.query.timeIso;
  78. var limit = req.query.searchLimit;
  79. var searchForwards = false;
  80. /**
  81. * Find and return the nearest available 6 hourly pre-parsed JSON data
  82. * If limit provided, searches backwards to limit, then forwards to limit before failing.
  83. *
  84. * @param targetMoment {Object} UTC moment
  85. */
  86. function sendNearestTo(targetMoment) {
  87. if (limit && Math.abs(moment.utc(time).diff(targetMoment, 'days')) >= limit) {
  88. if (!searchForwards) {
  89. searchForwards = true;
  90. sendNearestTo(moment(targetMoment).add(limit, 'days'));
  91. return;
  92. }
  93. else {
  94. return next(new Error('No data within searchLimit'));
  95. }
  96. }
  97. var stamp = moment(targetMoment).format('YYYYMMDD') + '/' + roundHours(moment(targetMoment).hour(), 6) + '/atmos';
  98. var fileName = __dirname + "/json-data/" + stamp.replaceAll('/', '_').replace('/a', '\\a') + ".json";
  99. res.setHeader('Content-Type', 'application/json');
  100. res.sendFile(fileName, {}, function (err) {
  101. if (err) {
  102. var nextTarget = searchForwards ? moment(targetMoment).add(6, 'hours') : moment(targetMoment).subtract(6, 'hours');
  103. sendNearestTo(nextTarget);
  104. }
  105. });
  106. }
  107. if (time && moment(time).isValid()) {
  108. sendNearestTo(moment.utc(time));
  109. }
  110. else {
  111. return next(new Error('Invalid params, expecting: timeIso=ISO_TIME_STRING'));
  112. }
  113. });
  114. /**
  115. *
  116. * Ping for new data every 3 hours
  117. *
  118. */
  119. setInterval(function () {
  120. run(moment.utc());
  121. }, 3 * 60 * 60000);
  122. /**
  123. *
  124. * @param targetMoment {Object} moment to check for new data
  125. */
  126. function run(targetMoment) {
  127. // const LevelArray = ['lev_750_mb'];
  128. for (const item of LevelArray) {
  129. getGribData(targetMoment, item).then(function (response) {
  130. if (response.stamp) {
  131. convertGribToJson(response.stamp, response.targetMoment, response.level);
  132. }
  133. });
  134. }
  135. }
  136. /**
  137. *
  138. * Finds and returns the latest 6 hourly GRIB2 data from NOAAA
  139. *
  140. * @returns {*|promise}
  141. */
  142. function getGribData(targetMoment, item) {
  143. var deferred = Q.defer();
  144. function runQuery(targetMoment, item) {
  145. // only go 1 day deep
  146. if (moment.utc().diff(targetMoment, 'days') > 2) {
  147. console.log('超出限定,不再向下查询', item);
  148. return;
  149. }
  150. var stamp = moment(targetMoment).format('YYYYMMDD') + '/' + roundHours(moment(targetMoment).hour(), 6) + '/atmos';
  151. if (checkPath('json-data/' + stamp.replaceAll('/', '_') + item + '.json')) {//避免重复请求覆盖
  152. runQuery(moment(targetMoment).subtract(6, 'hours'), item);
  153. return
  154. }
  155. const param = {
  156. file: 'gfs.t' + roundHours(moment(targetMoment).hour(), 6) + 'z.pgrb2.1p00.f000',
  157. var_TMP: 'on',
  158. var_UGRD: 'on',
  159. var_VGRD: 'on',
  160. leftlon: 0,
  161. rightlon: 360,
  162. toplat: 90,
  163. bottomlat: -90,
  164. dir: '/gfs.' + stamp
  165. }
  166. param[item] = 'on';
  167. request.get({
  168. url: baseDir,
  169. qs: param
  170. //https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_1p00.pl?file = gfs.t00z.pgrb2.1p00.f000&lev_800_mb=on&leftlon=0 &rightlon=360 & toplat=90 & bottomlat=-90 & dir=/gfs.20230302/00/atmos
  171. }).on('error', function (err) {
  172. runQuery(moment(targetMoment).subtract(6, 'hours'), item);
  173. }).on('response', function (response) {
  174. console.log('response ' + response.statusCode + ' | ' + stamp);
  175. if (response.statusCode != 200) {
  176. if (response.statusCode == 404) {
  177. runQuery(moment(targetMoment).subtract(6, 'hours'), item);
  178. } else {
  179. deferred.resolve({ stamp: false, targetMoment: false, level: item });
  180. }
  181. } else {
  182. // don't rewrite stamps
  183. if (!checkPath('json-data/' + stamp.replaceAll('/', '_') + item + '.json', false)) {
  184. console.log('转换' + stamp.replaceAll('/', '_'), item);
  185. // mk sure we've got somewhere to put output
  186. checkPath('grib-data', true);
  187. // pipe the file, resolve the valid time stamp
  188. var file = fs.createWriteStream("grib-data/" + stamp.replaceAll('/', '_') + item + ".f000");
  189. response.pipe(file);
  190. file.on('finish', function () {
  191. file.close();
  192. deferred.resolve({ stamp: stamp.replaceAll('/', '_'), targetMoment: targetMoment, level: item });
  193. });
  194. }
  195. else {
  196. console.log('already have ' + stamp.replaceAll('/', '_') + ', not looking further');
  197. deferred.resolve({ stamp: false, targetMoment: false, level: item });
  198. }
  199. }
  200. });
  201. }
  202. runQuery(targetMoment, item);
  203. return deferred.promise;
  204. }
  205. function convertGribToJson(stamp, targetMoment, level) {
  206. // debugger
  207. // mk sure we've got somewhere to put output
  208. checkPath('json-data', true);
  209. var exec = require('child_process').exec, child;
  210. var commandStr = `grib2json --data --output ./json-data/${stamp.replaceAll('/', '_').replace('/a', '\\a') + level}.json --names --compact ./grib-data/${stamp.replaceAll('/', '_').replace('/a', '\\a') + level}.f000`
  211. child = exec(commandStr,
  212. { maxBuffer: 500 * 1024, encoding: 'buffer', },
  213. function (error, stdout, stderr) {
  214. if (error) {
  215. console.log('exec error: ', error, iconv.decode(stderr, 'cp936'))
  216. } else {
  217. console.log("转换中..");
  218. // don't keep raw grib data
  219. exec('rm grib-data/*');
  220. // if we don't have older stamp, try and harvest one
  221. var prevMoment = moment(targetMoment).subtract(6, 'hours');
  222. var prevStamp = prevMoment.format('YYYYMMDD') + '/' + roundHours(prevMoment.hour(), 6) + '/atmos';
  223. if (!checkPath('json-data/' + prevStamp.replaceAll('/', '_').replace('/a', '\\a') + level + '.json', false)) {
  224. console.log("attempting to harvest older data " + stamp);
  225. run(prevMoment);
  226. } else {
  227. console.log('got older, no need to harvest further' + stamp);
  228. }
  229. }
  230. });
  231. }
  232. /**
  233. *
  234. * Round hours to expected interval, e.g. we're currently using 6 hourly interval
  235. * 00 || 06 || 12 || 18
  236. *
  237. * @param hours
  238. * @param interval
  239. * @returns {String}
  240. */
  241. function roundHours(hours, interval) {
  242. if (interval > 0) {
  243. var result = (Math.floor(hours / interval) * interval);
  244. return result < 10 ? '0' + result.toString() : result;
  245. }
  246. }
  247. /**
  248. * Sync check if path or file exists
  249. *
  250. * @param path {string}
  251. * @param mkdir {boolean} create dir if doesn't exist
  252. * @returns {boolean}
  253. */
  254. function checkPath(path, mkdir) {
  255. try {
  256. fs.statSync(path);
  257. return true;
  258. } catch (e) {
  259. if (mkdir) {
  260. fs.mkdirSync(path);
  261. }
  262. return false;
  263. }
  264. }
  265. // init harvest
  266. run(moment.utc());