- 백엔드: 관할서 목록 API, zone 필터링 쿼리 추가 - 프론트: ScatLeftPanel 관할서 드롭다운, ScatMap/ScatPopup 개선 - 기상탭: WeatherRightPanel 리팩토링 - prediction/scat: PDF 파싱 → 지오코딩 → ESI 매핑 파이프라인 - vite.config: proxy 설정 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
125 lines
3.7 KiB
Python
125 lines
3.7 KiB
Python
"""PDF에서 해안조사 사진을 추출하여 scat-photos 폴더에 저장.
|
|
|
|
Type A (해안사전평가정보집): 1페이지 = 1구간 → 가장 큰 RGB 사진
|
|
Type B (방제정보집): 1페이지 = 2구간 → RGB 사진 2장, 순서대로 매칭
|
|
|
|
저장 네이밍: {sect_cd}-1.png (ScatPopup에서 참조하는 형식)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from typing import List, Tuple
|
|
|
|
import fitz # PyMuPDF
|
|
|
|
from pdf_parser import is_data_page, _CODE_RE
|
|
from pdf_parser_b import is_data_page_b
|
|
|
|
import config
|
|
|
|
|
|
def _get_page_photos(
|
|
doc: fitz.Document, page: fitz.Page,
|
|
) -> List[fitz.Pixmap]:
|
|
"""페이지에서 RGB 사진만 추출 (배경 제외). 크기 내림차순."""
|
|
photos: List[Tuple[fitz.Pixmap, int]] = []
|
|
for img_info in page.get_images(full=True):
|
|
xref = img_info[0]
|
|
pix = fitz.Pixmap(doc, xref)
|
|
# CMYK → RGB
|
|
if pix.n > 4:
|
|
pix = fitz.Pixmap(fitz.csRGB, pix)
|
|
elif pix.n == 4:
|
|
pix = fitz.Pixmap(fitz.csRGB, pix)
|
|
# 흑백(n=1) 또는 배경(2000px 이상) 스킵
|
|
if pix.n < 3 or pix.width >= 2000:
|
|
continue
|
|
photos.append((pix, pix.width * pix.height))
|
|
photos.sort(key=lambda x: x[1], reverse=True)
|
|
return photos
|
|
|
|
|
|
def _extract_codes_type_a(page: fitz.Page) -> List[str]:
|
|
"""Type A 페이지에서 sect_cd 추출."""
|
|
text = page.get_text('text')
|
|
return _CODE_RE.findall(text)
|
|
|
|
|
|
def _extract_codes_type_b(page: fitz.Page) -> List[str]:
|
|
"""Type B 페이지에서 sect_cd 추출 (순서 유지)."""
|
|
text = page.get_text('text')
|
|
codes = re.findall(r'([A-Z]{4,}-\d+(?:-[A-Z]){3,})', text)
|
|
seen = set()
|
|
unique = []
|
|
for c in codes:
|
|
if c not in seen:
|
|
seen.add(c)
|
|
unique.append(c)
|
|
return unique
|
|
|
|
|
|
def _save_pixmap(pix: fitz.Pixmap, output_path: Path):
|
|
"""Pixmap을 PNG로 저장."""
|
|
if pix.alpha:
|
|
pix = fitz.Pixmap(fitz.csRGB, pix)
|
|
pix.save(str(output_path))
|
|
|
|
|
|
def extract_images_from_pdf(
|
|
pdf_path: str | Path,
|
|
output_dir: str | Path | None = None,
|
|
pdf_type: str = 'A',
|
|
) -> int:
|
|
"""PDF에서 데이터 페이지별 대표 사진을 추출.
|
|
|
|
Args:
|
|
pdf_path: PDF 파일 경로
|
|
output_dir: 이미지 저장 경로 (기본: config.SCAT_PHOTOS_DIR)
|
|
pdf_type: 'A' 또는 'B'
|
|
|
|
Returns:
|
|
추출된 이미지 수
|
|
"""
|
|
pdf_path = Path(pdf_path)
|
|
out_dir = Path(output_dir) if output_dir else config.SCAT_PHOTOS_DIR
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
doc = fitz.open(str(pdf_path))
|
|
saved = 0
|
|
|
|
for i in range(doc.page_count):
|
|
page = doc[i]
|
|
|
|
if pdf_type == 'A':
|
|
if not is_data_page(page):
|
|
continue
|
|
codes = _extract_codes_type_a(page)
|
|
photos = _get_page_photos(doc, page)
|
|
if not codes or not photos:
|
|
continue
|
|
# 가장 작은 RGB 이미지 = 실제 현장 사진
|
|
pix = photos[-1][0]
|
|
out_path = out_dir / f'{codes[0]}-1.png'
|
|
_save_pixmap(pix, out_path)
|
|
saved += 1
|
|
|
|
elif pdf_type == 'B':
|
|
if not is_data_page_b(page):
|
|
continue
|
|
codes = _extract_codes_type_b(page)
|
|
photos = _get_page_photos(doc, page)
|
|
if not codes or not photos:
|
|
continue
|
|
# Type B: 사진 크기 동일, 순서대로 매칭
|
|
for idx, code in enumerate(codes):
|
|
if idx < len(photos):
|
|
pix = photos[idx][0]
|
|
out_path = out_dir / f'{code}-1.png'
|
|
_save_pixmap(pix, out_path)
|
|
saved += 1
|
|
|
|
doc.close()
|
|
return saved
|