Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion knime_extension/knime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ author: Lingbo Liu,Xiaokang Fu,Tobias Koetter
vendor: SDL, Harvard, Cambridge US
description: Geospatial Analytics Extension for KNIME # Human readable bundle name / description
long_description: KNIME nodes for processing, analyzing and visualizing Geospatial data.
version: 2.0.0 # Version of this Python node extension
version: 2.1.0 # Version of this Python node extension
license_file: LICENSE.TXT # Best practice: put your LICENSE.TXT next to the knime.yml; otherwise you would need to change to path/to/LICENSE.txt
extension_module: src/geospatial_ext # The .py Python module containing the nodes of your extension
pixi_toml_path: pixi.toml # This is necessary for bundling, but not needed during development
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
122 changes: 122 additions & 0 deletions knime_extension/src/nodes/visualize.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import geopandas as gp
import knime_extension as knext
import util.knime_utils as knut
import pandas as pd


category = knext.category(
Expand Down Expand Up @@ -356,6 +357,67 @@ class BaseMapSettings:
],
)

disable_scroll_zoom = knext.BoolParameter(
"Disable scroll zoom",
"If checked, disables scroll wheel zoom on the map.",
default_value=False,
since_version="2.1.0",
)


@knext.parameter_group(label="Label Settings", since_version="2.1.0")
class LabelSettings:
"""Settings for displaying labels on points and polygons."""

show_labels = knext.BoolParameter(
"Show labels",
"If checked, labels will be displayed on the map for points and polygons.",
default_value=False,
)

label_column = knext.ColumnParameter(
"Label column",
"Select the column to use as labels. This column should contain text values.",
column_filter=knut.is_string,
include_row_key=False,
include_none_column=True,
).rule(knext.OneOf(show_labels, [True]), knext.Effect.SHOW)

label_color = knext.StringParameter(
"Label color",
"Select the color for the labels.",
default_value="black",
enum=[
"black",
"white",
"red",
"blue",
"green",
"orange",
"purple",
"brown",
"gray",
],
is_advanced=True,
).rule(knext.OneOf(show_labels, [True]), knext.Effect.SHOW)

label_size = knext.IntParameter(
"Label font size",
"Select the font size for the labels.",
default_value=12,
min_value=8,
max_value=24,
is_advanced=True,
).rule(knext.OneOf(show_labels, [True]), knext.Effect.SHOW)

label_weight = knext.StringParameter(
"Label font weight",
"Select the font weight for the labels.",
default_value="normal",
enum=["normal", "bold"],
is_advanced=True,
).rule(knext.OneOf(show_labels, [True]), knext.Effect.SHOW)


@knext.node(
name="Geospatial View",
Expand Down Expand Up @@ -418,6 +480,8 @@ class ViewNode:

legend_settings = LegendSettings()

label_settings = LabelSettings()

def configure(self, configure_context, input_schema):
self.geo_col = knut.column_exists_or_preset(
configure_context, self.geo_col, input_schema, knut.is_geo
Expand All @@ -437,6 +501,12 @@ def execute(self, exec_context: knext.ExecutionContext, input_table):
selected_col_names.add(self.color_settings.color_col)
if "none" not in str(self.size_settings.size_col).lower():
selected_col_names.add(self.size_settings.size_col)
if (
self.label_settings.show_labels
and self.label_settings.label_column is not None
):
selected_col_names.add(self.label_settings.label_column)

filtered_table = input_table[list(selected_col_names)]

gdf = gp.GeoDataFrame(filtered_table.to_pandas(), geometry=self.geo_col)
Expand Down Expand Up @@ -562,6 +632,58 @@ def execute(self, exec_context: knext.ExecutionContext, input_table):
kws["style_kwds"] = {"stroke": False}
map = gdf.explore(**kws)

# Disable scroll zoom if enabled
if self.basemap_setting.disable_scroll_zoom:
# Disable scroll wheel zoom directly without showing toggle button
map.options["scrollWheelZoom"] = False

# Add labels if enabled
if (
self.label_settings.show_labels
and self.label_settings.label_column is not None
):
import folium

for idx, row in gdf.iterrows():
if pd.notna(row[self.label_settings.label_column]):
# Get geometry representative point for label placement
if row[self.geo_col].geom_type == "Point":
coords = [row[self.geo_col].y, row[self.geo_col].x]
else:
# Use representative_point() instead of centroid to ensure point is inside geometry
rep_point = row[self.geo_col].representative_point()
coords = [rep_point.y, rep_point.x]

# Create a simple text label that will be added to the map
# We'll use a marker with a custom icon that contains just text
text_html = f"""
<div style="
color: {self.label_settings.label_color};
font-size: {self.label_settings.label_size}px;
font-weight: {self.label_settings.label_weight};
white-space: nowrap;
text-align: center;
background: transparent;
border: none;
">
{row[self.label_settings.label_column]}
</div>
"""

# Create a transparent marker with text
icon = folium.DivIcon(
html=text_html,
icon_size=(100, 20),
icon_anchor=(50, 10),
)

folium.Marker(
location=coords,
icon=icon,
popup=row[self.label_settings.label_column],
shadow=False,
).add_to(map)

# replace css and JavaScript paths
html = map.get_root().render()
html = replace_external_js_css_paths(
Expand Down