def get_electro_length(opts): # open vector plant pname = opts["struct"] pname, vmapset = pname.split("@") if "@" in pname else (pname, "") with VectorTopo(pname, mapset=vmapset, layer=int(opts["struct_layer"]), mode="r") as vect: kcol = opts["struct_column_kind"] ktype = opts["struct_kind_turbine"] # check if electro_length it is alredy in the table if "electro_length" not in vect.table.columns: vect.table.columns.add("electro_length", "double precision") # open vector map with the existing electroline ename = opts["electro"] ename, emapset = ename.split("@") if "@" in ename else (ename, "") ltemp = [] with VectorTopo(ename, mapset=emapset, layer=int(opts["electro_layer"]), mode="r") as electro: pid = os.getpid() elines = opts["elines"] if opts["elines"] else ( "tmprgreen_%i_elines" % pid) for cat, line in enumerate(vect): if line.attrs[kcol] == ktype: # the turbine is the last point of the penstock turbine = line[-1] # find the closest electro line eline = electro.find["by_point"].geo(turbine, maxdist=1e6) dist = eline.distance(turbine) line.attrs["electro_length"] = dist.dist if line.attrs["side"] == "option1": ltemp.append([ geo.Line([turbine, dist.point]), (line.attrs["plant_id"], line.attrs["side"]), ]) else: line.attrs["electro_length"] = 0.0 vect.table.conn.commit() new = VectorTopo(elines) # new vec with elines new.layer = 1 cols = [ (u"cat", "INTEGER PRIMARY KEY"), (u"plant_id", "VARCHAR(10)"), (u"side", "VARCHAR(10)"), ] new.open("w", tab_cols=cols) reg = Region() for cat, line in enumerate(ltemp): if version == 70: new.write(line[0], line[1]) else: new.write(line[0], cat=cat, attrs=line[1]) new.table.conn.commit() new.comment = " ".join(sys.argv) new.close()
def get_electro_length(opts): # open vector plant pname = opts['struct'] pname, vmapset = pname.split('@') if '@' in pname else (pname, '') with VectorTopo(pname, mapset=vmapset, layer=int(opts['struct_layer']), mode='r') as vect: kcol = opts['struct_column_kind'] ktype = opts['struct_kind_turbine'] # check if electro_length it is alredy in the table if 'electro_length' not in vect.table.columns: vect.table.columns.add('electro_length', 'double precision') # open vector map with the existing electroline ename = opts['electro'] ename, emapset = ename.split('@') if '@' in ename else (ename, '') ltemp = [] with VectorTopo(ename, mapset=emapset, layer=int(opts['electro_layer']), mode='r') as electro: pid = os.getpid() elines = (opts['elines'] if opts['elines'] else ('tmprgreen_%i_elines' % pid)) for cat, line in enumerate(vect): if line.attrs[kcol] == ktype: # the turbine is the last point of the penstock turbine = line[-1] # find the closest electro line eline = electro.find['by_point'].geo(turbine, maxdist=1e6) dist = eline.distance(turbine) line.attrs['electro_length'] = dist.dist if line.attrs['side'] == 'option1': ltemp.append([ geo.Line([turbine, dist.point]), (line.attrs['plant_id'], line.attrs['side']) ]) else: line.attrs['electro_length'] = 0. vect.table.conn.commit() new = VectorTopo(elines) # new vec with elines new.layer = 1 cols = [ (u'cat', 'INTEGER PRIMARY KEY'), (u'plant_id', 'VARCHAR(10)'), (u'side', 'VARCHAR(10)'), ] new.open('w', tab_cols=cols) reg = Region() for cat, line in enumerate(ltemp): if version == 70: new.write(line[0], line[1]) else: new.write(line[0], cat=cat, attrs=line[1]) new.table.conn.commit() new.comment = (' '.join(sys.argv)) new.close()
def extendLine(map, map_out, maxlen=200, scale=0.5, debug=False, verbose=1): # # map=Input map name # map_out=Output map with extensions # maxlen=Max length in map units that line can be extended (def=200) # scale=Maximum length of extension as proportion of original line, disabled if 0 (def=0.5) # vlen=number of verticies to look back in calculating line end direction (def=1) # Not sure if it is worth putting this in as parameter. # allowOverwrite = os.getenv('GRASS_OVERWRITE', '0') == '1' grass.info("map={}, map_out={}, maxlen={}, scale={}, debug={}".format( map, map_out, maxlen, scale, debug)) vlen = 1 # not sure if this is worth putting in as parameter cols = [(u'cat', 'INTEGER PRIMARY KEY'), (u'parent', 'INTEGER'), (u'dend', 'TEXT'), (u'orgx', 'DOUBLE PRECISION'), (u'orgy', 'DOUBLE PRECISION'), (u'search_len', 'DOUBLE PRECISION'), (u'search_az', 'DOUBLE PRECISION'), (u'best_xid', 'INTEGER'), (u'near_x', 'DOUBLE PRECISION'), (u'near_y', 'DOUBLE PRECISION'), (u'other_cat', 'INTEGER'), (u'xtype', 'TEXT'), (u'x_len', 'DOUBLE PRECISION')] extend = VectorTopo('extend') if extend.exist(): extend.remove() extend.open('w', tab_name='extend', tab_cols=cols) # # Go through input map, looking at each line and it's two nodes to find nodes # with only a single line starting/ending there - i.e. a dangle. # For each found, generate an extension line in the new map "extend" # inMap = VectorTopo(map) inMap.open('r') dangleCnt = 0 tickLen = len(inMap) grass.info("Searching {} features for dangles".format(tickLen)) ticker = 0 grass.message("Percent complete...") for ln in inMap: ticker = (ticker + 1) grass.percent(ticker, tickLen, 5) if ln.gtype == 2: # Only process lines for nd in ln.nodes(): if nd.nlines == 1: # We have a dangle dangleCnt = dangleCnt + 1 vtx = min(len(ln) - 1, vlen) if len([1 for _ in nd.lines(only_out=True) ]) == 1: # Dangle starting at node dend = "head" sx = ln[0].x sy = ln[0].y dx = sx - ln[vtx].x dy = sy - ln[vtx].y else: # Dangle ending at node dend = "tail" sx = ln[-1].x sy = ln[-1].y dx = sx - ln[-(vtx + 1)].x dy = sy - ln[-(vtx + 1)].y endaz = math.atan2(dy, dx) if scale > 0: extLen = min(ln.length() * scale, maxlen) else: extLen = maxlen ex = extLen * math.cos(endaz) + sx ey = extLen * math.sin(endaz) + sy extLine = geo.Line([(sx, sy), (ex, ey)]) quiet = extend.write(extLine, (ln.cat, dend, sx, sy, extLen, endaz, 0, 0, 0, 0, 'null', extLen)) grass.info( "{} dangle nodes found, committing table extend".format(dangleCnt)) extend.table.conn.commit() extend.close(build=True, release=True) inMap.close() # # Create two tables where extensions intersect; # 1. intersect with original lines # 2. intersect with self - to extract intersects between extensions # # First the intersects with original lines grass.info( "Searching for intersects between potential extensions and original lines" ) table_isectIn = Table('isectIn', connection=sqlite3.connect(get_path(path))) if table_isectIn.exist(): table_isectIn.drop(force=True) run_command("v.distance", flags='a', overwrite=True, quiet=True, from_="extend", from_type="line", to=map, to_type="line", dmax="0", upload="cat,dist,to_x,to_y", column="near_cat,dist,nx,ny", table="isectIn") # Will have touched the dangle it comes from, so remove those touches run_command( "db.execute", sql= "DELETE FROM isectIn WHERE rowid IN (SELECT isectIn.rowid FROM isectIn INNER JOIN extend ON from_cat=cat WHERE near_cat=parent)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") run_command("db.execute", sql="ALTER TABLE isectIn ADD ntype VARCHAR", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") run_command("db.execute", sql="UPDATE isectIn SET ntype = 'orig' ", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") # # Now second self intersect table # grass.info("Searching for intersects of potential extensions") table_isectX = Table('isectX', connection=sqlite3.connect(get_path(path))) if table_isectX.exist(): table_isectX.drop(force=True) run_command("v.distance", flags='a', overwrite=True, quiet=True, from_="extend", from_type="line", to="extend", to_type="line", dmax="0", upload="cat,dist,to_x,to_y", column="near_cat,dist,nx,ny", table="isectX") # Obviously all extensions will intersect with themself, so remove those "intersects" run_command("db.execute", sql="DELETE FROM isectX WHERE from_cat = near_cat", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") run_command("db.execute", sql="ALTER TABLE isectX ADD ntype VARCHAR", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") run_command("db.execute", sql="UPDATE isectX SET ntype = 'ext' ", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") # # Combine the two tables and add a few more attributes # run_command("db.execute", sql="INSERT INTO isectIn SELECT * FROM isectX", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") cols_isectIn = Columns('isectIn', connection=sqlite3.connect(get_path(path))) cols_isectIn.add(['from_x'], ['DOUBLE PRECISION']) cols_isectIn.add(['from_y'], ['DOUBLE PRECISION']) cols_isectIn.add(['ext_len'], ['DOUBLE PRECISION']) # Get starting coordinate at the end of the dangle run_command( "db.execute", sql= "UPDATE isectIn SET from_x = (SELECT extend.orgx FROM extend WHERE from_cat=extend.cat)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") run_command( "db.execute", sql= "UPDATE isectIn SET from_y = (SELECT extend.orgy FROM extend WHERE from_cat=extend.cat)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") table_isectIn.conn.commit() # For each intersect point, calculate the distance along extension line from end of dangle # Would be nicer to do this in the database but SQLite dosen't support sqrt or exponents grass.info( "Calculating distances of intersects along potential extensions") cur = table_isectIn.execute( sql_code="SELECT rowid, from_x, from_y, nx, ny FROM isectIn") for row in cur.fetchall(): rowid, fx, fy, nx, ny = row x_len = math.sqrt((fx - nx)**2 + (fy - ny)**2) sqlStr = "UPDATE isectIn SET ext_len={:.8f} WHERE rowid={:d}".format( x_len, rowid) table_isectIn.execute(sql_code=sqlStr) grass.verbose("Ready to commit isectIn changes") table_isectIn.conn.commit() # Remove any zero distance from end of their dangle. # This happens when another extension intersects exactly at that point run_command("db.execute", sql="DELETE FROM isectIn WHERE ext_len = 0.0", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") table_isectIn.conn.commit() # Go through the extensions and find the intersect closest to each origin. grass.info("Searching for closest intersect for each potential extension") # db.execute sql="ALTER TABLE extend_t1 ADD COLUMN bst INTEGER" # db.execute sql="ALTER TABLE extend_t1 ADD COLUMN nrx DOUBLE PRECISION" # db.execute sql="ALTER TABLE extend_t1 ADD COLUMN nry DOUBLE PRECISION" # db.execute sql="ALTER TABLE extend_t1 ADD COLUMN ocat TEXT" # run_command("db.execute", # sql = "INSERT OR REPLACE INTO extend_t1 (bst, nrx, nry, ocat) VALUES ((SELECT isectIn.rowid, ext_len, nx, ny, near_cat, ntype FROM isectIn WHERE from_cat=extend_t1.cat ORDER BY ext_len ASC LIMIT 1))", # driver = "sqlite", # database = "$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("CREATE index") run_command("db.execute", sql="CREATE INDEX idx_from_cat ON isectIn (from_cat)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("UPDATE best_xid") run_command( "db.execute", sql= "UPDATE extend SET best_xid = (SELECT isectIn.rowid FROM isectIn WHERE from_cat=extend.cat ORDER BY ext_len ASC LIMIT 1)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("UPDATE x_len") run_command( "db.execute", sql= "UPDATE extend SET x_len = (SELECT ext_len FROM isectIn WHERE from_cat=extend.cat ORDER BY ext_len ASC LIMIT 1)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("UPDATE near_x") run_command( "db.execute", sql= "UPDATE extend SET near_x = (SELECT nx FROM isectIn WHERE from_cat=extend.cat ORDER BY ext_len ASC LIMIT 1)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("UPDATE near_y") run_command( "db.execute", sql= "UPDATE extend SET near_y = (SELECT ny FROM isectIn WHERE from_cat=extend.cat ORDER BY ext_len ASC LIMIT 1)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("UPDATE other_cat") run_command( "db.execute", sql= "UPDATE extend SET other_cat = (SELECT near_cat FROM isectIn WHERE from_cat=extend.cat ORDER BY ext_len ASC LIMIT 1)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("UPDATE xtype") run_command( "db.execute", sql= "UPDATE extend SET xtype = (SELECT ntype FROM isectIn WHERE from_cat=extend.cat ORDER BY ext_len ASC LIMIT 1)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("DROP index") run_command("db.execute", sql="DROP INDEX idx_from_cat", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") grass.verbose("CREATE index on near_cat") run_command("db.execute", sql="CREATE INDEX idx_near_cat ON isectIn (near_cat)", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") quiet = table_isectIn.filters.select('rowid', 'ext_len', 'nx', 'ny', 'near_cat', 'ntype') # quiet=table_isectIn.filters.order_by(['ext_len ASC']) quiet = table_isectIn.filters.order_by('ext_len ASC') quiet = table_isectIn.filters.limit(1) table_extend = Table('extend', connection=sqlite3.connect(get_path(path))) # Code below was relplaced by commands above untill memory problem can be sorted # table_extend.filters.select('cat') # cur=table_extend.execute() # updateCnt = 0 # for row in cur.fetchall(): # cat, = row # quiet=table_isectIn.filters.where('from_cat={:d}'.format(cat)) ##SELECT rowid, ext_len, nx, ny, near_cat, ntype FROM isectIn WHERE from_cat=32734 ORDER BY ext_len ASC LIMIT 1 # x_sect=table_isectIn.execute().fetchone() # if x_sect is not None: # x_rowid, ext_len, nx, ny, other_cat, ntype = x_sect # sqlStr="UPDATE extend SET best_xid={:d}, x_len={:.8f}, near_x={:.8f}, near_y={:.8f}, other_cat={:d}, xtype='{}' WHERE cat={:d}".format(x_rowid, ext_len, nx, ny, other_cat, ntype, cat) # table_extend.execute(sql_code=sqlStr) ## Try periodic commit to avoide crash! # updateCnt = (updateCnt + 1) % 10000 # if updateCnt == 0: # table_extend.conn.commit() grass.verbose("Ready to commit extend changes") table_extend.conn.commit() # # There may be extensions that crossed, and that intersection chosen by one but # not "recripricated" by the other. # Need to remove those possibilities and allow the jilted extension to re-search. # grass.verbose("Deleting intersects already resolved") run_command( "db.execute", sql= "DELETE FROM isectIn WHERE rowid IN (SELECT isectIn.rowid FROM isectIn JOIN extend ON near_cat=cat WHERE ntype='ext' AND xtype!='null')", #"AND from_cat!=other_cat" no second chance! driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db") table_isectIn.conn.commit() grass.verbose("Deleting complete") # To find the jilted - need a copy of extensions that have found an # intersection (won't overwrite so drop first) grass.verbose( "Re-searching for mis-matched intersects between potential extensions") table_imatch = Table('imatch', connection=sqlite3.connect(get_path(path))) if table_imatch.exist(): table_imatch.drop(force=True) wvar = "xtype!='null'" run_command( "db.copy", overwrite=True, quiet=True, from_driver="sqlite", from_database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db", from_table="extend", to_driver="sqlite", to_database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db", to_table="imatch", where=wvar) # Memory problems? if gc.isenabled(): grass.verbose("Garbage collection enabled - forcing gc cycle") gc.collect() else: grass.verbose("Garbage collection not enabled") # Ensure tables are commited table_extend.conn.commit() table_imatch.conn.commit() table_isectIn.conn.commit() # Identify the jilted sqlStr = "SELECT extend.cat FROM extend JOIN imatch ON extend.other_cat=imatch.cat WHERE extend.xtype='ext' and extend.cat!=imatch.other_cat" cur = table_extend.execute(sql_code=sqlStr) updateCnt = 0 for row in cur.fetchall(): cat, = row grass.verbose("Reworking extend.cat={}".format(cat)) quiet = table_isectIn.filters.where('from_cat={:d}'.format(cat)) #print("SQL: {}".format(table_isectIn.filters.get_sql())) x_sect = table_isectIn.execute().fetchone( ) ## Problem here under modules if x_sect is None: sqlStr = "UPDATE extend SET best_xid=0, x_len=search_len, near_x=0, near_y=0, other_cat=0, xtype='null' WHERE cat={:d}".format( cat) else: x_rowid, ext_len, nx, ny, other_cat, ntype = x_sect sqlStr = "UPDATE extend SET best_xid={:d}, x_len={:.8f}, near_x={:.8f}, near_y={:.8f}, other_cat={:d}, xtype='{}' WHERE cat={:d}".format( x_rowid, ext_len, nx, ny, other_cat, ntype, cat) table_extend.execute(sql_code=sqlStr) ## Try periodic commit to avoide crash! updateCnt = (updateCnt + 1) % 100 if (updateCnt == 0): # or (cat == 750483): grass.verbose( "XXXXXXXXXXX Committing table_extend XXXXXXXXXXXXXXXXXXXXXX") table_extend.conn.commit() grass.verbose("Committing adjustments to table extend") table_extend.conn.commit() # # For debugging, create a map with the chosen intersect points # if debug: wvar = "xtype!='null' AND x_len!=0" # print(wvar) run_command( "v.in.db", overwrite=True, quiet=True, table="extend", driver="sqlite", database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db", x="near_x", y="near_y", key="cat", where=wvar, output="chosen") # # Finally adjust the dangle lines in input map - use a copy (map_out) if requested # if map_out: run_command("g.copy", overwrite=allowOverwrite, quiet=True, vector=map + "," + map_out) else: # Otherwise just modify the original dataset (map) if allowOverwrite: grass.warning("Modifying vector map ({})".format(map)) map_out = map else: grass.error( "Use switch --o to modifying input vector map ({})".format( map)) return 1 # # Get info for lines that need extending table_extend.filters.select( 'parent, dend, near_x, near_y, search_az, xtype') table_extend.filters.where("xtype!='null'") extLines = table_extend.execute().fetchall() cat_mods = [ext[0] for ext in extLines] tickLen = len(cat_mods) grass.info("Extending {} dangles".format(tickLen)) ticker = 0 grass.message("Percent complete...") # Open up the map_out copy (or the original) and work through looking for lines that need modifying inMap = VectorTopo(map_out) inMap.open('rw', tab_name=map_out) for ln_idx in range(len(inMap)): ln = inMap.read(ln_idx + 1) if ln.gtype == 2: # Only process lines while ln.cat in cat_mods: # Note: could be 'head' and 'tail' ticker = (ticker + 1) grass.percent(ticker, tickLen, 5) cat_idx = cat_mods.index(ln.cat) cat, dend, nx, ny, endaz, xtype = extLines.pop(cat_idx) dump = cat_mods.pop(cat_idx) if xtype == 'orig': # Overshoot by 0.1 as break lines is unreliable nx = nx + 0.1 * math.cos(endaz) ny = ny + 0.1 * math.sin(endaz) newEnd = geo.Point(x=nx, y=ny, z=None) if dend == 'head': ln.insert(0, newEnd) else: # 'tail' ln.append(newEnd) quiet = inMap.rewrite(ln_idx + 1, ln) else: quite = inMap.delete(ln_idx + 1) ## Try periodic commit and garbage collection to avoide crash! if (ln_idx % 1000) == 0: # inMap.table.conn.commit() - no such thing - Why?? if gc.isenabled(): quiet = gc.collect() inMap.close(build=True, release=True) grass.message("v.extendlines completing") # # Clean up temporary tables and maps # if not debug: table_isectIn.drop(force=True) table_isectX.drop(force=True) table_imatch.drop(force=True) extend.remove() chosen = VectorTopo('chosen') if chosen.exist(): chosen.remove() return 0
def main(): inputraster = options['input'] number_lines = int(options['number_lines']) edge_detection_algorithm = options['edge_detection'] no_edge_friction = int(options['no_edge_friction']) lane_border_multiplier = int(options['lane_border_multiplier']) min_tile_size = None if options['min_tile_size']: min_tile_size = float(options['min_tile_size']) existing_cutlines = None if options['existing_cutlines']: existing_cutlines = options['existing_cutlines'].split(',') tiles = options['output'] memory = int(options['memory']) tiled = False if options['tile_width']: tiled = True gscript.message(_("Using tiles processing for edge detection")) width = int(options['tile_width']) height = int(options['tile_height']) overlap = int(options['overlap']) processes = int(options['processes']) global temp_maps temp_maps = [] r = 'raster' v = 'vector' if existing_cutlines: existingcutlinesmap = 'temp_icutlines_existingcutlinesmap_%i' % os.getpid( ) if len(existing_cutlines) > 1: gscript.run_command('v.patch', input_=existing_cutlines, output=existingcutlinesmap, quiet=True, overwrite=True) existing_cutlines = existingcutlinesmap gscript.run_command('v.to.rast', input_=existing_cutlines, output=existingcutlinesmap, use='val', type_='line,boundary', overwrite=True, quiet=True) temp_maps.append([existingcutlinesmap, r]) temp_edge_map = "temp_icutlines_edgemap_%d" % os.getpid() temp_maps.append([temp_edge_map, r]) gscript.message( _("Creating edge map using <%s> edgedetection algorithm") % edge_detection_algorithm) if edge_detection_algorithm == 'zc': kwargs = { 'input': inputraster, 'output': temp_edge_map, 'width_': int(options['zc_width']), 'threshold': float(options['zc_threshold']), 'quiet': True } if tiled: grd = GridModule('i.zc', width=width, height=height, overlap=overlap, processes=processes, split=False, **kwargs) grd.run() else: gscript.run_command('i.zc', **kwargs) elif edge_detection_algorithm == 'canny': if not gscript.find_program('i.edge', '--help'): message = _("You need to install the addon i.edge to use ") message += _("the Canny edge detector.\n") message += _( " You can install the addon with 'g.extension i.edge'") gscript.fatal(message) kwargs = { 'input': inputraster, 'output': temp_edge_map, 'low_threshold': float(options['canny_low_threshold']), 'high_threshold': float(options['canny_high_threshold']), 'sigma': float(options['canny_sigma']), 'quiet': True } if tiled: grd = GridModule('i.edge', width=width, height=height, overlap=overlap, processes=processes, split=False, flags='n', **kwargs) grd.run() else: gscript.run_command('i.edge', flags='n', **kwargs) else: gscript.fatal( "Only zero-crossing and Canny available as edge detection algorithms." ) region = gscript.region() gscript.message(_("Finding cutlines in both directions")) nsrange = float(region.n - region.s - region.nsres) ewrange = float(region.e - region.w - region.ewres) if nsrange > ewrange: hnumber_lines = number_lines vnumber_lines = max(int(number_lines * (ewrange / nsrange)), 1) else: vnumber_lines = number_lines hnumber_lines = max(int(number_lines * (nsrange / ewrange)), 1) # Create the lines in horizonal direction nsstep = float(region.n - region.s - region.nsres) / hnumber_lines hpointsy = [((region.n - i * nsstep) - region.nsres / 2.0) for i in range(0, hnumber_lines + 1)] hlanepointsy = [y - nsstep / 2.0 for y in hpointsy] hstartpoints = listzip([region.w + 0.2 * region.ewres] * len(hpointsy), hpointsy) hstoppoints = listzip([region.e - 0.2 * region.ewres] * len(hpointsy), hpointsy) hlanestartpoints = listzip([region.w + 0.2 * region.ewres] * len(hlanepointsy), hlanepointsy) hlanestoppoints = listzip([region.e - 0.2 * region.ewres] * len(hlanepointsy), hlanepointsy) hlanemap = 'temp_icutlines_hlanemap_%i' % os.getpid() temp_maps.append([hlanemap, v]) temp_maps.append([hlanemap, r]) os.environ['GRASS_VERBOSE'] = '0' new = VectorTopo(hlanemap) new.open('w') for line in listzip(hlanestartpoints, hlanestoppoints): new.write(geom.Line(line), cat=1) new.close() del os.environ['GRASS_VERBOSE'] gscript.run_command('v.to.rast', input_=hlanemap, output=hlanemap, use='val', type_='line', overwrite=True, quiet=True) hbasemap = 'temp_icutlines_hbasemap_%i' % os.getpid() temp_maps.append([hbasemap, r]) # Building the cost maps using the following logic # - Any pixel not on an edge, nor on an existing cutline gets a # no_edge_friction cost, or no_edge_friction_cost x 10 if there are # existing cutlines # - Any pixel on an edge gets a cost of 1 if there are no existing cutlines, # and a cost of no_edge_friction if there are # - A lane line gets a very high cost (lane_border_multiplier x cost of no # edge pixel - the latter depending on the existence of cutlines). mapcalc_expression = "%s = " % hbasemap mapcalc_expression += "if(isnull(%s), " % hlanemap if existing_cutlines: mapcalc_expression += "if(%s == 0 && isnull(%s), " % ( temp_edge_map, existingcutlinesmap) mapcalc_expression += "%i, " % (no_edge_friction * 10) mapcalc_expression += "if(isnull(%s), %s, 1))," % (existingcutlinesmap, no_edge_friction) mapcalc_expression += "%i)" % (lane_border_multiplier * no_edge_friction * 10) else: mapcalc_expression += "if(%s == 0, " % temp_edge_map mapcalc_expression += "%i, " % no_edge_friction mapcalc_expression += "1), " mapcalc_expression += "%i)" % (lane_border_multiplier * no_edge_friction) gscript.run_command('r.mapcalc', expression=mapcalc_expression, quiet=True, overwrite=True) hcumcost = 'temp_icutlines_hcumcost_%i' % os.getpid() temp_maps.append([hcumcost, r]) hdir = 'temp_icutlines_hdir_%i' % os.getpid() temp_maps.append([hdir, r]) # Create the lines in vertical direction ewstep = float(region.e - region.w - region.ewres) / vnumber_lines vpointsx = [((region.e - i * ewstep) - region.ewres / 2.0) for i in range(0, vnumber_lines + 1)] vlanepointsx = [x + ewstep / 2.0 for x in vpointsx] vstartpoints = listzip(vpointsx, [region.n - 0.2 * region.nsres] * len(vpointsx)) vstoppoints = listzip(vpointsx, [region.s + 0.2 * region.nsres] * len(vpointsx)) vlanestartpoints = listzip(vlanepointsx, [region.n - 0.2 * region.nsres] * len(vlanepointsx)) vlanestoppoints = listzip(vlanepointsx, [region.s + 0.2 * region.nsres] * len(vlanepointsx)) vlanemap = 'temp_icutlines_vlanemap_%i' % os.getpid() temp_maps.append([vlanemap, v]) temp_maps.append([vlanemap, r]) os.environ['GRASS_VERBOSE'] = '0' new = VectorTopo(vlanemap) new.open('w') for line in listzip(vlanestartpoints, vlanestoppoints): new.write(geom.Line(line), cat=1) new.close() del os.environ['GRASS_VERBOSE'] gscript.run_command('v.to.rast', input_=vlanemap, output=vlanemap, use='val', type_='line', overwrite=True, quiet=True) vbasemap = 'temp_icutlines_vbasemap_%i' % os.getpid() temp_maps.append([vbasemap, r]) mapcalc_expression = "%s = " % vbasemap mapcalc_expression += "if(isnull(%s), " % vlanemap if existing_cutlines: mapcalc_expression += "if(%s == 0 && isnull(%s), " % ( temp_edge_map, existingcutlinesmap) mapcalc_expression += "%i, " % (no_edge_friction * 10) mapcalc_expression += "if(isnull(%s), %s, 1))," % (existingcutlinesmap, no_edge_friction) mapcalc_expression += "%i)" % (lane_border_multiplier * no_edge_friction * 10) else: mapcalc_expression += "if(%s == 0, " % temp_edge_map mapcalc_expression += "%i, " % no_edge_friction mapcalc_expression += "1), " mapcalc_expression += "%i)" % (lane_border_multiplier * no_edge_friction) gscript.run_command('r.mapcalc', expression=mapcalc_expression, quiet=True, overwrite=True) vcumcost = 'temp_icutlines_vcumcost_%i' % os.getpid() temp_maps.append([vcumcost, r]) vdir = 'temp_icutlines_vdir_%i' % os.getpid() temp_maps.append([vdir, r]) if processes > 1: pmemory = memory / 2.0 rcv = gscript.start_command('r.cost', input_=vbasemap, startcoordinates=vstartpoints, stopcoordinates=vstoppoints, output=vcumcost, outdir=vdir, memory=pmemory, quiet=True, overwrite=True) rch = gscript.start_command('r.cost', input_=hbasemap, startcoordinates=hstartpoints, stopcoordinates=hstoppoints, output=hcumcost, outdir=hdir, memory=pmemory, quiet=True, overwrite=True) rcv.wait() rch.wait() else: gscript.run_command('r.cost', input_=vbasemap, startcoordinates=vstartpoints, stopcoordinates=vstoppoints, output=vcumcost, outdir=vdir, memory=memory, quiet=True, overwrite=True) gscript.run_command('r.cost', input_=hbasemap, startcoordinates=hstartpoints, stopcoordinates=hstoppoints, output=hcumcost, outdir=hdir, memory=memory, quiet=True, overwrite=True) hlines = 'temp_icutlines_hlines_%i' % os.getpid() temp_maps.append([hlines, r]) vlines = 'temp_icutlines_vlines_%i' % os.getpid() temp_maps.append([vlines, r]) if processes > 1: rdh = gscript.start_command('r.drain', input_=hcumcost, direction=hdir, startcoordinates=hstoppoints, output=hlines, flags='d', quiet=True, overwrite=True) rdv = gscript.start_command('r.drain', input_=vcumcost, direction=vdir, startcoordinates=vstoppoints, output=vlines, flags='d', quiet=True, overwrite=True) rdh.wait() rdv.wait() else: gscript.run_command('r.drain', input_=hcumcost, direction=hdir, startcoordinates=hstoppoints, output=hlines, flags='d', quiet=True, overwrite=True) gscript.run_command('r.drain', input_=vcumcost, direction=vdir, startcoordinates=vstoppoints, output=vlines, flags='d', quiet=True, overwrite=True) # Combine horizonal and vertical lines temp_raster_tile_borders = 'temp_icutlines_raster_tile_borders_%i' % os.getpid( ) temp_maps.append([temp_raster_tile_borders, r]) gscript.run_command('r.patch', input_=[hlines, vlines], output=temp_raster_tile_borders, quiet=True, overwrite=True) gscript.message(_("Creating vector polygons")) # Create vector polygons # First we need to shrink the region a bit to make sure that all vector # points / lines fall within the raster gscript.use_temp_region() gscript.run_command('g.region', s=region.s + region.nsres, e=region.e - region.ewres, quiet=True) region_map = 'temp_icutlines_region_map_%i' % os.getpid() temp_maps.append([region_map, v]) temp_maps.append([region_map, r]) gscript.run_command('v.in.region', output=region_map, type_='line', quiet=True, overwrite=True) gscript.del_temp_region() gscript.run_command('v.to.rast', input_=region_map, output=region_map, use='val', type_='line', quiet=True, overwrite=True) temp_raster_polygons = 'temp_icutlines_raster_polygons_%i' % os.getpid() temp_maps.append([temp_raster_polygons, r]) gscript.run_command('r.patch', input_=[temp_raster_tile_borders, region_map], output=temp_raster_polygons, quiet=True, overwrite=True) temp_raster_polygons_thin = 'temp_icutlines_raster_polygons_thin_%i' % os.getpid( ) temp_maps.append([temp_raster_polygons_thin, r]) gscript.run_command('r.thin', input_=temp_raster_polygons, output=temp_raster_polygons_thin, quiet=True, overwrite=True) # Create a series of temporary map names as we have to go # through several steps until we reach the final map. temp_vector_polygons1 = 'temp_icutlines_vector_polygons1_%i' % os.getpid() temp_maps.append([temp_vector_polygons1, v]) temp_vector_polygons2 = 'temp_icutlines_vector_polygons2_%i' % os.getpid() temp_maps.append([temp_vector_polygons2, v]) temp_vector_polygons3 = 'temp_icutlines_vector_polygons3_%i' % os.getpid() temp_maps.append([temp_vector_polygons3, v]) temp_vector_polygons4 = 'temp_icutlines_vector_polygons4_%i' % os.getpid() temp_maps.append([temp_vector_polygons4, v]) gscript.run_command('r.to.vect', input_=temp_raster_polygons_thin, output=temp_vector_polygons1, type_='line', flags='t', quiet=True, overwrite=True) # Erase all category values from the lines gscript.run_command('v.category', input_=temp_vector_polygons1, op='del', cat='-1', output=temp_vector_polygons2, quiet=True, overwrite=True) # Transform lines to boundaries gscript.run_command('v.type', input_=temp_vector_polygons2, from_type='line', to_type='boundary', output=temp_vector_polygons3, quiet=True, overwrite=True) # Add centroids gscript.run_command('v.centroids', input_=temp_vector_polygons3, output=temp_vector_polygons4, quiet=True, overwrite=True) # If a threshold is given erase polygons that are too small if min_tile_size: gscript.run_command('v.clean', input_=temp_vector_polygons4, tool=['rmdangle', 'rmarea'], threshold=[-1, min_tile_size], output=tiles, quiet=True, overwrite=True) else: gscript.run_command('g.copy', vect=[temp_vector_polygons4, tiles], quiet=True, overwrite=True) gscript.vector_history(tiles)
def main(): input = options["input"] if options["refline"]: refline_cat = int(options["refline"]) else: refline_cat = None nb_vertices = int(options["vertices"]) if options["range"]: search_range = float(options["range"]) else: search_range = None output = options["output"] transversals = flags["t"] median = flags["m"] global tmp_points_map global tmp_centerpoints_map global tmp_line_map global tmp_cleaned_map global tmp_map tmp_points_map = "points_map_tmp_%d" % os.getpid() tmp_centerpoints_map = "centerpoints_map_tmp_%d" % os.getpid() tmp_line_map = "line_map_tmp_%d" % os.getpid() tmp_cleaned_map = "cleaned_map_tmp_%d" % os.getpid() tmp_map = "generaluse_map_tmp_%d" % os.getpid() nb_lines = grass.vector_info_topo(input)["lines"] # Find best reference line and max distance between centerpoints of lines segment_input = "" categories = grass.read_command("v.category", input=input, option="print", quiet=True).splitlines() for category in categories: segment_input += "P {}".format(category.strip()) segment_input += " {} {}".format(category.strip(), " 50%") segment_input += os.linesep grass.write_command( "v.segment", input=input, output=tmp_centerpoints_map, rules="-", stdin=segment_input, quiet=True, ) center_distances = grass.read_command( "v.distance", from_=tmp_centerpoints_map, to=tmp_centerpoints_map, upload="dist", flags="pa", quiet=True, ).splitlines() cats = [] mean_dists = [] count = 0 distmax = 0 for center in center_distances: if count < 2: count += 1 continue cat = center.strip().split("|")[0] distsum = 0 for x in center.strip().split("|")[1:]: distsum += float(x) mean_dist = distsum / len(center.strip().split("|")[1:]) cats.append(cat) mean_dists.append(mean_dist) if transversals and not search_range: search_range = sum(mean_dists) / len(mean_dists) grass.message(_("Calculated search range: %.5f." % search_range)) if not refline_cat: refline_cat = sorted(zip(cats, mean_dists), key=lambda tup: tup[1])[0][0] grass.message( _("Category number of chosen reference line: %s." % refline_cat)) # Use transversals algorithm if transversals: # Break any intersections in the original lines so that # they do not interfere further on grass.run_command("v.clean", input=input, output=tmp_cleaned_map, tool="break", quiet=True) xmean = [] ymean = [] xmedian = [] ymedian = [] step = 100.0 / nb_vertices os.environ["GRASS_VERBOSE"] = "-1" for vertice in range(0, nb_vertices + 1): # v.segment sometimes cannot find points when # using 0% or 100% offset length_offset = step * vertice if length_offset < 0.00001: length_offset = 0.00001 if length_offset > 99.99999: length_offset = 99.9999 # Create endpoints of transversal segment_input = "P 1 %s %.5f%% %f\n" % ( refline_cat, length_offset, search_range, ) segment_input += "P 2 %s %.5f%% %f\n" % ( refline_cat, length_offset, -search_range, ) grass.write_command( "v.segment", input=input, output=tmp_points_map, stdin=segment_input, overwrite=True, ) # Create transversal grass.write_command( "v.net", points=tmp_points_map, output=tmp_line_map, operation="arcs", file="-", stdin="99999 1 2", overwrite=True, ) # Patch transversal onto cleaned input lines maps = tmp_cleaned_map + "," + tmp_line_map grass.run_command("v.patch", input=maps, out=tmp_map, overwrite=True) # Find intersections grass.run_command( "v.clean", input=tmp_map, out=tmp_line_map, tool="break", error=tmp_points_map, overwrite=True, ) # Add categories to intersection points grass.run_command( "v.category", input=tmp_points_map, out=tmp_map, op="add", overwrite=True, ) # Get coordinates of points coords = grass.read_command("v.to.db", map=tmp_map, op="coor", flags="p").splitlines() count = 0 x = [] y = [] for coord in coords: x.append(float(coord.strip().split("|")[1])) y.append(float(coord.strip().split("|")[2])) # Calculate mean and median for this transversal if len(x) > 0: xmean.append(sum(x) / len(x)) ymean.append(sum(y) / len(y)) x.sort() y.sort() xmedian.append((x[(len(x) - 1) // 2] + x[(len(x)) // 2]) / 2) ymedian.append((y[(len(y) - 1) // 2] + y[(len(y)) // 2]) / 2) del os.environ["GRASS_VERBOSE"] # Use closest point algorithm else: # Get reference line calculate its length grass.run_command("v.extract", input=input, output=tmp_line_map, cats=refline_cat, quiet=True) os.environ["GRASS_VERBOSE"] = "0" lpipe = grass.read_command("v.to.db", map=tmp_line_map, op="length", flags="p").splitlines() del os.environ["GRASS_VERBOSE"] for l in lpipe: linelength = float(l.strip().split("|")[1]) step = linelength / nb_vertices # Create reference points for vertice calculation grass.run_command( "v.to.points", input=tmp_line_map, output=tmp_points_map, dmax=step, quiet=True, ) nb_points = grass.vector_info_topo(tmp_points_map)["points"] cat = [] x = [] y = [] # Get coordinates of closest points on all input lines if search_range: points = grass.read_command( "v.distance", from_=tmp_points_map, from_layer=2, to=input, upload="to_x,to_y", dmax=search_range, flags="pa", quiet=True, ).splitlines() else: points = grass.read_command( "v.distance", from_=tmp_points_map, from_layer=2, to=input, upload="to_x,to_y", flags="pa", quiet=True, ).splitlines() firstline = True for point in points: if firstline: firstline = False continue cat.append((int(point.strip().split("|")[0]))) x.append(float(point.strip().split("|")[2])) y.append(float(point.strip().split("|")[3])) # Calculate mean coordinates xsum = [0] * nb_points ysum = [0] * nb_points linecount = [0] * nb_points for i in range(len(cat)): index = cat[i] - 1 linecount[index] += 1 xsum[index] = xsum[index] + x[i] ysum[index] = ysum[index] + y[i] xmean = [0] * nb_points ymean = [0] * nb_points for c in range(0, nb_points): xmean[c] = xsum[c] / linecount[c] ymean[c] = ysum[c] / linecount[c] # Calculate the median xmedian = [0] * nb_points ymedian = [0] * nb_points for c in range(0, nb_points): xtemp = [] ytemp = [] for i in range(len(cat)): if cat[i] == c + 1: xtemp.append(x[i]) ytemp.append(y[i]) xtemp.sort() ytemp.sort() xmedian[c] = (xtemp[(len(xtemp) - 1) // 2] + xtemp[(len(xtemp)) // 2]) / 2 ymedian[c] = (ytemp[(len(ytemp) - 1) // 2] + ytemp[(len(ytemp)) // 2]) / 2 # Create new line and write to file if median and nb_lines > 2: line = geo.Line(list(zip(xmedian, ymedian))) else: if median and nb_lines <= 2: grass.message( _("More than 2 lines necesary for median, using mean.")) line = geo.Line(list(zip(xmean, ymean))) new = VectorTopo(output) new.open("w") new.write(line) new.close()
def main(): input = options['input'] if options['refline']: refline_cat = int(options['refline']) else: refline_cat = None nb_vertices = int(options['vertices']) if options['range']: search_range = float(options['range']) else: search_range = None output = options['output'] transversals = flags['t'] median = flags['m'] global tmp_points_map global tmp_centerpoints_map global tmp_line_map global tmp_cleaned_map global tmp_map tmp_points_map = 'points_map_tmp_%d' % os.getpid() tmp_centerpoints_map = 'centerpoints_map_tmp_%d' % os.getpid() tmp_line_map = 'line_map_tmp_%d' % os.getpid() tmp_cleaned_map = 'cleaned_map_tmp_%d' % os.getpid() tmp_map = 'generaluse_map_tmp_%d' % os.getpid() nb_lines = grass.vector_info_topo(input)['lines'] # Find best reference line and max distance between centerpoints of lines segment_input = '' categories = grass.pipe_command('v.category', input=input, option='print', quiet=True) for category in categories.stdout: segment_input += 'P ' + category.strip() segment_input += ' ' + category.strip() + ' 50%\n' grass.write_command('v.segment', input=input, output=tmp_centerpoints_map, rules='-', stdin=segment_input, quiet=True) center_distances = grass.pipe_command('v.distance', from_=tmp_centerpoints_map, to=tmp_centerpoints_map, upload='dist', flags='pa', quiet=True) cats = [] mean_dists = [] count = 0 distmax = 0 for center in center_distances.stdout: if count < 2: count += 1 continue cat = center.strip().split('|')[0] distsum = 0 for x in center.strip().split('|')[1:]: distsum += float(x) mean_dist = distsum / len(center.strip().split('|')[1:]) cats.append(cat) mean_dists.append(mean_dist) if transversals and not search_range: search_range = sum(mean_dists) / len(mean_dists) grass.message(_("Calculated search range: %.5f." % search_range)) if not refline_cat: refline_cat = sorted(zip(cats, mean_dists), key=lambda tup: tup[1])[0][0] grass.message( _("Category number of chosen reference line: %s." % refline_cat)) # Use transversals algorithm if transversals: # Break any intersections in the original lines so that # they do not interfere further on grass.run_command('v.clean', input=input, output=tmp_cleaned_map, tool='break', quiet=True) xmean = [] ymean = [] xmedian = [] ymedian = [] step = 100.0 / nb_vertices os.environ['GRASS_VERBOSE'] = '-1' for vertice in range(0, nb_vertices + 1): # v.segment sometimes cannot find points when # using 0% or 100% offset length_offset = step * vertice if length_offset < 0.00001: length_offset = 0.00001 if length_offset > 99.99999: length_offset = 99.9999 # Create endpoints of transversal segment_input = 'P 1 %s %.5f%% %f\n' % (refline_cat, length_offset, search_range) segment_input += 'P 2 %s %.5f%% %f\n' % ( refline_cat, length_offset, -search_range) grass.write_command('v.segment', input=input, output=tmp_points_map, stdin=segment_input, overwrite=True) # Create transversal grass.write_command('v.net', points=tmp_points_map, output=tmp_line_map, operation='arcs', file='-', stdin='99999 1 2', overwrite=True) # Patch transversal onto cleaned input lines maps = tmp_cleaned_map + ',' + tmp_line_map grass.run_command('v.patch', input=maps, out=tmp_map, overwrite=True) # Find intersections grass.run_command('v.clean', input=tmp_map, out=tmp_line_map, tool='break', error=tmp_points_map, overwrite=True) # Add categories to intersection points grass.run_command('v.category', input=tmp_points_map, out=tmp_map, op='add', overwrite=True) # Get coordinates of points coords = grass.pipe_command('v.to.db', map=tmp_map, op='coor', flags='p') count = 0 x = [] y = [] for coord in coords.stdout: x.append(float(coord.strip().split('|')[1])) y.append(float(coord.strip().split('|')[2])) # Calculate mean and median for this transversal if len(x) > 0: xmean.append(sum(x) / len(x)) ymean.append(sum(y) / len(y)) x.sort() y.sort() xmedian.append((x[(len(x) - 1) / 2] + x[(len(x)) / 2]) / 2) ymedian.append((y[(len(y) - 1) / 2] + y[(len(y)) / 2]) / 2) del os.environ['GRASS_VERBOSE'] # Use closest point algorithm else: # Get reference line calculate its length grass.run_command('v.extract', input=input, output=tmp_line_map, cats=refline_cat, quiet=True) os.environ['GRASS_VERBOSE'] = '0' lpipe = grass.pipe_command('v.to.db', map=tmp_line_map, op='length', flags='p') del os.environ['GRASS_VERBOSE'] for l in lpipe.stdout: linelength = float(l.strip().split('|')[1]) step = linelength / nb_vertices # Create reference points for vertice calculation grass.run_command('v.to.points', input=tmp_line_map, output=tmp_points_map, dmax=step, quiet=True) nb_points = grass.vector_info_topo(tmp_points_map)['points'] cat = [] x = [] y = [] # Get coordinates of closest points on all input lines if search_range: points = grass.pipe_command('v.distance', from_=tmp_points_map, from_layer=2, to=input, upload='to_x,to_y', dmax=search_range, flags='pa', quiet=True) else: points = grass.pipe_command('v.distance', from_=tmp_points_map, from_layer=2, to=input, upload='to_x,to_y', flags='pa', quiet=True) firstline = True for point in points.stdout: if firstline: firstline = False continue cat.append((int(point.strip().split('|')[0]))) x.append(float(point.strip().split('|')[2])) y.append(float(point.strip().split('|')[3])) # Calculate mean coordinates xsum = [0] * nb_points ysum = [0] * nb_points linecount = [0] * nb_points for i in range(len(cat)): index = cat[i] - 1 linecount[index] += 1 xsum[index] = xsum[index] + x[i] ysum[index] = ysum[index] + y[i] xmean = [0] * nb_points ymean = [0] * nb_points for c in range(0, nb_points): xmean[c] = xsum[c] / linecount[c] ymean[c] = ysum[c] / linecount[c] # Calculate the median xmedian = [0] * nb_points ymedian = [0] * nb_points for c in range(0, nb_points): xtemp = [] ytemp = [] for i in range(len(cat)): if cat[i] == c + 1: xtemp.append(x[i]) ytemp.append(y[i]) xtemp.sort() ytemp.sort() xmedian[c] = (xtemp[(len(xtemp) - 1) / 2] + xtemp[(len(xtemp)) / 2]) / 2 ymedian[c] = (ytemp[(len(ytemp) - 1) / 2] + ytemp[(len(ytemp)) / 2]) / 2 # Create new line and write to file if median and nb_lines > 2: line = geo.Line(zip(xmedian, ymedian)) else: if median and nb_lines <= 2: grass.message( _("More than 2 lines necesary for median, using mean.")) line = geo.Line(zip(xmean, ymean)) new = VectorTopo(output) new.open('w') new.write(line) new.close()