feat: add native road compiler provider

This commit is contained in:
2026-08-14 15:19:57 +08:00
parent e1f3fc10ca
commit 65cf8b96d9
14 changed files with 385 additions and 83 deletions

View File

@@ -657,6 +657,8 @@ def export(args):
raise RuntimeError("Dynamic traffic signal collection is empty")
export_glb(args["dynamic_glb"], dynamic_meshes)
for group, key in ((0, "countdown_0_glb"), (1, "countdown_1_glb")):
if not args.get(key):
continue
if not countdown_meshes[group]:
raise RuntimeError("Traffic countdown collection %d is empty" % group)
export_glb(args[key], countdown_meshes[group])

View File

@@ -110,7 +110,7 @@ TREE_STYLES = frozenset(("natural", "procedural")) | frozenset(_tree.MODEL_STYLE
def cli_args():
values = {"osm": None, "geojson": None, "output": None, "render": None,
values = {"osm": None, "geojson": None, "native_road": None, "output": None, "render": None,
"office_overrides": "", "tree_style": "natural"}
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
i = 0
@@ -789,8 +789,23 @@ def build(args):
_features.dispatch_ways(ways, projector, way_handlers)
geojson_dir = args.get("geojson")
native_road_dir = args.get("native_road")
road_counts = {}
if geojson_dir and os.path.isdir(geojson_dir):
if native_road_dir:
if not os.path.isdir(native_road_dir):
raise RuntimeError("--native-road directory does not exist: " + native_road_dir)
material_layers = {layer["id"]: layer for layer in catalog.ROAD_LAYERS}
for source in catalog.NATIVE_ROAD_LAYERS:
target = source["material_layer"]
layer = material_layers[target]
source_path = os.path.join(native_road_dir, "layers", source["source"] + ".geojson")
count = _roads.assemble_geojson_layer(
source_path, source["source"], projector, roads_c,
road_mats[target], layer["z"])
if count == 0:
raise RuntimeError("Native road layer has no usable geometry: " + source_path)
road_counts[source["source"]] = count
elif geojson_dir and os.path.isdir(geojson_dir):
for problem in catalog.check_layers(geojson_dir):
print("Layer catalog warning:", problem)
for layer in catalog.ROAD_LAYERS:
@@ -799,7 +814,7 @@ def build(args):
os.path.join(geojson_dir, layer_id + ".geojson"), layer_id,
projector, roads_c, road_mats[layer_id], layer["z"])
if road_counts.get("road_surface", 0) == 0:
if not native_road_dir and road_counts.get("road_surface", 0) == 0:
_roads.assemble_osm_fallback(
ways, projector, roads_c, road_mats["road_surface"])
@@ -910,6 +925,7 @@ def build(args):
scene.render.filepath = args["render"]
scene["source_osm"] = args["osm"]
scene["source_geojson"] = geojson_dir or ""
scene["source_native_road"] = native_road_dir or ""
scene["osm_bounds"] = json.dumps(bounds, ensure_ascii=True)
scene["building_count"] = counts["building_count"]
scene["industrial_building_count"] = counts["industrial_count"]

View File

@@ -48,6 +48,14 @@ ROAD_LAYERS = [
SCENE_STYLE_FILE = "osm2streets_scene_style.json"
# Native-road output intentionally maps into existing scene material layers.
# It is a provider adapter, not a second scene-layer registry.
NATIVE_ROAD_LAYERS = (
{"source": "road_surface", "material_layer": "road_surface"},
{"source": "intersection_surface", "material_layer": "intersection_surface"},
{"source": "sidewalk_surface", "material_layer": "sidewalks"},
)
# Material specs. `kind` selects the builder:
# solid — flat base colour

View File

@@ -30,7 +30,7 @@ from osmassets.geom import (
sample_tree_row,
signed_polygon_area,
)
from osmassets.catalog import ROAD_LAYERS
from osmassets.catalog import NATIVE_ROAD_LAYERS, ROAD_LAYERS
from osmassets.osm import Projector, parse_height, parse_osm, tags
@@ -46,6 +46,24 @@ class RoadLayerCatalogTest(unittest.TestCase):
self.assertNotEqual(layers["road_surface"]["z"],
layers["intersection_surface"]["z"])
def test_native_provider_maps_to_existing_material_layers(self):
layers = {layer["id"] for layer in ROAD_LAYERS}
self.assertEqual(
[(entry["source"], entry["material_layer"])
for entry in NATIVE_ROAD_LAYERS],
[("road_surface", "road_surface"),
("intersection_surface", "intersection_surface"),
("sidewalk_surface", "sidewalks")])
self.assertTrue(all(entry["material_layer"] in layers
for entry in NATIVE_ROAD_LAYERS))
def test_cesium_export_keeps_signal_assets_optional(self):
exporter = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "export_cesium.py")
with open(exporter, encoding="utf-8") as handle:
source = handle.read()
self.assertIn('if not args.get(key):\n continue', source)
class GeometryRingsTest(unittest.TestCase):
def test_polygon_keeps_only_the_exterior_ring(self):