Example #1
0
    def test_resampling_to_numpy(self):

        region = Region()
        region.ewres = 0.1
        region.nsres = 0.1
        region.adjust()
        region.set_raster_region()

        a = raster2numpy(self.name)

        self.assertEqual(len(a), 400)

        region.ewres = 1
        region.nsres = 1
        region.adjust()
        region.set_raster_region()

        a = raster2numpy(self.name)

        self.assertEqual(len(a), 40)

        region.ewres = 5
        region.nsres = 5
        region.adjust()
        region.set_raster_region()

        a = raster2numpy(self.name)

        self.assertEqual(len(a), 8)
    def test_resampling_to_numpy(self):
        
        region = Region()
        region.ewres = 0.1
        region.nsres = 0.1
        region.adjust()
        region.set_raster_region()
        
        a = raster2numpy(self.name)
        
        self.assertEqual(len(a), 400)
        
        region.ewres = 1
        region.nsres = 1
        region.adjust()
        region.set_raster_region()
        
        a = raster2numpy(self.name)
        
        self.assertEqual(len(a), 40)

        region.ewres = 5
        region.nsres = 5
        region.adjust()
        region.set_raster_region()
        
        a = raster2numpy(self.name)
        
        self.assertEqual(len(a), 8)
Example #3
0
 def setUpClass(cls):
     """Create test raster map and region"""
     cls.use_temp_region()
     cls.runModule("g.region", n=40, s=0, e=60, w=0, res=1)
     cls.runModule("r.mapcalc", expression="%s = float(row() + (10.0 * col()))"%(cls.name),
                                overwrite=True)
     cls.numpy_obj = raster2numpy(cls.name)
Example #4
0
 def setUpClass(cls):
     """Create test raster map and region"""
     cls.use_temp_region()
     cls.runModule("g.region", n=40, s=0, e=60, w=0, res=1)
     cls.runModule("r.mapcalc",
                   expression="%s = float(row() + (10.0 * col()))" %
                   (cls.name),
                   overwrite=True)
     cls.numpy_obj = raster2numpy(cls.name)
Example #5
0
    def _rst2np(self, raster):
        """
        Convert raster data into numpy array

        :param raster: raster name

        :return: numpy array
        """
        # raster is read from current computation region
        # g.region cannot be called here,
        # see https://github.com/storm-fsv-cvut/smoderp2d/issues/42
        Region().from_rast(raster)
        return raster2numpy(raster)
Example #6
0
def calcPhotoPolRast(myraster, max_distance, reg, elevation, lights,
                     photopollution):

    map2d_1 = garray.array()
    map2d_1.fill(np.nan)

    elev = raster.raster2numpy(elevation)
    elev = elev <> None

    xcoords, ycoords = elev.nonzero()

    counter = 1
    for pixel in zip(list(xcoords), list(ycoords)):

        i = pixel[0]
        j = pixel[1]

        indexForPixel = calcPhotoPol(i,
                                     j,
                                     reg=reg,
                                     elevation=elevation,
                                     lights=lights,
                                     max_distance=max_distance,
                                     obs_elevation=OBS_ELEVATION)
        map2d_1[i, j] = indexForPixel

        perc = counter * 100 / len(xcoords)
        print 'pixel:{}/{} - {}%'.format(counter, len(xcoords), perc)
        counter += 1

    #write array object to grass raster map
    map2d_1.write(
        mapname=photopollution,
        overwrite=True)  #SOS: region should be the same as generated map2d_1

    return ('map2d_1')
Example #7
0
    def _get_mat_stream_seg(self, stream):
        """Get numpy array of integers detecting whether there is a stream on
        corresponding pixel of raster (number equal or greater than
        1000 in return numpy array) or not (number 0 in return numpy
        array).

        :param stream: Polyline with stream in the area.
        :return mat_stream_seg: Numpy array
        """
        Module('g.region', vector=stream, res=self.spix)
        Module('v.to.rast',
               input=stream,
               type='line',
               output='stream_rst',
               use='cat')
        # TODO: probably not needed, see relevant code in arcgis provider
        Module(
            'r.mapcalc',
            # expression='{o} = if(isnull({i}), 1000, {i})'.format(
            expression='{o} = {i}'.format(o='stream_seg', i='stream_rst'))
        # TODO: probably not needed
        # Module('g.region',
        #        w=self.ll_corner[0],
        #        s=self.ll_corner[1],
        #        cols=self.cols,
        #        rows=self.rows
        # )
        mat_stream_seg = raster2numpy('stream_seg')
        mat_stream_seg = mat_stream_seg.astype('int16')

        # TODO: ?
        no_of_streams = mat_stream_seg.max()

        self._get_mat_stream_seg_(mat_stream_seg, no_of_streams)

        return mat_stream_seg
Example #8
0
def main():

    try:
        import pysptools.eea as eea
    except ImportError:
        gs.fatal(_("Cannot import pysptools \
                      (https://pypi.python.org/pypi/pysptools) library."
                      " Please install it (pip install pysptools)"
                      " or ensure that it is on path"
                      " (use PYTHONPATH variable)."))

    try:
        # sklearn is a dependency of used pysptools functionality
        import sklearn
    except ImportError:
        gs.fatal(_("Cannot import sklearn \
                      (https://pypi.python.org/pypi/scikit-learn) library."
                      " Please install it (pip install scikit-learn)"
                      " or ensure that it is on path"
                      " (use PYTHONPATH variable)."))

    try:
        from cvxopt import solvers, matrix
    except ImportError:
        gs.fatal(_("Cannot import cvxopt \
                      (https://pypi.python.org/pypi/cvxopt) library."
                      " Please install it (pip install cvxopt)"
                      " or ensure that it is on path"
                      " (use PYTHONPATH variable)."))

    # Parse input options
    input = options['input']
    output = options['output']
    prefix = options['prefix']
    endmember_n = int(options['endmember_n'])
    endmembers = options['endmembers']
    if options['maxit']:
        maxit = options['maxit']
    else:
        maxit = 0
    extraction_method = options['extraction_method']
    unmixing_method = options['unmixing_method']
    atgp_init = True if not flags['n'] else False

    # List maps in imagery group
    try:
        maps = gs.read_command('i.group', flags='g', group=input,
                               quiet=True).rstrip('\n').split('\n')
    except:
        pass

    # Validate input
    # q and maxit can be None according to manual, but does not work in current pysptools version
    if endmember_n <= 0:
        gs.fatal('Number of endmembers has to be > 0')
        """if (extraction_method == 'PPI' or
            extraction_method == 'NFINDR'):
            gs.fatal('Extraction methods PPI and NFINDR require endmember_n >= 2')
        endmember_n = None"""

    if maxit <= 0:
        maxit = 3 * len(maps)

    if endmember_n > len(maps) + 1:
        gs.warning('More endmembers ({}) requested than bands in \
                   input imagery group ({})'.format(endmember_n, len(maps)))
        if extraction_method != 'PPI':
            gs.fatal('Only PPI method can extract more endmembers than number \
                     of bands in the imagery group')

    if not atgp_init and extraction_method != 'NFINDR':
        gs.verbose('ATGP is only taken into account in \
                   NFINDR extraction method...')

    # Get metainformation from input bands
    band_types = {}
    img = None
    n = 0
    gs.verbose('Reading imagery group...')
    for m in maps:
        map = m.split('@')

        # Build numpy stack from imagery group
        raster = r.raster2numpy(map[0], mapset=map[1])
        if raster == np.float64:
            raster = float32(raster)
            gs.warning('{} is of type Float64.\
                        Float64 is currently not supported.\
                        Reducing precision to Float32'.format(raster))

        # Determine map type
        band_types[map[0]] = get_rastertype(raster)

        # Create cube and mask from GRASS internal NoData value
        if n == 0:
            img = raster
            # Create mask from GRASS internal NoData value
            mask = mask_rasternd(raster)
        else:
            img = np.dstack((img, raster))
            mask = np.logical_and((mask_rasternd(raster)), mask)

        n = n + 1

    # Read a mask if present and give waringing if not
    # Note that otherwise NoData is read as values
    gs.verbose('Checking for MASK...')
    try:
        MASK = r.raster2numpy('MASK', mapset=getenv('MAPSET')) == 1
        mask = np.logical_and(MASK, mask)
        MASK = None
    except:
        pass

    if extraction_method == 'NFINDR':
    # Extract endmembers from valid pixels using NFINDR function from pysptools
        gs.verbose('Extracting endmembers using NFINDR...')
        nfindr = eea.NFINDR()
        E = nfindr.extract(img, endmember_n, maxit=maxit, normalize=False,
                           ATGP_init=atgp_init, mask=mask)
    elif extraction_method == 'PPI':
    # Extract endmembers from valid pixels using PPI function from pysptools
        gs.verbose('Extracting endmembers using PPI...')
        ppi = eea.PPI()
        E = ppi.extract(img, endmember_n, numSkewers=10000, normalize=False,
                        mask=mask)
    elif extraction_method == 'FIPPI':
    # Extract endmembers from valid pixels using FIPPI function from pysptools
        gs.verbose('Extracting endmembers using FIPPI...')
        fippi = eea.FIPPI()
        # q and maxit can be None according to manual, but does not work
        """if not maxit and not endmember_n:
            E = fippi.extract(img, q=None, normalize=False, mask=mask)
        if not maxit:
            E = fippi.extract(img, q=endmember_n, normalize=False, mask=mask)
        if not endmember_n:
            E = fippi.extract(img, q=int(), maxit=maxit, normalize=False,
                              mask=mask)
        else:
            E = fippi.extract(img, q=endmember_n, maxit=maxit, normalize=False,
                              mask=mask)"""
        E = fippi.extract(img, q=endmember_n, maxit=maxit, normalize=False,
                          mask=mask)

    # Write output file in format required for i.spec.unmix addon
    if output:
        gs.verbose('Writing spectra file...')
        n = 0
        with open(output, 'w') as o:
            o.write('# Channels: {}\n'.format('\t'.join(band_types.keys())))
            o.write('# Wrote {} spectra line wise.\n#\n'.format(endmember_n))
            o.write('Matrix: {0} by {1}\n'.format(endmember_n, len(maps)))
            for e in E:
                o.write('row{0}: {1}\n'.format(n, '\t'.join([str(i) for i in  e])))
                n = n + 1

    # Write vector map with endmember information if requested
    if endmembers:
        gs.verbose('Writing vector map with endmembers...')
        from grass.pygrass import utils as u
        from grass.pygrass.gis.region import Region
        from grass.pygrass.vector import Vector
        from grass.pygrass.vector import VectorTopo
        from grass.pygrass.vector.geometry import Point

        # Build attribute table
        # Deinfe columns for attribute table
        cols = [(u'cat',       'INTEGER PRIMARY KEY')]
        for b in band_types.keys():
            cols.append((b.replace('.','_'), band_types[b]))
        
        # Get region information
        reg = Region()

        # Create vector map
        new = Vector(endmembers)
        new.open('w', tab_name=endmembers, tab_cols=cols)

        cat = 1
        for e in E:
            # Get indices
            idx = np.where((img[:,:]==e).all(-1))

            # Numpy array is ordered rows, columns (y,x)
            if len(idx[0]) == 0 or len(idx[1]) == 0:
                gs.warning('Could not compute coordinated for endmember {}. \
                            Please consider rescaling your data to integer'.format(cat))
                cat = cat + 1
                continue

            coords = u.pixel2coor((idx[1][0], idx[0][0]), reg)
            point = Point(coords[1] + reg.ewres / 2.0,
                          coords[0] - reg.nsres / 2.0)

            # Get attributes
            n = 0
            attr = []
            for b in band_types.keys():
                if band_types[b] == u'INTEGER':
                    attr.append(int(e[n]))
                else:
                    attr.append(float(e[n]))
                n = n + 1

            # Write geometry with attributes
            new.write(point, cat=cat,
                      attrs=tuple(attr))
            cat = cat + 1

        # Close vector map
        new.table.conn.commit()
        new.close(build=True)

    if prefix:
        # Run spectral unmixing
        import pysptools.abundance_maps as amaps
        if unmixing_method == 'FCLS':
            fcls = amaps.FCLS()
            result = fcls.map(img, E, normalize=False, mask=mask)
        elif unmixing_method == 'NNLS':
            nnls = amaps.NNLS()
            result = nnls.map(img, E, normalize=False, mask=mask)
        elif unmixing_method == 'UCLS':
            ucls = amaps.UCLS()
            result = ucls.map(img, E, normalize=False, mask=mask)

        # Write results
        for l in range(endmember_n):
            rastname = '{0}_{1}'.format(prefix, l + 1)
            r.numpy2raster(result[:,:,l], 'FCELL', rastname)
Example #9
0
def visual_magnitude_reverse(lreg_shape, t_loc, np_viewshed, reg, exp_range,
                             r_dsm, dsm_type, v_elevation, b_1):
    """Calculate visual magnitude from viewpoints to target based on
    Chamberlain (2011) and Chamberlain & Meither (2013) and use it to
    parametrise binary viewshed
    :param lreg_shape: Dimensions of local computational region
    :type lreg_shape: list
    :param t_loc: Array of target point coordinates in local coordinate system
    :type t_loc: ndarray
    :param np_viewshed: 2D array of binary viewshed
    :type np_viewshed: ndarray
    :param reg: computational region
    :type reg: Region()
    :param exp_range: exposure range
    :type reg: float
    :param r_dsm: Name of digital surface model raster
    :type r_dsm: string
    :param dsm_type: Raster map precision type
    :type dsm_type: string
    :param v_elevation: Observer height
    :type v_elevation: float
    :param b_1: radius in fuzzy viewshed parametrisation
    :type b_1: float
    :return: 2D array of weighted parametrised viewshed
    :rtype: ndarray
    """
    # 1. Convert DSM to numpy
    np_dsm = raster2numpy(r_dsm)

    # Ensure that values are represented as float (in case of CELL
    # data type) and replace integer NaN with numpy NaN
    dsm_type = grass.parse_command("r.info", map=r_dsm, flags="g")["datatype"]

    if dsm_type == "CELL":
        np_dsm = np_dsm.astype(np.float32)
        np_dsm[np_dsm == -2147483648] = np.nan

    # 2. local row, col coordinates and global Z coordinate of observer points V
    #    3D array (lreg_shape[0] x lreg_shape[1] x 3)
    v_loc = np.array([
        np.tile(
            np.arange(0.5, lreg_shape[0] + 0.5).reshape(-1, 1),
            (1, lreg_shape[1])),
        np.tile(
            np.arange(0.5, lreg_shape[1] + 0.5).reshape(-1, 1).transpose(),
            (lreg_shape[0], 1),
        ),
        np_dsm + v_elevation,
    ])

    # 3. vector VT, adjusted for cell size
    #    3D array (lreg_shape[0] x lreg_shape[1] x 3)
    v_vect = np.array([
        (v_loc[0] - t_loc[0]) * reg.nsres,
        (v_loc[1] - t_loc[1]) * reg.ewres,
        (v_loc[2] - t_loc[2]),
    ])

    # 4. projection of vector VT to XZ and YZ plane, adjusted for cell size
    v_vect_ns = np.array([(v_loc[0] - t_loc[0]) * reg.nsres,
                          v_loc[2] - t_loc[2]])

    v_vect_ew = np.array([(v_loc[1] - t_loc[1]) * reg.ewres,
                          v_loc[2] - t_loc[2]])

    v_vect_ns_unit = v_vect_ns / np.linalg.norm(v_vect_ns, axis=0)
    v_vect_ew_unit = v_vect_ew / np.linalg.norm(v_vect_ew, axis=0)

    # 5. size of vector VT
    #    2D array (lreg_shape[0] x lreg_shape[1])
    v_scal = np.sqrt(v_vect[0]**2 + v_vect[1]**2 + v_vect[2]**2)
    # replace 0 distance for central pixel by resolution to avoid division by 0
    # v_scal = np.where(abs(v_scal) < 1e-6, (reg.nsres + reg.ewres) / 2, v_scal)
    # grass.verbose(v_scal)

    # 6. vector n, its projection to XZ, YZ plane
    #   1D array [X, Z], [Y, Z]
    #   already unit vector
    n_vect_ns_unit = [
        np.sin(np.radians(t_loc[4])),
        np.cos(np.radians(t_loc[4]))
    ]
    n_vect_ew_unit = [
        np.sin(np.radians(t_loc[3])),
        np.cos(np.radians(t_loc[3]))
    ]

    # 7. angles beta (ns), theta (ew) (0-90 degrees)
    #    2D array (lreg_shape[0] x lreg_shape[1])
    beta = np.arccos(n_vect_ns_unit[0] * v_vect_ns_unit[:][0] +
                     n_vect_ns_unit[1] * v_vect_ns_unit[:][1])
    beta = np.where(beta > math.pi / 2, beta - math.pi / 2, beta)

    theta = np.arccos(n_vect_ew_unit[0] * v_vect_ew_unit[:][0] +
                      n_vect_ew_unit[1] * v_vect_ew_unit[:][1])
    theta = np.where(theta > math.pi / 2, theta - math.pi / 2, theta)

    # 8. visual magnitude adjusted for distance weight
    visual_magnitude = (np.cos(beta) * np.cos(theta) *
                        ((reg.nsres * reg.ewres) / (v_scal**2)))

    # 9. Multiply visual magnitude by binary viewshed and weight
    return visual_magnitude * np_viewshed * t_loc[-1]
Example #10
0
def solid_angle_reverse(lreg_shape, t_loc, np_viewshed, reg, exp_range, r_dsm,
                        dsm_type, v_elevation, b_1):
    """Calculate solid angle from viewpoints to target based on
    Domingo-Santos et al. (2011) and use it to parametrise binary viewshed
    :param lreg_shape: Dimensions of local computational region
    :type lreg_shape: list
    :param t_loc: Array of target point coordinates in local coordinate system
    :type t_loc: ndarray
    :param np_viewshed: 2D array of binary viewshed
    :type np_viewshed: ndarray
    :param reg: computational region
    :type reg: Region()
    :param exp_range: exposure range
    :type reg: float
    :param r_dsm: Name of digital surface model raster
    :type r_dsm: string
    :param dsm_type: Raster map precision type
    :type dsm_type: string
    :param v_elevation: Observer height
    :type v_elevation: float
    :param b_1: radius in fuzzy viewshed parametrisation
    :type b_1: float
    :return: 2D array of weighted parametrised viewshed
    :rtype: ndarray
    """
    # 1. Convert DSM to numpy
    np_dsm = raster2numpy(r_dsm)

    # Ensure that values are represented as float (in case of CELL
    # data type) and replace integer NaN with numpy NaN
    if dsm_type == "CELL":
        np_dsm = np_dsm.astype(np.float32)
        np_dsm[np_dsm == -2147483648] = np.nan

    # 2. local row, col coordinates and global Z coordinate of observer points V
    #    3D array (lreg_shape[0] x lreg_shape[1] x 3)
    v_loc = np.array([
        np.tile(
            np.arange(0.5, lreg_shape[0] + 0.5).reshape(-1, 1),
            (1, lreg_shape[1])),
        np.tile(
            np.arange(0.5, lreg_shape[1] + 0.5).reshape(-1, 1).transpose(),
            (lreg_shape[0], 1),
        ),
        np_dsm + v_elevation,
    ])

    # 3. local row, col coordinates and global Z coordinate of points A, B, C, D
    #    1D array [row, col, Z]
    a_loc = np.array([t_loc[0] + 0.5, t_loc[1] - 0.5, t_loc[3]])

    b_loc = np.array([t_loc[0] - 0.5, t_loc[1] - 0.5, t_loc[4]])

    c_loc = np.array([t_loc[0] - 0.5, t_loc[1] + 0.5, t_loc[5]])

    d_loc = np.array([t_loc[0] + 0.5, t_loc[1] + 0.5, t_loc[6]])

    # 4. vectors a, b, c, d, adjusted for cell size
    #    3D array (lreg_shape[0] x lreg_shape[1] x 3)
    a_vect = np.array([
        (v_loc[0] - a_loc[0]) * reg.nsres,
        (v_loc[1] - a_loc[1]) * reg.ewres,
        (v_loc[2] - a_loc[2]),
    ])

    b_vect = np.array([
        (v_loc[0] - b_loc[0]) * reg.nsres,
        (v_loc[1] - b_loc[1]) * reg.ewres,
        (v_loc[2] - b_loc[2]),
    ])

    c_vect = np.array([
        (v_loc[0] - c_loc[0]) * reg.nsres,
        (v_loc[1] - c_loc[1]) * reg.ewres,
        (v_loc[2] - c_loc[2]),
    ])

    d_vect = np.array([
        (v_loc[0] - d_loc[0]) * reg.nsres,
        (v_loc[1] - d_loc[1]) * reg.ewres,
        (v_loc[2] - d_loc[2]),
    ])

    # 5. sizes of vectors a, b, c, d
    #    2D array (lreg_shape[0] x lreg_shape[1])
    a_scal = np.sqrt(a_vect[0]**2 + a_vect[1]**2 + a_vect[2]**2)
    b_scal = np.sqrt(b_vect[0]**2 + b_vect[1]**2 + b_vect[2]**2)
    c_scal = np.sqrt(c_vect[0]**2 + c_vect[1]**2 + c_vect[2]**2)
    d_scal = np.sqrt(d_vect[0]**2 + d_vect[1]**2 + d_vect[2]**2)

    # 6. scalar products ab, ac, bc, ad, dc
    #    2D arrays (lreg_shape[0] x lreg_shape[1])
    ab_scal = sum(a_vect * b_vect)
    ac_scal = sum(a_vect * c_vect)
    bc_scal = sum(b_vect * c_vect)
    ad_scal = sum(a_vect * d_vect)
    dc_scal = sum(d_vect * c_vect)

    # 7. determinants of matrix abc, adc
    #    2D arrays (lreg_shape[0] x lreg_shape[1])
    det_abc = (a_vect[0] * (b_vect[1] * c_vect[2] - b_vect[2] * c_vect[1]) -
               b_vect[0] * (a_vect[1] * c_vect[2] - a_vect[2] * c_vect[1]) +
               c_vect[0] * (a_vect[1] * b_vect[2] - a_vect[2] * b_vect[1]))

    det_adc = (a_vect[0] * (d_vect[1] * c_vect[2] - d_vect[2] * c_vect[1]) -
               d_vect[0] * (a_vect[1] * c_vect[2] - a_vect[2] * c_vect[1]) +
               c_vect[0] * (a_vect[1] * d_vect[2] - a_vect[2] * d_vect[1]))

    # 8. solid angle
    solid_angle_1 = np.arctan2(
        det_abc,
        a_scal * b_scal * c_scal + ab_scal * c_scal + ac_scal * b_scal +
        bc_scal * a_scal,
    )

    solid_angle_2 = np.arctan2(
        det_adc,
        a_scal * d_scal * c_scal + ad_scal * c_scal + ac_scal * d_scal +
        dc_scal * a_scal,
    )

    solid_angle = np.absolute(solid_angle_1) + np.absolute(solid_angle_2)

    # 9. Multiply solid angle by binary viewshed and weight
    return solid_angle * np_viewshed * t_loc[-1]
Example #11
0
def do_it_all(global_vars, target_pts_np):
    """Conduct weighted and parametrised partial viewshed and cummulate it with
    the previous partial viewsheds
    :param target_pts_np: Array of target points in global coordinate system
    :type target_pts_np: ndarray
    :return: 2D array of weighted parametrised cummulative viewshed
    :rtype: ndarray
    """
    # Set counter
    counter = 1

    # Get variables out of global_vars dictionary
    reg = global_vars["region"]
    exp_range = global_vars["range"]
    flagstring = global_vars["flagstring"]
    r_dsm = global_vars["r_dsm"]
    v_elevation = global_vars["observer_elevation"]
    refr_coeff = global_vars["refr_coeff"]
    memory = global_vars["memory"]
    parametrise_viewshed = global_vars["param_viewshed"]
    dsm_type = global_vars["dsm_type"]
    b_1 = global_vars["b_1"]
    cores = global_vars["cores"]
    tempname = global_vars["tempname"]

    # Create empty viewshed
    np_cum = np.empty((reg.rows, reg.cols), dtype=np.single)
    np_cum[:] = np.nan
    tmp_vs = "{}_{}".format(tempname, os.getpid())

    for target_pnt in target_pts_np:

        # Display a progress info message
        grass.percent(counter, len(target_pts_np), 1)
        grass.verbose("Processing point {i} ({p:.1%})".format(
            i=int(target_pnt[0]), p=counter / len(target_pts_np)))

        # Global coordinates and attributes of target point T
        t_glob = target_pnt[1:]

        # ======================================================================
        # 1. Set local computational region: +/- exp_range from target point
        # ======================================================================
        # compute position of target point within a pixel
        delta_n = math.ceil(
            (t_glob[1] - reg.south) / reg.nsres) * reg.nsres - (t_glob[1] -
                                                                reg.south)
        delta_s = (t_glob[1] - reg.south) - math.floor(
            (t_glob[1] - reg.south) / reg.nsres) * reg.nsres
        delta_e = math.ceil(
            (t_glob[0] - reg.west) / reg.ewres) * reg.ewres - (t_glob[0] -
                                                               reg.west)
        delta_w = (t_glob[0] - reg.west) - math.floor(
            (t_glob[0] - reg.west) / reg.ewres) * reg.ewres

        # ensure that local region doesn't exceed global region
        loc_reg_n = min(t_glob[1] + exp_range + delta_n, reg.north)
        loc_reg_s = max(t_glob[1] - exp_range - delta_s, reg.south)
        loc_reg_e = min(t_glob[0] + exp_range + delta_e, reg.east)
        loc_reg_w = max(t_glob[0] - exp_range - delta_w, reg.west)

        # pygrass sets region for pygrass tasks
        lreg = deepcopy(reg)
        lreg.set_bbox(Bbox(loc_reg_n, loc_reg_s, loc_reg_e, loc_reg_w))
        lreg.set_raster_region()

        # Create processing environment with region information
        c_env = os.environ.copy()
        c_env["GRASS_REGION"] = grass.region_env(n=loc_reg_n,
                                                 s=loc_reg_s,
                                                 e=loc_reg_e,
                                                 w=loc_reg_w)

        lreg_shape = [lreg.rows, lreg.cols]

        # ======================================================================
        # 2. Calculate binary viewshed and convert to numpy
        # ======================================================================
        vs = grass.pipe_command(
            "r.viewshed",
            flags="b" + flagstring,
            input=r_dsm,
            output=tmp_vs,
            coordinates="{},{}".format(t_glob[0], t_glob[1]),
            observer_elevation=0.0,
            target_elevation=v_elevation,
            max_distance=exp_range,
            refraction_coeff=refr_coeff,
            memory=int(round(memory / cores)),
            quiet=True,
            overwrite=True,
            env=c_env,
        )
        vs.communicate()
        # Workaround for https://github.com/OSGeo/grass/issues/1436
        clean_temp(vs.pid)

        # Read viewshed into numpy with single precision and replace NoData
        np_viewshed = raster2numpy(tmp_vs).astype(np.single)
        np_viewshed[np_viewshed == -2147483648] = np.nan

        # ======================================================================
        # 3. Prepare local coordinates and attributes of target point T
        # ======================================================================
        # Calculate how much of rows/cols of local region lies
        # outside global region
        o_1 = [
            max(t_glob[1] + exp_range + reg.nsres / 2 - reg.north, 0),
            max(reg.west - (t_glob[0] - exp_range - reg.ewres / 2), 0),
        ]

        t_loc = np.append(
            np.array([
                exp_range / reg.nsres + 0.5 - o_1[0] / reg.nsres,
                exp_range / reg.ewres + 0.5 - o_1[1] / reg.ewres,
            ]),
            t_glob[2:],
        )

        # ======================================================================
        # 4. Parametrise viewshed
        # ======================================================================
        np_viewshed = parametrise_viewshed(
            lreg_shape,
            t_loc,
            np_viewshed,
            reg,
            exp_range,
            r_dsm,
            dsm_type,
            v_elevation,
            b_1,
        ).astype(np.single)

        # ======================================================================
        # 5. Cummulate viewsheds
        # ======================================================================
        # Determine position of local parametrised viewshed within
        # global cummulative viewshed
        o_2 = [
            int(round((reg.north - loc_reg_n) / reg.nsres)),  # NS (rows)
            int(round((loc_reg_w - reg.west) / reg.ewres)),  # EW (cols)
        ]

        # Add local parametrised viewshed to global cummulative viewshed
        # replace nans with 0 in processed regions, keep nan where both are nan
        all_nan = np.all(
            np.isnan([
                np_cum[o_2[0]:o_2[0] + lreg_shape[0],
                       o_2[1]:o_2[1] + lreg_shape[1]],
                np_viewshed,
            ]),
            axis=0,
        )

        np_cum[o_2[0]:o_2[0] + lreg_shape[0],
               o_2[1]:o_2[1] + lreg_shape[1]] = np.nansum(
                   [
                       np_cum[o_2[0]:o_2[0] + lreg_shape[0],
                              o_2[1]:o_2[1] + lreg_shape[1]],
                       np_viewshed,
                   ],
                   axis=0,
               )

        np_cum[o_2[0]:o_2[0] + lreg_shape[0],
               o_2[1]:o_2[1] + lreg_shape[1]][all_nan] = np.nan

        counter += 1

    return np_cum