def prepare_strokes_lvl2(delimited_stroke_class): """Constructs delimited strokes of level 2 for road sections in strokes that could not be matched.""" not_matched_strokes = session.query(delimited_stroke_class).filter( delimited_stroke_class.match_id == None) if delimited_stroke_class == DelimitedStrokeRef: delimited_strokes = delimited_strokes_ref else: delimited_strokes = delimited_strokes_target for delimited_stroke in not_matched_strokes: for road_section in delimited_strokes[delimited_stroke.id]: road_section.delimited_stroke = None begin_junction = delimited_stroke.begin_junction for road_section in delimited_strokes[delimited_stroke.id]: if road_section.delimited_stroke is None: new_stroke = construct_stroke_from_section( road_section, delimited_stroke_class, level=2, begin_junction=begin_junction) extended_stroke = construct_stroke(road_section, begin_junction, new_stroke, level=2) begin_junction = extended_stroke.end_junction session.query(delimited_stroke_class).filter( delimited_stroke_class.level == 1, delimited_stroke_class.match_id == None).delete()
def get_area(geom): """Calculates the area (in m2) of a plane.""" numpoints = session.query(func.st_numpoints(geom))[0][0] if numpoints > 2: return session.query( func.st_area( func.st_makepolygon( func.st_addpoint(geom, func.st_startpoint(geom)))))[0][0] else: return 0
def angle_at_junction(road_section, junction): """Calculates the angle of the line segment of road_section at junction.""" assert (road_section.begin_junction == junction or road_section.end_junction == junction) first_point_section = session.query(func.st_startpoint( road_section.geom))[0][0] if session.query(func.st_equals(first_point_section, junction.geom))[0][0]: angle = session.query( func.ST_Azimuth(junction.geom, road_section.geom.ST_PointN(2)))[0][0] else: angle = session.query( func.ST_Azimuth(junction.geom, road_section.geom.ST_PointN(-2)))[0][0] assert angle is not None return angle
def preprocess_target(preprocessing_check): """"Handles the preprocessing of the target dataset. Includes classification of junctions and construction of delimited strokes at level 1. Set the input preprocessing_check to false if the junctions are already classified and saved in the database""" session.query(DelimitedStrokeTarget).delete() reset_delimited_strokes(session.query(RoadSectionTarget)) junctions_target = session.query(JunctionTarget) if preprocessing_check: print("Classifying junctions of the target database.") classify_junctions(junctions_target) print("Constructing strokes of the target database.") construct_strokes(junctions_target, DelimitedStrokeTarget) remaining_sections_target = session.query(RoadSectionTarget).filter( RoadSectionTarget.delimited_stroke_id == None) for road_section in remaining_sections_target: construct_stroke_from_section(road_section, DelimitedStrokeTarget)
def generate_output(matches): session.query(LinkingTable).delete() for match in matches: strokes = session.query(DelimitedStrokeRef).filter( DelimitedStrokeRef.match_id == match.id) if not strokes.first(): # print('Match', match.id, 'no longer exists') continue for stroke_ref in match.strokes_ref: try: for section_ref in delimited_strokes_ref[stroke_ref.id]: for stroke_target in match.strokes_target: for section_target in delimited_strokes_target[ stroke_target.id]: link = LinkingTable( nwb_id=section_ref.id, top10nl_id=section_target.id, match_id=match.id, similarity_score=match.similarity_score) session.add(link) session.flush() except KeyError: print('Stroke', stroke_ref.id, 'not recorded in delimited strokes dictionary')
def set_similarity_score(self): """Calculates the similarity score of the match. It is a weighted sum of scaled attributes, which are difference in length, difference in distance (hausdorff distance) and difference in area.""" length_diff = length_difference(self.strokes_ref, self.strokes_target) hausdorff = session.query( func.st_hausdorffdistance(self.geom_ref, self.geom_target))[0][0] area_diff = self.set_area_difference() area_diff_normalized = area_diff / get_length(self.strokes_ref) weights = [0.5, 0.35, 0.15] # sum equal to 1 metrics = [ length_diff / tolerance_length, hausdorff / tolerance_hausdorff, area_diff_normalized / tolerance_area_normalized ] score = 0 for index, metric in enumerate(metrics): score += weights[index] * (1 - metric) return score
def matching_process(level, tolerance_distance): """Searches for a match for each delimited stroke in the reference database.""" strokes_ref = session.query(DelimitedStrokeRef).filter( DelimitedStrokeRef.level == level, DelimitedStrokeRef.match_id == None) count = 0 all_matches = [] for stroke in strokes_ref: try: matches = find_matching_candidates(stroke, tolerance_distance) except AssertionError as e: print(e) print(traceback.format_exc()) print('Something went wrong trying to find a match for stroke', stroke.id) matches = None # TODO add correct Exception, merging looping road sections is not possible if matches: best_score = 0 best_match = None for match in matches: if match.similarity_score >= best_score: best_match = match best_score = match.similarity_score if best_match: for stroke_ref in best_match.strokes_ref: stroke_ref.match_id = best_match.id for stroke_target in best_match.strokes_target: stroke_target.match_id = best_match.id all_matches.append(best_match) count += 1 if count % 100 == 0: print('Strokes analyzed:', count) return all_matches
def combine_geom(list_of_strokes): """Expects a list of strokes, returns a combined geometry of the strokes in the list.""" geoms = [stroke.geom for stroke in list_of_strokes] return session.query( func.st_astext(func.st_linemerge(func.st_collect(array(geoms)))))[0][0]
def get_length(list_of_strokes): """Calculates the length (in m) of the input array of strokes.""" length = 0 for stroke in list_of_strokes: length += session.query(func.st_length(stroke.geom)).first()[0] return length
def construct_stroke(road_section, junction, delimited_stroke, level=1): """Constructs a delimited stroke from road_section, with junction as its starting point. If a delimited_stroke is given as input, the next road_section is added to this delimited_stroke.""" session.flush() # the delimited stroke dictionary use here is based on type of the input stroke if type(delimited_stroke) == DelimitedStrokeRef: delimited_strokes = delimited_strokes_ref else: delimited_strokes = delimited_strokes_target road_section.delimited_stroke = delimited_stroke # determine which from which junction the next road section should be added if junction == road_section.begin_junction: next_junction = road_section.end_junction else: next_junction = road_section.begin_junction # if the next_junction is the same as the begin_junction of the delimited stroke, it is a loop and can be returned if next_junction.id == delimited_stroke.begin_junction_id: delimited_stroke.end_junction_id = next_junction.id return delimited_stroke # if the degree of the next junction is 2, the road section that is not equal to the input is added # to the delimited stroke if next_junction.degree == 2: for next_road_section in next_junction.road_sections: if next_road_section != road_section: delimited_stroke.geom = session.query( func.st_linemerge( func.st_collect(delimited_stroke.geom, next_road_section.geom))) delimited_strokes[delimited_stroke.id].append( next_road_section) return construct_stroke(next_road_section, next_junction, delimited_stroke, level) # only strokes at level 1, are extended with sections that have good continuity if level == 1: if next_junction.type_k3 == 2: current_angle = angle_at_junction(road_section, next_junction) if current_angle != next_junction.angle_k3: # select the next section that has good continuity for next_road_section in next_junction.road_sections: if next_road_section != road_section and next_road_section.delimited_stroke is None: next_angle = angle_at_junction(next_road_section, next_junction) if next_angle != next_junction.angle_k3: # if the next section is a loop, it is not added to the delimited stroke if next_road_section.begin_junction == next_road_section.end_junction: delimited_stroke.end_junction_id = next_junction.id return delimited_stroke # merge the selected section with the existing delimited stroke to extend it delimited_stroke.geom = session.query( func.st_linemerge( func.st_collect(delimited_stroke.geom, next_road_section.geom))) delimited_strokes[delimited_stroke.id].append( next_road_section) return construct_stroke(next_road_section, next_junction, delimited_stroke, level) # if the stroke is not extended, the end junction is set delimited_stroke.end_junction_id = next_junction.id return delimited_stroke
def get_distance(object_a, object_b): """Calculates the distance between two geometries.""" assert object_a.geom is not None assert object_b.geom is not None return session.query(func.st_distance(object_a.geom, object_b.geom))[0][0]
def extend_matching_pair(stroke_ref, stroke_target, junction_ref, junction_target, tolerance_distance): """Extends the input delimited strokes with strokes that have good continuity at input junction, until a good match is found or if no match is possible. Stroke_ref and stroke_target are both lists of delimited strokes.""" # create local variables of which stroke to extend and which to compare to when a new stroke is added if get_length(stroke_ref) < get_length(stroke_target): stroke_to_extend = stroke_ref stroke_to_compare = stroke_target junction_to_extend = junction_ref junction_to_compare = junction_target else: stroke_to_extend = stroke_target stroke_to_compare = stroke_ref junction_to_extend = junction_target junction_to_compare = junction_ref new_stroke = None # if the junction where the next stroke is added is a W-junction (type 1), select the stroke of the outer road # sections to be added if junction_to_extend.type_k3 == 1: if angle_at_junction( stroke_to_extend[-1], junction_to_extend) != junction_to_extend.angle_k3: for road_section in junction_to_extend.road_sections: if road_section.delimited_stroke != stroke_to_extend[-1] and junction_to_extend.angle_k3 != \ angle_at_junction(road_section, junction_to_extend): new_stroke = road_section.delimited_stroke # for other junctions, select the stroke that has good continuity if junction_to_extend.degree > 1: for road_section in junction_to_extend.road_sections: if road_section.delimited_stroke != stroke_to_extend[ -1] and has_good_continuity(road_section, stroke_to_extend[-1], junction_to_extend): new_stroke = road_section.delimited_stroke if new_stroke and (new_stroke.begin_junction == junction_to_extend or new_stroke.end_junction == junction_to_extend): stroke_to_extend.append(new_stroke) junction_to_extend = other_junction(stroke_to_extend[-1], junction_to_extend) point_distance = session.query( func.st_distance(junction_to_extend.geom, junction_to_compare.geom))[0][0] if point_distance < tolerance_distance: return Match(stroke_ref, stroke_target) elif session.query(func.st_distance(junction_to_extend.geom, stroke_to_compare[-1].geom))[0][0] < tolerance_distance or \ session.query(func.st_distance(junction_to_compare.geom, stroke_to_extend[-1].geom))[0][0] < tolerance_distance: if stroke_to_extend == stroke_ref: return extend_matching_pair(stroke_ref, stroke_target, junction_to_extend, junction_target, tolerance_distance) elif stroke_to_extend == stroke_target: return extend_matching_pair(stroke_ref, stroke_target, junction_ref, junction_to_extend, tolerance_distance) else: print('Error: wrong stroke') return None
def nearby_junctions(junction_ref, tolerance_distance): """Finds the junctions in the target database that are within the tolerance distance of junction_ref.""" junctions = session.query(JunctionTarget).filter( func.st_dwithin(JunctionTarget.geom, junction_ref.geom, tolerance_distance)) return junctions