用Scala+Play构建地理数据查询接口

来源:转载

因工作需要,要做一个基于国家统计局行政区划编码存储的地理数据查询接口.

1.需求:

1.获取所有省份(直辖市)信息

2.根据省份(直辖市)id获取城市(区)信息

3.根据城市id获取辖区信息

4.根据辖区或城市id或许省份信息

5.性能有要求

2.准备:

数据+结构参考

参考:

- http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/

- http://cnis.7east.com/new.html#

- http://www.oschina.net/code/snippet_247841_49375


得到下面的数据

[

{

"id": 110000,

"code": 110000,

"name": "北京市",

"children": [

{

"id": 110100,

"code": 110100,

"name": "市辖区",

"children": [

{

"id": 110101,

"code": 110101,

"name": "东城区"

},

{

"id": 110102,

"code": 110102,

"name": "西城区"

},

{

"id": 110105,

"code": 110105,

"name": "朝阳区"

},

{

"id": 110106,

"code": 110106,

"name": "丰台区"

},

{

"id": 110107,

"code": 110107,

"name": "石景山区"

},

{

"id": 110108,

"code": 110108,

"name": "海淀区"

},

{

"id": 110109,

"code": 110109,

"name": "门头沟区"

},

{

"id": 110111,

"code": 110111,

"name": "房山区"

},

{

"id": 110112,

"code": 110112,

"name": "通州区"

},

{

"id": 110113,

"code": 110113,

"name": "顺义区"

},

{

"id": 110114,

"code": 110114,

"name": "昌平区"

},

{

"id": 110115,

"code": 110115,

"name": "大兴区"

},

{

"id": 110116,

"code": 110116,

"name": "怀柔区"

},

{

"id": 110117,

"code": 110117,

"name": "平谷区"

}

]

},

{

"id": 110200,

"code": 110200,

"name": "县",

"children": [

{

"id": 110228,

"code": 110228,

"name": "密云县"

},

{

"id": 110229,

"code": 110229,

"name": "延庆县"

}

]

}

]

}

]

3.实现

算法

程序=数据结构+算法.那么算法当然就是代码啦.

由于playframework是典型的mvc模式,因此这里根据play的文件结构以获取省份API来介绍。

全局Global部分的代码:

package globals

import filters.{CORSFilter, LoggingFilter}

import models.LocationManager

import play.api._

import play.api.libs.json._

import play.api.mvc.Results._

import play.api.mvc._

import scala.concurrent.Future

object Global extends WithFilters(LoggingFilter, CORSFilter) with GlobalSettings {

override def onStart(app: Application) {

Logger.info("mosquito service has started.")

LocationManager.init("area.json")

}

override def onStop(app: Application) {

Logger.info("mosquito service has stopped.")

}

override def onError(request: RequestHeader, ex: Throwable) = {

Future.successful(InternalServerError(

Json.obj("code" -> 500, "message" -> ex.getLocalizedMessage))

)

}

override def onHandlerNotFound(request: RequestHeader) = {

Future.successful(

NotFound(Json.obj("code" -> 404, "message" -> "NotFound"))

)

}

override def onBadRequest(request: RequestHeader, error: String) = {

Future.successful(

BadRequest(Json.obj("code" -> 400, "message" -> s"Bad Request: $error"))

)

}

}

重写的onStart方法是程序启动时执行的,这里就是初始化area.json数据。

onError和onHandlerNotFoundonBadRequest是区分服务端错误与客户端错误。

Model部分的代码:

package models

import play.api.Play

import play.api.Play.current

import play.api.libs.json._

import scala.io.Source

import scala.collection.concurrent.TrieMap

case class Location(

var id: Long,

var code: Long,

var name: String)

case class LocationView(

var locations: Seq[Location],

var count: Int) extends Serializable

trait LocationFormat {

implicit val LocationFormat = Json.format[Location]

implicit val LocationViewFormat = Json.format[LocationView]

}

object LocationManager extends LocationFormat {

private val locTrieMap = TrieMap[Long, Location]()

private var provincesView: Option[LocationView] = None

def locsMap = { locTrieMap }

def init(filename: String) = {

val filepath = "public/jsons/" + filename

val file = Play.getFile(filepath)

val fileSource = Source.fromFile(file,"utf-8")

val fileContent = {

try

fileSource.getLines().mkString("\n")

finally

fileSource.close()

}

val fileJSON = Json.parse(fileContent)

val jsArrayOpt = fileJSON.asOpt[JsArray]

scanAreaArray(jsArrayOpt, locTrieMap)

// init provinces

initProvinces

}

def reload(filename: String) = {

init(filename)

}

def provinces: LocationView = {

var retProvinces = LocationView(Seq(), 0)

provincesView.foreach { provinces =>

retProvinces = provinces

}

retProvinces

}

def initProvinces: LocationView = {

val locations = locTrieMap.filterKeys(_ % 1000 == 0)

.filterKeys(_ != 419000)

.filterKeys(_ != 429000)

.filterKeys(_ != 469000)

.filterKeys(_ != 659000)

.toList.sortBy(_._1).map(_._2)

val size = locations.length

val provinces = LocationView(locations.toSeq, size)

provincesView = Option(provinces)

provinces

}

private def scanAreaArray(

areaJsArrayOpt: Option[JsArray],

areaTrieMap: TrieMap[Long, Location]): Unit = {

areaJsArrayOpt.foreach { areaJsArray =>

for (areaJsValue <- areaJsArray.value) {

val childrenOpt = (areaJsValue \ "children").asOpt[JsArray]

areaJsValue.validate[Location] match {

case s: JsSuccess[Location] => {

val areaItem = s.get

areaTrieMap += ((areaItem.id, areaItem))

}

case e: JsError => {

}

}

scanAreaArray(childrenOpt, areaTrieMap)

}

}

}

}

Controller部分的代码:

package controllers

import models.{LocationManager, LocationFormat}

import play.api.cache.Cached

import play.api.libs.json._

import play.api.mvc._

import play.api.Play.current

object LocationsCtrl extends Controller with LocationFormat {

def provinces = Cached((_: RequestHeader) => "queryProvinces", 3600){

Action {

val provinces = LocationManager.provinces

Ok(Json.toJson(provinces))

}

}

}

View部分样例:

{

"locations": [

{

"id": 110000,

"code": 110000,

"name": "北京市"

},

{

"id": 120000,

"code": 120000,

"name": "天津市"

},

{

"id": 130000,

"code": 130000,

"name": "河北省"

},

{

"id": 140000,

"code": 140000,

"name": "山西省"

},

{

"id": 150000,

"code": 150000,

"name": "内蒙古自治区"

}

],

"count": 31

}

4.说明

这就是获取省份的主要代码。不过在写的过程中需要注意一下几点:

 1. 4个直辖市根据需要单独处理。

2. 性能有要求,那么就要考虑用缓存。这里还用到了`play`自带的缓存`Cache`策略,其中获取省份信息是`Action`缓存。

3. 在实现根据地区id获取省份信息时,用了一个取巧的方式:截取地区id前2位乘以10000就可以得到省份id。

详细源码请戳 GitHub


分享给朋友:
您可能感兴趣的文章:
随机阅读: