feat(B07): 배수유역도 종류화 · 평면도 드롭다운 공용 꼴 · IP 표 차례 · 용지도 라벨 2배와 상자 밑 선 자름(109-1~4)
- 배수유역도 = 기본 종류(양식 watershed · 1/6,000 한 장 · 조각 없음) · 도면 id plan_watershed · × 지우기 · 「새 종류」 양식 고르기로 다시 · 옛 폴더에 한 번만 더함 - 평면도 칸 종류 · 양식 고르기 = 공용 createSelectField(compact) - IP 표 첫 줄 맨 위 · 왼 → 오 · 위 → 아래(묶음 왼쪽 아래 기준 그대로) - 용지도 필지 정보 글 3.6(상수 하나) · 상자 밑 등고선 · 계류 · 필지 · 읍면동 · 시군구 선 잘라 냄(clip_lines 일반화) - 시험: 기본 종류 · 유역 한 장 · 지우기 · 다시 만들기 · IP 표 두 줄 · 상자 안 선 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
① 필지마다 상자 하나 · 빈 값은 빈 줄 · 쪼개진 필지는 큰 조각에 지시선
|
||||
② 구역이 한 필지 안 — 경계 없이 어느 필지인지 한 번
|
||||
③ 상자 · 지시선 끝이 노선 버퍼와 안 겹침 · 상자끼리 안 겹침 · 도면에서도 노선과 안 겹침
|
||||
④ 상자 밑 선(등고선 · 계류 · 필지 · 읍면동 · 시군구)은 잘려 상자 안 선 0 (109-4)
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -11,6 +12,7 @@ from pathlib import Path
|
||||
from shapely.geometry import LineString, Point
|
||||
from shapely.geometry import box as box_polygon
|
||||
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_IpTable import clip_lines
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_MassHaul import balloon_size
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Plan import MM
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import (
|
||||
@@ -18,9 +20,11 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import (
|
||||
layer_id,
|
||||
)
|
||||
from B07_DesignDetail.B07_DesignDetail_Router_Support_Landuse import (
|
||||
LABEL_CLIPPED,
|
||||
PARCEL_FONT,
|
||||
ROUTE_CLEAR_M,
|
||||
clip_parcels,
|
||||
label_rects,
|
||||
place_labels,
|
||||
)
|
||||
|
||||
@@ -161,3 +165,44 @@ def _polyline(entity: dict) -> LineString:
|
||||
c["shapeData"]["endPoint"] for c in children
|
||||
]
|
||||
return LineString([(p["x"], p["y"]) for p in points])
|
||||
|
||||
|
||||
def test_lines_under_label_boxes_are_cut(tmp_path: Path) -> None:
|
||||
features = [
|
||||
(
|
||||
{
|
||||
"pnu": f"P{i}{j}",
|
||||
"jibun": f"{i}{j}-1",
|
||||
"jimok": "임야",
|
||||
"parea": 9999,
|
||||
"owner_nm": "홍길동",
|
||||
},
|
||||
_polygon(_square(i * 100.0, j * 100.0, 100.0)),
|
||||
)
|
||||
for i in range(4)
|
||||
for j in range(3)
|
||||
]
|
||||
parcels = clip_parcels(_geojson(tmp_path, features), "EPSG:4326", BOX)
|
||||
route = [(0.0, 140.0), (130.0, 160.0), (260.0, 120.0), (400.0, 170.0)]
|
||||
items = place_labels(parcels, route, BOX)
|
||||
rects = label_rects(items)
|
||||
assert PARCEL_FONT == 3.6 and rects == [_rect(item) for item in items] # 그리는 상자와 같음
|
||||
# 도곽 전체를 촘촘히 가로지르는 배경 선 — 상자마다 반드시 지나감
|
||||
across = [[[0.0, y], [400.0, y]] for y in range(1, 300, 2)]
|
||||
pieces = {
|
||||
"contour": {"lines": [list(line) for line in across]},
|
||||
"stream": {"lines": [[[x, 0.0], [x, 300.0]] for x in range(1, 400, 2)]},
|
||||
"parcel": {"lines": [line for parcel in parcels for line in parcel["lines"]]},
|
||||
"emd": {"lines": [list(line) for line in across]},
|
||||
"sgg": {"lines": [list(line) for line in across]},
|
||||
"centerline": {"lines": [[list(p) for p in route]]},
|
||||
}
|
||||
clip_lines(pieces, rects, LABEL_CLIPPED)
|
||||
inner = [box_polygon(*rect).buffer(-1e-3) for rect in rects]
|
||||
for piece in LABEL_CLIPPED:
|
||||
for points in pieces[piece]["lines"]:
|
||||
line = LineString(points)
|
||||
assert all(line.intersection(area).length < 1e-6 for area in inner), piece
|
||||
# 상자 밖은 남음 · 노선은 그대로
|
||||
assert sum(LineString(p).length for p in pieces["contour"]["lines"]) > 0.5 * 150 * 400
|
||||
assert pieces["centerline"]["lines"] == [[list(p) for p in route]]
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
옛 종류 파일의 slope_outline 펼침 · 종류 업데이트(기본 넷 포함) · 확정본 풀림 · 배수시설 기호
|
||||
⑥ IP 표(94장) — 예시 검산 · 도-분-초 올림 · 장마다 그 장 IP 한 번 · 표 밑 선 자름 · 층
|
||||
⑦ 용지도 종류(93장) — 기본 종류 · 옛 종류 폴더에 한 번 더함(지우면 안 살아남) · 용지 조각 층
|
||||
⑧ 배수유역도 종류(109장) — 양식 watershed · 한 장 목록 · 지우기 · 다시 만들기 · IP 표 두 줄 차례 ·
|
||||
용지도 상자 안 선 0
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -19,6 +21,7 @@ import pytest
|
||||
|
||||
from B06_Section.B06_Section_Engine_Design import compute_cross_design
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_IpTable import (
|
||||
_place,
|
||||
curve_values,
|
||||
dms,
|
||||
ip_blocks,
|
||||
@@ -38,6 +41,7 @@ from B07_DesignDetail.B07_DesignDetail_Router_Support_Plan import (
|
||||
add_kind,
|
||||
delete_kind,
|
||||
list_kinds,
|
||||
plan_kind_items,
|
||||
read_marks,
|
||||
slope_records,
|
||||
update_kind,
|
||||
@@ -139,6 +143,7 @@ def test_kinds_add_delete_persist(tmp_path: Path) -> None:
|
||||
"대피소",
|
||||
"라이다",
|
||||
"용지도",
|
||||
"배수유역도",
|
||||
]
|
||||
added = add_kind(root, "내 평면도", ["centerline", "station", "nope"])
|
||||
assert added["pieces"] == ["centerline", "station"]
|
||||
@@ -148,14 +153,28 @@ def test_kinds_add_delete_persist(tmp_path: Path) -> None:
|
||||
add_kind(root, "빈 것", [])
|
||||
delete_kind(root, "terrain") # 기본 넷도 지움
|
||||
again = list_kinds(root) # 다시 읽어 같음
|
||||
assert [k["name"] for k in again] == ["노선배치도", "대피소", "라이다", "용지도", "내 평면도"]
|
||||
assert [k["name"] for k in again] == [
|
||||
"노선배치도",
|
||||
"대피소",
|
||||
"라이다",
|
||||
"용지도",
|
||||
"배수유역도",
|
||||
"내 평면도",
|
||||
]
|
||||
assert again[-1]["pieces"] == ["centerline", "station"]
|
||||
with pytest.raises(FileNotFoundError):
|
||||
delete_kind(root, "terrain")
|
||||
for kind in list(again):
|
||||
delete_kind(root, kind["id"])
|
||||
assert list_kinds(root) == [] # 다 지우면 비어 있음(기본 넷이 되살아나지 않음)
|
||||
assert {k["id"] for k in DEFAULT_KINDS} == {"terrain", "route", "layout", "lidar", "landuse"}
|
||||
assert {k["id"] for k in DEFAULT_KINDS} == {
|
||||
"terrain",
|
||||
"route",
|
||||
"layout",
|
||||
"lidar",
|
||||
"landuse",
|
||||
"watershed",
|
||||
}
|
||||
|
||||
|
||||
def test_refuge_marks_same_spot(tmp_path: Path) -> None:
|
||||
@@ -202,7 +221,7 @@ def test_default_kinds_keep_old_picture_and_legacy_file(tmp_path: Path) -> None:
|
||||
outline = {"road_edge", "fill_outline", "cut_outline"}
|
||||
# 옛 slope_outline 자리를 나눈 셋이 그대로 채움 · 배수시설은 옛 그림에 없음(용지도는 따로)
|
||||
for kind in DEFAULT_KINDS:
|
||||
if kind["id"] != "landuse":
|
||||
if kind["id"] not in ("landuse", "watershed"):
|
||||
assert outline <= set(kind["pieces"]) and "drainage" not in kind["pieces"]
|
||||
folder = _kinds_dir(root)
|
||||
folder.mkdir(parents=True)
|
||||
@@ -227,6 +246,7 @@ def test_update_kind_default_and_release_confirmed(tmp_path: Path) -> None:
|
||||
"id": "terrain",
|
||||
"name": "지형2",
|
||||
"pieces": ["contour", "centerline"],
|
||||
"layout": "plan",
|
||||
}
|
||||
assert [k["id"] for k in list_kinds(root)][0] == "terrain" # 차례 그대로
|
||||
drawings = _read_manifest(root)["drawings"]
|
||||
@@ -315,9 +335,9 @@ def test_landuse_kind_added_once_to_old_folder(tmp_path: Path) -> None:
|
||||
json.dumps({"name": kind["name"], "pieces": kind["pieces"], "order": order}), "utf-8"
|
||||
)
|
||||
kinds = list_kinds(root)
|
||||
assert [k["id"] for k in kinds][-1] == "landuse"
|
||||
assert kinds[-1]["pieces"] == ["contour", "centerline", "parcel", "parcel_info", "emd", "sgg"]
|
||||
assert len(list_kinds(root)) == 5 # 두 번 읽어도 한 번
|
||||
assert [k["id"] for k in kinds][-2:] == ["landuse", "watershed"] # 늘어난 기본 종류 둘
|
||||
assert kinds[-2]["pieces"] == ["contour", "centerline", "parcel", "parcel_info", "emd", "sgg"]
|
||||
assert len(list_kinds(root)) == 6 # 두 번 읽어도 한 번
|
||||
delete_kind(root, "landuse")
|
||||
assert "landuse" not in [k["id"] for k in list_kinds(root)] # 지운 것은 안 살아남
|
||||
|
||||
@@ -344,3 +364,79 @@ def test_landuse_pieces_become_layers() -> None:
|
||||
if e.get("layerId") == layer_id("parcel_info") and e["type"] == "Text"
|
||||
]
|
||||
assert labels == ["1-1", "임야", "국"]
|
||||
|
||||
|
||||
def test_watershed_kind_layout_one_sheet(tmp_path: Path) -> None:
|
||||
"""배수유역도 = 기본 종류 · 양식 watershed · 조각 없음 · 장이 여럿이어도 목록은 한 장."""
|
||||
root = tmp_path / "project"
|
||||
watershed = next(k for k in list_kinds(root) if k["id"] == "watershed")
|
||||
assert watershed == {
|
||||
"id": "watershed",
|
||||
"name": "배수유역도",
|
||||
"pieces": [],
|
||||
"layout": "watershed",
|
||||
}
|
||||
# 측점 0 ~ 2,000 m — 1/1,200 평면도는 여러 장 · 유역도는 한 장
|
||||
stations = [
|
||||
{"chainage_m": float(c), "center_x": 0.0, "center_y": float(c)} for c in range(0, 2001, 20)
|
||||
]
|
||||
items = plan_kind_items(root, {"stations": stations})
|
||||
terrain = [item for item in items if item["plan_kind"] == "terrain"]
|
||||
mine = [item for item in items if item["plan_kind"] == "watershed"]
|
||||
assert len(terrain) > 1
|
||||
assert mine == [
|
||||
{
|
||||
"id": "plan_watershed",
|
||||
"label": "배수유역도",
|
||||
"plan_kind": "watershed",
|
||||
"plan_kind_name": "배수유역도",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_watershed_kind_delete_and_add_again(tmp_path: Path) -> None:
|
||||
"""× 로 지우면 안 살아남 · 「새 종류」 양식 유역도로 다시 만듦 · 업데이트는 양식 그대로."""
|
||||
root = tmp_path / "project"
|
||||
delete_kind(root, "watershed")
|
||||
assert "watershed" not in [k["id"] for k in list_kinds(root)]
|
||||
assert "watershed" not in [k["id"] for k in list_kinds(root)] # 다시 읽어도 안 살아남
|
||||
added = add_kind(root, "배수유역도", ["contour"], "watershed") # 조각은 버림
|
||||
assert added["layout"] == "watershed" and added["pieces"] == []
|
||||
again = next(k for k in list_kinds(root) if k["id"] == added["id"])
|
||||
assert again == added
|
||||
assert any(
|
||||
item["id"] == f"plan_{added['id']}" for item in plan_kind_items(root, {"stations": []})
|
||||
)
|
||||
update_kind(root, added["id"], "유역도2", []) # 유역도 양식은 조각 없이 업데이트
|
||||
assert next(k for k in list_kinds(root) if k["id"] == added["id"])["layout"] == "watershed"
|
||||
with pytest.raises(ValueError):
|
||||
add_kind(root, "이상한 양식", ["contour"], "nope")
|
||||
|
||||
|
||||
def test_watershed_kind_added_once_to_folder_with_landuse(tmp_path: Path) -> None:
|
||||
"""용지도 표시가 있는 옛 폴더 — 배수유역도만 한 번 더해짐 · 지우면 안 살아남."""
|
||||
root = tmp_path / "project"
|
||||
folder = _kinds_dir(root)
|
||||
folder.mkdir(parents=True)
|
||||
for order, kind in enumerate(DEFAULT_KINDS[:5]):
|
||||
(folder / f"{kind['id']}.json").write_text(
|
||||
json.dumps({"name": kind["name"], "pieces": kind["pieces"], "order": order}), "utf-8"
|
||||
)
|
||||
(folder / "_landuse_added").touch()
|
||||
assert [k["id"] for k in list_kinds(root)][-1] == "watershed"
|
||||
delete_kind(root, "watershed")
|
||||
assert [k["id"] for k in list_kinds(root)][-1] == "landuse"
|
||||
|
||||
|
||||
def test_ip_table_two_lines_left_to_right_top_to_bottom() -> None:
|
||||
"""한 줄 셋까지 들어가는 폭 · 묶음 넷 — 첫 줄(1 · 2 · 3) 맨 위 왼 → 오 · 넷째는 아랫줄 왼쪽."""
|
||||
blocks = [{"name": str(i), "ia": 1.0} for i in range(1, 5)]
|
||||
block_w = 7.0 + 6.0 + 23.0
|
||||
_place(blocks, 2 * 5.0 + 3 * block_w + 1.0)
|
||||
origins = [b["origin"] for b in blocks]
|
||||
tops = [o[1] for o in origins]
|
||||
assert tops[0] == tops[1] == tops[2] > tops[3] # 첫 줄이 위
|
||||
assert origins[0][0] < origins[1][0] < origins[2][0] # 왼 → 오
|
||||
assert origins[3][0] == origins[0][0] # 다음 줄은 다시 왼쪽
|
||||
assert tops[3] == 5.0 + 8 * 5.0 # 마지막 줄이 도곽 왼쪽 아래 기준(옛 한 줄 자리와 같음)
|
||||
assert tops[0] - tops[3] == 8 * 5.0 + 3.0 # 줄 간격 그대로
|
||||
|
||||
Reference in New Issue
Block a user