ITADN

Merge segmented ways to improve labels and shields

#5229Pull Requestleijurv 创建于 2026-05-19
L
leijurvcommented
Fixes #951 When a road is split across many ways that all share `name`, `ref`, and highway tags (such as at admin boundaries, when maxspeed changes, etc), each of these individual ways are labeled independently. This results in too many, or too few, labels and shields on what is really one logical road. As described in #951. Here's a representative test case that I added to my Postgres. The top road is 1 way. The bottom road has 5 nodes connected by 4 ways. Before this PR, they rendered quite differently: <img width="400" alt="Screen Shot 2026-05-18 at 9 54 26 PM" src="https://github.com/user-attachments/assets/207094d8-873b-4f6f-b004-f215b33eff2e" /> After this PR, they render (almost) exactly the same (asterisk see later): <img width="400" alt="Screen Shot 2026-05-18 at 9 53 02 PM" src="https://github.com/user-attachments/assets/a8f8c1e2-68a9-4c57-9f71-004607f86437" /> Changes proposed in this pull request: - **Join up linestrings that have identical references and highway types** - Three datasources will now GROUP BY the set of columns that control their rendering: `roads-text-ref`, `roads-text-name`, `roads-text-ref-minor`. In each case, we collect the ways in each group, and call this magical Postgres incantation: `(ST_Dump(ST_LineMerge(ST_Collect(way)))).geom`. First, `ST_LineMerge` looks at the collection of ways, and fuses anything that shares an exact endpoint. Importantly, if a way just crosses another way (while sharing rendering information), that is insufficient - they have to actually touch _at their endpoint_, which practically means they share an OSM node as an endpoint. There is also a `_link` check that is intentionally preserved so that the linestring building is not "distracted" by junctions and offramps. Even though such an offramp might be rendered no different to the highway, we put all offramps in a separate partition. Otherwise the linestring would be interrupted at every offramp because `ST_LineMerge` stops the linestring whenever it reaches an intersection of 3 or more lines. `ST_LineMerge` is run independently in each group/partition, so most of the time it will return a single coherent linestring. But some of the time it will fail to merge everything. Not a problem, maybe there are two roads with the exact same name without actually touching. In that case, `ST_Dump` brings us back down to those individual linestrings again, it's the opposite of `ST_Collect`. - Directional arrows were wrong in my first version, this is because `ST_LineMerge` completely forgets about the orientation of each line. I replaced `#roads-text-name::directions` with a new path that is not going to do any merging, `#roads-direction-arrows`. In other words, we had to keep arrows rendered exactly as they were, but to do that they had to be separated from `roads-text-name`. - I did not change road casing/fill. Technically, it changes a few pixels a teensy bit if you apply the line merging there, because of the rounded corners at the end of each way, but this is overkill in my opinion. [The PostGIS documentation](https://postgis.net/docs/ST_LineMerge.html) for `ST_LineMerge` has these images that make it clear, blue is the "before" and red is the "after": <img width="200" height="200" alt="st_linemerge01" src="https://github.com/user-attachments/assets/b5508d34-a202-4327-83fd-b2cf403a1fc9" /> <img width="200" height="200" alt="st_linemerge02" src="https://github.com/user-attachments/assets/0741c2b2-e98d-462d-a876-df643ae61215" /> This demonstrates how 1. direction is forgotten 2. when three lines come together, `ST_LineMerge` keeps them separate (now you can imagine why offramps were a problem). You may wonder: why not turn on `directed => true` in our call to `ST_LineMerge`? Because the vast majority of roads are not actually one-way roads, and that option would affect **all** roads in the tile, meaning a change in the node order of the way would cause a break in the labeling for all roads. <details> <summary> Asterisk from earlier </summary> In order to get the two roads to render **identically**, I had to expand some bboxes, specifically making the labels and shields use `ST_Expand(!bbox!, 2000)`. You can see where I removed that code here https://github.com/leijurv/openstreetmap-carto/commit/aba93ac770c6b8bb4c84a98e0f12abd4976b4a64 So technically that earlier screenshot is not from this exact code with the regular bbox. This is what it looks like for real, as you can see the shields are close but I could not get them to be identical no matter how hard I tried unless I cheated a little bit and selected a larger area due to tile boundary problems. Forgive me, I wanted the impressive screenshot with a truly identical result 🙏 <img width="400" alt="Screen Shot 2026-05-18 at 9 55 27 PM" src="https://github.com/user-attachments/assets/db28b5ca-aea9-45d4-a846-636f73dc0ead" /> </details> Test rendering. The main change is way more labels are shown. The shields are a bit different but pretty similar. `#13/40.7408/-73.9584` <img width="3336" height="1910" alt="nyc png" src="https://github.com/user-attachments/assets/102178a6-70cc-42b3-90df-e1e0824bbfb7" /> `#13/52.2321/20.9672` <img width="3336" height="1910" alt="warsaw png" src="https://github.com/user-attachments/assets/7c3f9f59-691f-494b-97bf-6e80fb2837a5" /> `#13/37.7610/-122.4440` <img width="3336" height="1910" alt="sf png" src="https://github.com/user-attachments/assets/25ec412d-b3fa-44d5-b334-2ad622c7a750" />
合并状态:未合并 14 条评论