Beispiel #1
0
    def uv_apply_func(x, y, z, idx):
        # Find nearest point in shape (or children), and return its height
        closest_o, closest_d = obj_2d.closest(ddd.point([x, y]))
        closest_uv = None
        closest_distsqr = float('inf')
        if closest_o.extra.get('uv', None):
            for idx, v in enumerate(closest_o.geom.exterior.coords):
                point_2d = [v[0], v[1], 0]
                diff = [point_2d[0] - x, point_2d[1] - y]
                distsqr = (diff[0]**2) + (diff[1]**2)
                if (distsqr < closest_distsqr):
                    closest_uv = closest_o.extra['uv'][idx]
                    closest_distsqr = distsqr
        else:
            logger.error(
                "Closest object has no UV mapping: %s (%s) (obj_2d=%s, obj_3d=%s)",
                closest_o, closest_o.extra.get('uv', None), obj_2d, obj_3d)
            raise DDDException("Closest object has no UV mapping: %s (%s)" %
                               (closest_o, closest_o.extra.get('uv', None)),
                               ddd_obj=obj_3d)

        if closest_uv is None:
            logger.error("Error mapping 3D from 2D (3d=%s, 2d=%s %s %s)",
                         obj_3d, obj_2d, obj_2d.geom,
                         [x.geom for x in obj_2d.children])
            raise DDDException(
                "Could not map 3D from 2D (no closest vertex found): %s" %
                obj_3d,
                ddd_obj=obj_3d)

        return closest_uv
Beispiel #2
0
 def parabolla_from_geom(obj):
     points = obj.vertex_list()
     if len(points) != 3:
         raise DDDException("Cannot create parabolla from geometry with other than 3 vertices: %s" % obj)
     if obj.children:
         raise DDDException("Cannot create parabolla from geometry with children nodes: %s" % obj)
     path = Path.parabolla_from_points(points[0], points[2], points[1])
     path.copy_from(obj)
     return path
Beispiel #3
0
def parse_bool(value):

    if value in (True, "True", "true", "Yes", "yes", "1", 1):
        return True
    if value in (False, "False", "false", "No", "no", "0", 0):
        return False
    raise DDDException("Could not parse boolean value: %r", value)
Beispiel #4
0
        def task_select_apply(o):
            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)
Beispiel #5
0
    def tasks_sorted(self):
        tasks = []

        for task in self.tasks:

            try:
                order = task.order
                if order is None: order = '*.+'
                if order.startswith('*.'):
                    if len(tasks) == 0:
                        order = "1"
                    else:
                        order = ".".join(
                            [str(e) for e in tasks[-1]._order_num[:-1]] +
                            order.split(".")[1:])

                order_split = order.split(".")
                for (el_idx, el_str) in enumerate(order_split):
                    if el_str == '+':
                        previous_order_num = self._find_last_order(
                            tasks, order_split[:el_idx])
                        el = previous_order_num + 1
                    else:
                        el = int(el_str)
                    order_split[el_idx] = el
                task._order_num = order_split
                tasks.append(task)
            except ValueError as e:
                raise DDDException("Cannot parse order of task %s: %s" %
                                   (task, order))

        tasks.sort(key=lambda t: (t._order_num, ))
        return tasks
Beispiel #6
0
    def load(self, configfile):

        DDDTask._tasks = []

        # Load file
        logger.debug("Loading pipeline config: %s", configfile)

        script_abspath = os.path.abspath(configfile)
        script_dirpath = os.path.dirname(configfile)
        script_name = os.path.basename(configfile)
        module_name = script_name[:-3]  # remove .py

        try:
            # Add to path
            #sys.path.append(script_dirpath)
            if script_dirpath not in sys.path:
                logger.info("Appending to path: %s", script_dirpath)
                sys.path.append(script_dirpath)

            importlib.import_module(
                module_name)  #, globals={'ddd_bootstrap': self})
        except ModuleNotFoundError as e:
            raise DDDException("Could not load pipeline definition file: %s" %
                               configfile)

        self.tasks = DDDTask._tasks
Beispiel #7
0
    def load(georaster_file, crs):

        logger.info("Loading georaster file: %s" % georaster_file)

        tile = GeoRasterTile()
        tile.crs = crs.lower()
        tile.crs_transformer = pyproj.Transformer.from_proj('epsg:4326',
                                                            tile.crs,
                                                            always_xy=True)

        try:
            tile.layer = gdal.Open(georaster_file, GA_ReadOnly)
            bands = tile.layer.RasterCount

            tile.geotransform = tile.layer.GetGeoTransform()
            logger.debug("Opened georaster [bands=%d, geotransform=%s]" %
                         (bands, tile.geotransform))

            #if (bands != 1):
            #    raise DDDException("Georaster file must have 1 band only.")

        except Exception as e:
            logger.warn("Could not read georaster file %s: %s", georaster_file,
                        e)
            raise DDDException("Could not read georaster file %s: %s",
                               georaster_file, e)

        return tile
Beispiel #8
0
    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,
            cache_override=False,
            init=False,
            params=None):

        # Metrics
        self._run_seconds = None
        self._run_selected = None

        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.cache_override = cache_override
        self.init = init

        self.log = log

        self.path = path
        self.filter = filter
        self.recurse = recurse
        self.replace = True

        # Dictionary of parameters introduced by the task
        self.params = params

        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)
Beispiel #9
0
 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, ))
     if interpolate:
         return tile.value_interpolated(point)
     else:
         return tile.value_simple(point)
Beispiel #10
0
 def process_node_meta(feat, node):
     meta = node['__meta__'] if '__meta__' in node.properties else {}
     if '_editor_description_' in meta:
         for l in meta['_editor_description_'].split("\n"):
             try:
                 l = l.strip()
                 if not l or l.startswith('#'): continue
                 k, v = l.split("=", 2)
                 feat.extra[k] = v
             except Exception as e:
                 raise DDDException(
                     "Cannot read meta key=value (%s) from node %s: %s" %
                     (l, node, e))
Beispiel #11
0
    def generate_item_3d_tree(self, item_2d):

        coords = item_2d.geom.coords[0]

        #invalid = ddd.group([self.osm.ways_2d["0"], self.osm.buildings_2d])
        #if not self.osm.osmops.placement_valid(ddd.disc(coords, r=0.4), invalid=invalid):
        #    return None

        numvariants = 5  # 7
        '''
        tree_type = item_2d.extra.get('osm:tree:type')
        if tree_type is None:
            tree_type = random.choice(['default', 'palm'])
        '''

        tree_type = item_2d.get('osm:tree:type')
        if isinstance(tree_type, dict):
            tree_type = weighted_choice(tree_type)

        key = "tree-%s-%d" % (
            tree_type, random.choice([x + 1 for x in range(numvariants)]))

        item_3d = self.osm.catalog.instance(key)
        if not item_3d:
            plant_height = random.normalvariate(8.0, 3.0)
            if plant_height < 4.0: plant_height = random.uniform(4.0, 6.5)
            if plant_height > 35.0: plant_height = random.uniform(30.0, 35.0)

            if tree_type == 'default':
                plant_height += 3
                item_3d = plants.tree_default(height=plant_height)
            elif tree_type == 'palm':
                plant_height += 6
                item_3d = plants.tree_palm(height=plant_height)
            elif tree_type == 'fir':
                item_3d = plants.tree_fir(height=plant_height)
            elif tree_type == 'bush':
                item_3d = plants.tree_bush(height=plant_height)
            elif tree_type == 'reed':
                item_3d = plants.reed()
            else:
                raise DDDException("Unknown tree type %r for object %s" %
                                   (tree_type, item_2d))

            item_3d = self.osm.catalog.add(key, item_3d)

        item_3d = item_3d.rotate([0.0, 0.0, random.uniform(0, math.pi * 2)])
        item_3d = item_3d.translate([coords[0], coords[1], 0.0])
        item_3d.name = 'Tree: %s' % item_2d.name
        return item_3d
Beispiel #12
0
    def load_atlas(filepath):
        """
        Process a Texture Atlas definition file, in
        PropertyList file (plistlib) format generated by PyTexturePack.
        """

        # Open file
        try:
            with open(filepath, 'rb') as fp:
                pl = plistlib.load(fp)
        except:
            raise DDDException(
                "Could not load atlas texture definition from: %s" %
                (filepath, ))

        atlas = TextureAtlas()
        texture_size = pl['metadata']['size'][1:-1].split(",")
        atlas.texture_width = int(texture_size[0])
        atlas.texture_height = int(texture_size[1])

        # Process it
        #print(pl)
        for key, frame in pl['frames'].items():
            bounds_str = frame['frame']
            bounds_str_split = bounds_str[1:-1].split(",")
            bounds_str_min, bounds_str_max = ",".join(
                bounds_str_split[:2]), ",".join(bounds_str_split[2:]),
            bounds_str_min = bounds_str_min[1:-1].split(",")
            bounds_str_max = bounds_str_max[1:-1].split(",")
            bounds_pixel = [
                int(bounds_str_min[0]),
                int(bounds_str_min[1]),
                int(bounds_str_min[0]) + int(bounds_str_max[0]),
                int(bounds_str_min[1]) + int(bounds_str_max[1])
            ]
            bounds_norm = [
                bounds_pixel[0] / atlas.texture_width,
                bounds_pixel[1] / atlas.texture_height,
                bounds_pixel[2] / atlas.texture_width,
                bounds_pixel[3] / atlas.texture_height
            ]
            rotated = frame['rotated']
            sprite = TextureAtlasSprite(key, bounds_pixel, bounds_norm,
                                        rotated)
            atlas.sprites[key.lower()] = sprite
            logger.debug("")

        logger.info("Loaded texture atlas %s with %d sprites.", filepath,
                    len(atlas.sprites))
        return atlas
Beispiel #13
0
 def append(self, obj):
     """
     Adds an object as a children to this node.
     If a list is passed, each element is added.
     """
     if isinstance(obj, Iterable):
         for i in obj:
             self.children.append(i)
     elif isinstance(obj, DDDObject):
         self.children.append(obj)
     else:
         raise DDDException(
             "Cannot append object to DDDObject children (wrong type): %s" %
             obj)
     return self
Beispiel #14
0
    def get(self, keys, default=(None, ), extra=None, type=None):
        """
        Returns a property from dictionary and settings.

        Keys can be a string or an array of strings which will be tried in order.
        """
        if isinstance(keys, str):
            keys = [keys]

        dicts = [self.extra]
        if extra is not None:
            if not isinstance(extra, (list, set, tuple)): extra = [extra]
            dicts.extend(extra)
        if D1D2D3.data is not None: dicts.append(D1D2D3.data)

        #logger.debug("Resolving %s in %s (def=%s)", keys, dicts, default)

        result = None
        key = None
        for k in keys:
            if key: break
            for d in dicts:
                if k in d:
                    key = k
                    result = d[k]
                    # logger.info("Found key in dictionary: %s", result)
                    break

        if key is None:
            if default is not self.get.__defaults__[0]:
                result = default
            else:
                raise DDDException(
                    "Cannot resolve property %r in object '%s' (own: %s)" %
                    (keys, self, self.extra))

        # Resolve lambda
        if callable(result):
            result = result()
            self.extra[key] = result

        if type is not None:
            if type == "bool":
                result = parse_bool(result)

        return result
Beispiel #15
0
    def area(self, bounds):
        """
        Returns a height matrix for the area defined by the given bounds in WGS84 coordinates.
        """

        # FIXME: Fails if multiple chunks are touched
        minx, miny, maxx, maxy = bounds

        tile = self.tile_from_point([minx, miny])

        if not tile or not tile.geotransform:
            raise DDDException(
                "No elevation data available for the given point.")

        if tile.crs != 'epsg:4326':
            minx, miny = self.crs_transformer.transform(minx, miny)
            maxx, maxy = self.crs_transformer.transform(maxx, maxy)

        # Transform to raster point coordinates
        raster_min_x = int(
            (minx - tile.geotransform[0]) / tile.geotransform[1])
        raster_min_y = int(
            (miny - tile.geotransform[3]) / tile.geotransform[5])
        raster_max_x = int(
            (maxx - tile.geotransform[0]) / tile.geotransform[1])
        raster_max_y = int(
            (maxy - tile.geotransform[3]) / tile.geotransform[5])

        # Check if limits are hit
        if (raster_max_x > tile.layer.RasterXSize - 1) or raster_max_y < 0:
            logger.error(
                "Raster area [%d, %d, %d, %d] requested exceeds tile bounds [%d, %d] (not implemented).",
                raster_min_x, raster_min_y, raster_max_x, raster_max_y,
                tile.layer.RasterXSize, tile.layer.RasterYSize)
            raise NotImplementedError()
        if raster_max_x > tile.layer.RasterXSize - 1:
            raster_max_x = tile.layer.RasterXSize - 1
        if raster_max_y < 0:
            raster_max_y = 0

        # Note that readasarray is positive south, whereas bounds are positive up
        height_matrix = tile.layer.GetRasterBand(1).ReadAsArray(
            raster_min_x, raster_max_y, raster_max_x - raster_min_x + 1,
            raster_min_y - raster_max_y + 1)

        return height_matrix
Beispiel #16
0
    def __init__(self, name=None, children=None, extra=None, material=None):
        self.name = name
        self.children = children if children is not None else []
        self.extra = extra if extra is not None else {}
        self.mat = material

        self._uid = None

        #self.geom = None
        #self.mesh = None

        for c in self.children:
            if not isinstance(c, self.__class__) and not (isinstance(
                    c, DDDInstance) and isinstance(self, DDDObject3)):
                raise DDDException("Invalid children type on %s (not %s): %s" %
                                   (self, self.__class__, c),
                                   ddd_obj=self)
Beispiel #17
0
    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())

        def task_select_apply(o):
            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)

        '''
Beispiel #18
0
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
Beispiel #19
0
    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))
Beispiel #20
0
    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
Beispiel #21
0
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
Beispiel #22
0
    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)
Beispiel #23
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)
Beispiel #24
0
 def one(self):
     if len(self.children) != 1:
         raise DDDException("Expected 1 object but found %s." %
                            len(self.children))
     return self.children[0]
Beispiel #25
0
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
Beispiel #26
0
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
Beispiel #27
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'))
        """
        #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)
Beispiel #28
0
    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
Beispiel #29
0
    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