gts2_client.py 45.3 KB
Newer Older
Hannes Diedrich's avatar
Hannes Diedrich committed
1
#!/usr/bin/env python
Hannes Diedrich's avatar
Hannes Diedrich committed
2
# -*- coding: utf-8 -*-
Hannes Diedrich's avatar
Hannes Diedrich committed
3
4
5

import requests
import warnings
6
import traceback
Hannes Diedrich's avatar
Hannes Diedrich committed
7
8
import argparse
import json
9
import time
Hannes Diedrich's avatar
Hannes Diedrich committed
10
import logging
11
12
import tempfile
import shutil
Hannes Diedrich's avatar
Hannes Diedrich committed
13
import os
Hannes Diedrich's avatar
Hannes Diedrich committed
14
15
16
import collections
import re

Hannes Diedrich's avatar
Hannes Diedrich committed
17
import gdal
Hannes Diedrich's avatar
Hannes Diedrich committed
18
19
20
import numpy as np
import netCDF4 as nc4
from glob import glob
21
22
from os.path import isfile
from os.path import join
Hannes Diedrich's avatar
Hannes Diedrich committed
23
24
25
26
from os.path import expanduser
from scipy.ndimage.interpolation import zoom
from skimage.exposure import rescale_intensity, adjust_gamma
from scipy.misc import imsave
27
from subprocess import Popen, PIPE
Hannes Diedrich's avatar
Hannes Diedrich committed
28
29
30
31
32
33
34

band_settings = {"realistic": ("B04", "B03", "B02"),
                 "nice_looking": ("B11", "B08", "B03"),
                 "vegetation": ("B8A", "B04", "B03"),
                 "healthy_vegetation_urban": ("B8A", "B11", "B02"),
                 "snow": ("B08", "B11", "B04"),
                 "agriculture": ("B11", "B08", "B02")}
Hannes Diedrich's avatar
Hannes Diedrich committed
35

36
37
38
warnings.filterwarnings("ignore")


Hannes Diedrich's avatar
Hannes Diedrich committed
39
class Gts2Request(dict):
40

41
42
43
44
45
46
47
    def check_geo(self, ll=(), ur=()):
        """
        Check if area of request is not too large.
        :param ll: tuple
        :param ur: tuple
        :return:
        """
Hannes Diedrich's avatar
Hannes Diedrich committed
48
        thres = 0.4
49
50
51
        latrange = ur[1]-ll[1]
        lonrange = ur[0]-ll[0]
        if (lonrange >= thres) | (latrange >= thres):
Hannes Diedrich's avatar
Hannes Diedrich committed
52
53
            raise ValueError("Your requestst area too large: ({lon:7.5f}°x{lat:7.5f}° exceeds {thres}°x{thres}°)"
                             "".format(lat=latrange, lon=lonrange, thres=thres))
54

Hannes Diedrich's avatar
Hannes Diedrich committed
55
56
    def __init__(self, opts, logger=None):

Hannes Diedrich's avatar
Hannes Diedrich committed
57
        self.check_geo(ll=opts["ll"], ur=opts["ur"])
58

Hannes Diedrich's avatar
Hannes Diedrich committed
59
        address = "https://rz-vm175.gfz-potsdam.de:{port}/AC".format(port=opts["auth"]["port"])
60
61

        # test parameters such that api string formatting wont fail
Hannes Diedrich's avatar
Hannes Diedrich committed
62
63
64
65
66
67
68
69
70
        assert len(opts["ur"]) == 2
        assert len(opts["ll"]) == 2
        assert opts["sensor"] in ["S2A", "S2B", "S2all"]
        assert opts["level"] in ["L1C", "L2A"]

        self.api_fmt = "{address}/{bands}/{date_from}_{date_to}/{ll_lon:.5f}_{ur_lon:.5f}_{ll_lat:.5f}_{ur_lat:.5f}" \
                       "?minimum_fill={minimum_fill}&sensor={sensor}&level={level}&version={version}&suffix={suffix}" \
                       "&max_cloudy={max_cloudy}&utm_zone={utm_zone}"

71
        # construct api call
Hannes Diedrich's avatar
Hannes Diedrich committed
72
73
74
75
76
77
        self.api_call = self.api_fmt.format(address=address, bands=opts["bands"], date_from=opts["date_from"],
                                            date_to=opts["date_to"], suffix=opts["suffix"], level=opts["level"],
                                            minimum_fill=opts["minimum_fill"], sensor=opts["sensor"],
                                            version=opts["version"], max_cloudy=opts["max_cloudy"],
                                            ll_lon=opts["ll"][0], ll_lat=opts["ll"][1],
                                            ur_lon=opts["ur"][0], ur_lat=opts["ur"][1], utm_zone=opts["utm_zone"])
78
79
80
        logger.info(">>>>>> API-call: %s " % self.api_call)
        # get data, update dict from json

Hannes Diedrich's avatar
Hannes Diedrich committed
81
        result = requests.get(self.api_call, verify=False, auth=opts["auth"]["auth"])
Hannes Diedrich's avatar
Hannes Diedrich committed
82
83
84
85
        status = {"http_code": result.status_code}

        self.update(result.json())
        self.update(status)
86

Hannes Diedrich's avatar
Hannes Diedrich committed
87

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def __spawn(cmd, cwd):
    """
    Runs shell command (cmd) in the working directory (cwd).
    :param cmd: string
    :param cwd: string
    :return:
    """
    pp = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True, cwd=cwd)
    stdout, stderr = pp.communicate()
    if pp.returncode != 0:
        raise ValueError(
            "cmd:%s,rc:%s,stderr:%s,stdout:%s" % (cmd, str(pp.returncode), str(stderr), str(stdout)))


def __merge_tifs_same_timestamp(tifs, outpath, target_tile=""):
    """
    Transforms all tifs from timestep to same utm zon (from target tile) and merges them together.
    :param outpath: string
    :param tifs: List of strings or None
    :param target_tile: 5-element string
    :return:
    """
    basepath = os.path.dirname(tifs[0])
    utm = target_tile[0:2]
    base_split = os.path.basename(tifs[0]).split("_")
    merge_file = "{path}/{pref}_{tile}_{suff}".format(path=outpath, pref="_".join(base_split[0:4]), tile=target_tile,
                                                      suff="_".join(base_split[5::]))

    with tempfile.TemporaryDirectory(dir=basepath) as tmp_dir:
        for fi in tifs:
            if target_tile not in fi:
                outfile = "{tmp_dir}/{orig_fn}_{tile}.tif".format(orig_fn=os.path.basename(fi.split(".")[0]),
                                                                  tile=target_tile, tmp_dir=tmp_dir)
                cmd = "gdalwarp -t_srs '+proj=utm +zone={utm} +datum=WGS84' -overwrite {infile} {outfile} ".format(
                    infile=fi, outfile=outfile, utm=utm)
                __spawn(cmd, tmp_dir)

            else:
                shutil.copy(fi, tmp_dir)

        merge_list = " ".join(glob(os.path.join(tmp_dir, "*.tif")))
        cmd = "gdal_merge.py -o {merge_file} {merge_list}".format(merge_file=merge_file, merge_list=merge_list)
        __spawn(cmd, tmp_dir)

    return merge_file


def __find_double_timestamp(files):
    """
    Finds filenames with doublicate time stamp (but different MGRS tile) in list of files.
    :param files: list of strings
    :return: dict
    """

    basepath = os.path.dirname(files[0])
    prefix = "_".join(os.path.basename(files[0]).split("_")[0:3])

    bands = list(set(["_".join(os.path.basename(fa).split("_")[5::]).split(".")[0] for fa in files]))

    double_dict = {}
    for bi in bands:
        band_files = [s for s in files if "_{band}.".format(band=bi) in s]
        dates = [os.path.basename(bf).split("_")[3] for bf in band_files]
        double_dates = ([item for item, count in collections.Counter(dates).items() if count > 1])

        double_files = {}
        for di in double_dates:
            double_files[di] = glob(os.path.join(basepath, "{pref}*_{date}*{band}.*".format(
                band=bi, date=di, pref=prefix)))

        double_dict[bi] = double_files

    return double_dict


def merge_tiles(tif_list, out_mode="single", target_tile=None):
    """
    Performs merge of tifs of the same timestep that are devided into two different MGRS tiles
    :param tif_list: list of strings
    :param out_mode: string
    :param target_tile: 5-element string or None
    :return:
    """

    stack = True if out_mode == "stack" else False

    double_dict = __find_double_timestamp(tif_list)
    for bi in double_dict.keys():
        for dates, tifs_one_date in double_dict[bi].items():
            basepath = os.path.dirname(tifs_one_date[0])
            if len(tifs_one_date) > 1:

                if target_tile is None:
                    tiles = [re.search('[0-9]{2,2}[A-Z]{3,3}', ti).group(0).replace("_", "") for ti in tifs_one_date]
                    target_tile = list(set(tiles))[0]

                if stack is False:
                    with tempfile.TemporaryDirectory(dir=basepath) as parent_dir:
                        _ = [shutil.move(fm, parent_dir) for fm in tifs_one_date]

                        tifs_all = glob(os.path.join(parent_dir, "*"))
                        if len(tifs_all) == 0:
                            raise FileNotFoundError("No files found in {dir}".format(dir=parent_dir))

                        bands = list(set(["_".join(os.path.basename(fa).split("_")[6::]).strip(".tif") for fa in tifs_all]))
                        for bi in bands:
                            tifs = glob(os.path.join(parent_dir, "S2*{band}*.tif".format(band=bi)))
                            if len(tifs) != 0:
                                __merge_tifs_same_timestamp(tifs, basepath, target_tile=target_tile)

                else:
                    with tempfile.TemporaryDirectory(dir=basepath) as parent_dir:
                        _ = [shutil.move(fm, parent_dir) for fm in tifs_one_date]
                        tifs_all = glob(os.path.join(parent_dir, "*"))

                        if len(tifs_all) == 0:
                            raise FileNotFoundError("No files found in {dir}".format(dir=parent_dir))

                        __merge_tifs_same_timestamp(tifs_all, basepath, target_tile=target_tile)

            else:
                raise ValueError("List of tifs to merge to short")


212
def mean_rgb_comb(infiles, rgb_comb=("B04", "B03", "B02"), bad_data_value=np.NaN):
Hannes Diedrich's avatar
Hannes Diedrich committed
213
214
215
216
    """
    Determines mean pixel value for the specified band combination for each input image.

    :param infiles: input fn
Hannes Diedrich's avatar
Hannes Diedrich committed
217
    :param rgb_comb: band combination(s) to use, use band_settings
Hannes Diedrich's avatar
Hannes Diedrich committed
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
    :param bad_data_value: bad data value in input images, default: NaN
    :return: list containing mean pixel value for the specified band combination for each input image
    """
    dicts = []
    mean_RGB = {}

    for fn in infiles:
        mean_val = []
        ds = gdal.Open(fn, gdal.GA_ReadOnly)
        for ii, band in enumerate(rgb_comb):
            data = ds.GetRasterBand(ii+1).ReadAsArray() / 10000.0
            if bad_data_value is np.NAN:
                bf = data[:, :][np.isfinite(data[:, :])]
            else:
                bf = data[:, :][data[:, :] == bad_data_value]

            # Only consider images with less than 20% pixels with value 0
            zero_per = (np.count_nonzero(bf == 0) / (data.shape[0] * data.shape[1])) * 100
            if zero_per < 20:
                band_mean = np.mean(bf)
                mean_val.append(band_mean)
            else:
                band_mean = 1
                mean_val.append(band_mean)

        # Append pathname and mean pixel value to dictionary
        mean_RGB[fn] = np.mean(mean_val)
        dicts.append(mean_RGB)
    return dicts


249
def get_lim(limfn, rgb_comb=("B04", "B03", "B02"), bad_data_value=np.NaN):
Hannes Diedrich's avatar
Hannes Diedrich committed
250
251
252
253
254
    """
    Determines standard stretching limits based on an optimized linear histogram stretching applied to
    the most cloud-free image of an image sequence.

    :param limfn: input directory, most cloud free image of a sequence
Hannes Diedrich's avatar
Hannes Diedrich committed
255
    :param rgb_comb: band combination(s) to determine stretching limits for, use band_settings
Hannes Diedrich's avatar
Hannes Diedrich committed
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
    :param bad_data_value: bad data value in input images, default: NaN
    :return: list containing stretching limits for rgb_comb
    """
    bins = np.linspace(0.0, 1.0, 255)
    band_lim = []

    ds = gdal.Open(limfn, gdal.GA_ReadOnly)
    for ii, band in enumerate(rgb_comb):

        data = ds.GetRasterBand(ii + 1).ReadAsArray() / 10000.0

        if bad_data_value is np.NAN:
            bf = data[:, :][np.isfinite(data[:, :])]
        else:
            bf = data[:, :][data[:, :] == bad_data_value]

        # Calculate stretching limits (optimized linear stretch)
        hh, xx = np.histogram(bf, bins=bins, normed=False)
        rel_cum_hist = np.cumsum(hh) / np.max(np.cumsum(hh))
        bp = (np.where(rel_cum_hist > 0.025)[0][0]) / 255
        wp = (np.where(rel_cum_hist > 0.99)[0][0]) / 255
        b = bp - 0.1 * (wp - bp)
        if wp > 0.65:
            w = wp
        elif wp < 0.15:
            w = wp + 2 * (wp - bp)
        else:
            w = wp + 0.5 * (wp - bp)
        lim = (b, w)
        band_lim.append(lim)
    return band_lim


289
def mk_rgb(basedir, outdir, rgb_comb=("B04", "B03", "B02"), rgb_gamma=(1.0, 1.0, 1.0), extension="jpg",
Hannes Diedrich's avatar
Hannes Diedrich committed
290
           resample_order=1, rgb_type=np.uint8, bad_data_value=np.NaN, logger=None):
Hannes Diedrich's avatar
Hannes Diedrich committed
291
292
293
294
295
296
297
    """
    Generation of RGB Images from Sentinel-2 data. Use hist_stretch = 'single', if to process a single image.
    Use hist_stretch = 'series', if to process a sequence of images of the same tile but different dates.

    :param extension:
    :param basedir: directory of a single image or a series of images
    :param outdir: output directory
Hannes Diedrich's avatar
Hannes Diedrich committed
298
    :param rgb_comb: band combination(s) for rgb image generation, use band_settings
Hannes Diedrich's avatar
Hannes Diedrich committed
299
300
301
302
303
304
305
306
    :param rgb_gamma: gamma values for each band, default: (1.0, 1.0, 1.0)
    :param resample_order: order of the spline interpolation, has to be in the range 0-5, default: 1
    :param rgb_type: data type of RGB image(s), default: uint8
    :param bad_data_value: bad data value in input images, default: NaN
    :return: output directory and filename
    """

    fnout_list = []
307
    infiles = glob(os.path.join(basedir, "*.tif"))
Hannes Diedrich's avatar
Hannes Diedrich committed
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
    if len(infiles) == 0:
        raise FileExistsError("Could not find any stacked tif files in {dir}".format(dir=basedir))

    # Get stretching limits
    dicts = mean_rgb_comb(infiles, rgb_comb, bad_data_value)
    super_dict = {}
    for dd in dicts:
        for k, v in dd.items():
            super_dict.setdefault(k, []).append(v)
    limdir = min(super_dict, key=super_dict.get)
    limits = get_lim(limdir, rgb_comb, bad_data_value)

    # Processing images
    for fin in infiles:

        # Output directory and filename
        split = tuple(os.path.basename(fin).split("_")[0:5])
Hannes Diedrich's avatar
Hannes Diedrich committed
325
326
        fn_out = join(outdir, "{pref}_RGB_{bands}.{ext}".format(pref="_".join(split), bands="_".join(rgb_comb),
                                                                ext=extension))
Hannes Diedrich's avatar
Hannes Diedrich committed
327
328
329
330
331
332
333
334
335
        if isfile(fn_out) is False:
            # Create output array
            ds = gdal.Open(fin, gdal.GA_ReadOnly)
            sdata = ds.GetRasterBand(1)
            output_shape = [sdata.YSize, sdata.XSize]

            S2_rgb = np.zeros(output_shape + [len(rgb_comb), ], dtype=rgb_type)
            if ds is None:
                raise ValueError("Can not load {fn}".format(fn=fin))
Hannes Diedrich's avatar
Hannes Diedrich committed
336

Hannes Diedrich's avatar
Hannes Diedrich committed
337
338
339
340
341
            for i_rgb, (band, gamma) in enumerate(zip(rgb_comb, rgb_gamma)):

                data = ds.GetRasterBand(i_rgb + 1).ReadAsArray() / 10000.0

                # Determine stretching limits
Hannes Diedrich's avatar
Hannes Diedrich committed
342
                lim = limits[rgb_comb.index(band)]
Hannes Diedrich's avatar
Hannes Diedrich committed
343
344
345
346
347
348
349
350
351
352
353

                # Calculate and apply zoom factor to input image
                zoom_factor = np.array(output_shape) / np.array(data[:, :].shape)
                zm = np.nan_to_num(np.array(data[:, :], dtype=np.float32))
                if (zoom_factor != [1.0, 1.0]).all():
                    zm = zoom(input=zm, zoom=zoom_factor, order=resample_order)

                # Rescale intensity to range (0.0, 255.0)
                bf = rescale_intensity(image=zm, in_range=lim, out_range=(0.0, 255.0))

                if gamma != 0.0:
Hannes Diedrich's avatar
Hannes Diedrich committed
354
355
356
                    S2_rgb[:, :, i_rgb] = np.array(adjust_gamma(bf, gamma), dtype=rgb_type)
                else:
                    S2_rgb[:, :, i_rgb] = np.array(bf, dtype=rgb_type)
Hannes Diedrich's avatar
Hannes Diedrich committed
357

358
                del data
Hannes Diedrich's avatar
Hannes Diedrich committed
359
360
            del ds

Hannes Diedrich's avatar
Hannes Diedrich committed
361
            imsave(fn_out, S2_rgb)
Hannes Diedrich's avatar
Hannes Diedrich committed
362
363
364
365
366
            fnout_list.append(fn_out)

    return fnout_list


Hannes Diedrich's avatar
Hannes Diedrich committed
367
368
369
370
def json_to_tiff(out_mode, api_result, only_tile, outpath, out_prefix, wl, level, stack_resolution, bands, logger=None):
    """
    Get data from json dict and save if as singletif files OR:
    save all requested bands plus cloudmask as one tiff file per date and tile.
Hannes Diedrich's avatar
Hannes Diedrich committed
371
372
373
374
375
376
377
378
379
380
    :type out_mode: string
    :type api_result: json sring
    :type only_tile: string
    :type outpath: string
    :type out_prefix: string list of 
    :type wl: dict
    :type level: string
    :type stack_resolution: string
    :type bands: string
    :type logger: logger
381
    :return: List of written tifs (list of strings)
Hannes Diedrich's avatar
Hannes Diedrich committed
382
383
    """

384
385
    tif_list = []

386
    if out_mode == "single":
Hannes Diedrich's avatar
Hannes Diedrich committed
387
388
389
390
391
392
393
394
395
396
397
        for tile_key in api_result['Results'].keys():
            if not ((only_tile != "") & (tile_key != only_tile)):
                for bi in range(len(api_result['Request']['bands'])):
                    band = api_result['Request']['bands'][bi]
                    res = api_result['Request']['resolution'][bi]
                    band_key = "%s_%sm" % (band, res)
                    if band_key not in api_result['Results'][tile_key].keys():
                        raise Exception("ERROR: No data was returned from API.")
                    count_dates = len(api_result['Results'][tile_key][band_key]['data'])
                    for ti in range(count_dates):
                        data = api_result['Results'][tile_key][band_key]['data'][ti]
398
399
                        ac_date = api_result['Results'][tile_key][band_key]['time'][ti][:10]  # only date without time
                        sensor = api_result['Results'][tile_key][band_key]['sensor'][ti]
Hannes Diedrich's avatar
Hannes Diedrich committed
400
401
402
403
404
405
406
407
408
409
410
411
                        x_min = api_result['Results'][tile_key][band_key]['mapinfo']['utm_coord_ul_x']
                        y_max = api_result['Results'][tile_key][band_key]['mapinfo']['utm_coord_ul_y']
                        geo_proj = api_result['Results'][tile_key][band_key]['mapinfo']['geo_projection']
                        arr = np.asarray(data)

                        # save each requested band plus cloudmask as single tiff file per date and tile
                        if out_mode == "single":
                            rows, cols = arr.shape
                            geotrans = (x_min, res, 0, y_max, 0, -res)
                            driver = gdal.GetDriverByName("GTiff")

                            # create tif file for band
412
                            outfile = "{path}/{sensor}_{level}_{pref}_{date}_{tile}_{band}_{res}m.tif".format(
413
                                path=outpath, pref=out_prefix, date=ac_date, tile=tile_key, band=band, level=level,
414
                                res=res, sensor=sensor)
Hannes Diedrich's avatar
Hannes Diedrich committed
415
416
417
418
                            img = driver.Create(outfile, cols, rows, 1, gdal.GDT_Int32)
                            img.SetGeoTransform(geotrans)
                            img.SetProjection(geo_proj)
                            img.GetRasterBand(1).WriteArray(arr)
419
                            img.GetRasterBand(1).SetNoDataValue(-9999)
420
421
422
                            img.GetRasterBand(1).SetMetadataItem("Name", "Band" + str(band)[1:3])
                            img.GetRasterBand(1).SetMetadataItem("Central Wavelength", wl[str(band)])
                            img.GetRasterBand(1).SetMetadataItem("Acknowledgement",
Hannes Diedrich's avatar
Hannes Diedrich committed
423
424
425
426
427
428
                                                                 "The 'GFZ Time Series System for Sentinel-2' (GTS2) "
                                                                 "was financed by AgriCircle/ADAMA between 2016/06 and "
                                                                 "2017/12.")

                            img.FlushCache()
                            del img
429
                            tif_list.append(outfile)
Hannes Diedrich's avatar
Hannes Diedrich committed
430
431
432
433
434

                            # create tif file for clm
                            if level != "L1C":
                                clm = api_result['Metadata'][tile_key]['MSK_' + str(res) + 'm']['data'][ti]
                                clm_arr = np.asarray(clm)
435
                                clm_outfile = "{path}/{sensor}_{level}_{pref}_{date}_{tile}_MSK_{res}m.tif".format(
436
                                    path=outpath, pref=out_prefix, date=ac_date, tile=tile_key, band=band, level=level,
437
                                    res=res, sensor=sensor)
Hannes Diedrich's avatar
Hannes Diedrich committed
438
                                img = driver.Create(clm_outfile, cols, rows, 1, gdal.GDT_Int32)
439
440
                                img.SetGeoTransform(geotrans)
                                img.SetProjection(geo_proj)
Hannes Diedrich's avatar
Hannes Diedrich committed
441
                                img.GetRasterBand(1).WriteArray(clm_arr)
442
                                img.GetRasterBand(1).SetNoDataValue(-9999)
443
                                img.GetRasterBand(1).SetMetadataItem("Name", "MSK")
Hannes Diedrich's avatar
Hannes Diedrich committed
444
                                img.GetRasterBand(1).SetMetadataItem("MSK_legend", str(api_result['Metadata']['MSK_legend']))
445
                                img.GetRasterBand(1).SetMetadataItem("Acknowledgement",
Hannes Diedrich's avatar
Hannes Diedrich committed
446
447
448
                                                                     "The 'GFZ Time Series System for Sentinel-2' "
                                                                     "(GTS2) was financed by AgriCircle/ADAMA between "
                                                                     "2016/06 and 2017/12.")
Hannes Diedrich's avatar
Hannes Diedrich committed
449
450
                                img.FlushCache()
                                del img
451
                                tif_list.append(clm_outfile)
Hannes Diedrich's avatar
Hannes Diedrich committed
452

Hannes Diedrich's avatar
Hannes Diedrich committed
453
    if out_mode == "stack":
Hannes Diedrich's avatar
Hannes Diedrich committed
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
        for tile_key in api_result['Results'].keys():
            # get the number of timesteps:
            band = api_result['Request']['bands'][0]
            res = api_result['Request']['resolution'][0]
            band_key = "%s_%sm" % (band, res)
            if band_key not in api_result['Results'][tile_key].keys():
                raise Exception("ERROR: No data was returned from API.")
            count_dates = len(api_result['Results'][tile_key][band_key]['data'])
            for ti in range(0, count_dates):
                zm_dict = {}

                # number of requested bands per date and tile
                count_bands = len(api_result['Request']['bands'])

                # define reference spatial resolution for bandstack
                find_ref_band_key = [key for key in api_result['Results'][tile_key].keys() if
                                     stack_resolution in key]

                if len(find_ref_band_key) == 0:
                    raise ValueError(
                        "You have chosen {res}m for stack resolution, but did not request at least one "
                        "band with spatial sampling of {res}m".format(res=stack_resolution))

                ref_band_key = find_ref_band_key[0]
                ref_data = api_result['Results'][tile_key][ref_band_key]['data'][ti]
                ref_data = np.asarray(ref_data)

                # get data from json dict, zoom and put in dict
                for bi in range(0, count_bands):
                    band = api_result['Request']['bands'][bi]
                    res = api_result['Request']['resolution'][bi]
                    band_key = "%s_%sm" % (band, res)
                    if band_key in api_result['Results'][tile_key].keys():
                        data = api_result['Results'][tile_key][band_key]['data'][ti]
                        data = np.asarray(data)

                        # resample array to reference spatial resolution
                        zoom_factor = np.array(ref_data.shape) / np.array(data.shape)
                        zm = np.nan_to_num(np.array(data, dtype=np.float32))
                        if (zoom_factor != [1.0, 1.0]).all():
                            zm = zoom(input=zm, zoom=zoom_factor, order=0)
                        zm_dict[band] = zm

                # preparation of output file:
                rows, cols = ref_data.shape
                ac_date = api_result['Results'][tile_key][ref_band_key]['time'][ti][:10]
500
                sensor = api_result['Results'][tile_key][band_key]['sensor'][ti]
Hannes Diedrich's avatar
Hannes Diedrich committed
501
502
503
504
505
506
                x_min = api_result['Results'][tile_key][ref_band_key]['mapinfo']['utm_coord_ul_x']
                y_max = api_result['Results'][tile_key][ref_band_key]['mapinfo']['utm_coord_ul_y']
                geo_proj = api_result['Results'][tile_key][band_key]['mapinfo']['geo_projection']
                geotrans = (x_min, int(stack_resolution), 0, y_max, 0, -int(stack_resolution))
                driver = gdal.GetDriverByName("GTiff")
                if level == "L1C":
507
                    outfile = "{path}/{sensor}_{level}_{pref}_{date}_{tile}_{band}_{res}m.tif".format(
508
                        path=outpath, pref=out_prefix, date=ac_date, tile=tile_key, band=bands, level=level,
509
                        res=stack_resolution, sensor=sensor)
Hannes Diedrich's avatar
Hannes Diedrich committed
510
                else:
511
                    outfile = "{path}/{sensor}_{level}_{pref}_{date}_{tile}_{band}_MSK_{res}m.tif".format(
512
                        path=outpath, pref=out_prefix, date=ac_date, tile=tile_key, band=bands, level=level,
513
                        res=stack_resolution, sensor=sensor)
Hannes Diedrich's avatar
Hannes Diedrich committed
514
515
516
517
518
519
520
                img = driver.Create(outfile, cols, rows, count_bands + 1, gdal.GDT_Int32)
                img.SetGeoTransform(geotrans)
                img.SetProjection(geo_proj)

                slice = 1
                for zi in bands.split("_"):
                    img.GetRasterBand(slice).WriteArray(zm_dict[zi])
521
                    img.GetRasterBand(slice).SetNoDataValue(-9999)
522
523
524
                    img.GetRasterBand(slice).SetMetadataItem("Name", "Band" + zi[1:3])
                    img.GetRasterBand(slice).SetMetadataItem("Central Wavelength", wl[str(zi)])
                    img.GetRasterBand(slice).SetMetadataItem("Acknowledgement",
Hannes Diedrich's avatar
Hannes Diedrich committed
525
526
527
528
529
530
531
532
533
534
535
536
                                                             "The 'GFZ Time Series System for Sentinel-2' (GTS2) "
                                                             "was financed by AgriCircle/ADAMA between 2016/06 and "
                                                             "2017/12.")
                    slice += 1

                img.SetMetadata({'TIFFTAG_YRESOLUTION': '%s' % stack_resolution,
                                 'TIFFTAG_XRESOLUTION': '%s' % stack_resolution})

                if level != "L1C":
                    clm = api_result['Metadata'][tile_key]['MSK_%sm' % stack_resolution]['data'][ti]
                    clm_arr = np.asarray(clm)
                    img.GetRasterBand(slice).WriteArray(clm_arr)
537
                    img.GetRasterBand(slice).SetNoDataValue(-9999)
538
                    img.GetRasterBand(slice).SetMetadataItem("Name", "MSK")
Hannes Diedrich's avatar
Hannes Diedrich committed
539
                    img.GetRasterBand(slice).SetMetadataItem("MSK_legend", str(api_result['Metadata']['MSK_legend']))
Hannes Diedrich's avatar
Hannes Diedrich committed
540
541
542

                img.FlushCache()
                del img
Hannes Diedrich's avatar
Hannes Diedrich committed
543

544
545
546
547
                tif_list.append(outfile)

    return tif_list

548
549

def json_to_netcdf(out_mode, api_result, outpath, out_prefix, geo_ll, geo_ur, start_date, end_date, bands, level, wl):
Hannes Diedrich's avatar
Hannes Diedrich committed
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
    """
    Get data from json-dict and save as netcdf file
    :param out_mode:
    :param api_result:
    :param outpath:
    :param out_prefix:
    :param geo_ll:
    :param geo_ur:
    :param start_date:
    :param end_date:
    :param bands:
    :param level:
    :param wl:
    :return:
    """

566
567
    if out_mode == "nc":
        try:
568
569
570
571
            outfile = "{path}/S2_{level}_{pref}_{lla}_{llo}_{ura}_{uro}_{sdate}_{edate}_{band}.nc".format(
                path=outpath, pref=out_prefix, sdate=start_date, edate=end_date, band=bands, level=level,
                lla=geo_ll[0], llo=geo_ll[1], ura=geo_ur[0], uro=geo_ur[1])

Niklas Bohn's avatar
Niklas Bohn committed
572
            f = nc4.Dataset(outfile, 'w', format='NETCDF4')
573
574
575
576
577
578
579

            f.Conventions = "CF-1.7"
            f.title = "Sentinel-2 data from the GTS2 cloud"
            f.institution = "Helmholtz Centre Potsdam, GFZ German Research Centre For Geosciences"
            f.source = "https://gitext.gfz-potsdam.de/gts2/gts2_client/blob/master/gts2_client/gts2_client.py"
            f.references = "GTS2 (GFZ Time Series System for Sentinel-2)"
            f.history = "[]"
Niklas Bohn's avatar
Niklas Bohn committed
580
581
            f.comment = "Sentinel-2 data from the GTS2 (GFZ Time Series System for Sentinel-2) cloud for an area of " \
                        "interest, time of interest and wanted band combination"
582
583
            f.acknowledgement = api_result['Acknowledgement']
            for key in api_result.keys():
584
                if key == 'ControlValues':
585
                    key_group = f.createGroup(key)
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
                    [setattr(key_group, attr, api_result[key][attr]) for attr in api_result[key].keys()]
                if key == 'Request':
                    key_group = f.createGroup(key)
                    [setattr(key_group, attr.replace("-", "_"), str(api_result[key][attr]))
                     for attr in api_result[key].keys() if attr != 'bands' and attr != 'resolution']
                    key_group.setncattr_string('bands', api_result[key]['bands'])
                    key_group.setncattr_string('resolution', api_result[key]['resolution'])
                if key == 'RequestGeoInfo':
                    key_group = f.createGroup(key)
                    tile_group = key_group.createGroup('Tiles')
                    [setattr(tile_group, attr, api_result[key]['Tiles'][attr])
                     for attr in api_result[key]['Tiles'].keys()]
                if key == 'Results':
                    key_group = f.createGroup(key)
                    for tile in api_result[key].keys():
                        tile_group = key_group.createGroup(tile)
                        for band in api_result[key][tile].keys():
                            band_group = tile_group.createGroup(band)
                            band_group.setncattr_string('sensor', api_result[key][tile][band]['sensor'])
                            band_group.setncattr_string('time', api_result[key][tile][band]['time'])
                            for data_info in api_result[key][tile][band].keys():
                                if data_info == 'mapinfo':
                                    data_info_group = band_group.createGroup(data_info)
                                    [setattr(data_info_group, attr, api_result[key][tile][band][data_info][attr])
                                     for attr in api_result[key][tile][band][data_info].keys()]
                                if data_info == 'data':
                                    band_arr = np.asarray(api_result[key][tile][band][data_info])
613
614
615
616
                                    band_group.createDimension('x', band_arr.shape[2])
                                    band_group.createDimension('y', band_arr.shape[1])
                                    band_group.createDimension('t', band_arr.shape[0])
                                    data = band_group.createVariable('Data', 'i4', ('x', 'y', 't'), fill_value=255)
617
618
619
620
621
622
623
624
625
626
                                    data.units = "None"
                                    if level == "L1C":
                                        data.long_name = "Top of Atmosphere Reflectance"
                                        data.standard_name = "toa_reflectance"
                                    else:
                                        data.long_name = "Surface Reflectance"
                                        data.standard_name = "boa_reflectance"
                                    data.valid_range = np.array((-2000.0, 16000.0))
                                    data.actual_range = np.array((np.min(band_arr), np.max(band_arr)))
                                    data[:, :, :] = band_arr
627
628
629
                                    band_group.Band = band.split("_")[0].split("B")[-1]
                                    band_group.Resolution = band.split("_")[-1]
                                    band_group.Central_Wavelength = wl[str(band.split("_")[0])]
630
631
632
633
634
635
636
637
638

                if key == 'Metadata':
                    key_group = f.createGroup(key)
                    for tile in api_result[key].keys():
                        tile_group = key_group.createGroup(tile)
                        if tile == 'MSK_legend':
                            [setattr(tile_group, "Class_" + str(attr), api_result[key][tile][attr])
                             for attr in api_result[key][tile].keys()]
                        else:
639
640
                            for msk in api_result[key][tile].keys():
                                mask_group = tile_group.createGroup(msk)
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
                                mask_group.setncattr_string('time', api_result[key][tile][msk]['time'])
                                for data_info in api_result[key][tile][msk].keys():
                                    if data_info == 'cloud_stats':
                                        data_info_group = mask_group.createGroup(data_info)
                                        data_info_group.setncattr_string('cloud_frac',
                                                                         api_result[key][tile][msk][data_info]
                                                                         ['cloud_frac'])
                                    if data_info == 'mapinfo':
                                        data_info_group = mask_group.createGroup(data_info)
                                        [setattr(data_info_group, attr, api_result[key][tile][msk][data_info][attr])
                                         for attr in api_result[key][tile][msk][data_info].keys()]
                                    if data_info == 'stats':
                                        data_info_group = mask_group.createGroup(data_info)
                                        data_info_group.setncattr_string('cloud_frac',
                                                                         api_result[key][tile][msk][data_info]
                                                                         ['cloud_frac'])
                                        data_info_group.setncattr_string('data_frac',
                                                                         api_result[key][tile][msk][data_info]
                                                                         ['data_frac'])
                                        data_info_group.setncattr_string('time',
                                                                         api_result[key][tile][msk][data_info]['time'])
                                    if data_info == 'data':
                                        mask_arr = np.asarray(api_result[key][tile][msk][data_info])
664
665
666
667
                                        mask_group.createDimension('x', mask_arr.shape[2])
                                        mask_group.createDimension('y', mask_arr.shape[1])
                                        mask_group.createDimension('t', mask_arr.shape[0])
                                        data = mask_group.createVariable('Data', 'i4', ('x', 'y', 't'), fill_value=255)
668
669
670
671
672
673
                                        data.units = "None"
                                        data.long_name = "Mask classes"
                                        data.standard_name = "classes"
                                        data.valid_range = np.array((10.0, 60.0))
                                        data.actual_range = np.array((np.min(mask_arr), np.max(mask_arr)))
                                        data[:, :, :] = mask_arr
674
675
676
677
678
            f.close()
        except:
            raise Exception("Something went wrong while saving as netcdf. " + traceback.format_exc())


Hannes Diedrich's avatar
Hannes Diedrich committed
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
def __get_auth(logger=None):
    """
    Gets auth and port
    :param logger: logger
    :return: dict
    """

    home_dir = expanduser("~")
    cred_file = "%s/credentials_gts2_client" % home_dir
    if len(glob(cred_file)) == 1:
        with open(cred_file, "r") as cf:
            cred_dict = json.load(cf)
        if "port" in cred_dict.keys():
            port = cred_dict["port"]
        else:
            port = 80
        auth = (cred_dict["user"], cred_dict["password"])
    else:
        logger.error("You did not save your credentials in %s." % cred_file)
        user = input("gts2_client - Please insert username: ")
        password = input("gts2_client - Please insert password: ")
700
701
702
        port = input("gts2_client - Please insert port for API (for default, type 'yes'): ")
        if port == "yes":
            port = "80"
Hannes Diedrich's avatar
Hannes Diedrich committed
703
704
705
706
707
        auth = (user, password)

    return {"auth": auth, "port": port}


708
709
def client(outpath="", out_prefix="", out_mode="json", geo_ll=(), geo_ur=(), sensor="S2A", bands="", max_cloudy="0.5",
           level="L2A", start_date="", end_date="", version="0.12", suffix="", minimum_fill="",
710
711
           only_tile="", stack_resolution="10", quiet=False, rgb_extension="jpg", rgb_bands_selection="realistic",
           merge_tifs=False, merge_tile=None):
712
    """
Niklas Bohn's avatar
Niklas Bohn committed
713
714
    Downloads data via API and saves it in a wanted file format (.json, .tiff or .nc) or alternatively returns a python 
    dictionary.
Hannes Diedrich's avatar
Hannes Diedrich committed
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
    :type merge_tile: string
    :type merge_tifs: boolean
    :type rgb_bands_selection: string
    :type rgb_extension: string
    :type quiet: boolean
    :type only_tile: string
    :type minimum_fill: string
    :type outpath: string
    :type out_prefix: string
    :type out_mode: string
    :type geo_ll: tuple of floats
    :type geo_ur: tuple of floats
    :type sensor: string
    :type bands: string
    :type max_cloudy: string
    :type level: string
    :type start_date: string
    :type end_date: string
    :type version: string
    :type suffix: string
    :type stack_resolution: string
736
737
738
    :return:
    """

739
740
    stime = time.time()

Hannes Diedrich's avatar
Hannes Diedrich committed
741
742
743
744
    wl = {'B01': '443nm', 'B02': '490nm', 'B03': '560nm', 'B04': '665nm', 'B05': '705nm', 'B06': '740nm',
          'B07': '783nm', 'B08': '842nm', 'B8A': '865nm', 'B09': '940nm', 'B10': '1375nm', 'B11': '1610nm',
          'B12': '2190nm'}

Hannes Diedrich's avatar
Hannes Diedrich committed
745
746
747
748
    if quiet is True:
        loggerlevel = logging.ERROR
    else:
        loggerlevel = logging.INFO
Hannes Diedrich's avatar
Hannes Diedrich committed
749
750
    logging.basicConfig(level=loggerlevel, format='# %(name)-15s %(message)s')
    logger = logging.getLogger(name="gts2_client")
Hannes Diedrich's avatar
Hannes Diedrich committed
751

Hannes Diedrich's avatar
Hannes Diedrich committed
752
753
754
    if "_" in out_prefix:
        raise ValueError("out_prefix contains '_'. Please use different character, e.g. '-'.")

Hannes Diedrich's avatar
Hannes Diedrich committed
755
756
757
    valid_stack_resolution = ["10", "20", ""]
    if stack_resolution not in valid_stack_resolution:
        raise AssertionError("Stack resolution is not in ", valid_stack_resolution)
758
759

    if out_mode == "stack" and "B01" in bands:
760
        raise AssertionError("Band 1 (B01) can not be stacked. Please request separately.")
761

762
763
764
    if out_mode != "rgb" and bands == "":
        raise AssertionError("Please provide at least one band.")

Hannes Diedrich's avatar
Hannes Diedrich committed
765
    valid_out_modes = ["json", "nc", "single", "stack", "python", "rgb"]
Hannes Diedrich's avatar
Hannes Diedrich committed
766
    try:
Hannes Diedrich's avatar
Hannes Diedrich committed
767
        assert out_mode in valid_out_modes
Hannes Diedrich's avatar
Hannes Diedrich committed
768
    except:
Hannes Diedrich's avatar
Hannes Diedrich committed
769
        raise AssertionError("Invalid output mode. Choose from {list}".format(list=", ".join(valid_out_modes)))
Hannes Diedrich's avatar
Hannes Diedrich committed
770

Hannes Diedrich's avatar
Hannes Diedrich committed
771
    auth = __get_auth(logger=logger)
772

Hannes Diedrich's avatar
Hannes Diedrich committed
773
774
775
    if out_mode == "rgb":
        band_setting = band_settings[rgb_bands_selection]
        bands = "_".join(band_setting)
776
777
    else:
        band_setting = []
Hannes Diedrich's avatar
Hannes Diedrich committed
778

779
780
781
    # options
    opts = {"ll": geo_ll, "ur": geo_ur, "sensor": sensor, "bands": bands,
            "max_cloudy": max_cloudy, "auth": auth, "level": level, "date_from": start_date, "date_to": end_date,
Hannes Diedrich's avatar
Hannes Diedrich committed
782
            "version": version, "suffix": suffix, "minimum_fill": minimum_fill, "utm_zone": only_tile, "logger": logger}
783

Hannes Diedrich's avatar
Hannes Diedrich committed
784
785
    # actual API request
    logger.info("Requesting data from the GTS2 server ...", )
786
    a_stime = time.time()
Hannes Diedrich's avatar
Hannes Diedrich committed
787
    api_result = Gts2Request(opts, logger=logger)
788
789
    a_runtime = time.time() - a_stime
    logger.info(">>>>>> Runtime of API: %7.2f minutes" % (a_runtime / 60.))
790

Hannes Diedrich's avatar
Hannes Diedrich committed
791
    if api_result["http_code"] >= 500:
792
        logger.error("##################")
Hannes Diedrich's avatar
Hannes Diedrich committed
793
794
        raise ChildProcessError("API call not right or server Problem (http_code={code}).".format(
            code=api_result["http_code"]))
795
    if api_result["ControlValues"]["API_status"] == 1:
796
        logger.error("##################")
Hannes Diedrich's avatar
Hannes Diedrich committed
797
        logger.error(str(api_result["ControlValues"]["API_message"]))
Hannes Diedrich's avatar
Hannes Diedrich committed
798
799
        raise ChildProcessError("Something went wrong on GTS2 Server (http_code={code}).".format(
            code=api_result["http_code"]))
800

Hannes Diedrich's avatar
Hannes Diedrich committed
801
802
803
    if only_tile != "":
        requ_tiles = api_result['Results'].keys()
        if only_tile not in requ_tiles:
Hannes Diedrich's avatar
Hannes Diedrich committed
804
            raise ValueError("Tile %s not available in results." % only_tile)
Hannes Diedrich's avatar
Hannes Diedrich committed
805

806
    if out_mode == "json":
Hannes Diedrich's avatar
Hannes Diedrich committed
807
        logger.info("Saving data to json file ...", )
808
809
810
811
        json_outfile = "{path}/S2_{level}_{pref}_{lla}_{llo}_{ura}_{uro}_{sdate}_{edate}_{band}.json".format(
            path=outpath, pref=out_prefix, sdate=start_date, edate=end_date, band=bands, level=level,
            lla=geo_ll[0], llo=geo_ll[1], ura=geo_ur[0], uro=geo_ur[1])

812
813
814
815
        with open(json_outfile, 'w') as f:
            json.dump(api_result, f, indent=4)

    elif out_mode == "nc":
Hannes Diedrich's avatar
Hannes Diedrich committed
816
        logger.info("Converting data to netcdf-file ...")
Niklas Bohn's avatar
Niklas Bohn committed
817
818
        json_to_netcdf(out_mode, api_result, outpath, out_prefix, geo_ll, geo_ur, start_date, end_date, bands, level,
                       wl)
Hannes Diedrich's avatar
Hannes Diedrich committed
819

Hannes Diedrich's avatar
Hannes Diedrich committed
820
821
    elif out_mode == "single" or out_mode == "stack":
        logger.info("Converting data to %s tif-files ..." % out_mode, )
Hannes Diedrich's avatar
Hannes Diedrich committed
822
823
        tif_list = json_to_tiff(out_mode, api_result, only_tile, outpath, out_prefix, wl, level, stack_resolution,
                                bands, logger=logger)
824
825
        if merge_tifs is True:
            merge_tiles(tif_list, out_mode=out_mode, target_tile=merge_tile)
826
827
828
829
830

    elif out_mode == "rgb":
        logger.info("Converting data to %s files ..." % out_mode, )
        with tempfile.TemporaryDirectory(dir=outpath) as tmp_dir:

831
            tif_list = json_to_tiff("stack", api_result, only_tile, tmp_dir, out_prefix, wl, level, stack_resolution,
Hannes Diedrich's avatar
Hannes Diedrich committed
832
                                    bands, logger=logger)
833
834
835
836

            if merge_tifs is True:
                merge_tiles(tif_list, out_mode="stack", target_tile=merge_tile)

Hannes Diedrich's avatar
Hannes Diedrich committed
837
            mk_rgb(tmp_dir, tmp_dir, rgb_comb=band_setting, extension=rgb_extension, logger=logger)
Hannes Diedrich's avatar
Hannes Diedrich committed
838
            rgb_files = glob(os.path.join(tmp_dir, "*RGB*.{ext}".format(ext=rgb_extension)))
839
840
            _ = [shutil.move(fi, os.path.join(outpath, os.path.basename(fi))) for fi in rgb_files]

Hannes Diedrich's avatar
Hannes Diedrich committed
841
842
    elif out_mode == "python":
        logger.info("Returning python dictionary ...")
843
        return api_result
Niklas Bohn's avatar
Niklas Bohn committed
844

845
846
847
    runtime = time.time() - stime
    logger.info("Total runtime: %7.2f minutes" % (runtime / 60.))
    logger.info("....DONE")
Hannes Diedrich's avatar
Hannes Diedrich committed
848
    return
849

Hannes Diedrich's avatar
Hannes Diedrich committed
850

851
852
853
854
855
856
857
858
def str2bool(v):
    if v.lower() in ('yes', 'true', 't', 'y', '1'):
        return True
    elif v.lower() in ('no', 'false', 'f', 'n', '0'):
        return False
    else:
        raise argparse.ArgumentTypeError('Boolean value expected.')

Hannes Diedrich's avatar
Hannes Diedrich committed
859

Hannes Diedrich's avatar
Hannes Diedrich committed
860
if __name__ == "__main__":
Hannes Diedrich's avatar
Hannes Diedrich committed
861

Hannes Diedrich's avatar
Hannes Diedrich committed
862
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Hannes Diedrich's avatar
Hannes Diedrich committed
863
864
    parser.add_argument("-o", "--out_dir", action="store", required=True, type=str,
                        help="output_directory")
865
    parser.add_argument("-r", "--out_prefix", action="store", required=False, type=str,
866
                        help="output_prefix for naming the output files, it should not contain '_'.")
Hannes Diedrich's avatar
Hannes Diedrich committed
867
    parser.add_argument("-m", "--out_mode", action="store", required=False, type=str, default="json",
Hannes Diedrich's avatar
Hannes Diedrich committed
868
                        help="output_mode, use 'json' for json-file,"
869
                             "'single' for single tiffs or 'stack' for band stack "
Niklas Bohn's avatar
Niklas Bohn committed
870
                             "'nc' for netcdf-file")
Hannes Diedrich's avatar
Hannes Diedrich committed
871
    parser.add_argument("-l", "--lat_ll", action="store", required=True, type=float,
Hannes Diedrich's avatar
Hannes Diedrich committed
872
                        help="latitude lower left corner")
Hannes Diedrich's avatar
Hannes Diedrich committed
873
    parser.add_argument("-k", "--lon_ll", action="store", required=True, type=float,
Hannes Diedrich's avatar
Hannes Diedrich committed
874
                        help="longitude lower left corner")
Hannes Diedrich's avatar
Hannes Diedrich committed
875
    parser.add_argument("-i", "--lat_ur", action="store", required=True, type=float,
Hannes Diedrich's avatar
Hannes Diedrich committed
876
                        help="latitude upper right corner")
Hannes Diedrich's avatar
Hannes Diedrich committed
877
    parser.add_argument("-j", "--lon_ur", action="store", required=True, type=float,
Hannes Diedrich's avatar
Hannes Diedrich committed
878
                        help="longitude upper right corner")
Hannes Diedrich's avatar
Hannes Diedrich committed
879
    parser.add_argument("-a", "--sensor", action="store", required=False, type=str, default="S2A",
Hannes Diedrich's avatar
Hannes Diedrich committed
880
                        help="sensor name (e.g. S2A) for all: S2all")
Hannes Diedrich's avatar
Hannes Diedrich committed
881
    parser.add_argument("-t", "--level", action="store", required=False, type=str, default="L2A",
882
                        help="processing level (e.g. L2A)")
Hannes Diedrich's avatar
Hannes Diedrich committed
883
    parser.add_argument("-v", "--version", action="store", required=False, type=str, default="0.12",
884
                        help="version of atmospheric correction (e.g. 0.10)")
885
    parser.add_argument("-b", "--bands", action="store", required=False, type=str, default="",
Hannes Diedrich's avatar
Hannes Diedrich committed
886
                        help="list of Bands (e.g. -b B02_B03")
Hannes Diedrich's avatar
Hannes Diedrich committed
887
    parser.add_argument("-s", "--start_date", action="store", required=True, type=str,
Hannes Diedrich's avatar
Hannes Diedrich committed
888
                        help="Startdate e.g. 20160701")
Hannes Diedrich's avatar
Hannes Diedrich committed
889
    parser.add_argument("-e", "--end_date", action="store", required=True, type=str,
Hannes Diedrich's avatar
Hannes Diedrich committed
890
                        help="Enddate e.g. 20160701")
Hannes Diedrich's avatar
Hannes Diedrich committed
891
    parser.add_argument("-c", "--coreg", action="store", required=False, type=str2bool, default=False,
892
                        help="get data with corrected pixel shifts (True or False)")
Hannes Diedrich's avatar
Hannes Diedrich committed
893
    parser.add_argument("-z", "--max_cloudy", action="store", required=False, type=str, default="0.5",
894
                        help="maximal percentage of cloudyness of requested scene (e.g. 0.5)")
895
    parser.add_argument("-f", "--minimum_fill", action="store", required=False, type=str, default="0.12",
896
                        help="minimal percentage of data in scene (e.g. 1.0)")
897
    parser.add_argument("-d", "--utm_zone", action="store", required=False, type=str, default="",
898
                        help="only return data for specific utm-zone (MGRS-tile, e.g. 33UUV)")
899
    parser.add_argument("-g", "--stack_resolution", action="store", required=False, type=str, default="10",
900
                        help="spatial sampling [in meters] of the output stack file, choose from [10,20,60])")
901
902
    parser.add_argument("-n", "--rgb_extension", action="store", required=False, type=str, default="jpg",
                        help="file extension of rgb files e.g.[jpg, png], default: jpg")
Hannes Diedrich's avatar
Hannes Diedrich committed
903
    parser.add_argument("-q", "--rgb_bands_selection", action="store", required=False, type=str, default="realistic",
904
                        help="band selection for rgb production, choose from: [realistic, nice_looking, vegetation, "
905
                             "healthy_vegetation_urban, snow, agriculture]")
906
907
908
909
    parser.add_argument("-w", "--merge_tifs", action="store", required=False, type=str2bool, default=False,
                        help="Merge tifs and RGBs if area in two or more MGRS tiles per time step (True or False).")
    parser.add_argument("-x", "--merge_tile", action="store", required=False, type=str, default=None,
                        help="Choose MGRS tile into which the merge of files is performed (e.g. 33UUV).")
910

Hannes Diedrich's avatar
Hannes Diedrich committed
911
912
913
914
    args = parser.parse_args()

    suffix = "coreg" if (args.coreg is True) else ""

915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
    client(outpath=args.out_dir,
           out_prefix=args.out_prefix,
           out_mode=args.out_mode,
           geo_ll=(args.lon_ll, args.lat_ll),
           geo_ur=(args.lon_ur, args.lat_ur),
           sensor=args.sensor,
           level=args.level,
           version=args.version,
           bands=args.bands,
           max_cloudy=args.max_cloudy,
           suffix=suffix,
           start_date=args.start_date,
           end_date=args.end_date,
           minimum_fill=args.minimum_fill,
           only_tile=args.utm_zone,
930
           stack_resolution=args.stack_resolution,
Hannes Diedrich's avatar
Hannes Diedrich committed
931
           rgb_extension=args.rgb_extension,
932
933
934
           rgb_bands_selection=args.rgb_bands_selection,
           merge_tifs=args.merge_tifs,
           merge_tile=args.merge_tile)