在網頁後台中的 dirty hacking

敝校原本的系所網頁可能是很多年前找資訊廠商撰寫的。看了一下後台管理系統和 code 判斷,那時候還是傳統的介面,固定左 欄的選單,中間是主要資訊呈現框架,上方有個仿效目前大圖輪播的設計(但只有 700 px 左右的寬度)。整體而言要更動不是很方便,後來聽計中說她們每天光是要應付各系所寫爛的 html 就沒時間了,遑論是再加上新功能。某天接到學校公文說有個系所網頁比賽,希望各系在原本的「大圖輪播」區呈現招生亮點,原本覺得這種比賽很無聊,但看在系辦小姐更新網頁已經愁眉苦臉的狀況下,加上前三名還有獎金(雖然區區幾千塊,但可以請人來演講或是課程帶學生出去有經費,我覺得在經費逐年遞減下不無小補),就開始研究怎麼用大圖輪播的功能,研究了半小時,我決定放棄 XD 因為後台只有 Internet Explorer 可以正常運作,用Chrome 或是其他瀏覽器都只有部分功能可用,所以就想說這種偏靜態的網頁可使用 jekyll + bootstrap 來解決,可是學校後台無法直接改 css,我就使用 github pages 當做大圖輪播的 server,然後在學校網頁對應大圖輪播的區塊嵌入 iframe 作為解決方案,當然也可以同時有響應式網頁設計(但這個後來我就沒空做的比較細緻,老實說我是前端苦手,html5/css3/js 都只會一點點)。

一開始其實沒有很順利,因為版型的關係,無法修改。後來在計中辦的教育訓練中,剛好和隔壁資訊系同學聊天(忘了問你的名字,如果你有看到這篇我請你吃個飯感謝你 XDD),他們系上也是碰到類似問題,是嵌入在「公告事項」中,大概長這樣:

<style>
    #iframe {
    border: 0px;
    left:0px; }
</style>
<iframe id="iframe" src="https://ncyubrd.github.io" width="100%" height="500px" style="overflow: hidden;" scroll="no">
</iframe>

以下是個懶惰簡單的 how-to:

關鍵字: jekyll, github pages, bootstrap (因為都是 workaround,有興趣自己查一下資料 XD)

  1. 我找了個 landing page 的 jekyll 範例 https://github.com/swcool/landing-page-theme
  2. 修改大圖輪播的 code,其他不相關的部份就刪除
  3. 在 _layout 目錄中修改 default.html
    <!DOCTYPE html>
    <html>
        {% include head.html %}
        <body>
            {% include header.html %}
            {% include carousel.html data=site.data.carousel %}
        ...略...
        </body>
    </html>
    
  4. 在 _include 中新增 carousel.html,並設計會出現 picture.title (大圖輪播的每個頁面標題)、picture.caption (大圖輪播的說明) 等變數,這些資訊是寫在一個 json 檔裡頭,之後要維護比較方便。
 <div class="item active">                                                                    
   <img class="first-slide" src="img/{{ picture.imgurl }}" alt="{{ picture.title }}"> 
   <div class="container">                                     
     <div class="carousel-caption">                                              
       <h1>{{ picture.title }}</h1>                   
       {{ picture.caption }}                                             
       <br/>                                      
       {% if picture.urldesc %}                                         
       <p><a class="btn btn-lg btn-primary" href="{{ picture.url }}" target="_blank" role="button">{{ picture.urldesc }}</a></p>                     
       {% endif %}                                 
     </div> 
   </div>                                                  
 </div> 
  1. 新增 data 目錄,裡頭的放 carousel.json,內容大概就是
    {
      "title": "特色講座:野望影展",
      "caption": "本系每學期都會和臺灣野望自然傳播學社合作,搭配環境教育學程,
        播放國內外相關的野生動植物生態紀錄片,並由系上老師導讀",
      "imgurl": "wildfilms.jpg",
      "url": "http://www.wildviewtaiwan.org.tw/festival",
      "urldesc": "更多資訊"
    },
    
  2. 把照片放在 img 目錄內(附檔名要對)
  3. 剩下設定看一下 [Using jekyll as a static site generator with github pages)(https://help.github.com/articles/using-jekyll-as-a-static-site-generator-with-github-pages/) 就可以了

不過以上都是 dirty hacks,正本之道應該是整體網頁後台系統換成 wordpress 或是 drupal 之類的開源內容管理系統(如果有經費的話)

相關資訊

  1. 敝系網頁
  2. 大圖輪播的頁面

使用 gdalwarp 來切割 raster 檔案

在 GIS 圖資處理中,我最喜歡 gdal 引擎了,因為他效率很好,不需要再透過肥肥的 GIS 軟體執行,只要在 shell 裡頭執行即可。其中 gdal 還有幾個子程式,像是重投影及轉換—gdalwarp, 不同格式間的轉換—gdal_translate, DEM 的處理—gdaldem 以及一些 python 的 script,如 gdal_calc.py

案例一、使用向量邊界圖(boundary vector)把特定 raster 中的相同範圍擷取(clip)資料出來,例如我們有一個全世界的平均氣溫圖層,想要把臺灣所在的區域範圍內,擷取出平均氣溫圖層資料,這應該怎麼做呢?

簡單的概念示意圖如下:

可以把臺灣的國界當成上圖的星星,我們所要的就是星星內部的 raster 資料,其餘的資料都移除。我們以 gdalwarp 為例解釋如何使用:

相關參數

  1. -cutline: 我們要使用的向量邊界圖(OGR 支援的格式列表),通常 ESRI Shapefile, Arc/Info E00 等都會有支援

  2. -crop_to_cutline: 這個搭配 -cutline 使用。有時候需要擷取的 raster 原始檔範圍很大,我們希望切出來的範圍是在 -cutline 的向量範圍內,此時就可以搭配這個參數使用

  3. -tr: 輸出的 x y 解析度。比如說輸出的解析度是弧度的話,要記得轉換單位成 10 進位,例如 30 弧度-秒(arc-second)就是 1/60/60*30=0.0083333333

  4. -dstnodata: 沒有資料的填空值,ErDAS Img 格式採用 -9999,你可以填寫一個較極端的數值。這邊要特別注意的是,如果你使用的是氣溫資料,有可能低於 0ºC ,所以切記不可以填 0。

  5. -of: 輸出的檔案格式(output format),預設是 GeoTiff (GTiff)

使用方法

假設我們的向量圖名稱為 taiwan.shp,要擷取的 raster 檔案為 world.tif,輸出的檔案名稱為 taiwan_clip.tif。 gdalwarp 的指令為(如果你是使用 windows作業系統,請再確認一下 gdalwarp.exe 的路徑):

gdalwarp -dstnodata -9999 -q -cutline taiwan.shp \

            -tr 0.00833333333333 0.00833333333333 -crop_to_cutline \

            -of GTiff world.tif taiwan_clip.tif

這樣就可以快速擷取出 raster 了!另外你也可以搭配迴圈批次執行處理,就不需要開啟笨重的 GIS 軟體了(ArcMap/QGIS, etc.)

使用 Excel/LibreOffice VLOOKUP() 函數來查詢

假設我們有一份學名不含作者與學名含作者的對照表,其中 A1:A10 是學名不含作者,B1:B10 則是含作者。如果我們有一個清單是學名不含作者,但需要對照找出含作者的學名時該怎麼做呢?下方的表一和表二為範例資料:

表一、學名不含作者及含作者對照(1, 2, 3, ..., 10 代表 excel 中的列數,而 A, B, ..., D, E 代表欄位名稱)

A B
1 Antrophyum parvulum Antrophyum parvulum Blume
2 Bulbophyllum kuanwuense Bulbophyllum kuanwuense S. W. Chung & T. C. Hsu
3 Cyclobalanopsis stenophylloides Cyclobalanopsis stenophylloides (Hayata) Kudo & Masam. ex Kudo
4 Eleocharis acutangula Eleocharis acutangula (Roxb.) Schult.
5 Thladiantha nudiflora Thladiantha nudiflora Hemsl.
6 Huperzia squarrosa Huperzia squarrosa (G. Forst.) Trevis.
7 Triumfetta tomentosa Triumfetta tomentosa Bojer
8 Digitaria ciliaris Digitaria ciliaris (Retz.) Koeler
9 Tacca chantieri Tacca chantieri Andre
10 Lasianthus japonicus Lasianthus japonicus Miq.

表二、需要查詢的範圍(1, 2, 3, ..., 10 代表 excel 中的列數,而 A, B, ..., D, E 代表欄位名稱)

D E
1 需查詢不含作者的學名 查詢結果
2 Eleocharis acutangula
3 Thladiantha nudiflora
4 Huperzia squarrosa

我們將使用 vlookup(lookup_value, table_array, col_index, [range_lookup]) 這個函數來查詢學名含作者。 vlookup 第一個參數 lookup_value 代表待查詢的資料欄位範圍(例上表二中的 D1:D4);第二個則是查詢的表,可以是多個欄位範圍,例如表一中的 A2:B10;第三個則是查詢的表所要對應的欄位索引,假設表一中,我們希望透過 A 欄位的資料去查詢 B 欄位的資料,在 vlookup 選取 A2:B10 (共兩欄),B 欄位是選取範圍中的第二欄,此時 col_index 就填 2。最後 range_lookup 則是代表需要用精確搜尋(FALSE)或是近似搜尋(TRUE)。所以在表二中的 E2 欄,我們可以使用:

=VLOOKUP(D2:D4, A1:B10, 2, FALSE)

之後再把 E2 cell 向下拉到 E4 就可以完成此查詢。相關的範例 excel 檔案及說明

使用 GNU parallel 來平行運算

通常我們在使用一些簡單的 shell 工具時,都只會用到一個 CPU 核心,但對多核心的機器來說可以使用 GNU parallel 來善用所有的資源,並減少執行時間。以下舉簡單的例子來說明如何使用 GNU parallel:

檔案

very_big_file.csv,tab 分隔的檔案,裡頭是 GBIF 下載物種的資訊,這個文字檔有 34 GB

使用程式

parallel
grep
測試平台: Mac OS X 10.10, 4 cores (8 threads), 16 GB ram

測試結果

Test A

只用 grep 來擷取 "Acanthiza katherina" 的記錄

$ time grep "Acanthiza katherina" very_big_file.csv
366.08s user 16.88s system 99% cpu 6:25.09 total

總共花了 385 秒左右

Test B

$ cat Chordata_small.csv | parallel --pipe -u grep \"Acanthiza katherina\"
0.82s user 30.26s system 8% cpu 6:16.76 total

總共花了 377 秒左右,疑? parallel 似乎也沒有快到哪邊。因為這個例子中瓶頸是輸出入(從硬碟讀取資料然後丟給 grep 擷取),所以試看看每次將 100 M 行的資料轉送給 grep 同時處理:

Test C

$ cat Chordata_small.csv | parallel --pipe --block 100M -u grep \"Acanthiza katherina\"
478.78 s user 135.86 s system 486% cpu 2:06.71 total

改用 block=100M 之後,僅需要 127 秒,節省了 66% 左右的時間。

結論

如果處理的東西不複雜,也許可以用 parallel 搭配 awk/sed/grep 來做平行運算,畢竟寫個 script 只要幾分鐘,相較寫 MPI 或是 Hadoop 這類比較複雜的平行運算系統,parallel 是 CP 比較高的選擇。

Vim 正規表示式(1)

這個教學中,主要使用 vim 來示範簡單的正規表示式(regular expression),如果不懂可以看一下 wikipedia 上面的正規表示式。看不懂?沒關係,簡單的說就是用某些匹配字元(某些符號,例如"*", "|", "[, ]" 等來找到輸入文字的特徵。

舉實際的例子來看好了,假設我們要用 latex 來排版,需要加索引的時候,通常都會在要加索引的字後面加上 \index{keywords},例如:

Nulla ut vestibulum \index{vestibulum} dolor. Aenean aliquet nisl et quam 
suscipit, eget porta libero molestie. Fusce eget nisl ac urna maximus laoreet. 
Donec bibendum iaculis orci id tristique. Quisque nunc quam, ultrices id sapien 
et, hendrerit \index{hendrerit} pharetra tortor. Class aptent taciti sociosqu 
ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam ut massa 
nec enim pharetra varius. Aenean nec arcu id sem pretium congue id ut orci. 
Curabitur ultricies in augue ut dignissim. Nulla facilisi.

但是如果你是一份文件,懶得手動加入索引時(假設需要加入特徵的字有 65535 筆時,一個一個加,不就做到天荒地老?),像下面這個例子中,我想要把「Lorem ipsum 福祿壽」當成索引:

\name[1. ]{Lorem ipsum 福祿壽}
\name[2. ]{Quisque nunc 過新年}
\name[3. ]{Vestibulum sit 來爬山}
...
\name[65535. ]{Curabitur augue 千里馬}

用肉眼判斷,需要加索引值的字元都是放在大括號內({}),所以這就是特徵,如果我們要用 vim 要怎麼做呢?在介紹完整作法之前,先來介紹一下 vim 的編輯模式和指令模式。vim 在預設進入編輯器時,是指令模式,要進入編輯模式需要按 i (insert),要跳出編輯模式進入指令模式則是按 esc 鍵 + : (冒號)。而正規表示式則是需要在指令模式中下指令。下面一步步解釋如何做:

  1. 了解如何取代(為了方便解釋,在指令下方有 1,2,3, ... 數字來指示字元位置)

    :%s/A/B/g
    1 2 3 4 5 
    

    1 (:) 代表進入指令模式

    2 (%s) 代表是所有行號(%)取代(s)

    3 A 代表某個特定 pattern

    4 B 代表若符合 A pattern 時,取代為 B

    5 g 代表全域(也就是在整行中所有符合的匹配規則(pattern)都會被取代掉),若沒有標示 g ,則只會取代掉第一個

    例如要把下面一段文字(取自Flora of Taiwan)中的 "lanceolate" 取代成 "oblong":

    Culms slender, to 70 cm tall, 1.5 mm in diam. Blade to 15 cm long,
    (2-)5-15 mm wide; ligule chartaceous, minute, less than 1 mm long,
    truncate. Panicle open, to 30 cm long, branches 4-6-nate, verticillate.
    Spikelets 1-flowered, 3 mm long; glumes unequal, lower glume narrowly
    lanceolate, 1.8 mm long, chartaceous, 1-nerved; upper glume lanceolate,
    3-nerved, 2.5 mm long, chartaceous; lemma subcoriaceous, lanceolate,
    3-nerved, acute, 3 mm long; palea subcoriaceous, linear-lanceolate,
    acute, 2-keeled,as long as spikelet.

    vim 的指令如下:

    :%s/lanceolate/oblong/g

    輸入上述指令則會將所有 lanceolate 取代成 oblong

  2. 範圍
    如果要表示範圍,可以用中括號"[ ]"來包住要表示的範圍,通則可以這樣寫

    [n-m]

    其中 n 代表起始的值,m 代表結束的值,這些值可以是英數字,例如


    [1-9][0-9]

    上面這個例字代表符合10, 11, 12, ... , 99 的數字都會被抓出來,範圍也可以是用","(逗號)做分隔,例如

    [1,3-5]

    代表符合 1 或是 3, 4, 5 數字的匹配規則都會被找出來。如果是英文字的話,可以使用```[A-Z]```代表符合 A 到 Z 所有的字元都會被抓出來,大小寫是有區別的。也可以使用其他的正規表示式符號來搭配,例如"*"代表符合 0 到多個符合的所有字元等,但如果是要符合所有的字元包括空白行則是要用".*",通常是使用包在括號、引號的字元匹配規則內,細節請參見 vim regular expression 的文件。
    
    再來看一個例子,如果我要把上面 Flora of Taiwan 的文字中,有數字單位的,例如 10 cm, 1.5 mm 等的匹配規則 抓出來,如下:
    

    Culms slender, to 70 cm tall, 1.5 mm in diam. Blade to 15 cm long,
    (2-)5-15 mm wide; ligule chartaceous, minute, less than 1 mm long,
    truncate. Panicle open, to 30 cm long, branches 4-6-nate, verticillate.
    Spikelets 1-flowered, 3 mm long; glumes unequal, lower glume narrowly
    lanceolate, 1.8 mm long, chartaceous, 1-nerved; upper glume lanceolate,
    3-nerved, 2.5 mm long, chartaceous; lemma subcoriaceous, lanceolate,
    3-nerved, acute, 3 mm long; palea subcoriaceous, linear-lanceolate,
    acute, 2-keeled,as long as spikelet.

    這樣就可以使用下列的正規表示式來找匹配規則:


    [0-9,0-9.]*\ [c|m]m

    其中,第一部分的範圍有[0-9,0-9\.]*代表符合 0-9 的數字或是 0-9 開頭且後面有小數點("."在 vim 中有特別意義,所以保險起見用 \ 跳脫字元),後面接的 metacharacter 則是代表所有 0-9 以及 0-9 開頭後面有小數點的匹配規則都會被找出來。第二部分觀察到的匹配規則則是數字和單位之間有空格,所以我們用" "(空格)接在後面,第三部分則是單位,有 cm 及 mm,寫成範圍的正規表示式則是 [c|m]m (c 或是 m,後面接 m,則為 cm 或 mm)。但這樣寫會有個問題,就是如果文字中數字單位前有加範圍,例如 1-3 cm ,這樣就無法抓到,所以得另外再處理。

  3. 符合匹配規則(pattern)的分組
    有時候我們需要幾個不同匹配規則做不同的取代,那就可以用 grouping 的方式把匹配規則分組,分組是用\(分組的元素\)(一對括號,但前面要使用 backslash "\" 來跳脫字元),而符合前述匹配規則的分組,可以用 \ 加上 1~9 的數字來代表,如果 \0 表示符合所有匹配規則的比如:

    :%s/\(30\) \(cm\)/\1 \2/g
    

    \1 代表了符合 30 的字元

    \2 則代表符合 cm 的字元

所以我們在「Lorem ipsum 福祿壽」這個例子中要怎麼寫 vim 的正規表示式呢?第一,先找出匹配規則,這個匹配規則是可以「精確」描述出來的,這是最重要的步驟;第二,試著用搜尋(指令為 /)來試試看第一步中所找出的匹配規則,如果不對的話,再重複第一步驟修正,直到確定找出為止。

我們希望 Lorem ipsum 是一組詞,用空白分隔,加入 \index 變成\index{Lorem ipsum},而「福祿壽」是第二組詞,加入 \index 變成\index{福祿壽},第一組的 pattern 就是第一個字首字大寫,第二個字的字首小寫。第一組和第二組都被大括號包住,而且大括號前有\name[數字加上"." ],因此用 vim 寫符合上述規則的正規表示式如下:

:\\name\[.*\]{\([A-Z].*\) \([a-z].*\) \(.*\)}

 |--1-|--2--|----3------|4|----5----|6|--7--|

解釋:
第一部分(|---1---|)代表符合有\name的字元,因為"\"已是跳脫字元,所以要多加一個"\"

第二部分則是代表[ ](中括號)包起來的字元

第三部分則是代表Lorem ipsum中的第一個字Lorem,首字大寫,所以使用[A-Z].*,因為我們希望它在 index 時有階層的索引(請參見 latex 詳細說明),所以要特別處理。所以把它用\(\)當成第一個群組

第四、六部分是空格

第五部分則是Lorem ipsum中的第二個字ipsum,因此寫成[a-z].*,是第二個群組

第七部分則是最後「福祿壽」

接下來則是把符合的匹配規則取代成\name[1. ]{Lorem ipsum 福祿壽} \index{Lorem!ipsum} \index{福祿壽},vim 的語法如下:

:%s/\\name\[.*\]{\([A-Z].*\) \([a-z].*\) \(.*\)}/\0 \\index{\1!\2} \\index{\3}/g
   |-----------------------1--------------------|--------------2--------------|

解釋:
取代的部份(|---2---|)中,\0代表符合前述匹配規則的字元,\1 則是「Lorem」,\2則是「ipsum」,而\3代表「福祿壽」。

雖然正規表示式寫起來像火星文,但是只要能夠精確找到匹配規則的話,可以很快速的節省手動取代或修改的時間,多練習就會上手了!有興趣學習 vim 的朋友們可以參考李果正前輩寫的「大家來學 vim」

參考文獻及延伸閱讀:
1. vim regexp
2. 大家來學 vim

PostgreSQL 延伸套件

PostgreSQL 可以透過 pgxn client 來安裝一些第三方的套件,使用起來還滿方便的。

因為 pgxn 是 python 寫的,所以安裝只要透過 pip :

$ pip install pgxnclient

安裝第三方套件:

# 編譯並安裝

$ pgxnclient install _pkgname_
# 將編譯好的套件使用 extension 方式安裝進特定資料庫

$ pgxnclient load -d _database_ _pkgname_

之後就能夠開心的使用第三方套件了

References:
https://github.com/dvarrazzo/pgxnclient
http://pgxnclient.projects.pgfoundry.org/install.html

關於香蕉的空照判釋

[注意!]其實這篇文章不應該是漂流木事件的主軸,但有些細節可以讓大家理解航空照片的判釋是具有高度專業及不確定性,不是隨便腦補就可以做出結論的(茶)


圖零、你能分得出來這些是什麼樹嗎?

關於潘建志醫師臉書及蘋果報導[1]的香蕉空照圖及街景圖來看,會誤以為屏東縣九如鄉榮泉街一號屏26縣道附近的香蕉是呈現圓團狀(參考圖一及圖二)


圖一、屏26縣道九如鄉榮泉街一號附近的農田街景圖(2013)


圖二、屏26縣道九如鄉榮泉街一號附近的農田衛星圖(2014)

根據本鍵盤森林學家的判釋,這塊 2013 年街景圖看起來都是香蕉田,其實是混作(可以仔細觀察一下圖一標註位置)。香蕉通常是每年就會重新種植,而有些農民會間作其他多年生的作物,像是檳榔、柑橘類(金桔、檸檬)等,加上 Google 街景圖和衛星圖的時間差,所以有可能會造成街景圖和衛星圖上的影像對不起來。仔細觀察圖二衛星圖的香蕉混作田,雖然都是圓團狀的柑橘類作物田(只是猜測,我也沒辦法判斷確切的種類),但還是可以看出有留幾棵香蕉在田中(標註處)。因此呼籲鍵盤辦案的鄉民在觀察街景圖和衛星圖時,除了注意拍攝時間外,畢竟可公開取得的衛星或航照影像圖解像力有限,還是要靠現場或其他證據輔助判斷。

另外對於航照判釋是門專業的技術,想起當年修測量學提到航空測量判釋時,都需要使用立體鏡觀察立體相片對,因為只有透過立體觀察可以看出景深並判斷物體高度(有時候影子是輔助判斷物體時很好的特徵),平面的影像比較不容易看出三維。至於是否能判斷樹種就需要經驗和野外確認了,像是高海拔樹種單純、特徵明顯,所以可以從航測照片看出台灣冷杉(Abies kawakamii)、台灣鐵杉(Tsuga chinensis var. formosana)等,但低海拔闊葉樹樹冠特徵相近,比較不容易確定,此時只能依照植群型和地點來判斷大概是哪些物種,再透過實地訪查確認。

最後整理一下低海拔從空照圖看起來為傘狀或放射狀的樹種,大部分是棕櫚科(Arecaceae)植物,也有些是芭蕉科(Musaceae)或樹蕨等:

A. 天然林

  1. 黃藤類(C. quiquesetinervius,包括水藤 Calamus formosanus 這類攀援性木本植物),看起來會在一些森林邊緣或樹冠層上,如下圖三,這類的森林多半都是干擾頻度較高、攀藤類的植物也較多,航照圖上看起來 pattern 不明顯(sorry 懶得找航照圖了)


    圖三、黃藤類植物

  2. 山棕(Arenga tremula)
    後來真的有學生跑去實地訪查[3],拍了一些照片,潘醫師 blog 內(提到的木材)的應該是山棕,跟香蕉比起來,山棕可以超過三米,葉幅也較香蕉長,加上山棕為一回羽狀複葉從基部生長,小葉會下垂,所以在較低解析度的空照俯瞰圖上看起來會較瘦長,所以潘醫生才會誤認為是木材。另外山棕通常分布在溪溝谷地較溼的環境,而像這樣的生態環境也可以做為輔助判斷

  3. 台灣芭蕉(Musa basjoo var. formosana)
    這個也可以把香蕉的部分拿來一起談,兩個外型很接近的。台灣芭蕉多半零星分布在林下,不容易直接從空照圖看到,下圖是天然闊葉林內的台灣芭蕉


    圖四、這張圖剛好有台灣芭蕉、黃藤和山棕 XD

  4. 樹蕨類
    台灣因為潮溼,所以樹蕨類(多半指桫欏科)也很常見,尤其是低海拔開闊地、道路旁或是崩塌的溪溝谷地常常可以看到筆筒樹(Cyathea lepifera),其特徵是葉子為三回羽狀複葉,其葉寬也比芭蕉或是山棕來得寬,加上筆筒樹葉子著生的位置接近輪生,所以航照圖看起來像是一朵朵小花,跟香蕉類或棕櫚科植物比起來葉部份的陰影也比較少,形狀也比較圓潤一些,請參照圖五及圖六


    圖五、陽明山一帶的筆筒樹

    圖六、如果你仔細尋找道路旁或山澗溪溝旁的空照圖,也可以找到筆筒樹。紅色圓圈標記處下方有一條黑色的陰影,這個可能是路徑或溪溝。

B. 作物或栽培植物

椰子類(可可椰子、亞歷山大椰子等)或檳榔,因為屏東種植椰子和檳榔很多,所以我就拿這兩種主要的栽培作物來做比較。


圖七、可可椰子和檳榔比較,能看出傘狀,樹冠幅度較大的為椰子。

本鍵盤森林學家老家就是種可可椰子和檳榔的,所以我對這兩種作物都十分熟悉。可可椰子高度可以達到五、六層樓(~15m)、冠幅約 5–7 公尺,所以從空照圖來看陰影會很深,如果使用立體相片對觀察,圖七椰子和檳榔交界處就可觀察的出高低的層次差異,而檳榔因為樹冠幅度窄,種植間距也比較近,這一點也是可以做判斷的依據。

結論:航照及衛星影像圖只能當成輔助判斷的依據,並不能直接腦補當成證據的,要知道真相還是要實地訪查和拍照 XD

參考文獻及引用地圖

[1] 關鍵木材變「香蕉樹」?霹靂潘挨轟邱毅2.0
[2] 高品質香蕉田間栽培管理手冊,台灣香蕉研究所
[3] PTT: [爆卦] 內湖山老鼠Google拍到了 興善宮也是違建
[4] Google Maps 圖五筆筒樹之原圖
[5] Google Maps 圖六筆筒樹之原圖
[6] Google Maps 圖七可可椰子與檳榔比較之原圖

zfs 單一裝置儲存池改為鏡映

標題有點難下 XD

前言及環境:

假設已經有一台 FreeBSD 預設將 root 安裝至 zfs 檔案系統,但一開始並沒有設定 mirror 或是 raidz ,之後想要將系統換硬碟,將原本的舊硬碟資料轉移出來,並設定 mirror (換句話說就是:一開始裝系統時沒錢買多餘硬碟,只好將就著用,等有經費時,可以買兩顆硬碟和新主機時,要怎麼比較無痛把舊機器舊硬碟上的資料整個放在新的系統上,並設定比較安全的儲存環境)因為網路上有許多是一開始就設定好 zfs mirror 或 raidz 取代壞掉的硬碟教學,所以本篇主要是著重在怎麼從無冗餘硬碟的狀況下,再增加 zfs mirror 裝置。

系統: FreeBSD 10.1
舊主機之硬碟: 代號假設為 A
新主機的兩顆硬碟: M0, M1

方法:

如果照原本 UFS / MBR 的方式,就是先將新的硬碟裝上去,分割好並格式化,掛載成 /mnt/root,用 dd 或 dump | restore 的方式將資料倒回去。等資料都倒完後再將舊硬碟取下,並設定成新硬碟開機,步驟不難但是很麻煩,所以這篇教學試著使用 zfs zpool resilvering(「銀鹽刻版複製」,方式類似 RAID 的重建[rebuild])的概念來置換舊硬碟資料,用新硬碟開機,並將原本無 mirror 的 zpool 增加磁碟鏡映群組,新增硬碟並重建資料。做法的概念圖如下:

實作:

  1. 先將新的硬碟裝上去,並用 gpart 分割磁區(partition),設定好 FreeBSD 開機磁區、SWAP等
    假設這顆新的硬碟 M1 的裝置節點是 /dev/ada1 ,以下指令請記得都用 sudo 或 root 身分執行
    # 建立 GPT 磁區
    
    gpart create -s gpt ada1
    # 新增 freebsd 開機磁區為,大小為 512 KB,標籤(-l)為 gptboot
    
    gpart add -t freebsd-boot -l gptboot  -s 512K ada1
    # 新增 swap 磁區,大小為 8192 MB
    
    gpart add -t freebsd-swap -l gptswap -s 8192M ada1
    # 剩下的全部分割給 freebsd zfs 磁區,標籤為 zfs1
    
    gpart add -t freebsd-zfs -l zfs1 ada1
    # 設定下次開機的硬碟為本顆硬碟的第一個分割區(ada1,參數 i)
    
    gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 ada1
    
    此時,可以到 /dev/gpt 目錄下確認是否有 zfs1 ,若有的話就可以繼續次步驟
  2. 將新硬碟 M1 的 zfs 磁區加入 zpool,並設定成 mirror

    zpool attach zroot gpt/zfs0 gpt/zfs1
    

    使用 zpool status 來確認,如下圖,此時 gpt/zfs1 顯示 resilvering,可能需要一些時間處理(端看資料量大小)

  3. 將舊硬碟 (A) 離線
    這個步驟將舊硬碟先離線,等待關機之後(不過理論上 SATA 硬碟可以熱拔插)換上另一顆新硬碟:

    zpool offline zroot gpt/zfs0
    

    zpool status 確認時,原本 gpt/zfs0 標籤會變成一串數字,STATE 則會顯示 OFFLINE

  4. 移除舊硬碟,安裝另外一顆新硬碟(M2),並重複步驟 1 的 gpart

    換硬碟時,記得不要拿錯 XD 下面的步驟中,在重開機後,原本的硬碟 M1 變成 ada0,而新的 M2 則是 ada1,所以在分割磁區設定標籤時,將 freebsd-boot 改成 gptboot0,而 M2 上的新 zfs 磁區標籤則為 zfs0

    # 建立 GPT 磁區
    
    gpart create -s gpt ada1
    # 新增 freebsd 開機磁區為,大小為 512 KB,標籤(-l)為 gptboot
    
    gpart add -t freebsd-boot -l gptboot0  -s 512K ada1
    # 新增 swap 磁區,大小為 8192 MB
    
    gpart add -t freebsd-swap -l gptswap -s 8192M ada1
    # 剩下的全部分割給 freebsd zfs 磁區,標籤為 zfs0
    
    gpart add -t freebsd-zfs -l zfs0 ada1
    
  5. 使用 replace 更新磁碟資料

    離線的磁碟在 zpool 中會顯示一串數字,更新磁碟時記得複製這串數字,如下圖

    zpool replace zroot 9450223813957032699 gpt/zfs0
    


    接下來就是等這顆新磁碟自動 resilvering 資料即可
    psilotum@biodiv ~ $ zpool status
    pool: zroot
    state: ONLINE
    scan: resilvered 98.4G in 0h20m with 0 errors on Thu Feb 5 19:01:57 2015
    config:
    NAME          STATE     READ WRITE CKSUM
    zroot         ONLINE       0     0     0
      mirror-0    ONLINE       0     0     0
        gpt/zfs0  ONLINE       0     0     0
        gpt/zfs1  ONLINE       0     0     0
    

    errors: No known data errors


    參考資料

    https://www.freebsd.org/doc/handbook/zfs-zpool.html
    http://www.freebsdwiki.net/index.php/ZFS,_booting_from

替換 windows ^M,並練習組合一些簡單 unix 指令

有時候拿到一些在 Microsoft Windows 處理的文字檔案,會有一些斷行後有 ^M (Ctrl+M) 的字元,在 unix 上處理這類的資料很惱人,會造成不正確斷行判斷,所以可以用 find, xargs, awk, sort 和 vim 一次取代完(^M 要按 Ctrl 加上 M)。分解做法如下:

  1. 找某目錄底下以 .txt 為名的所有檔案 實作:
    $ find . -type f -name "*.txt"
    
    解釋:
    .(dot) 代表現在目錄底下
    -name 後面接的參數代表檔名,可以用 wildcards 或 regular expression
    -type 後面接的是檔案類型, f 代表一般的檔案,d 代表目錄等
  2. 找完後用 xargs (eXecute ARGuments)

    xargs Manual 這樣寫:

    The xargs utility reads space, tab, newline and end-of-file delimited strings
    from the standard input and executes utility with the
    strings as arguments.

    簡單說就是把上個程序輸出的結果當成參數,餵給後面的程式使用,有點難懂對吧?那他跟 pipe (|) 有什麼差別呢?
    pipe 字面上是水管,只負責「傳導」這件工作,像是:

    $ ls | grep keywords
    

    這個指令就代表把 ls 的輸出導給 grep 執行,而 grep 把輸出中有 keywords 的行抓出來。

    若是下列指令則會用 grepls 所列出所有的檔案抓取有 keywords 的行

    $ ls | xargs grep
    

    有了 xargs 的基本概念後,用例子來解釋,下面用 ls 會列出目錄底下有三個 .txt 檔案,分別為 1.txt, 2.txt, 3.txt。「列出這三個檔案」就是標準的輸出

    $ ls
    1.txt 2.txt 3.txt
    

    接下來若導向給 xargs,則會把標準的輸出當成是參數清單(argument list),
    ls | xargs grep keywords 執行所得到的結果則和下列指令相同:

    grep keywords 1.txt
    grep keywords 2.txt
    grep keywords 3.txt
    
  3. awk 取出檔案名稱
    因為 xargs grep keywords 的輸出類似:

    1.txt: ipsum lorem keywords consectetur adipiscing elit, sed do eiusmod
    1.txt: lorem ipsum quasi voluptate velit esse keywords dolore eu fugiat
    2.txt: voluptate velit esse cillum dolore eu fugiat keywords, lorem

    因此我們希望只列出檔案名稱,觀察上述的 pattern,就是取出 : 前的文字,使用指令為:

    awk -F':' '{ print $1}'
    

    -F 後面用 '(single quote 單引號) 包起來的是分隔符號(separate Fields),後面加數字代表分隔符號切分後第幾個欄位,例如上面的 3. 的例子中, '{print $1}' 則會將 1.txt, 2.txt 等顯示出來

  4. sortunique 整理

    前面 awk 處理完後把含有 ^M 的檔案名稱丟給 sort -u (排序整理,並刪除重複)

  5. 把整個程序寫成迴圈,用 vim 取代 ^M
    shell script 迴圈:

    for _var_ in _condition_ 
    do
        _statements_
    done
    

    shell script 中變數是加上 符號,將步驟一到四所找到有 ^M 的檔案名稱當成輸入的清單,跑個迴圈再加上 vim 取代即可。在 shell 中 vim 後面加上 + 則會執行 vim 內部的指令,我使用 regular expression 取代 ^M,指令為:

    %s/被取代的文字/取代的文字/g
    

    記得最後還要加上寫入離開指令 +wq

最後把程式整理一下,完整程序如下:

#!/usr/bin/env bash

for i in `find . -type f -name "*.txt" | xargs grep "^M"| awk -F':' '{print $1}' | sort -u`
do 
    echo "Substitute ^M in ${i}"
    vim +%s/^M//g +wq ${i}
done

參考資料

http://en.wikipedia.org/wiki/Xargs

升級 PostgreSQL 主要版本

PostgreSQL 的版本從 8.3 之後,就是以 X.Y 當成是主要版本(major release),而每個版本都會有小修正的次要版本 X.Y.Z (minor release)。若要升級主要版本,例如從 9.3 升級至 9.4 ,因為主要版本升級會加入一些新功能,而系統資料表的配置(layout of system tables)則會改變,所以需要使用 dump / restore 來重新構建系統資料表(是的,很麻煩),在 PostgreSQL 升級主要版本前,可以用 pg_upgrade 來升級。升級的流程如下,以 MacOS X 底下 homebrew 安裝為例:

  1. 停止 postgres daemon 傳統用法是透過 service 來控制 /etc/rc.d/ (或 /usr/local/etc/rc.d) 底下的 scripts 來控制 daemons,例如
    $ service stop postgres
    
    但 MacOS X 則是使用 /Library/LaunchAgents/Library/LaunchDaemons 底下的 plist 來控制,所以先將其 unload (同時也會呼叫 pg_ctl 去停止 postgres 運作):
    $ launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
    
  2. 將舊資料庫儲存位置更名 homebrew 預設會將 PostgreSQL 的資料庫儲存於 /usr/local/var/postgres 底下,因為我是從 9.3 升級至 9.4,所以複製為 postgres93 (whatever)
    $ mv /usr/local/var/postgres /usr/local/var/postgres93 
    
  3. 升級現有版本之 postgres 及相關函式庫 如果你有使用相關延伸函式庫(例如 postgis),請記得也一起升級
    $ brew upgrade postgresql postgis
    
  4. 備份 雖說可以用 pg_upgrade 更新,但還是把所有資料庫備份出來比較保險
    $ pg_dumpall > postgres93.dbout
    
  5. 建立新的主要版本資料儲存庫 新建儲存庫
    $ mkdir /usr/local/var/postgres
    $ initdb -D /usr/local/var/postgres
    
    使用 launchctl 載入 postgresql (記得不要在 tmux or screen 內執行,否則會有 permission 的問題)
    $ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist 
    
  6. 使用 pg_upgrade 升級主要版本 因為需要用到舊版執行檔,所以記得不要太快移除舊版本(這也就是使用 homebrew 的好處,每個第三方套件都會裝在 /usr/local/Cellar 底下,再連結到 /usr/local 中相關的目錄位置) 四個參數如下: --old-datadir: 舊版本的資料儲存目錄, --new-datadir: 新版本的資料儲存目錄, --old-bindir: 舊版本的執行檔目錄, --new-bindir: 新版本的執行檔目錄。 確認上述的目錄和版本後,接下來就可以使用 pg_upgrade 來升級
    pg_upgrade
    --old-datadir /usr/local/var/postgres93
    --new-datadir /usr/local/var/postgres 
    --old-bindir /usr/local/Cellar/postgresql/9.3.5_1/bin 
    --new-bindir /usr/local/Cellar/postgresql/9.4.0/bin
    
    升級過程會確認版本,然後 dump 舊有資料庫,並轉成新版本:
    Performing Consistency Checks
    -----------------------------
    Checking cluster versions                                   ok
    Checking database user is a superuser                       ok
    Checking for prepared transactions                          ok
    Checking for reg* system OID user data types                ok
    Checking for contrib/isn with bigint-passing mismatch       ok
    Checking for invalid "line" user columns                    ok
    Creating dump of global objects                             ok
    Creating dump of database schemas
                                                            ok
    Checking for presence of required libraries                 ok
    Checking database user is a superuser                       ok
    Checking for prepared transactions                          ok
    ...(略)
    Optimizer statistics are not transferred by pg_upgrade so,
    once you start the new server, consider running:
    analyze_new_cluster.sh
    ...(略)
    Running this script will delete the old cluster's data files:
    delete_old_cluster.sh
    
  7. 新版本資料庫最佳化及刪除舊版本資料 升級完畢之後,會在你執行 pg_upgrade 的目錄底下自動產生兩個 shell script 檔案, 執行 analyze_new_cluster.sh 可以最佳化資料庫($PATH_TO_POSTGRESQL/bin/vacuumdb" --all --analyze-in-stages),執行 delete_old_cluster.sh 則會把舊版本的目錄刪除(其實就是`rm -rf postgres.old)
    ./analyze_new_cluster.sh && delete_old_cluster.sh
    
  8. 升級 Postgis extension 若有安裝 postgis extension,所以就順便升級
    spatial_db=# ALTER EXTENSION postgis UPDATE TO "2.1.5";
    spatial_db=# ALTER EXTENSION postgis_topology UPDATE TO "2.1.5";
    

References:

pg_upgrade