import math from PIL import Image import urllib.request from io import BytesIO import time class GoogleMapDownloader: def __init__(self, zoom=12, layer='raw_satellite'): self._zoom = zoom self.layer_map = { 'roadmap': 'm', 'terrain': 'p', 'satellite': 's', 'hybrid': 'y', 'raw_satellite': 's' } self._layer = self.layer_map.get(layer, 's') self._style = 'style=feature:all|element:labels|visibility:off' if layer == 'raw_satellite' else '' def latlng_to_tile(self, lat, lng): """Convierte coordenadas a tiles X/Y con precisión decimal""" tile_size = 256 numTiles = 1 << self._zoom point_x = (tile_size / 2 + lng * tile_size / 360.0) * numTiles / tile_size sin_y = math.sin(lat * (math.pi / 180.0)) point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) * -(tile_size / (2 * math.pi))) * numTiles / tile_size return point_x, point_y def generateImage(self, sw_lat, sw_lng, ne_lat, ne_lng): """Genera la imagen para un área rectangular definida por coordenadas""" # Convertir coordenadas a tiles con precisión decimal sw_x, sw_y = self.latlng_to_tile(sw_lat, sw_lng) ne_x, ne_y = self.latlng_to_tile(ne_lat, ne_lng) # Asegurar que las coordenadas estén en el orden correcto min_x = min(sw_x, ne_x) max_x = max(sw_x, ne_x) min_y = min(sw_y, ne_y) max_y = max(sw_y, ne_y) # Calcular los tiles mínimos y máximos necesarios min_tile_x = math.floor(min_x) max_tile_x = math.ceil(max_x) min_tile_y = math.floor(min_y) max_tile_y = math.ceil(max_y) # Calcular dimensiones en tiles tile_width = int(max_tile_x - min_tile_x) + 1 tile_height = int(max_tile_y - min_tile_y) + 1 # Crear imagen temporal para todos los tiles necesarios full_img = Image.new('RGB', (tile_width * 256, tile_height * 256)) headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'} servers = ['mt0', 'mt1', 'mt2', 'mt3'] for x in range(min_tile_x, max_tile_x + 1): for y in range(min_tile_y, max_tile_y + 1): server = servers[(x + y) % len(servers)] base_url = f"https://{server}.google.com/vt?lyrs={self._layer}&x={x}&y={y}&z={self._zoom}" url = f"{base_url}&{self._style}" if self._style else base_url try: req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req) as response: tile_data = response.read() img = Image.open(BytesIO(tile_data)) pos_x = (x - min_tile_x) * 256 pos_y = (y - min_tile_y) * 256 full_img.paste(img, (pos_x, pos_y)) #print(f"✅ Tile ({x}, {y}) descargado") except Exception as e: #print(f"❌ Error en tile ({x},{y}): {str(e)}") error_tile = Image.new('RGB', (256, 256), (255, 0, 0)) full_img.paste(error_tile, (pos_x, pos_y)) time.sleep(0.05) # Calcular desplazamientos para recorte final left_offset = int((min_x - min_tile_x) * 256) right_offset = int((max_tile_x - max_x) * 256) top_offset = int((min_y - min_tile_y) * 256) bottom_offset = int((max_tile_y - max_y) * 256) # Calcular coordenadas de recorte left = left_offset top = top_offset right = full_img.width - right_offset bottom = full_img.height - bottom_offset # Asegurar que las coordenadas sean válidas if right < left: right = left + 1 if bottom < top: bottom = top + 1 # Recortar la imagen al área exacta solicitada result = full_img.crop(( left, top, right, bottom )) return full_img class GoogleMapDownloader_1: def __init__(self, zoom=12, layer='hybrid'): """ Args: zoom: Zoom level (0-23) layer: Map type (roadmap, terrain, satellite, hybrid) """ self._zoom = zoom self.layer_map = { 'roadmap': 'm', 'terrain': 'p', 'satellite': 's', 'hybrid': 'y', 'raw_satellite': 's' # Capa especial sin etiquetas } self._layer = self.layer_map.get(layer, 's') self._style = 'style=feature:all|element:labels|visibility:off' if layer == 'raw_satellite' else '' def latlng_to_tile(self, lat, lng): """Convierte coordenadas a tiles X/Y""" tile_size = 256 numTiles = 1 << self._zoom # Cálculo para coordenada X point_x = (tile_size / 2 + lng * tile_size / 360.0) * numTiles / tile_size # Cálculo para coordenada Y sin_y = math.sin(lat * (math.pi / 180.0)) point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) * -(tile_size / (2 * math.pi))) * numTiles / tile_size return int(point_x), int(point_y) def generateImage(self, sw_lat, sw_lng, ne_lat, ne_lng): """ Genera la imagen para un área rectangular definida por: - sw_lat, sw_lng: Esquina suroeste (latitud, longitud) - ne_lat, ne_lng: Esquina noreste (latitud, longitud) """ # Convertir coordenadas a tiles sw_x, sw_y = self.latlng_to_tile(sw_lat, sw_lng) ne_x, ne_y = self.latlng_to_tile(ne_lat, ne_lng) # Determinar rango de tiles min_x = min(sw_x, ne_x) max_x = max(sw_x, ne_x) min_y = min(sw_y, ne_y) max_y = max(sw_y, ne_y) # Calcular dimensiones en tiles tile_width = max_x - min_x + 1 tile_height = max_y - min_y + 1 # Crear imagen final result = Image.new('RGB', (256 * tile_width, 256 * tile_height)) headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'} servers = ['mt0', 'mt1', 'mt2', 'mt3'] print(f"Descargando {tile_width}x{tile_height} tiles ({tile_width * tile_height} total)") for x in range(min_x, max_x + 1): for y in range(min_y, max_y + 1): # Seleccionar servidor rotatorio server = servers[(x + y) % len(servers)] # Construir URL con parámetro para quitar etiquetas si es necesario url = f"https://{server}.google.com/vt?lyrs={self._layer}&x={x}&y={y}&z={self._zoom}" if self._style: url = f"{url}&{self._style}" print("Descargando tile:", url) try: # Descargar tile req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req) as response: tile_data = response.read() # Procesar en memoria img = Image.open(BytesIO(tile_data)) pos_x = (x - min_x) * 256 pos_y = (y - min_y) * 256 result.paste(img, (pos_x, pos_y)) print(f"✅ Tile ({x}, {y}) descargado") except Exception as e: print(f"❌ Error en tile ({x},{y}): {str(e)}") # Crear tile de error (rojo) error_tile = Image.new('RGB', (256, 256), (255, 0, 0)) pos_x = (x - min_x) * 256 pos_y = (y - min_y) * 256 result.paste(error_tile, (pos_x, pos_y)) # Pausa para evitar bloqueos time.sleep(0.05) return result