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

时间:2013-01-11 14:34:07

标签: python matplotlib maps geography matplotlib-basemap

有没有办法用Basemap绘制大陆的边界(或者没有底图,如果有其他方式),没有那些令人讨厌的河流出现?尤其是那条甚至没有到达海洋的金刚江,令人不安。

编辑:我打算在地图上进一步绘制数据,例如在Basemap gallery中(并且仍然将大陆的边界线绘制为数据上的黑线,以提供世界地图的结构),所以下面的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)

7 个答案:

答案 0 :(得分:12)

出于这样的原因,我经常完全避免使用Basemap并使用OGR读取shapefile并将其转换为Matplotlib艺术家。这还有很多工作,但也提供了更多的灵活性。

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

如果您想坚持使用Basemap,请获取一个不包含河流的shapefile。例如,Natural Earth在物理部分有一个很好的“Land”shapefile(下载'scale rank'数据和uncompress)。见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)

给出:

enter image description here

答案 1 :(得分:7)

如何删除“恼人的”河流:

如果您想对图像进行后期处理(而不是直接使用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)

按照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 :(得分:3)

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

以下是我用来支持Natural Earth数据中可用的不同分辨率的内容:

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

Here is the output

请注意,默认情况下,Basemap使用resolution =' c' (原始),显示的代码不支持。

答案 4 :(得分:2)

如果您可以绘制轮廓而不是shapefile,那么可以很容易地绘制出可以从任何地方获得的海岸线。我从MATLAB格式的NOAA Coastline Extractor获得了我的海岸线:     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"
   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'

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

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