python 回调子集几何图形数据-绘制虚线

63lcw9qa  于 2023-03-16  发布在  Python
关注(0)|答案(2)|浏览(121)

我希望包含一个下拉栏和回调函数,允许用户在较小的区域内显示特定的点。最初,我希望使用所有的点几何数据作为默认值。然后,我的目标是包含一个下拉栏和回调函数,从这个主df返回较小的子集。这是通过合并特定多边形区域内的点数据来实现的。
使用下面的,默认df标记为gdf_all。它包含大区域的点数据。较小的多边形文件是gdf_poly的子集。包括非洲和欧洲大陆。这些文件用于函数中,仅当点数据与多边形形状相交时才返回点数据。
我已经硬编码了下面的输出。1)使用gdf_all和2)使用来自非洲大陆的子集。

理想情况下,下拉栏将用于输入所需的点数据,以便在图中显示。

import geopandas as gpd
import plotly.express as px
import dash
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc

# point data
gdf_all = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))

# polygon data
gdf_poly = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
gdf_poly = gdf_poly.drop('name', axis = 1)

gdf_all['LON'] = gdf_all['geometry'].x
gdf_all['LAT'] = gdf_all['geometry'].y

# subset African continent
Afr_gdf_area = gdf_poly[gdf_poly['continent'] == 'Africa'].reset_index(drop = True)

# subset European continent
Eur_gdf_area = gdf_poly[gdf_poly['continent'] == 'Europe'].reset_index(drop = True)

# function to merge point data within selected polygon area
def merge_withinboundary(gdf1, gdf2):

    # spatial join data within larger boundary
    gdf_out = gpd.sjoin(gdf1, gdf2, predicate = 'within', how = 'left').reset_index(drop = True)

    return gdf_out

gdf_Africa = merge_withinboundary(gdf_all, Afr_gdf_area)
gdf_Europe = merge_withinboundary(gdf_all, Eur_gdf_area)

external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]

app = dash.Dash(__name__, external_stylesheets = external_stylesheets)

# function to return selected df for plotting
def update_dataset(df):

    if df == 'gdf_Africa':
        gdf = gdf_Africa

    elif df == 'gdf_Europe':
        gdf = gdf_Europe

    else:
        gdf = gdf_all

    return gdf

nav_bar =  html.Div([
     html.P("area-dropdown:"),
     dcc.Dropdown(
       id='data', 
       value='data', 
       options=[{'value': 'gdf_all', 'label': 'gdf_all'},
            {'value': 'gdf_Africa', 'label': 'gdf_Africa'},
            {'value': 'gdf_Europe', 'label': 'gdf_Europe'}
            ],
       clearable=False
  ),
])

# output 1
df = gdf_all

# output 2
#df = gdf_Africa

scatter = px.scatter_mapbox(data_frame = df, 
                                   lat = 'LAT', 
                                   lon = 'LON',
                                   zoom = 2,
                                   mapbox_style = 'carto-positron', 
                                   )

count = df['name'].value_counts()

bar = px.bar(x = count.index, 
              y = count.values, 
              color = count.index, 
              )

app.layout = dbc.Container([
    dbc.Row([
        dbc.Col(html.Div(nav_bar), width=2),
        dbc.Col([
            dbc.Row([
                dbc.Col(dcc.Graph(figure = scatter))
            ]),
            dbc.Row([
                dbc.Col(dcc.Graph(figure = bar))
            ]),
        ], width=5),
        dbc.Col([
        ], width=5),
    ])
], fluid=True)

if __name__ == '__main__':
    app.run_server(debug=True, port = 8051)

产出1:

产出2:

nc1teljy

nc1teljy1#

我有一个部分可行的解决方案,但是需要做很多修改。当你使用px.choropleth创建一个地区分布图时,geojson参数必须是(GeoJSON-formatted dict) – Must contain a Polygon feature collection, with IDs, which are references from locations(来自documentation)--传递geojson = A_gdf_area是行不通的,因为A_gdf_areaGeoDataFrame
此外,geojson文件需要包含位置和颜色的列,以便在区域分布图上使用颜色-我不知道您打算使用哪些列来为Map着色,所以我创建了名为idsvals的虚拟列,用于提供位置和颜色。
由于您正在绘制意大利的区域(而px.choropleth只绘制世界各国或美国各州的边界),因此您需要首先使用从geojson中提取的latslons创建一个go.Scattergeo图形,然后将choropleth数据作为跟踪添加到该图形(此想法的功劳归于plotly论坛上的this answer)。
我已经把这个整合到下面的dash应用程序中,唯一的问题是vals列对应的颜色似乎没有像我期望的那样呈现在图中--当我弄清楚原因时,我会更新这个答案。

import numpy as np
import pandas as pd
import geopandas as gpd
import json
import plotly.express as px
import plotly.graph_objs as go
import dash
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

gdf_area = gpd.read_file('https://raw.githubusercontent.com/openpolis/geojson-italy/master/geojson/limits_IT_municipalities.geojson')

np.random.seed(42)
gdf_area['ids'] = np.random.choice([1,2,3], size=len(gdf_area))
gdf_area['vals'] = np.random.uniform(1, 10, size=len(gdf_area))

A_gdf_area = gdf_area[gdf_area['name'].str.startswith('A', na=False)][['name','geometry','ids','vals']]
B_gdf_area = gdf_area[gdf_area['name'].str.startswith('B', na=False)][['name','geometry','ids','vals']]
C_gdf_area = gdf_area[gdf_area['name'].str.startswith('C', na=False)][['name','geometry','ids','vals']]

dropdown_dict = {
    'A_gdf_area': A_gdf_area,
    'B_gdf_area': B_gdf_area,
    'C_gdf_area': C_gdf_area
}

def state_boundaries(geojdata):
    pts = []#list of points defining boundaries of polygons, pts has as coordinates the lon and lat
    for feature in geojdata['features']:
        if feature['geometry']['type'] == 'Polygon':
            pts.extend(feature['geometry']['coordinates'][0])    
            pts.append([None, None])#mark the end of a polygon   
        
        elif feature['geometry']['type'] == 'MultiPolygon':
            for polyg in feature['geometry']['coordinates']:
                pts.extend(polyg[0])
                pts.append([None, None])#end of polygon
        elif feature['geometry']['type'] == 'LineString': 
            pts.extend(feature['geometry']['coordinates'])
            pts.append([None, None])
        else: pass           
    #else: raise ValueError("geometry type irrelevant for map")
    lons, lats = zip(*pts) 
    return lons, lats

def create_choropleth_figure(gdf_area):
    geojson_string = gdf_area.to_json()
    geojson = json.loads(geojson_string)

    lons, lats = state_boundaries(geojson)
    fig = go.Figure(go.Scattergeo(lon=lons, lat=lats, mode="lines", line_width=1, line_color="black"))
    fig.update_layout(width=700, height=750,
        geo = dict(
            scope = 'europe',
            resolution = 110,  #for info help(go.layout.Geo.resolution)
            lataxis_range = [33, 48],
            lonaxis_range = [5, 20],
            landcolor = 'rgb(225, 225, 225)',
        ))

    reduced_geojson={"type": "FeatureCollection", "features":[]} 
    for feat in geojson["features"]:
        reduced_geojson["features"].append(feat)
    
    figc = px.choropleth(gdf_area, geojson=reduced_geojson, locations='ids', 
                                color="vals", color_continuous_scale="viridis")
    fig.add_trace(figc.data[0])
    return fig

def merge_withinboundary(gdf1, gdf2):

    # spatial join data within larger boundary
    gdf_out = gpd.sjoin(gdf1, gdf2, predicate = 'within', how = 'left').reset_index(drop = True)
    gdf_out['ids'] = gdf_out['ids_left']
    gdf_out['vals'] = gdf_out['vals_left']
    gdf_out.drop(columns=['ids_left','ids_right','vals_left','vals_right'], inplace=True)
    return gdf_out

gdf_A = merge_withinboundary(A_gdf_area, gdf_area)
fig = create_choropleth_figure(gdf_A)

external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]

app = dash.Dash(__name__, external_stylesheets = external_stylesheets)

nav_bar =  html.Div([
     html.P("area-dropdown:"),
     dcc.Dropdown(
       id='gdf', 
       value='Site', 
       options=[{'value': x, 'label': x} 
                for x in ['A_gdf_area', 'B_gdf_area', 'C_gdf_area']],
       clearable=False
   ),
])

app.layout = dbc.Container([
    dbc.Row([
        dbc.Col(html.Div(nav_bar), width=2),
        dbc.Col([
            dbc.Row([
                dbc.Col(dcc.Graph(id = 'choropleth-fig', figure = fig))
            ]),
        ], width=5),
        dbc.Col([
        ], width=5),
    ])
], fluid=True)

@app.callback(Output('choropleth-fig', 'figure'),
              Input('gdf', 'value'),
              prevent_initial_call=True)
def update_choropleth(dropdown_value):
    if dropdown_value is None:
        return dash.no_update
    
    selected_gdf_area = dropdown_dict[dropdown_value]
    new_gdf_area = merge_withinboundary(selected_gdf_area, gdf_area)
    
    fig = create_choropleth_figure(new_gdf_area)

    return fig

if __name__ == '__main__':
    app.run_server(debug=True, port = 8051)

enxuqcxy

enxuqcxy2#

尝试下面的方法。这会修改下拉列表中的选项列表以包含较小数据框的名称。然后,它会创建一个回调函数,该函数从下拉列表中获取所选值,并使用该值过滤主数据框以获得相应的较小数据框。

import pandas as pd
import geopandas as gpd
import plotly.express as px
import plotly.graph_objs as go
import dash
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

gdf_area = gpd.read_file('https://raw.githubusercontent.com/openpolis/geojson-italy/master/geojson/limits_IT_municipalities.geojson')

A_gdf_area = gdf_area[gdf_area['name'].str.startswith('A', na=False)][['name','geometry']]
B_gdf_area = gdf_area[gdf_area['name'].str.startswith('B', na=False)][['name','geometry']]
C_gdf_area = gdf_area[gdf_area['name'].str.startswith('C', na=False)][['name','geometry']]

def merge_withinboundary(gdf1, gdf2):
    # spatial join data within larger boundary
    gdf_out = gpd.sjoin(gdf1, gdf2, predicate = 'within', how = 'left').reset_index(drop = True)
    return gdf_out

gdf_A = merge_withinboundary(A_gdf_area, gdf_area)
gdf_B = merge_withinboundary(B_gdf_area, gdf_area)
gdf_C = merge_withinboundary(C_gdf_area, gdf_area)

external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]

app = dash.Dash(__name__, external_stylesheets = external_stylesheets)

nav_bar =  html.Div([
     html.P("area-dropdown:"),
     dcc.Dropdown(
       id='gdf', 
       value='A_gdf_area', 
       options=[{'value': 'A_gdf_area', 'label': 'A_gdf_area'},
                {'value': 'B_gdf_area', 'label': 'B_gdf_area'},
                {'value': 'C_gdf_area', 'label': 'C_gdf_area'}],
       clearable=False
   ),
])

@app.callback(
    Output('choropleth', 'figure'),
    Input('gdf', 'value')
)
def update_choropleth(selected_gdf):
    if selected_gdf == 'A_gdf_area':
        gdf = gdf_A
    elif selected_gdf == 'B_gdf_area':
        gdf = gdf_B
    else:
        gdf = gdf_C

    fig = px.choropleth(gdf, geojson=gdf.__geo_interface__, 
                        color='name',
                        color_discrete_sequence=px.colors.qualitative.Pastel)
    return fig

app.layout = dbc.Container([
    dbc.Row([
        dbc.Col(html.Div(nav_bar), width=2),
        dbc.Col([
            dbc.Row([
                dbc.Col(dcc.Graph(id='choropleth'))
            ]),
        ], width=10),
    ])
], fluid=True)

if __name__ == '__main__':
    app.run_server(debug=True, port=8051)

因此,它添加了三个新的数据框gdf_A、gdf_B和gdf_C,它们是在相应的较小数据框上调用merge_withinboundary的结果。此外,下拉列表中的选项列表被修改为包括名称。希望这能有所帮助!

相关问题