Простой плиточный OpenStreetMap для отображения для Python
Я хочу включить открытую карту улиц (OSM) в свой код python.
Я прочитал много веб-страниц, касающихся 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. (Я знаю, что мне нужно будет иметь дело с проекциями так далее.)
что мне не нужно/хочется:
- отображение на моем собственном веб-сайте
- чтобы загрузить мои данные на какой-то интернет-сервер
- интерактивные функции, такие как масштабирование, прокрутка (в первую очередь)
- вручную обрабатывать и визуализировать.xml-данные из OSM
- во-первых, я не хочу определять каждую деталь стиля рендеринга. Я надеюсь / ожидаю, что существуют некоторые стили по умолчанию.
сделать у вас есть хорошая отправная точка для меня? Или я недооцениваю сложность этой темы?
5 ответов
основываясь на вашем вводе, я смог достичь своей цели. Вот мой код для других, которые ищут отправную точку для 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()
основываясь на хорошем ответе BerndGit, я добавляю слегка измененную версию, которая позволяет отображать другое содержимое вместе с плитками (используя базовую карту). Кстати, я наткнулся на специальную библиотеку 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()
Это не так уж и сложно. Немного руководства можно получить от этой ссылка, где подробно объясняется сложность плиток.
его вряд ли можно воспроизвести здесь, но в целом вы должны
- определите плитки, которые вам нужны формула
- загрузите их со своего сервера (есть определенный выбор стилей карты)
- возможно, объединить их в обоих направлениях
- и тогда покажите их.
имейте в виду, что у вас, возможно, есть проблемы с соотношением сторон, которые вы также должны решить...
используя 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
и имейте в виду, что PIL должен быть реализован как pip install Pillow
еще один способ получить комбинированное изображение openstreetmap (с python3, amazing меркантильные библиотеки и параллельно):
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')