用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

你可能感兴趣的:(用Scala+Play构建地理数据查询接口)