def create_regular_grid(area_defn, tile_size, stride=None):
    """
    Defines a regular grid of (overlapping) tiles.

    :param area_defn:       dictionary, defines one or multiple rectangularly-shaped geographic regions from which
                            DSM patches will be sampled. The dictionary is composed of the following key-value pairs:
                            x_extent:   list of n tuples, where n denotes the number of rectangular regions (stripes).
                                        Each tuple defines the upper-left and lower-right x-coordinate of a rectangular
                                        region (stripe).
                            y_extent:   list of n tuples, where n denotes the number of rectangular regions (stripes).
                                        Each tuple defines the upper-left and lower-right y-coordinate of a rectangular
                                        region (stripe).

                            Assumption: The i.th tuple of x_extent and i.th tuple of y_extent define a geographically
                                        rectangular region (stripe).

    :param tile_size:       int, tile size in pixels
    :param stride:          int, stride in pixels (if None: stride equals tile_size)

    :return tile_position:      list of tuples, i.th tuple (uly, ulx) specifies the upper-left image coordinates of the
                                i.th tile (w.r.t. the full raster)
    :return region_wo_overlap:  list of tuples, i.th tuple (uly, ulx, lry, lrx) specifies the pixels of the i.th tile
                                that do not overlap with any other tile
    """

    if stride is None:
        stride = tile_size

    tile_position = []
    region_wo_overlap = []

    # Number of regions specified in area_defn
    num_regions = len(area_defn['x_extent'])

    # Iterate over each region
    for i in range(num_regions):
        # Extent of the i.th region
        x = area_defn['x_extent'][i]
        y = area_defn['y_extent'][i]

        # Upper-left coordinates of the tile (w.r.t. full raster)
        uly = y[0]
        lry = y[0]

        # Initialization
        border_uly = 0
        border_lry = stride - 1

        # Split the i.th region into a grid of regular tiles
        while lry < y[1]:
            # Initialization
            ulx = x[0]
            lrx = x[0]
            border_ulx = 0
            border_lrx = stride - 1

            # Compute the lower-right y-coordinate of the tile
            lry = uly + tile_size - 1

            # Check if the tile overlaps the region (in y-direction): if yes, shift the tile upwards such that its
            # lower border coincides with the lower border of the region
            if lry >= y[1]:
                border_uly += lry - y[1]
                lry = y[1]
                uly = y[1] - tile_size + 1
                border_lry = tile_size - 1

            while lrx < x[1]:
                # Compute lower-right x-coordinate of the tile
                lrx = ulx + tile_size - 1

                # Check if the tile overlaps the area (in x-direction): if yes, shift the tile to the left such that
                # its right border coincides with the right border of the region
                if lrx >= x[1]:
                    border_ulx += lrx - x[1]
                    lrx = x[1]
                    ulx = x[1] - tile_size + 1
                    border_lrx = tile_size - 1

                # Save the upper-left corner coordinates of the tile
                tile_position.append((int(uly), int(ulx)))

                # Save the pixels of the tile that do not overlap with any other tile
                region_wo_overlap.append((int(border_uly), int(border_ulx), int(border_lry), int(border_lrx)))

                ulx += stride
                border_ulx = tile_size - stride

            uly += stride
            border_uly = tile_size - stride

    return tile_position, region_wo_overlap