IoTデータのビジュアライズ

センサーから取得した、温度、湿度、照度、土壌湿度、画像などをビジュアライズする。

サンプルコード

内容

  • CSVデータのロード
  • データをパラメータにマップして可視化する。
  • データを時系列にマップして可視化する。

CSVデータのロード

今回のCSVデータは次のような形式になっている。

"2017-04-19 17:29:43","28.19","21.50","994.82","517","297","20170419_172943.jpg"

取得時刻,温度,湿度,大気水分量,照度,土壌水分量,画像ファイル名

とある時刻のデータをすべて格納するStatusクラス作成しておくことにする。

コンストラクタは、csvの1行分のデータを文字列で渡すことでデータを保持するようにする。

Statusクラスのコード

class Status{
  String time;
  float ondo;
  float shitsudo;
  float taiki;
  float shodo;
  float dojo;
  String file;

  //Statusのコンストラクタ
  Status(String _str){
      //引数(1行分のデータ)から各値を取り出す。
      //「"」をreplace()で除去しつつ、Strignからfloatへ変換など。
      String data[] = _str.split(",");
      time = data[0].replace("\"","");
      ondo = float(data[1].replace("\"",""));
      shitsudo = float(data[2].replace("\"",""));
      taiki = float(data[3].replace("\"",""));
      shodo = float(data[4].replace("\"",""));
      dojo = float(data[5].replace("\"",""));
      file = data[6].replace("\"","");
  }
}

スケッチのメインコード

ArrayList<Status> statuslog; //CSVのデータをまとめたStatusクラスの可変長配列


void setup(){
  size(600,600);
  background(0);
  noStroke();
  fill(255);

  colorMode(HSB);

  //statuslogを初期化する
  statuslog = new ArrayList<Status>();

  //CSVを読み込んでstatuslogへ格納する
  String dataLines[];
  dataLines = loadStrings("sensor_edi.csv");

  //CSVから1行ずつ取り出して処理
  for(int i=0; i < dataLines.length; i++){

    //1行を「,」で分割してdata配列に格納
    String data[] = dataLines[i].split(",");

    //データに欠損がない場合のみstatuslogに追加
    if(data.length == 7){
      Status status = new Status(dataLines[i]);
      statuslog.add(status);
    }
  }
}

全データをパラメータにマップしてポスターを作る

サンプルコード:sketch000_basic

温度をx座標、湿度をy座標、大気水分をドットの大きさ、照度を色相、土壌水分をアルファにマップしてプロットしたポスターを作ってみる。

結果

sketch000_basicのメインコード

ポイント

  • statusオブジェクトを格納する可変長配列、statuslogを用意する。

  • 各データの最大値、最小値を調べる

  • statuslogからデータを取り出しながら描画する

ArrayList<Status> statuslog; //CSVのデータをまとめたStatusクラスの可変長配列

//各値の最大値、最小値用の変数
float ondoMax; 
float ondoMin;
float shitsudoMax;
float shitsudoMin;
float taikiMax;
float taikiMin;
float shodoMax;
float shodoMin;
float dojoMax;
float dojoMin;


void setup(){
  size(600,600);
  background(0);
  noStroke();
  fill(255);

  colorMode(HSB);

  //statuslogを初期化する
  statuslog = new ArrayList<Status>();

  //CSVを読み込んでstatuslogへ格納する
  String dataLines[];
  dataLines = loadStrings("sensor_edi.csv");

  //CSVから1行ずつ取り出して処理
  for(int i=0; i < dataLines.length; i++){

    //1行を「,」で分割してdata配列に格納
    String data[] = dataLines[i].split(",");

    //データに欠損がない場合のみstatuslogに追加
    if(data.length == 7){
      Status status = new Status(dataLines[i]);
      statuslog.add(status);

      //ここからさきは最大値、最小値を調べている
      if(i==0){
        ondoMax = ondoMin = status.ondo;
        shitsudoMax = shitsudoMin = status.shitsudo;
        taikiMax = taikiMin = status.taiki;
        shodoMax = shodoMin = status.shodo;
        dojoMax = dojoMin = status.dojo;

      }else{
        if(status.ondo > ondoMax) ondoMax = status.ondo;
        else if(status.ondo < ondoMin) ondoMin = status.ondo; 

        if(status.shitsudo > shitsudoMax) shitsudoMax = status.shitsudo;
        else if(status.shitsudo < shitsudoMin) shitsudoMin = status.shitsudo; 

        if(status.taiki > taikiMax) taikiMax = status.taiki;
        else if(status.taiki < taikiMin) taikiMin = status.taiki; 

        if(status.shodo > shodoMax) shodoMax = status.shodo;
        else if(status.shodo < shodoMin) shodoMin = status.shodo;

        if(status.dojo > dojoMax) dojoMax = status.dojo;
        else if(status.dojo < dojoMin) dojoMin = status.dojo;
      }


    }

  }
}


void draw(){
  background(0);
  //座標、円の大きさ、色相、アルファにデータを当てはめて描画してみる。
  for(int i=0; i<statuslog.size(); i++){
    float x = map(statuslog.get(i).ondo, ondoMin, ondoMax, 0, width);
    float y = map(statuslog.get(i).shitsudo, shitsudoMin, shitsudoMax, 0, height);
    float w = map(statuslog.get(i).taiki, taikiMin, taikiMax, 5,50);
    float hue = map(statuslog.get(i).shodo, shodoMin, shodoMax, 128,255);
    float alpha = map(statuslog.get(i).dojo, dojoMax, dojoMin, 10, 200);
    fill(hue,255,255,alpha);
    ellipse(x, y, w, w);
  }
  int dataNum = statuslog.size();
  String logStart = statuslog.get(0).time;
  String logEnd = statuslog.get(dataNum-1).time;

  fill(255);
  textSize(14);
  text("lifelog of the tree, data size: " + dataNum , 10, height - 40);
  text(logStart + " to " + logEnd , 10,  height - 20);


}

時系列にデータをマップする

サンプルコード:sketch001_timeline

時間をデータとして扱うには以下の問題を考慮する必要がある

  • 取得時間は文字列である
  • 取得時間の間隔は一定であるとは限らない(エラー、誤差など)

ここでは、取得時間(文字列)をUnix時間に変換して数値として扱えるようにする。

Statusクラスの改良

取得時間の文字列をUnix時間に変換する関数「toUnixtime()」を用意。

クラスのプロパティ(変数)にunix時間を格納する「utime」を用意。

コンストラクタの実行時にunix時間を計算する。

import java.util.*; //unix時間の計算に必要

class Status{
  String time; //example 2017-05-09 16:36:35
  int utime;
  float ondo;
  float shitsudo;
  float taiki;
  float shodo;
  float dojo;
  String file;

  //Statusのコンストラクタ
  Status(String _str){
      //引数(1行分のデータ)から各値を取り出す。
      //「"」をreplace()で除去しつつ、Strignからfloatへ変換など。
      String data[] = _str.split(",");
      time = data[0].replace("\"","");
      ondo = float(data[1].replace("\"",""));
      shitsudo = float(data[2].replace("\"",""));
      taiki = float(data[3].replace("\"",""));
      shodo = float(data[4].replace("\"",""));
      dojo = float(data[5].replace("\"",""));
      file = data[6].replace("\"","");

      utime = toUnixtime(time);
  }

  //文字列の時刻(2017-05-09 16:36:35)をUnix時間(1970-01-01 00:00:00からの秒数)に変換
  int toUnixtime(String _time){
      int uTime = 0;
      String HIZUKE = _time.split(" ")[0]; //2017-05-09
      String JIKOKU = _time.split(" ")[1]; //16:36:35
      int year   = int( HIZUKE.split("-")[0] ); //2017
      int month  = int( HIZUKE.split("-")[1] ); //5
      int day    = int( HIZUKE.split("-")[2] ); //9
      int hour   = int( JIKOKU.split(":")[0] ); //16
      int min    = int( JIKOKU.split(":")[1] ); //36
      int sec    = int( JIKOKU.split(":")[2] ); //35

      //println(_time+" "+year+"/"+month+"/"+day+ " "+hour+":"+min+":"+sec );

      Date d = new Date(year, month-1, day, hour, min, sec);
      //println(d);
      uTime = (int)(d.getTime()/1000); //unix時間に変換
      println(uTime); //1322917251

    return uTime;
  }
}

左から右へ時系列で、円の大きさ、色相、アルファにデータを当てはめて描画

sketch000_basicのサンプルを少し改良して、x座標をutimeから計算するように変更してみた。

結果

sketch001_timelineのメインコード

ArrayList<Status> statuslog; //CSVのデータをまとめたStatusクラスの可変長配列

//各値の最大値、最小値用の変数
float ondoMax; 
float ondoMin;
float shitsudoMax;
float shitsudoMin;
float taikiMax;
float taikiMin;
float shodoMax;
float shodoMin;
float dojoMax;
float dojoMin;
float utimeMax;//utimeの最大値
float utimeMin;//utimeの最小値


void setup(){
  size(900,600);
  background(0);
  noStroke();
  fill(255);
  noLoop();

  colorMode(HSB);

  //statuslogを初期化する
  statuslog = new ArrayList<Status>();

  //CSVを読み込んでstatuslogへ格納する
  String dataLines[];
  dataLines = loadStrings("sensor_edi.csv");

  //CSVから1行ずつ取り出して処理
  for(int i=0; i < dataLines.length; i++){

    //1行を「,」で分割してdata配列に格納
    String data[] = dataLines[i].split(",");

    //データに欠損がない場合のみstatuslogに追加
    if(data.length == 7){
      Status status = new Status(dataLines[i]);
      statuslog.add(status);

      //ここからさきは最大値、最小値を調べている
      if(i==0){
        ondoMax = ondoMin = status.ondo;
        shitsudoMax = shitsudoMin = status.shitsudo;
        taikiMax = taikiMin = status.taiki;
        shodoMax = shodoMin = status.shodo;
        dojoMax = dojoMin = status.dojo;
        utimeMax = utimeMin = float(status.utime);

      }else{
        if(status.ondo > ondoMax) ondoMax = status.ondo;
        else if(status.ondo < ondoMin) ondoMin = status.ondo; 

        if(status.shitsudo > shitsudoMax) shitsudoMax = status.shitsudo;
        else if(status.shitsudo < shitsudoMin) shitsudoMin = status.shitsudo; 

        if(status.taiki > taikiMax) taikiMax = status.taiki;
        else if(status.taiki < taikiMin) taikiMin = status.taiki; 

        if(status.shodo > shodoMax) shodoMax = status.shodo;
        else if(status.shodo < shodoMin) shodoMin = status.shodo;

        if(status.dojo > dojoMax) dojoMax = status.dojo;
        else if(status.dojo < dojoMin) dojoMin = status.dojo;

        if(status.utime > utimeMax) utimeMax = float(status.utime);  //時刻の最大値
        else if(status.utime < utimeMin) utimeMin = float(status.utime);  //時刻の最小値
      }


    }

  }
}


void draw(){
  background(0);
  //左から右へ時系列で、円の大きさ、色相、アルファにデータを当てはめて描画してみる。
  for(int i=0; i<statuslog.size(); i++){
    float x = map(statuslog.get(i).utime, utimeMin, utimeMax, 0, width);
    float y = map(statuslog.get(i).shitsudo, shitsudoMin, shitsudoMax, height, 0); //上下を反転してmapする(最大値->0,最小値->heightにマップ)
    float w = map(statuslog.get(i).taiki, taikiMin, taikiMax, 1,20);
    float hue = map(statuslog.get(i).shodo, shodoMin, shodoMax, 128,255);
    float alpha = map(statuslog.get(i).dojo, dojoMax, dojoMin, 10, 200);
    fill(hue,255,255,alpha);
    ellipse(x, y, w, w);
  }
  int dataNum = statuslog.size();
  String logStart = statuslog.get(0).time;
  String logEnd = statuslog.get(dataNum-1).time;

  fill(255);
  textSize(14);
  text("lifelog of the tree, data size: " + dataNum , 10, height - 40);
  text(logStart + " to " + logEnd , 10,  height - 20);


}

日付のグリッドを入れる

サンプルコード:sketch002_timeline_grid

4/27,28,5/1は曇りで照度が低く、5/7は暑く乾燥していたことがわかる。

過去の東京の天気と比べてみよう。

sketch002_timeline_gridのメインコード

ArrayList<Status> statuslog; //CSVのデータをまとめたStatusクラスの可変長配列

//各値の最大値、最小値用の変数
float ondoMax; 
float ondoMin;
float shitsudoMax;
float shitsudoMin;
float taikiMax;
float taikiMin;
float shodoMax;
float shodoMin;
float dojoMax;
float dojoMin;
long utimeMax;//utimeの最大値
long utimeMin;//utimeの最小値

//グリッドの両端を指定
String timeGridStart = "2017-04-19 00:00:00";
String timeGridEnd = "2017-05-10 00:00:00";
int timeGridStep = 24*3600*1000; //24時間間隔とする(24時間*3600秒*1000ミリ秒)
long utimeGridStart = toUnixtime(timeGridStart);
long utimeGridEnd = toUnixtime(timeGridEnd);


void setup(){
  size(1200,600);
  background(0);
  noStroke();
  fill(255);
  noLoop();

  colorMode(HSB);

  //statuslogを初期化する
  statuslog = new ArrayList<Status>();

  //CSVを読み込んでstatuslogへ格納する
  String dataLines[];
  dataLines = loadStrings("sensor_edi.csv");

  //CSVから1行ずつ取り出して処理
  for(int i=0; i < dataLines.length; i++){

    //1行を「,」で分割してdata配列に格納
    String data[] = dataLines[i].split(",");

    //データに欠損がない場合のみstatuslogに追加
    if(data.length == 7){
      Status status = new Status(dataLines[i]);
      statuslog.add(status);

      //ここからさきは最大値、最小値を調べている
      if(i==0){
        ondoMax = ondoMin = status.ondo;
        shitsudoMax = shitsudoMin = status.shitsudo;
        taikiMax = taikiMin = status.taiki;
        shodoMax = shodoMin = status.shodo;
        dojoMax = dojoMin = status.dojo;
        utimeMax = utimeMin = status.utime;

      }else{
        if(status.ondo > ondoMax) ondoMax = status.ondo;
        else if(status.ondo < ondoMin) ondoMin = status.ondo; 

        if(status.shitsudo > shitsudoMax) shitsudoMax = status.shitsudo;
        else if(status.shitsudo < shitsudoMin) shitsudoMin = status.shitsudo; 

        if(status.taiki > taikiMax) taikiMax = status.taiki;
        else if(status.taiki < taikiMin) taikiMin = status.taiki; 

        if(status.shodo > shodoMax) shodoMax = status.shodo;
        else if(status.shodo < shodoMin) shodoMin = status.shodo;

        if(status.dojo > dojoMax) dojoMax = status.dojo;
        else if(status.dojo < dojoMin) dojoMin = status.dojo;

        if(status.utime > utimeMax) utimeMax = status.utime;  //時刻の最大値
        else if(status.utime < utimeMin) utimeMin = status.utime;  //時刻の最小値
      }


    }

  }
}


void draw(){
  background(0);

  //時間のグリッドを引く

  stroke(0,0,200,128);
  for(long i=utimeGridStart; i<=utimeGridEnd; i+=timeGridStep){
    float x = map(i , utimeGridStart, utimeGridEnd, 0, width);
    line(x, 0, x, height);
    fill(255,128);
    pushMatrix();
    translate(x, 20);
    rotate(PI/2);
    text(toTimeString(i).substring(0,16), 0, 0);
    popMatrix();

  }
  noStroke();



  //左から右へ時系列で、円の大きさ、色相、アルファにデータを当てはめて描画してみる。
  for(int i=0; i<statuslog.size(); i++){
    float x = map(statuslog.get(i).utime, utimeMin, utimeMax, 0, width);
    float y = map(statuslog.get(i).shitsudo, shitsudoMin, shitsudoMax, height, 0); //上下を反転してmapする(最大値->0,最小値->heightにマップ)
    float w = map(statuslog.get(i).taiki, taikiMin, taikiMax, 1,20);
    float hue = map(statuslog.get(i).shodo, shodoMin, shodoMax, 128,255);
    float alpha = map(statuslog.get(i).dojo, dojoMax, dojoMin, 10, 200);
    fill(hue,255,255,alpha);
    ellipse(x, y, w, w);
  }
  int dataNum = statuslog.size();
  String logStart = statuslog.get(0).time;
  String logEnd = statuslog.get(dataNum-1).time;

  fill(255);
  textSize(14);
  text("lifelog of the tree, data size: " + dataNum , 10, height - 40);
  text(logStart + " to " + logEnd , 10,  height - 20);


}

//文字列の時刻(2017-05-09 16:36:35)をUnix時間(1970-01-01 00:00:00からの秒数)に変換
long toUnixtime(String _time){
    long uTime = 0;
    String HIZUKE = _time.split(" ")[0]; //2017-05-09
    String JIKOKU = _time.split(" ")[1]; //16:36:35
    int year   = int( HIZUKE.split("-")[0] ); //2017
    int month  = int( HIZUKE.split("-")[1] ); //5
    int day    = int( HIZUKE.split("-")[2] ); //9
    int hour   = int( JIKOKU.split(":")[0] ); //16
    int min    = int( JIKOKU.split(":")[1] ); //36
    int sec    = int( JIKOKU.split(":")[2] ); //35

    //println(_time+" "+year+"/"+month+"/"+day+ " "+hour+":"+min+":"+sec );

    Date d = new Date(year, month-1, day, hour, min, sec);
    //println(d);
    uTime = d.getTime(); //unix時間に変換
    println(uTime); //1322917251

  return uTime;
}

//Unix時間から時間の文字列へ変換
String toTimeString(long utime){
  Date d = new Date(utime);
  return d.toString();

}


演習

時間もデータとして扱うことができるようになったので、時間と空間をうまく利用してデータをビジュアライズしてみる。

例:

  • 3次元空間上に時系列にデータをプロットしてみる。
  • 時間、温度、照度を3軸に割当、カメラ画像をプロットする
  • etc...

results matching ""

    No results matching ""