Создание шейп-файла с использованием LineCollection показывает все ребра, но частично их заполняет

Последние несколько дней я пытался получить данные метеостанции на карте только для моей страны. Я делаю это следующим образом:

  • Загружая данные, я создаю сетку используя интерполяцию
  • На основе этой сетки я рисую contour а также contourf образ
  • Затем я рисую шейп-файлы для Германии, Бельгии и Франции поверх карты, чтобы охватить не относящиеся к делу contour / contourf элементы. Я использовал этот учебник для этого.
  • Наконец, я использую шейп-файл Мирового океана (IHO Sea Areas; www.marineregions.org/downloads.php#iho), чтобы также строить графики для покрытия Северного моря. Используя QGIS, я отредактировал этот шейп-файл океанов и удалил все, кроме Северного моря - учитывая временные ограничения:)

Вы бы сказали, что все идет хорошо, но почему-то части страны, а также острова интерпретируются как вода. Я предполагаю, что это потому, что это отдельные части, все они не связаны с основной землей (из-за вод / рек).

Как ни странно, края нарисованы, но они не заполнены.

Я много пробовал и искал, но понятия не имею, как это исправить. Я думаю, это где-то в LineCollection потому что в QGIS шейп-файл правильный (т.е. он не распознает фигуры при щелчке по островам и т. д., что правильно, поскольку он должен распознавать фигуру только при щелчке по морю). Я искренне надеюсь, что вы могли бы помочь мне указать, где я ошибаюсь и как я могу это исправить!

Большое спасибо!

Вот карта, которую я получаю:

https://i.imgur.com/GHISN7n.png

Мой код выглядит следующим образом (и вы, вероятно, видите, что я очень новичок в этом виде программирования, я начал вчера:)):

import numpy as np
import matplotlib
matplotlib.use('Agg')
from scipy.interpolate import griddata
from mpl_toolkits.basemap import Basemap, maskoceans
import matplotlib.pyplot as plt
from numpy.random import seed
import shapefile as shp
from matplotlib.collections import LineCollection
from matplotlib import cm
# Set figure size
plt.figure(figsize=(15,15), dpi=80)
# Define map bounds
xMin, xMax = 2.5, 8.0
yMin, yMax = 50.6, 53.8
# Create map
m = Basemap(projection='merc',llcrnrlon=xMin,llcrnrlat=yMin,urcrnrlon=xMax,urcrnrlat=yMax,resolution='h')
m.drawmapboundary(fill_color='#d4dadc',linewidth=0.25)
# m.drawcoastlines(linewidth=0.5,color='#333333')
# Load data
y = [54.325666666667,52.36,53.269444444444,55.399166666667,54.116666666667,53.614444444444,53.491666666667,53.824130555556,52.918055555556,54.03694,52.139722,52.926865008825,54.853888888889,52.317222,53.240026656696,52.642696895243,53.391265948394,52.505333893732,52.098821802977,52.896643913235,52.457270486008,53.223000488316,52.701902388132,52.0548617826,53.411581103636,52.434561756559,52.749056395511,53.123676213651,52.067534268959,53.194409573306,52.27314817052,51.441334059998,51.224757511326,51.990941918858,51.447744494043,51.960667359998,51.969031121385,51.564889021961,51.857593837453,51.449772459909,51.658528382201,51.196699902606,50.905256257898,51.497306260089,yMin,yMin,yMax,yMax]
x = [2.93575,3.3416666666667,3.6277777777778,3.8102777777778,4.0122222222222,4.9602777777778,5.9416666666667,2.9452777777778,4.1502777777778,6.04167,4.436389,4.7811453228565,4.6961111111111,4.789722,4.9207907082729,4.9787572406902,5.3458010937365,4.6029300588208,5.1797058644882,5.383478899702,5.5196324030324,5.7515738887123,5.8874461671401,5.8723225499118,6.1990994508938,6.2589770334531,6.5729701105864,6.5848470019087,6.6567253619722,7.1493220605216,6.8908745111116,3.5958241584686,3.8609657214986,4.121849767852,4.342014,4.4469005114756,4.9259216999194,4.9352386335384,5.1453989235756,5.3770039280214,5.7065946674719,5.7625447234516,5.7617834850481,6.1961067840608,xMin,xMax,xMin,xMax]
z = [4.8,5.2,5.8,5.4,5,5.3,5.4,4.6,5.8,6.3,4.8,5.4,5.3,4.6,5.4,4.4,4.1,5.5,4.5,4.2,3.9,3.7,4.2,3.2,4,3.8,2.7,2.3,3.4,2.5,3.7,5.2,2.9,5.1,3.8,4.4,4.2,3.9,3.8,3.2,2.6,2.8,2.4,3.1]
avg = np.average(z)
z.extend([avg,avg,avg,avg])
x,y = m(x,y)
# target grid to interpolate to
xis = np.arange(min(x),max(x),2000)
yis = np.arange(min(y),max(y),2000)
xi,yi = np.meshgrid(xis,yis)
# interpolate
zi = griddata((x,y),z,(xi,yi),method='cubic')
# Decide on proper values for colour bar (todo)
vrange = max(z)-min(z)
mult = 2
vmin = min(z)-(mult*vrange)
vmax = max(z)+(mult*vrange)
# Draw contours
cs = m.contour(xi, yi, zi, 5, linewidths=0.25, colors='k')
cs = m.contourf(xi, yi, zi, 5,vmax=vmax,vmin=vmin,cmap=plt.get_cmap('jet'))
# Plot seas from shapefile
sf = shp.Reader(r'/DIR_TO_SHP/shapefiles/northsea')
shapes = sf.shapes()
print shapes[0].parts
records = sf.records()
ax = plt.subplot(111)
for record, shape in zip(records,shapes):
    lons,lats = zip(*shape.points)
    data = np.array(m(lons, lats)).T
    print len(shape.parts)
    if len(shape.parts) == 1:
        segs = [data,]
    else:
        segs = []
        for i in range(1,len(shape.parts)):
            index = shape.parts[i-1]
            index2 = shape.parts[i]
            segs.append(data[index:index2])
        segs.append(data[index2:])
    lines = LineCollection(segs,antialiaseds=(1,),zorder=3)
    lines.set_facecolors('#abc0d3')
    lines.set_edgecolors('red')
    lines.set_linewidth(0.5)
    ax.add_collection(lines)
# Plot France from shapefile
sf = shp.Reader(r'/DIR_TO_SHP/shapefiles/FRA_adm0')
shapes = sf.shapes()
records = sf.records()
ax = plt.subplot(111)
for record, shape in zip(records,shapes):
    lons,lats = zip(*shape.points)
    data = np.array(m(lons, lats)).T
    if len(shape.parts) == 1:
        segs = [data,]
    else:
        segs = []
        for i in range(1,len(shape.parts)):
            index = shape.parts[i-1]
            index2 = shape.parts[i]
            segs.append(data[index:index2])
        segs.append(data[index2:])
    lines = LineCollection(segs,antialiaseds=(1,))
    lines.set_facecolors('#fafaf8')
    lines.set_edgecolors('#333333')
    lines.set_linewidth(0.5)
    ax.add_collection(lines)
# Plot Belgium from shapefile
sf = shp.Reader(r'/DIR_TO_SHP/shapefiles/BEL_adm0')
shapes = sf.shapes()
records = sf.records()
ax = plt.subplot(111)
for record, shape in zip(records,shapes):
    lons,lats = zip(*shape.points)
    data = np.array(m(lons, lats)).T
    if len(shape.parts) == 1:
        segs = [data,]
    else:
        segs = []
        for i in range(1,len(shape.parts)):
            index = shape.parts[i-1]
            index2 = shape.parts[i]
            segs.append(data[index:index2])
        segs.append(data[index2:])
    lines = LineCollection(segs,antialiaseds=(1,))
    lines.set_facecolors('#fafaf8')
    lines.set_edgecolors('#333333')
    lines.set_linewidth(0.5)
    ax.add_collection(lines)
# Plot Germany from shapefile
sf = shp.Reader(r'/DIR_TO_SHP/shapefiles/DEU_adm0')
shapes = sf.shapes()
records = sf.records()
ax = plt.subplot(111)
for record, shape in zip(records,shapes):
    lons,lats = zip(*shape.points)
    data = np.array(m(lons, lats)).T
    if len(shape.parts) == 1:
        segs = [data,]
    else:
        segs = []
        for i in range(1,len(shape.parts)):
            index = shape.parts[i-1]
            index2 = shape.parts[i]
            segs.append(data[index:index2])
        segs.append(data[index2:])
    lines = LineCollection(segs,antialiaseds=(1,))
    lines.set_facecolors('#fafaf8')
    lines.set_edgecolors('#333333')
    lines.set_linewidth(0.5)
    ax.add_collection(lines)
# Finish plot
plt.axis('off')
plt.savefig("test2.png",bbox_inches='tight',pad_inches=0);

2 ответа

Решение

Ваша проблема в том, что LineCollection не делает то, что вы думаете, что делает. То, что вы хотите, это внешняя заполненная форма с "пробитыми отверстиями", которые имеют формы других линий в вашем LineCollection (т.е. острова в северном море). LineCollectionоднако заполняет каждый сегмент линии в списке, поэтому заполненные фигуры просто перекрывают друг друга.

Вдохновленный этим постом, я написал ответ, который, кажется, решил вашу проблему, используя Patches, Однако я не совсем уверен, насколько надежным является решение: согласно связанному (без ответа) посту, вершины внешней формы должны быть упорядочены по часовой стрелке, в то время как вершины "пробитых отверстий" должны быть упорядочены против часовой стрелки. (Я тоже это проверил, и это кажется правильным; этот пример matplotlib показывает то же поведение). Поскольку шейп-файлы являются бинарными, трудно проверить порядок вершин, но результат выглядит верным. В приведенном ниже примере я предполагаю, что в каждом шейп-файле самый длинный отрезок линии - это контур страны (или северного моря), а более короткие отрезки - это острова или некоторые другие. Поэтому я сначала упорядочиваю сегменты каждого шейп-файла по длине и создаю Path и PathPatch после этого. Я взял на себя свободу использовать разные цвета для каждого шейп-файла, чтобы убедиться, что все работает как надо.

import numpy as np
import matplotlib
matplotlib.use('Agg')
from scipy.interpolate import griddata
from mpl_toolkits.basemap import Basemap, maskoceans
import matplotlib.pyplot as plt
from numpy.random import seed
import shapefile as shp
from matplotlib.collections import LineCollection
from matplotlib.patches import Path, PathPatch
from matplotlib import cm


# Set figure size
fig, ax = plt.subplots(figsize=(15,15), dpi = 80)

# Define map bounds
xMin, xMax = 2.5, 8.0
yMin, yMax = 50.6, 53.8

shapefiles = [
    'shapefiles/BEL_adm0',
    'shapefiles/FRA_adm0',
    'shapefiles/DEU_adm0',
    'shapefiles/northsea',
]

colors = ['red', 'green', 'yellow', 'blue']


y = [54.325666666667,52.36,53.269444444444,55.399166666667,54.116666666667,53.614444444444,53.491666666667,53.824130555556,52.918055555556,54.03694,52.139722,52.926865008825,54.853888888889,52.317222,53.240026656696,52.642696895243,53.391265948394,52.505333893732,52.098821802977,52.896643913235,52.457270486008,53.223000488316,52.701902388132,52.0548617826,53.411581103636,52.434561756559,52.749056395511,53.123676213651,52.067534268959,53.194409573306,52.27314817052,51.441334059998,51.224757511326,51.990941918858,51.447744494043,51.960667359998,51.969031121385,51.564889021961,51.857593837453,51.449772459909,51.658528382201,51.196699902606,50.905256257898,51.497306260089,yMin,yMin,yMax,yMax]
x = [2.93575,3.3416666666667,3.6277777777778,3.8102777777778,4.0122222222222,4.9602777777778,5.9416666666667,2.9452777777778,4.1502777777778,6.04167,4.436389,4.7811453228565,4.6961111111111,4.789722,4.9207907082729,4.9787572406902,5.3458010937365,4.6029300588208,5.1797058644882,5.383478899702,5.5196324030324,5.7515738887123,5.8874461671401,5.8723225499118,6.1990994508938,6.2589770334531,6.5729701105864,6.5848470019087,6.6567253619722,7.1493220605216,6.8908745111116,3.5958241584686,3.8609657214986,4.121849767852,4.342014,4.4469005114756,4.9259216999194,4.9352386335384,5.1453989235756,5.3770039280214,5.7065946674719,5.7625447234516,5.7617834850481,6.1961067840608,xMin,xMax,xMin,xMax]
z = [4.8,5.2,5.8,5.4,5,5.3,5.4,4.6,5.8,6.3,4.8,5.4,5.3,4.6,5.4,4.4,4.1,5.5,4.5,4.2,3.9,3.7,4.2,3.2,4,3.8,2.7,2.3,3.4,2.5,3.7,5.2,2.9,5.1,3.8,4.4,4.2,3.9,3.8,3.2,2.6,2.8,2.4,3.1]
avg = np.average(z)
z.extend([avg,avg,avg,avg])



# Create map
m = Basemap(
    ax = ax,
    projection='merc',
    llcrnrlon=xMin,
    llcrnrlat=yMin,
    urcrnrlon=xMax,
    urcrnrlat=yMax,
    resolution='h'
)
x,y = m(x,y)
m.drawmapboundary(fill_color='#d4dadc',linewidth=0.25)

# target grid to interpolate to
xis = np.arange(min(x),max(x),2000)
yis = np.arange(min(y),max(y),2000)
xi,yi = np.meshgrid(xis,yis)

# interpolate
zi = griddata((x,y),z,(xi,yi),method='cubic')

# Decide on proper values for colour bar (todo)
vrange = max(z)-min(z)
mult = 2
vmin = min(z)-(mult*vrange)
vmax = max(z)+(mult*vrange)

# Draw contours
cs = m.contour(xi, yi, zi, 5, linewidths=0.25, colors='k')
cs = m.contourf(xi, yi, zi, 5,vmax=vmax,vmin=vmin,cmap=plt.get_cmap('jet'))

for sf_name,color in zip(shapefiles, colors):
    print(sf_name)

    # Load data

    #drawing shapes:
    sf = shp.Reader(sf_name)
    shapes = sf.shapes()
    ##print shapes[0].parts
    records = sf.records()
    ##ax = plt.subplot(111)
    for record, shape in zip(records,shapes):
        lons,lats = zip(*shape.points)
        data = np.array(m(lons, lats)).T

        if len(shape.parts) == 1:
            segs = [data,]
        else:
            segs = []
            for i in range(1,len(shape.parts)):
                index = shape.parts[i-1]
                index2 = shape.parts[i]

                seg = data[index:index2]
                segs.append(seg)
            segs.append(data[index2:])

        ##assuming that the longest segment is the enclosing
        ##line and ordering the segments by length:
        lens=np.array([len(s) for s in segs])
        order = lens.argsort()[::-1]
        segs = [segs[i] for i in order]

        ##producing the outlines:
        lines = LineCollection(segs,antialiaseds=(1,),zorder=4)

        ##note: leaving the facecolors out:
        ##lines.set_facecolors('#abc0d3')
        lines.set_edgecolors('red')
        lines.set_linewidth(0.5)
        ax.add_collection(lines)

        ##producing a path from the line segments:
        segs_lin = [v for s in segs for v in s]
        codes = [
            [Path.MOVETO]+
            [Path.LINETO for p in s[1:]]
        for s in segs]

        codes_lin = [c for s in codes for c in s]
        path = Path(segs_lin, codes_lin)

        ##patch = PathPatch(path, facecolor="#abc0d3", lw=0, zorder = 3)
        patch = PathPatch(path, facecolor=color, lw=0, zorder = 3)
        ax.add_patch(patch)



plt.axis('off')
fig.savefig("shapefiles.png",bbox_inches='tight',pad_inches=0)

Результат выглядит так:

результат приведенного выше кода

Надеюсь это поможет.

Вы не наносили на карту Нидерланды.

# Create map
...
# Draw contours
...
# Plot seas from shapefile
sf = shp.Reader(r'/DIR_TO_SHP/shapefiles/northsea')
...
# Plot France from shapefile
sf = shp.Reader(r'/DIR_TO_SHP/shapefiles/FRA_adm0')
...
# Plot Belgium from shapefile
sf = shp.Reader(r'/DIR_TO_SHP/shapefiles/BEL_adm0')
...
# Plot Germany from shapefile
sf = shp.Reader(r'/DIR_TO_SHP/shapefiles/DEU_adm0')
...
# Finish plot

Таким образом, части Нидерландов заполнены вашими нанесенными на карту данными. Береговые линии есть (от рисования морей?), Но остальная часть страны не там, потому что вы не нарисовали это.

Я бы отказался от этого метода рисования из каждого шейп-файла в отдельности и попытался бы просто использовать базовую карту для своих береговых линий и т. Д. Если вы должны сделать это из шейп-файлов, я бы посоветовал вам определить функцию, которая выполняет что-то вроде:

def drawContentsOfShapeFile(mymap, path_to_shapefile):
    <whatever>

#and use it:
m = Basemap(...)
myshapefiles = []
myshapefiles.append("/DIR_TO_SHP/shapefiles/FRA_adm0")
myshapefiles.append("/DIR_TO_SHP/shapefiles/norway")

for sf in myshapefiles: drawContentOfShapefile(m, sf)
Другие вопросы по тегам