2015-09-01 62 views

回答

1

我认为没有简单的盆栽解决方案可用于此:您必须使用图形元素自己绘制它。几年前,我编写了一些自适应代码来为任意比例的OS栅格地图添加一个比例尺。
不是真的你想要什么,我想,但它显示了必要的技术:

def add_osgb_scalebar(ax, at_x=(0.1, 0.4), at_y=(0.05, 0.075), max_stripes=5): 
    """ 
    Add a scalebar to a GeoAxes of type cartopy.crs.OSGB (only). 

    Args: 
    * at_x : (float, float) 
     target axes X coordinates (0..1) of box (= left, right) 
    * at_y : (float, float) 
     axes Y coordinates (0..1) of box (= lower, upper) 
    * max_stripes 
     typical/maximum number of black+white regions 
    """ 
    # ensure axis is an OSGB map (meaning coords are just metres) 
    assert isinstance(ax.projection, ccrs.OSGB) 
    # fetch axes coordinate mins+maxes 
    x0, x1 = ax.get_xlim() 
    y0, y1 = ax.get_ylim() 
    # set target rectangle in-visible-area (aka 'Axes') coordinates 
    ax0, ax1 = at_x 
    ay0, ay1 = at_y 
    # choose exact X points as sensible grid ticks with Axis 'ticker' helper 
    x_targets = [x0 + ax * (x1 - x0) for ax in (ax0, ax1)] 
    ll = mpl.ticker.MaxNLocator(nbins=max_stripes, steps=[1,2,4,5,10]) 
    x_vals = ll.tick_values(*x_targets) 
    # grab min+max for limits 
    xl0, xl1 = x_vals[0], x_vals[-1] 
    # calculate Axes Y coordinates of box top+bottom 
    yl0, yl1 = [y0 + ay * (y1 - y0) for ay in [ay0, ay1]] 
    # calculate Axes Y distance of ticks + label margins 
    y_margin = (yl1-yl0)*0.25 

    # fill black/white 'stripes' and draw their boundaries 
    fill_colors = ['black', 'white'] 
    i_color = 0 
    for xi0, xi1 in zip(x_vals[:-1],x_vals[1:]): 
     # fill region 
     plt.fill((xi0, xi1, xi1, xi0, xi0), (yl0, yl0, yl1, yl1, yl0), 
       fill_colors[i_color]) 
     # draw boundary 
     plt.plot((xi0, xi1, xi1, xi0, xi0), (yl0, yl0, yl1, yl1, yl0), 
       'black') 
     i_color = 1 - i_color 

    # add short tick lines 
    for x in x_vals: 
     plt.plot((x, x), (yl0, yl0-y_margin), 'black') 

    # add a scale legend 'Km' 
    font_props = mfonts.FontProperties(size='medium', weight='bold') 
    plt.text(
     0.5 * (xl0 + xl1), 
     yl1 + y_margin, 
     'Km', 
     verticalalignment='bottom', 
     horizontalalignment='center', 
     fontproperties=font_props) 

    # add numeric labels 
    for x in x_vals: 
     plt.text(x, 
       yl0 - 2 * y_margin, 
       '{:g}'.format((x - xl0) * 0.001), 
       verticalalignment='top', 
       horizontalalignment='center', 
       fontproperties=font_props) 

虽然凌乱,不是吗?
你会认为它可能可能为此添加某种'浮动轴对象',以提供自动自我缩放图形,但我不能找出一种方法来做到这一点(和我猜我仍然不能)。

HTH

+0

这很有用,但并不完全符合我的要求。可能能够使用它作为灵感。 – gauteh

6

这是我写的是我自己用它采用PP-MO的回答简单版本的Cartopy比例尺功能: 编辑:修改后的代码来创建为中心的新的投影,使得比例尺为平行于许多坐标系的轴,包括一些正字法和更大的地图,并且不需要指定utm系统。 还添加了代码来计算比例尺长度(如果没有指定)。

import cartopy.crs as ccrs 
import numpy as np 

def scale_bar(ax, length=None, location=(0.5, 0.05), linewidth=3): 
    """ 
    ax is the axes to draw the scalebar on. 
    length is the length of the scalebar in km. 
    location is center of the scalebar in axis coordinates. 
    (ie. 0.5 is the middle of the plot) 
    linewidth is the thickness of the scalebar. 
    """ 
    #Get the limits of the axis in lat long 
    llx0, llx1, lly0, lly1 = ax.get_extent(ccrs.PlateCarree()) 
    #Make tmc horizontally centred on the middle of the map, 
    #vertically at scale bar location 
    sbllx = (llx1 + llx0)/2 
    sblly = lly0 + (lly1 - lly0) * location[1] 
    tmc = ccrs.TransverseMercator(sbllx, sblly) 
    #Get the extent of the plotted area in coordinates in metres 
    x0, x1, y0, y1 = ax.get_extent(tmc) 
    #Turn the specified scalebar location into coordinates in metres 
    sbx = x0 + (x1 - x0) * location[0] 
    sby = y0 + (y1 - y0) * location[1] 

    #Calculate a scale bar length if none has been given 
    #(Theres probably a more pythonic way of rounding the number but this works) 
    if not length: 
     length = (x1 - x0)/5000 #in km 
     ndim = int(np.floor(np.log10(length))) #number of digits in number 
     length = round(length, -ndim) #round to 1sf 
     #Returns numbers starting with the list 
     def scale_number(x): 
      if str(x)[0] in ['1', '2', '5']: return int(x)   
      else: return scale_number(x - 10 ** ndim) 
     length = scale_number(length) 

    #Generate the x coordinate for the ends of the scalebar 
    bar_xs = [sbx - length * 500, sbx + length * 500] 
    #Plot the scalebar 
    ax.plot(bar_xs, [sby, sby], transform=tmc, color='k', linewidth=linewidth) 
    #Plot the scalebar label 
    ax.text(sbx, sby, str(length) + ' km', transform=tmc, 
      horizontalalignment='center', verticalalignment='bottom') 

它有一定的局限性,但比较简单,所以我希望你能看到如何改变它,如果你想要的东西不同。

用法示例:

import matplotlib.pyplot as plt 

ax = plt.axes(projection=ccrs.Mercator()) 
plt.title('Cyprus') 
ax.set_extent([31, 35.5, 34, 36], ccrs.Geodetic()) 
ax.coastlines(resolution='10m') 

scale_bar(ax, 100) 

plt.show() 

Cyprus map with scale bar

1

这里的@ Siyh的答案的改良版本,其补充道:

  • 自动UTM区选择
  • 文/酒吧后面的缓冲所以它显示在背景
  • a指北针

注:

  • ,如果你不使用UTM为您轴,酒吧将被绘制歪
  • 指北针假定北达

代码:

import os 
import cartopy.crs as ccrs 
from math import floor 
import matplotlib.pyplot as plt 
from matplotlib import patheffects 
import matplotlib 
if os.name == 'nt': 
    matplotlib.rc('font', family='Arial') 
else: # might need tweaking, must support black triangle for N arrow 
    matplotlib.rc('font', family='DejaVu Sans') 


def utm_from_lon(lon): 
    """ 
    utm_from_lon - UTM zone for a longitude 

    Not right for some polar regions (Norway, Svalbard, Antartica) 

    :param float lon: longitude 
    :return: UTM zone number 
    :rtype: int 
    """ 
    return floor((lon + 180)/6) + 1 

def scale_bar(ax, proj, length, location=(0.5, 0.05), linewidth=3, 
       units='km', m_per_unit=1000): 
    """ 

    http://stackoverflow.com/a/35705477/1072212 
    ax is the axes to draw the scalebar on. 
    proj is the projection the axes are in 
    location is center of the scalebar in axis coordinates ie. 0.5 is the middle of the plot 
    length is the length of the scalebar in km. 
    linewidth is the thickness of the scalebar. 
    units is the name of the unit 
    m_per_unit is the number of meters in a unit 
    """ 
    # find lat/lon center to find best UTM zone 
    x0, x1, y0, y1 = ax.get_extent(proj.as_geodetic()) 
    # Projection in metres 
    utm = ccrs.UTM(utm_from_lon((x0+x1)/2)) 
    # Get the extent of the plotted area in coordinates in metres 
    x0, x1, y0, y1 = ax.get_extent(utm) 
    # Turn the specified scalebar location into coordinates in metres 
    sbcx, sbcy = x0 + (x1 - x0) * location[0], y0 + (y1 - y0) * location[1] 
    # Generate the x coordinate for the ends of the scalebar 
    bar_xs = [sbcx - length * m_per_unit/2, sbcx + length * m_per_unit/2] 
    # buffer for scalebar 
    buffer = [patheffects.withStroke(linewidth=5, foreground="w")] 
    # Plot the scalebar with buffer 
    ax.plot(bar_xs, [sbcy, sbcy], transform=utm, color='k', 
     linewidth=linewidth, path_effects=buffer) 
    # buffer for text 
    buffer = [patheffects.withStroke(linewidth=3, foreground="w")] 
    # Plot the scalebar label 
    t0 = ax.text(sbcx, sbcy, str(length) + ' ' + units, transform=utm, 
     horizontalalignment='center', verticalalignment='bottom', 
     path_effects=buffer, zorder=2) 
    left = x0+(x1-x0)*0.05 
    # Plot the N arrow 
    t1 = ax.text(left, sbcy, u'\u25B2\nN', transform=utm, 
     horizontalalignment='center', verticalalignment='bottom', 
     path_effects=buffer, zorder=2) 
    # Plot the scalebar without buffer, in case covered by text buffer 
    ax.plot(bar_xs, [sbcy, sbcy], transform=utm, color='k', 
     linewidth=linewidth, zorder=3) 

if __name__ == '__main__': 

    ax = plt.axes(projection=ccrs.Mercator()) 
    plt.title('Cyprus') 
    ax.set_extent([31, 35.5, 34, 36], ccrs.Geodetic()) 
    ax.stock_img() 
    ax.coastlines(resolution='10m') 

    scale_bar(ax, ccrs.Mercator(), 100) # 100 km scale bar 
    # or to use m instead of km 
    # scale_bar(ax, ccrs.Mercator(), 100000, m_per_unit=1, units='m') 
    # or to use miles instead of km 
    # scale_bar(ax, ccrs.Mercator(), 60, m_per_unit=1609.34, units='miles') 

    plt.show() 

Demo image, map of Cyprus with scalebar

+1

看起来不错,数字'500'在代码中做了什么?它可以推广到其他领域吗?我专门与UPS合作。 – gauteh

+0

@gauteh - 非常好的问题,谢谢。代码被硬编码以绘制以公里为单位的酒吧,所以500为1公里/ 2,以居中。但根据你的评论,我把它作为参数,现在它支持其他单位:-) –

相关问题