Example #1
0
 def setClusters(self, feedback):
     #Get the center of each groups and check if some point are in the wrong group
     centers = []
     clusters = []
     for group in self.groups:
         geom = QgsGeometry.fromMultiPointXY(group)
         centers.append(geom.centroid().asPoint())
         clusters.append([])
     for group in self.groups:
         for point in group:
             minDist = None
             for i in range(0,len(centers)):
                 dist = self.pointDist(centers[i], point)
                 if minDist is None:
                     minDist = dist
                     index = i
                 elif dist < minDist:
                     minDist = dist
                     index = i
             clusters[index].append(point)
     #Create symbology and add features
     for id, cluster in enumerate(clusters):
         geom = QgsGeometry.fromMultiPointXY(cluster)
         cHull = geom.convexHull()
         buffer = cHull.buffer(10, 3)
         feat = QgsFeature()
         feat.setAttributes([id, cHull.area(), int(len(cluster))])
         feat.setGeometry(buffer)
         self.sink.addFeature(feat, QgsFeatureSink.FastInsert)
def _swap_qgs_geometry(qgsgeom):
    if qgsgeom.wkbType() == QgsWkbTypes.Point:
        p = qgsgeom.asPoint()
        qgsgeom = QgsGeometry.fromPointXY(QgsPointXY(p[1], p[0]))
    elif qgsgeom.wkbType() == QgsWkbTypes.MultiPoint:
        mp = qgsgeom.asMultiPoint()
        qgsgeom = QgsGeometry.fromMultiPointXY(
            [QgsPointXY(p[1], p[0]) for p in mp])
    elif qgsgeom.wkbType() == QgsWkbTypes.LineString:
        pl = qgsgeom.asPolyline()
        qgsgeom = QgsGeometry.fromPolylineXY(
            [QgsPointXY(p[1], p[0]) for p in pl])
    elif qgsgeom.wkbType() == QgsWkbTypes.MultiLineString:
        mls = qgsgeom.asMultiPolyline()
        qgsgeom = QgsGeometry.fromMultiPolylineXY(
            [[QgsPointXY(p[1], p[0]) for p in pl] for pl in mls])
    elif qgsgeom.wkbType() == QgsWkbTypes.Polygon:
        pl = qgsgeom.asPolygon()
        qgsgeom = QgsGeometry.fromPolygonXY(
            [[QgsPointXY(p[1], p[0]) for p in r] for r in pl])
    elif qgsgeom.wkbType() == QgsWkbTypes.MultiPolygon:
        mp = qgsgeom.asMultiPolygon()
        qgsgeom = QgsGeometry.fromMultiPolygonXY(
            [[[QgsPointXY(p[1], p[0]) for p in r] for r in pl] for pl in mp])
    return qgsgeom
 def buildFlagList(self, nodeFlags, source, nodeIdDict, feedback):
     """
     Builds record list from pointList to raise flags.
     :param nodeFlags: (dict) dictionary containing invalid node 
                         and its reason ( { (QgsPoint) node : (str) reason } )
     """
     # prepare point flag sink
     countNodeNotInDb = 0
     nodeNumber = len(nodeFlags)
     size = 100 / nodeNumber if nodeNumber else 0
     for current, (node, reason) in enumerate(nodeFlags.items()):
         if feedback.isCanceled():
             break
         if node in nodeIdDict:
             featid = nodeIdDict[node] if nodeIdDict[
                 node] is not None else -9999
         else:
             # if node is not previously classified on database, but then motivates a flag, it should appear on Flags list
             featid = -9999
             countNodeNotInDb += 1
         flagText = 'Feature with id={id} from {lyrName} with problem: {msg}'.format(
             id=featid, lyrName=source.name(), msg=reason)
         flagGeom = QgsGeometry.fromMultiPointXY([node])
         self.flagFeature(flagGeom, flagText)
         feedback.setProgress(size * current)
     if countNodeNotInDb:
         # in case there are flagged nodes that are not loaded in DB, user is notified
         msg = self.tr(
             'There are {0} flagged nodes that were introduced to network. Node reclassification is indicated.'
         ).format(countNodeNotInDb)
         feedback.pushInfo(msg)
Example #4
0
    def get_multipoint_features(self, id_field_value):
        """
        Generator to return isochrone snapped locations from response.

        :param id_field_value: Value of ID field.
        :type id_field_value: any

        :returns: output feature
        :rtype: QgsFeature
        """
        multipoints = [
            feature for feature in self.response['features']
            if feature['geometry']['type'] == 'MultiPoint'
        ]
        for multipoint in multipoints:
            feat = QgsFeature()
            coords = [
                QgsPointXY(*coords)
                for coords in multipoint['geometry']['coordinates']
            ]
            feat.setGeometry(QgsGeometry.fromMultiPointXY(coords))
            feat.setAttributes(
                [id_field_value, multipoint['properties']['type']])

            yield feat
 def fillNodeSink(self, nodeSink, networkLineLayerName, nodeFlagDict, feedback=None):
     """
     Populate hidrography node layer with all nodes.
     :param nodeSink: (QgsFeatureSink) hidrography nodes layer.
     :param networkLineLayerName: (str) network line layer name.
     """
     # get fields from layer in order to create new feature with the same attribute map
     fields = self.getFields()
     nPoints = len(self.nodeTypeDict)
     size = 100/nPoints if nPoints else 0
     # to avoid unnecessary calculation inside loop
     nodeTypeKeys = self.nodeTypeDict.keys()
     # initiate new features list
     featList = []
     for current, node in enumerate(self.nodeDict):
         # set attribute map
         feat = QgsFeature(fields)
         # set geometry
         nodeGeom = QgsGeometry.fromMultiPointXY([node])
         feat.setGeometry(nodeGeom)
         feat['node_type'] = self.nodeTypeDict[node] if node in nodeTypeKeys else None
         feat['layer'] = networkLineLayerName
         if node in nodeFlagDict:
             self.flagFeature(nodeGeom, nodeFlagDict[node])
         featList.append(feat)
         if feedback is not None:
             feedback.setProgress(size * current)
     nodeSink.addFeatures(featList, QgsFeatureSink.FastInsert)
Example #6
0
 def reprojectPoints(self, geom, xform):
     if geom.type() == 0:  #Point
         if geom.isMultipart():
             pnts = geom.asMultiPoint()
             newPnts = []
             for pnt in pnts:
                 newPnts += [xform.transform(pnt)]
             newGeom = QgsGeometry.fromMultiPointXY(newPnts)
             return newGeom
         else:
             pnt = geom.asPoint()
             newPnt = xform.transform(pnt)
             newGeom = QgsGeometry.fromPointXY(newPnt)
             return newGeom
     elif geom.type() == 1:  #Line
         if geom.isMultipart():
             linhas = geom.asMultiPolyline()
             newLines = []
             for linha in linhas:
                 newLine = []
                 for pnt in linha:
                     newLine += [xform.transform(pnt)]
                 newLines += [newLine]
             newGeom = QgsGeometry.fromMultiPolylineXY(newLines)
             return newGeom
         else:
             linha = geom.asPolyline()
             newLine = []
             for pnt in linha:
                 newLine += [xform.transform(pnt)]
             newGeom = QgsGeometry.fromPolylineXY(newLine)
             return newGeom
     elif geom.type() == 2:  #Polygon
         if geom.isMultipart():
             poligonos = geom.asMultiPolygon()
             newPolygons = []
             for pol in poligonos:
                 newPol = []
                 for anel in pol:
                     newAnel = []
                     for pnt in anel:
                         newAnel += [xform.transform(pnt)]
                     newPol += [newAnel]
                 newPolygons += [newPol]
             newGeom = QgsGeometry.fromMultiPolygonXY(newPolygons)
             return newGeom
         else:
             pol = geom.asPolygon()
             newPol = []
             for anel in pol:
                 newAnel = []
                 for pnt in anel:
                     newAnel += [xform.transform(pnt)]
                 newPol += [newAnel]
             newGeom = QgsGeometry.fromPolygonXY(newPol)
             return newGeom
     else:
         return None
Example #7
0
 def flipFeature(self, layer, feature, geomType=None, refreshCanvas=False):
     """
     Inverts the flow from a given feature. THE GIVEN FEATURE IS ALTERED. Standard behaviour is to not
     refresh canvas map.
     :param layer: layer containing the target feature for flipping.
     :param feature: feature to be flipped.
     :param geomType: if layer geometry type is not given, it'll calculate it (0,1 or 2)
     :param refreshCanvas: indicates whether the canvas should be refreshed after flipping feature.
     :returns: flipped feature as of [layer, feature, geometry_type].
     """
     if not geomType:
         geomType = layer.geometryType()
     # getting whether geometry is multipart or not
     # features not yet commited to layer always have SINGLE geometry
     isMulti = QgsWkbTypes.isMultiType(int(
         layer.wkbType())) and feature.id() > 0
     geom = feature.geometry()
     if geomType == 0:
         if isMulti:
             nodes = geom.asMultiPoint()
             # inverting the point list by parts
             for idx, part in enumerate(nodes):
                 nodes[idx] = part[::-1]
             # setting flipped geometry
             flippedFeatureGeom = QgsGeometry.fromMultiPointXY(nodes)
         else:
             # inverting the point list
             nodes = geom.asPoint()
             nodes = nodes[::-1]
             flippedFeatureGeom = QgsGeometry.fromPoint(nodes)
     elif geomType == 1:
         if isMulti:
             nodes = geom.asMultiPolyline()
             for idx, part in enumerate(nodes):
                 nodes[idx] = part[::-1]
             flippedFeatureGeom = QgsGeometry.fromMultiPolylineXY(nodes)
         else:
             nodes = geom.asPolyline()
             nodes = nodes[::-1]
             flippedFeatureGeom = QgsGeometry.fromPolylineXY(nodes)
     elif geomType == 2:
         if isMulti:
             nodes = geom.asMultiPolygon()
             for idx, part in enumerate(nodes):
                 nodes[idx] = part[::-1]
             flippedFeatureGeom = QgsGeometry.fromMultiPolygonXY(nodes)
         else:
             nodes = geom.asPolygon()
             nodes = nodes[::-1]
             flippedFeatureGeom = QgsGeometry.fromPolygonXY(nodes)
     # setting feature geometry to the flipped one
     # feature.setGeometry(flippedFeatureGeom)
     # layer.updateFeature(feature)
     layer.changeGeometry(feature.id(), flippedFeatureGeom)
     if refreshCanvas:
         self.iface.mapCanvas().refresh()
     return [layer, feature, geomType]
Example #8
0
    def toQgsGeometry(self):
        count = len(self.pts)
        if count > 1:
            pts = [pointToQgsPoint(pt) for pt in self.pts]
            return QgsGeometry.fromMultiPointXY(pts)

        if count == 1:
            return QgsGeometry.fromPointXY(pointToQgsPoint(self.pts[0]))

        return QgsGeometry()
Example #9
0
    def toQgsGeometry(self):
        count = len(self.pts)
        if count > 1:
            pts = [QgsPoint(x, y) for x, y, z in self.pts]
            return QgsGeometry.fromMultiPointXY(pts)

        if count == 1:
            x, y, z = self.pts[0]
            return QgsGeometry.fromPointXY(QgsPoint(x, y))

        return QgsGeometry()
    def collapse_to_node(self, group):

        # create new node, coords
        self.node_id += 1
        feat = QgsFeature()
        centroid = (
            QgsGeometry.fromMultiPointXY([self.sNodes[nd].feature.geometry().asPoint() for nd in group])).centroid()
        feat.setGeometry(centroid)
        feat.setAttributes([self.node_id])
        feat.setId(self.node_id)
        snode = sNode(self.node_id, feat, [], [])
        self.sNodes[self.node_id] = snode
        self.ndSpIndex.addFeature(feat)

        return self.node_id, centroid.asPoint()
Example #11
0
 def find_geometry(self, g):
     if self.output_type == "Poly":
         stat = g.area()
         if g.isMultipart():
             geometry = QgsGeometry.fromMultiPolygonXY(g.asMultiPolygon())
         else:
             geometry = QgsGeometry.fromPolygonXY(g.asPolygon())
     elif self.output_type == "Line":
         stat = g.length()
         if g.isMultipart():
             geometry = QgsGeometry.fromMultiPolylineXY(g.asMultiPolyLine())
         else:
             geometry = QgsGeometry.fromPolyline(g.asPoly())
     else:
         stat = 1
         if g.isMultipart():
             geometry = QgsGeometry.fromMultiPointXY(g.asMultiPoint())
         else:
             geometry = QgsGeometry.fromPointXY(g.asPoint())
     return geometry, stat
def _swap_qgs_geometry(qgsgeom):
    if qgsgeom.wkbType() == QgsWkbTypes.Point:
        p = qgsgeom.asPoint()
        qgsgeom = QgsGeometry.fromPointXY(QgsPointXY(p[1], p[0]))
    elif qgsgeom.wkbType() == QgsWkbTypes.MultiPoint:
        mp = qgsgeom.asMultiPoint()
        qgsgeom = QgsGeometry.fromMultiPointXY([QgsPointXY(p[1], p[0]) for p in mp])
    elif qgsgeom.wkbType() == QgsWkbTypes.LineString:
        pl = qgsgeom.asPolyline()
        qgsgeom = QgsGeometry.fromPolylineXY([QgsPointXY(p[1],p[0]) for p in pl])
    elif qgsgeom.wkbType() == QgsWkbTypes.MultiLineString:
        mls = qgsgeom.asMultiPolyline()
        qgsgeom = QgsGeometry.fromMultiPolylineXY([[QgsPointXY(p[1],p[0]) for p in pl] for pl in mls])
    elif qgsgeom.wkbType() == QgsWkbTypes.Polygon:
        pl = qgsgeom.asPolygon()
        qgsgeom = QgsGeometry.fromPolygonXY([[QgsPointXY(p[1],p[0]) for p in r] for r in pl])
    elif qgsgeom.wkbType() == QgsWkbTypes.MultiPolygon:
        mp = qgsgeom.asMultiPolygon()
        qgsgeom = QgsGeometry.fromMultiPolygonXY([[[QgsPointXY(p[1],p[0]) for p in r] for r in pl] for pl in mp])
    return qgsgeom
Example #13
0
    def move_feature(self, feat, dx, dy):
        self.new_feat = feat.copy()
        self.geometry = feat["geometry"]
        if self.geometry.wkbType() == 1:  # Point
            self.geom = self.geometry.asPoint()
            self.newGeom = QgsPointXY(self.geom[0] + dx, self.geom[1] + dy)
            self.newGeometry = QgsGeometry.fromPointXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 2:  # LineString
            self.geom = self.geometry.asPolyline()
            self.newGeom = [
                QgsPointXY(i[0] + dx, i[1] + dy) for i in self.geom
            ]
            self.newGeometry = QgsGeometry.fromPolylineXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 3:  # Polygon
            self.geom = self.geometry.buffer(0, 5).asPolygon()
            self.newGeom = []
            for g in self.geom:
                self.g1 = []
                for gg in g:
                    self.new_gg = QgsPointXY(gg[0] + dx, gg[1] + dy)
                    self.g1.append(self.new_gg)
                self.newGeom.append(self.g1)

            self.newGeometry = QgsGeometry.fromPolygonXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 4:  # MultiPoint
            self.geom = self.geometry.asMultiPoint()
            self.newGeom = [
                QgsPointXY(i[0] + dx, i[1] + dy) for i in self.geom
            ]
            self.newGeometry = QgsGeometry.fromMultiPointXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 5:  # MultiLineString
            self.geom = self.geometry.asMultiPolyline()
            self.newGeom = []
            for g in self.geom:
                self.g1 = []
                for gg in g:
                    self.new_gg = QgsPointXY(gg[0] + dx, gg[1] + dy)
                    self.g1.append(self.new_gg)
                self.newGeom.append(self.g1)

            self.newGeometry = QgsGeometry.fromMultiPolylineXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        elif self.geometry.wkbType() == 6:  # MultiPolygon
            self.geom = self.geometry.asMultiPolygon()
            self.newGeom = []
            for g in self.geom:
                self.g1 = []
                for gg in g:
                    self.g2 = []
                    for ggg in gg:
                        self.new_ggg = QgsPointXY(ggg[0] + dx, ggg[1] + dy)
                        self.g2.append(self.new_ggg)
                    self.g1.append(self.g2)
                self.newGeom.append(self.g1)

            self.newGeometry = QgsGeometry.fromMultiPolygonXY(self.newGeom)
            self.new_feat["geometry"] = self.newGeometry

        return self.new_feat
Example #14
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        buf = self.parameterAsDouble(parameters, self.BUFFER, context)
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               QgsWkbTypes.Polygon,
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))
        outFeat = QgsFeature()
        extent = source.sourceExtent()
        extraX = extent.width() * (buf / 100.0)
        # Adjust the extent
        extent.setXMinimum(extent.xMinimum() - extraX)
        extent.setXMaximum(extent.xMaximum() + extraX)
        extraY = extent.height() * (buf / 100.0)
        extent.setYMinimum(extent.yMinimum() - extraY)
        extent.setYMaximum(extent.yMaximum() + extraY)
        height = extent.height()
        width = extent.width()
        c = voronoi.Context()
        pts = []
        ptDict = {}
        ptNdx = -1
        # Find the minimum and maximum x and y for the input points
        xmin = width
        xmax = 0
        ymin = height
        ymax = 0
        features = source.getFeatures()
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        for current, inFeat in enumerate(features):
            if feedback.isCanceled():
                break
            geom = inFeat.geometry()
            point = geom.asPoint()
            x = point.x() - extent.xMinimum()
            y = point.y() - extent.yMinimum()
            pts.append((x, y))
            ptNdx += 1
            ptDict[ptNdx] = inFeat.id()
            if x < xmin:
                xmin = x
            if y < ymin:
                ymin = y
            if x > xmax:
                xmax = x
            if y > ymax:
                ymax = y
            feedback.setProgress(int(current * total))
        if xmin == xmax or ymin == ymax:
            raise QgsProcessingException('The extent of the input points is '
                                         'not a polygon (all the points are '
                                         'on a vertical or horizontal line) '
                                         '- cannot make a Voronoi diagram!')
        xyminmax = [xmin, ymin, xmax, ymax]
        if len(pts) < 3:
            raise QgsProcessingException(
                self.tr('Input file should contain at least 3 points. Choose '
                        'another file and try again.'))
        # Eliminate duplicate points
        uniqueSet = set(item for item in pts)
        ids = [pts.index(item) for item in uniqueSet]
        sl = voronoi.SiteList([
            voronoi.Site(i[0], i[1], sitenum=j)
            for (j, i) in enumerate(uniqueSet)
        ])
        voronoi.voronoi(sl, c)
        if len(c.polygons) == 0:
            raise QgsProcessingException(
                self.tr('There were no polygons created.'))

        inFeat = QgsFeature()
        current = 0
        total = 100.0 / len(c.polygons)
        # Clip each of the generated "polygons"
        for (site, edges) in list(c.polygons.items()):
            if feedback.isCanceled():
                break
            request = QgsFeatureRequest().setFilterFid(ptDict[ids[site]])
            inFeat = next(source.getFeatures(request))
            boundarypoints = self.clip_voronoi(edges, c, width, height, extent,
                                               inFeat.geometry().asPoint(),
                                               xyminmax)
            ptgeom = QgsGeometry.fromMultiPointXY(boundarypoints)
            geom = QgsGeometry(ptgeom.convexHull())
            outFeat.setGeometry(geom)
            outFeat.setAttributes(inFeat.attributes())
            sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
            current += 1
            feedback.setProgress(int(current * total))
        return {self.OUTPUT: dest_id}
    def run(self):
        """Run method that performs all the real work"""
        # make our clickTool the tool that we'll use for now
        self.canvas.setMapTool(self.clickTool)
        
        # show the dialog
        self.dlg.show()
        self.read()
        # Run the dialog event loop
        result = self.dlg.exec_()
        self.store()
        # See if OK was pressed
     

        
        if result == 1:
            # send the API request
            #function to send a get request  # arrange the input into an API call that checks with Zotero 

            def api_get(userID, collectionID, apiKey, limit=100, start=0):
                '''
                Make the API request.
                Filtering out notes and attachments drastically reduces the number of items, should save time.
                '''
                api_url = 'https://api.zotero.org/users/%s/collections/%s/items?key=%s&limit=%s&start=%s&itemType=-attachment || note' % (userID, collectionID, apiKey, limit, start)
#                api_url = (
#                        f"https://api.zotero.org/users/{userID}"
#                        f"/collections/{collectionID}/items?"
#                        f"key={apiKey}&limit={limit}&start={start}"
#                        f"&itemType=-attachment || note"
#                )
                
                QgsMessageLog.logMessage(api_url, 'LiteratureMapper', Qgis.Info)
                zotero_response = requests.get(api_url)
                return zotero_response
            
            #function to parse the Zotero API data
            def parse_zotero(zotero_response):
                '''parse the json into a usable object''' 
                parsed_data = json.loads(zotero_response.content.decode('utf-8'))
                return parsed_data
            
            '''
            def data_get(userID, collectionID, apiKey):
                #Alternative method of getting json that doesn't use requests.
                #Problem is we need the status not just the json returned.
                
                api_url = 'https://api.zotero.org/users/%s/collections/%s/items?v=3&key=%s&limit=100' % (userID, collectionID, apiKey)
                data_json = json.load(urllib.request.urlopen(api_url))
                return data_json
            '''
                        
            #Getting the variables the user entered
            self.userID = self.dlg.lineEdit_UserID.text()
            self.collectionID = self.dlg.lineEdit_CollectionKey.text()
            self.apiKey = self.dlg.lineEdit_APIKey.text()
            
            #Log the numbers the user entered
            QgsMessageLog.logMessage("User ID: %s" % self.userID, 'LiteratureMapper', Qgis.Info)
            QgsMessageLog.logMessage("Collection ID: %s" % self.collectionID, 'LiteratureMapper', Qgis.Info)
            QgsMessageLog.logMessage("API Key: %s" % self.apiKey, 'LiteratureMapper', Qgis.Info)
            
            #Send a Get Request to test the connection and get the collection data
            data = api_get(self.userID, self.collectionID, self.apiKey)
            #print zotero_response.status_code            
            # Check the status if 200 continue            
            data_parsed = parse_zotero(data)
            #data_json = data_get(self.userID, self.collectionID, self.apiKey)
            total = int(data.headers['Total-Results'])
            if (total > 100):
                # if total more than 100, page the request to get the remaining results and add them together
                # TODO: figure out how many requests to make
                # TODO: is zotero 0 or 1 indexed?
                pages = (ceil(total/100))
                for i in range(1,pages):
                    start = (i*100)
                    more = api_get(self.userID, self.collectionID, self.apiKey, limit=100, start=start)
                    data_parsed = data_parsed+parse_zotero(more)
            data_json = data_parsed
            
            #Filter the records to remove the Notes which contain no information about the citation
            # List of keys to be deleted from dictionary
            selectedKeys = list() 
            # Find the keys to delete, specifically the "note" items
            for i, record in enumerate(data_json):
                    if record['data']['itemType'] in ['note', 'attachment']:
                        #del data_json[i]
                        selectedKeys.append(i)
            #Reverse the order of the keys to work on the last one first.
            #If you delete an index, the number of the indexes after it are changed, so we have to start at the end
            selectedKeys.sort(reverse = True)
            # Iterate over the list and delete corresponding key from dictionary
            for key in selectedKeys:
                del data_json[key]
                    
            #if the server response = 200, start the window that records geometry from map canvas clicks.
            if data.status_code == 200:
                #self.iface.messageBar().pushMessage("Zotero is ready!", level=1)
                #open a new interface
                self.dlgTable.show()
                
                #put the data into a table in the interface
                self.dlgTable.tableWidget_Zotero.setRowCount(len(data_json))
                self.dlgTable.tableWidget_Zotero.verticalHeader().setVisible(False)
                
                #Create the empty Point shapefile memory layer
                self.pointLayer = QgsVectorLayer("Point?crs=epsg:4326", "Literature_Points", "memory")
                self.pointProvider = self.pointLayer.dataProvider()
                
                QgsProject.instance().addMapLayer(self.pointLayer)
                # add fields
                self.pointProvider.addAttributes([QgsField("Key", QVariant.String),
                    QgsField("Year",  QVariant.String),
                    QgsField("Author", QVariant.String),
                    QgsField("Title", QVariant.String),
                    QgsField("Geometry", QVariant.String)
                    ])
                self.pointLayer.updateFields() # tell the vector layer to fetch changes from the provider
                
                #Create the empty shapefile memory layer
                self.multipointLayer = QgsVectorLayer("Multipoint?crs=epsg:4326", "Literature_Multipoints", "memory")
                self.multipointProvider = self.multipointLayer.dataProvider()
                QgsProject.instance().addMapLayer(self.multipointLayer)
                # add fields
                self.multipointProvider.addAttributes([QgsField("Key", QVariant.String),
                    QgsField("Year",  QVariant.String),
                    QgsField("Author", QVariant.String),
                    QgsField("Title", QVariant.String),
                    QgsField("Geometry", QVariant.String)
                    ])
                self.multipointLayer.updateFields() # tell the vector layer to fetch changes from the provider
                
                for i, record in enumerate(data_json):
                    #if record['data']['itemType'] == 'note': continue
                    key = QTableWidgetItem(record['data']['key'])
                    self.dlgTable.tableWidget_Zotero.setItem(i, 0, key)
                    key_str = record['data']['key']

                    year = QTableWidgetItem(record['data']['date'])
                    self.dlgTable.tableWidget_Zotero.setItem(i, 1, year)
                    year_str = record['data']['date']
                    
                    author_list = ""
                    # Handle different athor types - Human has lastName, Corporate has name, Others get a blank because they are presumably blanks
                    for j, author in enumerate(record['data']['creators']):
                        if 'lastName' in author:
                            new_author = author['lastName']
                        elif 'name' in author:
                            new_author = author['name']
                        else:
                            new_author = ""
                            
                        author_list = author_list + ', ' + new_author
                    author_list = author_list[2 : len(author_list)]
                    self.dlgTable.tableWidget_Zotero.setItem(i, 2, QTableWidgetItem(author_list))
                    
                    title = QTableWidgetItem(record['data']['title'])
                    self.dlgTable.tableWidget_Zotero.setItem(i, 3, title)
                    title_str = record['data']['title']
                    
                    ########## Putting the Extra field into the table
                    # pre-populate the table with anything already in the Extra field
                    if 'extra' in record['data']:
                        #extra = QTableWidgetItem(record['data']['extra'])
                        
                        extra_zotero = record['data']['extra']
                        
                        #example of how to pull out the geojson string from a messy Extra field:
                        #geojson_str = text_extra[text_extra.find("<geojson>")+9:text_extra.find("</geojson>")]
                                         
                                                
                        if '<geojson>' in extra_zotero:
                            extra_str = extra_zotero[extra_zotero.find('<geojson>')+9:extra_zotero.find('</geojson>')]
                            #before_geojson = extra_zotero[0 : extra_zotero.find("<geojson>")]
                            #after_geojson = extra_zotero[extra_zotero.find("</geojson>")+10:]
                        elif '{"type":' in extra_zotero:
                            extra_str = extra_zotero[extra_zotero.find('{'):extra_zotero.find('}')]
                        else: extra_str = ''
                        
                        extra = QTableWidgetItem(extra_str)
                        
                        
                        #prints the extra string to the log
                        QgsMessageLog.logMessage("Extra String: %s" % extra_str, 'LiteratureMapper', Qgis.Info)
                        
                        
                        
                        check_point = '"type": "Point"'
                        check_multipoint = '"type": "Multip'
                        if extra_str[1:16] == check_point:
                            coords = extra_str[extra_str.find('['): extra_str.find(']')+1]
                            x = float(coords[1:coords.find(',')])
                            y = float(coords[coords.find(',')+1:coords.find(']')])
                            #put records with existing geometries into the virtual Point shapefile attribute table
                            self.fet = QgsFeature()
                            self.fet.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x,y)))
                            self.fet.setAttributes([key_str, year_str, author_list, title_str, extra_str])
                            self.pointProvider.addFeatures([self.fet])
                            self.pointLayer.updateExtents()
                        elif extra_str[1:16] == check_multipoint:
                            #Alter to make a multipoint
                            #Needs a loop to run through all the points?
                            QgsMessageLog.logMessage("Made it into Multipoint Elif", 'LiteratureMapper', Qgis.Info)
                            
                            # Coords needs to be formatted this way: gPolygon = QgsGeometry.fromPolygon([[QgsPoint(1, 1), QgsPoint(2, 2), QgsPoint(2, 1)]])
                            coords = extra_str[(extra_str.find('[')+1): (len(extra_str)-2)]
                            #looks like this: [-132.58038861948805, 36.36773760268237], [-126.90494519104253, 33.262306292778206], [-124.28139115336488, 36.84961487490887]
                            QgsMessageLog.logMessage("Coords: %s" % coords, 'LiteratureMapper', Qgis.Info)
                            #Replace [ with [( and add QgsPoint
                            p=re.compile( '\[' )
                            c = p.sub('QgsPointXY(', str(coords))
                            #Replace ] with )]
                            q = re.compile( '\]' )
                            coords_list = q.sub(')', str(c))
                            
                            coords_list = '['+coords_list+']'
                            QgsMessageLog.logMessage("Coords_List: %s" % coords_list, 'LiteratureMapper', Qgis.Info)
                            
                            #put records with existing geometries into the virtual Multipoint shapefile attribute table
                            self.fet = QgsFeature()
                            #does QgsPoint make a multipoint or do you need another command?
                            self.fet.setGeometry(QgsGeometry.fromMultiPointXY(eval(coords_list)))
                            #^change 1,1 back to x,y
                            self.fet.setAttributes([key_str, year_str, author_list, title_str, extra_str])
                            self.multipointProvider.addFeatures([self.fet])
                            self.multipointLayer.updateExtents()
                        else:
                            x = ''
                            QgsMessageLog.logMessage("Not a point or multipoint", 'LiteratureMapper', Qgis.Info)
                    else:
                        extra = QTableWidgetItem("")
                    self.dlgTable.tableWidget_Zotero.setItem(i, 4, extra)
                    

                
                # Reize the cells to fit the contents - behaves badly with the title column
                #self.dlgTable.tableWidget_Zotero.resizeRowsToContents()
                
                # Resize the Key and Year columns to fit the width of the contents
                self.dlgTable.tableWidget_Zotero.resizeColumnToContents(0)
                self.dlgTable.tableWidget_Zotero.resizeColumnToContents(1)
                
                # FUNCTIONALITY
                # TODO: Put points on the map canvas: http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/canvas.html#rubber-bands-and-vertex-markers  Memory Layers: http://gis.stackexchange.com/questions/72877/how-to-load-a-memory-layer-into-map-canvas-an-zoom-to-it-with-pyqgis  http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/vector.html#memory-provider
                # TODO: Transform coordinates if not in WGS84: http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/crs.html
                # TODO: update points in the memory shp don't just add new ones
                
                # USABILITY
                # TODO: Speed up saving to Zotero - will sending one query be quicker?  How does the version stuff work?
                # TODO: Make other table columns uneditable: http://stackoverflow.com/questions/2574115/qt-how-to-make-a-column-in-qtablewidget-read-only
                # TODO: Documentation
                
                
            else:
                self.iface.messageBar().pushMessage("Zotero cannot connect. Check the IDs you entered and try again.", level=1)
Example #16
0
 def fromMultiPointXY(mp):
     try:
         return QgsGeometry.fromMultiPointXY([QgsPointXY(p) for p in mp])
     except AttributeError:
         return QgsGeometry.fromMultiPoint(mp)
Example #17
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        # Retrieve the feature source and sink. The 'dest_id' variable is used
        # to uniquely identify the feature sink, and must be included in the
        # dictionary returned by the processAlgorithm function.
        source = self.parameterAsSource(parameters, self.INPUT, context)
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               source.wkbType(),
                                               source.sourceCrs())

        # Compute the number of steps to display within the progress bar and
        # get features from source
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        features = source.getFeatures()
        print(features)

        for current, feature in enumerate(features):
            # Stop the algorithm if cancel button has been clicked
            if feedback.isCanceled():
                break
            # sshuair begin
            geom = feature.geometry()
            attrs = feature.attributes()
            geom_type = geom.wkbType()

            feature_new = QgsFeature()

            # Point
            if geom_type == 1:
                vertices = geom.asPoint()
                vert_new = bd2gcj(vertices[0], vertices[1])
                feature_new.setGeometry(
                    QgsGeometry.fromPointXY(
                        QgsPointXY(vert_new[0], vert_new[1])))

            # LineString
            elif geom_type == 2:
                vert_new = []
                vertices = geom.asPolyline()
                for pt in vertices:
                    pt_new = bd2gcj(pt[0], pt[1])
                    vert_new.append(QgsPointXY(pt_new[0], pt_new[1]))
                feature_new.setGeometry(QgsGeometry.fromPolylineXY(vert_new))

            # Polygon
            elif geom_type == 3:
                vertices = geom.asPolygon()
                vert_new = []
                for ring in vertices:
                    ring_vert = []
                    for pt in ring:
                        pt_new = bd2gcj(pt[0], pt[1])
                        ring_vert.append(QgsPointXY(pt_new[0], pt_new[1]))
                    vert_new.append(ring_vert)
                feature_new.setGeometry(QgsGeometry.fromPolygonXY(vert_new))

            # MultiPoint
            elif geom_type == 4:
                vert_new = []
                vertices = geom.asMultiPoint()
                for pt in vertices:
                    pt_new = bd2gcj(pt[0], pt[1])
                    vert_new.append(QgsPointXY(pt_new[0], pt_new[1]))
                feature_new.setGeometry(QgsGeometry.fromMultiPointXY(vert_new))

            # MultiLineString
            elif geom_type == 5:
                vertices = geom.asMultiPolyline()
                vert_new = []
                for part in vertices:
                    linestring = []
                    for pt in part:
                        pt_new = bd2gcj(pt[0], pt[1])
                        linestring.append(QgsPointXY(pt_new[0], pt_new[1]))
                    vert_new.append(linestring)
                feature_new.setGeometry(
                    QgsGeometry.fromMultiPolylineXY(vert_new))

            # MultiPolygon
            elif geom_type == 6:
                vertices = geom.asMultiPolygon()
                vert_new = []
                for part in vertices:
                    poly = []
                    for ring in part:
                        ring_vert = []
                        for pt in ring:
                            pt_new = bd2gcj(pt[0], pt[1])
                            ring_vert.append(QgsPointXY(pt_new[0], pt_new[1]))
                        poly.append(ring_vert)
                    vert_new.append(poly)
                feature_new.setGeometry(
                    QgsGeometry.fromMultiPolygonXY(vert_new))
            else:
                continue

            feature_new.setAttributes(attrs)
            # sshuair end

            # feature = feature+0.1
            # Add a feature in the sink
            sink.addFeature(feature_new, QgsFeatureSink.FastInsert)

            # Update the progress bar
            feedback.setProgress(int(current * total))
            print(feature)

        # Return the results of the algorithm. In this case our only result is
        # the feature sink which contains the processed features, but some
        # algorithms may return multiple feature sinks, calculated numeric
        # statistics, etc. These should all be included in the returned
        # dictionary, with keys matching the feature corresponding parameter
        # or output names.
        return {self.OUTPUT: dest_id}
Example #18
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        # Retrieve the feature source and sink. The 'dest_id' variable is used
        # to uniquely identify the feature sink, and must be included in the
        # dictionary returned by the processAlgorithm function.
        startPoint = self.parameterAsPoint(
            parameters,
            self.START_POINT,
            context,
            # crs = QgsCoordinateReferenceSystem("EPSG:2193")
            crs=QgsCoordinateReferenceSystem(QgsProject().instance().crs()))

        # If source was not found, throw an exception to indicate that the algorithm
        # encountered a fatal error. The exception text can be any string, but in this
        # case we use the pre-built invalidSourceError method to return a standard
        # helper text for when a source cannot be evaluated
        if startPoint is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.START_POINT))

        fields = QgsFields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('coordinates', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        (point_sink, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT,
            context,
            fields,
            QgsWkbTypes.MultiPoint,
            # source.wkbType(),
            # QgsCoordinateReferenceSystem("EPSG:2193")
            crs=QgsCoordinateReferenceSystem(QgsProject().instance().crs()))

        results = {}

        # If sink was not created, throw an exception to indicate that the algorithm
        # encountered a fatal error. The exception text can be any string, but in this
        # case we use the pre-built invalidSinkError method to return a standard
        # helper text for when a sink cannot be evaluated
        if point_sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))

        if point_sink is not None:
            results[self.OUTPUT] = dest_id
            # geomPoints = QgsGeometry.fromMultiPointXY(points)
            geomPoints = QgsGeometry.fromMultiPointXY([startPoint])
            feat.setGeometry(geomPoints)
            feat['type'] = 'point'
            feat['coordinates'] = startPoint.toString()
            point_sink.addFeature(feat, QgsFeatureSink.FastInsert)

        # Return the results of the algorithm. In this case our only result is
        # the feature sink which contains the processed features, but some
        # algorithms may return multiple feature sinks, calculated numeric
        # statistics, etc. These should all be included in the returned
        # dictionary, with keys matching the feature corresponding parameter
        # or output names.

        #return {self.OUTPUT: dest_id}
        return results
Example #19
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        startPoints = self.parameterAsSource(parameters, self.START_POINTS, context)
        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context)

        directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context)
        defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        fields = startPoints.fields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(),
                                  True,
                                  tolerance)

        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromLayer', 'Loading start points…'))
        request = QgsFeatureRequest()
        request.setDestinationCrs(network.sourceCrs(), context.transformContext())
        features = startPoints.getFeatures(request)
        total = 100.0 / startPoints.featureCount() if startPoints.featureCount() else 0

        points = []
        source_attributes = {}
        i = 0
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                continue

            for p in f.geometry().vertices():
                points.append(QgsPointXY(p))
                source_attributes[i] = f.attributes()
                i += 1

            feedback.setProgress(int(current * total))

        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromLayer', 'Building graph…'))
        snappedPoints = director.makeGraph(builder, points, feedback)

        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromLayer', 'Calculating service areas…'))
        graph = builder.graph()

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.MultiPoint, network.sourceCrs())

        vertices = []
        upperBoundary = []
        lowerBoundary = []
        total = 100.0 / len(snappedPoints) if snappedPoints else 1
        for i, p in enumerate(snappedPoints):
            if feedback.isCanceled():
                break

            idxStart = graph.findVertex(snappedPoints[i])
            origPoint = points[i].toString()

            tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
            for j, v in enumerate(cost):
                if v > travelCost and tree[j] != -1:
                    vertexId = graph.edge(tree[j]).fromVertex()
                    if cost[vertexId] <= travelCost:
                        vertices.append(j)

            for j in vertices:
                upperBoundary.append(graph.vertex(graph.edge(tree[j]).toVertex()).point())
                lowerBoundary.append(graph.vertex(graph.edge(tree[j]).fromVertex()).point())

            geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
            geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)

            feat.setGeometry(geomUpper)

            attrs = source_attributes[i]
            attrs.extend(['upper', origPoint])
            feat.setAttributes(attrs)
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            feat.setGeometry(geomLower)
            attrs[-2] = 'lower'
            feat.setAttributes(attrs)
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            vertices[:] = []
            upperBoundary[:] = []
            lowerBoundary[:] = []

            feedback.setProgress(int(i * total))

        return {self.OUTPUT: dest_id}
Example #20
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        startPoints = self.parameterAsSource(parameters, self.START_POINTS, context)
        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context)

        directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context)
        defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        fields = startPoints.fields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(),
                                  True,
                                  tolerance)

        feedback.pushInfo(self.tr('Loading start points...'))
        request = QgsFeatureRequest()
        request.setDestinationCrs(network.sourceCrs())
        features = startPoints.getFeatures(request)
        total = 100.0 / startPoints.featureCount() if startPoints.featureCount() else 0

        points = []
        source_attributes = {}
        i = 0
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                continue

            for p in f.geometry().vertices():
                points.append(QgsPointXY(p))
                source_attributes[i] = f.attributes()
                i += 1

            feedback.setProgress(int(current * total))

        feedback.pushInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, points, feedback)

        feedback.pushInfo(self.tr('Calculating service areas...'))
        graph = builder.graph()

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.MultiPoint, network.sourceCrs())

        vertices = []
        upperBoundary = []
        lowerBoundary = []
        total = 100.0 / len(snappedPoints) if snappedPoints else 1
        for i, p in enumerate(snappedPoints):
            if feedback.isCanceled():
                break

            idxStart = graph.findVertex(snappedPoints[i])
            origPoint = points[i].toString()

            tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
            for j, v in enumerate(cost):
                if v > travelCost and tree[j] != -1:
                    vertexId = graph.edge(tree[j]).fromVertex()
                    if cost[vertexId] <= travelCost:
                        vertices.append(j)

            for j in vertices:
                upperBoundary.append(graph.vertex(graph.edge(tree[j]).toVertex()).point())
                lowerBoundary.append(graph.vertex(graph.edge(tree[j]).fromVertex()).point())

            geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
            geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)

            feat.setGeometry(geomUpper)

            attrs = source_attributes[i]
            attrs.extend(['upper', origPoint])
            feat.setAttributes(attrs)
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            feat.setGeometry(geomLower)
            attrs[-2] = 'lower'
            feat.setAttributes(attrs)
            sink.addFeature(feat, QgsFeatureSink.FastInsert)

            vertices[:] = []
            upperBoundary[:] = []
            lowerBoundary[:] = []

            feedback.setProgress(int(i * total))

        return {self.OUTPUT: dest_id}
Example #21
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        if source is None:
            raise QgsProcessingException(
                self.invalidSourceError(parameters, self.INPUT))

        buf = self.parameterAsDouble(parameters, self.BUFFER, context)
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               QgsWkbTypes.Polygon,
                                               source.sourceCrs())
        if sink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT))
        outFeat = QgsFeature()
        extent = source.sourceExtent()
        extraX = extent.width() * (buf / 100.0)
        # Adjust the extent
        extent.setXMinimum(extent.xMinimum() - extraX)
        extent.setXMaximum(extent.xMaximum() + extraX)
        extraY = extent.height() * (buf / 100.0)
        extent.setYMinimum(extent.yMinimum() - extraY)
        extent.setYMaximum(extent.yMaximum() + extraY)
        height = extent.height()
        width = extent.width()
        c = voronoi.Context()
        pts = []
        ptDict = {}
        ptNdx = -1
        # Find the minimum and maximum x and y for the input points
        xmin = width
        xmax = 0
        ymin = height
        ymax = 0
        features = source.getFeatures()
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        for current, inFeat in enumerate(features):
            if feedback.isCanceled():
                break
            geom = inFeat.geometry()
            point = geom.asPoint()
            x = point.x() - extent.xMinimum()
            y = point.y() - extent.yMinimum()
            pts.append((x, y))
            ptNdx += 1
            ptDict[ptNdx] = inFeat.id()
            if x < xmin:
                xmin = x
            if y < ymin:
                ymin = y
            if x > xmax:
                xmax = x
            if y > ymax:
                ymax = y
            feedback.setProgress(int(current * total))
        if xmin == xmax or ymin == ymax:
            raise QgsProcessingException('The extent of the input points is '
                                         'not a polygon (all the points are '
                                         'on a vertical or horizontal line) '
                                         '- cannot make a Voronoi diagram!')
        xyminmax = [xmin, ymin, xmax, ymax]
        if len(pts) < 3:
            raise QgsProcessingException(
                self.tr('Input file should contain at least 3 points. Choose '
                        'another file and try again.'))
        # Eliminate duplicate points
        uniqueSet = set(item for item in pts)
        ids = [pts.index(item) for item in uniqueSet]
        sl = voronoi.SiteList([voronoi.Site(i[0], i[1], sitenum=j)
                               for (j, i) in enumerate(uniqueSet)])
        voronoi.voronoi(sl, c)
        if len(c.polygons) == 0:
            raise QgsProcessingException(
                self.tr('There were no polygons created.'))

        inFeat = QgsFeature()
        current = 0
        total = 100.0 / len(c.polygons)
        # Clip each of the generated "polygons"
        for (site, edges) in list(c.polygons.items()):
            if feedback.isCanceled():
                break
            request = QgsFeatureRequest().setFilterFid(ptDict[ids[site]])
            inFeat = next(source.getFeatures(request))
            boundarypoints = self.clip_voronoi(edges, c, width,
                                               height, extent,
                                               inFeat.geometry().asPoint(),
                                               xyminmax)
            ptgeom = QgsGeometry.fromMultiPointXY(boundarypoints)
            geom = QgsGeometry(ptgeom.convexHull())
            outFeat.setGeometry(geom)
            outFeat.setAttributes(inFeat.attributes())
            sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
            current += 1
            feedback.setProgress(int(current * total))
        return {self.OUTPUT: dest_id}
Example #22
0
    def processAlgorithm(self, parameters, context, feedback):
        """
        Here is where the processing itself takes place.
        """

        source = self.parameterAsVectorLayer(parameters, self.INPUT, context)
        key_field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
        if key_field_name:
            field = source.fields().field(key_field_name)
            key_field_type = field.type()
            key_field_type_name = field.typeName()
        alpha = self.parameterAsDouble(parameters, self.ALPHA, context)
        sink_fields = QgsFields()
        if key_field_name:
            sink_fields.append(QgsField(key_field_name, key_field_type))
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                fields = sink_fields,
                geometryType = QgsWkbTypes.Polygon,
                crs = source.crs())
        points_groups = {}

        # Create a temporary layer for the Delaunay triangles that will form our hulls
        if key_field_name:
            if key_field_type_name == 'int':
                hull_triangles = QgsVectorLayer(f'polygon?field={key_field_name}:integer', 'α-shapes triangles', 'memory')
            elif key_field_type_name == 'double' or key_field_type_name == 'float':
                hull_triangles = QgsVectorLayer(f'polygon?field={key_field_name}:double', 'α-shapes triangles', 'memory')
            else:
                hull_triangles = QgsVectorLayer(f'polygon?field={key_field_name}:string', 'α-shapes triangles', 'memory')
        else:
            hull_triangles = QgsVectorLayer(f'polygon', 'α-shapes triangles', 'memory')
        hull_triangles.startEditing()

        # Extract points from layer, grouped by key
        if key_field_name:
            feedback.pushConsoleInfo(f'Extracting points and grouping by {key_field_name}...')
        else:
            feedback.pushConsoleInfo('Extracting points...')
        for f in source.getFeatures():
            if key_field_name:
                group_key = f.attribute(key_field_name)
                if group_key == None or group_key == 'NULL':
                    continue
            else:
                group_key = None
            points_groups.setdefault(group_key, [])
            points_groups[group_key].append(f.geometry().asPoint())

            if feedback.isCanceled():
                    return {self.OUTPUT: None}

        # Go through each cluster of points and generate Delaunay triangulations.
        # Use these triangulations to generate triangle polygons that will form the
        # α-shapes.
        if key_field_name:
            feedback.pushConsoleInfo(f'Creating Delaunay triangulations, keyed by {key_field_name}...')
        else:
            feedback.pushConsoleInfo('Creating Delaunay triangulation...')
        for group_key, points_group in points_groups.items():
            tri_polys = QgsGeometry.fromMultiPointXY(points_group).delaunayTriangulation()

            # Go through each triangle in the cluster triangulation.
            # If the circumscribed radius < alpha, we include it in the concave hull.
            #
            # NB: Alpha is expressed as a proportion of the max circumscribed radius length:
            #
            # alpha = 1 means that every triangle will be included in the hull
            # alpha = 0.5 means that only triangles with circumscribed radii that are 50%
            #   as long or shorter than the longest circumscribed radius will be included
            #   in the hull
            # alpha = 0 means that no triangles will be included in the hull
            triangle_vertices = [list(tri_poly.vertices()) for tri_poly in tri_polys.parts()]
            circumscribed_radii = [QgsTriangle(*vertices[0:-1]).circumscribedRadius() for vertices in triangle_vertices]
            if len(circumscribed_radii) > 0:
                sorted_radii = circumscribed_radii.copy()
                sorted_radii.sort()
                expanded_alpha = sorted_radii[
                    max(
                        min(
                            round(
                                alpha * len(sorted_radii)
                            ),
                            len(sorted_radii) - 1
                        ),
                        0
                    )
                ]
            else:
                expanded_alpha = 0

            for i, vertices in enumerate(triangle_vertices):
                if circumscribed_radii[i] <= expanded_alpha:
                    feature = QgsFeature(sink_fields)
                    feature.setGeometry(QgsGeometry.fromPolygonXY(
                        [[QgsPointXY(vertex) for vertex in vertices]]
                    ))
                    if key_field_name:
                        feature.setAttribute(key_field_name, group_key)
                    hull_triangles.addFeature(feature)

                if feedback.isCanceled():
                    return {self.OUTPUT: None}

        feedback.pushConsoleInfo(f'Committing {hull_triangles.featureCount()} triangles...')
        hull_triangles.commitChanges()

        # Dissolve triangles into hulls
        feedback.pushConsoleInfo('Dissolving triangles into hulls...')
        hulls = processing.run(
            'native:dissolve',
            {
                'INPUT': hull_triangles,
                'FIELD': key_field_name,
                'OUTPUT': 'memory:'
            },
            context = context,
            feedback = feedback
        )['OUTPUT']

        sink.addFeatures(hulls.getFeatures())

        sink.flushBuffer()
        feedback.setProgress(100)

        # Return the results of the algorithm. In this case our only result is
        # the feature sink which contains the processed features, but some
        # algorithms may return multiple feature sinks, calculated numeric
        # statistics, etc. These should all be included in the returned
        # dictionary, with keys matching the feature corresponding parameter
        # or output names.
        return {self.OUTPUT: dest_id}
Example #23
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        startPoint = self.parameterAsPoint(parameters, self.START_POINT, context, network.sourceCrs())
        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context)

        directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context)
        defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(),
                                  True,
                                  tolerance)
        feedback.pushInfo(self.tr('Building graph...'))
        snappedPoints = director.makeGraph(builder, [startPoint], feedback)

        feedback.pushInfo(self.tr('Calculating service area...'))
        graph = builder.graph()
        idxStart = graph.findVertex(snappedPoints[0])

        tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
        vertices = []
        for i, v in enumerate(cost):
            if v > travelCost and tree[i] != -1:
                vertexId = graph.edge(tree[i]).fromVertex()
                if cost[vertexId] <= travelCost:
                    vertices.append(i)

        upperBoundary = []
        lowerBoundary = []
        for i in vertices:
            upperBoundary.append(graph.vertex(graph.edge(tree[i]).toVertex()).point())
            lowerBoundary.append(graph.vertex(graph.edge(tree[i]).fromVertex()).point())

        feedback.pushInfo(self.tr('Writing results...'))

        fields = QgsFields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
        geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               fields, QgsWkbTypes.MultiPoint, network.sourceCrs())

        feat.setGeometry(geomUpper)
        feat['type'] = 'upper'
        feat['start'] = startPoint.toString()
        sink.addFeature(feat, QgsFeatureSink.FastInsert)

        feat.setGeometry(geomLower)
        feat['type'] = 'lower'
        feat['start'] = startPoint.toString()
        sink.addFeature(feat, QgsFeatureSink.FastInsert)

        upperBoundary.append(startPoint)
        lowerBoundary.append(startPoint)
        geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
        geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)

        return {self.OUTPUT: dest_id}
    def run(self):
        """Run method that performs all the real work"""
        # make our clickTool the tool that we'll use for now
        self.canvas.setMapTool(self.clickTool)

        # show the dialog
        self.dlg.show()
        self.read()
        # Run the dialog event loop
        result = self.dlg.exec_()
        self.store()
        # See if OK was pressed

        if result == 1:
            # send the API request
            #function to send a get request  # arrange the input into an API call that checks with Zotero

            def api_get(userID, collectionID, apiKey, limit=100, start=0):
                '''
                Make the API request.
                Filtering out notes and attachments drastically reduces the number of items, should save time.
                '''
                api_url = 'https://api.zotero.org/users/%s/collections/%s/items?key=%s&limit=%s&start=%s&itemType=-attachment || note' % (
                    userID, collectionID, apiKey, limit, start)
                #                api_url = (
                #                        f"https://api.zotero.org/users/{userID}"
                #                        f"/collections/{collectionID}/items?"
                #                        f"key={apiKey}&limit={limit}&start={start}"
                #                        f"&itemType=-attachment || note"
                #                )

                QgsMessageLog.logMessage(api_url, 'LiteratureMapper',
                                         Qgis.Info)
                zotero_response = requests.get(api_url)
                return zotero_response

            #function to parse the Zotero API data
            def parse_zotero(zotero_response):
                '''parse the json into a usable object'''
                parsed_data = json.loads(
                    zotero_response.content.decode('utf-8'))
                return parsed_data

            '''
            def data_get(userID, collectionID, apiKey):
                #Alternative method of getting json that doesn't use requests.
                #Problem is we need the status not just the json returned.
                
                api_url = 'https://api.zotero.org/users/%s/collections/%s/items?v=3&key=%s&limit=100' % (userID, collectionID, apiKey)
                data_json = json.load(urllib.request.urlopen(api_url))
                return data_json
            '''

            #Getting the variables the user entered
            self.userID = self.dlg.lineEdit_UserID.text()
            self.collectionID = self.dlg.lineEdit_CollectionKey.text()
            self.apiKey = self.dlg.lineEdit_APIKey.text()

            #Log the numbers the user entered
            QgsMessageLog.logMessage("User ID: %s" % self.userID,
                                     'LiteratureMapper', Qgis.Info)
            QgsMessageLog.logMessage("Collection ID: %s" % self.collectionID,
                                     'LiteratureMapper', Qgis.Info)
            QgsMessageLog.logMessage("API Key: %s" % self.apiKey,
                                     'LiteratureMapper', Qgis.Info)

            #Send a Get Request to test the connection and get the collection data
            data = api_get(self.userID, self.collectionID, self.apiKey)
            #print zotero_response.status_code
            # Check the status if 200 continue
            data_parsed = parse_zotero(data)
            #data_json = data_get(self.userID, self.collectionID, self.apiKey)
            total = int(data.headers['Total-Results'])
            if (total > 100):
                # if total more than 100, page the request to get the remaining results and add them together
                # TODO: figure out how many requests to make
                # TODO: is zotero 0 or 1 indexed?
                pages = (ceil(total / 100))
                for i in range(1, pages):
                    start = (i * 100)
                    more = api_get(self.userID,
                                   self.collectionID,
                                   self.apiKey,
                                   limit=100,
                                   start=start)
                    data_parsed = data_parsed + parse_zotero(more)
            data_json = data_parsed

            #Filter the records to remove the Notes which contain no information about the citation
            # List of keys to be deleted from dictionary
            selectedKeys = list()
            # Find the keys to delete, specifically the "note" items
            for i, record in enumerate(data_json):
                if record['data']['itemType'] in ['note', 'attachment']:
                    #del data_json[i]
                    selectedKeys.append(i)
            #Reverse the order of the keys to work on the last one first.
            #If you delete an index, the number of the indexes after it are changed, so we have to start at the end
            selectedKeys.sort(reverse=True)
            # Iterate over the list and delete corresponding key from dictionary
            for key in selectedKeys:
                del data_json[key]

            #if the server response = 200, start the window that records geometry from map canvas clicks.
            if data.status_code == 200:
                #self.iface.messageBar().pushMessage("Zotero is ready!", level=1)
                #open a new interface
                self.dlgTable.show()

                #put the data into a table in the interface
                self.dlgTable.tableWidget_Zotero.setRowCount(len(data_json))
                self.dlgTable.tableWidget_Zotero.verticalHeader().setVisible(
                    False)

                #Create the empty Point shapefile memory layer
                self.pointLayer = QgsVectorLayer("Point?crs=epsg:4326",
                                                 "Literature_Points", "memory")
                self.pointProvider = self.pointLayer.dataProvider()

                QgsProject.instance().addMapLayer(self.pointLayer)
                # add fields
                self.pointProvider.addAttributes([
                    QgsField("Key", QVariant.String),
                    QgsField("Year", QVariant.String),
                    QgsField("Author", QVariant.String),
                    QgsField("Title", QVariant.String),
                    QgsField("Geometry", QVariant.String)
                ])
                self.pointLayer.updateFields(
                )  # tell the vector layer to fetch changes from the provider

                #Create the empty shapefile memory layer
                self.multipointLayer = QgsVectorLayer(
                    "Multipoint?crs=epsg:4326", "Literature_Multipoints",
                    "memory")
                self.multipointProvider = self.multipointLayer.dataProvider()
                QgsProject.instance().addMapLayer(self.multipointLayer)
                # add fields
                self.multipointProvider.addAttributes([
                    QgsField("Key", QVariant.String),
                    QgsField("Year", QVariant.String),
                    QgsField("Author", QVariant.String),
                    QgsField("Title", QVariant.String),
                    QgsField("Geometry", QVariant.String)
                ])
                self.multipointLayer.updateFields(
                )  # tell the vector layer to fetch changes from the provider

                for i, record in enumerate(data_json):
                    #if record['data']['itemType'] == 'note': continue
                    key = QTableWidgetItem(record['data']['key'])
                    self.dlgTable.tableWidget_Zotero.setItem(i, 0, key)
                    key_str = record['data']['key']

                    year = QTableWidgetItem(record['data']['date'])
                    self.dlgTable.tableWidget_Zotero.setItem(i, 1, year)
                    year_str = record['data']['date']

                    author_list = ""
                    # Handle different athor types - Human has lastName, Corporate has name, Others get a blank because they are presumably blanks
                    for j, author in enumerate(record['data']['creators']):
                        if 'lastName' in author:
                            new_author = author['lastName']
                        elif 'name' in author:
                            new_author = author['name']
                        else:
                            new_author = ""

                        author_list = author_list + ', ' + new_author
                    author_list = author_list[2:len(author_list)]
                    self.dlgTable.tableWidget_Zotero.setItem(
                        i, 2, QTableWidgetItem(author_list))

                    title = QTableWidgetItem(record['data']['title'])
                    self.dlgTable.tableWidget_Zotero.setItem(i, 3, title)
                    title_str = record['data']['title']

                    ########## Putting the Extra field into the table
                    # pre-populate the table with anything already in the Extra field
                    if 'extra' in record['data']:
                        #extra = QTableWidgetItem(record['data']['extra'])

                        extra_zotero = record['data']['extra']

                        #example of how to pull out the geojson string from a messy Extra field:
                        #geojson_str = text_extra[text_extra.find("<geojson>")+9:text_extra.find("</geojson>")]

                        if '<geojson>' in extra_zotero:
                            extra_str = extra_zotero[
                                extra_zotero.find('<geojson>') +
                                9:extra_zotero.find('</geojson>')]
                            #before_geojson = extra_zotero[0 : extra_zotero.find("<geojson>")]
                            #after_geojson = extra_zotero[extra_zotero.find("</geojson>")+10:]
                        elif '{"type":' in extra_zotero:
                            extra_str = extra_zotero[
                                extra_zotero.find('{'):extra_zotero.find('}')]
                        else:
                            extra_str = ''

                        extra = QTableWidgetItem(extra_str)

                        #prints the extra string to the log
                        QgsMessageLog.logMessage(
                            "Extra String: %s" % extra_str, 'LiteratureMapper',
                            Qgis.Info)

                        check_point = '"type": "Point"'
                        check_multipoint = '"type": "Multip'
                        if extra_str[1:16] == check_point:
                            coords = extra_str[extra_str.
                                               find('['):extra_str.find(']') +
                                               1]
                            x = float(coords[1:coords.find(',')])
                            y = float(coords[coords.find(',') +
                                             1:coords.find(']')])
                            #put records with existing geometries into the virtual Point shapefile attribute table
                            self.fet = QgsFeature()
                            self.fet.setGeometry(
                                QgsGeometry.fromPointXY(QgsPointXY(x, y)))
                            self.fet.setAttributes([
                                key_str, year_str, author_list, title_str,
                                extra_str
                            ])
                            self.pointProvider.addFeatures([self.fet])
                            self.pointLayer.updateExtents()
                        elif extra_str[1:16] == check_multipoint:
                            #Alter to make a multipoint
                            #Needs a loop to run through all the points?
                            QgsMessageLog.logMessage(
                                "Made it into Multipoint Elif",
                                'LiteratureMapper', Qgis.Info)

                            # Coords needs to be formatted this way: gPolygon = QgsGeometry.fromPolygon([[QgsPoint(1, 1), QgsPoint(2, 2), QgsPoint(2, 1)]])
                            coords = extra_str[(extra_str.find('[') +
                                                1):(len(extra_str) - 2)]
                            #looks like this: [-132.58038861948805, 36.36773760268237], [-126.90494519104253, 33.262306292778206], [-124.28139115336488, 36.84961487490887]
                            QgsMessageLog.logMessage("Coords: %s" % coords,
                                                     'LiteratureMapper',
                                                     Qgis.Info)
                            #Replace [ with [( and add QgsPoint
                            p = re.compile('\[')
                            c = p.sub('QgsPointXY(', str(coords))
                            #Replace ] with )]
                            q = re.compile('\]')
                            coords_list = q.sub(')', str(c))

                            coords_list = '[' + coords_list + ']'
                            QgsMessageLog.logMessage(
                                "Coords_List: %s" % coords_list,
                                'LiteratureMapper', Qgis.Info)

                            #put records with existing geometries into the virtual Multipoint shapefile attribute table
                            self.fet = QgsFeature()
                            #does QgsPoint make a multipoint or do you need another command?
                            self.fet.setGeometry(
                                QgsGeometry.fromMultiPointXY(
                                    eval(coords_list)))
                            #^change 1,1 back to x,y
                            self.fet.setAttributes([
                                key_str, year_str, author_list, title_str,
                                extra_str
                            ])
                            self.multipointProvider.addFeatures([self.fet])
                            self.multipointLayer.updateExtents()
                        else:
                            x = ''
                            QgsMessageLog.logMessage(
                                "Not a point or multipoint",
                                'LiteratureMapper', Qgis.Info)
                    else:
                        extra = QTableWidgetItem("")
                    self.dlgTable.tableWidget_Zotero.setItem(i, 4, extra)

                # Reize the cells to fit the contents - behaves badly with the title column
                #self.dlgTable.tableWidget_Zotero.resizeRowsToContents()

                # Resize the Key and Year columns to fit the width of the contents
                self.dlgTable.tableWidget_Zotero.resizeColumnToContents(0)
                self.dlgTable.tableWidget_Zotero.resizeColumnToContents(1)

                # FUNCTIONALITY
                # TODO: Put points on the map canvas: http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/canvas.html#rubber-bands-and-vertex-markers  Memory Layers: http://gis.stackexchange.com/questions/72877/how-to-load-a-memory-layer-into-map-canvas-an-zoom-to-it-with-pyqgis  http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/vector.html#memory-provider
                # TODO: Transform coordinates if not in WGS84: http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/crs.html
                # TODO: update points in the memory shp don't just add new ones

                # USABILITY
                # TODO: Speed up saving to Zotero - will sending one query be quicker?  How does the version stuff work?
                # TODO: Make other table columns uneditable: http://stackoverflow.com/questions/2574115/qt-how-to-make-a-column-in-qtablewidget-read-only
                # TODO: Documentation

            else:
                self.iface.messageBar().pushMessage(
                    "Zotero cannot connect. Check the IDs you entered and try again.",
                    level=1)
Example #25
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        buf = self.parameterAsDouble(parameters, self.BUFFER, context)
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, source.fields(),
                                               QgsWkbTypes.Polygon,
                                               source.sourceCrs())

        outFeat = QgsFeature()
        extent = source.sourceExtent()
        extraX = extent.height() * (buf / 100.0)
        extraY = extent.width() * (buf / 100.0)
        height = extent.height()
        width = extent.width()
        c = voronoi.Context()
        pts = []
        ptDict = {}
        ptNdx = -1

        features = source.getFeatures()
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        for current, inFeat in enumerate(features):
            if feedback.isCanceled():
                break
            geom = inFeat.geometry()
            point = geom.asPoint()
            x = point.x() - extent.xMinimum()
            y = point.y() - extent.yMinimum()
            pts.append((x, y))
            ptNdx += 1
            ptDict[ptNdx] = inFeat.id()
            feedback.setProgress(int(current * total))

        if len(pts) < 3:
            raise QgsProcessingException(
                self.tr('Input file should contain at least 3 points. Choose '
                        'another file and try again.'))

        uniqueSet = set(item for item in pts)
        ids = [pts.index(item) for item in uniqueSet]
        sl = voronoi.SiteList([
            voronoi.Site(i[0], i[1], sitenum=j)
            for (j, i) in enumerate(uniqueSet)
        ])
        voronoi.voronoi(sl, c)
        inFeat = QgsFeature()

        current = 0
        if len(c.polygons) == 0:
            raise QgsProcessingException(
                self.tr('There were no polygons created.'))

        total = 100.0 / len(c.polygons)

        for (site, edges) in list(c.polygons.items()):
            if feedback.isCanceled():
                break

            request = QgsFeatureRequest().setFilterFid(ptDict[ids[site]])
            inFeat = next(source.getFeatures(request))
            lines = self.clip_voronoi(edges, c, width, height, extent, extraX,
                                      extraY)

            geom = QgsGeometry.fromMultiPointXY(lines)
            geom = QgsGeometry(geom.convexHull())
            outFeat.setGeometry(geom)
            outFeat.setAttributes(inFeat.attributes())
            sink.addFeature(outFeat, QgsFeatureSink.FastInsert)

            current += 1
            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
Example #26
0
    def on_btnRun_clicked(self):
                
        if self.inputfile == '':
            QMessageBox.critical(self,'Map Creator',
                                 'Please specify input coordinate file.')
            return
        
        if self.outputfile == '':
            QMessageBox.critical(self,'Map Creator',
                                 'Please specify output shapefile.')
            return
        
        self.setCursor(Qt.WaitCursor)

        #Open coordinate input file
        f = open(self.inputfile, 'r')
        lines = f.readlines()
        f.close()
        header = lines[0].split(',')[0]
        totfeat = len(lines) - 1
        lines.pop(0)
        lines.reverse() 
        
        #Create vector layer
        basename = os.path.basename(self.outputfile)
        vlayer = QgsVectorLayer("Polygon", basename, "memory")
        vprovider = vlayer.dataProvider()
        fld = QgsField(header,QVariant.String)
        flds = QgsFields()
        flds.append(fld)
        vprovider.addAttributes([fld])
        vlayer.startEditing()
                
        hull = []
        for cnt, line in enumerate(lines):
            line = line.rstrip().split(',')
            numcoords = int((len(line) - 1) / 2)
            hull[:] = []
            geom = QgsGeometry()
            feat = QgsFeature()
            feat.setFields(flds)
            for i in range(numcoords):
                hull.append(QgsPointXY(float(line[i*2+1]),float(line[i*2+2])))
            geom = geom.fromMultiPointXY(hull)
            geom = geom.convexHull()
            feat.setGeometry(geom)
            feat.setAttribute(header,str(line[0]))
            result = vlayer.addFeature(feat)
            if not result:
                self.setCursor(Qt.ArrowCursor)
                QMessageBox.critical(self,'Map Creator', 'Processing error.')
                return
            self.ui.ProgressBar.setValue(float(cnt+1)/float(totfeat) * 100.0)
            QApplication.processEvents()  
        vlayer.commitChanges()
        vlayer.updateExtents()
        
        #Write the output shapefile
        if os.path.exists(self.outputfile):
            QgsVectorFileWriter.deleteShapeFile(self.outputfile)
        voptions = QgsVectorFileWriter.SaveVectorOptions()
        voptions.driverName = 'ESRI Shapefile'
        voptions.fileEncoding = 'utf-8'
        result = QgsVectorFileWriter.writeAsVectorFormat(vlayer, 
                                                         self.outputfile, 
                                                         voptions)

        if result[0] != 0:
            QMessageBox.critical(self,'Map Creator','Error creating shapefile.')
        else: #Ask to add shapfile to map
            name = QFileInfo(self.outputfile).completeBaseName()
            result = QMessageBox.question(self,'Map Creator',
                                          'Add shapefile to map?',
                                          QMessageBox.Yes,
                                          QMessageBox.No)
            if result == QMessageBox.Yes:
                self.iface.addVectorLayer(self.outputfile, name, 'ogr')
                
        self.setCursor(Qt.ArrowCursor)
Example #27
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        startPoint = self.parameterAsPoint(parameters, self.START_POINT,
                                           context, network.sourceCrs())
        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST,
                                            context)

        directionFieldName = self.parameterAsString(parameters,
                                                    self.DIRECTION_FIELD,
                                                    context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD,
                                              context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD,
                                               context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH,
                                           context)
        defaultDirection = self.parameterAsEnum(parameters,
                                                self.DEFAULT_DIRECTION,
                                                context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD,
                                                context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED,
                                              context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network, directionField,
                                          forwardValue, backwardValue,
                                          bothValue, defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(
            distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField, defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance)
        feedback.pushInfo(
            QCoreApplication.translate('ServiceAreaFromPoint',
                                       'Building graph…'))
        snappedPoints = director.makeGraph(builder, [startPoint], feedback)

        feedback.pushInfo(
            QCoreApplication.translate('ServiceAreaFromPoint',
                                       'Calculating service area…'))
        graph = builder.graph()
        idxStart = graph.findVertex(snappedPoints[0])

        tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
        vertices = []
        for i, v in enumerate(cost):
            if v > travelCost and tree[i] != -1:
                vertexId = graph.edge(tree[i]).fromVertex()
                if cost[vertexId] <= travelCost:
                    vertices.append(i)

        upperBoundary = []
        lowerBoundary = []
        for i in vertices:
            upperBoundary.append(
                graph.vertex(graph.edge(tree[i]).toVertex()).point())
            lowerBoundary.append(
                graph.vertex(graph.edge(tree[i]).fromVertex()).point())

        feedback.pushInfo(
            QCoreApplication.translate('ServiceAreaFromPoint',
                                       'Writing results…'))

        fields = QgsFields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
        geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)

        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT,
                                               context, fields,
                                               QgsWkbTypes.MultiPoint,
                                               network.sourceCrs())

        feat.setGeometry(geomUpper)
        feat['type'] = 'upper'
        feat['start'] = startPoint.toString()
        sink.addFeature(feat, QgsFeatureSink.FastInsert)

        feat.setGeometry(geomLower)
        feat['type'] = 'lower'
        feat['start'] = startPoint.toString()
        sink.addFeature(feat, QgsFeatureSink.FastInsert)

        upperBoundary.append(startPoint)
        lowerBoundary.append(startPoint)
        geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
        geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)

        return {self.OUTPUT: dest_id}
Example #28
0
    def processAlgorithm(self, parameters, context, feedback):
        source = self.parameterAsSource(parameters, self.INPUT, context)
        buf = self.parameterAsDouble(parameters, self.BUFFER, context)
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                               source.fields(), QgsWkbTypes.Polygon, source.sourceCrs())

        outFeat = QgsFeature()
        extent = source.sourceExtent()
        extraX = extent.height() * (buf / 100.0)
        extraY = extent.width() * (buf / 100.0)
        height = extent.height()
        width = extent.width()
        c = voronoi.Context()
        pts = []
        ptDict = {}
        ptNdx = -1

        features = source.getFeatures()
        total = 100.0 / source.featureCount() if source.featureCount() else 0
        for current, inFeat in enumerate(features):
            if feedback.isCanceled():
                break
            geom = inFeat.geometry()
            point = geom.asPoint()
            x = point.x() - extent.xMinimum()
            y = point.y() - extent.yMinimum()
            pts.append((x, y))
            ptNdx += 1
            ptDict[ptNdx] = inFeat.id()
            feedback.setProgress(int(current * total))

        if len(pts) < 3:
            raise QgsProcessingException(
                self.tr('Input file should contain at least 3 points. Choose '
                        'another file and try again.'))

        uniqueSet = set(item for item in pts)
        ids = [pts.index(item) for item in uniqueSet]
        sl = voronoi.SiteList([voronoi.Site(i[0], i[1], sitenum=j) for (j,
                                                                        i) in enumerate(uniqueSet)])
        voronoi.voronoi(sl, c)
        inFeat = QgsFeature()

        current = 0
        if len(c.polygons) == 0:
            raise QgsProcessingException(
                self.tr('There were no polygons created.'))

        total = 100.0 / len(c.polygons)

        for (site, edges) in list(c.polygons.items()):
            if feedback.isCanceled():
                break

            request = QgsFeatureRequest().setFilterFid(ptDict[ids[site]])
            inFeat = next(source.getFeatures(request))
            lines = self.clip_voronoi(edges, c, width, height, extent, extraX, extraY)

            geom = QgsGeometry.fromMultiPointXY(lines)
            geom = QgsGeometry(geom.convexHull())
            outFeat.setGeometry(geom)
            outFeat.setAttributes(inFeat.attributes())
            sink.addFeature(outFeat, QgsFeatureSink.FastInsert)

            current += 1
            feedback.setProgress(int(current * total))

        return {self.OUTPUT: dest_id}
Example #29
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        if network is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        startPoints = self.parameterAsSource(parameters, self.START_POINTS, context)
        if startPoints is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.START_POINTS))

        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context)

        directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context)
        defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        include_bounds = True # default to true to maintain 3.0 API
        if self.INCLUDE_BOUNDS in parameters:
            include_bounds = self.parameterAsBoolean(parameters, self.INCLUDE_BOUNDS, context)

        fields = startPoints.fields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(),
                                  True,
                                  tolerance)

        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromLayer', 'Loading start points…'))
        request = QgsFeatureRequest()
        request.setDestinationCrs(network.sourceCrs(), context.transformContext())
        features = startPoints.getFeatures(request)
        total = 100.0 / startPoints.featureCount() if startPoints.featureCount() else 0

        points = []
        source_attributes = {}
        i = 0
        for current, f in enumerate(features):
            if feedback.isCanceled():
                break

            if not f.hasGeometry():
                continue

            for p in f.geometry().vertices():
                points.append(QgsPointXY(p))
                source_attributes[i] = f.attributes()
                i += 1

            feedback.setProgress(int(current * total))

        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromLayer', 'Building graph…'))
        snappedPoints = director.makeGraph(builder, points, feedback)

        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromLayer', 'Calculating service areas…'))
        graph = builder.graph()

        (point_sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                                     fields, QgsWkbTypes.MultiPoint, network.sourceCrs())
        (line_sink, line_dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LINES, context,
                                                         fields, QgsWkbTypes.MultiLineString, network.sourceCrs())

        total = 100.0 / len(snappedPoints) if snappedPoints else 1
        for i, p in enumerate(snappedPoints):
            if feedback.isCanceled():
                break

            idxStart = graph.findVertex(snappedPoints[i])
            origPoint = points[i].toString()

            tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)

            vertices = set()
            area_points = []
            lines = []
            for vertex, start_vertex_cost in enumerate(cost):
                inbound_edge_index = tree[vertex]
                if inbound_edge_index == -1 and vertex != idxStart:
                    # unreachable vertex
                    continue

                if start_vertex_cost > travelCost:
                    # vertex is too expensive, discard
                    continue

                vertices.add(vertex)
                start_point = graph.vertex(vertex).point()

                # find all edges coming from this vertex
                for edge_id in graph.vertex(vertex).outgoingEdges():
                    edge = graph.edge(edge_id)
                    end_vertex_cost = start_vertex_cost + edge.cost(0)
                    end_point = graph.vertex(edge.toVertex()).point()
                    if end_vertex_cost <= travelCost:
                        # end vertex is cheap enough to include
                        vertices.add(edge.toVertex())
                        lines.append([start_point, end_point])
                    else:
                        # travelCost sits somewhere on this edge, interpolate position
                        interpolated_end_point = QgsGeometryUtils.interpolatePointOnLineByValue(start_point.x(), start_point.y(), start_vertex_cost,
                                                                                                end_point.x(), end_point.y(), end_vertex_cost, travelCost)
                        area_points.append(interpolated_end_point)
                        lines.append([start_point, interpolated_end_point])

            for v in vertices:
                area_points.append(graph.vertex(v).point())

            feat = QgsFeature()
            if point_sink is not None:
                geomPoints = QgsGeometry.fromMultiPointXY(area_points)
                feat.setGeometry(geomPoints)
                attrs = source_attributes[i]
                attrs.extend(['within', origPoint])
                feat.setAttributes(attrs)
                point_sink.addFeature(feat, QgsFeatureSink.FastInsert)

                if include_bounds:
                    upperBoundary = []
                    lowerBoundary = []

                    vertices = []
                    for vertex, c in enumerate(cost):
                        if c > travelCost and tree[vertex] != -1:
                            vertexId = graph.edge(tree[vertex]).fromVertex()
                            if cost[vertexId] <= travelCost:
                                vertices.append(vertex)

                    for v in vertices:
                        upperBoundary.append(graph.vertex(graph.edge(tree[v]).toVertex()).point())
                        lowerBoundary.append(graph.vertex(graph.edge(tree[v]).fromVertex()).point())

                    geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
                    geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)

                    feat.setGeometry(geomUpper)
                    attrs[-2] = 'upper'
                    feat.setAttributes(attrs)
                    point_sink.addFeature(feat, QgsFeatureSink.FastInsert)

                    feat.setGeometry(geomLower)
                    attrs[-2] = 'lower'
                    feat.setAttributes(attrs)
                    point_sink.addFeature(feat, QgsFeatureSink.FastInsert)

            if line_sink is not None:
                geom_lines = QgsGeometry.fromMultiPolylineXY(lines)
                feat.setGeometry(geom_lines)
                attrs = source_attributes[i]
                attrs.extend(['lines', origPoint])
                feat.setAttributes(attrs)
                line_sink.addFeature(feat, QgsFeatureSink.FastInsert)

            feedback.setProgress(int(i * total))

        results = {}
        if point_sink is not None:
            results[self.OUTPUT] = dest_id
        if line_sink is not None:
            results[self.OUTPUT_LINES] = line_dest_id
        return results
Example #30
0
    def convexHullEntitySetToPolygon(self, entitySet, removeOriginals=False):
        """
      modifica il poligono corrente in modo che includa tutti i punti delle geometrie di entitySet
      """
        layerList = []
        layerList.append(self.poligonEntity.layer)
        pointsForConvexHull = []

        for layerEntitySet in entitySet.layerEntitySetList:
            layer = layerEntitySet.layer
            coordTransform = QgsCoordinateTransform(
                layer.crs(), self.poligonEntity.layer.crs(),
                QgsProject.instance())

            for featureId in layerEntitySet.featureIds:
                f = layerEntitySet.getFeature(featureId)
                # trasformo la geometria nel crs del layer del poligono da modificare
                geom = f.geometry()
                geom.transform(coordTransform)

                # Riduco la geometria in point o polyline
                simplifiedGeoms = qad_utils.asPointOrPolyline(geom)
                for simplifiedGeom in simplifiedGeoms:
                    if simplifiedGeom.wkbType(
                    ) == QgsWkbTypes.LineString or simplifiedGeom.wkbType(
                    ) == QgsWkbTypes.LineString25D:
                        pointsForConvexHull.extend(simplifiedGeom.asPolyline())
                    else:
                        pointsForConvexHull.append(simplifiedGeom.asPoint())

                if removeOriginals and layer.id(
                ) != self.poligonEntity.layerId():
                    layerList.append(layer)

        geom = QgsGeometry.fromMultiPointXY(pointsForConvexHull)
        geom = geom.convexHull()
        if geom is None:
            self.showMsg(QadMsg.translate("QAD", "Invalid object."))
            return False

        f = self.poligonEntity.getFeature()
        f.setGeometry(geom)

        self.plugIn.beginEditCommand("Feature edited", layerList)

        # plugIn, layer, feature, refresh, check_validity
        if qad_layer.updateFeatureToLayer(self.plugIn,
                                          self.poligonEntity.layer, f, False,
                                          False) == False:
            self.plugIn.destroyEditCommand()
            return False

        if removeOriginals:
            for layerEntitySet in entitySet.layerEntitySetList:
                if qad_layer.deleteFeaturesToLayer(self.plugIn,
                                                   layerEntitySet.layer,
                                                   layerEntitySet.featureIds,
                                                   False) == False:
                    self.plugIn.destroyEditCommand()
                    return

        self.plugIn.endEditCommand()
        self.nOperationsToUndo = self.nOperationsToUndo + 1

        return True
Example #31
0
    def processAlgorithm(self, parameters, context, feedback):
        network = self.parameterAsSource(parameters, self.INPUT, context)
        if network is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))

        startPoint = self.parameterAsPoint(parameters, self.START_POINT, context, network.sourceCrs())
        strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
        travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context)

        directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context)
        forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context)
        backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context)
        bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context)
        defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context)
        speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context)
        defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
        tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

        include_bounds = True # default to true to maintain 3.0 API
        if self.INCLUDE_BOUNDS in parameters:
            include_bounds = self.parameterAsBool(parameters, self.INCLUDE_BOUNDS, context)

        directionField = -1
        if directionFieldName:
            directionField = network.fields().lookupField(directionFieldName)
        speedField = -1
        if speedFieldName:
            speedField = network.fields().lookupField(speedFieldName)

        director = QgsVectorLayerDirector(network,
                                          directionField,
                                          forwardValue,
                                          backwardValue,
                                          bothValue,
                                          defaultDirection)

        distUnit = context.project().crs().mapUnits()
        multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
        if strategy == 0:
            strategy = QgsNetworkDistanceStrategy()
        else:
            strategy = QgsNetworkSpeedStrategy(speedField,
                                               defaultSpeed,
                                               multiplier * 1000.0 / 3600.0)

        director.addStrategy(strategy)
        builder = QgsGraphBuilder(network.sourceCrs(),
                                  True,
                                  tolerance)
        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromPoint', 'Building graph…'))
        snappedPoints = director.makeGraph(builder, [startPoint], feedback)

        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromPoint', 'Calculating service area…'))
        graph = builder.graph()
        idxStart = graph.findVertex(snappedPoints[0])

        tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
        vertices = set()
        points = []
        lines = []

        for vertex, start_vertex_cost in enumerate(cost):
            inbound_edge_index = tree[vertex]
            if inbound_edge_index == -1 and vertex != idxStart:
                # unreachable vertex
                continue

            if start_vertex_cost > travelCost:
                # vertex is too expensive, discard
                continue

            vertices.add(vertex)
            start_point = graph.vertex(vertex).point()

            # find all edges coming from this vertex
            for edge_id in graph.vertex(vertex).outgoingEdges():
                edge = graph.edge(edge_id)
                end_vertex_cost = start_vertex_cost + edge.cost(0)
                end_point = graph.vertex(edge.toVertex()).point()
                if end_vertex_cost <= travelCost:
                    # end vertex is cheap enough to include
                    vertices.add(edge.toVertex())
                    lines.append([start_point, end_point])
                else:
                    # travelCost sits somewhere on this edge, interpolate position
                    interpolated_end_point = QgsGeometryUtils.interpolatePointOnLineByValue(start_point.x(), start_point.y(), start_vertex_cost,
                                                                                            end_point.x(), end_point.y(), end_vertex_cost, travelCost)
                    points.append(interpolated_end_point)
                    lines.append([start_point, interpolated_end_point])

        for i in vertices:
            points.append(graph.vertex(i).point())

        feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromPoint', 'Writing results…'))

        fields = QgsFields()
        fields.append(QgsField('type', QVariant.String, '', 254, 0))
        fields.append(QgsField('start', QVariant.String, '', 254, 0))

        feat = QgsFeature()
        feat.setFields(fields)

        (point_sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
                                                     fields, QgsWkbTypes.MultiPoint, network.sourceCrs())

        results = {}

        if point_sink is not None:
            results[self.OUTPUT] = dest_id
            geomPoints = QgsGeometry.fromMultiPointXY(points)
            feat.setGeometry(geomPoints)
            feat['type'] = 'within'
            feat['start'] = startPoint.toString()
            point_sink.addFeature(feat, QgsFeatureSink.FastInsert)

            if include_bounds:
                upperBoundary = []
                lowerBoundary = []

                vertices = []
                for i, v in enumerate(cost):
                    if v > travelCost and tree[i] != -1:
                        vertexId = graph.edge(tree[i]).fromVertex()
                        if cost[vertexId] <= travelCost:
                            vertices.append(i)

                for i in vertices:
                    upperBoundary.append(graph.vertex(graph.edge(tree[i]).toVertex()).point())
                    lowerBoundary.append(graph.vertex(graph.edge(tree[i]).fromVertex()).point())

                geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
                geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)

                feat.setGeometry(geomUpper)
                feat['type'] = 'upper'
                feat['start'] = startPoint.toString()
                point_sink.addFeature(feat, QgsFeatureSink.FastInsert)

                feat.setGeometry(geomLower)
                feat['type'] = 'lower'
                feat['start'] = startPoint.toString()
                point_sink.addFeature(feat, QgsFeatureSink.FastInsert)

        (line_sink, line_dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LINES, context,
                                                         fields, QgsWkbTypes.MultiLineString, network.sourceCrs())
        if line_sink is not None:
            results[self.OUTPUT_LINES] = line_dest_id
            geom_lines = QgsGeometry.fromMultiPolylineXY(lines)
            feat.setGeometry(geom_lines)
            feat['type'] = 'lines'
            feat['start'] = startPoint.toString()
            line_sink.addFeature(feat, QgsFeatureSink.FastInsert)

        return results
    def generateDemandPoints(feature, parent):
        # Returns the location of points representing demand

        TOMsMessageLog.logMessage('generateDemandPoints: {}'.format(
            feature.attribute("GeometryID")),
                                  level=Qgis.Info)

        demand = feature.attribute("Demand")
        if demand == 0:
            return None

        capacity = feature.attribute("Capacity")

        nrSpaces = capacity - demand
        if nrSpaces < 0:
            nrSpaces = 0

        TOMsMessageLog.logMessage(
            'generateDemandPoints: capacity: {}; nrSpaces: {}; demand: {}'.
            format(capacity, nrSpaces, demand),
            level=Qgis.Info)

        # now get geometry for demand locations
        """
        #newFeature = QgsFeature(feature)
        currGeomShapeID = feature.attribute("GeomShapeID")
        if currGeomShapeID < 10:
            currGeomShapeID = currGeomShapeID + 20
        if currGeomShapeID >= 10 and currGeomShapeID < 20:
            currGeomShapeID = 21

        #newFeature.setAttribute("GeomShapeID", currGeomShapeID)"""

        try:
            #geomShowingSpaces = ElementGeometryFactory.getElementGeometry(newFeature)  # TODO: for some reason the details from newFeature are not "saved" and used
            #geomShowingSpaces = ElementGeometryFactory.getElementGeometry(feature, currGeomShapeID)
            geomShowingSpaces = ElementGeometryFactory.getElementGeometry(
                feature)
        except Exception as e:
            TOMsMessageLog.logMessage(
                'generateDemandPoints: error in expression function: {}'.
                format(e),
                level=Qgis.Warning)
            return None

        random.seed(
            1234
        )  # need to ramdomise, but it needs to be repeatable?!?, i.e., when you pan, they stay in the same place
        listBaysToDelete = []
        listBaysToDelete = random.sample(range(capacity),
                                         k=math.ceil(nrSpaces))

        # deal with split geometries - half on/half off
        if feature.attribute("GeomShapeID") == 22:
            for i in range(
                    capacity,
                (capacity * 2)):  # NB: range stops one before end ...
                listBaysToDelete.append(i)

        TOMsMessageLog.logMessage(
            'generateDemandPoints: bays to delete {}'.format(listBaysToDelete),
            level=Qgis.Info)

        centroidGeomList = []
        counter = 0
        for polygonGeom in geomShowingSpaces.parts():
            TOMsMessageLog.logMessage(
                'generateDemandPoints: considering part {}'.format(counter),
                level=Qgis.Info)
            if not counter in listBaysToDelete:
                centrePt = QgsPointXY(polygonGeom.centroid())
                TOMsMessageLog.logMessage(
                    'generateDemandPoints: adding centroid for {}: {}'.format(
                        counter, centrePt.asWkt()),
                    level=Qgis.Info)
                try:
                    centroidGeomList.append(centrePt)
                except Exception as e:
                    TOMsMessageLog.logMessage(
                        'generateDemandPoints: error adding centroid for counter {}: {}'
                        .format(counter, e),
                        level=Qgis.Warning)
            counter = counter + 1

        TOMsMessageLog.logMessage(
            'generateDemandPoints: nrDemandPoints {}'.format(
                len(centroidGeomList)),
            level=Qgis.Info)

        try:
            demandPoints = QgsGeometry.fromMultiPointXY(centroidGeomList)
        except Exception as e:
            TOMsMessageLog.logMessage(
                'generateDemandPoints: error creating final geom: {}'.format(
                    e),
                level=Qgis.Warning)

        return demandPoints