From c0291198b13c41f2d0bb8fe5abc6d9a8ad06bc8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Bra=C3=B1a?= Date: Fri, 28 Mar 2025 19:39:06 +0600 Subject: [PATCH] =?UTF-8?q?Importa=20carreteras=20y=20l=C3=ADneas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Importer/importOSM.py | 360 ++++++++++++++++++++++++++++++++---------- 1 file changed, 277 insertions(+), 83 deletions(-) diff --git a/Importer/importOSM.py b/Importer/importOSM.py index 3113640..00a56d5 100644 --- a/Importer/importOSM.py +++ b/Importer/importOSM.py @@ -1,90 +1,284 @@ -# *************************************************************************** -# * * -# * Copyright (c) 2016 microelly <> * -# * Copyright (c) 2020 Bernd Hahnebach * -# * Copyright (c) 2022 Hakan Seven * -# * * -# * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * -# * License along with this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -""" -Import data from OpenStreetMap -""" - import FreeCAD import Part +import Draft from xml.etree import ElementTree as ET +import ssl +import certifi import urllib.request -import urllib.parse +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]) -def import_osm_data(min_lat=40.41, min_lon=-3.71, max_lat=40.42, max_lon=-3.7): - # Configurar la consulta de Overpass API - overpass_url = "https://overpass-api.de/api/interpreter" - - query = f""" - [out:xml]; - ( - node({min_lat},{min_lon},{max_lat},{max_lon}); - way({min_lat},{min_lon},{max_lat},{max_lon}); - ); - out body; - >; - out skel qt; - """ - - print("Descargando datos de OSM...") - response = urllib.request.urlopen(overpass_url, data=query.encode('utf-8')) - osm_data = response.read() - - print("Procesando datos...") - root = ET.fromstring(osm_data) - nodes = {} - - if not FreeCAD.ActiveDocument: - FreeCAD.newDocument() - doc = FreeCAD.ActiveDocument - - # Creación de nodos corregida - for node in root.findall('node'): - node_id = node.attrib['id'] - lat = float(node.attrib['lat']) - lon = float(node.attrib['lon']) - nodes[node_id] = FreeCAD.Vector(lon, lat, 0) - - # Versión estable para todas las versiones de FreeCAD - point = Part.Vertex(nodes[node_id]) - Part.show(point, f"Node_{node_id}") - - # Procesar vías (manera alternativa) - for way in root.findall('way'): - way_points = [nodes[nd.attrib['ref']] - for nd in way.findall('nd') - if nd.attrib['ref'] in nodes] - - if len(way_points) > 1: try: - wire = Part.makePolygon(way_points) - Part.show(wire, f"Way_{way.attrib['id']}") - except Exception as e: - print(f"Error en vía {way.attrib['id']}: {str(e)}") - - print("Importación completada!") - FreeCAD.ActiveDocument.recompute() + polygon = Part.makePolygon(polygon_points) + face = Part.Face(polygon) + extruded = face.extrude(FreeCAD.Vector(0, 0, height) * scale - self.Origin ) -# Ejecutar la función -import_osm_data() \ No newline at end of file + 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']