示例#1
0
    def add_plugin_pages(self, pages) -> None:
        """
        Add pages from the plugin to the side pabel.
        :param pages: List of plugin page classes to create and attach to the side panel.
        """
        def safe_connect(method, to):
            try:
                method.connect(to)
            except AttributeError:
                pass

        for PageClass in pages:
            action = QAction(self.menutoolbar)
            text = PageClass.title.ljust(13)
            action.setIconText(text)
            action.setIcon(QIcon(PageClass.icon))
            action.setCheckable(True)
            if PageClass.projectpage:
                action.setVisible(False)
                self.projectbuttons.append(action)
                self.menutoolbar.insertAction(self.spaceraction, action)
            else:
                self.menutoolbar.insertAction(self.actionProject, action)

            pagewidget = PageClass(plugins.api, self)

            safe_connect(RoamEvents.selectionchanged,
                         pagewidget.selection_changed)
            safe_connect(RoamEvents.projectloaded, pagewidget.project_loaded)

            pageindex = self.stackedWidget.insertWidget(-1, pagewidget)
            action.setProperty('page', pageindex)
            self.pluginactions.append(action)
            self.menuGroup.addAction(action)
示例#2
0
    def add_action(
        self,
        name,
        icon_path,
        text,
        callback,
        toggle_flag=False,
        enabled_flag=True,
        checkable_flag=False,
        visible_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.
        :param name: Objectname of the action. Serves also as key for the stored actions.
        :type name: str

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param toggle_flag: A flag indicating if the action should connect
            the toggled or triggered signal by default.
            Defaults to triggered (False)
        :type toggle_flag: bool

        :param checkable_flag: A flag indicating if the action should be checkable
            by default. Defaults to False.
        :type checkable: bool

        :param visible_flag: A flag indicating if the action should be displayed
            by default. Defaults to True.
        :type visible: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.setObjectName(name)
        if toggle_flag:
            action.toggled.connect(callback)
        else:
            action.triggered.connect(callback)
        action.setEnabled(enabled_flag)
        action.setCheckable(checkable_flag)
        action.setVisible(visible_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions[name] = action

        return action
示例#3
0
class MapWidget(Ui_CanvasWidget, QMainWindow):
    def __init__(self, parent=None):
        super(MapWidget, self).__init__(parent)
        self.setupUi(self)
        self.snapping = True

        icon = roam_style.iconsize()
        self.projecttoolbar.setIconSize(QSize(icon, icon))

        self.defaultextent = None
        self.current_form = None
        self.last_form = None
        self.layerbuttons = []
        self.editfeaturestack = []
        self.lastgpsposition = None
        self.project = None
        self.gps = None
        self.gpslogging = None
        self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas))

        self.bridge = QgsLayerTreeMapCanvasBridge(
            QgsProject.instance().layerTreeRoot(), self.canvas)
        self.bridge.setAutoSetupOnFirstLayer(False)

        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)

        self.snappingutils = SnappingUtils(self.canvas, self)
        self.canvas.setSnappingUtils(self.snappingutils)

        threadcount = QThread.idealThreadCount()
        threadcount = 2 if threadcount > 2 else 1
        QgsApplication.setMaxThreads(threadcount)
        self.canvas.setParallelRenderingEnabled(True)

        self.canvas.setFrameStyle(QFrame.NoFrame)

        self.editgroup = QActionGroup(self)
        self.editgroup.setExclusive(True)
        self.editgroup.addAction(self.actionPan)
        self.editgroup.addAction(self.actionZoom_In)
        self.editgroup.addAction(self.actionZoom_Out)
        self.editgroup.addAction(self.actionInfo)

        self.actionGPS = GPSAction(self.canvas, self)
        self.projecttoolbar.addAction(self.actionGPS)

        if roam.config.settings.get('north_arrow', False):
            self.northarrow = NorthArrow(":/icons/north", self.canvas)
            self.northarrow.setPos(10, 10)
            self.canvas.scene().addItem(self.northarrow)

        smallmode = roam.config.settings.get("smallmode", False)
        self.projecttoolbar.setSmallMode(smallmode)

        self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu)

        gpsspacewidget = QWidget()
        gpsspacewidget.setMinimumWidth(30)
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)

        self.topspaceraction = self.projecttoolbar.insertWidget(
            self.actionGPS, gpsspacewidget)

        self.dataentryselection = QAction(self.projecttoolbar)
        self.dataentryaction = self.projecttoolbar.insertAction(
            self.topspaceraction, self.dataentryselection)
        self.dataentryselection.triggered.connect(self.select_data_entry)

        self.gpsMarker = GPSMarker(self.canvas)
        self.gpsMarker.hide()

        self.currentfeatureband = CurrentSelection(self.canvas)
        self.currentfeatureband.setIconSize(30)
        self.currentfeatureband.setWidth(10)
        self.currentfeatureband.setColor(QColor(88, 64, 173, 50))
        self.currentfeatureband.setOutlineColour(QColor(88, 64, 173))

        self.gpsband = QgsRubberBand(self.canvas)
        self.gpsband.setColor(QColor(165, 111, 212, 75))
        self.gpsband.setWidth(5)

        RoamEvents.refresh_map.connect(self.refresh_map)
        RoamEvents.editgeometry.connect(self.queue_feature_for_edit)
        RoamEvents.selectioncleared.connect(self.clear_selection)
        RoamEvents.selectionchanged.connect(self.highlight_selection)
        RoamEvents.openfeatureform.connect(self.feature_form_loaded)
        RoamEvents.sync_complete.connect(self.refresh_map)
        RoamEvents.snappingChanged.connect(self.snapping_changed)

        self.snappingbutton = QToolButton()
        self.snappingbutton.setText("Snapping: On")
        self.snappingbutton.setAutoRaise(True)
        self.snappingbutton.pressed.connect(self.toggle_snapping)

        spacer = QWidget()
        spacer2 = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.scalewidget = QgsScaleComboBox()

        self.scalebutton = QToolButton()
        self.scalebutton.setAutoRaise(True)
        self.scalebutton.setMaximumHeight(self.statusbar.height())
        self.scalebutton.pressed.connect(self.selectscale)
        self.scalebutton.setText("Scale")

        self.scalelist = BigList(parent=self.canvas,
                                 centeronparent=True,
                                 showsave=False)
        self.scalelist.hide()
        self.scalelist.setlabel("Map Scale")
        self.scalelist.setmodel(self.scalewidget.model())
        self.scalelist.closewidget.connect(self.scalelist.close)
        self.scalelist.itemselected.connect(self.update_scale_from_item)
        self.scalelist.itemselected.connect(self.scalelist.close)

        self.positionlabel = QLabel('')
        self.gpslabel = QLabel("GPS: Not active")
        self.gpslabelposition = QLabel("")

        self.statusbar.addWidget(self.snappingbutton)
        self.statusbar.addWidget(spacer2)
        self.statusbar.addWidget(self.gpslabel)
        self.statusbar.addWidget(self.gpslabelposition)
        self.statusbar.addPermanentWidget(self.scalebutton)

        self.canvas.extentsChanged.connect(self.update_status_label)
        self.canvas.scaleChanged.connect(self.update_status_label)

        self.connectButtons()

        scalebar_enabled = roam.config.settings.get('scale_bar', False)
        self.scalebar_enabled = False
        if scalebar_enabled:
            roam.utils.warning(
                "Unsupported feature: Scale bar support not ported to QGIS 3 API yet."
            )
            RoamEvents.raisemessage(
                "Unsupported feature",
                "Scale bar support not ported to QGIS 3 API yet",
                level=RoamEvents.CRITICAL)
            self.scalebar_enabled = False
            # self.scalebar = ScaleBarItem(self.canvas)
            # self.canvas.scene().addItem(self.scalebar)

    def clear_plugins(self) -> None:
        """
        Clear all the plugin added toolbars from the map interface.
        """
        toolbars = self.findChildren(QToolBar)
        for toolbar in toolbars:
            if toolbar.property("plugin_toolbar"):
                toolbar.unload()
                self.removeToolBar(toolbar)
                toolbar.deleteLater()

    def add_plugins(self, pluginnames) -> None:
        """
        Add the given plugins to to the mapping interface.

        Adds the toolbars the plugin exposes as new toolbars for the user.
        :param pluginnames: The names of the plugins to load.  Must already be loaded
                            by the plugin loader
        """
        for name in pluginnames:
            # Get the plugin
            try:
                plugin_mod = plugins.loaded_plugins[name]
            except KeyError:
                continue

            if not hasattr(plugin_mod, 'toolbars'):
                roam.utils.warning(
                    "No toolbars() function found in {}".format(name))
                continue

            toolbars = plugin_mod.toolbars()
            self.load_plugin_toolbars(toolbars)

    def load_plugin_toolbars(self, toolbars):
        """
        Load the plugin toolbars into the mapping interface.
        :param toolbars: The list of toolbars class objects to load.
        :return:
        """
        for ToolBarClass in toolbars:
            toolbar = ToolBarClass(plugins.api, self)
            self.addToolBar(Qt.BottomToolBarArea, toolbar)
            toolbar.setProperty("plugin_toolbar", True)

    def snapping_changed(self, snapping):
        """
        Called when the snapping settings have changed. Updates the label in the status bar.
        :param snapping:
        """
        self.snapping = snapping
        if snapping:
            self.snappingbutton.setText("Snapping: On")
        else:
            self.snappingbutton.setText("Snapping: Off")

    def toggle_snapping(self):
        """
        Toggle snapping on or off.
        """
        self.snapping = not self.snapping
        try:
            self.canvas.mapTool().toggle_snapping()
        except AttributeError:
            pass

        RoamEvents.snappingChanged.emit(self.snapping)

    def selectscale(self):
        """
        Show the select scale widget.
        :return:
        """
        self.scalelist.show()

    def update_scale_from_item(self, index):
        """
        Update the canvas scale from the selected scale item.
        :param index: The index of the selected item.
        """
        scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole))
        self.canvas.zoomScale(1.0 / scale)

    def update_gps_fixed_label(self, fixed, gpsinfo):
        if not fixed:
            self.gpslabel.setText("GPS: Acquiring fix")
            self.gpslabelposition.setText("")

    quality_mappings = {
        0: "invalid",
        1: "GPS",
        2: "DGPS",
        3: "PPS",
        4: "Real Time Kinematic",
        5: "Float RTK",
        6: "Estimated",
        7: "Manual input mode",
        8: "Simulation mode"
    }

    def update_gps_label(self, position, gpsinfo):
        """
        Update the GPS label in the status bar with the GPS status.
        :param position: The current GPS position.
        :param gpsinfo: The current extra GPS information.
        """
        if not self.gps.connected:
            return

        fixtype = self.quality_mappings.get(gpsinfo.quality, "")
        self.gpslabel.setText(
            "DOP P:<b>{0:.2f}</b> H:<b>{1:.2f}</b> V:<b>{2:.2f}</b> "
            "Fix: <b>{3}</b> "
            "Sats: <b>{4}</b> ".format(gpsinfo.pdop, gpsinfo.hdop,
                                       gpsinfo.vdop, fixtype,
                                       gpsinfo.satellitesUsed))

        places = roam.config.settings.get("gpsplaces", 8)
        self.gpslabelposition.setText("X: <b>{x:.{places}f}</b> "
                                      "Y: <b>{y:.{places}f}</b> "
                                      "Z: <b>{z}m</b> ".format(
                                          x=position.x(),
                                          y=position.y(),
                                          z=gpsinfo.elevation,
                                          places=places))

    def gps_disconnected(self):
        self.gpslabel.setText("GPS: Not Active")
        self.gpslabelposition.setText("")
        self.gpsMarker.hide()

    def zoom_to_feature(self, feature):
        """
        Zoom to the given feature in the map.
        :param feature:
        :return:
        """
        box = feature.geometry().boundingBox()
        xmin, xmax, ymin, ymax = box.xMinimum(), box.xMaximum(), box.yMinimum(
        ), box.yMaximum()
        xmin -= 5
        xmax += 5
        ymin -= 5
        ymax += 5
        box = QgsRectangle(xmin, ymin, xmax, ymax)
        self.canvas.setExtent(box)
        self.canvas.refresh()

    def update_status_label(self, *args) -> None:
        """
        Update the status bar labels when the information has changed.
        """
        extent = self.canvas.extent()
        self.positionlabel.setText("Map Center: {}".format(
            extent.center().toString()))
        scale = 1.0 / self.canvas.scale()
        scale = self.scalewidget.toString(scale)
        self.scalebutton.setText(scale)

    def refresh_map(self) -> None:
        """
        Refresh the map
        """
        self.canvas.refresh()

    def updatescale(self) -> None:
        """
        Update the scale of the map with the current scale from the scale widget
        :return:
        """
        self.canvas.zoomScale(1.0 / self.scalewidget.scale())

    @property
    def crs(self) -> QgsCoordinateReferenceSystem:
        """
        Get the CRS used that is being used in the canvas
        :return: The QgsCoordinateReferenceSystem that is used by the canvas
        """
        return self.canvas.mapSettings().destinationCrs()

    def feature_form_loaded(self, form, feature, *args):
        """
        Called when the feature form is loaded.
        :param form: The Form object. Holds a reference to the forms layer.
        :param feature: The current capture feature
        """
        self.currentfeatureband.setToGeometry(feature.geometry(),
                                              form.QGISLayer)

    def highlight_selection(self, results):
        """
        Highlight the selection on the canvas.  This updates all selected objects based on the result set.
        :param results: A dict-of-list of layer-features.
        """
        self.clear_selection()
        for layer, features in results.items():
            band = self.selectionbands[layer]
            band.setColor(QColor(255, 0, 0))
            band.setIconSize(25)
            band.setWidth(5)
            band.setBrushStyle(Qt.NoBrush)
            band.reset(layer.geometryType())
            band.setZValue(self.currentfeatureband.zValue() - 1)
            for feature in features:
                band.addGeometry(feature.geometry(), layer)
        self.canvas.update()

    def highlight_active_selection(self, layer, feature, features):
        """
        Update the current active selected feature.
        :param layer: The layer of the active feature.
        :param feature: The active feature.
        :param features: The other features in the set to show as non active selection.
        :return:
        """
        self.clear_selection()
        self.highlight_selection({layer: features})
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)
        self.canvas.update()

    def clear_selection(self):
        """
        Clear the selection from the canvas. Resets all selection rubber bands.
        :return:
        """
        # Clear the main selection rubber band
        self.canvas.scene().update()
        self.currentfeatureband.reset()
        # Clear the rest
        for band in self.selectionbands.values():
            band.reset()

        self.canvas.update()
        self.editfeaturestack = []

    def queue_feature_for_edit(self, form, feature):
        """
        Push a feature on the edit stack so the feature can have the geometry edited.
        :note: This is a big hack and I don't like it!
        :param form: The form for the current feature
        :param feature: The active feature.
        """
        def trigger_default_action():
            for action in self.projecttoolbar.actions():
                if action.property('dataentry') and action.isdefault:
                    action.trigger()
                    self.canvas.currentLayer().startEditing()
                    self.canvas.mapTool().setEditMode(True, feature.geometry(),
                                                      feature)
                    break

        self.editfeaturestack.append((form, feature))
        self.save_current_form()
        self.load_form(form)
        trigger_default_action()

    def save_current_form(self):
        self.last_form = self.current_form

    def restore_last_form(self):
        self.load_form(self.last_form)

    def clear_temp_objects(self):
        """
        Clear all temp objects from the canvas.
        :return:
        """
        def clear_tool_band():
            """
            Clear the rubber band of the active tool if it has one
            """
            tool = self.canvas.mapTool()
            if hasattr(tool, "clearBand"):
                tool.clearBand()

        self.currentfeatureband.reset()
        clear_tool_band()

    def settings_updated(self, settings):
        """
        Called when the settings have been updated in the Roam config.
        :param settings: A dict of the settings.
        """
        self.actionGPS.updateGPSPort()
        gpslogging = settings.get('gpslogging', True)
        if self.gpslogging:
            self.gpslogging.logging = gpslogging
        smallmode = settings.get("smallmode", False)
        self.projecttoolbar.setSmallMode(smallmode)

    def set_gps(self, gps, logging):
        """
        Set the GPS for the map widget.  Connects GPS signals
        """
        self.gps = gps
        self.gpslogging = logging
        self.gps.gpsfixed.connect(self.update_gps_fixed_label)
        self.gps.gpsposition.connect(self.update_gps_label)
        self.gps.gpsposition.connect(self.gps_update_canvas)
        self.gps.firstfix.connect(self.gps_first_fix)
        self.gps.gpsdisconnected.connect(self.gps_disconnected)

        self.gpsMarker.setgps(self.gps)
        self.actionGPS.setgps(gps)

    def gps_update_canvas(self, position, gpsinfo):
        """
        Updates the map canvas based on the GPS position.  By default if the GPS is outside the canvas
        extent the canvas will move to center on the GPS.  Can be turned off in settings.
        :param postion: The current GPS position.
        :param gpsinfo: The extra GPS information
        """
        # Recenter map if we go outside of the 95% of the area
        if self.gpslogging.logging:
            self.gpsband.addPoint(position)
            self.gpsband.show()

        if roam.config.settings.get('gpscenter', True):
            if not self.lastgpsposition == position:
                self.lastgpsposition = position
                rect = QgsRectangle(position, position)
                extentlimt = QgsRectangle(self.canvas.extent())
                extentlimt.scale(0.95)

                if not extentlimt.contains(position):
                    self.zoom_to_location(position)

        self.gpsMarker.show()
        self.gpsMarker.setCenter(position, gpsinfo)

    def gps_first_fix(self, postion, gpsinfo):
        """
        Called the first time the GPS gets a fix.  If set this will zoom to the GPS after the first fix
        :param postion: The current GPS position.
        :param gpsinfo: The extra GPS information
        """
        zoomtolocation = roam.config.settings.get('gpszoomonfix', True)
        if zoomtolocation:
            self.canvas.zoomScale(1000)
            self.zoom_to_location(postion)

    def zoom_to_location(self, position):
        """
        Zoom to ta given position on the map..
        """
        rect = QgsRectangle(position, position)
        self.canvas.setExtent(rect)
        self.canvas.refresh()

    def select_data_entry(self):
        """
        Open the form selection widget to allow the user to pick the active capture form.
        """
        def showformerror(form):
            pass

        def actions():
            for form in self.project.forms:
                if not self.form_valid_for_capture(form):
                    continue

                action = form.createuiaction()
                valid, failreasons = form.valid
                if not valid:
                    roam.utils.warning("Form {} failed to load".format(
                        form.label))
                    roam.utils.warning("Reasons {}".format(failreasons))
                    action.triggered.connect(partial(showformerror, form))
                else:
                    action.triggered.connect(partial(self.load_form, form))
                yield action

        formpicker = PickActionDialog(msg="Select data entry form", wrap=5)
        formpicker.addactions(actions())
        formpicker.exec_()

    def project_loaded(self, project):
        """
        Called when the project is loaded. Main entry point for a loade project.
        :param project: The Roam project that has been loaded.
        """
        self.snappingutils.setConfig(QgsProject.instance().snappingConfig())
        self.project = project
        self.actionPan.trigger()
        firstform = self.first_capture_form()
        if firstform:
            self.load_form(firstform)
            self.dataentryselection.setVisible(True)
        else:
            self.dataentryselection.setVisible(False)

        # Enable the raster layers button only if the project contains a raster layer.
        layers = roam.api.utils.layers()
        hasrasters = any(layer.type() == QgsMapLayer.RasterLayer
                         for layer in layers)
        self.actionRaster.setEnabled(hasrasters)
        self.defaultextent = self.canvas.extent()
        roam.utils.info("Extent: {}".format(self.defaultextent.toString()))

        self.infoTool.selectionlayers = project.selectlayersmapping()

        self.canvas.refresh()

        projectscales, _ = QgsProject.instance().readBoolEntry(
            "Scales", "/useProjectScales")
        if projectscales:
            projectscales, _ = QgsProject.instance().readListEntry(
                "Scales", "/ScalesList")

            self.scalewidget.updateScales(projectscales)
        else:
            scales = [
                "1:50000", "1:25000", "1:10000", "1:5000", "1:2500", "1:1000",
                "1:500", "1:250", "1:200", "1:100"
            ]
            scales = roam.config.settings.get('scales', scales)
            self.scalewidget.updateScales(scales)

        if self.scalebar_enabled:
            self.scalebar.update()

        red = QgsProject.instance().readNumEntry("Gui", "/CanvasColorRedPart",
                                                 255)[0]
        green = QgsProject.instance().readNumEntry("Gui",
                                                   "/CanvasColorGreenPart",
                                                   255)[0]
        blue = QgsProject.instance().readNumEntry("Gui",
                                                  "/CanvasColorBluePart",
                                                  255)[0]
        myColor = QColor(red, green, blue)
        self.canvas.setCanvasColor(myColor)

        self.actionPan.toggle()
        self.clear_plugins()
        self.add_plugins(project.enabled_plugins)

    def setMapTool(self, tool, *args):
        """
        Set the active map tool in the canvas.
        :param tool: The QgsMapTool to set.
        """
        if tool == self.canvas.mapTool():
            return

        if hasattr(tool, "setSnapping"):
            tool.setSnapping(self.snapping)
        self.canvas.setMapTool(tool)

    def connectButtons(self):
        """
        Connect the default buttons in the interface. Zoom, pan, etc
        """
        def connectAction(action, tool):
            action.toggled.connect(partial(self.setMapTool, tool))

        def cursor(name):
            pix = QPixmap(name)
            pix = pix.scaled(QSize(24, 24))
            return QCursor(pix)

        self.zoomInTool = QgsMapToolZoom(self.canvas, False)
        self.zoomOutTool = QgsMapToolZoom(self.canvas, True)
        self.panTool = QgsMapToolPan(self.canvas)
        self.infoTool = InfoTool(self.canvas)

        self.infoTool.setAction(self.actionInfo)
        self.zoomInTool.setAction(self.actionZoom_In)
        self.zoomOutTool.setAction(self.actionZoom_Out)
        self.panTool.setAction(self.actionPan)

        connectAction(self.actionZoom_In, self.zoomInTool)
        connectAction(self.actionZoom_Out, self.zoomOutTool)
        connectAction(self.actionPan, self.panTool)
        connectAction(self.actionInfo, self.infoTool)

        self.zoomInTool.setCursor(cursor(':/icons/in'))
        self.zoomOutTool.setCursor(cursor(':/icons/out'))
        self.infoTool.setCursor(cursor(':/icons/select'))

        self.actionRaster.triggered.connect(self.toggle_raster_layers)
        self.actionHome.triggered.connect(self.homeview)

    def homeview(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        if self.defaultextent:
            self.canvas.setExtent(self.defaultextent)
            self.canvas.refresh()

    def form_valid_for_capture(self, form):
        """
        Check if the given form is valid for capture.
        :param form: The form to check.
        :return: True if valid form for capture
        """
        return form.has_geometry and self.project.layer_can_capture(
            form.QGISLayer)

    def first_capture_form(self):
        """
        Return the first valid form for capture.
        """
        for form in self.project.forms:
            if self.form_valid_for_capture(form):
                return form

    def load_form(self, form):
        """
        Load the given form so it's the active one for capture
        :param form: The form to load
        """
        self.clear_capture_tools()
        self.dataentryselection.setIcon(QIcon(form.icon))
        self.dataentryselection.setText(form.icontext)
        self.create_capture_buttons(form)
        self.current_form = form

    def create_capture_buttons(self, form):
        """
        Create the capture buttons in the toolbar for the given form.
        :param form: The active form.
        """
        tool = form.getMaptool()(self.canvas, form.settings)
        for action in tool.actions:
            # Create the action here.
            if action.ismaptool:
                action.toggled.connect(partial(self.setMapTool, tool))

            # Set the action as a data entry button so we can remove it later.
            action.setProperty("dataentry", True)
            self.editgroup.addAction(action)
            self.layerbuttons.append(action)
            self.projecttoolbar.insertAction(self.topspaceraction, action)
            action.setChecked(action.isdefault)

        if hasattr(tool, 'geometryComplete'):
            add = partial(self.add_new_feature, form)
            tool.geometryComplete.connect(add)
        else:
            tool.finished.connect(self.openForm)

        tool.error.connect(self.show_invalid_geometry_message)

    def show_invalid_geometry_message(self, message) -> None:
        """
        Shows the message to the user if the there is a invalid geometry capture.
        :param message: The message to show the user.
        """
        RoamEvents.raisemessage("Invalid geometry capture",
                                message,
                                level=RoamEvents.CRITICAL)
        if self.canvas.currentLayer() is not None:
            self.canvas.currentLayer().rollBack()
        RoamEvents.editgeometry_invalid.emit()

    def add_new_feature(self, form, geometry: QgsGeometry):
        """
        Add a new new feature to the given layer
        :param form:  The form to use for the new feature.
        :param geometry: The new geometry to create the feature for.
        """
        # NOTE This function is doing too much, acts as add and also edit.
        layer = form.QGISLayer
        if geometry.isMultipart():
            geometry.convertToMultiType()

        # Transform the new geometry back into the map layers geometry if it's needed
        transform = self.canvas.mapSettings().layerTransform(layer)
        if transform.isValid():
            geometry.transform(transform,
                               QgsCoordinateTransform.ReverseTransform)

        try:
            form, feature = self.editfeaturestack.pop()
            self.editfeaturegeometry(form, feature, newgeometry=geometry)
            return
        except IndexError:
            pass

        feature = form.new_feature(geometry=geometry)
        RoamEvents.load_feature_form(form, feature, editmode=False)

    def editfeaturegeometry(self, form, feature, newgeometry):
        # TODO Extract into function.
        layer = form.QGISLayer
        layer.startEditing()
        feature.setGeometry(newgeometry)
        layer.updateFeature(feature)
        saved = layer.commitChanges()
        if not saved:
            map(roam.utils.error, layer.commitErrors())
        self.canvas.refresh()
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)
        RoamEvents.editgeometry_complete.emit(form, feature)
        self.canvas.mapTool().setEditMode(False, None, None)
        self.restore_last_form()

    def clear_capture_tools(self):
        """
        Clear the capture tools from the toolbar.
        :return: True if the capture button was active at the time of clearing.
        """
        captureselected = False
        for action in self.projecttoolbar.actions():
            if action.objectName() == "capture" and action.isChecked():
                captureselected = True

            if action.property('dataentry'):
                self.projecttoolbar.removeAction(action)
        return captureselected

    def toggle_raster_layers(self) -> None:
        """
        Toggle all raster layers on or off.
        """
        # Freeze the canvas to save on UI refresh
        dlg = PickActionDialog(msg="Raster visibility")
        actions = [
            (":/icons/raster_0", "Off", partial(self._set_basemaps_opacity,
                                                0), "photo_off"),
            (":/icons/raster_25", "25%",
             partial(self._set_basemaps_opacity, .25), "photo_25"),
            (":/icons/raster_50", "50%",
             partial(self._set_basemaps_opacity, .50), "photo_50"),
            (":/icons/raster_75", "75%",
             partial(self._set_basemaps_opacity, .75), "photo_75"),
            (":/icons/raster_100", "100%",
             partial(self._set_basemaps_opacity, 1), "photo_100"),
        ]

        # ":/icons/raster_100"), "100%", self, triggered=partial(self._set_raster_layer_value, 1),
        #                                                objectName="photo_100")
        dialog_actions = []
        for action in actions:
            icon = QIcon(action[0])
            qaction = QAction(icon,
                              action[1],
                              self,
                              triggered=action[2],
                              objectName=action[3])
            dialog_actions.append(qaction)

        dlg.addactions(dialog_actions)
        dlg.exec_()

    def _set_basemaps_opacity(self, value=0) -> None:
        """
        Set the opacity for all basemap raster layers.
        :param value: The opacity value betwen 0 and 1
        """
        tree = QgsProject.instance().layerTreeRoot()
        for node in tree.findLayers():
            layer = node.layer()
            if node.layer().type() == QgsMapLayer.RasterLayer:
                if value > 0:
                    node.setItemVisibilityChecked(Qt.Checked)
                    renderer = layer.renderer()
                    renderer.setOpacity(value)
                if value == 0:
                    node.setItemVisibilityChecked(Qt.Unchecked)

        self.canvas.refresh()

    def cleanup(self):
        """
        Clean up when the project has changed.
        :return:
        """
        # TODO Review cleanup
        # self.bridge.clear()
        self.gpsband.reset()
        self.gpsband.hide()
        self.clear_selection()
        self.clear_temp_objects()
        self.clear_capture_tools()
        for action in self.layerbuttons:
            self.editgroup.removeAction(action)
示例#4
0
class DocumentViewManager(QMainWindow):
    """
    MDI area for displaying supporting documents within a given context e.g.
    supporting documents for a specific household based on the lifetime of the
    'SourceDocumentManager' instance.
    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setWindowFlags(Qt.Window)

        self._mdi_area = QMdiArea()
        self._mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self._mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self._mdi_area)
        # set the size of mid_area and DocumentViewManager based on the
        # screen size.
        screen = QDesktopWidget().availableGeometry()
        self._mdi_area.resize(screen.width() - 30, screen.height() - 80)
        self.resize(self._mdi_area.size())
        self._mdi_area.subWindowActivated.connect(self.update_actions)
        self._viewer_mapper = QSignalMapper(self)
        self._viewer_mapper.mapped[QWidget].connect(self.set_active_sub_window)

        win_title = QApplication.translate("DocumentViewManager",
                                           "Document Viewer")
        self.setWindowTitle(win_title)
        self.setUnifiedTitleAndToolBarOnMac(True)
        self.statusBar().showMessage(
            QApplication.translate("DocumentViewManager", "Ready"))
        self._doc_viewers = {}

        self._create_menu_actions()
        self.update_actions()

    def center(self):
        """
        Move the Document viewer to the center of the screen.
        """
        # Get the current screens' dimensions...
        screen = QDesktopWidget().availableGeometry()
        # ... and get this windows' dimensions
        mdi_area_size = self.frameGeometry()
        # The horizontal position
        hpos = (screen.width() - mdi_area_size.width()) / 2
        # vertical position
        vpos = (screen.height() - mdi_area_size.height()) / 2
        # repositions the window
        self.move(hpos, vpos)

    def _create_menu_actions(self):
        self._window_menu = self.menuBar().addMenu(
            QApplication.translate("DocumentViewManager", "&Windows"))

        self._close_act = QAction(
            QApplication.translate("DocumentViewManager", "Cl&ose"), self)
        self._close_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Close the active document viewer"))
        self._close_act.triggered.connect(self._mdi_area.closeActiveSubWindow)

        self._close_all_act = QAction(
            QApplication.translate("DocumentViewManager", "Close &All"), self)
        self._close_all_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Close all the document viewers"))
        self._close_all_act.triggered.connect(
            self._mdi_area.closeAllSubWindows)

        self._tile_act = QAction(
            QApplication.translate("DocumentViewManager", "&Tile"), self)
        self._tile_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Tile the document viewers"))
        self._tile_act.triggered.connect(self.tile_windows)

        self._cascade_act = QAction(
            QApplication.translate("DocumentViewManager", "&Cascade"), self)
        self._cascade_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Cascade the document viewers"))
        self._cascade_act.triggered.connect(self.cascade_windows)

        self._next_act = QAction(
            QApplication.translate("DocumentViewManager", "Ne&xt"), self)
        self._next_act.setStatusTip(
            QApplication.translate(
                "DocumentViewManager",
                "Move the focus to the next document viewer"))
        self._next_act.triggered.connect(self._mdi_area.activateNextSubWindow)

        self._previous_act = QAction(
            QApplication.translate("DocumentViewManager", "Pre&vious"), self)
        self._previous_act.setStatusTip(
            QApplication.translate(
                "DocumentViewManager",
                "Move the focus to the previous document viewer"))
        self._previous_act.triggered.connect(
            self._mdi_area.activatePreviousSubWindow)

        self._separator_act = QAction(self)
        self._separator_act.setSeparator(True)

        self.update_window_menu()
        self._window_menu.aboutToShow.connect(self.update_window_menu)

    def cascade_windows(self):
        # Cascade document windows
        self._mdi_area.cascadeSubWindows()

    def tile_windows(self):
        # Arrange document windows to occupy the available space in mdi area
        self._mdi_area.tileSubWindows()

    def update_actions(self):
        if self._mdi_area.activeSubWindow():
            has_mdi_child = True

        else:
            has_mdi_child = False

        self._close_act.setEnabled(has_mdi_child)
        self._close_all_act.setEnabled(has_mdi_child)
        self._tile_act.setEnabled(has_mdi_child)
        self._cascade_act.setEnabled(has_mdi_child)
        self._previous_act.setEnabled(has_mdi_child)
        self._next_act.setEnabled(has_mdi_child)
        self._separator_act.setVisible(has_mdi_child)

    def update_window_menu(self):
        self._window_menu.clear()
        self._window_menu.addAction(self._close_act)
        self._window_menu.addAction(self._close_all_act)
        self._window_menu.addSeparator()
        self._window_menu.addAction(self._tile_act)
        self._window_menu.addAction(self._cascade_act)
        self._window_menu.addSeparator()
        self._window_menu.addAction(self._next_act)
        self._window_menu.addAction(self._previous_act)
        self._window_menu.addAction(self._separator_act)

        windows = self._mdi_area.subWindowList()
        self._separator_act.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            text = "%d. %s" % (i + 1, window.windowTitle())

            win_action = self._window_menu.addAction(text)
            win_action.setCheckable(True)
            win_action.setChecked(window is self._mdi_area.activeSubWindow())
            win_action.triggered.connect(self._viewer_mapper.map)
            self._viewer_mapper.setMapping(win_action, window)

    def load_viewer(self, document_widget, visible=True):
        """
        Open a new instance of the viewer or activate an existing one if the
        document had been previously loaded.
        :param document_widget: Contains all the necessary information required
        to load the specific document.
        :type document_widget: DocumentWidget
        :param visible: True to show the view manager after the viewer has
        been loaded, otherwise it will be the responsibility of the caller to
        enable visibility.
        :type visible: bool
        :returns: True if the document was successfully loaded, else False.
        :rtype: bool
        """
        doc_identifier = document_widget.file_identifier()

        if doc_identifier in self._doc_viewers:

            doc_sw = self._doc_viewers[doc_identifier]

            self._mdi_area.setActiveSubWindow(doc_sw)
            doc_sw.showNormal()

        else:
            abs_doc_path = self.absolute_document_path(document_widget)

            if not QFile.exists(abs_doc_path):
                msg = QApplication.translate(
                    "DocumentViewManager",
                    "The selected document does not exist."
                    "\nPlease check the supporting documents' "
                    "repository setting.")
                QMessageBox.critical(
                    self,
                    QApplication.translate("DocumentViewManager",
                                           "Invalid Document"), msg)

                return False

            file_info = QFileInfo(abs_doc_path)
            ext = file_info.suffix().lower()
            if ext == 'pdf':
                os.startfile(abs_doc_path)
                return True

            doc_viewer = self._create_viewer(document_widget)

            doc_viewer.load_document(abs_doc_path)

            self._doc_viewers[doc_identifier] = doc_viewer

            self._mdi_area.addSubWindow(doc_viewer)

            doc_viewer.show()

        if not self.isVisible() and visible:
            self.setVisible(True)

        if self.isMinimized():
            self.showNormal()

        self.center()

        return True

    def set_active_sub_window(self, viewer):
        if viewer:
            self._mdi_area.setActiveSubWindow(viewer)

    def absolute_document_path(self, document_widget):
        """
        Build the absolute document path using info from the document widget.
        :param document_widget: Instance of document widget.
        :return: Absolute path of the supporting document.
        :rtype: str
        """
        abs_path = ''

        file_manager = document_widget.fileManager
        if not file_manager is None:
            network_repository = file_manager.networkPath
            file_id = document_widget.file_identifier()
            source_entity = document_widget.doc_source_entity()
            profile_name = current_profile().name
            doc_type = document_widget.doc_type_value().lower().replace(
                ' ', '_')
            file_name, file_extension = guess_extension(
                document_widget.displayName())

            abs_path = network_repository + "/" + profile_name + '/' + \
                       str(source_entity) + "/" + str(doc_type) + "/" + \
                       str(file_id) + str(file_extension)

        return abs_path

    def reset(self):
        """
        Removes all document viewers in the view area.
        The QCloseEvent sent to each sub-window will decrement the register.
        """
        self._mdi_area.closeAllSubWindows()

    def _create_viewer(self, document_widget):
        """
        Creates a new instance of a document viewer.
        :param document_widget: Contains all
        the necessary information required
        to load the specific document.
        :return: Document viewer object
        :rtype: DocumentViewer
        """
        doc_viewer = DocumentViewer(self._mdi_area,
                                    document_widget.file_identifier())
        doc_viewer.setAttribute(Qt.WA_DeleteOnClose)
        doc_viewer.setWindowTitle(document_widget.displayName())

        # TODO: Incorporate logic for determining
        # TODO: viewer based on document type
        ph_viewer = PhotoViewer()

        # v_layout = QVBoxLayout()
        # v_layout.addWidget(ph_viewer)
        # doc_viewer.setLayout(v_layout)

        doc_viewer.set_view_widget(ph_viewer)

        doc_viewer.closed.connect(self._on_viewer_closed)

        return doc_viewer

    def remove_viewer(self, viewer_id):
        """
        Close and remove the viewer with the specified viewer ID.
        """
        if viewer_id in self._doc_viewers:
            viewer = self._doc_viewers[viewer_id]
            self._mdi_area.setActiveSubWindow(viewer)
            self._mdi_area.closeActiveSubWindow()

        self._on_viewer_closed(viewer_id)

    def _on_viewer_closed(self, file_id):
        """
        Slot raised when a document viewer is closed.
        """
        if file_id in self._doc_viewers:
            del self._doc_viewers[file_id]
示例#5
0
class XYZHubConnector(object):
    """base plugin"""
    def __init__(self, iface):
        """init"""
        import sys
        print(sys.version)
        self.iface = iface
        self.web_menu = "&XYZ Hub Connector"
        self.init_modules()
        self.obj = self

    def initGui(self):
        """startup"""

        parent = self.iface.mainWindow()

        ######## action, button

        icon = QIcon("%s/%s" % (config.PLUGIN_DIR, "images/xyz.png"))
        icon_bbox = QIcon("%s/%s" % (config.PLUGIN_DIR, "images/bbox.svg"))
        self.action_connect = QAction(icon, "New XYZ Hub Connection", parent)
        self.action_connect.setWhatsThis(
            QCoreApplication.translate(PLUGIN_NAME, "WhatsThis message"))
        self.action_connect.setStatusTip(
            QCoreApplication.translate(PLUGIN_NAME, "status tip message"))

        self.action_clear_cache = QAction("Clear cache", parent)
        self.action_upload = QAction("Upload to New XYZ Geospace", parent)
        self.action_basemap = QAction("Add HERE Map Tile", parent)

        self.action_magic_sync = QAction("Magic Sync (EXPERIMENTAL)", parent)
        self.action_manage = QAction("Manage XYZ Geospace (EXPERIMENTAL)",
                                     parent)
        self.action_edit = QAction("Edit/Delete XYZ Geospace (EXPERIMENTAL)",
                                   parent)

        if self.iface.activeLayer() is None:
            # self.action_upload.setEnabled(False)
            self.action_edit.setEnabled(False)
            self.action_magic_sync.setEnabled(False)

        # self.action_magic_sync.setVisible(False) # disable magic sync

        ######## CONNECT action, button

        self.action_connect.triggered.connect(self.open_connection_dialog)
        self.action_manage.triggered.connect(self.open_manage_dialog)
        self.action_edit.triggered.connect(self.open_edit_dialog)
        self.action_upload.triggered.connect(self.open_upload_dialog)
        self.action_magic_sync.triggered.connect(self.open_magic_sync_dialog)
        self.action_clear_cache.triggered.connect(self.open_clear_cache_dialog)
        self.action_basemap.triggered.connect(self.open_basemap_dialog)

        ######## Add the toolbar + button
        self.toolbar = self.iface.addToolBar(PLUGIN_NAME)
        self.toolbar.setObjectName("XYZ Hub Connector")

        tool_btn = QToolButton(self.toolbar)

        self.actions = [
            self.action_connect, self.action_upload, self.action_basemap,
            self.action_clear_cache
        ]  # , self.action_magic_sync, self.action_manage, self.action_edit
        for a in self.actions:
            tool_btn.addAction(a)
            self.iface.addPluginToWebMenu(self.web_menu, a)

        tool_btn.setDefaultAction(self.action_connect)
        tool_btn.setPopupMode(tool_btn.MenuButtonPopup)

        self.xyz_widget_action = self.toolbar.addWidget(tool_btn)

        self.action_help = None

        self.action_reload = QAction(icon_bbox, "Reload BBox", parent)
        self.action_reload.triggered.connect(self.layer_reload_bbox)
        self.action_reload.setVisible(False)  # disable
        self.toolbar.addAction(self.action_reload)

        progress = QProgressBar()
        progress.setMinimum(0)
        progress.setMaximum(0)
        progress.reset()
        progress.hide()
        # progress = self.iface.statusBarIface().children()[2] # will be hidden by qgis
        self.iface.statusBarIface().addPermanentWidget(progress)
        self.pb = progress

    def init_modules(self):
        # util.init_module()

        # parent = self.iface.mainWindow()
        parent = QgsProject.instance()

        ######## Init xyz modules
        self.map_basemap_meta = basemap.load_default_xml()
        self.auth_manager = AuthManager(config.PLUGIN_DIR + "/auth.ini")

        self.token_model = GroupTokenModel(parent)
        # self.layer = LayerManager(parent, self.iface)

        self.network = NetManager(parent)

        self.con_man = ControllerManager()
        self.layer_man = LayerManager()

        ######## data flow
        self.conn_info = SpaceConnectionInfo()

        ######## token
        print(config.PLUGIN_DIR)
        self.token_model.load_ini(config.PLUGIN_DIR + "/token.ini")

        ######## CALLBACK
        # self.iface.mapCanvas().extentsChanged.connect( self.debug_reload)
        # self.con_man.connect_ux( self.iface) # canvas ux
        # self.con_man.signal.canvas_span.connect( self.loader_reload_bbox)

        self.con_man.ld_pool.signal.progress.connect(
            self.cb_progress_busy)  #, Qt.QueuedConnection
        self.con_man.ld_pool.signal.finished.connect(self.cb_progress_done)

        QgsProject.instance().layersWillBeRemoved["QStringList"].connect(
            self.layer_man.remove)
        QgsProject.instance().layersWillBeRemoved["QStringList"].connect(
            self.con_man.remove)

        # self.iface.currentLayerChanged.connect( self.cb_layer_selected) # UNCOMMENT

        if DEBUG:
            QgsApplication.messageLog().messageReceived.connect(print_qgis)

    def unload_modules(self):
        # self.con_man.disconnect_ux( self.iface)
        QgsProject.instance().layersWillBeRemoved["QStringList"].disconnect(
            self.layer_man.remove)
        QgsProject.instance().layersWillBeRemoved["QStringList"].disconnect(
            self.con_man.remove)

        # utils.disconnect_silent(self.iface.currentLayerChanged)

        # self.con_man.unload()
        # del self.con_man

        # self.iface.mapCanvas().extentsChanged.disconnect( self.debug_reload)

        close_print_qgis()
        pass

    def unload(self):
        """teardown"""
        self.unload_modules()
        # remove the plugin menu item and icon
        self.iface.removePluginWebMenu(self.web_menu, self.action_help)

        self.toolbar.clear(
        )  # remove action from custom toolbar (toolbar still exist)
        self.toolbar.deleteLater()

        for a in self.actions:
            self.iface.removePluginWebMenu(self.web_menu, a)

    ###############
    # Callback
    ###############
    def cb_layer_selected(self, qlayer):
        flag_xyz = True if qlayer is not None and self.layer.is_xyz_supported_layer(
            qlayer) else False
        # disable magic sync
        # self.action_magic_sync.setEnabled(flag_xyz)
        flag_layer = True
        self.action_upload.setEnabled(flag_layer)
        self.action_edit.setEnabled(flag_layer)

    ###############
    # Callback of action (main function)
    ###############
    def cb_success_msg(self, msg, info=""):
        self.iface.messageBar().pushMessage(msg, info, Qgis.Success, 1)

    def make_cb_success(self, msg, info=""):
        def _cb_success_msg():
            txt = info
            self.cb_success_msg(msg, txt)

        return _cb_success_msg

    def cb_handle_error_msg(self, e):
        err = parse_exception_obj(e)
        if isinstance(err, ChainInterrupt):
            e0, idx = err.args[0:2]
            if isinstance(e0, net_handler.NetworkError):
                ok = self.show_net_err_dialog(e0)
                if ok: return
            elif isinstance(e0, loader.EmptyXYZSpaceError):
                ret = exec_warning_dialog(
                    "Warning", "Requested query returns no features")
        self.show_err_msgbar(err)

    def show_net_err_dialog(self, err):
        assert isinstance(err, net_handler.NetworkError)
        reply_tag, status, reason, body = err.args[:4]
        if reply_tag in ["count"]:  # too many error
            return 0

        msg = ("%s: %s\n" % (status, reason) +
               "There was a problem connecting to the server")
        if status == 403:
            msg += "\n\n" + "Please make sure that the token has WRITE permission"
        ret = exec_warning_dialog("Network Error", msg, body)
        return 1

    def show_err_msgbar(self, err):
        self.iface.messageBar().pushMessage(TAG_PLUGIN, repr(err),
                                            Qgis.Warning, 5)
        msg = format_traceback(err)
        QgsMessageLog.logMessage(msg, TAG_PLUGIN, Qgis.Warning)

    def cb_progress_busy(self, n_active):
        if n_active > 1: return
        self.flag_pb_show = True
        self.cb_progress_refresh()

    def cb_progress_done(self):
        self.flag_pb_show = False
        self.cb_progress_refresh()

    def cb_progress_refresh(self):
        if not hasattr(self, "flag_pb_show"): return

        pb = self.pb
        if self.flag_pb_show:
            pb.show()
            print_qgis("show", pb)
        else:
            pb.hide()
            print_qgis("hide")

    ###############
    # Action (main function)
    ###############
    def load_bbox(self, con, args):
        bbox = bbox_utils.extend_to_bbox(
            bbox_utils.get_bounding_box(self.iface))
        a, kw = parse_qt_args(args)
        kw["bbox"] = bbox
        kw["limit"] = 1000
        con.start(*a, **kw)

    def layer_reload_bbox(self):
        con_bbox_reload = ReloadLayerController_bbox(self.network)
        self.con_man.add(con_bbox_reload)
        # con_bbox_reload.signal.finished.connect( self.refresh_canvas, Qt.QueuedConnection)
        con_bbox_reload.signal.finished.connect(
            self.make_cb_success("Bounding box loading finish"))
        con_bbox_reload.signal.error.connect(self.cb_handle_error_msg)

        # TODO: set/get params from vlayer
        layer_id = self.iface.activeLayer().id()
        layer = self.layer_man.get(layer_id)
        self.load_bbox(con_bbox_reload, make_qt_args(layer))

    # UNUSED
    def debug_reload(self):
        print("debug_reload")

    def refresh_canvas(self):
        self.iface.mapCanvas().refresh()
        # assert False # debug unload module

    def previous_canvas_extent(self):
        self.iface.mapCanvas().zoomToPreviousExtent()

    def open_clear_cache_dialog(self):
        parent = self.iface.mainWindow()
        dialog = ConfirmDialog(
            parent, "Delete cache will make loaded layer unusable !!")
        ret = dialog.exec_()
        if ret != dialog.Ok: return

        utils.clear_cache()

    def open_connection_dialog(self):
        parent = self.iface.mainWindow()
        dialog = ConnectManageSpaceDialog(parent)
        dialog.config(self.token_model, self.conn_info)

        ############ edit btn

        con = EditSpaceController(self.network)
        self.con_man.add(con)
        con.signal.finished.connect(dialog.btn_use.clicked.emit)
        con.signal.error.connect(self.cb_handle_error_msg)
        dialog.signal_edit_space.connect(con.start_args)

        ############ delete btn

        con = DeleteSpaceController(self.network)
        self.con_man.add(con)
        con.signal.results.connect(dialog.btn_use.clicked.emit)
        con.signal.error.connect(self.cb_handle_error_msg)
        dialog.signal_del_space.connect(con.start_args)

        ############ Use Token btn

        con = LoadSpaceController(self.network)
        self.con_man.add(con)
        con.signal.results.connect(make_fun_args(dialog.cb_display_spaces))
        con.signal.error.connect(self.cb_handle_error_msg)
        con.signal.error.connect(lambda e: dialog.cb_enable_token_ui())
        con.signal.finished.connect(dialog.cb_enable_token_ui)
        dialog.signal_use_token.connect(con.start_args)

        ############ get statisitics
        con = StatSpaceController(self.network)
        self.con_man.add(con)
        con.signal.results.connect(make_fun_args(
            dialog.cb_display_space_count))
        con.signal.error.connect(self.cb_handle_error_msg)
        dialog.signal_space_count.connect(con.start_args)

        ############ TODO: bbox btn

        ############ connect btn
        con_load = loader.ReloadLayerController(self.network, n_parallel=2)
        self.con_man.add(con_load)
        # con_load.signal.finished.connect( self.refresh_canvas, Qt.QueuedConnection)
        con_load.signal.finished.connect(
            self.make_cb_success("Loading finish"))
        con_load.signal.error.connect(self.cb_handle_error_msg)

        dialog.signal_space_connect.connect(con_load.start_args)

        # con.signal.results.connect( self.layer_man.add_args) # IMPORTANT

        dialog.exec_()
        # self.startTime = time.time()

    def open_manage_dialog(self):
        pass

    def open_edit_dialog(self):
        pass

    def open_upload_dialog(self):
        vlayer = self.iface.activeLayer()
        parent = self.iface.mainWindow()
        dialog = UploadNewSpaceDialog(parent)
        dialog.config(self.token_model, self.network, vlayer)

        ############ Use Token btn
        con = LoadSpaceController(self.network)
        self.con_man.add(con)
        con.signal.results.connect(make_fun_args(
            dialog.cb_set_valid_token))  # finished signal !?
        con.signal.error.connect(self.cb_handle_error_msg)
        con.signal.finished.connect(dialog.cb_enable_token_ui)
        dialog.signal_use_token.connect(con.start_args)

        con_upload = UploadLayerController(self.network, n_parallel=2)
        self.con_man.add(con_upload)
        con_upload.signal.finished.connect(
            self.make_cb_success("Uploading finish"))
        con_upload.signal.error.connect(self.cb_handle_error_msg)

        con = InitUploadLayerController(self.network)
        self.con_man.add(con)

        dialog.signal_upload_new_space.connect(con.start_args)
        con.signal.results.connect(con_upload.start_args)
        con.signal.error.connect(self.cb_handle_error_msg)

        dialog.exec_()

    def open_magic_sync_dialog(self):
        pass

    def open_basemap_dialog(self):
        parent = self.iface.mainWindow()
        auth = self.auth_manager.get_auth()
        dialog = BaseMapDialog(parent)
        dialog.config(self.map_basemap_meta, auth)
        dialog.signal_add_basemap.connect(self.add_basemap_layer)

        dialog.exec_()

    def add_basemap_layer(self, args):
        a, kw = parse_qt_args(args)
        meta, app_id, app_code = a
        self.auth_manager.save(app_id, app_code)
        basemap.add_basemap_layer(meta, app_id, app_code)
示例#6
0
class DrawLine:
    def __init__(self, iface):
        self.iface = iface

        try:
            self.qgis_version = Qgis.QGIS_VERSION_INT
        except NameError:
            self.qgis_version = QGis.QGIS_VERSION_INT

        # we store geometry type
        self.Point, self.Line, self.Polygon = (
                [QgsWkbTypes.PointGeometry, QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry]
                if self.qgis_version >= 29900 else
                [QGis.Point, QGis.Line, QGis.Polygon])

        if QSettings().value('locale/overrideFlag', type=bool):
            locale = QSettings().value('locale/userLocale')
        else:
            locale = QLocale.system().name()
        if locale:
            locale_path = os.path.join(
                    os.path.dirname(__file__),
                    'i18n', locale)
            self.translator = QTranslator()
            if self.translator.load(locale_path):
                QCoreApplication.installTranslator(self.translator)

    def tr(self, message):
        return QCoreApplication.translate(self.__class__.__name__, message)


    def initGui(self):
        self.action = QAction(self.tr('Draw Line...'), self.iface.mainWindow())
        self.action.triggered.connect(self.run)

        if self.qgis_version >= 29900:
            self.iface.addCustomActionForLayerType(self.action, None, QgsMapLayer.VectorLayer, True)
        else:
            self.iface.legendInterface().addLegendLayerAction(self.action, None, self.__class__.__name__, QgsMapLayer.VectorLayer, True)

        self.iface.layerTreeView().clicked.connect(self.layer_tree_view_clicked)

    def unload(self):
        self.iface.layerTreeView().clicked.disconnect(self.layer_tree_view_clicked)
        if self.qgis_version >= 29900:
            self.iface.removeCustomActionForLayerType(self.action)
        else:
            self.iface.legendInterface().removeLegendLayerAction(self.action)

    # invoked when layer tree view clicked
    def layer_tree_view_clicked(self):
        layers = self.iface.layerTreeView().selectedLayers()

        if len(layers) == 1:
            self.action.setVisible(True)

            layer = layers[0]

            if layer.__class__.__name__ == 'QgsVectorLayer':
                dp = layer.dataProvider()
                self.action.setEnabled(bool(dp.capabilities() & dp.ChangeAttributeValues) and (not layer.readOnly()))
        else:
            self.action.setVisible(False)


    # invoked when our action triggered
    def run(self):
        QgsMessageLog.logMessage(self.tr('run'), 'DrawLine')

        layer = self.iface.layerTreeView().currentLayer()

        # only support polygon layer
        if layer.geometryType() not in [self.Polygon]:
            self.iface.messageBar().pushCritical(self.tr('Unsupported geometry type'), layer.name())
            return

        self.dialog = DrawLineDialog()

        # show dialog
        self.dialog.show()

        result = self.dialog.exec_()

        if result:
            self.do_logic(layer)

        self.dialog.deleteLater()


    def do_logic(self, layer):
        is_regenerate_front1 = self.dialog.check_box_regenerate_front1.isChecked()
        is_regenerate_front2 = self.dialog.check_box_regenerate_front2.isChecked()
        is_regenerate_rear = self.dialog.check_box_regenerate_rear.isChecked()

        if is_regenerate_front1:
            self.remove_layer_by_name('LINES_FRONT_SET1')

        if is_regenerate_front2:
            self.remove_layer_by_name('LINES_FRONT_SET2')

        if is_regenerate_rear:
            self.remove_layer_by_name('LINES_REAR_SET')

        front_lines_layer1 = QgsVectorLayer("LineString", "LINES_FRONT_SET1", "memory")
        front_lines_layer2 = QgsVectorLayer("LineString", "LINES_FRONT_SET2", "memory")

        rear_lines_layer = QgsVectorLayer("LineString", "LINES_REAR_SET", "memory")

        front_rotate_angle1 = float(self.dialog.line_edit_front_rotate_angle1.text())
        front_distance1 = float(self.dialog.line_edit_front_distance1.text())

        front_rotate_angle2 = float(self.dialog.line_edit_front_rotate_angle2.text())
        front_distance2 = float(self.dialog.line_edit_front_distance2.text())

        rear_rotate_angle = float(self.dialog.line_edit_rear_rotate_angle.text())
        rear_distance = float(self.dialog.line_edit_rear_distance.text())

        features = layer.getFeatures()

        for f in features:
            geometry = f.geometry()

            if layer.wkbType() == QgsWkbTypes.CurvePolygon or layer.wkbType() == QgsWkbTypes.CurvePolygon:
                polygon = geometry.asPolygon()
            elif layer.wkbType() == QgsWkbTypes.MultiPolygon:
                multi_polygon = geometry.asMultiPolygon()
                polygon = multi_polygon[0]

            # Polygon: first item of the list is outer ring, inner rings (if any) start from second item */
            # typedef QVector<QgsPolyline> QgsPolygon;

            polyline_count = len(polygon)

            if polyline_count == 0:
                continue

            # this is polyline

            outer_ring = polygon[0]

            count_of_point = len(outer_ring)

            if count_of_point != 5:
                continue

            first_point = outer_ring[0]
            second_point = outer_ring[1]
            third_point = outer_ring[2]
            fourth_point = outer_ring[3]

            # front1
            p1 = self.get_rotated_point(first_point, second_point, front_rotate_angle1, front_distance1)
            p2 = self.get_rotated_point(second_point, first_point, -front_rotate_angle1, front_distance1)

            self.add_line_to_layer(front_lines_layer1, first_point, p1)
            self.add_line_to_layer(front_lines_layer1, second_point, p2)

            # front2
            p1 = self.get_rotated_point(first_point, second_point, front_rotate_angle2, front_distance2)
            p2 = self.get_rotated_point(second_point, first_point, -front_rotate_angle2, front_distance2)

            self.add_line_to_layer(front_lines_layer2, first_point, p1)
            self.add_line_to_layer(front_lines_layer2, second_point, p2)

            # rear

            p3 = self.get_rotated_point(third_point, fourth_point, rear_rotate_angle, rear_distance)
            p4 = self.get_rotated_point(fourth_point, third_point, -rear_rotate_angle, rear_distance)

            self.add_line_to_layer(rear_lines_layer, third_point, p3)
            self.add_line_to_layer(rear_lines_layer, fourth_point, p4)

        QgsProject.instance().addMapLayer(front_lines_layer1)
        QgsProject.instance().addMapLayer(front_lines_layer2)
        QgsProject.instance().addMapLayer(rear_lines_layer)

    @staticmethod
    def add_line_to_layer(line_layer, start_point, end_point):
        # create a new feature
        """

        :param line_layer:
        :type line_layer QgsVectorLayer
        :param start_point:
        :type  start_point QgsPoint
        :param end_point:
        :type end_point QgsPoint
        """

        first_point_type = start_point.__class__.__name__

        # why
        # start_point 's type maybe is QgsPointXY so it result in bugs

        if first_point_type != 'QgsPoint':
            start_point = QgsPoint(start_point.x(), start_point.y())

        feature = QgsFeature()
        # noinspection PyCallByClass
        line = QgsGeometry.fromPolyline([start_point, end_point])

        feature.setGeometry(line)

        # add the feature to the layer
        data_provider = line_layer.dataProvider()
        data_provider.addFeatures( [ feature ] )

        line_layer.updateExtents()


    @staticmethod
    def get_rotated_point(first_point, second_point, rotate_angle, distance):
        """

        :param first_point:
        :type first_point QgsPoint
        :param second_point:
        :type second_point QgsPoint
        :param rotate_angle: in degree
        :type rotate_angle float
        :param distance:
        :type distance float
        """

        rotate_angle = math.radians(rotate_angle)

        # from https://math.stackexchange.com/questions/1964905/rotation-around-non-zero-point
        # rotate second point around first point by angle

        rotated_x = first_point.x() + (second_point.x() - first_point.x()) * math.cos(rotate_angle) - (second_point.y() - first_point.y()) * math.sin(rotate_angle)
        rotated_y = first_point.y() + (second_point.x() - first_point.x()) * math.sin(rotate_angle) + (second_point.y() - first_point.y()) * math.cos(rotate_angle)

        # from https://math.stackexchange.com/questions/175896/finding-a-point-along-a-line-a-certain-distance-away-from-another-point

        length = math.sqrt( (first_point.x() - second_point.x()) * (first_point.x() - second_point.x()) + (first_point.y() - second_point.y()) * (first_point.y() - second_point.y()))

        ratio = distance / length

        x = ( 1 - ratio ) * first_point.x() + ratio * rotated_x
        y = ( 1 - ratio ) * first_point.y() + ratio * rotated_y

        x = first_point.x() + ratio * (rotated_x - first_point.x())
        y = first_point.y() + ratio * (rotated_y - first_point.y())

        return QgsPoint(x, y)

    @staticmethod
    def remove_layer_by_name(layer_name):
        layers = QgsProject.instance().mapLayersByName(layer_name)

        for layer in layers:
             QgsProject.instance().removeMapLayer(layer.id())


    def property_changed(self):
        if self.dialog.comboBox_property.currentIndex() == -1:
            return
示例#7
0
class QuickFinder(QObject):

    name = "&Quick Finder"
    actions = None
    toolbar = None
    finders = {}

    loadingIcon = None

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.actions = {}
        self.finders = {}
        self.settings = MySettings()
        self.rubber = None

        self._init_finders()

        self.iface.projectRead.connect(self._reload_finders)
        self.iface.newProjectCreated.connect(self._reload_finders)

        # translation environment
        self.plugin_dir = os.path.dirname(__file__)
        locale = QSettings().value("locale/userLocale")[0:2]
        localePath = os.path.join(self.plugin_dir, 'i18n',
                                  'quickfinder_{0}.qm'.format(locale))
        if os.path.exists(localePath):
            self.translator = QTranslator()
            self.translator.load(localePath)
            QCoreApplication.installTranslator(self.translator)

    def initGui(self):
        self.actions['showSettings'] = QAction(
            QIcon(":/plugins/quickfinder/icons/settings.svg"),
            self.tr(u"&Settings"), self.iface.mainWindow())
        self.actions['showSettings'].triggered.connect(self.show_settings)
        self.iface.addPluginToMenu(self.name, self.actions['showSettings'])
        self._init_toolbar()

        # set selection area sytle and color
        self.rubber = QgsRubberBand(self.iface.mapCanvas())
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def unload(self):
        """ Unload plugin """
        for key in list(self.finders.keys()):
            self.finders[key].close()
        for action in self.actions.values():
            self.iface.removePluginMenu(self.name, action)
        if self.toolbar:
            del self.toolbar
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def _init_toolbar(self):
        """setup the plugin toolbar."""
        self.toolbar = self.iface.addToolBar(self.name)
        self.toolbar.setObjectName('mQuickFinderToolBar')
        self.search_action = QAction(
            QIcon(":/plugins/quickfinder/icons/magnifier13.svg"),
            self.tr("Search"), self.toolbar)
        self.stop_action = QAction(
            QIcon(":/plugins/quickfinder/icons/wrong2.svg"), self.tr("Cancel"),
            self.toolbar)
        self.finder_box = FinderBox(self.finders, self.iface, self.toolbar)
        self.finder_box.search_started.connect(self.search_started)
        self.finder_box.search_finished.connect(self.search_finished)

        self.finder_box_action = self.toolbar.addWidget(self.finder_box)
        self.finder_box_action.setVisible(True)
        self.search_action.triggered.connect(self.finder_box.search)
        self.toolbar.addAction(self.search_action)
        self.stop_action.setVisible(False)
        self.stop_action.triggered.connect(self.finder_box.stop)
        self.toolbar.addAction(self.stop_action)
        self.toolbar.setVisible(True)

    def _init_finders(self):
        #        self.finders['geomapfish'] = GeomapfishFinder(self)
        self.finders['osm'] = OsmFinder(self)
        self.finders['project'] = ProjectFinder(self)
        self.finders['amap'] = AmapFinder(self)
        #        self.finders['postgis'] = PostgisFinder(self)
        for key in list(self.finders.keys()):
            self.finders[key].message.connect(self.display_message)
        self.refresh_project()

    def _reload_finders(self):
        for key in list(self.finders.keys()):
            self.finders[key].close()
            self.finders[key].reload()
        self.refresh_project()

    @pyqtSlot(str, Qgis.MessageLevel)
    def display_message(self, message, level):
        self.iface.messageBar().pushMessage("QuickFinder", message, level)

    def show_settings(self):
        #        self.win = ConfigurationDialog()
        #        self.win.show()
        #ConfigurationDialog().show()
        if ConfigurationDialog().exec_():
            self._reload_finders()

    def search_started(self):
        self.search_action.setVisible(False)
        self.stop_action.setVisible(True)

    def search_finished(self):
        self.search_action.setVisible(True)
        self.stop_action.setVisible(False)

    def refresh_project(self):
        if not self.finders['project'].activated:
            return
        if not self.settings.value("refreshAuto"):
            return
        n_days = self.settings.value("refreshDelay")
        # do not ask more ofen than 3 days
        ask_limit = min(3, n_days)
        recently_asked = self.settings.value(
            "refreshLastAsked") >= n_days_ago_iso_date(ask_limit)
        if recently_asked:
            return
        thresh_date = n_days_ago_iso_date(n_days)
        uptodate = True
        for search in list(self.finders['project'].searches.values()):
            if search.dateEvaluated <= thresh_date:
                uptodate = False
                break
        if uptodate:
            return
        self.settings.setValue("refreshLastAsked", n_days_ago_iso_date(0))
        ret = QMessageBox(
            QMessageBox.Warning, "Quick Finder",
            QCoreApplication.translate(
                "Auto Refresh",
                "Some searches are outdated. Do you want to refresh them ?"),
            QMessageBox.Cancel | QMessageBox.Yes).exec_()
        if ret == QMessageBox.Yes:
            RefreshDialog(self.finders['project']).exec_()