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)
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)
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
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]
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()
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()
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
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
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)
def fromMultiPointXY(mp): try: return QgsGeometry.fromMultiPointXY([QgsPointXY(p) for p in mp]) except AttributeError: return QgsGeometry.fromMultiPoint(mp)
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}
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
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}
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}
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 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}
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)
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}
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)
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}
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}
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
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
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