2016-07-12 17 views
1

Ich versuche, this example mit County Daten für Michigan zu ändern. Kurz gesagt, es funktioniert, aber es scheint hier und da einige zusätzliche Formen hinzuzufügen, während die Grafschaften gezeichnet werden. Ich vermute, dass in einigen Fällen (wo es Kreise mit Inseln gibt) der Insel-Teil als eine separate "Grafschaft" aufgeführt werden muss, aber ich bin mir nicht sicher über den anderen Fall, wie zum Beispiel mit Wayne County in der unteren der rechte Teil des Staates.Bokeh Mapping Counties

Hier ist ein Bild von dem, was ich habe zur Zeit: enter image description here

Hier ist, was ich bisher getan:

  1. Get Kreis Daten von Bokeh der Probe Kreisdaten nur den Zustand Abkürzung pro Zustandsnummer zu erhalten (meine zweite, Hauptdatenquelle hat nur Zustandszahlen). Für dieses Beispiel werde ich es vereinfachen, indem ich nur nach Status Nummer 26 filtere).
  2. Get state coordinates ('500k' Datei) von der Grafschaft aus der U.S. Census site.
  3. Verwenden Sie den folgenden Code, um eine "interaktive" Karte von Michigan zu generieren.

Hinweis: pip Shape-Datei installieren (wirklich pyshp), ich glaube, ich die .whl Datei von here herunterladen musste und dann installieren Sie pip [Pfad zur Datei .whl].

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) 

Ich bin auf der Suche nach einer Möglichkeit, die zusätzlichen Linien und Formen zu vermeiden.

Vielen Dank im Voraus!

Antwort

2

Ich habe eine Lösung für dieses Problem, und ich denke, Ich könnte sogar wissen, warum es richtig ist. Zuerst lassen Sie zeigen mir Zitat von Bryan van de Ven in einem Google-Gruppen Bokeh Diskussion:

gibt es keine integrierte Unterstützung mit Shape-Dateien für den Umgang. Sie müssen die Daten in das einfache Format konvertieren, das Bokeh versteht. (Nebenbei: Es wäre schön, einen Beitrag zu haben, der den Umgang mit verschiedenen GIS-Formaten erleichtert).

Das Format, das Bokeh für Patches erwartet, ist eine "Liste von Listen" von Punkten. So etwas wie:

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

Beachten Sie, dass, wenn ein Patch von mehreren Polygonen besteht, ist dies zur Zeit, indem NaN-Werte in den Teil-Listen ausgedrückt. Also, die Aufgabe ist im Grunde zu konvertieren, welche Form von Polygondaten Sie haben, um dieses Format, und dann kann Bokeh es anzeigen.

Es scheint also so, als würden Sie NaNs ignorieren oder andere Polygone nicht richtig behandeln. Hier ist ein Code, der US-Volkszählungsdaten herunterlädt, entzippt, für Bokeh richtig liest und einen Datenrahmen von lat, long, state und county erstellt.

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 

Die Eingaben in diesem Befehl sind ein lokales Verzeichnis, in dem die Kartendaten an und der andere Eingang ist der Name der Shape-Datei herunterladen möchten. Ich weiß, dass es mindestens zwei verfügbare Karten aus der URL in der Funktion über das sind, könnten Sie rufen:

map_low_res = "cb_2015_us_county_20m" 
map_high_res = "cb_2015_us_county_500k" 

Wenn die US-Volkszählung ihre URL ändert, was sie werden sicherlich einen Tag dann müssen Sie das ändern Eingabedateiname und die URL-Variable. So können Sie die Funktion über

map_output = get_map_data(map_low_res, ".") 

aufrufen Dann könnten Sie es so plotten, wie der Code in der ursprünglichen Frage tut. Fügen Sie eine Farbe Datenspalte zuerst („county_colors“ in der ursprünglichen Frage), und dann auf die Quelle wie folgt aus:

source = ColumnDataSource(map_output) 

Damit dies funktionieren Sie müssen Bibliotheken importieren wie Anfragen, os, itertools, shapefile, bokeh.models.ColumnDataSource, etc ...

0

Eine Lösung: Verwenden Sie die Shape-Datei 1: 20.000.000 anstelle der 1: 500.000-Datei. Es verliert einige Details um die Form der einzelnen Grafschaft, hat aber keine zusätzlichen Formen (und nur ein paar zusätzliche Zeilen).

enter image description here