用编程赋能工作系列——高德地图开发

2020-02-25 11:40:21 浏览数 (1)

作为菜鸟分析师一枚,日常工作中需要处理大量地理位置相关(如城市、辖区、街道、商场、楼宇等)数据。分析报告中总是用吐了的柱形图、条形图,不仅自己看着辣眼睛,老板也审美疲劳。

想画个地图来装点一下报告视觉水平,可是又不会处理经纬度,知道地址不会批量获取经纬度,给出经纬度也不会获取对应的地址。求隔壁的开发小哥哥又远水解不了近渴(开发小哥哥又不是你家的)。

还好我早有准备,偷偷学习了Python和R,处理经纬度这点儿小事儿怎能难住我,自己动手丰衣足食,说不定哪天你就抢了隔壁开发的饭碗。


经纬度获取与处理是空间数据处理的基本功,当前各行业(特别是互联网行业)能叫的上号的头部公司,他们的主营业务也大多涉及复杂的地理区域分析工作,如用户的空间行为轨迹、O2O业务的配送轨迹、传统电商的物流轨迹、经营B端业务的商户地址、旅游&交通用户出行轨迹、摩拜单车行车轨迹、滴滴打车乘车轨迹等……

扯了这么多,没错我只是想说,学会今天要分享的经纬度获取知识,你在日常数据分析工作中会更加游刃有余,会拥有与众不同的视角和切入点。

好吧以下是学习时间:


本篇分为两个技能模块,四个小节,分别会用R语言和Python介绍并实现如何通过具体地址从高德地图api接口中调取地理经纬度,以及如何通过具体经纬度调用具体地理地址。

在开始介绍过程之前,你需要先申请一个高德地图开发者秘钥:

https://lbs.amap.com/api/webservice/guide/create-project/get-key

然后最好读一下高德地图经纬度解析的官方文档

https://lbs.amap.com/api/webservice/guide/api/georegeo

前年的时候分享过一篇百度地图经纬度解析的内容,但是因为两者经纬度编码规则不同,无法直接兼容,所以这里给出高德版方案,以后有时间详细介绍两者如何进行转换的算法。

高德地图开放了经纬度解析的api服务,但是对于个人开发者有日配额,作为非开发人员,完全可以满足日常需要。这些api是通用接口,大多数语言都具备调用能力,其中可提供的返回信息非常多,你可以参考开发api开发文档说明酌情考虑接收你需要的数据。

以下我只提供了精简版的代码,如果你需要更丰富的数据,可以适当修改代码即可。

一、R语言实现方案

1)地址转换经纬度

代码语言:javascript复制
## !/user/bin/env RStudio 1.1.423
## -*- coding: utf-8 -*-
## 高德地图经纬度编码解析

library("httr") 
library("magrittr") 
library("jsonlite")
library('stringr')
library('dplyr')
library('rlist')

dizhi = c('北京市朝阳区望京东路4号横店大厦','北京市海淀区上地信息路9号奎科科技大厦','aaa',NA)
#将地址解析为具体的经纬度:
GetJD <- function(address){
  url = "https://restapi.amap.com/v3/geocode/geo?parameters"
  # 这里是地址转经纬度的api服务调用地址
  header  <- c("User-Agent"="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36")
  # 这个是请求的报头,用来把你的开发平台伪装成一个真实的浏览器
  payload = list(
    'output' = 'json', 
    'key' = '***********************'
    # 各位小伙伴儿一定要自己去申请秘钥,我自己的我就匿了
  ) # 这里是请求的核心参数,一个都不能少,秘钥要自己去申请
  addinfo <- list()
  for (i in dizhi){
    payload[["address"]] = i
    tryCatch({
      web <- GET(url,add_headers(.headers = header),query = payload) %>% content(as="text",encoding="UTF-8") %>% fromJSON(flatten = TRUE) %>% .$geocodes
      if(length(web)  > 0){
        content <- web  %>% .$location %>% str_split(',') %>% `[[`(1) 
        print(sprintf("正在抓取【%s】的经纬度",i))
      }else{
        content <- c(NA,NA)
        cat(sprintf("任务【%s】请求失败!",i),sep = "n") 
      }
      addinfo <- rbind(addinfo,content) 
    },error = function(e){
      cat(sprintf("任务【%s】处理失败!",i),sep = "n")
      addinfo <- rbind(addinfo,c(NA,NA))
    })
    Sys.sleep(runif(1.5))
  }
  result_data <- addinfo %>% matrix(ncol=2) %>% data.frame(adress = dizhi,stringsAsFactors = F)  %>% rename(lng =X1,lat = X2 )
  result_data$lng <- as.numeric(result_data$lng)
  result_data$lat <- as.numeric(result_data$lat)
  print("所有数据全部抓取完毕!!!")
  return(result_data) 
}
system.time(myresult<-GetJD(dizhi))

存放地址的向量中加入了NA和aaa这样的无效值仅仅是为了保证程序容错能力,整体上不涉及太复杂的逻辑实现。

2)经纬度转地址

代码语言:javascript复制
#地址逆解析——将经纬度还原为具体地址
lddata = data.frame(
  lat = c(39.934,40.013,40.047,NA,4444),
  lon = c(116.329,116.495,116.313,NA,6666)
)
GetAddress <- function(lddata){
  url = "https://restapi.amap.com/v3/geocode/regeo?parameters"
  header = c('User-Agent'= 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36')
  payload = list(
    'output' = 'json', 
    'key'    = '*******************'
    # 匿了匿了
  ) 
  addinfo = c() 
  for (i in 1:nrow(lddata)){
    payload[['location']] = sprintf('%.3f,%.3f',lddata[i,'lon'],lddata[i,'lat'])
    tryCatch({
      web <-  GET(url,add_headers(.headers = header),query = payload)  %>% content(as="text",encoding="UTF-8")  %>% fromJSON(flatten = TRUE)   
      if(length(web$regeocode$formatted_address) > 0 ){
        content <-  web  %>% .$regeocode %>% .$formatted_address
        cat(sprintf("正在处理第【%d】个任务!",i),sep = "n")
      } else {
        content <-NA
        cat(sprintf("第【%d】个任务请求失败!",i),sep = "n")
      }
      addinfo <- content %>% c(addinfo,.)
    },error = function(e){
      cat(sprintf("第【%d】个任务处理错误!",i),sep = "n")
      addinfo <- c(addinfo,NA)
    })
    Sys.sleep(runif(2))
  } 
  print("所有任务处理完毕!!!")
  cbind(addinfo,lddata) %>% return()
 
}
system.time(MyAddress <- GetAddress(lddata))

测试的经纬度中加入了NA和444这样的非法经纬度,同样是为了在代码中植入容错模块,提高容错性。在构思代码之前,要提前思考可能遇到哪些错误,不同的错误应该在那个阶段进行容错,不同的错误会导致哪些不同的结果,出现了错误应该如何替补默认返回值。

二、Python实现方案

3)地址转经纬度

代码语言:javascript复制
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import  time
import requests
import re,random
import  numpy  as np
import  pandas  as pd
import 
dizhi = ['北京市朝阳区望京东路6号望京国际研发园三期','北京市海淀区上地信息路9号奎科科技大厦',None,'aaa']
def getid(dizhi):
    url = "https://restapi.amap.com/v3/geocode/geo?parameters"
    header = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}
    payload = {
        'output':'json',
        'key':'*******************'
        # 匿了哦
        }
    addinfo = []
    for i in dizhi:
        payload['address'] = i
        try:
            content =  requests.get(url,params=payload,headers=header).json()
            if content['status'] == '1':
                addinfo.append(re.split(',',content['geocodes'][0]['location']))
                print("任务【{}】请求成功!".format(i))
            else:
                addinfo.append([np.NaN,np.NaN])
                print("任务【{}】请求失败!".format(i))
        except:
            addinfo.append([np.NaN,np.NaN])
            print("任务【{}】处理失败!".format(i))
            pass
        time.sleep(random.choice(range(2)))
    temp = pd.DataFrame(addinfo,columns= ['lng','lat'])
    temp['address'] = dizhi
    return( temp)

if __name__ == "__main__":
    #计时开始:
    t0 = time.time()
    myaddress = getid(dizhi)
    t1 = time.time()
    total = t1 - t0
    print("消耗时间:{}".format(total))

此类api的调用方案,Python的可获取资料网上会比较多一些,毕竟这几年Python火起来之后,Python网络爬虫成了好多小伙伴儿更换职业赛道的重要突破口。

4)经纬度转地址

代码语言:javascript复制
lat = [39.934,40.013,40.047,None,7000]
long = [116.329,116.495,116.313,None,40000]
lddata = [(j,w) for j,w in zip(long,lat)]
def GetAddress(lddata):
    url = "https://restapi.amap.com/v3/geocode/regeo?parameters"
    header = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}
    payload = {
        'output':'json',
        'key':'**********************'
        }
    addinfo = []
    j = 0
    for lon,lat in lddata:
        payload['location'] = '{0:s},{1:s}'.format(str(lon),str(lat))
        try:
            content =  requests.get(url,params=payload,headers=header).json()
            if content['status'] == '1':
                addinfo.append(content['regeocode']['formatted_address'])
                print("任务【{}】请求成功!".format(j 1))
            else:
                addinfo.append(np.NaN)
                print("任务【{}】请求失败!".format(j 1))
        except:
            addinfo.append(np.NaN)
            print("任务【{}】处理失败!".format(j 1))
        time.sleep(random.choice(range(2)))
        j  = 1
    temp = pd.DataFrame(np.array(lddata),columns = ['long','lat'])
    print(addinfo)
    temp['address'] = addinfo
    return(temp)
if __name__ == "__main__":    
    #计时开始:
    t0 = time.time()
    mylonlat = GetAddress(lddata)
    t1 = time.time()
    total = t1 - t0
    print("消耗时间:{}".format(total))

针对高德地图API的调用总结几点:

1)api开放接口是很规范的数据获取渠道,调用成本低,效率高(在接口时限内)相比你从html或者xml里面解析数据要高效的多,核心只需要了解有那些必须提供的请求参数(如想要的返回值格式、使用服务的合法秘钥等)。

2)拿到返回值之后就可以在各平台进行结构化处理,一般都会选择json进行返回,因为这种格式比较主流,各平台处理工具也比较多,比如R语言中的jsonlite、python中则因为字典天然与json格式高度兼容,甚至都不怎么需要特殊处理,使用字典的基本函数就可以很好的提取其中有效信息。

3)关于容错,R语言需要重点复习trycach else的用法,Python中测试需要掌握try …… except等异常捕获处理机制。这里最重要的是,你要判断哪些是错误,哪些可以合法通过请求,但是却不一定能拿到想要的数据,这时需要想办法进行替补或跳出循环(即直接忽略)。

本文真的是一篇满满的干货,也就是这段疫情不能出门的时间才有足够的心力、时间去写,简直可以称作是疫情期间的学习日记。

0 人点赞