Merge remote-tracking branch 'origin/dev' into sub_laptop_3
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# 예정노선 등록 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
- 여러 해에 걸친 예정노선의 기본 정보(사업 연도 · 지역 · 임도 종류 · 설계속도 · 이름)를 넣고 새 예정노선 프로젝트를 만드는 첫 화면임.
|
||||
- 대시보드의 [+] 에서 「예정노선」 카드를 고르면 이 화면이 열림.
|
||||
- 도면 관련 칸은 없음. 노선의 실제 위치는 만든 뒤 다음 화면(1단계 기준 지정)에서 정함.
|
||||
- 예정노선은 **산림청만** 만들 수 있음. 산림청 = 시스템 관리자 또는 메일 주소가 `.go.kr` 로 끝나는 사용자임.
|
||||
|
||||
## 작업 순서
|
||||
1. 대시보드에서 [+] 를 누르고 「예정노선」 카드를 고름. 산림청이 아니면 카드가 흐리게 보이고 누르면 「예정노선은 산림청(시스템 관리자 · go.kr 메일)만 만들 수 있습니다.」 안내가 나옴.
|
||||
2. 「사업 연도 시작」 · 「사업 연도 끝」 에 사업 기간을 넣음. 한 해짜리면 두 칸을 같은 해로 둠.
|
||||
3. 「사업 지역」 을 적음. 필수 칸임.
|
||||
4. 「임도 종류」 를 고르고, 그에 맞춰 「설계속도」 를 고름.
|
||||
5. 「프로젝트명」 을 확인함. 위 칸을 채우면 자동으로 이름이 만들어지며, 직접 고쳐도 됨.
|
||||
6. 필요하면 「노선 시작 누가거리 (m)」 · 「노선 종료 누가거리 (m)」 를 넣음. 비워 두어도 됨.
|
||||
7. [등록] 을 누름. 칸에 문제가 있으면 해당 칸 아래에 빨간 안내가 나오고 등록되지 않음.
|
||||
|
||||
## 단추
|
||||
| 단추 | 하는 일 |
|
||||
| --- | --- |
|
||||
| [?] | 이 도움말을 엶 |
|
||||
| [취소] | 등록하지 않고 대시보드로 돌아감 |
|
||||
| [등록] | 입력한 값으로 예정노선 프로젝트를 만들고 곧바로 D02 「기준 지정」 화면으로 감 |
|
||||
|
||||
## 결과 읽는 법
|
||||
- 등록에 성공하면 「프로젝트를 만들었습니다.」 알림이 뜨고, 만든 프로젝트가 현재 프로젝트가 되어 「기준 지정」 화면(지도 화면)이 열림.
|
||||
- 대시보드 프로젝트 표에도 종류 「예정노선」 으로 올라감. 표의 단계 막대(기준 지정 · 노선 분석 · 확정 · 내보내기)를 누르면 그 단계 화면으로 감.
|
||||
- 실패하면 서버가 알려 준 까닭이 알림으로 뜨고 이 화면에 머묾.
|
||||
|
||||
## 알아 둘 것
|
||||
- 칸마다 뜻과 기본값은 「칸 뜻」 문서에 있음.
|
||||
- 임도 종류 · 설계속도는 임도설계(B 그룹)와 같은 기준을 씀. 등록 뒤 이 화면에서 고치는 방법은 코드에서 확인하지 못함.
|
||||
- 누가거리(노선을 따라 잰 거리) 두 칸은 저장만 되며, 다음 화면들에서 쓰이는 곳은 확인하지 못함. 「분석 뒤 실제 누가와 다르면 차이 표시」 는 준비 중으로 봄.
|
||||
- 등록 직후에는 노선 위치가 비어 있음. 지점을 정하고 저장해야 분석 단계로 갈 수 있음.
|
||||
- 지정받은 다른 회사 구성원은 예정노선을 보기만 할 수 있고 만들거나 고칠 수 없음.
|
||||
@@ -0,0 +1,36 @@
|
||||
# 칸 뜻
|
||||
|
||||
| 칸 | 뜻 | 기본값 | 범위 · 규칙 |
|
||||
| --- | --- | --- | --- |
|
||||
| 사업 연도 시작 | 사업 첫 해 | 올해 | 2000 ~ 2100 의 정수 |
|
||||
| 사업 연도 끝 | 사업 마지막 해 | 올해 | 시작 연도와 같거나 뒤여야 함 · 2100 까지 |
|
||||
| 사업 지역 | 사업이 있는 지역 이름 | 비어 있음 | 필수 · 글자 100 자까지 · 예: 울진군 금강송면 |
|
||||
| 종류 | 프로젝트 종류 | 「노선 계획」 | 고정 · 고칠 수 없음 |
|
||||
| 임도 종류 | 간선임도 · 산불진화임도 · 작업임도 중 하나 | 간선임도(목록 맨 위) | 설계속도 선택지와 이름 조합에 쓰임 |
|
||||
| 설계속도 | 노선을 설계하는 기준 속도 | 20 km/h | 아래 표 참조 |
|
||||
| 프로젝트명 | 대시보드에 보이는 이름 | 자동 조합 | 필수 · 글자 255 자까지 |
|
||||
| 노선 시작 누가거리 (m) | 노선 첫 지점의 측점 값 | 비어 있음 | 0 이상 · 선택 |
|
||||
| 노선 종료 누가거리 (m) | 노선 끝 지점의 계획 목표 거리 | 비어 있음 | 시작 값보다 커야 함 · 선택 |
|
||||
|
||||
## 설계속도 선택지
|
||||
| 임도 종류 | 고를 수 있는 값 |
|
||||
| --- | --- |
|
||||
| 간선임도 | 20 · 30 · 40 km/h |
|
||||
| 산불진화임도 | 20 · 30 · 40 km/h |
|
||||
| 작업임도 | 20 km/h 만 |
|
||||
|
||||
- 임도 종류를 바꾸면 설계속도 목록이 바뀌고 값이 가장 낮은 값(20 km/h)으로 돌아감.
|
||||
- 임도설계(B 그룹)와 같은 기준이며, 허용되지 않는 값이 서버에 오면 20 km/h 로 처리함.
|
||||
|
||||
## 프로젝트명 자동 조합
|
||||
- 「사업 연도」 · 「사업 지역」 · 「임도 종류」 를 띄어 이어 붙임. 예: 2026~2028 울진군 금강송면 간선임도
|
||||
- 연도가 여러 해이면 「시작~끝」 꼴이고, 한 해이면 해 하나만 들어감.
|
||||
- 이름을 직접 고치기 전에는 위 칸을 바꿀 때마다 통째로 다시 만들어짐.
|
||||
- 이름을 고친 뒤에는 위 칸이 바뀌어도 자동 조각이 차지한 부분만 새 값으로 바뀌고, 직접 넣은 글은 그대로 남음. 직접 지운 조각은 되살아나지 않음.
|
||||
|
||||
## 입력 오류 안내
|
||||
- 시작 연도가 2000 ~ 2100 밖이면 숫자 범위 안내가 나옴.
|
||||
- 끝 연도가 시작보다 앞이면 「끝 연도는 시작 연도와 같거나 뒤여야 합니다.」 가 나옴.
|
||||
- 지역이나 이름이 비면 필수 입력 안내가 나옴.
|
||||
- 종료 누가거리가 시작 이하이면 「종료 누가거리는 시작보다 커야 합니다.」 가 나옴.
|
||||
- 산림청이 아닌 계정으로 등록하면 서버가 「예정노선은 산림청만 만들 수 있습니다.」 로 막음. 회사에 연결되지 않은 계정도 막힘.
|
||||
@@ -0,0 +1,59 @@
|
||||
# 기준 지정 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
- 예정노선 1단계. 사업 연도마다 노선의 **시점**과 **종점**을 지도 위에 정하는 화면임.
|
||||
- 가운데 = 카카오맵(지점과 연도별 선이 그려짐). 왼쪽 = 「연도 지점」 표(지점 이름 · 경도 · 위도 입력칸).
|
||||
- 정한 지점은 다음 단계(노선 분석)가 길을 찾는 기준이 됨.
|
||||
- 지점 저장은 산림청만 할 수 있음. 지정받은 다른 회사는 보기만 가능하며 저장하면 「예정노선 지점은 산림청만 고칠 수 있습니다.」 로 막힘.
|
||||
|
||||
## 작업 순서
|
||||
1. 화면이 열리면 사업 연도 수 + 1 개의 지점 줄이 표에 나타남. 예: 2026 ~ 2027 두 해이면 「2026 시점」 · 「2026 종점 · 2027 시점」 · 「2027 종점」 세 줄.
|
||||
2. 처음에는 비어 있는 첫 줄이 놓을 지점으로 잡혀 있음(줄이 강조되어 보임).
|
||||
3. 지도를 눌러 그 지점을 놓음. 놓으면 다음 빈 지점이 자동으로 놓을 지점이 됨.
|
||||
4. 놓은 뒤 고치려면 지도의 표지를 끌어 옮기거나, 표의 경도 · 위도 칸에 값을 직접 넣음.
|
||||
5. 모든 줄이 채워지면 연도마다 다른 색의 선이 지점 사이를 이음.
|
||||
6. [저장] 또는 [>] 를 누름.
|
||||
|
||||
## 지점 정하는 방법
|
||||
| 방법 | 하는 법 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| 지도 누름 | 줄 옆 📍 로 놓을 지점을 고른 뒤 지도를 누름 | 그 자리에 표지가 놓임 |
|
||||
| 표지 끌기 | 지도의 표지를 끌어 놓음 | 표의 값이 따라 바뀜 |
|
||||
| 좌표 입력 | 경도 · 위도 두 칸에 값을 넣고 칸을 벗어남 | 값이 맞으면 표지가 옮겨짐 · 틀리면 칸이 빨갛게 표시됨 |
|
||||
|
||||
- 놓을 지점을 고르지 않고 지도를 누르면 「지도에 놓을 지점 고르기」 안내가 나옴.
|
||||
- 다른 프로그램에서 구한 좌표는 경도 · 위도(WGS84, 소수 7 자리까지 반영)로 넣을 것.
|
||||
- 경도는 120 ~ 135, 위도는 30 ~ 45 범위만 받음.
|
||||
|
||||
## 연도 표 읽는 법
|
||||
| 줄 이름 | 뜻 |
|
||||
| --- | --- |
|
||||
| 첫 연도 시점 | 노선 전체의 출발점 |
|
||||
| 앞 연도 종점 · 다음 연도 시점 | 한 점이 두 역할. 앞 연도의 끝이자 다음 연도의 시작 |
|
||||
| 마지막 연도 종점 | 노선 전체의 도착점 |
|
||||
|
||||
- 직전 연도 종점과 다음 연도 시점은 항상 같은 점이며, 하나만 옮기면 두 연도의 선이 함께 움직임.
|
||||
- 줄 색과 지도 선 색은 연도별로 대응함(6 색을 돌려 씀).
|
||||
|
||||
## 단추
|
||||
| 단추 | 하는 일 |
|
||||
| --- | --- |
|
||||
| [?] | 이 도움말 |
|
||||
| [<] | 이전 단계. 이 화면이 첫 단계라 흐림 |
|
||||
| [>] | 지점 저장 뒤 다음 단계 「노선 분석」 으로 감 |
|
||||
| [저장] | 지점을 저장하고 좌표계를 정함 |
|
||||
| 📍 | 지도를 눌렀을 때 놓을 지점으로 이 줄을 고름 |
|
||||
| 위 단계 막대 | 「기준 지정」 · 「노선 분석」 · 「확정 · 내보내기」 로 바로 이동 |
|
||||
|
||||
## 결과 읽는 법
|
||||
- 저장하면 「지점을 저장했습니다.」 알림이 뜸.
|
||||
- 표 아래 한 줄에 「저장 좌표계 EPSG:번호」 와 첫 연도 시점의 X · Y(m)가 나옴. 저장 전에는 「아직 저장 안 함」 이 나옴.
|
||||
- 저장 전 고친 값은 이 화면을 다시 열어도 같은 브라우저 안에서는 남아 있음. 서버에는 [저장] 이나 [>] 를 눌러야 반영됨(자동 저장 없음).
|
||||
|
||||
## 알아 둘 것
|
||||
- **[>] 와 수치지형도**: [>] 는 지점을 저장한 뒤 「노선 분석」 화면을 엶. 수치지형도(도엽) · 위성 사진 · 연도별 지형면 받기는 그 화면이 열릴 때 뒤에서 시작되며, 이미 받은 범위 안이면 건너뜀. 지점을 저장하지 못하면(예: 빈 지점) 이 화면에 머묾.
|
||||
- 빈 지점이 남은 채 저장하면 「○○○○년 시점이 비었습니다.」 또는 「○○○○년 종점이 비었습니다.」 로 막힘.
|
||||
- 연도 범위(시작 · 끝)는 등록 때 정해지며 이 화면에서 바꿀 수 없음.
|
||||
- **이미 분석 · 확정한 연도의 지점을 옮기면** 그 연도의 분석 노선과 확정 상태가 비워지고 다시 분석해야 함. 1 mm 이내 변화는 같은 지점으로 보아 지우지 않음.
|
||||
- 좌표계가 바뀌는 경우(「쓰인 방법과 근거」 문서의 좌표계 절)에는 모든 연도의 분석 노선이 비워짐.
|
||||
- 지도는 카카오맵을 씀. 점이 하나도 없어도 우리나라 가운데쯤을 먼저 보여 줌.
|
||||
@@ -0,0 +1,25 @@
|
||||
# 칸 뜻
|
||||
|
||||
## 왼쪽 「연도 지점」 표
|
||||
| 칸 | 뜻 | 규칙 |
|
||||
| --- | --- | --- |
|
||||
| 지점 이름 | 어느 연도의 시점 · 종점인지 | 예: 「2026 시점」 · 「2026 종점 · 2027 시점」 |
|
||||
| 📍 | 지도 누름이 놓을 지점으로 고름 | 고른 줄이 강조됨 |
|
||||
| 경도 | 동서 위치(도) | 120 ~ 135 · 소수 가능 |
|
||||
| 위도 | 남북 위치(도) | 30 ~ 45 · 소수 가능 |
|
||||
|
||||
- 경도 · 위도 중 하나만 넣으면 반영되지 않음. 두 칸이 모두 범위 안일 때 반영됨.
|
||||
- 반영될 때 소수 7 자리(약 1 cm)로 반올림됨.
|
||||
- 범위 밖 값은 두 칸이 모두 빨갛게 표시되고 반영되지 않음.
|
||||
|
||||
## 지도 위
|
||||
- 표지 하나 = 지점 하나. 마우스로 끌어 옮길 수 있음.
|
||||
- 선 하나 = 연도 하나(그 해의 시점에서 종점까지). 색은 연도별로 다름.
|
||||
- 지점이 하나도 없으면 우리나라 가운데쯤이 먼저 보임. 지점을 놓으면 처음 한 번만 그 지점에 맞춰 보이고, 그 뒤로는 사용자가 맞춘 보기(중심 · 확대)를 그대로 둠.
|
||||
- 지도 아래 안내문 = 「지도를 눌러 지점을 놓습니다.」
|
||||
|
||||
## 표 아래 한 줄
|
||||
| 표시 | 뜻 |
|
||||
| --- | --- |
|
||||
| 아직 저장 안 함 | 서버에 지점이 한 번도 저장되지 않음 |
|
||||
| 저장 좌표계 EPSG:번호 · 연도 X · Y | 저장된 좌표계와 첫 연도 시점의 평면좌표(m, 소수 3 자리) |
|
||||
@@ -0,0 +1,34 @@
|
||||
# 쓰인 방법과 근거
|
||||
|
||||
## 좌표계 처리
|
||||
- 사용자가 넣는 값은 모두 경도 · 위도(WGS84)임. 지도(카카오맵)도 이 값으로 그려짐.
|
||||
- 저장할 때 서버가 모든 지점의 **평균 경도**를 보고 한국 평면좌표(TM) 띠 하나를 고른 뒤, 모든 지점을 그 한 가지 좌표계의 X(동쪽) · Y(북쪽) 미터 값으로 바꿔 저장함.
|
||||
- 분석과 지형 받기는 이 평면좌표를 씀. 사용자가 변환을 따로 할 필요는 없음.
|
||||
|
||||
| 평균 경도 | 좌표계 | 이름 |
|
||||
| --- | --- | --- |
|
||||
| 126° 미만 | EPSG:5185 | 서부 |
|
||||
| 126° 이상 128° 미만 | EPSG:5186 | 중부 |
|
||||
| 128° 이상 | EPSG:5187 | 동부 |
|
||||
|
||||
- 한 노선은 연도가 달라도 항상 같은 좌표계 한 벌을 씀(연도마다 띠를 따로 쓰지 않음).
|
||||
- 동해(5188)는 쓰지 않음.
|
||||
- 지점을 옮겨 평균 경도가 126° 나 128° 선을 넘으면 좌표계가 바뀌고, 그러면 모든 연도가 다시 저장되며 분석 노선이 비워짐.
|
||||
- 내보내기 때의 좌표계(5179)는 확정 · 내보내기 단계에서 다룸. 이 화면에서는 쓰지 않음.
|
||||
|
||||
## 직전 연도 종점 = 다음 연도 시점
|
||||
- 화면: 점 하나를 두 연도가 함께 쓰는 지점 목록(연도 수 + 1 개)으로 다룸.
|
||||
- 서버: 저장할 때 첫 연도는 보낸 시점을 쓰고, 이후 연도의 시점은 항상 직전 연도의 종점으로 맞춤. 따로 다른 값을 보내도 무시됨.
|
||||
- 사업 연도 밖의 연도가 오거나 같은 연도가 두 번 오면 거절됨.
|
||||
- 비어 있는 시점 · 종점이 있으면 「○○○○년 시점(종점)이 비었습니다.」 로 거절됨.
|
||||
|
||||
## 저장 규칙
|
||||
- 바뀐 연도만 새로 쓰고, 안 바뀐 연도(1 mm 이내)는 그대로 둠.
|
||||
- 바뀐 연도는 이미 있던 분석 노선과 확정 시각을 지움. 한 묶음으로 처리되어 일부만 저장되는 일은 없음.
|
||||
- 사업 연도 밖으로 남아 있던 연도 행은 함께 지움.
|
||||
- 저장은 산림청 계정만 가능함(시스템 관리자 또는 `.go.kr` 메일).
|
||||
|
||||
## 한계
|
||||
- 좌표 입력은 경위도만 받음. 평면좌표(X · Y)를 직접 넣는 칸은 없음.
|
||||
- 지도 위 위치는 카카오맵 화면 기준이며, 정밀한 측량 값이 필요하면 좌표 입력으로 넣을 것.
|
||||
- 지형 받기와 분석에 관한 설명은 「노선 분석」 화면의 도움말을 볼 것.
|
||||
@@ -0,0 +1,80 @@
|
||||
# 예정노선 2단계 분석 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
- 1단계(기준 지정)에서 정한 지점들을 연도 구간마다 이어 주는 노선을 자동으로 찾아 줌.
|
||||
- 연도마다 노선 후보 넷(영선 · 토공 최소 · 기울기 최소 · 비중 계산)을 만들어 보여 주고, 연도별로 하나씩 고름.
|
||||
- 고른 후보를 이어 붙여 전체 예정노선 한 줄과 측점(20 m 간격)을 평면에 표시함.
|
||||
- 측점을 누르면 그 자리 지반선 횡단을 아래에 보여 주고, [3D] 로 지형과 노선을 입체로 봄.
|
||||
- 지점이 마음에 안 들면 평면에서 직접 끌어 옮긴 뒤 다시 분석 가능.
|
||||
- 노선 후보를 찾는 엔진은 임도설계 노선 추천(B05)과 같은 엔진임. 이 화면에서는 시작점과 끝점만 정해진 두 지점 사이를 구간별로 한 번씩 찾음.
|
||||
|
||||
## 화면 구성
|
||||
|
||||
- 가운데 위 = 평면 지도. 위성 사진 위에 등고선, 지점, 노선, 측점이 겹쳐 보임.
|
||||
- 가운데 아래 = 횡단 판. 측점을 누르면 채워지며, 제목 줄(「횡단」)을 누르면 접고 펼 수 있음.
|
||||
- 왼쪽 = 「지형」 카드, 「연도별 노선」 카드, 아래쪽 단추 [분석] [저장] [3D].
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. 화면을 열면 자동으로 지형을 확인함. 지점이 이미 받은 지형 범위 안이면 그대로 두고, 아니면 뒤에서 지형을 받기 시작함(왼쪽 「지형」 카드에 진행률 표시).
|
||||
2. 지점 위치가 마음에 안 들면 평면에서 지점(원)을 끌어 옮김. 지점이 받은 지형 밖이면 [범위 밖 — 더 받기] 를 눌러 지형을 더 받음.
|
||||
3. [분석] 을 누름. 연도 구간마다 후보 넷을 찾으며, 진행률이 「지형」 카드 자리에 표시됨.
|
||||
4. 「연도별 노선」 카드에서 연도마다 후보 하나를 누름(눌린 카드가 강조됨). 평면의 노선과 누가(거리) 숫자가 곧바로 바뀜.
|
||||
5. 평면의 측점 눈금을 눌러 횡단을 확인하고, 필요하면 [3D] 로 확인함.
|
||||
6. 모든 연도에 노선이 있으면 [저장] 을 누름. 저장 중에는 화면 전체가 잠깐 잠김.
|
||||
7. 다음 단계(확정 · 업체 · 내보내기)로 넘어감.
|
||||
|
||||
## 단추
|
||||
|
||||
| 단추 | 하는 일 |
|
||||
| --- | --- |
|
||||
| 분석 | 연도 구간마다 노선 후보 넷을 찾음. 입력(지점 · 기준 · 지형)이 이전과 같으면 저장된 결과를 그대로 불러와 바로 끝남. 받은 지형 밖에 지점이 있으면 눌 수 없음 |
|
||||
| 저장 | 연도별로 고른 후보를 정본으로 저장함. 끌어 옮긴 지점도 이때 함께 저장됨. 어느 연도든 노선이 없으면 눌 수 없음(마우스를 올리면 안내 문구 표시) |
|
||||
| 3D | 지형과 노선을 입체로 보는 창을 엶. 노선이 하나라도 있어야 눌 수 있음 |
|
||||
| 범위 밖 — 더 받기 | 지점이 받은 지형 밖일 때만 나타남. 지형(수치지형도 · 위성 · 연도별 높이 자료)을 더 받음 |
|
||||
|
||||
- 산림청 소속이 아닌 사용자(지정받은 회사 등)에게는 [분석] [저장] [범위 밖 — 더 받기] 가 보이지 않으며 지점도 끌 수 없음. 보기만 가능.
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
- **평면의 선 모양**
|
||||
- 굵은 선 = 연도별로 고른 후보를 이은 예정노선.
|
||||
- 흰 점선(옅음) = 고르지 않은 다른 후보들. 분석 전에는 지점끼리 잇는 점선만 보임.
|
||||
- 누른 측점 = 보라색 고리로 표시됨.
|
||||
- **지점(마디) 색**
|
||||
- 초록 = 첫 지점(시점), 빨강 = 마지막 지점(종점), 파랑 = 연도가 바뀌는 중간 지점.
|
||||
- 지점 둘레에 빨간 고리와 점선 고리 = 그 지점이 받은 지형 밖이라는 뜻. 「지형」 카드에 「빨간 고리 지점이 받은 지형 밖입니다 — 더 받아야 분석합니다.」 가 나옴.
|
||||
- **등고선** = 도엽(수치지형도) 등고선. 노란 점선과 노란 숫자(높이)로 보임. 지도를 확대하면 더 촘촘히 보임.
|
||||
- **연도별 노선 카드**
|
||||
- 윗줄 = 「연도 · 시작 측점 ~ 끝 측점 (연장 m)」. 분석 전에는 「분석 전 — [분석]을 누르세요」 가 보임.
|
||||
- 후보 카드 한 장마다 「연장 · 최대 기울기(%) · 위반 개수」 를 보여 줌. 위반 = 기울기 기준을 지키지 못한 곳의 수.
|
||||
- 「노선 없음」 으로 회색 처리된 카드 = 그 후보는 길을 찾지 못함. 마우스를 올리면 까닭 표시. 누를 수 없음.
|
||||
- 고르지 않고 두면 네 후보 중 차례(영선 → 토공 최소 → 기울기 최소 → 비중 계산)대로 처음 성공한 후보가 자동으로 잡힘.
|
||||
- **누가(측점 번호)** = 앞 연도 끝 = 다음 연도 시작으로 이어서 셈. 시작 값은 프로젝트 등록 때 정한 노선 시작 누가. 연도 경계는 꺾인 채 한 측점으로 표시됨.
|
||||
- **횡단 판** = 누른 측점의 지반선 단면. 제목 줄에 「연도 · 측점 이름 · 중심 지반고」 표시. 좌우 20 m 범위의 지반만 보이며 설계선은 없음.
|
||||
|
||||
## 보이는 것과 안 보이는 것
|
||||
|
||||
| 구분 | 내용 |
|
||||
| --- | --- |
|
||||
| 보임 | 위성 지도, 등고선, 지점, 노선 중심선, 측점 눈금과 이름, 지반선 횡단, 지형 + 중심선 3D |
|
||||
| 안 보임 | 노견, 절토 · 성토선, 종단도, 배수유역, 구조물(3D 에도 없음) |
|
||||
| 이 화면에 칸 없음 | 종단 기울기 상한 · 최소 곡선 반지름 등 기준값. 등록 때 정한 임도 종류와 설계속도로 자동 정해짐 |
|
||||
|
||||
## 지점 끌기
|
||||
|
||||
- 지점(원) 위에서 마우스 왼쪽 단추를 누른 채 끌면 지점이 따라 움직임. 끝점은 이웃 연도의 시작점과 한 점으로 묶여 있어 함께 움직임.
|
||||
- 지점을 옮기면 그 지점 앞 · 뒤 연도의 분석 결과가 비워지고 「지점을 옮겼습니다 — [분석] 뒤 측점을 누르세요.」 가 횡단 판에 나옴. 다시 [분석] 을 눌러야 함.
|
||||
- 옮긴 위치는 [저장] 전까지 이 브라우저 탭에만 임시로 남아 있음(새로 고침해도 유지, 탭을 닫으면 사라짐). [저장] 을 눌러야 정본이 바뀜.
|
||||
- 손이 조금 떨리는 정도(몇 픽셀)의 움직임은 끌기로 치지 않고 단순 누름으로 처리함.
|
||||
- 지도 자체는 지점이 아닌 곳을 끌어 옮기고, 휠로 확대 · 축소함.
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- [저장] 은 노선 모양을 화면에서 받지 않고, 고른 후보 이름만 서버에 보냄. 서버가 저장된 분석 결과로 노선을 다시 이어 붙여 저장하므로, [분석] 을 하지 않은 상태에서는 저장할 수 없음(「분석 저장본이 없습니다 — [분석]을 먼저 하세요.」).
|
||||
- 처음 열 때 지점이 비어 있으면 「N년 지점이 비었습니다 — 1단계(기준 지정)에서 정하세요.」 라는 문구만 나오고 진행이 안 됨.
|
||||
- 지형 받기와 분석은 서버 뒤에서 도는 작업임. 화면을 닫았다 다시 열어도 도는 중이면 이어서 진행률을 보여 줌.
|
||||
- 위성 사진은 못 받아도 지형 받기가 멈추지 않음. 위성이 없으면 어두운 바탕에 지점과 노선만 보임.
|
||||
- 3D 창은 한 번에 한 연도씩 보며, 창 위쪽 선택칸으로 연도를 바꿈. 창을 닫으면 3D 화면 자원이 정리됨.
|
||||
- 법 기준의 세부 수치와 사용자 확정 전 값은 「30_사용자_확정_전_값」 문서 참고.
|
||||
@@ -0,0 +1,52 @@
|
||||
# 쓰인 방법과 근거
|
||||
|
||||
## 노선을 찾는 방법
|
||||
|
||||
- 연도마다 그 연도의 시점과 종점 두 점 사이를 한 번씩 찾음. 연도 사이의 경계점은 옮기지 않으므로 앞 연도 끝과 다음 연도 시작이 정확히 맞물림.
|
||||
- 찾는 엔진은 임도설계 「노선 추천」(B05)과 같은 엔진임. 지형 높이 자료(연도별 높이 격자)에서 경사를 읽고, 기울기 · 곡선 · 하천 이격을 지키는 길을 찾아 곡선까지 맞춘 중심선을 냄.
|
||||
- 후보는 네 갈래로 같은 입력에서 각각 구함.
|
||||
|
||||
| 후보 | 고르는 기준 |
|
||||
| --- | --- |
|
||||
| 영선 | 목표 기울기로 등고선을 비스듬히 건너가는 일정한 기울기 선. 목표 기울기를 따로 주지 않으므로 기울기 상한을 목표로 삼음 |
|
||||
| 토공 최소 | 후보 길 중 절토 + 성토 + 반입 + 사토 물량이 가장 적은 길 |
|
||||
| 기울기 최소 | 후보 길 중 최대 종단 기울기가 가장 작은 길(같으면 평균 기울기가 작은 길) |
|
||||
| 비중 계산 | 예상 · 토공 · 기울기 세 가지 비용을 비중으로 섞어 한 번 찾은 길. 비중은 기본값 50 : 25 : 25(%) 그대로 씀 |
|
||||
|
||||
- B05 의 다른 후보(예상노선 추적 · 안전 우선 · 계류 횡단 최소 · 공사비 어림 최소)는 이 화면에서 쓰지 않음.
|
||||
- 예상노선 추적은 두 점 사이가 직선이 되어 의미가 없어 뺐음.
|
||||
|
||||
## 추천 기준(자동으로 정해지는 값)
|
||||
|
||||
- 화면에 기준 칸이 없음. 프로젝트 등록 때 정한 「임도 종류」와 「설계속도」로 임도 법정 함수가 값을 냄.
|
||||
- 설계속도는 종류별로 고를 수 있는 값이 정해져 있음. 작업임도는 20 km/h 하나, 간선 · 산불진화 임도는 20 · 30 · 40 km/h 중 선택. 지정하지 않으면 20 km/h.
|
||||
- 지형 구분은 등록 칸이 없어 가장 완화된 쪽(특수지형)으로 고정해 씀.
|
||||
|
||||
| 설계속도 | 종단 기울기 상한 | 최소 곡선 반지름 |
|
||||
| --- | --- | --- |
|
||||
| 20 km/h | 14 % | 12 m |
|
||||
| 30 km/h | 12 % | 20 m |
|
||||
| 40 km/h | 10 % | 40 m |
|
||||
|
||||
- 하천 이격 = 100 m. 도엽 하천선 중 소하천 · 지방하천 · 국가하천만 대상.
|
||||
- 탐색 복도 반폭 = 300 m. 두 지점을 이은 선 양옆 300 m 안에서만 길을 찾음. 100 m · 200 m 로는 기준을 못 지키는 곳이 남아 300 m 로 넓힘.
|
||||
- 위반 개수 = 기울기 기준을 지키지 못한 곳의 수. 지형상 길이 없을 때만 어쩔 수 없이 기준을 넘은 길이 나옴.
|
||||
|
||||
## 쓰는 지형 자료
|
||||
|
||||
- 수치지형도 도엽(축척 1/5,000)을 지점 둘레 3×3 장씩 받아 한 장으로 병합함. 이미 받은 도엽은 다시 받지 않고, 옛 도엽도 지우지 않음.
|
||||
- 위성 지도(VWorld)는 배경 그림용. 이미 받은 범위가 덮으면 건너뜀.
|
||||
- 연도별 높이 격자는 그 연도 구간 상자 + 탐색 복도 300 m + 여유 50 m 범위로 만듦. 지점이 격자 가장자리에서 300 m 안쪽에 있어야 「받은 지형 안」 으로 판정함.
|
||||
- 도엽 등고선을 평활화한 높이 격자라 실제 측량 지형과 차이가 있을 수 있음.
|
||||
|
||||
## 저장 방식
|
||||
|
||||
- 같은 입력(지점 · 기준 · 지형)이면 이전에 계산한 결과를 그대로 써서 [분석] 이 빨리 끝남. 지점이나 기준이 바뀌면 그 연도만 다시 계산함.
|
||||
- [저장] 은 서버가 저장된 분석 결과에서 고른 후보를 다시 이어 정본으로 기록함. 화면에서 계산한 값을 그대로 받아 적지 않음.
|
||||
- 누가 계산, 후보 고름, 중심선 잇기는 화면과 서버가 같은 규칙으로 따로 셈. 한쪽만 고치지 않도록 짝으로 관리하며 시험으로 대조함.
|
||||
|
||||
## 한계
|
||||
|
||||
- 후보 네 개가 서로 비슷한 길이 될 수 있음(가파른 지형에서 기준을 지키는 길이 좁은 띠 하나뿐인 경우).
|
||||
- 평면 노선만 다룸. 종단(세로 단면) · 토공량 확정 · 구조물 계획은 이 단계 범위가 아니며, 임도설계 단계에서 다룸.
|
||||
- 횡단은 지반선만 보여 주며 설계 단면은 아님.
|
||||
@@ -0,0 +1,21 @@
|
||||
# 사용자 확정 전 값
|
||||
|
||||
아래 값은 코드에 기본값으로 들어 있으나 사용자가 최종 확정한 값이 아님. 바꾸려면 개발 담당에게 요청.
|
||||
|
||||
| 값 | 지금 기본값 | 출처 | 상태 |
|
||||
| --- | --- | --- | --- |
|
||||
| 지형 구분 | 특수지형(가장 완화) 고정 | 예정노선 등록 화면에 지형 구분 칸이 없어 임도설계 기본 대체값을 그대로 씀 | 확정 전 |
|
||||
| 종단 기울기 상한 | 설계속도 20 · 30 · 40 km/h 에 14 · 12 · 10 % | 임도 규정 별표 설계속도별 값 중 특수지형 열 | 지형 구분 확정에 따라 바뀜 |
|
||||
| 최소 곡선 반지름 | 12 · 20 · 40 m | 같은 별표의 특수지형 열 | 지형 구분 확정에 따라 바뀜 |
|
||||
| 하천 이격 | 100 m | 법정 수치 없음. 타당성 평가 별표 비고(산지계류 100 m 이내 구간 비율)를 후보로 씀 | 임도설계와 같은 기본값 |
|
||||
| 탐색 복도 반폭 | 300 m | 예정노선 화면 전용값. 실제 지형으로 시험해 정함 | 시험 결과 기준 |
|
||||
| 비중 계산 비중 | 예상 50 · 토공 25 · 기울기 25 (%) | 임도설계 기본 비중 | 이 화면에서 바꾸는 칸 없음 |
|
||||
| 영선 목표 기울기 | 기울기 상한과 같게 | 임도설계 기본 동작 | 이 화면에서 바꾸는 칸 없음 |
|
||||
| 측점 간격 | 20 m | 임도설계 측점 규칙과 같음 | 고정 |
|
||||
| 횡단 표시 폭 | 좌우 각 20 m | 화면 고정값 | 고정 |
|
||||
| 지형 판정 여유 | 지점이 지형 가장자리에서 300 m 안쪽 | 탐색 복도와 같은 값 | 복도 값과 함께 움직임 |
|
||||
|
||||
## 준비 중
|
||||
|
||||
- 예정노선 단계에서 연도별 비중이나 목표 기울기를 직접 정하는 화면 칸은 없음. 지금은 모두 기본값으로 계산함.
|
||||
- 3D 보기에 노견 · 절성토면 · 구조물을 올리는 기능은 없음.
|
||||
@@ -0,0 +1,81 @@
|
||||
# 예정노선 확정 · 업체 · 내보내기 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
- 2단계(분석)에서 저장한 연도별 노선을 한눈에 확인함(연장 · 시점 · 종점 · 누가 · 확정 여부).
|
||||
- 연도마다 일을 맡길 업체를 지정함. 아이슬로에 등록된 회사를 고르거나, 업체명과 메일 주소를 직접 적는 수기 지정 중 선택. 한 연도에 업체 여럿 지정 가능.
|
||||
- [확정] 으로 연도 노선을 확정하고, 지정 업체에 노선 파일이 든 압축 파일(zip)을 메일로 보냄.
|
||||
- [내보내기] 로 노선 파일 묶음을 내려받음.
|
||||
- 업체 쪽에서는 받은 예정노선을 자기 임도 프로젝트로 연결해 설계를 이어 감(아래 「받은 업체의 임도 연결」 참고).
|
||||
|
||||
## 화면 구성
|
||||
|
||||
- 가운데 = 연도 표. 프로젝트 이름이 위에 크게 나오고, 표 열은 연도 · 연장(m) · 시점(위도, 경도) · 종점(위도, 경도) · 누가(m) · 확정 · 지정 업체.
|
||||
- 왼쪽 위 = 「확정할 연도」 카드. 분석 [저장] 이 된 연도마다 선택 상자가 있음. 아직 확정하지 않은 연도는 처음부터 선택되어 있음.
|
||||
- 왼쪽 아래 = 단추 [내보내기] [확정] [저장].
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. 연도 표에서 연도마다 연장과 누가를 확인함. 분석을 저장하지 않은 연도는 누가 칸에 「노선 분석 [저장] 전」 이 나오며, 확정할 연도 목록에도 나타나지 않음.
|
||||
2. 지정 업체 칸에서 업체를 추가함.
|
||||
- 등록 회사: 「업체(등록 회사 찾기 또는 이름)」 칸에 글자를 입력하면 가입된 회사 이름이 목록으로 뜸. 목록에서 고르면 등록 회사로 지정됨. 메일 칸을 비우면 그 회사의 대표 메일이 쓰임.
|
||||
- 수기 지정: 업체명과 메일 주소(@ 포함)를 둘 다 적음. 없으면 「등록 회사가 아니면 업체명과 메일이 필요합니다.」 안내가 나오고 추가되지 않음.
|
||||
- [추가] 를 누르면 그 연도 칸에 업체 이름 + 메일이 표시됨. 같은 업체를 두 번 넣어도 한 번만 들어감.
|
||||
- 잘못 넣은 업체는 업체 옆 [×] 로 뺌.
|
||||
3. [저장] 을 누르면 지정 업체가 서버에 저장됨(「지정 업체를 저장했습니다.」). 저장 전에는 표 위에 「저장 안 된 변경이 있습니다.」 가 표시됨.
|
||||
4. 「확정할 연도」 에서 확정할 연도를 고르고 [확정] 을 누름. 확인 창(「고른 연도를 확정하고 지정 업체에 노선 묶음을 메일로 보냅니다. 진행할까요?」)에서 진행을 누르면 확정과 메일 발송이 이뤄짐. 저장하지 않은 업체 변경이 있으면 자동으로 먼저 저장됨.
|
||||
5. 필요하면 [내보내기] 로 파일을 내려받아 보관함.
|
||||
|
||||
## 단추
|
||||
|
||||
| 단추 | 하는 일 |
|
||||
| --- | --- |
|
||||
| 내보내기 | 「프로젝트이름_예정노선.zip」 을 내려받음. 산림청은 모든 연도와 전체 노선을 받음. 지정받은 회사는 확정된 자기 연도만 받음 |
|
||||
| 확정 | 고른 연도를 확정 처리하고 지정 업체에 메일을 보냄. 고른 연도가 없으면 「확정할 연도를 고르세요.」 안내 |
|
||||
| 저장 | 연도별 지정 업체 목록을 서버에 저장함 |
|
||||
|
||||
- 업체 추가와 삭제는 [저장] 을 누르기 전까지 이 브라우저 탭에만 임시로 남음. 탭을 닫으면 사라지므로 [저장] 을 누를 것.
|
||||
- [저장] [확정] 은 산림청 소속 사용자만 가능. 그 밖의 사용자가 누르면 「산림청만 예정노선을 확정 · 지정할 수 있습니다.」 안내가 나옴.
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
- **확정 열** = 확정한 날짜와 시각(한국 시간). 비어 있으면 아직 확정 전.
|
||||
- **지정 업체 열의 표시**
|
||||
- 「Aislo」 = 아이슬로 등록 회사로 지정된 업체.
|
||||
- 「메일 보냄」 = 확정 때 메일이 나갔다는 표시. 같은 업체에는 다시 확정해도 중복 발송하지 않음.
|
||||
- 「연결됨」 = 그 업체가 이 연도 노선을 자기 임도 프로젝트로 가져가 연결했다는 표시.
|
||||
- **[확정] 후 안내 문구** = 「확정 — 메일 sent 2 · already_sent 1」 꼴로 업체별 처리 결과를 개수로 보여 줌.
|
||||
|
||||
| 결과 이름 | 뜻 |
|
||||
| --- | --- |
|
||||
| sent | 메일 발송 성공 |
|
||||
| already_sent | 이미 보낸 업체여서 다시 보내지 않음 |
|
||||
| no_email | 메일 주소가 없어 보내지 못함(등록 회사의 대표 메일이 비어 있는 경우 등) |
|
||||
| failed | 메일 발송 실패 |
|
||||
|
||||
## 보내지는 메일
|
||||
|
||||
- 제목: 「[예정노선 확정] 프로젝트이름 N년」.
|
||||
- 본문: 프로젝트 · 사업 지역 · 연도 · 연장(km 와 m) 요약과 첨부 파일 목록, 압축을 풀면 노선 입력에 바로 올릴 수 있다는 안내 문구.
|
||||
- 첨부: 그 업체의 연도 한 해 분량 zip 1개(파일 이름 「프로젝트이름_예정노선_연도.zip」). 내용은 「10_내보내기_파일_구성」 참고.
|
||||
|
||||
## 받은 업체의 임도 연결
|
||||
|
||||
- 지정받은 등록 회사는 대시보드의 「받은 예정노선」 구역에서 확정된 연도를 봄. 열은 예정노선 · 연도 · 확정 · 연결 상태(연결됨 / 미연결 / 업데이트 있음) · 작업.
|
||||
- [보기] = 예정노선 화면을 보기만 가능한 상태로 엶.
|
||||
- [연결] = 모달에서 두 가지 중 선택.
|
||||
- 「새 임도 프로젝트로」: 예정노선 이름 + 연도로 임도 프로젝트를 새로 만들고 노선 입력 단계(B03)로 이동. 예정노선의 설계속도(20 · 30 · 40 km/h)가 새 프로젝트의 설계속도 기본값으로 들어감.
|
||||
- 「기존 프로젝트에 연결」: 우리 회사의 임도 프로젝트를 골라 그 프로젝트에 연결. 임도 프로젝트가 없으면 「우리 회사 임도 프로젝트가 없습니다.」 표시.
|
||||
- 연결하면 그 연도 노선(shapefile 한 벌)이 노선 입력 단계의 입력 파일로 들어감. 파일을 직접 올린 것과 같은 길이라 이후 처리도 같음. 지형 자료는 업체가 따로 올려야 함.
|
||||
- 이미 분석 결과가 있는 임도 프로젝트에 연결하면 기존 결과를 지워도 되는지 묻는 확인 창이 먼저 나옴. 취소하면 아무것도 바뀌지 않음.
|
||||
- 임도 프로젝트 노선 입력 화면의 [예정노선에서 가져오기] 단추로도 같은 일을 할 수 있음. 지정받은 연도를 고르면 노선이 입력됨.
|
||||
- 연결 시점의 노선이 복사되므로, 산림청이 뒤에 노선을 바꿔도 임도 프로젝트는 자동으로 바뀌지 않음. 대신 「업데이트 있음」 표시가 뜨며, 노선 입력 화면 위쪽 띠에서 [다시 가져오기] 로 직접 반영 여부를 결정함.
|
||||
- 한 임도 프로젝트는 한 연도에만 연결됨. 다른 연도로 다시 연결하면 앞의 연결은 풀림.
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- 확정 시각은 [확정] 을 누를 때마다 새로 기록됨. 이미 메일을 보낸 업체에는 다시 보내지 않음(다시 보내는 단추는 화면에 없음).
|
||||
- 확정은 분석 [저장] 이 된 연도만 가능. 저장하지 않은 연도를 억지로 확정하려 하면 「분석 [저장]이 안 된 연도입니다」 오류가 나옴.
|
||||
- [저장] 은 표에 있는 모든 연도의 지정 업체를 화면 내용대로 맞춤. 업체를 모두 뺀 연도는 지정이 비워짐.
|
||||
- 같은 업체 판정: 등록 회사는 회사 번호, 수기 업체는 메일 주소(대소문자 구분 없음).
|
||||
- 내보내기는 서버에 저장된 노선만 사용함. 2단계에서 [저장] 하지 않은 변경은 반영되지 않음.
|
||||
@@ -0,0 +1,64 @@
|
||||
# 내보내기 파일 구성
|
||||
|
||||
## 한눈에
|
||||
|
||||
- 내려받는 파일 이름 = 「프로젝트이름_예정노선.zip」. 이름에 쓸 수 없는 글자는 밑줄(_)로 바뀜.
|
||||
- 압축 안에 연도 이름의 폴더가 연도마다 하나, 그리고 「전체」 폴더가 하나 들어 있음(전체 폴더는 산림청만 받음).
|
||||
- 업체에 보내는 메일 첨부 zip 은 한 연도 분량이며, 폴더 없이 그 연도 파일들이 바로 들어 있음.
|
||||
|
||||
| 위치 | 내용 |
|
||||
| --- | --- |
|
||||
| 연도 폴더(예: 2026) | 그해 시점에서 종점까지 중심선 한 줄(shapefile 한 벌) + 좌표표 엑셀 |
|
||||
| 전체 폴더 | 모든 연도를 이은 한 줄(shapefile 한 벌, 발주처 보관용) + 모든 연도가 담긴 좌표표 엑셀 |
|
||||
|
||||
## shapefile 한 벌
|
||||
|
||||
- 확장자 다섯 가지가 한 벌임: .shp(선 모양) · .shx(색인) · .dbf(속성 표) · .prj(좌표계 정의) · .cpg(글자 코드). 하나라도 빠지면 안 되므로 같이 보관할 것.
|
||||
- 파일 이름 = 「대상지_지역.연장km_연도」. 예: 연장이 2.2 km 이면 「대상지_지역.2.2_2026」. 전체 노선은 연도 자리에 「전체」.
|
||||
- 좌표계 = UTM-K(EPSG:5179). 프로젝트가 다른 좌표계로 작업했더라도 내보낼 때 5179 로 바뀜.
|
||||
- 글자 코드 = cp949(한글 윈도우 표준). 엑셀 · 지리정보 프로그램에서 한글 칸 이름이 깨지지 않음.
|
||||
- 선 모양 = 평면 중심선 한 줄(높이 값 없음), 기록 한 건.
|
||||
- 양식은 2026년 임도 용화 예정노선 파일(대상지_일월.용화.2.2)과 같은 꼴임. 좌표계 정의(.prj)는 그 파일 원문을 그대로 씀.
|
||||
|
||||
### 속성 표 칸
|
||||
|
||||
| 칸 이름 | 뜻 | 형식 |
|
||||
| --- | --- | --- |
|
||||
| 대상지 | 「사업지역.연장km」 글자(예: 지역.2.2). 20바이트(한글 10자 안팎)를 넘으면 뒤가 잘림 | 글자 20 |
|
||||
| 길이 | 노선 연장, 단위 km(소수 셋째 자리) | 숫자 11.3 |
|
||||
| 연도 | 사업 연도. 전체 노선은 비어 있음 | 정수 4 |
|
||||
| 시점누가 | 그 선이 시작하는 측점 값(m) | 숫자 11.2 |
|
||||
| 종점누가 | 그 선이 끝나는 측점 값(m) | 숫자 11.2 |
|
||||
|
||||
- 대상지 · 길이 두 칸은 용화 양식과 같고, 연도 · 시점누가 · 종점누가 는 이 프로그램이 덧붙인 칸임.
|
||||
- 누가 값은 2단계에서 이어 셈한 값 그대로임. 앞 연도 종점 누가 = 다음 연도 시점 누가.
|
||||
|
||||
## 좌표표 엑셀
|
||||
|
||||
- 파일 이름 = 「좌표표_프로젝트이름.xlsx」, 시트 이름 「좌표표」.
|
||||
- 첫 줄에 「프로젝트이름 예정노선 좌표표 (UTM-K EPSG:5179)」 제목, 둘째 줄이 열 이름, 이후 연도마다 한 줄.
|
||||
- 연도 폴더의 좌표표는 그 연도 한 줄, 전체 폴더의 좌표표는 모든 연도가 줄마다 들어 있음.
|
||||
|
||||
| 열 | 뜻 |
|
||||
| --- | --- |
|
||||
| 연도 | 사업 연도 |
|
||||
| 시점 X · 시점 Y | 시작점 UTM-K 좌표(m) |
|
||||
| 종점 X · 종점 Y | 끝점 UTM-K 좌표(m) |
|
||||
| 시점 경도 · 시점 위도 | 시작점 위경도(도) |
|
||||
| 종점 경도 · 종점 위도 | 끝점 위경도(도) |
|
||||
| 시점누가(m) · 종점누가(m) | 이어 셈한 측점 값 |
|
||||
| 연장(m) | 그 연도 노선 길이 |
|
||||
|
||||
## 보는 사람에 따른 차이
|
||||
|
||||
| 사용자 | 받는 범위 |
|
||||
| --- | --- |
|
||||
| 산림청 | 분석이 저장된 모든 연도 폴더 + 전체 폴더 |
|
||||
| 지정받은 등록 회사 | 확정되었고 자기 회사가 지정된 연도 폴더만. 전체 폴더 없음. 받을 연도가 없으면 「받을 수 있는 연도가 없습니다」 로 거절됨 |
|
||||
| 저장된 분석이 전혀 없을 때 | 「분석 [저장]이 된 연도가 없습니다.」 로 거절됨 |
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- 파일은 서버에 저장된 노선으로만 만들며, 화면에서 아직 저장하지 않은 노선은 반영되지 않음.
|
||||
- 전체 노선은 앞 연도 끝점과 다음 연도 시작점이 겹치므로 겹치는 한 점을 빼고 이어 붙임.
|
||||
- 임도 프로젝트로 연결할 때는 좌표표 없이 shapefile 한 벌만 노선 입력에 들어감.
|
||||
@@ -0,0 +1,18 @@
|
||||
# 사용자 확정 전 값
|
||||
|
||||
아래 항목은 코드의 현재 동작이며, 바꾸려면 개발 담당에게 요청.
|
||||
|
||||
| 값 | 지금 기본값 | 출처 | 상태 |
|
||||
| --- | --- | --- | --- |
|
||||
| 내보내기 좌표계 | UTM-K(EPSG:5179) 고정 | 용화 예정노선 양식 | 양식 따름 |
|
||||
| 글자 코드 | cp949 | 용화 예정노선 양식 | 양식 따름 |
|
||||
| 덧붙은 속성 칸 | 연도 · 시점누가 · 종점누가 | 이 프로그램에서 추가 | 업체 쪽 요청 시 조정 가능 |
|
||||
| 좌표표 엑셀 | 연도 폴더 · 전체 폴더에 포함, 선택 기능은 아님 | 요구서는 「선택」 으로 적었으나 실제로는 항상 만듦 | 확정 전 |
|
||||
| 메일 재발송 | 이미 보낸 업체에는 다시 보내지 않음, 화면에 재발송 단추 없음 | 서버에 재발송 옵션은 있으나 화면에 연결되지 않음 | 준비 중 |
|
||||
| 연결 시 연도 측점 | 연결한 임도 프로젝트에서 측점이 처음부터 다시 시작할지 | 이어 받기 기능이 필요해 뒤로 미룬 항목 | 준비 중 |
|
||||
| 설계속도 전달 | 새 임도 프로젝트로 연결할 때 예정노선 설계속도가 노선 단계(B05) 설정에도 들어감. 기존 프로젝트는 프로젝트 설계속도 칸이 비어 있을 때만 채움 | 임도설계 설정과 같은 값 범위 | 동작 중 |
|
||||
|
||||
## 준비 중
|
||||
|
||||
- 지정받은 업체에 확정 메일 외 별도 알림을 보내는 기능은 없음.
|
||||
- 업체 지정 이력(누가 언제 바꿨는지)을 보여 주는 화면은 없음.
|
||||
@@ -0,0 +1,55 @@
|
||||
# 안정성평가 만들기 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
- 횡단 설계(B06)까지 끝난 임도 프로젝트 하나를 골라 새 안정성평가 프로젝트를 만듦.
|
||||
- 만드는 순간의 원본 자료를 안정성평가 쪽에 따로 복사해 둠(동결 복사). 그 뒤 해석은 모두 이 복사본으로 함.
|
||||
- 대시보드 [+] 새 프로젝트 → 「안정성평가」를 누르면 이 화면이 열림.
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. **대상 임도 프로젝트** 칸에서 원본 임도를 고름. 목록 글 = 프로젝트 이름 · 지역 · 사업 연도(최근에 고친 프로젝트가 위).
|
||||
2. **프로젝트명** 칸을 확인함. 손대지 않으면 「원본 이름 안정성평가」로 자동으로 채워짐 — 원하는 이름으로 바꿔도 됨.
|
||||
3. [등록]을 누름. 복사하는 동안 화면 전체가 잠기고, 끝나면 「프로젝트를 만들었습니다.」 알림이 뜸.
|
||||
4. 새 프로젝트가 현재 프로젝트가 되고 첫 단계 「지반 조건」(E02) 화면으로 넘어감.
|
||||
|
||||
## 단추
|
||||
|
||||
| 단추 | 하는 일 |
|
||||
| --- | --- |
|
||||
| [?] | 이 설명서를 엶 |
|
||||
| [취소] | 만들지 않고 대시보드로 돌아감 |
|
||||
| [등록] | 고른 임도를 복사해 안정성평가 프로젝트를 만듦. 고를 임도가 없으면 흐리게 꺼져 있음 |
|
||||
|
||||
## 목록에 나오는 임도
|
||||
|
||||
- 우리 회사 임도 설계 프로젝트 중 **횡단 설계(B06) 단계가 끝난 것만** 나옴.
|
||||
- 고를 임도가 하나도 없으면 칸 아래에 「횡단 설계(B06)까지 끝난 임도 프로젝트가 없습니다.」가 보임 — 먼저 임도 프로젝트에서 횡단 설계를 끝낼 것.
|
||||
- 지역 · 도로 종류 · 사업 연도는 원본 값을 그대로 물려받음.
|
||||
|
||||
## 복사하는 것 · 안 하는 것
|
||||
|
||||
해석에 쓰는 자료만 복사함(보통 수십 MB 안쪽).
|
||||
|
||||
| 복사함 | 안 함 |
|
||||
| --- | --- |
|
||||
| 원본 프로젝트 정보 · 단계 상태 · 지표면 확정 값 | 원본 점군(LAS) 파일 |
|
||||
| 최신 노선과 측점 · 종단 · 횡단 자료 | 상세 설계(B07) · 수량(B08) · 내역(B09) |
|
||||
| 노선 폴더(구조물 배치 포함) · 종횡단 파일 | 쓰이지 않은 다른 지형 격자 |
|
||||
| 원본 설계에 쓰인 지형 격자(DTM) 하나 | |
|
||||
| 최신 노선 3D 면 · 배수 편집분 | |
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
- [등록]이 끝나면 지반 조건(E02) 화면이 열리고, 지반물성치 · 기준안전율 등은 기본값으로, 측점별 토층 두께는 복사한 B06 값으로 채워져 있음.
|
||||
- 원본 횡단 파일이 없는 측점이 있으면 E02 · E03 화면 위에 **「횡단 없는 측점 N 개 — 해석에서 빠짐」** 띠가 보임. 띠를 누르면 빠진 측점의 누가거리(m)가 펼쳐짐.
|
||||
- 빠진 측점은 해석 목록에 나오지 않음. 나머지 측점 해석에는 영향 없음.
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- **업데이트 없음**: 만든 뒤 원본 임도를 고쳐도 이 안정성평가는 바뀌지 않음(만든 순간의 자료로 고정).
|
||||
- 원본의 바뀐 설계로 다시 평가하려면 이 화면에서 **안정성평가를 새로 만들 것**.
|
||||
- 복사에 실패하면 「원본 자료 복사에 실패했습니다.」 알림이 뜨고, 만들다 만 프로젝트는 자동으로 지워짐 — 다시 [등록]하면 됨.
|
||||
- 원본 임도 프로젝트에는 아무것도 쓰지 않음(읽기만 함).
|
||||
- 안정성평가의 지반 값은 **문헌 추정치 · 현장 시험 없음**이 기본임 — 다음 단계(E02)에서 현장 여건에 맞게 확인 · 수정할 것.
|
||||
- 횡단 없는 측점을 살리려면 원본 임도에서 그 측점 횡단을 다시 만든 뒤 안정성평가를 새로 만들 것.
|
||||
@@ -0,0 +1,50 @@
|
||||
# 지반 조건 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
- 2D 해석(E03)에 들어갈 지반 조건을 정함 — 지반물성치 · 기준안전율 · 노면 하중 · 지진 · 지하수 · 층 두께.
|
||||
- 처음 열면 기본값(문헌 추정치)과 복사한 B06 측점 값으로 채워져 있음. 칸마다 바꿀 수 있음.
|
||||
- 좌측 = 안내 글 · 상태 글 · [초기화] [저장].
|
||||
- 가운데 = 카드 다섯 — 「지반물성치」 · 「기준안전율」 · 「노면 하중 · 지진」 · 「지하수」 · 「층 두께 · 구간 덮어쓰기」.
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. **지반물성치** 카드에서 지층별 γt · γsat · c · φ 를 확인함. 현장 값이 있으면 고쳐 넣고, 필요하면 [지층 추가].
|
||||
2. 같은 카드 아래 **B06 지반 → 지층** 줄에서 B06 의 토사 · 리핑암 · 발파암 · 성토체가 어느 지층으로 해석될지 고름.
|
||||
3. **기준안전율** 카드에서 조건(건기 · 우기 · 지진 시 · 단기)별 기준을 확인함.
|
||||
4. **노면 하중 · 지진** 카드에서 노면 하중(kPa)과 지진 검토 켬/끔 · 수평지진계수 kh 를 정함.
|
||||
5. **지하수** 카드에서 건기 지하수와 우기 규칙(넷 중 하나)을 고름.
|
||||
6. **층 두께 · 구간 덮어쓰기** 카드에서 특정 구간만 지층 · 토층 두께를 바꿀 것이 있으면 [구간 추가]. 아래 **측점별 적용 지층** 표로 결과를 확인함.
|
||||
7. 좌측 [저장]을 누름. 저장해야 2D 해석(E03)에 쓰임.
|
||||
|
||||
## 단추
|
||||
|
||||
| 단추 | 자리 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| [저장] | 좌측 아래 | 화면 값을 검사한 뒤 작업본으로 저장함. 틀린 칸이 있으면 저장하지 않고 알림 |
|
||||
| [초기화] | 좌측 아래 | 확인 창 뒤 초기값(만들 때 기본값 + B06 측점)으로 되돌림. 저장한 값이 사라짐 |
|
||||
| [?] [<] [>] | 좌측 아래 | 설명서 · 이전 단계 · 다음 단계(2D 해석) |
|
||||
| [지층 추가] | 지반물성치 카드 | 「새 지층 n」 줄을 덧붙임(풍화토 기본값으로 시작) |
|
||||
| [지움] | 표 줄 끝 | 그 줄을 지움. 쓰이는 지층은 지울 수 없음 |
|
||||
| [기본값] | 카드마다 | 그 카드 값만 기본값으로 되돌림(다른 카드는 그대로) |
|
||||
| [구간 추가] | 층 두께 카드 | 덮어쓰기 구간 한 줄(첫 측점 ~ 끝 측점)을 덧붙임 |
|
||||
| [수위선 구간 추가] · [점 추가] | 지하수 카드 | 우기 규칙이 「사용자 수위선」일 때만 보임 |
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
- 칸에 마우스를 올리면 **기본값과 근거**(지식DB 07 · KDS 11 70 05 등)가 보임. 사용자가 더한 지층은 「사용자 지층 — 출처 없음」.
|
||||
- 틀린 값은 칸 테두리가 **빨갛게** 되고, 마우스를 올리면 까닭(「범위 밖 (0 ~ 60)」 등)이 보임. 좌측에 「입력값을 확인하세요 — 틀린 칸 N」.
|
||||
- 바꾸고 저장하지 않으면 좌측에 「저장 안 된 변경이 있습니다.」가 보임.
|
||||
- **측점별 적용 지층** 표 = 측점마다 실제로 해석에 들어갈 위 지층 · 아래 지층 · 토층 두께. 구간 덮어쓰기가 걸린 줄은 바탕색이 다름. 아래 지층이 없으면 「전부 위 지층」.
|
||||
- 공유받은 회사(보기만)는 칸과 단추가 꺼지고 「보기만 허용된 프로젝트입니다.」가 보임.
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- **문헌 추정치 · 현장 시험 없음**: 지반물성치 기본값은 국내 설계 편람 · 학술 자료의 대표값임. 현장 시추 · 시험 값이 아니므로 결과는 참고용 — 현장 조사 값이 있으면 꼭 바꿔 넣을 것.
|
||||
- 자동저장 없음. 바꾼 값은 이 브라우저 탭에만 임시로 남고, [저장]해야 E03 [해석]에 쓰임.
|
||||
- 화면 위 「횡단 없는 측점 N 개 — 해석에서 빠짐」 띠 = 원본에 횡단 파일이 없어 해석에서 빠지는 측점. 누르면 목록이 펼쳐짐.
|
||||
- 측점의 B06 지반 · B06 토층 두께는 복사본 값이라 화면에서 고칠 수 없음 — 바꾸려면 구간 덮어쓰기를 쓸 것.
|
||||
- 저장 뒤 E03 에서 [해석]을 다시 누르면 값이 바뀐 측점만 다시 계산함.
|
||||
- 기준안전율 표의 「단기」 줄 · 「쌓기(연약지반)」 칸은 값만 보관함 — 지금 2D 해석 판정은 건기 · 우기 · 지진 시의 깎기 · 쌓기 칸만 씀.
|
||||
- 우기 지하수 규칙(KDS 기본 외 갈래 포함)은 지금 E03 횡단 그림의 수위선에 반영됨. 안전율 계산은 아직 모든 측점을 「공사 뒤 지표까지 포화」로 셈 — 고른 갈래의 계산 반영은 준비 중.
|
||||
- 칸 뜻 · 범위는 「칸 뜻」, 근거 · 수식은 「쓰인 방법과 근거」, 확정 안 된 값 목록은 「사용자 확정 전 값」 항목에 있음.
|
||||
@@ -0,0 +1,75 @@
|
||||
# 칸 뜻
|
||||
|
||||
## 지반물성치 표
|
||||
|
||||
| 칸 | 뜻 | 넣을 수 있는 범위 |
|
||||
| --- | --- | --- |
|
||||
| 지층 | 지층 이름 | 1~30 자 · 같은 이름 안 됨 |
|
||||
| γt (kN/m³) | 습윤 단위중량 — 물에 잠기지 않은 흙 1 m³ 의 무게 | 10 ~ 30 |
|
||||
| γsat (kN/m³) | 포화 단위중량 — 물에 잠긴 흙 1 m³ 의 무게. 지하수위 아래에서 씀 | 10 ~ 30 · γt 이상 |
|
||||
| c (kPa) | 점착력 — 흙 알갱이끼리 붙잡는 힘 | 0 ~ 1,000 |
|
||||
| φ (°) | 내부마찰각 — 흙이 서로 비비며 버티는 정도(클수록 단단함) | 0 ~ 60 |
|
||||
| 지하수 무리 | 「토사 · 풍화암」 또는 「암반」. 우기 규칙이 KDS 기본일 때 어느 수위 규칙을 쓸지 가름 | 둘 중 하나 |
|
||||
|
||||
기본 지층 여섯 = 풍화토 · 풍화암 · 연암 · 경암 · 성토 · 붕적토. 값은 「사용자 확정 전 값」 항목에 있음.
|
||||
|
||||
## B06 지반 → 지층
|
||||
|
||||
| 칸 | 뜻 | 기본 |
|
||||
| --- | --- | --- |
|
||||
| 토사 (경계 위) | B06 횡단의 암 경계 위 흙(모든 측점) | 풍화토 |
|
||||
| 리핑암 (경계 아래) | B06 리핑암 측점의 암 경계 아래 | 풍화암 |
|
||||
| 발파암 (경계 아래) | B06 발파암 측점의 암 경계 아래 | 연암 |
|
||||
| 성토체 | 쌓기로 새로 올린 흙 | 성토 |
|
||||
|
||||
## 층 두께 · 구간 덮어쓰기
|
||||
|
||||
| 칸 | 뜻 |
|
||||
| --- | --- |
|
||||
| 시작 (m) · 끝 (m) | 덮어쓸 누가거리 구간. 양 끝 측점 포함 · 끝은 시작보다 커야 함(0 ~ 100,000) |
|
||||
| 위 지층 | 구간 안 측점의 위 지층. 「B06 그대로」 = 바꾸지 않음 |
|
||||
| 아래 지층 | 구간 안 측점의 아래 지층. 「B06 그대로」 = 바꾸지 않음 |
|
||||
| 토층 두께 (m) | 원지반에서 아래 지층 윗면까지 깊이. 빈 칸 = B06 그대로(0 ~ 50) |
|
||||
|
||||
- 줄이 여럿 겹치면 **뒤 줄이 이김**.
|
||||
- 토사 측점은 B06 토층 두께가 없어 「전부 위 지층」임. 토사 측점에 아래 지층을 넣으려면 아래 지층과 함께 토층 두께도 넣을 것.
|
||||
- 쓰이는 지층(B06 지반 연결 · 구간에서 고른 지층)은 물성 표에서 지울 수 없음.
|
||||
|
||||
## 측점별 적용 지층 (읽기 전용)
|
||||
|
||||
| 칸 | 뜻 |
|
||||
| --- | --- |
|
||||
| 측점 | 측점 번호(예 `5+20`) |
|
||||
| B06 지반 | 복사한 B06 횡단의 지반 구분(토사 · 리핑암 · 발파암) |
|
||||
| B06 토층 두께 (m) | B06 암 경계 깊이. 토사 측점은 「-」 |
|
||||
| 위 지층 · 아래 지층 · 토층 두께 (m) | 연결 · 구간 덮어쓰기를 모두 반영한 최종 값 |
|
||||
|
||||
## 기준안전율
|
||||
|
||||
- 줄 = 건기 · 우기 · 지진 시 · 단기 · 칸 = 깎기 · 쌓기 · 쌓기(연약지반).
|
||||
- 넣을 수 있는 범위 = 1.0 ~ 3.0.
|
||||
- 지금 2D 해석 판정은 건기 · 우기 · 지진 시 줄의 깎기 · 쌓기 칸만 씀.
|
||||
|
||||
## 노면 하중 · 지진
|
||||
|
||||
| 칸 | 뜻 | 기본 · 범위 |
|
||||
| --- | --- | --- |
|
||||
| 노면 하중 (kPa) | 노면 양 끝 사이에 고르게 얹는 차량 · 통행 하중 | 10 · 0 ~ 100 |
|
||||
| 지진 검토 | 켜면 E03 에 「지진」 조건이 생김 | 끔 |
|
||||
| 수평지진계수 kh | 흙 무게의 몇 배를 옆으로 미는 힘으로 볼지. 지진 검토를 켰을 때만 넣음 | 0 · 0 ~ 0.5 |
|
||||
|
||||
## 지하수
|
||||
|
||||
| 칸 | 뜻 | 기본 |
|
||||
| --- | --- | --- |
|
||||
| 건기 지하수 | 건기 · 지진 시 수위 규칙(없음 · 지표 포화 · 비탈 높이 ½ 포화) | 없음 |
|
||||
| 우기 규칙 | 넷 중 하나 — KDS 기본 · 지표 아래 깊이 = 비탈 높이 × 비율 · 간극수압비 r_u · 사용자 수위선 | KDS 기본 |
|
||||
| 우기 · 토사 · 풍화암 | KDS 기본일 때, 비탈 겉 지층이 「토사 · 풍화암」 무리인 측점의 규칙 | 지표 포화 |
|
||||
| 우기 · 암반 | KDS 기본일 때, 비탈 겉 지층이 「암반」 무리인 측점의 규칙 | 비탈 높이 ½ 포화 |
|
||||
| 깊이 비율 | 수위 = 공사 뒤 지표에서 (비탈 높이 × 비율)만큼 아래 | 0.5 · 0 ~ 1 |
|
||||
| r_u | 간극수압 = 그 자리 흙 무게 압력 × r_u | 0.2 · 0 ~ 0.9 |
|
||||
| 수위선 구간 — 시작 · 끝 (m) | 수위선을 쓸 누가거리 구간. 덮지 않은 측점은 KDS 기본 규칙 | 없음 |
|
||||
| 점 (offset · 깊이) | offset = 중심선에서 거리(m · 좌 +, −200 ~ 200) · 깊이 = 공사 뒤 지표 아래(m, 0 ~ 50). 점은 둘 이상 · offset 은 작은 쪽부터 커지게 | [−10 : 1] · [10 : 1] 로 시작 |
|
||||
|
||||
- 비탈 겉 지층 = 토층 두께가 0 인 측점은 아래(암) 지층, 그 밖은 위 지층.
|
||||
- 수위선 구간이 겹치면 뒤 줄이 이김.
|
||||
@@ -0,0 +1,60 @@
|
||||
# 쓰인 방법과 근거
|
||||
|
||||
## 지반 값이 해석에 들어가는 길
|
||||
|
||||
- 비탈 안 흙덩이를 세로로 잘게 나눈 조각(절편)마다 무게와 버티는 힘을 셈(자세한 해석 방법은 E03 설명서 「쓰인 방법과 근거」).
|
||||
- **무게** = 지층별 (단위중량 × 그 지층 두께)의 합 × 절편 폭 + 노면 하중. 지하수위 위는 γt, 아래는 γsat 를 씀.
|
||||
- **버티는 힘**(Mohr-Coulomb 강도) = c × 바닥 길이 + (바닥을 누르는 힘 − 물이 떠받치는 힘) × tanφ.
|
||||
- 물이 떠받치는 힘(간극수압) = 물 단위중량 9.81 kN/m³ × 수위에서 절편 바닥까지 깊이. 물이 차면 버티는 힘이 줄어 안전율이 낮아짐.
|
||||
- **안전율** = 버티는 힘 합 ÷ 미끄러지게 하는 힘 합. 1 보다 작으면 무너지는 쪽, 기준안전율 이상이면 O.K.
|
||||
|
||||
## 지층을 나누는 방법
|
||||
|
||||
- 암 경계 = 원지반 선을 토층 두께만큼 그대로 아래로 내린 선(B06 물량 가름과 같은 정의).
|
||||
- 경계 위 = 위 지층(기본 풍화토) · 경계 아래 = 아래 지층(리핑암 측점 = 풍화암 · 발파암 측점 = 연암) · 쌓기 흙 = 성토.
|
||||
- B06 토층 두께는 물량을 가르려고 정한 값(기본 0.5 m)이라 실제 지층 두께와 다를 수 있음 — 구간 덮어쓰기로 고칠 것.
|
||||
|
||||
## 기준안전율 — KDS 11 70 05 (건설공사 비탈면 설계기준)
|
||||
|
||||
| 조건 | 깎기 | 쌓기 | 쌓기(연약지반) | 지하수 · 하중 조건 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 건기 | 1.5 | 1.5 | 1.3 | 비탈 안에 지하수 없음 |
|
||||
| 우기 | 1.2 | 1.3 | 1.2 | 토사 · 풍화암 = 지표까지 포화 · 암반 = 비탈 높이 ½ 깊이까지 포화 |
|
||||
| 지진 시 | 1.1 | 1.1 | 1.1 | 건기 수위 + 수평지진계수 kh |
|
||||
| 단기 | 1.1 | 1.1 | 1.1 | 1 년 미만 · 시공 중 |
|
||||
|
||||
- 근거 표 = KDS 11 70 05 표 4.3-1(쌓기 일반) · 4.3-2(연약지반 쌓기) · 4.3-3(깎기).
|
||||
- 우기 깎기 1.2 = 지하수위를 정해 두고 해석하는 방식의 값(강우 침투 해석을 따로 하면 1.3). 지하수 카드의 KDS 기본 규칙과 짝을 이룸 — 다른 우기 규칙을 고르면 기준안전율도 함께 검토할 것.
|
||||
|
||||
## 지하수 규칙 넷
|
||||
|
||||
1. **KDS 기본** — 비탈 겉 지층이 토사 · 풍화암이면 지표까지 포화, 암반이면 비탈 높이 ½ 깊이까지 포화(KDS 11 70 05 표 4.3-3).
|
||||
2. **지표 아래 깊이 = 비탈 높이 × 비율** — 수위 = 공사 뒤 지표에서 (비탈 높이 × 비율)만큼 아래. 좌 · 우 비탈은 각자 높이로 셈. 근거 없음 · 사용자 판단(0.5 = KDS 암반 ½ 와 같은 값).
|
||||
3. **간극수압비 r_u** — 수위선 없이 간극수압 = r_u × 그 자리 흙 무게 압력(γ × 깊이). 근거 없음 · 사용자 판단.
|
||||
4. **사용자 수위선** — 구간마다 점(offset · 지표 아래 깊이)을 이은 선. 점 사이는 곧은 선. 덮지 않은 측점은 KDS 기본 규칙.
|
||||
|
||||
- 건기 · 지진 시는 「건기 지하수」 규칙(기본 없음)을 씀.
|
||||
- 지금 E03 안전율 계산은 우기를 모든 측점 「공사 뒤 지표까지 포화」로 셈 — 위 규칙은 횡단 그림의 수위선에만 반영됨(계산 반영은 준비 중). 포화는 가장 불리한 쪽 가정임.
|
||||
|
||||
## 노면 하중
|
||||
|
||||
- 노면 양 끝 사이에 고르게 얹는 하중(기본 10 kPa = 1 m² 에 약 1 t).
|
||||
- 근거 = 옹벽 뒤 상재하중 10 kN/m² 를 빌려 씀(지식DB 구조물 안정계산 · KDS 11 80 05). 임도 차량 하중의 고유 근거는 없음.
|
||||
|
||||
## 지진
|
||||
|
||||
- 유사정적 해석 — 흙 무게 × kh 만큼 옆으로 미는 힘을 더해 셈.
|
||||
- KDS 11 90 00 1.8.1 ② — 무너졌을 때 영향 범위에 주요 구조물이 없으면 적용하지 않음. 임도는 발주자와 협의할 것 · 기본 끔.
|
||||
- kh 는 지반 가속도의 비율로 사용자가 넣음(지식DB 02 예시 0.05 ~ 0.15).
|
||||
|
||||
## 지반물성치 기본값 근거
|
||||
|
||||
- 지식DB 07(지반물성치 원문 근거) §3 · §4 종합 대조표 — 「도로설계요령」 · 「도로교시방서」 · 서울시 「지반조사편람」(2006) · 한국지반공학회 사면안정 학술발표회 · 한국도로공사 「도로설계실무편람」(1996) · 「건설공사 비탈면 설계기준 해설」 · Hoek & Bray.
|
||||
- 지식DB 04(표준 지반물성치 후보군 · 허용안전율) — 지층별 범위와 후보값.
|
||||
- 칸마다 근거 문장은 칸에 마우스를 올리면 보임.
|
||||
|
||||
## 한계
|
||||
|
||||
- 현장 시추 · 시험 없이 문헌 대표값을 씀 — 같은 이름의 지층도 현장마다 값이 크게 다를 수 있음.
|
||||
- 측점마다 지층은 위 · 아래 두 층까지만 나눔(구간 덮어쓰기도 두 층).
|
||||
- 강우 침투 해석(시간에 따른 수위 변화)은 하지 않음.
|
||||
@@ -0,0 +1,58 @@
|
||||
# 사용자 확정 전 값
|
||||
|
||||
아래 값은 문헌 · 기준에서 고른 **추정 기본값**임. 현장 시험 값이 아니며, 발주처 · 설계자 확인 전 상태임. 화면에서 언제든 바꿀 수 있음.
|
||||
|
||||
## 지반물성치 기본값
|
||||
|
||||
| 지층 | γt (kN/m³) | γsat (kN/m³) | c (kPa) | φ (°) | 문헌 범위(지식DB 07 §4) | 상태 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 풍화토 | 18.0 | 19.0 | 15 | 30 | γt 18~19 · c 5~20 · φ 27~32 | 문헌 추정 · 확정 전 |
|
||||
| 풍화암 | 20.0 | 21.0 | 30 | 33 | γt 19.5~21 · c 10~30 · φ 30~36 | 문헌 추정 · 확정 전 |
|
||||
| 연암 | 24.0 | 24.5 | 150 | 35 | γt 22~24 · c 50~300 · φ 33~40 | 문헌 추정 · 확정 전 |
|
||||
| 경암 | 26.0 | 26.0 | 300 | 40 | γt 25~27 · c 200 이상 · φ 40~45 | 문헌 추정 · 확정 전 |
|
||||
| 성토 | 18.0 | 19.0 | 10 | 28 | γt 18~19 · c 0~15 · φ 28~30 | 문헌 추정 · 확정 전 |
|
||||
| 붕적토 | 17.5 | 18.5 | 5 | 27 | γt 17~18 · c 0~10 · φ 25~28 | 문헌 추정 · 확정 전 |
|
||||
|
||||
- 출처 문장은 칸에 마우스를 올리면 보임.
|
||||
|
||||
## 지층 연결 · 두께
|
||||
|
||||
| 값 | 지금 기본값 | 출처 | 상태 |
|
||||
| --- | --- | --- | --- |
|
||||
| B06 토사 → | 풍화토 | 요구서 345 6장 · 분석 345-D | 브레인 제안 · 확정 전 |
|
||||
| B06 리핑암 → | 풍화암 | 같음 | 브레인 제안 · 확정 전 |
|
||||
| B06 발파암 → | 연암 | 같음 | 브레인 제안 · 확정 전 |
|
||||
| B06 성토체 → | 성토 | 같음 | 브레인 제안 · 확정 전 |
|
||||
| 암 측점 토층 두께 | B06 암 경계 깊이(기본 0.5 m) | B06 물량 가름용 값 — 지층 조사 값 아님 | 확인 필요 |
|
||||
|
||||
## 기준안전율
|
||||
|
||||
| 값 | 지금 기본값 | 출처 | 상태 |
|
||||
| --- | --- | --- | --- |
|
||||
| 건기 · 우기 · 지진 시 · 단기 | 「쓰인 방법과 근거」 표 | KDS 11 70 05 표 4.3-1 · 4.3-2 · 4.3-3 | 기준값 · 사용자 변경 가능 |
|
||||
| 우기 깎기 | 1.2 | 기본값 근거 글 = 지하수위 결정 방식 1.2(강우 침투 해석 1.3) | 지식DB 04 표는 「지표 포화 1.2 · 일반 지하수위 1.3」로 풀어 적음 — 어느 해석을 따를지 확인 필요 |
|
||||
|
||||
## 하중 · 지진
|
||||
|
||||
| 값 | 지금 기본값 | 출처 | 상태 |
|
||||
| --- | --- | --- | --- |
|
||||
| 노면 하중 | 10 kPa | 옹벽 뒤 상재하중 10 kN/m² 준용(KDS 11 80 05) · 임도 차량 하중 고유 근거 없음 | 브레인 제안 · 확정 전 |
|
||||
| 지진 검토 | 끔 | KDS 11 90 00 1.8.1 ② · 임도는 발주자 협의 | 발주자 협의 필요 |
|
||||
| 수평지진계수 kh | 0 | 사용자 입력 몫 | 켤 때 값 정할 것 |
|
||||
|
||||
## 지하수 규칙
|
||||
|
||||
| 값 | 지금 기본값 | 출처 | 상태 |
|
||||
| --- | --- | --- | --- |
|
||||
| 건기 지하수 | 없음 | KDS 11 70 05 표 4.3-3 | 기준값 |
|
||||
| 우기 규칙 | KDS 기본 | KDS 11 70 05 표 4.3-3 | 기준값 |
|
||||
| 우기 · 토사 · 풍화암 | 지표 포화 | KDS 11 70 05 표 4.3-3 | 기준값 |
|
||||
| 우기 · 암반 | 비탈 높이 ½ 포화 | KDS 11 70 05 표 4.3-3 | 기준값 |
|
||||
| 깊이 비율 | 0.5 | 근거 없음(KDS 암반 ½ 와 같은 값) | 사용자 입력 |
|
||||
| r_u | 0.2 | 근거 없음 | 사용자 입력 |
|
||||
| 지하수 무리 | 풍화토 · 풍화암 · 성토 · 붕적토 → 「토사 · 풍화암」, 연암 · 경암 → 「암반」 | 기본값 파일 | 확정 전 |
|
||||
|
||||
## 계산에 아직 안 들어간 값
|
||||
|
||||
- 우기 규칙(KDS 기본의 암반 ½ · 깊이 비율 · r_u · 사용자 수위선)은 지금 E03 횡단 그림의 수위선에만 보임. 안전율 계산은 모든 측점을 「공사 뒤 지표까지 포화」로 셈 — 계산 반영은 준비 중.
|
||||
- 기준안전율 「단기」 줄 · 「쌓기(연약지반)」 칸은 2D 판정에 아직 안 쓰임.
|
||||
@@ -0,0 +1,69 @@
|
||||
# 2D 해석 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
- 모든 측점 횡단에서 비탈이 미끄러질 위험을 2D 한계평형해석으로 셈 — 측점 · 좌우 비탈 · 조건(건기 · 우기 · 지진)마다 최소 안전율과 판정(O.K · N.G).
|
||||
- 깎기 비탈 · 쌓기 비탈을 따로 셈. 옹벽 · 석축 등 벽이 있는 측점은 벽을 넣어 셈.
|
||||
- 「보강 검토」 탭에서 비탈 완화 · 소단 가정을 다시 해석하고, 벽의 외적 안정(활동 · 전도 · 지지력)을 검토함.
|
||||
- 화면 = 좌 측점 목록 · 위 평면 위험 띠 · 가운데 횡단 · 우 결과.
|
||||
|
||||
## 작업 순서
|
||||
|
||||
### 해석
|
||||
|
||||
1. 지반 조건(E02)에서 값을 확인하고 [저장]해 둠 — 저장한 값으로만 계산함.
|
||||
2. 좌측 아래 [해석]을 누름. 화면이 잠기고 「해석 중 n% · 측점 k / N」 진행률이 보임. 측점 수십 개면 몇 분 걸림.
|
||||
3. 끝나면 「해석이 끝났습니다.」 알림 뒤 목록 · 평면 띠 · 횡단이 채워짐. 처음 보이는 측점 = 지금 조건에서 안전율이 가장 낮은 측점.
|
||||
4. 좌측 위 **조건**에서 건기 · 우기(지진 검토를 켰으면 지진도)를 고름. 목록 · 평면 띠 · 횡단이 그 조건으로 바뀜.
|
||||
5. 목록 줄이나 평면 띠의 측점 근처를 눌러 측점을 고름. 가운데 위 비탈 칸에서 「좌 비탈 · 깎기」 등 비탈을 고름(처음 = 안전율 낮은 쪽).
|
||||
6. 횡단의 절편이나 우측 절편표 줄을 누르면 그 절편의 힘이 자유물체도 · 그래프에 보임.
|
||||
7. 보고서(E05)에 넣을 대표 단면은 목록 「대표」 칸 별(★ · ☆)을 눌러 켜고 끈 뒤 [대표 단면 저장].
|
||||
8. [3D 보기]로 노선 3D 면 위 안전율 색을 봄.
|
||||
|
||||
### 보강 검토
|
||||
|
||||
1. 측점을 고르고 가운데 위 보기 칸에서 「보강 검토」를 고름. 우측이 보강 검토 판으로 바뀜.
|
||||
2. **비탈 완화 · 소단 가정**: 비탈 · 기울기 1:n · 소단 폭 · 소단 높이를 넣고 [가정 해석]. 또는 [최소 완화 찾기]로 지금 조건 기준안전율을 맞추는 가장 급한 기울기를 찾음(수십 초 ~ 1 분).
|
||||
3. **벽 외적 안정**: 벽 있는 측점에서만 보임. 보강 검토 탭을 열면 Coulomb · 배면 수위 0 으로 한 번 자동 검토함. 토압 · 배면 수위를 바꿔 [외적 안정 검토].
|
||||
4. 네일 · 앵커 보강 가정은 준비 중.
|
||||
|
||||
## 단추
|
||||
|
||||
| 단추 · 칸 | 자리 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| 조건 · 정렬 | 좌측 위 | 조건 고름 · 「측점 순」 또는 「안전율 낮은 순」 |
|
||||
| N.G 만 | 좌측 위 | 지금 조건에서 N.G 인 측점만 보임 |
|
||||
| [해석] | 좌측 아래 | 전 측점 해석. 입력이 같으면 「입력이 같아 다시 셀 것이 없습니다.」 · 바뀐 측점만 다시 셈 |
|
||||
| [3D 보기] | 좌측 아래 | 노선 3D 면 색칠 창을 엶(해석 뒤에만 켜짐) · [닫기]로 닫음 |
|
||||
| [대표 단면 저장] | 좌측 아래 | 별 켬/끔을 저장함(바꾼 것이 있을 때만 켜짐) |
|
||||
| 비탈 칸 | 가운데 위 | 볼 비탈(좌 · 우 · 깎기 · 쌓기) |
|
||||
| 보기 칸 | 가운데 위 | 「해석 결과」 ↔ 「보강 검토」 |
|
||||
| 등안전율 지도 · 원호 중심 격자 · 다른 후보 | 가운데 위 | 횡단 그림에 켜고 끔(기본 = 지도 켬 · 격자 끔 · 후보 켬) |
|
||||
| [가정 해석] · [최소 완화 찾기] | 보강 검토 | 비탈 가정 재해석 |
|
||||
| [외적 안정 검토] | 보강 검토 | 벽 활동 · 전도 · 지지력 검토 |
|
||||
|
||||
- 횡단 그림: 휠 = 확대 · 축소 · 가운데 단추 끌기 = 옮기기 · 두 번 누름 = 처음 배율. 마우스 아래 「거리 · EL」(offset · 표고) 표시.
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
- **O.K** = 최소 안전율 ≥ 기준안전율(E02) · **N.G** = 그보다 작음 · 「-」 = 해석 없음 · 「오류」 = 그 측점 계산 실패(줄에 마우스를 올리면 까닭).
|
||||
- 판정에 쓰는 안전율 = Bishop 간편법 최소값(원호 · 비원호 중 작은 쪽). 측점 판정 = 그 조건 좌 · 우 비탈 중 하나라도 N.G 면 N.G.
|
||||
- **좌측 목록**: 측점 · 좌 · 우(깎 · 쌓) · 조건별 최소 안전율 · 판정 · 대표. ▦ = 벽 있는 측점.
|
||||
- **평면 위험 띠**: 노선을 지금 조건 판정 색으로 칠함(초록 O.K · 빨강 N.G · 회색 해석 없음). 오른쪽 위에 판정별 측점 수. 고른 측점은 동그라미.
|
||||
- **횡단 색(등안전율)**: 원호 중심 자리마다 최소 안전율을 기준안전율 대비 일곱 띠로 칠함(밝은 화면 기준) — 진한 빨강(기준의 0.8 배 미만) · 빨강(0.8 ~ 0.9) · 분홍(0.9 ~ 1.0) · 연회색(1.0 ~ 1.1) · 연파랑(1.1 ~ 1.25) · 파랑(1.25 ~ 1.5) · 진파랑(1.5 배 이상). 범례에는 실제 안전율 값으로 적힘.
|
||||
- **횡단 선**: 굵은 선 = 임계 활동면(Fs 값 · 원호 중심 Xc · Zc · 반경 R) · 옅은 선 = 다른 방법 · 다른 꼴 후보 · 점선 = 지하수위 · 노면 위 눈금 = 노면 하중 · 회색 빗금 = 벽.
|
||||
- **우측 결과**: 최소 안전율 · 판정 · 기준안전율 · 방법 · 수렴 · 비탈 높이 · 원호 중심 · 반경 · 방법별 최소 안전율 표(Fellenius · Bishop · Janbu 보정 · Spencer) · 절편표 · 절편 자유물체도 · 절편력 그래프. 칸 뜻은 「칸 뜻」 항목.
|
||||
- **3D 보기**: 노선 면을 가장 가까운 측점의 (최소 안전율 ÷ 기준안전율) 띠 색으로 칠함 · 해석 없는 측점은 회색. 표시만 함.
|
||||
- **대표 단면 별**: 채운 별 ★ = 켬 · 밑줄 = 사용자가 바꿈. 자동으로 켜지는 측점 = 쌓기 높이 10 m 이상 · 깎기 높이 15 m 이상 · 최소 안전율 하위 5 곳.
|
||||
- 좌측 상태 글: 「해석 시각」 · 「지반 조건이 바뀌었습니다 — [해석]을 다시 누르세요」(보이는 결과는 옛 결과) · 「대표 단면 바뀜 — 저장 안 됨」.
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- **문헌 추정치 · 현장 시험 없음**: 지반 값이 문헌 대표값이라 안전율은 설계 검토용 참고값임. 현장 조사 값으로 바꿔 다시 해석할 것.
|
||||
- 해석은 서버가 함 · 화면은 결과만 보임. 같은 입력이면 다시 셈하지 않음.
|
||||
- 우기 계산 = 지금은 모든 측점 「공사 뒤 지표까지 포화」. E02 에서 고른 우기 규칙은 횡단 그림의 지하수위 점선에만 반영됨(계산 반영은 준비 중). r_u 를 골랐으면 그림 아래에 「지하수 = 간극수압비 r_u …」 글만 보임.
|
||||
- 탐색 범위 = 노면 반대쪽 끝 ~ 비탈 끝 바깥 (5 m 와 비탈 높이 2 배 중 큰 값)까지 — 설계 비탈을 지나는 면만 봄. 노선 밖 자연 비탈 전체 붕괴는 광역 위험(E07) 몫.
|
||||
- 깊이 0.3 m 미만으로 겉만 긁는 면은 붕괴로 보지 않음.
|
||||
- 벽은 아주 단단한 물체로 넣어 활동면이 벽을 지나지 않음 — 벽 자체 안정은 보강 검토의 「벽 외적 안정」으로 봄.
|
||||
- 보강 가정은 따로 저장되는 검토용 결과임 — 설계 · 원본 해석 결과 · 임도 프로젝트는 바뀌지 않음. 먼저 지금 입력으로 [해석]이 끝나 있어야 함. 벽이 선 비탈은 가정 해석 대신 벽 외적 안정으로 검토할 것.
|
||||
- 공유받은 회사(보기만)는 [해석] · 별 · 보강 단추가 꺼짐.
|
||||
@@ -0,0 +1,73 @@
|
||||
# 칸 뜻
|
||||
|
||||
## 좌측 측점 목록
|
||||
|
||||
| 칸 | 뜻 |
|
||||
| --- | --- |
|
||||
| 측점 | 측점 번호 · ▦ = 벽(옹벽 · 석축 등) 있는 측점 |
|
||||
| 좌 · 우 | 좌 비탈 · 우 비탈 역할(깎 = 깎기 · 쌓 = 쌓기) |
|
||||
| 건기 · 우기 · 지진 | 조건별 그 측점 최소 안전율(좌 · 우 중 작은 값 · Bishop) |
|
||||
| 판정 | 지금 조건의 O.K · N.G · 「-」(해석 없음) · 「오류」 |
|
||||
| 대표 | 보고서 대표 단면 별. 마우스를 올리면 자동으로 켜진 까닭(쌓기 10 m 이상 · 깎기 15 m 이상 · 최소 안전율 하위 5 · 사용자 지정) |
|
||||
|
||||
## 우측 결과
|
||||
|
||||
| 칸 | 뜻 |
|
||||
| --- | --- |
|
||||
| 최소 안전율 | 고른 비탈 · 조건에서 Bishop 간편법으로 찾은 가장 낮은 안전율(소수 셋째 자리) |
|
||||
| 판정 | 최소 안전율 ≥ 기준안전율이면 O.K |
|
||||
| 기준안전율 | E02 기준안전율 표의 그 조건 · 깎기 또는 쌓기 칸 |
|
||||
| 방법 | 「Bishop · 원호」 또는 「Bishop · 비원호」 — 임계면 꼴 |
|
||||
| 수렴 | 반복 계산이 답에 닿았는지(예 · 아니오)와 반복 횟수 |
|
||||
| 비탈 높이 | 노면 끝에서 비탈 끝(원지반과 만나는 점)까지 높이차(m) |
|
||||
| 벽 있음 | 이 측점 단면에 넣은 벽 수 |
|
||||
| 원호 중심 · 반경 | 임계 원호의 중심(offset, 표고)과 반지름(m). 비원호면 안 보임 |
|
||||
| 방법별 표 | Fellenius · Bishop · Janbu 보정 · Spencer 각각의 최소 안전율(원호 · 비원호 중 작은 값) |
|
||||
|
||||
## 절편표
|
||||
|
||||
임계 활동면 위 흙을 세로로 나눈 조각(절편)마다 한 줄. 줄을 누르면 그 절편을 고름.
|
||||
|
||||
| 칸 | 뜻 · 단위 |
|
||||
| --- | --- |
|
||||
| # | 절편 번호 |
|
||||
| 폭 b | 절편 가로 폭(m) |
|
||||
| 높이 h | 절편 가운데 높이(m) |
|
||||
| α° | 절편 바닥 기울기(°) |
|
||||
| W+Q | 절편 무게 + 노면 하중(kN/m — 단면 1 m 폭당) |
|
||||
| u | 바닥 간극수압(kPa) |
|
||||
| N' | 바닥을 누르는 유효 수직력(kN/m) — 물이 떠받치는 몫을 뺀 힘 |
|
||||
| S | 바닥에서 동원된 전단력 = 버틸 수 있는 힘 ÷ 안전율(kN/m) |
|
||||
| c · φ° | 바닥이 지나는 지층의 점착력(kPa) · 내부마찰각(°) |
|
||||
|
||||
## 절편 자유물체도 · 절편력 그래프
|
||||
|
||||
- 자유물체도 = 고른 절편 하나를 크게 그리고 힘을 화살로 보임. 화살 길이 = 힘 크기 비례(kN/m).
|
||||
- 화살 이름: W = 무게 · Q = 노면 하중 · N = 유효 수직력 · U = 바닥 수압 · S = 전단 저항 · EL · ER = 양옆 수평력 · XL · XR = 양옆 연직력(값이 있을 때만).
|
||||
- 그래프 = 절편마다 바닥의 유효 수직응력 σ' · 간극수압 u · 동원 전단응력 τ(kPa 한 축). 가로 = 거리(offset). 기둥에 마우스를 올리면 세 값이 보임.
|
||||
|
||||
## 보강 검토 — 비탈 완화 · 소단 가정
|
||||
|
||||
| 칸 | 뜻 |
|
||||
| --- | --- |
|
||||
| 비탈 | 가정을 넣을 비탈(좌 · 우 · 깎기 · 쌓기) |
|
||||
| 기울기 1:n | 비탈 전체를 바꿀 기울기 n(세로 1 에 가로 n). 빈 칸 = 지금 값 · 0.1 ~ 4 |
|
||||
| 소단 폭 m | 소단 한 칸 폭(0 ~ 10 m) |
|
||||
| 소단 높이 m 마다 | 몇 m 높이마다 소단을 둘지(0 ~ 30 m). 폭 · 높이 둘 다 넣어야 소단이 생김 |
|
||||
| 결과 글 | 「1:지금 → 1:가정 · 소단 폭 / 높이 · 찾은 결과(회) · 걸린 초」 |
|
||||
| 결과 표 | 조건마다 전 Fs · 후 Fs · 기준안전율 · 전 판정 · 후 판정 |
|
||||
| 가정 단면 | 바뀐 비탈과 그 임계면 그림(지금 조건) |
|
||||
|
||||
- [최소 완화 찾기] 목표 = 지금 조건 기준안전율. 지금 기울기부터 성긴 걸음으로 완만하게 하며 처음 맞는 기울기를 찾고, 그 앞 칸 사이를 반씩 좁혀 0.05 단위로 맞춤. 비탈이 급한 자연 사면에서는 완만하게 할수록 깎기 높이가 커져 오히려 안전율이 낮아질 수도 있음. 못 찾으면 「못 찾음 — 원지반을 만나는 가장 완만한 1:n 까지 봄」.
|
||||
|
||||
## 보강 검토 — 벽 외적 안정 (KDS 11 80 05)
|
||||
|
||||
| 칸 | 뜻 |
|
||||
| --- | --- |
|
||||
| 토압 | 벽 뒤 흙이 미는 힘 계산법 — Coulomb(기본) · Rankine |
|
||||
| 배면 수위 ÷ 벽 높이 | 벽 뒤 물 높이 비율(0 = 물 없음 · 1 = 벽 꼭대기까지) |
|
||||
| 머리 글 | 벽 이름 · 평상시 또는 지진 시 · H(벽 높이 m) · B(바닥 폭 m) · Ka(주동토압계수) · KAE(지진 시 토압계수) · e(바닥 합력이 가운데서 벗어난 거리 m) · q_max(최대 지반 반력 kPa) |
|
||||
| 활동 · 전도 · 지지력 | 줄마다 안전율 · 기준 · 판정 |
|
||||
|
||||
- 기준(KDS 11 80 05): 평상시 활동 1.5 · 전도 2.0 · 지지력 3.0 · 지진 시 활동 1.2 · 전도 1.5 · 지지력 2.0.
|
||||
- 지진 시 줄은 E02 에서 지진 검토를 켜고 kh 가 0 보다 클 때만 나옴.
|
||||
@@ -0,0 +1,81 @@
|
||||
# 쓰인 방법과 근거
|
||||
|
||||
## 한계평형해석 — 절편법
|
||||
|
||||
- 가상의 미끄럼면(활동면) 위 흙덩이를 세로 조각(절편) 50 개로 나눔.
|
||||
- 절편마다 무게 W(지층별 단위중량 × 두께 × 폭 + 노면 하중) · 바닥 수압 u · 바닥 강도 c · φ 를 셈.
|
||||
- **안전율 Fs** = 버티는 힘(또는 모멘트) 합 ÷ 미끄러지게 하는 힘(또는 모멘트) 합. 1 미만 = 무너지는 쪽.
|
||||
- 버티는 힘 = c × 바닥 길이 + (바닥을 누르는 힘 − 수압 × 바닥 길이) × tanφ (Mohr-Coulomb).
|
||||
- 수압 u = 9.81 kN/m³ × 지하수위에서 절편 바닥까지 깊이. 지하수위 아래 흙 무게는 γsat 로 셈.
|
||||
- 근거 = 지식DB 02(토사 · 복합 비탈면 한계평형해석 수식 명세) §1 · §2.
|
||||
|
||||
## 네 가지 방법
|
||||
|
||||
| 방법 | 화면 이름 | 원리(쉬운 말) | 쓰임 |
|
||||
| --- | --- | --- | --- |
|
||||
| Bishop 간편법 | Bishop | 절편 옆 수평력은 넣고 연직력은 뺌 · 원 중심 둘레 모멘트로 평형 | **기본 · 판정** |
|
||||
| Janbu 간편법 + 보정 | Janbu 보정 | 수평 · 연직 힘 평형만으로 셈 → 면 모양 보정계수 f0 를 곱함 | 비원호(꺾인 면) 탐색 · 비교 |
|
||||
| Fellenius 법 | Fellenius | 절편 사이 힘을 모두 뺌 · 반복 없이 한 번에 셈 · 보통 낮게(보수적으로) 나옴 | 비교 |
|
||||
| Spencer 법 | Spencer | 모멘트 · 힘 평형을 함께 맞춤(절편 사이 힘 기울기 λ 를 함께 찾음) | 비원호 탐색 · 비교 |
|
||||
|
||||
- 이 밖의 방법(GLE · Morgenstern-Price 등)은 쓰지 않음.
|
||||
- **Bishop 식 말풀이**: Fs = Σ[(c × 폭 + (W − u × 폭) × tanφ) ÷ mα] ÷ Σ(W × sinα + 지진 항). mα = cosα + sinα × tanφ ÷ Fs. 양쪽에 Fs 가 있어 맞을 때까지 되풀이해 셈.
|
||||
- 진출부가 급해 어느 절편의 mα 가 0.2 보다 작아지는 면은 값이 터지므로 버림(상용 프로그램 관례).
|
||||
- **Janbu 보정계수** f0 = 1 + b1 × (d/L − 1.4 × (d/L)²). L = 면 양 끝을 이은 직선 길이 · d = 그 직선에서 면까지 가장 깊은 거리 · b1 = 0.31(c 가 모두 0) · 0.69(φ 가 모두 0) · 0.50(그 밖).
|
||||
- **지진**: 절편마다 무게 × kh 만큼 옆으로 미는 힘을 더함(유사정적).
|
||||
|
||||
## 임계 활동면 찾기
|
||||
|
||||
- **원호** — 두 방식을 함께 씀.
|
||||
- Entry & Exit: 비탈 위 · 아래 진입 · 진출 점 25 개씩을 여러 각도의 원호로 이음.
|
||||
- Grid & Radius: 원호 중심 격자 15 × 15 자리마다 반지름 10 가지.
|
||||
- 위 후보를 절편 30 개로 빠르게 거른 뒤, 안전율 낮은 30 개를 출발점으로 차분진화(무리 최적화)로 다듬어 절편 50 개로 최종 셈.
|
||||
- **비원호** — 꼭짓점 4 개 꺾은선. 출발점 = 가장 낮은 원호 + 블록 모양 면. Janbu · Spencer 로 꼭짓점을 옮겨 가며 최소를 찾고, 찾은 면에서 Fellenius · Bishop 값도 셈.
|
||||
- 방법마다 원호 · 비원호 중 작은 값이 그 방법의 최소 안전율 · 판정은 Bishop 최소 안전율.
|
||||
- 같은 입력이면 늘 같은 답이 나오도록 무작위 씨앗을 고정함.
|
||||
- **등안전율 지도** = 원호 중심 격자 자리마다 그 중심으로 그린 원호 중 Bishop 최소 안전율.
|
||||
- 깊이 0.3 m 미만으로 겉만 긁는 면은 뺌(표토 벗겨짐은 붕괴로 보지 않음).
|
||||
- 근거 = 지식DB 02 §3(Grid & Radius · Entry & Exit). 상용 대응 = SLOPE/W · Slide 의 같은 이름 방식.
|
||||
|
||||
## 조건 · 비탈
|
||||
|
||||
| 조건 | 지하수 · 하중 | 기준안전율(E02 기본 · 깎기 / 쌓기) |
|
||||
| --- | --- | --- |
|
||||
| 건기 | 지하수 없음 | 1.5 / 1.5 |
|
||||
| 우기 | 지금은 모든 측점 공사 뒤 지표까지 포화 | 1.2 / 1.3 |
|
||||
| 지진(E02 에서 켰을 때) | 건기 수위 + 수평지진계수 kh | 1.1 / 1.1 |
|
||||
|
||||
- 기준 = KDS 11 70 05 표 4.3-1 · 4.3-2 · 4.3-3(지식DB 04 §2). 값은 E02 에서 바꿀 수 있음.
|
||||
- 좌 · 우 비탈을 따로 셈 — 깎기 = 산 쪽에서 노면 쪽으로 미끄러짐 · 쌓기 = 노면에서 바깥쪽으로 미끄러짐.
|
||||
- 탐색 범위 = 노면 반대쪽 끝 ~ 비탈 끝 바깥 (5 m 와 비탈 높이 2 배 중 큰 값).
|
||||
- 노면 하중 = 노면 양 끝 사이 고른 하중(E02 · 기본 10 kPa).
|
||||
- 판정 = Bishop 최소 안전율 ≥ 그 조건 · 그 비탈(깎기 · 쌓기) 기준안전율이면 O.K.
|
||||
|
||||
## 벽 넣기
|
||||
|
||||
- B06 에 놓인 옹벽 · 석축 · 돌쌓기 · 관 기슭막이 단면을 B06 화면과 같은 계산으로 그려 넣음.
|
||||
- 벽 = 단위중량 23 kN/m³ · c 1,000 kPa · φ 45° 의 아주 단단한 물체 — 활동면이 벽을 지나지 않고 벽 뒤 · 아래로 돎.
|
||||
|
||||
## 보강 검토
|
||||
|
||||
- **비탈 완화 · 소단** — 비탈 시작점(깎기 = 측구 바깥 · 쌓기 = 노면 끝)부터 1:n 기울기(+ 높이마다 소단)로 원지반을 만날 때까지 다시 그려 같은 방법 · 조건으로 다시 해석함. 2 단 경사(암 · 토사)는 한 기울기로 갈음.
|
||||
- **벽 외적 안정**(KDS 11 80 05 · 지식DB 04 §2) — 토압 = Coulomb 또는 Rankine · 지진 시 = Mononobe-Okabe + 벽 관성 kh × 벽 무게.
|
||||
- 활동 안전율 = (바닥 연직력 × 마찰계수 + 바닥 점착 × 폭) ÷ 수평력 합(앞쪽 수동토압 뺌).
|
||||
- 전도 안전율 = 앞굽 둘레 버티는 모멘트 ÷ 넘기는 모멘트.
|
||||
- 지지력 안전율 = Meyerhof 극한 지지력(유효 폭 · 경사 보정) ÷ 최대 지반 반력.
|
||||
- 기준 = 평상시 활동 1.5 · 전도 2.0 · 지지력 3.0 · 지진 시 1.2 · 1.5 · 2.0.
|
||||
- 가정: 벽 뒤 흙 점착 0(안전 쪽) · 벽 단위중량 23 kN/m³ · 벽 뒤 상재하중 = 쌓기 쪽 벽만 노면 하중 · 수압 = 배면 수위만큼 정수압 + 바닥 양압력.
|
||||
|
||||
## 검증
|
||||
|
||||
- 상용 프로그램 검증 예제 12 문제(40 개 값)와 비교해 차이 2.2 % 안.
|
||||
- 벽 외적 안정은 교과서 손계산과 비교해 차이 2 % 안.
|
||||
|
||||
## 가정 · 한계
|
||||
|
||||
- 지반 값 = 문헌 추정치 · 현장 시험 없음.
|
||||
- 2D 해석이라 비탈 양옆이 붙잡아 주는 효과(끝단 효과)는 빠짐 — 3D 해석(E04) 몫.
|
||||
- 우기는 지표까지 포화로 셈(가장 불리한 쪽). E02 우기 규칙 갈래(암반 ½ · 깊이 비율 · r_u · 수위선)의 계산 반영은 준비 중.
|
||||
- 강우 침투 해석(시간에 따른 수위 변화) · 인장균열은 넣지 않음.
|
||||
- 암반 절리를 따라 미끄러지는 평면 · 쐐기 파괴는 암반 해석(E06) 몫.
|
||||
- 네일 · 앵커 보강 가정은 준비 중.
|
||||
@@ -0,0 +1,68 @@
|
||||
# 3D 해석 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
- 노선 한 구간(보통 약 100 m)을 골라 지형 전체를 입체로 보고 가장 위험한 3차원 활동면(흙이 미끄러져 내리는 바닥면)을 찾음.
|
||||
- 땅을 1 m 기둥 격자로 나눠 셀마다 「그 셀을 지나는 활동면 중 가장 낮은 안전율」을 색 지도로 보여 줌.
|
||||
- 가장 위험한 활동면의 안전율 · 무너질 흙의 양(임계 체적) · 같은 구간 2D(E03) 결과와의 비를 오른쪽에 보여 줌.
|
||||
- 계산은 모두 서버가 함. 화면은 결과를 그리기만 함.
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. 왼쪽 **구간** 카드 목록에서 구간 하나를 누름(예: `0 – 100 m`). 직접 정하려면 아래 **시작 (m)** · **끝 (m)** 칸에 숫자를 넣음.
|
||||
2. **탐색 설정** 카드에서 **조건**(건기 · 우기)과 **활동면 모양**을 고름. 처음에는 기본값 그대로 써도 됨.
|
||||
3. 필요하면 **고급 설정** 카드를 펼쳐 기둥 크기 · 구 중심 격자 · 부피 범위를 바꿈(칸 뜻은 「칸 뜻」 문서).
|
||||
4. 왼쪽 아래 **[해석]** 을 누름. 진행 막대에 「해석 중 n% · 합성 지형 → 거친 탐색 → 고운 탐색 → 타원체」 차례가 보임.
|
||||
5. 끝나면 「3D 해석이 끝났습니다.」 알림과 함께 가운데 지도에 색이 칠해지고 오른쪽 **구간 결과** 가 채워짐.
|
||||
6. 가운데 위 **[3D 보기]** 로 바꿔 입체 지형 위의 임계 활동면을 확인함.
|
||||
7. 다른 구간도 같은 방법으로 해석함. 해석한 구간은 목록에 색 점과 `Fs` 값이 붙음.
|
||||
8. 보고서에 넣으려면 E05 보고서 화면에서 **[보고서 갱신]** 을 누름(3D요약 시트에 구간마다 한 줄).
|
||||
|
||||
## 단추
|
||||
|
||||
| 자리 | 단추 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| 왼쪽 아래 | **[해석]** | 고른 구간을 해석함. 입력(지반 조건 · 탐색 설정 · 구간)이 저장된 결과와 같으면 다시 셈하지 않고 「입력이 같아 저장된 결과를 엽니다.」 |
|
||||
| 왼쪽 아래 | **[다시 해석]** | 입력이 같아도 처음부터 다시 셈 |
|
||||
| 진행 막대 옆 | **[끊기]** | 돌고 있는 해석을 멈춤. 전에 저장된 결과는 그대로 남음 |
|
||||
| 고급 설정 카드 | **[기본값]** | 탐색 설정 칸을 기본값으로 되돌림(구간 시작 · 끝은 그대로 · 조건은 건기) |
|
||||
| 가운데 위 | **[평면 지도]** · **[3D 보기]** | 위에서 본 색 지도와 입체 보기를 바꿈 |
|
||||
| 가운데 위 | **[최소 안전율]** · **[임계 체적]** | 지도 · 3D 색칠 기준을 바꿈 |
|
||||
| 3D 보기 위 | **임계 활동면** 체크 | 켬 = 활동체를 걷어낸 지표 + 붉은 활동면 · 끔 = 원래 지표만 |
|
||||
|
||||
- 평면 지도: 마우스 휠 = 확대 · 축소, 끌기 = 옮김, 두 번 누름 = 화면 맞춤, 셀 위에 올리면 그 셀의 최소 안전율 · 임계 체적 · 가까운 측점이 뜸.
|
||||
- 3D 보기: 끌기 = 돌림, 휠 = 확대 · 축소. 오른쪽 위 나침반이 방위를 알려 줌.
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
- **평면 지도**: 셀 색 = 그 셀을 지나는 활동면 중 가장 낮은 안전율. 붉을수록 위험. 점선 윤곽 = 구간 전체에서 가장 위험한 활동면이 땅을 자르는 범위, 동그라미와 `Fs` 글 = 그 활동면 자리. 굵은 선 = 고른 구간.
|
||||
- **안전율 색 단계**: 1.0 미만 · 1.0~1.2 · 1.2~1.3 · 1.3~1.5 · 1.5~2.0 · 2.0~3.0 · 3.0 이상 · 회색 = 값 없음(활동면이 닿지 않은 셀). 1.3(우기) · 1.5(건기)가 기준 경계.
|
||||
- **임계 체적 색 단계**: 10 m³ 미만 · 10~100 · 100~1,000 · 1,000~10,000 · 10,000 m³ 이상 — 짙을수록 큰 붕괴.
|
||||
- **3D 보기**: 지표 색 = 지금 고른 층(안전율 또는 체적) · 파란 관 = 노선 중심선 · 짙은 붉은 면 = 임계 활동면(지표 아래 부분만).
|
||||
- **오른쪽 구간 결과**:
|
||||
|
||||
| 줄 | 뜻 |
|
||||
| --- | --- |
|
||||
| 3D 최소 안전율 | 구간에서 찾은 가장 낮은 3D 안전율 |
|
||||
| 판정 | 「만족」 또는 「불만족」 · 괄호 안 = 조건과 기준(예: 건기 ≥ 1.50) |
|
||||
| 2D 최소 안전율 (같은 구간) | E03 측점 결과 중 이 구간 안 가장 낮은 값 · 없으면 「2D 결과 없음」 |
|
||||
| 3D / 2D 비 | 3D 최소 ÷ 2D 최소 · 1 보다 크면 2D 가 안전 쪽(보수적) |
|
||||
| 임계 체적 | 가장 위험한 활동면 위 흙의 부피(m³) |
|
||||
| 다스린 방법 | 판정에 쓴 방법(3D Bishop 또는 3D Janbu 보정) |
|
||||
| 3D Bishop · 3D Janbu 보정 · Hovland (비교) | 같은 활동면을 세 방법으로 셈한 값 |
|
||||
| 위치 (가까운 측점) | 가장 위험한 활동면에 가장 가까운 측점 |
|
||||
| 셀 수 · 시험 활동면 수 · 계산 시간 | 계산 규모 |
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- **문헌 추정치 · 현장 시험 없음** — 지반 물성치는 문헌 · 기준값을 적용한 추정치이며 현장 조사 · 시험 성과를 반영하지 않았음. 안전율은 비교 · 검토용으로 볼 것.
|
||||
- 결과는 서버에 구간마다 자동 보관됨(따로 [저장] 없음). 탐색 설정 칸 값은 이 브라우저 탭에만 기억됨.
|
||||
- E02 지반 조건을 바꾸면 그 구간 결과 아래 「입력(지반 조건 · 탐색 설정 · 엔진)이 바뀌었습니다 — 다시 해석하세요.」 경고가 뜨고 목록에 「옛 결과」 가 붙음. **[해석]** 을 다시 누를 것.
|
||||
- 3D 는 E02 에서 **[저장]** 한 지반 조건을 씀. 해석 전 E02 에서 [저장] 을 한 번 눌러 둘 것.
|
||||
- 한 번에 해석할 수 있는 구간은 500 m 이하. 구간 안 측점이 둘 이상이어야 함.
|
||||
- 해석 하나에 수십 초 ~ 수 분 걸림(구간 길이 · 탐색 폭 · 기둥 크기에 따라). 한 프로젝트에서 한 번에 하나만 돎.
|
||||
- 「조건에 맞는 활동면이 없습니다 — 부피 · 높이 범위를 넓히세요.」 가 뜨면 고급 설정의 최소 · 최대 부피나 중심 높이 범위를 넓힐 것.
|
||||
- 우기는 「지표까지 포화」 한 가지로 셈 — E02 의 지하수 규칙(깊이 비율 · r_u · 수위선)은 3D 에 쓰지 않음. 지진 조건 · 옹벽 · 석축은 3D 에 넣지 않음.
|
||||
- 2D 대조 값은 E03 측점마다 기준 대비 가장 낮은 조건 · 비탈의 안전율이라 조건을 맞추지 않음. 같은 조건끼리 견준 표는 E05 보고서 「3D요약」 시트에 있음.
|
||||
- 보기만 허용된 프로젝트에서는 「보기만 허용된 프로젝트입니다.」 가 뜨고 [해석] 단추가 꺼짐.
|
||||
- 쓰인 방법 · 근거 · 한계는 「쓰인 방법과 근거」 문서, 아직 사용자 확정 전인 값은 「사용자 확정 전 값」 문서 참고.
|
||||
@@ -0,0 +1,51 @@
|
||||
# 3D 해석 칸 뜻
|
||||
|
||||
## 구간 카드
|
||||
|
||||
| 칸 | 뜻 | 기본값 · 범위 |
|
||||
| --- | --- | --- |
|
||||
| 구간 목록 | 측점을 앞에서부터 100 m 이상씩 묶은 자동 구간 · 경계는 측점 · 끝에 50 m 미만이 남으면 앞 구간에 붙임 | 자동 |
|
||||
| 시작 (m) | 해석 구간 시작 누가거리 | 첫 구간 시작 |
|
||||
| 끝 (m) | 해석 구간 끝 누가거리 | 시작보다 커야 함 · 구간 길이 500 m 이하 |
|
||||
|
||||
- 목록 항목 = `시작 – 끝 m` · 색 점(해석한 구간의 최소 안전율 색) · `Fs 0.00` · 입력이 바뀌었으면 「옛 결과」.
|
||||
- 시작 · 끝을 직접 넣어 해석한 구간도 목록 끝에 더해짐.
|
||||
|
||||
## 탐색 설정 카드
|
||||
|
||||
| 칸 | 뜻 | 기본값 · 범위 |
|
||||
| --- | --- | --- |
|
||||
| 조건 | 건기 = 지하수 없음 · 우기 (지표까지 포화) = 지하수위면을 지표에 둠 | 건기 |
|
||||
| 활동면 모양 | 구 = 공 모양 면만 · 구 + 타원체 = 공 모양으로 찾은 위험 자리에서 옆으로 늘린 타원체도 봄 | 구 + 타원체 |
|
||||
| 타원체 길이비 | 타원체를 활동 방향(비탈 아래쪽)에 직각인 옆 방향으로 몇 배 늘릴지 | 2.0 · 1.1~5.0 |
|
||||
| 탐색 폭 (중심선 ±m) | 활동면 중심을 찾는 범위 = 노선 중심선 좌우 이 거리 안 | 30 m · 5~100 m |
|
||||
|
||||
## 고급 설정 카드(접힘)
|
||||
|
||||
| 칸 | 뜻 | 기본값 · 범위 |
|
||||
| --- | --- | --- |
|
||||
| 기둥 크기 (m) | 땅을 나누는 정사각 기둥(셀) 한 변 · 작을수록 정밀하나 느림 | 1.0 m · 0.5~5 m |
|
||||
| 구 중심 간격 (m) | 가장 고운 탐색에서 구 중심끼리 수평 간격 | 2.0 m · 0.5~10 m |
|
||||
| 중심 높이 최소 (m) | 구 중심을 지표 위 몇 m 부터 둘지 | 2 m · 0~100 m |
|
||||
| 중심 높이 최대 (m) | 구 중심을 지표 위 몇 m 까지 둘지 | 30 m · 1~200 m |
|
||||
| 중심 높이 간격 (m) | 중심 높이를 몇 m 씩 올려 볼지 | 2 m · 0.5~20 m |
|
||||
| 반지름 증분 (m) | 구 반지름을 몇 m 씩 키워 볼지 | 1.0 m · 0.2~5 m |
|
||||
| 최대 깊이 (m) | 반지름 상한 = 중심 높이 + 이 값 · 활동면이 땅속 얼마나 깊이 갈 수 있는지 | 15 m · 1~60 m |
|
||||
| 최소 부피 (m³) | 이보다 작은 활동체는 셈에서 뺌(아주 얕은 표면 긁힘 제외) | 10 m³ · 0 이상 |
|
||||
| 최대 부피 (m³) | 이보다 큰 활동체는 셈에서 뺌 · 그 중심에서 더 큰 반지름도 그만 봄 | 10,000 m³ · 최소보다 커야 함 |
|
||||
|
||||
- **[기본값]** = 위 기본값으로 되돌림(시작 · 끝 그대로 · 조건 건기).
|
||||
- 최소가 최대보다 크면 「탐색 범위의 최소가 최대보다 큽니다.」 로 거절됨.
|
||||
- 칸 값은 이 브라우저 탭에 바로 기억됨(서버에는 [해석] 때 결과와 함께 남음).
|
||||
|
||||
## 화면에 안 나오는 고정값
|
||||
|
||||
| 값 | 뜻 | 지금 값 |
|
||||
| --- | --- | --- |
|
||||
| 거친 탐색 배수 | 처음엔 구 중심 간격의 4 배로 성기게 보고, 위험한 곳 둘레만 절반씩 좁혀 감 | 4 배 · 고운 단계 4 번까지 |
|
||||
| 셀 안 표본 | 셀마다 2 × 2 점에서 활동면 높이를 봐 가장자리 셀을 부분 기둥으로 셈 | 2 × 2 |
|
||||
| 고운 탐색 대상 | 안전율이 2.0 또는 지금까지 최소의 1.2 배보다 낮은 자리만 더 촘촘히 · 타원체도 그 자리만 | 2.0 · 1.2 배 |
|
||||
| Janbu 보정 전환 | 활동면 바닥이 못 뚫는 암반에 잘린 면적이 10 % 이상이면 3D Janbu 보정 값을 같이 봄 | 10 % |
|
||||
| 흙 두께(토사 측점) | 암 경계 정보가 없는 측점의 흙 두께 | 3 m |
|
||||
| 노면 하중 | E02 노면 하중(없으면 10 kPa)을 노면 띠 셀에 얹음 | E02 값 |
|
||||
| 기준 안전율 | E02 기준 안전율 중 깎기 · 쌓기 가운데 큰 값(엄한 쪽) | 건기 1.5 · 우기 1.3(기본) |
|
||||
@@ -0,0 +1,74 @@
|
||||
# 3D 해석 쓰인 방법과 근거
|
||||
|
||||
## 한눈에
|
||||
|
||||
| 단계 | 쓰인 방법 | 근거 |
|
||||
| --- | --- | --- |
|
||||
| 입력 지형 | 복사 DEM + 노선 계획면(B06 깎기 · 쌓기) 합성 | E01 복사본 · B06 횡단 설계 |
|
||||
| 지층 | 지층 경계 격자(B06 암 경계를 측점 사이 보간) | B06 암 경계 · E02 물성 |
|
||||
| 활동면 찾기 | Scoops3D 방식 구 격자 + 부피 제약 · 타원체 덧봄 | Reid 외(2015) USGS TM 14-A1 |
|
||||
| 안전율(기본) | Hungr 3D Bishop 간편법 | Hungr(1987) · Hungr 외(1989) |
|
||||
| 안전율(암반에 잘린 면) | 3D Janbu 간편법 + 보정 계수 | Hungr 외(1989) · Janbu(1973) |
|
||||
| 안전율(비교) | Hovland 3D 단순 기둥법 | Hovland(1977) |
|
||||
|
||||
- 지식DB 근거: `안정성평가/05_3D_사면안정해석_논문_기술정보` 1장(기둥법 수식) · 3장(Scoops3D) · 6.1절(구 격자 탐색) · 8장(3D/2D 비).
|
||||
- 계산 코드는 수식만 옮겨 직접 짬(외부 해석 프로그램 안 씀).
|
||||
|
||||
## 입력 지형 만들기
|
||||
|
||||
- 원지형 = 임도 프로젝트에서 복사해 온 DEM(지형 격자).
|
||||
- 측점마다 B06 설계선(깎기 · 쌓기 단면)과 원지형의 높이 차를 구하고, 이웃 두 측점 사이는 횡단선을 서로 이은 면 위에서 그 차를 고르게 이어 붙임.
|
||||
- 그래서 측점 위 단면은 B06 설계 단면과 같고, 노선 띠 밖은 원지형 그대로임.
|
||||
- 비탈이 횡단 끝에서 원지형과 안 만나면 끝 기울기로 원지형과 만날 때까지 늘림(최대 60 m) — 띠 끝에 가짜 절벽이 생기지 않게.
|
||||
- 해석 격자는 기둥 크기(기본 1 m) 정사각으로 다시 뜸. 구간 앞뒤 측점 하나씩을 더 붙여 구간 끝 지형이 끊기지 않게 함.
|
||||
|
||||
## 지층 경계
|
||||
|
||||
- 위에서 아래로 성토 · 풍화토 · 리핑암(풍화암 물성) · 발파암(연암 물성) 네 층.
|
||||
- 흙 두께 = 암 측점은 B06 암 경계 깊이 · 토사 측점은 3 m · 측점 사이는 고르게 보간, 노선 띠 밖은 가장 가까운 측점 값.
|
||||
- 토사 측점 아래는 리핑암으로 봄. **발파암(연암)은 활동면이 못 뚫는 층** — 활동면이 그 윗면에서 잘림.
|
||||
- 물성 값(γt · γsat · c · φ)은 E02 지반 조건 표의 성토 · 풍화토 · 풍화암 · 연암 줄을 그대로 씀.
|
||||
- 노면 띠(두 노면 끝 사이)에 E02 노면 하중(기본 10 kPa)을 얹음.
|
||||
|
||||
## 활동면 찾기 (Scoops3D 방식)
|
||||
|
||||
- 노선 중심선 좌우 탐색 폭(기본 ±30 m) 안, 구간 안에 구 중심 자리를 격자로 깖. 중심 높이 = 그 자리 지표 + 2~30 m(2 m 간격).
|
||||
- 중심마다 반지름을 1 m 씩 키우며 땅을 자르는 구를 만들고, 그 아래 흙 기둥들로 안전율을 셈.
|
||||
- 부피 제약: 활동체 부피가 10 m³ 미만이면 빼고 10,000 m³ 를 넘으면 그 중심에서 더 키우지 않음.
|
||||
- 거친 격자(중심 간격의 4 배)로 먼저 보고, 안전율이 낮게 나온 중심 둘레만 간격을 절반씩 좁혀 다시 봄.
|
||||
- 셀마다 지나간 활동면 중 가장 낮은 안전율과 그 부피를 남김 → 셀별 최소 안전율 지도 · 임계 체적 지도.
|
||||
- 구 + 타원체를 고르면, 위험하게 나온 중심에서 활동 방향에 직각으로 늘린 타원체(기본 2 배)를 더 봄.
|
||||
- 한 구가 땅을 여러 덩이로 자르면 덩이마다 따로 셈. 격자 끝이나 지형 빈칸에 닿은 덩이는 버림.
|
||||
- Scoops3D 와 다른 점: 중심 높이를 「그 자리 지표 위」 로 잡음(산지 고저차 대응) · 노선 띠 안에만 중심을 둠.
|
||||
|
||||
## 안전율 셈
|
||||
|
||||
- 안전율 = 활동면을 따라 버티는 힘(점착력 + 마찰) ÷ 미끄러뜨리는 힘. 1 보다 작으면 무너지는 쪽.
|
||||
- **Hungr 3D Bishop**: 기둥마다 위아래 힘이 맞도록 바닥 힘을 구하고, 회전축 둘레 힘의 모멘트가 맞도록 안전율을 되풀이 셈. 기본 방법.
|
||||
- **3D Janbu 보정**: 회전 대신 활동 방향 수평 힘이 맞도록 셈한 뒤 보정 계수 f0 = 1 + b1 × (d/L − 1.4 × (d/L)²) 를 곱함(b1 = 점착 · 마찰 둘 다 0.50 · 마찰만 0.69 · 점착만 0.31 · d/L = 활동면 양끝을 이은 직선에서 바닥까지 가장 먼 거리 ÷ 활동 방향 길이).
|
||||
- **Hovland**: 기둥 사이 힘을 모두 무시한 가장 단순한 셈. 판정에는 안 쓰고 비교로만 보여 줌.
|
||||
- 다스리는 값: 바닥이 못 뚫는 암반에 잘린 면적이 10 % 이상이면 Bishop 과 Janbu 보정 중 **작은 값**, 그 밖은 Bishop. Bishop 이 수렴하지 않은 후보는 버림. 너무 큰 안전율은 100 으로 자름.
|
||||
- 우기 = 지하수위면을 지표에 둔 간극수압(KDS 우기 규칙의 3D 꼴). 지진은 넣지 않음.
|
||||
- 판정 = 다스리는 값 ≥ 기준 안전율(E02 깎기 · 쌓기 중 큰 값 · 기본 건기 1.5 · 우기 1.3, KDS 11 70 05).
|
||||
|
||||
## 시험으로 맞춘 기준 문제
|
||||
|
||||
- Baligh & Azzouz 구 해석해 1.402 → 1.402.
|
||||
- Zhang(1988) 경우 1 = 2.158(문헌 Hungr 2.167) · 경우 2 약층 = 1.617(1.620).
|
||||
- Scoops3D 제방 30° = 1.325(1.32) · 원뿔 r_u 0.4 = 1.637(1.626).
|
||||
|
||||
## 2D 와 다른 점
|
||||
|
||||
- 2D(E03)는 측점 횡단마다 폭이 끝없이 긴 사면으로 보고 셈 — 양옆 흙이 잡아 주는 힘(끝단 효과)이 없음.
|
||||
- 3D 는 활동체 양옆 저항과 지형 굴곡(오목 · 볼록)을 넣음. 같은 땅이면 대개 3D 안전율이 2D 보다 크거나 같음(Cavounidis 1987).
|
||||
- 문헌 경향(지식DB 05 8장): 폭 ÷ 높이 1~1.5 = 3D/2D 약 1.25~1.50 · 2~3 = 약 1.10~1.25 · 6 이상 = 약 1.01~1.05.
|
||||
- 3D/2D 비가 1 보다 크면 2D 판정이 안전 쪽. 1 보다 작으면 측점 사이(20 m 간격 횡단이 놓친 곳)나 비스듬한 지형에 위험이 있을 수 있어 그 자리를 살펴볼 것.
|
||||
- 3D 는 무너질 흙 양(m³)을 알려 줌 — 2D 는 못 줌.
|
||||
|
||||
## 한계
|
||||
|
||||
- **3D 강도감소 FEM(유한요소) 안 함** — 활동면 모양을 미리 가정하지 않는 정밀 방법이지만 노선 전체에 쓰기엔 무거워 보류.
|
||||
- 활동면 모양은 구 · 타원체(암반에 잘린 면 포함)만. 비대칭 엄밀해(Cheng & Yip · 3D Spencer 등)는 안 씀.
|
||||
- 지층은 네 층 고정. E02 에서 더한 지층 · 구간별 덮어쓰기 · 지반 라벨 바꿈 · 붕적토 · 경암은 3D 에 안 들어감. 흙 두께는 B06 암 경계(없으면 3 m)를 씀.
|
||||
- 우기는 지표까지 포화 한 가지. 지진 · 옹벽 · 석축 · 배수 시설은 넣지 않음.
|
||||
- 물성은 문헌 추정치 · 현장 시험 없음. 시추 · 시험 성과가 있으면 E02 에서 바꿀 것.
|
||||
@@ -0,0 +1,30 @@
|
||||
# 3D 해석 사용자 확정 전 값
|
||||
|
||||
- 아래 값은 근거 문헌 · 기술정보를 보고 정한 지금 기본값임. 사용자 협의로 확정되기 전이며 바뀔 수 있음.
|
||||
- 「화면」 = 탐색 설정 · 고급 설정 칸에서 바꿀 수 있음 · 「고정」 = 화면에서 못 바꿈.
|
||||
|
||||
| 값 | 지금 기본값 | 출처 · 까닭 | 바꾸는 곳 | 상태 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 3D 기본 방법 | Hungr 3D Bishop · 암반에 잘린 면은 3D Janbu 보정과 작은 값 · Hovland 비교 | 지식DB 05 1장 · 요구서 345 4-2 | 고정 | 사용자 확정 전 |
|
||||
| Janbu 보정 전환 문턱 | 잘린 바닥 면적 10 % | 기둥 몇 개만 닿아 지도에 이음매가 생기는 것을 막는 값 · Zhang(1988) 약층 경우 잘림 비 33 % | 고정 | 사용자 확정 전 |
|
||||
| 활동면 모양 | 구 + 타원체 | Scoops3D · Hungr(1989) 타원체 꼴 | 화면 | 사용자 확정 전 |
|
||||
| 타원체 길이비 | 2.0 | 브레인 정함 | 화면 | 사용자 확정 전 |
|
||||
| 탐색 폭 | 중심선 ±30 m | 브레인 정함 · 지식DB 05 6.1절(노선 따라 탐색 상자 · 문헌 예 ±50 m) | 화면 | 사용자 확정 전 |
|
||||
| 기둥 크기 | 1.0 m | 브레인 정함 | 화면 | 사용자 확정 전 |
|
||||
| 구 중심 간격 · 높이 범위 · 간격 | 2 m · 지표 위 2~30 m · 2 m | 브레인 정함 · Scoops3D 탐색 격자 방식 | 화면 | 사용자 확정 전 |
|
||||
| 반지름 증분 · 최대 깊이 | 1 m · 15 m | 브레인 정함 · Scoops3D 방식 | 화면 | 사용자 확정 전 |
|
||||
| 부피 범위 | 10~10,000 m³ | 브레인 정함 · 지식DB 05 6.1절(문헌 예 50~50,000 m³) | 화면 | 사용자 확정 전 |
|
||||
| 토사 측점 흙 두께 | 3 m | 국내 산지 풍화토 · 붕적토 1~3 m 의 큰 쪽 | 고정 | 사용자 확정 전 |
|
||||
| 못 뚫는 층 | 발파암(연암 물성) | 토사 측점 아래 = 리핑암(풍화암 물성)으로 봄 | 고정 | 사용자 확정 전 |
|
||||
| 우기 지하수 | 지표까지 포화 | KDS 우기 규칙(토사)의 3D 꼴 | 고정 | 사용자 확정 전 |
|
||||
| 3D 기준 안전율 | E02 깎기 · 쌓기 중 큰 값(건기 1.5 · 우기 1.3) | KDS 11 70 05 · 한 구간에 깎기 · 쌓기가 섞여 엄한 쪽 | E02 기준 안전율 | 사용자 확정 전 |
|
||||
| 자동 구간 길이 | 100 m(경계 = 측점) · 꼬리 50 m 미만은 앞에 붙임 | 브레인 정함 | 고정(시작 · 끝 칸으로 직접 지정 가능) | 사용자 확정 전 |
|
||||
| 한 번에 해석할 구간 상한 | 500 m | 브레인 정함(계산 시간 · 메모리) | 고정 | 사용자 확정 전 |
|
||||
| 안전율 색 단계 | 1.0 · 1.2 · 1.3 · 1.5 · 2.0 · 3.0 | 1.3(우기) · 1.5(건기) 기준 경계 | 고정 | 사용자 확정 전 |
|
||||
|
||||
## 다른 화면에서 오는 값
|
||||
|
||||
| 값 | 지금 기본값 | 출처 | 상태 |
|
||||
| --- | --- | --- | --- |
|
||||
| 지반 물성치(성토 · 풍화토 · 풍화암 · 연암) | E02 표 값 | 문헌 추정치 · 현장 시험 없음(지식DB 04 · 07) | 사용자가 E02 에서 바꿈 |
|
||||
| 노면 하중 | 10 kPa | 옹벽 배면 상재하중 준용 | 사용자가 E02 에서 바꿈 |
|
||||
@@ -0,0 +1,55 @@
|
||||
# 보고서 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
- E02~E04 · E07 · E03 보강 검토 결과를 모아 비탈면 안정성 검토 보고서를 엑셀 꼴로 만듦.
|
||||
- 가운데 = 내장 엑셀(시트 탭 · 그림 · 쪽 나눔 점선)로 보고서를 바로 보고 고침.
|
||||
- 왼쪽 = 안내 글 · 상태 줄 · 내보내기 단추 · 저장 단추.
|
||||
- 내보내기 = 엑셀(xlsx) · PDF 파일.
|
||||
- 처음 들어오면 서버가 보고서를 한 번 만듦(2D 해석 결과가 있을 때만). 그 첫 판이 [초기화] 의 되돌림 기준이 됨.
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. E03 측점 해석에서 **[해석]** 을 먼저 끝냄(없으면 가운데에 「2D 해석 결과가 없습니다 — E03 에서 [해석] 뒤 만드세요.」).
|
||||
2. 필요하면 E04 3D 해석 · E07 광역 해석 · E03 보강 검토도 끝냄(하지 않은 부분은 시트에 「… 전」 으로 남음).
|
||||
3. E05 를 열면 보고서가 뜸. 아래 시트 탭(표지 · 개요 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · 보강검토)을 눌러 차례로 확인함.
|
||||
4. **표지** 의 발주처 · 작성자 칸, **개요** 의 「4. 의견」 칸에 글을 씀.
|
||||
5. 왼쪽 **[저장]** 을 누름(「저장했습니다.」).
|
||||
6. 해석을 다시 했으면 가운데 위 주의 색 띠 「해석 결과가 바뀜 —」 옆 **[보고서 갱신]** 을 누름.
|
||||
7. **[PDF 미리보기]** 로 인쇄 모양을 보고, **[엑셀]** 또는 **[PDF]** 로 내려받음.
|
||||
|
||||
## 단추
|
||||
|
||||
| 자리 | 단추 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| 왼쪽 위 줄 | **[엑셀]** | 지금 화면의 보고서를 `안정성평가_보고서.xlsx` 로 내려받음 · 저장은 안 함 |
|
||||
| 왼쪽 위 줄 | **[PDF]** | 지금 화면의 보고서를 `안정성평가_보고서.pdf` 로 내려받음 · 저장은 안 함 |
|
||||
| 왼쪽 위 줄 | **[PDF 미리보기]** | PDF 를 화면 안 창으로 띄움 · **[닫기]** 로 닫음 |
|
||||
| 왼쪽 아래 줄 | **[보고서 갱신]** | 프로그램 몫 시트 · 그림만 지금 해석 결과로 다시 씀 · 표지 · 개요 · 사용자가 더한 시트는 그대로 |
|
||||
| 왼쪽 아래 줄 | **[초기화]** | 처음 만든 보고서로 되돌림 · 확인 창 「처음 만든 보고서로 되돌립니다. 고친 내용 · 갱신이 모두 사라집니다.」 |
|
||||
| 왼쪽 아래 줄 | **[저장]** | 고친 보고서를 서버에 저장 · 고친 것이 있을 때만 켜짐 · 수식은 서버가 다시 풂 |
|
||||
| 가운데 위 띠 | **[보고서 갱신]** | 왼쪽 같은 이름 단추와 같음 · 해석 결과가 바뀐 때만 띠가 뜸 |
|
||||
|
||||
- 저장 안 한 고침이 있는데 [보고서 갱신] 을 누르면 「저장 안 한 고침이 있습니다. 저장한 뒤 갱신할까요?」 → **[저장]** 을 고르면 저장 뒤 갱신함.
|
||||
- 시트 탭 줄 끝 **+** = 빈 시트 추가 · 탭 오른쪽 누름 메뉴 = 이름 바꾸기 · 복사 · 숨기기 · 삭제.
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
- 상태 줄(왼쪽): 「저장 안 된 변경이 있습니다.」 · 「만드는 중…」 · 「보고서 만든 때 날짜 시각」.
|
||||
- 판정 칸 `O.K` = 초록 채움 · `N.G` = 빨강 채움.
|
||||
- 개요의 측점 수 · 최소 안전율 · 종합 판정 · 판정 글은 측점표 · 입력조건 시트를 가리키는 수식이라 갱신하면 같이 바뀜.
|
||||
- 시트마다 새 쪽에서 시작. 긴 표(측점표 등)는 자동으로 쪽이 나뉘고 머리 줄이 쪽마다 되풀이됨. 쪽 나눔은 화면에 점선으로 보임.
|
||||
- 시트별 내용은 「시트 구성」 문서 참고.
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- **문헌 추정치 · 현장 시험 없음** — 표지 · 입력조건 시트에 고정 글로 들어 있음. 지반 물성치는 문헌 · 기준값을 적용한 추정치이며 현장 조사 · 시험 성과를 반영하지 않았음. 입력조건 물성 표 「출처」 열 = 기본값 그대로면 「문헌 추정치(기본값)」 · 바꿨으면 「사용자 입력」.
|
||||
- **사용자 몫** = 표지 · 개요 — 처음 만들 때만 채우고 [보고서 갱신] 때 건드리지 않음. **프로그램 몫** = 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · 보강검토 — 갱신 때 통째로 다시 씀.
|
||||
- 프로그램 몫 시트를 손으로 고쳤다면 갱신 때 덮임. 이때 「고친 내용이 덮인 시트 — 옛 판은 _지난/ 에 보관: 시트 이름」 알림이 뜨고 옛 판은 서버에 보관됨. 오래 남길 글은 개요 의견 칸이나 새 시트에 쓸 것.
|
||||
- 프로그램 몫 시트를 지웠어도 갱신하면 끝에 다시 생김. 보강검토 시트는 E03 보강 검토 결과가 있을 때만 생김.
|
||||
- 고친 내용은 [저장] 전까지 이 브라우저 탭에만 남음(자동 저장 없음). 탭을 닫기 전 [저장] 할 것.
|
||||
- [엑셀] · [PDF] 는 화면에 지금 보이는 판(저장 안 한 고침 포함)으로 만듦. 보기만 허용된 프로젝트는 서버에 저장된 판으로 만듦.
|
||||
- 다른 곳에서 먼저 저장했으면 「다른 곳에서 보고서가 바뀌었습니다 — 다시 여세요.」 → 화면을 다시 열 것.
|
||||
- 보기만 허용된 프로젝트에서는 「보기만 허용된 프로젝트입니다.」 가 뜨고 [저장] · [초기화] · [보고서 갱신] 이 꺼짐(내보내기는 됨).
|
||||
- 왼쪽 안내 글은 입력조건 · 측점표 · 대표단면 · 3D요약만 적고 있으나, 광역요약 · 보강검토도 갱신 때 다시 씀.
|
||||
- **「사용자 확정 전」 표기** — E07 광역 화면에는 뿌리 점착 1 kPa · 측점 등급 문턱 5 % · 20 % 가 「사용자 확정 전」 으로 적혀 있으나, 보고서 광역요약 시트에는 그 줄이 아직 없음(준비 중). 값 자체는 광역요약 「토층 물성」 줄 · 「위험 측점」 제목에 들어감.
|
||||
@@ -0,0 +1,66 @@
|
||||
# 보고서 시트 구성
|
||||
|
||||
| 차례 | 시트 | 몫 | 용지 | 내용 출처 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | 표지 | 사용자 | A4 세로 | 처음 만들 때 사업명 · 노선명 · 작성일만 채움 |
|
||||
| 2 | 개요 | 사용자 | A4 세로 | 수식이 측점표 · 입력조건을 따라감 |
|
||||
| 3 | 입력조건 | 프로그램 | A4 세로 | E02 지반 조건 · 해석 방법 |
|
||||
| 4 | 측점표 | 프로그램 | A4 가로 | E03 측점 해석 |
|
||||
| 5 | 대표단면 | 프로그램 | A4 세로 | E03 대표 단면 · 그림 |
|
||||
| 6 | 3D요약 | 프로그램 | A4 세로 | E04 3D 해석 · 평면 위험 띠 그림 |
|
||||
| 7 | 광역요약 | 프로그램 | A4 세로 | E07 광역 해석 |
|
||||
| 8 | 보강검토 | 프로그램 | A4 세로 | E03 보강 검토(있을 때만) |
|
||||
|
||||
## 1. 표지
|
||||
|
||||
- 제목 「비탈면 안정성 검토 보고서」 · 부제 「임도 노선 비탈면 안정해석 결과」.
|
||||
- 사업명 · 노선명(지역 · 임도 종류) · 작성일 = 자동 · 발주처 · 작성자 = 사용자가 씀.
|
||||
- 「문헌 추정치 · 현장 시험 없음」 과 주의 글 「※ 지반 물성치는 문헌 · 기준값을 적용한 추정치이며 현장 조사 · 시험 성과를 반영하지 않았음.」 고정.
|
||||
|
||||
## 2. 개요
|
||||
|
||||
- 1. 해석 대상 — 대상 측점 수(개소) · 해석 구간 연장(m).
|
||||
- 2. 조건별 최소 안전율 — 건기 · 우기 · 지진마다 최소 안전율 · 기준 안전율 · 판정 · 최소 안전율 측점. 지진을 안 쓰면 「-」.
|
||||
- 3. 종합 판정 — 기준 미달(N.G) 측점 수 · 종합 판정 · 판정 글(예: 「대상 측점 57개소 중 3개소가 기준 안전율에 미달함. 미달 구간은 보강 대책 검토가 필요함.」).
|
||||
- 4. 의견 — 사용자 글 칸.
|
||||
|
||||
## 3. 입력조건
|
||||
|
||||
- 1. 해석 방법 — 해석 기법(Bishop 간편법 기본 · Janbu 보정 · Fellenius 비교) · 활동면 탐색(원호 + 비원호 · 좌우 비탈 따로) · 3D 해석(Hungr 3D Bishop(+Janbu 보정) · 구 · 타원체 탐색, 3D 를 안 했으면 「3D 해석 전」) · 적용 기준(KDS 11 70 05).
|
||||
- 2. 기준 안전율 — 건기 · 우기 · 지진 값 = 깎기 · 쌓기 중 엄한 쪽 · 비고에 둘 다 적음.
|
||||
- 3. 지하수 규칙 — 건기 · 우기(토사 · 풍화암 / 암반) · 지진 시.
|
||||
- 4. 노면 하중(kPa).
|
||||
- 5. 지반 물성치 — 지층 · 적용 구간(전체 · 구간 글) · γt · γsat · c · φ · 출처(「문헌 추정치(기본값)」 또는 「사용자 입력」). E02 구간별 덮어쓰기도 줄로 더해짐.
|
||||
|
||||
## 4. 측점표
|
||||
|
||||
- 측점 하나 = 한 줄 · 측점 · 누가거리 · 구분(깎기 · 쌓기 중 기준 대비 가장 낮은 쪽) · 건기 · 우기 · 지진 안전율 · 기준 안전율 · 판정.
|
||||
- 판정 = 모든 조건이 기준 이상이면 O.K · 하나라도 미달이면 N.G.
|
||||
|
||||
## 5. 대표단면
|
||||
|
||||
- 단면 하나 = 한 쪽 · 제목(대표 단면 ① — 측점 · 깎기/쌓기 · 높이) · 선정 사유 · 단면 그림(지반선 · 설계면 · 암선 · 노면 · 비탈 높이 · 조건별 임계 활동면).
|
||||
- 선정 사유 = 「쌓기 높이 10 m 이상」 · 「깎기 높이 15 m 이상」 · 「최소 안전율 하위 5」 · 「사용자 지정」(E03 대표 단면 켬).
|
||||
- 조건별 해석 방법 · 안전율 · 기준 · 판정 · 활동면 반경 R · 중심 x · y · 비고(원호 · 비원호 · 지반 · 토층 두께).
|
||||
- 대표 단면이 없으면 「대표 단면 — 해당 없음」.
|
||||
|
||||
## 6. 3D요약
|
||||
|
||||
- 1. 평면 위험 띠 그림 — 노선 · 측점 2D 판정 색 · 3D 구간 띠(O.K · N.G). 3D 를 안 했으면 「3D 해석 전」.
|
||||
- 2. 구간별 3D 안전율 — 구간 · 시작 측점 · 끝 측점 · 3D 최소 Fs · 2D 최소 Fs · 3D/2D 비(수식) · 임계 체적(m³) · 판정.
|
||||
- 2D 최소 Fs = 그 구간 측점들의 **같은 조건**(3D 를 셈한 건기 또는 우기) 최소값. 판정 · 기준은 E04 값을 그대로 씀.
|
||||
- 3D/2D 비가 1 보다 크면 2D 가 안전 쪽.
|
||||
|
||||
## 7. 광역요약
|
||||
|
||||
- 1. 해석 조건 — 노선 띠(중심선 ± m · 격자) · 토층 물성(γt · γsat · c · φ · 뿌리 점착 · 토층 두께) · 시나리오(표준 SINMAP 습윤지수 또는 최악 완전 포화) · 해석 때.
|
||||
- 2. 등급별 면적 — 안정 · 보통 안정 · 준안정 · 위험 하한 · 위험 상한 · 불안정의 SI 범위 · 면적(m²) · 비율(%) · 합계, 옆에 측점 등급(낮음 · 보통 · 높음)별 측점 수.
|
||||
- 3. 위험 측점 — 보통 · 높음 측점만 한 줄씩 · 최소 Fs · 최소 SI · 등급(SI<1 비율) · 위험 면적 · 최대 경사. 없으면 「위험 측점 없음」.
|
||||
- E07 해석 전이면 「광역 해석 전 — E07 광역에서 [해석] 뒤 [보고서 갱신]」.
|
||||
|
||||
## 8. 보강검토(가정)
|
||||
|
||||
- 머리 글 「E 안 가정 검토 — 비탈 완화 · 소단은 형상만 바꿔 다시 해석 · 설계(임도)에 안 돌려 씀 · 문헌 추정치」.
|
||||
- 1. 비탈 완화 · 소단 가정 — 측점 · 비탈 · 가정(1:n 전 → 후 · 소단 · 최소 완화) · 조건 · 전 Fs · 후 Fs · 기준 · 전 판정 · 후 판정.
|
||||
- 2. 벽 외적 안정 — 측점 · 벽 · 평상시/지진 시 · 토압법 · 배면 수위 · 활동 · 전도 · 지지력 Fs와 기준(KDS 11 80 05) · 판정.
|
||||
- E03 보강 검토 결과가 없으면 시트 자체가 빠짐.
|
||||
@@ -0,0 +1,72 @@
|
||||
# 암반 해석 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
암 측점(리핑암 · 발파암)의 깎기 비탈이 절리(암반 틈) 때문에 무너질 수 있는지 봄.
|
||||
현장에서 조사한 절리 방향을 넣으면 측점마다 두 가지를 셈.
|
||||
|
||||
- **Markland 판정** — 절리 방향만 보고 평면 · 쐐기 · 전도 가운데 어떤 무너짐이 생길 수 있는지 가림.
|
||||
- **Hoek-Bray 안전율** — 평면 · 쐐기로 판정된 것만 건기 · 우기(· 지진) 안전율을 셈. 전도는 판정만 함.
|
||||
|
||||
사면 방향 · 경사 · 높이는 복사본 횡단에서, 물성 · 지진 · 기준 안전율은 지반 조건(E02)에서 가져옴.
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. 좌측 **절리군** 카드에서 [+ 절리군] 을 눌러 줄을 늘림.
|
||||
2. 줄마다 이름 · 구간(시작 · 끝, m) · 경사방향 · 경사 · φj · cj · 지속성을 넣음. 칸 뜻은 `10_칸_뜻` 참고.
|
||||
3. 필요하면 **해석 설정** 카드(측방 한계 · 암 단위중량 · 우기 수위 비 · 인장균열 · 볼트)를 고침.
|
||||
4. [저장] 을 누름. 빨간 칸이 있으면 저장되지 않음.
|
||||
5. [해석] 을 누름. 저장 안 한 고침이 있으면 먼저 저장한 뒤 셈.
|
||||
6. 가운데 **위험 띠** 나 우측 **측점 결과** 표에서 측점을 눌러 스테레오넷 · 안전율 표를 봄.
|
||||
|
||||
## 단추
|
||||
|
||||
| 단추 | 자리 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| [+ 절리군] | 좌 · 절리군 카드 | 새 줄 추가(구간 = 0 m ~ 마지막 암 측점 · 경사방향 0° · 경사 45° · φj 30° · cj 0 kPa) |
|
||||
| [×] | 좌 · 절리군 줄 끝 | 그 줄 지움 |
|
||||
| [초기화] | 좌 아래 | 확인 창 뒤 절리군 · 설정을 처음 상태(절리 없음 · 기본 설정)로 되돌림 |
|
||||
| [저장] | 좌 아래 | 절리군 · 설정을 작업본에 씀 |
|
||||
| [해석] | 좌 아래 | 암 측점 × 깎기 쪽을 모두 셈 · 입력이 지난번과 같으면 지난 결과를 씀 |
|
||||
| [등각] [등면적] | 가운데 위 | 스테레오넷 투영 바꿈(보기만 · 저장 안 됨) |
|
||||
| [전체] [평면] [쐐기] [전도] | 가운데 위 | 스테레오넷에 그 모드 위험 영역만 보임 |
|
||||
|
||||
- 칸을 고치기만 하면 이 탭 안에만 남음(자동 저장 없음). 「저장 안 된 변경이 있습니다.」가 뜨면 [저장] 할 것.
|
||||
- 보기만 허용된 프로젝트는 칸 · 단추가 잠김.
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
**가운데 위 · 위험 띠** — 누가거리 축에 측점마다 가장 나쁜 판정 색. 띠 조각을 누르면 그 측점이 열림. 왼쪽에 판정별 측점 수.
|
||||
|
||||
**가운데 · 스테레오넷** — 하반구 투영. 그림 보는 법은 `20_쓰인_방법과_근거` 의 스테레오넷 절 참고.
|
||||
|
||||
- 굵은 선 · 네모 = 사면 대원 · 사면 극점
|
||||
- 점 + 이름 = 절리 극점(평면 · 전도 보기) · 세모 + 이름 = 두 절리 교선(쐐기 보기)
|
||||
- 테두리가 강조된 점 · 세모 = 이 측점에서 위험으로 판정된 것
|
||||
- 원 = 마찰원 φ · 색 영역 = 평면 · 쐐기 · 전도 위험 영역 · 선 = 일광 엔벨로프(평면)
|
||||
|
||||
**우측 · 측점 결과 표** — 측점 · 쪽(좌 · 우) · 평 · 쐐 · 전(위험 모드 수) · 최소 Fs · 판정.
|
||||
|
||||
| 판정 | 뜻 |
|
||||
| --- | --- |
|
||||
| N.G | 안전율이 기준보다 낮은 경우가 하나라도 있음 |
|
||||
| 검토 | 전도 위험이 있음, 또는 위험 판정은 났으나 블록이 안 생겨 안전율을 못 셈 |
|
||||
| O.K | 위험 모드는 있으나 모든 안전율이 기준 이상 |
|
||||
| 위험 없음 | Markland 판정에서 위험 모드 없음 |
|
||||
|
||||
**우측 아래 · 고른 측점** — 사면 경사방향 / 경사 · 높이 · 윗면 경사 · 암 단위중량 한 줄, 모드별 위험 절리, 안전율 표(모드 · 절리 · 조건 · Fs · 기준 · 판정).
|
||||
|
||||
- Fs 옆 「(블록 안 생김)」 = 그 조건에서 덩이가 만들어지지 않음.
|
||||
- Fs 옆 「(지속성 < 활동면)」 = 절리 길이가 활동면보다 짧음 · 실제로는 덩이가 덜 생길 수 있음.
|
||||
- 기준 = 지반 조건(E02)의 깎기 기준 안전율(기본 건기 1.5 · 우기 1.2 · 지진 1.1).
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- **문헌 추정치 · 현장 시험 없음.** 절리면 φj · cj · 암 단위중량 기본값은 문헌 값임. 현장 조사 · 시험 값으로 바꿀 것.
|
||||
- 절리 방향은 반드시 현장 조사값을 넣을 것. 절리가 없으면 해석할 것이 없음.
|
||||
- 주향으로 적은 야장은 경사방향 = 주향 + 90°(오른손 법칙)로 바꿔 넣을 것. 주향 입력 칸은 없음.
|
||||
- 대상 = 암 측점의 깎기 쪽 중 높이 1 m 이상만. 토사 측점 · 쌓기 쪽은 2D 해석(E03)에서 봄.
|
||||
- 지진 조건은 지반 조건(E02)에서 지진을 켠 때만 셈.
|
||||
- 절리 · 지반 조건이 해석 뒤 바뀌면 가운데 위에 「[해석] 을 다시 누르세요」 띠가 뜸.
|
||||
- 전도는 기하 판정만 함. 블록 전도 안전율은 별도 정밀 검토 몫.
|
||||
- 보고서(E05)에 암반 결과 시트는 **준비 중**.
|
||||
@@ -0,0 +1,51 @@
|
||||
# 칸 뜻
|
||||
|
||||
범위 밖 · 빈 칸(필수)은 칸이 빨갛게 되고 마우스를 올리면 「범위 밖 (아래 ~ 위)」가 보임. 빨간 칸이 있으면 [저장] · [해석] 이 안 됨.
|
||||
|
||||
## 절리군 표 (좌 · 절리군 카드)
|
||||
|
||||
한 줄 = 방향이 비슷한 절리 무리 하나. 현장 조사값을 넣음.
|
||||
|
||||
| 칸 | 뜻 | 단위 | 범위 | 새 줄 기본 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 이름 | 절리군 이름(그림 · 표에 쓰임) | - | 비우면 안 됨 | J1, J2 … |
|
||||
| 시작 · 끝 | 이 절리군을 쓰는 누가거리 구간 · 구간 안 측점에만 씀 | m | 0 ~ 100,000 · 끝 ≥ 시작 | 0 ~ 마지막 암 측점 |
|
||||
| 경사방향 | 절리면이 기울어 내려가는 방위(북 0° · 시계 방향) | ° | 0 ~ 360 | 0 |
|
||||
| 경사 | 절리면이 수평에서 기운 각 | ° | 0 ~ 90 | 45 |
|
||||
| φj | 절리면 마찰각 | ° | 0 ~ 60 | 30 |
|
||||
| cj | 절리면 점착력 | kPa | 0 ~ 1,000 | 0 |
|
||||
| 지속성 | 절리가 이어지는 길이 · 비우면 끝없이 이어진다고 봄 | m | 0 ~ 1,000 | 빈 칸 |
|
||||
|
||||
- 한 측점에 구간이 겹치는 절리군이 여럿이면 모두 함께 봄(쐐기 = 두 절리군 짝마다).
|
||||
- 새 줄 φj 30° · cj 0 kPa = 지식DB 07 절리면 범위(φj 25~35° · cj 0~30 kPa)의 보수 쪽.
|
||||
- 지속성은 안전율을 바꾸지 않음. 활동면보다 짧으면 결과 표에 「(지속성 < 활동면)」 표시만 붙음.
|
||||
|
||||
## 해석 설정 (좌 · 해석 설정 카드)
|
||||
|
||||
| 칸 | 뜻 | 범위 | 기본 |
|
||||
| --- | --- | --- | --- |
|
||||
| 평면 측방 한계 (°) | 사면과 절리 경사방향 차이가 이 각 안이면 평면 무너짐 후보 | 5 ~ 45 | 20 |
|
||||
| 전도 측방 한계 (°) | 절리가 사면 반대로 기운 방향 차이가 이 각 안이면 전도 후보 | 5 ~ 45 | 20 |
|
||||
| 암 단위중량 (kN/m³ · 비우면 E02) | 덩이 무게 계산 값 · 비우면 지반 조건(E02)의 그 측점 암 물성 γt · 그것도 없으면 25 | 15 ~ 35 | 빈 칸 |
|
||||
| 우기 수위 비 (0~1) | 우기 조건 물 차는 정도 · 0 = 마름 · 1 = 가득 참 | 0 ~ 1 | 0.5 |
|
||||
| 인장균열 | 사면 윗면에서 아래로 갈라진 틈을 볼지 · 없음 · 임계(최소 안전율 자리) · 깊이 지정 | - | 없음 |
|
||||
| 균열 깊이 (m) | 「깊이 지정」 때만 보임 · 윗면에서 잰 균열 깊이 | 0.1 ~ 100 | 1 |
|
||||
| 볼트 힘 (평면 kN/m · 쐐기 kN) | 록볼트 · 앵커가 잡아 주는 힘 · 평면은 폭 1 m 당 · 쐐기는 덩이 하나당 | 0 ~ 5,000 | 0 |
|
||||
| 볼트 경사 (수평 아래 °) | 볼트가 산 쪽으로 박히는 각(수평 아래가 +) | -30 ~ 60 | 15 |
|
||||
|
||||
- 측방 한계 기본 20° = 상용 Dips 기본 · 지식DB 03 은 전도에 30° 를 씀 — 화면에서 바꿀 수 있음.
|
||||
- 우기 수위 비 기본 0.5 = 비탈 높이 ½ 수위(KDS 11 70 05 암반).
|
||||
- 인장균열 「임계」 = 균열 자리를 마루 뒤로 옮겨 가며 안전율이 가장 낮은 자리를 찾음. 균열 없는 쪽이 더 낮으면 균열 없음으로 셈. 평면 무너짐에만 씀.
|
||||
- 볼트 = 수동 지지력(덩이가 움직이면 버팀). 0 이면 보강 없음.
|
||||
|
||||
## 결과 칸 (우측)
|
||||
|
||||
| 칸 | 뜻 |
|
||||
| --- | --- |
|
||||
| 쪽 | 노선 좌 · 우 깎기 비탈 |
|
||||
| 평 · 쐐 · 전 | 위험으로 판정된 평면 절리 수 · 쐐기 짝 수 · 전도 절리 수 |
|
||||
| 최소 Fs | 그 비탈 모든 경우 중 가장 낮은 안전율 |
|
||||
| 사면 줄 | 사면 경사방향 / 경사 · 높이 · 윗면(비탈 끝 바깥 원지반) 경사 · 쓴 암 단위중량 |
|
||||
| 쐐기 (추향/선주각) | 두 절리 교선이 향하는 방위 / 수평 아래 기운 각 |
|
||||
| 조건 | 건기(물 없음) · 우기(우기 수위 비) · 지진(E02 kh) |
|
||||
| 기준 | 지반 조건(E02) 깎기 기준 안전율 |
|
||||
@@ -0,0 +1,72 @@
|
||||
# 쓰인 방법과 근거
|
||||
|
||||
## 근거 문서
|
||||
|
||||
- 지식DB `안정성평가/03` 암반 비탈면 평사투영 및 블록 해석 수식 명세 — Markland 판정 · Hoek-Bray 평면 · 쐐기 안전율.
|
||||
- Hoek & Bray 「Rock Slope Engineering」 · Rocscience Dips · RocPlane · SWedge 이론서(식만 따름 · 프로그램은 쓰지 않음).
|
||||
- KDS 11 70 05 — 우기 수위(암반 = 비탈 높이 ½).
|
||||
- 지식DB `안정성평가/07` — 암 단위중량(25~27 kN/m³) · 절리면 강도 범위.
|
||||
|
||||
## 방향 적는 법
|
||||
|
||||
- 면 = 경사방향 / 경사. 예: 135/60 = 남동쪽으로 60° 기운 면.
|
||||
- 선 = 추향 / 선주각. 추향 = 선이 내려가는 방위 · 선주각 = 수평 아래로 기운 각.
|
||||
- 극점 = 면에 수직인 선. 면이 완만할수록 극점이 그림 가운데로 감.
|
||||
- 사면 경사방향 = 그 측점 노선 방향에 직각으로, 비탈이 노면 쪽으로 내려가는 방위(프로그램이 자동으로 셈).
|
||||
|
||||
## Markland 판정 (무너질 수 있는 모양인지)
|
||||
|
||||
절리 방향 · 사면 방향 · 마찰각만 봄. 힘 계산 없음.
|
||||
|
||||
| 모드 | 조건(모두 맞으면 위험) |
|
||||
| --- | --- |
|
||||
| 평면 | 절리 경사방향이 사면 경사방향 ± 평면 측방 한계 안 · φj < 절리 경사 < 그 방향에서 본 사면 경사 |
|
||||
| 쐐기 | 두 절리 교선이 사면 쪽(방위 차 90° 안)으로 향함 · φ < 교선 선주각 < 그 방향에서 본 사면 경사 · φ = 두 절리 중 작은 φj |
|
||||
| 전도 | 절리가 사면 반대쪽으로 기움(경사방향 차가 180° ± 전도 측방 한계 안) · 절리 경사 > 90° − 사면 경사 + φj |
|
||||
|
||||
쉬운 말 풀이
|
||||
- 평면 = 사면과 같은 쪽으로 기운 절리 하나를 따라 덩이가 미끄러짐. 절리가 사면보다 완만해야 비탈 면에 드러남(일광). 마찰각보다 가팔라야 미끄러짐.
|
||||
- 쐐기 = 두 절리가 만나 생긴 V 자 홈을 따라 덩이가 미끄러짐. 판정은 교선 하나로 봄.
|
||||
- 전도 = 사면 반대로 가파르게 선 절리 사이 기둥이 앞으로 넘어짐.
|
||||
- 「그 방향에서 본 사면 경사」(겉보기 경사) = 사면을 비스듬히 볼 때 덜 가팔라 보이는 경사. 지식DB 03 의 평면 조건은 사면 경사 그대로를 쓰나, 이 화면은 겉보기 경사를 씀(측방 한계 안에서 차이가 작음).
|
||||
|
||||
## Hoek-Bray 평면 안전율
|
||||
|
||||
안전율 = 버티는 힘 ÷ 미는 힘.
|
||||
- 버티는 힘 = cj × 활동면 길이 + (면을 누르는 힘) × tanφj + 볼트가 버티는 몫
|
||||
- 미는 힘 = 덩이 무게가 면을 따라 미는 몫 + 수압 · 지진 힘이 미는 몫
|
||||
- 덩이 = 단위 폭(1 m) 2D 단면 · 윗면은 비탈 끝 바깥 원지반 경사로 오름.
|
||||
- 물 · 인장균열 없음 = 활동면 가운데 수두가 (활동면 오름 높이 × 수위 비 ÷ 2)인 삼각형 양압력.
|
||||
- 물 · 인장균열 있음 = 균열 안 수심 = 균열 깊이 × 수위 비 · 균열 수평 수압 + 활동면 양압력.
|
||||
- 지진 = 바깥 쪽 수평 힘(무게 × kh).
|
||||
- 활동면이 윗면과 안 만나 덩이가 안 생기면 「(블록 안 생김)」.
|
||||
|
||||
## Hoek-Bray 쐐기 안전율
|
||||
|
||||
- 덩이 = 두 절리 · 사면 · 윗면 네 면이 만드는 삼각뿔. 부피 × 암 단위중량 = 무게.
|
||||
- 무게 · 수압 · 지진 · 볼트 힘을 3차원 벡터로 더한 뒤 미끄러지는 길을 고름 — 두 면을 함께 타고 미끄러짐 · 한 면만 타고 미끄러짐 · 들려 떨어짐.
|
||||
- 각 면 수직력으로 마찰 저항을 구해 안전율 = 버티는 힘 ÷ 미는 힘. 들린 면은 저항 없음.
|
||||
- 물 = 수위 비가 1 이면 지식DB 03 평균 수두(γw × Hw ÷ 6) · 수위 비 p 이면 그 p³ 배. Hw = 교선이 윗면에 닿는 높이.
|
||||
- 지진 = 교선 방향 수평 힘. 볼트 = 산 쪽(사면 반대 방위)으로 박힌 수동력.
|
||||
- 지식DB 03 의 닫힌 꼴 쐐기 식 대신 같은 이론의 벡터 풀이(SWedge 이론서 꼴)를 씀 — 한 면 미끄러짐 · 들림까지 가림.
|
||||
|
||||
## 스테레오넷 읽는 법
|
||||
|
||||
- 구를 아래 반쪽만 보고(하반구) 평면에 펼친 그림. 위 = 북 · 가장자리 = 수평 · 가운데 = 연직.
|
||||
- 등각 = 각도 모양이 바르게 보임 · 등면적 = 넓이 비율이 바르게 보임. 판정 결과는 같음.
|
||||
- **마찰원** = 마찰각 φ 원. 그림의 φ = 그 측점 절리 중 가장 작은 φj(제목 줄에 표시) · 표 판정은 절리마다 제 φj 를 씀 — φj 가 서로 다르면 그림과 표가 조금 다를 수 있음.
|
||||
- 극점 보기(평면 · 전도) — 극점이 마찰원 안이면 절리가 φ 보다 완만해 미끄러지지 않음.
|
||||
- 교선 보기(쐐기) — 교선이 마찰원 안이면 φ 보다 가팔라 미끄러질 수 있음.
|
||||
- **평면 위험 영역** = 마찰원 밖 · 일광 엔벨로프 안 · 사면 반대편 측방 한계 부채꼴. 극점이 이 안이면 평면 위험.
|
||||
- **쐐기 위험 영역** = 사면 대원 밖 · 교선 마찰원 안 · 사면 쪽 반원. 교선 세모가 이 안이면 쐐기 위험.
|
||||
- **전도 위험 영역** = 가장자리 쪽 · 사면 경사방향 측방 한계 부채꼴. 극점이 이 안이면 전도 위험.
|
||||
- 영역 경계는 Markland 조건과 같은 식으로 그림.
|
||||
|
||||
## 한계
|
||||
|
||||
- 문헌 추정치 · 현장 시험 없음. 결과는 넣은 절리 값만큼만 맞음.
|
||||
- 2D 평면 · 단순 삼각뿔 쐐기만 봄. 여러 덩이가 이어진 무너짐 · 단계 깎기 소단 · 지형 굴곡은 안 봄.
|
||||
- 사면 높이 1 m 미만 깎기는 뺌. 윗면 = 비탈 끝 바깥 max(높이, 5 m) 원지반 평균 오르막(사면 경사 − 1° 까지).
|
||||
- 지속성은 표시만 하고 안전율 · 판정에 안 넣음.
|
||||
- 전도는 기하 판정만 함(Goodman-Bray 블록 전도 안전율 없음).
|
||||
- 평면 우기 물: 지식DB 03 의 「균열 없음 · ½ 수위」 식(U = ¼ γw H A)은 이 화면 수위 비 1 과 같은 꼴임. 기본 0.5 는 그 반 수두 — 설계 목적에 맞게 수위 비를 정할 것.
|
||||
@@ -0,0 +1,76 @@
|
||||
# 광역 위험 설명서
|
||||
|
||||
## 이 화면에서 하는 일
|
||||
|
||||
노선 띠(중심선 좌우 일정 폭) 안 자연 비탈의 얕은 산사태(표층 붕괴) 위험을 지도로 그림.
|
||||
지형 격자 칸마다 **무한사면 안전율 Fs** 와 **SINMAP 안정 지수 SI** 를 셈 → SI 로 여섯 등급 색칠 → 측점마다 위험 정도(낮음 · 보통 · 높음)를 매김.
|
||||
|
||||
- 지형 = 복사한 원지형(노선 공사 전 땅). 노선 위 · 아래 자연 비탈을 봄.
|
||||
- 깎기 · 쌓기 비탈 자체는 2D 해석(E03) · 암반 해석(E06) 몫.
|
||||
|
||||
## 작업 순서
|
||||
|
||||
1. 좌측 **토층 물성** 카드에서 물성(E02 지반 조건)을 고름. 고르면 γt · γsat · c · φ 칸이 채워짐(기본 = 붕적토).
|
||||
2. 필요하면 뿌리 점착 · 토층 두께를 고침.
|
||||
3. **시나리오 · 포화** 카드에서 시나리오를 고름 — 표준(SINMAP 습윤지수) 또는 최악(완전 포화).
|
||||
4. **노선 띠 · 격자 · 측점 등급** 카드에서 띠 폭 · 격자 · 측점 문턱 둘을 확인함.
|
||||
5. [해석] 을 누름. 진행 막대가 끝나면 지도 · 결과가 나옴.
|
||||
6. 우측 **측점별 위험** 표에서 측점을 누르면 지도가 그 측점으로 옮겨 감.
|
||||
|
||||
## 단추
|
||||
|
||||
| 단추 | 자리 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| [기본값] | 좌 아래 | 모든 칸을 기본값으로 되돌림(해석은 안 함) |
|
||||
| [다시 해석] | 좌 아래 | 같은 조건이라도 처음부터 다시 셈 |
|
||||
| [해석] | 좌 아래 | 조건이 지난 결과와 같으면 「같은 조건의 결과가 있습니다.」 뒤 그 결과를 보임 · 다르면 새로 셈 |
|
||||
| [등급 (SI)] [안전율 Fs] | 가운데 위 | 지도 색을 SI 등급 · 안전율로 바꿈 |
|
||||
|
||||
- 칸 값은 이 탭 안에만 기억됨. 저장되는 것은 [해석] 한 결과(조건 포함) 하나뿐 — 새로 해석하면 지난 결과를 덮음.
|
||||
- 보기만 허용된 프로젝트는 [해석] 이 잠김.
|
||||
|
||||
## 결과 읽는 법
|
||||
|
||||
**가운데 · 지도** — 휠 = 확대 · 끌기 = 옮김 · 두 번 누름 = 전체 맞춤. 옅은 띠 = 노선 띠 · 굵은 선 = 중심선 · 점 = 측점 · 오른쪽 위 = 북 · 오른쪽 아래 = 축척.
|
||||
칸 위에 마우스를 올리면 등급 · SI · Fs · 경사 · 포화비 m · 가까운 측점이 보임.
|
||||
|
||||
**범례 (등급 SI)** — 밑에 해석한 시나리오 이름이 붙음.
|
||||
|
||||
| 색 | 등급 | SI |
|
||||
| --- | --- | --- |
|
||||
| 진초록 | 안정 | 1.5 이상 |
|
||||
| 연두 | 보통 안정 | 1.25 ~ 1.5 |
|
||||
| 연노랑 | 준안정 | 1.0 ~ 1.25 |
|
||||
| 노랑 | 위험 하한 | 0.5 ~ 1.0 |
|
||||
| 주황 | 위험 상한 | 0 초과 ~ 0.5 |
|
||||
| 빨강 | 불안정 | 0 |
|
||||
|
||||
- 위험 = 위험 하한 · 위험 상한 · 불안정(SI < 1).
|
||||
|
||||
**범례 (안전율 Fs)** — 빨강 1.0 미만 · 주황 1.0 ~ 1.2 · 노랑 1.2 ~ 1.5 · 연두 1.5 ~ 2.0 · 초록 2.0 이상. 가장 나쁜 쪽(c · φ 아래 · 가장 젖음) 값임.
|
||||
|
||||
**우측 · 광역 결과**
|
||||
|
||||
- 요약 — 시나리오 · 띠 안 최소 Fs · 위험 면적(SI < 1) · 측점 등급(낮음 · 보통 · 높음 수 / 전체 측점) · 지형 있는 띠 / 띠 면적 · 해석 걸린 시간과 때.
|
||||
- 등급별 면적 — 여섯 등급 면적(m²) · 비율(%).
|
||||
- 측점별 위험 — 측점 · 최소 Fs · SI<1 비율(%) · 측점 등급 · 위험 m². 높음 줄은 강조됨.
|
||||
|
||||
**측점 등급** — 측점 앞뒤 ½ 간격 × 띠 폭(영향 구역) 안 칸 중 SI < 1 칸 비율.
|
||||
|
||||
| 측점 등급 | 기본 문턱 |
|
||||
| --- | --- |
|
||||
| 낮음 | 5 % 미만 |
|
||||
| 보통 | 5 % 이상 20 % 미만 |
|
||||
| 높음 | 20 % 이상 |
|
||||
|
||||
문턱 둘은 좌측 「측점 보통 문턱」 · 「측점 높음 문턱」 칸에서 바꿈.
|
||||
|
||||
**보고서(E05) 광역요약 시트** — E05 [보고서 갱신] 때 이 화면 저장 결과로 채움: 해석 조건(띠 · 물성 · 시나리오 · 해석 때) · 등급별 면적 · 측점 등급 표 · 보통 · 높음 측점 줄(측점 · 누가거리 · 최소 Fs · 최소 SI · 등급과 비율 · 위험 면적 · 최대 경사). 결과가 없으면 「광역 해석 전」.
|
||||
|
||||
## 알아 둘 것
|
||||
|
||||
- **문헌 추정치 · 현장 시험 없음.** 물성 · T/R · 뿌리 점착 · 토층 두께 · 측점 문턱은 근거 후보 값임. 사용자 확정 전 값은 `30_사용자_확정_전_값` 참고.
|
||||
- 위험 지도는 넓은 범위에서 「어디를 먼저 볼지」 고르는 용도임. 측점 단면 안전율(E03)을 대신하지 않음.
|
||||
- 지형 격자 끝 밖 상류는 집수면적에 안 들어감 — 띠 가장자리는 실제보다 마르게(안전하게) 나올 수 있음.
|
||||
- 「엔진이 바뀌어 결과가 옛것입니다」가 뜨면 [다시 해석] 할 것.
|
||||
- 최악(완전 포화)은 모든 칸이 물에 찬 극한 강우 가정 — 대부분 측점이 높음으로 나옴. 비교용으로 쓸 것.
|
||||
@@ -0,0 +1,56 @@
|
||||
# 칸 뜻
|
||||
|
||||
범위 밖 값으로 [해석] 을 누르면 까닭이 알림으로 뜨고 해석하지 않음. 「위」 칸은 「아래」 칸보다 작으면 안 됨(c · φ · T/R) · 높음 문턱은 보통 문턱보다 작으면 안 됨.
|
||||
|
||||
## 토층 물성 (좌 · 첫 카드)
|
||||
|
||||
토층 = 기반암 위에 덮인 흙. 무한사면 식이 쓰는 값.
|
||||
|
||||
| 칸 | 뜻 | 범위 | 기본 |
|
||||
| --- | --- | --- | --- |
|
||||
| 물성(E02 지반 조건) | 지반 조건(E02) 물성 목록에서 고름 · 고르면 아래 여섯 칸을 채움(이름표일 뿐 · 값은 아래 칸) | - | 붕적토 |
|
||||
| γt (kN/m³) | 젖지 않은 흙 단위중량 | 0 초과 ~ 30 | 17.5 |
|
||||
| γsat (kN/m³) | 물에 찬 흙 단위중량 | 9.81 초과 ~ 30 | 18.5 |
|
||||
| c 아래 (kPa) | 점착력 범위 아래 끝 | 0 ~ 500 | 5 |
|
||||
| c 위 (kPa) | 점착력 범위 위 끝(물성을 고르면 아래 + 5) | 0 ~ 500 | 10 |
|
||||
| φ 아래 (°) | 내부 마찰각 범위 아래 끝 | 0 ~ 60 | 27 |
|
||||
| φ 위 (°) | 내부 마찰각 범위 위 끝(물성을 고르면 아래 + 3) | 0 ~ 60 | 30 |
|
||||
| 뿌리 점착 (kPa) | 나무뿌리가 흙을 붙잡는 힘 · c 에 더함 · 나지 · 절성토면 = 0 | 0 ~ 50 | 1 |
|
||||
| 토층 두께 (m) | 미끄러질 수 있는 흙 두께(지표에서 수직) | 0.1 ~ 10 | 1.5 |
|
||||
|
||||
- 범위(아래 ~ 위)는 SI 계산에 씀. Fs 는 아래 끝 값(보수 쪽)으로 셈.
|
||||
|
||||
## 시나리오 · 포화 (좌 · 둘째 카드)
|
||||
|
||||
| 칸 | 뜻 | 범위 | 기본 |
|
||||
| --- | --- | --- | --- |
|
||||
| 시나리오 | 표준 — SINMAP 습윤지수 (T/R 범위) · 최악 — 완전 포화 (m = 1 · 강우 극한) | - | 표준 |
|
||||
| T/R 아래 (m · 젖은 쪽) | 투수량계수 T ÷ 유효 강우 R · 작을수록 물이 잘 고임 | 1 ~ 100,000 | 2,000 |
|
||||
| T/R 위 (m · 마른 쪽) | 같은 값의 범위 위 끝 | 1 ~ 100,000 | 3,000 |
|
||||
|
||||
- T/R 두 칸은 표준일 때만 보임.
|
||||
- 포화비 m = 토층 두께 중 물에 찬 비율(0 = 마름 · 1 = 지표까지 참).
|
||||
|
||||
## 노선 띠 · 격자 · 측점 등급 (좌 · 셋째 카드)
|
||||
|
||||
| 칸 | 뜻 | 범위 | 기본 |
|
||||
| --- | --- | --- | --- |
|
||||
| 띠 폭 ± (m) | 중심선에서 좌우로 볼 거리 · 지도 옅은 띠가 바로 바뀜 | 10 ~ 500 | 100 |
|
||||
| 격자 (m) | 계산 칸 한 변 · 작을수록 촘촘하고 오래 걸림 | 1 ~ 10 | 2 |
|
||||
| 측점 보통 문턱 (SI<1 비율 %) | 영향 구역 안 SI < 1 칸 비율이 이 값 이상이면 보통 | 0 초과 ~ 100 | 5 |
|
||||
| 측점 높음 문턱 (SI<1 비율 %) | 이 값 이상이면 높음 | 0 초과 ~ 100 | 20 |
|
||||
|
||||
## 결과 칸 (우측)
|
||||
|
||||
| 칸 | 뜻 |
|
||||
| --- | --- |
|
||||
| 띠 안 최소 Fs | 띠 안 모든 칸 중 가장 낮은 안전율 |
|
||||
| 위험 면적 (SI < 1) | 위험 하한 · 위험 상한 · 불안정 등급 면적 합 |
|
||||
| 지형 있는 띠 / 띠 | 지형 자료가 있는 띠 면적 / 띠 전체 면적 · 차이 = 지형 밖이라 못 본 곳 |
|
||||
| 최소 Fs (측점 표) | 그 측점 영향 구역 안 가장 낮은 안전율 |
|
||||
| SI<1 비율 (%) | 영향 구역 칸 중 SI < 1 칸 비율 |
|
||||
| 위험 m² | 영향 구역 안 SI < 1 면적 |
|
||||
| 툴팁 m | 그 칸 포화비(가장 젖은 쪽) · 최악 시나리오는 1 |
|
||||
|
||||
- 영향 구역 = 띠 안 칸을 가장 가까운 중심선 누가거리로 나눈 뒤, 이웃 측점과 가운데에서 끊은 몫(측점 앞뒤 ½ 간격 × 띠 폭).
|
||||
- 영향 구역에 지형이 없는 측점은 값이 「-」.
|
||||
@@ -0,0 +1,69 @@
|
||||
# 쓰인 방법과 근거
|
||||
|
||||
## 근거 문서
|
||||
|
||||
- 지식DB `예정노선/02` §1.1 무한사면 안전율 식(포화비 · 뿌리 점착 포함) · §2 ① 지형습윤지수 + 정상류 무한사면(SINMAP · SHALSTAB).
|
||||
- Pack 외(1998) SINMAP 안정 지수 방법 · SINMAP 2 설명서 기본 보정(T/R 2,000 ~ 3,000 m) — 식만 따름 · 원 프로그램은 쓰지 않음.
|
||||
- 지식DB `안정성평가/04` · `07` — 붕적토 물성 범위(지반 조건 E02 기본값의 출처).
|
||||
- 국내 적용 연구 — 뿌리 점착(산림 0 ~ 5 kPa · 우면산 1 kPa).
|
||||
|
||||
## 계산 차례
|
||||
|
||||
1. 원지형 격자를 「격자」 간격으로 다시 고름.
|
||||
2. 칸마다 경사 β — 이웃 3 × 3 칸으로 구함(Horn 방법).
|
||||
3. 칸마다 단위 집수면적 a — 웅덩이를 채운 뒤 물이 가장 가파른 이웃으로 흐른다고 보고(D8) 위쪽에서 모여드는 칸 수 × 칸 크기. 배수유역(B04)과 같은 흐름 계산.
|
||||
4. 칸마다 포화비 m(시나리오별) → 안전율 Fs · 안정 지수 SI → 등급.
|
||||
5. 노선 띠 안 칸만 남겨 등급별 면적 · 측점 표를 만듦.
|
||||
|
||||
## 포화비 m (시나리오)
|
||||
|
||||
- **표준 — SINMAP 습윤지수**: m = (R ÷ T) × a ÷ sinβ, 단 1 을 넘으면 1.
|
||||
- 위쪽 모이는 넓이 a 가 크고 경사가 완만할수록 잘 젖음(골짜기 · 비탈 아래).
|
||||
- T/R 범위 안에서 바뀜 · Fs 지도는 가장 젖은 쪽(T/R 아래)으로 셈.
|
||||
- **최악 — 완전 포화**: 모든 칸 m = 1(지표까지 물에 참). 극한 강우 비교용.
|
||||
|
||||
## 무한사면 안전율 Fs
|
||||
|
||||
비탈과 나란한 얇은 흙층이 통째로 미끄러진다고 보는 식.
|
||||
|
||||
안전율 = 버티는 힘 ÷ 미는 힘
|
||||
- 버티는 힘 = (c + 뿌리 점착) + [γt × z × (1 − m) + (γsat − γw) × m × z] × cos²β × tanφ
|
||||
- 미는 힘 = [γt × z × (1 − m) + γsat × m × z] × sinβ × cosβ
|
||||
- z = 토층 두께 · γw = 물 단위중량 9.81 kN/m³.
|
||||
- 물이 차면(m 이 커지면) 흙을 누르는 힘이 줄어 마찰이 약해짐 → Fs 가 낮아짐.
|
||||
- 거의 평평한 칸은 미끄러지지 않는다고 봄(Fs · SI 무한대 = 안정).
|
||||
|
||||
## 안정 지수 SI
|
||||
|
||||
c · φ · T/R 을 정확히 모르니 범위(아래 ~ 위) 안에서 고르게 퍼져 있다고 보고 셈.
|
||||
|
||||
- 가장 나쁜 조합(c · φ 아래 · 가장 젖음)에서도 Fs ≥ 1 → SI = 그 Fs(1 이상 · 클수록 안정).
|
||||
- 가장 좋은 조합(c · φ 위 · 가장 마름)에서도 Fs < 1 → SI = 0(불안정).
|
||||
- 그 사이 → SI = Fs > 1 일 확률(0 ~ 1). 예: SI 0.7 = 범위 안 조합의 70 % 가 버팀.
|
||||
- 최악 시나리오는 m = 1 고정이라 c · φ 범위만 봄.
|
||||
|
||||
| 등급 | SI | 쉬운 말 |
|
||||
| --- | --- | --- |
|
||||
| 안정 | 1.5 이상 | 나쁜 조합에서도 넉넉히 버팀 |
|
||||
| 보통 안정 | 1.25 ~ 1.5 | 나쁜 조합에서도 버팀 |
|
||||
| 준안정 | 1.0 ~ 1.25 | 나쁜 조합에서 겨우 버팀 |
|
||||
| 위험 하한 | 0.5 ~ 1.0 | 조합 절반 이상은 버팀 |
|
||||
| 위험 상한 | 0 초과 ~ 0.5 | 조합 절반 넘게 무너짐 |
|
||||
| 불안정 | 0 | 좋은 조합에서도 무너짐 |
|
||||
|
||||
등급 경계 = SINMAP 원 방법 그대로.
|
||||
|
||||
## 측점 등급
|
||||
|
||||
- 영향 구역 = 측점 앞뒤 ½ 간격 × 띠 폭(띠 칸을 가장 가까운 중심선 누가거리로 나눔).
|
||||
- 영향 구역 칸 중 SI < 1(위험 하한 이상) 칸 비율로 낮음 · 보통 · 높음(문턱 기본 5 % · 20 %).
|
||||
- 한 칸이라도 위험이면 위험으로 잡던 방식은 거의 모든 측점이 위험으로 나와 변별력이 없어 비율 방식을 씀.
|
||||
|
||||
## 한계
|
||||
|
||||
- 문헌 추정치 · 현장 시험 없음. 토층 두께 · 물성 · T/R 이 띠 전체에 한 값임(칸마다 다르지 않음).
|
||||
- 얕은 평행 미끄러짐만 봄. 깊은 원호 활동 · 암반 무너짐 · 토석류 흘러감은 안 봄.
|
||||
- 지형 = 원지형. 깎기 · 쌓기로 바뀐 땅은 반영 안 됨.
|
||||
- 지형 격자 끝 밖 상류는 모름 — 띠 가장자리 칸은 집수면적이 작게 나와 실제보다 마르게 셈.
|
||||
- 강우량(mm) 직접 입력 없음 — 젖는 정도는 T/R 범위로만 넣음.
|
||||
- 산림청 위험지도 등급 비율 공개 자료가 없어 그와 대조하지 못함. 시험 프로젝트 위험(SI < 1) 비율 11.1 % 가 국내 SINMAP 적용 사례(약 7.5 ~ 16 %) 범위 안임만 확인함.
|
||||
@@ -0,0 +1,20 @@
|
||||
# 사용자 확정 전 값
|
||||
|
||||
아래 값은 문헌 · 연구에서 고른 근거 후보임. 화면에서 바꿀 수 있고, 기본값으로 굳힐지는 사용자 협의로 정함. 바꾸면 그 값으로 [해석] 하면 됨 — 기본값 자체는 바뀌지 않음.
|
||||
|
||||
| 값 | 지금 기본값 | 출처 | 상태 |
|
||||
| --- | --- | --- | --- |
|
||||
| 뿌리 점착 | 1 kPa | 국내 적용 연구 — 산림 0 ~ 5 kPa · 우면산 1 kPa · 나지 · 절성토면 0 | 사용자 확정 전 |
|
||||
| 측점 보통 문턱 | SI<1 비율 5 % | 개발 쪽에서 정함 · 시험 프로젝트 측점 SI<1 비율 분포(중앙 약 10 %)를 보고 고름 | 사용자 확정 전 |
|
||||
| 측점 높음 문턱 | SI<1 비율 20 % | 위와 같음 | 사용자 확정 전 |
|
||||
| 토층 물성 | E02 붕적토 — γt 17.5 · γsat 18.5 kN/m³ · c 5 kPa · φ 27° | 지식DB `안정성평가/07` 붕적토(γt 17 ~ 18 · c 0 ~ 10 · φ 25 ~ 28) · E02 기본값 | 문헌 추정치 · 사용자 확정 전 |
|
||||
| c · φ 범위 폭 | c + 5 kPa · φ + 3° (c 5 ~ 10 · φ 27 ~ 30) | 지식DB `안정성평가/04` 붕적토 범위 폭의 반 | 사용자 확정 전 |
|
||||
| T/R 범위 | 2,000 ~ 3,000 m | SINMAP 2 설명서 기본 보정 | 문헌 기본 · 현장 보정 없음 |
|
||||
| 토층 두께 | 1.5 m | 지식DB `예정노선/02` §1 표층 붕괴 토층 1 ~ 3 m | 사용자 확정 전 |
|
||||
| 노선 띠 폭 | 중심선 ± 100 m | 요구 분석(노선 좌우 100 m 예) | 사용자 확정 전 |
|
||||
| 격자 | 2 m | 프로그램 기본값 · 근거 문서 없음 | 사용자 확정 전 |
|
||||
| 기본 시나리오 | 표준(SINMAP 습윤지수) | SINMAP 표준 방법 · 최악(완전 포화)은 선택지 | 정함 |
|
||||
|
||||
- 물성 기본은 그 프로젝트 지반 조건(E02)의 붕적토 값을 따름 — E02 에서 붕적토를 고치면 이 화면 기본값도 바뀜.
|
||||
- 화면 좌측 카드 아래 안내 글(기본값 = 근거 후보 …)에 같은 출처가 한 줄로 적혀 있음.
|
||||
- 등급 경계(SI 1.5 · 1.25 · 1.0 · 0.5 · 0)는 SINMAP 원 방법 값이라 이 표에 넣지 않음.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,11 @@
|
||||
|
||||
## 공통
|
||||
|
||||
- 몫: 사용자 몫(표지 · 개요) = 처음 만들 때만 채움 · 다시 만들 때(보고서 갱신) 안 건드림. 프로그램 몫(입력조건 · 측점표 · 대표단면 · 3D요약) = 갱신 때 통째 다시 씀.
|
||||
- 서식 번호(`서식` 표): 1 title · 2 sub · 3 section · 4 label · 5 text · 6 head · 7 body(가운데 글) · 8 fs(0.00) · 9 int(0) · 10 ng(N.G 빨강 채움) · 11 ok(O.K 초록) · 12 notice · 13 num1(#,##0.0) · 14 num0(#,##0) · 15 para(위 맞춤 긴 글) · 16 prop(0.0) · 17 chain(#,##0.00).
|
||||
- 몫: 사용자 몫(표지 · 개요) = 처음 만들 때만 채움 · 다시 만들 때(보고서 갱신) 안 건드림. 프로그램 몫(목차 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · 보강검토 · 부록_절편) = 갱신 때 통째 다시 씀 · 작업본에 없는 프로그램 몫 시트는 양식 차례 자리에 끼움 · 새 서식은 작업본 서식 표 끝에 붙여 번호를 바꿈.
|
||||
- 시트 차례: 표지 · 목차 · 개요 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · (보강검토) · 부록_절편.
|
||||
- PDF: 표지 = 쪽 번호 · 머리말 없음 · 본문 = 머리말(왼 사업명 = 표지 `C8` · 오른 보고서 이름) · 꼬리말 「- n / N -」(목차 = 1 쪽).
|
||||
- 사용자 확정 전 표기 한 규칙 — 기본값 그대로 = 「(기본값 · 사용자 확정 전)」 · 바꿈 = 「(사용자 입력)」 · 물성 = 「문헌 추정치 · 사용자 확정 전」.
|
||||
- 서식 번호(`서식` 표): 1 title · 2 sub · 3 section · 4 label · 5 text · 6 head · 7 body(가운데 글) · 8 fs(0.00) · 9 int(0) · 10 ng(N.G 빨강 채움) · 11 ok(O.K 초록) · 12 notice · 13 num1(#,##0.0) · 14 num0(#,##0) · 15 para(위 맞춤 긴 글) · 16 prop(0.0) · 17 chain(#,##0.00) · 18 head8 · 19 num8(0.00) · 20 num8(0.0) · 21 int8 · 22 목차 내용 · 23 목차 쪽 · 24 목차 장 · 25 sum8(0.00 · 회색) · 26 표지 제목.
|
||||
- 판정 글(`O.K` · `N.G`) 칸 = 엔진 판정을 값으로 씀 · N.G 칸은 서식 10 · O.K 칸은 서식 11 을 직접 붙임 — 인쇄 HTML · xlsx 는 `조건부서식` 을 안 먹이므로(화면만). 양식의 `조건부서식`(글포함 `N.G` → 빨강)은 사용자가 화면에서 고친 판정 글용.
|
||||
- 수식이 보는 끝 행 = 20000(측점표 · 3D요약). 측점이 이보다 많으면 개요 수식의 `20000` 을 같이 늘림.
|
||||
- 빈 값은 칸을 안 적거나 `값` 을 안 둠(빈 글 `""` 금지).
|
||||
@@ -19,6 +22,16 @@
|
||||
| C8:E8 · C9:E9 · C10:E10 · C11:E11 · C12:E12 | 사업명 · 노선명 · 발주처 · 작성일 · 작성자 값(병합 · 서식 5) — 왼위 칸 `C8` … `C12` 에 값 |
|
||||
| B14:E14 | 「문헌 추정치 · 현장 시험 없음」(고정 글 · 건드리지 않음) |
|
||||
|
||||
## s9 목차 (프로그램 몫)
|
||||
|
||||
| 칸 | 내용 |
|
||||
| --- | --- |
|
||||
| `A1:C1` | 제목(고정 글) |
|
||||
| 3 행 | 머리 — 장 · 내용 · 쪽 |
|
||||
| 4 행부터 보이는 본문 시트 하나 = 한 줄 | A 장(`1.` … · 부록 = `부록`)(24) · B 내용(22) · C 시작 쪽(23 · 표지 뺀 본문 차례 · 목차 = 1) |
|
||||
|
||||
- 쪽 번호 = 시트 하나씩 인쇄 → PDF 쪽 수로 셈(보고서 만들 때 · PDF 내보내기 때 다시) · 브라우저가 없으면 「-」.
|
||||
|
||||
## s2 개요 (사용자 몫 · 수식은 양식에 들어 있음)
|
||||
|
||||
- 빌더가 쓸 것: 없음(수식이 `측점표` · `입력조건` 을 가리킴). 의견 칸 `A22:F30`(병합 · 서식 15) = 사용자 글.
|
||||
@@ -34,11 +47,12 @@
|
||||
| | `C12` `C13` `C14` (C:G 병합) | 비고 글 |
|
||||
| 지하수 규칙 | `B17` `B18` `B19` (B:G 병합) | 건기 · 우기 · 지진 시 규칙 글 |
|
||||
| 노면 하중 | `B22` | 수(kPa · 서식 16) |
|
||||
| | `D22` (D:G 병합) | 비고 — 사용자 확정 전 표기 |
|
||||
| 지반 물성치 | **26 행부터 한 줄씩** · 머리 25 행(머리 줄 반복) | A 지층 · B 적용 구간(전체 또는 구간 글) · C γt · D γsat · E c · F φ(서식 16) · G 출처(서식 5) · A·B = 서식 7 |
|
||||
|
||||
- 지반 물성치가 표의 맨 끝이라 줄 수가 늘어도 다른 칸이 안 밀림. 구간별 덮어쓰기 줄 = 같은 표에 줄 추가(B 열에 구간).
|
||||
|
||||
## s4 측점표 (프로그램 몫 · 머리 줄 4~5 행 쪽마다 반복 · 가로 · 폭 1쪽)
|
||||
## s4 측점표 (프로그램 몫 · 판정 일람표 · 머리 줄 4~5 행 쪽마다 반복 · 가로 · 폭 1쪽)
|
||||
|
||||
- 자료는 **6 행부터 측점 하나 = 한 줄** · 서식은 6 행 서식을 줄마다 그대로.
|
||||
|
||||
@@ -50,9 +64,10 @@
|
||||
| D · E · F | 건기 · 우기 · 지진 안전율 Fs(지진 끔 = 칸 없음) | 8 |
|
||||
| G · H · I | 건기 · 우기 · 지진 기준 안전율 | 8 |
|
||||
| J | 판정 `O.K` · `N.G`(엔진 판정 값) | N.G = 10 · O.K = 11 |
|
||||
| K | 대책 필요(`대책 검토` · O.K = `-`) | 7 |
|
||||
|
||||
- 개요 · 3D 수식이 `A6:A20000` · `B` · `D` · `E` · `F` · `J` 열을 읽음 → 이 열 배치 바꾸지 않음.
|
||||
- 줄 수 = 측점 수(시험: 측점 수 = 줄 수). 쪽 나눔은 자동(`인쇄.쪽나눔` 안 씀).
|
||||
- 줄 수 = 측점 수(시험: 측점 수 = 줄 수). 쪽 나눔 = 빌더가 쪽마다 고르게(`인쇄.쪽나눔` · 마지막 쪽에 한두 줄만 남지 않게).
|
||||
|
||||
## s5 대표단면 (프로그램 몫 · 단면 하나 = 28 행 덩이 = 한 쪽)
|
||||
|
||||
@@ -62,14 +77,14 @@
|
||||
| --- | --- | --- |
|
||||
| 1 | `A1:H1` 병합 | 단면 제목(`대표 단면 ① — 측점 · 구분 · 높이`) |
|
||||
| 2 | `B2:H2` 병합 | 선정 사유 글 |
|
||||
| 3~22 | 그림 `A3:H22` | 그림 한 장 — 그림 칸 크기 약 712 × 533 px(가로 : 세로 = 4 : 3) · 열쇠 `fig/<측점 키>.png` · 닻은 `dr` 만큼 내림 |
|
||||
| 3~22 | 그림 `A3:H22` | 그림 한 장 — 행 24 pt · 그림 칸 712 × 640 px · 안전율 상자 · 지층 범례(무늬) · 축척 막대 · 열쇠 `fig/<측점 키>.png` · 닻은 `dr` 만큼 내림 |
|
||||
| 23 | 머리(고정 글) | 조건 · 해석 방법 · 안전율 · 기준 · 판정 · 활동면 반경 R · 중심 x · 중심 y |
|
||||
| 24 · 25 · 26 | A = 건기 · 우기 · 지진(고정 글) | B 해석 방법 글(서식 7) · C Fs(8) · D 기준(8) · E 판정(7→ N.G 10 · O.K 11) · F 반경 · G 중심 x · H 중심 y(13) |
|
||||
| 27 | `B27:H27` 병합 | 비고 글 |
|
||||
| 28 | 빈 줄 | 쪽 끝 |
|
||||
|
||||
- `인쇄.쪽나눔` = `[28, 56, …, 28 × 단면 수]`(마지막 덩이 끝 쪽 나눔은 있어도 빈 쪽이 안 생김).
|
||||
- 지진을 안 쓰면 26 행(덩이 안) 칸을 비움.
|
||||
- 지진을 안 쓰면 26 행(덩이 안) B = 「검토 안 함」. 비원호 면 = F · G · H 「-」. 비고 끝 = 「부록 ⓝ 절편 계산표」.
|
||||
|
||||
## s6 3D요약 (프로그램 몫 · 머리 줄 19 행 반복)
|
||||
|
||||
@@ -79,3 +94,39 @@
|
||||
| 20 행부터 구간 하나 = 한 줄 | A 구간 · B 시작 측점 · C 끝 측점(서식 7) · D 3D 최소 Fs · E 2D 최소 Fs(서식 8) · **F 3D/2D 비 = 수식 `IF(E{r}>0,D{r}/E{r},"")`**(서식 8) · G 임계 체적 m³(서식 14) · H 판정(서식 7 → N.G 10 · O.K 11) |
|
||||
|
||||
- 20 행 서식을 줄마다 복사.
|
||||
|
||||
## s7 광역요약 (프로그램 몫 · 머리 줄 21 행 반복 · E07 광역 저장본 · 없으면 `B5` 에 「광역 해석 전」)
|
||||
|
||||
| 칸 | 내용 |
|
||||
| --- | --- |
|
||||
| `B5` `B6` `B7` `B8` (각 B:H 병합 · 서식 5) | 노선 띠 · 토층 물성 · 시나리오(표준 SINMAP 습윤지수 · 최악 완전 포화) · 해석 때 글 |
|
||||
| `B9` (B:H 병합) | 기본값 그대로인 설정 · 물성 — 사용자 확정 전 한 줄 |
|
||||
| `C12`~`C17` | 등급 1~6(안정 · 보통 안정 · 준안정 · 위험 하한 · 위험 상한 · 불안정) 면적 m²(서식 14) · `D` 비율 = 양식 수식 · `C18` 합계 = 양식 수식 |
|
||||
| `E11`~`G14` | 측점 등급 표 — 낮음 · 보통 · 높음별 SI<1 셀 비율 기준 글(7) · 측점 수(14) |
|
||||
| 22 행부터 보통 · 높음 측점 하나 = 한 줄 | A 측점(서식 7) · B 누가거리(17) · C 최소 Fs · D 최소 SI(8) · E 측점 등급 + SI<1 비율 글(7) · F 위험 면적 m²(14) · G 최대 경사 °(16) · 없으면 `A22` 「위험 측점 없음」 |
|
||||
|
||||
- 22 행 서식을 줄마다 복사. 측점 등급 = 영향 구역(측점 앞뒤 ½ 간격 × 띠 폭) 안 SI < 1(등급 4 이상) 셀 비율 — 낮음 < 5 % · 보통 5~20 % · 높음 ≥ 20 %(문턱 = E07 설정).
|
||||
|
||||
## s8 보강검토 (프로그램 몫 · E03 보강 검토 저장본 · 없으면 시트를 뺌)
|
||||
|
||||
| 칸 | 내용 |
|
||||
| --- | --- |
|
||||
| 6 행부터 가정 하나 × 조건 하나 = 한 줄 | A 측점 · B 비탈(좌 · 우 · 깎기 · 쌓기)(서식 7) · C 가정 글(1:n 전 → 후 · 소단 폭 / 높이 · 최소 완화)(5) · D 조건(7) · E 전 Fs · F 후 Fs · G 기준(8) · H 전 판정 · I 후 판정(7 → N.G 10 · O.K 11) · 없으면 「가정 없음」 |
|
||||
| 가정 줄 다음 한 줄 띄고 `8` · `9` 행 꼴(제목 3 · 머리 6) | 2. 벽 외적 안정 |
|
||||
| 그 아래 벽 하나 × 경우 하나 = 한 줄 | A 측점(7) · B 벽 이름 · 평상시 / 지진 시 · 토압법 · 배면 수위(5) · C 활동 · E 전도 · G 지지력 Fs(8) · D · F · H 기준(8 · KDS 11 80 05) · I 판정(7 → 10 · 11) · 없으면 「벽 검토 없음」 |
|
||||
|
||||
- 6 행 · 10 행 서식을 줄마다 복사 · 저장본 = (측점 · 종류 · 비탈)마다 가장 최근 하나.
|
||||
|
||||
## s10 부록_절편 (프로그램 몫 · 대표 단면마다 한 쪽)
|
||||
|
||||
양식 1~5 행 = 덩이 꼴(1 제목 · 2 단위 · 3 머리 · 4 절편 줄 서식 · 5 합계 서식). 대표 단면마다 지배 조건(Fs / 기준 가장 낮음) 임계면의 절편표.
|
||||
|
||||
| 덩이 안 행 | 칸 | 내용 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `A:M` 병합 | 「부록 ⓝ 절편 계산표 — 측점 · 구분 · 조건 · 방법 · Fs」 |
|
||||
| 2 | `A:M` 병합 | 임계면 · 단위 글 |
|
||||
| 3 | 머리 | 번호 · 폭 b · 높이 h · 저면 경사 α · 무게 W · 상재 Q · 간극수압 u · 저면 길이 l · c · φ · 유효 수직력 N' · 구동력 (W+Q)·sinα · 저항력 S |
|
||||
| 4 … | 절편 하나 = 한 줄 | 서식 21(번호) · 20(α · φ) · 19(그 밖) |
|
||||
| 끝 | 합계 | A 「합계」 · E ΣW · F ΣQ · L Σ구동력 · M Σ저항력(서식 25) |
|
||||
|
||||
- 저항력 S = 동원 전단 × Fs = c·l + N'·tanφ · 덩이 끝마다 쪽 나눔 · 대표 단면 없음 = `A1` 「부록 — 절편 계산표 없음」.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
- 공유 판정 — 지정 회사 GET 200 · PUT 403 · 미지정 403 · 같은 회사 그대로
|
||||
- 가입 — go.kr = 회사 「산림청」 · ACTIVE · 화면이 보낸 회사 값 무시 · 다른 메일 = NO_COMPANY
|
||||
- 종류별 단계 시드 · storage 허용 목록
|
||||
- 528-6 D · E 저장소 = 종류 단계만(B 양식 · B0x 빈 폴더 없음 · D 는 B04_PreProcess) · ROAD 그대로
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -17,6 +18,7 @@ from A07_Register import A07_Register_Router as reg_router
|
||||
from A07_Register.A07_Register_Schema import RegisterRequest
|
||||
from common_util import common_util_auth as auth
|
||||
from common_util import common_util_auth_repository as auth_repo
|
||||
from common_util.common_util_project_kind import KIND_STAGE_DIRS, init_kind_storage
|
||||
from common_util.common_util_workflow_state import initialize_project_stages
|
||||
from config.config_system import PROJECT_STORAGE_STAGE_DIRS
|
||||
from D01_PlanRegister import D01_PlanRegister_Router as d01
|
||||
@@ -165,7 +167,7 @@ def test_e01_requires_b06(monkeypatch):
|
||||
call()
|
||||
assert e.value.status_code == 404
|
||||
seen, call = _e01(monkeypatch, [{**src, "project_year": 2026, "section_complete": 1}])
|
||||
assert call() == {"project_id": "stab-id", "source_fingerprint": "f" * 40}
|
||||
assert call() == {"project_id": "stab-id", "source_fingerprint": "f" * 40, "warnings": []}
|
||||
assert seen["copied"] == ("stab-id", "s" * 36)
|
||||
assert seen["kind"] == "STABILITY" and seen["source_project_id"] == "s" * 36
|
||||
assert seen["name"] == "임도 안정성평가"
|
||||
@@ -257,7 +259,7 @@ def test_register_ignores_sent_company():
|
||||
assert "company_name" not in data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind,count", [("ROAD", 7), ("PLAN", 3), ("STABILITY", 4)])
|
||||
@pytest.mark.parametrize("kind,count", [("ROAD", 7), ("PLAN", 3), ("STABILITY", 5)])
|
||||
def test_stage_seed_by_kind(kind, count):
|
||||
log = []
|
||||
asyncio.run(initialize_project_stages(FakeCursor({}, log), "p1", kind))
|
||||
@@ -269,3 +271,36 @@ def test_stage_seed_by_kind(kind, count):
|
||||
def test_storage_dirs_allowed():
|
||||
for name in ("D02_PlanBase", "D04_PlanConfirm", "E02_StabGround", "E05_StabReport"):
|
||||
assert name in PROJECT_STORAGE_STAGE_DIRS
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind", ["PLAN", "STABILITY"])
|
||||
def test_kind_storage_light(tmp_path, kind):
|
||||
init_kind_storage(tmp_path, kind)
|
||||
names = {p.name for p in tmp_path.iterdir()}
|
||||
assert names == set(KIND_STAGE_DIRS[kind])
|
||||
assert ("B04_PreProcess" in names) == (kind == "PLAN")
|
||||
assert not [p for p in tmp_path.rglob("*") if p.is_file()]
|
||||
|
||||
|
||||
def test_road_storage_unchanged(tmp_path, monkeypatch):
|
||||
from B02_ProjRegister import B02_ProjRegister_Repository as b02
|
||||
|
||||
seeded = []
|
||||
monkeypatch.setattr(b02, "seed_project", seeded.append)
|
||||
b02._initialize_project_storage(tmp_path, "p1")
|
||||
assert seeded == [tmp_path]
|
||||
assert (tmp_path / "B09_Estimation" / "v1").is_dir()
|
||||
assert (tmp_path / "project_manifest.json").is_file()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind", ["PLAN", "STABILITY", None])
|
||||
def test_resolve_layout_skips_kind(tmp_path, kind):
|
||||
from common_util.common_util_storage import ensure_project_storage_layout
|
||||
|
||||
if kind:
|
||||
init_kind_storage(tmp_path, kind)
|
||||
before = {p.name for p in tmp_path.iterdir()}
|
||||
ensure_project_storage_layout(str(tmp_path))
|
||||
after = {p.name for p in tmp_path.iterdir()}
|
||||
assert (after == before) == bool(kind)
|
||||
assert ("B09_Estimation" in after) == (kind is None)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
"""532-1 덧 · 요구 351 — E01 복사본 읽기가 횡단 경로 null · 파일 없음 · 옛 꼴에서도 돎.
|
||||
|
||||
- 경로 null · 경로 파일 없음 → B06 이름 규칙(`find_cross_file`)으로 찾음
|
||||
- 옛 꼴 — 절대 경로(원본 쪽을 읽지 않음) · 소수 측점의 옛 정수 m 이름 · data 없음
|
||||
- 어디에도 없으면 그 측점만 빠짐(`missing_stations`) · cross_section 은 KeyError
|
||||
- 생성 복사(`copy_stability`)가 같은 검사 → manifest `missing_cross_chainages` · `warnings`
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
|
||||
from common_util import common_util_project_copy as cp # noqa: E402
|
||||
from E01_StabRegister import E01_StabRegister_Source as src # noqa: E402
|
||||
|
||||
CROSS = "B06_Section/cross_sections"
|
||||
|
||||
|
||||
def _cross(root: Path, name: str, label: str) -> None:
|
||||
path = root / CROSS / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps({"label": label, "samples": [{"offset_m": 0}]}), encoding="utf-8")
|
||||
|
||||
|
||||
def _row(chainage, path, data=..., seq=0):
|
||||
design = {"design": {"ground_type": "soil"}}
|
||||
return {
|
||||
"chainage_m": chainage,
|
||||
"sequence_num": seq,
|
||||
"cross_section_file_path": path,
|
||||
"data": json.dumps(design) if data is ... else data,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def root(tmp_path):
|
||||
outside = tmp_path / "original" / CROSS / "cross_00040m.json"
|
||||
outside.parent.mkdir(parents=True)
|
||||
outside.write_text(json.dumps({"label": "원본"}), encoding="utf-8")
|
||||
copy = tmp_path / "copy"
|
||||
_cross(copy, "cross_00000m.json", "정상")
|
||||
_cross(copy, "cross_00020m.json", "null")
|
||||
_cross(copy, "cross_00030m.json", "경로 틀림")
|
||||
_cross(copy, "cross_00040m.json", "절대")
|
||||
_cross(copy, "cross_00050m.json", "옛 정수") # 49.8 m 측점의 옛 이름
|
||||
rows = [
|
||||
_row(0.0, f"{CROSS}/cross_00000m.json"),
|
||||
_row(20.0, None, seq=None),
|
||||
_row(30.0, f"{CROSS}/cross_gone.json"),
|
||||
_row(40.0, str(outside)),
|
||||
_row(49.8, None, data=None),
|
||||
_row(60.0, None), # 어디에도 없음
|
||||
_row(None, None), # 누가거리 없는 행 — 측점 아님
|
||||
]
|
||||
(copy / src.STABILITY_DB_NAME).write_text(
|
||||
json.dumps({"cross_sections": rows}), encoding="utf-8"
|
||||
)
|
||||
(copy / src.MANIFEST_NAME).write_text("{}", encoding="utf-8")
|
||||
return copy
|
||||
|
||||
|
||||
def test_stations_skip_only_missing(root):
|
||||
got = src.stations(root)
|
||||
assert [s["chainage_m"] for s in got] == [0.0, 20.0, 30.0, 40.0, 49.8]
|
||||
assert [s["label"] for s in got] == ["정상", "null", "경로 틀림", "절대", "옛 정수"]
|
||||
assert got[0]["ground_type"] == "soil" and got[4]["ground_type"] is None
|
||||
assert src.missing_stations(root) == [60.0]
|
||||
|
||||
|
||||
def test_cross_section(root):
|
||||
assert src.cross_section(root, 20.0)["label"] == "null"
|
||||
assert src.cross_section(root, 40.0)["label"] == "절대" # 원본 쪽 파일 아님
|
||||
assert src.cross_section(root, 49.8)["design"] == {}
|
||||
with pytest.raises(KeyError, match="횡단 없음"):
|
||||
src.cross_section(root, 60.0)
|
||||
with pytest.raises(KeyError, match="측점 없음"):
|
||||
src.cross_section(root, 70.0)
|
||||
|
||||
|
||||
def test_normal_copy_has_no_missing(root):
|
||||
db = json.loads((root / src.STABILITY_DB_NAME).read_text(encoding="utf-8"))
|
||||
db["cross_sections"] = db["cross_sections"][:1]
|
||||
(root / src.STABILITY_DB_NAME).write_text(json.dumps(db), encoding="utf-8")
|
||||
assert [s["label"] for s in src.stations(root)] == ["정상"]
|
||||
assert src.missing_stations(root) == []
|
||||
|
||||
|
||||
def test_copy_stability_warns(tmp_path, monkeypatch):
|
||||
source = tmp_path / "source"
|
||||
_cross(source, "cross_00000m.json", "a")
|
||||
_cross(source, "cross_00020m.json", "b")
|
||||
dump = {
|
||||
"project": {"storage_path": "x"},
|
||||
"surface_params": {"source_filter": "none"},
|
||||
"route": None,
|
||||
"cross_sections": [
|
||||
_row(0.0, None),
|
||||
_row(20.0, f"{CROSS}/cross_00020m.json"),
|
||||
_row(35.5, None),
|
||||
],
|
||||
"route_points": [],
|
||||
"longitudinal_sections": [],
|
||||
}
|
||||
|
||||
async def fake_rows(connection, source_id):
|
||||
return dump
|
||||
|
||||
monkeypatch.setattr(cp, "_stability_rows", fake_rows)
|
||||
monkeypatch.setattr(cp, "resolve_stored_project_path", lambda _: str(source))
|
||||
manifest = asyncio.run(cp.copy_stability(None, "s" * 36, "e" * 36, tmp_path / "e"))
|
||||
assert manifest["missing_cross_chainages"] == [35.5]
|
||||
assert manifest["warnings"] == ["횡단 없는 측점 1 개 — 해석에서 빠짐"]
|
||||
written = json.loads(
|
||||
(tmp_path / "e" / cp.STABILITY_SOURCE_DIR / cp.MANIFEST_NAME).read_text("utf-8")
|
||||
)
|
||||
assert written["missing_cross_chainages"] == [35.5]
|
||||
@@ -0,0 +1,263 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""532-5 E03 이음매 — 2D 해석 길에 벽(532-4) · 지하수 갈래(E02) · 빠진 횡단 알림(532-1 덧).
|
||||
|
||||
- 벽: 검증_sub2 복사본 벽 선 측점(351 m 기슭막이) — 길이 벽을 넘김 · 임계면이 벽을 안 지남 ·
|
||||
측점 보기에 벽 폴리곤 · 목록 벽 수 · 벽 있는 측점만 지문이 바뀜
|
||||
- 지하수: E02 우기 갈래를 바꾸면 측점 지문이 바뀜 · 측점 보기 수위선 = 고른 갈래
|
||||
- 빠진 횡단: 횡단 파일이 없으면 result2d · stab/ground 의 missing_stations 에 그 측점
|
||||
- E05 이음매: E05 가 E03 정본만 읽음(사본 없음) · 측점 수 · N.G 수 · 지배 비탈이 E03 과 같음
|
||||
- result2d 덧칸: 지배 비탈 = fs / 기준 가장 낮은 쪽(깎기 1.22/1.2 · 쌓기 1.25/1.3 → 쌓기 · N.G)
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_532_5_e03_seam.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from shapely.geometry import LineString, Polygon
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from common_util import common_util_project_copy as cp # noqa: E402
|
||||
from common_util.common_util_station_key import chainage_key # noqa: E402
|
||||
from E01_StabRegister import E01_StabRegister_Source as src # noqa: E402
|
||||
from E02_StabGround import E02_StabGround_Model as ground_model # noqa: E402
|
||||
from E02_StabGround import E02_StabGround_Router as e02 # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Router as router # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_View as view # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Worker as worker # noqa: E402
|
||||
from E03_StabSection.E03_StabSection_Engine_Station import analyze_station # noqa: E402
|
||||
|
||||
SOURCE_ID = "cb15892a-5788-4a26-9316-20a15421f68b" # 검증_sub2(읽기만)
|
||||
PID = "e" * 8 + "-" + "e" * 4 + "-" + "e" * 4 + "-" + "e" * 4 + "-" + "e" * 12
|
||||
WALL_KEY = "351.00" # 검증_sub2 기슭막이 측점
|
||||
CORNER_M = 0.2 # 임계면이 벽 모서리를 스치는 길이 한도(test_532_4 와 같음)
|
||||
USER = {"role": "USER", "company_id": 1}
|
||||
|
||||
|
||||
async def _copy(target: Path) -> None:
|
||||
import aiomysql
|
||||
|
||||
from config.config_db import DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USER
|
||||
|
||||
connection = await aiomysql.connect(
|
||||
host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD, db=DB_NAME, autocommit=True
|
||||
)
|
||||
try:
|
||||
await cp.copy_project(
|
||||
connection, SOURCE_ID, scope="stability", new_id=PID, project_root=target
|
||||
)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def frozen():
|
||||
folder = Path(tempfile.mkdtemp(prefix="aislo_e03_seam_"))
|
||||
try:
|
||||
asyncio.run(_copy(folder / "copy"))
|
||||
except Exception as error: # noqa: BLE001
|
||||
shutil.rmtree(folder, ignore_errors=True)
|
||||
pytest.skip(f"검증 원본 복사 실패: {error}")
|
||||
root = folder / "copy"
|
||||
if WALL_KEY not in router._walls(str(src.source_dir(root)), "seam"):
|
||||
shutil.rmtree(folder, ignore_errors=True)
|
||||
pytest.skip("검증_sub2 에 벽 측점이 없음")
|
||||
yield root
|
||||
shutil.rmtree(folder, ignore_errors=True)
|
||||
|
||||
|
||||
def _fast(cross, design, **kw):
|
||||
"""진짜 엔진 — Bishop 원호만(빠르게)."""
|
||||
return analyze_station(cross, design, **{**kw, "methods": ("bishop",), "noncircular": False})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project(frozen, monkeypatch):
|
||||
"""벽 측점 + 벽 없는 측점 하나 · 진짜 엔진(빠르게) · 서버 안에서 차례로."""
|
||||
for name in ("_derived", "E02_StabGround", "E03_StabSection", "initial_snapshot"):
|
||||
shutil.rmtree(frozen / name, ignore_errors=True)
|
||||
original = src.stations
|
||||
|
||||
def pick(root):
|
||||
rows = original(root)
|
||||
wall = [r for r in rows if chainage_key(r["chainage_m"]) == WALL_KEY]
|
||||
return wall + [r for r in rows if chainage_key(r["chainage_m"]) != WALL_KEY][:1]
|
||||
|
||||
monkeypatch.setattr(router.src, "stations", pick)
|
||||
|
||||
async def fake_project(_pid):
|
||||
return frozen, {"company_id": 1, "project_kind": "STABILITY"}
|
||||
|
||||
monkeypatch.setattr(e02, "_project", fake_project)
|
||||
monkeypatch.setattr(worker, "analyze_station", _fast)
|
||||
monkeypatch.setattr(router, "WORKERS", 1)
|
||||
router._JOBS.clear()
|
||||
return frozen
|
||||
|
||||
|
||||
def _analyze() -> dict:
|
||||
async def go():
|
||||
first = await router.analyze2d(UUID(PID))
|
||||
while router._TASKS:
|
||||
await asyncio.gather(*list(router._TASKS))
|
||||
return first
|
||||
|
||||
return asyncio.run(go())
|
||||
|
||||
|
||||
def _save_ground(root: Path, ground: dict) -> None:
|
||||
ground_model.ground_path(root).parent.mkdir(parents=True, exist_ok=True)
|
||||
ground_model.ground_path(root).write_text(json.dumps(ground), encoding="utf-8")
|
||||
|
||||
|
||||
def test_벽_측점_임계면이_벽을_안_지남(project):
|
||||
assert _analyze()["status"] == "accepted"
|
||||
assert router._JOBS[PID]["error"] is None
|
||||
body = asyncio.run(router.result2d(UUID(PID), USER))
|
||||
walls = {r["key"]: r["walls"] for r in body["stations"]}
|
||||
assert walls[WALL_KEY] >= 1 and sorted(walls.values())[0] == 0
|
||||
sec = asyncio.run(router.section(UUID(PID), WALL_KEY))
|
||||
polys = [Polygon(r["polygon"]) for r in sec["regions"] if r["material_id"] == "wall"]
|
||||
assert polys and any(m["id"] == "wall" for m in sec["materials"])
|
||||
crossed = 0
|
||||
for a in sec["analyses"]:
|
||||
line = LineString(a["critical"]["points"])
|
||||
for poly in polys:
|
||||
assert line.intersection(poly).length <= CORNER_M
|
||||
crossed += 1
|
||||
assert crossed == len(sec["analyses"]) > 0
|
||||
|
||||
|
||||
def test_벽_있는_측점만_지문이_바뀜(project, monkeypatch):
|
||||
ground = router._ground(project)
|
||||
with_walls = {it["key"]: it["fp"] for it in router.plan_run(project, ground)["items"]}
|
||||
monkeypatch.setattr(router, "_walls", lambda source, fp: {})
|
||||
plain = {it["key"]: it["fp"] for it in router.plan_run(project, ground)["items"]}
|
||||
assert with_walls[WALL_KEY] != plain[WALL_KEY]
|
||||
assert [with_walls[k] == plain[k] for k in plain if k != WALL_KEY] == [True]
|
||||
|
||||
|
||||
def test_지하수_갈래_바꾸면_지문_바뀜(project):
|
||||
ground = router._ground(project)
|
||||
before = router.plan_run(project, ground)
|
||||
ground["groundwater"]["wet_mode"] = "ru"
|
||||
after = router.plan_run(project, ground)
|
||||
assert before["fp"] != after["fp"]
|
||||
assert all(a["fp"] != b["fp"] for a, b in zip(before["items"], after["items"]))
|
||||
|
||||
|
||||
def test_수위선_고른_갈래(project):
|
||||
_analyze()
|
||||
key = next(r["key"] for r in asyncio.run(router.result2d(UUID(PID), USER))["stations"])
|
||||
first = asyncio.run(router.section(UUID(PID), key))
|
||||
kds, surface = first["water"]["wet"], first["surface"]
|
||||
ground = router._ground(project)
|
||||
ground["groundwater"].update(wet_mode="ru", ru=0.3)
|
||||
_save_ground(project, ground)
|
||||
assert asyncio.run(router.section(UUID(PID), key))["water"]["wet"] is None # r_u = 선 없음
|
||||
ground["groundwater"].update(wet_mode="depth_ratio", depth_ratio=0.5)
|
||||
_save_ground(project, ground)
|
||||
deep = asyncio.run(router.section(UUID(PID), key))["water"]["wet"]
|
||||
assert kds and deep
|
||||
# 깊이 비율 선 = 공사 뒤 지표 아래 · 비탈 쪽은 확실히 아래(연직 면 x 는 뺌 — 지표 값이 둘)
|
||||
sx, sz = zip(*surface)
|
||||
steps = {x for x in sx if sx.count(x) > 1}
|
||||
below = [float(np.interp(x, sx, sz)) - z for x, z in deep if x not in steps]
|
||||
assert min(below) >= -1e-6 and max(below) > 0.1
|
||||
|
||||
|
||||
def test_빠진_횡단_목록(project):
|
||||
source = src.source_dir(project)
|
||||
row = src._cross_rows(source)[3]
|
||||
path = src.resolve_cross_file(source, row)
|
||||
moved = path.with_suffix(".bak")
|
||||
path.rename(moved)
|
||||
try:
|
||||
body = asyncio.run(router.result2d(UUID(PID), USER))
|
||||
ground = asyncio.run(e02.get_ground(UUID(PID), USER))
|
||||
finally:
|
||||
moved.rename(path)
|
||||
assert body["missing_stations"] == [float(row["chainage_m"])]
|
||||
assert ground["missing_stations"] == [float(row["chainage_m"])]
|
||||
assert asyncio.run(router.result2d(UUID(PID), USER))["missing_stations"] == []
|
||||
|
||||
|
||||
def test_E05_가_E03_정본으로_보고서(project):
|
||||
"""E03 [해석] 정본(stab2d_summary · stab2d_station)으로 E05 가 돎 — 사본 안 씀 · 수가 같음."""
|
||||
from E05_StabReport import E05_StabReport_Build as build
|
||||
|
||||
_analyze()
|
||||
assert not (project / "E03_StabSection" / "_derived").exists() # 옛 사본 꼴 없음
|
||||
body = asyncio.run(router.result2d(UUID(PID), USER))
|
||||
inputs = build.gather(project)
|
||||
rows = inputs["result2d"]["stations"]
|
||||
assert len(rows) == len(body["stations"]) == 2
|
||||
assert WALL_KEY in {r["station_key"] for r in rows}
|
||||
ng = sum(1 for r in body["stations"] if r["verdict_overall"] == "ng")
|
||||
assert sum(1 for r in rows if r["verdict"] == "ng") == ng
|
||||
e03 = {r["station_key"]: r for r in body["stations"]}
|
||||
for r in rows:
|
||||
mine = e03[r["station_key"]]
|
||||
assert r["side"] == mine["side"] in ("cut", "fill")
|
||||
assert r["fs_by_condition"] == mine["fs_by_condition"]
|
||||
assert r["verdict"] == mine["verdict_overall"] in ("ok", "ng")
|
||||
reps = inputs["representatives"]
|
||||
assert reps and all(rep["result"] and rep["result"]["cases"] for rep in reps)
|
||||
book = build.form()
|
||||
jobs = build.build_program(book, inputs)
|
||||
assert len([k for k, svg in jobs if svg.startswith("<svg")]) >= len(reps)
|
||||
|
||||
# 대표 단면 끔 → E05 목록에서 빠짐 · representative.json = overrides 만
|
||||
key = reps[0]["station_key"]
|
||||
asyncio.run(
|
||||
router.put_representative(UUID(PID), router.RepresentativeRequest(overrides={key: False}))
|
||||
)
|
||||
assert key not in [r["station_key"] for r in build.gather(project)["representatives"]]
|
||||
saved = json.loads((project / router.REPRESENTATIVE).read_text("utf-8"))
|
||||
assert saved == {"overrides": {key: False}}
|
||||
|
||||
|
||||
def _case(fs, need):
|
||||
return {"fs": fs, "required": need, "ok": fs >= need}
|
||||
|
||||
|
||||
def test_result2d_지배_비탈_기준_대비():
|
||||
"""깎기 1.22/1.2(O.K) · 쌓기 1.25/1.3(N.G) — fs 만 낮은 깎기가 아니라 쌓기가 지배 · N.G."""
|
||||
from E05_StabReport import E05_StabReport_Build as build
|
||||
|
||||
row = {
|
||||
"key": "10.00",
|
||||
"chainage_m": 10.0,
|
||||
"sides": [{"side": "left", "role": "cut"}, {"side": "right", "role": "fill"}],
|
||||
"cases": {
|
||||
"dry": {"left": _case(1.6, 1.5), "right": _case(1.7, 1.5)},
|
||||
"wet": {"left": _case(1.22, 1.2), "right": _case(1.25, 1.3)},
|
||||
},
|
||||
"min": {"dry": 1.6, "wet": 1.22},
|
||||
"verdict": {"dry": "ok", "wet": "ng"},
|
||||
}
|
||||
out = view.governing_row(row)
|
||||
assert out["side"] == "fill" and out["fs_min"] == 1.25
|
||||
assert out["fs_by_condition"] == {"dry": 1.7, "wet": 1.25}
|
||||
assert out["verdict_overall"] == "ng" and out["method"] == "bishop"
|
||||
e05 = build._row2d(row, "bishop")
|
||||
assert (e05["side"], e05["fs_by_condition"], e05["verdict"]) == (
|
||||
out["side"],
|
||||
out["fs_by_condition"],
|
||||
out["verdict_overall"],
|
||||
)
|
||||
row["verdict"] = {"dry": "ok", "wet": "ok"}
|
||||
assert view.governing_row(row)["verdict_overall"] == "ok"
|
||||
bare = {**row, "cases": {"dry": {"left": {"fs": 1.4, "required": None}}}}
|
||||
assert view.governing_row(bare)["side"] is None and view.governing_row(bare)["fs_min"] == 1.22
|
||||
@@ -0,0 +1,184 @@
|
||||
"""E04 엔진 잇기(PLAN 533-3) — 복사본 측점 → 533-1 합성 지형 → 533-2 탐색 → 저장본 꼴 ·
|
||||
탐색 노드 = 구간 · 탐색 폭 안 · 진행률 · 끊기 · 고리를 달아도 결과 불변.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_533_3_e04_engine.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from E04_Stab3D import E04_Stab3D_Run as run
|
||||
from E04_Stab3D.E04_Stab3D_Engine_Column import Strata
|
||||
from E04_Stab3D.E04_Stab3D_Engine_Search import SearchParams, search
|
||||
|
||||
# 노선 = x 축(동쪽) · 왼쪽(북) = 오르막 0.6 · 측점 20 m
|
||||
CHAINAGES = [0.0, 20.0, 40.0, 60.0, 80.0, 100.0]
|
||||
SLOPE = 0.6
|
||||
|
||||
|
||||
def _ground(y):
|
||||
return 100.0 + SLOPE * y
|
||||
|
||||
|
||||
def _cross(ch: float) -> dict:
|
||||
offsets = np.arange(-25.0, 25.01, 0.5)
|
||||
samples = [{"offset_m": o, "z": _ground(o), "valid": True} for o in offsets]
|
||||
# 노면 |o| ≤ 3 평평(100) · 왼쪽 1:1 깎기 · 오른쪽 1:1.5 쌓기 · 바깥 = 지반
|
||||
line = []
|
||||
for o in offsets:
|
||||
if abs(o) <= 3:
|
||||
z = 100.0
|
||||
elif o > 0:
|
||||
z = min(100.0 + (o - 3), _ground(o))
|
||||
else:
|
||||
z = max(100.0 - (-o - 3) / 1.5, _ground(o))
|
||||
line.append({"offset_m": o, "elevation_m": z})
|
||||
return {
|
||||
"chainage_m": ch,
|
||||
"frame": {"left_xy": [0.0, 1.0]},
|
||||
"samples": samples,
|
||||
"design": {
|
||||
"design_line": line,
|
||||
"ground_type": "soil",
|
||||
"road_edges": {"left": {"offset_m": 3.0}, "right": {"offset_m": -3.0}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
STATIONS = [
|
||||
{"chainage_m": ch, "label": f"No.{i}", "center_x": ch, "center_y": 0.0}
|
||||
for i, ch in enumerate(CHAINAGES)
|
||||
]
|
||||
|
||||
SETTINGS = {
|
||||
"start_m": 20.0,
|
||||
"end_m": 60.0,
|
||||
"condition": "dry",
|
||||
"shape": "both",
|
||||
"ellipsoid_ratio": 2.0,
|
||||
"box_half_width_m": 12.0,
|
||||
"cell_m": 1.0,
|
||||
"spacing_m": 3.0,
|
||||
"h_min_m": 2.0,
|
||||
"h_max_m": 10.0,
|
||||
"h_step_m": 2.0,
|
||||
"dr_m": 1.0,
|
||||
"max_depth_m": 6.0,
|
||||
"v_min_m3": 5.0,
|
||||
"v_max_m3": 3000.0,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def root(tmp_path, monkeypatch):
|
||||
models = tmp_path / "B04_PreProcess" / "models"
|
||||
models.mkdir(parents=True)
|
||||
xs = np.arange(-60.0, 160.0, 1.0)
|
||||
ys = np.arange(-60.0, 60.0, 1.0)
|
||||
xx, yy = np.meshgrid(xs, ys)
|
||||
z = _ground(yy)
|
||||
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)))
|
||||
return tmp_path
|
||||
|
||||
|
||||
def test_engine_raster_and_best(root):
|
||||
seen: list[tuple[int, str]] = []
|
||||
raw = run.run_engine(
|
||||
root, STATIONS[0:5], None, SETTINGS, lambda p, m: seen.append((p, m)), lambda: False
|
||||
)
|
||||
grid = raw["grid"]
|
||||
f = raw["f"]
|
||||
assert f.shape == (grid["ny"], grid["nx"]) == raw["z"].shape
|
||||
assert grid["dx"] == 1.0
|
||||
# 격자 = 구간 측점 ± (탐색 폭 + 깊이 + 10)
|
||||
assert grid["x0"] == pytest.approx(0.0 - 28.0, abs=1.0)
|
||||
finite = np.isfinite(f)
|
||||
assert finite.sum() > 50
|
||||
best = raw["best"]
|
||||
iy = round((best["y"] - grid["y0"]) / grid["dx"])
|
||||
ix = round((best["x"] - grid["x0"]) / grid["dx"])
|
||||
# 전체 최소 = 래스터 최소(그 활동체가 덮는 셀 중 하나) · 중심 = 탐색 띠 안(x 20~60 · |y| ≤ 12)
|
||||
assert np.nanmin(f) == pytest.approx(best["f"], rel=1e-6)
|
||||
assert 20.0 - 1e-6 <= best["x"] <= 60.0 + 1e-6 and abs(best["y"]) <= 12.0 + 1e-6
|
||||
assert 0 <= ix < grid["nx"] and 0 <= iy < grid["ny"]
|
||||
assert best["r"] > 0 and best["ratio"] in (1.0, 2.0) and 0 <= best["azimuth_deg"] < 360
|
||||
assert best["f"] == best["bishop"] or best["method"] == 1 # 다스림 = Bishop(잘림 없음)
|
||||
assert raw["reference_fs"] == 1.5 # 마스터 기본 건기
|
||||
# 합성 지표 — 노면 위(측점 40 · o=0) = 설계 100 · 깎기 끝 밖(o=+20 · 원지형) = 지반
|
||||
z = raw["z"]
|
||||
jy0 = round((0.0 - grid["y0"]) / grid["dx"])
|
||||
jx40 = round((40.0 - grid["x0"]) / grid["dx"])
|
||||
assert z[jy0, jx40] == pytest.approx(100.0, abs=0.05)
|
||||
jyw = round((20.0 - grid["y0"]) / grid["dx"])
|
||||
assert z[jyw, jx40] == pytest.approx(_ground(20.0), abs=0.05)
|
||||
# 진행률 — 0~100 · 단계 이름 · 마지막이 탐색 끝 무렵
|
||||
assert seen and all(0 <= p <= 100 for p, _ in seen) and seen[-1][0] >= 80
|
||||
|
||||
|
||||
def test_engine_stop(root):
|
||||
calls = {"n": 0}
|
||||
|
||||
def stop():
|
||||
calls["n"] += 1
|
||||
return calls["n"] > 3
|
||||
|
||||
with pytest.raises(InterruptedError):
|
||||
run.run_engine(root, STATIONS[0:5], None, SETTINGS, lambda p, m: None, stop)
|
||||
|
||||
|
||||
def test_wet_lower_than_dry(root):
|
||||
dry = run.run_engine(root, STATIONS[0:5], None, SETTINGS, lambda p, m: None, lambda: False)
|
||||
wet = run.run_engine(
|
||||
root,
|
||||
STATIONS[0:5],
|
||||
None,
|
||||
{**SETTINGS, "condition": "wet"},
|
||||
lambda p, m: None,
|
||||
lambda: False,
|
||||
)
|
||||
assert wet["best"]["f"] < dry["best"]["f"]
|
||||
assert wet["reference_fs"] == 1.3 # 우기 깎기 1.2 · 쌓기 1.3 중 큰 값
|
||||
|
||||
|
||||
def test_hooks_do_not_change_search():
|
||||
"""533-2 탐색에 단 진행률 · 끊기 고리 — 결과 불변(sub5 시험 꼴 작은 비탈)."""
|
||||
x = np.arange(-15.0, 35.0, 1.0)
|
||||
y = np.arange(-15.0, 15.0, 1.0)
|
||||
xx, yy = np.meshgrid(x, y)
|
||||
zz = np.clip(xx * 0.5, 0, 10)
|
||||
mat = [{"id": "m", "name": "m", "gamma_t": 19.0, "gamma_sat": 19.0, "c": 10.0, "phi": 25.0}]
|
||||
strata = Strata(x, y, zz, np.zeros((0,) + zz.shape), mat)
|
||||
base = dict(box=(-5, 22, -1, 1), spacing=2.0, h_min=2, h_max=14, h_step=2, dr=1.0,
|
||||
max_depth=10, v_min=20, v_max=2e4, ratios=(1.0,)) # fmt: skip
|
||||
plain = search(strata, SearchParams(**base))
|
||||
ticks: list[int] = []
|
||||
hooked = search(
|
||||
strata,
|
||||
SearchParams(**base, on_progress=lambda p, m: ticks.append(p), should_stop=lambda: False),
|
||||
)
|
||||
assert hooked.best["governing"] == plain.best["governing"]
|
||||
assert np.array_equal(np.isnan(hooked.f_min), np.isnan(plain.f_min))
|
||||
assert np.nanmax(np.abs(hooked.f_min - plain.f_min)) == 0
|
||||
assert ticks and min(ticks) >= 0 and max(ticks) <= 100
|
||||
with pytest.raises(InterruptedError):
|
||||
search(strata, SearchParams(**base, should_stop=lambda: True))
|
||||
assert math.isfinite(plain.best["governing"])
|
||||
|
||||
|
||||
def test_node_mask_section_and_route_ends(root):
|
||||
"""노드 자리 = 구간 누가 안 · 탐색 폭 안 · 노선 시점 앞(x < 0) · 종점 뒤(x > 100)는 뺌."""
|
||||
x = np.arange(-30.0, 131.0, 1.0)
|
||||
y = np.arange(-20.0, 21.0, 1.0)
|
||||
grid = {"x": x, "y": y}
|
||||
mask = run._node_mask(STATIONS, grid, 0.0, 100.0, 12.0)
|
||||
xx, yy = np.meshgrid(x, y)
|
||||
assert not mask[xx < -0.5].any() and not mask[xx > 100.5].any()
|
||||
assert not mask[np.abs(yy) > 12.0].any()
|
||||
assert mask[(np.abs(yy) <= 12.0) & (xx >= 1) & (xx <= 99)].all()
|
||||
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()
|
||||
@@ -60,6 +60,10 @@ writeFileSync(process.argv[3], JSON.stringify({
|
||||
sphere: mesh(M.slipMesh(c.grid, c.z, c.sphere)),
|
||||
ellipsoid: mesh(M.slipMesh(c.grid, c.z, c.ellipsoid)),
|
||||
cut: Array.from(M.cutCells(c.grid, c.z, c.sphere).keys()),
|
||||
center: M.footprintCenter(c.grid, M.cutCells(c.grid, c.z, c.sphere), c.sphere),
|
||||
centerEmpty: M.footprintCenter(c.grid, new Map(), c.sphere),
|
||||
floored: Object.fromEntries(M.cutCells(c.grid, c.z,
|
||||
{ ...c.sphere, floor: [[7 * 21 + 10, 53.0], [7 * 21 + 11, 60.0]] }).entries()),
|
||||
holed: mesh(M.terrainWithout(c.grid, c.z, M.cutCells(c.grid, c.z, c.sphere))),
|
||||
hCenter: M.slipHeight(c.sphere, 110, 207),
|
||||
hEdge: M.slipHeight(c.sphere, 116.5, 207),
|
||||
@@ -178,6 +182,23 @@ def test_slip_mesh_vertices(ts, name):
|
||||
assert all(z < Z[i] for i, z in cut.items())
|
||||
|
||||
|
||||
def test_footprint_center(ts):
|
||||
"""지도 최소 표시 = 잘린 셀 중심 평균 · 잘린 셀 없으면 구 중심."""
|
||||
cut = [i for i, _ in _py_cut(SPHERE)]
|
||||
x = sum(GRID["x0"] + i % GRID["nx"] for i in cut) / len(cut)
|
||||
y = sum(GRID["y0"] + i // GRID["nx"] for i in cut) / len(cut)
|
||||
assert ts["center"] == pytest.approx({"x": x, "y": y})
|
||||
assert ts["centerEmpty"] == {"x": SPHERE["x"], "y": SPHERE["y"]}
|
||||
|
||||
|
||||
def test_floor_truncates_slip(ts):
|
||||
"""못 뚫는 층 — 활동면이 층 윗면 아래로 안 내려감 · 층이 지표보다 높으면 잘린 셀 아님."""
|
||||
center = 7 * 21 + 10
|
||||
assert ts["floored"][str(center)] == pytest.approx(53.0) # 활동면 52 → 층 53
|
||||
assert str(center + 1) not in ts["floored"] # 층 60 ≥ 지표 53.5
|
||||
assert ts["floored"][str(center - 1)] == pytest.approx(dict(_py_cut(SPHERE))[center - 1])
|
||||
|
||||
|
||||
def test_terrain_hole_matches_cut(ts):
|
||||
"""활동체를 걷어낸 지표 = 지표 꼭짓점 − 잘린 셀 · 구멍 테두리는 활동면 메쉬와 같은 꼭짓점."""
|
||||
cut = [i for i, _ in _py_cut(SPHERE)]
|
||||
|
||||
@@ -24,6 +24,27 @@ STATIONS = [
|
||||
]
|
||||
|
||||
|
||||
def _fake_engine(root, stations, ground, settings, on_progress, should_stop):
|
||||
"""엔진 자리 — 길 시험은 셈 내용이 아니라 흐름(지문 · 끊기 · 저장)을 봄(엔진 = test_533_3_e04_engine)."""
|
||||
on_progress(50, "가짜")
|
||||
f = np.array([[1.4, 1.2, np.nan], [1.1, 0.9, 1.6]])
|
||||
return {
|
||||
"grid": {"x0": 100.0, "y0": 200.0, "dx": 1.0, "nx": 3, "ny": 2},
|
||||
"z": np.full(f.shape, 500.0),
|
||||
"f": f,
|
||||
"v": np.where(np.isfinite(f), 120.0, np.nan),
|
||||
"m": np.where(np.isfinite(f), 0.0, np.nan),
|
||||
"best": {
|
||||
"x": 101.0, "y": 201.0, "z": 505.0, "r": 6.0, "ratio": 1.0, "azimuth_deg": 0.0,
|
||||
"f": 0.9, "volume": 120.0, "method": 0,
|
||||
"hovland": 0.85, "bishop": 0.9, "janbu_corrected": 0.95, "truncated_ratio": 0.0,
|
||||
"floor": [],
|
||||
},
|
||||
"trials": 7,
|
||||
"reference_fs": 1.5,
|
||||
} # fmt: skip
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project(tmp_path, monkeypatch):
|
||||
src = tmp_path / "E01_StabRegister" / "source"
|
||||
@@ -36,25 +57,12 @@ def project(tmp_path, monkeypatch):
|
||||
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))
|
||||
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):
|
||||
return tmp_path
|
||||
|
||||
monkeypatch.setattr(run, "run_engine", _fake_engine)
|
||||
|
||||
monkeypatch.setattr(router_mod, "_project_root", fake_root)
|
||||
router_mod._JOBS.clear()
|
||||
return tmp_path
|
||||
@@ -102,7 +110,8 @@ def test_analyze_cached_force_and_result(client, project, monkeypatch):
|
||||
assert first == {"status": "accepted", "key": "0.00_100.00"}
|
||||
done = _wait(client)
|
||||
assert done["error"] is None and done["percent"] == 100
|
||||
assert done["summary"]["cells"] > 0
|
||||
assert done["summary"]["cells"] == 5 and done["summary"]["verdict"] == "ng"
|
||||
assert done["summary"]["by_method"] == {"hovland": 0.85, "bishop": 0.9, "janbu_corrected": 0.95}
|
||||
# 같은 입력 = 셈하지 않음
|
||||
again = client.post(f"/api/projects/{PID}/stab/analyze3d", json=BODY).json()
|
||||
assert again["status"] == "cached" and len(calls) == 1
|
||||
@@ -174,13 +183,7 @@ def test_cancel_and_restart(client, monkeypatch):
|
||||
== 404
|
||||
)
|
||||
# 다시 시작
|
||||
monkeypatch.setattr(
|
||||
run,
|
||||
"run_engine",
|
||||
run._preview_engine.__wrapped__
|
||||
if hasattr(run._preview_engine, "__wrapped__")
|
||||
else lambda *a: run._preview_engine(a[0], a[1], a[3], a[4], a[5]),
|
||||
)
|
||||
monkeypatch.setattr(run, "run_engine", _fake_engine)
|
||||
assert (
|
||||
client.post(f"/api/projects/{PID}/stab/analyze3d", json=BODY).json()["status"] == "accepted"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""534-3(빌더 · storage) — E05 안정성평가 보고서 `E05_StabReport_Build` · `_Store`.
|
||||
"""534-3 · 534-5(빌더 · storage · 길 · 인쇄) — E05 안정성평가 보고서 `E05_StabReport_Build` ·
|
||||
`_Section` · `_Print` · `_Store` · `_Router`.
|
||||
|
||||
가짜 E 프로젝트(2D 요약 · 측점별 저장본 · 대표 단면 · 지반 조건 · 3D 저장본 하나 · 복사본 읽기는 바꿔 끼움)로
|
||||
측점 수 = 줄 수 · N.G 수 · 그림 파일 수 · 갱신 뒤 사용자 몫 불변 · 덮임 목록 · 초기화 · xlsx 되읽기 · PDF 쪽 수.
|
||||
가짜 E 프로젝트(E03 파생 저장본 꼴 2D 요약 · 측점별 저장본 · 대표 단면 켬/끔 · 지반 조건 ·
|
||||
3D 저장본 하나 · 복사본 읽기는 바꿔 끼움)로 측점 수 = 줄 수 · N.G 수 · 그림 파일 수 ·
|
||||
갱신 뒤 사용자 몫 불변 · 덮임 목록 · 초기화 · 판 409 · xlsx 되읽기 · PDF 쪽 수 ·
|
||||
목차 쪽 번호 = 실제 · 부록 절편 · 측점표 쪽 나눔 고르게 · 옛 작업본 갱신(목차 자리 · 서식 붙임).
|
||||
"""
|
||||
|
||||
import io
|
||||
@@ -12,18 +15,24 @@ from pathlib import Path
|
||||
import pytest
|
||||
from openpyxl import load_workbook
|
||||
|
||||
from common_util.common_util_derived_store import derived_dir, write_derived
|
||||
from common_util.common_util_print_pdf import PrintError
|
||||
from common_util.common_util_spreadsheet import workbook_pdf
|
||||
from common_util.common_util_workbook_xlsx import workbook_to_xlsx
|
||||
from E02_StabGround import E02_StabGround_Model as ground_model
|
||||
from E05_StabReport import E05_StabReport_Build as build
|
||||
from E05_StabReport import E05_StabReport_Figure as figure
|
||||
from E05_StabReport import E05_StabReport_Print as printer
|
||||
from E05_StabReport import E05_StabReport_Store as store
|
||||
|
||||
pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
N = 30 # 측점 수
|
||||
NG_AT = {3, 10, 17, 24} # N.G 측점
|
||||
REPS = [("grid:3", "lowest_fs"), ("grid:12", "cut_15m"), ("grid:20", "fill_10m")]
|
||||
REPS = [("grid:3", "lowest"), ("grid:12", "cut_height"), ("grid:20", "fill_height")]
|
||||
# 자동 대표(높이 · 하위 5) 중 REPS 만 남기는 사용자 끔
|
||||
OFF = {"grid:0": False, "grid:5": False, "grid:10": False, "grid:17": False, "grid:24": False}
|
||||
KIND_SUMMARY, KIND_STATION = "stab2d_summary", "stab2d_station"
|
||||
SLICES = 24 # 임계면 절편 수
|
||||
REQUIRED = {"dry": {"cut": 1.5, "fill": 1.5}, "wet": {"cut": 1.2, "fill": 1.3}}
|
||||
|
||||
|
||||
def _key(i: int) -> str:
|
||||
@@ -31,29 +40,76 @@ def _key(i: int) -> str:
|
||||
|
||||
|
||||
def _summary(i: int, bump: float = 0.0) -> dict:
|
||||
ng = i in NG_AT
|
||||
"""E03 요약 줄 꼴 — 지배 비탈(홀수 = 깎기 왼쪽 · 짝수 = 쌓기 오른쪽)만 낮은 안전율."""
|
||||
ng = i in NG_AT and not bump
|
||||
dry = 1.31 if ng else 1.7 + i / 100 + bump
|
||||
return {
|
||||
"station_key": _key(i),
|
||||
"chainage_m": 20.0 * i,
|
||||
"side": "cut" if i % 2 else "fill",
|
||||
"fs_min": dry - 0.2,
|
||||
"fs_by_condition": {"dry": dry, "wet": dry - 0.2},
|
||||
"method": "bishop",
|
||||
"verdict": "ng" if ng else "ok",
|
||||
role = "cut" if i % 2 else "fill"
|
||||
side = "left" if role == "cut" else "right"
|
||||
other = "right" if side == "left" else "left"
|
||||
heights = {"cut": 15.0 if i == 12 else 6.0, "fill": 10.0 if i == 20 else 6.0}
|
||||
fs = {"dry": dry, "wet": dry - 0.2}
|
||||
cases = {
|
||||
cond: {
|
||||
side: {"fs": fs[cond], "required": REQUIRED[cond][role]},
|
||||
other: {"fs": 9.0, "required": REQUIRED[cond]["fill" if role == "cut" else "cut"]},
|
||||
}
|
||||
for cond in fs
|
||||
}
|
||||
return {
|
||||
"key": _key(i),
|
||||
"chainage_m": 20.0 * i,
|
||||
"label": f"No.{i}",
|
||||
"fp": f"st{i}",
|
||||
"sides": [
|
||||
{"side": "left", "role": "cut", "height_m": heights["cut"]},
|
||||
{"side": "right", "role": "fill", "height_m": heights["fill"]},
|
||||
],
|
||||
"cases": cases,
|
||||
"min": {c: min(v["fs"] for v in cases[c].values()) for c in cases},
|
||||
"verdict": {
|
||||
c: "ok" if all(v["fs"] >= v["required"] for v in cases[c].values()) else "ng"
|
||||
for c in cases
|
||||
},
|
||||
"error": None,
|
||||
}
|
||||
|
||||
|
||||
def _write_summary(root: Path, fp: str, rows: list[dict]) -> None:
|
||||
value = {
|
||||
"fingerprint": fp,
|
||||
"computed_at": "2026-10-25T10:00:00",
|
||||
"conditions": ["dry", "wet"],
|
||||
"method": "bishop",
|
||||
"stations": rows,
|
||||
}
|
||||
write_derived(root, KIND_SUMMARY, fp, value)
|
||||
|
||||
|
||||
def _slice(i: int) -> dict:
|
||||
return {
|
||||
"width": 0.5, "height": 1.0 + i / 10, "alpha_deg": 10.0 + i, "weight": 9.0 + i,
|
||||
"surcharge": 0.0, "pore_pressure": 2.0, "base_length": 0.51, "c": 15.0,
|
||||
"phi_deg": 30.0, "normal_eff": 8.0 + i, "shear_mobilized": 6.0 + i / 10,
|
||||
} # fmt: skip
|
||||
|
||||
|
||||
def _slip(F: float) -> dict:
|
||||
pts = [[-6.0 + k, 101.0 - 0.3 * (6 - abs(k - 6))] for k in range(13)]
|
||||
return {"F": F, "kind": "circle", "points": pts, "center": [0.0, 112.0], "radius": 12.5}
|
||||
return {
|
||||
"F": F,
|
||||
"kind": "circle",
|
||||
"points": pts,
|
||||
"center": [0.0, 112.0],
|
||||
"radius": 12.5,
|
||||
"slices": [_slice(i) for i in range(SLICES)],
|
||||
}
|
||||
|
||||
|
||||
def _station_doc(i: int) -> dict:
|
||||
s = _summary(i)
|
||||
cases = []
|
||||
for cond in ("dry", "wet"):
|
||||
F = s["fs_by_condition"][cond]
|
||||
F = s["min"][cond]
|
||||
for side, role in (("left", "cut"), ("right", "fill")):
|
||||
cases.append(
|
||||
{
|
||||
@@ -93,32 +149,13 @@ def _cross(chainage: float) -> dict:
|
||||
@pytest.fixture
|
||||
def project(tmp_path, monkeypatch) -> Path:
|
||||
root = tmp_path / "e_project"
|
||||
d2 = root / "E03_StabSection" / "_derived"
|
||||
(d2 / "results").mkdir(parents=True)
|
||||
(d2 / "result2d.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"input_fingerprint": "fp-1",
|
||||
"computed_at": "2026-10-25T10:00:00",
|
||||
"stations": [_summary(i) for i in range(N)],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
_write_summary(root, "fp-1", [_summary(i) for i in range(N)])
|
||||
for key, _ in REPS:
|
||||
i = int(key.split(":")[1])
|
||||
(d2 / "results" / f"grid_{i}.json").write_text(
|
||||
json.dumps(_station_doc(i)), encoding="utf-8"
|
||||
)
|
||||
write_derived(root, KIND_STATION, f"st{i}", {"out": _station_doc(i), "error": None}, keep=N)
|
||||
(root / "E03_StabSection").mkdir()
|
||||
(root / "E03_StabSection" / "representative.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"rule": {"fill_h_m": 10, "cut_h_m": 15, "lowest_n": 5},
|
||||
"stations": [{"station_key": k, "reason": r, "enabled": True} for k, r in REPS]
|
||||
+ [{"station_key": _key(5), "reason": "user", "enabled": False}],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
json.dumps({"overrides": OFF}), encoding="utf-8"
|
||||
)
|
||||
ground = ground_model.pick(ground_model.load_defaults())
|
||||
ground["stations"] = [
|
||||
@@ -138,7 +175,15 @@ def project(tmp_path, monkeypatch) -> Path:
|
||||
{
|
||||
"fingerprint": "3d-1",
|
||||
"settings": {},
|
||||
"summary": {"start_m": 0.0, "end_m": 200.0, "f_min": 1.42, "volume": 830.0},
|
||||
"summary": {
|
||||
"start_m": 0.0,
|
||||
"end_m": 200.0,
|
||||
"f_min": 1.42,
|
||||
"volume": 830.0,
|
||||
"condition": "wet",
|
||||
"reference_fs": 1.3,
|
||||
"verdict": "ok",
|
||||
},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
@@ -181,6 +226,29 @@ def test_처음_만듦_측점수_NG_그림(project):
|
||||
j = [c for a1, c in by["측점표"]["칸"].items() if a1.startswith("J") and int(a1[1:]) >= 6]
|
||||
assert sum(c.get("값") == "N.G" and c["서식"] == build.ST_NG for c in j) == len(NG_AT)
|
||||
assert sum(c.get("값") == "O.K" and c["서식"] == build.ST_OK for c in j) == N - len(NG_AT)
|
||||
k = [
|
||||
c.get("값")
|
||||
for a1, c in by["측점표"]["칸"].items()
|
||||
if a1.startswith("K") and int(a1[1:]) >= 6
|
||||
]
|
||||
assert k.count("대책 검토") == len(NG_AT) and k.count("-") == N - len(NG_AT)
|
||||
# 측점표 쪽 나눔 = 고르게(덩이 크기 차 ≤ 1 · 한 줄 꼬리 없음)
|
||||
cuts = by["측점표"]["인쇄"]["쪽나눔"]
|
||||
sizes = [b - a for a, b in zip([5, *cuts], [*cuts, 5 + N])]
|
||||
assert len(sizes) >= 2 and max(sizes) - min(sizes) <= 1
|
||||
# 부록 절편 — 대표 단면마다 지배 조건 한 덩이 · 저항력 = 동원 전단 × Fs · 덩이 끝 쪽 나눔
|
||||
s10 = by["부록_절편"]["칸"]
|
||||
block = 3 + SLICES + 1
|
||||
assert by["부록_절편"]["인쇄"]["쪽나눔"] == [block * (i + 1) for i in range(len(REPS))]
|
||||
assert "건기" in s10["A1"]["값"] and s10["A1"]["값"].startswith("부록 ① 절편 계산표 — No.3")
|
||||
assert s10["A4"]["값"] == 1 and s10["M4"]["값"] == pytest.approx(6.0 * 1.31)
|
||||
assert s10[f"A{block}"]["값"] == "합계"
|
||||
assert s10[f"E{block}"]["값"] == pytest.approx(sum(9.0 + i for i in range(SLICES)))
|
||||
assert book["서식"][s10["B4"]["서식"]]["크기"] == 8
|
||||
# 목차 — 본문 시트 차례 · 쪽 번호(목차 = 1)
|
||||
toc = by["목차"]["칸"]
|
||||
assert [toc[f"B{r}"]["값"] for r in (4, 5)] == [printer.TITLES["s2"], printer.TITLES["s3"]]
|
||||
assert toc["A10"]["값"] == "부록" # 보강검토 없음 → 본문 6 + 부록 and toc["C4"]["값"] == 2
|
||||
figs = sorted((project / "E05_StabReport" / "fig").glob("*.png"))
|
||||
assert len(figs) == len(REPS) + 1 and meta["figures"][-1] == build.BAND_KEY
|
||||
assert [f["열쇠"] for f in by["대표단면"]["그림"]] == [build.fig_key(k) for k, _ in REPS]
|
||||
@@ -196,12 +264,101 @@ def test_처음_만듦_측점수_NG_그림(project):
|
||||
assert s3["A26"]["값"] == "풍화토" and s3["B32"]["값"].startswith("0 ~ 100 m 위 지층")
|
||||
assert s3["A32"]["값"] == "붕적토" and s3["B13"]["값"] == 1.3
|
||||
s6 = by["3D요약"]["칸"]
|
||||
assert s6["D20"]["값"] == 1.42 and s6["H20"] == {"값": "N.G", "서식": build.ST_NG}
|
||||
assert s6["E20"]["값"] == pytest.approx(1.11) # 0~200 m 안 2D 최소
|
||||
assert s6["D20"]["값"] == 1.42 and s6["H20"] == {"값": "O.K", "서식": build.ST_OK} # E04 판정
|
||||
assert s6["E20"]["값"] == pytest.approx(1.11) # 0~200 m 안 2D 우기 최소(3D 와 같은 조건)
|
||||
assert (build.initial_dir(project) / build.REPORT).is_file()
|
||||
assert saved["fingerprint"] == build.analysis_fingerprint(project)
|
||||
|
||||
|
||||
def test_광역요약_E07_저장본(project):
|
||||
"""E07 저장본 없음 = 「광역 해석 전」 · 생김 = 지문 바뀜 → 갱신 뒤 등급 면적 · 위험 측점 줄."""
|
||||
_create(project)
|
||||
book, meta = store.load(project)
|
||||
assert _sheets(book)["광역요약"]["칸"]["B5"]["값"].startswith("광역 해석 전")
|
||||
keys = ("stable", "moderate", "quasi", "lower", "upper", "defended")
|
||||
area = {
|
||||
"settings": {
|
||||
"band_m": 100.0,
|
||||
"cell_m": 2.0,
|
||||
"gamma_t": 17.5,
|
||||
"gamma_sat": 18.5,
|
||||
"c": 5.0,
|
||||
"c_hi": 10.0,
|
||||
"phi": 27.0,
|
||||
"phi_hi": 30.0,
|
||||
"c_root": 0.0,
|
||||
"soil_depth_m": 1.5,
|
||||
"scenario": "sinmap",
|
||||
"tr_lo": 2000.0,
|
||||
"tr_hi": 3000.0,
|
||||
"level_lo_pct": 5.0,
|
||||
"level_hi_pct": 20.0,
|
||||
},
|
||||
"computed_at": "2026-10-25T21:00:00",
|
||||
"stations": [
|
||||
{
|
||||
"chainage_m": 0.0,
|
||||
"cells": 9,
|
||||
"fs_min": 1.6,
|
||||
"si_min": 1.6,
|
||||
"worst_class": 1,
|
||||
"risk_area_m2": 0.0,
|
||||
"risk_ratio": 0.0,
|
||||
"level": "low",
|
||||
"slope_max_deg": 12.0,
|
||||
"label": "No.0",
|
||||
},
|
||||
{
|
||||
"chainage_m": 20.0,
|
||||
"cells": 9,
|
||||
"fs_min": 0.8,
|
||||
"si_min": 0.0,
|
||||
"worst_class": 6,
|
||||
"risk_area_m2": 36.0,
|
||||
"risk_ratio": 1.0,
|
||||
"level": "high",
|
||||
"slope_max_deg": 41.5,
|
||||
"label": "No.1",
|
||||
},
|
||||
{
|
||||
"chainage_m": 40.0,
|
||||
"cells": 9,
|
||||
"fs_min": 0.95,
|
||||
"si_min": 0.7,
|
||||
"worst_class": 4,
|
||||
"risk_area_m2": 8.0,
|
||||
"risk_ratio": 0.0556,
|
||||
"level": "mid",
|
||||
"slope_max_deg": 33.0,
|
||||
"label": "No.2",
|
||||
},
|
||||
],
|
||||
"summary": {
|
||||
"areas_m2": dict(zip(keys, (100.0, 20.0, 12.0, 8.0, 0.0, 36.0))),
|
||||
"station_levels": {"low": 1, "mid": 1, "high": 1},
|
||||
},
|
||||
}
|
||||
path = project / "E07_StabArea" / "_derived" / "area.json"
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_text(json.dumps(area, ensure_ascii=False), encoding="utf-8")
|
||||
assert build.analysis_fingerprint(project) != meta["fingerprint"]
|
||||
store.refresh(project)
|
||||
book, _ = store.load(project)
|
||||
s7 = _sheets(book)["광역요약"]["칸"]
|
||||
assert [s7[f"C{r}"]["값"] for r in range(12, 18)] == [100.0, 20.0, 12.0, 8.0, 0.0, 36.0]
|
||||
assert [s7[f"A{r}"]["값"] for r in (22, 23)] == ["No.1", "No.2"] and "A24" not in s7
|
||||
assert (
|
||||
s7["E22"]["값"] == "높음 (100.0 %)"
|
||||
and s7["E23"]["값"] == "보통 (5.6 %)"
|
||||
and s7["F23"]["값"] == 8.0
|
||||
and [s7[f"G{r}"]["값"] for r in (12, 13, 14)] == [1, 1, 1]
|
||||
and s7["F13"]["값"] == "5 ~ 20 %"
|
||||
and s7["B7"]["값"].startswith("표준 — SINMAP")
|
||||
)
|
||||
vals = book["계산값"]["s7"]
|
||||
assert vals["C18"] == {"수": "176"} and vals["D17"]["수"].startswith("20.45")
|
||||
|
||||
|
||||
def test_갱신_사용자몫_불변_덮임(project):
|
||||
_create(project)
|
||||
book, _ = store.load(project)
|
||||
@@ -215,11 +372,9 @@ def test_갱신_사용자몫_불변_덮임(project):
|
||||
by = _sheets(book)
|
||||
by["측점표"]["칸"]["A6"]["값"] = "고친 측점"
|
||||
store.save(project, book)
|
||||
r2 = project / build.RESULT2D
|
||||
doc = json.loads(r2.read_text(encoding="utf-8"))
|
||||
doc["stations"][3] = _summary(4, 0.5) | {"station_key": _key(3), "chainage_m": 60.0}
|
||||
doc["input_fingerprint"] = "fp-2"
|
||||
r2.write_text(json.dumps(doc), encoding="utf-8")
|
||||
rows = [_summary(i) for i in range(N)]
|
||||
rows[3] = _summary(3, 0.5)
|
||||
_write_summary(project, "fp-2", rows)
|
||||
assert build.analysis_fingerprint(project) != store.load(project)[1]["fingerprint"]
|
||||
try:
|
||||
out = store.refresh(project)
|
||||
@@ -242,6 +397,29 @@ def test_갱신_사용자몫_불변_덮임(project):
|
||||
)
|
||||
|
||||
|
||||
def test_옛_작업본_갱신_목차자리_서식(project):
|
||||
"""534-5 전 작업본(목차 · 부록 없음 · 서식 18 개) → 갱신 = 목차는 표지 다음 · 부록은 끝 ·
|
||||
새 서식 붙임(옛 서식 번호 그대로) · 사용자 몫 불변."""
|
||||
_create(project)
|
||||
book, meta = store.load(project)
|
||||
book["시트"] = [s for s in book["시트"] if s["id"] not in ("s9", "s10")]
|
||||
book["서식"] = book["서식"][:18] + [{"크기": 30}] # 사용자가 더한 서식 = 18 번
|
||||
_sheets(book)["표지"]["칸"]["C10"] = {"값": "산림청", "서식": 18}
|
||||
meta["signs"] = {k: v for k, v in meta["signs"].items() if k not in ("s9", "s10")}
|
||||
store.save(project, book)
|
||||
(project / "E05_StabReport" / build.META).write_text(json.dumps(meta), encoding="utf-8")
|
||||
store.refresh(project)
|
||||
book, _ = store.load(project)
|
||||
ids = [s["id"] for s in book["시트"]]
|
||||
assert ids[:3] == ["s1", "s9", "s2"] and ids[-1] == "s10"
|
||||
by = _sheets(book)
|
||||
assert by["표지"]["칸"]["C10"] == {"값": "산림청", "서식": 18} and book["서식"][18] == {
|
||||
"크기": 30
|
||||
}
|
||||
head = book["서식"][by["부록_절편"]["칸"]["A3"]["서식"]]
|
||||
assert head["크기"] == 8 and head["굵게"] and len(book["서식"]) == len(build.form()["서식"]) + 1
|
||||
|
||||
|
||||
def test_3D_없음_2D_없음(project):
|
||||
shutil.rmtree(project / "E04_Stab3D")
|
||||
inputs = build.gather(project)
|
||||
@@ -250,7 +428,7 @@ def test_3D_없음_2D_없음(project):
|
||||
by = _sheets(book)
|
||||
assert by["3D요약"]["칸"]["A20"]["값"] == "3D 해석 전"
|
||||
assert by["입력조건"]["칸"]["B7"]["값"] == "3D 해석 전"
|
||||
(project / build.RESULT2D).unlink()
|
||||
shutil.rmtree(derived_dir(project, KIND_SUMMARY))
|
||||
with pytest.raises(build.MissingInput):
|
||||
build.gather(project)
|
||||
|
||||
@@ -261,24 +439,36 @@ def test_xlsx_되읽기_pdf_쪽수(project):
|
||||
figs = store.figures_of(project, book)
|
||||
assert len(figs) == len(REPS) + 1
|
||||
wb = load_workbook(io.BytesIO(workbook_to_xlsx(book, figs)))
|
||||
assert wb.sheetnames == ["표지", "개요", "입력조건", "측점표", "대표단면", "3D요약"]
|
||||
assert wb.sheetnames == [
|
||||
"표지", "목차", "개요", "입력조건", "측점표", "대표단면", "3D요약", "광역요약", "부록_절편",
|
||||
] # fmt: skip
|
||||
assert len(wb["대표단면"]._images) == len(REPS) and len(wb["3D요약"]._images) == 1
|
||||
assert [b.id for b in wb["대표단면"].row_breaks.brk] == [28, 56, 84]
|
||||
assert wb["측점표"]["J9"].value == "N.G" and wb["측점표"]["J9"].fill.fgColor.rgb.endswith(
|
||||
"FFC7CE"
|
||||
)
|
||||
book.pop("계산값", None)
|
||||
try:
|
||||
pdf = workbook_pdf(book, figs)
|
||||
pdf = printer.report_pdf(book, figs)
|
||||
except PrintError as error:
|
||||
pytest.skip(f"브라우저 없음 — {error}")
|
||||
from pypdf import PdfReader
|
||||
|
||||
texts = [p.extract_text() for p in PdfReader(io.BytesIO(pdf)).pages]
|
||||
stab = [i for i, t in enumerate(texts) if "산정 안전율" in t]
|
||||
assert stab and all("산정 안전율" in texts[i] for i in stab)
|
||||
# 표지 1 + 개요 1 + 입력조건 1 + 측점표 n + 대표단면 3 + 3D 1
|
||||
assert len(texts) == 3 + len(stab) + len(REPS) + 1
|
||||
assert len(stab) == len(book["시트"][4]["인쇄"]["쪽나눔"]) + 1 # 측점표 = 나눈 덩이 수
|
||||
# 표지 1 + 목차 1 + 개요 1 + 입력조건 1 + 측점표 n + 대표단면 3 + 3D 1 + 광역 1 + 부록 3
|
||||
assert len(texts) == 4 + len(stab) + len(REPS) + 1 + 1 + len(REPS)
|
||||
body = len(texts) - 1
|
||||
assert f"/ {body}" not in texts[0] and "검증 임도" not in texts[0].split("보고서")[0]
|
||||
assert all(f"- {i} / {body} -" in texts[i] and "검증 임도" in texts[i] for i in range(1, body))
|
||||
# 목차 쪽 번호 = 실제 쪽(본문 n 쪽 = PDF n + 1 번째 쪽)
|
||||
toc = texts[1]
|
||||
marks = {"측점별 안정성 판정 일람표": "산정 안전율", printer.TITLES["s5"]: "대표 단면 ①"}
|
||||
marks[printer.TITLES["s10"]] = "절편 계산표"
|
||||
for title, inside in marks.items():
|
||||
line = next(x for x in toc.splitlines() if title in x)
|
||||
page = int(line.split()[-1])
|
||||
assert inside in texts[page], (title, page)
|
||||
|
||||
|
||||
def test_단면_그림_SVG():
|
||||
@@ -294,3 +484,51 @@ def test_단면_그림_SVG():
|
||||
)
|
||||
assert svg.startswith("<svg") and "건기 임계면 Fs 1.30" in svg and "노폭 4.00 m" in svg
|
||||
assert "H 4.00 m" in svg and svg.count("<polyline") >= 4
|
||||
|
||||
|
||||
def test_길_저장_409_갱신_초기화_내보내기(project, monkeypatch):
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from common_util.common_util_auth import verify_session
|
||||
from E05_StabReport import E05_StabReport_Router as router_mod
|
||||
|
||||
async def fake_project(_pid):
|
||||
return project, 3
|
||||
|
||||
monkeypatch.setattr(router_mod, "_project", fake_project)
|
||||
app = FastAPI()
|
||||
app.include_router(router_mod.router)
|
||||
app.dependency_overrides[verify_session] = lambda: {"role": "USER", "company_id": 3}
|
||||
client = TestClient(app)
|
||||
url = "/api/projects/3d3776a1-2973-4385-8dcc-fdc6e49a900e/stab/report"
|
||||
try:
|
||||
got = client.get(url).json()
|
||||
except PrintError as error:
|
||||
pytest.skip(f"브라우저 없음 — {error}")
|
||||
assert got["editable"] and got["stale"] is False and len(got["book"]["시트"]) == 9
|
||||
book = got["book"]
|
||||
_sheets(book)["표지"]["칸"]["C10"] = {"값": "산림청", "서식": 5}
|
||||
assert client.put(url, json={"rev": "옛판", "book": book}).status_code == 409
|
||||
saved = client.put(url, json={"rev": got["rev"], "book": book}).json()
|
||||
assert saved["rev"] != got["rev"]
|
||||
assert client.post(f"{url}/refresh", json={"rev": got["rev"]}).status_code == 409
|
||||
out = client.post(f"{url}/refresh", json={"rev": saved["rev"]}).json()
|
||||
assert out["overwritten"] == [] and _sheets(out["book"])["표지"]["칸"]["C10"]["값"] == "산림청"
|
||||
fig = client.get(f"{url}/fig", params={"key": build.fig_key("grid:3")})
|
||||
assert fig.status_code == 200 and fig.content[:4] == b"\x89PNG"
|
||||
assert client.get(f"{url}/fig", params={"key": "../x"}).status_code == 400
|
||||
x = client.post(f"{url}/xlsx", json={"book": out["book"]})
|
||||
assert x.status_code == 200 and load_workbook(io.BytesIO(x.content)).sheetnames[0] == "표지"
|
||||
back = client.post(f"{url}/reset").json()
|
||||
assert "값" not in _sheets(back["book"])["표지"]["칸"].get("C10", {})
|
||||
|
||||
|
||||
def test_서명_화면_왕복_같음(project):
|
||||
"""JS 를 거친 책(10.0 → 10)도 같은 서명 — 안 고친 시트를 덮임으로 알리지 않음."""
|
||||
book = build.form()
|
||||
build.build_program(book, build.gather(project))
|
||||
s3 = _sheets(book)["입력조건"]
|
||||
assert s3["칸"]["B22"]["값"] == 10.0
|
||||
trip = json.loads(json.dumps(s3).replace("10.0", "10"))
|
||||
assert trip["칸"]["B22"]["값"] == 10 and build.sheet_sign(trip) == build.sheet_sign(s3)
|
||||
|
||||
@@ -36,7 +36,10 @@ STYLE_KEYS = {
|
||||
} # fmt: skip
|
||||
PRINT_KEYS = {"용지", "방향", "배율", "맞춤", "여백", "머리행", "쪽나눔"}
|
||||
A1 = re.compile(r"^([A-Z]{1,3})(\d+)$")
|
||||
NAMES = ["표지", "개요", "입력조건", "측점표", "대표단면", "3D요약"]
|
||||
NAMES = [
|
||||
"표지", "목차", "개요", "입력조건", "측점표", "대표단면", "3D요약", "광역요약", "보강검토",
|
||||
"부록_절편",
|
||||
] # fmt: skip
|
||||
|
||||
needs_node = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
|
||||
@@ -148,7 +151,8 @@ def test_양식_계약():
|
||||
book = _form()
|
||||
_contract(book)
|
||||
assert [s["이름"] for s in book["시트"]] == NAMES
|
||||
assert [s["id"] for s in book["시트"]] == ["s1", "s2", "s3", "s4", "s5", "s6"]
|
||||
ids = ["s1", "s9", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s10"]
|
||||
assert [s["id"] for s in book["시트"]] == ids
|
||||
|
||||
|
||||
def test_양식_인쇄_쪽나눔_머리행_그림():
|
||||
@@ -159,7 +163,18 @@ def test_양식_인쇄_쪽나눔_머리행_그림():
|
||||
assert by["측점표"]["인쇄"]["방향"] == "landscape" and by["측점표"]["인쇄"]["머리행"] == [4, 5]
|
||||
assert by["대표단면"]["인쇄"]["쪽나눔"] == [BLOCK]
|
||||
figs = {n: len(s.get("그림", [])) for n, s in by.items()}
|
||||
assert figs == {"표지": 0, "개요": 0, "입력조건": 0, "측점표": 0, "대표단면": 1, "3D요약": 1}
|
||||
assert figs == {
|
||||
"표지": 0,
|
||||
"목차": 0,
|
||||
"부록_절편": 0,
|
||||
"개요": 0,
|
||||
"입력조건": 0,
|
||||
"측점표": 0,
|
||||
"대표단면": 1,
|
||||
"3D요약": 1,
|
||||
"광역요약": 0,
|
||||
"보강검토": 0,
|
||||
}
|
||||
# N.G 빨강 조건부 서식 — 측점표 판정 열 · 서식 표에 N.G 칸 서식(빌더가 인쇄 · xlsx 용으로 직접 씀)
|
||||
assert by["측점표"]["조건부서식"][0]["범위"][0]["c0"] == 9
|
||||
assert any(st.get("글자색") == "#C00000" and st.get("채움") for st in _form()["서식"])
|
||||
@@ -222,8 +237,9 @@ def test_견본_pdf_쪽수_머리행(png):
|
||||
)
|
||||
sections = [i for i, t in enumerate(texts) if "대표 단면" in t and "안전율 Fs" in t]
|
||||
assert len(sections) == 2 # 단면마다 한 쪽
|
||||
# 표지 1 + 개요 1 + 입력조건 1 + 측점표 n + 대표단면 2 + 3D 1
|
||||
assert len(pages) == 1 + 1 + 1 + len(stab) + 2 + 1
|
||||
# 표지 1 + 목차 1 + 개요 1 + 입력조건 1 + 측점표 n + 대표단면 2 + 3D 1 + 광역 1 + 보강 1 + 부록 1
|
||||
# (양식 빈 판)
|
||||
assert len(pages) == 1 + 1 + 1 + 1 + len(stab) + 2 + 1 + 1 + 1 + 1
|
||||
assert all(float(pages[i].mediabox.width) > float(pages[i].mediabox.height) for i in stab)
|
||||
html = spreadsheet.print_htmls([{"book": book, "figures": {k: png for k in figs}}])[0]
|
||||
assert html.count("<img ") == 3 # 단면 2 + 3D 띠 1
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
"""PLAN 536-1 — E06 암반 평사투영 · Markland · Hoek-Bray 평면 · 쐐기 (요구 345 · 351).
|
||||
|
||||
기준 예제(문헌 값 · Rocscience 검증서가 옮긴 값):
|
||||
- 평면 = Hoek(Sau Mau Ping, 『Practical Rock Engineering』 7장 · RocPlane 검증 #1) 균열 없음 0.8184254 ·
|
||||
임계 균열 + 수위 0.9z 1.0654738 / Sharma 외(1995) 윗면 0~30° 무게(RocPlane 검증 #2)
|
||||
- 쐐기 = Hoek & Bray 쐐기 계수(대칭 쐐기 마찰만 · 해석해) · Kovári & Fritz · Kumsar 외(2000) 실험 ·
|
||||
Priest(1993) 두 예제(SWedge 검증 #1~#7 값)
|
||||
- Markland 경계 · 투영 왕복 · 위험 영역 = 판정 부등식
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from E06_StabRock import E06_StabRock_Engine as eng
|
||||
from E06_StabRock import E06_StabRock_Model as model
|
||||
from E06_StabRock import E06_StabRock_Stereonet as net
|
||||
|
||||
TOL = 0.02 # 완료 기준 ±2 %
|
||||
|
||||
|
||||
def close(value, expect, tol=TOL):
|
||||
assert value == pytest.approx(expect, rel=tol), (value, expect)
|
||||
|
||||
|
||||
# ---- 평면 (Hoek-Bray) -------------------------------------------------------------
|
||||
SAU_MAU = dict(
|
||||
H=60, slope_dip=50, plane_dip=35, c=100, phi=35, gamma=27, kh=0.08, gamma_w=10
|
||||
) # MN → kN · 0.1 MPa = 100 kPa
|
||||
|
||||
|
||||
def test_plane_hoek_no_crack():
|
||||
r = eng.plane_fs(**SAU_MAU, water_fill=1.0)
|
||||
close(r["F"], 0.8184254, 1e-4)
|
||||
close(r["W"], 28627.8, 1e-4)
|
||||
close(r["A"], 104.6068, 1e-5)
|
||||
close(r["U"], 15691.0, 1e-4)
|
||||
|
||||
|
||||
def test_plane_hoek_tension_crack():
|
||||
z = eng.plane_critical_depth_hoek(60, 50, 35)
|
||||
close(z, 14.0092, 1e-5)
|
||||
r = eng.plane_fs(**SAU_MAU, water_fill=0.9, crack={"mode": "depth", "z": z})
|
||||
close(r["F"], 1.0654738, 1e-4)
|
||||
close(r["W"], 24843.9, 1e-4)
|
||||
close(r["A"], 80.1826, 1e-5)
|
||||
close(r["crack_b"], 15.3357, 1e-4) # 마루 뒤 거리(RocPlane 검증 #1)
|
||||
|
||||
|
||||
def test_plane_critical_crack_search_matches_hoek():
|
||||
"""건조 · 윗면 수평 — 수치 탐색 임계 자리 = Hoek-Bray 식 z · b."""
|
||||
r = eng.plane_fs(
|
||||
H=60, slope_dip=50, plane_dip=35, c=20, phi=35, gamma=27, crack={"mode": "critical"}
|
||||
)
|
||||
close(r["z"], eng.plane_critical_depth_hoek(60, 50, 35), 1e-4)
|
||||
close(r["crack_b"], 15.3357, 1e-4)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"upper,weight,fs,fill",
|
||||
[
|
||||
(0, 2392.38, 1.58612, 1.00),
|
||||
(10, 2446.29, 1.58148, 0.84),
|
||||
(20, 2503.66, 1.58382, 0.71),
|
||||
(30, 2568.90, 1.57849, 0.61),
|
||||
],
|
||||
)
|
||||
def test_plane_upper_slope_sharma(upper, weight, fs, fill):
|
||||
"""윗면 기운 사면 · 연직 균열 마루 뒤 15.336 m · 균열 수위(RocPlane 정수 %)."""
|
||||
kw = dict(
|
||||
H=60, slope_dip=50, plane_dip=35, c=12, phi=45, gamma=2.6, gamma_w=1.0, upper_dip=upper
|
||||
)
|
||||
r = eng.plane_fs(**kw, crack={"mode": "x", "b": 15.33576}, water_fill=fill)
|
||||
close(r["W"], weight, 1e-4)
|
||||
close(r["F"], fs)
|
||||
|
||||
|
||||
def test_plane_bolt_vector():
|
||||
"""수동 볼트 — 저항 += T cos(θ+ψp) + T sin(θ+ψp) tanφ (힘 벡터 풀이)."""
|
||||
base = dict(H=10, slope_dip=60, plane_dip=35, c=10, phi=30, gamma=25)
|
||||
r0 = eng.plane_fs(**base)
|
||||
r1 = eng.plane_fs(**base, bolt_t=100, bolt_plunge=15)
|
||||
a = math.radians(50)
|
||||
expect = (
|
||||
r0["resist"] + 100 * math.cos(a) + 100 * math.sin(a) * math.tan(math.radians(30))
|
||||
) / r0["drive"]
|
||||
close(r1["F"], expect, 1e-9)
|
||||
|
||||
|
||||
def test_plane_not_formed():
|
||||
assert eng.plane_fs(H=10, slope_dip=40, plane_dip=45, c=0, phi=30, gamma=25) is None
|
||||
# 윗면이 활동면보다 가파르면 덩이 안 생김(균열 없음)
|
||||
assert (
|
||||
eng.plane_fs(H=10, slope_dip=60, plane_dip=30, c=0, phi=20, gamma=25, upper_dip=35) is None
|
||||
)
|
||||
|
||||
|
||||
# ---- 쐐기 (Hoek-Bray · SWedge 꼴) ------------------------------------------------
|
||||
FLAT = {"dip_direction": 180, "dip": 0}
|
||||
|
||||
|
||||
def _wedge(j1, j2, slope, phi, H=10, upper=FLAT, **kw):
|
||||
return eng.wedge_fs(
|
||||
j1={"dip_direction": j1[0], "dip": j1[1], "c": 0, "phi": phi},
|
||||
j2={"dip_direction": j2[0], "dip": j2[1], "c": 0, "phi": phi},
|
||||
slope={"dip_direction": slope[0], "dip": slope[1]},
|
||||
upper=upper,
|
||||
H=H,
|
||||
gamma=26,
|
||||
**kw,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"j1,j2", [((141, 45), (219, 45)), ((119, 50), (241, 50)), ((150, 60), (230, 55))]
|
||||
)
|
||||
def test_wedge_hoek_bray_wedge_factor(j1, j2):
|
||||
"""마찰만 · 같은 φ — Hoek & Bray FS = K tanφ / tanψi · K = sinβ / sin(ξ/2)
|
||||
(ξ = 두 면 사이 각(교선에 수직 단면) · β = 그 단면에서 쐐기 이등분선의 기울기)."""
|
||||
phi = 35
|
||||
r = _wedge(j1, j2, (180, 70), phi)
|
||||
n1 = eng.plane_normal(*j1)
|
||||
n2 = eng.plane_normal(*j2)
|
||||
i = eng.line_vector(*r["line"])
|
||||
# 교선에 수직인 면 안에서 두 면 안쪽 법선(위쪽) 사이 각 → 쐐기 사이 각 ξ = 180 − 그 각
|
||||
xi = math.pi - math.acos(float(n1 @ n2))
|
||||
bis = (n1 + n2) / np.linalg.norm(n1 + n2) # 쐐기 이등분선(교선에 수직 단면 안)
|
||||
h = np.cross(i, [0.0, 0.0, 1.0])
|
||||
h /= np.linalg.norm(h) # 그 단면의 수평
|
||||
beta = math.acos(abs(float(bis @ h)))
|
||||
K = math.sin(beta) / math.sin(xi / 2)
|
||||
expect = K * math.tan(math.radians(phi)) / math.tan(math.radians(r["line"][1]))
|
||||
close(r["F"], expect, 1e-3)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"j1,j2,slope,phi,plunge,fs",
|
||||
[
|
||||
((141, 45), (219, 45), (180, 70), 35, 37.85, 1.0), # Kovári & Fritz · SWedge #1
|
||||
((119, 50), (241, 50), (180, 70), 35, 30.02, 1.6325), # SWedge #2
|
||||
((195, 45), (105, 70), (160, 70), 30, None, 0.73), # Kumsar Case 3(Ankara) · SWedge 0.7123
|
||||
((33.5, 75), (248, 75), (337.5, 75), 40.8, None, 2.02), # Kumsar Case 4(Dinar)
|
||||
],
|
||||
)
|
||||
def test_wedge_kumsar_kovari(j1, j2, slope, phi, plunge, fs):
|
||||
r = _wedge(j1, j2, slope, phi)
|
||||
assert r["mode"] == "12"
|
||||
close(r["F"], fs, 0.03 if fs == 0.73 else 0.01) # 실험값 0.73 = 판독(SWedge 0.7123)
|
||||
if plunge:
|
||||
close(r["line"][1], plunge, 1e-3)
|
||||
|
||||
|
||||
def test_wedge_kumsar_seismic():
|
||||
r = _wedge((33.5, 75), (248, 75), (337.5, 75), 40.8, kh=0.3303)
|
||||
close(r["F"], 0.99) # Kumsar 0.99 · SWedge 0.9872
|
||||
|
||||
|
||||
def test_wedge_priest_double_plane():
|
||||
"""Priest(1993) 예제 1 — 두 면 미끄럼 · 수압 5 · 15 kPa · 철탑 180 kN(168/70)."""
|
||||
r = eng.wedge_fs(
|
||||
j1={"dip_direction": 203, "dip": 47, "c": 10, "phi": 40},
|
||||
j2={"dip_direction": 287, "dip": 52, "c": 20, "phi": 35},
|
||||
slope={"dip_direction": 230, "dip": 60},
|
||||
upper={"dip_direction": 225, "dip": 5},
|
||||
H=6.7978,
|
||||
gamma=26,
|
||||
water={"pressure": [5, 15]},
|
||||
external=[{"force": 180, "trend": 168, "plunge": 70}],
|
||||
)
|
||||
assert r["mode"] == "12"
|
||||
close(r["F"], 1.4966, 1e-3) # Priest ≈ 1.5
|
||||
close(r["volume"], 45.201, 1e-3)
|
||||
close(r["areas"][0], 41.147, 1e-3)
|
||||
close(r["areas"][1], 20.428, 1e-3)
|
||||
close(r["face_area"], 38.955, 1e-3)
|
||||
close(r["N"][0], 407, 3e-3)
|
||||
close(r["N"][1], 251, 3e-3)
|
||||
|
||||
|
||||
def test_wedge_priest_single_plane():
|
||||
"""Priest(1993) 예제 2 — 면 2 하나로 미끄럼(면 1 떨어짐) · 수압 25 · 15 kPa."""
|
||||
r = eng.wedge_fs(
|
||||
j1={"dip_direction": 65, "dip": 74, "c": 15, "phi": 32},
|
||||
j2={"dip_direction": 186, "dip": 41, "c": 5, "phi": 40},
|
||||
slope={"dip_direction": 134, "dip": 65},
|
||||
upper={"dip_direction": 122, "dip": 11},
|
||||
H=6.8471,
|
||||
gamma=25,
|
||||
water={"pressure": [25, 15]},
|
||||
)
|
||||
assert r["mode"] == "2"
|
||||
close(r["F"], 0.8493, 1e-3) # Priest ≈ 0.86 · SWedge 0.8493
|
||||
close(r["volume"], 81.741, 1e-3)
|
||||
assert r["N"][0] == 0.0
|
||||
|
||||
|
||||
def test_wedge_gravity_water_and_bolt():
|
||||
"""중력 수압 U = p³ γw Hw a / 6(SWedge · Hoek-Bray) · 볼트는 안전율을 낮추지 않음."""
|
||||
dry = _wedge((141, 45), (219, 45), (180, 70), 35)
|
||||
wet = _wedge((141, 45), (219, 45), (180, 70), 35, water={"fill": 1.0})
|
||||
expect = eng.GAMMA_W * wet["Hw"] / 6 * wet["areas"][0]
|
||||
close(wet["water_force"][0], expect, 1e-9)
|
||||
assert wet["F"] < dry["F"]
|
||||
bolted = _wedge(
|
||||
(141, 45), (219, 45), (180, 70), 35, bolt={"force": 50, "trend": 0, "plunge": 15}
|
||||
)
|
||||
assert bolted["F"] > dry["F"]
|
||||
|
||||
|
||||
def test_wedge_not_formed():
|
||||
# 교선이 사면보다 가파름 → 사면으로 안 나옴
|
||||
assert _wedge((141, 75), (219, 75), (180, 50), 35) is None
|
||||
|
||||
|
||||
# ---- Markland 경계 -------------------------------------------------------------
|
||||
SLOPE = {"dip_direction": 90.0, "dip": 70.0}
|
||||
|
||||
|
||||
def _j(dd, dip, phi=30.0, jid="J"):
|
||||
return {"id": jid, "dip_direction": dd, "dip": dip, "phi": phi}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dd,dip,hit",
|
||||
[
|
||||
(109.9, 45, True),
|
||||
(110.1, 45, False),
|
||||
(70.1, 45, True),
|
||||
(90, 30.1, True),
|
||||
(90, 29.9, False),
|
||||
(90, 69.9, True),
|
||||
(90, 70.1, False),
|
||||
],
|
||||
)
|
||||
def test_markland_planar(dd, dip, hit):
|
||||
assert (eng.kinematic(SLOPE, [_j(dd, dip)])["planar"] == ["J"]) is hit
|
||||
|
||||
|
||||
def test_markland_planar_apparent_daylight():
|
||||
"""측방 15° — 겉보기 사면 경사(tanψf cos15°)를 넘는 절리는 안 나옴."""
|
||||
app = eng.apparent_dip(70, 90, 105)
|
||||
assert eng.kinematic(SLOPE, [_j(105, app - 0.1)])["planar"] == ["J"]
|
||||
assert eng.kinematic(SLOPE, [_j(105, app + 0.1)])["planar"] == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dd,dip,hit", [(270, 50.1, True), (270, 49.9, False), (289.9, 80, True), (290.1, 80, False)]
|
||||
)
|
||||
def test_markland_toppling(dd, dip, hit):
|
||||
"""ψp > 90 − ψf + φ = 50 · 역방향 ±20."""
|
||||
assert (eng.kinematic(SLOPE, [_j(dd, dip)])["toppling"] == ["J"]) is hit
|
||||
|
||||
|
||||
def test_markland_wedge_plunge_bounds():
|
||||
a, b = _j(50, 60, jid="A"), _j(130, 60, jid="B")
|
||||
trend, plunge = eng.intersection(a, b)
|
||||
assert trend == pytest.approx(90.0, abs=1e-6)
|
||||
hit = eng.kinematic(SLOPE, [a, b])["wedge"]
|
||||
assert hit and hit[0]["ids"] == ["A", "B"]
|
||||
# φ 를 선주각 위로 올리면 사라짐
|
||||
hi = [dict(a, phi=plunge + 0.1), dict(b, phi=plunge + 0.1)]
|
||||
assert eng.kinematic(SLOPE, hi)["wedge"] == []
|
||||
# 사면을 교선보다 완만하게 하면 안 나옴
|
||||
assert eng.kinematic({"dip_direction": 90, "dip": plunge - 0.1}, [a, b])["wedge"] == []
|
||||
|
||||
|
||||
# ---- 투영 ----------------------------------------------------------------------
|
||||
@pytest.mark.parametrize("kind", net.PROJECTIONS)
|
||||
def test_projection_round_trip(kind):
|
||||
rng = np.random.default_rng(536)
|
||||
for trend, plunge in zip(rng.uniform(0, 360, 200), rng.uniform(0, 89.9, 200)):
|
||||
x, y = net.project(trend, plunge, kind)
|
||||
t2, p2 = net.inverse(x, y, kind)
|
||||
assert p2 == pytest.approx(plunge, abs=2e-3)
|
||||
# 좌표는 소수 다섯째 자리 — 중심 가까이는 추향 오차가 1e-5 / r rad 까지
|
||||
assert eng.ang_diff(t2, trend) <= math.degrees(1e-5 / math.hypot(x, y)) + 1e-6
|
||||
|
||||
|
||||
def test_projection_known_radii():
|
||||
assert net.radius(0, "equal_angle") == pytest.approx(1.0)
|
||||
assert net.radius(0, "equal_area") == pytest.approx(1.0)
|
||||
assert net.radius(90, "equal_angle") == pytest.approx(0.0)
|
||||
assert net.radius(45, "equal_angle") == pytest.approx(math.tan(math.radians(22.5)))
|
||||
assert net.radius(45, "equal_area") == pytest.approx(
|
||||
math.sqrt(2) * math.sin(math.radians(22.5))
|
||||
)
|
||||
# 동쪽 수평선 = (1, 0) · 북쪽 = (0, 1)
|
||||
assert net.project(90, 0, "equal_angle") == pytest.approx([1.0, 0.0], abs=1e-5)
|
||||
assert net.project(0, 0, "equal_area") == pytest.approx([0.0, 1.0], abs=1e-5)
|
||||
|
||||
|
||||
def _inside(pt, poly):
|
||||
x, y = pt
|
||||
hit = False
|
||||
for (x1, y1), (x2, y2) in zip(poly, poly[1:] + poly[:1]):
|
||||
if (y1 > y) != (y2 > y) and x < x1 + (y - y1) * (x2 - x1) / (y2 - y1):
|
||||
hit = not hit
|
||||
return hit
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind", net.PROJECTIONS)
|
||||
def test_zones_match_kinematic(kind):
|
||||
"""위험 영역 다각형 안 극점 · 교선 = Markland 판정(경계 1° 띠 밖 표본)."""
|
||||
rng = np.random.default_rng(7)
|
||||
phi = 30.0
|
||||
data = net.stereonet(SLOPE, [], phi, kind)
|
||||
checked = 0
|
||||
for dd, dip in zip(rng.uniform(0, 360, 1500), rng.uniform(1, 89, 1500)):
|
||||
kin = eng.kinematic(SLOPE, [_j(dd, dip, phi)])
|
||||
near = (
|
||||
abs(eng.ang_diff(dd, 90) - 20) < 1
|
||||
or abs(eng.ang_diff(dd, 270) - 20) < 1
|
||||
or abs(dip - phi) < 1
|
||||
or abs(dip - eng.apparent_dip(70, 90, dd)) < 1
|
||||
or abs(dip - 50) < 1
|
||||
)
|
||||
if near:
|
||||
continue
|
||||
pole = net.project(*eng.pole(dd, dip), kind)
|
||||
assert _inside(pole, data["zones"]["planar"]) == bool(kin["planar"]), (dd, dip)
|
||||
assert _inside(pole, data["zones"]["toppling"]) == bool(kin["toppling"]), (dd, dip)
|
||||
checked += 1
|
||||
assert checked > 1000
|
||||
|
||||
|
||||
# ---- 정본 · 측점 사면 ------------------------------------------------------------
|
||||
def _doc():
|
||||
doc = model.build_initial()
|
||||
doc["sets"] = [
|
||||
{
|
||||
"id": "J1",
|
||||
"name": "J1",
|
||||
"from_m": 0,
|
||||
"to_m": 100,
|
||||
"dip_direction": 90,
|
||||
"dip": 45,
|
||||
"phi": 30,
|
||||
"c": 5,
|
||||
"persistence_m": None,
|
||||
}
|
||||
]
|
||||
return doc
|
||||
|
||||
|
||||
def test_check_and_clean():
|
||||
assert model.check_joints(_doc()) == []
|
||||
bad = _doc()
|
||||
bad["sets"][0].update(dip=95, to_m=-1, name=" ")
|
||||
bad["sets"].append(dict(bad["sets"][0]))
|
||||
bad["settings"]["crack"] = {"mode": "depth", "z": None}
|
||||
paths = {e["path"] for e in model.check_joints(bad)}
|
||||
assert {"sets.0.dip", "sets.0.to_m", "sets.0.name", "sets.1.id", "settings.crack.z"} <= paths
|
||||
clean = model.clean(dict(_doc(), extra=1))
|
||||
assert set(clean) == {"version", "sets", "settings"}
|
||||
assert clean["sets"][0]["dip"] == 45.0
|
||||
|
||||
|
||||
def _cross(side_cut: str):
|
||||
"""좌(+offset) 또는 우 깎기 — 노선 북쪽 진행(left = 서쪽 −x) · 원지반 깎기 쪽으로 0.6 오름 ·
|
||||
노면 99 · 깎기 1:0.4 · 쌓기 1:1.5."""
|
||||
sign = 1.0 if side_cut == "left" else -1.0
|
||||
offs = np.arange(-20, 20.5, 0.5)
|
||||
ground = [[float(o), 100 + 0.6 * sign * o] for o in offs]
|
||||
design = []
|
||||
for o, z in ground:
|
||||
d = sign * o
|
||||
if abs(o) <= 2:
|
||||
z = 99.0
|
||||
elif d > 2:
|
||||
z = min(z, 99 + 2.5 * (d - 2))
|
||||
else:
|
||||
z = max(z, 99 - (abs(d) - 2) / 1.5)
|
||||
design.append([o, z])
|
||||
return (
|
||||
{
|
||||
"frame": {"left_xy": [-1.0, 0.0]},
|
||||
"samples": [{"offset_m": o, "elevation_m": z} for o, z in ground],
|
||||
},
|
||||
{
|
||||
"section_mode": f"{side_cut}_cut",
|
||||
"cut_slope_ratio": 0.4,
|
||||
"road_edges": {"left": {"offset_m": 2.0}, "right": {"offset_m": -2.0}},
|
||||
"design_line": [{"offset_m": o, "elevation_m": z} for o, z in design],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side,dd", [("left", 90.0), ("right", 270.0)])
|
||||
def test_station_slope_orientation(side, dd):
|
||||
"""좌 = 서쪽 산 · 사면이 동(노면)으로 기욺 = 경사방향 90 · 우는 270 · 경사 = atan(1/0.4)."""
|
||||
cross, design = _cross(side)
|
||||
slopes = model.station_slopes(cross, design)
|
||||
assert len(slopes) == 1 and slopes[0]["side"] == side
|
||||
assert slopes[0]["dip_direction"] == pytest.approx(dd)
|
||||
assert slopes[0]["dip"] == pytest.approx(math.degrees(math.atan(2.5)), abs=0.01)
|
||||
assert slopes[0]["height_m"] > 1
|
||||
assert slopes[0]["upper_dip"] == pytest.approx(math.degrees(math.atan(0.6)), abs=0.5)
|
||||
|
||||
|
||||
def test_analyze_station(monkeypatch):
|
||||
cross, design = _cross("left")
|
||||
monkeypatch.setattr(
|
||||
model,
|
||||
"rock_stations",
|
||||
lambda _s: [{"chainage_m": 20.0, "label": "STA", "ground_type": "ripping_rock"}],
|
||||
)
|
||||
monkeypatch.setattr(model.src, "cross_section", lambda _s, _c: {**cross, "design": design})
|
||||
ground = {
|
||||
"materials": [],
|
||||
"stations": [],
|
||||
"safety_factors": {"dry": {"cut": 1.5}, "wet": {"cut": 1.2}},
|
||||
"seismic": {"enabled": False},
|
||||
}
|
||||
out = model.analyze(None, _doc(), ground)
|
||||
assert len(out) == 1
|
||||
e = out[0]
|
||||
assert e["kinematic"]["planar"] == ["J1"]
|
||||
assert {c["condition"] for c in e["cases"]} == {"dry", "wet"}
|
||||
dry = next(c for c in e["cases"] if c["condition"] == "dry")
|
||||
assert dry["required"] == 1.5 and dry["ok"] == (dry["F"] >= 1.5)
|
||||
assert e["verdict"] in ("OK", "NG")
|
||||
assert e["gamma_r"] == model.DEFAULT_GAMMA_R
|
||||
nets = model.station_net(e, _doc()["settings"])
|
||||
assert set(nets) == {"equal_angle", "equal_area", "phi"}
|
||||
assert nets["equal_area"]["zones"]["planar"]
|
||||
@@ -0,0 +1,268 @@
|
||||
"""536-2 — E07 광역 자연사면 위험 `E07_StabArea_Engine` · `_Router`.
|
||||
|
||||
무한사면 해석해(건조 · 포화 · 점착) · SINMAP 안정 지수(닫힌 꼴 = 표본 · 등급 경계) ·
|
||||
D8 집수면적 견본(기운 평면 · V 골짜기) · 습윤지수 · SI 하한 / 상한 해석해 · 시나리오 둘(표준 · 최악) ·
|
||||
가짜 복사본으로 띠 · 측점표 · 저장본 · 지문.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from E07_StabArea import E07_StabArea_Engine as engine
|
||||
from E07_StabArea import E07_StabArea_Router as router_mod
|
||||
|
||||
SOIL = dict(gamma_t=18.0, gamma_sat=20.0, c=0.0, c_hi=0.0, phi=35.0, phi_hi=35.0)
|
||||
SAT = dict(scenario="saturated")
|
||||
|
||||
|
||||
def _fs(beta_deg: float, m: float, **kw) -> float:
|
||||
p = engine.AreaParams(**{**SOIL, **kw})
|
||||
return float(engine.infinite_slope_fs(np.array([math.radians(beta_deg)]), np.array([m]), p)[0])
|
||||
|
||||
|
||||
def test_무한사면_해석해_건조_포화_점착():
|
||||
t = math.tan(math.radians(35)) / math.tan(math.radians(30))
|
||||
assert _fs(30, 0.0) == pytest.approx(t, rel=1e-12) # 건조 c = 0 → tanφ / tanβ
|
||||
assert _fs(30, 1.0) == pytest.approx((20 - 9.81) / 20 * t, rel=1e-12) # 포화 → γ' / γsat 배
|
||||
# 손계산 — c 5 · 뿌리 2 · z 1.5 · β 35° · m 0.5
|
||||
b, z = math.radians(35), 1.5
|
||||
w = 18 * z * 0.5 + 20 * z * 0.5
|
||||
n = (18 * z * 0.5 + (20 - 9.81) * z * 0.5) * math.cos(b) ** 2
|
||||
hand = (7 + n * math.tan(math.radians(35))) / (w * math.sin(b) * math.cos(b))
|
||||
assert _fs(35, 0.5, c=5.0, c_root=2.0) == pytest.approx(hand, rel=1e-12)
|
||||
assert math.isinf(_fs(0, 1.0)) # 평평 = 활동 없음
|
||||
|
||||
|
||||
def test_SINMAP_안정지수_닫힌꼴_표본():
|
||||
p = engine.AreaParams(
|
||||
gamma_t=17.5, gamma_sat=18.5, c=0.0, c_hi=8.0, phi=25.0, phi_hi=38.0, **SAT
|
||||
)
|
||||
beta = np.radians(np.array([10.0, 30.0, 35.0, 40.0, 60.0]))
|
||||
m = np.ones_like(beta)
|
||||
si = engine.stability_index(beta, m, p)
|
||||
a, b = engine._terms(beta, m, p)
|
||||
rng = np.random.default_rng(1)
|
||||
c = rng.uniform(0, 8, 400_000)
|
||||
t = rng.uniform(math.tan(math.radians(25)), math.tan(math.radians(38)), 400_000)
|
||||
for k in range(len(beta)):
|
||||
lo = 0 * a[k] + math.tan(math.radians(25)) * b[k]
|
||||
fs = c * a[k] + t * b[k]
|
||||
if lo >= 1:
|
||||
assert si[k] == pytest.approx(lo) # Fs_min ≥ 1 → SI = Fs_min
|
||||
elif fs.max() < 1:
|
||||
assert si[k] == 0.0
|
||||
else:
|
||||
assert si[k] == pytest.approx((fs > 1).mean(), abs=3e-3) # P(Fs > 1)
|
||||
assert si[0] >= 1 and 0 < si[1] < 1 and si[4] == 0 # 세 갈래 모두 지남
|
||||
# 폭 0(범위 없음) — 결정 Fs 와 같거나 0
|
||||
q = engine.AreaParams(**SOIL, **SAT)
|
||||
fs = engine.infinite_slope_fs(beta, m, q)
|
||||
si0 = engine.stability_index(beta, m, q)
|
||||
assert np.allclose(si0, np.where(fs >= 1, fs, 0.0))
|
||||
|
||||
|
||||
def test_SINMAP_등급_경계():
|
||||
si = np.array([np.inf, 1.5, 1.4999, 1.25, 1.2499, 1.0, 0.9999, 0.5, 0.4999, 1e-9, 0.0, np.nan])
|
||||
assert engine.classify(si).tolist() == [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0]
|
||||
|
||||
|
||||
def test_D8_집수면적_기운평면_V골짜기():
|
||||
cell = 2.0
|
||||
ny, nx = 5, 7
|
||||
xx = np.arange(nx)[None, :] * cell
|
||||
plane = np.repeat(100.0 - 0.5 * xx, ny, axis=0) # 동쪽으로 기움 → 행마다 동쪽으로 흐름
|
||||
a = engine.catchment_area(plane, cell)
|
||||
assert np.allclose(a, (np.arange(nx)[None, :] + 1) * cell)
|
||||
yy = np.arange(9)[:, None] * cell
|
||||
valley = 100.0 + np.abs(yy - 8.0) * 1.0 - 0.1 * np.arange(nx)[None, :] * cell
|
||||
a = engine.catchment_area(valley, cell)
|
||||
assert a[4, -1] == pytest.approx(9 * nx * cell) # 전부가 골 출구로
|
||||
assert np.all(a[4, :-1] < a[4, 1:]) # 골 따라 늘어남
|
||||
|
||||
|
||||
def test_습윤지수():
|
||||
beta = np.radians(np.array([30.0, 30.0, 0.0]))
|
||||
a = np.array([100.0, 1e6, 50.0])
|
||||
w = engine.wetness(beta, a, 1 / 2000)
|
||||
assert w[0] == pytest.approx(100 / (2000 * 0.5)) # R a / (T sinβ)
|
||||
assert w[1] == 1.0 and w[2] == 1.0 # 넘침 · 평평 = 포화
|
||||
p = engine.AreaParams(**SOIL, tr_lo=2000, tr_hi=3000)
|
||||
assert engine.x_range(p) == (1 / 3000, 1 / 2000)
|
||||
assert engine.wet_m(beta, a, p).tolist() == w.tolist() # 기본 = 가장 젖은 쪽
|
||||
assert engine.wet_m(beta, a, engine.AreaParams(**SOIL, **SAT)).tolist() == [1.0] * 3
|
||||
|
||||
|
||||
SINMAP = dict(gamma_t=18.0, gamma_sat=18.0, c=2.0, c_hi=6.0, phi=30.0, phi_hi=40.0)
|
||||
|
||||
|
||||
def _sinmap_fs(beta, a, c, tanphi, x, z=1.5, g=18.0):
|
||||
"""Pack 1998 식 — FS = [C + cosθ(1 − W r) tanφ] / sinθ · C = c / (h ρs g) · h = z cosθ."""
|
||||
w = min(x * a / math.sin(beta), 1.0)
|
||||
h = z * math.cos(beta)
|
||||
return (c / (h * g) + math.cos(beta) * (1 - w * 9.81 / g) * tanphi) / math.sin(beta)
|
||||
|
||||
|
||||
def test_SI_하한_상한_해석해():
|
||||
"""γt = γsat 이면 엔진 = SINMAP 원식 · 하한 = (c, φ 아래 · T/R 아래) · 상한 = (위 · T/R 위)."""
|
||||
p = engine.AreaParams(**SINMAP, tr_lo=1000, tr_hi=3000)
|
||||
t1, t2 = math.tan(math.radians(30)), math.tan(math.radians(40))
|
||||
beta = np.radians(np.array([15.0, 33.0, 38.0, 45.0, 70.0]))
|
||||
area = np.array([20.0, 400.0, 300.0, 800.0, 5000.0])
|
||||
si = engine.stability_index(beta, area, p)
|
||||
rng = np.random.default_rng(7)
|
||||
n = 300_000
|
||||
c, t, x = rng.uniform(2, 6, n), rng.uniform(t1, t2, n), rng.uniform(1 / 3000, 1 / 1000, n)
|
||||
seen = set()
|
||||
for k, (b, ak) in enumerate(zip(beta, area)):
|
||||
lo = _sinmap_fs(b, ak, 2.0, t1, 1 / 1000)
|
||||
hi = _sinmap_fs(b, ak, 6.0, t2, 1 / 3000)
|
||||
fs = engine.infinite_slope_fs(
|
||||
beta[k : k + 1], engine.wet_m(beta[k : k + 1], area[k : k + 1], p), p
|
||||
)
|
||||
assert fs[0] == pytest.approx(lo, rel=1e-12) # 결정 Fs = 하한
|
||||
if lo >= 1:
|
||||
assert si[k] == pytest.approx(lo, rel=1e-12)
|
||||
seen.add("lo")
|
||||
elif hi < 1:
|
||||
assert si[k] == 0.0
|
||||
seen.add("zero")
|
||||
else:
|
||||
w = np.minimum(x * ak / math.sin(b), 1)
|
||||
h = 1.5 * math.cos(b)
|
||||
mc = (c / (h * 18) + math.cos(b) * (1 - w * 9.81 / 18) * t) / math.sin(b)
|
||||
assert si[k] == pytest.approx((mc > 1).mean(), abs=3e-3) # P(Fs > 1) · T/R 도 범위
|
||||
seen.add("prob")
|
||||
assert seen == {"lo", "zero", "prob"}
|
||||
|
||||
|
||||
def test_시나리오_둘_최악이_더_나쁨():
|
||||
beta = np.radians(np.linspace(5, 60, 56))
|
||||
area = np.full_like(beta, 50.0)
|
||||
std = engine.stability_index(beta, area, engine.AreaParams(**SINMAP))
|
||||
worst = engine.stability_index(beta, area, engine.AreaParams(**SINMAP, **SAT))
|
||||
assert np.all(worst <= std + 1e-12) # 완전 포화 = 아래 경계
|
||||
assert (engine.classify(worst) >= engine.RISK_CLASS).sum() > (
|
||||
engine.classify(std) >= engine.RISK_CLASS
|
||||
).sum()
|
||||
# 다 젖는 집수(x·a / sinβ ≥ 1) 면 두 시나리오가 같음
|
||||
big = np.full_like(beta, 1e7)
|
||||
assert np.allclose(
|
||||
engine.stability_index(beta, big, engine.AreaParams(**SINMAP)), worst, rtol=1e-12
|
||||
)
|
||||
|
||||
|
||||
def _decode(text: str, dtype: str, grid: dict) -> np.ndarray:
|
||||
return np.frombuffer(base64.b64decode(text), dtype=dtype).reshape(grid["ny"], grid["nx"])
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def project(tmp_path, monkeypatch):
|
||||
"""가짜 E 복사본 — 서쪽이 낮은 30° 비탈(동서 200 m · 남북 100 m) · 노선 = 남북 중심선."""
|
||||
x = np.arange(0.0, 200.5, 0.5)
|
||||
y = np.arange(0.0, 100.5, 0.5)
|
||||
z = np.repeat((x * math.tan(math.radians(30)))[None, :], len(y), axis=0)
|
||||
valid = np.ones_like(z, bool)
|
||||
valid[:, x > 180] = False # 동쪽 끝 = 지형 없음
|
||||
dem = tmp_path / "dtm.npz"
|
||||
np.savez(
|
||||
dem,
|
||||
x=x.astype(np.float32),
|
||||
y=y.astype(np.float32),
|
||||
z=z.astype(np.float32),
|
||||
valid_mask=valid,
|
||||
)
|
||||
stations = [
|
||||
{"chainage_m": float(c), "label": f"No.{i}", "center_x": 100.0, "center_y": float(c)}
|
||||
for i, c in enumerate(range(0, 101, 20))
|
||||
]
|
||||
route = [
|
||||
{"sequence_num": i, "model_x": 100.0, "model_y": float(c), "chainage_m": float(c)}
|
||||
for i, c in enumerate((0, 100))
|
||||
]
|
||||
src = router_mod.source
|
||||
monkeypatch.setattr(src, "terrain_grid_path", lambda _r: dem)
|
||||
monkeypatch.setattr(src, "stations", lambda _r: stations)
|
||||
monkeypatch.setattr(src, "db_snapshot", lambda _r: {"route_points": route})
|
||||
return tmp_path
|
||||
|
||||
|
||||
def test_측점_등급_문턱_경계():
|
||||
"""위험 셀 비율 < 5 % 낮음 · 5 % 이상 20 % 미만 보통 · 20 % 이상 높음 (경계 포함은 윗등급)."""
|
||||
cases = [(0, 100, "low"), (4, 100, "low"), (5, 100, "mid"), (19, 100, "mid")]
|
||||
cases += [(20, 100, "high"), (100, 100, "high"), (1, 20, "mid"), (1, 21, "low"), (1, 5, "high")]
|
||||
for risk, cells, want in cases:
|
||||
assert engine.station_level(risk, cells) == want, (risk, cells)
|
||||
# 사용자가 바꾼 문턱
|
||||
assert engine.station_level(9, 100, 10, 30) == "low"
|
||||
assert engine.station_level(30, 100, 10, 30) == "high"
|
||||
|
||||
|
||||
def test_측점_영향_구역과_비율():
|
||||
"""측점 0 · 10 · 30 → 영향 구역 = 이웃과 중간 누가로 나눔 · 띠 안 셀만 · 자료 없음 제외."""
|
||||
chain = np.arange(0.0, 40.0, 1.0)[None, :] # 1 × 40 셀 · 셀 i 누가 = i
|
||||
cls = np.ones(chain.shape, np.uint8)
|
||||
cls[0, [0, 1, 2, 3, 4]] = 4 # 측점 0 영역(0~5 · 정확히 중간 5 는 앞 측점) 6 셀 중 5 → 83 %
|
||||
cls[0, [6, 7]] = 5 # 측점 10 영역(6~20) 셀 14(19 는 띠 밖) · 위험 6 · 7 · 20 → 21 %
|
||||
cls[0, [20, 21]] = 6 # 20 은 측점 10 몫 · 측점 30 영역(21~39) 은 21 하나
|
||||
cls[0, 39] = 0 # 자료 없음 → 셀 수에서 빠짐(18 셀) 1/18 → 5.6 %
|
||||
inside = np.ones(chain.shape, bool)
|
||||
inside[0, 19] = False # 띠 밖 → 빠짐
|
||||
out = {k: np.ones(chain.shape) for k in ("fs", "si", "slope")} | {"cls": cls}
|
||||
rows = engine.station_table([30.0, 0.0, 10.0], chain, inside, out, 2.0)
|
||||
assert [r["chainage_m"] for r in rows] == [0.0, 10.0, 30.0]
|
||||
assert [r["cells"] for r in rows] == [6, 14, 18]
|
||||
assert [r["level"] for r in rows] == ["high", "high", "mid"]
|
||||
assert rows[0]["risk_ratio"] == round(5 / 6, 4) and rows[1]["risk_ratio"] == round(3 / 14, 4)
|
||||
assert rows[2]["risk_ratio"] == round(1 / 18, 4) and rows[2]["risk_area_m2"] == 4.0
|
||||
# 문턱을 올리면 등급이 내려감
|
||||
again = engine.station_table([0.0, 10.0, 30.0], chain, inside, out, 2.0, 15.0, 50.0)
|
||||
assert [r["level"] for r in again] == ["high", "mid", "low"]
|
||||
|
||||
|
||||
def test_측점_문턱_설정_검증():
|
||||
base = router_mod.AreaRequest.model_fields
|
||||
assert (base["level_lo_pct"].default, base["level_hi_pct"].default) == (5.0, 20.0)
|
||||
ok = dict(gamma_t=18.0, gamma_sat=20.0, c=0.0, c_hi=0.0, phi=30.0, phi_hi=30.0)
|
||||
router_mod.AreaRequest(**ok, level_lo_pct=10, level_hi_pct=10)
|
||||
with pytest.raises(ValueError):
|
||||
router_mod.AreaRequest(**ok, level_lo_pct=30, level_hi_pct=10)
|
||||
|
||||
|
||||
def test_길_띠_측점표_저장본(project):
|
||||
base = router_mod.defaults(project)
|
||||
assert base["scenario"] == "sinmap" and (base["tr_lo"], base["tr_hi"]) == (2000.0, 3000.0)
|
||||
settings = {**base, "band_m": 30.0, "cell_m": 2.0, "scenario": "saturated", "c_root": 0.0}
|
||||
assert settings["material_id"] == "colluvium" and settings["c_hi"] == settings["c"] + 5
|
||||
steps = []
|
||||
summary = router_mod.run(project, project, settings, "fp", lambda p, m: steps.append(p))
|
||||
doc = router_mod.load(project)
|
||||
assert steps == sorted(steps) and doc["fingerprint"] == "fp"
|
||||
g = doc["grid"]
|
||||
cls = _decode(doc["cls"], "u1", g)
|
||||
fs = _decode(doc["fs"], "<u2", g)
|
||||
# 붕적토 포화 30° — 손계산 Fs
|
||||
b = math.radians(30)
|
||||
n = (18.5 - 9.81) * 1.5 * math.cos(b) ** 2
|
||||
hand = (5 + n * math.tan(math.radians(27))) / (18.5 * 1.5 * math.sin(b) * math.cos(b))
|
||||
inner = fs[cls > 0]
|
||||
assert np.median(inner) / 1000 == pytest.approx(hand, abs=2e-3)
|
||||
assert summary["fs_min"] == pytest.approx(hand, abs=5e-3)
|
||||
# 띠 = 중심선 ± 30 m(격자 상자도 띠만큼) · 등급 면적 합 = 덮인 면적
|
||||
assert g["nx"] * g["dx"] <= 62 + 1e-9 and g["ny"] * g["dx"] <= 102 + 1e-9
|
||||
assert sum(summary["areas_m2"].values()) == pytest.approx(summary["covered_area_m2"])
|
||||
assert summary["covered_area_m2"] == pytest.approx((cls > 0).sum() * 4.0)
|
||||
rows = doc["stations"]
|
||||
assert [r["chainage_m"] for r in rows] == [0, 20, 40, 60, 80, 100]
|
||||
assert all(r["label"] == f"No.{i}" and r["worst_class"] >= 4 for i, r in enumerate(rows))
|
||||
assert all(r["level"] == "high" and r["risk_ratio"] > 0.99 for r in rows) # 완전 포화 30° 비탈
|
||||
assert summary["station_levels"] == {"low": 0, "mid": 0, "high": 6}
|
||||
assert sum(r["cells"] for r in rows) == (cls > 0).sum()
|
||||
# 지문 = 설정에 따라 바뀜
|
||||
assert router_mod.fingerprint("s", settings) != router_mod.fingerprint(
|
||||
"s", {**settings, "c": 6}
|
||||
)
|
||||
json.dumps(doc) # 그대로 JSON
|
||||
@@ -0,0 +1,328 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""536-3 E03 보강 검토 — 벽 외적 안정 · 비탈 완화 · 소단 가정 재해석 · E05 보강검토 시트.
|
||||
|
||||
- 벽 외적 안정(순수): 교과서 계수표(Coulomb · Rankine · Meyerhof) · M-O 닫힌 꼴 · 손계산 예제
|
||||
(직사각 벽 · 상재하중 · 수압 · 뒷굽 위 흙) ±2 %
|
||||
- 가정 형상(순수): 1:n 비탈이 원지반과 만나는 점 = 해석해 · 소단 꼭짓점 · 최소 완화 찾기(단조 · 창)
|
||||
- 길(검증_sub2 동결 복사 · 벽 측점 + 하나): 지금 기울기 그대로 = 원본 안전율(±3 %) · 최소 완화가
|
||||
목표 안전율을 맞춤 · 원본 측점 · 요약 저장본 그대로 · 벽 측점 외적 안정 · 벽 쪽 비탈 가정 거절 ·
|
||||
E05 보강검토 시트(있을 때만)
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_536_3_e03_reinforce.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import math
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from common_util import common_util_project_copy as cp # noqa: E402
|
||||
from common_util.common_util_station_key import chainage_key # noqa: E402
|
||||
from E01_StabRegister import E01_StabRegister_Source as src # noqa: E402
|
||||
from E02_StabGround import E02_StabGround_Router as e02 # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Router as router # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Router_WhatIf as wf # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Worker as worker # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_WallCheck as wc # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_WhatIf as whatif # noqa: E402
|
||||
from E03_StabSection.E03_StabSection_Engine_Station import analyze_station # noqa: E402
|
||||
|
||||
SOURCE_ID = "cb15892a-5788-4a26-9316-20a15421f68b" # 검증_sub2(읽기만)
|
||||
PID = "e" * 8 + "-" + "e" * 4 + "-" + "e" * 4 + "-" + "e" * 4 + "-" + "e" * 12
|
||||
WALL_KEY = "351.00" # 검증_sub2 기슭막이 측점
|
||||
SOIL = {"gamma_t": 18.0, "gamma_sat": 19.0, "c": 0.0, "phi": 30.0}
|
||||
|
||||
|
||||
def close(a: float, b: float, tol: float = 0.02) -> bool:
|
||||
return abs(a - b) <= tol * abs(b)
|
||||
|
||||
|
||||
# ── 벽 외적 안정 ─────────────────────────────────────────────────────────────
|
||||
def test_토압_지지력_계수_교과서표():
|
||||
"""Das(기초공학) 표 — Coulomb δ = 2/3φ · Rankine 경사 배면 · Meyerhof 지지력 계수."""
|
||||
assert close(wc.ka_coulomb(30, 20, 0, 0), 0.2973)
|
||||
assert close(wc.ka_coulomb(30, 0, 0, 0), 1 / 3)
|
||||
assert close(wc.ka_rankine(30, 0), 1 / 3)
|
||||
assert close(wc.ka_rankine(30, 10), 0.3495)
|
||||
nc, nq, ng = wc.bearing_factors(30)
|
||||
assert close(nc, 30.14) and close(nq, 18.40) and close(ng, 15.67)
|
||||
assert wc.bearing_factors(0) == (5.14, 1.0, 0.0)
|
||||
|
||||
|
||||
def test_MO_닫힌꼴():
|
||||
"""δ = θ = β = 0 · kv = 0 — KAE = cos²(φ−ψ) / (cos²ψ [1 + √(sinφ sin(φ−ψ) / cosψ)]²) · ψ = 0 이면 Coulomb."""
|
||||
for phi, kh in ((30, 0.2), (35, 0.15)):
|
||||
psi = math.atan(kh)
|
||||
f = math.radians(phi)
|
||||
want = math.cos(f - psi) ** 2 / (
|
||||
math.cos(psi) ** 2
|
||||
* (1 + math.sqrt(math.sin(f) * math.sin(f - psi) / math.cos(psi))) ** 2
|
||||
)
|
||||
assert close(wc.ka_coulomb(phi, 0, 0, 0, math.degrees(psi)), want, 1e-9)
|
||||
assert close(wc.ka_coulomb(30, 0, 0, 0, math.degrees(math.atan(0.2))), 0.4733)
|
||||
assert wc.ka_coulomb(32, 21, 5, 10, 0.0) == pytest.approx(wc.ka_coulomb(32, 21, 5, 10))
|
||||
|
||||
|
||||
RECT = [[0, 0], [2, 0], [2, 4], [0, 4]] # B 2 · H 4 · 배면 = +x(뒷굽 x = 2)
|
||||
|
||||
|
||||
def test_직사각_벽_손계산():
|
||||
"""Rankine · q 0 · 물 없음 · μ 0.5 — W 184 · Pa 48(H/3) · 활동 1.917 · 전도 2.875 ·
|
||||
e = 1 − 0.6522 · q_max · Meyerhof q_ult(유효폭 · 경사 계수)."""
|
||||
r = wc.check_wall(
|
||||
RECT, +1, backfill=SOIL, foundation=SOIL, method="rankine",
|
||||
surcharge_kpa=0.0, base_friction=0.5,
|
||||
) # fmt: skip
|
||||
W, Pa = 2 * 4 * 23.0, 0.5 * 18 * 16 / 3
|
||||
assert close(r["loads"]["W"], W) and close(r["loads"]["Pa"], Pa) and r["loads"]["W_soil"] == 0
|
||||
assert close(r["fs"]["sliding"], W * 0.5 / Pa)
|
||||
m_res, m_over = W * 1.0, Pa * 4 / 3
|
||||
assert close(r["fs"]["overturning"], m_res / m_over)
|
||||
xbar = (m_res - m_over) / W
|
||||
e = 1.0 - xbar
|
||||
q_max = W / 2 * (1 + 6 * e / 2) if e <= 2 / 6 else 2 * W / (3 * xbar)
|
||||
alpha = math.degrees(math.atan(Pa / W))
|
||||
q_ult = 0.5 * 18 * (2 - 2 * e) * 15.668 * (1 - alpha / 30) ** 2
|
||||
assert close(r["base"]["q_max"], q_max) and close(r["fs"]["bearing"], q_ult / q_max)
|
||||
assert r["case"] == "normal" and r["required"] == wc.REQUIRED["normal"]
|
||||
assert r["ok"] == {k: r["fs"][k] >= r["required"][k] for k in r["fs"]}
|
||||
|
||||
|
||||
def test_상재하중_수압_양압력():
|
||||
"""q 10 · 배면 수위 = 벽 높이 — Pa = Ka(½γ'H² + qH) · Pw = ½γwH² · U = ½γwHB · ΣH · V."""
|
||||
r = wc.check_wall(
|
||||
RECT, +1, backfill=SOIL, foundation=SOIL, method="rankine",
|
||||
surcharge_kpa=10.0, water_ratio=1.0, base_friction=0.5,
|
||||
) # fmt: skip
|
||||
g = SOIL["gamma_sat"] - wc.GAMMA_W
|
||||
Pa = (0.5 * g * 16 + 10 * 4) / 3
|
||||
Pw, U = 0.5 * wc.GAMMA_W * 16, 0.5 * wc.GAMMA_W * 4 * 2
|
||||
assert close(r["loads"]["Pa"], Pa) and close(r["loads"]["Pw"], Pw) and close(r["loads"]["U"], U)
|
||||
assert close(r["loads"]["H"], Pa + Pw) and close(r["loads"]["V"], 184 - U)
|
||||
arm = (0.5 * g * 16 * 4 / 3 + 40 * 2) / (0.5 * g * 16 + 40) # 삼각 + 사각 합력 높이
|
||||
assert close(r["loads"]["Pa_arm"], arm)
|
||||
m_over = Pa * arm + Pw * 4 / 3 + U * 4 / 3
|
||||
assert close(r["loads"]["M_overturn"], m_over)
|
||||
|
||||
|
||||
def test_L형_벽_뒷굽_위_흙_지진():
|
||||
"""역 T 형(앞굽 0 · 뒷굽 3 · 벽체 0.6~1.0) — Rankine 가상면 위 흙 = (3 − 1.0) × 4.5 × γ ·
|
||||
Coulomb 가상면 θ = atan(2 / 5) · 지진 시 = M-O 증분 0.6H + 관성 · 기준 = 지진 시 표."""
|
||||
L = [[0, 0], [3, 0], [3, 0.5], [1.0, 0.5], [1.0, 5], [0.6, 5], [0.6, 0.5], [0, 0.5]]
|
||||
r = wc.check_wall(L, +1, backfill=SOIL, foundation=SOIL, method="rankine")
|
||||
assert close(r["loads"]["W_soil"], 2.0 * 4.5 * 18)
|
||||
c = wc.check_wall(L, +1, backfill=SOIL, foundation=SOIL)
|
||||
assert close(c["geometry"]["theta_deg"], math.degrees(math.atan(2 / 5)))
|
||||
# 가상면(x = 3 − 0.4z)과 벽체(x = 1.0) 사이 · 0.5 ≤ z ≤ 5 → ∫(2 − 0.4z)dz = 4.05 m²
|
||||
assert close(c["loads"]["W_soil"], 4.05 * 18)
|
||||
s = wc.check_wall(L, +1, backfill=SOIL, foundation=SOIL, kh=0.15)
|
||||
assert s["case"] == "seismic" and s["required"] == wc.REQUIRED["seismic"]
|
||||
assert s["coefficients"]["kae"] > c["coefficients"]["ka"]
|
||||
assert s["loads"]["H"] > c["loads"]["H"] and s["fs"]["sliding"] < c["fs"]["sliding"]
|
||||
|
||||
|
||||
def test_배면이_반대쪽이면_거울():
|
||||
a = wc.check_wall(RECT, +1, backfill=SOIL, foundation=SOIL)
|
||||
b = wc.check_wall([[-x, z] for x, z in RECT], -1, backfill=SOIL, foundation=SOIL)
|
||||
for k in a["fs"]:
|
||||
assert a["fs"][k] == pytest.approx(b["fs"][k])
|
||||
|
||||
|
||||
# ── 가정 형상 · 최소 완화 찾기 ────────────────────────────────────────────────
|
||||
GROUND = [(-40.0, 120.0), (0.0, 100.0), (40.0, 80.0)] # 1:2 비탈(좌 = +x 낮음)
|
||||
LINE = [(-3.0, 101.5), (-2.0, 100.0), (2.0, 100.0), (3.0, 99.5)]
|
||||
|
||||
|
||||
def test_비탈_다시_그림_해석해():
|
||||
"""지반 z = 100 − x/2 · 우(−x) 깎기 시작 (−2, 100) · 1:n — u = −x 에서 (u − 2)/n = u/2 →
|
||||
u = 2 / (1 − n/2) · z = 100 + u/2 · n ≥ 2 = 못 만남. 좌 쌓기 (2, 100) 1:1.5 → (8, 96)."""
|
||||
for n in (0.5, 1.0, 1.5):
|
||||
line = whatif.reshape_side(LINE, GROUND, side="right", role="cut", start_x=-2.0, ratio=n)
|
||||
u = 2.0 / (1.0 - n / 2)
|
||||
x, z = min(line)
|
||||
assert x == pytest.approx(-u) and z == pytest.approx(100.0 + u / 2)
|
||||
assert [p for p in line if p[0] > -2.0 + 1e-9] == [list(p) for p in LINE[2:]]
|
||||
gentle = dict(side="right", role="cut", start_x=-2.0, ratio=2.5)
|
||||
assert whatif.reshape_side(LINE, GROUND, **gentle) is None
|
||||
line = whatif.reshape_side(
|
||||
LINE, GROUND, side="right", role="cut", start_x=-2.0, ratio=0.5, bench_w=1.0, bench_h=1.0
|
||||
)
|
||||
corners = [p for p in line if -3.6 < p[0] < -2.4]
|
||||
assert [[round(x, 6), round(z, 6)] for x, z in corners] == [[-3.5, 101.0], [-2.5, 101.0]]
|
||||
fill = whatif.reshape_side(LINE, GROUND, side="left", role="fill", start_x=2.0, ratio=1.5)
|
||||
assert max(fill) == pytest.approx([8.0, 96.0])
|
||||
assert [p for p in fill if p[0] < 2.0 - 1e-9] == [list(p) for p in LINE[:2]]
|
||||
assert (
|
||||
whatif.reshape_side(LINE, GROUND, side="left", role="fill", start_x=2.0, ratio=3.0) is None
|
||||
)
|
||||
|
||||
|
||||
def test_최소_완화_찾기():
|
||||
found = whatif.search_ratio(lambda n: n >= 1.23, 0.4)
|
||||
assert found["found"] == pytest.approx(1.25)
|
||||
window = whatif.search_ratio(lambda n: 1.0 <= n <= 1.3, 0.4) # 완만할수록 늘 안전하지 않음
|
||||
assert 1.0 <= window["found"] <= 1.3
|
||||
assert whatif.search_ratio(lambda n: False, 0.4, max_ratio=1.1)["found"] is None
|
||||
assert whatif.search_ratio(lambda n: True, 0.4) == {"found": 0.4, "tried": [(0.4, True)]}
|
||||
assert len(whatif.search_ratio(lambda n: n >= 3.9, 0.4)["tried"]) <= 10
|
||||
|
||||
|
||||
# ── 길(검증_sub2 동결 복사) ──────────────────────────────────────────────────
|
||||
async def _copy(target: Path) -> None:
|
||||
import aiomysql
|
||||
|
||||
from config.config_db import DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USER
|
||||
|
||||
connection = await aiomysql.connect(
|
||||
host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD, db=DB_NAME, autocommit=True
|
||||
)
|
||||
try:
|
||||
await cp.copy_project(
|
||||
connection, SOURCE_ID, scope="stability", new_id=PID, project_root=target
|
||||
)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def frozen():
|
||||
folder = Path(tempfile.mkdtemp(prefix="aislo_e03_rf_"))
|
||||
try:
|
||||
asyncio.run(_copy(folder / "copy"))
|
||||
except Exception as error: # noqa: BLE001
|
||||
shutil.rmtree(folder, ignore_errors=True)
|
||||
pytest.skip(f"검증 원본 복사 실패: {error}")
|
||||
yield folder / "copy"
|
||||
shutil.rmtree(folder, ignore_errors=True)
|
||||
|
||||
|
||||
def _fast(cross, design, **kw):
|
||||
"""진짜 엔진 — Bishop 원호만(빠르게)."""
|
||||
return analyze_station(cross, design, **{**kw, "methods": ("bishop",), "noncircular": False})
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def project(frozen):
|
||||
"""벽 측점 + 깎기 비탈 있는 벽 없는 측점 하나 · 진짜 엔진(빠르게) · [해석] 한 번."""
|
||||
mp = pytest.MonkeyPatch()
|
||||
original = src.stations
|
||||
|
||||
def pick(root):
|
||||
rows = original(root)
|
||||
wall = [r for r in rows if chainage_key(r["chainage_m"]) == WALL_KEY]
|
||||
rest = [r for r in rows if chainage_key(r["chainage_m"]) != WALL_KEY]
|
||||
return wall + rest[10:11]
|
||||
|
||||
mp.setattr(router.src, "stations", pick)
|
||||
|
||||
async def fake_project(_pid):
|
||||
return frozen, {"company_id": 1, "project_kind": "STABILITY"}
|
||||
|
||||
mp.setattr(e02, "_project", fake_project)
|
||||
mp.setattr(worker, "analyze_station", _fast)
|
||||
mp.setattr(wf, "analyze_station", _fast)
|
||||
mp.setattr(router, "WORKERS", 1)
|
||||
router._JOBS.clear()
|
||||
|
||||
async def go():
|
||||
await router.analyze2d(UUID(PID))
|
||||
while router._TASKS:
|
||||
await asyncio.gather(*list(router._TASKS))
|
||||
|
||||
asyncio.run(go())
|
||||
yield frozen
|
||||
mp.undo()
|
||||
|
||||
|
||||
def _stamps(root: Path) -> dict[str, bytes]:
|
||||
out = {}
|
||||
for kind in (router.KIND_STATION, router.KIND_SUMMARY):
|
||||
for p in (root / "_derived" / kind).glob("*.json"):
|
||||
out[str(p)] = p.read_bytes() # 읽기가 고친 때를 건드림(최근 표시) — 내용만 견줌
|
||||
return out
|
||||
|
||||
|
||||
def _plain_station(root: Path) -> tuple[str, str, float, dict]:
|
||||
summary, _ = router._latest_summary(root, "")
|
||||
row = next(r for r in summary["stations"] if r["key"] != WALL_KEY)
|
||||
side = next(s for s in row["sides"] if s["role"] == "cut")["side"]
|
||||
return row["key"], side, row["cases"]["dry"][side]["fs"], row
|
||||
|
||||
|
||||
def test_지금_기울기_그대로면_원본_안전율(project):
|
||||
key, side, before, _ = _plain_station(project)
|
||||
rec = wf.run_whatif(project, key, wf.WhatIfRequest(side=side))
|
||||
assert rec["assume"]["ratio"] == rec["assume"]["ratio_before"]
|
||||
assert close(rec["after"]["dry"]["fs"], before, 0.03)
|
||||
assert rec["view"]["analyses"] and rec["before"]["dry"]["fs"] == before
|
||||
|
||||
|
||||
def test_최소_완화가_목표를_맞춤_원본_불변(project):
|
||||
key, side, before, _ = _plain_station(project)
|
||||
stamps = _stamps(project)
|
||||
target = round(before + 0.02, 3)
|
||||
rec = wf.run_whatif(
|
||||
project,
|
||||
key,
|
||||
wf.WhatIfRequest(side=side, search=True, condition="dry", target_fs=target),
|
||||
)
|
||||
search = rec["search"]
|
||||
if search["found"] is None: # 완화가 깎기 높이를 키워 오히려 낮아지는 지형 — 끝값까지 봄
|
||||
assert all(not ok for _, ok in search["tried"])
|
||||
else:
|
||||
assert rec["after"]["dry"]["fs"] >= target - 1e-9
|
||||
assert rec["assume"]["ratio"] == search["found"] > rec["assume"]["ratio_before"]
|
||||
steeper = [n for n, ok in search["tried"] if n < search["found"]]
|
||||
assert all(not ok for n, ok in search["tried"] if n in steeper)
|
||||
assert _stamps(project) == stamps # 원본 측점 · 요약 저장본 그대로
|
||||
again = wf.run_whatif(
|
||||
project,
|
||||
key,
|
||||
wf.WhatIfRequest(side=side, search=True, condition="dry", target_fs=target),
|
||||
)
|
||||
assert again["computed_at"] == rec["computed_at"] # 같은 가정 = 저장본
|
||||
|
||||
|
||||
def test_벽_측점_외적_안정_벽쪽_가정_거절(project):
|
||||
rec = wf.run_wallcheck(project, WALL_KEY, wf.WallRequest(kh=0.15))
|
||||
assert len(rec["walls"]) == 1
|
||||
wall = rec["walls"][0]
|
||||
assert [c["case"] for c in wall["cases"]] == ["normal", "seismic"]
|
||||
for c in wall["cases"]:
|
||||
assert c["required"] == wc.REQUIRED[c["case"]]
|
||||
assert all(v > 0 for v in c["fs"].values())
|
||||
assert c["geometry"]["H"] == pytest.approx(1.5, abs=0.05)
|
||||
with pytest.raises(HTTPException) as error:
|
||||
wf.run_whatif(project, WALL_KEY, wf.WhatIfRequest(side=wall["side"]))
|
||||
assert error.value.status_code == 422
|
||||
|
||||
|
||||
def test_E05_보강검토_시트(project, monkeypatch):
|
||||
from E05_StabReport import E05_StabReport_Build as build
|
||||
|
||||
recs = wf.records(project)
|
||||
assert {r["type"] for r in recs} == {"slope", "wall"}
|
||||
book = build.form()
|
||||
build.build_program(book, build.gather(project))
|
||||
s8 = next(s for s in book["시트"] if s["id"] == "s8")
|
||||
cells = s8["칸"]
|
||||
slopes = [r for r in recs if r["type"] == "slope"]
|
||||
rows = sum(len(r["after"]) for r in slopes)
|
||||
assert all(cells[f"D{6 + i}"]["값"] in ("건기", "우기", "지진") for i in range(rows))
|
||||
wall_rows = [a1 for a1, c in cells.items() if a1[0] == "B" and "평상시" in str(c.get("값"))]
|
||||
assert len(wall_rows) == 1 and cells[f"I{wall_rows[0][1:]}"]["값"] in ("O.K", "N.G")
|
||||
monkeypatch.setattr(build.reinforce, "read", lambda root: [])
|
||||
empty = build.form()
|
||||
build.build_program(empty, build.gather(project))
|
||||
assert "s8" not in {s["id"] for s in empty["시트"]} # 없으면 시트 뺌
|
||||
@@ -1,5 +1,6 @@
|
||||
"""54-2 [?] 설명서 — 서버가 resources/help/<페이지>/*.md 를 부를 때마다 읽음(고치면 바로 바뀜)."""
|
||||
|
||||
import re
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
@@ -28,15 +29,30 @@ def test_title_order_and_live_edit():
|
||||
assert "고친 글" in read_help("B05", tmp_path)[1]["text"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("page", ["B01", "B10", "../B05", "b05", "D05", "E06", "C01"])
|
||||
@pytest.mark.parametrize("page", ["B01", "B10", "../B05", "b05", "D05", "E08", "C01"])
|
||||
def test_bad_page_refused(page):
|
||||
with pytest.raises(HTTPException):
|
||||
read_help(page)
|
||||
|
||||
|
||||
def test_529_1_D_E_pages_open_empty(tmp_path):
|
||||
assert read_help("D02", tmp_path) == [] and read_help("E05", tmp_path) == []
|
||||
assert read_help("D02", tmp_path) == [] and read_help("E07", tmp_path) == []
|
||||
|
||||
|
||||
def test_root_is_repo_resources():
|
||||
assert HELP_ROOT.parts[-2:] == ("resources", "help")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"page", ["D01", "D02", "D03", "D04", "E01", "E02", "E03", "E04", "E05", "E06", "E07"]
|
||||
)
|
||||
def test_d_e_pages_have_manual(page):
|
||||
items = read_help(page)
|
||||
assert items and items[0]["key"] == "00_설명서"
|
||||
assert len(items[0]["text"]) > 400
|
||||
for item in items: # 명사형 종결 — 합쇼체 · 해요체 · 한다체 금지
|
||||
for line in item["text"].splitlines():
|
||||
assert not re.search(r"(합니다|습니다|해요|세요|한다|된다|있다|없다)[.!]?\s*$", line), (
|
||||
page,
|
||||
line,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user