def terrain_geotiff_min_elevation_apply(obj, ddd_proj): elevation = ElevationModel.instance() min_h = None for v in obj.vertex_iterator(): v_h = elevation.value(transform_ddd_to_geo(ddd_proj, [v[0], v[1]])) if min_h is None: min_h = v_h if v_h < min_h: min_h = v_h if min_h is None: raise DDDException("Cannot calculate min value for elevation: %s" % obj) #func = lambda x, y, z, i: [x, y, z + min_h] obj = obj.translate([0, 0, min_h]) obj.extra['_terrain_geotiff_min_elevation_apply:elevation'] = min_h #mesh.mesh.invert() return obj
def apply_components(self, methodname, *args, **kwargs): """ Applies a method with arguments to all applicable components in this object (eg. apply transformation to colliders). Does not act on children. """ for k in ('ddd:collider:primitive', ): if k in self.extra: comp = self.extra[k] if isinstance(comp, DDDObject): try: method_to_call = getattr(comp, methodname) self.extra[k] = method_to_call(*args, **kwargs) except Exception as e: print(method_to_call.__code__) raise DDDException( "Cannot apply method to components of %s component %s: %s" % (self, comp, e))
def load(self, configfiles): if not isinstance(configfiles, str): for filename in configfiles: self.load(filename) return # Load file logger.debug("Loading pipeline config: %s", configfiles) if configfiles.endswith(".py"): configfiles = configfiles[:-3] try: script_abspath = os.path.abspath(configfiles) script_dirpath = os.path.dirname(configfiles) #sys.path.append(script_dirpath) sys.path.append("..") importlib.import_module( configfiles) #, globals={'ddd_bootstrap': self}) except ModuleNotFoundError as e: raise DDDException("Could not load pipeline definition file: %s" % configfiles) self.tasks = DDDTask._tasks
def __init__(self, name=None, path=None, select=None, filter=None, order=None, #parent=None, before=None, after=None, log=None, recurse=False, condition=False, cache=False): self.name = name self.order = order self._order_num = None #self.parent = parent #self.before = before #self.after = after self.condition = condition self.cache = cache self.log = log self.path = path self.filter = filter self.recurse = recurse self.replace = True try: self.selector = DDDSelector(select) if select else None except Exception as e: logger.error("Invalid selector: %s", select) #raise DDDException("Invalid selector: %s", select) raise # Sanity check if (self.path or self.selector or self.filter) and (self.condition): raise DDDException("A task cannot have both path/selector/filter and a condition parameters.") # TODO: Do this in the decorator, not here. Registry shall possisbly be separate, what if someone needs an unregistered task DDDTask._tasks.append(self)
def generate_building_3d_part_body(self, part): # temporarily, should be in metadata floor_0_height = part.extra.get('ddd:building:level:0:height') floors = part.extra.get('ddd:building:levels') floors_min = part.extra.get('ddd:building:min_level') roof_height = parse_meters(part.get('ddd:roof:height')) try: floors_min = int(floors_min) floors = int(floors) except Exception as e: floors_min = 0 floors = 2 logger.error("Invalid ddd:building: attributes (levels, min_level...) in building %s: %s", part, e) floors_height = floor_0_height + (floors - 1) * 3.00 floors_min_height = floors_min * 3.00 min_height = parse_meters(part.extra.get('osm:min_height', floors_min_height)) #max_height = parse_meters(part.extra.get('osm:height', floors_height + min_height)) - roof_height max_height = parse_meters(part.extra.get('osm:height', floors_height)) - roof_height dif_height = max_height - min_height if dif_height == 0: #logger.warn("Building with 0 height: %s (skipping)", part) raise DDDException("Building with 0 height: %s (skipping)" % part) material = None material_name = part.get('ddd:building:material') if material_name: if hasattr(ddd.mats, material_name): material = getattr(ddd.mats, material_name) material_name = part.get('ddd:building:facade:material', part.get('osm:building:facade:material', None)) if material_name: if hasattr(ddd.mats, material_name): material = getattr(ddd.mats, material_name) # Generate building procedurally floor_type = 'default' if part.get('osm:building', None) == 'roof': floor_type = 'columns' if floor_type == 'default': try: building_3d = part.extrude(dif_height) #self.generate_building_3d_part_body_hull(part) except ValueError as e: raise DDDException("Could not generate building part body (%s): %s" % (part, e)) elif floor_type == 'columns': return part.copy3() else: raise DDDException("Cannot generate building body, invalid floor_type=%s: %s" % (floor_type, part)) if min_height == 0: building_3d = ddd.meshops.remove_faces_pointing(building_3d, ddd.VECTOR_DOWN) if min_height: building_3d = building_3d.translate([0, 0, min_height]) building_3d = building_3d.material(material) # Building solid post processing if part.extra.get('osm:tower:type', None) == 'bell_tower': # and dif_height > 6: # Cut center_pos = part.centroid().geom.coords[0] (axis_major, axis_minor, axis_rot) = ddd.geomops.oriented_axis(part) cut1 = ddd.rect([-axis_major.length(), -axis_minor.length() * 0.20, +axis_major.length(), +axis_minor.length() * 0.20]) cut2 = ddd.rect([-axis_major.length() * 0.20, -axis_minor.length(), +axis_major.length() * 0.20, +axis_minor.length()]) cuts = ddd.group2([cut1, cut2]).union().rotate(axis_rot).extrude(-6.0).translate([center_pos[0], center_pos[1], max_height - 2]) #ddd.group3([building_3d, cuts]).show() building_3d = building_3d.subtract(cuts) #building_3d.show() # TODO: Create 1D items (axis_major, axis_minor, axis_rot) = ddd.geomops.oriented_axis(part.buffer(-0.80)) for coords in (axis_major.geom.coords[0], axis_major.geom.coords[1], axis_minor.geom.coords[0], axis_minor.geom.coords[1]): bell = urban.bell().translate([coords[0], coords[1], max_height - 3.0]) #entire_building_3d.append(bell) building_3d.append(bell) if part.get('osm:man_made', None) == 'water_tower': # and dif_height > 6: # TODO: Create this before, as a building part logger.info("Creating water tower: %s", part) tower_center = part.centroid() tower_radius = tower_center.distance(part.outline()) tower_height = 32 + tower_radius * 2 tower_top_height = tower_radius * 1.4 - 1.5 tower_base = tower_center.buffer(tower_radius * 0.35, resolution=4, cap_style=ddd.CAP_ROUND) tower_top = tower_center.buffer(tower_radius, resolution=4, cap_style=ddd.CAP_ROUND) tower = tower_base.extrude_step(tower_base, tower_height - tower_top_height, base=False) tower = tower.extrude_step(tower_top, tower_top_height - 1.5) tower = tower.extrude_step(tower_top, 1.5) tower = tower.material(material) tower = ddd.uv.map_cubic(tower) tower = tower.translate([0, 0, dif_height]) building_3d.append(tower) # Base if 'osm:building:part' not in part.extra: if random.uniform(0, 1) < 0.2: base = part.buffer(0.3, cap_style=2, join_style=2).extrude(1.00) base = base.material(random.choice([ddd.mats.building_1, ddd.mats.building_2, ddd.mats.building_3, ddd.mats.building_4, ddd.mats.building_5])) building_3d.append(base) building_3d = ddd.uv.map_cubic(building_3d) # Items processing (per floor) min_height_accum = 0 for floor_num in range(floors): floor_height = floor_0_height if floor_num == 0 else 3.0 # Get floor height, check whether it matches current building part height range # TODO: propagate ddd:floor:min/max and allow each floor object to decide if it can be drawn if (min_height_accum < min_height or min_height_accum + floor_height > max_height): # Floor not in facade range min_height_accum = min_height_accum + floor_height continue for parti in part.individualize(always=True).children: # Individualizing to allow for outline afterwards, not needed if floor metada nodes were already created by this time self.generate_building_3d_part_body_items_floor(building_3d, parti, floor_num, min_height_accum, floor_height) min_height_accum = min_height_accum + floor_height return building_3d
def extrude_step(obj, shape, offset, cap=True, method=EXTRUSION_METHOD_WRAP): """ Extrude a shape into another. """ last_shape = obj.extra['_extrusion_last_shape'] #obj = last_shape.individualize() #shape = shape.individualize() #obj_a.assert_single() #obj_b.assert_single() geom_a = last_shape.geom geom_b = shape.geom result = obj.copy() if geom_a is None or geom_a.is_empty: logger.debug( "Should be extruding from point or line, ignoring and returning argument." ) # Previous step was empty, avoid destroy the object # TODO: this can be improved, previous could be point or line return result elif geom_a.type in ('MultiPolygon', 'GeometryCollection'): logger.warn( "Cannot extrude a step from a 'MultiPolygon' or 'GeometryCollection'." ) return result if geom_b is None: logger.warn( "Cannot extrude-step to None (ignoring and returning argument).") return result elif geom_b.type in ('MultiPolygon', 'GeometryCollection'): logger.warn( "Cannot extrude a step to a 'MultiPolygon' or 'GeometryCollection' (skipping/flattening)." ) geom_b = Point() elif geom_b.is_empty and method == EXTRUSION_METHOD_WRAP: logger.debug("Extruding to point (should be using line too).") geom_b = geom_a.centroid elif geom_b.type == "LineString" and method == EXTRUSION_METHOD_WRAP: geom_b = Polygon(list(geom_b.coords) + [geom_b.coords[0]]) elif geom_b.is_empty and method == EXTRUSION_METHOD_SUBTRACT: logger.debug( "Cannot extrude subtract to empty geometry. Skipping / flattening." ) elif geom_b.type == "LineString" and method == EXTRUSION_METHOD_SUBTRACT: logger.info( "Cannot extrude subtract to linestring. Skipping / flattening.") geom_b = Point() vertices = list(result.mesh.vertices) if result.mesh else [] faces = list(result.mesh.faces) if result.mesh else [] # Remove previous last cap before extruding. last_cap_idx = result.extra.get('_extrusion_last_cap_idx', None) if last_cap_idx is not None: faces = faces[:last_cap_idx] if not geom_b.is_empty: result.extra['_extrusion_last_shape'] = shape result.extra['_extrusion_last_offset'] = obj.extra.get( '_extrusion_last_offset', 0) + offset if not (geom_a.is_empty or geom_b.is_empty): mesh = None if method == EXTRUSION_METHOD_WRAP: mesh = extrude_between_geoms_wrap( geom_a, geom_b, offset, obj.extra.get('_extrusion_last_offset', 0)) elif method == EXTRUSION_METHOD_SUBTRACT: try: mesh = extrude_between_geoms_subtract( last_shape, shape, offset, obj.extra.get('_extrusion_last_offset', 0)) except DDDException as e: logger.error( "Could not extrude subtract between geometries (%s): %s", obj, e) mesh = None result.extra['_extrusion_last_shape'] = last_shape result.extra['_extrusion_last_offset'] = result.extra[ '_extrusion_last_offset'] - offset else: raise DDDException("Invalid extrusion method: %s", method) if mesh: faces = faces + [[ f[0] + len(vertices), f[1] + len(vertices), f[2] + len(vertices) ] for f in mesh.faces] vertices = vertices + list(mesh.vertices) result.extra[ '_extrusion_steps'] = result.extra['_extrusion_steps'] + 1 result.extra['_extrusion_last_cap_idx'] = len(faces) if cap and not result.extra['_extrusion_last_shape'].geom.is_empty: #print(result.extra['_extrusion_last_shape']) #result.extra['_extrusion_last_shape'].dump() #print(result.extra['_extrusion_last_shape'].geom) try: cap_mesh = result.extra['_extrusion_last_shape'].triangulate( ).translate([0, 0, result.extra.get('_extrusion_last_offset', 0)]) if cap_mesh and cap_mesh.mesh: faces = faces + [[ f[0] + len(vertices), f[1] + len(vertices), f[2] + len(vertices) ] for f in cap_mesh.mesh.faces] vertices = vertices + list(cap_mesh.mesh.vertices) else: result.extra['_extrusion_last_offset'] = result.extra[ '_extrusion_last_offset'] - offset cap_mesh = last_shape.triangulate().translate( [0, 0, result.extra['_extrusion_last_offset']]) faces = faces + [[ f[0] + len(vertices), f[1] + len(vertices), f[2] + len(vertices) ] for f in cap_mesh.mesh.faces] vertices = vertices + list(cap_mesh.mesh.vertices) except Exception as e: logger.error( "Could not generate extrude cap triangulation (%s): %s", result.extra['_extrusion_last_shape'], e) cap_mesh = None # Merge if len(vertices) > 0 and len(faces) > 0: mesh = Trimesh(vertices, faces) mesh.merge_vertices() #mesh.fix_normals() result.mesh = mesh return result
def extrude_between_geoms_subtract(shape_a, shape_b, offset, base_height): """ This extrusion method works on the assumption that geometries are contained one within another. This is useful for extrusion of areas or roofs, where each step is known to be entirely inside or outside of the shape of previous steps, including possible holes. This method works for convex and concave shapes. This method calculates the containment relation between shapes and subtracts them, triangulates the result and adjusts height of vertices. """ from ddd.ddd import ddd #shape_a = shape_a.intersection(ddd.rect(shape_a.bounds())) #shape_b = shape_b.intersection(ddd.rect(shape_b.bounds())) shape_a.validate() shape_b.validate() inverted = False big, small = None, None if shape_a.geom.equals(shape_b.geom): # Calculate vertical extrusion vert = shape_a.extrude(offset, cap=False, base=False).translate([0, 0, base_height]) return vert.mesh elif shape_a.contains(shape_b): big, small = shape_a, shape_b elif shape_b.contains(shape_a): big, small = shape_b, shape_a inverted = True else: raise DDDException( "Cannot extrude-subtract between shapes as one is not contained within another.", ddd_obj=ddd.group2([shape_a, shape_b.material(ddd.mats.highlight)])) # Calculate difference and set heights diff = big.subtract(small).triangulate() shape_a_coords = list(shape_a.geom.exterior.coords) for g in shape_a.geom.interiors: shape_a_coords.extend(list(g.coords)) shape_b_coords = list(shape_b.geom.exterior.coords) for g in shape_b.geom.interiors: shape_b_coords.extend(list(g.coords)) """ shape_b_coords = list(shape_b.geom.exterior.coords) if shape_b.geom.type == "Polygon" else list(shape_b.geom.coords) if shape_b.geom.type == "Polygon": for g in shape_b.geom.interiors: shape_b_coords.extend(list(g.coords)) """ def func(x, y, z, i): if (x, y) in shape_a_coords or (x, y, z) in shape_a_coords: z = base_height elif (x, y) in shape_b_coords or (x, y, z) in shape_b_coords: z = base_height + offset else: logger.warn( "Could not match coordinate during extrusion-subtract (%s, %s, %s) between %s and %s.", x, y, z, shape_a, shape_b) z = base_height return x, y, z diff = diff.vertex_func(func) if inverted: diff.mesh.invert() return diff.combine().mesh
def export_svg(obj, instance_mesh=True, instance_marker=False, size_min=1.0, scale=1.0, margin=0.00): """ Produces a complete SVG document. By default, no margin is produced (margin=0). 0.04 is the default for R plots. If scale is specified, size is multiplied. This is done after calculating size from bounds and applying min/max sizes. """ #size_min = 1 size_max = 4096 # TODO: we don't need to recurse geometry here, we can get bounds directly from object geoms = obj.geom_recursive() geom = geometry.GeometryCollection(geoms) svg_top = ('<svg xmlns="http://www.w3.org/2000/svg" ' + 'xmlns:xlink="http://www.w3.org/1999/xlink" ') if geom.is_empty: return svg_top + '/>' else: # Establish SVG canvas that will fit all the data + small space xmin, ymin, xmax, ymax = geom.bounds if xmin == xmax and ymin == ymax: # This is a point; buffer using an arbitrary size xmin, ymin, xmax, ymax = geom.buffer(1).bounds else: # Expand bounds by a fraction of the data ranges expand = margin # 0.04 or 4%, same as R plots widest_part = max([xmax - xmin, ymax - ymin]) expand_amount = widest_part * expand xmin -= expand_amount ymin -= expand_amount xmax += expand_amount ymax += expand_amount dx = xmax - xmin dy = ymax - ymin width = min([max([size_min, dx]), size_max]) height = min([max([size_min, dy]), size_max]) width = width * scale height = height * scale ''' try: scale_factor = max([dx, dy]) / max([width, height]) except ZeroDivisionError: scale_factor = 1. ''' view_box = "{} {} {} {}".format(xmin, ymin, dx, dy) transform = "matrix(1,0,0,-1,0,{})".format(ymax + ymin) try: svg_obj = DDDSVG.svg_obj(obj, instance_mesh=instance_mesh, instance_marker=instance_marker) except Exception as e: logger.error("Could not export SVG object %s: %s", obj, e) raise DDDException("Could not export SVG object %s: %s" % (obj, e)) return svg_top + ( 'width="{1}" height="{2}" viewBox="{0}" ' 'preserveAspectRatio="xMinYMin meet">' '<g transform="{3}">{4}</g></svg>' ).format(view_box, width, height, transform, svg_obj)
def generate_areas_2d_postprocess_cut_outlines(self, areas_2d, ways_2d): """ """ """ areas_2d_original = ddd.group2() for a in areas_2d.children: if a.extra.get('ddd:area:original', None): if a.extra.get('ddd:area:original') not in areas_2d_original.children: areas_2d_original.append(a.extra.get('ddd:area:original')) """ #areas = areas_2d.select('["ddd:area:area"]').children areas_2d_original = areas_2d.select( '["ddd:area:original"]') # ;["ddd:area:interways"]') logger.info( "Postprocessing areas and ways (%d areas_2d_original, %d ways).", len(areas_2d_original.children), len(ways_2d.children)) # Sort areas from larger to smaller, so ways are cut first by outer areas areas_2d_original.children.sort( key=lambda a: a.get('ddd:area:original').get('ddd:area:area'), reverse=True) # extra['ddd:area:area']) # Index all original areas areas_2d_originals_idx = ddd.group2([ area.get('ddd:area:original') for area in areas_2d_original.children ]) areas_2d_originals_idx.index_create() to_process = ways_2d.children # Remove paths from some areas (sidewalks), and reincorporate to them #to_remove = [] while to_process: to_process_copy = to_process to_append = [] to_process = [] for way_2d in to_process_copy: if way_2d.is_empty(): continue #if way_2d.extra.get('osm:highway', None) not in ('footway', 'path', 'track', None): continue if way_2d.get('ddd:area:type', None) == 'water': continue if way_2d.geom.type == "MultiPolygon": logger.warn( "Skipping way postprocess (multipolygon not supported, should individualize ways2 earlier -introduces way intersection joint errors-?): %s", way_2d) continue if way_2d.get('ddd:area:cut:by', None) is None: way_2d.set('ddd:area:cut:by', []) # Find candidate intersections cand_geoms = areas_2d_originals_idx._strtree.query(way_2d.geom) cand_areas = ddd.group2([g._ddd_obj for g in cand_geoms]) #for area in areas_2d_original.children: #self.osm.areas_2d.children: # self.osm.areas_2d.children: for area_original in cand_areas.children: if area_original is None: continue if area_original.is_empty(): continue # Skip areas that have already cut if area_original in way_2d.extra["ddd:area:cut:by"]: continue #if area.extra.get('ddd:area:type', None) != 'sidewalk': continue area_original_outline = area_original.outline() try: # Margin is used to avoid same way chunk touching original area indefinitely. # Note that this algorithm is weak and can potentially result in infinite loops (chunks are re-added for processing) intersects = way_2d.buffer(-0.05).intersects( area_original) # FIDME: arbitrary 5cm margin intersects_outline = way_2d.intersects( area_original_outline) except Exception as e: logger.error( "Could not calculate intersections between way and area: %s %s", way_2d, area_original) raise DDDException( "Could not calculate intersections between way and area: %s %s" % (way_2d, area_original)) if intersects and not intersects_outline: way_2d.extra['ddd:area:container'] = area_original continue if intersects and intersects_outline: logger.debug( "Path %s intersects area: %s (subtracting and arranging)", way_2d, area_original) #ddd.group2([way_2d, area_original]).show() cut_intersection_line = way_2d.intersection( area_original_outline ) # .clean(eps=0.01) -removes lines too? cut_intersection_line_vc = cut_intersection_line.vertex_count( ) if cut_intersection_line_vc and cut_intersection_line_vc > 3: # Cut along: leave entire way inside area (area would be reduced (subtracted) later on) way_2d.extra['ddd:area:container'] = area_original continue intersection = way_2d.intersection(area_original) #intersection.extra['ddd:area:container'].append(area) intersection.name = "Path %s in area %s" % ( way_2d.name, area_original.name) intersection.extra[ 'ddd:area:container'] = area_original intersection.extra["ddd:area:cut:by"].append( area_original) remainder = way_2d.subtract(area_original) #remainder = remainder.material(ddd.mats.pavement) #area.extra['ddd:area:type'] = 'sidewalk' remainder.name = "Path %s to area %s" % ( way_2d.name, area_original.name) #remainder = remainder.clean(eps=0.001) intersection = intersection.clean() # eps=0.01) if not intersection.is_empty(): way_2d.replace(intersection) # Delay appending for c in remainder.individualize(always=True).children: c = c.clean() if c.clean().is_empty(): continue to_append.append(c) c.extra["ddd:area:cut:by"].append(area_original) to_process.append(c) # Expensive #to_process.append(way_2d) if to_append: for a in to_append: ways_2d.append(a)
def generate_area_2d_park(area, tree_density_m2=0.0025, tree_types=None): max_trees = None if tree_types is None: tree_types = {'default': 1} #, 'palm': 0.001} #area = ddd.shape(feature["geometry"], name="Park: %s" % feature['properties'].get('name', None)) feature = area.extra['osm:feature'] area = area.material(ddd.mats.park) area.name = "Park: %s" % feature['properties'].get('name', None) area.extra['ddd:area:type'] = 'park' # Add trees if necesary # FIXME: should not check for None in intersects, filter shall not return None (empty group) trees = ddd.group2(name="Trees (Aug): %s" % area.name) align = area.get('ddd:aug:itemfill:align', 'noise') if area.geom: tree_area = area # area.intersection(ddd.shape(osm.area_crop)).union() if tree_area.geom: if align == 'noise': # Decimation would affect after num_trees = int((tree_area.geom.area * tree_density_m2)) #if num_trees == 0 and random.uniform(0, 1) < 0.5: num_trees = 1 # alone trees if max_trees: num_trees = min(num_trees, max_trees) def filter_func_noise(coords): val = noise.pnoise2(coords[0] * 0.1, coords[1] * 0.1, octaves=2, persistence=0.5, lacunarity=2, repeatx=1024, repeaty=1024, base=0) return (val > random.uniform(-0.5, 0.5)) for p in tree_area.random_points(num_points=num_trees): tree_type = weighted_choice(tree_types) tree = ddd.point(p, name="Tree") tree.extra['ddd:aug:status'] = 'added' tree.extra['osm:natural'] = 'tree' # TODO: Change to DDD tree.extra[ 'osm:tree:type'] = tree_type # TODO: Change to DDD trees.append(tree) elif align == 'grid': # Give some margin for grid, and check the area is still a polygon tree_area = tree_area.buffer(-1.0) if tree_area.is_empty() or tree_area.geom.type != "Polygon": return trees (major_seg, minor_seg, angle) = ddd.geomops.oriented_axis(tree_area) # Decimation would affect this afterwards num_trees = int( (tree_area.geom.minimum_rotated_rectangle.area * tree_density_m2)) major_minor_ratio = major_seg.geom.length / minor_seg.geom.length trees_major = int( max(1, math.sqrt(num_trees) * major_minor_ratio)) trees_minor = int( max(1, math.sqrt(num_trees) * (1 / major_minor_ratio))) minor_seg_centered = minor_seg.recenter() for i in range(trees_major): p_major = major_seg.geom.interpolate( i * major_seg.geom.length / (trees_major - 1 if trees_major > 1 else 1)) for j in range(trees_minor): p_minor_offset = minor_seg_centered.geom.interpolate( j * minor_seg_centered.geom.length / (trees_minor - 1 if trees_minor > 1 else 1)) p = (p_major.coords[0][0] + p_minor_offset.coords[0][0], p_major.coords[0][1] + p_minor_offset.coords[0][1]) if not tree_area.contains(ddd.point(p)): continue tree_type = weighted_choice(tree_types) tree = ddd.point(p, name="Tree") tree.extra['ddd:aug:status'] = 'added' tree.extra[ 'osm:natural'] = 'tree' # TODO: Change to DDD tree.extra[ 'osm:tree:type'] = tree_type # TODO: Change to DDD trees.append(tree) else: raise DDDException("Invalid item align type: %s", align) return trees
def one(self): if len(self.children) != 1: raise DDDException("Expected 1 object but found %s." % len(self.children)) return self.children[0]
def generate_areas_2d_postprocess_cut_outlines(self, areas_2d, ways_2d): """ """ # areas_2d_original = ddd.group2() for a in areas_2d.children: if a.extra.get('ddd:area:original', None): if a.extra.get( 'ddd:area:original') not in areas_2d_original.children: areas_2d_original.append(a.extra.get('ddd:area:original')) logger.info( "Postprocessing areas and ways (%d areas_2d_original, %d ways).", len(areas_2d_original.children), len(ways_2d.children)) # Remove paths from some areas (sidewalks), and reincorporate to them #to_remove = [] to_append = [] for way_2d in ways_2d.children: for area in areas_2d_original.children: #self.osm.areas_2d.children: # self.osm.areas_2d.children: #if way_2d.extra.get('osm:highway', None) not in ('footway', 'path', 'track', None): continue if way_2d.extra.get('ddd:area:type', None) == 'water': continue #area_original = area.extra.get('ddd:area:original', None) #if area_original is None: continue area_original = area #if area.extra.get('ddd:area:type', None) != 'sidewalk': continue intersects = False try: intersects = area_original.intersects( way_2d) and area_original.outline().intersects(way_2d) except Exception as e: logger.error( "Could not calculate intersections between way and area: %s %s", way_2d, area) raise DDDException( "Could not calculate intersections between way and area: %s %s" % (way_2d, area)) if intersects: logger.debug( "Path %s intersects area: %s (subtracting and arranging)", way_2d, area) way_2d.extra['ddd:area:container'] = area_original #ddd.group2([way_2d, area_original]).show() remainder = way_2d.subtract(area_original) intersection = way_2d.intersection(area_original) #intersection.extra['ddd:area:container'].append(area) #remainder = remainder.material(ddd.mats.pavement) #area.extra['ddd:area:type'] = 'sidewalk' remainder.name = "Path to interways: %s" % way_2d.name remainder.clean(eps=0.001) way_2d.replace(remainder) # way_2d.subtract(intersection)) #ways_2d.append(remainder) # Delay appending to_append.append(intersection) for a in to_append: ways_2d.append(a)
def run_each(self, pipeline): func = self._funcargs[0] sig = inspect.signature(func) kwargs = {} for arg in sig.parameters.keys(): if arg == 'r': kwargs['r'] = pipeline.root elif arg == 'root': kwargs['root'] = pipeline.root elif arg == 'p': kwargs['r'] = pipeline elif arg == 'pipeline': kwargs['pipeline'] = pipeline elif arg == 'o': kwargs['o'] = None elif arg == 'obj': kwargs['obj'] = None elif arg == 'logger': kwargs['logger'] = logging.getLogger(func.__module__) elif arg in pipeline.data: kwargs[arg] = pipeline.data[arg] else: raise DDDException( "Unknown argument in task parameter list: %s (task: %s)" % (arg, self)) logger.debug("Select: func=%s selector=%s path=%s recurse=%s ", self.filter, self.selector, self.path, self.recurse) objs = pipeline.root.select(func=self.filter, selector=self.selector, path=self.path, recurse=self.recurse) #if self.log: self.runlog(objs.count()) #else: # logger.debug("Running task %ws for %d objects.", self, objs.count()) self._run_selected = 0 def task_select_apply(o): self._run_selected += 1 try: if 'o' in kwargs: kwargs['o'] = o if 'obj' in kwargs: kwargs['obj'] = o result = func(**kwargs) if self.replace: return result else: if result or result is False: logger.error( "Task function returned a replacement object or a delete (None), but task replace is set to False." ) raise DDDException( "Task function returned a replacement object or a delete (None), but task replace is set to False." ) return o except Exception as e: logger.error("Error running task %s on %s: %s", self, o, e) raise DDDException("Error running task %s on %s: %s" % (self, o, e), ddd_obj=o) pipeline.root.select(func=self.filter, selector=self.selector, path=self.path, recurse=self.recurse, apply_func=task_select_apply) '''
def value(self, point, interpolate=True): tile = self.tile_from_point(point) if tile is None: raise DDDException("No raster tile found for point: %s" % (point, )) return tile.value(point, interpolate)
def export_data(obj, path_prefix="", name_suffix="", instance_mesh=True, instance_marker=False): """ TODO: Unify export code paths and recursion, metadata, path name and mesh production. """ node_name = obj.uniquename() + name_suffix extra = obj.metadata(path_prefix, name_suffix) data = { '_name': node_name, '_path': extra['ddd:path'], '_str': str(obj), '_extra': extra, '_material': str(obj.mat) } for idx, c in enumerate(obj.children): cdata = DDDJSONFormat.export_data(c, path_prefix=path_prefix + node_name + "/", name_suffix="#%d" % (idx), instance_mesh=instance_mesh, instance_marker=instance_marker) cpath = cdata['_extra']['ddd:path'] data[cpath] = cdata # FIXME: This code is duplicated from DDDInstance: normalize export / generation from ddd.ddd import DDDInstance if isinstance(obj, DDDInstance): data['_transform'] = obj.transform if instance_mesh: # Export mesh if object instance has a referenced object (it may not, eg lights) if obj.ref: ref = obj.ref.copy() if obj.transform.scale != [1, 1, 1]: raise DDDException( "Invalid scale for an instance object (%s): %s", obj.transform.scale, obj) #ref = ref.rotate(transformations.euler_from_quaternion(obj.transform.rotation, axes='sxyz')) #ref = ref.translate(self.transform.position) # Export complete references? (too verbose) refdata = DDDJSONFormat.export_data( ref, path_prefix="", name_suffix="#ref", instance_mesh=instance_mesh, instance_marker=instance_marker ) #, instance_mesh=instance_mesh, instance_marker=instance_marker) data['_ref'] = refdata if instance_marker: ref = obj.marker() refdata = DDDJSONFormat.export_data( ref, path_prefix="", name_suffix="#marker", instance_mesh=instance_mesh, instance_marker=instance_marker) data['_marker'] = refdata return data