Skip to content

v.to.rast: Fix crash when rasterizing points with the dense flag - #7837

Open
Pranav-error wants to merge 2 commits into
OSGeo:mainfrom
Pranav-error:fix-vtorast-dense-point-crash
Open

v.to.rast: Fix crash when rasterizing points with the dense flag#7837
Pranav-error wants to merge 2 commits into
OSGeo:mainfrom
Pranav-error:fix-vtorast-dense-point-crash

Conversation

@Pranav-error

Copy link
Copy Markdown
Contributor

Fixes #3105.

configure_plot() in raster.c calls setup_plot() in dense mode and G_setup_plot() otherwise, but plot_points() always plotted through G_plot_point(). That function uses the move and cont routines which only G_setup_plot() sets, so rasterizing a point map with -d called a null function pointer:

v.to.rast input=points output=dense use=cat type=point -d

That matches the backtrace in the issue, where frame #0 is 0x0, frame #1 is G_plot_point and dense=1.

plot_points() now branches on dense the way plot_lines() already does, and dense_line.c gains plot_point_dense() for that path. It converts with the dense state's own X/Y macros and truncates the way dense_line() does, so both paths agree on which cell a coordinate belongs to. cell_dot() and dcell_dot() already bound check before writing, so a point outside the region is dropped there rather than needing a check in the caller.

Tests: vector/v.to.rast/tests/v_to_rast_dense_test.py rasterizes a three point map with and without the flag and checks the results match. Without the fix the test fails with return code -11; with it, it passes.

Verified locally as well: v.to.rast -d on a point map exits 245 (SIGSEGV) before the change and produces the three expected cells after it.

I used an AI assistant while investigating and writing this. I understand the change and can explain it.

configure_plot() calls setup_plot() in dense mode and G_setup_plot()
otherwise, but plot_points() always plotted through G_plot_point(), which
uses the move and cont routines that only G_setup_plot() sets. Rasterizing
a point map with -d therefore called a null function pointer.

plot_points() now branches on dense the way plot_lines() already does, and
dense_line.c gains plot_point_dense() for that path. The dot routines bound
check the cell before writing, so a point outside the region is dropped
rather than clipped by the caller.

Fixes OSGeo#3105
Copilot AI lite review requested due to automatic review settings August 22, 2026 16:38

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added vector Related to vector data processing Python Related code is in Python C Related code is in C module tests Related to Test Suite labels Aug 22, 2026
@petrasovaa

Copy link
Copy Markdown
Contributor

Before diving more into it, how does this compare to #3440? (besides this is adding tests unlike #3440)

@Pranav-error

Copy link
Copy Markdown
Contributor Author

Same fix, reached independently — I missed #3440 when I opened this, and it has priority.

Two differences, one of which matters.

Rounding. #3440's plot_point() copies G_plot_where_xy(): ifloor(X(...) + 0.5). That rounding belongs to the frame the non-dense path sets up, which is cell centres:

G_setup_plot(-0.5, rows - 0.5, -0.5, cols - 0.5, ...)
  -> ifloor(-0.5 + (east - west)/ew_res + 0.5) == floor((east - west)/ew_res)

Dense mode sets up a different frame, cell edges:

setup_plot(0, rows, 0, cols, dot)
  -> X(east) == (east - west)/ew_res

so in dense mode the + 0.5 is a half-cell shift rather than a rounding. With w=0, res=1, a point at east 10.6 goes to column 10 without -d and to column 11 with #3440. This PR truncates instead, which is also what dense_line() does with its own endpoints, so -d and plain agree. Same argument on Y.

That is from reading the two setup_plot calls, not from running #3440 — the test below is what would settle it.

Tests. This adds a pytest module asserting a point map rasterises identically with and without -d. It currently uses integer coordinates, which land on cell boundaries and therefore do not distinguish the two roundings. I'll switch it to fractional coordinates so it does.

Two things on my side either way: plot_point_dense() currently sits between dense_line()'s comment block and dense_line() itself, which I need to fix; and truncation vs ifloor differ only west/north of the region, where cell_dot() drops the point anyway, so I'm happy to use ifloor if you prefer it for tidiness.

Happy for this to be closed in favour of #3440 with the rounding and the test folded in — it is 18 months older and I would rather not duplicate work again (I did that on #7839 last week). Just say which way you want it.

The coordinates the test used were whole numbers, which land on cell edges
where rounding to the nearest cell and truncating to the containing cell
agree, so the test could not tell the two apart. Move the points inside
cells and compare the cells themselves rather than only the count and sum.

Also move plot_point_dense() above dense_line()'s comment block, which it
was splitting, and say in its own comment why it truncates.
@Pranav-error

Copy link
Copy Markdown
Contributor Author

I built both versions and measured it, so here is the concrete answer to "how does this compare to #3440".

The code fix is the same. Same three files, same dense flag threaded into plot_points(), same new function in dense_line.c. #3440 is 18 months older and I missed it when I opened this.

The rounding differs, and #3440's is wrong in dense mode. #3440 copies G_plot_where_xy()'s ifloor(X(...) + 0.5). That rounding belongs to the frame the non-dense path sets up, which is cell centres; dense mode frames the region by cell edges, so the + 0.5 becomes a half-cell shift rather than a rounding:

non-dense: G_setup_plot(-0.5, rows - 0.5, -0.5, cols - 0.5, ...)  ->  ifloor(-0.5 + (east-west)/res + 0.5)
dense:     setup_plot(0, rows, 0, cols, dot)                      ->  X(east) == (east-west)/res

Measured on the three points now in the test (region 0..50, res=1), printing the centre of the cell each point landed in:

point no -d -d with #3440's rounding -d with this PR
10.6, 10.7 10.5 10.5 11.5 10.5 10.5 10.5
20.2, 20.3 20.5 20.5 20.5 19.5 20.5 20.5
30.9, 15.1 30.5 15.5 31.5 14.5 30.5 15.5

All three land in the wrong cell — one shifted in column, one in row, one in both. This PR truncates, which is what dense_line() already does with its own endpoints, so -d and plain agree.

The test now actually checks that. You were right that this adds tests, but the ones I pushed first were weak: they used whole-numbered coordinates, which sit on cell edges where both roundings agree, so they would have passed against #3440 too. 5c578a8 moves the points inside cells and compares r.stats -gn output rather than only the count and sum. It fails with #3440's rounding (that is where the table above comes from) and passes here.

Also fixed in that commit: plot_point_dense() was sitting between dense_line()'s comment block and dense_line() itself.

I have no attachment to which PR carries this. If you would rather take #3440, I am happy for this to be closed and to send the rounding correction and the test as a follow-up to it — that seems fairer to @HuidaeCho, who got there first. Just say which.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C Related code is in C module Python Related code is in Python tests Related to Test Suite vector Related to vector data processing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v.to.rast segfault converting points

3 participants