我想在我的python代码中包含开放街道地图(OSM)。
我已经阅读了很多关于OSM的网页。但不幸的是,我有点迷失,关于我最好用的包。
我正在寻找一种在我的应用中获取OSM图像的简便方法。作为我的起点,我想的是:
import matplotlib.pyplot as plt
# Pseudo - Code for required function 'GetOSMImage'
Map = GetOSMImage(lat,long,delta_lat,delta_long)
imgplot = plt.imshow(Map)
稍后我想在此plt中添加我的附加数据。 (我知道我需要处理预测等。)
我不需要/想要的东西:
你有一个很好的起点吗? 或者我是否低估了这个主题的复杂性?
答案 0 :(得分:11)
根据您的输入,我能够实现目标。这是我的其他代码,它们正在寻找OSM的起点。 (当然还有很大的改进空间)。
import matplotlib.pyplot as plt
import numpy as np
import math
import urllib2
import StringIO
from PIL import Image
def deg2num(lat_deg, lon_deg, zoom):
lat_rad = math.radians(lat_deg)
n = 2.0 ** zoom
xtile = int((lon_deg + 180.0) / 360.0 * n)
ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
return (xtile, ytile)
def num2deg(xtile, ytile, zoom):
n = 2.0 ** zoom
lon_deg = xtile / n * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
lat_deg = math.degrees(lat_rad)
return (lat_deg, lon_deg)
def getImageCluster(lat_deg, lon_deg, delta_lat, delta_long, zoom):
smurl = r"http://a.tile.openstreetmap.org/{0}/{1}/{2}.png"
xmin, ymax =deg2num(lat_deg, lon_deg, zoom)
xmax, ymin =deg2num(lat_deg + delta_lat, lon_deg + delta_long, zoom)
Cluster = Image.new('RGB',((xmax-xmin+1)*256-1,(ymax-ymin+1)*256-1) )
for xtile in range(xmin, xmax+1):
for ytile in range(ymin, ymax+1):
try:
imgurl=smurl.format(zoom, xtile, ytile)
print("Opening: " + imgurl)
imgstr = urllib2.urlopen(imgurl).read()
tile = Image.open(StringIO.StringIO(imgstr))
Cluster.paste(tile, box=((xtile-xmin)*256 , (ytile-ymin)*255))
except:
print("Couldn't download image")
tile = None
return Cluster
if __name__ == '__main__':
a = getImageCluster(38.5, -77.04, 0.02, 0.05, 13)
fig = plt.figure()
fig.patch.set_facecolor('white')
plt.imshow(np.asarray(a))
plt.show()
答案 1 :(得分:7)
建立BerndGit的好答案,我添加了一个稍微修改过的版本,允许显示其他内容和图块(使用Basemap)。顺便说一下,我遇到了一个专用的库,geotiler(http://wrobell.it-zone.org/geotiler/intro.html),但它需要Python 3。
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
import math
import urllib2
import StringIO
from PIL import Image
def deg2num(lat_deg, lon_deg, zoom):
lat_rad = math.radians(lat_deg)
n = 2.0 ** zoom
xtile = int((lon_deg + 180.0) / 360.0 * n)
ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
return (xtile, ytile)
def num2deg(xtile, ytile, zoom):
"""
http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
This returns the NW-corner of the square.
Use the function with xtile+1 and/or ytile+1 to get the other corners.
With xtile+0.5 & ytile+0.5 it will return the center of the tile.
"""
n = 2.0 ** zoom
lon_deg = xtile / n * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
lat_deg = math.degrees(lat_rad)
return (lat_deg, lon_deg)
def getImageCluster(lat_deg, lon_deg, delta_lat, delta_long, zoom):
smurl = r"http://a.tile.openstreetmap.org/{0}/{1}/{2}.png"
xmin, ymax = deg2num(lat_deg, lon_deg, zoom)
xmax, ymin = deg2num(lat_deg + delta_lat, lon_deg + delta_long, zoom)
bbox_ul = num2deg(xmin, ymin, zoom)
bbox_ll = num2deg(xmin, ymax + 1, zoom)
#print bbox_ul, bbox_ll
bbox_ur = num2deg(xmax + 1, ymin, zoom)
bbox_lr = num2deg(xmax + 1, ymax +1, zoom)
#print bbox_ur, bbox_lr
Cluster = Image.new('RGB',((xmax-xmin+1)*256-1,(ymax-ymin+1)*256-1) )
for xtile in range(xmin, xmax+1):
for ytile in range(ymin, ymax+1):
try:
imgurl=smurl.format(zoom, xtile, ytile)
print("Opening: " + imgurl)
imgstr = urllib2.urlopen(imgurl).read()
tile = Image.open(StringIO.StringIO(imgstr))
Cluster.paste(tile, box=((xtile-xmin)*255 , (ytile-ymin)*255))
except:
print("Couldn't download image")
tile = None
return Cluster, [bbox_ll[1], bbox_ll[0], bbox_ur[1], bbox_ur[0]]
if __name__ == '__main__':
lat_deg, lon_deg, delta_lat, delta_long, zoom = 45.720-0.04/2, 4.210-0.08/2, 0.04, 0.08, 14
a, bbox = getImageCluster(lat_deg, lon_deg, delta_lat, delta_long, zoom)
fig = plt.figure(figsize=(10, 10))
ax = plt.subplot(111)
m = Basemap(
llcrnrlon=bbox[0], llcrnrlat=bbox[1],
urcrnrlon=bbox[2], urcrnrlat=bbox[3],
projection='merc', ax=ax
)
# list of points to display (long, lat)
ls_points = [m(x,y) for x,y in [(4.228, 45.722), (4.219, 45.742), (4.221, 45.737)]]
m.imshow(a, interpolation='lanczos', origin='upper')
ax.scatter([point[0] for point in ls_points],
[point[1] for point in ls_points],
alpha = 0.9)
plt.show()
答案 2 :(得分:5)
它不是那么复杂。可以从this链接获得一些指导,其中详细解释了瓷砖的复杂性。
这里很难复制,但一般来说你必须
请注意,您可能还有必须解决的宽高比问题......
答案 3 :(得分:4)
编辑:OpenStreetMap声明其磁贴服务器不能免费使用,并且处于使用政策下:
https://operations.osmfoundation.org/policies/tiles/
在使用示例之前,请先阅读此内容。
由于在Python 3.8中实现代码时遇到问题,我将一些答案合并在一起并修改了代码。现在它对我有效,并且没有任何错误。
当我尝试在Python 3中运行BerndGit的原始代码时,我必须做出与答案中描述的Joining Dots相同的更改。我替换了
import urllib2
import StringIO
使用
import requests
from io import BytesIO
因为urllib2库不再适用于Python 3。您必须使用urllib.request或请求。
然后我不得不从getImageCluster函数中更改这两行
imgstr = urllib2.urlopen(imgurl).read()
tile = Image.open(StringIO.StringIO(imgstr))
到
imgstr = requests.get(imgurl)
tile = Image.open(BytesIO(imgstr.content))
在那之后,我可以运行代码而不会出现错误,但是它仍然无法下载图像。结果我总是得到一块黑色的瓷砖。通过一些研究,我了解到在使用请求时伪造用户代理很重要,因为该网站可以告诉您该请求来自Python并可能阻止它。以下网站对此进行了介绍:
https://www.scrapehero.com/how-to-fake-and-rotate-user-agents-using-python-3/
因此,我遵循了网站上的建议,从而在getImageCluster函数的开头添加了这一行:
headers = {"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"}
现在,我们需要将这些标头包含在请求调用中:
imgstr = requests.get(imgurl, headers=headers)
整个代码现在看起来像这样:
import matplotlib.pyplot as plt
import numpy as np
import math
import requests
from io import BytesIO
from PIL import Image
def deg2num(lat_deg, lon_deg, zoom):
lat_rad = math.radians(lat_deg)
n = 2.0 ** zoom
xtile = int((lon_deg + 180.0) / 360.0 * n)
ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
return (xtile, ytile)
def num2deg(xtile, ytile, zoom):
n = 2.0 ** zoom
lon_deg = xtile / n * 360.0 - 180.0
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
lat_deg = math.degrees(lat_rad)
return (lat_deg, lon_deg)
def getImageCluster(lat_deg, lon_deg, delta_lat, delta_long, zoom):
headers = {"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"}
smurl = r"http://a.tile.openstreetmap.org/{0}/{1}/{2}.png"
xmin, ymax =deg2num(lat_deg, lon_deg, zoom)
xmax, ymin =deg2num(lat_deg + delta_lat, lon_deg + delta_long, zoom)
Cluster = Image.new('RGB',((xmax-xmin+1)*256-1,(ymax-ymin+1)*256-1) )
for xtile in range(xmin, xmax+1):
for ytile in range(ymin, ymax+1):
try:
imgurl = smurl.format(zoom, xtile, ytile)
print("Opening: " + imgurl)
imgstr = requests.get(imgurl, headers=headers)
tile = Image.open(BytesIO(imgstr.content))
Cluster.paste(tile, box = ((xtile-xmin)*256 , (ytile-ymin)*255))
except:
print("Couldn't download image")
tile = None
return Cluster
if __name__ == '__main__':
a = getImageCluster(38.5, -77.04, 0.02, 0.05, 13)
fig = plt.figure()
fig.patch.set_facecolor('white')
plt.imshow(np.asarray(a))
plt.show()
答案 4 :(得分:1)
使用python 3.6.5,您需要稍微修改标头:
import matplotlib.pyplot as plt
import numpy as np
import math
import urllib3
from io import StringIO
from PIL import Image
只需使用pip install
并且请注意,必须像pip install Pillow
答案 5 :(得分:1)
还有另一种获取合并的openstreetmap图像的方法(使用python3,令人惊叹的mercantile库和并行获取):
import multiprocessing
import random
import io
import mercantile
import urllib.request
import PIL.Image
def _download_tile(tile: mercantile.Tile):
"""
Helper function for downloading associated image
"""
server = random.choice(['a', 'b', 'c'])
url = 'http://{server}.tile.openstreetmap.org/{zoom}/{x}/{y}.png'.format(
server=server,
zoom=tile.z,
x=tile.x,
y=tile.y
)
response = urllib.request.urlopen(url)
img = PIL.Image.open(io.BytesIO(response.read()))
return img, tile
def get_image(west, south, east, north, zoom):
"""
return glued tiles as PIL image
:param west: west longitude in degrees
:param south: south latitude in degrees
:param east: east longitude in degrees
:param north: north latitude in degrees
:param zoom: wanted size
:return: Image
"""
tiles = list(mercantile.tiles(west, south, east, north, zoom))
tile_size = 256
min_x = min_y = max_x = max_y = None
for tile in tiles:
min_x = min(min_x, tile.x) if min_x is not None else tile.x
min_y = min(min_y, tile.y) if min_y is not None else tile.y
max_x = max(max_x, tile.x) if max_x is not None else tile.x
max_y = max(max_y, tile.y) if max_y is not None else tile.y
out_img = PIL.Image.new(
'RGB',
((max_x - min_x + 1) * tile_size, (max_y - min_y + 1) * tile_size)
)
pool = multiprocessing.Pool(8)
results = pool.map(_download_tile, tiles)
pool.close()
pool.join()
for img, tile in results:
left = tile.x - min_x
top = tile.y - min_y
bounds = (left * tile_size, top * tile_size, (left + 1) * tile_size, (top + 1) * tile_size)
out_img.paste(img, bounds)
return out_img
if __name__ == '__main__':
# get combined image and save to file
get_image(west=103, south=51, east=110, north=56, zoom=8).save('osm_image.png')
答案 6 :(得分:0)
以下内容也基于BerndGit的精彩回答。我必须进行一些修改才能使其与Python 3.6.7一起使用。将它们张贴在这里以防其他人使用。
设置必需的枕头,并用请求替换urllib,并用io / ByesIO替换io / StringIO
import requests
from io import BytesIO
然后只需修改在getImageCluster()函数中下载图像的方式:
imgstr = requests.get(imgurl)
tile = Image.open(BytesIO(imgstr.content))
非常感谢BerndGit麻烦张贴原件。
尚未设法使Etna的修改后的底图版本起作用。必须为底图的PROJ_LIB错误添加导出路径:
export PROJ_LIB=/path/to/your/instalation/of/anaconda/share/proj/
(位于Basemap import error in PyCharm —— KeyError: 'PROJ_LIB'的解决方案)
在尝试绘制时出现设置属性错误。它也可以使用底图教程(https://basemaptutorial.readthedocs.io/en/latest/plotting_data.html#plot)进行,但区别在于尽管有错误,数据的散布仍会在地图上绘制为一层。使用OSM磁贴,无法使数据层显示在地图顶部。必须分别导出每个图层,然后使用图像编辑软件进行合并。