Example #1
0
class ShapeTool(QgsMapTool):
    #signal emitted when the mouse is clicked. This indicates that the tool finished its job
    toolFinished = pyqtSignal()
    def __init__(self, canvas, geometryType, param, type, color = QColor( 254, 178, 76, 63 )):
        """
        Constructor
        """
        QgsMapTool.__init__(self, canvas)
        self.canvas = canvas
        self.active = False
        self.geometryType = self.tr(geometryType)
        self.param=param
        self.type=type       
        self.cursor=None
        self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)     
        self.setColor(color)
        self.reset()
        self.rotAngle = 0
        self.currentCentroid = None
        self.rotate = False
    
    def setColor(self, mFillColor):
        """
        Adjusting the color to create the rubber band
        """
    
        self.rubberBand.setColor(mFillColor)
        self.rubberBand.setWidth(1)
    
    def reset(self):
        """
        Resetting the rubber band
        """
        self.startPoint = self.endPoint = None
        self.isEmittingPoint = False
        try:
            self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
        except:
            pass

    def rotateRect(self, centroid, e):
        """
        Calculates the angle for the rotation.
        """
        item_position = self.canvas.mapToGlobal(e.pos())
        c = self.toCanvasCoordinates(centroid)
        c = self.canvas.mapToGlobal(c)
        rotAngle = pi - atan2(
            item_position.y() - c.y(), item_position.x() - c.x())
        return rotAngle

    def canvasPressEvent(self, e):
        """
        When the canvas is pressed the tool finishes its job
        """
        # enforce mouse restoring if clicked right after rotation 
        QApplication.restoreOverrideCursor()
        self.canvas.unsetMapTool(self)
        self.toolFinished.emit()

    def _baseDistanceInMeters(self):
        """
        Calculates the distance in meters of 2 points 1 unit map away on
        current canvas CRS.
        :return: (float) distance in meters between two points 1 map unit apart
                 from each other.
        """
        source_crs = self.canvas.mapSettings().destinationCrs()
        dest_crs = QgsCoordinateReferenceSystem(3857)
        tr = QgsCoordinateTransform(
            source_crs, dest_crs, QgsCoordinateTransformContext())
        p1t = QgsGeometry().fromPointXY(QgsPointXY(1, 0))
        p1t.transform(tr)
        p2t = QgsGeometry().fromPointXY(QgsPointXY(0, 0))
        p2t.transform(tr)
        return QgsDistanceArea().measureLine(p1t.asPoint(), p2t.asPoint())

    def getAdjustedSize(self, size):
        """
        If map unit is not metric, the figure to be drawn needs to have its
        size adjusted. This is necessary because input parameters are designed
        to be meters on tool's GUI.
        :param size: (float) tool's radius/length reference size in meters.
        :return: (float)  
        """
        source_crs = self.canvas.mapSettings().destinationCrs()
        if source_crs.mapUnits() != QgsUnitTypes.DistanceMeters:
            return size / self._baseDistanceInMeters()
        return size

    def canvasMoveEvent(self, e):
        """
        Deals with mouse move event to update the rubber band position in the canvas
        """
        ctrlIsHeld = QApplication.keyboardModifiers() == Qt2.ControlModifier
        if e.button() != None and not ctrlIsHeld:
            if self.rotate:
                # change rotate status
                self.rotate = False
            QApplication.restoreOverrideCursor()
            self.endPoint = self.toMapCoordinates( e.pos() )
        elif e.button() != None and ctrlIsHeld \
            and self.geometryType == self.tr(u"Square"):
            # calculate angle between mouse and last rubberband centroid before holding control
            self.rotAngle = self.rotateRect(self.currentCentroid, e)
            if not self.rotate:
                # only override mouse if it is not overriden already
                QApplication.setOverrideCursor(QCursor(Qt2.BlankCursor))
                self.rotate = True
        if self.geometryType == self.tr(u"Circle"):
                self.showCircle(self.endPoint)
        elif self.geometryType == self.tr(u"Square"):
            self.showRect(self.endPoint, sqrt(self.param)/2, self.rotAngle)
    
    def showCircle(self, startPoint):
        """
        Draws a circle in the canvas
        """
        nPoints = 50
        x = startPoint.x()
        y = startPoint.y()
        if self.type == self.tr('distance'):
            r = self.getAdjustedSize(self.param)
            self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
            for itheta in range(nPoints+1):
                theta = itheta*(2.0*pi/nPoints)
                self.rubberBand.addPoint(QgsPointXY(x+r*cos(theta), y+r*sin(theta)))
            self.rubberBand.show()
        else:
            r = self.getAdjustedSize(sqrt(self.param/pi))
            self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
            for itheta in range(nPoints+1):
                theta = itheta*(2.0*pi/nPoints)
                self.rubberBand.addPoint(QgsPointXY(x+r*cos(theta), y+r*sin(theta)))
            self.rubberBand.show()

    def showRect(self, startPoint, param, rotAngle=0):
        """
        Draws a rectangle in the canvas
        """  
        self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
        x = startPoint.x() # center point x
        y = startPoint.y() # center point y
        # rotation angle is always applied in reference to center point
        # to avoid unnecessary calculations
        c = cos(rotAngle)
        s = sin(rotAngle)
        # translating coordinate system to rubberband centroid
        param = self.getAdjustedSize(param)
        for posx, posy in ((-1, -1), (-1, 1), (1, 1), (1, -1)):
            px = posx * param
            py = posy * param
            pnt = QgsPointXY(px * c - py * s + x, py * c + px * s + y)
            self.rubberBand.addPoint(pnt, False)
        self.rubberBand.setVisible(True)
        self.rubberBand.updateRect()
        self.rubberBand.update()
        self.rubberBand.show()
        self.currentCentroid = startPoint
        
    def deactivate(self):
        """
        Deactivates the tool and hides the rubber band
        """
        self.rubberBand.hide()
        QgsMapTool.deactivate(self)
        # restore mouse in case tool is disabled right after rotation
        QApplication.restoreOverrideCursor()
        
    def activate(self):
        """
        Activates the tool
        """
        QgsMapTool.activate(self)
class MoveFeatureTool(QgsMapTool):
    def __init__(self, mission_track, canvas):
        QgsMapTool.__init__(self, canvas)
        self.band = None
        self.feature = None
        self.startcoord = None
        self.mission_track = mission_track
        self.layer = mission_track.get_mission_layer()
        self.clicked_outside_layer = False
        self.mCtrl = False
        self.rot_center = None
        self.rot_center_rb = None
        self.ini_rot_point = None
        self.last_rot_angle = 0.0
        self.curr_angle = 0.0
        self.distance = QgsDistanceArea()
        self.distance.setSourceCrs(QgsCoordinateReferenceSystem(4326), QgsProject.instance().transformContext())
        self.distance.setEllipsoid('WGS84')
        self.ini_geom = next(self.layer.dataProvider().getFeatures()).geometry()
        logger.info(mission_track.get_mission_name())

    def canvasMoveEvent(self, event):
        """
        Override of QgsMapTool mouse move event
        """
        if self.band and not self.mCtrl:
            point = self.toMapCoordinates(event.pos())
            offset_x = point.x() - self.startcoord.x()
            offset_y = point.y() - self.startcoord.y()
            self.band.setTranslationOffset(offset_x, offset_y)
            self.band.updatePosition()
            self.band.update()

        if self.band and self.mCtrl:
            end_rot_point = self.toMapCoordinates(event.pos())
            self.curr_angle = self.distance.bearing(self.rot_center, end_rot_point) \
                        - self.distance.bearing(self.rot_center, self.ini_rot_point)\
                        + self.last_rot_angle
            self.rotate_and_project_band(self.curr_angle)

    def canvasPressEvent(self, event):
        """
        Override of QgsMapTool mouse press event
        """

        if event.button() == Qt.LeftButton and not self.mCtrl:

            if self.band:
                self.band.hide()
                self.band = None
            self.feature = None

            logger.info("layer feature count {}".format(self.layer.featureCount()))
            if not self.layer:
                return

            logger.info("Trying to find feature in layer")
            point = self.toLayerCoordinates(self.layer, event.pos())
            search_radius = (QgsTolerance.toleranceInMapUnits(10, self.layer,
                                                              self.canvas().mapSettings(), QgsTolerance.Pixels))

            rect = QgsRectangle()
            rect.setXMinimum(point.x() - search_radius)
            rect.setXMaximum(point.x() + search_radius)
            rect.setYMinimum(point.y() - search_radius)
            rect.setYMaximum(point.y() + search_radius)

            rq = QgsFeatureRequest().setFilterRect(rect)

            f = QgsFeature()
            self.layer.getFeatures(rq).nextFeature(f)
            if f.geometry():
                self.band = self.create_rubber_band()
                self.band.setToGeometry(f.geometry(), self.layer)
                self.band.show()
                self.startcoord = self.toMapCoordinates(event.pos())
                self.feature = f
                self.clicked_outside_layer = False
                return
            else:
                self.clicked_outside_layer = True

    def canvasReleaseEvent(self, event):
        """
        Override of QgsMapTool mouse release event
        """
        if event.button() == Qt.LeftButton and not self.mCtrl:
            if not self.band:
                if self.clicked_outside_layer and len(self.mission_track.find_waypoints_in_mission()) > 0:
                    confirmation_msg = "Do you want to move the mission ? \n\n" \
                                       "First waypoint will set on the marked point."
                    reply = QMessageBox.question(self.parent(), 'Movement Confirmation',
                                                 confirmation_msg, QMessageBox.Yes, QMessageBox.No)

                    if reply == QMessageBox.Yes:
                        feats = self.layer.getFeatures()
                        for f in feats:  # will be only one
                            if self.layer.geometryType() == QgsWkbTypes.LineGeometry:
                                list_wp = f.geometry().asPolyline()
                                self.startcoord = self.toLayerCoordinates(self.layer, list_wp[0])
                            elif self.layer.geometryType() == QgsWkbTypes.PointGeometry:
                                wp = f.geometry().asPoint()
                                self.startcoord = self.toLayerCoordinates(self.layer, wp)
                            self.feature = f
                        self.move_position(event.pos())
                return

            if not self.layer:
                return

            if not self.feature:
                return

            self.move_position(event.pos())

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Control and self.band is None \
                and len(self.mission_track.find_waypoints_in_mission()) > 1:
            self.mCtrl = True
            self.show_rotation_center()
            self.show_rubber_band()
            self.rotate_and_project_band(self.last_rot_angle)
            self.ini_rot_point = self.toMapCoordinates(self.canvas().mouseLastXY())

    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_Control and self.mCtrl:
            self.mCtrl = False
            self.rotate_and_project_mission()
            self.hide_rotation_center()
            self.hide_rubber_band()
            self.last_rot_angle = self.curr_angle

    def move_position(self, pos):
        start_point = self.toLayerCoordinates(self.layer, self.startcoord)
        end_point = self.toLayerCoordinates(self.layer, pos)

        # Find vertical distance to be translated
        a = self.distance.bearing(start_point, end_point)
        d = self.distance.measureLine(start_point, end_point)
        vertical_dist = abs(cos(a) * d)

        # If translating a point or if translation is small,
        # do a simple translation (very small error, proportional to vertical dist)
        if vertical_dist < 9000 or self.layer.geometryType() == QgsWkbTypes.PointGeometry:
            dx = end_point.x() - start_point.x()
            dy = end_point.y() - start_point.y()
            self.layer.startEditing()
            self.layer.translateFeature(self.feature.id(), dx, dy)
            self.layer.commitChanges()

        # If translation is big, translate and project (small and constant error due to approximations in calculations)
        else:
            ini_coords = next(self.layer.dataProvider().getFeatures()).geometry().asPolyline()
            end_coords = []
            if len(ini_coords) > 1:
                dx = end_point.x() - start_point.x()
                dy = end_point.y() - start_point.y()
                end_c = QgsPointXY(ini_coords[0].x() + dx, ini_coords[0].y() + dy)
                end_coords.append(end_c)
                for i in range(1, len(ini_coords)):
                    dist = self.distance.measureLine(ini_coords[i-1], ini_coords[i])
                    angle = self.distance.bearing(ini_coords[i-1], ini_coords[i])
                    end_c = endpoint(end_coords[i-1], dist, degrees(angle))
                    end_coords.append(end_c)

                feature = next(self.layer.dataProvider().getFeatures())
                self.layer.startEditing()
                self.layer.changeGeometry(feature.id(), QgsGeometry.fromPolylineXY(end_coords))
                self.layer.commitChanges()

        if self.band is not None:
            self.band.hide()
            self.band = None

        # get new waypoints and put them into mission structure
        feats = self.layer.getFeatures()
        for f in feats:  # will be only one
            if self.layer.geometryType() == QgsWkbTypes.LineGeometry:
                list_wp = f.geometry().asPolyline()
                for wp in range(0, len(list_wp)):
                    point = list_wp[wp]
                    self.mission_track.change_position(wp, point)
            elif self.layer.geometryType() == QgsWkbTypes.PointGeometry:
                wp = f.geometry().asPoint()
                self.mission_track.change_position(0, wp)

        # rotation center and geometry have changed, set rot_center to none to recalculate when needed
        self.rot_center = None
        self.last_rot_angle = 0.0
        self.ini_geom = next(self.layer.dataProvider().getFeatures()).geometry()

    def deactivate(self):
        """
        Deactive the tool.
        """
        self.hide_rotation_center()
        self.hide_rubber_band()

    def create_rubber_band(self):
        """
        Creates a new rubber band.
        """
        band = QgsRubberBand(self.canvas())
        band.setColor(QColor("green"))
        band.setWidth(2)
        band.setLineStyle(Qt.DashLine)
        return band

    def show_rotation_center(self):
        """
        Shows rotation center of the mission with a point rubber band
        """
        if len(self.mission_track.find_waypoints_in_mission()) > 1:
            feature = next(self.layer.dataProvider().getFeatures())
            list_wp = feature.geometry().asPolyline()
            if self.rot_center is None or self.rot_center_rb is None:
                self.rot_center = self.find_geometric_center(list_wp)
                self.rot_center_rb = QgsRubberBand(self.canvas())
                self.rot_center_rb.setColor(QColor("black"))
                self.rot_center_rb.setWidth(3)
                self.rot_center_rb.setToGeometry(QgsGeometry.fromPointXY(self.rot_center), None)
                self.rot_center_rb.update()
            else:
                self.rot_center_rb.show()

    def hide_rotation_center(self):
        """
        Hides the rotation center of the mission and deletes the point rubber band
        """
        if self.rot_center_rb:
            self.rot_center_rb.hide()
            # self.rot_center_rb = None

    def find_geometric_center(self, list_wp):
        """
        Finds geometric center from a list of waypoints

        :param list_wp: list of waypoints
        :return: geometric center of the list of waypoints
        """
        center = QgsPointXY()
        max_x = None
        min_x = None
        max_y = None
        min_y = None

        # Geometric center
        for i in range(0, len(list_wp)):
            point = list_wp[i]
            if max_x is None or point.x() > max_x:
                max_x = point.x()
            if min_x is None or point.x() < min_x:
                min_x = point.x()
            if max_y is None or point.y() > max_y:
                max_y = point.y()
            if min_y is None or point.y() < min_y:
                min_y = point.y()

        center.setX((max_x + min_x)/2)
        center.setY((max_y + min_y)/2)

        return center

    def show_rubber_band(self):
        """
        Creates and shows a rubber band with the geometry of the mission
        """
        if len(self.mission_track.find_waypoints_in_mission()) > 1:
            self.band = self.create_rubber_band()
            self.band.setToGeometry(self.ini_geom, self.layer)

    def hide_rubber_band(self):
        """
        Hides and deletes the rubber band of the geometry of the mission
        """
        if self.band:
            self.band.hide()
            self.band = None

    def rotate_and_project_band(self, rot_angle=0.0):
        """
        Rebuilds the initial geometry of the mission rotated with the angle defined by rot_angle and it gets stored in
        the geometry of self.band

        :param rot_angle: Angle used to rotate the geometry
        """
        ini_coords = self.ini_geom.asPolyline()
        end_coords = []
        if len(ini_coords) > 1:
            dist = self.distance.measureLine(self.rot_center, ini_coords[0])
            angle = self.distance.bearing(self.rot_center, ini_coords[0]) + rot_angle
            end_first_wp = endpoint(self.rot_center, dist, degrees(angle))
            end_coords.append(end_first_wp)
            for i in range(1, len(ini_coords)):
                dist = self.distance.measureLine(ini_coords[i-1], ini_coords[i])
                angle = self.distance.bearing(ini_coords[i-1], ini_coords[i]) + rot_angle
                end_c = endpoint(end_coords[i-1], dist, degrees(angle))
                end_coords.append(end_c)

            end_band_geom = QgsGeometry().fromPolylineXY(end_coords)
            self.band.setToGeometry(end_band_geom, self.layer)

    def rotate_and_project_mission(self):
        """
        Copy the changes from the rotated and projected rubber band to the geometry of the mission
        """
        list_wp = self.band.asGeometry().asPolyline()
        if len(list_wp) > 1:
            for i in range(0, len(list_wp)):
                point = list_wp[i]
                self.mission_track.change_position(i, point)
            feature = next(self.layer.dataProvider().getFeatures())
            self.layer.startEditing()
            self.layer.changeGeometry(feature.id(), self.band.asGeometry())
            self.layer.commitChanges()
Example #3
0
class MoveLandmarkTool(QgsMapTool):
    def __init__(self, canvas):
        super().__init__(canvas)
        self.lm_layer = None
        self.lm_point = None
        self.clicked_on_landmark = False
        self.band = None

    def set_landmark_layer(self, layer):
        """Sets current land mark layer"""
        self.lm_layer = layer

    def canvasPressEvent(self, event):
        """
        If pressEvent point is near the layer point, enable dragging. Else show msg box
        """

        if event.button() == Qt.LeftButton:
            click_point = self.toLayerCoordinates(self.lm_layer, event.pos())
            if self.lm_layer.featureCount() == 1:
                feature_list = self.lm_layer.dataProvider().getFeatures(
                )  #Returns iterator to a list of one feature
                self.lm_feature = next(
                    feature_list)  #get first and only element in the list
                self.lm_point = self.lm_feature.geometry().asPoint()
                dist = QgsPointXY.distance(click_point, self.lm_point)
                tolerance = (QgsTolerance.toleranceInMapUnits(
                    15, self.lm_layer,
                    self.canvas().mapSettings(), QgsTolerance.Pixels))

                if dist < tolerance:
                    #Clicked on a landmark
                    self.clicked_on_landmark = True
                    self.create_ghost_point()

                else:
                    #Not clicked on a landmark
                    confirmation_msg = "Do you want to move {} here? \n\n".format(
                        self.lm_layer.name())
                    reply = QMessageBox.question(self.parent(),
                                                 'Movement Confirmation',
                                                 confirmation_msg,
                                                 QMessageBox.Yes,
                                                 QMessageBox.No)
                    if reply == QMessageBox.Yes:
                        self.move_position(click_point)

    def canvasReleaseEvent(self, event):
        """
        If dragging, stop and move landmark position to the release event point
        Also destroy rubberband (ghost point)
        """
        if (event.button() == Qt.LeftButton and self.lm_layer
                and self.clicked_on_landmark):
            release_point = self.toLayerCoordinates(self.lm_layer, event.pos())
            self.move_position(release_point)
            self.clicked_on_landmark = False
            if self.band:
                self.band.hide()
                self.band = None

    def move_position(self, end_point):
        """
        Translates landmark layer to end_point
        :param end_point: Where to move the landmark point
        """
        # If layer is not stored in memory, change its datasource to memory
        if self.lm_layer.source()[0] == "/":
            temp_feature = next(self.lm_layer.dataProvider().getFeatures()
                                )  # iterator with only 1 feature in list
            options = QgsDataProvider.ProviderOptions()
            self.lm_layer.setDataSource("Point?crs=epsg:4326",
                                        self.lm_layer.name(), "memory",
                                        options)
            self.lm_layer.dataProvider().addFeatures([temp_feature])
            self.lm_feature = next(self.lm_layer.dataProvider().getFeatures())

        dx = end_point.x() - self.lm_point.x()
        dy = end_point.y() - self.lm_point.y()
        self.lm_layer.startEditing()
        self.lm_layer.translateFeature(self.lm_feature.id(), dx, dy)
        self.lm_layer.commitChanges()

    def create_ghost_point(self):
        """
        Creates rubber band as a ghost image of the landmark point
        """
        if self.band is not None:
            self.band.hide()
            self.band = None
        self.band = QgsRubberBand(self.canvas())
        self.band.setColor(QColor("green"))
        self.band.setToGeometry(self.lm_feature.geometry(), self.lm_layer)
        self.band.show()

    def canvasMoveEvent(self, event):
        """
        If dragging, move ghost point
        """
        if self.clicked_on_landmark:
            point = self.toLayerCoordinates(self.lm_layer, event.pos())
            self.move_ghost_point(point)

    def move_ghost_point(self, point):
        """
        Translates ghost point to point
        :param point:  where the rubber band is going to be translated
        """
        if self.band:
            offset_x = point.x() - self.lm_point.x()
            offset_y = point.y() - self.lm_point.y()
            self.band.setTranslationOffset(offset_x, offset_y)
            self.band.updatePosition()
            self.band.update()
class EarthMineQGIS(QObject):
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        super(EarthMineQGIS, self).__init__()
        self.movingfeature = None
        self.iface = iface
        self.viewer = None
        self.canvas = self.iface.mapCanvas()
        self.settings = QSettings()
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value("locale/userLocale")[0:2]
        locale_path = os.path.join(self.plugin_dir, "i18n", "EarthMineQGIS_{}.qm".format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > "4.3.3":
                QCoreApplication.installTranslator(self.translator)

        self.pointtool = QgsMapToolEmitPoint(self.canvas)
        self.pointtool.canvasClicked.connect(self.set_viewer_location)

        self.settingsdialog = SettingsDialog(self.iface.mainWindow())

        self.actions = []
        self.menu = self.tr(u"&Earthmine")

        self.toolbar = self.iface.addToolBar(u"EarthMineQGIS")
        self.toolbar.setObjectName(u"EarthMineQGIS")

        self.legend = self.iface.legendInterface()

        emcolor = QColor(1, 150, 51)
        self.tempband = QgsRubberBand(self.canvas, QGis.Line)
        self.tempband.setWidth(5)
        self.tempband.setColor(emcolor)

        self.tempbandpoints = QgsRubberBand(self.canvas, QGis.Point)
        self.tempbandpoints.setWidth(7)
        self.tempbandpoints.setColor(emcolor)

        self.movingband = QgsRubberBand(self.canvas, QGis.Point)
        self.movingband.setWidth(5)
        self.movingband.setColor(emcolor)

        self.layersignals = []
        self.marker = None

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ":/icons/settings"
        self.add_action(
            icon_path, text=self.tr(u"Show Settings"), callback=self.show_settings, parent=self.iface.mainWindow()
        )
        icon_path = ":/icons/viewer"
        self.add_action(
            icon_path, text=self.tr(u"Earthmine Viewer"), callback=self.open_viewer, parent=self.iface.mainWindow()
        )

        self.marker = PostionMarker(self.canvas)
        self.marker.hide()

        self.viewer = Viewer(callbackobject=self)
        self.viewer.trackingChanged.connect(self.marker.setTracking)
        self.viewer.setLocationTriggered.connect(partial(self.canvas.setMapTool, self.pointtool))
        self.viewer.updateFeatures.connect(self.update_earthmine_features)
        self.viewer.layerChanged.connect(self.iface.setActiveLayer)
        self.viewer.clearLine.connect(self.clear_bands)
        self.viewer.closed.connect(self.remove_items)
        self.iface.currentLayerChanged.connect(self.viewer.update_current_layer)

        cursor = QCursor(QPixmap(":/icons/location"))
        self.pointtool.setCursor(cursor)
        self.pointtool.setAction(self.viewer.setlocationaction)

    def remove_items(self):
        self.marker.setTracking(False)
        self.disconnect_projectsignals()
        self.iface.actionPan().trigger()

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        self.canvas.scene().removeItem(self.marker)
        del self.marker

        self.disconnect_projectsignals()

        for action in self.actions:
            self.iface.removePluginMenu(self.tr(u"&Earthmine"), action)
            self.iface.removeToolBarIcon(action)

        del self.toolbar

        self.iface.removeDockWidget(self.viewer)
        self.viewer.deleteLater()

    def disconnect_projectsignals(self):
        safe_disconnect(QgsMapLayerRegistry.instance().layerWasAdded, self.connect_layer_signals)
        safe_disconnect(QgsMapLayerRegistry.instance().layersRemoved, self.layers_removed)
        safe_disconnect(self.canvas.layersChanged, self.layers_changed)
        safe_disconnect(self.iface.projectRead, self.connect_signals)
        safe_disconnect(self.canvas.selectionChanged, self.selection_changed)
        safe_disconnect(self.canvas.selectionChanged, self.viewer.selection_changed)

    def clear_bands(self):
        self.tempband.reset(QGis.Line)
        self.tempbandpoints.reset(QGis.Point)

    def visible_layers(self):
        """
        Return the visible layers shown in the map canvas
        :return:
        """
        return (layer for layer, visible in self.layers_with_states() if visible)

    def layers_with_states(self):
        for layer in maplayers():
            if not layer.type() == QgsMapLayer.VectorLayer:
                continue

            if not layer.geometryType() in [QGis.Point, QGis.Line]:
                continue

            yield layer, self.legend.isLayerVisible(layer)

    def _layer_feature_added(self, featureid):
        layer = self.sender()
        if not layer:
            return

        self.layer_feature_added(layer, featureid)

    def layer_feature_added(self, layer, featureid):
        if not self.viewer:
            return

        feature = layer.getFeatures(QgsFeatureRequest(featureid)).next()
        renderer = layer.rendererV2()
        transform = self.coordinatetransform(layer)
        featuredata = to_feature_data(layer.id(), feature, renderer, transform)
        geomtype = layer.geometryType()
        layerdata = dict(id=layer.id(), geomtype=QGis.vectorGeometryType(geomtype))
        self.viewer.load_features(layerdata, featuredata)

    def _layer_feature_delete(self, featureid):
        layer = self.sender()
        if not layer:
            return
        self.layer_feature_delete(layer, featureid)

    def layer_feature_delete(self, layer, featureid):
        if not self.viewer:
            return

        self.viewer.remove_feature(layer.id(), featureid)

    def _layer_geometry_changed(self, featureid, geometry):
        layer = self.sender()
        if not layer:
            return
        self.layer_geometry_changed(layer, featureid, geometry)

    def layer_geometry_changed(self, layer, featureid, geometry):
        if not self.viewer:
            return

        geomtype = layer.geometryType()
        if geomtype == QGis.Point:
            geom = geometry.asPoint()
            transform = self.coordinatetransform(layer)
            point = transform.transform(geom, QgsCoordinateTransform.ReverseTransform)
            location = dict(lat=point.y(), lng=point.x())
            self.viewer.edit_feature(layer.id(), featureid, [location])
        elif geomtype == QGis.Line:
            self.layer_feature_delete(layer, featureid)
            self.layer_feature_added(layer, featureid)

    def connect_layer_signals(self, layer):
        if not layer.type() == QgsMapLayer.VectorLayer:
            return

        layer.featureAdded.connect(self._layer_feature_added)
        layer.featureDeleted.connect(self._layer_feature_delete)
        layer.editingStarted.connect(self.layer_editstate_changed)
        layer.editingStopped.connect(self.layer_editstate_changed)
        # HACK The new style doesn't work here
        # http://hub.qgis.org/issues/6573
        signal = SIGNAL("geometryChanged(QgsFeatureId, QgsGeometry&)")
        self.connect(layer, signal, self._layer_geometry_changed)
        self.load_layer_features(layers=[layer])

    def layer_editstate_changed(self):
        layer = self.sender()
        if layer == self.iface.activeLayer():
            self.viewer.layer_changed(layer)

    def disconnect_signals(self):
        self.disconnect_projectsignals()

        for layer in maplayers():
            if not layer.type() == QgsMapLayer.VectorLayer:
                return

            safe_disconnect(layer.featureAdded, self._layer_feature_added)
            safe_disconnect(layer.featureDeleted, self._layer_feature_delete)
            safe_disconnect(layer.editingStarted, self.layer_editstate_changed)
            safe_disconnect(layer.editingStopped, self.layer_editstate_changed)
            # HACK The new style doesn't work here
            # http://hub.qgis.org/issues/6573
            signal = SIGNAL("geometryChanged(QgsFeatureId, QgsGeometry&)")
            self.disconnect(layer, signal, self._layer_geometry_changed)

    def connect_signals(self):
        for layer in maplayers():
            self.connect_layer_signals(layer)

        self.center_on_canvas()

    def set_viewer_location(self, point, mousebutton):
        transform = self.coordinatetransform()
        point = transform.transform(point, QgsCoordinateTransform.ReverseTransform)
        self.viewer.set_location(point)

    def distancearea(self):
        area = QgsDistanceArea()
        dest = self.canvas.mapRenderer().destinationCrs()
        area.setSourceCrs(dest)
        return area, dest.mapUnits()

    def coordinatetransform(self, layer=None):
        """
        Return the transform for WGS84 -> QGIS projection.
        """
        source = QgsCoordinateReferenceSystem()
        source.createFromWkt(
            'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]'
        )
        if not layer:
            dest = self.canvas.mapRenderer().destinationCrs()
        else:
            dest = layer.crs()
        transform = QgsCoordinateTransform(source, dest)
        return transform

    def earthmine_settings(self):
        settings = {}
        with settinggroup(self.settings, "plugins/Earthmine"):
            for key in ["serviceUrl", "baseDataUrl", "apiKey", "secretKey", "viewerUrl"]:
                if not self.settings.contains(key):
                    raise EarthmineSettingsError("{} not set".format(key))

                value = self.settings.value(key, type=str)
                if value is None:
                    raise EarthmineSettingsError("{} not set".format(key))

                settings[key] = value

        return settings

    @pyqtSlot()
    def ready(self):
        """
        Called when the viewer is ready to be started.  At this point the viewer hasn't been loaded
        so no other methods apart from startViewer will be handled.
        """
        settings = self.earthmine_settings()
        self.viewer.startViewer(settings)

    @pyqtSlot()
    def viewerReady(self):
        """
        Called once the viewer is loaded and ready to get location events.
        """
        self.disconnect_signals()
        self.connect_signals()
        self.iface.projectRead.connect(self.connect_signals)
        self.canvas.layersChanged.connect(self.layers_changed)
        self.canvas.selectionChanged.connect(self.selection_changed)
        self.canvas.selectionChanged.connect(self.viewer.selection_changed)
        QgsMapLayerRegistry.instance().layersRemoved.connect(self.layers_removed)
        QgsMapLayerRegistry.instance().layerWasAdded.connect(self.connect_layer_signals)
        self.center_on_canvas()
        self.viewer.activelayercombo.setLayer(self.iface.activeLayer())

    def center_on_canvas(self):
        point = self.canvas.extent().center()
        transform = self.coordinatetransform()
        point = transform.transform(point, QgsCoordinateTransform.ReverseTransform)
        self.viewer.set_location(point)
        self.viewer.infoaction.toggle()

    def selection_changed(self, layer):
        ids = [feature.id() for feature in layer.selectedFeatures()]
        if not ids:
            self.viewer.clear_selection(layer.id())
        else:
            self.viewer.set_selection(layer.id(), ids)

    def layers_changed(self):
        layerstates = self.layers_with_states()
        for layer, visible in layerstates:
            layerid = layer.id()
            viewerloaded = self.viewer.layer_loaded(layerid)
            QgsMessageLog.instance().logMessage(layerid, "Earthmine")
            QgsMessageLog.instance().logMessage("Viewer State:" + str(viewerloaded), "Earthmine")
            QgsMessageLog.instance().logMessage("QGIS State:" + str(visible), "Earthmine")
            if (viewerloaded and visible) or (not viewerloaded and not visible):
                QgsMessageLog.instance().logMessage("Ignoring as states match", "Earthmine")
                continue

            if viewerloaded and not visible:
                QgsMessageLog.instance().logMessage(
                    "Clearing layer because viewer loaded and disabled in QGIS", "Earthmine"
                )
                self.viewer.clear_layer_features(layerid)
                continue

            if not viewerloaded and visible:
                QgsMessageLog.instance().logMessage("Loading layer", "Earthmine")
                self.load_layer_features(layers=[layer])
                continue

    def layers_removed(self, layers):
        for layerid in layers:
            self.viewer.clear_layer_features(layerid)

    @pyqtSlot(str, float, float)
    def viewChanged(self, event, yaw, angle):
        self.marker.setAngle(angle)
        self.marker.setYaw(yaw)

    @pyqtSlot(str, str)
    def getInfo(self, layerid, featureid):
        featureid = int(featureid)
        activelayer = self.iface.activeLayer()
        if not activelayer:
            return

        activetool = self.viewer.active_tool()
        if not activetool in ["Info", "Select"]:
            return

        # Only show information for the active layer
        if not layerid == activelayer.id():
            return

        layer = layer_by_id(layerid)
        if activetool == "Select":
            layer.setSelectedFeatures([featureid])
        elif activetool == "Info":
            rq = QgsFeatureRequest(featureid)
            feature = layer.getFeatures(rq).next()
            dlg = get_feature_form(layer, feature)
            if dlg.dialog().exec_():
                self.canvas.refresh()

    @pyqtSlot(str, str, float, float, bool)
    def featureMoved(self, layerid, featureid, lat, lng, end):
        layer = layer_by_id(layerid)
        transform = self.coordinatetransform(layer)
        point = transform.transform(lng, lat)
        if not end:
            self.movingband.show()
            self.movingband.setToGeometry(QgsGeometry.fromPoint(point), layer)
            self.movingband.updatePosition()
            self.movingband.update()
        else:
            self.movingband.hide()
            feature = feature_by_id(layer, featureid)
            startpoint = feature.geometry().asPoint()
            dx = point.x() - startpoint.x()
            dy = point.y() - startpoint.y()
            layer.beginEditCommand("Feature Moved")
            # Block signals for this move as the geometry changed signal will re add the geometry on use.
            layer.blockSignals(True)
            layer.translateFeature(feature.id(), dx, dy)
            layer.blockSignals(False)
            self.canvas.refresh()
            layer.endEditCommand()

    @pyqtSlot(str, str)
    def onError(self, message, stacktrace=None):
        self.iface.messageBar().pushMessage("Earthmine", message, QgsMessageBar.WARNING)
        QgsMessageLog.logMessage(stacktrace, "Earthmine")

    @pyqtSlot(float, float, float)
    def addPoint(self, lat, lng, z):
        layer = self.viewer.active_layer
        if not layer.isEditable():
            self.iface.messageBar().pushMessage(
                "Earthmine",
                "Selected layer isn't editable. Please enable edit mode to add features",
                duration=3,
                level=QgsMessageBar.WARNING,
            )
            return

        transform = self.coordinatetransform(layer)
        point = transform.transform(lng, lat)
        geom = QgsGeometry.fromPoint(point)
        self.add_feature(layer, geom, z)

    def add_feature(self, layer, geom, z=None):
        feature = QgsFeature(layer.pendingFields())
        if z and self.viewer.copyZvalue:
            try:
                feature["Z"] = z
            except KeyError:
                QgsMessageLog.log("No Z found on layer {}".format(layer.name()))
                pass

        feature.setGeometry(geom)
        dlg = get_feature_form(layer, feature, isadd=True)
        if dlg.dialog().exec_():
            self.canvas.refresh()

    @pyqtSlot(str, bool, str)
    def drawLine(self, points, end, stats):
        points = json.loads(points)
        stats = json.loads(stats)
        QgsMessageLog.logMessage(str(stats), "Earthmine")
        self.tempband.reset(QGis.Line)
        self.tempbandpoints.reset(QGis.Point)
        color = QColor(self.viewer.current_action_color)
        self.tempband.setColor(color)
        self.tempbandpoints.setColor(color)

        layer = self.viewer.active_layer
        transform = self.coordinatetransform(layer)
        earthminepoints = []
        for point in points:
            newpoint = transform.transform(point["lng"], point["lat"])
            self.tempband.addPoint(newpoint)
            self.tempbandpoints.addPoint(newpoint)
            empoint = EarthminePoint(newpoint, point)
            earthminepoints.append(empoint)

        if end and not self.viewer.mode == "Vertical":
            geom = self.tempband.asGeometry()
            self.add_feature(layer, geom)
            self.clear_bands()

        self.viewer.geom = EarthmineLine(earthminepoints, stats)

        self.tempband.show()
        self.tempbandpoints.show()

    @pyqtSlot(str, str, str, float)
    def locationChanged(self, lat, lng, yaw, angle):
        transform = self.coordinatetransform()
        point = transform.transform(float(lng), float(lat))
        self.marker.setCenter(point)
        yaw = float(yaw)
        self.marker.setAngle(angle)
        self.marker.setYaw(yaw)
        self.marker.setTracking(self.viewer.tracking)

        if self.marker.tracking:
            rect = QgsRectangle(point, point)
            extentlimt = QgsRectangle(self.canvas.extent())
            extentlimt.scale(0.95)

            if not extentlimt.contains(point):
                self.canvas.setExtent(rect)
                self.canvas.refresh()

        # Clear old features
        self.viewer.clear_features()
        self.load_layer_features(point)

    def update_earthmine_features(self, viewfeatures):
        self.viewer.clear_features()

        if viewfeatures:
            self.load_layer_features()

    def load_layer_features(self, point=None, layers=None):

        # TODO Move this logic into the viewer and let it track it's position
        if point is None and self.marker.map_pos is None:
            return

        if point is None:
            point = self.marker.map_pos

        area, units = self.distancearea()
        rect = search_area(units, area, point)

        if layers is None:
            layers = self.visible_layers()

        for layer in layers:
            transform = self.coordinatetransform(layer)
            # Transform the rect
            source = self.canvas.mapRenderer().destinationCrs()
            dest = layer.crs()
            recttransform = QgsCoordinateTransform(source, dest)
            rect = recttransform.transformBoundingBox(rect)
            features = list(get_features_in_area(layer, rect, transform, self.canvas.mapSettings()))
            geomtype = layer.geometryType()
            layerdata = dict(id=layer.id(), geomtype=QGis.vectorGeometryType(geomtype))
            self.viewer.load_features(layerdata, features)

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate("EarthMineQGIS", message)

    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None,
    ):
        """Add a toolbar icon to the InaSAFE toolbar.

        :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 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 s, "Earhtmine"how 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.triggered.connect(callback)
        action.setEnabled(enabled_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.append(action)

        return action

    def open_viewer(self):
        """Run method that performs all the real work"""
        try:
            settings = self.earthmine_settings()
        except EarthmineSettingsError as ex:
            self.onError(ex.message)
            self.show_settings()
            return

        url = settings["viewerUrl"]
        if not url.startswith("http"):
            url = url.replace("\\\\", "\\")
            url = QUrl.fromLocalFile(url)
        else:
            url = QUrl(url)

        if not self.viewer.isVisible():
            self.iface.addDockWidget(Qt.RightDockWidgetArea, self.viewer)

        self.viewer.loadviewer(url)

    def show_settings(self):
        self.settingsdialog.show()
Example #5
0
class EarthMineQGIS(QObject):
    """QGIS Plugin Implementation."""
    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        super(EarthMineQGIS, self).__init__()
        self.movingfeature = None
        self.iface = iface
        self.viewer = None
        self.canvas = self.iface.mapCanvas()
        self.settings = QSettings()
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(self.plugin_dir, 'i18n',
                                   'EarthMineQGIS_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        self.pointtool = QgsMapToolEmitPoint(self.canvas)
        self.pointtool.canvasClicked.connect(self.set_viewer_location)

        self.settingsdialog = SettingsDialog(self.iface.mainWindow())

        self.actions = []
        self.menu = self.tr(u'&Earthmine')

        self.toolbar = self.iface.addToolBar(u'EarthMineQGIS')
        self.toolbar.setObjectName(u'EarthMineQGIS')

        self.legend = self.iface.legendInterface()

        emcolor = QColor(1, 150, 51)
        self.tempband = QgsRubberBand(self.canvas, QGis.Line)
        self.tempband.setWidth(5)
        self.tempband.setColor(emcolor)

        self.tempbandpoints = QgsRubberBand(self.canvas, QGis.Point)
        self.tempbandpoints.setWidth(7)
        self.tempbandpoints.setColor(emcolor)

        self.movingband = QgsRubberBand(self.canvas, QGis.Point)
        self.movingband.setWidth(5)
        self.movingband.setColor(emcolor)

        self.layersignals = []
        self.marker = None

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/icons/settings'
        self.add_action(icon_path,
                        text=self.tr(u'Show Settings'),
                        callback=self.show_settings,
                        parent=self.iface.mainWindow())
        icon_path = ':/icons/viewer'
        self.add_action(icon_path,
                        text=self.tr(u'Earthmine Viewer'),
                        callback=self.open_viewer,
                        parent=self.iface.mainWindow())

        self.marker = PostionMarker(self.canvas)
        self.marker.hide()

        self.viewer = Viewer(callbackobject=self)
        self.viewer.trackingChanged.connect(self.marker.setTracking)
        self.viewer.setLocationTriggered.connect(
            partial(self.canvas.setMapTool, self.pointtool))
        self.viewer.updateFeatures.connect(self.update_earthmine_features)
        self.viewer.layerChanged.connect(self.iface.setActiveLayer)
        self.viewer.clearLine.connect(self.clear_bands)
        self.viewer.closed.connect(self.remove_items)
        self.iface.currentLayerChanged.connect(
            self.viewer.update_current_layer)

        cursor = QCursor(QPixmap(":/icons/location"))
        self.pointtool.setCursor(cursor)
        self.pointtool.setAction(self.viewer.setlocationaction)

    def remove_items(self):
        self.marker.setTracking(False)
        self.disconnect_projectsignals()
        self.iface.actionPan().trigger()

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        self.canvas.scene().removeItem(self.marker)
        del self.marker

        self.disconnect_projectsignals()

        for action in self.actions:
            self.iface.removePluginMenu(self.tr(u'&Earthmine'), action)
            self.iface.removeToolBarIcon(action)

        del self.toolbar

        self.iface.removeDockWidget(self.viewer)
        self.viewer.deleteLater()

    def disconnect_projectsignals(self):
        safe_disconnect(QgsMapLayerRegistry.instance().layerWasAdded,
                        self.connect_layer_signals)
        safe_disconnect(QgsMapLayerRegistry.instance().layersRemoved,
                        self.layers_removed)
        safe_disconnect(self.canvas.layersChanged, self.layers_changed)
        safe_disconnect(self.iface.projectRead, self.connect_signals)
        safe_disconnect(self.canvas.selectionChanged, self.selection_changed)
        safe_disconnect(self.canvas.selectionChanged,
                        self.viewer.selection_changed)

    def clear_bands(self):
        self.tempband.reset(QGis.Line)
        self.tempbandpoints.reset(QGis.Point)

    def visible_layers(self):
        """
        Return the visible layers shown in the map canvas
        :return:
        """
        return (layer for layer, visible in self.layers_with_states()
                if visible)

    def layers_with_states(self):
        for layer in maplayers():
            if not layer.type() == QgsMapLayer.VectorLayer:
                continue

            if not layer.geometryType() in [QGis.Point, QGis.Line]:
                continue

            yield layer, self.legend.isLayerVisible(layer)

    def _layer_feature_added(self, featureid):
        layer = self.sender()
        if not layer:
            return

        self.layer_feature_added(layer, featureid)

    def layer_feature_added(self, layer, featureid):
        if not self.viewer:
            return

        feature = layer.getFeatures(QgsFeatureRequest(featureid)).next()
        renderer = layer.rendererV2()
        transform = self.coordinatetransform(layer)
        featuredata = to_feature_data(layer.id(), feature, renderer, transform)
        geomtype = layer.geometryType()
        layerdata = dict(id=layer.id(),
                         geomtype=QGis.vectorGeometryType(geomtype))
        self.viewer.load_features(layerdata, featuredata)

    def _layer_feature_delete(self, featureid):
        layer = self.sender()
        if not layer:
            return
        self.layer_feature_delete(layer, featureid)

    def layer_feature_delete(self, layer, featureid):
        if not self.viewer:
            return

        self.viewer.remove_feature(layer.id(), featureid)

    def _layer_geometry_changed(self, featureid, geometry):
        layer = self.sender()
        if not layer:
            return
        self.layer_geometry_changed(layer, featureid, geometry)

    def layer_geometry_changed(self, layer, featureid, geometry):
        if not self.viewer:
            return

        geomtype = layer.geometryType()
        if geomtype == QGis.Point:
            geom = geometry.asPoint()
            transform = self.coordinatetransform(layer)
            point = transform.transform(
                geom, QgsCoordinateTransform.ReverseTransform)
            location = dict(lat=point.y(), lng=point.x())
            self.viewer.edit_feature(layer.id(), featureid, [location])
        elif geomtype == QGis.Line:
            self.layer_feature_delete(layer, featureid)
            self.layer_feature_added(layer, featureid)

    def connect_layer_signals(self, layer):
        if not layer.type() == QgsMapLayer.VectorLayer:
            return

        layer.featureAdded.connect(self._layer_feature_added)
        layer.featureDeleted.connect(self._layer_feature_delete)
        layer.editingStarted.connect(self.layer_editstate_changed)
        layer.editingStopped.connect(self.layer_editstate_changed)
        # HACK The new style doesn't work here
        # http://hub.qgis.org/issues/6573
        signal = SIGNAL("geometryChanged(QgsFeatureId, QgsGeometry&)")
        self.connect(layer, signal, self._layer_geometry_changed)
        self.load_layer_features(layers=[layer])

    def layer_editstate_changed(self):
        layer = self.sender()
        if layer == self.iface.activeLayer():
            self.viewer.layer_changed(layer)

    def disconnect_signals(self):
        self.disconnect_projectsignals()

        for layer in maplayers():
            if not layer.type() == QgsMapLayer.VectorLayer:
                return

            safe_disconnect(layer.featureAdded, self._layer_feature_added)
            safe_disconnect(layer.featureDeleted, self._layer_feature_delete)
            safe_disconnect(layer.editingStarted, self.layer_editstate_changed)
            safe_disconnect(layer.editingStopped, self.layer_editstate_changed)
            # HACK The new style doesn't work here
            # http://hub.qgis.org/issues/6573
            signal = SIGNAL("geometryChanged(QgsFeatureId, QgsGeometry&)")
            self.disconnect(layer, signal, self._layer_geometry_changed)

    def connect_signals(self):
        for layer in maplayers():
            self.connect_layer_signals(layer)

        self.center_on_canvas()

    def set_viewer_location(self, point, mousebutton):
        transform = self.coordinatetransform()
        point = transform.transform(point,
                                    QgsCoordinateTransform.ReverseTransform)
        self.viewer.set_location(point)

    def distancearea(self):
        area = QgsDistanceArea()
        dest = self.canvas.mapRenderer().destinationCrs()
        area.setSourceCrs(dest)
        return area, dest.mapUnits()

    def coordinatetransform(self, layer=None):
        """
        Return the transform for WGS84 -> QGIS projection.
        """
        source = QgsCoordinateReferenceSystem()
        source.createFromWkt(
            'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]'
        )
        if not layer:
            dest = self.canvas.mapRenderer().destinationCrs()
        else:
            dest = layer.crs()
        transform = QgsCoordinateTransform(source, dest)
        return transform

    def earthmine_settings(self):
        settings = {}
        with settinggroup(self.settings, "plugins/Earthmine"):
            for key in [
                    'serviceUrl', 'baseDataUrl', "apiKey", 'secretKey',
                    'viewerUrl'
            ]:
                if not self.settings.contains(key):
                    raise EarthmineSettingsError("{} not set".format(key))

                value = self.settings.value(key, type=str)
                if value is None:
                    raise EarthmineSettingsError("{} not set".format(key))

                settings[key] = value

        return settings

    @pyqtSlot()
    def ready(self):
        """
        Called when the viewer is ready to be started.  At this point the viewer hasn't been loaded
        so no other methods apart from startViewer will be handled.
        """
        settings = self.earthmine_settings()
        self.viewer.startViewer(settings)

    @pyqtSlot()
    def viewerReady(self):
        """
        Called once the viewer is loaded and ready to get location events.
        """
        self.disconnect_signals()
        self.connect_signals()
        self.iface.projectRead.connect(self.connect_signals)
        self.canvas.layersChanged.connect(self.layers_changed)
        self.canvas.selectionChanged.connect(self.selection_changed)
        self.canvas.selectionChanged.connect(self.viewer.selection_changed)
        QgsMapLayerRegistry.instance().layersRemoved.connect(
            self.layers_removed)
        QgsMapLayerRegistry.instance().layerWasAdded.connect(
            self.connect_layer_signals)
        self.center_on_canvas()
        self.viewer.activelayercombo.setLayer(self.iface.activeLayer())

    def center_on_canvas(self):
        point = self.canvas.extent().center()
        transform = self.coordinatetransform()
        point = transform.transform(point,
                                    QgsCoordinateTransform.ReverseTransform)
        self.viewer.set_location(point)
        self.viewer.infoaction.toggle()

    def selection_changed(self, layer):
        ids = [feature.id() for feature in layer.selectedFeatures()]
        if not ids:
            self.viewer.clear_selection(layer.id())
        else:
            self.viewer.set_selection(layer.id(), ids)

    def layers_changed(self):
        layerstates = self.layers_with_states()
        for layer, visible in layerstates:
            layerid = layer.id()
            viewerloaded = self.viewer.layer_loaded(layerid)
            QgsMessageLog.instance().logMessage(layerid, "Earthmine")
            QgsMessageLog.instance().logMessage(
                "Viewer State:" + str(viewerloaded), "Earthmine")
            QgsMessageLog.instance().logMessage("QGIS State:" + str(visible),
                                                "Earthmine")
            if (viewerloaded and visible) or (not viewerloaded
                                              and not visible):
                QgsMessageLog.instance().logMessage("Ignoring as states match",
                                                    "Earthmine")
                continue

            if viewerloaded and not visible:
                QgsMessageLog.instance().logMessage(
                    "Clearing layer because viewer loaded and disabled in QGIS",
                    "Earthmine")
                self.viewer.clear_layer_features(layerid)
                continue

            if not viewerloaded and visible:
                QgsMessageLog.instance().logMessage("Loading layer",
                                                    "Earthmine")
                self.load_layer_features(layers=[layer])
                continue

    def layers_removed(self, layers):
        for layerid in layers:
            self.viewer.clear_layer_features(layerid)

    @pyqtSlot(str, float, float)
    def viewChanged(self, event, yaw, angle):
        self.marker.setAngle(angle)
        self.marker.setYaw(yaw)

    @pyqtSlot(str, str)
    def getInfo(self, layerid, featureid):
        featureid = int(featureid)
        activelayer = self.iface.activeLayer()
        if not activelayer:
            return

        activetool = self.viewer.active_tool()
        if not activetool in ["Info", "Select"]:
            return

        # Only show information for the active layer
        if not layerid == activelayer.id():
            return

        layer = layer_by_id(layerid)
        if activetool == "Select":
            layer.setSelectedFeatures([featureid])
        elif activetool == "Info":
            rq = QgsFeatureRequest(featureid)
            feature = layer.getFeatures(rq).next()
            dlg = get_feature_form(layer, feature)
            if dlg.dialog().exec_():
                self.canvas.refresh()

    @pyqtSlot(str, str, float, float, bool)
    def featureMoved(self, layerid, featureid, lat, lng, end):
        layer = layer_by_id(layerid)
        transform = self.coordinatetransform(layer)
        point = transform.transform(lng, lat)
        if not end:
            self.movingband.show()
            self.movingband.setToGeometry(QgsGeometry.fromPoint(point), layer)
            self.movingband.updatePosition()
            self.movingband.update()
        else:
            self.movingband.hide()
            feature = feature_by_id(layer, featureid)
            startpoint = feature.geometry().asPoint()
            dx = point.x() - startpoint.x()
            dy = point.y() - startpoint.y()
            layer.beginEditCommand("Feature Moved")
            # Block signals for this move as the geometry changed signal will re add the geometry on use.
            layer.blockSignals(True)
            layer.translateFeature(feature.id(), dx, dy)
            layer.blockSignals(False)
            self.canvas.refresh()
            layer.endEditCommand()

    @pyqtSlot(str, str)
    def onError(self, message, stacktrace=None):
        self.iface.messageBar().pushMessage("Earthmine", message,
                                            QgsMessageBar.WARNING)
        QgsMessageLog.logMessage(stacktrace, "Earthmine")

    @pyqtSlot(float, float, float)
    def addPoint(self, lat, lng, z):
        layer = self.viewer.active_layer
        if not layer.isEditable():
            self.iface.messageBar().pushMessage(
                "Earthmine",
                "Selected layer isn't editable. Please enable edit mode to add features",
                duration=3,
                level=QgsMessageBar.WARNING)
            return

        transform = self.coordinatetransform(layer)
        point = transform.transform(lng, lat)
        geom = QgsGeometry.fromPoint(point)
        self.add_feature(layer, geom, z)

    def add_feature(self, layer, geom, z=None):
        feature = QgsFeature(layer.pendingFields())
        if z and self.viewer.copyZvalue:
            try:
                feature['Z'] = z
            except KeyError:
                QgsMessageLog.log("No Z found on layer {}".format(
                    layer.name()))
                pass

        feature.setGeometry(geom)
        dlg = get_feature_form(layer, feature, isadd=True)
        if dlg.dialog().exec_():
            self.canvas.refresh()

    @pyqtSlot(str, bool, str)
    def drawLine(self, points, end, stats):
        points = json.loads(points)
        stats = json.loads(stats)
        QgsMessageLog.logMessage(str(stats), "Earthmine")
        self.tempband.reset(QGis.Line)
        self.tempbandpoints.reset(QGis.Point)
        color = QColor(self.viewer.current_action_color)
        self.tempband.setColor(color)
        self.tempbandpoints.setColor(color)

        layer = self.viewer.active_layer
        transform = self.coordinatetransform(layer)
        earthminepoints = []
        for point in points:
            newpoint = transform.transform(point['lng'], point['lat'])
            self.tempband.addPoint(newpoint)
            self.tempbandpoints.addPoint(newpoint)
            empoint = EarthminePoint(newpoint, point)
            earthminepoints.append(empoint)

        if end and not self.viewer.mode == "Vertical":
            geom = self.tempband.asGeometry()
            self.add_feature(layer, geom)
            self.clear_bands()

        self.viewer.geom = EarthmineLine(earthminepoints, stats)

        self.tempband.show()
        self.tempbandpoints.show()

    @pyqtSlot(str, str, str, float)
    def locationChanged(self, lat, lng, yaw, angle):
        transform = self.coordinatetransform()
        point = transform.transform(float(lng), float(lat))
        self.marker.setCenter(point)
        yaw = float(yaw)
        self.marker.setAngle(angle)
        self.marker.setYaw(yaw)
        self.marker.setTracking(self.viewer.tracking)

        if self.marker.tracking:
            rect = QgsRectangle(point, point)
            extentlimt = QgsRectangle(self.canvas.extent())
            extentlimt.scale(0.95)

            if not extentlimt.contains(point):
                self.canvas.setExtent(rect)
                self.canvas.refresh()

        # Clear old features
        self.viewer.clear_features()
        self.load_layer_features(point)

    def update_earthmine_features(self, viewfeatures):
        self.viewer.clear_features()

        if viewfeatures:
            self.load_layer_features()

    def load_layer_features(self, point=None, layers=None):

        # TODO Move this logic into the viewer and let it track it's position
        if point is None and self.marker.map_pos is None:
            return

        if point is None:
            point = self.marker.map_pos

        area, units = self.distancearea()
        rect = search_area(units, area, point)

        if layers is None:
            layers = self.visible_layers()

        for layer in layers:
            transform = self.coordinatetransform(layer)
            # Transform the rect
            source = self.canvas.mapRenderer().destinationCrs()
            dest = layer.crs()
            recttransform = QgsCoordinateTransform(source, dest)
            rect = recttransform.transformBoundingBox(rect)
            features = list(
                get_features_in_area(layer, rect, transform,
                                     self.canvas.mapSettings()))
            geomtype = layer.geometryType()
            layerdata = dict(id=layer.id(),
                             geomtype=QGis.vectorGeometryType(geomtype))
            self.viewer.load_features(layerdata, features)

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('EarthMineQGIS', message)

    def add_action(self,
                   icon_path,
                   text,
                   callback,
                   enabled_flag=True,
                   add_to_menu=True,
                   add_to_toolbar=True,
                   status_tip=None,
                   whats_this=None,
                   parent=None):
        """Add a toolbar icon to the InaSAFE toolbar.

        :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 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 s, "Earhtmine"how 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.triggered.connect(callback)
        action.setEnabled(enabled_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.append(action)

        return action

    def open_viewer(self):
        """Run method that performs all the real work"""
        try:
            settings = self.earthmine_settings()
        except EarthmineSettingsError as ex:
            self.onError(ex.message)
            self.show_settings()
            return

        url = settings["viewerUrl"]
        if not url.startswith("http"):
            url = url.replace("\\\\", "\\")
            url = QUrl.fromLocalFile(url)
        else:
            url = QUrl(url)

        if not self.viewer.isVisible():
            self.iface.addDockWidget(Qt.RightDockWidgetArea, self.viewer)

        self.viewer.loadviewer(url)

    def show_settings(self):
        self.settingsdialog.show()