没有河流的世界地图与matplotlib / Basemap?

Posted

技术标签:

【中文标题】没有河流的世界地图与matplotlib / Basemap?【英文标题】:world map without rivers with matplotlib / Basemap? 【发布时间】:2012-12-26 03:53:19 【问题描述】:

有没有办法用底图(或不用底图,如果有其他方法)绘制大陆边界,而不会出现那些烦人的河流?尤其是那条金刚河,连大海都没有,令人不安。

编辑:我打算在地图上进一步绘制数据,就像在Basemap gallery 中一样(并且仍然将大陆的边界线绘制为数据上的黑线,以便为世界地图提供结构)所以虽然解决方案是钩在下面很好,甚至是高手,它不适用于这个目的。

图片制作者:

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)

【问题讨论】:

非常感谢您的夸奖!随意选择最适合您需求的答案(这就是本网站的用途!)。我会留下我的答案,因为它可能在未来对其他人有用。以后,试着准确地说出你想做什么。提出的两个答案处理“绘图”部分,我们都将其解释为艺术渲染。但是,您没有理由仍然无法根据我们都提供的答案进行规划 - 所有坐标都在那里,除了现在您知道所有那些丑陋的河流在哪里! 【参考方案1】:

如何去除“烦人”的河流:

如果您想对图像进行后期处理(而不是直接使用底图),您可以移除不与海洋相连的水体:

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

第一张图片是原图,第二张是地块。第三个不是必需的,但很有趣,因为它标识了每个单独的连续陆地。第四张图片就是你想要的,去掉了“河流”的图片。

【讨论】:

你的第四张照片里还有河流。 @tiago 是的,但是如果您阅读我的回答,您会注意到所提出的方法是“去除不与海洋相连的水体”,我认为这是OP称为“烦人的河流”。提出明确提及的意见并不像提出解决剩余缺陷的建议那样具有建设性。【参考方案2】:

出于这样的原因,我经常一起避免使用 Basemap,并使用 OGR 读取 shapefile 并将它们自己转换为 Matplotlib 艺术家。这是更多的工作,但也提供了更多的灵活性。

底图有一些非常简洁的功能,例如将输入数据的坐标转换为您的“工作投影”。

如果您想坚持使用 Basemap,请获取不包含河流的 shapefile。例如,自然地球在物理部分有一个很好的“土地”形状文件(下载“比例等级”数据并解压缩)。见http://www.naturalearthdata.com/downloads/10m-physical-vectors/

您可以使用 Basemap 中的 m.readshapefile() 方法读取 shapefile。这允许您在投影坐标中获取 Matplotlib 路径顶点和代码,然后您可以将其转换为新路径。它有点绕道,但它为您提供了来自 Matplotlib 的所有样式选项,其中大部分不能通过 Basemap 直接获得。它有点 hackish,但我现在在坚持 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)

给予:

【讨论】:

+1 对于自然地球参考,谢谢!你知道任何其他好的“艺术”渲染开源吗? 其他然后是 Matplotlib 我有时使用 Mapnik(与 TileMill)。我刚刚注意到我上面的方法忽略/删除了多边形的内环(例如,卡斯皮海消失了):( 到目前为止谢谢你,但是:我从你的链接下载了 ne_10m_land.zip,解压缩,并将其读为“ne_10m_land”,但我收到错误:“第 10 行,在 shp_info = m.readshapefile('ne_10m_land', 'scalerank', drawbounds=True) [...] ValueError: invalid literal for int() with base 10: '****'". 我认为这是因为原始 shapefile 在 'scalerank' 属性中不包含有效整数。输入一些虚拟号码可能会有所帮助。如果没有属性,我不知道 Basemap 是否可以选择“fid”,会很好。 您能推荐一个底图替代方案吗?它似乎是放弃软件。谢谢。【参考方案3】:

好的,我想我有一个部分解决方案。

基本思想是 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()

【讨论】:

【参考方案4】:

按照 user1868739 的示例,我只能选择我想要的路径(对于某些湖泊):

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)

白色背景的版本足以对大陆上的各种土地信息进行进一步的彩色绘图,但如果想要为大陆保留灰色背景,则需要更精细的解决方案。

【讨论】:

我猜如果您还绘制“不需要的”多边形但颜色与大陆填充相同,那么白河问题就会消失。【参考方案5】:

根据我对@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)

【讨论】:

【参考方案6】:

如果您对绘制轮廓而不是 shapefile 感到满意,那么绘制可以从任何地方获得的海岸线非常容易。我从 NOAA Coastline Extractor 获得了 MATLAB 格式的海岸线: http://www.ngdc.noaa.gov/mgg/shorelines/shorelines.html

为了编辑海岸线,我将其转换为 SVG,然后使用 Inkscape 对其进行编辑,然后再转换回 lat/lon 文本文件(“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"
   
   
   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'

【讨论】:

【参考方案7】:

我经常修改 Basemap 的 drawcoastlines() 以避免那些“破碎”的河流。为了数据源的一致性,我还修改了 drawcountries()。

为了支持自然地球数据中可用的不同分辨率,我使用以下方法:

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

这是输出:

请注意,默认情况下,底图使用 resolution='c'(粗略),显示的代码不支持此功能。

【讨论】:

注意这个解决方案你需要从Natural Earth下载数据。

以上是关于没有河流的世界地图与matplotlib / Basemap?的主要内容,如果未能解决你的问题,请参考以下文章

[QGIS]绘制参考地图--德国

arcgis制作河流弯曲度图

如何在R中模拟河流水位上升

[codevs5578][咸鱼]tarjan/结论题

序章 遗迹

Geopandas和Matplotlib,我如何绘制没有任何形状的轮廓?