def create_point(coord, ret_geom=True): qgs_point = QgsPoint(coord[0], coord[1]) if ret_geom: ret_val = QgsGeometry(qgs_point) else: ret_val = qgs_point.clone() return ret_val
def gpsStateChanged(self, gpsInfo: QgsGpsInformation): # TODO This can also be removed after checking the new QGIS 3 API if gpsInfo.fixType == NMEA_FIX_BAD or gpsInfo.status == 0 or gpsInfo.quality == 0: self.gpsfixed.emit(False, gpsInfo) return elif gpsInfo.fixType == NMEA_FIX_3D or NMEA_FIX_2D: self.gpsfixed.emit(True, gpsInfo) self.elevation = gpsInfo.elevation - roam.config.settings.get( 'gps_antenna_height', 0.0) map_pos = QgsPoint(gpsInfo.longitude, gpsInfo.latitude, self.elevation) self.latlong_position = map_pos if self.crs: coordTransform = QgsCoordinateTransform(self.wgs84CRS, self.crs, QgsProject.instance()) try: map_pos.transform(coordTransform) except QgsCsException: log("Transform exception") return if self.position is None: self.firstfix.emit(map_pos, gpsInfo) if (datetime.now() - self._gpsupdate_last ).total_seconds() > self._gpsupdate_frequency: self.gpsposition.emit(map_pos, gpsInfo) self._gpsupdate_last = datetime.now() # --- averaging ----------------------------------------------- # if turned on if roam.config.settings.get('gps_averaging', True): # if currently happening if roam.config.settings.get('gps_averaging_in_action', True): self.gpspoints.append(map_pos) roam.config.settings[ 'gps_averaging_measurements'] = len(self.gpspoints) roam.config.save() else: if self.gpspoints: self.gpspoints = [] # ------------------------------------------------------------- self._position = map_pos.clone()
def processAlgorithm(self, parameters, context, feedback): """ Process algorithm. """ # Points: # origin: user origin point in proj crs # fire_origin: user fire origin point in proj crs # wgs84_origin: origin point in wgs84 crs, used for choosing utm_crs # wgs84_fire_origin: fire origin point in wgs84 crs # utm_origin: origin point in utm crs # utm_fire_origin: fire origin point in utm crs # CRSs: # project_crs: project crs # wgs84_crs: wgs84 crs # utm_crs: utm crs, calculated from wgs84_origin # dem_crs: dem crs, used for grid alignment # Extents: # extent: user selected terrain extent in its own crs. # utm_extent: extent to utm crs, used for FDS domain (MESH), # contains the extent, contained in the following dem_extent. # dem_extent: utm_extent to dem crs, used for FDS terrain (GEOM), # contains the utm_extent, contained in the following tex_extent. # Required for sampling grid alignment with dem raster data. # tex_extent: dem_extent to utm crs, used for FDS terrain texture crop, # contains dem_extent. # Required because the texture should be oriented as utm and # perfectly overlapping to dem_extent results, outputs, project = {}, {}, QgsProject.instance() # Get some of the parameters chid = self.parameterAsString(parameters, "chid", context) project.writeEntry("qgis2fds", "chid", parameters["chid"]) path = self.parameterAsFile(parameters, "path", context) project.writeEntry("qgis2fds", "path", parameters["path"]) landuse_type = self.parameterAsEnum(parameters, "landuse_type", context) project.writeEntry("qgis2fds", "landuse_type", parameters["landuse_type"]) dem_sampling = self.parameterAsInt(parameters, "dem_sampling", context) project.writeEntry("qgis2fds", "dem_sampling", parameters["dem_sampling"]) extent = self.parameterAsExtent(parameters, "extent", context) project.writeEntry("qgis2fds", "extent", parameters["extent"]) # Get layers in their respective crs: dem_layer, landuse_layer, tex_layer dem_layer = self.parameterAsRasterLayer(parameters, "dem_layer", context) project.writeEntry("qgis2fds", "dem_layer", parameters["dem_layer"]) if not parameters["landuse_layer"]: # it is optional landuse_layer = None else: landuse_layer = self.parameterAsRasterLayer( parameters, "landuse_layer", context) project.writeEntry("qgis2fds", "landuse_layer", parameters["landuse_layer"]) if not parameters["tex_layer"]: # it is optional tex_layer = None else: tex_layer = self.parameterAsRasterLayer(parameters, "tex_layer", context) project.writeEntry("qgis2fds", "tex_layer", parameters["tex_layer"]) # Get tex_pixel_size tex_pixel_size = self.parameterAsDouble(parameters, "tex_pixel_size", context) project.writeEntryDouble("qgis2fds", "tex_pixel_size", parameters["tex_pixel_size"]) # Prepare CRSs and check their validity project_crs = QgsProject.instance().crs() project.writeEntry("qgis2fds", "project_crs", project_crs.description()) if not project_crs.isValid(): raise QgsProcessingException( f"Project CRS <{project_crs.description()}> is not valid, cannot proceed." ) wgs84_crs = QgsCoordinateReferenceSystem("EPSG:4326") dem_crs = dem_layer.crs() if not dem_crs.isValid(): raise QgsProcessingException( f"DEM layer CRS <{dem_crs.description()}> is not valid, cannot proceed." ) if landuse_layer: landuse_crs = landuse_layer.crs() if not landuse_crs.isValid(): raise QgsProcessingException( f"Landuse layer CRS <{landuse_crs.description()}> is not valid, cannot proceed." ) if tex_layer: tex_crs = tex_layer.crs() if not tex_crs.isValid(): raise QgsProcessingException( f"Texture layer CRS <{tex_crs.description()}> is not valid, cannot proceed." ) # Get origin in WGS84 CRS project_to_wgs84_tr = QgsCoordinateTransform(project_crs, wgs84_crs, QgsProject.instance()) if parameters["origin"] is not None: # preventing a QGIS bug when using parameterAsPoint with crs=wgs84_crs origin = self.parameterAsPoint(parameters, "origin", context) project.writeEntry("qgis2fds", "origin", parameters["origin"]) wgs84_origin = QgsPoint(origin) wgs84_origin.transform(project_to_wgs84_tr) else: # no origin wgs84_origin = QgsPoint(extent.center()) wgs84_origin.transform(project_to_wgs84_tr) feedback.pushInfo( f"Domain origin: {wgs84_origin.x():.6f}, {wgs84_origin.y():.6f} (WGS 84)" ) # Get fire origin in WGS84 CRS if parameters["fire_origin"] is not None: # preventing a QGIS bug when using parameterAsPoint with crs=wgs84_crs fire_origin = self.parameterAsPoint(parameters, "fire_origin", context) project.writeEntry("qgis2fds", "fire_origin", parameters["fire_origin"]) wgs84_fire_origin = QgsPoint(fire_origin) wgs84_fire_origin.transform(project_to_wgs84_tr) else: # no fire origin wgs84_fire_origin = wgs84_origin.clone() feedback.pushInfo( f"Fire origin: {wgs84_fire_origin.x():.6f}, {wgs84_fire_origin.y():.6f} (WGS 84)" ) # Calc utm_crs from wgs84_origin utm_epsg = utils.lonlat_to_epsg(lon=wgs84_origin.x(), lat=wgs84_origin.y()) utm_crs = QgsCoordinateReferenceSystem(utm_epsg) # Feedback on CRSs feedback.pushInfo(f"\nProject CRS: <{project_crs.description()}>") feedback.pushInfo(f"DEM layer CRS: <{dem_crs.description()}>") feedback.pushInfo( f"Landuse layer CRS: <{landuse_layer and landuse_crs.description() or 'no landuse'}>" ) feedback.pushInfo( f"Texture layer CRS: <{tex_layer and tex_crs.description() or 'no texture'}>" ) feedback.pushInfo(f"FDS CRS: <{utm_crs.description()}>") # Get origin in utm_crs wgs84_to_utm_tr = QgsCoordinateTransform(wgs84_crs, utm_crs, QgsProject.instance()) utm_origin = wgs84_origin.clone() utm_origin.transform(wgs84_to_utm_tr) # Check for QGIS bug if utm_origin == wgs84_origin: raise QgsProcessingException( f"[QGIS bug] UTM Origin <{utm_origin}> and WGS84 Origin <{wgs84_origin}> are identical, cannot proceed.\n{wgs84_to_utm_tr}\n{wgs84_crs} {utm_crs}" ) # Get fire origin in utm_crs utm_fire_origin = wgs84_fire_origin.clone() utm_fire_origin.transform(wgs84_to_utm_tr) # Get utm_extent in utm_crs from extent (for MESH) # and dem_extent in dem_crs from utm_extent (for dem_layer sampling to GEOM) utm_extent = self.parameterAsExtent( parameters, "extent", context, crs=utm_crs, ) utm_to_dem_tr = QgsCoordinateTransform(utm_crs, dem_crs, QgsProject.instance()) dem_extent = utm_to_dem_tr.transformBoundingBox(utm_extent) # Get dem_layer resolution and top left extent corner coordinates, # because raster grid starts from top left corner of dem_layer extent dem_layer_xres = dem_layer.rasterUnitsPerPixelX() dem_layer_yres = dem_layer.rasterUnitsPerPixelY() dem_layer_x0, dem_layer_y1 = ( dem_layer.extent().xMinimum(), dem_layer.extent().yMaximum(), ) # Aligning dem_extent top left corner to dem_layer resolution, # never reduce its size x0, y0, x1, y1 = ( dem_extent.xMinimum(), dem_extent.yMinimum(), dem_extent.xMaximum(), dem_extent.yMaximum(), ) x0 = ( dem_layer_x0 # start lower + int( (x0 - dem_layer_x0) / dem_layer_xres) * dem_layer_xres # align - dem_layer_xres / 2.0 # to previous raster pixel center ) y1 = ( dem_layer_y1 # start upper - int( (dem_layer_y1 - y1) / dem_layer_yres) * dem_layer_yres # align + dem_layer_yres / 2.0 # to following raster pixel center ) dem_layer_xres *= dem_sampling # down sampling, if requested dem_layer_yres *= dem_sampling x1 = ( x0 # start lower + (ceil((x1 - x0) / dem_layer_xres) + 0.000001 ) # prevent rounding errors * dem_layer_xres # ceil multiple of xres ) y0 = ( y1 # start upper - (ceil((y1 - y0) / dem_layer_yres) + 0.000001 ) # prevent rounding errors * dem_layer_yres # ceil multiple of yres ) dem_extent = QgsRectangle(x0, y0, x1, y1) # Check dem_layer contains updated dem_extent if not dem_layer.extent().contains(dem_extent): feedback.reportError( "Terrain extent (GEOM) is larger than DEM layer extent, unknown elevations will be set to zero." ) # Calc and check number of dem sampling point dem_sampling_xn = int((x1 - x0) / dem_layer_xres) + 1 dem_sampling_yn = int((y1 - y0) / dem_layer_yres) + 1 if dem_sampling_xn < 3: raise QgsProcessingException( f"Too few sampling points <{dem_sampling_xn}> along x axis, cannot proceed." ) if dem_sampling_yn < 3: raise QgsProcessingException( f"Too few sampling points <{dem_sampling_yn}> along y axis, cannot proceed." ) nverts = (dem_sampling_xn + 1) * (dem_sampling_yn + 1) nfaces = dem_sampling_xn * dem_sampling_yn * 2 # Get tex_extent in utm_crs from dem_crs, # texture shall be aligned to MESH, and exactly cover the GEOM terrain dem_to_utm_tr = QgsCoordinateTransform(dem_crs, utm_crs, QgsProject.instance()) tex_extent = dem_to_utm_tr.transformBoundingBox(dem_extent) # Get FDS domain size in meters utm_extent_xm = utm_extent.xMaximum() - utm_extent.xMinimum() utm_extent_ym = utm_extent.yMaximum() - utm_extent.yMinimum() # Feedback feedback.pushInfo(f"\nFDS domain (MESH)") feedback.pushInfo( f"size: {utm_extent_xm:.1f} x {utm_extent_ym:.1f} meters") feedback.pushInfo(f"\nDEM layer sampling for FDS terrain (GEOM)") feedback.pushInfo( f"resolution: {dem_layer_xres:.1f} x {dem_layer_yres:.1f} meters") feedback.pushInfo(f"geometry: {nverts} verts, {nfaces} faces") feedback.pushInfo(f"\nPress <Cancel> to interrupt the execution.") if DEBUG: # Show utm_extent layer feedback.pushInfo(f"\n[DEBUG] Drawing utm_extent...") x0, y0, x1, y1 = ( utm_extent.xMinimum(), utm_extent.yMinimum(), utm_extent.xMaximum(), utm_extent.yMaximum(), ) alg_params = { "INPUT": f"{x0}, {x1}, {y0}, {y1} [{utm_crs.authid()}]", "OUTPUT": parameters["utm_extent_layer"], } outputs["CreateLayerFromExtent"] = processing.run( "native:extenttolayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) results["utm_extent_layer"] = outputs["CreateLayerFromExtent"][ "OUTPUT"] # Show dem_extent layer feedback.pushInfo(f"[DEBUG] Drawing dem_extent...") x0, y0, x1, y1 = ( dem_extent.xMinimum(), dem_extent.yMinimum(), dem_extent.xMaximum(), dem_extent.yMaximum(), ) alg_params = { "INPUT": f"{x0}, {x1}, {y0}, {y1} [{dem_crs.authid()}]", "OUTPUT": parameters["dem_extent_layer"], } outputs["CreateLayerFromExtent"] = processing.run( "native:extenttolayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) results["dem_extent_layer"] = outputs["CreateLayerFromExtent"][ "OUTPUT"] # Show tex_extent layer feedback.pushInfo(f"[DEBUG] Drawing tex_extent...") x0, y0, x1, y1 = ( tex_extent.xMinimum(), tex_extent.yMinimum(), tex_extent.xMaximum(), tex_extent.yMaximum(), ) alg_params = { "INPUT": f"{x0}, {x1}, {y0}, {y1} [{utm_crs.authid()}]", "OUTPUT": parameters["tex_extent_layer"], } outputs["CreateLayerFromExtent"] = processing.run( "native:extenttolayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) results["tex_extent_layer"] = outputs["CreateLayerFromExtent"][ "OUTPUT"] # Writing texture image to disk if feedback.isCanceled(): return {} feedback.setProgressText( "\n(1/7) Rendering, cropping, and writing texture image, timeout in 30s..." ) utils.write_texture( feedback=feedback, tex_layer=tex_layer, tex_extent=tex_extent, tex_pixel_size=tex_pixel_size, utm_crs=utm_crs, filepath=f"{path}/{chid}_tex.png", imagetype="png", ) # QGIS geographic transformations # Creating sampling grid in DEM crs if feedback.isCanceled(): return {} feedback.setProgressText( "\n(2/7) Creating sampling grid from DEM layer...") alg_params = { "CRS": dem_crs, "EXTENT": dem_extent, "HOVERLAY": 0, "HSPACING": dem_layer_xres, "TYPE": 0, # Points "VOVERLAY": 0, "VSPACING": dem_layer_yres, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateGrid"] = processing.run( "native:creategrid", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) # QGIS geographic transformations # Draping Z values to sampling grid in DEM crs if feedback.isCanceled(): return {} feedback.setProgressText( "\n(3/7) Draping elevations from DEM layer to sampling grid...") alg_params = { "BAND": 1, "INPUT": outputs["CreateGrid"]["OUTPUT"], "NODATA": 0, "RASTER": dem_layer, "SCALE": 1, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DrapeSetZValueFromRaster"] = processing.run( "native:setzfromraster", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) # QGIS geographic transformations # Sampling landuse layer with sampling grid in UTM CRS if feedback.isCanceled(): return {} feedback.setProgressText("\n(4/7) Sampling landuse layer...") if landuse_layer: alg_params = { "COLUMN_PREFIX": "landuse", "INPUT": outputs["DrapeSetZValueFromRaster"]["OUTPUT"], "RASTERCOPY": parameters["landuse_layer"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["RasterSampling"] = processing.run( "qgis:rastersampling", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) else: feedback.pushInfo("No landuse layer provided, no sampling.") # QGIS geographic transformations # Reprojecting sampling grid to UTM CRS if feedback.isCanceled(): return {} feedback.setProgressText( "\n(5/7) Reprojecting sampling grid layer to UTM CRS...") alg_params = { "INPUT": landuse_layer and outputs["RasterSampling"]["OUTPUT"] or outputs["DrapeSetZValueFromRaster"]["OUTPUT"], "TARGET_CRS": utm_crs, "OUTPUT": parameters["sampling_layer"], } outputs["ReprojectLayer"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) results["sampling_layer"] = outputs["ReprojectLayer"]["OUTPUT"] # Get point_layer and check it point_layer = context.getMapLayer(results["sampling_layer"]) if point_layer.featureCount() < 9: raise QgsProcessingException( f"[QGIS bug] Too few features in sampling layer, cannot proceed.\n{point_layer.featureCount()}" ) # Prepare geometry if feedback.isCanceled(): return {} feedback.setProgressText("\n(6/7) Building FDS geometry...") verts, faces, landuses = geometry.get_geometry( feedback=feedback, point_layer=point_layer, utm_origin=utm_origin, landuse_layer=landuse_layer, ) # Write the FDS case file if feedback.isCanceled(): return {} feedback.setProgressText("\n(7/7) Writing the FDS case file...") fds.write_case( feedback=feedback, dem_layer=dem_layer, landuse_layer=landuse_layer, path=path, chid=chid, wgs84_origin=wgs84_origin, utm_origin=utm_origin, wgs84_fire_origin=wgs84_fire_origin, utm_fire_origin=utm_fire_origin, utm_crs=utm_crs, verts=verts, faces=faces, landuses=landuses, landuse_type=landuse_type, utm_extent=utm_extent, ) return results