fix view point on map

This commit is contained in:
VALLONGOL 2025-05-14 07:55:57 +02:00
parent 6925e8e323
commit aa7fb18626

View File

@ -26,14 +26,17 @@ try:
except ImportError: except ImportError:
Image = None # type: ignore Image = None # type: ignore
ImageDraw = None # type: ignore # Define as None if import fails ImageDraw = None # type: ignore # Define as None if import fails
ImageType = None # type: ignore # MODIFIED: Added ImageType definition for type hinting even if PIL is missing.
# WHY: Allows static analysis tools to understand the intended type even if the library isn't installed.
# HOW: Defined ImageType = Any inside the except block.
ImageType = Any # type: ignore # Define ImageType as Any if PIL is not available
# This logger might not be configured yet if this is the first import in the process # This logger might not be configured yet if this is the first import in the process
# So, direct print or rely on higher-level logger configuration. # So, direct print or rely on higher-level logger configuration.
print("ERROR: GeoMapViewer - Pillow (PIL) library not found. Image operations will fail.") print("ERROR: GeoMapViewer - Pillow (PIL) library not found. Image operations will fail.")
try: try:
import cv2 # OpenCV for drawing operations import cv2 # OpenCV for windowing and drawing
import numpy as np import numpy as np
CV2_NUMPY_LIBS_AVAILABLE = True CV2_NUMPY_LIBS_AVAILABLE = True
except ImportError: except ImportError:
@ -107,7 +110,7 @@ class GeoElevationMapViewer:
initial_display_scale: float = 1.0 # Scale factor for the map image initial_display_scale: float = 1.0 # Scale factor for the map image
) -> None: ) -> None:
""" """
Initializes the GeoElevationMapViewer. Initializes the GeoElevationMapViewer.
Args: Args:
elevation_manager_instance: Instance of ElevationManager for fetching elevations. elevation_manager_instance: Instance of ElevationManager for fetching elevations.
@ -160,7 +163,7 @@ class GeoElevationMapViewer:
self._current_stitched_map_pil: Optional[ImageType] = None self._current_stitched_map_pil: Optional[ImageType] = None
self._current_map_geo_bounds_deg: Optional[Tuple[float, float, float, float]] = None self._current_map_geo_bounds_deg: Optional[Tuple[float, float, float, float]] = None
self._current_map_render_zoom: Optional[int] = None self._current_map_render_zoom: Optional[int] = None
self._current_stitched_map_pixel_shape: Optional[Tuple[int, int]] = None # H, W self._current_stitched_map_pixel_shape: Optional[Tuple[int, int]] = (0, 0) # H, W
self._last_user_click_pixel_coords_on_displayed_image: Optional[Tuple[int, int]] = None self._last_user_click_pixel_coords_on_displayed_image: Optional[Tuple[int, int]] = None
# MODIFIED: Added attribute to store the DEM tile bbox if a map view was initiated for a point with DEM data. # MODIFIED: Added attribute to store the DEM tile bbox if a map view was initiated for a point with DEM data.
@ -214,6 +217,7 @@ class GeoElevationMapViewer:
logger.critical(f"Failed to initialize map components: {e_init_map_comp}", exc_info=True) logger.critical(f"Failed to initialize map components: {e_init_map_comp}", exc_info=True)
raise raise
def display_map_for_point( def display_map_for_point(
self, self,
center_latitude: float, center_latitude: float,
@ -225,10 +229,10 @@ class GeoElevationMapViewer:
draws a marker at the point, and sends initial info back to the GUI. draws a marker at the point, and sends initial info back to the GUI.
Applies the current display scale. The zoom level is calculated to fit the DEM tile. Applies the current display scale. The zoom level is calculated to fit the DEM tile.
""" """
if not self.map_tile_fetch_manager or not self.map_display_window_controller or not self.elevation_manager: if not self.map_tile_fetch_manager or not self.map_display_window_controller or not self.elevation_manager or not self.map_service_provider: # Added check for map_service_provider
logger.error("Map or Elevation components not ready for display_map_for_point.") logger.error("Map or Elevation components not ready for display_map_for_point.")
# MODIFIED: If components aren't ready, send error info back to GUI queue. # MODIFIED: Send error info to GUI queue if components aren't ready.
# WHY: The GUI needs to know the map view failed. # WHY: GUI should update even if map isn't displayed.
# HOW: Put an error message into the queue. # HOW: Put an error message into the queue.
error_payload = {"type": "map_info_update", "latitude": center_latitude, "longitude": center_longitude, error_payload = {"type": "map_info_update", "latitude": center_latitude, "longitude": center_longitude,
"elevation_str": "Map Error", "map_area_size_str": "Error: Components N/A"} "elevation_str": "Map Error", "map_area_size_str": "Error: Components N/A"}
@ -252,6 +256,10 @@ class GeoElevationMapViewer:
# HOW: Set _dem_tile_geo_bbox_for_current_map to None. # HOW: Set _dem_tile_geo_bbox_for_current_map to None.
self._dem_tile_geo_bbox_for_current_map = None self._dem_tile_geo_bbox_for_current_map = None
# MODIFIED: Initialize map_tile_xy_ranges to None before the try block.
# WHY: To ensure the variable is defined even if an exception occurs before its assignment.
# HOW: Added the initialization here.
map_tile_xy_ranges = None
try: try:
# MODIFIED: 1. Get DEM tile info and its geographic bounds. # MODIFIED: 1. Get DEM tile info and its geographic bounds.
@ -303,36 +311,52 @@ class GeoElevationMapViewer:
# MODIFIED: 3. Calculate the appropriate zoom level to fit the map_fetch_geo_bbox into the target pixel size. # MODIFIED: 3. Calculate the appropriate zoom level to fit the map_fetch_geo_bbox into the target pixel size.
# WHY: To prevent creating excessively large map images like 28160x40192 px. # WHY: To prevent creating excessively large map images like 28160x40192 px.
# HOW: Calculate geographic height of map_fetch_geo_bbox and use calculate_zoom_level_for_geographic_size. # HOW: Calculate geographic height of map_fetch_geo_bbox and use calculate_zoom_level_for_geographic_size.
map_bbox_size_km = calculate_geographic_bbox_size_km(map_fetch_geo_bbox)
calculated_zoom = None calculated_zoom = None
if map_bbox_size_km: zoom_calculation_successful = False
_, map_bbox_height_km = map_bbox_size_km map_area_size_km = None # Added variable to store size for logging
map_bbox_height_meters = map_bbox_height_km * 1000.0
# Use the center latitude of the fetch box for zoom calculation accuracy # MODIFIED: Check PyProj availability before calculating size.
center_lat_fetch_bbox = (map_fetch_geo_bbox[1] + map_fetch_geo_bbox[3]) / 2.0 # WHY: calculate_geographic_bbox_size_km requires PyProj.
# HOW: Added check.
if PYPROJ_AVAILABLE: # type: ignore
map_area_size_km = calculate_geographic_bbox_size_km(map_fetch_geo_bbox)
if map_area_size_km:
width_km, height_km = map_area_size_km
map_bbox_height_meters = height_km * 1000.0
# Use the center latitude of the fetch box for zoom calculation accuracy
center_lat_fetch_bbox = (map_fetch_geo_bbox[1] + map_fetch_geo_bbox[3]) / 2.0
calculated_zoom = calculate_zoom_level_for_geographic_size(
center_lat_fetch_bbox,
map_bbox_height_meters,
TARGET_MAP_PIXEL_DIMENSION_FOR_POINT_VIEW, # Target pixel height
self.map_service_provider.tile_size # Tile size from the map service
)
if calculated_zoom is not None:
logger.info(f"Calculated zoom level {calculated_zoom} to fit BBox height ({map_bbox_height_meters:.2f}m) into {TARGET_MAP_PIXEL_DIMENSION_FOR_POINT_VIEW}px.")
zoom_calculation_successful = True
else:
logger.warning("Could not calculate appropriate zoom level. Falling back to default zoom.")
calculated_zoom = calculate_zoom_level_for_geographic_size(
center_lat_fetch_bbox,
map_bbox_height_meters,
TARGET_MAP_PIXEL_DIMENSION_FOR_POINT_VIEW, # Target pixel height
self.map_service_provider.tile_size # Tile size from the map service
)
if calculated_zoom is not None:
logger.info(f"Calculated zoom level {calculated_zoom} to fit BBox height ({map_bbox_height_meters:.2f}m) into {TARGET_MAP_PIXEL_DIMENSION_FOR_POINT_VIEW}px.")
else: else:
logger.warning("Could not calculate appropriate zoom level. Falling back to default zoom.") logger.warning("Could not calculate geographic size of fetch BBox. Falling back to default zoom.")
else:
logger.warning("PyProj not available. Cannot calculate geographic size for zoom calculation. Falling back to default zoom.")
# MODIFIED: 4. Use the calculated zoom level for tile ranges and stitching, falling back to effective_zoom if calculation failed. # MODIFIED: Determine the final zoom level to use.
# WHY: This is the core change to control the stitched image pixel size. # WHY: Use the calculated zoom if successful, otherwise use the default zoom as a fallback.
# HOW: Replace `effective_zoom` with `calculated_zoom` (or `effective_zoom` if `calculated_zoom` is None) in the calls below. # HOW: Check zoom_calculation_successful.
zoom_to_use = calculated_zoom if calculated_zoom is not None else effective_zoom zoom_to_use = calculated_zoom if zoom_calculation_successful else DEFAULT_MAP_DISPLAY_ZOOM_LEVEL
logger.debug(f"Using zoom level {zoom_to_use} for tile fetching and stitching.") logger.debug(f"Using zoom level {zoom_to_use} for tile fetching and stitching.")
# map_tile_xy_ranges assignment is here - line 346 originally
map_tile_xy_ranges = get_tile_ranges_for_bbox(map_fetch_geo_bbox, zoom_to_use) map_tile_xy_ranges = get_tile_ranges_for_bbox(map_fetch_geo_bbox, zoom_to_use)
if not map_tile_xy_ranges: if not map_tile_xy_ranges:
# This might happen if the BBox is valid but so small it doesn't intersect any tiles at this zoom # This might happen if the BBox is very small or outside standard tile limits, mercantile.tiles might be empty.
logger.warning(f"No map tile ranges found for fetch BBox {map_fetch_geo_bbox} at zoom {zoom_to_use}. Showing placeholder.") logger.warning(f"No map tile ranges found for fetch BBox {map_fetch_geo_bbox} at zoom {zoom_to_use}. Showing placeholder.")
self.map_display_window_controller.show_map(None) self.map_display_window_controller.show_map(None)
# MODIFIED: Send initial info to GUI even if map fails, with error status. # MODIFIED: Send initial info to GUI even if map fails, with error status.
@ -341,6 +365,7 @@ class GeoElevationMapViewer:
self._send_initial_point_info_to_gui(center_latitude, center_longitude, "Map Tiles N/A", "Map Tiles N/A") self._send_initial_point_info_to_gui(center_latitude, center_longitude, "Map Tiles N/A", "Map Tiles N/A")
return # Exit after showing placeholder/sending error return # Exit after showing placeholder/sending error
# MODIFIED: Pass the chosen zoom_to_use to stitch_map_image. # MODIFIED: Pass the chosen zoom_to_use to stitch_map_image.
stitched_pil = self.map_tile_fetch_manager.stitch_map_image( stitched_pil = self.map_tile_fetch_manager.stitch_map_image(
zoom_to_use, map_tile_xy_ranges[0], map_tile_xy_ranges[1] zoom_to_use, map_tile_xy_ranges[0], map_tile_xy_ranges[1]
@ -357,15 +382,15 @@ class GeoElevationMapViewer:
self._current_stitched_map_pil = stitched_pil self._current_stitched_map_pil = stitched_pil
# MODIFIED: Store the *actual* geographic bounds covered by the stitched tiles. # MODIFIED: Store the *actual* geographic bounds covered by the stitched tiles.
# WHY: This is needed for pixel-to-geo conversions and calculating the displayed area size. # WHY: Needed for pixel-to-geo conversions and calculating the displayed area size.
# HOW: Get bounds from map_tile_fetch_manager after stitching. # HOW: Get bounds from map_tile_fetch_manager after stitching.
# MODIFIED: Pass the zoom level *actually used* for stitching to get_bounds_for_tile_range. # MODIFIED: Pass the zoom level *actually used* for stitching (zoom_to_use) to get_bounds_for_tile_range.
# WHY: The bounds calculated must correspond to the tiles that were actually stitched. # WHY: The bounds calculated must correspond to the tiles that were actually stitched.
# HOW: Replaced `effective_zoom` with `zoom_to_use`. # HOW: Replaced `effective_zoom` with `zoom_to_use`.
self._current_map_geo_bounds_deg = self.map_tile_fetch_manager._get_bounds_for_tile_range( self._current_map_geo_bounds_deg = self.map_tile_fetch_manager._get_bounds_for_tile_range(
zoom_to_use, map_tile_xy_ranges zoom_to_use, map_tile_xy_ranges
) )
# MODIFIED: Store the zoom level *actually used* for stitching. # MODIFIED: Store the zoom level *actually used* for stitching (zoom_to_use).
# WHY: Consistency in context. # WHY: Consistency in context.
# HOW: Assigned `zoom_to_use` to _current_map_render_zoom. # HOW: Assigned `zoom_to_use` to _current_map_render_zoom.
self._current_map_render_zoom = zoom_to_use self._current_map_render_zoom = zoom_to_use
@ -405,10 +430,21 @@ class GeoElevationMapViewer:
# Calculate and send map area size # Calculate and send map area size
map_area_size_str = "N/A" map_area_size_str = "N/A"
if self._current_map_geo_bounds_deg: if self._current_map_geo_bounds_deg:
size_km = calculate_geographic_bbox_size_km(self._current_map_geo_bounds_deg) # MODIFIED: Check PyProj availability before calculating size.
if size_km: # WHY: calculate_geographic_bbox_size_km requires PyProj.
width_km, height_km = size_km # HOW: Added check.
map_area_size_str = f"{width_km:.2f} km W x {height_km:.2f} km H" if PYPROJ_AVAILABLE: # type: ignore
size_km = calculate_geographic_bbox_size_km(self._current_map_geo_bounds_deg)
if size_km:
width_km, height_km = size_km
map_area_size_str = f"{width_km:.2f} km W x {height_km:.2f} km H"
else:
map_area_size_str = "Size Calc Failed"
logger.warning("calculate_geographic_bbox_size_km returned None for current map bounds.")
else:
map_area_size_str = "PyProj N/A (Size Unknown)"
logger.warning("PyProj not available, cannot calculate map area size.")
self._send_initial_point_info_to_gui( self._send_initial_point_info_to_gui(
center_latitude, center_longitude, initial_elev_str, map_area_size_str center_latitude, center_longitude, initial_elev_str, map_area_size_str
@ -434,10 +470,13 @@ class GeoElevationMapViewer:
def display_map_for_area( def display_map_for_area(
self, self,
area_geo_bbox: Tuple[float, float, float, float], # west, south, east, north area_geo_bbox: Tuple[float, float, float, float], # west, south, east, north
target_map_zoom: Optional[int] = None target_map_zoom: Optional[int] = None # This parameter is now effectively ignored for area view sizing
) -> None: ) -> None:
"""Displays a map for a geographic area, applying the current display scale.""" """
if not self.map_tile_fetch_manager or not self.map_display_window_controller: Displays a map for a geographic area, applying the current display scale.
Calculates the zoom level dynamically to fit the requested area into a target pixel size.
"""
if not self.map_tile_fetch_manager or not self.map_display_window_controller or not self.map_service_provider: # Added check for map_service_provider
logger.error("Map components not ready for display_map_for_area.") logger.error("Map components not ready for display_map_for_area.")
# MODIFIED: Send error info to GUI queue if components aren't ready. # MODIFIED: Send error info to GUI queue if components aren't ready.
# WHY: GUI should update even if map isn't displayed. # WHY: GUI should update even if map isn't displayed.
@ -449,30 +488,83 @@ class GeoElevationMapViewer:
if self.map_display_window_controller: self.map_display_window_controller.show_map(None) # Show placeholder if self.map_display_window_controller: self.map_display_window_controller.show_map(None) # Show placeholder
return return
# MODIFIED: Default zoom for area view can still be the global default map display zoom. # MODIFIED: Remove the effective_zoom calculation that defaulted to DEFAULT_MAP_DISPLAY_ZOOM_LEVEL.
# WHY: For area view, the user requested a specific bounding box, not necessarily tied to a DEM tile size. # WHY: The goal is to calculate the zoom dynamically based on the area size, not use a fixed default.
# A fixed default zoom might be acceptable, or we could calculate zoom based on area bbox size too (future). # effective_zoom = target_map_zoom if target_map_zoom is not None else DEFAULT_MAP_DISPLAY_ZOOM_LEVEL
# HOW: Keep effective_zoom logic using DEFAULT_MAP_DISPLAY_ZOOM_LEVEL. # logger.info(
effective_zoom = target_map_zoom if target_map_zoom is not None else DEFAULT_MAP_DISPLAY_ZOOM_LEVEL # f"Requesting map display for area: BBox {area_geo_bbox}, "
# f"Zoom: {effective_zoom}, CurrentDisplayScale: {self.current_display_scale_factor:.2f}"
# )
logger.info( logger.info(
f"Requesting map display for area: BBox {area_geo_bbox}, " f"Requesting map display for area: BBox {area_geo_bbox}, "
f"Zoom: {effective_zoom}, CurrentDisplayScale: {self.current_display_scale_factor:.2f}" f"Target Pixel Size: {TARGET_MAP_PIXEL_DIMENSION_FOR_POINT_VIEW}x{TARGET_MAP_PIXEL_DIMENSION_FOR_POINT_VIEW}, "
f"CurrentDisplayScale: {self.current_display_scale_factor:.2f}"
) )
# MODIFIED: Clear the stored DEM tile bbox as this is an area view. # MODIFIED: Clear the stored DEM tile bbox as this is an area view.
# WHY: The DEM boundary is specific to the initial point view. # WHY: The DEM boundary is specific to the initial point view.
# HOW: Set _dem_tile_geo_bbox_for_current_map to None. # HOW: Set _dem_tile_geo_bbox_for_current_map to None.
self._dem_tile_geo_bbox_for_current_map = None self._dem_tile_geo_bbox_for_current_map = None
calculated_zoom: Optional[int] = None
zoom_calculation_successful = False
map_area_size_km: Optional[Tuple[float, float]] = None
# MODIFIED: Initialize map_tile_xy_ranges to None before the try block.
# WHY: To ensure the variable is defined even if an exception occurs before its assignment.
# HOW: Added the initialization here.
map_tile_xy_ranges = None
try: try:
# MODIFIED: Use the provided area_geo_bbox directly for tile range calculation. # MODIFIED: Calculate the geographic size of the requested area bounding box.
# WHY: For area view, we want to show the requested area, not necessarily tied to a single DEM tile. # WHY: Needed to determine the appropriate zoom level to fit this area into a target pixel size.
# HOW: Passed area_geo_bbox to get_tile_ranges_for_bbox. # HOW: Call map_utils.calculate_geographic_bbox_size_km.
# MODIFIED: Pass the effective_zoom (default 15 or user-provided) to get_tile_ranges_for_bbox. # MODIFIED: Check PyProj availability before calculating size.
# WHY: For area view, we use the specified or default zoom. # WHY: calculate_geographic_bbox_size_km requires PyProj.
# HOW: Replaced `zoom_to_use` with `effective_zoom`. # HOW: Added check.
map_tile_xy_ranges = get_tile_ranges_for_bbox(area_geo_bbox, effective_zoom) if PYPROJ_AVAILABLE: # type: ignore
map_area_size_km = calculate_geographic_bbox_size_km(area_geo_bbox)
if map_area_size_km:
width_km, height_km = map_area_size_km
logger.debug(f"Calculated geographic size of requested area: {width_km:.2f}km W x {height_km:.2f}km H")
# MODIFIED: Calculate the appropriate zoom level to fit the area into the target pixel size.
# WHY: To prevent creating excessively large map images for large geographic areas.
# HOW: Use calculate_zoom_level_for_geographic_size based on the area's height.
map_bbox_height_meters = height_km * 1000.0
# Use the center latitude of the requested area BBox for zoom calculation accuracy
center_lat_area_bbox = (area_geo_bbox[1] + area_geo_bbox[3]) / 2.0
calculated_zoom = calculate_zoom_level_for_geographic_size(
center_lat_area_bbox,
map_bbox_height_meters,
TARGET_MAP_PIXEL_DIMENSION_FOR_POINT_VIEW, # Target pixel height (reuse constant from point view)
self.map_service_provider.tile_size # Tile size from the map service
)
if calculated_zoom is not None:
logger.info(f"Calculated zoom level {calculated_zoom} to fit Area BBox height ({map_bbox_height_meters:.2f}m) into {TARGET_MAP_PIXEL_DIMENSION_FOR_POINT_VIEW}px.")
zoom_calculation_successful = True
else:
logger.warning("Could not calculate appropriate zoom level for area. Falling back to default zoom.")
else:
logger.warning("Could not calculate geographic size of requested area BBox. Falling back to default zoom.")
else:
logger.warning("PyProj not available. Cannot calculate geographic size for zoom calculation. Falling back to default zoom.")
# MODIFIED: Determine the final zoom level to use.
# WHY: Use the calculated zoom if successful, otherwise use the default zoom as a fallback.
# HOW: Check zoom_calculation_successful.
zoom_to_use = calculated_zoom if zoom_calculation_successful else DEFAULT_MAP_DISPLAY_ZOOM_LEVEL
logger.debug(f"Using zoom level {zoom_to_use} for tile fetching and stitching for area.")
# map_tile_xy_ranges assignment is here - corresponds to line 346 in point view
map_tile_xy_ranges = get_tile_ranges_for_bbox(area_geo_bbox, zoom_to_use)
if not map_tile_xy_ranges: if not map_tile_xy_ranges:
logger.warning(f"No map tile ranges found for area BBox {area_geo_bbox} at zoom {effective_zoom}. Showing placeholder.") logger.warning(f"No map tile ranges found for area BBox {area_geo_bbox} at zoom {zoom_to_use}. Showing placeholder.")
self.map_display_window_controller.show_map(None) self.map_display_window_controller.show_map(None)
# MODIFIED: Send error info to GUI queue even if map fails. # MODIFIED: Send error info to GUI queue even if map fails.
# WHY: GUI should update even if map isn't displayed. # WHY: GUI should update even if map isn't displayed.
@ -481,9 +573,9 @@ class GeoElevationMapViewer:
return # Exit after showing placeholder/sending error return # Exit after showing placeholder/sending error
# MODIFIED: Pass the effective_zoom to stitch_map_image. # MODIFIED: Pass the chosen zoom_to_use to stitch_map_image.
stitched_pil = self.map_tile_fetch_manager.stitch_map_image( stitched_pil = self.map_tile_fetch_manager.stitch_map_image(
effective_zoom, map_tile_xy_ranges[0], map_tile_xy_ranges[1] zoom_to_use, map_tile_xy_ranges[0], map_tile_xy_ranges[1]
) )
if not stitched_pil: if not stitched_pil:
logger.error("Failed to stitch map image for area display.") logger.error("Failed to stitch map image for area display.")
@ -498,16 +590,16 @@ class GeoElevationMapViewer:
# MODIFIED: Store the *actual* geographic bounds covered by the stitched tiles for the area view. # MODIFIED: Store the *actual* geographic bounds covered by the stitched tiles for the area view.
# WHY: Needed for pixel-to-geo conversions and calculating the displayed area size. # WHY: Needed for pixel-to-geo conversions and calculating the displayed area size.
# HOW: Get bounds from map_tile_fetch_manager after stitching. # HOW: Get bounds from map_tile_fetch_manager after stitching.
# MODIFIED: Pass the zoom level *actually used* for stitching (effective_zoom) to get_bounds_for_tile_range. # MODIFIED: Pass the zoom level *actually used* for stitching (zoom_to_use) to get_bounds_for_tile_range.
# WHY: The bounds calculated must correspond to the tiles that were actually stitched. # WHY: The bounds calculated must correspond to the tiles that were actually stitched.
# HOW: Replaced `zoom_to_use` with `effective_zoom`. # HOW: Replaced `effective_zoom` with `zoom_to_use`.
self._current_map_geo_bounds_deg = self.map_tile_fetch_manager._get_bounds_for_tile_range( self._current_map_geo_bounds_deg = self.map_tile_fetch_manager._get_bounds_for_tile_range(
effective_zoom, map_tile_xy_ranges zoom_to_use, map_tile_xy_ranges
) )
# MODIFIED: Store the zoom level *actually used* for stitching (effective_zoom). # MODIFIED: Store the zoom level *actually used* for stitching (zoom_to_use).
# WHY: Consistency in context. # WHY: Consistency in context.
# HOW: Assigned `effective_zoom` to _current_map_render_zoom. # HOW: Assigned `zoom_to_use` to _current_map_render_zoom.
self._current_map_render_zoom = effective_zoom self._current_map_render_zoom = zoom_to_use
self._current_stitched_map_pixel_shape = (stitched_pil.height, stitched_pil.width) self._current_stitched_map_pixel_shape = (stitched_pil.height, stitched_pil.width)
# MODIFIED: Draw the *requested* area bounding box on the map image. # MODIFIED: Draw the *requested* area bounding box on the map image.
@ -524,10 +616,22 @@ class GeoElevationMapViewer:
# HOW: Calculate size and send message (using N/A for point info in this case). # HOW: Calculate size and send message (using N/A for point info in this case).
map_area_size_str = "N/A" map_area_size_str = "N/A"
if self._current_map_geo_bounds_deg: if self._current_map_geo_bounds_deg:
size_km = calculate_geographic_bbox_size_km(self._current_map_geo_bounds_deg) # MODIFIED: Check PyProj availability before calculating size.
if size_km: # WHY: calculate_geographic_bbox_size_km requires PyProj.
width_km, height_km = size_km # HOW: Added check.
map_area_size_str = f"{width_km:.2f} km W x {height_km:.2f} km H" if PYPROJ_AVAILABLE: # type: ignore
# Calculate size of the *stitched* area's bounds (which might be slightly larger than requested)
size_km = calculate_geographic_bbox_size_km(self._current_map_geo_bounds_deg)
if size_km:
width_km, height_km = size_km
map_area_size_str = f"{width_km:.2f} km W x {height_km:.2f} km H"
else:
map_area_size_str = "Size Calc Failed"
logger.warning("calculate_geographic_bbox_size_km returned None for current map bounds.")
else:
map_area_size_str = "PyProj N/A (Size Unknown)"
logger.warning("PyProj not available, cannot calculate map area size.")
# Send info for area view (point info is N/A) # Send info for area view (point info is N/A)
self._send_initial_point_info_to_gui(None, None, "N/A (Area View)", map_area_size_str) self._send_initial_point_info_to_gui(None, None, "N/A (Area View)", map_area_size_str)
@ -640,8 +744,8 @@ class GeoElevationMapViewer:
# Relative position of the target geo point within the *unscaled* map's Mercator extent # Relative position of the target geo point within the *unscaled* map's Mercator extent
# Need to handle potential division by zero if map width/height is zero (e.g., invalid bounds) # Need to handle potential division by zero if map width/height is zero (e.g., invalid bounds)
relative_merc_x_in_map = (target_merc_x - map_ul_merc_x) / total_map_width_merc 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 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
# Convert these relative positions to pixel coordinates on the *unscaled* image # Convert these relative positions to pixel coordinates on the *unscaled* image
@ -735,8 +839,8 @@ class GeoElevationMapViewer:
target_merc_x, target_merc_y = local_mercantile.xy(lon, lat) # type: ignore target_merc_x, target_merc_y = local_mercantile.xy(lon, lat) # type: ignore
# Handle relative position calculation, ensuring bounds are respected # Handle relative position calculation, ensuring bounds are respected
relative_merc_x_in_map = (target_merc_x - map_ul_merc_x) / total_map_width_merc 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 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_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)) pixel_y_on_unscaled = int(round(relative_merc_y_in_map * unscaled_height))
@ -888,7 +992,7 @@ class GeoElevationMapViewer:
logger.debug( logger.debug(
f"Map mouse click (on scaled img) received at pixel ({pixel_x_on_displayed_img}, {pixel_y_on_displayed_img})" f"Map mouse click (on scaled img) received at pixel ({pixel_x_on_displayed_img}, {pixel_y_on_displayed_img})"
) )
# Store the pixel coordinates of the click on the *displayed* (scaled) image. # Store the pixel coordinates of the click on the *displayed* (scalata) image.
self._last_user_click_pixel_coords_on_displayed_image = (pixel_x_on_displayed_img, pixel_y_on_displayed_img) self._last_user_click_pixel_coords_on_displayed_image = (pixel_x_on_displayed_img, pixel_y_on_displayed_img)
# MODIFIED: Check if map context is ready before proceeding with conversion and elevation fetch. # MODIFIED: Check if map context is ready before proceeding with conversion and elevation fetch.
@ -1039,7 +1143,7 @@ class GeoElevationMapViewer:
self._current_stitched_map_pil = None self._current_stitched_map_pil = None
self._current_map_geo_bounds_deg = None self._current_map_geo_bounds_deg = None
self._current_map_render_zoom = None self._current_map_render_zoom = None
self._current_stitched_map_pixel_shape = None self._current_stitched_map_pixel_shape = (0, 0) # Reset to default tuple
self._last_user_click_pixel_coords_on_displayed_image = None self._last_user_click_pixel_coords_on_displayed_image = None
self._dem_tile_geo_bbox_for_current_map = None self._dem_tile_geo_bbox_for_current_map = None