Importa carreteras y líneas

This commit is contained in:
2025-03-28 19:39:06 +06:00
parent 4981b00918
commit c0291198b1

View File

@@ -1,90 +1,284 @@
# ***************************************************************************
# * *
# * Copyright (c) 2016 microelly <> *
# * Copyright (c) 2020 Bernd Hahnebach <bernd@bimstatik.org> *
# * Copyright (c) 2022 Hakan Seven <hakanseven12@gmail.com> *
# * *
# * 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()
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']