目录

一、不同差值效果对比

二、制图代码

2.1用到的模块

2.1.1遇到的问题

2.2经纬度转shp坐标点

2.2.1遇到的问题

2.3IDW(反距离权重)

2.3.1遇到的问题

2.4利用Basemap渲染

2.4.1遇到的问题

三、结果展示

3.1arcpy效果

3.2Basemap模块效果



一、不同差值效果对比

因为arcpy不能部署在linux因此采用python第三方模块,左侧为反距离权重差值方法,右侧为最近邻,等方法,可以直观的看出空间插值,idw效果较好。

二、制图代码

2.1用到的模块

因为最终程序需要部署到linux系统服务器上,arcgis等工具只能用于windows下,因此采用python第三方包。

import os
import conda
conda_file_dir = conda.__file__
conda_dir = conda_file_dir.split('lib')[0]
proj_lib = os.path.join(os.path.join(conda_dir, 'share'), 'proj')
os.environ["PROJ_LIB"] = proj_lib
#以上代码是linux中运行可能产生报错,需要加上
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
import cx_Oracle
import shapefile as sf
from matplotlib.path import Path
from matplotlib.patches import PathPatch
import datetime
from matplotlib.font_manager import FontProperties
#windows下需要定义字体中文
font = FontProperties(fname='C:/Windows/Fonts/simsun.ttc', size=13)

import gdal
import osgeo.ogr as ogr
import osgeo.osr as osr
#linux下没有gpu的做法。
plt.switch_backend('agg')

2.1.1遇到的问题

头部导入模块代码,其中除了import,很多内容是为了防止报错。

错误一,可能是因为环境问题,安装包后会报类似No such file or directory的错误,因此需要加上

conda_file_dir = conda.__file__
conda_dir = conda_file_dir.split('lib')[0]
proj_lib = os.path.join(os.path.join(conda_dir, 'share'), 'proj')
os.environ["PROJ_LIB"] = proj_lib

错误二,matplotlib不能支持中文,试过了很多方法,指定字体文件,window和linux下通用,字体可以自己选择,只需要使用的时候,指定font,代码如下。

font = FontProperties(fname='C:/Windows/Fonts/simsun.ttc', size=13)

错误三,linux下plt模块展示可能报错,需要指定用那种方式,代码如下,参数可以自行搜素。

plt.switch_backend('agg')

2.2经纬度转shp坐标点

gdal提供的差值函数,参数为shp点数据和最终的输出结果,因此这里将读取到的经纬度数据转换为shp,当然也可选择自己写反距离权重插值的算法,不将数据转换shp点,进行插值。

def XY_Point(shp_path,data_list):
    """

    :param shp_path: 输出文件路径
    :param data_list: 包含x,y,data的数据集
    [[112.503, 34.844, 22.0], [112.09, 34.43, 21.0]]
    :return:
    """
    #定义驱动
    driver = ogr.GetDriverByName("ESRI Shapefile")
    #创建一个shp文件
    data_source = driver.CreateDataSource(shp_path)
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(4326) #这是WGS84,想用什么自己去搜下对应的编码就行了
    #创建点文件
    layer = data_source.CreateLayer("Point", srs, ogr.wkbPoint)
    #定义字段的内容,添加字段
    field_name = ogr.FieldDefn("data", ogr.OFTString)
    field_name.SetWidth(14)
    layer.CreateField(field_name)
    feature = ogr.Feature(layer.GetLayerDefn())
    for xx in data_list:
        x = xx[0]
        y = xx[1]
        data = xx[2]
        #写入数据
        feature.SetField("data", "{0}".format(str(data)))
        #生成点的固定格式
        wkt = "POINT({0} {1})".format(x, y)
        point = ogr.CreateGeometryFromWkt(wkt)
        feature.SetGeometry(point)
        layer.CreateFeature(feature)
    feature = None
    data_source = None

2.2.1遇到的问题

问题一,坐标问题,坐标简单的可以分为地理坐标系和投影坐标系,经纬度为地理坐标系,WGS84,编号为4326,坐标系一定要注意 ,因为涉及到不同模块,默认坐标参数可能不一致,使用中应该调节为一致。

srs.ImportFromEPSG(4326)

问题二,生成点数据的固定格式,矢量数据分为点、线、面、生成矢量的时候,每一种数据都有自己对应的格式,具体可以参考此链接https://blog.csdn.net/weixin_42464154/article/details/104112786

问题三,怎么将多个坐标点生成在同一个shp文件中,因为网上大多教程都是单一点生成,循环遍历,然后将最后两行代码放到循环外面。

feature = None
data_source = None

2.3IDW(反距离权重)

gdal自带模块,具体度娘有详细的解释。

def idw(output_file, point_station_file):
    """
    idw空间插值
    :param output_file:插值结果
    :param point_station_file: 矢量站点数据
    :return:
    """
    # 代码调用outputBounds定义范围
    opts = gdal.GridOptions(
        algorithm="invdistnn:power=2.0:smothing=0.0:radius=1.0:max_points=12:min_points=0:nodata=-10",
        format="GTiff", outputType=gdal.GDT_Float32, zfield="data",outputBounds=[110,31,117,37])
    gdal.Grid(destName=output_file, srcDS=point_station_file, options=opts)

2.3.1遇到的问题

问题一,怎么确定插值的范围,如果范围不能确定,Basemap中的范围虽然可以根据差值的栅格动态调整出图范围,解决坐标偏移问题,但是出图可能裁剪掉感兴趣区域,如下图所示,因此这里的范围要和Basemap裁剪的范围保持一致,调节参数中的范围即可。

outputBounds=[110,31,117,37]

2.4利用Basemap渲染

basemap这个模块确实好用,但是接触的较少,具体用法可以搜索具体开发手册一类的文件。

def chutu(name,tif):
    """

    :param name: 判断输入的数据是温度还是降水
    :param tif: 经过gdal差值后的tif文件路径
    :return:
    """
    cont=""
    ds = gdal.Open(tif)
    grid_z = ds.ReadAsArray()
    adfGeoTransform = ds.GetGeoTransform()
    shapexy = grid_z.shape
    #根据分辨率和左上角坐标计算范围
    xmx = adfGeoTransform[0] + adfGeoTransform[1] * shapexy[0]
    ymi = adfGeoTransform[3] + adfGeoTransform[-1] * shapexy[1]
    #plt.rcParams['font.sans-serif']=['SimHei']
    # 用来正常显示负号
    #plt.rcParams['axes.unicode_minus'] = False
    map = Basemap(projection = 'cyl',
    llcrnrlon=adfGeoTransform[0], llcrnrlat=adfGeoTransform[3], urcrnrlon=xmx, urcrnrlat=ymi,
    resolution = 'h')#cyl,projection = 'tmerc'

    #llcrnrlon=110, llcrnrlat=31, urcrnrlon=117, urcrnrlat=37,
    map.readshapefile(hn,'comarques',drawbounds = True)
    #制作河南的图形
    for info, shape in zip(map.comarques_info, map.comarques):
        lon, lat = zip(*shape)
    map.plot(lon, lat, marker = None, color= 'k',lineStyle='-',linewidth=0.5)#--虚线,-.省界
    map.plot(112.356, 33.476, marker = "o", color= 'r',markersize=6)#南召位置

    #南召标注

    plt.annotate('南召县', xy=(112.356, 33.476),  xycoords='data',
                    xytext=(5, 0), textcoords='offset points',
                    arrowprops=None,fontproperties=font,size=10)
    #grid_x, grid_y = np.mgrid[110:120,31:40]
    # 插值方法:'nearest', 'linear', 'cubic'
    np_data = np.array(DATA_list)
    np_w = np.array(W_list)#坐标点集合

    #grid_z = griddata(np_w, np_data, (grid_x, grid_y), method='nearest')

    x1 = np.linspace(map.llcrnrlon, map.urcrnrlon, shapexy[1])
    y1 = np.linspace(map.llcrnrlat, map.urcrnrlat, shapexy[0])
    grid_x, grid_y = np.meshgrid(x1, y1)
    if name=="TEM":
        cont = '南召县逐小时温度\n %s年%s月%s日%s时BJT' % (year, month, day, hour)
        plt.title(cont, fontproperties=font, size=13)
        levels = np.linspace(-6, 38, 50)
        cbar_levels = np.linspace(-6, 38, 20)
        # cf = map.scatter(np_x, np_y, np_data, c=np_data, cmap='jet', alpha=0.75)

        cf = map.contourf(grid_x, grid_y, grid_z, levels=levels, cmap=plt.cm.jet)

        cbar = map.colorbar(cf, location='right', format='%d℃', size=0.1,
                            ticks=cbar_levels)
        cbar.ax.tick_params(labelsize=8)  # 图例宽度
    if name=="RAIN":
        cont = '南召县日累计降水分布\n %s年%s月%s日BJT' % (year, month, day)
        plt.title(cont,fontproperties=font,size=13)
        levels = np.linspace(0, 50, 50)
        cbar_levels = [1,10,25,50,100,200]
        #cf = map.scatter(np_x, np_y, np_data, c=np_data, cmap='jet', alpha=0.75)
        # Colors是一些自选颜色列表
        Colors = ('#8FF041', '#38A800', '#00A9E6', '#005CE6','#E600A9')#'#CEFFCE', '#28FF28', '#007500', '#FFFF93', '#8C8C00', '#FFB5B5',
        # '#FF0000', '#CE0000', '#750000')

        cf = map.contourf(grid_x, grid_y, grid_z, levels=cbar_levels,colors=Colors) #norm=mpl.colors.Normalize(vmin=0, vmax=100))#cmap= plt.cm.cool)

        cbar = map.colorbar(cf, location='right', format='%dmm', size=0.1,
                           ticks=cbar_levels)
        cbar.ax.tick_params(labelsize=8)#图例宽度
    #删除边界外面的数据
    sjz = sf.Reader(hn)
    for shape_rec in sjz.shapeRecords():
        vertices = []
        codes = []
        pts = shape_rec.shape.points
        prt = list(shape_rec.shape.parts) + [len(pts)]
        for i in range(len(prt) - 1):
            for j in range(prt[i], prt[i+1]):
                vertices.append(map(pts[j][0], pts[j][1]))
            codes += [Path.MOVETO]
            codes += [Path.LINETO] * (prt[i+1] - prt[i] -2)
            codes += [Path.CLOSEPOLY]
        clip = Path(vertices, codes)
        clip = PathPatch(clip, transform=ax.transData)


    for contour in cf.collections:
        contour.set_clip_path(clip)

    plt.savefig('%s_%s.png'%(currenttime,name))

2.4.1遇到的问题

问题一,如何自定义数值以及对应颜色。比如数值1-5位黄色,数值5-10为红色,主要代码如下,修改colors和levels的参数为自己想要数值以及对应的颜色。

cbar_levels = [1,10,25,50,100,200]
Colors = ('#8FF041', '#38A800', '#00A9E6', '#005CE6','#E600A9')
cf = map.contourf(grid_x, grid_y, grid_z, levels=cbar_levels,colors=Colors)

问题二,怎么保证行政区范围外的数据为空值,具体利用了shapefile模块,这里裁剪代码是来自互联网copy,用的话复制粘贴就行了,最终效果如下图所示。

sjz = sf.Reader(hn)
    for shape_rec in sjz.shapeRecords():
        vertices = []
        codes = []
        pts = shape_rec.shape.points
        prt = list(shape_rec.shape.parts) + [len(pts)]
        for i in range(len(prt) - 1):
            for j in range(prt[i], prt[i+1]):
                vertices.append(map(pts[j][0], pts[j][1]))
            codes += [Path.MOVETO]
            codes += [Path.LINETO] * (prt[i+1] - prt[i] -2)
            codes += [Path.CLOSEPOLY]
        clip = Path(vertices, codes)
        clip = PathPatch(clip, transform=ax.transData)


 for contour in cf.collections:
 contour.set_clip_path(clip)

三、结果展示

3.1arcpy效果

arcig作为GIS专业工具,制图效果一流(哈哈)。

3.2Basemap模块效果

左侧为降水,右侧为温度,虽然和arcgis存在一定差距,但是总体上还算过的去。

 

Logo

更多推荐