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

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

https://github.com/jianxiang78/DataVisualization

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

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()でロングリンクを実装しています。コードは以下の通りです。

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というクラスで設定します。コードは以下の通りです。

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 というアノテーションを使用して主キーをマークします。

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に挿入します。コードは以下の通りです。

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

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

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

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” を入力します。プロジェクトコードのデモ効果を見ることができます。

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *