この記事は、データサイエンティストでDelphi開発者のMax Kleiner氏の投稿です。彼は、Maxboxの作者であり、このツールを自身のサイトで「コンパイル済みオブジェクトをベースとしたスクリプトツール」と紹介しています。記事では、RAD Studio / Delphiを用いてWindowsソフトウェアを開発し、HTTPリクエストによって取得したJSONオブジェクトを使って、集積ガス貯蔵施設の貯蔵量データをグラフやチャートで可視化する方法を説明しています。
Table of Contents
貯蔵ガスのデータをAGSIデータセットのタイムラインとして可視化
「データサイエンティストにはさまざまな種類がある。その中で最高なのはデータに取り組む人だ。」
このデータサイエンスチュートリアルでは、AGISと呼ばれるデータストレージとタイムラインでの可視化について説明しています。AGISは、Aggregated Gas Storage Inventoryの略で、集積ガス貯蔵施設の貯蔵量のデータです。データプロバイダーから新しいサービスが発表されたり更新があり、Webサイトにその情報が掲載されるたびに、そのデータを最新に保つことが可能になります。
GIE(Gas Infrastructure Europe)は、Storage MapやStorage Investment Databaseといった関連情報も、https://www.gie.eu/publications/maps/ で提供しています。
データチャート(グラフ)の表示
タイムラインデータを可視化したグラフは、以下のようになります。
データのダウンロードとグラフ作成に使用したコンポーネント
このツールでは、WinHttp.WinHttpRequest、JSONObjects、そしてTEEChartsライブラリを使用して、プロットのロードとテストを行っています。APIキーも必要になるため、以下のサイトからあらかじめ入手しておきます。
データが表現するもの
取得するデータは、日ごとのガス貯蔵量を表します。データは毎日、19:30と23:00(中央ヨーロッパ時間)に更新されます。コードの詳細を説明する前に、スクリプトの主要な部分を以下に示します。
1 2 3 4 5 |
plotform:= getForm2(1400, 600, clsilver, 'Sciplot4maXbox'); plotform.icon.loadfromresourcename(hinstance,'ZHISTOGRAM'); HttpResponse:= getEnergyStreamJSON2(URL_AGSIAPI2,'DE,2022-01-03,150',AGSI_APIKEY); JSON2Plot(plotform, letGenerateJSON2(HttpResponse)); |
コード概要
コードのメイン部分では、フォームを生成し、API呼び出しとデータのプロットを行っています。
GIEでは、AGSIおよびALSI Transparency Publication Platformsで、API(Application Programming Interface)サービスを提供しています。
APIアクセスを用いれば、ユーザーはAGSIおよびALSIのWebサイトをバイパスして、データを直接かつ継続的に取得できます。Webサイトから各データセットを個別にダウンロードすることなく、必要に応じてデータ抽出、フィルタリング、集計、サブセット生成が可能になります。APIのデータエクスポートフォーマットは、JSONです。以下は、150日分のサブセットデータです。
グラフに描画するデータ
公開されているデータセットは、ACERに提供されているEICコードマッピングテーブルをベースとしています。ストレージデータは、国別、会社別に集計されます。
API呼び出しでは、国、開始日、日数を指定します。
1 |
getEnergyStreamJSON2(URL_AGSIAPI2,'DE,2022-01-03,150',AGSI_APIKEY); |
データセットは、Excel、CSV,JSON形式でもダウンロードできます。このレポートでは、集計ビューを表示していますが、会社ごと、貯蔵施設ごとの個別データセットにもアクセスできます。
データ取得のためのAPI呼び出し
では、API呼び出しの方法から紹介します。
1 |
HttpResponse:= getEnergyStreamJSON2(URL_AGSIAPI2,'DE,2022-01-03,150',AGSI_APIKEY); |
このコマンドにより、WinHttp.WinHttpRequest
を実行します。失敗すると、以下のような例外が複数生成されます。
1 2 3 4 5 6 7 8 |
Exception: WinHttp.WinHttpRequest: The data necessary to complete this operation is not yet available; or you missed a valid key: AGSIPost: Failed at getting response:403{ "error": { "code": 403, "message": "The request is missing a valid API key.", "status": "PERMISSION_DENIED" }} |
面白いのは、例外がJSON形式になっている点です。
Be careful, also, to expose your key as I get from Git: GitGuardian has detected the following Google API Key exposed within your GitHub account.
APIでJSONデータを取得する
Next is the formatting of the get-call with a valid API-key request in the function energyStream()
関数で、有効なAPIキーリクエストを使用してGET呼び出しを書式化します。
1 2 3 4 5 6 7 8 |
function getEnergyStreamJSON2(AURL, feedstream, aApikey:string): string; ... encodURL:= Format(AURL,[HTTPEncode(asp[0]),(asp[1]),asp[2]]); writeln(encodurl) //debug hr:= httpRq.Open('GET', encodURL, false); httpRq.setRequestheader('user-agent',USERAGENTE); httpRq.setRequestheader('x-key',aAPIkey); ... |
ところで、content-typeはどこで設定するのでしょうか。私の知る限り、content-typeを設定する箇所は、Webリクエストの2箇所のみです。
- クライアントが、サーバーに送信するbodyに対して、content-typeを設定(つまり、GETまたはPOSTで設定)
- サーバーが、レスポンスに対してcontent-typeを設定
利用可能なContent-Typeを明示する
ペイロードボディを含むメッセージを生成する送信元は、そのcontent-typeが不明でない限り、Content-Type
ヘッダフィールドを生成する必要があります。それ以外の場合、503503 - Service Unavailable
という応答で取得に失敗します。
1 2 |
('headers={"Content-Type":"application/json"}') httpRq.setRequestheader('Content-Type',application/json); |
つまり、content-type HTTPヘッダは、PUTおよびPOSTでのみ設定する必要があるということです。GETリクエストでは、クライアントが理解できるコンテンツタイプを示す「Accept」ヘッダを含めることができます。サーバーは、この情報を用いて、返信するコンテンツ タイプを決定できます。
オプションで、TALWinInetHttpClient
を用いることもできます。これは、使いやすいWinInetベースのプロトコルで、HTTPSをサポートしています。このHTTPクライアントコンポーネントは、HTTPプロトコルを介してWebでデータの送信および取得ができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function TALHTTPClient_Get(aUrl: AnsiString; feedstream, aApikey: string): string; Var LHttpClient: TALWininetHttpClient; asp: TStringArray; begin LHttpClient:= TALWininetHttpClient.create; asp:= splitStr(feedstream,','); LHttpClient.url:= Format(AURL,[HTTPEncode(asp[0]),(asp[1]),asp[2]]); LHttpClient.RequestMethod:= HTTPmt_Get; //HTTPrm_Post; LHttpClient.RequestHeader.UserAgent:=USERAGENTE; //LHttpClient.RequestHeader.CustomHeaders:= LHttpClient.RequestHeader.RawHeaderText:='x-key:'+aAPIkey; try result:= LHttpClient.Get1(LHttpClient.url); //overload; finally LHttpClient.Free; end; end; |
JSONレスポンスをグラフで使用できるデータに変換する
AGSIでは、欠落したデータも不完全なデータも表示されます。次に行うのは、取得したJSONデータをTJSONObject
を使って、プロット用に変換することです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function letGenerateJSON2(HttpRqresponseText: string): TJSONArray; var jo: TJSONObject; begin jo:= TJSONObject.Create4(HttpRqresponseText); try //writeln(jo.getstring('data')); writeln(itoa(jo.getjsonarray('data').getjsonobject(0).length)) writeln(itoa(jo.getjsonarray('data').length)) result:= jo.getjsonarray('data'); //write out to check for it:= 0 to result.length-1 do writeln(result.getjsonobject(it).getstring('gasDayStart')+':'+ result.getjsonobject(it).getstring('injection')); except writeln('EJson: '+ExceptiontoString(exceptiontype, exceptionparam)); end; end; |
関数の戻り値として取得できるこのJSON配列を、次のプロット処理に渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
procedure JSON2Plot(form1: TForm; jar: TJSONArray); var chart1: TChart; cnt: integer; sumup,tmp2,tmp: double; gday: string; begin form1.onclose:= @Form_CloseClick; chart1:= ChartInjector(form1); sumup:=0; tmp2:=0; tmp:=0; try for cnt:= 0 to jar.length-1 do begin //writeln(locate.getjsonobject(it).getstring('gasDayStart')+':'+ tmp:= jar.getjsonobject(jar.length-1-cnt).getdouble('injection'); tmp2:= jar.getjsonobject(jar.length-1-cnt).getdouble('full'); sumup:= sumup+tmp; gday:= jar.getjsonobject(jar.length-1-cnt).getstring('gasDayStart'); chart1.Series[0].Addxy(cnt,tmp,gday,clgreen); chart1.Series[1].Addxy(cnt,tmp2,'',clred); chart1.Series[2].Addxy(cnt,jar.getjsonobject(jar.length-1-cnt).getdouble('withdrawal'),'',clyellow); end; except writeln('EPlot: '+ExceptiontoString(exceptiontype, exceptionparam)); end; PrintF('Landrange %d: Injection sum: %.2f',[jar.length-1,sumup]); end; |
グラフの系列に含まれるデータ
グラフには、4つのデータ系列をプロットします(タイムラインを含む)。
- 注入量(ガス日あたり)
- 充足率 – Storage / WGV(in%)
- 放出量(ガス日あたり – 2桁精度)
- ガス日(レポートの開始ガス日)
時系列は、ガス日単位です。
「ガス日」とは、 UTCで5:00~5:00(夏時間では、4:00~4:00)を意味します。ガス日は、ヨーロッパ中央時間(CET)の場合、UTC+1、夏時間(CEST)の場合UTC+2となります(定義: CAM Network Code Specificationsを参照)。
データ取得用APIにアクセスする方法
APIアクセスは、RESTライクなインターフェイス (Representational State Transfer) で提供され、上述したレスポンスヘッダにcontent-typeを含む、JSON形式でデータベースリソースを公開しています。
他言語向けのデータアクセス方法
データサイエンスビジョンのコードには、使用例を示すサンプルが添付されており、Python3、Delphi、Jupyter-Notebook、Object Pascal、maXbox4で使用できます。なお、このAPIサービスは無料公開されています。このプラットフォーム上で現在利用可能なデータのみにアクセスできます。
メモ:システムから直接データを抽出するには、ブラウザで以下のリンクを直接クリックしてください。
https://agsi.gie.eu/api?type=eu
GIEデータAPIに関する情報
GIEでは、AGSIおよびAGSI+ストレージデータに関するAPI(Application Programming Interface)サービスを提供しています。APIドキュメントの作成は進行中であり、サービスの使用方法に関するサンプルやガイダンスも提供しており、APIキーを取得するための登録を行うと利用できます。以下は、過去半年のデータプロットです。
サンプルスクリプト/コードの入手先
スクリプトと画像は、https://github.com/maxkleiner/agsi-dataからダウンロードできます。
ダウンロードコードのサンプル
以下は、maXbox4統合用のWinAPIDownloadクラスからの抜粋です。
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 |
TWinApiDownload = class(TObject) private fEventWorkStart: TEventWorkStart; fEventWork: TEventWork; fEventWorkEnd: TEventWorkEnd; fEventError: TEventError; fURL: string; fUserAgent: string; fStop: Boolean; fActive: Boolean; fCachingEnabled: Boolean; fProgressUpdateInterval: Cardinal; function GetIsActive: Boolean; public constructor Create; destructor Destroy; override; function CheckURL(aURL: string): Integer; function Download(Stream: TStream): Integer; overload; function Download(var res: string): Integer; overload; function ErrorCodeToMessageString(aErrorCode: Integer): string; procedure Stop; procedure Clear; property UserAgent: string read fUserAgent write fUserAgent; property URL: string read fURL write fURL; property DownloadActive:Boolean read GetIsActive; property CachingEnabled:Boolean read fCachingEnabled write fCachingEnabled; property UpdateInterval:Cardinal read fProgressUpdateInterval write fProgressUpdateInterval; property OnWorkStart: TEventWorkStart read fEventWorkStart write fEventWorkStart; property OnWork: TEventWork read fEventWork write fEventWork; property OnWorkEnd: TEventWorkEnd read fEventWorkEnd write fEventWorkEnd; property OnError: TEventError read fEventError write fEventError; end; |
参考文献とリンク
- https://agsi.gie.eu/api
- https://www.gie.eu/transparency-platform/GIE_API_documentation_v006.pdf
- https://svn.code.sf.net/p/alcinoe/code/demos/ALWinInetHTTPClient/_source/Unit1.pas
- https://docwiki.embarcadero.com/Libraries/Sydney/en/System.Net.HttpClient.THTTPClient.Post
- ドキュメントとツール: https://maxbox4.wordpress.com (Script Ref: 1154_energy_api_agsi_plot14.txt)
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition