├── .gitignore ├── MANIFEST.in ├── README.rst ├── mapapi ├── __init__.py ├── baidu │ ├── __init__.py │ ├── exceptions.py │ ├── location_api.py │ ├── place_api.py │ ├── scheduler.py │ └── transform_api.py ├── example │ ├── __init__.py │ ├── clinic.py │ ├── shiwan.py │ └── uid.py └── tests │ ├── test_location_api.py │ ├── test_main.py │ ├── test_map_api.py │ ├── test_place_api.py │ ├── test_scheduler.py │ └── test_transform_api.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled python module. 2 | *.pyc 3 | 4 | # Setuptools distribution folder. 5 | /dist/ 6 | 7 | # Python egg metadata, regenerated from source files by setuptools. 8 | /*.egg-info 9 | 10 | .idea 11 | build 12 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | mapapi 2 | ----------------- 3 | 4 | 通过调用第三方地图接口, 处理与地图相关应用. 目前仅支持百度地图, 期待支持更多地图应用. 5 | 6 | 使用方法: 7 | 8 | - 获取地址地理坐标 9 | 10 | >>> from mapapi import baidu 11 | >>> map_api = baidu.MapApi(['your application key', ...]) 12 | >>> location = map_api.location_api.get_location_by_address(u'百度大厦', u'北京') 13 | >>> print(location) 14 | 15 | 16 | - 获取地理坐标对应的详细地址 17 | 18 | >>> from mapapi import baidu 19 | >>> map_api = baidu.MapApi(['your application key', ...]) 20 | >>> address = map_api.location_api.get_address_by_location({'lng': 116.322987, 'lat': 39.983424}) 21 | >>> print(address) 22 | 23 | 24 | - 获取标准化地址信息 25 | 26 | >>> from mapapi import baidu 27 | >>> map_api = baidu.MapApi(['your application key', ...]) 28 | >>> address = map_api.location_api.get_formatted_address(u'北京市海淀区百度大厦') 29 | >>> print(address) 30 | 31 | 32 | - 通过关键词查询所有地名或店铺等信息 33 | 34 | >>> from mapapi import baidu 35 | >>> map_api = baidu.MapApi(['your application key', ...]) 36 | >>> ret = map_api.place_api.get_place_all(u'银行', u'济南') 37 | >>> print(ret) 38 | 39 | - 通过百度地图uid信息获取对应的地址信息 40 | 41 | >>> from mapapi import baidu 42 | >>> map_api = baidu.MapApi(['your application key', ...]) 43 | >>> place = map_api.place_api.get_place_by_uids('c14fc238f7fadd4ea5da390f') 44 | >>> print(place) 45 | 46 | - 将腾讯地图坐标转换成百度地图坐标 47 | 48 | >>> from mapapi import baidu 49 | >>> map_api = baidu.MapApi(['your application key', ...]) 50 | >>> coords = map_api.transform_api.transform({'lat': 29.5754297789, 'lng': 114.218927345}) 51 | >>> print(coords) 52 | 53 | 54 | 安装方法: 55 | 56 | pip install mapapi 57 | -------------------------------------------------------------------------------- /mapapi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yimian/mapapi/248bdeb253f7a52d08f5cbb4327233a8dc5df885/mapapi/__init__.py -------------------------------------------------------------------------------- /mapapi/baidu/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ use as main map interface. """ 3 | 4 | from mapapi.baidu.scheduler import RoundRobinScheduler 5 | from mapapi.baidu.location_api import LocationApi 6 | from mapapi.baidu.place_api import PlaceApi 7 | from mapapi.baidu.transform_api import TransformApi 8 | 9 | 10 | class MapApi(object): 11 | def __init__(self, aks=None, scheduler=None, **kwargs): 12 | """ 13 | initialize MapApi instance with application keys `aks`, aks scheduler. 14 | 15 | :param aks: list of application keys. 16 | :param scheduler: default RoundRobinScheduler 17 | :param kwargs: 18 | """ 19 | super(MapApi, self).__init__() 20 | self.aks = aks or ['wwx6xhe8aQncZZUm7QsIPXKI', '9ea66EnDo1YLFuzu5QDDp4zU'] 21 | self.scheduler = scheduler 22 | self._place_api_inst = None 23 | self._location_api_inst = None 24 | self._transform_api_inst = None 25 | 26 | def set_aks(self, aks): 27 | """ 28 | set application keys. 29 | 30 | :param aks: list of application keys 31 | """ 32 | self.aks = aks 33 | 34 | def set_scheduler(self, scheduler): 35 | """ 36 | set scheduler. 37 | 38 | :param scheduler: 39 | """ 40 | self.scheduler = scheduler 41 | 42 | @property 43 | def place_api(self): 44 | """ 45 | get place api interface. 46 | 47 | :return: PlaceApi instance. 48 | """ 49 | if self._place_api_inst: 50 | return self._place_api_inst 51 | if not self.scheduler: 52 | self.scheduler = RoundRobinScheduler(self.aks) 53 | self._place_api_inst = PlaceApi(self.scheduler) 54 | return self._place_api_inst 55 | 56 | @property 57 | def location_api(self): 58 | """ 59 | get location api interface. 60 | 61 | :return: LocationApi instance. 62 | """ 63 | if self._location_api_inst: 64 | return self._location_api_inst 65 | if not self.scheduler: 66 | self.scheduler = RoundRobinScheduler(self.aks) 67 | self._location_api_inst = LocationApi(self.scheduler) 68 | return self._location_api_inst 69 | 70 | @property 71 | def transform_api(self): 72 | """ 73 | get transform api interface. 74 | 75 | :return: TransformApi instance. 76 | """ 77 | if self._transform_api_inst: 78 | return self._transform_api_inst 79 | if not self.scheduler: 80 | self.scheduler = RoundRobinScheduler(self.aks) 81 | self._transform_api_inst = TransformApi(self.scheduler) 82 | return self._transform_api_inst 83 | -------------------------------------------------------------------------------- /mapapi/baidu/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | 5 | class ParamError(Exception): 6 | """ 7 | raise when parameter format error. 8 | """ 9 | pass 10 | -------------------------------------------------------------------------------- /mapapi/baidu/location_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | u""" 处理城市坐标 """ 3 | __author__ = 'zhangjinjie' 4 | 5 | import requests 6 | import logging 7 | import json 8 | from mapapi.baidu.exceptions import ParamError 9 | 10 | # 返回码 11 | baidu_ret_code = { 12 | 0: u'正常', 13 | 1: u'服务器内部错误', 14 | 2: u'请求参数非法', 15 | 3: u'权限校验失败', 16 | 4: u'配额校验失败', 17 | 5: u'ak不存在或者非法', 18 | 101: u'服务禁用', 19 | 102: u'不通过白名单或者安全码不对', 20 | } 21 | 22 | 23 | class LocationApi(object): 24 | """ 25 | translate address to geographical coordinates(refer to location) or location to address.... 26 | """ 27 | # 通过地址获取地理坐标url地址 28 | baidu_json_location_url = u'http://api.map.baidu.com/geocoding/v3/' 29 | 30 | # 通过地理坐标返回地址信息url地址 31 | baidu_json_region_url = u'http://api.map.baidu.com/reverse_geocoding/v3/' 32 | 33 | def __init__(self, scheduler): 34 | self.scheduler = scheduler 35 | 36 | def _format_province(self, province): 37 | u""" 38 | 标准化省份信息, 比如将'内蒙古自治区'统一转换成'内蒙古'. 39 | 40 | :param province: origin province 41 | :return: formatted province 42 | """ 43 | length = len(province) 44 | if province.endswith(u'省') or province.endswith(u'市'): 45 | return province[0:length - 1] 46 | elif province.endswith(u'自治区'): 47 | if u'内蒙古' in province: 48 | return u'内蒙古' 49 | if u'宁夏' in province: 50 | return u'宁夏' 51 | if u'广西' in province: 52 | return u'广西' 53 | if u'新疆' in province: 54 | return u'新疆' 55 | if u'西藏' in province: 56 | return u'西藏' 57 | return province 58 | 59 | def _format_city(self, city): 60 | """ 61 | 标准化城市信息, 去掉城市尾部所带的'市'. 62 | 63 | :param city: origin city 64 | :return: formatted city 65 | """ 66 | if u'市' in city: 67 | length = len(city) 68 | return city[0:length - 1] 69 | return city 70 | 71 | def get_detail_location_by_address(self, address, city=None): 72 | """ 73 | 根据地址信息获取地理坐标 74 | 75 | :param address: address must include province and city information. 76 | :param city: city just functions as a filter for return data. 77 | :return: if success, return 78 | { 'location': # 经纬度坐标 79 | { 'lat': , 'lng': }, 80 | 'precise': , # 位置的附加信息, 是否精确查找, 1为精确查找, 0为不精确 81 | 'confidence': , # 可信度 82 | 'level': , # 地址类型 83 | } 84 | else return None. 85 | """ 86 | if address is None: 87 | raise ParamError(u'address不能为空') 88 | params = {'address': address, 'ak': self.scheduler.next(), 'output': 'json'} 89 | if city: 90 | params['city'] = city 91 | try: 92 | r = requests.get(self.baidu_json_location_url, params=params) 93 | r.raise_for_status() 94 | 95 | data = json.loads(r.text) 96 | if data['status'] == 0: 97 | return data['result'] 98 | else: 99 | logging.debug(r.text) 100 | except Exception as e: 101 | logging.exception(e) 102 | 103 | logging.error(u'failed to get address location for %s' % address) 104 | return None 105 | 106 | def get_location_by_address(self, address, city=None): 107 | """ 108 | Same with get_detail_location_by_address, except for just return {'lat': , 'lng'} location info. 109 | 110 | :param address: 111 | :param city: 112 | :return: if success return {'lat': , 'lng': } else return None. 113 | """ 114 | detail = self.get_detail_location_by_address(address, city) 115 | if detail: 116 | return detail['location'] 117 | return None 118 | 119 | def get_detail_address_by_location(self, location, **kwargs): 120 | """ 121 | 根据地理坐标获取地址信息 122 | 123 | :param location: {'lat': , 'lng': } 地理坐标 124 | :param kwargs: 125 | :return: if success return 126 | { 'location': {'lat':, 'lng'}, 127 | 'origin_location': {'lat':, 'lng'}, # 这是请求的location, 并非百度原始返回的 128 | 'formatted_address":, # 结构化地址信息 129 | 'business':, # 商圈 130 | 'addressComponent': # 地址信息 131 | { 'country":, # 国家 132 | 'province':, # 省名 133 | 'city':, # 城市名 134 | 'district':, # 区县名 135 | 'street':, # 街道名 136 | 'street_number':, # 街道门牌号 137 | 'country_code':, # 国家code 138 | 'direction':, # 和当前坐标点的方向 139 | 'distance': # 和当前坐标点的距离 140 | } 141 | 'pois': # 周边poi数组 142 | { 'addr':, # 地址信息 143 | 'cp':, # 数据来源 144 | 'direction':, # 和当前坐标点方向 145 | 'distance':, # 离坐标点距离 146 | 'name':, # poi名称 147 | 'poiType':, # poi类型, 如'办公大楼, 商务大厦' 148 | 'point':, # poi坐标 149 | 'tel':, # 电话 150 | 'uid':, # poi唯一标识 151 | 'zip':, # 邮编 152 | } 153 | 'sematic_description':, # 当前位置结合POI的语义化结果描述 154 | } or return None. 155 | :raise ParamError: if location is None. 156 | """ 157 | if location is None: 158 | raise ParamError(u'location不能为空') 159 | params = {'ak': self.scheduler.next(), 'output': 'json', 'location': '{lat},{lng}'.format(lat=location['lat'], 160 | lng=location['lng']), 161 | 'coordtype': kwargs.get('coordtype', 'bd09ll'), 'pois': kwargs.get('extensions_poi', 0)} 162 | try: 163 | r = requests.get(self.baidu_json_region_url, params=params) 164 | r.raise_for_status() 165 | 166 | data = json.loads(r.text) 167 | if data['status'] == 0: 168 | data['result']['origin_location'] = {'lat': location['lat'], 'lng': location['lng']} 169 | return data['result'] 170 | else: 171 | logging.debug(r.text) 172 | except Exception as e: 173 | logging.exception(e) 174 | 175 | logging.error(u'failed to get province for %s' % str(location)) 176 | return None 177 | 178 | def get_province_by_location(self, location, **kwargs): 179 | """ 180 | Same with get_detail_address_by_location, except for just returning formatted province info. 181 | 182 | :param location: {'lat':, 'lng':,} 183 | :param kwargs: 184 | :return: if success, return formatted province, else return None. 185 | """ 186 | detail = self.get_detail_address_by_location(location, **kwargs) 187 | if detail: 188 | return self._format_province(detail['addressComponent']['province']) 189 | return None 190 | 191 | def get_address_by_location(self, location, **kwargs): 192 | """ 193 | Same with get_detail_address_by_location, except for just returning addressComponent info whose provine and city 194 | are formatted. 195 | 196 | :param location: {'lat':, 'lng':} 197 | :param kwargs: 198 | :return: if success return 199 | { 'country":, # 国家 200 | 'province':, # 省名 201 | 'city':, # 城市名 202 | 'district':, # 区县名 203 | 'street':, # 街道名 204 | 'street_number':, # 街道门牌号 205 | 'country_code':, # 国家code 206 | 'direction':, # 和当前坐标点的方向 207 | 'distance': # 和当前坐标点的距离 208 | } 209 | else return None. 210 | """ 211 | detail = self.get_detail_address_by_location(location, **kwargs) 212 | if detail: 213 | address = detail['addressComponent'] 214 | address['province'] = self._format_province(address['province']) 215 | address['city'] = self._format_province(address['city']) 216 | return address 217 | return None 218 | 219 | def get_formatted_province(self, address): 220 | """ 221 | 返回地址所对应的标准化省份名称 222 | 223 | :param address: 详细地址信息 224 | :return: if success return province info else return None. 225 | """ 226 | location = self.get_location_by_address(address) 227 | if location: 228 | return self.get_province_by_location(location) 229 | return None 230 | 231 | def get_formatted_address(self, address): 232 | """ 233 | 返回地址所对应的标准化地址信息 234 | 235 | :param address: 详细地址信息 236 | :return: if success return 237 | { 'country":, # 国家 238 | 'province':, # 省名 239 | 'city':, # 城市名 240 | 'district':, # 区县名 241 | 'street':, # 街道名 242 | 'street_number':, # 街道门牌号 243 | 'country_code':, # 国家code 244 | 'direction':, # 和当前坐标点的方向 245 | 'distance': # 和当前坐标点的距离 246 | } 247 | else return None. 248 | """ 249 | location = self.get_location_by_address(address) 250 | if location: 251 | return self.get_address_by_location(location) 252 | return None 253 | 254 | def get_formatted_detail_address(self, address): 255 | """ 256 | 返回地址所对应的标准化的详细地址 257 | 258 | :param address: 详细地址信息 259 | :return: if success return 260 | { 'location': {'lat':, 'lng'}, 261 | 'origin_location': {'lat':, 'lng'}, # 这是请求的location, 并非百度原始返回的 262 | 'formatted_address":, # 结构化地址信息 263 | 'business':, # 商圈 264 | 'addressComponent': # 地址信息 265 | { 'country":, # 国家 266 | 'province':, # 省名 267 | 'city':, # 城市名 268 | 'district':, # 区县名 269 | 'street':, # 街道名 270 | 'street_number':, # 街道门牌号 271 | 'country_code':, # 国家code 272 | 'direction':, # 和当前坐标点的方向 273 | 'distance': # 和当前坐标点的距离 274 | } 275 | 'pois': # 周边poi数组 276 | { 'addr':, # 地址信息 277 | 'cp':, # 数据来源 278 | 'direction':, # 和当前坐标点方向 279 | 'distance':, # 离坐标点距离 280 | 'name':, # poi名称 281 | 'poiType':, # poi类型, 如'办公大楼, 商务大厦' 282 | 'point':, # poi坐标 283 | 'tel':, # 电话 284 | 'uid':, # poi唯一标识 285 | 'zip':, # 邮编 286 | } 287 | 'sematic_description':, # 当前位置结合POI的语义化结果描述 288 | } or return None. 289 | """ 290 | location = self.get_location_by_address(address) 291 | if location: 292 | return self.get_detail_address_by_location(location) 293 | return None 294 | 295 | -------------------------------------------------------------------------------- /mapapi/baidu/place_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | import requests 5 | import json 6 | import logging 7 | 8 | 9 | class PlaceApi(object): 10 | u""" 11 | search poi data by keyword. 12 | """ 13 | search_url = 'http://api.map.baidu.com/place/v2/search' 14 | detail_url = 'http://api.map.baidu.com/place/v2/detail' 15 | eventsearch_url = 'http://api.map.baidu.com/place/v2/eventsearch' 16 | eventdetail_url = 'http://api.map.baidu.com/place/v2/eventdetail' 17 | 18 | def __init__(self, scheduler): 19 | self.scheduler = scheduler 20 | 21 | def get_place_by_page(self, query, region, **kwargs): 22 | u""" 23 | 城市内检索 24 | 25 | 百度在没有查找到对应查询请求时, 会返回在其它城市查找到的结果, 返回格式为[{'num': , 'name': ''} ...]这样的数组 26 | 获取一页query相关地理信息 27 | :param query: 查询关键词 28 | :param region: 地区 29 | :param kwargs: 30 | :return: if success return 31 | { 32 | status: 本次API访问状态, 成功返回0, 其他返回其他数字, 33 | message: 对本次API访问状态值的英文说明, 如果成功返回'ok', 失败返回错误说明, 34 | total: 检索总数, 用户请求中设置了page_num字段时才会出现, 当检索总数超过760时, 多次刷新同一请求得到的total值, 可能稍有不同 35 | results: [ 36 | { 37 | name: POI名称, 38 | location: { 39 | lat: 纬度, 40 | lng: 经度 41 | }, 42 | address: POI地址信息, 43 | telephone: POI电话信息, 44 | uid: POI的唯一标识, 45 | detail_info: { # POI扩展信息, 仅当scope=2时, 显示该字段, 不同POI类型, 显示的detail_info字段不同 46 | distance: 距离中心点距离, 47 | type: POI类型, 48 | tag: 标签, 49 | detail_url: POI的详情页, 50 | price: POI商户的价格, 51 | shop_hours: 营业时间, 52 | overall_rating: 总体评分, 53 | taste_rating: 口味评分, 54 | service_rating: 服务评分, 55 | environment_rating: 环境评分, 56 | facility_rating: 星级评分, 57 | hygiene_rating: 卫生评分, 58 | technology_rating: 技术评分, 59 | image_num: 图片数, 60 | groupon_num: 团购数, 61 | discount_num: 优惠数, 62 | comment_num: 评论数, 63 | favorite_num: 收藏数, 64 | checkin_num: 签到数 65 | } 66 | } 67 | ... 68 | ] 69 | } 70 | else return None. 71 | """ 72 | tag = kwargs.get('tag', '') 73 | scope = kwargs.get('scope', 1) # 检索结果详细成都, 1 基本信息, 2 POI详细信息 74 | # filter字段设置, scope为2时有效 75 | industry_type = kwargs.get('industry_type', 'cater') # 行业类型. 取值范围为: hotel 宾馆, cater 餐饮, life 生活娱乐 76 | # 排序字段. industry_type为hotel时, 取指范围为: default 默认, price 价格, total_score 好评, level: 星级, 77 | # health_score: 卫生, distance: 距离; 为cater时, default: 默认, taste_rating: 口味, price: 价格, 78 | # overall_rating: 好评, service_rating: 服务, distance: 距离; 为life时, default: 默认, price: 价格, 79 | # overall_rating: 好评, comment_num: 服务, distance: 距离 80 | sort_name = kwargs.get('sort_name', 'default') 81 | sort_rule = kwargs.get('sort_rule', 0) # 排序规则, 0 从高到低, 1 从低到高 82 | groupon = kwargs.get('groupon', 1) # 是否有团购, 1 有团购, 0 无团购 83 | discount = kwargs.get('discount', 1) # 是否有打折, 1 有打折, 0 无打折 84 | page_size = kwargs.get('page_size', 20) # 每页数据记录数. 最大返回20条 85 | page_num = kwargs.get('page_num', 0) # 页序号 86 | params = {'query': query, 'output': 'json', 'scope': scope, 'page_size': page_size, 'page_num': page_num, 87 | 'ak': self.scheduler.next()} 88 | if scope == 2: 89 | filter = 'industry_type:{industry_type}|sort_name:{sort_name}|sort_rule:{sort_rule}|groupon:{groupon}|' \ 90 | 'discount:{discount}'.format(industry_type=industry_type, sort_name=sort_name, 91 | sort_rule=sort_rule, groupon=groupon, discount=discount) 92 | params['filter'] = filter 93 | 94 | if tag: 95 | params['tag'] = tag 96 | 97 | params['region'] = region 98 | r = requests.get(self.search_url, params=params) 99 | try: 100 | r.raise_for_status() 101 | data = json.loads(r.text) 102 | # print json.dumps(data, ensure_ascii=False) 103 | if data['status'] == 0: 104 | # 在状态为0时, 也有可能没有找到搜索结果, 而是返回在其它城市查找到的结果, 返回格式为[{'num': , 'name': ''} ...]这样的数组 105 | if len(data['results']) > 0: 106 | if 'location' in data['results'][0]: 107 | return data 108 | logging.debug(data['results']) 109 | return None 110 | return data 111 | else: 112 | logging.error('failed to get place, return result is %s' % r.text) 113 | return None 114 | except Exception as e: 115 | logging.exception(e) 116 | return None 117 | 118 | def get_place_all(self, query, region, **kwargs): 119 | u""" 120 | 根据关键词query查找所有地址信息 121 | 122 | *注意* 百度最多返回400条记录 123 | :param query: 查询关键词 124 | :param region: 地区 125 | :param kwargs: 126 | :return: if success return 127 | [ 128 | { 129 | name: POI名称, 130 | location: { 131 | lat: 纬度, 132 | lng: 经度 133 | }, 134 | address: POI地址信息, 135 | telephone: POI电话信息, 136 | uid: POI的唯一标识, 137 | detail_info: { # POI扩展信息, 仅当scope=2时, 显示该字段, 不同POI类型, 显示的detail_info字段不同 138 | distance: 距离中心点距离, 139 | type: POI类型, 140 | tag: 标签, 141 | detail_url: POI的详情页, 142 | price: POI商户的价格, 143 | shop_hours: 营业时间, 144 | overall_rating: 总体评分, 145 | taste_rating: 口味评分, 146 | service_rating: 服务评分, 147 | environment_rating: 环境评分, 148 | facility_rating: 星级评分, 149 | hygiene_rating: 卫生评分, 150 | technology_rating: 技术评分, 151 | image_num: 图片数, 152 | groupon_num: 团购数, 153 | discount_num: 优惠数, 154 | comment_num: 评论数, 155 | favorite_num: 收藏数, 156 | checkin_num: 签到数 157 | } 158 | } 159 | ... 160 | ] 161 | else return [] 162 | """ 163 | data = [] 164 | kwargs.update({'page_num': 0}) 165 | r = self.get_place_by_page(query, region, **kwargs) 166 | if r is None: 167 | return data 168 | data.extend(r['results']) 169 | total = r['total'] 170 | page_size = kwargs.get('page_size', 20) 171 | # print "total: %d, page_size: %d" % (total, page_size) 172 | for i in range(1, total // page_size + 1): 173 | kwargs.update({'page_num': i}) 174 | r = self.get_place_by_page(query, region, **kwargs) 175 | if r is None: 176 | break 177 | if r['total'] == 0: 178 | break 179 | data.extend(r['results']) 180 | return data 181 | 182 | def get_place_by_uids(self, uids, **kwargs): 183 | u""" 184 | Place详情检索服务 185 | 186 | uids最多支持10个 187 | :param uids: string or list 188 | :param kwargs: available keys include 'output', 'scope' 189 | :return: same with get_place_all. 190 | """ 191 | params = {} 192 | if isinstance(uids, list): 193 | params['uids'] = ','.join(uids) 194 | else: 195 | params['uid'] = uids 196 | params['output'] = kwargs.get('output', 'json') # json or xml 请求返回格式 197 | params['scope'] = kwargs.get('scope', 1) # 1 返回基本信息, 2 返回POI详细信息 198 | params['ak'] = self.scheduler.next() 199 | try: 200 | r = requests.get(self.detail_url, params=params) 201 | r.raise_for_status() 202 | 203 | data = json.loads(r.text) 204 | if data['status'] == 0: 205 | return data['result'] 206 | 207 | logging.error('failed to get place, return result is %s' % r.text) 208 | return [] 209 | except Exception as e: 210 | logging.exception(e) 211 | return [] 212 | -------------------------------------------------------------------------------- /mapapi/baidu/scheduler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from mapapi.baidu.exceptions import ParamError 4 | 5 | 6 | class BaseScheduler(object): 7 | """ 8 | use as scheduler base class. 9 | """ 10 | def __init__(self, aks): 11 | super(BaseScheduler, self).__init__() 12 | self.aks = aks 13 | 14 | def next(self): 15 | raise NotImplementedError 16 | 17 | 18 | class RoundRobinScheduler(BaseScheduler): 19 | """ 20 | implement round-robin poll scheduler algorithm 21 | """ 22 | def __init__(self, aks): 23 | super(RoundRobinScheduler, self).__init__(aks) 24 | self.cur_index = 0 25 | 26 | def next(self): 27 | if len(self.aks) < 1: 28 | raise ParamError(u'aks不能为空') 29 | ak = self.aks[self.cur_index] 30 | self.cur_index += 1 31 | if self.cur_index == len(self.aks): 32 | self.cur_index = 0 33 | return ak 34 | -------------------------------------------------------------------------------- /mapapi/baidu/transform_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | import requests 5 | import logging 6 | import json 7 | 8 | 9 | class TransformApi(object): 10 | """ 11 | transform other format coordinates to baidu coordinates. 12 | """ 13 | transform_url = 'http://api.map.baidu.com/geoconv/v1/' 14 | 15 | def __init__(self, scheduler): 16 | self.scheduler = scheduler 17 | 18 | def _stringify_coords(self, coords): 19 | if isinstance(coords, dict): 20 | return '%s,%s' % (coords['lng'], coords['lat']) 21 | 22 | coords_str = '' 23 | for coord in coords: 24 | coords_str += '%s,%s;' % (coord['lng'], coord['lat']) 25 | coords_str = coords_str[:-1] 26 | return coords_str 27 | 28 | def _format_result(self, result): 29 | ret = [] 30 | for item in result: 31 | ret.append({'lng': item['x'], 'lat': item['y']}) 32 | return ret 33 | 34 | def transform(self, coords, **kwargs): 35 | """ 36 | 完成坐标转换 37 | 38 | :param coords: {'lat':, 'lng':, }或者list of {'lat':, 'lng':,} 39 | :param kwargs: available parameters include 'from', 'to', 'output' 40 | :return: if success return 41 | [ 42 | { 'lat':, 'lng':, } 43 | ... 44 | ] 45 | else return None. 46 | """ 47 | from_type = kwargs.get('from', 3) 48 | to_type = kwargs.get('to', 5) 49 | output = kwargs.get('output', 'json') 50 | params = {'from': from_type, 'to': to_type, 'ak': self.scheduler.next(), 51 | 'output': output, 'coords': self._stringify_coords(coords)} 52 | try: 53 | r = requests.get(self.transform_url, params=params) 54 | r.raise_for_status() 55 | 56 | data = json.loads(r.text) 57 | if data['status'] == 0: 58 | return self._format_result(data['result']) 59 | logging.debug(u'failed to transform: %s' % r.text) 60 | return None 61 | except: 62 | logging.exception(u'获取坐标转化失败: %s' % json.dumps(params)) 63 | return None 64 | -------------------------------------------------------------------------------- /mapapi/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yimian/mapapi/248bdeb253f7a52d08f5cbb4327233a8dc5df885/mapapi/example/__init__.py -------------------------------------------------------------------------------- /mapapi/example/clinic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import codecs 3 | from mapapi import baidu 4 | import json 5 | 6 | with codecs.open('data/clinic.txt', 'r', encoding='utf-8') as f: 7 | map_api = baidu.MapApi() 8 | matched = [] 9 | unmatched = [] 10 | for line in f: 11 | clinic_name = line.strip() 12 | loc = map_api.location_api.get_location_by_address(u'深圳 %s' % clinic_name) 13 | if loc: 14 | matched.append({'name': clinic_name, 'loc': loc}) 15 | else: 16 | unmatched.append(clinic_name) 17 | 18 | print('nmatched: %d, unmatched: %d' % (len(matched), len(unmatched))) 19 | 20 | with codecs.open('data/matched_clinic.txt', 'w', encoding='utf-8') as f: 21 | json.dump(matched, f) 22 | -------------------------------------------------------------------------------- /mapapi/example/shiwan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 抓取广东省主要城市大中型超市, 便利店, 粤菜分布 4 | """ 5 | 6 | from xpinyin import Pinyin 7 | from mapapi import baidu 8 | import json 9 | import logging 10 | import re 11 | 12 | malls = [u'家乐福', u'沃尔玛', u'好又多'] 13 | cities = [u'广州', u'深圳', u'佛山', u'东莞', u'惠州', u'中山', u'湛江', u'茂名', u'江门'] 14 | 15 | piny = Pinyin() 16 | map_api = baidu.MapApi() 17 | 18 | 19 | def get_file_name_by_city(city): 20 | return 'data/%s.json' % (piny.get_pinyin(city, '')) 21 | 22 | 23 | def get_malls_by_city(malls, city, filter=None): 24 | """ 25 | 26 | :param malls: 27 | :param city: 28 | :param filter: 过滤函数func(item, mall, city), 返回值为False的item将会被过滤掉 29 | """ 30 | file_name = get_file_name_by_city(city) 31 | with open(file_name, 'w') as f: 32 | locations = [] 33 | for m in malls: 34 | data = map_api.place_api.get_place_all(m, city, scope=2, industry_type='life', groupon=0, discount=0) 35 | nfilter = 0 36 | for item in data: 37 | try: 38 | if filter and not filter(item, m, city): 39 | nfilter += 1 40 | continue 41 | locations.append({'lat': item['location']['lat'], 'lng': item['location']['lng'], 'name': item['name'], 'keyword': m}) 42 | except: 43 | logging.exception(u'item格式错误: %s' % json.dumps(item)) 44 | print(item) 45 | if filter: 46 | print(city, m, len(data) - nfilter, 'filtered: %d' % nfilter) 47 | else: 48 | print(city, m, len(data)) 49 | json.dump(locations, f) 50 | 51 | 52 | def get_malls_all(filter=None): 53 | for city in cities: 54 | get_malls_by_city(malls, city, filter) 55 | 56 | 57 | def filter_by_name(item, mall, city): 58 | pattern = u'\(%s.*店\)' % mall 59 | match = re.search(pattern, item['name']) 60 | if match: 61 | return False 62 | pattern = u'%s.*' % mall 63 | match = re.match(pattern, item['name']) 64 | if match: 65 | return True 66 | return False 67 | 68 | 69 | def run_func(func_name, env, *args): 70 | if func_name in env: 71 | func = env.get(func_name) 72 | if hasattr(func, '__call__'): 73 | func(*args) 74 | else: 75 | print('%s is not a function name' % func_name) 76 | else: 77 | print('%s not found' % func_name) 78 | 79 | 80 | if __name__ == '__main__': 81 | import sys 82 | 83 | if len(sys.argv) < 2: 84 | print("error, not less than one parameter") 85 | exit(-1) 86 | run_func(sys.argv[1], globals(), *sys.argv[2:]) 87 | -------------------------------------------------------------------------------- /mapapi/example/uid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import json 4 | import logging 5 | from mapapi import baidu 6 | import codecs 7 | 8 | 9 | map_api = baidu.MapApi() 10 | 11 | 12 | def get_locs(data): 13 | for item in data: 14 | loc = map_api.transform_api.get_place_by_uids(item['uid']) 15 | if loc: 16 | item['lat'] = loc['location']['lat'] 17 | item['lng'] = loc['location']['lng'] 18 | item['name'] = loc['name'] 19 | item['address'] = loc['address'] 20 | else: 21 | logging.error(u'获取地理坐标失败: %s' % item) 22 | 23 | 24 | def get_locs_from_file(infile): 25 | import os 26 | dir_name, ext_name = os.path.splitext(infile) 27 | outfile = '%s_loc%s' % (dir_name, ext_name) 28 | with codecs.open(infile, 'r', encoding='utf-8') as f: 29 | data = json.load(f) 30 | get_locs(data) 31 | with codecs.open(outfile, 'w', encoding='utf-8') as f: 32 | json.dump(data, f) 33 | 34 | 35 | def run_func(func_name, env, *args): 36 | if func_name in env: 37 | func = env.get(func_name) 38 | if hasattr(func, '__call__'): 39 | func(*args) 40 | else: 41 | print('%s is not a function name' % func_name) 42 | else: 43 | print('%s not found' % func_name) 44 | 45 | 46 | if __name__ == '__main__': 47 | import sys 48 | 49 | if len(sys.argv) < 2: 50 | print("error, not less than one parameter") 51 | exit(-1) 52 | run_func(sys.argv[1], globals(), *sys.argv[2:]) 53 | -------------------------------------------------------------------------------- /mapapi/tests/test_location_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | import unittest 5 | from baidu.location_api import LocationApi 6 | from baidu.scheduler import RoundRobinScheduler 7 | 8 | 9 | class TestLocationApi(unittest.TestCase): 10 | def setUp(self): 11 | self.scheduler = RoundRobinScheduler(['wwx6xhe8aQncZZUm7QsIPXKI', '9ea66EnDo1YLFuzu5QDDp4zU']) 12 | self.location_api = LocationApi(self.scheduler) 13 | 14 | def test_get_detail_location_by_address(self): 15 | detail = self.location_api.get_detail_location_by_address(u'百度大厦', u'北京') 16 | self.assertIsNotNone(detail) 17 | self.assertEqual(detail['precise'], 1) 18 | self.assertEqual(detail['confidence'], 80) 19 | self.assertEqual(detail['level'], u'商务大厦') 20 | self.assertEqual(detail['location']['lng'], 116.30783584945) 21 | self.assertEqual(detail['location']['lat'], 40.056876296398) 22 | 23 | def test_get_location_by_address(self): 24 | location = self.location_api.get_location_by_address(u'百度大厦', u'北京') 25 | self.assertIsNotNone(location) 26 | self.assertEqual(location['lat'], 40.056876296398) 27 | self.assertEqual(location['lng'], 116.30783584945) 28 | 29 | def test_get_detail_address_by_location(self): 30 | detail = self.location_api.get_detail_address_by_location({'lng': 116.322987, 'lat': 39.983424}) 31 | self.assertIsNotNone(detail) 32 | self.assertEqual(detail['formatted_address'], u'北京市海淀区中关村大街27号1101-08室') 33 | self.assertEqual(detail['business'], u"中关村,人民大学,苏州街") 34 | self.assertEqual(detail['addressComponent']['province'], u'北京市') 35 | self.assertEqual(detail['addressComponent']['city'], u'北京市') 36 | 37 | def test_get_address_by_location(self): 38 | address = self.location_api.get_address_by_location({'lng': 116.322987, 'lat': 39.983424}) 39 | self.assertIsNotNone(address) 40 | self.assertEqual(address['province'], u'北京') 41 | self.assertEqual(address['city'], u'北京') 42 | 43 | def test_get_province_by_location(self): 44 | province = self.location_api.get_province_by_location({'lng': 116.322987, 'lat': 39.983424}) 45 | self.assertIsNotNone(province) 46 | self.assertEqual(province, u'北京') 47 | 48 | def test_get_formatted_province(self): 49 | province = self.location_api.get_formatted_province(u'北京市海淀区百度大厦') 50 | self.assertIsNotNone(province) 51 | self.assertEqual(province, u'北京') 52 | 53 | def test_get_formatted_address(self): 54 | address = self.location_api.get_formatted_address(u'北京市海淀区百度大厦') 55 | self.assertIsNotNone(address) 56 | self.assertEqual(address['province'], u'北京') 57 | self.assertEqual(address['city'], u'北京') 58 | 59 | def test_get_formatted_detail_address(self): 60 | detail = self.location_api.get_formatted_detail_address(u'北京市海淀区百度大厦') 61 | self.assertIsNotNone(detail) 62 | self.assertEqual(detail['addressComponent']['province'], u'北京市') 63 | self.assertEqual(detail['addressComponent']['city'], u'北京市') 64 | 65 | 66 | if __name__ == '__main__': 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /mapapi/tests/test_main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | import unittest 5 | from test_location_api import TestLocationApi 6 | from test_place_api import TestPlaceApi 7 | from test_scheduler import TestRoundRobinScheduler 8 | from test_transform_api import TestTransformApi 9 | from test_map_api import TestMapApi 10 | 11 | if __name__ == '__main__': 12 | unittest.main() 13 | 14 | -------------------------------------------------------------------------------- /mapapi/tests/test_map_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | import unittest 5 | from baidu import MapApi 6 | 7 | 8 | class TestMapApi(unittest.TestCase): 9 | def setUp(self): 10 | self.map_api = MapApi(['wwx6xhe8aQncZZUm7QsIPXKI', '9ea66EnDo1YLFuzu5QDDp4zU']) 11 | 12 | def test_location_api(self): 13 | detail = self.map_api.location_api.get_detail_location_by_address(u'百度大厦', u'北京') 14 | self.assertIsNotNone(detail) 15 | self.assertEqual(detail['precise'], 1) 16 | self.assertEqual(detail['confidence'], 80) 17 | self.assertEqual(detail['level'], u'商务大厦') 18 | self.assertEqual(detail['location']['lng'], 116.30783584945) 19 | self.assertEqual(detail['location']['lat'], 40.056876296398) 20 | 21 | def test_place_api(self): 22 | ret = self.map_api.place_api.get_place_by_page(u'银行', u'济南') 23 | self.assertIsNotNone(ret) 24 | self.assertEqual(len(ret['results']), 20) 25 | 26 | def test_transform_api(self): 27 | coords = self.map_api.transform_api.transform({'lat': 29.5754297789, 'lng': 114.218927345}) 28 | self.assertIsNotNone(coords) 29 | self.assertEqual(len(coords), 1) 30 | self.assertEqual(coords[0]['lat'], 29.58158536743) 31 | self.assertEqual(coords[0]['lng'], 114.22539195408) 32 | 33 | if __name__ == '__main__': 34 | unittest.main() 35 | -------------------------------------------------------------------------------- /mapapi/tests/test_place_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | import unittest 5 | from baidu.scheduler import RoundRobinScheduler 6 | from baidu.place_api import PlaceApi 7 | import logging 8 | 9 | 10 | class TestPlaceApi(unittest.TestCase): 11 | def setUp(self): 12 | self.scheduler = RoundRobinScheduler(['wwx6xhe8aQncZZUm7QsIPXKI', '9ea66EnDo1YLFuzu5QDDp4zU']) 13 | self.place_api = PlaceApi(self.scheduler) 14 | 15 | def test_get_place_by_page(self): 16 | ret = self.place_api.get_place_by_page(u'银行', u'济南') 17 | self.assertIsNotNone(ret) 18 | self.assertEqual(len(ret['results']), 20) 19 | 20 | def test_get_place_all(self): 21 | ret = self.place_api.get_place_by_page(u'银行', u'济南') 22 | self.assertIsNotNone(ret) 23 | total = ret['total'] 24 | logging.debug('total: %d' % total) # 不起作用 25 | ret = self.place_api.get_place_all(u'银行', u'济南') 26 | self.assertIsNotNone(ret) 27 | self.assertEqual(len(ret), total) 28 | 29 | def test_get_place_by_uids_with_string(self): 30 | place = self.place_api.get_place_by_uids('c14fc238f7fadd4ea5da390f') 31 | self.assertIsNotNone(place) 32 | self.assertEqual(place['name'], u'觉品壹号') 33 | places = self.place_api.get_place_by_uids(['c14fc238f7fadd4ea5da390f', '5a8fb739999a70a54207c130']) 34 | self.assertIsNotNone(places) 35 | self.assertEqual(len(places), 2) 36 | self.assertEqual(places[1]['name'], u'百度大厦员工食堂') 37 | 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /mapapi/tests/test_scheduler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | import unittest 5 | from baidu.scheduler import RoundRobinScheduler 6 | from baidu.exceptions import ParamError 7 | 8 | 9 | class TestRoundRobinScheduler(unittest.TestCase): 10 | def test_next_with_correct_aks(self): 11 | scheduler = RoundRobinScheduler(['wwx6xhe8aQncZZUm7QsIPXKI', '9ea66EnDo1YLFuzu5QDDp4zU']) 12 | self.assertEqual(scheduler.next(), 'wwx6xhe8aQncZZUm7QsIPXKI') 13 | self.assertEqual(scheduler.next(), '9ea66EnDo1YLFuzu5QDDp4zU') 14 | self.assertEqual(scheduler.next(), 'wwx6xhe8aQncZZUm7QsIPXKI') 15 | 16 | def test_next_with_empty_aks(self): 17 | scheduler = RoundRobinScheduler([]) 18 | with self.assertRaises(ParamError): 19 | scheduler.next() 20 | 21 | 22 | if __name__ == '__main__': 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /mapapi/tests/test_transform_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'zhangjinjie' 3 | 4 | import unittest 5 | from baidu.scheduler import RoundRobinScheduler 6 | from baidu.transform_api import TransformApi 7 | 8 | 9 | class TestTransformApi(unittest.TestCase): 10 | def setUp(self): 11 | self.scheduler = RoundRobinScheduler(['wwx6xhe8aQncZZUm7QsIPXKI', '9ea66EnDo1YLFuzu5QDDp4zU']) 12 | self.transform_api = TransformApi(self.scheduler) 13 | 14 | def test_transform_with_single_param(self): 15 | # python 浮点数精度有限 16 | coords = self.transform_api.transform({'lat': 29.5754297789, 'lng': 114.218927345}) 17 | self.assertIsNotNone(coords) 18 | self.assertEqual(len(coords), 1) 19 | self.assertEqual(coords[0]['lat'], 29.58158536743) 20 | self.assertEqual(coords[0]['lng'], 114.22539195408) 21 | 22 | def test_transform_with_list(self): 23 | coords = self.transform_api.transform([{'lat': 29.5754297789, 'lng': 114.218927345}, 24 | {'lat': 29.5754297789, 'lng': 114.218927345}]) 25 | self.assertIsNotNone(coords) 26 | self.assertEqual(len(coords), 2) 27 | self.assertEqual(coords[1]['lat'], 29.58158536743) 28 | self.assertEqual(coords[1]['lng'], 114.22539195408) 29 | 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import codecs 3 | 4 | def readme(): 5 | with codecs.open('README.rst', 'r', encoding='utf-8') as f: 6 | return f.read() 7 | 8 | setup(name='mapapi', 9 | version='0.2.5', 10 | description='map web api, current support baidu', 11 | long_description=readme(), 12 | keywords='map baidu', 13 | url='https://github.com/starplanet/mapapi', 14 | author='starplanet', 15 | author_email='newvision1987@gmail.com', 16 | license='MIT', 17 | packages=['mapapi', 'mapapi/baidu'], 18 | install_requires=[ 19 | 'requests', 20 | ], 21 | include_package_data=True, 22 | zip_safe=False) 23 | --------------------------------------------------------------------------------