from PIL import Image import base64 from io import BytesIO def crop_and_encode_geographic_image( image_path: str, center_point: tuple[float, float] # (center_lon, center_lat) ) -> str: """ 지정된 PNG 이미지에서 특정 위경도 중심을 기준으로 주변 crop_radius_km 영역을 잘라내고 Base64 문자열로 인코딩합니다. :param image_path: 입력 PNG 파일 경로. :param image_bounds: 이미지 전체가 나타내는 영역의 (min_lon, min_lat, max_lon, max_lat) 좌표. (경도 최소, 위도 최소, 경도 최대, 위도 최대) :param center_point: 자르기 영역의 중심이 될 (lon, lat) 좌표. :param crop_radius_km: 중심에서 상하좌우로 자를 거리 (km). :return: 잘린 이미지의 PNG Base64 문자열. """ image_bounds = (124.2083983267507250, 32.7916655404227129, 129.9583954964914767, 38.9583268301827559) crop_radius_km = 25.0 # 1. 이미지 로드 try: img = Image.open(image_path) except FileNotFoundError: return f"Error: File not found at {image_path}" except Exception as e: return f"Error opening image: {e}" width, height = img.size min_lon, min_lat, max_lon, max_lat = image_bounds center_lon, center_lat = center_point # 2. 위경도 경계 계산 (25km 반경) # 1도당 근사적인 거리 (대한민국 지역 기준) # 위도 1도: 약 111 km (거의 일정) # 경도 1도: 위도에 따라 달라지지만, 한국의 위도(약 33~38도)에서 약 88~93 km 정도. # 안전을 위해 WGS84 타원체 기준 위도 35도에서 경도 1도당 약 91.2km 가정 # 더 정확한 계산을 위해선 `pyproj` 등의 라이브러리 사용이 권장되나, 여기선 근사치 사용 KM_PER_DEG_LAT = 111.0 # 위도 1도당 km (근사치) KM_PER_DEG_LON_AT_35 = 91.2 # 위도 35도에서 경도 1도당 km (근사치) # 위도/경도 1도에 해당하는 픽셀 수 계산 deg_lat_span = max_lat - min_lat deg_lon_span = max_lon - min_lon # KM_PER_DEG_LON을 중심 위도에 맞게 조정 (단순화를 위해 상수 사용을 유지) # 25km에 해당하는 위도/경도 변화량 계산 delta_lat = crop_radius_km / KM_PER_DEG_LAT delta_lon = crop_radius_km / KM_PER_DEG_LON_AT_35 # 근사치 사용 # 자를 영역의 위경도 바운딩 박스 (Bounding Box) crop_min_lon = center_lon - delta_lon crop_max_lon = center_lon + delta_lon crop_min_lat = center_lat - delta_lat crop_max_lat = center_lat + delta_lat bounds = { "min_lon": float(crop_min_lon), "max_lon": float(crop_max_lon), "min_lat": float(crop_min_lat), "max_lat": float(crop_max_lat) } # 3. 위경도 좌표를 픽셀 좌표로 변환 (선형 매핑 가정) # 픽셀 좌표 x: min_lon -> 0, max_lon -> width # 픽셀 좌표 y: max_lat -> 0, min_lat -> height (GIS 이미지는 보통 북쪽(위도 최대)이 0에 해당) def lon_to_pixel_x(lon): return int(width * (lon - min_lon) / deg_lon_span) def lat_to_pixel_y(lat): # Y축은 위도에 반비례 (큰 위도가 작은 Y 픽셀) return int(height * (max_lat - lat) / deg_lat_span) # 자를 영역의 픽셀 좌표 계산 pixel_x_min = max(0, lon_to_pixel_x(crop_min_lon)) pixel_y_min = max(0, lat_to_pixel_y(crop_max_lat)) # 위도 최대가 y_min (상단) pixel_x_max = min(width, lon_to_pixel_x(crop_max_lon)) pixel_y_max = min(height, lat_to_pixel_y(crop_min_lat)) # 위도 최소가 y_max (하단) # PIL의 crop 함수는 (left, top, right, bottom) 순서의 픽셀 좌표를 사용 crop_box = (pixel_x_min, pixel_y_min, pixel_x_max, pixel_y_max) # 4. 이미지 자르기 if pixel_x_min >= pixel_x_max or pixel_y_min >= pixel_y_max: return "Error: Crop area is outside the image bounds or zero size." cropped_img = img.crop(crop_box) # 5. Base64 문자열로 인코딩 buffer = BytesIO() cropped_img.save(buffer, format="PNG") base64_encoded_data = base64.b64encode(buffer.getvalue()).decode("utf-8") # Base64 문자열 앞에 MIME 타입 정보 추가 # base64_string = f"data:image/png;base64,{base64_encoded_data}" return base64_encoded_data, bounds