207 lines
7.7 KiB
Python
207 lines
7.7 KiB
Python
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 |