diff --git a/make/createtestdata.py b/make/createtestdata.py
new file mode 100644
index 0000000000000000000000000000000000000000..e86e535065da29ebeac95be2a62a2e82f5dcbe82
--- /dev/null
+++ b/make/createtestdata.py
@@ -0,0 +1,284 @@
+import sys, re, os, collections
+from os.path import join as jp
+
+
+from osgeo import gdal, ogr
+from qgis import *
+from qgis.core import *
+from qgis.gui import *
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+
+from timeseriesviewer import *
+from timeseriesviewer.utils import *
+from timeseriesviewer import file_search
+from timeseriesviewer.timeseries import *
+from timeseriesviewer.virtualrasters import VirtualBandInputSource, VirtualBand, VirtualRasterBuilder
+
+def groupCBERS(dirIn, dirOut, pattern='CBERS*.tif'):
+    files = file_search(dirIn, pattern, recursive=True)
+
+    if not os.path.exists(dirOut):
+        os.mkdir(dirOut)
+
+    CONTAINERS = dict()
+    for file in files:
+        dn = os.path.dirname(file)
+        bn = os.path.basename(file)
+        #basenames like CBERS_4_MUX_20150603_167_107_L4_BAND5_GRID_SURFACE.tif
+        splitted = bn.split('_')
+        id = '_'.join(splitted[:4])
+        bandName = splitted[7]
+
+        if id not in CONTAINERS.keys():
+            CONTAINERS[id] = dict()
+
+        bandSources = CONTAINERS[id]
+        if bandName not in bandSources.keys():
+            bandSources[bandName] = list()
+        bandSources[bandName].append(file)
+
+    #mosaic all scenes of same date
+    # and stack all bands related to the same channel
+    for id, bandSources in CONTAINERS.items():
+
+        pathVRT = id + '.vrt'
+        pathVRT = os.path.join(dirOut, pathVRT)
+        V = VirtualRasterBuilder()
+
+        #vrt = createVirtualBandStack(bandSources, pathVRT)
+        #add bands in sorted order
+        for bandName in sorted(bandSources.keys()):
+            vBandSources = bandSources[bandName]
+            VB = VirtualBand(name=bandName)
+            for path in vBandSources:
+                VB.addSourceBand(path, 0) #it's always one band only
+
+            V.addVirtualBand(VB)
+        #print(V)
+        dsVRT = V.saveVRT(pathVRT)
+        assert isinstance(dsVRT, gdal.Dataset)
+        #add center wavelength information to ENVI domain
+        import math
+        #MUXCAM bands
+        #see http://www.cbers.inpe.br/ingles/satellites/cameras_cbers3_4.php
+        cwl = [0.5*(0.45+0.52),
+               0.5*(0.52 + 0.59),
+               0.5*(0.63 + 0.69),
+               0.5*(0.77 + 0.89)]
+        #https://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html
+        dsVRT.SetMetadataItem('wavelength units','Micrometers', 'ENVI')
+        dsVRT.SetMetadataItem('wavelength', '{{{}}}'.format(','.join([str(w) for w in cwl])), 'ENVI')
+        dsVRT.SetMetadataItem('sensor type', 'CBERS', 'ENVI')
+        for i, cw in enumerate(cwl):
+            band = dsVRT.GetRasterBand(i+1)
+            assert isinstance(band, gdal.Band)
+            band.SetMetadataItem('wavelength',str(cw), 'ENVI')
+        dsVRT = None
+
+
+def groupLandsat(dirIn, dirOut, pattern='L*_sr_band*.img'):
+    files = file_search(dirIn, pattern, recursive=True)
+
+    if not os.path.exists(dirOut):
+        os.mkdir(dirOut)
+
+    CONTAINERS = dict()
+    for file in files:
+        dn = os.path.dirname(file)
+        bn = os.path.basename(file)
+        #basenames like LC82270652013140LGN01_sr_band2.img
+        splitted = re.split('[_\.]', bn)
+        id = splitted[0]
+        bandName = splitted[2]
+
+        if id not in CONTAINERS.keys():
+            CONTAINERS[id] = dict()
+
+        bandSources = CONTAINERS[id]
+        if bandName not in bandSources.keys():
+            bandSources[bandName] = list()
+        bandSources[bandName].append(file)
+
+    #mosaic all scenes of same date
+    # and stack all bands related to the same channel
+    for id, bandSources in CONTAINERS.items():
+
+        pathVRT = id + '.vrt'
+        pathVRT = os.path.join(dirOut, pathVRT)
+        V = VirtualRasterBuilder()
+
+        #vrt = createVirtualBandStack(bandSources, pathVRT)
+        #add bands in sorted order
+        for bandName in sorted(bandSources.keys()):
+            vBandSources = bandSources[bandName]
+            VB = VirtualBand(name=bandName)
+            for path in vBandSources:
+                VB.addSourceBand(path, 0) #it's always one band only
+
+            V.addVirtualBand(VB)
+        #print(V)
+        dsVRT = V.saveVRT(pathVRT)
+        assert isinstance(dsVRT, gdal.Dataset)
+        #add center wavelength information to ENVI domain
+        import math
+        #MUXCAM bands
+        #see http://www.cbers.inpe.br/ingles/satellites/cameras_cbers3_4.php
+        if re.search('LC8', id):
+            cwl = [0.5*(0.433 + 0.453),
+                   0.5*(0.450 + 0.515),
+                   0.5*(0.525 + 0.600),
+                   0.5*(0.630 + 0.680),
+                   0.5 * (0.845 + 0.885),
+                   0.5 * (1.560 + 1.660),
+                   0.5 * (2.100 + 2.300)]
+        else:
+            raise NotImplementedError()
+
+        #https://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html
+        dsVRT.SetMetadataItem('wavelength units','Micrometers', 'ENVI')
+        dsVRT.SetMetadataItem('wavelength', '{{{}}}'.format(','.join([str(w) for w in cwl])), 'ENVI')
+        dsVRT.SetMetadataItem('sensor type', 'Landsat-8 OLI', 'ENVI')
+        from timeseriesviewer.dateparser import datetime64FromYYYYDOY
+        dt = datetime64FromYYYYDOY(id[9:16])
+        assert dt > np.datetime64('1900-01-01')
+        assert dt < np.datetime64('2999-12-31')
+        dsVRT.SetMetadataItem('acquisition time', str(dt), 'ENVI')
+        for i, cw in enumerate(cwl):
+            band = dsVRT.GetRasterBand(i+1)
+            assert isinstance(band, gdal.Band)
+            band.SetMetadataItem('wavelength',str(cw), 'ENVI')
+        dsVRT = None
+
+
+def groupRapidEyeTiles(dirIn, dirOut):
+    """
+
+    :param dirIn:
+    :param dirOut:
+    :return:
+    """
+
+    files = file_search(dirIn, '*_RE*_3A_*2.tif', recursive=True)
+
+    if not os.path.exists(dirOut):
+        os.mkdir(dirOut)
+
+    sources = dict()
+    for file in files:
+        if not file.endswith('.tif'):
+            continue
+        dn = os.path.dirname(file)
+        bn = os.path.basename(file)
+        print(bn)
+        id, date, sensor, product, _ = tuple(bn.split('_'))
+
+        if not date in sources.keys():
+            sources[date] = []
+        sources[date].append(file)
+
+    from timeseriesviewer.virtualrasters import createVirtualBandMosaic
+    for date, files in sources.items():
+        pathVRT = os.path.join(dirOut, 're_{}.vrt'.format(date))
+
+
+        dsVRT = createVirtualBandMosaic(files, pathVRT)
+        cwl = [0.5 * (440 + 510),
+               0.5 * (520 + 590),
+               0.5 * (630 + 685),
+               0.5 * (690 + 730),
+               0.5 * (760 + 850)]
+
+        dsVRT.SetMetadataItem('wavelength units', 'Nanometers', 'ENVI')
+        dsVRT.SetMetadataItem('wavelength', '{{{}}}'.format(','.join([str(w) for w in cwl])), 'ENVI')
+        dsVRT.SetMetadataItem('sensor type', 'RapidEye', 'ENVI')
+        dsVRT = createVirtualBandMosaic(files, pathVRT)
+
+
+def copyMetadataDomains(dsSrc, dsDst, domains=['ENVI']):
+    """
+    Updates metadata.
+    :param dsSrc:
+    :param dsDst:
+    :param domains:
+    :return:
+    """
+    assert isinstance(dsSrc, gdal.Dataset)
+    assert isinstance(dsDst, gdal.Dataset)
+
+    for domain in domains:
+        mdDst = dsDst.GetMetadata(domain)
+        mdDst.update(dsSrc.GetMetadata(domain))
+        dsDst.SetMetadata(mdDst, domain)
+        for i in range(min([dsSrc.RasterCount, dsDst.RasterCount])):
+            bandSrc = dsSrc.GetRasterBand(i+1)
+            bandDst = dsDst.GetRasterBand(i+1)
+            mdDst = bandDst.GetMetadata(domain)
+            mdDst.update(bandSrc.GetMetadata(domain))
+            bandDst.SetMetadata(mdDst, domain)
+
+def addPyramids(dir, pattern, levels=None):
+
+    for file in file_search(dir, pattern):
+        ds = gdal.Open(file)
+        assert isinstance(ds, gdal.Dataset)
+        nb = ds.RasterCount
+        #BuildOverviews(Dataset self, char const * resampling="NEAREST", int overviewlist=0,
+        # GDALProgressFunc callback=0,
+        # void * callback_data=None) -> int
+        res = ds.BuildOverviews('NEAREST', [2,4,8])
+
+
+
+
+def vrt2Binary(dirVRTs, dirBins, drvNameDst='GTiff', recursive=False, overwrite=True):
+    pathVRTs = file_search(dirVRTs, '*.vrt', recursive=recursive)
+    if not os.path.isdir(dirBins):
+        os.mkdir(dirBins)
+    drvDst = gdal.GetDriverByName(drvNameDst)
+    assert isinstance(drvDst, gdal.Driver)
+    for pathVRT in pathVRTs:
+        bn = os.path.basename(pathVRT)
+        bn = os.path.splitext(bn)[0]
+        pathDst = os.path.join(dirBins, '{}.{}'.format(bn, drvDst.GetMetadataItem('DMD_EXTENSION')))
+        dsSrc = gdal.Open(pathVRT)
+        assert isinstance(dsSrc, gdal.Dataset)
+        if overwrite or not os.path.exists(pathDst):
+            options = gdal.TranslateOptions(format=drvDst.ShortName)
+            print('Write {}...'.format(pathDst))
+            dsDst = gdal.Translate(pathDst, dsSrc, options=options)
+            assert isinstance(dsDst, gdal.Dataset)
+            copyMetadataDomains(dsSrc, dsDst, ['ENVI'])
+
+
+
+def testdataMultitemp2017():
+
+    dirSrcCBERS = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\CBERS\Data'
+    dirVRTCBERS = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\CBERS'
+    dirSrcL8 = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\Landsat\L1T'
+    dirVRTL8 = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\Landsat'
+    dirSrcRE = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\RapidEye\3A'
+    dirVRTRE = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\RapidEye'
+
+    if False:
+        groupCBERS(dirSrcCBERS, dirVRTCBERS, '*CBERS*167_108_*.tif')
+
+    if False:
+        groupLandsat(dirSrcL8, dirVRTL8)
+
+    if True:
+        #groupRapidEyeTiles(dirSrcRE, dirVRTRE)
+        addPyramids(dirVRTRE, '*.vrt', levels=[2,4,8])
+
+    if True:
+        dirBinL8 = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\Landsat\L1TS'
+        gdal.SetCacheMax(100*2*20)
+        vrt2Binary(dirVRTL8, dirBinL8, overwrite=False)
+
+
+if __name__ == '__main__':
+    testdataMultitemp2017()
+
+
+    print('done')
\ No newline at end of file
diff --git a/timeseriesviewer/__init__.py b/timeseriesviewer/__init__.py
index 918ea9a039450c7b74f36382d3e32e7fdbc9d630..8c33c18dc326fe683ac7de45d93d4b647bcb86ec 100644
--- a/timeseriesviewer/__init__.py
+++ b/timeseriesviewer/__init__.py
@@ -7,6 +7,8 @@ REPOSITORY = 'https://bitbucket.org/jakimowb/hub-timeseriesviewer.git'
 
 import os, sys, fnmatch, site
 import six, logging
+from qgis.core import *
+from qgis.gui import *
 
 logger = logging.getLogger(__name__)
 
diff --git a/timeseriesviewer/dateparser.py b/timeseriesviewer/dateparser.py
index 3f9a3620800eb3ead1ef1eb34c65ecdc8e2326f6..9784cde076296cac5477ac65f40adbe70e6e0b56 100644
--- a/timeseriesviewer/dateparser.py
+++ b/timeseriesviewer/dateparser.py
@@ -8,13 +8,16 @@ logger = logging.getLogger(__file__)
 
 #thanks user "funkwurm" in
 #http://stackoverflow.com/questions/28020805/regex-validate-correct-iso8601-date-string-with-time
-regISODate = re.compile(r'(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)')
-
+regISODate1 = re.compile(r'(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)')
+regISODate3 = re.compile(r'([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
+regISODate2 = re.compile(r'(19|20|21\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
+#regISODate2 = re.compile(r'([12]\d{3}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?')
 #https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html
 
-regYYYYMMDD = re.compile(r'(?P<year>[0-9]{4})(?P<hyphen>-?)(?P<month>1[0-2]|0[1-9])(?P=hyphen)(?P<day>3[01]|0[1-9]|[12][0-9])')
+regYYYYMMDD = re.compile(r'(?P<year>(19|20)\d\d)(?P<hyphen>-?)(?P<month>1[0-2]|0[1-9])(?P=hyphen)(?P<day>3[01]|0[1-9]|[12][0-9])')
+regMissingHypen = re.compile('^\d{8}')
 regYYYYMM = re.compile(r'([0-9]{4})-(1[0-2]|0[1-9])')
-regYYYYDOY = re.compile(r'(?P<year>[0-9]{4})-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])')
+regYYYYDOY = re.compile(r'(?P<year>(19|20)\d\d)-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])')
 
 def matchOrNone(regex, text):
     match = regex.search(text)
@@ -23,23 +26,42 @@ def matchOrNone(regex, text):
     else:
         return None
 
-def extractDateTimeGroup(regex, text):
+def extractDateTimeGroup(text):
+    match = regISODate1.search(text)
+    if match:
+        matchedText = match.group()
+        if regMissingHypen.search(matchedText):
+            matchedText = '{}-{}-{}'.format(matchedText[0:4],matchedText[4:6],matchedText[6:])
+        return np.datetime64(matchedText)
 
-    match = regex.search(text)
-    if match is not None:
+    match = regYYYYMMDD.search(text)
+    if match:
+        return datetime64FromYYYYMMDD(match.group())
+
+    match = regYYYYDOY.search(text)
+    if match:
+        return datetime64FromYYYYDOY(match.group())
+
+    match = regYYYYMM.search(text)
+    if match:
         return np.datetime64(match.group())
-    else:
-        return None
 
+    return None
+
+def datetime64FromYYYYMMDD(yyyymmdd):
+    if re.search('^\d{8}$', yyyymmdd):
+        #insert hyphens
+        yyyymmdd = '{}-{}-{}'.format(yyyymmdd[0:4],yyyymmdd[4:6],yyyymmdd[6:8])
+    return np.datetime64(yyyymmdd)
 
-def getDateTime64FromYYYYDOY(yyyydoy):
-    return getDatetime64FromDOY(yyyydoy[0:4], yyyydoy[4:7])
+def datetime64FromYYYYDOY(yyyydoy):
+    return datetime64FromDOY(yyyydoy[0:4], yyyydoy[4:7])
 
-def getDOYfromDatetime64(dt):
+def DOYfromDatetime64(dt):
 
     return (dt.astype('datetime64[D]') - dt.astype('datetime64[Y]')).astype(int)+1
 
-def getDatetime64FromDOY(year, doy):
+def datetime64FromDOY(year, doy):
         if type(year) is str:
             year = int(year)
         if type(doy) is str:
@@ -87,12 +109,12 @@ class ImageDateReaderDefault(ImageDateReader):
 
         # search for ISO date in basename
         # search in basename
-        for reg in [regISODate, regYYYYMMDD, regYYYYDOY, regYYYYMM]:
-            dtg = extractDateTimeGroup(reg, self.baseName)
-            if dtg: return dtg
-        for reg in [regISODate, regYYYYMMDD, regYYYYDOY, regYYYYMM]:
-            dtg = extractDateTimeGroup(reg, self.dirName)
-            if dtg: return dtg
+        dtg = extractDateTimeGroup(self.baseName)
+        if dtg:
+            return dtg
+        dtg = extractDateTimeGroup(self.dirName)
+        if dtg:
+            return dtg
         return None
 
 class ImageDateReaderPLEIADES(ImageDateReader):
@@ -140,7 +162,7 @@ class ImageDateParserLandsat(ImageDateReader):
         #search for LandsatSceneID (old) and Landsat Product IDs (new)
         sceneID = matchOrNone(ImageDateParserLandsat.regLandsatSceneID, self.baseName)
         if sceneID:
-            return getDateTime64FromYYYYDOY(sceneID[9:16])
+            return datetime64FromYYYYDOY(sceneID[9:16])
 
         productID = matchOrNone(ImageDateParserLandsat.regLandsatProductID, self.baseName)
         if productID:
@@ -166,4 +188,6 @@ if __name__ == '__main__':
     p = r'E:\_EnMAP\temp\temp_bj\landsat\37S\EB\LE71720342015009SG100\LE71720342015009SG100_sr.tif'
     p = r'D:\Repositories\QGIS_Plugins\hub-timeseriesviewer\example\Images\2012-04-07_LE72270652012098EDC00_BOA.bsq'
     ds = gdal.Open(p)
+
+    print(datetime64FromYYYYMMDD('20141212'))
     print(parseDateFromDataSet(ds))
\ No newline at end of file
diff --git a/timeseriesviewer/main.py b/timeseriesviewer/main.py
index 99275de378612f3706f447f0d0fc280198d704d4..bf1cdf540787f5dbbfa20243aab6b97fb5bab237 100644
--- a/timeseriesviewer/main.py
+++ b/timeseriesviewer/main.py
@@ -156,12 +156,16 @@ class QgisTsvBridge(QObject):
         self.syncBlocked = False
 
         from main import TimeSeriesViewerUI
+        from timeseriesviewer.ui.docks import RenderingDockUI
         assert isinstance(self.ui, TimeSeriesViewerUI)
+        assert isinstance(self.ui.dockRendering, RenderingDockUI)
+
+        self.ui.dockRendering.sigQgisInteractionRequest.connect(self.onQgisInteractionRequest)
 
         self.cbQgsVectorLayer = self.ui.dockRendering.cbQgsVectorLayer
         self.gbQgsVectorLayer = self.ui.dockRendering.gbQgsVectorLayer
-        self.cbQgsVectorLayer.setEnabled(True)
-        self.gbQgsVectorLayer.setEnabled(True)
+        #self.cbQgsVectorLayer.setEnabled(True)
+        #self.gbQgsVectorLayer.setEnabled(True)
         self.qgsMapCanvas = self.iface.mapCanvas()
         assert isinstance(self.qgsMapCanvas, QgsMapCanvas)
 
@@ -178,6 +182,47 @@ class QgisTsvBridge(QObject):
         self.cbQgsVectorLayer.layerChanged.connect(self.onQgsVectorLayerChanged)
         self.onQgsVectorLayerChanged(None)
 
+    def onQgisInteractionRequest(self, request):
+        assert isinstance(self.qgsMapCanvas, QgsMapCanvas)
+        extQgs = SpatialExtent.fromMapCanvas(self.qgsMapCanvas)
+
+        assert isinstance(self.TSV, TimeSeriesViewer)
+        extTsv = self.TSV.spatialTemporalVis.spatialExtent()
+
+        assert request in ['tsvCenter2qgsCenter',
+                            'tsvExtent2qgsExtent',
+                            'qgisCenter2tsvCenter',
+                            'qgisExtent2tsvExtent']
+
+        if request == 'tsvCenter2qgsCenter':
+            center = SpatialPoint.fromSpatialExtent(extTsv)
+            center = center.toCrs(extQgs.crs())
+            if center:
+                self.qgsMapCanvas.setCenter(center)
+                self.qgsMapCanvas.refresh()
+
+        if request == 'qgisCenter2tsvCenter':
+            center = SpatialPoint.fromSpatialExtent(extQgs)
+            center = center.toCrs(extTsv.crs())
+            if center:
+                self.TSV.spatialTemporalVis.setSpatialCenter(center)
+
+
+        if request == 'tsvExtent2qgsExtent':
+            extent = extTsv.toCrs(extQgs.crs())
+            if extent:
+                self.qgsMapCanvas.setExtent(extent)
+                self.qgsMapCanvas.refresh()
+
+        if request == 'qgisExtent2tsvExtent':
+            extent = extQgs.toCrs(extTsv.crs())
+            if extent:
+                self.TSV.spatialTemporalVis.setSpatialExtent(extent)
+
+
+
+
+
     def syncTsvWithQgs(self, *args):
         if self.syncBlocked:
             return
@@ -340,8 +385,6 @@ class TimeSeriesViewerUI(QMainWindow,
         #todo: move to QGS_TSV_Bridge
         self.dockRendering.cbQgsVectorLayer.setFilters(QgsMapLayerProxyModel.VectorLayer)
 
-        #define subset-size behaviour
-
         self.restoreSettings()
 
 
@@ -357,8 +400,8 @@ class TimeSeriesViewerUI(QMainWindow,
         from timeseriesviewer import QGIS_TSV_BRIDGE
         from timeseriesviewer.main import QgisTsvBridge
         b = isinstance(QGIS_TSV_BRIDGE, QgisTsvBridge)
-        self.dockRendering.gbSyncQgs.setEnabled(b)
-        self.dockRendering.gbQgsVectorLayer.setEnabled(b)
+        self.dockRendering.enableQgisSyncronization(b)
+        #self.dockRendering.gbQgsVectorLayer.setEnabled(b)
 
     def _blockSignals(self, widgets, block=True):
         states = dict()
@@ -691,8 +734,14 @@ class TimeSeriesViewer:
 
     def addTimeSeriesImages(self, files=None):
         if files is None:
-            files = QFileDialog.getOpenFileNames()
-
+            s = QSettings('HU-Berlin','HUB TSV')
+            defDir = s.value('DIR_FILESEARCH')
+            files = QFileDialog.getOpenFileNames(directory=defDir)
+
+            if len(files) > 0 and os.path.exists(files[0]):
+                dn = os.path.dirname(files[0])
+                s.setValue('DIR_FILESEARCH', dn)
+            s = ""
             #collect sublayers, if existing
 
 
diff --git a/timeseriesviewer/mapcanvas.py b/timeseriesviewer/mapcanvas.py
index bdf89e8809bae5add9c479782df38337e93270d6..619574b02c83886d98dbe8f204473c9551b2981c 100644
--- a/timeseriesviewer/mapcanvas.py
+++ b/timeseriesviewer/mapcanvas.py
@@ -8,14 +8,13 @@ from qgis.gui import *
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 from timeseriesviewer import SETTINGS
-from timeseriesviewer.timeseries import TimeSeriesDatum
-from mapvisualization import MapView
 from timeseriesviewer.utils import *
 from timeseriesviewer.ui.widgets import TsvScrollArea
 
+class MapCanvas(QgsMapCanvas):
+
 
 
-class MapCanvas(QgsMapCanvas):
     from timeseriesviewer.main import SpatialExtent, SpatialPoint
     saveFileDirectories = dict()
     sigShowProfiles = pyqtSignal(SpatialPoint)
@@ -34,6 +33,7 @@ class MapCanvas(QgsMapCanvas):
         self.setCanvasColor(SETTINGS.value('CANVAS_BACKGROUND_COLOR', QColor(0, 0, 0)))
         self.setContextMenuPolicy(Qt.DefaultContextMenu)
 
+        #refreshTimer.timeout.connect(self.onTimerRefresh)
         self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent()))
 
 
@@ -51,7 +51,7 @@ class MapCanvas(QgsMapCanvas):
 
 
 
-        self.renderMe = False
+        self.mDataRefreshRequired = False
 
 
 
@@ -78,7 +78,7 @@ class MapCanvas(QgsMapCanvas):
         assert isinstance(crs, QgsCoordinateReferenceSystem)
         if self.crs() != crs:
             self.setDestinationCrs(crs)
-
+            self.mDataRefreshRequired = True
     def crs(self):
         return self.mapSettings().destinationCrs()
 
@@ -122,16 +122,22 @@ class MapCanvas(QgsMapCanvas):
 
     def setLayers(self, mapLayers):
         reg = QgsMapLayerRegistry.instance()
-        for l in mapLayers:
-            reg.addMapLayer(l, False)
+        reg.addMapLayers(mapLayers, False)
+
         self.mLayers = mapLayers[:]
+        self.mDataRefreshRequired = True
         super(MapCanvas, self).setLayerSet([QgsMapCanvasLayer(l) for l in self.mLayers])
-        s = ""
+
+        #self.refresh()
+
     def refresh(self):
-        self.setLayers(self.mapLayersToRender())
-        self.setRenderMe()
-        super(MapCanvas, self).refresh()
-        self.refreshAllLayers()
+        #low-level, only performed if MapCanvas is visible
+        self.checkRenderFlag()
+        if self.renderFlag():
+            print('REFRESH {}'.format(self.objectName()))
+            self.setLayers(self.mapLayersToRender())
+            #super(MapCanvas, self).refresh()
+            self.refreshAllLayers()
 
 
     def setCrosshairStyle(self,crosshairStyle):
@@ -146,14 +152,21 @@ class MapCanvas(QgsMapCanvas):
     def setShowCrosshair(self,b):
         self.crosshairItem.setShow(b)
 
-    def setRenderMe(self):
-        oldFlag = self.renderFlag()
+    def onTimerRefresh(self):
+        self.checkRenderFlag()
+        self.refresh()
 
-        newFlag = self.visibleRegion().boundingRect().isValid() \
+    def checkRenderFlag(self):
+        isVisible = self.visibleRegion().boundingRect().isValid() \
                   and self.isVisible()
-                  #and self.tsdView.timeSeriesDatum.isVisible()
-        if oldFlag != newFlag:
-            self.setRenderFlag(newFlag)
+        if not isVisible:
+            self.setRenderFlag(False)
+        else:
+            isRequired = self.renderFlag() or self.mDataRefreshRequired and not self.signalsBlocked()
+            self.setRenderFlag(isRequired)
+
+    def layerPaths(self):
+        return [str(l.source()) for l in self.layers()]
 
     def pixmap(self):
         """
@@ -195,7 +208,7 @@ class MapCanvas(QgsMapCanvas):
         action = m.addAction('Map to Clipboard')
         action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap()))
         action = m.addAction('Image Path')
-        action.triggered.connect(lambda: QApplication.clipboard().setText(self.tsdView.TSD.pathImg))
+        action.triggered.connect(lambda: QApplication.clipboard().setText('\n'.join(self.layerPaths())))
         action = m.addAction('Image Style')
         #action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.tsdView.TSD.pathImg))
 
@@ -234,7 +247,7 @@ class MapCanvas(QgsMapCanvas):
     def stretchToCurrentExtent(self):
 
         se = self.spatialExtent()
-
+        ceAlg = QgsContrastEnhancement.StretchToMinimumMaximum
         for l in self.layers():
             if isinstance(l, QgsRasterLayer):
                 r = l.renderer()
@@ -248,7 +261,10 @@ class MapCanvas(QgsMapCanvas):
 
                     def getCE(band, ce):
                         stats = dp.bandStatistics(band, QgsRasterBandStats.All, extent, 500)
-                        ce = QgsContrastEnhancement(ce)
+                        if stats.maximumValue is None:
+                            s = ""
+                        ce = QgsContrastEnhancement(dp.dataType(band))
+                        ce.setContrastEnhancementAlgorithm(ceAlg)
                         ce.setMinimumValue(stats.minimumValue)
                         ce.setMaximumValue(stats.maximumValue)
                         return ce
@@ -278,7 +294,8 @@ class MapCanvas(QgsMapCanvas):
         elif key in self.mMapTools.keys():
             super(MapCanvas, self).setMapTool(self.mMapTools[key])
         else:
-            logger.error('unknown map tool key "{}"'.format(key))
+            s = ""
+            #logger.error('unknown map tool key "{}"'.format(key))
 
     def saveMapImageDialog(self, fileType):
         lastDir = SETTINGS.value('CANVAS_SAVE_IMG_DIR', os.path.expanduser('~'))
@@ -290,6 +307,8 @@ class MapCanvas(QgsMapCanvas):
             SETTINGS.setValue('CANVAS_SAVE_IMG_DIR', os.path.dirname(path))
 
 
+
+
     def setRenderer(self, renderer):
 
         #lyrs = [l for l in self.mapLayersToRender() if str(l.source()) == targetLayerUri]
@@ -318,8 +337,7 @@ class MapCanvas(QgsMapCanvas):
             for lyr in lyrs:
                 lyr.setRenderer(renderer)
 
-        if not self.signalsBlocked():
-            self.refresh()
+        self.refresh()
 
     def setSpatialExtent(self, spatialExtent):
         assert isinstance(spatialExtent, SpatialExtent)
@@ -327,9 +345,7 @@ class MapCanvas(QgsMapCanvas):
             spatialExtent = spatialExtent.toCrs(self.crs())
             if spatialExtent:
                 self.setExtent(spatialExtent)
-
-            if not self.signalsBlocked():
-                self.refresh()
+                self.mDataRefreshRequired = True
 
 
     def spatialExtent(self):
diff --git a/timeseriesviewer/maptools.py b/timeseriesviewer/maptools.py
index 9bfead0c5b959ca78bbeb2723442aceb4b1b2d53..606d215b4b7c7c7acb58fa7be8b1b47773f74877 100644
--- a/timeseriesviewer/maptools.py
+++ b/timeseriesviewer/maptools.py
@@ -26,7 +26,7 @@ class CursorLocationMapTool(QgsMapToolEmitPoint):
         self.rubberband = QgsRubberBand(self.canvas, QGis.Polygon)
 
         color = QColor('red')
-
+        self.mButtons = [Qt.LeftButton]
         self.rubberband.setLineStyle(Qt.SolidLine)
         self.rubberband.setColor(color)
         self.rubberband.setWidth(2)
@@ -38,9 +38,15 @@ class CursorLocationMapTool(QgsMapToolEmitPoint):
         self.marker.setIconSize(5)
         self.marker.setIconType(QgsVertexMarker.ICON_CROSS)  # or ICON_CROSS, ICON_X
 
+    def setMouseButtons(self, listOfButtons):
+        assert isinstance(listOfButtons)
+        self.mButtons = listOfButtons
+
     def canvasPressEvent(self, e):
-        geoPoint = self.toMapCoordinates(e.pos())
-        self.marker.setCenter(geoPoint)
+        assert isinstance(e, QgsMapMouseEvent)
+        if e.button() in self.mButtons:
+            geoPoint = self.toMapCoordinates(e.pos())
+            self.marker.setCenter(geoPoint)
         #self.marker.show()
 
     def setStyle(self, color=None, brushStyle=None, fillColor=None, lineStyle=None):
@@ -53,28 +59,28 @@ class CursorLocationMapTool(QgsMapToolEmitPoint):
         if lineStyle:
             self.rubberband.setLineStyle(lineStyle)
     def canvasReleaseEvent(self, e):
-
-
-        pixelPoint = e.pixelPoint()
-
-        crs = self.canvas.mapSettings().destinationCrs()
-        self.marker.hide()
-        geoPoint = self.toMapCoordinates(pixelPoint)
-        if self.mShowCrosshair:
-            #show a temporary crosshair
-            ext = SpatialExtent.fromMapCanvas(self.canvas)
-            cen = geoPoint
-            geom = QgsGeometry()
-            geom.addPart([QgsPoint(ext.upperLeftPt().x(),cen.y()), QgsPoint(ext.lowerRightPt().x(), cen.y())],
-                          QGis.Line)
-            geom.addPart([QgsPoint(cen.x(), ext.upperLeftPt().y()), QgsPoint(cen.x(), ext.lowerRightPt().y())],
-                          QGis.Line)
-            self.rubberband.addGeometry(geom, None)
-            self.rubberband.show()
-            #remove crosshair after 0.25 sec
-            QTimer.singleShot(250, self.hideRubberband)
-
-        self.sigLocationRequest.emit(SpatialPoint(crs, geoPoint))
+        if e.button() in self.mButtons:
+
+            pixelPoint = e.pixelPoint()
+
+            crs = self.canvas.mapSettings().destinationCrs()
+            self.marker.hide()
+            geoPoint = self.toMapCoordinates(pixelPoint)
+            if self.mShowCrosshair:
+                #show a temporary crosshair
+                ext = SpatialExtent.fromMapCanvas(self.canvas)
+                cen = geoPoint
+                geom = QgsGeometry()
+                geom.addPart([QgsPoint(ext.upperLeftPt().x(),cen.y()), QgsPoint(ext.lowerRightPt().x(), cen.y())],
+                              QGis.Line)
+                geom.addPart([QgsPoint(cen.x(), ext.upperLeftPt().y()), QgsPoint(cen.x(), ext.lowerRightPt().y())],
+                              QGis.Line)
+                self.rubberband.addGeometry(geom, None)
+                self.rubberband.show()
+                #remove crosshair after 0.25 sec
+                QTimer.singleShot(250, self.hideRubberband)
+
+            self.sigLocationRequest.emit(SpatialPoint(crs, geoPoint))
 
     def hideRubberband(self):
         self.rubberband.reset()
diff --git a/timeseriesviewer/mapvisualization.py b/timeseriesviewer/mapvisualization.py
index 740dd074b90d2772f221e99fb1a5fadbcf46b056..119f55416ef976381fa00aeb4af143b905f8a4cc 100644
--- a/timeseriesviewer/mapvisualization.py
+++ b/timeseriesviewer/mapvisualization.py
@@ -12,7 +12,7 @@ from timeseriesviewer.main import TimeSeriesViewer
 from timeseriesviewer.timeseries import SensorInstrument, TimeSeriesDatum
 from timeseriesviewer.ui.docks import TsvDockWidgetBase, load
 from timeseriesviewer.ui.widgets import TsvMimeDataUtils, maxWidgetSizes
-
+from timeseriesviewer.mapcanvas import MapCanvas
 
 
 class MapView(QObject):
@@ -38,7 +38,7 @@ class MapView(QObject):
         self.ui = MapViewDefinitionUI(self, parent=parent)
         self.ui.create()
 
-        self.mMapCanvases = []
+        self.mMapCanvases = dict()
         self.setVisibility(True)
 
         self.vectorLayer = None
@@ -123,16 +123,19 @@ class MapView(QObject):
 
         #set basic settings
         sensorView = self.sensorViews[sensor]
+        assert isinstance(sensorView, MapViewSensorSettings)
+        sensorView.registerMapCanvas(mapCanvas)
+
+        #register signals sensor specific signals
         mapCanvas.setRenderer(sensorView.layerRenderer())
 
-        #register signals
-        sensorView.sigSensorRendererChanged.connect(mapCanvas.setRenderer)
+        #register non-sensor specific signals for this mpa view
         self.sigMapViewVisibility.connect(mapCanvas.refresh)
         self.sigCrosshairStyleChanged.connect(mapCanvas.setCrosshairStyle)
         self.sigShowCrosshair.connect(mapCanvas.setShowCrosshair)
         self.sigVectorLayerChanged.connect(mapCanvas.refresh)
         self.sigVectorVisibility.connect(mapCanvas.refresh)
-        self.mMapCanvases.append(mapCanvas)
+
 
 
 
@@ -148,9 +151,13 @@ class MapView(QObject):
 
         #w.showSensorName(False)
         self.sensorViews[sensor] = w
-        l = self.ui.sensorList
+        l = self.ui.sensorList.layout()
         i = l.count()
-        l.addWidget(w.ui)
+        lastWidgetIndex = 0
+        for i in range(l.count()):
+            if isinstance(l.itemAt(i), QWidget):
+                lastWidgetIndex = i
+        l.insertWidget(lastWidgetIndex, w.ui)
         self.ui.resize(self.ui.sizeHint())
 
     def getSensorWidget(self, sensor):
@@ -195,7 +202,7 @@ class MapViewSensorSettings(QObject):
     Describes the rendering of images of one Sensor
     """
 
-    sigSensorRendererChanged = pyqtSignal(QgsRasterRenderer)
+    #sigSensorRendererChanged = pyqtSignal(QgsRasterRenderer)
 
     def __init__(self, sensor, parent=None):
         """Constructor."""
@@ -208,7 +215,7 @@ class MapViewSensorSettings(QObject):
         self.ui.create()
         self.sensor.sigNameChanged.connect(self.onSensorNameChanged)
         self.onSensorNameChanged(self.sensor.name())
-
+        self.mMapCanvases = []
         self.ui.bandNames = sensor.bandNames
 
         self.multiBandMinValues = [self.ui.tbRedMin, self.ui.tbGreenMin, self.ui.tbBlueMin]
@@ -311,7 +318,7 @@ class MapViewSensorSettings(QObject):
         self.ui.actionSetSWIR.triggered.connect(lambda: self.setBandSelection('swIR'))
 
 
-        self.ui.actionApplyStyle.triggered.connect(lambda : self.sigSensorRendererChanged.emit(self.layerRenderer()))
+        self.ui.actionApplyStyle.triggered.connect(self.applyStyle)
         self.ui.actionCopyStyle.triggered.connect(lambda : QApplication.clipboard().setMimeData(self.mimeDataStyle()))
         self.ui.actionPasteStyle.triggered.connect(lambda : self.pasteStyleFromClipboard())
 
@@ -342,6 +349,13 @@ class MapViewSensorSettings(QObject):
         QApplication.clipboard().dataChanged.connect(self.onClipboardChange)
         self.onClipboardChange()
 
+    def registerMapCanvas(self, mapCanvas):
+
+        assert isinstance(mapCanvas, MapCanvas)
+        self.mMapCanvases.append(mapCanvas)
+        mapCanvas.sigChangeSVRequest.connect(self.onMapCanvasRendererChangeRequest)
+
+
     def onSensorNameChanged(self, newName):
         self.sensor.sigNameChanged.connect(self.ui.labelTitle.setText)
         self.ui.labelTitle.setText(self.sensor.name())
@@ -354,13 +368,19 @@ class MapViewSensorSettings(QObject):
             if renderer is not None:
                 self.setLayerRenderer(renderer)
 
-    def applyStyle(self):
-        self.sigSensorRendererChanged.emit(self.layerRenderer())
+    def applyStyle(self, *args):
+        #self.sigSensorRendererChanged.emit(self.layerRenderer())
+        r = self.layerRenderer()
+        for mapCanvas in self.mMapCanvases:
+            assert isinstance(mapCanvas, MapCanvas)
+            mapCanvas.setRenderer(r)
 
     def onClipboardChange(self):
         utils = TsvMimeDataUtils(QApplication.clipboard().mimeData())
         self.ui.btnPasteStyle.setEnabled(utils.hasRasterStyle())
 
+    def onMapCanvasRendererChangeRequest(self, mapCanvas, renderer):
+        self.setLayerRenderer(renderer)
 
     def setBandSelection(self, key):
 
@@ -407,7 +427,8 @@ class MapViewSensorSettings(QObject):
         self.ui.labelSummary.setText(text)
 
         if MapViewSensorSettings.SignalizeImmediately:
-            self.sigSensorRendererChanged.emit(self.layerRenderer())
+            #self.sigSensorRendererChanged.emit(self.layerRenderer())
+            self.applyStyle()
 
     def setLayerRenderer(self, renderer):
         ui = self.ui
@@ -426,13 +447,19 @@ class MapViewSensorSettings(QObject):
             for s in self.multiBandSliders:
                 s.blockSignals(False)
 
+
             ceRed = renderer.redContrastEnhancement()
             ceGreen = renderer.greenContrastEnhancement()
             ceBlue = renderer.blueContrastEnhancement()
 
+            if ceRed is None:
+                ceRed = ceGreen = ceBlue = QgsContrastEnhancement(self.sensor.bandDataType)
+                s = ""
             for i, ce in enumerate([ceRed, ceGreen, ceBlue]):
-                self.multiBandMinValues[i].setText(str(ce.minimumValue()))
-                self.multiBandMaxValues[i].setText(str(ce.maximumValue()))
+                vMin = ce.minimumValue()
+                vMax = ce.maximumValue()
+                self.multiBandMinValues[i].setText(str(vMin))
+                self.multiBandMaxValues[i].setText(str(vMax))
 
             idx = self.ceAlgs.values().index(ceRed.contrastEnhancementAlgorithm())
             ui.comboBoxContrastEnhancement.setCurrentIndex(idx)
@@ -456,7 +483,8 @@ class MapViewSensorSettings(QObject):
 
         self.updateUi()
         if updated and MapViewSensorSettings.SignalizeImmediately:
-            self.sigSensorRendererChanged.emit(renderer.clone())
+            #self.sigSensorRendererChanged.emit(renderer.clone())
+            self.applyStyle()
 
     def mimeDataStyle(self):
         r = self.layerRenderer()
@@ -603,16 +631,19 @@ class DatumView(QObject):
         self.adjustBaseMinSize()
 
     def refresh(self):
+
         if self.ui.isVisible():
             for c in self.mapCanvases.values():
                 if c.isVisible():
-                    c.refreshAllLayers()
+                    #c.refreshAllLayers()
+                    c.refresh()
 
     def insertMapView(self, mapView):
         assert isinstance(mapView, MapView)
         from timeseriesviewer.mapcanvas import MapCanvas
 
         mapCanvas = MapCanvas(self.ui)
+        mapCanvas.setObjectName('MapCanvas {} {}'.format(mapView.title(), self.TSD.date))
         mapCanvas.blockSignals(True)
         self.registerMapCanvas(mapView, mapCanvas)
 
@@ -621,8 +652,8 @@ class DatumView(QObject):
         # register MapCanvas on STV level
         self.STV.registerMapCanvas(mapCanvas)
         mapCanvas.blockSignals(False)
-        mapCanvas.refreshAllLayers()
-        mapCanvas.refresh()
+        #mapCanvas.refreshAllLayers()
+        #mapCanvas.refresh()
 
     def registerMapCanvas(self, mapView, mapCanvas):
         from timeseriesviewer.mapcanvas import MapCanvas
@@ -639,13 +670,14 @@ class DatumView(QObject):
         mapCanvas.renderStarting.connect(lambda: self.sigLoadingStarted.emit(mapView, self.TSD))
         mapCanvas.mapCanvasRefreshed.connect(lambda: self.sigLoadingFinished.emit(mapView, self.TSD))
         mapCanvas.sigShowProfiles.connect(mapView.sigShowProfiles.emit)
+        mapCanvas.sigChangeDVRequest.connect(self.onMapCanvasRequest)
 
+    def onMapCanvasRequest(self, mapCanvas, key):
 
+        if key == 'hide_date':
+            self.TSD.setVisibility(False)
 
 
-
-        pass
-
     def __lt__(self, other):
         assert isinstance(other, DatumView)
         return self.TSD < other.TSD
@@ -679,10 +711,20 @@ class SpatialTemporalVisualization(QObject):
         self.mColor = Qt.black
         self.mMapCanvases = []
         self.ui = timeSeriesViewer.ui
+        from timeseriesviewer.ui.widgets import TsvScrollArea
         self.scrollArea = self.ui.scrollAreaSubsets
+        assert isinstance(self.scrollArea, TsvScrollArea)
+        #self.scrollArea.sigResized.connect(self.refresh)
+        #self.scrollArea.horizontalScrollBar().valueChanged.connect(lambda:QTimer.singleShot(2000,self.refresh))
+
+
         self.TSV = timeSeriesViewer
         self.TS = timeSeriesViewer.TS
         self.targetLayout = self.ui.scrollAreaSubsetContent.layout()
+
+
+
+
         self.dockMapViews = self.ui.dockMapViews
         self.MVC = MapViewCollection(self)
         self.MVC.sigShowProfiles.connect(self.sigShowProfiles.emit)
@@ -717,12 +759,6 @@ class SpatialTemporalVisualization(QObject):
         #register on map canvas signals
         mapCanvas.sigSpatialExtentChanged.connect(lambda e: self.setSpatialExtent(e, mapCanvas))
 
-        from timeseriesviewer.ui.widgets import TsvScrollArea
-        assert isinstance(self.scrollArea, TsvScrollArea)
-        self.scrollArea.sigResized.connect(mapCanvas.setRenderMe)
-        self.scrollArea.horizontalScrollBar().valueChanged.connect(mapCanvas.setRenderMe)
-
-
 
 
     def setCrosshairStyle(self, crosshairStyle):
@@ -761,6 +797,7 @@ class SpatialTemporalVisualization(QObject):
     def subsetSize(self):
         return QSize(self.mSize)
 
+
     def refresh(self):
         for tsdView in self.DVC:
             tsdView.refresh()
@@ -795,6 +832,26 @@ class SpatialTemporalVisualization(QObject):
         self.nMaxTSDViews = n
         #todo: remove views
 
+    def setSpatialCenter(self, center, mapCanvas0=None):
+        if self.mBlockCanvasSignals:
+            return True
+
+        assert isinstance(center, SpatialPoint)
+        center = center.toCrs(self.mCRS)
+        if not isinstance(center, SpatialPoint):
+            return
+
+        self.mBlockCanvasSignals = True
+        self.mSpatialExtent.setCenter(center)
+        for mapCanvas in self.mMapCanvases:
+            if mapCanvas != mapCanvas0:
+                oldState = mapCanvas.blockSignals(True)
+                mapCanvas.setCenter(center)
+                mapCanvas.blockSignals(oldState)
+        self.mBlockCanvasSignals = False
+        self.sigSpatialExtentChanged.emit(self.mSpatialExtent)
+
+
     def setSpatialExtent(self, extent, mapCanvas0=None):
         if self.mBlockCanvasSignals:
             return True
@@ -811,12 +868,14 @@ class SpatialTemporalVisualization(QObject):
         self.mBlockCanvasSignals = True
         self.mSpatialExtent = extent
         for mapCanvas in self.mMapCanvases:
-            print('STV set EXTENT', str(mapCanvas))
             if mapCanvas != mapCanvas0:
                 oldState = mapCanvas.blockSignals(True)
                 mapCanvas.setExtent(extent)
                 mapCanvas.blockSignals(oldState)
+
         self.mBlockCanvasSignals = False
+        #for mapCanvas in self.mMapCanvases:
+        #    mapCanvas.refresh()
         self.sigSpatialExtentChanged.emit(extent)
 
     def setBackgroundColor(self, color):
@@ -860,6 +919,7 @@ class SpatialTemporalVisualization(QObject):
         assert isinstance(self.scrollArea, QScrollArea)
         self.scrollArea.ensureWidgetVisible(tsdv.ui)
 
+
     def setMapViewVisibility(self, bandView, isVisible):
         assert isinstance(bandView, MapView)
         assert isinstance(isVisible, bool)
@@ -1195,15 +1255,15 @@ class MapViewDefinitionUI(QGroupBox, load('mapviewdefinition.ui')):
         self.actionToggleVisibility.toggled.connect(lambda: self.setVisibility(not self.actionToggleVisibility.isChecked()))
         self.actionToggleVectorVisibility.toggled.connect(lambda : self.sigVectorVisibility.emit(self.actionToggleVectorVisibility.isChecked()))
 
-    def sizeHint(self):
+    def DEPRsizeHint(self):
 
         #m = self.layout().contentsMargins()
         #sl = maxWidgetSizes(self.sensorList)
         #sm = self.buttonList.size()
         #w = sl.width() + m.left()+ m.right() + sm.width()
         #h = sl.height() + m.top() + m.bottom() + sm.height()
-        return maxWidgetSizes(self.sensorList)
-        return QSize(w,h)
+        return maxWidgetSizes(self.sensorList.layout())
+
 
 
     def mapViewDefinition(self):
diff --git a/timeseriesviewer/plotstyling.py b/timeseriesviewer/plotstyling.py
index 32a22ea674fcc85d3d5b7e51a532b50ca93a5b73..3240b1a5e509e42170fd8dd84429ba6b54516297 100644
--- a/timeseriesviewer/plotstyling.py
+++ b/timeseriesviewer/plotstyling.py
@@ -243,11 +243,12 @@ class PlotStyleButton(QPushButton):
         return PlotStyle(plotStyle=self.mPlotStyle)
 
     def setPlotStyle(self, plotStyle):
-        #assert isinstance(plotStyle, PlotStyle)
         if isinstance(plotStyle, PlotStyle):
-            self.mPlotStyle = plotStyle
+            self.mPlotStyle.copyFrom(plotStyle)
             self._updateIcon()
             self.sigPlotStyleChanged.emit(self.mPlotStyle)
+        else:
+            s = ""
 
 
     def showDialog(self):
@@ -307,6 +308,8 @@ class PlotStyleDialog(QgsDialog):
         buttonBar = QHBoxLayout()
         #buttonBar.addWidget(self.btCancel)
         #buttonBar.addWidget(self.btOk)
+        if plotStyle:
+            self.setPlotStyle(plotStyle)
         l = self.layout()
         l.addWidget(self.w)
         l.addLayout(buttonBar)
diff --git a/timeseriesviewer/profilevisualization.py b/timeseriesviewer/profilevisualization.py
index 1812bb3c623ca444ca8c5a5e8c2a9345e681d92e..33e38433392ba1085e4b8c980e58c2d5589d4516 100644
--- a/timeseriesviewer/profilevisualization.py
+++ b/timeseriesviewer/profilevisualization.py
@@ -658,6 +658,12 @@ class ProfileViewDockUI(TsvDockWidgetBase, load('profileviewdock.ui')):
         super(ProfileViewDockUI, self).__init__(parent)
         self.setupUi(self)
         from timeseriesviewer import OPENGL_AVAILABLE, SETTINGS
+
+        #TBD.
+        self.line.setVisible(False)
+        self.listWidget.setVisible(False)
+        self.stackedWidget.setCurrentWidget(self.page2D)
+
         if OPENGL_AVAILABLE:
             l = self.page3D.layout()
             l.removeWidget(self.labelDummy3D)
@@ -667,7 +673,6 @@ class ProfileViewDockUI(TsvDockWidgetBase, load('profileviewdock.ui')):
         else:
             self.plotWidget3D = None
 
-
         #pi = self.plotWidget2D.plotItem
         #ax = DateAxis(orientation='bottom', showValues=True)
         #pi.layout.addItem(ax, 3,2)
@@ -948,7 +953,7 @@ class SpectralTemporalVisualization(QObject):
 
 
 
-def testPixelLoader():
+def examplePixelLoader():
 
     # prepare QGIS environment
     if sys.platform == 'darwin':
diff --git a/timeseriesviewer/sandbox.py b/timeseriesviewer/sandbox.py
index e666e6ef3c95c64a9642115e3928f97467bd9ef6..4f605460c9168909d9f6d294d527639246b8f06e 100644
--- a/timeseriesviewer/sandbox.py
+++ b/timeseriesviewer/sandbox.py
@@ -280,10 +280,35 @@ def sandboxTestdata():
     S.spatialTemporalVis.MVC.createMapView()
     import example.Images
     searchDir = jp(DIR_EXAMPLES, 'Images')
-    imgs = file_search(searchDir, '*.bsq', recursive=True)  # [0:5]
+    imgs = file_search(searchDir, '*.bsq', recursive=True)#[0:1]  # [0:5]
 
     S.addTimeSeriesImages(imgs)
 
+def sandboxMultitemp2017():
+    from timeseriesviewer.main import TimeSeriesViewer
+
+    from timeseriesviewer import PATH_EXAMPLE_TIMESERIES
+    S = TimeSeriesViewer(None)
+    S.ui.show()
+    S.run()
+
+    S.spatialTemporalVis.MVC.createMapView()
+    import example.Images
+
+    if True:
+        searchDir = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\Landsat'
+        imgs = file_search(searchDir, 'LC8*.vrt', recursive=True)[0:10]
+        S.addTimeSeriesImages(imgs)
+
+    if True:
+        searchDir = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\CBERS'
+        imgs = file_search(searchDir, 'CBERS*.vrt', recursive=True)#[0:1]
+        S.addTimeSeriesImages(imgs)
+
+        searchDir = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\RapidEye'
+        imgs = file_search(searchDir, 're*.vrt', recursive=True)#[0:1]
+        S.addTimeSeriesImages(imgs)
+
 
 if __name__ == '__main__':
     import site, sys, pyqtgraph
@@ -294,8 +319,10 @@ if __name__ == '__main__':
     #run tests
     if False: gdal_qgis_benchmark()
     if False: sandboxQgisBridge()
-    if True: sandboxGui()
+    if False: sandboxGui()
     if False: sandboxTestdata()
+    if True: sandboxMultitemp2017()
+
     #close QGIS
     qgsApp.exec_()
     qgsApp.exitQgis()
diff --git a/timeseriesviewer/sensorvisualization.py b/timeseriesviewer/sensorvisualization.py
index 3247b181239c33b0b576cdd9ff618057c639c845..0cdea7e02f3a5e2f56048ce3cfe40fb47daa253e 100644
--- a/timeseriesviewer/sensorvisualization.py
+++ b/timeseriesviewer/sensorvisualization.py
@@ -116,7 +116,7 @@ class SensorTableModel(QAbstractTableModel):
             elif columnName == 'id':
                 value = sensor.id()
             elif columnName == 'wl':
-                if sensor.wavelengths.ndim == 0:
+                if sensor.wavelengths is None or sensor.wavelengths.ndim == 0:
                     value = 'undefined'
                 else:
                     value = ','.join([str(w) for w in sensor.wavelengths]) +sensor.wavelengthUnits
diff --git a/timeseriesviewer/timeseries.py b/timeseriesviewer/timeseries.py
index 2fc30be0b5dec859c7359df0dd40dc7ce6021a2a..a007012f6f28ef13afb52936533d038b1d35c2af 100644
--- a/timeseriesviewer/timeseries.py
+++ b/timeseriesviewer/timeseries.py
@@ -98,9 +98,12 @@ class SensorInstrument(QObject):
 
         #find wavelength
         wl, wlu = parseWavelengthFromDataSet(ds)
-        self.wavelengths = np.asarray(wl)
-        self.wavelengthUnits = wlu
-
+        if wl is None:
+            self.wavelengths = None
+            self.wavelengthUnits = None
+        else:
+            self.wavelengths = np.asarray(wl)
+            self.wavelengthUnits = wlu
         self._id = '{}b{}m'.format(self.nb, self.px_size_x)
         if wl is not None:
             self._id += ';'.join([str(w) for w in self.wavelengths])+ wlu
@@ -190,12 +193,19 @@ def verifyInputImage(path, vrtInspection=''):
     if ds.RasterCount == 0 and len(ds.GetSubDatasets()) > 0:
         logger.error('Can not open container {}.\nPlease specify a subdataset'.format(path))
         return False
+
     if ds.GetDriver().ShortName == 'VRT':
         vrtInspection = 'VRT Inspection {}\n'.format(path)
         nextFiles = set(ds.GetFileList()) - set([path])
         validSrc = [verifyInputImage(p, vrtInspection=vrtInspection) for p in nextFiles]
         if not all(validSrc):
             return False
+
+    from timeseriesviewer.dateparser import parseDateFromDataSet
+    date = parseDateFromDataSet(ds)
+    if date is None:
+        return False
+
     return True
 
 def pixel2coord(gt, x, y):
@@ -249,8 +259,8 @@ class TimeSeriesDatum(QObject):
 
         self.date = parseDateFromDataSet(ds)
         assert self.date is not None, 'Unable to find acquisition date of {}'.format(pathImg)
-        from timeseriesviewer.dateparser import getDOYfromDatetime64
-        self.doy = getDOYfromDatetime64(self.date)
+        from timeseriesviewer.dateparser import DOYfromDatetime64
+        self.doy = DOYfromDatetime64(self.date)
 
 
         gt = ds.GetGeoTransform()
@@ -563,10 +573,10 @@ def parseWavelengthFromDataSet(ds):
     wlu = None
 
     #see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units
-    regWLkey = re.compile('.*wavelength[_ ]*$')
-    regWLUkey = re.compile('.*wavelength[_ ]*units?$')
-    regNumeric = re.compile(r"([-+]?\d*\.\d+|[-+]?\d+)")
-    regWLU = re.compile('((micro|nano|centi)meters)|(um|nm|mm|cm|m|GHz|MHz)')
+    regWLkey = re.compile('.*wavelength[_ ]*$', re.I)
+    regWLUkey = re.compile('.*wavelength[_ ]*units?$', re.I)
+    regNumeric = re.compile(r"([-+]?\d*\.\d+|[-+]?\d+)", re.I)
+    regWLU = re.compile('((micro|nano|centi)meters)|(um|nm|mm|cm|m|GHz|MHz)', re.I)
     for domain in ds.GetMetadataDomainList():
         md = ds.GetMetadata_Dict(domain)
         for key, value in md.items():
@@ -578,7 +588,7 @@ def parseWavelengthFromDataSet(ds):
             if wlu is None and regWLUkey.search(key):
                 match = regWLU.search(value)
                 if match:
-                    wlu = match.group()
+                    wlu = match.group().lower()
                 names = ['nanometers', 'micrometers', 'millimeters', 'centimeters', 'decimeters']
                 si = ['nm', 'um', 'mm', 'cm', 'dm']
                 if wlu in names:
diff --git a/timeseriesviewer/ui/docks.py b/timeseriesviewer/ui/docks.py
index cb03c932030f1ae9835b148c1344907259f3253f..5103b4a32973cd9f08cac77d2807cbc4581804d3 100644
--- a/timeseriesviewer/ui/docks.py
+++ b/timeseriesviewer/ui/docks.py
@@ -37,6 +37,7 @@ class RenderingDockUI(TsvDockWidgetBase, load('renderingdock.ui')):
     sigCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem)
     sigMapSizeChanged = pyqtSignal(QSize)
     sigQgisSyncStateChanged = pyqtSignal(QgisTsvBridge.SyncState)
+    sigQgisInteractionRequest = pyqtSignal(str)
 
     def __init__(self, parent=None):
         super(RenderingDockUI, self).__init__(parent)
@@ -53,6 +54,11 @@ class RenderingDockUI(TsvDockWidgetBase, load('renderingdock.ui')):
         self.btnCrs.crsChanged.connect(self.sigCrsChanged)
 
         #default: disable QgsSync box
+
+        #todo: realt-time syncing?
+        self.frameRTSync.setVisible(False)
+        self.progressBar.setVisible(False)
+
         self.enableQgisSyncronization(False)
 
         self.mLastSyncState = self.qgsSyncState()
@@ -60,8 +66,19 @@ class RenderingDockUI(TsvDockWidgetBase, load('renderingdock.ui')):
         self.cbSyncQgsMapCenter.stateChanged.connect(self.onSyncStateChanged)
         self.cbSyncQgsCRS.stateChanged.connect(self.onSyncStateChanged)
 
+        self.btnSetQGISCenter.clicked.connect(lambda : self.sigQgisInteractionRequest.emit('tsvCenter2qgsCenter'))
+        self.btnSetQGISExtent.clicked.connect(lambda: self.sigQgisInteractionRequest.emit('tsvExtent2qgsExtent'))
+        self.btnGetQGISCenter.clicked.connect(lambda: self.sigQgisInteractionRequest.emit('qgisCenter2tsvCenter'))
+        self.btnGetQGISExtent.clicked.connect(lambda: self.sigQgisInteractionRequest.emit('qgisExtent2tsvExtent'))
+
     def enableQgisSyncronization(self, b):
+
         self.gbSyncQgs.setEnabled(b)
+        if b:
+            self.gbSyncQgs.setTitle('QGIS')
+        else:
+            self.gbSyncQgs.setTitle('QGIS (not available)')
+        #self.gbQgsVectorLayer.setEnabled(b)
 
     def onSyncStateChanged(self, *args):
 
@@ -239,7 +256,6 @@ class TimeSeriesDockUI(TsvDockWidgetBase, load('timeseriesdock.ui')):
                 msg += ', {} to {}'.format(str(self.TS[0].date), str(self.TS[-1].date))
             self.progressInfo.setText(msg)
 
-
     def setProgressInfo(self, nDone, nMax, message=None):
         if self.progressBar.maximum() != nMax:
             self.progressBar.setMaximum(nMax)
@@ -249,13 +265,9 @@ class TimeSeriesDockUI(TsvDockWidgetBase, load('timeseriesdock.ui')):
         if nDone == nMax:
             QTimer.singleShot(3000, lambda: self.setStatus())
 
-
     def onSelectionChanged(self, *args):
         self.btnRemoveTSD.setEnabled(self.SM is not None and len(self.SM.selectedRows()) > 0)
 
-
-        s = ""
-
     def selectedTimeSeriesDates(self):
         if self.SM is not None:
             return [self.TSM.data(idx, Qt.UserRole) for idx in self.SM.selectedRows()]
diff --git a/timeseriesviewer/ui/mapviewdefinition.ui b/timeseriesviewer/ui/mapviewdefinition.ui
index 360e9082aaf42c83354c6ddf9f44a44d55049f41..cbc9c2aeeb7c08add641e29eba4e48006595907b 100644
--- a/timeseriesviewer/ui/mapviewdefinition.ui
+++ b/timeseriesviewer/ui/mapviewdefinition.ui
@@ -6,24 +6,36 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>253</width>
-    <height>159</height>
+    <width>325</width>
+    <height>136</height>
    </rect>
   </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
   <property name="windowTitle">
    <string/>
   </property>
   <property name="title">
    <string/>
   </property>
-  <layout class="QFormLayout" name="formLayout">
-   <property name="horizontalSpacing">
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <property name="leftMargin">
+    <number>1</number>
+   </property>
+   <property name="topMargin">
     <number>1</number>
    </property>
-   <property name="margin">
+   <property name="rightMargin">
     <number>1</number>
    </property>
-   <item row="0" column="0">
+   <property name="bottomMargin">
+    <number>6</number>
+   </property>
+   <item>
     <widget class="QFrame" name="buttonList">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
@@ -129,6 +141,12 @@
       </item>
       <item>
        <widget class="QToolButton" name="btnShowCrosshair">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
         <property name="text">
          <string>...</string>
         </property>
@@ -176,25 +194,30 @@
      </layout>
     </widget>
    </item>
-   <item row="0" column="1">
-    <layout class="QHBoxLayout" name="sensorList">
-     <property name="margin">
-      <number>1</number>
-     </property>
-    </layout>
-   </item>
-   <item row="1" column="1">
-    <spacer name="verticalSpacer">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
+   <item>
+    <widget class="QFrame" name="sensorList">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
      </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>0</width>
-       <height>0</height>
-      </size>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
      </property>
-    </spacer>
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>267</width>
+          <height>50</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
    </item>
   </layout>
   <action name="actionRemoveMapView">
diff --git a/timeseriesviewer/ui/mapviewdock.ui b/timeseriesviewer/ui/mapviewdock.ui
index 902829b7d91046f7a9e132e3f6a3f0362a35d280..2ce3eb174aa8d062ba4f165cce716446303bb180 100644
--- a/timeseriesviewer/ui/mapviewdock.ui
+++ b/timeseriesviewer/ui/mapviewdock.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>504</width>
-    <height>189</height>
+    <width>480</width>
+    <height>175</height>
    </rect>
   </property>
   <property name="minimumSize">
@@ -30,8 +30,8 @@
     <item>
      <widget class="QScrollArea" name="scrollAreaMapViews">
       <property name="sizePolicy">
-       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
-        <horstretch>1</horstretch>
+       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+        <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
@@ -51,7 +51,7 @@
        <number>1</number>
       </property>
       <property name="verticalScrollBarPolicy">
-       <enum>Qt::ScrollBarAsNeeded</enum>
+       <enum>Qt::ScrollBarAlwaysOff</enum>
       </property>
       <property name="horizontalScrollBarPolicy">
        <enum>Qt::ScrollBarAsNeeded</enum>
@@ -67,8 +67,8 @@
         <rect>
          <x>0</x>
          <y>0</y>
-         <width>504</width>
-         <height>167</height>
+         <width>480</width>
+         <height>153</height>
         </rect>
        </property>
        <property name="sizePolicy">
diff --git a/timeseriesviewer/ui/mapviewrendersettings.ui b/timeseriesviewer/ui/mapviewrendersettings.ui
index 2483cfd31e950cf921dcc12beba28f083dce6ea4..06d88855a69815b6c342947ae5dd3efeec41bf20 100644
--- a/timeseriesviewer/ui/mapviewrendersettings.ui
+++ b/timeseriesviewer/ui/mapviewrendersettings.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>300</width>
-    <height>216</height>
+    <width>260</width>
+    <height>135</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -47,19 +47,10 @@
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <property name="spacing">
-    <number>6</number>
-   </property>
-   <property name="leftMargin">
-    <number>6</number>
-   </property>
-   <property name="topMargin">
-    <number>6</number>
+    <number>2</number>
    </property>
-   <property name="rightMargin">
-    <number>6</number>
-   </property>
-   <property name="bottomMargin">
-    <number>6</number>
+   <property name="margin">
+    <number>2</number>
    </property>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_2">
@@ -67,7 +58,7 @@
       <widget class="QLabel" name="labelTitle">
        <property name="font">
         <font>
-         <pointsize>11</pointsize>
+         <pointsize>8</pointsize>
          <weight>50</weight>
          <italic>false</italic>
          <bold>false</bold>
@@ -95,7 +86,7 @@
        </property>
        <property name="font">
         <font>
-         <pointsize>11</pointsize>
+         <pointsize>8</pointsize>
         </font>
        </property>
        <item>
@@ -126,7 +117,7 @@
        </property>
        <property name="font">
         <font>
-         <pointsize>11</pointsize>
+         <pointsize>8</pointsize>
         </font>
        </property>
        <property name="text">
@@ -164,26 +155,23 @@
       <number>0</number>
      </property>
      <widget class="QWidget" name="pageMultiBand">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
       <property name="font">
        <font>
-        <pointsize>11</pointsize>
+        <pointsize>8</pointsize>
        </font>
       </property>
       <layout class="QGridLayout" name="gridLayout_2">
-       <property name="leftMargin">
-        <number>6</number>
-       </property>
-       <property name="topMargin">
-        <number>6</number>
-       </property>
-       <property name="rightMargin">
-        <number>6</number>
-       </property>
-       <property name="bottomMargin">
-        <number>6</number>
+       <property name="margin">
+        <number>2</number>
        </property>
        <property name="spacing">
-        <number>6</number>
+        <number>2</number>
        </property>
        <item row="1" column="1">
         <widget class="QSlider" name="sliderRed">
@@ -224,6 +212,11 @@
        </item>
        <item row="0" column="2">
         <widget class="QLabel" name="labelMax">
+         <property name="font">
+          <font>
+           <pointsize>8</pointsize>
+          </font>
+         </property>
          <property name="text">
           <string>Min</string>
          </property>
@@ -245,7 +238,7 @@
          </property>
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="text">
@@ -257,7 +250,7 @@
         <widget class="QLineEdit" name="tbGreenMin">
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="text">
@@ -281,7 +274,7 @@
          </property>
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="text">
@@ -291,6 +284,11 @@
        </item>
        <item row="0" column="3">
         <widget class="QLabel" name="labelMin">
+         <property name="font">
+          <font>
+           <pointsize>8</pointsize>
+          </font>
+         </property>
          <property name="text">
           <string>Max</string>
          </property>
@@ -306,7 +304,7 @@
          </property>
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="text">
@@ -318,7 +316,7 @@
         <widget class="QLabel" name="labelGreen">
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="styleSheet">
@@ -364,7 +362,7 @@
         <widget class="QLabel" name="labelRed">
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="styleSheet">
@@ -379,7 +377,7 @@
         <widget class="QLabel" name="labelBlue">
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="text">
@@ -416,7 +414,7 @@
         <widget class="QLineEdit" name="tbBlueMin">
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="text">
@@ -434,7 +432,7 @@
          </property>
          <property name="font">
           <font>
-           <pointsize>11</pointsize>
+           <pointsize>8</pointsize>
           </font>
          </property>
          <property name="text">
@@ -460,23 +458,14 @@
           <property name="spacing">
            <number>3</number>
           </property>
-          <property name="leftMargin">
-           <number>0</number>
-          </property>
-          <property name="topMargin">
-           <number>0</number>
-          </property>
-          <property name="rightMargin">
-           <number>0</number>
-          </property>
-          <property name="bottomMargin">
+          <property name="margin">
            <number>0</number>
           </property>
           <item>
            <widget class="QToolButton" name="btnDefaultMB">
             <property name="font">
              <font>
-              <pointsize>11</pointsize>
+              <pointsize>8</pointsize>
               <italic>false</italic>
              </font>
             </property>
@@ -492,7 +481,7 @@
            <widget class="QToolButton" name="btnTrueColor">
             <property name="font">
              <font>
-              <pointsize>11</pointsize>
+              <pointsize>8</pointsize>
               <italic>false</italic>
              </font>
             </property>
@@ -508,7 +497,7 @@
            <widget class="QToolButton" name="btnCIR">
             <property name="font">
              <font>
-              <pointsize>11</pointsize>
+              <pointsize>8</pointsize>
               <italic>false</italic>
              </font>
             </property>
@@ -524,7 +513,7 @@
            <widget class="QToolButton" name="btn453">
             <property name="font">
              <font>
-              <pointsize>11</pointsize>
+              <pointsize>8</pointsize>
               <italic>false</italic>
              </font>
             </property>
@@ -557,28 +546,19 @@
      <widget class="QWidget" name="pageSingleBand">
       <property name="font">
        <font>
-        <pointsize>11</pointsize>
+        <pointsize>8</pointsize>
        </font>
       </property>
       <layout class="QGridLayout" name="gridLayout">
-       <property name="leftMargin">
-        <number>0</number>
-       </property>
-       <property name="topMargin">
-        <number>0</number>
-       </property>
-       <property name="rightMargin">
-        <number>0</number>
-       </property>
-       <property name="bottomMargin">
-        <number>0</number>
-       </property>
        <property name="horizontalSpacing">
         <number>2</number>
        </property>
        <property name="verticalSpacing">
         <number>0</number>
        </property>
+       <property name="margin">
+        <number>0</number>
+       </property>
        <item row="3" column="1">
         <widget class="QSlider" name="sliderSingleBand">
          <property name="minimumSize">
@@ -625,24 +605,15 @@
           <enum>QFrame::Raised</enum>
          </property>
          <layout class="QGridLayout" name="gridLayout_4">
-          <property name="leftMargin">
-           <number>0</number>
-          </property>
-          <property name="topMargin">
-           <number>0</number>
-          </property>
-          <property name="rightMargin">
-           <number>0</number>
-          </property>
-          <property name="bottomMargin">
-           <number>0</number>
-          </property>
           <property name="horizontalSpacing">
            <number>1</number>
           </property>
           <property name="verticalSpacing">
            <number>0</number>
           </property>
+          <property name="margin">
+           <number>0</number>
+          </property>
           <item row="1" column="2">
            <widget class="QComboBox" name="cbSingleBandColorRampType">
             <property name="sizePolicy">
@@ -703,16 +674,7 @@
           <property name="spacing">
            <number>0</number>
           </property>
-          <property name="leftMargin">
-           <number>0</number>
-          </property>
-          <property name="topMargin">
-           <number>0</number>
-          </property>
-          <property name="rightMargin">
-           <number>0</number>
-          </property>
-          <property name="bottomMargin">
+          <property name="margin">
            <number>0</number>
           </property>
           <item>
@@ -882,7 +844,7 @@
       <widget class="QLabel" name="label">
        <property name="font">
         <font>
-         <pointsize>11</pointsize>
+         <pointsize>8</pointsize>
         </font>
        </property>
        <property name="text">
diff --git a/timeseriesviewer/ui/renderingdock.ui b/timeseriesviewer/ui/renderingdock.ui
index 9274a5c9bc1d1fad72fa645f3a7885a33174b756..77d15969c61908a9adf55bb161e5ea1dbb183432 100644
--- a/timeseriesviewer/ui/renderingdock.ui
+++ b/timeseriesviewer/ui/renderingdock.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>206</width>
-    <height>386</height>
+    <width>327</width>
+    <height>359</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -235,124 +235,220 @@
     </item>
     <item>
      <widget class="QGroupBox" name="gbSyncQgs">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
       <property name="minimumSize">
        <size>
         <width>0</width>
-        <height>60</height>
+        <height>25</height>
+       </size>
+      </property>
+      <property name="maximumSize">
+       <size>
+        <width>16777215</width>
+        <height>230</height>
        </size>
       </property>
       <property name="title">
-       <string>Sync with QGIS Map on...</string>
+       <string>QGIS</string>
       </property>
       <property name="flat">
        <bool>false</bool>
       </property>
-      <layout class="QGridLayout" name="gridLayout_2">
-       <item row="0" column="0">
-        <widget class="QCheckBox" name="cbSyncQgsMapCenter">
+      <layout class="QGridLayout" name="gridLayout_2" rowstretch="1,1,0,0" rowminimumheight="23,23,0,0">
+       <property name="sizeConstraint">
+        <enum>QLayout::SetMinimumSize</enum>
+       </property>
+       <property name="margin">
+        <number>6</number>
+       </property>
+       <property name="spacing">
+        <number>2</number>
+       </property>
+       <item row="1" column="2">
+        <widget class="QToolButton" name="btnGetQGISExtent">
+         <property name="toolTip">
+          <string>Take the spatial map extent from QGIS</string>
+         </property>
          <property name="text">
-          <string>Center</string>
+          <string>Get Extent</string>
          </property>
         </widget>
        </item>
-       <item row="0" column="3">
-        <spacer name="horizontalSpacer_3">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>40</width>
-           <height>20</height>
-          </size>
+       <item row="1" column="1">
+        <widget class="QToolButton" name="btnGetQGISCenter">
+         <property name="toolTip">
+          <string>Take the map center from QGIS</string>
          </property>
-        </spacer>
-       </item>
-       <item row="0" column="2">
-        <widget class="QCheckBox" name="cbSyncQgsCRS">
          <property name="text">
-          <string>CRS</string>
+          <string>Get Center</string>
          </property>
         </widget>
        </item>
-       <item row="0" column="1">
-        <widget class="QCheckBox" name="cbSyncQgsMapExtent">
+       <item row="0" column="2">
+        <widget class="QToolButton" name="btnSetQGISExtent">
+         <property name="toolTip">
+          <string>Try to set the QGIS map extent to the same spatial extent</string>
+         </property>
          <property name="text">
-          <string>Exent</string>
+          <string>Set Extent</string>
          </property>
         </widget>
        </item>
-      </layout>
-     </widget>
-    </item>
-    <item>
-     <widget class="QgsCollapsibleGroupBox" name="gbQgsVectorLayer">
-      <property name="title">
-       <string>Show Vector Layer</string>
-      </property>
-      <property name="checkable">
-       <bool>true</bool>
-      </property>
-      <property name="checked">
-       <bool>false</bool>
-      </property>
-      <property name="collapsed">
-       <bool>false</bool>
-      </property>
-      <property name="saveCheckedState">
-       <bool>true</bool>
-      </property>
-      <layout class="QFormLayout" name="formLayout_5">
-       <property name="fieldGrowthPolicy">
-        <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
-       </property>
-       <item row="0" column="1">
-        <widget class="QgsMapLayerComboBox" name="cbQgsVectorLayer">
+       <item row="3" column="0" colspan="4">
+        <widget class="QFrame" name="frameRTSync">
          <property name="enabled">
           <bool>false</bool>
          </property>
          <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
            <horstretch>0</horstretch>
            <verstretch>0</verstretch>
           </sizepolicy>
          </property>
+         <property name="minimumSize">
+          <size>
+           <width>0</width>
+           <height>50</height>
+          </size>
+         </property>
+         <property name="frameShape">
+          <enum>QFrame::StyledPanel</enum>
+         </property>
+         <property name="frameShadow">
+          <enum>QFrame::Raised</enum>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout">
+          <item>
+           <widget class="QCheckBox" name="cbSyncQgsMapCenter">
+            <property name="text">
+             <string>Center</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QCheckBox" name="cbSyncQgsMapExtent">
+            <property name="text">
+             <string>Exent</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QCheckBox" name="cbSyncQgsCRS">
+            <property name="text">
+             <string>CRS</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <spacer name="horizontalSpacer_3">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>40</width>
+              <height>20</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+         </layout>
+         <zorder>cbSyncQgsMapCenter</zorder>
+         <zorder>cbSyncQgsCRS</zorder>
+         <zorder>cbSyncQgsMapExtent</zorder>
+         <zorder>horizontalSpacer_3</zorder>
         </widget>
        </item>
-      </layout>
-     </widget>
-    </item>
-    <item>
-     <widget class="QGroupBox" name="groupBox_2">
-      <property name="title">
-       <string>Render progress</string>
-      </property>
-      <layout class="QVBoxLayout" name="verticalLayout_2">
-       <item>
-        <widget class="QProgressBar" name="progressBar">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
+       <item row="0" column="1">
+        <widget class="QToolButton" name="btnSetQGISCenter">
+         <property name="toolTip">
+          <string>Set QGIS Map to the same map center</string>
          </property>
-         <property name="value">
-          <number>0</number>
+         <property name="text">
+          <string>Set Center</string>
          </property>
+        </widget>
+       </item>
+       <item row="0" column="3">
+        <spacer name="horizontalSpacer">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
-         <property name="invertedAppearance">
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="2" column="0" colspan="4">
+        <widget class="QgsCollapsibleGroupBox" name="gbQgsVectorLayer">
+         <property name="title">
+          <string>Show Vector Layer</string>
+         </property>
+         <property name="checkable">
+          <bool>true</bool>
+         </property>
+         <property name="checked">
           <bool>false</bool>
          </property>
-         <property name="textDirection">
-          <enum>QProgressBar::TopToBottom</enum>
+         <property name="collapsed">
+          <bool>true</bool>
+         </property>
+         <property name="saveCheckedState">
+          <bool>true</bool>
          </property>
+         <layout class="QFormLayout" name="formLayout_5">
+          <property name="fieldGrowthPolicy">
+           <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+          </property>
+          <item row="0" column="1">
+           <widget class="QgsMapLayerComboBox" name="cbQgsVectorLayer">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+           </widget>
+          </item>
+         </layout>
         </widget>
        </item>
       </layout>
      </widget>
     </item>
+    <item>
+     <widget class="QProgressBar" name="progressBar">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="value">
+       <number>0</number>
+      </property>
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <property name="invertedAppearance">
+       <bool>false</bool>
+      </property>
+      <property name="textDirection">
+       <enum>QProgressBar::TopToBottom</enum>
+      </property>
+     </widget>
+    </item>
     <item>
      <spacer name="verticalSpacer">
       <property name="orientation">
@@ -361,7 +457,7 @@
       <property name="sizeHint" stdset="0">
        <size>
         <width>20</width>
-        <height>40</height>
+        <height>0</height>
        </size>
       </property>
      </spacer>
diff --git a/timeseriesviewer/ui/widgets.py b/timeseriesviewer/ui/widgets.py
index edb01bcb7efdc5754e2a2b57c959190512d28b58..60442ec8fd7b250c302a5813d4fa309eb0f90aca 100644
--- a/timeseriesviewer/ui/widgets.py
+++ b/timeseriesviewer/ui/widgets.py
@@ -125,7 +125,7 @@ PATH_RENDERINGDOCK_UI = jp(DIR_UI, 'renderingdock.ui')
 
 
 
-def maxWidgetSizes(layout):
+def maxWidgetSizes(layout, onHint=True):
     assert isinstance(layout, QBoxLayout)
 
     p = layout.parentWidget()
@@ -137,10 +137,15 @@ def maxWidgetSizes(layout):
 
     for item in [layout.itemAt(i) for i in range(layout.count())]:
         wid = item.widget()
+        ly = item.layout()
         if wid:
-            s = wid.sizeHint()
-        elif isinstance(item, QLayout):
-            s = ""
+            if onHint:
+                s = wid.sizeHint()
+            else:
+                s = wid.size()
+        elif ly:
+            continue
+        else:
             continue
         if horizontal:
             sizeX += s.width() + layout.spacing()
diff --git a/timeseriesviewer/utils.py b/timeseriesviewer/utils.py
index 8d9e4969a2087e7ac294019a0ac7b825236ea301..c982afade4652b43478dca3b7d627b02dd5d8328 100644
--- a/timeseriesviewer/utils.py
+++ b/timeseriesviewer/utils.py
@@ -45,6 +45,12 @@ class SpatialPoint(QgsPoint):
         crs = mapCanvas.mapSettings().destinationCrs()
         return SpatialPoint(crs, mapCanvas.center())
 
+    @staticmethod
+    def fromSpatialExtent(spatialExtent):
+        assert isinstance(spatialExtent, SpatialExtent)
+        crs = spatialExtent.crs()
+        return SpatialPoint(crs, spatialExtent.center())
+
     def __init__(self, crs, *args):
         assert isinstance(crs, QgsCoordinateReferenceSystem)
         super(SpatialPoint, self).__init__(*args)
@@ -105,7 +111,7 @@ def saveTransform(geom, crs1, crs2):
 
         transform = QgsCoordinateTransform(crs1, crs2);
         try:
-            pt = transform.transformBoundingBox(geom);
+            pt = transform.transform(geom);
             result = SpatialPoint(crs2, pt)
         except:
             print('Can not transform from {} to {} on QgsPoint {}'.format( \
diff --git a/timeseriesviewer/virtualrasters.py b/timeseriesviewer/virtualrasters.py
new file mode 100644
index 0000000000000000000000000000000000000000..d59059e823d06f7a77bf0140f66d150e52fb365a
--- /dev/null
+++ b/timeseriesviewer/virtualrasters.py
@@ -0,0 +1,294 @@
+import os, sys, re
+import tempfile
+from osgeo import gdal
+from timeseriesviewer import file_search
+
+class VirtualBandInputSource(object):
+    def __init__(self, path, bandIndex):
+        self.path = path
+        self.bandIndex = bandIndex
+        self.noData = None
+
+
+
+
+class VirtualBand(object):
+
+    def __init__(self, name=''):
+        self.sources = []
+        self.mName = name
+
+    def addSourceBand(self, path, bandIndex):
+        self.sources.append(VirtualBandInputSource(path, bandIndex))
+
+    def sourceFiles(self):
+        files = set([inputSource.path for inputSource in self.sources])
+        return sorted(list(files))
+
+    def __repr__(self):
+        infos = ['VirtualBand name="{}"'.format(self.mName)]
+        for i, info in enumerate(self.sources):
+            assert isinstance(info, VirtualBandInputSource)
+            infos.append('\t{} SourceFileName {} SourceBand {}'.format(i+1, info.path, info.bandIndex))
+        return '\n'.join(infos)
+
+class VirtualRasterBuilder(object):
+
+    def __init__(self):
+        self.vBands = []
+        self.vMetadata = dict()
+
+    def addVirtualBand(self, virtualBand):
+        assert isinstance(virtualBand, VirtualBand)
+        self.vBands.append(virtualBand)
+
+    def insertVirtualBand(self, i, virtualBand):
+        assert isinstance(virtualBand, VirtualBand)
+        self.vBands.insert(i, virtualBand)
+
+    def addFilesAsMosaic(self, files):
+        pass
+
+    def addFilesAsStack(self, files):
+        pass
+
+    def sourceFiles(self):
+        files = set()
+        for vBand in self.vBands:
+            assert isinstance(vBand, VirtualBand)
+            files.update(set(vBand.sourceFiles()))
+        return sorted(list(files))
+
+    def saveVRT(self, pathVRT):
+
+        dn = os.path.dirname(pathVRT)
+        if not os.path.isdir(dn):
+            os.mkdir(dn)
+
+        srcFiles = self.sourceFiles()
+        srcNodata = None
+        for src in srcFiles:
+            ds = gdal.Open(src)
+            band = ds.GetRasterBand(1)
+            noData = band.GetNoDataValue()
+            if noData and srcNodata is None:
+                srcNodata = noData
+
+        vro = gdal.BuildVRTOptions(separate=True,
+                                   srcNodata=srcNodata)
+        #1. build a temporary VRT that described the spatial shifts of all input sources
+        gdal.BuildVRT(pathVRT, srcFiles, options=vro)
+        dsVRTDst = gdal.Open(pathVRT)
+        assert isinstance(dsVRTDst, gdal.Dataset)
+        assert len(srcFiles) == dsVRTDst.RasterCount
+        ns, nl = dsVRTDst.RasterXSize, dsVRTDst.RasterYSize
+        gt = dsVRTDst.GetGeoTransform()
+        crs = dsVRTDst.GetProjectionRef()
+        eType = dsVRTDst.GetRasterBand(1).DataType
+        SOURCE_TEMPLATES = dict()
+        for i, srcFile in enumerate(srcFiles):
+            vrt_sources = dsVRTDst.GetRasterBand(i+1).GetMetadata('vrt_sources')
+            assert len(vrt_sources) == 1
+            srcXML = vrt_sources.values()[0]
+            assert os.path.basename(srcFile)+'</SourceFilename>' in srcXML
+            assert '<SourceBand>1</SourceBand>' in srcXML
+            SOURCE_TEMPLATES[srcFile] = srcXML
+        dsVRTDst = None
+        #remove the temporary VRT, we don't need it any more
+        os.remove(pathVRT)
+
+        #2. build final VRT from scratch
+        drvVRT = gdal.GetDriverByName('VRT')
+        assert isinstance(drvVRT, gdal.Driver)
+        dsVRTDst = drvVRT.Create(pathVRT, ns, nl,0, eType=eType)
+        #2.1. set general properties
+        assert isinstance(dsVRTDst, gdal.Dataset)
+        dsVRTDst.SetProjection(crs)
+        dsVRTDst.SetGeoTransform(gt)
+
+        #2.2. add virtual bands
+        for i, vBand in enumerate(self.vBands):
+            assert isinstance(vBand, VirtualBand)
+            assert dsVRTDst.AddBand(eType, options=['subClass=VRTSourcedRasterBand']) == 0
+            vrtBandDst = dsVRTDst.GetRasterBand(i+1)
+            assert isinstance(vrtBandDst, gdal.Band)
+            vrtBandDst.SetDescription(vBand.mName)
+            md = {}
+            #add all input sources for this virtual band
+            for iSrc, sourceInfo in enumerate(vBand.sources):
+                assert isinstance(sourceInfo, VirtualBandInputSource)
+                bandIndex = sourceInfo.bandIndex
+                xml = SOURCE_TEMPLATES[sourceInfo.path]
+                xml = re.sub('<SourceBand>1</SourceBand>','<SourceBand>{}</SourceBand>'.format(bandIndex+1), xml)
+                md['source_{}'.format(iSrc)] = xml
+            vrtBandDst.SetMetadata(md,'vrt_sources')
+            if False:
+                vrtBandDst.ComputeBandStats(1)
+
+
+        dsVRTDst = None
+
+        #check if we get what we like to get
+        dsCheck = gdal.Open(pathVRT)
+
+        s = ""
+        return dsCheck
+
+    def __repr__(self):
+
+        info = ['VirtualRasterBuilder: {} bands, {} source files'.format(
+            len(self.vBands), len(self.sourceFiles()))]
+        for vBand in self.vBands:
+            info.append(str(vBand))
+        return '\n'.join(info)
+
+def createVirtualBandMosaic(bandFiles, pathVRT):
+    drv = gdal.GetDriverByName('VRT')
+
+    refPath = bandFiles[0]
+    refDS = gdal.Open(refPath)
+    ns, nl, nb = refDS.RasterXSize, refDS.RasterYSize, refDS.RasterCount
+    noData = refDS.GetRasterBand(1).GetNoDataValue()
+
+    vrtOptions = gdal.BuildVRTOptions(
+        # here we can use the options known from http://www.gdal.org/gdalbuildvrt.html
+        separate=False
+    )
+    if len(bandFiles) > 1:
+        s =""
+    vrtDS = gdal.BuildVRT(pathVRT, bandFiles, options=vrtOptions)
+    vrtDS.FlushCache()
+
+    assert vrtDS.RasterCount == nb
+    return vrtDS
+
+def createVirtualBandStack(bandFiles, pathVRT):
+
+    nb = len(bandFiles)
+
+    drv = gdal.GetDriverByName('VRT')
+
+    refPath = bandFiles[0]
+    refDS = gdal.Open(refPath)
+    ns, nl = refDS.RasterXSize, refDS.RasterYSize
+    noData = refDS.GetRasterBand(1).GetNoDataValue()
+
+    vrtOptions = gdal.BuildVRTOptions(
+        # here we can use the options known from http://www.gdal.org/gdalbuildvrt.html
+        separate=True
+    )
+    vrtDS = gdal.BuildVRT(pathVRT, bandFiles, options=vrtOptions)
+    vrtDS.FlushCache()
+
+    assert vrtDS.RasterCount == nb
+
+    #copy band metadata from
+    for i in range(nb):
+        band = vrtDS.GetRasterBand(i+1)
+        band.SetDescription(bandFiles[i])
+        band.ComputeBandStats()
+
+        if noData:
+            band.SetNoDataValue(noData)
+
+    return vrtDS
+
+
+def groupRapidEyeTiles(dirIn, dirOut):
+    """
+
+    :param dirIn:
+    :param dirOut:
+    :return:
+    """
+
+    files = file_search(dirIn, '*_RE*_3A_*2.tif', recursive=True)
+
+    if not os.path.exists(dirOut):
+        os.mkdir(dirOut)
+
+    sources = dict()
+    for file in files:
+        if not file.endswith('.tif'):
+            continue
+        dn = os.path.dirname(file)
+        bn = os.path.basename(file)
+        print(bn)
+        id, date, sensor, product, _ = tuple(bn.split('_'))
+
+        if not date in sources.keys():
+            sources[date] = []
+        sources[date].append(file)
+    for date, files in sources.items():
+        pathVRT = os.path.join(dirOut, 're_{}.vrt'.format(date))
+        createVirtualBandMosaic(files, pathVRT)
+
+def groupCBERS(dirIn, dirOut):
+    files = file_search(dirIn, 'CBERS*.tif', recursive=True)
+
+    if not os.path.exists(dirOut):
+        os.mkdir(dirOut)
+
+    CONTAINERS = dict()
+    for file in files:
+        dn = os.path.dirname(file)
+        bn = os.path.basename(file)
+        #basenames like CBERS_4_MUX_20150603_167_107_L4_BAND5_GRID_SURFACE.tif
+        splitted = bn.split('_')
+        id = '_'.join(splitted[:4])
+        bandName = splitted[7]
+
+        if id not in CONTAINERS.keys():
+            CONTAINERS[id] = dict()
+
+        bandSources = CONTAINERS[id]
+        if bandName not in bandSources.keys():
+            bandSources[bandName] = list()
+        bandSources[bandName].append(file)
+
+    #mosaic all scenes of same date
+    # and stack all bands related to the same channel
+    for id, bandSources in CONTAINERS.items():
+
+        pathVRT = id + '.vrt'
+        pathVRT = os.path.join(dirOut, pathVRT)
+        V = VirtualRasterBuilder()
+
+        #vrt = createVirtualBandStack(bandSources, pathVRT)
+        #add bands in sorted order
+        for bandName in sorted(bandSources.keys()):
+            vBandSources = bandSources[bandName]
+            VB = VirtualBand(name=bandName)
+            for path in vBandSources:
+                VB.addSourceBand(path, 0) #it's always one band only
+
+            V.addVirtualBand(VB)
+        #print(V)
+        V.saveVRT(pathVRT)
+        s = ""
+        #add ISO time stamp
+
+    pass
+
+def groupLandsat(dirIn, dirOut):
+
+    pass
+
+
+if __name__ == '__main__':
+    if True:
+        dirIn = r'H:\CBERS\hugo\Download20170523'
+        dirOut = r'H:\CBERS\VRTs'
+
+        groupCBERS(dirIn, dirOut)
+        exit(0)
+
+    if True:
+        dirIn = r'H:\CBERS\hugo\Download20170523'
+        dirOut = r'H:\CBERS\VRTs'
+        groupCBERS(dirIn, dirOut)
+
+    if True:
+        dirIn = r'H:\RapidEye\3A'
+        dirOut = r'H:\RapidEye\VRTs'
+        groupRapidEyeTiles(dirIn, dirOut)
\ No newline at end of file