- 엔진(자체 · NumPy · SciPy): 평면 · 쐐기(교선) · 전도 Markland(측방 한계 20° 기본) · 평면 Hoek-Bray(인장균열 없음 · 임계 · 깊이 · 수압 · kh · 수동 볼트) · 쐐기 SWedge 꼴(미끄럼 방향 8 경우 · 두 면 · 한 면 · 수압 중력식 · kh · 볼트) - 스테레오넷 좌표(등각 · 등면적 하반구 · 극점 · 대원 · 교선 · 마찰원 · 일광 엔벨로프 · 위험 영역 = 판정 부등식) - 길: 절리 작업본 E06_StabRock/joints.json(초기값 · 저장 · 초기화) · 해석 = 암 측점 깎기 쪽만 · 파생 저장본 stab_rock · 결과 · 측점 스테레오넷 - 화면: 좌 절리군 표 · 설정 / 가운데 위험 띠 · 스테레오넷 / 우 측점 결과 · 안전율 표 · E 단계 막대에 E06(끝에 덧붙임) - 시험 test_536_1(Hoek Sau Mau Ping 0.8184 · 1.0655 · Sharma 윗면 무게 · Hoek-Bray 쐐기 계수 · Kumsar · Priest 두 예제 · Markland 경계 · 투영 왕복 · 위험 영역 = 판정) · test_528 E 단계 수 4 → 5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
"""E06 스테레오넷 투영 좌표 — 하반구 · 등각(Wulff) · 등면적(Schmidt) (PLAN 536-1).
|
||
|
||
화면(SVG)은 이 좌표를 그리기만 함 — 반지름 1 원 · x = 동 · y = 북(화면은 y 를 뒤집음).
|
||
극점 · 대원 · 교선 · 마찰원 · 일광 엔벨로프 · 위험 영역(Markland 부등식과 같은 경계 —
|
||
`E06_StabRock_Engine.kinematic`)을 냄. 투영식 = 표준 구면 투영(mplstereonet 등은 대조만).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from typing import Any
|
||
|
||
from E06_StabRock.E06_StabRock_Engine import (
|
||
LATERAL_PLANAR_DEG,
|
||
LATERAL_TOPPLING_DEG,
|
||
apparent_dip,
|
||
intersection,
|
||
pole,
|
||
)
|
||
|
||
PROJECTIONS = ("equal_angle", "equal_area")
|
||
STEP_DEG = 2.0 # 곡선 표본 간격
|
||
|
||
|
||
def radius(plunge: float, kind: str) -> float:
|
||
"""선주각 → 중심에서 거리(0 = 연직 · 1 = 수평)."""
|
||
half = math.radians(90.0 - plunge) / 2.0
|
||
return math.tan(half) if kind == "equal_angle" else math.sqrt(2.0) * math.sin(half)
|
||
|
||
|
||
def project(trend: float, plunge: float, kind: str) -> list[float]:
|
||
r = radius(plunge, kind)
|
||
t = math.radians(trend)
|
||
return [round(r * math.sin(t), 5), round(r * math.cos(t), 5)]
|
||
|
||
|
||
def inverse(x: float, y: float, kind: str) -> tuple[float, float]:
|
||
"""투영 좌표 → (추향 · 선주각) — 왕복 시험 · 화면 마우스 자리 읽기."""
|
||
r = math.hypot(x, y)
|
||
half = math.atan(r) if kind == "equal_angle" else math.asin(min(1.0, r / math.sqrt(2.0)))
|
||
return math.degrees(math.atan2(x, y)) % 360.0, 90.0 - math.degrees(2.0 * half)
|
||
|
||
|
||
def _samples(lo: float, hi: float) -> list[float]:
|
||
n = max(2, int(math.ceil((hi - lo) / STEP_DEG)) + 1)
|
||
return [lo + (hi - lo) * k / (n - 1) for k in range(n)]
|
||
|
||
|
||
def great_circle(dip_dir: float, dip: float, kind: str) -> list[list[float]]:
|
||
"""면의 대원 — 주향 한끝 → 경사 방향 → 다른 끝."""
|
||
return [
|
||
project(dip_dir + b, max(0.0, apparent_dip(dip, dip_dir, dip_dir + b)), kind)
|
||
for b in _samples(-90.0, 90.0)
|
||
]
|
||
|
||
|
||
def _zone(trend_lo, trend_hi, plunge_lo, plunge_hi, kind) -> list[list[float]] | None:
|
||
"""(추향 · 선주각) 띠 → 닫힌 다각형 — plunge_lo/hi 는 추향의 함수 · 비면 None."""
|
||
ts = _samples(trend_lo, trend_hi)
|
||
lows = [plunge_lo(t) for t in ts]
|
||
highs = [plunge_hi(t) for t in ts]
|
||
keep = [k for k in range(len(ts)) if highs[k] > lows[k]]
|
||
if len(keep) < 2:
|
||
return None
|
||
outer = [project(ts[k], lows[k], kind) for k in keep]
|
||
inner = [project(ts[k], highs[k], kind) for k in reversed(keep)]
|
||
return outer + inner
|
||
|
||
|
||
def stereonet(
|
||
slope: dict,
|
||
joints: list[dict],
|
||
phi: float,
|
||
kind: str = "equal_angle",
|
||
lateral_planar: float = LATERAL_PLANAR_DEG,
|
||
lateral_toppling: float = LATERAL_TOPPLING_DEG,
|
||
) -> dict[str, Any]:
|
||
"""slope = {dip_direction, dip} · joints = [{id, dip_direction, dip}] · phi = 영역 마찰각."""
|
||
af, pf = float(slope["dip_direction"]), float(slope["dip"])
|
||
|
||
def face_app(t: float) -> float:
|
||
return apparent_dip(pf, af, t)
|
||
|
||
planes = []
|
||
for j in joints:
|
||
tr, pl = pole(j["dip_direction"], j["dip"])
|
||
planes.append(
|
||
{
|
||
"id": j["id"],
|
||
"pole": project(tr, pl, kind),
|
||
"great_circle": great_circle(j["dip_direction"], j["dip"], kind),
|
||
}
|
||
)
|
||
lines = []
|
||
for i, a in enumerate(joints):
|
||
for b in joints[i + 1 :]:
|
||
line = intersection(a, b)
|
||
if line is not None:
|
||
lines.append({"ids": [a["id"], b["id"]], "point": project(*line, kind)})
|
||
# 일광 엔벨로프(극점 꼴) = 사면 겉보기 경사로 기운 면들의 극점
|
||
envelope = [
|
||
project(af + b + 180.0, 90.0 - face_app(af + b), kind) for b in _samples(-90.0, 90.0)
|
||
]
|
||
zones = {
|
||
# 평면(극점) — 극점 추향 αf + 180 ± 측방 · 선주각 90 − ψapp ~ 90 − φ
|
||
"planar": _zone(
|
||
af + 180.0 - lateral_planar,
|
||
af + 180.0 + lateral_planar,
|
||
lambda t: 90.0 - face_app(t - 180.0),
|
||
lambda t: 90.0 - phi,
|
||
kind,
|
||
),
|
||
# 쐐기(교선) — 추향 αf ± 90 · 선주각 φ ~ ψapp
|
||
"wedge": _zone(af - 90.0, af + 90.0, lambda t: phi, face_app, kind),
|
||
# 전도(극점) — 극점 추향 αf ± 측방 · 선주각 0 ~ ψf − φ
|
||
"toppling": _zone(
|
||
af - lateral_toppling,
|
||
af + lateral_toppling,
|
||
lambda t: 0.0,
|
||
lambda t: pf - phi,
|
||
kind,
|
||
),
|
||
}
|
||
return {
|
||
"projection": kind,
|
||
"slope": {
|
||
"pole": project(*pole(af, pf), kind),
|
||
"great_circle": great_circle(af, pf, kind),
|
||
},
|
||
"planes": planes,
|
||
"intersections": lines,
|
||
# 마찰원 — 극점 꼴 = 중심에서 φ(선주각 90 − φ) · 교선 꼴 = 선주각 φ
|
||
"friction_pole_r": round(radius(90.0 - phi, kind), 5),
|
||
"friction_line_r": round(radius(phi, kind), 5),
|
||
"daylight_envelope": envelope,
|
||
"zones": zones,
|
||
}
|