最近事情不少,各类你想不到的事情——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