def osm_structured_export_2d_tile(root, osm, pipeline): """Save a cropped tileable 2D image of the scene.""" tile = ddd.group2([ ddd.shape(osm.area_crop).material(ddd.material(color='#ffffff')), # White background (?) #self.ground_2d, root.select(path="/Water", recurse=False), root.select(path="/Areas", recurse=False), root.select(path="/Ways", recurse=False), #, select="") self.ways_2d['-1a'], self.ways_2d['0'], self.ways_2d['0a'], self.ways_2d['1'], root.select(path="/Roadlines2", recurse=False), root.select(path="/Buildings", recurse=False), #self.areas_2d_objects, self.buildings_2d.material(ddd.material(color='#8a857f')), root.select(path="/ItemsAreas", recurse=False), #self.items_2d, root.select(path="/ItemsWays", recurse=False), #self.items_2d, root.select(path="/ItemsNodes", recurse=False).buffer(0.5).material(ddd.mats.red), ]).flatten().select(func=lambda o: o.extra.get('ddd:area:type') != 'underwater') tile = tile.intersection(ddd.shape(osm.area_crop)) tile = tile.clean() tile.prop_set('svg:stroke-width', 0.01, children=True) path = pipeline.data['filenamebase'] + ".png" tile.save(path) tile.save("/tmp/osm-structured.png")
def __init__(self, features=None, area_filter=None, area_crop=None, osm_proj=None, ddd_proj=None): self.catalog = PrefabCatalog() self.items = ItemsOSMBuilder(self) self.items2 = AreaItemsOSMBuilder(self) self.ways1 = Ways1DOSMBuilder(self) self.ways2 = Ways2DOSMBuilder(self) self.ways3 = Ways3DOSMBuilder(self) self.areas2 = Areas2DOSMBuilder(self) self.areas3 = Areas3DOSMBuilder(self) self.buildings = BuildingOSMBuilder(self) self.customs = CustomsOSMBuilder(self) self.osmops = OSMBuilderOps(self) self.area_filter = area_filter self.area_crop = area_crop self.area_filter2 = ddd.shape(self.area_filter) self.area_crop2 = ddd.shape(self.area_crop) #self.simplify_tolerance = 0.01 self.layer_indexes = ('-2', '-1', '0', '1', '2', '3', '-2a', '-1a', '0a', '1a') self.layer_heights = { '-2': -12.0, '-1': -6.0, '0': 0.0, '1': 6.0, '2': 12.0, '3': 18.0, #'-2a': -9.0, '-1a': -2.5, '0a': 3.0, '1a': 9.0} #'-2a': -12.0, '-1a': -5.0, '0a': 0.0, '1a': 6.0} '-2a': 0.0, '-1a': 0.0, '0a': 0.0, '1a': 0.0 } self.webmercator_proj = pyproj.Proj(init='epsg:3857') self.osm_proj = osm_proj # 4326 self.ddd_proj = ddd_proj self.features = features if features else [] self.features_2d = ddd.group2(name="Features")
def preprocess_features(self): """ Corrects inconsistencies and adapts OSM data for generation. """ # Correct layers for f in self.features: f.properties['id'] = f.properties['id'].replace("/", "-") if f.properties.get( 'tunnel', None) == 'yes' and f.properties.get('layer', None) is None: f.properties['layer'] = "-1" if f.properties.get( 'brige', None) == 'yes' and f.properties.get('layer', None) is None: f.properties['layer'] = "1" if f.properties.get('layer', None) is None: f.properties['layer'] = "0" # Create feature objects defaultname = f.geometry.type # "Feature" name = f.properties.get('name', defaultname) osmid = f.properties.get('id', None) if osmid is not None: name = "%s_(%s)" % (name, osmid) feature_2d = ddd.shape(f.geometry, name=name) feature_2d.extra['osm:feature'] = f feature_2d.extra['osm:feature_2d'] = feature_2d feature_2d.extra['osm:element'] = f.properties['id'].split("-")[0] for k, v in f.properties.items(): feature_2d.extra['osm:' + k] = v # We do not validate or clean here as linestrings may crose themselves. Simply remove empty geometries. # Crop to area filter try: feature_2d.validate() except Exception as e: logger.debug("Invalid feature (1/2) '%s': %s", name, e) try: feature_2d = feature_2d.intersection(self.area_filter2) #feature_2d.clean(eps=0.01) feature_2d.validate() except Exception as e: logger.warn("Invalid feature (2/2) '%s' %s: %s", name, feature_2d.metadata("", ""), e) continue # Separate GeometryCollection geometries if feature_2d.geom.type == "GeometryCollection": logger.info("Splitting GeometryCollection: %s", feature_2d) for f in feature_2d.individualize().children: f.extra['osm:feature_2d'] = f f = f.intersection(self.area_filter2) #f = f.clean(eps=0.0) f.validate() self.features_2d.append(f) else: self.features_2d.append(feature_2d)
def generate_custom_1d(self, feature): otype = 'checkpoint' layer = feature.properties.get('ddd:layer') if otype == 'checkpoint': race = layer idx = int(feature.properties.get('fid')) item = ddd.shape(feature['geometry'], name="Checkpoint %d (race): %s" % (idx, race)) item.extra['checkpoint_idx'] = idx item.extra['type'] = otype else: logger.warn("Unknown custom feature: %s", feature) return return item
def preprocess_buildings_features(self, features_2d): logger.info("Preprocessing buildings and bulding parts (2D)") # TODO: create nested buildings them separately, consider them part of the bigger building for subtraction) # Assign each building part to a building, or transform it into a building if needed #features = sorted(features_2d.children, key=lambda f: f.geom.area) features_2d_original = list(features_2d.children) for feature in list(features_2d.children): if feature.geom.type == 'Point': continue if feature.extra.get('osm:building:part', None) is None and feature.extra.get('osm:building', None) is None: continue # Find building #buildings = features_2d.select(func=lambda o: o.extra.get('osm:building', None) and ddd.polygon(o.geom.exterior.coords).contains(feature)) buildings = features_2d.select(func=lambda o: o.extra.get('osm:building', None) and o != feature and o.contains(feature) and o in features_2d_original) if len(buildings.children) == 0: if feature.extra.get('osm:building', None) is None: logger.warn("Building part with no building: %s", feature) building = ddd.shape(feature.geom, name="Building (added): %s" % feature.name) building.extra['osm:building'] = feature.extra.get('osm:building:part', 'yes') building.extra['ddd:building:parts'] = [feature] feature.extra['ddd:building:parent'] = building features_2d.append(building) elif len(buildings.children) > 1: # Sort by area and get the smaller one buildings.children.sort(key=lambda b: b.geom.area, reverse=False) logger.warn("Building part with multiple buildings: %s -> %s", feature, buildings.children) feature.extra['ddd:building:parent'] = buildings.children[0] if 'ddd:building:parts' not in buildings.children[0].extra: buildings.children[0].extra['ddd:building:parts'] = [] buildings.children[0].extra['ddd:building:parts'].append(feature) else: logger.debug("Associating building part to building: %s -> %s", feature, buildings.children[0]) feature.extra['ddd:building:parent'] = buildings.children[0] if 'ddd:building:parts' not in buildings.children[0].extra: buildings.children[0].extra['ddd:building:parts'] = [] buildings.children[0].extra['ddd:building:parts'].append(feature)
def generate_ways_3d_subways(self): """ Generates boxing for sub ways. """ logger.info("Generating subways.") logger.warn("IMPLEMENT 2D/3D separation for this, as it needs to be cropped, and it's being already cropped earlier") # Take roads ways = [w for w in self.osm.ways_2d["-1a"].children] + [w for w in self.osm.ways_2d["-1"].children] union = self.osm.ways_2d["-1"].union() union_with_transitions = ddd.group(ways, empty="2").union() union_sidewalks = union_with_transitions.buffer(0.6, cap_style=2, join_style=2) sidewalks_2d = union_sidewalks.subtract(union_with_transitions) # we include transitions walls_2d = sidewalks_2d.buffer(0.5, cap_style=2, join_style=2).subtract(union_sidewalks) floors_2d = union_sidewalks.copy() ceilings_2d = union.buffer(0.6, cap_style=2, join_style=2).subtract(self.osm.ways_2d["-1a"]) # FIXME: Move cropping to generic site, use interintermediatemediate osm.something for storage crop = ddd.shape(self.osm.area_crop) sidewalks_2d = sidewalks_2d.intersection(crop) walls_2d = walls_2d.intersection(crop) floors_2d = floors_2d.intersection(crop) ceilings_2d = ceilings_2d.intersection(crop) sidewalks_3d = sidewalks_2d.extrude(0.3).translate([0, 0, -5]).material(ddd.mats.sidewalk) walls_3d = walls_2d.extrude(5).translate([0, 0, -5]).material(ddd.mats.cement) #floors_3d = floors_2d.extrude(-0.3).translate([0, 0, -5]).material(ddd.mats.sidewalk) floors_3d = floors_2d.triangulate().translate([0, 0, -5]).material(ddd.mats.sidewalk) ceilings_3d = ceilings_2d.extrude(0.5).translate([0, 0, -1.0]).material(ddd.mats.cement) sidewalks_3d = terrain.terrain_geotiff_elevation_apply(sidewalks_3d, self.osm.ddd_proj) sidewalks_3d = ddd.uv.map_cubic(sidewalks_3d) walls_3d = terrain.terrain_geotiff_elevation_apply(walls_3d, self.osm.ddd_proj) walls_3d = ddd.uv.map_cubic(walls_3d) floors_3d = terrain.terrain_geotiff_elevation_apply(floors_3d, self.osm.ddd_proj) ceilings_3d = terrain.terrain_geotiff_elevation_apply(ceilings_3d, self.osm.ddd_proj) ceilings_3d = ddd.uv.map_cubic(ceilings_3d) subway = ddd.group([sidewalks_3d, walls_3d, floors_3d, ceilings_3d], empty=3).translate([0, 0, -0.2]) self.osm.other_3d.children.append(subway)
def osm_groups_areas(root, osm, obj, logger): item = obj.copy(name="Area: %s" % obj.name) try: area = item.individualize().flatten() area.validate() except DDDException as e: logger.warn("Invalid geometry (cropping area) for area %s (%s): %s", area, area.extra, e) try: area = area.clean(eps=0.001).intersection(ddd.shape(osm.area_crop)) area = area.individualize().flatten() area.validate() except DDDException as e: logger.warn("Invalid geometry (ignoring area) for area %s (%s): %s", area, area.extra, e) return for a in area.children: if a.geom: a.extra['ddd:area:area'] = a.geom.area root.find("/Areas").append(a)
def generate_coastline_2d(self, area_crop): logger.info( "Generating water and land areas according to coastline: %s", (area_crop.bounds)) #self.water_3d = terrain.terrain_grid(self.area_crop.bounds, height=0.1, detail=200.0).translate([0, 0, 1]).material(mat_sea) water = ddd.rect(area_crop.bounds, name="Coastline Water") coastlines = [] coastlines_1d = [] for way in self.osm.features_2d.children: if way.extra.get('osm:natural') == 'coastline': original = way.extra[ 'osm:original'] # Original has not been cropped (cropped verison is not valid for this) coastlines_1d.append(original) coastlines.append(original.buffer(0.01)) #for way in self.osm.features.children: # if way.properties.get('natural') == 'coastline': # coastlines_1d.append(ddd.shape(way.geometry)) # coastlines.append(ddd.shape(way.geometry).buffer(0.1)) if not coastlines: logger.info("No coastlines in the feature set.") return coastlines_1d = ddd.group(coastlines_1d) #.individualize().flatten() coastlines = ddd.group(coastlines) # .individualize().flatten() coastline_areas = water.subtract(coastlines) logger.info("Coastlines: %s", (coastlines_1d, )) logger.info("Coastline areas: %s", (coastline_areas, )) # Generate coastline edge if coastlines_1d.children: coastlines_2d = coastlines_1d.intersection(water) coastlines_2d = coastlines_2d.individualize() #coastlines_3d = coastlines_2d.extrude(10.0).translate([0, 0, -10.0]) #coastlines_3d = terrain.terrain_geotiff_elevation_apply(coastlines_3d, self.osm.ddd_proj) #coastlines_3d = ddd.uv.map_cubic(coastlines_3d) #coastlines_3d.name = 'Coastline: %s' % coastlines_3d.name #self.osm.other_3d.append(coastlines_3d) areas_2d = [] #geoms = coastline_areas.geom.geoms if coastline_areas.geom.type == 'MultiPolygon' else [coastline_areas.geom] for water_area_geom in coastline_areas.individualize().flatten().clean( ).children: # Find closest point, closest segment, and angle to closest segment #water_area_geom = ddd.shape(water_area_geom).clean(eps=0.01).geom #if not water_area_geom.is_simple: # logger.error("Invalid water area geometry (not simple): %s", water_area_geom) # continue if not water_area_geom.geom: continue #water_area_geom.dump() coastlines_1d = coastlines_1d.outline().clean() water_area_point = water_area_geom.geom.representative_point() p, segment_idx, segment_coords_a, segment_coords_b, closest_obj, closest_d = coastlines_1d.closest_segment( ddd.shape(water_area_point)) pol = LinearRing([ segment_coords_a, segment_coords_b, (water_area_point.coords[0][0], water_area_point.coords[0][1], 0) ]) if not pol.is_ccw: #area_3d = area_2d.extrude(-0.2) area_2d = ddd.shape( water_area_geom.geom).buffer(0.10).clean(eps=0.01) area_2d.validate() area_2d = area_2d.material(ddd.mats.sea) area_2d.extra['ddd:area:type'] = 'sea' # not treated as sea area_2d.extra['ddd:area:elevation'] = 'none' area_2d.extra['ddd:height'] = 0 area_2d.extra['ddd:collider'] = False area_2d.extra['ddd:shadows'] = False area_2d.extra['ddd:occluder'] = False #area_3d = area_2d.triangulate().translate([0, 0, -0.5]) areas_2d.append(area_2d) #areas.append(area_3d) return ddd.group2(areas_2d, name="Water")
def generate_ways_3d_elevated(self): logger.info("Generating elevated ways.") logger.warn( "IMPLEMENT 2D/3D separation for this, as it needs to be cropped") elevated = [] # Walk roads ways = ([w for w in self.osm.ways_2d["1"].children] + [w for w in self.osm.ways_2d["0a"].children] + [w for w in self.osm.ways_2d["-1a"].children]) # ways_union = ddd.group(ways).union() sidewalk_width = 0.4 elevated_union = DDDObject2() for way in ways: # way_longer = way.buffer(0.3, cap_style=1, join_style=2) if 'intersection' in way.extra: continue way_with_sidewalk_2d = way.buffer(sidewalk_width, cap_style=2, join_style=2) #way_with_sidewalk_2d_extended = osmops.extend_way(way).buffer(sidewalk_width, cap_style=2, join_style=2) sidewalk_2d = way_with_sidewalk_2d.subtract(way).material( ddd.mats.sidewalk) wall_2d = way_with_sidewalk_2d.buffer( 0.25, cap_style=2, join_style=2).subtract(way_with_sidewalk_2d).buffer( 0.001, cap_style=2, join_style=2).material(ddd.mats.cement) floor_2d = way_with_sidewalk_2d.buffer( 0.3, cap_style=2, join_style=2).buffer(0.001, cap_style=2, join_style=2).material(ddd.mats.cement) sidewalk_2d.extra['way_2d'] = way wall_2d.extra['way_2d'] = way floor_2d.extra['way_2d'] = way # Get connected ways connected = self.osm.ways1.follow_way(way.extra['way_1d'], 1) connected_2d = ddd.group( [self.osm.ways2.get_way_2d(c) for c in connected]) if 'intersection_start_2d' in way.extra['way_1d'].extra: connected_2d.append( way.extra['way_1d'].extra['intersection_start_2d']) if 'intersection_end_2d' in way.extra['way_1d'].extra: connected_2d.append( way.extra['way_1d'].extra['intersection_end_2d']) # print(connected) sidewalk_2d = sidewalk_2d.subtract(connected_2d).buffer(0.001) wall_2d = wall_2d.subtract(connected_2d.buffer(sidewalk_width)) # TODO: Subtract floors from connected or resolve intersections wall_2d = wall_2d.subtract(elevated_union) # FIXME: Move cropping to generic site, use itermediate osm.something for storage crop = ddd.shape(self.osm.area_crop) sidewalk_2d = sidewalk_2d.intersection( crop.buffer(-0.003)).clean(eps=0.01) wall_2d = wall_2d.intersection(crop.buffer(-0.003)).clean(eps=0.01) floor_2d = floor_2d.intersection( crop.buffer(-0.003)).clean(eps=0.01) #FIXME: TODO: this shal be done earlier, before generating the path #if way.extra.get('ddd:way:elevated:material', None): # way.extra['way_2d'].material(way.extra.get('ddd:way:elevated:material')) # ddd.group((sidewalk_2d, wall_2d)).show() if way.extra.get('ddd:way:elevated:border', None) == 'fence': fence_2d = wall_2d.outline().clean() fence_2d = fence_2d.material(ddd.mats.fence) #fence_2d.dump() #wall_2d.show() fence_2d.extra['ddd:item'] = True fence_2d.extra['ddd:item:height'] = 1.2 fence_2d.extra['ddd:height'] = 1.2 fence_2d.extra['barrier'] = "fence" fence_2d.extra[ '_height_mapping'] = "terrain_geotiff_and_path_apply" fence_2d.extra['way_1d'] = way.extra['way_1d'] self.osm.items_1d.append(fence_2d) elevated.append((sidewalk_2d, None, floor_2d)) else: elevated.append((sidewalk_2d, wall_2d, floor_2d)) elevated_union = elevated_union.union( ddd.group([sidewalk_2d, wall_2d, floor_2d])) # Bridge piers path = way.extra['way_1d'] if path.geom.length > 15.0: # and path.extra['ddd:bridge:posts']: # Generate posts interval = 35.0 length = path.geom.length numposts = int(length / interval) idx = 0 logger.debug("Piers for bridge (length=%s, num=%d, way=%s)", length, numposts, way) for d in numpy.linspace(0.0, length, numposts, endpoint=False): if d == 0.0: continue # Calculate left and right perpendicular intersections with sidewalk, park, land... p, segment_idx, segment_coords_a, segment_coords_b = path.interpolate_segment( d) # FIXME: Use items and crop in a generic way (same for subways) (so ignore below in common etc) if not self.osm.area_crop.contains(ddd.point(p).geom): continue dir_vec = (segment_coords_b[0] - segment_coords_a[0], segment_coords_b[1] - segment_coords_a[1]) dir_vec_length = math.sqrt(dir_vec[0]**2 + dir_vec[1]**2) dir_vec = (dir_vec[0] / dir_vec_length, dir_vec[1] / dir_vec_length) angle = math.atan2(dir_vec[1], dir_vec[0]) idx = idx + 1 if len(p) < 3: logger.error( "Bridge path with less than 3 components when building bridge piers." ) continue if p[2] > 1.0: # If no height, no pilar, but should be a margin and also corrected by base_height item = ddd.rect([ -way.extra['ddd:way:width'] * 0.3, -0.5, way.extra['ddd:way:width'] * 0.3, 0.5 ], name="Bridge Post %s" % way.name) item = item.extrude(-(math.fabs(p[2]) - 0.5)).material( ddd.mats.cement) item = ddd.uv.map_cubic(item) item = item.rotate([0, 0, angle - math.pi / 2 ]).translate([p[0], p[1], 0]) vertex_func = self.get_height_apply_func(path) item = item.vertex_func(vertex_func) item = terrain.terrain_geotiff_elevation_apply( item, self.osm.ddd_proj) item = item.translate([0, 0, -0.8]) item.extra['way_2d'] = way item.extra['ddd:bridge:post'] = True self.osm.other_3d.children.append(item) elevated_3d = [] for item in elevated: sidewalk_2d, wall_2d, floor_2d = item sidewalk_3d = sidewalk_2d.extrude(0.2).translate([0, 0, -0.2]) wall_3d = wall_2d.extrude(0.6) if wall_2d else None floor_3d = floor_2d.extrude(-0.5).translate([0, 0, -0.2]) # extra_height = way_2d.extra['extra_height'] # way_3d = way_2d.extrude(-0.2 - extra_height).translate([0, 0, extra_height]) # + layer_height sidewalk_3d = ddd.uv.map_cubic(sidewalk_3d) wall_3d = ddd.uv.map_cubic(wall_3d) if wall_3d else None floor_3d = ddd.uv.map_cubic(floor_3d) elevated_3d.append(sidewalk_3d) elevated_3d.append(floor_3d) if wall_3d: elevated_3d.append(wall_3d) # Raise items to their way height position nitems = [] for item in elevated_3d: # print(item.extra) path = item.extra['way_1d'] vertex_func = self.osm.ways1.get_height_apply_func(path) nitem = item.vertex_func(vertex_func) nitems.append(nitem) result = ddd.group(nitems, empty=3) result = terrain.terrain_geotiff_elevation_apply( result, self.osm.ddd_proj) self.osm.other_3d.children.append(result)
#ddd.shape(osm.area_crop).material(ddd.material(color='#ffffff')), # White background (?) #self.ground_2d, root.select(path="/Water", recurse=False), root.select(path="/Areas", recurse=False), root.select(path="/Ways", recurse=False), #, select="") self.ways_2d['-1a'], self.ways_2d['0'], self.ways_2d['0a'], self.ways_2d['1'], root.select(path="/Buildings", recurse=False), #root.select(path="/Roadlines2", recurse=False), #root.select(path="/ItemsAreas", recurse=False), #self.items_2d, #root.select(path="/ItemsWays", recurse=False), #self.items_2d, #root.select(path="/ItemsNodes", recurse=False).buffer(0.5).material(ddd.mats.red), ]).flatten().select(func=lambda o: o.extra.get('ddd:area:type') != 'underwater') ''' # Save a cropped tileable idmap image of the scene. tile = tile.intersection(ddd.shape(osm.area_crop)) tile = tile.clean() tile.prop_set('svg:stroke-width', 0, children=True) # 0.01, path = pipeline.data['filenamebase'] + ".idmap.png" tile.save("/tmp/osm-idmap.png") #tile.save(path) ''' SPLAT_NONE = 0x00 SPLAT_UNKNOWN = 0x08 # Spatial index rtree = STRtree(tile.geom_recursive())