def main(args): ''' Main function that can be called from either the command line or via qc_wrap.py ''' try: pargs = parser.parse_args(args[1:]) except Exception as error_msg: print(str(error_msg)) return 1 kmname = constants.get_tilename(pargs.las_file) print("Running %s on block: %s, %s" % (PROGNAME, kmname, time.asctime())) if pargs.below_poly: below_poly = True ptype = "below_poly" else: below_poly = False if pargs.type is not None: ptype = pargs.type else: ptype = "undefined" if below_poly: print("Only using points which lie below polygon mean z!") pc = pointcloud.fromAny(pargs.las_file) print("Classes in pointcloud: %s" % pc.get_classes()) try: extent = np.asarray(constants.tilename_to_extent(kmname)) except Exception: print("Could not get extent from tilename.") extent = None polygons = vector_io.get_geometries(pargs.ref_data, pargs.layername, pargs.layersql, extent) feature_count = 0 use_local = pargs.use_local if pargs.schema is not None: report.set_schema(pargs.schema) reporter = report.ReportClassCheck(use_local) for polygon in polygons: if below_poly: if polygon.GetCoordinateDimension() < 3: print( "Error: polygon not 3D - below_poly does not make sense!") continue a_polygon3d = array_geometry.ogrpoly2array(polygon, flatten=False)[0] #warping loop here.... if pargs.toE: geoid = grid.fromGDAL(GEOID_GRID, upcast=True) print("Using geoid from %s to warp to ellipsoidal heights." % GEOID_GRID) toE = geoid.interpolate(a_polygon3d[:, :2].copy()) mask = toE == geoid.nd_val if mask.any(): raise Warning( "Warping to ellipsoidal heights produced no-data values!" ) a_polygon3d[:, 2] += toE mean_z = a_polygon3d[:, 2].mean() if mean_z < SENSIBLE_Z_MIN or mean_z > SENSIBLE_Z_MAX: msg = "Warning: This feature seems to have unrealistic mean z value: {0:.2f} m" print(msg.format(mean_z)) continue del a_polygon3d else: mean_z = -1 polygon.FlattenTo2D() feature_count += 1 separator = "-" * 70 print("%s\nFeature %d\n%s" % (separator, feature_count, separator)) a_polygon = array_geometry.ogrpoly2array(polygon) pc_in_poly = pc.cut_to_polygon(a_polygon) if below_poly: pc_in_poly = pc_in_poly.cut_to_z_interval(-999, mean_z) n_all = pc_in_poly.get_size() freqs = [0] * (len(constants.classes) + 1) #list of frequencies... if n_all > 0: c_all = pc_in_poly.get_classes() if below_poly and DEBUG: print("Mean z of polygon is: %.2f m" % mean_z) print("Mean z of points below is: %.2f m" % pc_in_poly.z.mean()) print("Number of points in polygon: %d" % n_all) print("Classes in polygon: %s" % str(c_all)) # important for reporting that the order here is the same as in the table # definition in report.py!! n_found = 0 for i, cls in enumerate(constants.classes): if cls in c_all: pcc = pc_in_poly.cut_to_class(cls) n_c = pcc.get_size() f_c = n_c / float(n_all) n_found += n_c print("Class %d::" % cls) print(" #Points: %d" % n_c) print(" Fraction: %.3f" % f_c) freqs[i] = f_c f_other = (n_all - n_found) / float(n_all) freqs[-1] = f_other send_args = [kmname] + freqs + [n_all, ptype] reporter.report(*send_args, ogr_geom=polygon)
def main(args): try: pargs = parser.parse_args(args[1:]) except Exception as e: print(str(e)) return 1 kmname = constants.get_tilename(pargs.las_file) print("Running %s on block: %s, %s" % (progname, kmname, time.asctime())) lasname = pargs.las_file polyname = pargs.poly_data use_local = pargs.use_local if pargs.schema is not None: report.set_schema(pargs.schema) reporter = report.ReportBuildingAbsposCheck(use_local) ################################## pc = pointcloud.fromAny(lasname).cut_to_class(cut_to_classes) try: extent = np.asarray(constants.tilename_to_extent(kmname)) except Exception as e: print("Could not get extent from tilename.") extent = None polys = vector_io.get_geometries(polyname, pargs.layername, pargs.layersql, extent) fn = 0 sl = "-" * 65 for poly in polys: n_corners_found = 0 fn += 1 print("%s\nChecking feature %d\n%s\n" % (sl, fn, sl)) a_poly = array_geometry.ogrgeom2array(poly) pcp = pc.cut_to_polygon(a_poly) if pcp.get_size() < 500: print("Few points in polygon...") continue a_poly = a_poly[0] all_post = np.zeros_like(a_poly) #array of vertices found all_pre = np.zeros_like( a_poly) #array of vertices in polygon, correpsonding to found... pcp.triangulate() geom = pcp.get_triangle_geometry() m = geom[:, 1].mean() sd = geom[:, 1].std() if (m > 1.5 or 0.5 * sd > m): print("Feature %d, bad geometry...." % fn) print("{} {}".format(m, sd)) continue #geom is ok - we proceed with a buffer around da house poly_buf = poly.Buffer(2.0) a_poly2 = array_geometry.ogrgeom2array(poly_buf) pcp = pc.cut_to_polygon(a_poly2) print("Points in buffer: %d" % pcp.get_size()) pcp.triangulate() geom = pcp.get_triangle_geometry() tanv2 = tan(radians(cut_angle))**2 #set a mask to mark the triangles we want to consider as marking the house boundary mask = np.logical_and(geom[:, 0] > tanv2, geom[:, 2] > z_limit) #we consider the 'high' lying vertices - could also just select the highest of the three vertices... p_mask = (pcp.z > pcp.z.min() + 2) #and only consider those points which lie close to the outer bd of the house... p_mask &= array_geometry.points_in_buffer( pcp.xy, a_poly, 1.2) #a larger shift than 1.2 ?? #this just selects vertices where p_mask is true, from triangles where mask is true - nothing else... bd_mask = pcp.get_boundary_vertices(mask, p_mask) bd_pts = pcp.xy[bd_mask] #subtract mean to get better numeric stability... xy_t = bd_pts.mean(axis=0) xy = bd_pts - xy_t a_poly -= xy_t if DEBUG: plot_points(a_poly, xy) #now find those corners! lines_ok = dict() found_lines = dict() for vertex in range(a_poly.shape[0] - 1): #check line emanating from vertex... p1 = a_poly[vertex] p2 = a_poly[vertex + 1] ok, pts = check_distribution(p1, p2, xy) lines_ok[vertex] = (ok, pts) #now find corners vertex = 0 #handle the 0'th corner specially... while vertex < a_poly.shape[0] - 2: if lines_ok[vertex][0] and lines_ok[vertex + 1][0]: #proceed print("%s\nCorner %d should be findable..." % ("+" * 50, vertex + 1)) corner_found = find_corner(vertex, lines_ok, found_lines, a_poly) all_pre[n_corners_found] = a_poly[vertex + 1] all_post[n_corners_found] = corner_found #print a_poly[vertex+1],corner_found,vertex n_corners_found += 1 vertex += 1 else: #skip to next findable corner vertex += 2 if lines_ok[0][0] and lines_ok[a_poly.shape[0] - 2][0]: print("Corner 0 should also be findable...") corner_found = find_corner(a_poly.shape[0] - 2, lines_ok, found_lines, a_poly) all_pre[n_corners_found] = a_poly[0] all_post[n_corners_found] = corner_found n_corners_found += 1 print("\n********** In total for feature %d:" % fn) print("Corners found: %d" % n_corners_found) if n_corners_found > 0: all_post = all_post[:n_corners_found] all_pre = all_pre[:n_corners_found] all_dxy = all_post - all_pre mdxy = all_dxy.mean(axis=0) sdxy = np.std(all_dxy, axis=0) ndxy = norm(all_dxy) params = (1, mdxy[0], mdxy[1]) print("Mean dxy: %.3f, %.3f" % (mdxy[0], mdxy[1])) print("Sd : %.3f, %.3f" % (sdxy[0], sdxy[1])) print("Max absolute : %.3f m" % (ndxy.max())) print("Mean absolute: %.3f m" % (ndxy.mean())) if n_corners_found > 1: print("Helmert transformation (pre to post):") params = helmert2d(all_pre, all_post) print("Scale: %.5f ppm" % ((params[0] - 1) * 1e6)) print("dx: %.3f m" % params[1]) print("dy: %.3f m" % params[2]) print("Residuals:") all_post_ = params[0] * all_pre + params[1:] all_dxy = all_post - all_post_ mdxy = all_dxy.mean(axis=0) sdxy = np.std(all_dxy, axis=0) ndxy = norm(all_dxy) print("Mean dxy: %.3f, %.3f" % (mdxy[0], mdxy[1])) print("Sd : %.3f, %.3f" % (sdxy[0], sdxy[1])) print("Max absolute : %.3f m" % (ndxy.max())) print("Mean absolute: %.3f m" % (ndxy.mean())) reporter.report(kmname, params[0], params[1], params[2], n_corners_found, ogr_geom=poly)
def zcheck_base(lasname,vectorname,angle_tolerance,xy_tolerance,z_tolerance,cut_class,reporter,buffer_dist=None,layername=None,layersql=None): is_roads=buffer_dist is not None #'hacky' signal that its roads we're checking print("Starting zcheck_base run at %s" %time.asctime()) tstart=time.clock() kmname=constants.get_tilename(lasname) pc=pointcloud.fromAny(lasname) t2=time.clock() tread=t2-tstart print("Reading data took %.3f ms" %(tread*1e3)) try: extent=np.asarray(constants.tilename_to_extent(kmname)) except Exception as e: print("Could not get extent from tilename.") extent=None geometries=vector_io.get_geometries(vectorname,layername,layersql,extent) pcs=dict() for id in pc.get_pids(): print("%s\n" %("+"*70)) print("Strip id: %d" %id) pc_=pc.cut_to_strip(id).cut_to_class(cut_class) if pc_.get_size()>50: pcs[id]=pc_ pcs[id].triangulate() pcs[id].calculate_validity_mask(angle_tolerance,xy_tolerance,z_tolerance) else: print("Not enough points....") del pc done=[] for id1 in pcs: pc1=pcs[id1] for id2 in pcs: if id1==id2 or (id1,id2) in done or (id2,id1) in done: continue done.append((id1,id2)) ml="-"*70 print("%s\nChecking strip %d against strip %d\n%s" %(ml,id1,id2,ml)) pc2=pcs[id2] if not pc1.might_overlap(pc2): if DEBUG: print("Strip %d and strip %d does not seem to overlap. Continuing..." %(id1,id2)) print("DEBUG: Strip1 bounds:\n%s\nStrip2 bounds:\n%s" %(pc1.get_bounds(),pc2.get_bounds())) continue overlap_box=array_geometry.bbox_intersection(pc1.get_bounds(),pc2.get_bounds()) #shouldn't be None fn=0 for ogr_geom in geometries: fn+=1 try: a_geom=array_geometry.ogrgeom2array(ogr_geom) except Exception as e: print(str(e)) continue if DEBUG: print("----- feature: %d ------" %fn) bbox=array_geometry.get_bounds(a_geom) if not (pc1.might_intersect_box(bbox) and pc2.might_intersect_box(bbox)): if DEBUG: print("Feature not in strip overlap. Continuing...") print("DEBUG: Strip1 bounds:\n%s\nStrip2 bounds:\n%s\nPolygon bounds:\n%s" %(pc1.get_bounds(),pc2.get_bounds(),bbox)) continue #possibly cut the geometry into pieces contained in 'overlap' bbox pieces=[ogr_geom] dim=ogr_geom.GetDimension() assert(dim==1 or dim==2) #only line or polygons if dim==1: cut_geom=array_geometry.cut_geom_to_bbox(ogr_geom,overlap_box) n_geoms=cut_geom.GetGeometryCount() if n_geoms>0: pieces=[cut_geom.GetGeometryRef(ng).Clone() for ng in range(n_geoms)] print("Cut line into %d pieces..." %n_geoms) for geom_piece in pieces: a_geom=array_geometry.ogrgeom2array(geom_piece) if buffer_dist is not None: pc2_in_poly=pc2.cut_to_line_buffer(a_geom,buffer_dist) else: pc2_in_poly=pc2.cut_to_polygon(a_geom) print("(%d,%d,%d):" %(id1,id2,fn)) if pc2_in_poly.get_size()>5: stats12=check_feature(pc1,pc2_in_poly,DEBUG) else: stats12=None print("Not enough points ( %d ) from strip %d in 'feature' (polygon / buffer)." %(pc2_in_poly.get_size(),id2)) if dim==1: pc1_in_poly=pc1.cut_to_line_buffer(a_geom,buffer_dist) else: pc1_in_poly=pc1.cut_to_polygon(a_geom) print("(%d,%d,%d):" %(id2,id1,fn)) if pc1_in_poly.get_size()>5: stats21=check_feature(pc2,pc1_in_poly,DEBUG) else: stats21=None print("Not enough points ( %d ) from strip %d in 'feature' (polygon / buffer)." %(pc1_in_poly.get_size(),id1)) if (stats12 is not None or stats21 is not None): c_prec=0 n_points=0 args12=[None]*4 args21=[None]*4 if stats12 is not None: n_points+=stats12[3] args12=stats12 if stats21 is not None: n_points+=stats21[3] args21=stats21 if stats12 is not None: c_prec+=(stats12[2])*(stats12[3]/float(n_points)) if stats21 is not None: c_prec+=(stats21[2])*(stats21[3]/float(n_points)) #Combined prec. now uses RMS-value.... Its simply a weightning of the two RMS'es... #TODO: consider setting a min bound for the combined number of points.... or a 'confidence' weight... args=[kmname,id1,id2] for i in range(4): args.extend([args12[i],args21[i]]) args.append(c_prec) t1=time.clock() reporter.report(*args,ogr_geom=geom_piece) t2=time.clock() print("Reporting took %.4s ms - concurrency?" %((t2-t1)*1e3)) tend=time.clock() tall=tend-tstart frac_read=tread/tall print("Finished checking tile, time spent: %.3f s, fraction spent on reading las data: %.3f" %(tall,frac_read)) return len(done)
def main(args): pargs = parser.parse_args(args[1:]) lasname = pargs.las_file polyname = pargs.build_polys kmname = constants.get_tilename(lasname) print("Running %s on block: %s, %s" % (os.path.basename(args[0]), kmname, time.asctime())) use_local = pargs.use_local if pargs.schema is not None: report.set_schema(pargs.schema) reporter = report.ReportRoofridgeCheck(use_local) cut_class = pargs.cut_class print("Using class(es): %s" % (cut_class)) # default step values for search... steps1 = 32 steps2 = 14 search_factor = pargs.search_factor if search_factor != 1: # can turn search steps up or down steps1 = int(search_factor * steps1) steps2 = int(search_factor * steps2) print("Incresing search factor by: %.2f" % search_factor) print( "Running time will increase exponentionally with search factor...") pc = pointcloud.fromAny(lasname).cut_to_class(cut_class).cut_to_z_interval( Z_MIN, Z_MAX) try: extent = np.asarray(constants.tilename_to_extent(kmname)) except Exception: print("Could not get extent from tilename.") extent = None polys = vector_io.get_geometries(polyname, pargs.layername, pargs.layersql, extent) fn = 0 sl = "+" * 60 is_sloppy = pargs.sloppy use_all = pargs.use_all for poly in polys: print(sl) fn += 1 print("Checking feature number %d" % fn) a_poly = array_geometry.ogrgeom2array(poly) # secret argument to use all buildings... if (len(a_poly) > 1 or a_poly[0].shape[0] != 5) and (not use_all) and (not is_sloppy): print("Only houses with 4 corners accepted... continuing...") continue pcp = pc.cut_to_polygon(a_poly) # hmmm, these consts should perhaps be made more visible... if (pcp.get_size() < 500 and (not is_sloppy)) or (pcp.get_size() < 10): print("Few points in polygon...") continue # Go to a more numerically stable coord system - from now on only consider outer ring... a_poly = a_poly[0] xy_t = a_poly.mean(axis=0) a_poly -= xy_t pcp.xy -= xy_t pcp.triangulate() geom = pcp.get_triangle_geometry() m = geom[:, 1].mean() sd = geom[:, 1].std() if (m > 1.5 or 0.5 * sd > m) and (not is_sloppy): print("Feature %d, bad geometry...." % fn) print(m, sd) continue planes = cluster(pcp, steps1, steps2) if len(planes) < 2: print("Feature %d, didn't find enough planes..." % fn) pair, equation = find_planar_pairs(planes) if pair is not None: p1 = planes[pair[0]] p2 = planes[pair[1]] z1 = p1[0] * pcp.xy[:, 0] + p1[1] * pcp.xy[:, 1] + p1[2] z2 = p2[0] * pcp.xy[:, 0] + p2[1] * pcp.xy[:, 1] + p2[2] print("%s" % ("*" * 60)) print("Statistics for feature %d" % fn) if DEBUG: plot3d(pcp.xy, pcp.z, z1, z2) intersections, distances, rotations = get_intersections( a_poly, equation) if intersections.shape[0] == 2: line_x = intersections[:, 0] line_y = intersections[:, 1] z_vals = p1[0] * intersections[:, 0] + p1[ 1] * intersections[:, 1] + p1[2] if abs(z_vals[0] - z_vals[1]) > 0.01: print("Numeric instabilty for z-calculation...") z_val = float(np.mean(z_vals)) print("Z for intersection is %.2f m" % z_val) if abs(equation[1]) > 1e-3: a = -equation[0] / equation[1] b = equation[2] / equation[1] line_y = a * line_x + b elif abs(equation[0]) > 1e-3: a = -equation[1] / equation[0] b = equation[2] / equation[0] line_x = a * line_y + b if DEBUG: plot_intersections(a_poly, intersections, line_x, line_y) # transform back to real coords line_x += xy_t[0] line_y += xy_t[1] wkt = "LINESTRING(%.3f %.3f %.3f, %.3f %.3f %.3f)" % ( line_x[0], line_y[0], z_val, line_x[1], line_y[1], z_val) print("WKT: %s" % wkt) reporter.report(kmname, rotations[0], distances[0], distances[1], wkt_geom=wkt) else: print( "Hmmm - something wrong, didn't get exactly two intersections..." )
def main(args): try: pargs = parser.parse_args(args[1:]) except Exception as e: print(str(e)) return 1 kmname = constants.get_tilename(pargs.las_file) print("Running %s on block: %s, %s" % (progname, kmname, time.asctime())) lasname = pargs.las_file pointname = pargs.ref_data use_local = pargs.use_local if pargs.schema is not None: report.set_schema(pargs.schema) reporter = report.ReportZcheckAbs(use_local) try: extent = np.asarray(constants.tilename_to_extent(kmname)) except Exception as e: print("Could not get extent from tilename.") extent = None pc_ref = None #base reference pointcloud pc_refs = [] #list of possibly 'cropped' pointclouds... if pargs.multipoints: ftype = "multipoints" explode = False elif pargs.lines: ftype = "lines" explode = True geoms = vector_io.get_geometries(pointname, pargs.layername, pargs.layersql, extent, explode=explode) for geom in geoms: xyz = array_geometry.ogrgeom2array(geom, flatten=False) if xyz.shape[0] > 0: pc_refs.append(pointcloud.Pointcloud(xyz[:, :2], xyz[:, 2])) print("Found %d non-empty geometries" % len(pc_refs)) if len(pc_refs) == 0: print("No input geometries in intersection...") if pargs.ftype is not None: ftype = pargs.ftype cut_input_to = pargs.cut_to print("Cutting input pointcloud to class %d" % cut_input_to) pc = pointcloud.fromAny(lasname).cut_to_class( cut_input_to) #what to cut to here...?? #warping loop here.... if (pargs.toE): geoid = grid.fromGDAL(GEOID_GRID, upcast=True) print("Using geoid from %s to warp to ellipsoidal heights." % GEOID_GRID) for i in range(len(pc_refs)): toE = geoid.interpolate(pc_refs[i].xy) M = (toE == geoid.nd_val) if (M.any()): print( "Warping to ellipsoidal heights produced no-data values!") M = np.logical_not(M) toE = toE[M] pc_refs[i] = pc_refs[i].cut(M) pc_refs[i].z += toE #Remove empty pointsets not_empty = [] for pc_r in pc_refs: if pc_r.get_size() > 0: not_empty.append(pc_r) #dont worry, just a pointer... else: raise Warning("Empty input set...") print("Checking %d point sets" % len(not_empty)) #Loop over strips# for id in pc.get_pids(): print("%s\n" % ("+" * 70)) print("Strip id: %d" % id) pc_c = pc.cut_to_strip(id) if pc_c.get_size() < 50: print("Not enough points...") continue might_intersect = [] for i, pc_r in enumerate(not_empty): if pc_c.might_overlap(pc_r): might_intersect.append(i) if len(might_intersect) == 0: print("Strip does not intersect any point 'patch'...") continue pc_c.triangulate() pc_c.calculate_validity_mask(angle_tolerance, xy_tolerance, z_tolerance) #now loop over the patches which might intersect this strip... any_checked = False for i in might_intersect: pc_r = not_empty[i].cut_to_box(*(pc_c.get_bounds())) if pc_r.get_size == 0: continue any_checked = True print("Stats for check of 'patch/set' %d against strip %d:" % (i, id)) stats = check_points(pc_c, pc_r) if stats is None: print("Not enough points in proper triangles...") continue m, sd, n = stats cm_x, cm_y = pc_r.xy.mean(axis=0) cm_z = pc_r.z.mean() print("Center of mass: %.2f %.2f %.2f" % (cm_x, cm_y, cm_z)) cm_geom = ogr.Geometry(ogr.wkbPoint25D) cm_geom.SetPoint(0, cm_x, cm_y, cm_z) #what geometry should be reported, bounding box?? reporter.report(kmname, id, ftype, m, sd, n, ogr_geom=cm_geom) if not any_checked: print("Strip did not intersect any point 'patch'...")
def main(args): ''' Run road delta check. Invoked from either command line or qc_wrap.py ''' pargs = parser.parse_args(args[1:]) lasname = pargs.las_file linename = pargs.lines kmname = constants.get_tilename(lasname) print("Running %s on block: %s, %s" % (os.path.basename(args[0]), kmname, time.asctime())) if pargs.schema is not None: report.set_schema(pargs.schema) reporter = report.ReportDeltaRoads(pargs.use_local) cut_class = pargs.cut_class pc = pointcloud.fromAny(lasname).cut_to_class(cut_class) if pc.get_size() < 5: print("Too few points to bother..") return 1 pc.triangulate() geom = pc.get_triangle_geometry() print("Using z-steepnes limit {0:.2f} m".format(pargs.zlim)) mask = np.logical_and(geom[:, 1] < XY_MAX, geom[:, 2] > pargs.zlim) geom = geom[mask] # save for reporting if not mask.any(): print("No steep triangles found...") return 0 # only the centers of the interesting triangles centers = pc.triangulation.get_triangle_centers()[mask] print("{0:d} steep triangles in tile.".format(centers.shape[0])) try: extent = np.asarray(constants.tilename_to_extent(kmname)) except Exception: print("Could not get extent from tilename.") extent = None lines = vector_io.get_geometries(linename, pargs.layername, pargs.layersql, extent) feature_count = 0 for line in lines: xy = array_geometry.ogrline2array(line, flatten=True) if xy.shape[0] == 0: print("Seemingly an unsupported geometry...") continue # select the triangle centers which lie within line_buffer of the road segment mask = array_geometry.points_in_buffer(centers, xy, LINE_BUFFER) critical = centers[mask] print("*" * 50) print("{0:d} steep centers along line {1:d}".format( critical.shape[0], feature_count)) feature_count += 1 if critical.shape[0] > 0: z_box = geom[mask][:, 2] z1 = z_box.max() z2 = z_box.min() wkt = "MULTIPOINT(" for point in critical: wkt += "{0:.2f} {1:.2f},".format(point[0], point[1]) wkt = wkt[:-1] + ")" reporter.report(kmname, z1, z2, wkt_geom=wkt)
def main(args): try: pargs = parser.parse_args(args[1:]) except Exception as e: print(str(e)) return 1 kmname = constants.get_tilename(pargs.las_file) print("Running %s on block: %s, %s" % (progname, kmname, time.asctime())) lasname = pargs.las_file polyname = pargs.poly_data use_local = pargs.use_local if pargs.schema is not None: report.set_schema(pargs.schema) reporter = report.ReportBuildingRelposCheck(use_local) ################################## pc = pointcloud.fromAny(lasname).cut_to_z_interval( -10, 200).cut_to_class(cut_to_classes) try: extent = np.asarray(constants.tilename_to_extent(kmname)) except Exception as e: print("Could not get extent from tilename.") extent = None polys = vector_io.get_geometries(polyname) fn = 0 sl = "-" * 65 pcs = dict() for id in pc.get_pids(): print("%s\n" % ("+" * 70)) print("Strip id: %d" % id) pc_ = pc.cut_to_strip(id) if pc_.get_size() > 500: pcs[id] = pc_ else: print("Not enough points....") del pc done = [] for id1 in pcs: pc1 = pcs[id1] for id2 in pcs: if id1 == id2 or (id1, id2) in done or (id2, id1) in done: continue done.append((id1, id2)) pc2 = pcs[id2] ml = "-" * 70 print("%s\nChecking strip %d against strip %d\n%s" % (ml, id1, id2, ml)) if not pc1.might_overlap(pc2): if DEBUG: print( "Strip %d and strip %d does not seem to overlap. Continuing..." % (id1, id2)) print("DEBUG: Strip1 bounds:\n%s\nStrip2 bounds:\n%s" % (pc1.get_bounds(), pc2.get_bounds())) continue for poly in polys: centroid = poly.Centroid() centroid.FlattenTo2D() if LIGHT_DEBUG: print("Geom type: %s" % centroid.GetGeometryName()) n_corners_found = 0 fn += 1 print("%s\nChecking feature %d\n%s\n" % (sl, fn, sl)) a_poly = array_geometry.ogrgeom2array(poly) pcp1 = pc1.cut_to_polygon(a_poly) if pcp1.get_size() < 300: print("Few (%d) points in polygon..." % pcp1.get_size()) continue pcp2 = pc2.cut_to_polygon(a_poly) if pcp2.get_size() < 300: print("Few (%d) points in polygon..." % pcp2.get_size()) continue a_poly = a_poly[0] poly_buf = poly.Buffer(2.0) xy_t = a_poly.mean( axis=0 ) #transform later to center of mass system for numerical stability... #transform a_poly coords to center of mass system here a_poly -= xy_t a_poly2 = array_geometry.ogrgeom2array(poly_buf) #dicts to store the found corners in the two strips... found1 = dict() found2 = dict() for pc, pcp, store in [(pc1, pcp1, found1), (pc2, pcp2, found2)]: pcp.triangulate() geom = pcp.get_triangle_geometry() m = geom[:, 1].mean() sd = geom[:, 1].std() if (m > 1.5 or 0.5 * sd > m): print("Feature %d, bad geometry...." % fn) print("{} {}".format(m, sd)) break #geom is ok - we proceed with a buffer around da house pcp = pc.cut_to_polygon(a_poly2) ###### ## Transform pointcloud to center of mass system here... ####### pcp.xy -= xy_t print("Points in buffer: %d" % pcp.get_size()) pcp.triangulate() geom = pcp.get_triangle_geometry() tanv2 = tan(radians(cut_angle))**2 #set a mask to mark the triangles we want to consider as marking the house boundary mask = np.logical_and(geom[:, 0] > tanv2, geom[:, 2] > z_limit) #we consider the 'high' lying vertices - could also just select the highest of the three vertices... p_mask = (pcp.z > pcp.z.min() + 2) #and only consider those points which lie close to the outer bd of the house... p_mask &= array_geometry.points_in_buffer( pcp.xy, a_poly, 1.2) #a larger shift than 1.2 ?? #this just selects vertices where p_mask is true, from triangles where mask is true - nothing else... bd_mask = pcp.get_boundary_vertices(mask, p_mask) xy = pcp.xy[bd_mask] print("Boundary points: %d" % xy.shape[0]) if DEBUG: plot_points(a_poly, xy) #now find those corners! lines_ok = dict() found_lines = dict() for vertex in range( a_poly.shape[0] - 1): #check line emanating from vertex... p1 = a_poly[vertex] p2 = a_poly[vertex + 1] ok, pts = check_distribution(p1, p2, xy) lines_ok[vertex] = (ok, pts) #now find corners vertex = 0 #handle the 0'th corner specially... while vertex < a_poly.shape[0] - 2: if lines_ok[vertex][0] and lines_ok[vertex + 1][0]: #proceed print("%s\nCorner %d should be findable..." % ("+" * 50, vertex + 1)) corner_found = find_corner(vertex, lines_ok, found_lines, a_poly) diff = norm2(a_poly[vertex + 1] - corner_found) if ( diff < TOL_CORNER ): #seems reasonable that this is a true corner... store[vertex + 1] = corner_found n_corners_found += 1 #print a_poly[vertex+1],corner_found,vertex vertex += 1 else: #skip to next findable corner vertex += 2 if lines_ok[0][0] and lines_ok[a_poly.shape[0] - 2][0]: print("Corner 0 should also be findable...") corner_found = find_corner(a_poly.shape[0] - 2, lines_ok, found_lines, a_poly) diff = norm2(a_poly[0] - corner_found) if (diff < TOL_CORNER ): #seems reasonable that this is a true corner... store[0] = corner_found n_corners_found += 1 print("Corners found: %d" % n_corners_found) if n_corners_found == 0: #no need to do another check... break print( "Found %d corners in strip %d, and %d corners in strip %d" % (len(found1), id1, len(found2), id2)) if len(found1) > 0 and len(found2) > 0: match1 = [] match2 = [] for cn in found1: if cn in found2: match1.append(found1[cn]) match2.append(found2[cn]) if len(match1) > 0: match1 = np.array(match1) match2 = np.array(match2) n_corners_found = match1.shape[0] print("\n********** In total for feature %d:" % fn) print("Corners found in both strips: %d" % n_corners_found) all_dxy = match1 - match2 mdxy = all_dxy.mean(axis=0) sdxy = np.std(all_dxy, axis=0) ndxy = norm(all_dxy) params = (1, mdxy[0], mdxy[1]) print("Mean dxy: %.3f, %.3f" % (mdxy[0], mdxy[1])) print("Sd : %.3f, %.3f" % (sdxy[0], sdxy[1])) print("Max absolute : %.3f m" % (ndxy.max())) print("Mean absolute: %.3f m" % (ndxy.mean())) if n_corners_found > 1: print( "Helmert transformation (corners1 to corners2):" ) params = helmert2d(match1, match2) #2->1 or 1->2 ? print("Scale: %.5f ppm" % ((params[0] - 1) * 1e6)) print("dx: %.3f m" % params[1]) print("dy: %.3f m" % params[2]) print("Residuals:") match2_ = params[0] * match1 + params[1:] all_dxy = match2_ - match2 mdxy_ = all_dxy.mean(axis=0) sdxy_ = np.std(all_dxy, axis=0) ndxy_ = norm(all_dxy) if DEBUG or LIGHT_DEBUG: plot3([match1, match2, match2_]) #center of mass distances only!! cm_vector = (match2.mean(axis=0) - match1.mean(axis=0)) cm_dist = norm2(cm_vector) print("Mean dxy: %.3f, %.3f" % (mdxy_[0], mdxy_[1])) print("Sd : %.3f, %.3f" % (sdxy_[0], sdxy_[1])) print("Max absolute : %.3f m" % (ndxy_.max())) print("Mean absolute: %.3f m" % (ndxy_.mean())) reporter.report(kmname, id1, id2, cm_vector[0], cm_vector[1], cm_dist, params[0], params[1], params[2], sdxy_[0], sdxy_[1], n_corners_found, ogr_geom=centroid)
def main(args): pargs = parser.parse_args(args[1:]) lasname = pargs.las_file polyname = pargs.build_polys kmname = constants.get_tilename(lasname) print("Running %s on block: %s, %s" % (os.path.basename(args[0]), kmname, time.asctime())) if pargs.schema is not None: report.set_schema(pargs.schema) reporter = report.ReportRoofridgeStripCheck(pargs.use_local) cut_class = pargs.cut_class # default step values for search... steps1 = 30 steps2 = 13 search_factor = pargs.search_factor if search_factor != 1: # can turn search steps up or down steps1 = int(search_factor * steps1) steps2 = int(search_factor * steps2) print("Incresing search factor by: %.2f" % search_factor) print("Running time will increase exponentionally with search factor...") pc = pointcloud.fromAny(lasname).cut_to_class(cut_class).cut_to_z_interval(Z_MIN, Z_MAX) try: extent = np.asarray(constants.tilename_to_extent(kmname)) except Exception: print("Could not get extent from tilename.") extent = None polys = vector_io.get_geometries(polyname, pargs.layername, pargs.layersql, extent) fn = 0 sl = "+" * 60 is_sloppy = pargs.sloppy use_all = pargs.use_all for poly in polys: print(sl) fn += 1 print("Checking feature number %d" % fn) a_poly = array_geometry.ogrgeom2array(poly) # secret argument to use all buildings... if (len(a_poly) > 1 or a_poly[0].shape[0] != 5) and (not use_all) and (not is_sloppy): print("Only houses with 4 corners accepted... continuing...") continue pcp = pc.cut_to_polygon(a_poly) strips = pcp.get_pids() if len(strips) != 2: print("Not exactly two overlapping strips... continuing...") continue # Go to a more numerically stable coord system - from now on only consider outer ring... a_poly = a_poly[0] xy_t = a_poly.mean(axis=0) # center of mass system a_poly -= xy_t lines = [] # for storing the two found lines... for sid in strips: print("-*-" * 15) print("Looking at strip %d" % sid) pcp_ = pcp.cut_to_strip(sid) # hmmm, these consts should perhaps be made more visible... if (pcp_.get_size() < 500 and (not is_sloppy)) or (pcp_.get_size() < 10): print("Few points in polygon... %d" % pcp_.get_size()) continue pcp_.xy -= xy_t pcp_.triangulate() geom = pcp_.get_triangle_geometry() m = geom[:, 1].mean() sd = geom[:, 1].std() if (m > 1.5 or 0.5 * sd > m) and (not is_sloppy): print("Feature %d, strip %d, bad geometry...." % (fn, sid)) break planes = cluster(pcp_, steps1, steps2) if len(planes) < 2: print("Feature %d, strip %d, didn't find enough planes..." % (fn, sid)) pair, equation = find_planar_pairs(planes) if pair is not None: p1 = planes[pair[0]] print("%s" % ("*" * 60)) print("Statistics for feature %d" % fn) # Now we need to find some points on the line near the house... (0,0) is # the center of mass norm_normal = equation[0]**2 + equation[1]**2 if norm_normal < 1e-10: print("Numeric instablity, small normal") break # this should be on the line cm_line = np.asarray(equation[:2]) * (equation[2] / norm_normal) line_dir = np.asarray((-equation[1], equation[0])) / (sqrt(norm_normal)) end1 = cm_line + line_dir * LINE_RAD end2 = cm_line - line_dir * LINE_RAD intersections = np.vstack((end1, end2)) line_x = intersections[:, 0] line_y = intersections[:, 1] z_vals = p1[0] * intersections[:, 0] + p1[1] * intersections[:, 1] + p1[2] if abs(z_vals[0] - z_vals[1]) > 0.01: print("Numeric instabilty for z-calculation...") z_val = float(np.mean(z_vals)) print("Z for intersection is %.2f m" % z_val) # transform back to real coords line_x += xy_t[0] line_y += xy_t[1] wkt = "LINESTRING(%.3f %.3f %.3f, %.3f %.3f %.3f)" % ( line_x[0], line_y[0], z_val, line_x[1], line_y[1], z_val) print("WKT: %s" % wkt) lines.append([sid, wkt, z_val, cm_line, line_dir]) if len(lines) == 2: # check for parallelity id1 = lines[0][0] id2 = lines[1][0] z1 = lines[0][2] z2 = lines[1][2] if abs(z1 - z2) > 0.5: print("Large difference in z-values for the two lines!") else: ids = "{0:d}_{1:d}".format(id1, id2) inner_prod = (lines[0][4] * lines[1][4]).sum() inner_prod = max(-1, inner_prod) inner_prod = min(1, inner_prod) if DEBUG: print("Inner product: %.4f" % inner_prod) ang = abs(degrees(acos(inner_prod))) if ang > 175: ang = abs(180 - ang) if ang < 15: v = (lines[0][3] - lines[1][3]) d = np.sqrt((v**2).sum()) if d < 5: for line in lines: reporter.report(kmname, id1, id2, ids, d, ang, line[2], wkt_geom=line[1]) else: print("Large distance between centers %s, %s, %.2f" % (lines[0][3], lines[1][3], d)) else: print("Pair found - but not very well aligned - angle: %.2f" % ang) else: print("Pair not found...")