diff --git a/knime_extension/knime.yml b/knime_extension/knime.yml index 316bce35..99b13afe 100644 --- a/knime_extension/knime.yml +++ b/knime_extension/knime.yml @@ -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 diff --git a/knime_extension/libs/leaflet/1.9.3/images/marker-shadow.png b/knime_extension/libs/leaflet/1.9.3/images/marker-shadow.png new file mode 100644 index 00000000..9fd29795 Binary files /dev/null and b/knime_extension/libs/leaflet/1.9.3/images/marker-shadow.png differ diff --git a/knime_extension/src/nodes/visualize.py b/knime_extension/src/nodes/visualize.py index e4dc1e77..3759cf53 100644 --- a/knime_extension/src/nodes/visualize.py +++ b/knime_extension/src/nodes/visualize.py @@ -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( @@ -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", @@ -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 @@ -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) @@ -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""" +