From 5c90afb0661305c8dc1cc300d2601223f684da03 Mon Sep 17 00:00:00 2001 From: SJiB Date: Tue, 17 Sep 2024 14:27:23 +0900 Subject: [PATCH 1/8] base-layout for TIT --- plugin/.docker/Dockerfile | 22 + plugin/.docker/run-docker-tests.sh | 24 + plugin/scripts/download-interlis-libs.sh | 4 + plugin/scripts/package-pip-packages.sh | 31 + plugin/scripts/run-tests.sh | 11 + plugin/teksi_wastewater/__init__.py | 29 + plugin/teksi_wastewater/gui/__init__.py | 0 plugin/teksi_wastewater/gui/about_dialog.py | 62 + plugin/teksi_wastewater/gui/forms.py | 26 + .../teksi_wastewater/gui/twwplotsvgwidget.py | 171 + .../gui/twwprofiledockwidget.py | 232 + .../teksi_wastewater/gui/twwsettingsdialog.py | 186 + plugin/teksi_wastewater/gui/twwwizard.py | 95 + .../icons/interlis_export.svg | 103 + .../icons/interlis_import.svg | 91 + .../icons/link-wastewater-networkelement.svg | 198 + plugin/teksi_wastewater/icons/newproject.svg | 159 + .../icons/refresh-network.svg | 211 + .../icons/teksi-abwasser-logo.svg | 55 + plugin/teksi_wastewater/icons/twwIcon.png | Bin 0 -> 974 bytes plugin/teksi_wastewater/icons/twwIcon.svg | 473 + .../icons/wastewater-downstream.svg | 2389 + .../icons/wastewater-profile.svg | 154 + .../icons/wastewater-upstream.svg | 2389 + plugin/teksi_wastewater/icons/wizard.svg | 253 + plugin/teksi_wastewater/interlis/config.py | 25 + .../teksi_wastewater/interlis/gui/__init__.py | 0 .../interlis/gui/editors/__init__.py | 25 + .../interlis/gui/editors/base.py | 155 + .../interlis/gui/editors/base.ui | 31 + .../interlis/gui/editors/damage_channel.py | 17 + .../interlis/gui/editors/data_media.py | 41 + .../interlis/gui/editors/data_media.ui | 48 + .../interlis/gui/featureselectorwidget.py | 236 + .../gui/interlis_export_settings_dialog.py | 119 + .../gui/interlis_export_settings_dialog.ui | 143 + .../gui/interlis_import_selection_dialog.py | 231 + .../gui/interlis_import_selection_dialog.ui | 249 + .../gui/interlis_importer_exporter_gui.py | 175 + .../interlis/interlis_importer_exporter.py | 618 + .../interlis_model_mapping/__init__.py | 0 ...nterlis_exporter_to_intermediate_schema.py | 3213 + ...nterlis_importer_to_intermediate_schema.py | 2296 + .../interlis_model_mapping/model_base.py | 20 + .../model_interlis_dss.py | 351 + .../model_interlis_sia405_abwasser.py | 191 + .../model_interlis_vsa_kek.py | 49 + .../interlis_model_mapping/model_tww.py | 12 + .../interlis_model_mapping/model_tww_od.py | 330 + .../interlis/processing_algs/__init__.py | 0 .../processing_algs/extractlabels_interlis.py | 223 + .../interlis/tpl/export.py.tpl | 65 + .../interlis/tpl/import_.py.tpl | 51 + .../interlis/tpl/mapping.py.tpl | 19 + .../interlis/utils/__init__.py | 0 .../teksi_wastewater/interlis/utils/ili2db.py | 196 + .../interlis/utils/tww_sqlalchemy.py | 106 + .../interlis/utils/various.py | 75 + plugin/teksi_wastewater/metadata.txt | 25 + .../processing_provider/TwwSwmm.py | 1306 + .../processing_provider/__init__.py | 0 .../change_reach_direction.py | 89 + .../processing_provider/flow_times.py | 214 + .../processing_provider/provider.py | 150 + .../processing_provider/snap_reach.py | 211 + .../processing_provider/sum_up_upstream.py | 447 + .../processing_provider/swmm_create_input.py | 178 + .../processing_provider/swmm_execute.py | 117 + .../swmm_extract_results.py | 159 + .../swmm_import_results.py | 161 + .../processing_provider/swmm_set_friction.py | 104 + .../processing_provider/tww_algorithm.py | 49 + plugin/teksi_wastewater/svgprofile/.gitignore | 2 + .../teksi_wastewater/svgprofile/d3.v2.min.js | 4 + plugin/teksi_wastewater/svgprofile/dojo.js | 15 + plugin/teksi_wastewater/svgprofile/index.html | 23 + plugin/teksi_wastewater/svgprofile/print.css | 3 + .../teksi_wastewater/svgprofile/profile.css | 125 + plugin/teksi_wastewater/svgprofile/profile.js | 315 + .../svgprofile/profile/profileElement.js | 84 + .../svgprofile/profile/reach.js | 219 + .../svgprofile/profile/specialStructure.js | 219 + .../svgprofile/profile/surface.js | 66 + plugin/teksi_wastewater/svgprofile/screen.css | 44 + .../svgprofile/utils/translation.js | 34 + .../teksi_wastewater_plugin.py | 669 + plugin/teksi_wastewater/tests/__init__.py | 0 .../tests/data/minimal-dataset-DSS.xtf | 149 + .../data/minimal-dataset-SIA405-ABWASSER.xtf | 149 + ...minimal-dataset-VSA-KEK-manhole-damage.xtf | 150 + ...inimal-dataset-organisation-arbon-only.xtf | 22 + ...ansferdatensatz_VSA-KEK-channel-damage.xtf | 286 + ...ansferdatensatz_VSA-KEK-manhole-damage.xtf | 156 + .../tests/data/test-dataset-DSS.xtf | 106681 +++++++++++++++ .../tests/data/test-dataset-organisations.xtf | 100 + .../teksi_wastewater/tests/test_interlis.py | 221 + .../tests/test_interlis_various.py | 10 + plugin/teksi_wastewater/tools/__init__.py | 0 .../tools/twwmaptooladdfeature.py | 514 + plugin/teksi_wastewater/tools/twwmaptools.py | 926 + plugin/teksi_wastewater/tools/twwnetwork.py | 449 + plugin/teksi_wastewater/tools/twwprofile.py | 514 + plugin/teksi_wastewater/ui/__init__.py | 0 plugin/teksi_wastewater/ui/about_dialog.ui | 221 + .../teksi_wastewater/ui/twwdatamodeldialog.ui | 374 + plugin/teksi_wastewater/ui/twwdockwidget.ui | 225 + .../ui/twwpgserviceeditordialog.ui | 201 + .../teksi_wastewater/ui/twwsettingsdialog.ui | 376 + plugin/teksi_wastewater/ui/twwwizard.ui | 60 + plugin/teksi_wastewater/utils/__init__.py | 5 + .../teksi_wastewater/utils/database_utils.py | 238 + plugin/teksi_wastewater/utils/plugin_utils.py | 64 + plugin/teksi_wastewater/utils/qt_utils.py | 30 + plugin/teksi_wastewater/utils/translation.py | 84 + .../teksi_wastewater/utils/twwlayermanager.py | 93 + plugin/teksi_wastewater/utils/twwlogging.py | 50 + plugin/teksi_wastewater/utils/ui.py | 16 + plugin/tww_cmd.py | 253 + scripts/banned-words.sh | 38 + 119 files changed, 134365 insertions(+) create mode 100644 plugin/.docker/Dockerfile create mode 100644 plugin/.docker/run-docker-tests.sh create mode 100644 plugin/scripts/download-interlis-libs.sh create mode 100644 plugin/scripts/package-pip-packages.sh create mode 100644 plugin/scripts/run-tests.sh create mode 100644 plugin/teksi_wastewater/__init__.py create mode 100644 plugin/teksi_wastewater/gui/__init__.py create mode 100644 plugin/teksi_wastewater/gui/about_dialog.py create mode 100644 plugin/teksi_wastewater/gui/forms.py create mode 100644 plugin/teksi_wastewater/gui/twwplotsvgwidget.py create mode 100644 plugin/teksi_wastewater/gui/twwprofiledockwidget.py create mode 100644 plugin/teksi_wastewater/gui/twwsettingsdialog.py create mode 100644 plugin/teksi_wastewater/gui/twwwizard.py create mode 100644 plugin/teksi_wastewater/icons/interlis_export.svg create mode 100644 plugin/teksi_wastewater/icons/interlis_import.svg create mode 100644 plugin/teksi_wastewater/icons/link-wastewater-networkelement.svg create mode 100644 plugin/teksi_wastewater/icons/newproject.svg create mode 100644 plugin/teksi_wastewater/icons/refresh-network.svg create mode 100644 plugin/teksi_wastewater/icons/teksi-abwasser-logo.svg create mode 100644 plugin/teksi_wastewater/icons/twwIcon.png create mode 100644 plugin/teksi_wastewater/icons/twwIcon.svg create mode 100644 plugin/teksi_wastewater/icons/wastewater-downstream.svg create mode 100644 plugin/teksi_wastewater/icons/wastewater-profile.svg create mode 100644 plugin/teksi_wastewater/icons/wastewater-upstream.svg create mode 100644 plugin/teksi_wastewater/icons/wizard.svg create mode 100644 plugin/teksi_wastewater/interlis/config.py create mode 100644 plugin/teksi_wastewater/interlis/gui/__init__.py create mode 100644 plugin/teksi_wastewater/interlis/gui/editors/__init__.py create mode 100644 plugin/teksi_wastewater/interlis/gui/editors/base.py create mode 100644 plugin/teksi_wastewater/interlis/gui/editors/base.ui create mode 100644 plugin/teksi_wastewater/interlis/gui/editors/damage_channel.py create mode 100644 plugin/teksi_wastewater/interlis/gui/editors/data_media.py create mode 100644 plugin/teksi_wastewater/interlis/gui/editors/data_media.ui create mode 100644 plugin/teksi_wastewater/interlis/gui/featureselectorwidget.py create mode 100644 plugin/teksi_wastewater/interlis/gui/interlis_export_settings_dialog.py create mode 100644 plugin/teksi_wastewater/interlis/gui/interlis_export_settings_dialog.ui create mode 100644 plugin/teksi_wastewater/interlis/gui/interlis_import_selection_dialog.py create mode 100644 plugin/teksi_wastewater/interlis/gui/interlis_import_selection_dialog.ui create mode 100644 plugin/teksi_wastewater/interlis/gui/interlis_importer_exporter_gui.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_importer_exporter.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/__init__.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/interlis_exporter_to_intermediate_schema.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/interlis_importer_to_intermediate_schema.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/model_base.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_dss.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_sia405_abwasser.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_vsa_kek.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/model_tww.py create mode 100644 plugin/teksi_wastewater/interlis/interlis_model_mapping/model_tww_od.py create mode 100644 plugin/teksi_wastewater/interlis/processing_algs/__init__.py create mode 100644 plugin/teksi_wastewater/interlis/processing_algs/extractlabels_interlis.py create mode 100644 plugin/teksi_wastewater/interlis/tpl/export.py.tpl create mode 100644 plugin/teksi_wastewater/interlis/tpl/import_.py.tpl create mode 100644 plugin/teksi_wastewater/interlis/tpl/mapping.py.tpl create mode 100644 plugin/teksi_wastewater/interlis/utils/__init__.py create mode 100644 plugin/teksi_wastewater/interlis/utils/ili2db.py create mode 100644 plugin/teksi_wastewater/interlis/utils/tww_sqlalchemy.py create mode 100644 plugin/teksi_wastewater/interlis/utils/various.py create mode 100644 plugin/teksi_wastewater/metadata.txt create mode 100644 plugin/teksi_wastewater/processing_provider/TwwSwmm.py create mode 100644 plugin/teksi_wastewater/processing_provider/__init__.py create mode 100644 plugin/teksi_wastewater/processing_provider/change_reach_direction.py create mode 100644 plugin/teksi_wastewater/processing_provider/flow_times.py create mode 100644 plugin/teksi_wastewater/processing_provider/provider.py create mode 100644 plugin/teksi_wastewater/processing_provider/snap_reach.py create mode 100644 plugin/teksi_wastewater/processing_provider/sum_up_upstream.py create mode 100644 plugin/teksi_wastewater/processing_provider/swmm_create_input.py create mode 100644 plugin/teksi_wastewater/processing_provider/swmm_execute.py create mode 100644 plugin/teksi_wastewater/processing_provider/swmm_extract_results.py create mode 100644 plugin/teksi_wastewater/processing_provider/swmm_import_results.py create mode 100644 plugin/teksi_wastewater/processing_provider/swmm_set_friction.py create mode 100644 plugin/teksi_wastewater/processing_provider/tww_algorithm.py create mode 100644 plugin/teksi_wastewater/svgprofile/.gitignore create mode 100644 plugin/teksi_wastewater/svgprofile/d3.v2.min.js create mode 100644 plugin/teksi_wastewater/svgprofile/dojo.js create mode 100644 plugin/teksi_wastewater/svgprofile/index.html create mode 100644 plugin/teksi_wastewater/svgprofile/print.css create mode 100644 plugin/teksi_wastewater/svgprofile/profile.css create mode 100644 plugin/teksi_wastewater/svgprofile/profile.js create mode 100644 plugin/teksi_wastewater/svgprofile/profile/profileElement.js create mode 100644 plugin/teksi_wastewater/svgprofile/profile/reach.js create mode 100644 plugin/teksi_wastewater/svgprofile/profile/specialStructure.js create mode 100644 plugin/teksi_wastewater/svgprofile/profile/surface.js create mode 100644 plugin/teksi_wastewater/svgprofile/screen.css create mode 100644 plugin/teksi_wastewater/svgprofile/utils/translation.js create mode 100644 plugin/teksi_wastewater/teksi_wastewater_plugin.py create mode 100644 plugin/teksi_wastewater/tests/__init__.py create mode 100644 plugin/teksi_wastewater/tests/data/minimal-dataset-DSS.xtf create mode 100644 plugin/teksi_wastewater/tests/data/minimal-dataset-SIA405-ABWASSER.xtf create mode 100644 plugin/teksi_wastewater/tests/data/minimal-dataset-VSA-KEK-manhole-damage.xtf create mode 100644 plugin/teksi_wastewater/tests/data/minimal-dataset-organisation-arbon-only.xtf create mode 100644 plugin/teksi_wastewater/tests/data/minimal-dataset-transferdatensatz_VSA-KEK-channel-damage.xtf create mode 100644 plugin/teksi_wastewater/tests/data/minimal-dataset-transferdatensatz_VSA-KEK-manhole-damage.xtf create mode 100644 plugin/teksi_wastewater/tests/data/test-dataset-DSS.xtf create mode 100644 plugin/teksi_wastewater/tests/data/test-dataset-organisations.xtf create mode 100644 plugin/teksi_wastewater/tests/test_interlis.py create mode 100644 plugin/teksi_wastewater/tests/test_interlis_various.py create mode 100644 plugin/teksi_wastewater/tools/__init__.py create mode 100644 plugin/teksi_wastewater/tools/twwmaptooladdfeature.py create mode 100644 plugin/teksi_wastewater/tools/twwmaptools.py create mode 100644 plugin/teksi_wastewater/tools/twwnetwork.py create mode 100644 plugin/teksi_wastewater/tools/twwprofile.py create mode 100644 plugin/teksi_wastewater/ui/__init__.py create mode 100644 plugin/teksi_wastewater/ui/about_dialog.ui create mode 100644 plugin/teksi_wastewater/ui/twwdatamodeldialog.ui create mode 100644 plugin/teksi_wastewater/ui/twwdockwidget.ui create mode 100644 plugin/teksi_wastewater/ui/twwpgserviceeditordialog.ui create mode 100644 plugin/teksi_wastewater/ui/twwsettingsdialog.ui create mode 100644 plugin/teksi_wastewater/ui/twwwizard.ui create mode 100644 plugin/teksi_wastewater/utils/__init__.py create mode 100644 plugin/teksi_wastewater/utils/database_utils.py create mode 100644 plugin/teksi_wastewater/utils/plugin_utils.py create mode 100644 plugin/teksi_wastewater/utils/qt_utils.py create mode 100644 plugin/teksi_wastewater/utils/translation.py create mode 100644 plugin/teksi_wastewater/utils/twwlayermanager.py create mode 100644 plugin/teksi_wastewater/utils/twwlogging.py create mode 100644 plugin/teksi_wastewater/utils/ui.py create mode 100644 plugin/tww_cmd.py create mode 100644 scripts/banned-words.sh diff --git a/plugin/.docker/Dockerfile b/plugin/.docker/Dockerfile new file mode 100644 index 0000000..fed32ed --- /dev/null +++ b/plugin/.docker/Dockerfile @@ -0,0 +1,22 @@ +ARG QGIS_TEST_VERSION=latest +FROM qgis/qgis:${QGIS_TEST_VERSION} + +# remove QGIS apt repo to avoid signing key issues +RUN add-apt-repository -r https://qgis.org/ubuntu && \ + add-apt-repository -r https://qgis.org/ubuntu-ltr + +RUN apt-get update && \ + apt-get -y install openjdk-8-jre curl locales postgresql-client python3-geoalchemy2 \ + && rm -rf /var/lib/apt/lists/* + +RUN printf '[postgres]\ndbname=postgres\nuser=postgres\n' >> /etc/postgresql-common/pg_service.conf +RUN printf '[pg_tww]\ndbname=tww\nuser=postgres\n' >> /etc/postgresql-common/pg_service.conf + +# Some defaults +ENV POSTGRES_PASSWORD=postgres +# otherwise psycopg2 cannot connect +ENV PGSERVICEFILE=/etc/postgresql-common/pg_service.conf +ENV PGSERVICE=pg_tww +ENV LANG=C.UTF-8 + +WORKDIR / diff --git a/plugin/.docker/run-docker-tests.sh b/plugin/.docker/run-docker-tests.sh new file mode 100644 index 0000000..e34af3b --- /dev/null +++ b/plugin/.docker/run-docker-tests.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +#*************************************************************************** +# ------------------- +# begin : 2017-08-24 +# git sha : :%H$ +# copyright : (C) 2017 by OPENGIS.ch +# email : info@opengis.ch +#*************************************************************************** +# +#*************************************************************************** +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU General Public License as published by * +#* the Free Software Foundation; either version 2 of the License, or * +#* (at your option) any later version. * +#* * +#*************************************************************************** + +set -e + +pushd /usr/src/plugin +DEFAULT_PARAMS='-v' +xvfb-run pytest-3 ${@:-`echo $DEFAULT_PARAMS`} +popd diff --git a/plugin/scripts/download-interlis-libs.sh b/plugin/scripts/download-interlis-libs.sh new file mode 100644 index 0000000..c2e482c --- /dev/null +++ b/plugin/scripts/download-interlis-libs.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +wget https://github.com/teksi/wastewater/archive/refs/tags/plugin-ili-libs-1.tar.gz +tar xvzf plugin-ili-libs-1.tar.gz --strip-components=1 diff --git a/plugin/scripts/package-pip-packages.sh b/plugin/scripts/package-pip-packages.sh new file mode 100644 index 0000000..4353e81 --- /dev/null +++ b/plugin/scripts/package-pip-packages.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +LIBS_DIR="plugin/teksi_wastewater/libs" + +MODELBAKER_LIBRARY=("modelbaker" "1.6.0") + +PACKAGES=( + MODELBAKER_LIBRARY[@] +) + +#create lib folder +mkdir -p $LIBS_DIR + +for PACKAGE in ${PACKAGES[@]}; do + echo download and unpack ${!PACKAGE:0:1} with version ${!PACKAGE:1:1} + #create temp folder + mkdir -p temp + #download the wheel + pip download -v ${!PACKAGE:0:1}==${!PACKAGE:1:1} --only-binary :all: -d temp/ + #unpack all the wheels found (means including dependencies) + unzip -o "temp/*.whl" -d $LIBS_DIR + #remove temp folder + rm -r temp + #set write rights to group (because qgis-plugin-ci needs it) + chmod -R g+w $LIBS_DIR +done + +#create the __init__.py in libs folder +cd $LIBS_DIR +touch __init__.py +chmod g+w __init__.py diff --git a/plugin/scripts/run-tests.sh b/plugin/scripts/run-tests.sh new file mode 100644 index 0000000..f213559 --- /dev/null +++ b/plugin/scripts/run-tests.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Arguments are passed one to one to pytest +# +# Run all tests: +# ./scripts/run-tests.sh # Run all tests +# +# Run all test starting with test_array_ +# ./scripts/run-tests.sh -k test_array_ + +docker-compose run qgis /usr/src/plugin/.docker/run-docker-tests.sh $@ diff --git a/plugin/teksi_wastewater/__init__.py b/plugin/teksi_wastewater/__init__.py new file mode 100644 index 0000000..6646dae --- /dev/null +++ b/plugin/teksi_wastewater/__init__.py @@ -0,0 +1,29 @@ +# ----------------------------------------------------------- +# +# TEKSI Wastewater +# Copyright (C) 2012 Matthias Kuhn +# ----------------------------------------------------------- +# +# licensed under the terms of GNU GPL 2 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# --------------------------------------------------------------------- + + +def classFactory(iface): + from .teksi_wastewater_plugin import TeksiWastewaterPlugin + + return TeksiWastewaterPlugin(iface) diff --git a/plugin/teksi_wastewater/gui/__init__.py b/plugin/teksi_wastewater/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugin/teksi_wastewater/gui/about_dialog.py b/plugin/teksi_wastewater/gui/about_dialog.py new file mode 100644 index 0000000..d123b7c --- /dev/null +++ b/plugin/teksi_wastewater/gui/about_dialog.py @@ -0,0 +1,62 @@ +# ----------------------------------------------------------- +# +# Profile +# Copyright (C) 2012 Patrice Verchere +# ----------------------------------------------------------- +# +# licensed under the terms of GNU GPL 2 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, print to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# --------------------------------------------------------------------- + +import os + +from qgis.PyQt.QtCore import QSettings +from qgis.PyQt.QtGui import QPixmap +from qgis.PyQt.QtWidgets import QDialog + +from ..utils import get_ui_class +from ..utils.plugin_utils import plugin_root_path + +DIALOG_UI = get_ui_class("about_dialog.ui") + + +class AboutDialog(QDialog, DIALOG_UI): + def __init__(self, parent=None): + QDialog.__init__(self, parent) + self.setupUi(self) + + metadata_file_path = os.path.join( + os.path.abspath(os.path.join(os.path.dirname(__file__), "..")), + "metadata.txt", + ) + + ini_text = QSettings(metadata_file_path, QSettings.IniFormat) + version = ini_text.value("version") + name = ini_text.value("name") + description = "".join(ini_text.value("description")) + about = " ".join(ini_text.value("about")) + qgisMinimumVersion = ini_text.value("qgisMinimumVersion") + + self.setWindowTitle(f"{name} - {version}") + self.titleLabel.setText(self.windowTitle()) + self.descriptionLabel.setText(description) + self.aboutLabel.setText(about) + self.qgisMinimumVersionLabel.setText(qgisMinimumVersion) + + self.iconLabel.setPixmap( + QPixmap(os.path.join(plugin_root_path(), "icons/teksi-abwasser-logo.svg")) + ) diff --git a/plugin/teksi_wastewater/gui/forms.py b/plugin/teksi_wastewater/gui/forms.py new file mode 100644 index 0000000..0e9d39d --- /dev/null +++ b/plugin/teksi_wastewater/gui/forms.py @@ -0,0 +1,26 @@ +import qgis +from qgis.core import QgsProject + +from ..tools.twwmaptooladdfeature import TwwMapToolDigitizeDrainageChannel + + +def geometryDigitized(fid, layer, tool): + layer.changeGeometry(fid, tool.geometry) + layer.triggerRepaint() + tool.deactivate() + + +def mapToolDeactivated(tool): + tool.deactivated.disconnect() + qgis.utils.plugins["teksi_wastewater"].iface.mapCanvas().unsetMapTool(tool) + tool.deleteLater() + + +def digitizeDrainageChannel(fid, layerid): + layer = QgsProject.instance().mapLayer(layerid) + layer.startEditing() + tool = TwwMapToolDigitizeDrainageChannel(qgis.utils.plugins["teksi_wastewater"].iface, layer) + qgis.utils.plugins["teksi_wastewater"].iface.mapCanvas().setMapTool(tool) + tool.geometryDigitized.connect(lambda: geometryDigitized(fid, layer, tool)) + # form.window().hide() + tool.deactivated.connect(lambda: mapToolDeactivated(tool)) diff --git a/plugin/teksi_wastewater/gui/twwplotsvgwidget.py b/plugin/teksi_wastewater/gui/twwplotsvgwidget.py new file mode 100644 index 0000000..41a3fb1 --- /dev/null +++ b/plugin/teksi_wastewater/gui/twwplotsvgwidget.py @@ -0,0 +1,171 @@ +# ----------------------------------------------------------- +# +# Profile +# Copyright (C) 2012 Matthias Kuhn +# ----------------------------------------------------------- +# +# licensed under the terms of GNU GPL 2 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this progsram; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# --------------------------------------------------------------------- + +import logging + +from qgis.PyQt.QtCore import QSettings, Qt, QUrl, pyqtSignal, pyqtSlot +from qgis.PyQt.QtPrintSupport import QPrinter, QPrintPreviewDialog +from qgis.PyQt.QtWebKit import QWebSettings +from qgis.PyQt.QtWebKitWidgets import QWebPage, QWebView +from qgis.PyQt.QtWidgets import QVBoxLayout, QWidget + +from ..tools.twwnetwork import TwwGraphManager +from ..utils.translation import TwwJsTranslator +from ..utils.ui import plugin_root_path + + +class TwwWebPage(QWebPage): + logger = logging.getLogger(__name__) + + def __init__(self, parent): + QWebPage.__init__(self, parent) + + def javaScriptConsoleMessage(self, msg, line, source): + self.logger.debug(f"{source} line {line}: {msg}") + + +class TwwPlotSVGWidget(QWidget): + webView = None + webPage = None + frame = None + profile = None + verticalExaggeration = 10 + jsTranslator = TwwJsTranslator() + + # Signals emitted triggered by javascript actions + reachClicked = pyqtSignal([str], name="reachClicked") + reachMouseOver = pyqtSignal([str], name="reachMouseOver") + reachMouseOut = pyqtSignal([str], name="reachMouseOut") + reachPointClicked = pyqtSignal([str, str], name="reachPointClicked") + reachPointMouseOver = pyqtSignal([str, str], name="reachPointMouseOver") + reachPointMouseOut = pyqtSignal([str, str], name="reachPointMouseOut") + specialStructureClicked = pyqtSignal([str], name="specialStructureClicked") + specialStructureMouseOver = pyqtSignal([str], name="specialStructureMouseOver") + specialStructureMouseOut = pyqtSignal([str], name="specialStructureMouseOut") + + # Signals emitted for javascript + profileChanged = pyqtSignal([str], name="profileChanged") + verticalExaggerationChanged = pyqtSignal([int], name="verticalExaggerationChanged") + + def __init__(self, parent, network_analyzer: TwwGraphManager, url: str = None): + QWidget.__init__(self, parent) + + self.webView = QWebView() + self.webView.setPage(TwwWebPage(self.webView)) + + self.networkAnalyzer = network_analyzer + + settings = QSettings() + + layout = QVBoxLayout(self) + if url is None: + # Starting with QGIS 3.4, QWebView requires paths with / even on windows. + default_url = plugin_root_path().replace("\\", "/") + "/svgprofile/index.html" + url = settings.value("/TWW/SvgProfilePath", default_url) + url = "file:///" + url + + developer_mode = settings.value("/TWW/DeveloperMode", False, type=bool) + + if developer_mode is True: + self.webView.page().settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True) + else: + self.webView.setContextMenuPolicy(Qt.NoContextMenu) + + self.webView.load(QUrl(url)) + self.frame = self.webView.page().mainFrame() + self.frame.javaScriptWindowObjectCleared.connect(self.initJs) + + layout.addWidget(self.webView) + + def setProfile(self, profile): + self.profile = profile + # Forward to javascript + self.profileChanged.emit(profile.asJson()) + + def initJs(self): + self.frame.addToJavaScriptWindowObject("profileProxy", self) + self.frame.addToJavaScriptWindowObject("i18n", self.jsTranslator) + + def changeVerticalExaggeration(self, val): + self.verticalExaggeration = val + self.verticalExaggerationChanged.emit(val) + + def printProfile(self): + printer = QPrinter(QPrinter.HighResolution) + printer.setOutputFormat(QPrinter.PdfFormat) + printer.setPaperSize(QPrinter.A4) + printer.setOrientation(QPrinter.Landscape) + + printpreviewdlg = QPrintPreviewDialog() + printpreviewdlg.paintRequested.connect(self.printRequested) + + printpreviewdlg.exec_() + + @pyqtSlot(QPrinter) + def printRequested(self, printer): + self.webView.print_(printer) + + @pyqtSlot(str) + def onReachClicked(self, obj_id): + self.reachClicked.emit(obj_id) + + @pyqtSlot(str) + def onReachMouseOver(self, obj_id): + self.reachMouseOver.emit(obj_id) + + @pyqtSlot(str) + def onReachMouseOut(self, obj_id): + self.reachMouseOut.emit(obj_id) + + @pyqtSlot(str, str) + def onReachPointClicked(self, obj_id, reach_obj_id): + self.reachPointClicked.emit(obj_id, reach_obj_id) + + @pyqtSlot(str, str) + def onReachPointMouseOver(self, obj_id, reach_obj_id): + self.reachPointMouseOver.emit(obj_id, reach_obj_id) + + @pyqtSlot(str, str) + def onReachPointMouseOut(self, obj_id, reach_obj_id): + self.reachPointMouseOut.emit(obj_id, reach_obj_id) + + @pyqtSlot(str) + def onSpecialStructureClicked(self, obj_id): + self.specialStructureClicked.emit(obj_id) + + @pyqtSlot(str) + def onSpecialStructureMouseOver(self, obj_id): + self.specialStructureMouseOver.emit(obj_id) + + @pyqtSlot(str) + def onSpecialStructureMouseOut(self, obj_id): + self.specialStructureMouseOut.emit(obj_id) + + # Is called from the webView when it's been reloaded and wants to have the + # profile information resent + @pyqtSlot() + def updateProfile(self): + if self.profile: + self.profileChanged.emit(self.profile.asJson()) + self.verticalExaggerationChanged.emit(self.verticalExaggeration) diff --git a/plugin/teksi_wastewater/gui/twwprofiledockwidget.py b/plugin/teksi_wastewater/gui/twwprofiledockwidget.py new file mode 100644 index 0000000..57dbc70 --- /dev/null +++ b/plugin/teksi_wastewater/gui/twwprofiledockwidget.py @@ -0,0 +1,232 @@ +# ----------------------------------------------------------- +# +# Profile +# Copyright (C) 2012 Matthias Kuhn +# ----------------------------------------------------------- +# +# licensed under the terms of GNU GPL 2 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, print to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# --------------------------------------------------------------------- + + +from qgis.core import QgsFeatureRequest, QgsProject +from qgis.PyQt.QtCore import Qt, pyqtSignal, pyqtSlot +from qgis.PyQt.QtWidgets import ( + QAction, + QCheckBox, + QDialog, + QDialogButtonBox, + QDockWidget, + QGridLayout, + QMessageBox, +) + +from ..utils import get_ui_class +from ..utils.twwlayermanager import TwwLayerManager + +DOCK_WIDGET_UI = get_ui_class("twwdockwidget.ui") + + +class TwwProfileDockWidget(QDockWidget, DOCK_WIDGET_UI): + # Signal emitted when the widget is closed + closed = pyqtSignal() + canvas = None + addDockWidget = None + # Lookup table for vertical exaggeration values + veLUT = {1: 1, 2: 2, 3: 3, 4: 5, 5: 10, 6: 20, 7: 30, 8: 50, 9: 100, 10: 500} + + def __init__(self, parent, canvas, add_dock_widget): + QDockWidget.__init__(self, parent) + self.setupUi(self) + + self.selectCurrentPathAction = QAction(self.tr("Select current path"), self.selectButton) + self.selectCurrentPathAction.triggered.connect(self.onSelectCurrentPathAction) + self.selectButton.setDefaultAction(self.selectCurrentPathAction) + self.configureSelectionAction = QAction(self.tr("Configure Select"), self.selectButton) + self.configureSelectionAction.triggered.connect(self.onConfigureSelectAction) + self.selectButton.addAction(self.configureSelectionAction) + self.setAttribute(Qt.WA_DeleteOnClose) + self.canvas = canvas + self.addDockWidget = add_dock_widget + + self.plotWidget = None + + def showIt(self): + # self.setLocation( Qt.BottomDockWidgetArea ) + self.location = Qt.BottomDockWidgetArea + minsize = self.minimumSize() + maxsize = self.maximumSize() + self.setMinimumSize(minsize) + self.setMaximumSize(maxsize) + self.canvas.setRenderFlag(False) + + self.addDockWidget(self.location, self) + self.canvas.setRenderFlag(True) + + self.printButton.clicked.connect(self.onPrintButtonClicked) + + self.mSliderVerticalExaggeration.valueChanged.connect(self.onVerticalExaggerationChanged) + + def closeEvent(self, event): + self.closed.emit() + return QDockWidget.closeEvent(self, event) + + def addPlotWidget(self, plot_widget): + self.plotWidget = plot_widget + self.verticalLayoutForPlot.addWidget(self.plotWidget) + ve_val = self.veLUT[self.mSliderVerticalExaggeration.value()] + self.plotWidget.changeVerticalExaggeration(ve_val) + + @pyqtSlot(int) + def onVerticalExaggerationChanged(self, value): + ve_val = self.veLUT[value] + self.mLblVerticalExaggeration.setText(str(ve_val) + "x") + + if self.plotWidget: + self.plotWidget.changeVerticalExaggeration(ve_val) + + @pyqtSlot() + def onPrintButtonClicked(self): + if not self.plotWidget: + QMessageBox.critical( + self, + self.tr("Print function not available"), + self.tr("Print function not available because of missing dependeny QtWebKit"), + ) + return + + self.plotWidget.printProfile() + + @pyqtSlot() + def onConfigureSelectAction(self): + dlg = QDialog() + dlg.setWindowTitle(self.tr("Selection Options")) + dlg.setLayout(QGridLayout()) + + ww_current_checkbox = QCheckBox(self.tr("Wastewater current")) + status, _ = QgsProject.instance().readBoolEntry("Tww", "FollowWastewaterCurrent", True) + ww_current_checkbox.setChecked(status) + ww_planned_checkbox = QCheckBox(self.tr("Wastewater planned")) + status, _ = QgsProject.instance().readBoolEntry("Tww", "FollowWastewaterPlanned", True) + ww_planned_checkbox.setChecked(status) + rw_current_checkbox = QCheckBox(self.tr("Rainwater current")) + status, _ = QgsProject.instance().readBoolEntry("Tww", "FollowRainwaterCurrent", True) + rw_current_checkbox.setChecked(status) + rw_planned_checkbox = QCheckBox(self.tr("Rainwater planned")) + status, _ = QgsProject.instance().readBoolEntry("Tww", "FollowRainwaterPlanned", True) + rw_planned_checkbox.setChecked(status) + btn_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + btn_box.accepted.connect(dlg.accept) + btn_box.rejected.connect(dlg.reject) + dlg.layout().addWidget(ww_current_checkbox) + dlg.layout().addWidget(ww_planned_checkbox) + dlg.layout().addWidget(rw_current_checkbox) + dlg.layout().addWidget(rw_planned_checkbox) + dlg.layout().addWidget(btn_box) + + if dlg.exec_(): + QgsProject.instance().writeEntry( + "Tww", "FollowWastewaterCurrent", ww_current_checkbox.isChecked() + ) + QgsProject.instance().writeEntry( + "Tww", "FollowWastewaterPlanned", ww_planned_checkbox.isChecked() + ) + QgsProject.instance().writeEntry( + "Tww", "FollowRainwaterCurrent", rw_current_checkbox.isChecked() + ) + QgsProject.instance().writeEntry( + "Tww", "FollowRainwaterPlanned", rw_planned_checkbox.isChecked() + ) + + @pyqtSlot() + def onSelectCurrentPathAction(self): + reaches = list() + wastewater_nodes = list() + wastewater_structures = list() + + for item in self.edges: + item_information = item[2] + if item_information["objType"] == "reach": + reaches.append(item_information["baseFeature"]) + + for item in self.nodes: + if item["objType"] == "wastewater_node": + wastewater_nodes.append(item["objId"]) + + tww_wastewater_structures_layer = TwwLayerManager.layer("vw_tww_wastewater_structure") + wastewater_nodes_layer = TwwLayerManager.layer("vw_wastewater_node") + tww_reach_layer = TwwLayerManager.layer("vw_tww_reach") + catchment_areas_layer = TwwLayerManager.layer("od_catchment_area") + + wastewater_node_list = ",".join("'" + id + "'" for id in wastewater_nodes) + reach_list = ",".join("'" + id + "'" for id in reaches) + + if catchment_areas_layer: + request = QgsFeatureRequest() + filters = list() + if QgsProject.instance().readBoolEntry("Tww", "FollowWastewaterCurrent", True)[0]: + filters.append( + f"fk_wastewater_networkelement_ww_current IN ({wastewater_node_list})" + ) + if QgsProject.instance().readBoolEntry("Tww", "FollowWastewaterPlanned", True)[0]: + filters.append( + f"fk_wastewater_networkelement_ww_planned IN ({wastewater_node_list})" + ) + if QgsProject.instance().readBoolEntry("Tww", "FollowRainwaterCurrent", True)[0]: + filters.append( + f"fk_wastewater_networkelement_rw_current IN ({wastewater_node_list})" + ) + if QgsProject.instance().readBoolEntry("Tww", "FollowRainwaterPlanned", True)[0]: + filters.append( + f"fk_wastewater_networkelement_rw_planned IN ({wastewater_node_list})" + ) + + if filters: + request.setFilterExpression(" OR ".join(filters)) + features = catchment_areas_layer.getFeatures(request) + catchment_areas_layer.select([f.id() for f in features]) + + if tww_reach_layer: + request = QgsFeatureRequest() + request.setFilterExpression(f"obj_id IN ({reach_list})") + features = tww_reach_layer.getFeatures(request) + tww_reach_layer.select([f.id() for f in features]) + + if wastewater_nodes_layer: + request = QgsFeatureRequest() + request.setFilterExpression(f"obj_id IN ({wastewater_node_list})") + features = wastewater_nodes_layer.getFeatures(request) + ids = list() + for feature in features: + ids.append(feature.id()) + wastewater_structures.append(feature["fk_wastewater_structure"]) + wastewater_nodes_layer.select(ids) + + wastewater_structure_list = ",".join( + "'" + id + "'" for id in wastewater_structures if type(id) is str + ) + + if tww_wastewater_structures_layer: + request = QgsFeatureRequest() + request.setFilterExpression(f"obj_id IN ({wastewater_structure_list})") + features = tww_wastewater_structures_layer.getFeatures(request) + tww_wastewater_structures_layer.select([f.id() for f in features]) + + def setTree(self, nodes, edges): + self.nodes = nodes + self.edges = edges + self.selectCurrentPathAction.setEnabled(self.nodes is not None) diff --git a/plugin/teksi_wastewater/gui/twwsettingsdialog.py b/plugin/teksi_wastewater/gui/twwsettingsdialog.py new file mode 100644 index 0000000..9b7ba3b --- /dev/null +++ b/plugin/teksi_wastewater/gui/twwsettingsdialog.py @@ -0,0 +1,186 @@ +# ----------------------------------------------------------- +# +# Profile +# Copyright (C) 2012 Matthias Kuhn +# ----------------------------------------------------------- +# +# licensed under the terms of GNU GPL 2 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, print to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# --------------------------------------------------------------------- + +import logging + +from qgis.core import QgsProject +from qgis.PyQt.QtCore import QSettings, pyqtSlot +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtWidgets import QDialog, QFileDialog + +from ..utils import get_ui_class + +DIALOG_UI = get_ui_class("twwsettingsdialog.ui") + +LOGFORMAT = "%(asctime)s:%(levelname)s:%(module)s:%(message)s" + + +class TwwSettingsDialog(QDialog, DIALOG_UI): + settings = QSettings() + + def __init__(self, parent=None): + QDialog.__init__(self, parent) + self.setupUi(self) + + project = QgsProject.instance() + + svgprofile_path = self.settings.value("/TWW/SvgProfilePath", None) + if svgprofile_path: + self.mGbOverrideDefaultProfileTemplate.setChecked(True) + self.mProfileTemplateFile.setText(svgprofile_path) + else: + self.mGbOverrideDefaultProfileTemplate.setChecked(False) + + develmode = self.settings.value("/TWW/DeveloperMode", False, type=bool) + self.mCbDevelMode.setChecked(develmode) + + adminmode = self.settings.value("/TWW/AdminMode", False, type=bool) + self.mCbAdminMode.setChecked(adminmode) + + lyr_special_structures, _ = project.readEntry("TWW", "SpecialStructureLayer") + lyr_graph_edges, _ = project.readEntry("TWW", "GraphEdgeLayer") + lyr_graph_nodes, _ = project.readEntry("TWW", "GraphNodeLayer") + + self.initLayerCombobox(self.mCbSpecialStructures, lyr_special_structures) + self.initLayerCombobox(self.mCbGraphEdges, lyr_graph_edges) + self.initLayerCombobox(self.mCbGraphNodes, lyr_graph_nodes) + + self.mCurrentProfileColorButton.setColor( + QColor(self.settings.value("/TWW/CurrentProfileColor", "#FF9500")) + ) + self.mHelperLineColorButton.setColor( + QColor(self.settings.value("/TWW/HelperLineColor", "#FFD900")) + ) + self.mHighlightColorButton.setColor( + QColor(self.settings.value("/TWW/HighlightColor", "#40FF40")) + ) + + self.mPbnChooseProfileTemplateFile.clicked.connect(self.onChooseProfileTemplateFileClicked) + self.mPbnChooseLogFile.clicked.connect(self.onChooseLogFileClicked) + + self.accepted.connect(self.onAccept) + + loglevel = self.settings.value("/TWW/LogLevel", "Warning") + self.mCbLogLevel.setCurrentIndex(self.mCbLogLevel.findText(self.tr(loglevel))) + + logfile = self.settings.value("/TWW/LogFile", None) + + if logfile is not None: + self.mGbLogToFile.setChecked(True) + self.mLogFile.setText(logfile) + else: + self.mGbLogToFile.setChecked(False) + + def initLayerCombobox(self, combobox, default): + reg = QgsProject.instance() + for key, layer in list(reg.mapLayers().items()): + combobox.addItem(layer.name(), key) + + idx = combobox.findData(default) + if idx != -1: + combobox.setCurrentIndex(idx) + + @pyqtSlot() + def onAccept(self): + twwlogger = logging.getLogger("tww") + # General settings + if self.mGbOverrideDefaultProfileTemplate.isChecked() and self.mProfileTemplateFile.text(): + self.settings.setValue("/TWW/SvgProfilePath", self.mProfileTemplateFile.text()) + else: + self.settings.remove("/TWW/SvgProfilePath") + + self.settings.setValue("/TWW/DeveloperMode", self.mCbDevelMode.isChecked()) + self.settings.setValue("/TWW/AdminMode", self.mCbAdminMode.isChecked()) + + # Logging + if hasattr(twwlogger, "twwFileHandler"): + twwlogger.removeHandler(twwlogger.twwFileHandler) + del twwlogger.twwFileHandler + + if self.mGbLogToFile.isChecked(): + logfile = str(self.mLogFile.text()) + log_handler = logging.FileHandler(logfile) + fmt = logging.Formatter(LOGFORMAT) + log_handler.setFormatter(fmt) + twwlogger.addHandler(log_handler) + twwlogger.twwFileHandler = log_handler + self.settings.setValue("/TWW/LogFile", logfile) + else: + self.settings.setValue("/TWW/LogFile", None) + + if self.tr("Debug") == self.mCbLogLevel.currentText(): + twwlogger.setLevel(logging.DEBUG) + self.settings.setValue("/TWW/LogLevel", "Debug") + elif self.tr("Info") == self.mCbLogLevel.currentText(): + twwlogger.setLevel(logging.INFO) + self.settings.setValue("/TWW/LogLevel", "Info") + elif self.tr("Warning") == self.mCbLogLevel.currentText(): + twwlogger.setLevel(logging.WARNING) + self.settings.setValue("/TWW/LogLevel", "Warning") + elif self.tr("Error") == self.mCbLogLevel.currentText(): + twwlogger.setLevel(logging.ERROR) + self.settings.setValue("/TWW/LogLevel", "Error") + + # Save colors + self.settings.setValue("/TWW/HelperLineColor", self.mHelperLineColorButton.color().name()) + self.settings.setValue("/TWW/HighlightColor", self.mHighlightColorButton.color().name()) + self.settings.setValue( + "/TWW/CurrentProfileColor", self.mCurrentProfileColorButton.color().name() + ) + + # Project specific settings + project = QgsProject.instance() + + specialstructure_idx = self.mCbSpecialStructures.currentIndex() + graph_edgelayer_idx = self.mCbGraphEdges.currentIndex() + graph_nodelayer_idx = self.mCbGraphNodes.currentIndex() + + project.writeEntry( + "TWW", + "SpecialStructureLayer", + self.mCbSpecialStructures.itemData(specialstructure_idx), + ) + project.writeEntry( + "TWW", "GraphEdgeLayer", self.mCbGraphEdges.itemData(graph_edgelayer_idx) + ) + project.writeEntry( + "TWW", "GraphNodeLayer", self.mCbGraphNodes.itemData(graph_nodelayer_idx) + ) + + @pyqtSlot() + def onChooseProfileTemplateFileClicked(self): + filename, __ = QFileDialog.getOpenFileName( + self, + self.tr("Select profile template"), + "", + self.tr("HTML files(*.htm *.html)"), + ) + self.mProfileTemplateFile.setText(filename) + + @pyqtSlot() + def onChooseLogFileClicked(self): + filename, __ = QFileDialog.getSaveFileName( + self, self.tr("Select log file"), "", self.tr("Log files(*.log)") + ) + self.mLogFile.setText(filename) diff --git a/plugin/teksi_wastewater/gui/twwwizard.py b/plugin/teksi_wastewater/gui/twwwizard.py new file mode 100644 index 0000000..a41369f --- /dev/null +++ b/plugin/teksi_wastewater/gui/twwwizard.py @@ -0,0 +1,95 @@ +# ----------------------------------------------------------- +# +# TEKSI Wastewater +# Copyright (C) 2014 Matthias Kuhn +# ----------------------------------------------------------- +# +# licensed under the terms of GNU GPL 2 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, print to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# --------------------------------------------------------------------- + +import logging + +from qgis.PyQt.QtCore import pyqtSlot +from qgis.PyQt.QtWidgets import QDockWidget + +from ..tools.twwmaptooladdfeature import TwwMapToolAddReach +from ..utils import get_ui_class +from ..utils.twwlayermanager import TwwLayerManager + +DOCK_WIDGET = get_ui_class("twwwizard.ui") + + +class TwwWizard(QDockWidget, DOCK_WIDGET): + logger = logging.getLogger(__name__) + + def __init__(self, parent, iface): + QDockWidget.__init__(self, parent) + self.setupUi(self) + self.layerComboBox.currentIndexChanged.connect(self.layerChanged) + self.stateButton.clicked.connect(self.stateChanged) + self.iface = iface + self.layerComboBox.insertItem( + self.layerComboBox.count(), + self.tr("Wastewater Structure"), + "wastewater_structure", + ) + self.layerComboBox.insertItem(self.layerComboBox.count(), self.tr("Reach"), "reach") + self.stateButton.setProperty("state", "inactive") + + self.mapToolAddReach = TwwMapToolAddReach( + self.iface, TwwLayerManager.layer("vw_tww_reach") + ) + + @pyqtSlot(int) + def layerChanged(self, index): + for lyr in [ + TwwLayerManager.layer("vw_tww_wastewater_structure"), + TwwLayerManager.layer("vw_tww_reach"), + ]: + lyr.commitChanges() + + if ( + self.layerComboBox.itemData(self.layerComboBox.currentIndex()) + == "wastewater_structure" + ): + lyr = TwwLayerManager.layer("vw_tww_wastewater_structure") + lyr.startEditing() + self.iface.setActiveLayer(lyr) + self.iface.actionAddFeature().trigger() + + elif self.layerComboBox.itemData(self.layerComboBox.currentIndex()) == "reach": + lyr = TwwLayerManager.layer("vw_tww_reach") + lyr.startEditing() + self.iface.mapCanvas().setMapTool(self.mapToolAddReach) + + @pyqtSlot() + def stateChanged(self): + if self.stateButton.property("state") != "active": + self.layerComboBox.setEnabled(True) + self.layerChanged(0) + self.stateButton.setText(self.tr("Stop Data Entry")) + self.stateButton.setProperty("state", "active") + else: + for lyr in [ + TwwLayerManager.layer("vw_tww_reach"), + TwwLayerManager.layer("vw_tww_wastewater_structure"), + ]: + lyr.commitChanges() + self.layerComboBox.setEnabled(False) + self.stateButton.setText(self.tr("Start Data Entry")) + self.stateButton.setProperty("state", "inactive") diff --git a/plugin/teksi_wastewater/icons/interlis_export.svg b/plugin/teksi_wastewater/icons/interlis_export.svg new file mode 100644 index 0000000..561775c --- /dev/null +++ b/plugin/teksi_wastewater/icons/interlis_export.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/interlis_import.svg b/plugin/teksi_wastewater/icons/interlis_import.svg new file mode 100644 index 0000000..3dd3fe1 --- /dev/null +++ b/plugin/teksi_wastewater/icons/interlis_import.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/link-wastewater-networkelement.svg b/plugin/teksi_wastewater/icons/link-wastewater-networkelement.svg new file mode 100644 index 0000000..4ed1c91 --- /dev/null +++ b/plugin/teksi_wastewater/icons/link-wastewater-networkelement.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/newproject.svg b/plugin/teksi_wastewater/icons/newproject.svg new file mode 100644 index 0000000..854eb12 --- /dev/null +++ b/plugin/teksi_wastewater/icons/newproject.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/refresh-network.svg b/plugin/teksi_wastewater/icons/refresh-network.svg new file mode 100644 index 0000000..7c8dd6a --- /dev/null +++ b/plugin/teksi_wastewater/icons/refresh-network.svg @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + SQL + + + + + diff --git a/plugin/teksi_wastewater/icons/teksi-abwasser-logo.svg b/plugin/teksi_wastewater/icons/teksi-abwasser-logo.svg new file mode 100644 index 0000000..d3af21e --- /dev/null +++ b/plugin/teksi_wastewater/icons/teksi-abwasser-logo.svg @@ -0,0 +1,55 @@ + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/twwIcon.png b/plugin/teksi_wastewater/icons/twwIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..aad5864cfc0b851129fdaae29e78b6871f3a5a2c GIT binary patch literal 974 zcmV;<12O!GP)YwQ15Vv)d_ohEy9s#1TBsW!D7 zx%b#|fh8oQegBc--N)e-EFm7cvv<@u&&>e!(Ag{|k zt)^K*B*6fb7pK8klopVeutZD;)10O#6s}Fgiu4%dX2u4V5ez_yL5qx(XfO!oi++p#-*VJNY+H)!1he9^Txtd#L2@#2XJiva>PbUz0SwU!f<5wvd{n+ zR;p2us{+fi=;-Y6T32GwB5R49e}H{kbYK`7m##m@!0?3EI*FLU{;e7O0&L3DATL{q z#uhWK)%GAM!S@O`>ea|ekKqp>Jt-Pi+W=0RTA@`iSgelsSuYVW*jqC9e}OYVoGc7? z>e}$#F2IJ>vyb5al$k2y4d&ErF;S8QNAx*?f}tJ^!C|7SP$YA zk(@jdu>d(b-?t=Z09m9I5evl-QVyt;aa?U!tX#woz~D~@WRViEv=pDe{@@HSI^p66 z;EQb(IXXF5!r<1Ux12lpEbc01oX+htoyjcI_LV^^;ByF5jJdr#J&lIy`vrqzgMs^*Fpkg@;cq=(BnM7U~;& z(Q5wA9{>Q{sA)$-lLdz>Hlt!o5=^(7X4Ooy1U1jPapvkPPMM590MjhN@e9qckGoL0 zcRfb!_8IeI6E2wUzsL2OcFvg$DBA501R+56m6xb_){XV6G|*|sVI3I5C(8($UiV?} zr;~daMY|cXkg@?#%vt=8%w0n;cMbV#n<0@9-{_RY@cN)qf>5$Lyn!Nw2v1&YI$^Pn wmrc1zJP0ccX;+GNix|qL)r8ldKbn8`7kWYtBIkvk@Bjb+07*qoM6N<$f_|;E+W-In literal 0 HcmV?d00001 diff --git a/plugin/teksi_wastewater/icons/twwIcon.svg b/plugin/teksi_wastewater/icons/twwIcon.svg new file mode 100644 index 0000000..66c5cab --- /dev/null +++ b/plugin/teksi_wastewater/icons/twwIcon.svg @@ -0,0 +1,473 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + style="overflow:visible"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/wastewater-downstream.svg b/plugin/teksi_wastewater/icons/wastewater-downstream.svg new file mode 100644 index 0000000..b9426bb --- /dev/null +++ b/plugin/teksi_wastewater/icons/wastewater-downstream.svg @@ -0,0 +1,2389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/wastewater-profile.svg b/plugin/teksi_wastewater/icons/wastewater-profile.svg new file mode 100644 index 0000000..daccc50 --- /dev/null +++ b/plugin/teksi_wastewater/icons/wastewater-profile.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/wastewater-upstream.svg b/plugin/teksi_wastewater/icons/wastewater-upstream.svg new file mode 100644 index 0000000..d84cc3c --- /dev/null +++ b/plugin/teksi_wastewater/icons/wastewater-upstream.svg @@ -0,0 +1,2389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/icons/wizard.svg b/plugin/teksi_wastewater/icons/wizard.svg new file mode 100644 index 0000000..1f2e0fc --- /dev/null +++ b/plugin/teksi_wastewater/icons/wizard.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/teksi_wastewater/interlis/config.py b/plugin/teksi_wastewater/interlis/config.py new file mode 100644 index 0000000..1bfeeb5 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/config.py @@ -0,0 +1,25 @@ +import os + +BASE = os.path.dirname(__file__) + +ILIVALIDATOR = os.path.join(BASE, "bin", "ilivalidator-1.11.9", "ilivalidator-1.11.9.jar") + +TWW_DEFAULT_PGSERVICE = "pg_tww" +TWW_OD_SCHEMA = "tww_od" +TWW_VL_SCHEMA = "tww_vl" +TWW_SYS_SCHEMA = "tww_sys" +ABWASSER_SCHEMA = "pg2ili_abwasser" +TWW_AG_SCHEMA = "tww_ag6496" +MODEL_NAME_VSA_KEK = "VSA_KEK_2020_1_LV95" +MODEL_NAME_SIA405_ABWASSER = "SIA405_ABWASSER_2020_1_LV95" +MODEL_NAME_SIA405_BASE_ABWASSER = "SIA405_Base_Abwasser_1_LV95" +MODEL_NAME_DSS = "DSS_2020_1_LV95" +MODEL_NAME_AG96 = "Genereller_Entwaesserungsplan_AG" +MODEL_NAME_AG64 = "Abwasserkataster_AG_V2_LV95" + +TOPIC_NAME_SIA405_ADMINISTRATION = "SIA405_Base_Abwasser_1_LV95.Administration" +TOPIC_NAME_SIA405_ABWASSER = "SIA405_ABWASSER_2020_1_LV95.SIA405_Abwasser" +TOPIC_NAME_DSS = "DSS_2020_1_LV95.Siedlungsentwaesserung" +TOPIC_NAME_KEK = "VSA_KEK_2020_1_LV95.KEK" +TOPIC_NAME_AG96 = "Genereller_Entwaesserungsplan_AG.AG96" +TOPIC_NAME_AG64 = "Abwasserkataster_AG_V2_LV95.AG64" diff --git a/plugin/teksi_wastewater/interlis/gui/__init__.py b/plugin/teksi_wastewater/interlis/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugin/teksi_wastewater/interlis/gui/editors/__init__.py b/plugin/teksi_wastewater/interlis/gui/editors/__init__.py new file mode 100644 index 0000000..10af290 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/editors/__init__.py @@ -0,0 +1,25 @@ +import importlib +import pkgutil + + +def import_submodules(package, recursive=True): + """Import all submodules of a module, recursively, including subpackages + + :param package: package (name or actual module) + :type package: str | module + :rtype: dict[str, types.ModuleType] + + Taken from https://stackoverflow.com/a/25562415/13690651 + """ + if isinstance(package, str): + package = importlib.import_module(package) + results = {} + for loader, name, is_pkg in pkgutil.walk_packages(package.__path__): + full_name = package.__name__ + "." + name + results[full_name] = importlib.import_module(full_name) + if recursive and is_pkg: + results.update(import_submodules(full_name)) + return results + + +import_submodules(__name__) diff --git a/plugin/teksi_wastewater/interlis/gui/editors/base.py b/plugin/teksi_wastewater/interlis/gui/editors/base.py new file mode 100644 index 0000000..bed275a --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/editors/base.py @@ -0,0 +1,155 @@ +import os +from collections import defaultdict + +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtGui import QBrush, QColor +from qgis.PyQt.QtWidgets import QTreeWidgetItem, QWidget +from qgis.PyQt.uic import loadUi +from sqlalchemy import inspect + + +class Editor: + """ + Base class to manage import options for TWW classes. + + Editor subclasses are responsible of: + - managing a listwidgetitem + - providing a widget to edit options + - change the current session objects according to widget interaction + - validate objects according to the current session + """ + + # Validity + INVALID = "INVALID" + UNKNOWN = "UNKNOWN" + WARNING = "WARNING" + VALID = "VALID" + + # State + NEW = "NEW" + DELETED = "DELETED" + MODIFIED = "MODIFIED" + EXISTING = "EXISTING" + + class_name = "base" + widget_name = "base.ui" + + registry = defaultdict(lambda: Editor) + + def __init_subclass__(cls): + """ + Populates Editor.registry + """ + Editor.registry[cls.class_name] = cls + + @staticmethod + def factory(main_dialog, session, obj): + """ + Creates an Editor instance, choosing the correct subclass from the registry + """ + editor_class = Editor.registry[obj.__class__.__name__] + return editor_class(main_dialog, session, obj) + + def __init__(self, main_dialog, session, obj): + self.main_dialog = main_dialog + self.session = session + self.obj = obj + + self.preprocess() + + self.update_state() + + @property + def listitem(self): + """ + The editor's listitem (created on the fly if needed) + """ + if not hasattr(self, "_listitem"): + self._listitem = QTreeWidgetItem() + self._listitem.setCheckState( + 0, Qt.Checked if self.initially_checked() else Qt.Unchecked + ) + self.update_listitem() + return self._listitem + + def update_listitem(self): + disp_id = str( + getattr(self.obj, "obj_id", getattr(self.obj, "value_en", "?")) + ) # some elements may not have obj_id, such as value_lists + self.listitem.setText(0, getattr(self.obj, "identifier", disp_id)) + self.listitem.setToolTip(0, disp_id) + + self.listitem.setText(1, self.status) + + self.listitem.setText(2, self.validity) + if self.status == Editor.EXISTING: + color = "lightgray" + elif self.validity == Editor.INVALID: + color = "red" + elif self.validity == Editor.WARNING: + color = "orange" + elif self.validity == Editor.VALID: + color = "lightgreen" + else: + color = "lightgray" + self.listitem.setBackground(2, QBrush(QColor(color))) + + @property + def widget(self): + """ + The editor's widget (created on the fly if needed) + """ + if not hasattr(self, "_widget"): + + class BaseWidget(QWidget): + pass + + self._widget = BaseWidget() + loadUi(os.path.join(os.path.dirname(__file__), self.widget_name), self._widget) + self.init_widget() + return self._widget + + def preprocess(self): + """ + Run some preprocessing steps (such as auto-assigning data)... To be overriden by subclasses. + """ + + def init_widget(self): + """ + Initialize the widget here, for things like connecting signals... To be overriden by subclasses. + """ + + def update_widget(self): + """ + Update the widget here, for things like repopulating from session... To be overriden by subclasses. + """ + + def update_state(self): + """ + Updates status and calls validate. Call this when the underlying object may have changed. + """ + obj_inspect = inspect(self.obj) + if obj_inspect.pending: + self.status = Editor.NEW + elif obj_inspect.deleted: + self.status = Editor.DELETED + elif obj_inspect.modified: + self.status = Editor.MODIFIED + elif obj_inspect.persistent: + self.status = Editor.EXISTING + else: + self.status = Editor.UNKNOWN + self.validate() + + def validate(self): + """ + Updates validity and message. To be overriden by subclasses. You should probably call update_state if you need to revalidate. + """ + self.validity = Editor.VALID + self.message = "No validity check" + + def initially_checked(self): + """ + Determines if the item must be initially checked. To be overriden by subclasses. + """ + return True diff --git a/plugin/teksi_wastewater/interlis/gui/editors/base.ui b/plugin/teksi_wastewater/interlis/gui/editors/base.ui new file mode 100644 index 0000000..c799300 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/editors/base.ui @@ -0,0 +1,31 @@ + + + Form + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + No import options + + + Qt::AlignCenter + + + + + + + + diff --git a/plugin/teksi_wastewater/interlis/gui/editors/damage_channel.py b/plugin/teksi_wastewater/interlis/gui/editors/damage_channel.py new file mode 100644 index 0000000..0bdcf0e --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/editors/damage_channel.py @@ -0,0 +1,17 @@ +from .base import Editor + + +class DamageChannelEditor(Editor): + class_name = "damage_channel" + + # DISABLED FOR NOW AS PER https://github.com/TWW/tww2ili/issues/8 + # this would allow to deselect BCD inspections (start of inspection) by default, + # as they don't provide valuable information. Once consensus is reached, we + # can remove this Editor altogether. + # def initially_checked(self): + # """ + # Determines if the item must be initially checked. To be overriden by subclasses. + # """ + # if self.obj.channel_damage_code__REL.value_en == "BCD": + # return False + # return True diff --git a/plugin/teksi_wastewater/interlis/gui/editors/data_media.py b/plugin/teksi_wastewater/interlis/gui/editors/data_media.py new file mode 100644 index 0000000..cb03ee2 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/editors/data_media.py @@ -0,0 +1,41 @@ +import os + +from .base import Editor + + +class DataMediaEditor(Editor): + class_name = "data_media" + widget_name = "data_media.ui" + + def __init__(self, *args, **kwargs): + self._path_was_changed = False + super().__init__(*args, **kwargs) + + def init_widget(self): + self.widget.pushButton.pressed.connect(lambda: self.button_clicked()) + self.update_widget() + + def update_widget(self): + self.widget.lineEdit.setText(self.obj.path) + + def button_clicked(self): + self.obj.path = self.widget.lineEdit.text() + self._path_was_changed = True + + self.update_state() + self.main_dialog.refresh_editor(self) + self.main_dialog.update_tree() + + def validate(self): + if not self._path_was_changed: + self.validity = Editor.WARNING + self.message = "Path was not adapted." + elif not os.path.exists(self.obj.path): + self.validity = Editor.WARNING + self.message = "This path does not exist on the current system" + elif not os.path.isdir(self.obj.path): + self.validity = Editor.WARNING + self.message = "This path is not a directory" + else: + self.validity = Editor.VALID + self.message = "No warning" diff --git a/plugin/teksi_wastewater/interlis/gui/editors/data_media.ui b/plugin/teksi_wastewater/interlis/gui/editors/data_media.ui new file mode 100644 index 0000000..1722c07 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/editors/data_media.ui @@ -0,0 +1,48 @@ + + + Form + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + + + + + + Set + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/plugin/teksi_wastewater/interlis/gui/featureselectorwidget.py b/plugin/teksi_wastewater/interlis/gui/featureselectorwidget.py new file mode 100644 index 0000000..068695a --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/featureselectorwidget.py @@ -0,0 +1,236 @@ +# ----------------------------------------------------------- +# +# QGIS wincan 2 TWW Plugin +# Copyright (C) 2016 Denis Rouzaud +# +# ----------------------------------------------------------- +# +# licensed under the terms of GNU GPL 2 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# --------------------------------------------------------------------- + + +from qgis.core import ( + Qgis, + QgsApplication, + QgsExpression, + QgsExpressionContext, + QgsExpressionContextScope, + QgsFeature, +) +from qgis.gui import QgsHighlight, QgsMapToolIdentifyFeature +from qgis.PyQt.QtCore import QSettings, QTimer, pyqtSignal, pyqtSlot +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtWidgets import QAction, QHBoxLayout, QLineEdit, QToolButton, QWidget + + +class CanvasExtent: + Fixed = 1 + Pan = 2 + Scale = 3 + + +class FeatureSelectorWidget(QWidget): + feature_identified = pyqtSignal(QgsFeature) + + def __init__(self, parent): + QWidget.__init__(self, parent) + + edit_layout = QHBoxLayout() + edit_layout.setContentsMargins(0, 0, 0, 0) + edit_layout.setSpacing(2) + self.setLayout(edit_layout) + + self.line_edit = QLineEdit(self) + self.line_edit.setReadOnly(True) + edit_layout.addWidget(self.line_edit) + + self.highlight_feature_button = QToolButton(self) + self.highlight_feature_button.setPopupMode(QToolButton.MenuButtonPopup) + self.highlight_feature_action = QAction( + QgsApplication.getThemeIcon("/mActionHighlightFeature.svg"), "Highlight feature", self + ) + self.scale_highlight_feature_action = QAction( + QgsApplication.getThemeIcon("/mActionScaleHighlightFeature.svg"), + "Scale and highlight feature", + self, + ) + self.pan_highlight_feature_action = QAction( + QgsApplication.getThemeIcon("/mActionPanHighlightFeature.svg"), + "Pan and highlight feature", + self, + ) + self.highlight_feature_button.addAction(self.highlight_feature_action) + self.highlight_feature_button.addAction(self.scale_highlight_feature_action) + self.highlight_feature_button.addAction(self.pan_highlight_feature_action) + self.highlight_feature_button.setDefaultAction(self.highlight_feature_action) + edit_layout.addWidget(self.highlight_feature_button) + + self.map_identification_button = QToolButton(self) + self.map_identification_button.setIcon( + QgsApplication.getThemeIcon("/mActionMapIdentification.svg") + ) + self.map_identification_button.setText("Select on map") + self.map_identification_button.setCheckable(True) + edit_layout.addWidget(self.map_identification_button) + + self.map_identification_button.clicked.connect(self.map_identification) + self.highlight_feature_button.triggered.connect(self.highlight_action_triggered) + + self.layer = None + self.map_tool = None + self.canvas = None + self.window_widget = None + self.highlight = None + self.feature = QgsFeature() + + def set_canvas(self, map_canvas): + self.map_tool = QgsMapToolIdentifyFeature(map_canvas) + self.map_tool.setButton(self.map_identification_button) + self.canvas = map_canvas + + def set_layer(self, layer): + self.layer = layer + + def set_feature(self, feature, canvas_extent=CanvasExtent.Fixed): + self.line_edit.clear() + self.feature = feature + + if self.feature is None or not self.feature.isValid() or self.layer is None: + return + + expression = QgsExpression(self.layer.displayExpression()) + context = QgsExpressionContext() + scope = QgsExpressionContextScope() + context.appendScope(scope) + scope.setFeature(feature) + feature_title = expression.evaluate(context) + if feature_title == "": + feature_title = feature.id() + self.line_edit.setText(str(feature_title)) + self.highlight_feature(canvas_extent) + + def clear(self): + self.feature = QgsFeature() + self.line_edit.clear() + + @pyqtSlot() + def map_identification(self): + if self.layer is None or self.map_tool is None or self.canvas is None: + return + + self.map_tool.setLayer(self.layer) + self.canvas.setMapTool(self.map_tool) + + self.window_widget = QWidget.window(self) + self.canvas.window().raise_() + self.canvas.activateWindow() + self.canvas.setFocus() + + self.map_tool.featureIdentified.connect(self.map_tool_feature_identified) + self.map_tool.deactivated.connect(self.map_tool_deactivated) + + def map_tool_feature_identified(self, feature): + feature = QgsFeature(feature) + self.feature_identified.emit(feature) + self.unset_map_tool() + self.set_feature(feature) + + def map_tool_deactivated(self): + if self.window_widget is not None: + self.window_widget.raise_() + self.window_widget.activateWindow() + + def highlight_feature(self, canvas_extent=CanvasExtent.Fixed): + if self.canvas is None or not self.feature.isValid(): + return + + geom = self.feature.geometry() + + if geom is None: + return + + if canvas_extent == CanvasExtent.Scale: + feature_bounding_box = geom.boundingBox() + feature_bounding_box = self.canvas.mapSettings().layerToMapCoordinates( + self.layer, feature_bounding_box + ) + extent = self.canvas.extent() + if not extent.contains(feature_bounding_box): + extent.combineExtentWith(feature_bounding_box) + extent.scale(1.1) + self.canvas.setExtent(extent) + self.canvas.refresh() + + elif canvas_extent == CanvasExtent.Pan: + centroid = geom.centroid() + center = centroid.asPoint() + + center = self.canvas.mapSettings().layerToMapCoordinates(self.layer, center) + self.canvas.zoomByFactor(1.0, center) # refresh is done in this method + + # highlight + self.delete_highlight() + self.highlight = QgsHighlight(self.canvas, geom, self.layer) + + settings = QSettings() + color = QColor(settings.value("/Map/highlight/color", Qgis.DEFAULT_HIGHLIGHT_COLOR.name())) + alpha = int( + settings.value("/Map/highlight/colorAlpha", Qgis.DEFAULT_HIGHLIGHT_COLOR.alpha()) + ) + buffer = 2 * float( + settings.value("/Map/highlight/buffer", Qgis.DEFAULT_HIGHLIGHT_BUFFER_MM) + ) + min_width = 2 * float( + settings.value("/Map/highlight/min_width", Qgis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM) + ) + + self.highlight.setColor(color) # sets also fill with default alpha + color.setAlpha(alpha) + self.highlight.setFillColor(color) # sets fill with alpha + self.highlight.setBuffer(buffer) + self.highlight.setMinWidth(min_width) + self.highlight.setWidth(4.0) + self.highlight.show() + + self.timer = QTimer(self) + self.timer.setSingleShot(True) + self.timer.timeout.connect(self.delete_highlight) + self.timer.start(3000) + + def delete_highlight(self): + if self.highlight is not None: + self.highlight.hide() + del self.highlight + self.highlight = None + + def unset_map_tool(self): + if self.canvas is not None and self.map_tool is not None: + # this will call mapTool.deactivated + self.canvas.unsetMapTool(self.map_tool) + + def highlight_action_triggered(self, action): + self.highlight_feature_button.setDefaultAction(action) + + if action == self.highlight_feature_action: + self.highlight_feature() + + elif action == self.scale_highlight_feature_action: + self.highlight_feature(CanvasExtent.Scale) + + elif action == self.pan_highlight_feature_action: + self.highlight_feature(CanvasExtent.Pan) diff --git a/plugin/teksi_wastewater/interlis/gui/interlis_export_settings_dialog.py b/plugin/teksi_wastewater/interlis/gui/interlis_export_settings_dialog.py new file mode 100644 index 0000000..b61aba0 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/interlis_export_settings_dialog.py @@ -0,0 +1,119 @@ +import os +from collections import OrderedDict + +from qgis.core import Qgis, QgsSettings +from qgis.PyQt.QtWidgets import QCheckBox, QDialog +from qgis.PyQt.uic import loadUi + +from ...utils.twwlayermanager import TwwLayerManager +from .. import config +from ..processing_algs.extractlabels_interlis import ExtractlabelsInterlisAlgorithm + + +class InterlisExportSettingsDialog(QDialog): + def __init__(self, parent): + super().__init__(parent) + loadUi(os.path.join(os.path.dirname(__file__), "interlis_export_settings_dialog.ui"), self) + + self.finished.connect(self.on_finish) + + # Fill models selection combobox + self.export_model_selection_comboBox.addItem( + config.MODEL_NAME_DSS, [config.MODEL_NAME_DSS] + ) + self.export_model_selection_comboBox.addItem( + config.MODEL_NAME_SIA405_ABWASSER, [config.MODEL_NAME_SIA405_ABWASSER] + ) + self.export_model_selection_comboBox.addItem( + config.MODEL_NAME_VSA_KEK, + [config.MODEL_NAME_VSA_KEK, config.MODEL_NAME_SIA405_ABWASSER], + ) + + # Fill orientation selection combobox + self.export_orientation_selection_comboBox.clear() + self.export_orientation_selection_comboBox.addItem("90°", 90.0) + self.export_orientation_selection_comboBox.addItem("0°", 0.0) + self.export_orientation_selection_comboBox.addItem("-90°", -90.0) + + structures_layer = TwwLayerManager.layer("vw_tww_wastewater_structure") + reaches_layer = TwwLayerManager.layer("vw_tww_reach") + self.structures = structures_layer.selectedFeatures() if structures_layer else [] + self.reaches = reaches_layer.selectedFeatures() if reaches_layer else [] + + self.limit_checkbox.setText( + f"Limit to selection ({len(self.structures)} structures and {len(self.reaches)} reaches)" + ) + + # Remember save next to file checkbox + settings_value = QgsSettings().value("tww_plugin/logs_next_to_file", False) + self.save_logs_next_to_file_checkbox.setChecked( + settings_value is True or settings_value == "true" + ) + + # Populate the labels list (restoring checked states of scaes) + selected_scales = QgsSettings().value("tww_plugin/last_selected_scales", "").split(",") + qgis_version_ok = Qgis.QGIS_VERSION_INT >= 32602 + self.labels_groupbox.setEnabled(qgis_version_ok) + self.labels_qgis_warning_label.setVisible(not qgis_version_ok) + self.scale_checkboxes = OrderedDict() + for scale_key, scale_disp, scale_val in ExtractlabelsInterlisAlgorithm.AVAILABLE_SCALES: + checkbox = QCheckBox(f"{scale_disp} [1:{scale_val}]") + checkbox.setChecked(qgis_version_ok and scale_key in selected_scales) + self.scale_checkboxes[scale_key] = checkbox + self.labels_groupbox.layout().addWidget(checkbox) + + def on_finish(self): + # Remember save next to file checkbox + QgsSettings().setValue("tww_plugin/logs_next_to_file", self.logs_next_to_file) + + # Save checked state of scales + if self.labels_groupbox.isChecked(): + selected_scales = [] + for key, checkbox in self.scale_checkboxes.items(): + if checkbox.isChecked(): + selected_scales.append(key) + QgsSettings().setValue("tww_plugin/last_selected_scales", ",".join(selected_scales)) + + @property + def logs_next_to_file(self): + return self.save_logs_next_to_file_checkbox.isChecked() + + @property + def selected_ids(self): + if self.limit_checkbox.isChecked(): + ids = [] + for struct in self.structures: + ids.append(str(struct["wn_obj_id"])) + for reach in self.reaches: + ids.append(str(reach["obj_id"])) + ids.append(str(reach["rp_from_fk_wastewater_networkelement"])) + ids.append(str(reach["rp_to_fk_wastewater_networkelement"])) + return ids + else: + return None + + @property + def limit_to_selection(self): + return self.limit_checkbox.isChecked() + + @property + def selected_labels_scales_indices(self): + if self.labels_groupbox.isChecked(): + scales = [] + for i, checkbox in enumerate(self.scale_checkboxes.values()): + if checkbox.isChecked(): + scales.append(i) + return scales + else: + return [] + + def selected_model(self): + return self.export_model_selection_comboBox.currentText() + + def selected_models(self): + return self.export_model_selection_comboBox.currentData() + + @property + def labels_orientation_offset(self): + eorientation = self.export_orientation_selection_comboBox.currentData() + return eorientation diff --git a/plugin/teksi_wastewater/interlis/gui/interlis_export_settings_dialog.ui b/plugin/teksi_wastewater/interlis/gui/interlis_export_settings_dialog.ui new file mode 100644 index 0000000..99caff2 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/interlis_export_settings_dialog.ui @@ -0,0 +1,143 @@ + + + Dialog + + + + 0 + 0 + 381 + 322 + + + + + 0 + 0 + + + + TEKSI Wastewater INTERLIS export + + + + + + Export model selection + + + + + + + + + + + + + + Limit to selection + + + + + + + true + + + Export labels + + + true + + + + + + Angle for horizontal labels (default: 90.0°) + + + + + + + Angle for horizontal labels (default: 90.0°) + + + + + + + + + *{color: red; } + + + This feature requires QGIS 3.26.2 or newer. + + + + + + + + + + + + Save logs next to output + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugin/teksi_wastewater/interlis/gui/interlis_import_selection_dialog.py b/plugin/teksi_wastewater/interlis/gui/interlis_import_selection_dialog.py new file mode 100644 index 0000000..1095093 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/interlis_import_selection_dialog.py @@ -0,0 +1,231 @@ +import os +import sys +from collections import defaultdict + +from qgis.core import Qgis +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtGui import QFont +from qgis.PyQt.QtWidgets import QDialog, QHeaderView, QMessageBox, QTreeWidgetItem +from qgis.PyQt.uic import loadUi +from qgis.utils import iface +from sqlalchemy import inspect +from sqlalchemy.orm import Session + +from ...utils.qt_utils import OverrideCursor +from .editors.base import Editor + +# Required for loadUi to find the custom widget +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..")) + + +class InterlisImportSelectionDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + loadUi( + os.path.join(os.path.dirname(__file__), "interlis_import_selection_dialog.ui"), self + ) + + self.accepted.connect(self.commit_session) + self.rejected.connect(self.rollback_session) + + header = self.treeWidget.header() + header.setSectionResizeMode(QHeaderView.ResizeToContents) + header.setSectionResizeMode(0, QHeaderView.Stretch) + + def init_with_session(self, session: Session): + """ + Shows the dialog with data from a session, and executes the dialog allowing to filter the rows to import + """ + self.session = session + + self.category_items = defaultdict(QTreeWidgetItem) # keys are instances' classes + self.editors = {} + + self.debugGroupBox.setChecked(False) + + self.treeWidget.clear() + self.update_tree() + + self.treeWidget.itemChanged.connect(self.item_changed) + self.treeWidget.currentItemChanged.connect(self.current_item_changed) + + def update_tree(self): + """ + Populates the tree, creating/updating items + """ + + for obj in self.session: + if obj not in self.editors: + self.editors[obj] = Editor.factory(self, self.session, obj) + editor = self.editors[obj] + + cls = obj.__class__ + + # Hide unmodified value lists items that may have been added to the session + if editor.status == Editor.EXISTING and cls.__table__.schema == "tww_vl": + continue + + if cls not in self.category_items: + self.category_items[cls].setText(0, cls.__name__) + self.category_items[cls].setCheckState(0, Qt.Checked) + self.category_items[cls].setFont( + 0, QFont(QFont().defaultFamily(), weight=QFont.Weight.Bold) + ) + self.treeWidget.addTopLevelItem(self.category_items[cls]) + + editor.update_listitem() + self.category_items[cls].addChild(editor.listitem) + + if editor.validity != Editor.VALID: + self.treeWidget.expandItem(self.category_items[cls]) + + # Show counts + for cls, category_item in self.category_items.items(): + category_item.setText(0, f"{cls.__name__} ({category_item.childCount()})") + + def item_changed(self, item, column): + """ + Adds or removes item's object from session. + + (propagation to parent/children is disabled for now) + """ + + checked = item.checkState(0) == Qt.Checked + + # add or remove object from session + obj = self.get_obj_from_listitem(item) + if obj is not None: + if checked: + self.session.add(obj) + else: + self.session.expunge(obj) + + checked_state = item.checkState(0) + if checked_state == Qt.PartiallyChecked: + return + + # propagate to children + for child in [item.child(i) for i in range(item.childCount())]: + child.setCheckState(0, checked_state) + + # propagate to parent + parent = item.parent() + if parent: + has_checked = False + has_unchecked = False + for sibling in [parent.child(i) for i in range(parent.childCount())]: + if sibling.checkState(0) == Qt.Checked: + has_checked = True + if sibling.checkState(0) == Qt.Unchecked: + has_unchecked = True + if has_checked and has_unchecked: + break + + if has_checked and has_unchecked: + parent.setCheckState(0, Qt.PartiallyChecked) + elif has_checked: + parent.setCheckState(0, Qt.Checked) + elif has_unchecked: + parent.setCheckState(0, Qt.Unchecked) + else: + # no children at all !! + parent.setCheckState(0, Qt.PartiallyChecked) + + def current_item_changed(self, current_item, previous_item): + """ + Calls refresh_widget_for_obj for the currently selected object + """ + for editor in self.editors.values(): + if editor.listitem == current_item: + self.refresh_editor(editor) + break + else: + self.debugTextEdit.clear() + self.validityLabel.clear() + current_widget = self.stackedWidget.currentWidget() + if current_widget: + self.stackedWidget.removeWidget(current_widget) + + def refresh_editor(self, editor): + """ + Refreshes the widget for the object, including validation, debug and status text + """ + # Revalidate the widget + editor.update_state() + + # Update the list item + editor.update_listitem() + + # Update generic widget contents + self.debugTextEdit.clear() + self.validityLabel.clear() + + # Show all attributes in the debug text edit + self.debugTextEdit.append("-- ATTRIBUTES --") + for c in inspect(editor.obj).mapper.column_attrs: + val = getattr(editor.obj, c.key) + self.debugTextEdit.append(f"{c.key}: {val}") + # Show sqlalchemy state in the debug text edit + self.debugTextEdit.append("-- SQLALCHEMY STATUS --") + for status_name in [ + "transient", + "pending", + "persistent", + "deleted", + "detached", + "modified", + "expired", + ]: + if getattr(inspect(editor.obj), status_name): + self.debugTextEdit.append(f"{status_name} ") + self.debugTextEdit.append("-- DEBUG --") + self.debugTextEdit.append(repr(editor.obj)) + + # Show the validity label + self.validityLabel.setText(editor.message) + if editor.validity == Editor.INVALID: + self.validityLabel.setStyleSheet("background-color: red; padding: 15px;") + elif editor.validity == Editor.WARNING: + self.validityLabel.setStyleSheet("background-color: orange; padding: 15px;") + elif editor.validity == Editor.VALID: + self.validityLabel.setStyleSheet("background-color: lightgreen; padding: 15px;") + else: + self.validityLabel.setStyleSheet("background-color: lightgray; padding: 15px;") + + # Update the actual widget + editor.update_widget() + + # Instantiate the specific widget (this has no effect if it's already active) + self.stackedWidget.addWidget(editor.widget) + self.stackedWidget.setCurrentWidget(editor.widget) + + def commit_session(self): + # TODO : rollback to pre-commit state, allowing user to try to fix issues + # probably a matter of creating a savepoint before saving with + # session.begin_nested() and one additionnal self.session.commit() + try: + with OverrideCursor(Qt.WaitCursor): + self.session.commit() + self.session.close() + except Exception as exception: + QMessageBox.critical(self, "Import error", f"Details: {exception}") + return + + if iface: + iface.messageBar().pushMessage( + "Sucess", "Data successfully imported", level=Qgis.Success + ) + + def rollback_session(self): + with OverrideCursor(Qt.WaitCursor): + self.session.rollback() + self.session.close() + + if iface: + iface.messageBar().pushMessage("Error", "Import was canceled", level=Qgis.Warning) + + def get_obj_from_listitem(self, listitem): + for obj, editor in self.editors.items(): + if editor.listitem == listitem: + return obj + return None diff --git a/plugin/teksi_wastewater/interlis/gui/interlis_import_selection_dialog.ui b/plugin/teksi_wastewater/interlis/gui/interlis_import_selection_dialog.ui new file mode 100644 index 0000000..f3145e7 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/interlis_import_selection_dialog.ui @@ -0,0 +1,249 @@ + + + Dialog + + + + 0 + 0 + 953 + 670 + + + + + 0 + 0 + + + + + 600 + 400 + + + + TWW interlis import + + + + + + Qt::Horizontal + + + + + 0 + 0 + + + + false + + + false + + + + name + + + + + state + + + + + validity + + + + + Inspections + + + + + + + + + + a + + + + + + + + + + + b + + + + + + + + + + + c + + + + + + + + + + + + Organisaitons + + + + + + + + + + a + + + + + + + + + + + b + + + + + + + + + + + c + + + + + + + + + + + + + + + + b + + + Unknown + + + true + + + 7 + + + + + + + + + + + + + Raw data (debug) + + + true + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
+
+ + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/plugin/teksi_wastewater/interlis/gui/interlis_importer_exporter_gui.py b/plugin/teksi_wastewater/interlis/gui/interlis_importer_exporter_gui.py new file mode 100644 index 0000000..dbaeccf --- /dev/null +++ b/plugin/teksi_wastewater/interlis/gui/interlis_importer_exporter_gui.py @@ -0,0 +1,175 @@ +import os +import webbrowser + +from qgis.core import Qgis, QgsProject, QgsSettings +from qgis.PyQt.QtCore import QFileInfo, QObject, Qt +from qgis.PyQt.QtWidgets import QApplication, QFileDialog, QProgressDialog, QPushButton +from qgis.utils import iface + +from ...utils.qt_utils import OverrideCursor +from ..interlis_importer_exporter import ( + InterlisImporterExporter, + InterlisImporterExporterError, + InterlisImporterExporterStopped, +) +from .interlis_export_settings_dialog import InterlisExportSettingsDialog + + +class InterlisImporterExporterGui(QObject): + _PROGRESS_DIALOG_MINIMUM_WIDTH = 350 + + def __init__(self): + super().__init__(parent=None) + self.import_dialog = None + self.progress_dialog = None + + self.interlis_importer_exporter = InterlisImporterExporter( + progress_done_callback=self._progress_done_callback + ) + + def action_import(self): + """ + Is executed when the user clicks the importAction tool + """ + default_folder = QgsSettings().value( + "tww_plugin/last_interlis_path", QgsProject.instance().absolutePath() + ) + xtf_file_input, _ = QFileDialog.getOpenFileName( + None, + self.tr("Import file"), + default_folder, + self.tr("INTERLIS transfer files (*.xtf)"), + ) + if not xtf_file_input: + # Operation canceled + return + + QgsSettings().setValue("tww_plugin/last_interlis_path", os.path.dirname(xtf_file_input)) + + setting_value = QgsSettings().value("tww_plugin/logs_next_to_file", False) + logs_next_to_file = setting_value is True or setting_value == "true" + + self.progress_dialog = QProgressDialog("", "", 0, 100) + self.progress_dialog.setMinimumWidth(self._PROGRESS_DIALOG_MINIMUM_WIDTH) + self.progress_dialog.setCancelButtonText("Cancel") + self.progress_dialog.setMinimumDuration(0) + self.progress_dialog.setWindowTitle("Import INTERLIS data...") + + try: + with OverrideCursor(Qt.WaitCursor): + self.interlis_importer_exporter.interlis_import( + xtf_file_input, show_selection_dialog=True, logs_next_to_file=logs_next_to_file + ) + iface.mapCanvas().refreshAllLayers() + + except InterlisImporterExporterStopped: + self._cleanup() + iface.messageBar().pushMessage("Warning", "Import was canceled.", level=Qgis.Warning) + + except InterlisImporterExporterError as exception: + self._cleanup() + self.show_failure( + exception.error, + exception.additional_text, + exception.log_path, + ) + + except Exception as exception: + self._cleanup() + raise exception + + def action_export(self): + """ + Is executed when the user clicks the exportAction tool + """ + export_dialog = InterlisExportSettingsDialog(None) + + if export_dialog.exec_() == export_dialog.Rejected: + return + + default_folder = QgsSettings().value( + "tww_plugin/last_interlis_path", QgsProject.instance().absolutePath() + ) + file_name, _ = QFileDialog.getSaveFileName( + None, + self.tr("Export to file"), + os.path.join(default_folder, "tww-export.xtf"), + self.tr("INTERLIS transfer files (*.xtf)"), + ) + if not file_name: + # Operation canceled + return + QgsSettings().setValue("tww_plugin/last_interlis_path", os.path.dirname(file_name)) + + self.progress_dialog = QProgressDialog("", "", 0, 100) + self.progress_dialog.setMinimumWidth(self._PROGRESS_DIALOG_MINIMUM_WIDTH) + self.progress_dialog.setCancelButtonText("Cancel") + self.progress_dialog.setMinimumDuration(0) + self.progress_dialog.setWindowTitle("Export INTERLIS data...") + + try: + self.interlis_importer_exporter.interlis_export( + xtf_file_output=file_name, + export_models=export_dialog.selected_models(), + logs_next_to_file=export_dialog.logs_next_to_file, + limit_to_selection=export_dialog.limit_to_selection, + export_orientation=export_dialog.labels_orientation_offset, + selected_labels_scales_indices=export_dialog.selected_labels_scales_indices, + selected_ids=export_dialog.selected_ids, + ) + + self.show_success( + "Success", + f"Data successfully exported to {QFileInfo(file_name).path()}", + None, + ) + + except InterlisImporterExporterStopped: + self._cleanup() + iface.messageBar().pushMessage("Warning", "Export was canceled.", level=Qgis.Warning) + + except InterlisImporterExporterError as exception: + self._cleanup() + self.show_failure( + exception.error, + exception.additional_text, + exception.log_path, + ) + + except Exception as exception: + self._cleanup() + raise exception + + def _progress_done_callback(self, progress, text=None): + self._check_for_canceled() + if text: + self.progress_dialog.setLabelText(text) + self.progress_dialog.setValue(progress) + QApplication.processEvents() + + def _show_results(self, title, message, log_path, level): + widget = iface.messageBar().createMessage(title, message) + if log_path: + button = QPushButton(widget) + button.setText("Show logs") + button.pressed.connect(lambda p=log_path: webbrowser.open(p)) + widget.layout().addWidget(button) + iface.messageBar().pushWidget(widget, level) + + def show_failure(self, title, message, log_path): + self._show_results(title, message, log_path, Qgis.Warning) + + def show_success(self, title, message, log_path): + self._show_results(title, message, log_path, Qgis.Success) + + def _check_for_canceled(self): + if self.progress_dialog.wasCanceled(): + raise InterlisImporterExporterStopped + + def _cleanup(self): + if self.progress_dialog: + self.progress_dialog.close() + + def _progress_done(self): + self._check_for_canceled() + self.progress_dialog.setValue(self.progress_dialog.value() + 1) diff --git a/plugin/teksi_wastewater/interlis/interlis_importer_exporter.py b/plugin/teksi_wastewater/interlis/interlis_importer_exporter.py new file mode 100644 index 0000000..ec25a57 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_importer_exporter.py @@ -0,0 +1,618 @@ +import logging +import os +import tempfile + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QApplication + +from ..utils.database_utils import DatabaseUtils +from . import config +from .gui.interlis_import_selection_dialog import InterlisImportSelectionDialog +from .interlis_model_mapping.interlis_exporter_to_intermediate_schema import ( + InterlisExporterToIntermediateSchema, + InterlisExporterToIntermediateSchemaError, +) +from .interlis_model_mapping.interlis_importer_to_intermediate_schema import ( + InterlisImporterToIntermediateSchema, +) +from .interlis_model_mapping.model_interlis_dss import ModelInterlisDss +from .interlis_model_mapping.model_interlis_sia405_abwasser import ( + ModelInterlisSia405Abwasser, +) +from .interlis_model_mapping.model_interlis_vsa_kek import ModelInterlisVsaKek +from .interlis_model_mapping.model_tww import ModelTwwSys, ModelTwwVl +from .interlis_model_mapping.model_tww_od import ModelTwwOd +from .utils.ili2db import InterlisTools +from .utils.various import CmdException, LoggingHandlerContext, logger, make_log_path + + +class InterlisImporterExporterStopped(Exception): + pass + + +class InterlisImporterExporterError(Exception): + def __init__(self, error, additional_text, log_path): + self.error = error + self.additional_text = additional_text + self.log_path = log_path + + +class InterlisImporterExporter: + def __init__(self, progress_done_callback=None): + self.progress_done_callback = progress_done_callback + self.interlisTools = InterlisTools() + self.base_log_path = None + + self.model_classes_interlis = None + self.model_classes_tww_od = None + self.model_classes_tww_vl = None + self.model_classes_tww_sys = None + + self.current_progress = 0 + + def interlis_import(self, xtf_file_input, show_selection_dialog=False, logs_next_to_file=True): + # Configure logging + if logs_next_to_file: + self.base_log_path = xtf_file_input + else: + self.base_log_path = None + + # Validating the input file + self._progress_done(5, "Validating the input file...") + self._import_validate_xtf_file(xtf_file_input) + + # Get model to import from xtf file + self._progress_done(10, "Extract model from xtf...") + import_models = self.interlisTools.get_xtf_models(xtf_file_input) + + import_model = "" + if config.MODEL_NAME_VSA_KEK in import_models: + import_model = config.MODEL_NAME_VSA_KEK + elif config.MODEL_NAME_SIA405_ABWASSER in import_models: + import_model = config.MODEL_NAME_SIA405_ABWASSER + elif config.MODEL_NAME_DSS in import_models: + import_model = config.MODEL_NAME_DSS + elif config.MODEL_NAME_SIA405_BASE_ABWASSER in import_models: + import_model = config.MODEL_NAME_SIA405_ABWASSER + else: + error_text = f"No supported model was found among '{import_models}'." + if len(import_models) == 1: + error_text = f"The model '{import_models[0]}' is not supported." + raise InterlisImporterExporterError("Import error", error_text, None) + logger.info( + f"Model '{import_model}' was choosen for import among found models '{import_models}'" + ) + + # Prepare the temporary ili2pg model + self._progress_done(10, "Creating ili schema...") + self._clear_ili_schema(recreate_schema=True) + + self._progress_done(20) + self._create_ili_schema( + [import_model], ext_columns_no_constraints=True, create_basket_col=True + ) + + # Import from xtf file to ili2pg model + self._progress_done(30, "Importing XTF data...") + self._import_xtf_file(xtf_file_input=xtf_file_input) + + # Disable symbology triggers + self._progress_done(35, "Disable symbology and modification triggers...") + self._import_disable_symbology_and_modification_triggers() + + try: + # Import from the temporary ili2pg model + self._progress_done(40, "Converting to TEKSI Wastewater...") + tww_session = self._import_from_intermediate_schema(import_model) + + if show_selection_dialog: + self._progress_done(90, "Import objects selection...") + import_dialog = InterlisImportSelectionDialog() + import_dialog.init_with_session(tww_session) + QApplication.restoreOverrideCursor() + if import_dialog.exec_() == import_dialog.Rejected: + tww_session.rollback() + tww_session.close() + raise InterlisImporterExporterStopped() + QApplication.setOverrideCursor(Qt.WaitCursor) + else: + self._progress_done(90, "Commit session...") + tww_session.commit() + tww_session.close() + + # Update main_cover and main_wastewater_node + self._progress_done(95, "Update main cover and refresh materialized views...") + self._import_update_main_cover_and_refresh_mat_views() + + # Validate subclasses after import + self._check_subclass_counts() + + # Update organisations + self._progress_done(96, "Set organisations filter...") + self._import_manage_organisations() + + # Reenable symbology triggers + self._progress_done(97, "Reenable symbology and modification triggers...") + self._import_enable_symbology_and_modification_triggers() + + except Exception as exception: + # Make sure to re-enable triggers in case an exception occourred + try: + self._import_enable_symbology_and_modification_triggers() + except Exception as enable_trigger_exception: + logger.error( + f"Symbology triggers couldn't be re-enabled because an exception occourred: '{enable_trigger_exception}'" + ) + + # Raise the original exception for further error handling + raise exception + + self._progress_done(100) + logger.info("INTERLIS import finished.") + + def interlis_export( + self, + xtf_file_output, + export_models, + logs_next_to_file=True, + limit_to_selection=False, + export_orientation=90.0, + selected_labels_scales_indices=[], + selected_ids=None, + ): + # Validate subclasses before export + self._check_subclass_counts(limit_to_selection) + + # File name without extension (used later for export) + file_name_base, _ = os.path.splitext(xtf_file_output) + + # Configure logging + if logs_next_to_file: + self.base_log_path = xtf_file_output + else: + self.base_log_path = None + + self._progress_done(5, "Clearing ili schema...") + self._clear_ili_schema(recreate_schema=True) + + self._progress_done(15, "Creating ili schema...") + create_basket_col = False + if config.MODEL_NAME_VSA_KEK in export_models: + create_basket_col = True + self._create_ili_schema(export_models, create_basket_col=create_basket_col) + + # Export the labels file + tempdir = tempfile.TemporaryDirectory() + labels_file_path = None + if len(selected_labels_scales_indices): + self._progress_done(25) + labels_file_path = os.path.join(tempdir.name, "labels.geojson") + self._export_labels_file( + limit_to_selection=limit_to_selection, + selected_labels_scales_indices=selected_labels_scales_indices, + labels_file_path=labels_file_path, + ) + + # Export to the temporary ili2pg model + self._progress_done(35, "Converting from TEKSI Wastewater...") + self._export_to_intermediate_schema( + export_model=export_models[0], + file_name=xtf_file_output, + selected_ids=selected_ids, + export_orientation=export_orientation, + labels_file_path=labels_file_path, + basket_enabled=create_basket_col, + ) + tempdir.cleanup() # Cleanup + + self._progress_done(60) + self._export_xtf_files(file_name_base, export_models) + + self._progress_done(100) + logger.info("INTERLIS export finished.") + + def _import_validate_xtf_file(self, xtf_file_input): + log_path = make_log_path(self.base_log_path, "ilivalidator") + try: + self.interlisTools.validate_xtf_data( + xtf_file_input, + log_path, + ) + except CmdException: + raise InterlisImporterExporterError( + "Invalid file", + "The input file is not a valid XTF file. Open the logs for more details on the error.", + log_path, + ) + + def _import_xtf_file(self, xtf_file_input): + log_path = make_log_path(self.base_log_path, "ili2pg-import") + try: + self.interlisTools.import_xtf_data( + config.ABWASSER_SCHEMA, + xtf_file_input, + log_path, + ) + except CmdException: + raise InterlisImporterExporterError( + "Could not import data", + "Open the logs for more details on the error.", + log_path, + ) + + def _import_from_intermediate_schema(self, import_model): + log_handler = logging.FileHandler( + make_log_path(self.base_log_path, "tww2ili-import"), mode="w", encoding="utf-8" + ) + log_handler.setLevel(logging.INFO) + log_handler.setFormatter(logging.Formatter("%(levelname)-8s %(message)s")) + + self._init_model_classes(import_model) + + interlisImporterToIntermediateSchema = InterlisImporterToIntermediateSchema( + model=import_model, + model_classes_interlis=self.model_classes_interlis, + model_classes_tww_od=self.model_classes_tww_od, + model_classes_tww_vl=self.model_classes_tww_vl, + callback_progress_done=self._progress_done_intermediate_schema, + ) + + with LoggingHandlerContext(log_handler): + interlisImporterToIntermediateSchema.tww_import(skip_closing_tww_session=True) + + return interlisImporterToIntermediateSchema.session_tww + + def _import_manage_organisations(self): + logger.info("Update organisation tww_active") + DatabaseUtils.execute("SELECT tww_app.set_organisations_active();") + + def _import_update_main_cover_and_refresh_mat_views(self): + with DatabaseUtils.PsycopgConnection() as connection: + cursor = connection.cursor() + + logger.info("Update wastewater structure fk_main_cover") + cursor.execute("SELECT tww_app.wastewater_structure_update_fk_main_cover('', True);") + + logger.info("Update wastewater structure fk_main_wastewater_node") + cursor.execute( + "SELECT tww_app.wastewater_structure_update_fk_main_wastewater_node('', True);" + ) + + logger.info("Refresh materialized views") + cursor.execute("SELECT tww_app.network_refresh_network_simple();") + + def _import_disable_symbology_and_modification_triggers(self): + DatabaseUtils.disable_symbology_triggers() + DatabaseUtils.disable_modification_triggers() + + def _import_enable_symbology_and_modification_triggers(self): + DatabaseUtils.enable_symbology_triggers() + DatabaseUtils.update_symbology() + DatabaseUtils.enable_modification_triggers() + + def _export_labels_file( + self, + limit_to_selection, + selected_labels_scales_indices, + labels_file_path, + ): + self._progress_done(self.current_progress, "Extracting labels...") + + try: + # We only import now to avoid useless exception if dependencies aren't met + from qgis import processing + + from ..utils.twwlayermanager import TwwLayerManager + except ImportError: + raise InterlisImporterExporterError( + "Export labels error", + "Could not load export labels as qgis.processing module is not available.", + None, + ) + + structures_lyr = TwwLayerManager.layer("vw_tww_wastewater_structure") + reaches_lyr = TwwLayerManager.layer("vw_tww_reach") + if not structures_lyr or not reaches_lyr: + raise InterlisImporterExporterError( + "Could not find the vw_tww_wastewater_structure and/or the vw_tww_reach layers.", + "Make sure your TEKSI Wastewater project is open.", + None, + ) + + self._progress_done(self.current_progress + 5) + processing.run( + "tww:extractlabels_interlis", + { + "OUTPUT": labels_file_path, + "RESTRICT_TO_SELECTION": limit_to_selection, + "STRUCTURE_VIEW_LAYER": structures_lyr, + "REACH_VIEW_LAYER": reaches_lyr, + "SCALES": selected_labels_scales_indices, + }, + ) + + def _export_to_intermediate_schema( + self, + export_model, + file_name=None, + selected_ids=None, + export_orientation=90.0, + labels_file_path=None, + basket_enabled=False, + ): + log_handler = logging.FileHandler( + make_log_path(file_name, "tww2ili-export"), mode="w", encoding="utf-8" + ) + log_handler.setLevel(logging.INFO) + log_handler.setFormatter(logging.Formatter("%(levelname)-8s %(message)s")) + + self._init_model_classes(export_model) + + twwInterlisExporter = InterlisExporterToIntermediateSchema( + model=export_model, + model_classes_interlis=self.model_classes_interlis, + model_classes_tww_od=self.model_classes_tww_od, + model_classes_tww_vl=self.model_classes_tww_vl, + model_classes_tww_sys=self.model_classes_tww_sys, + labels_orientation_offset=export_orientation, + selection=selected_ids, + labels_file=labels_file_path, + basket_enabled=basket_enabled, + callback_progress_done=self._progress_done_intermediate_schema, + ) + + with LoggingHandlerContext(log_handler): + try: + twwInterlisExporter.tww_export() + except ( + InterlisExporterToIntermediateSchemaError + ) as interlisExporterToIntermediateSchemaError: + raise InterlisImporterExporterError( + "Could not export to the interlis schema", + f"{interlisExporterToIntermediateSchemaError}", + None, + ) + + def _export_xtf_files(self, file_name_base, export_models): + progress_step = (100 - self.current_progress) / (2 * len(export_models)) + progress_step = int(progress_step) + + xtf_export_errors = [] + for index, export_model_name in enumerate(export_models): + export_file_name = f"{file_name_base}_{export_model_name}.xtf" + + # Export from ili2pg model to file + self._progress_done(self.current_progress, f"Saving XTF for '{export_model_name}'...") + log_path = make_log_path(self.base_log_path, f"ili2pg-export-{export_model_name}") + try: + self.interlisTools.export_xtf_data( + schema=config.ABWASSER_SCHEMA, + xtf_file=export_file_name, + log_path=log_path, + model_name=export_model_name, + export_model_name=export_model_name, + ) + except CmdException: + xtf_export_errors.append( + InterlisImporterExporterError( + error=f"Could not export the model '{export_model_name}' from ili2pg schema", + additional_text="Open the logs for more details on the error.", + log_path=log_path, + ) + ) + continue + + self._progress_done( + self.current_progress + progress_step, + f"Validating XTF for '{export_model_name}'...", + ) + log_path = make_log_path(self.base_log_path, f"ilivalidator-{export_model_name}") + try: + self.interlisTools.validate_xtf_data( + export_file_name, + log_path, + ) + except CmdException: + xtf_export_errors.append( + InterlisImporterExporterError( + error=f"Validation of exported file '{export_file_name}' failed", + additional_text=f"The created file is not a valid {export_model_name} XTF file.", + log_path=log_path, + ) + ) + continue + + self._progress_done(self.current_progress + progress_step) + + # In case some export had an error raise the first one + if xtf_export_errors: + raise xtf_export_errors[0] + + def _clear_ili_schema(self, recreate_schema=False): + logger.info("CONNECTING TO DATABASE...") + + with DatabaseUtils.PsycopgConnection() as connection: + cursor = connection.cursor() + + if not recreate_schema: + # If the schema already exists, we just truncate all tables + cursor.execute( + f"SELECT schema_name FROM information_schema.schemata WHERE schema_name = '{config.ABWASSER_SCHEMA}';" + ) + if cursor.rowcount > 0: + logger.info( + f"Schema {config.ABWASSER_SCHEMA} already exists, we truncate instead" + ) + cursor.execute( + f"SELECT table_name FROM information_schema.tables WHERE table_schema = '{config.ABWASSER_SCHEMA}';" + ) + for row in cursor.fetchall(): + cursor.execute( + f"TRUNCATE TABLE {config.ABWASSER_SCHEMA}.{row[0]} CASCADE;" + ) + return + + logger.info(f"DROPPING THE SCHEMA {config.ABWASSER_SCHEMA}...") + cursor.execute(f'DROP SCHEMA IF EXISTS "{config.ABWASSER_SCHEMA}" CASCADE ;') + logger.info(f"CREATING THE SCHEMA {config.ABWASSER_SCHEMA}...") + cursor.execute(f'CREATE SCHEMA "{config.ABWASSER_SCHEMA}";') + + def _create_ili_schema( + self, models, ext_columns_no_constraints=False, create_basket_col=False + ): + log_path = make_log_path(self.base_log_path, "ili2pg-schemaimport") + try: + self.interlisTools.import_ili_schema( + config.ABWASSER_SCHEMA, + models, + log_path, + ext_columns_no_constraints=ext_columns_no_constraints, + create_basket_col=create_basket_col, + ) + except CmdException: + raise InterlisImporterExporterError( + "Could not create the ili2pg schema", + "Open the logs for more details on the error.", + log_path, + ) + + def _check_subclass_counts(self, limit_to_selection=False): + self._check_subclass_count( + config.TWW_OD_SCHEMA, + "wastewater_networkelement", + ["reach", "wastewater_node"], + limit_to_selection, + ) + self._check_subclass_count( + config.TWW_OD_SCHEMA, + "wastewater_structure", + [ + "channel", + "manhole", + "special_structure", + "infiltration_installation", + "discharge_point", + "wwtp_structure", + "small_treatment_plant", + "drainless_toilet", + ], + limit_to_selection, + ) + self._check_subclass_count( + config.TWW_OD_SCHEMA, + "structure_part", + [ + "benching", + "tank_emptying", + "tank_cleaning", + "cover", + "access_aid", + "electric_equipment", + "electromechanical_equipment", + "solids_retention", + "backflow_prevention", + "flushing_nozzle", + "dryweather_flume", + "dryweather_downspout", + ], + limit_to_selection, + ) + self._check_subclass_count( + config.TWW_OD_SCHEMA, + "overflow", + ["pump", "leapingweir", "prank_weir"], + limit_to_selection, + ) + self._check_subclass_count( + config.TWW_OD_SCHEMA, + "maintenance_event", + ["maintenance", "examination", "bio_ecol_assessment"], + limit_to_selection, + ) + self._check_subclass_count( + config.TWW_OD_SCHEMA, + "damage", + ["damage_channel", "damage_manhole"], + limit_to_selection, + ) + self._check_subclass_count( + config.TWW_OD_SCHEMA, + "connection_object", + ["fountain", "individual_surface", "building", "reservoir"], + limit_to_selection, + ) + self._check_subclass_count( + config.TWW_OD_SCHEMA, + "zone", + ["infiltration_zone", "drainage_system"], + limit_to_selection, + ) + + def _check_subclass_count(self, schema_name, parent_name, child_list, limit_to_selection): + logger.info(f"INTEGRITY CHECK {parent_name} subclass data...") + logger.info("CONNECTING TO DATABASE...") + + with DatabaseUtils.PsycopgConnection() as connection: + cursor = connection.cursor() + + cursor.execute(f"SELECT obj_id FROM {schema_name}.{parent_name};") + parent_rows = cursor.fetchall() + if len(parent_rows) > 0: + parent_count = len(parent_rows) + logger.info(f"Number of {parent_name} datasets: {parent_count}") + for child_name in child_list: + cursor.execute(f"SELECT obj_id FROM {schema_name}.{child_name};") + child_rows = cursor.fetchall() + logger.info(f"Number of {child_name} datasets: {len(child_rows)}") + parent_count = parent_count - len(child_rows) + + if parent_count == 0: + logger.info( + f"OK: number of subclass elements of class {parent_name} OK in schema {schema_name}!" + ) + else: + if parent_count > 0: + errormsg = f"Too many superclass entries for {schema_name}.{parent_name}" + else: + errormsg = f"Too many subclass entries for {schema_name}.{parent_name}" + + if limit_to_selection: + logger.warning( + f"Overall Subclass Count: {errormsg}. The problem might lie outside the selection" + ) + else: + logger.error(f"Subclass Count error: {errormsg}") + raise InterlisImporterExporterError( + "Subclass Count error", + errormsg, + None, + ) + + def _init_model_classes(self, model): + ModelInterlis = ModelInterlisSia405Abwasser + if model == config.MODEL_NAME_DSS: + ModelInterlis = ModelInterlisDss + elif model == config.MODEL_NAME_VSA_KEK: + ModelInterlis = ModelInterlisVsaKek + self.model_classes_interlis = ModelInterlis().classes() + self._progress_done(self.current_progress + 1) + + if self.model_classes_tww_od is None: + self.model_classes_tww_od = ModelTwwOd().classes() + self._progress_done(self.current_progress + 1) + + if self.model_classes_tww_vl is None: + self.model_classes_tww_vl = ModelTwwVl().classes() + self._progress_done(self.current_progress + 1) + + if self.model_classes_tww_sys is None: + self.model_classes_tww_sys = ModelTwwSys().classes() + self._progress_done(self.current_progress + 1) + + def _progress_done_intermediate_schema(self): + self._progress_done(self.current_progress + 0.5) + + def _progress_done(self, progress, text=None): + self.current_progress = progress + if self.progress_done_callback: + self.progress_done_callback(int(progress), text) diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/__init__.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/interlis_exporter_to_intermediate_schema.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/interlis_exporter_to_intermediate_schema.py new file mode 100644 index 0000000..28c9ae1 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_model_mapping/interlis_exporter_to_intermediate_schema.py @@ -0,0 +1,3213 @@ +import json + +from geoalchemy2.functions import ST_Force2D, ST_GeomFromGeoJSON +from sqlalchemy import or_ +from sqlalchemy.orm import Session +from sqlalchemy.sql import text + +from ...utils.plugin_utils import logger +from .. import config, utils + + +class InterlisExporterToIntermediateSchemaError(Exception): + pass + + +class InterlisExporterToIntermediateSchema: + def __init__( + self, + model, + model_classes_interlis, + model_classes_tww_od, + model_classes_tww_vl, + model_classes_tww_sys, + labels_orientation_offset=90, + selection=None, + labels_file=None, + basket_enabled=False, + callback_progress_done=None, + use_vsacode=True, + ): + """ + Export data from the TWW model into the ili2pg model. + + Args: + selection: if provided, limits the export to networkelements that are provided in the selection + """ + self.model = model + self.callback_progress_done = callback_progress_done + + # Filtering + self.filtered = False + if selection: + self.filtered = True + + self.subset_ids = selection if selection is not None else [] + + self.labels_file = labels_file + self.use_vsacode = use_vsacode + self.oid_prefix = None + + self.basket_enabled = basket_enabled + + self.model_classes_interlis = model_classes_interlis + self.model_classes_tww_od = model_classes_tww_od + self.model_classes_tww_vl = model_classes_tww_vl + self.model_classes_tww_sys = model_classes_tww_sys + self.labels_orientation_offset = labels_orientation_offset + + self.tww_session = None + self.abwasser_session = None + self.tid_maker = utils.ili2db.TidMaker(id_attribute="obj_id") + + self.current_basket = None + self.basket_topic_sia405_administration = None + self.basket_topic_sia405_abwasser = None + self.basket_topic_dss = None + self.basket_topic_kek = None + + def tww_export(self): + # Logging disabled (very slow) + self.tww_session = Session( + utils.tww_sqlalchemy.create_engine(), autocommit=False, autoflush=False + ) + self.abwasser_session = Session( + utils.tww_sqlalchemy.create_engine(), autocommit=False, autoflush=False + ) + + # write self.subset_ids to logger.info + if self.filtered: + logger.info(f"Exporting selection {self.subset_ids}") + + try: + self._export() + self.abwasser_session.commit() + self.close_sessions() + except Exception as exception: + self.close_sessions() + raise exception + + def _export(self): + # Allow to insert rows with cyclic dependencies at once + self.abwasser_session.execute(text("SET CONSTRAINTS ALL DEFERRED;")) + + if self.basket_enabled: + self._create_basket() + self.current_basket = self.basket_topic_sia405_administration + + self._export_sia405_abwasser() + + if self.model == config.MODEL_NAME_DSS: + self.current_basket = self.basket_topic_dss + self._export_dss() + + if self.model == config.MODEL_NAME_VSA_KEK: + self.current_basket = self.basket_topic_kek + self._export_vsa_kek() + + # Labels + # Note: these are extracted from the optional labels file (not exported from the TWW database) + if self.labels_file: + logger.info(f"Exporting label positions from {self.labels_file}") + self._export_label_positions() + + def _create_basket(self): + dataset = self.model_classes_interlis.t_ili2db_dataset( + t_id=1, + datasetname="teksi-wastewater-export", + ) + self.abwasser_session.add(dataset) + + self.basket_topic_sia405_administration = self.model_classes_interlis.t_ili2db_basket( + t_id=2, + dataset=dataset.t_id, + topic=config.TOPIC_NAME_SIA405_ADMINISTRATION, + t_ili_tid=None, + attachmentkey=dataset.datasetname, + domains="", + ) + self.abwasser_session.add(self.basket_topic_sia405_administration) + + self.basket_topic_sia405_abwasser = self.model_classes_interlis.t_ili2db_basket( + t_id=3, + dataset=dataset.t_id, + topic=config.TOPIC_NAME_SIA405_ABWASSER, + t_ili_tid=None, + attachmentkey=dataset.datasetname, + domains="", + ) + self.abwasser_session.add(self.basket_topic_sia405_abwasser) + + self.basket_topic_dss = self.model_classes_interlis.t_ili2db_basket( + t_id=4, + dataset=dataset.t_id, + topic=config.TOPIC_NAME_DSS, + t_ili_tid=None, + attachmentkey=dataset.datasetname, + domains="", + ) + self.abwasser_session.add(self.basket_topic_dss) + + self.basket_topic_kek = self.model_classes_interlis.t_ili2db_basket( + t_id=5, + dataset=dataset.t_id, + topic=config.TOPIC_NAME_KEK, + t_ili_tid=None, + attachmentkey=dataset.datasetname, + domains="", + ) + self.abwasser_session.add(self.basket_topic_kek) + self.abwasser_session.flush() + + def _export_sia405_abwasser(self): + logger.info("Exporting TWW.organisation -> ABWASSER.organisation") + self._export_organisation() + self._check_for_stop() + + self.current_basket = self.basket_topic_sia405_abwasser + + logger.info("Exporting TWW.channel -> ABWASSER.kanal") + self._export_channel() + self._check_for_stop() + + logger.info("Exporting TWW.manhole -> ABWASSER.normschacht") + self._export_manhole() + self._check_for_stop() + + logger.info("Exporting TWW.discharge_point -> ABWASSER.einleitstelle") + self._export_discharge_point() + self._check_for_stop() + + logger.info("Exporting TWW.special_structure -> ABWASSER.spezialbauwerk") + self._export_special_structure() + self._check_for_stop() + + logger.info("Exporting TWW.infiltration_installation -> ABWASSER.versickerungsanlage") + self._export_infiltration_installation() + self._check_for_stop() + + logger.info("Exporting TWW.pipe_profile -> ABWASSER.rohrprofil") + self._export_pipe_profile() + self._check_for_stop() + + logger.info("Exporting TWW.reach_point -> ABWASSER.haltungspunkt") + self._export_reach_point() + self._check_for_stop() + + logger.info("Exporting TWW.wastewater_node -> ABWASSER.abwasserknoten") + self._export_wastewater_node() + self._check_for_stop() + + logger.info("Exporting TWW.reach -> ABWASSER.haltung") + self._export_reach() + self._check_for_stop() + + logger.info( + "Exporting TWW.reach_progression_alternative -> ABWASSER.haltung_alternativverlauf" + ) + self._export_reach_progression_alternative() + self._check_for_stop() + + logger.info("Exporting TWW.dryweather_downspout -> ABWASSER.trockenwetterfallrohr") + self._export_dryweather_downspout() + self._check_for_stop() + + logger.info("Exporting TWW.access_aid -> ABWASSER.einstiegshilfe") + self._export_access_aid() + self._check_for_stop() + + logger.info("Exporting TWW.dryweather_flume -> ABWASSER.trockenwetterrinne") + self._export_dryweather_flume() + self._check_for_stop() + + logger.info("Exporting TWW.cover -> ABWASSER.deckel") + self._export_cover() + self._check_for_stop() + + logger.info("Exporting TWW.benching -> ABWASSER.bankett") + self._export_benching() + self._check_for_stop() + + logger.info("Exporting TWW.wastewater_structure_symbol -> ABWASSER.abwasserbauwerk_symbol") + self._export_wastewater_structure_symbol() + self._check_for_stop() + + logger.info("Exporting TWW.flushing_nozzle -> ABWASSER.spuelstutzen") + self._export_flushing_nozzle() + self._check_for_stop() + + def _export_dss(self): + logger.info( + "Exporting TWW.waste_water_treatment_plant -> ABWASSER.abwasserreinigungsanlage" + ) + self._export_waste_water_treatment_plant() + self._check_for_stop() + + logger.info("Exporting TWW.wwtp_energy_use -> ABWASSER.araenergienutzung") + self._export_wwtp_energy_use() + self._check_for_stop() + + logger.info("Exporting TWW.waste_water_treatment -> ABWASSER.abwasserbehandlung") + self._export_waste_water_treatment() + self._check_for_stop() + + logger.info("Exporting TWW.sludge_treatment -> ABWASSER.schlammbehandlung") + self._export_sludge_treatment() + self._check_for_stop() + + logger.info("Exporting TWW.wwtp_structure -> ABWASSER.arabauwerk") + self._export_wwtp_structure() + self._check_for_stop() + + logger.info("Exporting TWW.control_center -> ABWASSER.steuerungszentrale") + self._export_control_center() + self._check_for_stop() + + logger.info("Exporting TWW.drainless_toilet -> ABWASSER.Abflusslose_Toilette") + self._export_drainless_toilet() + self._check_for_stop() + + logger.info("Exporting TWW.throttle_shut_off_unit -> ABWASSER.Absperr_Drosselorgan") + self._export_throttle_shut_off_unit() + self._check_for_stop() + + logger.info("Exporting TWW.tank_emptying -> ABWASSER.Beckenentleerung") + self._export_tank_emptying() + self._check_for_stop() + + logger.info("Exporting TWW.tank_cleaning -> ABWASSER.Beckenreinigung") + self._export_tank_cleaning() + self._check_for_stop() + + logger.info("Exporting TWW.bio_ecol_assessment -> ABWASSER.Biol_oekol_Gesamtbeurteilung") + self._export_bio_ecol_assessment() + self._check_for_stop() + + logger.info("Exporting TWW.fountain -> ABWASSER.Brunnen") + self._export_fountain() + self._check_for_stop() + + logger.info("Exporting TWW.param_ca_general -> ABWASSER.EZG_PARAMETER_ALLG") + self._export_param_ca_general() + self._check_for_stop() + + logger.info("Exporting TWW.param_ca_mouse1 -> ABWASSER.EZG_PARAMETER_MOUSE1") + self._export_param_ca_mouse1() + self._check_for_stop() + + logger.info("Exporting TWW.individual_surface -> ABWASSER.Einzelflaeche") + self._export_individual_surface() + self._check_for_stop() + + logger.info("Exporting TWW.catchment_area -> ABWASSER.Einzugsgebiet") + self._export_catchment_area() + self._check_for_stop() + + logger.info("Exporting TWW.electric_equipment -> ABWASSER.ElektrischeEinrichtung") + self._export_electric_equipment() + self._check_for_stop() + + logger.info( + "Exporting TWW.electromechanical_equipment -> ABWASSER.ElektromechanischeAusruestung" + ) + self._export_electromechanical_equipment() + self._check_for_stop() + + logger.info("Exporting TWW.disposal -> ABWASSER.Entsorgung") + self._export_disposal() + self._check_for_stop() + + logger.info("Exporting TWW.drainage_system -> ABWASSER.Entwaesserungssystem") + self._export_drainage_system() + self._check_for_stop() + + logger.info("Exporting TWW.solids_retention -> ABWASSER.Feststoffrueckhalt") + self._export_solids_retention() + self._check_for_stop() + + logger.info("Exporting TWW.pump -> ABWASSER.FoerderAggregat") + self._export_pump() + self._check_for_stop() + + logger.info("Exporting TWW.building -> ABWASSER.Gebaeude") + self._export_building() + self._check_for_stop() + + logger.info("Exporting TWW.building_group -> ABWASSER.Gebaeudegruppe") + self._export_building_group() + self._check_for_stop() + + logger.info("Exporting TWW.building_group_baugwr -> ABWASSER.Gebaeudegruppe_BAUGWR") + self._export_building_group_baugwr() + self._check_for_stop() + + logger.info("Exporting TWW.catchment_area_totals -> ABWASSER.Gesamteinzugsgebiet") + self._export_catchment_area_totals() + self._check_for_stop() + + logger.info("Exporting TWW.hq_relation -> ABWASSER.HQ_Relation") + self._export_hq_relation() + self._check_for_stop() + + logger.info("Exporting TWW.hydr_geom_relation -> ABWASSER.Hydr_GeomRelation") + self._export_hydr_geom_relation() + self._check_for_stop() + + logger.info("Exporting TWW.hydr_geometry -> ABWASSER.Hydr_Geometrie") + self._export_hydr_geometry() + self._check_for_stop() + + logger.info("Exporting TWW.hydraulic_char_data -> ABWASSER.Hydr_Kennwerte") + self._export_hydraulic_char_data() + self._check_for_stop() + + logger.info("Exporting TWW.small_treatment_plant -> ABWASSER.KLARA") + self._export_small_treatment_plant() + self._check_for_stop() + + logger.info("Exporting TWW.farm -> ABWASSER.Landwirtschaftsbetrieb") + self._export_farm() + self._check_for_stop() + + logger.info("Exporting TWW.leapingweir -> ABWASSER.Leapingwehr") + self._export_leapingweir() + self._check_for_stop() + + logger.info("Exporting TWW.measure -> ABWASSER.Massnahme") + self._export_measure() + self._check_for_stop() + + logger.info("Exporting TWW.mechanical_pretreatment -> ABWASSER.MechanischeVorreinigung") + self._export_mechanical_pretreatment() + self._check_for_stop() + + logger.info("Exporting TWW.measuring_device -> ABWASSER.Messgeraet") + self._export_measuring_device() + self._check_for_stop() + + logger.info("Exporting TWW.measurement_series -> ABWASSER.Messreihe") + self._export_measurement_series() + self._check_for_stop() + + logger.info("Exporting TWW.measurement_result -> ABWASSER.Messresultat") + self._export_measurement_result() + self._check_for_stop() + + logger.info("Exporting TWW.measuring_point -> ABWASSER.Messstelle") + self._export_measuring_point() + self._check_for_stop() + + logger.info("Exporting TWW.mutation -> ABWASSER.Mutation") + self._export_mutation() + self._check_for_stop() + + logger.info("Exporting TWW.reservoir -> ABWASSER.Reservoir") + self._export_reservoir() + self._check_for_stop() + + logger.info("Exporting TWW.retention_body -> ABWASSER.Retentionskoerper") + self._export_retention_body() + self._check_for_stop() + + logger.info("Exporting TWW.profile_geometry -> ABWASSER.Rohrprofil_Geometrie") + self._export_profile_geometry() + self._check_for_stop() + + logger.info("Exporting TWW.backflow_prevention -> ABWASSER.Rueckstausicherung") + self._export_backflow_prevention() + self._check_for_stop() + + logger.info("Exporting TWW.log_card -> ABWASSER.Stammkarte") + self._export_log_card() + self._check_for_stop() + + logger.info("Exporting TWW.prank_weir -> ABWASSER.Streichwehr") + self._export_prank_weir() + self._check_for_stop() + + logger.info("Exporting TWW.overflow_char -> ABWASSER.Ueberlaufcharakteristik") + self._export_overflow_char() + self._check_for_stop() + + logger.info("Exporting TWW.maintenance -> ABWASSER.Unterhalt") + self._export_maintenance() + self._check_for_stop() + + logger.info("Exporting TWW.infiltration_zone -> ABWASSER.Versickerungsbereich") + self._export_infiltration_zone() + self._check_for_stop() + + logger.info( + "Exporting TWW.re_maintenance_event_wastewater_structure -> ABWASSER.erhaltungsereignis_abwasserbauwerkassoc" + ) + self._export_re_maintenance_event_wastewater_structure() + self._check_for_stop() + + logger.info( + "Exporting TWW.re_building_group_disposal -> ABWASSER.gebaeudegruppe_entsorgungassoc" + ) + self._export_re_building_group_disposal() + self._check_for_stop() + + def _export_vsa_kek(self): + logger.info("Exporting TWW.examination -> ABWASSER.untersuchung") + self._export_examination() + self._check_for_stop() + + logger.info("Exporting TWW.damage_manhole -> ABWASSER.normschachtschaden") + self._export_damage_manhole() + self._check_for_stop() + + logger.info("Exporting TWW.damage_channel -> ABWASSER.kanalschaden") + self._export_damage_channel() + self._check_for_stop() + + logger.info("Exporting TWW.data_media -> ABWASSER.datentraeger") + self._export_data_media() + self._check_for_stop() + + logger.info("Exporting TWW.file -> ABWASSER.datei") + self._export_file() + self._check_for_stop() + + def _export_organisation(self): + query = self.tww_session.query(self.model_classes_tww_od.organisation) + for row in query: + organisation = self.model_classes_interlis.organisation( + # FIELDS TO MAP TO ABWASSER.organisation + # --- sia405_baseclass --- + **self.sia_405_base_common(row, "organisation"), + # --- organisation --- + auid=row.uid, + bemerkung=self.truncate(self.emptystr_to_null(row.remark), 255), + bezeichnung=self.null_to_emptystr(row.identifier), + kurzbezeichnung=row.identifier_short, + organisationstyp=self.get_vl(row.organisation_type__REL), + astatus=self.get_vl(row.status__REL), + ) + self.abwasser_session.add(organisation) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_channel(self): + query = self.tww_session.query(self.model_classes_tww_od.channel) + if self.filtered: + query = query.join(self.model_classes_tww_od.wastewater_networkelement).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + kanal = self.model_classes_interlis.kanal( + # FIELDS TO MAP TO ABWASSER.kanal + # --- abwasserbauwerk --- + **self.wastewater_structure_common(row, "kanal"), + # --- kanal --- + bettung_umhuellung=self.get_vl(row.bedding_encasement__REL), + funktionhierarchisch=self.get_vl(row.function_hierarchic__REL), + funktionhydraulisch=self.get_vl(row.function_hydraulic__REL), + nutzungsart_geplant=self.get_vl(row.usage_planned__REL), + nutzungsart_ist=self.get_vl(row.usage_current__REL), + rohrlaenge=row.pipe_length, + spuelintervall=row.jetting_interval, + verbindungsart=self.get_vl(row.connection_type__REL), + ) + self.abwasser_session.add(kanal) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_manhole(self): + query = self.tww_session.query(self.model_classes_tww_od.manhole) + if self.filtered: + query = query.join(self.model_classes_tww_od.wastewater_networkelement).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + normschacht = self.model_classes_interlis.normschacht( + # --- abwasserbauwerk --- + **self.wastewater_structure_common(row, "normschacht"), + # --- normschacht --- + dimension1=row.dimension1, + dimension2=row.dimension2, + funktion=self.get_vl(row.function__REL), + material=self.get_vl(row.material__REL), + oberflaechenzulauf=self.get_vl(row.surface_inflow__REL), + ) + self.abwasser_session.add(normschacht) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_discharge_point(self): + query = self.tww_session.query(self.model_classes_tww_od.discharge_point) + if self.filtered: + query = query.join(self.model_classes_tww_od.wastewater_networkelement).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + einleitstelle = self.model_classes_interlis.einleitstelle( + # --- abwasserbauwerk --- + **self.wastewater_structure_common(row, "einleitstelle"), + # --- einleitstelle --- + hochwasserkote=row.highwater_level, + relevanz=self.get_vl(row.relevance__REL), + terrainkote=row.terrain_level, + wasserspiegel_hydraulik=row.waterlevel_hydraulic, + ) + self.abwasser_session.add(einleitstelle) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_special_structure(self): + query = self.tww_session.query(self.model_classes_tww_od.special_structure) + if self.filtered: + query = query.join(self.model_classes_tww_od.wastewater_networkelement).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + logger.warning( + "TWW field special_structure.upper_elevation has no equivalent in the interlis model. It will be ignored." + ) + spezialbauwerk = self.model_classes_interlis.spezialbauwerk( + # FIELDS TO MAP TO ABWASSER.spezialbauwerk + # --- abwasserbauwerk --- + **self.wastewater_structure_common(row, "spezialbauwerk"), + # --- spezialbauwerk --- + # TODO : WARNING : upper_elevation is not mapped + bypass=self.get_vl(row.bypass__REL), + funktion=self.get_vl(row.function__REL), + notueberlauf=self.get_vl(row.emergency_overflow__REL), + regenbecken_anordnung=self.get_vl(row.stormwater_tank_arrangement__REL), + ) + self.abwasser_session.add(spezialbauwerk) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_infiltration_installation(self): + query = self.tww_session.query(self.model_classes_tww_od.infiltration_installation) + if self.filtered: + query = query.join(self.model_classes_tww_od.wastewater_networkelement).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + logger.warning( + "TWW field infiltration_installation.upper_elevation has no equivalent in the interlis model. It will be ignored." + ) + versickerungsanlage = self.model_classes_interlis.versickerungsanlage( + # FIELDS TO MAP TO ABWASSER.versickerungsanlage + # --- abwasserbauwerk --- + **self.wastewater_structure_common(row, "versickerungsanlage"), + # --- versickerungsanlage --- + # TODO : NOT MAPPED : upper_elevation + art=self.get_vl(row.kind__REL), + beschriftung=self.get_vl(row.labeling__REL), + dimension1=row.dimension1, + dimension2=row.dimension2, + gwdistanz=row.distance_to_aquifer, + maengel=self.get_vl(row.defects__REL), + notueberlauf=self.get_vl(row.emergency_overflow__REL), + saugwagen=self.get_vl(row.vehicle_access__REL), + schluckvermoegen=row.absorption_capacity, + versickerungswasser=self.get_vl(row.seepage_utilization__REL), + wasserdichtheit=self.get_vl(row.watertightness__REL), + wirksameflaeche=row.effective_area, + ) + self.abwasser_session.add(versickerungsanlage) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_pipe_profile(self): + query = self.tww_session.query(self.model_classes_tww_od.pipe_profile) + if self.filtered: + query = query.join(self.model_classes_tww_od.reach).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.pipe_profile + + # --- pipe_profile --- + # fk_dataowner, fk_provider, height_width_ratio, identifier, last_modification, obj_id, profile_type, remark + + # --- _bwrel_ --- + # profile_geometry__BWREL_fk_pipe_profile, reach__BWREL_fk_pipe_profile + + # --- _rel_ --- + # fk_dataowner__REL, fk_provider__REL, profile_type__REL + + rohrprofil = self.model_classes_interlis.rohrprofil( + # FIELDS TO MAP TO ABWASSER.rohrprofil + # --- vsa_baseclass --- + **self.vsa_base_common(row, "rohrprofil"), + # --- rohrprofil --- + bemerkung=self.truncate(self.emptystr_to_null(row.remark), 80), + bezeichnung=self.null_to_emptystr(row.identifier), + hoehenbreitenverhaeltnis=row.height_width_ratio, + profiltyp=self.get_vl(row.profile_type__REL), + ) + self.abwasser_session.add(rohrprofil) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_reach_point(self): + query = self.tww_session.query(self.model_classes_tww_od.reach_point) + if self.filtered: + query = query.join( + self.model_classes_tww_od.reach, + or_( + self.model_classes_tww_od.reach_point.obj_id + == self.model_classes_tww_od.reach.fk_reach_point_from, + self.model_classes_tww_od.reach_point.obj_id + == self.model_classes_tww_od.reach.fk_reach_point_to, + ), + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.reach_point + + # --- reach_point --- + # elevation_accuracy, fk_dataowner, fk_provider, fk_wastewater_networkelement, identifier, last_modification, level, obj_id, outlet_shape, position_of_connection, remark, situation_geometry + + # --- _bwrel_ --- + # examination__BWREL_fk_reach_point, reach__BWREL_fk_reach_point_from, reach__BWREL_fk_reach_point_to + + # --- _rel_ --- + # elevation_accuracy__REL, fk_dataowner__REL, fk_provider__REL, fk_wastewater_networkelement__REL, outlet_shape__REL + + haltungspunkt = self.model_classes_interlis.haltungspunkt( + # FIELDS TO MAP TO ABWASSER.haltungspunkt + # --- vsa_baseclass --- + **self.vsa_base_common(row, "haltungspunkt"), + # --- haltungspunkt --- + # changed call from get_tid to check_fk_in_subsetid so it does not write foreignkeys on elements that do not exist + abwassernetzelementref=self.check_fk_in_subsetid( + row.fk_wastewater_networkelement__REL + ), + auslaufform=self.get_vl(row.outlet_shape__REL), + bemerkung=self.truncate(self.emptystr_to_null(row.remark), 80), + bezeichnung=self.null_to_emptystr(row.identifier), + hoehengenauigkeit=self.get_vl(row.elevation_accuracy__REL), + kote=row.level, + lage=ST_Force2D(row.situation3d_geometry), + lage_anschluss=row.position_of_connection, + ) + self.abwasser_session.add(haltungspunkt) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_wastewater_node(self): + query = self.tww_session.query(self.model_classes_tww_od.wastewater_node) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.wastewater_node + + # --- wastewater_networkelement --- + # fk_dataowner, fk_provider, fk_wastewater_structure, identifier, last_modification, remark + + # --- wastewater_node --- + + # --- _bwrel_ --- + # catchment_area__BWREL_fk_wastewater_networkelement_rw_current, catchment_area__BWREL_fk_wastewater_networkelement_rw_planned, catchment_area__BWREL_fk_wastewater_networkelement_ww_current, catchment_area__BWREL_fk_wastewater_networkelement_ww_planned, connection_object__BWREL_fk_wastewater_networkelement, hydraulic_char_data__BWREL_fk_wastewater_node, overflow__BWREL_fk_overflow_to, overflow__BWREL_fk_wastewater_node, reach_point__BWREL_fk_wastewater_networkelement, throttle_shut_off_unit__BWREL_fk_wastewater_node, wastewater_structure__BWREL_fk_main_wastewater_node + + # --- _rel_ --- + # fk_dataowner__REL, fk_hydr_geometry__REL, fk_provider__REL, fk_wastewater_structure__REL + + logger.warning( + "TWW field wastewater_node.fk_hydr_geometry has no equivalent in the interlis model. It will be ignored." + ) + abwasserknoten = self.model_classes_interlis.abwasserknoten( + # FIELDS TO MAP TO ABWASSER.abwasserknoten + # --- abwassernetzelement --- + **self.wastewater_networkelement_common(row, "abwasserknoten"), + # --- abwasserknoten --- + # TODO : WARNING : fk_hydr_geometry is not mapped + lage=ST_Force2D(row.situation3d_geometry), + rueckstaukote_ist=row.backflow_level_current, + sohlenkote=row.bottom_level, + ) + self.abwasser_session.add(abwasserknoten) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_reach(self): + query = self.tww_session.query(self.model_classes_tww_od.reach) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.reach + + # --- wastewater_networkelement --- + # fk_dataowner, fk_provider, fk_wastewater_structure, identifier, last_modification, remark + + # --- reach --- + # clear_height, coefficient_of_friction, elevation_determination, fk_pipe_profile, fk_reach_point_from, fk_reach_point_to, horizontal_positioning, inside_coating, length_effective, material, obj_id, progression3d_geometry, reliner_material, reliner_nominal_size, relining_construction, relining_kind, ring_stiffness, slope_building_plan, wall_roughness + + # --- _bwrel_ --- + # catchment_area__BWREL_fk_wastewater_networkelement_rw_current, catchment_area__BWREL_fk_wastewater_networkelement_rw_planned, catchment_area__BWREL_fk_wastewater_networkelement_ww_current, catchment_area__BWREL_fk_wastewater_networkelement_ww_planned, connection_object__BWREL_fk_wastewater_networkelement, reach_point__BWREL_fk_wastewater_networkelement, reach_text__BWREL_fk_reach, txt_text__BWREL_fk_reach + + # --- _rel_ --- + # elevation_determination__REL, fk_dataowner__REL, fk_pipe_profile__REL, fk_provider__REL, fk_reach_point_from__REL, fk_reach_point_to__REL, fk_wastewater_structure__REL, horizontal_positioning__REL, inside_coating__REL, material__REL, reliner_material__REL, relining_construction__REL, relining_kind__REL + + logger.warning( + "TWW field reach.elevation_determination has no equivalent in the interlis model. It will be ignored." + ) + haltung = self.model_classes_interlis.haltung( + # FIELDS TO MAP TO ABWASSER.haltung + # --- abwassernetzelement --- + **self.wastewater_networkelement_common(row, "haltung"), + # --- haltung --- + # NOT MAPPED : elevation_determination + innenschutz=self.get_vl(row.inside_coating__REL), + laengeeffektiv=row.length_effective, + lagebestimmung=self.get_vl(row.horizontal_positioning__REL), + lichte_hoehe=row.clear_height, + material=self.get_vl(row.material__REL), + nachhaltungspunktref=self.get_tid(row.fk_reach_point_to__REL), + plangefaelle=row.slope_building_plan, # TODO : check, does this need conversion ? + reibungsbeiwert=row.coefficient_of_friction, + reliner_art=self.get_vl(row.relining_kind__REL), + reliner_bautechnik=self.get_vl(row.relining_construction__REL), + reliner_material=self.get_vl(row.reliner_material__REL), + reliner_nennweite=row.reliner_nominal_size, + ringsteifigkeit=row.ring_stiffness, + rohrprofilref=self.get_tid(row.fk_pipe_profile__REL), + verlauf=ST_Force2D(row.progression3d_geometry), + vonhaltungspunktref=self.get_tid(row.fk_reach_point_from__REL), + wandrauhigkeit=row.wall_roughness, + ) + self.abwasser_session.add(haltung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_reach_progression_alternative(self): + query = self.tww_session.query(self.model_classes_tww_od.reach_progression_alternative) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.reach_progression_alternative.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + haltung_alternativverlauf = self.model_classes_interlis.haltung_alternativverlauf( + **self.base_common(row, "haltung_alternativverlauf"), + # --- haltung_alternativverlauf --- + plantyp=self.get_vl(row.plantype__REL), + verlauf=row.progression_geometry, + haltungref=self.get_tid(row.fk_reach__REL), + ) + self.abwasser_session.add(haltung_alternativverlauf) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_dryweather_downspout(self): + query = self.tww_session.query(self.model_classes_tww_od.dryweather_downspout) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.dryweather_downspout + + # --- structure_part --- + # fk_dataowner, fk_provider, fk_wastewater_structure, identifier, last_modification, remark, renovation_demand + + # --- dryweather_downspout --- + # diameter, obj_id + + # --- _bwrel_ --- + # access_aid_kind__BWREL_obj_id, backflow_prevention__BWREL_obj_id, benching_kind__BWREL_obj_id, dryweather_flume_material__BWREL_obj_id, electric_equipment__BWREL_obj_id, electromechanical_equipment__BWREL_obj_id, solids_retention__BWREL_obj_id, flushing_nozzle__BWREL_obj_id, tank_cleaning__BWREL_obj_id, tank_emptying__BWREL_obj_id + + # --- _rel_ --- + # fk_dataowner__REL, fk_provider__REL, fk_wastewater_structure__REL, renovation_demand__REL + + trockenwetterfallrohr = self.model_classes_interlis.trockenwetterfallrohr( + # FIELDS TO MAP TO ABWASSER.trockenwetterfallrohr + # --- bauwerksteil --- + **self.structure_part_common(row, "trockenwetterfallrohr"), + # --- trockenwetterfallrohr --- + durchmesser=row.diameter, + ) + self.abwasser_session.add(trockenwetterfallrohr) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_access_aid(self): + query = self.tww_session.query(self.model_classes_tww_od.access_aid) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.access_aid + + # --- structure_part --- + # fk_dataowner, fk_provider, fk_wastewater_structure, identifier, last_modification, remark, renovation_demand + + # --- access_aid --- + # kind, obj_id + + # --- _bwrel_ --- + # access_aid_kind__BWREL_obj_id, backflow_prevention__BWREL_obj_id, benching_kind__BWREL_obj_id, dryweather_flume_material__BWREL_obj_id, electric_equipment__BWREL_obj_id, electromechanical_equipment__BWREL_obj_id, solids_retention__BWREL_obj_id, flushing_nozzle__BWREL_obj_id, tank_cleaning__BWREL_obj_id, tank_emptying__BWREL_obj_id + + # --- _rel_ --- + # fk_dataowner__REL, fk_provider__REL, fk_wastewater_structure__REL, kind__REL, renovation_demand__REL + + einstiegshilfe = self.model_classes_interlis.einstiegshilfe( + # FIELDS TO MAP TO ABWASSER.einstiegshilfe + # --- bauwerksteil --- + **self.structure_part_common(row, "einstiegshilfe"), + # --- einstiegshilfe --- + art=self.get_vl(row.kind__REL), + ) + self.abwasser_session.add(einstiegshilfe) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_dryweather_flume(self): + query = self.tww_session.query(self.model_classes_tww_od.dryweather_flume) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.dryweather_flume + + # --- structure_part --- + # fk_dataowner, fk_provider, fk_wastewater_structure, identifier, last_modification, remark, renovation_demand + + # --- dryweather_flume --- + # material, obj_id + + # --- _bwrel_ --- + # access_aid_kind__BWREL_obj_id, backflow_prevention__BWREL_obj_id, benching_kind__BWREL_obj_id, dryweather_flume_material__BWREL_obj_id, electric_equipment__BWREL_obj_id, electromechanical_equipment__BWREL_obj_id, solids_retention__BWREL_obj_id, flushing_nozzle__BWREL_obj_id, tank_cleaning__BWREL_obj_id, tank_emptying__BWREL_obj_id + + # --- _rel_ --- + # fk_dataowner__REL, fk_provider__REL, fk_wastewater_structure__REL, material__REL, renovation_demand__REL + + trockenwetterrinne = self.model_classes_interlis.trockenwetterrinne( + # FIELDS TO MAP TO ABWASSER.trockenwetterrinne + # --- bauwerksteil --- + **self.structure_part_common(row, "trockenwetterrinne"), + # --- trockenwetterrinne --- + material=self.get_vl(row.material__REL), + ) + self.abwasser_session.add(trockenwetterrinne) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_cover(self): + query = self.tww_session.query(self.model_classes_tww_od.cover) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.cover + + # --- structure_part --- + # fk_dataowner, fk_provider, fk_wastewater_structure, identifier, last_modification, remark, renovation_demand + + # --- cover --- + # brand, cover_shape, diameter, fastening, level, material, obj_id, positional_accuracy, situation3d_geometry, sludge_bucket, venting + + # --- _bwrel_ --- + # access_aid_kind__BWREL_obj_id, backflow_prevention__BWREL_obj_id, benching_kind__BWREL_obj_id, dryweather_flume_material__BWREL_obj_id, electric_equipment__BWREL_obj_id, electromechanical_equipment__BWREL_obj_id, solids_retention__BWREL_obj_id, flushing_nozzle__BWREL_obj_id, tank_cleaning__BWREL_obj_id, tank_emptying__BWREL_obj_id, wastewater_structure__BWREL_fk_main_cover + + # --- _rel_ --- + # cover_shape__REL, fastening__REL, fk_dataowner__REL, fk_provider__REL, fk_wastewater_structure__REL, material__REL, positional_accuracy__REL, renovation_demand__REL, sludge_bucket__REL, venting__REL + + deckel = self.model_classes_interlis.deckel( + # FIELDS TO MAP TO ABWASSER.deckel + # --- bauwerksteil --- + **self.structure_part_common(row, "deckel"), + # --- deckel --- + deckelform=self.get_vl(row.cover_shape__REL), + durchmesser=row.diameter, + entlueftung=self.get_vl(row.venting__REL), + fabrikat=row.brand, + kote=row.level, + lage=ST_Force2D(row.situation3d_geometry), + lagegenauigkeit=self.get_vl(row.positional_accuracy__REL), + material=self.get_vl(row.material__REL), + schlammeimer=self.get_vl(row.sludge_bucket__REL), + verschluss=self.get_vl(row.fastening__REL), + ) + self.abwasser_session.add(deckel) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_benching(self): + query = self.tww_session.query(self.model_classes_tww_od.benching) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.benching + + # --- structure_part --- + # fk_dataowner, fk_provider, fk_wastewater_structure, identifier, last_modification, remark, renovation_demand + + # --- benching --- + # kind, obj_id + + # --- _bwrel_ --- + # access_aid_kind__BWREL_obj_id, backflow_prevention__BWREL_obj_id, benching_kind__BWREL_obj_id, dryweather_flume_material__BWREL_obj_id, electric_equipment__BWREL_obj_id, electromechanical_equipment__BWREL_obj_id, solids_retention__BWREL_obj_id, flushing_nozzle__BWREL_obj_id, tank_cleaning__BWREL_obj_id, tank_emptying__BWREL_obj_id + + # --- _rel_ --- + # fk_dataowner__REL, fk_provider__REL, fk_wastewater_structure__REL, kind__REL, renovation_demand__REL + + bankett = self.model_classes_interlis.bankett( + # FIELDS TO MAP TO ABWASSER.bankett + # --- bauwerksteil --- + **self.structure_part_common(row, "bankett"), + # --- bankett --- + art=self.get_vl(row.kind__REL), + ) + self.abwasser_session.add(bankett) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_wastewater_structure_symbol(self): + query = self.tww_session.query(self.model_classes_tww_od.wastewater_structure_symbol) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + abwasserbauwerk_symbol = self.model_classes_interlis.abwasserbauwerk_symbol( + # FIELDS TO MAP TO ABWASSER.abwasserbauwerk_symbol + # --- abwasserbauwerk_symbol --- + **self.base_common(row, "abwasserbauwerk_symbol"), + plantyp=self.get_vl(row.plantype__REL), + symbolskalierunghoch=row.symbol_scaling_height, + symbolskalierunglaengs=row.symbol_scaling_width, + symbolori=row.symbolori, + symbolpos=row.symbolpos_geometry, + abwasserbauwerkref=self.get_tid(row.fk_wastewater_structure__REL), + ) + self.abwasser_session.add(abwasserbauwerk_symbol) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_flushing_nozzle(self): + query = self.tww_session.query(self.model_classes_tww_od.flushing_nozzle) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + spuelstutzen = self.model_classes_interlis.spuelstutzen( + # FIELDS TO MAP TO ABWASSER.spuelstutzen + # --- bauwerksteil --- + **self.structure_part_common(row, "spuelstutzen"), + # --- spuelstutzen --- + lage=row.situation_geometry, + ) + self.abwasser_session.add(spuelstutzen) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_waste_water_treatment_plant(self): + query = self.tww_session.query(self.model_classes_tww_od.waste_water_treatment_plant) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.waste_water_treatment_plant.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + abwasserreinigungsanlage = self.model_classes_interlis.abwasserreinigungsanlage( + **self.vsa_base_common(row, "abwasserreinigungsanlage"), + # --- abwasserreinigungsanlage --- + perimeter=row.area_geometry, + bsb5=row.bod5, + csb=row.cod, + eliminationcsb=row.elimination_cod, + eliminationn=row.elimination_n, + eliminationnh4=row.elimination_nh4, + eliminationp=row.elimination_p, + bezeichnung=row.identifier, + art=row.kind, + nh4=row.nh4, + betreibertyp=self.get_vl(row.operator_type__REL), + einwohner_angeschlossen=row.population_connected, + einwohner_total=row.population_total, + bemerkung=self.truncate(self.emptystr_to_null(row.remark), 255), + lage=row.situation_geometry, + inbetriebnahme=row.start_year, + ara_nr=row.wwtp_number, + ) + self.abwasser_session.add(abwasserreinigungsanlage) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_wwtp_energy_use(self): + query = self.tww_session.query(self.model_classes_tww_od.wwtp_energy_use) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.wwtp_energy_use.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + araenergienutzung = self.model_classes_interlis.araenergienutzung( + **self.vsa_base_common(row, "araenergienutzung"), + # --- araenergienutzung --- + gasmotor=row.gas_motor, + waermepumpe=row.heat_pump, + bezeichnung=row.identifier, + bemerkung=row.remark, + turbinierung=row.turbining, + abwasserreinigungsanlageref=self.get_tid(row.fk_waste_water_treatment_plant__REL), + ) + self.abwasser_session.add(araenergienutzung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_waste_water_treatment(self): + query = self.tww_session.query(self.model_classes_tww_od.waste_water_treatment) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.waste_water_treatment.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + abwasserbehandlung = self.model_classes_interlis.abwasserbehandlung( + **self.vsa_base_common(row, "abwasserbehandlung"), + # --- abwasserbehandlung --- + bezeichnung=row.identifier, + art=self.get_vl(row.kind__REL), + bemerkung=row.remark, + abwasserreinigungsanlageref=self.get_tid(row.fk_waste_water_treatment_plant__REL), + ) + self.abwasser_session.add(abwasserbehandlung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_sludge_treatment(self): + query = self.tww_session.query(self.model_classes_tww_od.sludge_treatment) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.sludge_treatment.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + schlammbehandlung = self.model_classes_interlis.schlammbehandlung( + **self.vsa_base_common(row, "schlammbehandlung"), + # --- schlammbehandlung --- + kompostierung=row.composting, + entwaesserung=row.dehydration, + faulschlammverbrennung=row.digested_sludge_combustion, + trocknung=row.drying, + frischschlammverbrennung=row.fresh_sludge_combustion, + hygienisierung=row.hygenisation, + bezeichnung=row.identifier, + ueberschusschlammvoreindickung=row.predensification_of_excess_sludge, + mischschlammvoreindickung=row.predensification_of_mixed_sludge, + primaerschlammvoreindickung=row.predensification_of_primary_sludge, + bemerkung=row.remark, + stabilisierung=self.get_vl(row.stabilisation__REL), + entwaessertklaerschlammstapelung=row.stacking_of_dehydrated_sludge, + fluessigklaerschlammstapelung=row.stacking_of_liquid_sludge, + abwasserreinigungsanlageref=self.get_tid(row.fk_waste_water_treatment_plant__REL), + ) + self.abwasser_session.add(schlammbehandlung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_wwtp_structure(self): + query = self.tww_session.query(self.model_classes_tww_od.wwtp_structure) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + arabauwerk = self.model_classes_interlis.arabauwerk( + # --- bauwerksteil --- + **self.wastewater_structure_common(row, "arabauwerk"), + # --- arabauwerk --- + art=self.get_vl(row.kind__REL), + abwasserreinigungsanlageref=self.get_tid(row.fk_waste_water_treatment_plant__REL), + ) + self.abwasser_session.add(arabauwerk) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_control_center(self): + query = self.tww_session.query(self.model_classes_tww_od.control_center) + if self.filtered: + query = query.join( + self.model_classes_tww_od.throttle_shut_off_unit, + self.model_classes_tww_od.wastewater_node, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + steuerungszentrale = self.model_classes_interlis.steuerungszentrale( + **self.vsa_base_common(row, "steuerungszentrale"), + # --- steuerungszentrale --- + bezeichnung=row.identifier, + lage=row.situation_geometry, + ) + self.abwasser_session.add(steuerungszentrale) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_drainless_toilet(self): + query = self.tww_session.query(self.model_classes_tww_od.drainless_toilet) + # subclass of wastewater_structure - therefore same as eg. manhole + if self.filtered: + query = query.join(self.model_classes_tww_od.wastewater_networkelement).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + abflusslose_toilette = self.model_classes_interlis.abflusslose_toilette( + **self.wastewater_structure_common(row, "abflusslose_toilette"), + # --- drainless_toilet --- + art=self.get_vl(row.kind__REL), + ) + self.abwasser_session.add(abflusslose_toilette) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_throttle_shut_off_unit(self): + query = self.tww_session.query(self.model_classes_tww_od.throttle_shut_off_unit) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_node, + or_( + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.throttle_shut_off_unit.fk_wastewater_node, + ), + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + absperr_drosselorgan = self.model_classes_interlis.absperr_drosselorgan( + **self.vsa_base_common(row, "absperr_drosselorgan"), + # --- throttle_shut_off_unit --- + antrieb=self.get_vl(row.actuation__REL), + verstellbarkeit=self.get_vl(row.adjustability__REL), + steuerung=self.get_vl(row.control__REL), + querschnitt=row.cross_section, + wirksamer_qs=row.effective_cross_section, + bruttokosten=row.gross_costs, + bezeichnung=row.identifier, + art=self.get_vl(row.kind__REL), + fabrikat=row.manufacturer, + bemerkung=row.remark, + signaluebermittlung=self.get_vl(row.signal_transmission__REL), + subventionen=row.subsidies, + drosselorgan_oeffnung_ist=row.throttle_unit_opening_current, + drosselorgan_oeffnung_ist_optimiert=row.throttle_unit_opening_current_optimized, + abwasserknotenref=self.get_tid(row.fk_wastewater_node__REL), + steuerungszentraleref=self.get_tid(row.fk_control_center__REL), + ueberlaufref=self.get_tid(row.fk_overflow__REL), + ) + self.abwasser_session.add(absperr_drosselorgan) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_tank_emptying(self): + query = self.tww_session.query(self.model_classes_tww_od.tank_emptying) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + beckenentleerung = self.model_classes_interlis.beckenentleerung( + **self.structure_part_common(row, "beckenentleerung"), + # --- tank_emptying --- + leistung=row.flow, + bruttokosten=row.gross_costs, + art=self.get_vl(row.kind__REL), + ersatzjahr=row.year_of_replacement, + absperr_drosselorganref=self.get_tid(row.fk_throttle_shut_off_unit__REL), + ueberlaufref=self.get_tid(row.fk_overflow__REL), + ) + self.abwasser_session.add(beckenentleerung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_tank_cleaning(self): + query = self.tww_session.query(self.model_classes_tww_od.tank_cleaning) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + beckenreinigung = self.model_classes_interlis.beckenreinigung( + **self.structure_part_common(row, "beckenreinigung"), + # --- tank_cleaning --- + bruttokosten=row.gross_costs, + art=self.get_vl(row.kind__REL), + ersatzjahr=row.year_of_replacement, + ) + self.abwasser_session.add(beckenreinigung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_bio_ecol_assessment(self): + query = self.tww_session.query(self.model_classes_tww_od.bio_ecol_assessment) + if self.filtered: + query = ( + query.join(self.model_classes_tww_od.re_maintenance_event_wastewater_structure) + .join(self.model_classes_tww_od.wastewater_structure) + .join(self.model_classes_tww_od.wastewater_networkelement) + .filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + biol_oekol_gesamtbeurteilung = self.model_classes_interlis.biol_oekol_gesamtbeurteilung( + **self.maintenance_event_common(row, "biol_oekol_gesamtbeurteilung"), + # --- bio_ecol_assessment --- + vergleich_letzte_untersuchung=self.get_vl(row.comparison_last__REL), + datum_letzte_untersuchung=row.date_last_examen, + einfluss_hilfsindikatoren=self.get_vl(row.impact_auxiliary_indic__REL), + einfluss_aeusserer_aspekt=self.get_vl(row.impact_external_aspect__REL), + einfluss_makroinvertebraten=self.get_vl(row.impact_macroinvertebrates__REL), + einfluss_wasserpflanzen=self.get_vl(row.impact_water_plants__REL), + handlungsbedarf=self.get_vl(row.intervention_demand__REL), + immissionsorientierte_berechnung=self.get_vl(row.io_calculation__REL), + auslaufrohr_lichte_hoehe=row.outlet_pipe_clear_height, + q347=row.q347, + relevanzmatrix=self.get_vl(row.relevance_matrix__REL), + relevantes_gefaelle=row.relevant_slope, + oberflaechengewaesser=row.surface_water_bodies, + gewaesserart=self.get_vl(row.kind_water_body__REL), + gewaesserspezifische_entlastungsfracht_nh4_n_ist=row.water_specific_discharge_freight_nh4_n_current, + gewaesserspezifische_entlastungsfracht_nh4_n_ist_optimiert=row.water_specific_discharge_freight_nh4_n_current_opt, + gewaesserspezifische_entlastungsfracht_nh4_n_geplant=row.water_specific_discharge_freight_nh4_n_planned, + ) + self.abwasser_session.add(biol_oekol_gesamtbeurteilung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_fountain(self): + query = self.tww_session.query(self.model_classes_tww_od.fountain) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + brunnen = self.model_classes_interlis.brunnen( + **self.connection_object_common(row, "brunnen"), + # --- fountain --- + standortname=row.location_name, + lage=row.situation_geometry, + ) + self.abwasser_session.add(brunnen) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_param_ca_general(self): + query = self.tww_session.query(self.model_classes_tww_od.param_ca_general) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.param_ca_general.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + ezg_parameter_allg = self.model_classes_interlis.ezg_parameter_allg( + **self.surface_runoff_parameters_common(row, "ezg_parameter_allg"), + # --- param_ca_general --- + trockenwetteranfall=row.dry_wheather_flow, + fliessweglaenge=row.flow_path_length, + fliessweggefaelle=row.flow_path_slope, + einwohnergleichwert=row.population_equivalent, + flaeche=row.surface_ca, + ) + self.abwasser_session.add(ezg_parameter_allg) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_param_ca_mouse1(self): + query = self.tww_session.query(self.model_classes_tww_od.param_ca_mouse1) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.param_ca_mouse1.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + ezg_parameter_mouse1 = self.model_classes_interlis.ezg_parameter_mouse1( + **self.surface_runoff_parameters_common(row, "ezg_parameter_mouse1"), + # --- param_ca_mouse1 --- + trockenwetteranfall=row.dry_wheather_flow, + fliessweglaenge=row.flow_path_length, + fliessweggefaelle=row.flow_path_slope, + einwohnergleichwert=row.population_equivalent, + flaeche=row.surface_ca_mouse, + nutzungsart=row.usage, + ) + self.abwasser_session.add(ezg_parameter_mouse1) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_individual_surface(self): + query = self.tww_session.query(self.model_classes_tww_od.individual_surface) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + einzelflaeche = self.model_classes_interlis.einzelflaeche( + **self.connection_object_common(row, "einzelflaeche"), + # --- individual_surface --- + funktion=self.get_vl(row.function__REL), + neigung=row.inclination, + befestigung=self.get_vl(row.pavement__REL), + perimeter=row.perimeter_geometry, + ) + self.abwasser_session.add(einzelflaeche) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_catchment_area(self): + query = self.tww_session.query(self.model_classes_tww_od.catchment_area) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_networkelement, + or_( + self.model_classes_tww_od.wastewater_networkelement.obj_id + == self.model_classes_tww_od.catchment_area.fk_wastewater_networkelement_rw_planned, + self.model_classes_tww_od.wastewater_networkelement.obj_id + == self.model_classes_tww_od.catchment_area.fk_wastewater_networkelement_rw_current, + self.model_classes_tww_od.wastewater_networkelement.obj_id + == self.model_classes_tww_od.catchment_area.fk_wastewater_networkelement_ww_planned, + self.model_classes_tww_od.wastewater_networkelement.obj_id + == self.model_classes_tww_od.catchment_area.fk_wastewater_networkelement_ww_current, + ), + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + einzugsgebiet = self.model_classes_interlis.einzugsgebiet( + **self.vsa_base_common(row, "einzugsgebiet"), + # --- catchment_area --- + direkteinleitung_in_gewaesser_ist=self.get_vl(row.direct_discharge_current__REL), + direkteinleitung_in_gewaesser_geplant=self.get_vl( + row.direct_discharge_planned__REL + ), + abflussbeiwert_rw_ist=row.discharge_coefficient_rw_current, + abflussbeiwert_rw_geplant=row.discharge_coefficient_rw_planned, + abflussbeiwert_sw_ist=row.discharge_coefficient_ww_current, + abflussbeiwert_sw_geplant=row.discharge_coefficient_ww_planned, + entwaesserungssystem_ist=self.get_vl(row.drainage_system_current__REL), + entwaesserungssystem_geplant=self.get_vl(row.drainage_system_planned__REL), + bezeichnung=row.identifier, + versickerung_ist=self.get_vl(row.infiltration_current__REL), + versickerung_geplant=self.get_vl(row.infiltration_planned__REL), + perimeter=row.perimeter_geometry, + einwohnerdichte_ist=row.population_density_current, + einwohnerdichte_geplant=row.population_density_planned, + bemerkung=row.remark, + retention_ist=self.get_vl(row.retention_current__REL), + retention_geplant=self.get_vl(row.retention_planned__REL), + abflussbegrenzung_ist=row.runoff_limit_current, + abflussbegrenzung_geplant=row.runoff_limit_planned, + befestigungsgrad_rw_ist=row.seal_factor_rw_current, + befestigungsgrad_rw_geplant=row.seal_factor_rw_planned, + befestigungsgrad_sw_ist=row.seal_factor_ww_current, + befestigungsgrad_sw_geplant=row.seal_factor_ww_planned, + fremdwasseranfall_ist=row.sewer_infiltration_water_production_current, + fremdwasseranfall_geplant=row.sewer_infiltration_water_production_planned, + flaeche=row.surface_area, + schmutzabwasseranfall_ist=row.waste_water_production_current, + schmutzabwasseranfall_geplant=row.waste_water_production_planned, + # changed call from get_tid to check_fk_in_subsetid so it does not write foreignkeys on elements that do not exist + abwassernetzelement_rw_geplantref=self.check_fk_in_subsetid( + row.fk_wastewater_networkelement_rw_planned__REL + ), + abwassernetzelement_rw_istref=self.check_fk_in_subsetid( + row.fk_wastewater_networkelement_rw_current__REL + ), + abwassernetzelement_sw_geplantref=self.check_fk_in_subsetid( + row.fk_wastewater_networkelement_ww_planned__REL + ), + abwassernetzelement_sw_istref=self.check_fk_in_subsetid( + row.fk_wastewater_networkelement_ww_current__REL + ), + sbw_rw_geplantref=self.get_tid(row.fk_special_building_rw_planned__REL), + sbw_rw_istref=self.get_tid(row.fk_special_building_rw_current__REL), + sbw_sw_geplantref=self.get_tid(row.fk_special_building_ww_planned__REL), + sbw_sw_istref=self.get_tid(row.fk_special_building_ww_current__REL), + ) + self.abwasser_session.add(einzugsgebiet) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_electric_equipment(self): + query = self.tww_session.query(self.model_classes_tww_od.electric_equipment) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + elektrischeeinrichtung = self.model_classes_interlis.elektrischeeinrichtung( + **self.structure_part_common(row, "elektrischeeinrichtung"), + # --- electric_equipment --- + bruttokosten=row.gross_costs, + art=self.get_vl(row.kind__REL), + ersatzjahr=row.year_of_replacement, + ) + self.abwasser_session.add(elektrischeeinrichtung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_electromechanical_equipment(self): + query = self.tww_session.query(self.model_classes_tww_od.electromechanical_equipment) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + elektromechanischeausruestung = self.model_classes_interlis.elektromechanischeausruestung( + **self.structure_part_common(row, "elektromechanischeausruestung"), + # --- electromechanical_equipment --- + bruttokosten=row.gross_costs, + art=self.get_vl(row.kind__REL), + ersatzjahr=row.year_of_replacement, + ) + self.abwasser_session.add(elektromechanischeausruestung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_disposal(self): + query = self.tww_session.query(self.model_classes_tww_od.disposal) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter(self.model_classes_tww_od.disposal.obj_id.in_(self.subset_ids)) + logger.info(f"Selection query: {query.statement}") + for row in query: + entsorgung = self.model_classes_interlis.entsorgung( + **self.vsa_base_common(row, "entsorgung"), + # --- disposal --- + entsorgungsintervall_ist=row.disposal_interval_current, + entsorgungsintervall_soll=row.disposal_interval_nominal, + entsorgungsort_ist=self.get_vl(row.disposal_place_current__REL), + entsorgungsort_geplant=self.get_vl(row.disposal_place_planned__REL), + volumenabflusslosegrube=row.volume_pit_without_drain, + versickerungsanlageref=self.get_tid(row.fk_infiltration_installation__REL), + einleitstelleref=self.get_tid(row.fk_discharge_point__REL), + abwasserbauwerkref=self.get_tid(row.fk_wastewater_structure__REL), + ) + self.abwasser_session.add(entsorgung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_drainage_system(self): + query = self.tww_session.query(self.model_classes_tww_od.drainage_system) + # no connection to sewer network - selected obj_id for drainage_system have to be added specifically + if self.filtered: + query = query.filter( + self.model_classes_tww_od.drainage_system.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + entwaesserungssystem = self.model_classes_interlis.entwaesserungssystem( + **self.zone_common(row, "entwaesserungssystem"), + # --- drainage_system --- + art=self.get_vl(row.kind__REL), + perimeter=row.perimeter_geometry, + ) + self.abwasser_session.add(entwaesserungssystem) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_solids_retention(self): + query = self.tww_session.query(self.model_classes_tww_od.solids_retention) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + feststoffrueckhalt = self.model_classes_interlis.feststoffrueckhalt( + **self.structure_part_common(row, "feststoffrueckhalt"), + # --- solids_retention --- + dimensionierungswert=row.dimensioning_value, + bruttokosten=row.gross_costs, + anspringkote=row.overflow_level, + art=self.get_vl(row.kind__REL), + ersatzjahr=row.year_of_replacement, + ) + self.abwasser_session.add(feststoffrueckhalt) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_pump(self): + query = self.tww_session.query(self.model_classes_tww_od.pump) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_node, + or_( + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.prank_weir.fk_wastewater_node, + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.prank_weir.fk_overflow_to, + ), + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + foerderaggregat = self.model_classes_interlis.foerderaggregat( + **self.overflow_common(row, "foerderaggregat"), + # --- pump --- + bauart=self.get_vl(row.construction_type__REL), + arbeitspunkt=row.operating_point, + aufstellungantrieb=self.get_vl(row.placement_of_actuation__REL), + aufstellungfoerderaggregat=self.get_vl(row.placement_of_pump__REL), + foerderstrommax_einzel=row.pump_flow_max_single, + foerderstrommin_einzel=row.pump_flow_min_single, + kotestart=row.start_level, + kotestop=row.stop_level, + ) + self.abwasser_session.add(foerderaggregat) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_building(self): + query = self.tww_session.query(self.model_classes_tww_od.building) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + gebaeude = self.model_classes_interlis.gebaeude( + **self.connection_object_common(row, "gebaeude"), + # --- building --- + hausnummer=row.house_number, + standortname=row.location_name, + perimeter=row.perimeter_geometry, + referenzpunkt=row.reference_point_geometry, + ) + self.abwasser_session.add(gebaeude) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_building_group(self): + query = self.tww_session.query(self.model_classes_tww_od.building_group) + if self.filtered: + query = query.join( + self.model_classes_tww_od.re_building_group_disposal, + self.model_classes_tww_od.disposal, + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + gebaeudegruppe = self.model_classes_interlis.gebaeudegruppe( + **self.vsa_base_common(row, "gebaeudegruppe"), + # --- building_group --- + kinositzplaetze=row.movie_theater_seats, + kirchesitzplaetze=row.church_seats, + campingflaeche=row.camping_area, + campinguebernachtungen=row.camping_lodgings, + anschlusspflicht=self.get_vl(row.connecting_obligation__REL), + anschlussara=self.get_vl(row.connection_wwtp__REL), + gewerbebeschaeftigte=row.craft_employees, + schlafsaalbetten=row.dorm_beds, + schlafsaaluebernachtungen=row.dorm_overnight_stays, + entwaesserungsplan=self.get_vl(row.drainage_map__REL), + trinkwassernetzanschluss=self.get_vl(row.drinking_water_network__REL), + trinkwasserandere=self.get_vl(row.drinking_water_others__REL), + stromanschluss=self.get_vl(row.electric_connection__REL), + veranstaltungbesucher=row.event_visitors, + funktion=self.get_vl(row.function__REL), + turnhalleflaeche=row.gym_area, + ferienuebernachtungen=row.holiday_accomodation, + spitalbetten=row.hospital_beds, + hotelbetten=row.hotel_beds, + hoteluebernachtungen=row.hotel_overnight_stays, + bezeichnung=row.identifier, + anderenutzungegw=row.other_usage_population_equivalent, + anderenutzungart=row.other_usage_type, + einwohnerwerte=row.population_equivalent, + bemerkung=row.remark, + sanierungsdatum=row.renovation_date, + sanierungsbedarf=self.get_vl(row.renovation_necessity__REL), + raststaettesitzplaetze=row.restaurant_seats, + restaurantsitzplaetze_saalgarten=row.restaurant_seats_hall_garden, + restaurantsitzplaetze_permanent=row.restaurant_seats_permanent, + sanierungskonzept=row.restructuring_concept, + schuleschueler=row.school_students, + lage=row.situation_geometry, + # entsorgungref=self.get_tid(row.fk_disposal__REL), # TODO check why not available + massnahmeref=self.get_tid(row.fk_measure__REL), + ) + self.abwasser_session.add(gebaeudegruppe) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_building_group_baugwr(self): + query = self.tww_session.query(self.model_classes_tww_od.building_group_baugwr) + if self.filtered: + query = query.join( + self.model_classes_tww_od.building_group, + self.model_classes_tww_od.re_building_group_disposal, + self.model_classes_tww_od.disposal, + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + gebaeudegruppe_baugwr = self.model_classes_interlis.gebaeudegruppe_baugwr( + **self.vsa_base_common(row, "gebaeudegruppe_baugwr"), + # --- building_group_baugwr --- + egid=row.egid, + gebaeudegrupperef=self.get_tid(row.fk_building_group__REL), + ) + self.abwasser_session.add(gebaeudegruppe_baugwr) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_catchment_area_totals(self): + query = self.tww_session.query(self.model_classes_tww_od.catchment_area_totals) + # only export catchment_area_totals if explicitly added + if self.filtered: + query = query.filter( + self.model_classes_tww_od.catchment_area_totals.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + gesamteinzugsgebiet = self.model_classes_interlis.gesamteinzugsgebiet( + **self.vsa_base_common(row, "gesamteinzugsgebiet"), + # --- catchment_area_totals --- + entlastungsfracht_nh4_n=row.discharge_freight_nh4_n, + entlastungsanteil_nh4_n=row.discharge_proportion_nh4_n, + bezeichnung=row.identifier, + einwohner=row.population, + einwohner_dim=row.population_dim, + fremdwasseranfall=row.sewer_infiltration_water, + flaeche=row.surface_area, + flaeche_dim=row.surface_dim, + flaeche_bef=row.surface_imp, + flaeche_bef_dim=row.surface_imp_dim, + flaeche_red=row.surface_red, + flaeche_red_dim=row.surface_red_dim, + schmutzabwasseranfall=row.waste_water_production, + # discharge_point might no be in selection - therefore use check_fk_in_subsetid instead of get_tid + einleitstelleref=self.check_fk_in_subsetid(row.fk_discharge_point__REL), + hydr_kennwerteref=self.get_tid(row.fk_hydraulic_char_data__REL), + ) + self.abwasser_session.add(gesamteinzugsgebiet) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_hq_relation(self): + query = self.tww_session.query(self.model_classes_tww_od.hq_relation) + if self.filtered: + # just check if overflow_char exists, but no filter + query = query.join( + self.model_classes_tww_od.overflow_char, + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + hq_relation = self.model_classes_interlis.hq_relation( + **self.vsa_base_common(row, "hq_relation"), + # --- hq_relation --- + hoehe=row.altitude, + abfluss=row.flow, + zufluss=row.flow_from, + ueberlaufcharakteristikref=self.get_tid(row.fk_overflow_char__REL), + ) + self.abwasser_session.add(hq_relation) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_hydr_geom_relation(self): + query = self.tww_session.query(self.model_classes_tww_od.hydr_geom_relation) + if self.filtered: + # to do check if join is ok or left/right join is needed + query = query.join( + self.model_classes_tww_od.hydr_geometry, + self.model_classes_tww_od.wastewater_node, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + hydr_geomrelation = self.model_classes_interlis.hydr_geomrelation( + **self.vsa_base_common(row, "hydr_geomrelation"), + # --- hydr_geom_relation --- + wassertiefe=row.water_depth, + wasseroberflaeche=row.water_surface, + benetztequerschnittsflaeche=row.wet_cross_section_area, + hydr_geometrieref=self.get_tid(row.fk_hydr_geometry__REL), + ) + self.abwasser_session.add(hydr_geomrelation) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_hydr_geometry(self): + query = self.tww_session.query(self.model_classes_tww_od.hydr_geometry) + if self.filtered: + # to do check if join is ok or left/right join is needed + query = query.join( + self.model_classes_tww_od.wastewater_node, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + hydr_geometrie = self.model_classes_interlis.hydr_geometrie( + **self.vsa_base_common(row, "hydr_geometrie"), + # --- hydr_geometry --- + bezeichnung=row.identifier, + bemerkung=row.remark, + stauraum=row.storage_volume, + nutzinhalt_fangteil=row.usable_capacity_storage, + nutzinhalt_klaerteil=row.usable_capacity_treatment, + nutzinhalt=row.utilisable_capacity, + volumen_pumpensumpf=row.volume_pump_sump, + ) + self.abwasser_session.add(hydr_geometrie) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_hydraulic_char_data(self): + query = self.tww_session.query(self.model_classes_tww_od.hydraulic_char_data) + if self.filtered: + # side fk_overflow_char not considered in filter query + query = query.join( + self.model_classes_tww_od.wastewater_node, + or_( + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.hydraulic_char_data.fk_wastewater_node, + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.hydraulic_char_data.fk_primary_direction, + ), + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + hydr_kennwerte = self.model_classes_interlis.hydr_kennwerte( + **self.vsa_base_common(row, "hydr_kennwerte"), + # --- hydraulic_char_data --- + qan=row.qon, + bemerkung=row.remark, + astatus=self.get_vl(row.status__REL), + aggregatezahl=row.aggregate_number, + foerderhoehe_geodaetisch=row.delivery_height_geodaetic, + bezeichnung=row.identifier, + springt_an=self.get_vl(row.is_overflowing__REL), + hauptwehrart=self.get_vl(row.main_weir_kind__REL), + mehrbelastung=row.overcharge, + ueberlaufdauer=row.overflow_duration, + ueberlauffracht=row.overflow_freight, + ueberlaufhaeufigkeit=row.overflow_frequency, + ueberlaufmenge=row.overflow_volume, + pumpenregime=self.get_vl(row.pump_characteristics__REL), + foerderstrommax=row.pump_flow_max, + foerderstrommin=row.pump_flow_min, + qab=row.q_discharge, + abwasserknotenref=self.get_tid(row.fk_wastewater_node__REL), + ueberlaufcharakteristikref=self.get_tid(row.fk_overflow_char__REL), + primaerrichtungref=self.get_tid(row.fk_primary_direction__REL), + ) + self.abwasser_session.add(hydr_kennwerte) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_small_treatment_plant(self): + query = self.tww_session.query(self.model_classes_tww_od.small_treatment_plant) + if self.filtered: + query = query.join(self.model_classes_tww_od.wastewater_networkelement).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + klara = self.model_classes_interlis.klara( + **self.wastewater_structure_common(row, "klara"), + # --- small_treatment_plant --- + bewilligungsnummer=row.approval_number, + funktion=self.get_vl(row.function__REL), + anlagenummer=row.installation_number, + fernueberwachung=self.get_vl(row.remote_monitoring__REL), + ) + self.abwasser_session.add(klara) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_farm(self): + query = self.tww_session.query(self.model_classes_tww_od.farm) + if self.filtered: + query = query.join( + self.model_classes_tww_od.building_group, + self.model_classes_tww_od.re_building_group_disposal, + self.model_classes_tww_od.disposal, + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + landwirtschaftsbetrieb = self.model_classes_interlis.landwirtschaftsbetrieb( + **self.vsa_base_common(row, "landwirtschaftsbetrieb"), + # --- farm --- + nutzflaechelandwirtschaft=row.agriculture_arable_surface, + guellegrubebemerkung=row.cesspit_comment, + guellegrubevolumen=self.get_vl(row.cesspit_volume__REL), + guellegrubevolumen_ist=row.cesspit_volume_current, + guellegrubevolumen_soll=row.cesspit_volume_nominal, + guellegrubevolumen_sw_behandelt=row.cesspit_volume_ww_treated, + guellegrubebewilligungsjahr=row.cesspit_year_of_approval, + konformitaet=self.get_vl(row.conformity__REL), + fortbestand=self.get_vl(row.continuance__REL), + fortbestandbemerkung=row.continuance_comment, + mistplatzflaeche_ist=row.dung_heap_area_current, + mistplatzflaeche_soll=row.dung_heap_area_nominal, + bemerkung=row.remark, + hirtenhuettebemerkung=row.shepherds_hut_comment, + hirtenhuetteegw=row.shepherds_hut_population_equivalent, + hirtenhuetteabwasser=self.get_vl(row.shepherds_hut_wastewater__REL), + stallvieh=self.get_vl(row.stable_cattle__REL), + stallgrossvieheinheit_fremdvieh=row.stable_cattle_equivalent_other_cattle, + stallgrossvieheinheit_eigenesvieh=row.stable_cattle_equivalent_own_cattle, + gebaeudegrupperef=self.get_tid(row.fk_building_group__REL), + ) + self.abwasser_session.add(landwirtschaftsbetrieb) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_leapingweir(self): + query = self.tww_session.query(self.model_classes_tww_od.leapingweir) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_node, + or_( + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.leapingweir.fk_wastewater_node, + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.leapingweir.fk_overflow_to, + ), + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + leapingwehr = self.model_classes_interlis.leapingwehr( + **self.overflow_common(row, "leapingwehr"), + # --- leapingweir --- + laenge=row.length, + oeffnungsform=self.get_vl(row.opening_shape__REL), + breite=row.width, + ) + self.abwasser_session.add(leapingwehr) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_measure(self): + query = self.tww_session.query(self.model_classes_tww_od.measure) + # always export all measure, therefore no if self.filtered. Adding filter here needs further investigation + # if self.filtered: + # query = query.filter(self.model_classes_tww_od.measure.obj_id.in_(self.subset_ids)) + for row in query: + massnahme = self.model_classes_interlis.massnahme( + **self.vsa_base_common(row, "massnahme"), + # --- measure --- + datum_eingang=row.date_entry, + beschreibung=row.description, + kategorie=self.get_vl(row.category__REL), + bezeichnung=row.identifier, + handlungsbedarf=row.intervention_demand, + linie=row.line_geometry, + verweis=row.link, + perimeter=row.perimeter_geometry, + prioritaet=self.get_vl(row.priority__REL), + bemerkung=row.remark, + astatus=self.get_vl(row.status__REL), + symbolpos=row.symbolpos_geometry, + gesamtkosten=row.total_cost, + jahr_umsetzung_effektiv=row.year_implementation_effective, + jahr_umsetzung_geplant=row.year_implementation_planned, + traegerschaftref=self.get_tid(row.fk_responsible_entity__REL), + verantwortlich_ausloesungref=self.get_tid(row.fk_responsible_start__REL), + ) + self.abwasser_session.add(massnahme) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_mechanical_pretreatment(self): + query = self.tww_session.query(self.model_classes_tww_od.mechanical_pretreatment) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + mechanischevorreinigung = self.model_classes_interlis.mechanischevorreinigung( + **self.vsa_base_common(row, "mechanischevorreinigung"), + # --- mechanical_pretreatment --- + bezeichnung=row.identifier, + art=self.get_vl(row.kind__REL), + bemerkung=row.remark, + abwasserbauwerkref=self.get_tid(row.fk_wastewater_structure__REL), + ) + self.abwasser_session.add(mechanischevorreinigung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_measuring_device(self): + query = self.tww_session.query(self.model_classes_tww_od.measuring_device) + if self.filtered: + query = query.join( + self.model_classes_tww_od.measuring_point, + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + messgeraet = self.model_classes_interlis.messgeraet( + **self.vsa_base_common(row, "messgeraet"), + # --- measuring_device --- + seriennummer=row.serial_number, + fabrikat=row.brand, + bezeichnung=row.identifier, + art=self.get_vl(row.kind__REL), + bemerkung=row.remark, + messstelleref=self.get_tid(row.fk_measuring_point__REL), + ) + self.abwasser_session.add(messgeraet) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_measurement_series(self): + query = self.tww_session.query(self.model_classes_tww_od.measurement_series) + if self.filtered: + query = query.join( + self.model_classes_tww_od.measuring_point, + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + messreihe = self.model_classes_interlis.messreihe( + **self.vsa_base_common(row, "messreihe"), + # --- measurement_series --- + dimension=row.dimension, + bezeichnung=row.identifier, + art=self.get_vl(row.kind__REL), + bemerkung=row.remark, + messstelleref=self.get_tid(row.fk_measuring_point__REL), + abwassernetzelementref=self.get_tid(row.fk_wastewater_networkelement__REL), + ) + self.abwasser_session.add(messreihe) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_measurement_result(self): + query = self.tww_session.query(self.model_classes_tww_od.measurement_result) + if self.filtered: + query = query.join( + self.model_classes_tww_od.measurement_series, + self.model_classes_tww_od.measuring_point, + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + messresultat = self.model_classes_interlis.messresultat( + **self.vsa_base_common(row, "messresultat"), + # --- measurement_result --- + bezeichnung=row.identifier, + messart=self.get_vl(row.measurement_type__REL), + messdauer=row.measuring_duration, + bemerkung=row.remark, + zeit=row.time, + wert=row.value, + messgeraetref=self.get_tid(row.fk_measuring_device__REL), + messreiheref=self.get_tid(row.fk_measurement_series__REL), + ) + self.abwasser_session.add(messresultat) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_measuring_point(self): + query = self.tww_session.query(self.model_classes_tww_od.measuring_point) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ) + # only filter via wastewater_networkelement, union queries need further investigation + query = query.filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + messstelle = self.model_classes_interlis.messstelle( + **self.vsa_base_common(row, "messstelle"), + # --- measuring_point --- + # zweck is a valuelist + # zweck=row.purpose, + zweck=self.get_vl(row.purpose__REL), + bemerkung=row.remark, + # staukoerper is a valuelist + # staukoerper=row.damming_device, + staukoerper=self.get_vl(row.damming_device__REL), + bezeichnung=row.identifier, + # here art is not a value list + art=row.kind, + lage=row.situation_geometry, + betreiberref=self.get_tid(row.fk_operator__REL), + abwasserreinigungsanlageref=self.get_tid(row.fk_waste_water_treatment_plant__REL), + abwasserbauwerkref=self.get_tid(row.fk_wastewater_structure__REL), + ) + self.abwasser_session.add(messstelle) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_mutation(self): + query = self.tww_session.query(self.model_classes_tww_od.mutation) + # only export explicitly specified mutation objects if filtered + if self.filtered: + query = query.filter(self.model_classes_tww_od.mutation.obj_id.in_(self.subset_ids)) + for row in query: + mutation = self.model_classes_interlis.mutation( + **self.vsa_base_common(row, "mutation"), + # --- mutation --- + attribut=row.attribute, + klasse=row.classname, + mutationsdatum=row.date_mutation, + aufnahmedatum=row.date_time, + art=self.get_vl(row.kind__REL), + letzter_wert=row.last_value, + objekt=row.object, + aufnehmer=row.recorded_by, + bemerkung=row.remark, + systembenutzer=row.user_system, + ) + self.abwasser_session.add(mutation) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_reservoir(self): + query = self.tww_session.query(self.model_classes_tww_od.reservoir) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + reservoir = self.model_classes_interlis.reservoir( + **self.connection_object_common(row, "reservoir"), + # --- reservoir --- + standortname=row.location_name, + lage=row.situation_geometry, + ) + self.abwasser_session.add(reservoir) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_retention_body(self): + query = self.tww_session.query(self.model_classes_tww_od.retention_body) + if self.filtered: + query = query.join( + self.model_classes_tww_od.infiltration_installation, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + for row in query: + retentionskoerper = self.model_classes_interlis.retentionskoerper( + **self.vsa_base_common(row, "retentionskoerper"), + # --- retention_body --- + bezeichnung=row.identifier, + art=self.get_vl(row.kind__REL), + bemerkung=row.remark, + retention_volumen=row.volume, + versickerungsanlageref=self.get_tid(row.fk_infiltration_installation__REL), + ) + self.abwasser_session.add(retentionskoerper) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_profile_geometry(self): + query = self.tww_session.query(self.model_classes_tww_od.profile_geometry) + if self.filtered: + query = query.filter( + self.model_classes_tww_od.profile_geometry.obj_id.in_(self.subset_ids) + ) + for row in query: + rohrprofil_geometrie = self.model_classes_interlis.rohrprofil_geometrie( + **self.vsa_base_common(row, "rohrprofil_geometrie"), + # --- profile_geometry --- + reihenfolge=row.sequence, + x=row.x, + y=row.y, + rohrprofilref=self.get_tid(row.fk_pipe_profile__REL), + ) + self.abwasser_session.add(rohrprofil_geometrie) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_backflow_prevention(self): + query = self.tww_session.query(self.model_classes_tww_od.backflow_prevention) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.backflow_prevention + + # --- structure_part --- + # fk_dataowner, fk_provider, fk_wastewater_structure, identifier, last_modification, remark, renovation_demand + + # --- backflow_prevention --- + # gross_costs, kind, obj_id, year_of_replacement + + # --- _bwrel_ --- + # access_aid_kind__BWREL_obj_id, backflow_prevention__BWREL_obj_id, benching_kind__BWREL_obj_id, dryweather_flume_material__BWREL_obj_id, electric_equipment__BWREL_obj_id, electromechanical_equipment__BWREL_obj_id, solids_retention__BWREL_obj_id, flushing_nozzle__BWREL_obj_id, tank_cleaning__BWREL_obj_id, tank_emptying__BWREL_obj_id + + # --- _rel_ --- + # fk_dataowner__REL, fk_provider__REL, fk_wastewater_structure__REL, renovation_demand__REL, fk_throttle_shut_off_unit__REL, fk_pump__REL + + rueckstausicherung = self.model_classes_interlis.rueckstausicherung( + # FIELDS TO MAP TO ABWASSER.rueckstausicherung + # --- bauwerksteil --- + **self.structure_part_common(row, "rueckstausicherung"), + # --- backflow_prevention --- + bruttokosten=row.gross_costs, + art=self.get_vl(row.kind__REL), + ersatzjahr=row.year_of_replacement, + absperr_drosselorganref=self.get_tid(row.fk_throttle_shut_off_unit__REL), + foerderaggregatref=self.get_tid(row.fk_pump__REL), + ) + self.abwasser_session.add(rueckstausicherung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_log_card(self): + query = self.tww_session.query(self.model_classes_tww_od.log_card) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_node, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + for row in query: + stammkarte = self.model_classes_interlis.stammkarte( + **self.vsa_base_common(row, "stammkarte"), + # --- log_card --- + steuerung_fernwirkung=self.get_vl(row.control_remote_control__REL), + informationsquelle=self.get_vl(row.information_source__REL), + sachbearbeiter=row.person_in_charge, + bemerkung=row.remark, + paa_knotenref=self.get_tid(row.fk_pwwf_wastewater_node__REL), + bueroref=self.get_tid(row.fk_agency__REL), + standortgemeinderef=self.get_tid(row.fk_location_municipality__REL), + ) + self.abwasser_session.add(stammkarte) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_prank_weir(self): + query = self.tww_session.query(self.model_classes_tww_od.prank_weir) + if self.filtered: + query = query.join( + self.model_classes_tww_od.wastewater_node, + or_( + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.prank_weir.fk_wastewater_node, + self.model_classes_tww_od.wastewater_node.obj_id + == self.model_classes_tww_od.prank_weir.fk_overflow_to, + ), + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + streichwehr = self.model_classes_interlis.streichwehr( + **self.overflow_common(row, "streichwehr"), + # --- prank_weir --- + hydrueberfalllaenge=row.hydraulic_overflow_length, + kotemax=row.level_max, + kotemin=row.level_min, + ueberfallkante=self.get_vl(row.weir_edge__REL), + wehr_art=self.get_vl(row.weir_kind__REL), + ) + self.abwasser_session.add(streichwehr) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_overflow_char(self): + query = self.tww_session.query(self.model_classes_tww_od.overflow_char) + # always export all overflow_char datasets + if self.filtered: + logger.info(f"Selection query: {query.statement}") + for row in query: + ueberlaufcharakteristik = self.model_classes_interlis.ueberlaufcharakteristik( + **self.vsa_base_common(row, "ueberlaufcharakteristik"), + # --- overflow_char --- + bezeichnung=row.identifier, + kennlinie_typ=self.get_vl(row.kind_overflow_char__REL), + bemerkung=row.remark, + ) + self.abwasser_session.add(ueberlaufcharakteristik) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_maintenance(self): + query = self.tww_session.query(self.model_classes_tww_od.maintenance) + if self.filtered: + query = ( + query.join(self.model_classes_tww_od.re_maintenance_event_wastewater_structure) + .join(self.model_classes_tww_od.wastewater_structure) + .join(self.model_classes_tww_od.wastewater_networkelement) + .filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + unterhalt = self.model_classes_interlis.unterhalt( + **self.maintenance_event_common(row, "unterhalt"), + # --- maintenance --- + art=self.get_vl(row.kind__REL), + ) + self.abwasser_session.add(unterhalt) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_infiltration_zone(self): + query = self.tww_session.query(self.model_classes_tww_od.infiltration_zone) + # no connection to sewer network - selected obj_id for infiltration_zone have to be added specifically + if self.filtered: + query = query.filter( + self.model_classes_tww_od.infiltration_zone.obj_id.in_(self.subset_ids) + ) + for row in query: + versickerungsbereich = self.model_classes_interlis.versickerungsbereich( + **self.zone_common(row, "versickerungsbereich"), + # --- infiltration_zone --- + versickerungsmoeglichkeit=self.get_vl(row.infiltration_capacity__REL), + perimeter=row.perimeter_geometry, + ) + self.abwasser_session.add(versickerungsbereich) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_examination(self): + query = self.tww_session.query(self.model_classes_tww_od.examination) + if self.filtered: + query = ( + query.join(self.model_classes_tww_od.re_maintenance_event_wastewater_structure) + .join(self.model_classes_tww_od.wastewater_structure) + .join(self.model_classes_tww_od.wastewater_networkelement) + .filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + untersuchung = self.model_classes_interlis.untersuchung( + # --- baseclass --- + **self.vsa_base_common(row, "untersuchung"), + # --- erhaltungsereignis --- + astatus=self.get_vl(row.status__REL), + ausfuehrende_firmaref=self.get_tid(row.fk_operating_company__REL), + ausfuehrender=row.operator, + bemerkung=self.truncate(self.emptystr_to_null(row.remark), 80), + bezeichnung=self.null_to_emptystr(row.identifier), + datengrundlage=row.base_data, + dauer=row.duration, + detaildaten=row.data_details, + ergebnis=row.result, + grund=row.reason, + kosten=row.cost, + zeitpunkt=row.time_point, + # --- untersuchung --- + bispunktbezeichnung=row.to_point_identifier, + erfassungsart=self.get_vl(row.recording_type__REL), + fahrzeug=row.vehicle, + geraet=row.equipment, + haltungspunktref=self.get_tid(row.fk_reach_point__REL), + inspizierte_laenge=row.inspected_length, + videonummer=row.videonumber, + vonpunktbezeichnung=row.from_point_identifier, + witterung=self.get_vl(row.weather__REL), + ) + self.abwasser_session.add(untersuchung) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_damage_manhole(self): + query = self.tww_session.query(self.model_classes_tww_od.damage_manhole) + if self.filtered: + query = ( + query.join(self.model_classes_tww_od.examination) + .join(self.model_classes_tww_od.re_maintenance_event_wastewater_structure) + .join(self.model_classes_tww_od.wastewater_structure) + .join(self.model_classes_tww_od.wastewater_networkelement) + .filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + normschachtschaden = self.model_classes_interlis.normschachtschaden( + # FIELDS TO MAP TO ABWASSER.normschachtschaden + # --- baseclass --- + # --- sia405_baseclass --- + **self.vsa_base_common(row, "normschachtschaden"), + # --- schaden --- + anmerkung=row.comments, + ansichtsparameter=row.view_parameters, + untersuchungref=self.get_tid(row.fk_examination__REL), + verbindung=self.get_vl(row.connection__REL), + videozaehlerstand=row.video_counter, + # --- normschachtschaden --- + distanz=row.manhole_distance, + quantifizierung1=row.manhole_quantification1, + quantifizierung2=row.manhole_quantification2, + schachtbereich=self.get_vl(row.manhole_shaft_area__REL), + schachtschadencode=self.get_vl(row.manhole_damage_code__REL), + schadenlageanfang=row.manhole_damage_begin, + schadenlageende=row.manhole_damage_end, + ) + self.abwasser_session.add(normschachtschaden) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_damage_channel(self): + query = self.tww_session.query(self.model_classes_tww_od.damage_channel) + if self.filtered: + query = ( + query.join(self.model_classes_tww_od.examination) + .join(self.model_classes_tww_od.re_maintenance_event_wastewater_structure) + .join(self.model_classes_tww_od.wastewater_structure) + .join(self.model_classes_tww_od.wastewater_networkelement) + .filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + kanalschaden = self.model_classes_interlis.kanalschaden( + # FIELDS TO MAP TO ABWASSER.kanalschaden + # --- baseclass --- + # --- sia405_baseclass --- + **self.vsa_base_common(row, "kanalschaden"), + # --- schaden --- + anmerkung=row.comments, + ansichtsparameter=row.view_parameters, + einzelschadenklasse=self.get_vl(row.single_damage_class__REL), + streckenschaden=row.damage_reach, + untersuchungref=self.get_tid(row.fk_examination__REL), + verbindung=self.get_vl(row.connection__REL), + videozaehlerstand=row.video_counter, + # --- kanalschaden --- + distanz=row.channel_distance, + kanalschadencode=self.get_vl(row.channel_damage_code__REL), + quantifizierung1=row.channel_quantification1, + quantifizierung2=row.channel_quantification2, + schadenlageanfang=row.channel_damage_begin, + schadenlageende=row.channel_damage_end, + ) + self.abwasser_session.add(kanalschaden) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_data_media(self): + query = self.tww_session.query(self.model_classes_tww_od.data_media) + if self.filtered: + logger.info(f"Selection query: {query.statement}") + for row in query: + # AVAILABLE FIELDS IN TWW.data_media + + # --- data_media --- + # fk_dataowner, fk_provider, identifier, kind, last_modification, location, obj_id, path, remark + + # --- _rel_ --- + # fk_dataowner__REL, fk_provider__REL, kind__REL + + datentraeger = self.model_classes_interlis.datentraeger( + # FIELDS TO MAP TO ABWASSER.datentraeger + # --- vsa_baseclass --- + **self.vsa_base_common(row, "datentraeger"), + # --- datentraeger --- + art=self.get_vl(row.kind__REL), + bemerkung=self.truncate(self.emptystr_to_null(row.remark), 80), + bezeichnung=self.null_to_emptystr(row.identifier), + pfad=row.path, + standort=row.location, + ) + self.abwasser_session.add(datentraeger) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_file(self): + query = self.tww_session.query(self.model_classes_tww_od.file) + if self.filtered: + query = ( + query.outerjoin( + self.model_classes_tww_od.damage, + self.model_classes_tww_od.file.object + == self.model_classes_tww_od.damage.obj_id, + ) + .join( + self.model_classes_tww_od.examination, + or_( + self.model_classes_tww_od.file.object + == self.model_classes_tww_od.damage.obj_id, + self.model_classes_tww_od.file.object + == self.model_classes_tww_od.examination.obj_id, + ), + ) + .join(self.model_classes_tww_od.re_maintenance_event_wastewater_structure) + .join(self.model_classes_tww_od.wastewater_structure) + .join(self.model_classes_tww_od.wastewater_networkelement) + .filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + datei = self.model_classes_interlis.datei( + # FIELDS TO MAP TO ABWASSER.datei + # --- vsa_baseclass --- + **self.vsa_base_common(row, "datei"), + # --- datei --- + art=self.get_vl(row.kind__REL) or "andere", + bemerkung=self.truncate(self.emptystr_to_null(row.remark), 80), + bezeichnung=self.null_to_emptystr(row.identifier), + datentraegerref=self.get_tid(row.fk_data_media__REL), + klasse=self.get_vl_by_code( + self.model_classes_tww_vl.file_classname, row.classname + ), + objekt=self.null_to_emptystr(row.object), + relativpfad=row.path_relative, + ) + self.abwasser_session.add(datei) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_re_maintenance_event_wastewater_structure(self): + query = self.tww_session.query( + self.model_classes_tww_od.re_maintenance_event_wastewater_structure + ) + if self.filtered: + query = ( + query.join(self.model_classes_tww_od.wastewater_structure) + .join(self.model_classes_tww_od.wastewater_networkelement) + .filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + + # Before exporting the relation object, check that it use one sublclass of maintenance_event + # supported by DSS + maintenance_event_obj_id = row.fk_maintenance_event + dss_supported = False + + subclass_object = ( + self.tww_session.query(self.model_classes_tww_od.maintenance) + .filter(self.model_classes_tww_od.maintenance.obj_id == maintenance_event_obj_id) + .first() + ) + if subclass_object is not None: + dss_supported = True + + subclass_object = ( + self.tww_session.query(self.model_classes_tww_od.bio_ecol_assessment) + .filter( + self.model_classes_tww_od.bio_ecol_assessment.obj_id + == maintenance_event_obj_id + ) + .first() + ) + if not dss_supported and subclass_object is not None: + dss_supported = True + + if not dss_supported: + continue + + erhaltungsereignis_abwasserbauwerkassoc = self.model_classes_interlis.erhaltungsereignis_abwasserbauwerkassoc( + # FIELDS TO MAP TO ABWASSER.erhaltungsereignis_abwasserbauwerkassoc + # this class does not inherit vsa_base_common + # --- erhaltungsereignis_abwasserbauwerkassoc --- + abwasserbauwerkref=self.get_tid(row.fk_wastewater_structure__REL), + erhaltungsereignis_abwasserbauwerkassocref=self.get_tid( + row.fk_maintenance_event__REL + ), + ) + self.abwasser_session.add(erhaltungsereignis_abwasserbauwerkassoc) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def _export_re_building_group_disposal(self): + query = self.tww_session.query(self.model_classes_tww_od.re_building_group_disposal) + if self.filtered: + query = query.join( + self.model_classes_tww_od.disposal, + self.model_classes_tww_od.wastewater_structure, + self.model_classes_tww_od.wastewater_networkelement, + ).filter( + self.model_classes_tww_od.wastewater_networkelement.obj_id.in_(self.subset_ids) + ) + logger.info(f"Selection query: {query.statement}") + for row in query: + gebaeudegruppe_entsorgungassoc = self.model_classes_interlis.gebaeudegruppe_entsorgungassoc( + # FIELDS TO MAP TO ABWASSER.gebaeudegruppe_entsorgungassoc + # --- baseclass --- + # --- sia405_baseclass --- + # this class does not inherit vsa_base_common + # **self.vsa_base_common(row, "gebaeudegruppe_entsorgungassoc"), + # --- gebaeudegruppe_entsorgungassoc --- + entsorgungref=self.get_tid(row.fk_disposal__REL), + gebaeudegruppe_entsorgungassocref=self.get_tid(row.fk_building_group__REL), + ) + self.abwasser_session.add(gebaeudegruppe_entsorgungassoc) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def get_tid(self, relation): + """ + Makes a tid for a relation + """ + if relation is None: + return None + return self.tid_maker.tid_for_row(relation) + + def get_vl(self, relation): + """ + Gets a literal value from a value list relation + """ + if relation is None: + return None + elif relation.code == relation.vsacode: + return relation.value_de + elif self.use_vsacode: + # use vsacode instead of code + + vsacode_value_de = self.get_vl_by_code( + self.model_classes_tww_vl.value_list_base, relation.vsacode + ) + if vsacode_value_de is None: + logger.warning( + f"Code {relation.code}: Usage of vsacode returned none. Falling back to {relation.value_de}. This will probably cause validation errors", + ) + return relation.value_de + return vsacode_value_de + else: # value list extension for other type + return relation.value_de + + def get_vl_by_code(self, vl_table, vl_code): + instance = self.tww_session.query(vl_table).filter(vl_table.code == vl_code).first() + if instance is None: + logger.warning( + f'Could not find code `{vl_code}` in value list "{vl_table.__table__.schema}.{vl_table.__name__}". Setting to None instead.' + ) + return None + + return instance.value_de + + def null_to_emptystr(self, val): + """ + Converts nulls to blank strings and raises a warning + """ + if val is None: + logger.warning( + "A mandatory value was null. It will be cast to a blank string, and probably cause validation errors", + ) + val = "" + return val + + def emptystr_to_null(self, val): + """ + Converts blank strings to nulls and raises a warning + + This is needed as is seems ili2pg 4.4.6 crashes with emptystrings under certain circumstances (see https://github.com/TWW/tww2ili/issues/33) + """ + if val == "": + logger.warning( + "An empty string was converted to NULL, to workaround ili2pg issue. This should have no impact on output.", + ) + val = None + return val + + def truncate(self, val, max_length): + """ + Raises a warning if values gets truncated + """ + if val is None: + return None + if len(val) > max_length: + logger.warning(f"Value '{val}' exceeds expected length ({max_length})", stacklevel=2) + return val[0:max_length] + + def _modulo_angle(self, val): + """ + Returns an angle between 0 and 359.9 (for Orientierung in Base_d-20181005.ili) + """ + if val is None: + return None + + # add labels_orientation_offset + val = val + self.labels_orientation_offset + + val = val % 360.0 + if val > 359.9: + val = 0 + return val + + # def check_fk_in_subsetid (self, subset, relation): + def check_fk_in_subsetid(self, relation): + """ + checks, whether foreignkey is in the subset_ids - if yes it return the tid of the foreignkey, if no it will return None + """ + # first check for None, as is get_tid + if relation is None: + return None + + # only if self.filtered + if self.filtered: + # logger.info(f"check_fk_in_subsetid - Subset ID's '{self.subset}'") + logger.info(f"check_fk_in_subsetid - Subset ID's '{self.subset_ids}'") + # get the value of the fk_ attribute as str out of the relation to be able to check whether it is in the subset + fremdschluesselstr = getattr(relation, "obj_id") + logger.info(f"check_fk_in_subsetid - fremdschluesselstr '{fremdschluesselstr}'") + + # if fremdschluesselstr in self.subset: + if fremdschluesselstr in self.subset_ids: + logger.info(f"check_fk_in_subsetid - '{fremdschluesselstr}' is in subset ") + logger.info( + f"check_fk_in_subsetid - tid = '{self.tid_maker.tid_for_row(relation)}' " + ) + # return tid_maker.tid_for_row(relation) + return self.tid_maker.tid_for_row(relation) + else: + logger.info( + f"check_fk_in_subsetid - '{fremdschluesselstr}' is not in subset - replaced with None instead!" + ) + return None + else: + # Makes a tid for a relation, like in get_tid + return self.tid_maker.tid_for_row(relation) + logger.info( + f"check_fk_in_subsetid not filtered - give back tid = '{self.tid_maker.tid_for_row(relation)}'" + ) + + def get_oid_prefix(self, oid_table): + instance = self.tww_session.query(oid_table).filter(oid_table.active.is_(True)).first() + if instance is None: + logger.warning( + f'Could not find an active entry in table"{oid_table.__table__.schema}.{oid_table.__name__}". \ + Returning an empty string, which will lead to INTERLIS Errors. \ + Set the value that you want to use as prefix to \'active\' in table"{oid_table.__table__.schema}.{oid_table.__name__}" \ + to avoid this issue.' + ) + return "" + + return instance.prefix + + def base_common(self, row, type_name): + """ + Returns common attributes for base + """ + base = { + "t_ili_tid": row.obj_id, + "t_type": type_name, + "t_id": self.get_tid(row), + } + if self.current_basket: + base["t_basket"] = self.current_basket.t_id + + return base + + def sia_405_base_common(self, row, type_name): + return { + **self.base_common(row, type_name), + "letzte_aenderung": row.last_modification, + } + + def vsa_base_common(self, row, type_name): + """ + Returns common attributes for base + """ + datenherrref = self.get_tid(row.fk_dataowner__REL) + if datenherrref is None: + raise InterlisExporterToIntermediateSchemaError( + f"Invalid dataowner reference for object '{row.obj_id}' of type '{type_name}'" + ) + + datenlieferantref = self.get_tid(row.fk_provider__REL) + if datenlieferantref is None: + raise InterlisExporterToIntermediateSchemaError( + f"Invalid provider reference for object '{row.obj_id}' of type '{type_name}'" + ) + + return { + **self.sia_405_base_common(row, type_name), + "datenherrref": datenherrref, + "datenlieferantref": datenlieferantref, + } + + def wastewater_structure_common(self, row, type_name): + """ + Returns common attributes for wastewater_structure + """ + logger.warning( + "Mapping of wastewater_structure->abwasserbauwerk is not fully implemented." + ) + + eigentuemerref = self.get_tid(row.fk_owner__REL) + if eigentuemerref is None: + raise InterlisExporterToIntermediateSchemaError( + f"Invalid owner reference for object '{row.obj_id}' of type '{type_name}'" + ) + + return { + **self.vsa_base_common(row, type_name), + "akten": row.records, + "astatus": self.get_vl(row.status__REL), + "baujahr": row.year_of_construction, + "baulicherzustand": self.get_vl(row.structure_condition__REL), + "baulos": row.contract_section, + "bemerkung": self.truncate(self.emptystr_to_null(row.remark), 80), + "betreiberref": self.get_tid(row.fk_operator__REL), + "bezeichnung": self.null_to_emptystr(row.identifier), + "bruttokosten": row.gross_costs, + "detailgeometrie": ST_Force2D(row.detail_geometry3d_geometry), + "eigentuemerref": eigentuemerref, + "ersatzjahr": row.year_of_replacement, + "finanzierung": self.get_vl(row.financing__REL), + "inspektionsintervall": row.inspection_interval, + "sanierungsbedarf": self.get_vl(row.renovation_necessity__REL), + "standortname": row.location_name, + "subventionen": row.subsidies, + "wbw_basisjahr": row.rv_base_year, + "wbw_bauart": self.get_vl(row.rv_construction_type__REL), + "wiederbeschaffungswert": row.replacement_value, + "zugaenglichkeit": self.get_vl(row.accessibility__REL), + } + + def wastewater_networkelement_common(self, row, type_name): + """ + Returns common attributes for network_element + """ + return { + **self.vsa_base_common(row, type_name), + "abwasserbauwerkref": self.get_tid(row.fk_wastewater_structure__REL), + "bemerkung": self.truncate(self.emptystr_to_null(row.remark), 80), + "bezeichnung": self.null_to_emptystr(row.identifier), + } + + def structure_part_common(self, row, type_name): + """ + Returns common attributes for structure_part + """ + return { + **self.vsa_base_common(row, type_name), + "abwasserbauwerkref": self.get_tid(row.fk_wastewater_structure__REL), + "bemerkung": self.truncate(self.emptystr_to_null(row.remark), 80), + "bezeichnung": self.null_to_emptystr(row.identifier), + "instandstellung": self.get_vl(row.renovation_demand__REL), + } + + def maintenance_event_common(self, row, type_name): + """ + Returns common attributes for maintenance_event + """ + maintenance_event = { + **self.vsa_base_common(row, type_name), + "ausfuehrender": row.operator, + "bemerkung": row.remark, + "bezeichnung": row.identifier, + "datengrundlage": row.base_data, + "dauer": row.duration, + "detaildaten": row.data_details, + "ergebnis": row.result, + "grund": row.reason, + "kosten": row.cost, + "astatus": self.get_vl(row.status__REL), + "zeitpunkt": row.time_point, + "ausfuehrende_firmaref": self.get_tid(row.fk_operating_company__REL), + "massnahmeref": self.get_tid(row.fk_measure__REL), + } + + if self.model == config.MODEL_NAME_VSA_KEK: + query = self.tww_session.query( + self.model_classes_tww_od.re_maintenance_event_wastewater_structure + ).where( + self.model_classes_tww_od.re_maintenance_event_wastewater_structure.fk_maintenance_event + == row.obj_id + ) + for assoc_row in query: + abwasserbauwerkref = maintenance_event.get("abwasserbauwerkref", None) + if abwasserbauwerkref is not None: + logger.warning( + f"Maintenance event '{row.obj_id}' is associated with multiple wastewater structures, but only the first will be exported: '{abwasserbauwerkref}'. This is a limitation of the KEK model." + ) + break + + maintenance_event["abwasserbauwerkref"] = self.get_tid( + assoc_row.fk_wastewater_structure__REL + ) + + return maintenance_event + + def connection_object_common(self, row, type_name): + """ + Returns common attributes for connection_object + """ + return { + **self.vsa_base_common(row, type_name), + "bezeichnung": row.identifier, + "bemerkung": row.remark, + "fremdwasseranfall": row.sewer_infiltration_water_production, + # added check_fk_in_subsetid instead of get_tid to make sure on if filtered we do not write missing fk_wastewater_networkelement + "abwassernetzelementref": self.check_fk_in_subsetid( + row.fk_wastewater_networkelement__REL + ), + } + + def surface_runoff_parameters_common(self, row, type_name): + """ + Returns common attributes for surface_runoff_parameters + """ + return { + **self.vsa_base_common(row, type_name), + "verdunstungsverlust": row.evaporation_loss, + "bezeichnung": row.identifier, + "versickerungsverlust": row.infiltration_loss, + "bemerkung": row.remark, + "muldenverlust": row.surface_storage, + "benetzungsverlust": row.wetting_loss, + "einzugsgebietref": self.get_tid(row.fk_catchment_area__REL), + } + + def zone_common(self, row, type_name): + """ + Returns common attributes for zone + """ + return { + **self.vsa_base_common(row, type_name), + "bemerkung": row.remark, + "bezeichnung": row.identifier, + } + + def overflow_common(self, row, type_name): + """ + Returns common attributes for overflow + """ + return { + **self.vsa_base_common(row, type_name), + "bezeichnung": row.identifier, + "bemerkung": row.remark, + "antrieb": self.get_vl(row.actuation__REL), + "verstellbarkeit": self.get_vl(row.adjustability__REL), + "fabrikat": row.brand, + "steuerung": self.get_vl(row.control__REL), + "einleitstelle": row.discharge_point, + "funktion": self.get_vl(row.function__REL), + "bruttokosten": row.gross_costs, + "qan_dim": row.qon_dim, + "signaluebermittlung": self.get_vl(row.signal_transmission__REL), + "subventionen": row.subsidies, + "abwasserknotenref": self.get_tid(row.fk_wastewater_node__REL), + "ueberlaufnachref": self.get_tid(row.fk_overflow_to__REL), + "ueberlaufcharakteristikref": self.get_tid(row.fk_overflow_char__REL), + "steuerungszentraleref": self.get_tid(row.fk_control_center__REL), + } + + def _textpos_common(self, row, t_type, geojson_crs_def, shortcut_en, oid_prefix): + """ + Returns common attributes for textpos + """ + t_id = self.tid_maker.next_tid() + if t_id > 999999: + logger.warning( + f"Exporting more than 999999 labels will generate invalid OIDs. Currently exporting {t_id} label of type '{t_type}'." + ) + + return { + "t_id": t_id, + "t_type": t_type, + "t_ili_tid": f"{oid_prefix}{shortcut_en}{t_id:06d}", + # --- TextPos --- + "textpos": ST_GeomFromGeoJSON( + json.dumps( + { + "type": "Point", + "coordinates": row["geometry"]["coordinates"], + "crs": geojson_crs_def, + } + ) + ), + "textori": self._modulo_angle(row["properties"]["LabelRotation"]), + "texthali": "Left", # can be Left/Center/Right + "textvali": "Bottom", # can be Top,Cap,Half,Base,Bottom + # --- SIA405_TextPos --- + "plantyp": row["properties"]["scale"], + "textinhalt": row["properties"]["LabelText"], + "bemerkung": None, + } + + def _export_label_positions(self): + logger.info(f"Exporting label positions from {self.labels_file}") + + # get oid prefix + self.oid_prefix = self.get_oid_prefix(self.model_classes_tww_sys.oid_prefixes) + # Get t_id by obj_name to create the reference on the labels below + tid_for_obj_id = { + "vw_tww_reach": {}, + "vw_tww_wastewater_structure": {}, + "catchment_area": {}, + } + for row in self.abwasser_session.query(self.model_classes_interlis.haltung): + tid_for_obj_id["vw_tww_reach"][row.t_ili_tid] = row.t_id + for row in self.abwasser_session.query(self.model_classes_interlis.abwasserbauwerk): + tid_for_obj_id["vw_tww_wastewater_structure"][row.t_ili_tid] = row.t_id + + if self.model in [config.MODEL_NAME_DSS, config.MODEL_NAME_AG96]: + for row in self.abwasser_session.query(self.model_classes_interlis.einzugsgebiet): + tid_for_obj_id["catchment_area"][row.t_ili_tid] = row.t_id + + with open(self.labels_file) as labels_file_handle: + labels = json.load(labels_file_handle) + + geojson_crs_def = labels["crs"] + + for label in labels["features"]: + layer_name = label["properties"]["Layer"] + obj_id = label["properties"]["tww_obj_id"] + + print(f"label[properties]: {label['properties']}") + + if self.subset_ids and obj_id not in self.subset_ids: + logger.warning( + f"Label for {layer_name} `{obj_id}` exists, but that object is not part of the export" + ) + continue + + if not label["properties"]["LabelText"]: + logger.warning( + f"Label of object '{obj_id}' from layer '{layer_name}' is empty and will not be exported" + ) + continue + + t_id = tid_for_obj_id.get(layer_name, {}).get(obj_id, None) + if not t_id: + logger.warning( + f"Label for '{layer_name}' '{obj_id}' exists, but that object is not part of the export" + ) + continue + + if layer_name == "vw_tww_reach": + ili_label = self.model_classes_interlis.haltung_text( + **self._textpos_common( + label, "haltung_text", geojson_crs_def, "RX", self.oid_prefix + ), + haltungref=t_id, + ) + + elif layer_name == "vw_tww_wastewater_structure": + ili_label = self.model_classes_interlis.abwasserbauwerk_text( + **self._textpos_common( + label, "abwasserbauwerk_text", geojson_crs_def, "WX", self.oid_prefix + ), + abwasserbauwerkref=t_id, + ) + + elif layer_name == "catchment_area": + ili_label = self.model_classes_interlis.einzugsgebiet_text( + **self._textpos_common( + label, "einzugsgebiet_text", geojson_crs_def, "CX", self.oid_prefix + ), + einzugsgebietref=t_id, + ) + + else: + logger.warning( + f"Unknown layer `{layer_name}` for label with id '{obj_id}'. Label will be ignored", + ) + continue + + self.abwasser_session.add(ili_label) + print(".", end="") + logger.info("done") + self.abwasser_session.flush() + + def close_sessions(self): + self.tww_session.close() + self.abwasser_session.close() + + def _check_for_stop(self): + if self.callback_progress_done: + self.callback_progress_done() diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/interlis_importer_to_intermediate_schema.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/interlis_importer_to_intermediate_schema.py new file mode 100644 index 0000000..273ad94 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_model_mapping/interlis_importer_to_intermediate_schema.py @@ -0,0 +1,2296 @@ +from geoalchemy2.functions import ST_Force3D +from sqlalchemy.orm import Session +from sqlalchemy.orm.attributes import flag_dirty +from sqlalchemy.sql import text + +from ...utils.plugin_utils import logger +from .. import config, utils + + +class InterlisImporterToIntermediateSchema: + def __init__( + self, + model, + model_classes_interlis, + model_classes_tww_od, + model_classes_tww_vl, + callback_progress_done=None, + ): + self.model = model + self.callback_progress_done = callback_progress_done + + self.model_classes_interlis = model_classes_interlis + self.model_classes_tww_od = model_classes_tww_od + self.model_classes_tww_vl = model_classes_tww_vl + + self.session_interlis = None + self.session_tww = None + + def tww_import(self, skip_closing_tww_session=False): + try: + self._tww_import(skip_closing_tww_session) + except Exception as exception: + try: + self.session_tww.rollback() + self.session_tww.close() + self.session_interlis.close() + except Exception as cleanup_exception: + logger.warning(f"Could not close sessions cleanly: {cleanup_exception}") + raise exception + + def _tww_import(self, skip_closing_tww_session): + """ + Imports data from the ili2pg model into the TWW model. + + Args: + precommit_callback: optional callable that gets invoked with the sqlalchemy's session, + allowing for a GUI to filter objects before committing. It MUST either + commit or rollback and close the session. + """ + + # We use two different sessions for reading and writing so it's easier to + # review imports and to keep the door open to getting data from another + # connection / database type. + self.session_interlis = Session( + utils.tww_sqlalchemy.create_engine(), autocommit=False, autoflush=False + ) + self.session_tww = Session( + utils.tww_sqlalchemy.create_engine(), autocommit=False, autoflush=False + ) + + # Allow to insert rows with cyclic dependencies at once + self.session_tww.execute(text("SET CONSTRAINTS ALL DEFERRED;")) + + self._import_sia405_abwasser() + + if self.model == config.MODEL_NAME_DSS: + self._import_dss() + + if self.model == config.MODEL_NAME_VSA_KEK: + self._import_vsa_kek() + + self.close_sessions(skip_closing_tww_session=skip_closing_tww_session) + + def _import_sia405_abwasser(self): + logger.info("\nImporting ABWASSER.organisation -> TWW.organisation") + self._import_organisation() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.kanal -> TWW.channel") + self._import_kanal() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.normschacht -> TWW.manhole") + self._import_normschacht() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.einleitstelle -> TWW.discharge_point") + self._import_einleitstelle() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.spezialbauwerk -> TWW.special_structure") + self._import_spezialbauwerk() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.versickerungsanlage -> TWW.infiltration_installation") + self._import_versickerungsanlage() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.rohrprofil -> TWW.pipe_profile") + self._import_rohrprofil() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.abwasserknoten -> TWW.wastewater_node") + self._import_abwasserknoten() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.haltung -> TWW.reach") + self._import_haltung() + self._check_for_stop() + + logger.info( + "\nImporting ABWASSER.haltung_alternativverlauf -> TWW.reach_progression_alternative" + ) + self._import_haltung_alternativverlauf() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.haltungspunkt -> TWW.reach_point") + self._import_haltungspunkt() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.trockenwetterfallrohr -> TWW.dryweather_downspout") + self._import_trockenwetterfallrohr() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.einstiegshilfe -> TWW.access_aid") + self._import_einstiegshilfe() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.trockenwetterrinne -> TWW.dryweather_flume") + self._import_trockenwetterrinne() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.deckel -> TWW.cover") + self._import_deckel() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.bankett -> TWW.benching") + self._import_bankett() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Spuelstutzen -> TWW.flushing_nozzle") + self._import_spuelstutzen() + self._check_for_stop() + + logger.info( + "\nImporting ABWASSER.abwasserbauwerk_symbol -> TWW.wastewater_structure_symbol" + ) + self._import_abwasserbauwerk_symbol() + self._check_for_stop() + + def _import_dss(self): + logger.info( + "\nImporting ABWASSER.abwasserreinigungsanlage -> TWW.waste_water_treatment_plant" + ) + self._import_abwasserreinigungsanlage() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.araenergienutzung -> TWW.wwtp_energy_use") + self._import_araenergienutzung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.abwasserbehandlung -> TWW.waste_water_treatment") + self._import_abwasserbehandlung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.schlammbehandlung -> TWW.sludge_treatment") + self._import_schlammbehandlung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.arabauwerk -> TWW.wwtp_structure") + self._import_arabauwerk() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.steuerungszentrale -> TWW.control_center") + self._import_steuerungszentrale() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Abflusslose_Toilette -> TWW.drainless_toilet") + self._import_abflusslose_toilette() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Absperr_Drosselorgan -> TWW.throttle_shut_off_unit") + self._import_absperr_drosselorgan() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Beckenentleerung -> TWW.tank_emptying") + self._import_beckenentleerung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Beckenreinigung -> TWW.tank_cleaning") + self._import_beckenreinigung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Biol_oekol_Gesamtbeurteilung -> TWW.bio_ecol_assessment") + self._import_biol_oekol_gesamtbeurteilung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Brunnen -> TWW.fountain") + self._import_brunnen() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.EZG_PARAMETER_ALLG -> TWW.param_ca_general") + self._import_ezg_parameter_allg() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.EZG_PARAMETER_MOUSE1 -> TWW.param_ca_mouse1") + self._import_ezg_parameter_mouse1() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Einzelflaeche -> TWW.individual_surface") + self._import_einzelflaeche() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Einzugsgebiet -> TWW.catchment_area") + self._import_einzugsgebiet() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.ElektrischeEinrichtung -> TWW.electric_equipment") + self._import_elektrischeeinrichtung() + self._check_for_stop() + + logger.info( + "\nImporting ABWASSER.ElektromechanischeAusruestung -> TWW.electromechanical_equipment" + ) + self._import_elektromechanischeausruestung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Entsorgung -> TWW.disposal") + self._import_entsorgung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Entwaesserungssystem -> TWW.drainage_system") + self._import_entwaesserungssystem() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Feststoffrueckhalt -> TWW.solids_retention") + self._import_feststoffrueckhalt() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.FoerderAggregat -> TWW.") + self._import_foerderaggregat() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Gebaeude -> TWW.building") + self._import_gebaeude() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Gebaeudegruppe -> TWW.building_group") + self._import_gebaeudegruppe() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Gebaeudegruppe_BAUGWR -> TWW.building_group_baugwr") + self._import_gebaeudegruppe_baugwr() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Gesamteinzugsgebiet -> TWW.catchment_area_totals") + self._import_gesamteinzugsgebiet() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.HQ_Relation -> TWW.hq_relation") + self._import_hq_relation() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Hydr_GeomRelation -> TWW.hydr_geom_relation") + self._import_hydr_geomrelation() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Hydr_Geometrie -> TWW.hydr_geometry") + self._import_hydr_geometrie() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Hydr_Kennwerte -> TWW.hydraulic_char_data") + self._import_hydr_kennwerte() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.KLARA -> TWW.small_treatment_plant") + self._import_klara() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Landwirtschaftsbetrieb -> TWW.farm") + self._import_landwirtschaftsbetrieb() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Leapingwehr -> TWW.leapingweir") + self._import_leapingwehr() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Massnahme -> TWW.measure") + self._import_massnahme() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.MechanischeVorreinigung -> TWW.mechanical_pretreatment") + self._import_mechanischevorreinigung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Messgeraet -> TWW.measuring_device") + self._import_messgeraet() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Messreihe -> TWW.measurement_series") + self._import_messreihe() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Messresultat -> TWW.measurement_result") + self._import_messresultat() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Messstelle -> TWW.measuring_point") + self._import_messstelle() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Mutation -> TWW.mutation") + self._import_mutation() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Reservoir -> TWW.reservoir") + self._import_reservoir() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Retentionskoerper -> TWW.retention_body") + self._import_retentionskoerper() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Rohrprofil_Geometrie -> TWW.profile_geometry") + self._import_rohrprofil_geometrie() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Rueckstausicherung -> TWW.backflow_prevention") + self._import_rueckstausicherung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Stammkarte -> TWW.log_card") + self._import_stammkarte() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Streichwehr -> TWW.prank_weir") + self._import_streichwehr() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Ueberlaufcharakteristik -> TWW.overflow_char") + self._import_ueberlaufcharakteristik() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Unterhalt -> TWW.maintenance") + self._import_unterhalt() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.Versickerungsbereich -> TWW.infiltration_zone") + self._import_versickerungsbereich() + self._check_for_stop() + + logger.info( + "\nImporting ABWASSER.erhaltungsereignis_abwasserbauwerkassoc -> TWW.re_maintenance_event_wastewater_structure" + ) + self._import_erhaltungsereignis_abwasserbauwerkassoc() + self._check_for_stop() + + logger.info( + "\nImporting ABWASSER.gebaeudegruppe_entsorgungassoc -> TWW.re_building_group_disposal" + ) + self._import_gebaeudegruppe_entsorgungassoc() + self._check_for_stop() + + def _import_vsa_kek(self): + logger.info("\nImporting ABWASSER.untersuchung -> TWW.examination") + self._import_untersuchung() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.normschachtschaden -> TWW.damage_manhole") + self._import_normschachtschaden() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.kanalschaden -> TWW.damage_channel") + self._import_kanalschaden() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.datentraeger -> TWW.data_media") + self._import_datentraeger() + self._check_for_stop() + + logger.info("\nImporting ABWASSER.datei -> TWW.file") + self._import_datei() + self._check_for_stop() + + def close_sessions(self, skip_closing_tww_session=False): + # Calling the precommit callback if provided, allowing to filter before final import + if not skip_closing_tww_session: + self.session_tww.commit() + self.session_tww.close() + self.session_interlis.close() + + def get_vl_instance(self, vl_table, value_de): + """ + Gets a value list instance from the value_de name. Returns None and a warning if not found. + """ + # TODO : memoize (and get the whole table at once) to improve N+1 performance issue + # TODO : return "other" (or other applicable value) rather than None, or even throwing an exception, would probably be better + instance = self.session_tww.query(vl_table).filter(vl_table.value_de == value_de).first() + if instance is None: + logger.warning( + f'Could not find value `{value_de}` in value list "{vl_table.__table__.schema}.{vl_table.__name__}". Setting to None instead.' + ) + return None + return instance + + def get_vl_code(self, vl_table, value_de): + if value_de is None: + return None + + instance = self.get_vl_instance(vl_table, value_de) + if instance is None: + return None + return instance.code + + def get_pk(self, relation): + """ + Returns the primary key for a relation + """ + if relation is None: + return None + return relation.t_ili_tid + + def create_or_update(self, cls, **kwargs): + """ + Updates an existing instance (if obj_id is found) or creates an instance of the provided class + with given kwargs, and returns it. + """ + instance = None + + # We try to get the instance from the session/database + obj_id = kwargs.get("obj_id", None) + if obj_id: + instance = self.session_tww.query(cls).get(kwargs.get("obj_id", None)) + + if instance: + # We found it -> update + instance.__dict__.update(kwargs) + flag_dirty(instance) # we flag it as dirty so it stays in the session + else: + # We didn't find it -> create + instance = cls(**kwargs) + + return instance + + def base_common(self, row): + """ + Returns common attributes for base + """ + return { + "obj_id": row.t_ili_tid, + "fk_dataowner": row.datenherrref, + "fk_provider": row.datenlieferantref, + "last_modification": row.letzte_aenderung, + } + + def wastewater_structure_common(self, row): + """ + Returns common attributes for wastewater_structure + """ + return { + "accessibility": self.get_vl_code( + self.model_classes_tww_od.wastewater_structure_accessibility, row.zugaenglichkeit + ), + "contract_section": row.baulos, + "detail_geometry3d_geometry": ST_Force3D(row.detailgeometrie), + # TODO : NOT MAPPED VSA-DSS 3D + # "elevation_determination": self.get_vl_code( + # self.model_classes_tww_od.wastewater_structure_elevation_determination, row.hoehenbestimmung + # ), + "financing": self.get_vl_code( + self.model_classes_tww_od.wastewater_structure_financing, row.finanzierung + ), + "fk_operator": row.betreiberref, + "fk_owner": row.eigentuemerref, + "gross_costs": row.bruttokosten, + "identifier": row.bezeichnung, + "inspection_interval": row.inspektionsintervall, + "location_name": row.standortname, + "records": row.akten, + "remark": row.bemerkung, + "renovation_necessity": self.get_vl_code( + self.model_classes_tww_od.wastewater_structure_renovation_necessity, + row.sanierungsbedarf, + ), + "replacement_value": row.wiederbeschaffungswert, + "rv_base_year": row.wbw_basisjahr, + "rv_construction_type": self.get_vl_code( + self.model_classes_tww_od.wastewater_structure_rv_construction_type, row.wbw_bauart + ), + "status": self.get_vl_code( + self.model_classes_tww_vl.wastewater_structure_status, row.astatus + ), + "structure_condition": self.get_vl_code( + self.model_classes_tww_od.wastewater_structure_structure_condition, + row.baulicherzustand, + ), + "subsidies": row.subventionen, + "year_of_construction": row.baujahr, + "year_of_replacement": row.ersatzjahr, + } + + def wastewater_networkelement_common(self, row): + """ + Returns common attributes for network_element + """ + return { + "fk_wastewater_structure": self.get_pk(row.abwasserbauwerkref__REL), + "identifier": row.bezeichnung, + "remark": row.bemerkung, + } + + def structure_part_common(self, row): + """ + Returns common attributes for structure_part + """ + return { + "fk_wastewater_structure": self.get_pk(row.abwasserbauwerkref__REL), + "identifier": row.bezeichnung, + "remark": row.bemerkung, + "renovation_demand": self.get_vl_code( + self.model_classes_tww_od.structure_part_renovation_demand, row.instandstellung + ), + } + + def maintenance_event_common(self, row): + """ + Returns common attributes for connection_object + """ + return { + "base_data": row.datengrundlage, + "cost": row.kosten, + "data_details": row.detaildaten, + "duration": row.dauer, + "operator": row.ausfuehrender, + "reason": row.grund, + "result": row.ergebnis, + "status": self.get_vl_code( + self.model_classes_tww_vl.maintenance_event_status, row.astatus + ), + "time_point": row.zeitpunkt, + "identifier": row.bezeichnung, + "remark": row.bemerkung, + "fk_operating_company": row.ausfuehrende_firmaref, + "fk_measure": self.get_pk(row.massnahmeref__REL), + } + + def connection_object_common(self, row): + """ + Returns common attributes for connection_object + """ + return { + "identifier": row.bezeichnung, + "remark": row.bemerkung, + "sewer_infiltration_water_production": row.fremdwasseranfall, + "fk_wastewater_networkelement": self.get_pk(row.abwassernetzelementref__REL), + } + + def surface_runoff_parameters_common(self, row): + """ + Returns common attributes for surface_runoff_parameters + """ + return { + "evaporation_loss": row.verdunstungsverlust, + "identifier": row.bezeichnung, + "infiltration_loss": row.versickerungsverlust, + "remark": row.bemerkung, + "surface_storage": row.muldenverlust, + "wetting_loss": row.benetzungsverlust, + "fk_catchment_area": self.get_pk(row.einzugsgebietref__REL), + } + + def zone_common(self, row): + """ + Returns common attributes for zone + """ + return { + "identifier": row.bezeichnung, + "remark": row.bemerkung, + } + + def overflow_common(self, row): + """ + Returns common attributes for overflow + """ + return { + "identifier": row.bezeichnung, + "remark": row.bemerkung, + "actuation": self.get_vl_code( + self.model_classes_tww_vl.overflow_actuation, row.antrieb + ), + "adjustability": self.get_vl_code( + self.model_classes_tww_vl.overflow_adjustability, row.verstellbarkeit + ), + "brand": row.fabrikat, + "control": self.get_vl_code(self.model_classes_tww_vl.overflow_control, row.steuerung), + "discharge_point": row.einleitstelle, + "function": self.get_vl_code( + self.model_classes_tww_vl.overflow_function, row.funktion + ), + "gross_costs": row.bruttokosten, + "qon_dim": row.qan_dim, + "signal_transmission": self.get_vl_code( + self.model_classes_tww_vl.overflow_signal_transmission, row.signaluebermittlung + ), + "subsidies": row.subventionen, + "fk_wastewater_node": self.get_pk(row.abwasserknotenref__REL), + "fk_overflow_to": self.get_pk(row.ueberlaufnachref__REL), + "fk_overflow_char": self.get_pk(row.ueberlaufcharakteristikref__REL), + "fk_control_center": self.get_pk(row.steuerungszentraleref__REL), + } + + def _import_organisation(self): + for row in self.session_interlis.query(self.model_classes_interlis.organisation): + organisation = self.create_or_update( + self.model_classes_tww_od.organisation, + obj_id=row.t_ili_tid, + # manually add for organisation (instead of adding **self.base_common(row) as this would also add fk_dataowner and fk_provider, that are not in INTERLIS for class organisation (change to VSA-DSS 2015, as organisation is now a separate external class maintained by the VSA (or its successor organisation for this) + last_modification=row.letzte_aenderung, + # --- organisation --- + identifier=row.bezeichnung, + identifier_short=row.kurzbezeichnung, + municipality_number=row.gemeindenummer, + organisation_type=self.get_vl_code( + self.model_classes_tww_vl.organisation_organisation_type, row.organisationstyp + ), + remark=row.bemerkung, + status=self.get_vl_code( + self.model_classes_tww_vl.organisation_status, row.astatus + ), + uid=row.auid, + ) + + self.session_tww.add(organisation) + print(".", end="") + + def _import_kanal(self): + for row in self.session_interlis.query(self.model_classes_interlis.kanal): + channel = self.create_or_update( + self.model_classes_tww_od.channel, + **self.base_common(row), + # --- wastewater_structure --- + **self.wastewater_structure_common(row), + # --- channel --- + bedding_encasement=self.get_vl_code( + self.model_classes_tww_od.channel_bedding_encasement, row.bettung_umhuellung + ), + connection_type=self.get_vl_code( + self.model_classes_tww_od.channel_connection_type, row.verbindungsart + ), + function_hierarchic=self.get_vl_code( + self.model_classes_tww_od.channel_function_hierarchic, row.funktionhierarchisch + ), + function_hydraulic=self.get_vl_code( + self.model_classes_tww_od.channel_function_hydraulic, row.funktionhydraulisch + ), + jetting_interval=row.spuelintervall, + pipe_length=row.rohrlaenge, + usage_current=self.get_vl_code( + self.model_classes_tww_od.channel_usage_current, row.nutzungsart_ist + ), + usage_planned=self.get_vl_code( + self.model_classes_tww_od.channel_usage_planned, row.nutzungsart_geplant + ), + ) + self.session_tww.add(channel) + print(".", end="") + + def _import_normschacht(self): + for row in self.session_interlis.query(self.model_classes_interlis.normschacht): + manhole = self.create_or_update( + self.model_classes_tww_od.manhole, + **self.base_common(row), + # --- wastewater_structure --- + **self.wastewater_structure_common(row), + # --- manhole --- + # _orientation=row.REPLACE_ME, + dimension1=row.dimension1, + dimension2=row.dimension2, + function=self.get_vl_code( + self.model_classes_tww_vl.manhole_function, row.funktion + ), + material=self.get_vl_code( + self.model_classes_tww_vl.manhole_material, row.material + ), + surface_inflow=self.get_vl_code( + self.model_classes_tww_od.manhole_surface_inflow, row.oberflaechenzulauf + ), + ) + self.session_tww.add(manhole) + print(".", end="") + + def _import_einleitstelle(self): + for row in self.session_interlis.query(self.model_classes_interlis.einleitstelle): + discharge_point = self.create_or_update( + self.model_classes_tww_od.discharge_point, + **self.base_common(row), + # --- wastewater_structure --- + **self.wastewater_structure_common(row), + # --- discharge_point --- + # fk_sector_water_body=row.REPLACE_ME, # TODO : NOT MAPPED + highwater_level=row.hochwasserkote, + relevance=self.get_vl_code( + self.model_classes_tww_od.discharge_point_relevance, row.relevanz + ), + terrain_level=row.terrainkote, + # TODO : NOT MAPPED VSA-DSS 3D + # upper_elevation=row.deckenkote, + waterlevel_hydraulic=row.wasserspiegel_hydraulik, + ) + self.session_tww.add(discharge_point) + print(".", end="") + + def _import_spezialbauwerk(self): + for row in self.session_interlis.query(self.model_classes_interlis.spezialbauwerk): + special_structure = self.create_or_update( + self.model_classes_tww_od.special_structure, + **self.base_common(row), + # --- wastewater_structure --- + **self.wastewater_structure_common(row), + # --- special_structure --- + bypass=self.get_vl_code( + self.model_classes_tww_vl.special_structure_bypass, row.bypass + ), + emergency_overflow=self.get_vl_code( + self.model_classes_tww_vl.special_structure_emergency_overflow, + row.notueberlauf, + ), + function=self.get_vl_code( + self.model_classes_tww_od.special_structure_function, row.funktion + ), + stormwater_tank_arrangement=self.get_vl_code( + self.model_classes_tww_od.special_structure_stormwater_tank_arrangement, + row.regenbecken_anordnung, + ), + # TODO : NOT MAPPED VSA-DSS 3D + # upper_elevation=row.deckenkote, + ) + self.session_tww.add(special_structure) + print(".", end="") + + def _import_versickerungsanlage(self): + for row in self.session_interlis.query(self.model_classes_interlis.versickerungsanlage): + infiltration_installation = self.create_or_update( + self.model_classes_tww_od.infiltration_installation, + **self.base_common(row), + # --- wastewater_structure --- + **self.wastewater_structure_common(row), + # --- infiltration_installation --- + absorption_capacity=row.schluckvermoegen, + defects=self.get_vl_code( + self.model_classes_tww_od.infiltration_installation_defects, row.maengel + ), + dimension1=row.dimension1, + dimension2=row.dimension2, + distance_to_aquifer=row.gwdistanz, + effective_area=row.wirksameflaeche, + emergency_overflow=self.get_vl_code( + self.model_classes_tww_od.infiltration_installation_emergency_overflow, + row.notueberlauf, + ), + # fk_dss15_aquifer=row.REPLACE_ME, # TODO : NOT MAPPED + kind=self.get_vl_code( + self.model_classes_tww_vl.infiltration_installation_kind, row.art + ), + labeling=self.get_vl_code( + self.model_classes_tww_od.infiltration_installation_labeling, row.beschriftung + ), + seepage_utilization=self.get_vl_code( + self.model_classes_tww_od.infiltration_installation_seepage_utilization, + row.versickerungswasser, + ), + # TODO : NOT MAPPED VSA-DSS 3D + # upper_elevation=row.deckenkote, + vehicle_access=self.get_vl_code( + self.model_classes_tww_od.infiltration_installation_vehicle_access, + row.saugwagen, + ), + watertightness=self.get_vl_code( + self.model_classes_tww_od.infiltration_installation_watertightness, + row.wasserdichtheit, + ), + ) + self.session_tww.add(infiltration_installation) + print(".", end="") + + def _import_abwasserreinigungsanlage(self): + for row in self.session_interlis.query( + self.model_classes_interlis.abwasserreinigungsanlage + ): + waste_water_treatment_plant = self.create_or_update( + self.model_classes_tww_od.waste_water_treatment_plant, + **self.base_common(row), + # --- waste_water_treatment_plant --- + area_geometry=row.perimeter, + bod5=row.bsb5, + cod=row.csb, + elimination_cod=row.eliminationcsb, + elimination_n=row.eliminationn, + elimination_nh4=row.eliminationnh4, + elimination_p=row.eliminationp, + identifier=row.bezeichnung, + kind=row.art, + nh4=row.nh4, + operator_type=self.get_vl_code( + self.model_classes_tww_vl.waste_water_treatment_plant_operator_type, + row.betreibertyp, + ), + population_connected=row.einwohner_angeschlossen, + population_total=row.einwohner_total, + remark=row.bemerkung, + situation_geometry=row.lage, + start_year=row.inbetriebnahme, + wwtp_number=row.ara_nr, + ) + self.session_tww.add(waste_water_treatment_plant) + print(".", end="") + + def _import_araenergienutzung(self): + for row in self.session_interlis.query(self.model_classes_interlis.araenergienutzung): + wwtp_energy_use = self.create_or_update( + self.model_classes_tww_od.wwtp_energy_use, + **self.base_common(row), + # --- wwtp_energy_use --- + gas_motor=row.gasmotor, + heat_pump=row.waermepumpe, + identifier=row.bezeichnung, + remark=row.bemerkung, + turbining=row.turbinierung, + fk_waste_water_treatment_plant=self.get_pk(row.abwasserreinigungsanlageref__REL), + ) + self.session_tww.add(wwtp_energy_use) + print(".", end="") + + def _import_abwasserbehandlung(self): + for row in self.session_interlis.query(self.model_classes_interlis.abwasserbehandlung): + waste_water_treatment = self.create_or_update( + self.model_classes_tww_od.waste_water_treatment, + **self.base_common(row), + # --- waste_water_treatment --- + identifier=row.bezeichnung, + kind=self.get_vl_code( + self.model_classes_tww_vl.waste_water_treatment_kind, row.art + ), + remark=row.bemerkung, + fk_waste_water_treatment_plant=self.get_pk(row.abwasserreinigungsanlageref__REL), + ) + self.session_tww.add(waste_water_treatment) + print(".", end="") + + def _import_schlammbehandlung(self): + for row in self.session_interlis.query(self.model_classes_interlis.schlammbehandlung): + sludge_treatment = self.create_or_update( + self.model_classes_tww_od.sludge_treatment, + **self.base_common(row), + # --- sludge_treatment --- + composting=row.kompostierung, + dehydration=row.entwaesserung, + digested_sludge_combustion=row.faulschlammverbrennung, + drying=row.trocknung, + fresh_sludge_combustion=row.frischschlammverbrennung, + hygenisation=row.hygienisierung, + identifier=row.bezeichnung, + predensification_of_excess_sludge=row.ueberschusschlammvoreindickung, + predensification_of_mixed_sludge=row.mischschlammvoreindickung, + predensification_of_primary_sludge=row.primaerschlammvoreindickung, + remark=row.bemerkung, + stabilisation=self.get_vl_code( + self.model_classes_tww_vl.sludge_treatment_stabilisation, row.stabilisierung + ), + stacking_of_dehydrated_sludge=row.entwaessertklaerschlammstapelung, + stacking_of_liquid_sludge=row.fluessigklaerschlammstapelung, + fk_waste_water_treatment_plant=self.get_pk(row.abwasserreinigungsanlageref__REL), + ) + self.session_tww.add(sludge_treatment) + print(".", end="") + + def _import_arabauwerk(self): + for row in self.session_interlis.query(self.model_classes_interlis.arabauwerk): + wwtp_structure = self.create_or_update( + self.model_classes_tww_od.wwtp_structure, + **self.base_common(row), + # --- wastewater_structure --- + **self.wastewater_structure_common(row), + # --- wwtp_structure --- + kind=self.get_vl_code(self.model_classes_tww_vl.wwtp_structure_kind, row.art), + fk_waste_water_treatment_plant=self.get_pk(row.abwasserreinigungsanlageref__REL), + ) + self.session_tww.add(wwtp_structure) + print(".", end="") + + def _import_steuerungszentrale(self): + for row in self.session_interlis.query(self.model_classes_interlis.steuerungszentrale): + control_center = self.create_or_update( + self.model_classes_tww_od.control_center, + **self.base_common(row), + # --- control_center --- + identifier=row.bezeichnung, + situation_geometry=row.lage, + ) + self.session_tww.add(control_center) + print(".", end="") + + def _import_abflusslose_toilette(self): + for row in self.session_interlis.query(self.model_classes_interlis.abflusslose_toilette): + drainless_toilet = self.create_or_update( + self.model_classes_tww_od.drainless_toilet, + **self.base_common(row), + **self.wastewater_structure_common(row), + # --- drainless_toilet --- + kind=self.get_vl_code(self.model_classes_tww_vl.drainless_toilet_kind, row.art), + ) + self.session_tww.add(drainless_toilet) + print(".", end="") + + def _import_absperr_drosselorgan(self): + for row in self.session_interlis.query(self.model_classes_interlis.absperr_drosselorgan): + throttle_shut_off_unit = self.create_or_update( + self.model_classes_tww_od.throttle_shut_off_unit, + **self.base_common(row), + # --- throttle_shut_off_unit --- + actuation=self.get_vl_code( + self.model_classes_tww_vl.throttle_shut_off_unit_actuation, row.antrieb + ), + adjustability=self.get_vl_code( + self.model_classes_tww_vl.throttle_shut_off_unit_adjustability, + row.verstellbarkeit, + ), + control=self.get_vl_code( + self.model_classes_tww_vl.throttle_shut_off_unit_control, row.steuerung + ), + cross_section=row.querschnitt, + effective_cross_section=row.wirksamer_qs, + gross_costs=row.bruttokosten, + identifier=row.bezeichnung, + kind=self.get_vl_code( + self.model_classes_tww_vl.throttle_shut_off_unit_kind, row.art + ), + manufacturer=row.fabrikat, + remark=row.bemerkung, + signal_transmission=self.get_vl_code( + self.model_classes_tww_vl.throttle_shut_off_unit_signal_transmission, + row.signaluebermittlung, + ), + subsidies=row.subventionen, + throttle_unit_opening_current=row.drosselorgan_oeffnung_ist, + throttle_unit_opening_current_optimized=row.drosselorgan_oeffnung_ist_optimiert, + fk_wastewater_node=self.get_pk(row.abwasserknotenref__REL), + fk_control_center=self.get_pk(row.steuerungszentraleref__REL), + fk_overflow=self.get_pk(row.ueberlaufref__REL), + ) + self.session_tww.add(throttle_shut_off_unit) + print(".", end="") + + def _import_beckenentleerung(self): + for row in self.session_interlis.query(self.model_classes_interlis.beckenentleerung): + tank_emptying = self.create_or_update( + self.model_classes_tww_od.tank_emptying, + **self.base_common(row), + **self.structure_part_common(row), + # --- tank_emptying --- + flow=row.leistung, + gross_costs=row.bruttokosten, + kind=self.get_vl_code(self.model_classes_tww_vl.tank_emptying_kind, row.art), + year_of_replacement=row.ersatzjahr, + fk_throttle_shut_off_unit=self.get_pk(row.absperr_drosselorganref__REL), + fk_overflow=self.get_pk(row.ueberlaufref__REL), + ) + self.session_tww.add(tank_emptying) + print(".", end="") + + def _import_beckenreinigung(self): + for row in self.session_interlis.query(self.model_classes_interlis.beckenreinigung): + tank_cleaning = self.create_or_update( + self.model_classes_tww_od.tank_cleaning, + **self.base_common(row), + **self.structure_part_common(row), + # --- tank_cleaning --- + gross_costs=row.bruttokosten, + kind=self.get_vl_code(self.model_classes_tww_vl.tank_cleaning_kind, row.art), + year_of_replacement=row.ersatzjahr, + ) + self.session_tww.add(tank_cleaning) + print(".", end="") + + def _import_biol_oekol_gesamtbeurteilung(self): + for row in self.session_interlis.query( + self.model_classes_interlis.biol_oekol_gesamtbeurteilung + ): + bio_ecol_assessment = self.create_or_update( + self.model_classes_tww_od.bio_ecol_assessment, + **self.base_common(row), + **self.maintenance_event_common(row), + # --- bio_ecol_assessment --- + comparison_last=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_comparison_last, + row.vergleich_letzte_untersuchung, + ), + date_last_examen=row.datum_letzte_untersuchung, + impact_auxiliary_indic=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_impact_auxiliary_indic, + row.einfluss_hilfsindikatoren, + ), + impact_external_aspect=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_impact_external_aspect, + row.einfluss_aeusserer_aspekt, + ), + impact_macroinvertebrates=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_impact_macroinvertebrates, + row.einfluss_makroinvertebraten, + ), + impact_water_plants=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_impact_water_plants, + row.einfluss_wasserpflanzen, + ), + intervention_demand=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_intervention_demand, + row.handlungsbedarf, + ), + io_calculation=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_io_calculation, + row.immissionsorientierte_berechnung, + ), + outlet_pipe_clear_height=row.auslaufrohr_lichte_hoehe, + q347=row.q347, + relevance_matrix=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_relevance_matrix, + row.relevanzmatrix, + ), + relevant_slope=row.relevantes_gefaelle, + surface_water_bodies=row.oberflaechengewaesser, + kind_water_body=self.get_vl_code( + self.model_classes_tww_vl.bio_ecol_assessment_kind_water_body, row.gewaesserart + ), + water_specific_discharge_freight_nh4_n_current=row.gewaesserspezifische_entlastungsfracht_nh4_n_ist, + water_specific_discharge_freight_nh4_n_current_opt=row.gewaesserspezifische_entlastungsfracht_nh4_n_ist_optimiert, + water_specific_discharge_freight_nh4_n_planned=row.gewaesserspezifische_entlastungsfracht_nh4_n_geplant, + ) + self.session_tww.add(bio_ecol_assessment) + print(".", end="") + + def _import_brunnen(self): + for row in self.session_interlis.query(self.model_classes_interlis.brunnen): + fountain = self.create_or_update( + self.model_classes_tww_od.fountain, + **self.base_common(row), + **self.connection_object_common(row), + # --- fountain --- + location_name=row.standortname, + situation_geometry=row.lage, + ) + self.session_tww.add(fountain) + print(".", end="") + + def _import_ezg_parameter_allg(self): + for row in self.session_interlis.query(self.model_classes_interlis.ezg_parameter_allg): + param_ca_general = self.create_or_update( + self.model_classes_tww_od.param_ca_general, + **self.base_common(row), + **self.surface_runoff_parameters_common(row), + # --- param_ca_general --- + dry_wheather_flow=row.trockenwetteranfall, + flow_path_length=row.fliessweglaenge, + flow_path_slope=row.fliessweggefaelle, + population_equivalent=row.einwohnergleichwert, + surface_ca=row.flaeche, + ) + self.session_tww.add(param_ca_general) + print(".", end="") + + def _import_ezg_parameter_mouse1(self): + for row in self.session_interlis.query(self.model_classes_interlis.ezg_parameter_mouse1): + param_ca_mouse1 = self.create_or_update( + self.model_classes_tww_od.param_ca_mouse1, + **self.base_common(row), + **self.surface_runoff_parameters_common(row), + # --- param_ca_mouse1 --- + dry_wheather_flow=row.trockenwetteranfall, + flow_path_length=row.fliessweglaenge, + flow_path_slope=row.fliessweggefaelle, + population_equivalent=row.einwohnergleichwert, + surface_ca_mouse=row.flaeche, + usage=row.nutzungsart, + ) + self.session_tww.add(param_ca_mouse1) + print(".", end="") + + def _import_einzelflaeche(self): + for row in self.session_interlis.query(self.model_classes_interlis.einzelflaeche): + individual_surface = self.create_or_update( + self.model_classes_tww_od.individual_surface, + **self.base_common(row), + **self.connection_object_common(row), + # --- individual_surface --- + function=self.get_vl_code( + self.model_classes_tww_vl.individual_surface_function, row.funktion + ), + inclination=row.neigung, + pavement=self.get_vl_code( + self.model_classes_tww_vl.individual_surface_pavement, row.befestigung + ), + perimeter_geometry=row.perimeter, + ) + self.session_tww.add(individual_surface) + print(".", end="") + + def _import_einzugsgebiet(self): + for row in self.session_interlis.query(self.model_classes_interlis.einzugsgebiet): + catchment_area = self.create_or_update( + self.model_classes_tww_od.catchment_area, + **self.base_common(row), + # --- catchment_area --- + direct_discharge_current=self.get_vl_code( + self.model_classes_tww_vl.catchment_area_direct_discharge_current, + row.direkteinleitung_in_gewaesser_ist, + ), + direct_discharge_planned=self.get_vl_code( + self.model_classes_tww_vl.catchment_area_direct_discharge_planned, + row.direkteinleitung_in_gewaesser_geplant, + ), + discharge_coefficient_rw_current=row.abflussbeiwert_rw_ist, + discharge_coefficient_rw_planned=row.abflussbeiwert_rw_geplant, + discharge_coefficient_ww_current=row.abflussbeiwert_sw_ist, + discharge_coefficient_ww_planned=row.abflussbeiwert_sw_geplant, + drainage_system_current=self.get_vl_code( + self.model_classes_tww_vl.catchment_area_drainage_system_current, + row.entwaesserungssystem_ist, + ), + drainage_system_planned=self.get_vl_code( + self.model_classes_tww_vl.catchment_area_drainage_system_planned, + row.entwaesserungssystem_geplant, + ), + identifier=row.bezeichnung, + infiltration_current=self.get_vl_code( + self.model_classes_tww_vl.catchment_area_infiltration_current, + row.versickerung_ist, + ), + infiltration_planned=self.get_vl_code( + self.model_classes_tww_vl.catchment_area_infiltration_planned, + row.versickerung_geplant, + ), + perimeter_geometry=row.perimeter, + population_density_current=row.einwohnerdichte_ist, + population_density_planned=row.einwohnerdichte_geplant, + remark=row.bemerkung, + retention_current=self.get_vl_code( + self.model_classes_tww_vl.catchment_area_retention_current, row.retention_ist + ), + retention_planned=self.get_vl_code( + self.model_classes_tww_vl.catchment_area_retention_planned, + row.retention_geplant, + ), + runoff_limit_current=row.abflussbegrenzung_ist, + runoff_limit_planned=row.abflussbegrenzung_geplant, + seal_factor_rw_current=row.befestigungsgrad_rw_ist, + seal_factor_rw_planned=row.befestigungsgrad_rw_geplant, + seal_factor_ww_current=row.befestigungsgrad_sw_ist, + seal_factor_ww_planned=row.befestigungsgrad_sw_geplant, + sewer_infiltration_water_production_current=row.fremdwasseranfall_ist, + sewer_infiltration_water_production_planned=row.fremdwasseranfall_geplant, + surface_area=row.flaeche, + waste_water_production_current=row.schmutzabwasseranfall_ist, + waste_water_production_planned=row.schmutzabwasseranfall_geplant, + fk_wastewater_networkelement_rw_current=self.get_pk( + row.abwassernetzelement_rw_istref__REL + ), + fk_wastewater_networkelement_rw_planned=self.get_pk( + row.abwassernetzelement_rw_geplantref__REL + ), + fk_wastewater_networkelement_ww_planned=self.get_pk( + row.abwassernetzelement_sw_geplantref__REL + ), + fk_wastewater_networkelement_ww_current=self.get_pk( + row.abwassernetzelement_sw_istref__REL + ), + fk_special_building_rw_planned=self.get_pk(row.sbw_rw_geplantref__REL), + fk_special_building_rw_current=self.get_pk(row.sbw_rw_istref__REL), + fk_special_building_ww_planned=self.get_pk(row.sbw_sw_geplantref__REL), + fk_special_building_ww_current=self.get_pk(row.sbw_sw_istref__REL), + ) + self.session_tww.add(catchment_area) + print(".", end="") + + def _import_elektrischeeinrichtung(self): + for row in self.session_interlis.query(self.model_classes_interlis.elektrischeeinrichtung): + electric_equipment = self.create_or_update( + self.model_classes_tww_od.electric_equipment, + **self.base_common(row), + **self.structure_part_common(row), + # --- electric_equipment --- + gross_costs=row.bruttokosten, + kind=self.get_vl_code(self.model_classes_tww_vl.electric_equipment_kind, row.art), + year_of_replacement=row.ersatzjahr, + ) + self.session_tww.add(electric_equipment) + print(".", end="") + + def _import_elektromechanischeausruestung(self): + for row in self.session_interlis.query( + self.model_classes_interlis.elektromechanischeausruestung + ): + electromechanical_equipment = self.create_or_update( + self.model_classes_tww_od.electromechanical_equipment, + **self.base_common(row), + **self.structure_part_common(row), + # --- electromechanical_equipment --- + gross_costs=row.bruttokosten, + kind=self.get_vl_code( + self.model_classes_tww_vl.electromechanical_equipment_kind, row.art + ), + year_of_replacement=row.ersatzjahr, + ) + self.session_tww.add(electromechanical_equipment) + print(".", end="") + + def _import_entsorgung(self): + for row in self.session_interlis.query(self.model_classes_interlis.entsorgung): + disposal = self.create_or_update( + self.model_classes_tww_od.disposal, + **self.base_common(row), + # --- disposal --- + disposal_interval_current=row.entsorgungsintervall_ist, + disposal_interval_nominal=row.entsorgungsintervall_soll, + disposal_place_current=self.get_vl_code( + self.model_classes_tww_vl.disposal_disposal_place_current, + row.entsorgungsort_ist, + ), + disposal_place_planned=self.get_vl_code( + self.model_classes_tww_vl.disposal_disposal_place_planned, + row.entsorgungsort_geplant, + ), + volume_pit_without_drain=row.volumenabflusslosegrube, + fk_infiltration_installation=self.get_pk(row.versickerungsanlageref__REL), + fk_discharge_point=self.get_pk(row.einleitstelleref__REL), + fk_wastewater_structure=self.get_pk(row.abwasserbauwerkref__REL), + ) + self.session_tww.add(disposal) + print(".", end="") + + def _import_entwaesserungssystem(self): + for row in self.session_interlis.query(self.model_classes_interlis.entwaesserungssystem): + drainage_system = self.create_or_update( + self.model_classes_tww_od.drainage_system, + **self.base_common(row), + **self.zone_common(row), + # --- drainage_system --- + kind=self.get_vl_code(self.model_classes_tww_vl.drainage_system_kind, row.art), + perimeter_geometry=row.perimeter, + ) + self.session_tww.add(drainage_system) + print(".", end="") + + def _import_feststoffrueckhalt(self): + for row in self.session_interlis.query(self.model_classes_interlis.feststoffrueckhalt): + solids_retention = self.create_or_update( + self.model_classes_tww_od.solids_retention, + **self.base_common(row), + **self.structure_part_common(row), + # --- solids_retention --- + dimensioning_value=row.dimensionierungswert, + gross_costs=row.bruttokosten, + overflow_level=row.anspringkote, + kind=self.get_vl_code(self.model_classes_tww_vl.solids_retention_kind, row.art), + year_of_replacement=row.ersatzjahr, + ) + self.session_tww.add(solids_retention) + print(".", end="") + + def _import_foerderaggregat(self): + for row in self.session_interlis.query(self.model_classes_interlis.foerderaggregat): + pump = self.create_or_update( + self.model_classes_tww_od.pump, + **self.base_common(row), + **self.overflow_common(row), + # --- pump --- + construction_type=self.get_vl_code( + self.model_classes_tww_vl.pump_construction_type, row.bauart + ), + operating_point=row.arbeitspunkt, + placement_of_actuation=self.get_vl_code( + self.model_classes_tww_vl.pump_placement_of_actuation, row.aufstellungantrieb + ), + placement_of_pump=self.get_vl_code( + self.model_classes_tww_vl.pump_placement_of_pump, + row.aufstellungfoerderaggregat, + ), + pump_flow_max_single=row.foerderstrommax_einzel, + pump_flow_min_single=row.foerderstrommin_einzel, + start_level=row.kotestart, + stop_level=row.kotestop, + ) + self.session_tww.add(pump) + print(".", end="") + + def _import_gebaeude(self): + for row in self.session_interlis.query(self.model_classes_interlis.gebaeude): + building = self.create_or_update( + self.model_classes_tww_od.building, + **self.base_common(row), + **self.connection_object_common(row), + # --- building --- + house_number=row.hausnummer, + location_name=row.standortname, + perimeter_geometry=row.perimeter, + reference_point_geometry=row.referenzpunkt, + ) + self.session_tww.add(building) + print(".", end="") + + def _import_gebaeudegruppe(self): + for row in self.session_interlis.query(self.model_classes_interlis.gebaeudegruppe): + building_group = self.create_or_update( + self.model_classes_tww_od.building_group, + **self.base_common(row), + # --- building_group --- + movie_theater_seats=row.kinositzplaetze, + church_seats=row.kirchesitzplaetze, + camping_area=row.campingflaeche, + camping_lodgings=row.campinguebernachtungen, + connecting_obligation=self.get_vl_code( + self.model_classes_tww_vl.building_group_connecting_obligation, + row.anschlusspflicht, + ), + connection_wwtp=self.get_vl_code( + self.model_classes_tww_vl.building_group_connection_wwtp, row.anschlussara + ), + craft_employees=row.gewerbebeschaeftigte, + dorm_beds=row.schlafsaalbetten, + dorm_overnight_stays=row.schlafsaaluebernachtungen, + drainage_map=self.get_vl_code( + self.model_classes_tww_vl.building_group_drainage_map, row.entwaesserungsplan + ), + drinking_water_network=self.get_vl_code( + self.model_classes_tww_vl.building_group_drinking_water_network, + row.trinkwassernetzanschluss, + ), + drinking_water_others=self.get_vl_code( + self.model_classes_tww_vl.building_group_drinking_water_others, + row.trinkwasserandere, + ), + electric_connection=self.get_vl_code( + self.model_classes_tww_vl.building_group_electric_connection, + row.stromanschluss, + ), + event_visitors=row.veranstaltungbesucher, + function=self.get_vl_code( + self.model_classes_tww_vl.building_group_function, row.funktion + ), + gym_area=row.turnhalleflaeche, + holiday_accomodation=row.ferienuebernachtungen, + hospital_beds=row.spitalbetten, + hotel_beds=row.hotelbetten, + hotel_overnight_stays=row.hoteluebernachtungen, + identifier=row.bezeichnung, + other_usage_population_equivalent=row.anderenutzungegw, + other_usage_type=row.anderenutzungart, + population_equivalent=row.einwohnerwerte, + remark=row.bemerkung, + renovation_date=row.sanierungsdatum, + renovation_necessity=self.get_vl_code( + self.model_classes_tww_vl.building_group_renovation_necessity, + row.sanierungsbedarf, + ), + restaurant_seats=row.raststaettesitzplaetze, + restaurant_seats_hall_garden=row.restaurantsitzplaetze_saalgarten, + restaurant_seats_permanent=row.restaurantsitzplaetze_permanent, + restructuring_concept=row.sanierungskonzept, + school_students=row.schuleschueler, + situation_geometry=row.lage, + # fk_disposal=self.get_pk(row.entsorgungref__REL), # TODO check why not available + fk_measure=self.get_pk(row.massnahmeref__REL), + ) + self.session_tww.add(building_group) + print(".", end="") + + def _import_gebaeudegruppe_baugwr(self): + for row in self.session_interlis.query(self.model_classes_interlis.gebaeudegruppe_baugwr): + building_group_baugwr = self.create_or_update( + self.model_classes_tww_od.building_group_baugwr, + **self.base_common(row), + # --- building_group_baugwr --- + egid=row.egid, + fk_building_group=self.get_pk(row.gebaeudegrupperef__REL), + ) + self.session_tww.add(building_group_baugwr) + print(".", end="") + + def _import_gesamteinzugsgebiet(self): + for row in self.session_interlis.query(self.model_classes_interlis.gesamteinzugsgebiet): + catchment_area_totals = self.create_or_update( + self.model_classes_tww_od.catchment_area_totals, + **self.base_common(row), + # --- catchment_area_totals --- + discharge_freight_nh4_n=row.entlastungsfracht_nh4_n, + discharge_proportion_nh4_n=row.entlastungsanteil_nh4_n, + identifier=row.bezeichnung, + population=row.einwohner, + population_dim=row.einwohner_dim, + sewer_infiltration_water=row.fremdwasseranfall, + surface_area=row.flaeche, + surface_dim=row.flaeche_dim, + surface_imp=row.flaeche_bef, + surface_imp_dim=row.flaeche_bef_dim, + surface_red=row.flaeche_red, + surface_red_dim=row.flaeche_red_dim, + waste_water_production=row.schmutzabwasseranfall, + fk_discharge_point=self.get_pk(row.einleitstelleref__REL), + fk_hydraulic_char_data=self.get_pk(row.hydr_kennwerteref__REL), + ) + self.session_tww.add(catchment_area_totals) + print(".", end="") + + def _import_hq_relation(self): + for row in self.session_interlis.query(self.model_classes_interlis.hq_relation): + hq_relation = self.create_or_update( + self.model_classes_tww_od.hq_relation, + **self.base_common(row), + # --- hq_relation --- + altitude=row.hoehe, + flow=row.abfluss, + flow_from=row.zufluss, + fk_overflow_char=self.get_pk(row.ueberlaufcharakteristikref__REL), + ) + self.session_tww.add(hq_relation) + print(".", end="") + + def _import_hydr_geomrelation(self): + for row in self.session_interlis.query(self.model_classes_interlis.hydr_geomrelation): + hydr_geom_relation = self.create_or_update( + self.model_classes_tww_od.hydr_geom_relation, + **self.base_common(row), + # --- hydr_geom_relation --- + water_depth=row.wassertiefe, + water_surface=row.wasseroberflaeche, + wet_cross_section_area=row.benetztequerschnittsflaeche, + fk_hydr_geometry=self.get_pk(row.hydr_geometrieref__REL), + ) + self.session_tww.add(hydr_geom_relation) + print(".", end="") + + def _import_hydr_geometrie(self): + for row in self.session_interlis.query(self.model_classes_interlis.hydr_geometrie): + hydr_geometry = self.create_or_update( + self.model_classes_tww_od.hydr_geometry, + **self.base_common(row), + # --- hydr_geometry --- + identifier=row.bezeichnung, + remark=row.bemerkung, + storage_volume=row.stauraum, + usable_capacity_storage=row.nutzinhalt_fangteil, + usable_capacity_treatment=row.nutzinhalt_klaerteil, + utilisable_capacity=row.nutzinhalt, + volume_pump_sump=row.volumen_pumpensumpf, + ) + self.session_tww.add(hydr_geometry) + print(".", end="") + + def _import_hydr_kennwerte(self): + for row in self.session_interlis.query(self.model_classes_interlis.hydr_kennwerte): + hydraulic_char_data = self.create_or_update( + self.model_classes_tww_od.hydraulic_char_data, + **self.base_common(row), + # --- hydraulic_char_data --- + qon=row.qan, + remark=row.bemerkung, + status=self.get_vl_code( + self.model_classes_tww_vl.hydraulic_char_data_status, row.astatus + ), + aggregate_number=row.aggregatezahl, + delivery_height_geodaetic=row.foerderhoehe_geodaetisch, + identifier=row.bezeichnung, + is_overflowing=self.get_vl_code( + self.model_classes_tww_vl.hydraulic_char_data_is_overflowing, row.springt_an + ), + main_weir_kind=self.get_vl_code( + self.model_classes_tww_vl.hydraulic_char_data_main_weir_kind, row.hauptwehrart + ), + overcharge=row.mehrbelastung, + overflow_duration=row.ueberlaufdauer, + overflow_freight=row.ueberlauffracht, + overflow_frequency=row.ueberlaufhaeufigkeit, + overflow_volume=row.ueberlaufmenge, + pump_characteristics=self.get_vl_code( + self.model_classes_tww_vl.hydraulic_char_data_pump_characteristics, + row.pumpenregime, + ), + pump_flow_max=row.foerderstrommax, + pump_flow_min=row.foerderstrommin, + q_discharge=row.qab, + fk_wastewater_node=self.get_pk(row.abwasserknotenref__REL), + fk_overflow_char=self.get_pk(row.ueberlaufcharakteristikref__REL), + fk_primary_direction=self.get_pk(row.primaerrichtungref__REL), + ) + self.session_tww.add(hydraulic_char_data) + print(".", end="") + + def _import_klara(self): + for row in self.session_interlis.query(self.model_classes_interlis.klara): + small_treatment_plant = self.create_or_update( + self.model_classes_tww_od.small_treatment_plant, + **self.base_common(row), + **self.wastewater_structure_common(row), + # --- small_treatment_plant --- + approval_number=row.bewilligungsnummer, + function=self.get_vl_code( + self.model_classes_tww_vl.small_treatment_plant_function, row.funktion + ), + installation_number=row.anlagenummer, + remote_monitoring=self.get_vl_code( + self.model_classes_tww_vl.small_treatment_plant_remote_monitoring, + row.fernueberwachung, + ), + ) + self.session_tww.add(small_treatment_plant) + print(".", end="") + + def _import_landwirtschaftsbetrieb(self): + for row in self.session_interlis.query(self.model_classes_interlis.landwirtschaftsbetrieb): + farm = self.create_or_update( + self.model_classes_tww_od.farm, + **self.base_common(row), + # --- farm --- + agriculture_arable_surface=row.nutzflaechelandwirtschaft, + cesspit_comment=row.guellegrubebemerkung, + cesspit_volume=self.get_vl_code( + self.model_classes_tww_vl.farm_cesspit_volume, row.guellegrubevolumen + ), + cesspit_volume_current=row.guellegrubevolumen_ist, + cesspit_volume_nominal=row.guellegrubevolumen_soll, + cesspit_volume_ww_treated=row.guellegrubevolumen_sw_behandelt, + cesspit_year_of_approval=row.guellegrubebewilligungsjahr, + conformity=self.get_vl_code( + self.model_classes_tww_vl.farm_conformity, row.konformitaet + ), + continuance=self.get_vl_code( + self.model_classes_tww_vl.farm_continuance, row.fortbestand + ), + continuance_comment=row.fortbestandbemerkung, + dung_heap_area_current=row.mistplatzflaeche_ist, + dung_heap_area_nominal=row.mistplatzflaeche_soll, + remark=row.bemerkung, + shepherds_hut_comment=row.hirtenhuettebemerkung, + shepherds_hut_population_equivalent=row.hirtenhuetteegw, + shepherds_hut_wastewater=self.get_vl_code( + self.model_classes_tww_vl.farm_shepherds_hut_wastewater, + row.hirtenhuetteabwasser, + ), + stable_cattle=self.get_vl_code( + self.model_classes_tww_vl.farm_stable_cattle, row.stallvieh + ), + stable_cattle_equivalent_other_cattle=row.stallgrossvieheinheit_fremdvieh, + stable_cattle_equivalent_own_cattle=row.stallgrossvieheinheit_eigenesvieh, + fk_building_group=self.get_pk(row.gebaeudegrupperef__REL), + ) + self.session_tww.add(farm) + print(".", end="") + + def _import_leapingwehr(self): + for row in self.session_interlis.query(self.model_classes_interlis.leapingwehr): + leapingweir = self.create_or_update( + self.model_classes_tww_od.leapingweir, + **self.base_common(row), + **self.overflow_common(row), + # --- leapingweir --- + length=row.laenge, + opening_shape=self.get_vl_code( + self.model_classes_tww_vl.leapingweir_opening_shape, row.oeffnungsform + ), + width=row.breite, + ) + self.session_tww.add(leapingweir) + print(".", end="") + + def _import_massnahme(self): + for row in self.session_interlis.query(self.model_classes_interlis.massnahme): + measure = self.create_or_update( + self.model_classes_tww_od.measure, + **self.base_common(row), + # --- measure --- + date_entry=row.datum_eingang, + description=row.beschreibung, + category=self.get_vl_code( + self.model_classes_tww_vl.measure_category, row.kategorie + ), + identifier=row.bezeichnung, + intervention_demand=row.handlungsbedarf, + line_geometry=row.linie, + link=row.verweis, + perimeter_geometry=row.perimeter, + priority=self.get_vl_code( + self.model_classes_tww_vl.measure_priority, row.prioritaet + ), + remark=row.bemerkung, + status=self.get_vl_code(self.model_classes_tww_vl.measure_status, row.astatus), + symbolpos_geometry=row.symbolpos, + total_cost=row.gesamtkosten, + year_implementation_effective=row.jahr_umsetzung_effektiv, + year_implementation_planned=row.jahr_umsetzung_geplant, + fk_responsible_entity=row.traegerschaftref, + fk_responsible_start=row.verantwortlich_ausloesungref, + ) + self.session_tww.add(measure) + print(".", end="") + + def _import_mechanischevorreinigung(self): + for row in self.session_interlis.query( + self.model_classes_interlis.mechanischevorreinigung + ): + mechanical_pretreatment = self.create_or_update( + self.model_classes_tww_od.mechanical_pretreatment, + **self.base_common(row), + # --- mechanical_pretreatment --- + identifier=row.bezeichnung, + kind=self.get_vl_code( + self.model_classes_tww_vl.mechanical_pretreatment_kind, row.art + ), + remark=row.bemerkung, + fk_wastewater_structure=self.get_pk(row.abwasserbauwerkref__REL), + ) + self.session_tww.add(mechanical_pretreatment) + print(".", end="") + + def _import_messgeraet(self): + for row in self.session_interlis.query(self.model_classes_interlis.messgeraet): + measuring_device = self.create_or_update( + self.model_classes_tww_od.measuring_device, + **self.base_common(row), + # --- measuring_device --- + serial_number=row.seriennummer, + brand=row.fabrikat, + identifier=row.bezeichnung, + kind=self.get_vl_code(self.model_classes_tww_vl.measuring_device_kind, row.art), + remark=row.bemerkung, + fk_measuring_point=self.get_pk(row.messstelleref__REL), + ) + self.session_tww.add(measuring_device) + print(".", end="") + + def _import_messreihe(self): + for row in self.session_interlis.query(self.model_classes_interlis.messreihe): + measurement_series = self.create_or_update( + self.model_classes_tww_od.measurement_series, + **self.base_common(row), + # --- measurement_series --- + dimension=row.dimension, + identifier=row.bezeichnung, + kind=self.get_vl_code(self.model_classes_tww_vl.measurement_series_kind, row.art), + remark=row.bemerkung, + fk_measuring_point=self.get_pk(row.messstelleref__REL), + fk_wastewater_networkelement=self.get_pk(row.abwassernetzelementref__REL), + ) + self.session_tww.add(measurement_series) + print(".", end="") + + def _import_messresultat(self): + for row in self.session_interlis.query(self.model_classes_interlis.messresultat): + measurement_result = self.create_or_update( + self.model_classes_tww_od.measurement_result, + **self.base_common(row), + # --- measurement_result --- + identifier=row.bezeichnung, + measurement_type=self.get_vl_code( + self.model_classes_tww_vl.measurement_result_measurement_type, row.messart + ), + measuring_duration=row.messdauer, + remark=row.bemerkung, + time=row.zeit, + value=row.wert, + fk_measuring_device=self.get_pk(row.messgeraetref__REL), + fk_measurement_series=self.get_pk(row.messreiheref__REL), + ) + self.session_tww.add(measurement_result) + print(".", end="") + + def _import_messstelle(self): + for row in self.session_interlis.query(self.model_classes_interlis.messstelle): + measuring_point = self.create_or_update( + self.model_classes_tww_od.measuring_point, + **self.base_common(row), + # --- measuring_point --- + # change to value list reference + # purpose=row.zweck, + purpose=self.get_vl_code( + self.model_classes_tww_vl.measuring_point_purpose, row.zweck + ), + remark=row.bemerkung, + # change to value list reference + # damming_device=row.staukoerper, + damming_device=self.get_vl_code( + self.model_classes_tww_vl.measuring_point_damming_device, row.staukoerper + ), + identifier=row.bezeichnung, + # kind is not a value list here + kind=row.art, + situation_geometry=row.lage, + fk_operator=row.betreiberref, + fk_waste_water_treatment_plant=self.get_pk(row.abwasserreinigungsanlageref__REL), + fk_wastewater_structure=self.get_pk(row.abwasserbauwerkref__REL), + ) + self.session_tww.add(measuring_point) + print(".", end="") + + def _import_mutation(self): + for row in self.session_interlis.query(self.model_classes_interlis.mutation): + mutation = self.create_or_update( + self.model_classes_tww_od.mutation, + **self.base_common(row), + # --- mutation --- + attribute=row.attribut, + classname=row.klasse, + date_mutation=row.mutationsdatum, + date_time=row.aufnahmedatum, + kind=self.get_vl_code(self.model_classes_tww_vl.mutation_kind, row.art), + last_value=row.letzter_wert, + object=row.objekt, + recorded_by=row.aufnehmer, + remark=row.bemerkung, + user_system=row.systembenutzer, + ) + self.session_tww.add(mutation) + print(".", end="") + + def _import_reservoir(self): + for row in self.session_interlis.query(self.model_classes_interlis.reservoir): + reservoir = self.create_or_update( + self.model_classes_tww_od.reservoir, + **self.base_common(row), + **self.connection_object_common(row), + # --- reservoir --- + location_name=row.standortname, + situation_geometry=row.lage, + ) + self.session_tww.add(reservoir) + print(".", end="") + + def _import_retentionskoerper(self): + for row in self.session_interlis.query(self.model_classes_interlis.retentionskoerper): + retention_body = self.create_or_update( + self.model_classes_tww_od.retention_body, + **self.base_common(row), + # --- retention_body --- + identifier=row.bezeichnung, + kind=self.get_vl_code(self.model_classes_tww_vl.retention_body_kind, row.art), + remark=row.bemerkung, + volume=row.retention_volumen, + fk_infiltration_installation=self.get_pk(row.versickerungsanlageref__REL), + ) + self.session_tww.add(retention_body) + print(".", end="") + + def _import_rohrprofil_geometrie(self): + for row in self.session_interlis.query(self.model_classes_interlis.rohrprofil_geometrie): + profile_geometry = self.create_or_update( + self.model_classes_tww_od.profile_geometry, + **self.base_common(row), + # --- profile_geometry --- + sequence=row.reihenfolge, + x=row.x, + y=row.y, + fk_pipe_profile=self.get_pk(row.rohrprofilref__REL), + ) + self.session_tww.add(profile_geometry) + print(".", end="") + + def _import_rueckstausicherung(self): + for row in self.session_interlis.query(self.model_classes_interlis.rueckstausicherung): + backflow_prevention = self.create_or_update( + self.model_classes_tww_od.backflow_prevention, + **self.base_common(row), + **self.structure_part_common(row), + # --- backflow_prevention --- + gross_costs=row.bruttokosten, + kind=self.get_vl_code(self.model_classes_tww_vl.backflow_prevention_kind, row.art), + year_of_replacement=row.ersatzjahr, + fk_throttle_shut_off_unit=self.get_pk(row.absperr_drosselorganref), + fk_pump=self.get_pk(row.foerderaggregatref), + ) + self.session_tww.add(backflow_prevention) + print(".", end="") + + def _import_stammkarte(self): + for row in self.session_interlis.query(self.model_classes_interlis.stammkarte): + log_card = self.create_or_update( + self.model_classes_tww_od.log_card, + **self.base_common(row), + # --- log_card --- + control_remote_control=self.get_vl_code( + self.model_classes_tww_vl.log_card_control_remote_control, + row.steuerung_fernwirkung, + ), + information_source=self.get_vl_code( + self.model_classes_tww_vl.log_card_information_source, row.informationsquelle + ), + person_in_charge=row.sachbearbeiter, + remark=row.bemerkung, + fk_pwwf_wastewater_node=self.get_pk(row.paa_knotenref__REL), + fk_agency=row.bueroref, + fk_location_municipality=row.standortgemeinderef, + ) + self.session_tww.add(log_card) + print(".", end="") + + def _import_streichwehr(self): + for row in self.session_interlis.query(self.model_classes_interlis.streichwehr): + prank_weir = self.create_or_update( + self.model_classes_tww_od.prank_weir, + **self.base_common(row), + **self.overflow_common(row), + # --- prank_weir --- + hydraulic_overflow_length=row.hydrueberfalllaenge, + level_max=row.kotemax, + level_min=row.kotemin, + weir_edge=self.get_vl_code( + self.model_classes_tww_vl.prank_weir_weir_edge, row.ueberfallkante + ), + weir_kind=self.get_vl_code( + self.model_classes_tww_vl.prank_weir_weir_kind, row.wehr_art + ), + ) + self.session_tww.add(prank_weir) + print(".", end="") + + def _import_ueberlaufcharakteristik(self): + for row in self.session_interlis.query( + self.model_classes_interlis.ueberlaufcharakteristik + ): + overflow_char = self.create_or_update( + self.model_classes_tww_od.overflow_char, + **self.base_common(row), + # --- overflow_char --- + identifier=row.bezeichnung, + kind_overflow_char=self.get_vl_code( + self.model_classes_tww_vl.overflow_char_kind_overflow_char, row.kennlinie_typ + ), + remark=row.bemerkung, + ) + self.session_tww.add(overflow_char) + print(".", end="") + + def _import_unterhalt(self): + for row in self.session_interlis.query(self.model_classes_interlis.unterhalt): + maintenance = self.create_or_update( + self.model_classes_tww_od.maintenance, + **self.base_common(row), + **self.maintenance_event_common(row), + # --- maintenance --- + kind=self.get_vl_code(self.model_classes_tww_vl.maintenance_kind, row.art), + ) + self.session_tww.add(maintenance) + print(".", end="") + + def _import_versickerungsbereich(self): + for row in self.session_interlis.query(self.model_classes_interlis.versickerungsbereich): + infiltration_zone = self.create_or_update( + self.model_classes_tww_od.infiltration_zone, + **self.base_common(row), + **self.zone_common(row), + # --- infiltration_zone --- + infiltration_capacity=self.get_vl_code( + self.model_classes_tww_vl.infiltration_zone_infiltration_capacity, + row.versickerungsmoeglichkeit, + ), + perimeter_geometry=row.perimeter, + ) + self.session_tww.add(infiltration_zone) + print(".", end="") + + def _import_rohrprofil(self): + for row in self.session_interlis.query(self.model_classes_interlis.rohrprofil): + pipe_profile = self.create_or_update( + self.model_classes_tww_od.pipe_profile, + **self.base_common(row), + # --- pipe_profile --- + height_width_ratio=row.hoehenbreitenverhaeltnis, + identifier=row.bezeichnung, + profile_type=self.get_vl_code( + self.model_classes_tww_od.pipe_profile_profile_type, row.profiltyp + ), + remark=row.bemerkung, + ) + self.session_tww.add(pipe_profile) + print(".", end="") + + def _import_haltungspunkt(self): + for row in self.session_interlis.query(self.model_classes_interlis.haltungspunkt): + reach_point = self.create_or_update( + self.model_classes_tww_od.reach_point, + **self.base_common(row), + # --- reach_point --- + elevation_accuracy=self.get_vl_code( + self.model_classes_tww_od.reach_point_elevation_accuracy, row.hoehengenauigkeit + ), + fk_wastewater_networkelement=self.get_pk(row.abwassernetzelementref__REL), + identifier=row.bezeichnung, + level=row.kote, + outlet_shape=self.get_vl_code( + self.model_classes_tww_od.reach_point_outlet_shape, row.auslaufform + ), + position_of_connection=row.lage_anschluss, + remark=row.bemerkung, + situation3d_geometry=ST_Force3D(row.lage), + ) + self.session_tww.add(reach_point) + print(".", end="") + + def _import_haltung_alternativverlauf(self): + for row in self.session_interlis.query( + self.model_classes_interlis.haltung_alternativverlauf + ): + reach_progression_alternative = self.create_or_update( + self.model_classes_tww_od.reach_progression_alternative, + obj_id=row.t_ili_tid, + plantype=self.get_vl_code( + self.model_classes_tww_od.reach_progression_alternative_plantype, row.plantyp + ), + progression_geometry=row.verlauf, + fk_reach=self.get_pk(row.haltungref__REL), + ) + self.session_tww.add(reach_progression_alternative) + print(".", end="") + + def _import_abwasserknoten(self): + for row in self.session_interlis.query(self.model_classes_interlis.abwasserknoten): + wastewater_node = self.create_or_update( + self.model_classes_tww_od.wastewater_node, + **self.base_common(row), + # --- wastewater_networkelement --- + **self.wastewater_networkelement_common(row), + # --- wastewater_node --- + # fk_hydr_geometry=row.REPLACE_ME, # TODO : NOT MAPPED + backflow_level_current=row.rueckstaukote_ist, + bottom_level=row.sohlenkote, + situation3d_geometry=ST_Force3D(row.lage), + ) + self.session_tww.add(wastewater_node) + print(".", end="") + + def _import_haltung(self): + for row in self.session_interlis.query(self.model_classes_interlis.haltung): + reach = self.create_or_update( + self.model_classes_tww_od.reach, + **self.base_common(row), + # --- wastewater_networkelement --- + **self.wastewater_networkelement_common(row), + # --- reach --- + clear_height=row.lichte_hoehe, + coefficient_of_friction=row.reibungsbeiwert, + # TODO : NOT MAPPED VSA-DSS 3D + # self.get_vl_code( + # self.model_classes_tww_od.wastewater_structure_elevation_determination, row.hoehenbestimmung + # ), + fk_pipe_profile=self.get_pk(row.rohrprofilref__REL), + fk_reach_point_from=self.get_pk(row.vonhaltungspunktref__REL), + fk_reach_point_to=self.get_pk(row.nachhaltungspunktref__REL), + horizontal_positioning=self.get_vl_code( + self.model_classes_tww_od.reach_horizontal_positioning, row.lagebestimmung + ), + inside_coating=self.get_vl_code( + self.model_classes_tww_od.reach_inside_coating, row.innenschutz + ), + length_effective=row.laengeeffektiv, + material=self.get_vl_code(self.model_classes_tww_vl.reach_material, row.material), + progression3d_geometry=ST_Force3D(row.verlauf), + reliner_material=self.get_vl_code( + self.model_classes_tww_od.reach_reliner_material, row.reliner_material + ), + reliner_nominal_size=row.reliner_nennweite, + relining_construction=self.get_vl_code( + self.model_classes_tww_od.reach_relining_construction, row.reliner_bautechnik + ), + relining_kind=self.get_vl_code( + self.model_classes_tww_od.reach_relining_kind, row.reliner_art + ), + ring_stiffness=row.ringsteifigkeit, + slope_building_plan=row.plangefaelle, # TODO : check, does this need conversion ? + wall_roughness=row.wandrauhigkeit, + ) + self.session_tww.add(reach) + print(".", end="") + + def _import_trockenwetterfallrohr(self): + for row in self.session_interlis.query(self.model_classes_interlis.trockenwetterfallrohr): + dryweather_downspout = self.create_or_update( + self.model_classes_tww_od.dryweather_downspout, + **self.base_common(row), + # --- structure_part --- + **self.structure_part_common(row), + # --- dryweather_downspout --- + diameter=row.durchmesser, + ) + self.session_tww.add(dryweather_downspout) + print(".", end="") + + def _import_einstiegshilfe(self): + for row in self.session_interlis.query(self.model_classes_interlis.einstiegshilfe): + access_aid = self.create_or_update( + self.model_classes_tww_od.access_aid, + **self.base_common(row), + # --- structure_part --- + **self.structure_part_common(row), + # --- access_aid --- + kind=self.get_vl_code(self.model_classes_tww_vl.access_aid_kind, row.art), + ) + self.session_tww.add(access_aid) + print(".", end="") + + def _import_trockenwetterrinne(self): + for row in self.session_interlis.query(self.model_classes_interlis.trockenwetterrinne): + dryweather_flume = self.create_or_update( + self.model_classes_tww_od.dryweather_flume, + **self.base_common(row), + # --- structure_part --- + **self.structure_part_common(row), + # --- dryweather_flume --- + material=self.get_vl_code( + self.model_classes_tww_od.dryweather_flume_material, row.material + ), + ) + self.session_tww.add(dryweather_flume) + print(".", end="") + + def _import_deckel(self): + for row in self.session_interlis.query(self.model_classes_interlis.deckel): + cover = self.create_or_update( + self.model_classes_tww_od.cover, + **self.base_common(row), + # --- structure_part --- + **self.structure_part_common(row), + # --- cover --- + brand=row.fabrikat, + cover_shape=self.get_vl_code( + self.model_classes_tww_vl.cover_cover_shape, row.deckelform + ), + diameter=row.durchmesser, + fastening=self.get_vl_code( + self.model_classes_tww_vl.cover_fastening, row.verschluss + ), + level=row.kote, + material=self.get_vl_code(self.model_classes_tww_vl.cover_material, row.material), + positional_accuracy=self.get_vl_code( + self.model_classes_tww_od.cover_positional_accuracy, row.lagegenauigkeit + ), + situation3d_geometry=ST_Force3D(row.lage), + sludge_bucket=self.get_vl_code( + self.model_classes_tww_od.cover_sludge_bucket, row.schlammeimer + ), + venting=self.get_vl_code(self.model_classes_tww_vl.cover_venting, row.entlueftung), + ) + self.session_tww.add(cover) + print(".", end="") + + def _import_bankett(self): + for row in self.session_interlis.query(self.model_classes_interlis.bankett): + benching = self.create_or_update( + self.model_classes_tww_od.benching, + **self.base_common(row), + # --- structure_part --- + **self.structure_part_common(row), + # --- benching --- + kind=self.get_vl_code(self.model_classes_tww_vl.benching_kind, row.art), + ) + self.session_tww.add(benching) + print(".", end="") + + def _import_spuelstutzen(self): + for row in self.session_interlis.query(self.model_classes_interlis.spuelstutzen): + flushing_nozzle = self.create_or_update( + self.model_classes_tww_od.flushing_nozzle, + **self.base_common(row), + **self.structure_part_common(row), + # --- flushing_nozzle --- + situation_geometry=row.lage, + ) + self.session_tww.add(flushing_nozzle) + print(".", end="") + + def _import_abwasserbauwerk_symbol(self): + for row in self.session_interlis.query(self.model_classes_interlis.abwasserbauwerk_symbol): + wastewater_structure_symbol = self.create_or_update( + self.model_classes_tww_od.wastewater_structure_symbol, + # --- wastewater_structure_symbol --- + obj_id=row.t_ili_tid, + plantype=self.get_vl_code( + self.model_classes_tww_vl.wastewater_structure_symbol_plantype, row.plantyp + ), + symbol_scaling_height=row.symbolskalierunghoch, + symbol_scaling_width=row.symbolskalierunglaengs, + symbolori=row.symbolori, + symbolpos_geometry=row.symbolpos, + fk_wastewater_structure=self.get_pk(row.abwasserbauwerkref__REL), + ) + self.session_tww.add(wastewater_structure_symbol) + print(".", end="") + + def _import_untersuchung(self): + for row in self.session_interlis.query(self.model_classes_interlis.untersuchung): + examination = self.create_or_update( + self.model_classes_tww_od.examination, + **self.base_common(row), + # --- maintenance_event --- + base_data=row.datengrundlage, + cost=row.kosten, + data_details=row.detaildaten, + duration=row.dauer, + # in VSA-KEK 2020 in class maintenance_event instead of examination + # fk_operating_company=( + # row.ausfuehrende_firmaref if row.ausfuehrende_firmaref else None + # ), + identifier=row.bezeichnung, + operator=row.ausfuehrender, + reason=row.grund, + remark=row.bemerkung, + result=row.ergebnis, + status=self.get_vl_code( + self.model_classes_tww_vl.maintenance_event_status, row.astatus + ), + time_point=row.zeitpunkt, + # --- examination --- + equipment=row.geraet, + fk_reach_point=row.haltungspunktref if row.haltungspunktref else None, + from_point_identifier=row.vonpunktbezeichnung, + inspected_length=row.inspizierte_laenge, + recording_type=self.get_vl_code( + self.model_classes_tww_od.examination_recording_type, row.erfassungsart + ), + to_point_identifier=row.bispunktbezeichnung, + vehicle=row.fahrzeug, + videonumber=row.videonummer, + weather=self.get_vl_code( + self.model_classes_tww_vl.examination_weather, row.witterung + ), + ) + self.session_tww.add(examination) + + # In TWW, relation between maintenance_event and wastewater_structure is done with + # an association table instead of a foreign key on maintenance_event. + # NOTE : this may change in future versions of VSA_KEK + if row.abwasserbauwerkref: + # TODO : for now, this will not work unless the related wastewaterstructures are part of the import, + # as ili2pg imports dangling references as NULL. + # The day ili2pg works, we probably need to double-check whether the referenced wastewater structure exists prior + # to creating this association. + # Soft matching based on from/to_point_identifier will be done in the GUI data checking process. + exam_to_wastewater_structure = self.create_or_update( + self.model_classes_tww_od.re_maintenance_event_wastewater_structure, + fk_wastewater_structure=row.abwasserbauwerkref, + fk_maintenance_event=row.t_ili_tid, + ) + self.session_tww.add(exam_to_wastewater_structure) + + print(".", end="") + + def _import_normschachtschaden(self): + for row in self.session_interlis.query(self.model_classes_interlis.normschachtschaden): + damage_manhole = self.create_or_update( + self.model_classes_tww_od.damage_manhole, + **self.base_common(row), + # --- damage --- + # to check Adaption VSA-KEK 2020 moved to superclass schaden + comments=row.anmerkung, + # to check Adaption VSA-KEK 2020 moved to superclass schaden + connection=self.get_vl_code( + self.model_classes_tww_vl.damage_connection, row.verbindung + ), + manhole_damage_begin=row.schadenlageanfang, + manhole_damage_end=row.schadenlageende, + line_damage=row.streckenschaden, + manhole_distance=row.distanz, + fk_examination=self.get_pk(row.untersuchungref__REL), + manhole_quantification1=row.quantifizierung1, + manhole_quantification2=row.quantifizierung2, + # to check Adaption VSA-KEK 2020 moved to superclass schaden + single_damage_class=self.get_vl_code( + self.model_classes_tww_od.damage_single_damage_class, row.einzelschadenklasse + ), + # to check Adaption VSA-KEK 2020 moved to superclass schaden + video_counter=row.videozaehlerstand, + # to check Adaption VSA-KEK 2020 moved to superclass schaden + view_parameters=row.ansichtsparameter, + # --- damage_manhole --- + manhole_damage_code=self.get_vl_code( + self.model_classes_tww_od.damage_manhole_manhole_damage_code, + row.schachtschadencode, + ), + manhole_shaft_area=self.get_vl_code( + self.model_classes_tww_od.damage_manhole_manhole_shaft_area, row.schachtbereich + ), + ) + self.session_tww.add(damage_manhole) + print(".", end="") + + def _import_kanalschaden(self): + for row in self.session_interlis.query(self.model_classes_interlis.kanalschaden): + # Note : in TWW, some attributes are on the base damage class, + # while they are on the normschachtschaden/kanalschaden subclasses + # in the ili2pg mode. + # Concerned attributes : distanz, quantifizierung1, quantifizierung2, schadenlageanfang, schadenlageende + damage_channel = self.create_or_update( + self.model_classes_tww_od.damage_channel, + **self.base_common(row), + # --- damage --- + comments=row.anmerkung, + connection=self.get_vl_code( + self.model_classes_tww_vl.damage_connection, row.verbindung + ), + channel_damage_begin=row.schadenlageanfang, + channel_damage_end=row.schadenlageende, + line_damage=row.streckenschaden, + channel_distance=row.distanz, + fk_examination=self.get_pk(row.untersuchungref__REL), + channel_quantification1=row.quantifizierung1, + channel_quantification2=row.quantifizierung2, + single_damage_class=self.get_vl_code( + self.model_classes_tww_od.damage_single_damage_class, row.einzelschadenklasse + ), + video_counter=row.videozaehlerstand, + view_parameters=row.ansichtsparameter, + # --- damage_channel --- + channel_damage_code=self.get_vl_code( + self.model_classes_tww_od.damage_channel_channel_damage_code, + row.kanalschadencode, + ), + ) + self.session_tww.add(damage_channel) + print(".", end="") + + def _import_datentraeger(self): + for row in self.session_interlis.query(self.model_classes_interlis.datentraeger): + data_media = self.create_or_update( + self.model_classes_tww_od.data_media, + **self.base_common(row), + # --- data_media --- + identifier=row.bezeichnung, + kind=self.get_vl_code(self.model_classes_tww_vl.data_media_kind, row.art), + location=row.standort, + path=row.pfad, + remark=row.bemerkung, + ) + self.session_tww.add(data_media) + print(".", end="") + + def _import_datei(self): + for row in self.session_interlis.query(self.model_classes_interlis.datei): + file_table_row = self.create_or_update( + self.model_classes_tww_od.file, + **self.base_common(row), + # --- file --- + fk_data_media=self.get_pk(row.datentraegerref__REL), + identifier=row.bezeichnung, + kind=self.get_vl_code(self.model_classes_tww_vl.file_kind, row.art), + object=row.objekt, + path_relative=row.relativpfad, + remark=row.bemerkung, + classname=self.get_vl_code(self.model_classes_tww_vl.file_classname, row.klasse), + ) + + self.session_tww.add(file_table_row) + print(".", end="") + + def _import_erhaltungsereignis_abwasserbauwerkassoc(self): + for row in self.session_interlis.query( + self.model_classes_interlis.erhaltungsereignis_abwasserbauwerkassoc + ): + + re_maintenance_event_wastewater_structure = self.create_or_update( + self.model_classes_tww_od.re_maintenance_event_wastewater_structure, + # this class does not inherit base_commmon + # **self.base_common(row), + # --- re_maintenance_event_wastewater_structure --- + fk_maintenance_event=self.get_pk( + row.erhaltungsereignis_abwasserbauwerkassocref__REL + ), + fk_wastewater_structure=self.get_pk(row.abwasserbauwerkref__REL), + ) + + self.session_tww.add(re_maintenance_event_wastewater_structure) + print(".", end="") + + def _import_gebaeudegruppe_entsorgungassoc(self): + for row in self.session_interlis.query( + self.model_classes_interlis.gebaeudegruppe_entsorgungassoc + ): + re_building_group_disposal = self.create_or_update( + self.model_classes_tww_od.re_building_group_disposal, + # this class does not inherit base_commmon + # **self.base_common(row), + # --- re_building_group_disposal --- + fk_building_group=self.get_pk(row.gebaeudegruppe_entsorgungassocref__REL), + fk_disposal=self.get_pk(row.entsorgungref__REL), + ) + + self.session_tww.add(re_building_group_disposal) + print(".", end="") + + def _check_for_stop(self): + if self.callback_progress_done: + self.callback_progress_done() diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_base.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_base.py new file mode 100644 index 0000000..ad05e33 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_base.py @@ -0,0 +1,20 @@ +from sqlalchemy.ext.automap import automap_base + +from ..utils import tww_sqlalchemy + + +class ModelBase: + """ + Base class for datamodels + All tables will be loaded from the respective schemas (pg2ili, tww_od, tww_vl) as a SqlAlchemy ORM class. + Only table specific relationships (e.g. inheritance) need to be manually + defined here. Other attributes will be loaded automatically. + """ + + def __init__(self, schema): + self.Base = automap_base() + self.schema = schema + + def classes(self): + tww_sqlalchemy.prepare_automap_base(self.Base, self.schema) + return self.Base.classes diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_dss.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_dss.py new file mode 100644 index 0000000..a8a8672 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_dss.py @@ -0,0 +1,351 @@ +from .. import config +from .model_interlis_sia405_abwasser import ModelInterlisSia405Abwasser + + +class ModelInterlisDss(ModelInterlisSia405Abwasser): + def __init__(self): + super().__init__() + + class anschlussobjekt(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "anschlussobjekt" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.anschlussobjekt = anschlussobjekt + + class erhaltungsereignis(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "erhaltungsereignis" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.erhaltungsereignis = erhaltungsereignis + + class oberflaechenabflussparameter(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "oberflaechenabflussparameter" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.oberflaechenabflussparameter = oberflaechenabflussparameter + + class ueberlauf(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "ueberlauf" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.ueberlauf = ueberlauf + + class zone(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "azone" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.zone = zone + + class abwasserreinigungsanlage(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "abwasserreinigungsanlage" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.abwasserreinigungsanlage = abwasserreinigungsanlage + + class araenergienutzung(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "araenergienutzung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.araenergienutzung = araenergienutzung + + class abwasserbehandlung(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "abwasserbehandlung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.abwasserbehandlung = abwasserbehandlung + + class schlammbehandlung(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "schlammbehandlung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.schlammbehandlung = schlammbehandlung + + class arabauwerk(ModelInterlisSia405Abwasser.abwasserbauwerk): + __tablename__ = "arabauwerk" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.arabauwerk = arabauwerk + + class steuerungszentrale(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "steuerungszentrale" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.steuerungszentrale = steuerungszentrale + + class abflusslose_toilette(ModelInterlisSia405Abwasser.abwasserbauwerk): + __tablename__ = "abflusslose_toilette" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.abflusslose_toilette = abflusslose_toilette + + class absperr_drosselorgan(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "absperr_drosselorgan" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.absperr_drosselorgan = absperr_drosselorgan + + class beckenentleerung(ModelInterlisSia405Abwasser.bauwerksteil): + __tablename__ = "beckenentleerung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.beckenentleerung = beckenentleerung + + class beckenreinigung(ModelInterlisSia405Abwasser.bauwerksteil): + __tablename__ = "beckenreinigung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.beckenreinigung = beckenreinigung + + class biol_oekol_gesamtbeurteilung(erhaltungsereignis): + __tablename__ = "biol_oekol_gesamtbeurteilung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.biol_oekol_gesamtbeurteilung = biol_oekol_gesamtbeurteilung + + class brunnen(anschlussobjekt): + __tablename__ = "brunnen" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.brunnen = brunnen + + class ezg_parameter_allg(oberflaechenabflussparameter): + __tablename__ = "ezg_parameter_allg" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.ezg_parameter_allg = ezg_parameter_allg + + class ezg_parameter_mouse1(oberflaechenabflussparameter): + __tablename__ = "ezg_parameter_mouse1" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.ezg_parameter_mouse1 = ezg_parameter_mouse1 + + class einzelflaeche(anschlussobjekt): + __tablename__ = "einzelflaeche" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.einzelflaeche = einzelflaeche + + class einzugsgebiet(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "einzugsgebiet" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.einzugsgebiet = einzugsgebiet + + class einzugsgebiet_text(ModelInterlisSia405Abwasser.sia405_textpos): + __tablename__ = "einzugsgebiet_text" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.einzugsgebiet_text = einzugsgebiet_text + + class elektrischeeinrichtung(ModelInterlisSia405Abwasser.bauwerksteil): + __tablename__ = "elektrischeeinrichtung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.elektrischeeinrichtung = elektrischeeinrichtung + + class elektromechanischeausruestung(ModelInterlisSia405Abwasser.bauwerksteil): + __tablename__ = "elektromechanischeausruestung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.elektromechanischeausruestung = elektromechanischeausruestung + + class entsorgung(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "entsorgung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.entsorgung = entsorgung + + class entwaesserungssystem(zone): + __tablename__ = "entwaesserungssystem" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.entwaesserungssystem = entwaesserungssystem + + class feststoffrueckhalt(ModelInterlisSia405Abwasser.bauwerksteil): + __tablename__ = "feststoffrueckhalt" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.feststoffrueckhalt = feststoffrueckhalt + + class foerderaggregat(ueberlauf): + __tablename__ = "foerderaggregat" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.foerderaggregat = foerderaggregat + + class gebaeude(anschlussobjekt): + __tablename__ = "gebaeude" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.gebaeude = gebaeude + + class gebaeudegruppe(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "gebaeudegruppe" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.gebaeudegruppe = gebaeudegruppe + + class gebaeudegruppe_baugwr(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "gebaeudegruppe_baugwr" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.gebaeudegruppe_baugwr = gebaeudegruppe_baugwr + + class gesamteinzugsgebiet(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "gesamteinzugsgebiet" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.gesamteinzugsgebiet = gesamteinzugsgebiet + + class hq_relation(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "hq_relation" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.hq_relation = hq_relation + + class hydr_geomrelation(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "hydr_geomrelation" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.hydr_geomrelation = hydr_geomrelation + + class hydr_geometrie(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "hydr_geometrie" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.hydr_geometrie = hydr_geometrie + + class hydr_kennwerte(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "hydr_kennwerte" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.hydr_kennwerte = hydr_kennwerte + + class klara(ModelInterlisSia405Abwasser.abwasserbauwerk): + __tablename__ = "klara" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.klara = klara + + class landwirtschaftsbetrieb(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "landwirtschaftsbetrieb" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.landwirtschaftsbetrieb = landwirtschaftsbetrieb + + class leapingwehr(ueberlauf): + __tablename__ = "leapingwehr" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.leapingwehr = leapingwehr + + class massnahme(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "massnahme" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.massnahme = massnahme + + class mechanischevorreinigung(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "mechanischevorreinigung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.mechanischevorreinigung = mechanischevorreinigung + + class messgeraet(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "messgeraet" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.messgeraet = messgeraet + + class messreihe(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "messreihe" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.messreihe = messreihe + + class messresultat(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "messresultat" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.messresultat = messresultat + + class messstelle(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "messstelle" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.messstelle = messstelle + + class mutation(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "mutation" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.mutation = mutation + + class reservoir(anschlussobjekt): + __tablename__ = "reservoir" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.reservoir = reservoir + + class retentionskoerper(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "retentionskoerper" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.retentionskoerper = retentionskoerper + + class rohrprofil_geometrie(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "rohrprofil_geometrie" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.rohrprofil_geometrie = rohrprofil_geometrie + + class rueckstausicherung(ModelInterlisSia405Abwasser.bauwerksteil): + __tablename__ = "rueckstausicherung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.rueckstausicherung = rueckstausicherung + + class stammkarte(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "stammkarte" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.stammkarte = stammkarte + + class streichwehr(ueberlauf): + __tablename__ = "streichwehr" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.streichwehr = streichwehr + + class ueberlaufcharakteristik(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "ueberlaufcharakteristik" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.ueberlaufcharakteristik = ueberlaufcharakteristik + + class unterhalt(erhaltungsereignis): + __tablename__ = "unterhalt" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.unterhalt = unterhalt + + class versickerungsbereich(zone): + __tablename__ = "versickerungsbereich" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.versickerungsbereich = versickerungsbereich + + class erhaltungsereignis_abwasserbauwerkassoc(self.Base): + __tablename__ = "erhaltungsereignis_abwasserbauwerkassoc" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.erhaltungsereignis_abwasserbauwerkassoc = ( + erhaltungsereignis_abwasserbauwerkassoc + ) + + class gebaeudegruppe_entsorgungassoc(self.Base): + __tablename__ = "gebaeudegruppe_entsorgungassoc" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisDss.gebaeudegruppe_entsorgungassoc = gebaeudegruppe_entsorgungassoc diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_sia405_abwasser.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_sia405_abwasser.py new file mode 100644 index 0000000..550d2a5 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_sia405_abwasser.py @@ -0,0 +1,191 @@ +from .. import config +from .model_base import ModelBase + + +class ModelInterlisSia405Abwasser(ModelBase): + def __init__(self): + super().__init__(config.ABWASSER_SCHEMA) + + class baseclass(self.Base): + __tablename__ = "baseclass" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.baseclass = baseclass + + class sia405_baseclass(baseclass): + __tablename__ = "sia405_baseclass" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.sia405_baseclass = sia405_baseclass + + class vsa_baseclass(sia405_baseclass): + __tablename__ = "vsa_baseclass" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.vsa_baseclass = vsa_baseclass + + class organisation(sia405_baseclass): + __tablename__ = "organisation" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.organisation = organisation + + class abwasserbauwerk(vsa_baseclass): + __tablename__ = "abwasserbauwerk" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.abwasserbauwerk = abwasserbauwerk + + class kanal(abwasserbauwerk): + __tablename__ = "kanal" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.kanal = kanal + + class normschacht(abwasserbauwerk): + __tablename__ = "normschacht" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.normschacht = normschacht + + class einleitstelle(abwasserbauwerk): + __tablename__ = "einleitstelle" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.einleitstelle = einleitstelle + + class spezialbauwerk(abwasserbauwerk): + __tablename__ = "spezialbauwerk" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.spezialbauwerk = spezialbauwerk + + class versickerungsanlage(abwasserbauwerk): + __tablename__ = "versickerungsanlage" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.versickerungsanlage = versickerungsanlage + + class rohrprofil(vsa_baseclass): + __tablename__ = "rohrprofil" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.rohrprofil = rohrprofil + + class abwassernetzelement(vsa_baseclass): + __tablename__ = "abwassernetzelement" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.abwassernetzelement = abwassernetzelement + + class haltungspunkt(vsa_baseclass): + __tablename__ = "haltungspunkt" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.haltungspunkt = haltungspunkt + + class abwasserknoten(abwassernetzelement): + __tablename__ = "abwasserknoten" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.abwasserknoten = abwasserknoten + + class haltung(abwassernetzelement): + __tablename__ = "haltung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.haltung = haltung + + class haltung_alternativverlauf(baseclass): + __tablename__ = "haltung_alternativverlauf" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.haltung_alternativverlauf = haltung_alternativverlauf + + class bauwerksteil(vsa_baseclass): + __tablename__ = "bauwerksteil" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.bauwerksteil = bauwerksteil + + class trockenwetterfallrohr(bauwerksteil): + __tablename__ = "trockenwetterfallrohr" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.trockenwetterfallrohr = trockenwetterfallrohr + + class einstiegshilfe(bauwerksteil): + __tablename__ = "einstiegshilfe" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.einstiegshilfe = einstiegshilfe + + class trockenwetterrinne(bauwerksteil): + __tablename__ = "trockenwetterrinne" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.trockenwetterrinne = trockenwetterrinne + + class deckel(bauwerksteil): + __tablename__ = "deckel" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.deckel = deckel + + class bankett(bauwerksteil): + __tablename__ = "bankett" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.bankett = bankett + + class spuelstutzen(bauwerksteil): + __tablename__ = "spuelstutzen" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.spuelstutzen = spuelstutzen + + # TEXTS + + class textpos(baseclass): + __tablename__ = "textpos" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.textpos = textpos + + class sia405_textpos(textpos): + __tablename__ = "sia405_textpos" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.sia405_textpos = sia405_textpos + + class haltung_text(sia405_textpos): + __tablename__ = "haltung_text" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.haltung_text = haltung_text + + class abwasserbauwerk_text(sia405_textpos): + __tablename__ = "abwasserbauwerk_text" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.abwasserbauwerk_text = abwasserbauwerk_text + + # SymbolPos + + class symbolpos(baseclass): + __tablename__ = "symbolpos" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.symbolpos = symbolpos + + class sia405_symbolpos(symbolpos): + __tablename__ = "sia405_symbolpos" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.sia405_symbolpos = sia405_symbolpos + + class abwasserbauwerk_symbol(sia405_symbolpos): + __tablename__ = "abwasserbauwerk_symbol" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisSia405Abwasser.abwasserbauwerk_symbol = abwasserbauwerk_symbol diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_vsa_kek.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_vsa_kek.py new file mode 100644 index 0000000..767e74d --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_interlis_vsa_kek.py @@ -0,0 +1,49 @@ +from .. import config +from .model_interlis_sia405_abwasser import ModelInterlisSia405Abwasser + + +class ModelInterlisVsaKek(ModelInterlisSia405Abwasser): + def __init__(self): + super().__init__() + + class erhaltungsereignis(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "erhaltungsereignis" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisVsaKek.erhaltungsereignis = erhaltungsereignis + + class untersuchung(ModelInterlisVsaKek.erhaltungsereignis): + __tablename__ = "untersuchung" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisVsaKek.untersuchung = untersuchung + + class schaden(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "schaden" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisVsaKek.schaden = schaden + + class normschachtschaden(schaden): + __tablename__ = "normschachtschaden" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisVsaKek.normschachtschaden = normschachtschaden + + class kanalschaden(schaden): + __tablename__ = "kanalschaden" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisVsaKek.kanalschaden = kanalschaden + + class datentraeger(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "datentraeger" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisVsaKek.datentraeger = datentraeger + + class datei(ModelInterlisSia405Abwasser.vsa_baseclass): + __tablename__ = "datei" + __table_args__ = {"schema": config.ABWASSER_SCHEMA} + + ModelInterlisVsaKek.datei = datei diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_tww.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_tww.py new file mode 100644 index 0000000..b62102e --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_tww.py @@ -0,0 +1,12 @@ +from ...interlis import config +from .model_base import ModelBase + + +class ModelTwwVl(ModelBase): + def __init__(self): + super().__init__(config.TWW_VL_SCHEMA) + + +class ModelTwwSys(ModelBase): + def __init__(self): + super().__init__(config.TWW_SYS_SCHEMA) diff --git a/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_tww_od.py b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_tww_od.py new file mode 100644 index 0000000..87d3c8e --- /dev/null +++ b/plugin/teksi_wastewater/interlis/interlis_model_mapping/model_tww_od.py @@ -0,0 +1,330 @@ +from ...interlis import config +from .model_base import ModelBase + + +class ModelTwwOd(ModelBase): + def __init__(self): + super().__init__(config.TWW_OD_SCHEMA) + + class wastewater_structure(self.Base): + __tablename__ = "wastewater_structure" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.wastewater_structure = wastewater_structure + + class channel(wastewater_structure): + __tablename__ = "channel" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.channel = channel + + class manhole(wastewater_structure): + __tablename__ = "manhole" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.manhole = manhole + + class discharge_point(wastewater_structure): + __tablename__ = "discharge_point" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.discharge_point = discharge_point + + class special_structure(wastewater_structure): + __tablename__ = "special_structure" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.special_structure = special_structure + + class infiltration_installation(wastewater_structure): + __tablename__ = "infiltration_installation" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.infiltration_installation = infiltration_installation + + class wastewater_networkelement(self.Base): + __tablename__ = "wastewater_networkelement" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.wastewater_networkelement = wastewater_networkelement + + class reach_point(self.Base): + __tablename__ = "reach_point" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.reach_point = reach_point + + class reach_progression_alternative(self.Base): + __tablename__ = "reach_progression_alternative" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.reach_progression_alternative = reach_progression_alternative + + class wastewater_node(wastewater_networkelement): + __tablename__ = "wastewater_node" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.wastewater_node = wastewater_node + + class reach(wastewater_networkelement): + __tablename__ = "reach" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.reach = reach + + # class structure_part(wastewater_structure): + class structure_part(self.Base): + __tablename__ = "structure_part" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.structure_part = structure_part + + class dryweather_downspout(structure_part): + __tablename__ = "dryweather_downspout" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.dryweather_downspout = dryweather_downspout + + class access_aid(structure_part): + __tablename__ = "access_aid" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.access_aid = access_aid + + class dryweather_flume(structure_part): + __tablename__ = "dryweather_flume" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.dryweather_flume = dryweather_flume + + class cover(structure_part): + __tablename__ = "cover" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.cover = cover + + class benching(structure_part): + __tablename__ = "benching" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.benching = benching + + class wwtp_structure(wastewater_structure): + __tablename__ = "wwtp_structure" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.wwtp_structure = wwtp_structure + + # VSA_KEK + + class maintenance_event(self.Base): + __tablename__ = "maintenance_event" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.maintenance_event = maintenance_event + + class examination(maintenance_event): + __tablename__ = "examination" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.examination = examination + + class damage(self.Base): + __tablename__ = "damage" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.damage = damage + + class damage_manhole(damage): + __tablename__ = "damage_manhole" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.damage_manhole = damage_manhole + + class damage_channel(damage): + __tablename__ = "damage_channel" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.damage_channel = damage_channel + + class flushing_nozzle(structure_part): + __tablename__ = "flushing_nozzle" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.flushing_nozzle = flushing_nozzle + + class drainless_toilet(wastewater_structure): + __tablename__ = "drainless_toilet" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.drainless_toilet = drainless_toilet + + class tank_emptying(structure_part): + __tablename__ = "tank_emptying" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.tank_emptying = tank_emptying + + class tank_cleaning(structure_part): + __tablename__ = "tank_cleaning" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.tank_cleaning = tank_cleaning + + class bio_ecol_assessment(maintenance_event): + __tablename__ = "bio_ecol_assessment" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.bio_ecol_assessment = bio_ecol_assessment + + class maintenance(maintenance_event): + __tablename__ = "maintenance" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.maintenance = maintenance + + class connection_object(self.Base): + __tablename__ = "connection_object" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.connection_object = connection_object + + class fountain(connection_object): + __tablename__ = "fountain" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.fountain = fountain + + class reservoir(connection_object): + __tablename__ = "reservoir" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.reservoir = reservoir + + class building(connection_object): + __tablename__ = "building" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.building = building + + class individual_surface(connection_object): + __tablename__ = "individual_surface" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.individual_surface = individual_surface + + class catchment_area(self.Base): + __tablename__ = "catchment_area" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.catchment_area = catchment_area + + class surface_runoff_parameters(self.Base): + __tablename__ = "surface_runoff_parameters" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.surface_runoff_parameters = surface_runoff_parameters + + class param_ca_general(surface_runoff_parameters): + __tablename__ = "param_ca_general" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.param_ca_general = param_ca_general + + class param_ca_mouse1(surface_runoff_parameters): + __tablename__ = "param_ca_mouse1" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.param_ca_mouse1 = param_ca_mouse1 + + class electric_equipment(structure_part): + __tablename__ = "electric_equipment" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.electric_equipment = electric_equipment + + class electromechanical_equipment(structure_part): + __tablename__ = "electromechanical_equipment" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.electromechanical_equipment = electromechanical_equipment + + class zone(self.Base): + __tablename__ = "zone" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.zone = zone + + class drainage_system(zone): + __tablename__ = "drainage_system" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.drainage_system = drainage_system + + class infiltration_zone(zone): + __tablename__ = "infiltration_zone" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.infiltration_zone = infiltration_zone + + class solids_retention(structure_part): + __tablename__ = "solids_retention" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.solids_retention = solids_retention + + class overflow(self.Base): + __tablename__ = "overflow" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.overflow = overflow + + class pump(overflow): + __tablename__ = "pump" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.pump = pump + + class leapingweir(overflow): + __tablename__ = "leapingweir" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.leapingweir = leapingweir + + class prank_weir(overflow): + __tablename__ = "prank_weir" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.prank_weir = prank_weir + + class small_treatment_plant(wastewater_structure): + __tablename__ = "small_treatment_plant" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.small_treatment_plant = small_treatment_plant + + class backflow_prevention(structure_part): + __tablename__ = "backflow_prevention" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.backflow_prevention = backflow_prevention + + class file(self.Base): + __tablename__ = "file" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.file = file + + class re_maintenance_event_wastewater_structure(self.Base): + __tablename__ = "re_maintenance_event_wastewater_structure" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.re_maintenance_event_wastewater_structure = ( + re_maintenance_event_wastewater_structure + ) + + class re_building_group_disposal(self.Base): + __tablename__ = "re_building_group_disposal" + __table_args__ = {"schema": config.TWW_OD_SCHEMA} + + ModelTwwOd.re_building_group_disposal = re_building_group_disposal diff --git a/plugin/teksi_wastewater/interlis/processing_algs/__init__.py b/plugin/teksi_wastewater/interlis/processing_algs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugin/teksi_wastewater/interlis/processing_algs/extractlabels_interlis.py b/plugin/teksi_wastewater/interlis/processing_algs/extractlabels_interlis.py new file mode 100644 index 0000000..45355ae --- /dev/null +++ b/plugin/teksi_wastewater/interlis/processing_algs/extractlabels_interlis.py @@ -0,0 +1,223 @@ +import json +import os +import tempfile +import uuid +from functools import partial + +from PyQt5.QtCore import QCoreApplication +from qgis import processing +from qgis.core import ( + QgsProcessingContext, + QgsProcessingFeedback, + QgsProcessingParameterBoolean, + QgsProcessingParameterEnum, + QgsProcessingParameterFileDestination, + QgsProcessingParameterVectorLayer, + QgsWkbTypes, +) + +from ...processing_provider.tww_algorithm import TwwAlgorithm +from ...utils.twwlayermanager import TwwLayerManager + + +class ExtractlabelsInterlisAlgorithm(TwwAlgorithm): + """This runs the native extractlabels algorithm for the given scales, and attaches obj_id and scale to the results. + + Otherwise, the output is difficult to use, because the FeatureID isn't stable (since it's the primary + key of the reach and structure views is not an integer)""" + + tr = partial(QCoreApplication.translate, "ExtractlabelsInterlisAlgorithm") + AVAILABLE_SCALE_PIPELINE_REGISTRY_1_1000 = "Leitungskataster" + AVAILABLE_SCALE_NETWORK_PLAN_1_500 = "Werkplan" + AVAILABLE_SCALE_OVERVIEWMAP_1_10000 = "Uebersichtsplan.UeP10" + AVAILABLE_SCALE_OVERVIEWMAP_1_5000 = "Uebersichtsplan.UeP5" + AVAILABLE_SCALE_OVERVIEWMAP_1_2000 = "Uebersichtsplan.UeP2" + AVAILABLE_SCALES = [ + # ili key, display name, scale value + ( + AVAILABLE_SCALE_PIPELINE_REGISTRY_1_1000, + tr("Leitungskataster"), + 1000, + ), # TODO: check scale ? + (AVAILABLE_SCALE_NETWORK_PLAN_1_500, tr("Werkplan"), 500), # TODO: check scale ? + (AVAILABLE_SCALE_OVERVIEWMAP_1_10000, tr("Uebersichtsplan 1:10000"), 10000), + (AVAILABLE_SCALE_OVERVIEWMAP_1_5000, tr("Uebersichtsplan 1:5000"), 5000), + (AVAILABLE_SCALE_OVERVIEWMAP_1_2000, tr("Uebersichtsplan 1:2000"), 2000), + ] + + OUTPUT = "OUTPUT" + INPUT_RESTRICT_TO_SELECTION = "RESTRICT_TO_SELECTION" + INPUT_SCALES = "SCALES" + INPUT_STRUCTURE_VIEW_LAYER = "STRUCTURE_VIEW_LAYER" + INPUT_REACH_VIEW_LAYER = "REACH_VIEW_LAYER" + + def name(self): + return "extractlabels_interlis" + + def displayName(self): + return self.tr("Extract labels for interlis") + + def initAlgorithm(self, config=None): + self.addParameter( + QgsProcessingParameterFileDestination( + self.OUTPUT, + description=self.tr("Output labels file"), + fileFilter="geojson (*.geojson)", + ) + ) + + self.addParameter( + QgsProcessingParameterBoolean( + self.INPUT_RESTRICT_TO_SELECTION, + description=self.tr("Restrict to selection"), + ) + ) + + self.addParameter( + QgsProcessingParameterEnum( + self.INPUT_SCALES, + description=self.tr("Scales to export"), + allowMultiple=True, + options=[f"{self.tr(i[1])} [1:{i[2]}]" for i in self.AVAILABLE_SCALES], + defaultValue=[i for i in range(len(self.AVAILABLE_SCALES))], + ) + ) + + self.addParameter( + QgsProcessingParameterVectorLayer( + self.INPUT_STRUCTURE_VIEW_LAYER, + description=self.tr("Structure view layer"), + types=[QgsWkbTypes.PointGeometry], + ) + ) + + self.addParameter( + QgsProcessingParameterVectorLayer( + self.INPUT_REACH_VIEW_LAYER, + description=self.tr("Reach view layer"), + types=[QgsWkbTypes.LineGeometry], + ) + ) + + def processAlgorithm( + self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ): + labels_file_path = self.parameterAsFileOutput(parameters, self.OUTPUT, context) + restrict_to_selection = self.parameterAsBoolean( + parameters, self.INPUT_RESTRICT_TO_SELECTION, context + ) + structure_view_layer = self.parameterAsVectorLayer( + parameters, self.INPUT_STRUCTURE_VIEW_LAYER, context + ) + reach_view_layer = self.parameterAsVectorLayer( + parameters, self.INPUT_REACH_VIEW_LAYER, context + ) + scales = [ + self.AVAILABLE_SCALES[i] + for i in self.parameterAsEnums(parameters, self.INPUT_SCALES, context) + ] + + # Clear the output file if it exists + if os.path.isfile(labels_file_path): + os.remove(labels_file_path) + + # Compute the extent + if restrict_to_selection: + extent = structure_view_layer.boundingBoxOfSelected() + extent.combineExtentWith(reach_view_layer.boundingBoxOfSelected()) + else: + extent = structure_view_layer.extent() + extent.combineExtentWith(reach_view_layer.extent()) + + # Store a mapping from FeatureID to obj_id (used below) + reach_feats = reach_view_layer.getFeatures() + structure_feats = structure_view_layer.getFeatures() + rowid_to_obj_id = { + "vw_tww_reach": {f.id(): f.attribute("obj_id") for f in reach_feats}, + "vw_tww_wastewater_structure": { + f.id(): f.attribute("obj_id") for f in structure_feats + }, + } + + annotated_paths = [] + + # Extract all labels at all scales + for scale_id, scale_display, scale_value in scales: + # Make an unique temporary name + unique = uuid.uuid4().hex + extract_path = os.path.join( + tempfile.gettempdir(), f"labels-{scale_id}_{unique}.geojson" + ) + + # Extract the labels + feedback.pushInfo(f"Extracting labels for scale {scale_display} [1:{scale_value}]") + processing.run( + "native:extractlabels", + { + "DPI": 96, # TODO: check what this changes + "EXTENT": extent, + "INCLUDE_UNPLACED": True, + "MAP_THEME": None, + "OUTPUT": extract_path, + "SCALE": scale_value, + }, + # this is a child algotihm + is_child_algorithm=True, + context=context, + feedback=feedback, + ) + + # Load the extracted labels + with open(extract_path) as extract_path_handle: + geojson = json.load(extract_path_handle) + + # Check that labels were generated + labels_count = len(geojson["features"]) + feedback.pushInfo(f"{labels_count} labels generated") + if labels_count == 0: + continue + + # Annotate features with tww_obj_id and scal + lyr_name_to_key = { + TwwLayerManager.layer( + "vw_tww_wastewater_structure" + ).name(): "vw_tww_wastewater_structure", + TwwLayerManager.layer("vw_tww_reach").name(): "vw_tww_reach", + } + for label in geojson["features"]: + layer_name = label["properties"]["Layer"] + # this is a non-TWW layer, we don't annotate it + if layer_name not in lyr_name_to_key: + continue + lyr = lyr_name_to_key[layer_name] + rowid = label["properties"]["FeatureID"] + label["properties"]["Layer"] = lyr + label["properties"]["tww_obj_id"] = rowid_to_obj_id[lyr][rowid] + label["properties"]["scale"] = scale_id + + # Save + annotated_path = extract_path + ".annotated.geojson" + with open(annotated_path, "w") as annotated_path_handle: + json.dump(geojson, annotated_path_handle) + + annotated_paths.append(annotated_path) + + # time.sleep(60) + + # Merge all scales to one geojson + feedback.pushInfo(f"Merging all labels ({annotated_paths}) to {labels_file_path}") + processing.run( + "native:mergevectorlayers", + { + "LAYERS": annotated_paths, + "OUTPUT": labels_file_path, + }, + # this is a child algotihm + is_child_algorithm=True, + context=context, + feedback=feedback, + ) + + feedback.setProgress(100) + + return {} diff --git a/plugin/teksi_wastewater/interlis/tpl/export.py.tpl b/plugin/teksi_wastewater/interlis/tpl/export.py.tpl new file mode 100644 index 0000000..a57935f --- /dev/null +++ b/plugin/teksi_wastewater/interlis/tpl/export.py.tpl @@ -0,0 +1,65 @@ +from sqlalchemy.orm import Session +from geoalchemy2.functions import ST_Transform, ST_Force2D + +from .. import utils + +from .model_{{model_name}} import get_{{model_name}}_model +from .model_{{ilimodel_name}} import get_{{ilimodel_name}}_model + + +def {{model_name}}_export(): + + {{model_name|upper}} = get_{{model_name}}_model() + {{ilimodel_name|upper}} = get_{{ilimodel_name}}_model() + + {{model_name}}_session = Session(utils.tww_sqlalchemy.create_engine(), autocommit=False, autoflush=False) + {{ilimodel_name}}_session = Session(utils.tww_sqlalchemy.create_engine(), autocommit=False, autoflush=False) + tid_maker = utils.ili2db.TidMaker(id_attribute='obj_id') + + def create_metaattributes(row, session): + metaattribute = {{ilimodel_name|upper}}.metaattribute( + # FIELDS TO MAP TO {{ilimodel_name|upper}}.metaattribute + # --- metaattribute --- + # datenherr=row.REPLACE_ME, + # datenlieferant=row.REPLACE_ME, + # letzte_aenderung=row.REPLACE_ME, + # sia405_baseclass_metaattribute=row.REPLACE_ME, + # t_id=row.REPLACE_ME + # t_seq=row.REPLACE_ME, + ) + session.add(metaattribute) + +{% for class_from, classes_to in mapping.items() %} + print("Exporting {{model_name|upper}}.{{class_from.__name__}} -> {{classes_to|qualclassesnames}}") + for row in {{model_name}}_session.query({{model_name|upper}}.{{class_from.__name__}}): + +{% for src_table, fields in class_from|classfields %} + # {{src_table}} --- {{fields|join(", ")}} +{% endfor %} + +{% for class_to in classes_to %} +{% if class_to.__name__ == 'metaattribute' %} + create_metaattributes(row, session) +{% else %} + {{class_to.__name__}} = {{ilimodel_name|upper}}.{{class_to.__name__}}( +{% for dst_table, fields in class_to|classfields %} +{% if dst_table != '_rel_' and dst_table != '_bwrel_' %} + + # --- {{dst_table}} --- +{% for field in fields %} + # {{field.name}}=row.REPLACE_ME, # {{field.property.columns[0].type}} +{% endfor %} +{% endif %} +{% endfor %} + ) + {{ilimodel_name}}_session.add({{class_to.__name__}}) +{% endif %} +{% endfor %} + print(".", end="") + print("done") + +{% endfor %} + {{ilimodel_name}}_session.commit() + + {{model_name}}_session.close() + {{ilimodel_name}}_session.close() diff --git a/plugin/teksi_wastewater/interlis/tpl/import_.py.tpl b/plugin/teksi_wastewater/interlis/tpl/import_.py.tpl new file mode 100644 index 0000000..5632870 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/tpl/import_.py.tpl @@ -0,0 +1,51 @@ +from sqlalchemy.orm import Session +from geoalchemy2.functions import ST_Transform, ST_Force2D + +from .. import utils + +from .model_{{model_name}} import get_{{model_name}}_model +from .model_{{ilimodel_name}} import get_{{ilimodel_name}}_model + + +def {{model_name}}_import(): + + {{model_name|upper}} = get_{{model_name}}_model() + {{ilimodel_name|upper}} = get_{{ilimodel_name}}_model() + + {{ilimodel_name}}_session = Session(utils.tww_sqlalchemy.create_engine(), autocommit=False, autoflush=False) + {{model_name}}_session = Session(utils.tww_sqlalchemy.create_engine(), autocommit=False, autoflush=False) + +{% for class_to, classes_from in mapping.items() %} + print("Importing {{classes_from|qualclassesnames}} -> {{model_name|upper}}.{{class_to.__name__}}") +{% if classes_from|length == 1 %} + for row in {{ilimodel_name}}_session.query({{classes_from|qualclassesnames}}): +{% else %} + for row, {{classes_from[1:]|classesnames}} in {{ilimodel_name}}_session.query({{classes_from|qualclassesnames}}){% if classes_from|length > 1 %}.join({{classes_from[1:]|qualclassesnames}}){% endif %}: +{% endif %} + +{% for class_from in classes_from %} +{% for src_table, fields in class_from|classfields %} + # {{src_table}} --- {{fields|join(", ")}} +{% endfor %} + +{% endfor %} + {{class_to.__name__}} = {{model_name|upper}}.{{class_to.__name__}}( +{% for dst_table, fields in class_to|classfields %} +{% if dst_table != '_rel_' and dst_table != '_bwrel_' %} + + # --- {{dst_table}} --- +{% for field in fields %} + # {{field.name}}=row.REPLACE_ME, # {{field.property.columns[0].type}} +{% endfor %} +{% endif %} +{% endfor %} + ) + {{model_name}}_session.add({{class_to.__name__}}) + print(".", end="") + print("done") + +{% endfor %} + {{model_name}}_session.commit() + + {{model_name}}_session.close() + {{ilimodel_name}}_session.close() diff --git a/plugin/teksi_wastewater/interlis/tpl/mapping.py.tpl b/plugin/teksi_wastewater/interlis/tpl/mapping.py.tpl new file mode 100644 index 0000000..485c958 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/tpl/mapping.py.tpl @@ -0,0 +1,19 @@ +from .{{model_name}} import Classes as {{model_name|upper}} +from .{{ilimodel_name}} import Classes as {{ilimodel_name|upper}} + +{{model_name|upper}}_TO_{{ilimodel_name|upper}} = { + # ALREADY MAPPED +{% for class_from, classes_to in mapping.items() %} + {{model_name|upper}}.{{class_from.__name__}}: [{{classes_to|qualclassesnames}}], +{% endfor %} + + # AVAILABLE TABLES + # {{ILIMODEL|sort(attribute='__name__')|qualclassesnames}} + + # NOT YET MAPPED +{% for class_from in MODEL|sort(attribute='__name__') %} +{% if class_from not in mapping.keys() %} + # {{model_name|upper}}.{{class_from.__name__}}: [{{ilimodel_name|upper}}.REPLACE_ME], +{% endif %} +{% endfor %} +} diff --git a/plugin/teksi_wastewater/interlis/utils/__init__.py b/plugin/teksi_wastewater/interlis/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugin/teksi_wastewater/interlis/utils/ili2db.py b/plugin/teksi_wastewater/interlis/utils/ili2db.py new file mode 100644 index 0000000..570564d --- /dev/null +++ b/plugin/teksi_wastewater/interlis/utils/ili2db.py @@ -0,0 +1,196 @@ +import collections +import re +import xml.etree.ElementTree as ET +from types import SimpleNamespace + +from sqlalchemy.ext.automap import AutomapBase + +from ...libs.modelbaker.iliwrapper import globals, ili2dbutils +from .. import config +from .various import execute_subprocess, get_pgconf_as_ili_args, logger + + +class InterlisToolsException(Exception): + pass + + +class InterlisTools: + def __init__(self): + class BaseConfiguration: + java_path = None + + self.java_executable_path = ili2dbutils.get_java_path(BaseConfiguration()) + + stdout = SimpleNamespace() + stdout.emit = logger.info + stderr = SimpleNamespace() + stderr.emit = logger.error + + self.ili2pg_executable_path = ili2dbutils.get_ili2db_bin( + globals.DbIliMode.ili2pg, 4, stdout, stderr + ) + + def import_ili_schema( + self, schema, models, log_path, ext_columns_no_constraints=False, create_basket_col=False + ): + sql_ext_refs_cols = "" + if ext_columns_no_constraints: + sql_ext_refs_cols = "--sqlExtRefCols" + + create_basket_col_args = "" + if create_basket_col: + create_basket_col_args = "--createBasketCol" + + logger.info(f"ILIDB SCHEMAIMPORT INTO {schema}...") + execute_subprocess( + " ".join( + [ + f'"{self.java_executable_path}"', + "-jar", + f'"{self.ili2pg_executable_path}"', + "--schemaimport", + *get_pgconf_as_ili_args(), + "--dbschema", + f"{schema}", + "--setupPgExt", + "--createGeomIdx", + f"{sql_ext_refs_cols}", + "--createFk", + "--createFkIdx", + "--createTidCol", + "--importTid", + f"{create_basket_col_args}", + "--noSmartMapping", + "--defaultSrsCode", + "2056", + "--log", + f'"{log_path}"', + "--nameLang", + "de", + "--models", + f'"{";".join(models)}"', + ] + ) + ) + + def validate_xtf_data(self, xtf_file, log_path): + logger.info("VALIDATING XTF DATA...") + execute_subprocess( + f'"{self.java_executable_path}" -jar "{config.ILIVALIDATOR}" --log "{log_path}" "{xtf_file}"' + ) + + def import_xtf_data(self, schema, xtf_file, log_path): + logger.info("IMPORTING XTF DATA...") + execute_subprocess( + " ".join( + [ + f'"{self.java_executable_path}"', + "-jar", + f'"{self.ili2pg_executable_path}"', + "--import", + "--deleteData", + *get_pgconf_as_ili_args(), + "--dbschema", + f'"{schema}"', + "--disableValidation", + "--skipReferenceErrors", + "--createTidCol", + "--noSmartMapping", + "--defaultSrsCode", + "2056", + "--log", + f'"{log_path}"', + f'"{xtf_file}"', + ] + ) + ) + + def export_xtf_data(self, schema, xtf_file, log_path, model_name, export_model_name): + logger.info("EXPORT ILIDB...") + + # if optional export_model_name is set, add it to the args + if export_model_name: + export_model_name_args = ["--exportModels", export_model_name] + else: + export_model_name_args = [] + + execute_subprocess( + " ".join( + [ + f'"{self.java_executable_path}"', + "-jar", + f'"{self.ili2pg_executable_path}"', + "--export", + "--models", + f"{model_name}", + *export_model_name_args, + *get_pgconf_as_ili_args(), + "--dbschema", + f"{schema}", + "--disableValidation", + "--skipReferenceErrors", + "--createTidCol", + "--noSmartMapping", + "--defaultSrsCode", + "2056", + "--log", + f'"{log_path}"', + "--trace", + f'"{xtf_file}"', + ] + ) + ) + + @staticmethod + def get_xtf_models(xtf_file): + logger.info(f"GET XTF MODELS from {xtf_file}...") + + # from xml file + tree = ET.parse(xtf_file) + root = tree.getroot() + + def get_namespace(element): + m = re.match(r"\{.*\}", element.tag) + return m.group(0) if m else "" + + namespace = get_namespace(root) + + model_elements = root.findall("./{0}HEADERSECTION/{0}MODELS/{0}MODEL".format(namespace)) + + if not model_elements: + raise InterlisToolsException(f"Couldn't find any model into '{xtf_file}'") + + models = [] + for model_element in model_elements: + models.append(model_element.attrib.get("NAME", None)) + + return models + + +class TidMaker: + """ + Helper class that creates globally unique integer primary key forili2pg class (t_id) + from a a TWW id (obj_id or id). + """ + + def __init__(self, id_attribute="id"): + self._id_attr = id_attribute + self._autoincrementer = collections.defaultdict(lambda: len(self._autoincrementer)) + + def tid_for_row(self, row, for_class=None): + # tid are globally unique, while ids are only guaranteed unique per table, + # so include the base table in the key + # this finds the base class (the first parent class before sqlalchemy.ext.automap.Base) + class_for_id = row.__class__.__mro__[row.__class__.__mro__.index(AutomapBase) - 2] + key = (class_for_id, getattr(row, self._id_attr), for_class) + # was_created = key not in self._autoincrementer # just for debugging + tid = self._autoincrementer[key] + # if was_created: + # # just for debugging + # logger.info(f"created tid {tid} for {key}") + return tid + + def next_tid(self): + """Get an arbitrary unused tid""" + key = len(self._autoincrementer) + return self._autoincrementer[key] diff --git a/plugin/teksi_wastewater/interlis/utils/tww_sqlalchemy.py b/plugin/teksi_wastewater/interlis/utils/tww_sqlalchemy.py new file mode 100644 index 0000000..5ae54e3 --- /dev/null +++ b/plugin/teksi_wastewater/interlis/utils/tww_sqlalchemy.py @@ -0,0 +1,106 @@ +import logging + +import sqlalchemy +from sqlalchemy import inspect +from sqlalchemy.ext.automap import generate_relationship +from sqlalchemy.orm import ColumnProperty + +from ...utils.database_utils import DatabaseUtils + + +def create_engine(logger_name=None): + logging_args = {} + if logger_name: + handler = logging.FileHandler(f"tww_export.{logger_name}.log", mode="w") + handler.setLevel(logging.DEBUG) + logging.getLogger(f"sqlalchemy.engine.base.Engine.{logger_name}").addHandler(handler) + logging_args = {"logging_name": logger_name, "echo": True} + + pgconf = DatabaseUtils.get_pgconf() + + return sqlalchemy.create_engine( + f"postgresql://{pgconf['user']}:{pgconf['password']}@{pgconf['host']}:{pgconf['port']}/{pgconf['dbname']}", + **logging_args, + ) + + +def custom_name_for_collection_relationship(base, local_cls, referred_cls, constraint): + # This customizes the name for backwards relation (uses the class name and it's fk column name), avoiding clashes for inherited classes. + # See https://stackoverflow.com/a/48288656/13690651 + return f"{referred_cls.__name__}__BWREL_{constraint.columns.keys()[0]}" + + +def custom_name_for_scalar_relationship(base, local_cls, referred_cls, constraint): + # This customizes the name for backwards relation (uses the fk column name), avoiding clashes for inherited classes. + # See https://stackoverflow.com/a/48288656/13690651 + return f"{constraint.columns.keys()[0]}__REL" + + +def custom_generate_relationship( + base, direction, return_fn, attrname, local_cls, referred_cls, **kw +): + """ + Skips creating backwards relations to avoid adding instances twice with session.merge + """ + # disabling type checks on all relations, allowing to flush subclasses instead of abstract classes in relations + # without requiring to configure polymorphism + kw["enable_typechecks"] = False + + # accept circular-dependencies (e.g. organisation.dataowner can be itself) + # see https://docs.sqlalchemy.org/en/20/orm/relationship_persistence.html#rows-that-point-to-themselves-mutually-dependent-rows) + if attrname in ["fk_dataowner__REL", "fk_provider__REL"]: + kw["post_update"] = True + + return generate_relationship( + base, direction, return_fn, attrname, local_cls, referred_cls, **kw + ) + + +def prepare_automap_base(base, schema): + """ + Prepares the automap base by reflecting all the fields with some specific configuration for relationship and population Base.classes with manually defined classes (which for some reason isn't done by default) + """ + + # DOESN'T WORK, BUT CACHING WOULD BE GOOD + # pickle_file = f'{__file__}.pickled' + # reflect = True + # if os.path.exists(pickle_file): + # with open(pickle_file, 'rb') as f: + # reflect = False + # base.metadata = pickle.load(f) + + base.prepare( + create_engine(), + reflect=True, + schema=schema, + name_for_collection_relationship=custom_name_for_collection_relationship, + name_for_scalar_relationship=custom_name_for_scalar_relationship, + generate_relationship=custom_generate_relationship, + ) + + # For some reason, automap_base doesn't add manually defined classes to Base.classes, + # so we do it manually here + def add_subclasses(Parent): + for subclass in Parent.__subclasses__(): + if subclass.__name__ not in base.classes: + base.classes[subclass.__name__] = subclass + add_subclasses(subclass) + + add_subclasses(base) + + # DOESN'T WORK, BUT CACHING WOULD BE GOOD + # with open(pickle_file, 'wb') as f: + # pickle.dump(base.metadata, f) + + +def copy_instance(instance): + """ + Creates a copy of an SQLAchely ORM instance. Dont forget to change (or nullify) the primary key. + """ + klass = instance.__class__ + mapper = inspect(klass) + new_instance = klass() + for attr in mapper.attrs: + if isinstance(attr, ColumnProperty): + setattr(new_instance, attr.key, getattr(instance, attr.key)) + return new_instance diff --git a/plugin/teksi_wastewater/interlis/utils/various.py b/plugin/teksi_wastewater/interlis/utils/various.py new file mode 100644 index 0000000..e00ec5b --- /dev/null +++ b/plugin/teksi_wastewater/interlis/utils/various.py @@ -0,0 +1,75 @@ +import datetime +import os +import re +import subprocess +import tempfile +from typing import List + +from ...utils.database_utils import DatabaseUtils +from ...utils.plugin_utils import logger + + +class CmdException(BaseException): + pass + + +def execute_subprocess(command, check=True, output_content=False): + command_masked_pwd = re.sub(r"(--dbpwd)\s\"[\w\.*#?!@$%^&-]+\"", r'\1 "[PASSWORD]"', command) + logger.info(f"EXECUTING: {command_masked_pwd}") + try: + proc = subprocess.run( + command, + check=True, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError as e: + if check: + logger.exception(e.output.decode("windows-1252" if os.name == "nt" else "utf-8")) + raise CmdException("Command errored ! See logs for more info.") + return e.output if output_content else e.returncode + return proc.stdout.decode().strip() if output_content else proc.returncode + + +def get_pgconf_as_ili_args() -> List[str]: + """Returns the pgconf as a list of ili2db arguments""" + pgconf = DatabaseUtils.get_pgconf() + args = [] + if pgconf["host"]: + args.extend(["--dbhost", '"' + pgconf["host"] + '"']) + if pgconf["port"]: + args.extend(["--dbport", '"' + pgconf["port"] + '"']) + if pgconf["user"]: + args.extend(["--dbusr", '"' + pgconf["user"] + '"']) + if pgconf["password"]: + args.extend(["--dbpwd", '"' + pgconf["password"] + '"']) + if pgconf["dbname"]: + args.extend(["--dbdatabase", '"' + pgconf["dbname"] + '"']) + return args + + +def make_log_path(next_to_path, step_name): + """Returns a path for logging purposes. If next_to_path is None, it will be saved in the temp directory""" + now = f"{datetime.datetime.now():%y%m%d%H%M%S}" + if next_to_path: + return f"{next_to_path}.{now}.{step_name}.log" + else: + temp_path = os.path.join(tempfile.gettempdir(), "tww2ili") + os.makedirs(temp_path, exist_ok=True) + return os.path.join(temp_path, f"{now}.{step_name}.log") + + +class LoggingHandlerContext: + """Temporarily sets a log handler, then removes it""" + + def __init__(self, handler): + self.handler = handler + + def __enter__(self): + logger.addHandler(self.handler) + + def __exit__(self, et, ev, tb): + logger.removeHandler(self.handler) + self.handler.close() + # implicit return of None => don't swallow exceptions diff --git a/plugin/teksi_wastewater/metadata.txt b/plugin/teksi_wastewater/metadata.txt new file mode 100644 index 0000000..61f77c6 --- /dev/null +++ b/plugin/teksi_wastewater/metadata.txt @@ -0,0 +1,25 @@ + +[general] +name=TEKSI Wastewater +description=TEKSI Wastewater plugin to manage wastewater networks. +about=Provides selection, connection and editing tools. Requires a compatible TEKSI wastewater datamodel and project file. Supports Swiss standards SIA405 wastewater, VSA-DSS and VSA-KEK (2020.x) for INTERLIS import / export. + +qgisMinimumVersion=3.34.8 +category=Vector +version=2024.0.0 + +tags=teksi, gep, pgee, abwasser, wastewater, assainissement, network, sewer, infrastructure, management + +author=TEKSI open source contributors +email=info@teksi.ch +homepage=https://www.teksi.ch/modules/abwasser-gep/ +tracker=https://github.com/teksi/wastewater/issues +repository=https://github.com/teksi/wastewater +changelog=https://github.com/teksi/wastewater/releases + +icon=icons/teksi-abwasser-logo.svg + +plugin_dependencies=Linking Relation Editor,Document Management System + +experimental=False +deprecated=False diff --git a/plugin/teksi_wastewater/processing_provider/TwwSwmm.py b/plugin/teksi_wastewater/processing_provider/TwwSwmm.py new file mode 100644 index 0000000..ab6937b --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/TwwSwmm.py @@ -0,0 +1,1306 @@ +""" +/*************************************************************************** + TWW-swmm processing provider + ------------------- + begin : 07.2019 + copyright : (C) 2019 by ig-group.ch + email : timothee.produit@ig-group.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import codecs +import subprocess +from datetime import datetime, timedelta + +try: + import psycopg +except ImportError: + import psycopg2 as psycopg + +MEASURING_POINT_KIND = "Diverse kind of SWMM simulation parameters" +MEASURING_DEVICE_REMARK = "SWMM Simulation" + +SWMM_SUMMARY_PARAMETERS = {} +SWMM_SUMMARY_PARAMETERS["average_depth"] = { + "recorded": True, + "dimension": "m", + "tww_measurement_type": 5734, +} +SWMM_SUMMARY_PARAMETERS["maximum_depth"] = { + "recorded": True, + "dimension": "m", + "tww_measurement_type": 5734, +} +SWMM_SUMMARY_PARAMETERS["maximum_hgl"] = { + "recorded": True, + "dimension": "m", + "tww_measurement_type": 5732, +} +SWMM_SUMMARY_PARAMETERS["reported_max_depth"] = { + "recorded": True, + "dimension": "m", + "tww_measurement_type": 5734, +} +SWMM_SUMMARY_PARAMETERS["maximum_flow"] = { + "recorded": True, + "dimension": "l/s", + "tww_measurement_type": 5733, +} +SWMM_SUMMARY_PARAMETERS["maximum_velocity"] = { + "recorded": True, + "dimension": "m/s", + "tww_measurement_type": 5732, +} +SWMM_SUMMARY_PARAMETERS["max_over_full_flow"] = { + "recorded": True, + "dimension": "-", + "tww_measurement_type": 5733, +} +SWMM_SUMMARY_PARAMETERS["max_over_full_depth"] = { + "recorded": True, + "dimension": "-", + "tww_measurement_type": 5734, +} + +SWMM_RESULTS_PARAMETERS = {} +SWMM_RESULTS_PARAMETERS["flow"] = { + "recorded": True, + "dimension": "l/s", + "tww_measurement_type": 5733, +} +SWMM_RESULTS_PARAMETERS["velocity"] = { + "recorded": True, + "dimension": "m/s", + "tww_measurement_type": 5732, +} +SWMM_RESULTS_PARAMETERS["depth"] = { + "recorded": True, + "dimension": "m", + "tww_measurement_type": 5734, +} +SWMM_RESULTS_PARAMETERS["capacity"] = { + "recorded": True, + "dimension": "-", + "tww_measurement_type": 5732, +} +SWMM_RESULTS_PARAMETERS["inflow"] = { + "recorded": True, + "dimension": "l/s", + "tww_measurement_type": 5733, +} +SWMM_RESULTS_PARAMETERS["flooding"] = { + "recorded": True, + "dimension": "l/s", + "tww_measurement_type": 5733, +} +SWMM_RESULTS_PARAMETERS["head"] = { + "recorded": True, + "dimension": "m", + "tww_measurement_type": 5732, +} + +NON_PHYSICAL_REM = "Non-physical point which materializes swmm simulations" + + +class TwwSwmm: + def __init__(self, title, service, state, inpfile, inptemplate, rptfile, binfile, feedback): + """ + Initiate TwwSwmm + + Parameters: + title (string): Title of the simulation + service (string): name of the service to be used to connect to the TWW database + state (string): state for which the network is extracted (current or planned) + inpfile (path): path of the INP file (input file for swmm) + inptemplate (path): path of the INP file which store simulations parameters + rptfile (path): path of the OUT file which contains swmm results + binfile (path): path of the swmm executable + feedback (pyQGIS feedback) + """ + self.title = title + self.service = service + self.input_file = inpfile + self.options_template_file = inptemplate + self.rpt_file = rptfile + self.bin_file = binfile + self.feedback = feedback + self.state = state + + def __enter__(self): + if self.service is not None: + self.con = psycopg.connect(service=self.service) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.service is not None: + del self.con + + def feedback_push(self, level, message): + if self.feedback is not None and message != "" and message is not None: + if level == "info": + self.feedback.pushInfo(message) + elif level == "warning": + self.feedback.pushWarning(message) + elif level == "error": + self.feedback.reportError(message) + else: + self.feedback.pushInfo(message) + return + + def feedback_set_progress(self, progress): + if self.feedback is not None: + self.feedback.setProgress(progress) + return + + def get_swmm_table(self, table_name, state, selected_structures, hierarchy): + """ + Extract data from the swmm views in the database + + Parameters: + table_name (string): Name of the view or table + state (string): current or planned + selected_structures ([string]): List of obj_id of the selected structures + + Returns: + dic: table content + array: table attributes + + """ + + # Connects to service and get data and attributes from tableName + cur = self.con.cursor() + + # Configure the filters + where_clauses = [] + if state == "planned": + where_clauses.append("(state = 'planned' OR state = 'current')") + elif state == "current": + where_clauses.append("state = 'current'") + if selected_structures: + where_clauses.append( + """ + obj_id in ('{ids}') + """.format( + ids="','".join(selected_structures) + ) + ) + if hierarchy: + where_clauses.append(f"""hierarchy = '{hierarchy}'""") + + sql = """ + select * from tww_swmm.vw_{table_name} + """.format( + table_name=table_name + ) + # Add the filters to the sql + if len(where_clauses) > 0: + sql = """ + {sql} where {where_clauses} + """.format( + sql=sql, where_clauses=" AND ".join(where_clauses) + ) + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while executing: {sql}") + return None, None + self.feedback_push("info", f"Process vw_{table_name}") + data = cur.fetchall() + attributes = [desc[0] for desc in cur.description] + del cur + + return data, attributes + + def swmm_table(self, table_name, hierarchy=None, state=None, selected_structures=[]): + """ + Write swmm objects extracted from TWW in swmm input file. Selects according + to the state planned or current. If the object is a teksi wastewater structure + when the state is "planned" both "planned" and "operational" wastewater structures are selected + + Parameters: + table_name (string): Name of the swmm section + state (string): current or planned + selected_structre ([string]). List of obj_id of the selected wastewater structures + ws (boolean): if the origin table is a wastewater structure + + Returns: + String: table content + + """ + notPrintedFields = [ + "description", + "tag", + "geom", + "state", + "ws_obj_id", + "hierarchy", + "message", + ] + # Create commented line which contains the field names + fields = "" + data, attributes = self.get_swmm_table(table_name, state, selected_structures, hierarchy) + if data is not None: + for i, field in enumerate(attributes): + # Does not write values stored in columns descriptions, tags and geom + if field not in notPrintedFields: + fields += field + "\t" + + # Create input paragraph + tbl = "[" + table_name + "]\n" ";;" + fields + "\n" + for feature in data: + for i, v in enumerate(feature): + # Write description + if attributes[i] == "description" and v is not None: + tbl += ";" + tbl += str(v) + tbl += "\n" + + for i, v in enumerate(feature): + # Does not write values stored in columns descriptions, tags and geom + if attributes[i] not in notPrintedFields: + if v is not None: + tbl += str(v) + "\t" + else: + tbl += "\t" + if attributes[i] == "message" and v != "": + self.feedback_push("warning", v) + tbl += "\n" + tbl += "\n" + return tbl + else: + return "\n" + + def copy_parameters_from_template(self, parameter_name): + """ + Write swmm objects extracted from template in swmm input file + + Parameters: + parameter_name (string): Name of the swmm section to be copied + + Returns: + String: section content + + """ + # Read template + options_template = open(self.options_template_file).read() + # Find and extract options + index_start = options_template.find(f"[{parameter_name}]") + if index_start == -1: + # The balise options is not found + self.feedback_push( + "info", + "There is no {parameter_name} in the template file".format( + parameter_name=parameter_name + ), + ) + return "" + else: + # Search for the next opening bracket + index_stop = options_template[index_start + 1 :].find("[") + if index_stop == -1: + # Copies text until the end of the file + index_stop = len(options_template) + option_text = options_template[index_start:index_stop] + "\n\n" + else: + index_stop = index_start + 1 + index_stop + option_text = options_template[index_start:index_stop] + return option_text + + def write_input(self, hierarchy, selected_structures, selected_reaches): + """ + Write the swmm input file + + """ + + # From qgis swmm + filename = self.input_file + state = self.state + + selected_ws_re = None + if selected_structures and selected_reaches: + selected_ws_re = selected_structures + selected_reaches + + with codecs.open(filename, "w", encoding="utf-8") as f: + # Title / Notes + # -------------- + f.write("[TITLE]\n") + f.write(self.title + "\n\n") + + # Options + # -------- + f.write(self.copy_parameters_from_template("OPTIONS")) + f.write(self.copy_parameters_from_template("REPORT")) + f.write(self.copy_parameters_from_template("FILES")) + f.write(self.copy_parameters_from_template("EVENTS")) + + # Climatology + # ------------ + f.write(self.copy_parameters_from_template("HYDROGRAPHS")) + f.write(self.copy_parameters_from_template("EVAPORATION")) + f.write(self.copy_parameters_from_template("TEMPERATURE")) + + # Hydrology + # ---------- + self.feedback_set_progress(5) + f.write(self.swmm_table("RAINGAGES", hierarchy, state, selected_structures)) + f.write(self.swmm_table("SYMBOLS", hierarchy, state, selected_structures)) + self.feedback_set_progress(10) + f.write(self.swmm_table("SUBCATCHMENTS", hierarchy, state, selected_structures)) + self.feedback_set_progress(15) + f.write(self.swmm_table("SUBAREAS", hierarchy, state, selected_structures)) + self.feedback_set_progress(20) + f.write(self.swmm_table("AQUIFERS")) + self.feedback_set_progress(25) + f.write(self.swmm_table("INFILTRATION", hierarchy, state, selected_structures)) + self.feedback_set_progress(30) + f.write(self.swmm_table("POLYGONS")) + + f.write(self.copy_parameters_from_template("GROUNDWATER")) + f.write(self.copy_parameters_from_template("SNOWPACKS")) + f.write(self.copy_parameters_from_template("HYDROGAPHS")) + f.write(self.copy_parameters_from_template("LID_CONTROLS")) + f.write(self.copy_parameters_from_template("LID_USAGE")) + + # Hydraulics: nodes + # ------------------ + self.feedback_set_progress(35) + f.write(self.swmm_table("JUNCTIONS", hierarchy, state, selected_ws_re)) + self.feedback_set_progress(40) + f.write(self.swmm_table("OUTFALLS", hierarchy, state, selected_structures)) + self.feedback_set_progress(45) + f.write(self.swmm_table("STORAGES", hierarchy, state, selected_structures)) + self.feedback_set_progress(50) + f.write(self.swmm_table("COORDINATES", hierarchy, state, selected_ws_re)) + self.feedback_set_progress(55) + f.write(self.swmm_table("DWF", hierarchy, state, selected_structures)) + + f.write(self.copy_parameters_from_template("INFLOWS")) + f.write(self.swmm_table("DIVIDERS")) + + # Hydraulics: links + # ------------------ + self.feedback_set_progress(60) + f.write(self.swmm_table("CONDUITS", hierarchy, state, selected_reaches)) + self.feedback_set_progress(65) + f.write(self.swmm_table("LOSSES", hierarchy, state, selected_structures)) + self.feedback_set_progress(70) + f.write(self.swmm_table("PUMPS", hierarchy, state, selected_structures)) + f.write(self.swmm_table("ORIFICES", hierarchy, state, selected_structures)) + f.write(self.swmm_table("WEIRS", hierarchy, state, selected_structures)) + self.feedback_set_progress(75) + f.write(self.swmm_table("XSECTIONS", hierarchy, state, selected_reaches)) + self.feedback_set_progress(80) + f.write(self.swmm_table("LOSSES", hierarchy, state, selected_structures)) + f.write(self.swmm_table("OUTLETS")) + self.feedback_set_progress(85) + f.write(self.swmm_table("VERTICES", hierarchy, state, selected_reaches)) + f.write(self.copy_parameters_from_template("TRANSECTS")) + f.write(self.copy_parameters_from_template("CONTROLS")) + + # Quality + # -------- + self.feedback_set_progress(90) + f.write(self.swmm_table("LANDUSES")) + self.feedback_set_progress(93) + f.write(self.swmm_table("COVERAGES", None, None, selected_structures)) + + f.write(self.copy_parameters_from_template("POLLUTANTS")) + f.write(self.copy_parameters_from_template("BUILDUP")) + f.write(self.copy_parameters_from_template("WASHOFF")) + f.write(self.copy_parameters_from_template("TREATMENT")) + f.write(self.copy_parameters_from_template("RDII")) + f.write(self.copy_parameters_from_template("LOADINGS")) + + # Curves + # ------- + f.write(self.swmm_table("CURVES")) + + # Time series + # ------------ + f.write(self.copy_parameters_from_template("TIMESERIES")) + + # Time patterns + # -------------- + f.write(self.copy_parameters_from_template("PATTERNS")) + + # Map labels + # ----------- + f.write(self.copy_parameters_from_template("LABELS")) + self.feedback_set_progress(96) + + # Tags + # ---- + f.write(self.swmm_table("TAGS", state, selected_ws_re)) + f.close() + return + + def extract_time_series_indexes(self): + """ + Extract full time series from swmm report file + + Returns: + data_indexes (dictionary): dictionary of the object id with data indexes + + """ + + o = codecs.open(self.rpt_file, "r", encoding="utf-8") + line = o.readline() + title_found = False + line_number = -1 + data_indexes = {} + heading_lines = 5 + while line: + line_number += 1 + line = line.rstrip() + # Search for the table title + if line.find("*****") != -1: + # The following title is found: stop the recording of the indexes + title_found = False + + if line.find("<<< Link ") != -1 or line.find("<<< Node ") != -1: + title_found = True + line_after_title = 0 + obj_id = line.strip().split(" ")[2] + data_indexes[obj_id] = {} + data_indexes[obj_id]["title_index"] = line_number + data_indexes[obj_id]["start_index"] = line_number + heading_lines + if line.find("Link") != -1: + data_indexes[obj_id]["type"] = "link" + if line.find("Node") != -1: + data_indexes[obj_id]["type"] = "node" + + if title_found and line_after_title > heading_lines and line.strip() == "": + data_indexes[obj_id]["end_index"] = line_number - 1 + + if title_found: + line_after_title += 1 + + line = o.readline() + o.close() + return data_indexes + + def extract_summary_lines(self, table_title): + """ + Extract result data from swmm report file + + Parameters: + table_title (string): Name of the section to be extracted + + Returns: + Array of array: Extracted computed values + + """ + + o = codecs.open(self.rpt_file, "r", encoding="utf-8") + + line = o.readline() + no_line = 0 + lines = [] + title_found = False + end_table_found = False + while line: + line = line.rstrip() + # Search for the table title + if line.find(table_title) != -1: + title_found = True + line_after_title = 0 + + if title_found and line_after_title > 7 and line == "": + end_table_found = True + + if title_found and end_table_found is False and line_after_title > 7: + lines.append(line.split()) + + if title_found: + line_after_title += 1 + + no_line += 1 + line = o.readline() + o.close() + + return lines + + def extract_node_depth_summary(self): + """ + Extract node depth result data from swmm output file + + Returns: + dic: Extracted computed values + + """ + + data = self.extract_summary_lines("Node Depth Summary") + result = [] + for d in data: + curres = {} + curres["id"] = d[0] + curres["type"] = d[1] + curres["average_depth"] = d[2] + curres["maximum_depth"] = d[3] + curres["maximum_hgl"] = d[4] + curres["time_max_day"] = d[5] + curres["time_max_time"] = d[6] + curres["reported_max_depth"] = d[7] + result.append(curres) + return result + + def extract_link_flow_summary(self): + """ + Extract link flow result data from swmm output file + + Returns: + dic: Extracted computed values + + """ + + data = self.extract_summary_lines("Link Flow Summary") + result = [] + for d in data: + curres = {} + curres["id"] = d[0] + curres["type"] = d[1] + curres["maximum_flow"] = d[2] + curres["time_max_day"] = d[3] + curres["time_max_time"] = d[4] + if d[1] == "CONDUIT": + curres["maximum_velocity"] = d[5] + curres["max_over_full_flow"] = d[6] + curres["max_over_full_depth"] = d[7] + elif d[1] == "PUMP": + curres["max_over_full_flow"] = d[5] + curres["maximum_velocity"] = None + curres["max_over_full_depth"] = None + + result.append(curres) + return result + + def execute_swmm(self): + """ + Execute SWMM + + Parameters: + dic: Extracted computed values + + """ + + command = [self.bin_file, self.input_file, self.rpt_file] + self.feedback_push("info", "command: " + " ".join(map(str, command))) + proc = subprocess.run( + command, + shell=True, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ).stdout + + return proc + + def get_analysis_option(self, parameter): + o = codecs.open(self.rpt_file, "r", encoding="utf-8") + line = o.readline() + while line: + line = line.rstrip() + # Search for the analyis option + if line.find(parameter) != -1: + value = line.split(".")[-1].strip() + line = o.readline() + o.close() + return value + + def convert_to_datetime(self, str_date): + date = datetime.strptime(str_date, "%d/%m/%Y %H:%M:%S") + return date + + def import_full_results(self, sim_description): + """ + Import the full results from an SWMM report file + + Parameters: + sim_description (string): Title of the simulation + + """ + + simulation_start_date = self.convert_to_datetime(self.get_analysis_option("Starting Date")) + simulation_end_date = self.convert_to_datetime(self.get_analysis_option("Ending Date")) + simulation_duration = simulation_end_date - simulation_start_date + measuring_duration = simulation_duration.total_seconds() + + data_indexes = self.extract_time_series_indexes() + + ndata = len(data_indexes.keys()) + self.feedback_push("info", "Import full results") + counter = 0 + for obj_id in data_indexes.keys(): + counter += 1 + self.feedback_set_progress(counter * 100 / ndata) + # Create measuring point if necessary + if data_indexes[obj_id]["type"] == "node": + mp_obj_id = self.create_measuring_point_node(obj_id, sim_description) + if data_indexes[obj_id]["type"] == "link": + mp_obj_id = self.create_measuring_point_link(obj_id, sim_description) + if mp_obj_id: + # Create measuring device + self.create_measuring_device(mp_obj_id) + # Get measurement data of the current object + measurement_data = self.get_full_results( + data_indexes[obj_id]["start_index"], + data_indexes[obj_id]["end_index"], + data_indexes[obj_id]["type"], + ) + # Record each measurement + m_counter = 0 + for m in measurement_data: + m_counter += 1 + time = self.convert_to_datetime(m["date"] + " " + m["time"]).isoformat() + for k in m.keys(): + if k in SWMM_RESULTS_PARAMETERS.keys(): + if SWMM_RESULTS_PARAMETERS[k]["recorded"]: + ms_obj_id = self.create_measurement_series( + mp_obj_id, + k, + SWMM_RESULTS_PARAMETERS[k]["dimension"], + ) + self.create_measurement_result( + ms_obj_id, + SWMM_RESULTS_PARAMETERS[k]["tww_measurement_type"], + measuring_duration, + time, + m[k], + ) + return + + def get_full_results(self, start_index, end_index, swmm_type): + """ + Get the full result of a node or link + + Parameters: + start_index (integer): Index of the first line containing the data + end_index (integer): Index of the last line containing the data + + Returns: + datas: array of dictionnary containing the data + """ + o = codecs.open(self.rpt_file, "r", encoding="utf-8") + line = o.readline() + no_line = -1 + datas = [] + while line: + no_line += 1 + if no_line >= start_index and no_line < end_index: + values = line.strip().split() + data = {} + if len(values) != 0: + data["date"] = values[0] + data["time"] = values[1] + if swmm_type == "node": + data["inflow"] = values[2] + data["flooding"] = values[3] + data["depth"] = values[4] + data["head"] = values[5] + if swmm_type == "link": + data["flow"] = values[2] + data["velocity"] = values[3] + data["depth"] = values[4] + data["capacity"] = values[5] + datas.append(data) + line = o.readline() + o.close() + return datas + + def import_summary(self, sim_description): + """ + Import the summary results from an SWMM report file + + Parameters: + sim_description (string): Title of the simulation + + """ + simulation_start_date = self.convert_to_datetime(self.get_analysis_option("Starting Date")) + simulation_end_date = self.convert_to_datetime(self.get_analysis_option("Ending Date")) + simulation_duration = simulation_end_date - simulation_start_date + measuring_duration = simulation_duration.total_seconds() + self.feedback_push("info", "Import nodes summary") + node_summary = self.extract_node_depth_summary() + self.record_summary( + node_summary, + simulation_start_date, + sim_description, + measuring_duration, + "node", + ) + self.feedback_push("info", "Import links summary") + link_summary = self.extract_link_flow_summary() + self.record_summary( + link_summary, + simulation_start_date, + sim_description, + measuring_duration, + "link", + ) + + return + + def convert_max_over_full_flow(self, link_summary): + """ + Convert max_over_full_flow in percent + + Parameters: + link_summary (array): data extracted from the summary + + Returns: + link_summary (array) + """ + + for ws in link_summary: + ws["max_over_full_flow"] = int(round(float(ws["max_over_full_flow"]))) * 100 + + return link_summary + + def import_backflow_level(self): + """ + Import the backflow level from an SWMM report file + """ + self.feedback_push("info", "Import backflow level") + print("1") + node_summary = self.extract_node_depth_summary() + print("2") + self.populate_attribute(node_summary, "wastewater_node", "backflow_level", "maximum_hgl") + + return + + def import_hydraulic_load(self): + """ + Import the hydraulic load from an SWMM report file + """ + self.feedback_push("info", "Import hydraulic load") + link_summary = self.extract_link_flow_summary() + link_summary = self.convert_max_over_full_flow(link_summary) + self.populate_attribute( + link_summary, + "reach", + "dss2020_hydraulic_load_current", + "max_over_full_flow", + ) + + return + + def record_summary( + self, data, simulation_start_date, sim_description, measuring_duration, obj_type + ): + """ + Record the node and link summary in the database + + Parameters: + data (array): data extracted from the summary + simulation_start_date (datetime): start of the simulation + sim_description (string): name of the simulation + measuring_duration (integer): time length of the simulation in seconds + obj_type (string): link or node + + """ + + ndata = len(data) + # Loop over each line of the node summary + counter = 0 + for ws in data: + counter += 1 + if obj_type == "node": + self.feedback_set_progress(counter * 50 / ndata) + mp_obj_id = self.create_measuring_point_node(ws["id"], sim_description) + else: + self.feedback_set_progress(50 + counter * 50 / ndata) + mp_obj_id = self.create_measuring_point_link(ws["id"], sim_description) + if mp_obj_id: + self.create_measuring_device(mp_obj_id) + delta = timedelta( + days=int(ws["time_max_day"]), + hours=int(ws["time_max_time"].split(":")[0]), + minutes=int(ws["time_max_time"].split(":")[1]), + ) + for k in ws.keys(): + if k in SWMM_SUMMARY_PARAMETERS.keys(): + if SWMM_SUMMARY_PARAMETERS[k]["recorded"]: + ms_obj_id = self.create_measurement_series( + mp_obj_id, k, SWMM_SUMMARY_PARAMETERS[k]["dimension"] + ) + time = (simulation_start_date + delta).isoformat() + self.create_measurement_result( + ms_obj_id, + SWMM_SUMMARY_PARAMETERS[k]["tww_measurement_type"], + measuring_duration, + time, + ws[k], + ) + return + + def populate_attribute(self, data, table_name, attribute_name, swmm_attribute): + """ + Update an attribute of a tww_od table according to a swmm result + + Parameters: + data (array): data extracted from the node summary + table_name (string): name of the destination table + attribute_name (string): name of the destination attribute + swmm_attribute (string): name of the swmm attribute (ie. maximum_hgl, max_over_full_flow) + """ + + ndata = len(data) + cur = self.con.cursor() + # Loop over each line of the node summary + counter = 0 + for ws in data: + counter += 1 + bf_level = ws[swmm_attribute] + obj_id = ws["id"] + sql = """ + UPDATE tww_od.{table_name} + SET {attribute_name} = {bf_level} + WHERE obj_id = '{obj_id}' + RETURNING obj_id; + """.format( + table_name=table_name, + attribute_name=attribute_name, + bf_level=bf_level, + obj_id=obj_id, + ) + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", str(psycopg.ProgrammingError)) + return None, None + res = cur.fetchone() + if res is None: + self.feedback_push( + "info", + """{obj_id} in the output file has no correspondance in tww_od.{table_name}.""".format( + obj_id=obj_id, table_name=table_name + ), + ) + self.feedback_set_progress(counter / ndata) + self.con.commit() + + return + + def create_measuring_point_node(self, node_obj_id, sim_description): + """ + For a node creates a measuring point or get its id. + + Parameters: + node_obj_id (string): wastewater node object ID + sim_description (string): name of the simulation + + Returns: + me_obj_id: measuring point object ID + + """ + + # Connects to service and get data and attributes from tableName + cur = self.con.cursor() + + # Test if the measuring point exists + sql = """ + SELECT mp.obj_id + FROM tww_od.measuring_point mp + JOIN tww_od.wastewater_structure ws on mp.fk_wastewater_structure = ws.obj_id + WHERE ws.fk_main_wastewater_node = '{node_obj_id}' + AND mp.remark = '{sim_description}' + """.format( + sim_description=sim_description, node_obj_id=node_obj_id + ) + cur.execute(sql) + res = cur.fetchone() + + if res is None: + # Measuring point doesnt exists, must be created + # 4594 = technical purpose [TO VALIDATE] + sql = """ + INSERT INTO tww_od.measuring_point + (damming_device, identifier, kind, + purpose, remark, fk_wastewater_structure) + SELECT 5721, NULL, '{MEASURING_POINT_KIND}', 4594, + '{sim_description}', ws.obj_id + FROM tww_od.wastewater_structure ws + WHERE fk_main_wastewater_node = '{node_obj_id}' + RETURNING obj_id + """.format( + MEASURING_POINT_KIND=MEASURING_POINT_KIND, + node_obj_id=node_obj_id, + sim_description=sim_description, + ) + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", str(psycopg.ProgrammingError)) + return None, None + res = cur.fetchone() + if res is None: + mp_obj_id = None + else: + mp_obj_id = res[0] + self.con.commit() + del cur + else: + mp_obj_id = res[0] + return mp_obj_id + + def create_measuring_point_link(self, reach_obj_id, sim_description): + """ + For a node creates a measuring point or get its id. + + Parameters: + reach_obj_id (string): reach object ID + sim_description (string): name of the simulation + + Returns: + me_obj_id: measuring point object ID + + """ + + # Connects to service and get data and attributes from tableName + cur = self.con.cursor() + + # Test if the measuring point exists + sql = """ + SELECT mp.obj_id + FROM tww_od.measuring_point mp + JOIN tww_od.wastewater_networkelement ne ON + ne.fk_wastewater_structure = mp.fk_wastewater_structure + WHERE ne.obj_id = '{reach_obj_id}' + AND mp.remark = '{sim_description}' + """.format( + sim_description=sim_description, reach_obj_id=reach_obj_id + ) + cur.execute(sql) + res = cur.fetchone() + + if res is None: + # Measuring point doesnt exists, must be created + # 4594 = technical purpose [TO VALIDATE] + sql = """ + INSERT INTO tww_od.measuring_point + (damming_device, identifier, kind, purpose, remark, + fk_wastewater_structure) + SELECT 5721, NULL, '{MEASURING_POINT_KIND}', 4594, + '{sim_description}', ne.fk_wastewater_structure + FROM tww_od.wastewater_networkelement ne + WHERE ne.obj_id = '{reach_obj_id}' + RETURNING obj_id + """.format( + MEASURING_POINT_KIND=MEASURING_POINT_KIND, + sim_description=sim_description, + reach_obj_id=reach_obj_id, + ) + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", str(psycopg.ProgrammingError)) + return None + res = cur.fetchone() + mp_obj_id = res[0] + self.con.commit() + del cur + else: + mp_obj_id = res[0] + return mp_obj_id + + def create_measuring_device(self, mp_obj_id): + """ + For a measuring point creates a measuring device or get its id. + + Parameters: + mp_obj_id (string): measuring point object ID + + Returns: + md_obj_id: measuring device object ID + + """ + + cur = self.con.cursor() + + # Test if the measuring device exists + sql = """ + SELECT md.obj_id + FROM tww_od.measuring_device md + WHERE md.fk_measuring_point = '{mp_obj_id}' + AND remark = '{MEASURING_DEVICE_REMARK}' + """.format( + MEASURING_DEVICE_REMARK=MEASURING_DEVICE_REMARK, mp_obj_id=mp_obj_id + ) + cur.execute(sql) + res = cur.fetchone() + + if res is None: + # Measuring device doesnt exists, must be created + sql = """ + INSERT INTO tww_od.measuring_device + (kind, remark, fk_measuring_point) + VALUES + (5702, '{MEASURING_DEVICE_REMARK}','{mp_obj_id}') + RETURNING obj_id + """.format( + MEASURING_DEVICE_REMARK=MEASURING_DEVICE_REMARK, mp_obj_id=mp_obj_id + ) + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", str(psycopg.ProgrammingError)) + return None, None + res = cur.fetchone() + if res is None: + mp_obj_id = None + else: + mp_obj_id = res[0] + self.con.commit() + del cur + else: + mp_obj_id = res[0] + return mp_obj_id + + def create_measurement_series(self, mp_obj_id, parameter_name, parameter_dimension): + """ + Creates a measurement serie or get its id. + + Parameters: + mp_obj_id (string): measurement point object ID + parameter_name (string): name of the parameter + parameter_dimension (string): dimension of the parameter + + Returns: + mp_obj_id: measuring point object ID + + """ + + # Connects to service + cur = self.con.cursor() + + # Test if the measurement serie exists + sql = """ + SELECT obj_id FROM tww_od.measurement_series + WHERE remark = '{parameter_name}' + AND fk_measuring_point = '{mp_obj_id}' + """.format( + parameter_name=parameter_name, mp_obj_id=mp_obj_id + ) + cur.execute(sql) + res = cur.fetchone() + + if res is None: + # Measuring point doesnt exists, must be created + # 3217 = other [TO VALIDATE] + # No dimension, else we would need to create four measurements + # series l/s m/s m - [TO VALIDATE] + sql = """ + INSERT INTO tww_od.measurement_series + (identifier, dimension, kind, remark, fk_measuring_point) + VALUES + (null, '{parameter_dimension}', 3217, + '{parameter_name}', '{mp_obj_id}') + RETURNING obj_id + """.format( + parameter_dimension=parameter_dimension, + parameter_name=parameter_name, + mp_obj_id=mp_obj_id, + ) + + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", str(psycopg.ProgrammingError)) + return None + ms_obj_id = cur.fetchone()[0] + self.con.commit() + else: + ms_obj_id = res[0] + del cur + return ms_obj_id + + def create_measurement_result( + self, ms_obj_id, measurement_type, measuring_duration, time, value + ): + """ + Creates a measurement result or update it. + + Parameters: + ms_obj_id (string): measurement serie object ID + measurement_type (integer): type of measurement 5733=flow, 5734=level, 5732=other + measuring_duration (integer): Time step of the simulation in seconds + time (string): timestamp of the recorded result + value (float): value of the measurement + + Returns: + mr_obj_id: measurement result object ID + + """ + + # Connects to service + cur = self.con.cursor() + + # Test if the measurement result exists (same measurement serie, same time, same type) + sql = """ + SELECT obj_id FROM tww_od.measurement_result + WHERE fk_measurement_series = '{ms_obj_id}' + AND time = '{time}' + AND measurement_type = {measurement_type} + """.format( + ms_obj_id=ms_obj_id, time=time, measurement_type=measurement_type + ) + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", (str(psycopg.ProgrammingError))) + return None + res = cur.fetchone() + + if res is None: + # Measurement result doesnt exists, must be created + + sql = """ + INSERT INTO tww_od.measurement_result + (identifier, measurement_type, measuring_duration, + time, value, fk_measurement_series) + VALUES + (null, {measurement_type}, {measuring_duration}, '{time}', {value}, '{ms_obj_id}') + RETURNING obj_id + """.format( + measurement_type=measurement_type, + measuring_duration=measuring_duration, + time=time, + value=value, + ms_obj_id=ms_obj_id, + ) + + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", (str(psycopg.ProgrammingError))) + return None + mr_obj_id = cur.fetchone()[0] + self.con.commit() + else: + mr_obj_id = res[0] + # Measurement result exists, must be updated + sql = """ + UPDATE tww_od.measurement_result + SET measuring_duration={measuring_duration}, value={value} + WHERE obj_id = '{mr_obj_id}' + RETURNING obj_id + """.format( + measuring_duration=measuring_duration, value=value, mr_obj_id=mr_obj_id + ) + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", (str(psycopg.ProgrammingError))) + return None + mr_obj_id = cur.fetchone()[0] + self.con.commit() + del cur + return mr_obj_id + + def disable_reach_trigger(self): + """ + Disable triggers on the table tww_od.reach + """ + + cur = self.con.cursor() + + # Set value for tww_od.reach.default_coefficient_friction where reach_material is known + sql = """ + ALTER TABLE tww_od.reach DISABLE TRIGGER ALL; + """ + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", (str(psycopg.ProgrammingError))) + return None + self.con.commit() + del cur + return + + def enable_reach_trigger(self): + """ + Enable triggers on the table tww_od.reach + """ + + cur = self.con.cursor() + + # Set value for tww_od.reach.default_coefficient_friction where reach_material is known + sql = """ + ALTER TABLE tww_od.reach ENABLE TRIGGER ALL; + """ + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", (str(psycopg.ProgrammingError))) + return None + self.con.commit() + del cur + return + + def set_reach_default_friction(self): + """ + Set default friction in tww_od.reach where default friction is not set + """ + + cur = self.con.cursor() + + # Set value for tww_od.reach.default_coefficient_friction where reach_material is known + sql = """ + UPDATE tww_od.reach r + SET swmm_default_coefficient_of_friction = f.coefficient_of_friction + FROM tww_swmm.reach_coefficient_of_friction f + WHERE r.swmm_default_coefficient_of_friction isnull AND f.fk_material = r.material; + """ + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", (str(psycopg.ProgrammingError))) + return None + self.con.commit() + del cur + return + + def overwrite_reach_default_friction(self): + """ + Reset default friction in tww_od.reach where default friction + """ + + cur = self.con.cursor() + + # Set value for tww_od.reach.default_coefficient_friction where reach_material is known + sql = """ + UPDATE tww_od.reach r + SET swmm_default_coefficient_of_friction = f.coefficient_of_friction + FROM tww_swmm.reach_coefficient_of_friction f + WHERE f.fk_material = r.material; + """ + try: + cur.execute(sql) + except psycopg.ProgrammingError: + self.feedback_push("error", f"Error while excecuting: {sql}") + self.feedback_push("error", (str(psycopg.ProgrammingError))) + return None + self.con.commit() + del cur + return diff --git a/plugin/teksi_wastewater/processing_provider/__init__.py b/plugin/teksi_wastewater/processing_provider/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugin/teksi_wastewater/processing_provider/change_reach_direction.py b/plugin/teksi_wastewater/processing_provider/change_reach_direction.py new file mode 100644 index 0000000..a9b7de7 --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/change_reach_direction.py @@ -0,0 +1,89 @@ +""" +/*************************************************************************** + TWW processing provider + ------------------- + begin : 18.11.2017 + copyright : (C) 2017 by OPENGIS.ch + email : matthias@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.core import QgsProcessingAlgorithm, QgsProcessingParameterVectorLayer + +from .tww_algorithm import TwwAlgorithm + +__author__ = "Matthias Kuhn & Maxime Trolliet" +__date__ = "2018-08-07" +__copyright__ = "(C) 2018 by OPENGIS.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class ChangeReachDirection(TwwAlgorithm): + """ + Change the direction of the selected reaches + """ + + REACH_LAYER = "REACH_LAYER" + + def name(self): + return "change_direction" + + def displayName(self): + return self.tr("Change reach direction") + + def flags(self): + return super().flags() | QgsProcessingAlgorithm.FlagNoThreading + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + + self.addParameter( + QgsProcessingParameterVectorLayer( + self.REACH_LAYER, + description=self.tr( + "Selected features only - Reach layer, will be modified in place and its direction will be inverted" + ), + ) + ) + + def processAlgorithm(self, parameters, context, feedback): + """Here is where the processing itself takes place.""" + reach_layer = self.parameterAsVectorLayer(parameters, self.REACH_LAYER, context) + + reach_layer.startEditing() + + # feature_count = 0 + + iterator = reach_layer.getSelectedFeatures() + # feature_count = reach_layer.selectedFeatureCount() + + # Loop through relevant reaches + reach_layer.beginEditCommand("change directions") + transaction = reach_layer.dataProvider().transaction() + # if not transaction: + # raise Exception: if there is no transaction, complain to the user! + selected_obj_ids = [feature["obj_id"] for feature in iterator] + transaction.executeSql( + "SELECT tww_app.reach_direction_change('{{{obj_ids}}}');".format( + obj_ids=",".join(selected_obj_ids) + ), + True, + ) + reach_layer.endEditCommand() + feedback.setProgress(100) + + return {} diff --git a/plugin/teksi_wastewater/processing_provider/flow_times.py b/plugin/teksi_wastewater/processing_provider/flow_times.py new file mode 100644 index 0000000..0921cda --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/flow_times.py @@ -0,0 +1,214 @@ +""" +/*************************************************************************** + TWW processing provider + ------------------- + begin : 18.11.2017 + copyright : (C) 2017 by OPENGIS.ch + email : matthias@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import qgis.utils as qgis_utils +from PyQt5.QtCore import QVariant +from qgis.core import ( + QgsExpression, + QgsFeature, + QgsFeatureRequest, + QgsFeatureSink, + QgsField, + QgsFields, + QgsProcessing, + QgsProcessingAlgorithm, + QgsProcessingContext, + QgsProcessingException, + QgsProcessingFeedback, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterField, + QgsProcessingParameterVectorLayer, + QgsWkbTypes, +) + +from .tww_algorithm import TwwAlgorithm + +__author__ = "Denis Rouzaud" +__date__ = "2018-07-19" +__copyright__ = "(C) 2018 by OPENGIS.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class FlowTimesAlgorithm(TwwAlgorithm): + """""" + + DISTANCE = "DISTANCE" + REACH_LAYER = "REACH_LAYER" + FLOWTIMES_LAYER = "FLOWTIMES_LAYER" + FK_REACH_FIELD = "FK_REACH_FIELD" + FLOWTIMES_FIELD = "FLOWTIMES_FIELD" + OUTPUT = "OUTPUT" + + def name(self): + return "tww_flow_times" + + def displayName(self): + return self.tr("Flow times downstream") + + def flags(self): + return super().flags() | QgsProcessingAlgorithm.FlagNoThreading + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + + # The parameters + description = self.tr("Reach layer") + self.addParameter( + QgsProcessingParameterVectorLayer( + self.REACH_LAYER, + description=description, + types=[QgsProcessing.TypeVectorLine], + ) + ) + description = self.tr("Flow times layer") + self.addParameter( + QgsProcessingParameterVectorLayer( + self.FLOWTIMES_LAYER, + description=description, + types=[QgsProcessing.TypeVector], + ) + ) + description = self.tr("Reach id field") + self.addParameter( + QgsProcessingParameterField( + self.FK_REACH_FIELD, + description=description, + parentLayerParameterName=self.FLOWTIMES_LAYER, + ) + ) + description = self.tr("Flow times field") + self.addParameter( + QgsProcessingParameterField( + self.FLOWTIMES_FIELD, + description=description, + parentLayerParameterName=self.FLOWTIMES_LAYER, + type=QgsProcessingParameterField.Numeric, + ) + ) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr("Flow times"))) + + def processAlgorithm( + self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ): + """Here is where the processing itself takes place.""" + + feedback.setProgress(0) + na = qgis_utils.plugins["teksi_wastewater"].network_analyzer + + # init params + reach_layer = self.parameterAsVectorLayer(parameters, self.REACH_LAYER, context) + flow_layer = self.parameterAsVectorLayer(parameters, self.FLOWTIMES_LAYER, context) + fk_reach_field = self.parameterAsFields(parameters, self.FK_REACH_FIELD, context)[0] + flow_time_field = self.parameterAsFields(parameters, self.FLOWTIMES_FIELD, context)[0] + + # create feature sink + fields = QgsFields() + fields.append(QgsField("flow_time", QVariant.Double)) + (sink, dest_id) = self.parameterAsSink( + parameters, + self.OUTPUT, + context, + fields, + QgsWkbTypes.LineString, + reach_layer.sourceCrs(), + ) + if sink is None: + raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) + + # get selected reach + iterator = reach_layer.getSelectedFeatures() + feature_count = reach_layer.selectedFeatureCount() + if feature_count != 1: + raise QgsProcessingException(self.invalidSourceError(parameters, self.REACH_LAYER)) + reach_feature = QgsFeature() + iterator.nextFeature(reach_feature) + assert reach_feature.isValid() + tww_reach_obj_id = reach_feature.attribute("obj_id") + + # get top node + reach_features = na.getFeaturesByAttr( + na.getEdgeLayer(), "obj_id", [tww_reach_obj_id] + ).asDict() + assert len(reach_features) > 0 + from_pos = 1 + top_node = None + for fid, reach_feature in reach_features.items(): + if from_pos > reach_feature.attribute("from_pos"): + top_node = reach_feature.attribute("from_obj_id_interpolate") + from_pos = reach_feature.attribute("from_pos") + assert top_node is not None + nodes = na.getFeaturesByAttr(na.getNodeLayer(), "obj_id", [top_node]).asDict() + assert len(nodes) == 1 + top_node_id = next(iter(nodes.values())).id() + + # create graph + _, edges = na.getTree(top_node_id) + feedback.setProgress(50) + cache_edge_features = na.getFeaturesById( + na.getEdgeLayer(), [edge[2]["feature"] for edge in edges] + ).asDict() + + # join and accumulate flow times + i = -1 + flow_time = 0.0 + while True: + i += 1 + feedback.setProgress(50 + i / len(edges) * 50) + if i >= len(edges): + break + + edge = edges[i] + edge_feature = cache_edge_features[edge[2]["feature"]] + # TODO: if top_pos != 1 => merge + if edge_feature.attribute("type") != "reach": + continue + rate = edge_feature.attribute("to_pos") - edge_feature.attribute("from_pos") + assert 0 < rate <= 1 + + expression = QgsExpression( + "{fk_reach} = '{obj_id}'".format( + fk_reach=fk_reach_field, obj_id=edge_feature["obj_id"] + ) + ) + print(expression.expression()) + request = QgsFeatureRequest(expression) + flow_time_feature = next(flow_layer.getFeatures(request)) + + if not flow_time_feature.isValid(): + break + + flow_time += rate * flow_time_feature.attribute(flow_time_field) + + sf = QgsFeature() + sf.setFields(fields) + sf.setAttribute("flow_time", flow_time) + sf.setGeometry(edge_feature.geometry()) + sink.addFeature(sf, QgsFeatureSink.FastInsert) + + # f.setAttributes(attrs) + # sink.addFeature(f, QgsFeatureSink.FastInsert) + # feedback.setProgress(int(current * total)) + + return {self.OUTPUT: dest_id} diff --git a/plugin/teksi_wastewater/processing_provider/provider.py b/plugin/teksi_wastewater/processing_provider/provider.py new file mode 100644 index 0000000..3258709 --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/provider.py @@ -0,0 +1,150 @@ +""" +/*************************************************************************** + TWW processing provider + ------------------- + begin : 18.11.2017 + copyright : (C) 2017 by OPENGIS.ch + email : matthias@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import logging +import os + +from processing.core.ProcessingConfig import ProcessingConfig, Setting +from PyQt5.QtGui import QIcon +from qgis.core import Qgis, QgsProcessingProvider +from qgis.utils import iface + +from .change_reach_direction import ChangeReachDirection +from .flow_times import FlowTimesAlgorithm +from .snap_reach import SnapReachAlgorithm +from .sum_up_upstream import SumUpUpstreamAlgorithm +from .swmm_create_input import SwmmCreateInputAlgorithm +from .swmm_execute import SwmmExecuteAlgorithm +from .swmm_extract_results import SwmmExtractResultsAlgorithm +from .swmm_import_results import SwmmImportResultsAlgorithm +from .swmm_set_friction import SwmmSetFrictionAlgorithm + +__author__ = "Matthias Kuhn" +__date__ = "2017-11-18" +__copyright__ = "(C) 2017 by OPENGIS.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + +logger = logging.getLogger(__package__) + + +class TwwProcessingProvider(QgsProcessingProvider): + def __init__(self): + QgsProcessingProvider.__init__(self) + # AlgorithmProvider.__init__(self) + + self.activate = True + + # Load algorithms + self.alglist = [ + SnapReachAlgorithm(), + FlowTimesAlgorithm(), + ChangeReachDirection(), + SumUpUpstreamAlgorithm(), + SwmmCreateInputAlgorithm(), + SwmmExtractResultsAlgorithm(), + SwmmImportResultsAlgorithm(), + SwmmExecuteAlgorithm(), + SwmmSetFrictionAlgorithm(), + ] + try: + from ..interlis.processing_algs.extractlabels_interlis import ( + ExtractlabelsInterlisAlgorithm, + ) + + self.alglist.append(ExtractlabelsInterlisAlgorithm()) + except ImportError: + pass + + for alg in self.alglist: + alg.provider = self + + def getAlgs(self): + algs = [ + SnapReachAlgorithm(), + FlowTimesAlgorithm(), + SumUpUpstreamAlgorithm(), + ChangeReachDirection(), + SwmmCreateInputAlgorithm(), + SwmmExtractResultsAlgorithm(), + SwmmImportResultsAlgorithm(), + SwmmExecuteAlgorithm(), + SwmmSetFrictionAlgorithm(), + ] + try: + from ..interlis.processing_algs.extractlabels_interlis import ( + ExtractlabelsInterlisAlgorithm, + ) + + algs.append(ExtractlabelsInterlisAlgorithm()) + + except ImportError as e: + iface.messageBar().pushMessage( + "Error", + "Could not load tww2ili due to unmet dependencies. See logs for more details.", + level=Qgis.Critical, + ) + logger.error(str(e)) + return algs + + def id(self): + return "tww" + + def name(self): + """This is the name that will appear on the toolbox group. + + It is also used to create the command line name of all the + algorithms from this provider. + """ + return "TWW" + + def icon(self): + return QIcon(self.svgIconPath()) + + def svgIconPath(self): + basepath = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(basepath, "..", "icons", "twwIcon.svg") + + def loadAlgorithms(self): + self.algs = self.getAlgs() + for a in self.algs: + self.addAlgorithm(a) + + def load(self): + ProcessingConfig.settingIcons[self.name()] = self.icon() + ProcessingConfig.addSetting( + Setting( + self.name(), + "SWMM_PATH", + self.tr( + r"SWMM executable (e.g. C:\Program Files (x86)\EPA SWMM 5.1.013\swmm55.exe)" + ), + None, + valuetype=Setting.FILE, + ) + ) + + ProcessingConfig.readSettings() + self.refreshAlgorithms() + return True + + def unload(self): + ProcessingConfig.removeSetting("SWMM_PATH") diff --git a/plugin/teksi_wastewater/processing_provider/snap_reach.py b/plugin/teksi_wastewater/processing_provider/snap_reach.py new file mode 100644 index 0000000..60349fd --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/snap_reach.py @@ -0,0 +1,211 @@ +""" +/*************************************************************************** + TWW processing provider + ------------------- + begin : 18.11.2017 + copyright : (C) 2017 by OPENGIS.ch + email : matthias@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.core import ( + QgsExpression, + QgsFeatureRequest, + QgsGeometry, + QgsPointXY, + QgsProcessingParameterBoolean, + QgsProcessingParameterNumber, + QgsProcessingParameterVectorLayer, +) + +from .tww_algorithm import TwwAlgorithm + +__author__ = "Matthias Kuhn" +__date__ = "2017-11-18" +__copyright__ = "(C) 2017 by OPENGIS.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class SnapReachAlgorithm(TwwAlgorithm): + """""" + + DISTANCE = "DISTANCE" + REACH_LAYER = "REACH_LAYER" + WASTEWATER_NODE_LAYER = "WASTEWATER_NODE_LAYER" + ONLY_SELECTED = "ONLY_SELECTED" + + def name(self): + return self.tr("tww_snap_rach") + + def displayName(self): + return self.tr("Snap reach geometry") + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + + # The parameters + self.addParameter( + QgsProcessingParameterNumber( + self.DISTANCE, + type=QgsProcessingParameterNumber.Double, + description=self.tr( + "Maximum snapping distance in meters. Set to 0 for no maximum." + ), + defaultValue=10.0, + ) + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.ONLY_SELECTED, + description=self.tr("Snap only selected reaches."), + defaultValue=True, + ) + ) + self.addParameter( + QgsProcessingParameterVectorLayer( + self.REACH_LAYER, + description=self.tr( + "Reach layer, will be modified in place and used as snapping target" + ), + ) + ) + self.addParameter( + QgsProcessingParameterVectorLayer( + self.WASTEWATER_NODE_LAYER, + description=self.tr("Wastewater node layer, will be used as snapping target"), + ) + ) + + def processAlgorithm(self, parameters, context, feedback): + """Here is where the processing itself takes place.""" + + reach_layer = self.parameterAsVectorLayer(parameters, self.REACH_LAYER, context) + wastewater_node_layer = self.parameterAsVectorLayer( + parameters, self.WASTEWATER_NODE_LAYER, context + ) + distance = self.parameterAsDouble(parameters, self.DISTANCE, context) + only_selected = self.parameterAsBool(parameters, self.ONLY_SELECTED, context) + + reach_layer.startEditing() + + feature_count = 0 + if only_selected: + iterator = reach_layer.getSelectedFeatures() + feature_count = reach_layer.selectedFeatureCount() + else: + iterator = reach_layer.getFeatures() + feature_count = reach_layer.featureCount() + + # Loop through relevant reaches + reach_layer.beginEditCommand("Snap reaches to points") + try: + reaches = list() + current_feature = 0 + for reach in iterator: + reaches.append(reach) + # Batch processing: process blocks of 2000 reaches + if len(reaches) == 2000: + self.processFeatures(reaches, reach_layer, wastewater_node_layer, distance) + reaches = list() + + current_feature += 1 + feedback.setProgress(current_feature * 100.0 / feature_count) + + self.processFeatures(reaches, reach_layer, wastewater_node_layer, distance) + except: # NOQA + reach_layer.destroyEditCommand() + raise + reach_layer.endEditCommand() + feedback.setProgress(100) + + return {} + + def processFeatures(self, reaches, reach_layer, wastewater_node_layer, distance_threshold): + ids = list() + to_ids = list() + # Gather ids of connected networkelements + # to_ids are also gathered separately, because they can be either + # reaches or nodes + for reach in reaches: + if reach["rp_from_fk_wastewater_networkelement"]: + ids.append(reach["rp_from_fk_wastewater_networkelement"]) + + if reach["rp_to_fk_wastewater_networkelement"]: + ids.append(reach["rp_to_fk_wastewater_networkelement"]) + to_ids.append(reach["rp_to_fk_wastewater_networkelement"]) + + # Get all nodes on which to snap + quoted_ids = [QgsExpression.quotedValue(objid) for objid in ids] + node_request = QgsFeatureRequest() + filter_expression = '"obj_id" IN ({ids})'.format(ids=",".join(quoted_ids)) + node_request.setFilterExpression(filter_expression) + node_request.setSubsetOfAttributes([]) + + nodes = dict() + for node in wastewater_node_layer.getFeatures(node_request): + nodes[node["obj_id"]] = node + + # Get all reaches on which to snap + quoted_to_ids = [QgsExpression.quotedValue(objid) for objid in to_ids] + reach_request = QgsFeatureRequest() + filter_expression = '"obj_id" IN ({ids})'.format(ids=",".join(quoted_to_ids)) + reach_request.setFilterExpression(filter_expression) + reach_request.setSubsetOfAttributes([]) + + target_reaches = dict() + for target_reach in reach_layer.getFeatures(reach_request): + target_reaches[target_reach["obj_id"]] = target_reach + + for reach in reaches: + reach_geometry = QgsGeometry(reach.geometry()) + from_id = reach["rp_from_fk_wastewater_networkelement"] + if from_id in list(nodes.keys()): + if ( + distance_threshold == 0 + or reach_geometry.sqrDistToVertexAt(nodes[from_id].geometry().asPoint(), 0) + < distance_threshold + ): + reach_geometry.moveVertex(nodes[from_id].geometry().constGet(), 0) + + to_id = reach["rp_to_fk_wastewater_networkelement"] + if to_id in list(nodes.keys()): + last_vertex = reach_geometry.constGet().nCoordinates() - 1 + if ( + distance_threshold == 0 + or reach_geometry.sqrDistToVertexAt( + nodes[to_id].geometry().asPoint(), last_vertex + ) + < distance_threshold + ): + reach_geometry.moveVertex(nodes[to_id].geometry().constGet(), last_vertex) + + if to_id in list(target_reaches.keys()): + last_vertex = reach_geometry.constGet().nCoordinates() - 1 + target_reach = target_reaches[to_id] + ( + distance, + point, + min_distance_point, + after_vertex, + ) = target_reach.geometry().closestSegmentWithContext( + QgsPointXY(reach_geometry.vertexAt(last_vertex)) + ) + if distance_threshold == 0 or distance < distance_threshold: + reach_geometry.moveVertex(point.x(), point.y(), last_vertex) + + reach.setGeometry(reach_geometry) + reach_layer.updateFeature(reach) diff --git a/plugin/teksi_wastewater/processing_provider/sum_up_upstream.py b/plugin/teksi_wastewater/processing_provider/sum_up_upstream.py new file mode 100644 index 0000000..c67fe47 --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/sum_up_upstream.py @@ -0,0 +1,447 @@ +""" +/*************************************************************************** + TWW processing provider + ------------------- + begin : 18.11.2017 + copyright : (C) 2017 by OPENGIS.ch + email : matthias@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import statistics + +from PyQt5.QtCore import QVariant +from qgis.core import ( + NULL, + QgsExpression, + QgsExpressionContext, + QgsExpressionContextUtils, + QgsFeature, + QgsFeatureRequest, + QgsFeatureSink, + QgsField, + QgsGeometry, + QgsProcessing, + QgsProcessingContext, + QgsProcessingException, + QgsProcessingFeedback, + QgsProcessingParameterBoolean, + QgsProcessingParameterDefinition, + QgsProcessingParameterEnum, + QgsProcessingParameterExpression, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterField, + QgsProcessingParameterVectorLayer, + QgsWkbTypes, +) + +from .tww_algorithm import TwwAlgorithm + +__author__ = "Matthias Kuhn" +__date__ = "2019-04-09" +__copyright__ = "(C) 2018 by OPENGIS.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class Reach: + def __init__(self, from_id, to_id, value, geometry): + self.from_id = from_id + self.to_id = to_id + self.value = value + self.geometry = geometry + + +class SumUpUpstreamAlgorithm(TwwAlgorithm): + """""" + + REACH_LAYER = "REACH_LAYER" + WASTEWATER_NODE_LAYER = "WASTEWATER_NODE_LAYER" + VALUE_EXPRESSION = "VALUE_EXPRESSION" + REACH_PK_NAME = "REACH_PK_NAME" + NODE_PK_NAME = "NODE_PK_NAME" + NODE_FROM_FK_NAME = "NODE_FROM_FK_NAME" + NODE_TO_FK_NAME = "NODE_TO_FK_NAME" + BRANCH_BEHAVIOR = "BRANCH_BEHAVIOR" + CREATE_LOOP_LAYER = "CREATE_LOOP_LAYER" + + OUTPUT = "OUTPUT" + LOOP_OUTPUT = "LOOP_OUTPUT" + + def name(self): + return "tww_values_upstream" + + def displayName(self): + return self.tr("Sum up upstream") + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + + # The parameters + description = self.tr( + 'Source value expression. Use COALESCE("field_name", 0) to treat NULL values as 0.' + ) + self.addParameter( + QgsProcessingParameterExpression( + self.VALUE_EXPRESSION, + description=description, + parentLayerParameterName=self.REACH_LAYER, + ) + ) + description = self.tr("Branch behavior") + self.addParameter( + QgsProcessingParameterEnum( + self.BRANCH_BEHAVIOR, + description=description, + options=[self.tr("Minimum"), self.tr("Maximum"), self.tr("Average")], + ) + ) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr("Summed up"))) + + description = self.tr("Create a layer with nodes in loops") + self.addAdvancedParameter( + QgsProcessingParameterBoolean( + self.CREATE_LOOP_LAYER, description=description, defaultValue=False + ) + ) + + self.addAdvancedParameter( + QgsProcessingParameterFeatureSink( + self.LOOP_OUTPUT, + self.tr( + 'Loop nodes (Only created if "Crate a layer with nodes in loops" option is activated)' + ), + optional=True, + ) + ) + description = self.tr("Reach Layer") + self.addAdvancedParameter( + QgsProcessingParameterVectorLayer( + self.REACH_LAYER, + description=description, + types=[QgsProcessing.TypeVectorLine], + defaultValue="vw_tww_reach", + ) + ) + description = self.tr("Wastewater Node Layer") + self.addAdvancedParameter( + QgsProcessingParameterVectorLayer( + self.WASTEWATER_NODE_LAYER, + description=description, + types=[QgsProcessing.TypeVector], + defaultValue="vw_wastewater_node", + ) + ) + + description = self.tr("Primary Key Field Reach") + self.addAdvancedParameter( + QgsProcessingParameterField( + self.REACH_PK_NAME, + description=description, + parentLayerParameterName=self.REACH_LAYER, + defaultValue="obj_id", + ) + ) + + description = self.tr("Primary Key Field Node") + self.addAdvancedParameter( + QgsProcessingParameterField( + self.NODE_PK_NAME, + description=description, + parentLayerParameterName=self.WASTEWATER_NODE_LAYER, + defaultValue="obj_id", + ) + ) + + description = self.tr("Foreign Key Field From") + self.addAdvancedParameter( + QgsProcessingParameterField( + self.NODE_FROM_FK_NAME, + description=description, + parentLayerParameterName=self.REACH_LAYER, + defaultValue="rp_from_fk_wastewater_networkelement", + ) + ) + + description = self.tr("Foreign Key Field To") + self.addAdvancedParameter( + QgsProcessingParameterField( + self.NODE_TO_FK_NAME, + description=description, + parentLayerParameterName=self.REACH_LAYER, + defaultValue="rp_to_fk_wastewater_networkelement", + ) + ) + + def addAdvancedParameter(self, parameter): + parameter.setFlags(parameter.flags() | QgsProcessingParameterDefinition.FlagAdvanced) + self.addParameter(parameter) + + def processAlgorithm( + self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ): + """Here is where the processing itself takes place.""" + + feedback.setProgress(0) + + # init params + reach_layer = self.parameterAsVectorLayer(parameters, self.REACH_LAYER, context) + wastewater_node_layer = self.parameterAsVectorLayer( + parameters, self.WASTEWATER_NODE_LAYER, context + ) + value_expression = self.parameterAsExpression(parameters, self.VALUE_EXPRESSION, context) + reach_pk_name = self.parameterAsFields(parameters, self.REACH_PK_NAME, context)[0] + node_pk_name = self.parameterAsFields(parameters, self.NODE_PK_NAME, context)[0] + node_from_fk_name = self.parameterAsFields(parameters, self.NODE_FROM_FK_NAME, context)[0] + node_to_fk_name = self.parameterAsFields(parameters, self.NODE_TO_FK_NAME, context)[0] + branch_behavior = self.parameterAsEnum(parameters, self.BRANCH_BEHAVIOR, context) + create_loop_layer = self.parameterAsBool(parameters, self.CREATE_LOOP_LAYER, context) + + if branch_behavior == 0: + aggregate_method = self.aggregate_method_min + elif branch_behavior == 1: + aggregate_method = self.aggregate_method_max + elif branch_behavior == 2: + aggregate_method = self.aggregate_method_mean + else: + aggregate_method = self.aggregate_method_not_implemented + + # create feature sink + fields = wastewater_node_layer.fields() + fields.append(QgsField("value", QVariant.Double)) + (sink, dest_id) = self.parameterAsSink( + parameters, + self.OUTPUT, + context, + fields, + QgsWkbTypes.Point, + reach_layer.sourceCrs(), + ) + + loop_sink = None + loop_dest_id = None + if create_loop_layer: + (loop_sink, loop_dest_id) = self.parameterAsSink( + parameters, + self.LOOP_OUTPUT, + context, + fields, + QgsWkbTypes.Point, + reach_layer.sourceCrs(), + ) + + if sink is None: + raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) + + feature_count = reach_layer.featureCount() + + reaches_by_from_node = dict() + reaches_by_id = dict() + + expression = QgsExpression(value_expression) + context = QgsExpressionContext( + QgsExpressionContextUtils.globalProjectLayerScopes(reach_layer) + ) + expression.prepare(context) + + progress = 0 + feedback.setProgressText(self.tr("Indexing reaches")) + for reach in reach_layer.getFeatures(QgsFeatureRequest()): + if reach[node_from_fk_name] == NULL: + continue + + context.setFeature(reach) + value = expression.evaluate(context) + reach_obj = Reach( + reach[node_from_fk_name], + reach[node_to_fk_name], + value, + reach.geometry(), + ) + reaches_by_from_node.setdefault(reach_obj.from_id, []).append(reach_obj) + reaches_by_id[reach[reach_pk_name]] = reach_obj + + feedback.setProgress(progress / feature_count * 10) + progress += 1 + + loop_nodes = [] + current_feature = 0 + calculated_values = {} + + feedback.setProgressText(self.tr("Analyzing network")) + for node in wastewater_node_layer.getFeatures(): + from_node_id = node[node_pk_name] + + processed_nodes = [] + times = [] + if from_node_id in reaches_by_from_node.keys(): + for reach in reaches_by_from_node[from_node_id]: + times.append( + self.calculate_branch( + reach, + reaches_by_from_node, + reaches_by_id, + list(processed_nodes), + calculated_values, + aggregate_method, + loop_nodes, + feedback, + ) + ) + + if times: + time = aggregate_method(times, feedback) + else: + time = 0 + + current_feature += 1 + + calculated_values[node[node_pk_name]] = time + new_node = QgsFeature(node) + new_node.setFields(fields) + new_node.setAttributes(node.attributes() + [time]) + + sink.addFeature(new_node, QgsFeatureSink.FastInsert) + + if create_loop_layer and from_node_id in loop_nodes: + loop_sink.addFeature(node, QgsFeatureSink.FastInsert) + + feedback.setProgress(10 + current_feature / feature_count * 90) + + result = {self.OUTPUT: dest_id} + if create_loop_layer: + result[self.LOOP_OUTPUT] = loop_dest_id + + return result + + def process_node( + self, + node_id, + previous_reach, + reaches_by_from_node, + reaches_by_id, + processed_nodes, + calculated_values, + aggregate_method, + loop_nodes, + feedback, + ): + time = 0 + + while node_id in reaches_by_from_node.keys() or node_id in reaches_by_id.keys(): + if node_id in calculated_values: + return calculated_values[node_id] + time + if node_id in processed_nodes: + # feedback.reportError(self.tr('Loop at node: {}'.format(node_id))) + loop_nodes.append(node_id) + return 0 + + processed_nodes.append(node_id) + + if feedback.isCanceled(): + return NULL + + if node_id not in reaches_by_from_node: + # Blind connection: add proportionally + reach = reaches_by_id[node_id] + offset = reach.geometry.lineLocatePoint( + QgsGeometry(previous_reach.geometry.constGet().endPoint()) + ) + length = reach.geometry.length() + remaining_part = 1 - offset / length + # feedback.pushInfo('Length: {} Offset: {} Part: {}'.format(length, offset, remaining_part, reach.value * remaining_part)) + time += reach.value * remaining_part + node_id = reach.to_id + else: + current_reaches = reaches_by_from_node[node_id] + if len(current_reaches) == 1: + # In case there is just one downstream reach, calculate in here + # Starting a recursive approach results in a maximum call stack exception + time += current_reaches[0].value + node_id = current_reaches[0].to_id + previous_reach = current_reaches[0] + else: + # Branching occurred: calculate every possible path and aggregate all values + times = [] + for reach in current_reaches: + times.append( + self.calculate_branch( + reach, + reaches_by_from_node, + reaches_by_id, + list(processed_nodes), + calculated_values, + aggregate_method, + loop_nodes, + feedback, + ) + ) + + if times: + time += aggregate_method(times, feedback) + + return time + + return time + + def calculate_branch( + self, + reach, + reaches_by_from_node, + reaches_by_id, + processed_nodes, + calculated_values, + aggregate_method, + loop_nodes, + feedback, + ): + return reach.value + self.process_node( + reach.to_id, + reach, + reaches_by_from_node, + reaches_by_id, + processed_nodes, + calculated_values, + aggregate_method, + loop_nodes, + feedback, + ) + + @staticmethod + def aggregate_method_min(values, feedback=QgsProcessingFeedback()): + if values: + return min(values) + + return 0 + + @staticmethod + def aggregate_method_max(values, feedback=QgsProcessingFeedback()): + if values: + return max(values) + + return 0 + + @staticmethod + def aggregate_method_mean(values, feedback=QgsProcessingFeedback()): + if values: + return statistics.mean(values) + + return 0 + + @staticmethod + def aggregate_method_not_implemented(values, feedback=QgsProcessingFeedback()): + feedback.pushError("Aggregate method not implemented") diff --git a/plugin/teksi_wastewater/processing_provider/swmm_create_input.py b/plugin/teksi_wastewater/processing_provider/swmm_create_input.py new file mode 100644 index 0000000..8f5525c --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/swmm_create_input.py @@ -0,0 +1,178 @@ +""" +/*************************************************************************** + TWW-swmm processing provider + ------------------- + begin : 07.2019 + copyright : (C) 2019 by ig-group.ch + email : timothee.produit@ig-group.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import datetime + +from qgis.core import ( + QgsProcessingContext, + QgsProcessingFeedback, + QgsProcessingParameterBoolean, + QgsProcessingParameterEnum, + QgsProcessingParameterFile, + QgsProcessingParameterFileDestination, + QgsProcessingParameterString, + QgsProject, +) + +from .tww_algorithm import TwwAlgorithm +from .TwwSwmm import TwwSwmm + +__author__ = "Timothée Produit" +__date__ = "2019-08-01" +__copyright__ = "(C) 2019 by IG-Group.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class SwmmCreateInputAlgorithm(TwwAlgorithm): + """""" + + DATABASE = "DATABASE" + TEMPLATE_INP_FILE = "TEMPLATE_INP_FILE" + INP_FILE = "INP_FILE" + STATE = "STATE" + ONLY_SELECTED = "ONLY_SELECTED" + + def name(self): + return "swmm_create_input" + + def displayName(self): + return self.tr("SWMM Create Input") + + def shortHelpString(self): + return self.tr( + """ + This tool will export the entire PRIMARY network as an input file for SWMM. + If \"Export only selected network\" is selected, the entire selected network is exported, including the secondary network. + Note that at this stage of the development, export of special structures (pumps, weirs, dividers) and related curves must be checked. + Advices to improve the export can be submited as github issues. + See: https://qgep.github.io/docs/tww_swmm/Create-Input.html # TODO should point to new url // skip-keyword-check + """ + ) + + def helpUrl(self): + return "https://qgep.github.io/docs/tww_swmm/Create-Input.html" # TODO should point to new url // skip-keyword-check + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + self.stateOptions = ["current", "planned"] + # The parameters + description = self.tr("Database") + self.addParameter( + QgsProcessingParameterString( + self.DATABASE, description=description, defaultValue="pg_tww" + ) + ) + + description = self.tr("State (current or planned)") + self.addParameter( + QgsProcessingParameterEnum( + self.STATE, + description=description, + options=self.stateOptions, + defaultValue=self.stateOptions[0], + ) + ) + + description = self.tr("Template INP File") + self.addParameter( + QgsProcessingParameterFile( + self.TEMPLATE_INP_FILE, description=description, extension="inp" + ) + ) + + description = self.tr("Destination INP File") + self.addParameter( + QgsProcessingParameterFileDestination( + self.INP_FILE, description=description, fileFilter="inp (*.inp)" + ) + ) + + description = self.tr( + "Export only selected network (including the secondary network if selected)" + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.ONLY_SELECTED, description=description, defaultValue=False + ) + ) + + def processAlgorithm( + self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ): + """Here is where the processing itself takes place.""" + + feedback.setProgress(0) + + # init params + database = self.parameterAsString(parameters, self.DATABASE, context) + state = self.parameterAsString(parameters, self.STATE, context) + template_inp_file = self.parameterAsFile(parameters, self.TEMPLATE_INP_FILE, context) + inp_file = self.parameterAsFileOutput(parameters, self.INP_FILE, context) + state = self.stateOptions[int(state)] + if state not in ["current", "planned"]: + feedback.reportError( + 'State must be "planned" or "current", state was set to "current"' + ) + state = "current" + only_selected = self.parameterAsBoolean(parameters, self.ONLY_SELECTED, context) + + # Get selection + if only_selected: + hierarchy = None + structures_layers = QgsProject.instance().mapLayersByName( + "vw_tww_wastewater_structure" + ) + if structures_layers: + structures = structures_layers[0].selectedFeatures() + selected_structures = [] + for struct in structures: + selected_structures.append(str(struct["wn_obj_id"])) + else: + self.structures = [] + + reaches_layers = QgsProject.instance().mapLayersByName("vw_tww_reach") + if reaches_layers: + reaches = reaches_layers[0].selectedFeatures() + selected_reaches = [] + for reach in reaches: + selected_reaches.append(str(reach["obj_id"])) + else: + hierarchy = "primary" + selected_structures = None + selected_reaches = None + # Connect to TWW database and perform translation + with TwwSwmm( + datetime.datetime.today().isoformat(), + database, + state, + inp_file, + template_inp_file, + None, + None, + feedback, + ) as qs: + qs.write_input(hierarchy, selected_structures, selected_reaches) + feedback.setProgress(100) + + return {self.INP_FILE: inp_file} diff --git a/plugin/teksi_wastewater/processing_provider/swmm_execute.py b/plugin/teksi_wastewater/processing_provider/swmm_execute.py new file mode 100644 index 0000000..014dfc8 --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/swmm_execute.py @@ -0,0 +1,117 @@ +""" +/*************************************************************************** + TWW-swmm processing provider + ------------------- + begin : 07.2019 + copyright : (C) 2019 by ig-group.ch + email : timothee.produit@ig-group.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +import os +import re + +from processing.core.ProcessingConfig import ProcessingConfig +from qgis.core import ( + QgsProcessingContext, + QgsProcessingException, + QgsProcessingFeedback, + QgsProcessingParameterFile, + QgsProcessingParameterFileDestination, +) + +from .tww_algorithm import TwwAlgorithm +from .TwwSwmm import TwwSwmm + +__author__ = "Timothée Produit" +__date__ = "2019-08-01" +__copyright__ = "(C) 2019 by IG-Group.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class SwmmExecuteAlgorithm(TwwAlgorithm): + """""" + + INP_FILE = "INP_FILE" + RPT_FILE = "RPT_FILE" + + def name(self): + return "swmm_execute" + + def displayName(self): + return self.tr("SWMM Execute") + + def shortHelpString(self): + return self.tr( + """ + Launch a swmm simulation. + Note that usually a .inp file exported with TWW is not directly launchable. It must be checked and edited with a SWMM interface. + See: https://qgep.github.io/docs/tww_swmm/Execute.html # TODO should point to new url // skip-keyword-check + """ + ) + + def helpUrl(self): + return "https://qgep.github.io/docs/tww_swmm/Execute.html" # TODO should point to new url // skip-keyword-check + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + + # The parameters + description = self.tr("INP File") + self.addParameter( + QgsProcessingParameterFile(self.INP_FILE, description=description, extension="inp") + ) + + description = self.tr("RPT File") + self.addParameter( + QgsProcessingParameterFileDestination( + self.RPT_FILE, description=description, fileFilter="rpt (*.rpt)" + ) + ) + + def processAlgorithm( + self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ): + """Here is where the processing itself takes place.""" + + # init params + rpt_file = self.parameterAsFile(parameters, self.RPT_FILE, context) + inp_file = self.parameterAsFileOutput(parameters, self.INP_FILE, context) + swmm_cli = os.path.abspath(ProcessingConfig.getSetting("SWMM_PATH")) + if not swmm_cli: + # raise GeoAlgorithmExecutionException( + # 'Swmm command line toom is not configured.\n\ + # Please configure it before running Swmm algorithms.') + raise QgsProcessingException( + self.tr( + "Swmm command line tool is not configured.\n\ + Please configure it before running Swmm algorithms." + ) + ) + + with TwwSwmm(None, None, None, inp_file, None, rpt_file, swmm_cli, feedback) as qs: + prompt = qs.execute_swmm() + + feedback.pushInfo(prompt) + + if re.search("There are errors", prompt): + feedback.reportError(prompt) + feedback.reportError("There were errors, run the file in SWMM GUI for more details") + + feedback.setProgress(100) + + return {} diff --git a/plugin/teksi_wastewater/processing_provider/swmm_extract_results.py b/plugin/teksi_wastewater/processing_provider/swmm_extract_results.py new file mode 100644 index 0000000..d484723 --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/swmm_extract_results.py @@ -0,0 +1,159 @@ +""" +/*************************************************************************** + TWW-swmm processing provider + ------------------- + begin : 07.2019 + copyright : (C) 2019 by ig-group.ch + email : timothee.produit@ig-group.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from PyQt5.QtCore import QVariant +from qgis.core import ( + QgsFeature, + QgsFeatureSink, + QgsField, + QgsFields, + QgsProcessingContext, + QgsProcessingException, + QgsProcessingFeedback, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterFile, +) + +from .tww_algorithm import TwwAlgorithm +from .TwwSwmm import TwwSwmm + +__author__ = "Timothée Produit" +__date__ = "2019-08-01" +__copyright__ = "(C) 2019 by IG-Group.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class SwmmExtractResultsAlgorithm(TwwAlgorithm): + """""" + + RPT_FILE = "RPT_FILE" + NODE_SUMMARY = "NODE_SUMMARY" + LINK_SUMMARY = "LINK_SUMMARY" + XSECTION_SUMMARY = "XSECTION_SUMMARY" + + def name(self): + return "swmm_extract_results" + + def displayName(self): + return self.tr("SWMM Extract Results") + + def shortHelpString(self): + return self.tr( + """ + Import SWMM results in QGIS temporary tables. + See: https://qgep.github.io/docs/tww_swmm/Extract-Results.html # TODO should point to new url // skip-keyword-check + """ + ) + + def helpUrl(self): + return "https://qgep.github.io/docs/tww_swmm/Extract-Results.html" # TODO should point to new url // skip-keyword-check + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + + # The parameters + description = self.tr("RPT File") + self.addParameter( + QgsProcessingParameterFile( + self.RPT_FILE, description=description, fileFilter="rpt (*.rpt)" + ) + ) + + self.addParameter( + QgsProcessingParameterFeatureSink(self.NODE_SUMMARY, self.tr("Node summary")) + ) + + self.addParameter( + QgsProcessingParameterFeatureSink(self.LINK_SUMMARY, self.tr("Link summary")) + ) + + def processAlgorithm( + self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ): + """Here is where the processing itself takes place.""" + + feedback.setProgress(1) + + # init params + rpt_file = self.parameterAsFileOutput(parameters, self.RPT_FILE, context) + + # create feature sink for node summary + fields = QgsFields() + + fields.append(QgsField("id", QVariant.String)) + fields.append(QgsField("type", QVariant.String)) + fields.append(QgsField("average_depth", QVariant.Double)) + fields.append(QgsField("maximum_depth", QVariant.Double)) + fields.append(QgsField("maximum_hgl", QVariant.Double)) + fields.append(QgsField("time_max_day", QVariant.Int)) + fields.append(QgsField("time_max_time", QVariant.String)) + fields.append(QgsField("reported_max_depth", QVariant.Double)) + (sink_node, dest_id) = self.parameterAsSink(parameters, self.NODE_SUMMARY, context, fields) + if sink_node is None: + raise QgsProcessingException(self.invalidSinkError(parameters, self.NODE_SUMMARY)) + + # Get node summary from output file + qs = TwwSwmm(None, None, None, None, None, rpt_file, None, feedback) + node_summary = qs.extract_node_depth_summary() + + # Fill node summary with data + for ns in node_summary: + sf = QgsFeature() + sf.setFields(fields) + for k in ns.keys(): + index = fields.indexOf(k) + if index != -1: + sf.setAttribute(k, ns[k]) + sink_node.addFeature(sf, QgsFeatureSink.FastInsert) + feedback.setProgress(50) + + # create feature sink for link summary + fields = QgsFields() + fields.append(QgsField("id", QVariant.String)) + fields.append(QgsField("type", QVariant.String)) + fields.append(QgsField("maximum_flow", QVariant.Double)) + fields.append(QgsField("time_max_day", QVariant.Int)) + fields.append(QgsField("time_max_time", QVariant.String)) + fields.append(QgsField("maximum_velocity", QVariant.Double)) + fields.append(QgsField("max_over_full_flow", QVariant.Double)) + fields.append(QgsField("max_over_full_depth", QVariant.Double)) + (sink_link, dest_id) = self.parameterAsSink(parameters, self.LINK_SUMMARY, context, fields) + if sink_link is None: + raise QgsProcessingException(self.invalidSinkError(parameters, self.LINK_SUMMARY)) + + # Get link summary from output file + link_summary = qs.extract_link_flow_summary() + + # Fill node summary with data + for ns in link_summary: + sf = QgsFeature() + sf.setFields(fields) + for k in ns.keys(): + index = fields.indexOf(k) + if index != -1: + sf.setAttribute(k, ns[k]) + sink_link.addFeature(sf, QgsFeatureSink.FastInsert) + feedback.setProgress(100) + + return {self.NODE_SUMMARY: sink_node, self.LINK_SUMMARY: sink_link} diff --git a/plugin/teksi_wastewater/processing_provider/swmm_import_results.py b/plugin/teksi_wastewater/processing_provider/swmm_import_results.py new file mode 100644 index 0000000..8d7f9b5 --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/swmm_import_results.py @@ -0,0 +1,161 @@ +""" +/*************************************************************************** + TWW-swmm processing provider + ------------------- + begin : 07.2019 + copyright : (C) 2019 by ig-group.ch + email : timothee.produit@ig-group.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.core import ( + QgsProcessingContext, + QgsProcessingFeedback, + QgsProcessingParameterBoolean, + QgsProcessingParameterFile, + QgsProcessingParameterString, +) + +from .tww_algorithm import TwwAlgorithm +from .TwwSwmm import TwwSwmm + +__author__ = "Timothée Produit" +__date__ = "2021-04-30" +__copyright__ = "(C) 2021 by map.ig-group.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class SwmmImportResultsAlgorithm(TwwAlgorithm): + """""" + + RPT_FILE = "RPT_FILE" + DATABASE = "DATABASE" + SIM_DESCRIPTION = "SIM_DESCRIPTION" + IMPORT_SUMMARY = "IMPORT_SUMMARY" + IMPORT_FULL_RESULTS = "IMPORT_FULL_RESULTS" + POPULATE_BACKFLOW_LEVEL = "POPULATE_BACKFLOW_LEVEL" + POPULATE_HYDRAULIC_LOAD = "POPULATE_HYDRAULIC_LOAD" + + def name(self): + return "swmm_import_results" + + def displayName(self): + return self.tr("SWMM Import Results") + + def shortHelpString(self): + return self.tr( + """ + Import SWMM results in TWW database. + See: https://qgep.github.io/docs/tww_swmm/Extract-Results.html # TODO should point to new url // skip-keyword-check + """ + ) + + def helpUrl(self): + return "https://qgep.github.io/docs/tww_swmm/Import-Results.html" # TODO should point to new url // skip-keyword-check + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + + # The parameters + description = self.tr("SWMM report file (.rpt)") + self.addParameter(QgsProcessingParameterFile(self.RPT_FILE, description=description)) + + description = self.tr("Database") + self.addParameter( + QgsProcessingParameterString( + self.DATABASE, description=description, defaultValue="pg_tww" + ) + ) + + description = self.tr("Simulation name") + self.addParameter( + QgsProcessingParameterString( + self.SIM_DESCRIPTION, + description=description, + defaultValue="SWMM simulation, rain T100, current", + ) + ) + + description = self.tr("Import summary") + self.addParameter( + QgsProcessingParameterBoolean( + self.IMPORT_SUMMARY, description=description, defaultValue=True + ) + ) + + description = self.tr("Import full results") + self.addParameter( + QgsProcessingParameterBoolean( + self.IMPORT_FULL_RESULTS, description=description, defaultValue=False + ) + ) + + description = self.tr("Import Max HGL in tww_od.wastewater_node.backflow_level") + self.addParameter( + QgsProcessingParameterBoolean( + self.POPULATE_BACKFLOW_LEVEL, + description=description, + defaultValue=False, + ) + ) + + description = self.tr( + "Import Max/Full Flow in tww_od.reach.dss2020_hydraulic_load_current" + ) + self.addParameter( + QgsProcessingParameterBoolean( + self.POPULATE_HYDRAULIC_LOAD, + description=description, + defaultValue=False, + ) + ) + + def processAlgorithm( + self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ): + """Here is where the processing itself takes place.""" + + feedback.pushInfo("The import started, it can take a few minutes.") + feedback.setProgress(1) + + # init params + rpt_file = self.parameterAsFileOutput(parameters, self.RPT_FILE, context) + database = self.parameterAsString(parameters, self.DATABASE, context) + sim_description = self.parameterAsString(parameters, self.SIM_DESCRIPTION, context) + import_summary = self.parameterAsBoolean(parameters, self.IMPORT_SUMMARY, context) + import_full_result = self.parameterAsBoolean(parameters, self.IMPORT_FULL_RESULTS, context) + import_backflow_level = self.parameterAsBoolean( + parameters, self.POPULATE_BACKFLOW_LEVEL, context + ) + import_hydraulic_load = self.parameterAsBoolean( + parameters, self.POPULATE_HYDRAULIC_LOAD, context + ) + + # Get node summary from output file + with TwwSwmm(sim_description, database, None, None, None, rpt_file, None, feedback) as qs: + if import_summary: + qs.import_summary(sim_description) + if import_full_result: + qs.import_full_results(sim_description) + if import_backflow_level: + qs.import_backflow_level() + if import_hydraulic_load: + qs.import_hydraulic_load() + + feedback.setProgress(100) + + return {} diff --git a/plugin/teksi_wastewater/processing_provider/swmm_set_friction.py b/plugin/teksi_wastewater/processing_provider/swmm_set_friction.py new file mode 100644 index 0000000..209d275 --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/swmm_set_friction.py @@ -0,0 +1,104 @@ +""" +/*************************************************************************** + TWW-swmm processing provider + ------------------- + begin : 07.2019 + copyright : (C) 2019 by ig-group.ch + email : timothee.produit@ig-group.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.core import ( + QgsProcessingContext, + QgsProcessingFeedback, + QgsProcessingParameterBoolean, + QgsProcessingParameterString, +) + +from .tww_algorithm import TwwAlgorithm +from .TwwSwmm import TwwSwmm + +__author__ = "Timothée Produit" +__date__ = "2019-08-01" +__copyright__ = "(C) 2019 by IG-Group.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class SwmmSetFrictionAlgorithm(TwwAlgorithm): + """""" + + DATABASE = "DATABASE" + OVERWRITE_VALUES = "OVERWRITE_VALUES" + + def name(self): + return "swmm_set_friction" + + def displayName(self): + return self.tr("SWMM Set default coefficient of friction") + + def shortHelpString(self): + return self.tr( + """ + Fill the attribute tww_od.reach.default_coefficient_of_friction where it is not filled. + If \"Overwrite existing default values\" is selected, all the default_coefficient_of_friction will be reseted. + See: https://qgep.github.io/docs/tww_swmm/coefficient_of_friction.html # TODO should point to new url // skip-keyword-check + """ + ) + + def helpUrl(self): + return "https://qgep.github.io/docs/tww_swmm/coefficient_of_friction.html" # TODO should point to new url // skip-keyword-check + + def initAlgorithm(self, config=None): + """Here we define the inputs and output of the algorithm, along + with some other properties. + """ + + # The parameters + description = self.tr("Database") + self.addParameter( + QgsProcessingParameterString( + self.DATABASE, description=description, defaultValue="pg_tww" + ) + ) + + description = self.tr("Overwrite existing default values") + self.addParameter( + QgsProcessingParameterBoolean( + self.OVERWRITE_VALUES, description=description, defaultValue=False + ) + ) + + def processAlgorithm( + self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback + ): + """Here is where the processing itself takes place.""" + + feedback.setProgress(0) + + # init params + database = self.parameterAsString(parameters, self.DATABASE, context) + overwrite_values = self.parameterAsBoolean(parameters, self.OVERWRITE_VALUES, context) + # Connect to TWW database and perform translation + with TwwSwmm(None, database, None, None, None, None, None, feedback) as qs: + qs.disable_reach_trigger() + if overwrite_values: + qs.overwrite_reach_default_friction() + else: + qs.set_reach_default_friction() + qs.enable_reach_trigger() + + feedback.setProgress(100) + + return {} diff --git a/plugin/teksi_wastewater/processing_provider/tww_algorithm.py b/plugin/teksi_wastewater/processing_provider/tww_algorithm.py new file mode 100644 index 0000000..4f1a568 --- /dev/null +++ b/plugin/teksi_wastewater/processing_provider/tww_algorithm.py @@ -0,0 +1,49 @@ +""" +/*************************************************************************** + TWW processing provider + ------------------- + begin : 15.08.2018 + copyright : (C) 2018 by OPENGIS.ch + email : matthias@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from PyQt5.QtCore import QCoreApplication +from qgis.core import QgsProcessingAlgorithm + +__author__ = "Matthias Kuhn" +__date__ = "2018-08-15" +__copyright__ = "(C) 2018 by OPENGIS.ch" + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = "$Format:%H$" + + +class TwwAlgorithm(QgsProcessingAlgorithm): + """ + Boilerplate code for TWW algorithms + """ + + def group(self): + return "TWW" + + def groupId(self): + return "tww" + + def tr(self, string, context=""): + if context == "": + context = self.__class__.__name__ + return QCoreApplication.translate(context, string) + + def createInstance(self): + return type(self)() diff --git a/plugin/teksi_wastewater/svgprofile/.gitignore b/plugin/teksi_wastewater/svgprofile/.gitignore new file mode 100644 index 0000000..4683d38 --- /dev/null +++ b/plugin/teksi_wastewater/svgprofile/.gitignore @@ -0,0 +1,2 @@ +*~ +dojo/ diff --git a/plugin/teksi_wastewater/svgprofile/d3.v2.min.js b/plugin/teksi_wastewater/svgprofile/d3.v2.min.js new file mode 100644 index 0000000..5cb4237 --- /dev/null +++ b/plugin/teksi_wastewater/svgprofile/d3.v2.min.js @@ -0,0 +1,4 @@ +(function(){function e(e,t){try{for(var n in t)Object.defineProperty(e.prototype,n,{value:t[n],enumerable:!1})}catch(r){e.prototype=t}}function t(e){var t=-1,n=e.length,r=[];while(++t=0?e.substring(t):(t=e.length,""),r=[];while(t>0)r.push(e.substring(t-=3,t+3));return r.reverse().join(",")+n}function b(e,t){var n=Math.pow(10,Math.abs(8-t)*3);return{scale:t>8?function(e){return e/n}:function(e){return e*n},symbol:e}}function w(e){return function(t){return t<=0?0:t>=1?1:e(t)}}function E(e){return function(t){return 1-e(1-t)}}function S(e){return function(t){return.5*(t<.5?e(2*t):2-e(2-2*t))}}function x(e){return e}function T(e){return function(t){return Math.pow(t,e)}}function N(e){return 1-Math.cos(e*Math.PI/2)}function C(e){return Math.pow(2,10*(e-1))}function k(e){return 1-Math.sqrt(1-e*e)}function L(e,t){var n;return arguments.length<2&&(t=.45),arguments.length<1?(e=1,n=t/4):n=t/(2*Math.PI)*Math.asin(1/e),function(r){return 1+e*Math.pow(2,10*-r)*Math.sin((r-n)*2*Math.PI/t)}}function A(e){return e||(e=1.70158),function(t){return t*t*((e+1)*t-e)}}function O(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375}function M(){d3.event.stopPropagation(),d3.event.preventDefault()}function _(){var e=d3.event,t;while(t=e.sourceEvent)e=t;return e}function D(e){var t=new d,n=0,r=arguments.length;while(++n360?e-=360:e<0&&(e+=360),e<60?s+(o-s)*e/60:e<180?o:e<240?s+(o-s)*(240-e)/60:s}function i(e){return Math.round(r(e)*255)}var s,o;return e%=360,e<0&&(e+=360),t=t<0?0:t>1?1:t,n=n<0?0:n>1?1:n,o=n<=.5?n*(1+t):n+t-n*t,s=2*n-o,U(i(e+120),i(e),i(e-120))}function Z(e,t,n){return new et(e,t,n)}function et(e,t,n){this.h=e,this.c=t,this.l=n}function tt(e,t,n){return nt(n,Math.cos(e*=Math.PI/180)*t,Math.sin(e)*t)}function nt(e,t,n){return new rt(e,t,n)}function rt(e,t,n){this.l=e,this.a=t,this.b=n}function it(e,t,n){var r=(e+16)/116,i=r+t/500,s=r-n/200;return i=ot(i)*ys,r=ot(r)*bs,s=ot(s)*ws,U(at(3.2404542*i-1.5371385*r-.4985314*s),at(-0.969266*i+1.8760108*r+.041556*s),at(.0556434*i-.2040259*r+1.0572252*s))}function st(e,t,n){return Z(Math.atan2(n,t)/Math.PI*180,Math.sqrt(t*t+n*n),e)}function ot(e){return e>.206893034?e*e*e:(e-4/29)/7.787037}function ut(e){return e>.008856?Math.pow(e,1/3):7.787037*e+4/29}function at(e){return Math.round(255*(e<=.00304?12.92*e:1.055*Math.pow(e,1/2.4)-.055))}function ft(e){return Qi(e,ks),e}function lt(e){return function(){return Ss(e,this)}}function ct(e){return function(){return xs(e,this)}}function ht(e,t){function n(){this.removeAttribute(e)}function r(){this.removeAttributeNS(e.space,e.local)}function i(){this.setAttribute(e,t)}function s(){this.setAttributeNS(e.space,e.local,t)}function o(){var n=t.apply(this,arguments);n==null?this.removeAttribute(e):this.setAttribute(e,n)}function u(){var n=t.apply(this,arguments);n==null?this.removeAttributeNS(e.space,e.local):this.setAttributeNS(e.space,e.local,n)}return e=d3.ns.qualify(e),t==null?e.local?r:n:typeof t=="function"?e.local?u:o:e.local?s:i}function pt(e){return new RegExp("(?:^|\\s+)"+d3.requote(e)+"(?:\\s+|$)","g")}function dt(e,t){function n(){var n=-1;while(++n0&&(e=e.substring(0,o)),t?i:r}function St(e,t){for(var n=0,r=e.length;nt?c():(v.active=t,i.forEach(function(t,n){(n=n.call(e,m,u))&&h.push(n)}),s.start.call(e,m,u),l(r)||d3.timer(l,0,n),1)}function l(n){if(v.active!==t)return c();var r=(n-p)/d,i=o(r),a=h.length;while(a>0)h[--a].call(e,i);if(r>=1)return c(),_s=t,s.end.call(e,m,u),_s=0,1}function c(){return--v.count||delete e.__transition__,1}var h=[],p=e.delay,d=e.duration,v=(e=e.node).__transition__||(e.__transition__={active:0,count:0}),m=e.__data__;++v.count,p<=r?f(r):d3.timer(f,p,n)})},0,n),e}function Nt(e){var t=_s,n=Fs,r=Bs,i=js;return _s=this.id,Fs=this.ease(),St(this,function(t,n,r){Bs=t.delay,js=t.duration,e.call(t=t.node,t.__data__,n,r)}),_s=t,Fs=n,Bs=r,js=i,this}function Ct(e,t,n){return n!=""&&Is}function kt(e,t){return d3.tween(e,F(t))}function Lt(){var e,t=Date.now(),n=Us;while(n)e=t-n.then,e>=n.delay&&(n.flush=n.callback(e)),n=n.next;var r=At()-t;r>24?(isFinite(r)&&(clearTimeout(Ws),Ws=setTimeout(Lt,r)),zs=0):(zs=1,Xs(Lt))}function At(){var e=null,t=Us,n=Infinity;while(t)t.flush?(delete Rs[t.callback.id],t=e?e.next=t.next:Us=t.next):(n=Math.min(n,t.then+t.delay),t=(e=t).next);return n}function Ot(e,t){var n=e.ownerSVGElement||e;if(n.createSVGPoint){var r=n.createSVGPoint();if(Vs<0&&(window.scrollX||window.scrollY)){n=d3.select(document.body).append("svg").style("position","absolute").style("top",0).style("left",0);var i=n[0][0].getScreenCTM();Vs=!i.f&&!i.e,n.remove()}return Vs?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(e.getScreenCTM().inverse()),[r.x,r.y]}var s=e.getBoundingClientRect();return[t.clientX-s.left-e.clientLeft,t.clientY-s.top-e.clientTop]}function Mt(){}function _t(e){var t=e[0],n=e[e.length-1];return t2?zt:Ut,a=r?q:I;return o=i(e,t,a,n),u=i(t,e,a,d3.interpolate),s}function s(e){return o(e)}var o,u;return s.invert=function(e){return u(e)},s.domain=function(t){return arguments.length?(e=t.map(Number),i()):e},s.range=function(e){return arguments.length?(t=e,i()):t},s.rangeRound=function(e){return s.range(e).interpolate(d3.interpolateRound)},s.clamp=function(e){return arguments.length?(r=e,i()):r},s.interpolate=function(e){return arguments.length?(n=e,i()):n},s.ticks=function(t){return qt(e,t)},s.tickFormat=function(t){return Rt(e,t)},s.nice=function(){return Pt(e,Ft),i()},s.copy=function(){return Bt(e,t,n,r)},i()}function jt(e,t){return d3.rebind(e,t,"range","rangeRound","interpolate","clamp")}function Ft(e){return e=Math.pow(10,Math.round(Math.log(e)/Math.LN10)-1),e&&{floor:function(t){return Math.floor(t/e)*e},ceil:function(t){return Math.ceil(t/e)*e}}}function It(e,t){var n=_t(e),r=n[1]-n[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),s=t/r*i;return s<=.15?i*=10:s<=.35?i*=5:s<=.75&&(i*=2),n[0]=Math.ceil(n[0]/i)*i,n[1]=Math.floor(n[1]/i)*i+i*.5,n[2]=i,n}function qt(e,t){return d3.range.apply(d3,It(e,t))}function Rt(e,t){return d3.format(",."+Math.max(0,-Math.floor(Math.log(It(e,t)[2])/Math.LN10+.01))+"f")}function Ut(e,t,n,r){var i=n(e[0],e[1]),s=r(t[0],t[1]);return function(e){return s(i(e))}}function zt(e,t,n,r){var i=[],s=[],o=0,u=Math.min(e.length,t.length)-1;e[u]0;f--)i.push(r(s)*f)}else{for(;sa;o--);i=i.slice(s,o)}return i},n.tickFormat=function(e,i){arguments.length<2&&(i=$s);if(arguments.length<1)return i;var s=Math.max(.1,e/n.ticks().length),o=t===Vt?(u=-1e-12,Math.floor):(u=1e-12,Math.ceil),u;return function(e){return e/r(o(t(e)+u))<=s?i(e):""}},n.copy=function(){return Wt(e.copy(),t)},jt(n,e)}function Xt(e){return Math.log(e<0?0:e)/Math.LN10}function Vt(e){return-Math.log(e>0?0:-e)/Math.LN10}function $t(e,t){function n(t){return e(r(t))}var r=Jt(t),i=Jt(1/t);return n.invert=function(t){return i(e.invert(t))},n.domain=function(t){return arguments.length?(e.domain(t.map(r)),n):e.domain().map(i)},n.ticks=function(e){return qt(n.domain(),e)},n.tickFormat=function(e){return Rt(n.domain(),e)},n.nice=function(){return n.domain(Pt(n.domain(),Ft))},n.exponent=function(e){if(!arguments.length)return t;var s=n.domain();return r=Jt(t=e),i=Jt(1/t),n.domain(s)},n.copy=function(){return $t(e.copy(),t)},jt(n,e)}function Jt(e){return function(t){return t<0?-Math.pow(-t,e):Math.pow(t,e)}}function Kt(e,t){function n(t){return o[((s.get(t)||s.set(t,e.push(t)))-1)%o.length]}function i(t,n){return d3.range(e.length).map(function(e){return t+n*e})}var s,o,u;return n.domain=function(i){if(!arguments.length)return e;e=[],s=new r;var o=-1,u=i.length,a;while(++o1){u=t[1],s=e[a],a++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(s[0]-u[0])+","+(s[1]-u[1])+","+s[0]+","+s[1];for(var f=2;f9&&(s=n*3/Math.sqrt(s),o[u]=s*r,o[u+1]=s*i));u=-1;while(++u<=a)s=(e[Math.min(a,u+1)][0]-e[Math.max(0,u-1)][0])/(6*(1+o[u]*o[u])),t.push([s||0,o[u]*s||0]);return t}function Cn(e){return e.length<3?an(e):e[0]+vn(e,Nn(e))}function kn(e){var t,n=-1,r=e.length,i,s;while(++n1){var r=_t(e.domain()),i,s=-1,o=t.length,u=(t[1]-t[0])/++n,a,f;while(++s0;)(f=+t[s]-a*u)>=r[0]&&i.push(f);for(--s,a=0;++ar&&(n=t,r=i);return n}function sr(e){return e.reduce(or,0)}function or(e,t){return e+t[1]}function ur(e,t){return ar(e,Math.ceil(Math.log(t.length)/Math.LN2+1))}function ar(e,t){var n=-1,r=+e[0],i=(e[1]-r)/t,s=[];while(++n<=t)s[n]=i*n+r;return s}function fr(e){return[d3.min(e),d3.max(e)]}function lr(e,t){return d3.rebind(e,t,"sort","children","value"),e.links=dr,e.nodes=function(t){return vo=!0,(e.nodes=e)(t)},e}function cr(e){return e.children}function hr(e){return e.value}function pr(e,t){return t.value-e.value}function dr(e){return d3.merge(e.map(function(e){return(e.children||[]).map(function(t){return{source:e,target:t}})}))}function vr(e,t){return e.value-t.value}function mr(e,t){var n=e._pack_next;e._pack_next=t,t._pack_prev=e,t._pack_next=n,n._pack_prev=t}function gr(e,t){e._pack_next=t,t._pack_prev=e}function yr(e,t){var n=t.x-e.x,r=t.y-e.y,i=e.r+t.r;return i*i-n*n-r*r>.001}function br(e){function t(e){r=Math.min(e.x-e.r,r),i=Math.max(e.x+e.r,i),s=Math.min(e.y-e.r,s),o=Math.max(e.y+e.r,o)}if(!(n=e.children)||!(p=n.length))return;var n,r=Infinity,i=-Infinity,s=Infinity,o=-Infinity,u,a,f,l,c,h,p;n.forEach(wr),u=n[0],u.x=-u.r,u.y=0,t(u);if(p>1){a=n[1],a.x=a.r,a.y=0,t(a);if(p>2){f=n[2],xr(u,a,f),t(f),mr(u,f),u._pack_prev=f,mr(f,a),a=u._pack_next;for(l=3;l0&&(e=r)}return e}function _r(e,t){return e.x-t.x}function Dr(e,t){return t.x-e.x}function Pr(e,t){return e.depth-t.depth}function Hr(e,t){function n(e,r){var i=e.children;if(i&&(a=i.length)){var s,o=null,u=-1,a;while(++u=0)s=r[i]._tree,s.prelim+=t,s.mod+=t,t+=s.shift+(n+=s.change)}function jr(e,t,n){e=e._tree,t=t._tree;var r=n/(t.number-e.number);e.change+=r,t.change-=r,t.shift+=n,t.prelim+=n,t.mod+=n}function Fr(e,t,n){return e._tree.ancestor.parent==t.parent?e._tree.ancestor:n}function Ir(e){return{x:e.x,y:e.y,dx:e.dx,dy:e.dy}}function qr(e,t){var n=e.x+t[3],r=e.y+t[0],i=e.dx-t[1]-t[3],s=e.dy-t[0]-t[2];return i<0&&(n+=i/2,i=0),s<0&&(r+=s/2,s=0),{x:n,y:r,dx:i,dy:s}}function Rr(e,t){function n(e,r){d3.text(e,t,function(e){r(e&&n.parse(e))})}function r(t){return t.map(i).join(e)}function i(e){return o.test(e)?'"'+e.replace(/\"/g,'""')+'"':e}var s=new RegExp("\r\n|["+e+"\r\n]","g"),o=new RegExp('["'+e+"\n]"),u=e.charCodeAt(0);return n.parse=function(e){var t;return n.parseRows(e,function(e,n){if(n){var r={},i=-1,s=t.length;while(++i=e.length)return i;if(l)return l=!1,r;var t=s.lastIndex;if(e.charCodeAt(t)===34){var n=t;while(n++0}function si(e,t,n){return(n[0]-t[0])*(e[1]-t[1])<(n[1]-t[1])*(e[0]-t[0])}function oi(e,t,n,r){var i=e[0],s=t[0],o=n[0],u=r[0],a=e[1],f=t[1],l=n[1],c=r[1],h=i-o,p=s-i,d=u-o,v=a-l,m=f-a,g=c-l,y=(d*v-g*h)/(g*p-d*m);return[i+y*p,a+y*m]}function ui(e,t){var n={list:e.map(function(e,t){return{index:t,x:e[0],y:e[1]}}).sort(function(e,t){return e.yt.y?1:e.xt.x?1:0}),bottomSite:null},r={list:[],leftEnd:null,rightEnd:null,init:function(){r.leftEnd=r.createHalfEdge(null,"l"),r.rightEnd=r.createHalfEdge(null,"l"),r.leftEnd.r=r.rightEnd,r.rightEnd.l=r.leftEnd,r.list.unshift(r.leftEnd,r.rightEnd)},createHalfEdge:function(e,t){return{edge:e,side:t,vertex:null,l:null,r:null}},insert:function(e,t){t.l=e,t.r=e.r,e.r.l=t,e.r=t},leftBound:function(e){var t=r.leftEnd;do t=t.r;while(t!=r.rightEnd&&i.rightOf(t,e));return t=t.l,t},del:function(e){e.l.r=e.r,e.r.l=e.l,e.edge=null},right:function(e){return e.r},left:function(e){return e.l},leftRegion:function(e){return e.edge==null?n.bottomSite:e.edge.region[e.side]},rightRegion:function(e){return e.edge==null?n.bottomSite:e.edge.region[wo[e.side]]}},i={bisect:function(e,t){var n={region:{l:e,r:t},ep:{l:null,r:null}},r=t.x-e.x,i=t.y-e.y,s=r>0?r:-r,o=i>0?i:-i;return n.c=e.x*r+e.y*i+(r*r+i*i)*.5,s>o?(n.a=1,n.b=i/r,n.c/=r):(n.b=1,n.a=r/i,n.c/=i),n},intersect:function(e,t){var n=e.edge,r=t.edge;if(!n||!r||n.region.r==r.region.r)return null;var i=n.a*r.b-n.b*r.a;if(Math.abs(i)<1e-10)return null;var s=(n.c*r.b-r.c*n.b)/i,o=(r.c*n.a-n.c*r.a)/i,u=n.region.r,a=r.region.r,f,l;u.y=l.region.r.x;return c&&f.side==="l"||!c&&f.side==="r"?null:{x:s,y:o}},rightOf:function(e,t){var n=e.edge,r=n.region.r,i=t.x>r.x;if(i&&e.side==="l")return 1;if(!i&&e.side==="r")return 0;if(n.a===1){var s=t.y-r.y,o=t.x-r.x,u=0,a=0;!i&&n.b<0||i&&n.b>=0?a=u=s>=n.b*o:(a=t.x+t.y*n.b>n.c,n.b<0&&(a=!a),a||(u=1));if(!u){var f=r.x-n.region.l.x;a=n.b*(o*o-s*s)h*h+p*p}return e.side==="l"?a:!a},endPoint:function(e,n,r){e.ep[n]=r;if(!e.ep[wo[n]])return;t(e)},distance:function(e,t){var n=e.x-t.x,r=e.y-t.y;return Math.sqrt(n*n+r*r)}},s={list:[],insert:function(e,t,n){e.vertex=t,e.ystar=t.y+n;for(var r=0,i=s.list,o=i.length;ru.ystar||e.ystar==u.ystar&&t.x>u.vertex.x)continue;break}i.splice(r,0,e)},del:function(e){for(var t=0,n=s.list,r=n.length;td.y&&(v=p,p=d,d=v,b="r"),y=i.bisect(p,d),h=r.createHalfEdge(y,b),r.insert(l,h),i.endPoint(y,wo[b],g),m=i.intersect(l,h),m&&(s.del(l),s.insert(l,m,i.distance(m,p))),m=i.intersect(h,c),m&&s.insert(h,m,i.distance(m,p))}}for(a=r.right(r.leftEnd);a!=r.rightEnd;a=r.right(a))t(a.edge)}function ai(){return{leaf:!0,nodes:[],point:null}}function fi(e,t,n,r,i,s){if(!e(t,n,r,i,s)){var o=(n+i)*.5,u=(r+s)*.5,a=t.nodes;a[0]&&fi(e,a[0],n,r,o,u),a[1]&&fi(e,a[1],o,r,i,u),a[2]&&fi(e,a[2],n,u,o,s),a[3]&&fi(e,a[3],o,u,i,s)}}function li(e){return{x:e[0],y:e[1]}}function ci(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function hi(e){return e.substring(0,3)}function pi(e,t,n,r){var i,s,o=0,u=t.length,a=n.length;while(o=a)return-1;i=t.charCodeAt(o++);if(i==37){s=Uo[t.charAt(o++)];if(!s||(r=s(e,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}function di(e){return new RegExp("^(?:"+e.map(d3.requote).join("|")+")","i")}function vi(e){var t=new r,n=-1,i=e.length;while(++n68?1900:2e3)}function Ci(e,t,n){zo.lastIndex=0;var r=zo.exec(t.substring(n,n+2));return r?(e.m=r[0]-1,n+=r[0].length):-1}function ki(e,t,n){zo.lastIndex=0;var r=zo.exec(t.substring(n,n+2));return r?(e.d=+r[0],n+=r[0].length):-1}function Li(e,t,n){zo.lastIndex=0;var r=zo.exec(t.substring(n,n+2));return r?(e.H=+r[0],n+=r[0].length):-1}function Ai(e,t,n){zo.lastIndex=0;var r=zo.exec(t.substring(n,n+2));return r?(e.M=+r[0],n+=r[0].length):-1}function Oi(e,t,n){zo.lastIndex=0;var r=zo.exec(t.substring(n,n+2));return r?(e.S=+r[0],n+=r[0].length):-1}function Mi(e,t,n){zo.lastIndex=0;var r=zo.exec(t.substring(n,n+3));return r?(e.L=+r[0],n+=r[0].length):-1}function _i(e,t,n){var r=Wo.get(t.substring(n,n+=2).toLowerCase());return r==null?-1:(e.p=r,n)}function Di(e){var t=e.getTimezoneOffset(),n=t>0?"-":"+",r=~~(Math.abs(t)/60),i=Math.abs(t)%60;return n+Mo(r)+Mo(i)}function Pi(e){return e.toISOString()}function Hi(e,t,n){function r(t){var n=e(t),r=s(n,1);return t-n1)while(ot?1:e>=t?0:NaN},d3.descending=function(e,t){return te?1:t>=e?0:NaN},d3.mean=function(e,t){var n=e.length,r,i=0,s=-1,o=0;if(arguments.length===1)while(++s1&&(e=e.map(t)),e=e.filter(f),e.length?d3.quantile(e.sort(d3.ascending),.5):undefined},d3.min=function(e,t){var n=-1,r=e.length,i,s;if(arguments.length===1){while(++ns&&(i=s)}else{while(++ns&&(i=s)}return i},d3.max=function(e,t){var n=-1,r=e.length,i,s;if(arguments.length===1){while(++ni&&(i=s)}else{while(++ni&&(i=s)}return i},d3.extent=function(e,t){var n=-1,r=e.length,i,s,o;if(arguments.length===1){while(++ns&&(i=s),os&&(i=s),o1);return e+t*n*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(e,t){var n=arguments.length;n<2&&(t=1),n<1&&(e=0);var r=d3.random.normal();return function(){return Math.exp(e+t*r())}},irwinHall:function(e){return function(){for(var t=0,n=0;n>>1;e.call(t,t[s],s)>>1;n0&&(i=s);return i},d3.last=function(e,t){var n=0,r=e.length,i=e[0],s;arguments.length===1&&(t=d3.ascending);while(++n=i.length)return u?u.call(n,t):o?t.sort(o):t;var a=-1,f=t.length,l=i[s++],c,h,p=new r,d,v={};while(++a=i.length)return e;var r=[],o=s[n++],u;for(u in e)r.push({key:u,values:t(e[u],n)});return o&&r.sort(function(e,t){return o(e.key,t.key)}),r}var n={},i=[],s=[],o,u;return n.map=function(t){return e(t,0)},n.entries=function(n){return t(e(n,0),0)},n.key=function(e){return i.push(e),n},n.sortKeys=function(e){return s[i.length-1]=e,n},n.sortValues=function(e){return o=e,n},n.rollup=function(e){return u=e,n},n},d3.keys=function(e){var t=[];for(var n in e)t.push(n);return t},d3.values=function(e){var t=[];for(var n in e)t.push(e[n]);return t},d3.entries=function(e){var t=[];for(var n in e)t.push({key:n,value:e[n]});return t},d3.permute=function(e,t){var n=[],r=-1,i=t.length;while(++rt)r.push(o/i);else while((o=e+n*++s)=200&&e<300||e===304?r:null)}},r.send(null)},d3.text=function(e,t,n){function r(e){n(e&&e.responseText)}arguments.length<3&&(n=t,t=null),d3.xhr(e,t,r)},d3.json=function(e,t){d3.text(e,"application/json",function(e){t(e?JSON.parse(e):null)})},d3.html=function(e,t){d3.text(e,"text/html",function(e){if(e!=null){var n=document.createRange();n.selectNode(document.body),e=n.createContextualFragment(e)}t(e)})},d3.xml=function(e,t,n){function r(e){n(e&&e.responseXML)}arguments.length<3&&(n=t,t=null),d3.xhr(e,t,r)};var ts={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};d3.ns={prefix:ts,qualify:function(e){var t=e.indexOf(":"),n=e;return t>=0&&(n=e.substring(0,t),e=e.substring(t+1)),ts.hasOwnProperty(n)?{space:ts[n],local:e}:e}},d3.dispatch=function(){var e=new d,t=-1,n=arguments.length;while(++t0&&(r=e.substring(n+1),e=e.substring(0,n)),arguments.length<2?this[e].on(r):this[e].on(r,t)},d3.format=function(e){var t=ns.exec(e),n=t[1]||" ",r=t[3]||"",i=t[5],s=+t[6],o=t[7],u=t[8],a=t[9],f=1,l="",c=!1;u&&(u=+u.substring(1)),i&&(n="0",o&&(s-=Math.floor((s-1)/4)));switch(a){case"n":o=!0,a="g";break;case"%":f=100,l="%",a="f";break;case"p":f=100,l="%",a="r";break;case"d":c=!0,u=0;break;case"s":f=-1,a="r"}return a=="r"&&!u&&(a="g"),a=rs.get(a)||g,function(e){if(c&&e%1)return"";var t=e<0&&(e=-e)?"-":r;if(f<0){var h=d3.formatPrefix(e,u);e=h.scale(e),l=h.symbol}else e*=f;e=a(e,u);if(i){var p=e.length+t.length;p=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,rs=d3.map({g:function(e,t){return e.toPrecision(t)},e:function(e,t){return e.toExponential(t)},f:function(e,t){return e.toFixed(t)},r:function(e,t){return d3.round(e,t=m(e,t)).toFixed(Math.max(0,Math.min(20,t)))}}),is=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(b);d3.formatPrefix=function(e,t){var n=0;return e&&(e<0&&(e*=-1),t&&(e=d3.round(e,m(e,t))),n=1+Math.floor(1e-12+Math.log(e)/Math.LN10),n=Math.max(-24,Math.min(24,Math.floor((n<=0?n+1:n-1)/3)*3))),is[8+n/3]};var ss=T(2),os=T(3),us=function(){return x},as=d3.map({linear:us,poly:T,quad:function(){return ss},cubic:function(){return os},sin:function(){return N},exp:function(){return C},circle:function(){return k},elastic:L,back:A,bounce:function(){return O}}),fs=d3.map({"in":x,out:E,"in-out":S,"out-in":function(e){return S(E(e))}});d3.ease=function(e){var t=e.indexOf("-"),n=t>=0?e.substring(0,t):e,r=t>=0?e.substring(t+1):"in";return n=as.get(n)||us,r=fs.get(r)||x,w(r(n.apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.transform=function(e){var t=document.createElementNS(d3.ns.prefix.svg,"g");return(d3.transform=function(e){t.setAttribute("transform",e);var n=t.transform.baseVal.consolidate();return new P(n?n.matrix:cs)})(e)},P.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var ls=180/Math.PI,cs={a:1,b:0,c:0,d:1,e:0,f:0};d3.interpolate=function(e,t){var n=d3.interpolators.length,r;while(--n>=0&&!(r=d3.interpolators[n](e,t)));return r},d3.interpolateNumber=function(e,t){return t-=e,function(n){return e+t*n}},d3.interpolateRound=function(e,t){return t-=e,function(n){return Math.round(e+t*n)}},d3.interpolateString=function(e,t){var n,r,i,s=0,o=0,u=[],a=[],f,l;hs.lastIndex=0;for(r=0;n=hs.exec(t);++r)n.index&&u.push(t.substring(s,o=n.index)),a.push({i:u.length,x:n[0]}),u.push(null),s=hs.lastIndex;s180?l+=360:l-f>180&&(f+=360),r.push({i:n.push(n.pop()+"rotate(",null,")")-2,x:d3.interpolateNumber(f,l)})):l&&n.push(n.pop()+"rotate("+l+")"),c!=h?r.push({i:n.push(n.pop()+"skewX(",null,")")-2,x:d3.interpolateNumber(c,h)}):h&&n.push(n.pop()+"skewX("+h+")"),p[0]!=d[0]||p[1]!=d[1]?(i=n.push(n.pop()+"scale(",null,",",null,")"),r.push({i:i-4,x:d3.interpolateNumber(p[0],d[0])},{i:i-2,x:d3.interpolateNumber(p[1],d[1])})):(d[0]!=1||d[1]!=1)&&n.push(n.pop()+"scale("+d+")"),i=r.length,function(e){var t=-1,s;while(++t180?s-=360:s<-180&&(s+=360),function(e){return Y(n+s*e,r+o*e,i+u*e)+""}},d3.interpolateLab=function(e,t){e=d3.lab(e),t=d3.lab(t);var n=e.l,r=e.a,i=e.b,s=t.l-n,o=t.a-r,u=t.b-i;return function(e){return it(n+s*e,r+o*e,i+u*e)+""}},d3.interpolateHcl=function(e,t){e=d3.hcl(e),t=d3.hcl(t);var n=e.h,r=e.c,i=e.l,s=t.h-n,o=t.c-r,u=t.l-i;return s>180?s-=360:s<-180&&(s+=360),function(e){return tt(n+s*e,r+o*e,i+u*e)+""}},d3.interpolateArray=function(e,t){var n=[],r=[],i=e.length,s=t.length,o=Math.min(e.length,t.length),u;for(u=0;u=0;)if(s=n[r])i&&i!==s.nextSibling&&i.parentNode.insertBefore(s,i),i=s;return this},ks.sort=function(e){e=wt.apply(this,arguments);for(var t=-1,n=this.length;++t=Zs?e?"M0,"+s+"A"+s+","+s+" 0 1,1 0,"+ -s+"A"+s+","+s+" 0 1,1 0,"+s+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+s+"A"+s+","+s+" 0 1,1 0,"+ -s+"A"+s+","+s+" 0 1,1 0,"+s+"Z":e?"M"+s*l+","+s*c+"A"+s+","+s+" 0 "+f+",1 "+s*h+","+s*p+"L"+e*h+","+e*p+"A"+e+","+e+" 0 "+f+",0 "+e*l+","+e*c+"Z":"M"+s*l+","+s*c+"A"+s+","+s+" 0 "+f+",1 "+s*h+","+s*p+"L0,0"+"Z"}var t=en,n=tn,r=nn,i=rn;return e.innerRadius=function(n){return arguments.length?(t=u(n),e):t},e.outerRadius=function(t){return arguments.length?(n=u(t),e):n},e.startAngle=function(t){return arguments.length?(r=u(t),e):r},e.endAngle=function(t){return arguments.length?(i=u(t),e):i},e.centroid=function(){var e=(t.apply(this,arguments)+n.apply(this,arguments))/2,s=(r.apply(this,arguments)+i.apply(this,arguments))/2+Ys;return[Math.cos(s)*e,Math.sin(s)*e]},e};var Ys=-Math.PI/2,Zs=2*Math.PI-1e-6;d3.svg.line=function(){return sn(i)};var eo=d3.map({linear:an,"linear-closed":fn,"step-before":ln,"step-after":cn,basis:gn,"basis-open":yn,"basis-closed":bn,bundle:wn,cardinal:dn,"cardinal-open":hn,"cardinal-closed":pn,monotone:Cn});eo.forEach(function(e,t){t.key=e,t.closed=/-closed$/.test(e)});var to=[0,2/3,1/3,0],no=[0,1/3,2/3,0],ro=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var e=sn(kn);return e.radius=e.x,delete e.x,e.angle=e.y,delete e.y,e},ln.reverse=cn,cn.reverse=ln,d3.svg.area=function(){return Ln(i)},d3.svg.area.radial=function(){var e=Ln(kn);return e.radius=e.x,delete e.x,e.innerRadius=e.x0,delete e.x0,e.outerRadius=e.x1,delete e.x1,e.angle=e.y,delete e.y,e.startAngle=e.y0,delete e.y0,e.endAngle=e.y1,delete e.y1,e},d3.svg.chord=function(){function e(e,u){var a=t(this,s,e,u),f=t(this,o,e,u);return"M"+a.p0+r(a.r,a.p1,a.a1-a.a0)+(n(a,f)?i(a.r,a.p1,a.r,a.p0):i(a.r,a.p1,f.r,f.p0)+r(f.r,f.p1,f.a1-f.a0)+i(f.r,f.p1,a.r,a.p0))+"Z"}function t(e,t,n,r){var i=t.call(e,n,r),s=a.call(e,i,r),o=f.call(e,i,r)+Ys,u=l.call(e,i,r)+Ys;return{r:s,a0:o,a1:u,p0:[s*Math.cos(o),s*Math.sin(o)],p1:[s*Math.cos(u),s*Math.sin(u)]}}function n(e,t){return e.a0==t.a0&&e.a1==t.a1}function r(e,t,n){return"A"+e+","+e+" 0 "+ +(n>Math.PI)+",1 "+t}function i(e,t,n,r){return"Q 0,0 "+r}var s=An,o=On,a=Mn,f=nn,l=rn;return e.radius=function(t){return arguments.length?(a=u(t),e):a},e.source=function(t){return arguments.length?(s=u(t),e):s},e.target=function(t){return arguments.length?(o=u(t),e):o},e.startAngle=function(t){return arguments.length?(f=u(t),e):f},e.endAngle=function(t){return arguments.length?(l=u(t),e):l},e},d3.svg.diagonal=function(){function e(e,i){var s=t.call(this,e,i),o=n.call(this,e,i),u=(s.y+o.y)/2,a=[s,{x:s.x,y:u},{x:o.x,y:u},o];return a=a.map(r),"M"+a[0]+"C"+a[1]+" "+a[2]+" "+a[3]}var t=An,n=On,r=Pn;return e.source=function(n){return arguments.length?(t=u(n),e):t},e.target=function(t){return arguments.length?(n=u(t),e):n},e.projection=function(t){return arguments.length?(r=t,e):r},e},d3.svg.diagonal.radial=function(){var e=d3.svg.diagonal(),t=Pn,n=e.projection;return e.projection=function(e){return arguments.length?n(Hn(t=e)):t},e},d3.svg.mouse=d3.mouse,d3.svg.touches=d3.touches,d3.svg.symbol=function(){function e(e,r){return(io.get(t.call(this,e,r))||Fn)(n.call(this,e,r))}var t=jn,n=Bn;return e.type=function(n){return arguments.length?(t=u(n),e):t},e.size=function(t){return arguments.length?(n=u(t),e):n},e};var io=d3.map({circle:Fn,cross:function(e){var t=Math.sqrt(e/5)/2;return"M"+ -3*t+","+ -t+"H"+ -t+"V"+ -3*t+"H"+t+"V"+ -t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+ -t+"V"+t+"H"+ -3*t+"Z"},diamond:function(e){var t=Math.sqrt(e/(2*oo)),n=t*oo;return"M0,"+ -t+"L"+n+",0"+" 0,"+t+" "+ -n+",0"+"Z"},square:function(e){var t=Math.sqrt(e)/2;return"M"+ -t+","+ -t+"L"+t+","+ -t+" "+t+","+t+" "+ -t+","+t+"Z"},"triangle-down":function(e){var t=Math.sqrt(e/so),n=t*so/2;return"M0,"+n+"L"+t+","+ -n+" "+ -t+","+ -n+"Z"},"triangle-up":function(e){var t=Math.sqrt(e/so),n=t*so/2;return"M0,"+ -n+"L"+t+","+n+" "+ -t+","+n+"Z"}});d3.svg.symbolTypes=io.keys();var so=Math.sqrt(3),oo=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function e(e){e.each(function(){var e=d3.select(this),c=a==null?t.ticks?t.ticks.apply(t,u):t.domain():a,h=f==null?t.tickFormat?t.tickFormat.apply(t,u):String:f,p=Rn(t,c,l),d=e.selectAll(".minor").data(p,String),v=d.enter().insert("line","g").attr("class","tick minor").style("opacity",1e-6),m=d3.transition(d.exit()).style("opacity",1e-6).remove(),g=d3.transition(d).style("opacity",1),y=e.selectAll("g").data(c,String),b=y.enter().insert("g","path").style("opacity",1e-6),w=d3.transition(y.exit()).style("opacity",1e-6).remove(),E=d3.transition(y).style("opacity",1),S,x=Dt(t),T=e.selectAll(".domain").data([0]),N=T.enter().append("path").attr("class","domain"),C=d3.transition(T),k=t.copy(),L=this.__chart__||k;this.__chart__=k,b.append("line").attr("class","tick"),b.append("text");var A=b.select("line"),O=E.select("line"),M=y.select("text").text(h),_=b.select("text"),D=E.select("text");switch(n){case"bottom":S=In,v.attr("y2",i),g.attr("x2",0).attr("y2",i),A.attr("y2",r),_.attr("y",Math.max(r,0)+o),O.attr("x2",0).attr("y2",r),D.attr("x",0).attr("y",Math.max(r,0)+o),M.attr("dy",".71em").attr("text-anchor","middle"),C.attr("d","M"+x[0]+","+s+"V0H"+x[1]+"V"+s);break;case"top":S=In,v.attr("y2",-i),g.attr("x2",0).attr("y2",-i),A.attr("y2",-r),_.attr("y",-(Math.max(r,0)+o)),O.attr("x2",0).attr("y2",-r),D.attr("x",0).attr("y",-(Math.max(r,0)+o)),M.attr("dy","0em").attr("text-anchor","middle"),C.attr("d","M"+x[0]+","+ -s+"V0H"+x[1]+"V"+ -s);break;case"left":S=qn,v.attr("x2",-i),g.attr("x2",-i).attr("y2",0),A.attr("x2",-r),_.attr("x",-(Math.max(r,0)+o)),O.attr("x2",-r).attr("y2",0),D.attr("x",-(Math.max(r,0)+o)).attr("y",0),M.attr("dy",".32em").attr("text-anchor","end"),C.attr("d","M"+ -s+","+x[0]+"H0V"+x[1]+"H"+ -s);break;case"right":S=qn,v.attr("x2",i),g.attr("x2",i).attr("y2",0),A.attr("x2",r),_.attr("x",Math.max(r,0)+o),O.attr("x2",r).attr("y2",0),D.attr("x",Math.max(r,0)+o).attr("y",0),M.attr("dy",".32em").attr("text-anchor","start"),C.attr("d","M"+s+","+x[0]+"H0V"+x[1]+"H"+s)}if(t.ticks)b.call(S,L),E.call(S,k),w.call(S,k),v.call(S,L),g.call(S,k),m.call(S,k);else{var P=k.rangeBand()/2,H=function(e){return k(e)+P};b.call(S,H),E.call(S,H)}})}var t=d3.scale.linear(),n="bottom",r=6,i=6,s=6,o=3,u=[10],a=null,f,l=0;return e.scale=function(n){return arguments.length?(t=n,e):t},e.orient=function(t){return arguments.length?(n=t,e):n},e.ticks=function(){return arguments.length?(u=arguments,e):u},e.tickValues=function(t){return arguments.length?(a=t,e):a},e.tickFormat=function(t){return arguments.length?(f=t,e):f},e.tickSize=function(t,n,o){if(!arguments.length)return r;var u=arguments.length-1;return r=+t,i=u>1?+n:r,s=u>0?+arguments[u]:r,e},e.tickPadding=function(t){return arguments.length?(o=+t,e):o},e.tickSubdivide=function(t){return arguments.length?(l=+t,e):l},e},d3.svg.brush=function(){function e(s){s.each(function(){var s=d3.select(this),f=s.selectAll(".background").data([0]),l=s.selectAll(".extent").data([0]),c=s.selectAll(".resize").data(a,String),h;s.style("pointer-events","all").on("mousedown.brush",i).on("touchstart.brush",i),f.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),l.enter().append("rect").attr("class","extent").style("cursor","move"),c.enter().append("g").attr("class",function(e){return"resize "+e}).style("cursor",function(e){return uo[e]}).append("rect").attr("x",function(e){return/[ew]$/.test(e)?-3:null}).attr("y",function(e){return/^[ns]/.test(e)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),c.style("display",e.empty()?"none":null),c.exit().remove(),o&&(h=Dt(o),f.attr("x",h[0]).attr("width",h[1]-h[0]),n(s)),u&&(h=Dt(u),f.attr("y",h[0]).attr("height",h[1]-h[0]),r(s)),t(s)})}function t(e){e.selectAll(".resize").attr("transform",function(e){return"translate("+f[+/e$/.test(e)][0]+","+f[+/^s/.test(e)][1]+")"})}function n(e){e.select(".extent").attr("x",f[0][0]),e.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1][0]-f[0][0])}function r(e){e.select(".extent").attr("y",f[0][1]),e.selectAll(".extent,.e>rect,.w>rect").attr("height",f[1][1]-f[0][1])}function i(){function i(){var e=d3.event.changedTouches;return e?d3.touches(v,e)[0]:d3.mouse(v)}function a(){d3.event.keyCode==32&&(S||(x=null,T[0]-=f[1][0],T[1]-=f[1][1],S=2),M())}function c(){d3.event.keyCode==32&&S==2&&(T[0]+=f[1][0],T[1]+=f[1][1],S=0,M())}function h(){var e=i(),s=!1;N&&(e[0]+=N[0],e[1]+=N[1]),S||(d3.event.altKey?(x||(x=[(f[0][0]+f[1][0])/2,(f[0][1]+f[1][1])/2]),T[0]=f[+(e[0]0?a=e:a=0:e>0&&(r.start({type:"start",alpha:a=e}),d3.timer(n.tick)),n):a},n.start=function(){function e(e,n){var i=t(r),s=-1,o=i.length,u;while(++si&&(i=u),r.push(u)}for(o=0;o0){s=-1;while(++s=a[0]&&d<=a[1]&&(l=o[d3.bisect(f,d,1,h)-1],l.y+=p,l.push(e[s]))}return o}var t=!0,n=Number,r=fr,i=ur;return e.value=function(t){return arguments.length?(n=t,e):n},e.range=function(t){return arguments.length?(r=u(t),e):r},e.bins=function(t){return arguments.length?(i=typeof t=="number"?function(e){return ar(e,t)}:u(t),e):i},e.frequency=function(n){return arguments.length?(t=!!n,e):t},e},d3.layout.hierarchy=function(){function e(t,o,u){var a=i.call(n,t,o),f=vo?t:{data:t};f.depth=o,u.push(f);if(a&&(c=a.length)){var l=-1,c,h=f.children=[],p=0,d=o+1,v;while(++l0){var l=n*f/2;Hr(o,function(e){e.r+=l}),Hr(o,br),Hr(o,function(e){e.r-=l}),f=Math.max(2*o.r/u,2*o.r/a)}return Sr(o,u/2,a/2,1/f),s}var t=d3.layout.hierarchy().sort(vr),n=0,r=[1,1];return e.size=function(t){return arguments.length?(r=t,e):r},e.padding=function(t){return arguments.length?(n=+t,e):n},lr(e,t)},d3.layout.cluster=function(){function e(e,i){var s=t.call(this,e,i),o=s[0],u,a=0,f,l;Hr(o,function(e){var t=e.children;t&&t.length?(e.x=Nr(t),e.y=Tr(t)):(e.x=u?a+=n(e,u):0,e.y=0,u=e)});var c=Cr(o),h=kr(o),p=c.x-n(c,h)/2,d=h.x+n(h,c)/2;return Hr(o,function(e){e.x=(e.x-p)/(d-p)*r[0],e.y=(1-(o.y?e.y/o.y:1))*r[1]}),s}var t=d3.layout.hierarchy().sort(null).value(null),n=Lr,r=[1,1];return e.separation=function(t){return arguments.length?(n=t,e):n},e.size=function(t){return arguments.length?(r=t,e):r},lr(e,t)},d3.layout.tree=function(){function e(e,i){function s(e,t){var r=e.children,i=e._tree;if(r&&(o=r.length)){var o,a=r[0],f,l=a,c,h=-1;while(++h0&&(jr(Fr(o,e,r),e,h),a+=h,f+=h),l+=o._tree.mod,a+=i._tree.mod,c+=u._tree.mod,f+=s._tree.mod;o&&!Or(s)&&(s._tree.thread=o,s._tree.mod+=l-f),i&&!Ar(u)&&(u._tree.thread=i,u._tree.mod+=a-c,r=e)}return r}var a=t.call(this,e,i),f=a[0];Hr(f,function(e,t){e._tree={ancestor:e,prelim:0,mod:0,change:0,shift:0,number:t?t._tree.number+1:0}}),s(f),o(f,-f._tree.prelim);var l=Mr(f,Dr),c=Mr(f,_r),h=Mr(f,Pr),p=l.x-n(l,c)/2,d=c.x+n(c,l)/2,v=h.depth||1;return Hr(f,function(e){e.x=(e.x-p)/(d-p)*r[0],e.y=e.depth/v*r[1],delete e._tree}),a}var t=d3.layout.hierarchy().sort(null).value(null),n=Lr,r=[1,1];return e.separation=function(t){return arguments.length?(n=t,e):n},e.size=function(t){return arguments.length?(r=t,e):r},lr(e,t)},d3.layout.treemap=function(){function e(e,t){var n=-1,r=e.length,i,s;while(++n0)u.push(f=a[d-1]),u.area+=f.area,(h=r(u,p))<=c?(a.pop(),c=h):(u.area-=u.pop().area,i(u,p,o,!1),p=Math.min(o.dx,o.dy),u.length=u.area=0,c=Infinity);u.length&&(i(u,p,o,!0),u.length=u.area=0),s.forEach(t)}}function n(t){var r=t.children;if(r&&r.length){var s=l(t),o=r.slice(),u,a=[];e(o,s.dx*s.dy/t.value),a.area=0;while(u=o.pop())a.push(u),a.area+=u.area,u.z!=null&&(i(a,u.z?s.dx:s.dy,s,!o.length),a.length=a.area=0);r.forEach(n)}}function r(e,t){var n=e.area,r,i=0,s=Infinity,o=-1,u=e.length;while(++oi&&(i=r)}return n*=n,t*=t,n?Math.max(t*i*p/n,n/(t*s*p)):Infinity}function i(e,t,n,r){var i=-1,s=e.length,o=n.x,a=n.y,f=t?u(e.area/t):0,l;if(t==n.dx){if(r||f>n.dy)f=n.dy;while(++in.dx)f=n.dx;while(++i50?n:s<-140?r:o<21?i:t)(e)}var t=d3.geo.albers(),n=d3.geo.albers().origin([-160,60]).parallels([55,65]),r=d3.geo.albers().origin([-160,20]).parallels([8,18]),i=d3.geo.albers().origin([-60,10]).parallels([8,18]);return e.scale=function(s){return arguments.length?(t.scale(s),n.scale(s*.6),r.scale(s),i.scale(s*1.5),e.translate(t.translate())):t.scale()},e.translate=function(s){if(!arguments.length)return t.translate();var o=t.scale()/1e3,u=s[0],a=s[1];return t.translate(s),n.translate([u-400*o,a+170*o]),r.translate([u-190*o,a+200*o]),i.translate([u+580*o,a+430*o]),e},e.scale(t.scale())},d3.geo.bonne=function(){function e(e){var u=e[0]*mo-r,a=e[1]*mo-i;if(s){var f=o+s-a,l=u*Math.cos(a)/f;u=f*Math.sin(l),a=f*Math.cos(l)-o}else u*=Math.cos(a),a*=-1;return[t*u+n[0],t*a+n[1]]}var t=200,n=[480,250],r,i,s,o;return e.invert=function(e){var i=(e[0]-n[0])/t,u=(e[1]-n[1])/t;if(s){var a=o+u,f=Math.sqrt(i*i+a*a);u=o+s-f,i=r+f*Math.atan2(i,a)/Math.cos(u)}else u*=-1,i/=Math.cos(u);return[i/mo,u/mo]},e.parallel=function(t){return arguments.length?(o=1/Math.tan(s=t*mo),e):s/mo},e.origin=function(t){return arguments.length?(r=t[0]*mo,i=t[1]*mo,e):[r/mo,i/mo]},e.scale=function( +n){return arguments.length?(t=+n,e):t},e.translate=function(t){return arguments.length?(n=[+t[0],+t[1]],e):n},e.origin([0,0]).parallel(45)},d3.geo.equirectangular=function(){function e(e){var r=e[0]/360,i=-e[1]/360;return[t*r+n[0],t*i+n[1]]}var t=500,n=[480,250];return e.invert=function(e){var r=(e[0]-n[0])/t,i=(e[1]-n[1])/t;return[360*r,-360*i]},e.scale=function(n){return arguments.length?(t=+n,e):t},e.translate=function(t){return arguments.length?(n=[+t[0],+t[1]],e):n},e},d3.geo.mercator=function(){function e(e){var r=e[0]/360,i=-(Math.log(Math.tan(Math.PI/4+e[1]*mo/2))/mo)/360;return[t*r+n[0],t*Math.max(-0.5,Math.min(.5,i))+n[1]]}var t=500,n=[480,250];return e.invert=function(e){var r=(e[0]-n[0])/t,i=(e[1]-n[1])/t;return[360*r,2*Math.atan(Math.exp(-360*i*mo))/mo-90]},e.scale=function(n){return arguments.length?(t=+n,e):t},e.translate=function(t){return arguments.length?(n=[+t[0],+t[1]],e):n},e},d3.geo.path=function(){function e(e,t){typeof s=="function"&&(o=zr(s.apply(this,arguments))),f(e);var n=a.length?a.join(""):null;return a=[],n}function t(e){return u(e).join(",")}function n(e){var t=i(e[0]),n=0,r=e.length;while(++n0){a.push("M");while(++o0){a.push("M");while(++lr&&(r=e),si&&(i=s)}),[[t,n],[r,i]]};var go={Feature:Xr,FeatureCollection:Vr,GeometryCollection:$r,LineString:Jr,MultiLineString:Kr,MultiPoint:Jr,MultiPolygon:Qr,Point:Gr,Polygon:Yr};d3.geo.circle=function(){function e(){}function t(e){return a.distance(e)=l*l+c*c?r[s].index=-1:(r[h].index=-1,d=r[s].angle,h=s,p=o)):(d=r[s].angle,h=s,p=o);i.push(u);for(s=0,o=0;s<2;++o)r[o].index!==-1&&(i.push(r[o].index),s++);v=i.length;for(;o=0?(n=e.ep.r,r=e.ep.l):(n=e.ep.l,r=e.ep.r),e.a===1?(o=n?n.y:-1e6,i=e.c-e.b*o,u=r?r.y:1e6,s=e.c-e.b*u):(i=n?n.x:-1e6,o=e.c-e.a*i,s=r?r.x:1e6,u=e.c-e.a*s);var a=[i,o],f=[s,u];t[e.region.l.index].push(a,f),t[e.region.r.index].push(a,f)}),t.map(function(t,n){var r=e[n][0],i=e[n][1];return t.forEach(function(e){e.angle=Math.atan2(e[0]-r,e[1]-i)}),t.sort(function(e,t){return e.angle-t.angle}).filter(function(e,n){return!n||e.angle-t[n-1].angle>1e-10})})};var wo={l:"r",r:"l"};d3.geom.delaunay=function(e){var t=e.map(function(){return[]}),n=[];return ui(e,function(n){t[n.region.l.index].push(e[n.region.r.index])}),t.forEach(function(t,r){var i=e[r],s=i[0],o=i[1];t.forEach(function(e){e.angle=Math.atan2(e[0]-s,e[1]-o)}),t.sort(function(e,t){return e.angle-t.angle});for(var u=0,a=t.length-1;u=u,l=t.y>=a,c=(l<<1)+f;e.leaf=!1,e=e.nodes[c]||(e.nodes[c]=ai()),f?n=u:i=u,l?r=a:o=a,s(e,t,n,r,i,o)}var u,a=-1,f=e.length;f&&isNaN(e[0].x)&&(e=e.map(li));if(arguments.length<5)if(arguments.length===3)i=r=n,n=t;else{t=n=Infinity,r=i=-Infinity;while(++ar&&(r=u.x),u.y>i&&(i=u.y);var l=r-t,c=i-n;l>c?i=n+l:r=t+c}var h=ai();return h.add=function(e){s(h,e,t,n,r,i)},h.visit=function(e){fi(e,h,t,n,r,i)},e.forEach(h.add),h},d3.time={};var Eo=Date,So=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];ci.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){xo.setUTCDate.apply(this._,arguments)},setDay:function(){xo.setUTCDay.apply(this._,arguments)},setFullYear:function(){xo.setUTCFullYear.apply(this._,arguments)},setHours:function(){xo.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){xo.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){xo.setUTCMinutes.apply(this._,arguments)},setMonth:function(){xo.setUTCMonth.apply(this._,arguments)},setSeconds:function(){xo.setUTCSeconds.apply(this._,arguments)},setTime:function(){xo.setTime.apply(this._,arguments)}};var xo=Date.prototype,To="%a %b %e %H:%M:%S %Y",No="%m/%d/%y",Co="%H:%M:%S",ko=So,Lo=ko.map(hi),Ao=["January","February","March","April","May","June","July","August","September","October","November","December"],Oo=Ao.map(hi);d3.time.format=function(e){function t(t){var r=[],i=-1,s=0,o,u;while(++i=12?"PM":"AM"},S:function(e){return Mo(e.getSeconds())},U:function(e){return Mo(d3.time.sundayOfYear(e))},w:function(e){return e.getDay()},W:function(e){return Mo(d3.time.mondayOfYear(e))},x:d3.time.format(No),X:d3.time.format(Co),y:function(e){return Mo(e.getFullYear()%100)},Y:function(e){return Do(e.getFullYear()%1e4)},Z:Di,"%":function(e){return"%"}},Uo={a:mi,A:gi,b:yi,B:bi,c:wi,d:ki,e:ki,H:Li,I:Li,L:Mi,m:Ci,M:Ai,p:_i,S:Oi,x:Ei,X:Si,y:Ti,Y:xi},zo=/^\s*\d+/,Wo=d3.map({am:0,pm:1});d3.time.format.utc=function(e){function t(e){try{Eo=ci;var t=new Eo;return t._=e,n(t)}finally{Eo=Date}}var n=d3.time.format(e);return t.parse=function(e){try{Eo=ci;var t=n.parse(e);return t&&t._}finally{Eo=Date}},t.toString=n.toString,t};var Xo=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?Pi:Xo,Pi.parse=function(e){var t=new Date(e);return isNaN(t)?null:t},Pi.toString=Xo.toString,d3.time.second=Hi(function(e){return new Eo(Math.floor(e/1e3)*1e3)},function(e,t){e.setTime(e.getTime()+Math.floor(t)*1e3)},function(e){return e.getSeconds()}),d3.time.seconds=d3.time.second.range,d3.time.seconds.utc=d3.time.second.utc.range,d3.time.minute=Hi(function(e){return new Eo(Math.floor(e/6e4)*6e4)},function(e,t){e.setTime(e.getTime()+Math.floor(t)*6e4)},function(e){return e.getMinutes()}),d3.time.minutes=d3.time.minute.range,d3.time.minutes.utc=d3.time.minute.utc.range,d3.time.hour=Hi(function(e){var t=e.getTimezoneOffset()/60;return new Eo((Math.floor(e/36e5-t)+t)*36e5)},function(e,t){e.setTime(e.getTime()+Math.floor(t)*36e5)},function(e){return e.getHours()}),d3.time.hours=d3.time.hour.range,d3.time.hours.utc=d3.time.hour.utc.range,d3.time.day=Hi(function(e){var t=new Eo(1970,0);return t.setFullYear(e.getFullYear(),e.getMonth(),e.getDate()),t},function(e,t){e.setDate(e.getDate()+t)},function(e){return e.getDate()-1}),d3.time.days=d3.time.day.range,d3.time.days.utc=d3.time.day.utc.range,d3.time.dayOfYear=function(e){var t=d3.time.year(e);return Math.floor((e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5)},So.forEach(function(e,t){e=e.toLowerCase(),t=7-t;var n=d3.time[e]=Hi(function(e){return(e=d3.time.day(e)).setDate(e.getDate()-(e.getDay()+t)%7),e},function(e,t){e.setDate(e.getDate()+Math.floor(t)*7)},function(e){var n=d3.time.year(e).getDay();return Math.floor((d3.time.dayOfYear(e)+(n+t)%7)/7)-(n!==t)});d3.time[e+"s"]=n.range,d3.time[e+"s"].utc=n.utc.range,d3.time[e+"OfYear"]=function(e){var n=d3.time.year(e).getDay();return Math.floor((d3.time.dayOfYear(e)+(n+t)%7)/7)}}),d3.time.week=d3.time.sunday,d3.time.weeks=d3.time.sunday.range,d3.time.weeks.utc=d3.time.sunday.utc.range,d3.time.weekOfYear=d3.time.sundayOfYear,d3.time.month=Hi(function(e){return e=d3.time.day(e),e.setDate(1),e},function(e,t){e.setMonth(e.getMonth()+t)},function(e){return e.getMonth()}),d3.time.months=d3.time.month.range,d3.time.months.utc=d3.time.month.utc.range,d3.time.year=Hi(function(e){return e=d3.time.day(e),e.setMonth(0,1),e},function(e,t){e.setFullYear(e.getFullYear()+t)},function(e){return e.getFullYear()}),d3.time.years=d3.time.year.range,d3.time.years.utc=d3.time.year.utc.range;var Vo=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],$o=[[d3.time.second,1],[d3.time.second,5],[d3.time.second,15],[d3.time.second,30],[d3.time.minute,1],[d3.time.minute,5],[d3.time.minute,15],[d3.time.minute,30],[d3.time.hour,1],[d3.time.hour,3],[d3.time.hour,6],[d3.time.hour,12],[d3.time.day,1],[d3.time.day,2],[d3.time.week,1],[d3.time.month,1],[d3.time.month,3],[d3.time.year,1]],Jo=[[d3.time.format("%Y"),function(e){return!0}],[d3.time.format("%B"),function(e){return e.getMonth()}],[d3.time.format("%b %d"),function(e){return e.getDate()!=1}],[d3.time.format("%a %d"),function(e){return e.getDay()&&e.getDate()!=1}],[d3.time.format("%I %p"),function(e){return e.getHours()}],[d3.time.format("%I:%M"),function(e){return e.getMinutes()}],[d3.time.format(":%S"),function(e){return e.getSeconds()}],[d3.time.format(".%L"),function(e){return e.getMilliseconds()}]],Ko=d3.scale.linear(),Qo=qi(Jo);$o.year=function(e,t){return Ko.domain(e.map(Ui)).ticks(t).map(Ri)},d3.time.scale=function(){return ji(d3.scale.linear(),$o,Qo)};var Go=$o.map(function(e){return[e[0].utc,e[1]]}),Yo=[[d3.time.format.utc("%Y"),function(e){return!0}],[d3.time.format.utc("%B"),function(e){return e.getUTCMonth()}],[d3.time.format.utc("%b %d"),function(e){return e.getUTCDate()!=1}],[d3.time.format.utc("%a %d"),function(e){return e.getUTCDay()&&e.getUTCDate()!=1}],[d3.time.format.utc("%I %p"),function(e){return e.getUTCHours()}],[d3.time.format.utc("%I:%M"),function(e){return e.getUTCMinutes()}],[d3.time.format.utc(":%S"),function(e){return e.getUTCSeconds()}],[d3.time.format.utc(".%L"),function(e){return e.getUTCMilliseconds()}]],Zo=qi(Yo);Go.year=function(e,t){return Ko.domain(e.map(Wi)).ticks(t).map(zi)},d3.time.scale.utc=function(){return ji(d3.scale.linear(),Go,Zo)}})(); diff --git a/plugin/teksi_wastewater/svgprofile/dojo.js b/plugin/teksi_wastewater/svgprofile/dojo.js new file mode 100644 index 0000000..2f47540 --- /dev/null +++ b/plugin/teksi_wastewater/svgprofile/dojo.js @@ -0,0 +1,15 @@ +/* + Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. + Available via Academic Free License >= 2.1 OR the modified BSD license. + see: http://dojotoolkit.org/license for details +*/ + +/* + This is an optimized version of Dojo, built for deployment and not for + development. To get sources and documentation, please visit: + + http://dojotoolkit.org +*/ + +//>>built +(function(_1,_2){var _3=function(){},_4=function(it){for(var p in it){return 0;}return 1;},_5={}.toString,_6=function(it){return _5.call(it)=="[object Function]";},_7=function(it){return _5.call(it)=="[object String]";},_8=function(it){return _5.call(it)=="[object Array]";},_9=function(_a,_b){if(_a){for(var i=0;i<_a.length;){_b(_a[i++]);}}},_c=function(_d,_e){for(var p in _e){_d[p]=_e[p];}return _d;},_f=function(_10,_11){return _c(new Error(_10),{src:"dojoLoader",info:_11});},_12=1,uid=function(){return "_"+_12++;},req=function(_13,_14,_15){return _16(_13,_14,_15,0,req);},_17=this,doc=_17.document,_18=doc&&doc.createElement("DiV"),has=req.has=function(_19){return _6(_1a[_19])?(_1a[_19]=_1a[_19](_17,doc,_18)):_1a[_19];},_1a=has.cache=_2.hasCache;has.add=function(_1b,_1c,now,_1d){(_1a[_1b]===undefined||_1d)&&(_1a[_1b]=_1c);return now&&has(_1b);};0&&has.add("host-node",_1.has&&"host-node" in _1.has?_1.has["host-node"]:(typeof process=="object"&&process.versions&&process.versions.node&&process.versions.v8));if(0){require("./_base/configNode.js").config(_2);_2.loaderPatch.nodeRequire=require;}0&&has.add("host-rhino",_1.has&&"host-rhino" in _1.has?_1.has["host-rhino"]:(typeof load=="function"&&(typeof Packages=="function"||typeof Packages=="object")));if(0){for(var _1e=_1.baseUrl||".",arg,_1f=this.arguments,i=0;i<_1f.length;){arg=(_1f[i++]+"").split("=");if(arg[0]=="baseUrl"){_1e=arg[1];break;}}load(_1e+"/_base/configRhino.js");rhinoDojoConfig(_2,_1e,_1f);}for(var p in _1.has){has.add(p,_1.has[p],0,1);}var _20=1,_21=2,_22=3,_23=4,_24=5;if(0){_20="requested";_21="arrived";_22="not-a-module";_23="executing";_24="executed";}var _25=0,_26="sync",xd="xd",_27=[],_28=0,_29=_3,_2a=_3,_2b;if(1){req.isXdUrl=_3;req.initSyncLoader=function(_2c,_2d,_2e){if(!_28){_28=_2c;_29=_2d;_2a=_2e;}return {sync:_26,requested:_20,arrived:_21,nonmodule:_22,executing:_23,executed:_24,syncExecStack:_27,modules:_2f,execQ:_30,getModule:_31,injectModule:_32,setArrived:_33,signal:_34,finishExec:_35,execModule:_36,dojoRequirePlugin:_28,getLegacyMode:function(){return _25;},guardCheckComplete:_37};};if(1){var _38=location.protocol,_39=location.host;req.isXdUrl=function(url){if(/^\./.test(url)){return false;}if(/^\/\//.test(url)){return true;}var _3a=url.match(/^([^\/\:]+\:)\/+([^\/]+)/);return _3a&&(_3a[1]!=_38||(_39&&_3a[2]!=_39));};1||has.add("dojo-xhr-factory",1);has.add("dojo-force-activex-xhr",1&&!doc.addEventListener&&window.location.protocol=="file:");has.add("native-xhr",typeof XMLHttpRequest!="undefined");if(has("native-xhr")&&!has("dojo-force-activex-xhr")){_2b=function(){return new XMLHttpRequest();};}else{for(var _3b=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"],_3c,i=0;i<3;){try{_3c=_3b[i++];if(new ActiveXObject(_3c)){break;}}catch(e){}}_2b=function(){return new ActiveXObject(_3c);};}req.getXhr=_2b;has.add("dojo-gettext-api",1);req.getText=function(url,_3d,_3e){var xhr=_2b();xhr.open("GET",_3f(url),false);xhr.send(null);if(xhr.status==200||(!location.host&&!xhr.status)){if(_3e){_3e(xhr.responseText,_3d);}}else{throw _f("xhrFailed",xhr.status);}return xhr.responseText;};}}else{req.async=1;}var _40=new Function("return eval(arguments[0]);");req.eval=function(_41,_42){return _40(_41+"\r\n////@ sourceURL="+_42);};var _43={},_44="error",_34=req.signal=function(_45,_46){var _47=_43[_45];_9(_47&&_47.slice(0),function(_48){_48.apply(null,_8(_46)?_46:[_46]);});},on=req.on=function(_49,_4a){var _4b=_43[_49]||(_43[_49]=[]);_4b.push(_4a);return {remove:function(){for(var i=0;i<_4b.length;i++){if(_4b[i]===_4a){_4b.splice(i,1);return;}}}};};var _4c=[],_4d={},_4e=[],_4f={},map=req.map={},_50=[],_2f={},_51="",_52={},_53="url:",_54={},_55={};if(1){var _56=function(_57){var p,_58,_59,now,m;for(p in _54){_58=_54[p];_59=p.match(/^url\:(.+)/);if(_59){_52[_53+_5a(_59[1],_57)]=_58;}else{if(p=="*now"){now=_58;}else{if(p!="*noref"){m=_5b(p,_57);_52[m.mid]=_52[_53+m.url]=_58;}}}}if(now){now(_5c(_57));}_54={};},_5d=function(s){return s.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g,function(c){return "\\"+c;});},_5e=function(map,_5f){_5f.splice(0,_5f.length);for(var p in map){_5f.push([p,map[p],new RegExp("^"+_5d(p)+"(/|$)"),p.length]);}_5f.sort(function(lhs,rhs){return rhs[3]-lhs[3];});return _5f;},_60=function(_61){var _62=_61.name;if(!_62){_62=_61;_61={name:_62};}_61=_c({main:"main"},_61);_61.location=_61.location?_61.location:_62;if(_61.packageMap){map[_62]=_61.packageMap;}if(!_61.main.indexOf("./")){_61.main=_61.main.substring(2);}_4f[_62]=_61;},_63=[],_64=function(_65,_66,_67){for(var p in _65){if(p=="waitSeconds"){req.waitms=(_65[p]||0)*1000;}if(p=="cacheBust"){_51=_65[p]?(_7(_65[p])?_65[p]:(new Date()).getTime()+""):"";}if(p=="baseUrl"||p=="combo"){req[p]=_65[p];}if(1&&p=="async"){var _68=_65[p];req.legacyMode=_25=(_7(_68)&&/sync|legacyAsync/.test(_68)?_68:(!_68?_26:false));req.async=!_25;}if(_65[p]!==_1a){req.rawConfig[p]=_65[p];p!="has"&&has.add("config-"+p,_65[p],0,_66);}}if(!req.baseUrl){req.baseUrl="./";}if(!/\/$/.test(req.baseUrl)){req.baseUrl+="/";}for(p in _65.has){has.add(p,_65.has[p],0,_66);}_9(_65.packages,_60);for(_1e in _65.packagePaths){_9(_65.packagePaths[_1e],function(_69){var _6a=_1e+"/"+_69;if(_7(_69)){_69={name:_69};}_69.location=_6a;_60(_69);});}_5e(_c(map,_65.map),_50);_9(_50,function(_6b){_6b[1]=_5e(_6b[1],[]);if(_6b[0]=="*"){_50.star=_6b[1];}});_5e(_c(_4d,_65.paths),_4e);_9(_65.aliases,function(_6c){if(_7(_6c[0])){_6c[0]=new RegExp("^"+_5d(_6c[0])+"$");}_4c.push(_6c);});if(_66){_63.push({config:_65.config});}else{for(p in _65.config){var _6d=_31(p,_67);_6d.config=_c(_6d.config||{},_65.config[p]);}}if(_65.cache){_56();_54=_65.cache;if(_65.cache["*noref"]){_56();}}_34("config",[_65,req.rawConfig]);};if(has("dojo-cdn")||1){var _6e=doc.getElementsByTagName("script"),i=0,_6f,_70,src,_71;while(i<_6e.length){_6f=_6e[i++];if((src=_6f.getAttribute("src"))&&(_71=src.match(/(((.*)\/)|^)dojo\.js(\W|$)/i))){_70=_71[3]||"";_2.baseUrl=_2.baseUrl||_70;src=(_6f.getAttribute("data-dojo-config")||_6f.getAttribute("djConfig"));if(src){_55=req.eval("({ "+src+" })","data-dojo-config");}if(0){var _72=_6f.getAttribute("data-main");if(_72){_55.deps=_55.deps||[_72];}}break;}}}if(0){try{if(window.parent!=window&&window.parent.require){var doh=window.parent.require("doh");doh&&_c(_55,doh.testConfig);}}catch(e){}}req.rawConfig={};_64(_2,1);if(has("dojo-cdn")){_4f.dojo.location=_70;if(_70){_70+="/";}_4f.dijit.location=_70+"../dijit/";_4f.dojox.location=_70+"../dojox/";}_64(_1,1);_64(_55,1);}else{_4d=_2.paths;_4e=_2.pathsMapProg;_4f=_2.packs;_4c=_2.aliases;_50=_2.mapProgs;_2f=_2.modules;_52=_2.cache;_51=_2.cacheBust;req.rawConfig=_2;}if(0){req.combo=req.combo||{add:_3};var _73=0,_74=[],_75=null;}var _76=function(_77){_37(function(){_9(_77.deps,_32);if(0&&_73&&!_75){_75=setTimeout(function(){_73=0;_75=null;req.combo.done(function(_78,url){var _79=function(){_7a(0,_78);_7b();};_74.push(_78);_7c=_78;req.injectUrl(url,_79,_78);_7c=0;},req);},0);}});},_16=function(a1,a2,a3,_7d,_7e){var _7f,_80;if(_7(a1)){_7f=_31(a1,_7d,true);if(_7f&&_7f.executed){return _7f.result;}throw _f("undefinedModule",a1);}if(!_8(a1)){_64(a1,0,_7d);a1=a2;a2=a3;}if(_8(a1)){if(!a1.length){a2&&a2();}else{_80="require*"+uid();for(var mid,_81=[],i=0;i_ab){_ac=_6(_ad[1])?mid.replace(_ad[0],_ad[1]):_ad[1];}});if(_ac){return _9c(_ac,0,_9e,_9f,_a0,_a1,_a2,_a3);}_a8=_9f[mid];if(_a8){return _a3?_82(_a8.pid,_a8.mid,_a8.pack,_a8.url):_9f[mid];}}_a7=_94(mid,_a2);if(_a7){url=_a7[1]+mid.substring(_a7[3]);}else{if(pid){url=_a4.location+"/"+_a5;}else{if(has("config-tlmSiblingOfDojo")){url="../"+mid;}else{url=mid;}}}if(!(/(^\/)|(\:)/.test(url))){url=_a0+url;}url+=".js";return _82(pid,mid,_a4,_96(url));},_5b=function(mid,_af){return _9c(mid,_af,_4f,_2f,req.baseUrl,_50,_4e);},_b0=function(_b1,_b2,_b3){return _b1.normalize?_b1.normalize(_b2,function(mid){return _b4(mid,_b3);}):_b4(_b2,_b3);},_b5=0,_31=function(mid,_b6,_b7){var _b8,_b9,_ba,_bb;_b8=mid.match(/^(.+?)\!(.*)$/);if(_b8){_b9=_31(_b8[1],_b6,_b7);if(1&&_25==_26&&!_b9.executed){_32(_b9);if(_b9.injected===_21&&!_b9.executed){_37(function(){_36(_b9);});}if(_b9.executed){_bc(_b9);}else{_30.unshift(_b9);}}if(_b9.executed===_24&&!_b9.load){_bc(_b9);}if(_b9.load){_ba=_b0(_b9,_b8[2],_b6);mid=(_b9.mid+"!"+(_b9.dynamic?++_b5+"!":"")+_ba);}else{_ba=_b8[2];mid=_b9.mid+"!"+(++_b5)+"!waitingForPlugin";}_bb={plugin:_b9,mid:mid,req:_5c(_b6),prid:_ba};}else{_bb=_5b(mid,_b6);}return _2f[_bb.mid]||(!_b7&&(_2f[_bb.mid]=_bb));},_b4=req.toAbsMid=function(mid,_bd){return _5b(mid,_bd).mid;},_5a=req.toUrl=function(_be,_bf){var _c0=_5b(_be+"/x",_bf),url=_c0.url;return _3f(_c0.pid===0?_be:url.substring(0,url.length-5));},_c1={injected:_21,executed:_24,def:_22,result:_22},_c2=function(mid){return _2f[mid]=_c({mid:mid},_c1);},_c3=_c2("require"),_c4=_c2("exports"),_c5=_c2("module"),_c6=function(_c7,_c8){req.trace("loader-run-factory",[_c7.mid]);var _c9=_c7.def,_ca;1&&_27.unshift(_c7);if(has("config-dojo-loader-catches")){try{_ca=_6(_c9)?_c9.apply(null,_c8):_c9;}catch(e){_34(_44,_c7.result=_f("factoryThrew",[_c7,e]));}}else{_ca=_6(_c9)?_c9.apply(null,_c8):_c9;}_c7.result=_ca===undefined&&_c7.cjs?_c7.cjs.exports:_ca;1&&_27.shift(_c7);},_cb={},_cc=0,_bc=function(_cd){var _ce=_cd.result;_cd.dynamic=_ce.dynamic;_cd.normalize=_ce.normalize;_cd.load=_ce.load;return _cd;},_cf=function(_d0){var map={};_9(_d0.loadQ,function(_d1){var _d2=_b0(_d0,_d1.prid,_d1.req.module),mid=_d0.dynamic?_d1.mid.replace(/waitingForPlugin$/,_d2):(_d0.mid+"!"+_d2),_d3=_c(_c({},_d1),{mid:mid,prid:_d2,injected:0});if(!_2f[mid]){_e5(_2f[mid]=_d3);}map[_d1.mid]=_2f[mid];_33(_d1);delete _2f[_d1.mid];});_d0.loadQ=0;var _d4=function(_d5){for(var _d6,_d7=_d5.deps||[],i=0;i<_d7.length;i++){_d6=map[_d7[i].mid];if(_d6){_d7[i]=_d6;}}};for(var p in _2f){_d4(_2f[p]);}_9(_30,_d4);},_35=function(_d8){req.trace("loader-finish-exec",[_d8.mid]);_d8.executed=_24;_d8.defOrder=_cc++;1&&_9(_d8.provides,function(cb){cb();});if(_d8.loadQ){_bc(_d8);_cf(_d8);}for(i=0;i<_30.length;){if(_30[i]===_d8){_30.splice(i,1);}else{i++;}}if(/^require\*/.test(_d8.mid)){delete _2f[_d8.mid];}},_d9=[],_36=function(_da,_db){if(_da.executed===_23){req.trace("loader-circular-dependency",[_d9.concat(_da.mid).join("->")]);return (!_da.def||_db)?_cb:(_da.cjs&&_da.cjs.exports);}if(!_da.executed){if(!_da.def){return _cb;}var mid=_da.mid,_dc=_da.deps||[],arg,_dd,_de=[],i=0;if(0){_d9.push(mid);req.trace("loader-exec-module",["exec",_d9.length,mid]);}_da.executed=_23;while(i<_dc.length){arg=_dc[i++];_dd=((arg===_c3)?_5c(_da):((arg===_c4)?_da.cjs.exports:((arg===_c5)?_da.cjs:_36(arg,_db))));if(_dd===_cb){_da.executed=0;req.trace("loader-exec-module",["abort",mid]);0&&_d9.pop();return _cb;}_de.push(_dd);}_c6(_da,_de);_35(_da);0&&_d9.pop();}return _da.result;},_84=0,_37=function(_df){try{_84++;_df();}finally{_84--;}if(_93()){_34("idle",[]);}},_7b=function(){if(_84){return;}_37(function(){_29();for(var _e0,_e1,i=0;i<_30.length;){_e0=_cc;_e1=_30[i];_36(_e1);if(_e0!=_cc){_29();i=0;}else{i++;}}});};if(0){req.undef=function(_e2,_e3){var _e4=_31(_e2,_e3);_33(_e4);delete _2f[_e4.mid];};}if(1){if(has("dojo-loader-eval-hint-url")===undefined){has.add("dojo-loader-eval-hint-url",1);}var _3f=function(url){url+="";return url+(_51?((/\?/.test(url)?"&":"?")+_51):"");},_e5=function(_e6){var _e7=_e6.plugin;if(_e7.executed===_24&&!_e7.load){_bc(_e7);}var _e8=function(def){_e6.result=def;_33(_e6);_35(_e6);_7b();};if(_e7.load){_e7.load(_e6.prid,_e6.req,_e8);}else{if(_e7.loadQ){_e7.loadQ.push(_e6);}else{_e7.loadQ=[_e6];_30.unshift(_e7);_32(_e7);}}},_8a=0,_7c=0,_e9=0,_8b=function(_ea,_eb){if(has("config-stripStrict")){_ea=_ea.replace(/"use strict"/g,"");}_e9=1;if(has("config-dojo-loader-catches")){try{if(_ea===_8a){_8a.call(null);}else{req.eval(_ea,has("dojo-loader-eval-hint-url")?_eb.url:_eb.mid);}}catch(e){_34(_44,_f("evalModuleThrew",_eb));}}else{if(_ea===_8a){_8a.call(null);}else{req.eval(_ea,has("dojo-loader-eval-hint-url")?_eb.url:_eb.mid);}}_e9=0;},_32=function(_ec){var mid=_ec.mid,url=_ec.url;if(_ec.executed||_ec.injected||_8d[mid]||(_ec.url&&((_ec.pack&&_8d[_ec.url]===_ec.pack)||_8d[_ec.url]==1))){return;}_8e(_ec);if(0){var _ed=0;if(_ec.plugin&&_ec.plugin.isCombo){req.combo.add(_ec.plugin.mid,_ec.prid,0,req);_ed=1;}else{if(!_ec.plugin){_ed=req.combo.add(0,_ec.mid,_ec.url,req);}}if(_ed){_73=1;return;}}if(_ec.plugin){_e5(_ec);return;}var _ee=function(){_7a(_ec);if(_ec.injected!==_21){_33(_ec);_c(_ec,_c1);req.trace("loader-define-nonmodule",[_ec.url]);}if(1&&_25){!_27.length&&_7b();}else{_7b();}};_8a=_52[mid]||_52[_53+_ec.url];if(_8a){req.trace("loader-inject",["cache",_ec.mid,url]);_8b(_8a,_ec);_ee();return;}if(1&&_25){if(_ec.isXd){_25==_26&&(_25=xd);}else{if(_ec.isAmd&&_25!=_26){}else{var _ef=function(_f0){if(_25==_26){_27.unshift(_ec);_8b(_f0,_ec);_27.shift();_7a(_ec);if(!_ec.cjs){_33(_ec);_35(_ec);}if(_ec.finish){var _f1=mid+"*finish",_f2=_ec.finish;delete _ec.finish;def(_f1,["dojo",("dojo/require!"+_f2.join(",")).replace(/\./g,"/")],function(_f3){_9(_f2,function(mid){_f3.require(mid);});});_30.unshift(_31(_f1));}_ee();}else{_f0=_2a(_ec,_f0);if(_f0){_8b(_f0,_ec);_ee();}else{_7c=_ec;req.injectUrl(_3f(url),_ee,_ec);_7c=0;}}};req.trace("loader-inject",["xhr",_ec.mid,url,_25!=_26]);if(has("config-dojo-loader-catches")){try{req.getText(url,_25!=_26,_ef);}catch(e){_34(_44,_f("xhrInjectFailed",[_ec,e]));}}else{req.getText(url,_25!=_26,_ef);}return;}}}req.trace("loader-inject",["script",_ec.mid,url]);_7c=_ec;req.injectUrl(_3f(url),_ee,_ec);_7c=0;},_f4=function(_f5,_f6,def){req.trace("loader-define-module",[_f5.mid,_f6]);if(0&&_f5.plugin&&_f5.plugin.isCombo){_f5.result=_6(def)?def():def;_33(_f5);_35(_f5);return _f5;}var mid=_f5.mid;if(_f5.injected===_21){_34(_44,_f("multipleDefine",_f5));return _f5;}_c(_f5,{deps:_f6,def:def,cjs:{id:_f5.mid,uri:_f5.url,exports:(_f5.result={}),setExports:function(_f7){_f5.cjs.exports=_f7;},config:function(){return _f5.config;}}});for(var i=0;i<_f6.length;i++){_f6[i]=_31(_f6[i],_f5);}if(1&&_25&&!_8d[mid]){_76(_f5);_30.push(_f5);_7b();}_33(_f5);if(!_6(def)&&!_f6.length){_f5.result=def;_35(_f5);}return _f5;},_7a=function(_f8,_f9){var _fa=[],_fb,_fc;while(_8c.length){_fc=_8c.shift();_f9&&(_fc[0]=_f9.shift());_fb=(_fc[0]&&_31(_fc[0]))||_f8;_fa.push([_fb,_fc[1],_fc[2]]);}_56(_f8);_9(_fa,function(_fd){_76(_f4.apply(null,_fd));});};}var _fe=0,_92=_3,_90=_3;if(1){_92=function(){_fe&&clearTimeout(_fe);_fe=0;},_90=function(){_92();if(req.waitms){_fe=window.setTimeout(function(){_92();_34(_44,_f("timeout",_8d));},req.waitms);}};}if(1){has.add("ie-event-behavior",!!doc.attachEvent&&(typeof opera==="undefined"||opera.toString()!="[object Opera]"));}if(1&&(1||1)){var _ff=function(node,_100,_101,_102){if(!has("ie-event-behavior")){node.addEventListener(_100,_102,false);return function(){node.removeEventListener(_100,_102,false);};}else{node.attachEvent(_101,_102);return function(){node.detachEvent(_101,_102);};}},_103=_ff(window,"load","onload",function(){req.pageLoaded=1;doc.readyState!="complete"&&(doc.readyState="complete");_103();});if(1){var _104=doc.getElementsByTagName("script")[0],_105=_104.parentNode;req.injectUrl=function(url,_106,_107){var node=_107.node=doc.createElement("script"),_108=function(e){e=e||window.event;var node=e.target||e.srcElement;if(e.type==="load"||/complete|loaded/.test(node.readyState)){_109();_10a();_106&&_106();}},_109=_ff(node,"load","onreadystatechange",_108),_10a=_ff(node,"error","onerror",function(e){_109();_10a();_34(_44,_f("scriptError",[url,e]));});node.type="text/javascript";node.charset="utf-8";node.src=url;_105.insertBefore(node,_104);return node;};}}if(1){req.log=function(){try{for(var i=0;i0){_129._delayTimer=setTimeout(_12a,de);return _129;}_12a();return _129;},_play:function(_12b){var _12c=this;if(_12c._delayTimer){_12c._clearTimer();}_12c._startTime=new Date().valueOf();if(_12c._paused){_12c._startTime-=_12c.duration*_12c._percent;}_12c._active=true;_12c._paused=false;var _12d=_12c.curve.getValue(_12c._getStep());if(!_12c._percent){if(!_12c._startRepeatCount){_12c._startRepeatCount=_12c.repeat;}_12c._fire("onBegin",[_12d]);}_12c._fire("onPlay",[_12d]);_12c._cycle();return _12c;},pause:function(){var _12e=this;if(_12e._delayTimer){_12e._clearTimer();}_12e._stopTimer();if(!_12e._active){return _12e;}_12e._paused=true;_12e._fire("onPause",[_12e.curve.getValue(_12e._getStep())]);return _12e;},gotoPercent:function(_12f,_130){var _131=this;_131._stopTimer();_131._active=_131._paused=true;_131._percent=_12f;if(_130){_131.play();}return _131;},stop:function(_132){var _133=this;if(_133._delayTimer){_133._clearTimer();}if(!_133._timer){return _133;}_133._stopTimer();if(_132){_133._percent=1;}_133._fire("onStop",[_133.curve.getValue(_133._getStep())]);_133._active=_133._paused=false;return _133;},status:function(){if(this._active){return this._paused?"paused":"playing";}return "stopped";},_cycle:function(){var _134=this;if(_134._active){var curr=new Date().valueOf();var step=_134.duration===0?1:(curr-_134._startTime)/(_134.duration);if(step>=1){step=1;}_134._percent=step;if(_134.easing){step=_134.easing(step);}_134._fire("onAnimate",[_134.curve.getValue(step)]);if(_134._percent<1){_134._startTimer();}else{_134._active=false;if(_134.repeat>0){_134.repeat--;_134.play(null,true);}else{if(_134.repeat==-1){_134.play(null,true);}else{if(_134._startRepeatCount){_134.repeat=_134._startRepeatCount;_134._startRepeatCount=0;}}}_134._percent=0;_134._fire("onEnd",[_134.node]);!_134.repeat&&_134._stopTimer();}}return _134;},_clearTimer:function(){clearTimeout(this._delayTimer);delete this._delayTimer;}});var ctr=0,_135=null,_136={run:function(){}};lang.extend(_124,{_startTimer:function(){if(!this._timer){this._timer=_11e.connect(_136,"run",this,"_cycle");ctr++;}if(!_135){_135=setInterval(lang.hitch(_136,"run"),this.rate);}},_stopTimer:function(){if(this._timer){_11e.disconnect(this._timer);this._timer=null;ctr--;}if(ctr<=0){clearInterval(_135);_135=null;ctr=0;}}});var _137=has("ie")?function(node){var ns=node.style;if(!ns.width.length&&_11f.get(node,"width")=="auto"){ns.width="auto";}}:function(){};_121._fade=function(args){args.node=dom.byId(args.node);var _138=_120({properties:{}},args),_139=(_138.properties.opacity={});_139.start=!("start" in _138)?function(){return +_11f.get(_138.node,"opacity")||0;}:_138.start;_139.end=_138.end;var anim=_121.animateProperty(_138);_11e.connect(anim,"beforeBegin",lang.partial(_137,_138.node));return anim;};_121.fadeIn=function(args){return _121._fade(_120({end:1},args));};_121.fadeOut=function(args){return _121._fade(_120({end:0},args));};_121._defaultEasing=function(n){return 0.5+((Math.sin((n+1.5)*Math.PI))/2);};var _13a=function(_13b){this._properties=_13b;for(var p in _13b){var prop=_13b[p];if(prop.start instanceof _11d){prop.tempColor=new _11d();}}};_13a.prototype.getValue=function(r){var ret={};for(var p in this._properties){var prop=this._properties[p],_13c=prop.start;if(_13c instanceof _11d){ret[p]=_11d.blendColors(_13c,prop.end,r,prop.tempColor).toCss();}else{if(!lang.isArray(_13c)){ret[p]=((prop.end-_13c)*r)+_13c+(p!="opacity"?prop.units||"px":0);}}}return ret;};_121.animateProperty=function(args){var n=args.node=dom.byId(args.node);if(!args.easing){args.easing=dojo._defaultEasing;}var anim=new _124(args);_11e.connect(anim,"beforeBegin",anim,function(){var pm={};for(var p in this.properties){if(p=="width"||p=="height"){this.node.display="block";}var prop=this.properties[p];if(lang.isFunction(prop)){prop=prop(n);}prop=pm[p]=_120({},(lang.isObject(prop)?prop:{end:prop}));if(lang.isFunction(prop.start)){prop.start=prop.start(n);}if(lang.isFunction(prop.end)){prop.end=prop.end(n);}var _13d=(p.toLowerCase().indexOf("color")>=0);function _13e(node,p){var v={height:node.offsetHeight,width:node.offsetWidth}[p];if(v!==undefined){return v;}v=_11f.get(node,p);return (p=="opacity")?+v:(_13d?v:parseFloat(v));};if(!("end" in prop)){prop.end=_13e(n,p);}else{if(!("start" in prop)){prop.start=_13e(n,p);}}if(_13d){prop.start=new _11d(prop.start);prop.end=new _11d(prop.end);}else{prop.start=(p=="opacity")?+prop.start:parseFloat(prop.start);}}this.curve=new _13a(pm);});_11e.connect(anim,"onAnimate",lang.hitch(_11f,"set",anim.node));return anim;};_121.anim=function(node,_13f,_140,_141,_142,_143){return _121.animateProperty({node:node,duration:_140||_124.prototype.duration,properties:_13f,easing:_141,onEnd:_142}).play(_143||0);};if(1){_120(dojo,_121);dojo._Animation=_124;}return _121;});},"dojo/dom-form":function(){define(["./_base/lang","./dom","./io-query","./json"],function(lang,dom,ioq,json){function _144(obj,name,_145){if(_145===null){return;}var val=obj[name];if(typeof val=="string"){obj[name]=[val,_145];}else{if(lang.isArray(val)){val.push(_145);}else{obj[name]=_145;}}};var _146="file|submit|image|reset|button";var form={fieldToObject:function fieldToObject(_147){var ret=null;_147=dom.byId(_147);if(_147){var _148=_147.name,type=(_147.type||"").toLowerCase();if(_148&&type&&!_147.disabled){if(type=="radio"||type=="checkbox"){if(_147.checked){ret=_147.value;}}else{if(_147.multiple){ret=[];var _149=[_147.firstChild];while(_149.length){for(var node=_149.pop();node;node=node.nextSibling){if(node.nodeType==1&&node.tagName.toLowerCase()=="option"){if(node.selected){ret.push(node.value);}}else{if(node.nextSibling){_149.push(node.nextSibling);}if(node.firstChild){_149.push(node.firstChild);}break;}}}}else{ret=_147.value;}}}}return ret;},toObject:function formToObject(_14a){var ret={},_14b=dom.byId(_14a).elements;for(var i=0,l=_14b.length;i=0){var mid=_18c.replace(/\./g,"/")+"_"+loc;_18a++;_190(mid,function(_197){for(var p in _197){_15d[_150.toAbsMid(p)+"/"+loc]=_197[p];}--_18a;while(!_18a&&_18b.length){load.apply(null,_18b.shift());}});return true;}return false;});};_195();_151.forEach(dojo.config.extraLocale,_195);},_177=function(id,_198,load){if(_18a){_18b.push([id,_198,load]);}return _18a;},_184=function(){};}if(1){var _199={},_19a=new Function("__bundle","__checkForLegacyModules","__mid","__amdValue","var define = function(mid, factory){define.called = 1; __amdValue.result = factory || mid;},"+"\t require = function(){define.called = 1;};"+"try{"+"define.called = 0;"+"eval(__bundle);"+"if(define.called==1)"+"return __amdValue;"+"if((__checkForLegacyModules = __checkForLegacyModules(__mid)))"+"return __checkForLegacyModules;"+"}catch(e){}"+"try{"+"return eval('('+__bundle+')');"+"}catch(e){"+"return e;"+"}"),_19b=function(deps,_19c,_19d){var _19e=[];_151.forEach(deps,function(mid){var url=_19d.toUrl(mid+".js");function load(text){var _19f=_19a(text,_184,mid,_199);if(_19f===_199){_19e.push(_15d[url]=_199.result);}else{if(_19f instanceof Error){console.error("failed to evaluate i18n bundle; url="+url,_19f);_19f={};}_19e.push(_15d[url]=(/nls\/[^\/]+\/[^\/]+$/.test(url)?_19f:{root:_19f,_v1x:1}));}};if(_15d[url]){_19e.push(_15d[url]);}else{var _1a0=_19d.syncLoadNls(mid);if(_1a0){_19e.push(_1a0);}else{if(!xhr){try{_19d.getText(url,true,load);}catch(e){_19e.push(_15d[url]={});}}else{xhr.get({url:url,sync:true,load:load,error:function(){_19e.push(_15d[url]={});}});}}}});_19c&&_19c.apply(null,_19e);};_184=function(_1a1){for(var _1a2,_1a3=_1a1.split("/"),_1a4=dojo.global[_1a3[0]],i=1;_1a4&&i<_1a3.length-1;_1a4=_1a4[_1a3[i++]]){}if(_1a4){_1a2=_1a4[_1a3[i]];if(!_1a2){_1a2=_1a4[_1a3[i].replace(/-/g,"_")];}if(_1a2){_15d[_1a1]=_1a2;}}return _1a2;};_154.getLocalization=function(_1a5,_1a6,_1a7){var _1a8,_1a9=_15e(_1a5,_1a6,_1a7);load(_1a9,(!isXd(_1a9,_150)?function(deps,_1aa){_19b(deps,_1aa,_150);}:_150),function(_1ab){_1a8=_1ab;});return _1a8;};if(has("dojo-unit-tests")){_185.push(function(doh){doh.register("tests.i18n.unit",function(t){var _1ac;_1ac=_19a("{prop:1}",_184,"nonsense",_199);t.is({prop:1},_1ac);t.is(undefined,_1ac[1]);_1ac=_19a("({prop:1})",_184,"nonsense",_199);t.is({prop:1},_1ac);t.is(undefined,_1ac[1]);_1ac=_19a("{'prop-x':1}",_184,"nonsense",_199);t.is({"prop-x":1},_1ac);t.is(undefined,_1ac[1]);_1ac=_19a("({'prop-x':1})",_184,"nonsense",_199);t.is({"prop-x":1},_1ac);t.is(undefined,_1ac[1]);_1ac=_19a("define({'prop-x':1})",_184,"nonsense",_199);t.is(_199,_1ac);t.is({"prop-x":1},_199.result);_1ac=_19a("define('some/module', {'prop-x':1})",_184,"nonsense",_199);t.is(_199,_1ac);t.is({"prop-x":1},_199.result);_1ac=_19a("this is total nonsense and should throw an error",_184,"nonsense",_199);t.is(_1ac instanceof Error,true);});});}}return lang.mixin(_154,{dynamic:true,normalize:_16f,load:load,cache:_15d});});},"dojo/promise/tracer":function(){define(["../_base/lang","./Promise","../Evented"],function(lang,_1ad,_1ae){"use strict";var _1af=new _1ae;var emit=_1af.emit;_1af.emit=null;function _1b0(args){setTimeout(function(){emit.apply(_1af,args);},0);};_1ad.prototype.trace=function(){var args=lang._toArray(arguments);this.then(function(_1b1){_1b0(["resolved",_1b1].concat(args));},function(_1b2){_1b0(["rejected",_1b2].concat(args));},function(_1b3){_1b0(["progress",_1b3].concat(args));});return this;};_1ad.prototype.traceRejected=function(){var args=lang._toArray(arguments);this.otherwise(function(_1b4){_1b0(["rejected",_1b4].concat(args));});return this;};return _1af;});},"dojo/errors/RequestError":function(){define(["./create"],function(_1b5){return _1b5("RequestError",function(_1b6,_1b7){this.response=_1b7;});});},"dojo/_base/html":function(){define("dojo/_base/html",["./kernel","../dom","../dom-style","../dom-attr","../dom-prop","../dom-class","../dom-construct","../dom-geometry"],function(dojo,dom,_1b8,attr,prop,cls,ctr,geom){dojo.byId=dom.byId;dojo.isDescendant=dom.isDescendant;dojo.setSelectable=dom.setSelectable;dojo.getAttr=attr.get;dojo.setAttr=attr.set;dojo.hasAttr=attr.has;dojo.removeAttr=attr.remove;dojo.getNodeProp=attr.getNodeProp;dojo.attr=function(node,name,_1b9){if(arguments.length==2){return attr[typeof name=="string"?"get":"set"](node,name);}return attr.set(node,name,_1b9);};dojo.hasClass=cls.contains;dojo.addClass=cls.add;dojo.removeClass=cls.remove;dojo.toggleClass=cls.toggle;dojo.replaceClass=cls.replace;dojo._toDom=dojo.toDom=ctr.toDom;dojo.place=ctr.place;dojo.create=ctr.create;dojo.empty=function(node){ctr.empty(node);};dojo._destroyElement=dojo.destroy=function(node){ctr.destroy(node);};dojo._getPadExtents=dojo.getPadExtents=geom.getPadExtents;dojo._getBorderExtents=dojo.getBorderExtents=geom.getBorderExtents;dojo._getPadBorderExtents=dojo.getPadBorderExtents=geom.getPadBorderExtents;dojo._getMarginExtents=dojo.getMarginExtents=geom.getMarginExtents;dojo._getMarginSize=dojo.getMarginSize=geom.getMarginSize;dojo._getMarginBox=dojo.getMarginBox=geom.getMarginBox;dojo.setMarginBox=geom.setMarginBox;dojo._getContentBox=dojo.getContentBox=geom.getContentBox;dojo.setContentSize=geom.setContentSize;dojo._isBodyLtr=dojo.isBodyLtr=geom.isBodyLtr;dojo._docScroll=dojo.docScroll=geom.docScroll;dojo._getIeDocumentElementOffset=dojo.getIeDocumentElementOffset=geom.getIeDocumentElementOffset;dojo._fixIeBiDiScrollLeft=dojo.fixIeBiDiScrollLeft=geom.fixIeBiDiScrollLeft;dojo.position=geom.position;dojo.marginBox=function marginBox(node,box){return box?geom.setMarginBox(node,box):geom.getMarginBox(node);};dojo.contentBox=function contentBox(node,box){return box?geom.setContentSize(node,box):geom.getContentBox(node);};dojo.coords=function(node,_1ba){dojo.deprecated("dojo.coords()","Use dojo.position() or dojo.marginBox().");node=dom.byId(node);var s=_1b8.getComputedStyle(node),mb=geom.getMarginBox(node,s);var abs=geom.position(node,_1ba);mb.x=abs.x;mb.y=abs.y;return mb;};dojo.getProp=prop.get;dojo.setProp=prop.set;dojo.prop=function(node,name,_1bb){if(arguments.length==2){return prop[typeof name=="string"?"get":"set"](node,name);}return prop.set(node,name,_1bb);};dojo.getStyle=_1b8.get;dojo.setStyle=_1b8.set;dojo.getComputedStyle=_1b8.getComputedStyle;dojo.__toPixelValue=dojo.toPixelValue=_1b8.toPixelValue;dojo.style=function(node,name,_1bc){switch(arguments.length){case 1:return _1b8.get(node);case 2:return _1b8[typeof name=="string"?"get":"set"](node,name);}return _1b8.set(node,name,_1bc);};return dojo;});},"dojo/_base/kernel":function(){define(["../has","./config","require","module"],function(has,_1bd,_1be,_1bf){var i,p,_1c0={},_1c1={},dojo={config:_1bd,global:this,dijit:_1c0,dojox:_1c1};var _1c2={dojo:["dojo",dojo],dijit:["dijit",_1c0],dojox:["dojox",_1c1]},_1c3=(_1be.map&&_1be.map[_1bf.id.match(/[^\/]+/)[0]]),item;for(p in _1c3){if(_1c2[p]){_1c2[p][0]=_1c3[p];}else{_1c2[p]=[_1c3[p],{}];}}for(p in _1c2){item=_1c2[p];item[1]._scopeName=item[0];if(!_1bd.noGlobals){this[item[0]]=item[1];}}dojo.scopeMap=_1c2;dojo.baseUrl=dojo.config.baseUrl=_1be.baseUrl;dojo.isAsync=!1||_1be.async;dojo.locale=_1bd.locale;var rev="$Rev: 29801 $".match(/\d+/);dojo.version={major:1,minor:8,patch:1,flag:"",revision:rev?+rev[0]:NaN,toString:function(){var v=dojo.version;return v.major+"."+v.minor+"."+v.patch+v.flag+" ("+v.revision+")";}};1||has.add("extend-dojo",1);(Function("d","d.eval = function(){return d.global.eval ? d.global.eval(arguments[0]) : eval(arguments[0]);}"))(dojo);if(0){dojo.exit=function(_1c4){quit(_1c4);};}else{dojo.exit=function(){};}1||has.add("dojo-guarantee-console",1);if(1){typeof console!="undefined"||(console={});var cn=["assert","count","debug","dir","dirxml","error","group","groupEnd","info","profile","profileEnd","time","timeEnd","trace","warn","log"];var tn;i=0;while((tn=cn[i++])){if(!console[tn]){(function(){var tcn=tn+"";console[tcn]=("log" in console)?function(){var a=Array.apply({},arguments);a.unshift(tcn+":");console["log"](a.join(" "));}:function(){};console[tcn]._fake=true;})();}}}has.add("dojo-debug-messages",!!_1bd.isDebug);dojo.deprecated=dojo.experimental=function(){};if(has("dojo-debug-messages")){dojo.deprecated=function(_1c5,_1c6,_1c7){var _1c8="DEPRECATED: "+_1c5;if(_1c6){_1c8+=" "+_1c6;}if(_1c7){_1c8+=" -- will be removed in version: "+_1c7;}console.warn(_1c8);};dojo.experimental=function(_1c9,_1ca){var _1cb="EXPERIMENTAL: "+_1c9+" -- APIs subject to change without notice.";if(_1ca){_1cb+=" "+_1ca;}console.warn(_1cb);};}1||has.add("dojo-modulePaths",1);if(1){if(_1bd.modulePaths){dojo.deprecated("dojo.modulePaths","use paths configuration");var _1cc={};for(p in _1bd.modulePaths){_1cc[p.replace(/\./g,"/")]=_1bd.modulePaths[p];}_1be({paths:_1cc});}}1||has.add("dojo-moduleUrl",1);if(1){dojo.moduleUrl=function(_1cd,url){dojo.deprecated("dojo.moduleUrl()","use require.toUrl","2.0");var _1ce=null;if(_1cd){_1ce=_1be.toUrl(_1cd.replace(/\./g,"/")+(url?("/"+url):"")+"/*.*").replace(/\/\*\.\*/,"")+(url?"":"/");}return _1ce;};}dojo._hasResource={};return dojo;});},"dojo/io-query":function(){define(["./_base/lang"],function(lang){var _1cf={};return {objectToQuery:function objectToQuery(map){var enc=encodeURIComponent,_1d0=[];for(var name in map){var _1d1=map[name];if(_1d1!=_1cf[name]){var _1d2=enc(name)+"=";if(lang.isArray(_1d1)){for(var i=0,l=_1d1.length;i=0;i--){var node=(_20b?this._cloneNode(ary[i]):ary[i]);if(ary._runParse&&dojo.parser&&dojo.parser.parse){if(!_20d){_20d=_20c.ownerDocument.createElement("div");}_20d.appendChild(node);dojo.parser.parse(_20d);node=_20d.firstChild;while(_20d.firstChild){_20d.removeChild(_20d.firstChild);}}if(i==_20e-1){_1fb.place(node,_20c,_20a);}else{_20c.parentNode.insertBefore(node,_20c);}_20c=node;}},position:aam(_1fc.position),attr:awc(_202(_1fd),_1ff),style:awc(_202(_1fe),_1ff),addClass:aafe(_1fa.add),removeClass:aafe(_1fa.remove),toggleClass:aafe(_1fa.toggle),replaceClass:aafe(_1fa.replace),empty:aafe(_1fb.empty),removeAttr:aafe(_1fd.remove),marginBox:aam(_1fc.getMarginBox),place:function(_20f,_210){var item=_1f8(_20f)[0];return this.forEach(function(node){_1fb.place(node,item,_210);});},orphan:function(_211){return (_211?_1f8._filterResult(this,_211):this).forEach(_200);},adopt:function(_212,_213){return _1f8(_212).place(this[0],_213)._stash(this);},query:function(_214){if(!_214){return this;}var ret=new _201;this.map(function(node){_1f8(_214,node).forEach(function(_215){if(_215!==undefined){ret.push(_215);}});});return ret._stash(this);},filter:function(_216){var a=arguments,_217=this,_218=0;if(typeof _216=="string"){_217=_1f8._filterResult(this,a[0]);if(a.length==1){return _217._stash(this);}_218=1;}return this._wrap(_1f9.filter(_217,a[_218],a[_218+1]),this);},addContent:function(_219,_21a){_219=this._normalize(_219,this[0]);for(var i=0,node;(node=this[i]);i++){this._place(_219,node,_21a,i>0);}return this;}});return _201;});},"dojo/query":function(){define(["./_base/kernel","./has","./dom","./on","./_base/array","./_base/lang","./selector/_loader","./selector/_loader!default"],function(dojo,has,dom,on,_21b,lang,_21c,_21d){"use strict";has.add("array-extensible",function(){return lang.delegate([],{length:1}).length==1&&!has("bug-for-in-skips-shadowed");});var ap=Array.prototype,aps=ap.slice,apc=ap.concat,_21e=_21b.forEach;var tnl=function(a,_21f,_220){var _221=new (_220||this._NodeListCtor||nl)(a);return _21f?_221._stash(_21f):_221;};var _222=function(f,a,o){a=[0].concat(aps.call(a,0));o=o||dojo.global;return function(node){a[0]=node;return f.apply(o,a);};};var _223=function(f,o){return function(){this.forEach(_222(f,arguments,o));return this;};};var _224=function(f,o){return function(){return this.map(_222(f,arguments,o));};};var _225=function(f,o){return function(){return this.filter(_222(f,arguments,o));};};var _226=function(f,g,o){return function(){var a=arguments,body=_222(f,a,o);if(g.call(o||dojo.global,a)){return this.map(body);}this.forEach(body);return this;};};var _227=function(_228){var _229=this instanceof nl&&has("array-extensible");if(typeof _228=="number"){_228=Array(_228);}var _22a=(_228&&"length" in _228)?_228:arguments;if(_229||!_22a.sort){var _22b=_229?this:[],l=_22b.length=_22a.length;for(var i=0;i0;};_23c.filter=_23a.filter||function(_240,_241,root){return _23c(_241,root).filter(function(node){return _21b.indexOf(_240,node)>-1;});};if(typeof _23a!="function"){var _242=_23a.search;_23a=function(_243,root){return _242(root||document,_243);};}return _23c;};var _236=_239(_21d,_227);dojo.query=_239(_21d,function(_244){return _227(_244);});_236.load=function(id,_245,_246){_21c.load(id,_245,function(_247){_246(_239(_247,_227));});};dojo._filterQueryResult=_236._filterResult=function(_248,_249,root){return new _227(_236.filter(_248,_249,root));};dojo.NodeList=_236.NodeList=_227;return _236;});},"dojo/has":function(){define(["require","module"],function(_24a,_24b){var has=_24a.has||function(){};if(!1){var _24c=typeof window!="undefined"&&typeof location!="undefined"&&typeof document!="undefined"&&window.location==location&&window.document==document,_24d=this,doc=_24c&&document,_24e=doc&&doc.createElement("DiV"),_24f=(_24b.config&&_24b.config())||{};has=function(name){return typeof _24f[name]=="function"?(_24f[name]=_24f[name](_24d,doc,_24e)):_24f[name];};has.cache=_24f;has.add=function(name,test,now,_250){(typeof _24f[name]=="undefined"||_250)&&(_24f[name]=test);return now&&has(name);};1||has.add("host-browser",_24c);1||has.add("dom",_24c);1||has.add("dojo-dom-ready-api",1);1||has.add("dojo-sniff",1);}if(1){has.add("dom-addeventlistener",!!document.addEventListener);has.add("touch","ontouchstart" in document);has.add("device-width",screen.availWidth||innerWidth);var form=document.createElement("form");has.add("dom-attributes-explicit",form.attributes.length==0);has.add("dom-attributes-specified-flag",form.attributes.length>0&&form.attributes.length<40);}has.clearElement=function(_251){_251.innerHTML="";return _251;};has.normalize=function(id,_252){var _253=id.match(/[\?:]|[^:\?]*/g),i=0,get=function(skip){var term=_253[i++];if(term==":"){return 0;}else{if(_253[i++]=="?"){if(!skip&&has(term)){return get();}else{get(true);return get(skip);}}return term||0;}};id=get();return id&&_252(id);};has.load=function(id,_254,_255){if(id){_254([id],_255);}else{_255();}};return has;});},"dojo/_base/loader":function(){define(["./kernel","../has","require","module","./json","./lang","./array"],function(dojo,has,_256,_257,json,lang,_258){if(!1){console.error("cannot load the Dojo v1.x loader with a foreign loader");return 0;}1||has.add("dojo-fast-sync-require",1);var _259=function(id){return {src:_257.id,id:id};},_25a=function(name){return name.replace(/\./g,"/");},_25b=/\/\/>>built/,_25c=[],_25d=[],_25e=function(mid,_25f,_260){_25c.push(_260);_258.forEach(mid.split(","),function(mid){var _261=_262(mid,_25f.module);_25d.push(_261);_263(_261);});_264();},_264=(1?function(){var _265,mid;for(mid in _266){_265=_266[mid];if(_265.noReqPluginCheck===undefined){_265.noReqPluginCheck=/loadInit\!/.test(mid)||/require\!/.test(mid)?1:0;}if(!_265.executed&&!_265.noReqPluginCheck&&_265.injected==_267){return;}}_268(function(){var _269=_25c;_25c=[];_258.forEach(_269,function(cb){cb(1);});});}:(function(){var _26a,_26b=function(m){_26a[m.mid]=1;for(var t,_26c,deps=m.deps||[],i=0;i=0;--j){_2ce=lin[j].prototype;if(!_2ce.hasOwnProperty("declaredClass")){_2ce.declaredClass="uniqName_"+(_2c5++);}name=_2ce.declaredClass;if(!_2cc.hasOwnProperty(name)){_2cc[name]={count:0,refs:[],cls:lin[j]};++_2cd;}rec=_2cc[name];if(top&&top!==rec){rec.refs.push(top);++top.count;}top=rec;}++top.count;_2cb[0].refs.push(top);}while(_2cb.length){top=_2cb.pop();_2ca.push(top.cls);--_2cd;while(refs=top.refs,refs.length==1){top=refs[0];if(!top||--top.count){top=0;break;}_2ca.push(top.cls);--_2cd;}if(top){for(i=0,l=refs.length;i=0;--i){f=_2e8[i];m=f._meta;f=m?m.ctor:f;if(f){f.apply(this,_2ea?_2ea[i]:a);}}f=this.postscript;if(f){f.apply(this,args);}};};function _2ec(ctor,_2ed){return function(){var a=arguments,t=a,a0=a[0],f;if(!(this instanceof a.callee)){return _2eb(a);}if(_2ed){if(a0){f=a0.preamble;if(f){t=f.apply(this,t)||t;}}f=this.preamble;if(f){f.apply(this,t);}}if(ctor){ctor.apply(this,a);}f=this.postscript;if(f){f.apply(this,a);}};};function _2ee(_2ef){return function(){var a=arguments,i=0,f,m;if(!(this instanceof a.callee)){return _2eb(a);}for(;f=_2ef[i];++i){m=f._meta;f=m?m.ctor:f;if(f){f.apply(this,a);break;}}f=this.postscript;if(f){f.apply(this,a);}};};function _2f0(name,_2f1,_2f2){return function(){var b,m,f,i=0,step=1;if(_2f2){i=_2f1.length-1;step=-1;}for(;b=_2f1[i];i+=step){m=b._meta;f=(m?m.hidden:b.prototype)[name];if(f){f.apply(this,arguments);}}};};function _2f3(ctor){xtor.prototype=ctor.prototype;var t=new xtor;xtor.prototype=null;return t;};function _2eb(args){var ctor=args.callee,t=_2f3(ctor);ctor.apply(t,args);return t;};function _2e4(_2f4,_2f5,_2f6){if(typeof _2f4!="string"){_2f6=_2f5;_2f5=_2f4;_2f4="";}_2f6=_2f6||{};var _2f7,i,t,ctor,name,_2f8,_2f9,_2fa=1,_2fb=_2f5;if(opts.call(_2f5)=="[object Array]"){_2f8=_2c7(_2f5,_2f4);t=_2f8[0];_2fa=_2f8.length-t;_2f5=_2f8[_2fa];}else{_2f8=[0];if(_2f5){if(opts.call(_2f5)=="[object Function]"){t=_2f5._meta;_2f8=_2f8.concat(t?t.bases:_2f5);}else{err("base class is not a callable constructor.",_2f4);}}else{if(_2f5!==null){err("unknown base class. Did you use dojo.require to pull it in?",_2f4);}}}if(_2f5){for(i=_2fa-1;;--i){_2f7=_2f3(_2f5);if(!i){break;}t=_2f8[i];(t._meta?_2da:mix)(_2f7,t.prototype);ctor=new Function;ctor.superclass=_2f5;ctor.prototype=_2f7;_2f5=_2f7.constructor=ctor;}}else{_2f7={};}_2e4.safeMixin(_2f7,_2f6);t=_2f6.constructor;if(t!==op.constructor){t.nom=_2c6;_2f7.constructor=t;}for(i=_2fa-1;i;--i){t=_2f8[i]._meta;if(t&&t.chains){_2f9=mix(_2f9||{},t.chains);}}if(_2f7["-chains-"]){_2f9=mix(_2f9||{},_2f7["-chains-"]);}t=!_2f9||!_2f9.hasOwnProperty(_2c6);_2f8[0]=ctor=(_2f9&&_2f9.constructor==="manual")?_2ee(_2f8):(_2f8.length==1?_2ec(_2f6.constructor,t):_2e7(_2f8,t));ctor._meta={bases:_2f8,hidden:_2f6,chains:_2f9,parents:_2fb,ctor:_2f6.constructor};ctor.superclass=_2f5&&_2f5.prototype;ctor.extend=_2e2;ctor.createSubclass=_2e5;ctor.prototype=_2f7;_2f7.constructor=ctor;_2f7.getInherited=_2d5;_2f7.isInstanceOf=_2d8;_2f7.inherited=_2d7;_2f7.__inherited=_2cf;if(_2f4){_2f7.declaredClass=_2f4;lang.setObject(_2f4,ctor);}if(_2f9){for(name in _2f9){if(_2f7[name]&&typeof _2f9[name]=="string"&&name!=_2c6){t=_2f7[name]=_2f0(name,_2f8,_2f9[name]==="after");t.nom=name;}}}return ctor;};dojo.safeMixin=_2e4.safeMixin=_2de;dojo.declare=_2e4;return _2e4;});},"dojo/dom":function(){define(["./sniff","./_base/lang","./_base/window"],function(has,lang,win){if(has("ie")<=7){try{document.execCommand("BackgroundImageCache",false,true);}catch(e){}}var dom={};if(has("ie")){dom.byId=function(id,doc){if(typeof id!="string"){return id;}var _2fc=doc||win.doc,te=id&&_2fc.getElementById(id);if(te&&(te.attributes.id.value==id||te.id==id)){return te;}else{var eles=_2fc.all[id];if(!eles||eles.nodeName){eles=[eles];}var i=0;while((te=eles[i++])){if((te.attributes&&te.attributes.id&&te.attributes.id.value==id)||te.id==id){return te;}}}};}else{dom.byId=function(id,doc){return ((typeof id=="string")?(doc||win.doc).getElementById(id):id)||null;};}dom.isDescendant=function(node,_2fd){try{node=dom.byId(node);_2fd=dom.byId(_2fd);while(node){if(node==_2fd){return true;}node=node.parentNode;}}catch(e){}return false;};dom.setSelectable=function(node,_2fe){node=dom.byId(node);if(has("mozilla")){node.style.MozUserSelect=_2fe?"":"none";}else{if(has("khtml")||has("webkit")){node.style.KhtmlUserSelect=_2fe?"auto":"none";}else{if(has("ie")){var v=(node.unselectable=_2fe?"":"on"),cs=node.getElementsByTagName("*"),i=0,l=cs.length;for(;i=0){_306+=" * ";}else{_306+=" ";}var ts=function(s,e){return trim(_306.slice(s,e));};var _307=[];var _308=-1,_309=-1,_30a=-1,_30b=-1,_30c=-1,inId=-1,_30d=-1,_30e,lc="",cc="",_30f;var x=0,ql=_306.length,_310=null,_311=null;var _312=function(){if(_30d>=0){var tv=(_30d==x)?null:ts(_30d,x);_310[(_302.indexOf(tv)<0)?"tag":"oper"]=tv;_30d=-1;}};var _313=function(){if(inId>=0){_310.id=ts(inId,x).replace(/\\/g,"");inId=-1;}};var _314=function(){if(_30c>=0){_310.classes.push(ts(_30c+1,x).replace(/\\/g,""));_30c=-1;}};var _315=function(){_313();_312();_314();};var _316=function(){_315();if(_30b>=0){_310.pseudos.push({name:ts(_30b+1,x)});}_310.loops=(_310.pseudos.length||_310.attrs.length||_310.classes.length);_310.oquery=_310.query=ts(_30f,x);_310.otag=_310.tag=(_310["oper"])?null:(_310.tag||"*");if(_310.tag){_310.tag=_310.tag.toUpperCase();}if(_307.length&&(_307[_307.length-1].oper)){_310.infixOper=_307.pop();_310.query=_310.infixOper.query+" "+_310.query;}_307.push(_310);_310=null;};for(;lc=cc,cc=_306.charAt(x),x=0){if(cc=="]"){if(!_311.attr){_311.attr=ts(_308+1,x);}else{_311.matchFor=ts((_30a||_308+1),x);}var cmf=_311.matchFor;if(cmf){if((cmf.charAt(0)=="\"")||(cmf.charAt(0)=="'")){_311.matchFor=cmf.slice(1,-1);}}if(_311.matchFor){_311.matchFor=_311.matchFor.replace(/\\/g,"");}_310.attrs.push(_311);_311=null;_308=_30a=-1;}else{if(cc=="="){var _317=("|~^$*".indexOf(lc)>=0)?lc:"";_311.type=_317+cc;_311.attr=ts(_308+1,x-_317.length);_30a=x+1;}}}else{if(_309>=0){if(cc==")"){if(_30b>=0){_311.value=ts(_309+1,x);}_30b=_309=-1;}}else{if(cc=="#"){_315();inId=x+1;}else{if(cc=="."){_315();_30c=x;}else{if(cc==":"){_315();_30b=x;}else{if(cc=="["){_315();_308=x;_311={};}else{if(cc=="("){if(_30b>=0){_311={name:ts(_30b+1,x),value:null};_310.pseudos.push(_311);}_309=x;}else{if((cc==" ")&&(lc!=cc)){_316();}}}}}}}}}return _307;};var _318=function(_319,_31a){if(!_319){return _31a;}if(!_31a){return _319;}return function(){return _319.apply(window,arguments)&&_31a.apply(window,arguments);};};var _31b=function(i,arr){var r=arr||[];if(i){r.push(i);}return r;};var _31c=function(n){return (1==n.nodeType);};var _31d="";var _31e=function(elem,attr){if(!elem){return _31d;}if(attr=="class"){return elem.className||_31d;}if(attr=="for"){return elem.htmlFor||_31d;}if(attr=="style"){return elem.style.cssText||_31d;}return (_303?elem.getAttribute(attr):elem.getAttribute(attr,2))||_31d;};var _31f={"*=":function(attr,_320){return function(elem){return (_31e(elem,attr).indexOf(_320)>=0);};},"^=":function(attr,_321){return function(elem){return (_31e(elem,attr).indexOf(_321)==0);};},"$=":function(attr,_322){return function(elem){var ea=" "+_31e(elem,attr);var _323=ea.lastIndexOf(_322);return _323>-1&&(_323==(ea.length-_322.length));};},"~=":function(attr,_324){var tval=" "+_324+" ";return function(elem){var ea=" "+_31e(elem,attr)+" ";return (ea.indexOf(tval)>=0);};},"|=":function(attr,_325){var _326=_325+"-";return function(elem){var ea=_31e(elem,attr);return ((ea==_325)||(ea.indexOf(_326)==0));};},"=":function(attr,_327){return function(elem){return (_31e(elem,attr)==_327);};}};var _328=(typeof _300().firstChild.nextElementSibling=="undefined");var _329=!_328?"nextElementSibling":"nextSibling";var _32a=!_328?"previousElementSibling":"previousSibling";var _32b=(_328?_31c:_304);var _32c=function(node){while(node=node[_32a]){if(_32b(node)){return false;}}return true;};var _32d=function(node){while(node=node[_329]){if(_32b(node)){return false;}}return true;};var _32e=function(node){var root=node.parentNode;root=root.nodeType!=7?root:root.nextSibling;var i=0,tret=root.children||root.childNodes,ci=(node["_i"]||node.getAttribute("_i")||-1),cl=(root["_l"]||(typeof root.getAttribute!=="undefined"?root.getAttribute("_l"):-1));if(!tret){return -1;}var l=tret.length;if(cl==l&&ci>=0&&cl>=0){return ci;}if(has("ie")&&typeof root.setAttribute!=="undefined"){root.setAttribute("_l",l);}else{root["_l"]=l;}ci=-1;for(var te=root["firstElementChild"]||root["firstChild"];te;te=te[_329]){if(_32b(te)){if(has("ie")){te.setAttribute("_i",++i);}else{te["_i"]=++i;}if(node===te){ci=i;}}}return ci;};var _32f=function(elem){return !((_32e(elem))%2);};var _330=function(elem){return ((_32e(elem))%2);};var _331={"checked":function(name,_332){return function(elem){return !!("checked" in elem?elem.checked:elem.selected);};},"disabled":function(name,_333){return function(elem){return elem.disabled;};},"enabled":function(name,_334){return function(elem){return !elem.disabled;};},"first-child":function(){return _32c;},"last-child":function(){return _32d;},"only-child":function(name,_335){return function(node){return _32c(node)&&_32d(node);};},"empty":function(name,_336){return function(elem){var cn=elem.childNodes;var cnl=elem.childNodes.length;for(var x=cnl-1;x>=0;x--){var nt=cn[x].nodeType;if((nt===1)||(nt==3)){return false;}}return true;};},"contains":function(name,_337){var cz=_337.charAt(0);if(cz=="\""||cz=="'"){_337=_337.slice(1,-1);}return function(elem){return (elem.innerHTML.indexOf(_337)>=0);};},"not":function(name,_338){var p=_305(_338)[0];var _339={el:1};if(p.tag!="*"){_339.tag=1;}if(!p.classes.length){_339.classes=1;}var ntf=_33a(p,_339);return function(elem){return (!ntf(elem));};},"nth-child":function(name,_33b){var pi=parseInt;if(_33b=="odd"){return _330;}else{if(_33b=="even"){return _32f;}}if(_33b.indexOf("n")!=-1){var _33c=_33b.split("n",2);var pred=_33c[0]?((_33c[0]=="-")?-1:pi(_33c[0])):1;var idx=_33c[1]?pi(_33c[1]):0;var lb=0,ub=-1;if(pred>0){if(idx<0){idx=(idx%pred)&&(pred+(idx%pred));}else{if(idx>0){if(idx>=pred){lb=idx-idx%pred;}idx=idx%pred;}}}else{if(pred<0){pred*=-1;if(idx>0){ub=idx;idx=idx%pred;}}}if(pred>0){return function(elem){var i=_32e(elem);return (i>=lb)&&(ub<0||i<=ub)&&((i%pred)==idx);};}else{_33b=idx;}}var _33d=pi(_33b);return function(elem){return (_32e(elem)==_33d);};}};var _33e=(has("ie")&&(has("ie")<9||has("quirks")))?function(cond){var clc=cond.toLowerCase();if(clc=="class"){cond="className";}return function(elem){return (_303?elem.getAttribute(cond):elem[cond]||elem[clc]);};}:function(cond){return function(elem){return (elem&&elem.getAttribute&&elem.hasAttribute(cond));};};var _33a=function(_33f,_340){if(!_33f){return _304;}_340=_340||{};var ff=null;if(!("el" in _340)){ff=_318(ff,_31c);}if(!("tag" in _340)){if(_33f.tag!="*"){ff=_318(ff,function(elem){return (elem&&((_303?elem.tagName:elem.tagName.toUpperCase())==_33f.getTag()));});}}if(!("classes" in _340)){each(_33f.classes,function(_341,idx,arr){var re=new RegExp("(?:^|\\s)"+_341+"(?:\\s|$)");ff=_318(ff,function(elem){return re.test(elem.className);});ff.count=idx;});}if(!("pseudos" in _340)){each(_33f.pseudos,function(_342){var pn=_342.name;if(_331[pn]){ff=_318(ff,_331[pn](pn,_342.value));}});}if(!("attrs" in _340)){each(_33f.attrs,function(attr){var _343;var a=attr.attr;if(attr.type&&_31f[attr.type]){_343=_31f[attr.type](a,attr.matchFor);}else{if(a.length){_343=_33e(a);}}if(_343){ff=_318(ff,_343);}});}if(!("id" in _340)){if(_33f.id){ff=_318(ff,function(elem){return (!!elem&&(elem.id==_33f.id));});}}if(!ff){if(!("default" in _340)){ff=_304;}}return ff;};var _344=function(_345){return function(node,ret,bag){while(node=node[_329]){if(_328&&(!_31c(node))){continue;}if((!bag||_346(node,bag))&&_345(node)){ret.push(node);}break;}return ret;};};var _347=function(_348){return function(root,ret,bag){var te=root[_329];while(te){if(_32b(te)){if(bag&&!_346(te,bag)){break;}if(_348(te)){ret.push(te);}}te=te[_329];}return ret;};};var _349=function(_34a){_34a=_34a||_304;return function(root,ret,bag){var te,x=0,tret=root.children||root.childNodes;while(te=tret[x++]){if(_32b(te)&&(!bag||_346(te,bag))&&(_34a(te,x))){ret.push(te);}}return ret;};};var _34b=function(node,root){var pn=node.parentNode;while(pn){if(pn==root){break;}pn=pn.parentNode;}return !!pn;};var _34c={};var _34d=function(_34e){var _34f=_34c[_34e.query];if(_34f){return _34f;}var io=_34e.infixOper;var oper=(io?io.oper:"");var _350=_33a(_34e,{el:1});var qt=_34e.tag;var _351=("*"==qt);var ecs=_300()["getElementsByClassName"];if(!oper){if(_34e.id){_350=(!_34e.loops&&_351)?_304:_33a(_34e,{el:1,id:1});_34f=function(root,arr){var te=dom.byId(_34e.id,(root.ownerDocument||root));if(!te||!_350(te)){return;}if(9==root.nodeType){return _31b(te,arr);}else{if(_34b(te,root)){return _31b(te,arr);}}};}else{if(ecs&&/\{\s*\[native code\]\s*\}/.test(String(ecs))&&_34e.classes.length&&!_301){_350=_33a(_34e,{el:1,classes:1,id:1});var _352=_34e.classes.join(" ");_34f=function(root,arr,bag){var ret=_31b(0,arr),te,x=0;var tret=root.getElementsByClassName(_352);while((te=tret[x++])){if(_350(te,root)&&_346(te,bag)){ret.push(te);}}return ret;};}else{if(!_351&&!_34e.loops){_34f=function(root,arr,bag){var ret=_31b(0,arr),te,x=0;var tag=_34e.getTag(),tret=tag?root.getElementsByTagName(tag):[];while((te=tret[x++])){if(_346(te,bag)){ret.push(te);}}return ret;};}else{_350=_33a(_34e,{el:1,tag:1,id:1});_34f=function(root,arr,bag){var ret=_31b(0,arr),te,x=0;var tag=_34e.getTag(),tret=tag?root.getElementsByTagName(tag):[];while((te=tret[x++])){if(_350(te,root)&&_346(te,bag)){ret.push(te);}}return ret;};}}}}else{var _353={el:1};if(_351){_353.tag=1;}_350=_33a(_34e,_353);if("+"==oper){_34f=_344(_350);}else{if("~"==oper){_34f=_347(_350);}else{if(">"==oper){_34f=_349(_350);}}}}return _34c[_34e.query]=_34f;};var _354=function(root,_355){var _356=_31b(root),qp,x,te,qpl=_355.length,bag,ret;for(var i=0;i0){bag={};ret.nozip=true;}var gef=_34d(qp);for(var j=0;(te=_356[j]);j++){gef(te,ret,bag);}if(!ret.length){break;}_356=ret;}return ret;};var _357={},_358={};var _359=function(_35a){var _35b=_305(trim(_35a));if(_35b.length==1){var tef=_34d(_35b[0]);return function(root){var r=tef(root,[]);if(r){r.nozip=true;}return r;};}return function(root){return _354(root,_35b);};};var _35c=has("ie")?"commentStrip":"nozip";var qsa="querySelectorAll";var _35d=!!_300()[qsa];var _35e=/\\[>~+]|n\+\d|([^ \\])?([>~+])([^ =])?/g;var _35f=function(_360,pre,ch,post){return ch?(pre?pre+" ":"")+ch+(post?" "+post:""):_360;};var _361=/([^[]*)([^\]]*])?/g;var _362=function(_363,_364,att){return _364.replace(_35e,_35f)+(att||"");};var _365=function(_366,_367){_366=_366.replace(_361,_362);if(_35d){var _368=_358[_366];if(_368&&!_367){return _368;}}var _369=_357[_366];if(_369){return _369;}var qcz=_366.charAt(0);var _36a=(-1==_366.indexOf(" "));if((_366.indexOf("#")>=0)&&(_36a)){_367=true;}var _36b=(_35d&&(!_367)&&(_302.indexOf(qcz)==-1)&&(!has("ie")||(_366.indexOf(":")==-1))&&(!(_301&&(_366.indexOf(".")>=0)))&&(_366.indexOf(":contains")==-1)&&(_366.indexOf(":checked")==-1)&&(_366.indexOf("|=")==-1));if(_36b){var tq=(_302.indexOf(_366.charAt(_366.length-1))>=0)?(_366+" *"):_366;return _358[_366]=function(root){try{if(!((9==root.nodeType)||_36a)){throw "";}var r=root[qsa](tq);r[_35c]=true;return r;}catch(e){return _365(_366,true)(root);}};}else{var _36c=_366.match(/([^\s,](?:"(?:\\.|[^"])+"|'(?:\\.|[^'])+'|[^,])*)/g);return _357[_366]=((_36c.length<2)?_359(_366):function(root){var _36d=0,ret=[],tp;while((tp=_36c[_36d++])){ret=ret.concat(_359(tp)(root));}return ret;});}};var _36e=0;var _36f=has("ie")?function(node){if(_303){return (node.getAttribute("_uid")||node.setAttribute("_uid",++_36e)||_36e);}else{return node.uniqueID;}}:function(node){return (node._uid||(node._uid=++_36e));};var _346=function(node,bag){if(!bag){return 1;}var id=_36f(node);if(!bag[id]){return bag[id]=1;}return 0;};var _370="_zipIdx";var _371=function(arr){if(arr&&arr.nozip){return arr;}var ret=[];if(!arr||!arr.length){return ret;}if(arr[0]){ret.push(arr[0]);}if(arr.length<2){return ret;}_36e++;var x,te;if(has("ie")&&_303){var _372=_36e+"";arr[0].setAttribute(_370,_372);for(x=1;te=arr[x];x++){if(arr[x].getAttribute(_370)!=_372){ret.push(te);}te.setAttribute(_370,_372);}}else{if(has("ie")&&arr.commentStrip){try{for(x=1;te=arr[x];x++){if(_31c(te)){ret.push(te);}}}catch(e){}}else{if(arr[0]){arr[0][_370]=_36e;}for(x=1;te=arr[x];x++){if(arr[x][_370]!=_36e){ret.push(te);}te[_370]=_36e;}}}return ret;};var _373=function(_374,root){root=root||_300();var od=root.ownerDocument||root;_303=(od.createElement("div").tagName==="div");var r=_365(_374)(root);if(r&&r.nozip){return r;}return _371(r);};_373.filter=function(_375,_376,root){var _377=[],_378=_305(_376),_379=(_378.length==1&&!/[^\w#\.]/.test(_376))?_33a(_378[0]):function(node){return _2ff.indexOf(_373(_376,dom.byId(root)),node)!=-1;};for(var x=0,te;te=_375[x];x++){if(_379(te)){_377.push(te);}}return _377;};return _373;});},"dojo/errors/RequestTimeoutError":function(){define("dojo/errors/RequestTimeoutError",["./create","./RequestError"],function(_37a,_37b){return _37a("RequestTimeoutError",null,_37b,{dojoType:"timeout"});});},"dojo/dom-style":function(){define("dojo/dom-style",["./sniff","./dom"],function(has,dom){var _37c,_37d={};if(has("webkit")){_37c=function(node){var s;if(node.nodeType==1){var dv=node.ownerDocument.defaultView;s=dv.getComputedStyle(node,null);if(!s&&node.style){node.style.display="";s=dv.getComputedStyle(node,null);}}return s||{};};}else{if(has("ie")&&(has("ie")<9||has("quirks"))){_37c=function(node){return node.nodeType==1&&node.currentStyle?node.currentStyle:{};};}else{_37c=function(node){return node.nodeType==1?node.ownerDocument.defaultView.getComputedStyle(node,null):{};};}}_37d.getComputedStyle=_37c;var _37e;if(!has("ie")){_37e=function(_37f,_380){return parseFloat(_380)||0;};}else{_37e=function(_381,_382){if(!_382){return 0;}if(_382=="medium"){return 4;}if(_382.slice&&_382.slice(-2)=="px"){return parseFloat(_382);}var s=_381.style,rs=_381.runtimeStyle,cs=_381.currentStyle,_383=s.left,_384=rs.left;rs.left=cs.left;try{s.left=_382;_382=s.pixelLeft;}catch(e){_382=0;}s.left=_383;rs.left=_384;return _382;};}_37d.toPixelValue=_37e;var astr="DXImageTransform.Microsoft.Alpha";var af=function(n,f){try{return n.filters.item(astr);}catch(e){return f?{}:null;}};var _385=has("ie")<9||(has("ie")&&has("quirks"))?function(node){try{return af(node).Opacity/100;}catch(e){return 1;}}:function(node){return _37c(node).opacity;};var _386=has("ie")<9||(has("ie")&&has("quirks"))?function(node,_387){var ov=_387*100,_388=_387==1;node.style.zoom=_388?"":1;if(!af(node)){if(_388){return _387;}node.style.filter+=" progid:"+astr+"(Opacity="+ov+")";}else{af(node,1).Opacity=ov;}af(node,1).Enabled=!_388;if(node.tagName.toLowerCase()=="tr"){for(var td=node.firstChild;td;td=td.nextSibling){if(td.tagName.toLowerCase()=="td"){_386(td,_387);}}}return _387;}:function(node,_389){return node.style.opacity=_389;};var _38a={left:true,top:true};var _38b=/margin|padding|width|height|max|min|offset/;function _38c(node,type,_38d){type=type.toLowerCase();if(has("ie")){if(_38d=="auto"){if(type=="height"){return node.offsetHeight;}if(type=="width"){return node.offsetWidth;}}if(type=="fontweight"){switch(_38d){case 700:return "bold";case 400:default:return "normal";}}}if(!(type in _38a)){_38a[type]=_38b.test(type);}return _38a[type]?_37e(node,_38d):_38d;};var _38e=has("ie")?"styleFloat":"cssFloat",_38f={"cssFloat":_38e,"styleFloat":_38e,"float":_38e};_37d.get=function getStyle(node,name){var n=dom.byId(node),l=arguments.length,op=(name=="opacity");if(l==2&&op){return _385(n);}name=_38f[name]||name;var s=_37d.getComputedStyle(n);return (l==1)?s:_38c(n,name,s[name]||n.style[name]);};_37d.set=function setStyle(node,name,_390){var n=dom.byId(node),l=arguments.length,op=(name=="opacity");name=_38f[name]||name;if(l==3){return op?_386(n,_390):n.style[name]=_390;}for(var x in name){_37d.set(node,x,name[x]);}return _37d.getComputedStyle(n);};return _37d;});},"dojo/dom-geometry":function(){define(["./sniff","./_base/window","./dom","./dom-style"],function(has,win,dom,_391){var geom={};geom.boxModel="content-box";if(has("ie")){geom.boxModel=document.compatMode=="BackCompat"?"border-box":"content-box";}geom.getPadExtents=function getPadExtents(node,_392){node=dom.byId(node);var s=_392||_391.getComputedStyle(node),px=_391.toPixelValue,l=px(node,s.paddingLeft),t=px(node,s.paddingTop),r=px(node,s.paddingRight),b=px(node,s.paddingBottom);return {l:l,t:t,r:r,b:b,w:l+r,h:t+b};};var none="none";geom.getBorderExtents=function getBorderExtents(node,_393){node=dom.byId(node);var px=_391.toPixelValue,s=_393||_391.getComputedStyle(node),l=s.borderLeftStyle!=none?px(node,s.borderLeftWidth):0,t=s.borderTopStyle!=none?px(node,s.borderTopWidth):0,r=s.borderRightStyle!=none?px(node,s.borderRightWidth):0,b=s.borderBottomStyle!=none?px(node,s.borderBottomWidth):0;return {l:l,t:t,r:r,b:b,w:l+r,h:t+b};};geom.getPadBorderExtents=function getPadBorderExtents(node,_394){node=dom.byId(node);var s=_394||_391.getComputedStyle(node),p=geom.getPadExtents(node,s),b=geom.getBorderExtents(node,s);return {l:p.l+b.l,t:p.t+b.t,r:p.r+b.r,b:p.b+b.b,w:p.w+b.w,h:p.h+b.h};};geom.getMarginExtents=function getMarginExtents(node,_395){node=dom.byId(node);var s=_395||_391.getComputedStyle(node),px=_391.toPixelValue,l=px(node,s.marginLeft),t=px(node,s.marginTop),r=px(node,s.marginRight),b=px(node,s.marginBottom);return {l:l,t:t,r:r,b:b,w:l+r,h:t+b};};geom.getMarginBox=function getMarginBox(node,_396){node=dom.byId(node);var s=_396||_391.getComputedStyle(node),me=geom.getMarginExtents(node,s),l=node.offsetLeft-me.l,t=node.offsetTop-me.t,p=node.parentNode,px=_391.toPixelValue,pcs;if(has("mozilla")){var sl=parseFloat(s.left),st=parseFloat(s.top);if(!isNaN(sl)&&!isNaN(st)){l=sl;t=st;}else{if(p&&p.style){pcs=_391.getComputedStyle(p);if(pcs.overflow!="visible"){l+=pcs.borderLeftStyle!=none?px(node,pcs.borderLeftWidth):0;t+=pcs.borderTopStyle!=none?px(node,pcs.borderTopWidth):0;}}}}else{if(has("opera")||(has("ie")==8&&!has("quirks"))){if(p){pcs=_391.getComputedStyle(p);l-=pcs.borderLeftStyle!=none?px(node,pcs.borderLeftWidth):0;t-=pcs.borderTopStyle!=none?px(node,pcs.borderTopWidth):0;}}}return {l:l,t:t,w:node.offsetWidth+me.w,h:node.offsetHeight+me.h};};geom.getContentBox=function getContentBox(node,_397){node=dom.byId(node);var s=_397||_391.getComputedStyle(node),w=node.clientWidth,h,pe=geom.getPadExtents(node,s),be=geom.getBorderExtents(node,s);if(!w){w=node.offsetWidth;h=node.offsetHeight;}else{h=node.clientHeight;be.w=be.h=0;}if(has("opera")){pe.l+=be.l;pe.t+=be.t;}return {l:pe.l,t:pe.t,w:w-pe.w-be.w,h:h-pe.h-be.h};};function _398(node,l,t,w,h,u){u=u||"px";var s=node.style;if(!isNaN(l)){s.left=l+u;}if(!isNaN(t)){s.top=t+u;}if(w>=0){s.width=w+u;}if(h>=0){s.height=h+u;}};function _399(node){return node.tagName.toLowerCase()=="button"||node.tagName.toLowerCase()=="input"&&(node.getAttribute("type")||"").toLowerCase()=="button";};function _39a(node){return geom.boxModel=="border-box"||node.tagName.toLowerCase()=="table"||_399(node);};geom.setContentSize=function setContentSize(node,box,_39b){node=dom.byId(node);var w=box.w,h=box.h;if(_39a(node)){var pb=geom.getPadBorderExtents(node,_39b);if(w>=0){w+=pb.w;}if(h>=0){h+=pb.h;}}_398(node,NaN,NaN,w,h);};var _39c={l:0,t:0,w:0,h:0};geom.setMarginBox=function setMarginBox(node,box,_39d){node=dom.byId(node);var s=_39d||_391.getComputedStyle(node),w=box.w,h=box.h,pb=_39a(node)?_39c:geom.getPadBorderExtents(node,s),mb=geom.getMarginExtents(node,s);if(has("webkit")){if(_399(node)){var ns=node.style;if(w>=0&&!ns.width){ns.width="4px";}if(h>=0&&!ns.height){ns.height="4px";}}}if(w>=0){w=Math.max(w-pb.w-mb.w,0);}if(h>=0){h=Math.max(h-pb.h-mb.h,0);}_398(node,box.l,box.t,w,h);};geom.isBodyLtr=function isBodyLtr(doc){doc=doc||win.doc;return (win.body(doc).dir||doc.documentElement.dir||"ltr").toLowerCase()=="ltr";};geom.docScroll=function docScroll(doc){doc=doc||win.doc;var node=win.doc.parentWindow||win.doc.defaultView;return "pageXOffset" in node?{x:node.pageXOffset,y:node.pageYOffset}:(node=has("quirks")?win.body(doc):doc.documentElement)&&{x:geom.fixIeBiDiScrollLeft(node.scrollLeft||0,doc),y:node.scrollTop||0};};if(has("ie")){geom.getIeDocumentElementOffset=function getIeDocumentElementOffset(doc){doc=doc||win.doc;var de=doc.documentElement;if(has("ie")<8){var r=de.getBoundingClientRect(),l=r.left,t=r.top;if(has("ie")<7){l+=de.clientLeft;t+=de.clientTop;}return {x:l<0?0:l,y:t<0?0:t};}else{return {x:0,y:0};}};}geom.fixIeBiDiScrollLeft=function fixIeBiDiScrollLeft(_39e,doc){doc=doc||win.doc;var ie=has("ie");if(ie&&!geom.isBodyLtr(doc)){var qk=has("quirks"),de=qk?win.body(doc):doc.documentElement,pwin=win.global;if(ie==6&&!qk&&pwin.frameElement&&de.scrollHeight>de.clientHeight){_39e+=de.clientLeft;}return (ie<8||qk)?(_39e+de.clientWidth-de.scrollWidth):-_39e;}return _39e;};geom.position=function(node,_39f){node=dom.byId(node);var db=win.body(node.ownerDocument),ret=node.getBoundingClientRect();ret={x:ret.left,y:ret.top,w:ret.right-ret.left,h:ret.bottom-ret.top};if(has("ie")){var _3a0=geom.getIeDocumentElementOffset(node.ownerDocument);ret.x-=_3a0.x+(has("quirks")?db.clientLeft+db.offsetLeft:0);ret.y-=_3a0.y+(has("quirks")?db.clientTop+db.offsetTop:0);}if(_39f){var _3a1=geom.docScroll(node.ownerDocument);ret.x+=_3a1.x;ret.y+=_3a1.y;}return ret;};geom.getMarginSize=function getMarginSize(node,_3a2){node=dom.byId(node);var me=geom.getMarginExtents(node,_3a2||_391.getComputedStyle(node));var size=node.getBoundingClientRect();return {w:(size.right-size.left)+me.w,h:(size.bottom-size.top)+me.h};};geom.normalizeEvent=function(_3a3){if(!("layerX" in _3a3)){_3a3.layerX=_3a3.offsetX;_3a3.layerY=_3a3.offsetY;}if(!has("dom-addeventlistener")){var se=_3a3.target;var doc=(se&&se.ownerDocument)||document;var _3a4=has("quirks")?doc.body:doc.documentElement;var _3a5=geom.getIeDocumentElementOffset(doc);_3a3.pageX=_3a3.clientX+geom.fixIeBiDiScrollLeft(_3a4.scrollLeft||0,doc)-_3a5.x;_3a3.pageY=_3a3.clientY+(_3a4.scrollTop||0)-_3a5.y;}};return geom;});},"dojo/dom-prop":function(){define(["exports","./_base/kernel","./sniff","./_base/lang","./dom","./dom-style","./dom-construct","./_base/connect"],function(_3a6,dojo,has,lang,dom,_3a7,ctr,conn){var _3a8={},_3a9=0,_3aa=dojo._scopeName+"attrid";_3a6.names={"class":"className","for":"htmlFor",tabindex:"tabIndex",readonly:"readOnly",colspan:"colSpan",frameborder:"frameBorder",rowspan:"rowSpan",valuetype:"valueType"};_3a6.get=function getProp(node,name){node=dom.byId(node);var lc=name.toLowerCase(),_3ab=_3a6.names[lc]||name;return node[_3ab];};_3a6.set=function setProp(node,name,_3ac){node=dom.byId(node);var l=arguments.length;if(l==2&&typeof name!="string"){for(var x in name){_3a6.set(node,x,name[x]);}return node;}var lc=name.toLowerCase(),_3ad=_3a6.names[lc]||name;if(_3ad=="style"&&typeof _3ac!="string"){_3a7.set(node,_3ac);return node;}if(_3ad=="innerHTML"){if(has("ie")&&node.tagName.toLowerCase() in {col:1,colgroup:1,table:1,tbody:1,tfoot:1,thead:1,tr:1,title:1}){ctr.empty(node);node.appendChild(ctr.toDom(_3ac,node.ownerDocument));}else{node[_3ad]=_3ac;}return node;}if(lang.isFunction(_3ac)){var _3ae=node[_3aa];if(!_3ae){_3ae=_3a9++;node[_3aa]=_3ae;}if(!_3a8[_3ae]){_3a8[_3ae]={};}var h=_3a8[_3ae][_3ad];if(h){conn.disconnect(h);}else{try{delete node[_3ad];}catch(e){}}if(_3ac){_3a8[_3ae][_3ad]=conn.connect(node,_3ad,_3ac);}else{node[_3ad]=null;}return node;}node[_3ad]=_3ac;return node;};});},"dojo/when":function(){define(["./Deferred","./promise/Promise"],function(_3af,_3b0){"use strict";return function when(_3b1,_3b2,_3b3,_3b4){var _3b5=_3b1&&typeof _3b1.then==="function";var _3b6=_3b5&&_3b1 instanceof _3b0;if(!_3b5){if(_3b2){return _3b2(_3b1);}else{return new _3af().resolve(_3b1);}}else{if(!_3b6){var _3b7=new _3af(_3b1.cancel);_3b1.then(_3b7.resolve,_3b7.reject,_3b7.progress);_3b1=_3b7.promise;}}if(_3b2||_3b3||_3b4){return _3b1.then(_3b2,_3b3,_3b4);}return _3b1;};});},"dojo/dom-attr":function(){define(["exports","./sniff","./_base/lang","./dom","./dom-style","./dom-prop"],function(_3b8,has,lang,dom,_3b9,prop){var _3ba={innerHTML:1,className:1,htmlFor:has("ie"),value:1},_3bb={classname:"class",htmlfor:"for",tabindex:"tabIndex",readonly:"readOnly"};function _3bc(node,name){var attr=node.getAttributeNode&&node.getAttributeNode(name);return attr&&attr.specified;};_3b8.has=function hasAttr(node,name){var lc=name.toLowerCase();return _3ba[prop.names[lc]||name]||_3bc(dom.byId(node),_3bb[lc]||name);};_3b8.get=function getAttr(node,name){node=dom.byId(node);var lc=name.toLowerCase(),_3bd=prop.names[lc]||name,_3be=_3ba[_3bd],_3bf=node[_3bd];if(_3be&&typeof _3bf!="undefined"){return _3bf;}if(_3bd!="href"&&(typeof _3bf=="boolean"||lang.isFunction(_3bf))){return _3bf;}var _3c0=_3bb[lc]||name;return _3bc(node,_3c0)?node.getAttribute(_3c0):null;};_3b8.set=function setAttr(node,name,_3c1){node=dom.byId(node);if(arguments.length==2){for(var x in name){_3b8.set(node,x,name[x]);}return node;}var lc=name.toLowerCase(),_3c2=prop.names[lc]||name,_3c3=_3ba[_3c2];if(_3c2=="style"&&typeof _3c1!="string"){_3b9.set(node,_3c1);return node;}if(_3c3||typeof _3c1=="boolean"||lang.isFunction(_3c1)){return prop.set(node,name,_3c1);}node.setAttribute(_3bb[lc]||name,_3c1);return node;};_3b8.remove=function removeAttr(node,name){dom.byId(node).removeAttribute(_3bb[name.toLowerCase()]||name);};_3b8.getNodeProp=function getNodeProp(node,name){node=dom.byId(node);var lc=name.toLowerCase(),_3c4=prop.names[lc]||name;if((_3c4 in node)&&_3c4!="href"){return node[_3c4];}var _3c5=_3bb[lc]||name;return _3bc(node,_3c5)?node.getAttribute(_3c5):null;};});},"dojo/dom-construct":function(){define(["exports","./_base/kernel","./sniff","./_base/window","./dom","./dom-attr","./on"],function(_3c6,dojo,has,win,dom,attr,on){var _3c7={option:["select"],tbody:["table"],thead:["table"],tfoot:["table"],tr:["table","tbody"],td:["table","tbody","tr"],th:["table","thead","tr"],legend:["fieldset"],caption:["table"],colgroup:["table"],col:["table","colgroup"],li:["ul"]},_3c8=/<\s*([\w\:]+)/,_3c9={},_3ca=0,_3cb="__"+dojo._scopeName+"ToDomId";for(var _3cc in _3c7){if(_3c7.hasOwnProperty(_3cc)){var tw=_3c7[_3cc];tw.pre=_3cc=="option"?"":"<"+tw.join("><")+">";tw.post="";}}function _3cd(node,ref){var _3ce=ref.parentNode;if(_3ce){_3ce.insertBefore(node,ref);}};function _3cf(node,ref){var _3d0=ref.parentNode;if(_3d0){if(_3d0.lastChild==ref){_3d0.appendChild(node);}else{_3d0.insertBefore(node,ref.nextSibling);}}};var _3d1=null,_3d2;on(window,"unload",function(){_3d1=null;});_3c6.toDom=function toDom(frag,doc){doc=doc||win.doc;var _3d3=doc[_3cb];if(!_3d3){doc[_3cb]=_3d3=++_3ca+"";_3c9[_3d3]=doc.createElement("div");}frag+="";var _3d4=frag.match(_3c8),tag=_3d4?_3d4[1].toLowerCase():"",_3d5=_3c9[_3d3],wrap,i,fc,df;if(_3d4&&_3c7[tag]){wrap=_3c7[tag];_3d5.innerHTML=wrap.pre+frag+wrap.post;for(i=wrap.length;i;--i){_3d5=_3d5.firstChild;}}else{_3d5.innerHTML=frag;}if(_3d5.childNodes.length==1){return _3d5.removeChild(_3d5.firstChild);}df=doc.createDocumentFragment();while(fc=_3d5.firstChild){df.appendChild(fc);}return df;};_3c6.place=function place(node,_3d6,_3d7){_3d6=dom.byId(_3d6);if(typeof node=="string"){node=/^\s*/im,"");var _402=text.match(/]*>\s*([\s\S]+)\s*<\/body>/im);if(_402){text=_402[1];}}else{text="";}return text;},_403={},_404={};dojo.cache=function(_405,url,_406){var key;if(typeof _405=="string"){if(/\//.test(_405)){key=_405;_406=url;}else{key=_3fe.toUrl(_405.replace(/\./g,"/")+(url?("/"+url):""));}}else{key=_405+"";_406=url;}var val=(_406!=undefined&&typeof _406!="string")?_406.value:_406,_407=_406&&_406.sanitize;if(typeof val=="string"){_400[key]=val;return _407?_401(val):val;}else{if(val===null){delete _400[key];return null;}else{if(!(key in _400)){_3ff(key,true,function(text){_400[key]=text;});}return _407?_401(_400[key]):_400[key];}}};return {dynamic:true,normalize:function(id,_408){var _409=id.split("!"),url=_409[0];return (/^\./.test(url)?_408(url):url)+(_409[1]?"!"+_409[1]:"");},load:function(id,_40a,load){var _40b=id.split("!"),_40c=_40b.length>1,_40d=_40b[0],url=_40a.toUrl(_40b[0]),_40e="url:"+url,text=_403,_40f=function(text){load(_40c?_401(text):text);};if(_40d in _400){text=_400[_40d];}else{if(_40e in _40a.cache){text=_40a.cache[_40e];}else{if(url in _400){text=_400[url];}}}if(text===_403){if(_404[url]){_404[url].push(_40f);}else{var _410=_404[url]=[_40f];_3ff(url,!_40a.async,function(text){_400[_40d]=_400[url]=text;for(var i=0;i<_410.length;){_410[i++](text);}delete _404[url];});}}else{_40f(text);}}};});},"dojo/keys":function(){define("dojo/keys",["./_base/kernel","./sniff"],function(dojo,has){return dojo.keys={BACKSPACE:8,TAB:9,CLEAR:12,ENTER:13,SHIFT:16,CTRL:17,ALT:18,META:has("webkit")?91:224,PAUSE:19,CAPS_LOCK:20,ESCAPE:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT_ARROW:37,UP_ARROW:38,RIGHT_ARROW:39,DOWN_ARROW:40,INSERT:45,DELETE:46,HELP:47,LEFT_WINDOW:91,RIGHT_WINDOW:92,SELECT:93,NUMPAD_0:96,NUMPAD_1:97,NUMPAD_2:98,NUMPAD_3:99,NUMPAD_4:100,NUMPAD_5:101,NUMPAD_6:102,NUMPAD_7:103,NUMPAD_8:104,NUMPAD_9:105,NUMPAD_MULTIPLY:106,NUMPAD_PLUS:107,NUMPAD_ENTER:108,NUMPAD_MINUS:109,NUMPAD_PERIOD:110,NUMPAD_DIVIDE:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,NUM_LOCK:144,SCROLL_LOCK:145,UP_DPAD:175,DOWN_DPAD:176,LEFT_DPAD:177,RIGHT_DPAD:178,copyKey:has("mac")&&!has("air")?(has("safari")?91:224):17};});},"dojo/domReady":function(){define(["./has"],function(has){var _411=this,doc=document,_412={"loaded":1,"complete":1},_413=typeof doc.readyState!="string",_414=!!_412[doc.readyState];if(_413){doc.readyState="loading";}if(!_414){var _415=[],_416=[],_417=function(evt){evt=evt||_411.event;if(_414||(evt.type=="readystatechange"&&!_412[doc.readyState])){return;}_414=1;if(_413){doc.readyState="complete";}while(_415.length){(_415.shift())(doc);}},on=function(node,_418){node.addEventListener(_418,_417,false);_415.push(function(){node.removeEventListener(_418,_417,false);});};if(!has("dom-addeventlistener")){on=function(node,_419){_419="on"+_419;node.attachEvent(_419,_417);_415.push(function(){node.detachEvent(_419,_417);});};var div=doc.createElement("div");try{if(div.doScroll&&_411.frameElement===null){_416.push(function(){try{div.doScroll("left");return 1;}catch(e){}});}}catch(e){}}on(doc,"DOMContentLoaded");on(_411,"load");if("onreadystatechange" in doc){on(doc,"readystatechange");}else{if(!_413){_416.push(function(){return _412[doc.readyState];});}}if(_416.length){var _41a=function(){if(_414){return;}var i=_416.length;while(i--){if(_416[i]()){_417("poller");return;}}setTimeout(_41a,30);};_41a();}}function _41b(_41c){if(_414){_41c(doc);}else{_415.push(_41c);}};_41b.load=function(id,req,load){_41b(load);};return _41b;});},"dojo/_base/lang":function(){define("dojo/_base/lang",["./kernel","../has","../sniff"],function(dojo,has){has.add("bug-for-in-skips-shadowed",function(){for(var i in {toString:1}){return 0;}return 1;});var _41d=has("bug-for-in-skips-shadowed")?"hasOwnProperty.valueOf.isPrototypeOf.propertyIsEnumerable.toLocaleString.toString.constructor".split("."):[],_41e=_41d.length,_41f=function(_420,_421,_422){var p,i=0,_423=dojo.global;if(!_422){if(!_420.length){return _423;}else{p=_420[i++];try{_422=dojo.scopeMap[p]&&dojo.scopeMap[p][1];}catch(e){}_422=_422||(p in _423?_423[p]:(_421?_423[p]={}:undefined));}}while(_422&&(p=_420[i++])){_422=(p in _422?_422[p]:(_421?_422[p]={}:undefined));}return _422;},opts=Object.prototype.toString,_424=function(obj,_425,_426){return (_426||[]).concat(Array.prototype.slice.call(obj,_425||0));},_427=/\{([^\}]+)\}/g;var lang={_extraNames:_41d,_mixin:function(dest,_428,_429){var name,s,i,_42a={};for(name in _428){s=_428[name];if(!(name in dest)||(dest[name]!==s&&(!(name in _42a)||_42a[name]!==s))){dest[name]=_429?_429(s):s;}}if(has("bug-for-in-skips-shadowed")){if(_428){for(i=0;i<_41e;++i){name=_41d[i];s=_428[name];if(!(name in dest)||(dest[name]!==s&&(!(name in _42a)||_42a[name]!==s))){dest[name]=_429?_429(s):s;}}}}return dest;},mixin:function(dest,_42b){if(!dest){dest={};}for(var i=1,l=arguments.length;i2){return lang._hitchArgs.apply(dojo,arguments);}if(!_436){_436=_435;_435=null;}if(lang.isString(_436)){_435=_435||dojo.global;if(!_435[_436]){throw (["lang.hitch: scope[\"",_436,"\"] is null (scope=\"",_435,"\")"].join(""));}return function(){return _435[_436].apply(_435,arguments||[]);};}return !_435?_436:function(){return _436.apply(_435,arguments||[]);};},delegate:(function(){function TMP(){};return function(obj,_437){TMP.prototype=obj;var tmp=new TMP();TMP.prototype=null;if(_437){lang._mixin(tmp,_437);}return tmp;};})(),_toArray:has("ie")?(function(){function slow(obj,_438,_439){var arr=_439||[];for(var x=_438||0;x=200&&stat<300)||stat===304||stat===1223||!stat;};});},"dojo/Evented":function(){define("dojo/Evented",["./aspect","./on"],function(_462,on){"use strict";var _463=_462.after;function _464(){};_464.prototype={on:function(type,_465){return on.parse(this,type,_465,function(_466,type){return _463(_466,"on"+type,_465,true);});},emit:function(type,_467){var args=[this];args.push.apply(args,arguments);return on.emit.apply(on,args);}};return _464;});},"dojo/mouse":function(){define("dojo/mouse",["./_base/kernel","./on","./has","./dom","./_base/window"],function(dojo,on,has,dom,win){has.add("dom-quirks",win.doc&&win.doc.compatMode=="BackCompat");has.add("events-mouseenter",win.doc&&"onmouseenter" in win.doc.createElement("div"));has.add("events-mousewheel",win.doc&&"onmousewheel" in win.doc);var _468;if((has("dom-quirks")&&has("ie"))||!has("dom-addeventlistener")){_468={LEFT:1,MIDDLE:4,RIGHT:2,isButton:function(e,_469){return e.button&_469;},isLeft:function(e){return e.button&1;},isMiddle:function(e){return e.button&4;},isRight:function(e){return e.button&2;}};}else{_468={LEFT:0,MIDDLE:1,RIGHT:2,isButton:function(e,_46a){return e.button==_46a;},isLeft:function(e){return e.button==0;},isMiddle:function(e){return e.button==1;},isRight:function(e){return e.button==2;}};}dojo.mouseButtons=_468;function _46b(type,_46c){var _46d=function(node,_46e){return on(node,type,function(evt){if(_46c){return _46c(evt,_46e);}if(!dom.isDescendant(evt.relatedTarget,node)){return _46e.call(this,evt);}});};_46d.bubble=function(_46f){return _46b(type,function(evt,_470){var _471=_46f(evt.target);var _472=evt.relatedTarget;if(_471&&(_471!=(_472&&_472.nodeType==1&&_46f(_472)))){return _470.call(_471,evt);}});};return _46d;};var _473;if(has("events-mousewheel")){_473="mousewheel";}else{_473=function(node,_474){return on(node,"DOMMouseScroll",function(evt){evt.wheelDelta=-evt.detail;_474.call(this,evt);});};}return {_eventHandler:_46b,enter:_46b("mouseover"),leave:_46b("mouseout"),wheel:_473,isLeft:_468.isLeft,isMiddle:_468.isMiddle,isRight:_468.isRight};});},"dojo/topic":function(){define("dojo/topic",["./Evented"],function(_475){var hub=new _475;return {publish:function(_476,_477){return hub.emit.apply(hub,arguments);},subscribe:function(_478,_479){return hub.on.apply(hub,arguments);}};});},"dojo/_base/xhr":function(){define("dojo/_base/xhr",["./kernel","./sniff","require","../io-query","../dom","../dom-form","./Deferred","./config","./json","./lang","./array","../on","../aspect","../request/watch","../request/xhr","../request/util"],function(dojo,has,_47a,ioq,dom,_47b,_47c,_47d,json,lang,_47e,on,_47f,_480,_481,util){dojo._xhrObj=_481._create;var cfg=dojo.config;dojo.objectToQuery=ioq.objectToQuery;dojo.queryToObject=ioq.queryToObject;dojo.fieldToObject=_47b.fieldToObject;dojo.formToObject=_47b.toObject;dojo.formToQuery=_47b.toQuery;dojo.formToJson=_47b.toJson;dojo._blockAsync=false;var _482=dojo._contentHandlers=dojo.contentHandlers={"text":function(xhr){return xhr.responseText;},"json":function(xhr){return json.fromJson(xhr.responseText||null);},"json-comment-filtered":function(xhr){if(!_47d.useCommentedJson){console.warn("Consider using the standard mimetype:application/json."+" json-commenting can introduce security issues. To"+" decrease the chances of hijacking, use the standard the 'json' handler and"+" prefix your json with: {}&&\n"+"Use djConfig.useCommentedJson=true to turn off this message.");}var _483=xhr.responseText;var _484=_483.indexOf("/*");var _485=_483.lastIndexOf("*/");if(_484==-1||_485==-1){throw new Error("JSON was not comment filtered");}return json.fromJson(_483.substring(_484+2,_485));},"javascript":function(xhr){return dojo.eval(xhr.responseText);},"xml":function(xhr){var _486=xhr.responseXML;if(has("ie")){if((!_486||!_486.documentElement)){var ms=function(n){return "MSXML"+n+".DOMDocument";};var dp=["Microsoft.XMLDOM",ms(6),ms(4),ms(3),ms(2)];_47e.some(dp,function(p){try{var dom=new ActiveXObject(p);dom.async=false;dom.loadXML(xhr.responseText);_486=dom;}catch(e){return false;}return true;});}}return _486;},"json-comment-optional":function(xhr){if(xhr.responseText&&/^[^{\[]*\/\*/.test(xhr.responseText)){return _482["json-comment-filtered"](xhr);}else{return _482["json"](xhr);}}};dojo._ioSetArgs=function(args,_487,_488,_489){var _48a={args:args,url:args.url};var _48b=null;if(args.form){var form=dom.byId(args.form);var _48c=form.getAttributeNode("action");_48a.url=_48a.url||(_48c?_48c.value:null);_48b=_47b.toObject(form);}var _48d=[{}];if(_48b){_48d.push(_48b);}if(args.content){_48d.push(args.content);}if(args.preventCache){_48d.push({"dojo.preventCache":new Date().valueOf()});}_48a.query=ioq.objectToQuery(lang.mixin.apply(null,_48d));_48a.handleAs=args.handleAs||"text";var d=new _47c(function(dfd){dfd.canceled=true;_487&&_487(dfd);var err=dfd.ioArgs.error;if(!err){err=new Error("request cancelled");err.dojoType="cancel";dfd.ioArgs.error=err;}return err;});d.addCallback(_488);var ld=args.load;if(ld&&lang.isFunction(ld)){d.addCallback(function(_48e){return ld.call(args,_48e,_48a);});}var err=args.error;if(err&&lang.isFunction(err)){d.addErrback(function(_48f){return err.call(args,_48f,_48a);});}var _490=args.handle;if(_490&&lang.isFunction(_490)){d.addBoth(function(_491){return _490.call(args,_491,_48a);});}d.addErrback(function(_492){return _489(_492,d);});if(cfg.ioPublish&&dojo.publish&&_48a.args.ioPublish!==false){d.addCallbacks(function(res){dojo.publish("/dojo/io/load",[d,res]);return res;},function(res){dojo.publish("/dojo/io/error",[d,res]);return res;});d.addBoth(function(res){dojo.publish("/dojo/io/done",[d,res]);return res;});}d.ioArgs=_48a;return d;};var _493=function(dfd){var ret=_482[dfd.ioArgs.handleAs](dfd.ioArgs.xhr);return ret===undefined?null:ret;};var _494=function(_495,dfd){if(!dfd.ioArgs.args.failOk){console.error(_495);}return _495;};var _496=function(dfd){if(_497<=0){_497=0;if(cfg.ioPublish&&dojo.publish&&(!dfd||dfd&&dfd.ioArgs.args.ioPublish!==false)){dojo.publish("/dojo/io/stop");}}};var _497=0;_47f.after(_480,"_onAction",function(){_497-=1;});_47f.after(_480,"_onInFlight",_496);dojo._ioCancelAll=_480.cancelAll;dojo._ioNotifyStart=function(dfd){if(cfg.ioPublish&&dojo.publish&&dfd.ioArgs.args.ioPublish!==false){if(!_497){dojo.publish("/dojo/io/start");}_497+=1;dojo.publish("/dojo/io/send",[dfd]);}};dojo._ioWatch=function(dfd,_498,_499,_49a){var args=dfd.ioArgs.options=dfd.ioArgs.args;lang.mixin(dfd,{response:dfd.ioArgs,isValid:function(_49b){return _498(dfd);},isReady:function(_49c){return _499(dfd);},handleResponse:function(_49d){return _49a(dfd);}});_480(dfd);_496(dfd);};var _49e="application/x-www-form-urlencoded";dojo._ioAddQueryToUrl=function(_49f){if(_49f.query.length){_49f.url+=(_49f.url.indexOf("?")==-1?"?":"&")+_49f.query;_49f.query=null;}};dojo.xhr=function(_4a0,args,_4a1){var rDfd;var dfd=dojo._ioSetArgs(args,function(dfd){rDfd&&rDfd.cancel();},_493,_494);var _4a2=dfd.ioArgs;if("postData" in args){_4a2.query=args.postData;}else{if("putData" in args){_4a2.query=args.putData;}else{if("rawBody" in args){_4a2.query=args.rawBody;}else{if((arguments.length>2&&!_4a1)||"POST|PUT".indexOf(_4a0.toUpperCase())===-1){dojo._ioAddQueryToUrl(_4a2);}}}}var _4a3={method:_4a0,handleAs:"text",timeout:args.timeout,withCredentials:args.withCredentials,ioArgs:_4a2};if(typeof args.headers!=="undefined"){_4a3.headers=args.headers;}if(typeof args.contentType!=="undefined"){if(!_4a3.headers){_4a3.headers={};}_4a3.headers["Content-Type"]=args.contentType;}if(typeof _4a2.query!=="undefined"){_4a3.data=_4a2.query;}if(typeof args.sync!=="undefined"){_4a3.sync=args.sync;}dojo._ioNotifyStart(dfd);try{rDfd=_481(_4a2.url,_4a3,true);}catch(e){dfd.cancel();return dfd;}dfd.ioArgs.xhr=rDfd.response.xhr;rDfd.then(function(){dfd.resolve(dfd);}).otherwise(function(_4a4){_4a2.error=_4a4;if(_4a4.response){_4a4.status=_4a4.response.status;_4a4.responseText=_4a4.response.text;_4a4.xhr=_4a4.response.xhr;}dfd.reject(_4a4);});return dfd;};dojo.xhrGet=function(args){return dojo.xhr("GET",args);};dojo.rawXhrPost=dojo.xhrPost=function(args){return dojo.xhr("POST",args,true);};dojo.rawXhrPut=dojo.xhrPut=function(args){return dojo.xhr("PUT",args,true);};dojo.xhrDelete=function(args){return dojo.xhr("DELETE",args);};dojo._isDocumentOk=function(x){return util.checkStatus(x.status);};dojo._getText=function(url){var _4a5;dojo.xhrGet({url:url,sync:true,load:function(text){_4a5=text;}});return _4a5;};lang.mixin(dojo.xhr,{_xhrObj:dojo._xhrObj,fieldToObject:_47b.fieldToObject,formToObject:_47b.toObject,objectToQuery:ioq.objectToQuery,formToQuery:_47b.toQuery,formToJson:_47b.toJson,queryToObject:ioq.queryToObject,contentHandlers:_482,_ioSetArgs:dojo._ioSetArgs,_ioCancelAll:dojo._ioCancelAll,_ioNotifyStart:dojo._ioNotifyStart,_ioWatch:dojo._ioWatch,_ioAddQueryToUrl:dojo._ioAddQueryToUrl,_isDocumentOk:dojo._isDocumentOk,_getText:dojo._getText,get:dojo.xhrGet,post:dojo.xhrPost,put:dojo.xhrPut,del:dojo.xhrDelete});return dojo.xhr;});},"dojo/loadInit":function(){define("dojo/loadInit",["./_base/loader"],function(_4a6){return {dynamic:0,normalize:function(id){return id;},load:_4a6.loadInit};});},"dojo/_base/unload":function(){define(["./kernel","./lang","../on"],function(dojo,lang,on){var win=window;var _4a7={addOnWindowUnload:function(obj,_4a8){if(!dojo.windowUnloaded){on(win,"unload",(dojo.windowUnloaded=function(){}));}on(win,"unload",lang.hitch(obj,_4a8));},addOnUnload:function(obj,_4a9){on(win,"beforeunload",lang.hitch(obj,_4a9));}};dojo.addOnWindowUnload=_4a7.addOnWindowUnload;dojo.addOnUnload=_4a7.addOnUnload;return _4a7;});},"dojo/Deferred":function(){define(["./has","./_base/lang","./errors/CancelError","./promise/Promise","./promise/instrumentation"],function(has,lang,_4aa,_4ab,_4ac){"use strict";var _4ad=0,_4ae=1,_4af=2;var _4b0="This deferred has already been fulfilled.";var _4b1=Object.freeze||function(){};var _4b2=function(_4b3,type,_4b4,_4b5,_4b6){if(1){if(type===_4af&&_4b7.instrumentRejected&&_4b3.length===0){_4b7.instrumentRejected(_4b4,false,_4b5,_4b6);}}for(var i=0;i<_4b3.length;i++){_4b8(_4b3[i],type,_4b4,_4b5);}};var _4b8=function(_4b9,type,_4ba,_4bb){var func=_4b9[type];var _4bc=_4b9.deferred;if(func){try{var _4bd=func(_4ba);if(type===_4ad){if(typeof _4bd!=="undefined"){_4be(_4bc,type,_4bd);}}else{if(_4bd&&typeof _4bd.then==="function"){_4b9.cancel=_4bd.cancel;_4bd.then(_4bf(_4bc,_4ae),_4bf(_4bc,_4af),_4bf(_4bc,_4ad));return;}_4be(_4bc,_4ae,_4bd);}}catch(error){_4be(_4bc,_4af,error);}}else{_4be(_4bc,type,_4ba);}if(1){if(type===_4af&&_4b7.instrumentRejected){_4b7.instrumentRejected(_4ba,!!func,_4bb,_4bc.promise);}}};var _4bf=function(_4c0,type){return function(_4c1){_4be(_4c0,type,_4c1);};};var _4be=function(_4c2,type,_4c3){if(!_4c2.isCanceled()){switch(type){case _4ad:_4c2.progress(_4c3);break;case _4ae:_4c2.resolve(_4c3);break;case _4af:_4c2.reject(_4c3);break;}}};var _4b7=function(_4c4){var _4c5=this.promise=new _4ab();var _4c6=this;var _4c7,_4c8,_4c9;var _4ca=false;var _4cb=[];if(1&&Error.captureStackTrace){Error.captureStackTrace(_4c6,_4b7);Error.captureStackTrace(_4c5,_4b7);}this.isResolved=_4c5.isResolved=function(){return _4c7===_4ae;};this.isRejected=_4c5.isRejected=function(){return _4c7===_4af;};this.isFulfilled=_4c5.isFulfilled=function(){return !!_4c7;};this.isCanceled=_4c5.isCanceled=function(){return _4ca;};this.progress=function(_4cc,_4cd){if(!_4c7){_4b2(_4cb,_4ad,_4cc,null,_4c6);return _4c5;}else{if(_4cd===true){throw new Error(_4b0);}else{return _4c5;}}};this.resolve=function(_4ce,_4cf){if(!_4c7){_4b2(_4cb,_4c7=_4ae,_4c8=_4ce,null,_4c6);_4cb=null;return _4c5;}else{if(_4cf===true){throw new Error(_4b0);}else{return _4c5;}}};var _4d0=this.reject=function(_4d1,_4d2){if(!_4c7){if(1&&Error.captureStackTrace){Error.captureStackTrace(_4c9={},_4d0);}_4b2(_4cb,_4c7=_4af,_4c8=_4d1,_4c9,_4c6);_4cb=null;return _4c5;}else{if(_4d2===true){throw new Error(_4b0);}else{return _4c5;}}};this.then=_4c5.then=function(_4d3,_4d4,_4d5){var _4d6=[_4d5,_4d3,_4d4];_4d6.cancel=_4c5.cancel;_4d6.deferred=new _4b7(function(_4d7){return _4d6.cancel&&_4d6.cancel(_4d7);});if(_4c7&&!_4cb){_4b8(_4d6,_4c7,_4c8,_4c9);}else{_4cb.push(_4d6);}return _4d6.deferred.promise;};this.cancel=_4c5.cancel=function(_4d8,_4d9){if(!_4c7){if(_4c4){var _4da=_4c4(_4d8);_4d8=typeof _4da==="undefined"?_4d8:_4da;}_4ca=true;if(!_4c7){if(typeof _4d8==="undefined"){_4d8=new _4aa();}_4d0(_4d8);return _4d8;}else{if(_4c7===_4af&&_4c8===_4d8){return _4d8;}}}else{if(_4d9===true){throw new Error(_4b0);}}};_4b1(_4c5);};_4b7.prototype.toString=function(){return "[object Deferred]";};if(_4ac){_4ac(_4b7);}return _4b7;});},"dojo/_base/NodeList":function(){define("dojo/_base/NodeList",["./kernel","../query","./array","./html","../NodeList-dom"],function(dojo,_4db,_4dc){var _4dd=_4db.NodeList,nlp=_4dd.prototype;nlp.connect=_4dd._adaptAsForEach(function(){return dojo.connect.apply(this,arguments);});nlp.coords=_4dd._adaptAsMap(dojo.coords);_4dd.events=["blur","focus","change","click","error","keydown","keypress","keyup","load","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","submit"];_4dc.forEach(_4dd.events,function(evt){var _4de="on"+evt;nlp[_4de]=function(a,b){return this.connect(_4de,a,b);};});dojo.NodeList=_4dd;return _4dd;});},"dojo/_base/Color":function(){define(["./kernel","./lang","./array","./config"],function(dojo,lang,_4df,_4e0){var _4e1=dojo.Color=function(_4e2){if(_4e2){this.setColor(_4e2);}};_4e1.named={"black":[0,0,0],"silver":[192,192,192],"gray":[128,128,128],"white":[255,255,255],"maroon":[128,0,0],"red":[255,0,0],"purple":[128,0,128],"fuchsia":[255,0,255],"green":[0,128,0],"lime":[0,255,0],"olive":[128,128,0],"yellow":[255,255,0],"navy":[0,0,128],"blue":[0,0,255],"teal":[0,128,128],"aqua":[0,255,255],"transparent":_4e0.transparentColor||[0,0,0,0]};lang.extend(_4e1,{r:255,g:255,b:255,a:1,_set:function(r,g,b,a){var t=this;t.r=r;t.g=g;t.b=b;t.a=a;},setColor:function(_4e3){if(lang.isString(_4e3)){_4e1.fromString(_4e3,this);}else{if(lang.isArray(_4e3)){_4e1.fromArray(_4e3,this);}else{this._set(_4e3.r,_4e3.g,_4e3.b,_4e3.a);if(!(_4e3 instanceof _4e1)){this.sanitize();}}}return this;},sanitize:function(){return this;},toRgb:function(){var t=this;return [t.r,t.g,t.b];},toRgba:function(){var t=this;return [t.r,t.g,t.b,t.a];},toHex:function(){var arr=_4df.map(["r","g","b"],function(x){var s=this[x].toString(16);return s.length<2?"0"+s:s;},this);return "#"+arr.join("");},toCss:function(_4e4){var t=this,rgb=t.r+", "+t.g+", "+t.b;return (_4e4?"rgba("+rgb+", "+t.a:"rgb("+rgb)+")";},toString:function(){return this.toCss(true);}});_4e1.blendColors=dojo.blendColors=function(_4e5,end,_4e6,obj){var t=obj||new _4e1();_4df.forEach(["r","g","b","a"],function(x){t[x]=_4e5[x]+(end[x]-_4e5[x])*_4e6;if(x!="a"){t[x]=Math.round(t[x]);}});return t.sanitize();};_4e1.fromRgb=dojo.colorFromRgb=function(_4e7,obj){var m=_4e7.toLowerCase().match(/^rgba?\(([\s\.,0-9]+)\)/);return m&&_4e1.fromArray(m[1].split(/\s*,\s*/),obj);};_4e1.fromHex=dojo.colorFromHex=function(_4e8,obj){var t=obj||new _4e1(),bits=(_4e8.length==4)?4:8,mask=(1<>=bits;t[x]=bits==4?17*c:c;});t.a=1;return t;};_4e1.fromArray=dojo.colorFromArray=function(a,obj){var t=obj||new _4e1();t._set(Number(a[0]),Number(a[1]),Number(a[2]),Number(a[3]));if(isNaN(t.a)){t.a=1;}return t.sanitize();};_4e1.fromString=dojo.colorFromString=function(str,obj){var a=_4e1.named[str];return a&&_4e1.fromArray(a,obj)||_4e1.fromRgb(str,obj)||_4e1.fromHex(str,obj);};return _4e1;});},"dojo/promise/instrumentation":function(){define(["./tracer","../has","../_base/lang","../_base/array"],function(_4e9,has,lang,_4ea){function _4eb(_4ec,_4ed,_4ee){var _4ef="";if(_4ec&&_4ec.stack){_4ef+=_4ec.stack;}if(_4ed&&_4ed.stack){_4ef+="\n ----------------------------------------\n rejected"+_4ed.stack.split("\n").slice(1).join("\n").replace(/^\s+/," ");}if(_4ee&&_4ee.stack){_4ef+="\n ----------------------------------------\n"+_4ee.stack;}console.error(_4ec,_4ef);};function _4f0(_4f1,_4f2,_4f3,_4f4){if(!_4f2){_4eb(_4f1,_4f3,_4f4);}};var _4f5=[];var _4f6=false;var _4f7=1000;function _4f8(_4f9,_4fa,_4fb,_4fc){if(_4fa){_4ea.some(_4f5,function(obj,ix){if(obj.error===_4f9){_4f5.splice(ix,1);return true;}});}else{if(!_4ea.some(_4f5,function(obj){return obj.error===_4f9;})){_4f5.push({error:_4f9,rejection:_4fb,deferred:_4fc,timestamp:new Date().getTime()});}}if(!_4f6){_4f6=setTimeout(_4fd,_4f7);}};function _4fd(){var now=new Date().getTime();var _4fe=now-_4f7;_4f5=_4ea.filter(_4f5,function(obj){if(obj.timestamp<_4fe){_4eb(obj.error,obj.rejection,obj.deferred);return false;}return true;});if(_4f5.length){_4f6=setTimeout(_4fd,_4f5[0].timestamp+_4f7-now);}};return function(_4ff){var _500=has("config-useDeferredInstrumentation");if(_500){_4e9.on("resolved",lang.hitch(console,"log","resolved"));_4e9.on("rejected",lang.hitch(console,"log","rejected"));_4e9.on("progress",lang.hitch(console,"log","progress"));var args=[];if(typeof _500==="string"){args=_500.split(",");_500=args.shift();}if(_500==="report-rejections"){_4ff.instrumentRejected=_4f0;}else{if(_500==="report-unhandled-rejections"||_500===true||_500===1){_4ff.instrumentRejected=_4f8;_4f7=parseInt(args[0],10)||_4f7;}else{throw new Error("Unsupported instrumentation usage <"+_500+">");}}}};});},"dojo/selector/_loader":function(){define(["../has","require"],function(has,_501){"use strict";var _502=document.createElement("div");has.add("dom-qsa2.1",!!_502.querySelectorAll);has.add("dom-qsa3",function(){try{_502.innerHTML="

";return _502.querySelectorAll(".TEST:empty").length==1;}catch(e){}});var _503;var acme="./acme",lite="./lite";return {load:function(id,_504,_505,_506){var req=_501;id=id=="default"?has("config-selectorEngine")||"css3":id;id=id=="css2"||id=="lite"?lite:id=="css2.1"?has("dom-qsa2.1")?lite:acme:id=="css3"?has("dom-qsa3")?lite:acme:id=="acme"?acme:(req=_504)&&id;if(id.charAt(id.length-1)=="?"){id=id.substring(0,id.length-1);var _507=true;}if(_507&&(has("dom-compliant-qsa")||_503)){return _505(_503);}req([id],function(_508){if(id!="./lite"){_503=_508;}_505(_508);});}};});},"dojo/promise/Promise":function(){define(["../_base/lang"],function(lang){"use strict";function _509(){throw new TypeError("abstract");};return lang.extend(function Promise(){},{then:function(_50a,_50b,_50c){_509();},cancel:function(_50d,_50e){_509();},isResolved:function(){_509();},isRejected:function(){_509();},isFulfilled:function(){_509();},isCanceled:function(){_509();},always:function(_50f){return this.then(_50f,_50f);},otherwise:function(_510){return this.then(null,_510);},trace:function(){return this;},traceRejected:function(){return this;},toString:function(){return "[object Promise]";}});});},"dojo/request/watch":function(){define(["./util","../errors/RequestTimeoutError","../errors/CancelError","../_base/array","../_base/window","../has!host-browser?dom-addeventlistener?:../on:"],function(util,_511,_512,_513,win,on){var _514=null,_515=[];function _516(){var now=+(new Date);for(var i=0,dfd;i<_515.length&&(dfd=_515[i]);i++){var _517=dfd.response,_518=_517.options;if((dfd.isCanceled&&dfd.isCanceled())||(dfd.isValid&&!dfd.isValid(_517))){_515.splice(i--,1);_519._onAction&&_519._onAction();}else{if(dfd.isReady&&dfd.isReady(_517)){_515.splice(i--,1);dfd.handleResponse(_517);_519._onAction&&_519._onAction();}else{if(dfd.startTime){if(dfd.startTime+(_518.timeout||0)-1){var _52e=type.split(/\s*,\s*/);var _52f=[];var i=0;var _530;while(_530=_52e[i++]){_52f.push(_52b(_529,_530,_52a,_52c,_52d));}_52f.remove=function(){for(var i=0;i<_52f.length;i++){_52f[i].remove();}};return _52f;}return _52b(_529,type,_52a,_52c,_52d);};var _531=/^touch/;function _51f(_532,type,_533,_534,_535){var _536=type.match(/(.*):(.*)/);if(_536){type=_536[2];_536=_536[1];return on.selector(_536,type).call(_535,_532,_533);}if(has("touch")){if(_531.test(type)){_533=_537(_533);}if(!has("event-orientationchange")&&(type=="orientationchange")){type="resize";_532=window;_533=_537(_533);}}if(_538){_533=_538(_533);}if(_532.addEventListener){var _539=type in _53a,_53b=_539?_53a[type]:type;_532.addEventListener(_53b,_533,_539);return {remove:function(){_532.removeEventListener(_53b,_533,_539);}};}type="on"+type;if(_53c&&_532.attachEvent){return _53c(_532,type,_533);}throw new Error("Target must be an event emitter");};on.selector=function(_53d,_53e,_53f){return function(_540,_541){var _542=typeof _53d=="function"?{matches:_53d}:this,_543=_53e.bubble;function _544(_545){_542=_542&&_542.matches?_542:dojo.query;while(!_542.matches(_545,_53d,_540)){if(_545==_540||_53f===false||!(_545=_545.parentNode)||_545.nodeType!=1){return;}}return _545;};if(_543){return on(_540,_543(_544),_541);}return on(_540,_53e,function(_546){var _547=_544(_546.target);return _547&&_541.call(_547,_546);});};};function _548(){this.cancelable=false;};function _549(){this.bubbles=false;};var _54a=[].slice,_54b=on.emit=function(_54c,type,_54d){var args=_54a.call(arguments,2);var _54e="on"+type;if("parentNode" in _54c){var _54f=args[0]={};for(var i in _54d){_54f[i]=_54d[i];}_54f.preventDefault=_548;_54f.stopPropagation=_549;_54f.target=_54c;_54f.type=type;_54d=_54f;}do{_54c[_54e]&&_54c[_54e].apply(_54c,args);}while(_54d&&_54d.bubbles&&(_54c=_54c.parentNode));return _54d&&_54d.cancelable&&_54d;};var _53a={};if(!has("event-stopimmediatepropagation")){var _550=function(){this.immediatelyStopped=true;this.modified=true;};var _538=function(_551){return function(_552){if(!_552.immediatelyStopped){_552.stopImmediatePropagation=_550;return _551.apply(this,arguments);}};};}if(has("dom-addeventlistener")){_53a={focusin:"focus",focusout:"blur"};if(has("opera")){_53a.keydown="keypress";}on.emit=function(_553,type,_554){if(_553.dispatchEvent&&document.createEvent){var _555=_553.ownerDocument.createEvent("HTMLEvents");_555.initEvent(type,!!_554.bubbles,!!_554.cancelable);for(var i in _554){var _556=_554[i];if(!(i in _555)){_555[i]=_554[i];}}return _553.dispatchEvent(_555)&&_555;}return _54b.apply(on,arguments);};}else{on._fixEvent=function(evt,_557){if(!evt){var w=_557&&(_557.ownerDocument||_557.document||_557).parentWindow||window;evt=w.event;}if(!evt){return evt;}if(_558&&evt.type==_558.type){evt=_558;}if(!evt.target){evt.target=evt.srcElement;evt.currentTarget=(_557||evt.srcElement);if(evt.type=="mouseover"){evt.relatedTarget=evt.fromElement;}if(evt.type=="mouseout"){evt.relatedTarget=evt.toElement;}if(!evt.stopPropagation){evt.stopPropagation=_559;evt.preventDefault=_55a;}switch(evt.type){case "keypress":var c=("charCode" in evt?evt.charCode:evt.keyCode);if(c==10){c=0;evt.keyCode=13;}else{if(c==13||c==27){c=0;}else{if(c==3){c=99;}}}evt.charCode=c;_55b(evt);break;}}return evt;};var _558,_55c=function(_55d){this.handle=_55d;};_55c.prototype.remove=function(){delete _dojoIEListeners_[this.handle];};var _55e=function(_55f){return function(evt){evt=on._fixEvent(evt,this);var _560=_55f.call(this,evt);if(evt.modified){if(!_558){setTimeout(function(){_558=null;});}_558=evt;}return _560;};};var _53c=function(_561,type,_562){_562=_55e(_562);if(((_561.ownerDocument?_561.ownerDocument.parentWindow:_561.parentWindow||_561.window||window)!=top||has("jscript")<5.8)&&!has("config-_allow_leaks")){if(typeof _dojoIEListeners_=="undefined"){_dojoIEListeners_=[];}var _563=_561[type];if(!_563||!_563.listeners){var _564=_563;_563=Function("event","var callee = arguments.callee; for(var i = 0; i0){return _578.lastIndexOf(a,x,from);}var l=a&&a.length||0,end=up?l+_577:_576,i;if(from===u){i=up?_576:l+_577;}else{if(from<0){i=l+from;if(i<0){i=_576;}}else{i=from>=l?l+_577:from;}}if(l&&typeof a=="string"){a=a.split("");}for(;i!=end;i+=_575){if(a[i]==x){return i;}}return -1;};};var _578={every:_571(false),some:_571(true),indexOf:_574(true),lastIndexOf:_574(false),forEach:function(arr,_579,_57a){var i=0,l=arr&&arr.length||0;if(l&&typeof arr=="string"){arr=arr.split("");}if(typeof _579=="string"){_579=_56f[_579]||_570(_579);}if(_57a){for(;i=0);},add:function addClass(node,_595){node=dom.byId(node);_595=_592(_595);var cls=node[_590],_596;cls=cls?" "+cls+" ":" ";_596=cls.length;for(var i=0,len=_595.length,c;i=0),has.add("khtml",dav.indexOf("Konqueror")>=0?tv:undefined);has.add("webkit",parseFloat(dua.split("WebKit/")[1])||undefined);has.add("chrome",parseFloat(dua.split("Chrome/")[1])||undefined);has.add("safari",dav.indexOf("Safari")>=0&&!has("chrome")?parseFloat(dav.split("Version/")[1]):undefined);has.add("mac",dav.indexOf("Macintosh")>=0);has.add("quirks",document.compatMode=="BackCompat");has.add("ios",/iPhone|iPod|iPad/.test(dua));has.add("android",parseFloat(dua.split("Android ")[1])||undefined);if(!has("webkit")){if(dua.indexOf("Opera")>=0){has.add("opera",tv>=9.8?parseFloat(dua.split("Version/")[1])||tv:tv);}if(dua.indexOf("Gecko")>=0&&!has("khtml")&&!has("webkit")){has.add("mozilla",tv);}if(has("mozilla")){has.add("ff",parseFloat(dua.split("Firefox/")[1]||dua.split("Minefield/")[1])||undefined);}if(document.all&&!has("opera")){var isIE=parseFloat(dav.split("MSIE ")[1])||undefined;var mode=document.documentMode;if(mode&&mode!=5&&Math.floor(isIE)!=mode){isIE=mode;}has.add("ie",isIE);}has.add("wii",typeof opera!="undefined"&&opera.wiiremote);}}return has;});},"dojo/request/handlers":function(){define(["../json","../_base/kernel","../_base/array","../has"],function(JSON,_5aa,_5ab,has){has.add("activex",typeof ActiveXObject!=="undefined");var _5ac;if(has("activex")){var dp=["Msxml2.DOMDocument.6.0","Msxml2.DOMDocument.4.0","MSXML2.DOMDocument.3.0","MSXML.DOMDocument"];_5ac=function(_5ad){var _5ae=_5ad.data;if(!_5ae||!_5ae.documentElement){var text=_5ad.text;_5ab.some(dp,function(p){try{var dom=new ActiveXObject(p);dom.async=false;dom.loadXML(text);_5ae=dom;}catch(e){return false;}return true;});}return _5ae;};}var _5af={"javascript":function(_5b0){return _5aa.eval(_5b0.text||"");},"json":function(_5b1){return JSON.parse(_5b1.text||null);},"xml":_5ac};function _5b2(_5b3){var _5b4=_5af[_5b3.options.handleAs];_5b3.data=_5b4?_5b4(_5b3):(_5b3.data||_5b3.text);return _5b3;};_5b2.register=function(name,_5b5){_5af[name]=_5b5;};return _5b2;});},"dojo/aspect":function(){define("dojo/aspect",[],function(){"use strict";var _5b6,_5b7=0;function _5b8(_5b9,type,_5ba,_5bb){var _5bc=_5b9[type];var _5bd=type=="around";var _5be;if(_5bd){var _5bf=_5ba(function(){return _5bc.advice(this,arguments);});_5be={remove:function(){_5be.cancelled=true;},advice:function(_5c0,args){return _5be.cancelled?_5bc.advice(_5c0,args):_5bf.apply(_5c0,args);}};}else{_5be={remove:function(){var _5c1=_5be.previous;var next=_5be.next;if(!next&&!_5c1){delete _5b9[type];}else{if(_5c1){_5c1.next=next;}else{_5b9[type]=next;}if(next){next.previous=_5c1;}}},id:_5b7++,advice:_5ba,receiveArguments:_5bb};}if(_5bc&&!_5bd){if(type=="after"){var next=_5bc;while(next){_5bc=next;next=next.next;}_5bc.next=_5be;_5be.previous=_5bc;}else{if(type=="before"){_5b9[type]=_5be;_5be.next=_5bc;_5bc.previous=_5be;}}}else{_5b9[type]=_5be;}return _5be;};function _5c2(type){return function(_5c3,_5c4,_5c5,_5c6){var _5c7=_5c3[_5c4],_5c8;if(!_5c7||_5c7.target!=_5c3){_5c3[_5c4]=_5c8=function(){var _5c9=_5b7;var args=arguments;var _5ca=_5c8.before;while(_5ca){args=_5ca.advice.apply(this,args)||args;_5ca=_5ca.next;}if(_5c8.around){var _5cb=_5c8.around.advice(this,args);}var _5cc=_5c8.after;while(_5cc&&_5cc.id<_5c9){if(_5cc.receiveArguments){var _5cd=_5cc.advice.apply(this,args);_5cb=_5cd===_5b6?_5cb:_5cd;}else{_5cb=_5cc.advice.call(this,_5cb,args);}_5cc=_5cc.next;}return _5cb;};if(_5c7){_5c8.around={advice:function(_5ce,args){return _5c7.apply(_5ce,args);}};}_5c8.target=_5c3;}var _5cf=_5b8((_5c8||_5c7),type,_5c5,_5c6);_5c5=null;return _5cf;};};var _5d0=_5c2("after");var _5d1=_5c2("before");var _5d2=_5c2("around");return {before:_5d1,around:_5d2,after:_5d0};});},"dojo/ready":function(){define("dojo/ready",["./_base/kernel","./has","require","./domReady","./_base/lang"],function(dojo,has,_5d3,_5d4,lang){var _5d5=0,_5d6,_5d7=[],_5d8=0,_5d9=function(){_5d5=1;dojo._postLoad=dojo.config.afterOnLoad=true;if(_5d7.length){_5d6(_5da);}},_5da=function(){if(_5d5&&!_5d8&&_5d7.length){_5d8=1;var f=_5d7.shift();try{f();}finally{_5d8=0;}_5d8=0;if(_5d7.length){_5d6(_5da);}}};_5d3.on("idle",_5da);_5d6=function(){if(_5d3.idle()){_5da();}};var _5db=dojo.ready=dojo.addOnLoad=function(_5dc,_5dd,_5de){var _5df=lang._toArray(arguments);if(typeof _5dc!="number"){_5de=_5dd;_5dd=_5dc;_5dc=1000;}else{_5df.shift();}_5de=_5de?lang.hitch.apply(dojo,_5df):function(){_5dd();};_5de.priority=_5dc;for(var i=0;i<_5d7.length&&_5dc>=_5d7[i].priority;i++){}_5d7.splice(i,0,_5de);_5d6();};1||has.add("dojo-config-addOnLoad",1);if(1){var dca=dojo.config.addOnLoad;if(dca){_5db[(lang.isArray(dca)?"apply":"call")](dojo,dca);}}if(1&&dojo.config.parseOnLoad&&!dojo.isAsync){_5db(99,function(){if(!dojo.parser){dojo.deprecated("Add explicit require(['dojo/parser']);","","2.0");_5d3(["dojo/parser"]);}});}if(1){_5d4(_5d9);}else{_5d9();}return _5db;});},"dojo/_base/connect":function(){define(["./kernel","../on","../topic","../aspect","./event","../mouse","./sniff","./lang","../keys"],function(dojo,on,hub,_5e0,_5e1,_5e2,has,lang){has.add("events-keypress-typed",function(){var _5e3={charCode:0};try{_5e3=document.createEvent("KeyboardEvent");(_5e3.initKeyboardEvent||_5e3.initKeyEvent).call(_5e3,"keypress",true,true,null,false,false,false,false,9,3);}catch(e){}return _5e3.charCode==0&&!has("opera");});function _5e4(obj,_5e5,_5e6,_5e7,_5e8){_5e7=lang.hitch(_5e6,_5e7);if(!obj||!(obj.addEventListener||obj.attachEvent)){return _5e0.after(obj||dojo.global,_5e5,_5e7,true);}if(typeof _5e5=="string"&&_5e5.substring(0,2)=="on"){_5e5=_5e5.substring(2);}if(!obj){obj=dojo.global;}if(!_5e8){switch(_5e5){case "keypress":_5e5=_5e9;break;case "mouseenter":_5e5=_5e2.enter;break;case "mouseleave":_5e5=_5e2.leave;break;}}return on(obj,_5e5,_5e7,_5e8);};var _5ea={106:42,111:47,186:59,187:43,188:44,189:45,190:46,191:47,192:96,219:91,220:92,221:93,222:39,229:113};var _5eb=has("mac")?"metaKey":"ctrlKey";var _5ec=function(evt,_5ed){var faux=lang.mixin({},evt,_5ed);_5ee(faux);faux.preventDefault=function(){evt.preventDefault();};faux.stopPropagation=function(){evt.stopPropagation();};return faux;};function _5ee(evt){evt.keyChar=evt.charCode?String.fromCharCode(evt.charCode):"";evt.charOrCode=evt.keyChar||evt.keyCode;};var _5e9;if(has("events-keypress-typed")){var _5ef=function(e,code){try{return (e.keyCode=code);}catch(e){return 0;}};_5e9=function(_5f0,_5f1){var _5f2=on(_5f0,"keydown",function(evt){var k=evt.keyCode;var _5f3=(k!=13)&&k!=32&&(k!=27||!has("ie"))&&(k<48||k>90)&&(k<96||k>111)&&(k<186||k>192)&&(k<219||k>222)&&k!=229;if(_5f3||evt.ctrlKey){var c=_5f3?0:k;if(evt.ctrlKey){if(k==3||k==13){return _5f1.call(evt.currentTarget,evt);}else{if(c>95&&c<106){c-=48;}else{if((!evt.shiftKey)&&(c>=65&&c<=90)){c+=32;}else{c=_5ea[c]||c;}}}}var faux=_5ec(evt,{type:"keypress",faux:true,charCode:c});_5f1.call(evt.currentTarget,faux);if(has("ie")){_5ef(evt,faux.keyCode);}}});var _5f4=on(_5f0,"keypress",function(evt){var c=evt.charCode;c=c>=32?c:0;evt=_5ec(evt,{charCode:c,faux:true});return _5f1.call(this,evt);});return {remove:function(){_5f2.remove();_5f4.remove();}};};}else{if(has("opera")){_5e9=function(_5f5,_5f6){return on(_5f5,"keypress",function(evt){var c=evt.which;if(c==3){c=99;}c=c<32&&!evt.shiftKey?0:c;if(evt.ctrlKey&&!evt.shiftKey&&c>=65&&c<=90){c+=32;}return _5f6.call(this,_5ec(evt,{charCode:c}));});};}else{_5e9=function(_5f7,_5f8){return on(_5f7,"keypress",function(evt){_5ee(evt);return _5f8.call(this,evt);});};}}var _5f9={_keypress:_5e9,connect:function(obj,_5fa,_5fb,_5fc,_5fd){var a=arguments,args=[],i=0;args.push(typeof a[0]=="string"?null:a[i++],a[i++]);var a1=a[i+1];args.push(typeof a1=="string"||typeof a1=="function"?a[i++]:null,a[i++]);for(var l=a.length;i - - - - - - - - - - - - - - diff --git a/plugin/teksi_interlis_tool/svgprofile/print.css b/plugin/teksi_interlis_tool/svgprofile/print.css deleted file mode 100644 index 96092fe..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/print.css +++ /dev/null @@ -1,3 +0,0 @@ -.dont-print { - display: none; -} diff --git a/plugin/teksi_interlis_tool/svgprofile/profile.css b/plugin/teksi_interlis_tool/svgprofile/profile.css deleted file mode 100644 index 641dbae..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/profile.css +++ /dev/null @@ -1,125 +0,0 @@ -body { - font: 10px sans-serif; -} - -.axis { - shape-rendering: crispEdges; -} - -.x.axis .minor { - stroke-opacity: .5; -} - -.y.axis line, .y.axis path, .x.axis line, .x.axis path { - fill: none; - stroke: #323232; -} - -/* The profile elements */ - -g.reach > path { - stroke-width: 1px; - fill-opacity: 0.7; -} - -g.reach > path:hover { - stroke-width: 2px; - fill-opacity: 1; -} - -g.reach.usage-current-4514 > path, -g.reach.usage-current-4518 > path, -g.reach.usage-current-4520 > path, -g.special-structure.usage-current-4514 > path, -g.special-structure.usage-current-4518 > path, -g.special-structure.usage-current-4520 > path -{ - stroke: #0000ff; - fill: #0000ff; -} - -g.reach.usage-current-4516 > path, -g.special-structure.usage-current-4516 > path { - stroke: #00ff6f; - fill: #00ff6f; -} - -g.reach.usage-current-4522 > path, -g.special-structure.usage-current-4522 > path { - stroke: #660066; - fill: #660066; -} - -g.reach.usage-current-4524 > path, -g.reach.usage-current-4526 > path, -g.special-structure.usage-current-4524 > path, -g.special-structure.usage-current-4526 > path { - stroke: #ff0000; - fill: #ff0000; -} - -/* Fallback */ -g.reach > path, -g.special-structure > path { - stroke: #969696; - fill: #969696; -} - -/* Blind connections */ -g.reach > .blind-connection { - stroke: #323232; - stroke-width: 2px; - fill: #323232; - fill-opacity: 0.7; -} - -.scale-large g.reach > .blind-connection { - visibility: hidden; -} - -/* - * Special structure - */ - -g.special-structure > path { - stroke-width: 2px; - fill-opacity: 0.7; -} - -g.special-structure:hover > path { - stroke-width: 3px; - fill-opacity: 1; -} - -g.special-structure:hover > text { - font-weight: bold; -} - -/* - * Terrain line - */ - -.terrain { - stroke-width: 1.5px; - vector-effect: non-scaling-stroke; - stroke: #007700; - fill: none; -} - -/* - * Backflow line - */ - -.backflow { - stroke-width: 1.5px; - vector-effect: non-scaling-stroke; - stroke: #007777; - fill: none; -} - -/* Zoom / pan overlay */ -rect.pane { - cursor: move; - fill: none; - pointer-events: all; -} diff --git a/plugin/teksi_interlis_tool/svgprofile/profile.js b/plugin/teksi_interlis_tool/svgprofile/profile.js deleted file mode 100644 index 502e9b4..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/profile.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * profile.js - * - * Copyright (C) 2013 Matthias Kuhn - * matthias [dot] kuhn [at] gmx [dot] ch - *----------------------------------------------------------- - * - * licensed under the terms of GNU GPL 2 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this progsram; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - - -// Create a function that returns a particular property of its parameter. -// If that property is a function, invoke it (and pass optional params). -function extract( name ) -{ - "use strict"; - var v, params = Array.prototype.slice.call(arguments, 1); - return function (o) - { - return (typeof (v = o[name]) === 'function' ? v.apply(o, params) : v ); - }; -} - -// Return the first argument passed in -function I(d) -{ - "use strict"; - return d; -} - -// Global Object, where we'll declare all the useful stuff inside -var tww = { def: {}, test: {} }; - -require( ["dojo/on", "dojo/ready", "dojo/_base/json", "dojo/_base/lang", "profile/specialStructure", "profile/reach", "profile/surface"], function( on, ready, dojo, lang, SpecialStructure, Reach, Surface ) { - tww.def.ProfilePlot = dojo.declare( null, - { - verticalExaggeration: 10, - - margin: 10, - - x: d3.scale.linear(), - y: d3.scale.linear(), - - xAxis: d3.svg.axis() - .scale( this.x ) - .tickSize( -this.height ) - .tickSubdivide( true ), - yAxis: d3.svg.axis() - .scale( this.y ) - .ticks( 4 ) - .orient( 'right' ), - - zoom: d3.behavior.zoom(), - - initSVG: function( parent ) - { - var that = this; - - // Add an SVG element with the desired dimensions and margin. - this.mainGroup = parent.append("svg:svg") - .append("svg:g") - .attr( "transform", "translate(" + this.margin + "," + this.margin + ")" ); - - // Add the clip path. - this.clipPath = this.mainGroup.append("svg:clipPath") - .attr("id", "profileMask") - .append("svg:rect"); - - // Add the x-axis. - this.gxAxis = this.mainGroup.append("svg:g") - .attr("class", "x axis"); - - // Add the y-axis. - this.gyAxis = this.mainGroup.append("svg:g") - .attr("class", "y axis"); - - this.zoom - .x( this.x ) - .y( this.y ) - .on( 'zoom', lang.hitch( this, this.zoomed ) ); - - this.zoomRect = this.mainGroup.append( "svg:rect" ) - .attr( "class", "pane" ) - .classed( 'dont-print', true ) - .call( this.zoom ); - - this.profileViewPort = this.mainGroup.append( "svg:g") - .attr( "clip-path", "url(#profileMask)" ); - - this.profile = this.profileViewPort.append( "svg:g" ) - .attr( "class", "profile" ) - .call( this.zoom ); - - // TODO: make resize more intelligent so it gets proper axis bounds already on first init - this.onResize(); - this.onResize(); - - // Subscribe to window resize event - on( window, "resize", lang.hitch( this, this.onResize ) ); - - // http://tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/ - if ( window.matchMedia ) - { - var mediaQueryList = window.matchMedia( 'print' ); - mediaQueryList.addListener( lang.hitch( this, this.onPrint ) ); - } - - this.specialStructure = new SpecialStructure({ - svgProfile: this.profile, - x: this.x, - y: this.y - }); - - this.reach = new Reach({ - svgProfile: this.profile, - x: this.x, - y: this.y - }); - - this.terrain = new Surface({ - svgProfile: this.profile, - x: this.x, - y: this.y, - className: "terrain", - xAttr: "offset", - yAttr: "coverLevel" - }); - - this.backflow = new Surface({ - svgProfile: this.profile, - x: this.x, - y: this.y, - className: "backflow", - xAttr: "offset", - yAttr: "backflowLevel" - }); - }, - - onPrint: function ( mql ) - { - // only before print - if ( mql.matches ) - { - console.info( 'onbeforeprint' ); - } - }, - - onResize: function () - { - // Get the size of the axis - var xAxisHeight = d3.max( d3.select( 'g.x.axis' ), function (d) { return d.pop().getBoundingClientRect().height; } ); - var yAxisWidth = d3.max( d3.select( 'g.y.axis' ), function (d) { return d.pop().getBoundingClientRect().width; } ); - - this.width = d3.select("body").property( 'clientWidth' ) - 2*this.margin - yAxisWidth; - this.height = d3.select("body").property( 'clientHeight' ) - 2*this.margin - xAxisHeight; - - // Scales and axes. Note the inverted domain for the y-scale: bigger is up! - this.x.range([0, this.width]); - this.y.range([this.height, 0]); - - this.mainGroup.select( "g.x.axis" ).attr( "transform", "translate(0," + this.height + ")" ); - this.mainGroup.select( "g.y.axis" ).attr( "transform", "translate(" + this.width + ",0)" ); - - this.xAxis.scale(this.x).tickSize(-5).tickPadding(5); - this.yAxis.scale(this.y); - - this.clipPath - .attr("width", this.width ) - .attr("height", this.height ); - - this.gxAxis - .attr("transform", "translate(0," + this.height + ")") - .call( this.xAxis ); - - this.gyAxis - .attr("transform", "translate(" + this.width + ",0)") - .call( this.yAxis ); - - this.zoomRect - .attr( "width", this.width ) - .attr( "height", this.height ); - }, - - // Data changed: domain needs to be adjusted - scaleDomain: function () - { -// this.zoom.scale(1); -// this.zoom.translate([0,0]); - - var rExt = this.reach.extent(); - var sExt = this.specialStructure.extent(); - var xExtent = [ d3.min([rExt.x[0], sExt.x[0]]), d3.max([rExt.x[1], sExt.x[1]]) ]; - this.x.domain( xExtent ); - var maxY = this.terrain.extent().y[1]; - var minY = maxY - ( this.height * xExtent[1] ) / this.width / this.verticalExaggeration; - this.y.domain([minY, maxY ]); - - this.zoom - .x( this.x ) - .y( this.y ); - - this.zoom - .scale( 1 ); - this.zoom - .translate( [0, 0] ); - - this.mainGroup.select( 'g.x.axis' ).call( this.xAxis ); - this.mainGroup.select( 'g.y.axis' ).call( this.yAxis ); - - this.redraw(); - }, - - // zoom and pan operations - zoomed: function () - { - var dom = this.x.domain(); - var ran = this.x.range(); - - var ddom = dom[1]-dom[0]; - var dran = ran[1]-ran[0]; - - if ( ddom / dran < 0.2 ) - { - this.profile - .classed( 'scale-small', true ) - .classed( 'scale-large', false ); - } - else - { - this.profile - .classed( 'scale-small', false ) - .classed( 'scale-large', true ); - } - - this.mainGroup.select( 'g.x.axis' ).call( this.xAxis ); - this.mainGroup.select( 'g.y.axis' ).call( this.yAxis ); - - this.redraw(0); - }, - - redraw: function( duration ) - { - if( typeof(duration) === 'undefined' ) { duration = 750; } - - this.reach.redraw( duration ); - this.specialStructure.redraw( duration ); - this.terrain.redraw( duration ); - this.backflow.redraw( duration ); - } - }); - - tww.test.clickReach = function( selector ) { - var event = document.createEvent( 'MouseEvents' ); - event.initMouseEvent( 'click' ); - var elem = d3.select( selector )[0][0]; - elem.dispatchEvent(event); - }; - - /* - * When the DOM becomes ready, run some init code - */ - - ready(function(){ - // Create the profile we need - tww.profilePlot = new tww.def.ProfilePlot(); - tww.profilePlot.initSVG( d3.select("body") ); - - // profileProxy is our bridge to the TWW plugin. It's inserted with teh use of black magic (and pyQt/QtWebKit bridge) - //profileProxy.profileChanged.connect( dojo.hitch( tww.profilePlot, tww.profilePlot.createReaches, dojo.fromJson( arguments[0] ) ) ); - if ( typeof profileProxy !== 'undefined' ) - { - profileProxy.profileChanged.connect( - function(data) { - var profileData = dojo.fromJson(data); - tww.data = profileData; - var reachData = profileData.filter ( function(d) { return d.type === 'reach'; } ); - tww.profilePlot.reach.data( reachData ); - var specialStructureData = profileData.filter ( function(d) { return d.type === 'special_structure'; } ); - tww.profilePlot.specialStructure.data( specialStructureData ); - var nodeData = profileData.filter( function(d) { return d.type === 'node'; } ); - tww.profilePlot.terrain.data( nodeData ); - tww.profilePlot.backflow.data( nodeData ); - - tww.profilePlot.scaleDomain(); - tww.profilePlot.redraw(); - } - ); - - profileProxy.verticalExaggerationChanged.connect( - function(ve) - { - tww.profilePlot.verticalExaggeration = ve; - tww.profilePlot.scaleDomain(); - } - ); - - profileProxy.updateProfile(); - } - }); -}); diff --git a/plugin/teksi_interlis_tool/svgprofile/profile/profileElement.js b/plugin/teksi_interlis_tool/svgprofile/profile/profileElement.js deleted file mode 100644 index 108cccc..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/profile/profileElement.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * profileElement.js - * - * Copyright (C) 2013 Matthias Kuhn - * matthias [dot] kuhn [at] gmx [dot] ch - *----------------------------------------------------------- - * - * licensed under the terms of GNU GPL 2 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this progsram; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -define([ "dojo/_base/declare" ], function (declare) { - return declare([], { - svgProfile: null, /* Reference to the svg object */ - x: null, /* Reference to the d3 x scale */ - y: null, /* Reference to the d3 y scale */ - - data: function (data) { - - }, - - redraw: function (duration) { - - }, - - extent: function () { - /* xmin, xmax, ymin, ymax */ - return { x: [0, 1], y: [0, 1] }; - }, - - tooltipTop: function( tt ) { - var height = d3.select("body").property( 'clientHeight' ); - tt.classed( 'dont-print', true ); - tt.attr('top'); - var ttHeight = tt.property( 'clientHeight' ); - - if ( height - ttHeight > event.pageY - 10 ) - { - return event.pageY - 10 + 'px'; - } - else - { - return height - ttHeight + 'px'; - } - }, - - formatMeters: function(n, decimal_places, multiplier, suffix) { - if ( n === null ) - { - return 'Undefined'; - } - if ( multiplier === undefined ) - { - multiplier = 1; - } - if ( suffix === undefined ) - { - suffix = 'm'; - } - if ( decimal_places === undefined ) - { - decimal_places = 2; - } - - var rf = Math.pow( 10, decimal_places ); - - return Math.round( n * multiplier * rf ) / rf + ' ' + suffix; - } - }); -}); diff --git a/plugin/teksi_interlis_tool/svgprofile/profile/reach.js b/plugin/teksi_interlis_tool/svgprofile/profile/reach.js deleted file mode 100644 index b585c27..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/profile/reach.js +++ /dev/null @@ -1,219 +0,0 @@ -/** - * reach.js - * - * Copyright (C) 2013 Matthias Kuhn - * matthias [dot] kuhn [at] gmx [dot] ch - *----------------------------------------------------------- - * - * licensed under the terms of GNU GPL 2 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this progsram; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - - -define([ "dojo/_base/declare", "dojo/_base/lang", "profile/profileElement" ], function ( declare, lang, _ProfileElement ) { - "use strict"; - - return declare( 'reach', [ _ProfileElement ], { - reaches: null, - line: d3.svg.line(), - tooltip: d3.select('body') - .append( 'div' ) - .attr('class', 'tooltip') - .attr('id', 'reach-tooltip'), - - constructor: function(/*Object*/ kwArgs) - { - lang.mixin( this, kwArgs ); - - this.line - .x( lang.hitch( this, function(d) { return this.x( d.x ); } ) ) - .y( lang.hitch( this, function(d) { return this.y( d.y ); } ) ); - }, - - data: function( data ) - { - this.reaches = this.svgProfile.selectAll('.reach') - .data( data, extract( 'gid' ) ); - - this.reaches - .exit() - .transition() - .duration(300) - .attr('opacity',0) - .remove(); - - var newReaches = this.reaches - .enter() - .append('svg:g') - .attr( 'id', function(d) { return d.objId; } ) - .attr( 'class', function(d) { return 'usage-current-' + d.usageCurrent; } ) - .classed( 'reach', true ) - .on('click', function(d) { profileProxy.onReachClicked( d.objId ); }); - - newReaches - .append('svg:path') - .attr( 'class', 'progression' ) - .on( 'mouseover', - lang.hitch( this, - function(d) - { - profileProxy.onReachMouseOver( d.objId ); - - return this.tooltip - .html( - '

' + qsTr( 'Reach', this ) + ' ' + d.objId + '


' + - '' + qsTr( 'Material:', this ) + ' ' + d.material + '
' + - '' + qsTr( 'Width:', this ) + ' ' + this.formatMeters( d.width_m, 0, 1000, 'mm' ) + '
' + - '' + qsTr( 'Length:', this ) + ' ' + this.formatMeters( d.length ) + '
' + - '' + qsTr( 'Gradient:', this ) + ' ' + Math.round( d.gradient * 10000 ) / 10 + ' ‰
' + - '' + qsTr( 'Entry level:', this ) + ' ' + this.formatMeters( d.startLevel ) + '
' + - '' + qsTr( 'Exit level:', this ) + ' ' + this.formatMeters( d.endLevel ) - ) - .style('top', lang.hitch( this, function() { return this.tooltipTop( this.tooltip ); } ) ) - .style('left', (event.pageX+10)+'px') - .style( 'opacity', 1 ); - } - ) - ) - .on( 'mouseout', - lang.hitch( this, - function( d ) - { - profileProxy.onReachMouseOut( d.objId ); - - return this.tooltip.transition().duration( 700 ).style( 'opacity', 0 ); - } - ) - ); - - this.reaches - .datum( - lang.hitch( this, - function(d) - { - d.pathPoints = this.pathPoints(d); - return d; - } - ) - ); - - /* Blind connections */ - this.reaches - .selectAll( '.blind-connection' ) - .data( - function(d) { - /* Do only show blind connections. offset 0 and 1 are normally special structures.*/ - var drp = d.reachPoints.filter( - function(rp) { return rp.offset !== 0 && rp.offset !== 1; } - ); - - /* Assign backreference to reach to each reachpoint */ - drp.forEach( - function(rp) { rp.reach = d; } - ); - - return drp; - }, - /* Id's for the reachopints are their respective objId */ - function(rp) { return rp.objId; } - ) - .enter() - .append( 'circle' ) - .attr( 'class', 'blind-connection') - .attr( 'r', '5' ) - .on( 'mouseover', - lang.hitch( this, - function(d) - { - profileProxy.onReachPointMouseOver( d.objId, d.reach.objId ); - } - ) - ) - .on( 'mouseout', - lang.hitch( this, - function(d) - { - profileProxy.onReachPointMouseOut(d.objId, d.reach.objId ); - } - ) - ); - }, - - redraw: function( duration ) - { - // create new reaches - var blindConnections = this.reaches.selectAll('.blind-connection'); - // For some reason, select does propagate the __data__ down from the g to the path element - // see http://stackoverflow.com/questions/10129432/inheritance-in-data-joins - // For some unknown reason selectAll does not do this!! - var paths = this.reaches.select('path'); - - if ( duration > 0 ) - { - paths = paths - .transition() - .duration(duration); - - blindConnections = blindConnections - .transition() - .duration(duration); - } - - paths - .attr( 'd', lang.hitch( this, function(d) { return this.line(d.pathPoints) +'Z'; } ) ); - - blindConnections - .attr( 'cx', lang.hitch( this, function(d) { return this.x(d.offset); } ) ) - .attr( 'cy', lang.hitch( this, function(d) { return this.y(d.level); } ) ); - }, - - extent: function() - { - var minX = d3.min( this.reaches.data(), extract('startOffset') ) || 0; - var maxX = d3.max( this.reaches.data(), extract('endOffset') ) || 1; - var minY = d3.min( this.reaches.data(), extract('endLevel') ) || 0; - var maxY = d3.max( this.reaches.data(), extract('startLevel') ) || 1; - - return {x: [minX, maxX], y: [minY, maxY] }; - }, - - pathPoints: function(d) - { - var endLevel = d.endLevel ? d.endLevel : d.startLevel; - var startLevel = d.startLevel ? d.startLevel : d.endLevel; - - var dy = d.width_m * Math.sqrt(Math.pow(( d.endOffset- d.startOffset ), 2) + Math.pow(( startLevel- endLevel ), 2)) / ( d.endOffset- d.startOffset ); - var x1, x2, x3, x4; - var y1, y2, y3, y4; - x1 = x2 = d.startOffset; - x3 = x4 = d.endOffset; - y1 = startLevel; - y2 = startLevel + dy; - y4 = endLevel; - y3 = endLevel + dy; - - var points = [ - {x: x1, y: y1 }, - {x: x2, y: y2 }, - {x: x3, y: y3 }, - {x: x4, y: y4 } - ]; - - return points; - } - }); -}); diff --git a/plugin/teksi_interlis_tool/svgprofile/profile/specialStructure.js b/plugin/teksi_interlis_tool/svgprofile/profile/specialStructure.js deleted file mode 100644 index 8659852..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/profile/specialStructure.js +++ /dev/null @@ -1,219 +0,0 @@ -/* - * specialStructure.js - * - * Copyright (C) 2013 Matthias Kuhn - * matthias [dot] kuhn [at] gmx [dot] ch - *----------------------------------------------------------- - * - * licensed under the terms of GNU GPL 2 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this progsram; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - - -define([ "dojo/_base/declare", "dojo/_base/lang", "profile/profileElement" ], function (declare, lang, _ProfileElement) { - "use strict"; - return declare( 'specialStructure', [ _ProfileElement ], { - specialStructures: null, /* Reference to the current working set */ - line: d3.svg.line(), - tooltip: d3.select( 'body' ) - .append( 'div' ) - .attr( 'class', 'tooltip' ) - .attr( 'id', 'special-structure-tooltip' ), - - constructor: function(/*Object*/ kwArgs) - { - lang.mixin(this, kwArgs); - - this.line - .x( lang.hitch( this, function( d ) { return this.x( d.x ); } ) ) - .y( lang.hitch( this, function( d ) { return this.y( d.y ); } ) ); - }, - - data: function( data ) - { - // Preprocess data - data.forEach( - lang.hitch( this, - function( d ) - { - d.pathPoints = this.pathPoints( d ); - } - ) - ); - - console.info( data.filter( function (d) { return d.description == '6008.0130'; } ) ); - - this.specialStructures = this.svgProfile - .selectAll( '.special-structure' ) - .data( data, extract( 'objId' ) ); - - var newSpecialStructures = this.specialStructures - .enter() - .append( 'svg:g' ) - .attr( 'id', function(d) { return d.objId; } ) - .attr( 'class', function(d) { return 'usage-current-' + d.usageCurrent; } ) - .classed( 'special-structure', true ) - .on( 'mouseover', - lang.hitch( this, - function(d) - { - profileProxy.onSpecialStructureMouseOver( d.objId ); - - var type = 'Unknown'; - - switch ( d.nodeType ) - { - case 'manhole': - type = qsTr( 'Manhole', this ); - break; - - case 'special_structure': - type = 'Special Structure'; - break; - } - - return this.tooltip - .html( - '

' + type + ': ' + d.objId + '


' + - '' + qsTr( 'Cover level:', this ) + ' ' + this.formatMeters( d.coverLevel ) + '
' + - '' + qsTr( 'Bottom level:', this ) + ' ' + this.formatMeters( d.bottomLevel ) + '
' + - '' + qsTr( 'Entry level:', this ) + ' ' + this.formatMeters( d.startLevel ) + '
' + - '' + qsTr( 'Exit level:', this ) + ' ' + this.formatMeters( d.endLevel ) + '
' - ) - .style( 'top', lang.hitch( this, function() { return this.tooltipTop( this.tooltip ); } ) ) - .style( 'left', (event.pageX+10)+'px' ) - .style( 'opacity', 1 ); - - } - ) - ) - .on( 'mouseout', - lang.hitch( this, - function( d ) - { - profileProxy.onSpecialStructureMouseOut( d.objId ); - - return this.tooltip.transition().duration( 700 ).style( 'opacity', 0 ); - } - ) - ); - - newSpecialStructures - .append( 'svg:path' ); - - newSpecialStructures - .append( 'svg:text' ) - .text( function( d ) { return d.description; } ); - - this.specialStructures.exit() - .transition() - .duration( 300 ) - .attr( 'opacity' , 0 ) - .remove(); - - }, - - redraw: function( duration ) - { - var texts = this.specialStructures.select( 'text' ); - // For some reason, select does propagate the __data__ down from the to the element - // see http://stackoverflow.com/questions/10129432/inheritance-in-data-joins - // For some unknown reason selectAll does not do this!! - var paths = this.specialStructures.select( 'path' ); - - if ( duration > 0 ) - { - texts = texts - .transition() - .duration( duration ); - - paths = paths - .transition() - .duration( duration ); - } - - texts - .attr( 'transform', - lang.hitch( this, - function ( d ) - { - return 'translate(' + - ( this.x( ( d.endOffset + d.startOffset ) / 2 ) ) + - ',' + - ( this.y( d.coverLevel ) - 3 ) + - ')' + - 'rotate(-80)'; - } - ) - ); - - paths - .attr( 'd', - lang.hitch( this, - function( d ) - { - return this.line( d.pathPoints ) +'Z'; - } - ) - ); - }, - - extent: function() - { - var minX = d3.min( this.specialStructures.data(), extract( 'startOffset' ) ) || 0; - var maxX = d3.max( this.specialStructures.data(), extract( 'endOffset' ) ) || 1; - var minY = d3.min( this.specialStructures.data(), extract( 'bottomLevel' ) ) || 0; - var maxY = d3.max( this.specialStructures.data(), extract( 'coverLevel' ) ) || 1; - - return { x: [minX, maxX], y: [minY, maxY] }; - }, - - pathPoints: function(d) - { - // TODO: remove this debugging stuff - if ( tww.p === undefined ) - { - tww.p = [] - } - - tww.p.push( d ); - - var x1 = d.startOffset; - var y1 = d.coverLevel; - var x2 = d.endOffset; - var y2 = d.coverLevel; - var x3 = d.endOffset; - var y3 = d.endLevel; - var x4 = d.wwNodeOffset; - var y4 = d.bottomLevel; - var x5 = d.startOffset; - var y5 = d.startLevel; - - var corners = [ - { x: x1, y: y1 }, - { x: x2, y: y2 }, - { x: x3, y: y3 }, - { x: x4, y: y4 }, - { x: x5, y: y5 } - ]; - - var filteredCorners = corners.filter( function(d) { return d.x !== null && d.y !== null; } ); - - return filteredCorners; - } - }); -}); diff --git a/plugin/teksi_interlis_tool/svgprofile/profile/surface.js b/plugin/teksi_interlis_tool/svgprofile/profile/surface.js deleted file mode 100644 index 4012293..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/profile/surface.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Created with JetBrains PhpStorm. - * User: kk - * Date: 11/29/12 - * Time: 9:07 AM - * To change this template use File | Settings | File Templates. - */ - -define([ "dojo/_base/declare", "dojo/_base/lang", "profile/profileElement" ], function (declare, lang, _ProfileElement) { - return declare([ _ProfileElement ], { - line: null, - surface: null, - className: "surface", - xAttr: undefined, - yAttr: undefined, - - constructor: function(/*Object*/ kwArgs) - { - lang.mixin( this, kwArgs ); - - this.line = d3.svg.line(); - - this.surface = this.svgProfile.append( 'svg:path' ) - .attr( 'class', this.className ); - - this.line - .x( lang.hitch( this, function( d ) { return this.x( d[this.xAttr] ); } ) ) - .y( lang.hitch( this, function( d ) { return this.y( d[this.yAttr] ); } ) ); - }, - - data: function( data ) - { - var surfaceData = data - .filter( lang.hitch( this, function( d ) { return d[this.yAttr] != null; } ) ) - .sort( lang.hitch( this, function( a, b ) { return a[this.xAttr] - b[this.xAttr]; } ) ); - - this.surface - .datum( surfaceData ); - }, - - redraw: function( duration ) - { - var opon = this.surface; - - if ( duration > 0 ) - { - opon = opon - .transition() - .duration( duration ); - } - - opon - .attr( 'd', this.line ); - }, - - extent: function() - { - var minX = d3.min( this.surface.datum(), extract( this.xAttr ) ) || 0; - var maxX = d3.max( this.surface.datum(), extract( this.xAttr ) ) || 1; - var minY = d3.min( this.surface.datum(), extract( this.yAttr ) ) || 0; - var maxY = d3.max( this.surface.datum(), extract( this.yAttr ) ) || 1; - - return {x: [minX, maxX], y: [minY, maxY] }; - } - }); -}); diff --git a/plugin/teksi_interlis_tool/svgprofile/screen.css b/plugin/teksi_interlis_tool/svgprofile/screen.css deleted file mode 100644 index c7c2a9e..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/screen.css +++ /dev/null @@ -1,44 +0,0 @@ -* { - margin:0; - padding:0; -} - -body, html { - height: 100%; - overflow: hidden; -} - -svg { - width: 100%; - height: 100%; -} -.axis, .profile{ - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.tooltip { - position: absolute; - background-color: #fefcea; /* For older browsers (Current OSGeo4W QtWebKit4.dll) */ - background-image: -webkit-linear-gradient(top, #efe7b3 0%,#fefcea 100%); /* Chrome10+,Safari5.1+ */ - -moz-border-radius: 0.5em; - border-radius: 0.5em; - box-shadow:2px 2px 10px #636363; - border: 1px solid #969696; - padding: 0.5em; - pointer-events: none; - - left: -9999px; /* hide by placing outside viewport. This way we can still calculate the size. */ -} - -g.reach { - cursor: pointer; -} - -g.special-structure { - cursor: pointer; -} diff --git a/plugin/teksi_interlis_tool/svgprofile/utils/translation.js b/plugin/teksi_interlis_tool/svgprofile/utils/translation.js deleted file mode 100644 index 90618a0..0000000 --- a/plugin/teksi_interlis_tool/svgprofile/utils/translation.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * translation.js - * - * Copyright (C) 2013 Matthias Kuhn - * matthias [dot] kuhn [at] gmx [dot] ch - *----------------------------------------------------------- - * - * licensed under the terms of GNU GPL 2 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this progsram; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - */ - -/* Integrates closely with TWW / Qt Linguist - * Needs to be in the global namespace for lupdate-qt4 to recognize translation calls. - * uses a named! dojo object as context. - **/ -function qsTr ( sourceString, context ) -{ - "use strict"; - return i18n.qsTr( context.declaredClass, sourceString ); -} From 0e7bcfa0fef574ed209dcff95421cac585335e6a Mon Sep 17 00:00:00 2001 From: Arnaud Poncet-Montanges Date: Thu, 19 Sep 2024 10:15:07 +0200 Subject: [PATCH 8/8] Reword Teksi wasterwater into teksi interlis tool --- plugin/scripts/package-pip-packages.sh | 2 +- plugin/teksi_interlis_tool/__init__.py | 4 ++-- plugin/teksi_interlis_tool/gui/forms.py | 6 +++--- .../teksi_interlis_tool_plugin.py | 4 ++-- .../teksi_interlis_tool/tests/test_interlis.py | 8 ++++---- .../tests/test_interlis_various.py | 2 +- .../teksi_interlis_tool/utils/translation.py | 2 +- plugin/tit_cmd.py | 18 +++++++++--------- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/plugin/scripts/package-pip-packages.sh b/plugin/scripts/package-pip-packages.sh index 4353e81..bc60d18 100644 --- a/plugin/scripts/package-pip-packages.sh +++ b/plugin/scripts/package-pip-packages.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -LIBS_DIR="plugin/teksi_wastewater/libs" +LIBS_DIR="plugin/teksi_interlis_tool/libs" MODELBAKER_LIBRARY=("modelbaker" "1.6.0") diff --git a/plugin/teksi_interlis_tool/__init__.py b/plugin/teksi_interlis_tool/__init__.py index e2d0c80..a777999 100644 --- a/plugin/teksi_interlis_tool/__init__.py +++ b/plugin/teksi_interlis_tool/__init__.py @@ -24,6 +24,6 @@ def classFactory(iface): - from .teksi_wastewater_plugin import TeksiWastewaterPlugin + from .teksi_interlis_tool_plugin import TeksiInterlisToolPlugin - return TeksiWastewaterPlugin(iface) + return TeksiInterlisToolPlugin(iface) diff --git a/plugin/teksi_interlis_tool/gui/forms.py b/plugin/teksi_interlis_tool/gui/forms.py index 0e9d39d..bc7259c 100644 --- a/plugin/teksi_interlis_tool/gui/forms.py +++ b/plugin/teksi_interlis_tool/gui/forms.py @@ -12,15 +12,15 @@ def geometryDigitized(fid, layer, tool): def mapToolDeactivated(tool): tool.deactivated.disconnect() - qgis.utils.plugins["teksi_wastewater"].iface.mapCanvas().unsetMapTool(tool) + qgis.utils.plugins["teksi_interlis_tool"].iface.mapCanvas().unsetMapTool(tool) tool.deleteLater() def digitizeDrainageChannel(fid, layerid): layer = QgsProject.instance().mapLayer(layerid) layer.startEditing() - tool = TwwMapToolDigitizeDrainageChannel(qgis.utils.plugins["teksi_wastewater"].iface, layer) - qgis.utils.plugins["teksi_wastewater"].iface.mapCanvas().setMapTool(tool) + tool = TwwMapToolDigitizeDrainageChannel(qgis.utils.plugins["teksi_interlis_tool"].iface, layer) + qgis.utils.plugins["teksi_interlis_tool"].iface.mapCanvas().setMapTool(tool) tool.geometryDigitized.connect(lambda: geometryDigitized(fid, layer, tool)) # form.window().hide() tool.deactivated.connect(lambda: mapToolDeactivated(tool)) diff --git a/plugin/teksi_interlis_tool/teksi_interlis_tool_plugin.py b/plugin/teksi_interlis_tool/teksi_interlis_tool_plugin.py index 8a33cbc..8945893 100644 --- a/plugin/teksi_interlis_tool/teksi_interlis_tool_plugin.py +++ b/plugin/teksi_interlis_tool/teksi_interlis_tool_plugin.py @@ -58,7 +58,7 @@ def locale(values, feature, parent): return QSettings().value("locale/userLocale", QLocale.system().name()) -class TeksiWastewaterPlugin: +class TeksiInterlisToolPlugin: """ A plugin for wastewater management https://github.com/teksi/wastewater @@ -199,7 +199,7 @@ def initGui(self): self.connectNetworkElementsAction = QAction( QIcon(os.path.join(plugin_root_path(), "icons/link-wastewater-networkelement.svg")), - QApplication.translate("teksi_wastewater", "Connect wastewater networkelements"), + QApplication.translate("teksi_interlis_tool", "Connect wastewater networkelements"), self.iface.mainWindow(), ) self.connectNetworkElementsAction.setEnabled(False) diff --git a/plugin/teksi_interlis_tool/tests/test_interlis.py b/plugin/teksi_interlis_tool/tests/test_interlis.py index 64a3672..b23d4f4 100644 --- a/plugin/teksi_interlis_tool/tests/test_interlis.py +++ b/plugin/teksi_interlis_tool/tests/test_interlis.py @@ -5,12 +5,12 @@ import xml.etree.ElementTree as ET from qgis.testing import start_app, unittest -from teksi_wastewater.interlis import config -from teksi_wastewater.interlis.interlis_importer_exporter import ( +from teksi_interlis_tool.interlis import config +from teksi_interlis_tool.interlis.interlis_importer_exporter import ( InterlisImporterExporter, ) -from teksi_wastewater.interlis.utils.ili2db import InterlisTools -from teksi_wastewater.utils.database_utils import DatabaseUtils +from teksi_interlis_tool.interlis.utils.ili2db import InterlisTools +from teksi_interlis_tool.utils.database_utils import DatabaseUtils # Display logging in unittest output logger = logging.getLogger() diff --git a/plugin/teksi_interlis_tool/tests/test_interlis_various.py b/plugin/teksi_interlis_tool/tests/test_interlis_various.py index 246dbb2..cc1b92b 100644 --- a/plugin/teksi_interlis_tool/tests/test_interlis_various.py +++ b/plugin/teksi_interlis_tool/tests/test_interlis_various.py @@ -1,6 +1,6 @@ import unittest -from teksi_wastewater.utils.plugin_utils import logger +from teksi_interlis_tool.utils.plugin_utils import logger class TestVarious(unittest.TestCase): diff --git a/plugin/teksi_interlis_tool/utils/translation.py b/plugin/teksi_interlis_tool/utils/translation.py index 396ba2e..847f124 100644 --- a/plugin/teksi_interlis_tool/utils/translation.py +++ b/plugin/teksi_interlis_tool/utils/translation.py @@ -52,7 +52,7 @@ def setup_i18n(the_preferred_locale=None): # the same translation twice) translator = QTranslator(QCoreApplication.instance()) - my_translator_file = "teksi_wastewater_" + my_locale_name + my_translator_file = "teksi_interlis_tool_" + my_locale_name my_translator_path = os.path.join( os.path.dirname(os.path.dirname(__file__)), "i18n", my_translator_file ) diff --git a/plugin/tit_cmd.py b/plugin/tit_cmd.py index 5711bc3..bf8dc2b 100644 --- a/plugin/tit_cmd.py +++ b/plugin/tit_cmd.py @@ -4,15 +4,15 @@ import sys from qgis.core import QgsApplication -from teksi_wastewater.interlis import config -from teksi_wastewater.interlis.interlis_importer_exporter import ( +from teksi_interlis_tool.interlis import config +from teksi_interlis_tool.interlis.interlis_importer_exporter import ( InterlisImporterExporter, InterlisImporterExporterError, ) -from teksi_wastewater.interlis.processing_algs.extractlabels_interlis import ( +from teksi_interlis_tool.interlis.processing_algs.extractlabels_interlis import ( ExtractlabelsInterlisAlgorithm, ) -from teksi_wastewater.utils.database_utils import DatabaseUtils +from teksi_interlis_tool.utils.database_utils import DatabaseUtils QgsApplication.setPrefixPath("/usr", True) @@ -242,12 +242,12 @@ def execute_interlis_export(self): if __name__ == "__main__": - teksi_wastewater_cmd = TeksiWastewaterCmd() + teksi_interlis_tool_cmd = TeksiWastewaterCmd() - teksi_wastewater_cmd.parse_arguments() + teksi_interlis_tool_cmd.parse_arguments() - if not teksi_wastewater_cmd.args: - teksi_wastewater_cmd.parser.print_help(sys.stderr) + if not teksi_interlis_tool_cmd.args: + teksi_interlis_tool_cmd.parser.print_help(sys.stderr) exit(1) - teksi_wastewater_cmd.execute() + teksi_interlis_tool_cmd.execute()