JavaとGridDBによるシンプルなデータ可視化ツールの作成

本記事では、主にGridDBのデータセットをWebプロジェクトで開発し、動的な可視化を実現する方法について紹介します。プロジェクトのソースコードは、GitHubにアップロードされています。

<https://github.com/jianxiang78/DataVisualization>

プロジェクトが実行された後、ローカルコンピュータのブラウザにアドレス http://127.0.0.1:8080 を入力します。スクリーンショットのアニメーション効果は次のとおりです。

[<img src="https://griddb.net/en/wp-content/uploads/2023/05/DataVisualization.gif" alt="" width="720" height="314" class="aligncenter size-full wp-image-29566" />][1]

    Technology Stack
    Operating System: Ubuntu-18.04.6
    Front-end: HTML, JavaScript, ECharts 5.4.0, WebSocket
    Back-end: Java 11.0.2, Spring Boot, WebSocket, Maven
    Development tool: IntelliJ IDEA 2019
    Database: GridDB 5.1.0
    

このプロジェクトは、シンプルで完全なWEBマイクロサービスシステムです。このプロジェクトは、モノのインターネットシナリオにおける温度計のデータ伝送をシミュレートし、ウェブ上でリアルタイムにデータセットの変化を可視化します。ウェブページを開いた後、ウェブページを更新する必要がなく、データの変化をウェブページに直接表示することができます。

温度計のデータセットはGridDBに格納されています。プロジェクトのコードでは、タイムドタスクプログラムが、GridDBに常に挿入されるモノのインターネットデバイスのデータをシミュレートしています。

フロントエンドは、EChartsを使ってゲージを表示し、SockJsとStompを使ってWebSocketでバックエンドと通信しています。バックエンドはSpringBootを使ってWebSocketのサーバーエンドを実装し、GridDBからデータを抽出し、フロントエンドにデータを転送しています。

私のローカルOSはUbuntu - 18.04.6を使用しており、GridDBをローカルにインストールします。インストール手順は、公式サイトを参照することで非常に簡単に行うことができます。ウェブサイトのリンクは以下の通りです。

<https://docs.griddb.net/gettingstarted/using-apt/#install-with-apt-get>

インストールしたGridDBのバージョンは5.1.0です。インストール後、データベースに接続するためのgridstore.jarライブラリが組み込まれています。それをプロジェクトコードの "lib "ディレクトリにコピーしています。gridstore.jarライブラリはJDK11.0.2で開発されているため、プロジェクト全体でも互換性のためにJDK11.0.2を使っています。

## フロントエンドでの通信の確立方法

このプロジェクトでは、フロントエンドとバックエンドがロングリンク通信を確立できるWebSocketモードを採用しています。私のプロジェクトファイルindex.htmlに、3つのJavaScriptライブラリファイルを導入します。コードは以下の通りです。

    <script src="./js/echarts/echarts.min.js"></script> 
    <script src="./js/sockjs-0.3.4.js"></script> 
    <script src="./js/stomp.js"></script>
    

ファイルindex.htmlでは、EChartsを使用してダッシュボードを表示し、SockJsとStompを使用してWebSocketのロングリンクを完成させます。カスタム関数fromServer()でロングリンクを実装しています。コードは以下の通りです。

```java
function fromServer() {
        var stomp = null;
        function connect() {
            var socket = new SockJS('/webSocketServer');
            stomp = Stomp.over(socket);
            var headers={
                "token":"data_token"
            };
            stomp.connect(headers, connectCallback ,errorCallback );
        };
        function connectCallback (frame) {
            //Callback function after successful connection
            console.log('Connected successful: ' + frame);
            stomp.subscribe('/data/iot1', function(serverResult){
                myChart1.setOption(JSON.parse(serverResult.body));
            });
            stomp.subscribe('/data/iot2', function(serverResult){
                myChart2.setOption(JSON.parse(serverResult.body));
            });
        }
        function errorCallback(frame){
            //The callback function after the connection fails. This function calls the connection function again
            console.log('Connected fails: ' + frame);
            setTimeout(connect,2000);
        }
        connect();
    }
```

上のコードの

      var socket = new SockJS('/webSocketServer');


では、バックエンドのアドレス "/webSocketServer" とハンドシェイクするための変数ソケットを定義します。この "/webSocketServer" というアドレスは、バックエンドのSpringBootに設定されているアクセスポイントです。この設定については、以下のコードで説明します。

      stomp = Stomp.over(socket);
    

変数stompを定義し、通信にSTOMPプロトコルを使用します。

      stomp.connect(headers, connectCallback ,errorCallback );
    

リンクの確立を開始し、connectCallback()とerrorCallback()という2つのコールバック関数を追加します。それぞれ接続成功と接続失敗を処理します。

## バックエンドでデータセットを転送する方法

SpringBootフレームワークでは、まずWebSocketのアクセスポイントを設定する。ここでは「/webSocketServer」というアドレスを設定しています。WebSocketConfig.javaというクラスで設定します。コードは以下の通りです。

```java
package com.auto17.dataVisualization.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/webSocketServer")
                .setAllowedOrigins("*")
                .withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/data");
    }
}
```

dbConnect.java は、GridDB のデータセットの操作を実装するクラスです。本クラスのメソッドについて紹介します。

getConnect() : GridDB の接続獲得を実現する  
close() : GridDB の接続を閉じる  
put(String containerName, Iot iot) : 時系列データの挿入を実現する  
queryTopOne(String containerName) : 時系列データの最新アイテムの抽出を実現する  

Iot.java クラスがデバイスデータのエンティティオブジェクトであり、GridDB の @RowKey というアノテーションを使用して主キーをマークします。

```java
package com.auto17.dataVisualization.DTO;

import com.toshiba.mwcloud.gs.RowKey;
import java.util.Date;

public class Iot {
    @RowKey Date timestamp;
    String name;
    int value;
    public Iot(){}
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
}
```

このプロジェクトでは、Internet of Thingsデバイスが送信するデータをシミュレートするために、スケジュールされたタスクを実行するクラスTaskSimulate.javaが作成されています。このクラスでは、タイミングタスクに2つのシミュレータを表す関数simulator1()とsimulator2()が用意されています。これらの関数は、シミュレーションデータを生成し、GridDBに挿入します。コードは以下の通りです。

```java
@Component
public class TaskSimulate {
    ...
    @Scheduled(cron ="*/3 * * * * ?")
    public void simulator1() {
        ...
    }

    @Scheduled(cron ="*/5 * * * * ?")
    public void simulator2() {
        ...
    }
}
```

また、IotTask.java には、スケジュールされたタスクを実行する IoT1() と IoT2() という関数が存在します。これらの関数は、GridDBから対応するデバイスのデータを抽出するために動作します。得られたデータは、WebSocketを通じてフロントエンドにプッシュされます。以下はそのコードです。

```java
package com.auto17.dataVisualization.iot;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.auto17.dataVisualization.DTO.Iot;
import com.auto17.dataVisualization.grid.dbConnect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class IotTask {
    public static SimpMessagingTemplate messagingTemplate=null;

    private final Logger logger = LoggerFactory.getLogger(IotTask.class);
    private int index;
    private Iot iot;

    //Execute every X seconds
    @Scheduled(fixedRate = 1000 * 4)
    public void IoT1(){
        String iotName="iot1";
        iot = dbConnect.queryTopOne(iotName);
        if(iot==null){
            return;
        }
        index = iot.getValue();
        logger.info("The server sends a message to the client ["+iotName+"]:");

        if(messagingTemplate!=null){
            messagingTemplate.convertAndSend(
                    "/data/"+iotName, ioTData("Temperature Gauge 1",index));
        }else {
            logger.info(iotName+" messagingTemplate is null");
        }
    }

    //Execute every X seconds
    @Scheduled(fixedRate = 1000 * 5)
    public void IoT2(){
        String iotName="iot2";
        iot = dbConnect.queryTopOne(iotName);
        if(iot==null){
            return;
        }
        index = iot.getValue();
        logger.info("The server sends a message to the client ["+iotName+"]:");
        if(messagingTemplate!=null){
            messagingTemplate.convertAndSend(
                    "/data/"+iotName, ioTData("Temperature Gauge 2",index));
        }else {
            logger.info(iotName+" messagingTemplate is null");
        }
    }

    private String ioTData(String name,int value) {
        JSONArray arrayRoot=new JSONArray();
        JSONObject data1=new JSONObject();
        JSONArray arrayNode1=new JSONArray();
        JSONObject Node1=new JSONObject();
        data1.put("name",name);
        data1.put("value",value);
        arrayNode1.add(data1);
        Node1.put("data",arrayNode1);
        arrayRoot.add(Node1);

        JSONObject data2=new JSONObject();
        JSONArray arrayNode2=new JSONArray();
        JSONObject Node2=new JSONObject();
        data2.put("name",name);
        data2.put("value",value);
        arrayNode2.add(data2);
        Node2.put("data",arrayNode2);
        arrayRoot.add(Node2);

        JSONObject ioTData=new JSONObject();
        ioTData.put("series",arrayRoot);
        return ioTData.toJSONString();
    }
}
```

上記のコードでは、関数 ioTData(String name, int value)がデータをJSON形式に変換しています。また、同じデータを保存するために、データ群ごとに2つのノードを作成しています。これは、EchartsのTemperature Gaugeが効果を表示するために、一度に2つのノードデータを必要とするためです。

本稿では、GridDBのデータセットに対するデータ可視化の簡単な実装の概要を説明しています。Internet of Thingsのビジネスシナリオでは、何千ものInternet of Thingsデバイスが存在し、そのデータは膨大な量になります。本記事では、そのアイデアを提供しております。もし興味があれば、GitHubからコードをダウンロードすることができます。

## ローカルで簡単に実行する方法

GitHubからプロジェクトコードをダウンロードした後、ZIP圧縮されたパッケージになっています。解凍後、コードは「DataVisualization」ディレクトリにあります。開発ツールのIntelliJ IDEAで、ファイル→新規→既存のソースからのプロジェクトを通して、ディレクトリ「DataVisualization」を指定すると、Mavenモードでプロジェクトコードを読み込むことができます。

プロジェクトの設定ファイル「pom.xml」で、Jarライブラリのパッケージング構成を設定しました。Mavenで「install」操作を実行すると、サブディレクトリ「target」の下に、モジュール「micro-web.jar」が生成されます。

生成された「micro-web.jar」は、TomcatのWebコンテナで実行可能なJarライブラリとなります。コマンドを入力します。

    Java -jar micro-web.jar
    

Webプロジェクトを起動することができます。起動後、Webブラウザにアドレス "http://127.0.0.1:8080" を入力します。プロジェクトコードのデモ効果を見ることができます。

 [1]: https://griddb.net/en/wp-content/uploads/2023/05/DataVisualization.gif