fix(E04): 중심선 = 측점 중심(B06 누가) — route_points 는 접선 꺾은선이라 누가가 달라 구간 노드 · 지도 구간선이 어긋남 (533-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -184,7 +184,6 @@ def _overview(project_root: Path, root: Path) -> dict[str, Any]:
|
|||||||
for item in saved:
|
for item in saved:
|
||||||
current = run.fingerprint(finger_source, ground, item.pop("settings"))
|
current = run.fingerprint(finger_source, ground, item.pop("settings"))
|
||||||
item["stale"] = item.pop("fingerprint") != current
|
item["stale"] = item.pop("fingerprint") != current
|
||||||
points = source.db_snapshot(root).get("route_points") or []
|
|
||||||
return {
|
return {
|
||||||
"sections": run.auto_sections(stations),
|
"sections": run.auto_sections(stations),
|
||||||
"stations": [
|
"stations": [
|
||||||
@@ -196,9 +195,10 @@ def _overview(project_root: Path, root: Path) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
for s in stations
|
for s in stations
|
||||||
],
|
],
|
||||||
|
# 중심선 = 측점 중심(B06 누가 그대로 · route_points 는 접선 꺾은선이라 누가가 다름)
|
||||||
"line": [
|
"line": [
|
||||||
[float(p["model_x"]), float(p["model_y"]), float(p["chainage_m"])]
|
[float(s["center_x"]), float(s["center_y"]), float(s["chainage_m"])]
|
||||||
for p in sorted(points, key=lambda p: p["sequence_num"])
|
for s in sorted(stations, key=lambda s: float(s["chainage_m"]))
|
||||||
],
|
],
|
||||||
"saved": saved,
|
"saved": saved,
|
||||||
"defaults": SearchSettings().model_dump(),
|
"defaults": SearchSettings().model_dump(),
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from E03_StabSection.E03_StabSection_Types import DEFAULT_ROAD_SURCHARGE_KPA
|
|||||||
from E04_Stab3D import E04_Stab3D_Engine_Grid as grid_engine
|
from E04_Stab3D import E04_Stab3D_Engine_Grid as grid_engine
|
||||||
from E04_Stab3D import E04_Stab3D_Engine_Search as search_engine
|
from E04_Stab3D import E04_Stab3D_Engine_Search as search_engine
|
||||||
|
|
||||||
ENGINE_VERSION = "e04-2" # 엔진 판 — 바뀌면 지문이 달라져 다시 셈
|
ENGINE_VERSION = "e04-3" # 엔진 판 — 바뀌면 지문이 달라져 다시 셈
|
||||||
STAGE_DIR = "E04_Stab3D"
|
STAGE_DIR = "E04_Stab3D"
|
||||||
GROUND_FILE = Path("E02_StabGround") / "ground.json"
|
GROUND_FILE = Path("E02_StabGround") / "ground.json"
|
||||||
SECTION_LENGTH_M = 100.0 # 자동 구간 길이
|
SECTION_LENGTH_M = 100.0 # 자동 구간 길이
|
||||||
@@ -127,14 +127,19 @@ def _engine_station(root: Path, station: dict[str, Any]) -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def _node_mask(
|
def _node_mask(
|
||||||
root: Path, grid: dict[str, Any], start_m: float, end_m: float, half_width_m: float
|
stations: list[dict[str, Any]],
|
||||||
|
grid: dict[str, Any],
|
||||||
|
start_m: float,
|
||||||
|
end_m: float,
|
||||||
|
half_width_m: float,
|
||||||
) -> np.ndarray:
|
) -> np.ndarray:
|
||||||
"""탐색 노드 자리 = 중심선에서 half_width 안 · 가장 가까운 중심선 점의 누가가 구간 안
|
"""탐색 노드 자리 = 중심선에서 half_width 안 · 가장 가까운 중심선 점의 누가가 구간 안
|
||||||
· 가장 가까운 점이 노선 양 끝점인 셀(노선 시점 앞 · 종점 뒤)은 뺌."""
|
· 가장 가까운 점이 중심선 양 끝점인 셀(시점 앞 · 종점 뒤)은 뺌.
|
||||||
points = sorted(
|
|
||||||
source.db_snapshot(root).get("route_points") or [], key=lambda p: p["sequence_num"]
|
중심선 = 측점 중심을 이은 선(B06 누가 그대로 · route_points 는 접선 꺾은선이라 누가가 다름)."""
|
||||||
|
line = np.array(
|
||||||
|
[[float(s["center_x"]), float(s["center_y"]), float(s["chainage_m"])] for s in stations]
|
||||||
)
|
)
|
||||||
line = np.array([[p["model_x"], p["model_y"], p["chainage_m"]] for p in points], float)
|
|
||||||
# 0.5 m 로 촘촘히 — 꺾인 점 사이 셀도 바른 누가
|
# 0.5 m 로 촘촘히 — 꺾인 점 사이 셀도 바른 누가
|
||||||
dense = [line[:1]]
|
dense = [line[:1]]
|
||||||
for a, b in zip(line[:-1], line[1:]):
|
for a, b in zip(line[:-1], line[1:]):
|
||||||
@@ -199,7 +204,7 @@ def run_engine(
|
|||||||
strata = grid_engine.strata_from_grid(
|
strata = grid_engine.strata_from_grid(
|
||||||
grid, (ground or {}).get("materials"), water_depth_m=0.0 if wet else None
|
grid, (ground or {}).get("materials"), water_depth_m=0.0 if wet else None
|
||||||
)
|
)
|
||||||
mask = _node_mask(root, grid, settings["start_m"], settings["end_m"], half)
|
mask = _node_mask(stations, grid, settings["start_m"], settings["end_m"], half)
|
||||||
ratios = (1.0,) if settings["shape"] == "sphere" else (1.0, float(settings["ellipsoid_ratio"]))
|
ratios = (1.0,) if settings["shape"] == "sphere" else (1.0, float(settings["ellipsoid_ratio"]))
|
||||||
params = search_engine.SearchParams(
|
params = search_engine.SearchParams(
|
||||||
node_mask=mask,
|
node_mask=mask,
|
||||||
|
|||||||
@@ -83,16 +83,6 @@ def root(tmp_path, monkeypatch):
|
|||||||
z = _ground(yy)
|
z = _ground(yy)
|
||||||
np.savez(models / "dtm_classification.npz", x=xs, y=ys, z=z, valid_mask=np.ones_like(z, bool))
|
np.savez(models / "dtm_classification.npz", x=xs, y=ys, z=z, valid_mask=np.ones_like(z, bool))
|
||||||
monkeypatch.setattr(run.source, "cross_section", lambda r, ch: _cross(float(ch)))
|
monkeypatch.setattr(run.source, "cross_section", lambda r, ch: _cross(float(ch)))
|
||||||
monkeypatch.setattr(
|
|
||||||
run.source,
|
|
||||||
"db_snapshot",
|
|
||||||
lambda r: {
|
|
||||||
"route_points": [
|
|
||||||
{"model_x": ch, "model_y": 0.0, "chainage_m": ch, "sequence_num": i}
|
|
||||||
for i, ch in enumerate(CHAINAGES)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return tmp_path
|
return tmp_path
|
||||||
|
|
||||||
|
|
||||||
@@ -185,10 +175,10 @@ def test_node_mask_section_and_route_ends(root):
|
|||||||
x = np.arange(-30.0, 131.0, 1.0)
|
x = np.arange(-30.0, 131.0, 1.0)
|
||||||
y = np.arange(-20.0, 21.0, 1.0)
|
y = np.arange(-20.0, 21.0, 1.0)
|
||||||
grid = {"x": x, "y": y}
|
grid = {"x": x, "y": y}
|
||||||
mask = run._node_mask(root, grid, 0.0, 100.0, 12.0)
|
mask = run._node_mask(STATIONS, grid, 0.0, 100.0, 12.0)
|
||||||
xx, yy = np.meshgrid(x, y)
|
xx, yy = np.meshgrid(x, y)
|
||||||
assert not mask[xx < -0.5].any() and not mask[xx > 100.5].any()
|
assert not mask[xx < -0.5].any() and not mask[xx > 100.5].any()
|
||||||
assert not mask[np.abs(yy) > 12.0].any()
|
assert not mask[np.abs(yy) > 12.0].any()
|
||||||
assert mask[(np.abs(yy) <= 12.0) & (xx >= 1) & (xx <= 99)].all()
|
assert mask[(np.abs(yy) <= 12.0) & (xx >= 1) & (xx <= 99)].all()
|
||||||
part = run._node_mask(root, grid, 20.0, 60.0, 12.0)
|
part = run._node_mask(STATIONS, grid, 20.0, 60.0, 12.0)
|
||||||
assert not part[(xx < 19.5) | (xx > 60.5)].any() and part[(yy == 0) & (xx == 40)].all()
|
assert not part[(xx < 19.5) | (xx > 60.5)].any() and part[(yy == 0) & (xx == 40)].all()
|
||||||
|
|||||||
@@ -57,21 +57,6 @@ def project(tmp_path, monkeypatch):
|
|||||||
z = 500.0 + 0.4 * (yy - 200.0) + 0.002 * (xx - 200.0) ** 2 # 노선 옆 비탈
|
z = 500.0 + 0.4 * (yy - 200.0) + 0.002 * (xx - 200.0) ** 2 # 노선 옆 비탈
|
||||||
np.savez(models / "dtm_classification.npz", x=xs, y=ys, z=z, valid_mask=np.ones_like(z, bool))
|
np.savez(models / "dtm_classification.npz", x=xs, y=ys, z=z, valid_mask=np.ones_like(z, bool))
|
||||||
monkeypatch.setattr(run.source, "stations", lambda root: STATIONS)
|
monkeypatch.setattr(run.source, "stations", lambda root: STATIONS)
|
||||||
monkeypatch.setattr(
|
|
||||||
run.source,
|
|
||||||
"db_snapshot",
|
|
||||||
lambda root: {
|
|
||||||
"route_points": [
|
|
||||||
{
|
|
||||||
"model_x": s["center_x"],
|
|
||||||
"model_y": 200.0,
|
|
||||||
"chainage_m": s["chainage_m"],
|
|
||||||
"sequence_num": i,
|
|
||||||
}
|
|
||||||
for i, s in enumerate(STATIONS)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
async def fake_root(project_id):
|
async def fake_root(project_id):
|
||||||
return tmp_path
|
return tmp_path
|
||||||
|
|||||||
Reference in New Issue
Block a user