From 219bcc2bfbdcd11ba98fe74f1cefdbe399bcfe85 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 3 Feb 2025 18:49:40 +0530 Subject: [PATCH 01/17] categorised symbology --- .../jupytergis_qgis/qgis_loader.py | 127 +++++++++++++++--- 1 file changed, 111 insertions(+), 16 deletions(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index 54137f5e6..ef59aa33d 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -52,6 +52,13 @@ def closeQgis(): qgs.exitQgis() +def rgb_to_hex(rgb_str): + """Converts an RGB string (comma-separated) to a hex color code.""" + rgb_values = rgb_str.split(",")[:3] + r, g, b = [int(val) for val in rgb_values] + return f"#{r:02x}{g:02x}{b:02x}" + + def qgis_layer_to_jgis( qgis_layer: QgsLayerTreeLayer, layers: dict[str, dict[str, Any]], @@ -60,6 +67,7 @@ def qgis_layer_to_jgis( ) -> str: """Load a QGIS layer into the provided layers/sources dictionary in the JGIS format. Returns the layer id or None if enable to load the layer.""" layer = qgis_layer.layer() + print("HIIIIIIIII") if layer is None: return @@ -152,6 +160,8 @@ def qgis_layer_to_jgis( minZoom=min_zoom, ) if isinstance(layer, QgsVectorLayer): + print("VECTOR LAYER") + layer_type = "VectorLayer" source_type = "GeoJSONSource" source = layer.source() @@ -166,30 +176,112 @@ def qgis_layer_to_jgis( source_parameters.update(path=file_name) renderer = layer.renderer() - symbol = renderer.symbol() - - # Opacity stuff - opacity = symbol.opacity() - alpha = hex(int(opacity * 255))[2:].zfill(2) + # Default symbol extraction + symbol = None color = {} - if isinstance(symbol, QgsMarkerSymbol): - color["circle-fill-color"] = symbol.color().name() + alpha - color["circle-stroke-color"] = symbol.color().name() + alpha - if isinstance(symbol, QgsLineSymbol): - color["stroke-color"] = symbol.color().name() + alpha + if isinstance(renderer, QgsSingleSymbolRenderer): + symbol = renderer.symbol() + layer_parameters["symbologyState"]["renderType"] = "Single Symbol" + + elif isinstance(renderer, QgsCategorizedSymbolRenderer): + case_conditions = ["case"] + field_name = renderer.classAttribute() + for category in renderer.categories(): + cat_symbol = category.symbol() + print(cat_symbol.symbolLayer(0).properties()) + opacity = cat_symbol.opacity() + + category_color = cat_symbol.color().name() + case_conditions.append(["==", ["get", field_name], category.value()]) + case_conditions.append(category_color) + + layer_parameters["symbologyState"] = { + "colorRamp": "cool", + "mode": "", + "nClasses": "", + "renderType": "Categorized", + "value": field_name, + } + case_conditions.append([0.0, 0.0, 0.0, 0.0]) + outline_color_str = ( + cat_symbol.symbolLayer(0).properties().get("outline_color", "0,0,0,255") + ) + if isinstance(cat_symbol, QgsMarkerSymbol): + color["circle-fill-color"] = case_conditions + color["circle-stroke-color"] = rgb_to_hex(outline_color_str) + elif isinstance(cat_symbol, QgsLineSymbol): + color["stroke-color"] = case_conditions + color["stroke-line-cap"] = ( + symbol.symbolLayer(0).properties().get("capstyle") + ) + color["stroke-line-join"] = ( + symbol.symbolLayer(0).properties().get("joinstyle") + ) + color["stroke-width"] = ( + symbol.symbolLayer(0).properties().get("line_width") + ) + elif isinstance(cat_symbol, QgsFillSymbol): + color["circle-fill-color"] = case_conditions + color["stroke-color"] = rgb_to_hex(outline_color_str) + + elif isinstance(renderer, QgsGraduatedSymbolRenderer): + ranges = [] + for range in renderer.ranges(): + range_symbol = range.symbol() + opacity = range_symbol.opacity() + alpha = hex(int(opacity * 255))[2:].zfill(2) + + range_color = range_symbol.color().name() + alpha + ranges.append( + { + "lower": range.lowerValue(), + "upper": range.upperValue(), + "color": range_color, + "label": range.label(), + } + ) + layer_parameters["symbologyState"] = { + "renderType": "Graduated", + "value": renderer.classAttribute(), + } - if isinstance(symbol, QgsFillSymbol): - color["fill-color"] = symbol.color().name() - color["stroke-color"] = symbol.color().name() + if symbol: + print("SYMBOL.......", symbol) + if symbol.symbolLayerCount() > 0: + print(symbol.symbolLayer(0).properties()) + + # Opacity handling + opacity = symbol.opacity() + alpha = hex(int(opacity * 255))[2:].zfill(2) + + if isinstance(symbol, QgsMarkerSymbol): + color["circle-fill-color"] = symbol.color().name() + color["circle-stroke-color"] = symbol.color().name() + + elif isinstance(symbol, QgsLineSymbol): + color["stroke-color"] = symbol.color().name() + color["stroke-line-cap"] = ( + symbol.symbolLayer(0).properties().get("capstyle") + ) + color["stroke-line-join"] = ( + symbol.symbolLayer(0).properties().get("joinstyle") + ) + color["stroke-width"] = ( + symbol.symbolLayer(0).properties().get("line_width") + ) + + elif isinstance(symbol, QgsFillSymbol): + color["circle-fill-color"] = symbol.color().name() + outline_color_str = ( + symbol.symbolLayer(0).properties().get("outline_color", "0,0,0,255") + ) + color["stroke-color"] = rgb_to_hex(outline_color_str) layer_parameters.update(type="fill") layer_parameters.update(color=color) - if isinstance(renderer, QgsSingleSymbolRenderer): - layer_parameters.update(symbologyState={"renderType": "Single Symbol"}) - if isinstance(layer, QgsVectorTileLayer): layer_type = "VectorTileLayer" source_type = "VectorTileSource" @@ -266,6 +358,9 @@ def qgis_layer_to_jgis( "parameters": source_parameters, } + print("LAYER.......", layer) + print("LAYERSS.......", layers) + return layer_id From ffad1f869c7961354cb05ca95aa175092ba29238 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 15:14:45 +0530 Subject: [PATCH 02/17] a little handling for radius --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index ef59aa33d..e205bea48 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -530,7 +530,10 @@ def create_categorized_renderer( QColor(int(color[0]), int(color[1]), int(color[2]), int(color[3] * 255)) ) - if geometry_type == "circle" and len(radius_rules) > i: + if geometry_type == "circle" and isinstance(radius_rules, (int, float)): + radius = radius_rules + category_symbol.setSize(2 * radius) + elif geometry_type == "circle" and len(radius_rules) > i: radius = radius_rules[i + 3] category_symbol.setSize(2 * radius) From ab3be178860a56946afd8bd52431059e3f1745dc Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 15:30:34 +0530 Subject: [PATCH 03/17] categorised symbology works nicely --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index e205bea48..9eefc0465 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -223,7 +223,7 @@ def qgis_layer_to_jgis( symbol.symbolLayer(0).properties().get("line_width") ) elif isinstance(cat_symbol, QgsFillSymbol): - color["circle-fill-color"] = case_conditions + color["fill-color"] = case_conditions color["stroke-color"] = rgb_to_hex(outline_color_str) elif isinstance(renderer, QgsGraduatedSymbolRenderer): From b41f8ba596ed711d49f17d8c7b91f0c535a0d550 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 18:00:07 +0530 Subject: [PATCH 04/17] phewww --- .../jupytergis_qgis/qgis_loader.py | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index 9eefc0465..a43111902 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -59,6 +59,20 @@ def rgb_to_hex(rgb_str): return f"#{r:02x}{g:02x}{b:02x}" +def hex_to_rgba(hex_color): + """Convert a hex color to an RGBA tuple.""" + hex_color = hex_color.lstrip("#") + if len(hex_color) == 6: + r, g, b = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) + a = 255 # Default alpha value + elif len(hex_color) == 8: + r, g, b, a = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4, 6)) + else: + raise ValueError(f"Invalid hex color: {hex_color}") + + return r, g, b, a + + def qgis_layer_to_jgis( qgis_layer: QgsLayerTreeLayer, layers: dict[str, dict[str, Any]], @@ -211,20 +225,23 @@ def qgis_layer_to_jgis( if isinstance(cat_symbol, QgsMarkerSymbol): color["circle-fill-color"] = case_conditions color["circle-stroke-color"] = rgb_to_hex(outline_color_str) + layer_parameters.update(type="circle") elif isinstance(cat_symbol, QgsLineSymbol): color["stroke-color"] = case_conditions color["stroke-line-cap"] = ( - symbol.symbolLayer(0).properties().get("capstyle") + cat_symbol.symbolLayer(0).properties().get("capstyle") ) color["stroke-line-join"] = ( - symbol.symbolLayer(0).properties().get("joinstyle") + cat_symbol.symbolLayer(0).properties().get("joinstyle") ) - color["stroke-width"] = ( - symbol.symbolLayer(0).properties().get("line_width") + color["stroke-width"] = float( + cat_symbol.symbolLayer(0).properties().get("line_width") ) + layer_parameters.update(type="line") elif isinstance(cat_symbol, QgsFillSymbol): color["fill-color"] = case_conditions color["stroke-color"] = rgb_to_hex(outline_color_str) + layer_parameters.update(type="fill") elif isinstance(renderer, QgsGraduatedSymbolRenderer): ranges = [] @@ -259,6 +276,7 @@ def qgis_layer_to_jgis( if isinstance(symbol, QgsMarkerSymbol): color["circle-fill-color"] = symbol.color().name() color["circle-stroke-color"] = symbol.color().name() + layer_parameters.update(type="circle") elif isinstance(symbol, QgsLineSymbol): color["stroke-color"] = symbol.color().name() @@ -271,6 +289,7 @@ def qgis_layer_to_jgis( color["stroke-width"] = ( symbol.symbolLayer(0).properties().get("line_width") ) + layer_parameters.update(type="line") elif isinstance(symbol, QgsFillSymbol): color["circle-fill-color"] = symbol.color().name() @@ -278,8 +297,8 @@ def qgis_layer_to_jgis( symbol.symbolLayer(0).properties().get("outline_color", "0,0,0,255") ) color["stroke-color"] = rgb_to_hex(outline_color_str) + layer_parameters.update(type="fill") - layer_parameters.update(type="fill") layer_parameters.update(color=color) if isinstance(layer, QgsVectorTileLayer): @@ -493,10 +512,8 @@ def get_base_symbol(geometry_type, color_params, opacity): stroke_width = color_params.get("circle-stroke-width", 1) symbol_layer.setStrokeWidth(stroke_width) elif geometry_type == "line": - stroke_color = QColor(color_params.get("stroke-color", "#000000")) - symbol_layer.setStrokeColor(stroke_color) stroke_width = color_params.get("stroke-width", 1) - symbol_layer.setWidth(stroke_width) + symbol_layer.setWidth(float(stroke_width)) elif geometry_type == "fill": stroke_color = QColor(color_params.get("stroke-color", "#000000")) symbol_layer.setStrokeColor(stroke_color) @@ -510,10 +527,13 @@ def create_categorized_renderer( symbology_state, geometry_type, color_params, base_symbol ): """Creates a categorized renderer.""" - fill_color_rules = color_params.get("circle-fill-color", []) - radius_rules = ( - color_params.get("circle-radius", []) if geometry_type == "circle" else [] - ) + if geometry_type == "circle": + fill_color_rules = color_params.get("circle-fill-color", []) + radius_rules = color_params.get("circle-radius", []) + elif geometry_type == "fill": + fill_color_rules = color_params.get("fill-color", []) + elif geometry_type == "line": + fill_color_rules = color_params.get("stroke-color", []) renderer = QgsCategorizedSymbolRenderer(symbology_state.get("value")) @@ -526,9 +546,16 @@ def create_categorized_renderer( color = [r, g, b, 1.0] category_symbol = base_symbol.clone() - category_symbol.setColor( - QColor(int(color[0]), int(color[1]), int(color[2]), int(color[3] * 255)) - ) + if isinstance(color, str) and color.startswith("#"): + r, g, b, a = hex_to_rgba(color) + elif isinstance(color, list) and len(color) == 4: + r, g, b, a = color + a *= 255 + else: + raise ValueError(f"Unexpected color format: {color}") + + category_symbol = base_symbol.clone() + category_symbol.setColor(QColor(r, g, b, int(a))) if geometry_type == "circle" and isinstance(radius_rules, (int, float)): radius = radius_rules @@ -566,12 +593,11 @@ def create_graduated_renderer( color = fill_color_rules[i + 1] if isinstance(color, list) and len(color) == 4: - r, g, b, a = color - qcolor = QColor(int(r), int(g), int(b), int(a * 255)) - last_color = qcolor + r, g, b, a = hex_to_rgba(color) + last_color = QColor(int(r), int(g), int(b), int(a * 255)) range_symbol = base_symbol.clone() - range_symbol.setColor(qcolor) + range_symbol.setColor(last_color) if geometry_type == "circle" and len(radius_rules) > i + 1: radius = radius_rules[i + 1] From cc36522bb463ee83fe6284b779c012983f15871a Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 18:26:05 +0530 Subject: [PATCH 05/17] single symbol fill --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index a43111902..4a5f66722 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -197,6 +197,9 @@ def qgis_layer_to_jgis( if isinstance(renderer, QgsSingleSymbolRenderer): symbol = renderer.symbol() + if "symbologyState" not in layer_parameters: + layer_parameters["symbologyState"] = {} + layer_parameters["symbologyState"]["renderType"] = "Single Symbol" elif isinstance(renderer, QgsCategorizedSymbolRenderer): @@ -292,7 +295,7 @@ def qgis_layer_to_jgis( layer_parameters.update(type="line") elif isinstance(symbol, QgsFillSymbol): - color["circle-fill-color"] = symbol.color().name() + color["fill-color"] = symbol.color().name() outline_color_str = ( symbol.symbolLayer(0).properties().get("outline_color", "0,0,0,255") ) From 142257f7c0e1411caf4101caf8d74a21f7a29813 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 18:37:09 +0530 Subject: [PATCH 06/17] fix for line --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index 4a5f66722..79ea47145 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -289,7 +289,7 @@ def qgis_layer_to_jgis( color["stroke-line-join"] = ( symbol.symbolLayer(0).properties().get("joinstyle") ) - color["stroke-width"] = ( + color["stroke-width"] = float( symbol.symbolLayer(0).properties().get("line_width") ) layer_parameters.update(type="line") From 8980170002d018e0bc325c63d49b2f845778d99a Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 19:02:15 +0530 Subject: [PATCH 07/17] Make graduated work --- .../jupytergis_qgis/qgis_loader.py | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index 79ea47145..c77012c1a 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -247,26 +247,62 @@ def qgis_layer_to_jgis( layer_parameters.update(type="fill") elif isinstance(renderer, QgsGraduatedSymbolRenderer): - ranges = [] + case_conditions = ["case"] + field_name = renderer.classAttribute() + for range in renderer.ranges(): range_symbol = range.symbol() opacity = range_symbol.opacity() alpha = hex(int(opacity * 255))[2:].zfill(2) - range_color = range_symbol.color().name() + alpha - ranges.append( - { - "lower": range.lowerValue(), - "upper": range.upperValue(), - "color": range_color, - "label": range.label(), - } + lower = range.lowerValue() + upper = range.upperValue() + + case_conditions.append( + [ + "all", + [">=", ["get", field_name], lower], + ["<", ["get", field_name], upper], + ] ) + case_conditions.append(range_color) + layer_parameters["symbologyState"] = { "renderType": "Graduated", - "value": renderer.classAttribute(), + "value": field_name, } + case_conditions.append([0.0, 0.0, 0.0, 0.0]) + + if isinstance(range_symbol, QgsMarkerSymbol): + color["circle-fill-color"] = case_conditions + color["circle-stroke-color"] = rgb_to_hex( + range_symbol.symbolLayer(0) + .properties() + .get("outline_color", "0,0,0,255") + ) + layer_parameters.update(type="circle") + elif isinstance(range_symbol, QgsLineSymbol): + color["stroke-color"] = case_conditions + color["stroke-line-cap"] = ( + range_symbol.symbolLayer(0).properties().get("capstyle") + ) + color["stroke-line-join"] = ( + range_symbol.symbolLayer(0).properties().get("joinstyle") + ) + color["stroke-width"] = float( + range_symbol.symbolLayer(0).properties().get("line_width") + ) + layer_parameters.update(type="line") + elif isinstance(range_symbol, QgsFillSymbol): + color["fill-color"] = case_conditions + color["stroke-color"] = rgb_to_hex( + range_symbol.symbolLayer(0) + .properties() + .get("outline_color", "0,0,0,255") + ) + layer_parameters.update(type="fill") + if symbol: print("SYMBOL.......", symbol) if symbol.symbolLayerCount() > 0: @@ -596,7 +632,7 @@ def create_graduated_renderer( color = fill_color_rules[i + 1] if isinstance(color, list) and len(color) == 4: - r, g, b, a = hex_to_rgba(color) + r, g, b, a = color last_color = QColor(int(r), int(g), int(b), int(a * 255)) range_symbol = base_symbol.clone() From dc81d145f0d629548a4c9ac1f54f47f692eebf54 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 19:03:52 +0530 Subject: [PATCH 08/17] clean up --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index c77012c1a..b14b3fdf0 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -81,7 +81,6 @@ def qgis_layer_to_jgis( ) -> str: """Load a QGIS layer into the provided layers/sources dictionary in the JGIS format. Returns the layer id or None if enable to load the layer.""" layer = qgis_layer.layer() - print("HIIIIIIIII") if layer is None: return @@ -174,8 +173,6 @@ def qgis_layer_to_jgis( minZoom=min_zoom, ) if isinstance(layer, QgsVectorLayer): - print("VECTOR LAYER") - layer_type = "VectorLayer" source_type = "GeoJSONSource" source = layer.source() @@ -207,7 +204,6 @@ def qgis_layer_to_jgis( field_name = renderer.classAttribute() for category in renderer.categories(): cat_symbol = category.symbol() - print(cat_symbol.symbolLayer(0).properties()) opacity = cat_symbol.opacity() category_color = cat_symbol.color().name() @@ -304,10 +300,6 @@ def qgis_layer_to_jgis( layer_parameters.update(type="fill") if symbol: - print("SYMBOL.......", symbol) - if symbol.symbolLayerCount() > 0: - print(symbol.symbolLayer(0).properties()) - # Opacity handling opacity = symbol.opacity() alpha = hex(int(opacity * 255))[2:].zfill(2) @@ -416,9 +408,6 @@ def qgis_layer_to_jgis( "parameters": source_parameters, } - print("LAYER.......", layer) - print("LAYERSS.......", layers) - return layer_id From 4c8b91ec6442eac9de002a29e712771d4b6feec6 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 19:17:37 +0530 Subject: [PATCH 09/17] graduated works --- .../jupytergis_qgis/qgis_loader.py | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index b14b3fdf0..030745dbb 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -243,35 +243,30 @@ def qgis_layer_to_jgis( layer_parameters.update(type="fill") elif isinstance(renderer, QgsGraduatedSymbolRenderer): - case_conditions = ["case"] field_name = renderer.classAttribute() + interpolate_conditions = ["interpolate", ["linear"], ["get", field_name]] for range in renderer.ranges(): range_symbol = range.symbol() opacity = range_symbol.opacity() - alpha = hex(int(opacity * 255))[2:].zfill(2) - range_color = range_symbol.color().name() + alpha + alpha = opacity + + range_color = range_symbol.color().getRgbF() + r, g, b, _ = range_color + lower = range.lowerValue() - upper = range.upperValue() - - case_conditions.append( - [ - "all", - [">=", ["get", field_name], lower], - ["<", ["get", field_name], upper], - ] - ) - case_conditions.append(range_color) + + interpolate_conditions.append(lower) + interpolate_conditions.append([r * 255, g * 255, b * 255, alpha]) layer_parameters["symbologyState"] = { "renderType": "Graduated", "value": field_name, } - case_conditions.append([0.0, 0.0, 0.0, 0.0]) - + # Determine geometry type and apply color interpolation if isinstance(range_symbol, QgsMarkerSymbol): - color["circle-fill-color"] = case_conditions + color["circle-fill-color"] = interpolate_conditions color["circle-stroke-color"] = rgb_to_hex( range_symbol.symbolLayer(0) .properties() @@ -279,7 +274,7 @@ def qgis_layer_to_jgis( ) layer_parameters.update(type="circle") elif isinstance(range_symbol, QgsLineSymbol): - color["stroke-color"] = case_conditions + color["stroke-color"] = interpolate_conditions color["stroke-line-cap"] = ( range_symbol.symbolLayer(0).properties().get("capstyle") ) @@ -291,7 +286,7 @@ def qgis_layer_to_jgis( ) layer_parameters.update(type="line") elif isinstance(range_symbol, QgsFillSymbol): - color["fill-color"] = case_conditions + color["fill-color"] = interpolate_conditions color["stroke-color"] = rgb_to_hex( range_symbol.symbolLayer(0) .properties() From a9535e10eb503646882d3b329ef6ee3488f2ae01 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 19:32:05 +0530 Subject: [PATCH 10/17] add test for fill graduated --- examples/world.jGIS | 199 +++++------------- .../jupytergis_qgis/tests/test_qgis.py | 69 +++++- 2 files changed, 115 insertions(+), 153 deletions(-) diff --git a/examples/world.jGIS b/examples/world.jGIS index a66009241..89b6c2e9d 100644 --- a/examples/world.jGIS +++ b/examples/world.jGIS @@ -8,190 +8,87 @@ "parameters": { "color": { "fill-color": [ - "case", + "interpolate", [ - "==", - [ - "get", - "POP_RANK" - ], - 1.0 - ], - [ - 255.0, - 0.0, - 255.0, - 1.0 - ], - [ - "==", - [ - "get", - "POP_RANK" - ], - 4.0 - ], - [ - 255.0, - 23.0, - 232.0, - 1.0 - ], - [ - "==", - [ - "get", - "POP_RANK" - ], - 8.0 - ], - [ - 255.0, - 46.0, - 209.0, - 1.0 - ], - [ - "==", - [ - "get", - "POP_RANK" - ], - 10.0 + "linear" ], [ - 255.0, - 70.0, - 185.0, - 1.0 - ], - [ - "==", - [ - "get", - "POP_RANK" - ], - 11.0 + "get", + "POP_RANK" ], + 2.888888888888889, [ - 255.0, - 93.0, - 162.0, + 125.0, + 0.0, + 179.0, 1.0 ], + 4.777777777777778, [ - "==", - [ - "get", - "POP_RANK" - ], - 12.0 - ], - [ - 255.0, 116.0, - 139.0, + 0.0, + 218.0, 1.0 ], + 6.666666666666666, [ - "==", - [ - "get", - "POP_RANK" - ], - 13.0 - ], - [ - 255.0, - 139.0, - 116.0, + 98.0, + 74.0, + 237.0, 1.0 ], + 8.555555555555555, [ - "==", - [ - "get", - "POP_RANK" - ], - 14.0 - ], - [ - 255.0, - 162.0, - 93.0, + 68.0, + 146.0, + 231.0, 1.0 ], + 10.444444444444445, [ - "==", - [ - "get", - "POP_RANK" - ], - 15.0 - ], - [ - 255.0, - 185.0, - 70.0, + 0.0, + 204.0, + 197.0, 1.0 ], + 12.333333333333334, [ - "==", - [ - "get", - "POP_RANK" - ], - 16.0 + 0.0, + 247.0, + 146.0, + 1.0 ], + 14.222222222222223, [ + 0.0, 255.0, - 209.0, - 46.0, + 88.0, 1.0 ], + 16.11111111111111, [ - "==", - [ - "get", - "POP_RANK" - ], - 17.0 - ], - [ + 40.0, 255.0, - 232.0, - 23.0, + 8.0, 1.0 ], + 18.0, [ - "==", - [ - "get", - "POP_RANK" - ], - 18.0 - ], - [ - 255.0, + 147.0, 255.0, 0.0, 1.0 - ], - [ - 0.0, - 0.0, - 0.0, - 0.0 ] ] }, "opacity": 1.0, "source": "b4287bea-e217-443c-b527-58f7559c824c", "symbologyState": { - "colorRamp": "spring", - "mode": "", - "nClasses": "", - "renderType": "Categorized", + "colorRamp": "cool", + "method": "color", + "mode": "equal interval", + "nClasses": "9", + "renderType": "Graduated", "value": "POP_RANK" }, "type": "fill" @@ -204,16 +101,16 @@ "options": { "bearing": 0.0, "extent": [ - -3689578.4464635598, - -20037508.34278924, - 14171832.118003651, - 20037508.34278924 + -35938860.79774074, + -17466155.24107265, + 4136155.887837734, + 18813049.299423713 ], - "latitude": 0.0, - "longitude": 47.08184342581217, + "latitude": 6.038467945870664, + "longitude": -142.8442794845441, "pitch": 0.0, "projection": "EPSG:3857", - "zoom": 2.1686721181322306 + "zoom": 2.100662339005199 }, "sources": { "b4287bea-e217-443c-b527-58f7559c824c": { diff --git a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py index ac56a77f2..b3a847688 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py +++ b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py @@ -125,8 +125,22 @@ def test_qgis_saver(): if os.path.exists(filename): os.remove(filename) - layer_ids = [str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4())] - source_ids = [str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4())] + layer_ids = [ + str(uuid4()), + str(uuid4()), + str(uuid4()), + str(uuid4()), + str(uuid4()), + str(uuid4()), + ] + source_ids = [ + str(uuid4()), + str(uuid4()), + str(uuid4()), + str(uuid4()), + str(uuid4()), + str(uuid4()), + ] jgis = { "options": { "bearing": 0.0, @@ -199,6 +213,49 @@ def test_qgis_saver(): "type": "VectorLayer", "visible": True, }, + layer_ids[5]: { + "name": "Custom GeoJSON Layer", + "parameters": { + "color": { + "fill-color": [ + "interpolate", + ["linear"], + ["get", "POP_RANK"], + 2.888888888888889, + [125.0, 0.0, 179.0, 1.0], + 4.777777777777778, + [116.0, 0.0, 218.0, 1.0], + 6.666666666666666, + [98.0, 74.0, 237.0, 1.0], + 8.555555555555555, + [68.0, 146.0, 231.0, 1.0], + 10.444444444444445, + [0.0, 204.0, 197.0, 1.0], + 12.333333333333334, + [0.0, 247.0, 146.0, 1.0], + 14.222222222222223, + [0.0, 255.0, 88.0, 1.0], + 16.11111111111111, + [40.0, 255.0, 8.0, 1.0], + 18.0, + [147.0, 255.0, 0.0, 1.0], + ] + }, + "opacity": 1.0, + "source": source_ids[5], + "symbologyState": { + "colorRamp": "cool", + "method": "color", + "mode": "equal interval", + "nClasses": "9", + "renderType": "Graduated", + "value": "POP_RANK", + }, + "type": "fill", + }, + "type": "VectorLayer", + "visible": True, + }, }, "layerTree": [ layer_ids[0], @@ -211,6 +268,7 @@ def test_qgis_saver(): "name": "group0", }, layer_ids[4], + layer_ids[5], ], "sources": { source_ids[0]: { @@ -256,6 +314,13 @@ def test_qgis_saver(): }, "type": "GeoJSONSource", }, + source_ids[5]: { + "name": "Custom GeoJSON Layer Source", + "parameters": { + "path": "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_110m_admin_0_countries.geojson" + }, + "type": "GeoJSONSource", + }, }, "metadata": {}, } From fdc157ca6e951c35a145561422fa1a32109b7e76 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 20:19:39 +0530 Subject: [PATCH 11/17] fix --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index 030745dbb..d55329ce7 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -254,7 +254,7 @@ def qgis_layer_to_jgis( range_color = range_symbol.color().getRgbF() r, g, b, _ = range_color - lower = range.lowerValue() + lower = range.upperValue() interpolate_conditions.append(lower) interpolate_conditions.append([r * 255, g * 255, b * 255, alpha]) From fb65bcbd8e3cfa25a0210df81c465687615c5302 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 21:09:49 +0530 Subject: [PATCH 12/17] remove some non supported options --- python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py index b3a847688..37e84bc11 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py +++ b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py @@ -244,10 +244,6 @@ def test_qgis_saver(): "opacity": 1.0, "source": source_ids[5], "symbologyState": { - "colorRamp": "cool", - "method": "color", - "mode": "equal interval", - "nClasses": "9", "renderType": "Graduated", "value": "POP_RANK", }, From fc9d342c9b75f6be826d2ff8a18c1963b7056a53 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 22:23:42 +0530 Subject: [PATCH 13/17] fix range in graduated symbology --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index d55329ce7..18e67deb0 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -607,7 +607,6 @@ def create_graduated_renderer( ranges = [] previous_value = 0 - last_color = None last_radius = None for i in range(3, len(fill_color_rules) - 2, 2): @@ -617,10 +616,10 @@ def create_graduated_renderer( if isinstance(color, list) and len(color) == 4: r, g, b, a = color - last_color = QColor(int(r), int(g), int(b), int(a * 255)) + range_color = QColor(int(r), int(g), int(b), int(a * 255)) range_symbol = base_symbol.clone() - range_symbol.setColor(last_color) + range_symbol.setColor(range_color) if geometry_type == "circle" and len(radius_rules) > i + 1: radius = radius_rules[i + 1] @@ -637,6 +636,12 @@ def create_graduated_renderer( ranges.append(g_range) previous_value = lower_value + if ( + isinstance(fill_color_rules[len(fill_color_rules) - 1], list) + and len(fill_color_rules[len(fill_color_rules) - 1]) == 4 + ): + r, g, b, a = fill_color_rules[len(fill_color_rules) - 1] + last_color = QColor(int(r), int(g), int(b), int(a * 255)) if last_color: final_symbol = base_symbol.clone() final_symbol.setColor(last_color) From 84cffe616f51d1bb3ab0887283cad002598cef45 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Tue, 4 Feb 2025 22:54:52 +0530 Subject: [PATCH 14/17] fix test --- python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py index 37e84bc11..fd5c71ad7 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py +++ b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py @@ -239,7 +239,8 @@ def test_qgis_saver(): [40.0, 255.0, 8.0, 1.0], 18.0, [147.0, 255.0, 0.0, 1.0], - ] + ], + "stroke-color": "#000000", }, "opacity": 1.0, "source": source_ids[5], From 33d59ee5cd577e7160b238e3a81f8dd1420ef3bf Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 5 Feb 2025 13:48:04 +0530 Subject: [PATCH 15/17] Add test for categorised rendering on line --- .../jupytergis_qgis/tests/test_qgis.py | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py index fd5c71ad7..dc43315a3 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py +++ b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py @@ -132,6 +132,7 @@ def test_qgis_saver(): str(uuid4()), str(uuid4()), str(uuid4()), + str(uuid4()), ] source_ids = [ str(uuid4()), @@ -140,6 +141,7 @@ def test_qgis_saver(): str(uuid4()), str(uuid4()), str(uuid4()), + str(uuid4()), ] jgis = { "options": { @@ -253,6 +255,61 @@ def test_qgis_saver(): "type": "VectorLayer", "visible": True, }, + layer_ids[6]: { + "name": "Custom GeoJSON Layer", + "parameters": { + "color": { + "stroke-color": [ + "case", + ["==", ["get", "min_label"], 6.0], + [125.0, 0.0, 179.0, 1.0], + ["==", ["get", "min_label"], 7.0], + [121.0, 0.0, 199.0, 1.0], + ["==", ["get", "min_label"], 7.4], + [116.0, 0.0, 218.0, 1.0], + ["==", ["get", "min_label"], 7.5], + [107.0, 37.0, 228.0, 1.0], + ["==", ["get", "min_label"], 7.9], + [98.0, 74.0, 237.0, 1.0], + ["==", ["get", "min_label"], 8.0], + [83.0, 110.0, 234.0, 1.0], + ["==", ["get", "min_label"], 8.4], + [68.0, 146.0, 231.0, 1.0], + ["==", ["get", "min_label"], 8.5], + [34.0, 175.0, 214.0, 1.0], + ["==", ["get", "min_label"], 8.6], + [0.0, 204.0, 197.0, 1.0], + ["==", ["get", "min_label"], 8.9], + [0.0, 247.0, 146.0, 1.0], + ["==", ["get", "min_label"], 9.0], + [0.0, 251.0, 117.0, 1.0], + ["==", ["get", "min_label"], 9.5], + [0.0, 255.0, 88.0, 1.0], + ["==", ["get", "min_label"], 9.6], + [20.0, 255.0, 48.0, 1.0], + ["==", ["get", "min_label"], 10.0], + [40.0, 255.0, 8.0, 1.0], + ["==", ["get", "min_label"], 10.1], + [94.0, 255.0, 4.0, 1.0], + ["==", ["get", "min_label"], 10.2], + [147.0, 255.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0], + ] + }, + "opacity": 1.0, + "source": source_ids[6], + "symbologyState": { + "colorRamp": "cool", + "mode": "", + "nClasses": "", + "renderType": "Categorized", + "value": "min_label", + }, + "type": "line", + }, + "type": "VectorLayer", + "visible": True, + }, }, "layerTree": [ layer_ids[0], @@ -266,6 +323,7 @@ def test_qgis_saver(): }, layer_ids[4], layer_ids[5], + layer_ids[6], ], "sources": { source_ids[0]: { @@ -318,6 +376,13 @@ def test_qgis_saver(): }, "type": "GeoJSONSource", }, + source_ids[6]: { + "name": "Custom GeoJSON Layer Source", + "parameters": { + "path": "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_10m_roads.geojson" + }, + "type": "GeoJSONSource", + }, }, "metadata": {}, } From cf31818b30242353e5cceda67a1fe0f523a143df Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 5 Feb 2025 14:03:44 +0530 Subject: [PATCH 16/17] fix test --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 3 ++- python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index 18e67deb0..781edc3f2 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -208,7 +208,8 @@ def qgis_layer_to_jgis( category_color = cat_symbol.color().name() case_conditions.append(["==", ["get", field_name], category.value()]) - case_conditions.append(category_color) + r, g, b, a = hex_to_rgba(category_color) + case_conditions.append([r, g, b, a / 255]) layer_parameters["symbologyState"] = { "colorRamp": "cool", diff --git a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py index dc43315a3..ccd994dfc 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py +++ b/python/jupytergis_qgis/jupytergis_qgis/tests/test_qgis.py @@ -294,7 +294,10 @@ def test_qgis_saver(): ["==", ["get", "min_label"], 10.2], [147.0, 255.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0], - ] + ], + "stroke-line-cap": "square", + "stroke-line-join": "bevel", + "stroke-width": 1.0, }, "opacity": 1.0, "source": source_ids[6], From 3aa6c692fdfd68467af675ca3a37a03942fd5330 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Wed, 5 Feb 2025 14:15:05 +0530 Subject: [PATCH 17/17] try fixing test --- python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py index 781edc3f2..1753d2c91 100644 --- a/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py +++ b/python/jupytergis_qgis/jupytergis_qgis/qgis_loader.py @@ -579,7 +579,7 @@ def create_categorized_renderer( raise ValueError(f"Unexpected color format: {color}") category_symbol = base_symbol.clone() - category_symbol.setColor(QColor(r, g, b, int(a))) + category_symbol.setColor(QColor(int(r), int(g), int(b), int(a))) if geometry_type == "circle" and isinstance(radius_rules, (int, float)): radius = radius_rules