HANSOL commited on
Commit ยท
2afa69c
0
Parent(s):
Geo-Lab v4.1 - Clean deployment
Browse filesThis view is limited to 50 files because it contains too many changes. ย See raw diff
- .gitignore +31 -0
- 00_Master_Integration_Plan.md +138 -0
- 01_River_Landforms_Spec.md +70 -0
- 02_Coastal_Landforms_Spec.md +65 -0
- 03_Karst_Landforms_Spec.md +71 -0
- 04_Volcanic_Landforms_Spec.md +72 -0
- 05_Glacial_Landforms_Spec.md +73 -0
- 06_Arid_Landforms_Spec.md +73 -0
- 07_Plain_Landforms_Spec.md +79 -0
- DEPLOY.md +78 -0
- README.md +44 -0
- app.py +14 -0
- app/__init__.py +1 -0
- app/components/__init__.py +1 -0
- app/main.py +0 -0
- debug_log.txt +9 -0
- debug_piracy.py +55 -0
- engine/__init__.py +1 -0
- engine/base.py +163 -0
- engine/climate.py +140 -0
- engine/delta_physics.py +282 -0
- engine/deposition.py +174 -0
- engine/erosion.py +191 -0
- engine/erosion_process.py +328 -0
- engine/fluids.py +286 -0
- engine/glacier.py +218 -0
- engine/grid.py +88 -0
- engine/ideal_landforms.py +1621 -0
- engine/lateral_erosion.py +199 -0
- engine/mass_movement.py +146 -0
- engine/meander_physics.py +273 -0
- engine/physics_engine.py +365 -0
- engine/precompute.py +245 -0
- engine/pyvista_render.py +254 -0
- engine/river/__init__.py +1 -0
- engine/river/delta.py +251 -0
- engine/river/meander.py +299 -0
- engine/river/v_valley.py +161 -0
- engine/script_engine.py +87 -0
- engine/system.py +131 -0
- engine/wave.py +236 -0
- engine/wind.py +215 -0
- image list.txt +160 -0
- pyproject.toml +9 -0
- requirements.txt +6 -0
- run_log.txt +0 -0
- run_trace.txt +0 -0
- run_trace_soft.txt +0 -0
- test_genesis.py +51 -0
- test_output.txt +0 -0
.gitignore
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
venv/
|
| 9 |
+
.env
|
| 10 |
+
|
| 11 |
+
# Streamlit
|
| 12 |
+
.streamlit/
|
| 13 |
+
|
| 14 |
+
# IDE
|
| 15 |
+
.vscode/
|
| 16 |
+
.idea/
|
| 17 |
+
*.swp
|
| 18 |
+
*.swo
|
| 19 |
+
|
| 20 |
+
# OS
|
| 21 |
+
.DS_Store
|
| 22 |
+
Thumbs.db
|
| 23 |
+
|
| 24 |
+
# Logs
|
| 25 |
+
*.log
|
| 26 |
+
|
| 27 |
+
# Binary assets (too large for HuggingFace)
|
| 28 |
+
assets/reference/
|
| 29 |
+
*.png
|
| 30 |
+
*.jpg
|
| 31 |
+
*.jpeg
|
00_Master_Integration_Plan.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ [Geo-Lab AI] ํตํฉ ์ํคํ
์ฒ ์ค๊ณ์ (Master Integration Plan)
|
| 2 |
+
|
| 3 |
+
**Project Name:** Geo-Lab AI: The Living Earth
|
| 4 |
+
**Version:** Global Build 1.0 (Integration Phase)
|
| 5 |
+
**Scope:** Full-stack Geomorphology Simulation (All Modules)
|
| 6 |
+
**Director:** [User Name]
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 0. ํตํฉ ๊ฐ๋ฐ ์ฒ ํ (System Philosophy)
|
| 11 |
+
|
| 12 |
+
**"์งํ์ ๊ณ ๋ฆฝ๋์ด ์์ง ์๋ค (No Landform is an Island)."**
|
| 13 |
+
๋ณธ ํตํฉ ์์คํ
์ 7๊ฐ์ ๋
๋ฆฝ ๋ชจ๋(ํ์ฒ, ํด์, ์นด๋ฅด์คํธ, ํ์ฐ, ๋นํ, ๊ฑด์กฐ, ํ์ผ)์ **'๊ฐ์ด์ ์์ง(Gaia Engine)'**์ด๋ผ๋ ํ๋์ ๋ฌผ๋ฆฌ ์๋ฒ ์์์ ์ ๊ธฐ์ ์ผ๋ก ๊ตฌ๋ํ๋ค. ์ฌ์ฉ์๊ฐ ์กฐ์ ํ๋ ๊ธฐํ์ ์ง๊ฐ ์๋์ง๊ฐ ๋๋น ํจ๊ณผ์ฒ๋ผ ์ ์ง๊ตฌ์ ์งํ ๋ณํ๋ฅผ ์ผ์ผํค๋ **์ํ ์์คํ
(Feedback Loop)** ๊ตฌํ์ ๋ชฉํ๋ก ํ๋ค.
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## 1. ์์คํ
์ํคํ
์ฒ: ๊ฐ์ด์ ์์ง (The Gaia Engine)
|
| 18 |
+
|
| 19 |
+
๋ชจ๋ ๊ฐ๋ณ ๋ชจ๋์ ์๋ 3๊ฐ์ง **๊ธ๋ก๋ฒ ๋ณ์(Global Variables)**์ ์ข
์๋์ด ์๋ํ๋ค.
|
| 20 |
+
|
| 21 |
+
### ๐๏ธ ๊ธ๋ก๋ฒ ์ปจํธ๋กค๋ฌ (Global Controllers)
|
| 22 |
+
1. **์ง๊ฐ ์๋์ง ($E_{tectonics}$):**
|
| 23 |
+
* *Role:* ํ์ ์ด๋ ์๋ ๋ฐ ๋ง๊ทธ๋ง ํ๋์ฑ ์ ์ด.
|
| 24 |
+
* *Impact:* $E \uparrow$ $\Rightarrow$ ์ ๊ธฐ ์ต๊ณก ์ฐ์ง ์ต๊ธฐ, ํ์ฐ ํญ๋ฐ ๋น๋ ์ฆ๊ฐ, ์ง์ง ๋ฐ์.
|
| 25 |
+
2. **๊ธฐํ ๋ ๋ฒจ ($C_{climate}$):**
|
| 26 |
+
* *Role:* ์ง๊ตฌ ํ๊ท ๊ธฐ์จ($T_{avg}$) ๋ฐ ๊ฐ์๋($P_{avg}$) ์ ์ด.
|
| 27 |
+
* *Impact:* $T \downarrow$ $\Rightarrow$ ๋นํ ํ์ฅ (๋น๊ธฐ). $T \uparrow$ $\Rightarrow$ ๋นํ ํํด ๋ฐ ํด์๋ฉด ์์น (๊ฐ๋น๊ธฐ).
|
| 28 |
+
3. **ํด์๋ฉด ๊ณ ๋ ($H_{sea}$):**
|
| 29 |
+
* *Role:* ์นจ์ ๊ธฐ์ค๋ฉด(Base Level) ์ค์ .
|
| 30 |
+
* *Impact:* $H \downarrow$ $\Rightarrow$ ํ์ฒ ํ๋ฐฉ ์นจ์ ๊ฐํ(๊ฐ์
๊ณก๋ฅ). $H \uparrow$ $\Rightarrow$ ํ๊ตฌ ํด์ ๊ฐํ(์ผ๊ฐ์ฃผ, ์ํธ).
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 2. ๋ชจ๋ ๊ฐ ์ํธ์์ฉ ๋ก์ง (Cross-Module Interaction)
|
| 35 |
+
|
| 36 |
+
์งํ ๋ชจ๋๋ผ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ผ๋ฉฐ ์๋ก์ด ์งํ์ ์์ฑํ๋ **์ธ๊ณผ๊ด๊ณ ์ฒด์ธ(Chain)**์ ์ ์ํ๋ค.
|
| 37 |
+
|
| 38 |
+
### ๐ Chain A. [์ฐ์ง] $\leftrightarrow$ [๊ฑด์กฐ]: ๋น๊ทธ๋ ํจ๊ณผ (Rain Shadow)
|
| 39 |
+
* **Logic:** ๊ฑฐ๋ํ ์ต๊ณก ์ฐ๋งฅ์ด ์ต๊ธฐํ์ฌ ์์ฆ๊ธฐ๋ฅผ ๋ง์.
|
| 40 |
+
* **Process:**
|
| 41 |
+
1. **[์ฐ์ง]** ๋ชจ๋: ํด๋ฐ๊ณ ๋ $H > 3,000m$ ์ฐ๋งฅ ์์ฑ.
|
| 42 |
+
2. **[๊ธฐ์]** ์์ง: ํธ์ํ์ด ์ฐ๋งฅ์ ๋ถ๋ชํ ๋น๋ฅผ ๋ฟ๋ฆผ (๋ฐ๋๋ฐ์ด ์ฌ๋ฉด).
|
| 43 |
+
3. **[๊ฑด์กฐ]** ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ: ์ฐ๋งฅ ๋ฐ๋ํธ์ ๊ฑด์กฐ ๊ธฐํ ํ์ฑ $\rightarrow$ ์ฌ๋ง/์ฌ๊ตฌ ์์ฑ.
|
| 44 |
+
|
| 45 |
+
### ๐ Chain B. [๋นํ] $\leftrightarrow$ [ํ์ผ]: ์ต๋น์์ ๋ฒ๋ (Meltwater Pulse)
|
| 46 |
+
* **Logic:** ๋นํ๊ธฐ๊ฐ ๋๋๊ณ ๊ฐ๋น๊ธฐ๊ฐ ์ค๋ฉด ๋ง๋ํ ๋ฌผ์ด ํ์ผ๋ก ์์์ง.
|
| 47 |
+
* **Process:**
|
| 48 |
+
1. **[๋นํ]** ๋ชจ๋: ๊ธฐ์จ ์์น($T \uparrow$)์ผ๋ก ๋นํ ํํด.
|
| 49 |
+
2. **[์๋ฌธ]** ์์ง: ์ต๋น์ ์ ์
๋($Q_{melt}$) ๊ธ์ฆ.
|
| 50 |
+
3. **[ํ์ผ]** ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ: ํ์ฒ ์ ๋ ํญ์ฆ $\rightarrow$ ๋ฒ๋์ ํ๋, ๋ฐฐํ์ต์ง ํ์ฑ, ํ๊ตฌ ์ผ๊ฐ์ฃผ ์ฑ์ฅ ๊ฐ์.
|
| 51 |
+
|
| 52 |
+
### ๐ Chain C. [ํ์ฐ] $\leftrightarrow$ [์นด๋ฅด์คํธ]: ์ฐ์ฑ๊ณผ ์ผ๊ธฐ์ฑ (Chemical Weathering)
|
| 53 |
+
* **Logic:** ํ์ฐ ํ๋์ผ๋ก ์ธํ ์ฐ์ฑ๋น๊ฐ ์ํ์ ์ฉ์์ ๊ฐ์ํ.
|
| 54 |
+
* **Process:**
|
| 55 |
+
1. **[ํ์ฐ]** ๋ชจ๋: ํ์ฐ ํญ๋ฐ๋ก ๋๊ธฐ ์ค $SO_2, CO_2$ ๋๋ ์ฆ๊ฐ.
|
| 56 |
+
2. **[ํ๊ฒฝ]** ์์ง: ๋น๋ฌผ์ $pH$ ๋๋ ํ๋ฝ (์ฐ์ฑ๋น).
|
| 57 |
+
3. **[์นด๋ฅด์คํธ]** ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ: ๊ธฐ๋ฐ์์ด ์ํ์์ธ ์ง์ญ์ ์ฉ์ ์๋($Rate_{dissolution}$) 2๋ฐฐ ๊ฐ์ $\rightarrow$ ๊ฑฐ๋ ๋๊ตด ์์ฑ.
|
| 58 |
+
|
| 59 |
+
### ๐ Chain D. [๋นํ] $\leftrightarrow$ [ํด์]: ํผ์ค๋ฅด์ ํ์ (Fjord Formation)
|
| 60 |
+
* **Logic:** ๋นํ๊ฐ ๊น์๋ธ U์๊ณก์ ๋ฐ๋ท๋ฌผ์ด ๋ค์ด์ฐจ ๊น๊ณ ์ข์ ๋ง์ ํ์ฑ.
|
| 61 |
+
* **Process:**
|
| 62 |
+
1. **[๋นํ]** ๋ชจ๋: ๋นํ๊ธฐ์ ํด์๊น์ง ๋๋ฌํ๋ ๊ฑฐ๋ ๋นํ๊ฐ U์๊ณก์ ๊น๊ฒ ์นจ์.
|
| 63 |
+
2. **[๊ธฐํ]** ์์ง: ๊ฐ๋น๊ธฐ ๋๋๋ก ํด์๋ฉด ์์น($H_{sea} \uparrow$).
|
| 64 |
+
3. **[ํด์]** ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ: ๋ฐ๋ท๋ฌผ์ด U์๊ณก์ผ๋ก ์นจํฌ $\rightarrow$ ํผ์ค๋ฅด(Fjord) ํ์ฑ.
|
| 65 |
+
|
| 66 |
+
### ๐ Chain E. [ํ์ฐ] $\leftrightarrow$ [ํด์]: ํ๋ฌด์ ํด์ (Volcanic Coast)
|
| 67 |
+
* **Logic:** ์ฉ์์ด ๋ฐ๋ค์ ๋ฟ์ผ๋ฉฐ ๊ธ๊ฒฉํ ๋๊ฐ๋์ด ๋
ํนํ ํด์ ์งํ ํ์ฑ.
|
| 68 |
+
* **Process:**
|
| 69 |
+
1. **[ํ์ฐ]** ๋ชจ๋: ํด์ ์ธ๊ทผ์์ ํ๋ฌด์์ง ์ฉ์ ๋ถ์ถ.
|
| 70 |
+
2. **[๋๊ฐ]** ์์ง: ๋ฐ๋ท๋ฌผ๊ณผ ์ ์ดํ๋ฉฐ ๊ธ๋ญ $\rightarrow$ ์ฃผ์์ ๋ฆฌ ํ์ฑ.
|
| 71 |
+
3. **[ํด์]** ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ: ์ฃผ์์ ๋ฆฌ ํด์ ์ ๋ฒฝ ์์ฑ (์: ์ ์ฃผ ์ค๋ฌธ ๋ํฌํด์).
|
| 72 |
+
|
| 73 |
+
### ๐ Chain F. [ํ์ฒ] $\leftrightarrow$ [ํด์]: ์ผ๊ฐ์ฃผ vs ์ผ๊ฐ๊ฐ (Delta vs Estuary)
|
| 74 |
+
* **Logic:** ํ๊ตฌ์์ ํ์ฒ ์๋์ง์ ํด์ ์๋์ง์ ์๋์ ํฌ๊ธฐ์ ๋ฐ๋ผ ํด์ ๋๋ ์นจ์ ๋ฐ์.
|
| 75 |
+
* **Process:**
|
| 76 |
+
1. **[ํ์ฒ]** ๋ชจ๋: ํ๊ตฌ์ ํด์ ๋ฌผ ๊ณต๊ธ (Sediment Load).
|
| 77 |
+
2. **[ํด์]** ์์ง: ํ๋/์กฐ๋ฅ์ ํด์ ๋ฌผ ์ฌ๋ถ๋ฐฐ ๋ฅ๋ ฅ ๊ณ์ฐ.
|
| 78 |
+
3. **๊ฒฐ๊ณผ:** ํ์ฒ ์ฐ์ธ โ ์ผ๊ฐ์ฃผ(Delta), ์กฐ๋ฅ ์ฐ์ธ โ ์ผ๊ฐ๊ฐ(Estuary).
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
## 3. ์บ ํ์ธ ์๋๋ฆฌ์ค (Campaign Mode Missions)
|
| 83 |
+
|
| 84 |
+
ํ์๋ค์ด ์งํ ํ์ฑ ์๏ฟฝ๏ฟฝ๏ฟฝ๋ฅผ ๊ฒ์์ฒ๋ผ ์ตํ ์ ์๋ ๋ฏธ์
๋ชฉ๋ก.
|
| 85 |
+
|
| 86 |
+
| ์๋๋ฆฌ์ค (Title) | ๋ชฉํ (Mission Objective) | ํ์ ๋ชจ๋ ์กฐํฉ | ๋์ด๋ |
|
| 87 |
+
| :--- | :--- | :--- | :--- |
|
| 88 |
+
| **๋ถ๊ณผ ์ผ์์ ๋
ธ๋** | ํ์ฐ ํญ๋ฐ๋ก ์๊ธด ์ฐ ์ ์์ ๋ง๋
์ค๊ณผ ๋นํ๋ฅผ ํ์ฑํ๋ผ. (์์ด์ฌ๋๋, ํฌ๋ฆฌ๋ง์๋ก ํ) | **ํ์ฐ + ๋นํ** | โญโญโญ |
|
| 89 |
+
| **์ฌ๋ง์ ๊ธฐ์ ** | ๊ฑด์กฐํ ์ฌ๋ง์ ์ธ๋ ํ์ฒ์ ํ๋ฅด๊ฒ ํ์ฌ ํ๊ตฌ์ ๊ฑฐ๋ ์ผ๊ฐ์ฃผ๋ฅผ ๊ฑด์คํ๋ผ. (์ด์งํธ ๋์ผ๊ฐ ํ) | **๊ฑด์กฐ + ํ์ผ** | โญโญโญโญ |
|
| 90 |
+
| **์๊ฐ์ ๋๊ตด** | ์ต๊ธฐ๋ ์ฐ์ง๋ฅผ ์นจ์์์ผ ์งํ์๋ฉด์ ๋ฎ์ถ๊ณ , ์ํ๋๊ตด์ ์ก์์ ๋
ธ์ถ์์ผ๋ผ. (๋จ์ ๊ณ ์๋๊ตด ํ) | **์ฐ์ง + ์นด๋ฅด์คํธ** | โญโญ |
|
| 91 |
+
| **๋ํ์ (The Flood)** | ๋นํ๊ธฐ๋ฅผ ๋๋ด๊ณ ํด์๋ฉด์ 100m ์์น์์ผ, U์๊ณก์ ํผ์ค๋ฅด๋ก, ํ๊ตฌ๋ฅผ ์ํธ๋ก ๋ง๋ค์ด๋ผ. | **๋นํ + ํ์ผ + ํด์** | โญโญโญโญโญ |
|
| 92 |
+
|
| 93 |
+
---
|
| 94 |
+
|
| 95 |
+
## 4. UI/UX: ๋ง์คํฐ ๋์๋ณด๋ (Master Dashboard Design)
|
| 96 |
+
|
| 97 |
+
### ๐ฅ๏ธ Viewport Layout
|
| 98 |
+
1. **Main Globe (Center):** 3D ์ง๊ตฌ๋ณธ ๋ทฐ. ํ ์คํฌ๋กค๋ก ์ฐ์ฃผ(์์ฑ ์ฌ์ง)์์ ์งํ(๋จ๋ฉด๋)๊น์ง ์ฌ๋ฆฌ์ค ์ค์ธ/์์.
|
| 99 |
+
2. **Control Deck (Bottom):** ํ์๋ผ์ธ ์ฌ๋ผ์ด๋. (๊ณ ์๋ $\leftrightarrow$ ํ์ธ). ์๊ฐ์ ๋๋ฆฌ๋ฉฐ ์งํ ๋ณํ ๊ด์ฐฐ.
|
| 100 |
+
3. **Mini-Map (Right-Top):** ํ์ฌ ์งํ์ ํ์ฑ ์๋ฆฌ(์์ฉ)๋ฅผ ์์ด์ฝ์ผ๋ก ํ์ (๐์ ์, ๐ฌ๏ธ๋ฐ๋, ๐๋ง๊ทธ๋ง, โ๏ธ๋นํ).
|
| 101 |
+
|
| 102 |
+
### ๐ Data Visualization
|
| 103 |
+
* **Cross-Section Overlay:** ์งํ๋ฉด ์๋ ์ง์ง ๊ตฌ์กฐ(์ต๊ณก, ๋จ์ธต, ์งํ์, ๋ง๊ทธ๋ง ๋ฐฉ)๋ฅผ ์์ค๋ ์ด์ฒ๋ผ ํฌ์.
|
| 104 |
+
* **Eco-System Status:** ํ์ฌ ์งํ์ ์ ํฉํ ๋์
(๋ฒผ๋์ฌ vs ๋ฐญ๋์ฌ vs ๋ชฉ์ถ) ๋ฐ ๊ฑฐ์ฃผ ์ ํฉ๋ ์ ์ ํ์.
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
## 5. ๋ฐ์ดํฐ ํต์ ๊ท๊ฒฉ (JSON Data Structure)
|
| 109 |
+
|
| 110 |
+
ํตํฉ ์์ง์ด ์ฒ๋ฆฌํ ์ต์ข
์๋ ์ํ ๋ฐ์ดํฐ ์์.
|
| 111 |
+
|
| 112 |
+
```json
|
| 113 |
+
{
|
| 114 |
+
"World_State": {
|
| 115 |
+
"Timestamp": "Epoch_Holocene_Early",
|
| 116 |
+
"Global_Temp": 14.5,
|
| 117 |
+
"Sea_Level_Base": 0
|
| 118 |
+
},
|
| 119 |
+
"Active_Regions": [
|
| 120 |
+
{
|
| 121 |
+
"Region_ID": "NE_Asia",
|
| 122 |
+
"Base_Rock": "Granite",
|
| 123 |
+
"Landforms": [
|
| 124 |
+
{"Type": "Mountain", "Age": "Old", "Feature": "Erosion_Dome"},
|
| 125 |
+
{"Type": "Plain", "Feature": "Alluvial_Fan", "Source": "Mountain_Runoff"}
|
| 126 |
+
],
|
| 127 |
+
"Climate_Effect": "Monsoon"
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
"Region_ID": "Pacific_Rim",
|
| 131 |
+
"Base_Rock": "Basalt",
|
| 132 |
+
"Landforms": [
|
| 133 |
+
{"Type": "Volcano", "Shape": "Shield", "Status": "Active"}
|
| 134 |
+
],
|
| 135 |
+
"Tectonic_Activity": "High"
|
| 136 |
+
}
|
| 137 |
+
]
|
| 138 |
+
}
|
01_River_Landforms_Spec.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ [Geo-Lab AI] ๊ฐ๋ฐ ๋ฐฑ์ (Development White Paper)
|
| 2 |
+
|
| 3 |
+
**Project Name:** Geo-Lab AI
|
| 4 |
+
**Version:** Final Release 1.0
|
| 5 |
+
**Module:** Fluvial Landforms (ํ์ฒ ์งํ)
|
| 6 |
+
**Target:** High School Geography ~ Undergraduate Geomorphology
|
| 7 |
+
**Director:** [User Name]
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. ๊ฐ๋ฐ ํ์ค ๋ฐ ์ ์ฝ์ฌํญ (Standard Protocols)
|
| 12 |
+
|
| 13 |
+
### ๐จ 1. ๋น์ฃผ์ผ ์คํ์ผ ๊ฐ์ด๋ (Visual Style: Schematic Education)
|
| 14 |
+
* **Tone:** Clean, Minimalist, Textbook-style 3D. (๊ตฐ๋๋๊ธฐ ์๋ ๊น๋ํ ๊ต๊ณผ์ ์ฝํ ์คํ์ผ)
|
| 15 |
+
* **Color Palette:**
|
| 16 |
+
* *Water:* ๋ฐํฌ๋ช
ํ ๋ฐ์ ํ๋ (๋ด๋ถ ์ ์ ํ๋ฆ ๊ฐ์ํ).
|
| 17 |
+
* *Sediment:* ์
์ ํฌ๊ธฐ๋ณ ์์ ๊ตฌ๋ถ (์๊ฐ=ํ์, ๋ชจ๋=๋
ธ๋, ์ ํ =๊ฐ์).
|
| 18 |
+
* *Bedrock:* ์ธต๋ฆฌ(Layer)๊ฐ ๋ช
ํํ ๋ณด์ด๋ ๋จ๋ฉด ํ
์ค์ฒ.
|
| 19 |
+
* **Key Feature:** ๋ชจ๋ ์งํ์ '์ผ์ดํฌ ์๋ฅด๋ฏ' ๋จ๋ฉด์ ๋ณด์ฌ์ฃผ๋ **Cutaway View**๊ฐ ๊ธฐ๋ณธ ์ง์๋์ด์ผ ํจ.
|
| 20 |
+
|
| 21 |
+
### ๐ ๏ธ 2. ๊ธฐ์ ์คํ (Tech Stack)
|
| 22 |
+
* **Backend:** Python 3.9+ (Libraries: `NumPy`, `SciPy`, `Taichi` for Physics).
|
| 23 |
+
* **Frontend:** WebGL via `Three.js` or `PyVista`.
|
| 24 |
+
* **App Framework:** `Streamlit` (Web-based Interface).
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## 1. ํ์ฒ ์งํ ๋ชจ๋ ์ธ๋ถ ๋ช
์ธ (Module Specifications)
|
| 29 |
+
|
| 30 |
+
### ๐ Chapter 1. ์๋ฅ (Upper Course): ํ๊ดด์ ๊ฐ์ฒ
|
| 31 |
+
**ํต์ฌ ๋ก์ง:** ํ๋ฐฉ ์นจ์(Vertical Erosion), ๋๋ถ ์นจ์(Headward Erosion), ์ฌ๋ฉด ๋ถ๊ดด.
|
| 32 |
+
|
| 33 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 34 |
+
| :--- | :--- | :--- | :--- |
|
| 35 |
+
| **V์๊ณก** | ์นจ์๋ ฅ($E$), ์์ ๊ฐ๋($K$), ๊ฒฝ์ฌ($S$) | ๋ฐ๋ฅ ํ๋ฐฉ ์นจ์ ํ **์์ ์ฌ๋ฉด์ด ๋ถ๊ดด(Mass Wasting)**๋์ด V์๊ฐ ์์ฑ๋๋ ๊ณผ์ ๊ฐ์กฐ. | $E = K \cdot A^m \cdot S^n$ (Stream Power Law) |
|
| 36 |
+
| **ํญํฌ & ํ๊ณก** | ์ ๋($Q$), ๋์ฐจ ๋์ด($h$) | ํ๋จ๋ถ ๊ตด์ฐฉ(Undercutting) โ ์๋จ ๋ถ๊ดด โ ํญํฌ๊ฐ ๋ค๋ก ๋ฌผ๋ฌ๋๋ฉฐ **ํ๊ณก(Gorge)** ํ์ฑ. | Crosby's Knickpoint Retreat Model |
|
| 37 |
+
| **ํ์ฒ ์ํ** | ์นจ์ ์๋ ์ฐจ์ด($\Delta E$), ๊ฑฐ๋ฆฌ | ํ์ชฝ ๊ฐ์ด ๋๋ถ ์นจ์์ผ๋ก ๋ค๋ฅธ ๊ฐ์ ์๊ตฌ๋ฆฌ๋ฅผ ํฐํธ๋ ค ๋ฌผ์ ๋บ์ด์ค๊ณ , ๋บ๊ธด ๊ฐ์ด ๋ง๋ผ๊ฐ๋ ๊ณผ์ . | Stream Capture Ratio |
|
| 38 |
+
| **๋๊ฐ๊ตฌ๋ฉ** | ์๋ฅ ๊ฐ๋(Vortex), ์๊ฐ ๊ฒฝ๋ | **[X-ray View]** ๊ฐ๋ฐ๋ฅ ํฌ์. ์๊ฐ์ด ์์ฉ๋์ด์น๋ฉฐ ์๋ฐ์ ๋๋ฆด์ฒ๋ผ ๋ซ๋ ๋ด๋ถ ๋ชจ์ต. | Abrasion Rate $\propto$ Kinetic Energy |
|
| 39 |
+
| **๊ฐ์
๊ณก๋ฅ (์๋ฅํ)** | ์์ ์ ํญ์ฑ, ์ต๋จ ๊ฒฝ๋ก | ๋จ๋จํ ์๋ฐ์ ํผํด ๊ตฝ์ด์น๋ฉฐ ํ๋ฅด๋ ๋ฌผ๊ธธ๊ณผ ๊น์ง ๋ ๋ฅ์ (Interlocking Spurs). | - |
|
| 40 |
+
|
| 41 |
+
### โฐ Chapter 2. ์ค๋ฅ (Middle Course): ์ด๋ฐ๊ณผ ๊ณก๋ฅ
|
| 42 |
+
**ํต์ฌ ๋ก์ง:** ์ธก๋ฐฉ ์นจ์(Lateral Erosion), ๋ถ๊ธ(Sorting), ์ ์ ์ฐจ์ด์ ์ํ ํด์ .
|
| 43 |
+
|
| 44 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 45 |
+
| :--- | :--- | :--- | :--- |
|
| 46 |
+
| **์ ์์ง** | ์
์ ํฌ๊ธฐ($d$), ์ ์ ๊ฐ์์จ($-\Delta v$) | ์ ์ (์๊ฐ)-์ ์(๋ชจ๋)-์ ๋จ(์ ํ ) ๋ถ๊ธ ๋ฐ **๋ณต๋ฅ์ฒ(์งํ์ ํ๋ฆ)** ๋จ๋ฉด๋. | Stokes' Law (Settling Velocity) |
|
| 47 |
+
| **๊ณก๋ฅ & ์ฐ๊ฐํธ** | ์์ฌ๋ ฅ($F_c$), ์ ๋ก ๊ตด๊ณก๋ | ๊ณต๊ฒฉ์ฌ๋ฉด(์ ๋ฒฝ) vs ํด์ ์ฌ๋ฉด(๋ฐฑ์ฌ์ฅ). ํ์ ์ ์ ๋ก ์ ๋จ(Cutoff) ๋ฐ ์ฐ๊ฐํธ ๊ณ ๋ฆฝ ์ ๋๋ฉ์ด์
. | Helical Flow Model |
|
| 48 |
+
| **๊ฐ์
๊ณก๋ฅ (์ต๊ธฐํ)** | ์ต๊ธฐ ์๋($U$), ๊ธฐ์กด ๊ตด๊ณก๋ | **[Comparison Mode]** ์์ ๊ณก๋ฅ(์ ์ํ ๋จ๋ฉด) vs ๊ฐ์
๊ณก๋ฅ(๋ฐฅ๊ทธ๋ฆํ ๊น์ ๋จ๋ฉด) ๋น๊ต. | Incision Rate = Erosion - Uplift |
|
| 49 |
+
| **ํ์๋จ๊ตฌ** | ์ง๋ฐ ์ต๊ธฐ($U$), ๊ธฐ์ ๋ฉด ๋ณ๋ | ์ต๊ธฐ ์ด๋ฒคํธ ๋ฐ์ ์ ๊ฐ๋ฐ๋ฅ์ด ๊น์ด์ง๋ฉฐ ๋ฒ๋์์ด ๊ณ๋จ ๋ชจ์ ์ธ๋(Terrace)์ผ๋ก ๋จ์. | - |
|
| 50 |
+
| **๋ฒ๋์** | ํ์ ๋น๋, ๋ถ์ ํ์ค | ํ์ ์ ์ ๋ฐฉ(๋๊บผ์/๋ชจ๋)๊ณผ ๋ฐฐํ์ต์ง(์์/์ ํ )์ ๊ฑฐ๋ฆฌ๋ณ ํด์ ์ฐจ์ด. | Overbank Sedimentation |
|
| 51 |
+
|
| 52 |
+
### โ Chapter 3. ํ๋ฅ (Lower Course): ๋ฐ๋ค์์ ๊ท ํ
|
| 53 |
+
**ํต์ฌ ๋ก์ง:** ์ ์ ์๋ฉธ, ํ๋/์กฐ๋ฅ ์๋์ง ์ํธ์์ฉ.
|
| 54 |
+
|
| 55 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 56 |
+
| :--- | :--- | :--- | :--- |
|
| 57 |
+
| **์ผ๊ฐ์ฃผ** | ๊ฐ ์๋์ง vs ํ๋ vs ์กฐ๋ฅ | **[Triangle Mixer UI]** 3๊ฐ์ง ํ ์กฐ์ โ ์กฐ์กฑ์(๋ฏธ์์ํผ) โ ์ํธ์(๋์ผ) ๋ชจ์ ๋ณํ. | Galloway's Classification |
|
| 58 |
+
| **์ผ๊ฐ๊ฐ (Estuary)** | ์กฐ์ฐจ(Tidal Range), ์กฐ๋ฅ ์๋ | ๊ฐํ ์กฐ๋ฅ๊ฐ ํด์ ๋ฌผ์ ์ธ์ด๊ฐ๋ฉฐ ํ๊ตฌ๊ฐ ๋ํ ๋ชจ์์ผ๋ก ํ์ฅ๋๋ ๊ณผ์ . | Tidal Scour Equation |
|
| 59 |
+
| **ํ์ค๋** | ์ ์ ์ฌ๊ฐ์ง๋, ์์ ๋ฐ๋ | ๊ฐํญ์ด ๋์ ๊ณณ์ ๋ชจ๋์ฌ ํ์ฑ ํ ์์์ด ์๋ผ๋ฉฐ ๊ณ ์ ๋๋ ๊ณผ์ . | Vegetation Stabilization |
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
## 2. ์ ๋ต์ง ์ด๋ฏธ์ง ๋ชฉ๋ก (Reference Images to Load)
|
| 64 |
+
*AI ํ์ต ์ ์๋ ํ์ผ๋ช
๊ณผ ๋งค์นญ๋๋ ์ด๋ฏธ์ง๋ฅผ ํจ๊ป ์
๋ก๋ํ ๊ฒ.*
|
| 65 |
+
|
| 66 |
+
1. `v_shaped_valley_diagram.jpg` (V์๊ณก ๋ฐ ์ฌ๋ฉด ๋ถ๊ดด ๋จ๋ฉด)
|
| 67 |
+
2. `alluvial_fan_structure.jpg` (์ ์์ง ํ๋ฉด ๋ฐ ์งํ ๋จ๋ฉด)
|
| 68 |
+
3. `meander_cross_section.jpg` (๊ณต๊ฒฉ์ฌ๋ฉด/ํด์ ์ฌ๋ฉด ๋น๋์นญ ๋จ๋ฉด)
|
| 69 |
+
4. `river_terrace_formation.jpg` (ํ์๋จ๊ตฌ ํ์ฑ ๋จ๊ณ)
|
| 70 |
+
5. `delta_classification.jpg` (์ผ๊ฐ์ฃผ 3๊ฐ์ง ์ ํ ๋น๊ต)
|
02_Coastal_Landforms_Spec.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ [Geo-Lab AI] ๊ฐ๋ฐ ๋ฐฑ์ (Development White Paper)
|
| 2 |
+
|
| 3 |
+
**Project Name:** Geo-Lab AI
|
| 4 |
+
**Version:** Final Release 1.0
|
| 5 |
+
**Module:** Coastal Landforms (ํด์ ์งํ)
|
| 6 |
+
**Target:** High School Geography ~ Undergraduate Geomorphology
|
| 7 |
+
**Director:** [User Name]
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. ๊ฐ๋ฐ ํ์ค ๋ฐ ์ ์ฝ์ฌํญ (Standard Protocols)
|
| 12 |
+
|
| 13 |
+
### ๐จ 1. ๋น์ฃผ์ผ ์คํ์ผ ๊ฐ์ด๋ (Visual Style: Schematic Education)
|
| 14 |
+
* **Tone:** Clean, Minimalist, Textbook-style 3D.
|
| 15 |
+
* **Color Palette:**
|
| 16 |
+
* *Sea:* ๊น์ด์ ๋ฐ๋ฅธ ํ๋์ ๊ทธ๋ผ๋ฐ์ด์
(์์ ๊ณณ=ํ๋์, ๊น์ ๊ณณ=๋จ์).
|
| 17 |
+
* *Sand:* ๋ฐ์ ๋ฒ ์ด์ง์ (์ด๋ ๊ฒฝ๋ก๊ฐ ์ ๋ณด์ฌ์ผ ํจ).
|
| 18 |
+
* *Rocks:* ์ง์ ํ์/๊ฐ์ (์ ๋ฒฝ์ ์ง๊ฐ ํํ).
|
| 19 |
+
* **Key Feature:** ํ๋์ ๊ตด์ ๊ณผ ๋ชจ๋์ ์ด๋ ๊ฒฝ๋ก(Vector Arrow)๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์.
|
| 20 |
+
|
| 21 |
+
### ๐ ๏ธ 2. ๊ธฐ์ ์คํ (Tech Stack)
|
| 22 |
+
* **Backend:** Python 3.9+ (Libraries: `NumPy`, `SciPy`, `Taichi`).
|
| 23 |
+
* **Frontend:** WebGL via `Three.js` or `PyVista`.
|
| 24 |
+
* **App Framework:** `Streamlit`.
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## 1. ํด์ ์งํ ๋ชจ๋ ์ธ๋ถ ๋ช
์ธ (Module Specifications)
|
| 29 |
+
|
| 30 |
+
### ๐ Chapter 1. ๊ณถ(Headland)๊ณผ ์นจ์: ํ๋์ ๊ณต๊ฒฉ
|
| 31 |
+
**ํต์ฌ ๋ก์ง:** **ํ๋์ ๊ตด์ (Wave Refraction)**. ํ๋์ ์๋์ง๊ฐ ํ์ด๋์จ ๋
(๊ณถ)์ผ๋ก ์ง์ค๋จ.
|
| 32 |
+
|
| 33 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 34 |
+
| :--- | :--- | :--- | :--- |
|
| 35 |
+
| **ํด์์ & ํ์๋** | ํ๋ ์๋์ง($P$), ์์ ๊ฒฝ๋ | ํ๋๊ฐ ์ ๋ฒฝ ๋ฐ์ ๋๋ ค(Notch) ์๋ถ๋ถ์ด ๋ฌด๋์ง๊ณ , ์ ๋ฒฝ์ด ํํดํ๋ฉฐ ํํํ ๋ฐ๋ฅ(ํ์๋) ๋
ธ์ถ. | Cliff Retreat Rate $\propto P$ ($P \propto H^2T$) |
|
| 36 |
+
| **์์คํ & ์์น** | ์ฐจ๋ณ ์นจ์, ์๊ฐ($t$) | **[Evolution Time-lapse]** ํด์๋๊ตด โ ์์น(๊ตฌ๋ฉ) โ ์์คํ(๊ธฐ๋ฅ) โ ์์คํ
ํ(๋ฐ์) 4๋จ๊ณ ๋ณํ. | - |
|
| 37 |
+
| **ํด์๋จ๊ตฌ** | ์ง๋ฐ ์ต๊ธฐ($U$), ํด์๋ฉด ๋ณ๋ | ํ์๋๊ฐ ์ต๊ธฐํ์ฌ ๊ณ๋จ ๋ชจ์์ ์ธ๋์ด ๋๋ ๊ณผ์ (๊ฐ์ ํ์๋จ๊ตฌ์ ๋น๊ต). | Uplift Event Trigger |
|
| 38 |
+
|
| 39 |
+
### ๐๏ธ Chapter 2. ๋ง(Bay)๊ณผ ํด์ : ๋ชจ๋์ ์ฌํ
|
| 40 |
+
**ํต์ฌ ๋ก์ง:** **์ฐ์๋ฅ (Longshore Drift)**. ํ๋๊ฐ ๋น์ค๋ฌํ ์น ๋ ๋ชจ๋๊ฐ ์ง๊ทธ์ฌ๊ทธ๋ก ์ด๋.
|
| 41 |
+
|
| 42 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 43 |
+
| :--- | :--- | :--- | :--- |
|
| 44 |
+
| **์ฌ๋น (Beach)** | ํ๋ ์
์ฌ๊ฐ($\theta$), ์
์ ํฌ๊ธฐ | ๋ง(Bay) ์์ชฝ์ผ๋ก ์๋์ง๊ฐ ๋ถ์ฐ๋๋ฉฐ ๋ชจ๋๊ฐ ์์. ์ฌ๋ฆ(ํด์ ) vs ๊ฒจ์ธ(์นจ์) ํด๋ณ ๋น๊ต. | Sediment Transport (CERC Formula) |
|
| 45 |
+
| **์ฌ๊ตฌ (Sand Dune)** | ํ์($V_{wind}$), ์์ ๋ฐ๋ | ์ฌ๋น์ ๋ชจ๋๊ฐ ๋ฐ๋์ ๋ ๋ ค ์ด๋. **[Windbreak Effect]** ์์ ์ค์น ์ ์ฌ๊ตฌ ์ฑ์ฅ ์๋ฎฌ๋ ์ด์
. | Aeolian Transport Rate |
|
| 46 |
+
| **์ฌ์ทจ & ์ฌ์ฃผ** | ์ฐ์๋ฅ ์๋, ํด์์ ๊ตด๊ณก | ๋ชจ๋๊ฐ ๋ฐ๋ค ์ชฝ์ผ๋ก ๋ป์ด ๋๊ฐ๋ฉฐ(์ฌ์ทจ) ๋ง์ ์
๊ตฌ๋ฅผ ๋ง์๋ฒ๋ฆฌ๋(์ฌ์ฃผ) ๊ณผ์ . | - |
|
| 47 |
+
| **์ํธ (Lagoon)** | ์ฌ์ฃผ ํ์ฑ ์ฌ๋ถ, ์ ์
์๋ | ์ฌ์ฃผ๊ฐ ๋ฐ๋ค๋ฅผ ๋ง์ ํธ์๊ฐ ๋๊ณ , ์๊ฐ์ด ์ง๋๋ฉฐ ํด์ ๋ฌผ๋ก ๋ฉ์์ ธ ๋ช์ด ๋๋ ์์ ์ฃผ๊ธฐ. | Water Balance Equation |
|
| 48 |
+
|
| 49 |
+
### ๐ฆ Chapter 3. ์กฐ๋ฅ์ ๊ฐฏ๋ฒ: ๋ฌ์ ์ธ๋ ฅ
|
| 50 |
+
**ํต์ฌ ๋ก์ง:** **์กฐ์ ์ฃผ๊ธฐ (Tidal Cycle)** & ๋ฏธ๋ฆฝ์ง ํด์ .
|
| 51 |
+
|
| 52 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 53 |
+
| :--- | :--- | :--- | :--- |
|
| 54 |
+
| **๊ฐฏ๋ฒ (Mudflat)** | ์กฐ์ฐจ(Tidal Range), ๋ฏธ๋ฆฝ์ง ๋น์จ | **[Tidal Simulation]** ๋ฐ๋ฌผ/์ฐ๋ฌผ ์์ ๋ณํ์ ๊ฐฏ๊ณจ(Tidal Channel)์ ํ๋ํ ๊ตฌ์กฐ ํ์ฑ. | Tidal Prism / Hydrodynamics |
|
| 55 |
+
| **๊ฐ์ฒ ์ฌ์
** | ์ ๋ฐฉ ๊ฑด์ค, ์๊ฐ ๊ฒฝ๊ณผ | ๊ฐฏ๋ฒ์ ๋์ ์์ ํ(User Action), ํด์ ๋ฌผ์ด ๋ง๋ฅด๊ณ ์ก์งํ๋๋ฉฐ ์ผ๋ถ์ด ๋น ์ง๋ ๊ณผ์ . | Soil Desalination Model |
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## 2. ์ ๋ต์ง ์ด๋ฏธ์ง ๋ชฉ๋ก (Reference Images to Load)
|
| 60 |
+
*AI ํ์ต ์ ์๋ ํ์ผ๋ช
๊ณผ ๋งค์นญ๋๋ ์ด๋ฏธ์ง๋ฅผ ํจ๊ป ์
๋ก๋ํ ๊ฒ.*
|
| 61 |
+
|
| 62 |
+
1. `sea_cliff_erosion.jpg` (ํด์์ , ํ์๋, ๋
ธ์น ๊ตฌ์กฐ ๋จ๋ฉด)
|
| 63 |
+
2. `sea_stack_formation.jpg` (๋๊ตด-์์น-์์คํ ๋ณํ ๊ณผ์ )
|
| 64 |
+
3. `sand_spit_bar_tombolo.jpg` (์ฌ์ทจ, ์ฌ์ฃผ, ์ํธ ์ง๋ํ ๊ทธ๋ฆผ)
|
| 65 |
+
4. `tidal_flat_zonation.jpg` (๊ฐฏ๋ฒ ๋จ๋ฉด ๋ฐ ์กฐ์ ์์)
|
03_Karst_Landforms_Spec.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ [Geo-Lab AI] ๊ฐ๋ฐ ๋ฐฑ์ (Development White Paper)
|
| 2 |
+
|
| 3 |
+
**Project Name:** Geo-Lab AI
|
| 4 |
+
**Version:** Final Release 1.0
|
| 5 |
+
**Module:** Karst Landforms (์นด๋ฅด์คํธ ์งํ)
|
| 6 |
+
**Target:** High School Geography ~ Undergraduate Geology
|
| 7 |
+
**Director:** [User Name]
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. ๊ฐ๋ฐ ํ์ค ๋ฐ ์ ์ฝ์ฌํญ (Standard Protocols)
|
| 12 |
+
|
| 13 |
+
### ๐จ 1. ๋น์ฃผ์ผ ์คํ์ผ ๊ฐ์ด๋ (Visual Style: Subsurface & Texture)
|
| 14 |
+
* **Tone:** Cross-sectional, Chemically Reactive, Hydrological.
|
| 15 |
+
* **Color Palette:**
|
| 16 |
+
* *Limestone:* ๋ฐ์ ํ๋ฐฑ์ (๊ธฐ๋ฐ์).
|
| 17 |
+
* *Soil (Terra Rossa):* ๋ถ์ ๊ฐ์ (์ฐํ์ฒ ์ฑ๋ถ ๊ฐ์กฐ).
|
| 18 |
+
* *Water:* ํฌ๋ช
ํ ์ฒญ๋ก์ (์งํ์ ์์ฃผ).
|
| 19 |
+
* *Vegetation:* ์ง์ ๋
น์ (๋๋ฆฌ๋ค ๋ด๋ถ ์์).
|
| 20 |
+
* **Key Feature:** ์งํ๋ฉด(Surface)๊ณผ ์งํ(Subsurface)๋ฅผ ๋์์ ๋ณด์ฌ์ฃผ๋ **์ด์ค ๋ ์ด์ด(Dual-Layer) ๋ทฐ** ์ง์. ์ฉ์ ๋ฐ์ ์ ๊ฑฐํ(Reaction Bubble) ์ดํํธ ์ ์ฉ.
|
| 21 |
+
|
| 22 |
+
### ๐ ๏ธ 2. ๊ธฐ์ ์คํ (Tech Stack)
|
| 23 |
+
* **Core Engine:** Chemical Weathering Simulator based on $pH$ & $Temperature$.
|
| 24 |
+
* **Rendering:** Voxel-based Terrain (๋๊ตด ํ์ฑ์ ์ํ ๋ด๋ถ ๊ตด์ฐฉ ํํ).
|
| 25 |
+
* **Physics:** Fluid Dynamics (์งํ์ ํ๋ฆ ๋ฐ ์นจํฌ).
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## 1. ์นด๋ฅด์คํธ ์งํ ๋ชจ๋ ์ธ๋ถ ๋ช
์ธ (Module Specifications)
|
| 30 |
+
|
| 31 |
+
### ๐ง Chapter 1. ์งํ ์นด๋ฅด์คํธ: ๋ฌผ์ด ์กฐ๊ฐํ ๋์ง
|
| 32 |
+
**ํต์ฌ ๋ก์ง:** **ํ์ฐ์นผ์ ์ฉ์ ์์ฉ (Dissolution)**. ๋น๋ฌผ์ด ์ํ์์ ๋
น์ฌ ์งํ๋ฉด์ด ํจ๋ชฐ๋จ.
|
| 33 |
+
|
| 34 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 35 |
+
| :--- | :--- | :--- | :--- |
|
| 36 |
+
| **์ฉ์ ๋ฉ์ปค๋์ฆ** | ๋น๋ฌผ ์ฐ๋($pH$), ํ์ฐ๊ฐ์ค ๋๋ | ๋น๋ฌผ์ด ๋ฟ๋ ์ฆ์ ์์ ํ๋ฉด์ด ๋
น์๋ด๋ฆฌ๋ **[Shader Effect]**. ํํ์ ์ค๋ฒ๋ ์ด. | $CaCO_3 + H_2O + CO_2 \leftrightarrow Ca(HCO_3)_2$ |
|
| 37 |
+
| **๋๋ฆฌ๋ค (Doline)** | ์ ๋ฆฌ ๋ฐ๋, ์๊ฐ($t$) | ์งํ๋ฉด์ ๋น๋ฌผ์ด ๊ณ ์ด๋ค๊ฐ ๋ฐฐ์๊ตฌ์ฒ๋ผ ๋น ์ง๋ฉฐ ์ํ์ผ๋ก ์ํน ๊บผ์ง๋ ์ ๋๋ฉ์ด์
. | Sinkhole Radius $R(t)$ |
|
| 38 |
+
| **์ฐ๋ฐ๋ผ & ํด๋ฆฌ์** | ๋๋ฆฌ๋ค ๊ฒฐํฉ ํ๋ฅ , ๊ธฐ๋ฐ์ ๊น์ด | ์ธ์ ํ ์ฌ๋ฌ ๊ฐ์ ๋๋ฆฌ๋ค๊ฐ ํฉ์ณ์ ธ ๊ฑฐ๋ ๋ถ์ง(์ฐ๋ฐ๋ผ)๊ฐ ๋๊ณ , ํํํ ๋คํ(ํด๋ฆฌ์)์ด ๋๋ ๊ณผ์ . | Merge Function $f(D_1, D_2)$ |
|
| 39 |
+
| **ํ
๋ผ๋ก์ฌ (Terra Rossa)** | ๋ถ์ฉ์ฑ ์๋ฅ๋ฌผ ๋น์จ | ์ํ์์ด ๋
น๊ณ ๋จ์ ๋ถ์ ํ์ด ์งํ๋ฉด์ ๋ฎ๋ ํ
์ค์ฒ ๋งคํ ๋ณํ (White โ Red). | Residue Accumulation |
|
| 40 |
+
|
| 41 |
+
### ๐ฆ Chapter 2. ์งํ ์นด๋ฅด์คํธ: ์ด๋ ์์ ์์
|
| 42 |
+
**ํต์ฌ ๋ก์ง:** **์นจ์ ์์ฉ (Precipitation)**. ๋
น์๋ ์ํ์ง์ด ๋ค์ ๊ตณ์ด์ง.
|
| 43 |
+
|
| 44 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 45 |
+
| :--- | :--- | :--- | :--- |
|
| 46 |
+
| **์ํ ๋๊ตด** | ์งํ์ ์ ๋, ์์ ๋ณํ | **[X-Ray Mode]** ์งํ์๋ฉด ์๋์์ ์ํ์์ด ๋
น์ ๋น ๊ณต๊ฐ(Cavity)์ด ํ์ฅ๋๋ ๊ณผ์ . | Cave Volume Expansion |
|
| 47 |
+
| **์ข
์ ์ & ์์** | ๋์ ์๋($V_{drip}$), ์ฆ๋ฐ๋ฅ | ์ฒ์ฅ์์ ๋ฌผ๋ฐฉ์ธ์ด ๋จ์ด์ง๋ฉฐ ๊ณ ๋๋ฆ(์ข
์ ์)์ด ์๋ผ๊ณ , ๋ฐ๋ฅ์์ ์ฃฝ์(์์)์ด ์์์ค๋ฆ. | Growth Rate $\approx mm/year$ |
|
| 48 |
+
| **์์ฃผ (Column)** | ์ฑ์ฅ ์๋, ๋๊ตด ๋์ด($H$) | ์(์ข
์ ์)์ ์๋(์์)๊ฐ ๋ง๋ ๊ธฐ๋ฅ์ผ๋ก ์ฐ๊ฒฐ๋๋ ์๊ฐ **[Highlight Effect]**. | Connection Event |
|
| 49 |
+
|
| 50 |
+
### ๐๏ธ Chapter 3. ํ ์นด๋ฅด์คํธ: ์ด๋์ ๊ธฐ์๊ดด์ (Bonus)
|
| 51 |
+
**ํต์ฌ ๋ก์ง:** **๊ณ ์จ ๋ค์ต & ์ฐจ๋ณ ์นจ์**.
|
| 52 |
+
|
| 53 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 54 |
+
| :--- | :--- | :--- | :--- |
|
| 55 |
+
| **ํ ์นด๋ฅด์คํธ** | ๊ฐ์๋($P_{high}$), ๊ธฐ์จ($T_{high}$) | ํ์ง์ ํ์ฒ๋ผ ์์ ๋ด์ฐ๋ฆฌ ํ์ฑ (๋ฒ ํธ๋จ ํ๋กฑ๋ฒ ์ด, ์ค๊ตญ ๊ตฌ์ด๋ฆฐ ์คํ์ผ). ์ธก๋ฉด ์นจ์ ๊ฐ์กฐ. | Tropical Erosion Rate |
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## 2. ์ ๋ต์ง ์ด๋ฏธ์ง ๋ชฉ๋ก (Reference Images to Load)
|
| 60 |
+
*AI ๋ชจ๋ธ ํ์ต ๋ฐ UI ๋ ๋๋ง ์ ๋งค์นญํ ๋ ํผ๋ฐ์ค ๋ฐ์ดํฐ.*
|
| 61 |
+
|
| 62 |
+
1. `chemical_weathering_reaction.png` (ํ์ฐ์นผ์ ์ฉ์ ํํ์ ๋ฐ ๋ชจ์๋)
|
| 63 |
+
2. `doline_uvala_polje_progression.jpg` (๋๋ฆฌ๋ค์์ ํด๋ฆฌ์๋ก ์ปค์ง๋ ๋จ๊ณ๋ณ ์ง๋)
|
| 64 |
+
3. `cave_interior_structure.jpg` (์ข
์ ์, ์์, ์์ฃผ๊ฐ ์๋ ๋๊ตด ๋ด๋ถ ๋จ๋ฉด๋)
|
| 65 |
+
4. `tower_karst_landscape.jpg` (ํ ์นด๋ฅด์คํธ์ ์งํ์ ํน์ง)
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
**[Director's Note]**
|
| 70 |
+
์นด๋ฅด์คํธ ๋ชจ๋์ **'ํํ ๋ฐ์'**์ด ์งํ์ ์ด๋ป๊ฒ ๋ฐ๊พธ๋์ง ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ํต์ฌ์
๋๋ค. $CaCO_3$ ํํ์์ด ์๋ฎฌ๋ ์ด์
๋ด์์ ํธ๋ฆฌ๊ฑฐ๋ก ์๋ํ๋๋ก ๋ก์ง์ ์ ๊ฒํ์ญ์์ค.
|
| 71 |
+
์น์ธํ์๋ฉด ๋ค์ **'ํ์ฐ ์งํ'**์ผ๋ก ๋์ด๊ฐ๊ฒ ์ต๋๋ค.
|
04_Volcanic_Landforms_Spec.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ [Geo-Lab AI] ๊ฐ๋ฐ ๋ฐฑ์ (Development White Paper)
|
| 2 |
+
|
| 3 |
+
**Project Name:** Geo-Lab AI
|
| 4 |
+
**Version:** Final Release 1.0
|
| 5 |
+
**Module:** Volcanic Landforms (ํ์ฐ ์งํ)
|
| 6 |
+
**Target:** High School Geography ~ Undergraduate Volcanology
|
| 7 |
+
**Director:** [User Name]
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. ๊ฐ๋ฐ ํ์ค ๋ฐ ์ ์ฝ์ฌํญ (Standard Protocols)
|
| 12 |
+
|
| 13 |
+
### ๐จ 1. ๋น์ฃผ์ผ ์คํ์ผ ๊ฐ์ด๋ (Visual Style: Magmatic & Explosive)
|
| 14 |
+
* **Tone:** Dynamic, Thermal-coded, Catastrophic.
|
| 15 |
+
* **Color Palette:**
|
| 16 |
+
* *Magma/Lava:* ์จ๋์ ๋ฐ๋ฅธ Black Body Radiation ์์ (White Hot โ Yellow โ Red โ Dark Crust).
|
| 17 |
+
* *Ash/Rock:* ํ๋ฌด์(๊ฒ์์) vs ์กฐ๋ฉด์/์์ฐ์(ํ๋ฐฑ์) ๊ตฌ๋ถ.
|
| 18 |
+
* *Terrain:* ๊ตณ์ ์ฉ์ ์ 1์ฐจ ์ฒ์ด ์์(์ด๋ผ, ๋ค๋ถ)์ ๋ฌ์ฑ๋ฌ์ฑํ ๋
น์.
|
| 19 |
+
* **Key Feature:** ๋ง๊ทธ๋ง์ **$SiO_2$ ํจ๋(์ด์ฐํ๊ท์)**์ ๋ฐ๋ฅธ ์ ์ฑ ๋ณํ๋ฅผ ์ฌ๋ผ์ด๋(Slider)๋ก ์กฐ์ ํ๋ฉด ํ์ฐ์ ๋ชจ์์ด ์ค์๊ฐ์ผ๋ก ๋ณํ๋๋ **[Morphing Terrain]** ๊ตฌํ.
|
| 20 |
+
|
| 21 |
+
### ๐ ๏ธ 2. ๊ธฐ์ ์คํ (Tech Stack)
|
| 22 |
+
* **Physics Engine:** SPH (Smoothed-Particle Hydrodynamics) for Lava Flow Simulation.
|
| 23 |
+
* **Rendering:** Volumetric Fog (ํ์ฐ์ฌ ๋ฐ ์ฐ๊ธฐ ํํ).
|
| 24 |
+
* **Algorithm:** Cooling Crystallization Model (์ฉ์ ๋๊ฐ ์๋์ ๋ฐ๋ฅธ ์ ๋ฆฌ ํ์ฑ ๊ณ์ฐ).
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## 1. ํ์ฐ ์งํ ๋ชจ๋ ์ธ๋ถ ๋ช
์ธ (Module Specifications)
|
| 29 |
+
|
| 30 |
+
### ๐ Chapter 1. ๋ง๊ทธ๋ง์ ์ฑ๊ฒฉ: ์ ์ฑ(Viscosity)๊ณผ ํ์ฐ์ฒด
|
| 31 |
+
**ํต์ฌ ๋ก์ง:** **$SiO_2$ ํจ๋ $\propto$ ์ ์ฑ $\propto$ ํ์ฐ ๊ฒฝ์ฌ๋**.
|
| 32 |
+
|
| 33 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 34 |
+
| :--- | :--- | :--- | :--- |
|
| 35 |
+
| **์์ ํ์ฐ (Shield)** | ์ ์ฑ($\mu_{low}$), ์จ๋($T_{high}$), ์ ๋์ฑ(High) | ๊ฟ์ฒ๋ผ ๋ฌฝ์ ์ฉ์์ด ๋๊ฒ ํผ์ ธ๋๊ฐ๋ฉฐ ์๋งํ ๊ฒฝ์ฌ(๋ฐฉํจ ๋ชจ์) ํ์ฑ. (์: ํ์์ด, ์ ์ฃผ๋ ํ๋ผ์ฐ ์ฐ๋ก) | Slope Angle $\theta \approx 2^{\circ} \sim 10^{\circ}$ |
|
| 36 |
+
| **์ข
์ ํ์ฐ (Lava Dome)** | ์ ์ฑ($\mu_{high}$), ์จ๋($T_{low}$), ์ ๋์ฑ(Low) | ์น์ฝ์ฒ๋ผ ๋ ์ฉ์์ด ๋ถํ๊ตฌ ์๋ก ์๊ตฌ์ณ ์ข
๋ชจ์ ํ์ฑ. (์: ์ ์ฃผ๋ ์ฐ๋ฐฉ์ฐ, ์ธ๋ฆ๋ ๋๋ฆฌ๋ถ์ง ๋ด ์๋ด) | Slope Angle $\theta > 30^{\circ}$ |
|
| 37 |
+
| **์ฑ์ธต ํ์ฐ (Stratovolcano)** | ๋ถ์ถ ์ฃผ๊ธฐ, ํญ๋ฐ์ฑ | ์ฉ์ ๋ถ์ถ(Flow)๊ณผ ํ์ฐ์ฌ ํญ๋ฐ(Explosion)์ด ๊ต๋๋ก ์ผ์ด๋ ์ธต์ธต์ด ์์ธ ์๋ฟํ ๊ตฌ์กฐ. (์: ํ์ง์ฐ, ํ๋ฆฌํ ๋ง์) | Layering Index |
|
| 38 |
+
| **์ฉ์ ๋์ง (Lava Plateau)** | ์ดํ ๋ถ์ถ(Fissure), $SiO_2$ < 52% | ์ง๊ฐ์ ํ์์ ๋ฌฝ์ ์ฉ์์ด ๋๋์ผ๋ก ํ๋ฌ๋์ ๊ธฐ์กด ๊ณ๊ณก์ ๋ฉ์ฐ๊ณ ํํ๋ฉด ํ์ฑ. (์: ์ฒ ์, ๊ฐ๋ง๊ณ ์) | Area Coverage Rate |
|
| 39 |
+
|
| 40 |
+
### ๐ฅ Chapter 2. ๋ถํ๊ตฌ์ ๋ณํ: ํจ๋ชฐ๊ณผ ํ์ฅ
|
| 41 |
+
**ํต์ฌ ๋ก์ง:** **์ง๋ ๊ฒฐ์(Mass Deficit)์ ์ํ ๋ถ๊ดด**.
|
| 42 |
+
|
| 43 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 44 |
+
| :--- | :--- | :--- | :--- |
|
| 45 |
+
| **ํ๊ตฌ (Crater)** | ํญ๋ฐ ์๋์ง($E_{exp}$) | ๋ง๊ทธ๋ง๊ฐ ๋ถ์ถ๋ ๋จ์ํ ๊ตฌ๋ฉ. | Diameter $D < 1km$ |
|
| 46 |
+
| **์นผ๋ฐ๋ผ (Caldera)** | ๋ง๊ทธ๋ง ๋ฐฉ ๊ณต๋ํ ๋น์จ, ์ง๋ฐ ํ์ค | **[Collapse Event]** ๋ํญ๋ฐ ํ ์งํ ๋ง๊ทธ๋ง ๋ฐฉ์ด ๋น๋ฉด์ ์ฐ์ ๋ถ๊ฐ ์๋ฅด๋ฅด ๋ฌด๋์ ธ ๋ด๋ฆฌ๋ ์๋ค๋งํฑ ์ปท. ๋ฌผ์ด ์ฐจ๋ฉด ์นผ๋ฐ๋ผ ํธ. (์: ๋ฐฑ๋์ฐ ์ฒ์ง, ๋๋ฆฌ๋ถ์ง) | Collapse Volume $V_c$ |
|
| 47 |
+
| **์ด์ค ํ์ฐ** | 1์ฐจ ๋ถ์ถ ํ ํด์ง๊ธฐ, 2์ฐจ ๋ถ์ถ | ์นผ๋ฐ๋ผ(ํฐ ๊ทธ๋ฆ) ์์ ์๋ก์ด ํ์ฐ(์์ ์ปต)์ด ์๊ธฐ๋ ๊ตฌ์กฐ. (์: ์ธ๋ฆ๋ ์ฑ์ธ๋ด-๋๋ฆฌ๋ถ์ง-์๋ด) | Nested Structure |
|
| 48 |
+
|
| 49 |
+
### โ๏ธ Chapter 3. ๋๊ฐ์ ๊ธฐํํ: 1์ฐจ ์งํ
|
| 50 |
+
**ํต์ฌ ๋ก์ง:** **์์ถ(Contraction)๊ณผ ๊ท ์ด**.
|
| 51 |
+
|
| 52 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 53 |
+
| :--- | :--- | :--- | :--- |
|
| 54 |
+
| **์ฃผ์์ ๋ฆฌ (Columnar Jointing)** | ๋๊ฐ ์๋($dT/dt$), ์์ถ ์ค์ฌ์ | ์ฉ์์ด ๊ธ๊ฒฉํ ์์ผ๋ฉฐ ๋ถํผ๊ฐ ์ค์ด๋ค ๋ ํ์ฑ๋๋ **์ก๊ฐํ(Hexagonal) ๊ธฐ๋ฅ** ํจํด ์์ฑ ์๊ณ ๋ฆฌ์ฆ. | Fracture Spacing $S \propto (dT/dt)^{-1/2}$ |
|
| 55 |
+
| **์ฉ์ ๋๊ตด (Lava Tube)** | ํ๋ฉด ๋๊ฐ์จ vs ๋ด๋ถ ์ ์ | ์ฉ์์ ๊ฒ๋ถ๋ถ์ ๊ตณ๊ณ (์ง๋ถ), ์์ ๊ณ์ ํ๋ฌ(ํฐ๋) ๋น ์ ธ๋๊ฐ ๋ค ๋จ์ ๋น ๊ณต๊ฐ. | Tube Formation Logic |
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## 2. ์ ๋ต์ง ์ด๋ฏธ์ง ๋ชฉ๋ก (Reference Images to Load)
|
| 60 |
+
*AI ๋ชจ๋ธ ํ์ต ๋ฐ UI ๋ ๋๋ง ์ ๋งค์นญํ ๋ ํผ๋ฐ์ค ๋ฐ์ดํฐ.*
|
| 61 |
+
|
| 62 |
+
1. `volcano_shape_viscosity.png` (์์ vs ์ฑ์ธต vs ์ข
์ ํ์ฐ์ ๋จ๋ฉด ๋น๊ต๋)
|
| 63 |
+
2. `caldera_formation_steps.gif` (๋ง๊ทธ๋ง ๋ฐฉ ๋น์ โ ๋ถ๊ดด โ ํธ์ ํ์ฑ 3๋จ๊ณ)
|
| 64 |
+
3. `columnar_jointing_hex.jpg` (์ฃผ์์ ๋ฆฌ์ ์ก๊ฐํ ๋จ๋ฉด ๋ฐ ์ธก๋ฉด ๊ธฐ๋ฅ ๊ตฌ์กฐ)
|
| 65 |
+
4. `korea_volcanic_map.png` (๋ฐฑ๋์ฐ, ์ ์ฃผ๋, ์ธ๋ฆ๋, ์ฒ ์ ์ฉ์๋์ง ์์น ์ง๋)
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
**[Director's Note]**
|
| 70 |
+
ํ์ฐ ๋ชจ๋์ **'์ ์ฑ($\mu$)'** ๋ณ์ ํ๋๊ฐ ์งํ์ ๋ชจ์(์์/์ข
์)์ ๊ฒฐ์ ์ง๋๋ค๋ ์ธ๊ณผ๊ด๊ณ๋ฅผ ๋ช
ํํ ๋ณด์ฌ์ค์ผ ํฉ๋๋ค. ํนํ ํ๊ตญ ๏ฟฝ๏ฟฝ๋ฆฌ ์ํ์์ ํ๊ฒ์ผ๋ก ํ๋ค๋ฉด **์ฒ ์ ์ฉ์๋์ง**์ **์ ์ฃผ๋**์ ํ์ฑ ๊ณผ์ ์ฐจ์ด๋ฅผ ์๋ฎฌ๋ ์ด์
์ผ๋ก ๋น๊ตํ๋ ๊ธฐ๋ฅ์ ๋ฐ๋์ ํฌํจํ์ญ์์ค.
|
| 71 |
+
|
| 72 |
+
์น์ธํ์๋ฉด ๋ค์์ ๊ฐ์ฅ ๋ค์ด๋ด๋ฏนํ **'๋นํ ์งํ'**์ผ๋ก ์ด๋ํ๊ฒ ์ต๋๋ค.
|
05_Glacial_Landforms_Spec.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ [Geo-Lab AI] ๊ฐ๋ฐ ๋ฐฑ์ (Development White Paper)
|
| 2 |
+
|
| 3 |
+
**Project Name:** Geo-Lab AI
|
| 4 |
+
**Version:** Final Release 1.0
|
| 5 |
+
**Module:** Glacial Landforms (๋นํ ์งํ)
|
| 6 |
+
**Target:** High School Geography ~ Undergraduate Glaciology
|
| 7 |
+
**Director:** [User Name]
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. ๊ฐ๋ฐ ํ์ค ๋ฐ ์ ์ฝ์ฌํญ (Standard Protocols)
|
| 12 |
+
|
| 13 |
+
### ๐จ 1. ๋น์ฃผ์ผ ์คํ์ผ ๊ฐ์ด๋ (Visual Style: Cryosphere)
|
| 14 |
+
* **Tone:** Majestic, Cold, High-Contrast.
|
| 15 |
+
* **Color Palette:**
|
| 16 |
+
* *Ice:* ๋ฐํฌ๋ช
ํ ์์ด์ค ๋ธ๋ฃจ(Ice Blue) ~ ์์ถ๋ ์ง์ ํ๋ (Deep Azure).
|
| 17 |
+
* *Bedrock:* ๊น์ฌ ๋๊ฐ ๊ฑฐ์น ํ์ ํ๊ฐ์ ์ง๊ฐ (Exposed Granite).
|
| 18 |
+
* *Water (Fjord):* ๊น๊ณ ์ด๋์ด ๋จ์ (Deep Navy).
|
| 19 |
+
* **Key Feature:** ๋นํ๊ฐ ํ๋ฅผ ๋ ๋ฐ๋ฅ์ ๊ธ์ด๋ด๋(Scouring) ํจ๊ณผ์ ๋
น์ ๋ ํ๋๋ฏธ๋ฅผ ์์๋๋(Dumping) ํจ๊ณผ๋ฅผ ๋ฌผ๋ฆฌ ์์ง์ผ๋ก ๊ตฌํ. **"๋ถ๊ธ(Sorting) ์์"** ์๊ฐํ ํ์.
|
| 20 |
+
|
| 21 |
+
### ๐ ๏ธ 2. ๊ธฐ์ ์คํ (Tech Stack)
|
| 22 |
+
* **Physics Engine:** Non-Newtonian Fluid Dynamics (๋น๋ดํด ์ ์ฒด ์ญํ - ์ผ์์ ๋๋ฆฐ ํ๋ฆ ๊ตฌํ).
|
| 23 |
+
* **Rendering:** Subsurface Scattering (์ผ์ ๋ด๋ถ์ ๋น ํฌ๊ณผ ํจ๊ณผ).
|
| 24 |
+
* **Algorithm:** Voxel Carving (๋นํ ์ด๋ ๊ฒฝ๋ก์ ๋ฐ๋ฅธ ์งํ ๊น๊ธฐ).
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## 1. ๋นํ ์งํ ๋ชจ๋ ์ธ๋ถ ๋ช
์ธ (Module Specifications)
|
| 29 |
+
|
| 30 |
+
### ๐ง Chapter 1. ๋นํ ์นจ์: ๊ฑฐ๋ํ ๋ถ๋์ (Erosion)
|
| 31 |
+
**ํต์ฌ ๋ก์ง:** **U์ํ ์นจ์ (U-shaped Profile)** & ๊ตด์ ์์ฉ(Plucking).
|
| 32 |
+
|
| 33 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 34 |
+
| :--- | :--- | :--- | :--- |
|
| 35 |
+
| **U์๊ณก (U-shaped Valley)** | ๋นํ ๋๊ป($H_{ice}$), ๋ง์ฐฐ๋ ฅ | ์ข๊ณ ๊น์ V์๊ณก(ํ์ฒ)์ด ๊ฑฐ๋ํ ๋นํ์ ์ํด ๋๊ณ ๋ฅ๊ทผ U์ ํํ๋ก ๊น์ฌ๋๊ฐ๋ **[Cross-section Transition]**. | Erosion Rate $E \propto U_{sliding}^2$ |
|
| 36 |
+
| **๊ถ๊ณก (Cirque)** | ์ค์ ๊ณ ๋(ELA), ๊ตด์ ๊ฐ๋ | ์ฐ ์ ์๋ถ ์ค๋ชฉํ ๊ณณ์ ๋์ด ์์ฌ ์ผ์์ด ๋๊ณ , ์์์ ๋ฏ์ด๋ด๋ฉฐ(Plucking) ๋ฐ์ํ ์์ ๋ชจ์์ ๋ง๋๋ ๊ณผ์ . | - |
|
| 37 |
+
| **ํธ๋ฅธ (Horn)** | ๊ถ๊ณก์ ๊ฐ์($N \ge 3$) | 3๊ฐ ์ด์์ ๊ถ๊ณก์ด ๋ค๋ก ํํดํ๋ฉฐ ์ ์์ ๊น์ ๋ง๋ค์ด์ง ํผ๋ผ๋ฏธ๋ํ ๋พฐ์กฑ ๋ด์ฐ๋ฆฌ (์: ๋งํฐํธ๋ฅธ). | Peak Sharpness Index |
|
| 38 |
+
| **ํผ์ค๋ฅด (Fjord)** | ํด์๋ฉด ์์น($\Delta SeaLevel$), ์นจ์ ๊น์ด | ๋น๊ธฐ๊ฐ ๋๋๊ณ ํด์๋ฉด์ด ์์นํ์ฌ U์๊ณก์ ๋ฐ๋ท๋ฌผ์ด ๋ค์ด์ฐจ๋ **[Flooding Animation]**. ๋ด๋ฅ ๊น์์ด ์ข๊ณ ๊ธด ๋ฐ๋ค ํ์ฑ. | Depth Profile $D(x)$ |
|
| 39 |
+
| **ํ๊ณก (Hanging Valley)** | ๋ณธ๋ฅ ๋นํ vs ์ง๋ฅ ๋นํ ๊น์ด ์ฐจ์ด | ๋ณธ๋ฅ๊ฐ ๊น๊ฒ ๊น๊ณ ์ง๋๊ฐ ๋ค, ์์ ์ง๋ฅ ๊ณจ์ง๊ธฐ๊ฐ ์ ๋ฒฝ ์์ ๊ฑธ๋ ค ํญํฌ๊ฐ ๋๋ ์งํ. | Drop Height $H_{drop}$ |
|
| 40 |
+
|
| 41 |
+
### ๐ชจ Chapter 2. ๋นํ ํด์ : ๋ฌด์ง์์ ๋ฏธํ (Deposition)
|
| 42 |
+
**ํต์ฌ ๋ก์ง:** **๋ถ๊ธ ๋ถ๋ (Poor Sorting)**. ํฌ๊ณ ์์ ์๊ฐ๊ณผ ํ์ด ๋ค์์.
|
| 43 |
+
|
| 44 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 45 |
+
| :--- | :--- | :--- | :--- |
|
| 46 |
+
| **๋นํด์ (Moraine)** | ๋นํ ํํด ์๋, ํด์ ๋ | ๋นํ๊ฐ ๋
น๋ ๋๋ถ๋ถ(Terminus)์์ ์ปจ๋ฒ ์ด์ด ๋ฒจํธ์ฒ๋ผ ์์ ๋ถ์ค๋ฌ๊ธฐ๋ฅผ ์์๋์ ์ธ๋์ ๋ง๋๋ ๊ณผ์ . | Sediment Flux $Q_s$ |
|
| 47 |
+
| **๋๋ผ๋ฆฐ (Drumlin)** | ๋นํ ์ด๋ ๋ฐฉํฅ, ๊ธฐ์ ํ์ค | ์๊ฐ๋ฝ์ ์์ด๋์ ๋ฏํ ์ ์ ํ ์ธ๋. **[Direction Indicator]** ์๋งํ ์ชฝ์ด ๋นํ๊ฐ ํ๋ฌ๊ฐ ๋ฐฉํฅ์์ ํ์ดํ๋ก ํ์. | Shape Factor (Elongation) |
|
| 48 |
+
| **์์ค์ปค (Esker)** | ์ต๋น์ ์ ๋($Q_{water}$), ํฐ๋ ํฌ๊ธฐ | ๋นํ ๋ฐ์ ํ๋ฅด๋ ๋ฌผ(์ต๋น์) ํฐ๋์ ํด์ ๋ฌผ์ด ์์ฌ, ๋นํ๊ฐ ๋
น์ ๋ค ๊ตฌ๋ถ๊ตฌ๋ถํ ์ ๋ฐฉ ๋ชจ์์ด ๋๋ฌ๋จ. | Sinuosity Index |
|
| 49 |
+
|
| 50 |
+
### โ๏ธ Chapter 3. ์ฃผ๋นํ ์งํ: ์ผ์๋ค ๋
น์๋ค (Periglacial - Bonus)
|
| 51 |
+
**ํต์ฌ ๋ก์ง:** **๋๊ฒฐ ์ตํด (Freeze-Thaw Cycle)**.
|
| 52 |
+
|
| 53 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 54 |
+
| :--- | :--- | :--- | :--- |
|
| 55 |
+
| **๊ตฌ์กฐํ (Patterned Ground)** | ํ ์ ์
์ ํฌ๊ธฐ, ๋๊ฒฐ ํ์ | ์๊ฐ๋ค์ด ์ผ์์ ์๊ธฐ ์์ฉ์ผ๋ก ๋ฐ๋ ค๋์ ๋ค๊ฐํ(Polygon) ๋ฌด๋ฌ๋ฅผ ๋ง๋๋ **[Time-lapse]**. | Convection Cell Model |
|
| 56 |
+
| **์๋ฆฌํ๋ญ์
(Solifluction)** | ๊ฒฝ์ฌ๋, ํ๋์ธต ์ตํด | ์๊ตฌ๋ํ ์ธต ์ ๋
น์ ํ์ด ํ๋ฌ๋ด๋ฆฌ๋ ํ์. | Creep Velocity |
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## 2. ์ ๋ต์ง ์ด๋ฏธ์ง ๋ชฉ๋ก (Reference Images to Load)
|
| 61 |
+
*AI ๋ชจ๋ธ ํ์ต ๋ฐ UI ๋ ๋๋ง ์ ๋งค์นญํ ๋ ํผ๋ฐ์ค ๋ฐ์ดํฐ.*
|
| 62 |
+
|
| 63 |
+
1. `valley_transformation_V_to_U.gif` (V์๊ณก์์ U์๊ณก์ผ๋ก ๋ณํ๋๋ 3D ๋ชจ๋ธ)
|
| 64 |
+
2. `fjord_cross_section.jpg` (๋ฐ๋ท๋ฌผ์ ์ ๊ธด U์๊ณก ๋จ๋ฉด๋)
|
| 65 |
+
3. `glacial_till_unsorted.png` (๋นํด์์ ๋ค์์ธ ์๊ฐ ๋จ๋ฉด - ๋ถ๊ธ ๋ถ๋ ์์)
|
| 66 |
+
4. `drumlin_ice_flow.jpg` (๋๋ผ๋ฆฐ์ ํํ์ ๋นํ ์ด๋ ๋ฐฉํฅ ๊ด๊ณ๋)
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
|
| 70 |
+
**[Director's Note]**
|
| 71 |
+
๋นํ ์งํ์ ํฌ๋ฌ ๋ฌธํญ ํฌ์ธํธ๋ **"๊ฐ๋ฌผ(์ ์)์ ์ํ ํด์ (๋ถ๊ธ ์ํธ)"**๊ณผ **"๋นํ์ ์ํ ํด์ (๋ถ๊ธ ๋ถ๋)"**์ ๊ตฌ๋ถํ๋ ๊ฒ์
๋๋ค. ์๋ฎฌ๋ ์ด์
๋ด์์ ์
์ ํฌ๊ธฐ ํํฐ(Sorting Filter)๊ฐ ๋นํ ๋ชจ๋์์๋ ์๋ํ์ง ์๋ ๊ฒ์ ์๊ฐ์ ์ผ๋ก ๊ฐ์กฐํด์ฃผ์ธ์.
|
| 72 |
+
|
| 73 |
+
์น์ธํ์๋ฉด ๋ค์์ ๊ฐ์ฅ ์ฒ๋ฐํ์ง๋ง ์๋ฆ๋ค์ด **'๊ฑด์กฐ ์งํ'**์ผ๋ก ๋์ด๊ฐ๊ฒ ์ต๋๋ค.
|
06_Arid_Landforms_Spec.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ [Geo-Lab AI] ๊ฐ๋ฐ ๋ฐฑ์ (Development White Paper)
|
| 2 |
+
|
| 3 |
+
**Project Name:** Geo-Lab AI
|
| 4 |
+
**Version:** Final Release 1.0
|
| 5 |
+
**Module:** Arid Landforms (๊ฑด์กฐ ์งํ)
|
| 6 |
+
**Target:** High School Geography ~ Undergraduate Geomorphology
|
| 7 |
+
**Director:** [User Name]
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. ๊ฐ๋ฐ ํ์ค ๋ฐ ์ ์ฝ์ฌํญ (Standard Protocols)
|
| 12 |
+
|
| 13 |
+
### ๐จ 1. ๋น์ฃผ์ผ ์คํ์ผ ๊ฐ์ด๋ (Visual Style: Desert & Erosion)
|
| 14 |
+
* **Tone:** High Contrast, Dusty, Heat-haze.
|
| 15 |
+
* **Color Palette:**
|
| 16 |
+
* *Sand/Dust:* ์ค์ปค(Ochre), ๋ฒํธ ์์๋(Burnt Sienna) ๋ฑ ํฉํ ์ ๊ณ์ด.
|
| 17 |
+
* *Sky:* ์ง์ ์ฝ๋ฐํธ ๋ธ๋ฃจ(๊ฑด์กฐํด์ ๋๊ธฐ๊ฐ ๊นจ๋ํจ) ๋๋ ๋ชจ๋ํญํ ์ ๋ฟ์ฐ ํฉ์.
|
| 18 |
+
* *Salt Lake (Playa):* ๋๋ถ์ ํฐ์ (์๊ธ ๊ฒฐ์ ๋ฐ์ฌ).
|
| 19 |
+
* **Key Feature:** ์งํ๋ฉด์ ์์ง๋์ด(Heat Haze) ํจ๊ณผ์ ๋ฐ๋์ ๋ฐฉํฅ์ ๋ณด์ฌ์ฃผ๋ **์
์ ํ๋ฆ(Particle Flow)** ์๊ฐํ.
|
| 20 |
+
|
| 21 |
+
### ๐ ๏ธ 2. ๊ธฐ์ ์คํ (Tech Stack)
|
| 22 |
+
* **Physics Engine:** Granular Physics (๋ชจ๋ ์
์ ๊ฐ ๋ง์ฐฐ ๋ฐ ์์๊ฐ ์๋ฎฌ๋ ์ด์
).
|
| 23 |
+
* **Rendering:** PBR Materials (์์์ ๊ฑฐ์น ์ง๊ฐ vs ์๊ธ ์ฌ๋ง์ ๋งค๋๋ฌ์ด ์ง๊ฐ).
|
| 24 |
+
* **Algorithm:** Cellular Automata (์ฌ๊ตฌ์ ์ด๋ ๋ฐ ์ฑ์ฅ ํจํด ๊ณ์ฐ).
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## 1. ๊ฑด์กฐ ์งํ ๋ชจ๋ ์ธ๋ถ ๋ช
์ธ (Module Specifications)
|
| 29 |
+
|
| 30 |
+
### ๐ช๏ธ Chapter 1. ๋ฐ๋์ ์กฐ๊ฐ: ์นจ์๊ณผ ํด์ (Aeolian Process)
|
| 31 |
+
**ํต์ฌ ๋ก์ง:** **๋์ฝ ์ด๋(Saltation)**. ๋ฌด๊ฑฐ์ด ๋ชจ๋๋ ์ง๋ฉด ๊ฐ๊น์ด์ ํ๋ฉฐ ์ด๋ํ๋ค.
|
| 32 |
+
|
| 33 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 34 |
+
| :--- | :--- | :--- | :--- |
|
| 35 |
+
| **๋ฒ์ฏ๋ฐ์ (Mushroom Rock)** | ๋ชจ๋ ๋น์ฐ ๋์ด($H_{max} \approx 1m$), ์์ ๊ฒฝ๋ | ๋ฐ๋์ ๋ ๋ฆฐ ๋ชจ๋์์ด ๋ฐ์์ **๋ฐ๋ถ๋ถ๋ง ์ง์ค ๊ณต๊ฒฉ**ํ์ฌ ๊น์๋ด๋ ๊ณผ์ . (์๋ถ๋ถ์ ์นจ์ ์ ๋จ). | Erosion Rate $E(z)$ |
|
| 36 |
+
| **์ฌ๊ตฌ (Sand Dune)** | ํํฅ ๋ฒกํฐ($\vec{V}$), ๋ชจ๋ ๊ณต๊ธ๋ | **[Dune Morphing]** ํํฅ์ ๋ฐ๋ผ ๋ฐ๋ฅดํ(์ด์น๋ฌํ, ๋จ์ผํ) $\leftrightarrow$ ์ฑ์ฌ๊ตฌ(๊ธด ์นผํ, ์๋ฐฉํฅํ) ๋ณํ. | Bagnold Formula |
|
| 37 |
+
| **์ผ๋ฆ์ (Ventifact)** | ์ฃผํํฅ์ ๊ฐ์ | ์๊ฐ์ด ๋ฐ๋์ ๋ฐ์ ๊น์ด๋ฉด์ 3๊ฐ์ ๋ฉด๊ณผ ๋ชจ์๋ฆฌ๊ฐ ์๊ธฐ๋ ๊ณผ์ . | Facet Formation |
|
| 38 |
+
| **์ฌ๋ง ํฌ์ฅ (Desert Pavement)** | ์
์ ํฌ๊ธฐ๋ณ ๋ฌด๊ฒ | ๋ฐ๋์ด ๊ณ ์ด ๋ชจ๋๋ง ๋ ๋ ค ๋ณด๋ด๊ณ (Deflation), ๋ฌด๊ฑฐ์ด ์๊ฐ๋ง ๋ฐ๋ฅ์ ๋จ๋ **[Sorting Filter]** ํจ๊ณผ. | Threshold Friction Velocity |
|
| 39 |
+
|
| 40 |
+
### ๐ง๏ธ Chapter 2. ๋ฌผ์ ์ญ์ค: ์ ์ ์งํ (Fluvial in Desert)
|
| 41 |
+
**ํต์ฌ ๋ก์ง:** **ํฌ์ ํ์(Flash Flood)**. ์์์ด ์์ด ๋น๊ฐ ์ค๋ฉด ๋ฌผ์ด ๊ธ๊ฒฉํ ๋ถ์ด๋จ.
|
| 42 |
+
|
| 43 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 44 |
+
| :--- | :--- | :--- | :--- |
|
| 45 |
+
| **์ ์์ง (Alluvial Fan)** | ๊ฒฝ์ฌ ๊ธ๋ณ์ , ์ ์ ๊ฐ์์จ | ์ฐ์ง์์ ํ์ง๋ก ๋์ฌ ๋ ์ ์์ด ๋๋ ค์ง๋ฉฐ ๋ถ์ฑ๊ผด๋ก ํด์ ๋ฌผ์ด ํผ์ง๋ ์๋ฎฌ๋ ์ด์
. (์ ์ -์ ์-์ ๋จ ๊ตฌ๋ถ). | Stream Power Law |
|
| 46 |
+
| **๋ฐํ๋ค (Bajada)** | ์ ์์ง ๊ฒฐํฉ ์($N$) | ์ฌ๋ฌ ๊ฐ์ ์ ์์ง๊ฐ ์์ผ๋ก ํฉ์ณ์ ธ ์ฐ๊ธฐ์ญ์ ๊ฐ์ธ๋ ๋ณตํฉ ์ ์์ง ํ์ฑ. | Coalescence Factor |
|
| 47 |
+
| **์๋ (Wadi)** | ๊ฐ์ ๋น๋, ์นจํฌ์จ | ํ์์ ๋ง๋ฅธ ๊ณจ์ง๊ธฐ(๊ตํต๋ก)์๋ค๊ฐ, ๋น๊ฐ ์ค๋ฉด ๊ธ๋ฅ๊ฐ ํ๋ฅด๋ ๊ฐ์ผ๋ก ๋ณํ๋ **[Event Trigger]**. | Infiltration Capacity |
|
| 48 |
+
| **ํ๋ผ์ผ (Playa)** | ์ฆ๋ฐ๋ >> ๊ฐ์๋ | ๋ฌผ์ด ๊ณ ์๋ค๊ฐ ์ฆ๋ฐํ๊ณ **์๊ธ(Salt)**๋ง ํ์๊ฒ ๋จ๋ ์ผํธ์ ํ์ฑ ๊ณผ์ . | Evaporation Rate |
|
| 49 |
+
|
| 50 |
+
### ๐งฑ Chapter 3. ๊ตฌ์กฐ ์งํ: ๊ฐํ ๋์ด ์ด์๋จ๋๋ค (Structural)
|
| 51 |
+
**ํต์ฌ ๋ก์ง:** **์ฐจ๋ณ ์นจ์(Differential Erosion)** & ์ํ ์ง์ธต.
|
| 52 |
+
|
| 53 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 54 |
+
| :--- | :--- | :--- | :--- |
|
| 55 |
+
| **๋ฉ์ฌ (Mesa)** | ์๋ถ ์์ ๊ฐ๋($S_{hard}$), ํ๋ถ($S_{soft}$) | ์๋ถ๋ถ์ ๋จ๋จํ ์์(Cap rock)์ด ๋๊ป์ฒ๋ผ ๋ณดํธํด์ค์ ์๊ธด ํ์ ๋ชจ์์ ์งํ. | Resistance Ratio |
|
| 56 |
+
| **๋ทฐํธ (Butte)** | ์นจ์ ์งํ๋ฅ | ๋ฉ์ฌ๊ฐ ๊น์ฌ์ ์์์ง ํ ๋ชจ์์ ์งํ. (๋ฉ์ฌ $\rightarrow$ ๋ทฐํธ ํฌ๊ธฐ ๋น๊ต). | Width/Height Ratio |
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## 2. ์ ๋ต์ง ์ด๋ฏธ์ง ๋ชฉ๋ก (Reference Images to Load)
|
| 61 |
+
*AI ๋ชจ๋ธ ํ์ต ๋ฐ UI ๋ ๋๋ง ์ ๋งค์นญํ ๋ ํผ๋ฐ์ค ๋ฐ์ดํฐ.*
|
| 62 |
+
|
| 63 |
+
1. `barchan_dune_wind_direction.jpg` (๋ฐ๋ฅดํ ์ฌ๊ตฌ์ ํํ์ ๋ฐ๋ ๋ฐฉํฅ ํ์ดํ)
|
| 64 |
+
2. `alluvial_fan_structure.png` (์ ์์ง์ ์ ์ , ์ ์, ์ ๋จ ๋จ๋ฉด๋ ๋ฐ ์
์ ํฌ๊ธฐ ๋ถํฌ)
|
| 65 |
+
3. `mesa_butte_evolution.jpg` (๊ณ ์ -> ๋ฉ์ฌ -> ๋ทฐํธ -> ์คํ์ด์ด ์นจ์ ๋จ๊ณ)
|
| 66 |
+
4. `mushroom_rock_formation.gif` (๋ชจ๋๋ฐ๋์ ์ํ ํ๋จ๋ถ ์นจ์ ์ ๋๋ฉ์ด์
)
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
|
| 70 |
+
**[Director's Note]**
|
| 71 |
+
๊ฑด์กฐ ์งํ์ **"๋ฐ๋"**์ด ๋ง๋ ๊ฒ ๊ฐ์ง๋ง, ์ค์ ๊ฑฐ๋ ์งํ์ **"์ผ์์ ์ธ ํญ์ฐ(๋ฌผ)"**๊ฐ ๋ง๋ค์๋ค๋ ์ค๊ฐ๋
์ ๋ฐ๋ก์ก๋ ๊ฒ์ด ๊ต์ก์ ๋ชฉํ์
๋๋ค. ์๋(Wadi) ์๋ฎฌ๋ ์ด์
์์ ๏ฟฝ๏ฟฝ๏ฟฝ๊ฐ ์ฌ ๋ ๊ธ๊ฒฉํ ๋ฌผ์ด ์ฐจ์ค๋ฅด๋ ์๋๋ฅผ ๊ทน์ ์ผ๋ก ํํํด ์ฃผ์ญ์์ค.
|
| 72 |
+
|
| 73 |
+
์น์ธํ์๋ฉด ๋ง์ง๋ง ๋๋จ์, ๊ฐ์ฅ ํ์จํ์ง๋ง ๊ฐ์ฅ ์ค์ํ **'ํ์ผ ์งํ'**์ผ๋ก ๋์ด๊ฐ๊ฒ ์ต๋๋ค.
|
07_Plain_Landforms_Spec.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ [Geo-Lab AI] ๊ฐ๋ฐ ๋ฐฑ์ (Development White Paper)
|
| 2 |
+
|
| 3 |
+
**Project Name:** Geo-Lab AI
|
| 4 |
+
**Version:** Final Release 1.0
|
| 5 |
+
**Module:** Plain Landforms (ํ์ผ ์งํ)
|
| 6 |
+
**Target:** High School Geography ~ Urban Planning Basics
|
| 7 |
+
**Director:** [User Name]
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. ๊ฐ๋ฐ ํ์ค ๋ฐ ์ ์ฝ์ฌํญ (Standard Protocols)
|
| 12 |
+
|
| 13 |
+
### ๐จ 1. ๋น์ฃผ์ผ ์คํ์ผ ๊ฐ์ด๋ (Visual Style: Fertile & Sedimentary)
|
| 14 |
+
* **Tone:** Horizontal, Expansive, Saturation-coded (Soil Moisture).
|
| 15 |
+
* **Color Palette:**
|
| 16 |
+
* *Levee (Dry):* ๋ฐ์ ํฉํ ์ (๋ฐฐ์๊ฐ ์ ๋จ โ ๋ฐญ, ์ทจ๋ฝ).
|
| 17 |
+
* *Backswamp (Wet):* ์ง์ ์งํ์ (๋ฐฐ์ ๋ถ๋ โ ๋
ผ, ์ต์ง).
|
| 18 |
+
* *River:* ํํ๋ฌผ(Turbid) ํํ (์๋ฅ์ ์ฒญ๋ช
ํจ๊ณผ ๋๋น).
|
| 19 |
+
* **Key Feature:** **๋จ๋ฉด๋(Cross-section) ๋ทฐ**๋ฅผ ํตํด ์งํ๋ฉด์ ๋ฏธ์ธํ ๊ณ ๋ ์ฐจ์ด(์ ๋ฐฉ vs ์ต์ง)์ ํด์ ๋ฌผ์ ์
์ ํฌ๊ธฐ ๋ณํ๋ฅผ ์ง๊ด์ ์ผ๋ก ํํ.
|
| 20 |
+
|
| 21 |
+
### ๐ ๏ธ 2. ๊ธฐ์ ์คํ (Tech Stack)
|
| 22 |
+
* **Physics Engine:** Navier-Stokes Equations (์ ์ฒด ์ญํ - ํ์ฒ์ ๊ณก๋ฅ ๋ฐ ๋ฒ๋ ์๋ฎฌ๋ ์ด์
).
|
| 23 |
+
* **Rendering:** Height Map Displacement (๋ฏธ์ธํ ๊ณ ๋ ์ฐจ์ด ํํ).
|
| 24 |
+
* **Algorithm:** Sedimentation Sorting (์ ์ ๊ฐ์์ ๋ฐ๋ฅธ ์
์๋ณ ํด์ ์์น ๊ณ์ฐ).
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## 1. ํ์ผ ์งํ ๋ชจ๋ ์ธ๋ถ ๋ช
์ธ (Module Specifications)
|
| 29 |
+
|
| 30 |
+
### ๐ Chapter 1. ํ์ฒ ์คยทํ๋ฅ: ๊ณก๋ฅ์ ๋ฒ๋์ (Floodplains)
|
| 31 |
+
**ํต์ฌ ๋ก์ง:** **์ธก๋ฐฉ ์นจ์(Lateral Erosion)** & ํ์ ์ ํด์ .
|
| 32 |
+
|
| 33 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 34 |
+
| :--- | :--- | :--- | :--- |
|
| 35 |
+
| **์์ ๊ณก๋ฅ ํ์ฒ** | ํ์ฒ ๊ตด๊ณก๋(Sinuosity), ์ ์ ํธ์ฐจ | ๊ฐ๋ฌผ์ด ๋ฑ์ฒ๋ผ ํ์ด์ง๋ฉฐ ํ๋ฅด๋ ๋ชจ์ต. **[Velocity Map]** ๋ฐ๊นฅ์ชฝ์ ๋น ๋ฆ(์นจ์/๊ณต๊ฒฉ์ฌ๋ฉด), ์์ชฝ์ ๋๋ฆผ(ํด์ /ํฌ์ธํธ๋ฐ) ์์ ๊ตฌ๋ถ. | Centrifugal Force $F_c$ |
|
| 36 |
+
| **์ฐ๊ฐํธ (Oxbow Lake)** | ๊ตด๊ณก๋ ์๊ณ๊ฐ, ์๊ฐ($t$) | ๊ตฝ์ด์น๋ ๊ฐ์ด ํ์ ๋ ์ง์ ์ผ๋ก ๋ซ๋ฆฌ๋ฉด์, ๋จ๊ฒจ์ง ๋ฌผ๊ธธ์ด ์๋ฟ ๋ชจ์ ํธ์๋ก ๊ณ ๋ฆฝ๋๋ **[Cut-off Animation]**. | Path Shortening |
|
| 37 |
+
| **์์ฐ ์ ๋ฐฉ (Levee)** | ํ์ ์์, ์กฐ๋ฆฝ์ง(ํฐ ์
์) ๋น์จ | ํ์ ์ ๊ฐ ๋ฐ๋ก ์์ ๋ฌด๊ฑฐ์ด ๋ชจ๋/์๊ฐ์ด ๋จผ์ ์์ฌ ๋์ ํ์ฑ. ์ฃผ๋ณ๋ณด๋ค ์ฝ๊ฐ ๋์(High & Dry). | Settling Velocity (Stokes) |
|
| 38 |
+
| **๋ฐฐํ ์ต์ง (Backswamp)** | ๋ฒ๋ ๊ฑฐ๋ฆฌ, ๋ฏธ๋ฆฝ์ง(์์ ์
์) ๋น์จ | ์์ฐ์ ๋ฐฉ ๋ค์ชฝ์ผ๋ก ๊ณ ์ด ์งํ์ด ๋์ด๊ฐ ์์ธ ๋ฎ๊ณ ์ถ์ถํ ๋
. (์์ฐ์ ๋ฐฉ๊ณผ ๋ฐฐ์ ์กฐ๊ฑด ๋น๊ต ํ์). | Permeability $k$ |
|
| 39 |
+
|
| 40 |
+
### ๐ Chapter 2. ํ๊ตฌ: ๋ฐ๋ค์์ ๋ง๋จ, ์ผ๊ฐ์ฃผ (Deltas)
|
| 41 |
+
**ํต์ฌ ๋ก์ง:** **์ ์ ์๋ฉธ(Velocity $\to$ 0)**. ๊ฐ๋ฌผ์ด ๋ฐ๋ค๋ฅผ ๋ง๋ ์ง์ ๋ด๋ ค๋์.
|
| 42 |
+
|
| 43 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 44 |
+
| :--- | :--- | :--- | :--- |
|
| 45 |
+
| **์ผ๊ฐ์ฃผ ํ์ฑ** | ํ์ฒ ํด์ ๋ > ์กฐ๋ฅ/ํ๋ ์ ๊ฑฐ๋ | ๊ฐ ํ๊ตฌ์์ ํด์ ๋ฌผ์ด ๋ถ์ฑ๊ผด๋ก ํผ์ ธ๋๊ฐ๋ ๊ณผ์ . (Topset, Foreset, Bottomset ์ธต๋ฆฌ ๊ตฌ์กฐ ๋จ๋ฉด). | Sediment Budget $\Delta S$ |
|
| 46 |
+
| **์กฐ๋ฅ ์ผ๊ฐ์ฃผ** | ์กฐ์ฐจ(Tidal Range), ์ ์ | ๋ฐ๋ฌผ/์ฐ๋ฌผ์ด ๊ฐํด์ ํด์ ๋ฌผ์ด ๋ฐ๋ค ์ชฝ์ผ๋ก ๊ธธ๊ฒ ๋ป์ง ๋ชปํ๊ณ ์ฌ์ฒ๋ผ ํ์ฑ๋ ์ผ๊ฐ์ฃผ (์: ๋๋๊ฐ ํ๊ตฌ ์์๋). | Tidal Current Power |
|
| 47 |
+
| **ํํ๋ณ ๋ถ๋ฅ** | ํ๋ ์๋์ง vs ํ์ฒ ์๋์ง | ์ํธ์(๋์ผ๊ฐ), ์กฐ์กฑ์(๋ฏธ์์ํผ๊ฐ-์๋ฐ๋ชจ์), ์ฒจ๊ฐ์(ํฐ๋ฒ ๋ฅด๊ฐ) ํํ ๋น๊ต ์๋ฎฌ๋ ์ด์
. | Energy Ratio |
|
| 48 |
+
|
| 49 |
+
### โณ Chapter 3. ์นจ์ ํ์ผ: ๊น์ด๊ณ ๋จ์ ๋
(Erosion Plains)
|
| 50 |
+
**ํต์ฌ ๋ก์ง:** **์ต์ข
๋จ๊ณ(Final Stage)**.
|
| 51 |
+
|
| 52 |
+
| ์งํ (Feature) | ํต์ฌ ๋ณ์ (Key Parameters) | ์๊ฐํ ํฌ์ธํธ (Visualization) | ํ์ต์ฉ ์์ (Formula Reference) |
|
| 53 |
+
| :--- | :--- | :--- | :--- |
|
| 54 |
+
| **์คํ์ (Peneplain)** | ์นจ์ ์๊ฐ($t \to \infty$) | ์ฐ์ง๊ฐ ๊น์ด๊ณ ๊น์ฌ ํด์๋ฉด์ ๊ฐ๊น๊ฒ ํํํด์ง ์งํ. ๋ฐ์ด๋น์ค์ ์งํ ์คํ์ค ๋ง์ง๋ง ๋จ๊ณ. | Base Level Approach |
|
| 55 |
+
| **์๊ตฌ (Monadnock)** | ์์์ ์ฐจ๋ณ ์นจ์ | ์คํ์ ์์ ๋จ๋จํ ์์๋ง ์นจ์์ ๊ฒฌ๋๊ณ ํ๋ก ๋จ์ ๋ฎ์ ์ธ๋. | Hardness Differential |
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## 2. ์ ๋ต์ง ์ด๋ฏธ์ง ๋ชฉ๋ก (Reference Images to Load)
|
| 60 |
+
*AI ๋ชจ๋ธ ํ์ต ๋ฐ UI ๋ ๋๋ง ์ ๋งค์นญํ ๋ ํผ๋ฐ์ค ๋ฐ์ดํฐ.*
|
| 61 |
+
|
| 62 |
+
1. `meandering_river_evolution.gif` (๊ณก๋ฅ ํ์ฒ์ด ์ฐ๊ฐํธ๋ก ๋ณํ๋ 4๋จ๊ณ ๊ณผ์ )
|
| 63 |
+
2. `floodplain_cross_section.png` (ํ๋ - ์์ฐ์ ๋ฐฉ - ๋ฐฐํ์ต์ง ๋จ๋ฉด๋ ๋ฐ ํ ์ง ์ด์ฉ)
|
| 64 |
+
3. `delta_types_satellite.jpg` (๋์ผ๊ฐ, ๋ฏธ์์ํผ๊ฐ, ๊ฐ ์ง์ค๊ฐ ์ผ๊ฐ์ฃผ ์์ฑ ์ฌ์ง ๋น๊ต)
|
| 65 |
+
4. `levee_vs_backswamp_soil.jpg` (์์ฐ์ ๋ฐฉ์ ๋ชจ๋ vs ๋ฐฐํ์ต์ง์ ์ ํ ์
์ ๋น๊ต)
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
**[Director's Note]**
|
| 70 |
+
ํ์ผ ์งํ์ ํต์ฌ์ **"์ธ๊ฐ์ ๊ฑฐ์ฃผ(Settlement)"**์ ์ฐ๊ฒฐํ๋ ๊ฒ์
๋๋ค.
|
| 71 |
+
์๋ฎฌ๋ ์ด์
UI์ **[Village Builder]** ๋ชจ๋๋ฅผ ์ถ๊ฐํ์ฌ, ์ฌ์ฉ์๊ฐ **์์ฐ์ ๋ฐฉ(ํ์ ์์ /๋ฐญ๋์ฌ)**๊ณผ **๋ฐฐํ์ต์ง(ํ์ ์ํ/๋
ผ๋์ฌ)** ์ค ์ด๋๏ฟฝ๏ฟฝ ์ง์ ์ง๊ณ ๋์ฌ๋ฅผ ์ง์์ง ์ ํํ๊ฒ ํ์ธ์. ์๋ชป๋ ์ ํ(์: ๋ฐฐํ์ต์ง์ ์ง ์ง๊ธฐ) ์ ํ์ ์ด๋ฒคํธ๋ก ํ๋ํฐ๋ฅผ ์ฃผ๋ฉด ํ์ต ํจ๊ณผ๊ฐ ๊ทน๋ํ๋ฉ๋๋ค.
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
**[Project Status]**
|
| 76 |
+
๐ **์ถํํฉ๋๋ค!**
|
| 77 |
+
์ฐ์ง, ์นด๋ฅด์คํธ, ํ์ฐ, ๋นํ, ๊ฑด์กฐ, ํ์ผ ์งํ๊น์ง **[Geo-Lab AI]์ ๋ชจ๋ ์งํ ๋ชจ๋ ์ค๊ณ๊ฐ ์๋ฃ๋์์ต๋๋ค.**
|
| 78 |
+
|
| 79 |
+
์ด์ ์ด ๋ฐฑ์๋ค์ ๋ฐํ์ผ๋ก ์ ์ฒด ํ๋ก์ ํธ๋ฅผ **ํตํฉ(Integration)**ํ๊ฑฐ๋, **์ถ์(Launch)** ๋จ๊ณ๋ก ๋์ด๊ฐ์๊ฒ ์ต๋๊น?
|
DEPLOY.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ Geo-Lab AI: ์น ๋ฐฐํฌ ๊ฐ์ด๋
|
| 2 |
+
|
| 3 |
+
## ๋ฐฐํฌ ์ต์
|
| 4 |
+
|
| 5 |
+
### 1. ๐ Streamlit Community Cloud (์ถ์ฒ - ๋ฌด๋ฃ)
|
| 6 |
+
|
| 7 |
+
**์ฅ์ :** ๋ฌด๋ฃ, GitHub ์ฐ๋, ์๋ ๋ฐฐํฌ
|
| 8 |
+
|
| 9 |
+
#### ๋จ๊ณ๋ณ ๊ฐ์ด๋:
|
| 10 |
+
|
| 11 |
+
1. **GitHub ์ ์ฅ์ ์์ฑ**
|
| 12 |
+
```bash
|
| 13 |
+
cd c:\Users\HANSOL\Desktop\Geo-lab
|
| 14 |
+
git init
|
| 15 |
+
git add .
|
| 16 |
+
git commit -m "Initial commit: ํ์ฒ ์งํ ๋ชจ๋ ํ๋กํ ํ์
"
|
| 17 |
+
git branch -M main
|
| 18 |
+
git remote add origin https://github.com/YOUR_USERNAME/geo-lab-ai.git
|
| 19 |
+
git push -u origin main
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
2. **Streamlit Cloud ์ ์**
|
| 23 |
+
- https://share.streamlit.io ๋ฐฉ๋ฌธ
|
| 24 |
+
- GitHub ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ
|
| 25 |
+
|
| 26 |
+
3. **์ฑ ๋ฐฐํฌ**
|
| 27 |
+
- "New app" ํด๋ฆญ
|
| 28 |
+
- Repository: `YOUR_USERNAME/geo-lab-ai`
|
| 29 |
+
- Branch: `main`
|
| 30 |
+
- Main file path: `app/main.py`
|
| 31 |
+
- "Deploy!" ํด๋ฆญ
|
| 32 |
+
|
| 33 |
+
4. **์๋ฃ!**
|
| 34 |
+
- URL ์์: `https://geo-lab-ai.streamlit.app`
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
### 2. ๐ง Hugging Face Spaces
|
| 39 |
+
|
| 40 |
+
**์ฅ์ :** ๋ฌด๋ฃ, ์ปค๋ฎค๋ํฐ ๊ณต์ ์ฉ์ด
|
| 41 |
+
|
| 42 |
+
1. https://huggingface.co/spaces ์์ ์ Space ์์ฑ
|
| 43 |
+
2. SDK: "Streamlit" ์ ํ
|
| 44 |
+
3. ํ์ผ ์
๋ก๋ ๋๋ GitHub ์ฐ๋
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
### 3. โ๏ธ ๊ธฐํ ์ต์
|
| 49 |
+
|
| 50 |
+
| ํ๋ซํผ | ๋น์ฉ | ํน์ง |
|
| 51 |
+
|-------|-----|-----|
|
| 52 |
+
| **Render** | ๋ฌด๋ฃ ํฐ์ด | ์๋ ์ฌ๋ฆฝ, ์ปค์คํ
๋๋ฉ์ธ |
|
| 53 |
+
| **Railway** | ๋ฌด๋ฃ $5/์ | ๋น ๋ฅธ ๋ฐฐํฌ |
|
| 54 |
+
| **Heroku** | ์ ๋ฃ | ์์ ์ |
|
| 55 |
+
|
| 56 |
+
---
|
| 57 |
+
|
| 58 |
+
## ๋ก์ปฌ ์คํ
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
# ์์กด์ฑ ์ค์น
|
| 62 |
+
pip install -r requirements.txt
|
| 63 |
+
|
| 64 |
+
# ์ฑ ์คํ
|
| 65 |
+
streamlit run app/main.py
|
| 66 |
+
|
| 67 |
+
# ๋ธ๋ผ์ฐ์ ์์ ์ด๊ธฐ
|
| 68 |
+
# http://localhost:8501
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
## ํ์ฌ ์ฑ ์ํ
|
| 74 |
+
|
| 75 |
+
โ
**๋ก์ปฌ ์คํ ์ค**: http://localhost:8501
|
| 76 |
+
|
| 77 |
+
**์ธ๋ถ ์ ์ URL**: http://211.114.121.192:8501
|
| 78 |
+
(๊ฐ์ ๋คํธ์ํฌ์์๋ง ์ ๊ทผ ๊ฐ๋ฅ)
|
README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Geo-Lab AI
|
| 3 |
+
emoji: ๐
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
sdk_version: 1.28.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# ๐ Geo-Lab AI - ์ด์์ ์งํ ๊ฐค๋ฌ๋ฆฌ
|
| 14 |
+
|
| 15 |
+
**๊ต์ฌ๋ฅผ ์ํ ์งํ ํ์ฑ๊ณผ์ ์๊ฐํ ๋๊ตฌ**
|
| 16 |
+
|
| 17 |
+
## ์ฃผ์ ๊ธฐ๋ฅ
|
| 18 |
+
|
| 19 |
+
- ๐ **31์ข
์ด์์ ์งํ** - ๊ต๊ณผ์์ ์งํ ํํ์ ๊ธฐํํ์ ๋ชจ๋ธ
|
| 20 |
+
- ๐ฌ **ํ์ฑ๊ณผ์ ์ ๋๋ฉ์ด์
** - 0%โ100% ์ฌ๋ผ์ด๋๋ก ์งํ ํ์ฑ ํ์ธ
|
| 21 |
+
- ๐๏ธ **7๊ฐ ์นดํ
๊ณ ๋ฆฌ** - ํ์ฒ, ์ผ๊ฐ์ฃผ, ๋นํ, ํ์ฐ, ์นด๋ฅด์คํธ, ๊ฑด์กฐ, ํด์
|
| 22 |
+
- ๐ **2D/3D ์๊ฐํ** - ํ๋ฉด๋ ๋ฐ ์
์ฒด ์งํ ๋ชจ๋ธ
|
| 23 |
+
|
| 24 |
+
## ์ง์ ์งํ
|
| 25 |
+
|
| 26 |
+
| ์นดํ
๊ณ ๋ฆฌ | ์งํ |
|
| 27 |
+
|----------|------|
|
| 28 |
+
| ๐ ํ์ฒ | ์ ์์ง, ์์ ๊ณก๋ฅ, ๊ฐ์
๊ณก๋ฅ, V์๊ณก, ๋ง์ํ์ฒ, ํญํฌ |
|
| 29 |
+
| ๐บ ์ผ๊ฐ์ฃผ | ์ผ๋ฐ, ์กฐ์กฑ์, ํธ์, ์ฒจ๋์ |
|
| 30 |
+
| โ๏ธ ๋นํ | U์๊ณก, ๊ถ๊ณก, ํธ๋ฅธ, ํผ์ค๋ฅด๋, ๋๋ผ๋ฆฐ, ๋นํด์ |
|
| 31 |
+
| ๐ ํ์ฐ | ์์ํ์ฐ, ์ฑ์ธตํ์ฐ, ์นผ๋ฐ๋ผ, ํ๊ตฌํธ, ์ฉ์๋์ง |
|
| 32 |
+
| ๐ฆ ์นด๋ฅด์คํธ | ๋๋ฆฌ๋ค |
|
| 33 |
+
| ๐๏ธ ๊ฑด์กฐ | ๋ฐ๋ฅดํ ์ฌ๊ตฌ, ๋ฉ์ฌ/๋ทฐํธ |
|
| 34 |
+
| ๐๏ธ ํด์ | ํด์์ ๋ฒฝ, ์ฌ์ทจ+์ํธ, ์ก๊ณ์ฌ์ฃผ, ๋ฆฌ์์คํด์, ํด์์์น, ํด์์ฌ๊ตฌ |
|
| 35 |
+
|
| 36 |
+
## ์ ์
|
| 37 |
+
|
| 38 |
+
**2025 ํ๋ฐฑ๊ณ ๋ฑํ๊ต ๊นํ์T**
|
| 39 |
+
|
| 40 |
+
## ๊ธฐ์ ์คํ
|
| 41 |
+
|
| 42 |
+
- Python / Streamlit
|
| 43 |
+
- NumPy / Matplotlib / Plotly
|
| 44 |
+
- ๊ธฐํํ์ ์งํ ์์ฑ ์๊ณ ๋ฆฌ์ฆ
|
app.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Geo-Lab AI - ์ด์์ ์งํ ๊ฐค๋ฌ๋ฆฌ
|
| 2 |
+
# HuggingFace Spaces Entry Point
|
| 3 |
+
|
| 4 |
+
import sys
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
# Add parent directory to path
|
| 8 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 9 |
+
|
| 10 |
+
# Import and run main app
|
| 11 |
+
from app.main import main
|
| 12 |
+
|
| 13 |
+
if __name__ == "__main__":
|
| 14 |
+
main()
|
app/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# App Package
|
app/components/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Components Package
|
app/main.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
debug_log.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Testing Sediment Transport...
|
| 2 |
+
Initial Elev Row 4 (Slope Base): 7.0
|
| 3 |
+
Initial Elev Row 7 (Flat Land): 5.0
|
| 4 |
+
Post Elev Row 4: 6.8
|
| 5 |
+
Post Elev Row 7: 5.0
|
| 6 |
+
Sediment Array (Column 0):
|
| 7 |
+
[0. 0. 0. 0. 0. 3.8 0. 0. 0. 0. ]
|
| 8 |
+
Total Sediment on Flat/Sea: 5.0
|
| 9 |
+
Sediment Transport OK
|
debug_piracy.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import numpy as np
|
| 3 |
+
import plotly.graph_objects as go
|
| 4 |
+
|
| 5 |
+
# Mock functions from main.py
|
| 6 |
+
def simulate_stream_piracy(time_years: int, params: dict, grid_size: int = 100):
|
| 7 |
+
"""ํ์ฒ์ํ ์๋ฎฌ๋ ์ด์
- ๊ต๊ณผ์์ ์ด์์ ๋ชจ์ต"""
|
| 8 |
+
x = np.linspace(0, 1000, grid_size)
|
| 9 |
+
y = np.linspace(0, 1000, grid_size)
|
| 10 |
+
X, Y = np.meshgrid(x, y)
|
| 11 |
+
elevation = 150 - Y * 0.1
|
| 12 |
+
ridge_x = 500
|
| 13 |
+
ridge = 20 * np.exp(-((X - ridge_x)**2) / (80**2))
|
| 14 |
+
elevation += ridge
|
| 15 |
+
river1_x = 300
|
| 16 |
+
river1_valley = 30 * np.exp(-((X - river1_x)**2) / (40**2))
|
| 17 |
+
elevation -= river1_valley
|
| 18 |
+
river2_x = 700
|
| 19 |
+
erosion_diff = params.get('erosion_diff', 0.7)
|
| 20 |
+
river2_depth = 50 * erosion_diff
|
| 21 |
+
river2_valley = river2_depth * np.exp(-((X - river2_x)**2) / (50**2))
|
| 22 |
+
elevation -= river2_valley
|
| 23 |
+
|
| 24 |
+
# ... logic ...
|
| 25 |
+
# Simplified logic for t=5000 (not captured)
|
| 26 |
+
return {'elevation': elevation, 'captured': False}
|
| 27 |
+
|
| 28 |
+
def render_terrain_plotly_debug(elevation):
|
| 29 |
+
print(f"Elevation stats: Min={elevation.min()}, Max={elevation.max()}, NaNs={np.isnan(elevation).sum()}")
|
| 30 |
+
|
| 31 |
+
dy, dx = np.gradient(elevation)
|
| 32 |
+
print(f"Gradient stats: dx_NaN={np.isnan(dx).sum()}, dy_NaN={np.isnan(dy).sum()}")
|
| 33 |
+
|
| 34 |
+
slope = np.sqrt(dx**2 + dy**2)
|
| 35 |
+
print(f"Slope stats: Min={slope.min()}, Max={slope.max()}, NaNs={np.isnan(slope).sum()}")
|
| 36 |
+
|
| 37 |
+
biome = np.zeros_like(elevation)
|
| 38 |
+
biome[:] = 1
|
| 39 |
+
# ...
|
| 40 |
+
noise = np.random.normal(0, 0.2, elevation.shape)
|
| 41 |
+
biome_noisy = np.clip(biome + noise, 0, 3)
|
| 42 |
+
print(f"Biome Noisy stats: Min={biome_noisy.min()}, Max={biome_noisy.max()}, NaNs={np.isnan(biome_noisy).sum()}")
|
| 43 |
+
|
| 44 |
+
realistic_colorscale = [
|
| 45 |
+
[0.0, '#E6C288'], [0.25, '#E6C288'],
|
| 46 |
+
[0.25, '#2E8B57'], [0.5, '#2E8B57'],
|
| 47 |
+
[0.5, '#696969'], [0.75, '#696969'],
|
| 48 |
+
[0.75, '#FFFFFF'], [1.0, '#FFFFFF']
|
| 49 |
+
]
|
| 50 |
+
print("Colorscale:", realistic_colorscale)
|
| 51 |
+
|
| 52 |
+
if __name__ == "__main__":
|
| 53 |
+
res = simulate_stream_piracy(5000, {'erosion_diff': 0.7})
|
| 54 |
+
elev = res['elevation']
|
| 55 |
+
render_terrain_plotly_debug(elev)
|
engine/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Engine Package
|
engine/base.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI Engine: ๊ธฐ๋ณธ ์งํ ํด๋์ค
|
| 3 |
+
"""
|
| 4 |
+
import numpy as np
|
| 5 |
+
from dataclasses import dataclass, field
|
| 6 |
+
from typing import Optional, Tuple
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@dataclass
|
| 10 |
+
class Terrain:
|
| 11 |
+
"""2D ๋์ด๋งต ๊ธฐ๋ฐ ์งํ ๋ฐ์ดํฐ ๊ตฌ์กฐ"""
|
| 12 |
+
|
| 13 |
+
width: int = 100 # X ๋ฐฉํฅ ์
์
|
| 14 |
+
height: int = 100 # Y ๋ฐฉํฅ ์
์
|
| 15 |
+
cell_size: float = 10.0 # ์
๋น ์ค์ ๊ฑฐ๋ฆฌ (m)
|
| 16 |
+
|
| 17 |
+
# ๋์ด๋งต (m)
|
| 18 |
+
elevation: np.ndarray = field(default=None)
|
| 19 |
+
|
| 20 |
+
# ์์ ์์ฑ
|
| 21 |
+
rock_hardness: np.ndarray = field(default=None) # 0-1, 1์ด ๊ฐ์ฅ ๋จ๋จํจ
|
| 22 |
+
|
| 23 |
+
def __post_init__(self):
|
| 24 |
+
if self.elevation is None:
|
| 25 |
+
self.elevation = np.zeros((self.height, self.width))
|
| 26 |
+
if self.rock_hardness is None:
|
| 27 |
+
self.rock_hardness = np.ones((self.height, self.width)) * 0.5
|
| 28 |
+
|
| 29 |
+
@classmethod
|
| 30 |
+
def create_slope(cls, width: int, height: int,
|
| 31 |
+
max_elevation: float = 1000.0,
|
| 32 |
+
slope_direction: str = 'south') -> 'Terrain':
|
| 33 |
+
"""๊ฒฝ์ฌ๋ฉด ์งํ ์์ฑ"""
|
| 34 |
+
terrain = cls(width=width, height=height)
|
| 35 |
+
|
| 36 |
+
if slope_direction == 'south':
|
| 37 |
+
# ๋ถ์ชฝ์ด ๋๊ณ ๋จ์ชฝ์ด ๋ฎ์
|
| 38 |
+
for y in range(height):
|
| 39 |
+
terrain.elevation[y, :] = max_elevation * (1 - y / height)
|
| 40 |
+
elif slope_direction == 'east':
|
| 41 |
+
for x in range(width):
|
| 42 |
+
terrain.elevation[:, x] = max_elevation * (1 - x / width)
|
| 43 |
+
|
| 44 |
+
return terrain
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def create_v_valley_initial(cls, width: int = 100, height: int = 100,
|
| 48 |
+
valley_depth: float = 50.0) -> 'Terrain':
|
| 49 |
+
"""V์๊ณก ์๋ฎฌ๋ ์ด์
์ด๊ธฐ ์งํ"""
|
| 50 |
+
terrain = cls(width=width, height=height)
|
| 51 |
+
|
| 52 |
+
# ๊ธฐ๋ณธ ๊ฒฝ์ฌ (๋ถโ๋จ)
|
| 53 |
+
for y in range(height):
|
| 54 |
+
terrain.elevation[y, :] = 500 * (1 - y / height)
|
| 55 |
+
|
| 56 |
+
# ์ค์์ ์ด๊ธฐ ํ์ฒ ์ฑ๋ (์ฝ๊ฐ์ ํจ์)
|
| 57 |
+
center = width // 2
|
| 58 |
+
for x in range(width):
|
| 59 |
+
dist = abs(x - center)
|
| 60 |
+
if dist < 5:
|
| 61 |
+
terrain.elevation[:, x] -= valley_depth * (1 - dist / 5)
|
| 62 |
+
|
| 63 |
+
return terrain
|
| 64 |
+
|
| 65 |
+
def get_slope(self) -> np.ndarray:
|
| 66 |
+
"""๊ฐ ์
์ ๊ฒฝ์ฌ๋ ๊ณ์ฐ (๋ผ๋์)"""
|
| 67 |
+
dy, dx = np.gradient(self.elevation, self.cell_size)
|
| 68 |
+
return np.arctan(np.sqrt(dx**2 + dy**2))
|
| 69 |
+
|
| 70 |
+
def get_flow_direction(self) -> Tuple[np.ndarray, np.ndarray]:
|
| 71 |
+
"""๋ฌผ ํ๋ฆ ๋ฐฉํฅ ๋ฒกํฐ (๊ฐ์ฅ ๊ฐํ๋ฅธ ํ๊ฐ ๋ฐฉํฅ)"""
|
| 72 |
+
dy, dx = np.gradient(self.elevation, self.cell_size)
|
| 73 |
+
magnitude = np.sqrt(dx**2 + dy**2) + 1e-10
|
| 74 |
+
return -dx / magnitude, -dy / magnitude
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@dataclass
|
| 78 |
+
class Water:
|
| 79 |
+
"""ํ์ฒ ์๋ฌธ ๋ฐ์ดํฐ"""
|
| 80 |
+
|
| 81 |
+
terrain: Terrain
|
| 82 |
+
|
| 83 |
+
# ๊ฐ ์
์ ์๋ (mยณ/s)
|
| 84 |
+
discharge: np.ndarray = field(default=None)
|
| 85 |
+
|
| 86 |
+
# ์ ์ (m/s)
|
| 87 |
+
velocity: np.ndarray = field(default=None)
|
| 88 |
+
|
| 89 |
+
# ํ๋ฆ ๋ฐฉํฅ (๋จ์ ๋ฒกํฐ)
|
| 90 |
+
flow_x: np.ndarray = field(default=None)
|
| 91 |
+
flow_y: np.ndarray = field(default=None)
|
| 92 |
+
|
| 93 |
+
def __post_init__(self):
|
| 94 |
+
shape = (self.terrain.height, self.terrain.width)
|
| 95 |
+
if self.discharge is None:
|
| 96 |
+
self.discharge = np.zeros(shape)
|
| 97 |
+
if self.velocity is None:
|
| 98 |
+
self.velocity = np.zeros(shape)
|
| 99 |
+
if self.flow_x is None:
|
| 100 |
+
self.flow_x, self.flow_y = self.terrain.get_flow_direction()
|
| 101 |
+
|
| 102 |
+
def add_precipitation(self, rate: float = 0.001):
|
| 103 |
+
"""๊ฐ์ ์ถ๊ฐ (m/s per cell)"""
|
| 104 |
+
self.discharge += rate
|
| 105 |
+
|
| 106 |
+
def accumulate_flow(self):
|
| 107 |
+
"""ํ๋ฆ ๋์ ๊ณ์ฐ (๊ฐ๋จํ D8 ์๊ณ ๋ฆฌ์ฆ)"""
|
| 108 |
+
h, w = self.terrain.height, self.terrain.width
|
| 109 |
+
accumulated = self.discharge.copy()
|
| 110 |
+
|
| 111 |
+
# ๋์ ๊ณณ์์ ๋ฎ์ ๊ณณ์ผ๋ก ์ ๋ ฌ
|
| 112 |
+
indices = np.argsort(self.terrain.elevation.ravel())[::-1]
|
| 113 |
+
|
| 114 |
+
for idx in indices:
|
| 115 |
+
y, x = idx // w, idx % w
|
| 116 |
+
if accumulated[y, x] <= 0:
|
| 117 |
+
continue
|
| 118 |
+
|
| 119 |
+
# ๊ฐ์ฅ ๋ฎ์ ์ด์ ์ฐพ๊ธฐ
|
| 120 |
+
min_elev = self.terrain.elevation[y, x]
|
| 121 |
+
min_neighbor = None
|
| 122 |
+
|
| 123 |
+
for dy, dx in [(-1,0), (1,0), (0,-1), (0,1)]:
|
| 124 |
+
ny, nx = y + dy, x + dx
|
| 125 |
+
if 0 <= ny < h and 0 <= nx < w:
|
| 126 |
+
if self.terrain.elevation[ny, nx] < min_elev:
|
| 127 |
+
min_elev = self.terrain.elevation[ny, nx]
|
| 128 |
+
min_neighbor = (ny, nx)
|
| 129 |
+
|
| 130 |
+
if min_neighbor:
|
| 131 |
+
accumulated[min_neighbor] += accumulated[y, x]
|
| 132 |
+
|
| 133 |
+
self.discharge = accumulated
|
| 134 |
+
|
| 135 |
+
# ์ ์ ๊ณ์ฐ (Manning ๋ฐฉ์ ์ ๋จ์ํ)
|
| 136 |
+
slope = self.terrain.get_slope() + 0.001 # 0 ๋ฐฉ์ง
|
| 137 |
+
self.velocity = 2.0 * np.sqrt(slope) * np.power(self.discharge + 0.1, 0.4)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
@dataclass
|
| 141 |
+
class SimulationState:
|
| 142 |
+
"""์๋ฎฌ๋ ์ด์
์ํ ๊ด๋ฆฌ"""
|
| 143 |
+
|
| 144 |
+
terrain: Terrain
|
| 145 |
+
water: Water
|
| 146 |
+
|
| 147 |
+
time_step: float = 1.0 # ์๋ฎฌ๋ ์ด์
1์คํ
= 1๋
|
| 148 |
+
current_time: float = 0.0
|
| 149 |
+
|
| 150 |
+
# ์ ์ญ ๋ณ์ (Master Plan์ Global Controllers)
|
| 151 |
+
climate_level: float = 1.0 # ๊ธฐํ (๊ฐ์๋ ๊ณ์)
|
| 152 |
+
sea_level: float = 0.0 # ํด์๋ฉด (m)
|
| 153 |
+
tectonic_energy: float = 0.0 # ์ง๊ฐ ์๋์ง (์ต๊ธฐ์จ m/year)
|
| 154 |
+
|
| 155 |
+
def step(self):
|
| 156 |
+
"""1 ํ์์คํ
์งํ"""
|
| 157 |
+
self.current_time += self.time_step
|
| 158 |
+
|
| 159 |
+
# ๊ฐ์ ์ถ๊ฐ
|
| 160 |
+
self.water.add_precipitation(rate=0.001 * self.climate_level)
|
| 161 |
+
|
| 162 |
+
# ํ๋ฆ ๋์
|
| 163 |
+
self.water.accumulate_flow()
|
engine/climate.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Climate Kernel (๊ธฐํ ์ปค๋)
|
| 3 |
+
|
| 4 |
+
๊ธฐํ ์กฐ๊ฑด์ ๋ฐ๋ฅธ ๊ฐ์/๊ธฐ์จ ๋ถํฌ ์์ฑ
|
| 5 |
+
- ์๋ ๊ธฐ๋ฐ ๊ฐ์ ํจํด
|
| 6 |
+
- ๊ณ ๋ ๊ธฐ๋ฐ ์งํ์ฑ ๊ฐ์
|
| 7 |
+
- ๊ธฐ์จ์ ๋ฐ๋ฅธ ํํ์จ ์กฐ์
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
from .grid import WorldGrid
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class ClimateKernel:
|
| 15 |
+
"""
|
| 16 |
+
๊ธฐํ ์ปค๋
|
| 17 |
+
|
| 18 |
+
๊ฐ์์ ๊ธฐ์จ ๋ถํฌ๋ฅผ ์์ฑํ์ฌ ๋ค๋ฅธ ํ๋ก์ธ์ค์ ์ํฅ.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(self, grid: WorldGrid,
|
| 22 |
+
base_precipitation: float = 1000.0, # mm/year
|
| 23 |
+
base_temperature: float = 15.0, # ยฐC
|
| 24 |
+
lapse_rate: float = 6.5): # ยฐC/km (๊ณ ๋ ๊ฐ๋ฅ )
|
| 25 |
+
self.grid = grid
|
| 26 |
+
self.base_precipitation = base_precipitation
|
| 27 |
+
self.base_temperature = base_temperature
|
| 28 |
+
self.lapse_rate = lapse_rate
|
| 29 |
+
|
| 30 |
+
def generate_precipitation(self,
|
| 31 |
+
orographic_factor: float = 0.5,
|
| 32 |
+
latitude_effect: bool = True) -> np.ndarray:
|
| 33 |
+
"""
|
| 34 |
+
๊ฐ์ ๋ถํฌ ์์ฑ
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
orographic_factor: ์งํ์ฑ ๊ฐ์ ๊ฐ๋ (0~1)
|
| 38 |
+
latitude_effect: ์๋ ํจ๊ณผ ์ ์ฉ ์ฌ๋ถ
|
| 39 |
+
|
| 40 |
+
Returns:
|
| 41 |
+
precipitation: ๊ฐ์๋ ๋ฐฐ์ด (mm/year โ m/timestep์ผ๋ก ๋ณํ ํ์)
|
| 42 |
+
"""
|
| 43 |
+
h, w = self.grid.height, self.grid.width
|
| 44 |
+
elev = self.grid.elevation
|
| 45 |
+
|
| 46 |
+
# ๊ธฐ๋ณธ ๊ฐ์ (๊ท ์ผ)
|
| 47 |
+
precip = np.ones((h, w)) * self.base_precipitation
|
| 48 |
+
|
| 49 |
+
# 1. ์๋ ํจ๊ณผ (์ ๋ > ๊ทน์ง๋ฐฉ)
|
| 50 |
+
if latitude_effect:
|
| 51 |
+
# ๊ทธ๋ฆฌ๋ Y์ถ์ ์๋๋ก ๊ทผ์ฌ (0=์ ๋, h=๊ทน)
|
| 52 |
+
# ์ด๋ = ๊ฐ์ฅ ๋ง์, ์์ด๋ ๊ฑด์กฐ, ์จ๋ ์ฆ๊ฐ, ๊ทน ๊ฐ์
|
| 53 |
+
lat_factor = np.zeros(h)
|
| 54 |
+
for r in range(h):
|
| 55 |
+
# ์ ๊ทํ๋ ์๋ (0~1)
|
| 56 |
+
normalized_lat = r / h
|
| 57 |
+
# ๊ฐ๋จํ ์๋-๊ฐ์ ๊ด๊ณ (์ผ๋ด ํจํด ๊ทผ์ฌ)
|
| 58 |
+
lat_factor[r] = 1.0 - 0.3 * abs(normalized_lat - 0.5) * 2
|
| 59 |
+
|
| 60 |
+
precip *= lat_factor[:, np.newaxis]
|
| 61 |
+
|
| 62 |
+
# 2. ์งํ์ฑ ๊ฐ์ (๋ฐ๋๋ฐ์ด vs ๊ทธ๋)
|
| 63 |
+
if orographic_factor > 0:
|
| 64 |
+
# ๋์ชฝ์์ ๋ฐ๋์ด ๋ถ๋ค๊ณ ๊ฐ์
|
| 65 |
+
# ๊ณ ๋ ์ฆ๊ฐ ๊ตฌ๊ฐ = ๊ฐ์ ์ฆ๊ฐ (์์น ๊ธฐ๋ฅ)
|
| 66 |
+
# ๊ณ ๋ ๊ฐ์ ๊ตฌ๊ฐ = ๊ฐ์ ๊ฐ์ (ํ๊ฐ ๊ธฐ๋ฅ)
|
| 67 |
+
|
| 68 |
+
# X ๋ฐฉํฅ ๊ฒฝ์ฌ
|
| 69 |
+
_, dx = self.grid.get_gradient()
|
| 70 |
+
|
| 71 |
+
# ์์ ๊ฒฝ์ฌ = ๋์ชฝ์ผ๋ก ์์น = ๊ฐ์ ์ฆ๊ฐ
|
| 72 |
+
orographic = 1.0 + orographic_factor * (-dx) * 0.1
|
| 73 |
+
orographic = np.clip(orographic, 0.2, 2.0)
|
| 74 |
+
|
| 75 |
+
precip *= orographic
|
| 76 |
+
|
| 77 |
+
# 3. ๊ณ ๋ ํจ๊ณผ (์ผ์ ๊ณ ๋๊น์ง๋ ์ฆ๊ฐ, ์ดํ ๊ฐ์)
|
| 78 |
+
# ์ต๋ ๊ฐ์ ๊ณ ๋ (์: 2000m)
|
| 79 |
+
optimal_elev = 2000.0
|
| 80 |
+
elev_effect = 1.0 - 0.2 * np.abs(elev - optimal_elev) / optimal_elev
|
| 81 |
+
elev_effect = np.clip(elev_effect, 0.3, 1.2)
|
| 82 |
+
|
| 83 |
+
precip *= elev_effect
|
| 84 |
+
|
| 85 |
+
return precip
|
| 86 |
+
|
| 87 |
+
def get_temperature(self) -> np.ndarray:
|
| 88 |
+
"""
|
| 89 |
+
๊ธฐ์จ ๋ถํฌ ์์ฑ
|
| 90 |
+
|
| 91 |
+
Returns:
|
| 92 |
+
temperature: ๊ธฐ์จ ๋ฐฐ์ด (ยฐC)
|
| 93 |
+
"""
|
| 94 |
+
h, w = self.grid.height, self.grid.width
|
| 95 |
+
elev = self.grid.elevation
|
| 96 |
+
|
| 97 |
+
# ๊ธฐ๋ณธ ๊ธฐ์จ
|
| 98 |
+
temp = np.ones((h, w)) * self.base_temperature
|
| 99 |
+
|
| 100 |
+
# 1. ์๋ ํจ๊ณผ (์ ๋ > ๊ทน)
|
| 101 |
+
for r in range(h):
|
| 102 |
+
normalized_lat = r / h
|
| 103 |
+
# ์ ๋(0.5) = ๊ธฐ๋ณธ, ๊ทน(0, 1) = -30ยฐC
|
| 104 |
+
lat_temp_diff = 30.0 * abs(normalized_lat - 0.5) * 2
|
| 105 |
+
temp[r, :] -= lat_temp_diff
|
| 106 |
+
|
| 107 |
+
# 2. ๊ณ ๋ ํจ๊ณผ (์ฒด๊ฐ ์จ๋ ๊ฐ๋ฅ )
|
| 108 |
+
# ํด์๋ฉด ๊ธฐ์ค์์ km๋น lapse_rate๋งํผ ๊ฐ์
|
| 109 |
+
temp -= (elev / 1000.0) * self.lapse_rate
|
| 110 |
+
|
| 111 |
+
return temp
|
| 112 |
+
|
| 113 |
+
def get_weathering_rate(self, temperature: np.ndarray = None) -> np.ndarray:
|
| 114 |
+
"""
|
| 115 |
+
๊ธฐ์จ์ ๋ฐ๋ฅธ ํํ์จ ๊ณ์ฐ
|
| 116 |
+
|
| 117 |
+
ํํ์ ํํ: ์จ๋ ๋ค์ต โ ๋น ๋ฆ
|
| 118 |
+
๋ฌผ๋ฆฌ์ ํํ: ๋๊ฒฐ-์ตํด (-10~10ยฐC) โ ๋น ๋ฆ
|
| 119 |
+
|
| 120 |
+
Args:
|
| 121 |
+
temperature: ๊ธฐ์จ ๋ฐฐ์ด (์์ผ๋ฉด ์์ฑ)
|
| 122 |
+
|
| 123 |
+
Returns:
|
| 124 |
+
weathering_rate: ์๋ ํํ์จ (0~1)
|
| 125 |
+
"""
|
| 126 |
+
if temperature is None:
|
| 127 |
+
temperature = self.get_temperature()
|
| 128 |
+
|
| 129 |
+
h, w = self.grid.height, self.grid.width
|
| 130 |
+
|
| 131 |
+
# ํํ์ ํํ (์จ๋ ๋์์๋ก)
|
| 132 |
+
chemical = np.clip((temperature + 10) / 40.0, 0, 1)
|
| 133 |
+
|
| 134 |
+
# ๋ฌผ๋ฆฌ์ ํํ (๋๊ฒฐ-์ตํด ๋ฒ์์์ ์ต๋)
|
| 135 |
+
freeze_thaw = np.exp(-((temperature - 0) ** 2) / (2 * 10 ** 2))
|
| 136 |
+
|
| 137 |
+
# ํตํฉ ํํ์จ
|
| 138 |
+
weathering = chemical * 0.5 + freeze_thaw * 0.5
|
| 139 |
+
|
| 140 |
+
return weathering
|
engine/delta_physics.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI: ์ผ๊ฐ์ฃผ ๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
|
| 3 |
+
Galloway ๋ถ๋ฅ ๊ธฐ๋ฐ 3๊ฐ์ง ์๋์ง ๊ท ํ
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from typing import List, Tuple
|
| 8 |
+
from enum import Enum
|
| 9 |
+
from scipy.ndimage import gaussian_filter
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class DeltaType(Enum):
|
| 13 |
+
RIVER_DOMINATED = "์กฐ์กฑ์ (Bird's Foot)"
|
| 14 |
+
WAVE_DOMINATED = "์ํธ์ (Arcuate)"
|
| 15 |
+
TIDE_DOMINATED = "์ฒจ๊ฐ์ (Cuspate)"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class DeltaGrid:
|
| 20 |
+
"""์ผ๊ฐ์ฃผ 2D ๊ทธ๋ฆฌ๋"""
|
| 21 |
+
width: int = 150
|
| 22 |
+
height: int = 150
|
| 23 |
+
cell_size: float = 50.0 # m
|
| 24 |
+
|
| 25 |
+
# ์งํ (ํด์๋ฉด ๊ธฐ์ค ๊ณ ๋, m)
|
| 26 |
+
elevation: np.ndarray = field(default=None)
|
| 27 |
+
# ํด์ ๋ฌผ (m)
|
| 28 |
+
sediment: np.ndarray = field(default=None)
|
| 29 |
+
# ์์ฌ (๋ฐ๋ค ๋ถ๋ถ)
|
| 30 |
+
water_depth: np.ndarray = field(default=None)
|
| 31 |
+
|
| 32 |
+
sea_level: float = 0.0
|
| 33 |
+
|
| 34 |
+
def __post_init__(self):
|
| 35 |
+
if self.elevation is None:
|
| 36 |
+
self.elevation = np.zeros((self.height, self.width))
|
| 37 |
+
if self.sediment is None:
|
| 38 |
+
self.sediment = np.zeros((self.height, self.width))
|
| 39 |
+
if self.water_depth is None:
|
| 40 |
+
self.water_depth = np.zeros((self.height, self.width))
|
| 41 |
+
|
| 42 |
+
@classmethod
|
| 43 |
+
def create_initial(cls, width: int = 150, height: int = 150,
|
| 44 |
+
land_fraction: float = 0.3):
|
| 45 |
+
"""์ด๊ธฐ ์งํ - ์๋ฅ(์ก์ง) โ ํ๋ฅ(๋ฐ๋ค)"""
|
| 46 |
+
grid = cls(width=width, height=height)
|
| 47 |
+
|
| 48 |
+
land_rows = int(height * land_fraction)
|
| 49 |
+
|
| 50 |
+
for y in range(height):
|
| 51 |
+
if y < land_rows:
|
| 52 |
+
# ์ก์ง: ๊ฒฝ์ฌ
|
| 53 |
+
grid.elevation[y, :] = 10 - y * 0.3
|
| 54 |
+
else:
|
| 55 |
+
# ๋ฐ๋ค: ํด์๋ฉด ์๋
|
| 56 |
+
depth = (y - land_rows) * 0.2
|
| 57 |
+
grid.elevation[y, :] = -depth
|
| 58 |
+
|
| 59 |
+
# ํ์ฒ ์ฑ๋
|
| 60 |
+
center = width // 2
|
| 61 |
+
for x in range(center - 5, center + 6):
|
| 62 |
+
if 0 <= x < width:
|
| 63 |
+
grid.elevation[:land_rows, x] -= 2
|
| 64 |
+
|
| 65 |
+
# ์์ฌ ๊ณ์ฐ
|
| 66 |
+
grid.water_depth = np.maximum(0, grid.sea_level - grid.elevation)
|
| 67 |
+
|
| 68 |
+
return grid
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class RiverMouthDeposition:
|
| 72 |
+
"""ํ๊ตฌ ํด์ (ํ์ฒ ์ฃผ๋)"""
|
| 73 |
+
|
| 74 |
+
def __init__(self, sediment_flux: float = 1000.0, # mยณ/yr
|
| 75 |
+
settling_velocity: float = 0.01): # m/s
|
| 76 |
+
self.sediment_flux = sediment_flux
|
| 77 |
+
self.settling_velocity = settling_velocity
|
| 78 |
+
|
| 79 |
+
def deposit(self, grid: DeltaGrid, river_energy: float,
|
| 80 |
+
channel_x: int, dt: float = 1.0) -> np.ndarray:
|
| 81 |
+
"""ํ์ฒ ํด์ ๋ฌผ ๋ถ๋ฐฐ"""
|
| 82 |
+
h, w = grid.height, grid.width
|
| 83 |
+
deposition = np.zeros((h, w))
|
| 84 |
+
|
| 85 |
+
# ํ๊ตฌ ์์น
|
| 86 |
+
estuary_y = np.argmax(grid.elevation[:, channel_x] < grid.sea_level)
|
| 87 |
+
if estuary_y == 0:
|
| 88 |
+
estuary_y = h // 2
|
| 89 |
+
|
| 90 |
+
# ํ์ฒ ์ฐ์ธ: ๊ธธ๊ฒ ๋ป์ด๋๊ฐ๋ ํจํด (jet ํ์ฐ)
|
| 91 |
+
spread_angle = np.radians(15 + (1 - river_energy) * 30) # ํ์ฒ ์๋์ง ๋์ผ๋ฉด ์ข๊ฒ
|
| 92 |
+
|
| 93 |
+
for y in range(estuary_y, h):
|
| 94 |
+
dy = y - estuary_y + 1
|
| 95 |
+
spread = int(dy * np.tan(spread_angle))
|
| 96 |
+
|
| 97 |
+
for x in range(channel_x - spread, channel_x + spread + 1):
|
| 98 |
+
if 0 <= x < w:
|
| 99 |
+
# ๊ฑฐ๋ฆฌ์ ๋ฐ๋ฅธ ๊ฐ์
|
| 100 |
+
dist = np.sqrt((x - channel_x)**2 + dy**2)
|
| 101 |
+
dep_rate = self.sediment_flux * river_energy * np.exp(-dist / 50) * 0.001
|
| 102 |
+
deposition[y, x] += dep_rate * dt
|
| 103 |
+
|
| 104 |
+
return deposition
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class WaveRedistribution:
|
| 108 |
+
"""ํ๋์ ์ํ ์ฌ๋ถ๋ฐฐ"""
|
| 109 |
+
|
| 110 |
+
def __init__(self, wave_power: float = 1.0):
|
| 111 |
+
self.wave_power = wave_power
|
| 112 |
+
|
| 113 |
+
def redistribute(self, grid: DeltaGrid, wave_energy: float,
|
| 114 |
+
dt: float = 1.0) -> np.ndarray:
|
| 115 |
+
"""ํ๋์ ์ํ ํด์ ๋ฌผ ์ข์ฐ ํ์ฐ"""
|
| 116 |
+
# ํด์์ ๋ถ๊ทผ์์ ์ข์ฐ๋ก ํผ๋จ๋ฆผ
|
| 117 |
+
shoreline = np.abs(grid.elevation - grid.sea_level) < 2
|
| 118 |
+
|
| 119 |
+
# ๊ฐ์ฐ์์ ํํฐ๋ก ์ข์ฐ ํ์ฐ (x์ถ ๋ฐฉํฅ)
|
| 120 |
+
sediment_change = np.zeros_like(grid.sediment)
|
| 121 |
+
|
| 122 |
+
if np.any(shoreline):
|
| 123 |
+
# ํด์์ ๋ถ๊ทผ ํด์ ๋ฌผ๋ง ํ์ฐ
|
| 124 |
+
coastal_sediment = grid.sediment * shoreline.astype(float)
|
| 125 |
+
smoothed = gaussian_filter(coastal_sediment, sigma=[0.5, 3 * wave_energy])
|
| 126 |
+
|
| 127 |
+
# ๋ณํ๋
|
| 128 |
+
sediment_change = (smoothed - coastal_sediment) * wave_energy * dt * 0.1
|
| 129 |
+
|
| 130 |
+
return sediment_change
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class TidalScouring:
|
| 134 |
+
"""์กฐ๋ฅ์ ์ํ ์นจ์"""
|
| 135 |
+
|
| 136 |
+
def __init__(self, tidal_range: float = 2.0): # m
|
| 137 |
+
self.tidal_range = tidal_range
|
| 138 |
+
|
| 139 |
+
def scour(self, grid: DeltaGrid, tidal_energy: float,
|
| 140 |
+
dt: float = 1.0) -> np.ndarray:
|
| 141 |
+
"""์กฐ๋ฅ์ ์ํ ํ๊ตฌ ํ๋ (์ธ๊ตด)"""
|
| 142 |
+
h, w = grid.height, grid.width
|
| 143 |
+
erosion = np.zeros((h, w))
|
| 144 |
+
|
| 145 |
+
# ํ๊ตฌ ๋ถ๊ทผ (ํด์๋ฉด ๊ทผ๏ฟฝ๏ฟฝ)
|
| 146 |
+
tidal_zone = np.abs(grid.elevation - grid.sea_level) < self.tidal_range
|
| 147 |
+
|
| 148 |
+
# ์ฑ๋ ๋ฐฉํฅ์ผ๋ก ์ธ๊ตด
|
| 149 |
+
for y in range(1, h):
|
| 150 |
+
for x in range(1, w-1):
|
| 151 |
+
if tidal_zone[y, x] and grid.sediment[y, x] > 0:
|
| 152 |
+
# ๋ฐ๋ค ๋ฐฉํฅ์ผ๋ก ํด์ ๋ฌผ ์ด๋
|
| 153 |
+
erosion[y, x] = grid.sediment[y, x] * tidal_energy * 0.01 * dt
|
| 154 |
+
|
| 155 |
+
return erosion
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class DeltaSimulation:
|
| 159 |
+
"""์ผ๊ฐ์ฃผ ์๋ฎฌ๋ ์ด์
"""
|
| 160 |
+
|
| 161 |
+
def __init__(self, width: int = 150, height: int = 150):
|
| 162 |
+
self.grid = DeltaGrid.create_initial(width=width, height=height)
|
| 163 |
+
|
| 164 |
+
self.river_dep = RiverMouthDeposition()
|
| 165 |
+
self.wave_redis = WaveRedistribution()
|
| 166 |
+
self.tidal_scour = TidalScouring()
|
| 167 |
+
|
| 168 |
+
self.river_energy = 0.5
|
| 169 |
+
self.wave_energy = 0.3
|
| 170 |
+
self.tidal_energy = 0.2
|
| 171 |
+
|
| 172 |
+
self.history: List[np.ndarray] = []
|
| 173 |
+
self.time = 0.0
|
| 174 |
+
|
| 175 |
+
def set_energy_balance(self, river: float, wave: float, tidal: float):
|
| 176 |
+
"""์๋์ง ๊ท ํ ์ค์ (0-1๋ก ์ ๊ทํ)"""
|
| 177 |
+
total = river + wave + tidal + 0.01
|
| 178 |
+
self.river_energy = river / total
|
| 179 |
+
self.wave_energy = wave / total
|
| 180 |
+
self.tidal_energy = tidal / total
|
| 181 |
+
|
| 182 |
+
def step(self, dt: float = 1.0):
|
| 183 |
+
"""1 ํ์์คํ
"""
|
| 184 |
+
center_x = self.grid.width // 2
|
| 185 |
+
|
| 186 |
+
# 1. ํ์ฒ ํด์
|
| 187 |
+
river_dep = self.river_dep.deposit(
|
| 188 |
+
self.grid, self.river_energy, center_x, dt
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
# 2. ํ๋ ์ฌ๋ถ๋ฐฐ
|
| 192 |
+
wave_change = self.wave_redis.redistribute(
|
| 193 |
+
self.grid, self.wave_energy, dt
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
# 3. ์กฐ๋ฅ ์ธ๊ตด
|
| 197 |
+
tidal_erosion = self.tidal_scour.scour(
|
| 198 |
+
self.grid, self.tidal_energy, dt
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# ์ ์ฉ
|
| 202 |
+
self.grid.sediment += river_dep + wave_change - tidal_erosion
|
| 203 |
+
self.grid.sediment = np.maximum(0, self.grid.sediment)
|
| 204 |
+
|
| 205 |
+
# ์งํ ์
๋ฐ์ดํธ
|
| 206 |
+
self.grid.elevation = self.grid.elevation + self.grid.sediment * 0.01
|
| 207 |
+
self.grid.water_depth = np.maximum(0, self.grid.sea_level - self.grid.elevation)
|
| 208 |
+
|
| 209 |
+
self.time += dt
|
| 210 |
+
|
| 211 |
+
def run(self, total_time: float, save_interval: float = 100.0, dt: float = 1.0):
|
| 212 |
+
"""์๋ฎฌ๋ ์ด์
์คํ"""
|
| 213 |
+
steps = int(total_time / dt)
|
| 214 |
+
save_every = max(1, int(save_interval / dt))
|
| 215 |
+
|
| 216 |
+
self.history = [self.grid.elevation.copy()]
|
| 217 |
+
|
| 218 |
+
for i in range(steps):
|
| 219 |
+
self.step(dt)
|
| 220 |
+
if (i + 1) % save_every == 0:
|
| 221 |
+
self.history.append(self.grid.elevation.copy())
|
| 222 |
+
|
| 223 |
+
return self.history
|
| 224 |
+
|
| 225 |
+
def get_delta_type(self) -> DeltaType:
|
| 226 |
+
"""ํ์ฌ ์ผ๊ฐ์ฃผ ์ ํ ํ๋ณ"""
|
| 227 |
+
if self.river_energy >= self.wave_energy and self.river_energy >= self.tidal_energy:
|
| 228 |
+
return DeltaType.RIVER_DOMINATED
|
| 229 |
+
elif self.wave_energy >= self.tidal_energy:
|
| 230 |
+
return DeltaType.WAVE_DOMINATED
|
| 231 |
+
else:
|
| 232 |
+
return DeltaType.TIDE_DOMINATED
|
| 233 |
+
|
| 234 |
+
def get_delta_area(self) -> float:
|
| 235 |
+
"""์ผ๊ฐ์ฃผ ๋ฉด์ (ํด์๋ฉด ์ ์ ๋
)"""
|
| 236 |
+
initial_land = self.history[0] > self.grid.sea_level
|
| 237 |
+
current_land = self.grid.elevation > self.grid.sea_level
|
| 238 |
+
new_land = current_land & ~initial_land
|
| 239 |
+
return float(np.sum(new_land)) * self.grid.cell_size**2 / 1e6 # kmยฒ
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
# ํ๋ฆฌ์ปดํจํ
|
| 243 |
+
def precompute_delta(max_time: int = 10000,
|
| 244 |
+
river_energy: float = 60,
|
| 245 |
+
wave_energy: float = 25,
|
| 246 |
+
tidal_energy: float = 15,
|
| 247 |
+
save_every: int = 100) -> dict:
|
| 248 |
+
"""์ผ๊ฐ์ฃผ ์๋ฎฌ๋ ์ด์
ํ๋ฆฌ์ปดํจํ
"""
|
| 249 |
+
sim = DeltaSimulation()
|
| 250 |
+
sim.set_energy_balance(river_energy, wave_energy, tidal_energy)
|
| 251 |
+
|
| 252 |
+
history = sim.run(
|
| 253 |
+
total_time=max_time,
|
| 254 |
+
save_interval=save_every,
|
| 255 |
+
dt=1.0
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
return {
|
| 259 |
+
'history': history,
|
| 260 |
+
'delta_type': sim.get_delta_type().value,
|
| 261 |
+
'delta_area': sim.get_delta_area()
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
if __name__ == "__main__":
|
| 266 |
+
print("์ผ๊ฐ์ฃผ ๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
ํ
์คํธ")
|
| 267 |
+
print("=" * 50)
|
| 268 |
+
|
| 269 |
+
# ์๋๋ฆฌ์ค 1: ํ์ฒ ์ฐ์ธ
|
| 270 |
+
sim1 = DeltaSimulation()
|
| 271 |
+
sim1.set_energy_balance(80, 10, 10)
|
| 272 |
+
sim1.run(5000, save_interval=1000)
|
| 273 |
+
print(f"ํ์ฒ ์ฐ์ธ: {sim1.get_delta_type().value}, ๋ฉด์ {sim1.get_delta_area():.2f} kmยฒ")
|
| 274 |
+
|
| 275 |
+
# ์๋๋ฆฌ์ค 2: ํ๋ ์ฐ์ธ
|
| 276 |
+
sim2 = DeltaSimulation()
|
| 277 |
+
sim2.set_energy_balance(30, 60, 10)
|
| 278 |
+
sim2.run(5000, save_interval=1000)
|
| 279 |
+
print(f"ํ๋ ์ฐ์ธ: {sim2.get_delta_type().value}, ๋ฉด์ {sim2.get_delta_area():.2f} kmยฒ")
|
| 280 |
+
|
| 281 |
+
print("=" * 50)
|
| 282 |
+
print("ํ
์คํธ ์๋ฃ!")
|
engine/deposition.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI Engine: ํด์ ๋ก์ง
|
| 3 |
+
์ ์ ๊ฐ์์ ๋ฐ๋ฅธ ์
์๋ณ ํด์ ๊ตฌํ
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from typing import TYPE_CHECKING
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
from .base import Terrain, Water
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def settling_deposition(terrain: 'Terrain', water: 'Water',
|
| 13 |
+
sediment_load: np.ndarray,
|
| 14 |
+
critical_velocity: float = 0.5,
|
| 15 |
+
dt: float = 1.0) -> tuple:
|
| 16 |
+
"""
|
| 17 |
+
์ ์ ๊ฐ์์ ๋ฐ๋ฅธ ํด์ (Stokes' Law ๊ธฐ๋ฐ)
|
| 18 |
+
์ ์์ด ์๊ณ๊ฐ ์ดํ๋ก ๋จ์ด์ง๋ฉด ํด์ ๋ฌผ์ด ์์
|
| 19 |
+
|
| 20 |
+
Parameters:
|
| 21 |
+
-----------
|
| 22 |
+
terrain : Terrain
|
| 23 |
+
์งํ ๊ฐ์ฒด
|
| 24 |
+
water : Water
|
| 25 |
+
์๋ฌธ ๊ฐ์ฒด
|
| 26 |
+
sediment_load : np.ndarray
|
| 27 |
+
ํ์ฌ ์ด๋ฐ ์ค์ธ ํด์ ๋ฌผ๋
|
| 28 |
+
critical_velocity : float
|
| 29 |
+
ํด์ ์ด ์์๋๋ ์๊ณ ์ ์
|
| 30 |
+
dt : float
|
| 31 |
+
์๊ฐ ๋จ์
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
--------
|
| 35 |
+
deposition_amount : np.ndarray
|
| 36 |
+
ํด์ ๋
|
| 37 |
+
remaining_sediment : np.ndarray
|
| 38 |
+
๋จ์ ํด์ ๋ฌผ๋
|
| 39 |
+
"""
|
| 40 |
+
# ์ ์์ด ๋ฎ์ ๊ณณ์์ ํด์
|
| 41 |
+
velocity_ratio = water.velocity / (critical_velocity + 0.01)
|
| 42 |
+
deposition_rate = np.maximum(0, 1 - velocity_ratio)
|
| 43 |
+
|
| 44 |
+
deposition_amount = sediment_load * deposition_rate * dt
|
| 45 |
+
remaining_sediment = sediment_load - deposition_amount
|
| 46 |
+
|
| 47 |
+
return deposition_amount, np.maximum(remaining_sediment, 0)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def alluvial_fan_deposition(terrain: 'Terrain', water: 'Water',
|
| 51 |
+
sediment_load: np.ndarray,
|
| 52 |
+
slope_threshold: float = 0.1,
|
| 53 |
+
dt: float = 1.0) -> np.ndarray:
|
| 54 |
+
"""
|
| 55 |
+
์ ์์ง(Alluvial Fan) ํด์
|
| 56 |
+
๊ธ๊ฒฉํ ๊ฒฝ์ฌ ๋ณํ ์ง์ (์ฐ์งโํ์ง)์์ ๋ถ์ฑ๊ผด ํด์
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
--------
|
| 60 |
+
deposition_amount : np.ndarray
|
| 61 |
+
"""
|
| 62 |
+
slope = terrain.get_slope()
|
| 63 |
+
|
| 64 |
+
# ๊ฒฝ์ฌ ๋ณํ์จ ๊ณ์ฐ
|
| 65 |
+
slope_change = np.gradient(slope, axis=0) + np.gradient(slope, axis=1)
|
| 66 |
+
|
| 67 |
+
# ๊ฒฝ์ฌ๊ฐ ๊ธ๊ฒฉํ ์ค์ด๋๋ ๊ณณ(์์ ๋ณํ์จ)์์ ํด์
|
| 68 |
+
fan_zone = slope_change < -slope_threshold
|
| 69 |
+
|
| 70 |
+
deposition = np.zeros_like(sediment_load)
|
| 71 |
+
deposition[fan_zone] = sediment_load[fan_zone] * 0.8 * dt
|
| 72 |
+
|
| 73 |
+
return deposition
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def levee_backswamp_deposition(terrain: 'Terrain', water: 'Water',
|
| 77 |
+
flood_level: float = 1.0,
|
| 78 |
+
dt: float = 1.0) -> tuple:
|
| 79 |
+
"""
|
| 80 |
+
์์ฐ์ ๋ฐฉ(Levee) & ๋ฐฐํ์ต์ง(Backswamp) ํด์
|
| 81 |
+
ํ์ ์ ํ์ฒ ๊ฐ๊น์ด์ ๊ตต์ ์
์, ๋ฉ๋ฆฌ์ ๋ฏธ๋ฆฝ ์
์ ํด์
|
| 82 |
+
|
| 83 |
+
Returns:
|
| 84 |
+
--------
|
| 85 |
+
levee_deposition : np.ndarray
|
| 86 |
+
์์ฐ์ ๋ฐฉ ํด์ ๋ (๋ชจ๋ - ๋๊บผ์)
|
| 87 |
+
backswamp_deposition : np.ndarray
|
| 88 |
+
๋ฐฐํ์ต์ง ํด์ ๋ (์ ํ - ์์)
|
| 89 |
+
"""
|
| 90 |
+
h, w = terrain.height, terrain.width
|
| 91 |
+
|
| 92 |
+
# ํ์ฒ ์์น (๋์ ์ ๋)
|
| 93 |
+
channel_mask = water.discharge > np.percentile(water.discharge, 80)
|
| 94 |
+
|
| 95 |
+
levee = np.zeros((h, w))
|
| 96 |
+
backswamp = np.zeros((h, w))
|
| 97 |
+
|
| 98 |
+
# ํ์ฒ์ผ๋ก๋ถํฐ์ ๊ฑฐ๋ฆฌ ๊ณ์ฐ (๊ฐ๋จํ ํ์ฐ)
|
| 99 |
+
from scipy.ndimage import distance_transform_edt
|
| 100 |
+
if np.any(channel_mask):
|
| 101 |
+
distance = distance_transform_edt(~channel_mask)
|
| 102 |
+
|
| 103 |
+
# ์์ฐ์ ๋ฐฉ: ํ์ฒ ๋ฐ๋ก ์ (2-5์
)
|
| 104 |
+
levee_zone = (distance > 1) & (distance < 5)
|
| 105 |
+
levee[levee_zone] = flood_level * 0.3 * (5 - distance[levee_zone]) / 4 * dt
|
| 106 |
+
|
| 107 |
+
# ๋ฐฐํ์ต์ง: ๋ ๋จผ ๊ณณ (5-15์
)
|
| 108 |
+
backswamp_zone = (distance >= 5) & (distance < 15)
|
| 109 |
+
backswamp[backswamp_zone] = flood_level * 0.05 * dt
|
| 110 |
+
|
| 111 |
+
return levee, backswamp
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def delta_deposition(terrain: 'Terrain', water: 'Water',
|
| 115 |
+
river_energy: float = 1.0,
|
| 116 |
+
wave_energy: float = 0.5,
|
| 117 |
+
tidal_energy: float = 0.3,
|
| 118 |
+
sea_level: float = 0.0,
|
| 119 |
+
dt: float = 1.0) -> np.ndarray:
|
| 120 |
+
"""
|
| 121 |
+
์ผ๊ฐ์ฃผ(Delta) ํด์
|
| 122 |
+
3๊ฐ์ง ์๋์ง(ํ์ฒ/ํ๋/์กฐ๋ฅ) ๊ท ํ์ ๋ฐ๋ผ ํํ ๊ฒฐ์
|
| 123 |
+
|
| 124 |
+
- ํ์ฒ ์ฐ์ธ: ์กฐ์กฑ์(Bird's foot) - ๋ฏธ์์ํผํ
|
| 125 |
+
- ํ๋ ์ฐ์ธ: ์ํธ์(Arcuate) - ๋์ผํ
|
| 126 |
+
- ์กฐ๋ฅ ์ฐ์ธ: ์ฒจ๊ฐ์(Cuspate) - ํฐ๋ฒ ๋ฅดํ
|
| 127 |
+
|
| 128 |
+
Returns:
|
| 129 |
+
--------
|
| 130 |
+
deposition_amount : np.ndarray
|
| 131 |
+
"""
|
| 132 |
+
h, w = terrain.height, terrain.width
|
| 133 |
+
deposition = np.zeros((h, w))
|
| 134 |
+
|
| 135 |
+
# ํด์๋ฉด ๊ทผ์ฒ (ํ๊ตฌ)
|
| 136 |
+
estuary_zone = (terrain.elevation > sea_level - 5) & (terrain.elevation < sea_level + 10)
|
| 137 |
+
channel_mask = water.discharge > np.percentile(water.discharge, 70)
|
| 138 |
+
delta_zone = estuary_zone & channel_mask
|
| 139 |
+
|
| 140 |
+
if not np.any(delta_zone):
|
| 141 |
+
return deposition
|
| 142 |
+
|
| 143 |
+
# ์๋์ง ๋น์จ ์ ๊ทํ
|
| 144 |
+
total_energy = river_energy + wave_energy + tidal_energy + 0.01
|
| 145 |
+
r_ratio = river_energy / total_energy
|
| 146 |
+
w_ratio = wave_energy / total_energy
|
| 147 |
+
t_ratio = tidal_energy / total_energy
|
| 148 |
+
|
| 149 |
+
# ํ์ฒ ์ฐ์ธ: ๊ธธ๊ฒ ๋ป์ด๋๊ฐ๋ ํจํด
|
| 150 |
+
if r_ratio > 0.5:
|
| 151 |
+
# ํ๋ฆ ๏ฟฝ๏ฟฝ๏ฟฝํฅ์ผ๋ก ํด์ ๋ฌผ ํ์ฅ
|
| 152 |
+
deposition[delta_zone] = water.discharge[delta_zone] * r_ratio * 0.1 * dt
|
| 153 |
+
|
| 154 |
+
# ํ๋ ์ฐ์ธ: ๋๊ฒ ํผ์ง๋ ์ํธ ํจํด
|
| 155 |
+
elif w_ratio > 0.4:
|
| 156 |
+
# ์ข์ฐ๋ก ํผ์ง๊ฒ
|
| 157 |
+
from scipy.ndimage import gaussian_filter
|
| 158 |
+
base = np.zeros((h, w))
|
| 159 |
+
base[delta_zone] = water.discharge[delta_zone] * 0.1
|
| 160 |
+
deposition = gaussian_filter(base, sigma=3) * w_ratio * dt
|
| 161 |
+
|
| 162 |
+
# ์กฐ๋ฅ ์ฐ์ธ: ์์ ์ฌ ํํ
|
| 163 |
+
else:
|
| 164 |
+
# ์ข์ ์์ญ์ ์ง์ค
|
| 165 |
+
deposition[delta_zone] = water.discharge[delta_zone] * t_ratio * 0.05 * dt
|
| 166 |
+
|
| 167 |
+
return deposition
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def apply_deposition(terrain: 'Terrain', deposition_amount: np.ndarray):
|
| 171 |
+
"""
|
| 172 |
+
์งํ์ ํด์ ์ ์ฉ
|
| 173 |
+
"""
|
| 174 |
+
terrain.elevation += deposition_amount
|
engine/erosion.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI Engine: ์นจ์ ๋ก์ง
|
| 3 |
+
Stream Power Law ๊ธฐ๋ฐ ํ๋ฐฉ/์ธก๋ฐฉ ์นจ์ ๊ตฌํ
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from typing import TYPE_CHECKING
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
from .base import Terrain, Water
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def vertical_erosion(terrain: 'Terrain', water: 'Water',
|
| 13 |
+
k_erosion: float = 0.0001,
|
| 14 |
+
m_exponent: float = 0.5,
|
| 15 |
+
n_exponent: float = 1.0,
|
| 16 |
+
dt: float = 1.0) -> np.ndarray:
|
| 17 |
+
"""
|
| 18 |
+
ํ๋ฐฉ ์นจ์ (Vertical/Downcutting Erosion)
|
| 19 |
+
Stream Power Law: E = K * A^m * S^n
|
| 20 |
+
|
| 21 |
+
Parameters:
|
| 22 |
+
-----------
|
| 23 |
+
terrain : Terrain
|
| 24 |
+
์งํ ๊ฐ์ฒด
|
| 25 |
+
water : Water
|
| 26 |
+
์๋ฌธ ๊ฐ์ฒด
|
| 27 |
+
k_erosion : float
|
| 28 |
+
์นจ์ ๊ณ์ (์์ ๊ฒฝ๋์ ์ญ์)
|
| 29 |
+
m_exponent : float
|
| 30 |
+
์ ๋ ์ง์ (๋ณดํต 0.4-0.6)
|
| 31 |
+
n_exponent : float
|
| 32 |
+
๊ฒฝ์ฌ ์ง์ (๋ณดํต 1.0)
|
| 33 |
+
dt : float
|
| 34 |
+
์๊ฐ ๋จ์ (๋
)
|
| 35 |
+
|
| 36 |
+
Returns:
|
| 37 |
+
--------
|
| 38 |
+
erosion_amount : np.ndarray
|
| 39 |
+
๊ฐ ์
์ ์นจ์๋ (m)
|
| 40 |
+
"""
|
| 41 |
+
# ๊ฒฝ์ฌ ๊ณ์ฐ
|
| 42 |
+
slope = terrain.get_slope()
|
| 43 |
+
|
| 44 |
+
# Stream Power Law
|
| 45 |
+
# E = K * Q^m * S^n
|
| 46 |
+
# ์์ ๊ฒฝ๋๋ก K ์กฐ์ (๊ฒฝ๋๊ฐ ๋์ผ๋ฉด ์นจ์์ด ์ ์)
|
| 47 |
+
effective_k = k_erosion * (1 - terrain.rock_hardness * 0.9)
|
| 48 |
+
|
| 49 |
+
erosion_rate = effective_k * np.power(water.discharge + 0.1, m_exponent) * np.power(slope + 0.001, n_exponent)
|
| 50 |
+
|
| 51 |
+
erosion_amount = erosion_rate * dt
|
| 52 |
+
|
| 53 |
+
# ์นจ์๋ ์ ํ (๋๋ฌด ๊ธ๊ฒฉํ ๋ณํ ๋ฐฉ์ง)
|
| 54 |
+
max_erosion = 10.0 # ์ต๋ 10m/yr
|
| 55 |
+
erosion_amount = np.clip(erosion_amount, 0, max_erosion)
|
| 56 |
+
|
| 57 |
+
return erosion_amount
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def lateral_erosion(terrain: 'Terrain', water: 'Water',
|
| 61 |
+
k_lateral: float = 0.00005,
|
| 62 |
+
curvature_factor: float = 1.0,
|
| 63 |
+
dt: float = 1.0) -> np.ndarray:
|
| 64 |
+
"""
|
| 65 |
+
์ธก๋ฐฉ ์นจ์ (Lateral Erosion)
|
| 66 |
+
๊ณก๋ฅ ํ์ฒ์์ ๋ฐ๊นฅ์ชฝ(๊ณต๊ฒฉ์ฌ๋ฉด)์ ๊น์
|
| 67 |
+
|
| 68 |
+
Parameters:
|
| 69 |
+
-----------
|
| 70 |
+
terrain : Terrain
|
| 71 |
+
์งํ ๊ฐ์ฒด
|
| 72 |
+
water : Water
|
| 73 |
+
์๋ฌธ ๊ฐ์ฒด
|
| 74 |
+
k_lateral : float
|
| 75 |
+
์ธก๋ฐฉ ์นจ์ ๊ณ์
|
| 76 |
+
curvature_factor : float
|
| 77 |
+
๊ณก๋ฅ ๊ฐ์กฐ ๊ณ์
|
| 78 |
+
dt : float
|
| 79 |
+
์๊ฐ ๋จ์ (๋
)
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
--------
|
| 83 |
+
erosion_amount : np.ndarray
|
| 84 |
+
๊ฐ ์
์ ์นจ์๋ (m)
|
| 85 |
+
"""
|
| 86 |
+
h, w = terrain.height, terrain.width
|
| 87 |
+
erosion = np.zeros((h, w))
|
| 88 |
+
|
| 89 |
+
# ์ ๋ก ๊ณก๋ฅ ๊ณ์ฐ (ํ๋ฆ ๋ฐฉํฅ์ 2์ฐจ ๋ฏธ๋ถ)
|
| 90 |
+
flow_x, flow_y = water.flow_x, water.flow_y
|
| 91 |
+
|
| 92 |
+
# ๊ณก๋ฅ ๊ทผ์ฌ: ํ๋ฆ ๋ฐฉํฅ์ ๋ณํ์จ
|
| 93 |
+
curvature_x = np.gradient(flow_x, axis=1)
|
| 94 |
+
curvature_y = np.gradient(flow_y, axis=0)
|
| 95 |
+
curvature = np.sqrt(curvature_x**2 + curvature_y**2)
|
| 96 |
+
|
| 97 |
+
# ์ธก๋ฐฉ ์นจ์ = ์ ๋ * ์ ์ * ๊ณก๋ฅ
|
| 98 |
+
# ๊ณก๋ฅ ์ด ํฐ ๊ณณ(๊ธ์ปค๋ธ) = ๋ฐ๊นฅ์ชฝ ์นจ์ ๊ฐํจ
|
| 99 |
+
erosion = k_lateral * water.discharge * water.velocity * curvature * curvature_factor * dt
|
| 100 |
+
|
| 101 |
+
# ํ์ฒ์ด ์๋ ๊ณณ์์๋ง ์นจ์ (์ ๋ ์๊ณ๊ฐ)
|
| 102 |
+
channel_mask = water.discharge > 0.1
|
| 103 |
+
erosion = erosion * channel_mask
|
| 104 |
+
|
| 105 |
+
return np.clip(erosion, 0, 5.0)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def headward_erosion(terrain: 'Terrain', water: 'Water',
|
| 109 |
+
k_headward: float = 0.0002,
|
| 110 |
+
dt: float = 1.0) -> np.ndarray:
|
| 111 |
+
"""
|
| 112 |
+
๋๋ถ ์นจ์ (Headward Erosion)
|
| 113 |
+
ํ์ฒ์ ์๋ฅ ๋์ด ์ ์ ๋ค๋ก ๋ฌผ๋ฌ๋จ
|
| 114 |
+
ํญํฌ, ํ๊ณก ํ์ฑ์ ํต์ฌ ๋ฉ์ปค๋์ฆ
|
| 115 |
+
|
| 116 |
+
Returns:
|
| 117 |
+
--------
|
| 118 |
+
erosion_amount : np.ndarray
|
| 119 |
+
๊ฐ ์
์ ์นจ์๋ (m)
|
| 120 |
+
"""
|
| 121 |
+
h, w = terrain.height, terrain.width
|
| 122 |
+
erosion = np.zeros((h, w))
|
| 123 |
+
|
| 124 |
+
# ๊ธ๊ฒฝ์ฌ ์ง์ (Knickpoint) ์ฐพ๊ธฐ
|
| 125 |
+
slope = terrain.get_slope()
|
| 126 |
+
steep_mask = slope > np.percentile(slope[slope > 0], 90) # ์์ 10% ๊ธ๊ฒฝ์ฌ
|
| 127 |
+
|
| 128 |
+
# ๊ธ๊ฒฝ์ฌ + ์ ๋์ด ์๋ ๊ณณ์์ ๋๋ถ ์นจ์ ๋ฐ์
|
| 129 |
+
channel_mask = water.discharge > 0.5
|
| 130 |
+
knickpoint_mask = steep_mask & channel_mask
|
| 131 |
+
|
| 132 |
+
# ์๋ฅ ๋ฐฉํฅ์ผ๋ก ์นจ์ ํ์ฅ
|
| 133 |
+
erosion[knickpoint_mask] = k_headward * water.discharge[knickpoint_mask] * dt
|
| 134 |
+
|
| 135 |
+
return np.clip(erosion, 0, 2.0)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def apply_erosion(terrain: 'Terrain', erosion_amount: np.ndarray,
|
| 139 |
+
min_elevation: float = 0.0):
|
| 140 |
+
"""
|
| 141 |
+
์งํ์ ์นจ์ ์ ์ฉ
|
| 142 |
+
|
| 143 |
+
Parameters:
|
| 144 |
+
-----------
|
| 145 |
+
terrain : Terrain
|
| 146 |
+
์์ ํ ์งํ ๊ฐ์ฒด
|
| 147 |
+
erosion_amount : np.ndarray
|
| 148 |
+
์นจ์๋ ๋ฐฐ์ด
|
| 149 |
+
min_elevation : float
|
| 150 |
+
์ต์ ๊ณ ๋ (ํด์๋ฉด)
|
| 151 |
+
"""
|
| 152 |
+
terrain.elevation -= erosion_amount
|
| 153 |
+
terrain.elevation = np.maximum(terrain.elevation, min_elevation)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def mass_wasting(terrain: 'Terrain',
|
| 157 |
+
critical_slope: float = 0.7, # ~35๋
|
| 158 |
+
transfer_rate: float = 0.3) -> np.ndarray:
|
| 159 |
+
"""
|
| 160 |
+
์ฌ๋ฉด ๋ถ๊ดด (Mass Wasting)
|
| 161 |
+
V์๊ณก ํ์ฑ ์ ์์ ์ฌ๋ฉด์ด ๋ฌด๋์ง๋ ๊ณผ์
|
| 162 |
+
|
| 163 |
+
Returns:
|
| 164 |
+
--------
|
| 165 |
+
elevation_change : np.ndarray
|
| 166 |
+
๊ณ ๋ ๋ณํ๋ (๋์ ๊ณณ -, ๋ฎ์ ๊ณณ +)
|
| 167 |
+
"""
|
| 168 |
+
h, w = terrain.height, terrain.width
|
| 169 |
+
change = np.zeros((h, w))
|
| 170 |
+
|
| 171 |
+
slope = terrain.get_slope()
|
| 172 |
+
|
| 173 |
+
# ์๊ณ ๊ฒฝ์ฌ ์ด๊ณผ ์ง์ ์์ ๋ฌผ์ง ์ด๋
|
| 174 |
+
unstable = slope > critical_slope
|
| 175 |
+
|
| 176 |
+
for y in range(1, h-1):
|
| 177 |
+
for x in range(1, w-1):
|
| 178 |
+
if unstable[y, x]:
|
| 179 |
+
# ์ฃผ๋ณ์ผ๋ก ๋ฌผ์ง ๋ถ๋ฐฐ
|
| 180 |
+
elev = terrain.elevation[y, x]
|
| 181 |
+
neighbors = [
|
| 182 |
+
(y-1, x), (y+1, x), (y, x-1), (y, x+1)
|
| 183 |
+
]
|
| 184 |
+
|
| 185 |
+
for ny, nx in neighbors:
|
| 186 |
+
if terrain.elevation[ny, nx] < elev:
|
| 187 |
+
transfer = (elev - terrain.elevation[ny, nx]) * transfer_rate * 0.25
|
| 188 |
+
change[y, x] -= transfer
|
| 189 |
+
change[ny, nx] += transfer
|
| 190 |
+
|
| 191 |
+
return change
|
engine/erosion_process.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from .grid import WorldGrid
|
| 3 |
+
|
| 4 |
+
class ErosionProcess:
|
| 5 |
+
"""
|
| 6 |
+
์งํ ๋ณ๊ฒฝ ์ปค๋ (Erosion/Deposition Kernel)
|
| 7 |
+
|
| 8 |
+
๋ฌผ๋ฆฌ ๊ณต์์ ์ ์ฉํ์ฌ ์งํ(๊ณ ๋)์ ๋ณ๊ฒฝํฉ๋๋ค.
|
| 9 |
+
1. Stream Power Law: ํ์ฒ ์นจ์ (E = K * A^m * S^n)
|
| 10 |
+
2. Hillslope Diffusion: ์ฌ๋ฉด ๋ถ๊ดด/ํ์ฐ (dz/dt = D * del^2 z)
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
def __init__(self, grid: WorldGrid, K: float = 1e-4, m: float = 0.5, n: float = 1.0, D: float = 0.01):
|
| 14 |
+
self.grid = grid
|
| 15 |
+
self.K = K # ์นจ์ ๊ณ์
|
| 16 |
+
self.m = m # ์ ๋ ์ง์
|
| 17 |
+
self.n = n # ๊ฒฝ์ฌ ์ง์
|
| 18 |
+
self.D = D # ํ์ฐ ๊ณ์ (์ฌ๋ฉด)
|
| 19 |
+
|
| 20 |
+
def stream_power_erosion(self, discharge: np.ndarray, dt: float = 1.0) -> np.ndarray:
|
| 21 |
+
"""Stream Power Law ๊ธฐ๋ฐ ํ์ฒ ์นจ์"""
|
| 22 |
+
slope, _ = self.grid.get_gradient()
|
| 23 |
+
|
| 24 |
+
# E = K * Q^m * S^n
|
| 25 |
+
# (์ ๋ Q๋ฅผ ์ ์ญ๋ฉด์ A ๋์ ์ฌ์ฉ)
|
| 26 |
+
erosion_rate = self.K * np.power(discharge, self.m) * np.power(slope, self.n)
|
| 27 |
+
|
| 28 |
+
# ์ค์ ์นจ์๋ = rate * time
|
| 29 |
+
erosion_amount = erosion_rate * dt
|
| 30 |
+
|
| 31 |
+
# ๊ธฐ๋ฐ์ ์ดํ๋ก๋ ์นจ์ ๋ถ๊ฐ (available sediment first, then bedrock)
|
| 32 |
+
# ์ฌ๊ธฐ์๋ ๋จ์ํ๋ฅผ ์ํด Topography(elevation)์ ๋ฐ๋ก ๊น์.
|
| 33 |
+
# ๋จ, ํด์๋ฉด ์๋๋ ์นจ์ ์์ฉ ๊ฐ์ (๋ฌผ ์์์๋ Stream Power๊ฐ ์๋)
|
| 34 |
+
underwater = self.grid.is_underwater()
|
| 35 |
+
erosion_amount[underwater] *= 0.1
|
| 36 |
+
|
| 37 |
+
# ์งํ ์
๋ฐ์ดํธ
|
| 38 |
+
self.grid.elevation -= erosion_amount
|
| 39 |
+
|
| 40 |
+
# ๋จ์ํ: ๊น์ธ ๋งํผ ํด์ ๋ฌผ๋ก ๋ณํ๋์ด ์ด๋๊ฐ๋ก ๊ฐ์ผ ํ์ง๋ง,
|
| 41 |
+
# Stream Power Model(SPL)์ ๋ณดํต Detachment-limited ๋ชจ๋ธ์ด๋ผ ํด์ ์ ๋ช
์์ ์ผ๋ก ๋ค๋ฃจ์ง ์์.
|
| 42 |
+
# ํตํฉ ๋ชจ๋ธ์ ์ํด, ์นจ์๋ ์์ ํด์ ๋ฌผ ํ๋ญ์ค์ ๋ํด์ค ์ ์์ (๊ตฌํ ์์ )
|
| 43 |
+
|
| 44 |
+
return erosion_amount
|
| 45 |
+
|
| 46 |
+
def hillslope_diffusion(self, dt: float = 1.0) -> np.ndarray:
|
| 47 |
+
"""์ฌ๋ฉด ํ์ฐ ํ๋ก์ธ์ค (Linear Diffusion)"""
|
| 48 |
+
elev = self.grid.elevation
|
| 49 |
+
|
| 50 |
+
# Laplacian calculation (์ด์ฐํ)
|
| 51 |
+
# del^2 z = (z_up + z_down + z_left + z_right - 4*z) / dx^2
|
| 52 |
+
|
| 53 |
+
# Numpy roll์ ์ด์ฉํ ๋น ๋ฅธ ๊ณ์ฐ
|
| 54 |
+
up = np.roll(elev, -1, axis=0)
|
| 55 |
+
down = np.roll(elev, 1, axis=0)
|
| 56 |
+
left = np.roll(elev, -1, axis=1)
|
| 57 |
+
right = np.roll(elev, 1, axis=1)
|
| 58 |
+
|
| 59 |
+
dx2 = self.grid.cell_size ** 2
|
| 60 |
+
laplacian = (up + down + left + right - 4 * elev) / dx2
|
| 61 |
+
|
| 62 |
+
# ๊ฒฝ๊ณ ์กฐ๊ฑด ์ฒ๋ฆฌ (๊ฐ์ฅ์๋ฆฌ๋ ๊ณ์ฐ ์ ์ธ or 0)
|
| 63 |
+
laplacian[0, :] = 0
|
| 64 |
+
laplacian[-1, :] = 0
|
| 65 |
+
laplacian[:, 0] = 0
|
| 66 |
+
laplacian[:, -1] = 0
|
| 67 |
+
|
| 68 |
+
# dz/dt = D * del^2 z
|
| 69 |
+
change = self.D * laplacian * dt
|
| 70 |
+
|
| 71 |
+
self.grid.elevation += change
|
| 72 |
+
return change
|
| 73 |
+
|
| 74 |
+
def overbank_deposition(self, discharge: np.ndarray,
|
| 75 |
+
bankfull_capacity: float = 100.0,
|
| 76 |
+
decay_rate: float = 0.1,
|
| 77 |
+
dt: float = 1.0) -> np.ndarray:
|
| 78 |
+
"""
|
| 79 |
+
๋ฒ๋์ ํด์ (Overbank Deposition)
|
| 80 |
+
|
| 81 |
+
ํ์ฒ ์ฉ๋ ์ด๊ณผ ์ ๋ฒ๋ํ์ฌ ์ฃผ๋ณ์ ์ธ๋ฆฝ์ง ํด์ .
|
| 82 |
+
ํ๋์์ ๋ฉ์ด์ง์๋ก ํด์ ๋ ๊ฐ์ (์์ฐ์ ๋ฐฉ ํ์ฑ).
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
discharge: ์ ๋ ๋ฐฐ์ด
|
| 86 |
+
bankfull_capacity: ํ๋ ์ฉ๋ (์ด๊ณผ ์ ๋ฒ๋)
|
| 87 |
+
decay_rate: ๊ฑฐ๋ฆฌ์ ๋ฐ๋ฅธ ํด์ ๊ฐ์ ์จ
|
| 88 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
deposition: ํด์ ๋ ๋ฐฐ์ด
|
| 92 |
+
"""
|
| 93 |
+
from scipy.ndimage import distance_transform_edt
|
| 94 |
+
|
| 95 |
+
h, w = self.grid.height, self.grid.width
|
| 96 |
+
|
| 97 |
+
# 1. ๋ฒ๋ ์ง์ ์๋ณ (์ฉ๋ ์ด๊ณผ)
|
| 98 |
+
overflow = np.maximum(0, discharge - bankfull_capacity)
|
| 99 |
+
flood_mask = overflow > 0
|
| 100 |
+
|
| 101 |
+
if not np.any(flood_mask):
|
| 102 |
+
return np.zeros((h, w))
|
| 103 |
+
|
| 104 |
+
# 2. ํ๋๋ก๋ถํฐ์ ๊ฑฐ๋ฆฌ ๊ณ์ฐ
|
| 105 |
+
# flood_mask๊ฐ ์๋ ๊ณณ์ด ํ๋
|
| 106 |
+
channel_mask = discharge > bankfull_capacity * 0.5
|
| 107 |
+
|
| 108 |
+
if not np.any(channel_mask):
|
| 109 |
+
return np.zeros((h, w))
|
| 110 |
+
|
| 111 |
+
# Distance Transform (ํ๋๋ก๋ถํฐ์ ๊ฑฐ๋ฆฌ)
|
| 112 |
+
distance = distance_transform_edt(~channel_mask) * self.grid.cell_size
|
| 113 |
+
|
| 114 |
+
# 3. ํด์ ๋ ๊ณ์ฐ (์ง์ ๊ฐ์ )
|
| 115 |
+
# Deposition = overflow * exp(-k * distance)
|
| 116 |
+
# ํ๋ ๊ทผ์ฒ(์์ฐ์ ๋ฐฉ)์ ๋ง์ด, ๋ฉ์๋ก(๋ฐฐํ์ต์ง) ์ ๊ฒ
|
| 117 |
+
max_overflow = overflow.max()
|
| 118 |
+
if max_overflow <= 0:
|
| 119 |
+
return np.zeros((h, w))
|
| 120 |
+
|
| 121 |
+
normalized_overflow = overflow / max_overflow
|
| 122 |
+
|
| 123 |
+
# ๋ฒ๋ ์ํฅ ๋ฒ์ (์ต๋ 50 ์
)
|
| 124 |
+
max_distance = 50 * self.grid.cell_size
|
| 125 |
+
influence = np.exp(-decay_rate * distance / self.grid.cell_size)
|
| 126 |
+
influence[distance > max_distance] = 0
|
| 127 |
+
|
| 128 |
+
# ํด์ ๏ฟฝ๏ฟฝ๏ฟฝ
|
| 129 |
+
deposition = normalized_overflow.max() * influence * 0.1 * dt
|
| 130 |
+
|
| 131 |
+
# ํด์๋ฉด ์๋๋ ์ ์ธ
|
| 132 |
+
underwater = self.grid.is_underwater()
|
| 133 |
+
deposition[underwater] = 0
|
| 134 |
+
|
| 135 |
+
# ํ๋ ์์ฒด๋ ์ ์ธ (ํ๋๋ ์นจ์์ด ์ฐ์ธ)
|
| 136 |
+
deposition[channel_mask] = 0
|
| 137 |
+
|
| 138 |
+
# 4. ํด์ ์ธต์ ์ถ๊ฐ
|
| 139 |
+
self.grid.add_sediment(deposition)
|
| 140 |
+
|
| 141 |
+
return deposition
|
| 142 |
+
|
| 143 |
+
def transport_and_deposit(self, discharge: np.ndarray, dt: float = 1.0, Kf: float = 0.01) -> np.ndarray:
|
| 144 |
+
"""
|
| 145 |
+
ํด์ ๋ฌผ ์ด๋ฐ ๋ฐ ํด์ (Sediment Transport & Deposition)
|
| 146 |
+
|
| 147 |
+
Transport Capacity Law:
|
| 148 |
+
Q_cap = Kf * Q^m * S^n
|
| 149 |
+
|
| 150 |
+
- Q_cap > Q_sed: ์นจ์ (Erosion) -> ํด์ ๋ฌผ ์ฆ๊ฐ
|
| 151 |
+
- Q_cap < Q_sed: ํด์ (Deposition) -> ํด์ ๋ฌผ ๊ฐ์, ์งํ ์์น
|
| 152 |
+
|
| 153 |
+
Args:
|
| 154 |
+
discharge: ์ ๋
|
| 155 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 156 |
+
Kf: ์ด๋ฐ ํจ์จ ๊ณ์ (Transport Efficiency)
|
| 157 |
+
"""
|
| 158 |
+
slope, _ = self.grid.get_gradient()
|
| 159 |
+
# ๊ฒฝ์ฌ๊ฐ 0์ด๋ฉด ๋ฌดํ ํด์ ๋ฐฉ์ง๋ฅผ ์ํด ์ต์๊ฐ ์ค์
|
| 160 |
+
slope = np.maximum(slope, 0.001)
|
| 161 |
+
|
| 162 |
+
# 1. ์ด๋ฐ ๋ฅ๋ ฅ (Transport Capacity) ๊ณ์ฐ
|
| 163 |
+
# ์นจ์ ๊ณ์ K ๋์ ์ด๋ฐ ๊ณ์ Kf ์ฌ์ฉ (์ผ๋ฐ์ ์ผ๋ก K๋ณด๋ค ํผ)
|
| 164 |
+
capacity = Kf * np.power(discharge, self.m) * np.power(slope, self.n)
|
| 165 |
+
|
| 166 |
+
# 2. ํ์ฌ ๋ถ์ ์ฌ(Suspended Sediment) ๊ฐ์
|
| 167 |
+
# ์๋ฅ์์ ๋ค์ด์ค๋ ์ ์ฌ๋์ ์ด์ ๋จ๊ณ์ ์นจ์๋์ด๋ ๊ธฐ์ ์
๋์ ์์กด
|
| 168 |
+
# ์ฌ๊ธฐ์๋ ๋จ์ํ๋ฅผ ์ํด 'Local Equilibrium'์ ๊ฐ์ ํ์ง ์๊ณ ,
|
| 169 |
+
# ์ ๋์ ๋น๋กํ๋ ์ด๊ธฐ ์ ์ฌ๋์ ๊ฐ์ ํ๊ฑฐ๋,
|
| 170 |
+
# ์ด์ ์คํ
์ ์นจ์ ๊ฒฐ๊ณผ๋ฅผ ์ด์ฉํด์ผ ํจ.
|
| 171 |
+
# ํตํฉ ๋ชจ๋ธ์ ์ํด: "Erosion" ํจ์๊ฐ ๊น์๋ธ ํ์ ๋ฐํํ๋๋ก ํ๊ณ ,
|
| 172 |
+
# ์ด๋ฅผ capacity์ ๋น๊ตํ์ฌ ์ฌํด์ ์ํค๊ฑฐ๋ ํ๋ฅ๋ก ๋ณด๋.
|
| 173 |
+
|
| 174 |
+
# ํ์ง๋ง D8 ์๊ณ ๋ฆฌ์ฆ ์ ํ๋ฅ๋ก์ '์ ๋ฌ(Routing)'์ด ํ์ํจ.
|
| 175 |
+
# ์ฌ๊ธฐ์๋ Simplified Landform Evolution Model (SLEM) ๋ฐฉ์ ์ ์ฉ:
|
| 176 |
+
# dZs/dt = U - E + D
|
| 177 |
+
# D = (Q_cap - Q_sed) / Length_scale (if Q_sed > Q_cap)
|
| 178 |
+
# E = Stream Power (detachment limited)
|
| 179 |
+
|
| 180 |
+
# Delta ์๋ฎฌ๋ ์ด์
์ ์ํ ์ ๊ทผ:
|
| 181 |
+
# ํด์ ๋ฌผ ํ๋ญ์ค(Flux)๋ฅผ ํ๋ฅ๋ก ๋ฐ์ด๋ด๋ ๋ก์ง ์ถ๊ฐ.
|
| 182 |
+
|
| 183 |
+
h, w = self.grid.height, self.grid.width
|
| 184 |
+
sediment_flux = np.zeros((h, w))
|
| 185 |
+
|
| 186 |
+
# ์ ๋ ์์๋๋ก ์ฒ๋ฆฌ (Upstream -> Downstream)
|
| 187 |
+
# discharge๊ฐ ๋ฎ์ ๊ณณ(์๋ฅ)์์ ๋์ ๊ณณ(ํ๋ฅ)์ผ๋ก?
|
| 188 |
+
# D8 ํ๋ฆ ๋ฐฉํฅ์ ๋ค์ ์ถ์ ํด์ผ ์ ํํจ.
|
| 189 |
+
# ์ฌ๊ธฐ์๋ ๊ฐ๋จํ 'Capacity ์ด๊ณผ๋ถ ํด์ '๋ง ๊ตฌํํ๊ณ ,
|
| 190 |
+
# Flux Routing์ HydroKernel๊ณผ ์ฐ๋๋์ด์ผ ํจ.
|
| 191 |
+
|
| 192 |
+
# ์์: Capacity based deposition only (Local)
|
| 193 |
+
# ํด์ ๋ = (ํ์ฌ ์ ์ฌ๋ - ์ฉ๋) * ๋น์จ
|
| 194 |
+
# ํ์ฌ ์ ์ฌ๋์ด ์์ผ๋ฏ๋ก, ์นจ์๋ ํ(Stream Power ๊ฒฐ๊ณผ)์ด
|
| 195 |
+
# ํด๋น ์
์ Capacity๋ฅผ ๋์ผ๋ฉด ์ฆ์ ํด์ ๋๋ค๊ณ ๊ฐ์ .
|
| 196 |
+
|
| 197 |
+
pass
|
| 198 |
+
# TODO: Flux Routing ๊ตฌํ ํ์. ํ์ฌ ๊ตฌ์กฐ์์๋ ์ด๋ ค์.
|
| 199 |
+
# ErosionProcess๋ฅผ ์์ ํ์ฌ 'simulate_transport' ๋ฉ์๋๋ก ํตํฉ.
|
| 200 |
+
|
| 201 |
+
return capacity
|
| 202 |
+
|
| 203 |
+
def simulate_transport(self, discharge: np.ndarray, dt: float = 1.0,
|
| 204 |
+
sediment_influx_map: np.ndarray = None) -> np.ndarray:
|
| 205 |
+
"""
|
| 206 |
+
ํตํฉ ํด์ ๋ฌผ ์ด์ก ์๋ฎฌ๋ ์ด์
(Flux-based)
|
| 207 |
+
1. ์๋ฅ์์ ํด์ ๋ฌผ ์ ์
(Flux In)
|
| 208 |
+
2. ๋ก์ปฌ ์นจ์/ํด์ (Erosion/Deposition)
|
| 209 |
+
3. ํ๋ฅ๋ก ๋ฐฐ์ถ (Flux Out)
|
| 210 |
+
"""
|
| 211 |
+
h, w = self.grid.height, self.grid.width
|
| 212 |
+
elev = self.grid.elevation
|
| 213 |
+
|
| 214 |
+
# 1. ์ ๋ ฌ (๋์ ๊ณณ -> ๋ฎ์ ๊ณณ)
|
| 215 |
+
indices = np.argsort(elev.ravel())[::-1]
|
| 216 |
+
|
| 217 |
+
# ํด์ ๋ฌผ ํ๋ญ์ค ์ด๊ธฐํ (์ ์
์ ๋ฐ์)
|
| 218 |
+
flux = np.zeros((h, w))
|
| 219 |
+
if sediment_influx_map is not None:
|
| 220 |
+
flux += sediment_influx_map
|
| 221 |
+
|
| 222 |
+
slope, _ = self.grid.get_gradient()
|
| 223 |
+
slope = np.maximum(slope, 0.001)
|
| 224 |
+
|
| 225 |
+
change = np.zeros((h, w))
|
| 226 |
+
|
| 227 |
+
# D8 Neighbors (Lookup for flow_dir)
|
| 228 |
+
d8_dr = [-1, -1, -1, 0, 0, 1, 1, 1]
|
| 229 |
+
d8_dc = [-1, 0, 1, -1, 1, -1, 0, 1]
|
| 230 |
+
|
| 231 |
+
# Check if flow_dir is available
|
| 232 |
+
use_flow_dir = (self.grid.flow_dir is not None)
|
| 233 |
+
|
| 234 |
+
for idx in indices:
|
| 235 |
+
r, c = idx // w, idx % w
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
# ํด์๋ฉด ์๋ ๊น์ ๊ณณ์ ํด์ ์์ฃผ
|
| 240 |
+
underwater = self.grid.is_underwater()[r, c]
|
| 241 |
+
|
| 242 |
+
# A. ์ด๋ฐ ๋ฅ๋ ฅ (Capacity)
|
| 243 |
+
# ๋ฌผ ์์์๋ ์ ์์ด ๊ธ๊ฐํ๋ค๊ณ ๊ฐ์ -> Capacity ๊ฐ์
|
| 244 |
+
# eff_slope calculation fix: slope can be very small on flat land
|
| 245 |
+
eff_slope = slope[r, c] if not underwater else slope[r, c] * 0.01
|
| 246 |
+
|
| 247 |
+
# Kf (Transportation efficiency) should be high enough
|
| 248 |
+
# Use 'self.K * 100' or similar
|
| 249 |
+
qs_cap = self.K * 500 * np.power(discharge[r, c], self.m) * np.power(eff_slope, self.n)
|
| 250 |
+
|
| 251 |
+
# B. ํ์ฌ ํ๋ญ์ค (์๋ฅ์์ ๋ค์ด์จ ๊ฒ + ๋ก์ปฌ ์นจ์ ์ ์ฌ๋)
|
| 252 |
+
qs_in = flux[r, c]
|
| 253 |
+
|
| 254 |
+
# C. ์นจ์ vs ํด์ ๊ฒฐ์
|
| 255 |
+
# ๊ธฐ๊ณ์ ์นจ์ (Stream Power)
|
| 256 |
+
potential_erosion = self.K * np.power(discharge[r, c], self.m) * np.power(slope[r, c], self.n) * dt
|
| 257 |
+
|
| 258 |
+
# ๋ง์ฝ ๋ค์ด์จ ํ(qs_in)์ด ์ฉ๋(qs_cap)๋ณด๋ค ๋ง์ผ๋ฉด -> ํด์
|
| 259 |
+
if qs_in > qs_cap:
|
| 260 |
+
# ํด์ ๋ = ์ด๊ณผ๋ถ * 1.0 (์ผ๋จ 100% ํด์ ๊ฐ์ ํ์ฌ ํจ๊ณผ ํ์ธ)
|
| 261 |
+
deposition_amount = (qs_in - qs_cap) * 1.0
|
| 262 |
+
change[r, c] += deposition_amount
|
| 263 |
+
qs_out = qs_cap # ๋๋จธ์ง๋ ํ๋ฅ๋ก? ์๋, ํด์ ํ ๋จ์๊ฑด qs_cap์ (Transport-limited)
|
| 264 |
+
else:
|
| 265 |
+
# ์ฉ๋์ด ๋จ์ผ๋ฉด -> ์นจ์ํ์ฌ ํ์ ๋ ์ฃ๊ณ ๊ฐ
|
| 266 |
+
# ์ค์ ์นจ์ = ์ ์ฌ ์นจ์ (๊ธฐ๋ฐ์๋ ์นจ์ ๊ฐ๋ฅ)
|
| 267 |
+
erosion_amount = potential_erosion
|
| 268 |
+
|
| 269 |
+
change[r, c] -= erosion_amount
|
| 270 |
+
qs_out = qs_in + erosion_amount
|
| 271 |
+
|
| 272 |
+
# D. ํ๋ฅ๋ก ์ ๋ฌ (Qs Out Routing)
|
| 273 |
+
# Use pre-calculated flow direction if available
|
| 274 |
+
target_r, target_c = -1, -1
|
| 275 |
+
|
| 276 |
+
if use_flow_dir:
|
| 277 |
+
k = self.grid.flow_dir[r, c]
|
| 278 |
+
# k could be default 0 even if no flow?
|
| 279 |
+
# Usually sink nodes have special value (e.g. -1 or point to self)
|
| 280 |
+
# But here we initialized to 0.
|
| 281 |
+
# Need to check constraints.
|
| 282 |
+
# If discharge[r,c] > 0, flow_dir should be valid.
|
| 283 |
+
if discharge[r, c] > 0:
|
| 284 |
+
nr = r + d8_dr[k]
|
| 285 |
+
nc = c + d8_dc[k]
|
| 286 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 287 |
+
target_r, target_c = nr, nc
|
| 288 |
+
else:
|
| 289 |
+
# Fallback: Local Seek (Slow)
|
| 290 |
+
min_z = elev[r, c]
|
| 291 |
+
for k in range(8):
|
| 292 |
+
nr = r + d8_dr[k]
|
| 293 |
+
nc = c + d8_dc[k]
|
| 294 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 295 |
+
if elev[nr, nc] < min_z:
|
| 296 |
+
min_z = elev[nr, nc]
|
| 297 |
+
target_r, target_c = nr, nc
|
| 298 |
+
|
| 299 |
+
if target_r != -1:
|
| 300 |
+
flux[target_r, target_c] += qs_out
|
| 301 |
+
else:
|
| 302 |
+
# ๊ฐํ ๊ณณ(Sink) -> ๊ทธ ์๋ฆฌ์ ํด์
|
| 303 |
+
# ์นจ์์ด ๋ฐ์ํ๋ค๋ฉด ๋๋๋ ค๋๊ณ ํด์
|
| 304 |
+
change[r, c] += qs_out
|
| 305 |
+
|
| 306 |
+
# ์งํ ์
๋ฐ์ดํธ
|
| 307 |
+
# ์นจ์์ elevation ๊ฐ์, ํด์ ์ sediment ์ฆ๊ฐ์ด์ง๋ง
|
| 308 |
+
# ์ฌ๊ธฐ์๋ ํตํฉํ์ฌ elevation/sediment ์กฐ์
|
| 309 |
+
|
| 310 |
+
# ํด์ ๋ถ: sediment ์ธต์ ์ถ๊ฐ
|
| 311 |
+
self.grid.add_sediment(np.maximum(change, 0))
|
| 312 |
+
|
| 313 |
+
# ์นจ์๋ถ: elevation ๊ฐ์ (grid.add_sediment๊ฐ ์์๋ ์ฒ๋ฆฌํ๋? ์๋)
|
| 314 |
+
# ์นจ์์ bedrock์ด๋ sediment๋ฅผ ๊น์์ผ ํจ.
|
| 315 |
+
# erosion_process.py์ ์ญํ ์ ์ง์ grid ์์ ์ ํด๋ ๋จ.
|
| 316 |
+
erosion_mask = change < 0
|
| 317 |
+
loss = -change[erosion_mask]
|
| 318 |
+
|
| 319 |
+
# ํด์ ์ธต ๋จผ์ ๊น๊ณ ๊ธฐ๋ฐ์ ๊น๊ธฐ
|
| 320 |
+
sed_thickness = self.grid.sediment[erosion_mask]
|
| 321 |
+
sed_loss = np.minimum(loss, sed_thickness)
|
| 322 |
+
rock_loss = loss - sed_loss
|
| 323 |
+
|
| 324 |
+
self.grid.sediment[erosion_mask] -= sed_loss
|
| 325 |
+
self.grid.bedrock[erosion_mask] -= rock_loss
|
| 326 |
+
self.grid.update_elevation()
|
| 327 |
+
|
| 328 |
+
return change
|
engine/fluids.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from .grid import WorldGrid
|
| 3 |
+
|
| 4 |
+
try:
|
| 5 |
+
from numba import jit
|
| 6 |
+
HAS_NUMBA = True
|
| 7 |
+
except ImportError:
|
| 8 |
+
HAS_NUMBA = False
|
| 9 |
+
# Dummy decorator if numba is missing
|
| 10 |
+
def jit(*args, **kwargs):
|
| 11 |
+
def decorator(func):
|
| 12 |
+
return func
|
| 13 |
+
return decorator
|
| 14 |
+
|
| 15 |
+
@jit(nopython=True)
|
| 16 |
+
def _d8_flow_kernel(elev, discharge, flow_dir, underwater, h, w):
|
| 17 |
+
"""
|
| 18 |
+
Numba-optimized D8 Flow Routing
|
| 19 |
+
"""
|
| 20 |
+
# Flatten elevation to sort
|
| 21 |
+
flat_elev = elev.ravel()
|
| 22 |
+
# Sort indices descending (Source -> Sink)
|
| 23 |
+
# Note: argsort in numba supports 1D array
|
| 24 |
+
flat_indices = np.argsort(flat_elev)[::-1]
|
| 25 |
+
|
| 26 |
+
# 8-neighbor offsets
|
| 27 |
+
# Numba doesn't like list of tuples in loops sometimes, simple arrays are better
|
| 28 |
+
dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
|
| 29 |
+
dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
|
| 30 |
+
|
| 31 |
+
for i in range(len(flat_indices)):
|
| 32 |
+
idx = flat_indices[i]
|
| 33 |
+
r = idx // w
|
| 34 |
+
c = idx % w
|
| 35 |
+
|
| 36 |
+
# Check underwater
|
| 37 |
+
if underwater[r, c]:
|
| 38 |
+
continue
|
| 39 |
+
|
| 40 |
+
current_z = elev[r, c]
|
| 41 |
+
min_z = current_z
|
| 42 |
+
target_r = -1
|
| 43 |
+
target_c = -1
|
| 44 |
+
target_k = -1
|
| 45 |
+
|
| 46 |
+
# Find steepest descent
|
| 47 |
+
for k in range(8):
|
| 48 |
+
nr = r + dr[k]
|
| 49 |
+
nc = c + dc[k]
|
| 50 |
+
|
| 51 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 52 |
+
n_elev = elev[nr, nc]
|
| 53 |
+
if n_elev < min_z:
|
| 54 |
+
min_z = n_elev
|
| 55 |
+
target_r = nr
|
| 56 |
+
target_c = nc
|
| 57 |
+
target_k = k
|
| 58 |
+
|
| 59 |
+
# Pass flow to lowest neighbor
|
| 60 |
+
if target_r != -1:
|
| 61 |
+
discharge[target_r, target_c] += discharge[r, c]
|
| 62 |
+
flow_dir[r, c] = target_k # Store direction (0-7)
|
| 63 |
+
|
| 64 |
+
class HydroKernel:
|
| 65 |
+
"""
|
| 66 |
+
์๋ ฅํ ์ปค๋ (Hydro Kernel)
|
| 67 |
+
|
| 68 |
+
๋ฌผ์ ํ๋ฆ๊ณผ ๋ถํฌ๋ฅผ ์๋ฎฌ๋ ์ด์
ํฉ๋๋ค.
|
| 69 |
+
- D8 ์๊ณ ๋ฆฌ์ฆ: ํ์ฒ ๋คํธ์ํฌ ํ์ฑ (Numba ๊ฐ์ ์ ์ฉ)
|
| 70 |
+
- Shallow Water (๊ฐ์ํ): ํ์ ๋ฐ ํด์๋ฉด ์นจ์
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
def __init__(self, grid: WorldGrid):
|
| 74 |
+
self.grid = grid
|
| 75 |
+
|
| 76 |
+
def route_flow_d8(self, precipitation: float = 0.001) -> np.ndarray:
|
| 77 |
+
"""
|
| 78 |
+
D8 ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ ๋(Discharge) ๊ณ์ฐ (Numba ๊ฐ์)
|
| 79 |
+
"""
|
| 80 |
+
h, w = self.grid.height, self.grid.width
|
| 81 |
+
elev = self.grid.elevation
|
| 82 |
+
|
| 83 |
+
# 1. ์ด๊ธฐ ๊ฐ์ ๋ถํฌ
|
| 84 |
+
discharge = np.full((h, w), precipitation * (self.grid.cell_size ** 2), dtype=np.float64)
|
| 85 |
+
|
| 86 |
+
# 2. ํด์๋ฉด ๋ง์คํฌ
|
| 87 |
+
underwater = self.grid.is_underwater()
|
| 88 |
+
|
| 89 |
+
# 3. Numba Kernel ํธ์ถ
|
| 90 |
+
if HAS_NUMBA:
|
| 91 |
+
_d8_flow_kernel(elev, discharge, self.grid.flow_dir, underwater, h, w)
|
| 92 |
+
else:
|
| 93 |
+
# Fallback (Slow Python) if numba somehow fails to import
|
| 94 |
+
self._route_flow_d8_python(discharge, self.grid.flow_dir, elev, underwater, h, w)
|
| 95 |
+
|
| 96 |
+
return discharge
|
| 97 |
+
|
| 98 |
+
def route_flow_mfd(self, precipitation: float = 0.001, p: float = 1.1) -> np.ndarray:
|
| 99 |
+
"""
|
| 100 |
+
MFD (Multiple Flow Direction) ์ ๋ ๋ถ๋ฐฐ
|
| 101 |
+
|
| 102 |
+
D8๊ณผ ๋ฌ๋ฆฌ ๋ฎ์ ๋ชจ๋ ์ด์์๊ฒ ๊ฒฝ์ฌ ๋น๋ก๋ก ์ ๋ ๋ถ๋ฐฐ.
|
| 103 |
+
๋ง๋ฅ(Braided Stream) ๋ฐ ๋ถ๊ธฐ๋ฅ ํํ์ ์ ํฉ.
|
| 104 |
+
|
| 105 |
+
Args:
|
| 106 |
+
precipitation: ๊ฐ์๋
|
| 107 |
+
p: ๋ถ๋ฐฐ ์ง์ (1.0=์ ํ, >1.0=๊ฐํ๋ฅธ ๊ณณ์ ์ง์ค)
|
| 108 |
+
|
| 109 |
+
Returns:
|
| 110 |
+
discharge: ์ ๋ ๋ฐฐ์ด
|
| 111 |
+
"""
|
| 112 |
+
h, w = self.grid.height, self.grid.width
|
| 113 |
+
elev = self.grid.elevation
|
| 114 |
+
|
| 115 |
+
# ์ด๊ธฐ ๊ฐ์
|
| 116 |
+
discharge = np.full((h, w), precipitation * (self.grid.cell_size ** 2), dtype=np.float64)
|
| 117 |
+
|
| 118 |
+
# ํด์๋ฉด ๋ง์คํฌ
|
| 119 |
+
underwater = self.grid.is_underwater()
|
| 120 |
+
|
| 121 |
+
# D8 ๋ฐฉํฅ ๋ฒกํฐ
|
| 122 |
+
dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
|
| 123 |
+
dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
|
| 124 |
+
dist = np.array([1.414, 1.0, 1.414, 1.0, 1.0, 1.414, 1.0, 1.414]) # ๋๊ฐ ๊ฑฐ๋ฆฌ
|
| 125 |
+
|
| 126 |
+
# ์ ๋ ฌ (๋์ ๊ณณ -> ๋ฎ์ ๊ณณ)
|
| 127 |
+
flat_indices = np.argsort(elev.ravel())[::-1]
|
| 128 |
+
|
| 129 |
+
for idx in flat_indices:
|
| 130 |
+
r, c = idx // w, idx % w
|
| 131 |
+
|
| 132 |
+
if underwater[r, c]:
|
| 133 |
+
continue
|
| 134 |
+
|
| 135 |
+
current_z = elev[r, c]
|
| 136 |
+
current_q = discharge[r, c]
|
| 137 |
+
|
| 138 |
+
if current_q <= 0:
|
| 139 |
+
continue
|
| 140 |
+
|
| 141 |
+
# ๋ฎ์ ์ด์๋ค์ ๊ฒฝ์ฌ ๊ณ์ฐ
|
| 142 |
+
slopes = []
|
| 143 |
+
targets = []
|
| 144 |
+
|
| 145 |
+
for k in range(8):
|
| 146 |
+
nr, nc = r + dr[k], c + dc[k]
|
| 147 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 148 |
+
dz = current_z - elev[nr, nc]
|
| 149 |
+
if dz > 0: # ํ๊ฐํ๋ ๋ฐฉํฅ๋ง
|
| 150 |
+
slope = dz / (dist[k] * self.grid.cell_size)
|
| 151 |
+
slopes.append(slope ** p)
|
| 152 |
+
targets.append((nr, nc))
|
| 153 |
+
|
| 154 |
+
if not slopes:
|
| 155 |
+
continue
|
| 156 |
+
|
| 157 |
+
# ๊ฒฝ์ฌ ๋น๋ก ๋ถ๋ฐฐ
|
| 158 |
+
total_slope = sum(slopes)
|
| 159 |
+
for i, (nr, nc) in enumerate(targets):
|
| 160 |
+
fraction = slopes[i] / total_slope
|
| 161 |
+
discharge[nr, nc] += current_q * fraction
|
| 162 |
+
|
| 163 |
+
return discharge
|
| 164 |
+
|
| 165 |
+
def _route_flow_d8_python(self, discharge, flow_dir, elev, underwater, h, w):
|
| 166 |
+
"""Legacy Python implementation for fallback"""
|
| 167 |
+
flat_indices = np.argsort(elev.ravel())[::-1]
|
| 168 |
+
neighbors = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
|
| 169 |
+
|
| 170 |
+
for idx in flat_indices:
|
| 171 |
+
r, c = idx // w, idx % w
|
| 172 |
+
if underwater[r, c]: continue
|
| 173 |
+
|
| 174 |
+
min_z = elev[r, c]
|
| 175 |
+
target = None
|
| 176 |
+
target_k = -1
|
| 177 |
+
|
| 178 |
+
for k, (dr, dc) in enumerate(neighbors):
|
| 179 |
+
nr, nc = r + dr, c + dc
|
| 180 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 181 |
+
if elev[nr, nc] < min_z:
|
| 182 |
+
min_z = elev[nr, nc]
|
| 183 |
+
target = (nr, nc)
|
| 184 |
+
target_k = k
|
| 185 |
+
|
| 186 |
+
if target:
|
| 187 |
+
tr, tc = target
|
| 188 |
+
discharge[tr, tc] += discharge[r, c]
|
| 189 |
+
flow_dir[r, c] = target_k
|
| 190 |
+
|
| 191 |
+
def calculate_water_depth(self, discharge: np.ndarray, manning_n: float = 0.03) -> np.ndarray:
|
| 192 |
+
"""
|
| 193 |
+
Manning ๊ณต์์ ์ด์ฉํ ํ์ฒ ์์ฌ ์ถ์ (์ ์ ๋ฑ๋ฅ ๊ฐ์ )
|
| 194 |
+
Depth = (Q * n / (Width * S^0.5))^(3/5)
|
| 195 |
+
|
| 196 |
+
* ๊ฒฝ์ฌ(S)๊ฐ 0์ธ ๊ฒฝ์ฐ ์ต์ ๊ฒฝ์ฌ ์ ์ฉ
|
| 197 |
+
* ํํญ(W)์ ์ ๋(Q)์ ํจ์๋ก ๊ฐ์ (W ~ Q^0.5)
|
| 198 |
+
"""
|
| 199 |
+
slope, _ = self.grid.get_gradient()
|
| 200 |
+
slope = np.maximum(slope, 0.001) # ์ต์ ๊ฒฝ์ฌ ์ค์
|
| 201 |
+
|
| 202 |
+
# ํํญ ์ถ์ : W = 5 * Q^0.5 (๊ฒฝํ์)
|
| 203 |
+
# Q๊ฐ ๋งค์ฐ ์์ผ๋ฉด W๋ ์์์ง
|
| 204 |
+
width = 5.0 * np.sqrt(discharge)
|
| 205 |
+
width = np.maximum(width, 1.0) # ์ต์ ํญ 1m
|
| 206 |
+
|
| 207 |
+
# ์์ฌ ๊ณ์ฐ
|
| 208 |
+
# Q = V * Area = (1/n * R^(2/3) * S^(1/2)) * (W * D)
|
| 209 |
+
# ์ง์ฌ๊ฐํ ๋จ๋ฉด ๊ฐ์ ์ R approx D (๋์ ํ์ฒ)
|
| 210 |
+
# Q = (1/n) * D^(5/3) * W * S^(1/2)
|
| 211 |
+
# D = (Q * n / (W * S^0.5)) ^ (3/5)
|
| 212 |
+
|
| 213 |
+
val = (discharge * manning_n) / (width * np.sqrt(slope))
|
| 214 |
+
depth = np.power(val, 0.6)
|
| 215 |
+
|
| 216 |
+
return depth
|
| 217 |
+
|
| 218 |
+
def simulate_inundation(self):
|
| 219 |
+
"""ํด์๋ฉด ์์น์ ๋ฐ๋ฅธ ์นจ์ ์๋ฎฌ๋ ์ด์
"""
|
| 220 |
+
# ํด์๋ฉด๋ณด๋ค ๋ฎ์ ๊ณณ์ ๋ฐ๋ค๋ก ๊ฐ์ฃผํ๊ณ ์์ฌ์ ์ฑ์
|
| 221 |
+
underwater = self.grid.is_underwater()
|
| 222 |
+
|
| 223 |
+
# ๋ฐ๋ค ์์ฌ = ํด์๋ฉด - ์งํ๋ฉด๊ณ ๋
|
| 224 |
+
sea_depth = np.maximum(0, self.grid.sea_level - self.grid.elevation)
|
| 225 |
+
|
| 226 |
+
# ๊ธฐ์กด ์์ฌ(ํ์ฒ)๊ณผ ๋ฐ๋ค ์์ฌ ์ค ํฐ ๊ฐ ์ ํ
|
| 227 |
+
# (ํ์ฒ์ด ๋ฐ๋ค๋ก ๋ค์ด๊ฐ๋ฉด ๋ฐ๋ค ์์ฌ์ ๋ฌปํ)
|
| 228 |
+
self.grid.water_depth = np.where(underwater, sea_depth, self.grid.water_depth)
|
| 229 |
+
|
| 230 |
+
def fill_sinks(self, max_iterations: int = 100, tolerance: float = 0.001):
|
| 231 |
+
"""
|
| 232 |
+
์ฑํฌ(์
๋ฉ์ด) ์ฑ์ฐ๊ธฐ - ํธ์ ํ์ฑ
|
| 233 |
+
|
| 234 |
+
๋ฌผ์ด ๊ฐํ๋ ๊ณณ์ ์ฐพ์ ์ฑ์์ ์๋ฅ(Overflow)๊ฐ ๊ฐ๋ฅํ๋๋ก ํจ.
|
| 235 |
+
๊ฐ๋จํ ๋ฐ๋ณต ์ค๋ฌด๋ฉ ๋ฐฉ์ (Priority-Flood ๊ทผ์ฌ)
|
| 236 |
+
|
| 237 |
+
Args:
|
| 238 |
+
max_iterations: ์ต๋ ๋ฐ๋ณต ํ์
|
| 239 |
+
tolerance: ์๋ ด ํ์ฉ ์ค์ฐจ
|
| 240 |
+
"""
|
| 241 |
+
h, w = self.grid.height, self.grid.width
|
| 242 |
+
elev = self.grid.elevation.copy()
|
| 243 |
+
|
| 244 |
+
# ๊ฒฝ๊ณ๋ ๊ณ ์ (๋ฌผ์ด ๋น ์ ธ๋๊ฐ)
|
| 245 |
+
# ๋ด๋ถ ์ฑํฌ๋ง ์ฑ์
|
| 246 |
+
|
| 247 |
+
dr = [-1, -1, -1, 0, 0, 1, 1, 1]
|
| 248 |
+
dc = [-1, 0, 1, -1, 1, -1, 0, 1]
|
| 249 |
+
|
| 250 |
+
for iteration in range(max_iterations):
|
| 251 |
+
changed = False
|
| 252 |
+
new_elev = elev.copy()
|
| 253 |
+
|
| 254 |
+
for r in range(1, h - 1):
|
| 255 |
+
for c in range(1, w - 1):
|
| 256 |
+
current = elev[r, c]
|
| 257 |
+
|
| 258 |
+
# ์ด์ ์ค ์ต์๊ฐ ์ฐพ๊ธฐ
|
| 259 |
+
min_neighbor = current
|
| 260 |
+
for k in range(8):
|
| 261 |
+
nr, nc = r + dr[k], c + dc[k]
|
| 262 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 263 |
+
min_neighbor = min(min_neighbor, elev[nr, nc])
|
| 264 |
+
|
| 265 |
+
# ๋ชจ๋ ์ด์๋ณด๋ค ๋ฎ์ผ๋ฉด (์ฑํฌ) โ ์ต์ ์ด์ ๋์ด๋ก ๋ง์ถค
|
| 266 |
+
if current < min_neighbor:
|
| 267 |
+
# ์ด์ง ๋์ฌ์ ํ๋ฆ ์ ๋
|
| 268 |
+
new_elev[r, c] = min_neighbor + tolerance
|
| 269 |
+
changed = True
|
| 270 |
+
|
| 271 |
+
elev = new_elev
|
| 272 |
+
|
| 273 |
+
if not changed:
|
| 274 |
+
break
|
| 275 |
+
|
| 276 |
+
# ์ฑ์์ง ์ = ์ ๊ณ ๋ - ๊ธฐ์กด ๊ณ ๋
|
| 277 |
+
fill_amount = elev - self.grid.elevation
|
| 278 |
+
|
| 279 |
+
# ๋ฌผ๋ก ์ฑ์์ง ๊ฒ์ผ๋ก ์ฒ๋ฆฌ (water_depth ์ฆ๊ฐ)
|
| 280 |
+
self.grid.water_depth += np.maximum(fill_amount, 0)
|
| 281 |
+
|
| 282 |
+
# bedrock์ ๊ทธ๋๋ก, sediment๋ ๊ทธ๋๋ก (๋ฌผ๋ง ์ฑ์)
|
| 283 |
+
# ๋๋ sediment๋ก ์ฑ์ธ ์๋ ์์ (ํธ์ ํด์ )
|
| 284 |
+
|
| 285 |
+
return fill_amount
|
| 286 |
+
|
engine/glacier.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Glacier Kernel (๋นํ ์ปค๋)
|
| 3 |
+
|
| 4 |
+
๋นํ ์งํ ํ์ฑ ํ๋ก์ธ์ค
|
| 5 |
+
- ๋นํ ์นจ์: Plucking (๋ฏ์ด๋ด๊ธฐ), Abrasion (๋ง์)
|
| 6 |
+
- U์๊ณก ํ์ฑ
|
| 7 |
+
- ๋ชจ๋ ์ธ(Moraine) ํด์
|
| 8 |
+
|
| 9 |
+
ํต์ฌ:
|
| 10 |
+
- ๋นํ ๋๊ป์ ๋น๋กํ ์นจ์
|
| 11 |
+
- ์ธก๋ฉด๋ณด๋ค ์ค์ ์นจ์์ด ๊ฐํจ โ U์ ํํ
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import numpy as np
|
| 15 |
+
from .grid import WorldGrid
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class GlacierKernel:
|
| 19 |
+
"""
|
| 20 |
+
๋นํ ์ปค๋
|
| 21 |
+
|
| 22 |
+
๋นํ๊ฐ ์กด์ฌํ๋ ์ง์ญ์์ ์นจ์๊ณผ ํด์ ์ ์๋ฎฌ๋ ์ด์
.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self, grid: WorldGrid,
|
| 26 |
+
ice_threshold_temp: float = 0.0, # ๋นํ ํ์ฑ ๊ธฐ์จ (ยฐC)
|
| 27 |
+
K_erosion: float = 0.0001,
|
| 28 |
+
sliding_velocity: float = 10.0): # m/year
|
| 29 |
+
self.grid = grid
|
| 30 |
+
self.ice_threshold = ice_threshold_temp
|
| 31 |
+
self.K = K_erosion
|
| 32 |
+
self.sliding_velocity = sliding_velocity
|
| 33 |
+
|
| 34 |
+
# ๋นํ ๋๊ป ๋ฐฐ์ด
|
| 35 |
+
self.ice_thickness = np.zeros((grid.height, grid.width))
|
| 36 |
+
|
| 37 |
+
def accumulate_ice(self, temperature: np.ndarray,
|
| 38 |
+
precipitation: np.ndarray,
|
| 39 |
+
dt: float = 1.0):
|
| 40 |
+
"""
|
| 41 |
+
๋นํ ์ถ์
|
| 42 |
+
|
| 43 |
+
๊ธฐ์จ < ์๊ณ๊ฐ์ธ ๊ณณ์ ๋/๋นํ ์ถ์
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
temperature: ๊ธฐ์จ ๋ฐฐ์ด
|
| 47 |
+
precipitation: ๊ฐ์๋ ๋ฐฐ์ด
|
| 48 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 49 |
+
"""
|
| 50 |
+
h, w = self.grid.height, self.grid.width
|
| 51 |
+
|
| 52 |
+
# ๋นํ ์ถ์ ์กฐ๊ฑด: ๊ธฐ์จ < 0ยฐC
|
| 53 |
+
cold_mask = temperature < self.ice_threshold
|
| 54 |
+
|
| 55 |
+
# ์ถ์ ๋ฅ = ๊ฐ์๋ * (๊ธฐ์จ์ด ๋ฎ์์๋ก ๋ ๋ง์ด)
|
| 56 |
+
accumulation_rate = precipitation * np.clip(-temperature / 10.0, 0, 1) * 0.001
|
| 57 |
+
|
| 58 |
+
# ์ถ์
|
| 59 |
+
self.ice_thickness[cold_mask] += accumulation_rate[cold_mask] * dt
|
| 60 |
+
|
| 61 |
+
# ๋นํ ์๋ฉธ (๊ธฐ์จ > 0ยฐC)
|
| 62 |
+
warm_mask = temperature > self.ice_threshold
|
| 63 |
+
melt_rate = temperature * 0.01 # ๊ธฐ์จ๋น ์ตํด์จ
|
| 64 |
+
self.ice_thickness[warm_mask] -= np.minimum(
|
| 65 |
+
melt_rate[warm_mask] * dt,
|
| 66 |
+
self.ice_thickness[warm_mask]
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
self.ice_thickness = np.maximum(self.ice_thickness, 0)
|
| 70 |
+
|
| 71 |
+
def flow_ice(self, dt: float = 1.0):
|
| 72 |
+
"""
|
| 73 |
+
๋นํ ํ๋ฆ (์ค๋ ฅ์ ์ํ ํ๊ฐ)
|
| 74 |
+
|
| 75 |
+
๋์ ๊ณณ์์ ๋ฎ์ ๊ณณ์ผ๋ก ๋นํ ์ด๋
|
| 76 |
+
"""
|
| 77 |
+
h, w = self.grid.height, self.grid.width
|
| 78 |
+
|
| 79 |
+
slope, _ = self.grid.get_gradient()
|
| 80 |
+
|
| 81 |
+
# ๋นํ ํ๋ฆ ์๋ = ๊ธฐ๋ณธ ์๋ * ๊ฒฝ์ฌ * ๋๊ป
|
| 82 |
+
flow_speed = self.sliding_velocity * slope * np.sqrt(self.ice_thickness + 0.1)
|
| 83 |
+
|
| 84 |
+
# D8 ๋ฐฉํฅ์ผ๋ก ๋นํ ์ด๋ (๊ฐ๋จํ ๊ทผ์ฌ)
|
| 85 |
+
dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
|
| 86 |
+
dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
|
| 87 |
+
|
| 88 |
+
new_ice = self.ice_thickness.copy()
|
| 89 |
+
elev = self.grid.elevation
|
| 90 |
+
|
| 91 |
+
for r in range(1, h - 1):
|
| 92 |
+
for c in range(1, w - 1):
|
| 93 |
+
if self.ice_thickness[r, c] <= 0:
|
| 94 |
+
continue
|
| 95 |
+
|
| 96 |
+
# ๊ฐ์ฅ ๋ฎ์ ์ด์ ์ฐพ๊ธฐ
|
| 97 |
+
min_z = elev[r, c]
|
| 98 |
+
target = None
|
| 99 |
+
|
| 100 |
+
for k in range(8):
|
| 101 |
+
nr, nc = r + dr[k], c + dc[k]
|
| 102 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 103 |
+
if elev[nr, nc] < min_z:
|
| 104 |
+
min_z = elev[nr, nc]
|
| 105 |
+
target = (nr, nc)
|
| 106 |
+
|
| 107 |
+
if target:
|
| 108 |
+
tr, tc = target
|
| 109 |
+
# ์ด๋๋ (์๋์ ๋น๋ก, ์ต๋ 10%)
|
| 110 |
+
move = min(self.ice_thickness[r, c] * 0.1 * dt, self.ice_thickness[r, c])
|
| 111 |
+
new_ice[r, c] -= move
|
| 112 |
+
new_ice[tr, tc] += move
|
| 113 |
+
|
| 114 |
+
self.ice_thickness = new_ice
|
| 115 |
+
|
| 116 |
+
def erode(self, dt: float = 1.0) -> np.ndarray:
|
| 117 |
+
"""
|
| 118 |
+
๋นํ ์นจ์
|
| 119 |
+
|
| 120 |
+
U์๊ณก ํ์ฑ์ ์ํ ์ฐจ๋ณ ์นจ์
|
| 121 |
+
- ์ค์(๋นํ ๋๊บผ์ด ๊ณณ): ๊ฐํ ์นจ์
|
| 122 |
+
- ์ธก๋ฉด: ์ฝํ ์นจ์
|
| 123 |
+
|
| 124 |
+
Returns:
|
| 125 |
+
erosion: ์นจ์๋ ๋ฐฐ์ด
|
| 126 |
+
"""
|
| 127 |
+
h, w = self.grid.height, self.grid.width
|
| 128 |
+
|
| 129 |
+
# ๋นํ๊ฐ ์๋ ๊ณณ๋ง ์นจ์
|
| 130 |
+
glacier_mask = self.ice_thickness > 0.1
|
| 131 |
+
|
| 132 |
+
erosion = np.zeros((h, w), dtype=np.float64)
|
| 133 |
+
|
| 134 |
+
if not np.any(glacier_mask):
|
| 135 |
+
return erosion
|
| 136 |
+
|
| 137 |
+
# ์นจ์๋ฅ = K * ๋๊ป * ์๋ * ๊ฒฝ์ฌ
|
| 138 |
+
slope, _ = self.grid.get_gradient()
|
| 139 |
+
|
| 140 |
+
erosion_rate = self.K * self.ice_thickness * self.sliding_velocity * slope
|
| 141 |
+
erosion[glacier_mask] = erosion_rate[glacier_mask] * dt
|
| 142 |
+
|
| 143 |
+
# ์นจ์ ์ ์ฉ
|
| 144 |
+
self.grid.bedrock -= erosion
|
| 145 |
+
self.grid.update_elevation()
|
| 146 |
+
|
| 147 |
+
return erosion
|
| 148 |
+
|
| 149 |
+
def deposit_moraine(self, dt: float = 1.0) -> np.ndarray:
|
| 150 |
+
"""
|
| 151 |
+
๋ชจ๋ ์ธ ํด์
|
| 152 |
+
|
| 153 |
+
๋นํ ๋ง๋จ(thickness ๊ธ๊ฐ ์ง์ )์ ํด์
|
| 154 |
+
|
| 155 |
+
Returns:
|
| 156 |
+
deposition: ํด์ ๋ ๋ฐฐ์ด
|
| 157 |
+
"""
|
| 158 |
+
h, w = self.grid.height, self.grid.width
|
| 159 |
+
|
| 160 |
+
deposition = np.zeros((h, w), dtype=np.float64)
|
| 161 |
+
|
| 162 |
+
# ๋นํ ๋๊ป ๊ฐ์ ์ง์ = ๋ง๋จ
|
| 163 |
+
# Gradient of ice thickness
|
| 164 |
+
dy, dx = np.gradient(self.ice_thickness)
|
| 165 |
+
ice_gradient = np.sqrt(dx**2 + dy**2)
|
| 166 |
+
|
| 167 |
+
# ๋ง๋จ ์กฐ๊ฑด: ๋นํ ์๊ณ , gradient ํผ
|
| 168 |
+
terminal_mask = (self.ice_thickness > 0.1) & (ice_gradient > 0.1)
|
| 169 |
+
|
| 170 |
+
# ํด์ ๋ = gradient์ ๋น๋ก
|
| 171 |
+
deposition[terminal_mask] = ice_gradient[terminal_mask] * 0.01 * dt
|
| 172 |
+
|
| 173 |
+
# ํด์ ์ ์ฉ
|
| 174 |
+
self.grid.add_sediment(deposition)
|
| 175 |
+
|
| 176 |
+
return deposition
|
| 177 |
+
|
| 178 |
+
def step(self, temperature: np.ndarray = None,
|
| 179 |
+
precipitation: np.ndarray = None,
|
| 180 |
+
dt: float = 1.0) -> dict:
|
| 181 |
+
"""
|
| 182 |
+
1๋จ๊ณ ๋นํ ์์ฉ ์คํ
|
| 183 |
+
|
| 184 |
+
Args:
|
| 185 |
+
temperature: ๊ธฐ์จ ๋ฐฐ์ด (์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ)
|
| 186 |
+
precipitation: ๊ฐ์๋ ๋ฐฐ์ด
|
| 187 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 188 |
+
|
| 189 |
+
Returns:
|
| 190 |
+
result: ๋นํ ์ํ ๋ฐ ๋ณํ๋
|
| 191 |
+
"""
|
| 192 |
+
h, w = self.grid.height, self.grid.width
|
| 193 |
+
|
| 194 |
+
# ๊ธฐ๋ณธ ๊ธฐ์จ/๊ฐ์
|
| 195 |
+
if temperature is None:
|
| 196 |
+
# ๊ณ ๋ ๊ธฐ๋ฐ ๊ฐ๋จํ ๊ธฐ์จ ๊ณ์ฐ
|
| 197 |
+
temperature = 15.0 - (self.grid.elevation / 1000.0) * 6.5
|
| 198 |
+
|
| 199 |
+
if precipitation is None:
|
| 200 |
+
precipitation = np.ones((h, w)) * 1000.0 # mm/year
|
| 201 |
+
|
| 202 |
+
# 1. ๋นํ ์ถ์ /์๋ฉธ
|
| 203 |
+
self.accumulate_ice(temperature, precipitation, dt)
|
| 204 |
+
|
| 205 |
+
# 2. ๋นํ ํ๋ฆ
|
| 206 |
+
self.flow_ice(dt)
|
| 207 |
+
|
| 208 |
+
# 3. ์นจ์
|
| 209 |
+
erosion = self.erode(dt)
|
| 210 |
+
|
| 211 |
+
# 4. ๋ชจ๋ ์ธ ํด์
|
| 212 |
+
moraine = self.deposit_moraine(dt)
|
| 213 |
+
|
| 214 |
+
return {
|
| 215 |
+
'ice_thickness': self.ice_thickness.copy(),
|
| 216 |
+
'erosion': erosion,
|
| 217 |
+
'moraine': moraine
|
| 218 |
+
}
|
engine/grid.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from dataclasses import dataclass, field
|
| 3 |
+
from typing import Tuple, Optional
|
| 4 |
+
|
| 5 |
+
@dataclass
|
| 6 |
+
class WorldGrid:
|
| 7 |
+
"""
|
| 8 |
+
์ง๊ตฌ ์์คํ
ํตํฉ ๊ทธ๋ฆฌ๋ (World Grid)
|
| 9 |
+
|
| 10 |
+
๋ชจ๋ ๋ฌผ๋ฆฌ์ ์ํ(๊ณ ๋, ๋ฌผ, ํด์ ๋ฌผ)๋ฅผ ํตํฉ ๊ด๋ฆฌํ๋ ์ค์ ๋ฐ์ดํฐ ๊ตฌ์กฐ์
๋๋ค.
|
| 11 |
+
๊ธฐ์กด์ ๊ฐ๋ณ ์๋ฎฌ๋ ์ด์
๊ทธ๋ฆฌ๋์ ๋ฌ๋ฆฌ, ์ ์ง๊ตฌ์ ํด์๋ฉด(Sea Level)๊ณผ ์ฐ๋๋ฉ๋๋ค.
|
| 12 |
+
"""
|
| 13 |
+
width: int = 100
|
| 14 |
+
height: int = 100
|
| 15 |
+
cell_size: float = 10.0 # ๋ฏธํฐ (m)
|
| 16 |
+
sea_level: float = 0.0 # ํด์๋ฉด ๊ณ ๋ (m)
|
| 17 |
+
|
| 18 |
+
# --- ์ํ ๋ ์ด์ด (State Layers) ---
|
| 19 |
+
# ๊ธฐ๋ฐ์ ๊ณ ๋ (Bedrock Elevation)
|
| 20 |
+
bedrock: np.ndarray = field(default=None)
|
| 21 |
+
# ํด์ ์ธต ๋๊ป (Sediment Thickness)
|
| 22 |
+
sediment: np.ndarray = field(default=None)
|
| 23 |
+
# ์์ฌ (Water Depth) - ํ๋ฉด ์ ์ถ์
|
| 24 |
+
water_depth: np.ndarray = field(default=None)
|
| 25 |
+
# ์ ๋ (Discharge)
|
| 26 |
+
discharge: np.ndarray = field(default=None)
|
| 27 |
+
# ์ ํฅ (Flow Direction)
|
| 28 |
+
flow_dir: np.ndarray = field(default=None)
|
| 29 |
+
|
| 30 |
+
# --- ํ์ ๋ ์ด์ด (Derived Layers) ---
|
| 31 |
+
# ์งํ๋ฉด ๊ณ ๋ (Topography = Bedrock + Sediment)
|
| 32 |
+
elevation: np.ndarray = field(default=None)
|
| 33 |
+
|
| 34 |
+
def __post_init__(self):
|
| 35 |
+
"""๊ทธ๋ฆฌ๋ ์ด๊ธฐํ"""
|
| 36 |
+
shape = (self.height, self.width)
|
| 37 |
+
|
| 38 |
+
if self.bedrock is None:
|
| 39 |
+
self.bedrock = np.zeros(shape)
|
| 40 |
+
if self.sediment is None:
|
| 41 |
+
self.sediment = np.zeros(shape)
|
| 42 |
+
if self.water_depth is None:
|
| 43 |
+
self.water_depth = np.zeros(shape)
|
| 44 |
+
if self.discharge is None:
|
| 45 |
+
self.discharge = np.zeros(shape)
|
| 46 |
+
if self.flow_dir is None:
|
| 47 |
+
self.flow_dir = np.zeros(shape, dtype=int)
|
| 48 |
+
if self.elevation is None:
|
| 49 |
+
self.update_elevation()
|
| 50 |
+
|
| 51 |
+
def update_elevation(self):
|
| 52 |
+
"""์งํ๋ฉด ๊ณ ๋ ๊ฐฑ์ (๊ธฐ๋ฐ์ + ํด์ ์ธต)"""
|
| 53 |
+
self.elevation = self.bedrock + self.sediment
|
| 54 |
+
|
| 55 |
+
def get_gradient(self) -> Tuple[np.ndarray, np.ndarray]:
|
| 56 |
+
"""
|
| 57 |
+
๊ฒฝ์ฌ๋(Slope)์ ๊ฒฝ์ฌํฅ(Aspect) ๊ณ์ฐ
|
| 58 |
+
Returns:
|
| 59 |
+
slope (m/m): ๊ฒฝ์ฌ๋
|
| 60 |
+
aspect (rad): ๊ฒฝ์ฌ ๋ฐฉํฅ (0=East, pi/2=North)
|
| 61 |
+
"""
|
| 62 |
+
dy, dx = np.gradient(self.elevation, self.cell_size)
|
| 63 |
+
slope = np.sqrt(dx**2 + dy**2)
|
| 64 |
+
aspect = np.arctan2(dy, dx)
|
| 65 |
+
return slope, aspect
|
| 66 |
+
|
| 67 |
+
def get_water_surface(self) -> np.ndarray:
|
| 68 |
+
"""์๋ฉด ๊ณ ๋ ๋ฐํ (์งํ๋ฉด + ์์ฌ)"""
|
| 69 |
+
return self.elevation + self.water_depth
|
| 70 |
+
|
| 71 |
+
def is_underwater(self) -> np.ndarray:
|
| 72 |
+
"""ํด์๋ฉด ๊ธฐ์ค ์นจ์ ์ฌ๋ถ ํ์ธ"""
|
| 73 |
+
# ํด์๋ฉด๋ณด๋ค ๋ฎ๊ฑฐ๋, ์งํ๋ฉด์ ๋ฌผ์ด ํ๋ฅด๊ณ ์๋ ๊ฒฝ์ฐ
|
| 74 |
+
# ์ฌ๊ธฐ์๋ ๊ฐ๋จํ 'ํด์๋ฉด' ๊ธฐ์ค๊ณผ '๋ด์' ์กด์ฌ ์ฌ๋ถ๋ฅผ ๋ถ๋ฆฌํด์ ์๊ฐํ ์ ์์.
|
| 75 |
+
# ์ผ๋จ ํด์๋ฉด(Sea Level) ๊ธฐ์ค ์นจ์ ์ง์ญ ๋ฐํ
|
| 76 |
+
return self.elevation < self.sea_level
|
| 77 |
+
|
| 78 |
+
def apply_uplift(self, rate: float, dt: float = 1.0):
|
| 79 |
+
"""์ง๋ฐ ์ต๊ธฐ ์ ์ฉ"""
|
| 80 |
+
self.bedrock += rate * dt
|
| 81 |
+
self.update_elevation()
|
| 82 |
+
|
| 83 |
+
def add_sediment(self, amount: np.ndarray):
|
| 84 |
+
"""ํด์ ๋ฌผ ์ถ๊ฐ/์ ๊ฑฐ"""
|
| 85 |
+
self.sediment += amount
|
| 86 |
+
# ํด์ ๋ฌผ์ 0๋ณด๋ค ์์ ์ ์์ (๊ธฐ๋ฐ์ ์นจ์์ ๋ณ๋ ๋ก์ง)
|
| 87 |
+
self.sediment = np.maximum(self.sediment, 0)
|
| 88 |
+
self.update_elevation()
|
engine/ideal_landforms.py
ADDED
|
@@ -0,0 +1,1621 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Ideal Landform Geometry Models (์ด์์ ์งํ ๊ธฐํํ ๋ชจ๋ธ)
|
| 3 |
+
|
| 4 |
+
๊ต๊ณผ์์ ์ธ ์งํ ํํ๋ฅผ ๊ธฐํํ์ ์ผ๋ก ์์ฑ.
|
| 5 |
+
๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
์ด ์๋, ์ง์ ์ํ์ ์ผ๋ก "์ด์์ ํํ"๋ฅผ ๊ทธ๋ฆผ.
|
| 6 |
+
|
| 7 |
+
- ์ผ๊ฐ์ฃผ: ๋ถ์ฑ๊ผด (Sector)
|
| 8 |
+
- ์ ์์ง: ์๋ฟ (Cone)
|
| 9 |
+
- ๊ณก๋ฅ: S์ ๊ณก์ (Kinoshita Curve)
|
| 10 |
+
- U์๊ณก: ํฌ๋ฌผ์ ๋จ๋ฉด
|
| 11 |
+
- V์๊ณก: ์ผ๊ฐํ ๋จ๋ฉด
|
| 12 |
+
- ํด์ ์ ๋ฒฝ: ๊ณ๋จํ ํํด
|
| 13 |
+
- ์ฌ๊ตฌ: ๋ฐ๋ฅดํ (Crescent)
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import numpy as np
|
| 17 |
+
from typing import Tuple
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def create_delta(grid_size: int = 100,
|
| 21 |
+
apex_row: float = 0.2,
|
| 22 |
+
spread_angle: float = 120.0,
|
| 23 |
+
num_channels: int = 7) -> np.ndarray:
|
| 24 |
+
"""
|
| 25 |
+
์ผ๊ฐ์ฃผ (Delta) - ์กฐ์กฑ์/๋ถ์ฑ๊ผด
|
| 26 |
+
|
| 27 |
+
Args:
|
| 28 |
+
grid_size: ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ
|
| 29 |
+
apex_row: ์ ์ (Apex) ์์น (0~1, ์๋จ ๊ธฐ์ค)
|
| 30 |
+
spread_angle: ํผ์ง ๊ฐ๋ (๋)
|
| 31 |
+
num_channels: ๋ถ๋ฐฐ ์๋ก ๊ฐ์
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
elevation: ๊ณ ๋ ๋ฐฐ์ด
|
| 35 |
+
"""
|
| 36 |
+
h, w = grid_size, grid_size
|
| 37 |
+
elevation = np.zeros((h, w))
|
| 38 |
+
|
| 39 |
+
apex_y = int(h * apex_row)
|
| 40 |
+
center_x = w // 2
|
| 41 |
+
|
| 42 |
+
# ๋ฐฐ๊ฒฝ: ๋ฐ๋ค (์์)
|
| 43 |
+
elevation[:, :] = -5.0
|
| 44 |
+
|
| 45 |
+
# ์ก์ง ๋ฐฐ๊ฒฝ (์ผ๊ฐ์ฃผ ์ ์ฒด)
|
| 46 |
+
half_angle = np.radians(spread_angle / 2)
|
| 47 |
+
|
| 48 |
+
for r in range(apex_y, h):
|
| 49 |
+
dist = r - apex_y
|
| 50 |
+
if dist == 0:
|
| 51 |
+
continue
|
| 52 |
+
|
| 53 |
+
# ๊ฐ๋ ๋ฒ์ ๋ด ์ก์ง
|
| 54 |
+
for c in range(w):
|
| 55 |
+
dx = c - center_x
|
| 56 |
+
angle = np.arctan2(dx, dist) # ์ ์ ๊ธฐ์ค ๊ฐ๋
|
| 57 |
+
|
| 58 |
+
if abs(angle) < half_angle:
|
| 59 |
+
# ์ผ๊ฐ์ฃผ ์ก์ง
|
| 60 |
+
# ์ค์ฌ์์ ๋ฉ์๋ก ๋ฎ์์ง
|
| 61 |
+
radial_dist = np.sqrt(dx**2 + dist**2)
|
| 62 |
+
max_dist = h - apex_y
|
| 63 |
+
elevation[r, c] = 10.0 * (1 - radial_dist / max_dist)
|
| 64 |
+
|
| 65 |
+
# ๋ถ๋ฐฐ ์๋ก (Distributary Channels)
|
| 66 |
+
for i in range(num_channels):
|
| 67 |
+
channel_angle = -half_angle + (2 * half_angle) * (i / (num_channels - 1))
|
| 68 |
+
|
| 69 |
+
for r in range(apex_y, h):
|
| 70 |
+
dist = r - apex_y
|
| 71 |
+
c = int(center_x + dist * np.tan(channel_angle))
|
| 72 |
+
|
| 73 |
+
if 0 <= c < w:
|
| 74 |
+
# ์๋ก ํ๊ธฐ (์๊ฐ)
|
| 75 |
+
for dc in range(-2, 3):
|
| 76 |
+
if 0 <= c + dc < w:
|
| 77 |
+
depth = 2.0 * (1 - abs(dc) / 3)
|
| 78 |
+
elevation[r, c + dc] -= depth
|
| 79 |
+
|
| 80 |
+
return elevation
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def create_alluvial_fan(grid_size: int = 100,
|
| 84 |
+
apex_row: float = 0.15,
|
| 85 |
+
cone_angle: float = 90.0,
|
| 86 |
+
max_height: float = 50.0) -> np.ndarray:
|
| 87 |
+
"""
|
| 88 |
+
์ ์์ง (Alluvial Fan) - ์๋ฟํ
|
| 89 |
+
|
| 90 |
+
Args:
|
| 91 |
+
grid_size: ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ
|
| 92 |
+
apex_row: ์ ์ ์์น
|
| 93 |
+
cone_angle: ๋ถ์ฑ๊ผด ๊ฐ๋
|
| 94 |
+
max_height: ์ต๋ ๊ณ ๋
|
| 95 |
+
|
| 96 |
+
Returns:
|
| 97 |
+
elevation: ๊ณ ๋ ๋ฐฐ์ด
|
| 98 |
+
"""
|
| 99 |
+
h, w = grid_size, grid_size
|
| 100 |
+
elevation = np.zeros((h, w))
|
| 101 |
+
|
| 102 |
+
apex_y = int(h * apex_row)
|
| 103 |
+
center_x = w // 2
|
| 104 |
+
half_angle = np.radians(cone_angle / 2)
|
| 105 |
+
|
| 106 |
+
# ๋ฐฐ๊ฒฝ ์ฐ์ง (์๋จ)
|
| 107 |
+
for r in range(apex_y):
|
| 108 |
+
elevation[r, :] = max_height + (apex_y - r) * 2.0
|
| 109 |
+
|
| 110 |
+
# ์ ์์ง ๋ณธ์ฒด (์๋ฟ)
|
| 111 |
+
for r in range(apex_y, h):
|
| 112 |
+
dist = r - apex_y
|
| 113 |
+
max_dist = h - apex_y
|
| 114 |
+
|
| 115 |
+
for c in range(w):
|
| 116 |
+
dx = c - center_x
|
| 117 |
+
angle = np.arctan2(abs(dx), dist) if dist > 0 else 0
|
| 118 |
+
|
| 119 |
+
if abs(np.arctan2(dx, dist)) < half_angle:
|
| 120 |
+
# ์๋ฟ ํํ: ์ค์ฌ์ด ๋๊ณ , ๊ฐ์ฅ์๋ฆฌ๊ฐ ๋ฎ์
|
| 121 |
+
radial = np.sqrt(dx**2 + dist**2)
|
| 122 |
+
# ์ ์ ์์ ๋ฉ์ด์ง์๋ก ๋ฎ์์ง
|
| 123 |
+
z = max_height * (1 - radial / (max_dist * 1.5))
|
| 124 |
+
# ๊ฐ์ฅ์๋ฆฌ๋ก ๊ฐ์๋ก ๋ ๊ธ๊ฒฉํ ๋ฎ์์ง
|
| 125 |
+
lateral_decay = 1 - abs(dx) / (w // 2)
|
| 126 |
+
elevation[r, c] = max(0, z * lateral_decay)
|
| 127 |
+
else:
|
| 128 |
+
elevation[r, c] = 0 # ํ์ง
|
| 129 |
+
|
| 130 |
+
# ํ๊ณก (Apex์์ ์์)
|
| 131 |
+
for r in range(0, apex_y + 5):
|
| 132 |
+
for dc in range(-3, 4):
|
| 133 |
+
c = center_x + dc
|
| 134 |
+
if 0 <= c < w:
|
| 135 |
+
depth = 10.0 * (1 - abs(dc) / 4)
|
| 136 |
+
elevation[r, c] -= depth
|
| 137 |
+
|
| 138 |
+
return elevation
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def create_meander(grid_size: int = 100,
|
| 142 |
+
amplitude: float = 0.3,
|
| 143 |
+
wavelength: float = 0.25,
|
| 144 |
+
num_bends: int = 3) -> np.ndarray:
|
| 145 |
+
"""
|
| 146 |
+
๊ณก๋ฅ (Meander) - S์ ์ฌํ ํ์ฒ
|
| 147 |
+
|
| 148 |
+
Args:
|
| 149 |
+
grid_size: ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ
|
| 150 |
+
amplitude: ์ฌํ ์งํญ (๊ทธ๋ฆฌ๋ ๋น์จ)
|
| 151 |
+
wavelength: ํ์ฅ (๊ทธ๋ฆฌ๋ ๋น์จ)
|
| 152 |
+
num_bends: ๊ตฝ์ด ๊ฐ์
|
| 153 |
+
|
| 154 |
+
Returns:
|
| 155 |
+
elevation: ๊ณ ๋ ๋ฐฐ์ด
|
| 156 |
+
"""
|
| 157 |
+
h, w = grid_size, grid_size
|
| 158 |
+
elevation = np.zeros((h, w))
|
| 159 |
+
|
| 160 |
+
# ๋ฐฐ๊ฒฝ: ๋ฒ๋์ ํํ๋ฉด
|
| 161 |
+
elevation[:, :] = 10.0
|
| 162 |
+
|
| 163 |
+
center_x = w // 2
|
| 164 |
+
amp = w * amplitude
|
| 165 |
+
wl = h / num_bends
|
| 166 |
+
channel_width = max(3, w // 20)
|
| 167 |
+
|
| 168 |
+
# ์ฌํ ํ์ฒ ๊ฒฝ๋ก
|
| 169 |
+
for r in range(h):
|
| 170 |
+
# Kinoshita curve (์ด์ํ๋ ๊ณก๋ฅ)
|
| 171 |
+
theta = 2 * np.pi * r / wl
|
| 172 |
+
meander_x = center_x + amp * np.sin(theta)
|
| 173 |
+
|
| 174 |
+
for c in range(w):
|
| 175 |
+
dist = abs(c - meander_x)
|
| 176 |
+
|
| 177 |
+
if dist < channel_width:
|
| 178 |
+
# ํ๋ (๋ฎ๊ฒ)
|
| 179 |
+
elevation[r, c] = 5.0 - (channel_width - dist) * 0.3
|
| 180 |
+
elif dist < channel_width * 3:
|
| 181 |
+
# ์์ฐ์ ๋ฐฉ (์ฝ๊ฐ ๋๊ฒ)
|
| 182 |
+
elevation[r, c] = 10.5
|
| 183 |
+
|
| 184 |
+
# ์ฐ๊ฐํธ (Oxbow Lake) ์ถ๊ฐ
|
| 185 |
+
# ์ค๊ฐ์ฏค์ ์ ๋จ๋ ๊ณก๋ฅ ํ์
|
| 186 |
+
oxbow_y = h // 2
|
| 187 |
+
oxbow_amp = amp * 1.5
|
| 188 |
+
|
| 189 |
+
for dy in range(-int(wl/4), int(wl/4)):
|
| 190 |
+
r = oxbow_y + dy
|
| 191 |
+
if 0 <= r < h:
|
| 192 |
+
theta = 2 * np.pi * dy / (wl/2)
|
| 193 |
+
ox_x = center_x + oxbow_amp * np.sin(theta)
|
| 194 |
+
|
| 195 |
+
for dc in range(-channel_width, channel_width + 1):
|
| 196 |
+
c = int(ox_x + dc)
|
| 197 |
+
if 0 <= c < w:
|
| 198 |
+
elevation[r, c] = 4.0 # ํธ์ ์๋ฉด
|
| 199 |
+
|
| 200 |
+
return elevation
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def create_u_valley(grid_size: int = 100,
|
| 204 |
+
valley_depth: float = 100.0,
|
| 205 |
+
valley_width: float = 0.4) -> np.ndarray:
|
| 206 |
+
"""
|
| 207 |
+
U์๊ณก (U-shaped Valley) - ๋นํ ์นจ์ ์งํ
|
| 208 |
+
|
| 209 |
+
Args:
|
| 210 |
+
grid_size: ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ
|
| 211 |
+
valley_depth: ๊ณก์ ๊น์ด
|
| 212 |
+
valley_width: ๊ณก์ ๋๋น (๋น์จ)
|
| 213 |
+
|
| 214 |
+
Returns:
|
| 215 |
+
elevation: ๊ณ ๋ ๋ฐฐ์ด
|
| 216 |
+
"""
|
| 217 |
+
h, w = grid_size, grid_size
|
| 218 |
+
elevation = np.zeros((h, w))
|
| 219 |
+
|
| 220 |
+
center = w // 2
|
| 221 |
+
half_width = int(w * valley_width / 2)
|
| 222 |
+
|
| 223 |
+
for r in range(h):
|
| 224 |
+
for c in range(w):
|
| 225 |
+
dx = abs(c - center)
|
| 226 |
+
|
| 227 |
+
if dx < half_width:
|
| 228 |
+
# U์ ๋ฐ๋ฅ (ํํ)
|
| 229 |
+
elevation[r, c] = 0
|
| 230 |
+
else:
|
| 231 |
+
# U์ ์ธก๋ฒฝ (๊ธ๊ฒฝ์ฌ ํ ์๋ง)
|
| 232 |
+
# y = (x/a)^4 ํํ
|
| 233 |
+
normalized_x = (dx - half_width) / (w // 2 - half_width)
|
| 234 |
+
elevation[r, c] = valley_depth * (normalized_x ** 2)
|
| 235 |
+
|
| 236 |
+
# ์๋ฅ๋ก ๊ฐ์๋ก ๋์์ง
|
| 237 |
+
elevation[r, :] += (h - r) / h * 30.0
|
| 238 |
+
|
| 239 |
+
return elevation
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def create_v_valley(grid_size: int = 100,
|
| 243 |
+
valley_depth: float = 80.0) -> np.ndarray:
|
| 244 |
+
"""
|
| 245 |
+
V์๊ณก (V-shaped Valley) - ํ์ฒ ์นจ์ ์งํ
|
| 246 |
+
|
| 247 |
+
Args:
|
| 248 |
+
grid_size: ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ
|
| 249 |
+
valley_depth: ๊ณก์ ๊น์ด
|
| 250 |
+
|
| 251 |
+
Returns:
|
| 252 |
+
elevation: ๊ณ ๋ ๋ฐฐ์ด
|
| 253 |
+
"""
|
| 254 |
+
h, w = grid_size, grid_size
|
| 255 |
+
elevation = np.zeros((h, w))
|
| 256 |
+
|
| 257 |
+
center = w // 2
|
| 258 |
+
|
| 259 |
+
for r in range(h):
|
| 260 |
+
for c in range(w):
|
| 261 |
+
dx = abs(c - center)
|
| 262 |
+
|
| 263 |
+
# V์ ํํ: |x| ์ ๋น๋ก
|
| 264 |
+
elevation[r, c] = valley_depth * (dx / (w // 2))
|
| 265 |
+
|
| 266 |
+
# ์๋ฅ๋ก ๊ฐ์๋ก ๋์์ง
|
| 267 |
+
elevation[r, :] += (h - r) / h * 50.0
|
| 268 |
+
|
| 269 |
+
# ํ์ฒ (V์ ๋ฐ๋ฅ)
|
| 270 |
+
for r in range(h):
|
| 271 |
+
for dc in range(-2, 3):
|
| 272 |
+
c = center + dc
|
| 273 |
+
if 0 <= c < w:
|
| 274 |
+
elevation[r, c] = max(0, elevation[r, c] - 5)
|
| 275 |
+
|
| 276 |
+
return elevation
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def create_barchan_dune(grid_size: int = 100,
|
| 280 |
+
num_dunes: int = 3) -> np.ndarray:
|
| 281 |
+
"""
|
| 282 |
+
๋ฐ๋ฅดํ ์ฌ๊ตฌ (Barchan Dune) - ์ด์น๋ฌ ๋ชจ์
|
| 283 |
+
|
| 284 |
+
Args:
|
| 285 |
+
grid_size: ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ
|
| 286 |
+
num_dunes: ์ฌ๊ตฌ ๊ฐ์
|
| 287 |
+
|
| 288 |
+
Returns:
|
| 289 |
+
elevation: ๊ณ ๋ ๋ฐฐ์ด
|
| 290 |
+
"""
|
| 291 |
+
h, w = grid_size, grid_size
|
| 292 |
+
elevation = np.zeros((h, w))
|
| 293 |
+
|
| 294 |
+
# ์ฌ๋ง ๊ธฐ๋ฐ๋ฉด
|
| 295 |
+
elevation[:, :] = 5.0
|
| 296 |
+
|
| 297 |
+
for i in range(num_dunes):
|
| 298 |
+
# ์ฌ๊ตฌ ์ค์ฌ
|
| 299 |
+
cy = h // 4 + i * (h // (num_dunes + 1))
|
| 300 |
+
cx = w // 2 + (i - num_dunes // 2) * (w // 5)
|
| 301 |
+
|
| 302 |
+
dune_height = 15.0 + np.random.rand() * 10.0
|
| 303 |
+
dune_length = w // 5
|
| 304 |
+
dune_width = w // 8
|
| 305 |
+
|
| 306 |
+
for r in range(h):
|
| 307 |
+
for c in range(w):
|
| 308 |
+
dy = r - cy
|
| 309 |
+
dx = c - cx
|
| 310 |
+
|
| 311 |
+
# ๋ฐ๋ฅดํ: ๋ฐ๋๋ฐ์ด(์)๋ ์๋ง, ๋ฐ๋๊ทธ๋(๋ค)๋ ๊ธ๊ฒฝ์ฌ
|
| 312 |
+
# ์ด์น๋ฌ ํํ
|
| 313 |
+
|
| 314 |
+
# ๊ฑฐ๋ฆฌ
|
| 315 |
+
dist = np.sqrt((dy / dune_length) ** 2 + (dx / dune_width) ** 2)
|
| 316 |
+
|
| 317 |
+
if dist < 1.0:
|
| 318 |
+
# ์ฌ๊ตฌ ๋ณธ์ฒด
|
| 319 |
+
# ์์ชฝ(๋ฐ๋๋ฐ์ด): ์๋งํ ๊ฒฝ์ฌ
|
| 320 |
+
# ๋ค์ชฝ: ๊ธ๊ฒฝ์ฌ (Slip Face)
|
| 321 |
+
|
| 322 |
+
if dy < 0: # ๋ฐ๋๋ฐ์ด
|
| 323 |
+
z = dune_height * (1 - dist) * (1 - abs(dy) / dune_length)
|
| 324 |
+
else: # ๋ฐ๋๊ทธ๋
|
| 325 |
+
z = dune_height * (1 - dist) * max(0, 1 - dy / (dune_length * 0.5))
|
| 326 |
+
|
| 327 |
+
# ์ด์น๋ฌ ๋ฟ (Horns)
|
| 328 |
+
horn_factor = 1 + 0.5 * abs(dx / dune_width)
|
| 329 |
+
|
| 330 |
+
elevation[r, c] = max(elevation[r, c], 5.0 + z * horn_factor)
|
| 331 |
+
|
| 332 |
+
return elevation
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def create_coastal_cliff(grid_size: int = 100,
|
| 336 |
+
cliff_height: float = 30.0,
|
| 337 |
+
num_stacks: int = 2) -> np.ndarray:
|
| 338 |
+
"""
|
| 339 |
+
ํด์ ์ ๋ฒฝ (Coastal Cliff) + ์์คํ
|
| 340 |
+
|
| 341 |
+
Args:
|
| 342 |
+
grid_size: ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ
|
| 343 |
+
cliff_height: ์ ๋ฒฝ ๋์ด
|
| 344 |
+
num_stacks: ์์คํ ๊ฐ์
|
| 345 |
+
|
| 346 |
+
Returns:
|
| 347 |
+
elevation: ๊ณ ๋ ๋ฐฐ์ด
|
| 348 |
+
"""
|
| 349 |
+
h, w = grid_size, grid_size
|
| 350 |
+
elevation = np.zeros((h, w))
|
| 351 |
+
|
| 352 |
+
# ๋ฐ๋ค (ํ๋จ)
|
| 353 |
+
sea_line = int(h * 0.6)
|
| 354 |
+
elevation[sea_line:, :] = -5.0
|
| 355 |
+
|
| 356 |
+
# ์ก์ง + ์ ๋ฒฝ
|
| 357 |
+
for r in range(sea_line):
|
| 358 |
+
cliff_dist = sea_line - r
|
| 359 |
+
if cliff_dist < 5:
|
| 360 |
+
# ์ ๋ฒฝ๋ฉด
|
| 361 |
+
elevation[r, :] = cliff_height * (cliff_dist / 5)
|
| 362 |
+
else:
|
| 363 |
+
# ํํํ ์ก์ง
|
| 364 |
+
elevation[r, :] = cliff_height
|
| 365 |
+
|
| 366 |
+
# ํ์๋ (Wave-cut Platform)
|
| 367 |
+
for r in range(sea_line, sea_line + 10):
|
| 368 |
+
if r < h:
|
| 369 |
+
elevation[r, :] = -2.0 + (r - sea_line) * 0.2
|
| 370 |
+
|
| 371 |
+
# ์์คํ (Sea Stacks)
|
| 372 |
+
for i in range(num_stacks):
|
| 373 |
+
sx = w // 3 + i * (w // 3)
|
| 374 |
+
sy = sea_line + 5 + i * 3
|
| 375 |
+
|
| 376 |
+
stack_height = cliff_height * 0.7
|
| 377 |
+
|
| 378 |
+
for dr in range(-3, 4):
|
| 379 |
+
for dc in range(-3, 4):
|
| 380 |
+
r, c = sy + dr, sx + dc
|
| 381 |
+
if 0 <= r < h and 0 <= c < w:
|
| 382 |
+
dist = np.sqrt(dr**2 + dc**2)
|
| 383 |
+
if dist < 3:
|
| 384 |
+
elevation[r, c] = stack_height * (1 - dist / 4)
|
| 385 |
+
|
| 386 |
+
return elevation
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
# ============================================
|
| 390 |
+
# ์ ๋๋ฉ์ด์
์ฉ ํ์ฑ๊ณผ์ ํจ์ (Stage-based)
|
| 391 |
+
# stage: 0.0 (์์) ~ 1.0 (์์ฑ)
|
| 392 |
+
# ============================================
|
| 393 |
+
|
| 394 |
+
def create_delta_animated(grid_size: int, stage: float,
|
| 395 |
+
spread_angle: float = 120.0, num_channels: int = 7) -> np.ndarray:
|
| 396 |
+
"""์ผ๊ฐ์ฃผ ํ์ฑ๊ณผ์ ์ ๋๋ฉ์ด์
"""
|
| 397 |
+
h, w = grid_size, grid_size
|
| 398 |
+
elevation = np.zeros((h, w))
|
| 399 |
+
|
| 400 |
+
apex_y = int(h * 0.2)
|
| 401 |
+
center_x = w // 2
|
| 402 |
+
|
| 403 |
+
# ๋ฐฐ๊ฒฝ: ๋ฐ๋ค
|
| 404 |
+
elevation[:, :] = -5.0
|
| 405 |
+
|
| 406 |
+
# ํ์ฒ (ํญ์ ์กด์ฌ)
|
| 407 |
+
for r in range(apex_y):
|
| 408 |
+
for dc in range(-3, 4):
|
| 409 |
+
c = center_x + dc
|
| 410 |
+
if 0 <= c < w:
|
| 411 |
+
elevation[r, c] = 5.0
|
| 412 |
+
|
| 413 |
+
# Stage์ ๋ฐ๋ผ ์ผ๊ฐ์ฃผ ์ฑ์ฅ
|
| 414 |
+
max_reach = int((h - apex_y) * stage)
|
| 415 |
+
half_angle = np.radians(spread_angle / 2) * stage # ๊ฐ๋๋ ์ ์ง์ ํ๋
|
| 416 |
+
|
| 417 |
+
for r in range(apex_y, apex_y + max_reach):
|
| 418 |
+
dist = r - apex_y
|
| 419 |
+
if dist == 0:
|
| 420 |
+
continue
|
| 421 |
+
|
| 422 |
+
for c in range(w):
|
| 423 |
+
dx = c - center_x
|
| 424 |
+
angle = np.arctan2(dx, dist)
|
| 425 |
+
|
| 426 |
+
if abs(angle) < half_angle:
|
| 427 |
+
radial_dist = np.sqrt(dx**2 + dist**2)
|
| 428 |
+
max_dist = max_reach if max_reach > 0 else 1
|
| 429 |
+
z = 10.0 * (1 - radial_dist / max_dist) * stage
|
| 430 |
+
elevation[r, c] = max(elevation[r, c], z)
|
| 431 |
+
|
| 432 |
+
# ๋ถ๋ฐฐ ์๋ก (stage 0.3 ์ดํ)
|
| 433 |
+
if stage > 0.3:
|
| 434 |
+
active_channels = int(num_channels * min(1.0, (stage - 0.3) / 0.7))
|
| 435 |
+
for i in range(active_channels):
|
| 436 |
+
channel_angle = -half_angle + (2 * half_angle) * (i / max(active_channels - 1, 1))
|
| 437 |
+
for r in range(apex_y, apex_y + max_reach):
|
| 438 |
+
dist = r - apex_y
|
| 439 |
+
c = int(center_x + dist * np.tan(channel_angle))
|
| 440 |
+
if 0 <= c < w:
|
| 441 |
+
for dc in range(-2, 3):
|
| 442 |
+
if 0 <= c + dc < w:
|
| 443 |
+
elevation[r, c + dc] -= 1.5
|
| 444 |
+
|
| 445 |
+
return elevation
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def create_alluvial_fan_animated(grid_size: int, stage: float,
|
| 449 |
+
cone_angle: float = 90.0, max_height: float = 50.0) -> np.ndarray:
|
| 450 |
+
"""์ ์์ง ํ์ฑ๊ณผ์ ์ ๋๋ฉ์ด์
"""
|
| 451 |
+
h, w = grid_size, grid_size
|
| 452 |
+
elevation = np.zeros((h, w))
|
| 453 |
+
|
| 454 |
+
apex_y = int(h * 0.15)
|
| 455 |
+
center_x = w // 2
|
| 456 |
+
|
| 457 |
+
# ๋ฐฐ๊ฒฝ ์ฐ์ง (ํญ์ ์กด์ฌ)
|
| 458 |
+
for r in range(apex_y):
|
| 459 |
+
elevation[r, :] = max_height + (apex_y - r) * 2.0
|
| 460 |
+
|
| 461 |
+
# ํ๊ณก
|
| 462 |
+
for r in range(apex_y + 5):
|
| 463 |
+
for dc in range(-3, 4):
|
| 464 |
+
c = center_x + dc
|
| 465 |
+
if 0 <= c < w:
|
| 466 |
+
elevation[r, c] -= 10.0 * (1 - abs(dc) / 4)
|
| 467 |
+
|
| 468 |
+
# Stage์ ๋ฐ๋ผ ์ ์์ง ์ฑ์ฅ
|
| 469 |
+
max_reach = int((h - apex_y) * stage)
|
| 470 |
+
half_angle = np.radians(cone_angle / 2) * (0.5 + 0.5 * stage)
|
| 471 |
+
|
| 472 |
+
for r in range(apex_y, apex_y + max_reach):
|
| 473 |
+
dist = r - apex_y
|
| 474 |
+
for c in range(w):
|
| 475 |
+
dx = c - center_x
|
| 476 |
+
if abs(np.arctan2(dx, max(dist, 1))) < half_angle:
|
| 477 |
+
radial = np.sqrt(dx**2 + dist**2)
|
| 478 |
+
z = max_height * (1 - radial / (max_reach * 1.5)) * stage
|
| 479 |
+
lateral_decay = 1 - abs(dx) / (w // 2)
|
| 480 |
+
elevation[r, c] = max(0, z * lateral_decay)
|
| 481 |
+
|
| 482 |
+
return elevation
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
def create_meander_animated(grid_size: int, stage: float,
|
| 486 |
+
amplitude: float = 0.3, num_bends: int = 3) -> np.ndarray:
|
| 487 |
+
"""๊ณก๋ฅ ํ์ฑ๊ณผ์ ์ ๋๋ฉ์ด์
(์ง์ -> ์ฌํ -> ์ฐ๊ฐํธ)"""
|
| 488 |
+
h, w = grid_size, grid_size
|
| 489 |
+
elevation = np.zeros((h, w))
|
| 490 |
+
elevation[:, :] = 10.0 # ๋ฒ๋์
|
| 491 |
+
|
| 492 |
+
center_x = w // 2
|
| 493 |
+
channel_width = max(3, w // 20)
|
| 494 |
+
|
| 495 |
+
# Stage์ ๋ฐ๋ฅธ ์ฌํ ์งํญ ๋ณํ (์ง์ -> ๊ตฝ์)
|
| 496 |
+
current_amp = w * amplitude * stage
|
| 497 |
+
wl = h / num_bends
|
| 498 |
+
|
| 499 |
+
for r in range(h):
|
| 500 |
+
theta = 2 * np.pi * r / wl
|
| 501 |
+
meander_x = center_x + current_amp * np.sin(theta)
|
| 502 |
+
|
| 503 |
+
for c in range(w):
|
| 504 |
+
dist = abs(c - meander_x)
|
| 505 |
+
if dist < channel_width:
|
| 506 |
+
elevation[r, c] = 5.0 - (channel_width - dist) * 0.3
|
| 507 |
+
elif dist < channel_width * 3:
|
| 508 |
+
elevation[r, c] = 10.5
|
| 509 |
+
|
| 510 |
+
# ์ฐ๊ฐํธ (stage > 0.8)
|
| 511 |
+
if stage > 0.8:
|
| 512 |
+
oxbow_intensity = (stage - 0.8) / 0.2
|
| 513 |
+
oxbow_y = h // 2
|
| 514 |
+
oxbow_amp = current_amp * 1.5
|
| 515 |
+
|
| 516 |
+
for dy in range(-int(wl/4), int(wl/4)):
|
| 517 |
+
r = oxbow_y + dy
|
| 518 |
+
if 0 <= r < h:
|
| 519 |
+
theta = 2 * np.pi * dy / (wl/2)
|
| 520 |
+
ox_x = center_x + oxbow_amp * np.sin(theta)
|
| 521 |
+
for dc in range(-channel_width, channel_width + 1):
|
| 522 |
+
c = int(ox_x + dc)
|
| 523 |
+
if 0 <= c < w:
|
| 524 |
+
elevation[r, c] = 4.0 * oxbow_intensity + elevation[r, c] * (1 - oxbow_intensity)
|
| 525 |
+
|
| 526 |
+
return elevation
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
def create_u_valley_animated(grid_size: int, stage: float,
|
| 530 |
+
valley_depth: float = 100.0, valley_width: float = 0.4) -> np.ndarray:
|
| 531 |
+
"""U์๊ณก ํ์ฑ๊ณผ์ (V์ -> U์ ๋ณํ)"""
|
| 532 |
+
h, w = grid_size, grid_size
|
| 533 |
+
elevation = np.zeros((h, w))
|
| 534 |
+
center = w // 2
|
| 535 |
+
|
| 536 |
+
# V์์์ U์๋ก ๋ณํ
|
| 537 |
+
# stage 0: ์์ V, stage 1: ์์ U
|
| 538 |
+
half_width = int(w * valley_width / 2) * stage # U ๋ฐ๋ฅ ๋๋น
|
| 539 |
+
|
| 540 |
+
for r in range(h):
|
| 541 |
+
for c in range(w):
|
| 542 |
+
dx = abs(c - center)
|
| 543 |
+
|
| 544 |
+
if dx < half_width:
|
| 545 |
+
# U์ ๋ฐ๋ฅ
|
| 546 |
+
elevation[r, c] = 0
|
| 547 |
+
else:
|
| 548 |
+
# V์์ U๋ก ์ ํ
|
| 549 |
+
# V: linear, U: parabolic
|
| 550 |
+
normalized_x = (dx - half_width) / max(1, w // 2 - half_width)
|
| 551 |
+
v_height = valley_depth * normalized_x # V shape
|
| 552 |
+
u_height = valley_depth * (normalized_x ** 2) # U shape
|
| 553 |
+
elevation[r, c] = v_height * (1 - stage) + u_height * stage
|
| 554 |
+
|
| 555 |
+
elevation[r, :] += (h - r) / h * 30.0
|
| 556 |
+
|
| 557 |
+
return elevation
|
| 558 |
+
|
| 559 |
+
|
| 560 |
+
def create_coastal_cliff_animated(grid_size: int, stage: float,
|
| 561 |
+
cliff_height: float = 30.0, num_stacks: int = 2) -> np.ndarray:
|
| 562 |
+
"""ํด์ ์ ๋ฒฝ ํํด ๊ณผ์ """
|
| 563 |
+
h, w = grid_size, grid_size
|
| 564 |
+
elevation = np.zeros((h, w))
|
| 565 |
+
|
| 566 |
+
# Stage์ ๋ฐ๋ฅธ ํด์์ ํํด
|
| 567 |
+
initial_sea_line = int(h * 0.8)
|
| 568 |
+
final_sea_line = int(h * 0.5)
|
| 569 |
+
sea_line = int(initial_sea_line - (initial_sea_line - final_sea_line) * stage)
|
| 570 |
+
|
| 571 |
+
# ๋ฐ๋ค
|
| 572 |
+
elevation[sea_line:, :] = -5.0
|
| 573 |
+
|
| 574 |
+
# ์ก์ง + ์ ๋ฒฝ
|
| 575 |
+
for r in range(sea_line):
|
| 576 |
+
cliff_dist = sea_line - r
|
| 577 |
+
if cliff_dist < 5:
|
| 578 |
+
elevation[r, :] = cliff_height * (cliff_dist / 5)
|
| 579 |
+
else:
|
| 580 |
+
elevation[r, :] = cliff_height
|
| 581 |
+
|
| 582 |
+
# ํ์๋ (stage > 0.3)
|
| 583 |
+
if stage > 0.3:
|
| 584 |
+
platform_width = int(10 * (stage - 0.3) / 0.7)
|
| 585 |
+
for r in range(sea_line, min(h, sea_line + platform_width)):
|
| 586 |
+
elevation[r, :] = -2.0 + (r - sea_line) * 0.2
|
| 587 |
+
|
| 588 |
+
# ์์คํ (stage > 0.6)
|
| 589 |
+
if stage > 0.6:
|
| 590 |
+
stack_stage = (stage - 0.6) / 0.4
|
| 591 |
+
for i in range(num_stacks):
|
| 592 |
+
sx = w // 3 + i * (w // 3)
|
| 593 |
+
sy = sea_line + 5 + i * 3
|
| 594 |
+
stack_height = cliff_height * 0.7 * stack_stage
|
| 595 |
+
|
| 596 |
+
for dr in range(-3, 4):
|
| 597 |
+
for dc in range(-3, 4):
|
| 598 |
+
r, c = sy + dr, sx + dc
|
| 599 |
+
if 0 <= r < h and 0 <= c < w:
|
| 600 |
+
dist = np.sqrt(dr**2 + dc**2)
|
| 601 |
+
if dist < 3:
|
| 602 |
+
elevation[r, c] = stack_height * (1 - dist / 4)
|
| 603 |
+
|
| 604 |
+
return elevation
|
| 605 |
+
|
| 606 |
+
|
| 607 |
+
def create_v_valley_animated(grid_size: int, stage: float,
|
| 608 |
+
valley_depth: float = 80.0) -> np.ndarray:
|
| 609 |
+
"""V์๊ณก ํ์ฑ๊ณผ์ (ํํ๋ฉด -> ์นจ์ -> ๊น์ V์)"""
|
| 610 |
+
h, w = grid_size, grid_size
|
| 611 |
+
elevation = np.zeros((h, w))
|
| 612 |
+
center = w // 2
|
| 613 |
+
|
| 614 |
+
# Stage์ ๋ฐ๋ฅธ ์นจ์ ๊น์ด ์ฆ๊ฐ
|
| 615 |
+
current_depth = valley_depth * stage
|
| 616 |
+
|
| 617 |
+
for r in range(h):
|
| 618 |
+
for c in range(w):
|
| 619 |
+
dx = abs(c - center)
|
| 620 |
+
|
| 621 |
+
# ์ด๊ธฐ ๊ณ ์ ์ํ์์ ์ ์ง์ ์ผ๋ก V์ ํ์ฑ
|
| 622 |
+
base_height = 50.0 # ์ด๊ธฐ ๊ณ ์ ๋์ด
|
| 623 |
+
v_shape = current_depth * (dx / (w // 2))
|
| 624 |
+
|
| 625 |
+
# ์นจ์ ์งํ์ ๋ฐ๋ผ V์ ๊น์ด์ง
|
| 626 |
+
elevation[r, c] = base_height - current_depth + v_shape
|
| 627 |
+
|
| 628 |
+
# ์๋ฅ ๊ฒฝ์ฌ
|
| 629 |
+
elevation[r, :] += (h - r) / h * 30.0
|
| 630 |
+
|
| 631 |
+
# ํ์ฒ (๋จ๊ณ์ ์ผ๋ก ํ์ฑ)
|
| 632 |
+
if stage > 0.2:
|
| 633 |
+
channel_intensity = min(1.0, (stage - 0.2) / 0.8)
|
| 634 |
+
for r in range(h):
|
| 635 |
+
for dc in range(-2, 3):
|
| 636 |
+
c = center + dc
|
| 637 |
+
if 0 <= c < w:
|
| 638 |
+
elevation[r, c] -= 5 * channel_intensity
|
| 639 |
+
|
| 640 |
+
return elevation
|
| 641 |
+
|
| 642 |
+
|
| 643 |
+
def create_barchan_animated(grid_size: int, stage: float,
|
| 644 |
+
num_dunes: int = 3) -> np.ndarray:
|
| 645 |
+
"""๋ฐ๋ฅดํ ์ฌ๊ตฌ ํ์ฑ๊ณผ์ (ํํ ์ฌ๋ง -> ๋ชจ๋ ์ถ์ -> ์ด์น๋ฌ ํ์ฑ)"""
|
| 646 |
+
h, w = grid_size, grid_size
|
| 647 |
+
elevation = np.zeros((h, w))
|
| 648 |
+
|
| 649 |
+
# ์ฌ๋ง ๊ธฐ๋ฐ๋ฉด
|
| 650 |
+
elevation[:, :] = 5.0
|
| 651 |
+
|
| 652 |
+
# Stage์ ๋ฐ๋ฅธ ์ฌ๊ตฌ ์ฑ์ฅ
|
| 653 |
+
np.random.seed(42) # ์ผ๊ด๋ ์์น
|
| 654 |
+
|
| 655 |
+
for i in range(num_dunes):
|
| 656 |
+
cy = h // 4 + i * (h // (num_dunes + 1))
|
| 657 |
+
cx = w // 2 + (i - num_dunes // 2) * (w // 5)
|
| 658 |
+
|
| 659 |
+
# ์ต์ข
๋์ด * stage
|
| 660 |
+
final_height = 15.0 + (i * 5.0)
|
| 661 |
+
dune_height = final_height * stage
|
| 662 |
+
|
| 663 |
+
# ์ฌ๊ตฌ ํฌ๊ธฐ๋ stage์ ๋น๋ก
|
| 664 |
+
dune_length = int((w // 5) * (0.3 + 0.7 * stage))
|
| 665 |
+
dune_width = int((w // 8) * (0.3 + 0.7 * stage))
|
| 666 |
+
|
| 667 |
+
if dune_length < 1 or dune_width < 1:
|
| 668 |
+
continue
|
| 669 |
+
|
| 670 |
+
for r in range(h):
|
| 671 |
+
for c in range(w):
|
| 672 |
+
dy = r - cy
|
| 673 |
+
dx = c - cx
|
| 674 |
+
|
| 675 |
+
dist = np.sqrt((dy / max(dune_length, 1)) ** 2 + (dx / max(dune_width, 1)) ** 2)
|
| 676 |
+
|
| 677 |
+
if dist < 1.0:
|
| 678 |
+
if dy < 0: # ๋ฐ๋๋ฐ์ด
|
| 679 |
+
z = dune_height * (1 - dist) * (1 - abs(dy) / max(dune_length, 1))
|
| 680 |
+
else: # ๋ฐ๋๊ทธ๋
|
| 681 |
+
z = dune_height * (1 - dist) * max(0, 1 - dy / (dune_length * 0.5))
|
| 682 |
+
|
| 683 |
+
horn_factor = 1 + 0.5 * abs(dx / max(dune_width, 1))
|
| 684 |
+
elevation[r, c] = max(elevation[r, c], 5.0 + z * horn_factor)
|
| 685 |
+
|
| 686 |
+
return elevation
|
| 687 |
+
# ============================================
|
| 688 |
+
# ํ์ฅ ์งํ (Extended Landforms)
|
| 689 |
+
# ============================================
|
| 690 |
+
|
| 691 |
+
def create_incised_meander(grid_size: int = 100, stage: float = 1.0,
|
| 692 |
+
valley_depth: float = 80.0, num_terraces: int = 3) -> np.ndarray:
|
| 693 |
+
"""
|
| 694 |
+
๊ฐ์
๊ณก๋ฅ (Incised Meander) + ํ์๋จ๊ตฌ (River Terraces)
|
| 695 |
+
|
| 696 |
+
์ต๊ธฐ ํ๊ฒฝ์์ ๊ณก๋ฅ๊ฐ ์๋ฐ์ ํ๊ณ ๋ค์ด๊ฐ๋ฉด์ ํ์ฑ
|
| 697 |
+
"""
|
| 698 |
+
h, w = grid_size, grid_size
|
| 699 |
+
elevation = np.zeros((h, w))
|
| 700 |
+
|
| 701 |
+
center_x = w // 2
|
| 702 |
+
amplitude = w * 0.25 * stage
|
| 703 |
+
wl = h / 3 # 3 bends
|
| 704 |
+
channel_width = max(3, w // 25)
|
| 705 |
+
|
| 706 |
+
# ๊ธฐ๋ฐ ๊ณ ์
|
| 707 |
+
elevation[:, :] = valley_depth
|
| 708 |
+
|
| 709 |
+
# ๊ฐ์
๊ณก๋ฅ ํ๊ธฐ
|
| 710 |
+
for r in range(h):
|
| 711 |
+
theta = 2 * np.pi * r / wl
|
| 712 |
+
meander_x = center_x + amplitude * np.sin(theta)
|
| 713 |
+
|
| 714 |
+
for c in range(w):
|
| 715 |
+
dist = abs(c - meander_x)
|
| 716 |
+
|
| 717 |
+
if dist < channel_width:
|
| 718 |
+
# ํ๋ (๊ฐ์ฅ ๊น์)
|
| 719 |
+
elevation[r, c] = 5.0
|
| 720 |
+
elif dist < channel_width * 2:
|
| 721 |
+
# ๊ธ๊ฒฝ์ฌ ์ธก๋ฒฝ
|
| 722 |
+
t = (dist - channel_width) / channel_width
|
| 723 |
+
elevation[r, c] = 5.0 + (valley_depth - 5.0) * t
|
| 724 |
+
|
| 725 |
+
# ํ์๋จ๊ตฌ (๊ณ๋จ)
|
| 726 |
+
terrace_heights = [valley_depth * (0.3 + 0.2 * i) for i in range(num_terraces)]
|
| 727 |
+
|
| 728 |
+
for terrace_h in terrace_heights:
|
| 729 |
+
for r in range(h):
|
| 730 |
+
theta = 2 * np.pi * r / wl
|
| 731 |
+
meander_x = center_x + amplitude * np.sin(theta) * 0.8
|
| 732 |
+
|
| 733 |
+
for c in range(w):
|
| 734 |
+
dist = abs(c - meander_x)
|
| 735 |
+
if channel_width * 3 < dist < channel_width * 4:
|
| 736 |
+
if elevation[r, c] > terrace_h:
|
| 737 |
+
elevation[r, c] = terrace_h
|
| 738 |
+
|
| 739 |
+
return elevation
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
def create_free_meander(grid_size: int = 100, stage: float = 1.0,
|
| 743 |
+
num_bends: int = 4) -> np.ndarray:
|
| 744 |
+
"""
|
| 745 |
+
์์ ๊ณก๋ฅ (Free Meander) + ๋ฒ๋์ (Floodplain) + ์์ฐ์ ๋ฐฉ (Natural Levee)
|
| 746 |
+
|
| 747 |
+
์ถฉ์ ํ์ผ ์๋ฅผ ์์ ๋กญ๊ฒ ์ฌํ
|
| 748 |
+
"""
|
| 749 |
+
h, w = grid_size, grid_size
|
| 750 |
+
elevation = np.zeros((h, w))
|
| 751 |
+
|
| 752 |
+
# ๋ฒ๋์ ๊ธฐ๋ฐ
|
| 753 |
+
elevation[:, :] = 10.0
|
| 754 |
+
|
| 755 |
+
center_x = w // 2
|
| 756 |
+
amplitude = w * 0.3 * stage
|
| 757 |
+
wl = h / num_bends
|
| 758 |
+
channel_width = max(3, w // 20)
|
| 759 |
+
|
| 760 |
+
for r in range(h):
|
| 761 |
+
theta = 2 * np.pi * r / wl
|
| 762 |
+
meander_x = center_x + amplitude * np.sin(theta)
|
| 763 |
+
|
| 764 |
+
for c in range(w):
|
| 765 |
+
dist = abs(c - meander_x)
|
| 766 |
+
|
| 767 |
+
if dist < channel_width:
|
| 768 |
+
# ํ๋
|
| 769 |
+
elevation[r, c] = 5.0 - (channel_width - dist) * 0.2
|
| 770 |
+
elif dist < channel_width * 2:
|
| 771 |
+
# ์์ฐ์ ๋ฐฉ (Levee) - ํ๋๋ณด๋ค ์ฝ๊ฐ ๋์
|
| 772 |
+
elevation[r, c] = 11.0
|
| 773 |
+
elif dist < channel_width * 4:
|
| 774 |
+
# ๋ฐฐํ์ต์ง (Backswamp) - ์ฝ๊ฐ ๋ฎ์
|
| 775 |
+
elevation[r, c] = 9.5
|
| 776 |
+
|
| 777 |
+
# ์ฐ๊ฐํธ (Oxbow Lake)
|
| 778 |
+
if stage > 0.7:
|
| 779 |
+
oxbow_y = h // 2
|
| 780 |
+
for dy in range(-int(wl/4), int(wl/4)):
|
| 781 |
+
r = oxbow_y + dy
|
| 782 |
+
if 0 <= r < h:
|
| 783 |
+
theta = 2 * np.pi * dy / (wl/2)
|
| 784 |
+
ox_x = center_x + amplitude * 1.3 * np.sin(theta)
|
| 785 |
+
for dc in range(-channel_width, channel_width + 1):
|
| 786 |
+
c = int(ox_x + dc)
|
| 787 |
+
if 0 <= c < w:
|
| 788 |
+
elevation[r, c] = 4.5
|
| 789 |
+
|
| 790 |
+
return elevation
|
| 791 |
+
|
| 792 |
+
|
| 793 |
+
def create_bird_foot_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 794 |
+
"""์กฐ์กฑ์ ์ผ๊ฐ์ฃผ (Bird-foot Delta) - ๋ฏธ์์ํผ๊ฐํ"""
|
| 795 |
+
h, w = grid_size, grid_size
|
| 796 |
+
elevation = np.zeros((h, w))
|
| 797 |
+
elevation[:, :] = -5.0 # ๋ฐ๋ค
|
| 798 |
+
|
| 799 |
+
apex_y = int(h * 0.15)
|
| 800 |
+
center_x = w // 2
|
| 801 |
+
|
| 802 |
+
# ๊ฐ๋๊ณ ๊ธด ๋ถ๋ฐฐ์๋ก๋ค
|
| 803 |
+
num_fingers = 5
|
| 804 |
+
max_length = int((h - apex_y) * stage)
|
| 805 |
+
|
| 806 |
+
for i in range(num_fingers):
|
| 807 |
+
angle = np.radians(-30 + 15 * i) # -30 to +30 degrees
|
| 808 |
+
|
| 809 |
+
for d in range(max_length):
|
| 810 |
+
r = apex_y + int(d * np.cos(angle))
|
| 811 |
+
c = center_x + int(d * np.sin(angle))
|
| 812 |
+
|
| 813 |
+
if 0 <= r < h and 0 <= c < w:
|
| 814 |
+
# ์ข์ finger ํํ
|
| 815 |
+
for dc in range(-3, 4):
|
| 816 |
+
for dr in range(-2, 3):
|
| 817 |
+
nr, nc = r + dr, c + dc
|
| 818 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 819 |
+
dist = np.sqrt(dr**2 + dc**2)
|
| 820 |
+
z = 8.0 * (1 - d / max_length) * (1 - dist / 4) * stage
|
| 821 |
+
elevation[nr, nc] = max(elevation[nr, nc], z)
|
| 822 |
+
|
| 823 |
+
# ํ์ฒ
|
| 824 |
+
for r in range(apex_y):
|
| 825 |
+
for dc in range(-3, 4):
|
| 826 |
+
if 0 <= center_x + dc < w:
|
| 827 |
+
elevation[r, center_x + dc] = 6.0
|
| 828 |
+
|
| 829 |
+
return elevation
|
| 830 |
+
|
| 831 |
+
|
| 832 |
+
def create_arcuate_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 833 |
+
"""ํธ์ ์ผ๊ฐ์ฃผ (Arcuate Delta) - ๋์ผ๊ฐํ"""
|
| 834 |
+
h, w = grid_size, grid_size
|
| 835 |
+
elevation = np.zeros((h, w))
|
| 836 |
+
elevation[:, :] = -5.0
|
| 837 |
+
|
| 838 |
+
apex_y = int(h * 0.2)
|
| 839 |
+
center_x = w // 2
|
| 840 |
+
|
| 841 |
+
# ๋ถ๋๋ฌ์ด ํธ ํํ
|
| 842 |
+
max_reach = int((h - apex_y) * stage)
|
| 843 |
+
|
| 844 |
+
for r in range(apex_y, apex_y + max_reach):
|
| 845 |
+
dist = r - apex_y
|
| 846 |
+
# Arc width increases with distance
|
| 847 |
+
arc_width = int(dist * 0.8)
|
| 848 |
+
|
| 849 |
+
for c in range(max(0, center_x - arc_width), min(w, center_x + arc_width)):
|
| 850 |
+
dx = abs(c - center_x)
|
| 851 |
+
radial = np.sqrt(dx**2 + dist**2)
|
| 852 |
+
|
| 853 |
+
# Smooth arc edge
|
| 854 |
+
edge_dist = arc_width - dx
|
| 855 |
+
if edge_dist > 0:
|
| 856 |
+
z = 10.0 * (1 - radial / (max_reach * 1.2)) * min(1, edge_dist / 10)
|
| 857 |
+
elevation[r, c] = max(elevation[r, c], z * stage)
|
| 858 |
+
|
| 859 |
+
# ํ์ฒ
|
| 860 |
+
for r in range(apex_y):
|
| 861 |
+
for dc in range(-4, 5):
|
| 862 |
+
if 0 <= center_x + dc < w:
|
| 863 |
+
elevation[r, center_x + dc] = 6.0
|
| 864 |
+
|
| 865 |
+
return elevation
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def create_cuspate_delta(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 869 |
+
"""์ฒจ๋์ ์ผ๊ฐ์ฃผ (Cuspate Delta) - ํฐ๋ฒ ๋ฅด๊ฐํ"""
|
| 870 |
+
h, w = grid_size, grid_size
|
| 871 |
+
elevation = np.zeros((h, w))
|
| 872 |
+
elevation[:, :] = -5.0
|
| 873 |
+
|
| 874 |
+
apex_y = int(h * 0.2)
|
| 875 |
+
center_x = w // 2
|
| 876 |
+
point_y = int(apex_y + (h - apex_y) * 0.8 * stage)
|
| 877 |
+
|
| 878 |
+
# ๋พฐ์กฑํ ์ผ๊ฐํ ํํ
|
| 879 |
+
for r in range(apex_y, point_y):
|
| 880 |
+
dist = r - apex_y
|
| 881 |
+
total_dist = point_y - apex_y
|
| 882 |
+
|
| 883 |
+
# Width narrows toward point
|
| 884 |
+
width = int((w // 3) * (1 - dist / total_dist))
|
| 885 |
+
|
| 886 |
+
for c in range(max(0, center_x - width), min(w, center_x + width)):
|
| 887 |
+
dx = abs(c - center_x)
|
| 888 |
+
z = 10.0 * (1 - dist / total_dist) * (1 - dx / max(width, 1))
|
| 889 |
+
elevation[r, c] = max(elevation[r, c], z * stage)
|
| 890 |
+
|
| 891 |
+
# ํ์ฒ
|
| 892 |
+
for r in range(apex_y):
|
| 893 |
+
for dc in range(-3, 4):
|
| 894 |
+
if 0 <= center_x + dc < w:
|
| 895 |
+
elevation[r, center_x + dc] = 6.0
|
| 896 |
+
|
| 897 |
+
return elevation
|
| 898 |
+
|
| 899 |
+
|
| 900 |
+
def create_cirque(grid_size: int = 100, stage: float = 1.0,
|
| 901 |
+
depth: float = 50.0) -> np.ndarray:
|
| 902 |
+
"""๊ถ๊ณก (Cirque) - ๋นํ ์์์ """
|
| 903 |
+
h, w = grid_size, grid_size
|
| 904 |
+
elevation = np.zeros((h, w))
|
| 905 |
+
|
| 906 |
+
# ์ฐ์
๋ฐฐ๊ฒฝ
|
| 907 |
+
elevation[:, :] = depth + 30.0
|
| 908 |
+
|
| 909 |
+
# ๊ถ๊ณก ์์น (์๋จ ์ค์)
|
| 910 |
+
cirque_y = int(h * 0.3)
|
| 911 |
+
cirque_x = w // 2
|
| 912 |
+
cirque_radius = int(w * 0.25 * (0.5 + 0.5 * stage))
|
| 913 |
+
|
| 914 |
+
for r in range(h):
|
| 915 |
+
for c in range(w):
|
| 916 |
+
dy = r - cirque_y
|
| 917 |
+
dx = c - cirque_x
|
| 918 |
+
dist = np.sqrt(dy**2 + dx**2)
|
| 919 |
+
|
| 920 |
+
if dist < cirque_radius:
|
| 921 |
+
# ๋ฐ์ํ ์ํนํ ํํ
|
| 922 |
+
# ๋ฐ๋ฅ์ ํํ, ํ๋ฒฝ(headwall)์ ๊ธ๊ฒฝ์ฌ
|
| 923 |
+
if dy < 0: # ํ๋ฒฝ
|
| 924 |
+
z = depth * (1 - dist / cirque_radius) * 0.3
|
| 925 |
+
else: # ๋ฐ๋ฅ
|
| 926 |
+
z = depth * 0.1
|
| 927 |
+
elevation[r, c] = z
|
| 928 |
+
|
| 929 |
+
return elevation
|
| 930 |
+
|
| 931 |
+
|
| 932 |
+
def create_horn(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 933 |
+
"""ํธ๋ฅธ (Horn) - ํผ๋ผ๋ฏธ๋ํ ๋ด์ฐ๋ฆฌ"""
|
| 934 |
+
h, w = grid_size, grid_size
|
| 935 |
+
elevation = np.zeros((h, w))
|
| 936 |
+
|
| 937 |
+
center = (h // 2, w // 2)
|
| 938 |
+
peak_height = 100.0 * stage
|
| 939 |
+
|
| 940 |
+
# 4๋ฐฉํฅ ๊ถ๊ณก์ ์ํ ํธ๋ฅธ ํ์ฑ
|
| 941 |
+
num_cirques = 4
|
| 942 |
+
cirque_radius = int(w * 0.3)
|
| 943 |
+
|
| 944 |
+
for r in range(h):
|
| 945 |
+
for c in range(w):
|
| 946 |
+
dy = r - center[0]
|
| 947 |
+
dx = c - center[1]
|
| 948 |
+
dist = np.sqrt(dy**2 + dx**2)
|
| 949 |
+
|
| 950 |
+
# ๊ธฐ๋ณธ ํผ๋ผ๋ฏธ๋ ํํ
|
| 951 |
+
elevation[r, c] = peak_height * max(0, 1 - dist / (w // 2))
|
| 952 |
+
|
| 953 |
+
# 4๋ฐฉํฅ ๊ถ๊ณก ํ๊ธฐ
|
| 954 |
+
for i in range(num_cirques):
|
| 955 |
+
angle = i * np.pi / 2
|
| 956 |
+
cx = center[1] + int(cirque_radius * 0.8 * np.cos(angle))
|
| 957 |
+
cy = center[0] + int(cirque_radius * 0.8 * np.sin(angle))
|
| 958 |
+
|
| 959 |
+
cdist = np.sqrt((r - cy)**2 + (c - cx)**2)
|
| 960 |
+
if cdist < cirque_radius * 0.6:
|
| 961 |
+
# ๊ถ๊ณก ํ๊ธฐ
|
| 962 |
+
elevation[r, c] = min(elevation[r, c],
|
| 963 |
+
20.0 + 30.0 * (cdist / (cirque_radius * 0.6)))
|
| 964 |
+
|
| 965 |
+
return elevation
|
| 966 |
+
|
| 967 |
+
|
| 968 |
+
def create_shield_volcano(grid_size: int = 100, stage: float = 1.0,
|
| 969 |
+
max_height: float = 40.0) -> np.ndarray:
|
| 970 |
+
"""์์ํ์ฐ (Shield Volcano) - ์๋งํ ๊ฒฝ์ฌ"""
|
| 971 |
+
h, w = grid_size, grid_size
|
| 972 |
+
elevation = np.zeros((h, w))
|
| 973 |
+
|
| 974 |
+
center = (h // 2, w // 2)
|
| 975 |
+
radius = w // 2
|
| 976 |
+
|
| 977 |
+
for r in range(h):
|
| 978 |
+
for c in range(w):
|
| 979 |
+
dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
|
| 980 |
+
|
| 981 |
+
if dist < radius:
|
| 982 |
+
# ์๋งํ ํฌ๋ฌผ์ ํํ (๊ฒฝ์ฌ 5-10๋)
|
| 983 |
+
elevation[r, c] = max_height * (1 - (dist / radius)**2) * stage
|
| 984 |
+
|
| 985 |
+
# ์ ์๋ถ ํ๊ตฌ
|
| 986 |
+
crater_radius = int(radius * 0.1)
|
| 987 |
+
for r in range(h):
|
| 988 |
+
for c in range(w):
|
| 989 |
+
dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
|
| 990 |
+
if dist < crater_radius:
|
| 991 |
+
elevation[r, c] = max_height * 0.9 * stage
|
| 992 |
+
|
| 993 |
+
return elevation
|
| 994 |
+
|
| 995 |
+
|
| 996 |
+
def create_stratovolcano(grid_size: int = 100, stage: float = 1.0,
|
| 997 |
+
max_height: float = 80.0) -> np.ndarray:
|
| 998 |
+
"""์ฑ์ธตํ์ฐ (Stratovolcano) - ๊ธํ ์๋ฟํ"""
|
| 999 |
+
h, w = grid_size, grid_size
|
| 1000 |
+
elevation = np.zeros((h, w))
|
| 1001 |
+
|
| 1002 |
+
center = (h // 2, w // 2)
|
| 1003 |
+
radius = int(w * 0.4)
|
| 1004 |
+
|
| 1005 |
+
for r in range(h):
|
| 1006 |
+
for c in range(w):
|
| 1007 |
+
dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
|
| 1008 |
+
|
| 1009 |
+
if dist < radius:
|
| 1010 |
+
# ๊ธํ ์๋ฟ (๊ฒฝ์ฌ 25-35๋)
|
| 1011 |
+
elevation[r, c] = max_height * (1 - dist / radius) * stage
|
| 1012 |
+
|
| 1013 |
+
# ์ ์๋ถ ํ๊ตฌ
|
| 1014 |
+
crater_radius = int(radius * 0.08)
|
| 1015 |
+
crater_depth = 10.0
|
| 1016 |
+
for r in range(h):
|
| 1017 |
+
for c in range(w):
|
| 1018 |
+
dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
|
| 1019 |
+
if dist < crater_radius:
|
| 1020 |
+
elevation[r, c] = max_height * stage - crater_depth
|
| 1021 |
+
|
| 1022 |
+
return elevation
|
| 1023 |
+
|
| 1024 |
+
|
| 1025 |
+
def create_caldera(grid_size: int = 100, stage: float = 1.0,
|
| 1026 |
+
rim_height: float = 50.0) -> np.ndarray:
|
| 1027 |
+
"""์นผ๋ฐ๋ผ (Caldera) - ํ๊ตฌ ํจ๋ชฐ"""
|
| 1028 |
+
h, w = grid_size, grid_size
|
| 1029 |
+
elevation = np.zeros((h, w))
|
| 1030 |
+
|
| 1031 |
+
center = (h // 2, w // 2)
|
| 1032 |
+
outer_radius = int(w * 0.45)
|
| 1033 |
+
caldera_radius = int(w * 0.3)
|
| 1034 |
+
|
| 1035 |
+
for r in range(h):
|
| 1036 |
+
for c in range(w):
|
| 1037 |
+
dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
|
| 1038 |
+
|
| 1039 |
+
if dist < outer_radius:
|
| 1040 |
+
if dist < caldera_radius:
|
| 1041 |
+
# ์นผ๋ฐ๋ผ ๋ฐ๋ฅ (ํํ, ํธ์ ๊ฐ๋ฅ)
|
| 1042 |
+
elevation[r, c] = 5.0
|
| 1043 |
+
else:
|
| 1044 |
+
# ์นผ๋ฐ๋ผ ๋ฒฝ (๊ธ๊ฒฝ์ฌ)
|
| 1045 |
+
t = (dist - caldera_radius) / (outer_radius - caldera_radius)
|
| 1046 |
+
elevation[r, c] = 5.0 + rim_height * (1 - t) * stage
|
| 1047 |
+
|
| 1048 |
+
return elevation
|
| 1049 |
+
|
| 1050 |
+
|
| 1051 |
+
def create_mesa_butte(grid_size: int = 100, stage: float = 1.0,
|
| 1052 |
+
num_mesas: int = 2) -> np.ndarray:
|
| 1053 |
+
"""๋ฉ์ฌ/๋ทฐํธ (Mesa/Butte) - ํ์์ง"""
|
| 1054 |
+
h, w = grid_size, grid_size
|
| 1055 |
+
elevation = np.zeros((h, w))
|
| 1056 |
+
|
| 1057 |
+
# ์ฌ๋ง ๊ธฐ๋ฐ
|
| 1058 |
+
elevation[:, :] = 5.0
|
| 1059 |
+
|
| 1060 |
+
mesa_height = 40.0 * stage
|
| 1061 |
+
|
| 1062 |
+
# ๋ฉ์ฌ ๋ฐฐ์น
|
| 1063 |
+
positions = [(h//3, w//3), (h//2, 2*w//3)]
|
| 1064 |
+
sizes = [(w//4, w//5), (w//6, w//6)] # ๋ฉ์ฌ, ๋ทฐํธ
|
| 1065 |
+
|
| 1066 |
+
for i, ((my, mx), (sw, sh)) in enumerate(zip(positions[:num_mesas], sizes[:num_mesas])):
|
| 1067 |
+
for r in range(h):
|
| 1068 |
+
for c in range(w):
|
| 1069 |
+
if abs(r - my) < sh and abs(c - mx) < sw:
|
| 1070 |
+
# ํํํ ์ ์๋ถ
|
| 1071 |
+
elevation[r, c] = mesa_height
|
| 1072 |
+
elif abs(r - my) < sh + 3 and abs(c - mx) < sw + 3:
|
| 1073 |
+
# ๊ธ๊ฒฝ์ฌ ์ธก๋ฒฝ
|
| 1074 |
+
edge_dist = min(abs(abs(r - my) - sh), abs(abs(c - mx) - sw))
|
| 1075 |
+
elevation[r, c] = mesa_height * (1 - edge_dist / 3)
|
| 1076 |
+
|
| 1077 |
+
return elevation
|
| 1078 |
+
|
| 1079 |
+
|
| 1080 |
+
def create_spit_lagoon(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 1081 |
+
"""์ฌ์ทจ (Spit) + ์ํธ (Lagoon)"""
|
| 1082 |
+
h, w = grid_size, grid_size
|
| 1083 |
+
elevation = np.zeros((h, w))
|
| 1084 |
+
|
| 1085 |
+
# ๋ฐ๋ค (์ค๋ฅธ์ชฝ)
|
| 1086 |
+
sea_line = int(w * 0.6)
|
| 1087 |
+
elevation[:, sea_line:] = -5.0
|
| 1088 |
+
|
| 1089 |
+
# ์ก์ง (์ผ์ชฝ)
|
| 1090 |
+
elevation[:, :sea_line] = 10.0
|
| 1091 |
+
|
| 1092 |
+
# ์ฌ์ทจ (์ฐ์๋ฅ ๋ฐฉํฅ์ผ๋ก ๊ธธ๊ฒ)
|
| 1093 |
+
spit_start = int(h * 0.3)
|
| 1094 |
+
spit_length = int(h * 0.5 * stage)
|
| 1095 |
+
spit_width = 5
|
| 1096 |
+
|
| 1097 |
+
for r in range(spit_start, min(h, spit_start + spit_length)):
|
| 1098 |
+
# ์ฌ์ทจ๊ฐ ๋ฐ๋ค ์ชฝ์ผ๋ก ํ์ด์ง
|
| 1099 |
+
curve = int((r - spit_start) / spit_length * (w * 0.15))
|
| 1100 |
+
spit_x = sea_line + curve
|
| 1101 |
+
|
| 1102 |
+
for dc in range(-spit_width, spit_width + 1):
|
| 1103 |
+
c = spit_x + dc
|
| 1104 |
+
if 0 <= c < w:
|
| 1105 |
+
elevation[r, c] = 3.0 * (1 - abs(dc) / spit_width)
|
| 1106 |
+
|
| 1107 |
+
# ์ํธ (์ฌ์ทจ ์์ชฝ)
|
| 1108 |
+
if stage > 0.5:
|
| 1109 |
+
for r in range(spit_start, spit_start + int(spit_length * 0.8)):
|
| 1110 |
+
curve = int((r - spit_start) / spit_length * (w * 0.1))
|
| 1111 |
+
for c in range(sea_line - 5, sea_line + curve):
|
| 1112 |
+
if 0 <= c < w:
|
| 1113 |
+
if elevation[r, c] < 3.0:
|
| 1114 |
+
elevation[r, c] = -2.0 # ์์ ์ํธ
|
| 1115 |
+
|
| 1116 |
+
return elevation
|
| 1117 |
+
|
| 1118 |
+
|
| 1119 |
+
# ============================================
|
| 1120 |
+
# ์ถ๊ฐ ์งํ (Additional Landforms)
|
| 1121 |
+
# ============================================
|
| 1122 |
+
|
| 1123 |
+
def create_fjord(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 1124 |
+
"""ํผ์ค๋ฅด๋ (Fjord) - ๋นํ๊ฐ ํ๋ธ U์๊ณก์ ๋ฐ๋ค ์ ์
"""
|
| 1125 |
+
h, w = grid_size, grid_size
|
| 1126 |
+
elevation = np.zeros((h, w))
|
| 1127 |
+
|
| 1128 |
+
# ์ฐ์
์งํ
|
| 1129 |
+
elevation[:, :] = 80.0
|
| 1130 |
+
|
| 1131 |
+
center = w // 2
|
| 1132 |
+
valley_width = int(w * 0.2)
|
| 1133 |
+
|
| 1134 |
+
# U์๊ณก + ๋ฐ๋ค ์ ์
|
| 1135 |
+
sea_line = int(h * 0.7)
|
| 1136 |
+
|
| 1137 |
+
for r in range(h):
|
| 1138 |
+
for c in range(w):
|
| 1139 |
+
dx = abs(c - center)
|
| 1140 |
+
|
| 1141 |
+
if dx < valley_width:
|
| 1142 |
+
# U์ ๋ฐ๋ฅ
|
| 1143 |
+
if r < sea_line:
|
| 1144 |
+
elevation[r, c] = 10.0 # ์ก์ง ๋ฐ๋ฅ
|
| 1145 |
+
else:
|
| 1146 |
+
elevation[r, c] = -30.0 * stage # ๋ฐ๋ค
|
| 1147 |
+
elif dx < valley_width + 10:
|
| 1148 |
+
# U์ ์ธก๋ฒฝ
|
| 1149 |
+
t = (dx - valley_width) / 10
|
| 1150 |
+
base = -30.0 if r >= sea_line else 10.0
|
| 1151 |
+
elevation[r, c] = base + 70.0 * t
|
| 1152 |
+
|
| 1153 |
+
return elevation
|
| 1154 |
+
|
| 1155 |
+
|
| 1156 |
+
def create_drumlin(grid_size: int = 100, stage: float = 1.0,
|
| 1157 |
+
num_drumlins: int = 5) -> np.ndarray:
|
| 1158 |
+
"""๋๋ผ๋ฆฐ (Drumlin) - ๋นํ ๋ฐฉํฅ ํ์ํ ์ธ๋"""
|
| 1159 |
+
h, w = grid_size, grid_size
|
| 1160 |
+
elevation = np.zeros((h, w))
|
| 1161 |
+
|
| 1162 |
+
elevation[:, :] = 5.0 # ๋นํ ํด์ ํ์
|
| 1163 |
+
|
| 1164 |
+
for i in range(num_drumlins):
|
| 1165 |
+
# ๋๋ผ๋ฆฐ ์์น (๋นํ ํ๋ฆ ๋ฐฉํฅ์ผ๋ก ์ ๋ ฌ)
|
| 1166 |
+
cy = int(h * 0.2 + (i % 3) * h * 0.25)
|
| 1167 |
+
cx = int(w * 0.2 + (i // 3) * w * 0.3)
|
| 1168 |
+
|
| 1169 |
+
# ํ์ํ (๋นํ ๋ฐฉํฅ์ผ๋ก ๊ธธ์ญ)
|
| 1170 |
+
length = int(w * 0.15 * stage)
|
| 1171 |
+
width = int(w * 0.06 * stage)
|
| 1172 |
+
height = 15.0 * stage
|
| 1173 |
+
|
| 1174 |
+
for r in range(h):
|
| 1175 |
+
for c in range(w):
|
| 1176 |
+
dy = (r - cy) / max(length, 1)
|
| 1177 |
+
dx = (c - cx) / max(width, 1)
|
| 1178 |
+
dist = np.sqrt(dy**2 + dx**2)
|
| 1179 |
+
|
| 1180 |
+
if dist < 1.0:
|
| 1181 |
+
# ๋พฐ์กฑํ ๋นํ ์๋ฅ, ์๋งํ ํ๋ฅ
|
| 1182 |
+
asymmetry = 1.0 if dy < 0 else 0.7
|
| 1183 |
+
z = height * (1 - dist) * asymmetry
|
| 1184 |
+
elevation[r, c] = max(elevation[r, c], 5.0 + z)
|
| 1185 |
+
|
| 1186 |
+
return elevation
|
| 1187 |
+
|
| 1188 |
+
|
| 1189 |
+
def create_moraine(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 1190 |
+
"""๋นํด์ (Moraine) - ์ธกํด์, ์ข
ํด์"""
|
| 1191 |
+
h, w = grid_size, grid_size
|
| 1192 |
+
elevation = np.zeros((h, w))
|
| 1193 |
+
|
| 1194 |
+
# ๋นํ ๊ณ๏ฟฝ๏ฟฝ๏ฟฝ ๋ฐฐ๊ฒฝ
|
| 1195 |
+
elevation[:, :] = 20.0
|
| 1196 |
+
center = w // 2
|
| 1197 |
+
|
| 1198 |
+
# ๋นํ ๋ณธ์ฒด (๊ณผ๊ฑฐ)
|
| 1199 |
+
glacier_width = int(w * 0.3)
|
| 1200 |
+
for r in range(h):
|
| 1201 |
+
for c in range(w):
|
| 1202 |
+
if abs(c - center) < glacier_width:
|
| 1203 |
+
elevation[r, c] = 5.0 # ๋นํ ๋ฐ๋ฅ
|
| 1204 |
+
|
| 1205 |
+
# ์ธกํด์ (Lateral Moraine)
|
| 1206 |
+
moraine_height = 15.0 * stage
|
| 1207 |
+
for r in range(h):
|
| 1208 |
+
for side in [-1, 1]:
|
| 1209 |
+
moraine_c = center + side * glacier_width
|
| 1210 |
+
for dc in range(-5, 6):
|
| 1211 |
+
c = moraine_c + dc
|
| 1212 |
+
if 0 <= c < w:
|
| 1213 |
+
z = moraine_height * (1 - abs(dc) / 6)
|
| 1214 |
+
elevation[r, c] = max(elevation[r, c], z)
|
| 1215 |
+
|
| 1216 |
+
# ์ข
ํด์ (Terminal Moraine)
|
| 1217 |
+
terminal_r = int(h * 0.8)
|
| 1218 |
+
for r in range(terminal_r - 5, min(h, terminal_r + 5)):
|
| 1219 |
+
for c in range(center - glacier_width, center + glacier_width):
|
| 1220 |
+
if 0 <= c < w:
|
| 1221 |
+
dr = abs(r - terminal_r)
|
| 1222 |
+
z = moraine_height * 1.2 * (1 - dr / 6)
|
| 1223 |
+
elevation[r, c] = max(elevation[r, c], z)
|
| 1224 |
+
|
| 1225 |
+
return elevation
|
| 1226 |
+
|
| 1227 |
+
|
| 1228 |
+
def create_braided_river(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 1229 |
+
"""๋ง์ํ์ฒ (Braided River) - ์ฌ๋ฌ ์๋ก"""
|
| 1230 |
+
h, w = grid_size, grid_size
|
| 1231 |
+
elevation = np.zeros((h, w))
|
| 1232 |
+
|
| 1233 |
+
# ๋์ ํ์
|
| 1234 |
+
elevation[:, :] = 10.0
|
| 1235 |
+
|
| 1236 |
+
center = w // 2
|
| 1237 |
+
river_width = int(w * 0.5)
|
| 1238 |
+
|
| 1239 |
+
# ๋๊ณ ์์ ํ์
|
| 1240 |
+
for c in range(center - river_width // 2, center + river_width // 2):
|
| 1241 |
+
if 0 <= c < w:
|
| 1242 |
+
elevation[:, c] = 5.0
|
| 1243 |
+
|
| 1244 |
+
# ์ฌ๋ฌ ์๋ก์ ์ฌ์ฃผ (๋ชจ๋์ฌ)
|
| 1245 |
+
num_channels = int(3 + 4 * stage)
|
| 1246 |
+
np.random.seed(42)
|
| 1247 |
+
|
| 1248 |
+
for r in range(h):
|
| 1249 |
+
# ํ์ฌ ํ์ ์๋ก ์์น
|
| 1250 |
+
for i in range(num_channels):
|
| 1251 |
+
channel_x = center - river_width // 3 + int((i / num_channels) * river_width * 0.7)
|
| 1252 |
+
channel_x += int(10 * np.sin(r / 10 + i)) # ์ฝ๊ฐ ์ฌํ
|
| 1253 |
+
|
| 1254 |
+
for dc in range(-2, 3):
|
| 1255 |
+
c = channel_x + dc
|
| 1256 |
+
if 0 <= c < w:
|
| 1257 |
+
elevation[r, c] = 3.0
|
| 1258 |
+
|
| 1259 |
+
# ์ฌ์ฃผ (๋ชจ๋์ฌ)
|
| 1260 |
+
for i in range(int(5 * stage)):
|
| 1261 |
+
bar_r = int(h * 0.2 + i * h * 0.15)
|
| 1262 |
+
bar_c = center + int((i - 2) * w * 0.1)
|
| 1263 |
+
|
| 1264 |
+
for dr in range(-5, 6):
|
| 1265 |
+
for dc in range(-8, 9):
|
| 1266 |
+
r, c = bar_r + dr, bar_c + dc
|
| 1267 |
+
if 0 <= r < h and 0 <= c < w:
|
| 1268 |
+
dist = np.sqrt((dr/5)**2 + (dc/8)**2)
|
| 1269 |
+
if dist < 1.0:
|
| 1270 |
+
elevation[r, c] = max(elevation[r, c], 6.0 * (1 - dist))
|
| 1271 |
+
|
| 1272 |
+
return elevation
|
| 1273 |
+
|
| 1274 |
+
|
| 1275 |
+
def create_waterfall(grid_size: int = 100, stage: float = 1.0,
|
| 1276 |
+
drop_height: float = 50.0) -> np.ndarray:
|
| 1277 |
+
"""ํญํฌ (Waterfall) - ์ฐจ๋ณ์นจ์"""
|
| 1278 |
+
h, w = grid_size, grid_size
|
| 1279 |
+
elevation = np.zeros((h, w))
|
| 1280 |
+
|
| 1281 |
+
center = w // 2
|
| 1282 |
+
fall_r = int(h * 0.4)
|
| 1283 |
+
|
| 1284 |
+
# ์๋ฅ (๋์ ๊ฒฝ์์ธต)
|
| 1285 |
+
for r in range(fall_r):
|
| 1286 |
+
for c in range(w):
|
| 1287 |
+
elevation[r, c] = drop_height + 20.0 + (fall_r - r) * 0.5
|
| 1288 |
+
|
| 1289 |
+
# ํญํฌ (๊ธ๊ฒฝ์ฌ)
|
| 1290 |
+
for r in range(fall_r, fall_r + 5):
|
| 1291 |
+
for c in range(w):
|
| 1292 |
+
t = (r - fall_r) / 5
|
| 1293 |
+
elevation[r, c] = drop_height * (1 - t) + 20.0
|
| 1294 |
+
|
| 1295 |
+
# ํ๋ฅ
|
| 1296 |
+
for r in range(fall_r + 5, h):
|
| 1297 |
+
for c in range(w):
|
| 1298 |
+
elevation[r, c] = 20.0 - (r - fall_r - 5) * 0.2
|
| 1299 |
+
|
| 1300 |
+
# ํ์ฒ ์๋ก
|
| 1301 |
+
for r in range(h):
|
| 1302 |
+
for dc in range(-4, 5):
|
| 1303 |
+
c = center + dc
|
| 1304 |
+
if 0 <= c < w:
|
| 1305 |
+
elevation[r, c] -= 5.0
|
| 1306 |
+
|
| 1307 |
+
# ํ๋ฐ์งํ (ํญํธ)
|
| 1308 |
+
pool_r = fall_r + 5
|
| 1309 |
+
for dr in range(-5, 6):
|
| 1310 |
+
for dc in range(-6, 7):
|
| 1311 |
+
r, c = pool_r + dr, center + dc
|
| 1312 |
+
if 0 <= r < h and 0 <= c < w:
|
| 1313 |
+
dist = np.sqrt(dr**2 + dc**2)
|
| 1314 |
+
if dist < 6:
|
| 1315 |
+
elevation[r, c] = min(elevation[r, c], 10.0)
|
| 1316 |
+
|
| 1317 |
+
return elevation
|
| 1318 |
+
|
| 1319 |
+
|
| 1320 |
+
def create_karst_doline(grid_size: int = 100, stage: float = 1.0,
|
| 1321 |
+
num_dolines: int = 5) -> np.ndarray:
|
| 1322 |
+
"""๋๋ฆฌ๋ค (Doline/Sinkhole) - ์นด๋ฅด์คํธ ์งํ"""
|
| 1323 |
+
h, w = grid_size, grid_size
|
| 1324 |
+
elevation = np.zeros((h, w))
|
| 1325 |
+
|
| 1326 |
+
# ์ํ์ ๋์ง
|
| 1327 |
+
elevation[:, :] = 30.0
|
| 1328 |
+
|
| 1329 |
+
np.random.seed(42)
|
| 1330 |
+
for i in range(num_dolines):
|
| 1331 |
+
dy = int(h * 0.2 + np.random.rand() * h * 0.6)
|
| 1332 |
+
dx = int(w * 0.2 + np.random.rand() * w * 0.6)
|
| 1333 |
+
radius = int(w * 0.08 * (0.5 + np.random.rand() * 0.5))
|
| 1334 |
+
depth = 20.0 * stage * (0.5 + np.random.rand() * 0.5)
|
| 1335 |
+
|
| 1336 |
+
for r in range(h):
|
| 1337 |
+
for c in range(w):
|
| 1338 |
+
dist = np.sqrt((r - dy)**2 + (c - dx)**2)
|
| 1339 |
+
if dist < radius:
|
| 1340 |
+
z = depth * (1 - (dist / radius) ** 2)
|
| 1341 |
+
elevation[r, c] = max(0, elevation[r, c] - z)
|
| 1342 |
+
|
| 1343 |
+
return elevation
|
| 1344 |
+
|
| 1345 |
+
|
| 1346 |
+
def create_ria_coast(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 1347 |
+
"""๋ฆฌ์์ค์ ํด์ (Ria Coast) - ์นจ์๋ ํ๊ณก"""
|
| 1348 |
+
h, w = grid_size, grid_size
|
| 1349 |
+
elevation = np.zeros((h, w))
|
| 1350 |
+
|
| 1351 |
+
# ์ฐ์ง ๋ฐฐ๊ฒฝ
|
| 1352 |
+
elevation[:, :] = 40.0
|
| 1353 |
+
|
| 1354 |
+
# ์ฌ๋ฌ ๊ฐ์ V์๊ณก (์นจ์๋จ)
|
| 1355 |
+
num_valleys = 4
|
| 1356 |
+
sea_level = int(h * 0.6)
|
| 1357 |
+
|
| 1358 |
+
for i in range(num_valleys):
|
| 1359 |
+
valley_x = int(w * 0.15 + i * w * 0.2)
|
| 1360 |
+
|
| 1361 |
+
for r in range(h):
|
| 1362 |
+
for c in range(w):
|
| 1363 |
+
dx = abs(c - valley_x)
|
| 1364 |
+
|
| 1365 |
+
if dx < 8:
|
| 1366 |
+
# V์๊ณก
|
| 1367 |
+
depth = 30.0 * (1 - dx / 8)
|
| 1368 |
+
elevation[r, c] -= depth
|
| 1369 |
+
|
| 1370 |
+
# ํด์๋ฉด ์ดํ = ๋ฐ๋ค
|
| 1371 |
+
for r in range(sea_level, h):
|
| 1372 |
+
for c in range(w):
|
| 1373 |
+
if elevation[r, c] < 10:
|
| 1374 |
+
elevation[r, c] = -5.0 * stage # ์นจ์
|
| 1375 |
+
|
| 1376 |
+
return elevation
|
| 1377 |
+
|
| 1378 |
+
|
| 1379 |
+
def create_tombolo(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 1380 |
+
"""์ก๊ณ์ฌ์ฃผ (Tombolo) - ์ก์ง์ ์ฌ์ ์ฐ๊ฒฐ"""
|
| 1381 |
+
h, w = grid_size, grid_size
|
| 1382 |
+
elevation = np.zeros((h, w))
|
| 1383 |
+
|
| 1384 |
+
# ๋ฐ๋ค
|
| 1385 |
+
elevation[:, :] = -5.0
|
| 1386 |
+
|
| 1387 |
+
# ๋ณธํ (์ผ์ชฝ)
|
| 1388 |
+
for c in range(int(w * 0.3)):
|
| 1389 |
+
elevation[:, c] = 15.0
|
| 1390 |
+
|
| 1391 |
+
# ์ฌ (์ค๋ฅธ์ชฝ)
|
| 1392 |
+
island_cy = h // 2
|
| 1393 |
+
island_cx = int(w * 0.75)
|
| 1394 |
+
island_radius = int(w * 0.12)
|
| 1395 |
+
|
| 1396 |
+
for r in range(h):
|
| 1397 |
+
for c in range(w):
|
| 1398 |
+
dist = np.sqrt((r - island_cy)**2 + (c - island_cx)**2)
|
| 1399 |
+
if dist < island_radius:
|
| 1400 |
+
elevation[r, c] = 20.0 * (1 - dist / island_radius / 1.5)
|
| 1401 |
+
|
| 1402 |
+
# ์ก๊ณ์ฌ์ฃผ (์ฐ๊ฒฐ)
|
| 1403 |
+
tombolo_start = int(w * 0.3)
|
| 1404 |
+
tombolo_end = island_cx - island_radius
|
| 1405 |
+
|
| 1406 |
+
for c in range(tombolo_start, tombolo_end):
|
| 1407 |
+
t = (c - tombolo_start) / (tombolo_end - tombolo_start)
|
| 1408 |
+
width = int(5 * (1 - abs(t - 0.5) * 2) * stage)
|
| 1409 |
+
|
| 1410 |
+
for dr in range(-width, width + 1):
|
| 1411 |
+
r = island_cy + dr
|
| 1412 |
+
if 0 <= r < h:
|
| 1413 |
+
elevation[r, c] = 3.0 * (1 - abs(dr) / max(width, 1))
|
| 1414 |
+
|
| 1415 |
+
return elevation
|
| 1416 |
+
|
| 1417 |
+
|
| 1418 |
+
def create_sea_arch(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 1419 |
+
"""ํด์์์น (Sea Arch) - ๋๊ตด์ด ๊ดํต"""
|
| 1420 |
+
h, w = grid_size, grid_size
|
| 1421 |
+
elevation = np.zeros((h, w))
|
| 1422 |
+
|
| 1423 |
+
# ๋ฐ๋ค (์๋)
|
| 1424 |
+
sea_line = int(h * 0.5)
|
| 1425 |
+
elevation[sea_line:, :] = -5.0
|
| 1426 |
+
|
| 1427 |
+
# ์ก์ง ์ ๋ฒฝ
|
| 1428 |
+
cliff_height = 30.0
|
| 1429 |
+
for r in range(sea_line):
|
| 1430 |
+
elevation[r, :] = cliff_height
|
| 1431 |
+
|
| 1432 |
+
# ๋์ถ๋ถ (๊ณถ)
|
| 1433 |
+
headland_width = int(w * 0.3)
|
| 1434 |
+
headland_cx = w // 2
|
| 1435 |
+
headland_length = int(h * 0.3)
|
| 1436 |
+
|
| 1437 |
+
for r in range(sea_line, sea_line + headland_length):
|
| 1438 |
+
for c in range(headland_cx - headland_width // 2, headland_cx + headland_width // 2):
|
| 1439 |
+
if 0 <= c < w:
|
| 1440 |
+
elevation[r, c] = cliff_height * (1 - (r - sea_line) / headland_length * 0.3)
|
| 1441 |
+
|
| 1442 |
+
# ์์น (๊ดํต)
|
| 1443 |
+
arch_r = sea_line + int(headland_length * 0.5)
|
| 1444 |
+
arch_width = int(headland_width * 0.4 * stage)
|
| 1445 |
+
|
| 1446 |
+
for dr in range(-5, 6):
|
| 1447 |
+
for dc in range(-arch_width // 2, arch_width // 2 + 1):
|
| 1448 |
+
r, c = arch_r + dr, headland_cx + dc
|
| 1449 |
+
if 0 <= r < h and 0 <= c < w:
|
| 1450 |
+
if abs(dr) < 3: # ์์น ๋์ด
|
| 1451 |
+
elevation[r, c] = -5.0 # ๊ดํต
|
| 1452 |
+
|
| 1453 |
+
return elevation
|
| 1454 |
+
|
| 1455 |
+
|
| 1456 |
+
def create_crater_lake(grid_size: int = 100, stage: float = 1.0,
|
| 1457 |
+
rim_height: float = 50.0) -> np.ndarray:
|
| 1458 |
+
"""ํ๊ตฌํธ (Crater Lake) - ํ๊ตฌ์ ๋ฌผ์ด ๊ณ ์"""
|
| 1459 |
+
h, w = grid_size, grid_size
|
| 1460 |
+
elevation = np.zeros((h, w))
|
| 1461 |
+
|
| 1462 |
+
center = (h // 2, w // 2)
|
| 1463 |
+
outer_radius = int(w * 0.4)
|
| 1464 |
+
crater_radius = int(w * 0.25)
|
| 1465 |
+
|
| 1466 |
+
for r in range(h):
|
| 1467 |
+
for c in range(w):
|
| 1468 |
+
dist = np.sqrt((r - center[0])**2 + (c - center[1])**2)
|
| 1469 |
+
|
| 1470 |
+
if dist > outer_radius:
|
| 1471 |
+
elevation[r, c] = 0
|
| 1472 |
+
elif dist > crater_radius:
|
| 1473 |
+
# ์ธ๋ฅ์ฐ
|
| 1474 |
+
t = (dist - crater_radius) / (outer_radius - crater_radius)
|
| 1475 |
+
elevation[r, c] = rim_height * (1 - t) * stage
|
| 1476 |
+
else:
|
| 1477 |
+
# ํธ์ (๋ฌผ)
|
| 1478 |
+
elevation[r, c] = -10.0 * stage
|
| 1479 |
+
|
| 1480 |
+
return elevation
|
| 1481 |
+
|
| 1482 |
+
|
| 1483 |
+
def create_lava_plateau(grid_size: int = 100, stage: float = 1.0) -> np.ndarray:
|
| 1484 |
+
"""์ฉ์๋์ง (Lava Plateau) - ํํํ ์ฉ์"""
|
| 1485 |
+
h, w = grid_size, grid_size
|
| 1486 |
+
elevation = np.zeros((h, w))
|
| 1487 |
+
|
| 1488 |
+
# ๊ธฐ๋ฐ
|
| 1489 |
+
elevation[:, :] = 5.0
|
| 1490 |
+
|
| 1491 |
+
# ์ฉ์๋์ง ์์ญ
|
| 1492 |
+
plateau_height = 30.0 * stage
|
| 1493 |
+
margin = int(w * 0.15)
|
| 1494 |
+
|
| 1495 |
+
for r in range(margin, h - margin):
|
| 1496 |
+
for c in range(margin, w - margin):
|
| 1497 |
+
# ๊ฑฐ์ ํํํ์ง๋ง ์ฝ๊ฐ์ ๊ตด๊ณก
|
| 1498 |
+
noise = np.sin(r * 0.2) * np.cos(c * 0.2) * 2.0
|
| 1499 |
+
elevation[r, c] = plateau_height + noise
|
| 1500 |
+
|
| 1501 |
+
# ๊ฐ์ฅ์๋ฆฌ ์ ๋ฒฝ
|
| 1502 |
+
for r in range(h):
|
| 1503 |
+
for c in range(w):
|
| 1504 |
+
edge_dist = min(r, h - r - 1, c, w - c - 1)
|
| 1505 |
+
if edge_dist < margin:
|
| 1506 |
+
t = edge_dist / margin
|
| 1507 |
+
elevation[r, c] = 5.0 + (elevation[r, c] - 5.0) * t
|
| 1508 |
+
|
| 1509 |
+
return elevation
|
| 1510 |
+
|
| 1511 |
+
|
| 1512 |
+
def create_coastal_dune(grid_size: int = 100, stage: float = 1.0,
|
| 1513 |
+
num_dunes: int = 3) -> np.ndarray:
|
| 1514 |
+
"""ํด์์ฌ๊ตฌ (Coastal Dune) - ํด์๊ฐ ๋ชจ๋ ์ธ๋"""
|
| 1515 |
+
h, w = grid_size, grid_size
|
| 1516 |
+
elevation = np.zeros((h, w))
|
| 1517 |
+
|
| 1518 |
+
# ๋ฐ๋ค (์๋)
|
| 1519 |
+
beach_line = int(h * 0.7)
|
| 1520 |
+
elevation[beach_line:, :] = -3.0
|
| 1521 |
+
|
| 1522 |
+
# ํด๋น (ํด๋ณ)
|
| 1523 |
+
for r in range(beach_line - 5, beach_line):
|
| 1524 |
+
elevation[r, :] = 2.0
|
| 1525 |
+
|
| 1526 |
+
# ํด์์ฌ๊ตฌ (ํด๋ณ ๋ค)
|
| 1527 |
+
dune_zone_start = int(h * 0.3)
|
| 1528 |
+
dune_zone_end = beach_line - 5
|
| 1529 |
+
|
| 1530 |
+
for i in range(num_dunes):
|
| 1531 |
+
dune_r = dune_zone_start + i * (dune_zone_end - dune_zone_start) // (num_dunes + 1)
|
| 1532 |
+
dune_height = 15.0 * stage * (1 - i * 0.2)
|
| 1533 |
+
|
| 1534 |
+
for r in range(h):
|
| 1535 |
+
for c in range(w):
|
| 1536 |
+
dr = abs(r - dune_r)
|
| 1537 |
+
if dr < 10:
|
| 1538 |
+
# ์ฌ๊ตฌ ํํ (๋ฐ๋๋ฐ์ด ์๋ง, ๋ฐ๋๊ทธ๋ ๊ธ)
|
| 1539 |
+
if r < dune_r:
|
| 1540 |
+
z = dune_height * (1 - dr / 12)
|
| 1541 |
+
else:
|
| 1542 |
+
z = dune_height * (1 - dr / 8)
|
| 1543 |
+
elevation[r, c] = max(elevation[r, c], z)
|
| 1544 |
+
|
| 1545 |
+
return elevation
|
| 1546 |
+
|
| 1547 |
+
|
| 1548 |
+
# ์ ๋๋ฉ์ด์
์์ฑ๊ธฐ ๋งคํ
|
| 1549 |
+
ANIMATED_LANDFORM_GENERATORS = {
|
| 1550 |
+
'delta': create_delta_animated,
|
| 1551 |
+
'alluvial_fan': create_alluvial_fan_animated,
|
| 1552 |
+
'meander': create_meander_animated,
|
| 1553 |
+
'u_valley': create_u_valley_animated,
|
| 1554 |
+
'v_valley': create_v_valley_animated,
|
| 1555 |
+
'barchan': create_barchan_animated,
|
| 1556 |
+
'coastal_cliff': create_coastal_cliff_animated,
|
| 1557 |
+
# ํ์ฅ
|
| 1558 |
+
'incised_meander': create_incised_meander,
|
| 1559 |
+
'free_meander': create_free_meander,
|
| 1560 |
+
'bird_foot_delta': create_bird_foot_delta,
|
| 1561 |
+
'arcuate_delta': create_arcuate_delta,
|
| 1562 |
+
'cuspate_delta': create_cuspate_delta,
|
| 1563 |
+
'cirque': create_cirque,
|
| 1564 |
+
'horn': create_horn,
|
| 1565 |
+
'shield_volcano': create_shield_volcano,
|
| 1566 |
+
'stratovolcano': create_stratovolcano,
|
| 1567 |
+
'caldera': create_caldera,
|
| 1568 |
+
'mesa_butte': create_mesa_butte,
|
| 1569 |
+
'spit_lagoon': create_spit_lagoon,
|
| 1570 |
+
# ์ถ๊ฐ ์งํ
|
| 1571 |
+
'fjord': create_fjord,
|
| 1572 |
+
'drumlin': create_drumlin,
|
| 1573 |
+
'moraine': create_moraine,
|
| 1574 |
+
'braided_river': create_braided_river,
|
| 1575 |
+
'waterfall': create_waterfall,
|
| 1576 |
+
'karst_doline': create_karst_doline,
|
| 1577 |
+
'ria_coast': create_ria_coast,
|
| 1578 |
+
'tombolo': create_tombolo,
|
| 1579 |
+
'sea_arch': create_sea_arch,
|
| 1580 |
+
'crater_lake': create_crater_lake,
|
| 1581 |
+
'lava_plateau': create_lava_plateau,
|
| 1582 |
+
'coastal_dune': create_coastal_dune,
|
| 1583 |
+
}
|
| 1584 |
+
|
| 1585 |
+
# ์งํ ์์ฑ ํจ์ ๋งคํ
|
| 1586 |
+
IDEAL_LANDFORM_GENERATORS = {
|
| 1587 |
+
'delta': create_delta,
|
| 1588 |
+
'alluvial_fan': create_alluvial_fan,
|
| 1589 |
+
'meander': create_meander,
|
| 1590 |
+
'u_valley': create_u_valley,
|
| 1591 |
+
'v_valley': create_v_valley,
|
| 1592 |
+
'barchan': create_barchan_dune,
|
| 1593 |
+
'coastal_cliff': create_coastal_cliff,
|
| 1594 |
+
# ํ์ฅ ์งํ
|
| 1595 |
+
'incised_meander': lambda gs: create_incised_meander(gs, 1.0),
|
| 1596 |
+
'free_meander': lambda gs: create_free_meander(gs, 1.0),
|
| 1597 |
+
'bird_foot_delta': lambda gs: create_bird_foot_delta(gs, 1.0),
|
| 1598 |
+
'arcuate_delta': lambda gs: create_arcuate_delta(gs, 1.0),
|
| 1599 |
+
'cuspate_delta': lambda gs: create_cuspate_delta(gs, 1.0),
|
| 1600 |
+
'cirque': lambda gs: create_cirque(gs, 1.0),
|
| 1601 |
+
'horn': lambda gs: create_horn(gs, 1.0),
|
| 1602 |
+
'shield_volcano': lambda gs: create_shield_volcano(gs, 1.0),
|
| 1603 |
+
'stratovolcano': lambda gs: create_stratovolcano(gs, 1.0),
|
| 1604 |
+
'caldera': lambda gs: create_caldera(gs, 1.0),
|
| 1605 |
+
'mesa_butte': lambda gs: create_mesa_butte(gs, 1.0),
|
| 1606 |
+
'spit_lagoon': lambda gs: create_spit_lagoon(gs, 1.0),
|
| 1607 |
+
# ์ถ๊ฐ ์งํ
|
| 1608 |
+
'fjord': lambda gs: create_fjord(gs, 1.0),
|
| 1609 |
+
'drumlin': lambda gs: create_drumlin(gs, 1.0),
|
| 1610 |
+
'moraine': lambda gs: create_moraine(gs, 1.0),
|
| 1611 |
+
'braided_river': lambda gs: create_braided_river(gs, 1.0),
|
| 1612 |
+
'waterfall': lambda gs: create_waterfall(gs, 1.0),
|
| 1613 |
+
'karst_doline': lambda gs: create_karst_doline(gs, 1.0),
|
| 1614 |
+
'ria_coast': lambda gs: create_ria_coast(gs, 1.0),
|
| 1615 |
+
'tombolo': lambda gs: create_tombolo(gs, 1.0),
|
| 1616 |
+
'sea_arch': lambda gs: create_sea_arch(gs, 1.0),
|
| 1617 |
+
'crater_lake': lambda gs: create_crater_lake(gs, 1.0),
|
| 1618 |
+
'lava_plateau': lambda gs: create_lava_plateau(gs, 1.0),
|
| 1619 |
+
'coastal_dune': lambda gs: create_coastal_dune(gs, 1.0),
|
| 1620 |
+
}
|
| 1621 |
+
|
engine/lateral_erosion.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Lateral Erosion Module (์ธก๋ฐฉ ์นจ์)
|
| 3 |
+
|
| 4 |
+
ํ์ฒ์ ๊ณก๋ฅ(Meander) ํ์ฑ์ ์ํ ์ธก๋ฐฉ ์นจ์ ํ๋ก์ธ์ค
|
| 5 |
+
- ๊ณก๋ฅ ์ธ์ธก: ์นจ์ (Cutbank)
|
| 6 |
+
- ๊ณก๋ฅ ๋ด์ธก: ํด์ (Point Bar)
|
| 7 |
+
|
| 8 |
+
ํต์ฌ ๊ณต์:
|
| 9 |
+
E_lateral = k * ฯ * (1/R)
|
| 10 |
+
- k: ์นจ์ ๊ณ์
|
| 11 |
+
- ฯ: ์ ๋จ์๋ ฅ (์ ๋ ํจ์)
|
| 12 |
+
- R: ๊ณก๋ฅ ๋ฐ๊ฒฝ (์์์๋ก ๊ธํ์ โ ์นจ์ ์ฆ๊ฐ)
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
from .grid import WorldGrid
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def compute_flow_curvature(flow_dir: np.ndarray, elevation: np.ndarray) -> np.ndarray:
|
| 20 |
+
"""
|
| 21 |
+
์ ํฅ(Flow Direction)์ผ๋ก๋ถํฐ ๊ณก๋ฅ (Curvature) ๊ณ์ฐ
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
flow_dir: D8 ์ ํฅ ๋ฐฐ์ด (0-7, -1 = sink)
|
| 25 |
+
elevation: ๊ณ ๋ ๋ฐฐ์ด
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
curvature: ๊ณก๋ฅ ๋ฐฐ์ด (์์: ์ขํ์ , ์์: ์ฐํ์ )
|
| 29 |
+
"""
|
| 30 |
+
h, w = flow_dir.shape
|
| 31 |
+
curvature = np.zeros((h, w), dtype=np.float64)
|
| 32 |
+
|
| 33 |
+
# D8 ๋ฐฉํฅ ๋ฒกํฐ (index 0-7)
|
| 34 |
+
# 0: NW, 1: N, 2: NE, 3: W, 4: E, 5: SW, 6: S, 7: SE
|
| 35 |
+
dir_dy = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
|
| 36 |
+
dir_dx = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
|
| 37 |
+
|
| 38 |
+
# ๋ฐฉํฅ์ ๊ฐ๋๋ก ๋ณํ (๋ผ๋์)
|
| 39 |
+
# atan2(dy, dx)
|
| 40 |
+
dir_angles = np.arctan2(dir_dy, dir_dx)
|
| 41 |
+
|
| 42 |
+
for r in range(1, h - 1):
|
| 43 |
+
for c in range(1, w - 1):
|
| 44 |
+
current_dir = int(flow_dir[r, c])
|
| 45 |
+
if current_dir < 0 or current_dir > 7:
|
| 46 |
+
continue
|
| 47 |
+
|
| 48 |
+
# ์๋ฅ ๋ฐฉํฅ ์ฐพ๊ธฐ (๋๋ฅผ ํฅํด ํ๋ฅด๋ ์
)
|
| 49 |
+
upstream_dirs = []
|
| 50 |
+
for k in range(8):
|
| 51 |
+
nr = r + dir_dy[k]
|
| 52 |
+
nc = c + dir_dx[k]
|
| 53 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 54 |
+
neighbor_dir = int(flow_dir[nr, nc])
|
| 55 |
+
if neighbor_dir >= 0 and neighbor_dir < 8:
|
| 56 |
+
# ์ด์์ด ๋๋ฅผ ํฅํด ํ๋ฅด๋๊ฐ?
|
| 57 |
+
target_r = nr + dir_dy[neighbor_dir]
|
| 58 |
+
target_c = nc + dir_dx[neighbor_dir]
|
| 59 |
+
if target_r == r and target_c == c:
|
| 60 |
+
upstream_dirs.append(neighbor_dir)
|
| 61 |
+
|
| 62 |
+
if not upstream_dirs:
|
| 63 |
+
continue
|
| 64 |
+
|
| 65 |
+
# ์๋ฅ ๋ฐฉํฅ์ ํ๊ท ๊ฐ๋
|
| 66 |
+
upstream_angle = np.mean([dir_angles[d] for d in upstream_dirs])
|
| 67 |
+
current_angle = dir_angles[current_dir]
|
| 68 |
+
|
| 69 |
+
# ๊ฐ๋ ๋ณํ = ๊ณก๋ฅ (์ ๊ทํ๋ ๊ฐ)
|
| 70 |
+
angle_diff = current_angle - upstream_angle
|
| 71 |
+
|
| 72 |
+
# -ฯ ~ ฯ ๋ฒ์๋ก ์ ๊ทํ
|
| 73 |
+
while angle_diff > np.pi:
|
| 74 |
+
angle_diff -= 2 * np.pi
|
| 75 |
+
while angle_diff < -np.pi:
|
| 76 |
+
angle_diff += 2 * np.pi
|
| 77 |
+
|
| 78 |
+
curvature[r, c] = angle_diff
|
| 79 |
+
|
| 80 |
+
return curvature
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def apply_lateral_erosion(grid: WorldGrid,
|
| 84 |
+
curvature: np.ndarray,
|
| 85 |
+
discharge: np.ndarray,
|
| 86 |
+
k: float = 0.01,
|
| 87 |
+
dt: float = 1.0) -> np.ndarray:
|
| 88 |
+
"""
|
| 89 |
+
์ธก๋ฐฉ ์นจ์ ์ ์ฉ
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
grid: WorldGrid ๊ฐ์ฒด
|
| 93 |
+
curvature: ๊ณก๋ฅ ๋ฐฐ์ด (์์: ์ขํ์ , ์์: ์ฐํ์ )
|
| 94 |
+
discharge: ์ ๋ ๋ฐฐ์ด
|
| 95 |
+
k: ์นจ์ ๊ณ์
|
| 96 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 97 |
+
|
| 98 |
+
Returns:
|
| 99 |
+
change: ์งํ ๋ณํ๋ ๋ฐฐ์ด
|
| 100 |
+
"""
|
| 101 |
+
h, w = grid.height, grid.width
|
| 102 |
+
change = np.zeros((h, w), dtype=np.float64)
|
| 103 |
+
|
| 104 |
+
# D8 ๋ฐฉํฅ ๋ฒกํฐ
|
| 105 |
+
dir_dy = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
|
| 106 |
+
dir_dx = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
|
| 107 |
+
|
| 108 |
+
# ์ข/์ฐ์ธก ๋ฐฉํฅ (์๋์ )
|
| 109 |
+
# ํ์ฌ ๋ฐฉํฅ์ด k์ผ ๋, ์ข์ธก์ (k-2)%8, ์ฐ์ธก์ (k+2)%8 (๋๋ต์ ๊ทผ์ฌ)
|
| 110 |
+
|
| 111 |
+
for r in range(1, h - 1):
|
| 112 |
+
for c in range(1, w - 1):
|
| 113 |
+
curv = curvature[r, c]
|
| 114 |
+
Q = discharge[r, c]
|
| 115 |
+
|
| 116 |
+
if abs(curv) < 0.01 or Q < 1.0:
|
| 117 |
+
continue
|
| 118 |
+
|
| 119 |
+
# ์นจ์๋ = k * sqrt(Q) * |curvature| * dt
|
| 120 |
+
erosion_amount = k * np.sqrt(Q) * abs(curv) * dt
|
| 121 |
+
|
| 122 |
+
# ํ์ฌ ์ ํฅ
|
| 123 |
+
flow_k = int(grid.flow_dir[r, c])
|
| 124 |
+
if flow_k < 0 or flow_k > 7:
|
| 125 |
+
continue
|
| 126 |
+
|
| 127 |
+
# ์ข/์ฐ์ธก ์
๊ฒฐ์
|
| 128 |
+
if curv > 0: # ์ขํ์ โ ์ฐ์ธก(์ธ์ธก) ์นจ์, ์ข์ธก(๋ด์ธก) ํด์
|
| 129 |
+
erode_k = (flow_k + 2) % 8 # ์ฐ์ธก
|
| 130 |
+
deposit_k = (flow_k - 2 + 8) % 8 # ์ข์ธก
|
| 131 |
+
else: # ์ฐํ์ โ ์ข์ธก(์ธ์ธก) ์นจ์, ์ฐ์ธก(๋ด์ธก) ํด์
|
| 132 |
+
erode_k = (flow_k - 2 + 8) % 8 # ์ข์ธก
|
| 133 |
+
deposit_k = (flow_k + 2) % 8 # ์ฐ์ธก
|
| 134 |
+
|
| 135 |
+
# ์นจ์ ์
|
| 136 |
+
er = r + dir_dy[erode_k]
|
| 137 |
+
ec = c + dir_dx[erode_k]
|
| 138 |
+
|
| 139 |
+
# ํด์ ์
|
| 140 |
+
dr = r + dir_dy[deposit_k]
|
| 141 |
+
dc = c + dir_dx[deposit_k]
|
| 142 |
+
|
| 143 |
+
# ๊ฒฝ๊ณ ์ฒดํฌ
|
| 144 |
+
if 0 <= er < h and 0 <= ec < w:
|
| 145 |
+
change[er, ec] -= erosion_amount
|
| 146 |
+
|
| 147 |
+
if 0 <= dr < h and 0 <= dc < w:
|
| 148 |
+
change[dr, dc] += erosion_amount * 0.8 # ์ผ๋ถ ์์ค
|
| 149 |
+
|
| 150 |
+
return change
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
class LateralErosionKernel:
|
| 154 |
+
"""
|
| 155 |
+
์ธก๋ฐฉ ์นจ์ ์ปค๋
|
| 156 |
+
|
| 157 |
+
EarthSystem์ ํตํฉํ์ฌ ์ฌ์ฉ
|
| 158 |
+
"""
|
| 159 |
+
|
| 160 |
+
def __init__(self, grid: WorldGrid, k: float = 0.01):
|
| 161 |
+
self.grid = grid
|
| 162 |
+
self.k = k
|
| 163 |
+
|
| 164 |
+
def step(self, discharge: np.ndarray, dt: float = 1.0) -> np.ndarray:
|
| 165 |
+
"""
|
| 166 |
+
1๋จ๊ณ ์ธก๋ฐฉ ์นจ์ ์คํ
|
| 167 |
+
|
| 168 |
+
Args:
|
| 169 |
+
discharge: ์ ๋ ๋ฐฐ์ด
|
| 170 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 171 |
+
|
| 172 |
+
Returns:
|
| 173 |
+
change: ์งํ ๋ณํ๋
|
| 174 |
+
"""
|
| 175 |
+
# 1. ๊ณก๋ฅ ๊ณ์ฐ
|
| 176 |
+
curvature = compute_flow_curvature(self.grid.flow_dir, self.grid.elevation)
|
| 177 |
+
|
| 178 |
+
# 2. ์ธก๋ฐฉ ์นจ์ ์ ์ฉ
|
| 179 |
+
change = apply_lateral_erosion(self.grid, curvature, discharge, self.k, dt)
|
| 180 |
+
|
| 181 |
+
# 3. ์งํ ์
๋ฐ์ดํธ
|
| 182 |
+
# ์นจ์๋ถ: bedrock/sediment์์ ์ ๊ฑฐ
|
| 183 |
+
erosion_mask = change < 0
|
| 184 |
+
erosion_amount = -change[erosion_mask]
|
| 185 |
+
|
| 186 |
+
# ํด์ ์ธต ๋จผ์ , ๋ถ์กฑํ๋ฉด ๊ธฐ๋ฐ์
|
| 187 |
+
sed_loss = np.minimum(erosion_amount, self.grid.sediment[erosion_mask])
|
| 188 |
+
rock_loss = erosion_amount - sed_loss
|
| 189 |
+
|
| 190 |
+
self.grid.sediment[erosion_mask] -= sed_loss
|
| 191 |
+
self.grid.bedrock[erosion_mask] -= rock_loss
|
| 192 |
+
|
| 193 |
+
# ํด์ ๋ถ: sediment์ ์ถ๊ฐ
|
| 194 |
+
self.grid.sediment += np.maximum(change, 0)
|
| 195 |
+
|
| 196 |
+
# ๊ณ ๋ ๋๊ธฐํ
|
| 197 |
+
self.grid.update_elevation()
|
| 198 |
+
|
| 199 |
+
return change
|
engine/mass_movement.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Mass Movement Kernel (๋งค์ค๋ฌด๋ธ๋จผํธ)
|
| 3 |
+
|
| 4 |
+
์ค๋ ฅ์ ์ํ ์ฌ๋ฉด ์ด๋ ํ๋ก์ธ์ค
|
| 5 |
+
- ์ฐ์ฌํ (Landslide)
|
| 6 |
+
- ์ฌ๋ผํ (Slump)
|
| 7 |
+
- ๋์ (Rockfall)
|
| 8 |
+
|
| 9 |
+
ํต์ฌ ์๋ฆฌ:
|
| 10 |
+
๊ฒฝ์ฌ(Slope) > ์๊ณ ๊ฒฝ์ฌ(Critical Angle) โ ๋ฌผ์ง ์ด๋
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
from .grid import WorldGrid
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class MassMovementKernel:
|
| 18 |
+
"""
|
| 19 |
+
๋งค์ค๋ฌด๋ธ๋จผํธ ์ปค๋
|
| 20 |
+
|
| 21 |
+
๊ฒฝ์ฌ ์์ ์ฑ์ ๊ฒ์ฌํ๊ณ , ๋ถ์์ ํ ๊ณณ์์ ๋ฌผ์ง ์ด๋์ ์๋ฎฌ๋ ์ด์
.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, grid: WorldGrid,
|
| 25 |
+
friction_angle: float = 35.0, # ๋ด๋ถ ๋ง์ฐฐ๊ฐ (๋)
|
| 26 |
+
cohesion: float = 0.0): # ์ ์ฐฉ๋ ฅ (Pa, ๊ฐ์ํ)
|
| 27 |
+
self.grid = grid
|
| 28 |
+
self.friction_angle = friction_angle
|
| 29 |
+
self.cohesion = cohesion
|
| 30 |
+
|
| 31 |
+
# ์๊ณ ๊ฒฝ์ฌ (ํ์ ํธ ๊ฐ)
|
| 32 |
+
self.critical_slope = np.tan(np.radians(friction_angle))
|
| 33 |
+
|
| 34 |
+
def check_stability(self) -> np.ndarray:
|
| 35 |
+
"""
|
| 36 |
+
๊ฒฝ์ฌ ์์ ์ฑ ๊ฒ์ฌ
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
unstable_mask: ๋ถ์์ ํ ์
๋ง์คํฌ (True = ๋ถ์์ )
|
| 40 |
+
"""
|
| 41 |
+
slope, _ = self.grid.get_gradient()
|
| 42 |
+
|
| 43 |
+
# ๊ฒฝ์ฌ > ์๊ณ ๊ฒฝ์ฌ โ ๋ถ์์
|
| 44 |
+
unstable = slope > self.critical_slope
|
| 45 |
+
|
| 46 |
+
return unstable
|
| 47 |
+
|
| 48 |
+
def trigger_landslide(self, unstable_mask: np.ndarray,
|
| 49 |
+
efficiency: float = 0.5) -> np.ndarray:
|
| 50 |
+
"""
|
| 51 |
+
์ฐ์ฌํ ๋ฐ์
|
| 52 |
+
|
| 53 |
+
๋ถ์์ ํ ์
์์ ๋ฌผ์ง์ ๋ฎ์ ๊ณณ์ผ๋ก ์ด๋.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
unstable_mask: ๋ถ์์ ๋ง์คํฌ
|
| 57 |
+
efficiency: ์ด๋ ํจ์จ (0.0~1.0, 1.0์ด๋ฉด ์์ ์ด๋)
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
change: ์งํ ๋ณํ๋
|
| 61 |
+
"""
|
| 62 |
+
h, w = self.grid.height, self.grid.width
|
| 63 |
+
change = np.zeros((h, w), dtype=np.float64)
|
| 64 |
+
|
| 65 |
+
if not np.any(unstable_mask):
|
| 66 |
+
return change
|
| 67 |
+
|
| 68 |
+
# D8 ๋ฐฉํฅ
|
| 69 |
+
dr = np.array([-1, -1, -1, 0, 0, 1, 1, 1])
|
| 70 |
+
dc = np.array([-1, 0, 1, -1, 1, -1, 0, 1])
|
| 71 |
+
|
| 72 |
+
elev = self.grid.elevation
|
| 73 |
+
slope, _ = self.grid.get_gradient()
|
| 74 |
+
|
| 75 |
+
# ๋ถ์์ ์
์ขํ
|
| 76 |
+
unstable_coords = np.argwhere(unstable_mask)
|
| 77 |
+
|
| 78 |
+
for r, c in unstable_coords:
|
| 79 |
+
# ํ์ฌ ๊ฒฝ์ฌ ์ด๊ณผ๋ถ ๊ณ์ฐ
|
| 80 |
+
excess_slope = slope[r, c] - self.critical_slope
|
| 81 |
+
if excess_slope <= 0:
|
| 82 |
+
continue
|
| 83 |
+
|
| 84 |
+
# ์ด๋๋ = ์ด๊ณผ ๊ฒฝ์ฌ์ ๋น๋ก
|
| 85 |
+
# ํด์ ์ธต ๋จผ์ ์ด๋, ๋ถ์กฑํ๋ฉด ๊ธฐ๋ฐ์
|
| 86 |
+
available = self.grid.sediment[r, c] + self.grid.bedrock[r, c] * 0.1
|
| 87 |
+
move_amount = min(excess_slope * efficiency * 5.0, available)
|
| 88 |
+
|
| 89 |
+
if move_amount <= 0:
|
| 90 |
+
continue
|
| 91 |
+
|
| 92 |
+
# ๊ฐ์ฅ ๋ฎ์ ์ด์ ์ฐพ๊ธฐ
|
| 93 |
+
min_z = elev[r, c]
|
| 94 |
+
target = None
|
| 95 |
+
|
| 96 |
+
for k in range(8):
|
| 97 |
+
nr, nc = r + dr[k], c + dc[k]
|
| 98 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 99 |
+
if elev[nr, nc] < min_z:
|
| 100 |
+
min_z = elev[nr, nc]
|
| 101 |
+
target = (nr, nc)
|
| 102 |
+
|
| 103 |
+
if target is None:
|
| 104 |
+
continue
|
| 105 |
+
|
| 106 |
+
tr, tc = target
|
| 107 |
+
|
| 108 |
+
# ๋ฌผ์ง ์ด๋
|
| 109 |
+
change[r, c] -= move_amount
|
| 110 |
+
change[tr, tc] += move_amount * 0.9 # ์ผ๋ถ ์์ค (๋ถ์ฐ)
|
| 111 |
+
|
| 112 |
+
# ์งํ ์
๋ฐ์ดํธ
|
| 113 |
+
# ์์ค๋ถ: sediment์์ ์ ๊ฑฐ
|
| 114 |
+
loss_mask = change < 0
|
| 115 |
+
loss = -change[loss_mask]
|
| 116 |
+
|
| 117 |
+
sed_loss = np.minimum(loss, self.grid.sediment[loss_mask])
|
| 118 |
+
rock_loss = loss - sed_loss
|
| 119 |
+
|
| 120 |
+
self.grid.sediment[loss_mask] -= sed_loss
|
| 121 |
+
self.grid.bedrock[loss_mask] -= rock_loss
|
| 122 |
+
|
| 123 |
+
# ํด์ ๋ถ: sediment์ ์ถ๊ฐ
|
| 124 |
+
self.grid.sediment += np.maximum(change, 0)
|
| 125 |
+
|
| 126 |
+
self.grid.update_elevation()
|
| 127 |
+
|
| 128 |
+
return change
|
| 129 |
+
|
| 130 |
+
def step(self, dt: float = 1.0) -> np.ndarray:
|
| 131 |
+
"""
|
| 132 |
+
1๋จ๊ณ ๋งค์ค๋ฌด๋ธ๋จผํธ ์คํ
|
| 133 |
+
|
| 134 |
+
Args:
|
| 135 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ (์ฌ์ฉํ์ง ์์ง๋ง ์ธํฐํ์ด์ค ์ผ๊ด์ฑ)
|
| 136 |
+
|
| 137 |
+
Returns:
|
| 138 |
+
change: ์งํ ๋ณํ๋
|
| 139 |
+
"""
|
| 140 |
+
# 1. ์์ ์ฑ ๊ฒ์ฌ
|
| 141 |
+
unstable = self.check_stability()
|
| 142 |
+
|
| 143 |
+
# 2. ๋ถ์์ ์ง์ ์์ ์ฐ์ฌํ ๋ฐ์
|
| 144 |
+
change = self.trigger_landslide(unstable)
|
| 145 |
+
|
| 146 |
+
return change
|
engine/meander_physics.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI: ๊ณก๋ฅ ํ์ฒ ๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
|
| 3 |
+
Helical Flow ๊ธฐ๋ฐ ์ธก๋ฐฉ ์นจ์/ํด์
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from typing import List, Tuple
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@dataclass
|
| 11 |
+
class MeanderChannel:
|
| 12 |
+
"""๊ณก๋ฅ ํ์ฒ ์ฑ๋ ํํ
|
| 13 |
+
|
| 14 |
+
1D ์ค์ฌ์ ๊ธฐ๋ฐ์ผ๋ก ์ฑ๋์ ํํํ๊ณ ,
|
| 15 |
+
๊ฐ ์ง์ ์์์ ๊ณก๋ฅ , ํญ, ๊น์ด๋ฅผ ์ถ์
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
# ์ฑ๋ ์ค์ฌ์ ์ขํ
|
| 19 |
+
x: np.ndarray = field(default=None)
|
| 20 |
+
y: np.ndarray = field(default=None)
|
| 21 |
+
|
| 22 |
+
# ๊ฐ ์ง์ ์ ์์ฑ
|
| 23 |
+
width: np.ndarray = field(default=None) # ์ฑ๋ ํญ (m)
|
| 24 |
+
depth: np.ndarray = field(default=None) # ์ฑ๋ ๊น์ด (m)
|
| 25 |
+
|
| 26 |
+
# ์ ๋ ๋ฐ ์ ์
|
| 27 |
+
discharge: float = 100.0 # mยณ/s
|
| 28 |
+
velocity: np.ndarray = field(default=None) # m/s
|
| 29 |
+
|
| 30 |
+
def __post_init__(self):
|
| 31 |
+
if self.x is not None and self.width is None:
|
| 32 |
+
n = len(self.x)
|
| 33 |
+
self.width = np.full(n, 20.0)
|
| 34 |
+
self.depth = np.full(n, 3.0)
|
| 35 |
+
self.velocity = np.full(n, 1.5)
|
| 36 |
+
|
| 37 |
+
@classmethod
|
| 38 |
+
def create_initial(cls, length: float = 1000.0,
|
| 39 |
+
initial_sinuosity: float = 1.2,
|
| 40 |
+
n_points: int = 200,
|
| 41 |
+
discharge: float = 100.0):
|
| 42 |
+
"""์ด๊ธฐ ๊ณก๋ฅ ํ์ฒ ์์ฑ"""
|
| 43 |
+
s = np.linspace(0, 1, n_points) # ์ ๊ทํ๋ ๊ฑฐ๋ฆฌ
|
| 44 |
+
|
| 45 |
+
# ์ฌ์ธํ ๊ธฐ๋ฐ ์ด๊ธฐ ๊ณก๋ฅ
|
| 46 |
+
amplitude = length * 0.1 * (initial_sinuosity - 1)
|
| 47 |
+
frequency = 3 # ๊ตฝ์ด ์
|
| 48 |
+
|
| 49 |
+
x = s * length
|
| 50 |
+
y = amplitude * np.sin(2 * np.pi * frequency * s)
|
| 51 |
+
|
| 52 |
+
return cls(x=x, y=y, discharge=discharge)
|
| 53 |
+
|
| 54 |
+
def calculate_curvature(self) -> np.ndarray:
|
| 55 |
+
"""๊ณก๋ฅ ๊ณ์ฐ (1/m)
|
| 56 |
+
ฮบ = (x'y'' - y'x'') / (x'^2 + y'^2)^(3/2)
|
| 57 |
+
"""
|
| 58 |
+
dx = np.gradient(self.x)
|
| 59 |
+
dy = np.gradient(self.y)
|
| 60 |
+
ddx = np.gradient(dx)
|
| 61 |
+
ddy = np.gradient(dy)
|
| 62 |
+
|
| 63 |
+
denominator = np.power(dx**2 + dy**2, 1.5) + 1e-10
|
| 64 |
+
curvature = (dx * ddy - dy * ddx) / denominator
|
| 65 |
+
|
| 66 |
+
return curvature
|
| 67 |
+
|
| 68 |
+
def calculate_sinuosity(self) -> float:
|
| 69 |
+
"""๊ตด๊ณก๋ = ํ์ฒ ๊ธธ์ด / ์ง์ ๊ฑฐ๋ฆฌ"""
|
| 70 |
+
# ๊ฒฝ๋ก ๊ธธ์ด
|
| 71 |
+
ds = np.sqrt(np.diff(self.x)**2 + np.diff(self.y)**2)
|
| 72 |
+
path_length = np.sum(ds)
|
| 73 |
+
|
| 74 |
+
# ์ง์ ๊ฑฐ๋ฆฌ
|
| 75 |
+
straight_length = np.sqrt(
|
| 76 |
+
(self.x[-1] - self.x[0])**2 +
|
| 77 |
+
(self.y[-1] - self.y[0])**2
|
| 78 |
+
) + 1e-10
|
| 79 |
+
|
| 80 |
+
return path_length / straight_length
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
class HelicalFlowErosion:
|
| 84 |
+
"""Helical Flow ๊ธฐ๋ฐ ๊ณก๋ฅ ์นจ์/ํด์
|
| 85 |
+
|
| 86 |
+
๊ณก๋ฅ์์ ๋ฌผ์ ๋์ ํ(helical)์ผ๋ก ํ๋ฆ:
|
| 87 |
+
- ํ๋ฉด: ๋ฐ๊นฅ์ชฝ์ผ๋ก (์์ฌ๋ ฅ)
|
| 88 |
+
- ๋ฐ๋ฅ: ์์ชฝ์ผ๋ก (์๋ ฅ ๊ตฌ๋ฐฐ)
|
| 89 |
+
|
| 90 |
+
๊ฒฐ๊ณผ:
|
| 91 |
+
- ๋ฐ๊นฅ์ชฝ (๊ณต๊ฒฉ์ฌ๋ฉด): ์นจ์
|
| 92 |
+
- ์์ชฝ (ํด์ ์ฌ๋ฉด): ํด์
|
| 93 |
+
"""
|
| 94 |
+
|
| 95 |
+
def __init__(self,
|
| 96 |
+
bank_erosion_rate: float = 0.5, # m/yr per unit stress
|
| 97 |
+
deposition_rate: float = 0.3):
|
| 98 |
+
self.bank_erosion_rate = bank_erosion_rate
|
| 99 |
+
self.deposition_rate = deposition_rate
|
| 100 |
+
|
| 101 |
+
def calculate_bank_migration(self, channel: MeanderChannel,
|
| 102 |
+
dt: float = 1.0) -> Tuple[np.ndarray, np.ndarray]:
|
| 103 |
+
"""ํ์ ์ด๋ ๊ณ์ฐ
|
| 104 |
+
|
| 105 |
+
๊ณก๋ฅ ์ด ํฐ ๊ณณ์์ ๋ฐ๊นฅ์ชฝ์ผ๋ก ์นจ์ โ ์ฑ๋ ์ด๋
|
| 106 |
+
"""
|
| 107 |
+
curvature = channel.calculate_curvature()
|
| 108 |
+
|
| 109 |
+
# ์นจ์/ํด์ ๋น๋์นญ
|
| 110 |
+
# ๊ณก๋ฅ > 0: ์ข์ธก์ด ๋ฐ๊นฅ (์นจ์)
|
| 111 |
+
# ๊ณก๋ฅ < 0: ์ฐ์ธก์ด ๋ฐ๊นฅ (์นจ์)
|
| 112 |
+
|
| 113 |
+
# ์ด๋ ๋ฒกํฐ (์ฑ๋์ ์์ง)
|
| 114 |
+
dx = np.gradient(channel.x)
|
| 115 |
+
dy = np.gradient(channel.y)
|
| 116 |
+
path_length = np.sqrt(dx**2 + dy**2) + 1e-10
|
| 117 |
+
|
| 118 |
+
# ์์ง ๋ฐฉํฅ (์ผ์ชฝ์ผ๋ก 90๋ ํ์ )
|
| 119 |
+
normal_x = -dy / path_length
|
| 120 |
+
normal_y = dx / path_length
|
| 121 |
+
|
| 122 |
+
# ์ด๋๋ = ๊ณก๋ฅ ร ์ ๋ ร erosion rate
|
| 123 |
+
migration_rate = curvature * channel.discharge * self.bank_erosion_rate * dt
|
| 124 |
+
|
| 125 |
+
# ์ด๋๋ ์ ํ
|
| 126 |
+
migration_rate = np.clip(migration_rate, -2.0, 2.0)
|
| 127 |
+
|
| 128 |
+
delta_x = migration_rate * normal_x
|
| 129 |
+
delta_y = migration_rate * normal_y
|
| 130 |
+
|
| 131 |
+
return delta_x, delta_y
|
| 132 |
+
|
| 133 |
+
def check_cutoff(self, channel: MeanderChannel,
|
| 134 |
+
threshold_distance: float = 30.0) -> List[Tuple[int, int]]:
|
| 135 |
+
"""์ฐ๊ฐํธ ํ์ฑ ์กฐ๊ฑด ์ฒดํฌ (์ ๋ก ์ ๋จ)"""
|
| 136 |
+
n = len(channel.x)
|
| 137 |
+
cutoffs = []
|
| 138 |
+
|
| 139 |
+
# ๊ฐ๊น์ด ๋ ์ ์ฐพ๊ธฐ (๊ฒฝ๋ก์ ๋ฉ๋ฆฌ ๋จ์ด์ก์ง๋ง ๊ณต๊ฐ์ ์ผ๋ก ๊ฐ๊น์ด)
|
| 140 |
+
for i in range(n):
|
| 141 |
+
for j in range(i + 30, n): # ์ต์ 30์ ๊ฐ๊ฒฉ
|
| 142 |
+
dist = np.sqrt(
|
| 143 |
+
(channel.x[i] - channel.x[j])**2 +
|
| 144 |
+
(channel.y[i] - channel.y[j])**2
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
if dist < threshold_distance:
|
| 148 |
+
cutoffs.append((i, j))
|
| 149 |
+
break # ์ฒซ ๋ฒ์งธ cutoff๋ง
|
| 150 |
+
|
| 151 |
+
return cutoffs
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class MeanderSimulation:
|
| 155 |
+
"""๊ณก๋ฅ ํ์ฒ ์๋ฎฌ๋ ์ด์
"""
|
| 156 |
+
|
| 157 |
+
def __init__(self, length: float = 1000.0, initial_sinuosity: float = 1.3):
|
| 158 |
+
self.channel = MeanderChannel.create_initial(
|
| 159 |
+
length=length,
|
| 160 |
+
initial_sinuosity=initial_sinuosity
|
| 161 |
+
)
|
| 162 |
+
self.erosion = HelicalFlowErosion()
|
| 163 |
+
|
| 164 |
+
self.history: List[Tuple[np.ndarray, np.ndarray]] = []
|
| 165 |
+
self.oxbow_lakes: List[Tuple[np.ndarray, np.ndarray]] = []
|
| 166 |
+
self.time = 0.0
|
| 167 |
+
|
| 168 |
+
def step(self, dt: float = 1.0):
|
| 169 |
+
"""1 ํ์์คํ
"""
|
| 170 |
+
# 1. ํ์ ์ด๋
|
| 171 |
+
dx, dy = self.erosion.calculate_bank_migration(self.channel, dt)
|
| 172 |
+
self.channel.x += dx
|
| 173 |
+
self.channel.y += dy
|
| 174 |
+
|
| 175 |
+
# 2. ์ฐ๊ฐํธ ์ฒดํฌ
|
| 176 |
+
cutoffs = self.erosion.check_cutoff(self.channel)
|
| 177 |
+
for start, end in cutoffs:
|
| 178 |
+
# ์ฐ๊ฐํธ ์ ์ฅ
|
| 179 |
+
ox = self.channel.x[start:end+1].copy()
|
| 180 |
+
oy = self.channel.y[start:end+1].copy()
|
| 181 |
+
self.oxbow_lakes.append((ox, oy))
|
| 182 |
+
|
| 183 |
+
# ์ฑ๋ ๋จ์ถ
|
| 184 |
+
self.channel.x = np.concatenate([
|
| 185 |
+
self.channel.x[:start+1],
|
| 186 |
+
self.channel.x[end:]
|
| 187 |
+
])
|
| 188 |
+
self.channel.y = np.concatenate([
|
| 189 |
+
self.channel.y[:start+1],
|
| 190 |
+
self.channel.y[end:]
|
| 191 |
+
])
|
| 192 |
+
|
| 193 |
+
# ์์ฑ ๋ฐฐ์ด๋ ์กฐ์
|
| 194 |
+
n_new = len(self.channel.x)
|
| 195 |
+
self.channel.width = np.full(n_new, 20.0)
|
| 196 |
+
self.channel.depth = np.full(n_new, 3.0)
|
| 197 |
+
self.channel.velocity = np.full(n_new, 1.5)
|
| 198 |
+
|
| 199 |
+
self.time += dt
|
| 200 |
+
|
| 201 |
+
def run(self, total_time: float, save_interval: float = 100.0, dt: float = 1.0):
|
| 202 |
+
"""์๋ฎฌ๋ ์ด์
์คํ"""
|
| 203 |
+
steps = int(total_time / dt)
|
| 204 |
+
save_every = max(1, int(save_interval / dt))
|
| 205 |
+
|
| 206 |
+
self.history = [(self.channel.x.copy(), self.channel.y.copy())]
|
| 207 |
+
|
| 208 |
+
for i in range(steps):
|
| 209 |
+
self.step(dt)
|
| 210 |
+
if (i + 1) % save_every == 0:
|
| 211 |
+
self.history.append((self.channel.x.copy(), self.channel.y.copy()))
|
| 212 |
+
|
| 213 |
+
return self.history
|
| 214 |
+
|
| 215 |
+
def get_cross_section(self, position: float = 0.5) -> Tuple[np.ndarray, np.ndarray]:
|
| 216 |
+
"""๊ณก๋ฅ ๋จ๋ฉด (๋น๋์นญ)
|
| 217 |
+
|
| 218 |
+
position: ๊ณก๋ฅ ๋ด ์์น (0=์ง์ ๋ถ, 1=์ต๋ ๊ตฝ์ด)
|
| 219 |
+
"""
|
| 220 |
+
curvature = self.channel.calculate_curvature()
|
| 221 |
+
max_curve = np.abs(curvature).max() + 1e-10
|
| 222 |
+
asymmetry = np.abs(curvature[int(len(curvature) * 0.5)]) / max_curve
|
| 223 |
+
asymmetry = min(1.0, asymmetry * position * 2)
|
| 224 |
+
|
| 225 |
+
# ๋จ๋ฉด ์์ฑ
|
| 226 |
+
x = np.linspace(-30, 30, 100)
|
| 227 |
+
|
| 228 |
+
# ๋น๋์นญ ๊ณก์
|
| 229 |
+
left_depth = 5 + asymmetry * 3 # ๊ณต๊ฒฉ์ฌ๋ฉด (๊น์)
|
| 230 |
+
right_depth = 3 - asymmetry * 1 # ํด์ ์ฌ๋ฉด (์์)
|
| 231 |
+
|
| 232 |
+
y = np.where(
|
| 233 |
+
x < 0,
|
| 234 |
+
-left_depth * (1 - np.power(x / -30, 2)),
|
| 235 |
+
-right_depth * (1 - np.power(x / 30, 2))
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
return x, y
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
# ํ๋ฆฌ์ปดํจํ
|
| 242 |
+
def precompute_meander(max_time: int = 10000,
|
| 243 |
+
initial_sinuosity: float = 1.3,
|
| 244 |
+
save_every: int = 100) -> dict:
|
| 245 |
+
"""๊ณก๋ฅ ์๋ฎฌ๋ ์ด์
ํ๋ฆฌ์ปดํจํ
"""
|
| 246 |
+
sim = MeanderSimulation(initial_sinuosity=initial_sinuosity)
|
| 247 |
+
|
| 248 |
+
history = sim.run(
|
| 249 |
+
total_time=max_time,
|
| 250 |
+
save_interval=save_every,
|
| 251 |
+
dt=1.0
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
return {
|
| 255 |
+
'history': history,
|
| 256 |
+
'oxbow_lakes': sim.oxbow_lakes,
|
| 257 |
+
'final_sinuosity': sim.channel.calculate_sinuosity()
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
if __name__ == "__main__":
|
| 262 |
+
print("๊ณก๋ฅ ํ์ฒ ๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
ํ
์คํธ")
|
| 263 |
+
print("=" * 50)
|
| 264 |
+
|
| 265 |
+
sim = MeanderSimulation(initial_sinuosity=1.3)
|
| 266 |
+
print(f"์ด๊ธฐ ๊ตด๊ณก๋: {sim.channel.calculate_sinuosity():.2f}")
|
| 267 |
+
|
| 268 |
+
sim.run(5000, save_interval=1000)
|
| 269 |
+
print(f"5000๋
ํ ๊ตด๊ณก๋: {sim.channel.calculate_sinuosity():.2f}")
|
| 270 |
+
print(f"ํ์ฑ๋ ์ฐ๊ฐํธ: {len(sim.oxbow_lakes)}๊ฐ")
|
| 271 |
+
|
| 272 |
+
print("=" * 50)
|
| 273 |
+
print("ํ
์คํธ ์๋ฃ!")
|
engine/physics_engine.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI: ์ง์ง ๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
์์ง
|
| 3 |
+
Stream Power Law ๊ธฐ๋ฐ ์ค์ ์นจ์ ์๋ฎฌ๋ ์ด์
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from typing import List, Tuple, Optional
|
| 8 |
+
from scipy.ndimage import gaussian_filter, uniform_filter
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class TerrainGrid:
|
| 13 |
+
"""2D ์งํ ๊ทธ๋ฆฌ๋"""
|
| 14 |
+
width: int = 100
|
| 15 |
+
height: int = 100
|
| 16 |
+
cell_size: float = 10.0 # ๋ฏธํฐ
|
| 17 |
+
|
| 18 |
+
elevation: np.ndarray = field(default=None)
|
| 19 |
+
bedrock: np.ndarray = field(default=None) # ๊ธฐ๋ฐ์ (์นจ์ ๋ถ๊ฐ ๋ ๋ฒจ)
|
| 20 |
+
rock_hardness: np.ndarray = field(default=None) # 0-1
|
| 21 |
+
|
| 22 |
+
def __post_init__(self):
|
| 23 |
+
if self.elevation is None:
|
| 24 |
+
self.elevation = np.zeros((self.height, self.width))
|
| 25 |
+
if self.bedrock is None:
|
| 26 |
+
self.bedrock = np.full((self.height, self.width), -100.0)
|
| 27 |
+
if self.rock_hardness is None:
|
| 28 |
+
self.rock_hardness = np.full((self.height, self.width), 0.5)
|
| 29 |
+
|
| 30 |
+
def get_slope(self) -> np.ndarray:
|
| 31 |
+
"""๊ฒฝ์ฌ๋ ๊ณ์ฐ (m/m)"""
|
| 32 |
+
dy, dx = np.gradient(self.elevation, self.cell_size)
|
| 33 |
+
return np.sqrt(dx**2 + dy**2)
|
| 34 |
+
|
| 35 |
+
def get_slope_direction(self) -> Tuple[np.ndarray, np.ndarray]:
|
| 36 |
+
"""์ต๋ ๊ฒฝ์ฌ ๋ฐฉํฅ (๋จ์ ๋ฒกํฐ)"""
|
| 37 |
+
dy, dx = np.gradient(self.elevation, self.cell_size)
|
| 38 |
+
magnitude = np.sqrt(dx**2 + dy**2) + 1e-10
|
| 39 |
+
return -dx / magnitude, -dy / magnitude
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@dataclass
|
| 43 |
+
class WaterFlow:
|
| 44 |
+
"""์๋ฌธ ์๋ฎฌ๋ ์ด์
"""
|
| 45 |
+
terrain: TerrainGrid
|
| 46 |
+
|
| 47 |
+
# ์ ๋ (mยณ/s per cell)
|
| 48 |
+
discharge: np.ndarray = field(default=None)
|
| 49 |
+
# ์ ์ (m/s)
|
| 50 |
+
velocity: np.ndarray = field(default=None)
|
| 51 |
+
# ์์ฌ (m)
|
| 52 |
+
depth: np.ndarray = field(default=None)
|
| 53 |
+
# ์ ๋จ์๋ ฅ (Pa)
|
| 54 |
+
shear_stress: np.ndarray = field(default=None)
|
| 55 |
+
|
| 56 |
+
manning_n: float = 0.03 # Manning ์กฐ๋๊ณ์
|
| 57 |
+
|
| 58 |
+
def __post_init__(self):
|
| 59 |
+
shape = (self.terrain.height, self.terrain.width)
|
| 60 |
+
if self.discharge is None:
|
| 61 |
+
self.discharge = np.zeros(shape)
|
| 62 |
+
if self.velocity is None:
|
| 63 |
+
self.velocity = np.zeros(shape)
|
| 64 |
+
if self.depth is None:
|
| 65 |
+
self.depth = np.zeros(shape)
|
| 66 |
+
if self.shear_stress is None:
|
| 67 |
+
self.shear_stress = np.zeros(shape)
|
| 68 |
+
|
| 69 |
+
def flow_accumulation_d8(self, precipitation: float = 0.001):
|
| 70 |
+
"""D8 ์๊ณ ๋ฆฌ์ฆ ๊ธฐ๋ฐ ์ ๋ ๋์ """
|
| 71 |
+
h, w = self.terrain.height, self.terrain.width
|
| 72 |
+
elev = self.terrain.elevation
|
| 73 |
+
|
| 74 |
+
# ์ด๊ธฐ ๊ฐ์
|
| 75 |
+
acc = np.full((h, w), precipitation)
|
| 76 |
+
|
| 77 |
+
# ๋์ ๊ณณ์์ ๋ฎ์ ๊ณณ์ผ๋ก ์ ๋ ฌ
|
| 78 |
+
flat_elev = elev.ravel()
|
| 79 |
+
sorted_indices = np.argsort(flat_elev)[::-1]
|
| 80 |
+
|
| 81 |
+
# D8 ๋ฐฉํฅ (8๋ฐฉํฅ ์ด์)
|
| 82 |
+
neighbors = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
|
| 83 |
+
|
| 84 |
+
for idx in sorted_indices:
|
| 85 |
+
y, x = idx // w, idx % w
|
| 86 |
+
current_elev = elev[y, x]
|
| 87 |
+
|
| 88 |
+
# ๊ฐ์ฅ ๋ฎ์ ์ด์ ์ฐพ๊ธฐ
|
| 89 |
+
min_elev = current_elev
|
| 90 |
+
min_neighbor = None
|
| 91 |
+
|
| 92 |
+
for dy, dx in neighbors:
|
| 93 |
+
ny, nx = y + dy, x + dx
|
| 94 |
+
if 0 <= ny < h and 0 <= nx < w:
|
| 95 |
+
if elev[ny, nx] < min_elev:
|
| 96 |
+
min_elev = elev[ny, nx]
|
| 97 |
+
min_neighbor = (ny, nx)
|
| 98 |
+
|
| 99 |
+
# ํ๋ฅ๋ก ์ ๋ ์ ๋ฌ
|
| 100 |
+
if min_neighbor is not None:
|
| 101 |
+
acc[min_neighbor] += acc[y, x]
|
| 102 |
+
|
| 103 |
+
self.discharge = acc
|
| 104 |
+
return acc
|
| 105 |
+
|
| 106 |
+
def calculate_hydraulics(self):
|
| 107 |
+
"""Manning ๋ฐฉ์ ์ ๊ธฐ๋ฐ ์๋ฆฌํ ๊ณ์ฐ"""
|
| 108 |
+
slope = self.terrain.get_slope() + 0.0001 # 0 ๋ฐฉ์ง
|
| 109 |
+
|
| 110 |
+
# ๊ฐ์ : ์ฑ๋ ํญ = ์ ๋์ ํจ์
|
| 111 |
+
channel_width = 2 * np.power(self.discharge + 0.01, 0.4)
|
| 112 |
+
|
| 113 |
+
# Manning ๋ฐฉ์ ์: V = (1/n) * R^(2/3) * S^(1/2)
|
| 114 |
+
# ๋จ์ํ: R โ depth
|
| 115 |
+
# Q = V * A, A = width * depth
|
| 116 |
+
# depth = (Q * n / (width * S^0.5))^(3/5)
|
| 117 |
+
|
| 118 |
+
self.depth = np.power(
|
| 119 |
+
self.discharge * self.manning_n / (channel_width * np.sqrt(slope) + 0.01),
|
| 120 |
+
0.6
|
| 121 |
+
)
|
| 122 |
+
self.depth = np.clip(self.depth, 0, 50)
|
| 123 |
+
|
| 124 |
+
# ์ ์
|
| 125 |
+
hydraulic_radius = self.depth # ๋จ์ํ
|
| 126 |
+
self.velocity = (1 / self.manning_n) * np.power(hydraulic_radius, 2/3) * np.sqrt(slope)
|
| 127 |
+
self.velocity = np.clip(self.velocity, 0, 10)
|
| 128 |
+
|
| 129 |
+
# ์ ๋จ์๋ ฅ ฯ = ฯgRS
|
| 130 |
+
rho_water = 1000 # kg/mยณ
|
| 131 |
+
g = 9.81
|
| 132 |
+
self.shear_stress = rho_water * g * self.depth * slope
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
class StreamPowerErosion:
|
| 136 |
+
"""Stream Power Law ๊ธฐ๋ฐ ์นจ์
|
| 137 |
+
|
| 138 |
+
E = K * A^m * S^n
|
| 139 |
+
- E: ์นจ์๋ฅ (m/yr)
|
| 140 |
+
- K: ์นจ์ ๊ณ์ (์์ ํน์ฑ ๋ฐ์)
|
| 141 |
+
- A: ์ ์ญ ๋ฉด์ (โ ์ ๋)
|
| 142 |
+
- S: ๊ฒฝ์ฌ
|
| 143 |
+
- m: ๋ฉด์ ์ง์ (typically 0.3-0.6)
|
| 144 |
+
- n: ๊ฒฝ์ฌ ์ง์ (typically 1.0-2.0)
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
def __init__(self, K: float = 1e-5, m: float = 0.5, n: float = 1.0):
|
| 148 |
+
self.K = K
|
| 149 |
+
self.m = m
|
| 150 |
+
self.n = n
|
| 151 |
+
|
| 152 |
+
def calculate_erosion(self, terrain: TerrainGrid, water: WaterFlow, dt: float = 1.0) -> np.ndarray:
|
| 153 |
+
"""์นจ์๋ ๊ณ์ฐ"""
|
| 154 |
+
slope = terrain.get_slope()
|
| 155 |
+
|
| 156 |
+
# Stream Power Law
|
| 157 |
+
# K๋ ์์ ๊ฒฝ๋์ ๋ฐ๋น๋ก
|
| 158 |
+
effective_K = self.K * (1 - terrain.rock_hardness * 0.9)
|
| 159 |
+
|
| 160 |
+
erosion_rate = effective_K * np.power(water.discharge, self.m) * np.power(slope + 0.001, self.n)
|
| 161 |
+
|
| 162 |
+
erosion = erosion_rate * dt
|
| 163 |
+
|
| 164 |
+
# ๊ธฐ๋ฐ์ ์ดํ๋ก ์นจ์ ๋ถ๊ฐ
|
| 165 |
+
max_erosion = terrain.elevation - terrain.bedrock
|
| 166 |
+
erosion = np.minimum(erosion, np.maximum(max_erosion, 0))
|
| 167 |
+
|
| 168 |
+
return np.clip(erosion, 0, 5.0) # ์ฐ๊ฐ ์ต๋ 5m
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
class HillslopeProcess:
|
| 172 |
+
"""์ฌ๋ฉด ํ๋ก์ธ์ค (Mass Wasting)
|
| 173 |
+
|
| 174 |
+
V์๊ณก ํ์ฑ์ ํต์ฌ - ํ๋ฐฉ ์นจ์ ํ ์ฌ๋ฉด ๋ถ๊ดด
|
| 175 |
+
"""
|
| 176 |
+
|
| 177 |
+
def __init__(self, critical_slope: float = 0.7, diffusion_rate: float = 0.01):
|
| 178 |
+
self.critical_slope = critical_slope # ์๊ณ ๊ฒฝ์ฌ (tan ฮธ)
|
| 179 |
+
self.diffusion_rate = diffusion_rate # ํ์ฐ ๊ณ์
|
| 180 |
+
|
| 181 |
+
def mass_wasting(self, terrain: TerrainGrid, dt: float = 1.0) -> np.ndarray:
|
| 182 |
+
"""์ฌ๋ฉด ๋ถ๊ดด (๊ธ๊ฒฝ์ฌ โ ๋ฌผ์ง ์ด๋)"""
|
| 183 |
+
h, w = terrain.height, terrain.width
|
| 184 |
+
change = np.zeros((h, w))
|
| 185 |
+
|
| 186 |
+
elev = terrain.elevation
|
| 187 |
+
slope = terrain.get_slope()
|
| 188 |
+
|
| 189 |
+
# ์๊ณ ๊ฒฝ์ฌ ์ด๊ณผ ์ง์
|
| 190 |
+
unstable = slope > self.critical_slope
|
| 191 |
+
|
| 192 |
+
# ๋ถ์์ ์ง์ ์์ ์ด์์ผ๋ก ๋ฌผ์ง ๋ถ๋ฐฐ
|
| 193 |
+
for y in range(1, h-1):
|
| 194 |
+
for x in range(1, w-1):
|
| 195 |
+
if not unstable[y, x]:
|
| 196 |
+
continue
|
| 197 |
+
|
| 198 |
+
current = elev[y, x]
|
| 199 |
+
excess = (slope[y, x] - self.critical_slope) * terrain.cell_size
|
| 200 |
+
|
| 201 |
+
# 8๋ฐฉํฅ ์ด์ ์ค ๋ฎ์ ๊ณณ์ผ๋ก ๋ถ๋ฐฐ
|
| 202 |
+
neighbors = [(y-1,x), (y+1,x), (y,x-1), (y,x+1)]
|
| 203 |
+
lower_neighbors = [(ny, nx) for ny, nx in neighbors
|
| 204 |
+
if elev[ny, nx] < current]
|
| 205 |
+
|
| 206 |
+
if lower_neighbors:
|
| 207 |
+
transfer = excess * 0.2 * dt # ์ ๋ฌ๋
|
| 208 |
+
change[y, x] -= transfer
|
| 209 |
+
per_neighbor = transfer / len(lower_neighbors)
|
| 210 |
+
for ny, nx in lower_neighbors:
|
| 211 |
+
change[ny, nx] += per_neighbor
|
| 212 |
+
|
| 213 |
+
return change
|
| 214 |
+
|
| 215 |
+
def soil_creep(self, terrain: TerrainGrid, dt: float = 1.0) -> np.ndarray:
|
| 216 |
+
"""ํ ์ ํฌ๋ฆฌํ (๋๋ฆฐ ํ์ฐ)"""
|
| 217 |
+
# ๋ผํ๋ผ์์ ํ์ฐ
|
| 218 |
+
laplacian = (
|
| 219 |
+
np.roll(terrain.elevation, 1, axis=0) +
|
| 220 |
+
np.roll(terrain.elevation, -1, axis=0) +
|
| 221 |
+
np.roll(terrain.elevation, 1, axis=1) +
|
| 222 |
+
np.roll(terrain.elevation, -1, axis=1) -
|
| 223 |
+
4 * terrain.elevation
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
return self.diffusion_rate * laplacian * dt
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
class VValleySimulation:
|
| 230 |
+
"""V์๊ณก ์๋ฎฌ๋ ์ด์
- ์ค์ ๋ฌผ๋ฆฌ ๊ธฐ๋ฐ
|
| 231 |
+
|
| 232 |
+
ํ๋ก์ธ์ค:
|
| 233 |
+
1. ๊ฐ์ โ ์ ์ถ (D8 flow accumulation)
|
| 234 |
+
2. Stream Power Law ์นจ์
|
| 235 |
+
3. ์ฌ๋ฉด ๋ถ๊ดด (Mass Wasting)
|
| 236 |
+
4. ํ ์ ํฌ๋ฆฌํ
|
| 237 |
+
"""
|
| 238 |
+
|
| 239 |
+
def __init__(self, width: int = 100, height: int = 100):
|
| 240 |
+
self.terrain = TerrainGrid(width=width, height=height)
|
| 241 |
+
self.water = WaterFlow(terrain=self.terrain)
|
| 242 |
+
self.erosion = StreamPowerErosion()
|
| 243 |
+
self.hillslope = HillslopeProcess()
|
| 244 |
+
|
| 245 |
+
self.history: List[np.ndarray] = []
|
| 246 |
+
self.time = 0.0
|
| 247 |
+
|
| 248 |
+
def initialize_terrain(self, max_elevation: float = 500.0,
|
| 249 |
+
initial_channel_depth: float = 10.0,
|
| 250 |
+
rock_hardness: float = 0.5):
|
| 251 |
+
"""์ด๊ธฐ ์งํ ์ค์ """
|
| 252 |
+
h, w = self.terrain.height, self.terrain.width
|
| 253 |
+
|
| 254 |
+
# ๋ถโ๋จ ๊ฒฝ์ฌ
|
| 255 |
+
for y in range(h):
|
| 256 |
+
base = max_elevation * (1 - y / h)
|
| 257 |
+
self.terrain.elevation[y, :] = base
|
| 258 |
+
|
| 259 |
+
# ์ค์์ ์ด๊ธฐ ํ์ฒ ์ฑ๋
|
| 260 |
+
center = w // 2
|
| 261 |
+
for x in range(center - 3, center + 4):
|
| 262 |
+
if 0 <= x < w:
|
| 263 |
+
depth = initial_channel_depth * (1 - abs(x - center) / 4)
|
| 264 |
+
self.terrain.elevation[:, x] -= depth
|
| 265 |
+
|
| 266 |
+
# ์์ ๊ฒฝ๋
|
| 267 |
+
self.terrain.rock_hardness[:] = rock_hardness
|
| 268 |
+
|
| 269 |
+
# ๊ธฐ๋ฐ์
|
| 270 |
+
self.terrain.bedrock[:] = self.terrain.elevation.min() - 200
|
| 271 |
+
|
| 272 |
+
self.history = [self.terrain.elevation.copy()]
|
| 273 |
+
self.time = 0.0
|
| 274 |
+
|
| 275 |
+
def step(self, dt: float = 1.0, precipitation: float = 0.001):
|
| 276 |
+
"""1 ํ์์คํ
์งํ"""
|
| 277 |
+
# 1. ์๋ฌธ ๊ณ์ฐ
|
| 278 |
+
self.water.flow_accumulation_d8(precipitation)
|
| 279 |
+
self.water.calculate_hydraulics()
|
| 280 |
+
|
| 281 |
+
# 2. Stream Power ์นจ์
|
| 282 |
+
erosion = self.erosion.calculate_erosion(self.terrain, self.water, dt)
|
| 283 |
+
self.terrain.elevation -= erosion
|
| 284 |
+
|
| 285 |
+
# 3. ์ฌ๋ฉด ๋ถ๊ดด
|
| 286 |
+
wasting = self.hillslope.mass_wasting(self.terrain, dt)
|
| 287 |
+
self.terrain.elevation += wasting
|
| 288 |
+
|
| 289 |
+
# 4. ํ ์ ํฌ๋ฆฌํ
|
| 290 |
+
creep = self.hillslope.soil_creep(self.terrain, dt)
|
| 291 |
+
self.terrain.elevation += creep
|
| 292 |
+
|
| 293 |
+
self.time += dt
|
| 294 |
+
|
| 295 |
+
def run(self, total_time: float, save_interval: float = 100.0, dt: float = 1.0):
|
| 296 |
+
"""์๋ฎฌ๋ ์ด์
์คํ ๋ฐ ํ์คํ ๋ฆฌ ์ ์ฅ"""
|
| 297 |
+
steps = int(total_time / dt)
|
| 298 |
+
save_every = int(save_interval / dt)
|
| 299 |
+
|
| 300 |
+
for i in range(steps):
|
| 301 |
+
self.step(dt)
|
| 302 |
+
if (i + 1) % save_every == 0:
|
| 303 |
+
self.history.append(self.terrain.elevation.copy())
|
| 304 |
+
|
| 305 |
+
return self.history
|
| 306 |
+
|
| 307 |
+
def get_cross_section(self, y_position: int = None) -> Tuple[np.ndarray, np.ndarray]:
|
| 308 |
+
"""๋จ๋ฉด ์ถ์ถ"""
|
| 309 |
+
if y_position is None:
|
| 310 |
+
y_position = self.terrain.height // 2
|
| 311 |
+
|
| 312 |
+
x = np.arange(self.terrain.width) * self.terrain.cell_size
|
| 313 |
+
z = self.terrain.elevation[y_position, :]
|
| 314 |
+
|
| 315 |
+
return x, z
|
| 316 |
+
|
| 317 |
+
def measure_valley_depth(self) -> float:
|
| 318 |
+
"""V์๊ณก ๊น์ด ์ธก์ """
|
| 319 |
+
center = self.terrain.width // 2
|
| 320 |
+
y_mid = self.terrain.height // 2
|
| 321 |
+
|
| 322 |
+
# ์ค์๊ณผ ์์ชฝ 20์
๋จ์ด์ง ๊ณณ์ ๊ณ ๋ ์ฐจ์ด
|
| 323 |
+
left = self.terrain.elevation[y_mid, max(0, center-20)]
|
| 324 |
+
right = self.terrain.elevation[y_mid, min(self.terrain.width-1, center+20)]
|
| 325 |
+
center_elev = self.terrain.elevation[y_mid, center]
|
| 326 |
+
|
| 327 |
+
return max(0, (left + right) / 2 - center_elev)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
# ํ๋ฆฌ์ปดํจํ
ํจ์
|
| 331 |
+
def precompute_v_valley(max_time: int = 10000,
|
| 332 |
+
rock_hardness: float = 0.5,
|
| 333 |
+
K: float = 1e-5,
|
| 334 |
+
precipitation: float = 0.001,
|
| 335 |
+
save_every: int = 100) -> List[np.ndarray]:
|
| 336 |
+
"""V์๊ณก ์๋ฎฌ๋ ์ด์
ํ๋ฆฌ์ปดํจํ
"""
|
| 337 |
+
sim = VValleySimulation(width=100, height=100)
|
| 338 |
+
sim.erosion.K = K
|
| 339 |
+
sim.initialize_terrain(rock_hardness=rock_hardness)
|
| 340 |
+
|
| 341 |
+
history = sim.run(
|
| 342 |
+
total_time=max_time,
|
| 343 |
+
save_interval=save_every,
|
| 344 |
+
dt=1.0
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
return history
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
if __name__ == "__main__":
|
| 351 |
+
print("V์๊ณก ๋ฌผ๋ฆฌ ์๋ฎฌ๋ ์ด์
ํ
์คํธ")
|
| 352 |
+
print("=" * 50)
|
| 353 |
+
|
| 354 |
+
sim = VValleySimulation()
|
| 355 |
+
sim.initialize_terrain(rock_hardness=0.3)
|
| 356 |
+
|
| 357 |
+
print(f"์ด๊ธฐ ์ํ: ๊น์ด = {sim.measure_valley_depth():.1f}m")
|
| 358 |
+
|
| 359 |
+
for year in [1000, 2000, 5000, 10000]:
|
| 360 |
+
sim.run(1000, save_interval=1000)
|
| 361 |
+
depth = sim.measure_valley_depth()
|
| 362 |
+
print(f"Year {year}: ๊น์ด = {depth:.1f}m")
|
| 363 |
+
|
| 364 |
+
print("=" * 50)
|
| 365 |
+
print("ํ
์คํธ ์๋ฃ!")
|
engine/precompute.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI: ํ๋ฆฌ์ปดํจํ
+ ์บ์ฑ ์์คํ
|
| 3 |
+
์๋ฎฌ๋ ์ด์
์ ๋ฏธ๋ฆฌ ๋๋ ค๋๊ณ ์ฌ๋ผ์ด๋๋ก ํ์
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pickle
|
| 7 |
+
import hashlib
|
| 8 |
+
import os
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from typing import Dict, Any, Optional, Callable
|
| 11 |
+
import threading
|
| 12 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class PrecomputeCache:
|
| 16 |
+
"""ํ๋ฆฌ์ปดํจํ
๊ฒฐ๊ณผ ์บ์"""
|
| 17 |
+
|
| 18 |
+
def __init__(self, cache_dir: str = None):
|
| 19 |
+
if cache_dir is None:
|
| 20 |
+
cache_dir = Path(__file__).parent.parent / "cache"
|
| 21 |
+
self.cache_dir = Path(cache_dir)
|
| 22 |
+
self.cache_dir.mkdir(exist_ok=True)
|
| 23 |
+
|
| 24 |
+
# ๋ฉ๋ชจ๋ฆฌ ์บ์
|
| 25 |
+
self.memory_cache: Dict[str, Any] = {}
|
| 26 |
+
|
| 27 |
+
def _get_key(self, sim_type: str, params: dict) -> str:
|
| 28 |
+
"""ํ๋ผ๋ฏธํฐ ๊ธฐ๋ฐ ์บ์ ํค ์์ฑ"""
|
| 29 |
+
param_str = f"{sim_type}_{sorted(params.items())}"
|
| 30 |
+
return hashlib.md5(param_str.encode()).hexdigest()[:16]
|
| 31 |
+
|
| 32 |
+
def get(self, sim_type: str, params: dict) -> Optional[Any]:
|
| 33 |
+
"""์บ์์์ ์กฐํ"""
|
| 34 |
+
key = self._get_key(sim_type, params)
|
| 35 |
+
|
| 36 |
+
# ๋ฉ๋ชจ๋ฆฌ ์บ์ ํ์ธ
|
| 37 |
+
if key in self.memory_cache:
|
| 38 |
+
return self.memory_cache[key]
|
| 39 |
+
|
| 40 |
+
# ๋์คํฌ ์บ์ ํ์ธ
|
| 41 |
+
cache_file = self.cache_dir / f"{key}.pkl"
|
| 42 |
+
if cache_file.exists():
|
| 43 |
+
try:
|
| 44 |
+
with open(cache_file, 'rb') as f:
|
| 45 |
+
data = pickle.load(f)
|
| 46 |
+
self.memory_cache[key] = data
|
| 47 |
+
return data
|
| 48 |
+
except:
|
| 49 |
+
pass
|
| 50 |
+
|
| 51 |
+
return None
|
| 52 |
+
|
| 53 |
+
def set(self, sim_type: str, params: dict, data: Any):
|
| 54 |
+
"""์บ์์ ์ ์ฅ"""
|
| 55 |
+
key = self._get_key(sim_type, params)
|
| 56 |
+
|
| 57 |
+
# ๋ฉ๋ชจ๋ฆฌ ์บ์
|
| 58 |
+
self.memory_cache[key] = data
|
| 59 |
+
|
| 60 |
+
# ๋์คํฌ ์บ์
|
| 61 |
+
cache_file = self.cache_dir / f"{key}.pkl"
|
| 62 |
+
try:
|
| 63 |
+
with open(cache_file, 'wb') as f:
|
| 64 |
+
pickle.dump(data, f)
|
| 65 |
+
except:
|
| 66 |
+
pass
|
| 67 |
+
|
| 68 |
+
def get_or_compute(self, sim_type: str, params: dict,
|
| 69 |
+
compute_fn: Callable, force_recompute: bool = False) -> Any:
|
| 70 |
+
"""์บ์ ๋๋ ๊ณ์ฐ"""
|
| 71 |
+
if not force_recompute:
|
| 72 |
+
cached = self.get(sim_type, params)
|
| 73 |
+
if cached is not None:
|
| 74 |
+
return cached
|
| 75 |
+
|
| 76 |
+
# ๊ณ์ฐ
|
| 77 |
+
result = compute_fn()
|
| 78 |
+
self.set(sim_type, params, result)
|
| 79 |
+
return result
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class SimulationManager:
|
| 83 |
+
"""์๋ฎฌ๋ ์ด์
๋งค๋์
|
| 84 |
+
|
| 85 |
+
ํ๋ผ๋ฏธํฐ๋ณ ์๋ฎฌ๋ ์ด์
์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ํ๋ฆฌ์ปดํจํ
ํ๊ณ
|
| 86 |
+
UI์์๋ ์บ์๋ ๊ฒฐ๊ณผ๋ฅผ ์กฐํ
|
| 87 |
+
"""
|
| 88 |
+
|
| 89 |
+
def __init__(self):
|
| 90 |
+
self.cache = PrecomputeCache()
|
| 91 |
+
self.executor = ThreadPoolExecutor(max_workers=2)
|
| 92 |
+
self.computing: Dict[str, bool] = {}
|
| 93 |
+
|
| 94 |
+
def get_v_valley(self, rock_hardness: float = 0.5,
|
| 95 |
+
K: float = 1e-5,
|
| 96 |
+
max_time: int = 10000) -> Dict:
|
| 97 |
+
"""V์๊ณก ์๋ฎฌ๋ ์ด์
๊ฒฐ๊ณผ"""
|
| 98 |
+
from engine.physics_engine import VValleySimulation
|
| 99 |
+
|
| 100 |
+
# ํ๋ผ๋ฏธํฐ ์์ํ (์บ์ ํจ์จ)
|
| 101 |
+
rock_hardness = round(rock_hardness, 1)
|
| 102 |
+
K = round(K, 6)
|
| 103 |
+
|
| 104 |
+
params = {
|
| 105 |
+
'rock_hardness': rock_hardness,
|
| 106 |
+
'K': K,
|
| 107 |
+
'max_time': max_time
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
def compute():
|
| 111 |
+
sim = VValleySimulation(width=100, height=100)
|
| 112 |
+
sim.erosion.K = K
|
| 113 |
+
sim.initialize_terrain(rock_hardness=rock_hardness)
|
| 114 |
+
history = sim.run(max_time, save_interval=max_time // 100)
|
| 115 |
+
|
| 116 |
+
# ๊ฐ ์ค๋
์ท์ ๋จ๋ฉด๊ณผ ๊น์ด ์ ์ฅ
|
| 117 |
+
cross_sections = []
|
| 118 |
+
depths = []
|
| 119 |
+
for elev in history:
|
| 120 |
+
temp_sim = VValleySimulation()
|
| 121 |
+
temp_sim.terrain.elevation = elev
|
| 122 |
+
x, z = temp_sim.get_cross_section()
|
| 123 |
+
depth = temp_sim.measure_valley_depth()
|
| 124 |
+
cross_sections.append((x, z))
|
| 125 |
+
depths.append(depth)
|
| 126 |
+
|
| 127 |
+
return {
|
| 128 |
+
'history': history,
|
| 129 |
+
'cross_sections': cross_sections,
|
| 130 |
+
'depths': depths,
|
| 131 |
+
'n_frames': len(history)
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
return self.cache.get_or_compute('v_valley', params, compute)
|
| 135 |
+
|
| 136 |
+
def get_meander(self, initial_sinuosity: float = 1.3,
|
| 137 |
+
max_time: int = 10000) -> Dict:
|
| 138 |
+
"""๊ณก๋ฅ ์๋ฎฌ๋ ์ด์
๊ฒฐ๊ณผ"""
|
| 139 |
+
from engine.meander_physics import MeanderSimulation
|
| 140 |
+
|
| 141 |
+
initial_sinuosity = round(initial_sinuosity, 1)
|
| 142 |
+
|
| 143 |
+
params = {
|
| 144 |
+
'initial_sinuosity': initial_sinuosity,
|
| 145 |
+
'max_time': max_time
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
def compute():
|
| 149 |
+
sim = MeanderSimulation(initial_sinuosity=initial_sinuosity)
|
| 150 |
+
history = sim.run(max_time, save_interval=max_time // 100)
|
| 151 |
+
|
| 152 |
+
# ๊ตด๊ณก๋ ํ์คํ ๋ฆฌ
|
| 153 |
+
sinuosities = []
|
| 154 |
+
for x, y in history:
|
| 155 |
+
temp_channel = type(sim.channel)(x=x, y=y)
|
| 156 |
+
sinuosities.append(temp_channel.calculate_sinuosity())
|
| 157 |
+
|
| 158 |
+
return {
|
| 159 |
+
'history': history,
|
| 160 |
+
'oxbow_lakes': sim.oxbow_lakes,
|
| 161 |
+
'sinuosities': sinuosities,
|
| 162 |
+
'n_frames': len(history)
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
return self.cache.get_or_compute('meander', params, compute)
|
| 166 |
+
|
| 167 |
+
def get_delta(self, river_energy: float = 60,
|
| 168 |
+
wave_energy: float = 25,
|
| 169 |
+
tidal_energy: float = 15,
|
| 170 |
+
max_time: int = 10000) -> Dict:
|
| 171 |
+
"""์ผ๊ฐ์ฃผ ์๋ฎฌ๋ ์ด์
๊ฒฐ๊ณผ"""
|
| 172 |
+
from engine.delta_physics import DeltaSimulation
|
| 173 |
+
|
| 174 |
+
# ์ ๊ทํ
|
| 175 |
+
total = river_energy + wave_energy + tidal_energy + 0.01
|
| 176 |
+
river_energy = round(river_energy / total, 2)
|
| 177 |
+
wave_energy = round(wave_energy / total, 2)
|
| 178 |
+
tidal_energy = round(tidal_energy / total, 2)
|
| 179 |
+
|
| 180 |
+
params = {
|
| 181 |
+
'river_energy': river_energy,
|
| 182 |
+
'wave_energy': wave_energy,
|
| 183 |
+
'tidal_energy': tidal_energy,
|
| 184 |
+
'max_time': max_time
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
def compute():
|
| 188 |
+
sim = DeltaSimulation()
|
| 189 |
+
sim.set_energy_balance(river_energy, wave_energy, tidal_energy)
|
| 190 |
+
history = sim.run(max_time, save_interval=max_time // 100)
|
| 191 |
+
|
| 192 |
+
return {
|
| 193 |
+
'history': history,
|
| 194 |
+
'delta_type': sim.get_delta_type().value,
|
| 195 |
+
'delta_area': sim.get_delta_area(),
|
| 196 |
+
'n_frames': len(history)
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
return self.cache.get_or_compute('delta', params, compute)
|
| 200 |
+
|
| 201 |
+
def precompute_common_scenarios(self):
|
| 202 |
+
"""์์ฃผ ์ฌ์ฉ๋๋ ์๋๋ฆฌ์ค ๋ฏธ๋ฆฌ ๊ณ์ฐ"""
|
| 203 |
+
scenarios = [
|
| 204 |
+
# V์๊ณก
|
| 205 |
+
{'type': 'v_valley', 'rock_hardness': 0.3, 'K': 1e-5},
|
| 206 |
+
{'type': 'v_valley', 'rock_hardness': 0.5, 'K': 1e-5},
|
| 207 |
+
{'type': 'v_valley', 'rock_hardness': 0.7, 'K': 1e-5},
|
| 208 |
+
# ๊ณก๋ฅ
|
| 209 |
+
{'type': 'meander', 'initial_sinuosity': 1.2},
|
| 210 |
+
{'type': 'meander', 'initial_sinuosity': 1.5},
|
| 211 |
+
# ์ผ๊ฐ์ฃผ
|
| 212 |
+
{'type': 'delta', 'river': 0.7, 'wave': 0.2, 'tidal': 0.1},
|
| 213 |
+
{'type': 'delta', 'river': 0.3, 'wave': 0.5, 'tidal': 0.2},
|
| 214 |
+
{'type': 'delta', 'river': 0.2, 'wave': 0.2, 'tidal': 0.6},
|
| 215 |
+
]
|
| 216 |
+
|
| 217 |
+
for scenario in scenarios:
|
| 218 |
+
if scenario['type'] == 'v_valley':
|
| 219 |
+
self.executor.submit(
|
| 220 |
+
self.get_v_valley,
|
| 221 |
+
rock_hardness=scenario['rock_hardness'],
|
| 222 |
+
K=scenario['K']
|
| 223 |
+
)
|
| 224 |
+
elif scenario['type'] == 'meander':
|
| 225 |
+
self.executor.submit(
|
| 226 |
+
self.get_meander,
|
| 227 |
+
initial_sinuosity=scenario['initial_sinuosity']
|
| 228 |
+
)
|
| 229 |
+
elif scenario['type'] == 'delta':
|
| 230 |
+
self.executor.submit(
|
| 231 |
+
self.get_delta,
|
| 232 |
+
river_energy=scenario['river'],
|
| 233 |
+
wave_energy=scenario['wave'],
|
| 234 |
+
tidal_energy=scenario['tidal']
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
# ๊ธ๋ก๋ฒ ์ธ์คํด์ค
|
| 239 |
+
_manager = None
|
| 240 |
+
|
| 241 |
+
def get_simulation_manager() -> SimulationManager:
|
| 242 |
+
global _manager
|
| 243 |
+
if _manager is None:
|
| 244 |
+
_manager = SimulationManager()
|
| 245 |
+
return _manager
|
engine/pyvista_render.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PyVista ๊ธฐ๋ฐ ๊ณ ํ์ง 3D ์งํ ๋ ๋๋ง
|
| 3 |
+
- ์ง์ง 3D ๋ ๋๋ง
|
| 4 |
+
- ํ
์ค์ฒ ๋งคํ
|
| 5 |
+
- ์กฐ๋ช
, ๊ทธ๋ฆผ์ ํจ๊ณผ
|
| 6 |
+
"""
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
import pyvista as pv
|
| 11 |
+
PYVISTA_AVAILABLE = True
|
| 12 |
+
except ImportError:
|
| 13 |
+
PYVISTA_AVAILABLE = False
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def create_terrain_mesh(elevation: np.ndarray, x_scale: float = 1.0, y_scale: float = 1.0, z_scale: float = 1.0):
|
| 17 |
+
"""๊ณ ๋ ๋ฐฐ์ด์ PyVista ๋ฉ์๋ก ๋ณํ"""
|
| 18 |
+
if not PYVISTA_AVAILABLE:
|
| 19 |
+
raise ImportError("PyVista is not installed")
|
| 20 |
+
|
| 21 |
+
h, w = elevation.shape
|
| 22 |
+
x = np.arange(w) * x_scale
|
| 23 |
+
y = np.arange(h) * y_scale
|
| 24 |
+
X, Y = np.meshgrid(x, y)
|
| 25 |
+
Z = elevation * z_scale
|
| 26 |
+
|
| 27 |
+
grid = pv.StructuredGrid(X, Y, Z)
|
| 28 |
+
grid["elevation"] = Z.flatten(order="F")
|
| 29 |
+
|
| 30 |
+
return grid
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def create_interactive_plotter(elevation: np.ndarray, title: str = "์งํ",
|
| 34 |
+
x_scale: float = 1.0, y_scale: float = 1.0, z_scale: float = 1.0):
|
| 35 |
+
"""์ธํฐ๋ํฐ๋ธ ํ์ ๊ฐ๋ฅํ PyVista ํ๋กํฐ ์์ฑ (stpyvista์ฉ)"""
|
| 36 |
+
if not PYVISTA_AVAILABLE:
|
| 37 |
+
return None
|
| 38 |
+
|
| 39 |
+
mesh = create_terrain_mesh(elevation, x_scale, y_scale, z_scale)
|
| 40 |
+
|
| 41 |
+
plotter = pv.Plotter(window_size=[800, 600])
|
| 42 |
+
plotter.set_background("#1a1a2e")
|
| 43 |
+
|
| 44 |
+
# ๋จ์ผ ์์ (copper)
|
| 45 |
+
plotter.add_mesh(
|
| 46 |
+
mesh,
|
| 47 |
+
scalars="elevation",
|
| 48 |
+
cmap="copper",
|
| 49 |
+
lighting=True,
|
| 50 |
+
smooth_shading=True,
|
| 51 |
+
show_scalar_bar=True,
|
| 52 |
+
scalar_bar_args={
|
| 53 |
+
"title": "๊ณ ๋ (m)",
|
| 54 |
+
"color": "white"
|
| 55 |
+
},
|
| 56 |
+
specular=0.3,
|
| 57 |
+
specular_power=15
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
# ์กฐ๋ช
|
| 61 |
+
plotter.remove_all_lights()
|
| 62 |
+
plotter.add_light(pv.Light(position=(1000, 1000, 2000), intensity=1.2))
|
| 63 |
+
plotter.add_light(pv.Light(position=(-500, -500, 1000), intensity=0.5))
|
| 64 |
+
|
| 65 |
+
plotter.add_text(title, font_size=14, position="upper_left", color="white")
|
| 66 |
+
|
| 67 |
+
return plotter
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def render_v_valley_pyvista(elevation: np.ndarray, depth: float):
|
| 71 |
+
"""V์๊ณก PyVista ๋ ๋๋ง - ๋จ์ผ ์์ ๋ช
๋ ๋ณํ"""
|
| 72 |
+
if not PYVISTA_AVAILABLE:
|
| 73 |
+
return None
|
| 74 |
+
|
| 75 |
+
# ๋ฉ์ ์์ฑ (์์ง ๊ณผ์ฅ 2๋ฐฐ)
|
| 76 |
+
mesh = create_terrain_mesh(elevation, x_scale=12.5, y_scale=12.5, z_scale=2.0)
|
| 77 |
+
|
| 78 |
+
# ํ๋กํฐ ์ค์
|
| 79 |
+
plotter = pv.Plotter(off_screen=True, window_size=[1200, 900])
|
| 80 |
+
plotter.set_background("#1a1a2e") # ์ด๋์ด ๋ฐฐ๊ฒฝ
|
| 81 |
+
|
| 82 |
+
# ๋จ์ผ ์์ (๊ฐ์ ๊ณ์ด - copper) ๋ช
๋ ๋ณํ
|
| 83 |
+
plotter.add_mesh(
|
| 84 |
+
mesh,
|
| 85 |
+
scalars="elevation",
|
| 86 |
+
cmap="copper", # ๊ฐ์ ๋จ์ผ ์์ ๋ช
๋ ๋ณํ
|
| 87 |
+
lighting=True,
|
| 88 |
+
smooth_shading=True,
|
| 89 |
+
show_scalar_bar=True,
|
| 90 |
+
scalar_bar_args={
|
| 91 |
+
"title": "๊ณ ๋ (m)",
|
| 92 |
+
"vertical": True,
|
| 93 |
+
"title_font_size": 14,
|
| 94 |
+
"label_font_size": 12
|
| 95 |
+
},
|
| 96 |
+
specular=0.3,
|
| 97 |
+
specular_power=15
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
# ํ์ฒ (๋ ์งํ ์์)
|
| 101 |
+
water_level = elevation.min() + 3
|
| 102 |
+
h, w = elevation.shape
|
| 103 |
+
x_water = np.arange(w) * 12.5
|
| 104 |
+
y_water = np.arange(h) * 12.5
|
| 105 |
+
X_w, Y_w = np.meshgrid(x_water, y_water)
|
| 106 |
+
Z_w = np.full_like(elevation, water_level, dtype=float)
|
| 107 |
+
water_mask = elevation < water_level
|
| 108 |
+
Z_w[~water_mask] = np.nan
|
| 109 |
+
|
| 110 |
+
if np.any(water_mask):
|
| 111 |
+
water_grid = pv.StructuredGrid(X_w, Y_w, Z_w)
|
| 112 |
+
plotter.add_mesh(water_grid, color="#2C3E50", opacity=0.9) # ์ด๋์ด ๋ฌผ
|
| 113 |
+
|
| 114 |
+
# ๋๋ผ๋งํฑํ ์นด๋ฉ๋ผ ๊ฐ๋
|
| 115 |
+
plotter.camera_position = [
|
| 116 |
+
(w * 12.5 * 1.5, h * 12.5 * 0.2, elevation.max() * 3),
|
| 117 |
+
(w * 12.5 * 0.5, h * 12.5 * 0.5, elevation.min()),
|
| 118 |
+
(0, 0, 1)
|
| 119 |
+
]
|
| 120 |
+
|
| 121 |
+
# ๊ฐํ ์กฐ๋ช
(๊ทธ๋ฆผ์ ํจ๊ณผ)
|
| 122 |
+
plotter.remove_all_lights()
|
| 123 |
+
key_light = pv.Light(position=(w*12.5*3, 0, elevation.max()*5), intensity=1.2)
|
| 124 |
+
fill_light = pv.Light(position=(-w*12.5, h*12.5*2, elevation.max()*2), intensity=0.4)
|
| 125 |
+
plotter.add_light(key_light)
|
| 126 |
+
plotter.add_light(fill_light)
|
| 127 |
+
|
| 128 |
+
plotter.add_text(f"V์๊ณก | ๊น์ด: {depth:.0f}m", font_size=16,
|
| 129 |
+
position="upper_left", color="white")
|
| 130 |
+
|
| 131 |
+
img = plotter.screenshot(return_img=True)
|
| 132 |
+
plotter.close()
|
| 133 |
+
|
| 134 |
+
return img
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def render_delta_pyvista(elevation: np.ndarray, delta_type: str, area: float):
|
| 138 |
+
"""์ผ๊ฐ์ฃผ PyVista ๋ ๋๋ง - ๋จ์ผ ์์"""
|
| 139 |
+
if not PYVISTA_AVAILABLE:
|
| 140 |
+
return None
|
| 141 |
+
|
| 142 |
+
# ๋ฉ์ ์์ฑ (์์ง ๊ณผ์ฅ)
|
| 143 |
+
mesh = create_terrain_mesh(elevation, x_scale=50, y_scale=50, z_scale=10.0)
|
| 144 |
+
|
| 145 |
+
plotter = pv.Plotter(off_screen=True, window_size=[1200, 900])
|
| 146 |
+
plotter.set_background("#0f0f23") # ์ด๋์ด ๋ฐฐ๊ฒฝ
|
| 147 |
+
|
| 148 |
+
# ๋จ์ผ ์์ (bone - ๋ฒ ์ด์ง/๊ฐ์ ๋ช
๋ ๋ณํ)
|
| 149 |
+
plotter.add_mesh(
|
| 150 |
+
mesh,
|
| 151 |
+
scalars="elevation",
|
| 152 |
+
cmap="bone", # ๋ฒ ์ด์ง ๋จ์ผ ์์ ๋ช
๋ ๋ณํ
|
| 153 |
+
lighting=True,
|
| 154 |
+
smooth_shading=True,
|
| 155 |
+
clim=[elevation.min(), elevation.max()],
|
| 156 |
+
show_scalar_bar=True,
|
| 157 |
+
scalar_bar_args={
|
| 158 |
+
"title": "๊ณ ๋ (m)",
|
| 159 |
+
"title_font_size": 14,
|
| 160 |
+
"label_font_size": 12
|
| 161 |
+
},
|
| 162 |
+
specular=0.2,
|
| 163 |
+
specular_power=10
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
# ํด์๋ฉด (์งํ ์์)
|
| 167 |
+
h, w = elevation.shape
|
| 168 |
+
x = np.arange(w) * 50
|
| 169 |
+
y = np.arange(h) * 50
|
| 170 |
+
X, Y = np.meshgrid(x, y)
|
| 171 |
+
Z_sea = np.zeros_like(elevation, dtype=float)
|
| 172 |
+
sea_mask = elevation < 0
|
| 173 |
+
Z_sea[~sea_mask] = np.nan
|
| 174 |
+
|
| 175 |
+
if np.any(sea_mask):
|
| 176 |
+
sea_grid = pv.StructuredGrid(X, Y, Z_sea)
|
| 177 |
+
plotter.add_mesh(sea_grid, color="#1a3a5f", opacity=0.85)
|
| 178 |
+
|
| 179 |
+
# ์นด๋ฉ๋ผ
|
| 180 |
+
plotter.camera_position = [
|
| 181 |
+
(w * 50 * 0.5, -h * 50 * 0.5, elevation.max() * 50),
|
| 182 |
+
(w * 50 * 0.5, h * 50 * 0.5, 0),
|
| 183 |
+
(0, 0, 1)
|
| 184 |
+
]
|
| 185 |
+
|
| 186 |
+
# ์กฐ๋ช
|
| 187 |
+
plotter.remove_all_lights()
|
| 188 |
+
plotter.add_light(pv.Light(position=(w*50*2, -h*50, 1000), intensity=1.3))
|
| 189 |
+
plotter.add_light(pv.Light(position=(-w*50, h*50*2, 500), intensity=0.5))
|
| 190 |
+
|
| 191 |
+
plotter.add_text(f"{delta_type} | ๋ฉด์ : {area:.2f} kmยฒ", font_size=16,
|
| 192 |
+
position="upper_left", color="white")
|
| 193 |
+
|
| 194 |
+
img = plotter.screenshot(return_img=True)
|
| 195 |
+
plotter.close()
|
| 196 |
+
|
| 197 |
+
return img
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def render_meander_pyvista(x: np.ndarray, y: np.ndarray, sinuosity: float, oxbow_lakes: list):
|
| 201 |
+
"""๊ณก๋ฅ ํ์ฒ PyVista ๋ ๋๋ง (2.5D)"""
|
| 202 |
+
if not PYVISTA_AVAILABLE:
|
| 203 |
+
return None
|
| 204 |
+
|
| 205 |
+
plotter = pv.Plotter(off_screen=True, window_size=[1400, 600])
|
| 206 |
+
|
| 207 |
+
# ๋ฒ๋์ (ํ๋ฉด)
|
| 208 |
+
floodplain = pv.Plane(
|
| 209 |
+
center=(x.mean(), y.mean(), -1),
|
| 210 |
+
direction=(0, 0, 1),
|
| 211 |
+
i_size=x.max() - x.min() + 200,
|
| 212 |
+
j_size=max(200, (y.max() - y.min()) * 3)
|
| 213 |
+
)
|
| 214 |
+
plotter.add_mesh(floodplain, color="#8FBC8F", opacity=0.8)
|
| 215 |
+
|
| 216 |
+
# ํ์ฒ (ํ๋ธ)
|
| 217 |
+
points = np.column_stack([x, y, np.zeros_like(x)])
|
| 218 |
+
spline = pv.Spline(points, 500)
|
| 219 |
+
tube = spline.tube(radius=8)
|
| 220 |
+
plotter.add_mesh(tube, color="#4169E1", smooth_shading=True)
|
| 221 |
+
|
| 222 |
+
# ์ฐ๊ฐํธ
|
| 223 |
+
for lake_x, lake_y in oxbow_lakes:
|
| 224 |
+
if len(lake_x) > 3:
|
| 225 |
+
lake_points = np.column_stack([lake_x, lake_y, np.full_like(lake_x, -0.5)])
|
| 226 |
+
lake_spline = pv.Spline(lake_points, 100)
|
| 227 |
+
lake_tube = lake_spline.tube(radius=6)
|
| 228 |
+
plotter.add_mesh(lake_tube, color="#87CEEB", opacity=0.8)
|
| 229 |
+
|
| 230 |
+
# ์นด๋ฉ๋ผ (์์์ ์ฝ๊ฐ ๊ธฐ์ธ์ด์ง ์์ )
|
| 231 |
+
plotter.camera_position = [
|
| 232 |
+
(x.mean(), y.mean() - 300, 400),
|
| 233 |
+
(x.mean(), y.mean(), 0),
|
| 234 |
+
(0, 0, 1)
|
| 235 |
+
]
|
| 236 |
+
|
| 237 |
+
# ์กฐ๋ช
|
| 238 |
+
plotter.add_light(pv.Light(position=(x.mean(), y.mean(), 500), intensity=1.0))
|
| 239 |
+
|
| 240 |
+
plotter.add_text(f"๊ณก๋ฅ ํ์ฒ (๊ตด๊ณก๋: {sinuosity:.2f})", font_size=14, position="upper_left")
|
| 241 |
+
|
| 242 |
+
img = plotter.screenshot(return_img=True)
|
| 243 |
+
plotter.close()
|
| 244 |
+
|
| 245 |
+
return img
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def save_pyvista_image(img: np.ndarray, filepath: str):
|
| 249 |
+
"""PyVista ๋ ๋๋ง ์ด๋ฏธ์ง ์ ์ฅ"""
|
| 250 |
+
from PIL import Image
|
| 251 |
+
if img is not None:
|
| 252 |
+
Image.fromarray(img).save(filepath)
|
| 253 |
+
return True
|
| 254 |
+
return False
|
engine/river/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# River Submodule
|
engine/river/delta.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI: ์ผ๊ฐ์ฃผ ์๋ฎฌ๋ ์ด์
|
| 3 |
+
ํ๋ฅ์์ 3๊ฐ์ง ์๋์ง ๊ท ํ์ ๋ฐ๋ฅธ ์ผ๊ฐ์ฃผ ํํ ํ์ฑ
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from typing import List, Tuple
|
| 8 |
+
from enum import Enum
|
| 9 |
+
|
| 10 |
+
import sys
|
| 11 |
+
import os
|
| 12 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
| 13 |
+
|
| 14 |
+
from engine.base import Terrain, Water
|
| 15 |
+
from engine.deposition import delta_deposition, apply_deposition
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class DeltaType(Enum):
|
| 19 |
+
"""์ผ๊ฐ์ฃผ ๋ถ๋ฅ (Galloway's Classification)"""
|
| 20 |
+
RIVER_DOMINATED = "์กฐ์กฑ์ (Bird's Foot)" # ๋ฏธ์์ํผํ
|
| 21 |
+
WAVE_DOMINATED = "์ํธ์ (Arcuate)" # ๋์ผํ
|
| 22 |
+
TIDE_DOMINATED = "์ฒจ๊ฐ์ (Cuspate)" # ํฐ๋ฒ ๋ฅดํ
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class DeltaSimulator:
|
| 27 |
+
"""์ผ๊ฐ์ฃผ ์๋ฎฌ๋ ์ด์
|
| 28 |
+
|
| 29 |
+
ํต์ฌ ์๋ฆฌ:
|
| 30 |
+
ํ์ฒ ์๋์ง vs ํ๋ ์๋์ง vs ์กฐ๋ฅ ์๋์ง์ ๊ท ํ
|
| 31 |
+
|
| 32 |
+
๊ฒฐ๊ณผ:
|
| 33 |
+
- ํ์ฒ ์ฐ์ธ: ๊ธธ๊ฒ ๋ป์ ์กฐ์กฑ์ (๋ฏธ์์ํผ๊ฐ)
|
| 34 |
+
- ํ๋ ์ฐ์ธ: ๋๊ฒ ํผ์ง ์ํธ์ (๋์ผ๊ฐ)
|
| 35 |
+
- ์กฐ๋ฅ ์ฐ์ธ: ๋พฐ์กฑํ ์ฒจ๊ฐ์ (ํฐ๋ฒ ๋ฅด๊ฐ)
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
# ์งํ ํฌ๊ธฐ
|
| 39 |
+
width: int = 120
|
| 40 |
+
height: int = 100
|
| 41 |
+
|
| 42 |
+
# ์๋์ง ํ๋ผ๋ฏธํฐ (0-100)
|
| 43 |
+
river_energy: float = 50.0
|
| 44 |
+
wave_energy: float = 30.0
|
| 45 |
+
tidal_energy: float = 20.0
|
| 46 |
+
|
| 47 |
+
# ํ๊ฒฝ
|
| 48 |
+
sea_level: float = 10.0
|
| 49 |
+
sediment_supply: float = 1.0 # ํด์ ๋ฌผ ๊ณต๊ธ๋ ๊ณ์
|
| 50 |
+
|
| 51 |
+
# ๋ด๋ถ ์ํ
|
| 52 |
+
terrain: Terrain = field(default=None)
|
| 53 |
+
water: Water = field(default=None)
|
| 54 |
+
delta_mask: np.ndarray = field(default=None) # ์ผ๊ฐ์ฃผ ์์ญ
|
| 55 |
+
history: List[np.ndarray] = field(default_factory=list)
|
| 56 |
+
current_step: int = 0
|
| 57 |
+
|
| 58 |
+
def __post_init__(self):
|
| 59 |
+
self.reset()
|
| 60 |
+
|
| 61 |
+
def reset(self):
|
| 62 |
+
"""์๋ฎฌ๋ ์ด์
์ด๊ธฐํ"""
|
| 63 |
+
self.terrain = Terrain(width=self.width, height=self.height)
|
| 64 |
+
|
| 65 |
+
# ์งํ ์ค์ : ์๋ฅ(์ก์ง) โ ํ๋ฅ(๋ฐ๋ค)
|
| 66 |
+
for y in range(self.height):
|
| 67 |
+
if y < self.height * 0.4:
|
| 68 |
+
# ์ก์ง (๊ฒฝ์ฌ)
|
| 69 |
+
elev = 50 - y * 0.5
|
| 70 |
+
else:
|
| 71 |
+
# ๋ฐ๋ค (ํด์๋ฉด ์๋)
|
| 72 |
+
elev = self.sea_level - (y - self.height * 0.4) * 0.3
|
| 73 |
+
self.terrain.elevation[y, :] = elev
|
| 74 |
+
|
| 75 |
+
# ์ค์์ ํ์ฒ ์ฑ๋
|
| 76 |
+
center = self.width // 2
|
| 77 |
+
for y in range(int(self.height * 0.5)):
|
| 78 |
+
for dx in range(-3, 4):
|
| 79 |
+
x = center + dx
|
| 80 |
+
if 0 <= x < self.width:
|
| 81 |
+
self.terrain.elevation[y, x] -= 5
|
| 82 |
+
|
| 83 |
+
# ์๋ฌธ ์ด๊ธฐํ
|
| 84 |
+
self.water = Water(terrain=self.terrain)
|
| 85 |
+
self.water.discharge[0, center] = 100 # ์๋ฅ ์ ์
|
| 86 |
+
self.water.accumulate_flow()
|
| 87 |
+
|
| 88 |
+
self.delta_mask = np.zeros((self.height, self.width), dtype=bool)
|
| 89 |
+
self.history = [self.terrain.elevation.copy()]
|
| 90 |
+
self.current_step = 0
|
| 91 |
+
|
| 92 |
+
def set_energy_balance(self, river: float, wave: float, tidal: float):
|
| 93 |
+
"""์๋์ง ๊ท ํ ์ค์ (0-100)"""
|
| 94 |
+
self.river_energy = max(0, min(100, river))
|
| 95 |
+
self.wave_energy = max(0, min(100, wave))
|
| 96 |
+
self.tidal_energy = max(0, min(100, tidal))
|
| 97 |
+
|
| 98 |
+
def step(self, n_steps: int = 1) -> np.ndarray:
|
| 99 |
+
"""์๋ฎฌ๋ ์ด์
n์คํ
์งํ"""
|
| 100 |
+
for _ in range(n_steps):
|
| 101 |
+
# 1. ์ ๋ ๊ณ์ฐ
|
| 102 |
+
self.water.accumulate_flow()
|
| 103 |
+
|
| 104 |
+
# 2. ์ผ๊ฐ์ฃผ ํด์
|
| 105 |
+
deposition = self._calculate_delta_deposition()
|
| 106 |
+
apply_deposition(self.terrain, deposition)
|
| 107 |
+
|
| 108 |
+
# 3. ํ๋/์กฐ๋ฅ์ ์ํ ์ฌ๋ถ๋ฐฐ
|
| 109 |
+
redistribution = self._calculate_redistribution()
|
| 110 |
+
self.terrain.elevation += redistribution
|
| 111 |
+
|
| 112 |
+
# 4. ์ผ๊ฐ์ฃผ ์์ญ ์
๋ฐ์ดํธ
|
| 113 |
+
self._update_delta_mask()
|
| 114 |
+
|
| 115 |
+
self.current_step += 1
|
| 116 |
+
|
| 117 |
+
if self.current_step % 10 == 0:
|
| 118 |
+
self.history.append(self.terrain.elevation.copy())
|
| 119 |
+
|
| 120 |
+
return self.terrain.elevation
|
| 121 |
+
|
| 122 |
+
def _calculate_delta_deposition(self) -> np.ndarray:
|
| 123 |
+
"""์๋์ง ๊ท ํ ๊ธฐ๋ฐ ์ผ๊ฐ์ฃผ ํด์ ๊ณ์ฐ"""
|
| 124 |
+
deposition = delta_deposition(
|
| 125 |
+
self.terrain, self.water,
|
| 126 |
+
river_energy=self.river_energy / 50, # ์ ๊ทํ
|
| 127 |
+
wave_energy=self.wave_energy / 50,
|
| 128 |
+
tidal_energy=self.tidal_energy / 50,
|
| 129 |
+
sea_level=self.sea_level
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
return deposition * self.sediment_supply
|
| 133 |
+
|
| 134 |
+
def _calculate_redistribution(self) -> np.ndarray:
|
| 135 |
+
"""ํ๋/์กฐ๋ฅ์ ์ํ ํด์ ๋ฌผ ์ฌ๋ถ๋ฐฐ"""
|
| 136 |
+
redistribution = np.zeros((self.height, self.width))
|
| 137 |
+
|
| 138 |
+
# ํ๊ตฌ ์์ญ (ํด์๋ฉด ๊ทผ์ฒ)
|
| 139 |
+
estuary = (self.terrain.elevation > self.sea_level - 5) & \
|
| 140 |
+
(self.terrain.elevation < self.sea_level + 5)
|
| 141 |
+
|
| 142 |
+
if not np.any(estuary):
|
| 143 |
+
return redistribution
|
| 144 |
+
|
| 145 |
+
# ํ๋: ์ข์ฐ๋ก ํผ๏ฟฝ๏ฟฝ๏ฟฝ๋ฆผ
|
| 146 |
+
if self.wave_energy > 20:
|
| 147 |
+
from scipy.ndimage import uniform_filter1d
|
| 148 |
+
for y in range(self.height):
|
| 149 |
+
if np.any(estuary[y, :]):
|
| 150 |
+
# ์ข์ฐ ๋ฐฉํฅ ํํํ
|
| 151 |
+
row = self.terrain.elevation[y, :].copy()
|
| 152 |
+
smoothed = uniform_filter1d(row, size=5)
|
| 153 |
+
redistribution[y, :] = (smoothed - row) * (self.wave_energy / 100) * 0.1
|
| 154 |
+
|
| 155 |
+
# ์กฐ๋ฅ: ๋ฐ๋ค ๋ฐฉํฅ์ผ๋ก ์ธ์ด๋
|
| 156 |
+
if self.tidal_energy > 20:
|
| 157 |
+
for y in range(1, self.height):
|
| 158 |
+
factor = self.tidal_energy / 100 * 0.05
|
| 159 |
+
if np.any(estuary[y, :]):
|
| 160 |
+
# ์๋๋ก ์ด๋
|
| 161 |
+
redistribution[y, :] += self.terrain.elevation[y-1, :] * factor * 0.1
|
| 162 |
+
redistribution[y-1, :] -= self.terrain.elevation[y-1, :] * factor * 0.1
|
| 163 |
+
|
| 164 |
+
return redistribution
|
| 165 |
+
|
| 166 |
+
def _update_delta_mask(self):
|
| 167 |
+
"""์ผ๊ฐ์ฃผ ์์ญ ์
๋ฐ์ดํธ"""
|
| 168 |
+
# ํด์๋ฉด๋ณด๋ค ์ฝ๊ฐ ๋๊ณ ํ๊ตฌ ๊ทผ์ฒ์ธ ์์ญ
|
| 169 |
+
self.delta_mask = (
|
| 170 |
+
(self.terrain.elevation > self.sea_level) &
|
| 171 |
+
(self.terrain.elevation < self.sea_level + 20) &
|
| 172 |
+
(np.arange(self.height)[:, None] > self.height * 0.4) # ํ๋ฅ
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
def get_delta_type(self) -> DeltaType:
|
| 176 |
+
"""ํ์ฌ ์ผ๊ฐ์ฃผ ์ ํ ํ๋ณ"""
|
| 177 |
+
total = self.river_energy + self.wave_energy + self.tidal_energy + 0.01
|
| 178 |
+
|
| 179 |
+
r_ratio = self.river_energy / total
|
| 180 |
+
w_ratio = self.wave_energy / total
|
| 181 |
+
t_ratio = self.tidal_energy / total
|
| 182 |
+
|
| 183 |
+
if r_ratio >= w_ratio and r_ratio >= t_ratio:
|
| 184 |
+
return DeltaType.RIVER_DOMINATED
|
| 185 |
+
elif w_ratio >= t_ratio:
|
| 186 |
+
return DeltaType.WAVE_DOMINATED
|
| 187 |
+
else:
|
| 188 |
+
return DeltaType.TIDE_DOMINATED
|
| 189 |
+
|
| 190 |
+
def get_delta_area(self) -> float:
|
| 191 |
+
"""์ผ๊ฐ์ฃผ ๋ฉด์ (์
์)"""
|
| 192 |
+
return float(np.sum(self.delta_mask))
|
| 193 |
+
|
| 194 |
+
def get_delta_extent(self) -> Tuple[float, float]:
|
| 195 |
+
"""์ผ๊ฐ์ฃผ ๊ฐ๋ก/์ธ๋ก ๋ฒ์"""
|
| 196 |
+
if not np.any(self.delta_mask):
|
| 197 |
+
return 0, 0
|
| 198 |
+
|
| 199 |
+
cols = np.any(self.delta_mask, axis=0)
|
| 200 |
+
rows = np.any(self.delta_mask, axis=1)
|
| 201 |
+
|
| 202 |
+
width = np.sum(cols) * self.terrain.cell_size
|
| 203 |
+
length = np.sum(rows) * self.terrain.cell_size
|
| 204 |
+
|
| 205 |
+
return width, length
|
| 206 |
+
|
| 207 |
+
def get_info(self) -> dict:
|
| 208 |
+
"""ํ์ฌ ์ํ ์ ๋ณด"""
|
| 209 |
+
delta_type = self.get_delta_type()
|
| 210 |
+
width, length = self.get_delta_extent()
|
| 211 |
+
|
| 212 |
+
return {
|
| 213 |
+
"step": self.current_step,
|
| 214 |
+
"delta_type": delta_type.value,
|
| 215 |
+
"delta_area": self.get_delta_area(),
|
| 216 |
+
"delta_width": width,
|
| 217 |
+
"delta_length": length,
|
| 218 |
+
"energy_balance": {
|
| 219 |
+
"river": self.river_energy,
|
| 220 |
+
"wave": self.wave_energy,
|
| 221 |
+
"tidal": self.tidal_energy
|
| 222 |
+
}
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
if __name__ == "__main__":
|
| 227 |
+
# ์๋๋ฆฌ์ค 1: ๋ฏธ์์ํผํ (ํ์ฒ ์ฐ์ธ)
|
| 228 |
+
print("=" * 50)
|
| 229 |
+
print("์๋๋ฆฌ์ค 1: ์กฐ์กฑ์ ์ผ๊ฐ์ฃผ (๋ฏธ์์ํผํ)")
|
| 230 |
+
sim1 = DeltaSimulator()
|
| 231 |
+
sim1.set_energy_balance(river=80, wave=10, tidal=10)
|
| 232 |
+
|
| 233 |
+
for i in range(10):
|
| 234 |
+
sim1.step(50)
|
| 235 |
+
info = sim1.get_info()
|
| 236 |
+
print(f"์ ํ: {info['delta_type']}")
|
| 237 |
+
print(f"๋ฉด์ : {info['delta_area']:.0f}, ํญ: {info['delta_width']:.0f}m, ๊ธธ์ด: {info['delta_length']:.0f}m")
|
| 238 |
+
|
| 239 |
+
# ์๋๋ฆฌ์ค 2: ๋์ผํ (ํ๋ ์ฐ์ธ)
|
| 240 |
+
print("\n" + "=" * 50)
|
| 241 |
+
print("์๋๋ฆฌ์ค 2: ์ํธ์ ์ผ๊ฐ์ฃผ (๋์ผํ)")
|
| 242 |
+
sim2 = DeltaSimulator()
|
| 243 |
+
sim2.set_energy_balance(river=30, wave=60, tidal=10)
|
| 244 |
+
|
| 245 |
+
for i in range(10):
|
| 246 |
+
sim2.step(50)
|
| 247 |
+
info = sim2.get_info()
|
| 248 |
+
print(f"์ ํ: {info['delta_type']}")
|
| 249 |
+
print(f"๋ฉด์ : {info['delta_area']:.0f}, ํญ: {info['delta_width']:.0f}m, ๊ธธ์ด: {info['delta_length']:.0f}m")
|
| 250 |
+
|
| 251 |
+
print("\n์๋ฎฌ๋ ์ด์
์๋ฃ!")
|
engine/river/meander.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI: ๊ณก๋ฅ & ์ฐ๊ฐํธ ์๋ฎฌ๋ ์ด์
|
| 3 |
+
์ค๋ฅ ํ์ฒ์ ์ธก๋ฐฉ ์นจ์์ผ๋ก ๊ตฝ์ด์น๋ ํ์ฒ๊ณผ ์ฐ๊ฐํธ ํ์ฑ
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from typing import List, Tuple, Optional
|
| 8 |
+
|
| 9 |
+
import sys
|
| 10 |
+
import os
|
| 11 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
| 12 |
+
|
| 13 |
+
from engine.base import Terrain, Water
|
| 14 |
+
from engine.erosion import lateral_erosion, apply_erosion
|
| 15 |
+
from engine.deposition import apply_deposition
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class MeanderSimulator:
|
| 20 |
+
"""๊ณก๋ฅ ํ์ฒ ์๋ฎฌ๋ ์ด์
|
| 21 |
+
|
| 22 |
+
ํต์ฌ ์๋ฆฌ:
|
| 23 |
+
1. ์ธก๋ฐฉ ์นจ์ (Lateral Erosion) - ๋ฐ๊นฅ์ชฝ(๊ณต๊ฒฉ์ฌ๋ฉด)
|
| 24 |
+
2. ์ธก๋ฐฉ ํด์ (Point Bar) - ์์ชฝ(ํด์ ์ฌ๋ฉด)
|
| 25 |
+
3. ์ ๋ก ์ ๋จ (Cutoff) - ์ฐ๊ฐํธ ํ์ฑ
|
| 26 |
+
|
| 27 |
+
๊ฒฐ๊ณผ: ๊ตฝ์ด์น๋ ํ์ฒ, ์ฐ๊ฐํธ(Oxbow Lake)
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
# ์งํ ํฌ๊ธฐ
|
| 31 |
+
width: int = 150
|
| 32 |
+
height: int = 150
|
| 33 |
+
|
| 34 |
+
# ์๋ฎฌ๋ ์ด์
ํ๋ผ๋ฏธํฐ
|
| 35 |
+
initial_sinuosity: float = 1.2 # ์ด๊ธฐ ๊ตด๊ณก๋
|
| 36 |
+
discharge: float = 50.0 # ์ ๋
|
| 37 |
+
|
| 38 |
+
# ์นจ์/ํด์ ๊ณ์
|
| 39 |
+
k_lateral: float = 0.0003
|
| 40 |
+
k_deposition: float = 0.0002
|
| 41 |
+
|
| 42 |
+
# ์ฐ๊ฐํธ ํ์ฑ ์กฐ๊ฑด
|
| 43 |
+
cutoff_threshold: float = 10.0 # ์ ๋ก ๊ฐ ๊ฑฐ๋ฆฌ๊ฐ ์ด ์ดํ๋ฉด ์ ๋จ
|
| 44 |
+
|
| 45 |
+
# ๋ด๋ถ ์ํ
|
| 46 |
+
terrain: Terrain = field(default=None)
|
| 47 |
+
water: Water = field(default=None)
|
| 48 |
+
channel_path: List[Tuple[int, int]] = field(default_factory=list)
|
| 49 |
+
oxbow_lakes: List[np.ndarray] = field(default_factory=list)
|
| 50 |
+
history: List[np.ndarray] = field(default_factory=list)
|
| 51 |
+
current_step: int = 0
|
| 52 |
+
|
| 53 |
+
def __post_init__(self):
|
| 54 |
+
self.reset()
|
| 55 |
+
|
| 56 |
+
def reset(self):
|
| 57 |
+
"""์๋ฎฌ๋ ์ด์
์ด๊ธฐํ"""
|
| 58 |
+
self.terrain = Terrain(width=self.width, height=self.height)
|
| 59 |
+
|
| 60 |
+
# ํํํ ๋ฒ๋์ (์ฝ๊ฐ์ ๊ฒฝ์ฌ)
|
| 61 |
+
for y in range(self.height):
|
| 62 |
+
self.terrain.elevation[y, :] = 100 - y * 0.2 # ์๋งํ ๊ฒฝ์ฌ
|
| 63 |
+
|
| 64 |
+
# ์ด๊ธฐ ๊ณก๋ฅ ํ์ฒ ๊ฒฝ๋ก ์์ฑ
|
| 65 |
+
self._create_initial_channel()
|
| 66 |
+
|
| 67 |
+
# ์๋ฌธ ์ด๊ธฐํ
|
| 68 |
+
self.water = Water(terrain=self.terrain)
|
| 69 |
+
self._update_water_from_channel()
|
| 70 |
+
|
| 71 |
+
self.oxbow_lakes = []
|
| 72 |
+
self.history = [self.terrain.elevation.copy()]
|
| 73 |
+
self.current_step = 0
|
| 74 |
+
|
| 75 |
+
def _create_initial_channel(self):
|
| 76 |
+
"""์ด๊ธฐ ์ฌ์ธํ ํํ์ ๊ณก๋ฅ ํ์ฒ ์์ฑ"""
|
| 77 |
+
self.channel_path = []
|
| 78 |
+
|
| 79 |
+
amplitude = self.width * 0.15 * self.initial_sinuosity
|
| 80 |
+
frequency = 3 # ๊ตฝ์ด ์
|
| 81 |
+
|
| 82 |
+
center = self.width // 2
|
| 83 |
+
|
| 84 |
+
for y in range(self.height):
|
| 85 |
+
# ์ฌ์ธํ ๊ณก์
|
| 86 |
+
x = int(center + amplitude * np.sin(2 * np.pi * frequency * y / self.height))
|
| 87 |
+
x = max(5, min(self.width - 5, x))
|
| 88 |
+
self.channel_path.append((y, x))
|
| 89 |
+
|
| 90 |
+
# ํ์ฒ ์ฑ๋ ํ๊ธฐ (์ฃผ๋ณ๋ ์ฝ๊ฐ)
|
| 91 |
+
for dx in range(-3, 4):
|
| 92 |
+
nx = x + dx
|
| 93 |
+
if 0 <= nx < self.width:
|
| 94 |
+
depth = 5 * (1 - abs(dx) / 4)
|
| 95 |
+
self.terrain.elevation[y, nx] -= depth
|
| 96 |
+
|
| 97 |
+
def _update_water_from_channel(self):
|
| 98 |
+
"""ํ์ฒ ๊ฒฝ๋ก๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ฌธ ๋ฐ์ดํฐ ์
๋ฐ์ดํธ"""
|
| 99 |
+
self.water.discharge[:] = 0
|
| 100 |
+
self.water.velocity[:] = 0
|
| 101 |
+
|
| 102 |
+
for y, x in self.channel_path:
|
| 103 |
+
self.water.discharge[y, x] = self.discharge
|
| 104 |
+
self.water.velocity[y, x] = 2.0 # ๊ธฐ๋ณธ ์ ์
|
| 105 |
+
|
| 106 |
+
# ์ฃผ๋ณ์ผ๋ก ํ์ฐ
|
| 107 |
+
from scipy.ndimage import gaussian_filter
|
| 108 |
+
self.water.discharge = gaussian_filter(self.water.discharge, sigma=1)
|
| 109 |
+
self.water.velocity = gaussian_filter(self.water.velocity, sigma=1)
|
| 110 |
+
|
| 111 |
+
def step(self, n_steps: int = 1) -> np.ndarray:
|
| 112 |
+
"""์๋ฎฌ๋ ์ด์
n์คํ
์งํ"""
|
| 113 |
+
for _ in range(n_steps):
|
| 114 |
+
# 1. ์ธก๋ฐฉ ์นจ์ (๋ฐ๊นฅ์ชฝ)
|
| 115 |
+
erosion = self._calculate_bank_erosion()
|
| 116 |
+
apply_erosion(self.terrain, erosion)
|
| 117 |
+
|
| 118 |
+
# 2. Point Bar ํด์ (์์ชฝ)
|
| 119 |
+
deposition = self._calculate_point_bar_deposition()
|
| 120 |
+
apply_deposition(self.terrain, deposition)
|
| 121 |
+
|
| 122 |
+
# 3. ํ์ฒ ๊ฒฝ๋ก ์
๋ฐ์ดํธ (๊ฐ์ฅ ๋ฎ์ ๊ณณ์ผ๋ก ์ด๋)
|
| 123 |
+
self._update_channel_path()
|
| 124 |
+
|
| 125 |
+
# 4. ์ฐ๊ฐํธ ์ฒดํฌ
|
| 126 |
+
self._check_cutoff()
|
| 127 |
+
|
| 128 |
+
# 5. ์๋ฌธ ์
๋ฐ์ดํธ
|
| 129 |
+
self._update_water_from_channel()
|
| 130 |
+
|
| 131 |
+
self.current_step += 1
|
| 132 |
+
|
| 133 |
+
if self.current_step % 10 == 0:
|
| 134 |
+
self.history.append(self.terrain.elevation.copy())
|
| 135 |
+
|
| 136 |
+
return self.terrain.elevation
|
| 137 |
+
|
| 138 |
+
def _calculate_bank_erosion(self) -> np.ndarray:
|
| 139 |
+
"""๊ณต๊ฒฉ์ฌ๋ฉด(๋ฐ๊นฅ์ชฝ) ์นจ์ ๊ณ์ฐ"""
|
| 140 |
+
erosion = np.zeros((self.height, self.width))
|
| 141 |
+
|
| 142 |
+
for i in range(1, len(self.channel_path) - 1):
|
| 143 |
+
y, x = self.channel_path[i]
|
| 144 |
+
y_prev, x_prev = self.channel_path[i - 1]
|
| 145 |
+
y_next, x_next = self.channel_path[i + 1]
|
| 146 |
+
|
| 147 |
+
# ๊ณก๋ฅ ๊ณ์ฐ (๋ฐฉํฅ ๋ณํ)
|
| 148 |
+
dx1, dy1 = x - x_prev, y - y_prev
|
| 149 |
+
dx2, dy2 = x_next - x, y_next - y
|
| 150 |
+
|
| 151 |
+
# ์ธ์ ์ผ๋ก ํ์ ๋ฐฉํฅ ํ๋จ
|
| 152 |
+
cross = dx1 * dy2 - dy1 * dx2
|
| 153 |
+
|
| 154 |
+
# ๋ฐ๊นฅ์ชฝ ๊ฒฐ์
|
| 155 |
+
if cross > 0: # ์ค๋ฅธ์ชฝ์ผ๋ก ํ์ โ ์ผ์ชฝ์ด ๋ฐ๊นฅ
|
| 156 |
+
outer_x = x - 1
|
| 157 |
+
else: # ์ผ์ชฝ์ผ๋ก ํ์ โ ์ค๋ฅธ์ชฝ์ด ๋ฐ๊นฅ
|
| 158 |
+
outer_x = x + 1
|
| 159 |
+
|
| 160 |
+
if 0 <= outer_x < self.width:
|
| 161 |
+
curvature = abs(cross) / (np.sqrt(dx1**2+dy1**2+0.1) * np.sqrt(dx2**2+dy2**2+0.1) + 0.1)
|
| 162 |
+
erosion[y, outer_x] = self.k_lateral * self.discharge * curvature
|
| 163 |
+
|
| 164 |
+
return erosion
|
| 165 |
+
|
| 166 |
+
def _calculate_point_bar_deposition(self) -> np.ndarray:
|
| 167 |
+
"""ํด์ ์ฌ๋ฉด(์์ชฝ) ํด์ ๊ณ์ฐ"""
|
| 168 |
+
deposition = np.zeros((self.height, self.width))
|
| 169 |
+
|
| 170 |
+
for i in range(1, len(self.channel_path) - 1):
|
| 171 |
+
y, x = self.channel_path[i]
|
| 172 |
+
y_prev, x_prev = self.channel_path[i - 1]
|
| 173 |
+
y_next, x_next = self.channel_path[i + 1]
|
| 174 |
+
|
| 175 |
+
dx1, dy1 = x - x_prev, y - y_prev
|
| 176 |
+
dx2, dy2 = x_next - x, y_next - y
|
| 177 |
+
cross = dx1 * dy2 - dy1 * dx2
|
| 178 |
+
|
| 179 |
+
# ์์ชฝ (๋ฐ๊นฅ์ชฝ ๋ฐ๋)
|
| 180 |
+
if cross > 0:
|
| 181 |
+
inner_x = x + 1
|
| 182 |
+
else:
|
| 183 |
+
inner_x = x - 1
|
| 184 |
+
|
| 185 |
+
if 0 <= inner_x < self.width:
|
| 186 |
+
curvature = abs(cross) / (np.sqrt(dx1**2+dy1**2+0.1) * np.sqrt(dx2**2+dy2**2+0.1) + 0.1)
|
| 187 |
+
deposition[y, inner_x] = self.k_deposition * self.discharge * curvature
|
| 188 |
+
|
| 189 |
+
return deposition
|
| 190 |
+
|
| 191 |
+
def _update_channel_path(self):
|
| 192 |
+
"""ํ์ฒ ๊ฒฝ๋ก๋ฅผ ๊ฐ์ฅ ๋ฎ์ ์ง์ ์ผ๋ก ์ด๋"""
|
| 193 |
+
new_path = [self.channel_path[0]] # ์์์ ์ ์ง
|
| 194 |
+
|
| 195 |
+
for i in range(1, len(self.channel_path) - 1):
|
| 196 |
+
y, x = self.channel_path[i]
|
| 197 |
+
|
| 198 |
+
# ์ฃผ๋ณ ์ค ๊ฐ์ฅ ๋ฎ์ ๊ณณ ํ์
|
| 199 |
+
min_elev = self.terrain.elevation[y, x]
|
| 200 |
+
best_x = x
|
| 201 |
+
|
| 202 |
+
for dx in [-1, 0, 1]:
|
| 203 |
+
nx = x + dx
|
| 204 |
+
if 0 <= nx < self.width:
|
| 205 |
+
if self.terrain.elevation[y, nx] < min_elev:
|
| 206 |
+
min_elev = self.terrain.elevation[y, nx]
|
| 207 |
+
best_x = nx
|
| 208 |
+
|
| 209 |
+
new_path.append((y, best_x))
|
| 210 |
+
|
| 211 |
+
new_path.append(self.channel_path[-1]) # ๋์ ์ ์ง
|
| 212 |
+
self.channel_path = new_path
|
| 213 |
+
|
| 214 |
+
def _check_cutoff(self):
|
| 215 |
+
"""์ฐ๊ฐํธ ํ์ฑ ์กฐ๊ฑด ์ฒดํฌ"""
|
| 216 |
+
# ๊ฐ๊น์ด ๋ ์ ๋ก ์ง์ ์ฐพ๊ธฐ
|
| 217 |
+
for i in range(len(self.channel_path)):
|
| 218 |
+
for j in range(i + 20, len(self.channel_path)): # ์ต์ 20์
๋จ์ด์ง ๊ฒ๋ง
|
| 219 |
+
y1, x1 = self.channel_path[i]
|
| 220 |
+
y2, x2 = self.channel_path[j]
|
| 221 |
+
|
| 222 |
+
dist = np.sqrt((x1 - x2)**2 + (y1 - y2)**2)
|
| 223 |
+
|
| 224 |
+
if dist < self.cutoff_threshold:
|
| 225 |
+
# Cutoff ๋ฐ์! ์ฐ๊ฐํธ ์์ฑ
|
| 226 |
+
self._create_oxbow_lake(i, j)
|
| 227 |
+
return
|
| 228 |
+
|
| 229 |
+
def _create_oxbow_lake(self, start_idx: int, end_idx: int):
|
| 230 |
+
"""์ฐ๊ฐํธ ์์ฑ"""
|
| 231 |
+
# ๊ณ ๋ฆฝ๋ ๊ตฌ๊ฐ ์ถ์ถ
|
| 232 |
+
cutoff_section = self.channel_path[start_idx:end_idx]
|
| 233 |
+
|
| 234 |
+
# ์ฐ๊ฐํธ๋ก ์ ์ฅ
|
| 235 |
+
oxbow = np.zeros((self.height, self.width), dtype=bool)
|
| 236 |
+
for y, x in cutoff_section:
|
| 237 |
+
oxbow[y, x] = True
|
| 238 |
+
self.oxbow_lakes.append(oxbow)
|
| 239 |
+
|
| 240 |
+
# ํ์ฒ ๊ฒฝ๋ก ๋จ์ถ (์ง์ ์ผ๋ก)
|
| 241 |
+
self.channel_path = (
|
| 242 |
+
self.channel_path[:start_idx+1] +
|
| 243 |
+
self.channel_path[end_idx:]
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
print(f"๐ ์ฐ๊ฐํธ ํ์ฑ! (Step {self.current_step})")
|
| 247 |
+
|
| 248 |
+
def get_cross_section(self, y_position: int = None) -> Tuple[np.ndarray, np.ndarray]:
|
| 249 |
+
"""ํน์ ์์น์ ๋จ๋ฉด๋"""
|
| 250 |
+
if y_position is None:
|
| 251 |
+
y_position = self.height // 2
|
| 252 |
+
|
| 253 |
+
x = np.arange(self.width) * self.terrain.cell_size
|
| 254 |
+
z = self.terrain.elevation[y_position, :]
|
| 255 |
+
|
| 256 |
+
return x, z
|
| 257 |
+
|
| 258 |
+
def get_sinuosity(self) -> float:
|
| 259 |
+
"""ํ์ฌ ๊ตด๊ณก๋ ๊ณ์ฐ"""
|
| 260 |
+
if len(self.channel_path) < 2:
|
| 261 |
+
return 1.0
|
| 262 |
+
|
| 263 |
+
# ์ค์ ๊ฒฝ๋ก ๊ธธ์ด
|
| 264 |
+
path_length = 0
|
| 265 |
+
for i in range(1, len(self.channel_path)):
|
| 266 |
+
y1, x1 = self.channel_path[i-1]
|
| 267 |
+
y2, x2 = self.channel_path[i]
|
| 268 |
+
path_length += np.sqrt((x2-x1)**2 + (y2-y1)**2)
|
| 269 |
+
|
| 270 |
+
# ์ง์ ๊ฑฐ๋ฆฌ
|
| 271 |
+
y_start, x_start = self.channel_path[0]
|
| 272 |
+
y_end, x_end = self.channel_path[-1]
|
| 273 |
+
straight_length = np.sqrt((x_end-x_start)**2 + (y_end-y_start)**2) + 0.1
|
| 274 |
+
|
| 275 |
+
return path_length / straight_length
|
| 276 |
+
|
| 277 |
+
def get_info(self) -> dict:
|
| 278 |
+
"""ํ์ฌ ์ํ ์ ๋ณด"""
|
| 279 |
+
return {
|
| 280 |
+
"step": self.current_step,
|
| 281 |
+
"sinuosity": self.get_sinuosity(),
|
| 282 |
+
"oxbow_lakes": len(self.oxbow_lakes),
|
| 283 |
+
"channel_length": len(self.channel_path)
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
if __name__ == "__main__":
|
| 288 |
+
sim = MeanderSimulator(initial_sinuosity=1.5)
|
| 289 |
+
|
| 290 |
+
print("๊ณก๋ฅ ํ์ฒ ์๋ฎฌ๋ ์ด์
์์")
|
| 291 |
+
print(f"์ด๊ธฐ ๊ตด๊ณก๋: {sim.get_sinuosity():.2f}")
|
| 292 |
+
|
| 293 |
+
for i in range(20):
|
| 294 |
+
sim.step(50)
|
| 295 |
+
info = sim.get_info()
|
| 296 |
+
print(f"Step {info['step']}: ๊ตด๊ณก๋ {info['sinuosity']:.2f}, "
|
| 297 |
+
f"์ฐ๊ฐํธ {info['oxbow_lakes']}๊ฐ")
|
| 298 |
+
|
| 299 |
+
print("์๋ฎฌ๋ ์ด์
์๋ฃ!")
|
engine/river/v_valley.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Geo-Lab AI: V์๊ณก ์๋ฎฌ๋ ์ด์
|
| 3 |
+
์๋ฅ ํ์ฒ์ ํ๋ฐฉ ์นจ์์ผ๋ก V์ ๋ชจ์ ๊ณจ์ง๊ธฐ ํ์ฑ
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from typing import List, Tuple
|
| 8 |
+
|
| 9 |
+
import sys
|
| 10 |
+
import os
|
| 11 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
| 12 |
+
|
| 13 |
+
from engine.base import Terrain, Water, SimulationState
|
| 14 |
+
from engine.erosion import vertical_erosion, headward_erosion, mass_wasting, apply_erosion
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass
|
| 18 |
+
class VValleySimulator:
|
| 19 |
+
"""V์๊ณก ์๋ฎฌ๋ ์ด์
|
| 20 |
+
|
| 21 |
+
ํต์ฌ ์๋ฆฌ:
|
| 22 |
+
1. ํ๋ฐฉ ์นจ์ (Stream Power Law)
|
| 23 |
+
2. ์ฌ๋ฉด ๋ถ๊ดด (Mass Wasting)
|
| 24 |
+
|
| 25 |
+
๊ฒฐ๊ณผ: V์ ํํ์ ๊น์ ๊ณจ์ง๊ธฐ
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
# ์งํ ํฌ๊ธฐ
|
| 29 |
+
width: int = 100
|
| 30 |
+
height: int = 100
|
| 31 |
+
|
| 32 |
+
# ์๋ฎฌ๋ ์ด์
ํ๋ผ๋ฏธํฐ
|
| 33 |
+
rock_hardness: float = 0.5 # 0=๋ฌด๋ฆ, 1=๋จ๋จํจ
|
| 34 |
+
slope_gradient: float = 0.1 # ์ด๊ธฐ ๊ฒฝ์ฌ
|
| 35 |
+
initial_discharge: float = 10.0 # ์ด๊ธฐ ์ ๋
|
| 36 |
+
|
| 37 |
+
# ์นจ์ ๊ณ์
|
| 38 |
+
k_vertical: float = 0.0005 # ํ๋ฐฉ ์นจ์ ๊ณ์
|
| 39 |
+
k_headward: float = 0.0003 # ๋๋ถ ์นจ์ ๊ณ์
|
| 40 |
+
|
| 41 |
+
# ๋ด๋ถ ์ํ
|
| 42 |
+
terrain: Terrain = field(default=None)
|
| 43 |
+
water: Water = field(default=None)
|
| 44 |
+
history: List[np.ndarray] = field(default_factory=list)
|
| 45 |
+
current_step: int = 0
|
| 46 |
+
|
| 47 |
+
def __post_init__(self):
|
| 48 |
+
self.reset()
|
| 49 |
+
|
| 50 |
+
def reset(self):
|
| 51 |
+
"""์๋ฎฌ๋ ์ด์
์ด๊ธฐํ"""
|
| 52 |
+
# ์ด๊ธฐ ์งํ: ๋ถ์ชฝ์ด ๋์ ๊ฒฝ์ฌ๋ฉด
|
| 53 |
+
self.terrain = Terrain(width=self.width, height=self.height)
|
| 54 |
+
|
| 55 |
+
# ๊ฒฝ์ฌ ์ค์
|
| 56 |
+
for y in range(self.height):
|
| 57 |
+
base_elevation = 500 * (1 - y / self.height)
|
| 58 |
+
self.terrain.elevation[y, :] = base_elevation
|
| 59 |
+
|
| 60 |
+
# ์ค์์ ์ด๊ธฐ ํ์ฒ ์ฑ๋ (์ฝ๊ฐ ๋ฎ๊ฒ)
|
| 61 |
+
center = self.width // 2
|
| 62 |
+
channel_width = 3
|
| 63 |
+
for x in range(center - channel_width, center + channel_width):
|
| 64 |
+
if 0 <= x < self.width:
|
| 65 |
+
self.terrain.elevation[:, x] -= 10
|
| 66 |
+
|
| 67 |
+
# ์์ ๊ฒฝ๋ ์ค์
|
| 68 |
+
self.terrain.rock_hardness[:] = self.rock_hardness
|
| 69 |
+
|
| 70 |
+
# ์๋ฌธ ์ด๊ธฐํ
|
| 71 |
+
self.water = Water(terrain=self.terrain)
|
| 72 |
+
self.water.discharge[0, center] = self.initial_discharge # ์๋ฅ์์ ์ ์
|
| 73 |
+
self.water.accumulate_flow()
|
| 74 |
+
|
| 75 |
+
self.history = [self.terrain.elevation.copy()]
|
| 76 |
+
self.current_step = 0
|
| 77 |
+
|
| 78 |
+
def step(self, n_steps: int = 1) -> np.ndarray:
|
| 79 |
+
"""์๋ฎฌ๋ ์ด์
n์คํ
์งํ"""
|
| 80 |
+
for _ in range(n_steps):
|
| 81 |
+
# 1. ์ ๋ ๊ณ์ฐ
|
| 82 |
+
self.water.add_precipitation(rate=0.001)
|
| 83 |
+
self.water.accumulate_flow()
|
| 84 |
+
|
| 85 |
+
# 2. ํ๋ฐฉ ์นจ์
|
| 86 |
+
v_erosion = vertical_erosion(
|
| 87 |
+
self.terrain, self.water,
|
| 88 |
+
k_erosion=self.k_vertical * (1 - self.rock_hardness)
|
| 89 |
+
)
|
| 90 |
+
apply_erosion(self.terrain, v_erosion)
|
| 91 |
+
|
| 92 |
+
# 3. ๋๋ถ ์นจ์
|
| 93 |
+
h_erosion = headward_erosion(
|
| 94 |
+
self.terrain, self.water,
|
| 95 |
+
k_headward=self.k_headward
|
| 96 |
+
)
|
| 97 |
+
apply_erosion(self.terrain, h_erosion)
|
| 98 |
+
|
| 99 |
+
# 4. ์ฌ๋ฉด ๋ถ๊ดด (V์ ํ์ฑ์ ํต์ฌ!)
|
| 100 |
+
mass_change = mass_wasting(self.terrain)
|
| 101 |
+
self.terrain.elevation += mass_change
|
| 102 |
+
|
| 103 |
+
# 5. ํ๋ฆ ๋ฐฉํฅ ์
๋ฐ์ดํธ
|
| 104 |
+
self.water.flow_x, self.water.flow_y = self.terrain.get_flow_direction()
|
| 105 |
+
|
| 106 |
+
self.current_step += 1
|
| 107 |
+
|
| 108 |
+
# ํ์คํ ๋ฆฌ ์ ์ฅ (๋งค 10์คํ
)
|
| 109 |
+
if self.current_step % 10 == 0:
|
| 110 |
+
self.history.append(self.terrain.elevation.copy())
|
| 111 |
+
|
| 112 |
+
return self.terrain.elevation
|
| 113 |
+
|
| 114 |
+
def get_cross_section(self, y_position: int = None) -> Tuple[np.ndarray, np.ndarray]:
|
| 115 |
+
"""ํน์ ์์น์ ๋จ๋ฉด๋ ๋ฐํ"""
|
| 116 |
+
if y_position is None:
|
| 117 |
+
y_position = self.height // 2
|
| 118 |
+
|
| 119 |
+
x = np.arange(self.width) * self.terrain.cell_size
|
| 120 |
+
z = self.terrain.elevation[y_position, :]
|
| 121 |
+
|
| 122 |
+
return x, z
|
| 123 |
+
|
| 124 |
+
def get_valley_depth(self) -> float:
|
| 125 |
+
"""ํ์ฌ V์๊ณก ๊น์ด ์ธก์ """
|
| 126 |
+
center = self.width // 2
|
| 127 |
+
y_mid = self.height // 2
|
| 128 |
+
|
| 129 |
+
# ์ข์ฐ ๊ณ ๋ ํ๊ท vs ์ค์ ๊ณ ๋
|
| 130 |
+
left_elev = self.terrain.elevation[y_mid, center - 20]
|
| 131 |
+
right_elev = self.terrain.elevation[y_mid, center + 20]
|
| 132 |
+
center_elev = self.terrain.elevation[y_mid, center]
|
| 133 |
+
|
| 134 |
+
depth = (left_elev + right_elev) / 2 - center_elev
|
| 135 |
+
return max(0, depth)
|
| 136 |
+
|
| 137 |
+
def get_info(self) -> dict:
|
| 138 |
+
"""ํ์ฌ ์๋ฎฌ๋ ์ด์
์ํ ์ ๋ณด"""
|
| 139 |
+
return {
|
| 140 |
+
"step": self.current_step,
|
| 141 |
+
"valley_depth": self.get_valley_depth(),
|
| 142 |
+
"max_elevation": float(self.terrain.elevation.max()),
|
| 143 |
+
"min_elevation": float(self.terrain.elevation.min()),
|
| 144 |
+
"total_erosion": float(self.history[0].sum() - self.terrain.elevation.sum())
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
# ํ
์คํธ ์ฝ๋
|
| 149 |
+
if __name__ == "__main__":
|
| 150 |
+
sim = VValleySimulator(rock_hardness=0.3)
|
| 151 |
+
|
| 152 |
+
print("V์๊ณก ์๋ฎฌ๋ ์ด์
์์")
|
| 153 |
+
print(f"์ด๊ธฐ ๊น์ด: {sim.get_valley_depth():.1f}m")
|
| 154 |
+
|
| 155 |
+
for i in range(10):
|
| 156 |
+
sim.step(100)
|
| 157 |
+
info = sim.get_info()
|
| 158 |
+
print(f"Step {info['step']}: ๊น์ด {info['valley_depth']:.1f}m, "
|
| 159 |
+
f"์ด ์นจ์๋ {info['total_erosion']:.0f}mยณ")
|
| 160 |
+
|
| 161 |
+
print("์๋ฎฌ๋ ์ด์
์๋ฃ!")
|
engine/script_engine.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import numpy as np
|
| 3 |
+
import math
|
| 4 |
+
from .grid import WorldGrid
|
| 5 |
+
from .fluids import HydroKernel
|
| 6 |
+
from .erosion_process import ErosionProcess
|
| 7 |
+
|
| 8 |
+
class ScriptExecutor:
|
| 9 |
+
"""
|
| 10 |
+
์ฌ์ฉ์ ์ ์ ์คํฌ๋ฆฝํธ ์คํ ์์ง
|
| 11 |
+
๋ณด์์ ์ํด ์ ํ๋ ํ๊ฒฝ์์ Python ์ฝ๋๋ฅผ ์คํํฉ๋๋ค.
|
| 12 |
+
"""
|
| 13 |
+
def __init__(self, grid: WorldGrid):
|
| 14 |
+
self.grid = grid
|
| 15 |
+
self.hydro = HydroKernel(grid)
|
| 16 |
+
self.erosion = ErosionProcess(grid)
|
| 17 |
+
|
| 18 |
+
def execute(self, script: str, dt: float = 1.0, allowed_modules: list = ['numpy', 'math']):
|
| 19 |
+
"""
|
| 20 |
+
์คํฌ๋ฆฝํธ ์คํ
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
script: ์คํํ Python ์ฝ๋ ๋ฌธ์์ด
|
| 24 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ (Time Step)
|
| 25 |
+
allowed_modules: ํ์ฉํ ๋ชจ๋ ๋ฆฌ์คํธ (๊ธฐ๋ณธ: numpy, math)
|
| 26 |
+
|
| 27 |
+
Available Variables in Context:
|
| 28 |
+
- grid: WorldGrid ๊ฐ์ฒด
|
| 29 |
+
- elevation: grid.elevation (Numpy Array)
|
| 30 |
+
- bedrock: grid.bedrock
|
| 31 |
+
- sediment: grid.sediment
|
| 32 |
+
- water_depth: grid.water_depth
|
| 33 |
+
- dt: Delta Time
|
| 34 |
+
- np: numpy module
|
| 35 |
+
- math: math module
|
| 36 |
+
- hydro: HydroKernel ๊ฐ์ฒด
|
| 37 |
+
- erosion: ErosionProcess ๊ฐ์ฒด
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
# 1. ์คํ ์ปจํ
์คํธ(Namespace) ์ค๋น
|
| 41 |
+
context = {
|
| 42 |
+
'grid': self.grid,
|
| 43 |
+
'elevation': self.grid.elevation,
|
| 44 |
+
'bedrock': self.grid.bedrock,
|
| 45 |
+
'sediment': self.grid.sediment,
|
| 46 |
+
'water_depth': self.grid.water_depth,
|
| 47 |
+
'dt': dt,
|
| 48 |
+
'np': np,
|
| 49 |
+
'math': math,
|
| 50 |
+
'hydro': self.hydro,
|
| 51 |
+
'erosion': self.erosion,
|
| 52 |
+
# Helper functions
|
| 53 |
+
'max': max,
|
| 54 |
+
'min': min,
|
| 55 |
+
'abs': abs,
|
| 56 |
+
'pow': pow,
|
| 57 |
+
'print': print # ๋๋ฒ๊น
์ฉ
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
# 2. ๊ธ์ง๋ ํค์๋ ์ฒดํฌ (๊ธฐ๋ณธ์ ์ธ ๋ณด์)
|
| 61 |
+
# ์๋ฒฝํ ์๋๋ฐ์ค๋ ์๋์ง๋ง, ์ค์ ๋ฐฉ์ง์ฉ
|
| 62 |
+
forbidden = ['import os', 'import sys', 'open(', 'exec(', 'eval(', '__import__']
|
| 63 |
+
for bad in forbidden:
|
| 64 |
+
if bad in script:
|
| 65 |
+
raise ValueError(f"๋ณด์ ๊ฒฝ๊ณ : ํ์ฉ๋์ง ์๋ ํค์๋ '{bad}'๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.")
|
| 66 |
+
|
| 67 |
+
# 3. ์ฝ๋ ์คํ
|
| 68 |
+
try:
|
| 69 |
+
exec(script, {"__builtins__": {}}, context)
|
| 70 |
+
|
| 71 |
+
# 4. ๋ณ๊ฒฝ ์ฌํญ ๋ฐ์ (Elevation์ derived property์ง๋ง, ์ง์ ์์ ํ์ ์ ์์ผ๋ฏ๋ก)
|
| 72 |
+
# ์ฌ์ฉ์๊ฐ elevation์ ์์ ํ๋ค๋ฉด bedrock/sediment ์
๋ฐ์ดํธ๊ฐ ๋ชจํธํด์ง.
|
| 73 |
+
# Grid ํด๋์ค์ update_elevation()์ bedrock+sediment -> elevation์ด๋ฏ๋ก,
|
| 74 |
+
# ์ฌ์ฉ์๊ฐ elevation์ ์์ ํ๋ฉด ๋ฌด์๋ ์ ์์.
|
| 75 |
+
# ๊ฐ์ด๋: "bedrock"์ด๋ "sediment"๋ฅผ ์์ ํ์ธ์.
|
| 76 |
+
# ํ์ง๋ง ํธ์๋ฅผ ์ํด elevation์ด ๋ฐ๋์์ผ๋ฉด bedrock์ ๋ฐ์ํ๋ ๋ก์ง ์ถ๊ฐ
|
| 77 |
+
|
| 78 |
+
# ๋ณ๊ฒฝ ์ elevation๊ณผ ๋น๊ตํด์ผ ํ๋?
|
| 79 |
+
# ์ผ๋จ grid.update_elevation()์ ํธ์ถํ์ฌ ๋๊ธฐํ
|
| 80 |
+
# (๋ง์ฝ ์ฌ์ฉ์๊ฐ bedrock์ ๋ฐ๊ฟจ๋ค๋ฉด ๋ฐ์๋จ)
|
| 81 |
+
self.grid.update_elevation()
|
| 82 |
+
|
| 83 |
+
return True, "์คํ ์ฑ๊ณต"
|
| 84 |
+
|
| 85 |
+
except Exception as e:
|
| 86 |
+
return False, f"์คํ ์ค๋ฅ: {str(e)}"
|
| 87 |
+
|
engine/system.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
from .grid import WorldGrid
|
| 4 |
+
from .fluids import HydroKernel
|
| 5 |
+
from .erosion_process import ErosionProcess
|
| 6 |
+
from .lateral_erosion import LateralErosionKernel
|
| 7 |
+
from .mass_movement import MassMovementKernel
|
| 8 |
+
from .climate import ClimateKernel
|
| 9 |
+
from .wave import WaveKernel
|
| 10 |
+
from .glacier import GlacierKernel
|
| 11 |
+
from .wind import WindKernel
|
| 12 |
+
|
| 13 |
+
class EarthSystem:
|
| 14 |
+
"""
|
| 15 |
+
Project Genesis: Unified Earth System Engine
|
| 16 |
+
|
| 17 |
+
ํตํฉ ์ง๊ตฌ ์์คํ
์์ง
|
| 18 |
+
- ๊ธฐํ(Climate) -> ์๋ฌธ(Hydro) -> ์งํ(Erosion/Tectonics) ์ํธ์์ฉ์
|
| 19 |
+
๋จ์ผ ๋ฃจํ(step) ๋ด์์ ์ฒ๋ฆฌํฉ๋๋ค.
|
| 20 |
+
"""
|
| 21 |
+
def __init__(self, grid: WorldGrid):
|
| 22 |
+
self.grid = grid
|
| 23 |
+
self.time = 0.0
|
| 24 |
+
|
| 25 |
+
# Core Kernels
|
| 26 |
+
self.hydro = HydroKernel(self.grid)
|
| 27 |
+
self.erosion = ErosionProcess(self.grid)
|
| 28 |
+
self.lateral = LateralErosionKernel(self.grid, k=0.01)
|
| 29 |
+
self.mass_movement = MassMovementKernel(self.grid, friction_angle=35.0)
|
| 30 |
+
self.climate = ClimateKernel(self.grid)
|
| 31 |
+
|
| 32 |
+
# Phase 2 Kernels (Optional, enabled via settings)
|
| 33 |
+
self.wave = WaveKernel(self.grid)
|
| 34 |
+
self.glacier = GlacierKernel(self.grid)
|
| 35 |
+
self.wind = WindKernel(self.grid)
|
| 36 |
+
|
| 37 |
+
# State History (Optional for analysis)
|
| 38 |
+
self.history = []
|
| 39 |
+
|
| 40 |
+
def step(self, dt: float = 1.0, settings: dict = None):
|
| 41 |
+
"""
|
| 42 |
+
์์คํ
1๋จ๊ณ ์งํ (Time Step)
|
| 43 |
+
|
| 44 |
+
Process Chain:
|
| 45 |
+
1. Tectonics (Endogenous): Uplift, subsidence based on scenario settings
|
| 46 |
+
2. Climate (Exogenous): Generate precipitation
|
| 47 |
+
3. Hydro (Physics): Route flow, calculate discharge/depth
|
| 48 |
+
4. Morphodynamics (Response): Erode, transport, deposit
|
| 49 |
+
"""
|
| 50 |
+
if settings is None:
|
| 51 |
+
settings = {}
|
| 52 |
+
|
| 53 |
+
# 1. Tectonics (์ง๊ตฌ์กฐ ์ด๋)
|
| 54 |
+
# Simple vertical uplift logic if provided
|
| 55 |
+
uplift_rate = settings.get('uplift_rate', 0.0)
|
| 56 |
+
tile_uplift = settings.get('uplift_mask', None)
|
| 57 |
+
|
| 58 |
+
if uplift_rate > 0:
|
| 59 |
+
if tile_uplift is not None:
|
| 60 |
+
self.grid.apply_uplift(tile_uplift * uplift_rate, dt)
|
| 61 |
+
else:
|
| 62 |
+
self.grid.apply_uplift(uplift_rate, dt)
|
| 63 |
+
|
| 64 |
+
# 2. Climate (๊ธฐํ)
|
| 65 |
+
# Generate precipitation map
|
| 66 |
+
# Default: Uniform rain + randomness
|
| 67 |
+
base_precip = settings.get('precipitation', 0.01)
|
| 68 |
+
precip_map = np.ones((self.grid.height, self.grid.width)) * base_precip
|
| 69 |
+
|
| 70 |
+
# Apply rain source if specified (e.g., river mouth)
|
| 71 |
+
rain_source = settings.get('rain_source', None) # (y, x, radius, amount)
|
| 72 |
+
if rain_source:
|
| 73 |
+
y, x, r, amount = rain_source
|
| 74 |
+
# Simplified box source for speed
|
| 75 |
+
y_min, y_max = max(0, int(y-r)), min(self.grid.height, int(y+r+1))
|
| 76 |
+
x_min, x_max = max(0, int(x-r)), min(self.grid.width, int(x+r+1))
|
| 77 |
+
precip_map[y_min:y_max, x_min:x_max] += amount
|
| 78 |
+
|
| 79 |
+
# 3. Hydro (์๋ฌธ)
|
| 80 |
+
# Route flow based on current topography + precip
|
| 81 |
+
discharge = self.hydro.route_flow_d8(precipitation=precip_map)
|
| 82 |
+
|
| 83 |
+
# Update grid state
|
| 84 |
+
self.grid.discharge = discharge
|
| 85 |
+
self.grid.water_depth = self.hydro.calculate_water_depth(discharge)
|
| 86 |
+
|
| 87 |
+
# 4. Erosion / Deposition (์งํ ๋ณํ)
|
| 88 |
+
# Using the ErosionKernel (ErosionProcess wrapper)
|
| 89 |
+
# Need to update K, m, n parameters dynamically?
|
| 90 |
+
# For now, use instance defaults or allow overrides
|
| 91 |
+
|
| 92 |
+
# Execute Transport & Deposition
|
| 93 |
+
sediment_influx_map = None
|
| 94 |
+
sediment_source = settings.get('sediment_source', None) # (y, x, radius, amount)
|
| 95 |
+
if sediment_source:
|
| 96 |
+
y, x, r, amount = sediment_source
|
| 97 |
+
sediment_influx_map = np.zeros((self.grid.height, self.grid.width))
|
| 98 |
+
y_min, y_max = max(0, int(y-r)), min(self.grid.height, int(y+r+1))
|
| 99 |
+
x_min, x_max = max(0, int(x-r)), min(self.grid.width, int(x+r+1))
|
| 100 |
+
sediment_influx_map[y_min:y_max, x_min:x_max] += amount
|
| 101 |
+
|
| 102 |
+
self.erosion.simulate_transport(discharge, dt=dt, sediment_influx_map=sediment_influx_map)
|
| 103 |
+
|
| 104 |
+
# 5. Lateral Erosion (์ธก๋ฐฉ ์นจ์) - ๊ณก๋ฅ ํ์ฑ
|
| 105 |
+
lateral_enabled = settings.get('lateral_erosion', True)
|
| 106 |
+
if lateral_enabled:
|
| 107 |
+
self.lateral.step(discharge, dt=dt)
|
| 108 |
+
|
| 109 |
+
# 6. Hillslope Diffusion (์ฌ๋ฉด ์์ ํ)
|
| 110 |
+
diff_rate = settings.get('diffusion_rate', 0.01)
|
| 111 |
+
if diff_rate > 0:
|
| 112 |
+
self.erosion.hillslope_diffusion(dt=dt * diff_rate)
|
| 113 |
+
|
| 114 |
+
# 7. Mass Movement (๋งค์ค๋ฌด๋ธ๋จผํธ) - ์ฐ์ฌํ
|
| 115 |
+
mass_movement_enabled = settings.get('mass_movement', True)
|
| 116 |
+
if mass_movement_enabled:
|
| 117 |
+
self.mass_movement.step(dt=dt)
|
| 118 |
+
|
| 119 |
+
# Update Time
|
| 120 |
+
self.time += dt
|
| 121 |
+
|
| 122 |
+
def get_state(self):
|
| 123 |
+
"""ํ์ฌ ์์ค๏ฟฝ๏ฟฝ๏ฟฝ ์ํ ๋ฐํ"""
|
| 124 |
+
self.grid.update_elevation()
|
| 125 |
+
return {
|
| 126 |
+
'elevation': self.grid.elevation.copy(),
|
| 127 |
+
'water_depth': self.grid.water_depth.copy(),
|
| 128 |
+
'discharge': self.grid.discharge.copy(),
|
| 129 |
+
'sediment': self.grid.sediment.copy(),
|
| 130 |
+
'time': self.time
|
| 131 |
+
}
|
engine/wave.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Wave Kernel (ํ๋ ์ปค๋)
|
| 3 |
+
|
| 4 |
+
ํด์ ์งํ ํ์ฑ ํ๋ก์ธ์ค
|
| 5 |
+
- ํ๋ ์นจ์ (Wave Erosion): ํด์์ ๋ฒฝ ํํด
|
| 6 |
+
- ์ฐ์ ํด์ (Coastal Deposition): ์ฌ์ฃผ, ์ฌ์ทจ ํ์ฑ
|
| 7 |
+
- ์ฐ์๋ฅ (Longshore Drift): ์ธก๋ฐฉ ํด์ ๋ฌผ ์ด๋
|
| 8 |
+
|
| 9 |
+
ํต์ฌ ๊ณต์:
|
| 10 |
+
์นจ์๋ฅ E = K * H^2 * (1/R)
|
| 11 |
+
- H: ํ๊ณ (Wave Height)
|
| 12 |
+
- R: ์์ ์ ํญ๋ ฅ (Rock Resistance)
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
from .grid import WorldGrid
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class WaveKernel:
|
| 20 |
+
"""
|
| 21 |
+
ํ๋ ์ปค๋
|
| 22 |
+
|
| 23 |
+
ํด์์ ์์์ ํ๋ ์์ฉ์ ์๋ฎฌ๋ ์ด์
.
|
| 24 |
+
์นจ์๊ณผ ํด์ ์ ๋์์ ์ฒ๋ฆฌ.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
def __init__(self, grid: WorldGrid,
|
| 28 |
+
wave_height: float = 2.0, # m
|
| 29 |
+
wave_period: float = 8.0, # s
|
| 30 |
+
wave_direction: float = 0.0, # degrees from N
|
| 31 |
+
K_erosion: float = 0.001):
|
| 32 |
+
self.grid = grid
|
| 33 |
+
self.wave_height = wave_height
|
| 34 |
+
self.wave_period = wave_period
|
| 35 |
+
self.wave_direction = np.radians(wave_direction)
|
| 36 |
+
self.K = K_erosion
|
| 37 |
+
|
| 38 |
+
def identify_coastline(self) -> np.ndarray:
|
| 39 |
+
"""
|
| 40 |
+
ํด์์ ์๋ณ
|
| 41 |
+
|
| 42 |
+
์ก์ง-๋ฐ๋ค ๊ฒฝ๊ณ์ ์ ์ฐพ์
|
| 43 |
+
|
| 44 |
+
Returns:
|
| 45 |
+
coastline_mask: ํด์์ ์
๋ง์คํฌ
|
| 46 |
+
"""
|
| 47 |
+
underwater = self.grid.is_underwater()
|
| 48 |
+
|
| 49 |
+
# ํด์์ = ์ก์ง์ธ๋ฐ ์ธ์ ์
์ ๋ฐ๋ค๊ฐ ์๋ ๊ณณ
|
| 50 |
+
h, w = self.grid.height, self.grid.width
|
| 51 |
+
coastline = np.zeros((h, w), dtype=bool)
|
| 52 |
+
|
| 53 |
+
for r in range(h):
|
| 54 |
+
for c in range(w):
|
| 55 |
+
if underwater[r, c]:
|
| 56 |
+
continue # ๋ฐ๋ค๋ ํด์์ ์๋
|
| 57 |
+
|
| 58 |
+
# ์ธ์ ์
ํ์ธ
|
| 59 |
+
for dr in [-1, 0, 1]:
|
| 60 |
+
for dc in [-1, 0, 1]:
|
| 61 |
+
if dr == 0 and dc == 0:
|
| 62 |
+
continue
|
| 63 |
+
nr, nc = r + dr, c + dc
|
| 64 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 65 |
+
if underwater[nr, nc]:
|
| 66 |
+
coastline[r, c] = True
|
| 67 |
+
break
|
| 68 |
+
if coastline[r, c]:
|
| 69 |
+
break
|
| 70 |
+
|
| 71 |
+
return coastline
|
| 72 |
+
|
| 73 |
+
def calculate_wave_energy(self) -> np.ndarray:
|
| 74 |
+
"""
|
| 75 |
+
ํ๋ ์๋์ง ๋ถํฌ ๊ณ์ฐ
|
| 76 |
+
|
| 77 |
+
Returns:
|
| 78 |
+
energy: ๊ฐ ์
์ ํ๋ ์๋์ง
|
| 79 |
+
"""
|
| 80 |
+
h, w = self.grid.height, self.grid.width
|
| 81 |
+
|
| 82 |
+
# ๊ธฐ๋ณธ ์๋์ง = H^2 (ํ๊ณ ์ ๊ณฑ์ ๋น๋ก)
|
| 83 |
+
base_energy = self.wave_height ** 2
|
| 84 |
+
|
| 85 |
+
# ์์ฌ์ ๋ฐ๋ฅธ ๊ฐ์
|
| 86 |
+
# ๊น์ ๋ฐ๋ค: ์๋์ง ์ ์ง, ์์ ๊ณณ: ์๋์ง ์ง์ค ํ ์ํ
|
| 87 |
+
sea_depth = np.maximum(0, self.grid.sea_level - self.grid.elevation)
|
| 88 |
+
|
| 89 |
+
# ์ฒํด ํจ๊ณผ: ์์ฌ < ํ์ฅ/2 ์ผ ๋ ์๋์ง ์ฆ๊ฐ
|
| 90 |
+
wavelength = 1.56 * (self.wave_period ** 2) # ์ฌํด ํ์ฅ ๊ทผ์ฌ
|
| 91 |
+
depth_factor = np.ones((h, w))
|
| 92 |
+
|
| 93 |
+
shallow = sea_depth < wavelength / 2
|
| 94 |
+
depth_factor[shallow] = 1.0 + 0.5 * (1 - sea_depth[shallow] / (wavelength / 2))
|
| 95 |
+
|
| 96 |
+
energy = base_energy * depth_factor
|
| 97 |
+
|
| 98 |
+
# ์ก์ง๋ ์๋์ง 0
|
| 99 |
+
energy[~self.grid.is_underwater()] = 0
|
| 100 |
+
|
| 101 |
+
return energy
|
| 102 |
+
|
| 103 |
+
def erode_coast(self, coastline: np.ndarray,
|
| 104 |
+
wave_energy: np.ndarray,
|
| 105 |
+
rock_resistance: np.ndarray = None,
|
| 106 |
+
dt: float = 1.0) -> np.ndarray:
|
| 107 |
+
"""
|
| 108 |
+
ํด์ ์นจ์
|
| 109 |
+
|
| 110 |
+
Args:
|
| 111 |
+
coastline: ํด์์ ๋ง์คํฌ
|
| 112 |
+
wave_energy: ํ๋ ์๋์ง ๋ฐฐ์ด
|
| 113 |
+
rock_resistance: ์์ ์ ํญ๋ ฅ (0~1, ๋์์๋ก ์ ํญ)
|
| 114 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 115 |
+
|
| 116 |
+
Returns:
|
| 117 |
+
erosion: ์นจ์๋ ๋ฐฐ์ด
|
| 118 |
+
"""
|
| 119 |
+
h, w = self.grid.height, self.grid.width
|
| 120 |
+
|
| 121 |
+
if rock_resistance is None:
|
| 122 |
+
rock_resistance = np.ones((h, w)) * 0.5
|
| 123 |
+
|
| 124 |
+
erosion = np.zeros((h, w), dtype=np.float64)
|
| 125 |
+
|
| 126 |
+
# ํด์์ ์
์ ๋ํด ์นจ์ ๊ณ์ฐ
|
| 127 |
+
coast_coords = np.argwhere(coastline)
|
| 128 |
+
|
| 129 |
+
for r, c in coast_coords:
|
| 130 |
+
# ์ธ์ ๋ฐ๋ค ์
์ ํ๊ท ์๋์ง
|
| 131 |
+
adjacent_energy = 0
|
| 132 |
+
count = 0
|
| 133 |
+
|
| 134 |
+
for dr in [-1, 0, 1]:
|
| 135 |
+
for dc in [-1, 0, 1]:
|
| 136 |
+
if dr == 0 and dc == 0:
|
| 137 |
+
continue
|
| 138 |
+
nr, nc = r + dr, c + dc
|
| 139 |
+
if 0 <= nr < h and 0 <= nc < w:
|
| 140 |
+
if self.grid.is_underwater()[nr, nc]:
|
| 141 |
+
adjacent_energy += wave_energy[nr, nc]
|
| 142 |
+
count += 1
|
| 143 |
+
|
| 144 |
+
if count > 0:
|
| 145 |
+
avg_energy = adjacent_energy / count
|
| 146 |
+
# ์นจ์๋ฅ = K * Energy / Resistance
|
| 147 |
+
resistance = rock_resistance[r, c]
|
| 148 |
+
erosion[r, c] = self.K * avg_energy * (1 - resistance) * dt
|
| 149 |
+
|
| 150 |
+
return erosion
|
| 151 |
+
|
| 152 |
+
def longshore_drift(self, coastline: np.ndarray,
|
| 153 |
+
sediment_available: np.ndarray,
|
| 154 |
+
dt: float = 1.0) -> np.ndarray:
|
| 155 |
+
"""
|
| 156 |
+
์ฐ์๋ฅ์ ์ํ ํด์ ๋ฌผ ์ด๋
|
| 157 |
+
|
| 158 |
+
Args:
|
| 159 |
+
coastline: ํด์์ ๋ง์คํฌ
|
| 160 |
+
sediment_available: ์ด๋ ๊ฐ๋ฅํ ํด์ ๋ฌผ
|
| 161 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 162 |
+
|
| 163 |
+
Returns:
|
| 164 |
+
change: ํด์ ๋ฌผ ๋ณํ๋
|
| 165 |
+
"""
|
| 166 |
+
h, w = self.grid.height, self.grid.width
|
| 167 |
+
change = np.zeros((h, w), dtype=np.float64)
|
| 168 |
+
|
| 169 |
+
# ํ๋ ๋ฐฉํฅ์ ๋ฐ๋ฅธ ์ฐ์๋ฅ ๋ฐฉํฅ
|
| 170 |
+
# 0 = N, 90 = E, etc.
|
| 171 |
+
drift_dx = np.sin(self.wave_direction)
|
| 172 |
+
drift_dy = -np.cos(self.wave_direction) # Y์ถ ๋ฐ์
|
| 173 |
+
|
| 174 |
+
coast_coords = np.argwhere(coastline)
|
| 175 |
+
|
| 176 |
+
for r, c in coast_coords:
|
| 177 |
+
available = sediment_available[r, c]
|
| 178 |
+
if available <= 0:
|
| 179 |
+
continue
|
| 180 |
+
|
| 181 |
+
# ์ด๋๋
|
| 182 |
+
move_amount = min(available * 0.1 * dt, available)
|
| 183 |
+
|
| 184 |
+
# ๋ชฉํ ์
(์ฐ์๋ฅ ๋ฐฉํฅ)
|
| 185 |
+
tr = int(r + drift_dy)
|
| 186 |
+
tc = int(c + drift_dx)
|
| 187 |
+
|
| 188 |
+
if 0 <= tr < h and 0 <= tc < w:
|
| 189 |
+
if coastline[tr, tc] or self.grid.is_underwater()[tr, tc]:
|
| 190 |
+
change[r, c] -= move_amount
|
| 191 |
+
change[tr, tc] += move_amount
|
| 192 |
+
|
| 193 |
+
return change
|
| 194 |
+
|
| 195 |
+
def step(self, dt: float = 1.0,
|
| 196 |
+
rock_resistance: np.ndarray = None) -> dict:
|
| 197 |
+
"""
|
| 198 |
+
1๋จ๊ณ ํ๋ ์์ฉ ์คํ
|
| 199 |
+
|
| 200 |
+
Args:
|
| 201 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 202 |
+
rock_resistance: ์์ ์ ํญ๋ ฅ ๋ฐฐ์ด
|
| 203 |
+
|
| 204 |
+
Returns:
|
| 205 |
+
result: ์นจ์/ํด์ ๊ฒฐ๊ณผ
|
| 206 |
+
"""
|
| 207 |
+
# 1. ํด์์ ์๋ณ
|
| 208 |
+
coastline = self.identify_coastline()
|
| 209 |
+
|
| 210 |
+
if not np.any(coastline):
|
| 211 |
+
return {'erosion': np.zeros_like(self.grid.elevation),
|
| 212 |
+
'drift': np.zeros_like(self.grid.elevation)}
|
| 213 |
+
|
| 214 |
+
# 2. ํ๋ ์๋์ง ๊ณ์ฐ
|
| 215 |
+
wave_energy = self.calculate_wave_energy()
|
| 216 |
+
|
| 217 |
+
# 3. ํด์ ์นจ์
|
| 218 |
+
erosion = self.erode_coast(coastline, wave_energy, rock_resistance, dt)
|
| 219 |
+
|
| 220 |
+
# ์นจ์ ์ ์ฉ (bedrock ๊ฐ์)
|
| 221 |
+
self.grid.bedrock -= erosion
|
| 222 |
+
|
| 223 |
+
# ์นจ์๋ ์์ ํด์ ๋ฌผ๋ก ๋ณํ
|
| 224 |
+
self.grid.sediment += erosion * 0.8 # ์ผ๋ถ ์ ์ค
|
| 225 |
+
|
| 226 |
+
# 4. ์ฐ์๋ฅ (Longshore Drift)
|
| 227 |
+
drift = self.longshore_drift(coastline, self.grid.sediment, dt)
|
| 228 |
+
self.grid.sediment += drift
|
| 229 |
+
|
| 230 |
+
# ๊ณ ๋ ๋๊ธฐํ
|
| 231 |
+
self.grid.update_elevation()
|
| 232 |
+
|
| 233 |
+
return {
|
| 234 |
+
'erosion': erosion,
|
| 235 |
+
'drift': drift
|
| 236 |
+
}
|
engine/wind.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Wind Kernel (๋ฐ๋ ์ปค๋)
|
| 3 |
+
|
| 4 |
+
ํ์ฑ ์งํ ํ์ฑ ํ๋ก์ธ์ค
|
| 5 |
+
- ํ์ (Deflation): ๋ฏธ์ธ ์
์ ์ ๊ฑฐ
|
| 6 |
+
- ๋ง์ (Abrasion): ๋ชจ๋ ์ถฉ๋์ ์ํ ์นจ์
|
| 7 |
+
- ํ์ (Aeolian Deposition): ์ฌ๊ตฌ ํ์ฑ
|
| 8 |
+
|
| 9 |
+
ํต์ฌ:
|
| 10 |
+
- ๋ฐ๋ ์๋์ ๋น๋กํ ์ด๋ฐ๋ ฅ
|
| 11 |
+
- ์
์ ํฌ๊ธฐ์ ๋ฐ๋ฅธ ์ ํ์ ์ด๋ฐ
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import numpy as np
|
| 15 |
+
from .grid import WorldGrid
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class WindKernel:
|
| 19 |
+
"""
|
| 20 |
+
๋ฐ๋ ์ปค๋
|
| 21 |
+
|
| 22 |
+
๊ฑด์กฐ ์ง์ญ์์์ ํ์๊ณผ ํ์ ์ ์๋ฎฌ๋ ์ด์
.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self, grid: WorldGrid,
|
| 26 |
+
wind_speed: float = 10.0, # m/s
|
| 27 |
+
wind_direction: float = 45.0, # degrees from N
|
| 28 |
+
K_erosion: float = 0.0001,
|
| 29 |
+
sand_threshold: float = 0.1): # ์ฌ๊ตฌ ํ์ฑ ์๊ณ ํด์ ๋
|
| 30 |
+
self.grid = grid
|
| 31 |
+
self.wind_speed = wind_speed
|
| 32 |
+
self.wind_direction = np.radians(wind_direction)
|
| 33 |
+
self.K = K_erosion
|
| 34 |
+
self.sand_threshold = sand_threshold
|
| 35 |
+
|
| 36 |
+
def get_wind_vector(self) -> tuple:
|
| 37 |
+
"""
|
| 38 |
+
๋ฐ๋ ๋ฐฉํฅ ๋ฒกํฐ ๋ฐํ
|
| 39 |
+
|
| 40 |
+
Returns:
|
| 41 |
+
(dy, dx): ๋ฐ๋ ๋ฐฉํฅ ๋จ์ ๋ฒกํฐ
|
| 42 |
+
"""
|
| 43 |
+
dx = np.sin(self.wind_direction)
|
| 44 |
+
dy = -np.cos(self.wind_direction) # Y์ถ ๋ฐ์ (ํ๋ฉด ์ขํ๊ณ)
|
| 45 |
+
return dy, dx
|
| 46 |
+
|
| 47 |
+
def calculate_transport_capacity(self,
|
| 48 |
+
vegetation_cover: np.ndarray = None) -> np.ndarray:
|
| 49 |
+
"""
|
| 50 |
+
๋ชจ๋ ์ด๋ฐ๋ ฅ ๊ณ์ฐ
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
vegetation_cover: ์์ ํผ๋ณต๋ฅ (0~1, ๋์ผ๋ฉด ์ด๋ฐ๋ ฅ ๊ฐ์)
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
capacity: ์ด๋ฐ๋ ฅ ๋ฐฐ์ด
|
| 57 |
+
"""
|
| 58 |
+
h, w = self.grid.height, self.grid.width
|
| 59 |
+
|
| 60 |
+
# ๊ธฐ๋ณธ ์ด๋ฐ๋ ฅ = ํ์^3 (๊ฒฝํ์)
|
| 61 |
+
base_capacity = (self.wind_speed ** 3) * self.K
|
| 62 |
+
|
| 63 |
+
capacity = np.ones((h, w)) * base_capacity
|
| 64 |
+
|
| 65 |
+
# ์์ ํจ๊ณผ (์์ผ๋ฉด ์ด๋ฐ๋ ฅ ๊ฐ์)
|
| 66 |
+
if vegetation_cover is not None:
|
| 67 |
+
capacity *= (1 - vegetation_cover)
|
| 68 |
+
|
| 69 |
+
# ๊ฒฝ์ฌ ํจ๊ณผ (๋ฐ๋๋ฐ์ด vs ๋ฐ๋๊ทธ๋)
|
| 70 |
+
slope, aspect = self.grid.get_gradient()
|
| 71 |
+
|
| 72 |
+
# ๋ฐ๋๋ฐ์ด = ํํฅ๊ณผ ๋ฐ๋ ๊ฒฝ์ฌ๋ฉด โ ๊ฐ์ โ ํด์
|
| 73 |
+
# ๋ฐ๋๊ทธ๋ = ํํฅ๊ณผ ๊ฐ์ ๊ฒฝ์ฌ๋ฉด โ ๊ฐ์ โ ์นจ์
|
| 74 |
+
dy, dx = self.get_wind_vector()
|
| 75 |
+
|
| 76 |
+
# ๊ฒฝ์ฌ๋ฉด์ ๋
ธ์ถ๋ (๋ฐ๋๊ณผ ๊ฒฝ์ฌ ๋ฐฉํฅ์ ๋ด์ )
|
| 77 |
+
exposure = dx * np.gradient(self.grid.elevation, axis=1) + \
|
| 78 |
+
dy * np.gradient(self.grid.elevation, axis=0)
|
| 79 |
+
|
| 80 |
+
# ๋
ธ์ถ๋์ ๋ฐ๋ฅธ ์ด๋ฐ๋ ฅ ์กฐ์
|
| 81 |
+
capacity *= (1 + 0.5 * np.clip(exposure, -1, 1))
|
| 82 |
+
|
| 83 |
+
# ํด์๋ฉด ์๋๋ 0
|
| 84 |
+
capacity[self.grid.is_underwater()] = 0
|
| 85 |
+
|
| 86 |
+
return np.maximum(capacity, 0)
|
| 87 |
+
|
| 88 |
+
def deflation(self, capacity: np.ndarray, dt: float = 1.0) -> np.ndarray:
|
| 89 |
+
"""
|
| 90 |
+
ํ์ (Deflation) - ๋ฏธ์ธ ์
์ ์ ๊ฑฐ
|
| 91 |
+
|
| 92 |
+
Args:
|
| 93 |
+
capacity: ์ด๋ฐ๋ ฅ ๋ฐฐ์ด
|
| 94 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 95 |
+
|
| 96 |
+
Returns:
|
| 97 |
+
erosion: ์นจ์๋ ๋ฐฐ์ด
|
| 98 |
+
"""
|
| 99 |
+
h, w = self.grid.height, self.grid.width
|
| 100 |
+
|
| 101 |
+
# ์นจ์๋ = ์ด๋ฐ๋ ฅ * dt (ํด์ ์ธต์์๋ง)
|
| 102 |
+
available = self.grid.sediment
|
| 103 |
+
erosion = np.minimum(capacity * dt, available)
|
| 104 |
+
|
| 105 |
+
# ํด์ ์ธต ๊ฐ์
|
| 106 |
+
self.grid.sediment -= erosion
|
| 107 |
+
|
| 108 |
+
return erosion
|
| 109 |
+
|
| 110 |
+
def transport_and_deposit(self,
|
| 111 |
+
eroded_material: np.ndarray,
|
| 112 |
+
capacity: np.ndarray,
|
| 113 |
+
dt: float = 1.0) -> np.ndarray:
|
| 114 |
+
"""
|
| 115 |
+
ํ์ (Aeolian Deposition) - ์ฌ๊ตฌ ํ์ฑ
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
eroded_material: ์นจ์๋ ๋ฌผ์ง๋
|
| 119 |
+
capacity: ์ด๋ฐ๋ ฅ ๋ฐฐ์ด
|
| 120 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 121 |
+
|
| 122 |
+
Returns:
|
| 123 |
+
deposition: ํด์ ๋ ๋ฐฐ์ด
|
| 124 |
+
"""
|
| 125 |
+
h, w = self.grid.height, self.grid.width
|
| 126 |
+
|
| 127 |
+
dy, dx = self.get_wind_vector()
|
| 128 |
+
|
| 129 |
+
# ๋ฌผ์ง ์ด๋
|
| 130 |
+
deposition = np.zeros((h, w), dtype=np.float64)
|
| 131 |
+
|
| 132 |
+
for r in range(h):
|
| 133 |
+
for c in range(w):
|
| 134 |
+
if eroded_material[r, c] <= 0:
|
| 135 |
+
continue
|
| 136 |
+
|
| 137 |
+
# ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ์ด๋
|
| 138 |
+
tr = int(r + dy * 2) # 2์
์ด๋
|
| 139 |
+
tc = int(c + dx * 2)
|
| 140 |
+
|
| 141 |
+
if not (0 <= tr < h and 0 <= tc < w):
|
| 142 |
+
continue
|
| 143 |
+
|
| 144 |
+
# ๋ชฉํ ์ง์ ์ ์ด๋ฐ๋ ฅ ํ์ธ
|
| 145 |
+
if capacity[tr, tc] < capacity[r, c]:
|
| 146 |
+
# ์ด๋ฐ๋ ฅ ๊ฐ์ โ ํด์
|
| 147 |
+
deposit_amount = eroded_material[r, c] * (1 - capacity[tr, tc] / capacity[r, c])
|
| 148 |
+
deposition[tr, tc] += deposit_amount
|
| 149 |
+
else:
|
| 150 |
+
# ๊ณ์ ์ด๋ฐ (๋ค์ ์
๋ก)
|
| 151 |
+
# ๊ฐ๋จํ ์ํด ์ผ๋ถ๋ง ํด์
|
| 152 |
+
deposition[tr, tc] += eroded_material[r, c] * 0.1
|
| 153 |
+
|
| 154 |
+
# ํด์ ์ ์ฉ
|
| 155 |
+
self.grid.add_sediment(deposition)
|
| 156 |
+
|
| 157 |
+
return deposition
|
| 158 |
+
|
| 159 |
+
def form_barchan(self, iteration: int = 5):
|
| 160 |
+
"""
|
| 161 |
+
๋ฐ๋ฅดํ ์ฌ๊ตฌ ํ์ฑ (๋ฐ๋ณต ์๋ฎฌ๋ ์ด์
)
|
| 162 |
+
|
| 163 |
+
๋ฐ๋๋ฐ์ด: ์๊ฒฝ์ฌ, ๋ฐ๋๊ทธ๋: ๊ธ๊ฒฝ์ฌ (Slip Face)
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
iteration: ํํ ๋ค๋ฌ๊ธฐ ๋ฐ๋ณต ํ์
|
| 167 |
+
"""
|
| 168 |
+
h, w = self.grid.height, self.grid.width
|
| 169 |
+
dy, dx = self.get_wind_vector()
|
| 170 |
+
|
| 171 |
+
# ์ฌ๊ตฌ ํ๋ณด (ํด์ ๋ฌผ ๋ง์ ๊ณณ)
|
| 172 |
+
dune_mask = self.grid.sediment > self.sand_threshold
|
| 173 |
+
|
| 174 |
+
for _ in range(iteration):
|
| 175 |
+
# ๋ฐ๋๋ฐ์ด ์ชฝ ์๋งํ๊ฒ
|
| 176 |
+
for r in range(1, h - 1):
|
| 177 |
+
for c in range(1, w - 1):
|
| 178 |
+
if not dune_mask[r, c]:
|
| 179 |
+
continue
|
| 180 |
+
|
| 181 |
+
# ๋ฐ๋๋ฐ์ด ์ด์
|
| 182 |
+
wr, wc = int(r - dy), int(c - dx)
|
| 183 |
+
if 0 <= wr < h and 0 <= wc < w:
|
| 184 |
+
# ๊ฒฝ์ฌ ์ํ
|
| 185 |
+
avg = (self.grid.sediment[r, c] + self.grid.sediment[wr, wc]) / 2
|
| 186 |
+
self.grid.sediment[r, c] = self.grid.sediment[r, c] * 0.9 + avg * 0.1
|
| 187 |
+
|
| 188 |
+
self.grid.update_elevation()
|
| 189 |
+
|
| 190 |
+
def step(self, vegetation_cover: np.ndarray = None,
|
| 191 |
+
dt: float = 1.0) -> dict:
|
| 192 |
+
"""
|
| 193 |
+
1๋จ๊ณ ๋ฐ๋ ์์ฉ ์คํ
|
| 194 |
+
|
| 195 |
+
Args:
|
| 196 |
+
vegetation_cover: ์์ ํผ๋ณต๋ฅ
|
| 197 |
+
dt: ์๊ฐ ๊ฐ๊ฒฉ
|
| 198 |
+
|
| 199 |
+
Returns:
|
| 200 |
+
result: ์นจ์/ํด์ ๊ฒฐ๊ณผ
|
| 201 |
+
"""
|
| 202 |
+
# 1. ์ด๋ฐ๋ ฅ ๊ณ์ฐ
|
| 203 |
+
capacity = self.calculate_transport_capacity(vegetation_cover)
|
| 204 |
+
|
| 205 |
+
# 2. ํ์
|
| 206 |
+
erosion = self.deflation(capacity, dt)
|
| 207 |
+
|
| 208 |
+
# 3. ์ด๋ ๋ฐ ํด์
|
| 209 |
+
deposition = self.transport_and_deposit(erosion, capacity, dt)
|
| 210 |
+
|
| 211 |
+
return {
|
| 212 |
+
'erosion': erosion,
|
| 213 |
+
'deposition': deposition,
|
| 214 |
+
'capacity': capacity
|
| 215 |
+
}
|
image list.txt
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
๐ [Chapter 1. ํ์ฒ ์งํ (River Landforms)]
|
| 2 |
+
ํ์ ์ด๋ฏธ์ง (5์ฅ)
|
| 3 |
+
|
| 4 |
+
1. V์๊ณก๊ณผ ํ๊ณก (V-shaped Valley)
|
| 5 |
+
Filename: v_shaped_valley_diagram.jpg
|
| 6 |
+
Point: ๊ฐ๋ฐ๋ฅ์ด ๊น๊ฒ ํ์ด๊ณ ์์์ด ๊ฒฝ์ฌ์ง ๋จ๋ฉด๋๊ฐ ํฌํจ๋ ๊ทธ๋ฆผ.
|
| 7 |
+
|
| 8 |
+
2. ์ ์์ง (Alluvial Fan)
|
| 9 |
+
Filename: alluvial_fan_structure.jpg
|
| 10 |
+
Point: ๋ถ์ฑ๊ผด ๋ชจ์์ ํ๋ฉด๋ + ๋
์(์ ์ /์ ์/์ ๋จ) ์
์ ํฌ๊ธฐ๊ฐ ๋ฌ์ฌ๋ ๊ทธ๋ฆผ.
|
| 11 |
+
|
| 12 |
+
3. ๊ณก๋ฅ ํ์ฒ ๋จ๋ฉด (Meander Cross-section)
|
| 13 |
+
Filename: meander_cross_section.jpg
|
| 14 |
+
Point: ๊ณต๊ฒฉ์ฌ๋ฉด(๊น์)์ ์ ๋ฒฝ, ํด์ ์ฌ๋ฉด(์์)์ ์๋งํ๊ฒ ๋น๋์นญ์ผ๋ก ๊ทธ๋ ค์ง ๋จ๋ฉด๋. (๊ฐ์ฅ ์ค์!)
|
| 15 |
+
|
| 16 |
+
4. ํ์๋จ๊ตฌ (River Terrace)
|
| 17 |
+
Filename: river_terrace_formation.jpg
|
| 18 |
+
Point: ๊ฐ ์์ ๊ณ๋จ์์ผ๋ก ํํํ ๋
์ด ์ธต์ธต์ด ์๋ ๊ทธ๋ฆผ.
|
| 19 |
+
|
| 20 |
+
5. ์ผ๊ฐ์ฃผ ์ ํ (Delta Types)
|
| 21 |
+
Filename: delta_classification.jpg
|
| 22 |
+
Point: ์๋ฐ ๋ชจ์(๋ฏธ์์ํผ), ๋ถ์ฑ ๋ชจ์(๋์ผ), ์ฒจ๊ฐ์(ํฐ๋ฒ ๋ฅด) 3๊ฐ์ง๊ฐ ๋น๊ต๋ ๊ทธ๋ฆผ.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
๐๏ธ [Chapter 2. ํด์ ์งํ (Coastal Landforms)]
|
| 27 |
+
ํ์ ์ด๋ฏธ์ง (4์ฅ)
|
| 28 |
+
|
| 29 |
+
1. ํด์์ ์ ํ์๋ (Sea Cliff & Wave-cut Platform)
|
| 30 |
+
Filename: sea_cliff_erosion.jpg
|
| 31 |
+
Point: ์ ๋ฒฝ ๋ฐ์ด ํ์ฌ ์๊ณ (Notch), ์ ๋ฒฝ ์๋ฐ๋ค ๋ฐ๋ฅ์ด ํํํ(Platform) ๊ทธ๋ฆผ.
|
| 32 |
+
|
| 33 |
+
2. ์์คํ ํ์ฑ ๊ณผ์ (Sea Stack Evolution)
|
| 34 |
+
Filename: sea_stack_formation.jpg
|
| 35 |
+
Point: ๋๊ตด โ ์์น โ ์์คํ โ ์์คํ
ํ ์์๊ฐ ํ ์ฅ์ ๊ทธ๋ ค์ง ๊ทธ๋ฆผ.
|
| 36 |
+
|
| 37 |
+
3. ํด์ ํด์ ์งํ (Coastal Deposition)
|
| 38 |
+
Filename: sand_spit_bar_tombolo.jpg
|
| 39 |
+
Point: ์ฌ์ทจ(Spit), ์ฌ์ฃผ(Bar), ์ก๊ณ์ฌ์ฃผ(Tombolo), ์ํธ(Lagoon)๊ฐ ๊ทธ๋ ค์ง ์ง๋ํ ๊ทธ๋ฆผ.
|
| 40 |
+
|
| 41 |
+
4. ๊ฐฏ๋ฒ ๋จ๋ฉด (Mudflat Profile)
|
| 42 |
+
Filename: tidal_flat_zonation.jpg
|
| 43 |
+
Point: ๋ฐ๋ฌผ/์ฐ๋ฌผ ์์ ํ์(High/Low tide)์ ๊ฐฏ๋ฒ์ ๊ฒฝ์ฌ๊ฐ ๊ทธ๋ ค์ง ๋จ๋ฉด๋.
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
๐ฆ [Chapter 3. ์นด๋ฅด์คํธ ์งํ (Karst Landforms)]
|
| 48 |
+
ํ์ ์ด๋ฏธ์ง (4์ฅ)
|
| 49 |
+
|
| 50 |
+
1. ํ์ฐ์นผ์ ์ฉ์ ๋ฐ์ (Chemical Weathering Reaction)
|
| 51 |
+
Filename: chemical_weathering_reaction.png
|
| 52 |
+
Point: CaCOโ + HโO + COโ โ Ca(HCOโ)โ ํํ์ ๋ฐ ๋ชจ์๋.
|
| 53 |
+
|
| 54 |
+
2. ๋๋ฆฌ๋ค-์ฐ๋ฐ๋ผ-ํด๋ฆฌ์ ๋ณํ (Doline Progression)
|
| 55 |
+
Filename: doline_uvala_polje_progression.jpg
|
| 56 |
+
Point: ๋๋ฆฌ๋ค์์ ํด๋ฆฌ์๋ก ์ปค์ง๋ ๋จ๊ณ๋ณ ์ง๋.
|
| 57 |
+
|
| 58 |
+
3. ์ํ ๋๊ตด ๋ด๋ถ ๊ตฌ์กฐ (Cave Interior)
|
| 59 |
+
Filename: cave_interior_structure.jpg
|
| 60 |
+
Point: ์ข
์ ์, ์์, ์์ฃผ๊ฐ ์๋ ๋๊ตด ๋ด๋ถ ๋จ๋ฉด๋.
|
| 61 |
+
|
| 62 |
+
4. ํ ์นด๋ฅด์คํธ (Tower Karst)
|
| 63 |
+
Filename: tower_karst_landscape.jpg
|
| 64 |
+
Point: ํ ์นด๋ฅด์คํธ์ ์งํ์ ํน์ง (ํ๋กฑ๋ฒ ์ด, ๊ตฌ์ด๋ฆฐ ์คํ์ผ).
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
๐ [Chapter 4. ํ์ฐ ์งํ (Volcanic Landforms)]
|
| 69 |
+
ํ์ ์ด๋ฏธ์ง (4์ฅ)
|
| 70 |
+
|
| 71 |
+
1. ํ์ฐ ํํ ๋น๊ต (Volcano Shape by Viscosity)
|
| 72 |
+
Filename: volcano_shape_viscosity.png
|
| 73 |
+
Point: ์์ vs ์ฑ์ธต vs ์ข
์ ํ์ฐ์ ๋จ๋ฉด ๋น๊ต๋.
|
| 74 |
+
|
| 75 |
+
2. ์นผ๋ฐ๋ผ ํ์ฑ ๊ณผ์ (Caldera Formation)
|
| 76 |
+
Filename: caldera_formation_steps.gif
|
| 77 |
+
Point: ๋ง๊ทธ๋ง ๋ฐฉ ๋น์ โ ๋ถ๊ดด โ ํธ์ ํ์ฑ 3๋จ๊ณ.
|
| 78 |
+
|
| 79 |
+
3. ์ฃผ์์ ๋ฆฌ (Columnar Jointing)
|
| 80 |
+
Filename: columnar_jointing_hex.jpg
|
| 81 |
+
Point: ์ฃผ์์ ๋ฆฌ์ ์ก๊ฐํ ๋จ๋ฉด ๋ฐ ์ธก๋ฉด ๊ธฐ๋ฅ ๊ตฌ์กฐ.
|
| 82 |
+
|
| 83 |
+
4. ํ๊ตญ ํ์ฐ ์งํ ์ง๋ (Korea Volcanic Map)
|
| 84 |
+
Filename: korea_volcanic_map.png
|
| 85 |
+
Point: ๋ฐฑ๋์ฐ, ์ ์ฃผ๋, ์ธ๋ฆ๋, ์ฒ ์ ์ฉ์๋์ง ์์น ์ง๋.
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
โ๏ธ [Chapter 5. ๋นํ ์งํ (Glacial Landforms)]
|
| 90 |
+
ํ์ ์ด๋ฏธ์ง (4์ฅ)
|
| 91 |
+
|
| 92 |
+
1. V์๊ณก์์ U์๊ณก์ผ๋ก ๋ณํ (Valley Transformation)
|
| 93 |
+
Filename: valley_transformation_V_to_U.gif
|
| 94 |
+
Point: V์๊ณก์์ U์๊ณก์ผ๋ก ๋ณํ๋๋ 3D ๋ชจ๋ธ.
|
| 95 |
+
|
| 96 |
+
2. ํผ์ค๋ฅด ๋จ๋ฉด (Fjord Cross-section)
|
| 97 |
+
Filename: fjord_cross_section.jpg
|
| 98 |
+
Point: ๋ฐ๋ท๋ฌผ์ ์ ๊ธด U์๊ณก ๋จ๋ฉด๋.
|
| 99 |
+
|
| 100 |
+
3. ๋นํด์ ๋ถ๊ธ ๋ถ๋ (Glacial Till Unsorted)
|
| 101 |
+
Filename: glacial_till_unsorted.png
|
| 102 |
+
Point: ๋นํด์์ ๋ค์์ธ ์๊ฐ ๋จ๋ฉด - ๋ถ๊ธ ๋ถ๋ ์์.
|
| 103 |
+
|
| 104 |
+
4. ๋๋ผ๋ฆฐ๊ณผ ๋นํ ์ด๋ ๋ฐฉํฅ (Drumlin Ice Flow)
|
| 105 |
+
Filename: drumlin_ice_flow.jpg
|
| 106 |
+
Point: ๋๋ผ๋ฆฐ์ ํํ์ ๋นํ ์ด๋ ๋ฐฉํฅ ๊ด๊ณ๋.
|
| 107 |
+
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
๐๏ธ [Chapter 6. ๊ฑด์กฐ ์งํ (Arid Landforms)]
|
| 111 |
+
ํ์ ์ด๋ฏธ์ง (4์ฅ)
|
| 112 |
+
|
| 113 |
+
1. ๋ฐ๋ฅดํ ์ฌ๊ตฌ์ ๋ฐ๋ ๋ฐฉํฅ (Barchan Dune)
|
| 114 |
+
Filename: barchan_dune_wind_direction.jpg
|
| 115 |
+
Point: ๋ฐ๋ฅดํ ์ฌ๊ตฌ์ ํํ์ ๋ฐ๋ ๋ฐฉํฅ ํ์ดํ.
|
| 116 |
+
|
| 117 |
+
2. ์ ์์ง ๊ตฌ์กฐ (Alluvial Fan in Arid)
|
| 118 |
+
Filename: alluvial_fan_structure.png
|
| 119 |
+
Point: ์ ์์ง์ ์ ์ , ์ ์, ์ ๋จ ๋จ๋ฉด๋ ๋ฐ ์
์ ํฌ๊ธฐ ๋ถํฌ.
|
| 120 |
+
|
| 121 |
+
3. ๋ฉ์ฌ-๋ทฐํธ ์นจ์ ๋จ๊ณ (Mesa-Butte Evolution)
|
| 122 |
+
Filename: mesa_butte_evolution.jpg
|
| 123 |
+
Point: ๊ณ ์ โ ๋ฉ์ฌ โ ๋ทฐํธ โ ์คํ์ด์ด ์นจ์ ๋จ๊ณ.
|
| 124 |
+
|
| 125 |
+
4. ๋ฒ์ฏ๋ฐ์ ํ์ฑ (Mushroom Rock)
|
| 126 |
+
Filename: mushroom_rock_formation.gif
|
| 127 |
+
Point: ๋ชจ๋๋ฐ๋์ ์ํ ํ๋จ๋ถ ์นจ์ ์ ๋๋ฉ์ด์
.
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
๐พ [Chapter 7. ํ์ผ ์งํ (Plain Landforms)]
|
| 132 |
+
ํ์ ์ด๋ฏธ์ง (4์ฅ)
|
| 133 |
+
|
| 134 |
+
1. ๊ณก๋ฅ ํ์ฒ ์งํ (Meandering River Evolution)
|
| 135 |
+
Filename: meandering_river_evolution.gif
|
| 136 |
+
Point: ๊ณก๋ฅ ํ์ฒ์ด ์ฐ๊ฐํธ๋ก ๋ณํ๋ 4๋จ๊ณ ๊ณผ์ .
|
| 137 |
+
|
| 138 |
+
2. ๋ฒ๋์ ๋จ๋ฉด (Floodplain Cross-section)
|
| 139 |
+
Filename: floodplain_cross_section.png
|
| 140 |
+
Point: ํ๋ - ์์ฐ์ ๋ฐฉ - ๋ฐฐํ์ต์ง ๋จ๋ฉด๋ ๋ฐ ํ ์ง ์ด์ฉ.
|
| 141 |
+
|
| 142 |
+
3. ์ผ๊ฐ์ฃผ ์ ํ ์์ฑ ์ฌ์ง (Delta Types Satellite)
|
| 143 |
+
Filename: delta_types_satellite.jpg
|
| 144 |
+
Point: ๋์ผ๊ฐ, ๋ฏธ์์ํผ๊ฐ, ๊ฐ ์ง์ค๊ฐ ์ผ๊ฐ์ฃผ ์์ฑ ์ฌ์ง ๋น๊ต.
|
| 145 |
+
|
| 146 |
+
4. ์์ฐ์ ๋ฐฉ vs ๋ฐฐํ์ต์ง ํ ์ (Levee vs Backswamp Soil)
|
| 147 |
+
Filename: levee_vs_backswamp_soil.jpg
|
| 148 |
+
Point: ์์ฐ์ ๋ฐฉ์ ๋ชจ๋ vs ๋ฐฐํ์ต์ง์ ์ ํ ์
์ ๋น๊ต.
|
| 149 |
+
|
| 150 |
+
---
|
| 151 |
+
|
| 152 |
+
๐ [์ด๊ณ]
|
| 153 |
+
- ํ์ฒ ์งํ: 5์ฅ
|
| 154 |
+
- ํด์ ์งํ: 4์ฅ
|
| 155 |
+
- ์นด๋ฅด์คํธ ์งํ: 4์ฅ
|
| 156 |
+
- ํ์ฐ ์งํ: 4์ฅ
|
| 157 |
+
- ๋นํ ์งํ: 4์ฅ
|
| 158 |
+
- ๊ฑด์กฐ ์งํ: 4์ฅ
|
| 159 |
+
- ํ์ผ ์งํ: 4์ฅ
|
| 160 |
+
- **์ดํฉ: 29์ฅ**
|
pyproject.toml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[tool.poetry]
|
| 2 |
+
name = "geo-lab-ai"
|
| 3 |
+
version = "1.0.0"
|
| 4 |
+
description = "๊ต์ก์ฉ ์งํ ์๋ฎฌ๋ ์ด์
- ํ์ฒ ์งํ ๋ชจ๋"
|
| 5 |
+
authors = ["Geo-Lab Team"]
|
| 6 |
+
readme = "README.md"
|
| 7 |
+
|
| 8 |
+
[tool.poetry.dependencies]
|
| 9 |
+
python = "^3.9"
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.28.0
|
| 2 |
+
numpy>=1.24.0
|
| 3 |
+
plotly>=5.18.0
|
| 4 |
+
matplotlib>=3.7.0
|
| 5 |
+
scipy>=1.11.0
|
| 6 |
+
Pillow>=10.0.0
|
run_log.txt
ADDED
|
Binary file (2.01 kB). View file
|
|
|
run_trace.txt
ADDED
|
Binary file (3.4 kB). View file
|
|
|
run_trace_soft.txt
ADDED
|
Binary file (1.94 kB). View file
|
|
|
test_genesis.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from engine.grid import WorldGrid
|
| 3 |
+
from engine.system import EarthSystem
|
| 4 |
+
|
| 5 |
+
def test_fan_mechanism():
|
| 6 |
+
print("Testing Project Genesis Alluvial Fan Mechanism...")
|
| 7 |
+
|
| 8 |
+
# 1. Setup Mini Grid (20x20)
|
| 9 |
+
grid = WorldGrid(width=20, height=20, cell_size=10.0)
|
| 10 |
+
|
| 11 |
+
# Slope N->S
|
| 12 |
+
for r in range(20):
|
| 13 |
+
grid.bedrock[r, :] = 20.0 - r * 0.5 # 20m -> 10m
|
| 14 |
+
|
| 15 |
+
grid.update_elevation()
|
| 16 |
+
|
| 17 |
+
# 2. Setup Engine
|
| 18 |
+
engine = EarthSystem(grid)
|
| 19 |
+
engine.erosion.K = 0.001 # Low K to force deposition
|
| 20 |
+
|
| 21 |
+
# 3. Step with sediment source
|
| 22 |
+
# Source at (2, 10)
|
| 23 |
+
source_y, source_x = 2, 10
|
| 24 |
+
amount = 100.0
|
| 25 |
+
|
| 26 |
+
settings = {
|
| 27 |
+
'precipitation': 0.0,
|
| 28 |
+
'rain_source': (0, 10, 2, 10.0), # Rain at top
|
| 29 |
+
'sediment_source': (source_y, source_x, 1, amount) # Sediment at source
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
print(f"Initial Max Sediment: {grid.sediment.max()}")
|
| 33 |
+
|
| 34 |
+
# Run 10 steps
|
| 35 |
+
np.set_printoptions(precision=4, suppress=True)
|
| 36 |
+
for i in range(10):
|
| 37 |
+
engine.step(dt=1.0, settings=settings)
|
| 38 |
+
print(f"Step {i+1}:")
|
| 39 |
+
print(f" Max Sediment: {grid.sediment.max():.6f}")
|
| 40 |
+
print(f" Max Discharge: {grid.discharge.max():.6f}")
|
| 41 |
+
print(f" Max Elev: {grid.elevation.max():.6f}")
|
| 42 |
+
|
| 43 |
+
if grid.sediment.max() > 0:
|
| 44 |
+
print("SUCCESS: Sediment is depositing.")
|
| 45 |
+
print("Sample Sediment Grid (Center):")
|
| 46 |
+
print(grid.sediment[0:5, 8:13])
|
| 47 |
+
else:
|
| 48 |
+
print("FAILURE: No sediment deposited.")
|
| 49 |
+
|
| 50 |
+
if __name__ == "__main__":
|
| 51 |
+
test_fan_mechanism()
|
test_output.txt
ADDED
|
Binary file (2.66 kB). View file
|
|
|