Skip to content

Speed up DataFrame conversion utilities - #1349

Merged
giswqs merged 6 commits into
opengeos:masterfrom
steps-re:perf/vector-converters
Aug 2, 2026
Merged

Speed up DataFrame conversion utilities#1349
giswqs merged 6 commits into
opengeos:masterfrom
steps-re:perf/vector-converters

Conversation

@steps-re

@steps-re steps-re commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Speeds up three conversion utilities in common.py, outputs unchanged:

  • h5_to_gdf: collect per-file DataFrames and concat once instead of concatenating inside the loop, which copies all previously read data on every iteration. Columns are built directly from the HDF5 arrays instead of a python list transpose. About 3x at 40 files / 2M rows, and the gap grows with the number of files.
  • convert_to_gdf: build point geometries with geopandas.points_from_xy instead of one shapely Point per row via apply (44x on 100k rows).
  • pandas_to_geojson: extract coordinates and properties column-wise instead of per-row iterrows (61x on 5k features). Property values now arrive as plain python scalars, so integer columns no longer break json.dump of the returned dict.

Verified outputs identical against the previous implementations on synthetic data. pytest tests/test_common.py gives the same results as a clean checkout (the 4 tile-url failures on my machine are unrelated and pre-existing).

Built with AI assistance and verified locally.

Summary by CodeRabbit

  • Performance Improvements

    • Improved HDF5-to-geographic conversion by aggregating results efficiently.
    • Accelerated tabular-to-GeoJSON conversion and geographic point creation.
  • Bug Fixes

    • Safely skip HDF5 files missing requested datasets or coordinate columns.
    • Added validation when latitude and longitude data are unavailable.
  • New Features

    • Added flexible colorbar tick configuration, including custom intervals, integer ticks, and integer formatting.

- h5_to_gdf: collect per-file DataFrames and concat once instead of
  concatenating inside the loop (which copies all previously read
  data on every iteration), and build columns directly from the HDF5
  arrays instead of a python list transpose. ~3x at 40 files / 2M
  rows, with the gap growing in the number of files.
- convert_to_gdf: build point geometries with
  geopandas.points_from_xy instead of one shapely Point per row via
  apply (44x on 100k rows).
- pandas_to_geojson: extract coordinates and properties column-wise
  instead of per-row iterrows (61x on 5k features). Property values
  now arrive as plain Python scalars, so integer columns no longer
  break json serialization of the returned dict.

Outputs verified identical against the previous implementations on
synthetic data.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c310d50a-9195-4566-b66a-59a54ae97c08

📥 Commits

Reviewing files that changed from the base of the PR and between 3eabe39 and d3edaf5.

📒 Files selected for processing (1)
  • leafmap/common.py

📝 Walkthrough

Walkthrough

leafmap/common.py updates HDF5 ingestion, GeoJSON feature construction, point geometry creation, and colorbar tick handling. The conversion paths use context-managed, column-oriented, or vectorized processing.

Changes

Data conversion optimizations

Layer / File(s) Summary
HDF5 DataFrame assembly
leafmap/common.py
h5_to_gdf uses context-managed reads, collects per-file data, skips missing inputs, concatenates once, and raises ValueError when no usable coordinates exist.
GeoJSON and point geometry construction
leafmap/common.py
pandas_to_geojson precomputes coordinates and properties. convert_to_gdf uses vectorized point creation and removes the unused Point import.

Estimated code review effort: 3 (Moderate) | ~15–30 minutes

Suggested reviewers: giswqs

Poem

A rabbit sorts columns with care,
HDF5 paths become light as air.
GeoJSON rows hop in a stream,
Vector points follow a speedy dream.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes to improve the performance of DataFrame conversion utilities.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mergify

mergify Bot commented Jul 29, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
leafmap/common.py (1)

16125-16131: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle the no-coordinate result before constructing geometry.

If all files are skipped—or the matching group contains no coordinate datasets—out_df[lon]/out_df[lat] raises KeyError. Raise a descriptive ValueError or return a schema-bearing empty GeoDataFrame instead.

Suggested guard
 out_df = pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame()
+if lat not in out_df.columns or lon not in out_df.columns:
+    raise ValueError(f"No coordinate data found for dataset {dataset!r}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/common.py` around lines 16125 - 16131, Update the GeoDataFrame
construction flow to handle an empty or coordinate-less out_df before accessing
out_df[lon] and out_df[lat]. When no files contribute coordinate data, raise a
descriptive ValueError or return a schema-bearing empty GeoDataFrame; preserve
the existing geometry creation for valid coordinate results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@leafmap/common.py`:
- Around line 16117-16120: Update the col_data comprehension in the relevant
data-column construction to replace the separate lat/lon equality checks with a
single membership test against (lat, lon), preserving the existing filtering
behavior while resolving the Ruff warnings.
- Around line 16109-16123: Update the HDF5 file handling loop around `h5py.File`
to use a context manager (`with h5py.File(file, "r") as h5:`), keeping the
existing dataset lookup, column extraction, DataFrame creation, and
missing-dataset behavior unchanged while removing manual close calls.
- Around line 16524-16530: Normalize values in the GeoJSON feature-building flow
before json.dump: replace pandas missing values, including pd.NA and NaN in both
coord_rows and prop_cols, with None; convert datetime-like values to
GeoJSON-compatible strings or numbers. Ensure geometry coordinates and feature
properties use only JSON-native values while preserving the existing feature
structure.

---

Outside diff comments:
In `@leafmap/common.py`:
- Around line 16125-16131: Update the GeoDataFrame construction flow to handle
an empty or coordinate-less out_df before accessing out_df[lon] and out_df[lat].
When no files contribute coordinate data, raise a descriptive ValueError or
return a schema-bearing empty GeoDataFrame; preserve the existing geometry
creation for valid coordinate results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2af2f975-5409-4068-925f-0a747a4a505a

📥 Commits

Reviewing files that changed from the base of the PR and between 7542826 and 3048ed5.

📒 Files selected for processing (1)
  • leafmap/common.py

Comment thread leafmap/common.py Outdated
Comment thread leafmap/common.py Outdated
Comment thread leafmap/common.py
Addresses review: h5py file handles are now closed deterministically
even if reading a dataset raises, and the coordinate column check
uses a single membership test.
@steps-re

Copy link
Copy Markdown
Contributor Author

addressed the review: h5py files now use a context manager so handles close on exceptional paths too, and the membership test is merged per the ruff hint.

on the geojson value normalization: the new column-wise extraction produces the same values for NA and datetime cells as the previous iterrows implementation did, so behavior is unchanged there. converting pd.NA to None or formatting timestamps would change existing output, happy to do that as a follow-up if you want it.

the build-and-push failure is the fork PR lacking write access to ghcr, not related to the change.

@steps-re

Copy link
Copy Markdown
Contributor Author

/claude-review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
leafmap/common.py (2)

16525-16531: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove duplicate keys from the feature literal.

properties and geometry are each declared twice; the later values silently replace the earlier ones. Keep only the populated entries to avoid masking future mistakes. (github.com)

Proposed cleanup
         "type": "Feature",
-        "properties": {},
-        "geometry": {"type": geometry_type, "coordinates": []},
         "properties": {prop: values[i] for prop, values in prop_cols.items()},
         "geometry": {"type": geometry_type, "coordinates": coords},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/common.py` around lines 16525 - 16531, Update the feature literal in
the GeoJSON feature-building block to declare properties and geometry only once,
retaining the populated values based on prop_cols, values, geometry_type, and
coords. Remove the earlier duplicate entries while preserving the subsequent
geojson["features"].append(feature) behavior.

16121-16129: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle empty or coordinate-less HDF5 results before constructing geometry.

When all files are skipped, or the selected dataset lacks lat/lon, out_df[lon] and out_df[lat] raise KeyError. Raise a descriptive ValueError or return a schema-bearing empty GeoDataFrame before Line 16128.

Proposed guard
     out_df = pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame()
+    if lat not in out_df.columns or lon not in out_df.columns:
+        raise ValueError(f"No coordinate data found for dataset {dataset!r}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@leafmap/common.py` around lines 16121 - 16129, Update the HDF5 result
handling before the GeoDataFrame construction to guard against an empty out_df
or missing latitude/longitude columns. Raise a descriptive ValueError, or return
a schema-bearing empty GeoDataFrame, before calling gpd.points_from_xy; preserve
the existing geometry creation for valid results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@leafmap/common.py`:
- Around line 16525-16531: Update the feature literal in the GeoJSON
feature-building block to declare properties and geometry only once, retaining
the populated values based on prop_cols, values, geometry_type, and coords.
Remove the earlier duplicate entries while preserving the subsequent
geojson["features"].append(feature) behavior.
- Around line 16121-16129: Update the HDF5 result handling before the
GeoDataFrame construction to guard against an empty out_df or missing
latitude/longitude columns. Raise a descriptive ValueError, or return a
schema-bearing empty GeoDataFrame, before calling gpd.points_from_xy; preserve
the existing geometry creation for valid results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fc6dadcc-a209-4b90-bf91-184b7a41cea3

📥 Commits

Reviewing files that changed from the base of the PR and between 3048ed5 and aa0f376.

📒 Files selected for processing (1)
  • leafmap/common.py

If every file is skipped or the dataset lacks the lat/lon columns,
h5_to_gdf now raises a descriptive ValueError instead of a KeyError
from the geometry construction.
@steps-re

Copy link
Copy Markdown
Contributor Author

second round addressed: h5_to_gdf now raises a descriptive ValueError when every file is skipped or the dataset has no lat/lon columns, instead of the KeyError that both the old and new code produced there.

on the duplicate-keys comment: the current feature literal declares type, properties, and geometry once each, so I believe that one is looking at a stale diff. happy to adjust if I am missing something.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@leafmap/common.py`:
- Around line 16125-16128: Update the docstring for the surrounding function
containing the lat/lon column validation to document the new ValueError raised
when all files are skipped or the latitude/longitude columns are absent,
alongside the existing filename-related ValueError cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 619e669a-1fdf-485a-b34c-8bcb5402275f

📥 Commits

Reviewing files that changed from the base of the PR and between aa0f376 and 56984df.

📒 Files selected for processing (1)
  • leafmap/common.py

Comment thread leafmap/common.py
@giswqs

giswqs commented Aug 2, 2026

Copy link
Copy Markdown
Member

/claude-review

Comment thread leafmap/common.py
Comment thread leafmap/common.py
Comment thread leafmap/common.py
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

All three inline comments posted successfully.

Code review

Bugs

  • h5_to_gdf's new pd.concat(dfs, ignore_index=True) drops the duplicated per-file index that the old per-iteration pd.concat([out_df, df]) (without ignore_index=True) produced. Values are unchanged, but the index shape differs for multi-file input — worth a conscious callout since the PR claims "outputs unchanged." Confidence: medium.
  • The new lat/lon presence check in h5_to_gdf only validates the columns exist somewhere in the concatenated result, not per-file — a mix of files where some lack lat/lon under dataset can still yield silent NaN coordinates for the affected rows instead of raising. Confidence: low-medium.

Security

  • None found.

Performance

  • The three optimizations (batched concat, points_from_xy, column-wise GeoJSON construction) are sound and match the stated intent; no issues found. Confidence: high.

Quality

  • Point import in convert_to_gdf (from shapely.geometry import Point, shape) is now unused after switching to gpd.points_from_xy. Confidence: high.
  • Verified the pandas_to_geojson int64→JSON-serialization fix (via .tolist()) is correctly reasoned and effective; the Point vs points_from_xy NaN-coordinate concern I considered doesn't apply since both route through the same shapely.points() GEOS call in shapely ≥2.0, which is what geopandas now requires.

CLAUDE.md

  • No CLAUDE.md found in the repository; nothing to check against.

- h5_to_gdf: validate lat/lon columns per file instead of only on the
  concatenated result. Files whose dataset lacks the coordinate columns
  are now skipped with a message rather than silently contributing rows
  with NaN coordinates when mixed with files that do have them.
- convert_to_gdf: drop the now-unused `Point` import, left over from
  switching point construction to `gpd.points_from_xy`.
@giswqs
giswqs merged commit 0da3a2a into opengeos:master Aug 2, 2026
19 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants