web-dev-qa-db-ja.com

matplotlib / Basemapを使用した川のない世界地図?

迷惑な川が流れ込むことなく、ベースマップを使用して(または、他の方法がある場合はベースマップを使用せずに)大陸の境界をプロットする方法はありますか?特に、海にさえ届いていないコンゴ川のその部分は邪魔です。

編集: ベースマップギャラリー のように、マップ上にデータをさらにプロットするつもりです(そして、世界地図の構造を与えるために、大陸の境界線をデータ上に黒い線として描画します)。以下のHookedによる解決策は素晴らしいですが、見事ですが、この目的には適用できません。

world map

作成された画像:

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(8, 4.5))
plt.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.00)
m = Basemap(projection='robin',lon_0=0,resolution='c')
m.fillcontinents(color='gray',lake_color='white')
m.drawcoastlines()
plt.savefig('world.png',dpi=75)
20
Sampo Smolander

User1868739の例に従って、必要なパス(一部の湖)のみを選択できます。 world2

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(8, 4.5))
plt.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.00)
m = Basemap(resolution='c',projection='robin',lon_0=0)
m.fillcontinents(color='white',lake_color='white',zorder=2)
coasts = m.drawcoastlines(zorder=1,color='white',linewidth=0)
coasts_paths = coasts.get_paths()

ipolygons = range(83) + [84] # want Baikal, but not Tanganyika
# 80 = Superior+Michigan+Huron, 81 = Victoria, 82 = Aral, 83 = Tanganyika,
# 84 = Baikal, 85 = Great Bear, 86 = Great Slave, 87 = Nyasa, 88 = Erie
# 89 = Winnipeg, 90 = Ontario
for ipoly in ipolygons:
    r = coasts_paths[ipoly]
    # Convert into lon/lat vertices
    polygon_vertices = [(vertex[0],vertex[1]) for (vertex,code) in
                        r.iter_segments(simplify=False)]
    px = [polygon_vertices[i][0] for i in xrange(len(polygon_vertices))]
    py = [polygon_vertices[i][2] for i in xrange(len(polygon_vertices))]
    m.plot(px,py,linewidth=0.5,zorder=3,color='black')

plt.savefig('world2.png',dpi=100)

ただし、これは大陸に白い背景を使用している場合にのみ機能します。次の行でcolor'gray'に変更すると、他の川や湖が大陸と同じ色で塗りつぶされていないことがわかります。 (また、area_threshで遊んでも、海につながっている川は削除されません。)

m.fillcontinents(color='gray',lake_color='white',zorder=2)

world3

背景が白のバージョンは、大陸全体のあらゆる種類の土地情報をさらにカラープロットするのに適していますが、大陸の背景を灰色のままにしておきたい場合は、より複雑なソリューションが必要になります。

3
Sampo Smolander

このような理由で、私はしばしばBasemapを完全に避け、OGRでシェープファイルを読み込み、自分でMatplotlibアーティストに変換します。これは多くの作業ですが、柔軟性も大幅に向上します。

ベースマップには、入力データの座標を「作業中の投影」に変換するなど、非常に優れた機能がいくつかあります。

Basemapを使い続けたい場合は、川を含まないシェープファイルを入手してください。たとえば、Natural Earthでは、物理セクションに「Land」シェープファイルがあります(「scalerank」データをダウンロードして解凍します)。 http://www.naturalearthdata.com/downloads/10m-physical-vectors/ を参照してください。

Basemapのm.readshapefile()メソッドを使用して、シェープファイルを読み込むことができます。これにより、Matplotlibパスの頂点とコードを投影座標で取得し、新しいパスに変換できます。少し回り道がありますが、Matplotlibのすべてのスタイリングオプションが提供されます。そのほとんどは、Basemapから直接利用することはできません。それは少しハックですが、Basemapに固執している間、私は今別の方法をしません。

そう:

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib.collections import PathCollection
from matplotlib.path import Path

fig = plt.figure(figsize=(8, 4.5))
plt.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.00)

# MPL searches for ne_10m_land.shp in the directory 'D:\\ne_10m_land'
m = Basemap(projection='robin',lon_0=0,resolution='c')
shp_info = m.readshapefile('D:\\ne_10m_land', 'scalerank', drawbounds=True)
ax = plt.gca()
ax.cla()

paths = []
for line in shp_info[4]._paths:
    paths.append(Path(line.vertices, codes=line.codes))

coll = PathCollection(paths, linewidths=0, facecolors='grey', zorder=2)

m = Basemap(projection='robin',lon_0=0,resolution='c')
# drawing something seems necessary to 'initiate' the map properly
m.drawcoastlines(color='white', zorder=0)

ax = plt.gca()
ax.add_collection(coll)

plt.savefig('world.png',dpi=75)

与える:

enter image description here

12
Rutger Kassies

「迷惑な」川を取り除く方法:

(Basemapを直接操作する代わりに)画像を後処理したい場合は、海に接続していない水域を削除できます。

import pylab as plt
A = plt.imread("world.png")

import numpy as np
import scipy.ndimage as nd
import collections

# Get a counter of the greyscale colors
a      = A[:,:,0]
colors = collections.Counter(a.ravel())
outside_and_water_color, land_color = colors.most_common(2)

# Find the contigous landmass
land_idx = a == land_color[0]

# Index these land masses
L = np.zeros(a.shape,dtype=int) 
L[land_idx] = 1
L,mass_count = nd.measurements.label(L)

# Loop over the land masses and fill the "holes"
# (rivers without outlays)
L2 = np.zeros(a.shape,dtype=int) 
L2[land_idx] = 1
L2 = nd.morphology.binary_fill_holes(L2)

# Remap onto original image
new_land = L2==1
A2 = A.copy()
c = [land_color[0],]*3 + [1,]
A2[new_land] = land_color[0]

# Plot results
plt.subplot(221)
plt.imshow(A)
plt.axis('off')

plt.subplot(222)
plt.axis('off')
B = A.copy()
B[land_idx] = [1,0,0,1]
plt.imshow(B)

plt.subplot(223)
L = L.astype(float)
L[L==0] = None
plt.axis('off')
plt.imshow(L)

plt.subplot(224)
plt.axis('off')
plt.imshow(A2)

plt.tight_layout()  # Only with newer matplotlib
plt.show()

enter image description here

最初の画像はオリジナルで、2番目の画像は陸地を示しています。 3つ目は必要ありませんが、IDがそれぞれ別々の隣接する陸地であるため楽しいです。 4番目の画像は、「川」を削除した画像です。

7
Hooked

これらの「壊れた」川を避けるために、Basemapのdrawcoastlines()を頻繁に変更します。また、データソースの一貫性を保つためにdrawcountries()を変更します。

NaturalEarthデータで利用可能なさまざまな解像度をサポートするために私が使用するものは次のとおりです。

from mpl_toolkits.basemap import Basemap


class Basemap(Basemap):
    """ Modify Basemap to use Natural Earth data instead of GSHHG data """
    def drawcoastlines(self):
        shapefile = 'data/naturalearth/coastline/ne_%sm_coastline' % \
                    {'l':110, 'm':50, 'h':10}[self.resolution]
        self.readshapefile(shapefile, 'coastline', linewidth=1.)
    def drawcountries(self):
        shapefile = 'data/naturalearth/countries/ne_%sm_admin_0_countries' % \
                    {'l':110, 'm':50, 'h':10}[self.resolution]
        self.readshapefile(shapefile, 'countries', linewidth=0.5)


m = Basemap(llcrnrlon=-90, llcrnrlat=-40, urcrnrlon=-30, urcrnrlat=+20,
            resolution='l')  # resolution = (l)ow | (m)edium | (h)igh
m.drawcoastlines()
m.drawcountries()

出力は次のとおりです: enter image description here

デフォルトでは、Basemapはresolution = 'c'(粗)を使用することに注意してください。これは、示されているコードではサポートされていません。

3
rmolina

シェープファイルではなくアウトラインをプロットしても問題がない場合は、どこからでも取得できる海岸線をプロットするのは非常に簡単です。私はMATLAB形式でNOAACoastline Extractorから海岸線を取得しました: http://www.ngdc.noaa.gov/mgg/shorelines/shorelines.html

海岸線を編集するために、SVGに変換し、Inkscapeで編集してから、緯度/経度のテキストファイル(「MATLAB」形式)に変換し直しました。

すべてのPythonコードは以下に含まれています。

# ---------------------------------------------------------------
def plot_lines(mymap, lons, lats, **kwargs) :
    """Plots a custom coastline.  This plots simple lines, not
    ArcInfo-style SHAPE files.

    Args:
        lons: Longitude coordinates for line segments (degrees E)
        lats: Latitude coordinates for line segments (degrees N)

    Type Info:
        len(lons) == len(lats)
        A NaN in lons and lats signifies a new line segment.

    See:
        giss.noaa.drawcoastline_file()
    """

    # Project onto the map
    x, y = mymap(lons, lats)

    # BUG workaround: Basemap projects our NaN's to 1e30.
    x[x==1e30] = np.nan
    y[y==1e30] = np.nan

    # Plot projected line segments.
    mymap.plot(x, y, **kwargs)


# Read "Matlab" format files from NOAA Coastline Extractor.
# See: http://www.ngdc.noaa.gov/mgg/coast/

lineRE=re.compile('(.*?)\s+(.*)')
def read_coastline(fname, take_every=1) :
    nlines = 0
    xdata = array.array('d')
    ydata = array.array('d')
    for line in file(fname) :
#        if (nlines % 10000 == 0) :
#            print 'nlines = %d' % (nlines,)
        if (nlines % take_every == 0 or line[0:3] == 'nan') :
            match = lineRE.match(line)
            lon = float(match.group(1))
            lat = float(match.group(2))

            xdata.append(lon)
            ydata.append(lat)
        nlines = nlines + 1


    return (np.array(xdata),np.array(ydata))

def drawcoastline_file(mymap, fname, **kwargs) :
    """Reads and plots a coastline file.
    See:
        giss.basemap.drawcoastline()
        giss.basemap.read_coastline()
    """

    lons, lats = read_coastline(fname, take_every=1)
    return drawcoastline(mymap, lons, lats, **kwargs)
# =========================================================
# coastline2svg.py
#
import giss.io.noaa
import os
import numpy as np
import sys

svg_header = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   version="1.1"
   width="360"
   height="180"
   id="svg2">
  <defs
     id="defs4" />
  <metadata
     id="metadata7">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     id="layer1">
"""

path_tpl = """
    <path
       d="%PATH%"
       id="%PATH_ID%"
       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
"""

svg_footer = "</g></svg>"




# Set up paths
data_root = os.path.join(os.environ['HOME'], 'data')
#modelerc = giss.modele.read_modelerc()
#cmrun = modelerc['CMRUNDIR']
#savedisk = modelerc['SAVEDISK']

ifname = sys.argv[1]
ofname = ifname.replace('.dat', '.svg')

lons, lats = giss.io.noaa.read_coastline(ifname, 1)

out = open(ofname, 'w')
out.write(svg_header)

path_id = 1
points = []
for lon, lat in Zip(lons, lats) :
    if np.isnan(lon) or np.isnan(lat) :
        # Process what we have
        if len(points) > 2 :
            out.write('\n<path d="')
            out.write('m %f,%f L' % (points[0][0], points[0][1]))
            for pt in points[1:] :
                out.write(' %f,%f' % pt)
            out.write('"\n   id="path%d"\n' % (path_id))
#            out.write('style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"')
            out.write(' />\n')
            path_id += 1
        points = []
    else :
        lon += 180
        lat = 180 - (lat + 90)
        points.append((lon, lat))


out.write(svg_footer)
out.close()

# =============================================================
# svg2coastline.py

import os
import sys
import re

# Reads the output of Inkscape's "Plain SVG" format, outputs in NOAA MATLAB coastline format

mainRE = re.compile(r'\s*d=".*"')
lineRE = re.compile(r'\s*d="(m|M)\s*(.*?)"')

fname = sys.argv[1]


lons = []
lats = []
for line in open(fname, 'r') :
    # Weed out extraneous lines in the SVG file
    match = mainRE.match(line)
    if match is None :
        continue

    match = lineRE.match(line)

    # Stop if something is wrong
    if match is None :
        sys.stderr.write(line)
        sys.exit(-1)

    type = match.group(1)[0]
    spairs = match.group(2).split(' ')
    x = 0
    y = 0
    for spair in spairs :
        if spair == 'L' :
            type = 'M'
            continue

        (sdelx, sdely) = spair.split(',')
        delx = float(sdelx)
        dely = float(sdely)
        if type == 'm' :
            x += delx
            y += dely
        else :
            x = delx
            y = dely
        lon = x - 180
        lat = 90 - y
        print '%f\t%f' % (lon, lat)
    print 'nan\tnan'
2
Bob F

@ sampo-smolanderへの私のコメントによると

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(8, 4.5))
plt.subplots_adjust(left=0.02, right=0.98, top=0.98, bottom=0.00)
m = Basemap(resolution='c',projection='robin',lon_0=0)
m.fillcontinents(color='gray',lake_color='white',zorder=2)
coasts = m.drawcoastlines(zorder=1,color='white',linewidth=0)
coasts_paths = coasts.get_paths()

ipolygons = range(83) + [84]
for ipoly in xrange(len(coasts_paths)):
    r = coasts_paths[ipoly]
    # Convert into lon/lat vertices
    polygon_vertices = [(vertex[0],vertex[1]) for (vertex,code) in
                        r.iter_segments(simplify=False)]
    px = [polygon_vertices[i][0] for i in xrange(len(polygon_vertices))]
    py = [polygon_vertices[i][1] for i in xrange(len(polygon_vertices))]
    if ipoly in ipolygons:
        m.plot(px,py,linewidth=0.5,zorder=3,color='black')
    else:
        m.plot(px,py,linewidth=0.5,zorder=4,color='grey')
plt.savefig('world2.png',dpi=100)

enter image description here

1
user1868739

さて、私は部分的な解決策があると思います。

基本的な考え方は、drawcoastlines()によって使用されるパスは、サイズ/領域によって順序付けられるということです。つまり、最初のN個のパスは(ほとんどのアプリケーションで)主要な陸地と湖であり、後のパスは小さな島と川です。

問題は、必要な最初のNパスが、射影(たとえば、グローバル、極地、地域)、area_threshが適用されているかどうか、湖または小さな島などが必要かどうかによって異なることです。つまり、微調整する必要があります。これはアプリケーションごとです。

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt

mp = 'cyl'
m = Basemap(resolution='c',projection=mp,lon_0=0,area_thresh=200000)

fill_color = '0.9'

# If you don't want lakes set lake_color to fill_color
m.fillcontinents(color=fill_color,lake_color='white')

# Draw the coastlines, with a thin line and same color as the continent fill.
coasts = m.drawcoastlines(zorder=100,color=fill_color,linewidth=0.5)

# Exact the paths from coasts
coasts_paths = coasts.get_paths()

# In order to see which paths you want to retain or discard you'll need to plot them one
# at a time noting those that you want etc. 
for ipoly in xrange(len(coasts_paths)):
    print ipoly
    r = coasts_paths[ipoly]
    # Convert into lon/lat vertices
    polygon_vertices = [(vertex[0],vertex[1]) for (vertex,code) in
                        r.iter_segments(simplify=False)]
    px = [polygon_vertices[i][0] for i in xrange(len(polygon_vertices))]
    py = [polygon_vertices[i][1] for i in xrange(len(polygon_vertices))]
    m.plot(px,py,'k-',linewidth=1)
    plt.show()

描画を停止するための関連するipoly(poly_stop)がわかったら、次のようなことを行うことができます...

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt

mproj = ['nplaea','cyl']
mp = mproj[0]

if mp == 'nplaea':
    m = Basemap(resolution='c',projection=mp,lon_0=0,boundinglat=30,area_thresh=200000,round=1)
    poly_stop = 10
else:
    m = Basemap(resolution='c',projection=mp,lon_0=0,area_thresh=200000)
    poly_stop = 18
fill_color = '0.9'

# If you don't want lakes set lake_color to fill_color
m.fillcontinents(color=fill_color,lake_color='white')

# Draw the coastlines, with a thin line and same color as the continent fill.
coasts = m.drawcoastlines(zorder=100,color=fill_color,linewidth=0.5)

# Exact the paths from coasts
coasts_paths = coasts.get_paths()

# In order to see which paths you want to retain or discard you'll need to plot them one
# at a time noting those that you want etc. 
for ipoly in xrange(len(coasts_paths)):
    if ipoly > poly_stop: continue
    r = coasts_paths[ipoly]
    # Convert into lon/lat vertices
    polygon_vertices = [(vertex[0],vertex[1]) for (vertex,code) in
                        r.iter_segments(simplify=False)]
    px = [polygon_vertices[i][0] for i in xrange(len(polygon_vertices))]
    py = [polygon_vertices[i][1] for i in xrange(len(polygon_vertices))]
    m.plot(px,py,'k-',linewidth=1)
plt.show()

enter image description here

1
user1868739