描述:
下载指定两个坐标点间或某区域的全景图片,由于是访问Google,所以网络方面需要
本文档仅供学习分享,提供思路
目的:
本地实现静态仿Google街景效果
参考资料:
Google文档:https://developers.google.com/maps/documentation/streetview/request-streetview?hl=en
全景图下载:https://github.com/Gage-Irwin/Street-View-Downloader
Google服务Python库:https://github.com/googlemaps/google-maps-services-python
目录
一、前提准备
二、获取Pano_id
2.1 获取两个坐标点间Pano_id
2.2 获取指定区域的Pano_id
三、下载全景图
Pano_id为Google街景全景图唯一ID
获取两点间距
def geo_distance(lat1, lon1, lat2, lon2):
"""
计算两点间距离(m)
"""
return geodesic((lat1, lon1), (lat2, lon2)).m
计算方位角
def geo_degree(lat1, lon1, lat2, lon2):
"""
公式计算两点间方位角
方位角:是与正北方向、顺时针之间的夹角
"""
lat1_rad = lat1 * math.pi / 180
lon1_rad = lon1 * math.pi / 180
lat2_rad = lat2 * math.pi / 180
lon2_rad = lon2 * math.pi / 180
y = math.sin(lon2_rad - lon1_rad) * math.cos(lat2_rad)
x = math.cos(lat1_rad) * math.sin(lat2_rad) - \
math.sin(lat1_rad) * math.cos(lat2_rad) * math.cos(lon2_rad - lon1_rad)
brng = math.atan2(y, x) * 180 / math.pi
return (brng + 360.0) % 360.0
按步长拆分间距,根据方位角预判点位置
def get_fill_points(location1, location2, distance, step, c=True):
lng_diff = location2[1] - location1[1]
lat_diff = location2[0] - location1[0]
n = math.ceil(distance / step)
points = list()
if n != 0:
a = lng_diff / n
b = lat_diff / n
for i in range(n):
lng = location1[1] + a * i
lat = location1[0] + b * i
points.append((round(lat, 7), round(lng, 7)))
if c:
points.insert(0, location1)
points.append(location2)
return points
根据经纬度、方位角、距离计算目标经纬度
def get_location(lat, lon, distance, degree):
"""
根据经纬度、方位角、距离计算目标经纬度
"""
RE = 6371.393 * 1000 # 赤道半径,单位:m
re = RE * math.cos(lat / 180 * math.pi) # 纬度切面的半径
lon2 = lon + distance * math.sin(degree / 180 * math.pi) / (re * 2 * math.pi) * 360 # 求切面长度占比,与已知点经度相加
lat2 = lat + distance * math.cos(degree / 180 * math.pi) / (re * 2 * math.pi) * 360
return lat2, lon2
完成代码为
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import sys
import csv
import math
import time
import requests
import geo_distance, geo_degree, get_location, get_fill_points
def join_url(url: str, filed_dict: dict):
"""
拼接URL参数
"""
filed_list = []
for k, v in filed_dict.items():
filed_list.append(str(k) + "=" + str(v))
return url + "?" + "&".join(filed_list)
def generate_location(location1: tuple, location2: tuple, step):
"""
生成位置信息
"""
m = math.ceil(geo_distance(*location1, *location2))
degree = geo_degree(*location1, *location2)
for i in range(0, m + 1, step):
lat, lon = get_location(*location1, i, degree)
yield round(lat, 7), round(lon, 7)
def write_csv(urls, write_path, file_name, pano_ids, data_list):
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36'
}
for i in range(len(urls)):
if urls:
url = urls.pop()
else:
continue
try:
response = requests.get(url, headers=headers)
except Exception as e:
print(e)
urls.append(url)
write_csv(urls, write_path, file_name, pano_ids, data_list)
else:
if response.status_code != 200:
print(f"{url}: 请求失败")
else:
data = response.json()
if data["status"] == "OK":
print(url)
# print(data)
pano_id = data.get("pano_id")
if pano_id not in pano_ids:
data_list.insert(1, [
url.split("location=")[1].split(",")[0],
url.split("location=")[1].split(",")[1],
pano_id
])
pano_ids.append(pano_id)
else:
print(f"该位置{url}无图片")
data_list.append([
url.split("location=")[1].split(",")[0],
url.split("location=")[1].split(",")[1],
""
])
time.sleep(0.03)
file_name = file_name + ".csv"
if not os.path.exists(write_path):
os.makedirs(write_path)
path = os.path.join(write_path, file_name)
if not os.path.exists(path):
print(f"write to {path}")
print(f"获取有效图片{len(data_list)}")
for index, data in enumerate(data_list):
if index == 0:
continue
data.insert(0, str(index))
col = csv.writer(open(path, 'w', newline=""))
col.writerows(data_list)
def start(road_data: list, write_path: str, step=5):
s_url = "https://maps.googleapis.com/maps/api/streetview/metadata"
_dict = {
"key": "your Google key",
"location": ""
}
url_list = []
for road_locations, road_name in road_data:
pano_ids = []
data_list = [["id", "lat", "lng", "pano_id"]]
_roads = road_locations.split("|")
for index, road in enumerate(_roads):
try:
location1 = (float(road.split("_")[1]), float(road.split("_")[0]))
location2 = (float(_roads[index + 1].split("_")[1]), float(_roads[index + 1].split("_")[0]))
except IndexError as e:
print(e)
_dict["location"] = str(road.split("_")[1]) + "," + str(road.split("_")[0])
url_list.append(join_url(s_url, _dict))
continue
m = math.ceil(geo_distance(*location1, *location2))
# 较长直线距离,根据方位角计算
print(f"两点{location1} > {location2}距离{m}, 开始计算直线坐标点,步长为:{step}")
points = get_fill_points(location1, location2, m, step)
for point in points:
_dict["location"] = str(point[0]) + "," + str(point[1])
url_list.append(join_url(s_url, _dict))
print(url_list)
print(f"该道路:{road_name}有{len(url_list)}个坐标点")
write_csv(url_list, write_path, road_name, pano_ids, data_list)
if __name__ == '__main__':
_path = "./"
roads = ["121.0125937_24.8587082|121.012562_24.8586107", "五一路"]
start(roads, _path)
自定义修改main方法后,执行代码,生成指定道路的pano_id csv文件
id,lat,lng,pano_id
1,24.8660544,120.9971263,e_mIi84_ZEfqSaJ-I2cAtA
2,24.8659941,120.9970576,byKKp4vuDwVhQ6fTE2h_Kw
3,24.8659337,120.9969889,JbPNGE6ZRMrPwgTt-86fOg
4,24.8658733,120.9969201,yjzAWCjp2x-5-TfPIF2AmA
5,24.865813,120.9968514,8LRF3xd_GHrasdAi7IkwWw
使用street view download 360(需付费)工具下载Pano_id后,执行下载脚本。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
import time
import requests
from PIL import Image
from io import BytesIO
# helper functions in increase image down and right
def increase_right(img, pixels):
width, height = img.size
new_width = width + pixels
result = Image.new(img.mode, (new_width, height), (250, 250, 250))
result.paste(img, (0, 0))
return result
def increase_down(img, pixels):
width, height = img.size
new_height = height + pixels
result = Image.new(img.mode, (width, new_height), (250, 250, 250))
result.paste(img, (0, 0))
return result
class GMAP360:
def __init__(
self,
sv_ids: list = None,
download_path: str = None,
zoom: int = 4,
retry: int = 5,
overwrite: bool = False,
):
self.street_info = sv_ids
if not os.path.exists(download_path):
os.makedirs(download_path)
self.download_path = download_path
self.retry = retry
# only zoom levels from 1-5 are allowed
if 5 < zoom < 0:
raise Exception(f"Incorrect zoom size: {zoom}, only sizes 1-5 are allowed.")
self.zoom = zoom
self.overwrite = overwrite
self.start()
def download_street_view(self, location_id: str, lat: str, lng: str, id: str):
# Street view 360 images are loaded in chunks.
# The chunks are layed out in a 2D plane with an X and Y cordinate.
# The number of chunks is based on the resolution of the full image.
# You can not get the resolution of the the full image so we loop over X and Y till we reach a 400 status code.
# to try to prevent throttling by using a browser user agent
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.82 Safari/537.36'}
# start from 0,0
X = Y = 0
# variable to know when the column ends so we don't have to check when each column ends.
end_column = 0
# the zoom level determines the size of each chunk
# zoom level 1-4 are 512 and 5 is 256
block_size = 512 if self.zoom <= 4 else 256
# loop over X positions of chunks
while True:
# loop over Y positions of chunks
while True:
# if the current Y position is at the end don't waste a GET requests
if Y == end_column and Y != 0:
break
# build url for chunk
response = requests.get(
f"https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=maps_sv.tactile&panoid={location_id}&x={X}&y={Y}&zoom={self.zoom}&nbt=1&fover=2",
headers=headers)
if response.status_code == 400:
end_column = Y
break
# load image from responce
image = Image.open(BytesIO(response.content))
# if first chunk create new image
if X == 0 and Y == 0:
# create empty panorama image of size block_size by block_size
panorama = Image.new('RGB', (block_size, block_size), (250, 250, 250))
elif end_column == 0 and Y != 0:
#
panorama = increase_down(panorama, block_size)
# paste loaded image into panorama image
panorama.paste(image, (X * block_size, Y * block_size))
# move to next Y position
Y += 1
time.sleep(0.01)
# reset Y position
Y = 0
# move to next X position
X += 1
response = requests.get(
f"https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=maps_sv.tactile&panoid={location_id}&x={X}&y={Y}&zoom={self.zoom}&nbt=1&fover=2",
headers=headers)
# if no more X positions then we are done
if response.status_code == 400:
break
# load image from responce
image = Image.open(BytesIO(response.content))
# increase panorama image right by block_size amount
panorama = increase_right(panorama, block_size)
# paste loaded image into panorama image
panorama.paste(image, (X * block_size, Y * block_size))
# save panorama image
panorama.save(os.path.join(self.download_path, f"{id}_{lat}_{lng}.jpg"), format="JPEG")
def download(self, location_id: str, retry: int, lat: str, lng: str, id: str):
# wrapper function for self.download_street_view() to allow retrying
try:
self.download_street_view(location_id, lat, lng, id)
except Exception as e:
print('Download Failed!')
print(e)
if retry > 0:
print('Retrying...')
self.download(location_id, retry - 1, lat, lng, id)
def start(self):
for index, street in enumerate(self.street_info):
id, lat, lng, location_id = street
if not self.overwrite and os.path.exists(os.path.join(self.download_path, f"{id}_{lat}_{lng}.jpg")):
print(f"Skipping download | Image {id}_{lat}_{lng}.jpg already exists")
continue
print(f"Downloading image {id}_{lat}_{lng}.jpg {index}/{len(self.street_info)}")
if location_id:
self.download(location_id, self.retry, lat, lng, id)
def generate_ids(file_path):
with open(file_path, "r") as f:
f.readline()
return [data.replace("\n", "").split(",") for data in f.readlines()]
if __name__ == '__main__':
import sys
try:
s_path = sys.argv[1]
d_path = sys.argv[2]
except:
s_path = "./"
d_path = "./"
if not os.path.exists(s_path):
print(f"查无此文件或目录:{s_path}")
# 下载颗粒度,0-5,颗粒度越大,请求次数越多
zoom = 3
# 重重次数
retry = 3
# 覆盖以前创建的任何文件。
overwrite = False
if os.path.isdir(s_path):
for file_name in os.listdir(s_path):
if file_name.endswith(".csv"):
csv_path = os.path.join(s_path, file_name)
# 图片ID
street_info = generate_ids(csv_path)
# 存储路径
f_name = os.path.splitext(file_name)[0]
output_path = os.path.join(
d_path,
str(zoom),
f_name
)
print(output_path)
GMAP360(
sv_ids=street_info,
download_path=output_path,
zoom=zoom, retry=retry,
overwrite=overwrite,
)
else:
csv_path = s_path
# 图片ID
street_info = generate_ids(csv_path)
# 存储路径
f_name = os.path.splitext(os.path.basename(s_path))[0]
output_path = os.path.join(
d_path,
str(zoom),
f_name
)
print(output_path)
GMAP360(
sv_ids=street_info,
download_path=output_path,
zoom=zoom, retry=retry,
overwrite=overwrite,
)
使用说明:
支持参数 |
类型 |
说明 |
zoom |
int |
下载颗粒度:0-5 颗粒度越大,请求次数越多,图片越清晰 |
retry |
int |
请求重试次数 |
overwrite |
bool |
是否覆盖已有文件 |
python street_download [panoid_path] [images_path]
# panoid_path:panoid文件位置
# images_path:保存图片路径