diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Landuse.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Landuse.py index 16c5c57fc..806d3c1b3 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Landuse.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Landuse.py @@ -109,6 +109,16 @@ def _parcel_info_lines(props: dict[str, Any]) -> list[str]: ] +def _enclosing_note(props: dict[str, Any]) -> str: + """구역 전체가 한 필지 안일 때 정보 묶음 맨 위 줄 — 소재지(시군구 읍면동 리)를 붙인다.""" + place = " ".join( + str(props[key]).strip() + for key in ("sgg_nm", "emd_nm", "ri_nm") + if isinstance(props.get(key), str) and props[key].strip() + ) + return f"구역 전체가 이 필지 안 ({place})" if place else "구역 전체가 이 필지 안" + + def _legend_entities(drawing_id: str, origin: tuple[float, float]) -> list[dict[str, Any]]: """범례 — 유역도에서 유역 정보표가 있던 자리(오른쪽 칸)에 놓는다.""" entities: list[dict[str, Any]] = [] @@ -217,6 +227,7 @@ def build_landuse_drawing( # 필지 경계 + 필지 정보. 도곽에 잘려 조각이 여럿인 필지도 정보는 한 번만 — # 가장 큰 조각 가운데에 적는다(구역 안 필지 수 = 정보 묶음 수). spots: dict[str, tuple[float, tuple[float, float], dict[str, Any]]] = {} + enclosing: set[str] = set() # 도곽을 통째로 감싼 필지 for index, parcel in enumerate(parcels): ring = parcel.get("ring") or [] props = parcel.get("props") or {} @@ -234,14 +245,19 @@ def build_landuse_drawing( elif label_at is not None: # 도곽을 통째로 감싼 필지 — 경계선이 없으니 지정된 자리에 정보만 적는다. size, center = 0.0, paper(tuple(label_at)) + enclosing.add(str(props.get("pnu") or f"#{index}")) else: continue key = str(props.get("pnu") or f"#{index}") if key not in spots or size > spots[key][0]: spots[key] = (size, center, props) + # 필지 경계가 하나도 안 보이면(구역이 한 필지 안) 어느 필지인지 글로 밝힌다(68-2). + no_outline = not any(len(parcel.get("ring") or []) >= 2 for parcel in parcels) jibun: list[dict[str, Any]] = [] for key, (_size, center, props) in spots.items(): lines = _parcel_info_lines(props) + if no_outline and key in enclosing: + lines = [_enclosing_note(props), *lines] top = center[1] + (len(lines) - 1) / 2.0 * _INFO_LINE_GAP for row, text in enumerate(lines): if text: diff --git a/resources/tester/test_b07_landuse_parcel_info.py b/resources/tester/test_b07_landuse_parcel_info.py index 55c81ec90..f44543a80 100644 --- a/resources/tester/test_b07_landuse_parcel_info.py +++ b/resources/tester/test_b07_landuse_parcel_info.py @@ -35,3 +35,45 @@ def test_one_label_group_per_parcel() -> None: e["shapeData"]["basePoint"]["x"] for e in texts if e["shapeData"]["label"] == "산1임" } assert split_x.pop() == pytest.approx(215.0 * MM) + + +def test_zone_inside_one_parcel_is_named_once(tmp_path) -> None: + """작은 구역이 큰 필지 하나 안에 통째로 — 경계선이 없어도 어느 필지인지 한 번 적는다 (68-2).""" + import json + + from B07_DesignDetail.B07_DesignDetail_Router_Support_Basin import _clip_parcels + + props = { + "pnu": "P1", + "sgg_nm": "봉화군", + "emd_nm": "재산면", + "ri_nm": "갈산리", + "jibun": "산77-1임", + "jimok": "임야", + "parea": 1234567, + "owner_nm": "국", + } + big = [[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0], [0.0, 0.0]] + geojson = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": props, + "geometry": {"type": "Polygon", "coordinates": [big]}, + } + ], + } + path = tmp_path / "parcels.geojson" + path.write_text(json.dumps(geojson, ensure_ascii=False), encoding="utf-8") + parcels = _clip_parcels(path, "EPSG:4326", (4.0, 4.0, 6.0, 6.0)) + assert len(parcels) == 1 and parcels[0]["ring"] == [] + + route = [(4.5, 4.5), (5.5, 5.5)] + drawing = build_landuse_drawing("landuse", "용지도", route, [], parcels, [], []) + labels = [ + e["shapeData"]["label"] for e in drawing["entities"] if e.get("layerId") == JIBUN_LAYER_ID + ] + notes = [label for label in labels if label.startswith("구역 전체가 이 필지 안")] + assert notes == ["구역 전체가 이 필지 안 (봉화군 재산면 갈산리)"] + assert labels[1:] == ["산77-1임", "임야", "1,234,567㎡", "국"]