def find(self, to_find): if self.settings.value("qftsfilepath") == '': return if not self.isValid: self.message.emit("Cannot search in project. QuickFinder file is probably currently in use.",QgsMessageBar.WARNING) return # add star after each word except numbers to_find = to_find.split(' ') for i,word in enumerate(to_find): try: int(word) except ValueError: to_find[i] = '%%%s%%' % word to_find = ' '.join(to_find) # FTS request sql = "SELECT search_id,content,x,y,wkb_geom FROM quickfinder_data WHERE content LIKE ?" cur = self.conn.cursor() catLimit = self.settings.value("categoryLimit") totalLimit = self.settings.value("totalLimit") catFound = {} for row in cur.execute(sql, [to_find]): search_id, content, x, y, wkb_geom = row if search_id in catFound: if catFound[search_id] >= catLimit: continue catFound[search_id] += 1 else: catFound[search_id] = 1 if search_id not in self._searches: continue gs = self._searches[search_id].geometryStorage geometry = QgsGeometry() if gs == 'wkb': geometry.fromWkb(binascii.a2b_hex(wkb_geom)) else: # wkt or extent are stored as wkt geometry = geometry.fromWkt(wkb_geom) crs = QgsCoordinateReferenceSystem() crs.createFromString(self._searches[search_id].srid) self.result_found.emit(self, self._searches[search_id].searchName, content, geometry, crs.postgisSrid()) if sum(catFound.values()) >= totalLimit: break
def find(self, to_find): if self.settings.value("qftsfilepath") == '': return if not self.isValid: self.message.emit("Cannot search in project. QuickFinder file is probably currently in use.",QgsMessageBar.WARNING) return # add star after each word except numbers to_find = to_find.split(' ') for i,word in enumerate(to_find): try: int(word) except ValueError: to_find[i] = '%s*' % word to_find = ' '.join(to_find) # FTS request sql = "SELECT search_id,content,x,y,wkb_geom FROM quickfinder_data WHERE content MATCH ?" cur = self.conn.cursor() catLimit = self.settings.value("categoryLimit") totalLimit = self.settings.value("totalLimit") catFound = {} for row in cur.execute(sql, [to_find]): search_id, content, x, y, wkb_geom = row if catFound.has_key(search_id): if catFound[search_id] >= catLimit: continue catFound[search_id] += 1 else: catFound[search_id] = 1 if not self._searches.has_key(search_id): continue gs = self._searches[search_id].geometryStorage geometry = QgsGeometry() if gs == 'wkb': geometry.fromWkb(binascii.a2b_hex(wkb_geom)) else: # wkt or extent are stored as wkt geometry = geometry.fromWkt(wkb_geom) crs = QgsCoordinateReferenceSystem() crs.createFromString(self._searches[search_id].srid) self.result_found.emit(self, self._searches[search_id].searchName, content, geometry, crs.postgisSrid()) if sum(catFound.values()) >= totalLimit: break
def find(self, toFind): if self.settings.value("qftsfilepath") == "": return if not self.isValid: self.message.emit( "Cannot search in project. QuickFinder file is probably currently in use.", QgsMessageBar.WARNING ) return # add star after each word except numbers toFind = toFind.split(" ") for i, word in enumerate(toFind): try: int(word) except ValueError: toFind[i] = "%s*" % word toFind = " ".join(toFind) # FTS request sql = "SELECT search_id,content,x,y,wkb_geom FROM quickfinder_data WHERE content MATCH ?" cur = self.conn.cursor() cur.execute(sql, [toFind]) catLimit = self.settings.value("categoryLimit") totalLimit = self.settings.value("totalLimit") nFound = 0 catFound = {} while True: s = cur.fetchone() if s is None: return search_id, content, x, y, wkb_geom = s if catFound.has_key(search_id): if catFound[search_id] >= catLimit: continue catFound[search_id] += 1 else: catFound[search_id] = 1 if not self._searches.has_key(search_id): continue geometry = QgsGeometry() geometry.fromWkb(binascii.a2b_hex(wkb_geom)) crs = QgsCoordinateReferenceSystem() crs.createFromString(self._searches[search_id].srid) self.resultFound.emit(self, self._searches[search_id].searchName, content, geometry, crs.postgisSrid()) nFound += 1 if nFound >= totalLimit: break
def runAlgorithm(self): start = timeit.default_timer() self._id = str(np.random.randint(1, 5000)) logMessageFile = open( os.path.dirname(__file__) + "/logLCP" + self._id + ".txt", "w") logMessage = "LCPNetwork plugin init - loading points and base raster layers with id: " + self._id QgsMessageLog.logMessage(logMessage, tag="LCPNetwork", level=Qgis.Info) logMessageFile.write(logMessage + "\n") origins, destinations = self.loadPoints() baseRaster = self.loadBaseRaster() logMessage = "computing " + str( origins.featureCount()) + " origin points towards " + str( destinations.featureCount()) + " destinations" QgsMessageLog.logMessage(logMessage, tag="LCPNetwork", level=Qgis.Info) logMessageFile.write(logMessage + "\n") self.transform = baseRaster.GetGeoTransform() self.projection = baseRaster.GetProjection() nodata = baseRaster.GetRasterBand(1).GetNoDataValue() logMessage = "loading cost map with nodata value " + str(nodata) QgsMessageLog.logMessage(logMessage, tag="LCPNetwork", level=Qgis.Info) logMessageFile.write(logMessage + "\n") topLeft = QgsPointXY(self.transform[0], self.transform[3]) pointsListO = [] for point in origins.getFeatures(): pointsListO.append(point.geometry().asPoint()) pointsListD = [] for point in destinations.getFeatures(): pointsListD.append(point.geometry().asPoint()) ## create the list of lcps lcps = [] numThreads = os.cpu_count() logMessage = "creating " + str(numThreads) + " threads" QgsMessageLog.logMessage(logMessage, tag="LCPNetwork", level=Qgis.Info) logMessageFile.write(logMessage + "\n") pool = futures.ThreadPoolExecutor(numThreads) index = 0 results = [] for source in pointsListO: results.append( pool.submit(self.computeOnePath, source, index, start, baseRaster, pointsListD, lcps, logMessageFile)) index = index + 1 for future in futures.as_completed(results): logMessageFile.write(future.result() + "\n") logMessage = "all lcps computed at time: " + str( "%.2f" % (timeit.default_timer() - start)) QgsMessageLog.logMessage(logMessage, tag="LCPNetwork", level=Qgis.Info) logMessageFile.write(logMessage + "\n") # same CRS than base raster cost map crs = QgsCoordinateReferenceSystem() crs.createFromString(baseRaster.GetProjection()) for i in range(index): outputName = os.path.dirname( __file__) + "/distances" + self._id + "_" + str(i) + ".tif" newRasterQGIS = QgsRasterLayer( outputName, "distances" + self._id + "_" + str(i)) newRasterQGIS.setContrastEnhancement( QgsContrastEnhancement.StretchToMinimumMaximum) newRasterQGIS.setCrs(crs) QgsProject.instance().addMapLayer(newRasterQGIS) # add the list of lcps to the network layer network = self.iface.addVectorLayer("LineString?crs=" + crs.authid(), "least cost path network", "memory") network.dataProvider().addFeatures(lcps) logMessage = "LCPNetwork plugin finished! time (sec.): " + str( "%.2f" % (timeit.default_timer() - start)) QgsMessageLog.logMessage(logMessage, tag="LCPNetwork", level=Qgis.Info) logMessageFile.write(logMessage + "\n") logMessageFile.close()
def setCrs(self, authid): crs = QgsCoordinateReferenceSystem() crs.createFromString(authid) self.crsWidget.setCrs(crs)
class SurvexImport: """QGIS Plugin Implementation.""" # The following are some dictionaries for flags in the .3d file station_attr = { 0x01: 'SURFACE', 0x02: 'UNDERGROUND', 0x04: 'ENTRANCE', 0x08: 'EXPORTED', 0x10: 'FIXED', 0x20: 'ANON' } leg_attr = {0x01: 'SURFACE', 0x02: 'DUPLICATE', 0x04: 'SPLAY'} style_type = { 0x00: 'NORMAL', 0x01: 'DIVING', 0x02: 'CARTESIAN', 0x03: 'CYLPOLAR', 0x04: 'NOSURVEY', 0xff: 'NOSTYLE' } # lists of keys of above, sorted to restore ordering station_flags = sorted(station_attr.keys()) leg_flags = sorted(leg_attr.keys()) # field names if there is error data error_fields = ('ERROR_VERT', 'ERROR_HORIZ', 'ERROR', 'LENGTH') # main data structures leg_list = [] # accumulates legs + metadata station_list = [] # ditto stations xsect_list = [] # ditto for cross sections for walls station_xyz = {} # map station names to xyz coordinates crs = None # used to set layer CRS in memory provider crs_source = None # records the origin of the CRS title = '' # used to set layer title in memory provider path_3d = '' # to remember the path to the survex .3d file path_gpkg = '' # ditto for path to save GeoPackage (.gpkg) def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join(self.plugin_dir, 'i18n', 'SurvexImport_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&Import .3d file') # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads self.first_start = None # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('SurvexImport', message) def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: # Adds plugin icon to Plugins toolbar self.iface.addToolBarIcon(action) if add_to_menu: self.iface.addPluginToVectorMenu(self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ':/plugins/survex_import/icon.png' self.add_action(icon_path, text=self.tr(u'.3d import'), callback=self.run, parent=self.iface.mainWindow()) # will be set False in run() self.first_start = True def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginVectorMenu(self.tr(u'&Import .3d file'), action) self.iface.removeToolBarIcon(action) def crs_from_file(self): """Enforce consistent CRS selector state""" if self.dlg.CRSFromFile.isChecked(): self.dlg.CRSFromProject.setChecked(False) def crs_from_project(self): """Enforce consistent CRS selector state""" if self.dlg.CRSFromProject.isChecked(): self.dlg.CRSFromFile.setChecked(False) def select_3d_file(self): """Select 3d file""" file_3d, _filter_3d = QFileDialog.getOpenFileName( self.dlg, "Select .3d file ", self.path_3d, '*.3d') self.dlg.selectedFile.setText(file_3d) self.path_3d = QFileInfo(file_3d).path() # memorise path selection def select_gpkg(self): """Select GeoPackage (.gpkg)""" file_gpkg, _filter_gpkg = QFileDialog.getSaveFileName( self.dlg, "Enter or select existing .gpkg file ", self.path_gpkg, '*.gpkg') self.dlg.selectedGPKG.setText(file_gpkg) self.path_gpkg = QFileInfo(file_gpkg).path() # memorise path selection def set_crs(self, s): """Figure out the CRS for layer creation, from the selected options and/or string""" if self.dlg.CRSFromProject.isChecked(): self.crs_source = 'from project' self.crs = QgsProject.instance().crs() elif self.dlg.CRSFromFile.isChecked() and s: self.crs_source = 'from .3d file' self.crs = QgsCoordinateReferenceSystem() match = search('epsg:([0-9]*)', s) # check for epsg in proj string if match: # if found, use the EPSG number explicitly self.crs.createFromString('EPSG:{}'.format(int( match.group(1)))) else: # fall back to proj4 self.crs.createFromProj4(s) else: # fall back to raising a CRS selection dialog self.crs_source = 'from dialog' dialog = QgsProjectionSelectionDialog() dialog.setMessage('define the CRS for the imported layers') dialog.exec() # run the dialog .. self.crs = dialog.crs() # .. and recover the user input if self.crs.isValid(): msg = 'CRS {} : {}'.format(self.crs_source, self.crs.authid()) QgsMessageLog.logMessage(msg, tag='Import .3d', level=Qgis.Info) QgsMessageLog.logMessage(self.crs.description(), tag='Import .3d', level=Qgis.Info) else: # hopefully never happens msg = "CRS invalid!" QgsMessageLog.logMessage(msg, tag='Import .3d', level=Qgis.Info) self.crs = None def all_checked(self): """ ensures the import all check box is consistent""" check = self.dlg.Legs.isChecked() check = check and self.dlg.Stations.isChecked() check = check and self.dlg.Polygons.isChecked() check = check and self.dlg.Walls.isChecked() check = check and self.dlg.XSections.isChecked() check = check and self.dlg.Traverses.isChecked() check = check and self.dlg.LegsSurface.isChecked() check = check and self.dlg.LegsSplay.isChecked() check = check and self.dlg.LegsDuplicate.isChecked() check = check and self.dlg.StationsSurface.isChecked() self.dlg.ImportAll.setChecked(check) def toggle_import_all(self): """toggle import all possible data""" checked = self.dlg.ImportAll.isChecked() self.dlg.Legs.setChecked(checked) self.dlg.Stations.setChecked(checked) self.dlg.Polygons.setChecked(checked) self.dlg.Walls.setChecked(checked) self.dlg.XSections.setChecked(checked) self.dlg.Traverses.setChecked(checked) self.dlg.LegsSurface.setChecked(checked) self.dlg.LegsSplay.setChecked(checked) self.dlg.LegsDuplicate.setChecked(checked) self.dlg.StationsSurface.setChecked(checked) def add_layer(self, subtitle, geom): """Add a memory layer with title(subtitle) and geom""" name = '%s(%s)' % (self.title, subtitle) if self.title else subtitle layer = QgsVectorLayer(geom, name, 'memory') if self.crs: # this should have been set by now layer.setCrs(self.crs) if not layer.isValid(): raise Exception("Invalid layer with %s" % geom) msg = "Memory layer '%s' called '%s' added" % (geom, name) QgsMessageLog.logMessage(msg, tag='Import .3d', level=Qgis.Info) return layer # The next three routines are to do with reading .3d binary file format def read_xyz(self, fp): """Read xyz as integers, according to .3d spec""" return unpack('<iii', fp.read(12)) def read_len(self, fp): """Read a number as a length according to .3d spec""" byte = ord(fp.read(1)) if byte != 0xff: return byte else: return unpack('<I', fp.read(4))[0] def read_label(self, fp, current_label): """Read a string as a label, or part thereof, according to .3d spec""" byte = ord(fp.read(1)) if byte != 0x00: ndel = byte >> 4 nadd = byte & 0x0f else: ndel = self.read_len(fp) nadd = self.read_len(fp) oldlen = len(current_label) return current_label[:oldlen - ndel] + fp.read(nadd).decode('ascii') def run(self): """Run method that performs all the real work""" # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started if self.first_start == True: self.first_start = False self.dlg = SurvexImportDialog() self.dlg.selectedFile.clear() self.dlg.fileSelector.clicked.connect(self.select_3d_file) self.dlg.selectedGPKG.clear() self.dlg.GPKGSelector.clicked.connect(self.select_gpkg) self.dlg.CRSFromProject.setChecked(False) self.dlg.CRSFromFile.clicked.connect(self.crs_from_file) self.dlg.CRSFromFile.setChecked(False) self.dlg.CRSFromProject.clicked.connect(self.crs_from_project) self.dlg.ImportAll.clicked.connect(self.toggle_import_all) self.dlg.Legs.clicked.connect(self.all_checked) self.dlg.Stations.clicked.connect(self.all_checked) self.dlg.Polygons.clicked.connect(self.all_checked) self.dlg.Walls.clicked.connect(self.all_checked) self.dlg.XSections.clicked.connect(self.all_checked) self.dlg.Traverses.clicked.connect(self.all_checked) self.dlg.LegsSurface.clicked.connect(self.all_checked) self.dlg.LegsSplay.clicked.connect(self.all_checked) self.dlg.LegsDuplicate.clicked.connect(self.all_checked) self.dlg.StationsSurface.clicked.connect(self.all_checked) self.dlg.show() # show the dialog result = self.dlg.exec_() # Run the dialog event loop if result: # The user pressed OK, and this is what happened next! survex_3d = self.dlg.selectedFile.text() gpkg_file = self.dlg.selectedGPKG.text() include_legs = self.dlg.Legs.isChecked() include_stations = self.dlg.Stations.isChecked() include_polygons = self.dlg.Polygons.isChecked() include_walls = self.dlg.Walls.isChecked() include_xsections = self.dlg.XSections.isChecked() include_traverses = self.dlg.Traverses.isChecked() exclude_surface_legs = not self.dlg.LegsSurface.isChecked() exclude_splay_legs = not self.dlg.LegsSplay.isChecked() exclude_duplicate_legs = not self.dlg.LegsDuplicate.isChecked() exclude_surface_stations = not self.dlg.StationsSurface.isChecked() use_clino_wgt = self.dlg.UseClinoWeights.isChecked() include_up_down = self.dlg.IncludeUpDown.isChecked() discard_features = not self.dlg.KeepFeatures.isChecked() if not os.path.exists(survex_3d): raise Exception("File '%s' doesn't exist" % survex_3d) if discard_features: self.leg_list = [] self.station_list = [] self.station_xyz = {} self.xsect_list = [] # Read .3d file as binary, parse, and save data structures with open(survex_3d, 'rb') as fp: line = fp.readline().rstrip() # File ID check if not line.startswith(b'Survex 3D Image File'): raise IOError('Not a survex .3d file: ' + survex_3d) line = fp.readline().rstrip() # File format version if not line.startswith(b'v'): raise IOError('Unrecognised survex .3d version in ' + survex_3d) version = int(line[1:]) if version < 8: raise IOError('Survex .3d version >= 8 required in ' + survex_3d) line = fp.readline().rstrip( ) # Metadata (title and coordinate system) fields = line.split(b'\x00') previous_title = '' if discard_features else self.title if previous_title: self.title = previous_title + ' + ' + fields[0].decode( 'ascii') else: self.title = fields[0].decode('ascii') self.set_crs( fields[1].decode('ascii') if len(fields) > 1 else None) line = fp.readline().rstrip( ) # Timestamp, unused in present application if not line.startswith(b'@'): raise IOError('Unrecognised timestamp in ' + survex_3d) # timestamp = int(line[1:]) flag = ord(fp.read(1)) # file-wide flag if flag & 0x80: # abort if extended elevation raise IOError("Can't deal with extended elevation in " + survex_3d) # All file-wide header data read in, now read byte-wise # according to .3d spec. Note that all elements must # be processed, in order, otherwise we get out of sync. # We first define some baseline dates date0 = QDate(1900, 1, 1) date1 = QDate(1900, 1, 1) date2 = QDate(1900, 1, 1) label, style = '', 0xff # initialise label and style legs = [] # will be used to capture leg data between MOVEs xsect = [] # will be used to capture XSECT data nlehv = None # .. remains None if there isn't any error data... while True: # start of byte-gobbling while loop char = fp.read(1) if not char: # End of file (reached prematurely?) raise IOError('Premature end of file in ' + survex_3d) byte = ord(char) if byte <= 0x05: # STYLE if byte == 0x00 and style == 0x00: # this signals end of data if legs: # there may be a pending list of legs to save self.leg_list.append((legs, nlehv)) break # escape from byte-gobbling while loop else: style = byte elif byte <= 0x0e: # Reserved continue elif byte == 0x0f: # MOVE xyz = self.read_xyz(fp) if legs: self.leg_list.append((legs, nlehv)) legs = [] elif byte == 0x10: # DATE (none) date1 = date2 = date0 elif byte == 0x11: # DATE (single date) days = unpack('<H', fp.read(2))[0] date1 = date2 = date0.addDays(days) elif byte == 0x12: # DATE (date range, short format) days, extra = unpack('<HB', fp.read(3)) date1 = date0.addDays(days) date2 = date0.addDays(days + extra + 1) elif byte == 0x13: # DATE (date range, long format) days1, days2 = unpack('<HH', fp.read(4)) date1 = date0.addDays(days1) date2 = date0.addDays(days2) elif byte <= 0x1e: # Reserved continue elif byte == 0x1f: # Error info nlehv = unpack('<iiiii', fp.read(20)) elif byte <= 0x2f: # Reserved continue elif byte <= 0x33: # XSECT label = self.read_label(fp, label) if byte & 0x02: lrud = unpack('<iiii', fp.read(16)) else: lrud = unpack('<hhhh', fp.read(8)) xsect.append((label, lrud)) if byte & 0x01: # XSECT_END self.xsect_list.append(xsect) xsect = [] elif byte <= 0x3f: # Reserved continue elif byte <= 0x7f: # LINE flag = byte & 0x3f if not (flag & 0x20): label = self.read_label(fp, label) xyz_prev = xyz xyz = self.read_xyz(fp) while (True): # code pattern to implement logic if exclude_surface_legs and flag & 0x01: break if exclude_duplicate_legs and flag & 0x02: break if exclude_splay_legs and flag & 0x04: break legs.append(((xyz_prev, xyz), label, style, date1, date2, flag)) break elif byte <= 0xff: # LABEL (or NODE) flag = byte & 0x7f label = self.read_label(fp, label) xyz = self.read_xyz(fp) while (True): # code pattern to implement logic if exclude_surface_stations and flag & 0x01 and not flag & 0x02: break self.station_list.append((xyz, label, flag)) break self.station_xyz[label] = xyz # End of byte-gobbling while loop # file closes automatically, with open(survex_3d, 'rb') as fp: layers = [] # used to keep a list of the created layers if include_stations and self.station_list: # station layer station_layer = self.add_layer('stations', 'PointZ') attrs = [ QgsField(self.station_attr[k], QVariant.Int) for k in self.station_flags ] attrs.insert(0, QgsField('ELEVATION', QVariant.Double)) attrs.insert(0, QgsField('NAME', QVariant.String)) station_layer.dataProvider().addAttributes(attrs) station_layer.updateFields() features = [] for (xyz, label, flag) in self.station_list: xyz = [0.01 * v for v in xyz] attrs = [1 if flag & k else 0 for k in self.station_flags] attrs.insert(0, round(xyz[2], 2)) # elevation attrs.insert(0, label) feat = QgsFeature() geom = QgsGeometry(QgsPoint(*xyz)) feat.setGeometry(geom) feat.setAttributes(attrs) features.append(feat) station_layer.dataProvider().addFeatures(features) layers.append(station_layer) if include_legs and self.leg_list: # leg layer leg_layer = self.add_layer('legs', 'LineStringZ') attrs = [ QgsField(self.leg_attr[k], QVariant.Int) for k in self.leg_flags ] if nlehv: [ attrs.insert(0, QgsField(s, QVariant.Double)) for s in self.error_fields ] attrs.insert(0, QgsField('NLEGS', QVariant.Int)) attrs.insert(0, QgsField('DATE2', QVariant.Date)) attrs.insert(0, QgsField('DATE1', QVariant.Date)) attrs.insert(0, QgsField('STYLE', QVariant.String)) attrs.insert(0, QgsField('ELEVATION', QVariant.Double)) attrs.insert(0, QgsField('NAME', QVariant.String)) leg_layer.dataProvider().addAttributes(attrs) leg_layer.updateFields() features = [] for legs, nlehv in self.leg_list: for (xyz_pair, label, style, from_date, to_date, flag) in legs: elev = 0.5 * sum([0.01 * xyz[2] for xyz in xyz_pair]) points = [] for xyz in xyz_pair: xyz = [0.01 * v for v in xyz] points.append(QgsPoint(*xyz)) attrs = [1 if flag & k else 0 for k in self.leg_flags] if nlehv: [ attrs.insert(0, 0.01 * v) for v in reversed(nlehv[1:5]) ] attrs.insert(0, nlehv[0]) attrs.insert(0, to_date) attrs.insert(0, from_date) attrs.insert(0, self.style_type[style]) attrs.insert(0, round(elev, 2)) attrs.insert(0, label) linestring = QgsLineString() linestring.setPoints(points) feat = QgsFeature() geom = QgsGeometry(linestring) feat.setGeometry(geom) feat.setAttributes(attrs) features.append(feat) leg_layer.dataProvider().addFeatures(features) layers.append(leg_layer) # Now do wall features if asked if (include_traverses or include_xsections or include_walls or include_polygons) and self.xsect_list: trav_features = [] wall_features = [] xsect_features = [] quad_features = [] for xsect in self.xsect_list: if len(xsect) < 2: # if there's only one station .. continue # .. give up as we don't know which way to face centerline = [ ] # will contain the station position and LRUD data for label, lrud in xsect: xyz = self.station_xyz[ label] # look up coordinates from label lrud_or_zero = tuple([max(0, v) for v in lrud ]) # deal with missing data centerline.append( xyz + lrud_or_zero) # and collect as 7-uple direction = [ ] # will contain the corresponding direction vectors # The calculations below use integers for xyz and lrud, and # conversion to metres is left to the end. Then dh2 is an # integer and the test for a plumb is safely dh2 = 0. # The directions are unit vectors optionally weighted by # cos(inclination) = dh/dl where dh^2 = dx^2 + dy^2 (note, no dz^2), # and dl^2 = dh^2 + dz^2. The normalisation is correspondingly # either 1/dh, or 1/dh * dh/dl = 1/dl. for i, xyzlrud in enumerate(centerline): x, y, z = xyzlrud[0:3] if i > 0: dx, dy, dz = x - xp, y - yp, z - zp dh2 = dx * dx + dy * dy # integer horizontal displacement (mm^2) norm = sqrt(dh2 + dz * dz) if use_clino_wgt else sqrt(dh2) dx, dy = (dx / norm, dy / norm) if dh2 > 0 and norm > 0 else (0, 0) direction.append((dx, dy)) xp, yp, zp = x, y, z left_wall = [] right_wall = [] up_down = [] # We build the walls by walking through the list # of stations and directions, with simple defaults # for the start and end stations for i, (x, y, z, l, r, u, d) in enumerate(centerline): d1x, d1y = direction[i - 1] if i > 0 else (0, 0) d2x, d2y = direction[i] if i + 1 < len( centerline) else (0, 0) dx, dy = d1x + d2x, d1y + d2y # mean (sum of) direction vectors norm = sqrt(dx * dx + dy * dy) # normalise to unit vector ex, ey = (dx / norm, dy / norm) if norm > 0 else (0, 0) # Convert to metres when saving the points left_wall.append((0.01 * (x - l * ey), 0.01 * (y + l * ex), 0.01 * z)) right_wall.append((0.01 * (x + r * ey), 0.01 * (y - r * ex), 0.01 * z)) up_down.append((0.01 * u, 0.01 * d)) # Mean elevation of centerline, used for elevation attribute elev = 0.01 * sum([xyzlrud[2] for xyzlrud in centerline ]) / len(centerline) attrs = [round(elev, 2)] # Now create the feature sets - first the centerline traverse points = [] for xyzlrud in centerline: xyz = [0.01 * v for v in xyzlrud[0:3] ] # These were mm, convert to metres points.append(QgsPoint(*xyz)) linestring = QgsLineString() linestring.setPoints(points) feat = QgsFeature() geom = QgsGeometry(linestring) feat.setGeometry(geom) feat.setAttributes(attrs) trav_features.append(feat) # The walls as line strings for wall in (left_wall, right_wall): points = [QgsPoint(*xyz) for xyz in wall] linestring = QgsLineString() linestring.setPoints(points) feat = QgsFeature() geom = QgsGeometry(linestring) feat.setGeometry(geom) feat.setAttributes(attrs) wall_features.append(feat) # Slightly more elaborate, pair up points on left # and right walls, and build a cross section as a # 2-point line string, and a quadrilateral polygon # with a closed 5-point line string for the # exterior ring. Note that QGIS polygons are # supposed to have their points ordered clockwise. for i, xyz_pair in enumerate(zip(left_wall, right_wall)): elev = 0.01 * centerline[i][ 2] # elevation of station in centerline attrs = [round(elev, 2)] points = [QgsPoint(*xyz) for xyz in xyz_pair] linestring = QgsLineString() linestring.setPoints(points) feat = QgsFeature() geom = QgsGeometry(linestring) feat.setGeometry(geom) feat.setAttributes(attrs) xsect_features.append(feat) if i > 0: elev = 0.5 * (prev_xyz_pair[0][2] + xyz_pair[0][2] ) # average elevation attrs = [round(elev, 2)] if include_up_down: # average up / down attrs += [ 0.5 * (v1 + v2) for (v1, v2) in zip(up_down[i - 1], up_down[i]) ] points = [ ] # will contain the exterior 5-point ring, as follows... for xyz in tuple( reversed(prev_xyz_pair)) + xyz_pair + ( prev_xyz_pair[1], ): points.append(QgsPoint(*xyz)) linestring = QgsLineString() linestring.setPoints(points) polygon = QgsPolygon() polygon.setExteriorRing(linestring) feat = QgsFeature() geom = QgsGeometry(polygon) feat.setGeometry(geom) feat.setAttributes(attrs) quad_features.append(feat) prev_xyz_pair = xyz_pair # End of processing xsect_list - now add features to requested layers attrs = [QgsField('ELEVATION', QVariant.Double)] # common to all if include_traverses and trav_features: # traverse layer travs_layer = self.add_layer('traverses', 'LineStringZ') travs_layer.dataProvider().addAttributes(attrs) travs_layer.updateFields() travs_layer.dataProvider().addFeatures(trav_features) layers.append(travs_layer) if include_xsections and xsect_features: # xsection layer xsects_layer = self.add_layer('xsections', 'LineStringZ') xsects_layer.dataProvider().addAttributes(attrs) xsects_layer.updateFields() xsects_layer.dataProvider().addFeatures(xsect_features) layers.append(xsects_layer) if include_walls and wall_features: # wall layer walls_layer = self.add_layer('walls', 'LineStringZ') walls_layer.dataProvider().addAttributes(attrs) walls_layer.updateFields() walls_layer.dataProvider().addFeatures(wall_features) layers.append(walls_layer) if include_up_down: # add fields if requested for polygons attrs += [ QgsField(s, QVariant.Double) for s in ('MEAN_UP', 'MEAN_DOWN') ] if include_polygons and quad_features: # polygon layer quads_layer = self.add_layer('polygons', 'PolygonZ') quads_layer.dataProvider().addAttributes(attrs) quads_layer.updateFields() quads_layer.dataProvider().addFeatures(quad_features) layers.append(quads_layer) # All layers have been created, now update extents and add to QGIS registry if layers: [layer.updateExtents() for layer in layers] QgsProject.instance().addMapLayers(layers) # Write to GeoPackage if requested if gpkg_file: opts = [ QgsVectorFileWriter.CreateOrOverwriteFile, QgsVectorFileWriter.CreateOrOverwriteLayer ] for i, layer in enumerate(layers): options = QgsVectorFileWriter.SaveVectorOptions() options.actionOnExistingFile = opts[int( i > 0)] # create file or layer layer_name = layer.name() match = search( ' - ([a-z]*)', layer_name) # ie, extract 'legs', 'stations', etc options.layerName = str( match.group(1)) if match else layer_name writer = QgsVectorFileWriter.writeAsVectorFormat( layer, gpkg_file, options) if writer: msg = "'{}' -> {} in {}".format( layer_name, options.layerName, gpkg_file) QgsMessageLog.logMessage(msg, tag='Import .3d', level=Qgis.Info) options, writer = None, None
def create_basic_qgis_project(project_path=None, project_name=None): """ Create a basic QGIS project with OSM background and a simple vector layer. :return: Project file path on successful creation of a new project, None otherwise """ if project_path is None: project_path = get_new_qgis_project_filepath(project_name=project_name) if project_path is None: return False new_project = QgsProject() crs = QgsCoordinateReferenceSystem() crs.createFromString("EPSG:3857") new_project.setCrs(crs) new_project.setFileName(project_path) osm_url = "crs=EPSG:3857&type=xyz&zmin=0&zmax=19&url=http://tile.openstreetmap.org/{z}/{x}/{y}.png" osm_layer = QgsRasterLayer(osm_url, "OpenStreetMap", "wms") new_project.addMapLayer(osm_layer) mem_uri = "Point?crs=epsg:3857" mem_layer = QgsVectorLayer(mem_uri, "Survey points", "memory") res = mem_layer.dataProvider().addAttributes([ QgsField("date", QVariant.DateTime), QgsField("notes", QVariant.String), QgsField("photo", QVariant.String), ]) mem_layer.updateFields() vector_fname, err = save_vector_layer_as_gpkg( mem_layer, os.path.dirname(project_path)) if err: QMessageBox.warning(None, "Error Creating New Project", f"Couldn't save vector layer to:\n{vector_fname}") vector_layer = QgsVectorLayer(vector_fname, "Survey", "ogr") symbol = QgsMarkerSymbol.createSimple({ 'name': 'circle', 'color': '#d73027', 'size': '3', "outline_color": '#e8e8e8', 'outline_style': 'solid', 'outline_width': '0.4' }) vector_layer.renderer().setSymbol(symbol) fid_ws = QgsEditorWidgetSetup("Hidden", {}) vector_layer.setEditorWidgetSetup(0, fid_ws) datetime_config = { 'allow_null': True, 'calendar_popup': True, 'display_format': 'yyyy-MM-dd HH:mm:ss', 'field_format': 'yyyy-MM-dd HH:mm:ss', 'field_iso_format': False } datetime_ws = QgsEditorWidgetSetup("DateTime", datetime_config) vector_layer.setEditorWidgetSetup(1, datetime_ws) photo_config = { 'DocumentViewer': 1, 'DocumentViewerHeight': 0, 'DocumentViewerWidth': 0, 'FileWidget': True, 'FileWidgetButton': True, 'FileWidgetFilter': '', 'RelativeStorage': 1, 'StorageMode': 0, 'PropertyCollection': { 'name': NULL, 'properties': {}, 'type': 'collection' }, } photo_ws = QgsEditorWidgetSetup("ExternalResource", photo_config) vector_layer.setEditorWidgetSetup(3, photo_ws) new_project.addMapLayer(vector_layer) write_success = new_project.write() if not write_success: QMessageBox.warning(None, "Error Creating New Project", f"Couldn't create new project:\n{project_path}") return None return project_path
class CreatePlanningFile(QgsProcessingAlgorithm, Planning): '''Create Planning File''' #processing parameters # inputs: FILE_TYPE = 'FILE_TYPE' CRS = 'CRS' MBES = 'MBES' # outputs: OUTPUT = 'OUTPUT' def __init__(self): '''Initialize CreatePlanningFile''' super(CreatePlanningFile, self).__init__() # style files for planning layer self.style_planning_lines = ':/plugins/cruisetools/styles/style_planning_lines.qml' self.style_planning_points = ':/plugins/cruisetools/styles/style_planning_points.qml' # initialize default configuration self.initConfig() def initConfig(self): '''Get default values from CruiseToolsConfig''' self.file_type = self.config.getint(self.module, 'file_type') self.default_crs = self.config.get(self.module, 'default_crs') self.crs = QgsCoordinateReferenceSystem() self.crs.createFromString(self.default_crs) self.mbes = self.config.getboolean(self.module, 'mbes') def initAlgorithm(self, config=None): self.file_types = [self.tr('Point Planning'), self.tr('Line Planning')] self.addParameter( QgsProcessingParameterEnum( name=self.FILE_TYPE, description=self.tr('Planning File Type'), options=self.file_types, defaultValue=self.file_type, optional=False, allowMultiple=False)) self.addParameter( QgsProcessingParameterCrs(name=self.CRS, description=self.tr('Output CRS'), optional=True, defaultValue=self.crs)) self.addParameter( QgsProcessingParameterBoolean( name=self.MBES, description=self.tr('MBES Planning Layer'), optional=False, defaultValue=self.mbes)) self.addParameter( QgsProcessingParameterFeatureSink( name=self.OUTPUT, description=self.tr('Planning File'), type=QgsProcessing.TypeVectorLine, defaultValue=None, optional=False, createByDefault=True)) def processAlgorithm(self, parameters, context, feedback): # get input variables as self.* for use in post processing file_type = self.parameterAsEnum(parameters, self.FILE_TYPE, context) crs = self.parameterAsCrs(parameters, self.CRS, context) mbes = self.parameterAsBoolean(parameters, self.MBES, context) # if CRS is None, use project CRS if not crs.isValid(): crs = context.project().crs() # set new default values in config feedback.pushConsoleInfo( self.tr(f'Storing new default settings in config...')) self.config.set(self.module, 'file_type', file_type) self.config.set(self.module, 'default_crs', crs.authid()) self.config.set(self.module, 'mbes', mbes) # fields to be created fields = QgsFields() # check for type (point or line) and set creat parameters feedback.pushConsoleInfo(self.tr(f'Creating output fields...')) if file_type == 0: geom_type = QgsWkbTypes.Point fields.append(QgsField('name', QVariant.String, '', 0, 0)) fields.append(QgsField('notes', QVariant.String, '', 0, 0)) elif file_type == 1: geom_type = QgsWkbTypes.LineString fields.append(QgsField('name', QVariant.String, '', 0, 0)) fields.append(QgsField('turn_radius_nm', QVariant.Double, '', 4, 2)) fields.append(QgsField('speed_kn', QVariant.Double, '', 4, 2)) fields.append(QgsField('time_h', QVariant.Double, '', 4, 2)) if mbes: fields.append( QgsField('mbes_swath_angle', QVariant.Int, '', 3, 0)) fields.append(QgsField('notes', QVariant.String, '', 0, 0)) # creating feature sink feedback.pushConsoleInfo(self.tr(f'Creating feature sink...')) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geom_type, crs) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) # make variables accessible for post processing self.file_type = file_type self.output = dest_id result = {self.OUTPUT: self.output} return result def postProcessAlgorithm(self, context, feedback): # get layer from source and context planning_layer = QgsProcessingUtils.mapLayerFromString( self.output, context) if self.file_type == 0: style = self.style_planning_points style_name = 'Cruise Tools Point Planning' style_desc = 'Point Planning style for QGIS Symbology from Cruise Tools plugin' elif self.file_type == 1: style = self.style_planning_lines style_name = 'Cruise Tools Line Planning' style_desc = 'Line Planning style for QGIS Symbology from Cruise Tools plugin' # loading Cruise Tools Planning style from QML style file feedback.pushConsoleInfo(self.tr(f'Loading style...')) planning_layer.loadNamedStyle(style) # writing style to GPKG (or else) feedback.pushConsoleInfo(self.tr(f'Writing style to output...\n')) planning_layer.saveStyleToDatabase(name=style_name, description=style_desc, useAsDefault=True, uiFileContent=None) # 100% done feedback.setProgress(100) feedback.pushInfo( self.tr(f'{utils.return_success()}! Planning file created!\n')) result = {self.OUTPUT: self.output} return result def name(self): return 'createplanningfile' def icon(self): icon = QIcon(f'{self.plugin_dir}/icons/create_planning_file.png') return icon def displayName(self): return self.tr('Create Planning File') def group(self): return self.tr('Planning') def groupId(self): return 'planning' def tr(self, string): return QCoreApplication.translate('Processing', string) def shortHelpString(self): doc = f'{self.plugin_dir}/doc/create_planning_file.help' if not os.path.exists(doc): return '' with open(doc) as helpf: help = helpf.read() return help def createInstance(self): return CreatePlanningFile()