Writing your first CWL CommandLineTool document
Goal
Create a CWL CommandLineTool that uses GDAL's gdal_translate to clip a GeoTIFF using a given bounding box expressed in a defined EPSG code.
CWL CommandLineTool skeleton
Use a text editor to create a new plain text file and:
- set the CWL class
CommandLineTool
class: CommandLineTool
- set the CWL version in the last line
class: CommandLineTool
cwlVersion: v1.0
- add the
CommandLineToolelements:baseCommand: the CLI utility to invokearguments: any arguments for the CLI utilityinputs: the input parameters to exposeoutputs: the outputs to collect after the execution of the CLI utility
class: CommandLineTool
baseCommand:
arguments:
inputs:
outputs:
cwlVersion: v1.0
- add the
requirementsblock to specify the CWL requirements used for running the CWL document
class: CommandLineTool
requirements:
baseCommand:
arguments:
inputs:
outputs:
cwlVersion: v1.0
baseCommand
It's now time to fill the elements with their values.
- add the
baseCommand:
class: CommandLineTool
requirements:
baseCommand: gdal_translate
arguments:
inputs:
outputs:
cwlVersion: v1.0
Set the container
- set the container where
gdal_translateis available:
class: CommandLineTool
requirements:
DockerRequirement:
dockerPull: docker.io/osgeo/gdal
baseCommand: gdal_translate
arguments:
inputs:
outputs:
cwlVersion: v1.0
Define the inputs
- set the inputs:
geotiffof typeFilewhich is the path to the GeoTIFF filebboxof typestringwhich is the bounding box expressed as"xmin,ymin,latmin,latmax"epsgof typestringwhich is the EPSG code of the bounding box coordinates
class: CommandLineTool
requirements:
DockerRequirement:
dockerPull: docker.io/osgeo/gdal
baseCommand: gdal_translate
arguments:
inputs:
geotiff:
type: File
bbox:
type: string
epsg:
type: string
default: "EPSG:4326"
outputs:
cwlVersion: v1.0
Set the arguments
gdal_translate uses -projwin ulx uly lrx lry to set the area of interest.
Since our bounding box is expressed as xmin,ymin,xmax,ymax, we'll have to manipulate the bbox value to format it as expected by gdal_translate (xmax,ymin,xmin,ymax).
This is easily achieved by using the InlineJavascriptRequirement CWL requirement and using Javascript to manipulate the bbox input:
class: CommandLineTool
requirements:
InlineJavascriptRequirement: {}
DockerRequirement:
dockerPull: docker.io/osgeo/gdal
baseCommand: gdal_translate
arguments:
inputs:
geotiff:
type: File
bbox:
type: string
epsg:
type: string
default: "EPSG:4326"
outputs:
cwlVersion: v1.0
and:
class: CommandLineTool
requirements:
InlineJavascriptRequirement: {}
DockerRequirement:
dockerPull: docker.io/osgeo/gdal
baseCommand: gdal_translate
arguments:
- -projwin
- valueFrom: ${ return inputs.bbox.split(",")[0]; }
- valueFrom: ${ return inputs.bbox.split(",")[3]; }
- valueFrom: ${ return inputs.bbox.split(",")[2]; }
- valueFrom: ${ return inputs.bbox.split(",")[1]; }
inputs:
geotiff:
type: File
bbox:
type: string
epsg:
type: string
default: "EPSG:4326"
outputs:
cwlVersion: v1.0
Now, we need to set gdal_translate flag -projwin_srs and use the input epsg value. This can be done by setting the inputBinding element:
class: CommandLineTool
requirements:
InlineJavascriptRequirement: {}
DockerRequirement:
dockerPull: docker.io/osgeo/gdal
baseCommand: gdal_translate
arguments:
- -projwin
- valueFrom: ${ return inputs.bbox.split(",")[0]; }
- valueFrom: ${ return inputs.bbox.split(",")[3]; }
- valueFrom: ${ return inputs.bbox.split(",")[2]; }
- valueFrom: ${ return inputs.bbox.split(",")[1]; }
inputs:
geotiff:
type: File
bbox:
type: string
epsg:
type: string
default: "EPSG:4326"
inputBinding:
position: 6
prefix: -projwin_srs
separate: true
outputs:
cwlVersion: v1.0
Link the inputs
The next step is to add the input file (the geotiff input). Again we use the inputBinding and set the position.
class: CommandLineTool
requirements:
InlineJavascriptRequirement: {}
DockerRequirement:
dockerPull: docker.io/osgeo/gdal
baseCommand: gdal_translate
arguments:
- -projwin
- valueFrom: ${ return inputs.bbox.split(",")[0]; }
- valueFrom: ${ return inputs.bbox.split(",")[3]; }
- valueFrom: ${ return inputs.bbox.split(",")[2]; }
- valueFrom: ${ return inputs.bbox.split(",")[1]; }
inputs:
geotiff:
type: File
inputBinding:
position: 7
bbox:
type: string
epsg:
type: string
default: "EPSG:4326"
inputBinding:
position: 6
prefix: -projwin_srs
separate: true
outputs:
cwlVersion: v1.0
Our last argument is the output file name. Since we haven't defined a parameter to set this value, we'll pass it in the arguments element and construct it based on the geotiff input parameter. We also have to tell where to put it.
class: CommandLineTool
requirements:
InlineJavascriptRequirement: {}
DockerRequirement:
dockerPull: docker.io/osgeo/gdal
baseCommand: gdal_translate
arguments:
- -projwin
- valueFrom: ${ return inputs.bbox.split(",")[0]; }
- valueFrom: ${ return inputs.bbox.split(",")[3]; }
- valueFrom: ${ return inputs.bbox.split(",")[2]; }
- valueFrom: ${ return inputs.bbox.split(",")[1]; }
- valueFrom: ${ return inputs.geotiff.basename.replace(".tif", "") + "_clipped.tif"; }
position: 8
inputs:
geotiff:
type: File
inputBinding:
position: 7
bbox:
type: string
epsg:
type: string
default: "EPSG:4326"
inputBinding:
position: 6
prefix: -projwin_srs
separate: true
outputs:
cwlVersion: v1.0
Set the output
The file element is the output section. We'll call the result clipped_tif and set a search pattern to collect the output.
class: CommandLineTool
requirements:
InlineJavascriptRequirement: {}
DockerRequirement:
dockerPull: docker.io/osgeo/gdal
baseCommand: gdal_translate
arguments:
- -projwin
- valueFrom: ${ return inputs.bbox.split(",")[0]; }
- valueFrom: ${ return inputs.bbox.split(",")[3]; }
- valueFrom: ${ return inputs.bbox.split(",")[2]; }
- valueFrom: ${ return inputs.bbox.split(",")[1]; }
- valueFrom: ${ return inputs.geotiff.basename.replace(".tif", "") + "_clipped.tif"; }
position: 8
inputs:
geotiff:
type: File
inputBinding:
position: 7
bbox:
type: string
epsg:
type: string
default: "EPSG:4326"
inputBinding:
position: 6
prefix: -projwin_srs
separate: true
outputs:
clipped_tif:
outputBinding:
glob: '*_clipped.tif'
type: File
cwlVersion: v1.0
Save it as gdal-translate.cwl
CWL document validation
Validate it using cwltool:
cwltool --validate gdal-translate.cwl
Test the CWL document
Now get the geotiff we'll use for testing our first CWL document:
curl -o B04.tif https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/53/H/PA/2021/7/S2B_53HPA_20210723_0_L2A/B04.tif
Create a file called params.yml with:
bbox: "136.659,-35.96,136.923,-35.791"
geotiff: {"class": "File", "path": "B04.tif" }
epsg: "EPSG:4326"
Use cwltool to run the CWL document:
cwltool gdal-translate.cwl params.yml
This will produce:
INFO /srv/conda/bin/cwltool 3.1.20210628163208
INFO Resolved 'gdal-translate.cwl' to 'file:///home/fbrito/work/guide/gdal-translate.cwl'
INFO [job gdal-translate.cwl] /tmp/m68dfr9m$ docker \
run \
-i \
--mount=type=bind,source=/tmp/m68dfr9m,target=/iWBQko \
--mount=type=bind,source=/tmp/ku7m_krp,target=/tmp \
--mount=type=bind,source=/home/fbrito/work/guide/B8A.tif,target=/var/lib/cwl/stg6d53b4c1-725d-4b0d-83dc-0d8662c928e7/B8A.tif,readonly \
--workdir=/iWBQko \
--read-only=true \
--user=1000:1000 \
--rm \
--cidfile=/tmp/e5jclx6k/20210810091516-035626.cid \
--env=TMPDIR=/tmp \
--env=HOME=/iWBQko \
osgeo/gdal \
gdal_translate \
-projwin \
136.659 \
-35.791 \
136.923 \
-35.96 \
-projwin_srs \
EPSG:4326 \
/var/lib/cwl/stg6d53b4c1-725d-4b0d-83dc-0d8662c928e7/B8A.tif \
B8A_clipped.tif
Input file size is 5490, 5490
0...10...20...30...40...50...60...70...80...90...100 - done.
INFO [job gdal-translate.cwl] Max memory used: 0MiB
INFO [job gdal-translate.cwl] completed success
{
"clipped_tif": {
"location": "file:///home/fbrito/work/guide/B8A_clipped.tif",
"basename": "B8A_clipped.tif",
"class": "File",
"checksum": "sha1$033898bb305bb2ae53980182cd882b05cc585fa2",
"size": 2256036,
"path": "/home/fbrito/work/guide/B8A_clipped.tif"
}
}
INFO Final process status is success