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>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>715</width>
|
||||
<height>520</height>
|
||||
<width>462</width>
|
||||
<height>282</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -78,6 +78,11 @@
|
||||
<string>Lineweight</string>
|
||||
</property>
|
||||
</column>
|
||||
<item row="2" column="1">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -204,6 +209,21 @@
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<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>
|
||||
<widget class="QPushButton" name="buttonAcept">
|
||||
<property name="text">
|
||||
|
||||
@@ -524,6 +524,7 @@ def exportToPVC(path, exportTerrain = False):
|
||||
# TODO: revisar
|
||||
for typ in frameType:
|
||||
isTracker = "tracker" in typ.Proxy.Type.lower()
|
||||
isTracker = False
|
||||
|
||||
objectlist = FreeCAD.ActiveDocument.findObjects(Name="Tracker")
|
||||
tmp = []
|
||||
|
||||
@@ -9,6 +9,7 @@ import urllib.request
|
||||
import math
|
||||
import utm
|
||||
from collections import defaultdict
|
||||
import PVPlantImportGrid as ImportElevation
|
||||
|
||||
scale = 1000.0
|
||||
|
||||
@@ -42,8 +43,10 @@ class OSMImporter:
|
||||
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||
|
||||
def transform_from_latlon(self, lat, lon):
|
||||
x, y, _, _ = utm.from_latlon(lat, lon)
|
||||
return FreeCAD.Vector(x, y, .0) * scale - self.Origin
|
||||
point = ImportElevation.getElevationFromOE([[lat, lon], ])
|
||||
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):
|
||||
query = f"""
|
||||
@@ -148,7 +151,6 @@ class OSMImporter:
|
||||
polyline.addProperty("App::PropertyFloat", "Width", "Metadata", "Ancho de la vía").Width = width
|
||||
layer.addObject(polyline)
|
||||
|
||||
|
||||
def create_railway(self, nodes, layer, name=""):
|
||||
points = [n for n in nodes]
|
||||
rail_line = Draft.make_wire(points, closed=False, face=False)
|
||||
@@ -737,27 +739,163 @@ class OSMImporter:
|
||||
def create_vegetation(self):
|
||||
vegetation_layer = self.create_layer("Vegetation")
|
||||
|
||||
# Árboles individuales
|
||||
for node_id, coords in self.nodes.items():
|
||||
# Verificar si es un árbol
|
||||
# (Necesitarías procesar los tags de los nodos, implementación simplificada)
|
||||
cylinder = Part.makeCylinder(0.5, 5.0, FreeCAD.Vector(coords[0], coords[1], 0))# * scale - self.Origin )
|
||||
tree = FreeCAD.ActiveDocument.addObject("Part::Feature", "Tree")
|
||||
vegetation_layer.addObject(tree)
|
||||
tree.Shape = cylinder
|
||||
tree.ViewObject.ShapeColor = self.feature_colors['vegetation']
|
||||
# Procesar nodos de vegetación individual
|
||||
for way_id, tags in self.ways_data.items():
|
||||
coords = self.nodes.get(way_id)
|
||||
if not coords:
|
||||
continue
|
||||
|
||||
# Á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():
|
||||
if 'natural' in data['tags'] or 'landuse' in data['tags']:
|
||||
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]
|
||||
polygon = Part.makePolygon(polygon_points)
|
||||
face = Part.Face(polygon)
|
||||
area = vegetation_layer.addObject("Part::Feature", "GreenArea")
|
||||
area.Shape = face
|
||||
area.ViewObject.ShapeColor = self.feature_colors['vegetation']
|
||||
tags = data['tags']
|
||||
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
|
||||
|
||||
if not nodes or len(nodes) < 3:
|
||||
continue
|
||||
|
||||
if tags.get('natural') == 'wood' or tags.get('landuse') == 'forest':
|
||||
self.create_forest(nodes, tags, vegetation_layer)
|
||||
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):
|
||||
water_layer = self.create_layer("Water")
|
||||
@@ -769,7 +907,8 @@ class OSMImporter:
|
||||
polygon_points = [n for n in nodes]
|
||||
polygon = Part.makePolygon(polygon_points)
|
||||
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.ViewObject.ShapeColor = self.feature_colors['water']
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ class MapWindow(QtGui.QWidget):
|
||||
self.maxLat = None
|
||||
self.minLon = None
|
||||
self.maxLon = None
|
||||
self.zoom = None
|
||||
self.WinTitle = WinTitle
|
||||
self.georeference_coordinates = {'lat': None, 'lon': None}
|
||||
self.setupUi()
|
||||
|
||||
def setupUi(self):
|
||||
@@ -152,6 +154,9 @@ class MapWindow(QtGui.QWidget):
|
||||
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
||||
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)
|
||||
RightLayout.addItem(verticalSpacer)
|
||||
|
||||
@@ -192,6 +197,7 @@ class MapWindow(QtGui.QWidget):
|
||||
"var data = drawnItems.toGeoJSON();"
|
||||
"MyApp.shapes(JSON.stringify(data));"
|
||||
)
|
||||
|
||||
self.close()
|
||||
|
||||
@QtCore.Slot(float, float)
|
||||
@@ -203,17 +209,22 @@ class MapWindow(QtGui.QWidget):
|
||||
' | UTM: ' + str(zone_number) + zone_letter +
|
||||
', {:.5f}m E, {:.5f}m N'.format(x, y))
|
||||
|
||||
@QtCore.Slot(float, float, float, float)
|
||||
def onMapZoom(self, minLat, minLon, maxLat, maxLon):
|
||||
@QtCore.Slot(float, float, float, float, int)
|
||||
def onMapZoom(self, minLat, minLon, maxLat, maxLon, zoom):
|
||||
self.minLat = min([minLat, maxLat])
|
||||
self.maxLat = max([minLat, maxLat])
|
||||
self.minLon = min([minLon, maxLon])
|
||||
self.maxLon = max([minLon, maxLon])
|
||||
self.zoom = zoom
|
||||
|
||||
@QtCore.Slot(float, float)
|
||||
def georeference(self, lat, lng):
|
||||
import PVPlantSite
|
||||
from geopy.geocoders import Nominatim
|
||||
|
||||
self.georeference_coordinates['lat'] = lat
|
||||
self.georeference_coordinates['lon'] = lng
|
||||
|
||||
Site = PVPlantSite.get(create=True)
|
||||
Site.Proxy.setLatLon(lat, lng)
|
||||
|
||||
@@ -278,7 +289,7 @@ class MapWindow(QtGui.QWidget):
|
||||
pts = [p.sub(offset) for p in tmp]
|
||||
|
||||
obj = Draft.makeWire(pts, closed=cw, face=False)
|
||||
#obj.Placement.Base = offset
|
||||
#obj.Placement.Base = Site.Origin
|
||||
obj.Label = name
|
||||
Draft.autogroup(obj)
|
||||
|
||||
@@ -288,6 +299,170 @@ class MapWindow(QtGui.QWidget):
|
||||
if self.checkboxImportGis.isChecked():
|
||||
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):
|
||||
import Importer.importOSM as importOSM
|
||||
import PVPlantSite
|
||||
|
||||
@@ -125,9 +125,9 @@ def getElevationFromOE(coordinates):
|
||||
points = []
|
||||
for i, point in enumerate(coordinates):
|
||||
c = utm.from_latlon(point[0], point[1])
|
||||
points.append(FreeCAD.Vector(round(c[0] * 1000, 0),
|
||||
round(c[1] * 1000, 0),
|
||||
0))
|
||||
points.append(FreeCAD.Vector(round(c[0], 0),
|
||||
round(c[1], 0),
|
||||
0) * 1000)
|
||||
return points
|
||||
|
||||
# Only get the json response in case of 200 or 201
|
||||
@@ -136,14 +136,16 @@ def getElevationFromOE(coordinates):
|
||||
results = r.json()
|
||||
for point in results["results"]:
|
||||
c = utm.from_latlon(point["latitude"], point["longitude"])
|
||||
v = FreeCAD.Vector(round(c[0] * 1000, 0),
|
||||
round(c[1] * 1000, 0),
|
||||
round(point["elevation"] * 1000, 0))
|
||||
v = FreeCAD.Vector(round(c[0], 0),
|
||||
round(c[1], 0),
|
||||
round(point["elevation"], 0)) * 1000
|
||||
points.append(v)
|
||||
return points
|
||||
|
||||
def getSinglePointElevationFromBing(lat, lng):
|
||||
#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 += str(lat) + "," + str(lng)
|
||||
source += "&heights=sealevel"
|
||||
@@ -153,11 +155,9 @@ def getSinglePointElevationFromBing(lat, lng):
|
||||
response = requests.get(source)
|
||||
ans = response.text
|
||||
|
||||
# +# to do: error handling - wait and try again
|
||||
s = json.loads(ans)
|
||||
print(s)
|
||||
res = s['resourceSets'][0]['resources'][0]['elevations']
|
||||
|
||||
import utm
|
||||
for elevation in res:
|
||||
c = utm.from_latlon(lat, lng)
|
||||
v = FreeCAD.Vector(
|
||||
@@ -324,7 +324,6 @@ def getSinglePointElevationUtm(lat, lon):
|
||||
print (v)
|
||||
return v
|
||||
|
||||
|
||||
def getElevationUTM(polygon, lat, lng, resolution = 10000):
|
||||
|
||||
import utm
|
||||
@@ -448,47 +447,6 @@ def getElevation(lat, lon, b=50.35, le=11.17, size=40):
|
||||
FreeCADGui.updateGui()
|
||||
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:
|
||||
|
||||
def __init__(self, obj = None):
|
||||
|
||||
@@ -578,22 +578,22 @@ class _PVPlantSite(ArchSite._Site):
|
||||
|
||||
obj.addProperty("App::PropertyLink",
|
||||
"Boundary",
|
||||
"Site",
|
||||
"PVPlant",
|
||||
"Boundary of land")
|
||||
|
||||
obj.addProperty("App::PropertyLinkList",
|
||||
"Frames",
|
||||
"Site",
|
||||
"PVPlant",
|
||||
"Frames templates")
|
||||
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"UtmZone",
|
||||
"Base",
|
||||
"PVPlant",
|
||||
"UTM zone").UtmZone = zone_list
|
||||
|
||||
obj.addProperty("App::PropertyVector",
|
||||
"Origin",
|
||||
"Base",
|
||||
"PVPlant",
|
||||
"Origin point.").Origin = (0, 0, 0)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
@@ -771,10 +771,12 @@ class _PVPlantSite(ArchSite._Site):
|
||||
import PVPlantImportGrid
|
||||
x, y, zone_number, zone_letter = utm.from_latlon(lat, lon)
|
||||
self.obj.UtmZone = zone_list[zone_number - 1]
|
||||
# self.obj.UtmZone = "Z"+str(zone_number)
|
||||
#z = PVPlantImportGrid.get_elevation(lat, lon)
|
||||
zz = PVPlantImportGrid.getSinglePointElevationFromBing(lat, lon)
|
||||
self.obj.Origin = FreeCAD.Vector(x * 1000, y * 1000, zz.z)
|
||||
zz = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
|
||||
self.obj.Origin = FreeCAD.Vector(x * 1000, y * 1000, zz[0].z)
|
||||
#self.obj.OriginOffset = FreeCAD.Vector(x * 1000, y * 1000, 0) #??
|
||||
self.obj.Latitude = lat
|
||||
self.obj.Longitude = lon
|
||||
self.obj.Elevation = zz[0].z
|
||||
|
||||
|
||||
class _ViewProviderSite(ArchSite._ViewProviderSite):
|
||||
|
||||
@@ -41,7 +41,7 @@ map.on('mousemove', function(e)
|
||||
MyApp.onMapMove(e.latlng.lat, e.latlng.lng);
|
||||
|
||||
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;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/
|
||||
|
||||
import math
|
||||
# from PIL import Image
|
||||
from PIL import Image
|
||||
import os
|
||||
import urllib
|
||||
|
||||
@@ -15,7 +15,7 @@ import urllib
|
||||
# alternativa a PIL: Image
|
||||
# CV2
|
||||
|
||||
class GoogleMapDownloader:
|
||||
class GoogleMapDownloader1:
|
||||
"""
|
||||
A class which generates high resolution google maps images given
|
||||
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">
|
||||
<name>PVPlant</name>
|
||||
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
||||
<version>2025.02.22</version>
|
||||
<date>2025.02.22</date>
|
||||
<version>2025.07.06</version>
|
||||
<date>2025.07.06</date>
|
||||
<maintainer email="javier.branagutierrez@gmail.com">Javier Braña</maintainer>
|
||||
<license file="LICENSE">LGPL-2.1-or-later</license>
|
||||
<url type="repository" branch="main">https://homehud.duckdns.org/javier/PVPlant</url>
|
||||
|
||||
Reference in New Issue
Block a user