任务描述
获取百度上关于深圳市的所有POI数据。
百度POI类型描述
这个链接给出了百度的POI分类标准,包括17个一级类别,每个一级类别下面有多个二级类别。
这次实验我们希望按照一级类别分类来获取数据。
百度POI接口介绍
这个链接介绍了百度的POI接口。文档介绍的很详细,这里就不赘述了。但是有几个要点希望读者注意:
- 百度POI接口,虽然可以指定页码和每页的显示数目,但无论如何一个请求,最多也只能返回400条数据。
- 可以把
region
设置为省级行政区来获得每个城市的POI数目 coord_type
字段控制的是,参数的坐标系统,返回值的坐标系统是BD09LL
,可以使用数值拟合的方法把该坐标系统的坐标转换到WGS84
eviltransform
方法
因为一个请求最多只能返回400条数据,所以不可能使用城市内检索api
来获取整个城市的所有POI数据。一个可行的想法是,使用矩形检索API
。首先,我们把城市等间距划分成多个矩形,使得每个矩形都足够小以至其内部的POI数据条目少于400.然后在分别获取每个矩形区域内的所有数据,就可以实现整个城市的POI数据获取。
这个方法虽然可行但存在一下缺点需要克服:
-
城市是不规则的多边形,简单的使用城市的bottomLeft和topRigh点来圈定城市的范围,将会包含很多城市外的区域。以深圳这样一个狭长的城市为例,它的外包矩形中大概包含了一半城市外区域。这导致我们发起了很多不必要的请求。
对于这个问题,我们可以使用Arcgis的fishnet工具
来生成矩形格网,然后使用select by location
,选出和城市相交的矩形。 -
POI数据的分布是很不均匀的,简单地均匀划分城市为多个矩形导致了,很多矩形内是没有POI数据的,而某些矩形内的POI数据仍然是远大于400的,无法完全获取。
对于这个问题,我们可以只对total == 400
的矩形进行进一步划分。 -
请求数目过多,导致网络连接错误
代码
// main
const config = require('./config')
const superagent = require('superagent')
const co = require('co')
const fs = require('fs')
const transform = require('./transform')
const parallel = require('co-parallel')
const async = require('async')
let type = process.argv[2]
let outputFileName = `./${type}.csv`
let writer = fs.createWriteStream(outputFileName)
const coordinateSplit = require('./coordinateSplit')
// 获得坐标块
let coordArr = coordinateSplit(100, config.bound.bottomLeft, config.bound.topRight)
let qBase = {
ak: config.ak,
q: type,
bounds: ``,
output: 'json',
coord_type: 1,
page_size: 20,
page_num: 0,
}
// 深圳 政府机构的poi数量 10827
function getPoiPromise(q) {
return new Promise((resolve, reject) => {
async.retry(5, (cb) => {
superagent.get(config.url)
.query(q)
.timeout({
response: 500
})
.end((err, res) => {
if (err) {
cb(err)
} else {
cb(null, res)
}
})
}, (err, res) => {
if (err) {
resolve({err,q}) // 重连5次后,仍然错误, 也不要抛出错误,避免程序终止
} else {
let obj = JSON.parse(res.text)
resolve(obj)
}
})
})
}
let numOfPoints = 0
let numOfQuery = 0
let numOfError = 0
function* thunnkGet(q) {
return yield getPoiPromise(q)
}
co(function* () {
let queryArr = []
let prePromiseArr = []
// 这个循环, 对每个小块发起一个请求,来获得小块内的POI数目。
for (let i = 0; i < coordArr.length; i++) {
let coord = coordArr[i]
let q = Object.assign({}, qBase, {
bounds: `${coord.bl.lat},${coord.bl.lng},${coord.tr.lat},${coord.tr.lng}`,
page_num: 0
})
queryArr.push(q)
prePromiseArr.push(thunnkGet(q))
}
let preResArr = yield parallel(prePromiseArr, 80)
for (let i = 0; i < preResArr.length; i++) {
numOfPoints += preResArr[i].total
}
// 这个循环, 针对前一步获得的,块内POI数目,根据块内的POI数目,并发的发出多个请求,来获得具体的POI
let promiseArr = []
for (let i = 0; i < queryArr.length; i++) {
let q = queryArr[i]
let total = preResArr[i].total
if (total > 0) {
let pageCount = Math.ceil(total / 20)
console.log('页数:' + pageCount)
for (let i = 0; i < pageCount; i++) {
let pageQuery = Object.assign({}, q, {
page_num: i
})
// console.log(pageQuery)
numOfQuery++
promiseArr.push(thunnkGet(pageQuery))
}
}
}
let resArr = yield parallel(promiseArr, 100)
for (let i = 0; i < resArr.length; i++) {
let results = resArr[i].results
if (!results) {
numOfError++
console.log(resArr[i])
continue
}
console.log(`获得 ${results.length} 条`)
for (let j = 0; j < results.length; j++) {
let item = results[j]
let wgsLnglat = transform.bd2wgs(item.location.lat, item.location.lng)
writer.write(`${item.name},${wgsLnglat.lat},${wgsLnglat.lng},${item.address},${item.uid}
`)
}
}
})
.catch(err => {
console.log(err)
})
.then(() => {
console.log('兴趣点数目:' + numOfPoints)
console.log('请求数目:' + numOfQuery)
console.log('错误数目:' + numOfError)
})
// console.log(lngArr.length)
// console.log(lngArr)
// console.log(latArr.length)
// console.log(latArr)
// console.log(coordArr.length)
/**
* 把一个由bottomLeft和topRight指定区域均匀划分为numOfCell块
*
*/
module.exports = function (numOfCell=100, bl, tr) {
let numOfRowOrCoL = Math.sqrt(numOfCell)
let spanLng = tr.lng - bl.lng
let spanLat = tr.lat - bl.lat
// 经度的步长
let stepLng = spanLng / numOfRowOrCoL
// 纬度的步长
let stepLat = spanLat / numOfRowOrCoL
let lngArr = []
let latArr = []
let beginLng = bl.lng
let beginLat = bl.lat
for (let i = 0; i < numOfRowOrCoL; i++) {
lngArr.push(beginLng + i * stepLng)
latArr.push(beginLat + i * stepLat)
}
lngArr.push(tr.lng)
latArr.push(tr.lat)
let coordArr = []
for (let row = 0; row < numOfRowOrCoL; row++) {
for (let col = 0; col < numOfRowOrCoL; col++) {
let bl = {
lat: latArr[row],
lng: lngArr[col],
}
let tr = {
lat: latArr[row + 1],
lng: lngArr[col + 1],
}
coordArr.push({
bl,
tr,
})
}
}
return coordArr
}
参考文献: