def vectorLayerRender(self, layer, outName): img = QImage(QSize(1600, 900), QImage.Format_ARGB32_Premultiplied) color = QColor(255, 255, 255) img.fill(color.rgb()) p = QPainter() p.begin(img) p.setRenderHint(QPainter.Antialiasing) render = QgsMapRenderer() lst = [layer.id()] # add ID of every layer render.setLayerSet(lst) # set extent rect = QgsRectangle(render.fullExtent()) rect.scale(1.1) render.setExtent(rect) # set output size render.setOutputSize(img.size(), img.logicalDpiX()) # do the rendering render.render(p) p.end() # save image path = os.path.join(self.workingFolder, outName + '.tiff') img.save(path, "tiff") return path
def preview(request, layer_slug): """Home page for layers. :param request: The web request. :param layer_slug: The layer """ layer = get_object_or_404(Layer, slug=layer_slug) layer_path = os.path.join( settings.MEDIA_ROOT, 'layers', layer.slug, 'raw') map_layer = QgsVectorLayer(layer_path, layer.name, 'ogr') QgsMapLayerRegistry.instance().addMapLayer(map_layer) layer_uri = tempfile.NamedTemporaryFile( suffix='.png', prefix='inasafe-web-', dir='/tmp/').name # create image image = QImage(QSize(100, 100), QImage.Format_ARGB32_Premultiplied) # set image's background color color = QColor(255, 255, 255) image.fill(color.rgb()) # create painter p = QPainter() p.begin(image) p.setRenderHint(QPainter.Antialiasing) renderer = QgsMapRenderer() # set layer set layers = [map_layer.id()] # add ID of every layer renderer.setLayerSet(layers) # set extent rect = QgsRectangle(renderer.fullExtent()) rect.scale(1.1) renderer.setExtent(rect) # set output size renderer.setOutputSize(image.size(), image.logicalDpiX()) # do the rendering renderer.render(p) p.end() # clean up registry_list = qgis_layers() QgsMapLayerRegistry.instance().removeMapLayer(map_layer.id()) print registry_list # save image image.save(layer_uri, 'png') with open(layer_uri, 'rb') as f: response = HttpResponse(f.read(), content_type='png') os.remove(layer_uri) return response
def save_layer_as_image(layer, extent, path_to_file, max_resolution='1024', image_type = 'tif'): """ Select and save the currently visible extent to a .tif file :param width: image width :type width: int :param height: image height :type height: int :param name: name of the created file :type name: str :return: :rtype: """ # calculate the extents width and height width = extent.width() height = extent.height() # calculate the missing value (width or height) of the output file, based on the extent if width >= height: height_as_dec = max_resolution / width * height width = max_resolution height = int(height_as_dec) else: width_as_dec = max_resolution / height * width width = int(width_as_dec) height = max_resolution # append the resolution to the filename and call the save method filename=layer.name() if filename.startswith("WMS_"): filename=filename.replace("WMS_","") else: resolution_prefix = '{}_{}-'.format(width, height) filename = resolution_prefix + layer.name() img = QImage(QSize(width, height), QImage.Format_ARGB32_Premultiplied) color = QColor(187, 187, 187, 0) img.fill(color.rgba()) leonardo = QPainter() leonardo.begin(img) leonardo.setRenderHint(QPainter.Antialiasing) renderer = QgsMapRenderer() lst = [layer.id()] renderer.setLayerSet(lst) renderer.setExtent(extent) renderer.setOutputSize(img.size(), img.logicalDpiX()) renderer.render(leonardo) leonardo.end() filename += '.{}'.format(image_type) out_path = os.path.join(path_to_file, filename) if img.save(out_path, image_type): return out_path
def printToPdf(self, params): self.check_required_params(params) with change_directory(self.project_root): crs = QgsCoordinateReferenceSystem() crs.createFromSrid(params.get('srs')) mapRenderer = QgsMapRenderer() mapUnits = crs.mapUnits() mapRenderer.setMapUnits(mapUnits) mapExtent = QgsRectangle(*params.get('bbox')) mapRenderer.setExtent(mapExtent) le = QgsPalLabeling() mapRenderer.setLabelingEngine(le) layers = params.get('layers') self.setTransparencies(layers, params.get('transparencies')) mapRenderer.setLayerSet(layers) composer = ( self.getLayoutbyName(params['layout']) .firstChildElement('Composition') ) comp = QgsComposition(mapRenderer) comp.setPlotStyle(QgsComposition.Print) comp.readXML(composer, self.doc) # read composition elements comp.addItemsFromXML(composer, self.doc) comp.setPrintResolution(90) # set bbox for the first Map in the layout comp_map = comp.getComposerMapById(0) comp_map.setNewExtent(QgsRectangle(*params.get('bbox'))) # comp_map.setNewScale(10000) # comp_map.setLayerSet(layers) # comp_map.setKeepLayerSet(True) # save the file comp.exportAsPDF(params['tmpFile'] + '.pdf')
def createimage(self, extent, crs, outputsize): render = QgsMapRenderer() render.setLayerSet(self.loadedlayers.values()) render.setProjectionsEnabled(True) render.setDestinationCrs(crs) render.setExtent(extent) img = QImage(outputsize, QImage.Format_ARGB32_Premultiplied) img.fill(Qt.transparent) #needed, apparently the QImage() is not empty painter = QPainter() painter.begin(img) painter.setRenderHint(QPainter.Antialiasing) render.setOutputSize(img.size(), img.logicalDpiX()) log("about to render") render.render(painter) log("just rendered") painter.end() return img
def renderLayers(size, layers, imageFileName): imgSize = QSize(size, size) # create image img = QImage(imgSize, QImage.Format_RGB32) # set image's background color color = QColor(255, 255, 255) img.fill(color.rgb()) # create painter p = QPainter() p.begin(img) p.setRenderHint(QPainter.Antialiasing) render = QgsMapRenderer() # set layer set render.setLayerSet(layers.keys()) # set extent rect = QgsRectangle(render.fullExtent()) render.setExtent(rect) # set output size render.setOutputSize(img.size(), img.logicalDpiX()) print "render()" # do the rendering render.render(p) p.end() print " ...Done" print "save(" + imageFileName + ")" # save image img.save(imageFileName) print " ...Done"
def testTransparency(self): myPath = os.path.join(unitTestDataPath('raster'), 'band1_float32_noct_epsg4326.tif') myFileInfo = QFileInfo(myPath) myBaseName = myFileInfo.baseName() myRasterLayer = QgsRasterLayer(myPath, myBaseName) myMessage = 'Raster not loaded: %s' % myPath assert myRasterLayer.isValid(), myMessage renderer = QgsSingleBandGrayRenderer(myRasterLayer.dataProvider(), 1) myRasterLayer.setRenderer(renderer) myRasterLayer.setContrastEnhancementAlgorithm( QgsContrastEnhancement.StretchToMinimumMaximum, QgsRasterLayer.ContrastEnhancementMinMax) myContrastEnhancement = myRasterLayer.renderer().contrastEnhancement() #print ("myContrastEnhancement.minimumValue = %.17g" % # myContrastEnhancement.minimumValue()) #print ("myContrastEnhancement.maximumValue = %.17g" % # myContrastEnhancement.maximumValue()) # Unfortunately the minimum/maximum values calculated in C++ and Python # are slightly different (e.g. 3.3999999521443642e+38 x # 3.3999999521444001e+38) # It is not clear where the precision is lost. # We set the same values as C++. myContrastEnhancement.setMinimumValue(-3.3319999287625854e+38) myContrastEnhancement.setMaximumValue(3.3999999521443642e+38) #myType = myRasterLayer.dataProvider().dataType(1); #myEnhancement = QgsContrastEnhancement(myType); myTransparentSingleValuePixelList = [] rasterTransparency = QgsRasterTransparency() myTransparentPixel1 = \ QgsRasterTransparency.TransparentSingleValuePixel() myTransparentPixel1.min = -2.5840000772112106e+38 myTransparentPixel1.max = -1.0879999684602689e+38 myTransparentPixel1.percentTransparent = 50 myTransparentSingleValuePixelList.append(myTransparentPixel1) myTransparentPixel2 = \ QgsRasterTransparency.TransparentSingleValuePixel() myTransparentPixel2.min = 1.359999960575336e+37 myTransparentPixel2.max = 9.520000231087593e+37 myTransparentPixel2.percentTransparent = 70 myTransparentSingleValuePixelList.append(myTransparentPixel2) rasterTransparency.setTransparentSingleValuePixelList( myTransparentSingleValuePixelList) rasterRenderer = myRasterLayer.renderer() assert rasterRenderer rasterRenderer.setRasterTransparency(rasterTransparency) QgsMapLayerRegistry.instance().addMapLayers([ myRasterLayer, ]) myMapRenderer = QgsMapRenderer() myLayers = QStringList() myLayers.append(myRasterLayer.id()) myMapRenderer.setLayerSet(myLayers) myMapRenderer.setExtent(myRasterLayer.extent()) myChecker = QgsRenderChecker() myChecker.setControlName("expected_raster_transparency") myChecker.setMapRenderer(myMapRenderer) myResultFlag = myChecker.runTest("raster_transparency_python"); assert myResultFlag, "Raster transparency rendering test failed"
def testTransparency(self): myPath = os.path.join(unitTestDataPath('raster'), 'band1_float32_noct_epsg4326.tif') myFileInfo = QFileInfo(myPath) myBaseName = myFileInfo.baseName() myRasterLayer = QgsRasterLayer(myPath, myBaseName) myMessage = 'Raster not loaded: %s' % myPath assert myRasterLayer.isValid(), myMessage renderer = QgsSingleBandGrayRenderer(myRasterLayer.dataProvider(), 1) myRasterLayer.setRenderer(renderer) myRasterLayer.setContrastEnhancement( QgsContrastEnhancement.StretchToMinimumMaximum, QgsRaster.ContrastEnhancementMinMax) myContrastEnhancement = myRasterLayer.renderer().contrastEnhancement() # print ("myContrastEnhancement.minimumValue = %.17g" % # myContrastEnhancement.minimumValue()) # print ("myContrastEnhancement.maximumValue = %.17g" % # myContrastEnhancement.maximumValue()) # Unfortunately the minimum/maximum values calculated in C++ and Python # are slightly different (e.g. 3.3999999521443642e+38 x # 3.3999999521444001e+38) # It is not clear where the precision is lost. # We set the same values as C++. myContrastEnhancement.setMinimumValue(-3.3319999287625854e+38) myContrastEnhancement.setMaximumValue(3.3999999521443642e+38) #myType = myRasterLayer.dataProvider().dataType(1); #myEnhancement = QgsContrastEnhancement(myType); myTransparentSingleValuePixelList = [] rasterTransparency = QgsRasterTransparency() myTransparentPixel1 = \ QgsRasterTransparency.TransparentSingleValuePixel() myTransparentPixel1.min = -2.5840000772112106e+38 myTransparentPixel1.max = -1.0879999684602689e+38 myTransparentPixel1.percentTransparent = 50 myTransparentSingleValuePixelList.append(myTransparentPixel1) myTransparentPixel2 = \ QgsRasterTransparency.TransparentSingleValuePixel() myTransparentPixel2.min = 1.359999960575336e+37 myTransparentPixel2.max = 9.520000231087593e+37 myTransparentPixel2.percentTransparent = 70 myTransparentSingleValuePixelList.append(myTransparentPixel2) rasterTransparency.setTransparentSingleValuePixelList( myTransparentSingleValuePixelList) rasterRenderer = myRasterLayer.renderer() assert rasterRenderer rasterRenderer.setRasterTransparency(rasterTransparency) QgsMapLayerRegistry.instance().addMapLayers([ myRasterLayer, ]) myMapRenderer = QgsMapRenderer() myLayers = [] myLayers.append(myRasterLayer.id()) myMapRenderer.setLayerSet(myLayers) myMapRenderer.setExtent(myRasterLayer.extent()) myChecker = QgsRenderChecker() myChecker.setControlName("expected_raster_transparency") myChecker.setMapRenderer(myMapRenderer) myResultFlag = myChecker.runTest("raster_transparency_python") assert myResultFlag, "Raster transparency rendering test failed"
def identify(self, params): self.check_required_params(params) feature_collections = [] with change_directory(self.project_root): crs = QgsCoordinateReferenceSystem() crs.createFromSrid(params.get('srs')) search_box = self._calcSearchBox( params.get('bbox'), params.get('image_size')[0], params.get('image_size')[1], params.get('click_point')[0], params.get('click_point')[1] ) # initialize mapRenderer and a rendering context in order to be # to check if a feature will actually be rendered # we don't want to return features that are not visible img = QImage(QSize( settings.SUNLUMO_GFI_BUFFER*2, settings.SUNLUMO_GFI_BUFFER*2), QImage.Format_ARGB32_Premultiplied ) dpm = 1 / 0.00028 img.setDotsPerMeterX(dpm) img.setDotsPerMeterY(dpm) mapRenderer = QgsMapRenderer() mapRenderer.clearLayerCoordinateTransforms() mapRenderer.setOutputSize(QSize( settings.SUNLUMO_GFI_BUFFER*2, settings.SUNLUMO_GFI_BUFFER*2), img.logicalDpiX() ) mapRenderer.setDestinationCrs(crs) mapRenderer.setProjectionsEnabled(True) mapUnits = crs.mapUnits() mapRenderer.setMapUnits(mapUnits) mapExtent = QgsRectangle(*search_box) mapRenderer.setExtent(mapExtent) renderContext = QgsRenderContext() renderContext.setExtent(mapRenderer.extent()) renderContext.setRasterScaleFactor(1.0) renderContext.setMapToPixel(mapRenderer.coordinateTransform()) renderContext.setRendererScale(mapRenderer.scale()) renderContext.setScaleFactor(mapRenderer.outputDpi() / 25.4) renderContext.setPainter(None) qfr = QgsFeatureRequest() search_rectangle = QgsRectangle(*search_box) qfr.setFilterRect(search_rectangle) for q_layer in params.get('query_layers'): layer = self.layerRegistry.mapLayer(q_layer) if layer.type() == QgsMapLayer.RasterLayer: # skip raster layer processing continue # update layer fields (expressions, calculated, joined) layer.updateFields() scaleCalc = QgsScaleCalculator( (img.logicalDpiX() + img.logicalDpiY()) / 2, mapRenderer.destinationCrs().mapUnits() ) scaleDenom = scaleCalc.calculate(mapExtent, img.width()) # skip the layer if it's not visible at the current map scale if layer.hasScaleBasedVisibility(): if not(layer.minimumScale() < scaleDenom < layer.maximumScale()): continue # check if features actually intersect search rectangle intersected_features = self._intersectedFeatures( layer.getFeatures(qfr), search_rectangle ) # visible features generator visible_features = self._visibleFeatures( layer, renderContext, intersected_features ) layer_features = [featureToGeoJSON( feature.id(), feature.geometry(), self._collectAttributes(layer, feature) ) for feature in visible_features ] feature_collections.append(layer_features) return writeGeoJSON(chain(*feature_collections))
def saveControlImage(self, tmpimg=''): # don't save control images for RenderVsOtherOutput (Vs) tests, since # those control images belong to a different test result if ('PAL_CONTROL_IMAGE' not in os.environ or 'Vs' in self._TestGroup): return imgpath = self.controlImagePath() # print "saveControlImage: {0}".format(imgpath) testdir = os.path.dirname(imgpath) if not os.path.exists(testdir): os.makedirs(testdir) imgbasepath = \ os.path.join(testdir, os.path.splitext(os.path.basename(imgpath))[0]) for f in glob.glob(imgbasepath + '.*'): if os.path.exists(f): os.remove(f) if tmpimg and os.path.exists(tmpimg): shutil.copyfile(tmpimg, imgpath) else: print '\nsaveControlImage.render(): entered' print '{0}.{1}'.format(self._TestGroup, self._TestFunction) ms = self._MapSettings # class settings """:type: QgsMapSettings""" if self._TestMapSettings is not None: ms = self._TestMapSettings # per test settings print 'self._MapSettings...' print 'ms.layers(): {0}'.format( [self._MapRegistry.mapLayer(i).name() for i in ms.layers()]) print 'ms.outputSize(): {0} x {1}'.format(ms.outputSize().width(), ms.outputSize().height()) print 'ms.outputDpi(): {0}'.format(ms.outputDpi()) print 'ms.mapUnits(): {0}'.format(ms.mapUnits()) print 'ms.extent(): {0}'.format(ms.extent().toString()) print 'ms.hasCrsTransformEnabled(): {0}'.format( ms.hasCrsTransformEnabled()) print 'ms.destinationCrs(): {0}'.format( ms.destinationCrs().authid()) # pal = QgsPalLabeling() pal = self._Pal.clone() # or custom settings are lost pal.init(ms) r = QgsMapRenderer() r.setLabelingEngine(pal) # this seems too redundant r.setOutputSize(ms.outputSize(), ms.outputDpi()) r.setMapUnits(ms.mapUnits()) r.setExtent(ms.extent()) r.setProjectionsEnabled(ms.hasCrsTransformEnabled()) r.setDestinationCrs(ms.destinationCrs()) r.setLayerSet(ms.layers()) ctx = r.rendererContext() ctx.setDrawEditingInformation( ms.testFlag(QgsMapSettings.DrawEditingInfo)) ctx.setForceVectorOutput( ms.testFlag(QgsMapSettings.ForceVectorOutput)) ctx.setUseAdvancedEffects( ms.testFlag(QgsMapSettings.UseAdvancedEffects)) image = QImage(ms.outputSize(), QImage.Format_ARGB32) image.fill(ms.backgroundColor().rgb()) image.setDotsPerMeterX(ms.outputDpi() / 25.4 * 1000) image.setDotsPerMeterY(ms.outputDpi() / 25.4 * 1000) p = QPainter(image) r.render(p) p.end() if not image.save(imgpath, 'png'): os.unlink(imgpath) # delete extraneous world file (always generated) # wrld_file = imgbasepath + '.PNGw' # if os.path.exists(wrld_file): # os.remove(wrld_file) if not os.path.exists(imgpath): raise OSError('Control image not created: {0}'.format(imgpath))
def updateDisplay(self): ''' Add map(s) of all QF components to the canvas based on what's selected in self.lstTimes''' timestamps = [ pd.datetime.strptime(newItem.text(), '%Y-%m-%d %H:%M') for newItem in self.lstTimes.selectedItems() ] for t in timestamps: outs = pd.read_csv(self.model.getFileList()[t], header=0, index_col=0) outLayer = self.outputLayer # Make sure the output file is properly appended (this gets evaluated for non-extra-disaggregated datasets) # because I didn't set an output shapefile path properly if os.path.split(self.outputLayer)[0] == '': outLayer = os.path.join(self.model.downscaledPath, os.path.split(self.outputLayer)[1]) fileToPopulate = self.outputLayer new_layer = populateShapefileFromTemplate( outs, self.featureIdField, outLayer, int(self.outputEPSG), title=t.strftime(' %Y-%m-%d %H:%M UTC')) # Set ranges suited to all the different QF types range_minima = [0, 0.000001, 0.1, 1, 10, 100] range_maxima = [0.000001, 0.1, 1, 10, 100, 1000] colours = [ '#CECECE', '#FEE6CE', '#FDAE6B', '#F16913', '#D94801', '#7F2704' ] opacity = 1 for component in self.componentTranslation.values(): layerName = component + t.strftime(' %Y-%m-%d %H:%M UTC') if component == self.componentTranslation.values()[0]: colourRanges(new_layer, component, opacity, range_minima, range_maxima, colours) new_layer.setLayerName(layerName) layerId = new_layer.id() QgsMapLayerRegistry.instance().addMapLayer(new_layer) proportion = new_layer.extent().height( ) / new_layer.extent().width() else: # Have to clone. Can't seem to duplicate a map layer... layer = duplicateVectorLayer(new_layer) layer.setLayerName(layerName) colourRanges(layer, component, opacity, range_minima, range_maxima, colours) layerId = layer.id() QgsMapLayerRegistry.instance().addMapLayer(layer) proportion = layer.extent().height() / layer.extent( ).width() maxSize = 2000 # Max size of output image if proportion > 1: hSize = maxSize / proportion vSize = maxSize else: hSize = maxSize vSize = maxSize * proportion # create image in proportion with layer img = QImage(QSize(hSize, vSize), QImage.Format_ARGB32_Premultiplied) # set image's background color color = QColor(255, 255, 255) img.fill(color.rgb()) # create painter p = QPainter() p.begin(img) p.setRenderHint(QPainter.Antialiasing) render = QgsMapRenderer() # set layer set lst = [layerId] # add ID of every layer render.setLayerSet(lst) # set extent rect = QgsRectangle(render.fullExtent()) rect.scale(1.1) render.setExtent(rect) # set output size render.setOutputSize(img.size(), img.logicalDpiX()) # do the rendering render.render(p) p.end() # save image img.save( os.path.join( self.model.renderPath, component + t.strftime('_%Y-%m-%d_%H-%M_UTC.png')), "png")
def saveControlImage(self, tmpimg=''): # don't save control images for RenderVsOtherOutput (Vs) tests, since # those control images belong to a different test result if ('PAL_CONTROL_IMAGE' not in os.environ or 'Vs' in self._TestGroup): return imgpath = self.controlImagePath() # print "saveControlImage: {0}".format(imgpath) testdir = os.path.dirname(imgpath) if not os.path.exists(testdir): os.makedirs(testdir) imgbasepath = \ os.path.join(testdir, os.path.splitext(os.path.basename(imgpath))[0]) for f in glob.glob(imgbasepath + '.*'): if os.path.exists(f): os.remove(f) if tmpimg and os.path.exists(tmpimg): shutil.copyfile(tmpimg, imgpath) else: print '\nsaveControlImage.render(): entered' print '{0}.{1}'.format(self._TestGroup, self._TestFunction) ms = self._MapSettings # class settings """:type: QgsMapSettings""" if self._TestMapSettings is not None: ms = self._TestMapSettings # per test settings print 'self._MapSettings...' print 'ms.layers(): {0}'.format( [self._MapRegistry.mapLayer(i).name() for i in ms.layers()] ) print 'ms.outputSize(): {0} x {1}'.format( ms.outputSize().width(), ms.outputSize().height()) print 'ms.outputDpi(): {0}'.format(ms.outputDpi()) print 'ms.mapUnits(): {0}'.format(ms.mapUnits()) print 'ms.extent(): {0}'.format(ms.extent().toString()) print 'ms.hasCrsTransformEnabled(): {0}'.format( ms.hasCrsTransformEnabled()) print 'ms.destinationCrs(): {0}'.format( ms.destinationCrs().authid()) # pal = QgsPalLabeling() pal = self._Pal.clone() # or custom settings are lost pal.init(ms) r = QgsMapRenderer() r.setLabelingEngine(pal) # this seems too redundant r.setOutputSize(ms.outputSize(), ms.outputDpi()) r.setMapUnits(ms.mapUnits()) r.setExtent(ms.extent()) r.setProjectionsEnabled(ms.hasCrsTransformEnabled()) r.setDestinationCrs(ms.destinationCrs()) r.setLayerSet(ms.layers()) ctx = r.rendererContext() ctx.setDrawEditingInformation( ms.testFlag(QgsMapSettings.DrawEditingInfo)) ctx.setForceVectorOutput( ms.testFlag(QgsMapSettings.ForceVectorOutput)) ctx.setUseAdvancedEffects( ms.testFlag(QgsMapSettings.UseAdvancedEffects)) image = QImage(ms.outputSize(), QImage.Format_ARGB32) image.fill(ms.backgroundColor().rgb()) image.setDotsPerMeterX(ms.outputDpi() / 25.4 * 1000) image.setDotsPerMeterY(ms.outputDpi() / 25.4 * 1000) p = QPainter(image) r.render(p) p.end() if not image.save(imgpath, 'png'): os.unlink(imgpath) # delete extraneous world file (always generated) # wrld_file = imgbasepath + '.PNGw' # if os.path.exists(wrld_file): # os.remove(wrld_file) if not os.path.exists(imgpath): raise OSError('Control image not created: {0}'.format(imgpath))
img = QImage(iwidth, iheight, QImage.Format_RGB32) img.setDotsPerMeterX(dpi / 25.4 * 1000) img.setDotsPerMeterY(dpi / 25.4 * 1000) img.fill(qRgb(255, 255, 255)) dpi = img.logicalDpiX() print("Image DPI: %d" % dpi) render.setOutputSize(QSize(img.width(), img.height()), dpi) render.setDestinationCrs(crs) render.setProjectionsEnabled(True) render.setMapUnits(crs.mapUnits()) render.setExtent(extent) print("Scale: %f" % render.scale()) painter = QPainter(img) painter.setRenderHint(QPainter.Antialiasing) render.render(painter) painter.end() ba = QByteArray() bf = QBuffer(ba) bf.open(QIODevice.WriteOnly) img.save(bf, 'PNG') bf.close() with open(args.output, 'wb') as fd: fd.write(ba)