简单的OpenStreetMap磁贴显示为Python

时间:2015-02-12 11:18:58

标签: python openstreetmap

我想在我的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中添加我的附加数据。 (我知道我需要处理预测等。)

我不需要/想要的东西:

  • 在我自己的网站上显示
  • 将我的数据上传到某些Internet服务器
  • 交互式功能,如缩放,滚动(首先)
  • 从OSM手动处理并呈现.xml数据
  • 首先,我不想定义渲染样式的每个细节。我希望/期望存在一些默认样式。

你有一个很好的起点吗? 或者我是否低估了这个主题的复杂性?

7 个答案:

答案 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链接获得一些指导,其中详细解释了瓷砖的复杂性。

这里很难复制,但一般来说你必须

  • formula
  • 确定所需的图块
  • 从他们的服务器加载它们(有一定的地图样式选择)
  • 可能在两个方向连接它们
  • 然后显示它们。

请注意,您可能还有必须解决的宽高比问题......

答案 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()

结果如下: output

答案 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

那样实现PIL

答案 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磁贴,无法使数据层显示在地图顶部。必须分别导出每个图层,然后使用图像编辑软件进行合并。