Source code for openworld_radio_twin.cli

import argparse
from collections.abc import Sequence
from pathlib import Path

from openworld_radio_twin.compiler import compile_scene_sync
from openworld_radio_twin.config import get_settings


def _add_server_arguments(parser: argparse.ArgumentParser) -> None:
    settings = get_settings()
    parser.add_argument("--host", default=settings.host)
    parser.add_argument("--port", type=int, default=settings.port)
    parser.add_argument("--reload", action="store_true")


def _serve(args: argparse.Namespace) -> None:
    try:
        import uvicorn
    except ImportError as exc:
        raise SystemExit(
            "The web server is not installed; install 'openworld-radio-twin[web]'"
        ) from exc
    uvicorn.run(
        "openworld_radio_twin.main:app",
        host=args.host,
        port=args.port,
        reload=args.reload,
    )


def _compile(args: argparse.Namespace) -> None:
    xml_path = compile_scene_sync(
        latitude=args.latitude,
        longitude=args.longitude,
        radius_m=args.radius,
        output=args.output,
        buildings=args.buildings,
        building_source=args.building_source,
        terrain=args.terrain,
        material_profile=args.material_profile,
        terrain_resolution_m=args.terrain_resolution,
        receiver_height_m=args.receiver_height,
        receiver_cell_size=tuple(args.receiver_cell_size),
        voxel_pitch_m=args.voxel_pitch,
        if_exists=args.if_exists,
    )
    print(xml_path)


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="owrt", description="OpenWorld Radio Twin")
    _add_server_arguments(parser)
    commands = parser.add_subparsers(dest="command")

    serve = commands.add_parser("serve", help="Run the web application")
    _add_server_arguments(serve)
    serve.set_defaults(handler=_serve)

    scene = commands.add_parser("scene", help="Compile reusable geographic scenes")
    scene_commands = scene.add_subparsers(dest="scene_command", required=True)
    compile_parser = scene_commands.add_parser("compile", help="Compile a geographic scene")
    compile_parser.add_argument("--lat", dest="latitude", type=float, required=True)
    compile_parser.add_argument("--lon", dest="longitude", type=float, required=True)
    compile_parser.add_argument("--radius", type=float, required=True)
    compile_parser.add_argument("--output", type=Path, required=True)
    compile_parser.add_argument("--buildings", choices=("auto", "none"), default="auto")
    compile_parser.add_argument(
        "--building-source",
        default="auto",
        help=(
            "auto (Netherlands: 3dbag), global (Overture/OSM), "
            "3dbag, overture, osm, or installed source ID"
        ),
    )
    compile_parser.add_argument(
        "--terrain", choices=("elevation", "flat", "none"), default="elevation"
    )
    compile_parser.add_argument("--material-profile", choices=("itu", "uniform"), default="itu")
    compile_parser.add_argument("--terrain-resolution", type=float, default=5.0)
    compile_parser.add_argument("--receiver-height", type=float, default=1.5)
    compile_parser.add_argument(
        "--receiver-cell-size",
        type=float,
        nargs=2,
        metavar=("EAST_M", "NORTH_M"),
        default=(10.0, 10.0),
    )
    compile_parser.add_argument("--voxel-pitch", type=float, default=3.0)
    compile_parser.add_argument(
        "--if-exists", choices=("reuse", "error", "replace"), default="reuse"
    )
    compile_parser.set_defaults(handler=_compile)
    return parser


[docs] def main(argv: Sequence[str] | None = None) -> None: parser = _parser() args = parser.parse_args(argv) if not hasattr(args, "handler"): _serve(args) return args.handler(args)
if __name__ == "__main__": main()