Files
Pier_v1/pierzainer_ground/pierzainer_ground/boreholes.py
T

172 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""G3 — 시추 시트 읽기 · 검사 · 양식 만들기.
엑셀 시트 "시추": 한 행 = 한 지층.
| 공번 | X | Y | 공구EL | 지층명 | 하단깊이 | 하단EL | N값 | 지하수위깊이 |
- X, Y, 공구EL, 지하수위깊이는 공마다 첫 행에만 적어도 된다.
- 하단EL 은 비워 두면 공구EL − 하단깊이 로 계산한다.
- X, Y 가 비어 있으면 지형 DXF의 시추공 위치(같은 공번)로 채운다.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from .config import GroundConfig
COLUMNS = ["공번", "X", "Y", "공구EL", "지층명", "하단깊이", "하단EL", "N값", "지하수위깊이"]
@dataclass
class Stratum:
name: str
bottom_depth: float
bottom_el: float
n_value: str | None = None
@dataclass
class Borehole:
id: str
X: float | None
Y: float | None
top_el: float | None
strata: list[Stratum] = field(default_factory=list)
gwl_depth: float | None = None
@property
def gwl_el(self) -> float | None:
if self.gwl_depth is None or self.top_el is None:
return None
return self.top_el - self.gwl_depth
@property
def names(self) -> list[str]:
return [s.name for s in self.strata]
def top_of(self, i: int) -> float:
return self.top_el if i == 0 else self.strata[i - 1].bottom_el
def _num(v) -> float | None:
if v is None or (isinstance(v, str) and not v.strip()):
return None
try:
return float(str(v).replace(",", ""))
except ValueError:
return None
def read_boreholes(path: str | Path, cfg: GroundConfig | None = None, sheet: str = "시추",
dxf_positions: dict[str, tuple[float, float]] | None = None
) -> tuple[list[Borehole], list[str]]:
"""시추 시트를 읽고 (시추공 목록, 검사 문제 목록)을 돌려준다. 문제가 있는 공은 빼지 않고 문제만 알린다."""
from openpyxl import load_workbook
cfg = cfg or GroundConfig()
wb = load_workbook(path, data_only=True)
if sheet not in wb.sheetnames:
raise ValueError(f"'{sheet}' 시트가 없습니다. 시트 목록: {wb.sheetnames}")
ws = wb[sheet]
rows = list(ws.iter_rows(values_only=True))
if not rows:
return [], ["시추 시트가 비어 있습니다."]
header = [str(h).replace(" ", "").strip() if h is not None else "" for h in rows[0]]
missing = [c for c in COLUMNS if c not in header]
if missing:
raise ValueError(f"시추 시트 머리글에 없는 열: {missing}")
col = {c: header.index(c) for c in COLUMNS}
bores: dict[str, Borehole] = {}
issues: list[str] = []
for r_i, r in enumerate(rows[1:], start=2):
bid = r[col["공번"]]
if bid is None or not str(bid).strip():
continue
bid = str(bid).strip()
b = bores.setdefault(bid, Borehole(bid, None, None, None))
for attr, c in (("X", "X"), ("Y", "Y"), ("top_el", "공구EL"), ("gwl_depth", "지하수위깊이")):
v = _num(r[col[c]])
if v is not None and getattr(b, attr) is None:
setattr(b, attr, v)
name = r[col["지층명"]]
depth = _num(r[col["하단깊이"]])
if name is None or not str(name).strip():
continue
name = str(name).strip()
if depth is None:
issues.append(f"{bid} {r_i}행: 하단깊이가 없습니다.")
continue
el = _num(r[col["하단EL"]])
n = r[col["N값"]]
b.strata.append(Stratum(name, depth, el if el is not None else float("nan"), None if n is None else str(n)))
for b in bores.values():
if (b.X is None or b.Y is None) and dxf_positions and b.id in dxf_positions:
b.X, b.Y = dxf_positions[b.id]
if b.X is None or b.Y is None:
issues.append(f"{b.id}: 좌표가 없습니다 (지형 도면에도 같은 공번이 없음).")
if b.top_el is None:
issues.append(f"{b.id}: 공구EL이 없습니다.")
prev_d, prev_rank = 0.0, -1
for s in b.strata:
if s.bottom_el != s.bottom_el and b.top_el is not None: # NaN → 계산
s.bottom_el = b.top_el - s.bottom_depth
elif b.top_el is not None and abs(b.top_el - s.bottom_depth - s.bottom_el) > 0.05:
issues.append(f"{b.id} {s.name}: 하단EL {s.bottom_el:.2f} ≠ 공구EL − 하단깊이 {b.top_el - s.bottom_depth:.2f}")
if s.bottom_depth <= prev_d:
issues.append(f"{b.id} {s.name}: 하단깊이 {s.bottom_depth}가 위 지층({prev_d})보다 깊지 않습니다.")
prev_d = s.bottom_depth
if s.name not in cfg.strata_order:
issues.append(f"{b.id}: 지층명 '{s.name}'이 지층 구분 {list(cfg.strata_order)}에 없습니다.")
else:
rank = cfg.strata_order.index(s.name)
if rank < prev_rank:
issues.append(f"{b.id}: '{s.name}'이 더 깊은 지층 아래에 있습니다 (지층 순서 확인).")
prev_rank = max(prev_rank, rank)
if not b.strata:
issues.append(f"{b.id}: 지층 행이 없습니다.")
return list(bores.values()), issues
def write_borehole_template(path: str | Path, bore_positions: list[dict], dtm=None) -> Path:
"""시추공 위치(지형 DXF)를 미리 채운 '시추' 시트 양식을 만든다. 공구EL은 DTM 값을 참고값으로 넣는다."""
from openpyxl import Workbook
from openpyxl.comments import Comment
from openpyxl.styles import Font, PatternFill
wb = Workbook()
ws = wb.active
ws.title = "시추"
ws.append(COLUMNS)
for c in ws[1]:
c.font = Font(bold=True)
c.fill = PatternFill("solid", fgColor="DDE6F0")
gray = Font(color="7F7F7F")
for b in bore_positions:
z = dtm.z_at(b["X"], b["Y"]) if dtm is not None else None
ws.append([b["id"], round(b["X"], 3), round(b["Y"], 3), None if z is None else round(z, 2),
None, None, None, None, None])
cell = ws.cell(row=ws.max_row, column=4)
if z is not None:
cell.font = gray
cell.comment = Comment("지형 DTM에서 구한 참고값입니다. 주상도의 공구 EL로 바꿔 주세요.", "pierzainer")
for i, w in enumerate([10, 13, 13, 9, 9, 9, 9, 7, 11], start=1):
ws.column_dimensions[chr(64 + i)].width = w
ws.freeze_panes = "A2"
info = wb.create_sheet("설명")
for line in [
["시추 시트 작성 방법"],
["한 행 = 한 지층. 같은 공번의 지층을 위에서 아래 순서로 적습니다."],
["X·Y·공구EL·지하수위깊이는 공의 첫 행에만 적어도 됩니다."],
["하단EL을 비우면 공구EL − 하단깊이로 계산합니다."],
["지층명: 매립토 / 퇴적토 / 풍화토 / 풍화암 / 연암 / 경암"],
["좌표: 측량 X(북) · Y(동), m"],
["회색 공구EL은 지형도에서 구한 참고값입니다."],
]:
info.append(line)
info.column_dimensions["A"].width = 70
path = Path(path)
wb.save(path)
return path