Невозможно получить фрагменты карты с высоким уровнем масштабирования (т.е. в уменьшенном масштабе) с помощью Matplotlib/Cartopy/Python

Я создаю картографическое приложение с помощью Python и Cartopy и пытаюсь использовать фрагменты карты с открытым исходным кодом для фона, чтобы иметь больше возможностей, чем карта Cartopy по умолчанию.

Он отлично работает для карт, которые достаточно близко увеличены, но когда я пытаюсь получить вид с большей высоты, что-то не получается. Если у меня установлен масштаб на 11, он работает. Если я установлю его на 12, он зависнет на неопределенный срок и не даст трассировки.

Тот же результат с картографическими серверами OSM и Stamen.

Вот короткий, самодостаточный пример (обратите внимание, что одна или две строки могут быть артефактами из разных способов, которые я пробовал)

import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt


def custom_background(source_point):

    source_point = source_point.split(" ")
    source_point = (float(source_point[0]), float(source_point[1]))
    dx = 1.5
    dy = 1.5
    lon_min, lon_max = source_point[0]-dx, source_point[0]+dx
    lat_min, lat_max = source_point[1]-dy, source_point[1]+dy
    zoom = 7
    map_url = "https://www.openstreetmap.org/#map={}/{}/{}".format(zoom,source_point[0],source_point[1])
    tile = cimgt.OSM(url=map_url)
    tile = cimgt.StamenTerrain()
    ax = plt.axes(projection=ccrs.PlateCarree())
    ax.set_extent([lat_min, lat_max, lon_min, lon_max])
    ax.add_image(tile, zoom)
    #~ ax.add_image(tile)
    return ax

custom_background("45.068466 -66.45477")
plt.savefig("tile.png")

результат, с масштабированием = 7:

результат выполнения кода для zoom = 7

но если я изменю масштаб, скажем, на 14, программа не завершится независимо от того, как долго я позволяю ей работать.

Параметр URL, передаваемый в cimgt.OSM(), является необязательным. Я получаю тот же результат с ним или без него. (См.: https://scitools.org.uk/cartopy/docs/v0.16/cartopy/io/img_tiles.html#cartopy.io.img_tiles.OSM)

Я что-то упустил здесь? Любая помощь будет оценена по достоинству, спасибо.


person D. Lef    schedule 01.08.2018    source источник


Ответы (1)


Уровни "масштабирования" основаны на Quadtree. Существенное увеличение разрешения «масштабирования» увеличивает количество плиток в четыре раза.

So:

zoom level 0: 4^0 = 1 tile(s) to cover the globe
zoom level 1: 4^1 = 4 tile(s) to cover the globe
...
zoom level 7: 4^7 = 16,384 tile(s) to cover the globe
...
zoom level 14: 4^14 = 268,435,456 tile(s) to cover the globe

Таким образом, если вы запрашиваете плитки с высоким уровнем масштабирования для большой области, вы можете запросить много плиток.

В объекте Tiler есть полезный, но недокументированный метод под названием find_images. Его реализация не слишком сложная:
https://github.com/SciTools/cartopy/blob/v0.16.0/lib/cartopy/io/img_tiles.py#L103-L122.

С помощью этого метода мы можем фактически увидеть плитки, которые будут использоваться для данного диапазона. Важно, чтобы диапазон был указан в системе координат плиточника (почти исключительно Web Mercator).

In [1]: import cartopy.io.img_tiles as cimgt

In [2]: import shapely.geometry as sgeom

In [3]: import cartopy.crs as ccrs

In [4]: tiler = cimgt.OSM()

In [5]: pt = 45.068466, -66.45477

In [6]: target = sgeom.box(pt[0] - 1.5, pt[1] - 1.5, pt[0] + 1.5, pt[1] + 1.5)

In [7]: target_mercator = tiler.crs.project_geometry(target, ccrs.Geodetic()).geoms[0]

Итак, со всеми частями мы можем начать выяснять, какие плитки нам нужно рисовать при определенных уровнях масштабирования:

Для цели, которую вы указываете, на уровне масштабирования 0 нам нужны следующие тайлы (x, y, z):

In [8]: list(tiler.find_images(target_mercator, 0))
Out[8]: [(0, 0, 0)]

Для z=1 это по-прежнему только одна плитка:

In [9]: list(tiler.find_images(target_mercator, 1))
Out[9]: [(1, 1, 1)]

Но для z=2 мы явно пересекли границу плитки, так как теперь нам нужно две плитки, чтобы покрыть целевой домен:

In [10]: list(tiler.find_images(target_mercator, 2))
Out[10]: [(2, 2, 2), (2, 3, 2)]

Naturally, the list grows as we increase the zoom level:

In [11]: list(tiler.find_images(target_mercator, 3))
Out[11]: [(4, 5, 3), (5, 5, 3), (4, 6, 3), (5, 6, 3)]

In [12]: list(tiler.find_images(target_mercator, 6))
Out[12]: [(39, 47, 6), (40, 47, 6), (39, 48, 6), (40, 48, 6)]

Когда мы достигнем z=7, мы обнаружим, что нам потребуется 8 плиток для представления рассматриваемой области:

In [13]: list(tiler.find_images(target_mercator, 7))
Out[13]: 
[(79, 94, 7),
 (79, 95, 7),
 (80, 94, 7),
 (80, 95, 7),
 (79, 96, 7),
 (79, 97, 7),
 (80, 96, 7),
 (80, 97, 7)]

Я уверен, что вы можете видеть, к чему это идет, но давайте попробуем ваш уровень масштабирования 14, чтобы узнать, сколько плиток нам понадобится. Чтобы сэкономить электроэнергию и время чтения, давайте просто напечатаем длину этого списка...

In [14]: len(list(tiler.find_images(target_mercator, 14)))
Out[14]: 47334

Правильно, поэтому для запроса уровня масштабирования 14 в желаемом размере вам потребуется загрузить ~ 47 000 плиток с разрешением 256x256 (8-битные PNG с цветовой картой). Плитка z=0 (https://a.tile.openstreetmap.org/0/0/0.png) сжимается примерно на 8882 байта, поэтому, если предположить, что это типично, в итоге вы загрузите ~420420588 байт (400 МБ). Чтобы хранить эти данные в памяти, вам также потребуется около 2,9 ГБ оперативной памяти. Наконец, чтобы перепроецировать эти данные в PlateCarree, вам потребуется как минимум удвоить этот объем ОЗУ, и это предполагает высокоэффективную реализацию перепроецирования (cartopy — нет).

Надеюсь, это объясняет вам, почему ваш код занимает много времени — вы просите его сделать много работы. Было несколько дискуссий о том, должна ли cartopy предупреждать, когда запрашивается чрезмерно большое количество плиток, но все всегда сводится к необоснованному количеству (на самом деле вы можете захотеть получить такое количество плиток! ). Мы также говорили об автоматическом выборе уровня масштабирования — это действительно осуществимо, если есть достаточный спрос.

ХТН

person pelson    schedule 02.08.2018
comment
Так что это не совсем решило мою проблему, но помогло мне понять ее. Моя проблема была не в уровне масштабирования, а в методе set_extent(). Я не уверен, какой здесь протокол, чтобы принять этот ответ или написать свой собственный. - person D. Lef; 02.08.2018
comment
Пожалуйста, не стесняйтесь писать свои собственные (и принимать их). Вся полезная информация/знания. - person pelson; 03.08.2018