nodejs數據處理實踐之百度poi數據獲取

任務描述

獲取百度上關於深圳市的全部POI數據。php

百度POI類型描述

百度POI行業分類git

這個連接給出了百度的POI分類標準,包括17個一級類別,每一個一級類別下面有多個二級類別。github

此次實驗咱們但願按照一級類別分類來獲取數據。web

百度POI接口介紹

Place APIjson

這個連接介紹了百度的POI接口。文檔介紹的很詳細,這裏就不贅述了。可是有幾個要點但願讀者注意:api

  1. 百度POI接口,雖然能夠指定頁碼和每頁的顯示數目,但不管如何一個請求,最多也只能返回400條數據。
  2. 能夠把region設置爲省級行政區來得到每一個城市的POI數目
  3. coord_type字段控制的是,參數的座標系統,返回值的座標系統是BD09LL,可使用數值擬合的方法把該座標系統的座標轉換到WGS84
    eviltransform

方法

由於一個請求最多隻能返回400條數據,因此不可能使用城市內檢索api來獲取整個城市的全部POI數據。一個可行的想法是,使用矩形檢索API。首先,咱們把城市等間距劃分紅多個矩形,使得每一個矩形都足夠小以致其內部的POI數據條目少於400.而後在分別獲取每一個矩形區域內的全部數據,就能夠實現整個城市的POI數據獲取。promise

這個方法雖然可行但存在一下缺點須要克服:網絡

  1. 城市是不規則的多邊形,簡單的使用城市的bottomLeft和topRigh點來圈定城市的範圍,將會包含不少城市外的區域。以深圳這樣一個狹長的城市爲例,它的外包矩形中大概包含了一半城市外區域。這致使咱們發起了不少沒必要要的請求。
    對於這個問題,咱們可使用Arcgis的fishnet工具來生成矩形格網,而後使用select by location,選出和城市相交的矩形。併發

  2. POI數據的分佈是很不均勻的,簡單地均勻劃分城市爲多個矩形致使了,不少矩形內是沒有POI數據的,而某些矩形內的POI數據仍然是遠大於400的,沒法徹底獲取。
    對於這個問題,咱們能夠只對total == 400的矩形進行進一步劃分。async

  3. 請求數目過多,致使網絡鏈接錯誤

代碼

// 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}\n`)
        }
    }

})
    .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
}

參考文獻:

  1. Place API
  2. eviltransform
  3. 我這樣抓取百度地圖的數據a
相關文章
相關標籤/搜索