2015年10月15日

追蹤熱島效應

螢幕熱島圖
這是我利用Arduino、GPS模組和溫度感測器做出的新竹市熱島效應調查圖



 我在Make雜誌37期看到Forrest M. Mims III,寫的這篇Amateur Scientist: Tracking Heat Islands驚豔不已,也很想作一個。

Forris M.Mims 用的是Vernier LabQuest 2加上GPS功能,然後再用熱敏電阻去測溫,不過LabQuest的價錢...所以我要用比較平價的方式去達成。就決定用Arduino加上GPS模組和溫度感測器了。

其實不一定要用GPS模組,只要能接受GPS訊號的手機(要能存軌跡),再加上一個能每秒測溫度和紀錄時間的arduino。帶出去同時測量這些訊號之後,事後再用時間當索引,把每個溫度測定的座標抓出來也是可以用。

我使用的GPS模組是IM120417017 GPS模組,測溫是用LM35溫度感測器,裝在車子上載著繞一圈。得到GPS定位對應氣溫的資料後,再用QGIS這個地理資訊系統的軟體處理出圖。

IMAG0131


整個裝置就是這樣一盆,然後把溫度感測器接到車外,GPS天線靠近窗邊。供電用的是行動電源,因為輸出只有5V,所以我是連接到Arduino的USB port。
IMAG0095


得到的結果和預想的一樣,在大馬路交叉口(東大路和經國路交叉口)或是鬧區(新竹車站),溫度比較高。而在綠地附近,溫度就低了。當然這樣車載調查的前提是,這段調查期間溫度不至於隨時間變化太大。

放後照鏡旁邊,雖然測的時候是陰天,但是也有可能受到陽光照射影響,而且也有可能受到行車速度影響。陽光照射可以用個容器改善,但是速度要怎麼改善?



再來講一些程式細節的東西,GPS模組輸出的資料較難閱讀,所以需要TinyGPS++的函式庫來解讀。輸出的東西,同時輸出到Serial port、LCD和SD卡裡頭。

最後得到的文字檔,改個檔名變成csv,可以用Google地圖來作資訊視覺化。
從Google地圖的「我的地圖」,找到「建立地圖」,接著用「新增圖層」,用「匯入」的方式把csv檔送進去。然後指定經緯度的欄位之後,可以將不同的溫度範圍用不同色階表示。不過Google地圖一個圖層只能匯入2000個點,所以後來我改用QGIS來處理。


GPS和溫度的文字資料,用「Layer/Add Layer/Add Delimited Text Layer」來匯入

先到Plugins裡安裝OpenLayers Plugin,這樣才能使用Google Map的圖層來套疊(安裝與使用方式看這
Arduino的程式碼則是在此

#include <SD.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>


#define DHTPIN 4     // what pin we're connected to
#define DHTTYPE DHT11   // DHT 11
DHT dht(DHTPIN, DHTTYPE);

const int chipSelect = 10;

TinyGPSPlus gps;

//LCD
LiquidCrystal_I2C lcd(0x27,16,2);


void setup()
{
  Serial.begin(9600);

  pinMode(10, OUTPUT);

  lcd.init();                      // initialize the lcd
  lcd.backlight();
  lcd.clear();

  if (!SD.begin(chipSelect)) {
    lcd.setCursor(0,0);
    lcd.print("No Card inserted");
    return;
  }
  Serial.print("lat");
  Serial.print(F(","));
  Serial.print("lng");
  Serial.print(F(","));
  Serial.print("date");
  Serial.print(F(","));
  Serial.print("time");
  Serial.print(F(","));
  Serial.print("humidity(%)");
  Serial.print(F(","));
  Serial.print("Temperature(C)");
  Serial.print(F(","));
  Serial.print("Heat Index(C)");
  Serial.print(F(","));
  Serial.println();

}

void loop()
{

  float h = dht.readHumidity();
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();
  // Compute heat index in Celsius (isFahreheit = false)
  float hic = dht.computeHeatIndex(t, h, false);

  char temp = 0;

  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  if(dataFile)
  {
    while(Serial.available())
    {
      temp = Serial.read();
      if(gps.encode(temp))
      {
        displayInfo();

        Serial.print(F(","));
        Serial.print(h);
        Serial.print(F(","));
        Serial.print(t);
        Serial.print(F(","));
        Serial.print(hic);
        Serial.println();

        lcd.setCursor(0,0);
        lcd.print(gps.location.lat(), 6);
        lcd.setCursor(11,0);
        lcd.print(t);
        lcd.setCursor(0,1);
        lcd.print(gps.location.lng(), 6);
        lcd.setCursor(11,1);
        lcd.print(h);  

        dataFile.print(gps.location.lat(), 6);
        dataFile.print(F(","));
        dataFile.print(gps.location.lng(), 6);
        dataFile.print(F(","));
        dataFile.print(gps.date.month());
        dataFile.print(F("/"));
        dataFile.print(gps.date.day());
        dataFile.print(F("/"));
        dataFile.print(gps.date.year());
        dataFile.print(F(","));
        if (gps.time.hour() < 10) dataFile.print(F("0"));
        dataFile.print(gps.time.hour());
        dataFile.print(F(":"));
        if (gps.time.minute() < 10) dataFile.print(F("0"));
        dataFile.print(gps.time.minute());
        dataFile.print(F(":"));
        if (gps.time.second() < 10) dataFile.print(F("0"));
        dataFile.print(gps.time.second());
        dataFile.print(F("."));
        if (gps.time.centisecond() < 10) dataFile.print(F("0"));
        dataFile.print(gps.time.centisecond());
        dataFile.print(F(","));
        dataFile.print(t);
        dataFile.print(F(","));
        dataFile.print(h);
        dataFile.print(F(","));
        dataFile.println(hic);

      }

    }
    dataFile.close();
  }
}

void displayInfo()
{

  //  Serial.print(F("Location: "));
  if (gps.location.isValid())
  {
    Serial.print(gps.location.lat(), 6);
    Serial.print(F(","));
    Serial.print(gps.location.lng(), 6);
    Serial.print(F(","));


  }
  else
  {
    Serial.print(F("INVALID"));
  }

  //  Serial.print(F("  Date/Time: "));
  if (gps.date.isValid())
  {
    Serial.print(gps.date.month());
    Serial.print(F("/"));
    Serial.print(gps.date.day());
    Serial.print(F("/"));
    Serial.print(gps.date.year());

  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.print(F(","));
  if (gps.time.isValid())
  {
    if (gps.time.hour()< 10) Serial.print(F("0"));
    Serial.print(gps.time.hour());
    Serial.print(F(":"));
    if (gps.time.minute() < 10) Serial.print(F("0"));
    Serial.print(gps.time.minute());
    Serial.print(F(":"));
    if (gps.time.second() < 10) Serial.print(F("0"));
    Serial.print(gps.time.second());
    Serial.print(F("."));
    if (gps.time.centisecond() < 10) Serial.print(F("0"));
    Serial.print(gps.time.centisecond());
  }
  else
  {
    Serial.print(F("INVALID"));
  }

}