Importa carreteras y líneas
This commit is contained in:
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user