import FreeCAD import Part import Draft from xml.etree import ElementTree as ET import ssl import certifi import urllib.request import math import utm from collections import defaultdict scale = 1000.0 class OSMImporter: def __init__(self, origin): self.Origin = origin if origin is None: self.Origin = FreeCAD.Vector(0, 0, 0) self.overpass_url = "https://overpass-api.de/api/interpreter" self.nodes = {} self.ways_data = defaultdict(dict) self.feature_colors = { 'building': (0.8, 0.8, 0.6), 'highway': { 'motorway': (1.0, 0.4, 0.4), 'trunk': (1.0, 0.6, 0.4), 'primary': (1.0, 0.8, 0.4), 'secondary': (1.0, 1.0, 0.4), 'tertiary': (0.8, 1.0, 0.4), 'residential': (0.6, 0.6, 0.6) }, 'railway': (0.4, 0.4, 0.4), 'power': { 'line': (0.0, 0.0, 0.0), 'tower': (0.3, 0.3, 0.3), 'substation': (0.8, 0.0, 0.0) }, 'vegetation': (0.4, 0.8, 0.4), 'water': (0.4, 0.6, 1.0) } def transformFromLatLon(self, lat, lon): x, y, _, _ = utm.from_latlon(lat, lon) return (x, y, 0) * 1000 def get_osm_data(self, bbox): query = f""" [out:xml][bbox:{bbox}]; ( way["building"]; way["highway"]; way["railway"]; way["power"="line"]; way["power"="substation"]; way["natural"="water"]; way["landuse"="forest"]; node["natural"="tree"]; ); (._;>;); out body; """ # Configurar contexto SSL seguro ssl_context = ssl.create_default_context(cafile=certifi.where()) # Modificar tu código de descarga response = urllib.request.urlopen( self.overpass_url, data=query.encode('utf-8'), context=ssl_context, timeout=30 ) return response.read() def create_layer(self, name): if not FreeCAD.ActiveDocument.getObject(name): layer = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name) return layer return FreeCAD.ActiveDocument.getObject(name) def process_osm_data(self, osm_data): root = ET.fromstring(osm_data) # Primera pasada: almacenar todos los nodos for node in root.findall('node'): '''self.nodes[node.attrib['id']] = ( float(node.attrib['lon']), float(node.attrib['lat']), 0)''' self.nodes[node.attrib['id']] = self.transformFromLatLon( float(node.attrib['lat']), float(node.attrib['lon']) ) # Segunda pasada: procesar ways y relaciones for way in root.findall('way'): tags = {tag.attrib['k']: tag.attrib['v'] for tag in way.findall('tag')} nodes = [nd.attrib['ref'] for nd in way.findall('nd')] self.ways_data[way.attrib['id']] = {'tags': tags, 'nodes': nodes} self.create_transportation() self.create_buildings() self.create_power_infrastructure() self.create_vegetation() self.create_water_bodies() def create_transportation(self): transport_layer = self.create_layer("Transport") for way_id, data in self.ways_data.items(): tags = data['tags'] nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes] if len(nodes) < 2: continue # Carreteras if 'highway' in tags: highway_type = tags['highway'] width = { 'motorway': 10.0, 'trunk': 8.0, 'primary': 6.0, 'secondary': 5.0, 'tertiary': 4.0 }.get(highway_type, 3.0) self.create_road(nodes, width, highway_type, transport_layer) # Vías férreas if 'railway' in tags: self.create_railway(nodes, transport_layer) def create_road(self, nodes, width, road_type, layer): points = [FreeCAD.Vector(n[0], n[1], .0) * scale - self.Origin for n in nodes] polyline = Draft.make_wire(points, closed=False, face=False) polyline.Label = f"Road_{road_type}" polyline.ViewObject.LineWidth = 2.0 polyline.ViewObject.ShapeColor = self.feature_colors['highway'].get(road_type, (0.5, 0.5, 0.5)) polyline.addProperty("App::PropertyString", "OSMType", "Metadata", "Tipo de vía").OSMType = road_type polyline.addProperty("App::PropertyFloat", "Width", "Metadata", "Ancho de la vía").Width = width layer.addObject(polyline) def create_railway(self, nodes, layer): points = [FreeCAD.Vector(n[0], n[1], .0) * scale - self.Origin for n in nodes] rail_line = Draft.make_wire(points, closed=False, face=False) rail_line.Label = "Railway" rail_line.ViewObject.LineWidth = 1.5 rail_line.ViewObject.ShapeColor = self.feature_colors['railway'] layer.addObject(rail_line) def create_buildings(self): building_layer = self.create_layer("Buildings") for way_id, data in self.ways_data.items(): if 'building' not in data['tags']: continue tags = data['tags'] nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes] if len(nodes) < 3: continue # Calcular altura height = self.get_building_height(tags) # Crear polígono base polygon_points = [FreeCAD.Vector(n[0], n[1], .0) * scale - self.Origin for n in nodes] if polygon_points[0] != polygon_points[-1]: polygon_points.append(polygon_points[0]) try: polygon = Part.makePolygon(polygon_points) face = Part.Face(polygon) extruded = face.extrude(FreeCAD.Vector(0, 0, height) * scale - self.Origin ) building = building_layer.addObject("Part::Feature", f"Building_{way_id}") building.Shape = extruded building.Label = f"Building ({height}m)" building.ViewObject.ShapeColor = self.feature_colors['building'] # Metadatos building.addProperty("App::PropertyFloat", "Height", "Metadata", "Altura del edificio").Height = height if 'building:levels' in tags: building.addProperty("App::PropertyInteger", "Levels", "Metadata", "Niveles del edificio").Levels = int(tags['building:levels']) except Exception as e: print(f"Error en edificio {way_id}: {str(e)}") def get_building_height(self, tags): # Lógica de cálculo de altura if 'height' in tags: try: return float(tags['height'].split()[0]) except: pass if 'building:levels' in tags: try: return float(tags['building:levels']) * 3.0 except: pass return 5.0 # Altura por defecto def create_power_infrastructure(self): power_layer = self.create_layer("Power") for way_id, data in self.ways_data.items(): tags = data['tags'] nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes] if 'power' in tags: if tags['power'] == 'line': self.create_power_line(nodes, power_layer) elif tags['power'] == 'substation': self.create_substation(nodes, power_layer) def create_power_line(self, nodes, layer): # Torres de alta tensión for node in nodes: cylinder = Part.makeCylinder(1.0, 20.0, FreeCAD.Vector(node[0], node[1], 0) * scale - self.Origin ) pole = FreeCAD.ActiveDocument.addObject("Part::Feature", "PowerPole") layer.addObject(pole) pole.Shape = cylinder pole.ViewObject.ShapeColor = self.feature_colors['power']['tower'] # Líneas eléctricas points = [FreeCAD.Vector(n[0], n[1], .0) * scale - self.Origin for n in nodes] cable = Draft.make_wire(points, closed=False, face=False) cable.ViewObject.LineWidth = 3.0 cable.ViewObject.ShapeColor = self.feature_colors['power']['line'] layer.addObject(cable) def create_substation(self, nodes, layer): # Crear área de subestación polygon_points = [FreeCAD.Vector(n[0], n[1], 0) * scale - self.Origin for n in nodes] if len(polygon_points) > 2: polygon = Part.makePolygon(polygon_points) face = Part.Face(polygon) substation = FreeCAD.ActiveDocument.addObject("Part::Feature", "Substation") layer.addObject(substation) substation.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.5) * scale - self.Origin ) substation.ViewObject.ShapeColor = self.feature_colors['power']['substation'] 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'] # Áreas verdes 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 = [FreeCAD.Vector(n[0], n[1], 0) * scale - self.Origin 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'] def create_water_bodies(self): water_layer = self.create_layer("Water") for way_id, data in self.ways_data.items(): if 'natural' in data['tags'] and data['tags']['natural'] == 'water': nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes] if len(nodes) > 2: polygon_points = [FreeCAD.Vector(n[0], n[1], 0) * scale - self.Origin for n in nodes] polygon = Part.makePolygon(polygon_points) face = Part.Face(polygon) water = water_layer.addObject("Part::Feature", "WaterBody") water.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1) * scale - self.Origin ) water.ViewObject.ShapeColor = self.feature_colors['water']