因工作需要,要做一个基于国家统计局行政区划编码存储的地理数据查询接口.
1.获取所有省份(直辖市)信息
2.根据省份(直辖市)id获取城市(区)信息
3.根据城市id获取辖区信息
4.根据辖区或城市id或许省份信息
5.性能有要求
参考:
- 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": "延庆县"
}
]
}
]
}
]
程序=数据结构+算法
.那么算法当然就是代码啦.
由于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
,和onHandlerNotFound
、onBadRequest
是区分服务端错误与客户端错误。
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
}
这就是获取省份的主要代码。不过在写的过程中需要注意一下几点:
1. 4个直辖市根据需要单独处理。
2. 性能有要求,那么就要考虑用缓存。这里还用到了`play`自带的缓存`Cache`策略,其中获取省份信息是`Action`缓存。
3. 在实现根据地区id获取省份信息时,用了一个取巧的方式:截取地区id前2位乘以10000就可以得到省份id。
详细源码请戳 GitHub