3131from typing import List , Optional , Tuple , Union
3232
3333from qgis .core import (
34+ Qgis ,
3435 QgsCategorizedSymbolRenderer ,
3536 QgsRasterMarkerSymbolLayer ,
3637 QgsRuleBasedRenderer ,
3738 QgsSingleSymbolRenderer ,
3839 QgsSvgMarkerSymbolLayer ,
3940 QgsSymbol ,
4041 QgsVectorLayer ,
41- QgsWkbTypes ,
4242)
4343from qgis .PyQt .QtCore import QCoreApplication
4444
@@ -229,11 +229,15 @@ def is_valid_filepath(path: str) -> bool:
229229 return True
230230
231231
232- def update_symbols_to_embedded (symbol : QgsSymbol ) -> None :
232+ def update_symbols_to_relative_embedded (
233+ symbol : QgsSymbol , home_path : Path , destination_path : Path
234+ ) -> None :
233235 """
234- Update SVG or Raster symbols layer to embed it in the QGIS project.
236+ Update SVG or Raster symbols layer to relative path or embed it in the QGIS project.
235237 Args:
236238 symbol: The QGIS symbol (from a renderer).
239+ home_path: The root of QGIS Project home path.
240+ destination_path: The target directory where the exported project will be saved.
237241 """
238242 if symbol is None :
239243 return
@@ -255,23 +259,47 @@ def update_symbols_to_embedded(symbol: QgsSymbol) -> None:
255259 if not source_path .is_file ():
256260 continue
257261
258- with open (source_path , "rb" ) as file :
259- file_data = file .read ()
260- encoded_data = base64 .b64encode (file_data ).decode ()
262+ if source_path .is_relative_to (home_path ):
263+ relative_path = source_path .relative_to (home_path )
264+ destination_path_file = destination_path .joinpath (relative_path )
265+
266+ if destination_path_file .exists ():
267+ symbol_layer .setPath (str (relative_path ))
268+ else :
269+ encoded_data = base64 .b64encode (source_path .read_bytes ()).decode ()
270+ symbol_layer .setPath (f"base64:{ encoded_data } " )
271+
272+ else :
273+ encoded_data = base64 .b64encode (source_path .read_bytes ()).decode ()
261274 symbol_layer .setPath (f"base64:{ encoded_data } " )
262275
263276
264- def embed_layer_symbols_on_project (layer : QgsVectorLayer ) -> None :
277+ def set_relative_embed_layer_symbols_on_project (
278+ layer : QgsVectorLayer , project_home : Path , export_project_path : Path
279+ ) -> None :
265280 """
266- Update the paths of symbols to embedded symbols in the QGIS project.
281+ Update the layer style SVG or Raster symbol paths to relative or embedded them in the QGIS project file.
282+
283+ First try to ensure the paths are within to the QGIS project path.
284+ If the resulting path is impossible, then embed the symbols in the QGIS project.
267285
268286 Args:
269287 layer: The QgsVectorLayer to update. The layer is a point layer.
288+ project_home: The root of QGIS Project home path.
289+ export_project_path: The target directory for the exported offline QGIS project.
270290 """
271291
292+ if Qgis .QGIS_VERSION_INT >= 33000 :
293+ point_geometry = Qgis .GeometryType .Point
294+ else :
295+ from qgis .core import QgsWkbTypes
296+
297+ point_geometry = QgsWkbTypes .GeometryType .PointGeometry
298+
272299 if (
273- not isinstance (layer , QgsVectorLayer )
274- or layer .geometryType () != QgsWkbTypes .PointGeometry
300+ not layer .isValid ()
301+ or not isinstance (layer , QgsVectorLayer )
302+ or layer .geometryType () != point_geometry
275303 ):
276304 return
277305
@@ -283,7 +311,9 @@ def embed_layer_symbols_on_project(layer: QgsVectorLayer) -> None:
283311 if isinstance (renderer , QgsSingleSymbolRenderer ):
284312 symbol = renderer .symbol ()
285313 if symbol :
286- update_symbols_to_embedded (symbol = symbol )
314+ update_symbols_to_relative_embedded (
315+ symbol , project_home , export_project_path
316+ )
287317
288318 elif isinstance (renderer , QgsRuleBasedRenderer ):
289319 for rule in renderer .rootRule ().children ():
@@ -293,16 +323,23 @@ def embed_layer_symbols_on_project(layer: QgsVectorLayer) -> None:
293323 continue
294324
295325 for symbol in symbols :
296- update_symbols_to_embedded (symbol = symbol )
326+ update_symbols_to_relative_embedded (
327+ symbol , project_home , export_project_path
328+ )
297329
298330 elif isinstance (renderer , QgsCategorizedSymbolRenderer ):
299331 categories = renderer .categories ()
300332 if categories :
301333 for index in range (len (categories )):
302- # Get a fresh category. The renderer doesn't update in-place modifications.
334+ # Get a fresh category.
335+ # The renderer doesn't update in-place modifications on categorized.
303336 category = renderer .categories ()[index ]
304337 symbol = category .symbol ().clone ()
305- update_symbols_to_embedded (symbol = symbol )
338+
339+ update_symbols_to_relative_embedded (
340+ symbol , project_home , export_project_path
341+ )
342+
306343 renderer .updateCategorySymbol (index , symbol )
307344
308345 layer .setRenderer (renderer )
0 commit comments