2014-03-26

Create World Files to Arrange Images in Tiles

(FME 2014 build 14235)

Imagine that there are hundreds of JPEG files stored in a specific folder.
The goal is to create world files (*.wld) so that the images will be arranged in tile shapes when displaying them on a map viewer (e.g. ArcMap).
Width and height of a tile, and number of columns are specified as constants. Aspect ratio of every image is approximately equal to the ratio of a tile, but their sizes are various.

Required result (simplified example: number of images = 9, number of columns = 3)










A world file is a plain text file in which these 6 parameter values have been written.
Line 1: pixel size in the x-direction in map units/pixel
Line 2: rotation about y-axis
Line 3: rotation about x-axis
Line 4: pixel size in the y-direction in map units, almost always negative
Line 5: x-coordinate of the center of the upper left pixel
Line 6: y-coordinate of the center of the upper left pixel
-- World file, Wikipedia

Line 2 and 3 can be always 0 since it's not necessary to rotate images in this case. I have to calculate other values (Line 1, 4, 5, 6), and write the lines into text files for every image.
Except extension, path of a world file has to be same as associated JPEG file.

OK. The requirement and conditions have been clarified. This image shows a workspace example.









Firstly I defined these user parameters for the constants.
TILE_WIDTH (Float): horizontal lenghth of a tile
TILE_HEIGHT (Float): vertical length of a tile
TILE_COLUMNS (Integer): number of columns of tiles

The JPEG reader reads every image, the Counter appends 0-based sequential number (_count) to each raster feature, and the RasterPropertiesExtractor extracts raster properties.
"_count" and these two properties will be used in the next step.
_num_columns: width of the image (pixels)
_num_rows: height of the image (pixels)

The AttributeCreator calculates values which should be written into a world file. Here I assigned the values to elements of a list attribute named "text_line_data{}".

The AttributeKeeper removes unnecessary attributes, and the GeometryRemover removes the geometry (raster). These transformers are not essential, but removing unnecessary objects is effective to reduce memory usage especially when exploding the feature.

The ListExploder explodes the feature to create features each of which has a text line.
Finally, the TEXTLINE writer with fanout option writes the text lines into each world file for every image. This image shows the fanout setting.














All the transformers and the TEXTLINE writer can be replaced with a PythonCaller.
-----
# Python Script Example: Create World Files to Arrange Images in Tiles
import fmeobjects

class WorldFileCreator(object):
    def __init__(self):
        # Constants (user parameter values)
        tileWidth = float(FME_MacroValues['TILE_WIDTH'])
        tileHeight = float(FME_MacroValues['TILE_HEIGHT'])
        tileColumns = int(FME_MacroValues['TILE_COLUMNS'])
     
        # Functions for calculating parameter values which will be written into a world file.
        # Every function receives same arguments, and returns a parameter value.
        # w: image width (pixels), h: image height (pixels)
        # List of lambda function objects can be used effectively in this case.
        self.count = 0 # Counter
        self.func = [lambda w, h: tileWidth / w,
                     lambda w, h: 0.0,
                     lambda w, h: 0.0,
                     lambda w, h: -tileHeight / h,
                     lambda w, h: tileWidth * (self.count % tileColumns + 0.5 / w),
                     lambda w, h: -tileHeight * (self.count / tileColumns + 0.5 / h)]
     
    def input(self,feature):
        # Ignore other than raster geometry.
        if feature.getAttribute('fme_type') != 'fme_raster':
            return
         
        # Create output world file path based on the JPEG file path.
        path = feature.getAttribute('fme_dataset')
        if path[-4:].lower() == '.jpg':
            path = '%s.wld' % path[:-4]
        elif path[-5:].lower() == '.jpeg':
            path = '%s.wld' % path[:-5]
        else:
            return
         
        # Call FME Function "@RasterProperties(RASTER)".
        # This function brings the same result as the RasterPropertiesExtractor.
        feature.performFunction('@RasterProperties(RASTER)')
        w = float(feature.getAttribute('_num_columns')) # image width (pixels)
        h = float(feature.getAttribute('_num_rows')) # image height (pixels)
     
        # Write a world file (6 lines), then increment the counter.
        wld = open(path, 'w')
        wld.writelines(['%.8f\n' % f(w, h) for f in self.func])
        wld.close()
        self.count += 1
         
    def close(self):
        pass
-----

No comments:

Post a Comment