如何在 matplotlib-cartopy geoaxes 中正确水平地打包不同的补丁而不会导致补丁之间出现间隙?

如何解决如何在 matplotlib-cartopy geoaxes 中正确水平地打包不同的补丁而不会导致补丁之间出现间隙?

最近,我一直在检查 matplotlib.offset 类(即:AuxTransformBox、VPacker、HPacker、TextArea)的适用性。

在我的研究中,我证实有时 Packer 类(VPacker 和 HPacker)确实会在提供的补丁之间插入一些间隙。因此,即使不是全部错误,它们的连接也会变得笨拙。

在下面的脚本中(改编自 here),我尝试应用 matplotlib.offset 类为每个地轴(cartopy 的地轴)创建比例尺。请注意,AnchoredScaleBar(AnchoredOffsetbox 的子类)实现了整个 VPacker 和 HPacker 操作。在每个返回的地图中,都有一个比例尺(或至少是它的一部分)。

代码如下:

import cartopy.crs as ccrs
import cartopy.geodesic as cgeo
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
from matplotlib.offsetbox import (AuxTransformBox,VPacker,HPacker,TextArea)
import matplotlib.transforms as transforms


from matplotlib.offsetbox import AnchoredOffsetbox


class AnchoredScaleBar(AnchoredOffsetbox):
    def __init__(self,ax,transform,xcoords,height,xlabels=None,ylabels=None,fontsize=4,pad=0.1,borderpad=0.1,sep=2,prop=None,**kwargs):
        """
        Draw a horizontal and/or vertical  bar with the size in
        data coordinate of the give axes. A label will be drawn
        underneath (center-aligned).

        - transform : the coordinate frame (typically axes.transData)

        - sizex,sizey : width of x,y bar,in data units. 0 to omit

        - labelx,labely : labels for x,y bars; None to omit

        - pad,borderpad : padding,in fraction of the legend
        font size (or prop)

        - sep : separation between labels and bars in points.

        - **kwargs : additional arguments passed to base class

        constructor
        """

        ATBs = []
        
        for enum,xcor in enumerate(xcoords[1:]):
            width = xcoords[1] - xcoords[0]
            if enum % 2 == 0:
                fc = 'white'
            else:
                fc = 'black'


            Rect = Rectangle((0,0),width,fc=fc,edgecolor='k',zorder=99+enum)
            
            ATB = AuxTransformBox(transform)

            ATB.add_artist(Rect)

            xlabel = xlabels[enum]

            xlabel = int(xlabel)
            
            Txt_xlabel = TextArea(xlabel,textprops=dict(fontsize=fontsize),minimumdescent=True)

            # vertically packing a single stripe with respective label

            child = VPacker(children=[Txt_xlabel,ATB],align="right",pad=5,sep=0)

            # TODO: add legend to the child
            # If we use ATBs.append(ATB),the resultant scalebar will have
            # no ticks next to each strap

            # If we use ATBs.append(child),there will be ticks. Though
            # there will be spaces between each strap.

            # While there is no solution for the problem,I am suggesting
            # the first case scenario

            # Therefore (TODO): add legend to the child
            ATBs.append(child)

        # horizontally packing all child packs in a single offsetBox

        Children = HPacker(children=list(ATBs),pad=0,sep=0)

        Txt = TextArea('Km',minimumdescent=False)

        child = VPacker(children=[Children,Txt],align="center",pad=2,sep=2)

        AnchoredOffsetbox.__init__(self,loc='center left',borderpad=borderpad,child=child,prop=prop,frameon=False,**kwargs)


def _add_scalebar(ax,bbox_to_anchor=(0.2,0.5),bbox_transform='axes fraction',**kwargs):
    """ Add scalebars to axes
    Adds a set of scale bars to *ax*,matching the size
    to the ticks of the plot
    and optionally hiding the x and y axes
    - ax : the axis to attach ticks to
    - matchx,matchy : if True,set size of scale bars to spacing
    between ticks
                    if False,size should be set using sizex and
                    sizey params

    - hidex,hidey : if True,hide x-axis and y-axis of parent

    - **kwargs : additional arguments passed to AnchoredScaleBars

    Returns
        created scalebar object
    """

    blended_transform = transforms.blended_transform_factory(
        ax.transData,ax.get_figure().dpi_scale_trans)

    sb = AnchoredScaleBar(ax,blended_transform,xlabels=xlabels,ylabels=ylabels,fontsize=fontsize,bbox_transform=ax.transAxes,bbox_to_anchor=bbox_to_anchor,**kwargs)

    sb.set_clip_on(False)
    ax.add_artist(sb)

    return sb


def get_unit_converter(unit):

    lookuptable = {'km': 1000,'mi': 1.60934 * 1000,'dm': 1e-1,'cm': 1e-2,'mm': 1e-3,'um': 1e-6,'nm': 1e-9}  # Miles to Km

    return lookuptable.get(unit,'km')



def _point_along_line(ax,start,distance,projected=False,verbose=False):
    """Point at a given distance from start at a given angle.

    Args:
        ax:       CartoPy axes.
        start:    Starting point for the line in data coordinates.
        distance: Positive physical distance to travel in meters.
        angle:    Anti-clockwise angle for the bar,in degrees. Default: 0

    Returns:
        (lon,lat) coords of a point (a (2,1)-shaped NumPy array)
    """

    # Direction vector of the line in axes coordinates.

    if not projected:

        geodesic = cgeo.Geodesic()

        Direct_R = geodesic.direct(start,90,distance)

        target_longitude,target_latitude,forw_azi = Direct_R.base.T

        target_point = ([target_longitude[0],target_latitude[0]])

        actual_dist = geodesic.inverse(start,target_point).base.ravel()[0]
        if verbose:

            print('Starting point',start)

            print('target point',target_point)
            print('Expected distance between points: ',distance)

            print('Actual distance between points: ',actual_dist)

    if projected:

        longitude,latitude = start

        target_longitude = longitude + distance

        target_point = (target_longitude,latitude)

        if verbose:
            print('Axes is projected? ',projected)
            print('Expected distance between points: ',target_longitude - longitude)

    return start,target_point



def fancy_scalebar(ax,bbox_to_anchor,length,unit_name='km',dy=5,max_stripes=5,ytick_label_margins=0.25,fontsize=8,font_weight='bold',rotation=45,zorder=999,paddings={'xmin': 0.1,'xmax': 0.1,'ymin': 0.3,'ymax': 0.8},bbox_kwargs={'facecolor': 'w','edgecolor': 'k','alpha': 0.7},numeric_scale_bar=True,numeric_scale_bar_kwgs={'x_text_offset': 0,'y_text_offset': -40,'box_x_coord': 0.5,'box_y_coord': 0.01},verbose=False):
    '''
    Description

    ----------
        This function draws a scalebar in the given geoaxes.

    Parameters
    ----------
        ax (geoaxes):

        bbox_to_anchor (length 2 tuple):
            It sets where the scalebar will be drawn
            in axes fraction units.

        length (float):
            The distance in geodesic meters that will be used
            for generating the scalebar.

        unit_name (str):
            Standard (km).


        angle (int or float): in azimuth degrees.
            The angle that will be used for evaluating the scalebar.

            If 90 (degrees),the distance between each tick in the
            scalebar will be evaluated in respect to the longitude
            of the map.

            If 0 (degrees),the ticks will be evaluated in accordance
            to variation in the latitude of the map.

        dy (int or float):
            The hight of the scalebar in axes fraction.

        max_stripes (int):
            The number of stripes present in the scalebar.

        ytick_label_margins (int or float):
            The size of the margins for drawing the scalebar ticklabels.

        fontsize (int or float):
            The fontsize used for drawing the scalebar ticklabels.

        font_weight (str):
            the fontweight used for drawing the scalebar ticklabels.

        rotation (int or float):
            the rotation used for drawing the scalebar ticklabels.

        zorder(int):
            The zorder used for drawing the scalebar.

        paddings (dict):
            A dictionary defining the padding to draw a background box
            around the scalebar.

            Example of allowed arguments for padding:
                {'xmin': 0.3,'xmax': 0.3,'ymax': 0.3}

        bbox_kwargs (dict):
            A dictionary defining the background box
            around the scalebar.

            Example of allowed arguments for padding:
                {'facecolor': 'w','alpha': 0.7}

        numeric_scale_bar(bool):
            whether or not to draw a number scalebar along side the
            graphic scalebar. Notice that this option can drastically
            vary in value,depending on the geoaxes projection used.

        numeric_scale_bar_kwgs (dict):
            A dictionary defining the numeric scale bar.

            Example of allowed arguments:
                {'x_text_offset': 0,'box_y_coord': 0.01}

    Returns
    ----------
    None
    '''

    proj_units = ax.projection.proj4_params.get('units','degrees')
    if proj_units.startswith('deg'):
        projected = False

    elif proj_units.startswith('m'):
        projected = True

    # getting the basic unit converter for labeling the xticks
    unit_converter = get_unit_converter(unit_name)

    if verbose:
        print('Axes is projected? ',projected)

    # Convert all units and types.
   
    # Map central XY data coordinates
    x0,x1,y0,y1 = ax.get_extent()

    central_coord_map = np.mean([[x0,x1],[y0,y1]],axis=1).tolist()

    # End-point of bar in lon/lat coords.
    start,end = _point_along_line(ax,central_coord_map,projected=projected,verbose=verbose)

    # choose exact X points as sensible grid ticks with Axis 'ticker' helper
    xcoords = np.empty(max_stripes + 1)
    xlabels = []

    xcoords[0] = start[0]

    ycoords = np.empty_like(xcoords)

    for i in range(0,max_stripes):

        startp,endp = _point_along_line(ax,length * (i + 1),projected=projected)

        xcoords[i + 1] = endp[0]

        ycoords[i + 1] = end[1]

        label = round(length * (i + 1) / unit_converter)

        xlabels.append(label)

    # Stacking data coordinates (the target ticks of the scalebar) in a list

    scalebar = _add_scalebar(ax,dy,bbox_to_anchor=bbox_to_anchor)

    return scalebar,xlabels
    
if '__main__' == __name__:
    

    
    def test_scalebar():
        """Test"""

        fig,axes = plt.subplots(2,2,subplot_kw={'projection':
                                             ccrs.Mercator()})
        
        projections = [ccrs.Mercator(),ccrs.PlateCarree(),ccrs.Mercator(),ccrs.PlateCarree()]

        axes = axes.ravel()
        
        scalebars = []
        for enum,(proj,ax) in enumerate(zip(projections,axes)):
            ax.projection = proj
            ax.set_title(str(proj).split(' ')[0].split('.')[-1])
            
            if enum>=2:
                length = 200_000
            else:
                length = 2_000_000
            scalebar,xlabels = fancy_scalebar(ax,0.2),length=length,max_stripes=4,fontsize=10,dy=0.05)
            
            scalebars.append(scalebar)
            
            gl = ax.gridlines(draw_labels=True)
        
            gl.top_labels = False
            gl.right_labels = False

            ax.stock_img()
            ax.coastlines()
            
            if enum>=2:
                ax.set_extent([-70,-45,-15,10])
            
            
        
        plt.tight_layout()
        
        return axes,scalebars,xlabels

    axes,xlabels = test_scalebar()

这是带有相应地图的结果图(每个地图都有给定的投影/扩展)。

注意比例尺补丁之间的间隙。

Maps with gaps within the respective scalebars


观察 1:

经过一些尝试后,我注意到如果第 86 行(其中声明为“ATBs.append(child)”)更改为“ATBs.append(ATB)”,则比例尺会正确放置而没有间隙。

>

然而,如果这样做,比例尺将丢失每个相应补丁的所有刻度标签(黑白矩形)。


这是第二种情况的图:

注意比例尺上方缺少刻度标签

Map with correct scalebars,though without proper ticklabeling

感谢所有帮助。

真诚的

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-