Files
PVPlant/lib/GoogleSatelitalImageDownload.py
2025-07-06 01:12:08 +02:00

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