commit 21ebb16fb7d29cf5c1c05ccc3dc77c4f4200fc24 Author: tpstn23 Date: Fri Oct 30 11:00:46 2026 +0900 SPipe 데모 v2 (2026-10-30) 서버 배포본 - SPipe_server_2026-10-30.zip 내용을 풀어서 등록 (압축 파일은 올리지 않음) - README에 데모 v2 서버 업데이트 순서 추가, 저장소에 없는 문서 목차 정리 - 패치내역을 docs/로 이동, _worklog.md 추가 - 임시 파일(backend/temp.*)과 API 키가 든 파일은 제외 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8fd94ab --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +* +!package.json +!package-lock.json +!tsconfig.json +!vite.config.ts +!index.html +!src/ +!src/** +!public/ +!public/** +!backend/ +!backend/__init__.py +!backend/main.py +!backend/epanet.py +!backend/columnar.py +!deploy/ +!deploy/** diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7dbeb9b --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# 지도 서비스 키 (이 파일을 .env.local 로 복사해 값을 채움. .env.local 은 커밋 금지) +# 브이월드 배경지도 API 키 +VITE_VWORLD_API_KEY= +# 카카오 JavaScript 키 (주소 검색). 콘솔의 JavaScript SDK 도메인에 접속 주소를 등록해야 함 +VITE_KAKAO_JS_KEY= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..963c944 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +node_modules/ +dist/ +.venv/ +__pycache__/ +*.pyc +*.rpt +*.out +.vite/ + +# wntr / EPANET scratch outputs +backend/temp.* +temp.bin +temp.inp + +# Local-only material (never part of the deployable app) +promo-video/ +.qa-tmp/ +.claude/ +*.log +*.hwp +*.hwpx +*.pdf +samples/ +# Real customer network data stays off the repository +1.0상리*.inp + +# Server-side DEM store (uploaded rasters) +data/ + +# Map service keys (copy .env.example to .env.local) +.env +.env.local +.env.*.local diff --git a/64bit/epanet2.dll b/64bit/epanet2.dll new file mode 100644 index 0000000..f57b5ad Binary files /dev/null and b/64bit/epanet2.dll differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c716a6 --- /dev/null +++ b/README.md @@ -0,0 +1,409 @@ +# SPipe + +## 데모 v2 서버 업데이트 (2026-10-30) + +이전 배포: 데모 v1 (2026-10-15). 변경 내역은 `docs/SPipe_패치내역_v2_2026-10-30.txt`를 확인합니다. + +1. 저장소의 소스와 실행 파일을 기존 SPipe 폴더에 반영합니다. 기존 `.venv`, `node_modules`, `data`와 `.env.local`은 유지합니다. +2. SPipe 폴더에서 새 라이브러리를 설치합니다. scipy, rasterio 등이 추가되었으므로 반드시 실행합니다. + +```powershell +.\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt +``` + +3. 화면을 다시 빌드하면서 서버를 켭니다. `-Rebuild` 없이 실행하면 예전 화면이 남을 수 있습니다. + +```powershell +powershell -ExecutionPolicy Bypass -File .\start-lan.ps1 -Rebuild +``` + +처음 설치하는 서버 PC는 `SERVER_SETUP.md` 순서를 따릅니다. 패치내역에서 (시험)으로 표시된 기능은 기존 방식과 결과를 비교해 사용합니다. + +## 프로젝트 개요 + +웹 브라우저에서 쓰는 상수도 관망 수리해석·관리 플랫폼. +EPANET 2.3.5 엔진으로 관망을 해석하고, GIS·DEM·자산 정보를 한 화면에서 함께 다룬다. + +주요 기능: + +- EPANET INP 관망 불러오기·편집·해석 (Native DLL과 WASM 두 엔진 교차 검증) +- 지도 기반 관망 편집 (절점·관로·밸브·펌프, 꺾임점, 분할·병합) +- 해석 결과 지도·그래프·종단·보고서, 실측 보정 +- 분석 도구: 소화전 모의, 관로 중요도, 급수 구역, VSP, 등치선, 에너지 비용, 자동 보정, 펌프 운전 최적화 +- GIS 연동: SHP·DXF·DGN·MIF·GML·KML·GPKG 불러오기·내보내기, 좌표계 변환, 도형 연산 +- DEM 기반 절점 표고 입력·검증, 유역·하천 분석, 3D 보기 +- 프로젝트 파일(.spipe), 시나리오, 자동 백업 + +## 웹 기반으로 만드는 이유 + +### 기존 운영 방식 + +설치형 프로그램(EPANET, GISpipe 등)을 PC마다 설치하고, 관망 파일·GIS 자료·DEM·엑셀 자료를 파일 단위로 주고받으며 작업하는 구조. + +### 기존 문제 + +- PC마다 프로그램 설치·버전 관리 필요 +- 관망 INP, GIS 레이어, 표고 자료, 자산 대장이 서로 다른 프로그램·파일에 분산 +- 같은 관망을 여러 사람이 볼 때 파일 사본이 늘어나고 최신본 확인이 어려움 +- 수리해석 결과를 지도·자산 정보와 함께 보려면 여러 프로그램을 오가야 함 + +### 웹 기반 통합의 필요성 + +사내망 서버 한 곳에 해석 엔진과 GIS 처리를 두고, 사용자는 브라우저로 접속해 같은 기능을 쓴다. +관망 모델(INP)을 기준으로 GIS·DEM·자산 정보를 연결해 하나의 작업 흐름으로 관리한다. + +### 기대 효과 + +- 설치 없이 브라우저 접속만으로 사용 +- 관망·지도·표고·자산 정보를 한 화면에서 확인 +- 해석 결과를 두 엔진으로 교차 검증해 신뢰도 확보 +- 분석 결과를 표준 형식(INP, SHP, GPKG, CSV)으로 내보내 기존 업무와 연계 + +## 핵심 용어 + +| 용어 | 의미 | +|---|---| +| INP | EPANET 관망 입력 파일. SPipe 관망 모델의 원본 형식 | +| 절점 / 링크 | 접합점·배수지·탱크(절점)와 관로·펌프·밸브(링크) | +| EPS | 연장 기간 해석. 시간대별 수요 패턴에 따라 여러 시점을 해석 | +| Native / WASM 엔진 | 서버의 EPANET DLL과 브라우저의 epanet-js. 같은 관망을 둘 다 해석해 결과를 비교 | +| 시나리오 | 원본 관망에서 갈라져 나온 대안 관망 | +| 자산 속성 | INP에 없는 관재질·설치년도·매설깊이·소블록 등 관리 정보 | +| DEM | 수치표고모델. 절점 표고 입력과 유역 분석에 사용 | +| 일괄 해석(Job) | 관로 중요도·소화전 모의처럼 해석을 여러 번 반복하는 서버 작업 | +| .spipe | 관망·시나리오·GIS 레이어·스타일·보정 자료를 묶은 프로젝트 파일 | + +## 처리 흐름 + +``` +INP 불러오기 또는 GIS 레이어로 관망 만들기 + ↓ +지도에서 관망 편집 · 자산 속성 · DEM 표고 입력 + ↓ +수리해석 (Native DLL + WASM 동시 실행, 결과 비교) + ↓ +결과 확인 (지도 · 그래프 · 종단 · 보고서 · 실측 보정) + ↓ +분석 도구로 검토 (중요도 · 급수 구역 · 소화전 · 최적화 ...) + ↓ +INP · SHP · 엑셀 · .spipe 로 저장·공유 +``` + +핵심 원칙: + +- 관망 모델의 원본은 INP 구조이며, 편집은 모두 INP로 다시 생성 가능한 형태로 저장 +- INP에 없는 정보(자산 속성, 화면 설정)는 별도 저장소에 두고 요소 ID로만 연결 +- 해석 결과는 엔진 출력이 원본이며, 화면은 단위 변환만 수행 +- 모든 편집은 실행 취소(Ctrl+Z) 가능 + +이 문서는 저장소의 시작점입니다. 상세 설계와 점검 기록은 별도 문서에 두고, 여기에는 실행·검증·배포 순서와 문서 선택 기준을 정리합니다. + +--- + +## 1. 빠른 시작 + +### 로컬 실행 (Windows) + +필요 조건: + +- Node.js 24.x (검증 버전 24.14.1) +- Python 3.12 64비트 +- EPANET 2.3.5 (`64bit/epanet2.dll` 포함) + +```powershell +npm install +py -3.12 -m venv .venv +.\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt pytest httpx +``` + +가장 간단한 실행 방법은 `start-gispipe.bat` 더블클릭입니다. 해석 API(8000)와 웹 개발 서버(5173)를 함께 띄우고 브라우저를 엽니다. 종료는 `stop-gispipe.bat`. + +수동 실행: + +```powershell +npm run api +``` + +```powershell +npm run dev +``` + +접속 주소: + +- Web: http://127.0.0.1:5173 +- 해석 API: http://127.0.0.1:8000 +- API 상태 확인: http://127.0.0.1:8000/api/health + +지도 서비스 키 설정 (`.env.example`을 `.env.local`로 복사해 값 입력. 없으면 배경지도·주소 검색만 동작하지 않음): + +```powershell +Copy-Item .env.example .env.local +``` + +개발 서버는 `127.0.0.1`에만 바인딩됩니다. 사내망에 공개할 때는 아래 "8. 사내 서버 배포"를 따릅니다. + +### 로컬 검증 + +```powershell +npm run typecheck +``` + +```powershell +npm test +``` + +```powershell +npm run test:api +``` + +```powershell +npm run build +``` + +- `npm test`: 모델·단위·비교 로직, 편집·분석 도구 회귀 테스트 (Node 테스트) +- `npm run test:api`: FastAPI·EPANET DLL·GIS·DEM 테스트 (pytest) + +CI는 `.github/workflows/ci.yml` (windows-latest). + +## 2. 서비스 구조 + +``` +브라우저 + │ + ▼ +Web (React + Vite) 지도·편집·결과 화면, WASM 해석(Web Worker) + │ /api 프록시 + ▼ +해석 API (FastAPI) EPANET DLL(ctypes) 해석, 일괄 해석, GIS·DEM 처리 + ├─▶ 64bit/epanet2.dll EPANET 2.3.5 엔진 (프로세스 풀) + ├─▶ GDAL (pyogrio, rasterio) 벡터·래스터 입출력 + └─▶ data/dem 업로드한 DEM 보관 + +외부 연동(브라우저에서 직접 호출): 브이월드 배경지도 · 카카오 주소 검색 · Open-Meteo 표고(90m) · OpenStreetMap 건물 +``` + +| 구성요소 | 책임 | 기본 포트 | +|---|---|---| +| `src/` Web | 관망 편집, 결과 표시, WASM 해석, 엔진 결과 비교 | 5173 개발 / 8080 사내 | +| `backend/` 해석 API | 해석, 일괄 분석 Job, GIS 변환·연산, DEM | 8000 (내부 전용) | +| `64bit/epanet2.dll` | EPANET 2.3.5 엔진 | - | +| `data/dem/` | 서버 DEM 보관 (최근 8개) | - | + +## 3. 데이터와 원본 원칙 + +관망의 원본은 **INP 구조**입니다. 화면 편집은 내부 모델(`src/inpModel.ts`)을 바꾸고, 해석·저장 시 항상 INP로 다시 생성합니다. + +INP로 저장되는 것: + +- 절점·관로·펌프·밸브, 패턴·곡선·컨트롤·규칙, 해석 옵션 +- 다중 수요, 이미터, 수질·수원, 탱크 혼합, 에너지, 초기 상태, 반응 계수, 지도 라벨 +- 관로 꺾임점(`[VERTICES]`) — 이름 변경·삭제·분할·방향 전환을 따라감 + +INP 밖에 별도로 두는 것: + +- 자산 속성(관재질·설치년도·매설깊이·소블록 등): 요소 ID로 연결, `.spipe`에 함께 저장 +- 화면 설정(레이어 스타일·범례·라벨): `.spipe`에 저장 +- DEM 원본: 서버 `data/dem`에 보관, 브라우저에는 식별자만 보관 + +원칙: + +- 해석 결과는 엔진 출력을 그대로 쓰고, 화면은 표시 단위 변환만 수행 +- 재생성한 INP가 원본 INP와 같은 해석 결과를 내는지 회귀 테스트로 확인 +- 실무 관망 원본 파일(INP)은 저장소에 커밋하지 않음 + +## 4. 해석 흐름 + +1. 사용자가 해석 실행을 누르면 현재 모델로 INP를 생성합니다. +2. 같은 INP를 서버(Native DLL)와 브라우저(WASM)에서 동시에 해석합니다. +3. 두 결과를 시간대별·요소별로 비교해 허용오차 안에서 일치하는지 표시합니다 (`src/comparisonEngine.ts`). +4. EPANET 경고 코드(1~6)와 비정상 압력을 별도로 점검해 표시합니다 (`src/convergence.ts`). +5. 분석 탭의 일괄 해석(관로 중요도, 소화전, 보정 등)은 서버 Job으로 실행합니다 (`backend/jobs.py`). + +관련 구현 위치: + +- INP 파싱·생성: `src/inpModel.ts`, `src/inpExtras.ts` +- 서버 해석: `backend/main.py`, `backend/epanet.py` +- 일괄 해석 프레임워크: `backend/batch.py`, `backend/jobs.py` +- WASM 해석: `src/worker.ts` + +## 5. 주요 기능 사용 가이드 + +### 관망 편집 + +상단 [편집] 모드에서 절점·관로를 추가하고 지도에서 끌어 옮깁니다. 관로를 선택하면 꺾임점 핸들이 나타나며, 주황 점을 끌면 이동, 흰 점을 끌면 추가, 더블클릭하면 삭제됩니다. 속성창 아래 "추가 속성"에서 다중 수요·이미터·수질·탱크 혼합·펌프 에너지를 편집합니다. + +### 결과 확인 + +[결과] 창에서 표·시계열·누적 빈도·종단(시간별)·실측 보정을 확인합니다. 표의 행을 누르면 지도가 해당 요소로 이동합니다. + +### 분석 탭 + +| 분석 | 내용 | +|---|---| +| 소화전 모의 | 시간대별 소화 유량 추가 해석, 확보 가능 유량 | +| 관로 중요도 | 관로를 하나씩 닫아 영향 절점·수요 순위 | +| 급수 구역 | 배수지별 공급 비율 추적 | +| 흐름 방향 | 역류·무흐름 관로 | +| VSP | 목표 압력용 시간별 펌프 회전수 | +| 등치선 | 압력·동수두·표고 등치선 | +| 에너지 비용 | 펌프별 가동률·효율·원단위·전력비 | +| 관로 통계 | 관경·재질·설치 연대별 집계 | +| 자동 보정 (시험) | 실측값으로 그룹별 C값 배율·관벽 계수 추정 | +| 펌프 운전 최적화 (시험) | 압력·탱크 조건을 지키는 저비용 운전 일정 | +| 편집 도구 | 이동·회전·2점 보정, 분할, 절점 병합, 관로 점검, 추적 | +| 지형(DEM) | DEM 표고 입력·검증, 인터넷 90m 표고, TIN, 유역·하천, 3D | +| GIS 도구 | 레이어 좌표계·내보내기, 도형 연산, 밸브·계량기·소블록 반영 | + +### 저장과 백업 + +프로젝트 메뉴에서 `.spipe`로 저장하면 관망·시나리오·레이어·보정 자료가 한 파일에 담깁니다. 수정 중에는 10분마다 브라우저에 자동 백업되며(최근 30개), 프로젝트 메뉴 > 백업 기록에서 복원합니다. + +## 6. API + +브라우저는 Web 서버의 `/api` 프록시를 통해 해석 API를 호출합니다. 해석 API 포트는 외부에 공개하지 않습니다. + +| 메서드 | 경로 | 설명 | +|---|---|---| +| GET | `/api/health` | 상태 확인 | +| POST | `/api/analyze` | multipart `file`(.inp) → 전체 EPS 해석 결과 JSON (`?format=columnar` 압축 형식) | +| POST | `/api/jobs/{name}` | 일괄 해석: `fireflow`, `criticality`, `servicearea`, `vsp`, `energy`, `calibrate`, `pumpschedule` | +| POST | `/api/gis/vector` | GDAL 벡터 파일 → WGS84 GeoJSON | +| POST | `/api/gis/export` | 레이어 → SHP·GPKG·DXF·GeoJSON·KML (좌표계 지정) | +| POST | `/api/gis/op` | 버퍼·병합·잘라내기·차이·보로노이·격자 | +| POST | `/api/gis/reproject` | 레이어 좌표계 다시 지정 | +| POST | `/api/dem` | DEM 업로드 (GeoTIFF·ASC·IMG, zip) | +| GET | `/api/dem/{id}/preview.png` | DEM 음영 미리보기 | +| GET | `/api/dem/{id}/terrain/{z}/{x}/{y}.png` | 3D 지형 타일 (terrarium) | +| POST | `/api/dem/{id}/sample` | 지점 표고 (쌍선형 보간) | +| POST | `/api/dem/{id}/hydro` | 하천선 추출 / 유역 | +| POST | `/api/dem/from-points` | 점 표고로 DEM 생성 (TIN) | + +해석 결과의 필드 정의는 `src/types.ts`의 `SimulationResult`를 기준으로 합니다. 오류는 `{"detail": "..."}` 형식입니다. + +| 상태 | 의미 | +|---|---| +| 400 | 입력 형식 오류 (확장자, 파라미터, 좌표계) | +| 413 | 업로드 한도 또는 해석 규모 상한 초과 | +| 500 | EPANET 입력·해석 오류 | +| 504 | 해석 시간 초과 | + +## 7. 환경변수와 비밀값 + +| 변수 | 기본값 | 의미 | +|---|---|---| +| `SPIPE_MAX_UPLOAD_MB` | 20 | INP·GIS 업로드 한도. nginx `client_max_body_size`와 맞출 것 | +| `SPIPE_MAX_DEM_MB` | 300 | DEM 업로드 한도. nginx `location /api/dem`과 맞출 것 | +| `SPIPE_ANALYZE_TIMEOUT_S` | 120 | 해석 1건 시간 제한 | +| `SPIPE_JOB_TIMEOUT_S` | 900 | 일괄 해석 1건 시간 제한 | +| `SPIPE_MAX_PERIODS` | 10000 | 수리 스텝 수 상한 | +| `SPIPE_MAX_RESULT_CELLS` | 50000000 | (노드+링크) × 스텝 상한 | +| `SPIPE_WORKERS` | min(4, CPU-1) | 해석 프로세스 수 | +| `SPIPE_DEM_DIR` | `data/dem` | DEM 보관 폴더 | +| `SPIPE_DEM_KEEP` | 8 | 보관할 DEM 개수 | +| `EPANET_LIBRARY_PATH` | `64bit/epanet2.dll` | EPANET 라이브러리 경로 | + +비밀값 원칙: + +- API 키·토큰은 저장소에 커밋하지 않습니다. +- 지도 서비스 키는 `.env.local`(커밋 제외)에 넣습니다. `.env.example`을 복사해 채우고 다시 빌드합니다. + - `VITE_VWORLD_API_KEY`: 브이월드 배경지도 + - `VITE_KAKAO_JS_KEY`: 카카오 주소 검색 (콘솔의 JavaScript SDK 도메인에 접속 주소 등록 필요) +- 이 키들은 빌드된 화면 코드에 포함되어 브라우저에 노출되므로, 각 콘솔에서 사용 도메인을 제한합니다. +- 이전 커밋 기록에는 키가 남아 있습니다. 저장소를 공개하려면 키를 재발급합니다. +- 실무 관망 INP, HWP·PDF 보고서, 화면 캡처는 커밋하지 않습니다 (`.gitignore` 참고). + +## 8. 사내 서버 배포 + +### Windows 서버 PC (현재 운영 방식) + +저장소의 파일(또는 배포용 압축 파일 `SPipe_server_*.zip`)을 서버 PC의 SPipe 폴더에 반영하고 다음 순서로 실행합니다. 최초 설치 절차는 `SERVER_SETUP.md`를 따릅니다. + +```powershell +.\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt +``` + +```powershell +powershell -ExecutionPolicy Bypass -File .\start-lan.ps1 -Rebuild +``` + +- 웹 화면: `http://<서버 IP>:8080` (사내망 공개) +- 해석 API: `127.0.0.1:8000` (서버 내부 전용, 웹 서버가 `/api`만 전달) +- 코드 갱신 후에는 반드시 `-Rebuild`로 화면을 다시 빌드합니다. + +### Docker (nginx + API) + +```bash +docker compose up -d --build +``` + +- 구성: `compose.yaml`, `deploy/Dockerfile`, `deploy/nginx.conf` +- DEM 보관 폴더는 컨테이너의 `/app/data/dem` (필요하면 볼륨으로 유지) +- 업로드 한도를 바꿀 때는 환경변수와 nginx 설정을 함께 바꿉니다. + +배포 전 확인: + +```powershell +git status +``` + +```powershell +npm run typecheck +``` + +```powershell +npm test +``` + +```powershell +npm run test:api +``` + +```powershell +npm run build +``` + +## 9. 문제 해결 순서 + +1. 브라우저 주소가 `127.0.0.1:5173`(개발)인지 `<서버 IP>:8080`(사내)인지 확인합니다. +2. `/api/health`가 `{"status":"ok"}`를 돌려주는지 확인합니다. 아니면 해석 API가 꺼진 상태입니다. +3. 해석 오류는 메시지의 `Error 2xx` 줄(EPANET 입력 오류)을 먼저 확인합니다. +4. 지도가 엉뚱한 곳에 그려지면 상단 좌표계 설정과 GIS 레이어의 원본 좌표계를 확인합니다. +5. 새 기능이 화면에 안 보이면 `start-lan.ps1 -Rebuild`로 다시 빌드했는지 확인합니다. +6. DEM·GIS 기능이 서버 오류를 내면 `pip install -r backend\requirements.txt`를 다시 실행했는지 확인합니다. +7. 인터넷 표고(90m)·OSM 건물·배경지도만 실패하면 사용자 PC의 외부 인터넷 차단 여부를 확인합니다. + +자료가 이상하다고 관망 파일이나 백업을 지우지 않습니다. 먼저 프로젝트 메뉴 > 백업 기록과 실행 취소로 되돌릴 수 있는지 확인합니다. + +## 10. 상세 문서 목차 + +- `SERVER_SETUP.md`: 다른 Windows PC를 사내 서버로 설치하는 절차 +- `deploy/README.md`: Docker·nginx 배포 +- `docs/SPipe_패치내역_v2_2026-10-30.txt`: 데모 v2 변경 내역 (공지·첨부용) +- `public/EXAMPLE_NETWORKS.md`: 예제 관망 설명 +- `_worklog.md`: 배포본 등록·변경 작업 기록 + +## 11. 저장소 구조 + +``` +src/ React 웹 앱 + App.tsx 상태와 화면 조립 + inpModel.ts INP 파싱·생성, 편집 함수 + inpExtras.ts 다중 수요·수질·에너지 등 구조화 섹션 + worker.ts WASM 해석 (Web Worker) + Map.tsx 지도 표시·편집 (MapLibre) + *Section.tsx 분석 탭 화면 (편집 도구, DEM, GIS, 최적화 ...) + *Tools.ts 분석·편집·GIS 순수 로직 (Node 테스트 대상) +backend/ FastAPI 해석 API + main.py API 진입점 + epanet.py EPANET DLL ctypes 바인딩 + batch.py, jobs.py 일괄 해석 프레임워크와 분석 Job + gisio.py, gisops.py GIS 입출력·연산 (GDAL) + dem.py DEM 보관·표고·지형 타일·유역 + blocksys/ 블록 분할 CLI + tests/ pytest +64bit/ EPANET 2.3.5 DLL +public/ 예제 관망 (Net1~3, 한글 CP949, 합성망) +tests/ Node 회귀 테스트 +deploy/ Dockerfile, nginx.conf +data/ 서버 실행 중 생성 (DEM 보관, 커밋 제외) +``` diff --git a/SERVER_SETUP.md b/SERVER_SETUP.md new file mode 100644 index 0000000..c08ec87 --- /dev/null +++ b/SERVER_SETUP.md @@ -0,0 +1,93 @@ +# SPipe 사내 서버 설치 안내 + +다른 Windows PC를 SPipe 서버로 쓰기 위한 절차입니다. 사내 사용자는 브라우저에서 `http://<서버 IP>:8080` 으로 접속합니다. + +- 웹 화면: 서버의 8080 포트 (사내망에 공개) +- 해석 API: 서버 내부 127.0.0.1:8000 (외부 비공개, 웹 서버가 `/api` 요청만 전달) + +## 1. 준비물 + +| 항목 | 버전 / 비고 | +|---|---| +| Windows x64 PC | 해석 엔진 `64bit/epanet2.dll` 사용 | +| Node.js | 24.x (검증 버전 24.14.1) | +| Python | 3.12 64비트 (검증 버전 3.12.10), Python Launcher(`py`) 포함 | +| 인터넷 | 최초 패키지 설치, 배경지도·주소 검색 | + +## 2. 설치 (최초 1회) + +전달받은 `SPipe_server_*.zip`을 `C:\SPipe`에 압축 해제한 뒤 PowerShell에서 실행합니다. 경로는 원하는 위치로 바꿔도 됩니다. + +```powershell +cd C:\SPipe + +npm.cmd ci +py -3.12 -m venv .venv +.\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt +.\.venv\Scripts\python.exe -m pip check + +Copy-Item .env.example .env.local +notepad .env.local + +npm.cmd test +npm.cmd run build +``` + +`.env.local`에는 브이월드 API 키와 카카오 JavaScript 키를 넣습니다. 키는 저장소·압축 파일에 들어 있지 않으니 담당자에게 따로 받습니다. 키를 넣지 않으면 브이월드 배경지도와 주소 검색만 동작하지 않고, 키를 바꾼 뒤에는 다시 빌드(`npm.cmd run build` 또는 `start-lan.ps1 -Rebuild`)해야 반영됩니다. + +`npm test`가 모두 통과하고 빌드가 성공하면 다음 단계로 넘어갑니다. + +## 3. 방화벽 (관리자 PowerShell, 최초 1회) + +같은 사내 서브넷에서만 8080 포트 접속을 허용합니다. 회사 네트워크가 "공용"으로 잡혀 있어도 적용되도록 세 프로필을 모두 지정합니다. + +```powershell +New-NetFirewallRule -DisplayName "SPipe 8080" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow -Profile Domain,Private,Public -RemoteAddress LocalSubnet +``` + +다른 층·지사 등 서브넷이 다른 곳에서도 접속해야 하면 `LocalSubnet` 대신 사내 IP 대역(예: `10.0.0.0/8`)을 넣습니다. 8000 포트는 열지 않습니다. + +## 4. 실행 + +```powershell +cd C:\SPipe +powershell -ExecutionPolicy Bypass -File .\start-lan.ps1 +``` + +화면에 표시되는 `http://<서버 IP>:8080` 주소를 사내에 공유합니다. 창을 닫거나 Ctrl+C를 누르면 웹·API 서버가 함께 종료됩니다. + +재부팅 후 자동으로 켜려면 작업 스케줄러에 "로그온 시" 트리거로 위 명령을 등록합니다. + +## 5. 카카오 주소 검색 도메인 등록 + +주소 검색은 카카오 JavaScript 키를 쓰며, **등록된 주소에서만 동작합니다.** 카카오 개발자 콘솔 → 앱 → 플랫폼 키 → JavaScript 키 → "JavaScript SDK 도메인"에 서버 주소를 추가합니다. + +- 예: `http://10.12.30.5:8080` (서버의 실제 IP) +- 서버 PC에서 직접 열 경우: `http://localhost:8080` + +서버 IP가 바뀌면 이 등록도 바꿔야 하므로, 서버 PC는 고정 IP를 받아 두는 것을 권장합니다. + +## 6. 업데이트 + +새 zip을 받으면 서버를 끄고(Ctrl+C), `C:\SPipe`에 덮어써 압축을 푼 뒤 실행합니다. `.venv`, `node_modules` 폴더는 지우지 않아도 됩니다. + +```powershell +cd C:\SPipe +npm.cmd ci +.\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt +powershell -ExecutionPolicy Bypass -File .\start-lan.ps1 -Rebuild +``` + +`start-lan.ps1`은 `dist` 폴더가 있으면 기존 빌드를 그대로 씁니다. 코드를 받은 뒤에는 반드시 `-Rebuild`를 붙입니다. + +## 7. 사용자 안내 사항 + +- 작업 내용 자동 저장은 각자의 브라우저에만 남습니다. 서버에 저장되지 않으므로 INP와 결과물은 파일로 보관합니다. +- 배경지도·주소 검색·표고 조회는 외부 인터넷이 필요합니다. +- `.net` 내보내기는 지원하지 않습니다. `.inp`로 저장합니다. +- 로그인 기능이 없어 사내망의 누구나 접속할 수 있습니다. + +## 8. 알려진 보류 항목 (2026-10-15 점검) + +- `vite preview`는 상시 운영용 서버가 아닙니다. 사용자가 늘면 nginx 구성(`deploy/`)으로 옮깁니다. +- API 업로드 의존성(python-multipart 0.0.20, Starlette 0.47.3)에 알려진 보안 문제가 있어 수정 버전으로 갱신이 예정되어 있습니다. 갱신 전까지는 사내망에서만 운영합니다. diff --git a/_worklog.md b/_worklog.md new file mode 100644 index 0000000..d4d59d0 --- /dev/null +++ b/_worklog.md @@ -0,0 +1,13 @@ +# SPipe 작업 기록 + +## 2026-10-30 데모 v2 배포본 등록 + +- 원본: `SPipe_server_2026-10-30.zip` (파일 181개). 압축은 저장소에 올리지 않고 풀어서 등록 +- 변경 내역: `docs/SPipe_패치내역_v2_2026-10-30.txt` (이전 배포 데모 v1, 2026-10-15) +- 등록 제외 + - `backend/temp.bin`, `backend/temp.inp`: 해석 중 생기는 임시 파일 + - `.env.local`, 배포 전달 메모: 브이월드·카카오 API 키 포함 + - 실데이터(상리 INP), HWP, 화면 캡처, 테스트 DEM: 배포본에 포함되지 않음 +- README: 원본 README에 데모 v2 서버 업데이트 순서를 추가하고, 저장소에 없는 문서 목차를 정리 +- 서버 반영: 기존 폴더에 덮어쓰기(`.venv`·`node_modules`·`data`·`.env.local` 유지) → `pip install -r backend\requirements.txt` → `start-lan.ps1 -Rebuild` +- 남은 일: 서버 PC IP 확정 시 카카오 JavaScript SDK 도메인에 `http://<서버 IP>:8080` 추가 diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..4164a3b --- /dev/null +++ b/backend/__init__.py @@ -0,0 +1 @@ +# backend module diff --git a/backend/batch.py b/backend/batch.py new file mode 100644 index 0000000..a5c0dd1 --- /dev/null +++ b/backend/batch.py @@ -0,0 +1,332 @@ +"""Repeated-solve helper for batch analyses (fire flow, criticality, VSP, +calibration ...). + +`Network` opens one INP with the EPANET 2.3 DLL and keeps it open, so an +analysis can change values (demands, statuses, settings, controls) and +re-solve hydraulics many times without re-reading the file. Results are +returned at the reporting periods only ([TIMES] REPORT START / REPORT STEP), +the same periods the main /api/analyze endpoint reports, as numpy arrays. + +Runs inside the API process pool (one Network per call, never shared). +""" +from __future__ import annotations + +import ctypes +import os +import tempfile +from dataclasses import dataclass, field + +import numpy as np + +from .epanet import EN, EpanetAPI, EpanetException + +_api: EpanetAPI | None = None + + +def get_api() -> EpanetAPI: + """One DLL handle per process (ctypes handles must not cross processes).""" + global _api + if _api is None: + _api = EpanetAPI() + return _api + + +class BatchError(Exception): + """User-facing input problem (unknown ID, bad parameter).""" + + +@dataclass +class SimResult: + times: list[int] + nodes: dict[int, np.ndarray] = field(default_factory=dict) # prop -> [period, node] + links: dict[int, np.ndarray] = field(default_factory=dict) # prop -> [period, link] + warnings: list[tuple[int, int]] = field(default_factory=list) # (time, code) + + +def _decode(raw: bytes) -> str: + try: + return raw.decode("utf-8") + except UnicodeDecodeError: + return raw.decode("cp949", errors="replace") + + +class Network: + def __init__(self, inp_bytes: bytes): + self.api = get_api() + self.lib = self.api.lib + self._tmp = tempfile.TemporaryDirectory() + inp = os.path.join(self._tmp.name, "n.inp") + with open(inp, "wb") as f: + f.write(inp_bytes) + self.ph = self.api.EN_Project() + self.lib.EN_createproject(ctypes.byref(self.ph)) + self._opened = False + try: + self._opened = True + self.lib.EN_open(self.ph, inp.encode(), os.path.join(self._tmp.name, "n.rpt").encode(), b"") + except EpanetException as e: + self.close() + raise BatchError(str(e)) from e + + n, l = ctypes.c_int(), ctypes.c_int() + self.lib.EN_getcount(self.ph, EN.NODECOUNT, ctypes.byref(n)) + self.lib.EN_getcount(self.ph, EN.LINKCOUNT, ctypes.byref(l)) + self.node_count, self.link_count = n.value, l.value + buf = ctypes.create_string_buffer(256) + t = ctypes.c_int() + self.node_ids: list[str] = [] + self.node_types: list[int] = [] + for i in range(1, self.node_count + 1): + self.lib.EN_getnodeid(self.ph, i, buf) + self.node_ids.append(_decode(buf.value)) + self.lib.EN_getnodetype(self.ph, i, ctypes.byref(t)) + self.node_types.append(t.value) + self.link_ids: list[str] = [] + self.link_types: list[int] = [] + self.link_nodes: list[tuple[int, int]] = [] + a, b = ctypes.c_int(), ctypes.c_int() + for i in range(1, self.link_count + 1): + self.lib.EN_getlinkid(self.ph, i, buf) + self.link_ids.append(_decode(buf.value)) + self.lib.EN_getlinktype(self.ph, i, ctypes.byref(t)) + self.link_types.append(t.value) + self.lib.EN_getlinknodes(self.ph, i, ctypes.byref(a), ctypes.byref(b)) + self.link_nodes.append((a.value, b.value)) + self._node_index = {nid: i + 1 for i, nid in enumerate(self.node_ids)} + self._link_index = {lid: i + 1 for i, lid in enumerate(self.link_ids)} + + self.duration = self.time_param(EN.DURATION) + self.hyd_step = self.time_param(EN.HYDSTEP) + self.report_step = max(1, self.time_param(EN.REPORTSTEP)) + self.report_start = self.time_param(EN.REPORTSTART) + self.pattern_step = max(1, self.time_param(EN.PATTERNSTEP)) + + # ---- lifecycle ---- + def close(self) -> None: + if getattr(self, "ph", None) is None: + return + try: + if self._opened: + self.lib.EN_close(self.ph) + except EpanetException: + pass + try: + self.lib.EN_deleteproject(self.ph) + except EpanetException: + pass + self.ph = None + self._tmp.cleanup() + + def __enter__(self) -> "Network": + return self + + def __exit__(self, *exc) -> None: + self.close() + + # ---- lookups ---- + def node_index(self, node_id: str) -> int: + try: + return self._node_index[node_id] + except KeyError: + raise BatchError(f"절점 {node_id}가 모델에 없습니다.") from None + + def link_index(self, link_id: str) -> int: + try: + return self._link_index[link_id] + except KeyError: + raise BatchError(f"링크 {link_id}가 모델에 없습니다.") from None + + def junction_indices(self) -> list[int]: + return [i + 1 for i, t in enumerate(self.node_types) if t == EN.JUNCTION] + + # ---- values ---- + def time_param(self, code: int) -> int: + v = ctypes.c_long() + self.lib.EN_gettimeparam(self.ph, code, ctypes.byref(v)) + return v.value + + def set_time_param(self, code: int, value: int) -> None: + self.lib.EN_settimeparam(self.ph, code, int(value)) + + def get_node(self, index: int, prop: int) -> float: + v = ctypes.c_double() + self.lib.EN_getnodevalue(self.ph, index, prop, ctypes.byref(v)) + return v.value + + def set_node(self, index: int, prop: int, value: float) -> None: + self.lib.EN_setnodevalue(self.ph, index, prop, float(value)) + + def get_link(self, index: int, prop: int) -> float: + v = ctypes.c_double() + self.lib.EN_getlinkvalue(self.ph, index, prop, ctypes.byref(v)) + return v.value + + def set_link(self, index: int, prop: int, value: float) -> None: + self.lib.EN_setlinkvalue(self.ph, index, prop, float(value)) + + def add_pattern(self, pid: str, values: list[float]) -> None: + """Create the pattern, or overwrite its values if the ID already exists.""" + idx = ctypes.c_int() + try: + self.lib.EN_getpatternindex(self.ph, pid.encode(), ctypes.byref(idx)) + except EpanetException: + self.lib.EN_addpattern(self.ph, pid.encode()) + self.lib.EN_getpatternindex(self.ph, pid.encode(), ctypes.byref(idx)) + arr = (ctypes.c_double * len(values))(*values) + self.lib.EN_setpattern(self.ph, idx.value, arr, len(values)) + + def window_pattern(self, pid: str, start_s: int, end_s: int) -> None: + """Pattern that is 1 on [start, end) and 0 elsewhere, over the duration.""" + # EPANET uses period (t + PATTERN START) // PATTERN STEP, so period k + # starts at simulation time k * step - pattern_start. + pstart = self.time_param(EN.PATTERNSTART) + n = max(1, -(-(max(self.duration, 1) + pstart) // self.pattern_step) + 1) + vals = [] + for k in range(n): + t0 = k * self.pattern_step - pstart + vals.append(1.0 if start_s <= t0 < end_s else 0.0) + self.add_pattern(pid, vals) + + def add_demand(self, index: int, base: float, pattern: str = "", name: str = "") -> int: + """Adds a demand category; returns its 1-based category index.""" + self.lib.EN_adddemand(self.ph, index, float(base), pattern.encode(), name.encode()) + k = ctypes.c_int() + self.lib.EN_getnumdemands(self.ph, index, ctypes.byref(k)) + return k.value + + def delete_demand(self, index: int, category: int) -> None: + self.lib.EN_deletedemand(self.ph, index, category) + + def add_timer_control(self, link_index: int, setting: float, at_s: int) -> int: + """setting: 0 = CLOSED, 1 = OPEN (pipes) or a speed/valve setting.""" + out = ctypes.c_int() + self.lib.EN_addcontrol(self.ph, EN.TIMER, link_index, float(setting), 0, float(at_s), ctypes.byref(out)) + return out.value + + def delete_control(self, index: int) -> None: + self.lib.EN_deletecontrol(self.ph, index) + + # ---- solve ---- + def run_quality(self, nodes: list[int] | None = None) -> tuple[list[int], np.ndarray]: + """Quality run on hydraulics saved by EN_solveH; QUALITY at report periods [period, node].""" + node_idx = nodes if nodes is not None else list(range(1, self.node_count + 1)) + t, tstep = ctypes.c_long(), ctypes.c_long(1) + v = ctypes.c_double() + times: list[int] = [] + rows: list[list[float]] = [] + ph, get_n = self.ph, self.lib.EN_getnodevalue + self.lib.EN_openQ(ph) + try: + self.lib.EN_initQ(ph, EN.NOSAVE) + while True: + self.lib.EN_runQ(ph, ctypes.byref(t)) + if self.is_report_time(t.value) and (not times or times[-1] != t.value): + times.append(t.value) + row = [] + for i in node_idx: + get_n(ph, i, EN.QUALITY, ctypes.byref(v)) + row.append(v.value) + rows.append(row) + self.lib.EN_nextQ(ph, ctypes.byref(tstep)) + if tstep.value <= 0: + break + finally: + self.lib.EN_closeQ(ph) + return times, np.asarray(rows, dtype=float).reshape(len(times), len(node_idx)) + + def pattern_value(self, pattern_index: int, t: int, pattern_start: int = 0) -> float: + """Multiplier of a pattern at simulation time t (1.0 without a pattern).""" + if pattern_index <= 0: + return 1.0 + n = ctypes.c_int() + self.lib.EN_getpatternlen(self.ph, pattern_index, ctypes.byref(n)) + if n.value <= 0: + return 1.0 + period = ((t + pattern_start) // self.pattern_step) % n.value + v = ctypes.c_double() + self.lib.EN_getpatternvalue(self.ph, pattern_index, period + 1, ctypes.byref(v)) + return v.value + + def option(self, code: int) -> float: + v = ctypes.c_double() + self.lib.EN_getoption(self.ph, code, ctypes.byref(v)) + return v.value + + def is_report_time(self, t: int) -> bool: + return t >= self.report_start and (t - self.report_start) % self.report_step == 0 + + def simulate( + self, + node_props: tuple[int, ...] = (EN.PRESSURE,), + link_props: tuple[int, ...] = (), + nodes: list[int] | None = None, + links: list[int] | None = None, + max_periods: int = 20000, + ) -> SimResult: + """Run an EPS and record the given properties at reporting periods. + + `nodes`/`links` are 1-based indices (default: all). Arrays are + [period, element] in the order of the given index lists. + """ + node_idx = nodes if nodes is not None else list(range(1, self.node_count + 1)) + link_idx = links if links is not None else list(range(1, self.link_count + 1)) + rows_n: dict[int, list[list[float]]] = {p: [] for p in node_props} + rows_l: dict[int, list[list[float]]] = {p: [] for p in link_props} + times: list[int] = [] + warnings: list[tuple[int, int]] = [] + t, tstep = ctypes.c_long(), ctypes.c_long(1) + v = ctypes.c_double() + get_n, get_l, ph = self.lib.EN_getnodevalue, self.lib.EN_getlinkvalue, self.ph + self.lib.EN_openH(ph) + try: + self.lib.EN_initH(ph, EN.NOSAVE) + while True: + code = self.lib.EN_runH(ph, ctypes.byref(t)) + if 0 < code <= 10: + warnings.append((t.value, code)) + if self.is_report_time(t.value): + times.append(t.value) + for p in node_props: + row = [] + for i in node_idx: + get_n(ph, i, p, ctypes.byref(v)) + row.append(v.value) + rows_n[p].append(row) + for p in link_props: + row = [] + for i in link_idx: + get_l(ph, i, p, ctypes.byref(v)) + row.append(v.value) + rows_l[p].append(row) + if len(times) > max_periods: + raise BatchError(f"보고 시간대가 {max_periods}개를 넘습니다.") + self.lib.EN_nextH(ph, ctypes.byref(tstep)) + if tstep.value <= 0: + break + finally: + self.lib.EN_closeH(ph) + return SimResult( + times=times, + nodes={p: np.asarray(r, dtype=float).reshape(len(times), len(node_idx)) for p, r in rows_n.items()}, + links={p: np.asarray(r, dtype=float).reshape(len(times), len(link_idx)) for p, r in rows_l.items()}, + warnings=warnings, + ) + + +def hhmm(seconds: int) -> str: + return f"{seconds // 3600}:{(seconds % 3600) // 60:02d}" + + +def parse_time(value, default: int = 0) -> int: + """Seconds from a number of hours, 'h:mm', or None.""" + if value is None or value == "": + return default + if isinstance(value, (int, float)): + return int(round(float(value) * 3600)) + s = str(value).strip() + if ":" in s: + parts = [float(x) for x in s.split(":")] + while len(parts) < 3: + parts.append(0.0) + return int(round(parts[0] * 3600 + parts[1] * 60 + parts[2])) + return int(round(float(s) * 3600)) diff --git a/backend/blocksys/__init__.py b/backend/blocksys/__init__.py new file mode 100644 index 0000000..deec701 --- /dev/null +++ b/backend/blocksys/__init__.py @@ -0,0 +1,11 @@ +"""상수도 블록시스템 자동 분할 도구. + +「지방상수도 현대화사업 업무편람」(환경부, 2021) 2편 Ⅰ.8 블록시스템 구축계획 수립의 +구축절차 ①~④를 지원한다. 절차 ⑤(블록고립 현장조사)와 최종 경계 확정은 범위 밖이며 +사람이 판단한다. + +현재 구현 범위(MVP, Step 1~6): 네트워크 로드, 기준 수리해석, 가중 그래프 구성, +블록 분할, 경계밸브/유량계 산정, 분할안 수리적 검증까지. +""" + +__version__ = "0.1.0" diff --git a/backend/blocksys/__main__.py b/backend/blocksys/__main__.py new file mode 100644 index 0000000..a742c52 --- /dev/null +++ b/backend/blocksys/__main__.py @@ -0,0 +1,6 @@ +import sys + +from .cli import main + +if __name__ == "__main__": + sys.exit(main()) diff --git a/backend/blocksys/boundary.py b/backend/blocksys/boundary.py new file mode 100644 index 0000000..b4aa826 --- /dev/null +++ b/backend/blocksys/boundary.py @@ -0,0 +1,83 @@ +"""Step 7 — 경계 관로 추출 및 밸브/유량계 배정, Step 8 — 모델에 반영.""" + +from __future__ import annotations + +import copy +from dataclasses import dataclass + +import networkx as nx +import wntr + +from .validate import BaselineResult + + +@dataclass +class BoundaryLink: + pipe_id: str + from_node: str + to_node: str + block_a: int + block_b: int + action: str # 'close' | 'meter' + baseline_flow: float + diameter: float + + +def find_boundary_links(G: nx.Graph, labels: dict[str, int]) -> list[BoundaryLink]: + """경계 엣지 = 양 끝 노드의 블록 라벨이 다른 엣지. 병렬관은 개별 pipe_id로 전개.""" + boundary: list[BoundaryLink] = [] + for u, v, data in G.edges(data=True): + if u not in labels or v not in labels: + continue + block_a, block_b = labels[u], labels[v] + if block_a == block_b: + continue + for pipe_id in data.get("pipe_ids", []): + boundary.append( + BoundaryLink( + pipe_id=pipe_id, + from_node=u, + to_node=v, + block_a=block_a, + block_b=block_b, + action="close", + baseline_flow=0.0, + diameter=data.get("diameter", 0.0), + ) + ) + return boundary + + +def assign_valve_or_meter( + boundary: list[BoundaryLink], + baseline: BaselineResult, + inlets_per_block: int = 1, +) -> list[BoundaryLink]: + """블록별 유입 경로: baseline 유량 상위 inlets_per_block개는 유량계, 나머지는 차단.""" + for link in boundary: + link.baseline_flow = baseline.link_flow_mean_abs.get(link.pipe_id, 0.0) + + by_block: dict[int, list[BoundaryLink]] = {} + for link in boundary: + by_block.setdefault(link.block_a, []).append(link) + by_block.setdefault(link.block_b, []).append(link) + + meter_pipe_ids: set[str] = set() + for links in by_block.values(): + ranked = sorted(links, key=lambda l: l.baseline_flow, reverse=True) + meter_pipe_ids.update(l.pipe_id for l in ranked[:inlets_per_block]) + + for link in boundary: + link.action = "meter" if link.pipe_id in meter_pipe_ids else "close" + + return boundary + + +def apply_blocks_to_model( + wn: "wntr.network.WaterNetworkModel", boundary: list[BoundaryLink] +) -> "wntr.network.WaterNetworkModel": + wn_blocked = copy.deepcopy(wn) + for link in boundary: + if link.action == "close": + wn_blocked.get_link(link.pipe_id).initial_status = "Closed" + return wn_blocked diff --git a/backend/blocksys/cli.py b/backend/blocksys/cli.py new file mode 100644 index 0000000..fcc427d --- /dev/null +++ b/backend/blocksys/cli.py @@ -0,0 +1,170 @@ +"""CLI: python -m blocksys run --inp network.inp --config config.yaml [--k K] --out ./result + +MVP(Step 1~6) 범위: 로드 -> 기준 수리해석 -> 가중 그래프 -> 분할 -> 경계밸브/유량계 -> +분할안 수리적 검증. balance/refine, score.py, report.html, valve_segment_analysis는 +아직 구현되지 않았다. +""" + +from __future__ import annotations + +import argparse +import csv +import logging +import math +import sys +from pathlib import Path + +import wntr + +from .boundary import BoundaryLink, apply_blocks_to_model, assign_valve_or_meter, find_boundary_links +from .config import Config +from .graph import apply_hard_constraints, build_weighted_graph +from .io import load_network +from .partition import partition +from .validate import ValidationResult, baseline_simulation, hydraulic_validation + +logger = logging.getLogger("blocksys") + +DISCLAIMER = ( + "본 결과는 블록분할 검토 초안이며, 최종 블록 경계는 업무편람 구축절차 ⑤ " + "블록고립 현장조사를 거쳐 확정한다." +) + + +def build_arg_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(prog="blocksys") + sub = parser.add_subparsers(dest="command", required=True) + + run = sub.add_parser("run", help="블록 분할 대안 생성 및 수리적 검증 (MVP: Step 1~6)") + run.add_argument("--inp", required=True, type=Path, help="EPANET .inp 파일 경로") + run.add_argument("--config", type=Path, default=None, help="config.yaml 경로 (생략 시 기본값)") + run.add_argument("--connections", type=Path, default=None, help="connections.csv 경로 (선택)") + run.add_argument("--k", type=int, default=None, help="단일 대안만 생성. 미지정 시 k_min~k_max 스윕") + run.add_argument("--out", type=Path, default=Path("./result"), help="출력 디렉터리") + + return parser + + +def _ensure_utf8_stdio() -> None: + # Windows 콘솔 기본 코드페이지(cp949 등)에서 한글 로그가 깨지는 것을 방지. + for stream in (sys.stdout, sys.stderr): + try: + stream.reconfigure(encoding="utf-8") + except (AttributeError, ValueError): + pass + + +def run_command(args: argparse.Namespace) -> int: + _ensure_utf8_stdio() + logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") + + cfg = Config.from_yaml(args.config) if args.config else Config() + args.out.mkdir(parents=True, exist_ok=True) + + logger.info("네트워크 로드: %s", args.inp) + bundle = load_network(args.inp, args.connections) + + logger.info("기준 수리해석(baseline) 실행 중...") + baseline = baseline_simulation(bundle.wn, cfg) + + Gw = build_weighted_graph(bundle.graph, baseline, cfg) + Gw = apply_hard_constraints(Gw, cfg) + + k_values = [args.k] if args.k is not None else list(range(cfg.partition.k_min, cfg.partition.k_max + 1)) + + summary_rows = [] + for k in k_values: + logger.info("k=%d 대안 생성 중...", k) + try: + labels = partition(Gw, k, cfg) + except ValueError as exc: + logger.warning("k=%d 분할 실패: %s", k, exc) + continue + + boundary = find_boundary_links(Gw, labels) + boundary = assign_valve_or_meter(boundary, baseline, inlets_per_block=cfg.boundary.inlets_per_block) + wn_blocked = apply_blocks_to_model(bundle.wn, boundary) + validation = hydraulic_validation(wn_blocked, baseline, cfg) + + _write_boundary_csv(args.out / f"boundary_valves_k{k}.csv", boundary) + wntr.network.io.write_inpfile(wn_blocked, str(args.out / f"alt_{k}.inp")) + + summary_rows.append(_summarize(k, Gw, boundary, validation)) + logger.info( + "k=%d: valve=%d meter=%d status=%s", + k, + sum(1 for b in boundary if b.action == "close"), + sum(1 for b in boundary if b.action == "meter"), + validation.status, + ) + + _write_summary_csv(args.out / "validation.csv", summary_rows) + + logger.info("완료. 결과 디렉터리: %s", args.out) + logger.info(DISCLAIMER) + return 0 + + +def _summarize(k: int, Gw, boundary: list[BoundaryLink], validation: ValidationResult) -> dict: + boundary_length = sum( + Gw[b.from_node][b.to_node].get("length", 0.0) for b in boundary if Gw.has_edge(b.from_node, b.to_node) + ) + return { + "k": k, + "n_valves": sum(1 for b in boundary if b.action == "close"), + "n_meters": sum(1 for b in boundary if b.action == "meter"), + "boundary_pipe_length_m": round(boundary_length, 1), + "min_pressure_m": _fmt(validation.min_pressure_m), + "pressure_violations": len(validation.pressure_low_violations) + len(validation.pressure_high_violations), + "isolated_nodes": len(validation.isolated_nodes), + "max_water_age_h": _fmt(validation.max_water_age_h), + "status": validation.status, + "rejection_reason": validation.rejection_reason or "", + } + + +def _fmt(value: float) -> str: + return "" if value is None or math.isnan(value) else round(value, 2) + + +def _write_boundary_csv(path: Path, boundary: list[BoundaryLink]) -> None: + fieldnames = ["pipe_id", "from_node", "to_node", "block_a", "block_b", "action", "baseline_flow", "diameter"] + with path.open("w", newline="", encoding="utf-8-sig") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + for b in boundary: + writer.writerow( + { + "pipe_id": b.pipe_id, + "from_node": b.from_node, + "to_node": b.to_node, + "block_a": b.block_a, + "block_b": b.block_b, + "action": b.action, + "baseline_flow": b.baseline_flow, + "diameter": b.diameter, + } + ) + + +def _write_summary_csv(path: Path, rows: list[dict]) -> None: + if not rows: + logger.warning("유효한 대안이 없어 validation.csv를 생성하지 않았습니다") + return + with path.open("w", newline="", encoding="utf-8-sig") as f: + writer = csv.DictWriter(f, fieldnames=list(rows[0].keys())) + writer.writeheader() + writer.writerows(rows) + + +def main(argv: list[str] | None = None) -> int: + parser = build_arg_parser() + args = parser.parse_args(argv) + if args.command == "run": + return run_command(args) + parser.print_help() + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/backend/blocksys/config.example.yaml b/backend/blocksys/config.example.yaml new file mode 100644 index 0000000..526a355 --- /dev/null +++ b/backend/blocksys/config.example.yaml @@ -0,0 +1,43 @@ +crs: EPSG:5186 + +partition: + level: small # large | medium | small + k_min: 4 # 블록 수 탐색 하한 + k_max: 20 # 상한 + method: spectral # spectral | louvain + flow_weight_alpha: 0.7 # 엣지 가중치에서 유량 비중 (나머지는 관경) + +constraints: + connections_per_block: + min: 500 # TODO: 편람에 수치 규정 없음. 사내 관행값 확정 필요 + max: 3000 + max_pipe_length_per_block: 30000 # m, TODO + demand_per_block: + max_cmd: null # m3/day, null이면 미적용 + elevation_band_m: 30 # 표고차 이 값 초과 시 경계 선호 + large_consumer_demand_lps: 5.0 # 이상이면 대수용가로 태깅, TODO + large_consumer_policy: isolate # isolate | keep_together + sources_in_separate_blocks: true + +hydraulics: + duration_hours: 24 + timestep_minutes: 60 + min_pressure_m: 15.0 # TODO: 수도법·상수도설계기준 확인 후 확정 + max_pressure_m: 71.0 # TODO + max_water_age_hours: 48 # TODO + allow_isolated_nodes: false + +refine: + max_iterations: 20 + moves_per_iteration: 50 + +boundary: + inlets_per_block: 1 # TODO: 통상 1개, 예비 포함 2개 + +scoring: + weights: + n_valves: 0.25 + n_meters: 0.15 + pressure_violations: 0.30 + max_water_age: 0.15 + boundary_pipe_length: 0.15 diff --git a/backend/blocksys/config.py b/backend/blocksys/config.py new file mode 100644 index 0000000..f7b6c1f --- /dev/null +++ b/backend/blocksys/config.py @@ -0,0 +1,114 @@ +"""설정파일(config.yaml) 스키마와 로더. + +사내 관행값·발주처 기준이 필요한 TODO 항목은 §9 표를 참고. 전부 이 모듈의 +기본값으로 노출되어 있으며, config.yaml로 덮어쓴다. +""" + +from __future__ import annotations + +import dataclasses +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Optional + +import yaml + + +@dataclass +class ConnectionsPerBlock: + min: int = 500 # TODO: 편람에 수치 규정 없음. 사내 관행값 확정 필요 + max: int = 3000 + + +@dataclass +class DemandPerBlock: + max_cmd: Optional[float] = None # m3/day, null이면 미적용 + + +@dataclass +class PartitionConfig: + level: str = "small" # large | medium | small + k_min: int = 4 + k_max: int = 20 + method: str = "spectral" # spectral | louvain + flow_weight_alpha: float = 0.7 + + +@dataclass +class ConstraintsConfig: + connections_per_block: ConnectionsPerBlock = field(default_factory=ConnectionsPerBlock) + max_pipe_length_per_block: float = 30000.0 # m, TODO + demand_per_block: DemandPerBlock = field(default_factory=DemandPerBlock) + elevation_band_m: float = 30.0 + large_consumer_demand_lps: float = 5.0 # TODO + large_consumer_policy: str = "isolate" # isolate | keep_together + sources_in_separate_blocks: bool = True + + +@dataclass +class HydraulicsConfig: + duration_hours: int = 24 + timestep_minutes: int = 60 + min_pressure_m: float = 15.0 # TODO: 수도법 시행규칙 / 상수도설계기준 확인 후 확정 + max_pressure_m: float = 71.0 # TODO + max_water_age_hours: float = 48.0 # TODO + allow_isolated_nodes: bool = False + + +@dataclass +class RefineConfig: + max_iterations: int = 20 + moves_per_iteration: int = 50 + + +@dataclass +class ScoringWeights: + n_valves: float = 0.25 + n_meters: float = 0.15 + pressure_violations: float = 0.30 + max_water_age: float = 0.15 + boundary_pipe_length: float = 0.15 + + +@dataclass +class ScoringConfig: + weights: ScoringWeights = field(default_factory=ScoringWeights) + + +@dataclass +class BoundaryConfig: + # 블록당 유입지점(유량계) 목표 개수. 편람에 수치 없음, 통상 1개 예비 포함 2개. TODO + inlets_per_block: int = 1 + + +@dataclass +class Config: + crs: str = "EPSG:5186" + partition: PartitionConfig = field(default_factory=PartitionConfig) + constraints: ConstraintsConfig = field(default_factory=ConstraintsConfig) + hydraulics: HydraulicsConfig = field(default_factory=HydraulicsConfig) + refine: RefineConfig = field(default_factory=RefineConfig) + scoring: ScoringConfig = field(default_factory=ScoringConfig) + boundary: BoundaryConfig = field(default_factory=BoundaryConfig) + + @classmethod + def from_yaml(cls, path: str | Path) -> "Config": + raw = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {} + return cls.from_dict(raw) + + @classmethod + def from_dict(cls, raw: dict[str, Any]) -> "Config": + cfg = cls() + _apply(cfg, raw) + return cfg + + +def _apply(instance: Any, data: dict[str, Any]) -> None: + for key, value in data.items(): + if not hasattr(instance, key): + continue + current = getattr(instance, key) + if dataclasses.is_dataclass(current) and isinstance(value, dict): + _apply(current, value) + else: + setattr(instance, key, value) diff --git a/backend/blocksys/graph.py b/backend/blocksys/graph.py new file mode 100644 index 0000000..45f9eaa --- /dev/null +++ b/backend/blocksys/graph.py @@ -0,0 +1,70 @@ +"""Step 3 — 가중 그래프 구성, Step 4 — 하드 제약(강한 선호)을 가중치에 반영.""" + +from __future__ import annotations + +import networkx as nx + +from .config import Config +from .validate import BaselineResult + + +def build_weighted_graph(G: nx.Graph, baseline: BaselineResult, cfg: Config) -> nx.Graph: + """엣지 가중치 = '이 관로를 자르기 싫은 정도'. 클수록 절단 회피.""" + Gw = G.copy() + alpha = cfg.partition.flow_weight_alpha + + flows: dict[tuple, float] = {} + for u, v, data in Gw.edges(data=True): + pipe_ids = data.get("pipe_ids", []) + flows[(u, v)] = max((baseline.link_flow_mean_abs.get(pid, 0.0) for pid in pipe_ids), default=0.0) + + max_flow = max(flows.values(), default=0.0) + max_diameter = max((data.get("diameter", 0.0) for _, _, data in Gw.edges(data=True)), default=0.0) + + for u, v, data in Gw.edges(data=True): + q_norm = (flows[(u, v)] / max_flow) if max_flow > 0 else 0.0 + d_norm = (data.get("diameter", 0.0) / max_diameter) if max_diameter > 0 else 0.0 + w = alpha * q_norm + (1 - alpha) * d_norm + data["w"] = float(min(max(w, 1e-6), 1.0)) + + return Gw + + +def apply_hard_constraints(Gw: nx.Graph, cfg: Config) -> nx.Graph: + c = cfg.constraints + + # 1. 표고대 분리 + for u, v, data in Gw.edges(data=True): + eu = Gw.nodes[u].get("elevation", 0.0) + ev = Gw.nodes[v].get("elevation", 0.0) + if abs(eu - ev) > c.elevation_band_m: + data["w"] *= 0.1 + + # 2. 대수용가 (base_demand는 SI[m3/s], 기준값은 L/s로 주어짐) + threshold_m3s = c.large_consumer_demand_lps / 1000.0 + large_consumers = { + n for n, ndata in Gw.nodes(data=True) if ndata.get("base_demand", 0.0) >= threshold_m3s + } + for n in Gw.nodes: + Gw.nodes[n]["is_large_consumer"] = n in large_consumers + + factor = 0.1 if c.large_consumer_policy == "isolate" else 5.0 + for n in large_consumers: + for _, _, data in Gw.edges(n, data=True): + data["w"] *= factor + + # 3. 수원 분리: Reservoir/Tank 노드쌍 간 최단경로(연장 기준) 상 엣지 + if c.sources_in_separate_blocks: + sources = [n for n, ndata in Gw.nodes(data=True) if ndata.get("node_type") in ("Reservoir", "Tank")] + for i, s1 in enumerate(sources): + for s2 in sources[i + 1 :]: + if not nx.has_path(Gw, s1, s2): + continue + path = nx.shortest_path(Gw, s1, s2, weight="length") + for a, b in zip(path[:-1], path[1:]): + Gw[a][b]["w"] *= 0.5 + + for _, _, data in Gw.edges(data=True): + data["w"] = float(min(max(data["w"], 1e-6), 1.0)) + + return Gw diff --git a/backend/blocksys/io.py b/backend/blocksys/io.py new file mode 100644 index 0000000..84eaaf0 --- /dev/null +++ b/backend/blocksys/io.py @@ -0,0 +1,122 @@ +"""Step 1 — EPANET 모델 로드 및 단순 그래프 변환.""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from pathlib import Path +from typing import Optional + +import networkx as nx +import pandas as pd +import wntr + +logger = logging.getLogger(__name__) + + +@dataclass +class NetworkBundle: + wn: "wntr.network.WaterNetworkModel" + graph: nx.Graph + isolated_nodes: list[str] + connectivity_warning: Optional[str] = None + + +def load_network(inp_path: str | Path, connections_csv: Optional[str | Path] = None) -> NetworkBundle: + wn = wntr.network.WaterNetworkModel(str(inp_path)) + + G = _build_simple_graph(wn) + _inject_node_attributes(wn, G) + _load_connections(G, connections_csv) + + isolated = [n for n in G.nodes if G.degree(n) == 0] + if isolated: + logger.warning("%d개 노드가 어떤 관로에도 연결되어 있지 않습니다: %s", len(isolated), isolated[:10]) + + connectivity_warning = None + if G.number_of_nodes() > 0 and not nx.is_connected(G): + n_components = nx.number_connected_components(G) + connectivity_warning = f"네트워크가 {n_components}개의 연결되지 않은 구성요소로 나뉘어 있습니다" + logger.warning(connectivity_warning) + + return NetworkBundle(wn=wn, graph=G, isolated_nodes=isolated, connectivity_warning=connectivity_warning) + + +def _build_simple_graph(wn: "wntr.network.WaterNetworkModel") -> nx.Graph: + multi = wn.to_graph() # MultiDiGraph, 노드 좌표 등 기본 속성 포함 + + G = nx.Graph() + G.add_nodes_from(multi.nodes) + + grouped: dict[tuple[str, str], list[str]] = {} + for u, v, key in multi.edges(keys=True): + pair = (u, v) if u <= v else (v, u) + grouped.setdefault(pair, []).append(key) + + for (u, v), link_names in grouped.items(): + lengths, diameters, roughness = [], [], [] + for name in link_names: + link = wn.get_link(name) + lengths.append(getattr(link, "length", 0.0) or 0.0) + diameters.append(getattr(link, "diameter", 0.0) or 0.0) + roughness.append(getattr(link, "roughness", 0.0) or 0.0) + + if len(link_names) == 1: + length, diameter, rough = lengths[0], diameters[0], roughness[0] + else: + # 병렬관 등가화: 단면적(D^2) 합산 기준 등가관경, 연장은 최단 경로 기준 최소값. + # 실제 수리해석은 원본 wn(EPANET 엔진)으로 수행하므로 여기서는 분할 가중치용 근사치. + length = min(lengths) if lengths else 0.0 + diameter = (sum(d**2 for d in diameters)) ** 0.5 if diameters else 0.0 + rough = max(roughness) if roughness else 0.0 + + G.add_edge(u, v, length=length, diameter=diameter, roughness=rough, pipe_ids=list(link_names)) + + return G + + +def _inject_node_attributes(wn: "wntr.network.WaterNetworkModel", G: nx.Graph) -> None: + for node_name in G.nodes: + node = wn.get_node(node_name) + + elevation = getattr(node, "elevation", None) + if elevation is None: + elevation = getattr(node, "base_head", 0.0) # Reservoir + + base_demand = 0.0 + for demand in getattr(node, "demand_timeseries_list", []) or []: + base_demand += demand.base_value + + G.nodes[node_name]["elevation"] = float(elevation or 0.0) + G.nodes[node_name]["base_demand"] = float(base_demand) + G.nodes[node_name]["coordinates"] = node.coordinates + G.nodes[node_name]["node_type"] = type(node).__name__ + G.nodes[node_name]["connections"] = None + G.nodes[node_name]["is_large_consumer_csv"] = False + + +def _load_connections(G: nx.Graph, connections_csv: Optional[str | Path]) -> None: + if not connections_csv: + logger.warning( + "connections.csv가 제공되지 않았습니다. 급수전수 기반 제약" + "(constraints.connections_per_block)은 이번 실행에서 평가되지 않습니다." + ) + return + + df = pd.read_csv(connections_csv, dtype={"node_id": str}) + missing = {"node_id", "connections"} - set(df.columns) + if missing: + raise ValueError(f"connections.csv에 필수 컬럼이 없습니다: {sorted(missing)}") + + unknown = 0 + for _, row in df.iterrows(): + node_id = str(row["node_id"]) + if node_id not in G.nodes: + unknown += 1 + continue + G.nodes[node_id]["connections"] = float(row["connections"]) + if "is_large_consumer" in df.columns and not pd.isna(row["is_large_consumer"]): + G.nodes[node_id]["is_large_consumer_csv"] = bool(row["is_large_consumer"]) + + if unknown: + logger.warning("connections.csv의 %d개 행이 네트워크 노드와 일치하지 않아 무시되었습니다", unknown) diff --git a/backend/blocksys/partition.py b/backend/blocksys/partition.py new file mode 100644 index 0000000..c4a9ba8 --- /dev/null +++ b/backend/blocksys/partition.py @@ -0,0 +1,64 @@ +"""Step 5 — 블록 분할 (MVP: partition만. balance_check/refine은 범위 밖).""" + +from __future__ import annotations + +import networkx as nx +import numpy as np +from sklearn.cluster import SpectralClustering + +from .config import Config + + +def partition(Gw: nx.Graph, k: int, cfg: Config) -> dict[str, int]: + if k < 1: + raise ValueError(f"k는 1 이상이어야 합니다: {k}") + if Gw.number_of_nodes() < k: + raise ValueError(f"노드 수({Gw.number_of_nodes()})가 k({k})보다 적습니다") + + method = cfg.partition.method + if method == "spectral": + return _partition_spectral(Gw, k) + if method == "louvain": + return _partition_louvain(Gw, k) + raise ValueError(f"알 수 없는 분할 방법: {method}") + + +def _partition_spectral(Gw: nx.Graph, k: int) -> dict[str, int]: + nodes = list(Gw.nodes) + A = nx.to_numpy_array(Gw, nodelist=nodes, weight="w") + np.fill_diagonal(A, 0.0) + + model = SpectralClustering( + n_clusters=k, + affinity="precomputed", + assign_labels="kmeans", + random_state=0, + ) + labels = model.fit_predict(A) + return {n: int(label) for n, label in zip(nodes, labels)} + + +def _partition_louvain(Gw: nx.Graph, k: int, max_steps: int = 30) -> dict[str, int]: + lo, hi = 0.01, 10.0 + best_communities: list[set] | None = None + best_diff = None + + for _ in range(max_steps): + r = (lo + hi) / 2 + communities = nx.community.louvain_communities(Gw, weight="w", resolution=r, seed=0) + n_found = len(communities) + diff = abs(n_found - k) + if best_diff is None or diff < best_diff: + best_diff, best_communities = diff, communities + if n_found == k: + break + if n_found < k: + lo = r + else: + hi = r + + labels: dict[str, int] = {} + for i, community in enumerate(best_communities or []): + for n in community: + labels[n] = i + return labels diff --git a/backend/blocksys/tests/__init__.py b/backend/blocksys/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/blocksys/tests/conftest.py b/backend/blocksys/tests/conftest.py new file mode 100644 index 0000000..8640b85 --- /dev/null +++ b/backend/blocksys/tests/conftest.py @@ -0,0 +1,37 @@ +"""공통 픽스처. wntr 동봉 Net3.inp를 개발용 데이터로 사용 (§7).""" + +from __future__ import annotations + +import pytest +import wntr + +from blocksys.config import Config +from blocksys.graph import apply_hard_constraints, build_weighted_graph +from blocksys.io import load_network +from blocksys.validate import baseline_simulation + +NET3_PATH = wntr.library.model_library.get_filepath("Net3") + + +@pytest.fixture(scope="session") +def cfg() -> Config: + c = Config() + c.hydraulics.duration_hours = 6 # 테스트 속도를 위해 24 -> 6시간으로 단축 + c.hydraulics.timestep_minutes = 60 + return c + + +@pytest.fixture(scope="session") +def bundle(): + return load_network(NET3_PATH) + + +@pytest.fixture(scope="session") +def baseline(bundle, cfg): + return baseline_simulation(bundle.wn, cfg) + + +@pytest.fixture(scope="session") +def weighted_graph(bundle, baseline, cfg): + Gw = build_weighted_graph(bundle.graph, baseline, cfg) + return apply_hard_constraints(Gw, cfg) diff --git a/backend/blocksys/tests/test_boundary.py b/backend/blocksys/tests/test_boundary.py new file mode 100644 index 0000000..2198ca4 --- /dev/null +++ b/backend/blocksys/tests/test_boundary.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from blocksys.boundary import apply_blocks_to_model, assign_valve_or_meter, find_boundary_links +from blocksys.partition import partition + + +def test_boundary_matches_differing_labels(weighted_graph, cfg): + labels = partition(weighted_graph, 6, cfg) + boundary = find_boundary_links(weighted_graph, labels) + + expected_pairs = { + (u, v) + for u, v in weighted_graph.edges + if u in labels and v in labels and labels[u] != labels[v] + } + actual_pairs = {(b.from_node, b.to_node) for b in boundary} + assert actual_pairs == expected_pairs + + +def test_each_boundary_link_has_a_real_pipe_id(weighted_graph, bundle, cfg): + labels = partition(weighted_graph, 6, cfg) + boundary = find_boundary_links(weighted_graph, labels) + for b in boundary: + assert b.pipe_id in bundle.wn.link_name_list + + +def test_assign_valve_or_meter_gives_every_block_an_inlet(weighted_graph, baseline, cfg): + labels = partition(weighted_graph, 6, cfg) + boundary = find_boundary_links(weighted_graph, labels) + boundary = assign_valve_or_meter(boundary, baseline, inlets_per_block=cfg.boundary.inlets_per_block) + + assert {b.action for b in boundary} <= {"close", "meter"} + + blocks_touched = {b.block_a for b in boundary} | {b.block_b for b in boundary} + for block in blocks_touched: + links = [b for b in boundary if b.block_a == block or b.block_b == block] + assert any(b.action == "meter" for b in links) + + +def test_apply_blocks_to_model_only_closes_marked_links(weighted_graph, bundle, baseline, cfg): + labels = partition(weighted_graph, 6, cfg) + boundary = find_boundary_links(weighted_graph, labels) + boundary = assign_valve_or_meter(boundary, baseline, inlets_per_block=cfg.boundary.inlets_per_block) + + already_closed = { + name for name, link in bundle.wn.links() if str(link.initial_status) == "Closed" + } + wn_blocked = apply_blocks_to_model(bundle.wn, boundary) + + newly_closed_expected = {b.pipe_id for b in boundary if b.action == "close"} + closed_actual = { + name for name, link in wn_blocked.links() if str(link.initial_status) == "Closed" + } + assert closed_actual == already_closed | newly_closed_expected + # 원본 모델은 변경되지 않아야 한다 + assert all( + str(bundle.wn.get_link(pid).initial_status) != "Closed" + for pid in newly_closed_expected - already_closed + ) diff --git a/backend/blocksys/tests/test_graph.py b/backend/blocksys/tests/test_graph.py new file mode 100644 index 0000000..70ccaae --- /dev/null +++ b/backend/blocksys/tests/test_graph.py @@ -0,0 +1,39 @@ +from __future__ import annotations + + +def test_node_preservation(bundle, weighted_graph): + assert set(weighted_graph.nodes) == set(bundle.graph.nodes) + + +def test_edge_preservation(bundle, weighted_graph): + assert weighted_graph.number_of_edges() == bundle.graph.number_of_edges() + + +def test_weight_range(weighted_graph): + weights = [d["w"] for _, _, d in weighted_graph.edges(data=True)] + assert weights, "가중치가 부여된 엣지가 없습니다" + assert all(0.0 < w <= 1.0 for w in weights) + + +def test_large_consumer_edges_suppressed(bundle, weighted_graph, baseline, cfg): + from blocksys.graph import build_weighted_graph + + large_nodes = [n for n, d in weighted_graph.nodes(data=True) if d.get("is_large_consumer")] + if not large_nodes: + return # Net3 기본 설정(5 L/s)에는 대수용가가 없을 수 있음 + + plain = build_weighted_graph(bundle.graph, baseline, cfg) + # 대수용가 인접 엣지는 제약 반영 전보다 가중치가 낮아야 한다(isolate 기본 정책) + for n in large_nodes: + for u, v, d in weighted_graph.edges(n, data=True): + assert d["w"] <= plain[u][v]["w"] + + +def test_reproducibility(bundle, baseline, cfg): + from blocksys.graph import apply_hard_constraints, build_weighted_graph + + g1 = apply_hard_constraints(build_weighted_graph(bundle.graph, baseline, cfg), cfg) + g2 = apply_hard_constraints(build_weighted_graph(bundle.graph, baseline, cfg), cfg) + + for u, v, d in g1.edges(data=True): + assert g2[u][v]["w"] == d["w"] diff --git a/backend/blocksys/tests/test_partition.py b/backend/blocksys/tests/test_partition.py new file mode 100644 index 0000000..c44a577 --- /dev/null +++ b/backend/blocksys/tests/test_partition.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import networkx as nx +import pytest + +from blocksys.partition import partition + +K_VALUES = [4, 6, 8] + + +@pytest.mark.parametrize("k", K_VALUES) +def test_node_preservation(weighted_graph, cfg, k): + labels = partition(weighted_graph, k, cfg) + assert set(labels.keys()) == set(weighted_graph.nodes) + + +@pytest.mark.parametrize("k", K_VALUES) +def test_block_connectivity(weighted_graph, cfg, k): + labels = partition(weighted_graph, k, cfg) + blocks: dict[int, list] = {} + for n, label in labels.items(): + blocks.setdefault(label, []).append(n) + + for label, nodes in blocks.items(): + sub = weighted_graph.subgraph(nodes) + assert nx.is_connected(sub), f"block {label} is not a single connected component" + + +def test_reproducibility(weighted_graph, cfg): + labels1 = partition(weighted_graph, 6, cfg) + labels2 = partition(weighted_graph, 6, cfg) + assert labels1 == labels2 + + +def test_k_too_large_raises(weighted_graph, cfg): + with pytest.raises(ValueError): + partition(weighted_graph, weighted_graph.number_of_nodes() + 1, cfg) + + +def test_louvain_method(weighted_graph, cfg): + cfg.partition.method = "louvain" + try: + labels = partition(weighted_graph, 6, cfg) + assert set(labels.keys()) == set(weighted_graph.nodes) + finally: + cfg.partition.method = "spectral" diff --git a/backend/blocksys/tests/test_validate.py b/backend/blocksys/tests/test_validate.py new file mode 100644 index 0000000..3ac7dba --- /dev/null +++ b/backend/blocksys/tests/test_validate.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import math + +from blocksys.boundary import BoundaryLink, apply_blocks_to_model +from blocksys.validate import hydraulic_validation + + +def test_baseline_simulation_has_all_nodes(bundle, baseline): + assert set(baseline.node_pressure_mean.keys()) == set(bundle.wn.node_name_list) + assert set(baseline.link_flow_mean_abs.keys()) == set(bundle.wn.link_name_list) + + +def test_baseline_pressures_are_finite(baseline): + assert all(math.isfinite(v) for v in baseline.node_pressure_mean.values()) + + +def test_hydraulic_validation_runs_on_unmodified_network(bundle, baseline, cfg): + result = hydraulic_validation(bundle.wn, baseline, cfg) + assert result.status in ("ok", "violated") + assert not result.isolated_nodes + + +def test_isolation_is_detected_and_rejected(bundle, baseline, cfg): + graph = bundle.graph + source_names = set(bundle.wn.reservoir_name_list) | set(bundle.wn.tank_name_list) + + leaf = next(n for n in graph.nodes if graph.degree(n) == 1 and n not in source_names) + _, neighbor, edge_data = next(iter(graph.edges(leaf, data=True))) + pipe_id = edge_data["pipe_ids"][0] + + boundary = [ + BoundaryLink( + pipe_id=pipe_id, + from_node=leaf, + to_node=neighbor, + block_a=0, + block_b=1, + action="close", + baseline_flow=0.0, + diameter=0.0, + ) + ] + wn_blocked = apply_blocks_to_model(bundle.wn, boundary) + + result = hydraulic_validation(wn_blocked, baseline, cfg) + assert leaf in result.isolated_nodes + assert result.status == "rejected" + assert result.rejection_reason + + +def test_allow_isolated_nodes_still_simulates(bundle, baseline, cfg): + graph = bundle.graph + source_names = set(bundle.wn.reservoir_name_list) | set(bundle.wn.tank_name_list) + leaf = next(n for n in graph.nodes if graph.degree(n) == 1 and n not in source_names) + _, neighbor, edge_data = next(iter(graph.edges(leaf, data=True))) + pipe_id = edge_data["pipe_ids"][0] + + boundary = [ + BoundaryLink( + pipe_id=pipe_id, from_node=leaf, to_node=neighbor, block_a=0, block_b=1, + action="close", baseline_flow=0.0, diameter=0.0, + ) + ] + wn_blocked = apply_blocks_to_model(bundle.wn, boundary) + + cfg.hydraulics.allow_isolated_nodes = True + try: + result = hydraulic_validation(wn_blocked, baseline, cfg) + assert leaf in result.isolated_nodes + assert result.status == "violated" + finally: + cfg.hydraulics.allow_isolated_nodes = False diff --git a/backend/blocksys/validate.py b/backend/blocksys/validate.py new file mode 100644 index 0000000..6572bf8 --- /dev/null +++ b/backend/blocksys/validate.py @@ -0,0 +1,171 @@ +"""Step 2 — 기준 수리해석, Step 9 — 분할안 수리적 검증.""" + +from __future__ import annotations + +import copy +import logging +import math +from dataclasses import dataclass, field +from typing import Optional + +import networkx as nx +import wntr + +from .config import Config + + +def _run_epanet(wn): + """Run wntr's EPANET simulator in a private temp dir. + + EpanetSimulator writes temp.inp/temp.rpt/temp.bin into the *current + directory* by default, which left stray files in backend/ on every run. + """ + import os + import tempfile + + with tempfile.TemporaryDirectory(prefix="spipe_blocksys_") as tmp: + return wntr.sim.EpanetSimulator(wn).run_sim(file_prefix=os.path.join(tmp, "sim")) + +logger = logging.getLogger(__name__) + + +@dataclass +class BaselineResult: + node_pressure_mean: dict[str, float] + node_pressure_min: dict[str, float] + link_flow_mean_abs: dict[str, float] + link_flow_max_abs: dict[str, float] + node_demand_mean: dict[str, float] + node_age_max: dict[str, float] # hours + + +@dataclass +class ValidationResult: + pressure_low_violations: list[str] = field(default_factory=list) + pressure_high_violations: list[str] = field(default_factory=list) + isolated_nodes: list[str] = field(default_factory=list) + water_age_violations: list[str] = field(default_factory=list) + min_pressure_m: float = math.nan + max_water_age_h: float = math.nan + pressure_drop: dict[str, float] = field(default_factory=dict) + status: str = "ok" # ok | violated | rejected + rejection_reason: Optional[str] = None + + @property + def is_ok(self) -> bool: + return self.status == "ok" + + +def _configure_time(wn: "wntr.network.WaterNetworkModel", cfg: Config) -> None: + duration = cfg.hydraulics.duration_hours * 3600 + step = cfg.hydraulics.timestep_minutes * 60 + wn.options.time.duration = duration + wn.options.time.hydraulic_timestep = step + wn.options.time.report_timestep = step + wn.options.time.quality_timestep = min(wn.options.time.quality_timestep, step) + + +def baseline_simulation(wn: "wntr.network.WaterNetworkModel", cfg: Config) -> BaselineResult: + wn_sim = copy.deepcopy(wn) + _configure_time(wn_sim, cfg) + + results = _run_epanet(wn_sim) + pressure = results.node["pressure"] + flow = results.link["flowrate"].abs() + demand = results.node["demand"] + + return BaselineResult( + node_pressure_mean=pressure.mean().to_dict(), + node_pressure_min=pressure.min().to_dict(), + link_flow_mean_abs=flow.mean().to_dict(), + link_flow_max_abs=flow.max().to_dict(), + node_demand_mean=demand.mean().to_dict(), + node_age_max=_run_water_age(wn, cfg), + ) + + +def _run_water_age(wn: "wntr.network.WaterNetworkModel", cfg: Config) -> dict[str, float]: + wn_age = copy.deepcopy(wn) + _configure_time(wn_age, cfg) + wn_age.options.quality.parameter = "AGE" + + results = _run_epanet(wn_age) + age_hours = results.node["quality"] / 3600.0 # EPANET AGE는 초 단위(SI)로 보고됨 + return age_hours.max().to_dict() + + +def _open_link_graph(wn: "wntr.network.WaterNetworkModel") -> nx.Graph: + G = nx.Graph() + G.add_nodes_from(wn.node_name_list) + for _, link in wn.links(): + if link.initial_status == wntr.network.LinkStatus.Closed: + continue + G.add_edge(link.start_node_name, link.end_node_name) + return G + + +def _find_isolated_nodes(G: nx.Graph, source_names: list[str]) -> list[str]: + sources = [s for s in source_names if s in G] + if not sources: + return [] + reachable: set[str] = set() + for source in sources: + reachable |= nx.node_connected_component(G, source) + return sorted(n for n in G.nodes if n not in reachable) + + +def hydraulic_validation( + wn_blocked: "wntr.network.WaterNetworkModel", + baseline: BaselineResult, + cfg: Config, +) -> ValidationResult: + source_names = list(wn_blocked.reservoir_name_list) + list(wn_blocked.tank_name_list) + isolated = _find_isolated_nodes(_open_link_graph(wn_blocked), source_names) + + if isolated and not cfg.hydraulics.allow_isolated_nodes: + return ValidationResult( + isolated_nodes=isolated, + status="rejected", + rejection_reason=f"{len(isolated)}개 노드가 수원으로부터 고립되었습니다 (구축절차 ④ 위반)", + ) + + try: + wn_sim = copy.deepcopy(wn_blocked) + _configure_time(wn_sim, cfg) + results = _run_epanet(wn_sim) + pressure = results.node["pressure"] + age_max = _run_water_age(wn_blocked, cfg) + except Exception as exc: # EPANET 엔진 수렴 실패 등 + logger.warning("수리해석 실패: %s", exc) + return ValidationResult( + isolated_nodes=isolated, + status="rejected", + rejection_reason=f"수리해석 실패: {exc}", + ) + + pressure_min = pressure.min() + pressure_max = pressure.max() + + pressure_low = sorted(pressure_min[pressure_min < cfg.hydraulics.min_pressure_m].index) + pressure_high = sorted(pressure_max[pressure_max > cfg.hydraulics.max_pressure_m].index) + age_violations = sorted(n for n, v in age_max.items() if v > cfg.hydraulics.max_water_age_hours) + + pressure_min_dict = pressure_min.to_dict() + pressure_drop = { + n: baseline.node_pressure_min[n] - pressure_min_dict[n] + for n in pressure_min_dict + if n in baseline.node_pressure_min + } + + violated = bool(pressure_low or pressure_high or age_violations or (isolated and cfg.hydraulics.allow_isolated_nodes)) + + return ValidationResult( + pressure_low_violations=pressure_low, + pressure_high_violations=pressure_high, + isolated_nodes=isolated, + water_age_violations=age_violations, + min_pressure_m=float(pressure_min.min()) if len(pressure_min) else math.nan, + max_water_age_h=max(age_max.values()) if age_max else math.nan, + pressure_drop=pressure_drop, + status="violated" if violated else "ok", + ) diff --git a/backend/columnar.py b/backend/columnar.py new file mode 100644 index 0000000..a515f6e --- /dev/null +++ b/backend/columnar.py @@ -0,0 +1,72 @@ +"""Compact wire format for /api/analyze results (``?format=columnar``). + +The object format repeats every key per node/link and writes each float as +~18 characters of JSON (9.5 MB for a 2,000-node, 25-step network). Here the +time series are packed per variable into one little-endian float64 array per +element kind, base64-encoded (~10.7 bytes per value), so full double precision +is kept -- the Native-vs-WASM comparison tolerances (rel 1e-8) still apply. + +Layout of each packed series: element-major, i.e. element ``i``'s values are +``data[i * steps:(i + 1) * steps]`` in the order of ``ids``. +src/api.ts (decodeColumnarResult) turns it back into the object format. +""" +import base64 +import sys +from array import array + +FORMAT = "spipe-columnar-1" +NODE_SERIES = ("demand", "head", "pressure", "quality") +LINK_SERIES = ("flow", "velocity", "headloss", "quality") +PUMP_FIELDS = ("energy", "efficiency", "totalKwh", "peakKw", "avgKw", "utilization") + + +def _pack(items: dict, ids: list, key: str, steps: int): + """Packed base64 series, or None when any element lacks a full series + (e.g. no water-quality run) -- the decoder then leaves it empty.""" + flat = array("d") + for element_id in ids: + series = items[element_id]["timeSeries"].get(key) or [] + if len(series) != steps: + return None + flat.extend(series) + if sys.byteorder != "little": + flat.byteswap() + return base64.b64encode(flat.tobytes()).decode("ascii") + + +def to_columnar(result: dict) -> dict: + steps = len(result.get("timeSteps") or []) + nodes, links = result["nodes"], result["links"] + node_ids, link_ids = list(nodes), list(links) + + # Pump series and the pump's scalar fields (initial energy/efficiency, kWh + # totals) use distinct keys -- they share names in the object format. + pumps = { + lid: { + "energySeries": links[lid]["timeSeries"].get("energy") or [], + "efficiencySeries": links[lid]["timeSeries"].get("efficiency") or [], + **{f: links[lid].get(f) for f in PUMP_FIELDS if f in links[lid]}, + } + for lid in link_ids + if "totalKwh" in links[lid] + } + + return { + "format": FORMAT, + **{k: v for k, v in result.items() if k not in ("nodes", "links")}, + "nodes": { + "ids": node_ids, + "types": [nodes[i]["type"] for i in node_ids], + "coordinates": [nodes[i]["coordinates"] for i in node_ids], + "series": {k: _pack(nodes, node_ids, k, steps) for k in NODE_SERIES}, + }, + "links": { + "ids": link_ids, + "types": [links[i]["type"] for i in link_ids], + "fromNodes": [links[i]["fromNode"] for i in link_ids], + "toNodes": [links[i]["toNode"] for i in link_ids], + "vertices": [links[i]["vertices"] for i in link_ids], + "series": {k: _pack(links, link_ids, k, steps) for k in LINK_SERIES}, + "pumps": pumps, + }, + } diff --git a/backend/dem.py b/backend/dem.py new file mode 100644 index 0000000..2cbe982 --- /dev/null +++ b/backend/dem.py @@ -0,0 +1,432 @@ +"""DEM (digital elevation model) store: upload once, preview, sample, build from points. + +Rasters are kept under SPIPE_DEM_DIR (default: /data/dem) as GeoTIFF, +keyed by a random id; only the newest SPIPE_DEM_KEEP files are retained. +Elevations are returned in the raster's own units (normally metres). +""" +from __future__ import annotations + +import json +import os +import shutil +import uuid +import warnings +import zipfile +from pathlib import Path + +import numpy as np +import rasterio +import shapely +import shapely.geometry +from rasterio.enums import Resampling +from rasterio.io import MemoryFile +from rasterio.transform import from_origin +from pyproj import CRS, Transformer + +from .batch import BatchError + +DEM_DIR = Path(os.environ.get("SPIPE_DEM_DIR", Path(__file__).resolve().parents[1] / "data" / "dem")) +DEM_KEEP = int(os.environ.get("SPIPE_DEM_KEEP", "8")) +RASTER_EXTENSIONS = (".tif", ".tiff", ".asc", ".img", ".dem", ".bil", ".flt", ".hgt", ".vrt") +MAX_CELLS_FROM_POINTS = 25_000_000 +_ID_OK = set("0123456789abcdef") + + +def _path(dem_id: str) -> Path: + if len(dem_id) != 32 or not set(dem_id) <= _ID_OK: + raise BatchError("DEM id가 올바르지 않습니다.") + p = DEM_DIR / f"{dem_id}.tif" + if not p.exists(): + raise BatchError("DEM을 찾을 수 없습니다. 다시 불러오세요 (서버가 오래된 DEM을 정리했을 수 있습니다).") + return p + + +def _trim() -> None: + files = sorted(DEM_DIR.glob("*.tif"), key=lambda p: p.stat().st_mtime, reverse=True) + for old in files[DEM_KEEP:]: + old.unlink(missing_ok=True) + old.with_suffix(".json").unlink(missing_ok=True) + + +def _crs_of(user: str | None) -> CRS: + if not user: + raise BatchError("좌표계가 필요합니다 (로컬 좌표계에서는 DEM을 쓸 수 없습니다).") + try: + return CRS.from_user_input(user) + except Exception as e: # pyproj raises several types + raise BatchError(f"좌표계를 읽을 수 없습니다: {user[:60]}") from e + + +def _info(dem_id: str, src, name: str) -> dict: + if src.crs is None: + raise BatchError("DEM에 좌표계 정보가 없습니다 (.prj 또는 GeoTIFF 좌표계를 넣어 주세요).") + band = src.read(1, masked=True, out_shape=(min(src.height, 1024), min(src.width, 1024)), resampling=Resampling.nearest) + to_wgs = Transformer.from_crs(src.crs, "EPSG:4326", always_xy=True) + l, b, r, t = src.bounds + corners = [list(to_wgs.transform(x, y)) for x, y in ((l, t), (r, t), (r, b), (l, b))] + return { + "id": dem_id, + "name": name, + "crs": src.crs.to_string(), + "width": src.width, + "height": src.height, + "resolution": [abs(src.res[0]), abs(src.res[1])], + "bounds": [l, b, r, t], + "corners": corners, + "min": float(band.min()) if band.count() else None, + "max": float(band.max()) if band.count() else None, + "nodata": src.nodata, + } + + +def _store(src_path: str, name: str) -> dict: + DEM_DIR.mkdir(parents=True, exist_ok=True) + dem_id = uuid.uuid4().hex + out = DEM_DIR / f"{dem_id}.tif" + try: + with rasterio.open(src_path) as src: + if src.count < 1: + raise BatchError("래스터 밴드가 없습니다.") + profile = src.profile.copy() + profile.update(driver="GTiff", count=1, compress="deflate", tiled=True, blockxsize=256, blockysize=256, BIGTIFF="IF_SAFER") + if src.width < 256 or src.height < 256: + profile.update(tiled=False) + profile.pop("blockxsize", None) + profile.pop("blockysize", None) + with rasterio.open(out, "w", **profile) as dst: + for _, window in src.block_windows(1): + dst.write(src.read(1, window=window), 1, window=window) + with rasterio.open(out) as src: + info = _info(dem_id, src, name) + except rasterio.errors.RasterioIOError as e: + out.unlink(missing_ok=True) + raise BatchError(f"래스터를 읽을 수 없습니다: {e}") from e + except BatchError: + out.unlink(missing_ok=True) + raise + out.with_suffix(".json").write_text(json.dumps(info, ensure_ascii=False), encoding="utf-8") + _trim() + return info + + +def import_dem(upload_path: str, filename: str) -> dict: + """Store an uploaded raster (or a zip holding one, e.g. ASC + PRJ) as a DEM.""" + ext = os.path.splitext(filename.lower())[1] + if ext == ".zip": + try: + with zipfile.ZipFile(upload_path) as z: + names = [n for n in z.namelist() if os.path.splitext(n.lower())[1] in RASTER_EXTENSIONS and not n.startswith("__MACOSX")] + except zipfile.BadZipFile as e: + raise BatchError("zip 파일이 손상되었습니다.") from e + if not names: + raise BatchError(f"zip 안에 래스터({', '.join(RASTER_EXTENSIONS)})가 없습니다.") + return _store("/vsizip/" + upload_path.replace("\\", "/") + "/" + names[0], filename) + if ext not in RASTER_EXTENSIONS: + raise BatchError(f"지원하지 않는 형식입니다: {ext} (GeoTIFF, ASC, IMG 또는 이를 담은 zip)") + # GDAL picks the driver from the extension; keep it on the temp copy. + named = upload_path + ext + shutil.copyfile(upload_path, named) + try: + return _store(named, filename) + finally: + os.remove(named) + + +def dem_info(dem_id: str) -> dict: + p = _path(dem_id) + return json.loads(p.with_suffix(".json").read_text(encoding="utf-8")) + + +def preview_png(dem_id: str, max_size: int = 1600) -> bytes: + """Hillshaded colour relief, transparent where there is no data.""" + with rasterio.open(_path(dem_id)) as src: + k = max(1, int(np.ceil(max(src.width, src.height) / max_size))) + h, w = max(1, src.height // k), max(1, src.width // k) + z = src.read(1, masked=True, out_shape=(h, w), resampling=Resampling.bilinear).astype("float64") + cell = abs(src.res[0]) * src.width / w + geographic = src.crs and src.crs.is_geographic + if geographic: + cell *= 111_000 + mask = np.ma.getmaskarray(z) + zf = z.filled(np.nan) + lo, hi = np.nanpercentile(zf, [2, 98]) if np.isfinite(zf).any() else (0.0, 1.0) + t = np.clip((zf - lo) / max(hi - lo, 1e-9), 0, 1) + # Green -> yellow -> brown -> white elevation ramp. + stops = np.array([[0.0, 64, 140, 80], [0.35, 190, 200, 110], [0.7, 160, 110, 60], [1.0, 245, 245, 240]]) + rgb = np.stack([np.interp(np.nan_to_num(t), stops[:, 0], stops[:, c]) for c in (1, 2, 3)]) + gy, gx = np.gradient(np.nan_to_num(zf, nan=float(np.nanmean(zf)) if np.isfinite(zf).any() else 0.0), cell) + slope = np.arctan(np.hypot(gx, gy)) + aspect = np.arctan2(-gx, gy) + az, alt = np.radians(315), np.radians(45) + shade = np.clip(np.sin(alt) * np.cos(slope) + np.cos(alt) * np.sin(slope) * np.cos(az - aspect), 0, 1) + rgb = rgb * (0.55 + 0.45 * shade) + alpha = np.where(mask | ~np.isfinite(zf), 0, 255) + img = np.concatenate([rgb, alpha[None]]).clip(0, 255).astype("uint8") + with warnings.catch_warnings(): + warnings.simplefilter("ignore", rasterio.errors.NotGeoreferencedWarning) # a plain image + with MemoryFile() as mem: + with mem.open(driver="PNG", width=w, height=h, count=4, dtype="uint8") as dst: + dst.write(img) + return mem.read() + + +def sample(dem_id: str, points: list, points_crs: str | None) -> list: + """Bilinear DEM values at [x, y] points given in `points_crs` (None outside / no data).""" + if not points: + return [] + pts = np.asarray(points, dtype="float64").reshape(-1, 2) + with rasterio.open(_path(dem_id)) as src: + tr = Transformer.from_crs(_crs_of(points_crs), src.crs, always_xy=True) + xs, ys = tr.transform(pts[:, 0], pts[:, 1]) + inv = ~src.transform + xs, ys = np.asarray(xs), np.asarray(ys) + cols, rows = inv.a * xs + inv.b * ys + inv.c, inv.d * xs + inv.e * ys + inv.f + # pixel centres sit at +0.5 + cols, rows = np.asarray(cols) - 0.5, np.asarray(rows) - 0.5 + ok = np.isfinite(cols) & np.isfinite(rows) & (cols >= -0.5) & (rows >= -0.5) & (cols <= src.width - 0.5) & (rows <= src.height - 0.5) + out: list = [None] * len(pts) + if not ok.any(): + return out + c0 = int(max(0, np.floor(cols[ok].min()))); c1 = int(min(src.width - 1, np.floor(cols[ok].max()) + 1)) + r0 = int(max(0, np.floor(rows[ok].min()))); r1 = int(min(src.height - 1, np.floor(rows[ok].max()) + 1)) + win = rasterio.windows.Window(c0, r0, c1 - c0 + 1, r1 - r0 + 1) + if win.width * win.height > 400_000_000: + raise BatchError("지점들이 너무 넓게 퍼져 있습니다. 나눠서 실행하세요.") + z = src.read(1, window=win, masked=True).astype("float64").filled(np.nan) + for k in np.where(ok)[0]: + c, r = cols[k] - c0, rows[k] - r0 + ci, ri = int(np.clip(np.floor(c), 0, z.shape[1] - 1)), int(np.clip(np.floor(r), 0, z.shape[0] - 1)) + cj, rj = min(ci + 1, z.shape[1] - 1), min(ri + 1, z.shape[0] - 1) + fc, fr = float(np.clip(c - ci, 0, 1)), float(np.clip(r - ri, 0, 1)) + q = np.array([z[ri, ci], z[ri, cj], z[rj, ci], z[rj, cj]]) + wts = np.array([(1 - fc) * (1 - fr), fc * (1 - fr), (1 - fc) * fr, fc * fr]) + good = np.isfinite(q) & (wts > 0) + if good.any(): + out[k] = round(float(np.sum(q[good] * wts[good]) / np.sum(wts[good])), 3) + elif np.isfinite(q[0]): + out[k] = round(float(q[0]), 3) + return out + + +def from_points(points: list, points_crs: str | None, cell: float, name: str = "점 표고 DEM", target_crs: str | None = None) -> dict: + """TIN-style DEM: linear interpolation over the Delaunay triangulation of [x, y, z] points.""" + from scipy.interpolate import LinearNDInterpolator + + pts = np.asarray(points, dtype="float64").reshape(-1, 3) + pts = pts[np.all(np.isfinite(pts), axis=1)] + if len(pts) < 3: + raise BatchError("표고 점이 3개 이상 필요합니다.") + crs = _crs_of(points_crs) + if target_crs: + # e.g. GIS points arrive in WGS84; grid them in the model's plane CRS. + dst = _crs_of(target_crs) + x, y = Transformer.from_crs(crs, dst, always_xy=True).transform(pts[:, 0], pts[:, 1]) + pts = np.column_stack([x, y, pts[:, 2]]) + crs = dst + if crs.is_geographic: + raise BatchError("경위도 좌표계 점으로는 만들 수 없습니다. 평면 좌표계를 쓰세요.") + if not (cell > 0): + raise BatchError("격자 크기는 0보다 커야 합니다.") + x0, y0 = pts[:, 0].min(), pts[:, 1].min() + x1, y1 = pts[:, 0].max(), pts[:, 1].max() + w, h = int(np.ceil((x1 - x0) / cell)) + 1, int(np.ceil((y1 - y0) / cell)) + 1 + if w * h > MAX_CELLS_FROM_POINTS: + raise BatchError(f"격자가 너무 큽니다 ({w}x{h}). 격자 크기를 키우세요.") + try: + interp = LinearNDInterpolator(pts[:, :2], pts[:, 2]) + except Exception as e: # Qhull errors on collinear input + raise BatchError("점들이 한 줄 위에 있어 삼각망을 만들 수 없습니다.") from e + gx = x0 + (np.arange(w) + 0.5) * cell + gy = y1 - (np.arange(h) + 0.5) * cell + xx, yy = np.meshgrid(gx, gy) + z = interp(xx, yy).astype("float32") + nodata = -9999.0 + z[~np.isfinite(z)] = nodata + DEM_DIR.mkdir(parents=True, exist_ok=True) + tmp = DEM_DIR / f"tmp_{uuid.uuid4().hex}.tif" + with rasterio.open(tmp, "w", driver="GTiff", width=w, height=h, count=1, dtype="float32", crs=crs.to_wkt(), + transform=from_origin(x0, y1, cell, cell), nodata=nodata, compress="deflate") as dst: + dst.write(z, 1) + try: + return _store(str(tmp), name) + finally: + tmp.unlink(missing_ok=True) + + +def terrain_tile(dem_id: str, z: int, x: int, y: int) -> bytes: + """Web-mercator XYZ tile of the DEM in Mapbox/MapLibre 'terrarium' encoding + (elevation = R*256 + G + B/256 - 32768).""" + from rasterio.warp import reproject + + if not (0 <= z <= 18 and 0 <= x < 2 ** z and 0 <= y < 2 ** z): + raise BatchError("타일 번호가 올바르지 않습니다.") + size = 256 + world = 20037508.342789244 + span = 2 * world / 2 ** z + x0, y1 = -world + x * span, world - y * span + dst = np.full((size, size), np.nan, dtype="float32") + with rasterio.open(_path(dem_id)) as src: + reproject( + source=rasterio.band(src, 1), destination=dst, + dst_transform=from_origin(x0, y1, span / size, span / size), dst_crs="EPSG:3857", + dst_nodata=np.nan, resampling=Resampling.bilinear, + ) + # Outside the DEM: its lowest value, so the terrain does not drop to sea level in a cliff. + fill = float(np.nanmin(dst)) if np.isfinite(dst).any() else float(dem_info(dem_id).get("min") or 0.0) + v = np.nan_to_num(dst, nan=fill).astype("float64") + 32768.0 + r = np.floor(v / 256) + g = np.floor(v - r * 256) + b = np.floor((v - r * 256 - g) * 256) + img = np.stack([r, g, b]).clip(0, 255).astype("uint8") + with warnings.catch_warnings(): + warnings.simplefilter("ignore", rasterio.errors.NotGeoreferencedWarning) + with MemoryFile() as mem: + with mem.open(driver="PNG", width=size, height=size, count=3, dtype="uint8") as d: + d.write(img) + return mem.read() + + +# ---------------------------------------------------------------- hydrology (유역·하천) + +MAX_HYDRO_CELLS = 1_000_000 +_D8 = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] + + +def _hydro_grid(dem_id: str): + """DEM (downsampled to <= MAX_HYDRO_CELLS) -> D8 receivers and flow accumulation + on a priority-flood filled surface (pits and flats drain).""" + import heapq + + with rasterio.open(_path(dem_id)) as src: + k = max(1, int(np.ceil(np.sqrt(src.width * src.height / MAX_HYDRO_CELLS)))) + h, w = max(2, src.height // k), max(2, src.width // k) + z = src.read(1, masked=True, out_shape=(h, w), resampling=Resampling.average).astype("float64") + transform = src.transform @ src.transform.scale(src.width / w, src.height / h) + crs = src.crs + if crs.is_geographic: + raise BatchError("유역 분석에는 평면 좌표계 DEM이 필요합니다.") + valid = ~np.ma.getmaskarray(z) + filled = np.where(valid, z.filled(np.nan), np.inf) + done = ~valid.copy() + heap: list = [] + edge = np.zeros_like(valid) + edge[0, :] = edge[-1, :] = edge[:, 0] = edge[:, -1] = True + pad = np.pad(~valid, 1, constant_values=True) + near_nodata = np.zeros_like(valid) + for dr, dc in _D8: + near_nodata |= pad[1 + dr:1 + dr + h, 1 + dc:1 + dc + w] + for r, c in zip(*np.where(valid & (edge | near_nodata))): + heapq.heappush(heap, (filled[r, c], int(r), int(c))) + done[r, c] = True + eps = 1e-4 + order = [] + while heap: + e, r, c = heapq.heappop(heap) + order.append(r * w + c) + for dr, dc in _D8: + rr, cc = r + dr, c + dc + if 0 <= rr < h and 0 <= cc < w and not done[rr, cc]: + done[rr, cc] = True + filled[rr, cc] = max(filled[rr, cc], e + eps) + heapq.heappush(heap, (filled[rr, cc], rr, cc)) + n = h * w + flat = filled.ravel() + recv = np.full(n, -1, dtype=np.int64) + best = np.zeros(n) + cell = abs(transform.a) + rows, cols = np.divmod(np.arange(n), w) + for dr, dc in _D8: + rr, cc = rows + dr, cols + dc + ok = (rr >= 0) & (rr < h) & (cc >= 0) & (cc < w) + nb = np.where(ok, rr * w + cc, 0) + with np.errstate(invalid="ignore"): + drop = np.where(ok, (flat - flat[nb]) / (cell * np.hypot(dr, dc)), -np.inf) + drop[~np.isfinite(drop)] = -np.inf + take = drop > best + best[take] = drop[take] + recv[take] = nb[take] + acc = valid.ravel().astype("float64") + for i in reversed(order): # from the highest cell down + j = recv[i] + if j >= 0: + acc[j] += acc[i] + return {"h": h, "w": w, "recv": recv, "acc": acc, "transform": transform, "crs": crs, "cell_area": abs(transform.a * transform.e)} + + +def streams(dem_id: str, min_area_km2: float) -> dict: + """Stream lines (WGS84 GeoJSON): cells draining at least `min_area_km2`.""" + g = _hydro_grid(dem_id) + thr = max(2.0, min_area_km2 * 1e6 / g["cell_area"]) + w, tr = g["w"], g["transform"] + idx = np.where((g["acc"] >= thr) & (g["recv"] >= 0))[0] + if len(idx) > 300_000: + raise BatchError("하천 셀이 너무 많습니다. 최소 유역 면적을 키우세요.") + if not len(idx): + return {"type": "FeatureCollection", "features": []} + + def xy(i): + r, c = divmod(int(i), w) + return tr @ (c + 0.5, r + 0.5) + + segs = np.array([shapely.LineString([xy(i), xy(g["recv"][i])]) for i in idx], dtype=object) + merged = shapely.get_parts(shapely.line_merge(shapely.union_all(segs))) + to_wgs = Transformer.from_crs(g["crs"], "EPSG:4326", always_xy=True) + pts = shapely.points([xy(i) for i in idx]) + tree = shapely.STRtree(pts) + acc_at = g["acc"][idx] + feats = [] + for line in merged: + near = tree.query(line, predicate="dwithin", distance=abs(tr.a) * 0.6) + area = float(acc_at[near].max() * g["cell_area"] / 1e6) if len(near) else 0.0 + coords = np.asarray(line.coords) + x, y = to_wgs.transform(coords[:, 0], coords[:, 1]) + feats.append({"type": "Feature", "properties": {"area_km2": round(area, 3), "length_m": round(float(line.length), 1)}, + "geometry": {"type": "LineString", "coordinates": [[float(a), float(b)] for a, b in zip(x, y)]}}) + return {"type": "FeatureCollection", "features": feats} + + +def watershed(dem_id: str, points: list, points_crs: str | None, snap_m: float = 50.0) -> dict: + """Catchment polygon upstream of each pour point (snapped to the largest flow within snap_m).""" + from rasterio import features as rfeatures + + g = _hydro_grid(dem_id) + h, w, tr = g["h"], g["w"], g["transform"] + to_dem = Transformer.from_crs(_crs_of(points_crs), g["crs"], always_xy=True) + to_wgs = Transformer.from_crs(g["crs"], "EPSG:4326", always_xy=True) + order = np.argsort(g["recv"], kind="stable") + recv_sorted = g["recv"][order] + inv = ~tr + rad = max(1, int(round(snap_m / abs(tr.a)))) + acc2 = g["acc"].reshape(h, w) + feats = [] + for k, p in enumerate(points): + x, y = to_dem.transform(float(p[0]), float(p[1])) + c = inv.a * x + inv.b * y + inv.c + r = inv.d * x + inv.e * y + inv.f + r, c = int(r), int(c) + if not (0 <= r < h and 0 <= c < w): + continue + r0, r1, c0, c1 = max(0, r - rad), min(h, r + rad + 1), max(0, c - rad), min(w, c + rad + 1) + win = acc2[r0:r1, c0:c1] + rr, cc = np.unravel_index(int(np.argmax(win)), win.shape) + start = (r0 + rr) * w + (c0 + cc) + mask = np.zeros(h * w, dtype=bool) + mask[start] = True + stack = [start] + while stack: + j = stack.pop() + lo = np.searchsorted(recv_sorted, j, "left") + hi = np.searchsorted(recv_sorted, j, "right") + for i in order[lo:hi]: + if not mask[i]: + mask[i] = True + stack.append(int(i)) + m2 = mask.reshape(h, w) + polys = [shapely.geometry.shape(geom) for geom, val in rfeatures.shapes(m2.astype("uint8"), mask=m2, transform=tr) if val == 1] + if not polys: + continue + poly = shapely.union_all(np.array(polys, dtype=object)) + poly = shapely.transform(poly, lambda a: np.column_stack(to_wgs.transform(a[:, 0], a[:, 1]))) + feats.append({"type": "Feature", "properties": {"point": k, "area_km2": round(float(mask.sum() * g["cell_area"] / 1e6), 4)}, + "geometry": shapely.geometry.mapping(poly)}) + return {"type": "FeatureCollection", "features": feats} diff --git a/backend/epanet.py b/backend/epanet.py new file mode 100644 index 0000000..b8388b8 --- /dev/null +++ b/backend/epanet.py @@ -0,0 +1,266 @@ +import ctypes +import os + + +class EN: + # EN_CountType + NODECOUNT = 0 + LINKCOUNT = 2 + + # EN_NodeProperty + ELEVATION = 0 + BASEDEMAND = 1 + EMITTER = 3 + TANKLEVEL = 8 + DEMAND = 9 + HEAD = 10 + PRESSURE = 11 + QUALITY = 12 + # EN_LinkProperty + DIAMETER = 0 + LENGTH = 1 + ROUGHNESS = 2 + INITSTATUS = 4 + INITSETTING = 5 + KWALL = 7 + STATUS = 11 + SETTING = 12 + FLOW = 8 + VELOCITY = 9 + HEADLOSS = 10 + ENERGY = 13 + LINKQUAL = 14 + PUMP_EFFIC = 17 + # EN_InitHydOption + NOSAVE = 0 + SAVE = 1 + + # EN_Option + PRESS_UNITS = 25 + + # EN_TimeParameter + DURATION = 0 + HYDSTEP = 1 + REPORTSTEP = 5 + REPORTSTART = 6 + + # EN_TimeParameter (cont.) + PATTERNSTEP = 3 + PATTERNSTART = 4 + + # EN_ControlType + TIMER = 2 + + # EN_QualityType / quality time step + QUAL_TRACE = 3 + QUALSTEP = 2 + + # EN_Option (energy) + GLOBALEFFIC = 8 + GLOBALPRICE = 9 + GLOBALPATTERN = 10 + DEMANDCHARGE = 11 + + # EN_LinkProperty (pump energy) + PUMP_ECOST = 21 + PUMP_EPAT = 22 + + # EN_NodeType / EN_LinkType + JUNCTION = 0 + RESERVOIR = 1 + TANK = 2 + CVPIPE = 0 + PIPE = 1 + PUMP = 2 + +FLOW_UNITS = { + 0: "CFS", 1: "GPM", 2: "MGD", 3: "IMGD", 4: "AFD", + 5: "LPS", 6: "LPM", 7: "MLD", 8: "CMH", 9: "CMD", 10: "CMS" +} + +PRESS_UNITS = { + 0: "psi", 1: "kPa", 2: "m", 3: "bar", 4: "ft" +} +QUAL_TYPES = { + 0: "NONE", 1: "CHEMICAL", 2: "AGE", 3: "TRACE" +} + + +class EpanetException(Exception): + pass + +class EpanetAPI: + def __init__(self): + library_name = "epanet2.dll" if os.name == "nt" else "libepanet2.so" + dll_path = os.environ.get("EPANET_LIBRARY_PATH") or os.path.join(os.path.dirname(os.path.dirname(__file__)), "64bit", library_name) + if not os.path.exists(dll_path): + raise RuntimeError(f"DLL not found: {dll_path}") + + self.lib = ctypes.CDLL(dll_path) + self.EN_Project = ctypes.c_void_p + self._setup() + + def _setup(self): + def _errcheck(result, func, args): + if result > 10: + err_msg = ctypes.create_string_buffer(256) + self.lib.EN_geterror(result, err_msg, 256) + raise EpanetException(f"EPANET Error {result}: {err_msg.value.decode('utf-8').strip()}") + return result + + self.lib.EN_geterror.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int] + self.lib.EN_geterror.restype = ctypes.c_int + + self.lib.EN_getversion.argtypes = [ctypes.POINTER(ctypes.c_int)] + self.lib.EN_getversion.restype = ctypes.c_int + self.lib.EN_getversion.errcheck = _errcheck + + self.lib.EN_createproject.argtypes = [ctypes.POINTER(self.EN_Project)] + self.lib.EN_createproject.restype = ctypes.c_int + self.lib.EN_createproject.errcheck = _errcheck + + self.lib.EN_deleteproject.argtypes = [self.EN_Project] + self.lib.EN_deleteproject.restype = ctypes.c_int + self.lib.EN_deleteproject.errcheck = _errcheck + + self.lib.EN_open.argtypes = [self.EN_Project, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p] + self.lib.EN_open.restype = ctypes.c_int + self.lib.EN_open.errcheck = _errcheck + + self.lib.EN_close.argtypes = [self.EN_Project] + self.lib.EN_close.restype = ctypes.c_int + self.lib.EN_close.errcheck = _errcheck + + self.lib.EN_getcount.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int)] + self.lib.EN_getcount.restype = ctypes.c_int + self.lib.EN_getcount.errcheck = _errcheck + + self.lib.EN_getnodeid.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_char_p] + self.lib.EN_getnodeid.restype = ctypes.c_int + self.lib.EN_getnodeid.errcheck = _errcheck + + self.lib.EN_getnodetype.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int)] + self.lib.EN_getnodetype.restype = ctypes.c_int + self.lib.EN_getnodetype.errcheck = _errcheck + + self.lib.EN_getnodevalue.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_double)] + self.lib.EN_getnodevalue.restype = ctypes.c_int + self.lib.EN_getnodevalue.errcheck = _errcheck + + self.lib.EN_getcoord.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double)] + self.lib.EN_getcoord.restype = ctypes.c_int + self.lib.EN_getcoord.errcheck = _errcheck + + self.lib.EN_getlinkid.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_char_p] + self.lib.EN_getlinkid.restype = ctypes.c_int + self.lib.EN_getlinkid.errcheck = _errcheck + + self.lib.EN_getlinktype.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int)] + self.lib.EN_getlinktype.restype = ctypes.c_int + self.lib.EN_getlinktype.errcheck = _errcheck + + self.lib.EN_getlinknodes.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)] + self.lib.EN_getlinknodes.restype = ctypes.c_int + self.lib.EN_getlinknodes.errcheck = _errcheck + + self.lib.EN_getlinkvalue.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_double)] + self.lib.EN_getlinkvalue.restype = ctypes.c_int + self.lib.EN_getlinkvalue.errcheck = _errcheck + + self.lib.EN_getvertexcount.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int)] + self.lib.EN_getvertexcount.restype = ctypes.c_int + self.lib.EN_getvertexcount.errcheck = _errcheck + + self.lib.EN_getvertex.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double)] + self.lib.EN_getvertex.restype = ctypes.c_int + self.lib.EN_getvertex.errcheck = _errcheck + + self.lib.EN_getflowunits.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_int)] + self.lib.EN_getflowunits.restype = ctypes.c_int + self.lib.EN_getflowunits.errcheck = _errcheck + + self.lib.EN_getoption.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_double)] + self.lib.EN_getoption.restype = ctypes.c_int + self.lib.EN_getoption.errcheck = _errcheck + + self.lib.EN_gettimeparam.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_long)] + self.lib.EN_gettimeparam.restype = ctypes.c_int + self.lib.EN_gettimeparam.errcheck = _errcheck + + self.lib.EN_openH.argtypes = [self.EN_Project] + self.lib.EN_openH.restype = ctypes.c_int + self.lib.EN_openH.errcheck = _errcheck + + self.lib.EN_initH.argtypes = [self.EN_Project, ctypes.c_int] + self.lib.EN_initH.restype = ctypes.c_int + self.lib.EN_initH.errcheck = _errcheck + + self.lib.EN_runH.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_long)] + self.lib.EN_runH.restype = ctypes.c_int + self.lib.EN_runH.errcheck = _errcheck + + + self.lib.EN_nextH.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_long)] + self.lib.EN_nextH.restype = ctypes.c_int + self.lib.EN_nextH.errcheck = _errcheck + self.lib.EN_closeH.argtypes = [self.EN_Project] + self.lib.EN_closeH.restype = ctypes.c_int + self.lib.EN_closeH.errcheck = _errcheck + + # Water Quality Analysis Bindings + self.lib.EN_openQ.argtypes = [self.EN_Project] + self.lib.EN_openQ.restype = ctypes.c_int + self.lib.EN_openQ.errcheck = _errcheck + + self.lib.EN_initQ.argtypes = [self.EN_Project, ctypes.c_int] + self.lib.EN_initQ.restype = ctypes.c_int + self.lib.EN_initQ.errcheck = _errcheck + + self.lib.EN_runQ.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_long)] + self.lib.EN_runQ.restype = ctypes.c_int + self.lib.EN_runQ.errcheck = _errcheck + + self.lib.EN_nextQ.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_long)] + self.lib.EN_nextQ.restype = ctypes.c_int + self.lib.EN_nextQ.errcheck = _errcheck + + self.lib.EN_closeQ.argtypes = [self.EN_Project] + self.lib.EN_closeQ.restype = ctypes.c_int + self.lib.EN_closeQ.errcheck = _errcheck + + self.lib.EN_getqualinfo.argtypes = [ + self.EN_Project, + ctypes.POINTER(ctypes.c_int), + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.POINTER(ctypes.c_int), + ] + self.lib.EN_getqualinfo.restype = ctypes.c_int + self.lib.EN_getqualinfo.errcheck = _errcheck + # ---- Bindings used by batch analyses (backend/batch.py) ---- + P, I, D, L, S = self.EN_Project, ctypes.c_int, ctypes.c_double, ctypes.c_long, ctypes.c_char_p + + def bind(name, argtypes): + fn = getattr(self.lib, name) + fn.argtypes = argtypes + fn.restype = ctypes.c_int + fn.errcheck = _errcheck + + bind("EN_getnodeindex", [P, S, ctypes.POINTER(I)]) + bind("EN_getlinkindex", [P, S, ctypes.POINTER(I)]) + bind("EN_setnodevalue", [P, I, I, D]) + bind("EN_setlinkvalue", [P, I, I, D]) + bind("EN_settimeparam", [P, I, L]) + bind("EN_addpattern", [P, S]) + bind("EN_getpatternindex", [P, S, ctypes.POINTER(I)]) + bind("EN_setpattern", [P, I, ctypes.POINTER(D), I]) + bind("EN_adddemand", [P, I, D, S, S]) + bind("EN_getnumdemands", [P, I, ctypes.POINTER(I)]) + bind("EN_deletedemand", [P, I, I]) + bind("EN_addcontrol", [P, I, I, D, I, D, ctypes.POINTER(I)]) + bind("EN_deletecontrol", [P, I]) + bind("EN_solveH", [P]) + bind("EN_setqualtype", [P, I, S, S, S]) + bind("EN_getpatternlen", [P, I, ctypes.POINTER(I)]) + bind("EN_getpatternvalue", [P, I, I, ctypes.POINTER(D)]) + bind("EN_getcontrol", [P, I, ctypes.POINTER(I), ctypes.POINTER(I), ctypes.POINTER(D), ctypes.POINTER(I), ctypes.POINTER(D)]) diff --git a/backend/gisio.py b/backend/gisio.py new file mode 100644 index 0000000..da2dfe6 --- /dev/null +++ b/backend/gisio.py @@ -0,0 +1,119 @@ +"""Vector GIS input via GDAL (pyogrio): any supported format -> GeoJSON in WGS84. + +Shapefiles must arrive zipped (they are several files); everything else is a +single file. Files are read straight from a temp copy (zip through GDAL's +/vsizip/, never extracted), so archive paths cannot escape the temp dir. +""" +from __future__ import annotations + +import os +import tempfile + +import numpy as np +import pyogrio +import shapely +from pyogrio import raw as ogr_raw +from pyproj import CRS, Transformer +from shapely.geometry import mapping + +from .batch import BatchError + +VECTOR_EXTENSIONS = { + ".zip": "ESRI Shapefile (zip)", + ".geojson": "GeoJSON", + ".json": "GeoJSON", + ".gpkg": "GeoPackage", + ".dxf": "DXF", + ".dgn": "MicroStation DGN", + ".mif": "MapInfo MIF", + ".tab": "MapInfo TAB", + ".gml": "GML", + ".kml": "KML", + ".e00": "ArcInfo E00", + ".csv": "CSV (WKT/X,Y)", +} +MAX_FEATURES = 200_000 + + +def _gdal_path(path: str, ext: str) -> str: + if ext == ".zip": + return "/vsizip/" + path.replace("\\", "/") + return path + + +def _clean(v): + if isinstance(v, (np.floating, float)): + f = float(v) + return None if f != f or f in (float("inf"), float("-inf")) else f + if isinstance(v, np.integer): + return int(v) + if isinstance(v, bytes): + try: + return v.decode("utf-8") + except UnicodeDecodeError: + return v.decode("cp949", errors="replace") + if hasattr(v, "isoformat"): + return v.isoformat() + return v + + +def read_vector(contents: bytes, filename: str, layer: str | None = None, src_epsg: int | None = None, default_crs: str | None = None) -> dict: + """`default_crs` applies only when the file itself carries no CRS (DXF, bare CSV ...).""" + ext = os.path.splitext(filename.lower())[1] + if ext not in VECTOR_EXTENSIONS: + raise BatchError(f"지원하지 않는 형식입니다: {ext or filename}. 지원: {', '.join(sorted(VECTOR_EXTENSIONS))}") + with tempfile.TemporaryDirectory() as tmp: + path = os.path.join(tmp, "input" + ext) + with open(path, "wb") as f: + f.write(contents) + src = _gdal_path(path, ext) + try: + layers = [str(row[0]) for row in pyogrio.list_layers(src)] + except Exception as e: # GDAL raises its own error types + raise BatchError(f"파일을 읽을 수 없습니다: {e}") from e + if not layers: + raise BatchError("파일에 레이어가 없습니다.") + name = layer if layer in layers else layers[0] + try: + info = pyogrio.read_info(src, layer=name, encoding=None) + meta, _fids, geometry, field_data = ogr_raw.read(src, layer=name, max_features=MAX_FEATURES + 1) + except Exception as e: + raise BatchError(f"레이어 {name}을(를) 읽을 수 없습니다: {e}") from e + + count = len(geometry) + if count > MAX_FEATURES: + raise BatchError(f"도형이 {MAX_FEATURES:,}개를 넘습니다.") + + crs_text = meta.get("crs") or info.get("crs") + assumed = False + src_crs = CRS.from_epsg(src_epsg) if src_epsg else (CRS.from_user_input(crs_text) if crs_text else None) + if src_crs is None and default_crs: + try: + src_crs = CRS.from_user_input(default_crs) + assumed = True + except Exception as e: + raise BatchError(f"기본 좌표계를 읽을 수 없습니다: {default_crs[:60]}") from e + src_code = src_crs.to_epsg() if src_crs else None + + geoms = shapely.from_wkb(np.array(geometry, dtype=object)) + if src_crs is not None and not src_crs.equals(CRS.from_epsg(4326)): + tr = Transformer.from_crs(src_crs, CRS.from_epsg(4326), always_xy=True) + geoms = shapely.transform(geoms, lambda xy: np.column_stack(tr.transform(xy[:, 0], xy[:, 1]))) + fields = list(meta.get("fields", [])) + features = [] + for i in range(count): + g = geoms[i] + props = {fields[k]: _clean(field_data[k][i]) for k in range(len(fields))} + features.append({"type": "Feature", "geometry": mapping(g) if g is not None else None, "properties": props}) + + return { + "layers": layers, + "layer": name, + "crs": f"EPSG:{src_code}" if src_code else (src_crs.to_string() if src_crs else None), + "reprojected": src_crs is not None, + "crsAssumed": assumed, + "geometryType": meta.get("geometry_type"), + "count": count, + "fields": fields, + "featureCollection": {"type": "FeatureCollection", "features": features}, + } diff --git a/backend/gisops.py b/backend/gisops.py new file mode 100644 index 0000000..33de630 --- /dev/null +++ b/backend/gisops.py @@ -0,0 +1,233 @@ +"""GIS layer operations: export in any CRS/format, geometry ops, Voronoi, grids. + +Layers travel as GeoJSON FeatureCollections in WGS84 (what the map holds). +Metric operations run in a planar CRS given by the caller (the model's CRS), +and results come back in WGS84. +""" +from __future__ import annotations + +import io +import os +import tempfile +import zipfile + +import numpy as np +import shapely +from pyogrio import raw as ogr_raw +from pyproj import CRS, Transformer +from shapely.geometry import mapping, shape + +from .batch import BatchError + +EXPORT_FORMATS = { + "shp": ("ESRI Shapefile", ".shp"), + "gpkg": ("GPKG", ".gpkg"), + "dxf": ("DXF", ".dxf"), + "geojson": ("GeoJSON", ".geojson"), + "kml": ("KML", ".kml"), +} +WGS84 = CRS.from_epsg(4326) +MAX_FEATURES = 200_000 +MAX_GRID_CELLS = 200_000 + + +def _crs(user: str | None, what: str = "좌표계") -> CRS: + if not user: + raise BatchError(f"{what}가 필요합니다.") + try: + return CRS.from_user_input(user) + except Exception as e: + raise BatchError(f"{what}를 읽을 수 없습니다: {str(user)[:60]}") from e + + +def _planar(user: str | None) -> CRS: + c = _crs(user, "평면 좌표계") + if c.is_geographic: + raise BatchError("거리 계산에는 평면 좌표계(예: EPSG:5186)가 필요합니다.") + return c + + +def _project(geoms: np.ndarray, src: CRS, dst: CRS) -> np.ndarray: + if src.equals(dst): + return geoms + tr = Transformer.from_crs(src, dst, always_xy=True) + return shapely.transform(geoms, lambda xy: np.column_stack(tr.transform(xy[:, 0], xy[:, 1]))) + + +def _read_fc(fc: dict) -> tuple[np.ndarray, list[dict]]: + feats = (fc or {}).get("features") or [] + if len(feats) > MAX_FEATURES: + raise BatchError(f"도형이 {MAX_FEATURES:,}개를 넘습니다.") + geoms, props = [], [] + for f in feats: + g = f.get("geometry") + try: + geoms.append(shape(g) if g else None) + except Exception: + geoms.append(None) + props.append(dict(f.get("properties") or {})) + return np.array(geoms, dtype=object), props + + +def _to_fc(geoms, props: list[dict]) -> dict: + out = [] + for g, p in zip(geoms, props): + if g is None or g.is_empty: + continue + out.append({"type": "Feature", "geometry": mapping(g), "properties": p}) + return {"type": "FeatureCollection", "features": out} + + +# ---------------------------------------------------------------- export + + +def _field_arrays(props: list[dict], fmt: str): + names: list[str] = [] + for p in props: + for k in p: + if k not in names: + names.append(k) + if fmt == "shp": + # dBASE: 10-char unique names + seen, short = set(), [] + for n in names: + base = str(n)[:10] or "F" + s, k = base, 1 + while s in seen: + k += 1 + s = f"{base[:10 - len(str(k))]}{k}" + seen.add(s) + short.append(s) + else: + short = [str(n) for n in names] + arrays = [] + for n in names: + vals = [p.get(n) for p in props] + nums = [v for v in vals if v is not None and v != ""] + if nums and all(isinstance(v, (int, float)) and not isinstance(v, bool) for v in nums): + arrays.append(np.array([float(v) if v is not None and v != "" else np.nan for v in vals], dtype="float64")) + else: + arrays.append(np.array(["" if v is None else str(v) for v in vals], dtype=object)) + return short, arrays + + +def write_vector(fc: dict, target_crs: str | None, fmt: str, name: str) -> tuple[bytes, str, str]: + """FeatureCollection (WGS84) -> file bytes in `fmt` and `target_crs` (shapefile zipped).""" + if fmt not in EXPORT_FORMATS: + raise BatchError(f"지원하지 않는 형식입니다: {fmt}") + driver, ext = EXPORT_FORMATS[fmt] + geoms, props = _read_fc(fc) + keep = [i for i, g in enumerate(geoms) if g is not None and not g.is_empty] + if not keep: + raise BatchError("내보낼 도형이 없습니다.") + geoms, props = geoms[keep], [props[i] for i in keep] + dst = _crs(target_crs) if target_crs else WGS84 + if fmt == "kml": + dst = WGS84 + geoms = _project(geoms, WGS84, dst) + safe = "".join(ch for ch in (name or "layer") if ch.isalnum() or ch in "-_ ").strip() or "layer" + kinds = {g.geom_type.replace("Multi", "") for g in geoms} + if fmt == "shp" and len(kinds) > 1: + raise BatchError("Shapefile은 한 가지 도형(점/선/면)만 담을 수 있습니다. GeoPackage로 내보내세요.") + geometry_type = "Unknown" if len(kinds) > 1 else ("Multi" + kinds.pop() if any(g.geom_type.startswith("Multi") for g in geoms) else geoms[0].geom_type) + # Promote singles to multi when the layer mixes them (pyogrio needs one type). + if geometry_type.startswith("Multi"): + geoms = np.array([g if g.geom_type.startswith("Multi") else getattr(shapely, geometry_type)([g]) for g in geoms], dtype=object) + fields, arrays = _field_arrays(props, fmt) if fmt != "dxf" else ([], []) + with tempfile.TemporaryDirectory() as tmp: + path = os.path.join(tmp, safe + ext) + kwargs = {"encoding": "UTF-8"} if fmt == "shp" else {} + ogr_raw.write(path, shapely.to_wkb(geoms), arrays, fields, driver=driver, geometry_type=geometry_type, + crs=dst.to_wkt() if fmt != "dxf" else None, **kwargs) + if fmt == "shp": + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z: + for fn in sorted(os.listdir(tmp)): + z.write(os.path.join(tmp, fn), fn) + return buf.getvalue(), safe + ".zip", "application/zip" + with open(path, "rb") as f: + data = f.read() + media = {"gpkg": "application/geopackage+sqlite3", "dxf": "application/dxf", "geojson": "application/geo+json", "kml": "application/vnd.google-earth.kml+xml"}[fmt] + return data, safe + ext, media + + +# ---------------------------------------------------------------- operations + + +def reproject_fc(fc: dict, src: str, dst: str) -> dict: + """Coordinates that were read as `src` but are really `dst`: fix the layer (WGS84 in/out).""" + a, b = _crs(src, "원래 좌표계"), _crs(dst, "새 좌표계") + geoms, props = _read_fc(fc) + raw = _project(geoms, WGS84, a) # back to the numbers in the file + return _to_fc(_project(raw, b, WGS84), props) + + +def vector_op(op: str, params: dict) -> dict: + planar = _planar(params.get("crs")) + if op == "voronoi": + pts = np.asarray(params.get("points") or [], dtype="float64").reshape(-1, 2) # in planar CRS + ids = [str(i) for i in params.get("ids") or []] + if len(pts) < 3 or len(ids) != len(pts): + raise BatchError("점이 3개 이상 필요합니다.") + margin = float(params.get("margin") or 0) + hull = shapely.MultiPoint(pts).convex_hull + clip = hull.buffer(margin if margin > 0 else max(1.0, 0.05 * np.sqrt(max(hull.area, 1.0)))) + cells = shapely.get_parts(shapely.voronoi_polygons(shapely.MultiPoint(pts), extend_to=clip.envelope)) + tree = shapely.STRtree(shapely.points(pts)) + out_g, out_p = [], [] + for c in cells: + hit = tree.query(c, predicate="contains") + if len(hit) == 0: + continue + g = c.intersection(clip) + if g.is_empty: + continue + k = int(hit[0]) + out_g.append(g) + out_p.append({"node": ids[k], "area_m2": round(float(g.area), 2)}) + return _to_fc(_project(np.array(out_g, dtype=object), planar, WGS84), out_p) + + if op == "grid": + b = params.get("bounds") # planar [minx, miny, maxx, maxy] + cell = float(params.get("cell") or 0) + if not b or cell <= 0: + raise BatchError("범위와 격자 크기가 필요합니다.") + x0, y0, x1, y1 = map(float, b) + nx, ny = int(np.ceil((x1 - x0) / cell)), int(np.ceil((y1 - y0) / cell)) + if nx * ny > MAX_GRID_CELLS: + raise BatchError(f"격자가 {nx * ny:,}칸입니다. 격자 크기를 키우세요 (최대 {MAX_GRID_CELLS:,}).") + gs, ps = [], [] + for j in range(ny): + for i in range(nx): + gs.append(shapely.box(x0 + i * cell, y0 + j * cell, x0 + (i + 1) * cell, y0 + (j + 1) * cell)) + ps.append({"col": i, "row": j, "cell": f"{i}_{j}"}) + return _to_fc(_project(np.array(gs, dtype=object), planar, WGS84), ps) + + geoms, props = _read_fc(params.get("layer") or {}) + if not len(geoms): + raise BatchError("대상 레이어가 비어 있습니다.") + g = _project(geoms, WGS84, planar) + if op == "buffer": + d = float(params.get("distance") or 0) + if d == 0: + raise BatchError("버퍼 거리를 입력하세요.") + res = shapely.buffer(g, d) + return _to_fc(_project(res, planar, WGS84), props) + if op == "dissolve": + field = params.get("field") + groups: dict = {} + for geom, p in zip(g, props): + if geom is None: + continue + groups.setdefault(p.get(field) if field else None, []).append(geom) + out_g = [shapely.union_all(v) for v in groups.values()] + out_p = [{field: k, "count": len(v)} if field else {"count": len(v)} for k, v in groups.items()] + return _to_fc(_project(np.array(out_g, dtype=object), planar, WGS84), out_p) + if op in ("intersection", "difference", "clip"): + other_g, _ = _read_fc(params.get("other") or {}) + if not len(other_g): + raise BatchError("두 번째 레이어가 비어 있습니다.") + mask = shapely.union_all(_project(other_g, WGS84, planar)) + res = shapely.difference(g, mask) if op == "difference" else shapely.intersection(g, mask) + return _to_fc(_project(res, planar, WGS84), props) + raise BatchError(f"알 수 없는 연산입니다: {op}") diff --git a/backend/jobs.py b/backend/jobs.py new file mode 100644 index 0000000..6ac987c --- /dev/null +++ b/backend/jobs.py @@ -0,0 +1,724 @@ +"""Batch analysis jobs exposed as POST /api/jobs/{name}. + +Each job takes the INP bytes and a params dict and returns a JSON-able dict. +Jobs run in the API process pool; raise BatchError for input problems +(reported as HTTP 400). Register new jobs in JOBS at the bottom. +""" +from __future__ import annotations + +import numpy as np + +import ctypes + +from .batch import BatchError, Network, hhmm, parse_time +from .epanet import EN, EpanetException + + +def job_summary(inp: bytes, params: dict) -> dict: + """Framework smoke job: min/max pressure per reporting period.""" + with Network(inp) as net: + junctions = net.junction_indices() + res = net.simulate(node_props=(EN.PRESSURE,), nodes=junctions) + p = res.nodes[EN.PRESSURE] + return { + "times": res.times, + "labels": [hhmm(t) for t in res.times], + "minPressure": p.min(axis=1).round(4).tolist() if p.size else [], + "maxPressure": p.max(axis=1).round(4).tolist() if p.size else [], + "junctions": len(junctions), + "warnings": [{"time": t, "code": c} for t, c in res.warnings], + } + + +def _window_rows(times: list[int], start: int, end: int) -> np.ndarray: + """Reporting periods inside [start, end); falls back to the one nearest start.""" + t = np.asarray(times) + rows = np.where((t >= start) & (t < end))[0] + if rows.size == 0: + rows = np.array([int(np.argmin(np.abs(t - start)))]) + return rows + + +def job_fireflow(inp: bytes, params: dict) -> dict: + """소화전 모의: extra demand at hydrant nodes for a time window. + + params: nodes [ids], flow (model flow units, per hydrant), start/end (h or h:mm), + residual (model pressure units, optional: also find the available flow), + threshold (optional: count junctions whose window-min pressure drops below it). + """ + ids = [str(x) for x in params.get("nodes") or []] + if not ids: + raise BatchError("소화전 절점을 하나 이상 지정하세요.") + flow = float(params.get("flow") or 0) + if flow <= 0: + raise BatchError("소화 유량은 0보다 커야 합니다.") + with Network(inp) as net: + start = parse_time(params.get("start"), 0) + end = parse_time(params.get("end"), max(net.duration, net.report_step)) + if end <= start: + end = start + net.report_step + hyd = [net.node_index(i) for i in ids] + junctions = net.junction_indices() + base = net.simulate(nodes=junctions) + rows = _window_rows(base.times, start, end) + base_min = base.nodes[EN.PRESSURE][rows].min(axis=0) + + net.window_pattern("SPIPE_FIRE", start, end) + cats = [net.add_demand(h, flow, "SPIPE_FIRE", "fire") for h in hyd] + fire = net.simulate(nodes=junctions) + fire_min = fire.nodes[EN.PRESSURE][rows].min(axis=0) + for h, c in zip(hyd, cats): + net.delete_demand(h, c) + + col = {j: k for k, j in enumerate(junctions)} + # Compare the two runs period by period: tank/pump controls can shift + # *when* the daily minimum happens, so "min before vs min after" may + # even show a rise. Report each node at its largest drop instead. + bw = base.nodes[EN.PRESSURE][rows] + fw = fire.nodes[EN.PRESSURE][rows] + drop = fw - bw + worst = np.argmin(drop, axis=0) + cols = np.arange(len(junctions)) + diff = drop[worst, cols] + order = np.argsort(diff) + threshold = params.get("threshold") + out_nodes = [ + { + "id": net.node_ids[junctions[k] - 1], + "basePressure": round(float(bw[worst[k], k]), 3), + "firePressure": round(float(fw[worst[k], k]), 3), + "diff": round(float(diff[k]), 3), + "time": hhmm(base.times[rows[worst[k]]]), + } + for k in order + if abs(diff[k]) > 1e-4 + ] + + residual = params.get("residual") + hydrants = [] + for nid, h in zip(ids, hyd): + entry = {"id": nid} + if h in col: + k = col[h] + # Hydrant: window minimum before/after (what the residual criterion is about). + entry.update(basePressure=round(float(base_min[k]), 3), firePressure=round(float(fire_min[k]), 3)) + if residual not in (None, "") and h in col: + entry["available"] = _available_fire_flow(net, h, col[h], junctions, rows, start, end, float(residual), flow) + hydrants.append(entry) + + return { + "window": [start, end], + "windowLabel": f"{hhmm(start)}~{hhmm(end)}", + "flow": flow, + "hydrants": hydrants, + "nodes": out_nodes, + "belowThreshold": int(np.sum(fire_min < float(threshold))) if threshold not in (None, "") else None, + "belowThresholdBefore": int(np.sum(base_min < float(threshold))) if threshold not in (None, "") else None, + "warnings": [{"time": t, "code": c} for t, c in fire.warnings], + } + + +def _available_fire_flow(net, h, col, junctions, rows, start, end, residual, hint) -> float: + """Largest extra flow at node h keeping its window-min pressure >= residual (bisection).""" + def min_p(q: float) -> float: + net.window_pattern("SPIPE_AFF", start, end) + c = net.add_demand(h, q, "SPIPE_AFF", "aff") + try: + r = net.simulate(nodes=[h]) + finally: + net.delete_demand(h, c) + return float(r.nodes[EN.PRESSURE][rows, 0].min()) + + if min_p(0.0) < residual: + return 0.0 + lo, hi = 0.0, max(hint, 1e-3) + for _ in range(30): # grow until the residual is violated + if min_p(hi) < residual: + break + lo, hi = hi, hi * 2 + else: + return round(hi, 3) + for _ in range(25): + mid = (lo + hi) / 2 + if min_p(mid) >= residual: + lo = mid + else: + hi = mid + if hi - lo <= max(1e-3, 1e-3 * hi): + break + return round(lo, 3) + + +def _window(net: Network, params: dict) -> tuple[int, int]: + start = parse_time(params.get("start"), 0) + end = parse_time(params.get("end"), max(net.duration, net.report_step)) + return start, (end if end > start else start + net.report_step) + + +MAX_CRITICALITY_LINKS = 4000 + + +def job_criticality(inp: bytes, params: dict) -> dict: + """관로 중요도: close each pipe in turn and measure who loses pressure. + + params: links [ids] (default all pipes), threshold (model pressure units), + start/end (window, h). A junction is "affected" when its window-min pressure + falls below the threshold while it was at/above it in the base run. + """ + threshold = float(params.get("threshold") or 0) + with Network(inp) as net: + start, end = _window(net, params) + ids = [str(x) for x in params.get("links") or []] + if ids: + links = [net.link_index(i) for i in ids] + else: + links = [k + 1 for k, t in enumerate(net.link_types) if t in (EN.CVPIPE, EN.PIPE)] + if not links: + raise BatchError("분석할 관로가 없습니다.") + if len(links) > MAX_CRITICALITY_LINKS: + raise BatchError(f"관로가 {len(links)}개입니다. {MAX_CRITICALITY_LINKS}개 이하로 선택해 나눠 실행하세요.") + junctions = net.junction_indices() + base = net.simulate(node_props=(EN.PRESSURE, EN.DEMAND), nodes=junctions) + rows = _window_rows(base.times, start, end) + bmin = base.nodes[EN.PRESSURE][rows].min(axis=0) + bdem = base.nodes[EN.DEMAND][rows].mean(axis=0) + ok_before = bmin >= threshold + out = [] + for li in links: + orig = net.get_link(li, EN.INITSTATUS) + net.set_link(li, EN.INITSTATUS, 0) + entry = {"id": net.link_ids[li - 1]} + try: + r = net.simulate(nodes=junctions) + pmin = r.nodes[EN.PRESSURE][_window_rows(r.times, start, end)].min(axis=0) + affected = np.where((pmin < threshold) & ok_before)[0] + drop = bmin - pmin + order = affected[np.argsort(-drop[affected])] + entry.update( + affectedNodes=int(affected.size), + affectedDemand=round(float(np.sum(np.maximum(bdem[affected], 0))), 4), + minPressure=round(float(pmin.min()), 3), + maxDrop=round(float(drop.max()), 3), + nodes=[net.node_ids[junctions[k] - 1] for k in order[:200]], + warning=any(c in (3, 6) for _, c in r.warnings), + disconnected=bool(pmin.min() < -1000), + ) + except (EpanetException, BatchError) as e: + entry.update(error=str(e), affectedNodes=0, affectedDemand=0.0, minPressure=None, maxDrop=None, nodes=[]) + finally: + net.set_link(li, EN.INITSTATUS, orig) + out.append(entry) + out.sort(key=lambda e: (-e["affectedDemand"], -e["affectedNodes"], -(e["maxDrop"] or 0))) + return { + "window": [start, end], + "windowLabel": f"{hhmm(start)}~{hhmm(end)}", + "threshold": threshold, + "belowBefore": int(np.sum(~ok_before)), + "totalDemand": round(float(np.sum(np.maximum(bdem, 0))), 4), + "links": out, + } + + +def job_servicearea(inp: bytes, params: dict) -> dict: + """급수 구역: source tracing (EPANET TRACE) from each reservoir (tanks optional). + + Returns each node's average share (%) of water from each source over the + analysis window (default: the last 24 h of the run; a run shorter than 24 h + is extended to 72 h so traced water can reach the far ends). + """ + with Network(inp) as net: + notes = [] + if net.duration < 72 * 3600: + # Traced water starts at 0 everywhere; give it time to reach the far ends. + net.set_time_param(EN.DURATION, 72 * 3600) + notes.append(f"해석 기간({net.duration // 3600}시간)을 72시간으로 늘려 추적하고 마지막 24시간을 평균했습니다.") + net.duration = 72 * 3600 + # Default: reservoirs only. A traced tank counts water that merely passed + # through it, so tank shares overlap the reservoir shares (sum > 100%). + kinds = (EN.RESERVOIR, EN.TANK) if params.get("includeTanks") else (EN.RESERVOIR,) + sources = [str(x) for x in params.get("sources") or []] or [ + nid for nid, t in zip(net.node_ids, net.node_types) if t in kinds + ] or [nid for nid, t in zip(net.node_ids, net.node_types) if t == EN.TANK] + if not sources: + raise BatchError("배수지·탱크가 없습니다.") + if len(sources) > 30: + raise BatchError("수원이 30개를 넘습니다. 추적할 수원을 골라 주세요.") + for sid in sources: + net.node_index(sid) + start = parse_time(params.get("start"), max(0, net.duration - 24 * 3600)) + end = parse_time(params.get("end"), net.duration + 1) + net.lib.EN_solveH(net.ph) + shares: dict[str, np.ndarray] = {} + for sid in sources: + net.lib.EN_setqualtype(net.ph, EN.QUAL_TRACE, b"", b"", sid.encode("utf-8")) + times, q = net.run_quality() + rows = _window_rows(times, start, end) + shares[sid] = np.clip(q[rows].mean(axis=0), 0, 100) + mat = np.vstack([shares[s] for s in sources]) # [source, node] + dom = np.argmax(mat, axis=0) + nodes = {} + for k, nid in enumerate(net.node_ids): + col = mat[:, k] + if col.max() < 0.5: + nodes[nid] = {"source": None, "share": 0.0, "shares": {}} + continue + nodes[nid] = { + "source": sources[dom[k]], + "share": round(float(col[dom[k]]), 1), + "shares": {s: round(float(v), 1) for s, v in zip(sources, col) if v >= 0.5}, + } + summary = [] + for sid in sources: + mine = [nid for nid in net.node_ids if nodes[nid]["source"] == sid] + summary.append({"source": sid, "nodes": len(mine), "mixed": sum(1 for nid in mine if nodes[nid]["share"] < 80)}) + return { + "sources": sources, + "window": [start, end], + "windowLabel": f"{hhmm(start)}~{hhmm(end)}", + "nodes": nodes, + "summary": summary, + "unreached": sum(1 for v in nodes.values() if v["source"] is None), + "notes": notes, + } + + +def job_vsp(inp: bytes, params: dict) -> dict: + """VSP: pump speed giving a target pressure at a control node (bisection). + + Each requested hour is solved as a snapshot (DURATION 0, patterns shifted to + that hour, tanks at their initial levels). params: pump, node, target + (model pressure units), hours [list] (default 0..23), minSpeed, maxSpeed. + """ + pump_id, node_id = str(params.get("pump") or ""), str(params.get("node") or "") + target = float(params.get("target") or 0) + lo_s, hi_s = float(params.get("minSpeed") or 0.5), float(params.get("maxSpeed") or 1.2) + if not (0 < lo_s < hi_s): + raise BatchError("속도 범위가 올바르지 않습니다 (0 < 최소 < 최대).") + hours = [float(h) for h in (params.get("hours") or list(range(24)))][:168] + with Network(inp) as net: + pi = net.link_index(pump_id) + if net.link_types[pi - 1] != EN.PUMP: + raise BatchError(f"{pump_id}는 펌프가 아닙니다.") + ni = net.node_index(node_id) + ps0 = net.time_param(EN.PATTERNSTART) + net.set_time_param(EN.DURATION, 0) + + def solve(speed: float) -> tuple[float, float, float]: + net.set_link(pi, EN.INITSTATUS, 1) + net.set_link(pi, EN.INITSETTING, speed) + net.lib.EN_solveH(net.ph) + return net.get_node(ni, EN.PRESSURE), net.get_link(pi, EN.FLOW), net.get_link(pi, EN.ENERGY) + + out = [] + for h in hours: + net.set_time_param(EN.PATTERNSTART, ps0 + int(h * 3600)) + try: + p_hi = solve(hi_s) + p_lo = solve(lo_s) + if p_hi[0] < target: + speed, res, status = hi_s, p_hi, "max" + elif p_lo[0] >= target: + speed, res, status = lo_s, p_lo, "min" + else: + a, b = lo_s, hi_s + for _ in range(30): + m = (a + b) / 2 + if solve(m)[0] >= target: + b = m + else: + a = m + if b - a < 1e-4: + break + speed, res, status = b, solve(b), "ok" + out.append({"hour": h, "speed": round(speed, 4), "pressure": round(res[0], 3), + "flow": round(res[1], 4), "power": round(res[2], 3), "status": status}) + except EpanetException as e: + out.append({"hour": h, "error": str(e)}) + return {"pump": pump_id, "node": node_id, "target": target, "rows": out, + "note": "시간별 정상상태 해석입니다 (탱크는 초기 수위, 그 시각의 시간 제어만 반영)."} + + +# m3/s per model flow unit, by EN_getflowunits code +_M3S = {0: 0.0283168, 1: 6.30902e-5, 2: 0.0438126, 3: 0.0526168, 4: 0.0142764, 5: 1e-3, + 6: 1e-3 / 60, 7: 1e3 / 86400, 8: 1 / 3600, 9: 1 / 86400, 10: 1.0} + + +def job_energy(inp: bytes, params: dict) -> dict: + """에너지 보고서: per-pump utilisation, efficiency, kWh, kWh/m3, peak kW and cost, + integrated over every hydraulic step (prices from [ENERGY] incl. patterns).""" + with Network(inp) as net: + pumps = [k + 1 for k, t in enumerate(net.link_types) if t == EN.PUMP] + if not pumps: + raise BatchError("펌프가 없습니다.") + fu = ctypes.c_int() + net.lib.EN_getflowunits(net.ph, ctypes.byref(fu)) + to_m3s = _M3S.get(fu.value, 1.0) + g_price = net.option(EN.GLOBALPRICE) + g_pat = int(net.option(EN.GLOBALPATTERN)) + d_charge = net.option(EN.DEMANDCHARGE) + g_eff = net.option(EN.GLOBALEFFIC) + ps0 = net.time_param(EN.PATTERNSTART) + price = {p: (net.get_link(p, EN.PUMP_ECOST) or g_price) for p in pumps} + pat = {p: (int(net.get_link(p, EN.PUMP_EPAT)) or g_pat) for p in pumps} + acc = {p: {"on": 0.0, "kwh": 0.0, "m3": 0.0, "effw": 0.0, "peak": 0.0, "cost": 0.0} for p in pumps} + peak_total = 0.0 + total_s = 0.0 + t, tstep = ctypes.c_long(), ctypes.c_long(1) + steady = net.duration == 0 + net.lib.EN_openH(net.ph) + try: + net.lib.EN_initH(net.ph, EN.NOSAVE) + while True: + net.lib.EN_runH(net.ph, ctypes.byref(t)) + now = t.value + # Values at `now` hold until the next hydraulic time. + vals = [(p, abs(net.get_link(p, EN.FLOW)), max(0.0, net.get_link(p, EN.ENERGY)), net.get_link(p, EN.PUMP_EFFIC)) for p in pumps] + net.lib.EN_nextH(net.ph, ctypes.byref(tstep)) + dt = 3600.0 if steady else float(tstep.value) + kw_total = 0.0 + for p, q, kw, eff in vals: + a = acc[p] + if q > 1e-9 and kw > 0: + a["on"] += dt + a["effw"] += eff * dt + a["kwh"] += kw * dt / 3600 + a["m3"] += q * to_m3s * dt + a["peak"] = max(a["peak"], kw) + a["cost"] += kw * dt / 3600 * price[p] * net.pattern_value(pat[p], now, ps0) + kw_total += kw + peak_total = max(peak_total, kw_total) + total_s += dt + if tstep.value <= 0: + break + finally: + net.lib.EN_closeH(net.ph) + total_s = max(total_s, 1.0) + rows = [] + for p in pumps: + a = acc[p] + rows.append({ + "id": net.link_ids[p - 1], + "utilization": round(100 * a["on"] / total_s, 2), + "efficiency": round(100 * a["effw"] / a["on"], 2) if a["on"] else None, # DLL gives a fraction + "kwh": round(a["kwh"], 3), + "kwhPerM3": round(a["kwh"] / a["m3"], 5) if a["m3"] > 0 else None, + "volume": round(a["m3"], 3), + "avgKw": round(a["kwh"] * 3600 / a["on"], 3) if a["on"] else 0.0, + "peakKw": round(a["peak"], 3), + "cost": round(a["cost"], 3), + "costPerDay": round(a["cost"] * 86400 / total_s, 3), + }) + return { + "pumps": rows, + "hours": round(total_s / 3600, 3), + "globalPrice": g_price, + "globalEfficiency": g_eff, + "demandCharge": d_charge, + "peakKw": round(peak_total, 3), + "demandCost": round(peak_total * d_charge, 3), + "totalCost": round(sum(r["cost"] for r in rows) + peak_total * d_charge, 3), + "steady": steady, + } + + +PARAM_PROPS = {"pressure": ("node", EN.PRESSURE), "head": ("node", EN.HEAD), "demand": ("node", EN.DEMAND), + "quality": ("node", EN.QUALITY), "flow": ("link", EN.FLOW), "velocity": ("link", EN.VELOCITY)} + + +def job_calibrate(inp: bytes, params: dict) -> dict: + """자동 보정: per-group pipe roughness multipliers ('roughness') or wall + coefficients ('wall') fitted to observations by bounded least squares. + + params: mode, param, observations [{location, time (s), value (model units)}], + groups {name: [pipe ids]}, bounds [lo, hi], maxEvaluations. + """ + from scipy.optimize import least_squares + + mode = params.get("mode") or "roughness" + param = params.get("param") or "pressure" + if param not in PARAM_PROPS: + raise BatchError(f"보정할 수 없는 항목입니다: {param}") + kind, prop = PARAM_PROPS[param] + if mode == "wall" and param != "quality": + raise BatchError("관벽 반응계수 보정에는 수질(잔류염소) 실측값이 필요합니다.") + if mode == "roughness" and param == "quality": + raise BatchError("조도 보정에는 수압·수두·유량 실측값을 쓰세요.") + obs = [o for o in params.get("observations") or [] if o.get("location") is not None and o.get("value") is not None] + if len(obs) < 2: + raise BatchError("실측값이 2개 이상 필요합니다.") + groups = {str(k): [str(i) for i in v] for k, v in (params.get("groups") or {}).items() if v} + if not groups: + raise BatchError("보정할 관로 그룹이 없습니다.") + if len(groups) > 30: + raise BatchError("그룹이 30개를 넘습니다. 묶음을 줄이세요.") + lo, hi = (float(b) for b in (params.get("bounds") or ((0.5, 1.5) if mode == "roughness" else (-5.0, 0.0)))) + if not lo < hi: + raise BatchError("범위가 올바르지 않습니다.") + max_eval = int(params.get("maxEvaluations") or 200) + + with Network(inp) as net: + locs = sorted({str(o["location"]) for o in obs}) + index = {} + for loc in locs: + index[loc] = net.node_index(loc) if kind == "node" else net.link_index(loc) + cols = {loc: k for k, loc in enumerate(locs)} + o_col = np.array([cols[str(o["location"])] for o in obs]) + o_t = np.array([float(o["time"]) for o in obs]) + o_v = np.array([float(o["value"]) for o in obs]) + g_names = list(groups) + g_links = [[net.link_index(i) for i in groups[g]] for g in g_names] + if mode == "wall": + qt = ctypes.c_int(); tr = ctypes.c_int() + net.lib.EN_getqualinfo(net.ph, ctypes.byref(qt), ctypes.create_string_buffer(64), ctypes.create_string_buffer(64), ctypes.byref(tr)) + if qt.value != 1: + raise BatchError("모델의 수질 해석이 화학물질(CHEMICAL)이 아닙니다. 모델 설정에서 수질 모드를 바꾸세요.") + base = [np.array([net.get_link(li, EN.KWALL) for li in links]) for links in g_links] + x0 = np.array([float(np.clip(np.mean(b) if len(b) else (lo + hi) / 2, lo, hi)) for b in base]) + net.lib.EN_solveH(net.ph) # wall reactions do not change hydraulics + else: + base = [np.array([net.get_link(li, EN.ROUGHNESS) for li in links]) for links in g_links] + x0 = np.clip(np.ones(len(g_names)), lo, hi) + + def apply(x): + for links, b, v in zip(g_links, base, x): + for li, bv in zip(links, b): + net.set_link(li, EN.KWALL if mode == "wall" else EN.ROUGHNESS, v if mode == "wall" else bv * v) + + def simulate_values() -> np.ndarray: + if mode == "wall": + times, q = net.run_quality(nodes=[index[l] for l in locs]) + times = np.asarray(times, dtype=float) + mat = q + else: + r = net.simulate(node_props=(prop,) if kind == "node" else (), link_props=(prop,) if kind == "link" else (), + nodes=[index[l] for l in locs] if kind == "node" else [], links=[index[l] for l in locs] if kind == "link" else []) + times = np.asarray(r.times, dtype=float) + mat = (r.nodes if kind == "node" else r.links)[prop] + if param == "flow": + mat = np.abs(mat) + out = np.empty(len(obs)) + for c in range(len(locs)): + sel = o_col == c + out[sel] = np.interp(o_t[sel], times, mat[:, c]) if len(times) > 1 else mat[0, c] + return out + + evals = {"n": 0} + + def residuals(x): + evals["n"] += 1 + apply(x) + try: + return simulate_values() - o_v + except (EpanetException, BatchError): + return np.full(len(obs), 1e3) + + r0 = residuals(x0) + fit = least_squares(residuals, x0, bounds=(np.full(len(x0), lo), np.full(len(x0), hi)), + diff_step=0.02 if mode == "roughness" else 0.05, max_nfev=max_eval, xtol=1e-4, ftol=1e-6) + r1 = residuals(fit.x) + apply(x0) # leave the network as it was (not strictly needed) + rmse = lambda r: float(np.sqrt(np.mean(r ** 2))) + out_groups = [] + for name, links, b, v in zip(g_names, g_links, base, fit.x): + out_groups.append({ + "name": name, "pipes": len(links), + "value": round(float(v), 4), + "meanBefore": round(float(np.mean(b)), 4) if len(b) else None, + "meanAfter": round(float(np.mean(b * v)) if mode == "roughness" else float(v), 4) if len(b) else None, + "atBound": bool(abs(v - lo) < 1e-6 or abs(v - hi) < 1e-6), + }) + per_loc = [] + for c, loc in enumerate(locs): + sel = o_col == c + per_loc.append({"location": loc, "n": int(sel.sum()), "before": round(rmse(r0[sel]), 4), "after": round(rmse(r1[sel]), 4)}) + return { + "mode": mode, "param": param, "groups": out_groups, "locations": per_loc, + "rmseBefore": round(rmse(r0), 4), "rmseAfter": round(rmse(r1), 4), + "evaluations": evals["n"], "converged": bool(fit.success), "message": str(fit.message), + "observations": len(obs), + } + + +def _pump_controls(net: Network, pump_idx: set) -> list[int]: + """1-based indices of simple controls acting on the given pumps.""" + n = ctypes.c_int() + net.lib.EN_getcount(net.ph, 5, ctypes.byref(n)) + out = [] + t, li, ni = ctypes.c_int(), ctypes.c_int(), ctypes.c_int() + st, lv = ctypes.c_double(), ctypes.c_double() + for k in range(1, n.value + 1): + net.lib.EN_getcontrol(net.ph, k, ctypes.byref(t), ctypes.byref(li), ctypes.byref(st), ctypes.byref(ni), ctypes.byref(lv)) + if li.value in pump_idx: + out.append(k) + return out + + +def job_pumpschedule(inp: bytes, params: dict) -> dict: + """펌프 운전 최적화: hourly (pattern-step) on/off schedules that cut energy cost + while keeping junction pressure >= minPressure and tanks back to their start + level at the end. Existing simple controls on those pumps are ignored. + + params: pumps [ids] (default all), minPressure (model units), maxEvaluations, + tankTolerance (fraction of tank range allowed below the start level at the end). + """ + min_p = float(params.get("minPressure") or 0) + budget = int(params.get("maxEvaluations") or 400) + tank_tol = float(params.get("tankTolerance") if params.get("tankTolerance") is not None else 0.02) + with Network(inp) as net: + all_pumps = [k + 1 for k, t in enumerate(net.link_types) if t == EN.PUMP] + pumps = [net.link_index(str(p)) for p in params.get("pumps") or []] or all_pumps + if not pumps: + raise BatchError("펌프가 없습니다.") + if net.duration < net.pattern_step: + raise BatchError("연장 기간 해석(EPS) 모델이어야 합니다.") + rules = ctypes.c_int() + net.lib.EN_getcount(net.ph, 6, ctypes.byref(rules)) + controls = _pump_controls(net, set(pumps)) + slot = net.pattern_step + n_slots = int(np.ceil(net.duration / slot)) + if n_slots > 168: + raise BatchError("시간 구간이 168개를 넘습니다 (패턴 간격을 늘리세요).") + junctions = net.junction_indices() + tanks = [k + 1 for k, t in enumerate(net.node_types) if t == EN.TANK] + t_min = np.array([net.get_node(t, 20) for t in tanks]) + t_max = np.array([net.get_node(t, 21) for t in tanks]) + t_init = np.array([net.get_node(t, EN.TANKLEVEL) for t in tanks]) # initial level + t_elev = np.array([net.get_node(t, EN.ELEVATION) for t in tanks]) + ps0 = net.time_param(EN.PATTERNSTART) + g_price = net.option(EN.GLOBALPRICE) + g_pat = int(net.option(EN.GLOBALPATTERN)) + price = {p: (net.get_link(p, EN.PUMP_ECOST) or g_price or 1.0) for p in pumps} + ppat = {p: (int(net.get_link(p, EN.PUMP_EPAT)) or g_pat) for p in pumps} + priced = bool(g_price or any(net.get_link(p, EN.PUMP_ECOST) for p in pumps)) + + def evaluate(sched: dict | None) -> dict: + t, tstep = ctypes.c_long(), ctypes.c_long(1) + cost = kwh = 0.0 + pmin = np.inf + level_end = t_init.copy() + net.lib.EN_openH(net.ph) + try: + net.lib.EN_initH(net.ph, EN.NOSAVE) + while True: + net.lib.EN_runH(net.ph, ctypes.byref(t)) + now = t.value + kw = [max(0.0, net.get_link(p, EN.ENERGY)) for p in pumps] + if net.is_report_time(now) and junctions: + pmin = min(pmin, min(net.get_node(j, EN.PRESSURE) for j in junctions)) + if tanks: + # EN_TANKLEVEL reads back the *initial* level; the current one is head - bottom. + level_end = np.array([net.get_node(tk, EN.HEAD) for tk in tanks]) - t_elev + net.lib.EN_nextH(net.ph, ctypes.byref(tstep)) + dt = float(tstep.value) + for p, k in zip(pumps, kw): + kwh += k * dt / 3600 + cost += k * dt / 3600 * price[p] * net.pattern_value(ppat[p], now, ps0) + if tstep.value <= 0: + break + finally: + net.lib.EN_closeH(net.ph) + short = np.maximum(0.0, (t_init - tank_tol * (t_max - t_min)) - level_end) if tanks else np.zeros(0) + violation = max(0.0, min_p - pmin) + float(np.sum(short)) + return {"cost": cost, "kwh": kwh, "minPressure": float(pmin), "violation": violation, "tankEnd": level_end.tolist()} + + base = evaluate(None) + # Baseline schedule = what the existing controls did (pump on in each slot?). + status = {p: np.zeros(n_slots) for p in pumps} + r = net.simulate(node_props=(), link_props=(EN.FLOW,), nodes=[], links=pumps) + for c, p in enumerate(pumps): + for k, tt in enumerate(r.times): + s = min(n_slots - 1, int(tt // slot)) + if abs(r.links[EN.FLOW][k, c]) > 1e-9: + status[p][s] = 1 + # Take control of the pumps: drop their simple controls, drive them by a pattern. + for k in sorted(controls, reverse=True): + net.delete_control(k) + for p in pumps: + pid = f"SPSCH{p}" + net.add_pattern(pid, [1.0] * n_slots) + net.set_link(p, 15, float(_pattern_index(net, pid))) + net.set_link(p, EN.INITSTATUS, 1) + evals = {"n": 0} + + def run(sched): + evals["n"] += 1 + for p in pumps: + net.add_pattern(f"SPSCH{p}", [float(v) for v in sched[p]]) + return evaluate(sched) + + cur = {p: status[p].copy() for p in pumps} + cur_res = run(cur) + if cur_res["violation"] > 1e-6: + # Pattern replay of the control schedule is not feasible (e.g. sub-slot switching): start from all on. + cur = {p: np.ones(n_slots) for p in pumps} + cur_res = run(cur) + start_res = cur_res + slot_price = {p: np.array([price[p] * net.pattern_value(ppat[p], k * slot, ps0) for k in range(n_slots)]) for p in pumps} + + improved = True + while improved and evals["n"] < budget: + improved = False + # 1) switch off the most expensive running slots + cand = sorted(((slot_price[p][k], p, k) for p in pumps for k in range(n_slots) if cur[p][k] > 0), reverse=True) + for _, p, k in cand: + if evals["n"] >= budget: + break + trial = {q: cur[q].copy() for q in pumps} + trial[p][k] = 0 + res = run(trial) + if res["violation"] <= 1e-6 and res["cost"] < cur_res["cost"] - 1e-9: + cur, cur_res, improved = trial, res, True + # 2) move running time from expensive to cheap slots + for p in pumps: + on = [k for k in range(n_slots) if cur[p][k] > 0] + off = [k for k in range(n_slots) if cur[p][k] == 0] + for k_on in sorted(on, key=lambda k: -slot_price[p][k]): + for k_off in sorted(off, key=lambda k: slot_price[p][k]): + if evals["n"] >= budget or slot_price[p][k_off] >= slot_price[p][k_on]: + break + trial = {q: cur[q].copy() for q in pumps} + trial[p][k_on], trial[p][k_off] = 0, 1 + res = run(trial) + if res["violation"] <= 1e-6 and res["cost"] < cur_res["cost"] - 1e-9: + cur, cur_res, improved = trial, res, True + off.remove(k_off) + off.append(k_on) + break + return { + "pumps": [net.link_ids[p - 1] for p in pumps], + "slotSeconds": slot, + "schedules": {net.link_ids[p - 1]: [int(v) for v in cur[p]] for p in pumps}, + "before": {k: round(v, 3) if isinstance(v, float) else v for k, v in base.items()}, + "start": {k: round(v, 3) if isinstance(v, float) else v for k, v in start_res.items()}, + "after": {k: round(v, 3) if isinstance(v, float) else v for k, v in cur_res.items()}, + "priced": priced, + "evaluations": evals["n"], + "removedControls": len(controls), + "rules": rules.value, + "feasible": cur_res["violation"] <= 1e-6, + } + + +def _pattern_index(net: Network, pid: str) -> int: + idx = ctypes.c_int() + net.lib.EN_getpatternindex(net.ph, pid.encode(), ctypes.byref(idx)) + return idx.value + + +JOBS = { + "summary": job_summary, + "fireflow": job_fireflow, + "criticality": job_criticality, + "servicearea": job_servicearea, + "vsp": job_vsp, + "energy": job_energy, + "calibrate": job_calibrate, + "pumpschedule": job_pumpschedule, +} + + +def get_job(name: str): + fn = JOBS.get(name) + if fn is None: + raise BatchError(f"알 수 없는 분석입니다: {name}") + return fn diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..a128aff --- /dev/null +++ b/backend/main.py @@ -0,0 +1,743 @@ +import asyncio +import ctypes +import json +import logging +import os +import re +import tempfile +from concurrent.futures import ProcessPoolExecutor + +from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse, Response + +from .batch import BatchError +from .columnar import to_columnar +from .gisio import read_vector +from . import dem as dem_store +from . import gisops +from urllib.parse import quote +from .jobs import get_job +from .epanet import EN, EpanetAPI, EpanetException, FLOW_UNITS, PRESS_UNITS, QUAL_TYPES + +logger = logging.getLogger("spipe") + +# Limits (overridable per deployment). The upload limit must match nginx's +# client_max_body_size; the period/cell caps bound CPU time and response size +# for inputs like DURATION 10000 hrs + HYDRAULIC TIMESTEP 1 sec. +MAX_UPLOAD_BYTES = int(float(os.environ.get("SPIPE_MAX_UPLOAD_MB", "20")) * 1024 * 1024) +# DEM rasters are much larger than INP/GIS files; streamed to disk, never held in memory. +MAX_DEM_BYTES = int(float(os.environ.get("SPIPE_MAX_DEM_MB", "300")) * 1024 * 1024) +ANALYZE_TIMEOUT_S = float(os.environ.get("SPIPE_ANALYZE_TIMEOUT_S", "120")) +MAX_PERIODS = int(os.environ.get("SPIPE_MAX_PERIODS", "10000")) +MAX_RESULT_CELLS = int(os.environ.get("SPIPE_MAX_RESULT_CELLS", "50000000")) +# Batch jobs (criticality, optimisation ...) solve the network many times. +JOB_TIMEOUT_S = float(os.environ.get("SPIPE_JOB_TIMEOUT_S", "900")) +WORKERS = int(os.environ.get("SPIPE_WORKERS", str(max(1, min(4, (os.cpu_count() or 2) - 1))))) + +app = FastAPI() + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"], + allow_methods=["*"], + allow_headers=["*"], +) + +api = EpanetAPI() + +# Analyses run in separate processes: the ctypes loop is CPU-bound and +# synchronous, so running it on the event loop froze every other request +# (health checks waited up to 16 s behind one large network). +_pool = None + + +def _get_pool() -> ProcessPoolExecutor: + global _pool + if _pool is None: + _pool = ProcessPoolExecutor(max_workers=WORKERS) + return _pool + + +@app.middleware("http") +async def reject_oversized_body(request: Request, call_next): + # Starlette spools the whole multipart body before the handler runs, so + # check the declared size first instead of after receiving it all. + # scope["path"] is the routed path; request.url is rebuilt from the Host + # header, so a malformed Host could make the path check miss (GHSA-86qp). + length = request.headers.get("content-length") + path = request.scope.get("path", "") + if (path == "/api/analyze" or path.startswith(("/api/jobs/", "/api/gis/"))) and length and length.isdigit() and int(length) > MAX_UPLOAD_BYTES + 64 * 1024: + return JSONResponse(status_code=413, content={"detail": f"File size exceeds {MAX_UPLOAD_BYTES // (1024 * 1024)} MiB limit"}) + return await call_next(request) + + +def run_job_bytes(name: str, contents: bytes, params: dict) -> dict: + """Process-pool entry point for /api/jobs/{name}; same envelope as analyze_bytes.""" + try: + result = get_job(name)(contents, params) + return {"json": json.dumps(result, ensure_ascii=False, allow_nan=False, separators=(",", ":")).encode("utf-8")} + except BatchError as e: + return {"error": (400, str(e))} + except EpanetException as e: + return {"error": (422, str(e))} + except ValueError as e: + return {"error": (500, f"Result contains non-finite values: {e}")} + + +@app.post("/api/jobs/{name}") +async def run_job(name: str, file: UploadFile = File(...), params: str = Form("{}")): + """Batch analysis over one INP. `params` is a JSON object (see backend/jobs.py).""" + if not (file.filename or "").lower().endswith(".inp"): + raise HTTPException(status_code=400, detail="File must be an .inp file") + try: + parsed = json.loads(params or "{}") + if not isinstance(parsed, dict): + raise ValueError + except ValueError: + raise HTTPException(status_code=400, detail="params must be a JSON object") + contents = await file.read(MAX_UPLOAD_BYTES + 1) + if len(contents) > MAX_UPLOAD_BYTES: + raise HTTPException(status_code=413, detail=f"File size exceeds {MAX_UPLOAD_BYTES // (1024 * 1024)} MiB limit") + loop = asyncio.get_running_loop() + try: + outcome = await asyncio.wait_for( + loop.run_in_executor(_get_pool(), run_job_bytes, name, contents, parsed), + timeout=JOB_TIMEOUT_S, + ) + except asyncio.TimeoutError: + raise HTTPException(status_code=504, detail=f"Analysis exceeded {JOB_TIMEOUT_S:.0f} s time limit") + if "error" in outcome: + status, detail = outcome["error"] + raise HTTPException(status_code=status, detail=detail) + return Response(content=outcome["json"], media_type="application/json") + + +def read_vector_bytes(contents: bytes, filename: str, layer: str | None, src_epsg: int | None, default_crs: str | None = None) -> dict: + try: + result = read_vector(contents, filename, layer, src_epsg, default_crs) + return {"json": json.dumps(result, ensure_ascii=False, allow_nan=False, separators=(",", ":")).encode("utf-8")} + except BatchError as e: + return {"error": (400, str(e))} + + +@app.post("/api/gis/vector") +async def gis_vector(file: UploadFile = File(...), layer: str = Form(""), srcEpsg: str = Form(""), defaultCrs: str = Form("")): + """Any GDAL vector format (zipped SHP, DXF, DGN, MIF, GML, KML, GPKG, E00 ...) -> GeoJSON (WGS84).""" + src_epsg = None + if srcEpsg.strip(): + if not srcEpsg.strip().isdigit(): + raise HTTPException(status_code=400, detail="srcEpsg must be an EPSG number") + src_epsg = int(srcEpsg) + contents = await file.read(MAX_UPLOAD_BYTES + 1) + if len(contents) > MAX_UPLOAD_BYTES: + raise HTTPException(status_code=413, detail=f"File size exceeds {MAX_UPLOAD_BYTES // (1024 * 1024)} MiB limit") + loop = asyncio.get_running_loop() + try: + outcome = await asyncio.wait_for( + loop.run_in_executor(_get_pool(), read_vector_bytes, contents, file.filename or "", layer or None, src_epsg, defaultCrs or None), + timeout=ANALYZE_TIMEOUT_S, + ) + except asyncio.TimeoutError: + raise HTTPException(status_code=504, detail="GIS conversion timed out") + if "error" in outcome: + status, detail = outcome["error"] + raise HTTPException(status_code=status, detail=detail) + return Response(content=outcome["json"], media_type="application/json") + + +def _dem_call(fn, *args): + """Run a DEM operation; BatchError -> 400 (pickle-safe tuple for the pool).""" + try: + return {"ok": fn(*args)} + except dem_store.BatchError as e: + return {"error": (400, str(e))} + + +async def _dem_run(fn, *args, timeout: float = 300): + loop = asyncio.get_running_loop() + try: + outcome = await asyncio.wait_for(loop.run_in_executor(None, _dem_call, fn, *args), timeout=timeout) + except asyncio.TimeoutError: + raise HTTPException(status_code=504, detail="DEM processing timed out") + if "error" in outcome: + status, detail = outcome["error"] + raise HTTPException(status_code=status, detail=detail) + return outcome["ok"] + + +@app.post("/api/dem") +async def dem_upload(file: UploadFile = File(...)): + """Store a DEM raster (GeoTIFF / ASC / IMG, or a zip holding one) and return its metadata.""" + fd, tmp = tempfile.mkstemp(prefix="spipe_dem_") + size = 0 + try: + with os.fdopen(fd, "wb") as f: + while chunk := await file.read(1024 * 1024): + size += len(chunk) + if size > MAX_DEM_BYTES: + raise HTTPException(status_code=413, detail=f"DEM exceeds {MAX_DEM_BYTES // (1024 * 1024)} MiB limit") + f.write(chunk) + return await _dem_run(dem_store.import_dem, tmp, file.filename or "dem.tif") + finally: + try: + os.remove(tmp) + except OSError: + pass + + +@app.get("/api/dem/{dem_id}") +async def dem_get(dem_id: str): + return await _dem_run(dem_store.dem_info, dem_id) + + +@app.get("/api/dem/{dem_id}/preview.png") +async def dem_preview(dem_id: str): + png = await _dem_run(dem_store.preview_png, dem_id) + return Response(content=png, media_type="image/png", headers={"Cache-Control": "private, max-age=86400"}) + + +@app.get("/api/dem/{dem_id}/terrain/{z}/{x}/{y}.png") +async def dem_terrain(dem_id: str, z: int, x: int, y: int): + """Terrarium-encoded terrain tiles for the 3D view.""" + png = await _dem_run(dem_store.terrain_tile, dem_id, z, x, y, timeout=60) + return Response(content=png, media_type="image/png", headers={"Cache-Control": "private, max-age=86400"}) + + +@app.post("/api/dem/{dem_id}/sample") +async def dem_sample(dem_id: str, request: Request): + """Body: {"points": [[x, y], ...], "crs": ""}.""" + body = await request.json() + points = body.get("points") or [] + if len(points) > 500_000: + raise HTTPException(status_code=400, detail="too many points") + return {"values": await _dem_run(dem_store.sample, dem_id, points, body.get("crs"))} + + +@app.post("/api/dem/{dem_id}/hydro") +async def dem_hydro(dem_id: str, request: Request): + """Body: {"op": "streams", "minAreaKm2": 0.1} or {"op": "watershed", "points": [[x, y]], "crs": "...", "snap": m}.""" + body = await request.json() + if body.get("op") == "streams": + return await _dem_run(dem_store.streams, dem_id, float(body.get("minAreaKm2") or 0.1), timeout=600) + if body.get("op") == "watershed": + return await _dem_run(dem_store.watershed, dem_id, body.get("points") or [], body.get("crs"), float(body.get("snap") or 50), timeout=600) + raise HTTPException(status_code=400, detail="op must be streams or watershed") + + +@app.post("/api/dem/from-points") +async def dem_from_points(request: Request): + """Body: {"points": [[x, y, z], ...], "crs": "...", "cell": metres, "name": "..."} -> new DEM.""" + body = await request.json() + points = body.get("points") or [] + if len(points) > 2_000_000: + raise HTTPException(status_code=400, detail="too many points") + return await _dem_run(dem_store.from_points, points, body.get("crs"), float(body.get("cell") or 0), str(body.get("name") or "점 표고 DEM"), body.get("targetCrs")) + + +@app.post("/api/gis/export") +async def gis_export(request: Request): + """Body: {"featureCollection": {...WGS84}, "crs": "", "format": "shp|gpkg|dxf|geojson|kml", "name": "..."}.""" + body = await request.json() + data, filename, media = await _dem_run(gisops.write_vector, body.get("featureCollection"), body.get("crs"), str(body.get("format") or "geojson"), str(body.get("name") or "layer")) + return Response(content=data, media_type=media, headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"}) + + +@app.post("/api/gis/op") +async def gis_op(request: Request): + """Body: {"op": "buffer|dissolve|intersection|clip|difference|voronoi|grid", "crs": "", ...}.""" + body = await request.json() + return await _dem_run(gisops.vector_op, str(body.get("op") or ""), body) + + +@app.post("/api/gis/reproject") +async def gis_reproject(request: Request): + """Body: {"featureCollection": {...}, "from": "", "to": ""}.""" + body = await request.json() + return await _dem_run(gisops.reproject_fc, body.get("featureCollection"), body.get("from"), body.get("to")) + + +@app.get("/api/health") +def health(): + return {"status": "ok"} + + +def analyze_bytes(contents: bytes, columnar: bool = False) -> dict: + """Process-pool entry point. Returns {"json": bytes} or {"error": (status, detail)} + because HTTPException does not survive pickling back to the parent.""" + try: + with tempfile.TemporaryDirectory() as tmpdir: + inp_path = os.path.join(tmpdir, "network.inp") + rpt_path = os.path.join(tmpdir, "network.rpt") + out_path = os.path.join(tmpdir, "network.out") + with open(inp_path, "wb") as f: + f.write(contents) + result = run_epanet(inp_path, rpt_path, out_path) + if columnar: + result = to_columnar(result) + # Serialise here, in the worker: encoding a 10 MB result on the event + # loop stalled other requests by ~0.5 s per response. + body = json.dumps(result, ensure_ascii=False, allow_nan=False, separators=(",", ":")).encode("utf-8") + return {"json": body} + except HTTPException as e: + return {"error": (e.status_code, e.detail)} + except ValueError as e: # NaN/Inf in results + return {"error": (500, f"Result contains non-finite values: {e}")} + + +@app.post("/api/analyze") +async def analyze(file: UploadFile = File(...), format: str = "object"): + """Full EPS analysis. ``format=columnar`` returns the compact packed time + series (backend/columnar.py); the default keeps the per-element objects.""" + if not (file.filename or "").lower().endswith(".inp"): + raise HTTPException(status_code=400, detail="File must be an .inp file") + + contents = await file.read(MAX_UPLOAD_BYTES + 1) + if len(contents) > MAX_UPLOAD_BYTES: + raise HTTPException(status_code=413, detail=f"File size exceeds {MAX_UPLOAD_BYTES // (1024 * 1024)} MiB limit") + + loop = asyncio.get_running_loop() + try: + outcome = await asyncio.wait_for( + loop.run_in_executor(_get_pool(), analyze_bytes, contents, format == "columnar"), + timeout=ANALYZE_TIMEOUT_S, + ) + except asyncio.TimeoutError: + raise HTTPException(status_code=504, detail=f"Analysis exceeded {ANALYZE_TIMEOUT_S:.0f} s time limit") + if "error" in outcome: + status, detail = outcome["error"] + raise HTTPException(status_code=status, detail=detail) + return Response(content=outcome["json"], media_type="application/json") + + +def decode_id(raw_bytes: bytes) -> str: + try: + return raw_bytes.decode('utf-8') + except UnicodeDecodeError: + try: + return raw_bytes.decode('cp949') + except UnicodeDecodeError: + return raw_bytes.decode('latin1', errors='replace') + +def read_report_errors(rpt_path: str, limit: int = 10) -> list: + """Return the 'Error NNN: ...' lines EPANET wrote to the report file.""" + if not os.path.exists(rpt_path): + return [] + try: + with open(rpt_path, "r", encoding="utf-8", errors="replace") as rf: + lines = [ln.strip() for ln in rf if re.match(r"\s*Error \d+", ln)] + except OSError: + return [] + return lines[:limit] + +def run_epanet(inp_path: str, rpt_path: str, out_path: str): + ph = api.EN_Project() + + # 1. Version check + version = ctypes.c_int() + api.lib.EN_getversion(ctypes.byref(version)) + if version.value != 20305: + raise HTTPException(status_code=500, detail=f"Unsupported EPANET version: {version.value}, expected 20305") + + ph_created = False + ph_opened = False + hydraulics_opened = False + + try: + api.lib.EN_createproject(ctypes.byref(ph)) + ph_created = True + + # open -- a failed EN_open still leaves the input/report files open, so + # EN_close must run in the finally block either way (EN_runproject does + # the same); otherwise Windows can't delete the temp dir afterwards. + ph_opened = True + api.lib.EN_open(ph, inp_path.encode('utf-8'), rpt_path.encode('utf-8'), out_path.encode('utf-8')) + + # get units + flow_unit = ctypes.c_int() + api.lib.EN_getflowunits(ph, ctypes.byref(flow_unit)) + f_unit_str = FLOW_UNITS.get(flow_unit.value, "Unknown") + + press_unit = ctypes.c_double() + api.lib.EN_getoption(ph, EN.PRESS_UNITS, ctypes.byref(press_unit)) + p_unit_str = PRESS_UNITS.get(int(press_unit.value), "Unknown") + + # get counts + node_count = ctypes.c_int() + api.lib.EN_getcount(ph, EN.NODECOUNT, ctypes.byref(node_count)) + + link_count = ctypes.c_int() + api.lib.EN_getcount(ph, EN.LINKCOUNT, ctypes.byref(link_count)) + + # Reject resource-exhausting inputs before solving (a few-KB INP with + # DURATION 10000 hrs / HYDRAULIC TIMESTEP 1 sec asks for 36M periods). + duration, hstep = ctypes.c_long(), ctypes.c_long() + api.lib.EN_gettimeparam(ph, EN.DURATION, ctypes.byref(duration)) + api.lib.EN_gettimeparam(ph, EN.HYDSTEP, ctypes.byref(hstep)) + report_step, report_start = ctypes.c_long(), ctypes.c_long() + api.lib.EN_gettimeparam(ph, EN.REPORTSTEP, ctypes.byref(report_step)) + api.lib.EN_gettimeparam(ph, EN.REPORTSTART, ctypes.byref(report_start)) + planned_periods = duration.value // max(1, hstep.value) + 1 + planned_cells = planned_periods * (node_count.value + link_count.value) + if planned_periods > MAX_PERIODS or planned_cells > MAX_RESULT_CELLS: + raise HTTPException( + status_code=413, + detail=( + f"Simulation too large: {planned_periods} hydraulic periods x " + f"{node_count.value + link_count.value} elements " + f"(limits {MAX_PERIODS} periods, {MAX_RESULT_CELLS} result values). " + "Increase HYDRAULIC TIMESTEP or shorten DURATION." + ), + ) + + # extract node metadata + nodes = {} + node_id_map = {} + for i in range(1, node_count.value + 1): + node_id = ctypes.create_string_buffer(256) + api.lib.EN_getnodeid(ph, i, node_id) + nid = decode_id(node_id.value) + node_id_map[i] = nid + + node_type = ctypes.c_int() + api.lib.EN_getnodetype(ph, i, ctypes.byref(node_type)) + ntype = node_type.value + + x, y = ctypes.c_double(), ctypes.c_double() + coords = None + try: + api.lib.EN_getcoord(ph, i, ctypes.byref(x), ctypes.byref(y)) + coords = {"x": x.value, "y": y.value} + except EpanetException: + pass + + nodes[nid] = { + "id": nid, + "type": ntype, + "coordinates": coords, + } + + # extract link metadata + links = {} + link_id_map = {} + for i in range(1, link_count.value + 1): + link_id = ctypes.create_string_buffer(256) + api.lib.EN_getlinkid(ph, i, link_id) + lid = decode_id(link_id.value) + link_id_map[i] = lid + + link_type = ctypes.c_int() + api.lib.EN_getlinktype(ph, i, ctypes.byref(link_type)) + ltype = link_type.value + + n1, n2 = ctypes.c_int(), ctypes.c_int() + api.lib.EN_getlinknodes(ph, i, ctypes.byref(n1), ctypes.byref(n2)) + + n1_id = ctypes.create_string_buffer(256) + api.lib.EN_getnodeid(ph, n1.value, n1_id) + + n2_id = ctypes.create_string_buffer(256) + api.lib.EN_getnodeid(ph, n2.value, n2_id) + + v_count = ctypes.c_int() + api.lib.EN_getvertexcount(ph, i, ctypes.byref(v_count)) + + vertices = [] + for j in range(1, v_count.value + 1): + vx, vy = ctypes.c_double(), ctypes.c_double() + api.lib.EN_getvertex(ph, i, j, ctypes.byref(vx), ctypes.byref(vy)) + vertices.append({"x": vx.value, "y": vy.value}) + + links[lid] = { + "id": lid, + "type": ltype, + "fromNode": decode_id(n1_id.value), + "toNode": decode_id(n2_id.value), + "vertices": vertices, + } + + # 3. Open hydraulics with EN_SAVE=1 (Save to hyd file so water quality can use it) + api.lib.EN_openH(ph) + hydraulics_opened = True + api.lib.EN_initH(ph, EN.SAVE) + + time_steps = [] + node_series = {nid: {"demand": [], "head": [], "pressure": [], "quality": []} for nid in nodes} + link_series = {lid: {"flow": [], "velocity": [], "headloss": [], "quality": [], "energy": [], "efficiency": []} for lid in links} + + t = ctypes.c_long() + tstep = ctypes.c_long(1) + + engine_warnings = [] + while True: + # Warning codes 1-6 (e.g. 1 unbalanced, 6 negative pressures) are + # returned here, not raised; they only reach the report file when + # [REPORT] MESSAGES is on, so collect them directly. + code = api.lib.EN_runH(ph, ctypes.byref(t)) + if 0 < code <= 10: + engine_warnings.append({"time": t.value, "code": code}) + time_steps.append(t.value) + # Control/tank events add steps beyond DURATION/HYDSTEP; keep the cap hard. + if len(time_steps) > MAX_PERIODS * 2: + raise HTTPException(status_code=413, detail=f"Simulation produced more than {MAX_PERIODS * 2} hydraulic periods") + + for i in range(1, node_count.value + 1): + nid = node_id_map[i] + d, h, p = ctypes.c_double(), ctypes.c_double(), ctypes.c_double() + api.lib.EN_getnodevalue(ph, i, EN.DEMAND, ctypes.byref(d)) + api.lib.EN_getnodevalue(ph, i, EN.HEAD, ctypes.byref(h)) + api.lib.EN_getnodevalue(ph, i, EN.PRESSURE, ctypes.byref(p)) + node_series[nid]["demand"].append(d.value) + node_series[nid]["head"].append(h.value) + node_series[nid]["pressure"].append(p.value) + + for i in range(1, link_count.value + 1): + lid = link_id_map[i] + f, v, hl = ctypes.c_double(), ctypes.c_double(), ctypes.c_double() + api.lib.EN_getlinkvalue(ph, i, EN.FLOW, ctypes.byref(f)) + api.lib.EN_getlinkvalue(ph, i, EN.VELOCITY, ctypes.byref(v)) + api.lib.EN_getlinkvalue(ph, i, EN.HEADLOSS, ctypes.byref(hl)) + link_series[lid]["flow"].append(f.value) + link_series[lid]["velocity"].append(v.value) + link_series[lid]["headloss"].append(hl.value) + + if links[lid]["type"] == EN.PUMP: + e_val, ef_val = ctypes.c_double(), ctypes.c_double() + api.lib.EN_getlinkvalue(ph, i, EN.ENERGY, ctypes.byref(e_val)) + api.lib.EN_getlinkvalue(ph, i, EN.PUMP_EFFIC, ctypes.byref(ef_val)) + link_series[lid]["energy"].append(round(e_val.value, 2)) + # EN_PUMP_EFFIC is a decimal ratio (0.75 for 75%), convert to percent + link_series[lid]["efficiency"].append(round(ef_val.value * 100, 1)) + api.lib.EN_nextH(ph, ctypes.byref(tstep)) + if tstep.value <= 0: + break + + # Close hydraulics before running quality + api.lib.EN_closeH(ph) + hydraulics_opened = False + + # 4. Check and run Water Quality Simulation (Chlorine/Fluoride decay, Water Age, Chemical Tracer) + qual_type = ctypes.c_int(0) + trace_node = ctypes.c_int(0) + chem_name = ctypes.create_string_buffer(256) + chem_units = ctypes.create_string_buffer(256) + quality_info = None + quality_failed = False + quality_error_message = None + try: + api.lib.EN_getqualinfo(ph, ctypes.byref(qual_type), chem_name, chem_units, ctypes.byref(trace_node)) + q_val = qual_type.value + if q_val > 0: + quality_info = { + "qualType": q_val, + "typeName": QUAL_TYPES.get(q_val, "CHEMICAL"), + "chemName": chem_name.value.decode("utf-8", errors="ignore").strip() or ("Age" if q_val == 2 else "Quality"), + "chemUnits": chem_units.value.decode("utf-8", errors="ignore").strip() or ("hours" if q_val == 2 else "mg/L"), + "traceNode": node_id_map.get(trace_node.value, "") if trace_node.value > 0 else "", + "status": "success", + } + except Exception as e: + quality_failed = True + quality_error_message = str(e) + + if quality_info and quality_info["qualType"] > 0: + quality_opened = False + try: + api.lib.EN_openQ(ph) + quality_opened = True + api.lib.EN_initQ(ph, 0) + tQ = ctypes.c_long() + tstepQ = ctypes.c_long(1) + + while True: + api.lib.EN_runQ(ph, ctypes.byref(tQ)) + for i in range(1, node_count.value + 1): + nid = node_id_map[i] + qv = ctypes.c_double() + api.lib.EN_getnodevalue(ph, i, EN.QUALITY, ctypes.byref(qv)) + node_series[nid]["quality"].append(round(qv.value, 4)) + + for i in range(1, link_count.value + 1): + lid = link_id_map[i] + lqv = ctypes.c_double() + api.lib.EN_getlinkvalue(ph, i, EN.LINKQUAL, ctypes.byref(lqv)) + link_series[lid]["quality"].append(round(lqv.value, 4)) + + api.lib.EN_nextQ(ph, ctypes.byref(tstepQ)) + if tstepQ.value <= 0: + break + except Exception as e: + quality_failed = True + quality_error_message = str(e) + quality_info["status"] = "failed" + quality_info["errorMessage"] = quality_error_message + finally: + if quality_opened: + try: + api.lib.EN_closeQ(ph) + except Exception: + pass + + # Assign initial values and full timeSeries + num_steps = len(time_steps) + has_valid_quality = ( + quality_info is not None + and quality_info.get("status") == "success" + and not quality_failed + ) + + for nid in nodes: + if not has_valid_quality or len(node_series[nid]["quality"]) != num_steps: + node_series[nid]["quality"] = [] + nodes[nid]["demand"] = node_series[nid]["demand"][0] if node_series[nid]["demand"] else 0.0 + nodes[nid]["head"] = node_series[nid]["head"][0] if node_series[nid]["head"] else 0.0 + nodes[nid]["pressure"] = node_series[nid]["pressure"][0] if node_series[nid]["pressure"] else 0.0 + nodes[nid]["quality"] = node_series[nid]["quality"][0] if has_valid_quality and node_series[nid]["quality"] else None + nodes[nid]["timeSeries"] = node_series[nid] + + for lid in links: + if not has_valid_quality or len(link_series[lid]["quality"]) != num_steps: + link_series[lid]["quality"] = [] + links[lid]["flow"] = link_series[lid]["flow"][0] if link_series[lid]["flow"] else 0.0 + links[lid]["velocity"] = link_series[lid]["velocity"][0] if link_series[lid]["velocity"] else 0.0 + links[lid]["headloss"] = link_series[lid]["headloss"][0] if link_series[lid]["headloss"] else 0.0 + links[lid]["quality"] = link_series[lid]["quality"][0] if has_valid_quality and link_series[lid]["quality"] else None + links[lid]["timeSeries"] = link_series[lid] + + # 5. Compute Pump Energy Metrics (kWh, peak kW, average efficiency, utilization %) + pump_lids = [lid for lid, link in links.items() if link["type"] == EN.PUMP] + pump_energy_map = {} + total_duration_sec = (time_steps[-1] - time_steps[0]) if len(time_steps) > 1 else 0 + + for lid in pump_lids: + link = links[lid] + energies = link_series[lid].get("energy", []) + effics = link_series[lid].get("efficiency", []) + active_effics = [ef for ef in effics if ef > 0] + + # dt-weighted kWh integration (excluding terminal reading which has no following duration) + pump_kwh = 0.0 + pump_active_sec = 0.0 + if len(time_steps) > 1: + for i in range(len(time_steps) - 1): + dt_hours = max(0.0, float(time_steps[i + 1] - time_steps[i])) / 3600.0 + kw = energies[i] if i < len(energies) else 0.0 + pump_kwh += kw * dt_hours + if kw > 0: + pump_active_sec += max(0.0, float(time_steps[i + 1] - time_steps[i])) + elif energies: + pump_kwh = float(energies[0]) + if energies[0] > 0: + pump_active_sec = 3600.0 + + total_kwh = round(pump_kwh, 2) + peak_kw = round(max(energies, default=0.0), 2) + # Time-weighted (hydraulic steps are not uniform: control/tank events + # insert short steps), i.e. kWh over hours actually running. + avg_kw = round(pump_kwh / (pump_active_sec / 3600.0), 2) if pump_active_sec > 0 else 0.0 + avg_effic = round(sum(active_effics) / len(active_effics), 1) if active_effics else 0.0 + utilization = round((pump_active_sec / total_duration_sec) * 100.0, 1) if total_duration_sec > 0 else (100.0 if energies and energies[0] > 0 else 0.0) + + link["energy"] = energies[0] if energies else 0.0 + link["efficiency"] = effics[0] if effics else 0.0 + link["totalKwh"] = total_kwh + link["peakKw"] = peak_kw + link["avgKw"] = avg_kw + link["utilization"] = utilization + + pump_energy_map[lid] = { + "id": lid, + "fromNode": link["fromNode"], + "toNode": link["toNode"], + "totalKwh": total_kwh, + "peakKw": peak_kw, + "avgKw": avg_kw, + "avgEfficiency": avg_effic, + "utilization": utilization, + } + + # System peak kW is the maximum over all time steps of the simultaneous sum of pump kWs + system_peak_kw = 0.0 + if pump_lids and time_steps: + for i in range(len(time_steps)): + step_sum = sum(link_series[lid]["energy"][i] for lid in pump_lids if i < len(link_series[lid]["energy"])) + if step_sum > system_peak_kw: + system_peak_kw = step_sum + + energy_summary = { + "pumps": pump_energy_map, + "totalKwh": round(sum(p["totalKwh"] for p in pump_energy_map.values()), 2), + "peakKw": round(system_peak_kw, 2), + } if pump_energy_map else None + + if ph_opened: + try: + api.lib.EN_close(ph) + ph_opened = False + except Exception: + pass + report_text = "" + if os.path.exists(rpt_path): + try: + with open(rpt_path, "r", encoding="utf-8", errors="ignore") as rf: + report_text = rf.read() + except Exception: + pass + + return { + "engine": { + "runtime": "native", + "version": "2.3.5" + }, + "timeSeconds": time_steps[0] if time_steps else 0, + "timeSteps": time_steps, + "units": { + "flow": f_unit_str, + "pressure": p_unit_str + }, + "nodes": nodes, + "links": links, + "reportText": report_text, + "qualityInfo": quality_info, + "energySummary": energy_summary, + "engineWarnings": engine_warnings, + "reportStart": report_start.value, + "reportStep": report_step.value, + } + except EpanetException as e: + # The report file usually names the root cause (e.g. "Error 227: invalid + # head curve") that the API code alone (e.g. 110) hides. Close first so + # the report is flushed and readable. + if hydraulics_opened: + try: + api.lib.EN_closeH(ph) + except Exception: + pass + hydraulics_opened = False + if ph_opened: + try: + api.lib.EN_close(ph) + except Exception: + pass + ph_opened = False + detail = str(e) + causes = read_report_errors(rpt_path) + if causes: + detail += " | " + " / ".join(causes) + raise HTTPException(status_code=500, detail=detail) + finally: + if hydraulics_opened: + try: + api.lib.EN_closeH(ph) + except Exception: + pass + if ph_opened: + try: + api.lib.EN_close(ph) + except Exception: + pass + if ph_created: + try: + api.lib.EN_deleteproject(ph) + except Exception: + pass \ No newline at end of file diff --git a/backend/requirements-api.txt b/backend/requirements-api.txt new file mode 100644 index 0000000..7b19beb --- /dev/null +++ b/backend/requirements-api.txt @@ -0,0 +1,12 @@ +# API server (also used by deploy/Dockerfile). Keep pins in this one file. +fastapi==0.143.1 +starlette==1.8.0 +python-multipart==0.0.32 +uvicorn==0.54.0 +numpy==2.5.3 +# GIS / raster (GDAL is bundled in these wheels) +pyogrio==0.13.0 +rasterio==1.5.2 +pyproj==3.8.0 +shapely==2.2.0 +scipy==1.18.1 diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..02632a3 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,8 @@ +-r requirements-api.txt + +# blocksys (상수도 블록시스템 자동 분할 도구) +wntr==1.5.0 +networkx==3.6.1 +scikit-learn==1.9.0 +pandas==3.0.5 +pyyaml==6.0.3 \ No newline at end of file diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py new file mode 100644 index 0000000..41d4d07 --- /dev/null +++ b/backend/tests/test_api.py @@ -0,0 +1,155 @@ +"""HTTP-layer tests for backend/main.py (FastAPI TestClient + real EPANET DLL). + +Run from the repo root: .venv/Scripts/python -m pytest backend/tests -q +""" +from pathlib import Path + +import pytest +from fastapi.testclient import TestClient + +from backend import main +from backend.main import app + +REPO = Path(__file__).resolve().parents[2] +PUBLIC = REPO / "public" + + +@pytest.fixture(scope="module") +def client(): + with TestClient(app, raise_server_exceptions=False) as c: + yield c + + +def post(client, name: str, data: bytes): + return client.post("/api/analyze", files={"file": (name, data, "text/plain")}) + + +SIMPLE = b"""[JUNCTIONS] + J1 10 5 + J2 12 3 +[RESERVOIRS] + R1 50 +[PIPES] + P1 R1 J1 1000 200 100 + P2 J1 J2 1000 150 100 +[OPTIONS] + Units LPS +[END] +""" + + +def test_health(client): + r = client.get("/api/health") + assert r.status_code == 200 + assert r.json() == {"status": "ok"} + + +def test_net1_runs(client): + r = post(client, "Net1.inp", (PUBLIC / "Net1.inp").read_bytes()) + assert r.status_code == 200 + body = r.json() + assert len(body["nodes"]) == 11 and len(body["links"]) == 13 + assert body["units"] == {"flow": "GPM", "pressure": "psi"} + assert body["reportStep"] > 0 + assert body["engineWarnings"] == [] + + +def test_wrong_extension_rejected(client): + assert post(client, "a.txt", SIMPLE).status_code == 400 + + +def test_oversized_upload_rejected(client): + r = post(client, "big.inp", b"x" * (main.MAX_UPLOAD_BYTES + 1)) + assert r.status_code == 413 + + +def test_early_size_check_not_bypassed_by_bad_host(client): + # request.url is rebuilt from Host; the early 413 must not depend on it. + big = str(main.MAX_UPLOAD_BYTES + 10 * 1024 * 1024) + for host in ("localhost", "bad host:abc", "evil.example/../x"): + r = client.post("/api/analyze", headers={"content-length": big, "host": host}, content=b"") + assert r.status_code == 413, (host, r.status_code) + + +def test_invalid_inp_returns_json_with_root_cause(client): + # Previously: EN_open failure leaked the .rpt handle, cleanup raised + # PermissionError on Windows, and the client got plain-text 500. + r = post(client, "bad.inp", b"[JUNCTIONS]\n J1 abc\n[END]\n") + assert r.status_code == 500 + assert r.headers["content-type"].startswith("application/json") + detail = r.json()["detail"] + assert "Error 200" in detail + assert "Error 202" in detail # root cause pulled from the report file + + +def test_warning_codes_collected_even_with_messages_no(client): + # J1 sits above the reservoir head -> negative pressure (warning 6). + inp = SIMPLE.replace(b" J1 10 5", b" J1 100 5").replace( + b"[OPTIONS]", b"[REPORT]\n Messages No\n[OPTIONS]" + ) + r = post(client, "neg.inp", inp) + assert r.status_code == 200 + body = r.json() + assert "WARNING" not in body["reportText"] + assert any(w["code"] == 6 for w in body["engineWarnings"]) + + +def test_step_amplification_rejected_fast(client): + inp = SIMPLE.replace( + b"[OPTIONS]", b"[TIMES]\n Duration 10000:00\n Hydraulic Timestep 0:00:01\n[OPTIONS]" + ) + r = post(client, "dos.inp", inp) + assert r.status_code == 413 + assert "Simulation too large" in r.json()["detail"] + + +def test_columnar_format_carries_identical_values(client): + import base64 + from array import array + + data = (PUBLIC / "Net1.inp").read_bytes() # pump + chlorine: every series kind + obj = client.post("/api/analyze", files={"file": ("Net1.inp", data, "text/plain")}).json() + col = client.post("/api/analyze?format=columnar", files={"file": ("Net1.inp", data, "text/plain")}).json() + assert col["format"] == "spipe-columnar-1" + steps = len(obj["timeSteps"]) + for side, keys in (("nodes", ("demand", "head", "pressure", "quality")), ("links", ("flow", "velocity", "headloss", "quality"))): + ids = col[side]["ids"] + assert ids == list(obj[side]) + for key in keys: + flat = array("d", base64.b64decode(col[side]["series"][key])) + for i, element_id in enumerate(ids): + assert list(flat[i * steps:(i + 1) * steps]) == obj[side][element_id]["timeSeries"][key] + pump = col["links"]["pumps"]["9"] + assert pump["energySeries"] == obj["links"]["9"]["timeSeries"]["energy"] + assert pump["totalKwh"] == obj["links"]["9"]["totalKwh"] + + +def test_overlong_utf8_id_is_engine_error(client): + # 12 Korean chars = 36 UTF-8 bytes > EPANET MAXID (31 bytes) -> Error 252. + long_id = "상리배수지유출관로제일번".encode("utf-8") + inp = SIMPLE.replace(b" J2 12 3", b" " + long_id + b" 12 3").replace(b"J1 J2", b"J1 " + long_id) + r = post(client, "longid.inp", inp) + assert r.status_code == 500 + assert "252" in r.json()["detail"] + + +NET1 = (PUBLIC / "Net1.inp").read_bytes() + + +def post_job(client, name, inp=SIMPLE, params="{}"): + return client.post(f"/api/jobs/{name}", files={"file": ("n.inp", inp, "text/plain")}, data={"params": params}) + + +def test_job_summary_runs(client): + r = post_job(client, "summary", NET1) + assert r.status_code == 200, r.text + body = r.json() + assert body["junctions"] == 9 + assert len(body["times"]) == len(body["minPressure"]) == 25 + assert all(lo <= hi for lo, hi in zip(body["minPressure"], body["maxPressure"])) + + +def test_job_unknown_and_bad_params(client): + assert post_job(client, "nope").status_code == 400 + assert post_job(client, "summary", params="[1]").status_code == 400 + assert post_job(client, "summary", params="not json").status_code == 400 diff --git a/backend/tests/test_batch.py b/backend/tests/test_batch.py new file mode 100644 index 0000000..06c6db0 --- /dev/null +++ b/backend/tests/test_batch.py @@ -0,0 +1,87 @@ +"""backend/batch.py against the real EPANET DLL.""" +from pathlib import Path + +import numpy as np + +from backend.batch import Network, parse_time +from backend.epanet import EN + +NET1 = (Path(__file__).resolve().parents[2] / "public" / "Net1.inp").read_bytes() + + +def test_open_lookup_and_baseline(): + with Network(NET1) as net: + assert net.node_count == 11 and net.link_count == 13 + assert net.node_ids[net.node_index("22") - 1] == "22" + res = net.simulate(node_props=(EN.PRESSURE,), link_props=(EN.FLOW,)) + assert len(res.times) == 25 and res.times[0] == 0 and res.times[-1] == 24 * 3600 + assert res.nodes[EN.PRESSURE].shape == (25, 11) + assert res.links[EN.FLOW].shape == (25, 13) + + +def test_windowed_extra_demand_only_acts_in_window(): + with Network(NET1) as net: + j = net.node_index("22") + base = net.simulate(nodes=[j]).nodes[EN.PRESSURE][:, 0] + net.window_pattern("FIREWIN", 6 * 3600, 9 * 3600) + cat = net.add_demand(j, 500.0, "FIREWIN", "fire") + fire = net.simulate(nodes=[j]).nodes[EN.PRESSURE][:, 0] + hours = np.arange(25) + inside = (hours >= 6) & (hours < 9) + assert np.all(fire[inside] < base[inside] - 1.0) + # Tank levels carry the draw-down a little past the window; before it, nothing changes. + assert np.allclose(fire[hours < 6], base[hours < 6], atol=1e-6) + net.delete_demand(j, cat) + again = net.simulate(nodes=[j]).nodes[EN.PRESSURE][:, 0] + assert np.allclose(again, base, atol=1e-6) + + +def test_timer_control_closes_pipe_in_window(): + with Network(NET1) as net: + p = net.link_index("12") + c1 = net.add_timer_control(p, 0, 3 * 3600) + c2 = net.add_timer_control(p, 1, 5 * 3600) + flow = net.simulate(node_props=(), link_props=(EN.FLOW,), links=[p]).links[EN.FLOW][:, 0] + assert abs(flow[3]) < 1e-6 and abs(flow[4]) < 1e-6 + assert abs(flow[2]) > 1e-3 and abs(flow[6]) > 1e-3 + net.delete_control(c2) + net.delete_control(c1) + + +def test_parse_time(): + assert parse_time("6:30") == 6 * 3600 + 1800 + assert parse_time(2.5) == 9000 + assert parse_time(None, 7) == 7 + + +def test_fireflow_job_and_available_flow(): + from backend.jobs import job_fireflow + + r = job_fireflow(NET1, {"nodes": ["22"], "flow": 500, "start": 6, "end": 9, "residual": 20, "threshold": 50}) + assert r["windowLabel"] == "6:00~9:00" + h = r["hydrants"][0] + assert h["firePressure"] < h["basePressure"] - 1 + assert r["nodes"][0]["diff"] <= r["nodes"][-1]["diff"] # most affected first + # available flow keeps the hydrant at (just above) the residual pressure + q = h["available"] + assert q > 0 + with Network(NET1) as net: + j = net.node_index("22") + net.window_pattern("W", 6 * 3600, 9 * 3600) + c = net.add_demand(j, q, "W", "x") + p = net.simulate(nodes=[j]).nodes[EN.PRESSURE][6:9, 0].min() + assert p >= 20 - 1e-6 + net.delete_demand(j, c) + net.add_demand(j, q * 1.02, "W", "x") + assert net.simulate(nodes=[j]).nodes[EN.PRESSURE][6:9, 0].min() < 20 + 0.5 + + +def test_fireflow_rejects_bad_input(): + import pytest + from backend.batch import BatchError + from backend.jobs import job_fireflow + + with pytest.raises(BatchError): + job_fireflow(NET1, {"nodes": [], "flow": 10}) + with pytest.raises(BatchError): + job_fireflow(NET1, {"nodes": ["NOPE"], "flow": 10}) diff --git a/backend/tests/test_calibrate.py b/backend/tests/test_calibrate.py new file mode 100644 index 0000000..846df10 --- /dev/null +++ b/backend/tests/test_calibrate.py @@ -0,0 +1,70 @@ +"""Automatic calibration (roughness / wall coefficient) and pump scheduling jobs, on Net1 with synthetic truth.""" +import re +from pathlib import Path + +import pytest + +from backend.batch import BatchError, Network +from backend.epanet import EN +from backend.jobs import job_calibrate, job_pumpschedule + +NET1 = (Path(__file__).resolve().parents[2] / "public" / "Net1.inp").read_text() +PIPES = [l.split()[0] for l in re.search(r"\[PIPES\](.*?)\[", NET1, re.S).group(1).splitlines() if l.strip() and not l.strip().startswith(";")] + + +def synthetic(factor=None, wall=None, prop=EN.PRESSURE, locs=("22", "31", "12", "23"), groups=None): + with Network(NET1.encode()) as n: + idx = [n.node_index(l) for l in locs] + for pid in PIPES: + li = n.link_index(pid) + f = factor(pid) if callable(factor) else factor + if f: + n.set_link(li, EN.ROUGHNESS, n.get_link(li, EN.ROUGHNESS) * f) + if wall is not None: + n.set_link(li, EN.KWALL, wall) + if prop == EN.QUALITY: + n.lib.EN_solveH(n.ph) + times, mat = n.run_quality(nodes=idx) + else: + r = n.simulate(node_props=(prop,), nodes=idx) + times, mat = r.times, r.nodes[prop] + return [{"location": l, "time": t, "value": float(mat[k, c])} for k, t in enumerate(times) if k % 2 == 0 for c, l in enumerate(locs)] + + +def test_roughness_recovers_two_group_truth(): + big = {"10", "11", "12", "111", "110"} + obs = synthetic(factor=lambda p: 0.75 if p in big else 1.1, locs=("10", "11", "12", "13", "21", "22", "23", "31", "32")) + groups = {"main": sorted(big), "rest": [p for p in PIPES if p not in big]} + r = job_calibrate(NET1.encode(), {"mode": "roughness", "param": "pressure", "observations": obs, "groups": groups, "bounds": [0.5, 1.5]}) + got = {g["name"]: g["value"] for g in r["groups"]} + assert got["main"] == pytest.approx(0.75, abs=0.02) and got["rest"] == pytest.approx(1.1, abs=0.05) + assert r["rmseAfter"] < 0.05 * r["rmseBefore"] + + +def test_wall_coefficient_from_chlorine(): + obs = synthetic(wall=-0.4, prop=EN.QUALITY) + r = job_calibrate(NET1.encode(), {"mode": "wall", "param": "quality", "observations": obs, "groups": {"all": PIPES}, "bounds": [-3, 0]}) + assert r["groups"][0]["value"] == pytest.approx(-0.4, abs=0.01) and r["groups"][0]["meanBefore"] == -1.0 + + +def test_calibrate_input_errors(): + with pytest.raises(BatchError): + job_calibrate(NET1.encode(), {"mode": "wall", "param": "pressure", "observations": synthetic(), "groups": {"a": PIPES}}) + with pytest.raises(BatchError): + job_calibrate(NET1.encode(), {"observations": [], "groups": {"a": PIPES}}) + with pytest.raises(BatchError): + job_calibrate(NET1.encode(), {"observations": synthetic(), "groups": {}}) + + +def test_pump_schedule_keeps_constraints_and_reports_controls(): + priced = NET1.replace("[PATTERNS]", "[PATTERNS]\n PRICE 0.5 0.5 0.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 0.5") + priced = re.sub(r"Global Price\s+0\.0", "Global Price 0.1\n Global Pattern PRICE", priced) + r = job_pumpschedule(priced.encode(), {"minPressure": 20}) + assert r["pumps"] == ["9"] and r["removedControls"] == 2 and r["slotSeconds"] == 7200 + assert len(r["schedules"]["9"]) == 12 and r["feasible"] + assert r["after"]["violation"] == 0 and r["after"]["minPressure"] >= 20 + assert r["after"]["tankEnd"][0] >= 120 - 0.02 * 50 - 1e-6 + # never worse than the feasible starting schedule + assert r["after"]["cost"] <= r["start"]["cost"] + 1e-9 + # Net1's own controls leave the tank ~4.6 ft lower after a day: reported, not hidden. + assert r["before"]["violation"] > 0 diff --git a/backend/tests/test_dem.py b/backend/tests/test_dem.py new file mode 100644 index 0000000..e056e12 --- /dev/null +++ b/backend/tests/test_dem.py @@ -0,0 +1,131 @@ +"""DEM store: import (GeoTIFF, zipped ASC), bilinear sampling across CRSs, preview, TIN from points, HTTP.""" +import io +import zipfile + +import numpy as np +import pytest +import rasterio +from fastapi.testclient import TestClient +from pyproj import CRS, Transformer +from rasterio.transform import from_origin + +from backend import dem as dem_store +from backend.batch import BatchError +from backend.main import app + +EPSG = "EPSG:5186" +X0, Y0, CELL, W, H = 200_000.0, 550_000.0, 10.0, 60, 40 # top-left corner + + +def plane(x, y): + return 100 + 0.01 * (x - X0) - 0.02 * (Y0 - y) + + +@pytest.fixture(autouse=True) +def store(tmp_path, monkeypatch): + monkeypatch.setattr(dem_store, "DEM_DIR", tmp_path / "dem") + + +def make_tif(path): + xs = X0 + (np.arange(W) + 0.5) * CELL + ys = Y0 - (np.arange(H) + 0.5) * CELL + z = plane(*np.meshgrid(xs, ys)).astype("float32") + z[0, 0] = -9999 + with rasterio.open(path, "w", driver="GTiff", width=W, height=H, count=1, dtype="float32", crs=EPSG, + transform=from_origin(X0, Y0, CELL, CELL), nodata=-9999) as dst: + dst.write(z, 1) + + +def test_import_sample_is_bilinear_and_handles_outside(tmp_path): + src = tmp_path / "up.bin" + make_tif(tmp_path / "a.tif") + src.write_bytes((tmp_path / "a.tif").read_bytes()) + info = dem_store.import_dem(str(src), "a.tif") + assert info["width"] == W and info["resolution"] == [CELL, CELL] and len(info["corners"]) == 4 + pts = [[X0 + 123.4, Y0 - 77.7], [X0 + 300, Y0 - 200], [X0 - 50, Y0 - 50]] + vals = dem_store.sample(info["id"], pts, EPSG) + assert vals[0] == pytest.approx(plane(*pts[0]), abs=1e-3) + assert vals[1] == pytest.approx(plane(*pts[1]), abs=1e-3) + assert vals[2] is None + # Same point given in WGS84 lands on the same value. + lon, lat = Transformer.from_crs(EPSG, "EPSG:4326", always_xy=True).transform(*pts[0]) + assert dem_store.sample(info["id"], [[lon, lat]], "EPSG:4326")[0] == pytest.approx(vals[0], abs=1e-3) + assert dem_store.preview_png(info["id"])[:8] == b"\x89PNG\r\n\x1a\n" + assert dem_store.dem_info(info["id"])["id"] == info["id"] + + +def test_zipped_asc_with_prj(tmp_path): + xs = X0 + (np.arange(W) + 0.5) * CELL + ys = Y0 - (np.arange(H) + 0.5) * CELL + z = plane(*np.meshgrid(xs, ys)) + asc = f"ncols {W}\nnrows {H}\nxllcorner {X0}\nyllcorner {Y0 - H * CELL}\ncellsize {CELL}\nNODATA_value -9999\n" + asc += "\n".join(" ".join(f"{v:.4f}" for v in row) for row in z) + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w") as zf: + zf.writestr("dem/ground.asc", asc) + zf.writestr("dem/ground.prj", CRS.from_user_input(EPSG).to_wkt("WKT1_ESRI")) + up = tmp_path / "up.zip" + up.write_bytes(buf.getvalue()) + info = dem_store.import_dem(str(up), "ground.zip") + p = [X0 + 250, Y0 - 150] + assert dem_store.sample(info["id"], [p], EPSG)[0] == pytest.approx(plane(*p), abs=1e-3) + + +def test_errors(tmp_path): + with pytest.raises(BatchError): + dem_store.dem_info("0" * 32) + with pytest.raises(BatchError): + dem_store.dem_info("../../etc") + bad = tmp_path / "x.bin" + bad.write_bytes(b"not a raster") + with pytest.raises(BatchError): + dem_store.import_dem(str(bad), "x.tif") + with pytest.raises(BatchError): + dem_store.import_dem(str(bad), "x.docx") + + +def test_tin_from_points_reproduces_a_plane(): + rng = np.random.default_rng(1) + xy = np.column_stack([X0 + rng.uniform(0, 500, 300), Y0 - rng.uniform(0, 300, 300)]) + pts = [[x, y, plane(x, y)] for x, y in xy] + pts += [[X0, Y0, plane(X0, Y0)], [X0 + 500, Y0, plane(X0 + 500, Y0)], [X0, Y0 - 300, plane(X0, Y0 - 300)], [X0 + 500, Y0 - 300, plane(X0 + 500, Y0 - 300)]] + info = dem_store.from_points(pts, EPSG, 5.0) + q = [X0 + 211, Y0 - 133] + assert dem_store.sample(info["id"], [q], EPSG)[0] == pytest.approx(plane(*q), abs=0.05) + with pytest.raises(BatchError): + dem_store.from_points([[0, 0, 1], [1, 1, 2], [2, 2, 3]], EPSG, 1.0) # collinear + + +def test_http_roundtrip(tmp_path): + make_tif(tmp_path / "a.tif") + with TestClient(app) as c: + r = c.post("/api/dem", files={"file": ("a.tif", (tmp_path / "a.tif").read_bytes(), "image/tiff")}) + assert r.status_code == 200, r.text + dem_id = r.json()["id"] + assert c.get(f"/api/dem/{dem_id}/preview.png").headers["content-type"] == "image/png" + s = c.post(f"/api/dem/{dem_id}/sample", json={"points": [[X0 + 100, Y0 - 100]], "crs": EPSG}) + assert s.json()["values"][0] == pytest.approx(plane(X0 + 100, Y0 - 100), abs=1e-3) + assert c.get("/api/dem/" + "f" * 32).status_code == 400 + assert c.post("/api/dem", files={"file": ("a.txt", b"x", "text/plain")}).status_code == 400 + + +def test_terrain_tile_terrarium(tmp_path): + make_tif(tmp_path / "a.tif") + up = tmp_path / "up.bin" + up.write_bytes((tmp_path / "a.tif").read_bytes()) + info = dem_store.import_dem(str(up), "a.tif") + import math + lon, lat = Transformer.from_crs(EPSG, "EPSG:4326", always_xy=True).transform(X0 + 300, Y0 - 200) + z = 15 + n = 2 ** z + fx = (lon + 180) / 360 * n + fy = (1 - math.asinh(math.tan(math.radians(lat))) / math.pi) / 2 * n + xt, yt = int(fx), int(fy) + png = dem_store.terrain_tile(info["id"], z, xt, yt) + with rasterio.MemoryFile(png) as mem, mem.open() as ds: + rgb = ds.read().astype(float) + elev = rgb[0] * 256 + rgb[1] + rgb[2] / 256 - 32768 + px, py = int((fx - xt) * 256), int((fy - yt) * 256) + assert elev[py, px] == pytest.approx(plane(X0 + 300, Y0 - 200), abs=0.2) + with pytest.raises(BatchError): + dem_store.terrain_tile(info["id"], 3, 99, 0) diff --git a/backend/tests/test_gisio.py b/backend/tests/test_gisio.py new file mode 100644 index 0000000..abbe9ee --- /dev/null +++ b/backend/tests/test_gisio.py @@ -0,0 +1,69 @@ +"""Vector GIS import (/api/gis/vector) with real GDAL files written on the fly.""" +import io +import os +import tempfile +import zipfile + +import numpy as np +import pytest +import shapely +from fastapi.testclient import TestClient +from pyogrio import raw as ogr_raw + +from backend.main import app + + +@pytest.fixture(scope="module") +def client(): + with TestClient(app, raise_server_exceptions=False) as c: + yield c + + +def _write(path, driver, crs="EPSG:5187"): + geoms = [shapely.LineString([(200000, 300000), (200100, 300050)]), shapely.LineString([(200100, 300050), (200200, 300000)])] + with_fields = driver != "DXF" # DXF has a fixed schema (Layer, EntityHandle ...) + ogr_raw.write( + path, + geometry=np.array(shapely.to_wkb(geoms), dtype=object), + field_data=[np.array([150, 200], dtype=np.int32), np.array(["DCIP", "PE"], dtype=object)] if with_fields else [], + fields=["DIA", "MAT"] if with_fields else [], + geometry_type="LineString", + crs=crs, + driver=driver, + ) + + +def test_zipped_shapefile_reprojected_to_wgs84(client): + with tempfile.TemporaryDirectory() as tmp: + shp = os.path.join(tmp, "pipes.shp") + _write(shp, "ESRI Shapefile") + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w") as z: + for f in os.listdir(tmp): + z.write(os.path.join(tmp, f), f) + r = client.post("/api/gis/vector", files={"file": ("pipes.zip", buf.getvalue(), "application/zip")}) + assert r.status_code == 200, r.text + body = r.json() + assert body["crs"] == "EPSG:5187" and body["count"] == 2 + feat = body["featureCollection"]["features"][0] + assert feat["properties"] == {"DIA": 150, "MAT": "DCIP"} + lon, lat = feat["geometry"]["coordinates"][0] + assert 127 < lon < 132 and 33 < lat < 39 # East Belt -> somewhere in Korea + + +def test_dxf_without_crs_needs_src_epsg(client): + with tempfile.TemporaryDirectory() as tmp: + dxf = os.path.join(tmp, "pipes.dxf") + _write(dxf, "DXF", crs=None) + data = open(dxf, "rb").read() + raw = client.post("/api/gis/vector", files={"file": ("pipes.dxf", data, "application/dxf")}).json() + assert raw["reprojected"] is False and raw["count"] >= 2 + fixed = client.post("/api/gis/vector", files={"file": ("pipes.dxf", data, "application/dxf")}, data={"srcEpsg": "5187"}).json() + lon, lat = fixed["featureCollection"]["features"][0]["geometry"]["coordinates"][0] + assert 127 < lon < 132 and 33 < lat < 39 + + +def test_rejects_unknown_extension_and_bad_epsg(client): + assert client.post("/api/gis/vector", files={"file": ("a.exe", b"MZ", "application/octet-stream")}).status_code == 400 + assert client.post("/api/gis/vector", files={"file": ("a.dxf", b"0", "x")}, data={"srcEpsg": "abc"}).status_code == 400 + assert client.post("/api/gis/vector", files={"file": ("broken.dxf", b"not a dxf", "x")}).status_code == 400 diff --git a/backend/tests/test_gisops.py b/backend/tests/test_gisops.py new file mode 100644 index 0000000..ecf2083 --- /dev/null +++ b/backend/tests/test_gisops.py @@ -0,0 +1,111 @@ +"""GIS layer export / reprojection / geometry operations (/api/gis/export, /op, /reproject).""" +import io +import os +import tempfile +import zipfile + +import numpy as np +import pyogrio +import pytest +import shapely +from fastapi.testclient import TestClient +from pyproj import Transformer + +from backend import gisops +from backend.batch import BatchError +from backend.main import app + +EPSG = "EPSG:5186" +TO_WGS = Transformer.from_crs(EPSG, "EPSG:4326", always_xy=True) + + +def wgs(x, y): + return list(TO_WGS.transform(x, y)) + + +def fc(features): + return {"type": "FeatureCollection", "features": features} + + +SQUARE = fc([{"type": "Feature", "properties": {"name": "블록A", "dia": 150}, "geometry": {"type": "Polygon", "coordinates": [[wgs(200000, 500000), wgs(200100, 500000), wgs(200100, 500100), wgs(200000, 500100), wgs(200000, 500000)]]}}]) +LINES = fc([ + {"type": "Feature", "properties": {"id": "P1", "dia": 150}, "geometry": {"type": "LineString", "coordinates": [wgs(200000, 500000), wgs(200200, 500000)]}}, + {"type": "Feature", "properties": {"id": "P2", "dia": None}, "geometry": {"type": "LineString", "coordinates": [wgs(200200, 500000), wgs(200200, 500200)]}}, +]) + + +@pytest.fixture(scope="module") +def client(): + with TestClient(app, raise_server_exceptions=False) as c: + yield c + + +def _area_m2(feature_collection): + from shapely.geometry import shape + g = gisops._project(np.array([shape(f["geometry"]) for f in feature_collection["features"]], dtype=object), gisops.WGS84, gisops.CRS.from_user_input(EPSG)) + return float(sum(x.area for x in g)) + + +def test_export_shapefile_in_target_crs_with_korean_fields(): + data, name, media = gisops.write_vector(LINES, EPSG, "shp", "관로") + assert name == "관로.zip" and media == "application/zip" + with tempfile.TemporaryDirectory() as tmp: + zipfile.ZipFile(io.BytesIO(data)).extractall(tmp) + shp = [f for f in os.listdir(tmp) if f.endswith(".shp")][0] + df_info = pyogrio.read_info(os.path.join(tmp, shp)) + assert "5186" in df_info["crs"] and df_info["features"] == 2 + meta, _, geom, fields = pyogrio.raw.read(os.path.join(tmp, shp)) + x0 = shapely.from_wkb(geom[0]).coords[0][0] + assert x0 == pytest.approx(200000, abs=0.01) + assert list(meta["fields"]) == ["id", "dia"] + + +def test_export_gpkg_dxf_and_mixed_shapefile_refused(): + gpkg, name, _ = gisops.write_vector(SQUARE, EPSG, "gpkg", "blocks") + assert name == "blocks.gpkg" and gpkg[:15] == b"SQLite format 3" + dxf, _, _ = gisops.write_vector(LINES, EPSG, "dxf", "lines") + assert b"SECTION" in dxf + mixed = fc(SQUARE["features"] + LINES["features"]) + with pytest.raises(BatchError): + gisops.write_vector(mixed, EPSG, "shp", "x") + + +def test_reproject_fixes_a_layer_read_in_the_wrong_crs(): + # Numbers really in EPSG:5186 but read as EPSG:5187: reassign -> lands on the 5186 position. + wrong = Transformer.from_crs("EPSG:5187", "EPSG:4326", always_xy=True) + bad = fc([{"type": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": list(wrong.transform(200000, 500000))}}]) + fixed = gisops.reproject_fc(bad, "EPSG:5187", EPSG) + assert fixed["features"][0]["geometry"]["coordinates"] == pytest.approx(wgs(200000, 500000), abs=1e-8) + + +def test_buffer_clip_difference_dissolve(): + buf = gisops.vector_op("buffer", {"crs": EPSG, "layer": LINES, "distance": 10}) + assert len(buf["features"]) == 2 and buf["features"][0]["properties"]["id"] == "P1" + # P1 buffered: 200 x 20 rectangle + two half-discs of r=10 + from shapely.geometry import shape + a = gisops._project(np.array([shape(buf["features"][0]["geometry"])], dtype=object), gisops.WGS84, gisops.CRS.from_user_input(EPSG))[0].area + assert a == pytest.approx(200 * 20 + np.pi * 100, rel=0.01) + clipped = gisops.vector_op("clip", {"crs": EPSG, "layer": LINES, "other": SQUARE}) + assert len(clipped["features"]) == 1 # only P1 crosses the square + diff = gisops.vector_op("difference", {"crs": EPSG, "layer": SQUARE, "other": buf}) + assert _area_m2(diff) < 100 * 100 + dis = gisops.vector_op("dissolve", {"crs": EPSG, "layer": buf, "field": None}) + assert len(dis["features"]) == 1 and dis["features"][0]["properties"]["count"] == 2 + + +def test_voronoi_and_grid(): + pts = [[200000, 500000], [200100, 500000], [200000, 500100], [200100, 500100], [200050, 500050]] + v = gisops.vector_op("voronoi", {"crs": EPSG, "points": pts, "ids": ["A", "B", "C", "D", "E"]}) + assert sorted(f["properties"]["node"] for f in v["features"]) == ["A", "B", "C", "D", "E"] + g = gisops.vector_op("grid", {"crs": EPSG, "bounds": [200000, 500000, 200100, 500050], "cell": 25}) + assert len(g["features"]) == 8 + with pytest.raises(BatchError): + gisops.vector_op("grid", {"crs": "EPSG:4326", "bounds": [0, 0, 1, 1], "cell": 1}) + + +def test_http(client): + r = client.post("/api/gis/export", json={"featureCollection": LINES, "crs": EPSG, "format": "geojson", "name": "관로"}) + assert r.status_code == 200 and "attachment" in r.headers["content-disposition"] + r = client.post("/api/gis/op", json={"op": "buffer", "crs": EPSG, "layer": LINES, "distance": 5}) + assert r.status_code == 200 and len(r.json()["features"]) == 2 + assert client.post("/api/gis/op", json={"op": "nope", "crs": EPSG, "layer": LINES}).status_code == 400 diff --git a/backend/tests/test_hydro.py b/backend/tests/test_hydro.py new file mode 100644 index 0000000..bcd44ad --- /dev/null +++ b/backend/tests/test_hydro.py @@ -0,0 +1,59 @@ +"""DEM hydrology: streams follow a synthetic valley; the outlet's catchment is the whole valley.""" +import numpy as np +import pytest +import rasterio +from pyproj import Transformer +from rasterio.transform import from_origin +from shapely.geometry import shape + +from backend import dem as dem_store + +EPSG = "EPSG:5186" +X0, Y1, CELL, W, H = 200_000.0, 550_000.0, 10.0, 81, 120 # valley axis at column 40 + + +@pytest.fixture(autouse=True) +def store(tmp_path, monkeypatch): + monkeypatch.setattr(dem_store, "DEM_DIR", tmp_path / "dem") + + +def valley(tmp_path): + xs = X0 + (np.arange(W) + 0.5) * CELL + ys = Y1 - (np.arange(H) + 0.5) * CELL + xx, yy = np.meshgrid(xs, ys) + axis = X0 + 40.5 * CELL + z = 100 + 0.05 * np.abs(xx - axis) + 0.01 * (yy - (Y1 - H * CELL)) # V valley draining south + z[30, 40] -= 3 # a pit on the axis must not stop the flow + p = tmp_path / "v.tif" + with rasterio.open(p, "w", driver="GTiff", width=W, height=H, count=1, dtype="float32", crs=EPSG, + transform=from_origin(X0, Y1, CELL, CELL)) as d: + d.write(z.astype("float32"), 1) + up = tmp_path / "up.bin" + up.write_bytes(p.read_bytes()) + return dem_store.import_dem(str(up), "v.tif") + + +def test_streams_follow_the_axis_through_a_pit(tmp_path): + info = valley(tmp_path) + fc = dem_store.streams(info["id"], 0.05) # 0.05 km2 = 500 cells + assert fc["features"] + to_xy = Transformer.from_crs("EPSG:4326", EPSG, always_xy=True) + axis = X0 + 40.5 * CELL + longest = max(fc["features"], key=lambda f: f["properties"]["length_m"]) + xs = [to_xy.transform(*c)[0] for c in longest["geometry"]["coordinates"]] + assert max(abs(x - axis) for x in xs) < CELL * 1.5 + assert longest["properties"]["length_m"] > 0.5 * H * CELL + assert longest["properties"]["area_km2"] == pytest.approx(W * H * CELL * CELL / 1e6, rel=0.1) + + +def test_watershed_of_the_outlet_is_the_whole_valley(tmp_path): + info = valley(tmp_path) + outlet = [X0 + 40.5 * CELL, Y1 - (H - 1) * CELL] + fc = dem_store.watershed(info["id"], [outlet], EPSG, snap_m=30) + (f,) = fc["features"] + assert f["properties"]["area_km2"] == pytest.approx(W * H * CELL * CELL / 1e6, rel=0.05) + assert shape(f["geometry"]).is_valid + # A point halfway up drains only the upper part. + mid = [X0 + 40.5 * CELL, Y1 - (H / 2) * CELL] + half = dem_store.watershed(info["id"], [mid], EPSG, snap_m=30)["features"][0]["properties"]["area_km2"] + assert 0.3 * f["properties"]["area_km2"] < half < 0.7 * f["properties"]["area_km2"] diff --git a/backend/tests/test_jobs_analysis.py b/backend/tests/test_jobs_analysis.py new file mode 100644 index 0000000..dfac125 --- /dev/null +++ b/backend/tests/test_jobs_analysis.py @@ -0,0 +1,60 @@ +"""Criticality, service area (trace), VSP and energy jobs against the real DLL.""" +import re +from pathlib import Path + +import pytest + +from backend.batch import BatchError +from backend.jobs import job_criticality, job_energy, job_servicearea, job_vsp + +PUBLIC = Path(__file__).resolve().parents[2] / "public" +NET1 = (PUBLIC / "Net1.inp").read_bytes() +NET3 = (PUBLIC / "Net3.inp").read_bytes() + + +def test_criticality_ranks_the_supply_main_first(): + r = job_criticality(NET1, {"threshold": 40}) + assert len(r["links"]) == 12 # pipes only, the pump is skipped + top = r["links"][0] + # Pipe 10 is the only path from the source: closing it cuts off every junction. + assert top["id"] == "10" and top["affectedNodes"] == 8 and top["disconnected"] + loops = [e for e in r["links"] if e["id"] in ("111", "112", "121", "122")] + assert all(e["affectedNodes"] == 0 for e in loops) + + +def test_criticality_subset_and_limits(): + r = job_criticality(NET1, {"threshold": 40, "links": ["111"]}) + assert [e["id"] for e in r["links"]] == ["111"] + with pytest.raises(BatchError): + job_criticality(NET1, {"links": ["nope"]}) + + +def test_service_area_shares_sum_to_at_most_100(): + r = job_servicearea(NET3, {}) + assert r["sources"] == ["River", "Lake"] + assert r["unreached"] == 0 and r["notes"] # 24 h run extended to 72 h + assert max(sum(v["shares"].values()) for v in r["nodes"].values()) <= 100.5 + assert r["nodes"]["River"]["source"] == "River" and r["nodes"]["Lake"]["source"] == "Lake" + + +def test_vsp_hits_the_target_or_reports_the_bound(): + r = job_vsp(NET1, {"pump": "9", "node": "31", "target": 125, "hours": [0, 18], "maxSpeed": 1.5}) + by_hour = {row["hour"]: row for row in r["rows"]} + assert by_hour[0]["status"] == "max" and by_hour[0]["pressure"] < 125 + assert by_hour[18]["status"] == "ok" and abs(by_hour[18]["pressure"] - 125) < 0.05 + assert 1.0 < by_hour[18]["speed"] < 1.5 + with pytest.raises(BatchError): + job_vsp(NET1, {"pump": "10", "node": "31", "target": 100}) # a pipe + + +def test_energy_report_matches_epanet_energy_section(): + r = job_energy(NET1, {}) + (p,) = r["pumps"] + # EPANET's own Net1 energy report: 57.71 % utilisation, 75 % efficiency, ~96 kW average. + assert p["utilization"] == pytest.approx(57.71, abs=0.05) + assert p["efficiency"] == pytest.approx(75.0) + assert p["avgKw"] == pytest.approx(96.25, abs=0.5) + assert p["kwhPerM3"] > 0 and r["totalCost"] == 0.0 # Net1 has no price + priced = re.sub(rb"Global Price\s+0\.0", b"Global Price 0.1", NET1) + assert priced != NET1 + assert job_energy(priced, {})["pumps"][0]["cost"] == pytest.approx(p["kwh"] * 0.1, rel=1e-3) diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..bfa23b6 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,24 @@ +name: spipe +services: + api: + build: + context: . + dockerfile: deploy/Dockerfile + target: api + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health', timeout=5)"] + interval: 30s + timeout: 10s + retries: 3 + web: + build: + context: . + dockerfile: deploy/Dockerfile + target: web + ports: + - "${SPIPE_PORT:-80}:80" + restart: unless-stopped + depends_on: + api: + condition: service_healthy diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..955f29f --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,30 @@ +FROM node:24-bookworm-slim AS web-build +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY tsconfig.json vite.config.ts index.html ./ +COPY src ./src +COPY public ./public +RUN npm run build + +FROM python:3.12-slim-bookworm AS engine-build +RUN apt-get update && apt-get install -y --no-install-recommends git cmake build-essential ca-certificates && rm -rf /var/lib/apt/lists/* +RUN git clone --depth 1 --branch v2.3.5 https://github.com/OpenWaterAnalytics/EPANET.git /engine +RUN cmake -S /engine -B /engine/build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF && cmake --build /engine/build --target epanet2 -j 2 + +FROM python:3.12-slim-bookworm AS api +WORKDIR /app +COPY backend/requirements-api.txt /tmp/requirements-api.txt +RUN pip install --no-cache-dir -r /tmp/requirements-api.txt +COPY --from=engine-build /engine/build/lib/libepanet2.so /app/64bit/libepanet2.so +COPY backend/__init__.py backend/main.py backend/epanet.py backend/columnar.py backend/batch.py backend/jobs.py backend/gisio.py backend/gisops.py backend/dem.py ./backend/ +COPY public/Net1.inp /app/smoke.inp +RUN python -c "from backend.main import run_epanet; import tempfile; from pathlib import Path; t=tempfile.TemporaryDirectory(); p=Path(t.name); r=run_epanet('/app/smoke.inp',str(p/'test.rpt'),str(p/'test.bin')); assert r['nodes'] and r['links']" +RUN useradd --system --uid 10001 spipe && mkdir -p /app/data/dem && chown -R spipe /app/data +ENV SPIPE_DEM_DIR=/app/data/dem +USER spipe +CMD ["python", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"] + +FROM nginx:stable-bookworm AS web +COPY --from=web-build /app/dist /usr/share/nginx/html +COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..0eb7515 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,26 @@ +# Ubuntu deployment + +The repository is private. Authenticate on the server using `gh auth login` +(GitHub.com, HTTPS, web browser), then run: + +```sh +gh repo clone tpstn23-afk/SPipe +cd SPipe +COMPOSE_PARALLEL_LIMIT=1 docker compose build +docker compose up -d +curl --fail http://127.0.0.1/api/health +``` + +Docker Engine and Compose must already be installed. Check free disk space +before building; the target server originally had about 4 GB free. +Set `SPIPE_PORT=8080` when port 80 is already occupied. + +The API builds the official EPANET v2.3.5 Linux library and runs a Net1 +analysis while building. Source: https://github.com/OpenWaterAnalytics/EPANET/releases/tag/v2.3.5 +Only the web/API dependencies are installed; blocksys is a separate CLI. +The web and API share one origin through nginx. Only the web port is published. +The configuration serves HTTP; domain/TLS setup is a separate server step. +Map service allowed domains may need to include the deployed address. + +Validation: local Windows regression suite; Docker build and live server +checks still required (Docker is unavailable on the development PC). diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..949f477 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,30 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + # Keep in sync with SPIPE_MAX_UPLOAD_MB (backend); nginx rejects larger + # bodies before they reach the API. + client_max_body_size 21m; + gzip on; + gzip_types application/json text/plain application/javascript text/css; + gzip_min_length 1024; + # DEM rasters (SPIPE_MAX_DEM_MB, default 300). + location /api/dem { + client_max_body_size 301m; + proxy_request_buffering off; + proxy_pass http://api:8000; + proxy_read_timeout 600s; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + location /api/ { + proxy_pass http://api:8000; + proxy_read_timeout 300s; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/docs/SPipe_패치내역_v2_2026-10-30.txt b/docs/SPipe_패치내역_v2_2026-10-30.txt new file mode 100644 index 0000000..f00a613 --- /dev/null +++ b/docs/SPipe_패치내역_v2_2026-10-30.txt @@ -0,0 +1,104 @@ +================================================================ + SPipe 패치 내역 - 데모 v2 (2026-10-30) + 이전 배포: 데모 v1 (2026-10-15) +================================================================ + +※ 새 기능은 대부분 오른쪽 [분석] 탭에 있습니다. +※ 표시가 (시험)인 기능은 결과를 기존 방식과 비교해서 써 주세요. + + +[1] 해석 · 분석 +---------------------------------------------------------------- + + 소화전 모의: 지정 시간대에 소화 유량을 더해 다시 해석하고, + 잔류압력을 지키며 낼 수 있는 최대 소화 유량까지 계산 + + 관로 중요도: 관로를 하나씩 닫아 보고 영향 받는 절점·수요량 순위 + (단절 관로 표시, CSV 저장) + + 급수 구역: 배수지별 공급 비율 추적 (탱크 포함 선택) + + 흐름 방향: 하루 중 흐름이 바뀌는 관로, 흐름 없는 관로 찾기 + + VSP: 제어 절점 목표 압력에 맞는 시간별 펌프 회전수 계산, 패턴으로 적용 + + 등치선: 압력·동수두·표고 등치선을 지도 레이어 또는 GeoJSON으로 + + 에너지 비용: 펌프별 가동률·효율·전력량·원단위(kWh/m³)·전력비 + + 관로 통계: 관경·재질·설치 연대·소블록별 개수, 연장, 최대 유속, 경과년수 + + 관망 그룹, 소블록 고립 확인 + + 자동 보정 (시험): 실측 파일로 관로 그룹별 C값 배율 또는 + 관벽 반응계수를 자동으로 맞춤 + + 펌프 운전 최적화 (시험): 최저압력과 탱크 수위를 지키면서 + 전력비가 낮은 운전 일정 탐색 (최적해 보장 안 함, 참고용) + + +[2] 모델 입력 · 편집 +---------------------------------------------------------------- + + 속성창 "추가 속성": 다중 수요(범주), 이미터(누수), 초기 수질·수원, + 탱크 혼합·반응, 펌프 효율곡선·전력 단가, 초기 상태([STATUS]) + + 수요량 테이블: 전체 절점 수요 한눈에, 엑셀 붙여넣기/CSV 일괄 입력, + 총량 배분(수요 비례·균등), 누수량 이미터 배분 + + 모델 설정: 고급 해석 옵션, 에너지·수질 반응 옵션 + + 곡선 편집: 펌프·효율·체적·손실 곡선 + + 범례 편집: 구간·색 직접 지정, 값에 따라 크기 표시 + + 지도 라벨 추가·편집 (INP [LABELS]로 저장) + + 편집 도구: 이동·회전·축척, 2점 좌표 보정, N등분·간격 분할, + 접속 분할, 절점 제거·병합, 관로 점검·자동 정리, + 상·하류 추적, 두 절점 최단 경로 + + 지도에서 꺾임점 편집: 관로 선택 후 주황 점 끌기(이동), + 흰 점 끌기(추가), 더블클릭(삭제) + + 관로 길이 자동 계산, 새 절점 표고 = 가장 가까운 절점 표고 (편집 옵션) + + 자산 속성(관재질·설치년도·매설깊이·소블록 등) 보관 + + 프로젝트 파일(.spipe): 관망·시나리오·레이어·스타일·보정자료를 한 파일로 + + 자동 백업: 수정 중 10분마다 최근 30개 보관 + (프로젝트 메뉴 > 백업 기록에서 복원, Ctrl+Z로 되돌리기 가능) + + +[3] 지형 (DEM) +---------------------------------------------------------------- + + DEM 파일 불러오기 (GeoTIFF·ASC·IMG, zip 가능, 최대 300MB) + → 국토지리정보원 5m 자료를 받으면 여기에 넣으면 됨 + + 절점 표고 입력: 지반고 또는 관저고(지반고 - 매설깊이) + + 표고 검증: 모델 표고와 DEM 차이가 큰 절점 목록 + + 인터넷 표고 (90m 근사) 일괄 입력: DEM 파일이 없을 때 + + 점 표고로 DEM 만들기 (TIN) + + 유역·하천: 하천선 추출, 선택 절점의 상류 유역 + + +[4] GIS 도구 +---------------------------------------------------------------- + + 불러오기 형식 추가: DXF, DGN, MIF/TAB, GML, KML, GPKG, E00 + (좌표계 없는 파일은 관망 좌표계로 읽음) + + 레이어 좌표계 다시 지정, 원하는 좌표계로 내보내기 + (SHP, GeoPackage, DXF, GeoJSON, KML) + + 도형 연산: 버퍼, 병합, 잘라내기, 빼기 + + 레이어 만들기: 절점 보로노이, 격자, OSM 건물, 엑셀·CSV 좌표 → 점 + + 관망 반영: 점 레이어로 밸브 삽입, 계량기 사용량 배정, + 면 레이어로 소블록 매핑, 자산 속성 엑셀 붙여넣기 + + +[5] 화면 +---------------------------------------------------------------- + + 3D 보기: 지도 기울이기, DEM 지형, 절점 압력·동수두 기둥 + + 이미지 겹치기: 항공사진·도면(PNG/JPG + 월드 파일) + + 결과 그래프: 여러 요소 시계열 비교, 누적 빈도 + + 종단: 평균선, 구간표, 방향 뒤집기 + + 실측 보정 값을 지도에 표시 + + +[6] 버그 수정 +---------------------------------------------------------------- + * 관로 이름 변경 시 꺾임점이 사라지던 문제 + * 관로 분할 시 꺾임점이 한쪽에만 남던 문제 + * 관로 방향 뒤집기 후 선이 지그재그로 그려지던 문제 + * 삭제한 관로의 꺾임점이 남아 같은 ID의 새 관로에 붙던 문제 + * 예제를 열어도 상태줄 파일명이 "Net1.inp"로 남던 문제 + * 로컬 좌표계에서 관로 길이가 잘못 재계산되던 문제 (확인 후 실행) + * 업로드 크기 검사 강화, 서버 라이브러리 보안 업데이트 + + +[7] 서버 설치 · 운영 참고 +---------------------------------------------------------------- + - 업데이트 후 반드시 실행: + .venv\Scripts\python.exe -m pip install -r backend\requirements.txt + (scipy, rasterio 등 새 라이브러리 필요) + - 화면 다시 빌드: start-lan.ps1 -Rebuild + - 올린 DEM은 서버의 data\dem 폴더에 최근 8개만 보관 + - 인터넷 표고(90m), OSM 건물은 사용자 PC에서 인터넷 접속이 필요 + (사내망에서 막히면 이 두 기능만 안 됨) + - 미반영: 탁도 분석(정의 협의 중), .net·DWG 형식(DWG는 DXF로 변환해 사용) +================================================================ diff --git a/index.html b/index.html new file mode 100644 index 0000000..8e2fa1e --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + + spipe + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8c80aea --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2403 @@ +{ + "name": "spipe", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "spipe", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@types/proj4": "^2.5.6", + "epanet-js": "^0.9.0", + "lucide-react": "^1.42.0", + "maplibre-gl": "^4.7.1", + "proj4": "^2.22.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/react": "^18.3.24", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.8.3", + "vite": "^6.4.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "license": "ISC", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.3.tgz", + "integrity": "sha512-0SElaV0uMxEnxzBhhX9WTuPyUeMsAN/SS0i16tjuba4/mio63MG9khjC1a0JAiPGXAwvwm4UfHJURCN7nyudQg==", + "license": "MIT", + "engines": { + "node": ">= 22" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.2.0.tgz", + "integrity": "sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.4.0.tgz", + "integrity": "sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "license": "ISC" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==", + "license": "MIT" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", + "license": "MIT" + }, + "node_modules/@types/proj4": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@types/proj4/-/proj4-2.5.6.tgz", + "integrity": "sha512-zfMrPy9fx+8DchqM0kIUGeu2tTVB5ApO1KGAYcSGFS8GoqRIkyL41xq2yCx/iV3sOLzo7v4hEgViSLTiPI1L0w==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", + "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", + "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/earcut": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.2.3.tgz", + "integrity": "sha512-vnS4AVwp1KHAF13i1vp1/2D5evWy3k5u/iW/B81QVsUZtV8cv2tU0b2VNFlqvh4kYwrFMDdjPCfAmfyJW9y14Q==", + "license": "ISC" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.422", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", + "integrity": "sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==", + "dev": true, + "license": "ISC" + }, + "node_modules/epanet-js": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/epanet-js/-/epanet-js-0.9.0.tgz", + "integrity": "sha512-zb+jE3lzzB/mu97jXdEFagk3NDxlG1KOPDwZzFww+0uRHA8oCpG+Qz/9OaOdyWA8gR4ZtSLt82amlpIP1oIAgA==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/geojson-vt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.3.tgz", + "integrity": "sha512-jR1MwkLaZGa8Zftct9ZFruyWFrdl9ZyD2OliXNy9Qq5bBPeg5wHVpBQF9p5GjnicSDQqvBVpysxTPKmWdsfWMA==", + "license": "ISC" + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/global-prefix": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "license": "MIT", + "dependencies": { + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kdbush": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.1.0.tgz", + "integrity": "sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==", + "license": "ISC" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.42.0.tgz", + "integrity": "sha512-b3jprplnoLS8n5etw1z8xODe3hF/yjKATrTitsrKrnjUhCef5BdDct6Ppv3zVvzFwmtfWgLO6XNM3C9fAD93ug==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/maplibre-gl": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz", + "integrity": "sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^20.3.1", + "@types/geojson": "^7946.0.14", + "@types/geojson-vt": "3.2.5", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/mapbox__vector-tile": "^1.3.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.0", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.3", + "global-prefix": "^4.0.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.3.0", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0", + "vt-pbf": "^3.1.3" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/mgrs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz", + "integrity": "sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA==", + "license": "MIT" + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/pbf": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", + "license": "ISC" + }, + "node_modules/proj4": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.22.0.tgz", + "integrity": "sha512-MVvH09QrYm3A235rcX39WDiCKanrBiblu28l0KYIn/B2DofmYhvP/LLS43kE66jNDfx/P1l/nah3JmsAq8UgbQ==", + "license": "MIT", + "dependencies": { + "mgrs": "1.0.0", + "wkt-parser": "^1.5.5" + }, + "funding": { + "url": "https://github.com/sponsors/ahocevar" + }, + "peerDependencies": { + "geotiff": "*" + }, + "peerDependenciesMeta": { + "geotiff": { + "optional": true + } + } + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", + "license": "MIT" + }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wkt-parser": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.5.6.tgz", + "integrity": "sha512-cqHU3lzGt/gt2OqIORP0uVy5yeOX43WABmgFkmJsmcqH3HhQo9PiJG6ftytwGKmpQUbegeX7+pQc2BuNCRfcrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ahocevar" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..08c25a1 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "spipe", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "test": "node --experimental-strip-types --test tests/regression.test.ts tests/fixes.test.ts", + "test:api": ".\\.venv\\Scripts\\python.exe -m pytest -q", + "typecheck": "tsc --noEmit", + "preview": "vite preview", + "serve:lan": "vite preview", + "api": ".\\.venv\\Scripts\\python.exe -m uvicorn backend.main:app --host 127.0.0.1 --port 8000" + }, + "license": "MIT", + "dependencies": { + "@types/proj4": "^2.5.6", + "epanet-js": "^0.9.0", + "lucide-react": "^1.42.0", + "maplibre-gl": "^4.7.1", + "proj4": "^2.22.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/react": "^18.3.24", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "^5.8.3", + "vite": "^6.4.3" + } +} diff --git a/public/EXAMPLE_NETWORKS.md b/public/EXAMPLE_NETWORKS.md new file mode 100644 index 0000000..4bc7455 --- /dev/null +++ b/public/EXAMPLE_NETWORKS.md @@ -0,0 +1,33 @@ +# 예제 관망 출처 및 라이선스 (Example Network Sources & License) + +이 폴더의 `Net1.inp`, `Net2.inp`, `Net3.inp`는 GISPipe가 자체 제작한 데이터가 아니라, +**Open Water Analytics(OWA)가 관리하는 공식 EPANET 오픈소스 저장소**에 포함된 표준 예제 +관망 파일을 그대로 가져온 것이다. 상용 GISPipe 또는 그 외 상용 관망해석 소프트웨어의 +샘플 데이터는 어떤 형태로도 사용하지 않았다. + +## 출처 (Source) + +- 저장소: `github.com/OpenWaterAnalytics/EPANET` (dev 브랜치, `example-networks/` 폴더) +- Net1.inp: EPANET Example Network 1 - 염소 소독(bulk/wall reaction) 예제, 절점 9개, 관로 13개, 탱크 1개. +- Net2.inp: EPANET Example Network 2 - 55시간 불소(fluoride) 추적 예제, 절점 36개, 관로 40개, 탱크 1개. +- Net3.inp: EPANET Example Network 3 - 하천(River)·호수(Lake) 이중 수원 대규모 예제, 절점 92개, + 관로 117개, 펌프 2개, 탱크 3개. + +## 라이선스 (License) + +Open Water Analytics의 EPANET 저장소는 원본 USEPA(미국 환경보호청)의 EPANET 소프트웨어를 +계승한 오픈소스 프로젝트로, 저장소 전체가 MIT 라이선스로 배포되며 미국 정부 저작물(공유 +저작물, public domain) 성격의 예제 데이터를 포함한다. 이 예제망들은 EPANET 배포판과 사용자 +매뉴얼에도 동일하게 포함되어 실무·교육용으로 자유롭게 재배포·활용이 허용된다. + +- 프로젝트 라이선스: MIT (`github.com/OpenWaterAnalytics/EPANET/blob/dev/LICENSE`) +- 저작권 관련 불확실성이 있는 데이터, 상용 소프트웨어의 비공개 샘플 데이터는 사용하지 않았다. + +## 용도 + +GISPipe의 Native(OWA-EPANET 2.3.5 C DLL)/WASM(epanet-js) 듀얼 엔진 해석 및 INP +파싱·재생성(round-trip) 신뢰성 회귀 테스트(`tests/regression.test.ts`)와 앱 UI의 +"예제 로드" 선택 기능(Net1/Net2/Net3)에 사용된다. Net2/Net3는 각각 확장 EPS 지속시간, +다중 수요 패턴, 펌프 곡선(`[CURVES]`), 규칙 기반이 아닌 시간/수위 조건 제어 +(`[CONTROLS]`)를 포함하고 있어 Net1보다 더 폭넓은 EPANET 구문 커버리지를 회귀 +테스트에 제공한다. diff --git a/public/Net1.inp b/public/Net1.inp new file mode 100644 index 0000000..3389f8d --- /dev/null +++ b/public/Net1.inp @@ -0,0 +1,174 @@ +[TITLE] + EPANET Example Network 1 +A simple example of modeling chlorine decay. Both bulk and +wall reactions are included. + +[JUNCTIONS] +;ID Elev Demand Pattern + 10 710 0 ; + 11 710 150 ; + 12 700 150 ; + 13 695 100 ; + 21 700 150 ; + 22 695 200 ; + 23 690 150 ; + 31 700 100 ; + 32 710 100 ; + +[RESERVOIRS] +;ID Head Pattern + 9 800 ; + +[TANKS] +;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve + 2 850 120 100 150 50.5 0 ; + +[PIPES] +;ID Node1 Node2 Length Diameter Roughness MinorLoss Status + 10 10 11 10530 18 100 0 Open ; + 11 11 12 5280 14 100 0 Open ; + 12 12 13 5280 10 100 0 Open ; + 21 21 22 5280 10 100 0 Open ; + 22 22 23 5280 12 100 0 Open ; + 31 31 32 5280 6 100 0 Open ; + 110 2 12 200 18 100 0 Open ; + 111 11 21 5280 10 100 0 Open ; + 112 12 22 5280 12 100 0 Open ; + 113 13 23 5280 8 100 0 Open ; + 121 21 31 5280 8 100 0 Open ; + 122 22 32 5280 6 100 0 Open ; + +[PUMPS] +;ID Node1 Node2 Parameters + 9 9 10 HEAD 1 ; + +[VALVES] +;ID Node1 Node2 Diameter Type Setting MinorLoss + +[TAGS] + +[DEMANDS] +;Junction Demand Pattern Category + +[STATUS] +;ID Status/Setting + +[PATTERNS] +;ID Multipliers +;Demand Pattern + 1 1.0 1.2 1.4 1.6 1.4 1.2 + 1 1.0 0.8 0.6 0.4 0.6 0.8 + +[CURVES] +;ID X-Value Y-Value +;PUMP: Pump Curve for Pump 9 + 1 1500 250 + +[CONTROLS] + LINK 9 OPEN IF NODE 2 BELOW 110 + LINK 9 CLOSED IF NODE 2 ABOVE 140 + +[RULES] + +[ENERGY] + Global Efficiency 75 + Global Price 0.0 + Demand Charge 0.0 + +[EMITTERS] +;Junction Coefficient + +[QUALITY] +;Node InitQual + 10 0.5 + 11 0.5 + 12 0.5 + 13 0.5 + 21 0.5 + 22 0.5 + 23 0.5 + 31 0.5 + 32 0.5 + 9 1.0 + 2 1.0 + +[SOURCES] +;Node Type Quality Pattern + +[REACTIONS] +;Type Pipe/Tank Coefficient + Order Bulk 1 + Order Tank 1 + Order Wall 1 + Global Bulk -.5 + Global Wall -1 + Limiting Potential 0.0 + Roughness Correlation 0.0 + +[MIXING] +;Tank Model + +[TIMES] + Duration 24:00 + Hydraulic Timestep 1:00 + Quality Timestep 0:05 + Pattern Timestep 2:00 + Pattern Start 0:00 + Report Timestep 1:00 + Report Start 0:00 + Start ClockTime 12 am + Statistic None + +[REPORT] + Status Yes + Summary No + Page 0 + +[OPTIONS] + Units GPM + Headloss H-W + Specific Gravity 1.0 + Viscosity 1.0 + Trials 40 + Accuracy 0.001 + CHECKFREQ 2 + MAXCHECK 10 + DAMPLIMIT 0 + Unbalanced Continue 10 + Pattern 1 + Demand Multiplier 1.0 + Emitter Exponent 0.5 + Quality Chlorine mg/L + Diffusivity 1.0 + Tolerance 0.01 + +[COORDINATES] +;Node X-Coord Y-Coord + 10 20.00 70.00 + 11 30.00 70.00 + 12 50.00 70.00 + 13 70.00 70.00 + 21 30.00 40.00 + 22 50.00 40.00 + 23 70.00 40.00 + 31 30.00 10.00 + 32 50.00 10.00 + 9 10.00 70.00 + 2 50.00 90.00 + +[VERTICES] +;Link X-Coord Y-Coord + +[LABELS] +;X-Coord Y-Coord Label & Anchor Node + 6.99 73.63 "Source" + 13.48 68.13 "Pump" + 43.85 91.21 "Tank" + +[BACKDROP] + DIMENSIONS 7.00 6.00 73.00 94.00 + UNITS None + FILE + OFFSET 0.00 0.00 + +[END] diff --git a/public/Net2.inp b/public/Net2.inp new file mode 100644 index 0000000..47e3959 --- /dev/null +++ b/public/Net2.inp @@ -0,0 +1,308 @@ +[TITLE] +EPANET Example Network 2 +Example of modeling a 55-hour fluoride tracer study. +Measured fluoride data is contained in the file Net2-FL.dat +and should be registered with the project to produce a +Calibration Report (select Calibration Data from the Project +menu). + +[JUNCTIONS] +;ID Elev Demand Pattern + 1 50 -694.4 2 ; + 2 100 8 ; + 3 60 14 ; + 4 60 8 ; + 5 100 8 ; + 6 125 5 ; + 7 160 4 ; + 8 110 9 ; + 9 180 14 ; + 10 130 5 ; + 11 185 34.78 ; + 12 210 16 ; + 13 210 2 ; + 14 200 2 ; + 15 190 2 ; + 16 150 20 ; + 17 180 20 ; + 18 100 20 ; + 19 150 5 ; + 20 170 19 ; + 21 150 16 ; + 22 200 10 ; + 23 230 8 ; + 24 190 11 ; + 25 230 6 ; + 27 130 8 ; + 28 110 0 ; + 29 110 7 ; + 30 130 3 ; + 31 190 17 ; + 32 110 17 ; + 33 180 1.5 ; + 34 190 1.5 ; + 35 110 0 ; + 36 110 1 ; + +[RESERVOIRS] +;ID Head Pattern + +[TANKS] +;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve + 26 235 56.7 50 70 50 0 ; + +[PIPES] +;ID Node1 Node2 Length Diameter Roughness MinorLoss Status + 1 1 2 2400 12 100 0 Open ; + 2 2 5 800 12 100 0 Open ; + 3 2 3 1300 8 100 0 Open ; + 4 3 4 1200 8 100 0 Open ; + 5 4 5 1000 12 100 0 Open ; + 6 5 6 1200 12 100 0 Open ; + 7 6 7 2700 12 100 0 Open ; + 8 7 8 1200 12 140 0 Open ; + 9 7 9 400 12 100 0 Open ; + 10 8 10 1000 8 140 0 Open ; + 11 9 11 700 12 100 0 Open ; + 12 11 12 1900 12 100 0 Open ; + 13 12 13 600 12 100 0 Open ; + 14 13 14 400 12 100 0 Open ; + 15 14 15 300 12 100 0 Open ; + 16 13 16 1500 8 100 0 Open ; + 17 15 17 1500 8 100 0 Open ; + 18 16 17 600 8 100 0 Open ; + 19 17 18 700 12 100 0 Open ; + 20 18 32 350 12 100 0 Open ; + 21 16 19 1400 8 100 0 Open ; + 22 14 20 1100 12 100 0 Open ; + 23 20 21 1300 8 100 0 Open ; + 24 21 22 1300 8 100 0 Open ; + 25 20 22 1300 8 100 0 Open ; + 26 24 23 600 12 100 0 Open ; + 27 15 24 250 12 100 0 Open ; + 28 23 25 300 12 100 0 Open ; + 29 25 26 200 12 100 0 Open ; + 30 25 31 600 12 100 0 Open ; + 31 31 27 400 8 100 0 Open ; + 32 27 29 400 8 100 0 Open ; + 34 29 28 700 8 100 0 Open ; + 35 22 33 1000 8 100 0 Open ; + 36 33 34 400 8 100 0 Open ; + 37 32 19 500 8 100 0 Open ; + 38 29 35 500 8 100 0 Open ; + 39 35 30 1000 8 100 0 Open ; + 40 28 35 700 8 100 0 Open ; + 41 28 36 300 8 100 0 Open ; + +[PUMPS] +;ID Node1 Node2 Parameters + +[VALVES] +;ID Node1 Node2 Diameter Type Setting MinorLoss + +[TAGS] + +[DEMANDS] +;Junction Demand Pattern Category + +[STATUS] +;ID Status/Setting + +[PATTERNS] +;ID Multipliers +;Demand Pattern + 1 1.26 1.04 .97 .97 .89 1.19 + 1 1.28 .67 .67 1.34 2.46 .97 + 1 .92 .68 1.43 .61 .31 .78 + 1 .37 .67 1.26 1.56 1.19 1.26 + 1 .6 1.1 1.03 .73 .88 1.06 + 1 .99 1.72 1.12 1.34 1.12 .97 + 1 1.04 1.15 .91 .61 .68 .46 + 1 .51 .74 1.12 1.34 1.26 .97 + 1 .82 1.37 1.03 .81 .88 .81 + 1 .81 +;Pump Station Outflow Pattern + 2 .96 .96 .96 .96 .96 .96 + 2 .62 0 0 0 0 0 + 2 .8 1 1 1 1 .15 + 2 0 0 0 0 0 0 + 2 .55 .92 .92 .92 .92 .9 + 2 .9 .45 0 0 0 0 + 2 0 .7 1 1 1 1 + 2 .2 0 0 0 0 0 + 2 0 .74 .92 .92 .92 .92 + 2 .92 +;Pump Station Fluoride Pattern + 3 .98 1.02 1.05 .99 .64 .46 + 3 .35 .35 .35 .35 .35 .35 + 3 .17 .17 .13 .13 .13 .15 + 3 .15 .15 .15 .15 .15 .15 + 3 .15 .12 .1 .08 .11 .09 + 3 .09 .08 .08 .08 .08 .08 + 3 .08 .09 .07 .07 .09 .09 + 3 .09 .09 .09 .09 .09 .09 + 3 .09 .08 .35 .72 .82 .92 + 3 1 + +[CURVES] +;ID X-Value Y-Value + +[CONTROLS] + +[RULES] + +[ENERGY] + Global Efficiency 75 + Global Price 0.0 + Demand Charge 0.0 + +[EMITTERS] +;Junction Coefficient + +[QUALITY] +;Node InitQual + 1 1.0 + 2 1.0 + 3 1.0 + 4 1.0 + 5 1.0 + 6 1.0 + 7 1.0 + 8 1.0 + 9 1.0 + 10 1.0 + 11 1.0 + 12 1.0 + 13 1.0 + 14 1.0 + 15 1.0 + 16 1.0 + 17 1.0 + 18 1.0 + 19 1.0 + 20 1.0 + 21 1.0 + 22 1.0 + 23 1.0 + 24 1.0 + 25 1.0 + 27 1.0 + 28 1.0 + 29 1.0 + 30 1.0 + 31 1.0 + 32 1.0 + 33 1.0 + 34 1.0 + 35 1.0 + 36 1.0 + 26 1.0 + +[SOURCES] +;Node Type Quality Pattern + 1 CONCEN 1.0 3 + +[REACTIONS] +;Type Pipe/Tank Coefficient + +[REACTIONS] + Order Bulk 1 + Order Tank 1 + Order Wall 1 + Global Bulk 0.0 + Global Wall 0.0 + Limiting Potential 0.0 + Roughness Correlation 0.0 + +[MIXING] +;Tank Model + +[TIMES] + Duration 55:00 + Hydraulic Timestep 1:00 + Quality Timestep 0:05 + Pattern Timestep 1:00 + Pattern Start 0:00 + Report Timestep 1:00 + Report Start 0:00 + Start ClockTime 8 am + Statistic None + +[REPORT] + Status No + Summary No + Page 0 + +[OPTIONS] + Units GPM + Headloss H-W + Specific Gravity 1.0 + Viscosity 1.0 + Trials 40 + Accuracy 0.001 + CHECKFREQ 2 + MAXCHECK 10 + DAMPLIMIT 0 + Unbalanced Continue 10 + Pattern 1 + Demand Multiplier 1.0 + Emitter Exponent 0.5 + Quality Fluoride mg/L + Diffusivity 1.0 + Tolerance 0.01 + +[COORDINATES] +;Node X-Coord Y-Coord + 1 21.00 4.00 + 2 19.00 20.00 + 3 11.00 21.00 + 4 14.00 28.00 + 5 19.00 25.00 + 6 28.00 23.00 + 7 36.00 39.00 + 8 38.00 30.00 + 9 36.00 42.00 + 10 37.00 23.00 + 11 37.00 49.00 + 12 39.00 60.00 + 13 38.00 64.00 + 14 38.00 66.00 + 15 37.00 69.00 + 16 27.00 65.00 + 17 27.00 69.00 + 18 23.00 68.00 + 19 21.00 59.00 + 20 45.00 68.00 + 21 51.00 62.00 + 22 54.00 69.00 + 23 35.00 74.00 + 24 37.00 71.00 + 25 35.00 76.00 + 27 39.00 87.00 + 28 49.00 85.00 + 29 42.00 86.00 + 30 47.00 80.00 + 31 37.00 80.00 + 32 23.00 64.00 + 33 56.00 73.00 + 34 56.00 77.00 + 35 43.00 81.00 + 36 53.00 87.00 + 26 33.00 76.00 + +[VERTICES] +;Link X-Coord Y-Coord + +[LABELS] +;X-Coord Y-Coord Label & Anchor Node + 24.00 7.00 "Pump" + 24.00 4.00 "Station" + 26.76 77.42 "Tank" + +[BACKDROP] + DIMENSIONS 8.75 -0.15 58.25 91.15 + UNITS None + FILE + OFFSET 0.00 0.00 + +[END] diff --git a/public/Net3.inp b/public/Net3.inp new file mode 100644 index 0000000..d9070e6 --- /dev/null +++ b/public/Net3.inp @@ -0,0 +1,479 @@ +[TITLE] +EPANET Example Network 3 +Example showing how the percent of Lake water in a dual-source +system changes over time. + +[JUNCTIONS] +;ID Elev Demand Pattern + 10 147 0 ; + 15 32 1 3 ; + 20 129 0 ; + 35 12.5 1 4 ; + 40 131.9 0 ; + 50 116.5 0 ; + 60 0 0 ; + 601 0 0 ; + 61 0 0 ; + 101 42 189.95 ; + 103 43 133.2 ; + 105 28.5 135.37 ; + 107 22 54.64 ; + 109 20.3 231.4 ; + 111 10 141.94 ; + 113 2 20.01 ; + 115 14 52.1 ; + 117 13.6 117.71 ; + 119 2 176.13 ; + 120 0 0 ; + 121 -2 41.63 ; + 123 11 1 2 ; + 125 11 45.6 ; + 127 56 17.66 ; + 129 51 0 ; + 131 6 42.75 ; + 139 31 5.89 ; + 141 4 9.85 ; + 143 -4.5 6.2 ; + 145 1 27.63 ; + 147 18.5 8.55 ; + 149 16 27.07 ; + 151 33.5 144.48 ; + 153 66.2 44.17 ; + 157 13.1 51.79 ; + 159 6 41.32 ; + 161 4 15.8 ; + 163 5 9.42 ; + 164 5 0 ; + 166 -2 2.6 ; + 167 -5 14.56 ; + 169 -5 0 ; + 171 -4 39.34 ; + 173 -4 0 ; + 177 8 58.17 ; + 179 8 0 ; + 181 8 0 ; + 183 11 0 ; + 184 16 0 ; + 185 16 25.65 ; + 187 12.5 0 ; + 189 4 107.92 ; + 191 25 81.9 ; + 193 18 71.31 ; + 195 15.5 0 ; + 197 23 17.04 ; + 199 -2 119.32 ; + 201 0.1 44.61 ; + 203 2 1 5 ; + 204 21 0 ; + 205 21 65.36 ; + 206 1 0 ; + 207 9 69.39 ; + 208 16 0 ; + 209 -2 0.87 ; + 211 7 8.67 ; + 213 7 13.94 ; + 215 7 92.19 ; + 217 6 24.22 ; + 219 4 41.32 ; + 225 8 22.8 ; + 229 10.5 64.18 ; + 231 5 16.48 ; + 237 14 15.61 ; + 239 13 44.61 ; + 241 13 0 ; + 243 14 4.34 ; + 247 18 70.38 ; + 249 18 0 ; + 251 30 24.16 ; + 253 36 54.52 ; + 255 27 40.39 ; + 257 17 0 ; + 259 25 0 ; + 261 0 0 ; + 263 0 0 ; + 265 0 0 ; + 267 21 0 ; + 269 0 0 ; + 271 6 0 ; + 273 8 0 ; + 275 10 0 ; + +[RESERVOIRS] +;ID Head Pattern + River 220.0 ; + Lake 167.0 ; + +[TANKS] +;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve + 1 131.9 13.1 .1 32.1 85 0 ; + 2 116.5 23.5 6.5 40.3 50 0 ; + 3 129.0 29.0 4.0 35.5 164 0 ; + +[PIPES] +;ID Node1 Node2 Length Diameter Roughness MinorLoss Status + 20 3 20 99 99 199 0 Open ; + 40 1 40 99 99 199 0 Open ; + 50 2 50 99 99 199 0 Open ; + 60 River 60 1231 24 140 0 Open ; + 101 10 101 14200 18 110 0 Open ; + 103 101 103 1350 16 130 0 Open ; + 105 101 105 2540 12 130 0 Open ; + 107 105 107 1470 12 130 0 Open ; + 109 103 109 3940 16 130 0 Open ; + 111 109 111 2000 12 130 0 Open ; + 112 115 111 1160 12 130 0 Open ; + 113 111 113 1680 12 130 0 Open ; + 114 115 113 2000 8 130 0 Open ; + 115 107 115 1950 8 130 0 Open ; + 116 113 193 1660 12 130 0 Open ; + 117 263 105 2725 12 130 0 Open ; + 119 115 117 2180 12 130 0 Open ; + 120 119 120 730 12 130 0 Open ; + 121 120 117 1870 12 130 0 Open ; + 122 121 120 2050 8 130 0 Open ; + 123 121 119 2000 30 141 0 Open ; + 125 123 121 1500 30 141 0 Open ; + 129 121 125 930 24 130 0 Open ; + 131 125 127 3240 24 130 0 Open ; + 133 20 127 785 20 130 0 Open ; + 135 127 129 900 24 130 0 Open ; + 137 129 131 6480 16 130 0 Open ; + 145 129 139 2750 8 130 0 Open ; + 147 139 141 2050 8 130 0 Open ; + 149 143 141 1400 8 130 0 Open ; + 151 15 143 1650 8 130 0 Open ; + 153 145 141 3510 12 130 0 Open ; + 155 147 145 2200 12 130 0 Open ; + 159 147 149 880 12 130 0 Open ; + 161 149 151 1020 8 130 0 Open ; + 163 151 153 1170 12 130 0 Open ; + 169 125 153 4560 8 130 0 Open ; + 171 119 151 3460 12 130 0 Open ; + 173 119 157 2080 30 141 0 Open ; + 175 157 159 2910 30 141 0 Open ; + 177 159 161 2000 30 141 0 Open ; + 179 161 163 430 30 141 0 Open ; + 180 163 164 150 14 130 0 Open ; + 181 164 166 490 14 130 0 Open ; + 183 265 169 590 30 141 0 Open ; + 185 167 169 60 8 130 0 Open ; + 186 187 204 99.9 8 130 0 Open ; + 187 169 171 1270 30 141 0 Open ; + 189 171 173 50 30 141 0 Open ; + 191 271 171 760 24 130 0 Open ; + 193 35 181 30 24 130 0 Open ; + 195 181 177 30 12 130 0 Open ; + 197 177 179 30 12 130 0 Open ; + 199 179 183 210 12 130 0 Open ; + 201 40 179 1190 12 130 0 Open ; + 202 185 184 99.9 8 130 0 Open ; + 203 183 185 510 8 130 0 Open ; + 204 184 205 4530. 12 130 0 Open ; + 205 204 185 1325. 12 130 0 Open ; + 207 189 183 1350 12 130 0 Open ; + 209 189 187 500 8 130 0 Open ; + 211 169 269 646 12 130 0 Open ; + 213 191 187 2560 12 130 0 Open ; + 215 267 189 1230 12 130 0 Open ; + 217 191 193 520 12 130 0 Open ; + 219 193 195 360 12 130 0 Open ; + 221 161 195 2300 8 130 0 Open ; + 223 197 191 1150 12 130 0 Open ; + 225 111 197 2790 12 130 0 Open ; + 229 173 199 4000 24 141 0 Open ; + 231 199 201 630 24 141 0 Open ; + 233 201 203 120 24 130 0 Open ; + 235 199 273 725 12 130 0 Open ; + 237 205 207 1200 12 130 0 Open ; + 238 207 206 450 12 130 0 Open ; + 239 275 207 1430 12 130 0 Open ; + 240 206 208 510 12 130 0 Open ; + 241 208 209 885 12 130 0 Open ; + 243 209 211 1210 16 130 0 Open ; + 245 211 213 990 16 130 0 Open ; + 247 213 215 4285 16 130 0 Open ; + 249 215 217 1660 16 130 0 Open ; + 251 217 219 2050 14 130 0 Open ; + 257 217 225 1560 12 130 0 Open ; + 261 213 229 2200 8 130 0 Open ; + 263 229 231 1960 12 130 0 Open ; + 269 211 237 2080 12 130 0 Open ; + 271 237 229 790 8 130 0 Open ; + 273 237 239 510 12 130 0 Open ; + 275 239 241 35 12 130 0 Open ; + 277 241 243 2200 12 130 0 Open ; + 281 241 247 445 10 130 0 Open ; + 283 239 249 430 12 130 0 Open ; + 285 247 249 10 12 130 0 Open ; + 287 247 255 1390 10 130 0 Open ; + 289 50 255 925 10 130 0 Open ; + 291 255 253 1100 10 130 0 Open ; + 293 255 251 1100 8 130 0 Open ; + 295 249 251 1450 12 130 0 Open ; + 297 120 257 645 8 130 0 Open ; + 299 257 259 350 8 130 0 Open ; + 301 259 263 1400 8 130 0 Open ; + 303 257 261 1400 8 130 0 Open ; + 305 117 261 645 12 130 0 Open ; + 307 261 263 350 12 130 0 Open ; + 309 265 267 1580 8 130 0 Open ; + 311 193 267 1170 12 130 0 Open ; + 313 269 189 646 12 130 0 Open ; + 315 181 271 260 24 130 0 Open ; + 317 273 275 2230 8 130 0 Open ; + 319 273 205 645 12 130 0 Open ; + 321 163 265 1200 30 141 0 Open ; + 323 201 275 300 12 130 0 Open ; + 325 269 271 1290 8 130 0 Open ; + 329 61 123 45500 30 140 0 Open ; + 330 60 601 1 30 140 0 Closed ; + 333 601 61 1 30 140 0 Open ; + +[PUMPS] +;ID Node1 Node2 Parameters + 10 Lake 10 HEAD 1 ; + 335 60 61 HEAD 2 ; + +[VALVES] +;ID Node1 Node2 Diameter Type Setting MinorLoss + +[TAGS] + +[DEMANDS] +;Junction Demand Pattern Category + +[STATUS] +;ID Status/Setting + 10 Closed + +[PATTERNS] +;ID Multipliers +;General Default Demand Pattern + 1 1.34 1.94 1.46 1.44 .76 .92 + 1 .85 1.07 .96 1.1 1.08 1.19 + 1 1.16 1.08 .96 .83 .79 .74 + 1 .64 .64 .85 .96 1.24 1.67 +;Demand Pattern for Node 123 + 2 0 0 0 0 0 1219 + 2 0 0 0 1866 1836 1818 + 2 1818 1822 1822 1817 1824 1816 + 2 1833 1817 1830 1814 1840 1859 +;Demand Pattern for Node 15 + 3 620 620 620 620 620 360 + 3 360 0 0 0 0 360 + 3 360 360 360 360 0 0 + 3 0 0 0 0 360 360 +;Demand Pattern for Node 35 + 4 1637 1706 1719 1719 1791 1819 + 4 1777 1842 1815 1825 1856 1801 + 4 1819 1733 1664 1620 1613 1620 + 4 1616 1647 1627 1627 1671 1668 +;Demand Pattern for Node 203 + 5 4439 4531 4511 4582 4531 4582 + 5 4572 4613 4643 4643 4592 4613 + 5 4531 4521 4449 4439 4449 4460 + 5 4439 4419 4368 4399 4470 4480 + +[CURVES] +;ID X-Value Y-Value +;PUMP: Pump Curve for Pump 10 (Lake Source) + 1 0 104. + 1 2000. 92. + 1 4000. 63. +;PUMP: Pump Curve for Pump 335 (River Source) + 2 0 200. + 2 8000. 138. + 2 14000. 86. + +[CONTROLS] +;Lake source operates only part of the day +Link 10 OPEN AT TIME 1 +Link 10 CLOSED AT TIME 15 + +;Pump 335 controlled by level in Tank 1 +;When pump is closed, bypass pipe is opened +Link 335 OPEN IF Node 1 BELOW 17.1 +Link 335 CLOSED IF Node 1 ABOVE 19.1 +Link 330 CLOSED IF Node 1 BELOW 17.1 +Link 330 OPEN IF Node 1 ABOVE 19.1 + +[RULES] + +[ENERGY] + Global Efficiency 75 + Global Price 0.0 + Demand Charge 0.0 + +[EMITTERS] +;Junction Coefficient + +[QUALITY] +;Node InitQual + +[SOURCES] +;Node Type Quality Pattern + +[REACTIONS] +;Type Pipe/Tank Coefficient + +[REACTIONS] + Order Bulk 1 + Order Tank 1 + Order Wall 1 + Global Bulk 0.0 + Global Wall 0.0 + Limiting Potential 0.0 + Roughness Correlation 0.0 + +[MIXING] +;Tank Model + +[TIMES] + Duration 24:00 + Hydraulic Timestep 1:00 + Quality Timestep 0:05 + Pattern Timestep 1:00 + Pattern Start 0:00 + Report Timestep 1:00 + Report Start 0:00 + Start ClockTime 12 am + Statistic None + +[REPORT] + Status Yes + Summary No + Page 0 + +[OPTIONS] + Units GPM + Headloss H-W + Specific Gravity 1.0 + Viscosity 1.0 + Trials 40 + Accuracy 0.001 + CHECKFREQ 2 + MAXCHECK 10 + DAMPLIMIT 0 + Unbalanced Continue 10 + Pattern 1 + Demand Multiplier 1.0 + Emitter Exponent 0.5 + Quality Trace Lake + Diffusivity 1.0 + Tolerance 0.01 + +[COORDINATES] +;Node X-Coord Y-Coord + 10 9.00 27.85 + 15 38.68 23.76 + 20 29.44 26.91 + 35 25.46 10.52 + 40 27.02 9.81 + 50 33.01 3.01 + 60 23.90 29.94 + 601 23.00 29.49 + 61 23.71 29.03 + 101 13.81 22.94 + 103 12.96 21.31 + 105 16.97 21.28 + 107 18.45 20.46 + 109 17.64 18.92 + 111 20.21 17.53 + 113 22.04 16.61 + 115 20.98 19.18 + 117 21.69 21.28 + 119 23.70 22.76 + 120 22.08 23.10 + 121 23.54 25.50 + 123 23.37 27.31 + 125 24.59 25.64 + 127 29.29 26.40 + 129 30.32 26.39 + 131 37.89 29.55 + 139 33.28 24.54 + 141 35.68 23.08 + 143 37.47 21.97 + 145 33.02 19.29 + 147 30.24 20.38 + 149 29.62 20.74 + 151 28.29 21.39 + 153 28.13 22.63 + 157 24.85 20.16 + 159 23.12 17.50 + 161 25.10 15.28 + 163 25.39 14.98 + 164 25.98 15.14 + 166 26.48 15.13 + 167 25.88 12.98 + 169 25.68 12.74 + 171 26.65 11.80 + 173 26.87 11.59 + 177 25.92 10.59 + 179 25.71 10.40 + 181 25.72 10.74 + 183 25.45 10.18 + 184 25.15 9.52 + 185 25.01 9.67 + 187 23.64 11.04 + 189 24.15 11.37 + 191 22.10 14.07 + 193 22.88 14.35 + 195 23.18 14.72 + 197 20.97 15.18 + 199 29.42 8.44 + 201 30.89 8.57 + 203 31.14 8.89 + 204 23.80 10.90 + 205 29.20 6.46 + 206 31.66 6.64 + 207 31.00 6.61 + 208 32.54 6.81 + 209 33.76 6.59 + 211 34.20 5.54 + 213 35.26 6.16 + 215 39.95 8.73 + 217 42.11 8.67 + 219 44.86 9.32 + 225 43.53 7.38 + 229 36.16 3.49 + 231 38.38 2.54 + 237 35.37 3.08 + 239 35.76 2.31 + 241 35.87 2.11 + 243 37.04 0.00 + 247 35.02 2.05 + 249 35.02 1.81 + 251 34.15 1.10 + 253 32.17 1.88 + 255 33.51 2.45 + 257 21.17 23.32 + 259 20.80 23.40 + 261 20.79 21.45 + 263 20.32 21.57 + 265 25.39 13.60 + 267 23.38 12.95 + 269 25.03 12.14 + 271 25.97 11.00 + 273 29.16 7.38 + 275 31.07 8.29 + River 24.15 31.06 + Lake 8.00 27.53 + 1 27.46 9.84 + 2 32.99 3.45 + 3 29.41 27.27 + +[VERTICES] +;Link X-Coord Y-Coord + +[LABELS] +;X-Coord Y-Coord Label & Anchor Node + 8.00 29.42 "LAKE" + 25.00 31.10 "RIVER" + +[BACKDROP] + DIMENSIONS 6.16 -1.55 46.70 32.61 + UNITS None + FILE + OFFSET 0.00 0.00 + +[END] diff --git a/public/fonts/Noto Sans Regular/0-255.pbf b/public/fonts/Noto Sans Regular/0-255.pbf new file mode 100644 index 0000000..736ba4a Binary files /dev/null and b/public/fonts/Noto Sans Regular/0-255.pbf differ diff --git a/public/fonts/Noto Sans Regular/12288-12543.pbf b/public/fonts/Noto Sans Regular/12288-12543.pbf new file mode 100644 index 0000000..da78937 Binary files /dev/null and b/public/fonts/Noto Sans Regular/12288-12543.pbf differ diff --git a/public/fonts/Noto Sans Regular/256-511.pbf b/public/fonts/Noto Sans Regular/256-511.pbf new file mode 100644 index 0000000..6cc6850 Binary files /dev/null and b/public/fonts/Noto Sans Regular/256-511.pbf differ diff --git a/public/fonts/Noto Sans Regular/512-767.pbf b/public/fonts/Noto Sans Regular/512-767.pbf new file mode 100644 index 0000000..659826e Binary files /dev/null and b/public/fonts/Noto Sans Regular/512-767.pbf differ diff --git a/public/fonts/Noto Sans Regular/768-1023.pbf b/public/fonts/Noto Sans Regular/768-1023.pbf new file mode 100644 index 0000000..660c89e Binary files /dev/null and b/public/fonts/Noto Sans Regular/768-1023.pbf differ diff --git a/public/fonts/Noto Sans Regular/8192-8447.pbf b/public/fonts/Noto Sans Regular/8192-8447.pbf new file mode 100644 index 0000000..3bd9adc Binary files /dev/null and b/public/fonts/Noto Sans Regular/8192-8447.pbf differ diff --git a/public/fonts/Noto Sans Regular/8448-8703.pbf b/public/fonts/Noto Sans Regular/8448-8703.pbf new file mode 100644 index 0000000..fdd8c1e Binary files /dev/null and b/public/fonts/Noto Sans Regular/8448-8703.pbf differ diff --git a/public/fonts/Noto Sans Regular/8704-8959.pbf b/public/fonts/Noto Sans Regular/8704-8959.pbf new file mode 100644 index 0000000..6722a21 Binary files /dev/null and b/public/fonts/Noto Sans Regular/8704-8959.pbf differ diff --git a/public/korean_cp949_network.inp b/public/korean_cp949_network.inp new file mode 100644 index 0000000..33f5f23 --- /dev/null +++ b/public/korean_cp949_network.inp @@ -0,0 +1,53 @@ +[TITLE] + Çѱ¹¾î ÁöÀÚü ±޼ö±¸¿ª ¼ö¸®Çؼ® ¸ðµ¨ (CP949 ÀÎÄڵù ǥÁØ °ËÁõº») + ¹è¼öº»°ü ¹× ºí·Ï À¯·®°è °¡¾Ð °ü¸Á + +[JUNCTIONS] +;ID Elev Demand Pattern + Á¤¼öÀåÀ¯Ãâ_J01 25.0 10.0 1 ; Á¦1°¡¾ÐÀå Èí¼ö¶óÀÎ + »ç°Ÿ®ºбâ_J02 28.5 35.0 1 ; Á߾ӷÎ ±³Â÷Á¡ ºбâ + ÁְŴÜÁöÀ¯ÀÔ_J03 32.0 50.0 1 ; ¾ÆÆÄƮ ´ÜÁö ÀÎÀ԰ü + °íÁö´ëÀýÁ¡_J04 45.0 20.0 1 ; ¹è¼ö±¸¿ª ¸»´Ü °¡¾ÐÀýÁ¡ + +[RESERVOIRS] +;ID Head Pattern + Á߾ÓÁ¤¼öÁö_RES 65.0 ; ÁÖ ¼ö¿øÁö ¼öµÎ 65m + +[PIPES] +;ID Node1 Node2 Length Diameter Roughness MinorLoss Status + ¼ۼö°£¼±_P01 Á߾ÓÁ¤¼öÁö_RES Á¤¼öÀåÀ¯Ãâ_J01 500 350 130 0 Open ; ´ÚŸÀÏÁÖö°ü + ¹è¼öº»°ü_P02 Á¤¼öÀåÀ¯Ãâ_J01 »ç°Ÿ®ºбâ_J02 420 300 130 0 Open ; ½ż³ º»°ü + ÁְÅÀÎÀ԰ü_P03 »ç°Ÿ®ºбâ_J02 ÁְŴÜÁöÀ¯ÀÔ_J03 280 200 140 0 Open ; PE°ü + °íÁö´밡¾аü_P04 »ç°Ÿ®ºбâ_J02 °íÁö´ëÀýÁ¡_J04 650 150 110 0 Open ; ÁÖö°ü + +[PATTERNS] +;ID Multipliers + 1 0.70 0.80 1.20 1.60 1.40 1.10 + 1 1.00 0.90 1.30 1.50 1.00 0.70 + +[TIMES] + Duration 24:00 + Hydraulic Timestep 1:00 + Quality Timestep 0:05 + Pattern Timestep 2:00 + Pattern Start 0:00 + Report Timestep 1:00 + Report Start 0:00 + Start ClockTime 12 am + Statistic None + +[OPTIONS] + Units LPS + Headloss H-W + Specific Gravity 1.0 + Accuracy 0.001 + +[COORDINATES] +;Node X-Coord Y-Coord + Á߾ÓÁ¤¼öÁö_RES 954200.00 1945100.00 + Á¤¼öÀåÀ¯Ãâ_J01 954700.00 1945100.00 + »ç°Ÿ®ºбâ_J02 955120.00 1945100.00 + ÁְŴÜÁöÀ¯ÀÔ_J03 955120.00 1945380.00 + °íÁö´ëÀýÁ¡_J04 955770.00 1945100.00 + +[END] diff --git a/public/sample_gis_pipes.geojson b/public/sample_gis_pipes.geojson new file mode 100644 index 0000000..b9d19ea --- /dev/null +++ b/public/sample_gis_pipes.geojson @@ -0,0 +1,118 @@ +{ + "type": "FeatureCollection", + "name": "상수관로_GIS_샘플", + "crs": { + "type": "name", + "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } + }, + "features": [ + { + "type": "Feature", + "properties": { + "PIP_ID": "PIPE_101", + "PIP_DIA": 300, + "PIP_LEN": 450, + "PIP_MAT": "DIP", + "ROUGHNESS": 110, + "LAYER": "상수원관" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [126.9750, 37.5650], + [126.9780, 37.5650] + ] + } + }, + { + "type": "Feature", + "properties": { + "PIP_ID": "PIPE_102", + "PIP_DIA": 200, + "PIP_LEN": 350, + "PIP_MAT": "PE", + "ROUGHNESS": 130, + "LAYER": "배수관" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [126.9780, 37.5650], + [126.9780, 37.5610] + ] + } + }, + { + "type": "Feature", + "properties": { + "PIP_ID": "PIPE_103", + "PIP_DIA": 200, + "PIP_LEN": 380, + "PIP_MAT": "PE", + "ROUGHNESS": 130, + "LAYER": "배수관" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [126.9780, 37.5610], + [126.9740, 37.5610] + ] + } + }, + { + "type": "Feature", + "properties": { + "PIP_ID": "PIPE_104", + "PIP_DIA": 150, + "PIP_LEN": 420, + "PIP_MAT": "PVC", + "ROUGHNESS": 140, + "LAYER": "급수관" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [126.9740, 37.5610], + [126.9750, 37.5650] + ] + } + }, + { + "type": "Feature", + "properties": { + "PIP_ID": "PIPE_105", + "PIP_DIA": 150, + "PIP_LEN": 280, + "PIP_MAT": "DIP", + "ROUGHNESS": 120, + "LAYER": "급수지선" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [126.9780, 37.5650], + [126.9820, 37.5630] + ] + } + }, + { + "type": "Feature", + "properties": { + "PIP_ID": "PIPE_106", + "PIP_DIA": 150, + "PIP_LEN": 310, + "PIP_MAT": "PE", + "ROUGHNESS": 130, + "LAYER": "급수지선" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [126.9820, 37.5630], + [126.9780, 37.5610] + ] + } + } + ] +} diff --git a/public/sample_pressure_network.inp b/public/sample_pressure_network.inp new file mode 100644 index 0000000..274fa35 --- /dev/null +++ b/public/sample_pressure_network.inp @@ -0,0 +1,139 @@ +[TITLE] + Anonymous District Metered Area Water Network (EPSG:5179) + spipe Verification Benchmark Model + +[JUNCTIONS] +;ID Elev Demand Pattern + N01 35.0 15.0 1 ; + N02 36.5 20.0 1 ; + N03 34.0 25.0 1 ; + N04 38.0 18.0 1 ; + N05 33.5 30.0 1 ; + N06 35.0 22.0 1 ; + N07 37.0 16.0 1 ; + N08 39.0 12.0 1 ; + +[RESERVOIRS] +;ID Head Pattern + RES01 85.0 ; + +[TANKS] +;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve + TANK01 50.0 8.0 2.0 12.0 25.0 0 ; + +[PIPES] +;ID Node1 Node2 Length Diameter Roughness MinorLoss Status + PIPE01 RES01 N01 450 300 110 0 Open ; + PIPE02 N01 N02 320 250 120 0 Open ; + PIPE03 N02 N03 280 200 130 0 Open ; + PIPE04 N03 N04 360 200 130 0 Open ; + PIPE05 N04 N05 410 200 130 0 Open ; + PIPE06 N05 N06 290 150 130 0 Open ; + PIPE07 N06 N07 350 150 130 0 Open ; + PIPE08 N07 N08 310 150 130 0 Open ; + PIPE09 N08 TANK01 250 200 120 0 Open ; + PIPE10 N02 N06 520 200 130 0 Open ; + PIPE11 N03 N07 480 150 130 0 Open ; + +[PUMPS] +;ID Node1 Node2 Parameters + +[VALVES] +;ID Node1 Node2 Diameter Type Setting MinorLoss + +[TAGS] + +[DEMANDS] +;Junction Demand Pattern Category + +[STATUS] +;ID Status/Setting + +[PATTERNS] +;ID Multipliers +;Demand Pattern (Residential 2-Peak) + 1 0.60 0.70 0.90 1.40 1.60 1.30 + 1 1.10 1.00 1.20 1.50 1.10 0.80 + +[CURVES] +;ID X-Value Y-Value + +[CONTROLS] + +[RULES] + +[ENERGY] + Global Efficiency 75 + Global Price 0.0 + Demand Charge 0.0 + +[EMITTERS] +;Junction Coefficient + +[QUALITY] +;Node InitQual + +[SOURCES] +;Node Type Quality Pattern + +[REACTIONS] +;Type Pipe/Tank Coefficient + +[MIXING] +;Tank Model + +[TIMES] + Duration 24:00 + Hydraulic Timestep 1:00 + Quality Timestep 0:05 + Pattern Timestep 2:00 + Pattern Start 0:00 + Report Timestep 1:00 + Report Start 0:00 + Start ClockTime 12 am + Statistic None + +[REPORT] + Status Yes + Summary No + Page 0 + +[OPTIONS] + Units LPS + Headloss H-W + Specific Gravity 1.0 + Viscosity 1.0 + Trials 40 + Accuracy 0.001 + CHECKFREQ 2 + MAXCHECK 10 + DAMPLIMIT 0 + Unbalanced Continue 10 + Pattern 1 + Demand Multiplier 1.0 + Emitter Exponent 0.5 + Quality None + Diffusivity 1.0 + Tolerance 0.01 + +[COORDINATES] +;Node X-Coord Y-Coord + RES01 986615.92 1894617.19 + N01 987015.92 1894417.19 + N02 987315.92 1894217.19 + N03 987615.92 1894117.19 + N04 987915.92 1894317.19 + N05 988315.92 1894217.19 + N06 987415.92 1893717.19 + N07 987715.92 1893617.19 + N08 988015.92 1893517.19 + TANK01 988265.92 1893417.19 + +[VERTICES] +;Link X-Coord Y-Coord + +[LABELS] + +[BACKDROP] + +[END] diff --git a/public/synthetic_large_network_5179.inp b/public/synthetic_large_network_5179.inp new file mode 100644 index 0000000..50b2fcc --- /dev/null +++ b/public/synthetic_large_network_5179.inp @@ -0,0 +1,8057 @@ +[TITLE] + Synthetic Large District Metered Network (EPSG:5179, > 2000 Pipes) + Benchmarking Grid for High-Capacity Hydraulic Verification + +[JUNCTIONS] +;ID Elev Demand Pattern + J_00_00 20.00 5.00 1 + J_00_01 20.20 0.00 1 + J_00_02 20.40 0.00 1 + J_00_03 20.60 5.00 1 + J_00_04 20.80 0.00 1 + J_00_05 21.00 0.00 1 + J_00_06 21.20 5.00 1 + J_00_07 21.40 0.00 1 + J_00_08 21.60 0.00 1 + J_00_09 21.80 5.00 1 + J_00_10 22.00 0.00 1 + J_00_11 22.20 0.00 1 + J_00_12 22.40 5.00 1 + J_00_13 22.60 0.00 1 + J_00_14 22.80 0.00 1 + J_00_15 23.00 5.00 1 + J_00_16 23.20 0.00 1 + J_00_17 23.40 0.00 1 + J_00_18 23.60 5.00 1 + J_00_19 23.80 0.00 1 + J_00_20 24.00 0.00 1 + J_00_21 24.20 5.00 1 + J_00_22 24.40 0.00 1 + J_00_23 24.60 0.00 1 + J_00_24 24.80 5.00 1 + J_00_25 25.00 0.00 1 + J_00_26 25.20 0.00 1 + J_00_27 25.40 5.00 1 + J_00_28 25.60 0.00 1 + J_00_29 25.80 0.00 1 + J_00_30 26.00 5.00 1 + J_00_31 26.20 0.00 1 + J_00_32 26.40 0.00 1 + J_00_33 26.60 5.00 1 + J_00_34 26.80 0.00 1 + J_00_35 27.00 0.00 1 + J_00_36 27.20 5.00 1 + J_00_37 27.40 0.00 1 + J_00_38 27.60 0.00 1 + J_00_39 27.80 5.00 1 + J_00_40 28.00 0.00 1 + J_00_41 28.20 0.00 1 + J_00_42 28.40 5.00 1 + J_00_43 28.60 0.00 1 + J_00_44 28.80 0.00 1 + J_01_00 20.20 0.00 1 + J_01_01 20.40 0.00 1 + J_01_02 20.60 5.00 1 + J_01_03 20.80 0.00 1 + J_01_04 21.00 0.00 1 + J_01_05 21.20 5.00 1 + J_01_06 21.40 0.00 1 + J_01_07 21.60 0.00 1 + J_01_08 21.80 5.00 1 + J_01_09 22.00 0.00 1 + J_01_10 22.20 0.00 1 + J_01_11 22.40 5.00 1 + J_01_12 22.60 0.00 1 + J_01_13 22.80 0.00 1 + J_01_14 23.00 5.00 1 + J_01_15 23.20 0.00 1 + J_01_16 23.40 0.00 1 + J_01_17 23.60 5.00 1 + J_01_18 23.80 0.00 1 + J_01_19 24.00 0.00 1 + J_01_20 24.20 5.00 1 + J_01_21 24.40 0.00 1 + J_01_22 24.60 0.00 1 + J_01_23 24.80 5.00 1 + J_01_24 25.00 0.00 1 + J_01_25 25.20 0.00 1 + J_01_26 25.40 5.00 1 + J_01_27 25.60 0.00 1 + J_01_28 25.80 0.00 1 + J_01_29 26.00 5.00 1 + J_01_30 26.20 0.00 1 + J_01_31 26.40 0.00 1 + J_01_32 26.60 5.00 1 + J_01_33 26.80 0.00 1 + J_01_34 27.00 0.00 1 + J_01_35 27.20 5.00 1 + J_01_36 27.40 0.00 1 + J_01_37 27.60 0.00 1 + J_01_38 27.80 5.00 1 + J_01_39 28.00 0.00 1 + J_01_40 28.20 0.00 1 + J_01_41 28.40 5.00 1 + J_01_42 28.60 0.00 1 + J_01_43 28.80 0.00 1 + J_01_44 29.00 5.00 1 + J_02_00 20.40 0.00 1 + J_02_01 20.60 5.00 1 + J_02_02 20.80 0.00 1 + J_02_03 21.00 0.00 1 + J_02_04 21.20 5.00 1 + J_02_05 21.40 0.00 1 + J_02_06 21.60 0.00 1 + J_02_07 21.80 5.00 1 + J_02_08 22.00 0.00 1 + J_02_09 22.20 0.00 1 + J_02_10 22.40 5.00 1 + J_02_11 22.60 0.00 1 + J_02_12 22.80 0.00 1 + J_02_13 23.00 5.00 1 + J_02_14 23.20 0.00 1 + J_02_15 23.40 0.00 1 + J_02_16 23.60 5.00 1 + J_02_17 23.80 0.00 1 + J_02_18 24.00 0.00 1 + J_02_19 24.20 5.00 1 + J_02_20 24.40 0.00 1 + J_02_21 24.60 0.00 1 + J_02_22 24.80 5.00 1 + J_02_23 25.00 0.00 1 + J_02_24 25.20 0.00 1 + J_02_25 25.40 5.00 1 + J_02_26 25.60 0.00 1 + J_02_27 25.80 0.00 1 + J_02_28 26.00 5.00 1 + J_02_29 26.20 0.00 1 + J_02_30 26.40 0.00 1 + J_02_31 26.60 5.00 1 + J_02_32 26.80 0.00 1 + J_02_33 27.00 0.00 1 + J_02_34 27.20 5.00 1 + J_02_35 27.40 0.00 1 + J_02_36 27.60 0.00 1 + J_02_37 27.80 5.00 1 + J_02_38 28.00 0.00 1 + J_02_39 28.20 0.00 1 + J_02_40 28.40 5.00 1 + J_02_41 28.60 0.00 1 + J_02_42 28.80 0.00 1 + J_02_43 29.00 5.00 1 + J_02_44 29.20 0.00 1 + J_03_00 20.60 5.00 1 + J_03_01 20.80 0.00 1 + J_03_02 21.00 0.00 1 + J_03_03 21.20 5.00 1 + J_03_04 21.40 0.00 1 + J_03_05 21.60 0.00 1 + J_03_06 21.80 5.00 1 + J_03_07 22.00 0.00 1 + J_03_08 22.20 0.00 1 + J_03_09 22.40 5.00 1 + J_03_10 22.60 0.00 1 + J_03_11 22.80 0.00 1 + J_03_12 23.00 5.00 1 + J_03_13 23.20 0.00 1 + J_03_14 23.40 0.00 1 + J_03_15 23.60 5.00 1 + J_03_16 23.80 0.00 1 + J_03_17 24.00 0.00 1 + J_03_18 24.20 5.00 1 + J_03_19 24.40 0.00 1 + J_03_20 24.60 0.00 1 + J_03_21 24.80 5.00 1 + J_03_22 25.00 0.00 1 + J_03_23 25.20 0.00 1 + J_03_24 25.40 5.00 1 + J_03_25 25.60 0.00 1 + J_03_26 25.80 0.00 1 + J_03_27 26.00 5.00 1 + J_03_28 26.20 0.00 1 + J_03_29 26.40 0.00 1 + J_03_30 26.60 5.00 1 + J_03_31 26.80 0.00 1 + J_03_32 27.00 0.00 1 + J_03_33 27.20 5.00 1 + J_03_34 27.40 0.00 1 + J_03_35 27.60 0.00 1 + J_03_36 27.80 5.00 1 + J_03_37 28.00 0.00 1 + J_03_38 28.20 0.00 1 + J_03_39 28.40 5.00 1 + J_03_40 28.60 0.00 1 + J_03_41 28.80 0.00 1 + J_03_42 29.00 5.00 1 + J_03_43 29.20 0.00 1 + J_03_44 29.40 0.00 1 + J_04_00 20.80 0.00 1 + J_04_01 21.00 0.00 1 + J_04_02 21.20 5.00 1 + J_04_03 21.40 0.00 1 + J_04_04 21.60 0.00 1 + J_04_05 21.80 5.00 1 + J_04_06 22.00 0.00 1 + J_04_07 22.20 0.00 1 + J_04_08 22.40 5.00 1 + J_04_09 22.60 0.00 1 + J_04_10 22.80 0.00 1 + J_04_11 23.00 5.00 1 + J_04_12 23.20 0.00 1 + J_04_13 23.40 0.00 1 + J_04_14 23.60 5.00 1 + J_04_15 23.80 0.00 1 + J_04_16 24.00 0.00 1 + J_04_17 24.20 5.00 1 + J_04_18 24.40 0.00 1 + J_04_19 24.60 0.00 1 + J_04_20 24.80 5.00 1 + J_04_21 25.00 0.00 1 + J_04_22 25.20 0.00 1 + J_04_23 25.40 5.00 1 + J_04_24 25.60 0.00 1 + J_04_25 25.80 0.00 1 + J_04_26 26.00 5.00 1 + J_04_27 26.20 0.00 1 + J_04_28 26.40 0.00 1 + J_04_29 26.60 5.00 1 + J_04_30 26.80 0.00 1 + J_04_31 27.00 0.00 1 + J_04_32 27.20 5.00 1 + J_04_33 27.40 0.00 1 + J_04_34 27.60 0.00 1 + J_04_35 27.80 5.00 1 + J_04_36 28.00 0.00 1 + J_04_37 28.20 0.00 1 + J_04_38 28.40 5.00 1 + J_04_39 28.60 0.00 1 + J_04_40 28.80 0.00 1 + J_04_41 29.00 5.00 1 + J_04_42 29.20 0.00 1 + J_04_43 29.40 0.00 1 + J_04_44 29.60 5.00 1 + J_05_00 21.00 0.00 1 + J_05_01 21.20 5.00 1 + J_05_02 21.40 0.00 1 + J_05_03 21.60 0.00 1 + J_05_04 21.80 5.00 1 + J_05_05 22.00 0.00 1 + J_05_06 22.20 0.00 1 + J_05_07 22.40 5.00 1 + J_05_08 22.60 0.00 1 + J_05_09 22.80 0.00 1 + J_05_10 23.00 5.00 1 + J_05_11 23.20 0.00 1 + J_05_12 23.40 0.00 1 + J_05_13 23.60 5.00 1 + J_05_14 23.80 0.00 1 + J_05_15 24.00 0.00 1 + J_05_16 24.20 5.00 1 + J_05_17 24.40 0.00 1 + J_05_18 24.60 0.00 1 + J_05_19 24.80 5.00 1 + J_05_20 25.00 0.00 1 + J_05_21 25.20 0.00 1 + J_05_22 25.40 5.00 1 + J_05_23 25.60 0.00 1 + J_05_24 25.80 0.00 1 + J_05_25 26.00 5.00 1 + J_05_26 26.20 0.00 1 + J_05_27 26.40 0.00 1 + J_05_28 26.60 5.00 1 + J_05_29 26.80 0.00 1 + J_05_30 27.00 0.00 1 + J_05_31 27.20 5.00 1 + J_05_32 27.40 0.00 1 + J_05_33 27.60 0.00 1 + J_05_34 27.80 5.00 1 + J_05_35 28.00 0.00 1 + J_05_36 28.20 0.00 1 + J_05_37 28.40 5.00 1 + J_05_38 28.60 0.00 1 + J_05_39 28.80 0.00 1 + J_05_40 29.00 5.00 1 + J_05_41 29.20 0.00 1 + J_05_42 29.40 0.00 1 + J_05_43 29.60 5.00 1 + J_05_44 29.80 0.00 1 + J_06_00 21.20 5.00 1 + J_06_01 21.40 0.00 1 + J_06_02 21.60 0.00 1 + J_06_03 21.80 5.00 1 + J_06_04 22.00 0.00 1 + J_06_05 22.20 0.00 1 + J_06_06 22.40 5.00 1 + J_06_07 22.60 0.00 1 + J_06_08 22.80 0.00 1 + J_06_09 23.00 5.00 1 + J_06_10 23.20 0.00 1 + J_06_11 23.40 0.00 1 + J_06_12 23.60 5.00 1 + J_06_13 23.80 0.00 1 + J_06_14 24.00 0.00 1 + J_06_15 24.20 5.00 1 + J_06_16 24.40 0.00 1 + J_06_17 24.60 0.00 1 + J_06_18 24.80 5.00 1 + J_06_19 25.00 0.00 1 + J_06_20 25.20 0.00 1 + J_06_21 25.40 5.00 1 + J_06_22 25.60 0.00 1 + J_06_23 25.80 0.00 1 + J_06_24 26.00 5.00 1 + J_06_25 26.20 0.00 1 + J_06_26 26.40 0.00 1 + J_06_27 26.60 5.00 1 + J_06_28 26.80 0.00 1 + J_06_29 27.00 0.00 1 + J_06_30 27.20 5.00 1 + J_06_31 27.40 0.00 1 + J_06_32 27.60 0.00 1 + J_06_33 27.80 5.00 1 + J_06_34 28.00 0.00 1 + J_06_35 28.20 0.00 1 + J_06_36 28.40 5.00 1 + J_06_37 28.60 0.00 1 + J_06_38 28.80 0.00 1 + J_06_39 29.00 5.00 1 + J_06_40 29.20 0.00 1 + J_06_41 29.40 0.00 1 + J_06_42 29.60 5.00 1 + J_06_43 29.80 0.00 1 + J_06_44 30.00 0.00 1 + J_07_00 21.40 0.00 1 + J_07_01 21.60 0.00 1 + J_07_02 21.80 5.00 1 + J_07_03 22.00 0.00 1 + J_07_04 22.20 0.00 1 + J_07_05 22.40 5.00 1 + J_07_06 22.60 0.00 1 + J_07_07 22.80 0.00 1 + J_07_08 23.00 5.00 1 + J_07_09 23.20 0.00 1 + J_07_10 23.40 0.00 1 + J_07_11 23.60 5.00 1 + J_07_12 23.80 0.00 1 + J_07_13 24.00 0.00 1 + J_07_14 24.20 5.00 1 + J_07_15 24.40 0.00 1 + J_07_16 24.60 0.00 1 + J_07_17 24.80 5.00 1 + J_07_18 25.00 0.00 1 + J_07_19 25.20 0.00 1 + J_07_20 25.40 5.00 1 + J_07_21 25.60 0.00 1 + J_07_22 25.80 0.00 1 + J_07_23 26.00 5.00 1 + J_07_24 26.20 0.00 1 + J_07_25 26.40 0.00 1 + J_07_26 26.60 5.00 1 + J_07_27 26.80 0.00 1 + J_07_28 27.00 0.00 1 + J_07_29 27.20 5.00 1 + J_07_30 27.40 0.00 1 + J_07_31 27.60 0.00 1 + J_07_32 27.80 5.00 1 + J_07_33 28.00 0.00 1 + J_07_34 28.20 0.00 1 + J_07_35 28.40 5.00 1 + J_07_36 28.60 0.00 1 + J_07_37 28.80 0.00 1 + J_07_38 29.00 5.00 1 + J_07_39 29.20 0.00 1 + J_07_40 29.40 0.00 1 + J_07_41 29.60 5.00 1 + J_07_42 29.80 0.00 1 + J_07_43 30.00 0.00 1 + J_07_44 30.20 5.00 1 + J_08_00 21.60 0.00 1 + J_08_01 21.80 5.00 1 + J_08_02 22.00 0.00 1 + J_08_03 22.20 0.00 1 + J_08_04 22.40 5.00 1 + J_08_05 22.60 0.00 1 + J_08_06 22.80 0.00 1 + J_08_07 23.00 5.00 1 + J_08_08 23.20 0.00 1 + J_08_09 23.40 0.00 1 + J_08_10 23.60 5.00 1 + J_08_11 23.80 0.00 1 + J_08_12 24.00 0.00 1 + J_08_13 24.20 5.00 1 + J_08_14 24.40 0.00 1 + J_08_15 24.60 0.00 1 + J_08_16 24.80 5.00 1 + J_08_17 25.00 0.00 1 + J_08_18 25.20 0.00 1 + J_08_19 25.40 5.00 1 + J_08_20 25.60 0.00 1 + J_08_21 25.80 0.00 1 + J_08_22 26.00 5.00 1 + J_08_23 26.20 0.00 1 + J_08_24 26.40 0.00 1 + J_08_25 26.60 5.00 1 + J_08_26 26.80 0.00 1 + J_08_27 27.00 0.00 1 + J_08_28 27.20 5.00 1 + J_08_29 27.40 0.00 1 + J_08_30 27.60 0.00 1 + J_08_31 27.80 5.00 1 + J_08_32 28.00 0.00 1 + J_08_33 28.20 0.00 1 + J_08_34 28.40 5.00 1 + J_08_35 28.60 0.00 1 + J_08_36 28.80 0.00 1 + J_08_37 29.00 5.00 1 + J_08_38 29.20 0.00 1 + J_08_39 29.40 0.00 1 + J_08_40 29.60 5.00 1 + J_08_41 29.80 0.00 1 + J_08_42 30.00 0.00 1 + J_08_43 30.20 5.00 1 + J_08_44 30.40 0.00 1 + J_09_00 21.80 5.00 1 + J_09_01 22.00 0.00 1 + J_09_02 22.20 0.00 1 + J_09_03 22.40 5.00 1 + J_09_04 22.60 0.00 1 + J_09_05 22.80 0.00 1 + J_09_06 23.00 5.00 1 + J_09_07 23.20 0.00 1 + J_09_08 23.40 0.00 1 + J_09_09 23.60 5.00 1 + J_09_10 23.80 0.00 1 + J_09_11 24.00 0.00 1 + J_09_12 24.20 5.00 1 + J_09_13 24.40 0.00 1 + J_09_14 24.60 0.00 1 + J_09_15 24.80 5.00 1 + J_09_16 25.00 0.00 1 + J_09_17 25.20 0.00 1 + J_09_18 25.40 5.00 1 + J_09_19 25.60 0.00 1 + J_09_20 25.80 0.00 1 + J_09_21 26.00 5.00 1 + J_09_22 26.20 0.00 1 + J_09_23 26.40 0.00 1 + J_09_24 26.60 5.00 1 + J_09_25 26.80 0.00 1 + J_09_26 27.00 0.00 1 + J_09_27 27.20 5.00 1 + J_09_28 27.40 0.00 1 + J_09_29 27.60 0.00 1 + J_09_30 27.80 5.00 1 + J_09_31 28.00 0.00 1 + J_09_32 28.20 0.00 1 + J_09_33 28.40 5.00 1 + J_09_34 28.60 0.00 1 + J_09_35 28.80 0.00 1 + J_09_36 29.00 5.00 1 + J_09_37 29.20 0.00 1 + J_09_38 29.40 0.00 1 + J_09_39 29.60 5.00 1 + J_09_40 29.80 0.00 1 + J_09_41 30.00 0.00 1 + J_09_42 30.20 5.00 1 + J_09_43 30.40 0.00 1 + J_09_44 30.60 0.00 1 + J_10_00 22.00 0.00 1 + J_10_01 22.20 0.00 1 + J_10_02 22.40 5.00 1 + J_10_03 22.60 0.00 1 + J_10_04 22.80 0.00 1 + J_10_05 23.00 5.00 1 + J_10_06 23.20 0.00 1 + J_10_07 23.40 0.00 1 + J_10_08 23.60 5.00 1 + J_10_09 23.80 0.00 1 + J_10_10 24.00 0.00 1 + J_10_11 24.20 5.00 1 + J_10_12 24.40 0.00 1 + J_10_13 24.60 0.00 1 + J_10_14 24.80 5.00 1 + J_10_15 25.00 0.00 1 + J_10_16 25.20 0.00 1 + J_10_17 25.40 5.00 1 + J_10_18 25.60 0.00 1 + J_10_19 25.80 0.00 1 + J_10_20 26.00 5.00 1 + J_10_21 26.20 0.00 1 + J_10_22 26.40 0.00 1 + J_10_23 26.60 5.00 1 + J_10_24 26.80 0.00 1 + J_10_25 27.00 0.00 1 + J_10_26 27.20 5.00 1 + J_10_27 27.40 0.00 1 + J_10_28 27.60 0.00 1 + J_10_29 27.80 5.00 1 + J_10_30 28.00 0.00 1 + J_10_31 28.20 0.00 1 + J_10_32 28.40 5.00 1 + J_10_33 28.60 0.00 1 + J_10_34 28.80 0.00 1 + J_10_35 29.00 5.00 1 + J_10_36 29.20 0.00 1 + J_10_37 29.40 0.00 1 + J_10_38 29.60 5.00 1 + J_10_39 29.80 0.00 1 + J_10_40 30.00 0.00 1 + J_10_41 30.20 5.00 1 + J_10_42 30.40 0.00 1 + J_10_43 30.60 0.00 1 + J_10_44 30.80 5.00 1 + J_11_00 22.20 0.00 1 + J_11_01 22.40 5.00 1 + J_11_02 22.60 0.00 1 + J_11_03 22.80 0.00 1 + J_11_04 23.00 5.00 1 + J_11_05 23.20 0.00 1 + J_11_06 23.40 0.00 1 + J_11_07 23.60 5.00 1 + J_11_08 23.80 0.00 1 + J_11_09 24.00 0.00 1 + J_11_10 24.20 5.00 1 + J_11_11 24.40 0.00 1 + J_11_12 24.60 0.00 1 + J_11_13 24.80 5.00 1 + J_11_14 25.00 0.00 1 + J_11_15 25.20 0.00 1 + J_11_16 25.40 5.00 1 + J_11_17 25.60 0.00 1 + J_11_18 25.80 0.00 1 + J_11_19 26.00 5.00 1 + J_11_20 26.20 0.00 1 + J_11_21 26.40 0.00 1 + J_11_22 26.60 5.00 1 + J_11_23 26.80 0.00 1 + J_11_24 27.00 0.00 1 + J_11_25 27.20 5.00 1 + J_11_26 27.40 0.00 1 + J_11_27 27.60 0.00 1 + J_11_28 27.80 5.00 1 + J_11_29 28.00 0.00 1 + J_11_30 28.20 0.00 1 + J_11_31 28.40 5.00 1 + J_11_32 28.60 0.00 1 + J_11_33 28.80 0.00 1 + J_11_34 29.00 5.00 1 + J_11_35 29.20 0.00 1 + J_11_36 29.40 0.00 1 + J_11_37 29.60 5.00 1 + J_11_38 29.80 0.00 1 + J_11_39 30.00 0.00 1 + J_11_40 30.20 5.00 1 + J_11_41 30.40 0.00 1 + J_11_42 30.60 0.00 1 + J_11_43 30.80 5.00 1 + J_11_44 31.00 0.00 1 + J_12_00 22.40 5.00 1 + J_12_01 22.60 0.00 1 + J_12_02 22.80 0.00 1 + J_12_03 23.00 5.00 1 + J_12_04 23.20 0.00 1 + J_12_05 23.40 0.00 1 + J_12_06 23.60 5.00 1 + J_12_07 23.80 0.00 1 + J_12_08 24.00 0.00 1 + J_12_09 24.20 5.00 1 + J_12_10 24.40 0.00 1 + J_12_11 24.60 0.00 1 + J_12_12 24.80 5.00 1 + J_12_13 25.00 0.00 1 + J_12_14 25.20 0.00 1 + J_12_15 25.40 5.00 1 + J_12_16 25.60 0.00 1 + J_12_17 25.80 0.00 1 + J_12_18 26.00 5.00 1 + J_12_19 26.20 0.00 1 + J_12_20 26.40 0.00 1 + J_12_21 26.60 5.00 1 + J_12_22 26.80 0.00 1 + J_12_23 27.00 0.00 1 + J_12_24 27.20 5.00 1 + J_12_25 27.40 0.00 1 + J_12_26 27.60 0.00 1 + J_12_27 27.80 5.00 1 + J_12_28 28.00 0.00 1 + J_12_29 28.20 0.00 1 + J_12_30 28.40 5.00 1 + J_12_31 28.60 0.00 1 + J_12_32 28.80 0.00 1 + J_12_33 29.00 5.00 1 + J_12_34 29.20 0.00 1 + J_12_35 29.40 0.00 1 + J_12_36 29.60 5.00 1 + J_12_37 29.80 0.00 1 + J_12_38 30.00 0.00 1 + J_12_39 30.20 5.00 1 + J_12_40 30.40 0.00 1 + J_12_41 30.60 0.00 1 + J_12_42 30.80 5.00 1 + J_12_43 31.00 0.00 1 + J_12_44 31.20 0.00 1 + J_13_00 22.60 0.00 1 + J_13_01 22.80 0.00 1 + J_13_02 23.00 5.00 1 + J_13_03 23.20 0.00 1 + J_13_04 23.40 0.00 1 + J_13_05 23.60 5.00 1 + J_13_06 23.80 0.00 1 + J_13_07 24.00 0.00 1 + J_13_08 24.20 5.00 1 + J_13_09 24.40 0.00 1 + J_13_10 24.60 0.00 1 + J_13_11 24.80 5.00 1 + J_13_12 25.00 0.00 1 + J_13_13 25.20 0.00 1 + J_13_14 25.40 5.00 1 + J_13_15 25.60 0.00 1 + J_13_16 25.80 0.00 1 + J_13_17 26.00 5.00 1 + J_13_18 26.20 0.00 1 + J_13_19 26.40 0.00 1 + J_13_20 26.60 5.00 1 + J_13_21 26.80 0.00 1 + J_13_22 27.00 0.00 1 + J_13_23 27.20 5.00 1 + J_13_24 27.40 0.00 1 + J_13_25 27.60 0.00 1 + J_13_26 27.80 5.00 1 + J_13_27 28.00 0.00 1 + J_13_28 28.20 0.00 1 + J_13_29 28.40 5.00 1 + J_13_30 28.60 0.00 1 + J_13_31 28.80 0.00 1 + J_13_32 29.00 5.00 1 + J_13_33 29.20 0.00 1 + J_13_34 29.40 0.00 1 + J_13_35 29.60 5.00 1 + J_13_36 29.80 0.00 1 + J_13_37 30.00 0.00 1 + J_13_38 30.20 5.00 1 + J_13_39 30.40 0.00 1 + J_13_40 30.60 0.00 1 + J_13_41 30.80 5.00 1 + J_13_42 31.00 0.00 1 + J_13_43 31.20 0.00 1 + J_13_44 31.40 5.00 1 + J_14_00 22.80 0.00 1 + J_14_01 23.00 5.00 1 + J_14_02 23.20 0.00 1 + J_14_03 23.40 0.00 1 + J_14_04 23.60 5.00 1 + J_14_05 23.80 0.00 1 + J_14_06 24.00 0.00 1 + J_14_07 24.20 5.00 1 + J_14_08 24.40 0.00 1 + J_14_09 24.60 0.00 1 + J_14_10 24.80 5.00 1 + J_14_11 25.00 0.00 1 + J_14_12 25.20 0.00 1 + J_14_13 25.40 5.00 1 + J_14_14 25.60 0.00 1 + J_14_15 25.80 0.00 1 + J_14_16 26.00 5.00 1 + J_14_17 26.20 0.00 1 + J_14_18 26.40 0.00 1 + J_14_19 26.60 5.00 1 + J_14_20 26.80 0.00 1 + J_14_21 27.00 0.00 1 + J_14_22 27.20 5.00 1 + J_14_23 27.40 0.00 1 + J_14_24 27.60 0.00 1 + J_14_25 27.80 5.00 1 + J_14_26 28.00 0.00 1 + J_14_27 28.20 0.00 1 + J_14_28 28.40 5.00 1 + J_14_29 28.60 0.00 1 + J_14_30 28.80 0.00 1 + J_14_31 29.00 5.00 1 + J_14_32 29.20 0.00 1 + J_14_33 29.40 0.00 1 + J_14_34 29.60 5.00 1 + J_14_35 29.80 0.00 1 + J_14_36 30.00 0.00 1 + J_14_37 30.20 5.00 1 + J_14_38 30.40 0.00 1 + J_14_39 30.60 0.00 1 + J_14_40 30.80 5.00 1 + J_14_41 31.00 0.00 1 + J_14_42 31.20 0.00 1 + J_14_43 31.40 5.00 1 + J_14_44 31.60 0.00 1 + J_15_00 23.00 5.00 1 + J_15_01 23.20 0.00 1 + J_15_02 23.40 0.00 1 + J_15_03 23.60 5.00 1 + J_15_04 23.80 0.00 1 + J_15_05 24.00 0.00 1 + J_15_06 24.20 5.00 1 + J_15_07 24.40 0.00 1 + J_15_08 24.60 0.00 1 + J_15_09 24.80 5.00 1 + J_15_10 25.00 0.00 1 + J_15_11 25.20 0.00 1 + J_15_12 25.40 5.00 1 + J_15_13 25.60 0.00 1 + J_15_14 25.80 0.00 1 + J_15_15 26.00 5.00 1 + J_15_16 26.20 0.00 1 + J_15_17 26.40 0.00 1 + J_15_18 26.60 5.00 1 + J_15_19 26.80 0.00 1 + J_15_20 27.00 0.00 1 + J_15_21 27.20 5.00 1 + J_15_22 27.40 0.00 1 + J_15_23 27.60 0.00 1 + J_15_24 27.80 5.00 1 + J_15_25 28.00 0.00 1 + J_15_26 28.20 0.00 1 + J_15_27 28.40 5.00 1 + J_15_28 28.60 0.00 1 + J_15_29 28.80 0.00 1 + J_15_30 29.00 5.00 1 + J_15_31 29.20 0.00 1 + J_15_32 29.40 0.00 1 + J_15_33 29.60 5.00 1 + J_15_34 29.80 0.00 1 + J_15_35 30.00 0.00 1 + J_15_36 30.20 5.00 1 + J_15_37 30.40 0.00 1 + J_15_38 30.60 0.00 1 + J_15_39 30.80 5.00 1 + J_15_40 31.00 0.00 1 + J_15_41 31.20 0.00 1 + J_15_42 31.40 5.00 1 + J_15_43 31.60 0.00 1 + J_15_44 31.80 0.00 1 + J_16_00 23.20 0.00 1 + J_16_01 23.40 0.00 1 + J_16_02 23.60 5.00 1 + J_16_03 23.80 0.00 1 + J_16_04 24.00 0.00 1 + J_16_05 24.20 5.00 1 + J_16_06 24.40 0.00 1 + J_16_07 24.60 0.00 1 + J_16_08 24.80 5.00 1 + J_16_09 25.00 0.00 1 + J_16_10 25.20 0.00 1 + J_16_11 25.40 5.00 1 + J_16_12 25.60 0.00 1 + J_16_13 25.80 0.00 1 + J_16_14 26.00 5.00 1 + J_16_15 26.20 0.00 1 + J_16_16 26.40 0.00 1 + J_16_17 26.60 5.00 1 + J_16_18 26.80 0.00 1 + J_16_19 27.00 0.00 1 + J_16_20 27.20 5.00 1 + J_16_21 27.40 0.00 1 + J_16_22 27.60 0.00 1 + J_16_23 27.80 5.00 1 + J_16_24 28.00 0.00 1 + J_16_25 28.20 0.00 1 + J_16_26 28.40 5.00 1 + J_16_27 28.60 0.00 1 + J_16_28 28.80 0.00 1 + J_16_29 29.00 5.00 1 + J_16_30 29.20 0.00 1 + J_16_31 29.40 0.00 1 + J_16_32 29.60 5.00 1 + J_16_33 29.80 0.00 1 + J_16_34 30.00 0.00 1 + J_16_35 30.20 5.00 1 + J_16_36 30.40 0.00 1 + J_16_37 30.60 0.00 1 + J_16_38 30.80 5.00 1 + J_16_39 31.00 0.00 1 + J_16_40 31.20 0.00 1 + J_16_41 31.40 5.00 1 + J_16_42 31.60 0.00 1 + J_16_43 31.80 0.00 1 + J_16_44 32.00 5.00 1 + J_17_00 23.40 0.00 1 + J_17_01 23.60 5.00 1 + J_17_02 23.80 0.00 1 + J_17_03 24.00 0.00 1 + J_17_04 24.20 5.00 1 + J_17_05 24.40 0.00 1 + J_17_06 24.60 0.00 1 + J_17_07 24.80 5.00 1 + J_17_08 25.00 0.00 1 + J_17_09 25.20 0.00 1 + J_17_10 25.40 5.00 1 + J_17_11 25.60 0.00 1 + J_17_12 25.80 0.00 1 + J_17_13 26.00 5.00 1 + J_17_14 26.20 0.00 1 + J_17_15 26.40 0.00 1 + J_17_16 26.60 5.00 1 + J_17_17 26.80 0.00 1 + J_17_18 27.00 0.00 1 + J_17_19 27.20 5.00 1 + J_17_20 27.40 0.00 1 + J_17_21 27.60 0.00 1 + J_17_22 27.80 5.00 1 + J_17_23 28.00 0.00 1 + J_17_24 28.20 0.00 1 + J_17_25 28.40 5.00 1 + J_17_26 28.60 0.00 1 + J_17_27 28.80 0.00 1 + J_17_28 29.00 5.00 1 + J_17_29 29.20 0.00 1 + J_17_30 29.40 0.00 1 + J_17_31 29.60 5.00 1 + J_17_32 29.80 0.00 1 + J_17_33 30.00 0.00 1 + J_17_34 30.20 5.00 1 + J_17_35 30.40 0.00 1 + J_17_36 30.60 0.00 1 + J_17_37 30.80 5.00 1 + J_17_38 31.00 0.00 1 + J_17_39 31.20 0.00 1 + J_17_40 31.40 5.00 1 + J_17_41 31.60 0.00 1 + J_17_42 31.80 0.00 1 + J_17_43 32.00 5.00 1 + J_17_44 32.20 0.00 1 + J_18_00 23.60 5.00 1 + J_18_01 23.80 0.00 1 + J_18_02 24.00 0.00 1 + J_18_03 24.20 5.00 1 + J_18_04 24.40 0.00 1 + J_18_05 24.60 0.00 1 + J_18_06 24.80 5.00 1 + J_18_07 25.00 0.00 1 + J_18_08 25.20 0.00 1 + J_18_09 25.40 5.00 1 + J_18_10 25.60 0.00 1 + J_18_11 25.80 0.00 1 + J_18_12 26.00 5.00 1 + J_18_13 26.20 0.00 1 + J_18_14 26.40 0.00 1 + J_18_15 26.60 5.00 1 + J_18_16 26.80 0.00 1 + J_18_17 27.00 0.00 1 + J_18_18 27.20 5.00 1 + J_18_19 27.40 0.00 1 + J_18_20 27.60 0.00 1 + J_18_21 27.80 5.00 1 + J_18_22 28.00 0.00 1 + J_18_23 28.20 0.00 1 + J_18_24 28.40 5.00 1 + J_18_25 28.60 0.00 1 + J_18_26 28.80 0.00 1 + J_18_27 29.00 5.00 1 + J_18_28 29.20 0.00 1 + J_18_29 29.40 0.00 1 + J_18_30 29.60 5.00 1 + J_18_31 29.80 0.00 1 + J_18_32 30.00 0.00 1 + J_18_33 30.20 5.00 1 + J_18_34 30.40 0.00 1 + J_18_35 30.60 0.00 1 + J_18_36 30.80 5.00 1 + J_18_37 31.00 0.00 1 + J_18_38 31.20 0.00 1 + J_18_39 31.40 5.00 1 + J_18_40 31.60 0.00 1 + J_18_41 31.80 0.00 1 + J_18_42 32.00 5.00 1 + J_18_43 32.20 0.00 1 + J_18_44 32.40 0.00 1 + J_19_00 23.80 0.00 1 + J_19_01 24.00 0.00 1 + J_19_02 24.20 5.00 1 + J_19_03 24.40 0.00 1 + J_19_04 24.60 0.00 1 + J_19_05 24.80 5.00 1 + J_19_06 25.00 0.00 1 + J_19_07 25.20 0.00 1 + J_19_08 25.40 5.00 1 + J_19_09 25.60 0.00 1 + J_19_10 25.80 0.00 1 + J_19_11 26.00 5.00 1 + J_19_12 26.20 0.00 1 + J_19_13 26.40 0.00 1 + J_19_14 26.60 5.00 1 + J_19_15 26.80 0.00 1 + J_19_16 27.00 0.00 1 + J_19_17 27.20 5.00 1 + J_19_18 27.40 0.00 1 + J_19_19 27.60 0.00 1 + J_19_20 27.80 5.00 1 + J_19_21 28.00 0.00 1 + J_19_22 28.20 0.00 1 + J_19_23 28.40 5.00 1 + J_19_24 28.60 0.00 1 + J_19_25 28.80 0.00 1 + J_19_26 29.00 5.00 1 + J_19_27 29.20 0.00 1 + J_19_28 29.40 0.00 1 + J_19_29 29.60 5.00 1 + J_19_30 29.80 0.00 1 + J_19_31 30.00 0.00 1 + J_19_32 30.20 5.00 1 + J_19_33 30.40 0.00 1 + J_19_34 30.60 0.00 1 + J_19_35 30.80 5.00 1 + J_19_36 31.00 0.00 1 + J_19_37 31.20 0.00 1 + J_19_38 31.40 5.00 1 + J_19_39 31.60 0.00 1 + J_19_40 31.80 0.00 1 + J_19_41 32.00 5.00 1 + J_19_42 32.20 0.00 1 + J_19_43 32.40 0.00 1 + J_19_44 32.60 5.00 1 + J_20_00 24.00 0.00 1 + J_20_01 24.20 5.00 1 + J_20_02 24.40 0.00 1 + J_20_03 24.60 0.00 1 + J_20_04 24.80 5.00 1 + J_20_05 25.00 0.00 1 + J_20_06 25.20 0.00 1 + J_20_07 25.40 5.00 1 + J_20_08 25.60 0.00 1 + J_20_09 25.80 0.00 1 + J_20_10 26.00 5.00 1 + J_20_11 26.20 0.00 1 + J_20_12 26.40 0.00 1 + J_20_13 26.60 5.00 1 + J_20_14 26.80 0.00 1 + J_20_15 27.00 0.00 1 + J_20_16 27.20 5.00 1 + J_20_17 27.40 0.00 1 + J_20_18 27.60 0.00 1 + J_20_19 27.80 5.00 1 + J_20_20 28.00 0.00 1 + J_20_21 28.20 0.00 1 + J_20_22 28.40 5.00 1 + J_20_23 28.60 0.00 1 + J_20_24 28.80 0.00 1 + J_20_25 29.00 5.00 1 + J_20_26 29.20 0.00 1 + J_20_27 29.40 0.00 1 + J_20_28 29.60 5.00 1 + J_20_29 29.80 0.00 1 + J_20_30 30.00 0.00 1 + J_20_31 30.20 5.00 1 + J_20_32 30.40 0.00 1 + J_20_33 30.60 0.00 1 + J_20_34 30.80 5.00 1 + J_20_35 31.00 0.00 1 + J_20_36 31.20 0.00 1 + J_20_37 31.40 5.00 1 + J_20_38 31.60 0.00 1 + J_20_39 31.80 0.00 1 + J_20_40 32.00 5.00 1 + J_20_41 32.20 0.00 1 + J_20_42 32.40 0.00 1 + J_20_43 32.60 5.00 1 + J_20_44 32.80 0.00 1 + J_21_00 24.20 5.00 1 + J_21_01 24.40 0.00 1 + J_21_02 24.60 0.00 1 + J_21_03 24.80 5.00 1 + J_21_04 25.00 0.00 1 + J_21_05 25.20 0.00 1 + J_21_06 25.40 5.00 1 + J_21_07 25.60 0.00 1 + J_21_08 25.80 0.00 1 + J_21_09 26.00 5.00 1 + J_21_10 26.20 0.00 1 + J_21_11 26.40 0.00 1 + J_21_12 26.60 5.00 1 + J_21_13 26.80 0.00 1 + J_21_14 27.00 0.00 1 + J_21_15 27.20 5.00 1 + J_21_16 27.40 0.00 1 + J_21_17 27.60 0.00 1 + J_21_18 27.80 5.00 1 + J_21_19 28.00 0.00 1 + J_21_20 28.20 0.00 1 + J_21_21 28.40 5.00 1 + J_21_22 28.60 0.00 1 + J_21_23 28.80 0.00 1 + J_21_24 29.00 5.00 1 + J_21_25 29.20 0.00 1 + J_21_26 29.40 0.00 1 + J_21_27 29.60 5.00 1 + J_21_28 29.80 0.00 1 + J_21_29 30.00 0.00 1 + J_21_30 30.20 5.00 1 + J_21_31 30.40 0.00 1 + J_21_32 30.60 0.00 1 + J_21_33 30.80 5.00 1 + J_21_34 31.00 0.00 1 + J_21_35 31.20 0.00 1 + J_21_36 31.40 5.00 1 + J_21_37 31.60 0.00 1 + J_21_38 31.80 0.00 1 + J_21_39 32.00 5.00 1 + J_21_40 32.20 0.00 1 + J_21_41 32.40 0.00 1 + J_21_42 32.60 5.00 1 + J_21_43 32.80 0.00 1 + J_21_44 33.00 0.00 1 + J_22_00 24.40 0.00 1 + J_22_01 24.60 0.00 1 + J_22_02 24.80 5.00 1 + J_22_03 25.00 0.00 1 + J_22_04 25.20 0.00 1 + J_22_05 25.40 5.00 1 + J_22_06 25.60 0.00 1 + J_22_07 25.80 0.00 1 + J_22_08 26.00 5.00 1 + J_22_09 26.20 0.00 1 + J_22_10 26.40 0.00 1 + J_22_11 26.60 5.00 1 + J_22_12 26.80 0.00 1 + J_22_13 27.00 0.00 1 + J_22_14 27.20 5.00 1 + J_22_15 27.40 0.00 1 + J_22_16 27.60 0.00 1 + J_22_17 27.80 5.00 1 + J_22_18 28.00 0.00 1 + J_22_19 28.20 0.00 1 + J_22_20 28.40 5.00 1 + J_22_21 28.60 0.00 1 + J_22_22 28.80 0.00 1 + J_22_23 29.00 5.00 1 + J_22_24 29.20 0.00 1 + J_22_25 29.40 0.00 1 + J_22_26 29.60 5.00 1 + J_22_27 29.80 0.00 1 + J_22_28 30.00 0.00 1 + J_22_29 30.20 5.00 1 + J_22_30 30.40 0.00 1 + J_22_31 30.60 0.00 1 + J_22_32 30.80 5.00 1 + J_22_33 31.00 0.00 1 + J_22_34 31.20 0.00 1 + J_22_35 31.40 5.00 1 + J_22_36 31.60 0.00 1 + J_22_37 31.80 0.00 1 + J_22_38 32.00 5.00 1 + J_22_39 32.20 0.00 1 + J_22_40 32.40 0.00 1 + J_22_41 32.60 5.00 1 + J_22_42 32.80 0.00 1 + J_22_43 33.00 0.00 1 + J_22_44 33.20 5.00 1 + J_23_00 24.60 0.00 1 + J_23_01 24.80 5.00 1 + J_23_02 25.00 0.00 1 + J_23_03 25.20 0.00 1 + J_23_04 25.40 5.00 1 + J_23_05 25.60 0.00 1 + J_23_06 25.80 0.00 1 + J_23_07 26.00 5.00 1 + J_23_08 26.20 0.00 1 + J_23_09 26.40 0.00 1 + J_23_10 26.60 5.00 1 + J_23_11 26.80 0.00 1 + J_23_12 27.00 0.00 1 + J_23_13 27.20 5.00 1 + J_23_14 27.40 0.00 1 + J_23_15 27.60 0.00 1 + J_23_16 27.80 5.00 1 + J_23_17 28.00 0.00 1 + J_23_18 28.20 0.00 1 + J_23_19 28.40 5.00 1 + J_23_20 28.60 0.00 1 + J_23_21 28.80 0.00 1 + J_23_22 29.00 5.00 1 + J_23_23 29.20 0.00 1 + J_23_24 29.40 0.00 1 + J_23_25 29.60 5.00 1 + J_23_26 29.80 0.00 1 + J_23_27 30.00 0.00 1 + J_23_28 30.20 5.00 1 + J_23_29 30.40 0.00 1 + J_23_30 30.60 0.00 1 + J_23_31 30.80 5.00 1 + J_23_32 31.00 0.00 1 + J_23_33 31.20 0.00 1 + J_23_34 31.40 5.00 1 + J_23_35 31.60 0.00 1 + J_23_36 31.80 0.00 1 + J_23_37 32.00 5.00 1 + J_23_38 32.20 0.00 1 + J_23_39 32.40 0.00 1 + J_23_40 32.60 5.00 1 + J_23_41 32.80 0.00 1 + J_23_42 33.00 0.00 1 + J_23_43 33.20 5.00 1 + J_23_44 33.40 0.00 1 + J_24_00 24.80 5.00 1 + J_24_01 25.00 0.00 1 + J_24_02 25.20 0.00 1 + J_24_03 25.40 5.00 1 + J_24_04 25.60 0.00 1 + J_24_05 25.80 0.00 1 + J_24_06 26.00 5.00 1 + J_24_07 26.20 0.00 1 + J_24_08 26.40 0.00 1 + J_24_09 26.60 5.00 1 + J_24_10 26.80 0.00 1 + J_24_11 27.00 0.00 1 + J_24_12 27.20 5.00 1 + J_24_13 27.40 0.00 1 + J_24_14 27.60 0.00 1 + J_24_15 27.80 5.00 1 + J_24_16 28.00 0.00 1 + J_24_17 28.20 0.00 1 + J_24_18 28.40 5.00 1 + J_24_19 28.60 0.00 1 + J_24_20 28.80 0.00 1 + J_24_21 29.00 5.00 1 + J_24_22 29.20 0.00 1 + J_24_23 29.40 0.00 1 + J_24_24 29.60 5.00 1 + J_24_25 29.80 0.00 1 + J_24_26 30.00 0.00 1 + J_24_27 30.20 5.00 1 + J_24_28 30.40 0.00 1 + J_24_29 30.60 0.00 1 + J_24_30 30.80 5.00 1 + J_24_31 31.00 0.00 1 + J_24_32 31.20 0.00 1 + J_24_33 31.40 5.00 1 + J_24_34 31.60 0.00 1 + J_24_35 31.80 0.00 1 + J_24_36 32.00 5.00 1 + J_24_37 32.20 0.00 1 + J_24_38 32.40 0.00 1 + J_24_39 32.60 5.00 1 + J_24_40 32.80 0.00 1 + J_24_41 33.00 0.00 1 + J_24_42 33.20 5.00 1 + J_24_43 33.40 0.00 1 + J_24_44 33.60 0.00 1 + J_25_00 25.00 0.00 1 + J_25_01 25.20 0.00 1 + J_25_02 25.40 5.00 1 + J_25_03 25.60 0.00 1 + J_25_04 25.80 0.00 1 + J_25_05 26.00 5.00 1 + J_25_06 26.20 0.00 1 + J_25_07 26.40 0.00 1 + J_25_08 26.60 5.00 1 + J_25_09 26.80 0.00 1 + J_25_10 27.00 0.00 1 + J_25_11 27.20 5.00 1 + J_25_12 27.40 0.00 1 + J_25_13 27.60 0.00 1 + J_25_14 27.80 5.00 1 + J_25_15 28.00 0.00 1 + J_25_16 28.20 0.00 1 + J_25_17 28.40 5.00 1 + J_25_18 28.60 0.00 1 + J_25_19 28.80 0.00 1 + J_25_20 29.00 5.00 1 + J_25_21 29.20 0.00 1 + J_25_22 29.40 0.00 1 + J_25_23 29.60 5.00 1 + J_25_24 29.80 0.00 1 + J_25_25 30.00 0.00 1 + J_25_26 30.20 5.00 1 + J_25_27 30.40 0.00 1 + J_25_28 30.60 0.00 1 + J_25_29 30.80 5.00 1 + J_25_30 31.00 0.00 1 + J_25_31 31.20 0.00 1 + J_25_32 31.40 5.00 1 + J_25_33 31.60 0.00 1 + J_25_34 31.80 0.00 1 + J_25_35 32.00 5.00 1 + J_25_36 32.20 0.00 1 + J_25_37 32.40 0.00 1 + J_25_38 32.60 5.00 1 + J_25_39 32.80 0.00 1 + J_25_40 33.00 0.00 1 + J_25_41 33.20 5.00 1 + J_25_42 33.40 0.00 1 + J_25_43 33.60 0.00 1 + J_25_44 33.80 5.00 1 + J_26_00 25.20 0.00 1 + J_26_01 25.40 5.00 1 + J_26_02 25.60 0.00 1 + J_26_03 25.80 0.00 1 + J_26_04 26.00 5.00 1 + J_26_05 26.20 0.00 1 + J_26_06 26.40 0.00 1 + J_26_07 26.60 5.00 1 + J_26_08 26.80 0.00 1 + J_26_09 27.00 0.00 1 + J_26_10 27.20 5.00 1 + J_26_11 27.40 0.00 1 + J_26_12 27.60 0.00 1 + J_26_13 27.80 5.00 1 + J_26_14 28.00 0.00 1 + J_26_15 28.20 0.00 1 + J_26_16 28.40 5.00 1 + J_26_17 28.60 0.00 1 + J_26_18 28.80 0.00 1 + J_26_19 29.00 5.00 1 + J_26_20 29.20 0.00 1 + J_26_21 29.40 0.00 1 + J_26_22 29.60 5.00 1 + J_26_23 29.80 0.00 1 + J_26_24 30.00 0.00 1 + J_26_25 30.20 5.00 1 + J_26_26 30.40 0.00 1 + J_26_27 30.60 0.00 1 + J_26_28 30.80 5.00 1 + J_26_29 31.00 0.00 1 + J_26_30 31.20 0.00 1 + J_26_31 31.40 5.00 1 + J_26_32 31.60 0.00 1 + J_26_33 31.80 0.00 1 + J_26_34 32.00 5.00 1 + J_26_35 32.20 0.00 1 + J_26_36 32.40 0.00 1 + J_26_37 32.60 5.00 1 + J_26_38 32.80 0.00 1 + J_26_39 33.00 0.00 1 + J_26_40 33.20 5.00 1 + J_26_41 33.40 0.00 1 + J_26_42 33.60 0.00 1 + J_26_43 33.80 5.00 1 + J_26_44 34.00 0.00 1 + J_27_00 25.40 5.00 1 + J_27_01 25.60 0.00 1 + J_27_02 25.80 0.00 1 + J_27_03 26.00 5.00 1 + J_27_04 26.20 0.00 1 + J_27_05 26.40 0.00 1 + J_27_06 26.60 5.00 1 + J_27_07 26.80 0.00 1 + J_27_08 27.00 0.00 1 + J_27_09 27.20 5.00 1 + J_27_10 27.40 0.00 1 + J_27_11 27.60 0.00 1 + J_27_12 27.80 5.00 1 + J_27_13 28.00 0.00 1 + J_27_14 28.20 0.00 1 + J_27_15 28.40 5.00 1 + J_27_16 28.60 0.00 1 + J_27_17 28.80 0.00 1 + J_27_18 29.00 5.00 1 + J_27_19 29.20 0.00 1 + J_27_20 29.40 0.00 1 + J_27_21 29.60 5.00 1 + J_27_22 29.80 0.00 1 + J_27_23 30.00 0.00 1 + J_27_24 30.20 5.00 1 + J_27_25 30.40 0.00 1 + J_27_26 30.60 0.00 1 + J_27_27 30.80 5.00 1 + J_27_28 31.00 0.00 1 + J_27_29 31.20 0.00 1 + J_27_30 31.40 5.00 1 + J_27_31 31.60 0.00 1 + J_27_32 31.80 0.00 1 + J_27_33 32.00 5.00 1 + J_27_34 32.20 0.00 1 + J_27_35 32.40 0.00 1 + J_27_36 32.60 5.00 1 + J_27_37 32.80 0.00 1 + J_27_38 33.00 0.00 1 + J_27_39 33.20 5.00 1 + J_27_40 33.40 0.00 1 + J_27_41 33.60 0.00 1 + J_27_42 33.80 5.00 1 + J_27_43 34.00 0.00 1 + J_27_44 34.20 0.00 1 + J_28_00 25.60 0.00 1 + J_28_01 25.80 0.00 1 + J_28_02 26.00 5.00 1 + J_28_03 26.20 0.00 1 + J_28_04 26.40 0.00 1 + J_28_05 26.60 5.00 1 + J_28_06 26.80 0.00 1 + J_28_07 27.00 0.00 1 + J_28_08 27.20 5.00 1 + J_28_09 27.40 0.00 1 + J_28_10 27.60 0.00 1 + J_28_11 27.80 5.00 1 + J_28_12 28.00 0.00 1 + J_28_13 28.20 0.00 1 + J_28_14 28.40 5.00 1 + J_28_15 28.60 0.00 1 + J_28_16 28.80 0.00 1 + J_28_17 29.00 5.00 1 + J_28_18 29.20 0.00 1 + J_28_19 29.40 0.00 1 + J_28_20 29.60 5.00 1 + J_28_21 29.80 0.00 1 + J_28_22 30.00 0.00 1 + J_28_23 30.20 5.00 1 + J_28_24 30.40 0.00 1 + J_28_25 30.60 0.00 1 + J_28_26 30.80 5.00 1 + J_28_27 31.00 0.00 1 + J_28_28 31.20 0.00 1 + J_28_29 31.40 5.00 1 + J_28_30 31.60 0.00 1 + J_28_31 31.80 0.00 1 + J_28_32 32.00 5.00 1 + J_28_33 32.20 0.00 1 + J_28_34 32.40 0.00 1 + J_28_35 32.60 5.00 1 + J_28_36 32.80 0.00 1 + J_28_37 33.00 0.00 1 + J_28_38 33.20 5.00 1 + J_28_39 33.40 0.00 1 + J_28_40 33.60 0.00 1 + J_28_41 33.80 5.00 1 + J_28_42 34.00 0.00 1 + J_28_43 34.20 0.00 1 + J_28_44 34.40 5.00 1 + J_29_00 25.80 0.00 1 + J_29_01 26.00 5.00 1 + J_29_02 26.20 0.00 1 + J_29_03 26.40 0.00 1 + J_29_04 26.60 5.00 1 + J_29_05 26.80 0.00 1 + J_29_06 27.00 0.00 1 + J_29_07 27.20 5.00 1 + J_29_08 27.40 0.00 1 + J_29_09 27.60 0.00 1 + J_29_10 27.80 5.00 1 + J_29_11 28.00 0.00 1 + J_29_12 28.20 0.00 1 + J_29_13 28.40 5.00 1 + J_29_14 28.60 0.00 1 + J_29_15 28.80 0.00 1 + J_29_16 29.00 5.00 1 + J_29_17 29.20 0.00 1 + J_29_18 29.40 0.00 1 + J_29_19 29.60 5.00 1 + J_29_20 29.80 0.00 1 + J_29_21 30.00 0.00 1 + J_29_22 30.20 5.00 1 + J_29_23 30.40 0.00 1 + J_29_24 30.60 0.00 1 + J_29_25 30.80 5.00 1 + J_29_26 31.00 0.00 1 + J_29_27 31.20 0.00 1 + J_29_28 31.40 5.00 1 + J_29_29 31.60 0.00 1 + J_29_30 31.80 0.00 1 + J_29_31 32.00 5.00 1 + J_29_32 32.20 0.00 1 + J_29_33 32.40 0.00 1 + J_29_34 32.60 5.00 1 + J_29_35 32.80 0.00 1 + J_29_36 33.00 0.00 1 + J_29_37 33.20 5.00 1 + J_29_38 33.40 0.00 1 + J_29_39 33.60 0.00 1 + J_29_40 33.80 5.00 1 + J_29_41 34.00 0.00 1 + J_29_42 34.20 0.00 1 + J_29_43 34.40 5.00 1 + J_29_44 34.60 0.00 1 + J_30_00 26.00 5.00 1 + J_30_01 26.20 0.00 1 + J_30_02 26.40 0.00 1 + J_30_03 26.60 5.00 1 + J_30_04 26.80 0.00 1 + J_30_05 27.00 0.00 1 + J_30_06 27.20 5.00 1 + J_30_07 27.40 0.00 1 + J_30_08 27.60 0.00 1 + J_30_09 27.80 5.00 1 + J_30_10 28.00 0.00 1 + J_30_11 28.20 0.00 1 + J_30_12 28.40 5.00 1 + J_30_13 28.60 0.00 1 + J_30_14 28.80 0.00 1 + J_30_15 29.00 5.00 1 + J_30_16 29.20 0.00 1 + J_30_17 29.40 0.00 1 + J_30_18 29.60 5.00 1 + J_30_19 29.80 0.00 1 + J_30_20 30.00 0.00 1 + J_30_21 30.20 5.00 1 + J_30_22 30.40 0.00 1 + J_30_23 30.60 0.00 1 + J_30_24 30.80 5.00 1 + J_30_25 31.00 0.00 1 + J_30_26 31.20 0.00 1 + J_30_27 31.40 5.00 1 + J_30_28 31.60 0.00 1 + J_30_29 31.80 0.00 1 + J_30_30 32.00 5.00 1 + J_30_31 32.20 0.00 1 + J_30_32 32.40 0.00 1 + J_30_33 32.60 5.00 1 + J_30_34 32.80 0.00 1 + J_30_35 33.00 0.00 1 + J_30_36 33.20 5.00 1 + J_30_37 33.40 0.00 1 + J_30_38 33.60 0.00 1 + J_30_39 33.80 5.00 1 + J_30_40 34.00 0.00 1 + J_30_41 34.20 0.00 1 + J_30_42 34.40 5.00 1 + J_30_43 34.60 0.00 1 + J_30_44 34.80 0.00 1 + J_31_00 26.20 0.00 1 + J_31_01 26.40 0.00 1 + J_31_02 26.60 5.00 1 + J_31_03 26.80 0.00 1 + J_31_04 27.00 0.00 1 + J_31_05 27.20 5.00 1 + J_31_06 27.40 0.00 1 + J_31_07 27.60 0.00 1 + J_31_08 27.80 5.00 1 + J_31_09 28.00 0.00 1 + J_31_10 28.20 0.00 1 + J_31_11 28.40 5.00 1 + J_31_12 28.60 0.00 1 + J_31_13 28.80 0.00 1 + J_31_14 29.00 5.00 1 + J_31_15 29.20 0.00 1 + J_31_16 29.40 0.00 1 + J_31_17 29.60 5.00 1 + J_31_18 29.80 0.00 1 + J_31_19 30.00 0.00 1 + J_31_20 30.20 5.00 1 + J_31_21 30.40 0.00 1 + J_31_22 30.60 0.00 1 + J_31_23 30.80 5.00 1 + J_31_24 31.00 0.00 1 + J_31_25 31.20 0.00 1 + J_31_26 31.40 5.00 1 + J_31_27 31.60 0.00 1 + J_31_28 31.80 0.00 1 + J_31_29 32.00 5.00 1 + J_31_30 32.20 0.00 1 + J_31_31 32.40 0.00 1 + J_31_32 32.60 5.00 1 + J_31_33 32.80 0.00 1 + J_31_34 33.00 0.00 1 + J_31_35 33.20 5.00 1 + J_31_36 33.40 0.00 1 + J_31_37 33.60 0.00 1 + J_31_38 33.80 5.00 1 + J_31_39 34.00 0.00 1 + J_31_40 34.20 0.00 1 + J_31_41 34.40 5.00 1 + J_31_42 34.60 0.00 1 + J_31_43 34.80 0.00 1 + J_31_44 35.00 5.00 1 + J_32_00 26.40 0.00 1 + J_32_01 26.60 5.00 1 + J_32_02 26.80 0.00 1 + J_32_03 27.00 0.00 1 + J_32_04 27.20 5.00 1 + J_32_05 27.40 0.00 1 + J_32_06 27.60 0.00 1 + J_32_07 27.80 5.00 1 + J_32_08 28.00 0.00 1 + J_32_09 28.20 0.00 1 + J_32_10 28.40 5.00 1 + J_32_11 28.60 0.00 1 + J_32_12 28.80 0.00 1 + J_32_13 29.00 5.00 1 + J_32_14 29.20 0.00 1 + J_32_15 29.40 0.00 1 + J_32_16 29.60 5.00 1 + J_32_17 29.80 0.00 1 + J_32_18 30.00 0.00 1 + J_32_19 30.20 5.00 1 + J_32_20 30.40 0.00 1 + J_32_21 30.60 0.00 1 + J_32_22 30.80 5.00 1 + J_32_23 31.00 0.00 1 + J_32_24 31.20 0.00 1 + J_32_25 31.40 5.00 1 + J_32_26 31.60 0.00 1 + J_32_27 31.80 0.00 1 + J_32_28 32.00 5.00 1 + J_32_29 32.20 0.00 1 + J_32_30 32.40 0.00 1 + J_32_31 32.60 5.00 1 + J_32_32 32.80 0.00 1 + J_32_33 33.00 0.00 1 + J_32_34 33.20 5.00 1 + J_32_35 33.40 0.00 1 + J_32_36 33.60 0.00 1 + J_32_37 33.80 5.00 1 + J_32_38 34.00 0.00 1 + J_32_39 34.20 0.00 1 + J_32_40 34.40 5.00 1 + J_32_41 34.60 0.00 1 + J_32_42 34.80 0.00 1 + J_32_43 35.00 5.00 1 + J_32_44 35.20 0.00 1 + J_33_00 26.60 5.00 1 + J_33_01 26.80 0.00 1 + J_33_02 27.00 0.00 1 + J_33_03 27.20 5.00 1 + J_33_04 27.40 0.00 1 + J_33_05 27.60 0.00 1 + J_33_06 27.80 5.00 1 + J_33_07 28.00 0.00 1 + J_33_08 28.20 0.00 1 + J_33_09 28.40 5.00 1 + J_33_10 28.60 0.00 1 + J_33_11 28.80 0.00 1 + J_33_12 29.00 5.00 1 + J_33_13 29.20 0.00 1 + J_33_14 29.40 0.00 1 + J_33_15 29.60 5.00 1 + J_33_16 29.80 0.00 1 + J_33_17 30.00 0.00 1 + J_33_18 30.20 5.00 1 + J_33_19 30.40 0.00 1 + J_33_20 30.60 0.00 1 + J_33_21 30.80 5.00 1 + J_33_22 31.00 0.00 1 + J_33_23 31.20 0.00 1 + J_33_24 31.40 5.00 1 + J_33_25 31.60 0.00 1 + J_33_26 31.80 0.00 1 + J_33_27 32.00 5.00 1 + J_33_28 32.20 0.00 1 + J_33_29 32.40 0.00 1 + J_33_30 32.60 5.00 1 + J_33_31 32.80 0.00 1 + J_33_32 33.00 0.00 1 + J_33_33 33.20 5.00 1 + J_33_34 33.40 0.00 1 + J_33_35 33.60 0.00 1 + J_33_36 33.80 5.00 1 + J_33_37 34.00 0.00 1 + J_33_38 34.20 0.00 1 + J_33_39 34.40 5.00 1 + J_33_40 34.60 0.00 1 + J_33_41 34.80 0.00 1 + J_33_42 35.00 5.00 1 + J_33_43 35.20 0.00 1 + J_33_44 35.40 0.00 1 + J_34_00 26.80 0.00 1 + J_34_01 27.00 0.00 1 + J_34_02 27.20 5.00 1 + J_34_03 27.40 0.00 1 + J_34_04 27.60 0.00 1 + J_34_05 27.80 5.00 1 + J_34_06 28.00 0.00 1 + J_34_07 28.20 0.00 1 + J_34_08 28.40 5.00 1 + J_34_09 28.60 0.00 1 + J_34_10 28.80 0.00 1 + J_34_11 29.00 5.00 1 + J_34_12 29.20 0.00 1 + J_34_13 29.40 0.00 1 + J_34_14 29.60 5.00 1 + J_34_15 29.80 0.00 1 + J_34_16 30.00 0.00 1 + J_34_17 30.20 5.00 1 + J_34_18 30.40 0.00 1 + J_34_19 30.60 0.00 1 + J_34_20 30.80 5.00 1 + J_34_21 31.00 0.00 1 + J_34_22 31.20 0.00 1 + J_34_23 31.40 5.00 1 + J_34_24 31.60 0.00 1 + J_34_25 31.80 0.00 1 + J_34_26 32.00 5.00 1 + J_34_27 32.20 0.00 1 + J_34_28 32.40 0.00 1 + J_34_29 32.60 5.00 1 + J_34_30 32.80 0.00 1 + J_34_31 33.00 0.00 1 + J_34_32 33.20 5.00 1 + J_34_33 33.40 0.00 1 + J_34_34 33.60 0.00 1 + J_34_35 33.80 5.00 1 + J_34_36 34.00 0.00 1 + J_34_37 34.20 0.00 1 + J_34_38 34.40 5.00 1 + J_34_39 34.60 0.00 1 + J_34_40 34.80 0.00 1 + J_34_41 35.00 5.00 1 + J_34_42 35.20 0.00 1 + J_34_43 35.40 0.00 1 + J_34_44 35.60 5.00 1 + J_35_00 27.00 0.00 1 + J_35_01 27.20 5.00 1 + J_35_02 27.40 0.00 1 + J_35_03 27.60 0.00 1 + J_35_04 27.80 5.00 1 + J_35_05 28.00 0.00 1 + J_35_06 28.20 0.00 1 + J_35_07 28.40 5.00 1 + J_35_08 28.60 0.00 1 + J_35_09 28.80 0.00 1 + J_35_10 29.00 5.00 1 + J_35_11 29.20 0.00 1 + J_35_12 29.40 0.00 1 + J_35_13 29.60 5.00 1 + J_35_14 29.80 0.00 1 + J_35_15 30.00 0.00 1 + J_35_16 30.20 5.00 1 + J_35_17 30.40 0.00 1 + J_35_18 30.60 0.00 1 + J_35_19 30.80 5.00 1 + J_35_20 31.00 0.00 1 + J_35_21 31.20 0.00 1 + J_35_22 31.40 5.00 1 + J_35_23 31.60 0.00 1 + J_35_24 31.80 0.00 1 + J_35_25 32.00 5.00 1 + J_35_26 32.20 0.00 1 + J_35_27 32.40 0.00 1 + J_35_28 32.60 5.00 1 + J_35_29 32.80 0.00 1 + J_35_30 33.00 0.00 1 + J_35_31 33.20 5.00 1 + J_35_32 33.40 0.00 1 + J_35_33 33.60 0.00 1 + J_35_34 33.80 5.00 1 + J_35_35 34.00 0.00 1 + J_35_36 34.20 0.00 1 + J_35_37 34.40 5.00 1 + J_35_38 34.60 0.00 1 + J_35_39 34.80 0.00 1 + J_35_40 35.00 5.00 1 + J_35_41 35.20 0.00 1 + J_35_42 35.40 0.00 1 + J_35_43 35.60 5.00 1 + J_35_44 35.80 0.00 1 + J_36_00 27.20 5.00 1 + J_36_01 27.40 0.00 1 + J_36_02 27.60 0.00 1 + J_36_03 27.80 5.00 1 + J_36_04 28.00 0.00 1 + J_36_05 28.20 0.00 1 + J_36_06 28.40 5.00 1 + J_36_07 28.60 0.00 1 + J_36_08 28.80 0.00 1 + J_36_09 29.00 5.00 1 + J_36_10 29.20 0.00 1 + J_36_11 29.40 0.00 1 + J_36_12 29.60 5.00 1 + J_36_13 29.80 0.00 1 + J_36_14 30.00 0.00 1 + J_36_15 30.20 5.00 1 + J_36_16 30.40 0.00 1 + J_36_17 30.60 0.00 1 + J_36_18 30.80 5.00 1 + J_36_19 31.00 0.00 1 + J_36_20 31.20 0.00 1 + J_36_21 31.40 5.00 1 + J_36_22 31.60 0.00 1 + J_36_23 31.80 0.00 1 + J_36_24 32.00 5.00 1 + J_36_25 32.20 0.00 1 + J_36_26 32.40 0.00 1 + J_36_27 32.60 5.00 1 + J_36_28 32.80 0.00 1 + J_36_29 33.00 0.00 1 + J_36_30 33.20 5.00 1 + J_36_31 33.40 0.00 1 + J_36_32 33.60 0.00 1 + J_36_33 33.80 5.00 1 + J_36_34 34.00 0.00 1 + J_36_35 34.20 0.00 1 + J_36_36 34.40 5.00 1 + J_36_37 34.60 0.00 1 + J_36_38 34.80 0.00 1 + J_36_39 35.00 5.00 1 + J_36_40 35.20 0.00 1 + J_36_41 35.40 0.00 1 + J_36_42 35.60 5.00 1 + J_36_43 35.80 0.00 1 + J_36_44 36.00 0.00 1 + J_37_00 27.40 0.00 1 + J_37_01 27.60 0.00 1 + J_37_02 27.80 5.00 1 + J_37_03 28.00 0.00 1 + J_37_04 28.20 0.00 1 + J_37_05 28.40 5.00 1 + J_37_06 28.60 0.00 1 + J_37_07 28.80 0.00 1 + J_37_08 29.00 5.00 1 + J_37_09 29.20 0.00 1 + J_37_10 29.40 0.00 1 + J_37_11 29.60 5.00 1 + J_37_12 29.80 0.00 1 + J_37_13 30.00 0.00 1 + J_37_14 30.20 5.00 1 + J_37_15 30.40 0.00 1 + J_37_16 30.60 0.00 1 + J_37_17 30.80 5.00 1 + J_37_18 31.00 0.00 1 + J_37_19 31.20 0.00 1 + J_37_20 31.40 5.00 1 + J_37_21 31.60 0.00 1 + J_37_22 31.80 0.00 1 + J_37_23 32.00 5.00 1 + J_37_24 32.20 0.00 1 + J_37_25 32.40 0.00 1 + J_37_26 32.60 5.00 1 + J_37_27 32.80 0.00 1 + J_37_28 33.00 0.00 1 + J_37_29 33.20 5.00 1 + J_37_30 33.40 0.00 1 + J_37_31 33.60 0.00 1 + J_37_32 33.80 5.00 1 + J_37_33 34.00 0.00 1 + J_37_34 34.20 0.00 1 + J_37_35 34.40 5.00 1 + J_37_36 34.60 0.00 1 + J_37_37 34.80 0.00 1 + J_37_38 35.00 5.00 1 + J_37_39 35.20 0.00 1 + J_37_40 35.40 0.00 1 + J_37_41 35.60 5.00 1 + J_37_42 35.80 0.00 1 + J_37_43 36.00 0.00 1 + J_37_44 36.20 5.00 1 + J_38_00 27.60 0.00 1 + J_38_01 27.80 5.00 1 + J_38_02 28.00 0.00 1 + J_38_03 28.20 0.00 1 + J_38_04 28.40 5.00 1 + J_38_05 28.60 0.00 1 + J_38_06 28.80 0.00 1 + J_38_07 29.00 5.00 1 + J_38_08 29.20 0.00 1 + J_38_09 29.40 0.00 1 + J_38_10 29.60 5.00 1 + J_38_11 29.80 0.00 1 + J_38_12 30.00 0.00 1 + J_38_13 30.20 5.00 1 + J_38_14 30.40 0.00 1 + J_38_15 30.60 0.00 1 + J_38_16 30.80 5.00 1 + J_38_17 31.00 0.00 1 + J_38_18 31.20 0.00 1 + J_38_19 31.40 5.00 1 + J_38_20 31.60 0.00 1 + J_38_21 31.80 0.00 1 + J_38_22 32.00 5.00 1 + J_38_23 32.20 0.00 1 + J_38_24 32.40 0.00 1 + J_38_25 32.60 5.00 1 + J_38_26 32.80 0.00 1 + J_38_27 33.00 0.00 1 + J_38_28 33.20 5.00 1 + J_38_29 33.40 0.00 1 + J_38_30 33.60 0.00 1 + J_38_31 33.80 5.00 1 + J_38_32 34.00 0.00 1 + J_38_33 34.20 0.00 1 + J_38_34 34.40 5.00 1 + J_38_35 34.60 0.00 1 + J_38_36 34.80 0.00 1 + J_38_37 35.00 5.00 1 + J_38_38 35.20 0.00 1 + J_38_39 35.40 0.00 1 + J_38_40 35.60 5.00 1 + J_38_41 35.80 0.00 1 + J_38_42 36.00 0.00 1 + J_38_43 36.20 5.00 1 + J_38_44 36.40 0.00 1 + J_39_00 27.80 5.00 1 + J_39_01 28.00 0.00 1 + J_39_02 28.20 0.00 1 + J_39_03 28.40 5.00 1 + J_39_04 28.60 0.00 1 + J_39_05 28.80 0.00 1 + J_39_06 29.00 5.00 1 + J_39_07 29.20 0.00 1 + J_39_08 29.40 0.00 1 + J_39_09 29.60 5.00 1 + J_39_10 29.80 0.00 1 + J_39_11 30.00 0.00 1 + J_39_12 30.20 5.00 1 + J_39_13 30.40 0.00 1 + J_39_14 30.60 0.00 1 + J_39_15 30.80 5.00 1 + J_39_16 31.00 0.00 1 + J_39_17 31.20 0.00 1 + J_39_18 31.40 5.00 1 + J_39_19 31.60 0.00 1 + J_39_20 31.80 0.00 1 + J_39_21 32.00 5.00 1 + J_39_22 32.20 0.00 1 + J_39_23 32.40 0.00 1 + J_39_24 32.60 5.00 1 + J_39_25 32.80 0.00 1 + J_39_26 33.00 0.00 1 + J_39_27 33.20 5.00 1 + J_39_28 33.40 0.00 1 + J_39_29 33.60 0.00 1 + J_39_30 33.80 5.00 1 + J_39_31 34.00 0.00 1 + J_39_32 34.20 0.00 1 + J_39_33 34.40 5.00 1 + J_39_34 34.60 0.00 1 + J_39_35 34.80 0.00 1 + J_39_36 35.00 5.00 1 + J_39_37 35.20 0.00 1 + J_39_38 35.40 0.00 1 + J_39_39 35.60 5.00 1 + J_39_40 35.80 0.00 1 + J_39_41 36.00 0.00 1 + J_39_42 36.20 5.00 1 + J_39_43 36.40 0.00 1 + J_39_44 36.60 0.00 1 + J_40_00 28.00 0.00 1 + J_40_01 28.20 0.00 1 + J_40_02 28.40 5.00 1 + J_40_03 28.60 0.00 1 + J_40_04 28.80 0.00 1 + J_40_05 29.00 5.00 1 + J_40_06 29.20 0.00 1 + J_40_07 29.40 0.00 1 + J_40_08 29.60 5.00 1 + J_40_09 29.80 0.00 1 + J_40_10 30.00 0.00 1 + J_40_11 30.20 5.00 1 + J_40_12 30.40 0.00 1 + J_40_13 30.60 0.00 1 + J_40_14 30.80 5.00 1 + J_40_15 31.00 0.00 1 + J_40_16 31.20 0.00 1 + J_40_17 31.40 5.00 1 + J_40_18 31.60 0.00 1 + J_40_19 31.80 0.00 1 + J_40_20 32.00 5.00 1 + J_40_21 32.20 0.00 1 + J_40_22 32.40 0.00 1 + J_40_23 32.60 5.00 1 + J_40_24 32.80 0.00 1 + J_40_25 33.00 0.00 1 + J_40_26 33.20 5.00 1 + J_40_27 33.40 0.00 1 + J_40_28 33.60 0.00 1 + J_40_29 33.80 5.00 1 + J_40_30 34.00 0.00 1 + J_40_31 34.20 0.00 1 + J_40_32 34.40 5.00 1 + J_40_33 34.60 0.00 1 + J_40_34 34.80 0.00 1 + J_40_35 35.00 5.00 1 + J_40_36 35.20 0.00 1 + J_40_37 35.40 0.00 1 + J_40_38 35.60 5.00 1 + J_40_39 35.80 0.00 1 + J_40_40 36.00 0.00 1 + J_40_41 36.20 5.00 1 + J_40_42 36.40 0.00 1 + J_40_43 36.60 0.00 1 + J_40_44 36.80 5.00 1 + J_41_00 28.20 0.00 1 + J_41_01 28.40 5.00 1 + J_41_02 28.60 0.00 1 + J_41_03 28.80 0.00 1 + J_41_04 29.00 5.00 1 + J_41_05 29.20 0.00 1 + J_41_06 29.40 0.00 1 + J_41_07 29.60 5.00 1 + J_41_08 29.80 0.00 1 + J_41_09 30.00 0.00 1 + J_41_10 30.20 5.00 1 + J_41_11 30.40 0.00 1 + J_41_12 30.60 0.00 1 + J_41_13 30.80 5.00 1 + J_41_14 31.00 0.00 1 + J_41_15 31.20 0.00 1 + J_41_16 31.40 5.00 1 + J_41_17 31.60 0.00 1 + J_41_18 31.80 0.00 1 + J_41_19 32.00 5.00 1 + J_41_20 32.20 0.00 1 + J_41_21 32.40 0.00 1 + J_41_22 32.60 5.00 1 + J_41_23 32.80 0.00 1 + J_41_24 33.00 0.00 1 + J_41_25 33.20 5.00 1 + J_41_26 33.40 0.00 1 + J_41_27 33.60 0.00 1 + J_41_28 33.80 5.00 1 + J_41_29 34.00 0.00 1 + J_41_30 34.20 0.00 1 + J_41_31 34.40 5.00 1 + J_41_32 34.60 0.00 1 + J_41_33 34.80 0.00 1 + J_41_34 35.00 5.00 1 + J_41_35 35.20 0.00 1 + J_41_36 35.40 0.00 1 + J_41_37 35.60 5.00 1 + J_41_38 35.80 0.00 1 + J_41_39 36.00 0.00 1 + J_41_40 36.20 5.00 1 + J_41_41 36.40 0.00 1 + J_41_42 36.60 0.00 1 + J_41_43 36.80 5.00 1 + J_41_44 37.00 0.00 1 + J_42_00 28.40 5.00 1 + J_42_01 28.60 0.00 1 + J_42_02 28.80 0.00 1 + J_42_03 29.00 5.00 1 + J_42_04 29.20 0.00 1 + J_42_05 29.40 0.00 1 + J_42_06 29.60 5.00 1 + J_42_07 29.80 0.00 1 + J_42_08 30.00 0.00 1 + J_42_09 30.20 5.00 1 + J_42_10 30.40 0.00 1 + J_42_11 30.60 0.00 1 + J_42_12 30.80 5.00 1 + J_42_13 31.00 0.00 1 + J_42_14 31.20 0.00 1 + J_42_15 31.40 5.00 1 + J_42_16 31.60 0.00 1 + J_42_17 31.80 0.00 1 + J_42_18 32.00 5.00 1 + J_42_19 32.20 0.00 1 + J_42_20 32.40 0.00 1 + J_42_21 32.60 5.00 1 + J_42_22 32.80 0.00 1 + J_42_23 33.00 0.00 1 + J_42_24 33.20 5.00 1 + J_42_25 33.40 0.00 1 + J_42_26 33.60 0.00 1 + J_42_27 33.80 5.00 1 + J_42_28 34.00 0.00 1 + J_42_29 34.20 0.00 1 + J_42_30 34.40 5.00 1 + J_42_31 34.60 0.00 1 + J_42_32 34.80 0.00 1 + J_42_33 35.00 5.00 1 + J_42_34 35.20 0.00 1 + J_42_35 35.40 0.00 1 + J_42_36 35.60 5.00 1 + J_42_37 35.80 0.00 1 + J_42_38 36.00 0.00 1 + J_42_39 36.20 5.00 1 + J_42_40 36.40 0.00 1 + J_42_41 36.60 0.00 1 + J_42_42 36.80 5.00 1 + J_42_43 37.00 0.00 1 + J_42_44 37.20 0.00 1 + J_43_00 28.60 0.00 1 + J_43_01 28.80 0.00 1 + J_43_02 29.00 5.00 1 + J_43_03 29.20 0.00 1 + J_43_04 29.40 0.00 1 + J_43_05 29.60 5.00 1 + J_43_06 29.80 0.00 1 + J_43_07 30.00 0.00 1 + J_43_08 30.20 5.00 1 + J_43_09 30.40 0.00 1 + J_43_10 30.60 0.00 1 + J_43_11 30.80 5.00 1 + J_43_12 31.00 0.00 1 + J_43_13 31.20 0.00 1 + J_43_14 31.40 5.00 1 + J_43_15 31.60 0.00 1 + J_43_16 31.80 0.00 1 + J_43_17 32.00 5.00 1 + J_43_18 32.20 0.00 1 + J_43_19 32.40 0.00 1 + J_43_20 32.60 5.00 1 + J_43_21 32.80 0.00 1 + J_43_22 33.00 0.00 1 + J_43_23 33.20 5.00 1 + J_43_24 33.40 0.00 1 + J_43_25 33.60 0.00 1 + J_43_26 33.80 5.00 1 + J_43_27 34.00 0.00 1 + J_43_28 34.20 0.00 1 + J_43_29 34.40 5.00 1 + J_43_30 34.60 0.00 1 + J_43_31 34.80 0.00 1 + J_43_32 35.00 5.00 1 + J_43_33 35.20 0.00 1 + J_43_34 35.40 0.00 1 + J_43_35 35.60 5.00 1 + J_43_36 35.80 0.00 1 + J_43_37 36.00 0.00 1 + J_43_38 36.20 5.00 1 + J_43_39 36.40 0.00 1 + J_43_40 36.60 0.00 1 + J_43_41 36.80 5.00 1 + J_43_42 37.00 0.00 1 + J_43_43 37.20 0.00 1 + J_43_44 37.40 5.00 1 + J_44_00 28.80 0.00 1 + J_44_01 29.00 5.00 1 + J_44_02 29.20 0.00 1 + J_44_03 29.40 0.00 1 + J_44_04 29.60 5.00 1 + J_44_05 29.80 0.00 1 + J_44_06 30.00 0.00 1 + J_44_07 30.20 5.00 1 + J_44_08 30.40 0.00 1 + J_44_09 30.60 0.00 1 + J_44_10 30.80 5.00 1 + J_44_11 31.00 0.00 1 + J_44_12 31.20 0.00 1 + J_44_13 31.40 5.00 1 + J_44_14 31.60 0.00 1 + J_44_15 31.80 0.00 1 + J_44_16 32.00 5.00 1 + J_44_17 32.20 0.00 1 + J_44_18 32.40 0.00 1 + J_44_19 32.60 5.00 1 + J_44_20 32.80 0.00 1 + J_44_21 33.00 0.00 1 + J_44_22 33.20 5.00 1 + J_44_23 33.40 0.00 1 + J_44_24 33.60 0.00 1 + J_44_25 33.80 5.00 1 + J_44_26 34.00 0.00 1 + J_44_27 34.20 0.00 1 + J_44_28 34.40 5.00 1 + J_44_29 34.60 0.00 1 + J_44_30 34.80 0.00 1 + J_44_31 35.00 5.00 1 + J_44_32 35.20 0.00 1 + J_44_33 35.40 0.00 1 + J_44_34 35.60 5.00 1 + J_44_35 35.80 0.00 1 + J_44_36 36.00 0.00 1 + J_44_37 36.20 5.00 1 + J_44_38 36.40 0.00 1 + J_44_39 36.60 0.00 1 + J_44_40 36.80 5.00 1 + J_44_41 37.00 0.00 1 + J_44_42 37.20 0.00 1 + J_44_43 37.40 5.00 1 + J_44_44 37.60 0.00 1 + +[RESERVOIRS] +;ID Head Pattern + RES_NORTH 95.0 + RES_SOUTH 92.0 + +[PIPES] +;ID Node1 Node2 Length Diameter Roughness MinorLoss Status + P_SRC1 RES_NORTH J_00_00 200 400 120 0 Open + P_SRC2 RES_SOUTH J_44_44 200 400 120 0 Open + P_H_00003 J_00_00 J_00_01 120 200 130 0 Open + P_H_00004 J_00_01 J_00_02 120 200 130 0 Open + P_H_00005 J_00_02 J_00_03 120 200 130 0 Open + P_H_00006 J_00_03 J_00_04 120 200 130 0 Open + P_H_00007 J_00_04 J_00_05 120 200 130 0 Open + P_H_00008 J_00_05 J_00_06 120 200 130 0 Open + P_H_00009 J_00_06 J_00_07 120 200 130 0 Open + P_H_00010 J_00_07 J_00_08 120 200 130 0 Open + P_H_00011 J_00_08 J_00_09 120 200 130 0 Open + P_H_00012 J_00_09 J_00_10 120 200 130 0 Open + P_H_00013 J_00_10 J_00_11 120 200 130 0 Open + P_H_00014 J_00_11 J_00_12 120 200 130 0 Open + P_H_00015 J_00_12 J_00_13 120 200 130 0 Open + P_H_00016 J_00_13 J_00_14 120 200 130 0 Open + P_H_00017 J_00_14 J_00_15 120 200 130 0 Open + P_H_00018 J_00_15 J_00_16 120 200 130 0 Open + P_H_00019 J_00_16 J_00_17 120 200 130 0 Open + P_H_00020 J_00_17 J_00_18 120 200 130 0 Open + P_H_00021 J_00_18 J_00_19 120 200 130 0 Open + P_H_00022 J_00_19 J_00_20 120 200 130 0 Open + P_H_00023 J_00_20 J_00_21 120 200 130 0 Open + P_H_00024 J_00_21 J_00_22 120 200 130 0 Open + P_H_00025 J_00_22 J_00_23 120 200 130 0 Open + P_H_00026 J_00_23 J_00_24 120 200 130 0 Open + P_H_00027 J_00_24 J_00_25 120 200 130 0 Open + P_H_00028 J_00_25 J_00_26 120 200 130 0 Open + P_H_00029 J_00_26 J_00_27 120 200 130 0 Open + P_H_00030 J_00_27 J_00_28 120 200 130 0 Open + P_H_00031 J_00_28 J_00_29 120 200 130 0 Open + P_H_00032 J_00_29 J_00_30 120 200 130 0 Open + P_H_00033 J_00_30 J_00_31 120 200 130 0 Open + P_H_00034 J_00_31 J_00_32 120 200 130 0 Open + P_H_00035 J_00_32 J_00_33 120 200 130 0 Open + P_H_00036 J_00_33 J_00_34 120 200 130 0 Open + P_H_00037 J_00_34 J_00_35 120 200 130 0 Open + P_H_00038 J_00_35 J_00_36 120 200 130 0 Open + P_H_00039 J_00_36 J_00_37 120 200 130 0 Open + P_H_00040 J_00_37 J_00_38 120 200 130 0 Open + P_H_00041 J_00_38 J_00_39 120 200 130 0 Open + P_H_00042 J_00_39 J_00_40 120 200 130 0 Open + P_H_00043 J_00_40 J_00_41 120 200 130 0 Open + P_H_00044 J_00_41 J_00_42 120 200 130 0 Open + P_H_00045 J_00_42 J_00_43 120 200 130 0 Open + P_H_00046 J_00_43 J_00_44 120 200 130 0 Open + P_H_00047 J_01_00 J_01_01 120 200 130 0 Open + P_H_00048 J_01_01 J_01_02 120 200 130 0 Open + P_H_00049 J_01_02 J_01_03 120 200 130 0 Open + P_H_00050 J_01_03 J_01_04 120 200 130 0 Open + P_H_00051 J_01_04 J_01_05 120 200 130 0 Open + P_H_00052 J_01_05 J_01_06 120 200 130 0 Open + P_H_00053 J_01_06 J_01_07 120 200 130 0 Open + P_H_00054 J_01_07 J_01_08 120 200 130 0 Open + P_H_00055 J_01_08 J_01_09 120 200 130 0 Open + P_H_00056 J_01_09 J_01_10 120 200 130 0 Open + P_H_00057 J_01_10 J_01_11 120 200 130 0 Open + P_H_00058 J_01_11 J_01_12 120 200 130 0 Open + P_H_00059 J_01_12 J_01_13 120 200 130 0 Open + P_H_00060 J_01_13 J_01_14 120 200 130 0 Open + P_H_00061 J_01_14 J_01_15 120 200 130 0 Open + P_H_00062 J_01_15 J_01_16 120 200 130 0 Open + P_H_00063 J_01_16 J_01_17 120 200 130 0 Open + P_H_00064 J_01_17 J_01_18 120 200 130 0 Open + P_H_00065 J_01_18 J_01_19 120 200 130 0 Open + P_H_00066 J_01_19 J_01_20 120 200 130 0 Open + P_H_00067 J_01_20 J_01_21 120 200 130 0 Open + P_H_00068 J_01_21 J_01_22 120 200 130 0 Open + P_H_00069 J_01_22 J_01_23 120 200 130 0 Open + P_H_00070 J_01_23 J_01_24 120 200 130 0 Open + P_H_00071 J_01_24 J_01_25 120 200 130 0 Open + P_H_00072 J_01_25 J_01_26 120 200 130 0 Open + P_H_00073 J_01_26 J_01_27 120 200 130 0 Open + P_H_00074 J_01_27 J_01_28 120 200 130 0 Open + P_H_00075 J_01_28 J_01_29 120 200 130 0 Open + P_H_00076 J_01_29 J_01_30 120 200 130 0 Open + P_H_00077 J_01_30 J_01_31 120 200 130 0 Open + P_H_00078 J_01_31 J_01_32 120 200 130 0 Open + P_H_00079 J_01_32 J_01_33 120 200 130 0 Open + P_H_00080 J_01_33 J_01_34 120 200 130 0 Open + P_H_00081 J_01_34 J_01_35 120 200 130 0 Open + P_H_00082 J_01_35 J_01_36 120 200 130 0 Open + P_H_00083 J_01_36 J_01_37 120 200 130 0 Open + P_H_00084 J_01_37 J_01_38 120 200 130 0 Open + P_H_00085 J_01_38 J_01_39 120 200 130 0 Open + P_H_00086 J_01_39 J_01_40 120 200 130 0 Open + P_H_00087 J_01_40 J_01_41 120 200 130 0 Open + P_H_00088 J_01_41 J_01_42 120 200 130 0 Open + P_H_00089 J_01_42 J_01_43 120 200 130 0 Open + P_H_00090 J_01_43 J_01_44 120 200 130 0 Open + P_H_00091 J_02_00 J_02_01 120 200 130 0 Open + P_H_00092 J_02_01 J_02_02 120 200 130 0 Open + P_H_00093 J_02_02 J_02_03 120 200 130 0 Open + P_H_00094 J_02_03 J_02_04 120 200 130 0 Open + P_H_00095 J_02_04 J_02_05 120 200 130 0 Open + P_H_00096 J_02_05 J_02_06 120 200 130 0 Open + P_H_00097 J_02_06 J_02_07 120 200 130 0 Open + P_H_00098 J_02_07 J_02_08 120 200 130 0 Open + P_H_00099 J_02_08 J_02_09 120 200 130 0 Open + P_H_00100 J_02_09 J_02_10 120 200 130 0 Open + P_H_00101 J_02_10 J_02_11 120 200 130 0 Open + P_H_00102 J_02_11 J_02_12 120 200 130 0 Open + P_H_00103 J_02_12 J_02_13 120 200 130 0 Open + P_H_00104 J_02_13 J_02_14 120 200 130 0 Open + P_H_00105 J_02_14 J_02_15 120 200 130 0 Open + P_H_00106 J_02_15 J_02_16 120 200 130 0 Open + P_H_00107 J_02_16 J_02_17 120 200 130 0 Open + P_H_00108 J_02_17 J_02_18 120 200 130 0 Open + P_H_00109 J_02_18 J_02_19 120 200 130 0 Open + P_H_00110 J_02_19 J_02_20 120 200 130 0 Open + P_H_00111 J_02_20 J_02_21 120 200 130 0 Open + P_H_00112 J_02_21 J_02_22 120 200 130 0 Open + P_H_00113 J_02_22 J_02_23 120 200 130 0 Open + P_H_00114 J_02_23 J_02_24 120 200 130 0 Open + P_H_00115 J_02_24 J_02_25 120 200 130 0 Open + P_H_00116 J_02_25 J_02_26 120 200 130 0 Open + P_H_00117 J_02_26 J_02_27 120 200 130 0 Open + P_H_00118 J_02_27 J_02_28 120 200 130 0 Open + P_H_00119 J_02_28 J_02_29 120 200 130 0 Open + P_H_00120 J_02_29 J_02_30 120 200 130 0 Open + P_H_00121 J_02_30 J_02_31 120 200 130 0 Open + P_H_00122 J_02_31 J_02_32 120 200 130 0 Open + P_H_00123 J_02_32 J_02_33 120 200 130 0 Open + P_H_00124 J_02_33 J_02_34 120 200 130 0 Open + P_H_00125 J_02_34 J_02_35 120 200 130 0 Open + P_H_00126 J_02_35 J_02_36 120 200 130 0 Open + P_H_00127 J_02_36 J_02_37 120 200 130 0 Open + P_H_00128 J_02_37 J_02_38 120 200 130 0 Open + P_H_00129 J_02_38 J_02_39 120 200 130 0 Open + P_H_00130 J_02_39 J_02_40 120 200 130 0 Open + P_H_00131 J_02_40 J_02_41 120 200 130 0 Open + P_H_00132 J_02_41 J_02_42 120 200 130 0 Open + P_H_00133 J_02_42 J_02_43 120 200 130 0 Open + P_H_00134 J_02_43 J_02_44 120 200 130 0 Open + P_H_00135 J_03_00 J_03_01 120 200 130 0 Open + P_H_00136 J_03_01 J_03_02 120 200 130 0 Open + P_H_00137 J_03_02 J_03_03 120 200 130 0 Open + P_H_00138 J_03_03 J_03_04 120 200 130 0 Open + P_H_00139 J_03_04 J_03_05 120 200 130 0 Open + P_H_00140 J_03_05 J_03_06 120 200 130 0 Open + P_H_00141 J_03_06 J_03_07 120 200 130 0 Open + P_H_00142 J_03_07 J_03_08 120 200 130 0 Open + P_H_00143 J_03_08 J_03_09 120 200 130 0 Open + P_H_00144 J_03_09 J_03_10 120 200 130 0 Open + P_H_00145 J_03_10 J_03_11 120 200 130 0 Open + P_H_00146 J_03_11 J_03_12 120 200 130 0 Open + P_H_00147 J_03_12 J_03_13 120 200 130 0 Open + P_H_00148 J_03_13 J_03_14 120 200 130 0 Open + P_H_00149 J_03_14 J_03_15 120 200 130 0 Open + P_H_00150 J_03_15 J_03_16 120 200 130 0 Open + P_H_00151 J_03_16 J_03_17 120 200 130 0 Open + P_H_00152 J_03_17 J_03_18 120 200 130 0 Open + P_H_00153 J_03_18 J_03_19 120 200 130 0 Open + P_H_00154 J_03_19 J_03_20 120 200 130 0 Open + P_H_00155 J_03_20 J_03_21 120 200 130 0 Open + P_H_00156 J_03_21 J_03_22 120 200 130 0 Open + P_H_00157 J_03_22 J_03_23 120 200 130 0 Open + P_H_00158 J_03_23 J_03_24 120 200 130 0 Open + P_H_00159 J_03_24 J_03_25 120 200 130 0 Open + P_H_00160 J_03_25 J_03_26 120 200 130 0 Open + P_H_00161 J_03_26 J_03_27 120 200 130 0 Open + P_H_00162 J_03_27 J_03_28 120 200 130 0 Open + P_H_00163 J_03_28 J_03_29 120 200 130 0 Open + P_H_00164 J_03_29 J_03_30 120 200 130 0 Open + P_H_00165 J_03_30 J_03_31 120 200 130 0 Open + P_H_00166 J_03_31 J_03_32 120 200 130 0 Open + P_H_00167 J_03_32 J_03_33 120 200 130 0 Open + P_H_00168 J_03_33 J_03_34 120 200 130 0 Open + P_H_00169 J_03_34 J_03_35 120 200 130 0 Open + P_H_00170 J_03_35 J_03_36 120 200 130 0 Open + P_H_00171 J_03_36 J_03_37 120 200 130 0 Open + P_H_00172 J_03_37 J_03_38 120 200 130 0 Open + P_H_00173 J_03_38 J_03_39 120 200 130 0 Open + P_H_00174 J_03_39 J_03_40 120 200 130 0 Open + P_H_00175 J_03_40 J_03_41 120 200 130 0 Open + P_H_00176 J_03_41 J_03_42 120 200 130 0 Open + P_H_00177 J_03_42 J_03_43 120 200 130 0 Open + P_H_00178 J_03_43 J_03_44 120 200 130 0 Open + P_H_00179 J_04_00 J_04_01 120 200 130 0 Open + P_H_00180 J_04_01 J_04_02 120 200 130 0 Open + P_H_00181 J_04_02 J_04_03 120 200 130 0 Open + P_H_00182 J_04_03 J_04_04 120 200 130 0 Open + P_H_00183 J_04_04 J_04_05 120 200 130 0 Open + P_H_00184 J_04_05 J_04_06 120 200 130 0 Open + P_H_00185 J_04_06 J_04_07 120 200 130 0 Open + P_H_00186 J_04_07 J_04_08 120 200 130 0 Open + P_H_00187 J_04_08 J_04_09 120 200 130 0 Open + P_H_00188 J_04_09 J_04_10 120 200 130 0 Open + P_H_00189 J_04_10 J_04_11 120 200 130 0 Open + P_H_00190 J_04_11 J_04_12 120 200 130 0 Open + P_H_00191 J_04_12 J_04_13 120 200 130 0 Open + P_H_00192 J_04_13 J_04_14 120 200 130 0 Open + P_H_00193 J_04_14 J_04_15 120 200 130 0 Open + P_H_00194 J_04_15 J_04_16 120 200 130 0 Open + P_H_00195 J_04_16 J_04_17 120 200 130 0 Open + P_H_00196 J_04_17 J_04_18 120 200 130 0 Open + P_H_00197 J_04_18 J_04_19 120 200 130 0 Open + P_H_00198 J_04_19 J_04_20 120 200 130 0 Open + P_H_00199 J_04_20 J_04_21 120 200 130 0 Open + P_H_00200 J_04_21 J_04_22 120 200 130 0 Open + P_H_00201 J_04_22 J_04_23 120 200 130 0 Open + P_H_00202 J_04_23 J_04_24 120 200 130 0 Open + P_H_00203 J_04_24 J_04_25 120 200 130 0 Open + P_H_00204 J_04_25 J_04_26 120 200 130 0 Open + P_H_00205 J_04_26 J_04_27 120 200 130 0 Open + P_H_00206 J_04_27 J_04_28 120 200 130 0 Open + P_H_00207 J_04_28 J_04_29 120 200 130 0 Open + P_H_00208 J_04_29 J_04_30 120 200 130 0 Open + P_H_00209 J_04_30 J_04_31 120 200 130 0 Open + P_H_00210 J_04_31 J_04_32 120 200 130 0 Open + P_H_00211 J_04_32 J_04_33 120 200 130 0 Open + P_H_00212 J_04_33 J_04_34 120 200 130 0 Open + P_H_00213 J_04_34 J_04_35 120 200 130 0 Open + P_H_00214 J_04_35 J_04_36 120 200 130 0 Open + P_H_00215 J_04_36 J_04_37 120 200 130 0 Open + P_H_00216 J_04_37 J_04_38 120 200 130 0 Open + P_H_00217 J_04_38 J_04_39 120 200 130 0 Open + P_H_00218 J_04_39 J_04_40 120 200 130 0 Open + P_H_00219 J_04_40 J_04_41 120 200 130 0 Open + P_H_00220 J_04_41 J_04_42 120 200 130 0 Open + P_H_00221 J_04_42 J_04_43 120 200 130 0 Open + P_H_00222 J_04_43 J_04_44 120 200 130 0 Open + P_H_00223 J_05_00 J_05_01 120 200 130 0 Open + P_H_00224 J_05_01 J_05_02 120 200 130 0 Open + P_H_00225 J_05_02 J_05_03 120 200 130 0 Open + P_H_00226 J_05_03 J_05_04 120 200 130 0 Open + P_H_00227 J_05_04 J_05_05 120 200 130 0 Open + P_H_00228 J_05_05 J_05_06 120 200 130 0 Open + P_H_00229 J_05_06 J_05_07 120 200 130 0 Open + P_H_00230 J_05_07 J_05_08 120 200 130 0 Open + P_H_00231 J_05_08 J_05_09 120 200 130 0 Open + P_H_00232 J_05_09 J_05_10 120 200 130 0 Open + P_H_00233 J_05_10 J_05_11 120 200 130 0 Open + P_H_00234 J_05_11 J_05_12 120 200 130 0 Open + P_H_00235 J_05_12 J_05_13 120 200 130 0 Open + P_H_00236 J_05_13 J_05_14 120 200 130 0 Open + P_H_00237 J_05_14 J_05_15 120 200 130 0 Open + P_H_00238 J_05_15 J_05_16 120 200 130 0 Open + P_H_00239 J_05_16 J_05_17 120 200 130 0 Open + P_H_00240 J_05_17 J_05_18 120 200 130 0 Open + P_H_00241 J_05_18 J_05_19 120 200 130 0 Open + P_H_00242 J_05_19 J_05_20 120 200 130 0 Open + P_H_00243 J_05_20 J_05_21 120 200 130 0 Open + P_H_00244 J_05_21 J_05_22 120 200 130 0 Open + P_H_00245 J_05_22 J_05_23 120 200 130 0 Open + P_H_00246 J_05_23 J_05_24 120 200 130 0 Open + P_H_00247 J_05_24 J_05_25 120 200 130 0 Open + P_H_00248 J_05_25 J_05_26 120 200 130 0 Open + P_H_00249 J_05_26 J_05_27 120 200 130 0 Open + P_H_00250 J_05_27 J_05_28 120 200 130 0 Open + P_H_00251 J_05_28 J_05_29 120 200 130 0 Open + P_H_00252 J_05_29 J_05_30 120 200 130 0 Open + P_H_00253 J_05_30 J_05_31 120 200 130 0 Open + P_H_00254 J_05_31 J_05_32 120 200 130 0 Open + P_H_00255 J_05_32 J_05_33 120 200 130 0 Open + P_H_00256 J_05_33 J_05_34 120 200 130 0 Open + P_H_00257 J_05_34 J_05_35 120 200 130 0 Open + P_H_00258 J_05_35 J_05_36 120 200 130 0 Open + P_H_00259 J_05_36 J_05_37 120 200 130 0 Open + P_H_00260 J_05_37 J_05_38 120 200 130 0 Open + P_H_00261 J_05_38 J_05_39 120 200 130 0 Open + P_H_00262 J_05_39 J_05_40 120 200 130 0 Open + P_H_00263 J_05_40 J_05_41 120 200 130 0 Open + P_H_00264 J_05_41 J_05_42 120 200 130 0 Open + P_H_00265 J_05_42 J_05_43 120 200 130 0 Open + P_H_00266 J_05_43 J_05_44 120 200 130 0 Open + P_H_00267 J_06_00 J_06_01 120 200 130 0 Open + P_H_00268 J_06_01 J_06_02 120 200 130 0 Open + P_H_00269 J_06_02 J_06_03 120 200 130 0 Open + P_H_00270 J_06_03 J_06_04 120 200 130 0 Open + P_H_00271 J_06_04 J_06_05 120 200 130 0 Open + P_H_00272 J_06_05 J_06_06 120 200 130 0 Open + P_H_00273 J_06_06 J_06_07 120 200 130 0 Open + P_H_00274 J_06_07 J_06_08 120 200 130 0 Open + P_H_00275 J_06_08 J_06_09 120 200 130 0 Open + P_H_00276 J_06_09 J_06_10 120 200 130 0 Open + P_H_00277 J_06_10 J_06_11 120 200 130 0 Open + P_H_00278 J_06_11 J_06_12 120 200 130 0 Open + P_H_00279 J_06_12 J_06_13 120 200 130 0 Open + P_H_00280 J_06_13 J_06_14 120 200 130 0 Open + P_H_00281 J_06_14 J_06_15 120 200 130 0 Open + P_H_00282 J_06_15 J_06_16 120 200 130 0 Open + P_H_00283 J_06_16 J_06_17 120 200 130 0 Open + P_H_00284 J_06_17 J_06_18 120 200 130 0 Open + P_H_00285 J_06_18 J_06_19 120 200 130 0 Open + P_H_00286 J_06_19 J_06_20 120 200 130 0 Open + P_H_00287 J_06_20 J_06_21 120 200 130 0 Open + P_H_00288 J_06_21 J_06_22 120 200 130 0 Open + P_H_00289 J_06_22 J_06_23 120 200 130 0 Open + P_H_00290 J_06_23 J_06_24 120 200 130 0 Open + P_H_00291 J_06_24 J_06_25 120 200 130 0 Open + P_H_00292 J_06_25 J_06_26 120 200 130 0 Open + P_H_00293 J_06_26 J_06_27 120 200 130 0 Open + P_H_00294 J_06_27 J_06_28 120 200 130 0 Open + P_H_00295 J_06_28 J_06_29 120 200 130 0 Open + P_H_00296 J_06_29 J_06_30 120 200 130 0 Open + P_H_00297 J_06_30 J_06_31 120 200 130 0 Open + P_H_00298 J_06_31 J_06_32 120 200 130 0 Open + P_H_00299 J_06_32 J_06_33 120 200 130 0 Open + P_H_00300 J_06_33 J_06_34 120 200 130 0 Open + P_H_00301 J_06_34 J_06_35 120 200 130 0 Open + P_H_00302 J_06_35 J_06_36 120 200 130 0 Open + P_H_00303 J_06_36 J_06_37 120 200 130 0 Open + P_H_00304 J_06_37 J_06_38 120 200 130 0 Open + P_H_00305 J_06_38 J_06_39 120 200 130 0 Open + P_H_00306 J_06_39 J_06_40 120 200 130 0 Open + P_H_00307 J_06_40 J_06_41 120 200 130 0 Open + P_H_00308 J_06_41 J_06_42 120 200 130 0 Open + P_H_00309 J_06_42 J_06_43 120 200 130 0 Open + P_H_00310 J_06_43 J_06_44 120 200 130 0 Open + P_H_00311 J_07_00 J_07_01 120 200 130 0 Open + P_H_00312 J_07_01 J_07_02 120 200 130 0 Open + P_H_00313 J_07_02 J_07_03 120 200 130 0 Open + P_H_00314 J_07_03 J_07_04 120 200 130 0 Open + P_H_00315 J_07_04 J_07_05 120 200 130 0 Open + P_H_00316 J_07_05 J_07_06 120 200 130 0 Open + P_H_00317 J_07_06 J_07_07 120 200 130 0 Open + P_H_00318 J_07_07 J_07_08 120 200 130 0 Open + P_H_00319 J_07_08 J_07_09 120 200 130 0 Open + P_H_00320 J_07_09 J_07_10 120 200 130 0 Open + P_H_00321 J_07_10 J_07_11 120 200 130 0 Open + P_H_00322 J_07_11 J_07_12 120 200 130 0 Open + P_H_00323 J_07_12 J_07_13 120 200 130 0 Open + P_H_00324 J_07_13 J_07_14 120 200 130 0 Open + P_H_00325 J_07_14 J_07_15 120 200 130 0 Open + P_H_00326 J_07_15 J_07_16 120 200 130 0 Open + P_H_00327 J_07_16 J_07_17 120 200 130 0 Open + P_H_00328 J_07_17 J_07_18 120 200 130 0 Open + P_H_00329 J_07_18 J_07_19 120 200 130 0 Open + P_H_00330 J_07_19 J_07_20 120 200 130 0 Open + P_H_00331 J_07_20 J_07_21 120 200 130 0 Open + P_H_00332 J_07_21 J_07_22 120 200 130 0 Open + P_H_00333 J_07_22 J_07_23 120 200 130 0 Open + P_H_00334 J_07_23 J_07_24 120 200 130 0 Open + P_H_00335 J_07_24 J_07_25 120 200 130 0 Open + P_H_00336 J_07_25 J_07_26 120 200 130 0 Open + P_H_00337 J_07_26 J_07_27 120 200 130 0 Open + P_H_00338 J_07_27 J_07_28 120 200 130 0 Open + P_H_00339 J_07_28 J_07_29 120 200 130 0 Open + P_H_00340 J_07_29 J_07_30 120 200 130 0 Open + P_H_00341 J_07_30 J_07_31 120 200 130 0 Open + P_H_00342 J_07_31 J_07_32 120 200 130 0 Open + P_H_00343 J_07_32 J_07_33 120 200 130 0 Open + P_H_00344 J_07_33 J_07_34 120 200 130 0 Open + P_H_00345 J_07_34 J_07_35 120 200 130 0 Open + P_H_00346 J_07_35 J_07_36 120 200 130 0 Open + P_H_00347 J_07_36 J_07_37 120 200 130 0 Open + P_H_00348 J_07_37 J_07_38 120 200 130 0 Open + P_H_00349 J_07_38 J_07_39 120 200 130 0 Open + P_H_00350 J_07_39 J_07_40 120 200 130 0 Open + P_H_00351 J_07_40 J_07_41 120 200 130 0 Open + P_H_00352 J_07_41 J_07_42 120 200 130 0 Open + P_H_00353 J_07_42 J_07_43 120 200 130 0 Open + P_H_00354 J_07_43 J_07_44 120 200 130 0 Open + P_H_00355 J_08_00 J_08_01 120 200 130 0 Open + P_H_00356 J_08_01 J_08_02 120 200 130 0 Open + P_H_00357 J_08_02 J_08_03 120 200 130 0 Open + P_H_00358 J_08_03 J_08_04 120 200 130 0 Open + P_H_00359 J_08_04 J_08_05 120 200 130 0 Open + P_H_00360 J_08_05 J_08_06 120 200 130 0 Open + P_H_00361 J_08_06 J_08_07 120 200 130 0 Open + P_H_00362 J_08_07 J_08_08 120 200 130 0 Open + P_H_00363 J_08_08 J_08_09 120 200 130 0 Open + P_H_00364 J_08_09 J_08_10 120 200 130 0 Open + P_H_00365 J_08_10 J_08_11 120 200 130 0 Open + P_H_00366 J_08_11 J_08_12 120 200 130 0 Open + P_H_00367 J_08_12 J_08_13 120 200 130 0 Open + P_H_00368 J_08_13 J_08_14 120 200 130 0 Open + P_H_00369 J_08_14 J_08_15 120 200 130 0 Open + P_H_00370 J_08_15 J_08_16 120 200 130 0 Open + P_H_00371 J_08_16 J_08_17 120 200 130 0 Open + P_H_00372 J_08_17 J_08_18 120 200 130 0 Open + P_H_00373 J_08_18 J_08_19 120 200 130 0 Open + P_H_00374 J_08_19 J_08_20 120 200 130 0 Open + P_H_00375 J_08_20 J_08_21 120 200 130 0 Open + P_H_00376 J_08_21 J_08_22 120 200 130 0 Open + P_H_00377 J_08_22 J_08_23 120 200 130 0 Open + P_H_00378 J_08_23 J_08_24 120 200 130 0 Open + P_H_00379 J_08_24 J_08_25 120 200 130 0 Open + P_H_00380 J_08_25 J_08_26 120 200 130 0 Open + P_H_00381 J_08_26 J_08_27 120 200 130 0 Open + P_H_00382 J_08_27 J_08_28 120 200 130 0 Open + P_H_00383 J_08_28 J_08_29 120 200 130 0 Open + P_H_00384 J_08_29 J_08_30 120 200 130 0 Open + P_H_00385 J_08_30 J_08_31 120 200 130 0 Open + P_H_00386 J_08_31 J_08_32 120 200 130 0 Open + P_H_00387 J_08_32 J_08_33 120 200 130 0 Open + P_H_00388 J_08_33 J_08_34 120 200 130 0 Open + P_H_00389 J_08_34 J_08_35 120 200 130 0 Open + P_H_00390 J_08_35 J_08_36 120 200 130 0 Open + P_H_00391 J_08_36 J_08_37 120 200 130 0 Open + P_H_00392 J_08_37 J_08_38 120 200 130 0 Open + P_H_00393 J_08_38 J_08_39 120 200 130 0 Open + P_H_00394 J_08_39 J_08_40 120 200 130 0 Open + P_H_00395 J_08_40 J_08_41 120 200 130 0 Open + P_H_00396 J_08_41 J_08_42 120 200 130 0 Open + P_H_00397 J_08_42 J_08_43 120 200 130 0 Open + P_H_00398 J_08_43 J_08_44 120 200 130 0 Open + P_H_00399 J_09_00 J_09_01 120 200 130 0 Open + P_H_00400 J_09_01 J_09_02 120 200 130 0 Open + P_H_00401 J_09_02 J_09_03 120 200 130 0 Open + P_H_00402 J_09_03 J_09_04 120 200 130 0 Open + P_H_00403 J_09_04 J_09_05 120 200 130 0 Open + P_H_00404 J_09_05 J_09_06 120 200 130 0 Open + P_H_00405 J_09_06 J_09_07 120 200 130 0 Open + P_H_00406 J_09_07 J_09_08 120 200 130 0 Open + P_H_00407 J_09_08 J_09_09 120 200 130 0 Open + P_H_00408 J_09_09 J_09_10 120 200 130 0 Open + P_H_00409 J_09_10 J_09_11 120 200 130 0 Open + P_H_00410 J_09_11 J_09_12 120 200 130 0 Open + P_H_00411 J_09_12 J_09_13 120 200 130 0 Open + P_H_00412 J_09_13 J_09_14 120 200 130 0 Open + P_H_00413 J_09_14 J_09_15 120 200 130 0 Open + P_H_00414 J_09_15 J_09_16 120 200 130 0 Open + P_H_00415 J_09_16 J_09_17 120 200 130 0 Open + P_H_00416 J_09_17 J_09_18 120 200 130 0 Open + P_H_00417 J_09_18 J_09_19 120 200 130 0 Open + P_H_00418 J_09_19 J_09_20 120 200 130 0 Open + P_H_00419 J_09_20 J_09_21 120 200 130 0 Open + P_H_00420 J_09_21 J_09_22 120 200 130 0 Open + P_H_00421 J_09_22 J_09_23 120 200 130 0 Open + P_H_00422 J_09_23 J_09_24 120 200 130 0 Open + P_H_00423 J_09_24 J_09_25 120 200 130 0 Open + P_H_00424 J_09_25 J_09_26 120 200 130 0 Open + P_H_00425 J_09_26 J_09_27 120 200 130 0 Open + P_H_00426 J_09_27 J_09_28 120 200 130 0 Open + P_H_00427 J_09_28 J_09_29 120 200 130 0 Open + P_H_00428 J_09_29 J_09_30 120 200 130 0 Open + P_H_00429 J_09_30 J_09_31 120 200 130 0 Open + P_H_00430 J_09_31 J_09_32 120 200 130 0 Open + P_H_00431 J_09_32 J_09_33 120 200 130 0 Open + P_H_00432 J_09_33 J_09_34 120 200 130 0 Open + P_H_00433 J_09_34 J_09_35 120 200 130 0 Open + P_H_00434 J_09_35 J_09_36 120 200 130 0 Open + P_H_00435 J_09_36 J_09_37 120 200 130 0 Open + P_H_00436 J_09_37 J_09_38 120 200 130 0 Open + P_H_00437 J_09_38 J_09_39 120 200 130 0 Open + P_H_00438 J_09_39 J_09_40 120 200 130 0 Open + P_H_00439 J_09_40 J_09_41 120 200 130 0 Open + P_H_00440 J_09_41 J_09_42 120 200 130 0 Open + P_H_00441 J_09_42 J_09_43 120 200 130 0 Open + P_H_00442 J_09_43 J_09_44 120 200 130 0 Open + P_H_00443 J_10_00 J_10_01 120 200 130 0 Open + P_H_00444 J_10_01 J_10_02 120 200 130 0 Open + P_H_00445 J_10_02 J_10_03 120 200 130 0 Open + P_H_00446 J_10_03 J_10_04 120 200 130 0 Open + P_H_00447 J_10_04 J_10_05 120 200 130 0 Open + P_H_00448 J_10_05 J_10_06 120 200 130 0 Open + P_H_00449 J_10_06 J_10_07 120 200 130 0 Open + P_H_00450 J_10_07 J_10_08 120 200 130 0 Open + P_H_00451 J_10_08 J_10_09 120 200 130 0 Open + P_H_00452 J_10_09 J_10_10 120 200 130 0 Open + P_H_00453 J_10_10 J_10_11 120 200 130 0 Open + P_H_00454 J_10_11 J_10_12 120 200 130 0 Open + P_H_00455 J_10_12 J_10_13 120 200 130 0 Open + P_H_00456 J_10_13 J_10_14 120 200 130 0 Open + P_H_00457 J_10_14 J_10_15 120 200 130 0 Open + P_H_00458 J_10_15 J_10_16 120 200 130 0 Open + P_H_00459 J_10_16 J_10_17 120 200 130 0 Open + P_H_00460 J_10_17 J_10_18 120 200 130 0 Open + P_H_00461 J_10_18 J_10_19 120 200 130 0 Open + P_H_00462 J_10_19 J_10_20 120 200 130 0 Open + P_H_00463 J_10_20 J_10_21 120 200 130 0 Open + P_H_00464 J_10_21 J_10_22 120 200 130 0 Open + P_H_00465 J_10_22 J_10_23 120 200 130 0 Open + P_H_00466 J_10_23 J_10_24 120 200 130 0 Open + P_H_00467 J_10_24 J_10_25 120 200 130 0 Open + P_H_00468 J_10_25 J_10_26 120 200 130 0 Open + P_H_00469 J_10_26 J_10_27 120 200 130 0 Open + P_H_00470 J_10_27 J_10_28 120 200 130 0 Open + P_H_00471 J_10_28 J_10_29 120 200 130 0 Open + P_H_00472 J_10_29 J_10_30 120 200 130 0 Open + P_H_00473 J_10_30 J_10_31 120 200 130 0 Open + P_H_00474 J_10_31 J_10_32 120 200 130 0 Open + P_H_00475 J_10_32 J_10_33 120 200 130 0 Open + P_H_00476 J_10_33 J_10_34 120 200 130 0 Open + P_H_00477 J_10_34 J_10_35 120 200 130 0 Open + P_H_00478 J_10_35 J_10_36 120 200 130 0 Open + P_H_00479 J_10_36 J_10_37 120 200 130 0 Open + P_H_00480 J_10_37 J_10_38 120 200 130 0 Open + P_H_00481 J_10_38 J_10_39 120 200 130 0 Open + P_H_00482 J_10_39 J_10_40 120 200 130 0 Open + P_H_00483 J_10_40 J_10_41 120 200 130 0 Open + P_H_00484 J_10_41 J_10_42 120 200 130 0 Open + P_H_00485 J_10_42 J_10_43 120 200 130 0 Open + P_H_00486 J_10_43 J_10_44 120 200 130 0 Open + P_H_00487 J_11_00 J_11_01 120 200 130 0 Open + P_H_00488 J_11_01 J_11_02 120 200 130 0 Open + P_H_00489 J_11_02 J_11_03 120 200 130 0 Open + P_H_00490 J_11_03 J_11_04 120 200 130 0 Open + P_H_00491 J_11_04 J_11_05 120 200 130 0 Open + P_H_00492 J_11_05 J_11_06 120 200 130 0 Open + P_H_00493 J_11_06 J_11_07 120 200 130 0 Open + P_H_00494 J_11_07 J_11_08 120 200 130 0 Open + P_H_00495 J_11_08 J_11_09 120 200 130 0 Open + P_H_00496 J_11_09 J_11_10 120 200 130 0 Open + P_H_00497 J_11_10 J_11_11 120 200 130 0 Open + P_H_00498 J_11_11 J_11_12 120 200 130 0 Open + P_H_00499 J_11_12 J_11_13 120 200 130 0 Open + P_H_00500 J_11_13 J_11_14 120 200 130 0 Open + P_H_00501 J_11_14 J_11_15 120 200 130 0 Open + P_H_00502 J_11_15 J_11_16 120 200 130 0 Open + P_H_00503 J_11_16 J_11_17 120 200 130 0 Open + P_H_00504 J_11_17 J_11_18 120 200 130 0 Open + P_H_00505 J_11_18 J_11_19 120 200 130 0 Open + P_H_00506 J_11_19 J_11_20 120 200 130 0 Open + P_H_00507 J_11_20 J_11_21 120 200 130 0 Open + P_H_00508 J_11_21 J_11_22 120 200 130 0 Open + P_H_00509 J_11_22 J_11_23 120 200 130 0 Open + P_H_00510 J_11_23 J_11_24 120 200 130 0 Open + P_H_00511 J_11_24 J_11_25 120 200 130 0 Open + P_H_00512 J_11_25 J_11_26 120 200 130 0 Open + P_H_00513 J_11_26 J_11_27 120 200 130 0 Open + P_H_00514 J_11_27 J_11_28 120 200 130 0 Open + P_H_00515 J_11_28 J_11_29 120 200 130 0 Open + P_H_00516 J_11_29 J_11_30 120 200 130 0 Open + P_H_00517 J_11_30 J_11_31 120 200 130 0 Open + P_H_00518 J_11_31 J_11_32 120 200 130 0 Open + P_H_00519 J_11_32 J_11_33 120 200 130 0 Open + P_H_00520 J_11_33 J_11_34 120 200 130 0 Open + P_H_00521 J_11_34 J_11_35 120 200 130 0 Open + P_H_00522 J_11_35 J_11_36 120 200 130 0 Open + P_H_00523 J_11_36 J_11_37 120 200 130 0 Open + P_H_00524 J_11_37 J_11_38 120 200 130 0 Open + P_H_00525 J_11_38 J_11_39 120 200 130 0 Open + P_H_00526 J_11_39 J_11_40 120 200 130 0 Open + P_H_00527 J_11_40 J_11_41 120 200 130 0 Open + P_H_00528 J_11_41 J_11_42 120 200 130 0 Open + P_H_00529 J_11_42 J_11_43 120 200 130 0 Open + P_H_00530 J_11_43 J_11_44 120 200 130 0 Open + P_H_00531 J_12_00 J_12_01 120 200 130 0 Open + P_H_00532 J_12_01 J_12_02 120 200 130 0 Open + P_H_00533 J_12_02 J_12_03 120 200 130 0 Open + P_H_00534 J_12_03 J_12_04 120 200 130 0 Open + P_H_00535 J_12_04 J_12_05 120 200 130 0 Open + P_H_00536 J_12_05 J_12_06 120 200 130 0 Open + P_H_00537 J_12_06 J_12_07 120 200 130 0 Open + P_H_00538 J_12_07 J_12_08 120 200 130 0 Open + P_H_00539 J_12_08 J_12_09 120 200 130 0 Open + P_H_00540 J_12_09 J_12_10 120 200 130 0 Open + P_H_00541 J_12_10 J_12_11 120 200 130 0 Open + P_H_00542 J_12_11 J_12_12 120 200 130 0 Open + P_H_00543 J_12_12 J_12_13 120 200 130 0 Open + P_H_00544 J_12_13 J_12_14 120 200 130 0 Open + P_H_00545 J_12_14 J_12_15 120 200 130 0 Open + P_H_00546 J_12_15 J_12_16 120 200 130 0 Open + P_H_00547 J_12_16 J_12_17 120 200 130 0 Open + P_H_00548 J_12_17 J_12_18 120 200 130 0 Open + P_H_00549 J_12_18 J_12_19 120 200 130 0 Open + P_H_00550 J_12_19 J_12_20 120 200 130 0 Open + P_H_00551 J_12_20 J_12_21 120 200 130 0 Open + P_H_00552 J_12_21 J_12_22 120 200 130 0 Open + P_H_00553 J_12_22 J_12_23 120 200 130 0 Open + P_H_00554 J_12_23 J_12_24 120 200 130 0 Open + P_H_00555 J_12_24 J_12_25 120 200 130 0 Open + P_H_00556 J_12_25 J_12_26 120 200 130 0 Open + P_H_00557 J_12_26 J_12_27 120 200 130 0 Open + P_H_00558 J_12_27 J_12_28 120 200 130 0 Open + P_H_00559 J_12_28 J_12_29 120 200 130 0 Open + P_H_00560 J_12_29 J_12_30 120 200 130 0 Open + P_H_00561 J_12_30 J_12_31 120 200 130 0 Open + P_H_00562 J_12_31 J_12_32 120 200 130 0 Open + P_H_00563 J_12_32 J_12_33 120 200 130 0 Open + P_H_00564 J_12_33 J_12_34 120 200 130 0 Open + P_H_00565 J_12_34 J_12_35 120 200 130 0 Open + P_H_00566 J_12_35 J_12_36 120 200 130 0 Open + P_H_00567 J_12_36 J_12_37 120 200 130 0 Open + P_H_00568 J_12_37 J_12_38 120 200 130 0 Open + P_H_00569 J_12_38 J_12_39 120 200 130 0 Open + P_H_00570 J_12_39 J_12_40 120 200 130 0 Open + P_H_00571 J_12_40 J_12_41 120 200 130 0 Open + P_H_00572 J_12_41 J_12_42 120 200 130 0 Open + P_H_00573 J_12_42 J_12_43 120 200 130 0 Open + P_H_00574 J_12_43 J_12_44 120 200 130 0 Open + P_H_00575 J_13_00 J_13_01 120 200 130 0 Open + P_H_00576 J_13_01 J_13_02 120 200 130 0 Open + P_H_00577 J_13_02 J_13_03 120 200 130 0 Open + P_H_00578 J_13_03 J_13_04 120 200 130 0 Open + P_H_00579 J_13_04 J_13_05 120 200 130 0 Open + P_H_00580 J_13_05 J_13_06 120 200 130 0 Open + P_H_00581 J_13_06 J_13_07 120 200 130 0 Open + P_H_00582 J_13_07 J_13_08 120 200 130 0 Open + P_H_00583 J_13_08 J_13_09 120 200 130 0 Open + P_H_00584 J_13_09 J_13_10 120 200 130 0 Open + P_H_00585 J_13_10 J_13_11 120 200 130 0 Open + P_H_00586 J_13_11 J_13_12 120 200 130 0 Open + P_H_00587 J_13_12 J_13_13 120 200 130 0 Open + P_H_00588 J_13_13 J_13_14 120 200 130 0 Open + P_H_00589 J_13_14 J_13_15 120 200 130 0 Open + P_H_00590 J_13_15 J_13_16 120 200 130 0 Open + P_H_00591 J_13_16 J_13_17 120 200 130 0 Open + P_H_00592 J_13_17 J_13_18 120 200 130 0 Open + P_H_00593 J_13_18 J_13_19 120 200 130 0 Open + P_H_00594 J_13_19 J_13_20 120 200 130 0 Open + P_H_00595 J_13_20 J_13_21 120 200 130 0 Open + P_H_00596 J_13_21 J_13_22 120 200 130 0 Open + P_H_00597 J_13_22 J_13_23 120 200 130 0 Open + P_H_00598 J_13_23 J_13_24 120 200 130 0 Open + P_H_00599 J_13_24 J_13_25 120 200 130 0 Open + P_H_00600 J_13_25 J_13_26 120 200 130 0 Open + P_H_00601 J_13_26 J_13_27 120 200 130 0 Open + P_H_00602 J_13_27 J_13_28 120 200 130 0 Open + P_H_00603 J_13_28 J_13_29 120 200 130 0 Open + P_H_00604 J_13_29 J_13_30 120 200 130 0 Open + P_H_00605 J_13_30 J_13_31 120 200 130 0 Open + P_H_00606 J_13_31 J_13_32 120 200 130 0 Open + P_H_00607 J_13_32 J_13_33 120 200 130 0 Open + P_H_00608 J_13_33 J_13_34 120 200 130 0 Open + P_H_00609 J_13_34 J_13_35 120 200 130 0 Open + P_H_00610 J_13_35 J_13_36 120 200 130 0 Open + P_H_00611 J_13_36 J_13_37 120 200 130 0 Open + P_H_00612 J_13_37 J_13_38 120 200 130 0 Open + P_H_00613 J_13_38 J_13_39 120 200 130 0 Open + P_H_00614 J_13_39 J_13_40 120 200 130 0 Open + P_H_00615 J_13_40 J_13_41 120 200 130 0 Open + P_H_00616 J_13_41 J_13_42 120 200 130 0 Open + P_H_00617 J_13_42 J_13_43 120 200 130 0 Open + P_H_00618 J_13_43 J_13_44 120 200 130 0 Open + P_H_00619 J_14_00 J_14_01 120 200 130 0 Open + P_H_00620 J_14_01 J_14_02 120 200 130 0 Open + P_H_00621 J_14_02 J_14_03 120 200 130 0 Open + P_H_00622 J_14_03 J_14_04 120 200 130 0 Open + P_H_00623 J_14_04 J_14_05 120 200 130 0 Open + P_H_00624 J_14_05 J_14_06 120 200 130 0 Open + P_H_00625 J_14_06 J_14_07 120 200 130 0 Open + P_H_00626 J_14_07 J_14_08 120 200 130 0 Open + P_H_00627 J_14_08 J_14_09 120 200 130 0 Open + P_H_00628 J_14_09 J_14_10 120 200 130 0 Open + P_H_00629 J_14_10 J_14_11 120 200 130 0 Open + P_H_00630 J_14_11 J_14_12 120 200 130 0 Open + P_H_00631 J_14_12 J_14_13 120 200 130 0 Open + P_H_00632 J_14_13 J_14_14 120 200 130 0 Open + P_H_00633 J_14_14 J_14_15 120 200 130 0 Open + P_H_00634 J_14_15 J_14_16 120 200 130 0 Open + P_H_00635 J_14_16 J_14_17 120 200 130 0 Open + P_H_00636 J_14_17 J_14_18 120 200 130 0 Open + P_H_00637 J_14_18 J_14_19 120 200 130 0 Open + P_H_00638 J_14_19 J_14_20 120 200 130 0 Open + P_H_00639 J_14_20 J_14_21 120 200 130 0 Open + P_H_00640 J_14_21 J_14_22 120 200 130 0 Open + P_H_00641 J_14_22 J_14_23 120 200 130 0 Open + P_H_00642 J_14_23 J_14_24 120 200 130 0 Open + P_H_00643 J_14_24 J_14_25 120 200 130 0 Open + P_H_00644 J_14_25 J_14_26 120 200 130 0 Open + P_H_00645 J_14_26 J_14_27 120 200 130 0 Open + P_H_00646 J_14_27 J_14_28 120 200 130 0 Open + P_H_00647 J_14_28 J_14_29 120 200 130 0 Open + P_H_00648 J_14_29 J_14_30 120 200 130 0 Open + P_H_00649 J_14_30 J_14_31 120 200 130 0 Open + P_H_00650 J_14_31 J_14_32 120 200 130 0 Open + P_H_00651 J_14_32 J_14_33 120 200 130 0 Open + P_H_00652 J_14_33 J_14_34 120 200 130 0 Open + P_H_00653 J_14_34 J_14_35 120 200 130 0 Open + P_H_00654 J_14_35 J_14_36 120 200 130 0 Open + P_H_00655 J_14_36 J_14_37 120 200 130 0 Open + P_H_00656 J_14_37 J_14_38 120 200 130 0 Open + P_H_00657 J_14_38 J_14_39 120 200 130 0 Open + P_H_00658 J_14_39 J_14_40 120 200 130 0 Open + P_H_00659 J_14_40 J_14_41 120 200 130 0 Open + P_H_00660 J_14_41 J_14_42 120 200 130 0 Open + P_H_00661 J_14_42 J_14_43 120 200 130 0 Open + P_H_00662 J_14_43 J_14_44 120 200 130 0 Open + P_H_00663 J_15_00 J_15_01 120 200 130 0 Open + P_H_00664 J_15_01 J_15_02 120 200 130 0 Open + P_H_00665 J_15_02 J_15_03 120 200 130 0 Open + P_H_00666 J_15_03 J_15_04 120 200 130 0 Open + P_H_00667 J_15_04 J_15_05 120 200 130 0 Open + P_H_00668 J_15_05 J_15_06 120 200 130 0 Open + P_H_00669 J_15_06 J_15_07 120 200 130 0 Open + P_H_00670 J_15_07 J_15_08 120 200 130 0 Open + P_H_00671 J_15_08 J_15_09 120 200 130 0 Open + P_H_00672 J_15_09 J_15_10 120 200 130 0 Open + P_H_00673 J_15_10 J_15_11 120 200 130 0 Open + P_H_00674 J_15_11 J_15_12 120 200 130 0 Open + P_H_00675 J_15_12 J_15_13 120 200 130 0 Open + P_H_00676 J_15_13 J_15_14 120 200 130 0 Open + P_H_00677 J_15_14 J_15_15 120 200 130 0 Open + P_H_00678 J_15_15 J_15_16 120 200 130 0 Open + P_H_00679 J_15_16 J_15_17 120 200 130 0 Open + P_H_00680 J_15_17 J_15_18 120 200 130 0 Open + P_H_00681 J_15_18 J_15_19 120 200 130 0 Open + P_H_00682 J_15_19 J_15_20 120 200 130 0 Open + P_H_00683 J_15_20 J_15_21 120 200 130 0 Open + P_H_00684 J_15_21 J_15_22 120 200 130 0 Open + P_H_00685 J_15_22 J_15_23 120 200 130 0 Open + P_H_00686 J_15_23 J_15_24 120 200 130 0 Open + P_H_00687 J_15_24 J_15_25 120 200 130 0 Open + P_H_00688 J_15_25 J_15_26 120 200 130 0 Open + P_H_00689 J_15_26 J_15_27 120 200 130 0 Open + P_H_00690 J_15_27 J_15_28 120 200 130 0 Open + P_H_00691 J_15_28 J_15_29 120 200 130 0 Open + P_H_00692 J_15_29 J_15_30 120 200 130 0 Open + P_H_00693 J_15_30 J_15_31 120 200 130 0 Open + P_H_00694 J_15_31 J_15_32 120 200 130 0 Open + P_H_00695 J_15_32 J_15_33 120 200 130 0 Open + P_H_00696 J_15_33 J_15_34 120 200 130 0 Open + P_H_00697 J_15_34 J_15_35 120 200 130 0 Open + P_H_00698 J_15_35 J_15_36 120 200 130 0 Open + P_H_00699 J_15_36 J_15_37 120 200 130 0 Open + P_H_00700 J_15_37 J_15_38 120 200 130 0 Open + P_H_00701 J_15_38 J_15_39 120 200 130 0 Open + P_H_00702 J_15_39 J_15_40 120 200 130 0 Open + P_H_00703 J_15_40 J_15_41 120 200 130 0 Open + P_H_00704 J_15_41 J_15_42 120 200 130 0 Open + P_H_00705 J_15_42 J_15_43 120 200 130 0 Open + P_H_00706 J_15_43 J_15_44 120 200 130 0 Open + P_H_00707 J_16_00 J_16_01 120 200 130 0 Open + P_H_00708 J_16_01 J_16_02 120 200 130 0 Open + P_H_00709 J_16_02 J_16_03 120 200 130 0 Open + P_H_00710 J_16_03 J_16_04 120 200 130 0 Open + P_H_00711 J_16_04 J_16_05 120 200 130 0 Open + P_H_00712 J_16_05 J_16_06 120 200 130 0 Open + P_H_00713 J_16_06 J_16_07 120 200 130 0 Open + P_H_00714 J_16_07 J_16_08 120 200 130 0 Open + P_H_00715 J_16_08 J_16_09 120 200 130 0 Open + P_H_00716 J_16_09 J_16_10 120 200 130 0 Open + P_H_00717 J_16_10 J_16_11 120 200 130 0 Open + P_H_00718 J_16_11 J_16_12 120 200 130 0 Open + P_H_00719 J_16_12 J_16_13 120 200 130 0 Open + P_H_00720 J_16_13 J_16_14 120 200 130 0 Open + P_H_00721 J_16_14 J_16_15 120 200 130 0 Open + P_H_00722 J_16_15 J_16_16 120 200 130 0 Open + P_H_00723 J_16_16 J_16_17 120 200 130 0 Open + P_H_00724 J_16_17 J_16_18 120 200 130 0 Open + P_H_00725 J_16_18 J_16_19 120 200 130 0 Open + P_H_00726 J_16_19 J_16_20 120 200 130 0 Open + P_H_00727 J_16_20 J_16_21 120 200 130 0 Open + P_H_00728 J_16_21 J_16_22 120 200 130 0 Open + P_H_00729 J_16_22 J_16_23 120 200 130 0 Open + P_H_00730 J_16_23 J_16_24 120 200 130 0 Open + P_H_00731 J_16_24 J_16_25 120 200 130 0 Open + P_H_00732 J_16_25 J_16_26 120 200 130 0 Open + P_H_00733 J_16_26 J_16_27 120 200 130 0 Open + P_H_00734 J_16_27 J_16_28 120 200 130 0 Open + P_H_00735 J_16_28 J_16_29 120 200 130 0 Open + P_H_00736 J_16_29 J_16_30 120 200 130 0 Open + P_H_00737 J_16_30 J_16_31 120 200 130 0 Open + P_H_00738 J_16_31 J_16_32 120 200 130 0 Open + P_H_00739 J_16_32 J_16_33 120 200 130 0 Open + P_H_00740 J_16_33 J_16_34 120 200 130 0 Open + P_H_00741 J_16_34 J_16_35 120 200 130 0 Open + P_H_00742 J_16_35 J_16_36 120 200 130 0 Open + P_H_00743 J_16_36 J_16_37 120 200 130 0 Open + P_H_00744 J_16_37 J_16_38 120 200 130 0 Open + P_H_00745 J_16_38 J_16_39 120 200 130 0 Open + P_H_00746 J_16_39 J_16_40 120 200 130 0 Open + P_H_00747 J_16_40 J_16_41 120 200 130 0 Open + P_H_00748 J_16_41 J_16_42 120 200 130 0 Open + P_H_00749 J_16_42 J_16_43 120 200 130 0 Open + P_H_00750 J_16_43 J_16_44 120 200 130 0 Open + P_H_00751 J_17_00 J_17_01 120 200 130 0 Open + P_H_00752 J_17_01 J_17_02 120 200 130 0 Open + P_H_00753 J_17_02 J_17_03 120 200 130 0 Open + P_H_00754 J_17_03 J_17_04 120 200 130 0 Open + P_H_00755 J_17_04 J_17_05 120 200 130 0 Open + P_H_00756 J_17_05 J_17_06 120 200 130 0 Open + P_H_00757 J_17_06 J_17_07 120 200 130 0 Open + P_H_00758 J_17_07 J_17_08 120 200 130 0 Open + P_H_00759 J_17_08 J_17_09 120 200 130 0 Open + P_H_00760 J_17_09 J_17_10 120 200 130 0 Open + P_H_00761 J_17_10 J_17_11 120 200 130 0 Open + P_H_00762 J_17_11 J_17_12 120 200 130 0 Open + P_H_00763 J_17_12 J_17_13 120 200 130 0 Open + P_H_00764 J_17_13 J_17_14 120 200 130 0 Open + P_H_00765 J_17_14 J_17_15 120 200 130 0 Open + P_H_00766 J_17_15 J_17_16 120 200 130 0 Open + P_H_00767 J_17_16 J_17_17 120 200 130 0 Open + P_H_00768 J_17_17 J_17_18 120 200 130 0 Open + P_H_00769 J_17_18 J_17_19 120 200 130 0 Open + P_H_00770 J_17_19 J_17_20 120 200 130 0 Open + P_H_00771 J_17_20 J_17_21 120 200 130 0 Open + P_H_00772 J_17_21 J_17_22 120 200 130 0 Open + P_H_00773 J_17_22 J_17_23 120 200 130 0 Open + P_H_00774 J_17_23 J_17_24 120 200 130 0 Open + P_H_00775 J_17_24 J_17_25 120 200 130 0 Open + P_H_00776 J_17_25 J_17_26 120 200 130 0 Open + P_H_00777 J_17_26 J_17_27 120 200 130 0 Open + P_H_00778 J_17_27 J_17_28 120 200 130 0 Open + P_H_00779 J_17_28 J_17_29 120 200 130 0 Open + P_H_00780 J_17_29 J_17_30 120 200 130 0 Open + P_H_00781 J_17_30 J_17_31 120 200 130 0 Open + P_H_00782 J_17_31 J_17_32 120 200 130 0 Open + P_H_00783 J_17_32 J_17_33 120 200 130 0 Open + P_H_00784 J_17_33 J_17_34 120 200 130 0 Open + P_H_00785 J_17_34 J_17_35 120 200 130 0 Open + P_H_00786 J_17_35 J_17_36 120 200 130 0 Open + P_H_00787 J_17_36 J_17_37 120 200 130 0 Open + P_H_00788 J_17_37 J_17_38 120 200 130 0 Open + P_H_00789 J_17_38 J_17_39 120 200 130 0 Open + P_H_00790 J_17_39 J_17_40 120 200 130 0 Open + P_H_00791 J_17_40 J_17_41 120 200 130 0 Open + P_H_00792 J_17_41 J_17_42 120 200 130 0 Open + P_H_00793 J_17_42 J_17_43 120 200 130 0 Open + P_H_00794 J_17_43 J_17_44 120 200 130 0 Open + P_H_00795 J_18_00 J_18_01 120 200 130 0 Open + P_H_00796 J_18_01 J_18_02 120 200 130 0 Open + P_H_00797 J_18_02 J_18_03 120 200 130 0 Open + P_H_00798 J_18_03 J_18_04 120 200 130 0 Open + P_H_00799 J_18_04 J_18_05 120 200 130 0 Open + P_H_00800 J_18_05 J_18_06 120 200 130 0 Open + P_H_00801 J_18_06 J_18_07 120 200 130 0 Open + P_H_00802 J_18_07 J_18_08 120 200 130 0 Open + P_H_00803 J_18_08 J_18_09 120 200 130 0 Open + P_H_00804 J_18_09 J_18_10 120 200 130 0 Open + P_H_00805 J_18_10 J_18_11 120 200 130 0 Open + P_H_00806 J_18_11 J_18_12 120 200 130 0 Open + P_H_00807 J_18_12 J_18_13 120 200 130 0 Open + P_H_00808 J_18_13 J_18_14 120 200 130 0 Open + P_H_00809 J_18_14 J_18_15 120 200 130 0 Open + P_H_00810 J_18_15 J_18_16 120 200 130 0 Open + P_H_00811 J_18_16 J_18_17 120 200 130 0 Open + P_H_00812 J_18_17 J_18_18 120 200 130 0 Open + P_H_00813 J_18_18 J_18_19 120 200 130 0 Open + P_H_00814 J_18_19 J_18_20 120 200 130 0 Open + P_H_00815 J_18_20 J_18_21 120 200 130 0 Open + P_H_00816 J_18_21 J_18_22 120 200 130 0 Open + P_H_00817 J_18_22 J_18_23 120 200 130 0 Open + P_H_00818 J_18_23 J_18_24 120 200 130 0 Open + P_H_00819 J_18_24 J_18_25 120 200 130 0 Open + P_H_00820 J_18_25 J_18_26 120 200 130 0 Open + P_H_00821 J_18_26 J_18_27 120 200 130 0 Open + P_H_00822 J_18_27 J_18_28 120 200 130 0 Open + P_H_00823 J_18_28 J_18_29 120 200 130 0 Open + P_H_00824 J_18_29 J_18_30 120 200 130 0 Open + P_H_00825 J_18_30 J_18_31 120 200 130 0 Open + P_H_00826 J_18_31 J_18_32 120 200 130 0 Open + P_H_00827 J_18_32 J_18_33 120 200 130 0 Open + P_H_00828 J_18_33 J_18_34 120 200 130 0 Open + P_H_00829 J_18_34 J_18_35 120 200 130 0 Open + P_H_00830 J_18_35 J_18_36 120 200 130 0 Open + P_H_00831 J_18_36 J_18_37 120 200 130 0 Open + P_H_00832 J_18_37 J_18_38 120 200 130 0 Open + P_H_00833 J_18_38 J_18_39 120 200 130 0 Open + P_H_00834 J_18_39 J_18_40 120 200 130 0 Open + P_H_00835 J_18_40 J_18_41 120 200 130 0 Open + P_H_00836 J_18_41 J_18_42 120 200 130 0 Open + P_H_00837 J_18_42 J_18_43 120 200 130 0 Open + P_H_00838 J_18_43 J_18_44 120 200 130 0 Open + P_H_00839 J_19_00 J_19_01 120 200 130 0 Open + P_H_00840 J_19_01 J_19_02 120 200 130 0 Open + P_H_00841 J_19_02 J_19_03 120 200 130 0 Open + P_H_00842 J_19_03 J_19_04 120 200 130 0 Open + P_H_00843 J_19_04 J_19_05 120 200 130 0 Open + P_H_00844 J_19_05 J_19_06 120 200 130 0 Open + P_H_00845 J_19_06 J_19_07 120 200 130 0 Open + P_H_00846 J_19_07 J_19_08 120 200 130 0 Open + P_H_00847 J_19_08 J_19_09 120 200 130 0 Open + P_H_00848 J_19_09 J_19_10 120 200 130 0 Open + P_H_00849 J_19_10 J_19_11 120 200 130 0 Open + P_H_00850 J_19_11 J_19_12 120 200 130 0 Open + P_H_00851 J_19_12 J_19_13 120 200 130 0 Open + P_H_00852 J_19_13 J_19_14 120 200 130 0 Open + P_H_00853 J_19_14 J_19_15 120 200 130 0 Open + P_H_00854 J_19_15 J_19_16 120 200 130 0 Open + P_H_00855 J_19_16 J_19_17 120 200 130 0 Open + P_H_00856 J_19_17 J_19_18 120 200 130 0 Open + P_H_00857 J_19_18 J_19_19 120 200 130 0 Open + P_H_00858 J_19_19 J_19_20 120 200 130 0 Open + P_H_00859 J_19_20 J_19_21 120 200 130 0 Open + P_H_00860 J_19_21 J_19_22 120 200 130 0 Open + P_H_00861 J_19_22 J_19_23 120 200 130 0 Open + P_H_00862 J_19_23 J_19_24 120 200 130 0 Open + P_H_00863 J_19_24 J_19_25 120 200 130 0 Open + P_H_00864 J_19_25 J_19_26 120 200 130 0 Open + P_H_00865 J_19_26 J_19_27 120 200 130 0 Open + P_H_00866 J_19_27 J_19_28 120 200 130 0 Open + P_H_00867 J_19_28 J_19_29 120 200 130 0 Open + P_H_00868 J_19_29 J_19_30 120 200 130 0 Open + P_H_00869 J_19_30 J_19_31 120 200 130 0 Open + P_H_00870 J_19_31 J_19_32 120 200 130 0 Open + P_H_00871 J_19_32 J_19_33 120 200 130 0 Open + P_H_00872 J_19_33 J_19_34 120 200 130 0 Open + P_H_00873 J_19_34 J_19_35 120 200 130 0 Open + P_H_00874 J_19_35 J_19_36 120 200 130 0 Open + P_H_00875 J_19_36 J_19_37 120 200 130 0 Open + P_H_00876 J_19_37 J_19_38 120 200 130 0 Open + P_H_00877 J_19_38 J_19_39 120 200 130 0 Open + P_H_00878 J_19_39 J_19_40 120 200 130 0 Open + P_H_00879 J_19_40 J_19_41 120 200 130 0 Open + P_H_00880 J_19_41 J_19_42 120 200 130 0 Open + P_H_00881 J_19_42 J_19_43 120 200 130 0 Open + P_H_00882 J_19_43 J_19_44 120 200 130 0 Open + P_H_00883 J_20_00 J_20_01 120 200 130 0 Open + P_H_00884 J_20_01 J_20_02 120 200 130 0 Open + P_H_00885 J_20_02 J_20_03 120 200 130 0 Open + P_H_00886 J_20_03 J_20_04 120 200 130 0 Open + P_H_00887 J_20_04 J_20_05 120 200 130 0 Open + P_H_00888 J_20_05 J_20_06 120 200 130 0 Open + P_H_00889 J_20_06 J_20_07 120 200 130 0 Open + P_H_00890 J_20_07 J_20_08 120 200 130 0 Open + P_H_00891 J_20_08 J_20_09 120 200 130 0 Open + P_H_00892 J_20_09 J_20_10 120 200 130 0 Open + P_H_00893 J_20_10 J_20_11 120 200 130 0 Open + P_H_00894 J_20_11 J_20_12 120 200 130 0 Open + P_H_00895 J_20_12 J_20_13 120 200 130 0 Open + P_H_00896 J_20_13 J_20_14 120 200 130 0 Open + P_H_00897 J_20_14 J_20_15 120 200 130 0 Open + P_H_00898 J_20_15 J_20_16 120 200 130 0 Open + P_H_00899 J_20_16 J_20_17 120 200 130 0 Open + P_H_00900 J_20_17 J_20_18 120 200 130 0 Open + P_H_00901 J_20_18 J_20_19 120 200 130 0 Open + P_H_00902 J_20_19 J_20_20 120 200 130 0 Open + P_H_00903 J_20_20 J_20_21 120 200 130 0 Open + P_H_00904 J_20_21 J_20_22 120 200 130 0 Open + P_H_00905 J_20_22 J_20_23 120 200 130 0 Open + P_H_00906 J_20_23 J_20_24 120 200 130 0 Open + P_H_00907 J_20_24 J_20_25 120 200 130 0 Open + P_H_00908 J_20_25 J_20_26 120 200 130 0 Open + P_H_00909 J_20_26 J_20_27 120 200 130 0 Open + P_H_00910 J_20_27 J_20_28 120 200 130 0 Open + P_H_00911 J_20_28 J_20_29 120 200 130 0 Open + P_H_00912 J_20_29 J_20_30 120 200 130 0 Open + P_H_00913 J_20_30 J_20_31 120 200 130 0 Open + P_H_00914 J_20_31 J_20_32 120 200 130 0 Open + P_H_00915 J_20_32 J_20_33 120 200 130 0 Open + P_H_00916 J_20_33 J_20_34 120 200 130 0 Open + P_H_00917 J_20_34 J_20_35 120 200 130 0 Open + P_H_00918 J_20_35 J_20_36 120 200 130 0 Open + P_H_00919 J_20_36 J_20_37 120 200 130 0 Open + P_H_00920 J_20_37 J_20_38 120 200 130 0 Open + P_H_00921 J_20_38 J_20_39 120 200 130 0 Open + P_H_00922 J_20_39 J_20_40 120 200 130 0 Open + P_H_00923 J_20_40 J_20_41 120 200 130 0 Open + P_H_00924 J_20_41 J_20_42 120 200 130 0 Open + P_H_00925 J_20_42 J_20_43 120 200 130 0 Open + P_H_00926 J_20_43 J_20_44 120 200 130 0 Open + P_H_00927 J_21_00 J_21_01 120 200 130 0 Open + P_H_00928 J_21_01 J_21_02 120 200 130 0 Open + P_H_00929 J_21_02 J_21_03 120 200 130 0 Open + P_H_00930 J_21_03 J_21_04 120 200 130 0 Open + P_H_00931 J_21_04 J_21_05 120 200 130 0 Open + P_H_00932 J_21_05 J_21_06 120 200 130 0 Open + P_H_00933 J_21_06 J_21_07 120 200 130 0 Open + P_H_00934 J_21_07 J_21_08 120 200 130 0 Open + P_H_00935 J_21_08 J_21_09 120 200 130 0 Open + P_H_00936 J_21_09 J_21_10 120 200 130 0 Open + P_H_00937 J_21_10 J_21_11 120 200 130 0 Open + P_H_00938 J_21_11 J_21_12 120 200 130 0 Open + P_H_00939 J_21_12 J_21_13 120 200 130 0 Open + P_H_00940 J_21_13 J_21_14 120 200 130 0 Open + P_H_00941 J_21_14 J_21_15 120 200 130 0 Open + P_H_00942 J_21_15 J_21_16 120 200 130 0 Open + P_H_00943 J_21_16 J_21_17 120 200 130 0 Open + P_H_00944 J_21_17 J_21_18 120 200 130 0 Open + P_H_00945 J_21_18 J_21_19 120 200 130 0 Open + P_H_00946 J_21_19 J_21_20 120 200 130 0 Open + P_H_00947 J_21_20 J_21_21 120 200 130 0 Open + P_H_00948 J_21_21 J_21_22 120 200 130 0 Open + P_H_00949 J_21_22 J_21_23 120 200 130 0 Open + P_H_00950 J_21_23 J_21_24 120 200 130 0 Open + P_H_00951 J_21_24 J_21_25 120 200 130 0 Open + P_H_00952 J_21_25 J_21_26 120 200 130 0 Open + P_H_00953 J_21_26 J_21_27 120 200 130 0 Open + P_H_00954 J_21_27 J_21_28 120 200 130 0 Open + P_H_00955 J_21_28 J_21_29 120 200 130 0 Open + P_H_00956 J_21_29 J_21_30 120 200 130 0 Open + P_H_00957 J_21_30 J_21_31 120 200 130 0 Open + P_H_00958 J_21_31 J_21_32 120 200 130 0 Open + P_H_00959 J_21_32 J_21_33 120 200 130 0 Open + P_H_00960 J_21_33 J_21_34 120 200 130 0 Open + P_H_00961 J_21_34 J_21_35 120 200 130 0 Open + P_H_00962 J_21_35 J_21_36 120 200 130 0 Open + P_H_00963 J_21_36 J_21_37 120 200 130 0 Open + P_H_00964 J_21_37 J_21_38 120 200 130 0 Open + P_H_00965 J_21_38 J_21_39 120 200 130 0 Open + P_H_00966 J_21_39 J_21_40 120 200 130 0 Open + P_H_00967 J_21_40 J_21_41 120 200 130 0 Open + P_H_00968 J_21_41 J_21_42 120 200 130 0 Open + P_H_00969 J_21_42 J_21_43 120 200 130 0 Open + P_H_00970 J_21_43 J_21_44 120 200 130 0 Open + P_H_00971 J_22_00 J_22_01 120 200 130 0 Open + P_H_00972 J_22_01 J_22_02 120 200 130 0 Open + P_H_00973 J_22_02 J_22_03 120 200 130 0 Open + P_H_00974 J_22_03 J_22_04 120 200 130 0 Open + P_H_00975 J_22_04 J_22_05 120 200 130 0 Open + P_H_00976 J_22_05 J_22_06 120 200 130 0 Open + P_H_00977 J_22_06 J_22_07 120 200 130 0 Open + P_H_00978 J_22_07 J_22_08 120 200 130 0 Open + P_H_00979 J_22_08 J_22_09 120 200 130 0 Open + P_H_00980 J_22_09 J_22_10 120 200 130 0 Open + P_H_00981 J_22_10 J_22_11 120 200 130 0 Open + P_H_00982 J_22_11 J_22_12 120 200 130 0 Open + P_H_00983 J_22_12 J_22_13 120 200 130 0 Open + P_H_00984 J_22_13 J_22_14 120 200 130 0 Open + P_H_00985 J_22_14 J_22_15 120 200 130 0 Open + P_H_00986 J_22_15 J_22_16 120 200 130 0 Open + P_H_00987 J_22_16 J_22_17 120 200 130 0 Open + P_H_00988 J_22_17 J_22_18 120 200 130 0 Open + P_H_00989 J_22_18 J_22_19 120 200 130 0 Open + P_H_00990 J_22_19 J_22_20 120 200 130 0 Open + P_H_00991 J_22_20 J_22_21 120 200 130 0 Open + P_H_00992 J_22_21 J_22_22 120 200 130 0 Open + P_H_00993 J_22_22 J_22_23 120 200 130 0 Open + P_H_00994 J_22_23 J_22_24 120 200 130 0 Open + P_H_00995 J_22_24 J_22_25 120 200 130 0 Open + P_H_00996 J_22_25 J_22_26 120 200 130 0 Open + P_H_00997 J_22_26 J_22_27 120 200 130 0 Open + P_H_00998 J_22_27 J_22_28 120 200 130 0 Open + P_H_00999 J_22_28 J_22_29 120 200 130 0 Open + P_H_01000 J_22_29 J_22_30 120 200 130 0 Open + P_H_01001 J_22_30 J_22_31 120 200 130 0 Open + P_H_01002 J_22_31 J_22_32 120 200 130 0 Open + P_H_01003 J_22_32 J_22_33 120 200 130 0 Open + P_H_01004 J_22_33 J_22_34 120 200 130 0 Open + P_H_01005 J_22_34 J_22_35 120 200 130 0 Open + P_H_01006 J_22_35 J_22_36 120 200 130 0 Open + P_H_01007 J_22_36 J_22_37 120 200 130 0 Open + P_H_01008 J_22_37 J_22_38 120 200 130 0 Open + P_H_01009 J_22_38 J_22_39 120 200 130 0 Open + P_H_01010 J_22_39 J_22_40 120 200 130 0 Open + P_H_01011 J_22_40 J_22_41 120 200 130 0 Open + P_H_01012 J_22_41 J_22_42 120 200 130 0 Open + P_H_01013 J_22_42 J_22_43 120 200 130 0 Open + P_H_01014 J_22_43 J_22_44 120 200 130 0 Open + P_H_01015 J_23_00 J_23_01 120 200 130 0 Open + P_H_01016 J_23_01 J_23_02 120 200 130 0 Open + P_H_01017 J_23_02 J_23_03 120 200 130 0 Open + P_H_01018 J_23_03 J_23_04 120 200 130 0 Open + P_H_01019 J_23_04 J_23_05 120 200 130 0 Open + P_H_01020 J_23_05 J_23_06 120 200 130 0 Open + P_H_01021 J_23_06 J_23_07 120 200 130 0 Open + P_H_01022 J_23_07 J_23_08 120 200 130 0 Open + P_H_01023 J_23_08 J_23_09 120 200 130 0 Open + P_H_01024 J_23_09 J_23_10 120 200 130 0 Open + P_H_01025 J_23_10 J_23_11 120 200 130 0 Open + P_H_01026 J_23_11 J_23_12 120 200 130 0 Open + P_H_01027 J_23_12 J_23_13 120 200 130 0 Open + P_H_01028 J_23_13 J_23_14 120 200 130 0 Open + P_H_01029 J_23_14 J_23_15 120 200 130 0 Open + P_H_01030 J_23_15 J_23_16 120 200 130 0 Open + P_H_01031 J_23_16 J_23_17 120 200 130 0 Open + P_H_01032 J_23_17 J_23_18 120 200 130 0 Open + P_H_01033 J_23_18 J_23_19 120 200 130 0 Open + P_H_01034 J_23_19 J_23_20 120 200 130 0 Open + P_H_01035 J_23_20 J_23_21 120 200 130 0 Open + P_H_01036 J_23_21 J_23_22 120 200 130 0 Open + P_H_01037 J_23_22 J_23_23 120 200 130 0 Open + P_H_01038 J_23_23 J_23_24 120 200 130 0 Open + P_H_01039 J_23_24 J_23_25 120 200 130 0 Open + P_H_01040 J_23_25 J_23_26 120 200 130 0 Open + P_H_01041 J_23_26 J_23_27 120 200 130 0 Open + P_H_01042 J_23_27 J_23_28 120 200 130 0 Open + P_H_01043 J_23_28 J_23_29 120 200 130 0 Open + P_H_01044 J_23_29 J_23_30 120 200 130 0 Open + P_H_01045 J_23_30 J_23_31 120 200 130 0 Open + P_H_01046 J_23_31 J_23_32 120 200 130 0 Open + P_H_01047 J_23_32 J_23_33 120 200 130 0 Open + P_H_01048 J_23_33 J_23_34 120 200 130 0 Open + P_H_01049 J_23_34 J_23_35 120 200 130 0 Open + P_H_01050 J_23_35 J_23_36 120 200 130 0 Open + P_H_01051 J_23_36 J_23_37 120 200 130 0 Open + P_H_01052 J_23_37 J_23_38 120 200 130 0 Open + P_H_01053 J_23_38 J_23_39 120 200 130 0 Open + P_H_01054 J_23_39 J_23_40 120 200 130 0 Open + P_H_01055 J_23_40 J_23_41 120 200 130 0 Open + P_H_01056 J_23_41 J_23_42 120 200 130 0 Open + P_H_01057 J_23_42 J_23_43 120 200 130 0 Open + P_H_01058 J_23_43 J_23_44 120 200 130 0 Open + P_H_01059 J_24_00 J_24_01 120 200 130 0 Open + P_H_01060 J_24_01 J_24_02 120 200 130 0 Open + P_H_01061 J_24_02 J_24_03 120 200 130 0 Open + P_H_01062 J_24_03 J_24_04 120 200 130 0 Open + P_H_01063 J_24_04 J_24_05 120 200 130 0 Open + P_H_01064 J_24_05 J_24_06 120 200 130 0 Open + P_H_01065 J_24_06 J_24_07 120 200 130 0 Open + P_H_01066 J_24_07 J_24_08 120 200 130 0 Open + P_H_01067 J_24_08 J_24_09 120 200 130 0 Open + P_H_01068 J_24_09 J_24_10 120 200 130 0 Open + P_H_01069 J_24_10 J_24_11 120 200 130 0 Open + P_H_01070 J_24_11 J_24_12 120 200 130 0 Open + P_H_01071 J_24_12 J_24_13 120 200 130 0 Open + P_H_01072 J_24_13 J_24_14 120 200 130 0 Open + P_H_01073 J_24_14 J_24_15 120 200 130 0 Open + P_H_01074 J_24_15 J_24_16 120 200 130 0 Open + P_H_01075 J_24_16 J_24_17 120 200 130 0 Open + P_H_01076 J_24_17 J_24_18 120 200 130 0 Open + P_H_01077 J_24_18 J_24_19 120 200 130 0 Open + P_H_01078 J_24_19 J_24_20 120 200 130 0 Open + P_H_01079 J_24_20 J_24_21 120 200 130 0 Open + P_H_01080 J_24_21 J_24_22 120 200 130 0 Open + P_H_01081 J_24_22 J_24_23 120 200 130 0 Open + P_H_01082 J_24_23 J_24_24 120 200 130 0 Open + P_H_01083 J_24_24 J_24_25 120 200 130 0 Open + P_H_01084 J_24_25 J_24_26 120 200 130 0 Open + P_H_01085 J_24_26 J_24_27 120 200 130 0 Open + P_H_01086 J_24_27 J_24_28 120 200 130 0 Open + P_H_01087 J_24_28 J_24_29 120 200 130 0 Open + P_H_01088 J_24_29 J_24_30 120 200 130 0 Open + P_H_01089 J_24_30 J_24_31 120 200 130 0 Open + P_H_01090 J_24_31 J_24_32 120 200 130 0 Open + P_H_01091 J_24_32 J_24_33 120 200 130 0 Open + P_H_01092 J_24_33 J_24_34 120 200 130 0 Open + P_H_01093 J_24_34 J_24_35 120 200 130 0 Open + P_H_01094 J_24_35 J_24_36 120 200 130 0 Open + P_H_01095 J_24_36 J_24_37 120 200 130 0 Open + P_H_01096 J_24_37 J_24_38 120 200 130 0 Open + P_H_01097 J_24_38 J_24_39 120 200 130 0 Open + P_H_01098 J_24_39 J_24_40 120 200 130 0 Open + P_H_01099 J_24_40 J_24_41 120 200 130 0 Open + P_H_01100 J_24_41 J_24_42 120 200 130 0 Open + P_H_01101 J_24_42 J_24_43 120 200 130 0 Open + P_H_01102 J_24_43 J_24_44 120 200 130 0 Open + P_H_01103 J_25_00 J_25_01 120 200 130 0 Open + P_H_01104 J_25_01 J_25_02 120 200 130 0 Open + P_H_01105 J_25_02 J_25_03 120 200 130 0 Open + P_H_01106 J_25_03 J_25_04 120 200 130 0 Open + P_H_01107 J_25_04 J_25_05 120 200 130 0 Open + P_H_01108 J_25_05 J_25_06 120 200 130 0 Open + P_H_01109 J_25_06 J_25_07 120 200 130 0 Open + P_H_01110 J_25_07 J_25_08 120 200 130 0 Open + P_H_01111 J_25_08 J_25_09 120 200 130 0 Open + P_H_01112 J_25_09 J_25_10 120 200 130 0 Open + P_H_01113 J_25_10 J_25_11 120 200 130 0 Open + P_H_01114 J_25_11 J_25_12 120 200 130 0 Open + P_H_01115 J_25_12 J_25_13 120 200 130 0 Open + P_H_01116 J_25_13 J_25_14 120 200 130 0 Open + P_H_01117 J_25_14 J_25_15 120 200 130 0 Open + P_H_01118 J_25_15 J_25_16 120 200 130 0 Open + P_H_01119 J_25_16 J_25_17 120 200 130 0 Open + P_H_01120 J_25_17 J_25_18 120 200 130 0 Open + P_H_01121 J_25_18 J_25_19 120 200 130 0 Open + P_H_01122 J_25_19 J_25_20 120 200 130 0 Open + P_H_01123 J_25_20 J_25_21 120 200 130 0 Open + P_H_01124 J_25_21 J_25_22 120 200 130 0 Open + P_H_01125 J_25_22 J_25_23 120 200 130 0 Open + P_H_01126 J_25_23 J_25_24 120 200 130 0 Open + P_H_01127 J_25_24 J_25_25 120 200 130 0 Open + P_H_01128 J_25_25 J_25_26 120 200 130 0 Open + P_H_01129 J_25_26 J_25_27 120 200 130 0 Open + P_H_01130 J_25_27 J_25_28 120 200 130 0 Open + P_H_01131 J_25_28 J_25_29 120 200 130 0 Open + P_H_01132 J_25_29 J_25_30 120 200 130 0 Open + P_H_01133 J_25_30 J_25_31 120 200 130 0 Open + P_H_01134 J_25_31 J_25_32 120 200 130 0 Open + P_H_01135 J_25_32 J_25_33 120 200 130 0 Open + P_H_01136 J_25_33 J_25_34 120 200 130 0 Open + P_H_01137 J_25_34 J_25_35 120 200 130 0 Open + P_H_01138 J_25_35 J_25_36 120 200 130 0 Open + P_H_01139 J_25_36 J_25_37 120 200 130 0 Open + P_H_01140 J_25_37 J_25_38 120 200 130 0 Open + P_H_01141 J_25_38 J_25_39 120 200 130 0 Open + P_H_01142 J_25_39 J_25_40 120 200 130 0 Open + P_H_01143 J_25_40 J_25_41 120 200 130 0 Open + P_H_01144 J_25_41 J_25_42 120 200 130 0 Open + P_H_01145 J_25_42 J_25_43 120 200 130 0 Open + P_H_01146 J_25_43 J_25_44 120 200 130 0 Open + P_H_01147 J_26_00 J_26_01 120 200 130 0 Open + P_H_01148 J_26_01 J_26_02 120 200 130 0 Open + P_H_01149 J_26_02 J_26_03 120 200 130 0 Open + P_H_01150 J_26_03 J_26_04 120 200 130 0 Open + P_H_01151 J_26_04 J_26_05 120 200 130 0 Open + P_H_01152 J_26_05 J_26_06 120 200 130 0 Open + P_H_01153 J_26_06 J_26_07 120 200 130 0 Open + P_H_01154 J_26_07 J_26_08 120 200 130 0 Open + P_H_01155 J_26_08 J_26_09 120 200 130 0 Open + P_H_01156 J_26_09 J_26_10 120 200 130 0 Open + P_H_01157 J_26_10 J_26_11 120 200 130 0 Open + P_H_01158 J_26_11 J_26_12 120 200 130 0 Open + P_H_01159 J_26_12 J_26_13 120 200 130 0 Open + P_H_01160 J_26_13 J_26_14 120 200 130 0 Open + P_H_01161 J_26_14 J_26_15 120 200 130 0 Open + P_H_01162 J_26_15 J_26_16 120 200 130 0 Open + P_H_01163 J_26_16 J_26_17 120 200 130 0 Open + P_H_01164 J_26_17 J_26_18 120 200 130 0 Open + P_H_01165 J_26_18 J_26_19 120 200 130 0 Open + P_H_01166 J_26_19 J_26_20 120 200 130 0 Open + P_H_01167 J_26_20 J_26_21 120 200 130 0 Open + P_H_01168 J_26_21 J_26_22 120 200 130 0 Open + P_H_01169 J_26_22 J_26_23 120 200 130 0 Open + P_H_01170 J_26_23 J_26_24 120 200 130 0 Open + P_H_01171 J_26_24 J_26_25 120 200 130 0 Open + P_H_01172 J_26_25 J_26_26 120 200 130 0 Open + P_H_01173 J_26_26 J_26_27 120 200 130 0 Open + P_H_01174 J_26_27 J_26_28 120 200 130 0 Open + P_H_01175 J_26_28 J_26_29 120 200 130 0 Open + P_H_01176 J_26_29 J_26_30 120 200 130 0 Open + P_H_01177 J_26_30 J_26_31 120 200 130 0 Open + P_H_01178 J_26_31 J_26_32 120 200 130 0 Open + P_H_01179 J_26_32 J_26_33 120 200 130 0 Open + P_H_01180 J_26_33 J_26_34 120 200 130 0 Open + P_H_01181 J_26_34 J_26_35 120 200 130 0 Open + P_H_01182 J_26_35 J_26_36 120 200 130 0 Open + P_H_01183 J_26_36 J_26_37 120 200 130 0 Open + P_H_01184 J_26_37 J_26_38 120 200 130 0 Open + P_H_01185 J_26_38 J_26_39 120 200 130 0 Open + P_H_01186 J_26_39 J_26_40 120 200 130 0 Open + P_H_01187 J_26_40 J_26_41 120 200 130 0 Open + P_H_01188 J_26_41 J_26_42 120 200 130 0 Open + P_H_01189 J_26_42 J_26_43 120 200 130 0 Open + P_H_01190 J_26_43 J_26_44 120 200 130 0 Open + P_H_01191 J_27_00 J_27_01 120 200 130 0 Open + P_H_01192 J_27_01 J_27_02 120 200 130 0 Open + P_H_01193 J_27_02 J_27_03 120 200 130 0 Open + P_H_01194 J_27_03 J_27_04 120 200 130 0 Open + P_H_01195 J_27_04 J_27_05 120 200 130 0 Open + P_H_01196 J_27_05 J_27_06 120 200 130 0 Open + P_H_01197 J_27_06 J_27_07 120 200 130 0 Open + P_H_01198 J_27_07 J_27_08 120 200 130 0 Open + P_H_01199 J_27_08 J_27_09 120 200 130 0 Open + P_H_01200 J_27_09 J_27_10 120 200 130 0 Open + P_H_01201 J_27_10 J_27_11 120 200 130 0 Open + P_H_01202 J_27_11 J_27_12 120 200 130 0 Open + P_H_01203 J_27_12 J_27_13 120 200 130 0 Open + P_H_01204 J_27_13 J_27_14 120 200 130 0 Open + P_H_01205 J_27_14 J_27_15 120 200 130 0 Open + P_H_01206 J_27_15 J_27_16 120 200 130 0 Open + P_H_01207 J_27_16 J_27_17 120 200 130 0 Open + P_H_01208 J_27_17 J_27_18 120 200 130 0 Open + P_H_01209 J_27_18 J_27_19 120 200 130 0 Open + P_H_01210 J_27_19 J_27_20 120 200 130 0 Open + P_H_01211 J_27_20 J_27_21 120 200 130 0 Open + P_H_01212 J_27_21 J_27_22 120 200 130 0 Open + P_H_01213 J_27_22 J_27_23 120 200 130 0 Open + P_H_01214 J_27_23 J_27_24 120 200 130 0 Open + P_H_01215 J_27_24 J_27_25 120 200 130 0 Open + P_H_01216 J_27_25 J_27_26 120 200 130 0 Open + P_H_01217 J_27_26 J_27_27 120 200 130 0 Open + P_H_01218 J_27_27 J_27_28 120 200 130 0 Open + P_H_01219 J_27_28 J_27_29 120 200 130 0 Open + P_H_01220 J_27_29 J_27_30 120 200 130 0 Open + P_H_01221 J_27_30 J_27_31 120 200 130 0 Open + P_H_01222 J_27_31 J_27_32 120 200 130 0 Open + P_H_01223 J_27_32 J_27_33 120 200 130 0 Open + P_H_01224 J_27_33 J_27_34 120 200 130 0 Open + P_H_01225 J_27_34 J_27_35 120 200 130 0 Open + P_H_01226 J_27_35 J_27_36 120 200 130 0 Open + P_H_01227 J_27_36 J_27_37 120 200 130 0 Open + P_H_01228 J_27_37 J_27_38 120 200 130 0 Open + P_H_01229 J_27_38 J_27_39 120 200 130 0 Open + P_H_01230 J_27_39 J_27_40 120 200 130 0 Open + P_H_01231 J_27_40 J_27_41 120 200 130 0 Open + P_H_01232 J_27_41 J_27_42 120 200 130 0 Open + P_H_01233 J_27_42 J_27_43 120 200 130 0 Open + P_H_01234 J_27_43 J_27_44 120 200 130 0 Open + P_H_01235 J_28_00 J_28_01 120 200 130 0 Open + P_H_01236 J_28_01 J_28_02 120 200 130 0 Open + P_H_01237 J_28_02 J_28_03 120 200 130 0 Open + P_H_01238 J_28_03 J_28_04 120 200 130 0 Open + P_H_01239 J_28_04 J_28_05 120 200 130 0 Open + P_H_01240 J_28_05 J_28_06 120 200 130 0 Open + P_H_01241 J_28_06 J_28_07 120 200 130 0 Open + P_H_01242 J_28_07 J_28_08 120 200 130 0 Open + P_H_01243 J_28_08 J_28_09 120 200 130 0 Open + P_H_01244 J_28_09 J_28_10 120 200 130 0 Open + P_H_01245 J_28_10 J_28_11 120 200 130 0 Open + P_H_01246 J_28_11 J_28_12 120 200 130 0 Open + P_H_01247 J_28_12 J_28_13 120 200 130 0 Open + P_H_01248 J_28_13 J_28_14 120 200 130 0 Open + P_H_01249 J_28_14 J_28_15 120 200 130 0 Open + P_H_01250 J_28_15 J_28_16 120 200 130 0 Open + P_H_01251 J_28_16 J_28_17 120 200 130 0 Open + P_H_01252 J_28_17 J_28_18 120 200 130 0 Open + P_H_01253 J_28_18 J_28_19 120 200 130 0 Open + P_H_01254 J_28_19 J_28_20 120 200 130 0 Open + P_H_01255 J_28_20 J_28_21 120 200 130 0 Open + P_H_01256 J_28_21 J_28_22 120 200 130 0 Open + P_H_01257 J_28_22 J_28_23 120 200 130 0 Open + P_H_01258 J_28_23 J_28_24 120 200 130 0 Open + P_H_01259 J_28_24 J_28_25 120 200 130 0 Open + P_H_01260 J_28_25 J_28_26 120 200 130 0 Open + P_H_01261 J_28_26 J_28_27 120 200 130 0 Open + P_H_01262 J_28_27 J_28_28 120 200 130 0 Open + P_H_01263 J_28_28 J_28_29 120 200 130 0 Open + P_H_01264 J_28_29 J_28_30 120 200 130 0 Open + P_H_01265 J_28_30 J_28_31 120 200 130 0 Open + P_H_01266 J_28_31 J_28_32 120 200 130 0 Open + P_H_01267 J_28_32 J_28_33 120 200 130 0 Open + P_H_01268 J_28_33 J_28_34 120 200 130 0 Open + P_H_01269 J_28_34 J_28_35 120 200 130 0 Open + P_H_01270 J_28_35 J_28_36 120 200 130 0 Open + P_H_01271 J_28_36 J_28_37 120 200 130 0 Open + P_H_01272 J_28_37 J_28_38 120 200 130 0 Open + P_H_01273 J_28_38 J_28_39 120 200 130 0 Open + P_H_01274 J_28_39 J_28_40 120 200 130 0 Open + P_H_01275 J_28_40 J_28_41 120 200 130 0 Open + P_H_01276 J_28_41 J_28_42 120 200 130 0 Open + P_H_01277 J_28_42 J_28_43 120 200 130 0 Open + P_H_01278 J_28_43 J_28_44 120 200 130 0 Open + P_H_01279 J_29_00 J_29_01 120 200 130 0 Open + P_H_01280 J_29_01 J_29_02 120 200 130 0 Open + P_H_01281 J_29_02 J_29_03 120 200 130 0 Open + P_H_01282 J_29_03 J_29_04 120 200 130 0 Open + P_H_01283 J_29_04 J_29_05 120 200 130 0 Open + P_H_01284 J_29_05 J_29_06 120 200 130 0 Open + P_H_01285 J_29_06 J_29_07 120 200 130 0 Open + P_H_01286 J_29_07 J_29_08 120 200 130 0 Open + P_H_01287 J_29_08 J_29_09 120 200 130 0 Open + P_H_01288 J_29_09 J_29_10 120 200 130 0 Open + P_H_01289 J_29_10 J_29_11 120 200 130 0 Open + P_H_01290 J_29_11 J_29_12 120 200 130 0 Open + P_H_01291 J_29_12 J_29_13 120 200 130 0 Open + P_H_01292 J_29_13 J_29_14 120 200 130 0 Open + P_H_01293 J_29_14 J_29_15 120 200 130 0 Open + P_H_01294 J_29_15 J_29_16 120 200 130 0 Open + P_H_01295 J_29_16 J_29_17 120 200 130 0 Open + P_H_01296 J_29_17 J_29_18 120 200 130 0 Open + P_H_01297 J_29_18 J_29_19 120 200 130 0 Open + P_H_01298 J_29_19 J_29_20 120 200 130 0 Open + P_H_01299 J_29_20 J_29_21 120 200 130 0 Open + P_H_01300 J_29_21 J_29_22 120 200 130 0 Open + P_H_01301 J_29_22 J_29_23 120 200 130 0 Open + P_H_01302 J_29_23 J_29_24 120 200 130 0 Open + P_H_01303 J_29_24 J_29_25 120 200 130 0 Open + P_H_01304 J_29_25 J_29_26 120 200 130 0 Open + P_H_01305 J_29_26 J_29_27 120 200 130 0 Open + P_H_01306 J_29_27 J_29_28 120 200 130 0 Open + P_H_01307 J_29_28 J_29_29 120 200 130 0 Open + P_H_01308 J_29_29 J_29_30 120 200 130 0 Open + P_H_01309 J_29_30 J_29_31 120 200 130 0 Open + P_H_01310 J_29_31 J_29_32 120 200 130 0 Open + P_H_01311 J_29_32 J_29_33 120 200 130 0 Open + P_H_01312 J_29_33 J_29_34 120 200 130 0 Open + P_H_01313 J_29_34 J_29_35 120 200 130 0 Open + P_H_01314 J_29_35 J_29_36 120 200 130 0 Open + P_H_01315 J_29_36 J_29_37 120 200 130 0 Open + P_H_01316 J_29_37 J_29_38 120 200 130 0 Open + P_H_01317 J_29_38 J_29_39 120 200 130 0 Open + P_H_01318 J_29_39 J_29_40 120 200 130 0 Open + P_H_01319 J_29_40 J_29_41 120 200 130 0 Open + P_H_01320 J_29_41 J_29_42 120 200 130 0 Open + P_H_01321 J_29_42 J_29_43 120 200 130 0 Open + P_H_01322 J_29_43 J_29_44 120 200 130 0 Open + P_H_01323 J_30_00 J_30_01 120 200 130 0 Open + P_H_01324 J_30_01 J_30_02 120 200 130 0 Open + P_H_01325 J_30_02 J_30_03 120 200 130 0 Open + P_H_01326 J_30_03 J_30_04 120 200 130 0 Open + P_H_01327 J_30_04 J_30_05 120 200 130 0 Open + P_H_01328 J_30_05 J_30_06 120 200 130 0 Open + P_H_01329 J_30_06 J_30_07 120 200 130 0 Open + P_H_01330 J_30_07 J_30_08 120 200 130 0 Open + P_H_01331 J_30_08 J_30_09 120 200 130 0 Open + P_H_01332 J_30_09 J_30_10 120 200 130 0 Open + P_H_01333 J_30_10 J_30_11 120 200 130 0 Open + P_H_01334 J_30_11 J_30_12 120 200 130 0 Open + P_H_01335 J_30_12 J_30_13 120 200 130 0 Open + P_H_01336 J_30_13 J_30_14 120 200 130 0 Open + P_H_01337 J_30_14 J_30_15 120 200 130 0 Open + P_H_01338 J_30_15 J_30_16 120 200 130 0 Open + P_H_01339 J_30_16 J_30_17 120 200 130 0 Open + P_H_01340 J_30_17 J_30_18 120 200 130 0 Open + P_H_01341 J_30_18 J_30_19 120 200 130 0 Open + P_H_01342 J_30_19 J_30_20 120 200 130 0 Open + P_H_01343 J_30_20 J_30_21 120 200 130 0 Open + P_H_01344 J_30_21 J_30_22 120 200 130 0 Open + P_H_01345 J_30_22 J_30_23 120 200 130 0 Open + P_H_01346 J_30_23 J_30_24 120 200 130 0 Open + P_H_01347 J_30_24 J_30_25 120 200 130 0 Open + P_H_01348 J_30_25 J_30_26 120 200 130 0 Open + P_H_01349 J_30_26 J_30_27 120 200 130 0 Open + P_H_01350 J_30_27 J_30_28 120 200 130 0 Open + P_H_01351 J_30_28 J_30_29 120 200 130 0 Open + P_H_01352 J_30_29 J_30_30 120 200 130 0 Open + P_H_01353 J_30_30 J_30_31 120 200 130 0 Open + P_H_01354 J_30_31 J_30_32 120 200 130 0 Open + P_H_01355 J_30_32 J_30_33 120 200 130 0 Open + P_H_01356 J_30_33 J_30_34 120 200 130 0 Open + P_H_01357 J_30_34 J_30_35 120 200 130 0 Open + P_H_01358 J_30_35 J_30_36 120 200 130 0 Open + P_H_01359 J_30_36 J_30_37 120 200 130 0 Open + P_H_01360 J_30_37 J_30_38 120 200 130 0 Open + P_H_01361 J_30_38 J_30_39 120 200 130 0 Open + P_H_01362 J_30_39 J_30_40 120 200 130 0 Open + P_H_01363 J_30_40 J_30_41 120 200 130 0 Open + P_H_01364 J_30_41 J_30_42 120 200 130 0 Open + P_H_01365 J_30_42 J_30_43 120 200 130 0 Open + P_H_01366 J_30_43 J_30_44 120 200 130 0 Open + P_H_01367 J_31_00 J_31_01 120 200 130 0 Open + P_H_01368 J_31_01 J_31_02 120 200 130 0 Open + P_H_01369 J_31_02 J_31_03 120 200 130 0 Open + P_H_01370 J_31_03 J_31_04 120 200 130 0 Open + P_H_01371 J_31_04 J_31_05 120 200 130 0 Open + P_H_01372 J_31_05 J_31_06 120 200 130 0 Open + P_H_01373 J_31_06 J_31_07 120 200 130 0 Open + P_H_01374 J_31_07 J_31_08 120 200 130 0 Open + P_H_01375 J_31_08 J_31_09 120 200 130 0 Open + P_H_01376 J_31_09 J_31_10 120 200 130 0 Open + P_H_01377 J_31_10 J_31_11 120 200 130 0 Open + P_H_01378 J_31_11 J_31_12 120 200 130 0 Open + P_H_01379 J_31_12 J_31_13 120 200 130 0 Open + P_H_01380 J_31_13 J_31_14 120 200 130 0 Open + P_H_01381 J_31_14 J_31_15 120 200 130 0 Open + P_H_01382 J_31_15 J_31_16 120 200 130 0 Open + P_H_01383 J_31_16 J_31_17 120 200 130 0 Open + P_H_01384 J_31_17 J_31_18 120 200 130 0 Open + P_H_01385 J_31_18 J_31_19 120 200 130 0 Open + P_H_01386 J_31_19 J_31_20 120 200 130 0 Open + P_H_01387 J_31_20 J_31_21 120 200 130 0 Open + P_H_01388 J_31_21 J_31_22 120 200 130 0 Open + P_H_01389 J_31_22 J_31_23 120 200 130 0 Open + P_H_01390 J_31_23 J_31_24 120 200 130 0 Open + P_H_01391 J_31_24 J_31_25 120 200 130 0 Open + P_H_01392 J_31_25 J_31_26 120 200 130 0 Open + P_H_01393 J_31_26 J_31_27 120 200 130 0 Open + P_H_01394 J_31_27 J_31_28 120 200 130 0 Open + P_H_01395 J_31_28 J_31_29 120 200 130 0 Open + P_H_01396 J_31_29 J_31_30 120 200 130 0 Open + P_H_01397 J_31_30 J_31_31 120 200 130 0 Open + P_H_01398 J_31_31 J_31_32 120 200 130 0 Open + P_H_01399 J_31_32 J_31_33 120 200 130 0 Open + P_H_01400 J_31_33 J_31_34 120 200 130 0 Open + P_H_01401 J_31_34 J_31_35 120 200 130 0 Open + P_H_01402 J_31_35 J_31_36 120 200 130 0 Open + P_H_01403 J_31_36 J_31_37 120 200 130 0 Open + P_H_01404 J_31_37 J_31_38 120 200 130 0 Open + P_H_01405 J_31_38 J_31_39 120 200 130 0 Open + P_H_01406 J_31_39 J_31_40 120 200 130 0 Open + P_H_01407 J_31_40 J_31_41 120 200 130 0 Open + P_H_01408 J_31_41 J_31_42 120 200 130 0 Open + P_H_01409 J_31_42 J_31_43 120 200 130 0 Open + P_H_01410 J_31_43 J_31_44 120 200 130 0 Open + P_H_01411 J_32_00 J_32_01 120 200 130 0 Open + P_H_01412 J_32_01 J_32_02 120 200 130 0 Open + P_H_01413 J_32_02 J_32_03 120 200 130 0 Open + P_H_01414 J_32_03 J_32_04 120 200 130 0 Open + P_H_01415 J_32_04 J_32_05 120 200 130 0 Open + P_H_01416 J_32_05 J_32_06 120 200 130 0 Open + P_H_01417 J_32_06 J_32_07 120 200 130 0 Open + P_H_01418 J_32_07 J_32_08 120 200 130 0 Open + P_H_01419 J_32_08 J_32_09 120 200 130 0 Open + P_H_01420 J_32_09 J_32_10 120 200 130 0 Open + P_H_01421 J_32_10 J_32_11 120 200 130 0 Open + P_H_01422 J_32_11 J_32_12 120 200 130 0 Open + P_H_01423 J_32_12 J_32_13 120 200 130 0 Open + P_H_01424 J_32_13 J_32_14 120 200 130 0 Open + P_H_01425 J_32_14 J_32_15 120 200 130 0 Open + P_H_01426 J_32_15 J_32_16 120 200 130 0 Open + P_H_01427 J_32_16 J_32_17 120 200 130 0 Open + P_H_01428 J_32_17 J_32_18 120 200 130 0 Open + P_H_01429 J_32_18 J_32_19 120 200 130 0 Open + P_H_01430 J_32_19 J_32_20 120 200 130 0 Open + P_H_01431 J_32_20 J_32_21 120 200 130 0 Open + P_H_01432 J_32_21 J_32_22 120 200 130 0 Open + P_H_01433 J_32_22 J_32_23 120 200 130 0 Open + P_H_01434 J_32_23 J_32_24 120 200 130 0 Open + P_H_01435 J_32_24 J_32_25 120 200 130 0 Open + P_H_01436 J_32_25 J_32_26 120 200 130 0 Open + P_H_01437 J_32_26 J_32_27 120 200 130 0 Open + P_H_01438 J_32_27 J_32_28 120 200 130 0 Open + P_H_01439 J_32_28 J_32_29 120 200 130 0 Open + P_H_01440 J_32_29 J_32_30 120 200 130 0 Open + P_H_01441 J_32_30 J_32_31 120 200 130 0 Open + P_H_01442 J_32_31 J_32_32 120 200 130 0 Open + P_H_01443 J_32_32 J_32_33 120 200 130 0 Open + P_H_01444 J_32_33 J_32_34 120 200 130 0 Open + P_H_01445 J_32_34 J_32_35 120 200 130 0 Open + P_H_01446 J_32_35 J_32_36 120 200 130 0 Open + P_H_01447 J_32_36 J_32_37 120 200 130 0 Open + P_H_01448 J_32_37 J_32_38 120 200 130 0 Open + P_H_01449 J_32_38 J_32_39 120 200 130 0 Open + P_H_01450 J_32_39 J_32_40 120 200 130 0 Open + P_H_01451 J_32_40 J_32_41 120 200 130 0 Open + P_H_01452 J_32_41 J_32_42 120 200 130 0 Open + P_H_01453 J_32_42 J_32_43 120 200 130 0 Open + P_H_01454 J_32_43 J_32_44 120 200 130 0 Open + P_H_01455 J_33_00 J_33_01 120 200 130 0 Open + P_H_01456 J_33_01 J_33_02 120 200 130 0 Open + P_H_01457 J_33_02 J_33_03 120 200 130 0 Open + P_H_01458 J_33_03 J_33_04 120 200 130 0 Open + P_H_01459 J_33_04 J_33_05 120 200 130 0 Open + P_H_01460 J_33_05 J_33_06 120 200 130 0 Open + P_H_01461 J_33_06 J_33_07 120 200 130 0 Open + P_H_01462 J_33_07 J_33_08 120 200 130 0 Open + P_H_01463 J_33_08 J_33_09 120 200 130 0 Open + P_H_01464 J_33_09 J_33_10 120 200 130 0 Open + P_H_01465 J_33_10 J_33_11 120 200 130 0 Open + P_H_01466 J_33_11 J_33_12 120 200 130 0 Open + P_H_01467 J_33_12 J_33_13 120 200 130 0 Open + P_H_01468 J_33_13 J_33_14 120 200 130 0 Open + P_H_01469 J_33_14 J_33_15 120 200 130 0 Open + P_H_01470 J_33_15 J_33_16 120 200 130 0 Open + P_H_01471 J_33_16 J_33_17 120 200 130 0 Open + P_H_01472 J_33_17 J_33_18 120 200 130 0 Open + P_H_01473 J_33_18 J_33_19 120 200 130 0 Open + P_H_01474 J_33_19 J_33_20 120 200 130 0 Open + P_H_01475 J_33_20 J_33_21 120 200 130 0 Open + P_H_01476 J_33_21 J_33_22 120 200 130 0 Open + P_H_01477 J_33_22 J_33_23 120 200 130 0 Open + P_H_01478 J_33_23 J_33_24 120 200 130 0 Open + P_H_01479 J_33_24 J_33_25 120 200 130 0 Open + P_H_01480 J_33_25 J_33_26 120 200 130 0 Open + P_H_01481 J_33_26 J_33_27 120 200 130 0 Open + P_H_01482 J_33_27 J_33_28 120 200 130 0 Open + P_H_01483 J_33_28 J_33_29 120 200 130 0 Open + P_H_01484 J_33_29 J_33_30 120 200 130 0 Open + P_H_01485 J_33_30 J_33_31 120 200 130 0 Open + P_H_01486 J_33_31 J_33_32 120 200 130 0 Open + P_H_01487 J_33_32 J_33_33 120 200 130 0 Open + P_H_01488 J_33_33 J_33_34 120 200 130 0 Open + P_H_01489 J_33_34 J_33_35 120 200 130 0 Open + P_H_01490 J_33_35 J_33_36 120 200 130 0 Open + P_H_01491 J_33_36 J_33_37 120 200 130 0 Open + P_H_01492 J_33_37 J_33_38 120 200 130 0 Open + P_H_01493 J_33_38 J_33_39 120 200 130 0 Open + P_H_01494 J_33_39 J_33_40 120 200 130 0 Open + P_H_01495 J_33_40 J_33_41 120 200 130 0 Open + P_H_01496 J_33_41 J_33_42 120 200 130 0 Open + P_H_01497 J_33_42 J_33_43 120 200 130 0 Open + P_H_01498 J_33_43 J_33_44 120 200 130 0 Open + P_H_01499 J_34_00 J_34_01 120 200 130 0 Open + P_H_01500 J_34_01 J_34_02 120 200 130 0 Open + P_H_01501 J_34_02 J_34_03 120 200 130 0 Open + P_H_01502 J_34_03 J_34_04 120 200 130 0 Open + P_H_01503 J_34_04 J_34_05 120 200 130 0 Open + P_H_01504 J_34_05 J_34_06 120 200 130 0 Open + P_H_01505 J_34_06 J_34_07 120 200 130 0 Open + P_H_01506 J_34_07 J_34_08 120 200 130 0 Open + P_H_01507 J_34_08 J_34_09 120 200 130 0 Open + P_H_01508 J_34_09 J_34_10 120 200 130 0 Open + P_H_01509 J_34_10 J_34_11 120 200 130 0 Open + P_H_01510 J_34_11 J_34_12 120 200 130 0 Open + P_H_01511 J_34_12 J_34_13 120 200 130 0 Open + P_H_01512 J_34_13 J_34_14 120 200 130 0 Open + P_H_01513 J_34_14 J_34_15 120 200 130 0 Open + P_H_01514 J_34_15 J_34_16 120 200 130 0 Open + P_H_01515 J_34_16 J_34_17 120 200 130 0 Open + P_H_01516 J_34_17 J_34_18 120 200 130 0 Open + P_H_01517 J_34_18 J_34_19 120 200 130 0 Open + P_H_01518 J_34_19 J_34_20 120 200 130 0 Open + P_H_01519 J_34_20 J_34_21 120 200 130 0 Open + P_H_01520 J_34_21 J_34_22 120 200 130 0 Open + P_H_01521 J_34_22 J_34_23 120 200 130 0 Open + P_H_01522 J_34_23 J_34_24 120 200 130 0 Open + P_H_01523 J_34_24 J_34_25 120 200 130 0 Open + P_H_01524 J_34_25 J_34_26 120 200 130 0 Open + P_H_01525 J_34_26 J_34_27 120 200 130 0 Open + P_H_01526 J_34_27 J_34_28 120 200 130 0 Open + P_H_01527 J_34_28 J_34_29 120 200 130 0 Open + P_H_01528 J_34_29 J_34_30 120 200 130 0 Open + P_H_01529 J_34_30 J_34_31 120 200 130 0 Open + P_H_01530 J_34_31 J_34_32 120 200 130 0 Open + P_H_01531 J_34_32 J_34_33 120 200 130 0 Open + P_H_01532 J_34_33 J_34_34 120 200 130 0 Open + P_H_01533 J_34_34 J_34_35 120 200 130 0 Open + P_H_01534 J_34_35 J_34_36 120 200 130 0 Open + P_H_01535 J_34_36 J_34_37 120 200 130 0 Open + P_H_01536 J_34_37 J_34_38 120 200 130 0 Open + P_H_01537 J_34_38 J_34_39 120 200 130 0 Open + P_H_01538 J_34_39 J_34_40 120 200 130 0 Open + P_H_01539 J_34_40 J_34_41 120 200 130 0 Open + P_H_01540 J_34_41 J_34_42 120 200 130 0 Open + P_H_01541 J_34_42 J_34_43 120 200 130 0 Open + P_H_01542 J_34_43 J_34_44 120 200 130 0 Open + P_H_01543 J_35_00 J_35_01 120 200 130 0 Open + P_H_01544 J_35_01 J_35_02 120 200 130 0 Open + P_H_01545 J_35_02 J_35_03 120 200 130 0 Open + P_H_01546 J_35_03 J_35_04 120 200 130 0 Open + P_H_01547 J_35_04 J_35_05 120 200 130 0 Open + P_H_01548 J_35_05 J_35_06 120 200 130 0 Open + P_H_01549 J_35_06 J_35_07 120 200 130 0 Open + P_H_01550 J_35_07 J_35_08 120 200 130 0 Open + P_H_01551 J_35_08 J_35_09 120 200 130 0 Open + P_H_01552 J_35_09 J_35_10 120 200 130 0 Open + P_H_01553 J_35_10 J_35_11 120 200 130 0 Open + P_H_01554 J_35_11 J_35_12 120 200 130 0 Open + P_H_01555 J_35_12 J_35_13 120 200 130 0 Open + P_H_01556 J_35_13 J_35_14 120 200 130 0 Open + P_H_01557 J_35_14 J_35_15 120 200 130 0 Open + P_H_01558 J_35_15 J_35_16 120 200 130 0 Open + P_H_01559 J_35_16 J_35_17 120 200 130 0 Open + P_H_01560 J_35_17 J_35_18 120 200 130 0 Open + P_H_01561 J_35_18 J_35_19 120 200 130 0 Open + P_H_01562 J_35_19 J_35_20 120 200 130 0 Open + P_H_01563 J_35_20 J_35_21 120 200 130 0 Open + P_H_01564 J_35_21 J_35_22 120 200 130 0 Open + P_H_01565 J_35_22 J_35_23 120 200 130 0 Open + P_H_01566 J_35_23 J_35_24 120 200 130 0 Open + P_H_01567 J_35_24 J_35_25 120 200 130 0 Open + P_H_01568 J_35_25 J_35_26 120 200 130 0 Open + P_H_01569 J_35_26 J_35_27 120 200 130 0 Open + P_H_01570 J_35_27 J_35_28 120 200 130 0 Open + P_H_01571 J_35_28 J_35_29 120 200 130 0 Open + P_H_01572 J_35_29 J_35_30 120 200 130 0 Open + P_H_01573 J_35_30 J_35_31 120 200 130 0 Open + P_H_01574 J_35_31 J_35_32 120 200 130 0 Open + P_H_01575 J_35_32 J_35_33 120 200 130 0 Open + P_H_01576 J_35_33 J_35_34 120 200 130 0 Open + P_H_01577 J_35_34 J_35_35 120 200 130 0 Open + P_H_01578 J_35_35 J_35_36 120 200 130 0 Open + P_H_01579 J_35_36 J_35_37 120 200 130 0 Open + P_H_01580 J_35_37 J_35_38 120 200 130 0 Open + P_H_01581 J_35_38 J_35_39 120 200 130 0 Open + P_H_01582 J_35_39 J_35_40 120 200 130 0 Open + P_H_01583 J_35_40 J_35_41 120 200 130 0 Open + P_H_01584 J_35_41 J_35_42 120 200 130 0 Open + P_H_01585 J_35_42 J_35_43 120 200 130 0 Open + P_H_01586 J_35_43 J_35_44 120 200 130 0 Open + P_H_01587 J_36_00 J_36_01 120 200 130 0 Open + P_H_01588 J_36_01 J_36_02 120 200 130 0 Open + P_H_01589 J_36_02 J_36_03 120 200 130 0 Open + P_H_01590 J_36_03 J_36_04 120 200 130 0 Open + P_H_01591 J_36_04 J_36_05 120 200 130 0 Open + P_H_01592 J_36_05 J_36_06 120 200 130 0 Open + P_H_01593 J_36_06 J_36_07 120 200 130 0 Open + P_H_01594 J_36_07 J_36_08 120 200 130 0 Open + P_H_01595 J_36_08 J_36_09 120 200 130 0 Open + P_H_01596 J_36_09 J_36_10 120 200 130 0 Open + P_H_01597 J_36_10 J_36_11 120 200 130 0 Open + P_H_01598 J_36_11 J_36_12 120 200 130 0 Open + P_H_01599 J_36_12 J_36_13 120 200 130 0 Open + P_H_01600 J_36_13 J_36_14 120 200 130 0 Open + P_H_01601 J_36_14 J_36_15 120 200 130 0 Open + P_H_01602 J_36_15 J_36_16 120 200 130 0 Open + P_H_01603 J_36_16 J_36_17 120 200 130 0 Open + P_H_01604 J_36_17 J_36_18 120 200 130 0 Open + P_H_01605 J_36_18 J_36_19 120 200 130 0 Open + P_H_01606 J_36_19 J_36_20 120 200 130 0 Open + P_H_01607 J_36_20 J_36_21 120 200 130 0 Open + P_H_01608 J_36_21 J_36_22 120 200 130 0 Open + P_H_01609 J_36_22 J_36_23 120 200 130 0 Open + P_H_01610 J_36_23 J_36_24 120 200 130 0 Open + P_H_01611 J_36_24 J_36_25 120 200 130 0 Open + P_H_01612 J_36_25 J_36_26 120 200 130 0 Open + P_H_01613 J_36_26 J_36_27 120 200 130 0 Open + P_H_01614 J_36_27 J_36_28 120 200 130 0 Open + P_H_01615 J_36_28 J_36_29 120 200 130 0 Open + P_H_01616 J_36_29 J_36_30 120 200 130 0 Open + P_H_01617 J_36_30 J_36_31 120 200 130 0 Open + P_H_01618 J_36_31 J_36_32 120 200 130 0 Open + P_H_01619 J_36_32 J_36_33 120 200 130 0 Open + P_H_01620 J_36_33 J_36_34 120 200 130 0 Open + P_H_01621 J_36_34 J_36_35 120 200 130 0 Open + P_H_01622 J_36_35 J_36_36 120 200 130 0 Open + P_H_01623 J_36_36 J_36_37 120 200 130 0 Open + P_H_01624 J_36_37 J_36_38 120 200 130 0 Open + P_H_01625 J_36_38 J_36_39 120 200 130 0 Open + P_H_01626 J_36_39 J_36_40 120 200 130 0 Open + P_H_01627 J_36_40 J_36_41 120 200 130 0 Open + P_H_01628 J_36_41 J_36_42 120 200 130 0 Open + P_H_01629 J_36_42 J_36_43 120 200 130 0 Open + P_H_01630 J_36_43 J_36_44 120 200 130 0 Open + P_H_01631 J_37_00 J_37_01 120 200 130 0 Open + P_H_01632 J_37_01 J_37_02 120 200 130 0 Open + P_H_01633 J_37_02 J_37_03 120 200 130 0 Open + P_H_01634 J_37_03 J_37_04 120 200 130 0 Open + P_H_01635 J_37_04 J_37_05 120 200 130 0 Open + P_H_01636 J_37_05 J_37_06 120 200 130 0 Open + P_H_01637 J_37_06 J_37_07 120 200 130 0 Open + P_H_01638 J_37_07 J_37_08 120 200 130 0 Open + P_H_01639 J_37_08 J_37_09 120 200 130 0 Open + P_H_01640 J_37_09 J_37_10 120 200 130 0 Open + P_H_01641 J_37_10 J_37_11 120 200 130 0 Open + P_H_01642 J_37_11 J_37_12 120 200 130 0 Open + P_H_01643 J_37_12 J_37_13 120 200 130 0 Open + P_H_01644 J_37_13 J_37_14 120 200 130 0 Open + P_H_01645 J_37_14 J_37_15 120 200 130 0 Open + P_H_01646 J_37_15 J_37_16 120 200 130 0 Open + P_H_01647 J_37_16 J_37_17 120 200 130 0 Open + P_H_01648 J_37_17 J_37_18 120 200 130 0 Open + P_H_01649 J_37_18 J_37_19 120 200 130 0 Open + P_H_01650 J_37_19 J_37_20 120 200 130 0 Open + P_H_01651 J_37_20 J_37_21 120 200 130 0 Open + P_H_01652 J_37_21 J_37_22 120 200 130 0 Open + P_H_01653 J_37_22 J_37_23 120 200 130 0 Open + P_H_01654 J_37_23 J_37_24 120 200 130 0 Open + P_H_01655 J_37_24 J_37_25 120 200 130 0 Open + P_H_01656 J_37_25 J_37_26 120 200 130 0 Open + P_H_01657 J_37_26 J_37_27 120 200 130 0 Open + P_H_01658 J_37_27 J_37_28 120 200 130 0 Open + P_H_01659 J_37_28 J_37_29 120 200 130 0 Open + P_H_01660 J_37_29 J_37_30 120 200 130 0 Open + P_H_01661 J_37_30 J_37_31 120 200 130 0 Open + P_H_01662 J_37_31 J_37_32 120 200 130 0 Open + P_H_01663 J_37_32 J_37_33 120 200 130 0 Open + P_H_01664 J_37_33 J_37_34 120 200 130 0 Open + P_H_01665 J_37_34 J_37_35 120 200 130 0 Open + P_H_01666 J_37_35 J_37_36 120 200 130 0 Open + P_H_01667 J_37_36 J_37_37 120 200 130 0 Open + P_H_01668 J_37_37 J_37_38 120 200 130 0 Open + P_H_01669 J_37_38 J_37_39 120 200 130 0 Open + P_H_01670 J_37_39 J_37_40 120 200 130 0 Open + P_H_01671 J_37_40 J_37_41 120 200 130 0 Open + P_H_01672 J_37_41 J_37_42 120 200 130 0 Open + P_H_01673 J_37_42 J_37_43 120 200 130 0 Open + P_H_01674 J_37_43 J_37_44 120 200 130 0 Open + P_H_01675 J_38_00 J_38_01 120 200 130 0 Open + P_H_01676 J_38_01 J_38_02 120 200 130 0 Open + P_H_01677 J_38_02 J_38_03 120 200 130 0 Open + P_H_01678 J_38_03 J_38_04 120 200 130 0 Open + P_H_01679 J_38_04 J_38_05 120 200 130 0 Open + P_H_01680 J_38_05 J_38_06 120 200 130 0 Open + P_H_01681 J_38_06 J_38_07 120 200 130 0 Open + P_H_01682 J_38_07 J_38_08 120 200 130 0 Open + P_H_01683 J_38_08 J_38_09 120 200 130 0 Open + P_H_01684 J_38_09 J_38_10 120 200 130 0 Open + P_H_01685 J_38_10 J_38_11 120 200 130 0 Open + P_H_01686 J_38_11 J_38_12 120 200 130 0 Open + P_H_01687 J_38_12 J_38_13 120 200 130 0 Open + P_H_01688 J_38_13 J_38_14 120 200 130 0 Open + P_H_01689 J_38_14 J_38_15 120 200 130 0 Open + P_H_01690 J_38_15 J_38_16 120 200 130 0 Open + P_H_01691 J_38_16 J_38_17 120 200 130 0 Open + P_H_01692 J_38_17 J_38_18 120 200 130 0 Open + P_H_01693 J_38_18 J_38_19 120 200 130 0 Open + P_H_01694 J_38_19 J_38_20 120 200 130 0 Open + P_H_01695 J_38_20 J_38_21 120 200 130 0 Open + P_H_01696 J_38_21 J_38_22 120 200 130 0 Open + P_H_01697 J_38_22 J_38_23 120 200 130 0 Open + P_H_01698 J_38_23 J_38_24 120 200 130 0 Open + P_H_01699 J_38_24 J_38_25 120 200 130 0 Open + P_H_01700 J_38_25 J_38_26 120 200 130 0 Open + P_H_01701 J_38_26 J_38_27 120 200 130 0 Open + P_H_01702 J_38_27 J_38_28 120 200 130 0 Open + P_H_01703 J_38_28 J_38_29 120 200 130 0 Open + P_H_01704 J_38_29 J_38_30 120 200 130 0 Open + P_H_01705 J_38_30 J_38_31 120 200 130 0 Open + P_H_01706 J_38_31 J_38_32 120 200 130 0 Open + P_H_01707 J_38_32 J_38_33 120 200 130 0 Open + P_H_01708 J_38_33 J_38_34 120 200 130 0 Open + P_H_01709 J_38_34 J_38_35 120 200 130 0 Open + P_H_01710 J_38_35 J_38_36 120 200 130 0 Open + P_H_01711 J_38_36 J_38_37 120 200 130 0 Open + P_H_01712 J_38_37 J_38_38 120 200 130 0 Open + P_H_01713 J_38_38 J_38_39 120 200 130 0 Open + P_H_01714 J_38_39 J_38_40 120 200 130 0 Open + P_H_01715 J_38_40 J_38_41 120 200 130 0 Open + P_H_01716 J_38_41 J_38_42 120 200 130 0 Open + P_H_01717 J_38_42 J_38_43 120 200 130 0 Open + P_H_01718 J_38_43 J_38_44 120 200 130 0 Open + P_H_01719 J_39_00 J_39_01 120 200 130 0 Open + P_H_01720 J_39_01 J_39_02 120 200 130 0 Open + P_H_01721 J_39_02 J_39_03 120 200 130 0 Open + P_H_01722 J_39_03 J_39_04 120 200 130 0 Open + P_H_01723 J_39_04 J_39_05 120 200 130 0 Open + P_H_01724 J_39_05 J_39_06 120 200 130 0 Open + P_H_01725 J_39_06 J_39_07 120 200 130 0 Open + P_H_01726 J_39_07 J_39_08 120 200 130 0 Open + P_H_01727 J_39_08 J_39_09 120 200 130 0 Open + P_H_01728 J_39_09 J_39_10 120 200 130 0 Open + P_H_01729 J_39_10 J_39_11 120 200 130 0 Open + P_H_01730 J_39_11 J_39_12 120 200 130 0 Open + P_H_01731 J_39_12 J_39_13 120 200 130 0 Open + P_H_01732 J_39_13 J_39_14 120 200 130 0 Open + P_H_01733 J_39_14 J_39_15 120 200 130 0 Open + P_H_01734 J_39_15 J_39_16 120 200 130 0 Open + P_H_01735 J_39_16 J_39_17 120 200 130 0 Open + P_H_01736 J_39_17 J_39_18 120 200 130 0 Open + P_H_01737 J_39_18 J_39_19 120 200 130 0 Open + P_H_01738 J_39_19 J_39_20 120 200 130 0 Open + P_H_01739 J_39_20 J_39_21 120 200 130 0 Open + P_H_01740 J_39_21 J_39_22 120 200 130 0 Open + P_H_01741 J_39_22 J_39_23 120 200 130 0 Open + P_H_01742 J_39_23 J_39_24 120 200 130 0 Open + P_H_01743 J_39_24 J_39_25 120 200 130 0 Open + P_H_01744 J_39_25 J_39_26 120 200 130 0 Open + P_H_01745 J_39_26 J_39_27 120 200 130 0 Open + P_H_01746 J_39_27 J_39_28 120 200 130 0 Open + P_H_01747 J_39_28 J_39_29 120 200 130 0 Open + P_H_01748 J_39_29 J_39_30 120 200 130 0 Open + P_H_01749 J_39_30 J_39_31 120 200 130 0 Open + P_H_01750 J_39_31 J_39_32 120 200 130 0 Open + P_H_01751 J_39_32 J_39_33 120 200 130 0 Open + P_H_01752 J_39_33 J_39_34 120 200 130 0 Open + P_H_01753 J_39_34 J_39_35 120 200 130 0 Open + P_H_01754 J_39_35 J_39_36 120 200 130 0 Open + P_H_01755 J_39_36 J_39_37 120 200 130 0 Open + P_H_01756 J_39_37 J_39_38 120 200 130 0 Open + P_H_01757 J_39_38 J_39_39 120 200 130 0 Open + P_H_01758 J_39_39 J_39_40 120 200 130 0 Open + P_H_01759 J_39_40 J_39_41 120 200 130 0 Open + P_H_01760 J_39_41 J_39_42 120 200 130 0 Open + P_H_01761 J_39_42 J_39_43 120 200 130 0 Open + P_H_01762 J_39_43 J_39_44 120 200 130 0 Open + P_H_01763 J_40_00 J_40_01 120 200 130 0 Open + P_H_01764 J_40_01 J_40_02 120 200 130 0 Open + P_H_01765 J_40_02 J_40_03 120 200 130 0 Open + P_H_01766 J_40_03 J_40_04 120 200 130 0 Open + P_H_01767 J_40_04 J_40_05 120 200 130 0 Open + P_H_01768 J_40_05 J_40_06 120 200 130 0 Open + P_H_01769 J_40_06 J_40_07 120 200 130 0 Open + P_H_01770 J_40_07 J_40_08 120 200 130 0 Open + P_H_01771 J_40_08 J_40_09 120 200 130 0 Open + P_H_01772 J_40_09 J_40_10 120 200 130 0 Open + P_H_01773 J_40_10 J_40_11 120 200 130 0 Open + P_H_01774 J_40_11 J_40_12 120 200 130 0 Open + P_H_01775 J_40_12 J_40_13 120 200 130 0 Open + P_H_01776 J_40_13 J_40_14 120 200 130 0 Open + P_H_01777 J_40_14 J_40_15 120 200 130 0 Open + P_H_01778 J_40_15 J_40_16 120 200 130 0 Open + P_H_01779 J_40_16 J_40_17 120 200 130 0 Open + P_H_01780 J_40_17 J_40_18 120 200 130 0 Open + P_H_01781 J_40_18 J_40_19 120 200 130 0 Open + P_H_01782 J_40_19 J_40_20 120 200 130 0 Open + P_H_01783 J_40_20 J_40_21 120 200 130 0 Open + P_H_01784 J_40_21 J_40_22 120 200 130 0 Open + P_H_01785 J_40_22 J_40_23 120 200 130 0 Open + P_H_01786 J_40_23 J_40_24 120 200 130 0 Open + P_H_01787 J_40_24 J_40_25 120 200 130 0 Open + P_H_01788 J_40_25 J_40_26 120 200 130 0 Open + P_H_01789 J_40_26 J_40_27 120 200 130 0 Open + P_H_01790 J_40_27 J_40_28 120 200 130 0 Open + P_H_01791 J_40_28 J_40_29 120 200 130 0 Open + P_H_01792 J_40_29 J_40_30 120 200 130 0 Open + P_H_01793 J_40_30 J_40_31 120 200 130 0 Open + P_H_01794 J_40_31 J_40_32 120 200 130 0 Open + P_H_01795 J_40_32 J_40_33 120 200 130 0 Open + P_H_01796 J_40_33 J_40_34 120 200 130 0 Open + P_H_01797 J_40_34 J_40_35 120 200 130 0 Open + P_H_01798 J_40_35 J_40_36 120 200 130 0 Open + P_H_01799 J_40_36 J_40_37 120 200 130 0 Open + P_H_01800 J_40_37 J_40_38 120 200 130 0 Open + P_H_01801 J_40_38 J_40_39 120 200 130 0 Open + P_H_01802 J_40_39 J_40_40 120 200 130 0 Open + P_H_01803 J_40_40 J_40_41 120 200 130 0 Open + P_H_01804 J_40_41 J_40_42 120 200 130 0 Open + P_H_01805 J_40_42 J_40_43 120 200 130 0 Open + P_H_01806 J_40_43 J_40_44 120 200 130 0 Open + P_H_01807 J_41_00 J_41_01 120 200 130 0 Open + P_H_01808 J_41_01 J_41_02 120 200 130 0 Open + P_H_01809 J_41_02 J_41_03 120 200 130 0 Open + P_H_01810 J_41_03 J_41_04 120 200 130 0 Open + P_H_01811 J_41_04 J_41_05 120 200 130 0 Open + P_H_01812 J_41_05 J_41_06 120 200 130 0 Open + P_H_01813 J_41_06 J_41_07 120 200 130 0 Open + P_H_01814 J_41_07 J_41_08 120 200 130 0 Open + P_H_01815 J_41_08 J_41_09 120 200 130 0 Open + P_H_01816 J_41_09 J_41_10 120 200 130 0 Open + P_H_01817 J_41_10 J_41_11 120 200 130 0 Open + P_H_01818 J_41_11 J_41_12 120 200 130 0 Open + P_H_01819 J_41_12 J_41_13 120 200 130 0 Open + P_H_01820 J_41_13 J_41_14 120 200 130 0 Open + P_H_01821 J_41_14 J_41_15 120 200 130 0 Open + P_H_01822 J_41_15 J_41_16 120 200 130 0 Open + P_H_01823 J_41_16 J_41_17 120 200 130 0 Open + P_H_01824 J_41_17 J_41_18 120 200 130 0 Open + P_H_01825 J_41_18 J_41_19 120 200 130 0 Open + P_H_01826 J_41_19 J_41_20 120 200 130 0 Open + P_H_01827 J_41_20 J_41_21 120 200 130 0 Open + P_H_01828 J_41_21 J_41_22 120 200 130 0 Open + P_H_01829 J_41_22 J_41_23 120 200 130 0 Open + P_H_01830 J_41_23 J_41_24 120 200 130 0 Open + P_H_01831 J_41_24 J_41_25 120 200 130 0 Open + P_H_01832 J_41_25 J_41_26 120 200 130 0 Open + P_H_01833 J_41_26 J_41_27 120 200 130 0 Open + P_H_01834 J_41_27 J_41_28 120 200 130 0 Open + P_H_01835 J_41_28 J_41_29 120 200 130 0 Open + P_H_01836 J_41_29 J_41_30 120 200 130 0 Open + P_H_01837 J_41_30 J_41_31 120 200 130 0 Open + P_H_01838 J_41_31 J_41_32 120 200 130 0 Open + P_H_01839 J_41_32 J_41_33 120 200 130 0 Open + P_H_01840 J_41_33 J_41_34 120 200 130 0 Open + P_H_01841 J_41_34 J_41_35 120 200 130 0 Open + P_H_01842 J_41_35 J_41_36 120 200 130 0 Open + P_H_01843 J_41_36 J_41_37 120 200 130 0 Open + P_H_01844 J_41_37 J_41_38 120 200 130 0 Open + P_H_01845 J_41_38 J_41_39 120 200 130 0 Open + P_H_01846 J_41_39 J_41_40 120 200 130 0 Open + P_H_01847 J_41_40 J_41_41 120 200 130 0 Open + P_H_01848 J_41_41 J_41_42 120 200 130 0 Open + P_H_01849 J_41_42 J_41_43 120 200 130 0 Open + P_H_01850 J_41_43 J_41_44 120 200 130 0 Open + P_H_01851 J_42_00 J_42_01 120 200 130 0 Open + P_H_01852 J_42_01 J_42_02 120 200 130 0 Open + P_H_01853 J_42_02 J_42_03 120 200 130 0 Open + P_H_01854 J_42_03 J_42_04 120 200 130 0 Open + P_H_01855 J_42_04 J_42_05 120 200 130 0 Open + P_H_01856 J_42_05 J_42_06 120 200 130 0 Open + P_H_01857 J_42_06 J_42_07 120 200 130 0 Open + P_H_01858 J_42_07 J_42_08 120 200 130 0 Open + P_H_01859 J_42_08 J_42_09 120 200 130 0 Open + P_H_01860 J_42_09 J_42_10 120 200 130 0 Open + P_H_01861 J_42_10 J_42_11 120 200 130 0 Open + P_H_01862 J_42_11 J_42_12 120 200 130 0 Open + P_H_01863 J_42_12 J_42_13 120 200 130 0 Open + P_H_01864 J_42_13 J_42_14 120 200 130 0 Open + P_H_01865 J_42_14 J_42_15 120 200 130 0 Open + P_H_01866 J_42_15 J_42_16 120 200 130 0 Open + P_H_01867 J_42_16 J_42_17 120 200 130 0 Open + P_H_01868 J_42_17 J_42_18 120 200 130 0 Open + P_H_01869 J_42_18 J_42_19 120 200 130 0 Open + P_H_01870 J_42_19 J_42_20 120 200 130 0 Open + P_H_01871 J_42_20 J_42_21 120 200 130 0 Open + P_H_01872 J_42_21 J_42_22 120 200 130 0 Open + P_H_01873 J_42_22 J_42_23 120 200 130 0 Open + P_H_01874 J_42_23 J_42_24 120 200 130 0 Open + P_H_01875 J_42_24 J_42_25 120 200 130 0 Open + P_H_01876 J_42_25 J_42_26 120 200 130 0 Open + P_H_01877 J_42_26 J_42_27 120 200 130 0 Open + P_H_01878 J_42_27 J_42_28 120 200 130 0 Open + P_H_01879 J_42_28 J_42_29 120 200 130 0 Open + P_H_01880 J_42_29 J_42_30 120 200 130 0 Open + P_H_01881 J_42_30 J_42_31 120 200 130 0 Open + P_H_01882 J_42_31 J_42_32 120 200 130 0 Open + P_H_01883 J_42_32 J_42_33 120 200 130 0 Open + P_H_01884 J_42_33 J_42_34 120 200 130 0 Open + P_H_01885 J_42_34 J_42_35 120 200 130 0 Open + P_H_01886 J_42_35 J_42_36 120 200 130 0 Open + P_H_01887 J_42_36 J_42_37 120 200 130 0 Open + P_H_01888 J_42_37 J_42_38 120 200 130 0 Open + P_H_01889 J_42_38 J_42_39 120 200 130 0 Open + P_H_01890 J_42_39 J_42_40 120 200 130 0 Open + P_H_01891 J_42_40 J_42_41 120 200 130 0 Open + P_H_01892 J_42_41 J_42_42 120 200 130 0 Open + P_H_01893 J_42_42 J_42_43 120 200 130 0 Open + P_H_01894 J_42_43 J_42_44 120 200 130 0 Open + P_H_01895 J_43_00 J_43_01 120 200 130 0 Open + P_H_01896 J_43_01 J_43_02 120 200 130 0 Open + P_H_01897 J_43_02 J_43_03 120 200 130 0 Open + P_H_01898 J_43_03 J_43_04 120 200 130 0 Open + P_H_01899 J_43_04 J_43_05 120 200 130 0 Open + P_H_01900 J_43_05 J_43_06 120 200 130 0 Open + P_H_01901 J_43_06 J_43_07 120 200 130 0 Open + P_H_01902 J_43_07 J_43_08 120 200 130 0 Open + P_H_01903 J_43_08 J_43_09 120 200 130 0 Open + P_H_01904 J_43_09 J_43_10 120 200 130 0 Open + P_H_01905 J_43_10 J_43_11 120 200 130 0 Open + P_H_01906 J_43_11 J_43_12 120 200 130 0 Open + P_H_01907 J_43_12 J_43_13 120 200 130 0 Open + P_H_01908 J_43_13 J_43_14 120 200 130 0 Open + P_H_01909 J_43_14 J_43_15 120 200 130 0 Open + P_H_01910 J_43_15 J_43_16 120 200 130 0 Open + P_H_01911 J_43_16 J_43_17 120 200 130 0 Open + P_H_01912 J_43_17 J_43_18 120 200 130 0 Open + P_H_01913 J_43_18 J_43_19 120 200 130 0 Open + P_H_01914 J_43_19 J_43_20 120 200 130 0 Open + P_H_01915 J_43_20 J_43_21 120 200 130 0 Open + P_H_01916 J_43_21 J_43_22 120 200 130 0 Open + P_H_01917 J_43_22 J_43_23 120 200 130 0 Open + P_H_01918 J_43_23 J_43_24 120 200 130 0 Open + P_H_01919 J_43_24 J_43_25 120 200 130 0 Open + P_H_01920 J_43_25 J_43_26 120 200 130 0 Open + P_H_01921 J_43_26 J_43_27 120 200 130 0 Open + P_H_01922 J_43_27 J_43_28 120 200 130 0 Open + P_H_01923 J_43_28 J_43_29 120 200 130 0 Open + P_H_01924 J_43_29 J_43_30 120 200 130 0 Open + P_H_01925 J_43_30 J_43_31 120 200 130 0 Open + P_H_01926 J_43_31 J_43_32 120 200 130 0 Open + P_H_01927 J_43_32 J_43_33 120 200 130 0 Open + P_H_01928 J_43_33 J_43_34 120 200 130 0 Open + P_H_01929 J_43_34 J_43_35 120 200 130 0 Open + P_H_01930 J_43_35 J_43_36 120 200 130 0 Open + P_H_01931 J_43_36 J_43_37 120 200 130 0 Open + P_H_01932 J_43_37 J_43_38 120 200 130 0 Open + P_H_01933 J_43_38 J_43_39 120 200 130 0 Open + P_H_01934 J_43_39 J_43_40 120 200 130 0 Open + P_H_01935 J_43_40 J_43_41 120 200 130 0 Open + P_H_01936 J_43_41 J_43_42 120 200 130 0 Open + P_H_01937 J_43_42 J_43_43 120 200 130 0 Open + P_H_01938 J_43_43 J_43_44 120 200 130 0 Open + P_H_01939 J_44_00 J_44_01 120 200 130 0 Open + P_H_01940 J_44_01 J_44_02 120 200 130 0 Open + P_H_01941 J_44_02 J_44_03 120 200 130 0 Open + P_H_01942 J_44_03 J_44_04 120 200 130 0 Open + P_H_01943 J_44_04 J_44_05 120 200 130 0 Open + P_H_01944 J_44_05 J_44_06 120 200 130 0 Open + P_H_01945 J_44_06 J_44_07 120 200 130 0 Open + P_H_01946 J_44_07 J_44_08 120 200 130 0 Open + P_H_01947 J_44_08 J_44_09 120 200 130 0 Open + P_H_01948 J_44_09 J_44_10 120 200 130 0 Open + P_H_01949 J_44_10 J_44_11 120 200 130 0 Open + P_H_01950 J_44_11 J_44_12 120 200 130 0 Open + P_H_01951 J_44_12 J_44_13 120 200 130 0 Open + P_H_01952 J_44_13 J_44_14 120 200 130 0 Open + P_H_01953 J_44_14 J_44_15 120 200 130 0 Open + P_H_01954 J_44_15 J_44_16 120 200 130 0 Open + P_H_01955 J_44_16 J_44_17 120 200 130 0 Open + P_H_01956 J_44_17 J_44_18 120 200 130 0 Open + P_H_01957 J_44_18 J_44_19 120 200 130 0 Open + P_H_01958 J_44_19 J_44_20 120 200 130 0 Open + P_H_01959 J_44_20 J_44_21 120 200 130 0 Open + P_H_01960 J_44_21 J_44_22 120 200 130 0 Open + P_H_01961 J_44_22 J_44_23 120 200 130 0 Open + P_H_01962 J_44_23 J_44_24 120 200 130 0 Open + P_H_01963 J_44_24 J_44_25 120 200 130 0 Open + P_H_01964 J_44_25 J_44_26 120 200 130 0 Open + P_H_01965 J_44_26 J_44_27 120 200 130 0 Open + P_H_01966 J_44_27 J_44_28 120 200 130 0 Open + P_H_01967 J_44_28 J_44_29 120 200 130 0 Open + P_H_01968 J_44_29 J_44_30 120 200 130 0 Open + P_H_01969 J_44_30 J_44_31 120 200 130 0 Open + P_H_01970 J_44_31 J_44_32 120 200 130 0 Open + P_H_01971 J_44_32 J_44_33 120 200 130 0 Open + P_H_01972 J_44_33 J_44_34 120 200 130 0 Open + P_H_01973 J_44_34 J_44_35 120 200 130 0 Open + P_H_01974 J_44_35 J_44_36 120 200 130 0 Open + P_H_01975 J_44_36 J_44_37 120 200 130 0 Open + P_H_01976 J_44_37 J_44_38 120 200 130 0 Open + P_H_01977 J_44_38 J_44_39 120 200 130 0 Open + P_H_01978 J_44_39 J_44_40 120 200 130 0 Open + P_H_01979 J_44_40 J_44_41 120 200 130 0 Open + P_H_01980 J_44_41 J_44_42 120 200 130 0 Open + P_H_01981 J_44_42 J_44_43 120 200 130 0 Open + P_H_01982 J_44_43 J_44_44 120 200 130 0 Open + P_V_01983 J_00_00 J_01_00 120 200 130 0 Open + P_V_01984 J_00_01 J_01_01 120 200 130 0 Open + P_V_01985 J_00_02 J_01_02 120 200 130 0 Open + P_V_01986 J_00_03 J_01_03 120 200 130 0 Open + P_V_01987 J_00_04 J_01_04 120 200 130 0 Open + P_V_01988 J_00_05 J_01_05 120 200 130 0 Open + P_V_01989 J_00_06 J_01_06 120 200 130 0 Open + P_V_01990 J_00_07 J_01_07 120 200 130 0 Open + P_V_01991 J_00_08 J_01_08 120 200 130 0 Open + P_V_01992 J_00_09 J_01_09 120 200 130 0 Open + P_V_01993 J_00_10 J_01_10 120 200 130 0 Open + P_V_01994 J_00_11 J_01_11 120 200 130 0 Open + P_V_01995 J_00_12 J_01_12 120 200 130 0 Open + P_V_01996 J_00_13 J_01_13 120 200 130 0 Open + P_V_01997 J_00_14 J_01_14 120 200 130 0 Open + P_V_01998 J_00_15 J_01_15 120 200 130 0 Open + P_V_01999 J_00_16 J_01_16 120 200 130 0 Open + P_V_02000 J_00_17 J_01_17 120 200 130 0 Open + P_V_02001 J_00_18 J_01_18 120 200 130 0 Open + P_V_02002 J_00_19 J_01_19 120 200 130 0 Open + P_V_02003 J_00_20 J_01_20 120 200 130 0 Open + P_V_02004 J_00_21 J_01_21 120 200 130 0 Open + P_V_02005 J_00_22 J_01_22 120 200 130 0 Open + P_V_02006 J_00_23 J_01_23 120 200 130 0 Open + P_V_02007 J_00_24 J_01_24 120 200 130 0 Open + P_V_02008 J_00_25 J_01_25 120 200 130 0 Open + P_V_02009 J_00_26 J_01_26 120 200 130 0 Open + P_V_02010 J_00_27 J_01_27 120 200 130 0 Open + P_V_02011 J_00_28 J_01_28 120 200 130 0 Open + P_V_02012 J_00_29 J_01_29 120 200 130 0 Open + P_V_02013 J_00_30 J_01_30 120 200 130 0 Open + P_V_02014 J_00_31 J_01_31 120 200 130 0 Open + P_V_02015 J_00_32 J_01_32 120 200 130 0 Open + P_V_02016 J_00_33 J_01_33 120 200 130 0 Open + P_V_02017 J_00_34 J_01_34 120 200 130 0 Open + P_V_02018 J_00_35 J_01_35 120 200 130 0 Open + P_V_02019 J_00_36 J_01_36 120 200 130 0 Open + P_V_02020 J_00_37 J_01_37 120 200 130 0 Open + P_V_02021 J_00_38 J_01_38 120 200 130 0 Open + P_V_02022 J_00_39 J_01_39 120 200 130 0 Open + P_V_02023 J_00_40 J_01_40 120 200 130 0 Open + P_V_02024 J_00_41 J_01_41 120 200 130 0 Open + P_V_02025 J_00_42 J_01_42 120 200 130 0 Open + P_V_02026 J_00_43 J_01_43 120 200 130 0 Open + P_V_02027 J_00_44 J_01_44 120 200 130 0 Open + P_V_02028 J_01_00 J_02_00 120 200 130 0 Open + P_V_02029 J_01_01 J_02_01 120 200 130 0 Open + P_V_02030 J_01_02 J_02_02 120 200 130 0 Open + P_V_02031 J_01_03 J_02_03 120 200 130 0 Open + P_V_02032 J_01_04 J_02_04 120 200 130 0 Open + P_V_02033 J_01_05 J_02_05 120 200 130 0 Open + P_V_02034 J_01_06 J_02_06 120 200 130 0 Open + P_V_02035 J_01_07 J_02_07 120 200 130 0 Open + P_V_02036 J_01_08 J_02_08 120 200 130 0 Open + P_V_02037 J_01_09 J_02_09 120 200 130 0 Open + P_V_02038 J_01_10 J_02_10 120 200 130 0 Open + P_V_02039 J_01_11 J_02_11 120 200 130 0 Open + P_V_02040 J_01_12 J_02_12 120 200 130 0 Open + P_V_02041 J_01_13 J_02_13 120 200 130 0 Open + P_V_02042 J_01_14 J_02_14 120 200 130 0 Open + P_V_02043 J_01_15 J_02_15 120 200 130 0 Open + P_V_02044 J_01_16 J_02_16 120 200 130 0 Open + P_V_02045 J_01_17 J_02_17 120 200 130 0 Open + P_V_02046 J_01_18 J_02_18 120 200 130 0 Open + P_V_02047 J_01_19 J_02_19 120 200 130 0 Open + P_V_02048 J_01_20 J_02_20 120 200 130 0 Open + P_V_02049 J_01_21 J_02_21 120 200 130 0 Open + P_V_02050 J_01_22 J_02_22 120 200 130 0 Open + P_V_02051 J_01_23 J_02_23 120 200 130 0 Open + P_V_02052 J_01_24 J_02_24 120 200 130 0 Open + P_V_02053 J_01_25 J_02_25 120 200 130 0 Open + P_V_02054 J_01_26 J_02_26 120 200 130 0 Open + P_V_02055 J_01_27 J_02_27 120 200 130 0 Open + P_V_02056 J_01_28 J_02_28 120 200 130 0 Open + P_V_02057 J_01_29 J_02_29 120 200 130 0 Open + P_V_02058 J_01_30 J_02_30 120 200 130 0 Open + P_V_02059 J_01_31 J_02_31 120 200 130 0 Open + P_V_02060 J_01_32 J_02_32 120 200 130 0 Open + P_V_02061 J_01_33 J_02_33 120 200 130 0 Open + P_V_02062 J_01_34 J_02_34 120 200 130 0 Open + P_V_02063 J_01_35 J_02_35 120 200 130 0 Open + P_V_02064 J_01_36 J_02_36 120 200 130 0 Open + P_V_02065 J_01_37 J_02_37 120 200 130 0 Open + P_V_02066 J_01_38 J_02_38 120 200 130 0 Open + P_V_02067 J_01_39 J_02_39 120 200 130 0 Open + P_V_02068 J_01_40 J_02_40 120 200 130 0 Open + P_V_02069 J_01_41 J_02_41 120 200 130 0 Open + P_V_02070 J_01_42 J_02_42 120 200 130 0 Open + P_V_02071 J_01_43 J_02_43 120 200 130 0 Open + P_V_02072 J_01_44 J_02_44 120 200 130 0 Open + P_V_02073 J_02_00 J_03_00 120 200 130 0 Open + P_V_02074 J_02_01 J_03_01 120 200 130 0 Open + P_V_02075 J_02_02 J_03_02 120 200 130 0 Open + P_V_02076 J_02_03 J_03_03 120 200 130 0 Open + P_V_02077 J_02_04 J_03_04 120 200 130 0 Open + P_V_02078 J_02_05 J_03_05 120 200 130 0 Open + P_V_02079 J_02_06 J_03_06 120 200 130 0 Open + P_V_02080 J_02_07 J_03_07 120 200 130 0 Open + P_V_02081 J_02_08 J_03_08 120 200 130 0 Open + P_V_02082 J_02_09 J_03_09 120 200 130 0 Open + P_V_02083 J_02_10 J_03_10 120 200 130 0 Open + P_V_02084 J_02_11 J_03_11 120 200 130 0 Open + P_V_02085 J_02_12 J_03_12 120 200 130 0 Open + P_V_02086 J_02_13 J_03_13 120 200 130 0 Open + P_V_02087 J_02_14 J_03_14 120 200 130 0 Open + P_V_02088 J_02_15 J_03_15 120 200 130 0 Open + P_V_02089 J_02_16 J_03_16 120 200 130 0 Open + P_V_02090 J_02_17 J_03_17 120 200 130 0 Open + P_V_02091 J_02_18 J_03_18 120 200 130 0 Open + P_V_02092 J_02_19 J_03_19 120 200 130 0 Open + P_V_02093 J_02_20 J_03_20 120 200 130 0 Open + P_V_02094 J_02_21 J_03_21 120 200 130 0 Open + P_V_02095 J_02_22 J_03_22 120 200 130 0 Open + P_V_02096 J_02_23 J_03_23 120 200 130 0 Open + P_V_02097 J_02_24 J_03_24 120 200 130 0 Open + P_V_02098 J_02_25 J_03_25 120 200 130 0 Open + P_V_02099 J_02_26 J_03_26 120 200 130 0 Open + P_V_02100 J_02_27 J_03_27 120 200 130 0 Open + P_V_02101 J_02_28 J_03_28 120 200 130 0 Open + P_V_02102 J_02_29 J_03_29 120 200 130 0 Open + P_V_02103 J_02_30 J_03_30 120 200 130 0 Open + P_V_02104 J_02_31 J_03_31 120 200 130 0 Open + P_V_02105 J_02_32 J_03_32 120 200 130 0 Open + P_V_02106 J_02_33 J_03_33 120 200 130 0 Open + P_V_02107 J_02_34 J_03_34 120 200 130 0 Open + P_V_02108 J_02_35 J_03_35 120 200 130 0 Open + P_V_02109 J_02_36 J_03_36 120 200 130 0 Open + P_V_02110 J_02_37 J_03_37 120 200 130 0 Open + P_V_02111 J_02_38 J_03_38 120 200 130 0 Open + P_V_02112 J_02_39 J_03_39 120 200 130 0 Open + P_V_02113 J_02_40 J_03_40 120 200 130 0 Open + P_V_02114 J_02_41 J_03_41 120 200 130 0 Open + P_V_02115 J_02_42 J_03_42 120 200 130 0 Open + P_V_02116 J_02_43 J_03_43 120 200 130 0 Open + P_V_02117 J_02_44 J_03_44 120 200 130 0 Open + P_V_02118 J_03_00 J_04_00 120 200 130 0 Open + P_V_02119 J_03_01 J_04_01 120 200 130 0 Open + P_V_02120 J_03_02 J_04_02 120 200 130 0 Open + P_V_02121 J_03_03 J_04_03 120 200 130 0 Open + P_V_02122 J_03_04 J_04_04 120 200 130 0 Open + P_V_02123 J_03_05 J_04_05 120 200 130 0 Open + P_V_02124 J_03_06 J_04_06 120 200 130 0 Open + P_V_02125 J_03_07 J_04_07 120 200 130 0 Open + P_V_02126 J_03_08 J_04_08 120 200 130 0 Open + P_V_02127 J_03_09 J_04_09 120 200 130 0 Open + P_V_02128 J_03_10 J_04_10 120 200 130 0 Open + P_V_02129 J_03_11 J_04_11 120 200 130 0 Open + P_V_02130 J_03_12 J_04_12 120 200 130 0 Open + P_V_02131 J_03_13 J_04_13 120 200 130 0 Open + P_V_02132 J_03_14 J_04_14 120 200 130 0 Open + P_V_02133 J_03_15 J_04_15 120 200 130 0 Open + P_V_02134 J_03_16 J_04_16 120 200 130 0 Open + P_V_02135 J_03_17 J_04_17 120 200 130 0 Open + P_V_02136 J_03_18 J_04_18 120 200 130 0 Open + P_V_02137 J_03_19 J_04_19 120 200 130 0 Open + P_V_02138 J_03_20 J_04_20 120 200 130 0 Open + P_V_02139 J_03_21 J_04_21 120 200 130 0 Open + P_V_02140 J_03_22 J_04_22 120 200 130 0 Open + P_V_02141 J_03_23 J_04_23 120 200 130 0 Open + P_V_02142 J_03_24 J_04_24 120 200 130 0 Open + P_V_02143 J_03_25 J_04_25 120 200 130 0 Open + P_V_02144 J_03_26 J_04_26 120 200 130 0 Open + P_V_02145 J_03_27 J_04_27 120 200 130 0 Open + P_V_02146 J_03_28 J_04_28 120 200 130 0 Open + P_V_02147 J_03_29 J_04_29 120 200 130 0 Open + P_V_02148 J_03_30 J_04_30 120 200 130 0 Open + P_V_02149 J_03_31 J_04_31 120 200 130 0 Open + P_V_02150 J_03_32 J_04_32 120 200 130 0 Open + P_V_02151 J_03_33 J_04_33 120 200 130 0 Open + P_V_02152 J_03_34 J_04_34 120 200 130 0 Open + P_V_02153 J_03_35 J_04_35 120 200 130 0 Open + P_V_02154 J_03_36 J_04_36 120 200 130 0 Open + P_V_02155 J_03_37 J_04_37 120 200 130 0 Open + P_V_02156 J_03_38 J_04_38 120 200 130 0 Open + P_V_02157 J_03_39 J_04_39 120 200 130 0 Open + P_V_02158 J_03_40 J_04_40 120 200 130 0 Open + P_V_02159 J_03_41 J_04_41 120 200 130 0 Open + P_V_02160 J_03_42 J_04_42 120 200 130 0 Open + P_V_02161 J_03_43 J_04_43 120 200 130 0 Open + P_V_02162 J_03_44 J_04_44 120 200 130 0 Open + P_V_02163 J_04_00 J_05_00 120 200 130 0 Open + P_V_02164 J_04_01 J_05_01 120 200 130 0 Open + P_V_02165 J_04_02 J_05_02 120 200 130 0 Open + P_V_02166 J_04_03 J_05_03 120 200 130 0 Open + P_V_02167 J_04_04 J_05_04 120 200 130 0 Open + P_V_02168 J_04_05 J_05_05 120 200 130 0 Open + P_V_02169 J_04_06 J_05_06 120 200 130 0 Open + P_V_02170 J_04_07 J_05_07 120 200 130 0 Open + P_V_02171 J_04_08 J_05_08 120 200 130 0 Open + P_V_02172 J_04_09 J_05_09 120 200 130 0 Open + P_V_02173 J_04_10 J_05_10 120 200 130 0 Open + P_V_02174 J_04_11 J_05_11 120 200 130 0 Open + P_V_02175 J_04_12 J_05_12 120 200 130 0 Open + P_V_02176 J_04_13 J_05_13 120 200 130 0 Open + P_V_02177 J_04_14 J_05_14 120 200 130 0 Open + P_V_02178 J_04_15 J_05_15 120 200 130 0 Open + P_V_02179 J_04_16 J_05_16 120 200 130 0 Open + P_V_02180 J_04_17 J_05_17 120 200 130 0 Open + P_V_02181 J_04_18 J_05_18 120 200 130 0 Open + P_V_02182 J_04_19 J_05_19 120 200 130 0 Open + P_V_02183 J_04_20 J_05_20 120 200 130 0 Open + P_V_02184 J_04_21 J_05_21 120 200 130 0 Open + P_V_02185 J_04_22 J_05_22 120 200 130 0 Open + P_V_02186 J_04_23 J_05_23 120 200 130 0 Open + P_V_02187 J_04_24 J_05_24 120 200 130 0 Open + P_V_02188 J_04_25 J_05_25 120 200 130 0 Open + P_V_02189 J_04_26 J_05_26 120 200 130 0 Open + P_V_02190 J_04_27 J_05_27 120 200 130 0 Open + P_V_02191 J_04_28 J_05_28 120 200 130 0 Open + P_V_02192 J_04_29 J_05_29 120 200 130 0 Open + P_V_02193 J_04_30 J_05_30 120 200 130 0 Open + P_V_02194 J_04_31 J_05_31 120 200 130 0 Open + P_V_02195 J_04_32 J_05_32 120 200 130 0 Open + P_V_02196 J_04_33 J_05_33 120 200 130 0 Open + P_V_02197 J_04_34 J_05_34 120 200 130 0 Open + P_V_02198 J_04_35 J_05_35 120 200 130 0 Open + P_V_02199 J_04_36 J_05_36 120 200 130 0 Open + P_V_02200 J_04_37 J_05_37 120 200 130 0 Open + P_V_02201 J_04_38 J_05_38 120 200 130 0 Open + P_V_02202 J_04_39 J_05_39 120 200 130 0 Open + P_V_02203 J_04_40 J_05_40 120 200 130 0 Open + P_V_02204 J_04_41 J_05_41 120 200 130 0 Open + P_V_02205 J_04_42 J_05_42 120 200 130 0 Open + P_V_02206 J_04_43 J_05_43 120 200 130 0 Open + P_V_02207 J_04_44 J_05_44 120 200 130 0 Open + P_V_02208 J_05_00 J_06_00 120 200 130 0 Open + P_V_02209 J_05_01 J_06_01 120 200 130 0 Open + P_V_02210 J_05_02 J_06_02 120 200 130 0 Open + P_V_02211 J_05_03 J_06_03 120 200 130 0 Open + P_V_02212 J_05_04 J_06_04 120 200 130 0 Open + P_V_02213 J_05_05 J_06_05 120 200 130 0 Open + P_V_02214 J_05_06 J_06_06 120 200 130 0 Open + P_V_02215 J_05_07 J_06_07 120 200 130 0 Open + P_V_02216 J_05_08 J_06_08 120 200 130 0 Open + P_V_02217 J_05_09 J_06_09 120 200 130 0 Open + P_V_02218 J_05_10 J_06_10 120 200 130 0 Open + P_V_02219 J_05_11 J_06_11 120 200 130 0 Open + P_V_02220 J_05_12 J_06_12 120 200 130 0 Open + P_V_02221 J_05_13 J_06_13 120 200 130 0 Open + P_V_02222 J_05_14 J_06_14 120 200 130 0 Open + P_V_02223 J_05_15 J_06_15 120 200 130 0 Open + P_V_02224 J_05_16 J_06_16 120 200 130 0 Open + P_V_02225 J_05_17 J_06_17 120 200 130 0 Open + P_V_02226 J_05_18 J_06_18 120 200 130 0 Open + P_V_02227 J_05_19 J_06_19 120 200 130 0 Open + P_V_02228 J_05_20 J_06_20 120 200 130 0 Open + P_V_02229 J_05_21 J_06_21 120 200 130 0 Open + P_V_02230 J_05_22 J_06_22 120 200 130 0 Open + P_V_02231 J_05_23 J_06_23 120 200 130 0 Open + P_V_02232 J_05_24 J_06_24 120 200 130 0 Open + P_V_02233 J_05_25 J_06_25 120 200 130 0 Open + P_V_02234 J_05_26 J_06_26 120 200 130 0 Open + P_V_02235 J_05_27 J_06_27 120 200 130 0 Open + P_V_02236 J_05_28 J_06_28 120 200 130 0 Open + P_V_02237 J_05_29 J_06_29 120 200 130 0 Open + P_V_02238 J_05_30 J_06_30 120 200 130 0 Open + P_V_02239 J_05_31 J_06_31 120 200 130 0 Open + P_V_02240 J_05_32 J_06_32 120 200 130 0 Open + P_V_02241 J_05_33 J_06_33 120 200 130 0 Open + P_V_02242 J_05_34 J_06_34 120 200 130 0 Open + P_V_02243 J_05_35 J_06_35 120 200 130 0 Open + P_V_02244 J_05_36 J_06_36 120 200 130 0 Open + P_V_02245 J_05_37 J_06_37 120 200 130 0 Open + P_V_02246 J_05_38 J_06_38 120 200 130 0 Open + P_V_02247 J_05_39 J_06_39 120 200 130 0 Open + P_V_02248 J_05_40 J_06_40 120 200 130 0 Open + P_V_02249 J_05_41 J_06_41 120 200 130 0 Open + P_V_02250 J_05_42 J_06_42 120 200 130 0 Open + P_V_02251 J_05_43 J_06_43 120 200 130 0 Open + P_V_02252 J_05_44 J_06_44 120 200 130 0 Open + P_V_02253 J_06_00 J_07_00 120 200 130 0 Open + P_V_02254 J_06_01 J_07_01 120 200 130 0 Open + P_V_02255 J_06_02 J_07_02 120 200 130 0 Open + P_V_02256 J_06_03 J_07_03 120 200 130 0 Open + P_V_02257 J_06_04 J_07_04 120 200 130 0 Open + P_V_02258 J_06_05 J_07_05 120 200 130 0 Open + P_V_02259 J_06_06 J_07_06 120 200 130 0 Open + P_V_02260 J_06_07 J_07_07 120 200 130 0 Open + P_V_02261 J_06_08 J_07_08 120 200 130 0 Open + P_V_02262 J_06_09 J_07_09 120 200 130 0 Open + P_V_02263 J_06_10 J_07_10 120 200 130 0 Open + P_V_02264 J_06_11 J_07_11 120 200 130 0 Open + P_V_02265 J_06_12 J_07_12 120 200 130 0 Open + P_V_02266 J_06_13 J_07_13 120 200 130 0 Open + P_V_02267 J_06_14 J_07_14 120 200 130 0 Open + P_V_02268 J_06_15 J_07_15 120 200 130 0 Open + P_V_02269 J_06_16 J_07_16 120 200 130 0 Open + P_V_02270 J_06_17 J_07_17 120 200 130 0 Open + P_V_02271 J_06_18 J_07_18 120 200 130 0 Open + P_V_02272 J_06_19 J_07_19 120 200 130 0 Open + P_V_02273 J_06_20 J_07_20 120 200 130 0 Open + P_V_02274 J_06_21 J_07_21 120 200 130 0 Open + P_V_02275 J_06_22 J_07_22 120 200 130 0 Open + P_V_02276 J_06_23 J_07_23 120 200 130 0 Open + P_V_02277 J_06_24 J_07_24 120 200 130 0 Open + P_V_02278 J_06_25 J_07_25 120 200 130 0 Open + P_V_02279 J_06_26 J_07_26 120 200 130 0 Open + P_V_02280 J_06_27 J_07_27 120 200 130 0 Open + P_V_02281 J_06_28 J_07_28 120 200 130 0 Open + P_V_02282 J_06_29 J_07_29 120 200 130 0 Open + P_V_02283 J_06_30 J_07_30 120 200 130 0 Open + P_V_02284 J_06_31 J_07_31 120 200 130 0 Open + P_V_02285 J_06_32 J_07_32 120 200 130 0 Open + P_V_02286 J_06_33 J_07_33 120 200 130 0 Open + P_V_02287 J_06_34 J_07_34 120 200 130 0 Open + P_V_02288 J_06_35 J_07_35 120 200 130 0 Open + P_V_02289 J_06_36 J_07_36 120 200 130 0 Open + P_V_02290 J_06_37 J_07_37 120 200 130 0 Open + P_V_02291 J_06_38 J_07_38 120 200 130 0 Open + P_V_02292 J_06_39 J_07_39 120 200 130 0 Open + P_V_02293 J_06_40 J_07_40 120 200 130 0 Open + P_V_02294 J_06_41 J_07_41 120 200 130 0 Open + P_V_02295 J_06_42 J_07_42 120 200 130 0 Open + P_V_02296 J_06_43 J_07_43 120 200 130 0 Open + P_V_02297 J_06_44 J_07_44 120 200 130 0 Open + P_V_02298 J_07_00 J_08_00 120 200 130 0 Open + P_V_02299 J_07_01 J_08_01 120 200 130 0 Open + P_V_02300 J_07_02 J_08_02 120 200 130 0 Open + P_V_02301 J_07_03 J_08_03 120 200 130 0 Open + P_V_02302 J_07_04 J_08_04 120 200 130 0 Open + P_V_02303 J_07_05 J_08_05 120 200 130 0 Open + P_V_02304 J_07_06 J_08_06 120 200 130 0 Open + P_V_02305 J_07_07 J_08_07 120 200 130 0 Open + P_V_02306 J_07_08 J_08_08 120 200 130 0 Open + P_V_02307 J_07_09 J_08_09 120 200 130 0 Open + P_V_02308 J_07_10 J_08_10 120 200 130 0 Open + P_V_02309 J_07_11 J_08_11 120 200 130 0 Open + P_V_02310 J_07_12 J_08_12 120 200 130 0 Open + P_V_02311 J_07_13 J_08_13 120 200 130 0 Open + P_V_02312 J_07_14 J_08_14 120 200 130 0 Open + P_V_02313 J_07_15 J_08_15 120 200 130 0 Open + P_V_02314 J_07_16 J_08_16 120 200 130 0 Open + P_V_02315 J_07_17 J_08_17 120 200 130 0 Open + P_V_02316 J_07_18 J_08_18 120 200 130 0 Open + P_V_02317 J_07_19 J_08_19 120 200 130 0 Open + P_V_02318 J_07_20 J_08_20 120 200 130 0 Open + P_V_02319 J_07_21 J_08_21 120 200 130 0 Open + P_V_02320 J_07_22 J_08_22 120 200 130 0 Open + P_V_02321 J_07_23 J_08_23 120 200 130 0 Open + P_V_02322 J_07_24 J_08_24 120 200 130 0 Open + P_V_02323 J_07_25 J_08_25 120 200 130 0 Open + P_V_02324 J_07_26 J_08_26 120 200 130 0 Open + P_V_02325 J_07_27 J_08_27 120 200 130 0 Open + P_V_02326 J_07_28 J_08_28 120 200 130 0 Open + P_V_02327 J_07_29 J_08_29 120 200 130 0 Open + P_V_02328 J_07_30 J_08_30 120 200 130 0 Open + P_V_02329 J_07_31 J_08_31 120 200 130 0 Open + P_V_02330 J_07_32 J_08_32 120 200 130 0 Open + P_V_02331 J_07_33 J_08_33 120 200 130 0 Open + P_V_02332 J_07_34 J_08_34 120 200 130 0 Open + P_V_02333 J_07_35 J_08_35 120 200 130 0 Open + P_V_02334 J_07_36 J_08_36 120 200 130 0 Open + P_V_02335 J_07_37 J_08_37 120 200 130 0 Open + P_V_02336 J_07_38 J_08_38 120 200 130 0 Open + P_V_02337 J_07_39 J_08_39 120 200 130 0 Open + P_V_02338 J_07_40 J_08_40 120 200 130 0 Open + P_V_02339 J_07_41 J_08_41 120 200 130 0 Open + P_V_02340 J_07_42 J_08_42 120 200 130 0 Open + P_V_02341 J_07_43 J_08_43 120 200 130 0 Open + P_V_02342 J_07_44 J_08_44 120 200 130 0 Open + P_V_02343 J_08_00 J_09_00 120 200 130 0 Open + P_V_02344 J_08_01 J_09_01 120 200 130 0 Open + P_V_02345 J_08_02 J_09_02 120 200 130 0 Open + P_V_02346 J_08_03 J_09_03 120 200 130 0 Open + P_V_02347 J_08_04 J_09_04 120 200 130 0 Open + P_V_02348 J_08_05 J_09_05 120 200 130 0 Open + P_V_02349 J_08_06 J_09_06 120 200 130 0 Open + P_V_02350 J_08_07 J_09_07 120 200 130 0 Open + P_V_02351 J_08_08 J_09_08 120 200 130 0 Open + P_V_02352 J_08_09 J_09_09 120 200 130 0 Open + P_V_02353 J_08_10 J_09_10 120 200 130 0 Open + P_V_02354 J_08_11 J_09_11 120 200 130 0 Open + P_V_02355 J_08_12 J_09_12 120 200 130 0 Open + P_V_02356 J_08_13 J_09_13 120 200 130 0 Open + P_V_02357 J_08_14 J_09_14 120 200 130 0 Open + P_V_02358 J_08_15 J_09_15 120 200 130 0 Open + P_V_02359 J_08_16 J_09_16 120 200 130 0 Open + P_V_02360 J_08_17 J_09_17 120 200 130 0 Open + P_V_02361 J_08_18 J_09_18 120 200 130 0 Open + P_V_02362 J_08_19 J_09_19 120 200 130 0 Open + P_V_02363 J_08_20 J_09_20 120 200 130 0 Open + P_V_02364 J_08_21 J_09_21 120 200 130 0 Open + P_V_02365 J_08_22 J_09_22 120 200 130 0 Open + P_V_02366 J_08_23 J_09_23 120 200 130 0 Open + P_V_02367 J_08_24 J_09_24 120 200 130 0 Open + P_V_02368 J_08_25 J_09_25 120 200 130 0 Open + P_V_02369 J_08_26 J_09_26 120 200 130 0 Open + P_V_02370 J_08_27 J_09_27 120 200 130 0 Open + P_V_02371 J_08_28 J_09_28 120 200 130 0 Open + P_V_02372 J_08_29 J_09_29 120 200 130 0 Open + P_V_02373 J_08_30 J_09_30 120 200 130 0 Open + P_V_02374 J_08_31 J_09_31 120 200 130 0 Open + P_V_02375 J_08_32 J_09_32 120 200 130 0 Open + P_V_02376 J_08_33 J_09_33 120 200 130 0 Open + P_V_02377 J_08_34 J_09_34 120 200 130 0 Open + P_V_02378 J_08_35 J_09_35 120 200 130 0 Open + P_V_02379 J_08_36 J_09_36 120 200 130 0 Open + P_V_02380 J_08_37 J_09_37 120 200 130 0 Open + P_V_02381 J_08_38 J_09_38 120 200 130 0 Open + P_V_02382 J_08_39 J_09_39 120 200 130 0 Open + P_V_02383 J_08_40 J_09_40 120 200 130 0 Open + P_V_02384 J_08_41 J_09_41 120 200 130 0 Open + P_V_02385 J_08_42 J_09_42 120 200 130 0 Open + P_V_02386 J_08_43 J_09_43 120 200 130 0 Open + P_V_02387 J_08_44 J_09_44 120 200 130 0 Open + P_V_02388 J_09_00 J_10_00 120 200 130 0 Open + P_V_02389 J_09_01 J_10_01 120 200 130 0 Open + P_V_02390 J_09_02 J_10_02 120 200 130 0 Open + P_V_02391 J_09_03 J_10_03 120 200 130 0 Open + P_V_02392 J_09_04 J_10_04 120 200 130 0 Open + P_V_02393 J_09_05 J_10_05 120 200 130 0 Open + P_V_02394 J_09_06 J_10_06 120 200 130 0 Open + P_V_02395 J_09_07 J_10_07 120 200 130 0 Open + P_V_02396 J_09_08 J_10_08 120 200 130 0 Open + P_V_02397 J_09_09 J_10_09 120 200 130 0 Open + P_V_02398 J_09_10 J_10_10 120 200 130 0 Open + P_V_02399 J_09_11 J_10_11 120 200 130 0 Open + P_V_02400 J_09_12 J_10_12 120 200 130 0 Open + P_V_02401 J_09_13 J_10_13 120 200 130 0 Open + P_V_02402 J_09_14 J_10_14 120 200 130 0 Open + P_V_02403 J_09_15 J_10_15 120 200 130 0 Open + P_V_02404 J_09_16 J_10_16 120 200 130 0 Open + P_V_02405 J_09_17 J_10_17 120 200 130 0 Open + P_V_02406 J_09_18 J_10_18 120 200 130 0 Open + P_V_02407 J_09_19 J_10_19 120 200 130 0 Open + P_V_02408 J_09_20 J_10_20 120 200 130 0 Open + P_V_02409 J_09_21 J_10_21 120 200 130 0 Open + P_V_02410 J_09_22 J_10_22 120 200 130 0 Open + P_V_02411 J_09_23 J_10_23 120 200 130 0 Open + P_V_02412 J_09_24 J_10_24 120 200 130 0 Open + P_V_02413 J_09_25 J_10_25 120 200 130 0 Open + P_V_02414 J_09_26 J_10_26 120 200 130 0 Open + P_V_02415 J_09_27 J_10_27 120 200 130 0 Open + P_V_02416 J_09_28 J_10_28 120 200 130 0 Open + P_V_02417 J_09_29 J_10_29 120 200 130 0 Open + P_V_02418 J_09_30 J_10_30 120 200 130 0 Open + P_V_02419 J_09_31 J_10_31 120 200 130 0 Open + P_V_02420 J_09_32 J_10_32 120 200 130 0 Open + P_V_02421 J_09_33 J_10_33 120 200 130 0 Open + P_V_02422 J_09_34 J_10_34 120 200 130 0 Open + P_V_02423 J_09_35 J_10_35 120 200 130 0 Open + P_V_02424 J_09_36 J_10_36 120 200 130 0 Open + P_V_02425 J_09_37 J_10_37 120 200 130 0 Open + P_V_02426 J_09_38 J_10_38 120 200 130 0 Open + P_V_02427 J_09_39 J_10_39 120 200 130 0 Open + P_V_02428 J_09_40 J_10_40 120 200 130 0 Open + P_V_02429 J_09_41 J_10_41 120 200 130 0 Open + P_V_02430 J_09_42 J_10_42 120 200 130 0 Open + P_V_02431 J_09_43 J_10_43 120 200 130 0 Open + P_V_02432 J_09_44 J_10_44 120 200 130 0 Open + P_V_02433 J_10_00 J_11_00 120 200 130 0 Open + P_V_02434 J_10_01 J_11_01 120 200 130 0 Open + P_V_02435 J_10_02 J_11_02 120 200 130 0 Open + P_V_02436 J_10_03 J_11_03 120 200 130 0 Open + P_V_02437 J_10_04 J_11_04 120 200 130 0 Open + P_V_02438 J_10_05 J_11_05 120 200 130 0 Open + P_V_02439 J_10_06 J_11_06 120 200 130 0 Open + P_V_02440 J_10_07 J_11_07 120 200 130 0 Open + P_V_02441 J_10_08 J_11_08 120 200 130 0 Open + P_V_02442 J_10_09 J_11_09 120 200 130 0 Open + P_V_02443 J_10_10 J_11_10 120 200 130 0 Open + P_V_02444 J_10_11 J_11_11 120 200 130 0 Open + P_V_02445 J_10_12 J_11_12 120 200 130 0 Open + P_V_02446 J_10_13 J_11_13 120 200 130 0 Open + P_V_02447 J_10_14 J_11_14 120 200 130 0 Open + P_V_02448 J_10_15 J_11_15 120 200 130 0 Open + P_V_02449 J_10_16 J_11_16 120 200 130 0 Open + P_V_02450 J_10_17 J_11_17 120 200 130 0 Open + P_V_02451 J_10_18 J_11_18 120 200 130 0 Open + P_V_02452 J_10_19 J_11_19 120 200 130 0 Open + P_V_02453 J_10_20 J_11_20 120 200 130 0 Open + P_V_02454 J_10_21 J_11_21 120 200 130 0 Open + P_V_02455 J_10_22 J_11_22 120 200 130 0 Open + P_V_02456 J_10_23 J_11_23 120 200 130 0 Open + P_V_02457 J_10_24 J_11_24 120 200 130 0 Open + P_V_02458 J_10_25 J_11_25 120 200 130 0 Open + P_V_02459 J_10_26 J_11_26 120 200 130 0 Open + P_V_02460 J_10_27 J_11_27 120 200 130 0 Open + P_V_02461 J_10_28 J_11_28 120 200 130 0 Open + P_V_02462 J_10_29 J_11_29 120 200 130 0 Open + P_V_02463 J_10_30 J_11_30 120 200 130 0 Open + P_V_02464 J_10_31 J_11_31 120 200 130 0 Open + P_V_02465 J_10_32 J_11_32 120 200 130 0 Open + P_V_02466 J_10_33 J_11_33 120 200 130 0 Open + P_V_02467 J_10_34 J_11_34 120 200 130 0 Open + P_V_02468 J_10_35 J_11_35 120 200 130 0 Open + P_V_02469 J_10_36 J_11_36 120 200 130 0 Open + P_V_02470 J_10_37 J_11_37 120 200 130 0 Open + P_V_02471 J_10_38 J_11_38 120 200 130 0 Open + P_V_02472 J_10_39 J_11_39 120 200 130 0 Open + P_V_02473 J_10_40 J_11_40 120 200 130 0 Open + P_V_02474 J_10_41 J_11_41 120 200 130 0 Open + P_V_02475 J_10_42 J_11_42 120 200 130 0 Open + P_V_02476 J_10_43 J_11_43 120 200 130 0 Open + P_V_02477 J_10_44 J_11_44 120 200 130 0 Open + P_V_02478 J_11_00 J_12_00 120 200 130 0 Open + P_V_02479 J_11_01 J_12_01 120 200 130 0 Open + P_V_02480 J_11_02 J_12_02 120 200 130 0 Open + P_V_02481 J_11_03 J_12_03 120 200 130 0 Open + P_V_02482 J_11_04 J_12_04 120 200 130 0 Open + P_V_02483 J_11_05 J_12_05 120 200 130 0 Open + P_V_02484 J_11_06 J_12_06 120 200 130 0 Open + P_V_02485 J_11_07 J_12_07 120 200 130 0 Open + P_V_02486 J_11_08 J_12_08 120 200 130 0 Open + P_V_02487 J_11_09 J_12_09 120 200 130 0 Open + P_V_02488 J_11_10 J_12_10 120 200 130 0 Open + P_V_02489 J_11_11 J_12_11 120 200 130 0 Open + P_V_02490 J_11_12 J_12_12 120 200 130 0 Open + P_V_02491 J_11_13 J_12_13 120 200 130 0 Open + P_V_02492 J_11_14 J_12_14 120 200 130 0 Open + P_V_02493 J_11_15 J_12_15 120 200 130 0 Open + P_V_02494 J_11_16 J_12_16 120 200 130 0 Open + P_V_02495 J_11_17 J_12_17 120 200 130 0 Open + P_V_02496 J_11_18 J_12_18 120 200 130 0 Open + P_V_02497 J_11_19 J_12_19 120 200 130 0 Open + P_V_02498 J_11_20 J_12_20 120 200 130 0 Open + P_V_02499 J_11_21 J_12_21 120 200 130 0 Open + P_V_02500 J_11_22 J_12_22 120 200 130 0 Open + P_V_02501 J_11_23 J_12_23 120 200 130 0 Open + P_V_02502 J_11_24 J_12_24 120 200 130 0 Open + P_V_02503 J_11_25 J_12_25 120 200 130 0 Open + P_V_02504 J_11_26 J_12_26 120 200 130 0 Open + P_V_02505 J_11_27 J_12_27 120 200 130 0 Open + P_V_02506 J_11_28 J_12_28 120 200 130 0 Open + P_V_02507 J_11_29 J_12_29 120 200 130 0 Open + P_V_02508 J_11_30 J_12_30 120 200 130 0 Open + P_V_02509 J_11_31 J_12_31 120 200 130 0 Open + P_V_02510 J_11_32 J_12_32 120 200 130 0 Open + P_V_02511 J_11_33 J_12_33 120 200 130 0 Open + P_V_02512 J_11_34 J_12_34 120 200 130 0 Open + P_V_02513 J_11_35 J_12_35 120 200 130 0 Open + P_V_02514 J_11_36 J_12_36 120 200 130 0 Open + P_V_02515 J_11_37 J_12_37 120 200 130 0 Open + P_V_02516 J_11_38 J_12_38 120 200 130 0 Open + P_V_02517 J_11_39 J_12_39 120 200 130 0 Open + P_V_02518 J_11_40 J_12_40 120 200 130 0 Open + P_V_02519 J_11_41 J_12_41 120 200 130 0 Open + P_V_02520 J_11_42 J_12_42 120 200 130 0 Open + P_V_02521 J_11_43 J_12_43 120 200 130 0 Open + P_V_02522 J_11_44 J_12_44 120 200 130 0 Open + P_V_02523 J_12_00 J_13_00 120 200 130 0 Open + P_V_02524 J_12_01 J_13_01 120 200 130 0 Open + P_V_02525 J_12_02 J_13_02 120 200 130 0 Open + P_V_02526 J_12_03 J_13_03 120 200 130 0 Open + P_V_02527 J_12_04 J_13_04 120 200 130 0 Open + P_V_02528 J_12_05 J_13_05 120 200 130 0 Open + P_V_02529 J_12_06 J_13_06 120 200 130 0 Open + P_V_02530 J_12_07 J_13_07 120 200 130 0 Open + P_V_02531 J_12_08 J_13_08 120 200 130 0 Open + P_V_02532 J_12_09 J_13_09 120 200 130 0 Open + P_V_02533 J_12_10 J_13_10 120 200 130 0 Open + P_V_02534 J_12_11 J_13_11 120 200 130 0 Open + P_V_02535 J_12_12 J_13_12 120 200 130 0 Open + P_V_02536 J_12_13 J_13_13 120 200 130 0 Open + P_V_02537 J_12_14 J_13_14 120 200 130 0 Open + P_V_02538 J_12_15 J_13_15 120 200 130 0 Open + P_V_02539 J_12_16 J_13_16 120 200 130 0 Open + P_V_02540 J_12_17 J_13_17 120 200 130 0 Open + P_V_02541 J_12_18 J_13_18 120 200 130 0 Open + P_V_02542 J_12_19 J_13_19 120 200 130 0 Open + P_V_02543 J_12_20 J_13_20 120 200 130 0 Open + P_V_02544 J_12_21 J_13_21 120 200 130 0 Open + P_V_02545 J_12_22 J_13_22 120 200 130 0 Open + P_V_02546 J_12_23 J_13_23 120 200 130 0 Open + P_V_02547 J_12_24 J_13_24 120 200 130 0 Open + P_V_02548 J_12_25 J_13_25 120 200 130 0 Open + P_V_02549 J_12_26 J_13_26 120 200 130 0 Open + P_V_02550 J_12_27 J_13_27 120 200 130 0 Open + P_V_02551 J_12_28 J_13_28 120 200 130 0 Open + P_V_02552 J_12_29 J_13_29 120 200 130 0 Open + P_V_02553 J_12_30 J_13_30 120 200 130 0 Open + P_V_02554 J_12_31 J_13_31 120 200 130 0 Open + P_V_02555 J_12_32 J_13_32 120 200 130 0 Open + P_V_02556 J_12_33 J_13_33 120 200 130 0 Open + P_V_02557 J_12_34 J_13_34 120 200 130 0 Open + P_V_02558 J_12_35 J_13_35 120 200 130 0 Open + P_V_02559 J_12_36 J_13_36 120 200 130 0 Open + P_V_02560 J_12_37 J_13_37 120 200 130 0 Open + P_V_02561 J_12_38 J_13_38 120 200 130 0 Open + P_V_02562 J_12_39 J_13_39 120 200 130 0 Open + P_V_02563 J_12_40 J_13_40 120 200 130 0 Open + P_V_02564 J_12_41 J_13_41 120 200 130 0 Open + P_V_02565 J_12_42 J_13_42 120 200 130 0 Open + P_V_02566 J_12_43 J_13_43 120 200 130 0 Open + P_V_02567 J_12_44 J_13_44 120 200 130 0 Open + P_V_02568 J_13_00 J_14_00 120 200 130 0 Open + P_V_02569 J_13_01 J_14_01 120 200 130 0 Open + P_V_02570 J_13_02 J_14_02 120 200 130 0 Open + P_V_02571 J_13_03 J_14_03 120 200 130 0 Open + P_V_02572 J_13_04 J_14_04 120 200 130 0 Open + P_V_02573 J_13_05 J_14_05 120 200 130 0 Open + P_V_02574 J_13_06 J_14_06 120 200 130 0 Open + P_V_02575 J_13_07 J_14_07 120 200 130 0 Open + P_V_02576 J_13_08 J_14_08 120 200 130 0 Open + P_V_02577 J_13_09 J_14_09 120 200 130 0 Open + P_V_02578 J_13_10 J_14_10 120 200 130 0 Open + P_V_02579 J_13_11 J_14_11 120 200 130 0 Open + P_V_02580 J_13_12 J_14_12 120 200 130 0 Open + P_V_02581 J_13_13 J_14_13 120 200 130 0 Open + P_V_02582 J_13_14 J_14_14 120 200 130 0 Open + P_V_02583 J_13_15 J_14_15 120 200 130 0 Open + P_V_02584 J_13_16 J_14_16 120 200 130 0 Open + P_V_02585 J_13_17 J_14_17 120 200 130 0 Open + P_V_02586 J_13_18 J_14_18 120 200 130 0 Open + P_V_02587 J_13_19 J_14_19 120 200 130 0 Open + P_V_02588 J_13_20 J_14_20 120 200 130 0 Open + P_V_02589 J_13_21 J_14_21 120 200 130 0 Open + P_V_02590 J_13_22 J_14_22 120 200 130 0 Open + P_V_02591 J_13_23 J_14_23 120 200 130 0 Open + P_V_02592 J_13_24 J_14_24 120 200 130 0 Open + P_V_02593 J_13_25 J_14_25 120 200 130 0 Open + P_V_02594 J_13_26 J_14_26 120 200 130 0 Open + P_V_02595 J_13_27 J_14_27 120 200 130 0 Open + P_V_02596 J_13_28 J_14_28 120 200 130 0 Open + P_V_02597 J_13_29 J_14_29 120 200 130 0 Open + P_V_02598 J_13_30 J_14_30 120 200 130 0 Open + P_V_02599 J_13_31 J_14_31 120 200 130 0 Open + P_V_02600 J_13_32 J_14_32 120 200 130 0 Open + P_V_02601 J_13_33 J_14_33 120 200 130 0 Open + P_V_02602 J_13_34 J_14_34 120 200 130 0 Open + P_V_02603 J_13_35 J_14_35 120 200 130 0 Open + P_V_02604 J_13_36 J_14_36 120 200 130 0 Open + P_V_02605 J_13_37 J_14_37 120 200 130 0 Open + P_V_02606 J_13_38 J_14_38 120 200 130 0 Open + P_V_02607 J_13_39 J_14_39 120 200 130 0 Open + P_V_02608 J_13_40 J_14_40 120 200 130 0 Open + P_V_02609 J_13_41 J_14_41 120 200 130 0 Open + P_V_02610 J_13_42 J_14_42 120 200 130 0 Open + P_V_02611 J_13_43 J_14_43 120 200 130 0 Open + P_V_02612 J_13_44 J_14_44 120 200 130 0 Open + P_V_02613 J_14_00 J_15_00 120 200 130 0 Open + P_V_02614 J_14_01 J_15_01 120 200 130 0 Open + P_V_02615 J_14_02 J_15_02 120 200 130 0 Open + P_V_02616 J_14_03 J_15_03 120 200 130 0 Open + P_V_02617 J_14_04 J_15_04 120 200 130 0 Open + P_V_02618 J_14_05 J_15_05 120 200 130 0 Open + P_V_02619 J_14_06 J_15_06 120 200 130 0 Open + P_V_02620 J_14_07 J_15_07 120 200 130 0 Open + P_V_02621 J_14_08 J_15_08 120 200 130 0 Open + P_V_02622 J_14_09 J_15_09 120 200 130 0 Open + P_V_02623 J_14_10 J_15_10 120 200 130 0 Open + P_V_02624 J_14_11 J_15_11 120 200 130 0 Open + P_V_02625 J_14_12 J_15_12 120 200 130 0 Open + P_V_02626 J_14_13 J_15_13 120 200 130 0 Open + P_V_02627 J_14_14 J_15_14 120 200 130 0 Open + P_V_02628 J_14_15 J_15_15 120 200 130 0 Open + P_V_02629 J_14_16 J_15_16 120 200 130 0 Open + P_V_02630 J_14_17 J_15_17 120 200 130 0 Open + P_V_02631 J_14_18 J_15_18 120 200 130 0 Open + P_V_02632 J_14_19 J_15_19 120 200 130 0 Open + P_V_02633 J_14_20 J_15_20 120 200 130 0 Open + P_V_02634 J_14_21 J_15_21 120 200 130 0 Open + P_V_02635 J_14_22 J_15_22 120 200 130 0 Open + P_V_02636 J_14_23 J_15_23 120 200 130 0 Open + P_V_02637 J_14_24 J_15_24 120 200 130 0 Open + P_V_02638 J_14_25 J_15_25 120 200 130 0 Open + P_V_02639 J_14_26 J_15_26 120 200 130 0 Open + P_V_02640 J_14_27 J_15_27 120 200 130 0 Open + P_V_02641 J_14_28 J_15_28 120 200 130 0 Open + P_V_02642 J_14_29 J_15_29 120 200 130 0 Open + P_V_02643 J_14_30 J_15_30 120 200 130 0 Open + P_V_02644 J_14_31 J_15_31 120 200 130 0 Open + P_V_02645 J_14_32 J_15_32 120 200 130 0 Open + P_V_02646 J_14_33 J_15_33 120 200 130 0 Open + P_V_02647 J_14_34 J_15_34 120 200 130 0 Open + P_V_02648 J_14_35 J_15_35 120 200 130 0 Open + P_V_02649 J_14_36 J_15_36 120 200 130 0 Open + P_V_02650 J_14_37 J_15_37 120 200 130 0 Open + P_V_02651 J_14_38 J_15_38 120 200 130 0 Open + P_V_02652 J_14_39 J_15_39 120 200 130 0 Open + P_V_02653 J_14_40 J_15_40 120 200 130 0 Open + P_V_02654 J_14_41 J_15_41 120 200 130 0 Open + P_V_02655 J_14_42 J_15_42 120 200 130 0 Open + P_V_02656 J_14_43 J_15_43 120 200 130 0 Open + P_V_02657 J_14_44 J_15_44 120 200 130 0 Open + P_V_02658 J_15_00 J_16_00 120 200 130 0 Open + P_V_02659 J_15_01 J_16_01 120 200 130 0 Open + P_V_02660 J_15_02 J_16_02 120 200 130 0 Open + P_V_02661 J_15_03 J_16_03 120 200 130 0 Open + P_V_02662 J_15_04 J_16_04 120 200 130 0 Open + P_V_02663 J_15_05 J_16_05 120 200 130 0 Open + P_V_02664 J_15_06 J_16_06 120 200 130 0 Open + P_V_02665 J_15_07 J_16_07 120 200 130 0 Open + P_V_02666 J_15_08 J_16_08 120 200 130 0 Open + P_V_02667 J_15_09 J_16_09 120 200 130 0 Open + P_V_02668 J_15_10 J_16_10 120 200 130 0 Open + P_V_02669 J_15_11 J_16_11 120 200 130 0 Open + P_V_02670 J_15_12 J_16_12 120 200 130 0 Open + P_V_02671 J_15_13 J_16_13 120 200 130 0 Open + P_V_02672 J_15_14 J_16_14 120 200 130 0 Open + P_V_02673 J_15_15 J_16_15 120 200 130 0 Open + P_V_02674 J_15_16 J_16_16 120 200 130 0 Open + P_V_02675 J_15_17 J_16_17 120 200 130 0 Open + P_V_02676 J_15_18 J_16_18 120 200 130 0 Open + P_V_02677 J_15_19 J_16_19 120 200 130 0 Open + P_V_02678 J_15_20 J_16_20 120 200 130 0 Open + P_V_02679 J_15_21 J_16_21 120 200 130 0 Open + P_V_02680 J_15_22 J_16_22 120 200 130 0 Open + P_V_02681 J_15_23 J_16_23 120 200 130 0 Open + P_V_02682 J_15_24 J_16_24 120 200 130 0 Open + P_V_02683 J_15_25 J_16_25 120 200 130 0 Open + P_V_02684 J_15_26 J_16_26 120 200 130 0 Open + P_V_02685 J_15_27 J_16_27 120 200 130 0 Open + P_V_02686 J_15_28 J_16_28 120 200 130 0 Open + P_V_02687 J_15_29 J_16_29 120 200 130 0 Open + P_V_02688 J_15_30 J_16_30 120 200 130 0 Open + P_V_02689 J_15_31 J_16_31 120 200 130 0 Open + P_V_02690 J_15_32 J_16_32 120 200 130 0 Open + P_V_02691 J_15_33 J_16_33 120 200 130 0 Open + P_V_02692 J_15_34 J_16_34 120 200 130 0 Open + P_V_02693 J_15_35 J_16_35 120 200 130 0 Open + P_V_02694 J_15_36 J_16_36 120 200 130 0 Open + P_V_02695 J_15_37 J_16_37 120 200 130 0 Open + P_V_02696 J_15_38 J_16_38 120 200 130 0 Open + P_V_02697 J_15_39 J_16_39 120 200 130 0 Open + P_V_02698 J_15_40 J_16_40 120 200 130 0 Open + P_V_02699 J_15_41 J_16_41 120 200 130 0 Open + P_V_02700 J_15_42 J_16_42 120 200 130 0 Open + P_V_02701 J_15_43 J_16_43 120 200 130 0 Open + P_V_02702 J_15_44 J_16_44 120 200 130 0 Open + P_V_02703 J_16_00 J_17_00 120 200 130 0 Open + P_V_02704 J_16_01 J_17_01 120 200 130 0 Open + P_V_02705 J_16_02 J_17_02 120 200 130 0 Open + P_V_02706 J_16_03 J_17_03 120 200 130 0 Open + P_V_02707 J_16_04 J_17_04 120 200 130 0 Open + P_V_02708 J_16_05 J_17_05 120 200 130 0 Open + P_V_02709 J_16_06 J_17_06 120 200 130 0 Open + P_V_02710 J_16_07 J_17_07 120 200 130 0 Open + P_V_02711 J_16_08 J_17_08 120 200 130 0 Open + P_V_02712 J_16_09 J_17_09 120 200 130 0 Open + P_V_02713 J_16_10 J_17_10 120 200 130 0 Open + P_V_02714 J_16_11 J_17_11 120 200 130 0 Open + P_V_02715 J_16_12 J_17_12 120 200 130 0 Open + P_V_02716 J_16_13 J_17_13 120 200 130 0 Open + P_V_02717 J_16_14 J_17_14 120 200 130 0 Open + P_V_02718 J_16_15 J_17_15 120 200 130 0 Open + P_V_02719 J_16_16 J_17_16 120 200 130 0 Open + P_V_02720 J_16_17 J_17_17 120 200 130 0 Open + P_V_02721 J_16_18 J_17_18 120 200 130 0 Open + P_V_02722 J_16_19 J_17_19 120 200 130 0 Open + P_V_02723 J_16_20 J_17_20 120 200 130 0 Open + P_V_02724 J_16_21 J_17_21 120 200 130 0 Open + P_V_02725 J_16_22 J_17_22 120 200 130 0 Open + P_V_02726 J_16_23 J_17_23 120 200 130 0 Open + P_V_02727 J_16_24 J_17_24 120 200 130 0 Open + P_V_02728 J_16_25 J_17_25 120 200 130 0 Open + P_V_02729 J_16_26 J_17_26 120 200 130 0 Open + P_V_02730 J_16_27 J_17_27 120 200 130 0 Open + P_V_02731 J_16_28 J_17_28 120 200 130 0 Open + P_V_02732 J_16_29 J_17_29 120 200 130 0 Open + P_V_02733 J_16_30 J_17_30 120 200 130 0 Open + P_V_02734 J_16_31 J_17_31 120 200 130 0 Open + P_V_02735 J_16_32 J_17_32 120 200 130 0 Open + P_V_02736 J_16_33 J_17_33 120 200 130 0 Open + P_V_02737 J_16_34 J_17_34 120 200 130 0 Open + P_V_02738 J_16_35 J_17_35 120 200 130 0 Open + P_V_02739 J_16_36 J_17_36 120 200 130 0 Open + P_V_02740 J_16_37 J_17_37 120 200 130 0 Open + P_V_02741 J_16_38 J_17_38 120 200 130 0 Open + P_V_02742 J_16_39 J_17_39 120 200 130 0 Open + P_V_02743 J_16_40 J_17_40 120 200 130 0 Open + P_V_02744 J_16_41 J_17_41 120 200 130 0 Open + P_V_02745 J_16_42 J_17_42 120 200 130 0 Open + P_V_02746 J_16_43 J_17_43 120 200 130 0 Open + P_V_02747 J_16_44 J_17_44 120 200 130 0 Open + P_V_02748 J_17_00 J_18_00 120 200 130 0 Open + P_V_02749 J_17_01 J_18_01 120 200 130 0 Open + P_V_02750 J_17_02 J_18_02 120 200 130 0 Open + P_V_02751 J_17_03 J_18_03 120 200 130 0 Open + P_V_02752 J_17_04 J_18_04 120 200 130 0 Open + P_V_02753 J_17_05 J_18_05 120 200 130 0 Open + P_V_02754 J_17_06 J_18_06 120 200 130 0 Open + P_V_02755 J_17_07 J_18_07 120 200 130 0 Open + P_V_02756 J_17_08 J_18_08 120 200 130 0 Open + P_V_02757 J_17_09 J_18_09 120 200 130 0 Open + P_V_02758 J_17_10 J_18_10 120 200 130 0 Open + P_V_02759 J_17_11 J_18_11 120 200 130 0 Open + P_V_02760 J_17_12 J_18_12 120 200 130 0 Open + P_V_02761 J_17_13 J_18_13 120 200 130 0 Open + P_V_02762 J_17_14 J_18_14 120 200 130 0 Open + P_V_02763 J_17_15 J_18_15 120 200 130 0 Open + P_V_02764 J_17_16 J_18_16 120 200 130 0 Open + P_V_02765 J_17_17 J_18_17 120 200 130 0 Open + P_V_02766 J_17_18 J_18_18 120 200 130 0 Open + P_V_02767 J_17_19 J_18_19 120 200 130 0 Open + P_V_02768 J_17_20 J_18_20 120 200 130 0 Open + P_V_02769 J_17_21 J_18_21 120 200 130 0 Open + P_V_02770 J_17_22 J_18_22 120 200 130 0 Open + P_V_02771 J_17_23 J_18_23 120 200 130 0 Open + P_V_02772 J_17_24 J_18_24 120 200 130 0 Open + P_V_02773 J_17_25 J_18_25 120 200 130 0 Open + P_V_02774 J_17_26 J_18_26 120 200 130 0 Open + P_V_02775 J_17_27 J_18_27 120 200 130 0 Open + P_V_02776 J_17_28 J_18_28 120 200 130 0 Open + P_V_02777 J_17_29 J_18_29 120 200 130 0 Open + P_V_02778 J_17_30 J_18_30 120 200 130 0 Open + P_V_02779 J_17_31 J_18_31 120 200 130 0 Open + P_V_02780 J_17_32 J_18_32 120 200 130 0 Open + P_V_02781 J_17_33 J_18_33 120 200 130 0 Open + P_V_02782 J_17_34 J_18_34 120 200 130 0 Open + P_V_02783 J_17_35 J_18_35 120 200 130 0 Open + P_V_02784 J_17_36 J_18_36 120 200 130 0 Open + P_V_02785 J_17_37 J_18_37 120 200 130 0 Open + P_V_02786 J_17_38 J_18_38 120 200 130 0 Open + P_V_02787 J_17_39 J_18_39 120 200 130 0 Open + P_V_02788 J_17_40 J_18_40 120 200 130 0 Open + P_V_02789 J_17_41 J_18_41 120 200 130 0 Open + P_V_02790 J_17_42 J_18_42 120 200 130 0 Open + P_V_02791 J_17_43 J_18_43 120 200 130 0 Open + P_V_02792 J_17_44 J_18_44 120 200 130 0 Open + P_V_02793 J_18_00 J_19_00 120 200 130 0 Open + P_V_02794 J_18_01 J_19_01 120 200 130 0 Open + P_V_02795 J_18_02 J_19_02 120 200 130 0 Open + P_V_02796 J_18_03 J_19_03 120 200 130 0 Open + P_V_02797 J_18_04 J_19_04 120 200 130 0 Open + P_V_02798 J_18_05 J_19_05 120 200 130 0 Open + P_V_02799 J_18_06 J_19_06 120 200 130 0 Open + P_V_02800 J_18_07 J_19_07 120 200 130 0 Open + P_V_02801 J_18_08 J_19_08 120 200 130 0 Open + P_V_02802 J_18_09 J_19_09 120 200 130 0 Open + P_V_02803 J_18_10 J_19_10 120 200 130 0 Open + P_V_02804 J_18_11 J_19_11 120 200 130 0 Open + P_V_02805 J_18_12 J_19_12 120 200 130 0 Open + P_V_02806 J_18_13 J_19_13 120 200 130 0 Open + P_V_02807 J_18_14 J_19_14 120 200 130 0 Open + P_V_02808 J_18_15 J_19_15 120 200 130 0 Open + P_V_02809 J_18_16 J_19_16 120 200 130 0 Open + P_V_02810 J_18_17 J_19_17 120 200 130 0 Open + P_V_02811 J_18_18 J_19_18 120 200 130 0 Open + P_V_02812 J_18_19 J_19_19 120 200 130 0 Open + P_V_02813 J_18_20 J_19_20 120 200 130 0 Open + P_V_02814 J_18_21 J_19_21 120 200 130 0 Open + P_V_02815 J_18_22 J_19_22 120 200 130 0 Open + P_V_02816 J_18_23 J_19_23 120 200 130 0 Open + P_V_02817 J_18_24 J_19_24 120 200 130 0 Open + P_V_02818 J_18_25 J_19_25 120 200 130 0 Open + P_V_02819 J_18_26 J_19_26 120 200 130 0 Open + P_V_02820 J_18_27 J_19_27 120 200 130 0 Open + P_V_02821 J_18_28 J_19_28 120 200 130 0 Open + P_V_02822 J_18_29 J_19_29 120 200 130 0 Open + P_V_02823 J_18_30 J_19_30 120 200 130 0 Open + P_V_02824 J_18_31 J_19_31 120 200 130 0 Open + P_V_02825 J_18_32 J_19_32 120 200 130 0 Open + P_V_02826 J_18_33 J_19_33 120 200 130 0 Open + P_V_02827 J_18_34 J_19_34 120 200 130 0 Open + P_V_02828 J_18_35 J_19_35 120 200 130 0 Open + P_V_02829 J_18_36 J_19_36 120 200 130 0 Open + P_V_02830 J_18_37 J_19_37 120 200 130 0 Open + P_V_02831 J_18_38 J_19_38 120 200 130 0 Open + P_V_02832 J_18_39 J_19_39 120 200 130 0 Open + P_V_02833 J_18_40 J_19_40 120 200 130 0 Open + P_V_02834 J_18_41 J_19_41 120 200 130 0 Open + P_V_02835 J_18_42 J_19_42 120 200 130 0 Open + P_V_02836 J_18_43 J_19_43 120 200 130 0 Open + P_V_02837 J_18_44 J_19_44 120 200 130 0 Open + P_V_02838 J_19_00 J_20_00 120 200 130 0 Open + P_V_02839 J_19_01 J_20_01 120 200 130 0 Open + P_V_02840 J_19_02 J_20_02 120 200 130 0 Open + P_V_02841 J_19_03 J_20_03 120 200 130 0 Open + P_V_02842 J_19_04 J_20_04 120 200 130 0 Open + P_V_02843 J_19_05 J_20_05 120 200 130 0 Open + P_V_02844 J_19_06 J_20_06 120 200 130 0 Open + P_V_02845 J_19_07 J_20_07 120 200 130 0 Open + P_V_02846 J_19_08 J_20_08 120 200 130 0 Open + P_V_02847 J_19_09 J_20_09 120 200 130 0 Open + P_V_02848 J_19_10 J_20_10 120 200 130 0 Open + P_V_02849 J_19_11 J_20_11 120 200 130 0 Open + P_V_02850 J_19_12 J_20_12 120 200 130 0 Open + P_V_02851 J_19_13 J_20_13 120 200 130 0 Open + P_V_02852 J_19_14 J_20_14 120 200 130 0 Open + P_V_02853 J_19_15 J_20_15 120 200 130 0 Open + P_V_02854 J_19_16 J_20_16 120 200 130 0 Open + P_V_02855 J_19_17 J_20_17 120 200 130 0 Open + P_V_02856 J_19_18 J_20_18 120 200 130 0 Open + P_V_02857 J_19_19 J_20_19 120 200 130 0 Open + P_V_02858 J_19_20 J_20_20 120 200 130 0 Open + P_V_02859 J_19_21 J_20_21 120 200 130 0 Open + P_V_02860 J_19_22 J_20_22 120 200 130 0 Open + P_V_02861 J_19_23 J_20_23 120 200 130 0 Open + P_V_02862 J_19_24 J_20_24 120 200 130 0 Open + P_V_02863 J_19_25 J_20_25 120 200 130 0 Open + P_V_02864 J_19_26 J_20_26 120 200 130 0 Open + P_V_02865 J_19_27 J_20_27 120 200 130 0 Open + P_V_02866 J_19_28 J_20_28 120 200 130 0 Open + P_V_02867 J_19_29 J_20_29 120 200 130 0 Open + P_V_02868 J_19_30 J_20_30 120 200 130 0 Open + P_V_02869 J_19_31 J_20_31 120 200 130 0 Open + P_V_02870 J_19_32 J_20_32 120 200 130 0 Open + P_V_02871 J_19_33 J_20_33 120 200 130 0 Open + P_V_02872 J_19_34 J_20_34 120 200 130 0 Open + P_V_02873 J_19_35 J_20_35 120 200 130 0 Open + P_V_02874 J_19_36 J_20_36 120 200 130 0 Open + P_V_02875 J_19_37 J_20_37 120 200 130 0 Open + P_V_02876 J_19_38 J_20_38 120 200 130 0 Open + P_V_02877 J_19_39 J_20_39 120 200 130 0 Open + P_V_02878 J_19_40 J_20_40 120 200 130 0 Open + P_V_02879 J_19_41 J_20_41 120 200 130 0 Open + P_V_02880 J_19_42 J_20_42 120 200 130 0 Open + P_V_02881 J_19_43 J_20_43 120 200 130 0 Open + P_V_02882 J_19_44 J_20_44 120 200 130 0 Open + P_V_02883 J_20_00 J_21_00 120 200 130 0 Open + P_V_02884 J_20_01 J_21_01 120 200 130 0 Open + P_V_02885 J_20_02 J_21_02 120 200 130 0 Open + P_V_02886 J_20_03 J_21_03 120 200 130 0 Open + P_V_02887 J_20_04 J_21_04 120 200 130 0 Open + P_V_02888 J_20_05 J_21_05 120 200 130 0 Open + P_V_02889 J_20_06 J_21_06 120 200 130 0 Open + P_V_02890 J_20_07 J_21_07 120 200 130 0 Open + P_V_02891 J_20_08 J_21_08 120 200 130 0 Open + P_V_02892 J_20_09 J_21_09 120 200 130 0 Open + P_V_02893 J_20_10 J_21_10 120 200 130 0 Open + P_V_02894 J_20_11 J_21_11 120 200 130 0 Open + P_V_02895 J_20_12 J_21_12 120 200 130 0 Open + P_V_02896 J_20_13 J_21_13 120 200 130 0 Open + P_V_02897 J_20_14 J_21_14 120 200 130 0 Open + P_V_02898 J_20_15 J_21_15 120 200 130 0 Open + P_V_02899 J_20_16 J_21_16 120 200 130 0 Open + P_V_02900 J_20_17 J_21_17 120 200 130 0 Open + P_V_02901 J_20_18 J_21_18 120 200 130 0 Open + P_V_02902 J_20_19 J_21_19 120 200 130 0 Open + P_V_02903 J_20_20 J_21_20 120 200 130 0 Open + P_V_02904 J_20_21 J_21_21 120 200 130 0 Open + P_V_02905 J_20_22 J_21_22 120 200 130 0 Open + P_V_02906 J_20_23 J_21_23 120 200 130 0 Open + P_V_02907 J_20_24 J_21_24 120 200 130 0 Open + P_V_02908 J_20_25 J_21_25 120 200 130 0 Open + P_V_02909 J_20_26 J_21_26 120 200 130 0 Open + P_V_02910 J_20_27 J_21_27 120 200 130 0 Open + P_V_02911 J_20_28 J_21_28 120 200 130 0 Open + P_V_02912 J_20_29 J_21_29 120 200 130 0 Open + P_V_02913 J_20_30 J_21_30 120 200 130 0 Open + P_V_02914 J_20_31 J_21_31 120 200 130 0 Open + P_V_02915 J_20_32 J_21_32 120 200 130 0 Open + P_V_02916 J_20_33 J_21_33 120 200 130 0 Open + P_V_02917 J_20_34 J_21_34 120 200 130 0 Open + P_V_02918 J_20_35 J_21_35 120 200 130 0 Open + P_V_02919 J_20_36 J_21_36 120 200 130 0 Open + P_V_02920 J_20_37 J_21_37 120 200 130 0 Open + P_V_02921 J_20_38 J_21_38 120 200 130 0 Open + P_V_02922 J_20_39 J_21_39 120 200 130 0 Open + P_V_02923 J_20_40 J_21_40 120 200 130 0 Open + P_V_02924 J_20_41 J_21_41 120 200 130 0 Open + P_V_02925 J_20_42 J_21_42 120 200 130 0 Open + P_V_02926 J_20_43 J_21_43 120 200 130 0 Open + P_V_02927 J_20_44 J_21_44 120 200 130 0 Open + P_V_02928 J_21_00 J_22_00 120 200 130 0 Open + P_V_02929 J_21_01 J_22_01 120 200 130 0 Open + P_V_02930 J_21_02 J_22_02 120 200 130 0 Open + P_V_02931 J_21_03 J_22_03 120 200 130 0 Open + P_V_02932 J_21_04 J_22_04 120 200 130 0 Open + P_V_02933 J_21_05 J_22_05 120 200 130 0 Open + P_V_02934 J_21_06 J_22_06 120 200 130 0 Open + P_V_02935 J_21_07 J_22_07 120 200 130 0 Open + P_V_02936 J_21_08 J_22_08 120 200 130 0 Open + P_V_02937 J_21_09 J_22_09 120 200 130 0 Open + P_V_02938 J_21_10 J_22_10 120 200 130 0 Open + P_V_02939 J_21_11 J_22_11 120 200 130 0 Open + P_V_02940 J_21_12 J_22_12 120 200 130 0 Open + P_V_02941 J_21_13 J_22_13 120 200 130 0 Open + P_V_02942 J_21_14 J_22_14 120 200 130 0 Open + P_V_02943 J_21_15 J_22_15 120 200 130 0 Open + P_V_02944 J_21_16 J_22_16 120 200 130 0 Open + P_V_02945 J_21_17 J_22_17 120 200 130 0 Open + P_V_02946 J_21_18 J_22_18 120 200 130 0 Open + P_V_02947 J_21_19 J_22_19 120 200 130 0 Open + P_V_02948 J_21_20 J_22_20 120 200 130 0 Open + P_V_02949 J_21_21 J_22_21 120 200 130 0 Open + P_V_02950 J_21_22 J_22_22 120 200 130 0 Open + P_V_02951 J_21_23 J_22_23 120 200 130 0 Open + P_V_02952 J_21_24 J_22_24 120 200 130 0 Open + P_V_02953 J_21_25 J_22_25 120 200 130 0 Open + P_V_02954 J_21_26 J_22_26 120 200 130 0 Open + P_V_02955 J_21_27 J_22_27 120 200 130 0 Open + P_V_02956 J_21_28 J_22_28 120 200 130 0 Open + P_V_02957 J_21_29 J_22_29 120 200 130 0 Open + P_V_02958 J_21_30 J_22_30 120 200 130 0 Open + P_V_02959 J_21_31 J_22_31 120 200 130 0 Open + P_V_02960 J_21_32 J_22_32 120 200 130 0 Open + P_V_02961 J_21_33 J_22_33 120 200 130 0 Open + P_V_02962 J_21_34 J_22_34 120 200 130 0 Open + P_V_02963 J_21_35 J_22_35 120 200 130 0 Open + P_V_02964 J_21_36 J_22_36 120 200 130 0 Open + P_V_02965 J_21_37 J_22_37 120 200 130 0 Open + P_V_02966 J_21_38 J_22_38 120 200 130 0 Open + P_V_02967 J_21_39 J_22_39 120 200 130 0 Open + P_V_02968 J_21_40 J_22_40 120 200 130 0 Open + P_V_02969 J_21_41 J_22_41 120 200 130 0 Open + P_V_02970 J_21_42 J_22_42 120 200 130 0 Open + P_V_02971 J_21_43 J_22_43 120 200 130 0 Open + P_V_02972 J_21_44 J_22_44 120 200 130 0 Open + P_V_02973 J_22_00 J_23_00 120 200 130 0 Open + P_V_02974 J_22_01 J_23_01 120 200 130 0 Open + P_V_02975 J_22_02 J_23_02 120 200 130 0 Open + P_V_02976 J_22_03 J_23_03 120 200 130 0 Open + P_V_02977 J_22_04 J_23_04 120 200 130 0 Open + P_V_02978 J_22_05 J_23_05 120 200 130 0 Open + P_V_02979 J_22_06 J_23_06 120 200 130 0 Open + P_V_02980 J_22_07 J_23_07 120 200 130 0 Open + P_V_02981 J_22_08 J_23_08 120 200 130 0 Open + P_V_02982 J_22_09 J_23_09 120 200 130 0 Open + P_V_02983 J_22_10 J_23_10 120 200 130 0 Open + P_V_02984 J_22_11 J_23_11 120 200 130 0 Open + P_V_02985 J_22_12 J_23_12 120 200 130 0 Open + P_V_02986 J_22_13 J_23_13 120 200 130 0 Open + P_V_02987 J_22_14 J_23_14 120 200 130 0 Open + P_V_02988 J_22_15 J_23_15 120 200 130 0 Open + P_V_02989 J_22_16 J_23_16 120 200 130 0 Open + P_V_02990 J_22_17 J_23_17 120 200 130 0 Open + P_V_02991 J_22_18 J_23_18 120 200 130 0 Open + P_V_02992 J_22_19 J_23_19 120 200 130 0 Open + P_V_02993 J_22_20 J_23_20 120 200 130 0 Open + P_V_02994 J_22_21 J_23_21 120 200 130 0 Open + P_V_02995 J_22_22 J_23_22 120 200 130 0 Open + P_V_02996 J_22_23 J_23_23 120 200 130 0 Open + P_V_02997 J_22_24 J_23_24 120 200 130 0 Open + P_V_02998 J_22_25 J_23_25 120 200 130 0 Open + P_V_02999 J_22_26 J_23_26 120 200 130 0 Open + P_V_03000 J_22_27 J_23_27 120 200 130 0 Open + P_V_03001 J_22_28 J_23_28 120 200 130 0 Open + P_V_03002 J_22_29 J_23_29 120 200 130 0 Open + P_V_03003 J_22_30 J_23_30 120 200 130 0 Open + P_V_03004 J_22_31 J_23_31 120 200 130 0 Open + P_V_03005 J_22_32 J_23_32 120 200 130 0 Open + P_V_03006 J_22_33 J_23_33 120 200 130 0 Open + P_V_03007 J_22_34 J_23_34 120 200 130 0 Open + P_V_03008 J_22_35 J_23_35 120 200 130 0 Open + P_V_03009 J_22_36 J_23_36 120 200 130 0 Open + P_V_03010 J_22_37 J_23_37 120 200 130 0 Open + P_V_03011 J_22_38 J_23_38 120 200 130 0 Open + P_V_03012 J_22_39 J_23_39 120 200 130 0 Open + P_V_03013 J_22_40 J_23_40 120 200 130 0 Open + P_V_03014 J_22_41 J_23_41 120 200 130 0 Open + P_V_03015 J_22_42 J_23_42 120 200 130 0 Open + P_V_03016 J_22_43 J_23_43 120 200 130 0 Open + P_V_03017 J_22_44 J_23_44 120 200 130 0 Open + P_V_03018 J_23_00 J_24_00 120 200 130 0 Open + P_V_03019 J_23_01 J_24_01 120 200 130 0 Open + P_V_03020 J_23_02 J_24_02 120 200 130 0 Open + P_V_03021 J_23_03 J_24_03 120 200 130 0 Open + P_V_03022 J_23_04 J_24_04 120 200 130 0 Open + P_V_03023 J_23_05 J_24_05 120 200 130 0 Open + P_V_03024 J_23_06 J_24_06 120 200 130 0 Open + P_V_03025 J_23_07 J_24_07 120 200 130 0 Open + P_V_03026 J_23_08 J_24_08 120 200 130 0 Open + P_V_03027 J_23_09 J_24_09 120 200 130 0 Open + P_V_03028 J_23_10 J_24_10 120 200 130 0 Open + P_V_03029 J_23_11 J_24_11 120 200 130 0 Open + P_V_03030 J_23_12 J_24_12 120 200 130 0 Open + P_V_03031 J_23_13 J_24_13 120 200 130 0 Open + P_V_03032 J_23_14 J_24_14 120 200 130 0 Open + P_V_03033 J_23_15 J_24_15 120 200 130 0 Open + P_V_03034 J_23_16 J_24_16 120 200 130 0 Open + P_V_03035 J_23_17 J_24_17 120 200 130 0 Open + P_V_03036 J_23_18 J_24_18 120 200 130 0 Open + P_V_03037 J_23_19 J_24_19 120 200 130 0 Open + P_V_03038 J_23_20 J_24_20 120 200 130 0 Open + P_V_03039 J_23_21 J_24_21 120 200 130 0 Open + P_V_03040 J_23_22 J_24_22 120 200 130 0 Open + P_V_03041 J_23_23 J_24_23 120 200 130 0 Open + P_V_03042 J_23_24 J_24_24 120 200 130 0 Open + P_V_03043 J_23_25 J_24_25 120 200 130 0 Open + P_V_03044 J_23_26 J_24_26 120 200 130 0 Open + P_V_03045 J_23_27 J_24_27 120 200 130 0 Open + P_V_03046 J_23_28 J_24_28 120 200 130 0 Open + P_V_03047 J_23_29 J_24_29 120 200 130 0 Open + P_V_03048 J_23_30 J_24_30 120 200 130 0 Open + P_V_03049 J_23_31 J_24_31 120 200 130 0 Open + P_V_03050 J_23_32 J_24_32 120 200 130 0 Open + P_V_03051 J_23_33 J_24_33 120 200 130 0 Open + P_V_03052 J_23_34 J_24_34 120 200 130 0 Open + P_V_03053 J_23_35 J_24_35 120 200 130 0 Open + P_V_03054 J_23_36 J_24_36 120 200 130 0 Open + P_V_03055 J_23_37 J_24_37 120 200 130 0 Open + P_V_03056 J_23_38 J_24_38 120 200 130 0 Open + P_V_03057 J_23_39 J_24_39 120 200 130 0 Open + P_V_03058 J_23_40 J_24_40 120 200 130 0 Open + P_V_03059 J_23_41 J_24_41 120 200 130 0 Open + P_V_03060 J_23_42 J_24_42 120 200 130 0 Open + P_V_03061 J_23_43 J_24_43 120 200 130 0 Open + P_V_03062 J_23_44 J_24_44 120 200 130 0 Open + P_V_03063 J_24_00 J_25_00 120 200 130 0 Open + P_V_03064 J_24_01 J_25_01 120 200 130 0 Open + P_V_03065 J_24_02 J_25_02 120 200 130 0 Open + P_V_03066 J_24_03 J_25_03 120 200 130 0 Open + P_V_03067 J_24_04 J_25_04 120 200 130 0 Open + P_V_03068 J_24_05 J_25_05 120 200 130 0 Open + P_V_03069 J_24_06 J_25_06 120 200 130 0 Open + P_V_03070 J_24_07 J_25_07 120 200 130 0 Open + P_V_03071 J_24_08 J_25_08 120 200 130 0 Open + P_V_03072 J_24_09 J_25_09 120 200 130 0 Open + P_V_03073 J_24_10 J_25_10 120 200 130 0 Open + P_V_03074 J_24_11 J_25_11 120 200 130 0 Open + P_V_03075 J_24_12 J_25_12 120 200 130 0 Open + P_V_03076 J_24_13 J_25_13 120 200 130 0 Open + P_V_03077 J_24_14 J_25_14 120 200 130 0 Open + P_V_03078 J_24_15 J_25_15 120 200 130 0 Open + P_V_03079 J_24_16 J_25_16 120 200 130 0 Open + P_V_03080 J_24_17 J_25_17 120 200 130 0 Open + P_V_03081 J_24_18 J_25_18 120 200 130 0 Open + P_V_03082 J_24_19 J_25_19 120 200 130 0 Open + P_V_03083 J_24_20 J_25_20 120 200 130 0 Open + P_V_03084 J_24_21 J_25_21 120 200 130 0 Open + P_V_03085 J_24_22 J_25_22 120 200 130 0 Open + P_V_03086 J_24_23 J_25_23 120 200 130 0 Open + P_V_03087 J_24_24 J_25_24 120 200 130 0 Open + P_V_03088 J_24_25 J_25_25 120 200 130 0 Open + P_V_03089 J_24_26 J_25_26 120 200 130 0 Open + P_V_03090 J_24_27 J_25_27 120 200 130 0 Open + P_V_03091 J_24_28 J_25_28 120 200 130 0 Open + P_V_03092 J_24_29 J_25_29 120 200 130 0 Open + P_V_03093 J_24_30 J_25_30 120 200 130 0 Open + P_V_03094 J_24_31 J_25_31 120 200 130 0 Open + P_V_03095 J_24_32 J_25_32 120 200 130 0 Open + P_V_03096 J_24_33 J_25_33 120 200 130 0 Open + P_V_03097 J_24_34 J_25_34 120 200 130 0 Open + P_V_03098 J_24_35 J_25_35 120 200 130 0 Open + P_V_03099 J_24_36 J_25_36 120 200 130 0 Open + P_V_03100 J_24_37 J_25_37 120 200 130 0 Open + P_V_03101 J_24_38 J_25_38 120 200 130 0 Open + P_V_03102 J_24_39 J_25_39 120 200 130 0 Open + P_V_03103 J_24_40 J_25_40 120 200 130 0 Open + P_V_03104 J_24_41 J_25_41 120 200 130 0 Open + P_V_03105 J_24_42 J_25_42 120 200 130 0 Open + P_V_03106 J_24_43 J_25_43 120 200 130 0 Open + P_V_03107 J_24_44 J_25_44 120 200 130 0 Open + P_V_03108 J_25_00 J_26_00 120 200 130 0 Open + P_V_03109 J_25_01 J_26_01 120 200 130 0 Open + P_V_03110 J_25_02 J_26_02 120 200 130 0 Open + P_V_03111 J_25_03 J_26_03 120 200 130 0 Open + P_V_03112 J_25_04 J_26_04 120 200 130 0 Open + P_V_03113 J_25_05 J_26_05 120 200 130 0 Open + P_V_03114 J_25_06 J_26_06 120 200 130 0 Open + P_V_03115 J_25_07 J_26_07 120 200 130 0 Open + P_V_03116 J_25_08 J_26_08 120 200 130 0 Open + P_V_03117 J_25_09 J_26_09 120 200 130 0 Open + P_V_03118 J_25_10 J_26_10 120 200 130 0 Open + P_V_03119 J_25_11 J_26_11 120 200 130 0 Open + P_V_03120 J_25_12 J_26_12 120 200 130 0 Open + P_V_03121 J_25_13 J_26_13 120 200 130 0 Open + P_V_03122 J_25_14 J_26_14 120 200 130 0 Open + P_V_03123 J_25_15 J_26_15 120 200 130 0 Open + P_V_03124 J_25_16 J_26_16 120 200 130 0 Open + P_V_03125 J_25_17 J_26_17 120 200 130 0 Open + P_V_03126 J_25_18 J_26_18 120 200 130 0 Open + P_V_03127 J_25_19 J_26_19 120 200 130 0 Open + P_V_03128 J_25_20 J_26_20 120 200 130 0 Open + P_V_03129 J_25_21 J_26_21 120 200 130 0 Open + P_V_03130 J_25_22 J_26_22 120 200 130 0 Open + P_V_03131 J_25_23 J_26_23 120 200 130 0 Open + P_V_03132 J_25_24 J_26_24 120 200 130 0 Open + P_V_03133 J_25_25 J_26_25 120 200 130 0 Open + P_V_03134 J_25_26 J_26_26 120 200 130 0 Open + P_V_03135 J_25_27 J_26_27 120 200 130 0 Open + P_V_03136 J_25_28 J_26_28 120 200 130 0 Open + P_V_03137 J_25_29 J_26_29 120 200 130 0 Open + P_V_03138 J_25_30 J_26_30 120 200 130 0 Open + P_V_03139 J_25_31 J_26_31 120 200 130 0 Open + P_V_03140 J_25_32 J_26_32 120 200 130 0 Open + P_V_03141 J_25_33 J_26_33 120 200 130 0 Open + P_V_03142 J_25_34 J_26_34 120 200 130 0 Open + P_V_03143 J_25_35 J_26_35 120 200 130 0 Open + P_V_03144 J_25_36 J_26_36 120 200 130 0 Open + P_V_03145 J_25_37 J_26_37 120 200 130 0 Open + P_V_03146 J_25_38 J_26_38 120 200 130 0 Open + P_V_03147 J_25_39 J_26_39 120 200 130 0 Open + P_V_03148 J_25_40 J_26_40 120 200 130 0 Open + P_V_03149 J_25_41 J_26_41 120 200 130 0 Open + P_V_03150 J_25_42 J_26_42 120 200 130 0 Open + P_V_03151 J_25_43 J_26_43 120 200 130 0 Open + P_V_03152 J_25_44 J_26_44 120 200 130 0 Open + P_V_03153 J_26_00 J_27_00 120 200 130 0 Open + P_V_03154 J_26_01 J_27_01 120 200 130 0 Open + P_V_03155 J_26_02 J_27_02 120 200 130 0 Open + P_V_03156 J_26_03 J_27_03 120 200 130 0 Open + P_V_03157 J_26_04 J_27_04 120 200 130 0 Open + P_V_03158 J_26_05 J_27_05 120 200 130 0 Open + P_V_03159 J_26_06 J_27_06 120 200 130 0 Open + P_V_03160 J_26_07 J_27_07 120 200 130 0 Open + P_V_03161 J_26_08 J_27_08 120 200 130 0 Open + P_V_03162 J_26_09 J_27_09 120 200 130 0 Open + P_V_03163 J_26_10 J_27_10 120 200 130 0 Open + P_V_03164 J_26_11 J_27_11 120 200 130 0 Open + P_V_03165 J_26_12 J_27_12 120 200 130 0 Open + P_V_03166 J_26_13 J_27_13 120 200 130 0 Open + P_V_03167 J_26_14 J_27_14 120 200 130 0 Open + P_V_03168 J_26_15 J_27_15 120 200 130 0 Open + P_V_03169 J_26_16 J_27_16 120 200 130 0 Open + P_V_03170 J_26_17 J_27_17 120 200 130 0 Open + P_V_03171 J_26_18 J_27_18 120 200 130 0 Open + P_V_03172 J_26_19 J_27_19 120 200 130 0 Open + P_V_03173 J_26_20 J_27_20 120 200 130 0 Open + P_V_03174 J_26_21 J_27_21 120 200 130 0 Open + P_V_03175 J_26_22 J_27_22 120 200 130 0 Open + P_V_03176 J_26_23 J_27_23 120 200 130 0 Open + P_V_03177 J_26_24 J_27_24 120 200 130 0 Open + P_V_03178 J_26_25 J_27_25 120 200 130 0 Open + P_V_03179 J_26_26 J_27_26 120 200 130 0 Open + P_V_03180 J_26_27 J_27_27 120 200 130 0 Open + P_V_03181 J_26_28 J_27_28 120 200 130 0 Open + P_V_03182 J_26_29 J_27_29 120 200 130 0 Open + P_V_03183 J_26_30 J_27_30 120 200 130 0 Open + P_V_03184 J_26_31 J_27_31 120 200 130 0 Open + P_V_03185 J_26_32 J_27_32 120 200 130 0 Open + P_V_03186 J_26_33 J_27_33 120 200 130 0 Open + P_V_03187 J_26_34 J_27_34 120 200 130 0 Open + P_V_03188 J_26_35 J_27_35 120 200 130 0 Open + P_V_03189 J_26_36 J_27_36 120 200 130 0 Open + P_V_03190 J_26_37 J_27_37 120 200 130 0 Open + P_V_03191 J_26_38 J_27_38 120 200 130 0 Open + P_V_03192 J_26_39 J_27_39 120 200 130 0 Open + P_V_03193 J_26_40 J_27_40 120 200 130 0 Open + P_V_03194 J_26_41 J_27_41 120 200 130 0 Open + P_V_03195 J_26_42 J_27_42 120 200 130 0 Open + P_V_03196 J_26_43 J_27_43 120 200 130 0 Open + P_V_03197 J_26_44 J_27_44 120 200 130 0 Open + P_V_03198 J_27_00 J_28_00 120 200 130 0 Open + P_V_03199 J_27_01 J_28_01 120 200 130 0 Open + P_V_03200 J_27_02 J_28_02 120 200 130 0 Open + P_V_03201 J_27_03 J_28_03 120 200 130 0 Open + P_V_03202 J_27_04 J_28_04 120 200 130 0 Open + P_V_03203 J_27_05 J_28_05 120 200 130 0 Open + P_V_03204 J_27_06 J_28_06 120 200 130 0 Open + P_V_03205 J_27_07 J_28_07 120 200 130 0 Open + P_V_03206 J_27_08 J_28_08 120 200 130 0 Open + P_V_03207 J_27_09 J_28_09 120 200 130 0 Open + P_V_03208 J_27_10 J_28_10 120 200 130 0 Open + P_V_03209 J_27_11 J_28_11 120 200 130 0 Open + P_V_03210 J_27_12 J_28_12 120 200 130 0 Open + P_V_03211 J_27_13 J_28_13 120 200 130 0 Open + P_V_03212 J_27_14 J_28_14 120 200 130 0 Open + P_V_03213 J_27_15 J_28_15 120 200 130 0 Open + P_V_03214 J_27_16 J_28_16 120 200 130 0 Open + P_V_03215 J_27_17 J_28_17 120 200 130 0 Open + P_V_03216 J_27_18 J_28_18 120 200 130 0 Open + P_V_03217 J_27_19 J_28_19 120 200 130 0 Open + P_V_03218 J_27_20 J_28_20 120 200 130 0 Open + P_V_03219 J_27_21 J_28_21 120 200 130 0 Open + P_V_03220 J_27_22 J_28_22 120 200 130 0 Open + P_V_03221 J_27_23 J_28_23 120 200 130 0 Open + P_V_03222 J_27_24 J_28_24 120 200 130 0 Open + P_V_03223 J_27_25 J_28_25 120 200 130 0 Open + P_V_03224 J_27_26 J_28_26 120 200 130 0 Open + P_V_03225 J_27_27 J_28_27 120 200 130 0 Open + P_V_03226 J_27_28 J_28_28 120 200 130 0 Open + P_V_03227 J_27_29 J_28_29 120 200 130 0 Open + P_V_03228 J_27_30 J_28_30 120 200 130 0 Open + P_V_03229 J_27_31 J_28_31 120 200 130 0 Open + P_V_03230 J_27_32 J_28_32 120 200 130 0 Open + P_V_03231 J_27_33 J_28_33 120 200 130 0 Open + P_V_03232 J_27_34 J_28_34 120 200 130 0 Open + P_V_03233 J_27_35 J_28_35 120 200 130 0 Open + P_V_03234 J_27_36 J_28_36 120 200 130 0 Open + P_V_03235 J_27_37 J_28_37 120 200 130 0 Open + P_V_03236 J_27_38 J_28_38 120 200 130 0 Open + P_V_03237 J_27_39 J_28_39 120 200 130 0 Open + P_V_03238 J_27_40 J_28_40 120 200 130 0 Open + P_V_03239 J_27_41 J_28_41 120 200 130 0 Open + P_V_03240 J_27_42 J_28_42 120 200 130 0 Open + P_V_03241 J_27_43 J_28_43 120 200 130 0 Open + P_V_03242 J_27_44 J_28_44 120 200 130 0 Open + P_V_03243 J_28_00 J_29_00 120 200 130 0 Open + P_V_03244 J_28_01 J_29_01 120 200 130 0 Open + P_V_03245 J_28_02 J_29_02 120 200 130 0 Open + P_V_03246 J_28_03 J_29_03 120 200 130 0 Open + P_V_03247 J_28_04 J_29_04 120 200 130 0 Open + P_V_03248 J_28_05 J_29_05 120 200 130 0 Open + P_V_03249 J_28_06 J_29_06 120 200 130 0 Open + P_V_03250 J_28_07 J_29_07 120 200 130 0 Open + P_V_03251 J_28_08 J_29_08 120 200 130 0 Open + P_V_03252 J_28_09 J_29_09 120 200 130 0 Open + P_V_03253 J_28_10 J_29_10 120 200 130 0 Open + P_V_03254 J_28_11 J_29_11 120 200 130 0 Open + P_V_03255 J_28_12 J_29_12 120 200 130 0 Open + P_V_03256 J_28_13 J_29_13 120 200 130 0 Open + P_V_03257 J_28_14 J_29_14 120 200 130 0 Open + P_V_03258 J_28_15 J_29_15 120 200 130 0 Open + P_V_03259 J_28_16 J_29_16 120 200 130 0 Open + P_V_03260 J_28_17 J_29_17 120 200 130 0 Open + P_V_03261 J_28_18 J_29_18 120 200 130 0 Open + P_V_03262 J_28_19 J_29_19 120 200 130 0 Open + P_V_03263 J_28_20 J_29_20 120 200 130 0 Open + P_V_03264 J_28_21 J_29_21 120 200 130 0 Open + P_V_03265 J_28_22 J_29_22 120 200 130 0 Open + P_V_03266 J_28_23 J_29_23 120 200 130 0 Open + P_V_03267 J_28_24 J_29_24 120 200 130 0 Open + P_V_03268 J_28_25 J_29_25 120 200 130 0 Open + P_V_03269 J_28_26 J_29_26 120 200 130 0 Open + P_V_03270 J_28_27 J_29_27 120 200 130 0 Open + P_V_03271 J_28_28 J_29_28 120 200 130 0 Open + P_V_03272 J_28_29 J_29_29 120 200 130 0 Open + P_V_03273 J_28_30 J_29_30 120 200 130 0 Open + P_V_03274 J_28_31 J_29_31 120 200 130 0 Open + P_V_03275 J_28_32 J_29_32 120 200 130 0 Open + P_V_03276 J_28_33 J_29_33 120 200 130 0 Open + P_V_03277 J_28_34 J_29_34 120 200 130 0 Open + P_V_03278 J_28_35 J_29_35 120 200 130 0 Open + P_V_03279 J_28_36 J_29_36 120 200 130 0 Open + P_V_03280 J_28_37 J_29_37 120 200 130 0 Open + P_V_03281 J_28_38 J_29_38 120 200 130 0 Open + P_V_03282 J_28_39 J_29_39 120 200 130 0 Open + P_V_03283 J_28_40 J_29_40 120 200 130 0 Open + P_V_03284 J_28_41 J_29_41 120 200 130 0 Open + P_V_03285 J_28_42 J_29_42 120 200 130 0 Open + P_V_03286 J_28_43 J_29_43 120 200 130 0 Open + P_V_03287 J_28_44 J_29_44 120 200 130 0 Open + P_V_03288 J_29_00 J_30_00 120 200 130 0 Open + P_V_03289 J_29_01 J_30_01 120 200 130 0 Open + P_V_03290 J_29_02 J_30_02 120 200 130 0 Open + P_V_03291 J_29_03 J_30_03 120 200 130 0 Open + P_V_03292 J_29_04 J_30_04 120 200 130 0 Open + P_V_03293 J_29_05 J_30_05 120 200 130 0 Open + P_V_03294 J_29_06 J_30_06 120 200 130 0 Open + P_V_03295 J_29_07 J_30_07 120 200 130 0 Open + P_V_03296 J_29_08 J_30_08 120 200 130 0 Open + P_V_03297 J_29_09 J_30_09 120 200 130 0 Open + P_V_03298 J_29_10 J_30_10 120 200 130 0 Open + P_V_03299 J_29_11 J_30_11 120 200 130 0 Open + P_V_03300 J_29_12 J_30_12 120 200 130 0 Open + P_V_03301 J_29_13 J_30_13 120 200 130 0 Open + P_V_03302 J_29_14 J_30_14 120 200 130 0 Open + P_V_03303 J_29_15 J_30_15 120 200 130 0 Open + P_V_03304 J_29_16 J_30_16 120 200 130 0 Open + P_V_03305 J_29_17 J_30_17 120 200 130 0 Open + P_V_03306 J_29_18 J_30_18 120 200 130 0 Open + P_V_03307 J_29_19 J_30_19 120 200 130 0 Open + P_V_03308 J_29_20 J_30_20 120 200 130 0 Open + P_V_03309 J_29_21 J_30_21 120 200 130 0 Open + P_V_03310 J_29_22 J_30_22 120 200 130 0 Open + P_V_03311 J_29_23 J_30_23 120 200 130 0 Open + P_V_03312 J_29_24 J_30_24 120 200 130 0 Open + P_V_03313 J_29_25 J_30_25 120 200 130 0 Open + P_V_03314 J_29_26 J_30_26 120 200 130 0 Open + P_V_03315 J_29_27 J_30_27 120 200 130 0 Open + P_V_03316 J_29_28 J_30_28 120 200 130 0 Open + P_V_03317 J_29_29 J_30_29 120 200 130 0 Open + P_V_03318 J_29_30 J_30_30 120 200 130 0 Open + P_V_03319 J_29_31 J_30_31 120 200 130 0 Open + P_V_03320 J_29_32 J_30_32 120 200 130 0 Open + P_V_03321 J_29_33 J_30_33 120 200 130 0 Open + P_V_03322 J_29_34 J_30_34 120 200 130 0 Open + P_V_03323 J_29_35 J_30_35 120 200 130 0 Open + P_V_03324 J_29_36 J_30_36 120 200 130 0 Open + P_V_03325 J_29_37 J_30_37 120 200 130 0 Open + P_V_03326 J_29_38 J_30_38 120 200 130 0 Open + P_V_03327 J_29_39 J_30_39 120 200 130 0 Open + P_V_03328 J_29_40 J_30_40 120 200 130 0 Open + P_V_03329 J_29_41 J_30_41 120 200 130 0 Open + P_V_03330 J_29_42 J_30_42 120 200 130 0 Open + P_V_03331 J_29_43 J_30_43 120 200 130 0 Open + P_V_03332 J_29_44 J_30_44 120 200 130 0 Open + P_V_03333 J_30_00 J_31_00 120 200 130 0 Open + P_V_03334 J_30_01 J_31_01 120 200 130 0 Open + P_V_03335 J_30_02 J_31_02 120 200 130 0 Open + P_V_03336 J_30_03 J_31_03 120 200 130 0 Open + P_V_03337 J_30_04 J_31_04 120 200 130 0 Open + P_V_03338 J_30_05 J_31_05 120 200 130 0 Open + P_V_03339 J_30_06 J_31_06 120 200 130 0 Open + P_V_03340 J_30_07 J_31_07 120 200 130 0 Open + P_V_03341 J_30_08 J_31_08 120 200 130 0 Open + P_V_03342 J_30_09 J_31_09 120 200 130 0 Open + P_V_03343 J_30_10 J_31_10 120 200 130 0 Open + P_V_03344 J_30_11 J_31_11 120 200 130 0 Open + P_V_03345 J_30_12 J_31_12 120 200 130 0 Open + P_V_03346 J_30_13 J_31_13 120 200 130 0 Open + P_V_03347 J_30_14 J_31_14 120 200 130 0 Open + P_V_03348 J_30_15 J_31_15 120 200 130 0 Open + P_V_03349 J_30_16 J_31_16 120 200 130 0 Open + P_V_03350 J_30_17 J_31_17 120 200 130 0 Open + P_V_03351 J_30_18 J_31_18 120 200 130 0 Open + P_V_03352 J_30_19 J_31_19 120 200 130 0 Open + P_V_03353 J_30_20 J_31_20 120 200 130 0 Open + P_V_03354 J_30_21 J_31_21 120 200 130 0 Open + P_V_03355 J_30_22 J_31_22 120 200 130 0 Open + P_V_03356 J_30_23 J_31_23 120 200 130 0 Open + P_V_03357 J_30_24 J_31_24 120 200 130 0 Open + P_V_03358 J_30_25 J_31_25 120 200 130 0 Open + P_V_03359 J_30_26 J_31_26 120 200 130 0 Open + P_V_03360 J_30_27 J_31_27 120 200 130 0 Open + P_V_03361 J_30_28 J_31_28 120 200 130 0 Open + P_V_03362 J_30_29 J_31_29 120 200 130 0 Open + P_V_03363 J_30_30 J_31_30 120 200 130 0 Open + P_V_03364 J_30_31 J_31_31 120 200 130 0 Open + P_V_03365 J_30_32 J_31_32 120 200 130 0 Open + P_V_03366 J_30_33 J_31_33 120 200 130 0 Open + P_V_03367 J_30_34 J_31_34 120 200 130 0 Open + P_V_03368 J_30_35 J_31_35 120 200 130 0 Open + P_V_03369 J_30_36 J_31_36 120 200 130 0 Open + P_V_03370 J_30_37 J_31_37 120 200 130 0 Open + P_V_03371 J_30_38 J_31_38 120 200 130 0 Open + P_V_03372 J_30_39 J_31_39 120 200 130 0 Open + P_V_03373 J_30_40 J_31_40 120 200 130 0 Open + P_V_03374 J_30_41 J_31_41 120 200 130 0 Open + P_V_03375 J_30_42 J_31_42 120 200 130 0 Open + P_V_03376 J_30_43 J_31_43 120 200 130 0 Open + P_V_03377 J_30_44 J_31_44 120 200 130 0 Open + P_V_03378 J_31_00 J_32_00 120 200 130 0 Open + P_V_03379 J_31_01 J_32_01 120 200 130 0 Open + P_V_03380 J_31_02 J_32_02 120 200 130 0 Open + P_V_03381 J_31_03 J_32_03 120 200 130 0 Open + P_V_03382 J_31_04 J_32_04 120 200 130 0 Open + P_V_03383 J_31_05 J_32_05 120 200 130 0 Open + P_V_03384 J_31_06 J_32_06 120 200 130 0 Open + P_V_03385 J_31_07 J_32_07 120 200 130 0 Open + P_V_03386 J_31_08 J_32_08 120 200 130 0 Open + P_V_03387 J_31_09 J_32_09 120 200 130 0 Open + P_V_03388 J_31_10 J_32_10 120 200 130 0 Open + P_V_03389 J_31_11 J_32_11 120 200 130 0 Open + P_V_03390 J_31_12 J_32_12 120 200 130 0 Open + P_V_03391 J_31_13 J_32_13 120 200 130 0 Open + P_V_03392 J_31_14 J_32_14 120 200 130 0 Open + P_V_03393 J_31_15 J_32_15 120 200 130 0 Open + P_V_03394 J_31_16 J_32_16 120 200 130 0 Open + P_V_03395 J_31_17 J_32_17 120 200 130 0 Open + P_V_03396 J_31_18 J_32_18 120 200 130 0 Open + P_V_03397 J_31_19 J_32_19 120 200 130 0 Open + P_V_03398 J_31_20 J_32_20 120 200 130 0 Open + P_V_03399 J_31_21 J_32_21 120 200 130 0 Open + P_V_03400 J_31_22 J_32_22 120 200 130 0 Open + P_V_03401 J_31_23 J_32_23 120 200 130 0 Open + P_V_03402 J_31_24 J_32_24 120 200 130 0 Open + P_V_03403 J_31_25 J_32_25 120 200 130 0 Open + P_V_03404 J_31_26 J_32_26 120 200 130 0 Open + P_V_03405 J_31_27 J_32_27 120 200 130 0 Open + P_V_03406 J_31_28 J_32_28 120 200 130 0 Open + P_V_03407 J_31_29 J_32_29 120 200 130 0 Open + P_V_03408 J_31_30 J_32_30 120 200 130 0 Open + P_V_03409 J_31_31 J_32_31 120 200 130 0 Open + P_V_03410 J_31_32 J_32_32 120 200 130 0 Open + P_V_03411 J_31_33 J_32_33 120 200 130 0 Open + P_V_03412 J_31_34 J_32_34 120 200 130 0 Open + P_V_03413 J_31_35 J_32_35 120 200 130 0 Open + P_V_03414 J_31_36 J_32_36 120 200 130 0 Open + P_V_03415 J_31_37 J_32_37 120 200 130 0 Open + P_V_03416 J_31_38 J_32_38 120 200 130 0 Open + P_V_03417 J_31_39 J_32_39 120 200 130 0 Open + P_V_03418 J_31_40 J_32_40 120 200 130 0 Open + P_V_03419 J_31_41 J_32_41 120 200 130 0 Open + P_V_03420 J_31_42 J_32_42 120 200 130 0 Open + P_V_03421 J_31_43 J_32_43 120 200 130 0 Open + P_V_03422 J_31_44 J_32_44 120 200 130 0 Open + P_V_03423 J_32_00 J_33_00 120 200 130 0 Open + P_V_03424 J_32_01 J_33_01 120 200 130 0 Open + P_V_03425 J_32_02 J_33_02 120 200 130 0 Open + P_V_03426 J_32_03 J_33_03 120 200 130 0 Open + P_V_03427 J_32_04 J_33_04 120 200 130 0 Open + P_V_03428 J_32_05 J_33_05 120 200 130 0 Open + P_V_03429 J_32_06 J_33_06 120 200 130 0 Open + P_V_03430 J_32_07 J_33_07 120 200 130 0 Open + P_V_03431 J_32_08 J_33_08 120 200 130 0 Open + P_V_03432 J_32_09 J_33_09 120 200 130 0 Open + P_V_03433 J_32_10 J_33_10 120 200 130 0 Open + P_V_03434 J_32_11 J_33_11 120 200 130 0 Open + P_V_03435 J_32_12 J_33_12 120 200 130 0 Open + P_V_03436 J_32_13 J_33_13 120 200 130 0 Open + P_V_03437 J_32_14 J_33_14 120 200 130 0 Open + P_V_03438 J_32_15 J_33_15 120 200 130 0 Open + P_V_03439 J_32_16 J_33_16 120 200 130 0 Open + P_V_03440 J_32_17 J_33_17 120 200 130 0 Open + P_V_03441 J_32_18 J_33_18 120 200 130 0 Open + P_V_03442 J_32_19 J_33_19 120 200 130 0 Open + P_V_03443 J_32_20 J_33_20 120 200 130 0 Open + P_V_03444 J_32_21 J_33_21 120 200 130 0 Open + P_V_03445 J_32_22 J_33_22 120 200 130 0 Open + P_V_03446 J_32_23 J_33_23 120 200 130 0 Open + P_V_03447 J_32_24 J_33_24 120 200 130 0 Open + P_V_03448 J_32_25 J_33_25 120 200 130 0 Open + P_V_03449 J_32_26 J_33_26 120 200 130 0 Open + P_V_03450 J_32_27 J_33_27 120 200 130 0 Open + P_V_03451 J_32_28 J_33_28 120 200 130 0 Open + P_V_03452 J_32_29 J_33_29 120 200 130 0 Open + P_V_03453 J_32_30 J_33_30 120 200 130 0 Open + P_V_03454 J_32_31 J_33_31 120 200 130 0 Open + P_V_03455 J_32_32 J_33_32 120 200 130 0 Open + P_V_03456 J_32_33 J_33_33 120 200 130 0 Open + P_V_03457 J_32_34 J_33_34 120 200 130 0 Open + P_V_03458 J_32_35 J_33_35 120 200 130 0 Open + P_V_03459 J_32_36 J_33_36 120 200 130 0 Open + P_V_03460 J_32_37 J_33_37 120 200 130 0 Open + P_V_03461 J_32_38 J_33_38 120 200 130 0 Open + P_V_03462 J_32_39 J_33_39 120 200 130 0 Open + P_V_03463 J_32_40 J_33_40 120 200 130 0 Open + P_V_03464 J_32_41 J_33_41 120 200 130 0 Open + P_V_03465 J_32_42 J_33_42 120 200 130 0 Open + P_V_03466 J_32_43 J_33_43 120 200 130 0 Open + P_V_03467 J_32_44 J_33_44 120 200 130 0 Open + P_V_03468 J_33_00 J_34_00 120 200 130 0 Open + P_V_03469 J_33_01 J_34_01 120 200 130 0 Open + P_V_03470 J_33_02 J_34_02 120 200 130 0 Open + P_V_03471 J_33_03 J_34_03 120 200 130 0 Open + P_V_03472 J_33_04 J_34_04 120 200 130 0 Open + P_V_03473 J_33_05 J_34_05 120 200 130 0 Open + P_V_03474 J_33_06 J_34_06 120 200 130 0 Open + P_V_03475 J_33_07 J_34_07 120 200 130 0 Open + P_V_03476 J_33_08 J_34_08 120 200 130 0 Open + P_V_03477 J_33_09 J_34_09 120 200 130 0 Open + P_V_03478 J_33_10 J_34_10 120 200 130 0 Open + P_V_03479 J_33_11 J_34_11 120 200 130 0 Open + P_V_03480 J_33_12 J_34_12 120 200 130 0 Open + P_V_03481 J_33_13 J_34_13 120 200 130 0 Open + P_V_03482 J_33_14 J_34_14 120 200 130 0 Open + P_V_03483 J_33_15 J_34_15 120 200 130 0 Open + P_V_03484 J_33_16 J_34_16 120 200 130 0 Open + P_V_03485 J_33_17 J_34_17 120 200 130 0 Open + P_V_03486 J_33_18 J_34_18 120 200 130 0 Open + P_V_03487 J_33_19 J_34_19 120 200 130 0 Open + P_V_03488 J_33_20 J_34_20 120 200 130 0 Open + P_V_03489 J_33_21 J_34_21 120 200 130 0 Open + P_V_03490 J_33_22 J_34_22 120 200 130 0 Open + P_V_03491 J_33_23 J_34_23 120 200 130 0 Open + P_V_03492 J_33_24 J_34_24 120 200 130 0 Open + P_V_03493 J_33_25 J_34_25 120 200 130 0 Open + P_V_03494 J_33_26 J_34_26 120 200 130 0 Open + P_V_03495 J_33_27 J_34_27 120 200 130 0 Open + P_V_03496 J_33_28 J_34_28 120 200 130 0 Open + P_V_03497 J_33_29 J_34_29 120 200 130 0 Open + P_V_03498 J_33_30 J_34_30 120 200 130 0 Open + P_V_03499 J_33_31 J_34_31 120 200 130 0 Open + P_V_03500 J_33_32 J_34_32 120 200 130 0 Open + P_V_03501 J_33_33 J_34_33 120 200 130 0 Open + P_V_03502 J_33_34 J_34_34 120 200 130 0 Open + P_V_03503 J_33_35 J_34_35 120 200 130 0 Open + P_V_03504 J_33_36 J_34_36 120 200 130 0 Open + P_V_03505 J_33_37 J_34_37 120 200 130 0 Open + P_V_03506 J_33_38 J_34_38 120 200 130 0 Open + P_V_03507 J_33_39 J_34_39 120 200 130 0 Open + P_V_03508 J_33_40 J_34_40 120 200 130 0 Open + P_V_03509 J_33_41 J_34_41 120 200 130 0 Open + P_V_03510 J_33_42 J_34_42 120 200 130 0 Open + P_V_03511 J_33_43 J_34_43 120 200 130 0 Open + P_V_03512 J_33_44 J_34_44 120 200 130 0 Open + P_V_03513 J_34_00 J_35_00 120 200 130 0 Open + P_V_03514 J_34_01 J_35_01 120 200 130 0 Open + P_V_03515 J_34_02 J_35_02 120 200 130 0 Open + P_V_03516 J_34_03 J_35_03 120 200 130 0 Open + P_V_03517 J_34_04 J_35_04 120 200 130 0 Open + P_V_03518 J_34_05 J_35_05 120 200 130 0 Open + P_V_03519 J_34_06 J_35_06 120 200 130 0 Open + P_V_03520 J_34_07 J_35_07 120 200 130 0 Open + P_V_03521 J_34_08 J_35_08 120 200 130 0 Open + P_V_03522 J_34_09 J_35_09 120 200 130 0 Open + P_V_03523 J_34_10 J_35_10 120 200 130 0 Open + P_V_03524 J_34_11 J_35_11 120 200 130 0 Open + P_V_03525 J_34_12 J_35_12 120 200 130 0 Open + P_V_03526 J_34_13 J_35_13 120 200 130 0 Open + P_V_03527 J_34_14 J_35_14 120 200 130 0 Open + P_V_03528 J_34_15 J_35_15 120 200 130 0 Open + P_V_03529 J_34_16 J_35_16 120 200 130 0 Open + P_V_03530 J_34_17 J_35_17 120 200 130 0 Open + P_V_03531 J_34_18 J_35_18 120 200 130 0 Open + P_V_03532 J_34_19 J_35_19 120 200 130 0 Open + P_V_03533 J_34_20 J_35_20 120 200 130 0 Open + P_V_03534 J_34_21 J_35_21 120 200 130 0 Open + P_V_03535 J_34_22 J_35_22 120 200 130 0 Open + P_V_03536 J_34_23 J_35_23 120 200 130 0 Open + P_V_03537 J_34_24 J_35_24 120 200 130 0 Open + P_V_03538 J_34_25 J_35_25 120 200 130 0 Open + P_V_03539 J_34_26 J_35_26 120 200 130 0 Open + P_V_03540 J_34_27 J_35_27 120 200 130 0 Open + P_V_03541 J_34_28 J_35_28 120 200 130 0 Open + P_V_03542 J_34_29 J_35_29 120 200 130 0 Open + P_V_03543 J_34_30 J_35_30 120 200 130 0 Open + P_V_03544 J_34_31 J_35_31 120 200 130 0 Open + P_V_03545 J_34_32 J_35_32 120 200 130 0 Open + P_V_03546 J_34_33 J_35_33 120 200 130 0 Open + P_V_03547 J_34_34 J_35_34 120 200 130 0 Open + P_V_03548 J_34_35 J_35_35 120 200 130 0 Open + P_V_03549 J_34_36 J_35_36 120 200 130 0 Open + P_V_03550 J_34_37 J_35_37 120 200 130 0 Open + P_V_03551 J_34_38 J_35_38 120 200 130 0 Open + P_V_03552 J_34_39 J_35_39 120 200 130 0 Open + P_V_03553 J_34_40 J_35_40 120 200 130 0 Open + P_V_03554 J_34_41 J_35_41 120 200 130 0 Open + P_V_03555 J_34_42 J_35_42 120 200 130 0 Open + P_V_03556 J_34_43 J_35_43 120 200 130 0 Open + P_V_03557 J_34_44 J_35_44 120 200 130 0 Open + P_V_03558 J_35_00 J_36_00 120 200 130 0 Open + P_V_03559 J_35_01 J_36_01 120 200 130 0 Open + P_V_03560 J_35_02 J_36_02 120 200 130 0 Open + P_V_03561 J_35_03 J_36_03 120 200 130 0 Open + P_V_03562 J_35_04 J_36_04 120 200 130 0 Open + P_V_03563 J_35_05 J_36_05 120 200 130 0 Open + P_V_03564 J_35_06 J_36_06 120 200 130 0 Open + P_V_03565 J_35_07 J_36_07 120 200 130 0 Open + P_V_03566 J_35_08 J_36_08 120 200 130 0 Open + P_V_03567 J_35_09 J_36_09 120 200 130 0 Open + P_V_03568 J_35_10 J_36_10 120 200 130 0 Open + P_V_03569 J_35_11 J_36_11 120 200 130 0 Open + P_V_03570 J_35_12 J_36_12 120 200 130 0 Open + P_V_03571 J_35_13 J_36_13 120 200 130 0 Open + P_V_03572 J_35_14 J_36_14 120 200 130 0 Open + P_V_03573 J_35_15 J_36_15 120 200 130 0 Open + P_V_03574 J_35_16 J_36_16 120 200 130 0 Open + P_V_03575 J_35_17 J_36_17 120 200 130 0 Open + P_V_03576 J_35_18 J_36_18 120 200 130 0 Open + P_V_03577 J_35_19 J_36_19 120 200 130 0 Open + P_V_03578 J_35_20 J_36_20 120 200 130 0 Open + P_V_03579 J_35_21 J_36_21 120 200 130 0 Open + P_V_03580 J_35_22 J_36_22 120 200 130 0 Open + P_V_03581 J_35_23 J_36_23 120 200 130 0 Open + P_V_03582 J_35_24 J_36_24 120 200 130 0 Open + P_V_03583 J_35_25 J_36_25 120 200 130 0 Open + P_V_03584 J_35_26 J_36_26 120 200 130 0 Open + P_V_03585 J_35_27 J_36_27 120 200 130 0 Open + P_V_03586 J_35_28 J_36_28 120 200 130 0 Open + P_V_03587 J_35_29 J_36_29 120 200 130 0 Open + P_V_03588 J_35_30 J_36_30 120 200 130 0 Open + P_V_03589 J_35_31 J_36_31 120 200 130 0 Open + P_V_03590 J_35_32 J_36_32 120 200 130 0 Open + P_V_03591 J_35_33 J_36_33 120 200 130 0 Open + P_V_03592 J_35_34 J_36_34 120 200 130 0 Open + P_V_03593 J_35_35 J_36_35 120 200 130 0 Open + P_V_03594 J_35_36 J_36_36 120 200 130 0 Open + P_V_03595 J_35_37 J_36_37 120 200 130 0 Open + P_V_03596 J_35_38 J_36_38 120 200 130 0 Open + P_V_03597 J_35_39 J_36_39 120 200 130 0 Open + P_V_03598 J_35_40 J_36_40 120 200 130 0 Open + P_V_03599 J_35_41 J_36_41 120 200 130 0 Open + P_V_03600 J_35_42 J_36_42 120 200 130 0 Open + P_V_03601 J_35_43 J_36_43 120 200 130 0 Open + P_V_03602 J_35_44 J_36_44 120 200 130 0 Open + P_V_03603 J_36_00 J_37_00 120 200 130 0 Open + P_V_03604 J_36_01 J_37_01 120 200 130 0 Open + P_V_03605 J_36_02 J_37_02 120 200 130 0 Open + P_V_03606 J_36_03 J_37_03 120 200 130 0 Open + P_V_03607 J_36_04 J_37_04 120 200 130 0 Open + P_V_03608 J_36_05 J_37_05 120 200 130 0 Open + P_V_03609 J_36_06 J_37_06 120 200 130 0 Open + P_V_03610 J_36_07 J_37_07 120 200 130 0 Open + P_V_03611 J_36_08 J_37_08 120 200 130 0 Open + P_V_03612 J_36_09 J_37_09 120 200 130 0 Open + P_V_03613 J_36_10 J_37_10 120 200 130 0 Open + P_V_03614 J_36_11 J_37_11 120 200 130 0 Open + P_V_03615 J_36_12 J_37_12 120 200 130 0 Open + P_V_03616 J_36_13 J_37_13 120 200 130 0 Open + P_V_03617 J_36_14 J_37_14 120 200 130 0 Open + P_V_03618 J_36_15 J_37_15 120 200 130 0 Open + P_V_03619 J_36_16 J_37_16 120 200 130 0 Open + P_V_03620 J_36_17 J_37_17 120 200 130 0 Open + P_V_03621 J_36_18 J_37_18 120 200 130 0 Open + P_V_03622 J_36_19 J_37_19 120 200 130 0 Open + P_V_03623 J_36_20 J_37_20 120 200 130 0 Open + P_V_03624 J_36_21 J_37_21 120 200 130 0 Open + P_V_03625 J_36_22 J_37_22 120 200 130 0 Open + P_V_03626 J_36_23 J_37_23 120 200 130 0 Open + P_V_03627 J_36_24 J_37_24 120 200 130 0 Open + P_V_03628 J_36_25 J_37_25 120 200 130 0 Open + P_V_03629 J_36_26 J_37_26 120 200 130 0 Open + P_V_03630 J_36_27 J_37_27 120 200 130 0 Open + P_V_03631 J_36_28 J_37_28 120 200 130 0 Open + P_V_03632 J_36_29 J_37_29 120 200 130 0 Open + P_V_03633 J_36_30 J_37_30 120 200 130 0 Open + P_V_03634 J_36_31 J_37_31 120 200 130 0 Open + P_V_03635 J_36_32 J_37_32 120 200 130 0 Open + P_V_03636 J_36_33 J_37_33 120 200 130 0 Open + P_V_03637 J_36_34 J_37_34 120 200 130 0 Open + P_V_03638 J_36_35 J_37_35 120 200 130 0 Open + P_V_03639 J_36_36 J_37_36 120 200 130 0 Open + P_V_03640 J_36_37 J_37_37 120 200 130 0 Open + P_V_03641 J_36_38 J_37_38 120 200 130 0 Open + P_V_03642 J_36_39 J_37_39 120 200 130 0 Open + P_V_03643 J_36_40 J_37_40 120 200 130 0 Open + P_V_03644 J_36_41 J_37_41 120 200 130 0 Open + P_V_03645 J_36_42 J_37_42 120 200 130 0 Open + P_V_03646 J_36_43 J_37_43 120 200 130 0 Open + P_V_03647 J_36_44 J_37_44 120 200 130 0 Open + P_V_03648 J_37_00 J_38_00 120 200 130 0 Open + P_V_03649 J_37_01 J_38_01 120 200 130 0 Open + P_V_03650 J_37_02 J_38_02 120 200 130 0 Open + P_V_03651 J_37_03 J_38_03 120 200 130 0 Open + P_V_03652 J_37_04 J_38_04 120 200 130 0 Open + P_V_03653 J_37_05 J_38_05 120 200 130 0 Open + P_V_03654 J_37_06 J_38_06 120 200 130 0 Open + P_V_03655 J_37_07 J_38_07 120 200 130 0 Open + P_V_03656 J_37_08 J_38_08 120 200 130 0 Open + P_V_03657 J_37_09 J_38_09 120 200 130 0 Open + P_V_03658 J_37_10 J_38_10 120 200 130 0 Open + P_V_03659 J_37_11 J_38_11 120 200 130 0 Open + P_V_03660 J_37_12 J_38_12 120 200 130 0 Open + P_V_03661 J_37_13 J_38_13 120 200 130 0 Open + P_V_03662 J_37_14 J_38_14 120 200 130 0 Open + P_V_03663 J_37_15 J_38_15 120 200 130 0 Open + P_V_03664 J_37_16 J_38_16 120 200 130 0 Open + P_V_03665 J_37_17 J_38_17 120 200 130 0 Open + P_V_03666 J_37_18 J_38_18 120 200 130 0 Open + P_V_03667 J_37_19 J_38_19 120 200 130 0 Open + P_V_03668 J_37_20 J_38_20 120 200 130 0 Open + P_V_03669 J_37_21 J_38_21 120 200 130 0 Open + P_V_03670 J_37_22 J_38_22 120 200 130 0 Open + P_V_03671 J_37_23 J_38_23 120 200 130 0 Open + P_V_03672 J_37_24 J_38_24 120 200 130 0 Open + P_V_03673 J_37_25 J_38_25 120 200 130 0 Open + P_V_03674 J_37_26 J_38_26 120 200 130 0 Open + P_V_03675 J_37_27 J_38_27 120 200 130 0 Open + P_V_03676 J_37_28 J_38_28 120 200 130 0 Open + P_V_03677 J_37_29 J_38_29 120 200 130 0 Open + P_V_03678 J_37_30 J_38_30 120 200 130 0 Open + P_V_03679 J_37_31 J_38_31 120 200 130 0 Open + P_V_03680 J_37_32 J_38_32 120 200 130 0 Open + P_V_03681 J_37_33 J_38_33 120 200 130 0 Open + P_V_03682 J_37_34 J_38_34 120 200 130 0 Open + P_V_03683 J_37_35 J_38_35 120 200 130 0 Open + P_V_03684 J_37_36 J_38_36 120 200 130 0 Open + P_V_03685 J_37_37 J_38_37 120 200 130 0 Open + P_V_03686 J_37_38 J_38_38 120 200 130 0 Open + P_V_03687 J_37_39 J_38_39 120 200 130 0 Open + P_V_03688 J_37_40 J_38_40 120 200 130 0 Open + P_V_03689 J_37_41 J_38_41 120 200 130 0 Open + P_V_03690 J_37_42 J_38_42 120 200 130 0 Open + P_V_03691 J_37_43 J_38_43 120 200 130 0 Open + P_V_03692 J_37_44 J_38_44 120 200 130 0 Open + P_V_03693 J_38_00 J_39_00 120 200 130 0 Open + P_V_03694 J_38_01 J_39_01 120 200 130 0 Open + P_V_03695 J_38_02 J_39_02 120 200 130 0 Open + P_V_03696 J_38_03 J_39_03 120 200 130 0 Open + P_V_03697 J_38_04 J_39_04 120 200 130 0 Open + P_V_03698 J_38_05 J_39_05 120 200 130 0 Open + P_V_03699 J_38_06 J_39_06 120 200 130 0 Open + P_V_03700 J_38_07 J_39_07 120 200 130 0 Open + P_V_03701 J_38_08 J_39_08 120 200 130 0 Open + P_V_03702 J_38_09 J_39_09 120 200 130 0 Open + P_V_03703 J_38_10 J_39_10 120 200 130 0 Open + P_V_03704 J_38_11 J_39_11 120 200 130 0 Open + P_V_03705 J_38_12 J_39_12 120 200 130 0 Open + P_V_03706 J_38_13 J_39_13 120 200 130 0 Open + P_V_03707 J_38_14 J_39_14 120 200 130 0 Open + P_V_03708 J_38_15 J_39_15 120 200 130 0 Open + P_V_03709 J_38_16 J_39_16 120 200 130 0 Open + P_V_03710 J_38_17 J_39_17 120 200 130 0 Open + P_V_03711 J_38_18 J_39_18 120 200 130 0 Open + P_V_03712 J_38_19 J_39_19 120 200 130 0 Open + P_V_03713 J_38_20 J_39_20 120 200 130 0 Open + P_V_03714 J_38_21 J_39_21 120 200 130 0 Open + P_V_03715 J_38_22 J_39_22 120 200 130 0 Open + P_V_03716 J_38_23 J_39_23 120 200 130 0 Open + P_V_03717 J_38_24 J_39_24 120 200 130 0 Open + P_V_03718 J_38_25 J_39_25 120 200 130 0 Open + P_V_03719 J_38_26 J_39_26 120 200 130 0 Open + P_V_03720 J_38_27 J_39_27 120 200 130 0 Open + P_V_03721 J_38_28 J_39_28 120 200 130 0 Open + P_V_03722 J_38_29 J_39_29 120 200 130 0 Open + P_V_03723 J_38_30 J_39_30 120 200 130 0 Open + P_V_03724 J_38_31 J_39_31 120 200 130 0 Open + P_V_03725 J_38_32 J_39_32 120 200 130 0 Open + P_V_03726 J_38_33 J_39_33 120 200 130 0 Open + P_V_03727 J_38_34 J_39_34 120 200 130 0 Open + P_V_03728 J_38_35 J_39_35 120 200 130 0 Open + P_V_03729 J_38_36 J_39_36 120 200 130 0 Open + P_V_03730 J_38_37 J_39_37 120 200 130 0 Open + P_V_03731 J_38_38 J_39_38 120 200 130 0 Open + P_V_03732 J_38_39 J_39_39 120 200 130 0 Open + P_V_03733 J_38_40 J_39_40 120 200 130 0 Open + P_V_03734 J_38_41 J_39_41 120 200 130 0 Open + P_V_03735 J_38_42 J_39_42 120 200 130 0 Open + P_V_03736 J_38_43 J_39_43 120 200 130 0 Open + P_V_03737 J_38_44 J_39_44 120 200 130 0 Open + P_V_03738 J_39_00 J_40_00 120 200 130 0 Open + P_V_03739 J_39_01 J_40_01 120 200 130 0 Open + P_V_03740 J_39_02 J_40_02 120 200 130 0 Open + P_V_03741 J_39_03 J_40_03 120 200 130 0 Open + P_V_03742 J_39_04 J_40_04 120 200 130 0 Open + P_V_03743 J_39_05 J_40_05 120 200 130 0 Open + P_V_03744 J_39_06 J_40_06 120 200 130 0 Open + P_V_03745 J_39_07 J_40_07 120 200 130 0 Open + P_V_03746 J_39_08 J_40_08 120 200 130 0 Open + P_V_03747 J_39_09 J_40_09 120 200 130 0 Open + P_V_03748 J_39_10 J_40_10 120 200 130 0 Open + P_V_03749 J_39_11 J_40_11 120 200 130 0 Open + P_V_03750 J_39_12 J_40_12 120 200 130 0 Open + P_V_03751 J_39_13 J_40_13 120 200 130 0 Open + P_V_03752 J_39_14 J_40_14 120 200 130 0 Open + P_V_03753 J_39_15 J_40_15 120 200 130 0 Open + P_V_03754 J_39_16 J_40_16 120 200 130 0 Open + P_V_03755 J_39_17 J_40_17 120 200 130 0 Open + P_V_03756 J_39_18 J_40_18 120 200 130 0 Open + P_V_03757 J_39_19 J_40_19 120 200 130 0 Open + P_V_03758 J_39_20 J_40_20 120 200 130 0 Open + P_V_03759 J_39_21 J_40_21 120 200 130 0 Open + P_V_03760 J_39_22 J_40_22 120 200 130 0 Open + P_V_03761 J_39_23 J_40_23 120 200 130 0 Open + P_V_03762 J_39_24 J_40_24 120 200 130 0 Open + P_V_03763 J_39_25 J_40_25 120 200 130 0 Open + P_V_03764 J_39_26 J_40_26 120 200 130 0 Open + P_V_03765 J_39_27 J_40_27 120 200 130 0 Open + P_V_03766 J_39_28 J_40_28 120 200 130 0 Open + P_V_03767 J_39_29 J_40_29 120 200 130 0 Open + P_V_03768 J_39_30 J_40_30 120 200 130 0 Open + P_V_03769 J_39_31 J_40_31 120 200 130 0 Open + P_V_03770 J_39_32 J_40_32 120 200 130 0 Open + P_V_03771 J_39_33 J_40_33 120 200 130 0 Open + P_V_03772 J_39_34 J_40_34 120 200 130 0 Open + P_V_03773 J_39_35 J_40_35 120 200 130 0 Open + P_V_03774 J_39_36 J_40_36 120 200 130 0 Open + P_V_03775 J_39_37 J_40_37 120 200 130 0 Open + P_V_03776 J_39_38 J_40_38 120 200 130 0 Open + P_V_03777 J_39_39 J_40_39 120 200 130 0 Open + P_V_03778 J_39_40 J_40_40 120 200 130 0 Open + P_V_03779 J_39_41 J_40_41 120 200 130 0 Open + P_V_03780 J_39_42 J_40_42 120 200 130 0 Open + P_V_03781 J_39_43 J_40_43 120 200 130 0 Open + P_V_03782 J_39_44 J_40_44 120 200 130 0 Open + P_V_03783 J_40_00 J_41_00 120 200 130 0 Open + P_V_03784 J_40_01 J_41_01 120 200 130 0 Open + P_V_03785 J_40_02 J_41_02 120 200 130 0 Open + P_V_03786 J_40_03 J_41_03 120 200 130 0 Open + P_V_03787 J_40_04 J_41_04 120 200 130 0 Open + P_V_03788 J_40_05 J_41_05 120 200 130 0 Open + P_V_03789 J_40_06 J_41_06 120 200 130 0 Open + P_V_03790 J_40_07 J_41_07 120 200 130 0 Open + P_V_03791 J_40_08 J_41_08 120 200 130 0 Open + P_V_03792 J_40_09 J_41_09 120 200 130 0 Open + P_V_03793 J_40_10 J_41_10 120 200 130 0 Open + P_V_03794 J_40_11 J_41_11 120 200 130 0 Open + P_V_03795 J_40_12 J_41_12 120 200 130 0 Open + P_V_03796 J_40_13 J_41_13 120 200 130 0 Open + P_V_03797 J_40_14 J_41_14 120 200 130 0 Open + P_V_03798 J_40_15 J_41_15 120 200 130 0 Open + P_V_03799 J_40_16 J_41_16 120 200 130 0 Open + P_V_03800 J_40_17 J_41_17 120 200 130 0 Open + P_V_03801 J_40_18 J_41_18 120 200 130 0 Open + P_V_03802 J_40_19 J_41_19 120 200 130 0 Open + P_V_03803 J_40_20 J_41_20 120 200 130 0 Open + P_V_03804 J_40_21 J_41_21 120 200 130 0 Open + P_V_03805 J_40_22 J_41_22 120 200 130 0 Open + P_V_03806 J_40_23 J_41_23 120 200 130 0 Open + P_V_03807 J_40_24 J_41_24 120 200 130 0 Open + P_V_03808 J_40_25 J_41_25 120 200 130 0 Open + P_V_03809 J_40_26 J_41_26 120 200 130 0 Open + P_V_03810 J_40_27 J_41_27 120 200 130 0 Open + P_V_03811 J_40_28 J_41_28 120 200 130 0 Open + P_V_03812 J_40_29 J_41_29 120 200 130 0 Open + P_V_03813 J_40_30 J_41_30 120 200 130 0 Open + P_V_03814 J_40_31 J_41_31 120 200 130 0 Open + P_V_03815 J_40_32 J_41_32 120 200 130 0 Open + P_V_03816 J_40_33 J_41_33 120 200 130 0 Open + P_V_03817 J_40_34 J_41_34 120 200 130 0 Open + P_V_03818 J_40_35 J_41_35 120 200 130 0 Open + P_V_03819 J_40_36 J_41_36 120 200 130 0 Open + P_V_03820 J_40_37 J_41_37 120 200 130 0 Open + P_V_03821 J_40_38 J_41_38 120 200 130 0 Open + P_V_03822 J_40_39 J_41_39 120 200 130 0 Open + P_V_03823 J_40_40 J_41_40 120 200 130 0 Open + P_V_03824 J_40_41 J_41_41 120 200 130 0 Open + P_V_03825 J_40_42 J_41_42 120 200 130 0 Open + P_V_03826 J_40_43 J_41_43 120 200 130 0 Open + P_V_03827 J_40_44 J_41_44 120 200 130 0 Open + P_V_03828 J_41_00 J_42_00 120 200 130 0 Open + P_V_03829 J_41_01 J_42_01 120 200 130 0 Open + P_V_03830 J_41_02 J_42_02 120 200 130 0 Open + P_V_03831 J_41_03 J_42_03 120 200 130 0 Open + P_V_03832 J_41_04 J_42_04 120 200 130 0 Open + P_V_03833 J_41_05 J_42_05 120 200 130 0 Open + P_V_03834 J_41_06 J_42_06 120 200 130 0 Open + P_V_03835 J_41_07 J_42_07 120 200 130 0 Open + P_V_03836 J_41_08 J_42_08 120 200 130 0 Open + P_V_03837 J_41_09 J_42_09 120 200 130 0 Open + P_V_03838 J_41_10 J_42_10 120 200 130 0 Open + P_V_03839 J_41_11 J_42_11 120 200 130 0 Open + P_V_03840 J_41_12 J_42_12 120 200 130 0 Open + P_V_03841 J_41_13 J_42_13 120 200 130 0 Open + P_V_03842 J_41_14 J_42_14 120 200 130 0 Open + P_V_03843 J_41_15 J_42_15 120 200 130 0 Open + P_V_03844 J_41_16 J_42_16 120 200 130 0 Open + P_V_03845 J_41_17 J_42_17 120 200 130 0 Open + P_V_03846 J_41_18 J_42_18 120 200 130 0 Open + P_V_03847 J_41_19 J_42_19 120 200 130 0 Open + P_V_03848 J_41_20 J_42_20 120 200 130 0 Open + P_V_03849 J_41_21 J_42_21 120 200 130 0 Open + P_V_03850 J_41_22 J_42_22 120 200 130 0 Open + P_V_03851 J_41_23 J_42_23 120 200 130 0 Open + P_V_03852 J_41_24 J_42_24 120 200 130 0 Open + P_V_03853 J_41_25 J_42_25 120 200 130 0 Open + P_V_03854 J_41_26 J_42_26 120 200 130 0 Open + P_V_03855 J_41_27 J_42_27 120 200 130 0 Open + P_V_03856 J_41_28 J_42_28 120 200 130 0 Open + P_V_03857 J_41_29 J_42_29 120 200 130 0 Open + P_V_03858 J_41_30 J_42_30 120 200 130 0 Open + P_V_03859 J_41_31 J_42_31 120 200 130 0 Open + P_V_03860 J_41_32 J_42_32 120 200 130 0 Open + P_V_03861 J_41_33 J_42_33 120 200 130 0 Open + P_V_03862 J_41_34 J_42_34 120 200 130 0 Open + P_V_03863 J_41_35 J_42_35 120 200 130 0 Open + P_V_03864 J_41_36 J_42_36 120 200 130 0 Open + P_V_03865 J_41_37 J_42_37 120 200 130 0 Open + P_V_03866 J_41_38 J_42_38 120 200 130 0 Open + P_V_03867 J_41_39 J_42_39 120 200 130 0 Open + P_V_03868 J_41_40 J_42_40 120 200 130 0 Open + P_V_03869 J_41_41 J_42_41 120 200 130 0 Open + P_V_03870 J_41_42 J_42_42 120 200 130 0 Open + P_V_03871 J_41_43 J_42_43 120 200 130 0 Open + P_V_03872 J_41_44 J_42_44 120 200 130 0 Open + P_V_03873 J_42_00 J_43_00 120 200 130 0 Open + P_V_03874 J_42_01 J_43_01 120 200 130 0 Open + P_V_03875 J_42_02 J_43_02 120 200 130 0 Open + P_V_03876 J_42_03 J_43_03 120 200 130 0 Open + P_V_03877 J_42_04 J_43_04 120 200 130 0 Open + P_V_03878 J_42_05 J_43_05 120 200 130 0 Open + P_V_03879 J_42_06 J_43_06 120 200 130 0 Open + P_V_03880 J_42_07 J_43_07 120 200 130 0 Open + P_V_03881 J_42_08 J_43_08 120 200 130 0 Open + P_V_03882 J_42_09 J_43_09 120 200 130 0 Open + P_V_03883 J_42_10 J_43_10 120 200 130 0 Open + P_V_03884 J_42_11 J_43_11 120 200 130 0 Open + P_V_03885 J_42_12 J_43_12 120 200 130 0 Open + P_V_03886 J_42_13 J_43_13 120 200 130 0 Open + P_V_03887 J_42_14 J_43_14 120 200 130 0 Open + P_V_03888 J_42_15 J_43_15 120 200 130 0 Open + P_V_03889 J_42_16 J_43_16 120 200 130 0 Open + P_V_03890 J_42_17 J_43_17 120 200 130 0 Open + P_V_03891 J_42_18 J_43_18 120 200 130 0 Open + P_V_03892 J_42_19 J_43_19 120 200 130 0 Open + P_V_03893 J_42_20 J_43_20 120 200 130 0 Open + P_V_03894 J_42_21 J_43_21 120 200 130 0 Open + P_V_03895 J_42_22 J_43_22 120 200 130 0 Open + P_V_03896 J_42_23 J_43_23 120 200 130 0 Open + P_V_03897 J_42_24 J_43_24 120 200 130 0 Open + P_V_03898 J_42_25 J_43_25 120 200 130 0 Open + P_V_03899 J_42_26 J_43_26 120 200 130 0 Open + P_V_03900 J_42_27 J_43_27 120 200 130 0 Open + P_V_03901 J_42_28 J_43_28 120 200 130 0 Open + P_V_03902 J_42_29 J_43_29 120 200 130 0 Open + P_V_03903 J_42_30 J_43_30 120 200 130 0 Open + P_V_03904 J_42_31 J_43_31 120 200 130 0 Open + P_V_03905 J_42_32 J_43_32 120 200 130 0 Open + P_V_03906 J_42_33 J_43_33 120 200 130 0 Open + P_V_03907 J_42_34 J_43_34 120 200 130 0 Open + P_V_03908 J_42_35 J_43_35 120 200 130 0 Open + P_V_03909 J_42_36 J_43_36 120 200 130 0 Open + P_V_03910 J_42_37 J_43_37 120 200 130 0 Open + P_V_03911 J_42_38 J_43_38 120 200 130 0 Open + P_V_03912 J_42_39 J_43_39 120 200 130 0 Open + P_V_03913 J_42_40 J_43_40 120 200 130 0 Open + P_V_03914 J_42_41 J_43_41 120 200 130 0 Open + P_V_03915 J_42_42 J_43_42 120 200 130 0 Open + P_V_03916 J_42_43 J_43_43 120 200 130 0 Open + P_V_03917 J_42_44 J_43_44 120 200 130 0 Open + P_V_03918 J_43_00 J_44_00 120 200 130 0 Open + P_V_03919 J_43_01 J_44_01 120 200 130 0 Open + P_V_03920 J_43_02 J_44_02 120 200 130 0 Open + P_V_03921 J_43_03 J_44_03 120 200 130 0 Open + P_V_03922 J_43_04 J_44_04 120 200 130 0 Open + P_V_03923 J_43_05 J_44_05 120 200 130 0 Open + P_V_03924 J_43_06 J_44_06 120 200 130 0 Open + P_V_03925 J_43_07 J_44_07 120 200 130 0 Open + P_V_03926 J_43_08 J_44_08 120 200 130 0 Open + P_V_03927 J_43_09 J_44_09 120 200 130 0 Open + P_V_03928 J_43_10 J_44_10 120 200 130 0 Open + P_V_03929 J_43_11 J_44_11 120 200 130 0 Open + P_V_03930 J_43_12 J_44_12 120 200 130 0 Open + P_V_03931 J_43_13 J_44_13 120 200 130 0 Open + P_V_03932 J_43_14 J_44_14 120 200 130 0 Open + P_V_03933 J_43_15 J_44_15 120 200 130 0 Open + P_V_03934 J_43_16 J_44_16 120 200 130 0 Open + P_V_03935 J_43_17 J_44_17 120 200 130 0 Open + P_V_03936 J_43_18 J_44_18 120 200 130 0 Open + P_V_03937 J_43_19 J_44_19 120 200 130 0 Open + P_V_03938 J_43_20 J_44_20 120 200 130 0 Open + P_V_03939 J_43_21 J_44_21 120 200 130 0 Open + P_V_03940 J_43_22 J_44_22 120 200 130 0 Open + P_V_03941 J_43_23 J_44_23 120 200 130 0 Open + P_V_03942 J_43_24 J_44_24 120 200 130 0 Open + P_V_03943 J_43_25 J_44_25 120 200 130 0 Open + P_V_03944 J_43_26 J_44_26 120 200 130 0 Open + P_V_03945 J_43_27 J_44_27 120 200 130 0 Open + P_V_03946 J_43_28 J_44_28 120 200 130 0 Open + P_V_03947 J_43_29 J_44_29 120 200 130 0 Open + P_V_03948 J_43_30 J_44_30 120 200 130 0 Open + P_V_03949 J_43_31 J_44_31 120 200 130 0 Open + P_V_03950 J_43_32 J_44_32 120 200 130 0 Open + P_V_03951 J_43_33 J_44_33 120 200 130 0 Open + P_V_03952 J_43_34 J_44_34 120 200 130 0 Open + P_V_03953 J_43_35 J_44_35 120 200 130 0 Open + P_V_03954 J_43_36 J_44_36 120 200 130 0 Open + P_V_03955 J_43_37 J_44_37 120 200 130 0 Open + P_V_03956 J_43_38 J_44_38 120 200 130 0 Open + P_V_03957 J_43_39 J_44_39 120 200 130 0 Open + P_V_03958 J_43_40 J_44_40 120 200 130 0 Open + P_V_03959 J_43_41 J_44_41 120 200 130 0 Open + P_V_03960 J_43_42 J_44_42 120 200 130 0 Open + P_V_03961 J_43_43 J_44_43 120 200 130 0 Open + P_V_03962 J_43_44 J_44_44 120 200 130 0 Open + +[PATTERNS] +;ID Multipliers + 1 0.80 0.90 1.20 1.50 1.30 1.00 + 1 1.10 1.00 1.30 1.40 0.90 0.70 + +[TIMES] + Duration 24:00 + Hydraulic Timestep 1:00 + Quality Timestep 0:05 + Pattern Timestep 2:00 + Pattern Start 0:00 + Report Timestep 1:00 + Report Start 0:00 + Start ClockTime 12 am + Statistic None + +[OPTIONS] + Units LPS + Headloss H-W + Specific Gravity 1.0 + Viscosity 1.0 + Trials 50 + Accuracy 0.001 + +[COORDINATES] +;Node X-Coord Y-Coord + RES_NORTH 949850.00 1939850.00 + RES_SOUTH 955550.00 1945550.00 + J_00_00 950000.00 1940000.00 + J_00_01 950120.00 1940000.00 + J_00_02 950240.00 1940000.00 + J_00_03 950360.00 1940000.00 + J_00_04 950480.00 1940000.00 + J_00_05 950600.00 1940000.00 + J_00_06 950720.00 1940000.00 + J_00_07 950840.00 1940000.00 + J_00_08 950960.00 1940000.00 + J_00_09 951080.00 1940000.00 + J_00_10 951200.00 1940000.00 + J_00_11 951320.00 1940000.00 + J_00_12 951440.00 1940000.00 + J_00_13 951560.00 1940000.00 + J_00_14 951680.00 1940000.00 + J_00_15 951800.00 1940000.00 + J_00_16 951920.00 1940000.00 + J_00_17 952040.00 1940000.00 + J_00_18 952160.00 1940000.00 + J_00_19 952280.00 1940000.00 + J_00_20 952400.00 1940000.00 + J_00_21 952520.00 1940000.00 + J_00_22 952640.00 1940000.00 + J_00_23 952760.00 1940000.00 + J_00_24 952880.00 1940000.00 + J_00_25 953000.00 1940000.00 + J_00_26 953120.00 1940000.00 + J_00_27 953240.00 1940000.00 + J_00_28 953360.00 1940000.00 + J_00_29 953480.00 1940000.00 + J_00_30 953600.00 1940000.00 + J_00_31 953720.00 1940000.00 + J_00_32 953840.00 1940000.00 + J_00_33 953960.00 1940000.00 + J_00_34 954080.00 1940000.00 + J_00_35 954200.00 1940000.00 + J_00_36 954320.00 1940000.00 + J_00_37 954440.00 1940000.00 + J_00_38 954560.00 1940000.00 + J_00_39 954680.00 1940000.00 + J_00_40 954800.00 1940000.00 + J_00_41 954920.00 1940000.00 + J_00_42 955040.00 1940000.00 + J_00_43 955160.00 1940000.00 + J_00_44 955280.00 1940000.00 + J_01_00 950000.00 1940120.00 + J_01_01 950120.00 1940120.00 + J_01_02 950240.00 1940120.00 + J_01_03 950360.00 1940120.00 + J_01_04 950480.00 1940120.00 + J_01_05 950600.00 1940120.00 + J_01_06 950720.00 1940120.00 + J_01_07 950840.00 1940120.00 + J_01_08 950960.00 1940120.00 + J_01_09 951080.00 1940120.00 + J_01_10 951200.00 1940120.00 + J_01_11 951320.00 1940120.00 + J_01_12 951440.00 1940120.00 + J_01_13 951560.00 1940120.00 + J_01_14 951680.00 1940120.00 + J_01_15 951800.00 1940120.00 + J_01_16 951920.00 1940120.00 + J_01_17 952040.00 1940120.00 + J_01_18 952160.00 1940120.00 + J_01_19 952280.00 1940120.00 + J_01_20 952400.00 1940120.00 + J_01_21 952520.00 1940120.00 + J_01_22 952640.00 1940120.00 + J_01_23 952760.00 1940120.00 + J_01_24 952880.00 1940120.00 + J_01_25 953000.00 1940120.00 + J_01_26 953120.00 1940120.00 + J_01_27 953240.00 1940120.00 + J_01_28 953360.00 1940120.00 + J_01_29 953480.00 1940120.00 + J_01_30 953600.00 1940120.00 + J_01_31 953720.00 1940120.00 + J_01_32 953840.00 1940120.00 + J_01_33 953960.00 1940120.00 + J_01_34 954080.00 1940120.00 + J_01_35 954200.00 1940120.00 + J_01_36 954320.00 1940120.00 + J_01_37 954440.00 1940120.00 + J_01_38 954560.00 1940120.00 + J_01_39 954680.00 1940120.00 + J_01_40 954800.00 1940120.00 + J_01_41 954920.00 1940120.00 + J_01_42 955040.00 1940120.00 + J_01_43 955160.00 1940120.00 + J_01_44 955280.00 1940120.00 + J_02_00 950000.00 1940240.00 + J_02_01 950120.00 1940240.00 + J_02_02 950240.00 1940240.00 + J_02_03 950360.00 1940240.00 + J_02_04 950480.00 1940240.00 + J_02_05 950600.00 1940240.00 + J_02_06 950720.00 1940240.00 + J_02_07 950840.00 1940240.00 + J_02_08 950960.00 1940240.00 + J_02_09 951080.00 1940240.00 + J_02_10 951200.00 1940240.00 + J_02_11 951320.00 1940240.00 + J_02_12 951440.00 1940240.00 + J_02_13 951560.00 1940240.00 + J_02_14 951680.00 1940240.00 + J_02_15 951800.00 1940240.00 + J_02_16 951920.00 1940240.00 + J_02_17 952040.00 1940240.00 + J_02_18 952160.00 1940240.00 + J_02_19 952280.00 1940240.00 + J_02_20 952400.00 1940240.00 + J_02_21 952520.00 1940240.00 + J_02_22 952640.00 1940240.00 + J_02_23 952760.00 1940240.00 + J_02_24 952880.00 1940240.00 + J_02_25 953000.00 1940240.00 + J_02_26 953120.00 1940240.00 + J_02_27 953240.00 1940240.00 + J_02_28 953360.00 1940240.00 + J_02_29 953480.00 1940240.00 + J_02_30 953600.00 1940240.00 + J_02_31 953720.00 1940240.00 + J_02_32 953840.00 1940240.00 + J_02_33 953960.00 1940240.00 + J_02_34 954080.00 1940240.00 + J_02_35 954200.00 1940240.00 + J_02_36 954320.00 1940240.00 + J_02_37 954440.00 1940240.00 + J_02_38 954560.00 1940240.00 + J_02_39 954680.00 1940240.00 + J_02_40 954800.00 1940240.00 + J_02_41 954920.00 1940240.00 + J_02_42 955040.00 1940240.00 + J_02_43 955160.00 1940240.00 + J_02_44 955280.00 1940240.00 + J_03_00 950000.00 1940360.00 + J_03_01 950120.00 1940360.00 + J_03_02 950240.00 1940360.00 + J_03_03 950360.00 1940360.00 + J_03_04 950480.00 1940360.00 + J_03_05 950600.00 1940360.00 + J_03_06 950720.00 1940360.00 + J_03_07 950840.00 1940360.00 + J_03_08 950960.00 1940360.00 + J_03_09 951080.00 1940360.00 + J_03_10 951200.00 1940360.00 + J_03_11 951320.00 1940360.00 + J_03_12 951440.00 1940360.00 + J_03_13 951560.00 1940360.00 + J_03_14 951680.00 1940360.00 + J_03_15 951800.00 1940360.00 + J_03_16 951920.00 1940360.00 + J_03_17 952040.00 1940360.00 + J_03_18 952160.00 1940360.00 + J_03_19 952280.00 1940360.00 + J_03_20 952400.00 1940360.00 + J_03_21 952520.00 1940360.00 + J_03_22 952640.00 1940360.00 + J_03_23 952760.00 1940360.00 + J_03_24 952880.00 1940360.00 + J_03_25 953000.00 1940360.00 + J_03_26 953120.00 1940360.00 + J_03_27 953240.00 1940360.00 + J_03_28 953360.00 1940360.00 + J_03_29 953480.00 1940360.00 + J_03_30 953600.00 1940360.00 + J_03_31 953720.00 1940360.00 + J_03_32 953840.00 1940360.00 + J_03_33 953960.00 1940360.00 + J_03_34 954080.00 1940360.00 + J_03_35 954200.00 1940360.00 + J_03_36 954320.00 1940360.00 + J_03_37 954440.00 1940360.00 + J_03_38 954560.00 1940360.00 + J_03_39 954680.00 1940360.00 + J_03_40 954800.00 1940360.00 + J_03_41 954920.00 1940360.00 + J_03_42 955040.00 1940360.00 + J_03_43 955160.00 1940360.00 + J_03_44 955280.00 1940360.00 + J_04_00 950000.00 1940480.00 + J_04_01 950120.00 1940480.00 + J_04_02 950240.00 1940480.00 + J_04_03 950360.00 1940480.00 + J_04_04 950480.00 1940480.00 + J_04_05 950600.00 1940480.00 + J_04_06 950720.00 1940480.00 + J_04_07 950840.00 1940480.00 + J_04_08 950960.00 1940480.00 + J_04_09 951080.00 1940480.00 + J_04_10 951200.00 1940480.00 + J_04_11 951320.00 1940480.00 + J_04_12 951440.00 1940480.00 + J_04_13 951560.00 1940480.00 + J_04_14 951680.00 1940480.00 + J_04_15 951800.00 1940480.00 + J_04_16 951920.00 1940480.00 + J_04_17 952040.00 1940480.00 + J_04_18 952160.00 1940480.00 + J_04_19 952280.00 1940480.00 + J_04_20 952400.00 1940480.00 + J_04_21 952520.00 1940480.00 + J_04_22 952640.00 1940480.00 + J_04_23 952760.00 1940480.00 + J_04_24 952880.00 1940480.00 + J_04_25 953000.00 1940480.00 + J_04_26 953120.00 1940480.00 + J_04_27 953240.00 1940480.00 + J_04_28 953360.00 1940480.00 + J_04_29 953480.00 1940480.00 + J_04_30 953600.00 1940480.00 + J_04_31 953720.00 1940480.00 + J_04_32 953840.00 1940480.00 + J_04_33 953960.00 1940480.00 + J_04_34 954080.00 1940480.00 + J_04_35 954200.00 1940480.00 + J_04_36 954320.00 1940480.00 + J_04_37 954440.00 1940480.00 + J_04_38 954560.00 1940480.00 + J_04_39 954680.00 1940480.00 + J_04_40 954800.00 1940480.00 + J_04_41 954920.00 1940480.00 + J_04_42 955040.00 1940480.00 + J_04_43 955160.00 1940480.00 + J_04_44 955280.00 1940480.00 + J_05_00 950000.00 1940600.00 + J_05_01 950120.00 1940600.00 + J_05_02 950240.00 1940600.00 + J_05_03 950360.00 1940600.00 + J_05_04 950480.00 1940600.00 + J_05_05 950600.00 1940600.00 + J_05_06 950720.00 1940600.00 + J_05_07 950840.00 1940600.00 + J_05_08 950960.00 1940600.00 + J_05_09 951080.00 1940600.00 + J_05_10 951200.00 1940600.00 + J_05_11 951320.00 1940600.00 + J_05_12 951440.00 1940600.00 + J_05_13 951560.00 1940600.00 + J_05_14 951680.00 1940600.00 + J_05_15 951800.00 1940600.00 + J_05_16 951920.00 1940600.00 + J_05_17 952040.00 1940600.00 + J_05_18 952160.00 1940600.00 + J_05_19 952280.00 1940600.00 + J_05_20 952400.00 1940600.00 + J_05_21 952520.00 1940600.00 + J_05_22 952640.00 1940600.00 + J_05_23 952760.00 1940600.00 + J_05_24 952880.00 1940600.00 + J_05_25 953000.00 1940600.00 + J_05_26 953120.00 1940600.00 + J_05_27 953240.00 1940600.00 + J_05_28 953360.00 1940600.00 + J_05_29 953480.00 1940600.00 + J_05_30 953600.00 1940600.00 + J_05_31 953720.00 1940600.00 + J_05_32 953840.00 1940600.00 + J_05_33 953960.00 1940600.00 + J_05_34 954080.00 1940600.00 + J_05_35 954200.00 1940600.00 + J_05_36 954320.00 1940600.00 + J_05_37 954440.00 1940600.00 + J_05_38 954560.00 1940600.00 + J_05_39 954680.00 1940600.00 + J_05_40 954800.00 1940600.00 + J_05_41 954920.00 1940600.00 + J_05_42 955040.00 1940600.00 + J_05_43 955160.00 1940600.00 + J_05_44 955280.00 1940600.00 + J_06_00 950000.00 1940720.00 + J_06_01 950120.00 1940720.00 + J_06_02 950240.00 1940720.00 + J_06_03 950360.00 1940720.00 + J_06_04 950480.00 1940720.00 + J_06_05 950600.00 1940720.00 + J_06_06 950720.00 1940720.00 + J_06_07 950840.00 1940720.00 + J_06_08 950960.00 1940720.00 + J_06_09 951080.00 1940720.00 + J_06_10 951200.00 1940720.00 + J_06_11 951320.00 1940720.00 + J_06_12 951440.00 1940720.00 + J_06_13 951560.00 1940720.00 + J_06_14 951680.00 1940720.00 + J_06_15 951800.00 1940720.00 + J_06_16 951920.00 1940720.00 + J_06_17 952040.00 1940720.00 + J_06_18 952160.00 1940720.00 + J_06_19 952280.00 1940720.00 + J_06_20 952400.00 1940720.00 + J_06_21 952520.00 1940720.00 + J_06_22 952640.00 1940720.00 + J_06_23 952760.00 1940720.00 + J_06_24 952880.00 1940720.00 + J_06_25 953000.00 1940720.00 + J_06_26 953120.00 1940720.00 + J_06_27 953240.00 1940720.00 + J_06_28 953360.00 1940720.00 + J_06_29 953480.00 1940720.00 + J_06_30 953600.00 1940720.00 + J_06_31 953720.00 1940720.00 + J_06_32 953840.00 1940720.00 + J_06_33 953960.00 1940720.00 + J_06_34 954080.00 1940720.00 + J_06_35 954200.00 1940720.00 + J_06_36 954320.00 1940720.00 + J_06_37 954440.00 1940720.00 + J_06_38 954560.00 1940720.00 + J_06_39 954680.00 1940720.00 + J_06_40 954800.00 1940720.00 + J_06_41 954920.00 1940720.00 + J_06_42 955040.00 1940720.00 + J_06_43 955160.00 1940720.00 + J_06_44 955280.00 1940720.00 + J_07_00 950000.00 1940840.00 + J_07_01 950120.00 1940840.00 + J_07_02 950240.00 1940840.00 + J_07_03 950360.00 1940840.00 + J_07_04 950480.00 1940840.00 + J_07_05 950600.00 1940840.00 + J_07_06 950720.00 1940840.00 + J_07_07 950840.00 1940840.00 + J_07_08 950960.00 1940840.00 + J_07_09 951080.00 1940840.00 + J_07_10 951200.00 1940840.00 + J_07_11 951320.00 1940840.00 + J_07_12 951440.00 1940840.00 + J_07_13 951560.00 1940840.00 + J_07_14 951680.00 1940840.00 + J_07_15 951800.00 1940840.00 + J_07_16 951920.00 1940840.00 + J_07_17 952040.00 1940840.00 + J_07_18 952160.00 1940840.00 + J_07_19 952280.00 1940840.00 + J_07_20 952400.00 1940840.00 + J_07_21 952520.00 1940840.00 + J_07_22 952640.00 1940840.00 + J_07_23 952760.00 1940840.00 + J_07_24 952880.00 1940840.00 + J_07_25 953000.00 1940840.00 + J_07_26 953120.00 1940840.00 + J_07_27 953240.00 1940840.00 + J_07_28 953360.00 1940840.00 + J_07_29 953480.00 1940840.00 + J_07_30 953600.00 1940840.00 + J_07_31 953720.00 1940840.00 + J_07_32 953840.00 1940840.00 + J_07_33 953960.00 1940840.00 + J_07_34 954080.00 1940840.00 + J_07_35 954200.00 1940840.00 + J_07_36 954320.00 1940840.00 + J_07_37 954440.00 1940840.00 + J_07_38 954560.00 1940840.00 + J_07_39 954680.00 1940840.00 + J_07_40 954800.00 1940840.00 + J_07_41 954920.00 1940840.00 + J_07_42 955040.00 1940840.00 + J_07_43 955160.00 1940840.00 + J_07_44 955280.00 1940840.00 + J_08_00 950000.00 1940960.00 + J_08_01 950120.00 1940960.00 + J_08_02 950240.00 1940960.00 + J_08_03 950360.00 1940960.00 + J_08_04 950480.00 1940960.00 + J_08_05 950600.00 1940960.00 + J_08_06 950720.00 1940960.00 + J_08_07 950840.00 1940960.00 + J_08_08 950960.00 1940960.00 + J_08_09 951080.00 1940960.00 + J_08_10 951200.00 1940960.00 + J_08_11 951320.00 1940960.00 + J_08_12 951440.00 1940960.00 + J_08_13 951560.00 1940960.00 + J_08_14 951680.00 1940960.00 + J_08_15 951800.00 1940960.00 + J_08_16 951920.00 1940960.00 + J_08_17 952040.00 1940960.00 + J_08_18 952160.00 1940960.00 + J_08_19 952280.00 1940960.00 + J_08_20 952400.00 1940960.00 + J_08_21 952520.00 1940960.00 + J_08_22 952640.00 1940960.00 + J_08_23 952760.00 1940960.00 + J_08_24 952880.00 1940960.00 + J_08_25 953000.00 1940960.00 + J_08_26 953120.00 1940960.00 + J_08_27 953240.00 1940960.00 + J_08_28 953360.00 1940960.00 + J_08_29 953480.00 1940960.00 + J_08_30 953600.00 1940960.00 + J_08_31 953720.00 1940960.00 + J_08_32 953840.00 1940960.00 + J_08_33 953960.00 1940960.00 + J_08_34 954080.00 1940960.00 + J_08_35 954200.00 1940960.00 + J_08_36 954320.00 1940960.00 + J_08_37 954440.00 1940960.00 + J_08_38 954560.00 1940960.00 + J_08_39 954680.00 1940960.00 + J_08_40 954800.00 1940960.00 + J_08_41 954920.00 1940960.00 + J_08_42 955040.00 1940960.00 + J_08_43 955160.00 1940960.00 + J_08_44 955280.00 1940960.00 + J_09_00 950000.00 1941080.00 + J_09_01 950120.00 1941080.00 + J_09_02 950240.00 1941080.00 + J_09_03 950360.00 1941080.00 + J_09_04 950480.00 1941080.00 + J_09_05 950600.00 1941080.00 + J_09_06 950720.00 1941080.00 + J_09_07 950840.00 1941080.00 + J_09_08 950960.00 1941080.00 + J_09_09 951080.00 1941080.00 + J_09_10 951200.00 1941080.00 + J_09_11 951320.00 1941080.00 + J_09_12 951440.00 1941080.00 + J_09_13 951560.00 1941080.00 + J_09_14 951680.00 1941080.00 + J_09_15 951800.00 1941080.00 + J_09_16 951920.00 1941080.00 + J_09_17 952040.00 1941080.00 + J_09_18 952160.00 1941080.00 + J_09_19 952280.00 1941080.00 + J_09_20 952400.00 1941080.00 + J_09_21 952520.00 1941080.00 + J_09_22 952640.00 1941080.00 + J_09_23 952760.00 1941080.00 + J_09_24 952880.00 1941080.00 + J_09_25 953000.00 1941080.00 + J_09_26 953120.00 1941080.00 + J_09_27 953240.00 1941080.00 + J_09_28 953360.00 1941080.00 + J_09_29 953480.00 1941080.00 + J_09_30 953600.00 1941080.00 + J_09_31 953720.00 1941080.00 + J_09_32 953840.00 1941080.00 + J_09_33 953960.00 1941080.00 + J_09_34 954080.00 1941080.00 + J_09_35 954200.00 1941080.00 + J_09_36 954320.00 1941080.00 + J_09_37 954440.00 1941080.00 + J_09_38 954560.00 1941080.00 + J_09_39 954680.00 1941080.00 + J_09_40 954800.00 1941080.00 + J_09_41 954920.00 1941080.00 + J_09_42 955040.00 1941080.00 + J_09_43 955160.00 1941080.00 + J_09_44 955280.00 1941080.00 + J_10_00 950000.00 1941200.00 + J_10_01 950120.00 1941200.00 + J_10_02 950240.00 1941200.00 + J_10_03 950360.00 1941200.00 + J_10_04 950480.00 1941200.00 + J_10_05 950600.00 1941200.00 + J_10_06 950720.00 1941200.00 + J_10_07 950840.00 1941200.00 + J_10_08 950960.00 1941200.00 + J_10_09 951080.00 1941200.00 + J_10_10 951200.00 1941200.00 + J_10_11 951320.00 1941200.00 + J_10_12 951440.00 1941200.00 + J_10_13 951560.00 1941200.00 + J_10_14 951680.00 1941200.00 + J_10_15 951800.00 1941200.00 + J_10_16 951920.00 1941200.00 + J_10_17 952040.00 1941200.00 + J_10_18 952160.00 1941200.00 + J_10_19 952280.00 1941200.00 + J_10_20 952400.00 1941200.00 + J_10_21 952520.00 1941200.00 + J_10_22 952640.00 1941200.00 + J_10_23 952760.00 1941200.00 + J_10_24 952880.00 1941200.00 + J_10_25 953000.00 1941200.00 + J_10_26 953120.00 1941200.00 + J_10_27 953240.00 1941200.00 + J_10_28 953360.00 1941200.00 + J_10_29 953480.00 1941200.00 + J_10_30 953600.00 1941200.00 + J_10_31 953720.00 1941200.00 + J_10_32 953840.00 1941200.00 + J_10_33 953960.00 1941200.00 + J_10_34 954080.00 1941200.00 + J_10_35 954200.00 1941200.00 + J_10_36 954320.00 1941200.00 + J_10_37 954440.00 1941200.00 + J_10_38 954560.00 1941200.00 + J_10_39 954680.00 1941200.00 + J_10_40 954800.00 1941200.00 + J_10_41 954920.00 1941200.00 + J_10_42 955040.00 1941200.00 + J_10_43 955160.00 1941200.00 + J_10_44 955280.00 1941200.00 + J_11_00 950000.00 1941320.00 + J_11_01 950120.00 1941320.00 + J_11_02 950240.00 1941320.00 + J_11_03 950360.00 1941320.00 + J_11_04 950480.00 1941320.00 + J_11_05 950600.00 1941320.00 + J_11_06 950720.00 1941320.00 + J_11_07 950840.00 1941320.00 + J_11_08 950960.00 1941320.00 + J_11_09 951080.00 1941320.00 + J_11_10 951200.00 1941320.00 + J_11_11 951320.00 1941320.00 + J_11_12 951440.00 1941320.00 + J_11_13 951560.00 1941320.00 + J_11_14 951680.00 1941320.00 + J_11_15 951800.00 1941320.00 + J_11_16 951920.00 1941320.00 + J_11_17 952040.00 1941320.00 + J_11_18 952160.00 1941320.00 + J_11_19 952280.00 1941320.00 + J_11_20 952400.00 1941320.00 + J_11_21 952520.00 1941320.00 + J_11_22 952640.00 1941320.00 + J_11_23 952760.00 1941320.00 + J_11_24 952880.00 1941320.00 + J_11_25 953000.00 1941320.00 + J_11_26 953120.00 1941320.00 + J_11_27 953240.00 1941320.00 + J_11_28 953360.00 1941320.00 + J_11_29 953480.00 1941320.00 + J_11_30 953600.00 1941320.00 + J_11_31 953720.00 1941320.00 + J_11_32 953840.00 1941320.00 + J_11_33 953960.00 1941320.00 + J_11_34 954080.00 1941320.00 + J_11_35 954200.00 1941320.00 + J_11_36 954320.00 1941320.00 + J_11_37 954440.00 1941320.00 + J_11_38 954560.00 1941320.00 + J_11_39 954680.00 1941320.00 + J_11_40 954800.00 1941320.00 + J_11_41 954920.00 1941320.00 + J_11_42 955040.00 1941320.00 + J_11_43 955160.00 1941320.00 + J_11_44 955280.00 1941320.00 + J_12_00 950000.00 1941440.00 + J_12_01 950120.00 1941440.00 + J_12_02 950240.00 1941440.00 + J_12_03 950360.00 1941440.00 + J_12_04 950480.00 1941440.00 + J_12_05 950600.00 1941440.00 + J_12_06 950720.00 1941440.00 + J_12_07 950840.00 1941440.00 + J_12_08 950960.00 1941440.00 + J_12_09 951080.00 1941440.00 + J_12_10 951200.00 1941440.00 + J_12_11 951320.00 1941440.00 + J_12_12 951440.00 1941440.00 + J_12_13 951560.00 1941440.00 + J_12_14 951680.00 1941440.00 + J_12_15 951800.00 1941440.00 + J_12_16 951920.00 1941440.00 + J_12_17 952040.00 1941440.00 + J_12_18 952160.00 1941440.00 + J_12_19 952280.00 1941440.00 + J_12_20 952400.00 1941440.00 + J_12_21 952520.00 1941440.00 + J_12_22 952640.00 1941440.00 + J_12_23 952760.00 1941440.00 + J_12_24 952880.00 1941440.00 + J_12_25 953000.00 1941440.00 + J_12_26 953120.00 1941440.00 + J_12_27 953240.00 1941440.00 + J_12_28 953360.00 1941440.00 + J_12_29 953480.00 1941440.00 + J_12_30 953600.00 1941440.00 + J_12_31 953720.00 1941440.00 + J_12_32 953840.00 1941440.00 + J_12_33 953960.00 1941440.00 + J_12_34 954080.00 1941440.00 + J_12_35 954200.00 1941440.00 + J_12_36 954320.00 1941440.00 + J_12_37 954440.00 1941440.00 + J_12_38 954560.00 1941440.00 + J_12_39 954680.00 1941440.00 + J_12_40 954800.00 1941440.00 + J_12_41 954920.00 1941440.00 + J_12_42 955040.00 1941440.00 + J_12_43 955160.00 1941440.00 + J_12_44 955280.00 1941440.00 + J_13_00 950000.00 1941560.00 + J_13_01 950120.00 1941560.00 + J_13_02 950240.00 1941560.00 + J_13_03 950360.00 1941560.00 + J_13_04 950480.00 1941560.00 + J_13_05 950600.00 1941560.00 + J_13_06 950720.00 1941560.00 + J_13_07 950840.00 1941560.00 + J_13_08 950960.00 1941560.00 + J_13_09 951080.00 1941560.00 + J_13_10 951200.00 1941560.00 + J_13_11 951320.00 1941560.00 + J_13_12 951440.00 1941560.00 + J_13_13 951560.00 1941560.00 + J_13_14 951680.00 1941560.00 + J_13_15 951800.00 1941560.00 + J_13_16 951920.00 1941560.00 + J_13_17 952040.00 1941560.00 + J_13_18 952160.00 1941560.00 + J_13_19 952280.00 1941560.00 + J_13_20 952400.00 1941560.00 + J_13_21 952520.00 1941560.00 + J_13_22 952640.00 1941560.00 + J_13_23 952760.00 1941560.00 + J_13_24 952880.00 1941560.00 + J_13_25 953000.00 1941560.00 + J_13_26 953120.00 1941560.00 + J_13_27 953240.00 1941560.00 + J_13_28 953360.00 1941560.00 + J_13_29 953480.00 1941560.00 + J_13_30 953600.00 1941560.00 + J_13_31 953720.00 1941560.00 + J_13_32 953840.00 1941560.00 + J_13_33 953960.00 1941560.00 + J_13_34 954080.00 1941560.00 + J_13_35 954200.00 1941560.00 + J_13_36 954320.00 1941560.00 + J_13_37 954440.00 1941560.00 + J_13_38 954560.00 1941560.00 + J_13_39 954680.00 1941560.00 + J_13_40 954800.00 1941560.00 + J_13_41 954920.00 1941560.00 + J_13_42 955040.00 1941560.00 + J_13_43 955160.00 1941560.00 + J_13_44 955280.00 1941560.00 + J_14_00 950000.00 1941680.00 + J_14_01 950120.00 1941680.00 + J_14_02 950240.00 1941680.00 + J_14_03 950360.00 1941680.00 + J_14_04 950480.00 1941680.00 + J_14_05 950600.00 1941680.00 + J_14_06 950720.00 1941680.00 + J_14_07 950840.00 1941680.00 + J_14_08 950960.00 1941680.00 + J_14_09 951080.00 1941680.00 + J_14_10 951200.00 1941680.00 + J_14_11 951320.00 1941680.00 + J_14_12 951440.00 1941680.00 + J_14_13 951560.00 1941680.00 + J_14_14 951680.00 1941680.00 + J_14_15 951800.00 1941680.00 + J_14_16 951920.00 1941680.00 + J_14_17 952040.00 1941680.00 + J_14_18 952160.00 1941680.00 + J_14_19 952280.00 1941680.00 + J_14_20 952400.00 1941680.00 + J_14_21 952520.00 1941680.00 + J_14_22 952640.00 1941680.00 + J_14_23 952760.00 1941680.00 + J_14_24 952880.00 1941680.00 + J_14_25 953000.00 1941680.00 + J_14_26 953120.00 1941680.00 + J_14_27 953240.00 1941680.00 + J_14_28 953360.00 1941680.00 + J_14_29 953480.00 1941680.00 + J_14_30 953600.00 1941680.00 + J_14_31 953720.00 1941680.00 + J_14_32 953840.00 1941680.00 + J_14_33 953960.00 1941680.00 + J_14_34 954080.00 1941680.00 + J_14_35 954200.00 1941680.00 + J_14_36 954320.00 1941680.00 + J_14_37 954440.00 1941680.00 + J_14_38 954560.00 1941680.00 + J_14_39 954680.00 1941680.00 + J_14_40 954800.00 1941680.00 + J_14_41 954920.00 1941680.00 + J_14_42 955040.00 1941680.00 + J_14_43 955160.00 1941680.00 + J_14_44 955280.00 1941680.00 + J_15_00 950000.00 1941800.00 + J_15_01 950120.00 1941800.00 + J_15_02 950240.00 1941800.00 + J_15_03 950360.00 1941800.00 + J_15_04 950480.00 1941800.00 + J_15_05 950600.00 1941800.00 + J_15_06 950720.00 1941800.00 + J_15_07 950840.00 1941800.00 + J_15_08 950960.00 1941800.00 + J_15_09 951080.00 1941800.00 + J_15_10 951200.00 1941800.00 + J_15_11 951320.00 1941800.00 + J_15_12 951440.00 1941800.00 + J_15_13 951560.00 1941800.00 + J_15_14 951680.00 1941800.00 + J_15_15 951800.00 1941800.00 + J_15_16 951920.00 1941800.00 + J_15_17 952040.00 1941800.00 + J_15_18 952160.00 1941800.00 + J_15_19 952280.00 1941800.00 + J_15_20 952400.00 1941800.00 + J_15_21 952520.00 1941800.00 + J_15_22 952640.00 1941800.00 + J_15_23 952760.00 1941800.00 + J_15_24 952880.00 1941800.00 + J_15_25 953000.00 1941800.00 + J_15_26 953120.00 1941800.00 + J_15_27 953240.00 1941800.00 + J_15_28 953360.00 1941800.00 + J_15_29 953480.00 1941800.00 + J_15_30 953600.00 1941800.00 + J_15_31 953720.00 1941800.00 + J_15_32 953840.00 1941800.00 + J_15_33 953960.00 1941800.00 + J_15_34 954080.00 1941800.00 + J_15_35 954200.00 1941800.00 + J_15_36 954320.00 1941800.00 + J_15_37 954440.00 1941800.00 + J_15_38 954560.00 1941800.00 + J_15_39 954680.00 1941800.00 + J_15_40 954800.00 1941800.00 + J_15_41 954920.00 1941800.00 + J_15_42 955040.00 1941800.00 + J_15_43 955160.00 1941800.00 + J_15_44 955280.00 1941800.00 + J_16_00 950000.00 1941920.00 + J_16_01 950120.00 1941920.00 + J_16_02 950240.00 1941920.00 + J_16_03 950360.00 1941920.00 + J_16_04 950480.00 1941920.00 + J_16_05 950600.00 1941920.00 + J_16_06 950720.00 1941920.00 + J_16_07 950840.00 1941920.00 + J_16_08 950960.00 1941920.00 + J_16_09 951080.00 1941920.00 + J_16_10 951200.00 1941920.00 + J_16_11 951320.00 1941920.00 + J_16_12 951440.00 1941920.00 + J_16_13 951560.00 1941920.00 + J_16_14 951680.00 1941920.00 + J_16_15 951800.00 1941920.00 + J_16_16 951920.00 1941920.00 + J_16_17 952040.00 1941920.00 + J_16_18 952160.00 1941920.00 + J_16_19 952280.00 1941920.00 + J_16_20 952400.00 1941920.00 + J_16_21 952520.00 1941920.00 + J_16_22 952640.00 1941920.00 + J_16_23 952760.00 1941920.00 + J_16_24 952880.00 1941920.00 + J_16_25 953000.00 1941920.00 + J_16_26 953120.00 1941920.00 + J_16_27 953240.00 1941920.00 + J_16_28 953360.00 1941920.00 + J_16_29 953480.00 1941920.00 + J_16_30 953600.00 1941920.00 + J_16_31 953720.00 1941920.00 + J_16_32 953840.00 1941920.00 + J_16_33 953960.00 1941920.00 + J_16_34 954080.00 1941920.00 + J_16_35 954200.00 1941920.00 + J_16_36 954320.00 1941920.00 + J_16_37 954440.00 1941920.00 + J_16_38 954560.00 1941920.00 + J_16_39 954680.00 1941920.00 + J_16_40 954800.00 1941920.00 + J_16_41 954920.00 1941920.00 + J_16_42 955040.00 1941920.00 + J_16_43 955160.00 1941920.00 + J_16_44 955280.00 1941920.00 + J_17_00 950000.00 1942040.00 + J_17_01 950120.00 1942040.00 + J_17_02 950240.00 1942040.00 + J_17_03 950360.00 1942040.00 + J_17_04 950480.00 1942040.00 + J_17_05 950600.00 1942040.00 + J_17_06 950720.00 1942040.00 + J_17_07 950840.00 1942040.00 + J_17_08 950960.00 1942040.00 + J_17_09 951080.00 1942040.00 + J_17_10 951200.00 1942040.00 + J_17_11 951320.00 1942040.00 + J_17_12 951440.00 1942040.00 + J_17_13 951560.00 1942040.00 + J_17_14 951680.00 1942040.00 + J_17_15 951800.00 1942040.00 + J_17_16 951920.00 1942040.00 + J_17_17 952040.00 1942040.00 + J_17_18 952160.00 1942040.00 + J_17_19 952280.00 1942040.00 + J_17_20 952400.00 1942040.00 + J_17_21 952520.00 1942040.00 + J_17_22 952640.00 1942040.00 + J_17_23 952760.00 1942040.00 + J_17_24 952880.00 1942040.00 + J_17_25 953000.00 1942040.00 + J_17_26 953120.00 1942040.00 + J_17_27 953240.00 1942040.00 + J_17_28 953360.00 1942040.00 + J_17_29 953480.00 1942040.00 + J_17_30 953600.00 1942040.00 + J_17_31 953720.00 1942040.00 + J_17_32 953840.00 1942040.00 + J_17_33 953960.00 1942040.00 + J_17_34 954080.00 1942040.00 + J_17_35 954200.00 1942040.00 + J_17_36 954320.00 1942040.00 + J_17_37 954440.00 1942040.00 + J_17_38 954560.00 1942040.00 + J_17_39 954680.00 1942040.00 + J_17_40 954800.00 1942040.00 + J_17_41 954920.00 1942040.00 + J_17_42 955040.00 1942040.00 + J_17_43 955160.00 1942040.00 + J_17_44 955280.00 1942040.00 + J_18_00 950000.00 1942160.00 + J_18_01 950120.00 1942160.00 + J_18_02 950240.00 1942160.00 + J_18_03 950360.00 1942160.00 + J_18_04 950480.00 1942160.00 + J_18_05 950600.00 1942160.00 + J_18_06 950720.00 1942160.00 + J_18_07 950840.00 1942160.00 + J_18_08 950960.00 1942160.00 + J_18_09 951080.00 1942160.00 + J_18_10 951200.00 1942160.00 + J_18_11 951320.00 1942160.00 + J_18_12 951440.00 1942160.00 + J_18_13 951560.00 1942160.00 + J_18_14 951680.00 1942160.00 + J_18_15 951800.00 1942160.00 + J_18_16 951920.00 1942160.00 + J_18_17 952040.00 1942160.00 + J_18_18 952160.00 1942160.00 + J_18_19 952280.00 1942160.00 + J_18_20 952400.00 1942160.00 + J_18_21 952520.00 1942160.00 + J_18_22 952640.00 1942160.00 + J_18_23 952760.00 1942160.00 + J_18_24 952880.00 1942160.00 + J_18_25 953000.00 1942160.00 + J_18_26 953120.00 1942160.00 + J_18_27 953240.00 1942160.00 + J_18_28 953360.00 1942160.00 + J_18_29 953480.00 1942160.00 + J_18_30 953600.00 1942160.00 + J_18_31 953720.00 1942160.00 + J_18_32 953840.00 1942160.00 + J_18_33 953960.00 1942160.00 + J_18_34 954080.00 1942160.00 + J_18_35 954200.00 1942160.00 + J_18_36 954320.00 1942160.00 + J_18_37 954440.00 1942160.00 + J_18_38 954560.00 1942160.00 + J_18_39 954680.00 1942160.00 + J_18_40 954800.00 1942160.00 + J_18_41 954920.00 1942160.00 + J_18_42 955040.00 1942160.00 + J_18_43 955160.00 1942160.00 + J_18_44 955280.00 1942160.00 + J_19_00 950000.00 1942280.00 + J_19_01 950120.00 1942280.00 + J_19_02 950240.00 1942280.00 + J_19_03 950360.00 1942280.00 + J_19_04 950480.00 1942280.00 + J_19_05 950600.00 1942280.00 + J_19_06 950720.00 1942280.00 + J_19_07 950840.00 1942280.00 + J_19_08 950960.00 1942280.00 + J_19_09 951080.00 1942280.00 + J_19_10 951200.00 1942280.00 + J_19_11 951320.00 1942280.00 + J_19_12 951440.00 1942280.00 + J_19_13 951560.00 1942280.00 + J_19_14 951680.00 1942280.00 + J_19_15 951800.00 1942280.00 + J_19_16 951920.00 1942280.00 + J_19_17 952040.00 1942280.00 + J_19_18 952160.00 1942280.00 + J_19_19 952280.00 1942280.00 + J_19_20 952400.00 1942280.00 + J_19_21 952520.00 1942280.00 + J_19_22 952640.00 1942280.00 + J_19_23 952760.00 1942280.00 + J_19_24 952880.00 1942280.00 + J_19_25 953000.00 1942280.00 + J_19_26 953120.00 1942280.00 + J_19_27 953240.00 1942280.00 + J_19_28 953360.00 1942280.00 + J_19_29 953480.00 1942280.00 + J_19_30 953600.00 1942280.00 + J_19_31 953720.00 1942280.00 + J_19_32 953840.00 1942280.00 + J_19_33 953960.00 1942280.00 + J_19_34 954080.00 1942280.00 + J_19_35 954200.00 1942280.00 + J_19_36 954320.00 1942280.00 + J_19_37 954440.00 1942280.00 + J_19_38 954560.00 1942280.00 + J_19_39 954680.00 1942280.00 + J_19_40 954800.00 1942280.00 + J_19_41 954920.00 1942280.00 + J_19_42 955040.00 1942280.00 + J_19_43 955160.00 1942280.00 + J_19_44 955280.00 1942280.00 + J_20_00 950000.00 1942400.00 + J_20_01 950120.00 1942400.00 + J_20_02 950240.00 1942400.00 + J_20_03 950360.00 1942400.00 + J_20_04 950480.00 1942400.00 + J_20_05 950600.00 1942400.00 + J_20_06 950720.00 1942400.00 + J_20_07 950840.00 1942400.00 + J_20_08 950960.00 1942400.00 + J_20_09 951080.00 1942400.00 + J_20_10 951200.00 1942400.00 + J_20_11 951320.00 1942400.00 + J_20_12 951440.00 1942400.00 + J_20_13 951560.00 1942400.00 + J_20_14 951680.00 1942400.00 + J_20_15 951800.00 1942400.00 + J_20_16 951920.00 1942400.00 + J_20_17 952040.00 1942400.00 + J_20_18 952160.00 1942400.00 + J_20_19 952280.00 1942400.00 + J_20_20 952400.00 1942400.00 + J_20_21 952520.00 1942400.00 + J_20_22 952640.00 1942400.00 + J_20_23 952760.00 1942400.00 + J_20_24 952880.00 1942400.00 + J_20_25 953000.00 1942400.00 + J_20_26 953120.00 1942400.00 + J_20_27 953240.00 1942400.00 + J_20_28 953360.00 1942400.00 + J_20_29 953480.00 1942400.00 + J_20_30 953600.00 1942400.00 + J_20_31 953720.00 1942400.00 + J_20_32 953840.00 1942400.00 + J_20_33 953960.00 1942400.00 + J_20_34 954080.00 1942400.00 + J_20_35 954200.00 1942400.00 + J_20_36 954320.00 1942400.00 + J_20_37 954440.00 1942400.00 + J_20_38 954560.00 1942400.00 + J_20_39 954680.00 1942400.00 + J_20_40 954800.00 1942400.00 + J_20_41 954920.00 1942400.00 + J_20_42 955040.00 1942400.00 + J_20_43 955160.00 1942400.00 + J_20_44 955280.00 1942400.00 + J_21_00 950000.00 1942520.00 + J_21_01 950120.00 1942520.00 + J_21_02 950240.00 1942520.00 + J_21_03 950360.00 1942520.00 + J_21_04 950480.00 1942520.00 + J_21_05 950600.00 1942520.00 + J_21_06 950720.00 1942520.00 + J_21_07 950840.00 1942520.00 + J_21_08 950960.00 1942520.00 + J_21_09 951080.00 1942520.00 + J_21_10 951200.00 1942520.00 + J_21_11 951320.00 1942520.00 + J_21_12 951440.00 1942520.00 + J_21_13 951560.00 1942520.00 + J_21_14 951680.00 1942520.00 + J_21_15 951800.00 1942520.00 + J_21_16 951920.00 1942520.00 + J_21_17 952040.00 1942520.00 + J_21_18 952160.00 1942520.00 + J_21_19 952280.00 1942520.00 + J_21_20 952400.00 1942520.00 + J_21_21 952520.00 1942520.00 + J_21_22 952640.00 1942520.00 + J_21_23 952760.00 1942520.00 + J_21_24 952880.00 1942520.00 + J_21_25 953000.00 1942520.00 + J_21_26 953120.00 1942520.00 + J_21_27 953240.00 1942520.00 + J_21_28 953360.00 1942520.00 + J_21_29 953480.00 1942520.00 + J_21_30 953600.00 1942520.00 + J_21_31 953720.00 1942520.00 + J_21_32 953840.00 1942520.00 + J_21_33 953960.00 1942520.00 + J_21_34 954080.00 1942520.00 + J_21_35 954200.00 1942520.00 + J_21_36 954320.00 1942520.00 + J_21_37 954440.00 1942520.00 + J_21_38 954560.00 1942520.00 + J_21_39 954680.00 1942520.00 + J_21_40 954800.00 1942520.00 + J_21_41 954920.00 1942520.00 + J_21_42 955040.00 1942520.00 + J_21_43 955160.00 1942520.00 + J_21_44 955280.00 1942520.00 + J_22_00 950000.00 1942640.00 + J_22_01 950120.00 1942640.00 + J_22_02 950240.00 1942640.00 + J_22_03 950360.00 1942640.00 + J_22_04 950480.00 1942640.00 + J_22_05 950600.00 1942640.00 + J_22_06 950720.00 1942640.00 + J_22_07 950840.00 1942640.00 + J_22_08 950960.00 1942640.00 + J_22_09 951080.00 1942640.00 + J_22_10 951200.00 1942640.00 + J_22_11 951320.00 1942640.00 + J_22_12 951440.00 1942640.00 + J_22_13 951560.00 1942640.00 + J_22_14 951680.00 1942640.00 + J_22_15 951800.00 1942640.00 + J_22_16 951920.00 1942640.00 + J_22_17 952040.00 1942640.00 + J_22_18 952160.00 1942640.00 + J_22_19 952280.00 1942640.00 + J_22_20 952400.00 1942640.00 + J_22_21 952520.00 1942640.00 + J_22_22 952640.00 1942640.00 + J_22_23 952760.00 1942640.00 + J_22_24 952880.00 1942640.00 + J_22_25 953000.00 1942640.00 + J_22_26 953120.00 1942640.00 + J_22_27 953240.00 1942640.00 + J_22_28 953360.00 1942640.00 + J_22_29 953480.00 1942640.00 + J_22_30 953600.00 1942640.00 + J_22_31 953720.00 1942640.00 + J_22_32 953840.00 1942640.00 + J_22_33 953960.00 1942640.00 + J_22_34 954080.00 1942640.00 + J_22_35 954200.00 1942640.00 + J_22_36 954320.00 1942640.00 + J_22_37 954440.00 1942640.00 + J_22_38 954560.00 1942640.00 + J_22_39 954680.00 1942640.00 + J_22_40 954800.00 1942640.00 + J_22_41 954920.00 1942640.00 + J_22_42 955040.00 1942640.00 + J_22_43 955160.00 1942640.00 + J_22_44 955280.00 1942640.00 + J_23_00 950000.00 1942760.00 + J_23_01 950120.00 1942760.00 + J_23_02 950240.00 1942760.00 + J_23_03 950360.00 1942760.00 + J_23_04 950480.00 1942760.00 + J_23_05 950600.00 1942760.00 + J_23_06 950720.00 1942760.00 + J_23_07 950840.00 1942760.00 + J_23_08 950960.00 1942760.00 + J_23_09 951080.00 1942760.00 + J_23_10 951200.00 1942760.00 + J_23_11 951320.00 1942760.00 + J_23_12 951440.00 1942760.00 + J_23_13 951560.00 1942760.00 + J_23_14 951680.00 1942760.00 + J_23_15 951800.00 1942760.00 + J_23_16 951920.00 1942760.00 + J_23_17 952040.00 1942760.00 + J_23_18 952160.00 1942760.00 + J_23_19 952280.00 1942760.00 + J_23_20 952400.00 1942760.00 + J_23_21 952520.00 1942760.00 + J_23_22 952640.00 1942760.00 + J_23_23 952760.00 1942760.00 + J_23_24 952880.00 1942760.00 + J_23_25 953000.00 1942760.00 + J_23_26 953120.00 1942760.00 + J_23_27 953240.00 1942760.00 + J_23_28 953360.00 1942760.00 + J_23_29 953480.00 1942760.00 + J_23_30 953600.00 1942760.00 + J_23_31 953720.00 1942760.00 + J_23_32 953840.00 1942760.00 + J_23_33 953960.00 1942760.00 + J_23_34 954080.00 1942760.00 + J_23_35 954200.00 1942760.00 + J_23_36 954320.00 1942760.00 + J_23_37 954440.00 1942760.00 + J_23_38 954560.00 1942760.00 + J_23_39 954680.00 1942760.00 + J_23_40 954800.00 1942760.00 + J_23_41 954920.00 1942760.00 + J_23_42 955040.00 1942760.00 + J_23_43 955160.00 1942760.00 + J_23_44 955280.00 1942760.00 + J_24_00 950000.00 1942880.00 + J_24_01 950120.00 1942880.00 + J_24_02 950240.00 1942880.00 + J_24_03 950360.00 1942880.00 + J_24_04 950480.00 1942880.00 + J_24_05 950600.00 1942880.00 + J_24_06 950720.00 1942880.00 + J_24_07 950840.00 1942880.00 + J_24_08 950960.00 1942880.00 + J_24_09 951080.00 1942880.00 + J_24_10 951200.00 1942880.00 + J_24_11 951320.00 1942880.00 + J_24_12 951440.00 1942880.00 + J_24_13 951560.00 1942880.00 + J_24_14 951680.00 1942880.00 + J_24_15 951800.00 1942880.00 + J_24_16 951920.00 1942880.00 + J_24_17 952040.00 1942880.00 + J_24_18 952160.00 1942880.00 + J_24_19 952280.00 1942880.00 + J_24_20 952400.00 1942880.00 + J_24_21 952520.00 1942880.00 + J_24_22 952640.00 1942880.00 + J_24_23 952760.00 1942880.00 + J_24_24 952880.00 1942880.00 + J_24_25 953000.00 1942880.00 + J_24_26 953120.00 1942880.00 + J_24_27 953240.00 1942880.00 + J_24_28 953360.00 1942880.00 + J_24_29 953480.00 1942880.00 + J_24_30 953600.00 1942880.00 + J_24_31 953720.00 1942880.00 + J_24_32 953840.00 1942880.00 + J_24_33 953960.00 1942880.00 + J_24_34 954080.00 1942880.00 + J_24_35 954200.00 1942880.00 + J_24_36 954320.00 1942880.00 + J_24_37 954440.00 1942880.00 + J_24_38 954560.00 1942880.00 + J_24_39 954680.00 1942880.00 + J_24_40 954800.00 1942880.00 + J_24_41 954920.00 1942880.00 + J_24_42 955040.00 1942880.00 + J_24_43 955160.00 1942880.00 + J_24_44 955280.00 1942880.00 + J_25_00 950000.00 1943000.00 + J_25_01 950120.00 1943000.00 + J_25_02 950240.00 1943000.00 + J_25_03 950360.00 1943000.00 + J_25_04 950480.00 1943000.00 + J_25_05 950600.00 1943000.00 + J_25_06 950720.00 1943000.00 + J_25_07 950840.00 1943000.00 + J_25_08 950960.00 1943000.00 + J_25_09 951080.00 1943000.00 + J_25_10 951200.00 1943000.00 + J_25_11 951320.00 1943000.00 + J_25_12 951440.00 1943000.00 + J_25_13 951560.00 1943000.00 + J_25_14 951680.00 1943000.00 + J_25_15 951800.00 1943000.00 + J_25_16 951920.00 1943000.00 + J_25_17 952040.00 1943000.00 + J_25_18 952160.00 1943000.00 + J_25_19 952280.00 1943000.00 + J_25_20 952400.00 1943000.00 + J_25_21 952520.00 1943000.00 + J_25_22 952640.00 1943000.00 + J_25_23 952760.00 1943000.00 + J_25_24 952880.00 1943000.00 + J_25_25 953000.00 1943000.00 + J_25_26 953120.00 1943000.00 + J_25_27 953240.00 1943000.00 + J_25_28 953360.00 1943000.00 + J_25_29 953480.00 1943000.00 + J_25_30 953600.00 1943000.00 + J_25_31 953720.00 1943000.00 + J_25_32 953840.00 1943000.00 + J_25_33 953960.00 1943000.00 + J_25_34 954080.00 1943000.00 + J_25_35 954200.00 1943000.00 + J_25_36 954320.00 1943000.00 + J_25_37 954440.00 1943000.00 + J_25_38 954560.00 1943000.00 + J_25_39 954680.00 1943000.00 + J_25_40 954800.00 1943000.00 + J_25_41 954920.00 1943000.00 + J_25_42 955040.00 1943000.00 + J_25_43 955160.00 1943000.00 + J_25_44 955280.00 1943000.00 + J_26_00 950000.00 1943120.00 + J_26_01 950120.00 1943120.00 + J_26_02 950240.00 1943120.00 + J_26_03 950360.00 1943120.00 + J_26_04 950480.00 1943120.00 + J_26_05 950600.00 1943120.00 + J_26_06 950720.00 1943120.00 + J_26_07 950840.00 1943120.00 + J_26_08 950960.00 1943120.00 + J_26_09 951080.00 1943120.00 + J_26_10 951200.00 1943120.00 + J_26_11 951320.00 1943120.00 + J_26_12 951440.00 1943120.00 + J_26_13 951560.00 1943120.00 + J_26_14 951680.00 1943120.00 + J_26_15 951800.00 1943120.00 + J_26_16 951920.00 1943120.00 + J_26_17 952040.00 1943120.00 + J_26_18 952160.00 1943120.00 + J_26_19 952280.00 1943120.00 + J_26_20 952400.00 1943120.00 + J_26_21 952520.00 1943120.00 + J_26_22 952640.00 1943120.00 + J_26_23 952760.00 1943120.00 + J_26_24 952880.00 1943120.00 + J_26_25 953000.00 1943120.00 + J_26_26 953120.00 1943120.00 + J_26_27 953240.00 1943120.00 + J_26_28 953360.00 1943120.00 + J_26_29 953480.00 1943120.00 + J_26_30 953600.00 1943120.00 + J_26_31 953720.00 1943120.00 + J_26_32 953840.00 1943120.00 + J_26_33 953960.00 1943120.00 + J_26_34 954080.00 1943120.00 + J_26_35 954200.00 1943120.00 + J_26_36 954320.00 1943120.00 + J_26_37 954440.00 1943120.00 + J_26_38 954560.00 1943120.00 + J_26_39 954680.00 1943120.00 + J_26_40 954800.00 1943120.00 + J_26_41 954920.00 1943120.00 + J_26_42 955040.00 1943120.00 + J_26_43 955160.00 1943120.00 + J_26_44 955280.00 1943120.00 + J_27_00 950000.00 1943240.00 + J_27_01 950120.00 1943240.00 + J_27_02 950240.00 1943240.00 + J_27_03 950360.00 1943240.00 + J_27_04 950480.00 1943240.00 + J_27_05 950600.00 1943240.00 + J_27_06 950720.00 1943240.00 + J_27_07 950840.00 1943240.00 + J_27_08 950960.00 1943240.00 + J_27_09 951080.00 1943240.00 + J_27_10 951200.00 1943240.00 + J_27_11 951320.00 1943240.00 + J_27_12 951440.00 1943240.00 + J_27_13 951560.00 1943240.00 + J_27_14 951680.00 1943240.00 + J_27_15 951800.00 1943240.00 + J_27_16 951920.00 1943240.00 + J_27_17 952040.00 1943240.00 + J_27_18 952160.00 1943240.00 + J_27_19 952280.00 1943240.00 + J_27_20 952400.00 1943240.00 + J_27_21 952520.00 1943240.00 + J_27_22 952640.00 1943240.00 + J_27_23 952760.00 1943240.00 + J_27_24 952880.00 1943240.00 + J_27_25 953000.00 1943240.00 + J_27_26 953120.00 1943240.00 + J_27_27 953240.00 1943240.00 + J_27_28 953360.00 1943240.00 + J_27_29 953480.00 1943240.00 + J_27_30 953600.00 1943240.00 + J_27_31 953720.00 1943240.00 + J_27_32 953840.00 1943240.00 + J_27_33 953960.00 1943240.00 + J_27_34 954080.00 1943240.00 + J_27_35 954200.00 1943240.00 + J_27_36 954320.00 1943240.00 + J_27_37 954440.00 1943240.00 + J_27_38 954560.00 1943240.00 + J_27_39 954680.00 1943240.00 + J_27_40 954800.00 1943240.00 + J_27_41 954920.00 1943240.00 + J_27_42 955040.00 1943240.00 + J_27_43 955160.00 1943240.00 + J_27_44 955280.00 1943240.00 + J_28_00 950000.00 1943360.00 + J_28_01 950120.00 1943360.00 + J_28_02 950240.00 1943360.00 + J_28_03 950360.00 1943360.00 + J_28_04 950480.00 1943360.00 + J_28_05 950600.00 1943360.00 + J_28_06 950720.00 1943360.00 + J_28_07 950840.00 1943360.00 + J_28_08 950960.00 1943360.00 + J_28_09 951080.00 1943360.00 + J_28_10 951200.00 1943360.00 + J_28_11 951320.00 1943360.00 + J_28_12 951440.00 1943360.00 + J_28_13 951560.00 1943360.00 + J_28_14 951680.00 1943360.00 + J_28_15 951800.00 1943360.00 + J_28_16 951920.00 1943360.00 + J_28_17 952040.00 1943360.00 + J_28_18 952160.00 1943360.00 + J_28_19 952280.00 1943360.00 + J_28_20 952400.00 1943360.00 + J_28_21 952520.00 1943360.00 + J_28_22 952640.00 1943360.00 + J_28_23 952760.00 1943360.00 + J_28_24 952880.00 1943360.00 + J_28_25 953000.00 1943360.00 + J_28_26 953120.00 1943360.00 + J_28_27 953240.00 1943360.00 + J_28_28 953360.00 1943360.00 + J_28_29 953480.00 1943360.00 + J_28_30 953600.00 1943360.00 + J_28_31 953720.00 1943360.00 + J_28_32 953840.00 1943360.00 + J_28_33 953960.00 1943360.00 + J_28_34 954080.00 1943360.00 + J_28_35 954200.00 1943360.00 + J_28_36 954320.00 1943360.00 + J_28_37 954440.00 1943360.00 + J_28_38 954560.00 1943360.00 + J_28_39 954680.00 1943360.00 + J_28_40 954800.00 1943360.00 + J_28_41 954920.00 1943360.00 + J_28_42 955040.00 1943360.00 + J_28_43 955160.00 1943360.00 + J_28_44 955280.00 1943360.00 + J_29_00 950000.00 1943480.00 + J_29_01 950120.00 1943480.00 + J_29_02 950240.00 1943480.00 + J_29_03 950360.00 1943480.00 + J_29_04 950480.00 1943480.00 + J_29_05 950600.00 1943480.00 + J_29_06 950720.00 1943480.00 + J_29_07 950840.00 1943480.00 + J_29_08 950960.00 1943480.00 + J_29_09 951080.00 1943480.00 + J_29_10 951200.00 1943480.00 + J_29_11 951320.00 1943480.00 + J_29_12 951440.00 1943480.00 + J_29_13 951560.00 1943480.00 + J_29_14 951680.00 1943480.00 + J_29_15 951800.00 1943480.00 + J_29_16 951920.00 1943480.00 + J_29_17 952040.00 1943480.00 + J_29_18 952160.00 1943480.00 + J_29_19 952280.00 1943480.00 + J_29_20 952400.00 1943480.00 + J_29_21 952520.00 1943480.00 + J_29_22 952640.00 1943480.00 + J_29_23 952760.00 1943480.00 + J_29_24 952880.00 1943480.00 + J_29_25 953000.00 1943480.00 + J_29_26 953120.00 1943480.00 + J_29_27 953240.00 1943480.00 + J_29_28 953360.00 1943480.00 + J_29_29 953480.00 1943480.00 + J_29_30 953600.00 1943480.00 + J_29_31 953720.00 1943480.00 + J_29_32 953840.00 1943480.00 + J_29_33 953960.00 1943480.00 + J_29_34 954080.00 1943480.00 + J_29_35 954200.00 1943480.00 + J_29_36 954320.00 1943480.00 + J_29_37 954440.00 1943480.00 + J_29_38 954560.00 1943480.00 + J_29_39 954680.00 1943480.00 + J_29_40 954800.00 1943480.00 + J_29_41 954920.00 1943480.00 + J_29_42 955040.00 1943480.00 + J_29_43 955160.00 1943480.00 + J_29_44 955280.00 1943480.00 + J_30_00 950000.00 1943600.00 + J_30_01 950120.00 1943600.00 + J_30_02 950240.00 1943600.00 + J_30_03 950360.00 1943600.00 + J_30_04 950480.00 1943600.00 + J_30_05 950600.00 1943600.00 + J_30_06 950720.00 1943600.00 + J_30_07 950840.00 1943600.00 + J_30_08 950960.00 1943600.00 + J_30_09 951080.00 1943600.00 + J_30_10 951200.00 1943600.00 + J_30_11 951320.00 1943600.00 + J_30_12 951440.00 1943600.00 + J_30_13 951560.00 1943600.00 + J_30_14 951680.00 1943600.00 + J_30_15 951800.00 1943600.00 + J_30_16 951920.00 1943600.00 + J_30_17 952040.00 1943600.00 + J_30_18 952160.00 1943600.00 + J_30_19 952280.00 1943600.00 + J_30_20 952400.00 1943600.00 + J_30_21 952520.00 1943600.00 + J_30_22 952640.00 1943600.00 + J_30_23 952760.00 1943600.00 + J_30_24 952880.00 1943600.00 + J_30_25 953000.00 1943600.00 + J_30_26 953120.00 1943600.00 + J_30_27 953240.00 1943600.00 + J_30_28 953360.00 1943600.00 + J_30_29 953480.00 1943600.00 + J_30_30 953600.00 1943600.00 + J_30_31 953720.00 1943600.00 + J_30_32 953840.00 1943600.00 + J_30_33 953960.00 1943600.00 + J_30_34 954080.00 1943600.00 + J_30_35 954200.00 1943600.00 + J_30_36 954320.00 1943600.00 + J_30_37 954440.00 1943600.00 + J_30_38 954560.00 1943600.00 + J_30_39 954680.00 1943600.00 + J_30_40 954800.00 1943600.00 + J_30_41 954920.00 1943600.00 + J_30_42 955040.00 1943600.00 + J_30_43 955160.00 1943600.00 + J_30_44 955280.00 1943600.00 + J_31_00 950000.00 1943720.00 + J_31_01 950120.00 1943720.00 + J_31_02 950240.00 1943720.00 + J_31_03 950360.00 1943720.00 + J_31_04 950480.00 1943720.00 + J_31_05 950600.00 1943720.00 + J_31_06 950720.00 1943720.00 + J_31_07 950840.00 1943720.00 + J_31_08 950960.00 1943720.00 + J_31_09 951080.00 1943720.00 + J_31_10 951200.00 1943720.00 + J_31_11 951320.00 1943720.00 + J_31_12 951440.00 1943720.00 + J_31_13 951560.00 1943720.00 + J_31_14 951680.00 1943720.00 + J_31_15 951800.00 1943720.00 + J_31_16 951920.00 1943720.00 + J_31_17 952040.00 1943720.00 + J_31_18 952160.00 1943720.00 + J_31_19 952280.00 1943720.00 + J_31_20 952400.00 1943720.00 + J_31_21 952520.00 1943720.00 + J_31_22 952640.00 1943720.00 + J_31_23 952760.00 1943720.00 + J_31_24 952880.00 1943720.00 + J_31_25 953000.00 1943720.00 + J_31_26 953120.00 1943720.00 + J_31_27 953240.00 1943720.00 + J_31_28 953360.00 1943720.00 + J_31_29 953480.00 1943720.00 + J_31_30 953600.00 1943720.00 + J_31_31 953720.00 1943720.00 + J_31_32 953840.00 1943720.00 + J_31_33 953960.00 1943720.00 + J_31_34 954080.00 1943720.00 + J_31_35 954200.00 1943720.00 + J_31_36 954320.00 1943720.00 + J_31_37 954440.00 1943720.00 + J_31_38 954560.00 1943720.00 + J_31_39 954680.00 1943720.00 + J_31_40 954800.00 1943720.00 + J_31_41 954920.00 1943720.00 + J_31_42 955040.00 1943720.00 + J_31_43 955160.00 1943720.00 + J_31_44 955280.00 1943720.00 + J_32_00 950000.00 1943840.00 + J_32_01 950120.00 1943840.00 + J_32_02 950240.00 1943840.00 + J_32_03 950360.00 1943840.00 + J_32_04 950480.00 1943840.00 + J_32_05 950600.00 1943840.00 + J_32_06 950720.00 1943840.00 + J_32_07 950840.00 1943840.00 + J_32_08 950960.00 1943840.00 + J_32_09 951080.00 1943840.00 + J_32_10 951200.00 1943840.00 + J_32_11 951320.00 1943840.00 + J_32_12 951440.00 1943840.00 + J_32_13 951560.00 1943840.00 + J_32_14 951680.00 1943840.00 + J_32_15 951800.00 1943840.00 + J_32_16 951920.00 1943840.00 + J_32_17 952040.00 1943840.00 + J_32_18 952160.00 1943840.00 + J_32_19 952280.00 1943840.00 + J_32_20 952400.00 1943840.00 + J_32_21 952520.00 1943840.00 + J_32_22 952640.00 1943840.00 + J_32_23 952760.00 1943840.00 + J_32_24 952880.00 1943840.00 + J_32_25 953000.00 1943840.00 + J_32_26 953120.00 1943840.00 + J_32_27 953240.00 1943840.00 + J_32_28 953360.00 1943840.00 + J_32_29 953480.00 1943840.00 + J_32_30 953600.00 1943840.00 + J_32_31 953720.00 1943840.00 + J_32_32 953840.00 1943840.00 + J_32_33 953960.00 1943840.00 + J_32_34 954080.00 1943840.00 + J_32_35 954200.00 1943840.00 + J_32_36 954320.00 1943840.00 + J_32_37 954440.00 1943840.00 + J_32_38 954560.00 1943840.00 + J_32_39 954680.00 1943840.00 + J_32_40 954800.00 1943840.00 + J_32_41 954920.00 1943840.00 + J_32_42 955040.00 1943840.00 + J_32_43 955160.00 1943840.00 + J_32_44 955280.00 1943840.00 + J_33_00 950000.00 1943960.00 + J_33_01 950120.00 1943960.00 + J_33_02 950240.00 1943960.00 + J_33_03 950360.00 1943960.00 + J_33_04 950480.00 1943960.00 + J_33_05 950600.00 1943960.00 + J_33_06 950720.00 1943960.00 + J_33_07 950840.00 1943960.00 + J_33_08 950960.00 1943960.00 + J_33_09 951080.00 1943960.00 + J_33_10 951200.00 1943960.00 + J_33_11 951320.00 1943960.00 + J_33_12 951440.00 1943960.00 + J_33_13 951560.00 1943960.00 + J_33_14 951680.00 1943960.00 + J_33_15 951800.00 1943960.00 + J_33_16 951920.00 1943960.00 + J_33_17 952040.00 1943960.00 + J_33_18 952160.00 1943960.00 + J_33_19 952280.00 1943960.00 + J_33_20 952400.00 1943960.00 + J_33_21 952520.00 1943960.00 + J_33_22 952640.00 1943960.00 + J_33_23 952760.00 1943960.00 + J_33_24 952880.00 1943960.00 + J_33_25 953000.00 1943960.00 + J_33_26 953120.00 1943960.00 + J_33_27 953240.00 1943960.00 + J_33_28 953360.00 1943960.00 + J_33_29 953480.00 1943960.00 + J_33_30 953600.00 1943960.00 + J_33_31 953720.00 1943960.00 + J_33_32 953840.00 1943960.00 + J_33_33 953960.00 1943960.00 + J_33_34 954080.00 1943960.00 + J_33_35 954200.00 1943960.00 + J_33_36 954320.00 1943960.00 + J_33_37 954440.00 1943960.00 + J_33_38 954560.00 1943960.00 + J_33_39 954680.00 1943960.00 + J_33_40 954800.00 1943960.00 + J_33_41 954920.00 1943960.00 + J_33_42 955040.00 1943960.00 + J_33_43 955160.00 1943960.00 + J_33_44 955280.00 1943960.00 + J_34_00 950000.00 1944080.00 + J_34_01 950120.00 1944080.00 + J_34_02 950240.00 1944080.00 + J_34_03 950360.00 1944080.00 + J_34_04 950480.00 1944080.00 + J_34_05 950600.00 1944080.00 + J_34_06 950720.00 1944080.00 + J_34_07 950840.00 1944080.00 + J_34_08 950960.00 1944080.00 + J_34_09 951080.00 1944080.00 + J_34_10 951200.00 1944080.00 + J_34_11 951320.00 1944080.00 + J_34_12 951440.00 1944080.00 + J_34_13 951560.00 1944080.00 + J_34_14 951680.00 1944080.00 + J_34_15 951800.00 1944080.00 + J_34_16 951920.00 1944080.00 + J_34_17 952040.00 1944080.00 + J_34_18 952160.00 1944080.00 + J_34_19 952280.00 1944080.00 + J_34_20 952400.00 1944080.00 + J_34_21 952520.00 1944080.00 + J_34_22 952640.00 1944080.00 + J_34_23 952760.00 1944080.00 + J_34_24 952880.00 1944080.00 + J_34_25 953000.00 1944080.00 + J_34_26 953120.00 1944080.00 + J_34_27 953240.00 1944080.00 + J_34_28 953360.00 1944080.00 + J_34_29 953480.00 1944080.00 + J_34_30 953600.00 1944080.00 + J_34_31 953720.00 1944080.00 + J_34_32 953840.00 1944080.00 + J_34_33 953960.00 1944080.00 + J_34_34 954080.00 1944080.00 + J_34_35 954200.00 1944080.00 + J_34_36 954320.00 1944080.00 + J_34_37 954440.00 1944080.00 + J_34_38 954560.00 1944080.00 + J_34_39 954680.00 1944080.00 + J_34_40 954800.00 1944080.00 + J_34_41 954920.00 1944080.00 + J_34_42 955040.00 1944080.00 + J_34_43 955160.00 1944080.00 + J_34_44 955280.00 1944080.00 + J_35_00 950000.00 1944200.00 + J_35_01 950120.00 1944200.00 + J_35_02 950240.00 1944200.00 + J_35_03 950360.00 1944200.00 + J_35_04 950480.00 1944200.00 + J_35_05 950600.00 1944200.00 + J_35_06 950720.00 1944200.00 + J_35_07 950840.00 1944200.00 + J_35_08 950960.00 1944200.00 + J_35_09 951080.00 1944200.00 + J_35_10 951200.00 1944200.00 + J_35_11 951320.00 1944200.00 + J_35_12 951440.00 1944200.00 + J_35_13 951560.00 1944200.00 + J_35_14 951680.00 1944200.00 + J_35_15 951800.00 1944200.00 + J_35_16 951920.00 1944200.00 + J_35_17 952040.00 1944200.00 + J_35_18 952160.00 1944200.00 + J_35_19 952280.00 1944200.00 + J_35_20 952400.00 1944200.00 + J_35_21 952520.00 1944200.00 + J_35_22 952640.00 1944200.00 + J_35_23 952760.00 1944200.00 + J_35_24 952880.00 1944200.00 + J_35_25 953000.00 1944200.00 + J_35_26 953120.00 1944200.00 + J_35_27 953240.00 1944200.00 + J_35_28 953360.00 1944200.00 + J_35_29 953480.00 1944200.00 + J_35_30 953600.00 1944200.00 + J_35_31 953720.00 1944200.00 + J_35_32 953840.00 1944200.00 + J_35_33 953960.00 1944200.00 + J_35_34 954080.00 1944200.00 + J_35_35 954200.00 1944200.00 + J_35_36 954320.00 1944200.00 + J_35_37 954440.00 1944200.00 + J_35_38 954560.00 1944200.00 + J_35_39 954680.00 1944200.00 + J_35_40 954800.00 1944200.00 + J_35_41 954920.00 1944200.00 + J_35_42 955040.00 1944200.00 + J_35_43 955160.00 1944200.00 + J_35_44 955280.00 1944200.00 + J_36_00 950000.00 1944320.00 + J_36_01 950120.00 1944320.00 + J_36_02 950240.00 1944320.00 + J_36_03 950360.00 1944320.00 + J_36_04 950480.00 1944320.00 + J_36_05 950600.00 1944320.00 + J_36_06 950720.00 1944320.00 + J_36_07 950840.00 1944320.00 + J_36_08 950960.00 1944320.00 + J_36_09 951080.00 1944320.00 + J_36_10 951200.00 1944320.00 + J_36_11 951320.00 1944320.00 + J_36_12 951440.00 1944320.00 + J_36_13 951560.00 1944320.00 + J_36_14 951680.00 1944320.00 + J_36_15 951800.00 1944320.00 + J_36_16 951920.00 1944320.00 + J_36_17 952040.00 1944320.00 + J_36_18 952160.00 1944320.00 + J_36_19 952280.00 1944320.00 + J_36_20 952400.00 1944320.00 + J_36_21 952520.00 1944320.00 + J_36_22 952640.00 1944320.00 + J_36_23 952760.00 1944320.00 + J_36_24 952880.00 1944320.00 + J_36_25 953000.00 1944320.00 + J_36_26 953120.00 1944320.00 + J_36_27 953240.00 1944320.00 + J_36_28 953360.00 1944320.00 + J_36_29 953480.00 1944320.00 + J_36_30 953600.00 1944320.00 + J_36_31 953720.00 1944320.00 + J_36_32 953840.00 1944320.00 + J_36_33 953960.00 1944320.00 + J_36_34 954080.00 1944320.00 + J_36_35 954200.00 1944320.00 + J_36_36 954320.00 1944320.00 + J_36_37 954440.00 1944320.00 + J_36_38 954560.00 1944320.00 + J_36_39 954680.00 1944320.00 + J_36_40 954800.00 1944320.00 + J_36_41 954920.00 1944320.00 + J_36_42 955040.00 1944320.00 + J_36_43 955160.00 1944320.00 + J_36_44 955280.00 1944320.00 + J_37_00 950000.00 1944440.00 + J_37_01 950120.00 1944440.00 + J_37_02 950240.00 1944440.00 + J_37_03 950360.00 1944440.00 + J_37_04 950480.00 1944440.00 + J_37_05 950600.00 1944440.00 + J_37_06 950720.00 1944440.00 + J_37_07 950840.00 1944440.00 + J_37_08 950960.00 1944440.00 + J_37_09 951080.00 1944440.00 + J_37_10 951200.00 1944440.00 + J_37_11 951320.00 1944440.00 + J_37_12 951440.00 1944440.00 + J_37_13 951560.00 1944440.00 + J_37_14 951680.00 1944440.00 + J_37_15 951800.00 1944440.00 + J_37_16 951920.00 1944440.00 + J_37_17 952040.00 1944440.00 + J_37_18 952160.00 1944440.00 + J_37_19 952280.00 1944440.00 + J_37_20 952400.00 1944440.00 + J_37_21 952520.00 1944440.00 + J_37_22 952640.00 1944440.00 + J_37_23 952760.00 1944440.00 + J_37_24 952880.00 1944440.00 + J_37_25 953000.00 1944440.00 + J_37_26 953120.00 1944440.00 + J_37_27 953240.00 1944440.00 + J_37_28 953360.00 1944440.00 + J_37_29 953480.00 1944440.00 + J_37_30 953600.00 1944440.00 + J_37_31 953720.00 1944440.00 + J_37_32 953840.00 1944440.00 + J_37_33 953960.00 1944440.00 + J_37_34 954080.00 1944440.00 + J_37_35 954200.00 1944440.00 + J_37_36 954320.00 1944440.00 + J_37_37 954440.00 1944440.00 + J_37_38 954560.00 1944440.00 + J_37_39 954680.00 1944440.00 + J_37_40 954800.00 1944440.00 + J_37_41 954920.00 1944440.00 + J_37_42 955040.00 1944440.00 + J_37_43 955160.00 1944440.00 + J_37_44 955280.00 1944440.00 + J_38_00 950000.00 1944560.00 + J_38_01 950120.00 1944560.00 + J_38_02 950240.00 1944560.00 + J_38_03 950360.00 1944560.00 + J_38_04 950480.00 1944560.00 + J_38_05 950600.00 1944560.00 + J_38_06 950720.00 1944560.00 + J_38_07 950840.00 1944560.00 + J_38_08 950960.00 1944560.00 + J_38_09 951080.00 1944560.00 + J_38_10 951200.00 1944560.00 + J_38_11 951320.00 1944560.00 + J_38_12 951440.00 1944560.00 + J_38_13 951560.00 1944560.00 + J_38_14 951680.00 1944560.00 + J_38_15 951800.00 1944560.00 + J_38_16 951920.00 1944560.00 + J_38_17 952040.00 1944560.00 + J_38_18 952160.00 1944560.00 + J_38_19 952280.00 1944560.00 + J_38_20 952400.00 1944560.00 + J_38_21 952520.00 1944560.00 + J_38_22 952640.00 1944560.00 + J_38_23 952760.00 1944560.00 + J_38_24 952880.00 1944560.00 + J_38_25 953000.00 1944560.00 + J_38_26 953120.00 1944560.00 + J_38_27 953240.00 1944560.00 + J_38_28 953360.00 1944560.00 + J_38_29 953480.00 1944560.00 + J_38_30 953600.00 1944560.00 + J_38_31 953720.00 1944560.00 + J_38_32 953840.00 1944560.00 + J_38_33 953960.00 1944560.00 + J_38_34 954080.00 1944560.00 + J_38_35 954200.00 1944560.00 + J_38_36 954320.00 1944560.00 + J_38_37 954440.00 1944560.00 + J_38_38 954560.00 1944560.00 + J_38_39 954680.00 1944560.00 + J_38_40 954800.00 1944560.00 + J_38_41 954920.00 1944560.00 + J_38_42 955040.00 1944560.00 + J_38_43 955160.00 1944560.00 + J_38_44 955280.00 1944560.00 + J_39_00 950000.00 1944680.00 + J_39_01 950120.00 1944680.00 + J_39_02 950240.00 1944680.00 + J_39_03 950360.00 1944680.00 + J_39_04 950480.00 1944680.00 + J_39_05 950600.00 1944680.00 + J_39_06 950720.00 1944680.00 + J_39_07 950840.00 1944680.00 + J_39_08 950960.00 1944680.00 + J_39_09 951080.00 1944680.00 + J_39_10 951200.00 1944680.00 + J_39_11 951320.00 1944680.00 + J_39_12 951440.00 1944680.00 + J_39_13 951560.00 1944680.00 + J_39_14 951680.00 1944680.00 + J_39_15 951800.00 1944680.00 + J_39_16 951920.00 1944680.00 + J_39_17 952040.00 1944680.00 + J_39_18 952160.00 1944680.00 + J_39_19 952280.00 1944680.00 + J_39_20 952400.00 1944680.00 + J_39_21 952520.00 1944680.00 + J_39_22 952640.00 1944680.00 + J_39_23 952760.00 1944680.00 + J_39_24 952880.00 1944680.00 + J_39_25 953000.00 1944680.00 + J_39_26 953120.00 1944680.00 + J_39_27 953240.00 1944680.00 + J_39_28 953360.00 1944680.00 + J_39_29 953480.00 1944680.00 + J_39_30 953600.00 1944680.00 + J_39_31 953720.00 1944680.00 + J_39_32 953840.00 1944680.00 + J_39_33 953960.00 1944680.00 + J_39_34 954080.00 1944680.00 + J_39_35 954200.00 1944680.00 + J_39_36 954320.00 1944680.00 + J_39_37 954440.00 1944680.00 + J_39_38 954560.00 1944680.00 + J_39_39 954680.00 1944680.00 + J_39_40 954800.00 1944680.00 + J_39_41 954920.00 1944680.00 + J_39_42 955040.00 1944680.00 + J_39_43 955160.00 1944680.00 + J_39_44 955280.00 1944680.00 + J_40_00 950000.00 1944800.00 + J_40_01 950120.00 1944800.00 + J_40_02 950240.00 1944800.00 + J_40_03 950360.00 1944800.00 + J_40_04 950480.00 1944800.00 + J_40_05 950600.00 1944800.00 + J_40_06 950720.00 1944800.00 + J_40_07 950840.00 1944800.00 + J_40_08 950960.00 1944800.00 + J_40_09 951080.00 1944800.00 + J_40_10 951200.00 1944800.00 + J_40_11 951320.00 1944800.00 + J_40_12 951440.00 1944800.00 + J_40_13 951560.00 1944800.00 + J_40_14 951680.00 1944800.00 + J_40_15 951800.00 1944800.00 + J_40_16 951920.00 1944800.00 + J_40_17 952040.00 1944800.00 + J_40_18 952160.00 1944800.00 + J_40_19 952280.00 1944800.00 + J_40_20 952400.00 1944800.00 + J_40_21 952520.00 1944800.00 + J_40_22 952640.00 1944800.00 + J_40_23 952760.00 1944800.00 + J_40_24 952880.00 1944800.00 + J_40_25 953000.00 1944800.00 + J_40_26 953120.00 1944800.00 + J_40_27 953240.00 1944800.00 + J_40_28 953360.00 1944800.00 + J_40_29 953480.00 1944800.00 + J_40_30 953600.00 1944800.00 + J_40_31 953720.00 1944800.00 + J_40_32 953840.00 1944800.00 + J_40_33 953960.00 1944800.00 + J_40_34 954080.00 1944800.00 + J_40_35 954200.00 1944800.00 + J_40_36 954320.00 1944800.00 + J_40_37 954440.00 1944800.00 + J_40_38 954560.00 1944800.00 + J_40_39 954680.00 1944800.00 + J_40_40 954800.00 1944800.00 + J_40_41 954920.00 1944800.00 + J_40_42 955040.00 1944800.00 + J_40_43 955160.00 1944800.00 + J_40_44 955280.00 1944800.00 + J_41_00 950000.00 1944920.00 + J_41_01 950120.00 1944920.00 + J_41_02 950240.00 1944920.00 + J_41_03 950360.00 1944920.00 + J_41_04 950480.00 1944920.00 + J_41_05 950600.00 1944920.00 + J_41_06 950720.00 1944920.00 + J_41_07 950840.00 1944920.00 + J_41_08 950960.00 1944920.00 + J_41_09 951080.00 1944920.00 + J_41_10 951200.00 1944920.00 + J_41_11 951320.00 1944920.00 + J_41_12 951440.00 1944920.00 + J_41_13 951560.00 1944920.00 + J_41_14 951680.00 1944920.00 + J_41_15 951800.00 1944920.00 + J_41_16 951920.00 1944920.00 + J_41_17 952040.00 1944920.00 + J_41_18 952160.00 1944920.00 + J_41_19 952280.00 1944920.00 + J_41_20 952400.00 1944920.00 + J_41_21 952520.00 1944920.00 + J_41_22 952640.00 1944920.00 + J_41_23 952760.00 1944920.00 + J_41_24 952880.00 1944920.00 + J_41_25 953000.00 1944920.00 + J_41_26 953120.00 1944920.00 + J_41_27 953240.00 1944920.00 + J_41_28 953360.00 1944920.00 + J_41_29 953480.00 1944920.00 + J_41_30 953600.00 1944920.00 + J_41_31 953720.00 1944920.00 + J_41_32 953840.00 1944920.00 + J_41_33 953960.00 1944920.00 + J_41_34 954080.00 1944920.00 + J_41_35 954200.00 1944920.00 + J_41_36 954320.00 1944920.00 + J_41_37 954440.00 1944920.00 + J_41_38 954560.00 1944920.00 + J_41_39 954680.00 1944920.00 + J_41_40 954800.00 1944920.00 + J_41_41 954920.00 1944920.00 + J_41_42 955040.00 1944920.00 + J_41_43 955160.00 1944920.00 + J_41_44 955280.00 1944920.00 + J_42_00 950000.00 1945040.00 + J_42_01 950120.00 1945040.00 + J_42_02 950240.00 1945040.00 + J_42_03 950360.00 1945040.00 + J_42_04 950480.00 1945040.00 + J_42_05 950600.00 1945040.00 + J_42_06 950720.00 1945040.00 + J_42_07 950840.00 1945040.00 + J_42_08 950960.00 1945040.00 + J_42_09 951080.00 1945040.00 + J_42_10 951200.00 1945040.00 + J_42_11 951320.00 1945040.00 + J_42_12 951440.00 1945040.00 + J_42_13 951560.00 1945040.00 + J_42_14 951680.00 1945040.00 + J_42_15 951800.00 1945040.00 + J_42_16 951920.00 1945040.00 + J_42_17 952040.00 1945040.00 + J_42_18 952160.00 1945040.00 + J_42_19 952280.00 1945040.00 + J_42_20 952400.00 1945040.00 + J_42_21 952520.00 1945040.00 + J_42_22 952640.00 1945040.00 + J_42_23 952760.00 1945040.00 + J_42_24 952880.00 1945040.00 + J_42_25 953000.00 1945040.00 + J_42_26 953120.00 1945040.00 + J_42_27 953240.00 1945040.00 + J_42_28 953360.00 1945040.00 + J_42_29 953480.00 1945040.00 + J_42_30 953600.00 1945040.00 + J_42_31 953720.00 1945040.00 + J_42_32 953840.00 1945040.00 + J_42_33 953960.00 1945040.00 + J_42_34 954080.00 1945040.00 + J_42_35 954200.00 1945040.00 + J_42_36 954320.00 1945040.00 + J_42_37 954440.00 1945040.00 + J_42_38 954560.00 1945040.00 + J_42_39 954680.00 1945040.00 + J_42_40 954800.00 1945040.00 + J_42_41 954920.00 1945040.00 + J_42_42 955040.00 1945040.00 + J_42_43 955160.00 1945040.00 + J_42_44 955280.00 1945040.00 + J_43_00 950000.00 1945160.00 + J_43_01 950120.00 1945160.00 + J_43_02 950240.00 1945160.00 + J_43_03 950360.00 1945160.00 + J_43_04 950480.00 1945160.00 + J_43_05 950600.00 1945160.00 + J_43_06 950720.00 1945160.00 + J_43_07 950840.00 1945160.00 + J_43_08 950960.00 1945160.00 + J_43_09 951080.00 1945160.00 + J_43_10 951200.00 1945160.00 + J_43_11 951320.00 1945160.00 + J_43_12 951440.00 1945160.00 + J_43_13 951560.00 1945160.00 + J_43_14 951680.00 1945160.00 + J_43_15 951800.00 1945160.00 + J_43_16 951920.00 1945160.00 + J_43_17 952040.00 1945160.00 + J_43_18 952160.00 1945160.00 + J_43_19 952280.00 1945160.00 + J_43_20 952400.00 1945160.00 + J_43_21 952520.00 1945160.00 + J_43_22 952640.00 1945160.00 + J_43_23 952760.00 1945160.00 + J_43_24 952880.00 1945160.00 + J_43_25 953000.00 1945160.00 + J_43_26 953120.00 1945160.00 + J_43_27 953240.00 1945160.00 + J_43_28 953360.00 1945160.00 + J_43_29 953480.00 1945160.00 + J_43_30 953600.00 1945160.00 + J_43_31 953720.00 1945160.00 + J_43_32 953840.00 1945160.00 + J_43_33 953960.00 1945160.00 + J_43_34 954080.00 1945160.00 + J_43_35 954200.00 1945160.00 + J_43_36 954320.00 1945160.00 + J_43_37 954440.00 1945160.00 + J_43_38 954560.00 1945160.00 + J_43_39 954680.00 1945160.00 + J_43_40 954800.00 1945160.00 + J_43_41 954920.00 1945160.00 + J_43_42 955040.00 1945160.00 + J_43_43 955160.00 1945160.00 + J_43_44 955280.00 1945160.00 + J_44_00 950000.00 1945280.00 + J_44_01 950120.00 1945280.00 + J_44_02 950240.00 1945280.00 + J_44_03 950360.00 1945280.00 + J_44_04 950480.00 1945280.00 + J_44_05 950600.00 1945280.00 + J_44_06 950720.00 1945280.00 + J_44_07 950840.00 1945280.00 + J_44_08 950960.00 1945280.00 + J_44_09 951080.00 1945280.00 + J_44_10 951200.00 1945280.00 + J_44_11 951320.00 1945280.00 + J_44_12 951440.00 1945280.00 + J_44_13 951560.00 1945280.00 + J_44_14 951680.00 1945280.00 + J_44_15 951800.00 1945280.00 + J_44_16 951920.00 1945280.00 + J_44_17 952040.00 1945280.00 + J_44_18 952160.00 1945280.00 + J_44_19 952280.00 1945280.00 + J_44_20 952400.00 1945280.00 + J_44_21 952520.00 1945280.00 + J_44_22 952640.00 1945280.00 + J_44_23 952760.00 1945280.00 + J_44_24 952880.00 1945280.00 + J_44_25 953000.00 1945280.00 + J_44_26 953120.00 1945280.00 + J_44_27 953240.00 1945280.00 + J_44_28 953360.00 1945280.00 + J_44_29 953480.00 1945280.00 + J_44_30 953600.00 1945280.00 + J_44_31 953720.00 1945280.00 + J_44_32 953840.00 1945280.00 + J_44_33 953960.00 1945280.00 + J_44_34 954080.00 1945280.00 + J_44_35 954200.00 1945280.00 + J_44_36 954320.00 1945280.00 + J_44_37 954440.00 1945280.00 + J_44_38 954560.00 1945280.00 + J_44_39 954680.00 1945280.00 + J_44_40 954800.00 1945280.00 + J_44_41 954920.00 1945280.00 + J_44_42 955040.00 1945280.00 + J_44_43 955160.00 1945280.00 + J_44_44 955280.00 1945280.00 + +[END] diff --git a/public/test_water_network.zip b/public/test_water_network.zip new file mode 100644 index 0000000..9b7382a Binary files /dev/null and b/public/test_water_network.zip differ diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..f89c130 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +# Run from the repo root: backend.* (API tests) and blocksys.* (its own tests +# import it as a top-level package) must both be importable. +pythonpath = . backend +testpaths = backend/tests backend/blocksys/tests +addopts = -p no:cacheprovider diff --git a/src/AddElementModal.tsx b/src/AddElementModal.tsx new file mode 100644 index 0000000..b8d50f1 --- /dev/null +++ b/src/AddElementModal.tsx @@ -0,0 +1,402 @@ +import React, { useState } from 'react'; +import { X, Plus } from 'lucide-react'; +import type { NetworkModel } from './inpModel'; +import { + addNode, + addPipe, + addPump, + addValve, + addCurve, + addRule, + addControl, + updatePattern, + getNextNodeId, + getNextPipeId, + getNextPumpId, + getNextValveId, + getNextCurveId, +} from './inpModel'; +import type { TableCategory } from './FullAttributeTableModal'; + +interface AddElementModalProps { + category: TableCategory; + model: NetworkModel; + onAdd: (newModel: NetworkModel) => void; + onClose: () => void; +} + +const LABELS: Partial> = { + junctions: '절점 (Junction)', + pipes: '관로 (Pipe)', + pumps: '펌프 (Pump)', + valves: '밸브 (Valve)', + reservoirs: '저수지 (Reservoir)', + tanks: '탱크 (Tank)', + patterns: '패턴 (Pattern)', + controls: '컨트롤 (Control)', + rules: '규칙 (Rule)', + curves: '펌프 곡선 (Curve)', +}; + +function averageExistingCoords(model: NetworkModel): { x: number; y: number } { + const nodes = Object.values(model.nodes); + if (nodes.length === 0) return { x: 0, y: 0 }; + const sum = nodes.reduce((acc, n) => ({ x: acc.x + n.x, y: acc.y + n.y }), { x: 0, y: 0 }); + return { x: Math.round((sum.x / nodes.length) * 100) / 100, y: Math.round((sum.y / nodes.length) * 100) / 100 }; +} + +export const AddElementModal: React.FC = ({ category, model, onAdd, onClose }) => { + const [error, setError] = useState(null); + const nodeIds = Object.keys(model.nodes); + const linkIds = [...Object.keys(model.pipes), ...Object.keys(model.pumps), ...Object.keys(model.valves || {})]; + const defaultCoords = averageExistingCoords(model); + + // Shared field state (only relevant fields are rendered/used per category) + const [id, setId] = useState(''); + const [node1, setNode1] = useState(nodeIds[0] || ''); + const [node2, setNode2] = useState(nodeIds[1] || nodeIds[0] || ''); + const [elevation, setElevation] = useState('0'); + const [demand, setDemand] = useState('0'); + const [pattern, setPattern] = useState(''); + const [head, setHead] = useState('0'); + const [initLevel, setInitLevel] = useState('5'); + const [minLevel, setMinLevel] = useState('0'); + const [maxLevel, setMaxLevel] = useState('10'); + const [diameter, setDiameter] = useState('10'); + const [minVol, setMinVol] = useState('0'); + const [length, setLength] = useState('100'); + const [pipeDiameter, setPipeDiameter] = useState('200'); + const [roughness, setRoughness] = useState('100'); + const [minorLoss, setMinorLoss] = useState('0'); + const [status, setStatus] = useState('Open'); + const [parameters, setParameters] = useState(''); + const [valveType, setValveType] = useState('PRV'); + const [valveSetting, setValveSetting] = useState('0'); + const [multipliers, setMultipliers] = useState('1.0 1.0 1.0 1.0 1.0 1.0'); + const [controlLinkId, setControlLinkId] = useState(linkIds[0] || ''); + const [controlStatus, setControlStatus] = useState('Open'); + const [ruleText, setRuleText] = useState('RULE 1\nIF NODE J1 PRESSURE BELOW 10\nTHEN PIPE P1 STATUS = OPEN\nPRIORITY 1'); + const [curvePoints, setCurvePoints] = useState('0 50\n50 40\n100 20'); + + const title = LABELS[category] || category; + + const handleSubmit = () => { + setError(null); + try { + if (category === 'junctions') { + const trimmedId = id.trim(); + if (!trimmedId) throw new Error('절점 ID를 입력하세요.'); + if (model.nodes[trimmedId]) throw new Error(`이미 존재하는 절점 ID입니다: ${trimmedId}`); + const next = addNode(model, { + id: trimmedId, + type: 'JUNCTION', + x: defaultCoords.x, + y: defaultCoords.y, + elevation: parseFloat(elevation) || 0, + demand: parseFloat(demand) || 0, + pattern: pattern.trim() || undefined, + }); + onAdd(next); + } else if (category === 'reservoirs') { + const trimmedId = id.trim(); + if (!trimmedId) throw new Error('저수지 ID를 입력하세요.'); + if (model.nodes[trimmedId]) throw new Error(`이미 존재하는 절점 ID입니다: ${trimmedId}`); + const next = addNode(model, { + id: trimmedId, + type: 'RESERVOIR', + x: defaultCoords.x, + y: defaultCoords.y, + elevation: parseFloat(head) || 0, + head: parseFloat(head) || 0, + pattern: pattern.trim() || undefined, + }); + onAdd(next); + } else if (category === 'tanks') { + const trimmedId = id.trim(); + if (!trimmedId) throw new Error('탱크 ID를 입력하세요.'); + if (model.nodes[trimmedId]) throw new Error(`이미 존재하는 절점 ID입니다: ${trimmedId}`); + const next = addNode(model, { + id: trimmedId, + type: 'TANK', + x: defaultCoords.x, + y: defaultCoords.y, + elevation: parseFloat(elevation) || 0, + initLevel: parseFloat(initLevel) || 0, + minLevel: parseFloat(minLevel) || 0, + maxLevel: parseFloat(maxLevel) || 0, + diameter: parseFloat(diameter) || 0, + minVol: parseFloat(minVol) || 0, + }); + onAdd(next); + } else if (category === 'pipes') { + if (!node1 || !node2) throw new Error('시작/끝 절점을 선택하세요.'); + if (node1 === node2) throw new Error('시작과 끝 절점은 달라야 합니다.'); + const trimmedId = id.trim() || getNextPipeId(model); + if (model.pipes[trimmedId] || model.pumps[trimmedId] || model.valves?.[trimmedId]) { + throw new Error(`이미 존재하는 링크 ID입니다: ${trimmedId}`); + } + const next = addPipe(model, { + id: trimmedId, + node1, + node2, + length: parseFloat(length) || 0, + diameter: parseFloat(pipeDiameter) || 0, + roughness: parseFloat(roughness) || 100, + minorLoss: parseFloat(minorLoss) || 0, + status, + }); + onAdd(next); + } else if (category === 'pumps') { + if (!node1 || !node2) throw new Error('상류/하류 절점을 선택하세요.'); + if (node1 === node2) throw new Error('상류와 하류 절점은 달라야 합니다.'); + const trimmedId = id.trim() || getNextPumpId(model); + if (model.pipes[trimmedId] || model.pumps[trimmedId] || model.valves?.[trimmedId]) { + throw new Error(`이미 존재하는 링크 ID입니다: ${trimmedId}`); + } + if (!parameters.trim()) throw new Error('펌프 특성 (예: HEAD Curve1 또는 POWER 10)을 입력하세요.'); + const next = addPump(model, { id: trimmedId, node1, node2, parameters: parameters.trim() }); + onAdd(next); + } else if (category === 'valves') { + if (!node1 || !node2) throw new Error('상류/하류 절점을 선택하세요.'); + if (node1 === node2) throw new Error('상류와 하류 절점은 달라야 합니다.'); + const trimmedId = id.trim() || getNextValveId(model); + if (model.pipes[trimmedId] || model.pumps[trimmedId] || model.valves?.[trimmedId]) { + throw new Error(`이미 존재하는 링크 ID입니다: ${trimmedId}`); + } + const settingNum = parseFloat(valveSetting); + const next = addValve(model, { + id: trimmedId, + node1, + node2, + diameter: parseFloat(diameter) || 0, + valveType, + setting: isNaN(settingNum) ? valveSetting : settingNum, + minorLoss: parseFloat(minorLoss) || 0, + }); + onAdd(next); + } else if (category === 'patterns') { + const trimmedId = id.trim(); + if (!trimmedId) throw new Error('패턴 ID를 입력하세요.'); + if (model.patterns[trimmedId]) throw new Error(`이미 존재하는 패턴 ID입니다: ${trimmedId}`); + const values = multipliers + .split(/[\s,]+/) + .map((v) => v.trim()) + .filter(Boolean) + .map(Number); + if (values.length === 0 || values.some((v) => isNaN(v))) { + throw new Error('시간별 배율은 공백/쉼표로 구분된 숫자여야 합니다.'); + } + const next = updatePattern(model, trimmedId, values); + onAdd(next); + } else if (category === 'controls') { + if (!controlLinkId) throw new Error('대상 링크를 선택하세요.'); + const next = addControl(model, { linkId: controlLinkId, status: controlStatus }); + onAdd(next); + } else if (category === 'rules') { + const next = addRule(model, ruleText); + onAdd(next); + } else if (category === 'curves') { + const trimmedId = id.trim(); + if (!trimmedId) throw new Error('곡선 ID를 입력하세요.'); + if (model.curves[trimmedId]) throw new Error(`이미 존재하는 곡선 ID입니다: ${trimmedId}`); + const points = curvePoints + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + .map((line) => { + const [xStr, yStr] = line.split(/[\s,]+/); + return { x: parseFloat(xStr), y: parseFloat(yStr) }; + }); + if (points.length === 0 || points.some((p) => isNaN(p.x) || isNaN(p.y))) { + throw new Error('곡선 점은 한 줄당 "X Y" 형식의 숫자 쌍이어야 합니다.'); + } + const next = addCurve(model, { id: trimmedId, points }); + onAdd(next); + } + } catch (err: unknown) { + setError(err instanceof Error ? err.message : String(err)); + } + }; + + const inputStyle: React.CSSProperties = { + width: '100%', + padding: '5px 8px', + fontSize: '12px', + border: '1px solid var(--border-strong)', + borderRadius: '4px', + }; + const labelStyle: React.CSSProperties = { fontSize: '11px', color: 'var(--text-secondary)', display: 'block', marginBottom: '3px' }; + const fieldWrap: React.CSSProperties = { marginBottom: '10px' }; + + const nodeSelect = (value: string, onChange: (v: string) => void, label: string) => ( +
+ + +
+ ); + + return ( +
+
+
+

{title} 추가

+ +
+ + {error && ( +
+ {error} +
+ )} + + {(category === 'junctions' || category === 'reservoirs' || category === 'tanks' || category === 'pipes' || category === 'pumps' || category === 'valves' || category === 'patterns' || category === 'curves') && ( +
+ + setId(e.target.value)} style={inputStyle} autoFocus /> +
+ )} + + {category === 'junctions' && ( + <> +
setElevation(e.target.value)} style={inputStyle} />
+
setDemand(e.target.value)} style={inputStyle} />
+
setPattern(e.target.value)} style={inputStyle} />
+ + )} + + {category === 'reservoirs' && ( + <> +
setHead(e.target.value)} style={inputStyle} />
+
setPattern(e.target.value)} style={inputStyle} />
+ + )} + + {category === 'tanks' && ( + <> +
setElevation(e.target.value)} style={inputStyle} />
+
setInitLevel(e.target.value)} style={inputStyle} />
+
setMinLevel(e.target.value)} style={inputStyle} />
+
setMaxLevel(e.target.value)} style={inputStyle} />
+
setDiameter(e.target.value)} style={inputStyle} />
+
setMinVol(e.target.value)} style={inputStyle} />
+ + )} + + {category === 'pipes' && ( + <> + {nodeSelect(node1, setNode1, '시작절점 (Node1)')} + {nodeSelect(node2, setNode2, '끝절점 (Node2)')} +
setLength(e.target.value)} style={inputStyle} />
+
setPipeDiameter(e.target.value)} style={inputStyle} />
+
setRoughness(e.target.value)} style={inputStyle} />
+
setMinorLoss(e.target.value)} style={inputStyle} />
+
+ + +
+ + )} + + {category === 'pumps' && ( + <> + {nodeSelect(node1, setNode1, '상류절점 (Node1)')} + {nodeSelect(node2, setNode2, '하류절점 (Node2)')} +
+ + setParameters(e.target.value)} style={inputStyle} placeholder="HEAD Curve1" /> +
+ + )} + + {category === 'valves' && ( + <> + {nodeSelect(node1, setNode1, '상류절점 (Node1)')} + {nodeSelect(node2, setNode2, '하류절점 (Node2)')} +
+ + +
+
setDiameter(e.target.value)} style={inputStyle} />
+
setValveSetting(e.target.value)} style={inputStyle} />
+
setMinorLoss(e.target.value)} style={inputStyle} />
+ + )} + + {category === 'patterns' && ( +
+ +