104 lines
5.5 KiB
Python
104 lines
5.5 KiB
Python
"""Drawing helpers migrated from original project; adapted to new package layout.
|
|
"""
|
|
import logging
|
|
from typing import Optional, Tuple, List, Dict
|
|
|
|
try:
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
PIL_LIB_AVAILABLE_DRAWING = True
|
|
except ImportError:
|
|
Image = None # type: ignore
|
|
ImageDraw = None # type: ignore
|
|
ImageFont = None # type: ignore
|
|
PIL_LIB_AVAILABLE_DRAWING = False
|
|
logging.error("MapDrawing: Pillow (PIL) library not found. Drawing operations will fail.")
|
|
|
|
try:
|
|
import cv2
|
|
import numpy as np
|
|
CV2_NUMPY_LIBS_AVAILABLE_DRAWING = True
|
|
except ImportError:
|
|
cv2 = None # type: ignore
|
|
np = None # type: ignore
|
|
CV2_NUMPY_LIBS_AVAILABLE_DRAWING = False
|
|
logging.warning("MapDrawing: OpenCV or NumPy not found. Some drawing operations (markers) will be disabled.")
|
|
|
|
try:
|
|
import mercantile
|
|
MERCANTILE_LIB_AVAILABLE_DRAWING = True
|
|
except ImportError:
|
|
mercantile = None # type: ignore
|
|
MERCANTILE_LIB_AVAILABLE_DRAWING = False
|
|
logging.error("MapDrawing: 'mercantile' library not found. Coordinate conversions will fail.")
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Prefer local utils import name
|
|
from .utils import get_hgt_tile_geographic_bounds
|
|
from .utils import MapCalculationError
|
|
|
|
# Fallback style constants
|
|
DEM_BOUNDARY_COLOR = "red"
|
|
DEM_BOUNDARY_THICKNESS_PX = 3
|
|
AREA_BOUNDARY_COLOR = "blue"
|
|
AREA_BOUNDARY_THICKNESS_PX = 2
|
|
TILE_TEXT_COLOR = "white"
|
|
TILE_TEXT_BG_COLOR = "rgba(0, 0, 0, 150)"
|
|
DEM_TILE_LABEL_BASE_FONT_SIZE = 12
|
|
DEM_TILE_LABEL_BASE_ZOOM = 10
|
|
_DEFAULT_FONT_FOR_LABELS = None
|
|
|
|
|
|
def _geo_to_pixel_on_unscaled_map(latitude_deg: float, longitude_deg: float, current_map_geo_bounds: Optional[Tuple[float, float, float, float]], current_stitched_map_pixel_shape: Optional[Tuple[int, int]]) -> Optional[Tuple[int, int]]:
|
|
if not MERCANTILE_LIB_AVAILABLE_DRAWING or current_map_geo_bounds is None or current_stitched_map_pixel_shape is None:
|
|
logger.warning("Map context incomplete or mercantile missing for geo_to_pixel_on_unscaled_map conversion.")
|
|
return None
|
|
unscaled_height, unscaled_width = current_stitched_map_pixel_shape
|
|
map_west_lon, map_south_lat, map_east_lon, map_north_lat = current_map_geo_bounds
|
|
try:
|
|
map_ul_merc_x, map_ul_merc_y = mercantile.xy(map_west_lon, map_north_lat) # type: ignore
|
|
map_lr_merc_x, map_lr_merc_y = mercantile.xy(map_east_lon, map_south_lat) # type: ignore
|
|
total_map_width_merc = abs(map_lr_merc_x - map_ul_merc_x)
|
|
total_map_height_merc = abs(map_ul_merc_y - map_lr_merc_y)
|
|
if total_map_width_merc <= 0 or total_map_height_merc <= 0:
|
|
logger.warning("Map Mercator extent is zero, cannot convert geo to pixel on unscaled map.")
|
|
return None
|
|
target_merc_x, target_merc_y = mercantile.xy(longitude_deg, latitude_deg) # type: ignore
|
|
relative_merc_x_in_map = (target_merc_x - map_ul_merc_x) / total_map_width_merc if total_map_width_merc > 0 else 0.0
|
|
relative_merc_y_in_map = (map_ul_merc_y - target_merc_y) / total_map_height_merc if total_map_height_merc > 0 else 0.0
|
|
pixel_x_on_unscaled = int(round(relative_merc_x_in_map * unscaled_width))
|
|
pixel_y_on_unscaled = int(round(relative_merc_y_in_map * unscaled_height))
|
|
px_clamped = max(0, min(pixel_x_on_unscaled, unscaled_width - 1))
|
|
py_clamped = max(0, min(pixel_y_on_unscaled, unscaled_height - 1))
|
|
return (px_clamped, py_clamped)
|
|
except Exception as e_geo_to_px_unscaled:
|
|
logger.exception(f"Error during geo_to_pixel_on_unscaled_map conversion: {e_geo_to_px_unscaled}")
|
|
return None
|
|
|
|
|
|
def draw_point_marker(pil_image_to_draw_on, latitude_deg: float, longitude_deg: float, current_map_geo_bounds: Optional[Tuple[float, float, float, float]], current_stitched_map_pixel_shape: Optional[Tuple[int, int]]):
|
|
if not PIL_LIB_AVAILABLE_DRAWING or pil_image_to_draw_on is None or current_map_geo_bounds is None or current_stitched_map_pixel_shape is None:
|
|
logger.warning("Cannot draw point marker: PIL image or map context missing.")
|
|
return pil_image_to_draw_on
|
|
pixel_coords_on_unscaled = _geo_to_pixel_on_unscaled_map(latitude_deg, longitude_deg, current_map_geo_bounds, current_stitched_map_pixel_shape)
|
|
if pixel_coords_on_unscaled:
|
|
px_clamped, py_clamped = pixel_coords_on_unscaled
|
|
logger.debug(f"Drawing point marker at unscaled pixel ({px_clamped},{py_clamped}) for geo ({latitude_deg:.5f},{longitude_deg:.5f})")
|
|
if CV2_NUMPY_LIBS_AVAILABLE_DRAWING and cv2 and np:
|
|
try:
|
|
if pil_image_to_draw_on.mode != 'RGB':
|
|
map_cv_bgr = cv2.cvtColor(np.array(pil_image_to_draw_on.convert('RGB')), cv2.COLOR_RGB2BGR) # type: ignore
|
|
else:
|
|
map_cv_bgr = cv2.cvtColor(np.array(pil_image_to_draw_on), cv2.COLOR_RGB2BGR) # type: ignore
|
|
cv2.drawMarker(map_cv_bgr, (px_clamped, py_clamped), (0, 0, 255), cv2.MARKER_CROSS, markerSize=15, thickness=2) # type: ignore
|
|
return Image.fromarray(cv2.cvtColor(map_cv_bgr, cv2.COLOR_BGR2RGB)) # type: ignore
|
|
except Exception as e_draw_click_cv:
|
|
logger.exception(f"Error drawing point marker with OpenCV: {e_draw_click_cv}")
|
|
return pil_image_to_draw_on
|
|
else:
|
|
logger.warning("CV2 or NumPy not available, cannot draw point marker using OpenCV.")
|
|
return pil_image_to_draw_on
|
|
else:
|
|
logger.warning(f"Geo-to-pixel conversion failed for ({latitude_deg:.5f},{longitude_deg:.5f}), cannot draw point marker.")
|
|
return pil_image_to_draw_on
|