Skip to content

Commit 2a686e8

Browse files
committed
Document categorical rasterize: docstring + user guide section (#3482)
1 parent ee4d89c commit 2a686e8

3 files changed

Lines changed: 77 additions & 12 deletions

File tree

docs/source/user_guide/rasterize.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"cell_type": "markdown",
55
"id": "jb1yj58wq4q",
6-
"source": "## Rasterize\n\n`xrspatial.rasterize` converts vector geometries (polygons, lines, points) into a 2D `xr.DataArray`. No GDAL dependency required.\n\nThis guide covers:\n- [Basic rasterization](#Basic-rasterization) -- polygons, lines, and points\n- [Merge modes](#Merge-modes) -- controlling how overlapping geometries combine\n- [Custom merge functions](#Custom-merge-functions) -- user-defined numba-jitted merge logic\n- [Dask parallel rasterization](#Dask-parallel-rasterization) -- tile-based output chunking",
6+
"source": "## Rasterize\n\n`xrspatial.rasterize` converts vector geometries (polygons, lines, points) into a 2D `xr.DataArray`. No GDAL dependency required.\n\nThis guide covers:\n- [Basic rasterization](#Basic-rasterization) -- polygons, lines, and points\n- [Categorical columns](#Categorical-columns) -- burn string/categorical labels, readable in QGIS\n- [Merge modes](#Merge-modes) -- controlling how overlapping geometries combine\n- [Custom merge functions](#Custom-merge-functions) -- user-defined numba-jitted merge logic\n- [Dask parallel rasterization](#Dask-parallel-rasterization) -- tile-based output chunking",
77
"metadata": {}
88
}
99
],

examples/user_guide/28_Rasterize.ipynb

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,21 @@
1818
"### What you'll build\n",
1919
"\n",
2020
"1. Rasterize land-use zones with the `.xrs` accessor\n",
21-
"2. Handle overlapping polygons and interior holes\n",
22-
"3. Burn lines and points into a raster\n",
23-
"4. Compare merge modes for overlapping features\n",
24-
"5. Write a custom numba merge function\n",
25-
"6. Use multi-column properties for density mapping\n",
26-
"7. Run Dask parallel rasterization\n",
27-
"8. Combine rasterization with zonal statistics\n",
28-
"9. Compare default vs. `all_touched` rasterization\n",
29-
"10. Use the standalone `rasterize()` function with geometry pairs\n",
21+
"2. Burn a string or categorical column into a labeled raster\n",
22+
"3. Handle overlapping polygons and interior holes\n",
23+
"4. Burn lines and points into a raster\n",
24+
"5. Compare merge modes for overlapping features\n",
25+
"6. Write a custom numba merge function\n",
26+
"7. Use multi-column properties for density mapping\n",
27+
"8. Run Dask parallel rasterization\n",
28+
"9. Combine rasterization with zonal statistics\n",
29+
"10. Compare default vs. `all_touched` rasterization\n",
30+
"11. Use the standalone `rasterize()` function with geometry pairs\n",
3031
"\n",
3132
"![Rasterize preview](images/rasterize_preview.png)\n",
3233
"\n",
3334
"**Jump to a section:**\n",
34-
"[Basic rasterization](#Basic-rasterization) | [Overlapping polygons](#Overlapping-polygons) | [Lines and points](#Lines-and-points) | [Merge modes](#Merge-modes) | [Custom merge](#Custom-merge) | [Multi-column properties](#Multi-column-properties) | [Dask parallel](#Dask-parallel) | [Zonal statistics](#Zonal-statistics) | [All touched](#All-touched) | [Standalone function](#Standalone-function)"
35+
"[Basic rasterization](#Basic-rasterization) | [Categorical columns](#Categorical-columns) | [Overlapping polygons](#Overlapping-polygons) | [Lines and points](#Lines-and-points) | [Merge modes](#Merge-modes) | [Custom merge](#Custom-merge) | [Multi-column properties](#Multi-column-properties) | [Dask parallel](#Dask-parallel) | [Zonal statistics](#Zonal-statistics) | [All touched](#All-touched) | [Standalone function](#Standalone-function)"
3536
]
3637
},
3738
{
@@ -120,6 +121,58 @@
120121
"plt.tight_layout()"
121122
]
122123
},
124+
{
125+
"cell_type": "markdown",
126+
"id": "cat3482md",
127+
"metadata": {},
128+
"source": [
129+
"## Categorical columns\n",
130+
"\n",
131+
"Pass a string or categorical column straight to `column=` and `rasterize` label-encodes it for you. Each distinct label becomes an integer code, the output is an `int32` band with a `-1` nodata value, and the label map rides along in `result.attrs['category_names']` (the list index is the pixel code). A matching `attrs['category_colors']` holds one RGBA per class.\n",
132+
"\n",
133+
"The plot below burns the land-use `label` column directly, with no manual encoding step."
134+
]
135+
},
136+
{
137+
"cell_type": "code",
138+
"id": "cat3482code",
139+
"metadata": {},
140+
"source": [
141+
"# Burn the string 'label' column directly -- no manual encoding\n",
142+
"landcover = template.xrs.rasterize(gdf, column='label')\n",
143+
"\n",
144+
"names = landcover.attrs['category_names']\n",
145+
"print('category_names:', names)\n",
146+
"print('dtype:', landcover.dtype, '| nodata:', landcover.attrs['nodata'])\n",
147+
"\n",
148+
"# Colors the encoder assigned, scaled to 0-1 for matplotlib\n",
149+
"colors = [tuple(c / 255 for c in rgba)\n",
150+
" for rgba in landcover.attrs['category_colors']]\n",
151+
"cmap = ListedColormap(colors)\n",
152+
"\n",
153+
"fig, ax = plt.subplots(figsize=(10, 4))\n",
154+
"landcover.where(landcover >= 0).plot.imshow(\n",
155+
" ax=ax, cmap=cmap, add_colorbar=False, vmin=0, vmax=len(names) - 1)\n",
156+
"ax.legend(handles=[Patch(facecolor=colors[i], label=name)\n",
157+
" for i, name in enumerate(names)],\n",
158+
" loc='upper right', fontsize=10, framealpha=0.9)\n",
159+
"ax.set_title('Rasterized by string label')\n",
160+
"ax.set_axis_off()\n",
161+
"plt.tight_layout()"
162+
],
163+
"execution_count": null,
164+
"outputs": []
165+
},
166+
{
167+
"cell_type": "markdown",
168+
"id": "cat3482alert",
169+
"metadata": {},
170+
"source": [
171+
"<div class=\"alert alert-block alert-info\">\n",
172+
"<b>Labels travel to QGIS.</b> Writing this result with <code>to_geotiff(landcover, 'landcover.tif')</code> also writes a <code>landcover.tif.aux.xml</code> sidecar holding the category names and colors. GDAL reads the sidecar, so the file opens in QGIS showing the class names instead of bare codes. Keep the sidecar next to the <code>.tif</code> when you move the file. <code>open_geotiff</code> restores <code>category_names</code> / <code>category_colors</code> back onto <code>attrs</code>.\n",
173+
"</div>"
174+
]
175+
},
123176
{
124177
"cell_type": "markdown",
125178
"id": "wtyzoml2vc",
@@ -596,4 +649,4 @@
596649
},
597650
"nbformat": 4,
598651
"nbformat_minor": 5
599-
}
652+
}

xrspatial/rasterize.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4189,6 +4189,18 @@ def rasterize(
41894189
Name of the GeoDataFrame column whose values are burned into
41904190
the raster. Ignored when ``geometries`` is a list of pairs.
41914191
Mutually exclusive with ``columns``.
4192+
4193+
A string or categorical column is label-encoded: each distinct
4194+
label gets an integer code ``0..N-1`` and the result is an
4195+
``int32`` band with a ``-1`` nodata sentinel (unless ``dtype`` /
4196+
``fill`` are passed explicitly). Plain string/object columns are
4197+
ordered lexically; an existing pandas ``Categorical`` keeps its
4198+
declared order. The label map is stored on the result as
4199+
``attrs['category_names']`` (index == pixel code) plus an
4200+
auto-generated ``attrs['category_colors']`` (one RGBA per class).
4201+
``to_geotiff`` writes these to a PAM ``<file>.aux.xml`` sidecar
4202+
so GDAL/QGIS display the class names, and ``open_geotiff`` reads
4203+
them back.
41924204
columns : list of str, optional
41934205
Names of multiple GeoDataFrame columns to pass as a properties
41944206
array to the merge function. Mutually exclusive with ``column``.

0 commit comments

Comments
 (0)