散景映射县

时间:2016-07-12 18:06:17

标签: python-3.x pandas bokeh shapefile

我正在尝试使用密歇根州的县数据修改this example。简而言之,它正在发挥作用,但它似乎在绘制县的过程中在这里和那里增加了一些额外的形状。我猜测在某些情况下(有岛屿的县),岛屿部分需要被列为一个单独的县#34;但我不确定另一个案例,如在韦恩县的右下角部分。

这是我目前所拥有的图片: enter image description here

这是我到目前为止所做的:

  1. 从Bokeh的样本县数据中获取县数据只是为了获得每个州号的州缩写(我的第二个,主数据源只有州号)。对于这个例子,我只是通过过滤状态号26来简化它。
  2. state coordinates按县获取U.S. Census site(' 500k'文件)。
  3. 使用以下代码生成“交互式”#39;密歇根州地图。
  4. 注意:要pip install shapefile(真的是pyshp),我想我必须从here下载.whl文件,然后执行pip install [.white文件的路径]。

    import pandas as pd
    import numpy as np
    import shapefile
    from bokeh.models import HoverTool, ColumnDataSource
    from bokeh.palettes import Viridis6
    from bokeh.plotting import figure, show, output_notebook
    shpfile=r'Path\500K_US_Counties\cb_2015_us_county_500k.shp'
    sf = shapefile.Reader(shpfile)
    shapes = sf.shapes()
    
    #Here are the rows from the shape file (plus lat/long coordinates)
    rows=[]
    lenrow=[]
    for i,j in zip(sf.shapeRecords(),sf.shapes()):
        rows.append(i.record+[j.points])
        if len(i.record+[j.points])!=10:
               print("Found record with irrular number of columns")
    fields1=sf.fields[1:] #Ignore first field as it is not used (maybe it's a meta field?)
    fields=[seq[0] for seq in fields1]+['Long_Lat']#Take the first element in each tuple of the list
    c=pd.DataFrame(rows,columns=fields)
    try:
        c['STATEFP']=c['STATEFP'].astype(int)
    except:
        pass
    #cns=pd.read_csv(r'Path\US_Counties.csv')
    #cns=cns[['State Abbr.','STATE num']]
    #cns=cns.drop_duplicates('State Abbr.',keep='first')
    #c=pd.merge(c,cns,how='left',left_on='STATEFP',right_on='STATE num')
    c['Lat']=c['Long_Lat'].apply(lambda x: [e[0] for e in x]) 
    c['Long']=c['Long_Lat'].apply(lambda x: [e[1] for e in x])
    #c=c.loc[c['State Abbr.']=='MI']
    c=c.loc[c['STATEFP']==26]
    #latitudex, longitude=y
    county_xs = c['Lat']
    county_ys = c['Long']
    county_names = c['NAME']
    county_colors = [Viridis6[np.random.randint(1,6, size=1).tolist()[0]] for l in aland]
    randns=np.random.randint(1,6, size=1).tolist()[0]
    #county_colors = [Viridis6[e] for e in randns]
    #county_colors = 'b'
    source = ColumnDataSource(data=dict(
        x=county_xs,
        y=county_ys,
        color=county_colors,
        name=county_names,
        #rate=county_rates,
    ))
    
    output_notebook()
    
    TOOLS="pan,wheel_zoom,box_zoom,reset,hover,save"
    
    p = figure(title="Title", tools=TOOLS,
               x_axis_location=None, y_axis_location=None)
    p.grid.grid_line_color = None
    
    p.patches('x', 'y', source=source,
              fill_color='color', fill_alpha=0.7,
              line_color="white", line_width=0.5)
    
    hover = p.select_one(HoverTool)
    hover.point_policy = "follow_mouse"
    hover.tooltips = [
        ("Name", "@name"),
        #("Unemployment rate)", "@rate%"),
        ("(Long, Lat)", "($x, $y)"),
    ]
    
    show(p)
    

    我正在寻找避免额外线条和形状的方法。

    提前致谢!

2 个答案:

答案 0 :(得分:5)

我有一个解决这个问题的方法,我我甚至可能知道它为什么是正确的。首先,让我在Google小组Bokeh讨论中向Bryan Van de ven引用一句话:

  

没有内置支持来处理shapefile。您必须将数据转换为Bokeh理解的简单格式。 (顺便说一下:如果能够更轻松地处理各种GIS格式,那将会很棒。)

     

Bokeh期望补丁的格式是点的“列表”。如下所示:

  xs = [ [patch0 x-coords], [patch1 x-coords], ... ]
  ys = [ [patch1 y-coords], [patch1 y-coords], ... ]
     

请注意,如果修补程序由多个多边形组成,则目前通过将NaN值放在子列表中来表示。因此,任务基本上是将您拥有的任何形式的多边形数据转换为此格式,然后Bokeh可以显示它。

所以看起来你似乎忽视了NaN或者没有正确处理多个多边形。下面是一些代码,可以下载美国人口普查数据,解压缩,正确读取Bokeh,并建立纬度,长度,州和县的数据框。

def get_map_data(shape_data_file, local_file_path):
    url = "http://www2.census.gov/geo/tiger/GENZ2015/shp/" + \
      shape_data_file + ".zip"
    zfile = local_file_path + shape_data_file + ".zip"
    sfile = local_file_path + shape_data_file + ".shp"
    dfile = local_file_path + shape_data_file + ".dbf"
    if not os.path.exists(zfile):
        print("Getting file: ", url)
        response = requests.get(url)
        with open(zfile, "wb") as code:
            code.write(response.content)

    if not os.path.exists(sfile):
        uz_cmd = 'unzip ' + zfile + " -d " + local_file_path
        print("Executing command: " + uz_cmd)
        os.system(uz_cmd)

    shp = open(sfile, "rb")
    dbf = open(dfile, "rb")
    sf = shapefile.Reader(shp=shp, dbf=dbf)

    lats = []
    lons = []
    ct_name = []
    st_id = []
    for shprec in sf.shapeRecords():
        st_id.append(int(shprec.record[0]))
        ct_name.append(shprec.record[5])
        lat, lon = map(list, zip(*shprec.shape.points))
        indices = shprec.shape.parts.tolist()
        lat = [lat[i:j] + [float('NaN')] for i, j in zip(indices, indices[1:]+[None])]
        lon = [lon[i:j] + [float('NaN')] for i, j in zip(indices, indices[1:]+[None])]
        lat = list(itertools.chain.from_iterable(lat))
        lon = list(itertools.chain.from_iterable(lon))
        lats.append(lat)
        lons.append(lon)

    map_data = pd.DataFrame({'x': lats, 'y': lons, 'state': st_id, 'county_name': ct_name})
    return map_data

此命令的输入是您要将地图数据下载到的本地目录,另一个输入是形状文件的名称。我知道上面函数中的url至少有两个可用的地图你可以调用:

map_low_res = "cb_2015_us_county_20m"
map_high_res = "cb_2015_us_county_500k"

如果美国人口普查改变了他们的网址,他们肯定会有一天,那么你需要更改输入文件名和网址变量。所以,你可以调用上面的函数

map_output = get_map_data(map_low_res, ".")

然后你可以像原始问题中的代码那样绘制它。首先添加一个颜色数据列(原始问题中的“county_colors”),然后将其设置为源,如下所示:

source = ColumnDataSource(map_output)

要完成所有工作,您需要导入诸如requests,os,itertools,shapefile,bokeh.models.ColumnDataSource等库...

答案 1 :(得分:1)

一个解决方案: 使用1:20,000,000形状文件而不是1:500,000文件。 它会在每个县的形状周围丢失一些细节,但没有任何额外的形状(只有几条额外的线条)。

enter image description here