Getting started =============== Get acquainted with the FME Objects Python API and learn how to accomplish some common tasks. PythonCreator transformer ------------------------- The PythonCreator transformer can be used to create new features in the workspace. It contains a Python code template that looks similar to this:: import fmeobjects class FeatureCreator(object): def __init__(self): pass # create features before first reader feature is processed def input(self, feature): newFeature = fmeobjects.FMEFeature() self.pyoutput(newFeature) # output features in close() to create the features # after all reader features have been processed def close(self): pass The first line, ``import fmeobjects``, imports the FME Objects Python API. In ``FeatureCreator.input()``, an :class:`~fmeobjects.FMEFeature` is constructed, then returned using ``self.pyoutput()``. .. note:: See also: `PythonCreator transformer documentation`_. .. _PythonCreator transformer documentation: https://docs.safe.com/fme/2019.0/html/FME_Desktop_Documentation/FME_Transformers/Transformers/pythoncreator.htm PythonCaller transformer ------------------------ The PythonCaller transformer can be used to manipulate existing features using Python. It contains a Python code template that looks similar to this:: import fmeobjects class FeatureProcessor(object): def __init__(self): pass def input(self, feature): self.pyoutput(feature) def close(self): pass This template is similar to the one in the PythonCreator transformer. The difference is that its ``input()`` method receives an :class:`~fmeobjects.FMEFeature` as an argument. Each feature entering the PythonCaller's input port is passed to the ``input()`` method in the PythonCaller, where it can be worked with. ``self.pyoutput()`` then emits the feature from the PythonCaller's output port. Though the PythonCaller is intended for manipulating existing features in a pipeline, it's also possible to use it to create new features, just like PythonCreator. ``self.pyoutput()`` can be called multiple times in order to emit more than one feature for each feature entering the input port. The same feature can also be emitted multiple times: each feature from the output port is a copy. .. note:: See also: `PythonCaller transformer documentation`_. .. _PythonCaller transformer documentation: https://docs.safe.com/fme/2019.0/html/FME_Desktop_Documentation/FME_Transformers/Transformers/pythoncaller.htm Using FME's Python REPL ----------------------- Aside from using the PythonCreator and PythonCaller transformers described above, another good way to experiment with the FME Objects Python API is the FME Python interpreter's interactive shell. To use it, open a terminal or command prompt in your FME install directory, and enter: .. code-block:: powershell > fme python Python 2.7.10 (default, May 23 2015, 09:44:00) [MSC v.1500 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import fmeobjects >>> This brings up an interactive Python shell with all neccessary paths and dependencies set up. Getting and setting the feature type ------------------------------------ When using a PythonCaller to work with features that originated from a reader, it may be useful to know the feature's feature type. :meth:`~fmeobjects.FMEFeature.getFeatureType` is used to obtain the feature type. :meth:`~fmeobjects.FMEFeature.setFeatureType` is used to set the feature type of a feature. Getting and setting attributes ------------------------------ Use :meth:`~fmeobjects.FMEFeature.setAttribute` to set all *non-null* attributes. Most attribute types are supported. To set a list attribute, pass a list as the value. To set null attribute values, see the next section. Below is an example of getting and setting various attributes:: >>> from fmeobjects import FMEFeature, FME_ATTR_REAL32 >>> feature = FMEFeature() >>> feature.setAttribute('beavers', 10) >>> feature.setAttribute('park_name', 'Rat Park') >>> feature.setAttribute('rangers', ['Alice', 'Bob']) >>> feature.getAttribute('beavers'), feature.getAttribute('park_name'), feature.getAttribute('rangers') 10, 'Rat Park', ['Alice', 'Bob'] Working with null or missing attributes --------------------------------------- :meth:`~fmeobjects.FMEFeature.getAttribute` cannot be used to determine whether an attribute is missing or has a null value. Similarly, :meth:`~fmeobjects.FMEFeature.setAttribute` cannot be used to set null values on attributes. Separate methods are dedicated to this task. To set null attribute values, use :meth:`~fmeobjects.FMEFeature.setAttributeNullWithType`, passing it the name of the attribute to set to null, and the attribute type it would have been if it weren't null. The attribute type information typically comes from any schema that your data is honouring, if any. .. note:: By convention, when setting a null value and the type is unimportant, :data:`~fmeobjects.FME_ATTR_STRING` is declared as the type. To remove an attribute, or in other words, to make an existing attribute be missing, use :meth:`~fmeobjects.FMEFeature.removeAttribute`. To determine whether an attribute is missing, has a null value, and its type, use :meth:`~fmeobjects.FMEFeature.getAttributeNullMissingAndType`. It returns a 3-element tuple. Below is an example of getting and setting null and missing attributes:: >>> from fmeobjects import FMEFeature, FME_ATTR_REAL32 >>> feature = FMEFeature() >>> feature.setAttributeNullWithType('park_area', FME_ATTR_REAL32) >>> feature.getAttributeNullMissingAndType('park_area') True, False, 8 >>> feature.setAttribute('raccoons', 64) >>> feature.removeAttribute('raccoons') >>> feature.getAttributeNullMissingAndType('raccoons') False, True, 0 >>> feature.getAttributeNullMissingAndType('nonexistent') False, True, 0 Working with geometry --------------------- The FME Objects Python API has many :ref:`geometry classes `. The following is a simple example that constructs a 2D point and an offset 2D point, offsets the point by the offset point, fetches the new coordinates, and sets the new point to a feature in two different ways:: >>> from fmeobjects import FMEFeature, FMEPoint, FMEGeometryTools >>> feature = FMEFeature() >>> point = FMEPoint(10, 20) >>> offset = FMEPoint(10, 10) >>> point.offset(offset) >>> point.getXYZ() (20.0, 30.0, 0.0) >>> feature.setGeometry(point) >>> feature.getGeometry().getXYZ() (20.0, 30.0, 0.0) >>> offsetPoint = FMEGeomtryTools().offset(point, offset) >>> offsetPoint.getXYZ() (30.0, 40.0, 0.0) >>> feature.setGeometry(offsetPoint) >>> feature.getGeometry().getXYZ() (30.0, 40.0, 0.0) Many geometric operations are available through :class:`~fmeobjects.FMEGeometryTools`. Consult their API for details. Working with rasters --------------------- The FME Objects Python API has many :ref:`raster classes `. The following is an example of a PythonCreator transformer that creates new raster data, specifies the parameters of the raster and populates it when requested:: import fmeobjects class MyBandTilePopulator(fmeobjects.FMEBandTilePopulator): """ This is a subclass of the FMEBandTilePopulator superclass. It will be used when data is requested to create a new tile and and populate it to a new FMEBand. """ def __init__(self, rasterData): self._rasterData = rasterData # required method def clone(self): """ This method is used to create a copy of the data multiple times while creating a new band """ return MyBandTilePopulator(self._rasterData) # required method def getTile(self, startRow, startCol, tile): """ Creates a new tile that's sized based on the input tile. Populates that tile using this populator's raster data beginning at the startRow and startCol. """ numRows, numCols = tile.getNumRows(), tile.getNumCols() newTile = fmeobjects.FMEUInt8Tile(numRows, numCols) data = newTile.getData() for row in range(startRow, startRow+numRows): for col in range(startCol, startCol+numCols): if row < len(self._rasterData) and col < len(self._rasterData[0]): data[row-startRow][col-startCol] = self._rasterData[row][col] newTile.setData(data) return newTile class FeatureCreator(object): def __init__(self): pass def input(self, feature): pass def close(self): # creating the raster data and specifying the formatting of the new raster rasterData = [ [0, 128, 0, 128, 0, 128, 0], [128, 0, 128, 0, 128, 0, 128], [0, 128, 0, 128, 0, 128, 0], [128, 0, 128, 0, 128, 0, 128], [0, 128, 0, 128, 0, 128, 0] ] # specifying all of the properties for the new FMERaster numRows, numCols = len(rasterData), len(rasterData[0]) xCellOrigin, yCellOrigin = 0.5, 0.5 xSpacing, ySpacing = 1.0, 1.0 xOrigin, yOrigin = 0, 0 xRotation, yRotation = 0.0, 0.0 # creating the new FMERaster rasterProperties = fmeobjects.FMERasterProperties(numRows, numCols, xSpacing, ySpacing, xCellOrigin, yCellOrigin, xOrigin, yOrigin, xRotation, yRotation) raster = fmeobjects.FMERaster(rasterProperties) # Populating the contents of the band and appending it to the raster bandTilePopulator = MyBandTilePopulator(rasterData) bandName = '' bandProperties = fmeobjects.FMEBandProperties(bandName, fmeobjects.FME_INTERPRETATION_UINT8, fmeobjects.FME_TILE_TYPE_FIXED, numRows, numCols) band = fmeobjects.FMEBand(bandTilePopulator, rasterProperties, bandProperties) raster.appendBand(band) # creating a new feature with the FMERaster geometry to be output feature = fmeobjects.FMEFeature() feature.setGeometry(raster) self.pyoutput(feature) Coordinate systems and reprojection ----------------------------------- Coordinate systems are set at the feature level instead of the geometry level. By default, features do not have a coordinate system. To assign one, pass a coordinate system name to :meth:`~fmeobjects.FMEFeature.setCoordSys`:: >>> from fmeobjects import FMEFeature, FMEPoint >>> feature = FMEFeature() >>> feature.setGeometry(FMEPoint(-122.842764, 49.177847)) >>> feature.setCoordSys('LL84') When a feature has a coordinate system, it can be reprojected using :meth:`~fmeobjects.FMEFeature.reproject`. This operation is similar to the Reprojector transformer. For example:: >>> feature.reproject('SPHERICAL_MERCATOR') >>> feature.getCoordSys() 'SPHERICAL_MERCATOR' >>> feature.getGeometry().getXYZ() (-13674793.936118279, 6305092.363454558, 0.0) The :class:`~fmeobjects.FMECoordSysManager` and :class:`~fmeobjects.FMEReprojector` classes are available for more advanced operations with coordinate systems and reprojection. Writing messages to the FME log ------------------------------- Python's :func:`print` function can be used to log messages, but for best results in FME, use :class:`~fmeobjects.FMELogFile`:: >>> from fmeobjects import FMELogFile >>> FMELogFile().logMessageString("hello world") If this code executed in Workbench, this immediately prints "hello world" as an informational message in the log. The message severity can be set using the second parameter of :meth:`~fmeobjects.FMELogFile.logMessageString`. Typical values are: * :data:`~fmeobjects.FME_INFORM` (default): Informational message. * :data:`~fmeobjects.FME_WARN`: Warning that appears in blue. * :data:`~fmeobjects.FME_ERROR`: Error message that appears in red. * :data:`~fmeobjects.FME_DEBUG`: Message only appears if debug messages are enabled in FME Options. :class:`~fmeobjects.FMEFeature` instances can also be logged using :meth:`~fmeobjects.FMELogFile.logFeature`. The result is similar to using the Logger transformer. This method also supports severity levels.