最近事情不少,各類你想不到的事情——such as singing and dancing——再加上最近又研究docker上癮,因此geotrellis看上去彷佛沒有關注,其實我一直在腦中思考着geotrellis。以前看geotrellis源碼看到有關geotrellis.slick的相關部分,僅大概瀏覽了一番,知道是用於讀取PostGIS數據庫的,未作深刻研究,又恰巧前幾日有老外在gitter上問了如何讀取PostGIS數據庫,我當時回答他能夠用傳統的JDBC方式或者使用geotrellis.slick。JDBC方式我是親自測試過的,在geotrellis使用(十一)實現空間數據庫柵格化以及根據屬性字段進行賦值一文中,我詳細講述瞭如何從PostGIS中讀取空間數據並進行柵格化操做;然而我也有極度強迫症,一個事物不知道還着罷了,一旦讓我知道我是必定要拿來試試的,尤爲在新技術方面,因此這兩天就研究了一下,基本調通。現總結以下,以待查用。html
geotrellis.slick是geotrellis的一個模塊,它是對slick的封裝。固然若是你要問我什麼是geotrellis,請你先從底部的系列連接中看看前面的博客,大體能對其有個瞭解。git
先介紹一下slick,它是一款開源的scala語言數據庫處理框架,官網http://slick.lightbend.com/。官網介紹以下:sql
Slick is a modern database query and access library for Scala. It allows you to work with stored data almost as if you were using Scala collections while at the same time giving you full control over when a database access happens and which data is transferred. You can write your database queries in Scala instead of SQL, thus profiting from the static checking, compile-time safety and compositionality of Scala. Slick features an extensible query compiler which can generate code for different backends.docker
大概是說Slick使得咱們能像處理普通Scala集合那樣處理多種數據庫,並能對數據庫進行控制,至關於一個ORM框架。它支持如下幾種數據庫:shell
geotrellis.slick對其進行了封裝,支持PostGIS數據庫並可以簡單的進行空間數據的讀寫。數據庫
話很少說,直接進入乾貨。首先是對geotrllis.slick的引用,在build.sbt中的libraryDependencies添加以下項:api
"org.locationtech.geotrellis" %% "geotrellis-slick" % 1.1.1
與普通JDBC方式鏈接基本相同,建立一個鏈接對象便可。代碼以下:數組
object ConnDatabase { def newInstance(pghost: String, pgdb: String, pguser: String, pgpass: String) = { val s = s"jdbc:postgresql://$pghost/$pgdb" Database.forURL( s, driver="org.postgresql.Driver", user=pguser, password=pgpass ) } } trait ConnDatabase { protected var db: Database = null def connectDb(pghost: String, pgdb: String, pguser: String, pgpass: String) { db = ConnDatabase.newInstance(pghost, pgdb, pguser, pgpass) } }
建立了一個特質(trait)ConnDatabase,其中包含了db對象,此對象即爲數據庫鏈接,後續都要基於此對象進行操做。app
首先要在PostGIS中建立一個數據庫(此處假設爲test),此數據庫要選擇空間模板以使該數據庫支持空間操做。框架
在建立映射以前,須要先建立一個類使得程序可以正確識別此類映射並加入相應PostGIS擴展。代碼以下:
object driver extends PostgresDriver with PostGisSupport { override val api = new API with PostGISAssistants with PostGISImplicits }
此類中的api對象須要被實體類和操做類引用,具體在下面講述。
咱們以城市這個實體爲例,假設僅僅關注城市名稱以及經緯度座標,考慮到數據庫操做則須要再加一ID項。那麼城市實體的定義以下:
import driver.api._ class City(tag: Tag) extends Table[(Int, String, Point)](tag, "cities") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) def name = column[String]("name") def geom = column[Point]("geom") def * = (id, name, geom) }
直觀上說這段代碼很容易理解,City實體對應與cities表;id字段對應表中id字段,併爲主鍵及自動增加,類型爲Int;name對應表中name字段,類型爲String;geom對應空間字段geom,類型爲Point(空間字段類型能夠直接設置爲Geometry);def * 表示三個字段的組合。固然此處也能夠設置字段可空,只須要將類型使用Option包裹而且上下對應便可,如須要設置geom可空,則整個類修改以下:
class City(tag: Tag) extends Table[(Int, String, Option[Point])](tag, "cities") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) def name = column[String]("name") def geom = column[Option[Point]]("geom") def * = (id, name, geom) }
因此在定義實體類與數據庫表映射的時候,首先引入上面driver中定義的api,以後定義實體類繼承自Table對象,其泛型即爲def *中組合類型,而且兩者順序必須徹底一致。這樣就定義好了兩者映射。
上文講到slick的優點就在於咱們能夠像使用scala集合那樣讀取數據庫中信息,並可以對數據庫進行操做。首先定義一個CityOperate類,在其中完成對City的操做,有點dal或者bll的感受。
import geotrellis.postgis.model.City import org.scalatest.concurrent.ScalaFutures import geotrellis.vector._ object CityOperate extends ConnDatabase with ScalaFutures { import driver.api._ implicit override val patienceConfig = PatienceConfig(timeout = Span(5, Seconds)) val pguser = "******" val pgpass = "******" val pgdb = "test" val pghost = "127.0.0.1:5432" connectDb(pghost, pgdb, pguser, pgpass) val CityTable = TableQuery[City] }
該類繼承自ConnDatabase和ScalaFutures。
其中ConnDatabase是上文咱們寫好的數據庫鏈接類,主要目的在於獲得其中的db對象,因此必須先執行connectDb函數,傳入數據庫參數。
ScalaFutures主要是獲取查詢等的Future操做的結果值。
引入上面driver中定義的api,並重寫patienceConfig加大超時時間,防止下面的future執行超時。
CityTable很明顯是City的映射對象,主要基於此對象對數據庫進行操做。
咱們能夠無需建立表cities而由slick完成,只須要在上述類中添加以下方法:
def createSchema { try { db.run(CityTable.schema.create).futureValue } catch { case _: Throwable => } }
該函數實現的功能就是建立cities表。從這段代碼大體能看出slick的整個操做模式,其全部操做都要執行db.run函數,傳入的是進行的操做,不管是增刪改查仍是建立、刪除表等。此函數的結果須要進行futureValue操做,來獲取真正的結果,若是不加此項則不會進行操做。CityTable.schema.create表示進行的是建立schema操做。
能夠經過CityTable.schema.create.statements
來查看建立表的SQL語句。
有了建立表操做,刪除操做就很容易了,代碼以下:
def dropSchema { try { db.run(CityTable.schema.drop).futureValue } catch { case _: Throwable => } }
很簡單,只須要在db.run函數中傳入CityTable.schema.drop。
能夠經過CityTable.schema.drop.statements
來查看建立表的SQL語句。
進入數據庫操做以及碼農的最最最常規操做。增長數據代碼以下:
def insertData(data: Array[(String, Point)]) { db.run(CityTable.map(c => (c.name, c.geom)) ++= data.map { d => (d._1, d._2) }) }
函數接受(String, Point)類型的數組,表示名稱和位置。插入操做也很容易,直接像db.run函數傳入CityTable.map(c => (c.name, c.geom)) ++= data.map { d => (d._1, d._2) }
,++=正是一個插入操做的action,前面表示的是要插入的字段名稱,後面則是對應的數據,此處表示插入name和geom字段,後面爲數據。
固然若是在實體映射中某個字段按照上述方式設置可空,那麼在insert以及下面的update操做的時候此字段的類型都要爲Option,即有值的地方使用Some包裹,無值的地方設置爲None。
能夠經過
(CityTable.map(c => (c.name, c.geom)) ++= data.map { d => (d._1, d._2) }).statements
來查看插入的SQL語句,其實到這裏你們應該能總結出來規律,只要對傳入db.run函數的參數執行statements操做就能查看此操做的SQL語句,如下同,再也不贅述。
刪除數據分爲刪除所有和有條件刪除。
def deleteAllData { val q = for { c <- CityTable } yield c db.run(q.delete).futureValue }
從這段代碼能看出slick對數據操做的基本流程,首先使用for循環生成想要處理的數據的集合,然後使用db.run對此集合執行相應的操做。
上述代碼中q表示的是所有數據,db.run傳入的也是q.delete,則表中全部數據都會被刪除。
def bboxBuffer(x: Double, y: Double, d: Double) = Polygon(Line( (x - d, y - d), (x - d, y + d), (x + d, y + d), (x + d, y - d), (x - d, y - d) )) def deleteDataByBufer { val bbox = bboxBuffer(78.32, 40.30, 0.01) val q = for(c <- CityTable if c.geom @&& bbox) yield c db.run(q.delete).futureValue }
其中bboxBuffer函數表示給定一個點和距離建立其緩衝區。
在deleteDataByBufer函數中,咱們先建立了一個bbox緩衝區,該函數的目的是刪除全部座標在給定緩衝區內的城市。能夠看出此處q的值在獲取的時候稍有變化,加了一個c.geom @&& bbox的條件,@&&是geotrellis寫好的空間支持函數,該函數表示前面的空間是否在緩衝區(Polygon)中。將q.delete傳入db.run便可實現刪除部分數據的目的,固然按照其餘條件刪除則同理。
def updateData(name: String) { val bbox = bboxBuffer(78.32, 40.30, 0.01) val q = for (c <- CityTable if c.geom @&& bbox) yield c.name db.run(q.update(name)).futureValue }
此函數實現的功能是將詞緩衝區內城市名稱所有改成傳入的name參數。區別只是在於將q.update(name)傳入db.run函數。
一樣查也分爲查詢所有數據和查詢部分數據,其實基本與上述相同。
def getData = { val q = for { c <- CityTable } yield (c.name, c.geom) db.run(q.result).futureValue.toList }
q獲取到的是城市名稱和位置信息,則最後查詢的結果就是全部城市的名稱和位置信息,不包含id。將q.result傳入db.run函數便可獲取到最終結果。
def getDataByBuffer = { val bbox = bboxBuffer(78.32, 40.30, 0.01) val q = for(c <- CityTable if c.geom @&& bbox) yield c db.run(q.result).futureValue.toList }
該函數實現的功能是查詢緩衝區內的城市信息,此處q直接獲取到的是緩衝區內的城市全部信息,因此將q.result傳入db.run後就能獲取到緩衝區內的城市的全部信息。
geotrelis.slick支持將scala的空間操做轉換爲PostGIS的空間函數,以下:
def getGeomWKTData { val q = for { c <- CityTable } yield c.geom.asEWKT println(q.result.statements) db.run(q.result).futureValue.toList }
上述函數中直接對geom對象進行asEWKT操做,將Point轉化爲WKT語言,並輸出查詢結果。執行上面的函數會打印出以下信息:
List(select ST_AsEWKT("geom") from "cities")
代表geotrellis.slick確實將asEWKT操做轉換爲PostGIS中的ST_AsEWKT函數。
本文嘗試了geotrliis.slick的相關功能和用法,因爲剛接觸可能有理解不透徹的地方,歡迎留言指正,不甚感激!
Geotrellis系列文章連接地址http://www.cnblogs.com/shoufengwei/p/5619419.html