update
This commit is contained in:
1021
Export/exportDXF.py
1021
Export/exportDXF.py
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>715</width>
|
<width>462</width>
|
||||||
<height>520</height>
|
<height>282</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -78,6 +78,11 @@
|
|||||||
<string>Lineweight</string>
|
<string>Lineweight</string>
|
||||||
</property>
|
</property>
|
||||||
</column>
|
</column>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -204,6 +209,21 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QWidget" name="widget" native="true">
|
<widget class="QWidget" name="widget" native="true">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="buttonAcept">
|
<widget class="QPushButton" name="buttonAcept">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|||||||
@@ -524,6 +524,7 @@ def exportToPVC(path, exportTerrain = False):
|
|||||||
# TODO: revisar
|
# TODO: revisar
|
||||||
for typ in frameType:
|
for typ in frameType:
|
||||||
isTracker = "tracker" in typ.Proxy.Type.lower()
|
isTracker = "tracker" in typ.Proxy.Type.lower()
|
||||||
|
isTracker = False
|
||||||
|
|
||||||
objectlist = FreeCAD.ActiveDocument.findObjects(Name="Tracker")
|
objectlist = FreeCAD.ActiveDocument.findObjects(Name="Tracker")
|
||||||
tmp = []
|
tmp = []
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import urllib.request
|
|||||||
import math
|
import math
|
||||||
import utm
|
import utm
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
import PVPlantImportGrid as ImportElevation
|
||||||
|
|
||||||
scale = 1000.0
|
scale = 1000.0
|
||||||
|
|
||||||
@@ -42,8 +43,10 @@ class OSMImporter:
|
|||||||
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
|
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||||
|
|
||||||
def transform_from_latlon(self, lat, lon):
|
def transform_from_latlon(self, lat, lon):
|
||||||
x, y, _, _ = utm.from_latlon(lat, lon)
|
point = ImportElevation.getElevationFromOE([[lat, lon], ])
|
||||||
return FreeCAD.Vector(x, y, .0) * scale - self.Origin
|
return FreeCAD.Vector(point[0].x, point[0].y, point[0].z) * scale - self.Origin
|
||||||
|
'''x, y, _, _ = utm.from_latlon(lat, lon)
|
||||||
|
return FreeCAD.Vector(x, y, .0) * scale - self.Origin'''
|
||||||
|
|
||||||
def get_osm_data(self, bbox):
|
def get_osm_data(self, bbox):
|
||||||
query = f"""
|
query = f"""
|
||||||
@@ -148,7 +151,6 @@ class OSMImporter:
|
|||||||
polyline.addProperty("App::PropertyFloat", "Width", "Metadata", "Ancho de la vía").Width = width
|
polyline.addProperty("App::PropertyFloat", "Width", "Metadata", "Ancho de la vía").Width = width
|
||||||
layer.addObject(polyline)
|
layer.addObject(polyline)
|
||||||
|
|
||||||
|
|
||||||
def create_railway(self, nodes, layer, name=""):
|
def create_railway(self, nodes, layer, name=""):
|
||||||
points = [n for n in nodes]
|
points = [n for n in nodes]
|
||||||
rail_line = Draft.make_wire(points, closed=False, face=False)
|
rail_line = Draft.make_wire(points, closed=False, face=False)
|
||||||
@@ -737,27 +739,163 @@ class OSMImporter:
|
|||||||
def create_vegetation(self):
|
def create_vegetation(self):
|
||||||
vegetation_layer = self.create_layer("Vegetation")
|
vegetation_layer = self.create_layer("Vegetation")
|
||||||
|
|
||||||
# Árboles individuales
|
# Procesar nodos de vegetación individual
|
||||||
for node_id, coords in self.nodes.items():
|
for way_id, tags in self.ways_data.items():
|
||||||
# Verificar si es un árbol
|
coords = self.nodes.get(way_id)
|
||||||
# (Necesitarías procesar los tags de los nodos, implementación simplificada)
|
if not coords:
|
||||||
cylinder = Part.makeCylinder(0.5, 5.0, FreeCAD.Vector(coords[0], coords[1], 0))# * scale - self.Origin )
|
continue
|
||||||
tree = FreeCAD.ActiveDocument.addObject("Part::Feature", "Tree")
|
|
||||||
vegetation_layer.addObject(tree)
|
|
||||||
tree.Shape = cylinder
|
|
||||||
tree.ViewObject.ShapeColor = self.feature_colors['vegetation']
|
|
||||||
|
|
||||||
# Áreas verdes
|
pos = FreeCAD.Vector(*coords)
|
||||||
|
|
||||||
|
if tags.get('natural') == 'tree':
|
||||||
|
self.create_tree(pos, tags, vegetation_layer)
|
||||||
|
elif tags.get('natural') == 'shrub':
|
||||||
|
self.create_shrub(pos, tags, vegetation_layer)
|
||||||
|
"""elif tags.get('natural') == 'tree_stump':
|
||||||
|
self.create_tree_stump(pos, vegetation_layer)"""
|
||||||
|
|
||||||
|
# Procesar áreas vegetales
|
||||||
for way_id, data in self.ways_data.items():
|
for way_id, data in self.ways_data.items():
|
||||||
if 'natural' in data['tags'] or 'landuse' in data['tags']:
|
tags = data['tags']
|
||||||
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
|
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
|
||||||
if len(nodes) > 2:
|
|
||||||
polygon_points = [n for n in nodes]
|
if not nodes or len(nodes) < 3:
|
||||||
polygon = Part.makePolygon(polygon_points)
|
continue
|
||||||
face = Part.Face(polygon)
|
|
||||||
area = vegetation_layer.addObject("Part::Feature", "GreenArea")
|
if tags.get('natural') == 'wood' or tags.get('landuse') == 'forest':
|
||||||
area.Shape = face
|
self.create_forest(nodes, tags, vegetation_layer)
|
||||||
area.ViewObject.ShapeColor = self.feature_colors['vegetation']
|
elif tags.get('natural') == 'grassland':
|
||||||
|
self.create_grassland(nodes, vegetation_layer)
|
||||||
|
elif tags.get('natural') == 'heath':
|
||||||
|
self.create_heathland(nodes, vegetation_layer)
|
||||||
|
elif tags.get('natural') == 'scrub':
|
||||||
|
self.create_scrub_area(nodes, vegetation_layer)
|
||||||
|
|
||||||
|
def create_tree(self, position, tags, layer):
|
||||||
|
"""Crea un árbol individual con propiedades basadas en etiquetas OSM"""
|
||||||
|
height = float(tags.get('height', 10.0))
|
||||||
|
trunk_radius = float(tags.get('circumference', 1.0)) / (2 * math.pi)
|
||||||
|
canopy_radius = float(tags.get('diameter_crown', 4.0)) / 2
|
||||||
|
|
||||||
|
# Crear tronco
|
||||||
|
trunk = Part.makeCylinder(trunk_radius, height, position)
|
||||||
|
|
||||||
|
# Crear copa (forma cónica)
|
||||||
|
canopy_center = position + FreeCAD.Vector(0, 0, height)
|
||||||
|
canopy = Part.makeCone(canopy_radius, canopy_radius * 0.7, canopy_radius * 1.5, canopy_center)
|
||||||
|
|
||||||
|
tree = trunk.fuse(canopy)
|
||||||
|
tree_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Tree")
|
||||||
|
layer.addObject(tree_obj)
|
||||||
|
tree_obj.Shape = tree
|
||||||
|
tree_obj.ViewObject.ShapeColor = (0.3, 0.6, 0.2) # Verde bosque
|
||||||
|
|
||||||
|
# Añadir metadatos
|
||||||
|
for prop in ['genus', 'species', 'leaf_type', 'height']:
|
||||||
|
if prop in tags:
|
||||||
|
tree_obj.addProperty("App::PropertyString", prop.capitalize(), "Botany",
|
||||||
|
"Botanical property").__setattr__(prop.capitalize(), tags[prop])
|
||||||
|
|
||||||
|
def create_forest(self, nodes, tags, layer):
|
||||||
|
"""Crea un área boscosa con densidad variable"""
|
||||||
|
polygon_points = [FreeCAD.Vector(*n) for n in nodes]
|
||||||
|
if polygon_points[0] != polygon_points[-1]:
|
||||||
|
polygon_points.append(polygon_points[0])
|
||||||
|
|
||||||
|
# Crear base del bosque
|
||||||
|
polygon = Part.makePolygon(polygon_points)
|
||||||
|
face = Part.Face(polygon)
|
||||||
|
forest_base = FreeCAD.ActiveDocument.addObject("Part::Feature", "Forest_Base")
|
||||||
|
layer.addObject(forest_base)
|
||||||
|
forest_base.Shape = face
|
||||||
|
forest_base.ViewObject.ShapeColor = (0.15, 0.4, 0.1) # Verde oscuro
|
||||||
|
|
||||||
|
# Generar árboles aleatorios dentro del polígono
|
||||||
|
density = float(tags.get('density', 0.5)) # Árboles por m²
|
||||||
|
area = face.Area
|
||||||
|
num_trees = int(area * density)
|
||||||
|
|
||||||
|
for _ in range(num_trees):
|
||||||
|
rand_point = self.random_point_in_polygon(polygon_points)
|
||||||
|
self.create_tree(rand_point, {}, layer)
|
||||||
|
|
||||||
|
def create_grassland(self, nodes, layer):
|
||||||
|
"""Crea un área de pastizales"""
|
||||||
|
polygon_points = [FreeCAD.Vector(*n) for n in nodes]
|
||||||
|
if polygon_points[0] != polygon_points[-1]:
|
||||||
|
polygon_points.append(polygon_points[0])
|
||||||
|
|
||||||
|
polygon = Part.makePolygon(polygon_points)
|
||||||
|
face = Part.Face(polygon)
|
||||||
|
grassland = FreeCAD.ActiveDocument.addObject("Part::Feature", "Grassland")
|
||||||
|
layer.addObject(grassland)
|
||||||
|
grassland.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))
|
||||||
|
grassland.ViewObject.ShapeColor = (0.5, 0.7, 0.3) # Verde pasto
|
||||||
|
|
||||||
|
def create_heathland(self, nodes, layer):
|
||||||
|
"""Crea un área de brezales con vegetación baja"""
|
||||||
|
polygon_points = [FreeCAD.Vector(*n) for n in nodes]
|
||||||
|
if polygon_points[0] != polygon_points[-1]:
|
||||||
|
polygon_points.append(polygon_points[0])
|
||||||
|
|
||||||
|
polygon = Part.makePolygon(polygon_points)
|
||||||
|
face = Part.Face(polygon)
|
||||||
|
heath = FreeCAD.ActiveDocument.addObject("Part::Feature", "Heathland")
|
||||||
|
layer.addObject(heath)
|
||||||
|
heath.Shape = face
|
||||||
|
heath.ViewObject.ShapeColor = (0.6, 0.5, 0.4) # Color terroso
|
||||||
|
|
||||||
|
# Añadir arbustos dispersos
|
||||||
|
for _ in range(int(face.Area * 0.1)): # 1 arbusto cada 10m²
|
||||||
|
rand_point = self.random_point_in_polygon(polygon_points)
|
||||||
|
self.create_shrub(rand_point, {}, layer)
|
||||||
|
|
||||||
|
def create_shrub(self, position, tags, layer):
|
||||||
|
"""Crea un arbusto individual"""
|
||||||
|
height = float(tags.get('height', 1.5))
|
||||||
|
radius = float(tags.get('diameter_crown', 1.0)) / 2
|
||||||
|
|
||||||
|
# Crear forma de arbusto (cono invertido)
|
||||||
|
base_center = position + FreeCAD.Vector(0, 0, height / 2)
|
||||||
|
shrub = Part.makeCone(radius, radius * 1.5, height, base_center)
|
||||||
|
|
||||||
|
shrub_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Shrub")
|
||||||
|
layer.addObject(shrub_obj)
|
||||||
|
shrub_obj.Shape = shrub
|
||||||
|
shrub_obj.ViewObject.ShapeColor = (0.4, 0.5, 0.3) # Verde arbusto
|
||||||
|
|
||||||
|
# Añadir metadatos si existen
|
||||||
|
if 'genus' in tags:
|
||||||
|
shrub_obj.addProperty("App::PropertyString", "Genus", "Botany", "Plant genus").Genus = tags['genus']
|
||||||
|
|
||||||
|
def create_tree_stump(self, position, layer):
|
||||||
|
"""Crea un tocón de árbol"""
|
||||||
|
height = 0.4
|
||||||
|
radius = 0.5
|
||||||
|
|
||||||
|
stump = Part.makeCylinder(radius, height, position)
|
||||||
|
stump_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Tree_Stump")
|
||||||
|
layer.addObject(stump_obj)
|
||||||
|
stump_obj.Shape = stump
|
||||||
|
stump_obj.ViewObject.ShapeColor = (0.3, 0.2, 0.1) # Marrón madera
|
||||||
|
|
||||||
|
def random_point_in_polygon(self, polygon_points):
|
||||||
|
"""Genera un punto aleatorio dentro de un polígono"""
|
||||||
|
min_x = min(p.x for p in polygon_points)
|
||||||
|
max_x = max(p.x for p in polygon_points)
|
||||||
|
min_y = min(p.y for p in polygon_points)
|
||||||
|
max_y = max(p.y for p in polygon_points)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
rand_x = random.uniform(min_x, max_x)
|
||||||
|
rand_y = random.uniform(min_y, max_y)
|
||||||
|
rand_point = FreeCAD.Vector(rand_x, rand_y, 0)
|
||||||
|
|
||||||
|
# Verificar si el punto está dentro del polígono
|
||||||
|
polygon = Part.makePolygon(polygon_points)
|
||||||
|
face = Part.Face(polygon)
|
||||||
|
if face.isInside(rand_point, 0.1, True):
|
||||||
|
return rand_point
|
||||||
|
|
||||||
def create_water_bodies(self):
|
def create_water_bodies(self):
|
||||||
water_layer = self.create_layer("Water")
|
water_layer = self.create_layer("Water")
|
||||||
@@ -769,7 +907,8 @@ class OSMImporter:
|
|||||||
polygon_points = [n for n in nodes]
|
polygon_points = [n for n in nodes]
|
||||||
polygon = Part.makePolygon(polygon_points)
|
polygon = Part.makePolygon(polygon_points)
|
||||||
face = Part.Face(polygon)
|
face = Part.Face(polygon)
|
||||||
water = water_layer.addObject("Part::Feature", "WaterBody")
|
water = FreeCAD.ActiveDocument.addObject("Part::Feature", "WaterBody")
|
||||||
|
water_layer.addObject(water)
|
||||||
water.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))# * scale - self.Origin )
|
water.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))# * scale - self.Origin )
|
||||||
water.ViewObject.ShapeColor = self.feature_colors['water']
|
water.ViewObject.ShapeColor = self.feature_colors['water']
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ class MapWindow(QtGui.QWidget):
|
|||||||
self.maxLat = None
|
self.maxLat = None
|
||||||
self.minLon = None
|
self.minLon = None
|
||||||
self.maxLon = None
|
self.maxLon = None
|
||||||
|
self.zoom = None
|
||||||
self.WinTitle = WinTitle
|
self.WinTitle = WinTitle
|
||||||
|
self.georeference_coordinates = {'lat': None, 'lon': None}
|
||||||
self.setupUi()
|
self.setupUi()
|
||||||
|
|
||||||
def setupUi(self):
|
def setupUi(self):
|
||||||
@@ -152,6 +154,9 @@ class MapWindow(QtGui.QWidget):
|
|||||||
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
||||||
RightLayout.addWidget(self.checkboxImportGis)
|
RightLayout.addWidget(self.checkboxImportGis)
|
||||||
|
|
||||||
|
self.checkboxImportSatelitalImagen = QtGui.QCheckBox("Importar Imagen Satelital")
|
||||||
|
RightLayout.addWidget(self.checkboxImportSatelitalImagen)
|
||||||
|
|
||||||
verticalSpacer = QtGui.QSpacerItem(20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
verticalSpacer = QtGui.QSpacerItem(20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||||
RightLayout.addItem(verticalSpacer)
|
RightLayout.addItem(verticalSpacer)
|
||||||
|
|
||||||
@@ -192,6 +197,7 @@ class MapWindow(QtGui.QWidget):
|
|||||||
"var data = drawnItems.toGeoJSON();"
|
"var data = drawnItems.toGeoJSON();"
|
||||||
"MyApp.shapes(JSON.stringify(data));"
|
"MyApp.shapes(JSON.stringify(data));"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
@QtCore.Slot(float, float)
|
@QtCore.Slot(float, float)
|
||||||
@@ -203,17 +209,22 @@ class MapWindow(QtGui.QWidget):
|
|||||||
' | UTM: ' + str(zone_number) + zone_letter +
|
' | UTM: ' + str(zone_number) + zone_letter +
|
||||||
', {:.5f}m E, {:.5f}m N'.format(x, y))
|
', {:.5f}m E, {:.5f}m N'.format(x, y))
|
||||||
|
|
||||||
@QtCore.Slot(float, float, float, float)
|
@QtCore.Slot(float, float, float, float, int)
|
||||||
def onMapZoom(self, minLat, minLon, maxLat, maxLon):
|
def onMapZoom(self, minLat, minLon, maxLat, maxLon, zoom):
|
||||||
self.minLat = min([minLat, maxLat])
|
self.minLat = min([minLat, maxLat])
|
||||||
self.maxLat = max([minLat, maxLat])
|
self.maxLat = max([minLat, maxLat])
|
||||||
self.minLon = min([minLon, maxLon])
|
self.minLon = min([minLon, maxLon])
|
||||||
self.maxLon = max([minLon, maxLon])
|
self.maxLon = max([minLon, maxLon])
|
||||||
|
self.zoom = zoom
|
||||||
|
|
||||||
@QtCore.Slot(float, float)
|
@QtCore.Slot(float, float)
|
||||||
def georeference(self, lat, lng):
|
def georeference(self, lat, lng):
|
||||||
import PVPlantSite
|
import PVPlantSite
|
||||||
from geopy.geocoders import Nominatim
|
from geopy.geocoders import Nominatim
|
||||||
|
|
||||||
|
self.georeference_coordinates['lat'] = lat
|
||||||
|
self.georeference_coordinates['lon'] = lng
|
||||||
|
|
||||||
Site = PVPlantSite.get(create=True)
|
Site = PVPlantSite.get(create=True)
|
||||||
Site.Proxy.setLatLon(lat, lng)
|
Site.Proxy.setLatLon(lat, lng)
|
||||||
|
|
||||||
@@ -278,7 +289,7 @@ class MapWindow(QtGui.QWidget):
|
|||||||
pts = [p.sub(offset) for p in tmp]
|
pts = [p.sub(offset) for p in tmp]
|
||||||
|
|
||||||
obj = Draft.makeWire(pts, closed=cw, face=False)
|
obj = Draft.makeWire(pts, closed=cw, face=False)
|
||||||
#obj.Placement.Base = offset
|
#obj.Placement.Base = Site.Origin
|
||||||
obj.Label = name
|
obj.Label = name
|
||||||
Draft.autogroup(obj)
|
Draft.autogroup(obj)
|
||||||
|
|
||||||
@@ -288,6 +299,170 @@ class MapWindow(QtGui.QWidget):
|
|||||||
if self.checkboxImportGis.isChecked():
|
if self.checkboxImportGis.isChecked():
|
||||||
self.getDataFromOSM(self.minLat, self.minLon, self.maxLat, self.maxLon)
|
self.getDataFromOSM(self.minLat, self.minLon, self.maxLat, self.maxLon)
|
||||||
|
|
||||||
|
if self.checkboxImportSatelitalImagen.isChecked():
|
||||||
|
# Usar los límites reales del terreno (rectangular)
|
||||||
|
'''s_lat = self.minLat
|
||||||
|
s_lon = self.minLon
|
||||||
|
n_lat = self.maxLat
|
||||||
|
n_lon = self.maxLon
|
||||||
|
|
||||||
|
# Obtener puntos UTM para las esquinas
|
||||||
|
corners = ImportElevation.getElevationFromOE([
|
||||||
|
[s_lat, s_lon], # Esquina suroeste
|
||||||
|
[n_lat, s_lon], # Esquina sureste
|
||||||
|
[n_lat, n_lon], # Esquina noreste
|
||||||
|
[s_lat, n_lon] # Esquina noroeste
|
||||||
|
])
|
||||||
|
|
||||||
|
if not corners or len(corners) < 4:
|
||||||
|
FreeCAD.Console.PrintError("Error obteniendo elevaciones para las esquinas\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Descargar imagen satelital
|
||||||
|
from lib.GoogleSatelitalImageDownload import GoogleMapDownloader
|
||||||
|
downloader = GoogleMapDownloader(
|
||||||
|
zoom= 18, #self.zoom,
|
||||||
|
layer='raw_satellite'
|
||||||
|
)
|
||||||
|
img = downloader.generateImage(
|
||||||
|
sw_lat=s_lat,
|
||||||
|
sw_lng=s_lon,
|
||||||
|
ne_lat=n_lat,
|
||||||
|
ne_lng=n_lon
|
||||||
|
)
|
||||||
|
|
||||||
|
# Guardar imagen en el directorio del documento
|
||||||
|
doc_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) if FreeCAD.ActiveDocument.FileName else ""
|
||||||
|
if not doc_path:
|
||||||
|
doc_path = FreeCAD.ConfigGet("UserAppData")
|
||||||
|
|
||||||
|
filename = os.path.join(doc_path, "background.jpeg")
|
||||||
|
img.save(filename)
|
||||||
|
|
||||||
|
ancho, alto = img.size
|
||||||
|
|
||||||
|
# Crear objeto de imagen en FreeCAD
|
||||||
|
doc = FreeCAD.ActiveDocument
|
||||||
|
img_obj = doc.addObject('Image::ImagePlane', 'Background')
|
||||||
|
img_obj.ImageFile = filename
|
||||||
|
img_obj.Label = 'Background'
|
||||||
|
|
||||||
|
# Calcular dimensiones en metros usando las coordenadas UTM
|
||||||
|
# Extraer las coordenadas de las esquinas
|
||||||
|
sw = corners[0] # Suroeste
|
||||||
|
se = corners[1] # Sureste
|
||||||
|
ne = corners[2] # Noreste
|
||||||
|
nw = corners[3] # Noroeste
|
||||||
|
|
||||||
|
# Calcular ancho (promedio de los lados superior e inferior)
|
||||||
|
width_bottom = se.x - sw.x
|
||||||
|
width_top = ne.x - nw.x
|
||||||
|
width_m = (width_bottom + width_top) / 2
|
||||||
|
|
||||||
|
# Calcular alto (promedio de los lados izquierdo y derecho)
|
||||||
|
height_left = nw.y - sw.y
|
||||||
|
height_right = ne.y - se.y
|
||||||
|
height_m = (height_left + height_right) / 2
|
||||||
|
|
||||||
|
img_obj.XSize = width_m
|
||||||
|
img_obj.YSize = height_m
|
||||||
|
|
||||||
|
# Posicionar el centro de la imagen en (0,0,0)
|
||||||
|
img_obj.Placement.Base = FreeCAD.Vector(-width_m / 2, -height_m / 2, 0)'''
|
||||||
|
|
||||||
|
# Definir área rectangular
|
||||||
|
s_lat = self.minLat
|
||||||
|
s_lon = self.minLon
|
||||||
|
n_lat = self.maxLat
|
||||||
|
n_lon = self.maxLon
|
||||||
|
|
||||||
|
# Obtener puntos UTM para las esquinas y el punto de referencia
|
||||||
|
points = [
|
||||||
|
[s_lat, s_lon], # Suroeste
|
||||||
|
[n_lat, n_lon], # Noreste
|
||||||
|
[self.georeference_coordinates['lat'], self.georeference_coordinates['lon']] # Punto de referencia
|
||||||
|
]
|
||||||
|
utm_points = ImportElevation.getElevationFromOE(points)
|
||||||
|
|
||||||
|
if not utm_points or len(utm_points) < 3:
|
||||||
|
FreeCAD.Console.PrintError("Error obteniendo elevaciones para las esquinas y referencia\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
sw_utm, ne_utm, ref_utm = utm_points
|
||||||
|
|
||||||
|
# Descargar imagen satelital
|
||||||
|
from lib.GoogleSatelitalImageDownload import GoogleMapDownloader
|
||||||
|
downloader = GoogleMapDownloader(
|
||||||
|
zoom=self.zoom,
|
||||||
|
layer='raw_satellite'
|
||||||
|
)
|
||||||
|
img = downloader.generateImage(
|
||||||
|
sw_lat=s_lat,
|
||||||
|
sw_lng=s_lon,
|
||||||
|
ne_lat=n_lat,
|
||||||
|
ne_lng=n_lon
|
||||||
|
)
|
||||||
|
|
||||||
|
# Guardar imagen
|
||||||
|
doc_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) if FreeCAD.ActiveDocument.FileName else ""
|
||||||
|
if not doc_path:
|
||||||
|
doc_path = FreeCAD.ConfigGet("UserAppData")
|
||||||
|
|
||||||
|
filename = os.path.join(doc_path, "background.jpeg")
|
||||||
|
img.save(filename)
|
||||||
|
|
||||||
|
# Calcular dimensiones reales en metros
|
||||||
|
width_m = ne_utm.x - sw_utm.x # Ancho en metros (este-oeste)
|
||||||
|
height_m = ne_utm.y - sw_utm.y # Alto en metros (norte-sur)
|
||||||
|
|
||||||
|
# Calcular posición relativa del punto de referencia dentro de la imagen
|
||||||
|
rel_x = (ref_utm.x - sw_utm.x) / width_m if width_m != 0 else 0.5
|
||||||
|
rel_y = (ref_utm.y - sw_utm.y) / height_m if height_m != 0 else 0.5
|
||||||
|
|
||||||
|
# Crear objeto de imagen en FreeCAD
|
||||||
|
doc = FreeCAD.ActiveDocument
|
||||||
|
img_obj = doc.addObject('Image::ImagePlane', 'Background')
|
||||||
|
img_obj.ImageFile = filename
|
||||||
|
img_obj.Label = 'Background'
|
||||||
|
|
||||||
|
# Convertir dimensiones a milímetros (FreeCAD trabaja en mm)
|
||||||
|
img_obj.XSize = width_m * 1000
|
||||||
|
img_obj.YSize = height_m * 1000
|
||||||
|
|
||||||
|
# Posicionar para que el punto de referencia esté en (0,0,0)
|
||||||
|
# La esquina inferior izquierda debe estar en:
|
||||||
|
# x = -rel_x * ancho_total
|
||||||
|
# y = -rel_y * alto_total
|
||||||
|
img_obj.Placement.Base = FreeCAD.Vector(
|
||||||
|
-rel_x * width_m * 1000,
|
||||||
|
-rel_y * height_m * 1000,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Refrescar el documento
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
def calculate_texture_transform(self, mesh_obj, width_m, height_m):
|
||||||
|
"""Calcula la transformación precisa para la textura"""
|
||||||
|
try:
|
||||||
|
# Obtener coordenadas reales de las esquinas
|
||||||
|
import utm
|
||||||
|
sw = utm.from_latlon(self.minLat, self.minLon)
|
||||||
|
ne = utm.from_latlon(self.maxLat, self.maxLon)
|
||||||
|
|
||||||
|
# Crear matriz de transformación
|
||||||
|
scale_x = (ne[0] - sw[0]) / width_m
|
||||||
|
scale_y = (ne[1] - sw[1]) / height_m
|
||||||
|
|
||||||
|
# Aplicar transformación (solo si se usa textura avanzada)
|
||||||
|
if hasattr(mesh_obj.ViewObject, "TextureMapping"):
|
||||||
|
mesh_obj.ViewObject.TextureMapping = "PLANE"
|
||||||
|
mesh_obj.ViewObject.TextureScale = (scale_x, scale_y)
|
||||||
|
mesh_obj.ViewObject.TextureOffset = (sw[0], sw[1])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
FreeCAD.Console.PrintWarning(f"No se pudo calcular transformación: {str(e)}\n")
|
||||||
|
|
||||||
def getDataFromOSM(self, min_lat, min_lon, max_lat, max_lon):
|
def getDataFromOSM(self, min_lat, min_lon, max_lat, max_lon):
|
||||||
import Importer.importOSM as importOSM
|
import Importer.importOSM as importOSM
|
||||||
import PVPlantSite
|
import PVPlantSite
|
||||||
|
|||||||
@@ -125,9 +125,9 @@ def getElevationFromOE(coordinates):
|
|||||||
points = []
|
points = []
|
||||||
for i, point in enumerate(coordinates):
|
for i, point in enumerate(coordinates):
|
||||||
c = utm.from_latlon(point[0], point[1])
|
c = utm.from_latlon(point[0], point[1])
|
||||||
points.append(FreeCAD.Vector(round(c[0] * 1000, 0),
|
points.append(FreeCAD.Vector(round(c[0], 0),
|
||||||
round(c[1] * 1000, 0),
|
round(c[1], 0),
|
||||||
0))
|
0) * 1000)
|
||||||
return points
|
return points
|
||||||
|
|
||||||
# Only get the json response in case of 200 or 201
|
# Only get the json response in case of 200 or 201
|
||||||
@@ -136,14 +136,16 @@ def getElevationFromOE(coordinates):
|
|||||||
results = r.json()
|
results = r.json()
|
||||||
for point in results["results"]:
|
for point in results["results"]:
|
||||||
c = utm.from_latlon(point["latitude"], point["longitude"])
|
c = utm.from_latlon(point["latitude"], point["longitude"])
|
||||||
v = FreeCAD.Vector(round(c[0] * 1000, 0),
|
v = FreeCAD.Vector(round(c[0], 0),
|
||||||
round(c[1] * 1000, 0),
|
round(c[1], 0),
|
||||||
round(point["elevation"] * 1000, 0))
|
round(point["elevation"], 0)) * 1000
|
||||||
points.append(v)
|
points.append(v)
|
||||||
return points
|
return points
|
||||||
|
|
||||||
def getSinglePointElevationFromBing(lat, lng):
|
def getSinglePointElevationFromBing(lat, lng):
|
||||||
#http://dev.virtualearth.net/REST/v1/Elevation/List?points={lat1,long1,lat2,long2,latN,longnN}&heights={heights}&key={BingMapsAPIKey}
|
#http://dev.virtualearth.net/REST/v1/Elevation/List?points={lat1,long1,lat2,long2,latN,longnN}&heights={heights}&key={BingMapsAPIKey}
|
||||||
|
import utm
|
||||||
|
|
||||||
source = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
|
source = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
|
||||||
source += str(lat) + "," + str(lng)
|
source += str(lat) + "," + str(lng)
|
||||||
source += "&heights=sealevel"
|
source += "&heights=sealevel"
|
||||||
@@ -153,11 +155,9 @@ def getSinglePointElevationFromBing(lat, lng):
|
|||||||
response = requests.get(source)
|
response = requests.get(source)
|
||||||
ans = response.text
|
ans = response.text
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
s = json.loads(ans)
|
||||||
|
print(s)
|
||||||
res = s['resourceSets'][0]['resources'][0]['elevations']
|
res = s['resourceSets'][0]['resources'][0]['elevations']
|
||||||
|
|
||||||
import utm
|
|
||||||
for elevation in res:
|
for elevation in res:
|
||||||
c = utm.from_latlon(lat, lng)
|
c = utm.from_latlon(lat, lng)
|
||||||
v = FreeCAD.Vector(
|
v = FreeCAD.Vector(
|
||||||
@@ -324,7 +324,6 @@ def getSinglePointElevationUtm(lat, lon):
|
|||||||
print (v)
|
print (v)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
def getElevationUTM(polygon, lat, lng, resolution = 10000):
|
def getElevationUTM(polygon, lat, lng, resolution = 10000):
|
||||||
|
|
||||||
import utm
|
import utm
|
||||||
@@ -448,47 +447,6 @@ def getElevation(lat, lon, b=50.35, le=11.17, size=40):
|
|||||||
FreeCADGui.updateGui()
|
FreeCADGui.updateGui()
|
||||||
return FreeCAD.activeDocument().ActiveObject
|
return FreeCAD.activeDocument().ActiveObject
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
# original::
|
|
||||||
def getElevation(lat, lon, b=50.35, le=11.17, size=40):
|
|
||||||
tm.lat = lat
|
|
||||||
tm.lon = lon
|
|
||||||
baseheight = 0 #getheight(tm.lat, tm.lon)
|
|
||||||
center = tm.fromGeographic(tm.lat, tm.lon)
|
|
||||||
|
|
||||||
#https://maps.googleapis.com/maps/api/elevation/json?path=36.578581,-118.291994|36.23998,-116.83171&samples=3&key=YOUR_API_KEY
|
|
||||||
#https://maps.googleapis.com/maps/api/elevation/json?locations=39.7391536,-104.9847034&key=YOUR_API_KEY
|
|
||||||
|
|
||||||
source = "https://maps.googleapis.com/maps/api/elevation/json?path="
|
|
||||||
source += str(b-size*0.001) + "," + str(le) + "|" + str(b+size*0.001) + "," + str(le)
|
|
||||||
source += "&samples=" + str(100)
|
|
||||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
|
||||||
|
|
||||||
response = urllib.request.urlopen(source)
|
|
||||||
ans = response.read()
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['results']
|
|
||||||
|
|
||||||
points = []
|
|
||||||
for r in res:
|
|
||||||
c = tm.fromGeographic(r['location']['lat'], r['location']['lng'])
|
|
||||||
v = FreeCAD.Vector(
|
|
||||||
round(c[0], 2),
|
|
||||||
round(c[1], 2),
|
|
||||||
round(r['elevation'] * 1000, 2) - baseheight
|
|
||||||
)
|
|
||||||
points.append(v)
|
|
||||||
|
|
||||||
line = Draft.makeWire(points, closed=False, face=False, support=None)
|
|
||||||
line.ViewObject.Visibility = False
|
|
||||||
#FreeCAD.activeDocument().recompute()
|
|
||||||
FreeCADGui.updateGui()
|
|
||||||
return FreeCAD.activeDocument().ActiveObject
|
|
||||||
'''
|
|
||||||
|
|
||||||
class _ImportPointsTaskPanel:
|
class _ImportPointsTaskPanel:
|
||||||
|
|
||||||
def __init__(self, obj = None):
|
def __init__(self, obj = None):
|
||||||
|
|||||||
@@ -578,22 +578,22 @@ class _PVPlantSite(ArchSite._Site):
|
|||||||
|
|
||||||
obj.addProperty("App::PropertyLink",
|
obj.addProperty("App::PropertyLink",
|
||||||
"Boundary",
|
"Boundary",
|
||||||
"Site",
|
"PVPlant",
|
||||||
"Boundary of land")
|
"Boundary of land")
|
||||||
|
|
||||||
obj.addProperty("App::PropertyLinkList",
|
obj.addProperty("App::PropertyLinkList",
|
||||||
"Frames",
|
"Frames",
|
||||||
"Site",
|
"PVPlant",
|
||||||
"Frames templates")
|
"Frames templates")
|
||||||
|
|
||||||
obj.addProperty("App::PropertyEnumeration",
|
obj.addProperty("App::PropertyEnumeration",
|
||||||
"UtmZone",
|
"UtmZone",
|
||||||
"Base",
|
"PVPlant",
|
||||||
"UTM zone").UtmZone = zone_list
|
"UTM zone").UtmZone = zone_list
|
||||||
|
|
||||||
obj.addProperty("App::PropertyVector",
|
obj.addProperty("App::PropertyVector",
|
||||||
"Origin",
|
"Origin",
|
||||||
"Base",
|
"PVPlant",
|
||||||
"Origin point.").Origin = (0, 0, 0)
|
"Origin point.").Origin = (0, 0, 0)
|
||||||
|
|
||||||
def onDocumentRestored(self, obj):
|
def onDocumentRestored(self, obj):
|
||||||
@@ -771,10 +771,12 @@ class _PVPlantSite(ArchSite._Site):
|
|||||||
import PVPlantImportGrid
|
import PVPlantImportGrid
|
||||||
x, y, zone_number, zone_letter = utm.from_latlon(lat, lon)
|
x, y, zone_number, zone_letter = utm.from_latlon(lat, lon)
|
||||||
self.obj.UtmZone = zone_list[zone_number - 1]
|
self.obj.UtmZone = zone_list[zone_number - 1]
|
||||||
# self.obj.UtmZone = "Z"+str(zone_number)
|
zz = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
|
||||||
#z = PVPlantImportGrid.get_elevation(lat, lon)
|
self.obj.Origin = FreeCAD.Vector(x * 1000, y * 1000, zz[0].z)
|
||||||
zz = PVPlantImportGrid.getSinglePointElevationFromBing(lat, lon)
|
#self.obj.OriginOffset = FreeCAD.Vector(x * 1000, y * 1000, 0) #??
|
||||||
self.obj.Origin = FreeCAD.Vector(x * 1000, y * 1000, zz.z)
|
self.obj.Latitude = lat
|
||||||
|
self.obj.Longitude = lon
|
||||||
|
self.obj.Elevation = zz[0].z
|
||||||
|
|
||||||
|
|
||||||
class _ViewProviderSite(ArchSite._ViewProviderSite):
|
class _ViewProviderSite(ArchSite._ViewProviderSite):
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ map.on('mousemove', function(e)
|
|||||||
MyApp.onMapMove(e.latlng.lat, e.latlng.lng);
|
MyApp.onMapMove(e.latlng.lat, e.latlng.lng);
|
||||||
|
|
||||||
const bounds = map.getBounds();
|
const bounds = map.getBounds();
|
||||||
MyApp.onMapZoom(bounds.getSouth(), bounds.getWest(), bounds.getNorth(), bounds.getEast());
|
MyApp.onMapZoom(bounds.getSouth(), bounds.getWest(), bounds.getNorth(), bounds.getEast(), map.getZoom());
|
||||||
});
|
});
|
||||||
|
|
||||||
var DrawShapes;
|
var DrawShapes;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/
|
# Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/
|
||||||
|
|
||||||
import math
|
import math
|
||||||
# from PIL import Image
|
from PIL import Image
|
||||||
import os
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ import urllib
|
|||||||
# alternativa a PIL: Image
|
# alternativa a PIL: Image
|
||||||
# CV2
|
# CV2
|
||||||
|
|
||||||
class GoogleMapDownloader:
|
class GoogleMapDownloader1:
|
||||||
"""
|
"""
|
||||||
A class which generates high resolution google maps images given
|
A class which generates high resolution google maps images given
|
||||||
a longitude, latitude and zoom level
|
a longitude, latitude and zoom level
|
||||||
|
|||||||
207
lib/GoogleSatelitalImageDownload.py
Normal file
207
lib/GoogleSatelitalImageDownload.py
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
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
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
||||||
<name>PVPlant</name>
|
<name>PVPlant</name>
|
||||||
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
||||||
<version>2025.02.22</version>
|
<version>2025.07.06</version>
|
||||||
<date>2025.02.22</date>
|
<date>2025.07.06</date>
|
||||||
<maintainer email="javier.branagutierrez@gmail.com">Javier Braña</maintainer>
|
<maintainer email="javier.branagutierrez@gmail.com">Javier Braña</maintainer>
|
||||||
<license file="LICENSE">LGPL-2.1-or-later</license>
|
<license file="LICENSE">LGPL-2.1-or-later</license>
|
||||||
<url type="repository" branch="main">https://homehud.duckdns.org/javier/PVPlant</url>
|
<url type="repository" branch="main">https://homehud.duckdns.org/javier/PVPlant</url>
|
||||||
|
|||||||
Reference in New Issue
Block a user