eovolkov-ivix created an issue (openstreetmap/mod_tile#501)
## Summary
When `render_list --all` is given `--min-lon/--max-lon/--min-lat/--max-lat`, it
enqueues fewer
metatiles than the bounding box covers. Tiles in the skipped blocks are never
rendered, and
`render_list` exits `0` with no warning.
On one real run over the continental USA at z0-8 it enqueued **32** metatiles
where the box needs
**41** — a 22% shortfall, concentrated at the south and east edges of the box.
Two independent causes, both in the same block of `src/render_list.c`. They are
separable; the
first accounts for most of the loss.
Version: **0.8.0** (also reproduced by inspection in **v0.8.1** and `master` —
the relevant lines
are unchanged).
## Cause 1 — the render loop starts at an unaligned coordinate
```c
for (x = min_x; x <= current_max_x; x += METATILE) {
for (y = min_y; y <= current_max_y; y += METATILE) {
```
<sub>`src/render_list.c`, v0.8.0 lines 468-469 / `master` lines 469-470</sub>
`min_x` and `min_y` come straight from `lon2tilex()` / `lat2tiley()` and are
arbitrary tile
coordinates, not metatile-aligned. `enqueue()` is then called with them, and
the coordinate is
rounded down to the containing metatile downstream — which is why rendering
works at all, and why
the problem is invisible rather than an error.
Because the walk *starts* unaligned, stepping by `METATILE` can step past the
final block. The loop
runs `floor((max - min) / METATILE) + 1` times, but the box spans
`floor((max - align(min)) / METATILE) + 1` blocks. When `min % METATILE` is
large enough that the
remainder crosses a boundary, the last row or column is never enqueued.
Worked example, z8, y axis, `min_y = 87`, `max_y = 110`:
```
iterations: 87, 95, 103 (111 > 110, stop)
blocks hit: 80, 88, 96
blocks needed: 80, 88, 96, 104 <-- 104 covers rows 104-110, never enqueued
```
### Suggested fix
```c
for (x = min_x - (min_x % METATILE); x <= current_max_x; x += METATILE) {
for (y = min_y - (min_y % METATILE); y <= current_max_y; y += METATILE) {
```
Both values are non-negative here, so `%` is well defined. This only ever
*adds* the partial edge
blocks; no currently-rendered tile changes.
## Cause 2 — `current_max_x` is decremented but `current_max_y` is not
```c
current_max_x = max_x_tmp ? max_x_tmp - 1 : max_x_tmp;
current_max_y = max_y_tmp;
```
<sub>`src/render_list.c`, v0.8.0 lines 460-461 / `master` lines 461-462</sub>
With `lon2tilex()` defined as `floor((lon + 180) / 360 * 2^z)`, `max_x_tmp` is
the tile *containing*
`max_lon`, so it is part of the box and the `- 1` drops it. The decrement is
only correct in the
special case where `max_lon` falls exactly on a tile boundary. The latitude
axis gets no equivalent
adjustment, so the two axes disagree about whether the maximum is inclusive.
Worked example, z8, `max_lon = -66.9`:
```
lon2tilex(-66.9, 8) = floor(80.42) = 80 tile 80 spans lon [-67.5, -66.09],
contains -66.9
current_max_x = 79 tile column 80 excluded
```
### Suggested fix
`current_max_x = max_x_tmp;`, matching the y axis. Where `max_lon` does sit
exactly on a boundary
this renders one extra column, which seems clearly preferable to dropping a
needed one — and it
makes the two axes consistent.
## Reproduction
```
render_list --all --map default --num-threads 4 \
--min-zoom 0 --max-zoom 8 \
--min-lon -125.0 --max-lon -66.9 --min-lat 24.4 --max-lat 49.4
```
`render_list`'s own log lines give the declared ranges. "Blocks needed" is the
count of distinct
metatiles covering that range; "enqueued" is what the loop above produces:
| zoom | declared range | blocks needed | enqueued | short |
|------|---------------------------|---------------|----------|-------|
| 0 | (0, 0) → (0, 0) | 1 | 1 | — |
| 1 | (0, 0) → (0, 0) | 1 | 1 | — |
| 2 | (0, 1) → (0, 1) | 1 | 1 | — |
| 3 | (1, 2) → (1, 3) | 1 | 1 | — |
| 4 | (2, 5) → (4, 6) | 1 | 1 | — |
| 5 | (4, 10) → (9, 13) | 2 | 1 | 1 |
| 6 | (9, 21) → (19, 27) | 4 | 2 | 2 |
| 7 | (19, 43) → (39, 55) | 6 | 6 | — |
| 8 | (39, 87) → (79, 110) | 24 | 18 | 6 |
| | | **41** | **32** | **9** |
`render_list` then reports:
```
** Message: Metatiles rendered:
** Message: 32 in 2314.60 seconds (0.01/s)
```
The enqueued column is not measured — it is what Cause 1 predicts — and it
matches the reported
total of 32 exactly, as well as the per-zoom request counts in the timing
summary. Counting `.meta`
files on disk afterwards confirms the same gaps.
z7 is unaffected because its remainders happen not to cross a block boundary;
that is luck, not
correctness.
Note the table understates the total loss, because the declared ranges it is
computed from are
themselves already clipped by Cause 2 — the z8 row is missing column 80 before
the loop even runs.
## Impact
The skipped blocks lie along the edges of the requested box, so a pre-render
pass appears to
succeed while leaving holes exactly where a user panning to the edge of their
region will land.
With `ModTileMissingRequestTimeout` set, those requests return 404 while the
render is queued, so
the gap surfaces as intermittent missing tiles well after the pre-render was
declared complete.
--
Reply to this email directly or view it on GitHub:
https://github.com/openstreetmap/mod_tile/issues/501
You are receiving this because you are subscribed to this thread.
Message ID: <openstreetmap/mod_tile/issues/[email protected]>_______________________________________________
Tile-serving mailing list
[email protected]
https://lists.openstreetmap.org/listinfo/tile-serving