diff --git a/generic_functions.py b/generic_functions.py index 69d6105..2712cb0 100755 --- a/generic_functions.py +++ b/generic_functions.py @@ -576,25 +576,26 @@ def remove_layerlist(listoflayer_alias): project_instance.removeMapLayer(layerfullname) -def remove_unconnected_lines(inputlayer): - #thx: https://gis.stackexchange.com/a/316058/49900 - with edit(inputlayer): - for i,feature_A in enumerate(inputlayer.getFeatures()): - is_disjointed = True - # disjointed_features = 0 - for j,feature_B in enumerate(inputlayer.getFeatures()): - if not i == j: - if not feature_A.geometry().disjoint(feature_B.geometry()): - # print('not disjointed!!',i,j) - is_disjointed=False - - if is_disjointed: - # print(is_disjointed) - - inputlayer.deleteFeature(feature_A.id()) - - # # # # it works inplace =D () - # # # # return inputlayer +### DEPRECATED ### +# # def remove_unconnected_lines(inputlayer): +# # #thx: https://gis.stackexchange.com/a/316058/49900 +# # with edit(inputlayer): +# # for i,feature_A in enumerate(inputlayer.getFeatures()): +# # is_disjointed = True +# # # disjointed_features = 0 +# # for j,feature_B in enumerate(inputlayer.getFeatures()): +# # if not i == j: +# # if not feature_A.geometry().disjoint(feature_B.geometry()): +# # # print('not disjointed!!',i,j) +# # is_disjointed=False + +# # if is_disjointed: +# # # print(is_disjointed) + +# # inputlayer.deleteFeature(feature_A.id()) + +# # # # # # it works inplace =D () +# # # # # # return inputlayer def qgs_point_geom_from_line_at(inputlinefeature,index=0): return QgsGeometry.fromPointXY(inputlinefeature.geometry().asPolyline()[index]) @@ -607,6 +608,7 @@ def remove_lines_from_no_block(inputlayer,layer_to_check_culdesac=None): the "layer_to_check_culdesac" is a whole layer (dissolved) that should be checked for 'within' condition ''' + # TODO: check if will work with multilinestrings check_for_culdesacs = False @@ -617,6 +619,8 @@ def remove_lines_from_no_block(inputlayer,layer_to_check_culdesac=None): feature_ids_to_be_removed = [] + index = QgsSpatialIndex(inputlayer.getFeatures()) + for i,feature_A in enumerate(inputlayer.getFeatures()): @@ -626,19 +630,27 @@ def remove_lines_from_no_block(inputlayer,layer_to_check_culdesac=None): P0_count = 0 PF_count = 0 + # THE OPTIMIZATION PATROL: + # for j,feature_B in enumerate(inputlayer.getFeatures()): + # # if not i == j: + # if P0.intersects(feature_B.geometry()): + # P0_count += 1 + # if PF.intersects(feature_B.geometry()): + # PF_count += 1 - for j,feature_B in enumerate(inputlayer.getFeatures()): - # if not i == j: - if P0.intersects(feature_B.geometry()): - P0_count += 1 - if PF.intersects(feature_B.geometry()): - PF_count += 1 + intersect_ids = index.intersects(feature_A.geometry().boundingBox()) + for id in intersect_ids: + if id != feature_A.id(): + if P0.intersects(inputlayer.getFeature(id).geometry()): + P0_count += 1 + if PF.intersects(inputlayer.getFeature(id).geometry()): + PF_count += 1 # print(P0_count,PF_count) - if any(count == 1 for count in [P0_count,PF_count]): + if any(count == 0 for count in [P0_count,PF_count]): # after checking, only add if its not a "culdesac" if check_for_culdesacs: if not feature_A.geometry().within(checker_geom): @@ -1079,12 +1091,36 @@ def select_vertex_pol_nodes(inputpolygonfeature,minC_angle=160,maxC_angle=200): def create_incidence_field_layers_A_B(inputlayer,incident_layer,fieldname='incident',total_length_instead=False): + + """ + Creates incidence field layers A and B based on the given input layer and incident layer. + + :param inputlayer: The input layer on which the incidence field layers will be created. + :type inputlayer: QgsVectorLayer + + :param incident_layer: The incident layer from which the features will be used to create the incidence field layers. + :type incident_layer: QgsVectorLayer + + :param fieldname: The name of the field in the input layer that will store the incidence information. Defaults to 'incident'. + :type fieldname: str + + :param total_length_instead: If True, the total length of intersecting features will be stored in the field. If False, the IDs of intersecting features will be stored. Defaults to False. + :type total_length_instead: bool + + :return: The field ID of the created incidence field layer. + :rtype: int + """ + + if total_length_instead: field_id = create_new_layerfield(inputlayer,fieldname,QVariant.Double) else: field_id = create_new_layerfield(inputlayer,fieldname,QVariant.String) + index = QgsSpatialIndex(incident_layer.getFeatures()) + + with edit(inputlayer): for feature in inputlayer.getFeatures(): @@ -1092,8 +1128,15 @@ def create_incidence_field_layers_A_B(inputlayer,incident_layer,fieldname='incid contained_list = [] sum = 0 - for tested_feature in incident_layer.getFeatures(): - # with not disjoint one can go back and forth + + intersecting_ids = index.intersects(feature.geometry().boundingBox()) + + + # for tested_feature in incident_layer.getFeatures(): + for id in intersecting_ids: + + tested_feature = incident_layer.getFeature(id) + # with not disjointed one can go back and forth if not feature.geometry().disjoint(tested_feature.geometry()): if total_length_instead: diff --git a/osm_sidewalkreator.py b/osm_sidewalkreator.py index cfc052f..5ac310c 100755 --- a/osm_sidewalkreator.py +++ b/osm_sidewalkreator.py @@ -153,7 +153,7 @@ class sidewalkreator: already_existing_sidewalks_layer = None # hint texts: - en_hint = 'Many Times its better to\ncorrect errors on OSM data first!' + en_hint = "Sometimes it's better to\ncorrect errors on OSM data first!" ptbr_hint = 'Pode ser melhor consertar\nerros na base do OSM antes!' @@ -507,8 +507,8 @@ def data_clean(self): width_value = float(self.dlg.higway_values_table.item(i,1).text()) except: # if the user input value not convertible to float, just use the value from - print('invalid input value: ',self.dlg.higway_values_table.item(i,1).text(),', using default: ',default_widths[val]) - width_value = default_widths[val] + print('invalid input value: ',self.dlg.higway_values_table.item(i,1).text(),', using default: ',default_widths.get(val,fallback_default_width)) + width_value = default_widths.get(val,fallback_default_width) highway_valuestable_dict[val] = width_value @@ -553,7 +553,7 @@ def data_clean(self): - # protoblocks has been moved to here: + # protoblocks have been moved to here: self.protoblocks = polygonize_lines(self.clipped_reproj_datalayer) self.protoblocks.setCrs(self.custom_localTM_crs) # better safe than sorry kkkk @@ -575,7 +575,7 @@ def data_clean(self): with edit(self.protoblocks): for feature in self.protoblocks.getFeatures(): - # dividing by 4 approximates a square corner + # dividing by 4 approximates the side of a squared shape block ratio = (((feature['inc_sidewalk_len']/4)**2) / feature.geometry().area()) * 100 self.protoblocks.changeAttributeValue(feature.id(),protoblocks_ratio_id,ratio) @@ -1002,6 +1002,7 @@ def draw_crossings(self): self.above_tol_fieldname = self.string_according_language('above_tol','acima_da_tolerancia') self.nearest_centerpoint_fieldname= self.string_according_language('nearest_centerpoint','ponto_central_maisprox') + index = QgsSpatialIndex(self.splitted_lines.getFeatures()) for i,feature_A in enumerate(self.splitted_lines.getFeatures()): @@ -1031,28 +1032,33 @@ def draw_crossings(self): # tolerance for considering that a crossing center will have the crossing effectively drawn # a: two times (half width plus self.dlg.d_to_add_box.value()) # b: three times half the width - initial_vec_len = featurewidth + self.dlg.d_to_add_box.value() # # 3 times to KNN search # # KNN search was deprecated - for j,feature_B in enumerate(self.splitted_lines.getFeatures()): - # if not i == j: - if P0.intersects(feature_B.geometry()): - P0_count += 1 + P0_small_region = P0.buffer(.1,5).boundingBox() + PF_small_region = PF.buffer(.1,5).boundingBox() + + intersecting_ids_P0 = index.intersects(P0_small_region) - if not feature_B.id() == feature_layer_id: - if not feature_osm_id == feature_B['id']: - P0_intersecting_widths[feature_B.id()] = feature_B['width'] + if intersecting_ids_P0: + P0_count = len([id for id in intersecting_ids_P0 if self.splitted_lines.getFeature(id).geometry().intersects(P0.buffer(.1,5))]) + for id in intersecting_ids_P0: + if id != feature_A.id(): + P0_intersecting_widths[id] = self.splitted_lines.getFeature(id)['width'] - if PF.intersects(feature_B.geometry()): - PF_count += 1 + intersecting_ids_PF = index.intersects(PF_small_region) - if not feature_B.id() == feature_layer_id: - if not feature_osm_id == feature_B['id']: - PF_intersecting_widths[feature_B.id()] = feature_B['width'] + if intersecting_ids_PF: + PF_count = len([id for id in intersecting_ids_PF if self.splitted_lines.getFeature(id).geometry().intersects(PF.buffer(.1,5))]) + for id in intersecting_ids_PF: + if id != feature_A.id(): + PF_intersecting_widths[id] = self.splitted_lines.getFeature(id)['width'] + + + initial_vec_len = featurewidth + self.dlg.d_to_add_box.value() # print(i+1,P0_intersecting_widths) # print(i+1,PF_intersecting_widths,'\n') @@ -2120,6 +2126,10 @@ def reset_fields(self): self.set_text_based_on_language(self.dlg.input_status,'waiting a valid input...','aguardando uma entrada vĂ¡lida...',self.change_input_labels) self.set_text_based_on_language(self.dlg.input_status_of_data,'waiting for data...','aguardando dados...',self.change_input_labels) + # hint box: + self.en_hint = "Sometimes it's better to\ncorrect errors on OSM data first!" + self.ptbr_hint = 'Pode ser melhor consertar\nerros na base do OSM antes!' + # to not exclude created layers and data when the "Ok" button is pressed: if not self.ok_ready: @@ -2502,7 +2512,7 @@ def call_get_osm_data(self): self.dlg.higway_values_table.setItem(i,0,QTableWidgetItem(vvalue)) - self.dlg.higway_values_table.setItem(i,1,QTableWidgetItem(str(default_widths[vvalue]))) + self.dlg.higway_values_table.setItem(i,1,QTableWidgetItem(str(default_widths.get(vvalue,fallback_default_width)))) @@ -3409,6 +3419,8 @@ def try_to_merge_small_stretches(self): # touching_times_dict[feat_id] = 0 # touchers[feat_id] = [] + # MARKED FOR IMPROVEMENT WITH QgsSpatialIndex + for feat2 in extracted_adj_lines.getFeatures(): if not feat2.id in already_used_adj: # to avoid a feature to be used 2 times, or acess a not existent anymore feature if tiny_feats_dict[feat_id].geometry().touches(feat2.geometry()): @@ -3430,7 +3442,7 @@ def try_to_merge_small_stretches(self): break # so, go for the next small stretch - print(feat2.geometry().length()) + # print(feat2.geometry().length()) # print(already_used_adj) #remove the layerfield: It wont be necessary anymore diff --git a/parameters.py b/parameters.py index 5a2ce96..c436128 100644 --- a/parameters.py +++ b/parameters.py @@ -104,6 +104,9 @@ # '' : , } +# for cases of an unexpected value: +fallback_default_width = 6.0 + # ASSETS: # names of the assets filenames: sidewalks_stylefilename = 'sidewalkstyles.qml'