Skip to content

Commit

Permalink
Move logic for generating geocoding tables out of export_sbdf. (#44)
Browse files Browse the repository at this point in the history
Add new public function `spotfire.set_geocoding_table` with the logic that was removed from `export_sbdf`.
  • Loading branch information
bbassett-tibco committed Jun 4, 2024
1 parent 2c8e0fd commit 9b28ac8
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 81 deletions.
2 changes: 1 addition & 1 deletion spotfire/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

"""User visible utility functions."""

from spotfire.public import copy_metadata, get_spotfire_types, set_spotfire_types
from spotfire.public import copy_metadata, get_spotfire_types, set_spotfire_types, set_geocoding_table
62 changes: 62 additions & 0 deletions spotfire/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@

from spotfire import sbdf

try:
import geopandas as gpd
except ImportError:
gpd = None


_ColumnTypes = dict[str, str]

Expand Down Expand Up @@ -88,3 +93,60 @@ def set_spotfire_types(dataframe: pd.DataFrame, column_types: _ColumnTypes) -> N
warnings.warn(f"Spotfire type '{spotfire_type}' for column '{col}' not recognized", sbdf.SBDFWarning)
continue
dataframe[col].attrs['spotfire_type'] = spotfire_type


# Spotfire geocoding table functions

def set_geocoding_table(dataframe: gpd.GeoDataFrame) -> None:
"""Add the required columns and set properties to export a ``GeoDataFrame`` as a geocoding table for Spotfire.
:param dataframe: the data frame to configure as a geocoding table.
.. seealso::
More information about column and table metadata properties available to geocoding tables
https://docs.tibco.com/pub/sfire-analyst/latest/doc/html/en-US/TIB_sfire-analyst_UsersGuide/map_60/map_60_setting_up_new_geocoding_tables.htm
"""
if gpd is not None:
# Remove columns that will be generated by the geometry
columns = ["XMin", "XMax", "YMin", "YMax", "XCenter", "YCenter"]
try:
dataframe.drop(columns=columns, inplace=True)
except KeyError:
pass

# Create columns from geometry
bounds = dataframe.geometry.bounds
centroid = dataframe.geometry.centroid
dataframe.assign(XMin=bounds["minx"], XMax=bounds["maxx"],
YMin=bounds["miny"], YMax=bounds["maxy"],
XCenter=centroid.x, YCenter=centroid.y)
for col in columns:
dataframe[col].spotfire_column_metadata = {"MapChart.ColumnTypeId": [col]}

# Set table metadata
try:
table_metadata = dataframe.spotfire_table_metadata
except AttributeError:
table_metadata = {}

table_metadata["MapChart.IsGeocodingTable"] = True
if not table_metadata.get("MapChart.IsGeocodingEnabled"):
table_metadata["MapChart.IsGeocodingEnabled"] = True

if dataframe.geom_type.nunique() != 1:
raise sbdf.SBDFError("geocoding tables cannot contain mixed geometry types")
geom_type = dataframe.geom_type[0]
if geom_type == "Point":
table_metadata["MapChart.GeometryType"] = ["Point"]
elif geom_type in ("LineString", "LinearRing"):
table_metadata["MapChart.GeometryType"] = ["Line"]
elif geom_type == "MultiLineString":
table_metadata["MapChart.GeometryType"] = ["PolyLine"]
elif geom_type == "Polygon":
table_metadata["MapChart.GeometryType"] = ["Polygon"]
else:
raise sbdf.SBDFError(f"geocoding tables cannot contain unknown geometry types ('{geom_type}')")

with warnings.catch_warnings():
warnings.simplefilter("ignore")
dataframe.spotfire_table_metadata = table_metadata
80 changes: 0 additions & 80 deletions spotfire/sbdf.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -892,12 +892,6 @@ cdef _export_obj_dataframe(obj):
except AttributeError:
table_metadata = {}
export_column_names = obj.columns.tolist()
if gpd is not None and isinstance(obj, gpd.GeoDataFrame):
export_column_names = obj.columns.tolist()
# Remove columns that will be generated by the Geometry column
for col in ["XMin", "XMax", "YMin", "YMax", "XCenter", "YCenter"]:
if col in export_column_names:
export_column_names.remove(col)
column_names = []
column_metadata = []
exporter_contexts = []
Expand Down Expand Up @@ -947,8 +941,6 @@ cdef void _export_obj_geodataframe_geometry(geometry, geometry_crs, table_metada
:param exporter_contexts: list of export context objects
"""
cdef Py_ssize_t geometry_len = len(geometry)
cdef object geometry_bounds = geometry.bounds
cdef object geometry_centroid = geometry.centroid
cdef np_c.npy_intp shape[1]
shape[0] = <np_c.npy_intp>geometry_len
cdef np_c.ndarray invalids = np_c.PyArray_ZEROS(1, shape, np_c.NPY_BOOL, 0)
Expand All @@ -966,78 +958,6 @@ cdef void _export_obj_geodataframe_geometry(geometry, geometry_crs, table_metada
context.set_arrays(values, invalids)
exporter_contexts.append(context)

# XMin
column_names.append("XMin")
column_metadata.append({"MapChart.ColumnTypeId": ["XMin"]})
context = _ExportContext()
context.set_valuetype_id(sbdf_c.SBDF_DOUBLETYPEID)
context.set_arrays(geometry_bounds["minx"].to_numpy(), invalids)
exporter_contexts.append(context)

# XMax
column_names.append("XMax")
column_metadata.append({"MapChart.ColumnTypeId": ["XMax"]})
context = _ExportContext()
context.set_valuetype_id(sbdf_c.SBDF_DOUBLETYPEID)
context.set_arrays(geometry_bounds["maxx"].to_numpy(), invalids)
exporter_contexts.append(context)

# YMin
column_names.append("YMin")
column_metadata.append({"MapChart.ColumnTypeId": ["YMin"]})
context = _ExportContext()
context.set_valuetype_id(sbdf_c.SBDF_DOUBLETYPEID)
context.set_arrays(geometry_bounds["miny"].to_numpy(), invalids)
exporter_contexts.append(context)

# YMax
column_names.append("YMax")
column_metadata.append({"MapChart.ColumnTypeId": ["YMax"]})
context = _ExportContext()
context.set_valuetype_id(sbdf_c.SBDF_DOUBLETYPEID)
context.set_arrays(geometry_bounds["maxy"].to_numpy(), invalids)
exporter_contexts.append(context)

# XCenter
column_names.append("XCenter")
column_metadata.append({"MapChart.ColumnTypeId": ["XCenter"]})
context = _ExportContext()
context.set_valuetype_id(sbdf_c.SBDF_DOUBLETYPEID)
values = np_c.PyArray_ZEROS(1, shape, np_c.NPY_FLOAT64, 0)
for i in range(geometry_len):
values[i] = geometry_centroid[i].x
context.set_arrays(values, invalids)
exporter_contexts.append(context)

# YCenter
column_names.append("YCenter")
column_metadata.append({"MapChart.ColumnTypeId": ["YCenter"]})
context = _ExportContext()
context.set_valuetype_id(sbdf_c.SBDF_DOUBLETYPEID)
values = np_c.PyArray_ZEROS(1, shape, np_c.NPY_FLOAT64, 0)
for i in range(geometry_len):
values[i] = geometry_centroid[i].y
context.set_arrays(values, invalids)
exporter_contexts.append(context)

# Table Metadata
table_metadata["MapChart.IsGeocodingTable"] = [True]
if not table_metadata.get("MapChart.IsGeocodingEnabled"):
table_metadata["MapChart.IsGeocodingEnabled"] = [True]
# Geometry types
if geometry.geom_type.nunique() != 1:
raise SBDFError("cannot convert mixed geometry types")
geom_type = geometry[0].geom_type
if geom_type == "Point":
table_metadata["MapChart.GeometryType"] = ["Point"]
elif geom_type == "LineString" or geom_type == "LinearRing":
table_metadata["MapChart.GeometryType"] = ["Line"]
elif geom_type == "MultiLineString":
table_metadata["MapChart.GeometryType"] = ["PolyLine"]
elif geom_type == "Polygon":
table_metadata["MapChart.GeometryType"] = ["Polygon"]
else:
raise SBDFError("cannot convert collections of Shapely objects")
# CRS
if geometry_crs is not None:
try:
Expand Down

0 comments on commit 9b28ac8

Please sign in to comment.