2013年7月19日 星期五

Groovy & Grails 上使用 Postgres 與 PostGIS extension - on Mac

    PostgreSQL 提供了相當方便的 Geo 應用的 extension: PostGIS。有多方便呢?例如,可以下 SQL 撈出「與某點距離小於1公里的其他點」。這樣的功能在開發地圖應用的時候相當的方便。雖然說 Google Map API 也可以幫忙算距離,但如果你資料庫中的景點太多,送 Google Map API 的量就會很可怕,很沒效率!

    Ubuntu 上的安裝與使用,可參考這篇:Postgre 與 PostGIS 安裝 - on Ubuntu
    而 MAC 的話,嚐試了幾種 Mac 上的 Postgres + PostGIS 的安裝方法,KYNG CHAOS Wiki上提供的 package 最容易安裝成功 (for me)。

    由於我是使用 Groovy&Grails 方式開發,安裝設定上遇到一些零粹的咩咩角角的問題,花了一些時間才解掉,這邊跟大家分享對我而言,可以 work 的方法。


1. 安裝:
    a. 自 http://www.kyngchaos.com/software/postgres 下載
        (1) PostgreSQL (撰文時版本為 9.2.4-2)
        (2) GDAL Complete (撰文時版本為 1.10)
        (3) PostGIS (撰文時版本為 2.0.3-4)

    b. 皆為 dmg 檔,直接安裝即可。安裝順序如上(1) -> (2) -> (3)
    c. 安裝完 postgres service 自己會啟動起來,可以 telnet localhost 5432 試看看。
    d. 下載 postgre 的 client 工具: pgAdmin3 (目前版本為 1.16.1),也是 dmg 方式安裝。
    e. 開啟 pgAdmin3,對資料庫 New Extension,可以看到有 postgis 與 postgis_topology extension 可以安裝。(我只裝了 postgis,另一個沒用到)

2. Groovy & Grails (GG)
    a. jar libraries:
        (1) postgre jdbc driver: postgresql-9.2-1003.jdbc4.jar 放於 project 的 lib 目錄。
             於 http://jdbc.postgresql.org/download.html 下載。
        (2) postgis jdbc driver: postgis-jdbc-2.0.2SVN.jar 放於 project lib目錄,
             在 http://postgis.net/source 下載 postgis-2.0.3.tar.gz,解開後,在 postgis-2.0.3/java/jdbc 目錄下執行 ant,會產生 postgis-jdbc-2.0.2SVN.jar 在 target 目錄。
        (3) project 右鍵 --> Grails Tools --> Refresh Dependencies 。

    b. Data Source Config
        修改 DataSource.groovy,如下:
dataSource {
dbCreate = "update" // one of 'create', 'create-drop','update'
    url = "jdbc:postgresql://localhost/trip85m"
    // driverClassName = "org.postgresql.Driver" //for postgres only
    // dialect = 'com.e4net.hibernate.dialect.PostgreSQL82Dialect' //for postgres only
  driverClassName = "org.postgis.DriverWrapper" //for postgis
dialect = 'org.postgis.hibernate.PostGISDialect'        //for postgis
logSql = true
    username = "postgres"
    password = ""
}
hibernate {
    cache.use_second_level_cache = true
    cache.use_query_cache = false
    cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory'
}
// environment specific settings
environments {
    development {
        dataSource {
            dbCreate = "update" // one of 'create', 'create-drop', 'update', 'validate', ''
        }
    }
    test {
        dataSource {
            dbCreate = "update"
        }
    }
    production {
        dataSource {
            dbCreate = "update"
        }
    }
}

    c. PostGIS hibernate Dialect
        在 GG project src/java 中,建立 org.postgis.hibernate package,然後將 http://postgis.net/source 下載的 postgis-2.0.3.tar.gz 解開後,複製 postgis-2.0.3/java/ejb3/src/org/postgis/hibernate 目錄中的 GeometryType.java、PostGISDialect.java 到 src/java org.postgis.hibernate package 中。

    d. 進入到 code...
        (1) Domain Class
             domain field 直接宣告為 Point Datatype,然後 mapping 中定義 column data type 為 GeometryType
import org.postgis.Point
import org.postgis.hibernate.GeometryType

class Spot {

    String name;
    String latitude;
    String longitude;
    Point point;

    static constraints = {
    }

    static mapping = {
        columns {
            point type:GeometryType
        }
    }
}

            當 grails 中,執行 run-app command 會自動建立 table,以 pgAdmin3 檢視 table,domain class 的 point 對應 column,其 data type 為 geometry


        (2) 儲存 domain class
    Spot spot1 = new Spot();
    spot1.name = "美利冷熱飲食小吃";
    spot1.longitude = "120.6136";
    spot1.latitude = "24.332579";
    spot1.point = new Point(Double.parseDouble(spot.longitude), Double.parseDouble(spot.latitude));
    spot1.point.srid = 4326;  //以 4326 座標系統儲存
    spot1.save();

    Spot spot2 = new Spot();
    spot2.name = "香榭自助式牛排";
    spot2.longitude = "120.60535";
    spot2.latitude = "24.334566";
    spot2.point = new Point(Double.parseDouble(spot.longitude), Double.parseDouble(spot.latitude));
    spot2.point.srid = 4326;  //以 4326 座標系統儲存
    spot2.save();



    d. 驗證
        存入後,pgAdmin 開 SQL 視窗 select 一下,可以看到有存入。id 為 5 與 6。

    e. 計算兩景點距離
select ST_Distance(
    ST_Transform((select point from spot where id=5), 900913),
    ST_Transform((select point from spot where id=6), 900913)
) as distance; 
        透過 ST_Transform function 將 Point 轉為 google 座標系統。(900913 looks like google...)
        透過 ST_Distance function 計算兩 Point 距離。如下圖,以上兩點相距 949.9 公尺。

    f. 多塞一點資料,撈出與 id=5 的距離小於 1500 公尺所有景點。


    威吧!!! 就是這麼方便!!!
    (待補...以 groovy 撈點出來......)


沒有留言: