def processAlgorithm(self, parameters, context, feedback): communes = self.parameterAsString(parameters, self.LISTE_CODE_INSEE, context) filtre = self.parameterAsString(parameters, self.FILTRE, context) date = self.parameterAsString(parameters, self.DATE, context) url = self.parameterAsString(parameters, self.URL_TEMPLATE, context) directory = Path( self.parameterAsString(parameters, self.DOSSIER, context)) if not directory.exists(): feedback.pushDebugInfo( "Création du répertoire {}".format(directory)) os.makedirs(directory, exist_ok=True) filtre = [c.strip() for c in filtre.split(',')] communes = [c.strip() for c in communes.split(',')] departements = [] self.results = { self.DOSSIER: str(directory), self.NB_COMMUNES: len(communes), self.NB_FEUILLES: 0, self.DEPARTEMENTS: "", } multi_feedback = QgsProcessingMultiStepFeedback( len(communes), feedback) for i, commune_insee in enumerate(communes): commune = Commune(commune_insee, date=date, base_url=url) if not self.download_commune(directory, commune, filtre, multi_feedback, context): multi_feedback.reportError("Erreur sur la commune {}".format( commune.insee)) break if multi_feedback.isCanceled(): break multi_feedback.setCurrentStep(i) if commune.departement not in departements: departements.append(commune.departement) self.results[self.DEPARTEMENTS] = ','.join(departements) multi_feedback.pushInfo("\n") multi_feedback.pushInfo("\n") multi_feedback.pushInfo( "Téléchargement terminé pour {} communes".format(len(communes))) multi_feedback.pushInfo("{} feuilles".format( self.results[self.NB_FEUILLES])) multi_feedback.pushInfo("dans {}".format(str(directory))) multi_feedback.pushInfo("\n") multi_feedback.pushInfo("\n") return self.results
def processAlgorithm(self, parameters, context, model_feedback): """ Process the algorithm :param parameters: parameters of the process :param context: context of the process :param model_feedback: feedback instance for the process :return: """ # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the # overall progress through the model self.xml_path = parameters["XMLPATH"] if not self.xml_path.lower().endswith(".xml"): feedback = QgsProcessingMultiStepFeedback(0, model_feedback) feedback.reportError("XML Workspace Definition is not an XML file!", True) return {} self.pg_conn_name = parameters["DBNAME"] self.pg_schema = parameters["SCHEMA"] self.pg_drop_before = parameters["DROPIFEXISTS"] domain_list = self.getDomains() feedback = QgsProcessingMultiStepFeedback(1+len(domain_list), model_feedback) step=0 for domain in domain_list: step+=1 definition = self.getDomainDef(domain) try: alg_params = { 'DATABASE': self.pg_conn_name, 'SQL': definition[1] } processing.run( 'qgis:postgisexecutesql', alg_params, context=context, feedback=feedback, is_child_algorithm=True) feedback.pushInfo("Domain: " + definition[0]) feedback.pushInfo(" SQL: " + definition[1]) feedback.pushInfo(" Rows: " + str(definition[2])) except Exception as e: feedback.reportError("Error importing domain " + definition[0] + ": " + str(e), False) feedback.setCurrentStep(step) results = {} outputs = {} return results
def processAlgorithm(self, params, context, feedback): isValid = lambda x: 0 if x is None else 1 isBusStop = isValid(params['BUSSTOP']) isTramStop = isValid(params['TRAMSTOP']) isBikeStop = isValid(params['BIKESTOP']) isBikeWay = isValid(params['BIKEWAY']) isCrossWalk = isValid(params['CROSSWALK']) isRoads = isValid(params['ROADS']) totalValides = isBusStop + isTramStop + isBikeStop + isBikeWay + isCrossWalk if (totalValides >= 3): if isRoads == 0 and params['DISTANCE_OPTIONS'] == 0: feedback.reportError( str(('Distancia isocrona requiere la red vial'))) return {} steps = 0 totalStpes = 37 fieldPopulateOrHousing = params['FIELD_POPULATE_HOUSING'] DISTANCE_BUSSTOP = 300 DISTANCE_TRAMSTOP = 500 DISTANCE_BKESTOP = 300 DISTANCE_BIKEWAY = 300 DISTANCE_CROSSWALK = 300 MIN_FACILITIES = 3 OPERATOR_GE = 3 feedback = QgsProcessingMultiStepFeedback(totalStpes, feedback) """ ----------------------------------------------------------------- Calcular las facilidades ----------------------------------------------------------------- """ steps = steps + 1 feedback.setCurrentStep(steps) if not OPTIONAL_GRID_INPUT: params['CELL_SIZE'] = P_CELL_SIZE grid, isStudyArea = buildStudyArea(params['CELL_SIZE'], params['BLOCKS'], params['STUDY_AREA_GRID'], context, feedback) gridNeto = grid steps = steps + 1 feedback.setCurrentStep(steps) blocks = calculateArea(params['BLOCKS'], 'area_bloc', context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) segments = intersection(blocks['OUTPUT'], gridNeto['OUTPUT'], 'area_bloc;' + fieldPopulateOrHousing, 'id_grid', context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) segmentsArea = calculateArea(segments['OUTPUT'], 'area_seg', context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) formulaPopulationSegments = '(area_seg/area_bloc) * ' + fieldPopulateOrHousing populationForSegments = calculateField(segmentsArea['OUTPUT'], 'pop_seg', formulaPopulationSegments, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) blocksWithId = calculateField(populationForSegments['OUTPUT'], 'id_block', '$id', context, feedback, type=1) steps = steps + 1 feedback.setCurrentStep(steps) centroidsBlocks = createCentroids(blocksWithId['OUTPUT'], context, feedback) result = [] idxs = [ 'idxbus', 'idxtram', 'idxbikestop', 'idkbikeway', 'idxwalk' ] layers = [] if (params['DISTANCE_OPTIONS'] == 0): steps = steps + 1 feedback.setCurrentStep(steps) feedback.pushConsoleInfo(str(('Cálculo de áreas de servicio'))) pointsBikeWay = pointsAlongLines(params['BIKEWAY'], 50, context, feedback) pointsCrossWalk = pointsAlongLines(params['CROSSWALK'], 50, context, feedback) if isBusStop == 1: layers.append([ params['BUSSTOP'], STRATEGY_DISTANCE, DISTANCE_BUSSTOP ]) if isTramStop == 1: layers.append([ params['TRAMSTOP'], STRATEGY_DISTANCE, DISTANCE_TRAMSTOP ]) if isBikeStop == 1: layers.append([ params['BIKESTOP'], STRATEGY_DISTANCE, DISTANCE_BKESTOP ]) if isBikeWay == 1: layers.append([ pointsBikeWay['OUTPUT'], STRATEGY_DISTANCE, DISTANCE_BIKEWAY ]) if isCrossWalk == 1: layers.append([ pointsCrossWalk['OUTPUT'], STRATEGY_DISTANCE, DISTANCE_CROSSWALK ]) serviceAreas = multiBufferIsocrono(params['ROADS'], layers, context, feedback) iidx = -1 for serviceArea in serviceAreas: iidx = iidx + 1 idx = idxs[iidx] steps = steps + 1 feedback.setCurrentStep(steps) serviceArea = calculateField(serviceArea, idx, '$id', context, feedback, type=1) steps = steps + 1 feedback.setCurrentStep(steps) centroidsBlocks = joinByLocation(centroidsBlocks['OUTPUT'], serviceArea['OUTPUT'], [idx], [INTERSECTA], [COUNT], UNDISCARD_NONMATCHING, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) # formulaDummy = 'idxbus_count * 1' formulaDummy = 'coalesce(idxbus_count, 0) + coalesce(idxtram_count, 0) + coalesce(idxbikestop_count,0) + coalesce(idkbikeway_count, 0) + coalesce(idxwalk_count, 0)' facilitiesCover = calculateField(centroidsBlocks['OUTPUT'], 'facilities', formulaDummy, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) facilitiesFullCover = filter(facilitiesCover['OUTPUT'], 'facilities', OPERATOR_GE, MIN_FACILITIES, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) gridNetoFacilitiesCover = joinByLocation( gridNeto['OUTPUT'], facilitiesCover['OUTPUT'], ['pop_seg', 'facilities'], [CONTIENE], [SUM], UNDISCARD_NONMATCHING, context, feedback) fieldsMapping = [{ 'expression': '"id_grid"', 'length': 10, 'name': 'id_grid', 'precision': 0, 'type': 4 }, { 'expression': '"area_grid"', 'length': 16, 'name': 'area_grid', 'precision': 3, 'type': 6 }, { 'expression': '"pop_seg_sum"', 'length': 20, 'name': 'ptotal', 'precision': 2, 'type': 6 }, { 'expression': '"facilities_sum"', 'length': 20, 'name': 'facilities', 'precision': 2, 'type': 6 }] steps = steps + 1 feedback.setCurrentStep(steps) gridNetoFacilitiesCover = refactorFields( fieldsMapping, gridNetoFacilitiesCover['OUTPUT'], context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) gridNetoFacilities = joinByLocation( gridNetoFacilitiesCover['OUTPUT'], facilitiesFullCover['OUTPUT'], ['pop_seg'], [CONTIENE], [SUM], UNDISCARD_NONMATCHING, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) formulaProximity = 'coalesce((coalesce(pop_seg_sum,0) / coalesce(ptotal,""))*100,"")' proximity2AlternativeTransport = calculateField( gridNetoFacilities['OUTPUT'], NAMES_INDEX['IC04'][0], formulaProximity, context, feedback, params['OUTPUT']) result = proximity2AlternativeTransport else: feedback.pushConsoleInfo(str(('Cálculo de buffer radial'))) blocksJoined = blocksWithId steps = steps + 1 feedback.setCurrentStep(steps) blockBuffer4BusStop = createBuffer(centroidsBlocks['OUTPUT'], DISTANCE_BUSSTOP, context, feedback) # ------------------------------------ if isBusStop == 1: steps = steps + 1 feedback.setCurrentStep(steps) layerBusStop = calculateField(params['BUSSTOP'], 'idx', '$id', context, feedback, type=1) layerBusStop = layerBusStop['OUTPUT'] steps = steps + 1 feedback.setCurrentStep(steps) counterBusStop = joinByLocation( blockBuffer4BusStop['OUTPUT'], layerBusStop, 'idx', [INTERSECTA], [COUNT], UNDISCARD_NONMATCHING, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) blocksJoined = joinByAttr(blocksJoined['OUTPUT'], 'id_block', counterBusStop['OUTPUT'], 'id_block', 'idx_count', UNDISCARD_NONMATCHING, 'bs_', context, feedback) # --------------------------------------------------- if isTramStop == 1: steps = steps + 1 feedback.setCurrentStep(steps) blockBuffer4TramStop = createBuffer( centroidsBlocks['OUTPUT'], DISTANCE_TRAMSTOP, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) layerTramStop = calculateField(params['TRAMSTOP'], 'idx', '$id', context, feedback, type=1) layerTramStop = layerTramStop['OUTPUT'] steps = steps + 1 feedback.setCurrentStep(steps) counterTramStop = joinByLocation( blockBuffer4TramStop['OUTPUT'], layerTramStop, 'idx', [INTERSECTA], [COUNT], UNDISCARD_NONMATCHING, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) blocksJoined = joinByAttr(blocksJoined['OUTPUT'], 'id_block', counterTramStop['OUTPUT'], 'id_block', 'idx_count', UNDISCARD_NONMATCHING, 'ts_', context, feedback) # ----------------------------------------------- if isBikeStop == 1: steps = steps + 1 feedback.setCurrentStep(steps) blockBuffer4BikeStop = createBuffer( centroidsBlocks['OUTPUT'], DISTANCE_BKESTOP, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) layerBikeStop = calculateField(params['BIKESTOP'], 'idx', '$id', context, feedback, type=1) layerBikeStop = layerBikeStop['OUTPUT'] steps = steps + 1 feedback.setCurrentStep(steps) counteBikeStop = joinByLocation( blockBuffer4BikeStop['OUTPUT'], layerBikeStop, 'idx', [INTERSECTA], [COUNT], UNDISCARD_NONMATCHING, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) blocksJoined = joinByAttr(blocksJoined['OUTPUT'], 'id_block', counteBikeStop['OUTPUT'], 'id_block', 'idx_count', UNDISCARD_NONMATCHING, 'bks_', context, feedback) # ----------------------------------------- if isBikeWay == 1: steps = steps + 1 feedback.setCurrentStep(steps) BlockBuffer4BikeWay = createBuffer( centroidsBlocks['OUTPUT'], DISTANCE_BIKEWAY, context, feedback) pointsBikeWay = pointsAlongLines(params['BIKEWAY'], 50, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) layerBikeWay = calculateField(pointsBikeWay['OUTPUT'], 'idx', '$id', context, feedback, type=1) layerBikeWay = layerBikeWay['OUTPUT'] steps = steps + 1 feedback.setCurrentStep(steps) counterBikeWay = joinByLocation( BlockBuffer4BikeWay['OUTPUT'], layerBikeWay, 'idx', [INTERSECTA], [COUNT], UNDISCARD_NONMATCHING, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) blocksJoined = joinByAttr(blocksJoined['OUTPUT'], 'id_block', counterBikeWay['OUTPUT'], 'id_block', 'idx_count', UNDISCARD_NONMATCHING, 'bw_', context, feedback) # ------------------------------------------ if isCrossWalk == 1: steps = steps + 1 feedback.setCurrentStep(steps) BlockBuffer4CrossWalk = createBuffer( centroidsBlocks['OUTPUT'], DISTANCE_CROSSWALK, context, feedback) pointsCrossWalk = pointsAlongLines(params['CROSSWALK'], 50, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) layerCrossWalk = calculateField(pointsCrossWalk['OUTPUT'], 'idx', '$id', context, feedback, type=1) layerCrossWalk = layerCrossWalk['OUTPUT'] steps = steps + 1 feedback.setCurrentStep(steps) counterCrossWalk = joinByLocation( BlockBuffer4CrossWalk['OUTPUT'], layerCrossWalk, 'idx', [INTERSECTA], [COUNT], UNDISCARD_NONMATCHING, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) blocksJoined = joinByAttr(blocksJoined['OUTPUT'], 'id_block', counterCrossWalk['OUTPUT'], 'id_block', 'idx_count', UNDISCARD_NONMATCHING, 'cw_', context, feedback) # -------------------------------------------- #TODO: CAMBIAR POR UN METODO BUCLE formulaParseBS = 'CASE WHEN coalesce(bs_idx_count, 0) > 0 THEN 1 ELSE 0 END' steps = steps + 1 feedback.setCurrentStep(steps) blocksFacilities = calculateField(blocksJoined['OUTPUT'], 'parse_bs', formulaParseBS, context, feedback) formulaParseTS = 'CASE WHEN coalesce(ts_idx_count, 0) > 0 THEN 1 ELSE 0 END' steps = steps + 1 feedback.setCurrentStep(steps) blocksFacilities = calculateField(blocksFacilities['OUTPUT'], 'parse_ts', formulaParseTS, context, feedback) formulaParseBKS = 'CASE WHEN coalesce(bks_idx_count, 0) > 0 THEN 1 ELSE 0 END' steps = steps + 1 feedback.setCurrentStep(steps) blocksFacilities = calculateField(blocksFacilities['OUTPUT'], 'parse_bks', formulaParseBKS, context, feedback) formulaParseBW = 'CASE WHEN coalesce(bw_idx_count, 0) > 0 THEN 1 ELSE 0 END' steps = steps + 1 feedback.setCurrentStep(steps) blocksFacilities = calculateField(blocksFacilities['OUTPUT'], 'parse_bw', formulaParseBW, context, feedback) formulaParseCW = 'CASE WHEN coalesce(cw_idx_count, 0) > 0 THEN 1 ELSE 0 END' steps = steps + 1 feedback.setCurrentStep(steps) blocksFacilities = calculateField(blocksFacilities['OUTPUT'], 'parse_cw', formulaParseCW, context, feedback) formulaFacilities = 'parse_bs + parse_ts + parse_bks + parse_bw + parse_cw' steps = steps + 1 feedback.setCurrentStep(steps) blocksFacilities = calculateField(blocksFacilities['OUTPUT'], 'facilities', formulaFacilities, context, feedback) # Haciendo el buffer inverso aseguramos que los segmentos # quden dentro de la malla steps = steps + 1 feedback.setCurrentStep(steps) facilitiesForSegmentsFixed = makeSureInside( blocksFacilities['OUTPUT'], context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) gridNetoAndSegments = joinByLocation( gridNeto['OUTPUT'], facilitiesForSegmentsFixed['OUTPUT'], 'bs_idx_count;ts_idx_count;bks_idx_count;bw_idx_count;cw_idx_count;facilities;pop_seg', [CONTIENE], [MAX, SUM], UNDISCARD_NONMATCHING, context, feedback) # tomar solo los que tienen cercania simultanea (descartar lo menores de 3) steps = steps + 1 feedback.setCurrentStep(steps) facilitiesNotNullForSegmentsFixed = filter( facilitiesForSegmentsFixed['OUTPUT'], 'facilities', OPERATOR_GE, MIN_FACILITIES, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) gridNetoAndSegmentsSimulta = joinByLocation( gridNeto['OUTPUT'], facilitiesNotNullForSegmentsFixed['OUTPUT'], 'pop_seg', [CONTIENE], [MAX, SUM], UNDISCARD_NONMATCHING, context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) totalHousing = joinByAttr(gridNetoAndSegments['OUTPUT'], 'id_grid', gridNetoAndSegmentsSimulta['OUTPUT'], 'id_grid', 'pop_seg_sum', UNDISCARD_NONMATCHING, 'net_', context, feedback) steps = steps + 1 feedback.setCurrentStep(steps) formulaProximity = 'coalesce((coalesce(net_pop_seg_sum,0) / coalesce(pop_seg_sum,""))*100,"")' proximity2AlternativeTransport = calculateField( totalHousing['OUTPUT'], NAMES_INDEX['IC04'][0], formulaProximity, context, feedback, params['OUTPUT']) result = proximity2AlternativeTransport return result else: feedback.reportError( str(('Se necesita al menos tres redes de transporte'))) return {}
def processAlgorithm(self, parameters, context, model_feedback): # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the # overall progress through the model feedback = QgsProcessingMultiStepFeedback(13, model_feedback) results = {} outputs = {} input_layer = self.parameterAsVectorLayer(parameters, "inputlayer", context) overlay_layer = self.parameterAsVectorLayer(parameters, "overlaylayer", context) input_epsg_code = input_layer.crs().authid() overlay_epsg_code = overlay_layer.crs().authid() crs_input = QgsCoordinateReferenceSystem(input_epsg_code) crs_overlay = QgsCoordinateReferenceSystem(overlay_epsg_code) if crs_input.isGeographic(): feedback.reportError( "CRS of the Input Layer is Geographic. Results accuracy may get impacted. For most accurate results, both input and overlay layers should be in the same Projected CRS\n" ) if crs_overlay.isGeographic(): feedback.reportError( "CRS of the Input Layer is Geographic. Results accuracy may get impacted. For most accurate results, both input and overlay layers should be in the same Projected CRS\n" ) if input_epsg_code == overlay_epsg_code: pass else: feedback.reportError( "Input and Overlay Layers are in different CRS. For most accurate results, both input and overlay layers should be in the same Projected CRS\n" ) # add_ID_field to input layer alg_params = { "FIELD_NAME": "input_feat_id", "GROUP_FIELDS": [""], "INPUT": parameters["inputlayer"], "SORT_ASCENDING": True, "SORT_EXPRESSION": "", "SORT_NULLS_FIRST": False, "START": 1, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Add_id_field"] = processing.run( "native:addautoincrementalfield", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(1) if feedback.isCanceled(): return {} # add_area_field to input layer alg_params = { "FIELD_LENGTH": 0, "FIELD_NAME": "area_awa", "FIELD_PRECISION": 0, "FIELD_TYPE": 0, "FORMULA": "area($geometry)", "INPUT": outputs["Add_id_field"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Add_area_field"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(2) if feedback.isCanceled(): return {} # dissolve all overlay fields so as not to repeat record in reporting alg_params = { "FIELD": [parameters["fieldtoaverage"]] + [ field for field in parameters["additionalfields"] if field != str(parameters["fieldtoaverage"]) ], "INPUT": parameters["overlaylayer"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Dissolve"] = processing.run( "native:dissolve", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(3) if feedback.isCanceled(): return {} # intersection between input and overlay layer # delete no field in input layer and all fields in overlay layer # except field to average and additional fields alg_params = { "INPUT": outputs["Add_area_field"]["OUTPUT"], "INPUT_FIELDS": [""], "OVERLAY": outputs["Dissolve"]["OUTPUT"], "OVERLAY_FIELDS": [str(parameters["fieldtoaverage"])] + parameters["additionalfields"], "OVERLAY_FIELDS_PREFIX": "", "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Intersection"] = processing.run( "native:intersection", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(4) if feedback.isCanceled(): return {} # add_Weight alg_params = { "FIELD_LENGTH": 0, "FIELD_NAME": parameters["fieldtoaverage"] + "_area", "FIELD_PRECISION": 0, "FIELD_TYPE": 0, "FORMULA": ' "' + parameters["fieldtoaverage"] + '" * area($geometry)', "INPUT": outputs["Intersection"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Add_Weight"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(5) if feedback.isCanceled(): return {} # area_average weighted_field = "weighted_" + parameters["fieldtoaverage"] alg_params = { "FIELD_LENGTH": 0, "FIELD_NAME": weighted_field, "FIELD_PRECISION": 0, "FIELD_TYPE": 0, "FORMULA": ' sum("' + parameters["fieldtoaverage"] + "_area" '","input_feat_id")/"area_awa"', "INPUT": outputs["Add_Weight"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["area_average"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(6) if feedback.isCanceled(): return {} # remerge input layer elements alg_params = { "FIELD": ["input_feat_id"], "INPUT": outputs["area_average"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Dissolve2"] = processing.run( "native:dissolve", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(7) if feedback.isCanceled(): return {} input_layer = self.parameterAsVectorLayer(parameters, "inputlayer", context) result_name = input_layer.name() + "_" + parameters["fieldtoaverage"] parameters["result"].destinationName = result_name # drop field(s) for Result alg_params = { "COLUMN": ["input_feat_id", "area_awa"] + [parameters["fieldtoaverage"]] + [ field for field in parameters["additionalfields"] if field != str(parameters["fieldtoaverage"]) ] + [parameters["fieldtoaverage"] + "_area"], "INPUT": outputs["Dissolve2"]["OUTPUT"], "OUTPUT": parameters["result"], } outputs["Drop1"] = processing.run( "qgis:deletecolumn", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(8) if feedback.isCanceled(): return {} results["result"] = outputs["Drop1"]["OUTPUT"] # Reporting # Drop field(s) for Report int_layer = context.takeResultLayer(outputs["area_average"]["OUTPUT"]) all_fields = [f.name() for f in int_layer.fields()] fields_to_keep = (["input_feat_id", weighted_field] + [ field for field in parameters["additionalfields"] if field != str(parameters["fieldtoaverage"]) ] + [parameters["fieldtoaverage"]] + [parameters["identifierfieldforreport"]]) fields_to_drop = [f for f in all_fields if f not in fields_to_keep] alg_params = { "COLUMN": fields_to_drop, "INPUT": int_layer, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Drop2"] = processing.run( "qgis:deletecolumn", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(9) if feedback.isCanceled(): return {} # update area alg_params = { "FIELD_LENGTH": 20, "FIELD_NAME": "area_crs_units", "FIELD_PRECISION": 5, "FIELD_TYPE": 0, "FORMULA": "round(area($geometry),5)", "INPUT": outputs["Drop2"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["update_area"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(10) if feedback.isCanceled(): return {} parameters["reportaslayer"].destinationName = "Report as Layer" # add area % alg_params = { "FIELD_LENGTH": 9, "FIELD_NAME": "area_prcnt", "FIELD_PRECISION": 5, "FIELD_TYPE": 0, "FORMULA": ' round("area_crs_units" *100/ sum( "area_crs_units" , "input_feat_id" ),5)', "INPUT": outputs["update_area"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["area_prcnt"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(11) if feedback.isCanceled(): return {} # Order by expression alg_params = { "ASCENDING": True, "EXPRESSION": ' "input_feat_id" + area_prcnt" ', "INPUT": outputs["area_prcnt"]["OUTPUT"], "NULLS_FIRST": False, "OUTPUT": parameters["reportaslayer"], } outputs["OrderByExpression"] = processing.run( "native:orderbyexpression", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(12) if feedback.isCanceled(): return {} results["reportaslayer"] = outputs["OrderByExpression"]["OUTPUT"] output_file = self.parameterAsFileOutput(parameters, "reportasHTML", context) # create HTML report if output_file: try: try: import pandas as pd except ImportError: feedback.pushInfo( "Python library pandas was not found. Installing pandas to QGIS python ..." ) import pathlib as pl import subprocess qgis_Path = pl.Path(sys.executable) qgis_python_path = (qgis_Path.parent / "python3.exe").as_posix() subprocess.check_call([ qgis_python_path, "-m", "pip", "install", "--user", "pandas" ]) import pandas as pd feedback.pushInfo( "Python library pandas was successfully installed for QGIS python" ) except: feedback.reportError( "Failed to import pandas. Tried installing pandas but failed.\nPlease manually install pandas for the python that comes with your QGIS.", True, ) return results # Drop geometries alg_params = { "INPUT": outputs["area_prcnt"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DropGeometries"] = processing.run( "native:dropgeometries", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(13) if feedback.isCanceled(): return {} with tempfile.TemporaryDirectory() as td: f_name = os.path.join(td, "report_df.csv") report_layer = context.takeResultLayer( outputs["DropGeometries"]["OUTPUT"]) QgsVectorFileWriter.writeAsVectorFormat( report_layer, f_name, fileEncoding="utf-8", driverName="CSV", ) df = pd.read_csv(f_name) total_FIDs = df["input_feat_id"].max() ident_name = parameters["identifierfieldforreport"] html = "" df.sort_values(by="area_prcnt", ascending=False, inplace=True) pd.set_option("display.float_format", "{:.5f}".format) for i in range(1, total_FIDs + 1): df_sub = df.loc[df["input_feat_id"] == i] df_sub.reset_index(inplace=True, drop=True) avg_value = df_sub.at[0, weighted_field] if ident_name: feature_name = df_sub.at[0, ident_name] df_sub.drop( columns=["input_feat_id", ident_name, weighted_field], inplace=True, ) html += f"<p><b>{i}. {feature_name}</b><br>{weighted_field}: {avg_value}<br>count of distinct intersecting features: {len(df_sub.index)}<br></p>\n" else: df_sub.drop(columns=["input_feat_id", weighted_field], inplace=True) html += f"<p><b>Feature ID: {i}</b><br>{weighted_field}: {avg_value}<br>count of distinct intersecting features: {len(df_sub.index)}<br></p>\n" html += f"{df_sub.to_html(bold_rows=False, index=False, na_rep='Null',justify='left')}<br>\n" with codecs.open(output_file, "w", encoding="utf-8") as f: f.write("<html><head>\n") f.write( '<meta http-equiv="Content-Type" content="text/html; \ charset=utf-8" /></head><body>\n') f.write(html) f.write("</body></html>\n") results["reportasHTML"] = output_file # log usage with open(os.path.join(cmd_folder, "usage_counter.log"), "r+") as f: counter = int(f.readline()) f.seek(0) f.write(str(counter + 1)) # check if counter is a milestone if (counter + 1) % 25 == 0: appeal_file = NamedTemporaryFile("w", suffix=".html", delete=False) self.createHTML(appeal_file.name, counter + 1) results["Message"] = appeal_file.name return results
def processAlgorithm(self, parameters, context, model_feedback): """ Process the algorithm :param parameters: parameters of the process :param context: context of the process :param model_feedback: feedback instance for the process :return: """ # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the # overall progress through the model self.xml_path = parameters["XMLPATH"] self.gpkg_path = parameters["GPKGPATH"] if not self.xml_path.lower().endswith(".xml"): feedback = QgsProcessingMultiStepFeedback(0, model_feedback) feedback.reportError( "XML Workspace Definition is not an XML file!", True) return {} if not self.gpkg_path.lower().endswith(".gpkg"): feedback = QgsProcessingMultiStepFeedback(0, model_feedback) feedback.reportError("GeoPackage is not an GPKG file!", True) return {} self.pg_conn_name = parameters["DBNAME"] self.pg_schema = parameters["SCHEMA"] self.pg_drop_before = parameters["DROPIFEXISTS"] dataset_list = self.getDatasets() feedback = QgsProcessingMultiStepFeedback(2 + len(dataset_list), model_feedback) step = 0 self.create_pk_metadata_table(context, feedback) step = 1 for dataset in dataset_list: step += 1 definition = self.getDatasetDef(dataset) if definition is not None: try: in_layer = self.get_gpkg_vector_layer(definition[0]) if in_layer is not None: feedback.pushInfo("Feature Class: " + definition[0]) try: alg_params = { 'DATABASE': self.pg_conn_name, 'SQL': definition[1] } feedback.pushInfo( " processing (A) => qgis:postgisexecutesql") processing.run('qgis:postgisexecutesql', alg_params, context=context, feedback=feedback, is_child_algorithm=True) except Exception as e1: feedback.reportError( "Error creating table definition: \n" + definition[1] + ": " + str(e1), False) break try: # Esporta in PostgreSQL (connessioni disponibili) alg_params = { 'ADDFIELDS': False, 'APPEND': False, 'A_SRS': None, 'CLIP': False, 'DATABASE': self.pg_conn_name, 'DIM': 0, 'GEOCOLUMN': 'geom', 'GT': '', 'GTYPE': definition[4], 'INDEX': False, 'INPUT': self.get_gpkg_vector_layer(definition[0]), 'LAUNDER': False, 'OPTIONS': '', 'OVERWRITE': True, 'PK': '', 'PRECISION': True, 'PRIMARY_KEY': '', 'PROMOTETOMULTI': True, 'SCHEMA': self.pg_schema, 'SEGMENTIZE': '', 'SHAPE_ENCODING': '', 'SIMPLIFY': '', 'SKIPFAILURES': False, 'SPAT': None, 'S_SRS': None, 'TABLE': definition[0].lower() + '_tmp', 'T_SRS': None, 'WHERE': '' } feedback.pushInfo( " processing (B) => qgis:importvectorintopostgisdatabaseavailableconnections" ) processing.run( 'gdal:importvectorintopostgisdatabaseavailableconnections', alg_params, context=context, feedback=feedback, is_child_algorithm=True) except Exception as e2: feedback.reportError( "Error importing data: \n" + definition[0] + ": " + str(e2), False) break try: #Copy from TMP to FINAL table sql_copy = "INSERT INTO %s.%s(%s) SELECT %s FROM %s.%s_tmp" % ( self.pg_schema, definition[0], definition[2], definition[3], self.pg_schema, definition[0]) + ";" sql_drop = "DROP TABLE %s.%s_tmp" % ( self.pg_schema, definition[0]) + ";" alg_params = { 'DATABASE': self.pg_conn_name, 'SQL': sql_copy + sql_drop } feedback.pushInfo( " processing (C) => qgis:postgisexecutesql") processing.run('qgis:postgisexecutesql', alg_params, context=context, feedback=feedback, is_child_algorithm=True) except Exception as e3: feedback.reportError( "Error moving data: \n" + sql_copy + sql_drop + ": " + str(e3), False) break except Exception as e: feedback.reportError( "Error importing domain " + definition[1] + ": " + str(e), False) feedback.setCurrentStep(step) results = {} outputs = {} return results
def processAlgorithm(self, parameters, context, model_feedback): # source: 'export as python script' in processing modeler feedback = QgsProcessingMultiStepFeedback(3, model_feedback) # pass source = self.parameterAsFile(parameters, self.INPUT, context) server_name = self.server_name_options[self.parameterAsInt( parameters, self.SERVER_NAME, context)] graph_name = self.parameterAsString(parameters, self.GRAPH_NAME, context) graph_version = self.graph_version_options[self.parameterAsInt( parameters, self.GRAPH_VERSION, context)] routing_mode = self.routing_mode_options[self.parameterAsInt( parameters, self.ROUTING_MODE, context)] if os.path.splitext(source)[-1].lower() == '.json': feedback.pushInfo('Load json track file') with open(source) as json_data: track_data = json.load(json_data) elif os.path.splitext(source)[-1].lower() == '.gpx': feedback.pushInfo( 'Convert track file from GPX to JSON format using Graphium:Gpx2JsonConverter' ) try: output = processing.run("Graphium:gpx2jsonconverter", parameters={ 'INPUT': source, 'OUTPUT': 'TEMPORARY_OUTPUT' }, is_child_algorithm=True, context=context, feedback=feedback)['OUTPUT'] with open(output) as json_data: track_data = json.load(json_data) except QgsProcessingException as e: feedback.reportError( "Could not convert GPX file to JSON: " + str(e), True) return {self.OUTPUT_MATCHED_SEGMENTS: None} else: feedback.reportError( "Wrong track file format (" + os.path.splitext(source)[-1].lower() + ")", True) return {self.OUTPUT_MATCHED_SEGMENTS: None} # Connect to Graphium feedback.setCurrentStep(2) feedback.pushInfo("Connect to Graphium server '" + server_name + "' ...") graphium = GraphiumUtilitiesApi(feedback) selected_connection = self.connection_manager.select_graphium_server( server_name) if selected_connection is None: feedback.reportError('Cannot select connection to Graphium', True) return {self.OUTPUT_MATCHED_SEGMENTS: None} if graphium.connect(selected_connection) is False: feedback.reportError('Cannot connect to Graphium', True) return {self.OUTPUT_MATCHED_SEGMENTS: None} feedback.pushInfo("Start Map-Matching task on Graphium server '" + server_name + "' ...") response = graphium.do_map_matching(track_data, graph_name, graph_version, routing_mode) # Process map matching result if 'segments' in response: feedback.pushInfo('Finished map matching task!') elif 'error' in response: if 'msg' in response['error']: if response['error']['msg'] == 'ContentNotFoundError': feedback.reportError( 'Graphium server "' + server_name + '" does not support map matching', True) else: feedback.reportError(response['error']['msg'], True) return {self.OUTPUT_MATCHED_SEGMENTS: None} elif 'exception' in response: feedback.reportError(response['exception'], True) return {self.OUTPUT_MATCHED_SEGMENTS: None} else: feedback.reportError('Unknown mapmatching error', True) return {self.OUTPUT_MATCHED_SEGMENTS: None} feedback.setCurrentStep(3) feedback.pushInfo("Prepare result vector layer ...") vector_layer = self.prepare_vector_layer('matched_track') (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_MATCHED_SEGMENTS, context, vector_layer.fields(), QgsWkbTypes.LineString, vector_layer.sourceCrs()) total = 100.0 / len(response['segments']) for current, segment in enumerate(response['segments']): if feedback.isCanceled(): break feature = QgsFeature() feature.setGeometry(QgsGeometry.fromWkt(segment['geometry'])) feature.setFields(vector_layer.fields(), True) feature.setAttribute('order', current) for attribute_key in segment: try: feature.setAttribute(attribute_key, segment[attribute_key]) except KeyError: pass sink.addFeature(feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) feedback.pushInfo("Finished preparing vector layer " + dest_id) return { self.OUTPUT_MATCHED_SEGMENTS: dest_id, self.OUTPUT_NR_OF_U_TURNS: response['nrOfUTurns'], self.OUTPUT_NR_OF_SHORTEST_PATH_SEARCHES: response['nrOfShortestPathSearches'], self.OUTPUT_LENGTH: response['length'], self.OUTPUT_MATCHED_FACTOR: response['matchedFactor'], self.OUTPUT_MATCHED_POINTS: response['matchedPoints'], self.OUTPUT_CERTAIN_PATH_END_SEGMENT_ID: response['certainPathEndSegmentId'] }
def processAlgorithm(self, parameters, context, model_feedback): feedback = QgsProcessingMultiStepFeedback(2, model_feedback) source = self.parameterAsSource(parameters, self.INPUT, context) field_segment_id = self.parameterAsString(parameters, self.FIELD_SEGMENT_ID, context) segment_attribute_index = self.parameterAsInt(parameters, self.SEGMENT_ATTRIBUTE, context) segment_attribute = self.segment_attribute_options[segment_attribute_index] target_field = self.parameterAsString(parameters, self.TARGET_FIELD, context) server_name = self.connection_options[self.parameterAsInt(parameters, self.SERVER_NAME, context)] graph_name = self.parameterAsString(parameters, self.GRAPH_NAME, context) graph_version = self.parameterAsString(parameters, self.GRAPH_VERSION, context) feedback.pushInfo("Connect to Graphium server '" + server_name + "' ...") graphium = GraphiumGraphDataApi(feedback) selected_connection = self.connection_manager.select_graphium_server(server_name) if selected_connection is None: feedback.reportError('Cannot select connection to Graphium', True) return {self.OUTPUT: None} if graphium.connect(selected_connection) is False: feedback.reportError('Cannot connect to [' + server_name + ']', True) return {self.OUTPUT: None} feedback.pushInfo("Start downloading task on Graphium server '" + server_name + "' ...") total = 100.0 / source.featureCount() if source.featureCount() else 0 # Read segment IDs segment_ids = [] attributes_per_segment = dict() for current, feature in enumerate(source.getFeatures()): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break if not feature[field_segment_id]: continue if not feature[field_segment_id] in segment_ids: segment_ids.append(feature[field_segment_id]) if len(segment_ids) > 50: self.get_segment_attributes(feedback, graphium, graph_name, graph_version, segment_attribute, segment_ids, attributes_per_segment) # Update the progress bar feedback.setProgress(int(current * total)) if len(segment_ids) > 0: self.get_segment_attributes(feedback, graphium, graph_name, graph_version, segment_attribute, segment_ids, attributes_per_segment) feedback.setCurrentStep(1) feedback.pushInfo("Add attributes to features") (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) for current, feature in enumerate(source.getFeatures()): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break if feature[field_segment_id]: if int(feature[field_segment_id]) in attributes_per_segment: feature[target_field] = attributes_per_segment[int(feature[field_segment_id])] else: feedback.pushInfo("No attribute for segment " + str(feature[field_segment_id])) sink.addFeature(feature, QgsFeatureSink.FastInsert) # Update the progress bar feedback.setProgress(int(current * total)) feedback.setProgress(100) return { self.OUTPUT: dest_id }
def processAlgorithm(self, parameters, context, model_feedback): # entrées profils_l = self.parameterAsVectorLayer(parameters, 'profils', context) mnt = self.parameterAsRasterLayer(parameters, 'mnt', context) # sorties output = self.parameterAsOutputLayer(parameters, 'OUTPUT', context) # paramètres echantillons_nb = parameters['echantillons_nb'] # nombre d'échantillons seuil_diff = parameters['seuil_diff'] seuil_rug = parameters['seuil_rug'] # variables propres à Processing feedback = QgsProcessingMultiStepFeedback(profils_l.featureCount()*2, model_feedback) status = 0 results = {} # l'algo SAGA Cross Profiles ajoute à chaque profil un attribut LINE qui permet d'identifier pour chaque profil la ligne dont il est issu # permet de traiter les profils de cours d'eau à cours d'eau if profils_l.fields().indexOf('LINE')<0: feedback.reportError("Les profils en entrée doivent contenir un attribut numérique LINE qui identifie chaque cours d'eau de manière unique !", True) return{} # préparation de la sortie id = 1 fields = QgsFields() fields.append(QgsField("id", QVariant.Int)) fields.append(QgsField("obstruct", QVariant.Int)) writer = QgsVectorFileWriter(output, "System", fields, QgsWkbTypes.LineString, QgsCoordinateReferenceSystem(2154), "ESRI Shapefile") # on récupère les identifiants uniques de lignes pour traiter les profils par cours d'eau lines_ids = profils_l.uniqueValues(profils_l.fields().indexOf('LINE')) for line_id in lines_ids: # variables liées aux traitements low = None # dernière valeur d'altitude non-obstruée ids = [] # liste des identifiants de profils obstrués plist = [] # liste des X derniers profils p2 = None # id du deuxième profil traité count = 0 # pour chaque cours d'eau for profil_f in profils_l.getFeatures("LINE = %s"%line_id): # ajout des points sur chaque profil profil_g = profil_f.geometry() freq = profil_g.length()/(echantillons_nb-1) echantillons_g = [QgsGeometry().fromPointXY(profil_g.asMultiPolyline()[0][0])] for i in range(1, echantillons_nb-1): echantillons_g.append(profil_g.interpolate(freq*i)) echantillons_g.append(QgsGeometry().fromPointXY(profil_g.asMultiPolyline()[0][-1])) # on affecte aux points la valeur du MNT correspondante elevations = [] for echantillon_g in echantillons_g: elevation = mnt.dataProvider().sample(echantillon_g.asPoint(), 1)[0] elevations.append(elevation) # exécuté pour le tout premier profil qui va déterminer la première valeur d'altitude considérée if low == None: low = min(elevations) plist.append(profil_f) # détection des obstructions else: # seuil utilisé pour détecter les ruptures franches et limiter les "petites détections" if min(elevations) <= low+seuil_diff: # en cas de longue portion souterraine, la condition d'écoulement peut être remplie alors que le CE est toujours souterrain (cas sur le Malvan à Cagnes) # on vérifie alors si le terrain est relativement plat (rugosité faible) et, si c'est le cas, on maintient l'obstruction # pose problème sur les CE peu profonds if not plist and (max(elevations)-min(elevations))<seuil_rug: ids.append(profil_f.id()) # il y a écoulement else: low = min(elevations) plist.append(profil_f.id()) else: # il n'y a pas écoulement ids.append(profil_f.id()) # s'il y a un groupe de 5 (ou moins) profils non-obstrués au milieu de profils obstrués, on les ajoute au traitement if len(plist) <= 5: ids += plist del plist[:] # récupération de l'id du deuxième profil if count == 1: p2 = profil_f.id() status += 1 count += 1 feedback.setCurrentStep(status) if feedback.isCanceled(): return {} # si le second profil est obstrué, on considère qu'il y a erreur et on ne traite pas le CE # lorsque le cas arrive, il s'agit souvent d'un CE non-présent sur le MNT et le traitement est erronné if p2 in ids: del ids[:] # post processing pour étendre la détection à x profils amont/aval pour permettre l'interpolation # extention modulable par la variable ext ci-dessous ext = 2 prev = [] count = 0 for profil_f in profils_l.getFeatures("LINE = %s"%line_id): if len(prev) > 0: if count == 0: if profil_f.id() in ids and prev[-1] not in ids: ids += prev if profil_f.id() not in ids and prev[-1] in ids: ids.append(profil_f.id()) count += 1 else: if count < ext: ids.append(profil_f.id()) count += 1 else: count = 0 prev.append(profil_f.id()) if len(prev) > ext: del prev[0] # attribution d'un identifiant unique à chaque groupe de profils souterrains # ecriture de chaque profil dans la nouvelle couche qui contient deux attributs : # id : identifie les profils obstrués contigus pour les traiter en groupe # obstruct : à 1 si le profil est obstrué, sinon à 0 for profil_f in profils_l.getFeatures("LINE = %s"%line_id): if profil_f.id() not in ids: profil_f.setAttributes([0,0]) id += 1 else: profil_f.setAttributes([id,1]) writer.addFeature(profil_f) status += 1 feedback.setCurrentStep(status) if feedback.isCanceled(): return {} results['OUTPUT']=output return results
def processAlgorithm(self, parameters, context, model_feedback): # source: 'export as python script' in processing modeler feedback = QgsProcessingMultiStepFeedback(4, model_feedback) start_coordinate = self.parameterAsPoint( parameters, self.START_COORDINATE, context, QgsCoordinateReferenceSystem(4326)) end_coordinate = self.parameterAsPoint( parameters, self.END_COORDINATE, context, QgsCoordinateReferenceSystem(4326)) routing_mode = self.routing_mode_options[self.parameterAsInt( parameters, self.ROUTING_MODE, context)] routing_criteria = self.routing_criteria_options[self.parameterAsInt( parameters, self.ROUTING_CRITERIA, context)] # cut_segments = self.parameterAsBool(parameters, self.CUT_SEGMENTS, context) server_name = self.server_name_options[self.parameterAsInt( parameters, self.SERVER_NAME, context)] graph_name = self.parameterAsString(parameters, self.GRAPH_NAME, context) graph_version = self.parameterAsString(parameters, self.GRAPH_VERSION, context) # Connect to Graphium feedback.setCurrentStep(2) feedback.pushInfo("Connect to Graphium server '" + server_name + "' ...") graphium = GraphiumUtilitiesApi(feedback) selected_connection = self.connection_manager.select_graphium_server( server_name) if selected_connection is None: feedback.reportError('Cannot select connection to Graphium', True) return {self.OUTPUT: None, self.OUTPUT_PATH: None} if graphium.connect(selected_connection) is False: feedback.reportError('Cannot connect to Graphium', True) return {self.OUTPUT: None, self.OUTPUT_PATH: None} feedback.pushInfo("Start Routing task on Graphium server '" + server_name + "' ...") response = graphium.do_routing(graph_name, graph_version, start_coordinate.x(), start_coordinate.y(), end_coordinate.x(), end_coordinate.y(), datetime.today(), None, routing_mode, routing_criteria) # Process routing result if 'route' in response: if response['route']['length'] == 0: feedback.reportError('No route found', False) return {self.OUTPUT: None, self.OUTPUT_PATH: None} elif 'error' in response: if 'msg' in response['error']: if response['error']['msg'] == 'ContentNotFoundError': feedback.reportError( 'Graphium server "' + server_name + '" does not support routing', True) else: feedback.reportError(response['error']['msg'], True) return {self.OUTPUT: None, self.OUTPUT_PATH: None} else: feedback.reportError('Unknown routing error', True) feedback.reportError(str(response), True) return {self.OUTPUT: None, self.OUTPUT_PATH: None} # create feature output feedback.setCurrentStep(3) vector_layer = self.prepare_vector_layer('route') (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, vector_layer.fields(), QgsWkbTypes.LineString, vector_layer.sourceCrs()) if response['route']['geometry'] is not None: feature = QgsFeature() feature.setGeometry( QgsGeometry.fromWkt(response['route']['geometry'])) feature.setFields(vector_layer.fields(), True) for attribute_key in response['route']: if feedback.isCanceled(): break try: feature.setAttribute(attribute_key, response['route'][attribute_key]) except KeyError: pass sink.addFeature(feature, QgsFeatureSink.FastInsert) # create path output feedback.setCurrentStep(4) path_layer = self.prepare_path_layer('route_path') (sink_path, dest_id_path) = self.parameterAsSink(parameters, self.OUTPUT_PATH, context, path_layer.fields(), QgsWkbTypes.NoGeometry, vector_layer.sourceCrs()) if response['route']['geometry'] is not None: total = 100.0 / len(response['route']['segments']) for current, path_segment in enumerate( response['route']['segments']): if feedback.isCanceled(): break feature = QgsFeature() feature.setFields(path_layer.fields(), True) feature.setAttribute('order', current) feature.setAttribute('segment_id', path_segment['id']) feature.setAttribute('linkDirectionForward', path_segment['linkDirectionForward']) sink_path.addFeature(feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id, self.OUTPUT_PATH: dest_id_path}
def processAlgorithm(self, parameters, context, model_feedback): # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the # overall progress through the model feedback = QgsProcessingMultiStepFeedback(25, model_feedback) results = {} outputs = {} nlcd_rast_output = self.parameterAsBool(parameters, "OutputNLCDLandCoverRaster", context) nlcd_vect_output = self.parameterAsBool(parameters, "OutputNLCDLandCoverVector", context) nlcd_rast_imp_output = self.parameterAsBool( parameters, "OutputNLCDImperviousRaster", context) soil_output = self.parameterAsBool(parameters, "OutputSoilLayer", context) curve_number_output = self.parameterAsBool(parameters, "OutputCurveNumberLayer", context) # Assiging Default CN_Lookup Table if parameters["cnlookup"] == None: csv_uri = ("file:///" + os.path.join(cmd_folder, "CN_Lookup.csv") + "?delimiter=,") csv = QgsVectorLayer(csv_uri, "CN_Lookup.csv", "delimitedtext") parameters["cnlookup"] = csv area_layer = self.parameterAsVectorLayer(parameters, "areaboundary", context) EPSGCode = area_layer.crs().authid() origEPSGCode = EPSGCode # preserve orignal EPSGCode to project back to it # feedback.pushInfo(str(EPSGCode)) if check_crs_acceptable(EPSGCode): pass else: # Reproject layer to EPSG:5070 alg_params = { "INPUT": parameters["areaboundary"], "OPERATION": "", "TARGET_CRS": QgsCoordinateReferenceSystem("EPSG:5070"), "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["ReprojectLayer5070"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) area_layer = context.takeResultLayer( outputs["ReprojectLayer5070"]["OUTPUT"]) EPSGCode = area_layer.crs().authid() # Check if area of the extent is less than 100,000 Acres d = QgsDistanceArea() tr_cont = QgsCoordinateTransformContext() d.setSourceCrs(area_layer.crs(), tr_cont) # d.setEllipsoid(area_layer.crs().ellipsoidAcronym()) extent_area = d.measureArea(QgsGeometry().fromRect( area_layer.extent())) area_acres = d.convertAreaMeasurement(extent_area, QgsUnitTypes.AreaAcres) if area_acres > 500000: feedback.reportError( f"Area Boundary layer extent area should be less than 500,000 acres.\nArea Boundary layer extent area is {round(area_acres,4):,} acres.\n\nExecution Failed", True, ) return results elif area_acres > 100000: feedback.reportError( f"Your Area Boundary layer extent area is {round(area_acres,4):,} acres. The recommended extent area is 100,000 acres or less. If the Algorithm fails, rerun with a smaller input layer.\n", False, ) else: feedback.pushInfo( f"Area Boundary layer extent area is {round(area_acres,4):,} acres\n" ) # Get extent of the area boundary layer xmin = area_layer.extent().xMinimum() ymin = area_layer.extent().yMinimum() xmax = area_layer.extent().xMaximum() ymax = area_layer.extent().yMaximum() BBOX_width = (xmax - xmin) / 30 BBOX_height = (ymax - ymin) / 30 BBOX_width_int = round(BBOX_width) BBOX_height_int = round(BBOX_height) # NLCD Impervious Raster if nlcd_rast_imp_output == True: request_URL = f"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Impervious_L48/ows?version=1.3.0&service=WMS&layers=NLCD_2016_Impervious_L48&styles&crs={str(EPSGCode)}&format=image/geotiff&request=GetMap&width={str(BBOX_width_int)}&height={str(BBOX_height_int)}&BBOX={str(xmin)},{str(ymin)},{str(xmax)},{str(ymax)}&" # Download NLCD Impervious Raster try: ping_URL = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Impervious_L48/ows" r = requests.head(ping_URL, verify=False) r.raise_for_status() alg_params = { "URL": request_URL, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadNlcdImp"] = processing.run( "native:filedownloader", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except (QgsProcessingException, requests.exceptions.HTTPError) as e: feedback.reportError( f"Error: {str(e)}\n\nError requesting land use data from 'www.mrlc.gov'. Most probably because either their server is down or there is a certification issue.\nThis should be temporary. Try again later.\n", True, ) return results feedback.setCurrentStep(1) if feedback.isCanceled(): return {} # reproject to original crs # Warp (reproject) if EPSGCode != origEPSGCode: alg_params = { "DATA_TYPE": 0, "EXTRA": "", "INPUT": outputs["DownloadNlcdImp"]["OUTPUT"], "MULTITHREADING": False, "NODATA": None, "OPTIONS": "", "RESAMPLING": 0, "SOURCE_CRS": None, "TARGET_CRS": QgsCoordinateReferenceSystem(str(origEPSGCode)), "TARGET_EXTENT": None, "TARGET_EXTENT_CRS": None, "TARGET_RESOLUTION": None, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadNlcdImp"] = processing.run( "gdal:warpreproject", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) # Set layer style alg_params = { "INPUT": outputs["DownloadNlcdImp"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "NLCD_Raster_Imp.qml"), } try: # for QGIS Version later than 3.12 outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForRasterLayer"] = processing.run( "qgis:setstyleforrasterlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(2) if feedback.isCanceled(): return {} # NLCD Land Cover Data if (curve_number_output == True or nlcd_vect_output == True or nlcd_rast_output == True): request_URL = f"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Land_Cover_L48/ows?version=1.3.0&service=WMS&layers=NLCD_2016_Land_Cover_L48&styles&crs={str(EPSGCode)}&format=image/geotiff&request=GetMap&width={str(BBOX_width_int)}&height={str(BBOX_height_int)}&BBOX={str(xmin)},{str(ymin)},{str(xmax)},{str(ymax)}&" # Download NLCD try: ping_URL = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Land_Cover_L48/ows" r = requests.head(ping_URL, verify=False) r.raise_for_status() alg_params = { "URL": request_URL, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadNlcd"] = processing.run( "native:filedownloader", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except (QgsProcessingException, requests.exceptions.HTTPError) as e: feedback.reportError( f"Error: {str(e)}\n\nError requesting land use data from 'www.mrlc.gov'. Most probably because either their server is down or there is a certification issue.\nThis should be temporary. Try again later.\n", True, ) return results feedback.setCurrentStep(3) if feedback.isCanceled(): return {} # reproject to original crs # Warp (reproject) if EPSGCode != origEPSGCode: alg_params = { "DATA_TYPE": 0, "EXTRA": "", "INPUT": outputs["DownloadNlcd"]["OUTPUT"], "MULTITHREADING": False, "NODATA": None, "OPTIONS": "", "RESAMPLING": 0, "SOURCE_CRS": None, "TARGET_CRS": QgsCoordinateReferenceSystem(str(origEPSGCode)), "TARGET_EXTENT": None, "TARGET_EXTENT_CRS": None, "TARGET_RESOLUTION": None, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadNlcd"] = processing.run( "gdal:warpreproject", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) # Reclassify by table alg_params = { "DATA_TYPE": 5, "INPUT_RASTER": outputs["DownloadNlcd"]["OUTPUT"], "NODATA_FOR_MISSING": False, "NO_DATA": -9999, "RANGE_BOUNDARIES": 0, "RASTER_BAND": 1, "TABLE": QgsExpression( "'0,1,11,1,2,12,2,3,21,3,4,22,4,5,23,5,6,24,6,7,31,7,8,32,8,9,41,9,10,42,10,11,43,11,12,51,12,13,52,13,14,71,14,15,72,15,16,73,16,17,74,17,18,81,18,19,82,19,20,90,20,21,95'" ).evaluate(), "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["ReclassifyByTable"] = processing.run( "native:reclassifybytable", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(4) if feedback.isCanceled(): return {} # Set layer style alg_params = { "INPUT": outputs["ReclassifyByTable"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "NLCD_Raster.qml"), } try: # for QGIS Version later than 3.12 outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForRasterLayer"] = processing.run( "qgis:setstyleforrasterlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(5) if feedback.isCanceled(): return {} if curve_number_output == True or nlcd_vect_output == True: # Polygonize (raster to vector) alg_params = { "BAND": 1, "EIGHT_CONNECTEDNESS": False, "EXTRA": "", "FIELD": "VALUE", "INPUT": outputs["ReclassifyByTable"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["PolygonizeRasterToVector"] = processing.run( "gdal:polygonize", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(6) if feedback.isCanceled(): return {} # Fix geometries alg_params = { "INPUT": outputs["PolygonizeRasterToVector"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["FixGeometries"] = processing.run( "native:fixgeometries", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(7) if feedback.isCanceled(): return {} # Set layer style alg_params = { "INPUT": outputs["FixGeometries"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "NLCD_Vector.qml"), } try: # for QGIS Version 3.12 and later outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForVectorLayer"] = processing.run( "qgis:setstyleforvectorlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(8) if feedback.isCanceled(): return {} # Soil Layer if soil_output == True or curve_number_output == True: # Reproject layer alg_params = { "INPUT": parameters["areaboundary"], "OPERATION": "", "TARGET_CRS": QgsCoordinateReferenceSystem("EPSG:4326"), "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["ReprojectLayer4326"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(9) if feedback.isCanceled(): return {} # Get Area Boundary layer extent in EPSG:4326 area_layer_reprojected = context.takeResultLayer( outputs["ReprojectLayer4326"]["OUTPUT"]) # Download Soil try: # request using post rest # create vector layer structure to store data feedback.pushInfo("Creating POST request...") uri = "Polygon?crs=epsg:4326" soil_layer = QgsVectorLayer(uri, "soil layer", "memory") provider = soil_layer.dataProvider() attributes = [] attr_dict = [ { "name": "musym", "type": "str" }, { "name": "muname", "type": "str" }, { "name": "mustatus", "type": "str" }, { "name": "slopegraddcp", "type": "str" }, { "name": "slopegradwta", "type": "str" }, { "name": "brockdepmin", "type": "str" }, { "name": "wtdepannmin", "type": "str" }, { "name": "wtdepaprjunmin", "type": "str" }, { "name": "flodfreqdcd", "type": "str" }, { "name": "flodfreqmax", "type": "str" }, { "name": "pondfreqprs", "type": "str" }, { "name": "aws025wta", "type": "str" }, { "name": "aws050wta", "type": "str" }, { "name": "aws0100wta", "type": "str" }, { "name": "aws0150wta", "type": "str" }, { "name": "drclassdcd", "type": "str" }, { "name": "drclasswettest", "type": "str" }, { "name": "hydgrpdcd", "type": "str" }, { "name": "iccdcd", "type": "str" }, { "name": "iccdcdpct", "type": "str" }, { "name": "niccdcd", "type": "str" }, { "name": "niccdcdpct", "type": "str" }, { "name": "engdwobdcd", "type": "str" }, { "name": "engdwbdcd", "type": "str" }, { "name": "engdwbll", "type": "str" }, { "name": "engdwbml", "type": "str" }, { "name": "engstafdcd", "type": "str" }, { "name": "engstafll", "type": "str" }, { "name": "engstafml", "type": "str" }, { "name": "engsldcd", "type": "str" }, { "name": "engsldcp", "type": "str" }, { "name": "englrsdcd", "type": "str" }, { "name": "engcmssdcd", "type": "str" }, { "name": "engcmssmp", "type": "str" }, { "name": "urbrecptdcd", "type": "str" }, { "name": "urbrecptwta", "type": "str" }, { "name": "forpehrtdcp", "type": "str" }, { "name": "hydclprs", "type": "str" }, { "name": "awmmfpwwta", "type": "str" }, { "name": "mukey", "type": "str" }, { "name": "mupolygonkey", "type": "str" }, { "name": "areasymbol", "type": "str" }, { "name": "nationalmusym", "type": "str" }, ] # initialize fields for field in attr_dict: attributes.append(QgsField(field["name"], QVariant.String)) provider.addAttributes(attributes) soil_layer.updateFields() # get area layer extent polygon as WKT in 4326 aoi_reproj_wkt = area_layer_reprojected.extent().asWktPolygon() # send post request body = { "format": "JSON", "query": f"select Ma.*, M.mupolygonkey, M.areasymbol, M.nationalmusym, M.mupolygongeo from mupolygon M, muaggatt Ma where M.mupolygonkey in (select * from SDA_Get_Mupolygonkey_from_intersection_with_WktWgs84('{aoi_reproj_wkt.lower()}')) and M.mukey=Ma.mukey", } url = "https://sdmdataaccess.sc.egov.usda.gov/TABULAR/post.rest" soil_response = requests.post(url, json=body).json() feedback.setCurrentStep(10) if feedback.isCanceled(): return {} for row in soil_response["Table"]: # None attribute for empty data row = [None if not attr else attr for attr in row] feat = QgsFeature(soil_layer.fields()) # populate data for index, col in enumerate(row): if index != len(attr_dict): feat.setAttribute(attr_dict[index]["name"], col) else: feat.setGeometry(QgsGeometry.fromWkt(col)) provider.addFeatures([feat]) feedback.setCurrentStep(11) if feedback.isCanceled(): return {} except: # try wfs request feedback.reportError( "Error getting soil data through post request. Your input layer maybe too large. Trying WFS download now.\nIf the Algorithm get stuck during download. Terminate the Algorithm and rerun with a smaller input layer.", False, ) xmin_reprojected = area_layer_reprojected.extent().xMinimum() ymin_reprojected = area_layer_reprojected.extent().yMinimum() xmax_reprojected = area_layer_reprojected.extent().xMaximum() ymax_reprojected = area_layer_reprojected.extent().yMaximum() request_URL_soil = f"https://sdmdataaccess.sc.egov.usda.gov/Spatial/SDMWGS84GEOGRAPHIC.wfs?SERVICE=WFS&VERSION=1.1.0&REQUEST=GetFeature&TYPENAME=mapunitpolyextended&SRSNAME=EPSG:4326&BBOX={str(xmin_reprojected)},{str(ymin_reprojected)},{str(xmax_reprojected)},{str(ymax_reprojected)}" alg_params = { "URL": request_URL_soil, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadSoil"] = processing.run( "native:filedownloader", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(12) if feedback.isCanceled(): return {} # Swap X and Y coordinates alg_params = { "INPUT": outputs["DownloadSoil"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["SwapXAndYCoordinates"] = processing.run( "native:swapxy", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(13) if feedback.isCanceled(): return {} soil_layer = outputs["SwapXAndYCoordinates"]["OUTPUT"] # Fix soil layer geometries alg_params = { "INPUT": soil_layer, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT } outputs["FixGeometries2"] = processing.run( "native:fixgeometries", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(14) if feedback.isCanceled(): return {} # Clip Soil Layer alg_params = { "INPUT": outputs["FixGeometries2"]["OUTPUT"], "OVERLAY": parameters["areaboundary"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Clip"] = processing.run( "native:clip", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(15) if feedback.isCanceled(): return {} # Reproject Soil alg_params = { "INPUT": outputs["Clip"]["OUTPUT"], "OPERATION": "", "TARGET_CRS": QgsCoordinateReferenceSystem(origEPSGCode), "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["ReprojectSoil"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(16) if feedback.isCanceled(): return {} # Fix soil layer geometries second time alg_params = { "INPUT": outputs["ReprojectSoil"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["FixGeometries3"] = processing.run( "native:fixgeometries", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(17) if feedback.isCanceled(): return {} # Set layer style alg_params = { "INPUT": outputs["FixGeometries3"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "Soil_Layer.qml"), } try: # for QGIS Version 3.12 and later outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForVectorLayer"] = processing.run( "qgis:setstyleforvectorlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(18) if feedback.isCanceled(): return {} # Curve Number Calculations if curve_number_output == True: feedback.pushInfo( "Generating Curve Number Layer. This may take a while. Do not cancel." ) # Intersection alg_params = { "INPUT": outputs["FixGeometries3"]["OUTPUT"], "INPUT_FIELDS": ["MUSYM", "HYDGRPDCD", "MUNAME"], "OVERLAY": outputs["FixGeometries"]["OUTPUT"], "OVERLAY_FIELDS": ["VALUE"], "OVERLAY_FIELDS_PREFIX": "", "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Intersection"] = processing.run( "native:intersection", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(19) if feedback.isCanceled(): return {} # Create GDCodeTemp alg_params = { "FIELD_LENGTH": 5, "FIELD_NAME": "GDCodeTemp", "FIELD_PRECISION": 3, "FIELD_TYPE": 2, "FORMULA": 'IF ("HYDGRPDCD" IS NOT NULL, "Value" || "HYDGRPDCD", IF (("MUSYM" = \'W\' OR lower("MUSYM") = \'water\' OR lower("MUNAME") = \'water\' OR "MUNAME" = \'W\'), 11, "VALUE"))', "INPUT": outputs["Intersection"]["OUTPUT"], "NEW_FIELD": True, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateGdcodetemp"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(20) if feedback.isCanceled(): return {} # Create GDCode alg_params = { "FIELD_LENGTH": 5, "FIELD_NAME": "GDCode", "FIELD_PRECISION": 3, "FIELD_TYPE": 2, "FORMULA": "if( var('drainedsoilsleaveuncheckedifnotsure') = True,replace(\"GDCodeTemp\", '/D', ''),replace(\"GDCodeTemp\", map('A/', '', 'B/', '', 'C/', '')))", "INPUT": outputs["CreateGdcodetemp"]["OUTPUT"], "NEW_FIELD": True, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateGdcode"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(21) if feedback.isCanceled(): return {} # Create NLCD_LU alg_params = { "FIELD_LENGTH": 2, "FIELD_NAME": "NLCD_LU", "FIELD_PRECISION": 3, "FIELD_TYPE": 1, "FORMULA": '"Value"', "INPUT": outputs["CreateGdcode"]["OUTPUT"], "NEW_FIELD": True, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateNlcd_lu"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(22) if feedback.isCanceled(): return {} # Join with CNLookup alg_params = { "DISCARD_NONMATCHING": False, "FIELD": "GDCode", "FIELDS_TO_COPY": ["CN_Join"], "FIELD_2": "GDCode", "INPUT": outputs["CreateNlcd_lu"]["OUTPUT"], "INPUT_2": parameters["cnlookup"], "METHOD": 1, "PREFIX": "", "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["JoinWithCnlookup"] = processing.run( "native:joinattributestable", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(23) if feedback.isCanceled(): return {} # Create Integer CN alg_params = { "FIELD_LENGTH": 3, "FIELD_NAME": "CN", "FIELD_PRECISION": 0, "FIELD_TYPE": 1, "FORMULA": "CN_Join * 1", "INPUT": outputs["JoinWithCnlookup"]["OUTPUT"], "NEW_FIELD": True, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateIntegerCn"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(24) if feedback.isCanceled(): return {} # Drop field(s) alg_params = { "COLUMN": ["VALUE", "GDCodeTemp", "CN_Join"], "INPUT": outputs["CreateIntegerCn"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DropFields"] = processing.run( "qgis:deletecolumn", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(25) if feedback.isCanceled(): return {} # Set layer style alg_params = { "INPUT": outputs["DropFields"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "CN_Grid.qml"), } try: # for QGIS Version 3.12 and later outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForVectorLayer"] = processing.run( "qgis:setstyleforvectorlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if nlcd_rast_output: # Load NLCD Raster into project alg_params = { "INPUT": outputs["ReclassifyByTable"]["OUTPUT"], "NAME": "NLCD Land Cover Raster", } outputs["LoadLayerIntoProject1"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if nlcd_vect_output: # Load NLCD Vector Layer into project alg_params = { "INPUT": outputs["FixGeometries"]["OUTPUT"], "NAME": "NLCD Land Cover Vector", } outputs["LoadLayerIntoProject2"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if nlcd_rast_imp_output: # Load NLCD Impervious Raster into project alg_params = { "INPUT": outputs["DownloadNlcdImp"]["OUTPUT"], "NAME": "NLCD Impervious Raster", } outputs["LoadLayerIntoProject3"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if soil_output: # Load Soil Layer into project alg_params = { "INPUT": outputs["FixGeometries3"]["OUTPUT"], "NAME": "SSURGO Soil Layer", } outputs["LoadLayerIntoProject4"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if curve_number_output: # Load Curve Number Layer into project alg_params = { "INPUT": outputs["DropFields"]["OUTPUT"], "NAME": "Curve Number Layer", } outputs["LoadLayerIntoProject5"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) # log usage with open(os.path.join(cmd_folder, "usage_counter.log"), "r+") as f: counter = int(f.readline()) f.seek(0) f.write(str(counter + 1)) # check if counter is milestone if (counter + 1) % 25 == 0: appeal_file = NamedTemporaryFile("w", suffix=".html", delete=False) self.createHTML(appeal_file.name, counter + 1) results["Message"] = appeal_file.name return results
def processAlgorithm(self, parameters, context, model_feedback): feedback = QgsProcessingMultiStepFeedback(2, model_feedback) server_name = self.connection_options[self.parameterAsInt(parameters, self.SERVER_NAME, context)] graph_name = self.parameterAsString(parameters, self.GRAPH_NAME, context) graph_version = self.parameterAsString(parameters, self.GRAPH_VERSION, context) save_json_file = self.parameterAsBoolean(parameters, self.SAVE_JSON_FILE, context) json_file = self.parameterAsFileOutput(parameters, self.OUTPUT_JSON, context) # Connect to Graphium feedback.pushInfo("Connect to Graphium server '" + server_name + "' ...") graphium_data = GraphiumGraphDataApi(feedback) graphium_management = GraphiumGraphManagementApi(feedback) selected_connection = self.connection_manager.select_graphium_server(server_name) if selected_connection is None: feedback.reportError('Cannot select connection to Graphium', True) return {self.OUTPUT_SEGMENTS: None} if graphium_management.connect(selected_connection) is False: feedback.reportError('Cannot connect to Graphium', True) return {self.OUTPUT_SEGMENTS: None} if graphium_data.connect(selected_connection) is False: feedback.reportError('Cannot connect to Graphium', True) return {self.OUTPUT_SEGMENTS: None} metadata = graphium_management.get_graph_version_metadata(graph_name, graph_version) if not metadata['type']: feedback.reportError('Cannot correctly retrieve graph metadata', True) return {self.OUTPUT_SEGMENTS: None} feedback.pushInfo("Start downloading task on Graphium server '" + server_name + "' ...") response = graphium_data.export_graph(graph_name, graph_version, metadata.get('type') == 'hdwaysegment') if save_json_file: feedback.pushInfo("Write graph to JSON file...") with open(json_file, 'w') as output_file: output_file.write(json.dumps(response)) feedback.setCurrentStep(1) if 'graphVersionMetadata' in response: # if response['graphVersionMetadata']['segmentsCount'] == 0: # feedback.reportError('No segments available', False) # return {self.OUTPUT_SEGMENT_COUNT: 0} # elif response['graphVersionMetadata']['state'] == 'DELETED': if response['graphVersionMetadata']['state'] == 'DELETED': feedback.reportError('Graph version has been deleted', False) return {self.OUTPUT_SEGMENT_COUNT: 0} elif 'error' in response: if 'msg' in response['error']: feedback.reportError(response['error']['msg'], True) return {self.OUTPUT_SEGMENT_COUNT: 0} else: feedback.reportError('Unknown error', True) return {self.OUTPUT_SEGMENT_COUNT: 0} feedback.pushInfo("Prepare result vector layer ...") vector_layer = self.prepare_vector_layer('segments_' + graph_name + '_' + graph_version, response['graphVersionMetadata']['type']) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_SEGMENTS, context, vector_layer.fields(), QgsWkbTypes.LineString, vector_layer.sourceCrs()) total = 100.0 / len(response[response['graphVersionMetadata']['type']]) for current, segment in enumerate(response[response['graphVersionMetadata']['type']]): if feedback.isCanceled(): break feature = QgsFeature() feature.setGeometry(QgsGeometry.fromWkt(segment['geometry'])) feature.setFields(vector_layer.fields(), True) for attribute_key in segment: try: if attribute_key == 'tags' or attribute_key == 'connection': feature.setAttribute(attribute_key, json.dumps(segment[attribute_key])) else: feature.setAttribute(attribute_key, segment[attribute_key]) except KeyError: pass sink.addFeature(feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) feedback.pushInfo("Finished preparing vector layer " + dest_id) return {self.OUTPUT_SEGMENTS: dest_id, self.OUTPUT_JSON: json_file if save_json_file else None, self.OUTPUT_SEGMENT_COUNT: response['graphVersionMetadata']['segmentsCount'] }
def processAlgorithm(self, parameters, context, model_feedback): feedback = QgsProcessingMultiStepFeedback(2, model_feedback) source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) group_field_name = self.parameterAsString(parameters, self.GROUP_FIELD, context) order_field_name = self.parameterAsString(parameters, self.ORDER_FIELD, context) date_format = self.parameterAsString(parameters, self.DATE_FORMAT, context) group_field_index = source.fields().lookupField(group_field_name) order_field_index = source.fields().lookupField(order_field_name) field_names = self.parameterAsFields(parameters, self.FIELDS, context) if group_field_index >= 0: group_field_def = source.fields().at(group_field_index) else: group_field_def = None order_field_def = source.fields().at(order_field_index) #Create output for lines fields = QgsFields() self.addFields(source, fields, 'start_order', field_names, order_field_def) self.addFields(source, fields, 'end_order', field_names, order_field_def) fields.append(QgsField('COUNT', QVariant.LongLong)) output_wkb = QgsWkbTypes.LineString if QgsWkbTypes.hasM(source.wkbType()): output_wkb = QgsWkbTypes.addM(output_wkb) if QgsWkbTypes.hasZ(source.wkbType()): output_wkb = QgsWkbTypes.addZ(output_wkb) (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LINE, context, fields, output_wkb, source.sourceCrs()) if self.sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT_LINE)) #Create output for Points fields_point = QgsFields() fields_point.append(QgsField("fid", QVariant.Int, "int", 9, 0)) self.addFields(source, fields_point, 'end_order', field_names) fields_point.append(QgsField("COUNT", QVariant.LongLong)) fields_point.append(QgsField("COUNT_IN", QVariant.LongLong)) fields_point.append(QgsField("COUNT_OUT", QVariant.LongLong)) (self.sink_point, self.dest_id_point) = self.parameterAsSink( parameters, self.OUTPUT_POINT, context, fields_point, source.wkbType(), source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey) if self.sink_point is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT_POINT)) #Compute the lines points = dict() features = source.getFeatures( QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): return {} if not f.hasGeometry(): continue point = f.geometry().asPoint() if group_field_index >= 0: group = f[group_field_index] else: group = 1 order = f[order_field_index] if date_format != '': if isinstance(order, QDateTime): pass else: try: order = datetime.strptime(str(order), date_format) except ValueError as ve: feedback.reportError( self.tr('Invalid format {}').format(ve)) return {} data = [order, point] for indice in self.field_indices: data.append(f[indice]) if group in points: points[group].append(data) else: points[group] = [data] feedback.setProgress(int(current * total)) feedback.setCurrentStep(1) #Create the features current = 0 total = 100.0 / len(points) if points else 1 edge_ids = {} end_points = {} self.point_id = 0 for group, vertices in points.items(): if feedback.isCanceled(): return {} feedback.setProgress(int(current * total)) # log("attrs: {}".format(vertices)) vertices.sort(key=lambda x: (x[0] is None, x[0])) for i in range(len(vertices) - 1): if feedback.isCanceled(): return {} start_id = vertices[i][1].asWkt() end_id = vertices[i + 1][1].asWkt() edge_id = start_id + "-" + end_id if edge_id in edge_ids: f = edge_ids[edge_id] count_index = 2 * (len(self.field_indices) + 1) count = f.attribute(count_index) + 1 f.setAttribute(count_index, count) else: f = QgsFeature() attrs = [] attrs.append(vertices[i][0]) #Add Order begin for j in range(len( self.field_indices)): #Add Attrs for begin attrs.append( vertices[i][j + 2]) # +2 ignore order and point attrs.append(vertices[i + 1][0]) #Add Order end for j in range(0, len( self.field_indices)): #Add Attrs for end attrs.append( vertices[i + 1][j + 2]) # +2 ignore order and point attrs.append(1) #Count = 1 f.setAttributes(attrs) line = [vertices[i][1], vertices[i + 1][1]] geom = QgsGeometry() f.setGeometry(QgsGeometry(geom.fromPolylineXY(line))) edge_ids[edge_id] = f self.updatePoints(start_id, end_points, vertices[i], 'start') self.updatePoints(end_id, end_points, vertices[i + 1], 'end') current += 1 feedback.setProgress(int(current * total)) for id in edge_ids: if feedback.isCanceled(): return {} self.sink.addFeature(edge_ids[id], QgsFeatureSink.FastInsert) for id in end_points: if feedback.isCanceled(): return {} self.sink_point.addFeature(end_points[id], QgsFeatureSink.FastInsert) return {self.OUTPUT_LINE: self.dest_id}
def processAlgorithm(self, parameters, context, model_feedback): feedback = QgsProcessingMultiStepFeedback(2, model_feedback) source = self.parameterAsSource(parameters, self.INPUT, context) field_segment_id = self.parameterAsString(parameters, self.FIELD_SEGMENT_ID, context) server_name = self.connection_options[self.parameterAsInt( parameters, self.SERVER_NAME, context)] graph_name = self.parameterAsString(parameters, self.GRAPH_NAME, context) graph_version = self.parameterAsString(parameters, self.GRAPH_VERSION, context) # Connect to Graphium feedback.pushInfo("Connect to Graphium server '" + server_name + "' ...") graphium = GraphiumGraphDataApi(feedback) selected_connection = self.connection_manager.select_graphium_server( server_name) if selected_connection is None: feedback.reportError('Cannot select connection to Graphium', True) return {self.OUTPUT_SEGMENTS: None} if graphium.connect(selected_connection) is False: feedback.reportError('Cannot connect to Graphium', True) return {self.OUTPUT_SEGMENTS: None} feedback.pushInfo("Start downloading task on Graphium server '" + server_name + "' ...") total = 100.0 / source.featureCount() if source.featureCount() else 0 segments_with_geometry = 0 # Read segment IDs segment_ids = [] segment_geometries = dict() for current, feature in enumerate(source.getFeatures()): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break segment_ids.append(feature[field_segment_id]) if len(segment_ids) > 50: self.get_segment_geometries(feedback, graphium, graph_name, graph_version, segment_ids, segment_geometries) # Update the progress bar feedback.setProgress(int(current * total)) if len(segment_ids) > 0: self.get_segment_geometries(feedback, graphium, graph_name, graph_version, segment_ids, segment_geometries) feedback.setCurrentStep(1) feedback.pushInfo("Add geometries to segments") (sink, dest_id) = self.parameterAsSink( parameters, self.OUTPUT_SEGMENTS, context, source.fields(), QgsWkbTypes.LineString, QgsCoordinateReferenceSystem('EPSG:4326')) for current, feature in enumerate(source.getFeatures()): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break if int(feature[field_segment_id]) in segment_geometries: feature.setGeometry(segment_geometries[int( feature[field_segment_id])]) segments_with_geometry += 1 else: pass # feedback.pushInfo("No geometry for segment " + str(feature[field_segment_id])) sink.addFeature(feature, QgsFeatureSink.FastInsert) # Update the progress bar feedback.setProgress(int(current * total)) feedback.setProgress(100) return { self.OUTPUT_SEGMENTS: dest_id, self.OUTPUT_SEGMENT_COUNT: source.featureCount() if source.featureCount() else 0, self.OUTPUT_SEGMENT_WITH_GEOMETRY_COUNT: segments_with_geometry }
def processAlgorithm(self, parameters, context, model_feedback): # variables propres à Processing feedback = QgsProcessingMultiStepFeedback(2, model_feedback) results = {} # entrées ponts = self.parameterAsVectorLayer(parameters, 'ponts', context) berges = self.parameterAsVectorLayer(parameters, 'berges', context) # sorties lines = [] output = self.parameterAsOutputLayer(parameters, 'OUTPUT', context) # paramètres extension = parameters['extension'] distance = parameters['distance'] # traitement # on étend le pont original d'une valeur définie pour assurer son itnersection avec les berges # on récupère les deux points d'intersection et on les décale en amont et en aval de X m sur la ligne de berge # puis on reconstruit la ligne à partir des deux points décalés pont = 0 for pont_f in ponts.getFeatures(): pont_g = pont_f.geometry().extendLine( extension, extension ) # extension du pont pour assurer son intersection avec les berges l0 = [] # liste qui contiendra les deux points du profil amont l1 = [] # liste qui contiendra les deux points du pont l2 = [] # liste qui contiendra les deux points du profil aval bcount = 0 for berge_f in berges.getFeatures( ): # pour chaque pont on traite une berge, puis l'autre berge_g = berge_f.geometry() if berge_g.intersects( pont_g ): # on vérifie l'intersection, si absence d'intersection on renvoie une erreur et on stoppe l'algorithme point_g = berge_g.intersection(pont_g) line_distance = berge_g.lineLocatePoint( point_g ) # on détermine à quelle distance on se situe sur la ligne de berge pour permettre le décalage p1, p2 = berge_g.interpolate( line_distance + distance), berge_g.interpolate( line_distance - distance ) # on décale le point original en amont et en aval l1.append(point_g.asPoint()) if (bcount == 1 and (QgsGeometry.fromPolylineXY([ l0[0], p1.asPoint() ]).length() > QgsGeometry.fromPolylineXY( [l0[0], p2.asPoint()]).length())): l0.append(p2.asPoint()) l2.append(p1.asPoint()) else: l0.append(p1.asPoint()) l2.append(p2.asPoint()) bcount += 1 if bcount != 2: feedback.reportError( "Le pont %s n'intersecte pas avec les berges !" % pont_f.id(), True) return {} lines.append((QgsGeometry.fromPolylineXY(l0), [0, pont, 'Profil'])) lines.append((QgsGeometry.fromPolylineXY(l1), [1, pont, 'Pont'])) lines.append((QgsGeometry.fromPolylineXY(l2), [2, pont, 'Profil'])) pont += 1 feedback.setCurrentStep(1) if feedback.isCanceled(): return {} # écriture des données en sortie fields = QgsFields() fields.append(QgsField("id", QVariant.Int)) fields.append(QgsField("pont", QVariant.Int)) fields.append(QgsField("type", QVariant.String)) writer = QgsVectorFileWriter(output, "System", fields, QgsWkbTypes.LineString, QgsCoordinateReferenceSystem(2154), "ESRI Shapefile") for line, attributes in lines: f = QgsFeature() f.setGeometry(line) f.setAttributes(attributes) writer.addFeature(f) feedback.setCurrentStep(2) results['OUTPUT'] = output return results
def processAlgorithm(self, parameters, context, model_feedback): """ Process algorithm. """ feedback = QgsProcessingMultiStepFeedback(13, model_feedback) results = {} outputs = {} project = QgsProject.instance() feedback.setCurrentStep(1) if feedback.isCanceled(): return {} # Points: # origin: user origin point in proj crs # wgs84_origin: origin point in wgs84 crs, used for choosing utm zone # utm_origin: origin point in utm crs # fire_origin: user fire point in proj crs # wgs84_fire_origin: fire point in wgs84 crs # utm_fire_origin: fire 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 terrain extent in any crs # mesh_extent: extent to utm crs, used for FDS MESH # as it is always contained in the terrain # dem_extent: mesh_extent to dem crs, used for grid # alignment with dem raster data # tex_extent: dem_extent to utm crs, used for texture, # that should be oriented as utm and perfectly # correspond to dem # 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) # FIXME crs? 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()) feedback.pushInfo(f"Project CRS: <{project_crs.description()}>") if not project_crs.isValid(): raise QgsProcessingException( "Project CRS is not usable, cannot proceed.") wgs84_crs = QgsCoordinateReferenceSystem("EPSG:4326") dem_crs = dem_layer.crs() feedback.pushInfo(f"DEM layer CRS: <{dem_crs.description()}>") if not dem_crs.isValid(): raise QgsProcessingException( "DEM layer CRS is not usable, cannot proceed.") if landuse_layer: landuse_crs = landuse_layer.crs() feedback.pushInfo( f"Landuse layer CRS: <{landuse_crs.description()}>") if not landuse_crs.isValid(): raise QgsProcessingException( "Landuse layer CRS is not usable, cannot proceed.") if tex_layer: tex_crs = tex_layer.crs() feedback.pushInfo(f"Texture layer CRS: <{tex_crs.description()}>") if not tex_crs.isValid(): raise QgsProcessingException( "Texture layer CRS is not usable, cannot proceed.") # Get extent in WGS84 CRS wgs84_extent = self.parameterAsExtent(parameters, "extent", context, crs=wgs84_crs) # Get origin in WGS84 CRS project_to_wgs84_tr = QgsCoordinateTransform(project_crs, wgs84_crs, QgsProject.instance()) if parameters["origin"] is not None: wgs84_origin = self.parameterAsPoint(parameters, "origin", context, crs=wgs84_crs) feedback.pushInfo("Using user origin.") project.writeEntry("qgis2fds", "origin", parameters["origin"]) else: # no origin wgs84_origin = wgs84_extent.center() feedback.pushInfo(f"Using terrain extent center as origin.") # Get fire origin in WGS84 CRS if parameters["fire_origin"] is not None: wgs84_fire_origin = self.parameterAsPoint(parameters, "fire_origin", context, crs=wgs84_crs) feedback.pushInfo("Using user fire origin.") else: wgs84_fire_origin = QgsPoint(wgs84_origin.x(), wgs84_origin.y()) feedback.pushInfo("Using origin as fire origin.") project.writeEntry("qgis2fds", "fire_origin", parameters["fire_origin"]) # 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.pushInfo(f"Using UTM CRS: <{utm_crs.description()}>") # Get origin in UTM CRS wgs84_to_utm_tr = QgsCoordinateTransform(wgs84_crs, utm_crs, QgsProject.instance()) utm_origin = QgsPoint(wgs84_origin.x(), wgs84_origin.y()) 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." ) # Get fire origin in UTM CRS utm_fire_origin = QgsPoint(wgs84_fire_origin.x(), wgs84_fire_origin.y()) utm_fire_origin.transform(wgs84_to_utm_tr) # Get FDS MESH extent in UTM CRS, then obtain dem extent in DEM CRS mesh_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(mesh_extent) # Check DEM contains dem_extent if not dem_layer.extent().contains(dem_extent): feedback.reportError( "Terrain extent is larger than available DEM data.") feedback.setCurrentStep(2) if feedback.isCanceled(): return {} # QGIS geographic transformations # Creating sampling grid in DEM crs feedback.pushInfo("Creating sampling grid layer from DEM...") xspacing = dem_layer.rasterUnitsPerPixelX() yspacing = dem_layer.rasterUnitsPerPixelY() x0, y0, x1, y1 = ( # terrain extent in DEM CRS dem_extent.xMinimum(), dem_extent.yMinimum(), dem_extent.xMaximum(), dem_extent.yMaximum(), ) xd0, yd1 = ( # DEM extent in DEM CRS dem_layer.extent().xMinimum(), dem_layer.extent().yMaximum(), ) # align terrain extent to DEM grid (gridding starts from top left corner) x0 = xd0 + round((x0 - xd0) / xspacing) * xspacing + xspacing / 2.0 y1 = yd1 + round((y1 - yd1) / yspacing) * yspacing - yspacing / 2.0 dem_extent = QgsRectangle(x0, y0, x1, y1) # terrain extent in DEM CRS npoints = int( (x1 - x0) / xspacing * (y1 - y0) / yspacing / dem_sampling**2) if npoints < 9: raise QgsProcessingException( f"Too few sampling points, cannot proceed. (npoints: {npoints})" ) feedback.pushInfo( f"Sampling points: {npoints} (xspacing: {xspacing}, yspacing: {yspacing})" ) alg_params = { "CRS": dem_crs, "EXTENT": dem_extent, "HOVERLAY": 0, "HSPACING": xspacing * dem_sampling, # reduce sampling, if requested "TYPE": 0, # Points "VOVERLAY": 0, "VSPACING": yspacing * dem_sampling, # reduce sampling, if requested "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateGrid"] = processing.run( "native:creategrid", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(3) if feedback.isCanceled(): return {} # Save texture dem_to_utm_tr = QgsCoordinateTransform(dem_crs, utm_crs, QgsProject.instance()) tex_extent = dem_to_utm_tr.transformBoundingBox(dem_extent) utils.write_image( feedback=feedback, tex_layer=tex_layer, tex_pixel_size=tex_pixel_size, # pixel size in meters destination_crs= utm_crs, # using UTM crs, texture aligned to axis in Smokeview destination_extent=tex_extent, filepath=f"{path}/{chid}_tex.png", imagetype="png", ) feedback.setCurrentStep(4) if feedback.isCanceled(): return {} # QGIS geographic transformations # Draping Z values to sampling grid in DEM crs feedback.pushInfo("Draping Z values from DEM...") 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, ) feedback.setCurrentStep(5) if feedback.isCanceled(): return {} # QGIS geographic transformations # Reprojecting sampling grid to UTM CRS feedback.pushInfo("Reprojecting sampling grid layer to UTM CRS...") alg_params = { "INPUT": outputs["DrapeSetZValueFromRaster"]["OUTPUT"], "TARGET_CRS": utm_crs, "OUTPUT": landuse_layer and QgsProcessing.TEMPORARY_OUTPUT or parameters["sampling_layer"], } outputs["ReprojectLayer"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(6) if feedback.isCanceled(): return {} # QGIS geographic transformations # Sampling landuse layer with sampling grid in UTM CRS if landuse_layer: feedback.pushInfo("Sampling landuse...") alg_params = { "COLUMN_PREFIX": "landuse", "INPUT": outputs["ReprojectLayer"]["OUTPUT"], "RASTERCOPY": parameters["landuse_layer"], "OUTPUT": parameters["sampling_layer"], } outputs["sampling_layer"] = processing.run( "qgis:rastersampling", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) results["sampling_layer"] = outputs["sampling_layer"]["OUTPUT"] point_layer = context.getMapLayer(results["sampling_layer"]) else: feedback.pushInfo("No landuse layer provided, no sampling.") results["sampling_layer"] = outputs["ReprojectLayer"]["OUTPUT"] point_layer = context.getMapLayer(results["sampling_layer"]) # add fake landuse point_layer.dataProvider().addAttributes( (QgsField("landuse", QVariant.Int), )) point_layer.updateFields() feedback.setCurrentStep(7) if feedback.isCanceled(): return {} # Prepare geometry verts, faces, landuses = geometry.get_geometry( feedback=feedback, layer=point_layer, utm_origin=utm_origin, ) feedback.setCurrentStep(12) if feedback.isCanceled(): return {} # Write 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, mesh_extent=mesh_extent, ) return results