# HG changeset patch # User iuc # Date 1773501358 0 # Node ID 2adfc39ffbacb563f8c72d0b8b0b89ddc23d1659 planemo upload for repository https://github.com/galaxyproject/tools-iuc/tree/main/tools/spatialdata commit 87bff76d897c5a4277d9987cf26432a18e0458cd-dirty diff -r 000000000000 -r 2adfc39ffbac macros.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/macros.xml Sat Mar 14 15:15:58 2026 +0000 @@ -0,0 +1,442 @@ + + 0.7.2 + 0 + 25.0 + + + spatialdata + spatialdata-io + spatialdata-plot + + ome-zarr + + anndata + pandas + rioxarray + zip + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10.1038/s41592-024-02212-x + + + \ No newline at end of file diff -r 000000000000 -r 2adfc39ffbac spatialdata_operation.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spatialdata_operation.xml Sat Mar 14 15:15:58 2026 +0000 @@ -0,0 +1,1679 @@ + + perform operations on SpatialData objects + + macros.xml + + + + + + +import spatialdata as sd + +## Load the SpatialData object +sdata = sd.read_zarr("./input/spatialdata") + +print("Input SpatialData object:") +print(sdata) + +#if $operation_condi.operation == 'bounding_box_query': +## Bounding box query + #set axes_list = [str(x.strip()) for x in str($operation_condi.axes).split(',')] + #set min_coord_list = [float(x.strip()) for x in str($operation_condi.min_coordinate).split(',')] + #set max_coord_list = [float(x.strip()) for x in str($operation_condi.max_coordinate).split(',')] +result = sd.bounding_box_query( + sdata, + axes=tuple($axes_list), + min_coordinate=$min_coord_list, + max_coordinate=$max_coord_list, + target_coordinate_system='$operation_condi.target_coordinate_system', + filter_table=$operation_condi.filter_table_bool +) + +#else if $operation_condi.operation == 'polygon_query': +## Polygon query +from shapely.geometry import Polygon +coords_str = '$operation_condi.polygon_coords' +coords_list = [tuple(float(y.strip()) for y in x.split(',')) for x in coords_str.split(';')] +polygon = Polygon(coords_list) +result = sd.polygon_query( + sdata, + polygon=polygon, + target_coordinate_system='$operation_condi.target_coordinate_system', + filter_table=$operation_condi.filter_table_bool, + clip=$operation_condi.clip_shapes_bool +) + +#else if $operation_condi.operation == 'get_values': +## Get values +result = sd.get_values( + value_key='$operation_condi.value_key', + sdata=sdata, + element_name='$operation_condi.element_name', + #if $operation_condi.table_name: + table_name='$operation_condi.table_name', + #end if + #if $operation_condi.table_layer: + table_layer='$operation_condi.table_layer', + #end if + return_obsm_as_is=$operation_condi.return_obsm_as_is +) + +#else if $operation_condi.operation == 'get_element_instances': +## Get element instances +element = sdata['$operation_condi.element_name'] +result = sd.get_element_instances( + element, + return_background=$operation_condi.return_background_bool +) + +#else if $operation_condi.operation == 'get_extent': +## Get extent + #if $operation_condi.element_name: +result = sd.get_extent( + sdata['$operation_condi.element_name'], + coordinate_system='$operation_condi.coordinate_system', + exact=$operation_condi.exact_bool +) + #else: +result = sd.get_extent( + sdata, + coordinate_system='$operation_condi.coordinate_system', + exact=$operation_condi.exact_bool, + has_images=$operation_condi.has_images_bool, + has_labels=$operation_condi.has_labels_bool, + has_points=$operation_condi.has_points_bool, + has_shapes=$operation_condi.has_shapes_bool +) + #end if + +#else if $operation_condi.operation == 'get_centroids': +## Get centroids +element = sdata['$operation_condi.element_name'] +result_element = sd.get_centroids( + element, + coordinate_system='$operation_condi.coordinate_system', + return_background=$operation_condi.return_background_bool +) +sanitized_name = sd.sanitize_name('$operation_condi.output_element_name') +sdata[sanitized_name] = result_element +result = sdata + +#else if $operation_condi.operation == 'join_spatialelement_table': +## Join SpatialElement and table + #set element_names_list = [str(x.strip()) for x in str($operation_condi.spatial_element_names).split(',')] +elements_dict, result_table = sd.join_spatialelement_table( + sdata=sdata, + spatial_element_names=$element_names_list, + table_name='$operation_condi.table_name', + how='$operation_condi.how', + match_rows='$operation_condi.match_rows' +) +## Update sdata with filtered table +table = sdata.update_annotated_regions_metadata(table=result_table, + #if $operation_condi.region_key: + region_key='$operation_condi.region_key' + #end if + ) + +table_name =sd.sanitize_name('$operation_condi.output_table_name') +sdata[f"filtered_{table_name}"] = table +sdata[table_name] = result_table + +result = sdata + +#else if $operation_condi.operation == 'match_element_to_table': +## Match element to table + #set element_names_list = [str(x.strip()) for x in str($operation_condi.element_names).split(',')] +elements_dict, result_table = sd.match_element_to_table( + sdata=sdata, + element_name=$element_names_list, + table_name='$operation_condi.table_name' +) +for elem_name, elem_data in elements_dict.items(): + #if str($operation_condi.inplace_bool) == "True": + sdata[elem_name] = elem_data + #else: + sdata[f"matched_{elem_name}"] = elem_data + #end if + +result = sdata + +#else if $operation_condi.operation == 'match_table_to_element': +## Match table to element +result_table = sd.match_table_to_element( + sdata=sdata, + element_name='$operation_condi.element_name', + table_name='$operation_condi.table_name' +) +table_name = '$operation_condi.table_name' +#if str($operation_condi.inplace_bool) == "True": +sdata.tables[f"{table_name}"] = result_table +#else: +sdata.tables[f"matched_{table_name}"] = result_table +#end if +result = sdata + +#else if $operation_condi.operation == 'match_sdata_to_table': +## Match SpatialData to table +result = sd.match_sdata_to_table( + sdata=sdata, + table_name='$operation_condi.table_name', + how='$operation_condi.how' +) + +#else if $operation_condi.operation == 'filter_by_table_query': +## Filter by table query +import annsel as an + +result = sd.filter_by_table_query( + sdata=sdata, + table_name='$operation_condi.table_name', + filter_tables=$operation_condi.filter_tables_bool, + #if $operation_condi.element_names: + #set element_names_list = [str(x.strip()) for x in str($operation_condi.element_names).split(',')] + element_names = $element_names_list, + #else: + element_names = None, + #end if + #if $operation_condi.obs_expr: + obs_expr = $operation_condi.obs_expr, + #end if + #if $operation_condi.var_expr: + var_expr = $operation_condi.var_expr, + #end if + #if $operation_condi.x_expr: + x_expr = $operation_condi.x_expr, + #end if + #if $operation_condi.obs_names_expr: + obs_names_expr = $operation_condi.obs_names_expr, + #end if + #if $operation_condi.var_names_expr: + var_names_expr = $operation_condi.var_names_expr, + #end if + #if $operation_condi.layer: + layer = '$operation_condi.layer', + #end if + how='$operation_condi.how' +) + +#else if $operation_condi.operation == 'concatenate': +## Concatenate multiple SpatialData objects +import zipfile +import os + +sdatas_list = [sdata] + #for i, filepath in enumerate($operation_condi.other_sdatas) +os.makedirs(f"./input_{$i}", exist_ok=True) +with zipfile.ZipFile('$filepath', 'r') as zip_ref: + zip_ref.extractall(f"./input_{$i}") +sdata_$i = sd.read_zarr(f"./input_{$i}/spatialdata") +sdatas_list.append(sdata_$i) + #end for +result = sd.concatenate( + sdatas=sdatas_list, + #if $operation_condi.region_key: + region_key='$operation_condi.region_key', + #end if + #if $operation_condi.instance_key: + instance_key='$operation_condi.instance_key', + #end if + concatenate_tables=$operation_condi.concatenate_tables_bool, + obs_names_make_unique=$operation_condi.obs_names_make_unique_bool, + modify_tables_inplace=False, + merge_coordinate_systems_on_name=$operation_condi.merge_coordinate_systems_on_name_bool + #if $operation_condi.attrs_merge: + ,attrs_merge='$operation_condi.attrs_merge' + #end if +) + +#else if $operation_condi.operation == 'transform': +## Transform element +element = sdata['$operation_condi.element_name'] + #if $operation_condi.maintain_positioning_condi.maintain_positioning == 'yes': +## Apply transformation with maintain_positioning=True + #if $operation_condi.maintain_positioning_condi.transformation_condi.transformation_type == 'identity': +from spatialdata.transformations import Identity +transformation = Identity() + #else if $operation_condi.maintain_positioning_condi.transformation_condi.transformation_type == 'map_axis': +from spatialdata.transformations import MapAxis + #set input_axes_list = [str(x.strip()) for x in str($operation_condi.maintain_positioning_condi.transformation_condi.map_axis_input).split(',')] + #set output_axes_list = [str(x.strip()) for x in str($operation_condi.maintain_positioning_condi.transformation_condi.map_axis_output).split(',')] +map_axis_dict = dict(zip($input_axes_list, $output_axes_list)) +transformation = MapAxis(map_axis_dict) + #else if $operation_condi.maintain_positioning_condi.transformation_condi.transformation_type == 'translation': +from spatialdata.transformations import Translation + #set translation_list = [float(x.strip()) for x in str($operation_condi.maintain_positioning_condi.transformation_condi.translation).split(',')] + #set axes_list = [str(x.strip()) for x in str($operation_condi.maintain_positioning_condi.transformation_condi.axes).split(',')] +transformation = Translation($translation_list, axes=tuple($axes_list)) + #else if $operation_condi.maintain_positioning_condi.transformation_condi.transformation_type == 'scale': +from spatialdata.transformations import Scale + #set scale_list = [float(x.strip()) for x in str($operation_condi.maintain_positioning_condi.transformation_condi.scale).split(',')] + #set axes_list = [str(x.strip()) for x in str($operation_condi.maintain_positioning_condi.transformation_condi.axes).split(',')] +transformation = Scale($scale_list, axes=tuple($axes_list)) + #else if $operation_condi.maintain_positioning_condi.transformation_condi.transformation_type == 'affine': +from spatialdata.transformations import Affine +import ast + #set input_axes_list = [str(x.strip()) for x in str($operation_condi.maintain_positioning_condi.transformation_condi.input_axes).split(',')] + #set output_axes_list = [str(x.strip()) for x in str($operation_condi.maintain_positioning_condi.transformation_condi.output_axes).split(',')] +matrix = ast.literal_eval('$operation_condi.maintain_positioning_condi.transformation_condi.matrix') +transformation = Affine( + matrix, + input_axes=tuple($input_axes_list), + output_axes=tuple($output_axes_list), +) + #else if $operation_condi.maintain_positioning_condi.transformation_condi.transformation_type == 'sequence': +from spatialdata.transformations import Sequence, Identity, MapAxis, Translation, Scale, Affine +## Execute user-provided code to create transformation list +transformations_list = eval('$operation_condi.maintain_positioning_condi.transformation_condi.transformations_code') +transformation = Sequence(transformations_list) + #end if +result_element = sd.transform( + element, + transformation=transformation, + maintain_positioning=True +) + #else: +## Transform to a different coordinate system with maintain_positioning=False +result_element = sd.transform( + element, + to_coordinate_system='$operation_condi.maintain_positioning_condi.to_coordinate_system' +) + #end if + +sanitized_name = sd.sanitize_name('$operation_condi.output_element_name') +sdata[sanitized_name] = result_element +result = sdata + + + + +#else if $operation_condi.operation == 'rasterize': +## Rasterize element + #set axes_list = [str(x.strip()) for x in str($operation_condi.axes).split(',')] + #set min_coord_list = [float(x.strip()) for x in str($operation_condi.min_coordinate).split(',')] + #set max_coord_list = [float(x.strip()) for x in str($operation_condi.max_coordinate).split(',')] + +rasterized = sd.rasterize( + data='$operation_condi.element_name', + axes=tuple($axes_list), + min_coordinate=$min_coord_list, + max_coordinate=$max_coord_list, + target_coordinate_system='$operation_condi.target_coordinate_system', + #if $operation_condi.target_unit_to_pixels: + target_unit_to_pixels=$operation_condi.target_unit_to_pixels, + #end if + #if $operation_condi.target_width: + target_width=$operation_condi.target_width, + #end if + #if $operation_condi.target_height: + target_height=$operation_condi.target_height, + #end if + #if $operation_condi.target_depth: + target_depth=$operation_condi.target_depth, + #end if + sdata=sdata, + #if $operation_condi.value_key: + value_key='$operation_condi.value_key', + #end if + #if $operation_condi.table_name: + table_name='$operation_condi.table_name', + #end if + #if $operation_condi.agg_func: + agg_func='$operation_condi.agg_func', + #end if + return_regions_as_labels=$operation_condi.return_regions_as_labels_bool, + return_single_channel=$operation_condi.return_single_channel_bool +) +sdata.images['rasterized_' + '$operation_condi.element_name'] = rasterized +result = sdata + +#else if $operation_condi.operation == 'rasterize_bins': +## Rasterize bins +rasterized = sd.rasterize_bins( + sdata=sdata, + bins='$operation_condi.bins_element', + table_name='$operation_condi.table_name', + col_key='$operation_condi.col_key', + row_key='$operation_condi.row_key', + #if $operation_condi.value_key: + value_key='$operation_condi.value_key', + #end if + return_region_as_labels=$operation_condi.return_regions_as_labels_bool +) + +#if str($operation_condi.return_regions_as_labels_bool) == "True": +sdata.labels['rasterized_' + '$operation_condi.bins_element'] = rasterized +#else: +sdata.images['rasterized_' + '$operation_condi.bins_element'] = rasterized +#end if + +result = sdata + +#else if $operation_condi.operation == 'rasterize_bins_link_table_to_labels': +## Link table to rasterized labels +sd.rasterize_bins_link_table_to_labels( + sdata=sdata, + table_name='$operation_condi.table_name', + rasterized_labels_name='$operation_condi.rasterized_labels_name' +) +result = sdata + +#else if $operation_condi.operation == 'to_circles': +## Convert to circles +element = sdata['$operation_condi.element_name'] + #if str($operation_condi.radius) != '': +result_element = sd.to_circles(element, radius=$operation_condi.radius) + #else: +result_element = sd.to_circles(element) + #end if +sanitized_name = sd.sanitize_name('$operation_condi.output_element_name') +sdata[sanitized_name] = result_element +result = sdata + +#else if $operation_condi.operation == 'to_polygons': +## Convert to polygons +## it is recommended to configure Dask to use ‘processes’ rather than ‘threads’ +##import dask +##dask.config.set(scheduler='processes') + +element = sdata['$operation_condi.element_name'] + #if $operation_condi.buffer_resolution: +result_element = sd.to_polygons(element, buffer_resolution=$operation_condi.buffer_resolution) + #else: +result_element = sd.to_polygons(element) + #end if +sanitized_name = sd.sanitize_name('$operation_condi.output_element_name') +sdata[sanitized_name] = result_element +result = sdata + +#else if $operation_condi.operation == 'aggregate': +## Aggregate values by regions +result = sd.aggregate( + values_sdata=sdata, + values='$operation_condi.values_element', + by_sdata=sdata, + by='$operation_condi.by_element', + #if $operation_condi.value_key: + value_key='$operation_condi.value_key', + #end if + agg_func='$operation_condi.agg_func', + target_coordinate_system='$operation_condi.target_coordinate_system', + fractions=$operation_condi.fractions_bool, + region_key='$operation_condi.region_key', + instance_key='$operation_condi.instance_key', + deepcopy=$operation_condi.deepcopy_bool, + #if $operation_condi.table_name: + table_name='$operation_condi.table_name', + #end if + buffer_resolution=$operation_condi.buffer_resolution +) + +#else if $operation_condi.operation == 'map_raster': +## Map raster +element = sdata['$operation_condi.element_name'] +import importlib +func_module, func_name = '$operation_condi.func_name'.rsplit('.', 1) +func = getattr(importlib.import_module(func_module), func_name) +mapped = sd.map_raster( + data=element, + func=func, + blockwise=$operation_condi.blockwise_bool, + #if $operation_condi.depth: + depth=$operation_condi.depth, + #end if + #if $operation_condi.chunks: + chunks=eval('$operation_condi.chunks'), + #end if + #if $operation_condi.c_coords: + #set c_coords_list = [x.strip() for x in str($operation_condi.c_coords).split(',')] + ## Try to convert to int, otherwise keep as string + #set c_coords_parsed = [] + #for coord in $c_coords_list: + #try: + #silent c_coords_parsed.append(int(coord)) + #except ValueError: + #silent c_coords_parsed.append(str(coord)) + #end try + #end for + c_coords=$c_coords_parsed, + #end if + #if $operation_condi.dims: + #set dims_list = [str(x.strip()) for x in str($operation_condi.dims).split(',')] + dims=tuple($dims_list), + #end if + #if $operation_condi.transformations: + transformations=eval('$operation_condi.transformations'), + #end if + relabel=$operation_condi.relabel_bool +) +sdata.images['mapped_' + '$operation_condi.element_name'] = mapped +result = sdata + +#else if $operation_condi.operation == 'unpad_raster': +## Unpad raster +element = sdata['$operation_condi.element_name'] +unpadded = sd.unpad_raster(element) +sdata.images['unpadded_${operation_condi.element_name}'] = unpadded +result = sdata + +#else if $operation_condi.operation == 'relabel_sequential': +## no good example to see how this should work and also when it works, the sdata can not be written +## Relabel sequential +import dask.array as da +element = sdata['$operation_condi.element_name'] +relabeled_arr = sd.relabel_sequential(element.data) +relabeled = element.copy() +relabeled.data = relabeled_arr +sanitized_name = sd.sanitize_name('$operation_condi.output_element_name') +sdata[sanitized_name] = relabeled +result = sdata + +#else if $operation_condi.operation == 'are_extents_equal': +## Check if extents are equal +extent1 = sd.get_extent(sdata['$operation_condi.element1_name'], coordinate_system='$operation_condi.coordinate_system') +extent2 = sd.get_extent(sdata['$operation_condi.element2_name'], coordinate_system='$operation_condi.coordinate_system') +result = sd.are_extents_equal(extent1, extent2, atol=$operation_condi.atol) + +#else if $operation_condi.operation == 'get_pyramid_levels': +## no good example to see how this should work and also when it works, the sdata can not be written +## Get pyramid levels +element = sdata.images['$operation_condi.element_name'] +result = sd.get_pyramid_levels( + element, + #if $operation_condi.attr: + attr='$operation_condi.attr', + #end if + #if $operation_condi.n: + n=$operation_condi.n + #end if +) +result[f"pyramid_{$operation_condi.element_name}"] = result.pop('pyramid') + +#else if $operation_condi.operation == 'sanitize_table': +## Sanitize table +adata = sdata['$operation_condi.table_name'] +sd.sanitize_table(adata, inplace=True) +sdata.tables['$operation_condi.table_name'] = adata +result = sdata + +#else if $operation_condi.operation == 'export_table': +## Export table to anndata +result = sdata['$operation_condi.table_name'] +result.write_h5ad('./output/anndata.h5ad', compression='gzip') + +#else if $operation_condi.operation == 'import_table': +## Import anndata table +import anndata as ad +adata = ad.read_h5ad('$operation_condi.adata') + +table_name = sd.sanitize_name('$operation_condi.table_name') +sdata[table_name] = adata +result = sdata + +#else if $operation_condi.operation == 'add_shape': +## Add shape to SpatialData +from spatialdata.models import ShapesModel +shape_data = ShapesModel.parse('$operation_condi.shape') +element_name = sd.sanitize_name('$operation_condi.element_name') +sdata[element_name] = shape_data +result = sdata + +#end if + +print("\nOperation result:") +print(result) + +## Save the result +#if $operation_condi.operation in ['bounding_box_query', 'polygon_query', 'concatenate', 'transform', 'aggregate', 'to_circles', 'to_polygons', 'get_centroids', 'join_spatialelement_table', 'match_element_to_table', 'match_table_to_element', 'match_sdata_to_table', 'filter_by_table_query', 'rasterize', 'rasterize_bins', 'rasterize_bins_link_table_to_labels', 'map_raster', 'unpad_raster', 'relabel_sequential', 'sanitize_table', 'import_table', 'add_shape']: +result.write("./output/spatialdata", overwrite=True) + +#else if $operation_condi.operation in ['get_values']: +import pandas as pd +## Save DataFrame as TSV +if isinstance(result, pd.DataFrame): + result.to_csv('./output/result.tabular', sep='\t', index=True) +else: + ## If result is a Series or other, convert to DataFrame + pd.DataFrame(result).to_csv('./output/result.tabular', sep='\t', index=True) + +#else if $operation_condi.operation in ['get_extent', 'get_element_instances', 'are_extents_equal', 'get_pyramid_levels']: +import json +import pandas as pd +import numpy as np + +class NumpyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, np.integer): + return int(obj) + if isinstance(obj, np.floating): + return float(obj) + if isinstance(obj, np.ndarray): + return obj.tolist() + if isinstance(obj, pd.Series): + return obj.tolist() + if isinstance(obj, pd.Index): + return obj.tolist() + if isinstance(obj, pd.DataFrame): + return obj.to_dict(orient='records') + return super(NumpyEncoder, self).default(obj) + +with open('./output/result.json', 'w') as f: + json.dump(result, f, cls=NumpyEncoder, indent=2) +#end if + +print("\nOperation completed successfully!") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + operation_condi['operation'] in ['bounding_box_query', 'polygon_query', 'concatenate', 'transform', 'aggregate', 'to_circles', 'to_polygons', 'get_centroids', 'join_spatialelement_table', 'match_element_to_table', 'match_table_to_element', 'match_sdata_to_table', 'filter_by_table_query', 'rasterize', 'rasterize_bins', 'rasterize_bins_link_table_to_labels', 'map_raster', 'unpad_raster', 'relabel_sequential', 'sanitize_table', 'import_table', 'add_shape'] + + + operation_condi['operation'] == 'get_values' + + + operation_condi['operation'] in ['get_extent', 'get_element_instances', 'are_extents_equal', 'get_pyramid_levels'] + + + operation_condi['operation'] == 'export_table' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `__ +- `SpatialData operations API `__ + + ]]> + +