class Gewerbesteuer(QObject): ''' parameters and calculation of business tax ''' changed = pyqtSignal() def __init__(self, project, ui, layer_group): super().__init__() self.layer_group = layer_group self.project = project self.ui = ui self.ui.calc_gewerbesteuer_button.clicked.connect(self.calculate) self.params = None def load_content(self): self.gemeinden = Gemeindebilanzen.features(project=self.project) self.setup_params() def setup_params(self): if self.params: self.params.close() layout = self.ui.gewerbesteuer_hebesatz_param_group.layout() clear_layout(layout) self.params = Params(layout, help_file='einnahmen_gewerbesteuer_hebesätze.txt') self.params.add(Title('Hebesätze', bold=False)) for gemeinde in sorted(self.gemeinden, key=lambda x: x.GEN): spinbox = SpinBox(minimum=0, maximum=999, step=1) param = Param(gemeinde.Hebesatz_GewSt, spinbox, label=f' - {gemeinde.GEN}', unit='v.H.') self.params.add(param, name=gemeinde.AGS) def save(): self.changed.emit() for gemeinde in self.gemeinden: param = self.params[gemeinde.AGS] gemeinde.Hebesatz_GewSt = param.value gemeinde.save() self.params.show(title='Hebesätze Gewerbesteuer bearbeiten', scrollable=True) self.params.changed.connect(save) def calculate(self): ''' calculate the business tax ''' if len(BeschaeftigtenWanderung.features()) == 0: QMessageBox.warning( self.ui, 'Fehler', 'Bitte führen Sie zunächst die Schätzung der Wanderungssalden ' '(Beschäftigte) durch.') return job = GewerbesteuerCalculation(self.project) def on_success(r): self.add_layer() self.changed.emit() self.dialog = ProgressDialog(job, parent=self.ui, on_success=on_success, auto_close=True) self.dialog.show() def add_layer(self): ''' show business tax layer ''' self.output = ProjectLayer.from_table(self.gemeinden.table, groupname=self.layer_group) self.output.draw(label='Gewerbesteuer', style_file='einnahmen_gewerbesteuer.qml', filter="gewerbesteuer != 'NULL'", uncheck_siblings=True, redraw=False) self.output.zoom_to() def close(self): if self.params: self.params.close()
class EinwohnerMigration(Migration): ''' migration of inhabitants ''' def __init__(self, project, ui, layer_group, canvas): super().__init__(project, ui, layer_group, canvas) self.ui.migration_inhabitants_button.clicked.connect(self.calculate) def load_content(self): super().load_content() self.wanderung = EinwohnerWanderung.features(create=True) self.ui.einwohner_parameter_group.setVisible(False) if len(self.wanderung) == 0: self.ui.recalculate_inhabitants_check.setChecked(True) self.ui.recalculate_inhabitants_check.setVisible(False) self.ui.einwohner_parameter_group.setVisible(False) else: self.setup_params() def calculate(self): ''' calculate the migration ''' if not self.ui.recalculate_inhabitants_check.isChecked(): self.add_layer(toggle_if_exists=True) return sum_ew = sum(x or 0 for x in self.areas.values('ew')) if sum_ew == 0: QMessageBox.warning( self.ui, 'Fehler', 'Es wurden keine definierten Teilflächen mit ' 'der Nutzungsart "Wohnen" gefunden.') return job = EwMigrationCalculation(self.project) def on_close(): self.changed.emit() if not self.dialog.success: self.wanderung.table.truncate() self.ui.recalculate_inhabitants_check.setVisible(False) self.ui.recalculate_inhabitants_check.setChecked(True) return self.ui.recalculate_inhabitants_check.setVisible(True) self.ui.recalculate_inhabitants_check.setChecked(False) self.add_layer() self.setup_params() self.dialog = ProgressDialog(job, parent=self.ui, on_close=on_close) self.dialog.show() def setup_params(self): ''' set up migration settings for each muncipality in study area ''' if self.params: self.params.close() self.ui.einwohner_parameter_group.setVisible(True) layout = self.ui.einwohner_parameter_group.layout() clear_layout(layout) self.params = Params(layout, help_file='einnahmen_einwohner_wanderung.txt') self.df_wanderung = self.wanderung.to_pandas() randsummen = self.project.basedata.get_table('Wanderung_Randsummen', 'Einnahmen').features() factor_inner = randsummen.get(IDWanderungstyp=1).Anteil_Wohnen factor_outer = randsummen.get(IDWanderungstyp=2).Anteil_Wohnen project_ags = self.project_frame.ags project_gem = self.gemeinden.get(AGS=project_ags) wanderung = self.wanderung.get(AGS=project_ags) sum_ew = sum(x or 0 for x in self.areas.values('ew')) def update_salden(ags_changed): param = self.params[ags_changed] fixed = param.is_locked saldo = param.input.value idx = self.df_wanderung['AGS'] == ags_changed if fixed: fixed_fortzug = self.df_wanderung[np.invert(idx) & self.df_wanderung['fixed'] == True]['fortzug'].sum() # the rest of "fortzüge" that can be applied to this row min_value = fixed_fortzug - (sum_ew * factor_inner) saldo = max(saldo, min_value) zuzug = self.df_wanderung[idx]['zuzug'].values[0] self.df_wanderung.loc[idx, 'fortzug'] = zuzug - saldo self.df_wanderung.loc[idx, 'fixed'] = fixed self.df_wanderung.loc[idx, 'saldo'] = saldo self.df_wanderung = MigrationCalculation.calculate_saldi( self.df_wanderung, factor_inner, project_ags) for gemeinde_ags in self.df_wanderung['AGS'].values: param = self.params[gemeinde_ags] row = self.df_wanderung[self.df_wanderung['AGS'] == gemeinde_ags] param.input.blockSignals(True) param.input.value = row['saldo'].values[0] param.input.blockSignals(False) self.params.add(Title('Standortgemeinde des Projekts', bold=False)) spinbox = DoubleSpinBox(minimum=0, maximum=1000, step=1, lockable=True, locked=wanderung.fixed, reversed_lock=True) project_saldo = Param(wanderung.saldo, spinbox, repr_format='%+.2f', label=f' - {project_gem.GEN}', unit='Ew') self.params.add(project_saldo, name=project_ags) spinbox.changed.connect(lambda o: update_salden(project_ags)) spinbox.locked.connect(lambda o: update_salden(project_ags)) self.params.add(Seperator()) self.params.add(Title('Region um Standortgemeinde', bold=False)) for gemeinde in sorted(self.gemeinden, key=lambda x: x.GEN): ags = gemeinde.AGS if ags == project_ags: continue wanderung = self.wanderung.get(AGS=ags) if not wanderung: continue spinbox = DoubleSpinBox(minimum=-1000, maximum=0, step=1, lockable=True, locked=wanderung.fixed, reversed_lock=True) param = Param(wanderung.saldo, spinbox, label=f' - {gemeinde.GEN}', unit='Ew') self.params.add(param, name=ags) spinbox.changed.connect(lambda o, a=ags: update_salden(a)) spinbox.locked.connect(lambda o, a=ags: update_salden(a)) self.params.add(Seperator()) self.params.add( Param(-factor_outer * sum_ew, label='Restliches Bundesgebiet / Ausland', unit='Ew')) def save(): self.wanderung.update_pandas(self.df_wanderung) self.changed.emit() self.canvas.refreshAllLayers() self.params.show(title='Geschätzte Salden (Einwohner) bearbeiten', scrollable=True) self.params.changed.connect(save) def add_layer(self, toggle_if_exists=False): ''' show layer with migration of inhabitants ''' self.output = ProjectLayer.from_table(self.wanderung.table, groupname=self.layer_group) self.output.draw(label='Wanderungssalden Einwohner', style_file='einnahmen_einwohnerwanderung.qml', uncheck_siblings=True, redraw=not toggle_if_exists, toggle_if_exists=toggle_if_exists) if self.output.tree_layer.isVisible(): self.output.zoom_to()
class Kostentraeger: ''' parameters and calculations of shares of infrastructural costs between different payers ''' def __init__(self, ui, project): self.ui = ui self.project = project self.ui.kostentraeger_button.clicked.connect( self.calculate_kostentraeger) self.default_kostenaufteilung = self.project.basedata.get_table( 'Kostenaufteilung_Startwerte', 'Kosten') self.kostenphasen = self.project.basedata.get_table( 'Kostenphasen', 'Kosten').features() self.aufteilungsregeln = self.project.basedata.get_table( 'Aufteilungsregeln', 'Kosten').features() self.applyable_aufteilungsregeln = self.project.basedata.get_table( 'Aufteilungsregeln_zu_Netzen_und_Phasen', 'Kosten').features() self.netzelemente = self.project.basedata.get_table( 'Netze_und_Netzelemente', 'Kosten', fields=['IDNetz', 'Netz'] ).features() df_netzelemente = self.netzelemente.to_pandas() del df_netzelemente['fid'] df_netzelemente.drop_duplicates(inplace=True) for i, (index, row) in enumerate(df_netzelemente.iterrows()): net_id = row['IDNetz'] net_name = row['Netz'] radio = QRadioButton(net_name) self.ui.kostenaufteilung_radio_grid.addWidget(radio, i // 2, i % 2) if i == 0: self.net_id = net_id radio.setChecked(True) radio.toggled.connect( lambda b, i=net_id: self.setup_kostenaufteilung(i)) def load_content(self): self.kostenaufteilung = Kostenaufteilung.features( create=True, project=self.project) # initialize empty project 'kostenaufteilungen' with the default ones if len(self.kostenaufteilung) == 0: for default in self.default_kostenaufteilung.features(): rule = self.aufteilungsregeln.get( IDAufteilungsregel=default.IDKostenregel) self.kostenaufteilung.add( Anteil_GSB=rule.Anteil_GSB, Anteil_GEM=rule.Anteil_GEM, Anteil_ALL=rule.Anteil_ALL, IDNetz=default.IDNetz, IDKostenphase=default.IDKostenphase ) self.setup_kostenaufteilung(self.net_id) def calculate_kostentraeger(self): ''' calculations of cost shares ''' job = KostentraegerAuswerten(self.project) def on_close(): if not self.dialog.success: return # the years originate from gesamtkosten calculation diagram = KostentraegerDiagramm(project=self.project, years=GesamtkostenErmitteln.years) diagram.draw() self.dialog = ProgressDialog(job, parent=self.ui, on_close=on_close, auto_close=True) self.dialog.show() def setup_kostenaufteilung(self, net_id): ''' set up parameters to edit shares for a specific type of infrastructure ''' self.net_id = net_id ui_group = self.ui.kostenaufteilung_params_group net_name = self.netzelemente.filter(IDNetz=net_id)[0].Netz ui_group.setTitle(net_name) layout = ui_group.layout() clear_layout(layout) self.params = Params( layout, help_file='infrastruktur_kostenaufteilung.txt') field_names = ['Anteil_GSB', 'Anteil_GEM', 'Anteil_ALL'] labels = ['Kostenanteil der Grunstücksbesitzer/innen', 'Kostenanteil der Gemeinde', 'Netznutzer/innen und Tarifkundschaft'] def preset_changed(c, p): preset = c.get_data() if not preset: return for field_name in field_names: param = self.params.get(f'{p.Kostenphase}_{field_name}') param.input.value = preset[field_name] for i, phase in enumerate(self.kostenphasen): dependency = SumDependency(100) self.params.add(Title(phase.Kostenphase)) feature = self.kostenaufteilung.get( IDKostenphase=phase.IDKostenphase, IDNetz=net_id) preset_combo, options = self.create_presets( net_id, phase.IDKostenphase) param = Param(0, preset_combo, label='Vorschlagswerte') param.hide_in_overview = True self.params.add(param, name=f'{phase.Kostenphase}_presets') for j, field_name in enumerate(field_names): label = labels[j] slider = Slider(maximum=100, lockable=True) param = Param(feature[field_name], slider, label=label, unit='%') self.params.add( param, name=f'{phase.Kostenphase}_{field_name}') dependency.add(param) slider.changed.connect( lambda b, c=preset_combo, o=options: c.set_value(o[0])) if i != len(self.kostenphasen) - 1: self.params.add(Seperator(margin=0)) preset_combo.changed.connect( lambda b, c=preset_combo, p=phase: preset_changed(c, p)) self.params.show(title='Kostenaufteilung festlegen') self.params.changed.connect(lambda: self.save(net_id)) def create_presets(self, net_id: int, phase_id: int) -> tuple: ''' create a combobox with presets for shares for a specific phase and type of infrastructure element ''' applyable_rules = self.applyable_aufteilungsregeln.filter( IDNetz=net_id, IDPhase=phase_id) rules = [] for applyable_rule in applyable_rules: rule_id = applyable_rule.IDAufteilungsregel rule = self.aufteilungsregeln.get(IDAufteilungsregel=rule_id) rules.append(rule) options = (['Aufteilungsregel wählen'] + [rule.Aufteilungsregel for rule in rules]) preset_combo = ComboBox(options, [None] + rules) preset_combo.input.model().item(0).setEnabled(False) return preset_combo, options def save(self, net_id): ''' write the current settings of paramaters for a specific type of infrastructure element ''' for phase in self.kostenphasen: feature = self.kostenaufteilung.get( IDKostenphase=phase.IDKostenphase, IDNetz=net_id) for field_name in ['Anteil_GSB', 'Anteil_GEM', 'Anteil_ALL']: param = self.params[f'{phase.Kostenphase}_{field_name}'] feature[field_name] = param.value feature.save()
class Grundsteuer(QObject): ''' parameters and calculation of property tax ''' changed = pyqtSignal() def __init__(self, project, ui, layer_group): super().__init__() self.project = project self.ui = ui self.layer_group = layer_group self.ui.calc_grundsteuer_button.clicked.connect(self.calculate) self.hebesatz_params = None self.rohmiete_params = None self.sachwert_params = None self.bauvolumen_params = None def load_content(self): self.project_frame = Projektrahmendaten.features( project=self.project)[0] self.gemeinden = Gemeindebilanzen.features(project=self.project) self.grst_settings = GrundsteuerSettings.features(create=True) if len(self.grst_settings) == 0: self.init_grst_base_settings() self.grst_settings = self.grst_settings[0] self.areas = Teilflaechen.features(project=self.project) self.ew_wanderung = EinwohnerWanderung.features() self.svb_wanderung = BeschaeftigtenWanderung.features() self.setup_hebesatz() self.setup_rohmiete() self.setup_sachwert() self.setup_bauvolumen() def init_grst_base_settings(self): ''' initialize the parameters of the base settings ''' gemeinden = self.project.basedata.get_table( 'bkg_gemeinden', 'Basisdaten_deutschland').features() gem = gemeinden.get(AGS=self.project_frame.ags) is_new_bundesland = int(self.project_frame.ags) >= 11000000 attrs = { 'Hebesatz_GrStB': gem.Hebesatz_GrStB, 'is_new_bundesland': is_new_bundesland } startwerte = self.project.basedata.get_table( 'GrSt_Startwerte_Rohmieten_Bodenwert', 'Einnahmen').features() gem_typ_startwerte = startwerte.get(Gemeindetyp=gem.Gemeindetyp) common_fields = set([f.name for f in startwerte.fields()]).intersection( [f.name for f in self.grst_settings.fields()]) for field in common_fields: attrs[field] = gem_typ_startwerte[field] self.grst_settings.add(**attrs) def setup_hebesatz(self): ''' assessment rate parameters ''' if self.hebesatz_params: self.hebesatz_params.close() layout = self.ui.grundsteuer_hebesatz_param_group.layout() clear_layout(layout) self.hebesatz_params = Params( layout, help_file='einnahmen_grundsteuer_hebesatz.txt') self.hebesatz_params.hebesatz = Param( self.grst_settings.Hebesatz_GrStB, SpinBox(maximum=999, step=10), label='Hebesatz GrSt B Projektgemeinde', unit='v.H.') def save(): self.changed.emit() self.grst_settings.Hebesatz_GrStB = \ self.hebesatz_params.hebesatz.value self.grst_settings.save() self.hebesatz_params.show(title='Hebesatz bearbeiten') self.hebesatz_params.changed.connect(save) def setup_rohmiete(self): ''' gross rent parameters ''' tou = self.areas.values('nutzungsart') if self.grst_settings.is_new_bundesland \ or not Nutzungsart.WOHNEN.value in tou: self.ui.grundsteuer_rohmiete_param_group.setVisible(False) return self.ui.grundsteuer_rohmiete_param_group.setVisible(True) if self.rohmiete_params: self.rohmiete_params.close() layout = self.ui.grundsteuer_rohmiete_param_group.layout() clear_layout(layout) self.rohmiete_params = Params( layout, help_file='einnahmen_grundsteuer_rohmieten.txt') self.rohmiete_params.add( Title('Rohmiete 1964 in Euro pro Monat', bold=False)) self.rohmiete_params.efh = Param(self.grst_settings.EFH_Rohmiete / 100, DoubleSpinBox(minimum=0.3, maximum=5, step=0.05), label=f' - Einfamilienhaus', unit='€/m²') self.rohmiete_params.dhh = Param(self.grst_settings.DHH_Rohmiete / 100, DoubleSpinBox(minimum=0.3, maximum=5, step=0.05), label=f' - Doppelhaus', unit='€/m²') self.rohmiete_params.rhw = Param(self.grst_settings.RHW_Rohmiete / 100, DoubleSpinBox(minimum=0.3, maximum=5, step=0.05), label=f' - Reihenhaus', unit='€/m²') self.rohmiete_params.mfh = Param(self.grst_settings.MFH_Rohmiete / 100, DoubleSpinBox(minimum=0.3, maximum=5, step=0.05), label=f' - Mehrfamilienhaus', unit='€/m²') def save(): self.changed.emit() self.grst_settings.EFH_Rohmiete = round( self.rohmiete_params.efh.value * 100) self.grst_settings.DHH_Rohmiete = round( self.rohmiete_params.dhh.value * 100) self.grst_settings.RHW_Rohmiete = round( self.rohmiete_params.rhw.value * 100) self.grst_settings.MFH_Rohmiete = round( self.rohmiete_params.mfh.value * 100) self.grst_settings.save() self.rohmiete_params.show(title='Rohmieten bearbeiten') self.rohmiete_params.changed.connect(save) def setup_sachwert(self): ''' asset value parameters ''' tou = self.areas.values('nutzungsart') if not self.grst_settings.is_new_bundesland\ or not Nutzungsart.WOHNEN.value in tou: self.ui.grundsteuer_sachwert_param_group.setVisible(False) return if self.sachwert_params: self.sachwert_params.close() self.ui.grundsteuer_sachwert_param_group.setVisible(True) layout = self.ui.grundsteuer_sachwert_param_group.layout() clear_layout(layout) self.sachwert_params = Params( layout, help_file='einnahmen_grundsteuer_sachwertverfahren.txt') self.sachwert_params.add(Title('Sachwertverfahren', bold=False)) self.sachwert_params.bodenwert = Param( self.grst_settings.Bodenwert_SWV / 100, DoubleSpinBox(minimum=0.3, maximum=5, step=0.05), label=f' - Bodenwert 1935 pro m²', unit='€/m²') self.sachwert_params.flaeche = Param( self.grst_settings.qm_Grundstueck_pro_WE_EFH, SpinBox(minimum=300, maximum=2000, step=1), label=f' - mittl. Größe Einfamilienhausgrundstücke', unit='m²') def save(): self.changed.emit() self.grst_settings.Bodenwert_SWV = round( self.sachwert_params.bodenwert.value * 100) self.grst_settings.qm_Grundstueck_pro_WE_EFH = \ self.sachwert_params.flaeche.value self.grst_settings.save() self.sachwert_params.show(title='Sachwertverfahren bearbeiten') self.sachwert_params.changed.connect(save) def setup_bauvolumen(self): ''' construction volume parameters ''' tou = self.areas.values('nutzungsart') if not (Nutzungsart.GEWERBE.value in tou or Nutzungsart.EINZELHANDEL.value in tou): self.ui.grundsteuer_bauvolumen_param_group.setVisible(False) # set to 0 as a precaution to not put some old values into # the calculation self.grst_settings.Bueroflaeche = 0 self.grst_settings.Verkaufsraeume = 0 self.grst_settings.save() return self.ui.grundsteuer_bauvolumen_param_group.setVisible(True) if self.bauvolumen_params: self.bauvolumen_params.close() layout = self.ui.grundsteuer_bauvolumen_param_group.layout() clear_layout(layout) self.bauvolumen_params = Params( layout, help_file='einnahmen_grundsteuer_bauvolumen.txt') self.bauvolumen_params.add( Title( 'Gewerbe / Einzelhandel: Voraussichtliches ' 'Bauvolumen\n(Brutto-Grundfläche, BGF)', bold=False)) self.bauvolumen_params.bueroflaeche = Param( self.grst_settings.Bueroflaeche, SpinBox(minimum=0, maximum=99999, step=10), label=f' - Bürofläche', unit='m²') self.bauvolumen_params.verkaufsraeume = Param( self.grst_settings.Verkaufsraeume, SpinBox(minimum=0, maximum=99999, step=10), label=f' - Hallen und Verkaufsräume', unit='m²') def save(): self.changed.emit() self.grst_settings.Bueroflaeche = \ self.bauvolumen_params.bueroflaeche.value self.grst_settings.Verkaufsraeume = \ self.bauvolumen_params.verkaufsraeume.value self.grst_settings.save() self.bauvolumen_params.show( title='Voraussichtliches Bauvolumen bearbeiten') self.bauvolumen_params.changed.connect(save) def calculate(self): ''' calculate the property tax ''' job = GrundsteuerCalculation(self.project) def on_success(r): self.add_layer() self.changed.emit() self.dialog = ProgressDialog(job, parent=self.ui, on_success=on_success, auto_close=True) self.dialog.show() def add_layer(self): ''' show property tax layer ''' self.output = ProjectLayer.from_table(self.gemeinden.table, groupname=self.layer_group) self.output.draw(label='Grundsteuer', style_file='einnahmen_grundsteuer.qml', filter="grundsteuer != 'NULL'", uncheck_siblings=True, redraw=False) self.output.zoom_to() def close(self): if self.hebesatz_params: self.hebesatz_params.close() if self.rohmiete_params: self.rohmiete_params.close() if self.sachwert_params: self.sachwert_params.close() if self.bauvolumen_params: self.bauvolumen_params.close()
class LandUse(Domain): ''' domain-widget calculating and visualizing the residential density and the integrated position of the planning area ''' ui_label = 'Flächeninanspruchnahme' ui_file = 'domain_05-Fl.ui' ui_icon = "images/iconset_mob/20190619_iconset_mob_domain_landuse_1.png" layer_group = ('Wirkungsbereich 4 - Fläche und Ökologie/' 'Flächeninanspruchnahme') def setupUi(self): self.ui.area_combo.currentIndexChanged.connect( lambda: self.change_area()) self.layout = self.ui.parameter_group.layout() self.ui.calculate_density_button.clicked.connect( self.calculate_wohndichte) self.ui.calculate_areadensity_button.clicked.connect( self.calculate_wohnflaechendichte) self.ui.calculate_integration_button.clicked.connect( self.calculate_integration) self.bordertool = LineMapTool( self.ui.draw_border_button, canvas=self.canvas, line_width=3)# , color='#33ccff') self.bordertool.drawn.connect(self.add_border) self.ui.remove_drawing_button.clicked.connect(self.remove_borders) pdf_path = os.path.join( self.settings.HELP_PATH, 'Anleitung_Flaecheninanspruchnahme.pdf') self.ui.manual_button.clicked.connect(lambda: open_file(pdf_path)) def load_content(self): super().load_content() self.gebaeudetypen_base = self.basedata.get_table( 'Wohnen_Gebaeudetypen', 'Definition_Projekt' ) self.areas = Teilflaechen.features() self.residential_areas = Teilflaechen.features().filter( nutzungsart=Nutzungsart.WOHNEN.value) self.wohnbauland_anteile = WohnbaulandAnteile.features(create=True) self.wohnflaeche = WohnflaecheGebaeudetyp.features(create=True) self.borders = GrenzeSiedlungskoerper.features(create=True) self.wohneinheiten = Wohneinheiten.features(create=True) self.rahmendaten = Projektrahmendaten.features()[0] self.wohndichte_kreis = self.basedata.get_table( 'Wohndichte_Wohnflaechendichte_Kreise', 'Flaeche_und_Oekologie') self.wohndichte_raumtyp = self.basedata.get_table( 'Wohndichte_Wohnflaechendichte_RaumTypen', 'Flaeche_und_Oekologie') self.raumtypen = self.basedata.get_table( 'RaumTypen', 'Flaeche_und_Oekologie') self.ui.area_combo.blockSignals(True) self.ui.area_combo.clear() for area in self.residential_areas: self.ui.area_combo.addItem(area.name, area) self.ui.area_combo.blockSignals(False) self.add_border_output() self.change_area() self.area_union = None # ToDo: fix filter side effects self.areas.filter() for area in self.areas: self.area_union = area.geom if not self.area_union \ else self.area_union.combine(area.geom) # buffer to fill gaps self.area_union = self.area_union.buffer(0.1, 6) self.bordertool.set_snap_geometry(self.area_union) def setup_params(self): ''' set up the parameters for editing the mean residential area per appartment and the net share of residential building land in the active area ''' anteil = self.wohnbauland_anteile.get(id_teilflaeche=self.area.id) value = anteil.nettoflaeche if anteil else 85 clear_layout(self.layout) self.params = Params( self.layout, help_file='flaecheninanspruchnahme_wohnbauland_wohnflaeche.txt' ) self.params.add(Title('Anteil Nettowohnbauland')) self.params.nettoflaeche = Param( int(value), Slider(maximum=100), label='Anteil des Nettowohnbaulandes (= Summe aller\n' 'Wohnbaugrundstücke) an der Gesamtfläche der\n' 'ausgewählten Teilfläche', unit='%' ) self.params.add(Seperator()) self.params.add(Title('Durchschnittliche Wohnfläche je Wohnung')) for bt in self.gebaeudetypen_base.features(): param_name = bt.param_we feature = self.wohnflaeche.get(id_gebaeudetyp=bt.id, id_teilflaeche=self.area.id) # default value on first time value = bt.Wohnfl_m2_pro_WE if not feature \ else feature.mean_wohnflaeche self.params.add(Param( value, Slider(maximum=200), label=f'... in {bt.display_name}', unit='m²'), name=param_name ) self.params.changed.connect(self.save) self.params.show( title='Annahmen für Wohnungsdichte und Wohnflächendichte') self.save() def save(self): ''' save the current settings of the parameters ''' feature = self.wohnbauland_anteile.get(id_teilflaeche=self.area.id) # ToDo: get_or_create if not feature: feature = self.wohnbauland_anteile.add(id_teilflaeche=self.area.id) feature.nettoflaeche = self.params.nettoflaeche.value feature.save() for bt in self.gebaeudetypen_base.features(): feature = self.wohnflaeche.get(id_gebaeudetyp=bt.id, id_teilflaeche=self.area.id) if not feature: feature = self.wohnflaeche.add( id_gebaeudetyp=bt.id, id_teilflaeche=self.area.id) feature.mean_wohnflaeche = getattr( self.params, bt.param_we).value feature.save() def change_area(self): ''' set currently selected area as active area ''' self.area = self.ui.area_combo.itemData( self.ui.area_combo.currentIndex()) if not self.area: return output = ProjectLayer.find('Umriss des Plangebiets') if output: layer = output[0].layer() layer.removeSelection() layer.select(self.area.id) self.setup_params() def calculate_wohndichte(self): ''' calculate residential density and show chart of results ''' if not self.area: return # calculation for area anteil = self.wohnbauland_anteile.get(id_teilflaeche=self.area.id) netto_wb = (self.area.geom.area() / 10000) * (anteil.nettoflaeche / 100) wohndichte = round(self.area.we_gesamt / netto_wb, 1) \ if netto_wb > 0 else 0 # get data to compare to kreis, kreisname, kreistyp, typname = self.get_kreis_data() wohndichte_kreis = kreis.Wohndichte_WE_pro_ha_Nettowohnbauland wohndichte_kreistyp = kreistyp.Wohndichte_WE_pro_ha_Nettowohnbauland # chart values = [wohndichte, wohndichte_kreis, wohndichte_kreistyp] labels = [f'Teilfläche "{self.area.name}"', f'Kreis "{kreisname}"', typname] colors = ['#70ad47', '#385723', '#385723'] custom_legend={ f'Teilfläche "{self.area.name}"': '#70ad47', 'Vergleichswerte': '#385723' } chart = BarChart( values, labels=labels, title=f'Teilfläche "{self.area.name}": ' 'Wohneinheiten pro Hektar Nettowohnbauland', y_label='Wohneinheiten pro Hektar Nettowohnbauland', colors=colors, custom_legend=custom_legend ) chart.draw() def calculate_wohnflaechendichte(self): ''' calculate density of living areas and show results as chart ''' if not self.area: return # calculation for area anteil = self.wohnbauland_anteile.get(id_teilflaeche=self.area.id) wohneinheiten = self.wohneinheiten.filter(id_teilflaeche=self.area.id) wohnflaeche = self.wohnflaeche.filter(id_teilflaeche=self.area.id) df_wohneinheiten = wohneinheiten.to_pandas() df_wohnflaeche = wohnflaeche.to_pandas() df_merged = df_wohneinheiten.merge(df_wohnflaeche, on='id_gebaeudetyp') wohnflaeche_gesamt = (df_merged['mean_wohnflaeche'] * df_merged['we']).sum() netto_wb = (self.area.geom.area() / 10000) * (anteil.nettoflaeche / 100) wohnflaechendichte = round(wohnflaeche_gesamt / netto_wb)\ if netto_wb > 0 else 0 # get data to compare to kreis, kreisname, kreistyp, typname = self.get_kreis_data() wohndichte_kreis = \ round(kreis.Wohnflaechendichte_qm_Wohnfl_pro_ha_Nettowohnbauland) wohndichte_kreistyp = \ round(kreistyp.Wohnflaechendichte_qm_Wohnfl_pro_ha_Nettowohnbauland) # chart values = [wohnflaechendichte, wohndichte_kreis, wohndichte_kreistyp] values = [round(v) for v in values] labels = [f'Teilfläche "{self.area.name}"', f'Kreis {kreisname}', typname] colors = ['#70ad47', '#385723', '#385723'] custom_legend={ f'Teilfläche "{self.area.name}"': '#70ad47', 'Vergleichswerte': '#385723' } chart = BarChart( values, labels=labels, title=f'Teilfläche "{self.area.name}": ' 'Wohnfläche (m²) pro Hektar Nettowohnbauland', colors=colors, custom_legend=custom_legend, y_label='Quadratmeter Wohnfläche pro Hektar Nettowohnbauland' ) chart.draw() def get_kreis_data(self) -> tuple: ''' get comparable data of muncipality of planning area ''' ags5 = str(self.rahmendaten.ags)[0:5] kreis = self.wohndichte_kreis.features().get(AGS5=ags5) kreistyp_id = kreis.Siedlungsstruktureller_Kreistyp kreistyp = self.wohndichte_raumtyp.features().get( Siedlungsstruktureller_Kreistyp=kreistyp_id) kreisname = kreis.Kreis_kreisfreie_Stadt.split(',')[0] typname = self.raumtypen.features().get(ID=kreistyp_id).Name return kreis, kreisname, kreistyp, typname def add_border(self, geom): ''' add a geometry to the drawn border ''' self.borders.add(geom=geom) # workaround: layer style is not applied correctly # with empty features -> redraw on first geometry if len(self.borders) == 1: self.add_border_output() self.canvas.refreshAllLayers() def remove_borders(self): ''' remove drawn border ''' self.borders.delete() self.canvas.refreshAllLayers() def add_border_output(self): ''' add layer to visualize drawn border ''' self.output_border = ProjectLayer.from_table( self.borders.table, groupname=self.layer_group, prepend=True) self.output_border.draw( label='Grenze Siedlunskörper', style_file='flaeche_oekologie_grenze_siedlungskoerper.qml' ) def calculate_integration(self): ''' calculate integration shares from drawing and show results in a pie chart ''' area_outer_border = self.area_union.length() drawn_borders = sum([line.geom.length() for line in self.borders]) shared_border = round(100 * drawn_borders / area_outer_border) shared_border = min(shared_border, 100) values = [shared_border, 100 - shared_border] labels = ['Anteil der Plangebietsgrenze,\n' 'die an bestehende Siedlungs-\n' 'flächen angrenzt', 'Anteil der Plangebietsgrenze,\n' 'die nicht an bestehende\n' 'Siedlungsflächen angrenzt',] chart = PieChart(values, labels=labels, title=f'{self.project.name}: Lage zu bestehenden ' 'Siedlungsflächen', decimals=0) chart.draw() def close(self): ''' close parameters and drawing tools ''' # ToDo: implement this in project (collecting all used workscpaces) output = ProjectLayer.find('Umriss des Plangebiets') if output: layer = output[0].layer() layer.removeSelection() if hasattr(self, 'params'): self.params.close() self.bordertool.set_active(False) super().close()
class InfrastructureDrawing: ''' drawing tools and amounts of infrastructure ''' def __init__(self, ui, project, canvas, layer_group): self.ui = ui self.layer_group = layer_group self.project = project self.canvas = canvas self.ui.show_lines_button.clicked.connect( lambda: self.draw_output('line', toggle_if_exists=True)) self.ui.show_lines_button.setCheckable(False) self.ui.show_points_button.clicked.connect( lambda: self.draw_output('point', toggle_if_exists=True)) self.ui.show_points_button.setCheckable(False) self.ui.points_combo.currentIndexChanged.connect( lambda idx: self.toggle_point( self.ui.points_combo.currentData())) self.ui.infrastrukturmengen_button.clicked.connect( self.infrastrukturmengen) self.setup_tools() self.point_params = None self.line_params = None def load_content(self): self.netzelemente = self.project.basedata.get_table( 'Netze_und_Netzelemente', 'Kosten' ).features() self.drawn_lines = ErschliessungsnetzLinienZeichnung.features( create=True) self.line_elements = ErschliessungsnetzLinien.features( create=True) self.points = ErschliessungsnetzPunkte.features(create=True) self.output_lines = ProjectLayer.from_table( self.drawn_lines.table, groupname=self.layer_group, prepend=True) self.output_points = ProjectLayer.from_table( self.points.table, groupname=self.layer_group, prepend=True) self.fill_points_combo() self.setup_line_params() def fill_points_combo(self, select: 'Feature'=None): ''' fill the combobox with all drawn point measures, preselect given point measure ''' self.ui.points_combo.blockSignals(True) self.ui.points_combo.clear() points = [point for point in self.points] points.sort(key=lambda x: x.IDNetzelement) self.ui.points_combo.addItem('nichts ausgewählt') idx = 0 for i, point in enumerate(points): typ = self.netzelemente.get(IDNetzelement=point.IDNetzelement) self.ui.points_combo.addItem( f'{point.bezeichnung} ({typ.Netzelement if typ else "-"})', point ) if select and point.id == select.id: idx = i + 1 if idx: self.ui.points_combo.setCurrentIndex(idx) self.ui.points_combo.blockSignals(False) self.toggle_point() def setup_tools(self): ''' set up the drawing tools ''' self._tools = [] self.line_tools = { self.ui.anliegerstrasse_innere_button: 11, self.ui.sammelstrasse_innere_button: 12, self.ui.anliegerstrasse_aeussere_button: 21, self.ui.sammelstrasse_aeussere_button: 22, self.ui.kanal_trennsystem_button: 31, self.ui.kanal_mischsystem_button: 32, self.ui.kanal_schmutzwasser_button: 33, self.ui.trinkwasserleitung_button: 41, self.ui.stromleitung_button: 51 } for button, net_id in self.line_tools.items(): button.clicked.connect(lambda: self.draw_output('line')) tool = LineMapTool(button, canvas=self.canvas) tool.drawn.connect( lambda geom, i=net_id: self.add_geom(geom, i, geom_typ='line')) self._tools.append(tool) self.select_lines_tool = FeaturePicker( self.ui.select_lines_button, canvas=self.canvas) self.ui.select_lines_button.clicked.connect( lambda: self.draw_output('line')) self.select_lines_tool.feature_picked.connect(self.select_line) self.ui.remove_lines_button.clicked.connect( self.remove_selected_lines) self.ui.remove_drawing_button.clicked.connect( self.remove_drawing) self._tools.append(self.select_lines_tool) self.draw_point_tool = MapClickedTool( self.ui.add_point_button, canvas=self.canvas, target_epsg=self.project.settings.EPSG) self.draw_point_tool.map_clicked.connect(self.add_point) self.select_point_tool = FeaturePicker( self.ui.select_point_button, canvas=self.canvas) self.select_point_tool.feature_picked.connect(self.select_point) self.ui.select_point_button.clicked.connect( lambda: self.draw_output('point')) self.ui.add_point_button.clicked.connect( lambda: self.draw_output('point')) self._tools.append(self.draw_point_tool) def add_geom(self, geom: 'QgsGeometry', net_id: int, geom_typ: str='line') -> 'Feature': ''' add a geometry to the database with the id of network element and the type ('line' or 'point') ''' features = self.drawn_lines if geom_typ == 'line' \ else self.points typ = self.netzelemente.get(IDNetzelement=net_id) feature = features.add(IDNetzelement=net_id, IDNetz=typ.IDNetz if typ else 0, geom=geom) if geom_typ == 'line': feature.length = geom.length() feature.save() # workaround: if layer had no data before it needs to be readded to show # sth, refresh doesn't work if len(features) == 1: self.draw_output(geom_typ, redraw=True) self.canvas.refreshAllLayers() return feature def draw_output(self, geom_typ: str='line', redraw: bool=False, toggle_if_exists: bool=False): ''' add either the point measures or line measures as a layer depending on given geom_type ('line' or 'point') ''' label = 'Erschließungsnetz' if geom_typ == 'point': label += ' - punktuelle Maßnahmen' output = self.output_lines if geom_typ == 'line' else self.output_points style = 'kosten_erschliessungsnetze_{}elemente.qml'.format( 'linien' if geom_typ == 'line' else 'punkt') output.draw(label=label, style_file=style, redraw=redraw, toggle_if_exists=toggle_if_exists) tool = self.select_lines_tool if geom_typ == 'line' \ else self.select_point_tool tool.set_layer(output.layer) def select_line(self, feature: 'Feature'): ''' select the line measure in output layer ''' layer = self.output_lines.layer selected = [f.id() for f in layer.selectedFeatures()] if feature.id() not in selected: layer.select(feature.id()) else: layer.removeSelection() layer.selectByIds([fid for fid in selected if fid != feature.id()]) def remove_selected_lines(self): ''' remove all line measures selected in the line output layer ''' layer = self.output_lines.layer if not layer: return for qf in layer.selectedFeatures(): feat = self.drawn_lines.get(id=qf.id()) if feat: feat.delete() self.canvas.refreshAllLayers() def remove_drawing(self): ''' remove all line measures ''' reply = QMessageBox.question( self.ui, 'Zeichnung löschen', f'Sollen alle gezeichneten Linienelemente gelöscht werden?', QMessageBox.Yes, QMessageBox.No ) if reply == QMessageBox.No: return self.drawn_lines.delete() self.canvas.refreshAllLayers() def add_point(self, geom: 'QgsGeometry'): ''' add a point measure ''' point = self.add_geom(geom, 0, geom_typ='point') point.bezeichnung = 'unbenannte Maßnahme' point.save() self.fill_points_combo(select=point) def remove_point(self): ''' remove point measure currently selected in the point combobox ''' point = self.ui.points_combo.currentData() if not point: return reply = QMessageBox.question( self.ui, 'Maßnahme entfernen', f'Soll die punktuelle Maßnahme "{point.bezeichnung}" ' 'entfernt werden?\n', QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: point.delete() self.fill_points_combo() def select_point(self, feature: 'Feature'): ''' select the point measure in output layer ''' if not self.output_points.layer: return self.output_points.layer.removeSelection() self.output_points.layer.select(feature.id()) fid = feature.id() for idx in range(len(self.ui.points_combo)): point = self.ui.points_combo.itemData(idx) if point and fid == point.id: break self.ui.points_combo.setCurrentIndex(idx) def toggle_point(self, point: 'Feature'=None): ''' toggle selection of point measure, draws layers and groups for displaying the point measure ''' if self.output_points.layer: self.output_points.layer.removeSelection() if not point: point = self.ui.points_combo.currentData() self.setup_point_params(point) if not point: self.ui.point_parameter_group.setVisible(False) return self.ui.point_parameter_group.setVisible(True) self.draw_output('point') self.output_points.layer.select(point.id) self.setup_point_params(point) def setup_point_params(self, point: 'Feature'): ''' set up the parameters in the UI to edit the given point measure ''' if self.point_params: self.point_params.close() ui_group = self.ui.point_parameter_group layout = ui_group.layout() clear_layout(layout) if not point: return self.point_params = Params( layout, help_file='infrastruktur_punktmassnahme.txt') self.point_params.bezeichnung = Param( point.bezeichnung, LineEdit(width=300), label='Bezeichnung') punktelemente = list(self.netzelemente.filter(Typ='Punkt')) type_names = [p.Netzelement for p in punktelemente] typ = self.netzelemente.get(IDNetzelement=point.IDNetzelement) type_combo = ComboBox( ['nicht gesetzt'] + type_names, data=[None] + list(punktelemente), width=300) self.point_params.typ = Param( typ.Netzelement if typ else 'nicht gesetzt', type_combo, label='Erschließungsnetz' ) self.point_params.add(Seperator(margin=0)) self.point_params.euro_EH = Param( point.Euro_EH, DoubleSpinBox(), unit='€', label='Kosten der erstmaligen Herstellung' ) self.point_params.euro_BU = Param( point.Cent_BU / 100, DoubleSpinBox(), unit='€', label='Jährliche Kosten für Betrieb und Unterhaltung' ) self.point_params.euro_EN = Param( point.Euro_EN, DoubleSpinBox(), unit='€', label='Erneuerungskosten nach Ablauf der Lebensdauer' ) self.point_params.lebensdauer = Param( point.Lebensdauer, SpinBox(minimum=1, maximum=1000), label='Technische oder wirtschaftliche \n' 'Lebensdauer bis zur Erneuerung', unit='Jahr(e)' ) def save(): point.bezeichnung = self.point_params.bezeichnung.value typ = type_combo.get_data() point.IDNetzelement = typ.IDNetzelement if typ else 0 point.IDNetz = typ.IDNetz if typ else 0 point.Lebensdauer = self.point_params.lebensdauer.value point.Euro_EH = self.point_params.euro_EH.value point.Euro_EN = self.point_params.euro_EN.value point.Cent_BU = self.point_params.euro_BU.value * 100 point.save() # lazy way to update the combo box self.fill_points_combo(select=point) self.point_params.show() self.point_params.changed.connect(save) last_row = self.point_params.layout.children()[-1] button = QPushButton() icon_path = 'iconset_mob/20190619_iconset_mob_delete_1.png' icon = QIcon(os.path.join(self.project.settings.IMAGE_PATH, icon_path)) button.setText('Maßnahme entfernen') button.setIcon(icon) button.setToolTip( '<p><span style=" font-weight:600;">Maßnahme entfernen</span>' '</p><p>Löscht die aktuell gewählte Maßnahme. ' '<br/>Dieser Schritt kann nicht rückgängig gemacht werden. </p>') last_row.insertWidget(0, button) button.clicked.connect(self.remove_point) def init_lines(self): ''' initialize the line table in the database ''' line_elements = self.netzelemente.filter(Typ='Linie') df_line_elements = line_elements.to_pandas() del(df_line_elements['fid']) self.line_elements.update_pandas(df_line_elements) # reset filter ToDo: fix filtering self.netzelemente.filter() def setup_line_params(self): ''' set up the parameters to edit the lengths of the line measures ''' layout = self.ui.mengen_params_group.layout() clear_layout(layout) if len(self.line_elements) == 0: self.init_lines() self.line_params = Params( layout, help_file='infrastruktur_linienelemente.txt') for element in self.line_elements: param = Param(int(element.length), Slider(maximum=10000), label=element.Netzelement, unit='m') self.line_params.add( param, name=f'netzelement_{element.IDNetzelement}') def save(): for element in self.line_elements: param = self.line_params[f'netzelement_{element.IDNetzelement}'] element.length = param.value element.save() self.line_params.show(title=self.ui.mengen_params_group.title()) self.line_params.changed.connect(save) last_row = self.line_params.layout.children()[-1] button = QPushButton() button.setText('aus Zeichnung übernehmen') last_row.insertWidget(0, button) button.clicked.connect(self.apply_drawing) def apply_drawing(self): ''' analyse the drawing and apply the lengths of line measures to the line parameters ''' df_drawing = self.drawn_lines.to_pandas() for element in self.line_elements: param = self.line_params[f'netzelement_{element.IDNetzelement}'] drawn_elements = df_drawing[ df_drawing['IDNetzelement']==element.IDNetzelement] length = int(drawn_elements['length'].sum()) param.value = length element.length = length element.save() def infrastrukturmengen(self): ''' show the infrastructure amounts as diagrams ''' diagram = NetzlaengenDiagramm(project=self.project) diagram.draw() diagram = MassnahmenKostenDiagramm(project=self.project) diagram.draw(offset_x=100, offset_y=100) def close(self): ''' close parameters and tools ''' for tool in self._tools: tool.set_active(False) if self.point_params: self.point_params.close() if self.line_params: self.line_params.close()
class EditPlanfallMarkets(EditMarkets): ''' planfall (scenario) market control ''' layer_filter = 'id_betriebstyp_nullfall = 0' layer_style = 'standortkonkurrenz_geplante_maerkte.qml' filter_args = {'id_betriebstyp_nullfall': 0} market_label = 'Geplante Märkte' suffix = 'planfall' def setup_params(self, market): ''' set up the parameters to edit the given scenario market ''' # ToDo: that's mostly the same as in EditNullfallMarkets, # might be merged if self.params: self.params.close() layout = self.param_group.layout() clear_layout(layout) if not market: self.param_group.setVisible(False) return self.param_group.setVisible(True) self.params = Params( layout, help_file='standortkonkurrenz_geplante_maerkte.txt') self.params.name = Param(market.name, LineEdit(width=300), label='Name') self.params.add(Seperator(margin=0)) # 'nicht aufgeführt' (kette 0) is first, rest alphabetical order ketten = sorted(self.ketten, key=lambda k: k.name.lower() if k.name != 'nicht aufgeführt' else '') chain_ids = [typ.id_kette for typ in ketten] chain_labels = [kette.name for kette in ketten] chain_combo = ComboBox(chain_labels, data=chain_ids, width=300) value = self.ketten.get(id_kette=market.id_kette).name self.params.kette = Param(value, chain_combo, label='Anbieter') type_ids = [typ.id_betriebstyp for typ in self.typen] type_labels = [self.detailed_type_label(i) for i in type_ids if i > 0] type_combo = ComboBox(type_labels, data=type_ids, width=300) self.params.typ = Param(self.detailed_type_label( market.id_betriebstyp_planfall), type_combo, label='Neue Märkte', value_label=market.betriebstyp_planfall) def save(): market.name = self.params.name.value id_bt = type_combo.get_data() bt = self.typen.get(id_betriebstyp=id_bt).name market.id_betriebstyp_planfall = id_bt market.betriebstyp_planfall = bt market.id_kette = chain_combo.get_data() market.kette = self.ketten.get(id_kette=market.id_kette).name vkfl = self.market_tool.betriebstyp_to_vkfl( market.id_betriebstyp_planfall, market.id_kette) market.vkfl_planfall = vkfl market.save() self.canvas.refreshAllLayers() # lazy way to update the combo box self.fill_combo(select=market) self.changed.emit() self.params.show(title='Neuen Markt im Planfall bearbeiten') self.params.changed.connect(save) # markets on project areas can not be deleted if market.id_teilflaeche < 0: last_row = self.params.layout.children()[-1] button = QPushButton() icon_path = 'iconset_mob/20190619_iconset_mob_delete_1.png' icon = QIcon( os.path.join(self.project.settings.IMAGE_PATH, icon_path)) button.setText('Markt entfernen') button.setIcon(icon) button.setToolTip( '<p><span style=" font-weight:600;">Markt entfernen</span>' '</p><p>Löscht den aktuell gewählten Markt. ' '<br/>Dieser Schritt kann nicht rückgängig gemacht ' 'werden. </p>') last_row.insertWidget(0, button) button.clicked.connect(lambda: self.remove_market(market)) def add_market(self, geom, name='unbenannter geplanter Markt'): ''' add a scenario market to the database ''' market = self.markets.add( name=name, id_betriebstyp_nullfall=0, betriebstyp_nullfall=self.typen.get(id_betriebstyp=0).name, id_betriebstyp_planfall=1, betriebstyp_planfall=self.typen.get(id_betriebstyp=1).name, id_kette=0, kette=self.ketten.get(id_kette=0).name, geom=geom) crs = QgsCoordinateReferenceSystem( f'EPSG:{self.project.settings.EPSG}') ags = get_ags([market], self.basedata, source_crs=crs)[0] market.AGS = ags.AGS vkfl = self.market_tool.betriebstyp_to_vkfl( market.id_betriebstyp_planfall, market.id_kette) market.vkfl_planfall = vkfl market.save() self.changed.emit() # workaround: if layer had no data before it needs to be readded to show # sth, refresh doesn't work if len(self.markets) == 1: self.add_layer() self.canvas.refreshAllLayers() self.fill_combo(select=market)
class ChangeMarkets(EditMarkets): ''' control markets already existing in status quo but are changed in the scenario ''' layer_filter = ('id_betriebstyp_nullfall != id_betriebstyp_planfall ' 'and id_betriebstyp_nullfall > 0') layer_style = 'standortkonkurrenz_veraenderte_maerkte.qml' filter_args = {'id_betriebstyp_nullfall__gt': 0} market_label = 'Veränderte Märkte im Bestand' suffix = 'nullfall' show_change = True def __init__(self, nullfall_edit, combobox, select_button, param_group, canvas, project, remove_button=None, layer_group=''): super().__init__(combobox, select_button, param_group, canvas, project, remove_button=remove_button, layer_group=layer_group) self.nullfall_edit = nullfall_edit def add_layer(self, zoom_to=False, toggle_if_exists=False): ''' add the nullfall layer in addition to layer showing the changed markets ''' super().add_layer(toggle_if_exists=toggle_if_exists) self.nullfall_edit.add_layer(zoom_to=zoom_to, toggle_if_exists=toggle_if_exists) self.select_tool.set_layer(self.nullfall_edit.output.layer) def setup_params(self, market): ''' set up the parameters to change attributes of the given status quo market in the scenario ''' if self.params: self.params.close() layout = self.param_group.layout() clear_layout(layout) if not market: self.param_group.setVisible(False) return self.param_group.setVisible(True) self.params = Params( layout, help_file='standortkonkurrenz_veraenderte_maerkte.txt') self.params.name = Param(market.name, label='Name') self.params.add(Seperator(margin=0)) self.params.kette = Param(market.kette, label='Anbieter') self.params.nullfall = Param( market.betriebstyp_nullfall, label='Betriebstyp im Nullfall', ) closed_label = 'Markt geschlossen' type_ids = [typ.id_betriebstyp for typ in self.typen] type_labels = [] for tid in type_ids: type_labels.append(closed_label if tid == 0 else self.detailed_type_label(tid)) type_combo = ComboBox(type_labels, data=type_ids, width=300) typ = market.id_betriebstyp_planfall self.params.planfall = Param( closed_label if typ == 0 else self.detailed_type_label(typ), type_combo, label='Betriebstyp im Planfall', value_label=closed_label if typ == 0 else market.betriebstyp_planfall) close_check = Checkbox() self.params.gets_closed = Param(typ == 0, close_check, label='Markt im Planfall schließen') self.params.gets_closed.hide_in_overview = True def closed_toggled(checked): if checked: type_combo.set_value(closed_label) close_check.changed.connect(closed_toggled) def type_changed(value): close_check.set_value(value == closed_label) type_combo.changed.connect(type_changed) def save(): id_bt = type_combo.get_data() bt = self.typen.get(id_betriebstyp=id_bt).name market.id_betriebstyp_planfall = id_bt market.betriebstyp_planfall = bt vkfl = self.market_tool.betriebstyp_to_vkfl( market.id_betriebstyp_planfall, market.id_kette) market.vkfl_planfall = vkfl market.save() self.canvas.refreshAllLayers() # lazy way to update the combo box self.fill_combo(select=market) self.changed.emit() self.params.show(title='Markt im Planfall verändern') self.params.changed.connect(save) def remove_markets(self): ''' reset all changes made to status quo markets in the scenario ''' reply = QMessageBox.question( self.param_group, f'Veränderungen zurücksetzen', 'Möchten Sie alle Veränderungen der bestehenden Märkte ' 'zurücksetzen?', QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: for market in self.markets.filter(**self.filter_args): market.id_betriebstyp_planfall = market.id_betriebstyp_nullfall market.betriebstyp_planfall = market.betriebstyp_nullfall market.save() self.canvas.refreshAllLayers() self.fill_combo()
class Traffic(Domain): ''' domain-widget for calculating and visualizing the additional traffic load ''' ui_label = 'Verkehr im Umfeld' ui_file = 'domain_03-ViU.ui' ui_icon = "images/iconset_mob/20190619_iconset_mob_domain_traffic_6.png" layer_group = "Wirkungsbereich 3 - Verkehr im Umfeld" @classmethod def reset(cls, project=None): ''' remove existing results ''' if not project: project = cls.project_manager.active_project RouteLinks.features(project=project, create=True).table.truncate() Itineraries.features(project=project, create=True).table.truncate() TrafficLoadLinks.features(project=project, create=True).table.truncate() Ways.features(project=project, create=True).table.truncate() def setupUi(self): self.node_output = None self.itinerary_output = None self.ways_params = None self.weight_params = None self.node_params = None self.select_tool = FeaturePicker(self.ui.select_transfer_node_button, canvas=self.canvas) self.select_tool.feature_picked.connect(self.select_node) self.drag_tool = FeatureDragger(self.ui.move_transfer_node_button, canvas=self.canvas) self.drag_tool.feature_dragged.connect(self.move_node) self.ui.select_transfer_node_button.clicked.connect( lambda: self.draw_nodes()) self.ui.move_transfer_node_button.clicked.connect( lambda: self.draw_nodes()) self.ui.transfer_node_combo.currentIndexChanged.connect( lambda idx: self.toggle_node(self.ui.transfer_node_combo. currentData(), center_on_point=True)) self.ui.transfer_node_parameter_group.setVisible(False) self.add_node_tool = MapClickedTool( self.ui.add_transfer_node_button, canvas=self.canvas, target_epsg=self.project.settings.EPSG) self.add_node_tool.map_clicked.connect(self.add_node) self.ui.add_transfer_node_button.clicked.connect( lambda: self.draw_nodes()) self.ui.calc_transfer_nodes_button.clicked.connect( self.calculate_nodes) self.ui.calculate_traffic_button.clicked.connect( self.calculate_traffic) self.ui.remove_transfer_nodes_button.clicked.connect(self.remove_nodes) pdf_path = os.path.join(self.settings.HELP_PATH, 'Anleitung_Verkehr_im_Umfeld.pdf') self.ui.manual_button.clicked.connect(lambda: open_file(pdf_path)) def load_content(self): super().load_content() output = ProjectLayer.find('Projektdefinition') if output: output[0].setItemVisibilityChecked(True) self.areas = Teilflaechen.features(project=self.project) self.links = RouteLinks.features(project=self.project, create=True) self.traffic_load = TrafficLoadLinks.features(project=self.project, create=True) self.transfer_nodes = TransferNodes.features(project=self.project, create=True) self.itineraries = Itineraries.features(project=self.project, create=True) self.ways = Ways.features(project=self.project, create=True) self.connectors = Connectors.features(project=self.project, create=True) self.draw_nodes() self.fill_node_combo() self.setup_ways() self.setup_weights() def fill_node_combo(self, select: 'Feature' = None): ''' set up node selection ''' self.ui.transfer_node_combo.blockSignals(True) self.ui.transfer_node_combo.clear() self.ui.transfer_node_combo.addItem('nichts ausgewählt') idx = 0 for i, node in enumerate(self.transfer_nodes): self.ui.transfer_node_combo.addItem(node.name, node) if select and node.id == select.id: idx = i + 1 if idx: self.ui.transfer_node_combo.setCurrentIndex(idx) self.ui.transfer_node_combo.blockSignals(False) self.toggle_node(self.ui.transfer_node_combo.currentData()) def toggle_node(self, node, center_on_point=False): ''' set up given transfer node ''' if node and self.node_output and self.node_output.layer: self.node_output.layer.removeSelection() self.node_output.layer.select(node.id) if center_on_point: center_canvas(self.canvas, node.geom.asPoint(), self.node_output.layer.crs()) self.setup_node_params(node) def add_node(self, geom, name=None): ''' add a transfer node to the database ''' if self.itinerary_output: self.itinerary_output.remove() # equal share of weight for new node weight = 100 / (len(self.transfer_nodes) + 1) # reduce weights of other nodes by weight of new node d = (100 - weight) / 100 for node in self.transfer_nodes: node.weight = round(node.weight * d) node.save() # distribute delta caused by rounding errors weight = 100 - sum(self.transfer_nodes.values('weight')) node = self.transfer_nodes.add(name=name, geom=geom, weight=weight) if not name: node.name = f'Herkunfts-/Zielpunkt {node.id + 1}' node.save() # workaround: if layer had no data before it needs to be readded to show # sth, refresh doesn't work if len(self.transfer_nodes) == 1: self.draw_nodes() self.canvas.refreshAllLayers() self.fill_node_combo(select=node) self.links.table.truncate() self.traffic_load.table.truncate() self.setup_weights() def move_node(self, feature_id: int, point: 'QgsPointXY'): ''' move transfer node with given id to given point ''' node = self.transfer_nodes.get(id=feature_id) node.geom = point node.save() self.drag_tool.reset() self.canvas.refreshAllLayers() self.links.table.truncate() self.traffic_load.table.truncate() self.set_status() def select_node(self, feature): ''' select and highlight given transfer node feature ''' if not self.node_output or not self.node_output.layer: return self.node_output.layer.removeSelection() self.node_output.layer.select(feature.id()) fid = feature.id() for idx in range(len(self.ui.transfer_node_combo)): node = self.ui.transfer_node_combo.itemData(idx) if node and fid == node.id: break self.ui.transfer_node_combo.setCurrentIndex(idx) def setup_node_params(self, node): ''' set up the parameters of a single transfer node ''' if self.node_params: self.node_params.close() layout = self.ui.transfer_node_parameter_group.layout() clear_layout(layout) if not node: self.ui.transfer_node_parameter_group.setVisible(False) return self.ui.transfer_node_parameter_group.setVisible(True) #self.ui.transfer_node_parameter_group.setTitle(node.name) self.node_params = Params(layout, help_file='verkehr_knoten.txt') self.node_params.name = Param(node.name, LineEdit(width=300), label='Name') def save(): node.name = self.node_params.name.value #self.ui.transfer_node_parameter_group.setTitle(node.name) node.save() self.canvas.refreshAllLayers() # lazy way to update the combo box self.fill_node_combo(select=node) self.setup_weights() self.node_params.show(title='Herkunfts-/Zielpunkt bearbeiten') self.node_params.changed.connect(save) last_row = self.node_params.layout.children()[-1] button = QPushButton() icon_path = 'iconset_mob/20190619_iconset_mob_delete_1.png' icon = QIcon(os.path.join(self.project.settings.IMAGE_PATH, icon_path)) button.setText('Punkt entfernen') button.setIcon(icon) button.setToolTip( '<p><span style=" font-weight:600;">' 'Herkunfts-/Zielpunkt entfernen</span>' '</p><p>Löscht den aktuell gewählten Herkunfts-/Zielpunkt. ' '<br/>Dieser Schritt kann nicht rückgängig gemacht werden. </p>') last_row.insertWidget(0, button) button.clicked.connect(lambda: self.remove_node(node)) def remove_node(self, node): ''' remove given transfer node ''' if not node: return reply = QMessageBox.question( self.ui.transfer_node_parameter_group, 'Herkunfts-/Zielpunkt entfernen', f'Soll der Punkt "{node.name}" ' 'entfernt werden?', QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: if self.itinerary_output: self.itinerary_output.remove() # workaround: refresg node if weight was changed in the meantime node = self.transfer_nodes.get(id=node.id) # distribute removed weight d = (100 - node.weight) / 100 node.delete() if d > 0: for node in self.transfer_nodes: node.weight = round(node.weight / d) node.save() # distribute delta caused by rounding errors delta = 100 - sum(self.transfer_nodes.values('weight')) first = self.transfer_nodes[0] first.weight = max(first.weight + delta, 0) self.links.table.truncate() self.traffic_load.table.truncate() self.canvas.refreshAllLayers() self.fill_node_combo() self.setup_weights() def remove_nodes(self): ''' remove all transfer nodes ''' if len(self.transfer_nodes) == 0: return reply = QMessageBox.question( self.ui.transfer_node_parameter_group, 'Herkunfts-/Zielpunkte entfernen', f'Sollen alle Herkunfts-/Zielpunkte entfernt werden?', QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: if self.itinerary_output: self.itinerary_output.remove() self.transfer_nodes.table.truncate() self.links.table.truncate() self.traffic_load.table.truncate() self.canvas.refreshAllLayers() self.fill_node_combo() self.setup_weights() def setup_weights(self): ''' set up the parameters to weight the transfer nodes ''' self.set_status() if self.weight_params: self.weight_params.close() layout = self.ui.weights_group.layout() clear_layout(layout) self.weight_params = Params(parent=layout, button_label='Gewichtungen verändern', help_file='verkehr_gewichtungen.txt') dependency = SumDependency(100) for node in self.transfer_nodes: perc = round(node.weight) param = Param(perc, Slider(maximum=100, lockable=True), label=node.name, unit='%') self.weight_params.add(param, name=node.name) dependency.add(param) def save(): for node in self.transfer_nodes: node.weight = self.weight_params[node.name].value node.save() self.traffic_load.table.truncate() self.canvas.refreshAllLayers() self.set_status() self.weight_params.changed.connect(save) self.weight_params.show() def setup_ways(self): ''' set up paramaters to set ways ''' if self.ways_params: self.ways_params.close() if len(self.ways) == 0: self.calculate_ways() layout = self.ui.ways_group.layout() clear_layout(layout) self.ways_params = Params(parent=layout, button_label='Annahmen verändern', help_file='verkehr_wege.txt') for i, way in enumerate(self.ways): name = Nutzungsart(way.nutzungsart).name.capitalize() self.ways_params.add(Title(name, fontsize=8)) self.ways_params[f'{name}_gesamt'] = Param( way.wege_gesamt, SpinBox(), label='Gesamtanzahl der Wege pro Werktag (Hin- und Rückwege)') self.ways_params[f'{name}_miv'] = Param( way.miv_anteil, SpinBox(maximum=100), label='Anteil der von Pkw-Fahrenden gefahrenen Wegen', unit='%') if i != len(self.ways) - 1: self.ways_params.add(Seperator(margin=0)) def save(): for way in self.ways: name = Nutzungsart(way.nutzungsart).name.capitalize() way.miv_anteil = self.ways_params[f'{name}_miv'].value way.wege_gesamt = self.ways_params[f'{name}_gesamt'].value way.save() self.traffic_load.table.truncate() self.canvas.refreshAllLayers() self.set_status() self.ways_params.changed.connect(save) self.ways_params.show() def calculate_ways(self): ''' calculate and store the additional ways per type of use of the areas ''' # get ways per type of use ways_tou = {} self.ways.table.truncate() for area in self.areas: if area.nutzungsart == 0: continue entry = ways_tou.get(area.nutzungsart) if not entry: entry = ways_tou[area.nutzungsart] = [0, 0] entry[0] += area.wege_gesamt entry[1] += area.wege_miv for tou, (wege_gesamt, wege_miv) in ways_tou.items(): if wege_gesamt == 0: continue miv_anteil = round(100 * wege_miv / wege_gesamt) # \ # if wege_gesamt > 0 else 0 self.ways.add(wege_gesamt=wege_gesamt, nutzungsart=tou, miv_anteil=miv_anteil) def set_status(self): ''' sets visibility and active status of certain ui elements depending on current state of calculations ''' has_nodes = len(self.transfer_nodes) != 0 calc_done = len(self.traffic_load) != 0 self.ui.calculate_traffic_button.setEnabled(has_nodes) self.ui.settings_frame.setVisible(has_nodes) button_text = 'Straßenverkehrsbelastung anzeigen' if calc_done \ else 'Straßenverkehrsbelastung berechnen' self.ui.calculate_traffic_button.setText(button_text) def calculate_nodes(self): ''' calculate the traffic nodes. resets all nodes and results ''' if len(self.transfer_nodes) > 0: reply = QMessageBox.question( self.ui.transfer_node_parameter_group, 'Herkunfts-/Zielpunkt entfernen', 'Die Berechnung der Herkunfts-/Zielpunkte setzt alle ' 'vorhandenen Punkte und Berechnungen zurück.\n' 'Wollen Sie die Berechnung fortsetzen?', QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.No: return self.transfer_nodes.table.truncate() self.links.table.truncate() self.traffic_load.table.truncate() distance = self.ui.distance_input.value() job = TransferNodeCalculation(self.project, distance=distance) def on_success(res): self.draw_nodes() self.draw_itineraries(zoom_to=True) def on_close(): self.fill_node_combo() self.setup_weights() self.canvas.refreshAllLayers() dialog = ProgressDialog(job, parent=self.ui, on_success=on_success, on_close=on_close) dialog.show() def calculate_traffic(self): ''' calculate the traffic load ''' max_dist = getattr(self.settings, 'MAX_AREA_DISTANCE', None) points = [c.geom.asPoint() for c in self.connectors] xs = [p.x() for p in points] ys = [p.y() for p in points] if max_dist is not None: distances = [] for i in range(len(points)): for j in range(i): dist = np.linalg.norm( np.subtract((xs[i], ys[i]), (xs[j], ys[j]))) distances.append(dist) if distances and max(distances) > 2 * max_dist: QMessageBox.warning( self.ui, 'Hinweis', 'Der Abstand zwischen den Anbindungspunkten ist zu groß. ' 'Er darf für die Schätzung der Verkehrsbelastung jeweils ' f'nicht größer als {2 * max_dist} m sein!') return if len(self.traffic_load) == 0: recalculate = len(self.links) == 0 job = Routing(self.project, recalculate=recalculate) def on_success(res): if self.itinerary_output: self.itinerary_output.remove() self.draw_nodes() self.draw_traffic(zoom_to=True) self.setup_weights() dialog = ProgressDialog(job, parent=self.ui, on_success=on_success) dialog.show() else: self.draw_traffic(zoom_to=True, toggle_if_exists=True) def draw_nodes(self, zoom_to=False): ''' show layer visualizing the transfer nodes ''' self.node_output = ProjectLayer.from_table(self.transfer_nodes.table, groupname=self.layer_group) self.node_output.draw(label='Herkunfts-/Zielpunkte', style_file='verkehr_zielpunkte.qml', prepend=True) self.select_tool.set_layer(self.node_output.layer) self.drag_tool.set_layer(self.node_output.layer) if zoom_to: self.node_output.zoom_to() def draw_itineraries(self, zoom_to=False): ''' show layer visualizing the itineraries used for determining the transfer nodes ''' self.itinerary_output = ProjectLayer.from_table( self.itineraries.table, groupname=self.layer_group) self.itinerary_output.draw(label='Zulaufstrecken', expanded=False, style_file='verkehr_kuerzeste_Wege.qml') if zoom_to: self.itinerary_output.zoom_to() def draw_traffic(self, zoom_to=False, toggle_if_exists=False): ''' show layer visualizing the additional traffic load ''' output = ProjectLayer.from_table(self.traffic_load.table, groupname=self.layer_group) output.draw(label='Zusätzliche PKW-Fahrten', style_file='verkehr_links_zusaetzliche_PKW-Fahrten.qml', filter=f'trips > 0', toggle_if_exists=toggle_if_exists, redraw=not toggle_if_exists) if zoom_to and output.tree_layer.isVisible(): output.zoom_to() def close(self): if self.node_params: self.node_params.close() if self.ways_params: self.ways_params.close() if self.weight_params: self.weight_params.close() self.select_tool.set_active(False) self.drag_tool.set_active(False) self.add_node_tool.set_active(False) super().close()
class ProjectDefinitions(Domain): ''' domain-widget for the basic setup of the project areas (type of use etc.) ''' ui_label = 'Projektdefinition' ui_file = 'definitions.ui' layer_group = 'Projektdefinition' def setupUi(self): ''' set up possible user interactions and the sub-domains ''' self.tou_output = None self.ui.area_combo.currentIndexChanged.connect( lambda: self.change_area()) self.connector_setter = TrafficConnectors( self.ui, self.canvas, self.project) type_layout = self.ui.type_parameter_group.layout() # ToDo: somehow generate this (resp. assign index) from the enum # preferably store labels and id in a base table self.types = [ ('Nutzung noch nicht definiert', None), ('Wohnen', Wohnen(self.project, type_layout)), ('Gewerbe', Gewerbe(self.project, type_layout)), ('Einzelhandel', Einzelhandel(self.project, type_layout)) ] self.typ = None pdf_path = os.path.join( self.settings.HELP_PATH, 'Anleitung_Projektdefinition.pdf') self.ui.manual_button.clicked.connect(lambda: open_file(pdf_path)) def load_content(self): ''' load the areas and data ''' super().load_content() self.areas = Teilflaechen.features() self.connectors = Connectors.features() self.projektrahmendaten = Projektrahmendaten.features()[0] created = self.projektrahmendaten.datum.replace('"', '') self.ui.date_label.setText(created or '-') version = self.projektrahmendaten.basisdaten_version version_date = self.projektrahmendaten.basisdaten_datum self.ui.basedata_label.setText( f'v{version} (Stand: {version_date})' if version else '-') self.ui.area_combo.blockSignals(True) self.ui.area_combo.clear() for area in self.areas: tou_label = self.types[area.nutzungsart][0] self.ui.area_combo.addItem(f'{area.name} ({tou_label})', area) self.ui.area_combo.blockSignals(False) self.show_outputs() self.change_area() def change_area(self): ''' change selected area and reload parameters ''' self.area = self.ui.area_combo.itemData( self.ui.area_combo.currentIndex()) layer = self.tou_output.layer if layer: layer.removeSelection() layer.select(self.area.id) self.connector_setter.load_content(self.area) self.setup_type() self.setup_type_params() def show_outputs(self, zoom=False): ''' show the definition layers (planning areas with type of use) ''' table = Teilflaechen.get_table() self.tou_output = ProjectLayer.from_table( table, groupname=self.layer_group) self.tou_output.draw(label='Nutzungen des Plangebiets', style_file='definitions.qml', redraw=False) if zoom: self.tou_output.zoom_to() output = ProjectLayer.from_table(table, groupname='Hintergrund', prepend=False) output.draw(label='Umriss des Plangebiets', style_file='areas.qml') self.connector_setter.show_connectors() def setup_type(self): ''' set up basic parameters (name, type of use) ''' layout = self.ui.parameter_group.layout() clear_layout(layout) self.params = Params(layout, help_file='definitionen_flaechen.txt', ) self.params.name = Param(self.area.name, LineEdit(width=300), label='Name') self.params.add(Seperator(margin=0)) ha = round(self.area.geom.area()) / 10000 self.area.area = ha self.params.area = Param(ha, label='Größe', unit='ha') self.params.typ = Param( self.types[self.area.nutzungsart][0], ComboBox([t[0] for t in self.types], width=300), label='Nutzungsart' ) # user changed type of use def type_changed(): name = self.params.name.value type_labels = [t[0] for t in self.types] tou_id = type_labels.index(self.params.typ.value) self.area.nutzungsart = tou_id tou_label = self.types[tou_id][0] self.ui.area_combo.setItemText( self.ui.area_combo.currentIndex(), f'{name} ({tou_label})' ) self.area.name = name self.area.save() # update connector names connector = self.connectors.get(id_teilflaeche=self.area.id) connector.name_teilflaeche = self.area.name connector.save() if self.typ: self.typ.clear(self.area) self.setup_type_params() self.canvas.refreshAllLayers() Traffic.reset() self.params.changed.connect(type_changed) self.params.show(title='Teilfläche definieren') def setup_type_params(self): ''' set up detailled parameters depending on current type of use ''' tou_label = self.types[self.area.nutzungsart][0] title = f'Maß der baulichen Nutzung ({tou_label})' self.ui.type_parameter_group.setTitle(title) clear_layout(self.ui.type_parameter_group.layout()) if self.typ: self.typ.close() self.typ = self.types[self.area.nutzungsart][1] if self.typ is None: self.ui.type_parameter_group.setVisible(False) return self.ui.type_parameter_group.setVisible(True) self.typ.setup_params(self.area) self.typ.params.changed.connect(lambda: self.canvas.refreshAllLayers()) def close(self): ''' close sub-domains and parameters ''' self.connector_setter.close() if self.tou_output and self.tou_output.layer: self.tou_output.layer.removeSelection() if hasattr(self, 'params'): self.params.close() if self.typ: self.typ.close() super().close()
class Einzelhandel: ''' sub-domain of project area definitions. Set up the structure of a project area with retail trade as the type of use ''' def __init__(self, project, layout): self.project = project self.basedata = project.basedata self.sortimente_base = self.basedata.get_table( 'Einzelhandel_Sortimente', 'Definition_Projekt' ) self.verkaufsflaechen = Verkaufsflaechen.features(create=True) self.layout = layout self.markets = Markets.features(create=True) def setup_params(self, area): ''' set the parameters according to the data of the given area ''' self.area = area clear_layout(self.layout) self.params = Params(self.layout, help_file='definitionen_einzelhandel.txt') self.params.add(Title('Verkaufsfläche')) for sortiment in self.sortimente_base.features(): feature = self.verkaufsflaechen.get(id_sortiment=sortiment.id, id_teilflaeche=self.area.id) value = feature.verkaufsflaeche_qm if feature else 0 self.params.add(Param( value, Slider(maximum=20000), label=f'{sortiment.Name_Sortiment_ProjektCheck}', unit='m²'), name=sortiment.param_vfl ) self.params.changed.connect(self.save) self.params.show( title='Einzelhandel: Bezugszeitraum und Maß der baulichen Nutzung') def save(self): ''' write the current parameter values to the database. Create/change supermarket based on the settings for area of food retailing. ''' vkfl_sum = 0 vkfl_lebensmittel = 0 # id of food id_lm = 1 for sortiment in self.sortimente_base.features(): feature = self.verkaufsflaechen.get(id_sortiment=sortiment.id, id_teilflaeche=self.area.id) if not feature: feature = self.verkaufsflaechen.add( id_sortiment=sortiment.id, id_teilflaeche=self.area.id) vkfl = getattr(self.params, sortiment.param_vfl).value feature.verkaufsflaeche_qm = vkfl vkfl_sum += vkfl if sortiment.id == id_lm: vkfl_lebensmittel += vkfl feature.name_sortiment = sortiment.Name_Sortiment_ProjektCheck feature.save() self.area.vf_gesamt = vkfl_sum self.area.save() market = self.markets.get(id_teilflaeche=self.area.id) if vkfl_lebensmittel > 0: if not market: centroid = self.area.geom.centroid().asPoint() name = f'Neuer Lebensmittelmarkt auf Fläche "{self.area.name}"' market = self.markets.add( id_teilflaeche=self.area.id, name = name, geom=centroid, kette= 'Anbieter unbekannt' ) gem = get_ags([market], self.project.basedata)[0] market.AGS = gem.AGS market.save() sm = Supermarket(0, 0, 0, name='a', kette='b', vkfl=vkfl_lebensmittel) market_tool = ReadMarketsWorker(self.project) sm = market_tool.vkfl_to_betriebstyp([sm])[0] market.id_betriebstyp_planfall = sm.id_betriebstyp market.betriebstyp_planfall = sm.betriebstyp market.vkfl_planfall = vkfl_lebensmittel market.save() SupermarketsCompetition.remove_results() else: if market: market.delete() SupermarketsCompetition.remove_results() self.set_ways(self.area) Traffic.reset() MunicipalTaxRevenue.reset_gewerbe_einzelhandel() def clear(self, area): ''' clear all data related to retail trade as the type of use of the given area ''' # remove existing market market = self.markets.get(id_teilflaeche=area.id) if market: market.delete() SupermarketsCompetition.remove_results() self.verkaufsflaechen.filter(id_teilflaeche=area.id).delete() area.vf_gesamt = None area.save() MunicipalTaxRevenue.reset_gewerbe_einzelhandel() def set_ways(self, area): ''' calculate and store the daily ways done by the employees and customers of the area ''' df_verkaufsflaechen = self.verkaufsflaechen.filter( id_teilflaeche=area.id).to_pandas() default_branche = self.basedata.get_table( 'Gewerbe_Branchen', 'Definition_Projekt').features().get( ID_Branche_ProjektCheck=0) df_sortimente = self.sortimente_base.to_pandas() df_sortimente.rename( columns={'ID_Sortiment_ProjektCheck': 'id_sortiment'}, inplace=True) joined = df_verkaufsflaechen.merge(df_sortimente, on='id_sortiment', how='left') n_ways = (joined['verkaufsflaeche_qm'] * joined['Besucher_je_qm_Vfl'] * joined['Wege_je_Besucher']) n_ways_miv = n_ways * joined['Anteil_Pkw_Fahrer'] / 100 n_job_ways = (joined['verkaufsflaeche_qm'] * joined['AP_je_qm_Vfl'] * default_branche.Wege_je_Beschäftigten) n_job_miv = n_job_ways * default_branche.Anteil_Pkw_Fahrer / 100 area.wege_gesamt = int(n_ways.sum() + n_job_ways.sum()) area.wege_miv = int(n_ways_miv.sum() + n_job_miv.sum()) area.save() def close(self): ''' close parameters ''' if hasattr(self, 'params'): self.params.close()
class Gewerbe: ''' sub-domain of project area definitions. Set up the structure of a project area with industry as the type of use ''' # Default Gewerbegebietstyp DEFAULT_INDUSTRY_ID = 2 # analysis period BETRACHTUNGSZEITRAUM_JAHRE = 15 def __init__(self, project, layout): self.layout = layout self.gewerbeanteile = Gewerbeanteile.features(create=True) self.ap_nach_jahr = ApProJahr.features(create=True) self.projektrahmendaten = Projektrahmendaten.features() self.basedata = project.basedata self.branchen = list(self.basedata.get_table( 'Gewerbe_Branchen', 'Definition_Projekt' ).features().filter(ID_Branche_ProjektCheck__gt=0)) presets = self.basedata.get_table( 'Vorschlagswerte_Branchenstruktur', 'Definition_Projekt' ) self.df_presets_base = presets.to_pandas() density = self.basedata.get_table( 'Dichtekennwerte_Gewerbe', 'Definition_Projekt' ) self.df_density_base = density.to_pandas() industry_types = self.basedata.get_table( 'Gewerbegebietstypen', 'Definition_Projekt' ) self.df_industry_types_base = industry_types.to_pandas() default_idx = self.df_industry_types_base['IDGewerbegebietstyp'] == \ self.DEFAULT_INDUSTRY_ID self.df_industry_types_base.loc[ default_idx, 'Name_Gewerbegebietstyp'] += ' (default)' def set_industry_presets(self, preset_id): ''' set all sector values to database presets of given industry id ''' if preset_id == -1: return idx = self.df_presets_base['IDGewerbegebietstyp'] == preset_id presets = self.df_presets_base[idx] for branche in self.branchen: param = getattr(self.params, branche.param_gewerbenutzung) p_idx = presets['ID_Branche_ProjektCheck'] == branche.id preset = int(presets[p_idx]['Vorschlagswert_in_Prozent'].values[0]) param.value = preset def estimate_jobs(self): ''' calculate estimation of number of jobs set estimated jobs to sectors of industry ''' gemeindetyp = self.area.gemeinde_typ df_kennwerte = self.df_density_base[ self.df_density_base['Gemeindetyp_ProjektCheck'] == gemeindetyp] jobs_sum = 0 for branche in self.branchen: param = getattr(self.params, branche.param_gewerbenutzung) idx = df_kennwerte['ID_Branche_ProjektCheck'] == branche.id jobs_per_ha = int(df_kennwerte[idx]['AP_pro_ha_brutto'].values[0]) jobs_ind = round(self.area.area * (param.input.value / 100.) * jobs_per_ha) branche.estimated_jobs = jobs_ind branche.jobs_per_ha = jobs_per_ha jobs_sum += jobs_ind return jobs_sum def setup_params(self, area): ''' set the parameters according to the data of the given area ''' self.area = area clear_layout(self.layout) self.params = Params(self.layout, help_file='definitionen_gewerbe.txt') self.params.add(Title('Bezugszeitraum')) self.params.beginn_nutzung = Param( area.beginn_nutzung, SpinBox(minimum=2000, maximum=2100), label='Beginn des Bezuges', repr_format='%d' ) self.params.aufsiedlungsdauer = Param( area.aufsiedlungsdauer, SpinBox(minimum=1, maximum=100), label='Dauer des Bezuges (Jahre, 1 = Bezug wird noch\n' 'im Jahr des Bezugsbeginns abgeschlossen)', unit='Jahr(e)' ) self.params.add(Seperator(margin=10)) self.params.add( Title('Voraussichtlicher Anteil der Branchen an der Nettofläche')) preset_names = self.df_industry_types_base[ 'Name_Gewerbegebietstyp'].values preset_ids = self.df_industry_types_base['IDGewerbegebietstyp'].values options = ['Gebietstyp wählen'] + list(preset_names) self.preset_combo = ComboBox(options, [-1] + list(preset_ids)) self.preset_combo.input.model().item(0).setEnabled(False) param = Param(0, self.preset_combo, label='Vorschlagswerte') param.hide_in_overview = True self.params.add(param, name='gebietstyp') # break grid layout self.params.add( QSpacerItem(0, 3, QSizePolicy.Fixed, QSizePolicy.Minimum)) def values_changed(): if self.auto_check.value: n_jobs = self.estimate_jobs() self.ap_slider.set_value(n_jobs) def slider_changed(): self.preset_combo.set_value(options[0]) values_changed() def preset_changed(): self.set_industry_presets(self.preset_combo.input.currentData()) values_changed() self.preset_combo.changed.connect(preset_changed) dependency = SumDependency(100) for branche in self.branchen: feature = self.gewerbeanteile.get( id_branche=branche.id, id_teilflaeche=self.area.id ) value = feature.anteil_definition if feature else 0 slider = Slider(maximum=100, width=200, lockable=True) param = Param( value, slider, label=f'{branche.Name_Branche_ProjektCheck}', unit='%' ) slider.changed.connect(slider_changed) dependency.add(param) self.params.add(param, name=branche.param_gewerbenutzung) self.params.add(Seperator()) self.params.add(Title('Voraussichtliche Anzahl an Arbeitsplätzen')) self.auto_check = Checkbox() self.params.auto_check = Param( bool(self.area.ap_ist_geschaetzt), self.auto_check, label='Automatische Schätzung' ) self.ap_slider = Slider(maximum=10000) self.params.arbeitsplaetze_insgesamt = Param( self.area.ap_gesamt, self.ap_slider, label='Zahl der Arbeitsplätze\n' 'nach Vollbezug (Summe über alle Branchen)' ) def toggle_auto_check(): # disable input for manual setting of jobs # when auto check is enabled read_only = self.auto_check.value for _input in [self.ap_slider.slider, self.ap_slider.spinbox]: _input.setAttribute(Qt.WA_TransparentForMouseEvents, read_only) _input.setFocusPolicy(Qt.NoFocus if read_only else Qt.StrongFocus) _input.update() values_changed() self.auto_check.changed.connect(toggle_auto_check) toggle_auto_check() # set to default preset if assignment is new if len(self.gewerbeanteile) == 0: self.set_industry_presets(self.DEFAULT_INDUSTRY_ID) ap_gesamt = self.estimate_jobs() self.params.arbeitsplaetze_insgesamt.value = ap_gesamt self.save() self.params.changed.connect(self.save) self.params.show( title='Gewerbe: Bezugszeitraum und Maß der baulichen Nutzung') def save(self): ''' write the current parameter values to the database ''' for branche in self.branchen: feature = self.gewerbeanteile.get(id_branche=branche.id, id_teilflaeche=self.area.id) if not feature: feature = self.gewerbeanteile.add( id_branche=branche.id, id_teilflaeche=self.area.id ) feature.name_teilflaeche = self.area.name feature.anteil_definition = getattr( self.params, branche.param_gewerbenutzung).value feature.name_branche = branche.Name_Branche_ProjektCheck feature.anzahl_jobs_schaetzung = getattr( branche, 'estimated_jobs', 0) feature.dichtekennwert = getattr( branche, 'jobs_per_ha', 0) feature.save() self.area.beginn_nutzung = self.params.beginn_nutzung.value self.area.aufsiedlungsdauer = self.params.aufsiedlungsdauer.value self.area.ap_gesamt = self.params.arbeitsplaetze_insgesamt.value self.area.ap_ist_geschaetzt = self.params.auto_check.value self.area.save() # just estimate for output in case auto estimation is deactivated # (estimated values needed in any case) self.estimate_jobs() self.set_growth(self.area) self.set_percentages(self.area) self.set_ways(self.area) Traffic.reset() MunicipalTaxRevenue.reset_gewerbe_einzelhandel() def clear(self, area): ''' clear all data related to industry as the type of use of the given area ''' MunicipalTaxRevenue.reset_gewerbe_einzelhandel() self.gewerbeanteile.filter(id_teilflaeche=area.id).delete() self.ap_nach_jahr.filter(id_teilflaeche=area.id).delete() area.ap_gesamt = None area.save() def set_growth(self, area): ''' calculate and store the development of the number of employees ''' n_jobs = area.ap_gesamt begin = area.beginn_nutzung duration = area.aufsiedlungsdauer end = begin + self.BETRACHTUNGSZEITRAUM_JAHRE - 1 self.ap_nach_jahr.filter(id_teilflaeche=area.id).delete() for progress in range(0, end - begin + 1): proc_factor = (float(progress + 1) / duration if progress + 1 <= duration else 1) year = begin + progress self.ap_nach_jahr.add( id_teilflaeche=self.area.id, name_teilflaeche=self.area.name, jahr=year, arbeitsplaetze=n_jobs * proc_factor ) def set_percentages(self, area): ''' this already could have done when saving, but is here based on the old ArcGIS code ''' df = self.gewerbeanteile.filter(id_teilflaeche=area.id).to_pandas() df['anteil_branche'] = df['anteil_definition'] * df['dichtekennwert'] df['anteil_branche'] /= df['anteil_branche'].sum() df['anteil_branche'] *= 100 df = df.round({'anteil_branche': 0}) self.gewerbeanteile.update_pandas(df) def set_ways(self, area): ''' calculate and store the daily ways done by the employees of the area ''' df_anteile = self.gewerbeanteile.filter( id_teilflaeche=area.id).to_pandas() df_basedata = (self.basedata.get_table( 'Gewerbe_Branchen', 'Definition_Projekt').features().filter( ID_Branche_ProjektCheck__gt=0).to_pandas()) df_basedata.rename(columns={'ID_Branche_ProjektCheck': 'id_branche'}, inplace=True) estimated = df_anteile['anzahl_jobs_schaetzung'] estimated_sum = estimated.sum() preset = area.ap_gesamt cor_factor = preset / estimated_sum if estimated_sum > 0 else 0 joined = df_anteile.merge(df_basedata, on='id_branche', how='left') n_ways = estimated * cor_factor * joined['Wege_je_Beschäftigten'] n_ways_miv = n_ways * joined['Anteil_Pkw_Fahrer'] / 100 area.wege_gesamt = int(n_ways.sum()) area.wege_miv = int(n_ways_miv.sum()) area.save() def close(self): ''' close parameters ''' if hasattr(self, 'params'): self.params.close()
class Wohnen: ''' sub-domain of project area definitions. Set up the structure of a residential project area ''' def __init__(self, project, layout): self.basedata = project.basedata self.gebaeudetypen_base = self.basedata.get_table( 'Wohnen_Gebaeudetypen', 'Definition_Projekt' ).features() self.df_presets = self.basedata.get_table( 'WE_nach_Gebietstyp', 'Definition_Projekt' ).to_pandas() self.wohneinheiten = Wohneinheiten.features(create=True) self.layout = layout def setup_params(self, area): ''' set the parameters according to the data of the given area ''' self.area = area clear_layout(self.layout) self.params = Params(self.layout, help_file='definitionen_wohnen.txt') self.params.add(Title('Bezugszeitraum')) self.params.beginn_nutzung = Param( area.beginn_nutzung, SpinBox(minimum=2000, maximum=2100), label='Beginn des Bezuges', repr_format='%d' ) self.params.aufsiedlungsdauer = Param( area.aufsiedlungsdauer, SpinBox(minimum=1, maximum=100), label='Dauer des Bezuges', unit='Jahr(e)') self.params.add(Seperator()) self.params.add(Title('Anzahl Wohneinheiten nach Gebäudetypen')) # load the building presets to select from preset_names, idx = np.unique(self.df_presets['Gebietstyp'].values, return_index=True) idx.sort() preset_names = self.df_presets['Gebietstyp'].values[idx] options = ['Gebietstyp wählen'] + list(preset_names) self.preset_combo = ComboBox(options) self.preset_combo.input.model().item(0).setEnabled(False) param = Param(0, self.preset_combo, label='Vorschlagswerte') param.hide_in_overview = True self.params.add(param, name='gebietstyp') self.params.add( QSpacerItem(0, 3, QSizePolicy.Fixed, QSizePolicy.Minimum)) # preset is selected def preset_changed(gebietstyp): presets = self.df_presets[self.df_presets['Gebietstyp']==gebietstyp] for idx, preset in presets.iterrows(): id_bt = preset['IDGebaeudetyp'] bt = self.gebaeudetypen_base.get(id=id_bt) param = self.params.get(bt.param_we) param.input.value = self.area.area * preset['WE_pro_Hektar'] self.preset_combo.changed.connect(preset_changed) for bt in self.gebaeudetypen_base: param_name = bt.param_we feature = self.wohneinheiten.get(id_gebaeudetyp=bt.id, id_teilflaeche=self.area.id) value = feature.we if feature else 0 slider = Slider(maximum=2000) self.params.add(Param( value, slider, label=f'... in {bt.display_name}'), name=param_name ) slider.changed.connect( lambda: self.preset_combo.set_value(options[0])) self.params.add(Seperator()) self.params.add(Title('Mittlere Anzahl Bewohner pro Wohneinheit\n' '(3 Jahre nach Bezug)')) for bt in self.gebaeudetypen_base: param_name = bt.param_ew_je_we feature = self.wohneinheiten.get(id_gebaeudetyp=bt.id, id_teilflaeche=self.area.id) # set to default if no feature yet value = feature.ew_je_we if feature else bt.default_ew_je_we self.params.add(Param( value, DoubleSpinBox(step=0.1, maximum=50), label=f'... in {bt.display_name}'), name=param_name ) self.params.add(Seperator()) self.params.add(Title('Anteil der Bewohner unter 18 Jahre')) # load the age presets to select from for bt in self.gebaeudetypen_base: param_name = bt.param_anteil_u18 feature = self.wohneinheiten.get(id_gebaeudetyp=bt.id, id_teilflaeche=self.area.id) # set to default if no feature yet value = feature.anteil_u18 if feature else bt.default_anteil_u18 self.params.add(Param( value, Slider(maximum=60), unit='%', label=f'... in {bt.display_name}'), name=param_name ) self.params.changed.connect(self.save) self.params.show( title='Wohnen: Bezugszeitraum, Maß der baulichen Nutzung, ' 'Haushaltsstrukturen') def save(self): ''' write the current parameter values to the database ''' we_sum = 0 for bt in self.gebaeudetypen_base: feature = self.wohneinheiten.get(id_gebaeudetyp=bt.id, id_teilflaeche=self.area.id) if not feature: feature = self.wohneinheiten.add( id_gebaeudetyp=bt.id, id_teilflaeche=self.area.id) we = getattr(self.params, bt.param_we).value feature.we = we we_sum += we ew_je_we = getattr(self.params, bt.param_ew_je_we).value feature.ew_je_we = ew_je_we anteil_u18 = getattr(self.params, bt.param_anteil_u18).value feature.anteil_u18 = anteil_u18 cor_factor = ew_je_we / bt.Ew_pro_WE_Referenz feature.korrekturfaktor = cor_factor feature.name_gebaeudetyp = bt.NameGebaeudetyp feature.save() self.area.beginn_nutzung = self.params.beginn_nutzung.value self.area.aufsiedlungsdauer = self.params.aufsiedlungsdauer.value self.area.we_gesamt = we_sum Traffic.reset() MunicipalTaxRevenue.reset_wohnen() self.area.save() job = WohnenDevelopment(self.basedata, self.area) dialog = ProgressDialog( job, auto_close=True, parent=self.layout.parentWidget()) dialog.show() def clear(self, area): ''' clear all data related to residential use of the given area ''' self.wohneinheiten.filter(id_teilflaeche=area.id).delete() area.we_gesamt = None area.ew = 0 area.save() MunicipalTaxRevenue.reset_wohnen() def close(self): ''' close parameters ''' if hasattr(self, 'params'): self.params.close()