def test_contains_point(self): """Test contain_point method""" area = Area(v_id=1, c_mapinfo=self.c_mapinfo) p = Point(0.5, 0.5) bbox = Bbox(4.0, 0.0, 4.0, 0.0) self.assertTrue(area.contains_point(p, bbox)) self.assertTrue(area.contains_point(p))
def get_bbox(reg, row, col, width, height, overlap): """Return a Bbox :param reg: a Region object to split :type reg: Region object :param row: the number of row :type row: int :param col: the number of row :type col: int :param width: the width of tiles :type width: int :param height: the width of tiles :type height: int :param overlap: the value of overlap between tiles :type overlap: int """ north = reg.north - (row * height - overlap) * reg.nsres south = reg.north - ((row + 1) * height + overlap) * reg.nsres east = reg.west + ((col + 1) * width + overlap) * reg.ewres west = reg.west + (col * width - overlap) * reg.ewres return Bbox( north=north if north <= reg.north else reg.north, south=south if south >= reg.south else reg.south, east=east if east <= reg.east else reg.east, west=west if west >= reg.west else reg.west, )
def bbox(self): """Return the BBox of the vecor map """ bbox = Bbox() if libvect.Vect_get_map_box(self.c_mapinfo, bbox.c_bbox) == 0: raise GrassError("I can not find the Bbox.") return bbox
def __init__(self, points_xy=None, align_to_region=True, xmin=None, xmax=None, ymin=None, ymax=None): if points_xy is not None: points = np.array(points_xy) self.xmin = np.min(points[:,0]) self.xmax = np.max(points[:,0]) self.ymin = np.min(points[:,1]) self.ymax = np.max(points[:,1]) else: self.xmin = xmin self.ymin = ymin self.xmax = xmax self.ymax = ymax if align_to_region is not None: reg = region.Region() self.xmin = np.floor( (self.xmin - reg.get_bbox().west) / reg.ewres ) * \ reg.ewres + reg.get_bbox().west self.ymin = np.floor( (self.ymin - reg.get_bbox().south ) / reg.nsres ) * \ reg.nsres + reg.get_bbox().south self.xmax = np.ceil( (self.xmax - reg.get_bbox().east ) / reg.ewres ) * \ reg.ewres + reg.get_bbox().east self.ymax = np.ceil( (self.ymax - reg.get_bbox().north ) / reg.nsres ) * \ reg.nsres + reg.get_bbox().north self.bbox = Bbox() self.bbox.north = self.ymax self.bbox.south = self.ymin self.bbox.west = self.xmin self.bbox.east = self.xmax
def select_by_bbox(self, bbox): """Return the BBox of the vector map """ # TODO replace with bbox if bbox else Bbox() ?? bbox = Bbox() if libvect.Vect_get_map_box(self.c_mapinfo, bbox.c_bbox) == 0: raise GrassError("I can not find the Bbox.") return bbox
def update_lines(line, alist, cur=None, sql=None): """Update lines using only the boundary""" to_up = [] bbox = Bbox() aline = Line() for area in alist: bbox = area.bbox(bbox) if (intersects(area.get_points(aline), line)) or (area.contain_pnt( line[0], bbox)): to_up.append((line.cat, area.cat)) if (cur is not None) and (sql is not None): cur.executemany(sql, to_up) return to_up
def get_bbox(self): """Return a Bbox object with the extension of the region. :: >>> reg = Region() >>> reg.get_bbox() Bbox(228500.0, 215000.0, 645000.0, 630000.0) .. """ from grass.pygrass.vector.basic import Bbox return Bbox(north=self.north, south=self.south, east=self.east, west=self.west, top=self.top, bottom=self.bottom)
def _get_vector_features_as_wkb_list(lock, conn, data): """Return vector layer features as wkb list supported feature types: point, centroid, line, boundary, area :param lock: A multiprocessing.Lock instance :param conn: A multiprocessing.Pipe instance used to send True or False :param data: The list of data entries [function_id,name,mapset,extent, feature_type, field] """ wkb_list = None try: name = data[1] mapset = data[2] extent = data[3] feature_type = data[4] field = data[5] bbox = None mapset = utils.get_mapset_vector(name, mapset) if not mapset: raise ValueError("Unable to find vector map <%s>" % (name)) layer = VectorTopo(name, mapset) if layer.exist() is True: if extent is not None: bbox = Bbox( north=extent["north"], south=extent["south"], east=extent["east"], west=extent["west"], ) layer.open("r") if feature_type.lower() == "area": wkb_list = layer.areas_to_wkb_list(bbox=bbox, field=field) else: wkb_list = layer.features_to_wkb_list( bbox=bbox, feature_type=feature_type, field=field) layer.close() finally: # Send even if an exception was raised. conn.send(wkb_list)
def main(): """Do the main processing """ # Parse input options: patch_map = options['input'] patches = patch_map.split('@')[0] patches_mapset = patch_map.split('@')[1] if len( patch_map.split('@')) > 1 else None pop_proxy = options['pop_proxy'] layer = options['layer'] costs = options['costs'] cutoff = float(options['cutoff']) border_dist = int(options['border_dist']) conefor_dir = options['conefor_dir'] memory = int(options['memory']) # Parse output options: prefix = options['prefix'] edge_map = '{}_edges'.format(prefix) vertex_map = '{}_vertices'.format(prefix) shortest_paths = '{}_shortest_paths'.format(prefix) # Parse flags: p_flag = flags['p'] t_flag = flags['t'] r_flag = flags['r'] dist_flags = 'kn' if flags['k'] else 'n' lin_cat = 1 zero_dist = None folder = grass.tempdir() if not os.path.exists(folder): os.makedirs(folder) # Setup counter for progress message counter = 0 # Check if location is lat/lon (only in lat/lon geodesic distance # measuring is supported) if grass.locn_is_latlong(): grass.verbose("Location is lat/lon: Geodesic distance \ measure is used") # Check if prefix is legal GRASS name if not grass.legal_name(prefix): grass.fatal('{} is not a legal name for GRASS \ maps.'.format(prefix)) if prefix[0].isdigit(): grass.fatal('Tables names starting with a digit are not SQL \ compliant.'.format(prefix)) # Check if output maps not already exists or could be overwritten for output in [edge_map, vertex_map, shortest_paths]: if grass.db.db_table_exist(output) and not grass.overwrite(): grass.fatal('Vector map <{}> already exists'.format(output)) # Check if input has required attributes in_db_connection = grass.vector.vector_db(patch_map) if not int(layer) in in_db_connection.keys(): grass.fatal('No attribute table connected vector map {} at \ layer {}.'.format(patches, layer)) #Check if cat column exists pcols = grass.vector.vector_columns(patch_map, layer=layer) #Check if cat column exists if not 'cat' in pcols.keys(): grass.fatal('Cannot find the reqired column cat in vector map \ {}.'.format(patches)) #Check if pop_proxy column exists if not pop_proxy in pcols.keys(): grass.fatal('Cannot find column {} in vector map \ {}'.format(pop_proxy, patches)) #Check if pop_proxy column is numeric type if not pcols[pop_proxy]['type'] in ['INTEGER', 'REAL', 'DOUBLE PRECISION']: grass.fatal('Column {} is of type {}. Only numeric types \ (integer or double precision) \ allowed!'.format(pop_proxy, pcols[pop_proxy]['type'])) #Check if pop_proxy column does not contain values <= 0 pop_vals = np.fromstring(grass.read_command('v.db.select', flags='c', map=patches, columns=pop_proxy, nv=-9999).rstrip('\n'), dtype=float, sep='\n') if np.min(pop_vals) <= 0: grass.fatal('Column {} contains values <= 0 or NULL. Neither \ values <= 0 nor NULL allowed!}'.format(pop_proxy)) ############################################## # Use pygrass region instead of grass.parse_command !?! start_reg = grass.parse_command('g.region', flags='ugp') max_n = start_reg['n'] min_s = start_reg['s'] max_e = start_reg['e'] min_w = start_reg['w'] # cost_nsres = reg['nsres'] # cost_ewres = reg['ewres'] # Rasterize patches # http://www.gdal.org/gdal_tutorial.html # http://geoinformaticstutorial.blogspot.no/2012/11/convert- # shapefile-to-raster-with-gdal.html if t_flag: # Rasterize patches with "all-touched" mode using GDAL # Read region-settings (not needed canuse max_n, min_s, max_e, # min_w nsres, ewres... prast = os.path.join(folder, 'patches_rast.tif') # Check if GDAL-GRASS plugin is installed if ogr.GetDriverByName('GRASS'): #With GDAL-GRASS plugin #Locate file for patch vector map pfile = grass.parse_command('g.findfile', element='vector', file=patches, mapset=patches_mapset)['file'] pfile = os.path.join(pfile, 'head') else: # Without GDAL-GRASS-plugin grass.warning("Cannot find GDAL-GRASS plugin. Consider \ installing it in order to save time for \ all-touched rasterisation") pfile = os.path.join(folder, 'patches_vect.gpkg') # Export patch vector map to temp-file in a GDAL-readable # format (shp) grass.run_command('v.out.ogr', flags='m', quiet=True, input=patch_map, type='area', layer=layer, output=pfile, lco='GEOMETRY_NAME=geom') # Rasterize vector map with all-touched option os.system('gdal_rasterize -l {} -at -tr {} {} \ -te {} {} {} {} -ot Uint32 -a cat \ {} {} -q'.format(patches, start_reg['ewres'], start_reg['nsres'], start_reg['w'], start_reg['s'], start_reg['e'], start_reg['n'], pfile, prast)) if not ogr.GetDriverByName('GRASS'): # Remove vector temp-file os.remove(os.path.join(folder, 'patches_vect.gpkg')) # Import rasterized patches grass.run_command('r.external', flags='o', quiet=True, input=prast, output='{}_patches_pol'.format(TMP_PREFIX)) else: # Simple rasterisation (only area) # in G 7.6 also with support for 'centroid' if float(grass.version()['version'][:3]) >= 7.6: conv_types = ['area', 'centroid'] else: conv_types = ['area'] grass.run_command('v.to.rast', quiet=True, input=patches, use='cat', type=conv_types, output='{}_patches_pol'.format(TMP_PREFIX)) # Extract boundaries from patch raster map grass.run_command('r.mapcalc', expression='{p}_patches_boundary=if(\ {p}_patches_pol,\ if((\ (isnull({p}_patches_pol[-1,0])||| \ {p}_patches_pol[-1,0]!={p}_patches_pol)||| \ (isnull({p}_patches_pol[0,1])||| \ {p}_patches_pol[0,1]!={p}_patches_pol)||| \ (isnull({p}_patches_pol[1,0])||| \ {p}_patches_pol[1,0]!={p}_patches_pol)||| \ (isnull({p}_patches_pol[0,-1])||| \ {p}_patches_pol[0,-1]!={p}_patches_pol)), \ {p}_patches_pol,null()), null())'.format(p=TMP_PREFIX), quiet=True) rasterized_cats = grass.read_command( 'r.category', separator='newline', map='{p}_patches_boundary'.format(p=TMP_PREFIX)).replace( '\t', '').strip('\n') rasterized_cats = list( map(int, set([x for x in rasterized_cats.split('\n') if x != '']))) #Init output vector maps if they are requested by user network = VectorTopo(edge_map) network_columns = [(u'cat', 'INTEGER PRIMARY KEY'), (u'from_p', 'INTEGER'), (u'to_p', 'INTEGER'), (u'min_dist', 'DOUBLE PRECISION'), (u'dist', 'DOUBLE PRECISION'), (u'max_dist', 'DOUBLE PRECISION')] network.open('w', tab_name=edge_map, tab_cols=network_columns) vertex = VectorTopo(vertex_map) vertex_columns = [ (u'cat', 'INTEGER PRIMARY KEY'), (pop_proxy, 'DOUBLE PRECISION'), ] vertex.open('w', tab_name=vertex_map, tab_cols=vertex_columns) if p_flag: # Init cost paths file for start-patch grass.run_command('v.edit', quiet=True, map=shortest_paths, tool='create') grass.run_command('v.db.addtable', quiet=True, map=shortest_paths, columns="cat integer,\ from_p integer,\ to_p integer,\ dist_min double precision,\ dist double precision,\ dist_max double precision") start_region_bbox = Bbox(north=float(max_n), south=float(min_s), east=float(max_e), west=float(min_w)) vpatches = VectorTopo(patches, mapset=patches_mapset) vpatches.open('r', layer=int(layer)) ###Loop through patches vpatch_ids = np.array(vpatches.features_to_wkb_list( feature_type="centroid", bbox=start_region_bbox), dtype=[('vid', 'uint32'), ('cat', 'uint32'), ('geom', '|S10')]) cats = set(vpatch_ids['cat']) n_cats = len(cats) if n_cats < len(vpatch_ids['cat']): grass.verbose('At least one MultiPolygon found in patch map.\n \ Using average coordinates of the centroids for \ visual representation of the patch.') for cat in cats: if cat not in rasterized_cats: grass.warning('Patch {} has not been rasterized and will \ therefore not be treated as part of the \ network. Consider using t-flag or change \ resolution.'.format(cat)) continue grass.verbose("Calculating connectivity-distances for patch \ number {}".format(cat)) # Filter from_vpatch = vpatch_ids[vpatch_ids['cat'] == cat] # Get patch ID if from_vpatch['vid'].size == 1: from_centroid = Centroid(v_id=int(from_vpatch['vid']), c_mapinfo=vpatches.c_mapinfo) from_x = from_centroid.x from_y = from_centroid.y # Get centroid if not from_centroid: continue else: xcoords = [] ycoords = [] for f_p in from_vpatch['vid']: from_centroid = Centroid(v_id=int(f_p), c_mapinfo=vpatches.c_mapinfo) xcoords.append(from_centroid.x) ycoords.append(from_centroid.y) # Get centroid if not from_centroid: continue from_x = np.average(xcoords) from_y = np.average(ycoords) # Get BoundingBox from_bbox = grass.parse_command('v.db.select', map=patch_map, flags='r', where='cat={}'.format(cat)) attr_filter = vpatches.table.filters.select(pop_proxy) attr_filter = attr_filter.where("cat={}".format(cat)) proxy_val = vpatches.table.execute().fetchone() # Prepare start patch start_patch = '{}_patch_{}'.format(TMP_PREFIX, cat) reclass_rule = grass.encode('{} = 1\n* = NULL'.format(cat)) recl = grass.feed_command( 'r.reclass', quiet=True, input='{}_patches_boundary'.format(TMP_PREFIX), output=start_patch, rules='-') recl.stdin.write(reclass_rule) recl.stdin.close() recl.wait() # Check if patch was rasterised (patches smaller raster resolution and close to larger patches may not be rasterised) #start_check = grass.parse_command('r.info', flags='r', map=start_patch) #start_check = grass.parse_command('r.univar', flags='g', map=start_patch) #print(start_check) """if start_check['min'] != '1': grass.warning('Patch {} has not been rasterized and will \ therefore not be treated as part of the \ network. Consider using t-flag or change \ resolution.'.format(cat)) grass.run_command('g.remove', flags='f', vector=start_patch, raster=start_patch, quiet=True) grass.del_temp_region() continue""" # Prepare stop patches ############################################ reg = grass.parse_command('g.region', flags='ug', quiet=True, raster=start_patch, n=float(from_bbox['n']) + float(cutoff), s=float(from_bbox['s']) - float(cutoff), e=float(from_bbox['e']) + float(cutoff), w=float(from_bbox['w']) - float(cutoff), align='{}_patches_pol'.format(TMP_PREFIX)) north = reg['n'] if max_n > reg['n'] else max_n south = reg['s'] if min_s < reg['s'] else min_s east = reg['e'] if max_e < reg['e'] else max_e west = reg['w'] if min_w > reg['w'] else min_w # Set region to patch search radius grass.use_temp_region() grass.run_command('g.region', quiet=True, n=north, s=south, e=east, w=west, align='{}_patches_pol'.format(TMP_PREFIX)) # Create buffer around start-patch as a mask # for cost distance analysis grass.run_command('r.buffer', quiet=True, input=start_patch, output='MASK', distances=cutoff) grass.run_command('r.mapcalc', quiet=True, expression='{pf}_patch_{p}_neighbours_contur=\ if({pf}_patches_boundary=={p},\ null(),\ {pf}_patches_boundary)'.format( pf=TMP_PREFIX, p=cat)) grass.run_command('r.mask', flags='r', quiet=True) # Calculate cost distance cost_distance_map = '{}_patch_{}_cost_dist'.format(prefix, cat) grass.run_command('r.cost', flags=dist_flags, quiet=True, overwrite=True, input=costs, output=cost_distance_map, start_rast=start_patch, memory=memory) #grass.run_command('g.region', flags='up') # grass.raster.raster_history(cost_distance_map) cdhist = History(cost_distance_map) cdhist.clear() cdhist.creator = os.environ['USER'] cdhist.write() # History object cannot modify description grass.run_command('r.support', map=cost_distance_map, description='Generated by r.connectivity.distance', history=os.environ['CMDLINE']) # Export distance at boundaries maps = '{0}_patch_{1}_neighbours_contur,{2}_patch_{1}_cost_dist' maps = maps.format(TMP_PREFIX, cat, prefix), connections = grass.encode( grass.read_command('r.stats', flags='1ng', quiet=True, input=maps, separator=';').rstrip('\n')) if connections: con_array = np.genfromtxt(BytesIO(connections), delimiter=';', dtype=None, names=['x', 'y', 'cat', 'dist']) else: grass.warning('No connections for patch {}'.format(cat)) # Write centroid to vertex map vertex.write(Point(from_x, from_y), cat=int(cat), attrs=proxy_val) vertex.table.conn.commit() # Remove temporary map data grass.run_command('g.remove', quiet=True, flags='f', type=['raster', 'vector'], pattern="{}*{}*".format(TMP_PREFIX, cat)) grass.del_temp_region() continue #Find closest points on neigbour patches to_cats = set(np.atleast_1d(con_array['cat'])) to_coords = [] for to_cat in to_cats: connection = con_array[con_array['cat'] == to_cat] connection.sort(order=['dist']) pixel = border_dist if len( connection) > border_dist else len(connection) - 1 # closest_points_x = connection['x'][pixel] # closest_points_y = connection['y'][pixel] closest_points_to_cat = to_cat closest_points_min_dist = connection['dist'][0] closest_points_dist = connection['dist'][pixel] closest_points_max_dist = connection['dist'][-1] to_patch_ids = vpatch_ids[vpatch_ids['cat'] == int(to_cat)]['vid'] if len(to_patch_ids) == 1: to_centroid = Centroid(v_id=to_patch_ids, c_mapinfo=vpatches.c_mapinfo) to_x = to_centroid.x to_y = to_centroid.y elif len(to_patch_ids) >= 1: xcoords = [] ycoords = [] for t_p in to_patch_ids: to_centroid = Centroid(v_id=int(t_p), c_mapinfo=vpatches.c_mapinfo) xcoords.append(to_centroid.x) ycoords.append(to_centroid.y) # Get centroid if not to_centroid: continue to_x = np.average(xcoords) to_y = np.average(ycoords) to_coords.append('{},{},{},{},{},{}'.format( connection['x'][0], connection['y'][0], to_cat, closest_points_min_dist, closest_points_dist, closest_points_max_dist)) #Save edges to network dataset if closest_points_dist <= 0: zero_dist = 1 # Write data to network network.write(Line([(from_x, from_y), (to_x, to_y)]), cat=lin_cat, attrs=( cat, int(closest_points_to_cat), closest_points_min_dist, closest_points_dist, closest_points_max_dist, )) network.table.conn.commit() lin_cat = lin_cat + 1 # Save closest points and shortest paths through cost raster as # vector map (r.drain limited to 1024 points) if requested if p_flag: grass.verbose('Extracting shortest paths for patch number \ {}...'.format(cat)) points_n = len(to_cats) tiles = int(points_n / 1024.0) rest = points_n % 1024 if not rest == 0: tiles = tiles + 1 tile_n = 0 while tile_n < tiles: tile_n = tile_n + 1 #Import closest points for start-patch in 1000er blocks sp = grass.feed_command('v.in.ascii', flags='nr', overwrite=True, quiet=True, input='-', stderr=subprocess.PIPE, output="{}_{}_cp".format( TMP_PREFIX, cat), separator=",", columns="x double precision,\ y double precision,\ to_p integer,\ dist_min double precision,\ dist double precision,\ dist_max double precision") sp.stdin.write(grass.encode("\n".join(to_coords))) sp.stdin.close() sp.wait() # Extract shortest paths for start-patch in chunks of # 1024 points cost_paths = "{}_{}_cost_paths".format(TMP_PREFIX, cat) start_points = "{}_{}_cp".format(TMP_PREFIX, cat) grass.run_command('r.drain', overwrite=True, quiet=True, input=cost_distance_map, output=cost_paths, drain=cost_paths, start_points=start_points) grass.run_command('v.db.addtable', map=cost_paths, quiet=True, columns="cat integer,\ from_p integer,\ to_p integer,\ dist_min double precision,\ dist double precision,\ dist_max double precision") grass.run_command('v.db.update', map=cost_paths, column='from_p', value=cat, quiet=True) grass.run_command('v.distance', quiet=True, from_=cost_paths, to=start_points, upload='to_attr', column='to_p', to_column='to_p') grass.run_command('v.db.join', quiet=True, map=cost_paths, column='to_p', other_column='to_p', other_table=start_points, subset_columns='dist_min,dist,dist_max') #grass.run_command('v.info', flags='c', # map=cost_paths) grass.run_command('v.patch', flags='ae', overwrite=True, quiet=True, input=cost_paths, output=shortest_paths) # Remove temporary map data grass.run_command('g.remove', quiet=True, flags='f', type=['raster', 'vector'], pattern="{}*{}*".format(TMP_PREFIX, cat)) # Remove temporary map data for patch if r_flag: grass.run_command('g.remove', flags='f', type='raster', name=cost_distance_map, quiet=True) vertex.write(Point(from_x, from_y), cat=int(cat), attrs=proxy_val) vertex.table.conn.commit() # Print progress message grass.percent(i=int((float(counter) / n_cats) * 100), n=100, s=3) # Update counter for progress message counter = counter + 1 if zero_dist: grass.warning('Some patches are directly adjacent to others. \ Minimum distance set to 0.0000000001') # Close vector maps and build topology network.close() vertex.close() # Add vertex attributes # grass.run_command('v.db.addtable', map=vertex_map) # grass.run_command('v.db.join', map=vertex_map, column='cat', # other_table=in_db_connection[int(layer)]['table'], # other_column='cat', subset_columns=pop_proxy, # quiet=True) # Add history and meta data to produced maps grass.run_command('v.support', flags='h', map=edge_map, person=os.environ['USER'], cmdhist=os.environ['CMDLINE']) grass.run_command('v.support', flags='h', map=vertex_map, person=os.environ['USER'], cmdhist=os.environ['CMDLINE']) if p_flag: grass.run_command('v.support', flags='h', map=shortest_paths, person=os.environ['USER'], cmdhist=os.environ['CMDLINE']) # Output also Conefor files if requested if conefor_dir: query = """SELECT p_from, p_to, avg(dist) FROM (SELECT CASE WHEN from_p > to_p THEN to_p ELSE from_p END AS p_from, CASE WHEN from_p > to_p THEN from_p ELSE to_p END AS p_to, dist FROM {}) AS x GROUP BY p_from, p_to""".format(edge_map) with open(os.path.join(conefor_dir, 'undirected_connection_file'), 'w') as edges: edges.write( grass.read_command('db.select', sql=query, separator=' ')) with open(os.path.join(conefor_dir, 'directed_connection_file'), 'w') as edges: edges.write( grass.read_command('v.db.select', map=edge_map, separator=' ', flags='c')) with open(os.path.join(conefor_dir, 'node_file'), 'w') as nodes: nodes.write( grass.read_command('v.db.select', map=vertex_map, separator=' ', flags='c'))
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