diff --git a/timeseriesviewer/main.py b/timeseriesviewer/main.py
index d52d275f0c05586047a88d3d62176dcf07e974e6..b7f3e83fb7b2efd264f1b6a23a258407b9beb2dd 100644
--- a/timeseriesviewer/main.py
+++ b/timeseriesviewer/main.py
@@ -73,6 +73,16 @@ import pyqtgraph as pg
 
 
 class SpatialExtent(QgsRectangle):
+
+    @staticmethod
+    def fromMapCanvas(mapCanvas):
+        assert isinstance(mapCanvas, QgsMapCanvas)
+        extent = mapCanvas.extent()
+        crs = mapCanvas.mapSettings().destinationCrs()
+        return SpatialExtent(crs, extent)
+
+
+
     def __init__(self, crs, *args):
         assert isinstance(crs, QgsCoordinateReferenceSystem)
         super(SpatialExtent, self).__init__(*args)
@@ -133,6 +143,17 @@ class SpatialExtent(QgsRectangle):
     def __mul__(self, other):
         raise NotImplementedError()
 
+    def upperLeft(self):
+        return self.xMinimum(), self.yMaximum()
+
+    def lowerRight(self):
+        return self.xMaximum(), self.yMinimum()
+
+    def __repr__(self):
+
+        return '{} {} {}'.format(self.upperLeft(), self.lowerRight(), self.crs().authid())
+
+
 from timeseriesviewer.ui.widgets import *
 from timeseriesviewer.timeseries import TimeSeries, TimeSeriesDatum, SensorInstrument
 
@@ -287,21 +308,25 @@ class TimeSeriesDatumViewManager(QObject):
         super(TimeSeriesDatumViewManager, self).__init__()
 
         self.TSV = timeSeriesViewer
-
         self.TSDViews = list()
+
         self.bandViewMananger = self.TSV.bandViewManager
         self.bandViewMananger.sigBandViewAdded.connect(self.addBandView)
         self.bandViewMananger.sigBandViewRemoved.connect(self.removeBandView)
         self.bandViewMananger.sigBandViewVisibility.connect(self.setBandViewVisibility)
 
-
-        self.setExtent(self.TSV.TS.getMaxExtent())
-
+        self.setSpatialExtent(self.TSV.TS.getMaxSpatialExtent())
         self.setMaxTSDViews()
-        self.setTimeSeries(self.TSV.TS)
+        self.initTimeSeries(self.TSV.TS)
         self.L = self.TSV.ui.scrollAreaSubsetContent.layout()
         self.setSubsetSize(QSize(100,50))
 
+
+    def activateMapTool(self, key):
+        for tsdv in self.TSDViews:
+            tsdv.activateMapTool(key)
+
+
     def setSubsetSize(self, size):
         assert isinstance(size, QSize)
         self.subsetSize = size
@@ -309,6 +334,10 @@ class TimeSeriesDatumViewManager(QObject):
             tsdv.setSubsetSize(size)
         self.adjustScrollArea()
 
+    def redraw(self):
+        for tsdv in self.TSDViews:
+            tsdv.redraw()
+
 
     def adjustScrollArea(self):
 
@@ -325,17 +354,16 @@ class TimeSeriesDatumViewManager(QObject):
             self.L.parentWidget().setFixedSize(w,h)
 
 
-    def setTimeSeries(self,TS):
+    def initTimeSeries(self, TS):
         assert isinstance(TS, TimeSeries)
         self.TS = TS
-        self.TS.sigTimeSeriesDatumAdded.connect(self.createTSDView)
-
+        self.TS.sigTimeSeriesDatesAdded.connect(self.createTSDViews)
 
     def setMaxTSDViews(self, n=-1):
         self.nMaxTSDViews = n
         #todo: remove views
 
-    def setExtent(self, extent):
+    def setSpatialExtent(self, extent):
         self.extent = extent
         if extent:
             assert isinstance(extent, SpatialExtent)
@@ -362,7 +390,7 @@ class TimeSeriesDatumViewManager(QObject):
         assert isinstance(isVisible, bool)
 
         for tsdv in self.TSDViews:
-            tsdv.ui.setBandViewVisibility(isVisible)
+            tsdv.setBandViewVisibility(bandView, isVisible)
 
 
     def addBandView(self, bandView):
@@ -389,19 +417,21 @@ class TimeSeriesDatumViewManager(QObject):
         for tsdv in self.TSDViews:
             tsdv.removeBandView(bandView)
 
-
-
-    def createTSDView(self, TSD):
-        assert isinstance(TSD, TimeSeriesDatum)
-        tsdView = TimeSeriesDatumView(TSD)
-        tsdView.setSubsetSize(self.subsetSize)
-        if self.extent:
-            tsdView.setSpatialExtent(self.extent)
-        self.addTSDView(tsdView)
+    def createTSDViews(self, timeSeriesDates):
+        for TSD in timeSeriesDates:
+            assert isinstance(TSD, TimeSeriesDatum)
+            tsdView = TimeSeriesDatumView(TSD)
+            tsdView.setSubsetSize(self.subsetSize)
+            tsdView.sigExtentsChanged.connect(self.setSpatialExtent)
+            for i, bandView in enumerate(self.bandViewMananger):
+                tsdView.insertBandView(bandView)
+            if self.extent:
+                tsdView.setSpatialExtent(self.extent)
+            self.addTSDView(tsdView)
 
 
     def removeTSD(self, TSD):
-        assert isinstance(TSDV, TimeSeriesDatum)
+        assert isinstance(TSD, TimeSeriesDatum)
         tsdvs = [tsdv for tsdv in self.TSDViews if tsdv.TSD == TSD]
         assert len(tsdvs) == 1
         self.removeTSDView(tsdvs[0])
@@ -431,24 +461,41 @@ class TimeSeriesDatumViewManager(QObject):
 
 class BandView(QObject):
 
-    sigAddBandView = pyqtSignal(object)
+
     sigRemoveBandView = pyqtSignal(object)
-    sigHideBandView = pyqtSignal(bool)
+    #sigBandViewVisibility = pyqtSignal(bool)
+    sigHideBandView = pyqtSignal()
+    sigShowBandView = pyqtSignal()
+    sigTitleChanged = pyqtSignal(str)
 
     def __init__(self, recommended_bands=None, parent=None, showSensorNames=True):
         super(BandView, self).__init__()
-        self.ui = BandViewUI(parent)
+        self.ui = MapViewUI(parent)
         self.ui.create()
+        self.setVisibility(True)
 
         #forward actions with reference to this band view
-        self.ui.actionAddBandView.triggered.connect(lambda : self.sigAddBandView.emit(self))
+
         self.ui.actionRemoveBandView.triggered.connect(lambda: self.sigRemoveBandView.emit(self))
-        self.ui.actionHideBandView.toggled.connect(self.sigHideBandView)
+        self.ui.sigHideBandView.connect(lambda : self.sigHideBandView.emit())
+        self.ui.sigShowBandView.connect(lambda: self.sigShowBandView.emit())
         self.sensorViews = collections.OrderedDict()
         self.mShowSensorNames = showSensorNames
 
+
+    def setVisibility(self, isVisible):
+        self.ui.setVisibility(isVisible)
+
+
+    def visibility(self):
+        return self.ui.visibility()
+
     def setTitle(self, title):
         self.ui.labelViewName.setText(title)
+        self.sigTitleChanged.emit(self.ui.labelViewName.text())
+
+    def title(self):
+        return self.ui.labelViewName.text()
 
     def showSensorNames(self, b):
         assert isinstance(b, bool)
@@ -478,12 +525,12 @@ class BandView(QObject):
         """
         assert type(sensor) is SensorInstrument
         assert sensor not in self.sensorViews.keys()
-        w = BandViewRenderSettings(sensor)
-        w.showSensorName(self.mShowSensorNames)
+        w = MapViewRenderSettings(sensor)
+        w.showSensorName(False)
         self.sensorViews[sensor] = w
-        l = self.ui.sensorList.layout()
-        i = l.count() #last widget is a spacer
-        l.insertWidget(i-1, w.ui)
+        l = self.ui.layout()
+        i = l.count()
+        l.insertWidget(i, w.ui)
 
 
     def getSensorWidget(self, sensor):
@@ -508,6 +555,8 @@ class BandViewManager(QObject):
 
         self.TSV = timeSeriesViewer
         self.bandViews = []
+        self.bandViewButtons = dict()
+        self.recentBandViewDefinition = None
 
     def removeSensor(self, sensor):
         assert isinstance(sensor, SensorInstrument)
@@ -521,45 +570,73 @@ class BandViewManager(QObject):
 
 
     def createBandView(self):
+        btnList = self.TSV.ui.BVButtonList
+        btn = QToolButton(btnList)
+        btnList.layout().insertWidget(btnList.layout().count() - 1, btn)
+
         bandView = BandView(parent=self.TSV.ui.scrollAreaBandViewsContent, showSensorNames=False)
-        bandView.sigAddBandView.connect(self.createBandView)
         bandView.sigRemoveBandView.connect(self.removeBandView)
-        bandView.sigHideBandView.connect(lambda b: self.sigBandViewVisibility.emit(bandView, b))
-        self.addBandView(bandView)
-
-    def setBandViewVisibility(self, bandView, isVisible):
-        assert isinstance(bandView, BandView)
-        assert isinstance(isVisible, bool)
+        bandView.sigShowBandView.connect(lambda : self.sigBandViewVisibility.emit(bandView, bandView.visibility()))
+        bandView.sigHideBandView.connect(lambda: self.sigBandViewVisibility.emit(bandView, bandView.visibility()))
+        bandView.sigTitleChanged.connect(btn.setText)
 
+        #bandView.setTitle('#{}'.format(len(self)))
 
-
-    def addBandView(self, bandView):
-        assert isinstance(bandView, BandView)
-        l = self.TSV.BVP
+        self.bandViewButtons[bandView] = btn
         self.bandViews.append(bandView)
-        l.addWidget(bandView.ui)
-        n = len(self)
-
         for sensor in self.TSV.TS.Sensors:
             bandView.addSensor(sensor)
 
-        bandView.showSensorNames(n == 1)
-        bandView.setTitle('#{}'.format(n))
+        btn.clicked.connect(lambda : self.showBandViewDefinition(bandView))
+        self.refreshBandViewTitles()
         self.sigBandViewAdded.emit(bandView)
 
     def removeBandView(self, bandView):
         assert isinstance(bandView, BandView)
+        btn = self.bandViewButtons[bandView]
+        btnList = self.TSV.ui.BVButtonList
+
+
         self.bandViews.remove(bandView)
+        self.bandViewButtons.pop(bandView)
+        bandView.ui.setVisible(False)
+        btn.setVisible(False)
+        btnList.layout().removeWidget(btn)
         self.TSV.BVP.removeWidget(bandView.ui)
         bandView.ui.close()
-        #self.TSV.ui.scrollAreaBandViewsContent.update()
+        btn.close()
+        self.refreshBandViewTitles()
+        self.sigBandViewRemoved.emit(bandView)
+
+    def refreshBandViewTitles(self):
+        for i, bandView in enumerate(self.bandViews):
+            bandView.setTitle('#{}'.format(i + 1))
+
+
+    def showBandViewDefinition(self, bandView):
+        assert bandView in self.bandViews
+        if self.recentBandViewDefinition == bandView:
+            return
+        l = self.TSV.BVP
+        assert l.rowCount() == 2
+        assert l.columnCount() == 2
+        toRemove = l.itemAtPosition(0,0)
+        if toRemove:
+            oldBandViewUI = toRemove.widget()
+            oldBandViewUI.setVisible(False)
+            l.removeWidget(oldBandViewUI)
+        l.addWidget(bandView.ui, 0,0)
+        bandView.ui.setVisible(True)
+        self.recentBandViewDefinition = bandView
+        print(('show ', l.itemAtPosition(0,0).widget().labelViewName.text()))
+
+    def setBandViewVisibility(self, bandView, isVisible):
+        assert isinstance(bandView, BandView)
+        assert isinstance(isVisible, bool)
+
+
 
-        if len(self.bandViews) > 0:
-            self.bandViews[0].showSensorNames(True)
-            for i, bv in enumerate(self.bandViews):
-                bv.setTitle('#{}'.format(i + 1))
 
-        self.sigBandViewRemoved.emit(bandView)
 
     def __len__(self):
         return len(self.bandViews)
@@ -575,6 +652,9 @@ class BandViewManager(QObject):
 
 
 class TimeSeriesDatumView(QObject):
+
+    sigExtentsChanged = pyqtSignal(SpatialExtent)
+
     def __init__(self, TSD, parent=None):
 
         super(TimeSeriesDatumView, self).__init__()
@@ -590,15 +670,20 @@ class TimeSeriesDatumView(QObject):
         self.wOffset = self.L.count()-1
         self.setSubsetSize(QSize(150,100))
 
+
+    def activateMapTool(self, key):
+        for c in self.bandViewCanvases.values():
+            c.activateMapTool(key)
+
     def setBandViewVisibility(self, bandView, isVisible):
         self.bandViewCanvases[bandView].setVisible(isVisible)
 
-    def setSpatialExtent(self, extent):
-        assert isinstance(extent, SpatialExtent)
+    def setSpatialExtent(self, spatialExtent):
+        assert isinstance(spatialExtent, SpatialExtent)
 
         for c in self.bandViewCanvases.values():
-            c.setExtent(extent)
-            c.setDestinationCrs(extent.crs())
+            c.setSpatialExtent(spatialExtent)
+
 
     def setSubsetSize(self, size):
         assert isinstance(size, QSize)
@@ -629,22 +714,29 @@ class TimeSeriesDatumView(QObject):
 
     def removeBandView(self, bandView):
         self.bandViewOrder.remove(bandView)
-        canvas = self.bandViewCanvases[bandView]
+        canvas = self.bandViewCanvases.pop(bandView)
         self.L.removeWidget(canvas)
         canvas.close()
 
+    def redraw(self):
+        for c in self.bandViewCanvases.values():
+            c.refreshAllLayers()
+
     def insertBandView(self, bandView, i=-1):
         assert isinstance(bandView, BandView)
         assert bandView not in self.bandViewOrder
-        assert len(self.bandViewCanvases) == len(self.bandViewOrder)
+        if len(self.bandViewCanvases) != len(self.bandViewOrder):
+            s = ""
 
         assert i >= -1 and i <= len(self.bandViewOrder)
         if i == -1:
             i = len(self.bandViewCanvases)
 
-        canvas = BandViewMapCanvas(self.ui)
+        canvas = MapViewMapCanvas(self.ui)
         canvas.setLayer(self.TSD.pathImg)
         canvas.setFixedSize(self.subsetSize)
+        canvas.extentsChanged.connect(lambda : self.sigExtentsChanged.emit(canvas.spatialExtent()))
+
 
         self.bandViewCanvases[bandView] = canvas
         self.bandViewOrder.insert(i, bandView)
@@ -674,416 +766,37 @@ class RenderJob(object):
 
 
 
-class PixmapBuffer(QObject):
-    sigProgress = pyqtSignal(int, int)
-    sigPixmapCreated = pyqtSignal(RenderJob, QPixmap)
-
-    def __init__(self):
-        super(PixmapBuffer, self).__init__()
-
-        self.extent = None
-        self.crs = None
-        self.size = None
-        self.nWorkersMax = 1
-        self.Workers = []
-        self.PIXMAPS = dict()
-        self.JOBS = []
-        self.nTotalJobs = -1
-        self.nJobsDone = -1
-
-    def addWorker(self):
-        w = Worker()
-        w.setCrsTransformEnabled(True)
-        w.sigPixmapCreated.connect(self.newPixmapCreated)
-        if self.isValid():
-            self.setWorkerProperties(w)
-        self.Workers.append(w)
-
-    def setWorkerProperties(self, worker):
-        assert isinstance(worker, Worker)
-        worker.setFixedSize(self.size)
-        worker.setDestinationCrs(self.crs)
-        worker.setExtent(self.extent)
-        worker.setCenter(self.extent.center())
-
-    def newPixmapCreated(self, renderJob, pixmap):
-        self.JOBS.remove(renderJob)
-        self.nJobsDone += 1
-        self.sigPixmapCreated.emit(renderJob, pixmap)
-        self.sigProgress.emit(self.nJobsDone, self.nTotalJobs)
-
-    def isValid(self):
-        return self.extent != None and self.crs != None and self.size != None
-
-    def setExtent(self, extent, crs, maxPx):
-        self.stopRendering()
-        assert isinstance(extent, QgsRectangle)
-        assert isinstance(crs, QgsCoordinateReferenceSystem)
-        assert isinstance(maxPx, int)
-        ratio = extent.width() / extent.height()
-        if ratio < 1:  # x is largest side
-            size = QSize(maxPx, int(maxPx / ratio))
-        else:  # y is largest
-            size = QSize(int(maxPx * ratio), maxPx)
-
-        self.crs = crs
-        self.size = size
-        self.extent = extent
-        for w in self.Workers:
-            self.setWorkerProperties(w)
-        return size
-
-    def stopRendering(self):
-        for w in self.Workers:
-            w.stopRendering()
-            w.clear()
-        while len(self.JOBS) > 0:
-            self.JOBS.pop(0)
-        self.sigProgress.emit(0, 0)
-
-    def loadSubsets(self, jobs):
-        for j in jobs:
-            assert isinstance(j, RenderJob)
-
-        self.stopRendering()
-
-        self.JOBS.extend(jobs)
-        self.nTotalJobs = len(self.JOBS)
-        self.nJobsDone = 0
-        self.sigProgress.emit(0, self.nTotalJobs)
-
-        if len(self.Workers) == 0:
-            self.addWorker()
-
-        #split jobs to number of workers
-        i = 0
-        chunkSize = int(len(self.JOBS) / len(self.Workers))
-        assert chunkSize > 0
-        for i in range(0, len(self.Workers), chunkSize):
-            worker = self.Workers[i]
-            j = min(i+chunkSize, len(self.JOBS))
-            worker.startLayerRendering(self.JOBS[i:j])
-
-class Worker(QgsMapCanvas):
-
-    sigPixmapCreated = pyqtSignal(RenderJob, QPixmap)
-
-
-    def __init__(self, *args, **kwds):
-        super(Worker,self).__init__(*args, **kwds)
-        self.reg = QgsMapLayerRegistry.instance()
-        self.painter = QPainter()
-        self.renderJobs = list()
-        self.mapCanvasRefreshed.connect(self.createPixmap)
-
-    def isBusy(self):
-        return len(self.renderJobs) != 0
-
-    def createPixmap(self, *args):
-        if len(self.renderJobs) > 0:
-            pixmap = QPixmap(self.size())
-            self.painter.begin(pixmap)
-            self.map().paint(self.painter)
-            self.painter.end()
-            assert not pixmap.isNull()
-            job = self.renderJobs.pop(0)
-            self.sigPixmapCreated.emit(job, pixmap)
-            self.startSingleLayerRendering()
-
-    def stopLayerRendering(self):
-        self.stopRendering()
-        del self.renderJobs[:]
-        assert self.isBusy() is False
-
-    def startLayerRendering(self, renderJobs):
-        assert isinstance(renderJobs, list)
-        self.renderJobs.extend(renderJobs)
-        self.startSingleLayerRendering()
-
-
-
-    def startSingleLayerRendering(self):
-
-        if len(self.renderJobs) > 0:
-            renderJob = self.renderJobs[0]
-            assert isinstance(renderJob, RenderJob)
-            #mapLayer = QgsRasterLayer(renderJob.TSD.pathImg)
-            mapLayer = renderJob.TSD.lyrImg
-            mapLayer.setRenderer(renderJob.renderer)
-            dprint('QgsMapLayerRegistry count: {}'.format(self.reg.count()))
-            self.reg.addMapLayer(mapLayer)
-
-            lyrSet = [QgsMapCanvasLayer(mapLayer)]
-            self.setLayerSet(lyrSet)
-
-            #todo: add crosshair
-            self.refreshAllLayers()
-
-
-class ImageChipLabel(QLabel):
-
-    clicked = pyqtSignal(object, object)
-
-
-    def __init__(self, time_series_viewer, TSD, renderer):
-        assert isinstance(time_series_viewer, TimeSeriesViewer)
-        assert isinstance(TSD, TimeSeriesDatum)
-        assert isinstance(renderer, QgsRasterRenderer)
-
-        super(ImageChipLabel, self).__init__(time_series_viewer.ui)
-        self.TSV = time_series_viewer
-        self.TSD = TSD
-        self.bn = os.path.basename(self.TSD.pathImg)
-
-        self.renderer = renderer
-        self.setContextMenuPolicy(Qt.DefaultContextMenu)
-        self.setFrameShape(QFrame.StyledPanel)
-        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
-
-        tt = ['Date: {}'.format(TSD.date) \
-             ,'Name: {}'.format(self.bn) \
-             ]
-
-        self.setToolTip(list2str(tt))
-
-    def mouseReleaseEvent(self, event):
-	    self.clicked.emit(self, event)
-
-
-
-    def contextMenuEvent(self, event):
-        menu = QMenu()
-        #add general options
-
-        action = menu.addAction('Copy to clipboard')
-        action.triggered.connect(lambda : QApplication.clipboard().setPixmap(self.pixmap()))
-
-
-        #add QGIS specific options
-        if self.TSV.iface:
-            action = menu.addAction('Add {} to QGIS layers'.format(self.bn))
-            action.triggered.connect(lambda : qgis_add_ins.add_QgsRasterLayer(self.iface, self.TSD.pathImg, self.bands))
-
-        menu.exec_(event.globalPos())
-
-
-
-
-
-def getBoundingBoxPolygon(points, srs=None):
-    ring = ogr.Geometry(ogr.wkbLinearRing)
-    for point in points:
-        ring.AddPoint(point[0], point[1])
-    bb = ogr.Geometry(ogr.wkbPolygon)
-    bb.AddGeometry(ring)
-    if isinstance(srs, QgsCoordinateReferenceSystem):
-
-        _crs = osr.SpatialReference()
-        _crs.ImportFromWkt(srs.toWkt())
-        bb.AssignSpatialReference(_crs)
-    return bb
-
-
-
-
+list2str = lambda ll : '\n'.join([str(l) for l in ll])
 
 
-def getDS(ds):
-    if type(ds) is not gdal.Dataset:
-        ds = gdal.Open(ds)
-    return ds
+class QgsInstanceInteraction(QObject):
 
+    def __init__(self, iface, TSV_UI):
+        super(QgsInstanceInteraction, self).__init__()
 
+        self.iface = iface
+        self.ui = TSV_UI
+        self.cbVectorLayer = TSV_UI.cbQgsVectorLayer
 
-def getBandNames(lyr):
-    assert isinstance(lyr, QgsRasterLayer)
-    dp = lyr.dataProvider()
-    assert isinstance(dp, QgsRasterDataProvider)
-    if str(dp.name()) == 'gdal':
+    def extent(self):
         s = ""
-    else:
-        return lyr
-
-def getImageDate(ds):
-    if type(ds) is str:
-        ds = gdal.Open(ds)
-
-    path = ds.GetFileList()[0]
-    to_check = [os.path.basename(path), os.path.dirname(path)]
-
-    regAcqDate = re.compile(r'acquisition (time|date|day)', re.I)
-    for key, value in ds.GetMetadata_Dict().items():
-        if regAcqDate.search(key):
-            to_check.insert(0, value)
-
-    for text in to_check:
-        date = parseAcquisitionDate(text)
-        if date:
-            return date
-
-    raise Exception('Can not identify acquisition date of {}'.format(path))
-
-
-
-def getChip3d(chips, rgb_idx, ranges):
-    assert len(rgb_idx) == 3 and len(rgb_idx) == len(ranges)
-    for i in rgb_idx:
-        assert i in chips.keys()
-
-    nl, ns = chips[rgb_idx[0]].shape
-    a3d = np.ndarray((3,nl,ns), dtype='float')
-
-    for i, rgb_i in enumerate(rgb_idx):
-        range = ranges[i]
-        data = chips[rgb_i].astype('float')
-        data -= range[0]
-        data *= 255./range[1]
-        a3d[i,:] = data
-
-    np.clip(a3d, 0, 255, out=a3d)
-
-    return a3d.astype('uint8')
-
-def Array2Image(d3d):
-    nb, nl, ns = d3d.shape
-    byteperline = nb
-    d3d = d3d.transpose([1,2,0]).copy()
-
-    return QImage(d3d.data, ns, nl, QImage.Format_RGB888)
-
-
-
-class ImageChipBuffer(object):
-
-
-    def __init__(self):
-        self.data = dict()
-        self.BBox = None
-        self.SRS = None
-        pass
-
-
-    def hasDataCube(self, TSD):
-        return TSD in self.data.keys()
-
-    def getMissingBands(self, TSD, bands):
-
-        missing = set(bands)
-        if TSD in self.data.keys():
-            missing = missing - set(self.data[TSD].keys())
-        return missing
-
-    def addDataCube(self, TSD, chipData):
-
-        assert self.BBox is not None, 'Please initialize the bounding box first.'
-        assert isinstance(chipData, dict)
-
-        if TSD not in self.data.keys():
-            self.data[TSD] = dict()
-        self.data[TSD].update(chipData)
-
-    def getDataCube(self, TSD):
-        return self.data.get(TSD)
-
-    def getChipArray(self, TSD, band_view, mode='rgb'):
-        assert mode in ['rgb', 'bgr']
-        bands = band_view.getBands(TSD.sensor)
-        band_ranges = band_view.getRanges(TSD.sensor)
-        nb = len(bands)
-        assert nb == 3 and nb == len(band_ranges)
-        assert TSD in self.data.keys(), 'Time Series Datum {} is not in buffer'.format(TSD.getDate())
-        chipData = self.data[TSD]
-        for b in bands:
-            assert b in chipData.keys()
-
 
+    def center(self):
+        s = ""
 
-        nl, ns = chipData[bands[0]].shape
-
-        dtype= 'uint8'
-        array_data = np.ndarray((nl,ns, nb), dtype=dtype)
-
-        if mode == 'rgb':
-            ch_dst = [0,1,2]
-        elif mode == 'bgr':
-            # r -> dst channel 2
-            # g -> dst channel 1
-            # b -> dst channel 0
-            ch_dst = [2,1,0]
-        for i, i_dst in enumerate(ch_dst):
-
-            offset = band_ranges[i][0]
-            scale = 255./band_ranges[i][1]
-
-            res = pg.rescaleData(chipData[bands[i]], scale, offset, dtype='float')
-            np.clip(res, 0, 255, out=res)
-            array_data[:,:,i_dst] = res
-
-        return array_data
-
-
-    def getChipRGB(self, TSD, band_view):
-        bands = band_view.getBands(TSD.sensor)
-        band_ranges = band_view.getRanges(TSD.sensor)
-        assert len(bands) == 3 and len(bands) == len(band_ranges)
-        assert TSD in self.data.keys(), 'Time Series Datum {} is not in buffer'.format(TSD.getDate())
-        chipData = self.data[TSD]
-        for b in bands:
-            assert b in chipData.keys()
-
-        nl, ns = chipData[bands[0]].shape
-        rgb_data = np.ndarray((3,nl,ns), dtype='float')
-
-        for i, b in enumerate(bands):
-            range = band_ranges[i]
-            data = chipData[b].astype('float')
-            data -= range[0]
-            data *= 255./range[1]
-            rgb_data[i,:] = data
-
-        np.clip(rgb_data, 0, 255, out=rgb_data)
-        rgb_data = rgb_data.astype('uint8')
-
-        if band_view.useMaskValues():
-            rgb = band_view.getMaskColor()
-            is_masked = np.where(np.logical_not(chipData['mask']))
-            for i, c in enumerate(rgb):
-                rgb_data[i, is_masked[0], is_masked[1]] = c
-
-        return  rgb_data
-
-    def getChipImage(self, date, view):
-        rgb = self.getChipRGB(date, view)
-        nb, nl, ns = rgb.shape
-        rgb = rgb.transpose([1,2,0]).copy('C')
-        return QImage(rgb.data, ns, nl, QImage.Format_RGB888)
-
-    def clear(self):
-        self.data.clear()
-
-    def setBoundingBox(self, BBox):
-        assert type(BBox) is ogr.Geometry
-        SRS = BBox.GetSpatialReference()
-        assert SRS is not None
-        if self.BBox is None or not self.BBox.Equals(BBox) or not self.SRS.IsSame(SRS):
-            self.clear()
-            self.BBox = BBox
-            self.SRS = SRS
-
-    def __repr__(self):
-        info = ['Chipbuffer']
-        info.append('Bounding Box: {}'.format(self.bbBoxWkt))
-        info.append('Chips: {}'.format(len(self.data)))
-        return '\n'.join(info)
-
-
-list2str = lambda ll : '\n'.join([str(l) for l in ll])
-
+    def crs(self):
+        s = ""
 
+    def getVectorLayerRepresentation(self):
+        if self.ui.gbQgsVectorLayer.isChecked():
+            lyr = self.cbVectorLayer.currentLayer()
+            alpha = self.ui.sliderQgsVectorTransparency.value()
+            return lyr
+        else:
+            return None
 
 
 class TimeSeriesViewer:
-    """QGIS Plugin Implementation."""
 
     def __init__(self, iface):
         """Constructor.
@@ -1094,22 +807,18 @@ class TimeSeriesViewer:
         :type iface: QgsInterface
         """
         # Save reference to the QGIS interface
-        self.iface = iface
-        if isinstance(self.iface, qgis.gui.QgisInterface):
-            import console
-            console.show_console()
-
-
-        # Create the dialog (after translation) and keep reference
         from timeseriesviewer.ui.widgets import TimeSeriesViewerUI
-        self.ui = TimeSeriesViewerUI()
 
+        self.ui = TimeSeriesViewerUI()
+        if iface:
+            import timeseriesviewer
+            timeseriesviewer.QGIS_TSV_BRIDGE = QgsInstanceInteraction(iface, self.ui)
+            self.ui.setQgsLinkWidgets()
 
         #init empty time series
         self.TS = TimeSeries()
         self.hasInitialCenterPoint = False
-        self.TS.sigTimeSeriesDatumAdded.connect(self.ua_datumAdded)
-        self.TS.sigChanged.connect(self.timeseriesChanged)
+        self.TS.sigTimeSeriesDatesAdded.connect(self.datesAdded)
         self.TS.sigProgress.connect(self.ua_TSprogress)
 
         #init TS model
@@ -1125,11 +834,6 @@ class TimeSeriesViewer:
         self.bandViewManager = BandViewManager(self)
         self.timeSeriesViewManager = TimeSeriesDatumViewManager(self)
 
-        self.ImageChipBuffer = ImageChipBuffer()
-        self.PIXMAPS = PixmapBuffer()
-        self.PIXMAPS.sigPixmapCreated.connect(self.showSubset)
-        self.CHIPWIDGETS = collections.OrderedDict()
-
         self.ValidatorPxX = QIntValidator(0,99999)
         self.ValidatorPxY = QIntValidator(0,99999)
 
@@ -1137,23 +841,30 @@ class TimeSeriesViewer:
 
         #D.btn_showPxCoordinate.clicked.connect(lambda: self.showSubsetsStart())
         #connect actions with logic
-        D.actionSelectCenter.triggered.connect(self.ua_selectByCoordinate)
-        D.actionSelectArea.triggered.connect(self.ua_selectByRectangle)
+
+        D.actionSelectCenter.triggered.connect(lambda : self.timeSeriesViewManager.activateMapTool('selectCenter'))
+        D.actionSelectArea.triggered.connect(lambda : self.timeSeriesViewManager.activateMapTool('selectArea'))
+        D.actionZoomMaxExtent.triggered.connect(lambda : self.zoomTo('maxExtent'))
+        D.actionZoomPixelScale.triggered.connect(lambda: self.zoomTo('pixelScale'))
+        D.actionZoomIn.triggered.connect(lambda: self.timeSeriesViewManager.activateMapTool('zoomIn'))
+        D.actionZoomOut.triggered.connect(lambda: self.timeSeriesViewManager.activateMapTool('zoomOut'))
+        D.actionPan.triggered.connect(lambda: self.timeSeriesViewManager.activateMapTool('pan'))
 
         D.actionAddBandView.triggered.connect(self.bandViewManager.createBandView)
 
         D.actionAddTSD.triggered.connect(self.ua_addTSImages)
         D.actionRemoveTSD.triggered.connect(self.removeTimeSeriesDates)
-
-        D.actionLoadTS.triggered.connect(self.ua_loadTSFile)
+        D.actionRedraw.triggered.connect(self.timeSeriesViewManager.redraw)
+        D.actionLoadTS.triggered.connect(self.loadTimeSeries)
         D.actionClearTS.triggered.connect(self.clearTimeSeries)
         D.actionSaveTS.triggered.connect(self.ua_saveTSFile)
         D.actionAddTSExample.triggered.connect(self.ua_loadExampleTS)
 
+
         #connect buttons with actions
         D.btnClearLabelList.clicked.connect(D.tbCollectedLabels.clear)
-        D.actionAbout.triggered.connect( \
-            lambda: QMessageBox.about(self.ui, 'SenseCarbon TimeSeriesViewer', 'A viewer to visualize raster time series data'))
+        D.actionAbout.triggered.connect(lambda: AboutDialogUI(self.ui).exec_())
+        D.actionSettings.triggered.connect(lambda : PropertyDialogUI(self.ui).exec_())
 
         D.actionFirstTSD.triggered.connect(lambda: self.setDOISliderValue('first'))
         D.actionLastTSD.triggered.connect(lambda: self.setDOISliderValue('last'))
@@ -1165,22 +876,17 @@ class TimeSeriesViewer:
 
         D.actionSetSubsetSize.triggered.connect(lambda : self.timeSeriesViewManager.setSubsetSize(
                                                 self.ui.subsetSize()))
-        D.actionSetExtent.triggered.connect(lambda: self.timeSeriesViewManager.setExtent(self.ui.extent()))
+        D.actionSetExtent.triggered.connect(lambda: self.timeSeriesViewManager.setSpatialExtent(self.ui.spatialExtent()))
 
-        self.RectangleMapTool = None
-        self.PointMapTool = None
         self.canvasCrs = QgsCoordinateReferenceSystem()
 
-        if self.iface:
 
-            self.canvas = self.iface.mapCanvas()
-            self.RectangleMapTool = qgis_add_ins.RectangleMapTool(self.canvas)
-            self.RectangleMapTool.rectangleDrawed.connect(self.setSpatialSubset)
-            self.PointMapTool = qgis_add_ins.PointMapTool(self.canvas)
-            self.PointMapTool.coordinateSelected.connect(self.setSpatialSubset)
-            self.ui.setQgsLinkWidgets(True)
-        else:
-            self.ui.setQgsLinkWidgets(False)
+    def zoomTo(self, key):
+        if key == 'maxExtent':
+            ext = self.TS.getMaxSpatialExtent(self.ui.crs())
+            self.timeSeriesViewManager.setSpatialExtent(ext)
+        elif key == 'pixelScale':
+            s = ""
 
 
     def setDOISliderValue(self, key):
@@ -1203,7 +909,7 @@ class TimeSeriesViewer:
         if len(self.TS) == 0:
             text = '<empty timeseries>'
         else:
-            assert i < len(self.TS)
+            assert i <= len(self.TS)
             TSD = self.TS.data[i - 1]
             text = str(TSD.date)
 
@@ -1212,9 +918,6 @@ class TimeSeriesViewer:
         if TSD:
             self.timeSeriesViewManager.navToDOI(TSD)
 
-    @staticmethod
-    def icon():
-        return QIcon(':/plugins/SenseCarbon/icon.png')
 
     def icon(self):
         return TimeSeriesViewer.icon()
@@ -1222,21 +925,32 @@ class TimeSeriesViewer:
     def timeseriesChanged(self):
         D = self.ui
         D.sliderDOI.setMinimum(1)
-        D.sliderDOI.setMaximum(len(self.TS.data))
-        if len(self.TS.data)>0 and not self.hasInitialCenterPoint:
-            extent = self.TS.getMaxExtent(self.canvasCrs)
-            self.setSpatialSubset(extent.center(), self.canvasCrs)
-            self.hasInitialCenterPoint = True
-        if len(self.TS.data) == 0:
-            self.hasInitialCenterPoint = False
-        else:
+        l = len(self.TS.data)
+        D.sliderDOI.setMaximum(l)
+        #get meaningfull tick intervall
+        for tickInterval in [1,5,10,25,50,100,200]:
+            if (D.sliderDOI.size().width() / float(l) * tickInterval) > 5:
+                break
+        D.sliderDOI.setTickInterval(tickInterval)
+
+        if not self.hasInitialCenterPoint:
+            if len(self.TS.data) > 0:
+                extent = self.TS.getMaxSpatialExtent(self.canvasCrs)
+                self.timeSeriesViewManager.setSubsetSize(self.ui.subsetSize())
+                self.timeSeriesViewManager.setSpatialExtent(extent)
+                self.ui.setSpatialExtent(extent)
+                self.hasInitialCenterPoint = True
+
             if len(self.bandViewManager) == 0:
                 # add two empty band-views by default
-                self.ua_addBandView()
-                self.ua_addBandView()
+                self.bandViewManager.createBandView()
+                self.bandViewManager.createBandView()
+
+        if len(self.TS.data) == 0:
+            self.hasInitialCenterPoint = False
 
 
-    def ua_loadTSFile(self, path=None, n_max=None):
+    def loadTimeSeries(self, path=None, n_max=None):
         if path is None or path is False:
             path = QFileDialog.getOpenFileName(self.ui, 'Open Time Series file', '')
 
@@ -1260,36 +974,24 @@ class TimeSeriesViewer:
         if not os.path.exists(PATH_EXAMPLE_TIMESERIES):
             QMessageBox.information(self.ui, 'File not found', '{} - this file describes an exemplary time series.'.format(path_example))
         else:
-            self.ua_loadTSFile(path=PATH_EXAMPLE_TIMESERIES)
+            self.loadTimeSeries(path=PATH_EXAMPLE_TIMESERIES)
 
 
 
     def ua_selectByRectangle(self):
         if self.RectangleMapTool is not None:
-            self.canvas.setMapTool(self.RectangleMapTool)
+            self.qgsCanvas.setMapTool(self.RectangleMapTool)
 
     def ua_selectByCoordinate(self):
         if self.PointMapTool is not None:
-            self.canvas.setMapTool(self.PointMapTool)
-
-    def setCanvasSRS(self,srs):
-        assert isinstance(srs, QgsCoordinateReferenceSystem)
-        #self.canvas_srs = srs
-        self.canvasCrs = srs
-        self.ui.tb_bb_srs.setPlainText(self.canvasCrs.toWkt())
+            self.qgsCanvas.setMapTool(self.PointMapTool)
 
     #todo: define as qt slot
-    def setSpatialSubset(self, geometry, crs):
-        assert isinstance(crs, QgsCoordinateReferenceSystem)
-        assert isinstance(geometry, QgsRectangle) or isinstance(geometry, QgsPoint)
-
-        extent = self.ui.spatialExtent()
-        if type(geometry) is QgsPoint:
-            extent.setCenter(geometry, crs)
-
-        if type(geometry) is QgsRectangle:
-            extent = SpatialExtent(crs,geometry).toCrs(self.ui.crs())
+    def setSpatialSubset(self, spatialExtent):
+        #keep specified CRS but translate extent
+        oldExtent = self.ui.spatialExtent()
         self.ui.setSpatialExtent(extent)
+        self.timeSeriesViewManager.setSpatialExtent(extent)
 
     def qgs_handleMouseDown(self, pt, btn):
         pass
@@ -1307,13 +1009,10 @@ class TimeSeriesViewer:
 
             P.setValue(v)
 
-    def ua_datumAdded(self, TSD):
-
-        if len(self.TS) == 1:
-            self.ui.setSpatialExtent(TSD.spatialExtent())
-
+    def datesAdded(self, dates):
+        assert isinstance(dates, list)
         self.ui.tableView_TimeSeries.resizeColumnsToContents()
-
+        self.timeseriesChanged()
 
 
     # noinspection PyMethodMayBeStatic
@@ -1374,123 +1073,6 @@ class TimeSeriesViewer:
         HBar.setValue(i_doi * step)
 
 
-    def showSubsetsStart(self):
-
-        if len(self.TS) == 0:
-            return
-
-        D = self.ui
-        easting = QgsVector(D.doubleSpinBox_subset_size_x.value(), 0.0)
-        northing = QgsVector(0.0, D.doubleSpinBox_subset_size_y.value())
-
-        Center = QgsPoint(D.spinBox_coordinate_x.value(), D.spinBox_coordinate_y.value())
-        UL = Center - (easting * 0.5) + (northing * 0.5)
-        LR = Center + (easting * 0.5) - (northing * 0.5)
-        extent = QgsRectangle(UL,LR)
-        maxPx = int(D.spinBoxMaxPixmapSize.value())
-        pxSize = self.PIXMAPS.setExtent(extent, self.canvasCrs, maxPx)
-
-        #get the dates of interes
-        dates_of_interest = list()
-
-        doiTSD = self.TS.data[int(D.sliderDOI.value())-1]
-        centerDate = doiTSD.getDate()
-        allDates = self.TS.getObservationDates()
-        i_doi = allDates.index(centerDate)
-
-        if D.rb_showEntireTS.isChecked():
-            dates_of_interest = allDates
-        elif D.rb_showTimeWindow.isChecked():
-            i0 = max([0, i_doi-D.sb_ndates_before.value()])
-            ie = min([i_doi + D.sb_ndates_after.value(), len(allDates)-1])
-            dates_of_interest = allDates[i0:ie+1]
-
-
-        diff = set(dates_of_interest)
-        diff = diff.symmetric_difference(self.CHIPWIDGETS.keys())
-
-        self.clearLayoutWidgets(self.ICP)
-        self.CHIPWIDGETS.clear()
-
-
-
-        #initialize image labels
-
-        cnt_chips = 0
-
-        TSDs_of_interest = list()
-
-        for date in dates_of_interest:
-
-            #LV = QVBoxLayout()
-            #LV.setSizeConstraint(QLayout.SetNoConstraint)
-
-            for TSD in self.TS.getTSDs(date_of_interest=date):
-                TSDs_of_interest.append(TSD)
-                info_label_text = '{}\n{}'.format(TSD.date, TSD.sensor.sensorName)
-                textLabel = QLabel(info_label_text)
-                tt = [TSD.date,TSD.pathImg, TSD.pathMsk]
-                textLabel.setToolTip(list2str(tt))
-                self.ICP.addWidget(textLabel, 0, cnt_chips)
-                viewList = list()
-                j = 1
-                for view in self.bandViewManager:
-                    viewWidget = view.getSensorWidget(TSD.sensor)
-                    layerRenderer = viewWidget.layerRenderer()
-
-                    #imageLabel = QLabel()
-                    #imv = pg.GraphicsView()
-                    #imv = QGraphicsView(self.dlg.scrollArea_imageChip_content)
-                    #imv = MyGraphicsView(self.dlg.scrollArea_imageChip_content, iface=self.iface, path=TSD.pathImg, bands=bands)
-                    #imv = pg.ImageView(view=None)
-                    imgLabel = ImageChipLabel(self, TSD, layerRenderer)
-
-                    imgLabel.setMinimumSize(pxSize)
-                    imgLabel.setMaximumSize(pxSize)
-                    imgLabel.clicked.connect(self.ua_collect_date)
-
-
-                    viewList.append(imgLabel)
-                    self.ICP.addWidget(imgLabel, j, cnt_chips)
-                    j += 1
-
-                textLabel = QLabel(info_label_text)
-                textLabel.setToolTip(str(TSD))
-                self.ICP.addWidget(textLabel, j, cnt_chips)
-
-                self.CHIPWIDGETS[TSD] = viewList
-
-                cnt_chips += 1
-
-        self.ui.scrollArea_imageChip_content.update()
-
-        self.scrollToDate(centerDate)
-
-        #todo: start pixmap loading
-        #define render jobs
-        #(TSD, [renderers] in order of views)
-
-        LUT_RENDERER = {}
-        for view in self.bandViewManager:
-            for sensor in view.Sensors.keys():
-                if sensor not in LUT_RENDERER.keys():
-                    LUT_RENDERER[sensor] = []
-                LUT_RENDERER[sensor].append(
-                    view.getSensorWidget(sensor).layerRenderer()
-                )
-
-
-        jobs = []
-        for TSD in TSDs_of_interest:
-            for i, r in enumerate(LUT_RENDERER[TSD.sensor]):
-                jobs.append(RenderJob(TSD, r.clone(), destinationId=i))
-
-        #oder jobs by distance to DOI
-        jobs = sorted(jobs, key = lambda j: abs(j.TSD.date - doiTSD.date))
-
-        #todo: recycling to save loading time
-        self.PIXMAPS.loadSubsets(jobs)
-
     def showSubset(self, renderJob, pixmap):
 
         assert isinstance(renderJob, RenderJob)
@@ -1543,14 +1125,6 @@ class TimeSeriesViewer:
 
 
 
-    def ua_addBandView(self):
-        self.bandViewManager.createBandView()
-
-    def ua_removeBandView(self, bandView):
-        self.bandViewManager.removeBandView(bandView)
-
-
-
     def clearTimeSeries(self):
         #remove views