//////////////////////////////////////////////////////////////////////
// Copyright (C) Hiroshi SUGIMURA 2022.09.06
//////////////////////////////////////////////////////////////////////
/**
* @module mainJma
*/
'use strict'
//////////////////////////////////////////////////////////////////////
// 基本ライブラリ
const Store = require('electron-store');
const request = require('request');
const cron = require('node-cron');
require('date-utils'); // for log
const { Sequelize, sqlite3, jmaRawModel, jmaAbstModel, weatherForecastModel, popsForecastModel, tempForecastModel } = require('./models/localDBModels'); // DBデータと連携
const {isObjEmpty, mergeDeeply} = require('./mainSubmodule');
let sendIPCMessage = null;
const store = new Store();
let config = {
enabled: true,
area: '東京都',
code: '130000',
debug: false
};
let persist = {};
//////////////////////////////////////////////////////////////////////
// config
let mainJma = {
isRun: false,
abstURL: "https://www.jma.go.jp/bosai/forecast/data/overview_forecast/",
detailURL: "https://www.jma.go.jp/bosai/forecast/data/forecast/",
observationJob: null,
callback: null,
debug: false,
areaCodes: {
"群馬県": "100000",
"埼玉県": "110000",
"千葉県": "120000",
"東京都": "130000",
"神奈川県": "140000",
"新潟県": "150000",
"富山県": "160000",
"石川県": "170000",
"福井県": "180000",
"山梨県": "190000",
"長野県": "200000",
"岐阜県": "210000",
"静岡県": "220000",
"愛知県": "230000",
"三重県": "240000",
"滋賀県": "250000",
"京都府": "260000",
"大阪府": "270000",
"兵庫県": "280000",
"奈良県": "290000",
"和歌山県": "300000",
"鳥取県": "310000",
"島根県": "320000",
"岡山県": "330000",
"広島県": "340000",
"山口県": "350000",
"徳島県": "360000",
"香川県": "370000",
"愛媛県": "380000",
"高知県": "390000",
"福岡県": "400000",
"佐賀県": "410000",
"長崎県": "420000",
"熊本県": "430000",
"大分県": "440000",
"宮崎県": "450000",
"奄美地方": "460040",
"鹿児島県(奄美地方除く)": "460100",
"沖縄本島地方": "471000",
"大東島地方": "472000",
"宮古島地方": "473000",
"八重山地方": "474000",
"青森県": "20000",
"岩手県": "30000",
"宮城県": "40000",
"秋田県": "50000",
"山形県": "60000",
"福島県": "70000",
"茨城県": "80000",
"栃木県": "90000"
},
//////////////////////////////////////////////////////////////////////
// 気象庁の処理
/**
* @func start
* @desc start
* @async
* @param {void}
* @return void
* @throw error
*/
// 重複起動してもよい
start: function ( _sendIPCMessage ) {
sendIPCMessage = _sendIPCMessage;
if( mainJma.isRun ) { // 重複起動は現在データを渡す
sendIPCMessage('renewJmaConfigView', config );
if( !isObjEmpty( persist.abst ) ) { sendIPCMessage( "renewJmaAbst", persist.abst ); }
if( !isObjEmpty( persist.detail ) ) { sendIPCMessage( "renewJmaDetail", persist.detail ); }
return;
}
config.enabled = store.get('config.JMA.enabled', true);
config.area = store.get('config.JMA.area', '東京都');
config.code = store.get('config.JMA.code', '130000');
config.debug = store.get('config.JMA.debug', false);
persist = store.get('persist.JMA', {});
if( !config.enabled ) {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| JmaStart(): Jma is disabled.'):0;
mainJma.isRun = false;
return;
}
mainJma.isRun = true;
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| JmaStart() config:', '\x1b[32m', config, '\x1b[0m'):0;
if( !persist || isObjEmpty(persist) ) { persist = {abst:{}, detail:{}}; }
mainJma.callback = async function (res) {
switch( res.cmd ) {
case "abst":
try{
if( !isObjEmpty(res.json) ) {
// raw
let raw = mainJma.parseAbstRaw( res.json );
// config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| JmaStart() abst raw:', '\x1b[32m', raw, '\x1b[0m'):0;
await jmaRawModel.findOne( {where: { requestAreaCode: raw.requestAreaCode, reportDatetime: raw.reportDatetime } })
.then( async (row) => {
if( !row ) { // 重複は蓄積しない
await jmaRawModel.create( raw );
}
}).catch( (error) => {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| Error JmaStart() abst raw:', '\x1b[32m', raw, '\x1b[0m'):0;
throw error;
});
// 構造化されたデータ
persist.abst = mainJma.parseAbst(res.json);
sendIPCMessage( "renewJmaAbst", persist.abst );
await jmaAbstModel.findOne( {where: { requestAreaCode: persist.abst.requestAreaCode, reportDatetime: persist.abst.reportDatetime } })
.then( async (row) => {
if( !row ) { // 重複は蓄積しない
await jmaAbstModel.create( persist.abst );
}
}).catch( (error) => {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| Error JmaStart() abst:', '\x1b[32m', persist.abst, '\x1b[0m'):0;
throw error;
});
}
}catch( error ) {
// JSONじゃないbodyもくる?
console.error( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| JmaStart() abst error:', error);
}
break;
case "detail":
try{
if( !isObjEmpty(res.json) ) {
// raw
let raw = mainJma.parseDetailRaw( res.json );
// config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| JmaStart() abst raw:', '\x1b[32m', raw, '\x1b[0m'):0;
await jmaRawModel.findOne( {where: { requestAreaCode: raw.requestAreaCode, reportDatetime: raw.reportDatetime } })
.then( async (row) => {
if( !row ) { // 重複は蓄積しない
await jmaRawModel.create( raw );
}
}).catch( (error) => {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| Error JmaStart() detail raw:', '\x1b[32m', raw, '\x1b[0m'):0;
throw error;
});
// 構造化されたデータ
persist.detail = mainJma.parseDetail(res.json);
sendIPCMessage( "renewJmaDetail", persist.detail );
// 詳細の天気
for( let i in persist.detail.weather ) {
await weatherForecastModel.findOne( {where: { code: persist.detail.weather[i].code, reportDatetime: persist.detail.weather[i].reportDatetime } })
.then( async (row) => {
if( !row ) { // 重複は蓄積しない
await weatherForecastModel.create( persist.detail.weather[i] );
}
}).catch( (error) => {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| Error JmaStart() detail weather:', '\x1b[32m', persist.detail.weather[i], '\x1b[0m'):0;
throw error;
});
}
// 詳細の降水確率
for( let i in persist.detail.pops ) {
await popsForecastModel.findOne( {where: { code: persist.detail.pops[i].code, reportDatetime: persist.detail.pops[i].reportDatetime } })
.then( async (row) => {
if( !row ) { // 重複は蓄積しない
await popsForecastModel.create( persist.detail.pops[i] );
}
}).catch( (error) => {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| Error JmaStart() detail pops:', '\x1b[32m', persist.detail.pops[i], '\x1b[0m'):0;
throw error;
});
}
// 詳細の気温
for( let i in persist.detail.temperature ) {
await tempForecastModel.findOne( {where: { code: persist.detail.temperature[i].code, reportDatetime: persist.detail.temperature[i].reportDatetime } })
.then( async (row) => {
if( !row ) { // 重複は蓄積しない
await tempForecastModel.create( persist.detail.temperature[i] );
}
}).catch( (error) => {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| Error JmaStart() detail temperature:', '\x1b[32m', persist.detail.temperature[i], '\x1b[0m'):0;
throw error;
});
}
}
}catch( error ) {
// JSONじゃないbodyもくる?
console.error( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| Error JmaStart() detail:', error);
}
break;
}
};
mainJma.setObserve(); // 3 hour each
sendIPCMessage('renewJmaConfigView', config );
if( !isObjEmpty( persist.abst ) ) { sendIPCMessage( "renewJmaAbst", persist.abst ); }
if( !isObjEmpty( persist.detail ) ) { sendIPCMessage( "renewJmaDetail", persist.detail ); }
mainJma.gets(); // 初回起動はデータ取得する
},
// ---------------------------------------------------------------
/**
* @func gets
* @desc gets
* @async
* @param {void}
* @return void
* @throw error
*/
// inner functions
gets: function() {
request( { url: mainJma.abstURL + config.code + ".json", method: 'GET', json:true }, function (error, response, body ) {
if( error ) {
console.error( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainJma.observe().abst error:', error);
return;
}
mainJma.callback( {cmd:"abst", json:body} );
});
request( { url: mainJma.detailURL + config.code + ".json", method: 'GET', json:true }, function (error, response, body ) {
if( error ) {
console.error( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainJma.observe().detail error:', error);
return;
}
mainJma.callback( {cmd:"detail", json:body} );
});
},
/**
* @func parseAbstRaw
* @desc parseAbstRaw
* @async
* @param {void}
* @return void
* @throw error
*/
// getしたbodyを使い物になる形に変える
parseAbstRaw: function(body) {
return {
type: 'abst',
publishingOffice: body.publishingOffice,
reportDatetime: body.reportDatetime,
requestAreaCode: config.code,
json: JSON.stringify( body )
};
},
/**
* @func parseAbst
* @desc parseAbst
* @async
* @param {void}
* @return void
* @throw error
*/
parseAbst: function(body) {
return {
reportDatetime: body.reportDatetime,
publishingOffice: body.publishingOffice,
requestAreaCode: config.code,
headlineText: body.headlineText,
text: body.text
}
},
/**
* @func parseDetailRaw
* @desc parseDetailRaw
* @async
* @param {void}
* @return void
* @throw error
*/
parseDetailRaw: function(body) {
let w = body[0]; // json[1]はちょっとよくわからんので
let publishingOffice = w.publishingOffice;
let reportDatetime = w.reportDatetime;
return {
type: 'detail',
publishingOffice: publishingOffice,
reportDatetime: reportDatetime,
requestAreaCode: config.code,
json: JSON.stringify( body )
};
},
/**
* @func parseDetail
* @desc parseDetail
* @async
* @param {void}
* @return void
* @throw error
*/
parseDetail: function(body) {
// console.log( body );
let res = {weather:[], pops:[], temperature:[]};
let w = body[0]; // body[1]はちょっとよくわからんので
let publishingOffice = w.publishingOffice;
let reportDatetime = w.reportDatetime;
let we = w.timeSeries[0]; // 天気関係
let po = w.timeSeries[1]; // 降水確率
let te = w.timeSeries[2]; // 気温
// timeseries 0 = weather
let timeDefines = JSON.stringify(we.timeDefines);
for( let a of we.areas ) {
res.weather.push( {
reportDatetime: reportDatetime,
publishingOffice: publishingOffice,
targetArea: a.area.name,
code: a.area.code,
timeDefines: timeDefines,
weatherCodes: JSON.stringify(a.weatherCodes),
weathers: JSON.stringify(a.weathers),
winds: JSON.stringify(a.winds),
waves: JSON.stringify(a.waves)
});
}
// timeseries 1 = pops
timeDefines = JSON.stringify(po.timeDefines);
for( let a of po.areas ) {
res.pops.push( {
reportDatetime: reportDatetime,
publishingOffice: publishingOffice,
targetArea: a.area.name,
code: a.area.code,
timeDefines: timeDefines,
pops: JSON.stringify(a.pops)
});
};
// timeseries 2 = temperature
timeDefines = JSON.stringify(te.timeDefines);
for( let a of te.areas ) {
res.temperature.push( {
reportDatetime: reportDatetime,
publishingOffice: publishingOffice,
targetArea: a.area.name,
code: a.area.code,
timeDefines: timeDefines,
temps: JSON.stringify(a.temps)
});
};
return res;
},
/**
* @func setObserve
* @desc setObserve
* @async
* @param {void}
* @return void
* @throw error
*/
// 監視開始する
setObserve: function() {
mainJma.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainJma.setObserve() start.' ):0;
if( mainJma.observationJob ) {
mainJma.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainJma.setObserve() already started.' ):0;
}
// 監視はcronで実施、3時間毎
mainJma.observationJob = cron.schedule('0 */3 * * *', () => {
mainJma.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainJma.setObserve cron.schedule()'):0;
mainJma.gets();
})
},
/**
* @func stopObservation
* @desc stopObservation
* @async
* @param {void}
* @return void
* @throw error
*/
// 監視をやめる
stopObservation: function() {
mainJma.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainJma.stopObservation() observation.' ):0;
if( mainJma.observationJob ) {
mainJma.observationJob.stop();
mainJma.observationJob = null;
}
},
/**
* @func stop
* @desc stop
* @async
* @param {void}
* @return void
* @throw error
*/
// interface
// 機能を停止する
stop: async function () {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainJma.stop()'):0;
await mainJma.setConfig( config );
await store.set('persist.JMA', persist);
await mainJma.stopObservation();
},
/**
* @func stopWithoutSave
* @desc stopWithoutSave
* @async
* @param {void}
* @return void
* @throw error
*/
stopWithoutSave: async function () {
config.debug?console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainJma.stopWithoutSave()'):0;
await mainJma.stopObservation();
},
/**
* @func setConfig
* @desc setConfig
* @async
* @param {void}
* @return void
* @throw error
*/
setConfig: async function ( _config ) {
if( _config ) {
config = mergeDeeply( config, _config );
}
await store.set('config.JMA', config);
sendIPCMessage( "configSaved", 'JMA' ); // 保存したので画面に通知
},
/**
* @func getConfig
* @desc getConfig
* @async
* @param {void}
* @return void
* @throw error
*/
getConfig: function () {
return config;
},
/**
* @func getPersist
* @desc getPersist
* @async
* @param {void}
* @return void
* @throw error
*/
getPersist: function() {
return persist;
}
};
module.exports = mainJma;
//////////////////////////////////////////////////////////////////////
// EOF
//////////////////////////////////////////////////////////////////////