Add support for selecting/editing multiple features
giswqs opened this issue · 3 comments
giswqs commented
Selecting and highlight multiple features
import os
import json
import requests
import copy
from ipyleaflet import Map, GeoJSON, LayersControl
def create_geojson_map(data, style, hover_style, highlight_style, center=(50.6252978589571, 0.34580993652344), zoom=4):
"""
Create a GeoJSON map with click highlighting functionality.
Parameters:
- data: GeoJSON data
- style: Default style for the GeoJSON layer
- hover_style: Style when hovering over features
- highlight_style: Style to apply when a feature is clicked
- center: Initial center of the map (default: Europe)
- zoom: Initial zoom level (default: 4)
"""
# Create the map
m = Map(center=center, zoom=zoom, scroll_wheel_zoom=True)
# Function to highlight the clicked feature
def highlight_feature(event, feature, **kwargs):
original_data = copy.deepcopy(geo_json.data)
for index, f in enumerate(original_data['features']):
if f['properties']['name'] == feature['properties']['name']:
if "fillColor" in original_data['features'][index]['properties']['style']:
color = original_data['features'][index]['properties']['style']['fillColor']
if color == "yellow":
original_data['features'][index]['properties']['style'] = style
else:
original_data['features'][index]['properties']['style'] = highlight_style
else:
original_data['features'][index]['properties']['style'] = highlight_style
break
geo_json.data = original_data
# Create the GeoJSON layer
geo_json = GeoJSON(
data=data,
style=style,
hover_style=hover_style,
name='Countries'
)
# Add click event to highlight features
geo_json.on_click(highlight_feature)
# Add the GeoJSON layer to the map
m.add_layer(geo_json)
# Add a layer control
control = LayersControl(position='topright')
m.add_control(control)
return m
# Download the GeoJSON file if it doesn't exist
if not os.path.exists('europe_110.geo.json'):
url = 'https://raw.githubusercontent.com/jupyter-widgets/ipyleaflet/master/examples/europe_110.geo.json'
r = requests.get(url)
with open('europe_110.geo.json', 'w') as f:
f.write(r.content.decode("utf-8"))
# Load the GeoJSON data
with open('europe_110.geo.json', 'r') as f:
data = json.load(f)
# Default styles
style = {"color": "#3388ff"}
hover_style = {'color': 'yellow', 'dashArray': '0', 'fillOpacity': 0.3}
highlight_style = {'color': '#3388ff', 'fillColor': 'yellow', 'weight': 3, 'fillOpacity': 0.5}
# Create and display the map
m = create_geojson_map(data, style, hover_style, highlight_style)
m
giswqs commented
import os
import json
import requests
import copy
import ipywidgets as widgets
from ipyleaflet import Map, GeoJSON, LayersControl, WidgetControl
from ipywidgets import VBox
def create_geojson_map(data, style, hover_style, highlight_style, display_props=None, center=(50.6252978589571, 0.34580993652344), zoom=4):
"""
Create a GeoJSON map with hover and click functionality, dynamic attribute editors, and a clear selection button.
The attribute editor will be displayed inside the map using ipyleaflet's WidgetControl.
Parameters:
- data: GeoJSON data
- style: Default style for the GeoJSON layer
- hover_style: Style when hovering over features
- highlight_style: Style to apply when a feature is clicked
- display_props: List of attribute keys to display in the widgets (default: show all attributes)
- center: Initial center of the map (default: Europe)
- zoom: Initial zoom level (default: 4)
"""
# Create the map
m = Map(center=center, zoom=zoom, scroll_wheel_zoom=True)
# List to store the IDs of highlighted features
highlighted_features = []
# Create a dictionary to hold attribute widgets
attribute_widgets = {}
# Get the keys from the first feature to dynamically create widgets
first_feature = data['features'][0]['properties']
# If display_props is not provided, show all attributes
if display_props is None:
display_props = first_feature.keys()
# Loop through only the specified properties in display_props
for key in display_props:
if key in first_feature: # Ensure the property exists
attribute_widgets[key] = widgets.Text(description=f"{key.capitalize()}:")
# value = first_feature[key]
# if isinstance(value, str):
# attribute_widgets[key] = widgets.Text(description=f"{key.capitalize()}:")
# elif isinstance(value, int):
# attribute_widgets[key] = widgets.IntText(description=f"{key.capitalize()}:")
# elif isinstance(value, float):
# attribute_widgets[key] = widgets.FloatText(description=f"{key.capitalize()}:")
# Add more types as needed (e.g., bool, list, etc.)
# Update button and clear selection button
update_button = widgets.Button(description="Update Attributes")
clear_button = widgets.Button(description="Clear Selection")
# Function to highlight the clicked feature and clear attribute fields
def highlight_feature(event, feature, **kwargs):
nonlocal highlighted_features
original_data = copy.deepcopy(geo_json.data)
for index, f in enumerate(original_data['features']):
if f['properties']['name'] == feature['properties']['name']:
if index in highlighted_features:
highlighted_features.remove(index)
original_data['features'][index]['properties']['style'] = style
else:
highlighted_features.append(index)
original_data['features'][index]['properties']['style'] = highlight_style
# Clear the widget fields when a feature is clicked
for key, widget in attribute_widgets.items():
widget.value = ''
widget.placeholder = ""
# if type(widget) == widgets.Text:
# widget.value = ''
# widget.placeholder = "ABD"
# elif type(widget) == widgets.IntText:
# widget.value = 0
# elif type(widget) == widgets.FloatText:
# widget.value = 0
geo_json.data = original_data
# Function to clear the selection
def clear_selection(_):
original_data = copy.deepcopy(geo_json.data)
# Reset the style for all highlighted features
for index in highlighted_features:
original_data['features'][index]['properties']['style'] = style
highlighted_features.clear()
geo_json.data = original_data
# Function to apply changes to highlighted features
def update_highlighted_features(_):
original_data = copy.deepcopy(geo_json.data)
# Update the properties for all highlighted features
for index in highlighted_features:
for key, widget in attribute_widgets.items():
original_data['features'][index]['properties'][key] = widget.value
geo_json.data = original_data
clear_selection(None)
for key, widget in attribute_widgets.items():
widget.value = ""
# Function to populate attribute fields on hover
def populate_hover_attributes(event, feature, **kwargs):
# Populate the widget fields with the hovered feature's attributes
for key, widget in attribute_widgets.items():
# widget.value = feature['properties'].get(key, '')
if widget.value.strip() == "":
widget.value = ""
widget.placeholder = str(feature['properties'].get(key, ''))
# Create the GeoJSON layer
geo_json = GeoJSON(
data=data,
style=style,
hover_style=hover_style,
name='Countries'
)
# Add click event to highlight features and clear attribute fields
geo_json.on_click(highlight_feature)
# Add hover event to populate attribute fields
geo_json.on_hover(populate_hover_attributes)
# Add the GeoJSON layer to the map
m.add_layer(geo_json)
# Add a layer control
control = LayersControl(position='topright')
m.add_control(control)
# Add event listeners to the buttons
update_button.on_click(update_highlighted_features)
clear_button.on_click(clear_selection)
# Create a VBox to hold the widgets for editing attributes and the buttons
attribute_editor = VBox([*attribute_widgets.values(), update_button, clear_button])
# Embed the attribute editor inside the map using WidgetControl
widget_control = WidgetControl(widget=attribute_editor, position='topright')
m.add_control(widget_control)
return m
# Download the GeoJSON file if it doesn't exist
if not os.path.exists('europe_110.geo.json'):
url = 'https://raw.githubusercontent.com/jupyter-widgets/ipyleaflet/master/examples/europe_110.geo.json'
r = requests.get(url)
with open('europe_110.geo.json', 'w') as f:
f.write(r.content.decode("utf-8"))
# Load the GeoJSON data
with open('europe_110.geo.json', 'r') as f:
data = json.load(f)
# Default styles
style = {"color": "#3388ff"}
hover_style = {'color': 'yellow', 'dashArray': '0', 'fillOpacity': 0.3}
highlight_style = {'color': '#3388ff', 'fillColor': 'yellow', 'weight': 3, 'fillOpacity': 0.5}
# Example usage: Specify certain properties to display
display_props = ['name', 'pop_est']
# Create and display the map with the widget control
m = create_geojson_map(data, style, hover_style, highlight_style, display_props=display_props)
m
giswqs commented
import os
import json
import requests
import copy
import ipywidgets as widgets
from ipyleaflet import Map, GeoJSON, LayersControl, WidgetControl
# from ipywidgets import VBox, HBox
import ipywidgets as widgets
def create_geojson_map(data, style, hover_style, highlight_style, changed_style, display_props=None, center=[40.71278477995827, -73.99725437164308], zoom=14):
"""
Create a GeoJSON map with hover and click functionality, dynamic attribute editors, and a clear selection button.
The attribute editor will be displayed inside the map using ipyleaflet's WidgetControl.
Parameters:
- data: GeoJSON data
- style: Default style for the GeoJSON layer
- hover_style: Style when hovering over features
- highlight_style: Style to apply when a feature is clicked
- display_props: List of attribute keys to display in the widgets (default: show all attributes)
- center: Initial center of the map (default: Europe)
- zoom: Initial zoom level (default: 4)
"""
# Create the map
m = Map(center=center, zoom=zoom, scroll_wheel_zoom=True)
# List to store the IDs of highlighted features
highlighted_features = []
# Create a dictionary to hold attribute widgets
attribute_widgets = {}
# Get the keys from the first feature to dynamically create widgets
first_feature = data['features'][0]['properties']
# If display_props is not provided, show all attributes
if display_props is None:
display_props = first_feature.keys()
text_width = "250px"
text_layout = widgets.Layout(width=text_width)
# Loop through only the specified properties in display_props
for key in display_props:
if key in first_feature: # Ensure the property exists
attribute_widgets[key] = widgets.Text(description=f"{key.capitalize()}:", layout=text_layout)
# value = first_feature[key]
# if isinstance(value, str):
# attribute_widgets[key] = widgets.Text(description=f"{key.capitalize()}:")
# elif isinstance(value, int):
# attribute_widgets[key] = widgets.IntText(description=f"{key.capitalize()}:")
# elif isinstance(value, float):
# attribute_widgets[key] = widgets.FloatText(description=f"{key.capitalize()}:")
# Add more types as needed (e.g., bool, list, etc.)
# Update button and clear selection button
button_width = "80px"
button_layout = widgets.Layout(width=button_width)
update_button = widgets.Button(description="Update", layout=button_layout)
clear_button = widgets.Button(description="Clear", layout=button_layout)
close_button = widgets.Button(description="Close", layout=button_layout)
# Function to highlight the clicked feature and clear attribute fields
def highlight_feature(event, feature, **kwargs):
nonlocal highlighted_features
original_data = copy.deepcopy(geo_json.data)
for index, f in enumerate(original_data['features']):
if f == feature:
# if f['properties']['name'] == feature['properties']['name']:
if index in highlighted_features:
highlighted_features.remove(index)
original_data['features'][index]['properties']['style'] = style
else:
highlighted_features.append(index)
original_data['features'][index]['properties']['style'] = highlight_style
# Clear the widget fields when a feature is clicked
# for key, widget in attribute_widgets.items():
# widget.value = ''
# widget.placeholder = ""
# if type(widget) == widgets.Text:
# widget.value = ''
# widget.placeholder = "ABD"
# elif type(widget) == widgets.IntText:
# widget.value = 0
# elif type(widget) == widgets.FloatText:
# widget.value = 0
geo_json.data = original_data
# Function to clear the selection
def clear_selection(_):
original_data = copy.deepcopy(geo_json.data)
# Reset the style for all highlighted features
for index in highlighted_features:
if original_data['features'][index]['properties']['style'] != changed_style:
original_data['features'][index]['properties']['style'] = style
highlighted_features.clear()
geo_json.data = original_data
# Function to apply changes to highlighted features
def update_highlighted_features(_):
original_data = copy.deepcopy(geo_json.data)
# Update the properties for all highlighted features
for index in highlighted_features:
for key, widget in attribute_widgets.items():
if widget.value.strip() != "":
dtype = type(original_data['features'][index]['properties'][key])
# original_data['features'][index]['properties'][key] = widget.value
if dtype == str:
value = str(widget.value)
elif dtype == int:
value = int(widget.value)
elif dtype == float:
value = float(widget.value)
else:
value = widget.value
original_data['features'][index]['properties'][key] = value
original_data['features'][index]['properties']['style'] = changed_style
geo_json.data = original_data
clear_selection(None)
for key, widget in attribute_widgets.items():
widget.value = ""
# Function to populate attribute fields on hover
def populate_hover_attributes(event, feature, **kwargs):
# Populate the widget fields with the hovered feature's attributes
for key, widget in attribute_widgets.items():
# widget.value = feature['properties'].get(key, '')
if widget.value.strip() == "":
widget.value = ""
widget.placeholder = str(feature['properties'].get(key, ''))
# Create the GeoJSON layer
geo_json = GeoJSON(
data=data,
style=style,
hover_style=hover_style,
name='Countries'
)
# Add click event to highlight features and clear attribute fields
geo_json.on_click(highlight_feature)
# Add hover event to populate attribute fields
geo_json.on_hover(populate_hover_attributes)
# Add the GeoJSON layer to the map
m.add_layer(geo_json)
# Add a layer control
control = LayersControl(position='topright')
m.add_control(control)
# Add event listeners to the buttons
update_button.on_click(update_highlighted_features)
clear_button.on_click(clear_selection)
# Create a VBox to hold the widgets for editing attributes and the buttons
buttons = widgets.HBox([update_button, clear_button, close_button])
attribute_editor = widgets.VBox([*attribute_widgets.values(), buttons])
# Embed the attribute editor inside the map using WidgetControl
widget_control = WidgetControl(widget=attribute_editor, position='topright')
m.add_control(widget_control)
def close_widget_control(_):
m.remove(widget_control)
close_button.on_click(close_widget_control)
return m
# Download the GeoJSON file if it doesn't exist
if not os.path.exists('europe_110.geo.json'):
url = 'https://github.com/opengeos/datasets/releases/download/places/nyc_buildings.geojson'
r = requests.get(url)
with open('nyc_buildings.geojson', 'w') as f:
f.write(r.content.decode("utf-8"))
# Load the GeoJSON data
with open('nyc_buildings.geojson', 'r') as f:
data = json.load(f)
# Default styles
style = {"color": "#3388ff"}
hover_style = {'color': 'yellow', 'dashArray': '0', 'fillOpacity': 0.3}
highlight_style = {'color': '#3388ff', 'fillColor': 'yellow', 'weight': 3, 'fillOpacity': 0.5}
changed_style = {'color': '#3388ff', 'fillColor': 'red', 'weight': 3, 'fillOpacity': 0.3}
# Example usage: Specify certain properties to display
display_props = ['name', 'pop_est']
# Create and display the map with the widget control
m = create_geojson_map(data, style, hover_style, highlight_style, changed_style, display_props=None)
m.layout.height="600px"
m