`render_list --all` with a lon/lat bbox silently skips metatiles at the range edges

已关闭 适合新手
#501 2 条评论 1 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
76/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
c
领域
backend

调研方向

从 src/render_list.c 中的边界框范围计算和渲染循环入手,尤其关注 issue 中描述的两个原因。运行提供的 render_list 命令,并将排入队列的元瓦片与覆盖每个缩放级别范围所需的不同元瓦片进行比较。完成的标准是:请求的边界框得到完整覆盖,包括边缘块,且没有元瓦片被悄然跳过。

由索引模型根据 Issue 内容生成。

描述

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

for (x = min_x; x <= current_max_x; x += METATILE) {
    for (y = min_y; y <= current_max_y; y += METATILE) {

src/render_list.c, v0.8.0 lines 468-469 / master lines 469-470

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
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

current_max_x = max_x_tmp ? max_x_tmp - 1 : max_x_tmp;
current_max_y = max_y_tmp;

src/render_list.c, v0.8.0 lines 460-461 / master lines 461-462

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.

主要语言
C++
星标
318
派生
200
平均合并
3 天 18 小时
30 天内合并 PR
35

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

openstreetmap/mod_tile 的其他 Issue

查看 openstreetmap/mod_tile 的全部 Issue

相似的 Issue

更多 C++ Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。