diff --git a/deploy/app.js b/deploy/app.js
index d62f1edf76234d12614bae93c6df97d03e49e1af..3364e2113a0a746bc60d8aac2d3457c3bff70b5d 100644
--- a/deploy/app.js
+++ b/deploy/app.js
@@ -7,6 +7,8 @@ const MemoryStore = require('memorystore')(session)
 const crypto = require('crypto')
 const cookieParser = require('cookie-parser')
 
+const { router: regionalFeaturesRouter, regionalFeaturesIsReady } = require('./regionalFeatures')
+
 const LOCAL_CDN_FLAG = !!process.env.PRECOMPUTED_SERVER
 
 if (process.env.NODE_ENV !== 'production') {
@@ -164,8 +166,10 @@ app.use('/logo', require('./logo'))
 
 app.get('/ready', async (req, res) => {
   const authIsReady = await authReady()
+  const regionalFeatureReady = await regionalFeaturesIsReady()
   const allReady = [ 
-    authIsReady
+    authIsReady,
+    regionalFeatureReady,
     /**
      * add other ready endpoints here
      * call sig is await fn(): boolean
@@ -214,6 +218,7 @@ app.use('/atlases', setResLocalMiddleWare('atlases'), atlasesRouter)
 app.use('/templates', setResLocalMiddleWare('templates'), jsonMiddleware, templateRouter)
 app.use('/nehubaConfig', jsonMiddleware, nehubaConfigRouter)
 app.use('/datasets', jsonMiddleware, datasetRouter)
+app.use('/regionalFeatures', jsonMiddleware, regionalFeaturesRouter)
 app.use('/plugins', jsonMiddleware, pluginRouter)
 app.use('/preview', jsonMiddleware, previewRouter)
 
diff --git a/deploy/regionalFeatures/index.js b/deploy/regionalFeatures/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e05545ab5499d23d8c6e60205c35b26b92a685ac
--- /dev/null
+++ b/deploy/regionalFeatures/index.js
@@ -0,0 +1,224 @@
+const router = require('express').Router()
+const request = require('request')
+
+/**
+ * TODO migrate to brainscape in the future
+ */
+
+const REGIONAL_FEATURE_ENDPOINT_ARRAY = process.env.REGIONAL_FEATURE_ENDPOINT_ARRAY || []
+
+let arrayToFetch = []
+try {
+  arrayToFetch = JSON.parse(REGIONAL_FEATURE_ENDPOINT_ARRAY)
+} catch (e) {
+  console.warn(`parsing arrayToFetch parse failed`)
+}
+
+const regionIdToDataIdMap = new Map()
+const datasetIdToDataMap = new Map()
+const datasetIdDetailMap = new Map()
+
+const ITERABLE_KEY_SYMBOL = Symbol('ITERABLE_KEY_SYMBOL')
+
+/**
+ * this pattern allows all of the special data to be fetched in parallel
+ * async await would mean it is fetched one at a time
+ */
+const init = Promise.all(
+  arrayToFetch.map(url =>
+    new Promise((rs, rj) => {
+      request.get(url, (err, _resp, body) => {
+        if (err) return rj(err)
+        const { regions, data, ['@id']: datasetId, type, name } = JSON.parse(body)
+        datasetIdDetailMap.set(datasetId, {
+          ['@id']: datasetId,
+          type,
+          name
+        })
+        for (const { status, ['@id']: regionId, name, files } of regions) {
+          if (regionIdToDataIdMap.has(regionId)) {
+            const existingObj = regionIdToDataIdMap.get(regionId)
+            existingObj[datasetId][status] = (existingObj[datasetId][status] || []).concat(files)
+            existingObj[datasetId][ITERABLE_KEY_SYMBOL] = existingObj[datasetId][ITERABLE_KEY_SYMBOL].concat(status)
+          } else {
+            const datasetObj = {
+              [status]: files,
+              type,
+            }
+            datasetObj[ITERABLE_KEY_SYMBOL] = [status]
+            const obj = {
+              name,
+              '@id': regionId,
+              [datasetId]: datasetObj
+            }
+            obj[ITERABLE_KEY_SYMBOL] = [datasetId]
+            regionIdToDataIdMap.set(regionId, obj)
+          }
+        }
+        
+        const dataIdToDataMap = new Map()
+        datasetIdToDataMap.set(datasetId, dataIdToDataMap)
+
+        for (const { ['@id']: dataId, contact_points: contactPoints, referenceSpaces } of data) {
+          dataIdToDataMap.set(dataId, {
+            ['@id']: dataId,
+            contactPoints,
+            referenceSpaces,
+          })
+        }
+        rs()
+      })
+    })
+  )
+)
+
+const getFeatureMiddleware = (req, res, next) => {
+  const { featureFullId } = req.params
+  const datasetIdToDataMapToUse = res.locals['overwrite_datasetIdToDataMap'] || datasetIdToDataMap
+  if (!datasetIdToDataMapToUse.has(featureFullId)) {
+    return res.status(404).send(`Not found. - getFeatureMiddleware -`)
+  }
+  res.locals['getFeatureMiddleware_cache_0'] = datasetIdToDataMapToUse.get(featureFullId)
+  res.locals['getFeatureMiddleware_cache_1'] = datasetIdDetailMap.get(featureFullId)
+  next()
+}
+
+const sendFeatureResponse = (req, res) => {
+  if (!res.locals['getFeatureMiddleware_cache_0']) return res.status(500).send(`getFeatureMiddleware_cache_0 not populated`)
+  const fullIdMap = res.locals['getFeatureMiddleware_cache_0']
+  const featureDetail = res.locals['getFeatureMiddleware_cache_1'] || {}
+  const dataKeys = Array.from(fullIdMap.keys())
+  return res.status(200).json({
+    ...featureDetail,
+    data: dataKeys.map(dataId => {
+      return {
+        ['@id']: dataId,
+      }
+    })
+  })
+}
+
+const getFeatureGetDataMiddleware = (req, res, next) => {
+  const { dataId } = req.params
+  if (!res.locals['getFeatureMiddleware_cache_0']) return res.status(500).send(`getFeatureMiddleware_cache_0 not populated`)
+  const map = res.locals['getFeatureMiddleware_cache_0']
+  if (!map.has(dataId)) {
+    return res.status(404).send(`Not found. - getFeatureGetDataMiddleware -`)
+  }
+  res.locals['getFeatureGetDataMiddleware_cache_0'] = map.get(dataId)
+  next()
+}
+
+const sendFeatureDataResponse = (req, res) => {
+  if (!res.locals['getFeatureGetDataMiddleware_cache_0']) return res.stauts(500).send(`getFeatureGetDataMiddleware_cache_0 not populated`)
+  const result = res.locals['getFeatureGetDataMiddleware_cache_0']
+  res.status(200).json(result)
+}
+
+router.get(
+  '/byFeature/:featureFullId',
+  getFeatureMiddleware,
+  sendFeatureResponse,
+)
+
+router.get(
+  '/byFeature/:featureFullId/:dataId',
+  getFeatureMiddleware,
+  getFeatureGetDataMiddleware,
+  sendFeatureDataResponse,
+)
+
+const byRegionMiddleware = (req, res, next) => {
+
+  const { regionFullId } = req.params
+  const { hemisphere, referenceSpaceId } = req.query
+
+  if (!regionIdToDataIdMap.has(regionFullId)) {
+    return res.status(404).send(`Not found. - byRegionMiddleware -`)
+  }
+
+  /**
+   * datasetIdToDataMap:
+   * datasetId -> dataId -> { ['@id']: string, contactPoints, referenceSpaces }
+   */
+  const overWriteDatasetIdToMap = new Map()
+  res.locals['byRegionMiddleware_cache_0'] = overWriteDatasetIdToMap
+  res.locals['overwrite_datasetIdToDataMap'] = overWriteDatasetIdToMap
+
+  /**
+   * TODO filter by reference spaces
+   */
+
+  const regionObj = regionIdToDataIdMap.get(regionFullId)
+
+  for (const datasetId of regionObj[ITERABLE_KEY_SYMBOL]) {
+    const returnMap = new Map()
+    overWriteDatasetIdToMap.set(datasetId, returnMap)
+    for (const hemisphereKey of regionObj[datasetId][ITERABLE_KEY_SYMBOL]) {
+      
+      /**
+       * if hemisphere is defined, then skip if hemisphereKey does not match
+       */
+
+      if (!!hemisphere && hemisphereKey !== hemisphere) continue
+      for (const { ['@id']: dataId } of regionObj[datasetId][hemisphereKey] || []) {
+        try {
+          const dataObj = datasetIdToDataMap.get(datasetId).get(dataId)
+          if (
+            !!referenceSpaceId
+            && !! dataObj['referenceSpaces']
+            && dataObj['referenceSpaces'].every(rs => rs['fullId'] !== referenceSpaceId)
+          ) {
+            continue
+          }
+          returnMap.set(
+            dataId,
+            dataObj
+          )
+        } catch (e) {
+          console.warn(`${datasetId} or ${dataId} could not be found in datasetIdToDataMap`)
+        }
+      }
+    }
+  }
+
+  next()
+}
+
+router.get(
+  '/byRegion/:regionFullId',
+  byRegionMiddleware,
+  async (req, res) => {
+    if (!res.locals['byRegionMiddleware_cache_0']) return res.status(500).send(`byRegionMiddleware_cache_0 not populated`)
+
+    const returnMap = res.locals['byRegionMiddleware_cache_0']
+    return res.status(200).json({
+      features: Array.from(returnMap.keys()).map(id => ({ ['@id']: id }))
+    })
+  }
+)
+
+router.get(
+  '/byRegion/:regionFullId/:featureFullId',
+  byRegionMiddleware,
+  getFeatureMiddleware,
+  sendFeatureResponse,
+)
+
+router.get(
+  '/byRegion/:regionFullId/:featureFullId/:dataId',
+  byRegionMiddleware,
+  getFeatureMiddleware,
+  getFeatureGetDataMiddleware,
+  sendFeatureDataResponse,
+)
+
+const regionalFeatureIsReady = () => Promise.race([
+  init.then(() => true),
+  new Promise(rs => setTimeout(() => rs(false), 500))
+])
+
+module.exports = {
+  router,
+  regionalFeatureIsReady,
+}
diff --git a/docs/releases/v2.3.0.md b/docs/releases/v2.3.0.md
index a0c965335ab9379b2a8ebfd3f29282255fd9a360..168e0384fb236945f7766bd619d092035e351136 100644
--- a/docs/releases/v2.3.0.md
+++ b/docs/releases/v2.3.0.md
@@ -12,6 +12,7 @@
 - tweaked mesh loading strategies. Now it will wait for all image chunks to be loaded before loading any meshes
 - showing contributors to a regional feature/dataset if publications are not available
 - added the ability to customize preview origin dataset to labels other to `View probability map`
+- **experimental** : previewing of curated regional features: iEEG coordinates
 
 ## Bugfixes:
 
diff --git a/src/atlasViewer/atlasViewer.apiService.service.spec.ts b/src/atlasViewer/atlasViewer.apiService.service.spec.ts
index 846b29bd7c57d4ae34735873fa9029fb3ac2619d..f8dd6f5a8eb71361a90e4dbf07069205808d6235 100644
--- a/src/atlasViewer/atlasViewer.apiService.service.spec.ts
+++ b/src/atlasViewer/atlasViewer.apiService.service.spec.ts
@@ -1,9 +1,8 @@
-import { AtlasViewerAPIServices, overrideNehubaClickFactory, CANCELLABLE_DIALOG } from "src/atlasViewer/atlasViewer.apiService.service";
+import { AtlasViewerAPIServices, CANCELLABLE_DIALOG } from "src/atlasViewer/atlasViewer.apiService.service";
 import { async, TestBed, fakeAsync, tick } from "@angular/core/testing";
 import { provideMockStore } from "@ngrx/store/testing";
 import { defaultRootState } from "src/services/stateStore.service";
 import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module";
-import { HttpClientModule } from '@angular/common/http';
 import { WidgetModule } from 'src/widget';
 import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
 import { PluginServices } from "./pluginUnit";
@@ -276,376 +275,4 @@ describe('atlasViewer.apiService.service.ts', () => {
       })
     })
   })
-
-
-  describe('overrideNehubaClickFactory', () => {
-
-    const OVERRIDE_NEHUBA_TOKEN = 'OVERRIDE_NEHUBA_TOKEN'
-    const MOCK_GET_STATE_SNAPSHOT_TOKEN = 'MOCK_GET_STATE_SNAPSHOT_TOKEN'
-
-    let mockGetMouseOverSegments = []
-    let mousePositionReal = [1,2,3]
-    
-    afterEach(() => {
-      mockGetMouseOverSegments = []
-    })
-
-    beforeEach(async(() => {
-      TestBed.configureTestingModule({
-        imports: [
-          AngularMaterialModule,
-          HttpClientModule,
-          WidgetModule,
-        ],
-        providers: [
-          {
-            provide: OVERRIDE_NEHUBA_TOKEN,
-            useFactory: overrideNehubaClickFactory,
-            deps: [
-              AtlasViewerAPIServices,
-              MOCK_GET_STATE_SNAPSHOT_TOKEN,
-            ]
-          },
-          {
-            provide: MOCK_GET_STATE_SNAPSHOT_TOKEN,
-            useValue: () => {
-              return {
-                state: {
-                  uiState: {
-                    mouseOverSegments: mockGetMouseOverSegments
-                  }
-                },
-                other: {
-                  mousePositionReal
-                }
-              }
-            }
-          },
-          {
-            provide: PluginServices,
-            useValue: {}
-          },
-          AtlasViewerAPIServices,
-          provideMockStore({ initialState: defaultRootState }),
-        ]
-      }).compileComponents()
-    }))
-
-    it('can obtain override fn', () => {
-      const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any)
-      expect(fn).not.toBeNull()
-    })
-
-    describe('> if getUserToSelectRegion.length === 0', () => {
-
-      it('by default, next fn will be called', () => {
-        const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-        const nextSpy = jasmine.createSpy('next')
-        fn(nextSpy)
-        expect(nextSpy).toHaveBeenCalled()
-      })
-  
-      it('if apiService.getUserToSelectRegion.length === 0, and mouseoversegment.length > 0 calls next', () => {
-        const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-  
-        const mockSegment = {
-          layer: {
-            name: 'apple'
-          },
-          segment: {
-            name: 'bananas'
-          }
-        }
-        mockGetMouseOverSegments = [ mockSegment ]
-        
-        const nextSpy = jasmine.createSpy('next')
-        fn(nextSpy)
-  
-        expect(nextSpy).toHaveBeenCalled()
-      })
-    })
-    describe('> if getUserToSelectRegion.length > 0', () => {
-      it('if both apiService.getUserToSelectRegion.length > 0 and mouseoverSegment.length >0, then next will not be called, but rs will be', () => {
-        const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-        const apiService = TestBed.inject(AtlasViewerAPIServices)
-  
-        const rsSpy = jasmine.createSpy('rs') 
-        const rjSpy = jasmine.createSpy('rj')
-        apiService.getUserToSelectRegion = [
-          {
-            message: 'test',
-            promise: null,
-            rs: rsSpy,
-            rj: rjSpy,
-          }
-        ]
-  
-        const mockSegment = {
-          layer: {
-            name: 'apple'
-          },
-          segment: {
-            name: 'bananas'
-          }
-        }
-        mockGetMouseOverSegments = [ mockSegment ]
-        
-        const nextSpy = jasmine.createSpy('next')
-        fn(nextSpy)
-  
-        expect(nextSpy).not.toHaveBeenCalled()
-        expect(rsSpy).toHaveBeenCalledWith(mockSegment)
-      })
-      it('if multiple getUserToSelectRegion handler exists, it resolves in a LIFO manner', () => {
-        const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-        const apiService = TestBed.inject(AtlasViewerAPIServices)
-  
-        const rsSpy1 = jasmine.createSpy('rs1') 
-        const rjSpy1 = jasmine.createSpy('rj1')
-  
-        const rsSpy2 = jasmine.createSpy('rs2') 
-        const rjSpy2 = jasmine.createSpy('rj2')
-        apiService.getUserToSelectRegion = [
-          {
-            message: 'test1',
-            promise: null,
-            rs: rsSpy1,
-            rj: rjSpy1,
-          },
-          {
-            message: 'test2',
-            promise: null,
-            rs: rsSpy2,
-            rj: rjSpy2,
-          }
-        ]
-        
-        const mockSegment = {
-          layer: {
-            name: 'apple'
-          },
-          segment: {
-            name: 'bananas'
-          }
-        }
-  
-        mockGetMouseOverSegments = [ mockSegment ]
-  
-        const nextSpy1 = jasmine.createSpy('next1')
-        fn(nextSpy1)
-  
-        expect(rsSpy2).toHaveBeenCalledWith(mockSegment)
-        expect(rjSpy2).not.toHaveBeenCalled()
-  
-        expect(nextSpy1).not.toHaveBeenCalled()
-        expect(rsSpy1).not.toHaveBeenCalled()
-        expect(rjSpy1).not.toHaveBeenCalled()
-  
-        const nextSpy2 = jasmine.createSpy('next2')
-        fn(nextSpy2)
-  
-        expect(nextSpy2).not.toHaveBeenCalled()
-        expect(rsSpy1).toHaveBeenCalledWith(mockSegment)
-        expect(rjSpy1).not.toHaveBeenCalled()
-  
-        const nextSpy3 = jasmine.createSpy('next3')
-        fn(nextSpy3)
-  
-        expect(nextSpy3).toHaveBeenCalled()
-      })
-
-      describe('> if spec is not set (defaults to parcellation region mode)', () => {
-
-        it('if apiService.getUserToSelectRegion.length > 0, but mouseoversegment.length ===0, will not call next, will not rs, will not call rj', () => {
-          const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-          const apiService = TestBed.inject(AtlasViewerAPIServices)
-    
-          const rsSpy = jasmine.createSpy('rs') 
-          const rjSpy = jasmine.createSpy('rj')
-          apiService.getUserToSelectRegion = [
-            {
-              message: 'test',
-              promise: null,
-              rs: rsSpy,
-              rj: rjSpy,
-            }
-          ]
-          
-          const nextSpy = jasmine.createSpy('next')
-          fn(nextSpy)
-    
-          expect(rsSpy).not.toHaveBeenCalled()
-          expect(nextSpy).toHaveBeenCalled()
-          expect(rjSpy).not.toHaveBeenCalled()
-        })
-      })
-
-      describe('> if spec is set', () => {
-        describe('> if spec is set to PARCELLATION_REGION', () => {
-
-          it('> mouseoversegment.length === 0, will not call next, will not rs, will not call rj', () => {
-            const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-            const apiService = TestBed.inject(AtlasViewerAPIServices)
-      
-            const rsSpy = jasmine.createSpy('rs') 
-            const rjSpy = jasmine.createSpy('rj')
-            apiService.getUserToSelectRegion = [
-              {
-                message: 'test',
-                promise: null,
-                spec: {
-                  type: 'PARCELLATION_REGION'
-                },
-                rs: rsSpy,
-                rj: rjSpy,
-              }
-            ]
-            
-            const nextSpy = jasmine.createSpy('next')
-            fn(nextSpy)
-      
-            expect(rsSpy).not.toHaveBeenCalled()
-            expect(nextSpy).toHaveBeenCalled()
-            expect(rjSpy).not.toHaveBeenCalled()
-          })
-          
-          it('> mouseoversegment.length > 0, will not call next, will call rs', () => {
-            const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-            const apiService = TestBed.inject(AtlasViewerAPIServices)
-      
-            const mockSegment = {
-              layer: {
-                name: 'apple'
-              },
-              segment: {
-                name: 'bananas'
-              }
-            }
-            mockGetMouseOverSegments = [ mockSegment ]
-
-            const rsSpy = jasmine.createSpy('rs') 
-            const rjSpy = jasmine.createSpy('rj')
-            apiService.getUserToSelectRegion = [
-              {
-                message: 'test',
-                promise: null,
-                spec: {
-                  type: 'PARCELLATION_REGION'
-                },
-                rs: rsSpy,
-                rj: rjSpy,
-              }
-            ]
-            
-            const nextSpy = jasmine.createSpy('next')
-            fn(nextSpy)
-      
-            expect(rsSpy).toHaveBeenCalled()
-            expect(nextSpy).not.toHaveBeenCalled()
-            expect(rjSpy).not.toHaveBeenCalled()
-          })
-        })
-
-        describe('> if spec is set to POINT', () => {
-          it('> rs is called if mouseoversegment.length === 0', () => {
-            const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-            const apiService = TestBed.inject(AtlasViewerAPIServices)
-      
-            const rsSpy = jasmine.createSpy('rs') 
-            const rjSpy = jasmine.createSpy('rj')
-            apiService.getUserToSelectRegion = [
-              {
-                message: 'test',
-                promise: null,
-                spec: {
-                  type: 'POINT'
-                },
-                rs: rsSpy,
-                rj: rjSpy,
-              }
-            ]
-            
-            const nextSpy = jasmine.createSpy('next')
-            fn(nextSpy)
-      
-            expect(rsSpy).toHaveBeenCalled()
-            expect(nextSpy).not.toHaveBeenCalled()
-            expect(rjSpy).not.toHaveBeenCalled()
-          })
-          it('> rs is called with correct arg if mouseoversegment.length > 0', () => {
-            const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-            const apiService = TestBed.inject(AtlasViewerAPIServices)
-      
-            const rsSpy = jasmine.createSpy('rs') 
-            const rjSpy = jasmine.createSpy('rj')
-            apiService.getUserToSelectRegion = [
-              {
-                message: 'test',
-                promise: null,
-                spec: {
-                  type: 'POINT'
-                },
-                rs: rsSpy,
-                rj: rjSpy,
-              }
-            ]
-            
-            const nextSpy = jasmine.createSpy('next')
-            fn(nextSpy)
-      
-            expect(rsSpy).toHaveBeenCalledWith({
-              type: 'POINT',
-              payload: mousePositionReal
-            })
-            expect(nextSpy).not.toHaveBeenCalled()
-            expect(rjSpy).not.toHaveBeenCalled()
-          })
-        })
-
-        describe('> if multiple getUserToSelectRegion exist', () => {
-          it('> only the last Promise will be evaluated', () => {
-            const fn = TestBed.inject(OVERRIDE_NEHUBA_TOKEN as any) as (next: () => void) => void
-            const apiService = TestBed.inject(AtlasViewerAPIServices)
-      
-            const rsSpy1 = jasmine.createSpy('rs1') 
-            const rjSpy1 = jasmine.createSpy('rj1')
-      
-            const rsSpy2 = jasmine.createSpy('rs2') 
-            const rjSpy2 = jasmine.createSpy('rj2')
-            apiService.getUserToSelectRegion = [
-              {
-                message: 'test1',
-                promise: null,
-                spec: {
-                  type: 'POINT'
-                },
-                rs: rsSpy1,
-                rj: rjSpy1,
-              },
-              {
-                message: 'test2',
-                promise: null,
-                spec: {
-                  type: 'PARCELLATION_REGION'
-                },
-                rs: rsSpy2,
-                rj: rjSpy2,
-              }
-            ]
-      
-            const nextSpy1 = jasmine.createSpy('next1')
-            fn(nextSpy1)
-      
-            expect(rsSpy2).not.toHaveBeenCalled()
-            expect(rjSpy2).not.toHaveBeenCalled()
-      
-            expect(nextSpy1).toHaveBeenCalled()
-            expect(rsSpy1).not.toHaveBeenCalled()
-            expect(rjSpy1).not.toHaveBeenCalled()
-      
-          })
-        })
-      })
-    })
-  })
 })
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
index aea9618aa57060e40a6333322834bbadb14220c3..7277a3ebb7422137b55ea234b7d1b3db591baeab 100644
--- a/src/atlasViewer/atlasViewer.apiService.service.ts
+++ b/src/atlasViewer/atlasViewer.apiService.service.ts
@@ -2,14 +2,16 @@
 import {Injectable, NgZone, Optional, Inject, OnDestroy, InjectionToken} from "@angular/core";
 import { select, Store } from "@ngrx/store";
 import { Observable, Subject, Subscription, from, race, of, } from "rxjs";
-import { distinctUntilChanged, map, filter, startWith, switchMap, catchError, mapTo } from "rxjs/operators";
+import { distinctUntilChanged, map, filter, startWith, switchMap, catchError, mapTo, take } from "rxjs/operators";
 import { DialogService } from "src/services/dialogService.service";
+import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors";
 import {
   getLabelIndexMap,
   getMultiNgIdsRegionsLabelIndexMap,
   IavRootStoreInterface,
   safeFilter
 } from "src/services/stateStore.service";
+import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util";
 import { ModalHandler } from "../util/pluginHandlerClasses/modalHandler";
 import { ToastHandler } from "../util/pluginHandlerClasses/toastHandler";
 import { IPluginManifest, PluginServices } from "./pluginUnit";
@@ -38,6 +40,7 @@ export const GET_TOAST_HANDLER_TOKEN = 'GET_TOAST_HANDLER_TOKEN'
 
 export class AtlasViewerAPIServices implements OnDestroy{
 
+  private onDestoryCb: Function[] = []
   private loadedTemplates$: Observable<any>
   private selectParcellation$: Observable<any>
   public interactiveViewer: IInteractiveViewerInterface
@@ -77,6 +80,68 @@ export class AtlasViewerAPIServices implements OnDestroy{
 
   private s: Subscription[] = []
 
+  private onMouseClick(ev: any, next){
+    const { rs, spec } = this.getNextUserRegionSelectHandler() || {}
+    if (!!rs) {
+      
+      let moSegments
+      this.store.pipe(
+        select(uiStateMouseOverSegmentsSelector),
+        take(1)
+      ).subscribe(val => moSegments = val)
+
+      /**
+       * getROI api
+       */
+      if (spec) {
+        /**
+         * if spec of overwrite click is for a point
+         */
+        if (spec.type === EnumCustomRegion.POINT) {
+          this.popUserRegionSelectHandler()
+          let mousePositionReal
+          // rather than commiting mousePositionReal in state via action, do a single subscription instead.
+          // otherwise, the state gets updated way too often
+          if (window && (window as any).nehubaViewer) {
+            (window as any).nehubaViewer.mousePosition.inRealSpace
+              .take(1)
+              .subscribe(floatArr => {
+                mousePositionReal = floatArr && Array.from(floatArr).map((val: number) => val / 1e6)
+              })
+          }
+          return rs({
+            type: spec.type,
+            payload: mousePositionReal
+          })
+        }
+
+        /**
+         * if spec of overwrite click is for a point
+         */
+        if (spec.type === EnumCustomRegion.PARCELLATION_REGION) {
+
+          if (!!moSegments && Array.isArray(moSegments) && moSegments.length > 0) {
+            this.popUserRegionSelectHandler()
+            return rs({
+              type: spec.type,
+              payload: moSegments
+            })
+          }
+        }
+      } else {
+        /**
+         * selectARegion API
+         * TODO deprecate
+         */
+        if (!!moSegments && Array.isArray(moSegments) && moSegments.length > 0) {
+          this.popUserRegionSelectHandler()
+          return rs(moSegments[0])
+        }
+      }
+    }
+    next()
+  }
+
   constructor(
     private store: Store<IavRootStoreInterface>,
     private dialogService: DialogService,
@@ -84,7 +149,14 @@ export class AtlasViewerAPIServices implements OnDestroy{
     private pluginService: PluginServices,
     @Optional() @Inject(CANCELLABLE_DIALOG) openCancellableDialog: (message: string, options: any) => () => void,
     @Optional() @Inject(GET_TOAST_HANDLER_TOKEN) private getToastHandler: Function,
+    @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor
   ) {
+    if (clickInterceptor) {
+      const { register, deregister } = clickInterceptor
+      const onMouseClick = this.onMouseClick.bind(this)
+      register(onMouseClick)
+      this.onDestoryCb.push(() => deregister(onMouseClick))
+    }
     if (openCancellableDialog) {
       this.s.push(
         this.getUserToSelectRegionUI$.pipe(
@@ -299,6 +371,7 @@ export class AtlasViewerAPIServices implements OnDestroy{
   }
 
   ngOnDestroy(){
+    while (this.onDestoryCb.length > 0) this.onDestoryCb.pop()()
     while(this.s.length > 0){
       this.s.pop().unsubscribe()
     }
@@ -396,57 +469,6 @@ export interface ICustomRegionSpec{
   type: string // type of EnumCustomRegion
 }
 
-export const overrideNehubaClickFactory = (apiService: AtlasViewerAPIServices, getState: () => any ) => {
-  return (next: () => void) => {
-    const { state, other } = getState()
-    const moSegments = state?.uiState?.mouseOverSegments
-    const { mousePositionReal } = other || {}
-    const { rs, spec } = apiService.getNextUserRegionSelectHandler() || {}
-    if (!!rs) {
-
-      /**
-       * getROI api
-       */
-      if (spec) {
-
-        /**
-         * if spec of overwrite click is for a point
-         */
-        if (spec.type === EnumCustomRegion.POINT) {
-          apiService.popUserRegionSelectHandler()
-          return rs({
-            type: spec.type,
-            payload: mousePositionReal
-          })
-        }
-
-        /**
-         * if spec of overwrite click is for a point
-         */
-        if (spec.type === EnumCustomRegion.PARCELLATION_REGION) {
-          if (!!moSegments && Array.isArray(moSegments) && moSegments.length > 0) {
-            apiService.popUserRegionSelectHandler()
-            return rs({
-              type: spec.type,
-              payload: moSegments
-            })
-          }
-        }
-      } else {
-        /**
-         * selectARegion API
-         * TODO deprecate
-         */
-        if (!!moSegments && Array.isArray(moSegments) && moSegments.length > 0) {
-          apiService.popUserRegionSelectHandler()
-          return rs(moSegments[0])
-        }
-      }
-    }
-    next()
-  }
-}
-
 export const API_SERVICE_SET_VIEWER_HANDLE_TOKEN = new InjectionToken<(viewerHandle) => void>('API_SERVICE_SET_VIEWER_HANDLE_TOKEN')
 
 export const setViewerHandleFactory = (apiService: AtlasViewerAPIServices) => {
diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index b584c241041aa0b53cd036ab78dcf56a8da9c5a9..66a9b72648ecf3a751de5c2014e0d402173f5795 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -35,8 +35,6 @@ import {MatSnackBar, MatSnackBarRef} from "@angular/material/snack-bar";
 import {MatDialog, MatDialogRef} from "@angular/material/dialog";
 import { ARIA_LABELS, CONST } from 'common/constants'
 
-export const NEHUBA_CLICK_OVERRIDE: InjectionToken<(next: () => void) => void> = new InjectionToken('NEHUBA_CLICK_OVERRIDE')
-
 import { MIN_REQ_EXPLAINER } from 'src/util/constants'
 import { SlServiceService } from "src/spotlight/sl-service.service";
 import { PureContantService } from "src/util";
@@ -45,6 +43,7 @@ import { viewerStateGetOverlayingAdditionalParcellations, viewerStateParcVersion
 import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors";
 import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions";
 import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors";
+import { ClickInterceptorService } from "src/glue";
 
 /**
  * TODO
@@ -149,8 +148,8 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     public localFileService: LocalFileService,
     private snackbar: MatSnackBar,
     private el: ElementRef,
-    @Optional() @Inject(NEHUBA_CLICK_OVERRIDE) private nehubaClickOverride: Function,
-    private slService: SlServiceService
+    private slService: SlServiceService,
+    private clickIntService: ClickInterceptorService
   ) {
 
     this.snackbarMessage$ = this.store.pipe(
@@ -249,6 +248,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
 
   public ngOnInit() {
     this.meetsRequirement = this.meetsRequirements()
+    this.clickIntService.addInterceptor(this.selectHoveredRegion.bind(this), true)
 
     if (KIOSK_MODE) {
 
@@ -388,6 +388,18 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
    */
   public ngOnDestroy() {
     this.subscriptions.forEach(s => s.unsubscribe())
+    this.clickIntService.removeInterceptor(this.selectHoveredRegion.bind(this))
+  }
+
+  private selectHoveredRegion(ev: any, next: Function){
+    if (!this.onhoverSegments) return
+      
+    this.store.dispatch(
+      viewerStateSetSelectedRegions({
+        selectRegions: this.onhoverSegments.slice(0, 1)
+      })
+    )
+    next()
   }
 
   public unsetClearViewByKey(key: string){
@@ -431,18 +443,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
   }
 
   public mouseClickDocument(_event: MouseEvent) {
-
-    const next = () => {
-      if (!this.onhoverSegments) return
-      this.store.dispatch(
-        viewerStateSetSelectedRegions({
-          selectRegions: this.onhoverSegments.slice(0, 1)
-        })
-      )
-    }
-
-    this.nehubaClickOverride(next)
-
+    this.clickIntService.run(_event)
   }
 
   /**
diff --git a/src/atlasViewer/mouseOver.directive.ts b/src/atlasViewer/mouseOver.directive.ts
index 9ba058431e326445fe97014d2decf15bbca6dabf..7938d4a36c1ef02ce7431565426a34af42af7b62 100644
--- a/src/atlasViewer/mouseOver.directive.ts
+++ b/src/atlasViewer/mouseOver.directive.ts
@@ -5,7 +5,7 @@ import { combineLatest, merge, Observable } from "rxjs";
 import { distinctUntilChanged, filter, map, scan, shareReplay, startWith, withLatestFrom } from "rxjs/operators";
 import { TransformOnhoverSegmentPipe } from "src/atlasViewer/onhoverSegment.pipe";
 import { LoggingService } from "src/logging";
-import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors";
+import { uiStateMouseOverSegmentsSelector, uiStateMouseoverUserLandmark } from "src/services/state/uiState/selectors";
 import { getNgIdLabelIndexFromId } from "src/services/stateStore.service";
 
 /**
@@ -53,8 +53,7 @@ export class MouseHoverDirective {
     // can potentially net better performance
 
     const onHoverUserLandmark$ = this.store$.pipe(
-      select('uiState'),
-      select('mouseOverUserLandmark'),
+      select(uiStateMouseoverUserLandmark)
     )
 
     const onHoverLandmark$ = combineLatest(
diff --git a/src/glue.spec.ts b/src/glue.spec.ts
index a78f1a6058706ea7d56add6cbee66fadc95322e9..aba6f15912e2050d8e2a25bc11d58216bd001934 100644
--- a/src/glue.spec.ts
+++ b/src/glue.spec.ts
@@ -1206,4 +1206,70 @@ describe('> glue.ts', () => {
     })
 
   })
+
+  describe('> ClickInterceptorService', () => {
+    /**
+     * TODO finish writing the test for ClickInterceptorService
+     */
+
+    it('can obtain override fn', () => {
+
+    })
+
+    describe('> if getUserToSelectRegion.length === 0', () => {
+
+      it('by default, next fn will be called', () => {
+
+      })
+  
+      it('if apiService.getUserToSelectRegion.length === 0, and mouseoversegment.length > 0 calls next', () => {
+
+      })
+    })
+    describe('> if getUserToSelectRegion.length > 0', () => {
+      it('if both apiService.getUserToSelectRegion.length > 0 and mouseoverSegment.length >0, then next will not be called, but rs will be', () => {
+        
+      })
+      it('if multiple getUserToSelectRegion handler exists, it resolves in a LIFO manner', () => {
+
+      })
+
+      describe('> if spec is not set (defaults to parcellation region mode)', () => {
+
+        it('if apiService.getUserToSelectRegion.length > 0, but mouseoversegment.length ===0, will not call next, will not rs, will not call rj', () => {
+
+        })
+      })
+
+      describe('> if spec is set', () => {
+        describe('> if spec is set to PARCELLATION_REGION', () => {
+
+          it('> mouseoversegment.length === 0, will not call next, will not rs, will not call rj', () => {
+
+          })
+          
+          it('> mouseoversegment.length > 0, will not call next, will call rs', () => {
+
+          })
+        })
+
+        describe('> if spec is set to POINT', () => {
+          it('> rs is called if mouseoversegment.length === 0', () => {
+
+          })
+          it('> rs is called with correct arg if mouseoversegment.length > 0', () => {
+
+          })
+        })
+
+        describe('> if multiple getUserToSelectRegion exist', () => {
+          it('> only the last Promise will be evaluated', () => {
+
+      
+          })
+        })
+      })
+    })
+
+  })
 })
diff --git a/src/glue.ts b/src/glue.ts
index 671fc0bebf2039930a5278ea645f2936cd553ae1..5d94c0c6a839b9b4c85042123044d6899cd501b3 100644
--- a/src/glue.ts
+++ b/src/glue.ts
@@ -720,3 +720,39 @@ export const gluActionSetFavDataset = createAction(
   '[glue] favDataset',
   props<{dataentries: Partial<IKgDataEntry>[]}>()
 )
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class ClickInterceptorService{
+  private clickInterceptorStack: Function[] = []
+
+  removeInterceptor(fn: Function) {
+    const idx = this.clickInterceptorStack.findIndex(int => int === fn)
+    if (idx < 0) {
+      console.warn(`clickInterceptorService could not remove the function. Did you pass the EXACT reference? 
+      Anonymouse functions such as () => {}  or .bind will create a new reference! 
+      You may want to assign .bind to a ref, and pass it to register and unregister functions`)
+    } else {
+      this.clickInterceptorStack.splice(idx, 1)
+    }
+  }
+  addInterceptor(fn: Function, atTheEnd?: boolean) {
+    if (atTheEnd) {
+      this.clickInterceptorStack.push(fn)
+    } else {
+      this.clickInterceptorStack.unshift(fn)
+    }
+  }
+
+  run(ev: any){
+    for (const clickInc of this.clickInterceptorStack) {
+      let runNext = false
+      clickInc(ev, () => {
+        runNext = true
+      })
+      if (!runNext) break
+    }
+  }
+}
diff --git a/src/main.module.ts b/src/main.module.ts
index 0c274937627d74c3498016649b6aab73c2b5f55c..b0ca199211e7e8476df25182d2dc1c195a24373b 100644
--- a/src/main.module.ts
+++ b/src/main.module.ts
@@ -4,7 +4,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule, InjectionToken } from "@angular/core"
 import { FormsModule } from "@angular/forms";
 import { StoreModule, Store, ActionReducer } from "@ngrx/store";
 import { AngularMaterialModule } from 'src/ui/sharedModules/angularMaterial.module'
-import { AtlasViewer, NEHUBA_CLICK_OVERRIDE } from "./atlasViewer/atlasViewer.component";
+import { AtlasViewer } from "./atlasViewer/atlasViewer.component";
 import { ComponentsModule } from "./components/components.module";
 import { LayoutModule } from "./layouts/layout.module";
 import { ngViewerState, pluginState, uiState, userConfigState, UserConfigStateUseEffect, viewerConfigState, viewerState } from "./services/stateStore.service";
@@ -14,7 +14,7 @@ import { GetNamesPipe } from "./util/pipes/getNames.pipe";
 
 import { HttpClientModule } from "@angular/common/http";
 import { EffectsModule } from "@ngrx/effects";
-import { AtlasViewerAPIServices, overrideNehubaClickFactory, CANCELLABLE_DIALOG, GET_TOAST_HANDLER_TOKEN, API_SERVICE_SET_VIEWER_HANDLE_TOKEN, setViewerHandleFactory } from "./atlasViewer/atlasViewer.apiService.service";
+import { AtlasViewerAPIServices, CANCELLABLE_DIALOG, GET_TOAST_HANDLER_TOKEN, API_SERVICE_SET_VIEWER_HANDLE_TOKEN, setViewerHandleFactory } from "./atlasViewer/atlasViewer.apiService.service";
 import { AtlasWorkerService } from "./atlasViewer/atlasViewer.workerService.service";
 import { ModalUnit } from "./atlasViewer/modalUnit/modalUnit.component";
 import { TransformOnhoverSegmentPipe } from "./atlasViewer/onhoverSegment.pipe";
@@ -33,7 +33,7 @@ import { DragDropDirective } from "./util/directives/dragDrop.directive";
 import { FloatingContainerDirective } from "./util/directives/floatingContainer.directive";
 import { FloatingMouseContextualContainerDirective } from "./util/directives/floatingMouseContextualContainer.directive";
 import { NewViewerDisctinctViewToLayer } from "./util/pipes/newViewerDistinctViewToLayer.pipe";
-import { UtilModule } from "src/util";
+import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR, UtilModule } from "src/util";
 import { SpotLightModule } from 'src/spotlight/spot-light.module'
 import { TryMeComponent } from "./ui/tryme/tryme.component";
 import { MouseHoverDirective, MouseOverIconPipe, MouseOverTextPipe } from "./atlasViewer/mouseOver.directive";
@@ -53,7 +53,7 @@ import 'hammerjs'
 import 'src/res/css/extra_styles.css'
 import 'src/res/css/version.css'
 import 'src/theme.scss'
-import { DatasetPreviewGlue, datasetPreviewMetaReducer, IDatasetPreviewGlue, GlueEffects } from './glue';
+import { DatasetPreviewGlue, datasetPreviewMetaReducer, IDatasetPreviewGlue, GlueEffects, ClickInterceptorService } from './glue';
 import { viewerStateHelperReducer, viewerStateFleshOutDetail, viewerStateMetaReducers, ViewerStateHelperEffect } from './services/state/viewerState.store.helper';
 import { take } from 'rxjs/operators';
 import { TOS_OBS_INJECTION_TOKEN } from './ui/kgtos/kgtos.component';
@@ -68,8 +68,6 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
   };
 }
 
-export const GET_STATE_SNAPSHOT_TOKEN = new InjectionToken('GET_STATE_SNAPSHOT_TOKEN')
-
 @NgModule({
   imports : [
     FormsModule,
@@ -154,37 +152,7 @@ export const GET_STATE_SNAPSHOT_TOKEN = new InjectionToken('GET_STATE_SNAPSHOT_T
     DialogService,
     UIService,
     TemplateCoordinatesTransformation,
-    {
-      provide: NEHUBA_CLICK_OVERRIDE,
-      useFactory: overrideNehubaClickFactory,
-      deps: [
-        AtlasViewerAPIServices,
-        GET_STATE_SNAPSHOT_TOKEN
-      ]
-    },
-    {
-      provide: GET_STATE_SNAPSHOT_TOKEN,
-      useFactory: (store: Store<any>) => {
-        return () => {
-          const other: any = {}
-          let state
-          // rather than commiting mousePositionReal in state via action, do a single subscription instead.
-          // otherwise, the state gets updated way too often
-          if (window && (window as any).nehubaViewer) {
-            (window as any).nehubaViewer.mousePosition.inRealSpace
-              .take(1)
-              .subscribe(floatArr => {
-                other.mousePositionReal = floatArr && Array.from(floatArr).map((val: number) => val / 1e6)
-              })
-          }
-          store.pipe(
-            take(1)
-          ).subscribe(v => state = v)
-          return { state, other }
-        }
-      },
-      deps: [ Store ]
-    },
+    ClickInterceptorService,
     {
       provide: GET_TOAST_HANDLER_TOKEN,
       useFactory: (uiService: UIService) => {
@@ -263,6 +231,18 @@ export const GET_STATE_SNAPSHOT_TOKEN = new InjectionToken('GET_STATE_SNAPSHOT_T
       useFactory: setViewerHandleFactory,
       deps: [ AtlasViewerAPIServices ]
     },
+    {
+      provide: CLICK_INTERCEPTOR_INJECTOR,
+      useFactory: (clickIntService: ClickInterceptorService) => {
+        return {
+          deregister: clickIntService.removeInterceptor.bind(clickIntService),
+          register: clickIntService.addInterceptor.bind(clickIntService)
+        } as ClickInterceptor
+      },
+      deps: [
+        ClickInterceptorService
+      ]
+    }
   ],
   bootstrap : [
     AtlasViewer,
diff --git a/src/services/state/uiState/selectors.ts b/src/services/state/uiState/selectors.ts
index 3a8e3f913b509c03cf4f0bd8e767b9bd3c098ad3..f72757b436adbf77e19becde70cff55213484504 100644
--- a/src/services/state/uiState/selectors.ts
+++ b/src/services/state/uiState/selectors.ts
@@ -21,3 +21,8 @@ export const uiStateMouseOverSegmentsSelector = createSelector(
       })
   }
 )
+
+export const uiStateMouseoverUserLandmark = createSelector(
+  state => state['uiState'],
+  uiState => uiState['mouseOverUserLandmark']
+)
diff --git a/src/services/state/viewerState.store.helper.ts b/src/services/state/viewerState.store.helper.ts
index d69751026a665900376790f645a887beaf2fa6b0..16ee0b763bf6211febbb479318463d0ca84bc849 100644
--- a/src/services/state/viewerState.store.helper.ts
+++ b/src/services/state/viewerState.store.helper.ts
@@ -61,6 +61,7 @@ import {
   viewerStateFetchedTemplatesSelector,
   viewerStateNavigationStateSelector,
 } from './viewerState/selectors'
+import { IHasId } from "src/util/interfaces";
 
 export {
   viewerStateSelectedRegionsSelector,
@@ -160,10 +161,6 @@ interface IHasVersion{
   ['@version']: IVersion
 }
 
-interface IHasId{
-  ['@id']: string
-}
-
 export function isNewerThan(arr: IHasVersion[], srcObj: IHasId, compObj: IHasId): boolean {
 
   function* GenNewerVersions(flag){
diff --git a/src/ui/atlasDropdown/atlasDropdown.template.html b/src/ui/atlasDropdown/atlasDropdown.template.html
index 3516dc424273afd50b6f98a4bf04d6d94f3d2455..4b5ddc4b0309e62ff937c8e6e5ffefeadfc55a6a 100644
--- a/src/ui/atlasDropdown/atlasDropdown.template.html
+++ b/src/ui/atlasDropdown/atlasDropdown.template.html
@@ -4,7 +4,7 @@
   </mat-label>
   <mat-select
     [aria-label]="SELECT_ATLAS_ARIA_LABEL"
-    [value]="selectedAtlas$ | async | mapToProperty"
+    [value]="selectedAtlas$ | async | getProperty"
     (selectionChange)="handleChangeAtlas($event)">
     <mat-option
       *ngFor="let atlas of (fetchedAtlases$ | async)"
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index 34e1da0f90683908fd3330ae7f0d2571af001da7..479512f7a9ebc9896b018f6e594f325073f7d1f8 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -459,12 +459,15 @@
     </ng-container>
 
     <!--  regional features-->
-    <ng-template #regionalFeaturesTmpl>
-      <data-browser [template]="templateSelected$ | async"
-        [parcellation]="selectedParcellation"
-        [disableVirtualScroll]="true"
-        [regions]="[region]">
-      </data-browser>
+    <ng-template #regionalFeaturesTmpl let-expansionPanel="expansionPanel">
+      <regional-features *ngIf="expansionPanel.expanded"
+        [region]="region">
+        <data-browser [template]="templateSelected$ | async"
+          [parcellation]="selectedParcellation"
+          [disableVirtualScroll]="true"
+          [regions]="[region]">
+        </data-browser>
+      </regional-features>
     </ng-template>
 
     <div class="hidden" iav-databrowser-directive
@@ -576,8 +579,8 @@
   <layout-floating-container *ngIf="panelIndex < 3" landmarkContainer>
 
     <!-- customLandmarks -->
-    <nehuba-2dlandmark-unit *ngFor="let lm of (customLandmarks$ | async)"
-    (mouseenter)="handleMouseEnterCustomLandmark(lm)"
+    <nehuba-2dlandmark-unit *ngFor="let lm of (customLandmarks$ | async | filterByProperty : 'showInSliceView')"
+      (mouseenter)="handleMouseEnterCustomLandmark(lm)"
       (mouseleave)="handleMouseLeaveCustomLandmark(lm)"
       fasClass="fa-chevron-down"
       [color]="lm.color || [255, 255, 255]"
diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
index c20f8f143ad0e8f27928597e1ead856309d2fd2b..4557642ee848cae8c9fa6523221ffe9bafe6fb60 100644
--- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
+++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
@@ -527,7 +527,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy {
     const appendConditional = (frag, idx) => frag && `if (label > ${idx - 0.01} && label < ${idx + 0.01}) { ${frag} }`
 
     if (landmarks.some(parseLmColor)) {
-      this.userLandmarkShader = `void main(){ ${landmarks.map(parseLmColor).map(appendConditional).filter(v => !!v).join('else ')} else {${FRAGMENT_EMIT_RED}} }`
+      this.userLandmarkShader = `void main(){ ${landmarks.map(parseLmColor).map(appendConditional).filter(v => !!v).join('else ')} else {${FRAGMENT_EMIT_WHITE}} }`
     } else {
       this.userLandmarkShader = FRAGMENT_MAIN_WHITE  
     }
diff --git a/src/ui/regionalFeatures/featureExplorer/featureExplorer.component.ts b/src/ui/regionalFeatures/featureExplorer/featureExplorer.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8be301cd949c843d6722f1c3e3d201695af3b564
--- /dev/null
+++ b/src/ui/regionalFeatures/featureExplorer/featureExplorer.component.ts
@@ -0,0 +1,146 @@
+import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from "@angular/core";
+import { BehaviorSubject, forkJoin, Observable, Subject, Subscription } from "rxjs";
+import { filter, shareReplay, switchMap, switchMapTo, tap } from "rxjs/operators";
+import { IHasId } from "src/util/interfaces";
+import { IFeature, RegionalFeaturesService } from "../regionalFeature.service";
+import { RegionFeatureBase } from "../regionFeature.base";
+
+const selectedColor = [ 255, 0, 0 ]
+
+@Component({
+  selector: 'feature-explorer',
+  templateUrl: './featureExplorer.template.html',
+  styleUrls: [
+    './featureExplorer.style.css'
+  ]
+})
+
+export class FeatureExplorer extends RegionFeatureBase implements OnChanges, OnInit, OnDestroy{
+
+  private landmarksLoaded: IHasId[] = []
+  private onDestroyCb: Function[] = []
+  private sub: Subscription[] = []
+  private _feature: IFeature
+
+  private feature$ = new BehaviorSubject(null)
+  @Input()
+  set feature(val) {
+    this._feature = val
+    this.feature$.next(val)
+  }
+
+  public data$: Observable<IHasId[]>
+
+  constructor(
+    private regionFeatureService: RegionalFeaturesService,
+  ){
+    super(regionFeatureService)
+    /**
+    * once feature stops loading, watch for input feature
+    */
+    this.data$ = this.isLoading$.pipe(
+      filter(v => !v),
+      tap(() => this.dataIsLoading = true),
+      switchMapTo(this.feature$),
+      switchMap((feature: IFeature) => forkJoin(
+        feature.data.map(datum => this.regionFeatureService.getFeatureData(this.region, feature, datum)))
+      ),
+      tap(() => this.dataIsLoading = false),
+      shareReplay(1),
+    )
+  }
+
+  ngOnInit(){
+    this.sub.push(
+      this.data$.subscribe(data => {
+        const landmarksTobeLoaded: IHasId[] = []
+        
+        for (const datum of data) {
+          const electrodeId = datum['@id']
+          landmarksTobeLoaded.push(
+            ...datum['contactPoints'].map(({ ['@id']: contactPtId, position }) => {
+              return {
+                _: {
+                  electrodeId,
+                  contactPtId
+                },
+                ['@id']: `${electrodeId}#${contactPtId}`,
+                position
+              }
+            })
+          )
+        }
+        /**
+         * remove first, then add
+         */
+        if (this.landmarksLoaded.length > 0) this.regionFeatureService.removeLandmarks(this.landmarksLoaded)
+        if (landmarksTobeLoaded.length > 0) this.regionFeatureService.addLandmarks(landmarksTobeLoaded)
+        this.landmarksLoaded = landmarksTobeLoaded
+      })
+    )
+
+    this.onDestroyCb.push(() => {
+      if (this.landmarksLoaded.length > 0) this.regionFeatureService.removeLandmarks(this.landmarksLoaded)
+    })
+  }
+
+  public dataIsLoading$ = new BehaviorSubject(false)
+  private _dataIsLoading = false
+  set dataIsLoading(val) {
+    if (val === this._dataIsLoading) return
+    this._dataIsLoading = val
+    this.dataIsLoading$.next(val)
+  }
+  get dataIsLoading(){
+    return this._dataIsLoading
+  }
+
+  ngOnChanges(changes: SimpleChanges){
+    super.ngOnChanges(changes)
+  }
+
+  ngOnDestroy(){
+    while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()()
+    while(this.sub.length > 0) this.sub.pop().unsubscribe()
+  }
+
+  handleDatumExpansion(electrodeId: string, open: boolean){
+    /**
+     * TODO either debounce call here, or later down stream
+     */
+    if (this.landmarksLoaded.length > 0) this.regionFeatureService.removeLandmarks(this.landmarksLoaded)
+    if (this.landmarksLoaded.length > 0) {
+      this.regionFeatureService.addLandmarks(this.landmarksLoaded.map(lm => {
+        if (lm['_']['electrodeId'] === electrodeId) {
+          return {
+            ...lm,
+            color: open ? selectedColor : null,
+            showInSliceView: open
+          }
+        } else {
+          return lm
+        }
+      }))
+    }
+  }
+
+  public exploreElectrode$ = new Subject()
+
+  handleLandmarkClick(arg: { landmark: any, next: Function }) {
+    const { landmark, next } = arg
+
+    /**
+     * there may be other custom landmarks
+     * so check if the landmark clicked is one that's managed by this cmp
+     */
+    const isOne = this.landmarksLoaded.some(lm => {
+      return lm['_']['electrodeId'] === landmark['_']['electrodeId']
+    })
+
+    if (isOne) {
+      this.exploreElectrode$.next(landmark)
+    } else {
+      next()
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/ui/regionalFeatures/featureExplorer/featureExplorer.style.css b/src/ui/regionalFeatures/featureExplorer/featureExplorer.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/ui/regionalFeatures/featureExplorer/featureExplorer.template.html b/src/ui/regionalFeatures/featureExplorer/featureExplorer.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..06f519eb256af715bf1f419ee13089f1e6d20ee5
--- /dev/null
+++ b/src/ui/regionalFeatures/featureExplorer/featureExplorer.template.html
@@ -0,0 +1,38 @@
+<ng-container *ngIf="!dataIsLoading; else loadingTmpl">
+  
+  <mat-accordion
+    regional-feature-interactiviity
+    (rf-interact-onclick-3d-landmark)="handleLandmarkClick($event)"
+    #interactDir="regionalFeatureInteractivity">
+    <mat-expansion-panel *ngFor="let datum of (data$ | async)"
+      [expanded]="(exploreElectrode$ | async | getProperty : '_' | getProperty : 'electrodeId') === datum['@id']"
+      (opened)="handleDatumExpansion(datum['@id'], true)"
+      (closed)="handleDatumExpansion(datum['@id'], false)"
+      hideToggle>
+      <mat-expansion-panel-header>
+        <mat-panel-title>
+          Electrode
+        </mat-panel-title>
+        <mat-panel-description class="text-nowrap">
+          {{ datum['@id'] }}
+        </mat-panel-description>
+      </mat-expansion-panel-header>
+
+      <span>
+        contact points
+      </span>
+      <mat-list>
+        <mat-list-item *ngFor="let contactPt of datum['contactPoints']">
+          {{ contactPt.position | addUnitAndJoin : '' }}
+        </mat-list-item>
+      </mat-list>
+  
+    </mat-expansion-panel>
+  </mat-accordion>
+
+</ng-container>
+
+<!-- loading template -->
+<ng-template #loadingTmpl>
+  <div class="spinnerAnimationCircle"></div>
+</ng-template>
diff --git a/src/ui/regionalFeatures/index.ts b/src/ui/regionalFeatures/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a71230d7107939f7da63d18d843339937ed89290
--- /dev/null
+++ b/src/ui/regionalFeatures/index.ts
@@ -0,0 +1,2 @@
+export { RegionalFeaturesModule } from './module'
+export { IFeature } from './regionalFeature.service'
\ No newline at end of file
diff --git a/src/ui/regionalFeatures/interactivity.directive.ts b/src/ui/regionalFeatures/interactivity.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a5c24ff8f456e762a01da417307f504790b3c148
--- /dev/null
+++ b/src/ui/regionalFeatures/interactivity.directive.ts
@@ -0,0 +1,54 @@
+import { Directive, Inject, EventEmitter, OnDestroy, Optional, Output } from "@angular/core";
+import { take } from "rxjs/operators";
+import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util";
+import { RegionalFeaturesService } from "./regionalFeature.service";
+
+@Directive({
+  selector: '[regional-feature-interactiviity]',
+  exportAs: 'regionalFeatureInteractivity'
+})
+
+export class RegionalFeatureInteractivity implements OnDestroy{
+
+  @Output('rf-interact-onclick-3d-landmark')
+  onClick3DLandmark: EventEmitter<{ landmark: any, next: Function }> = new EventEmitter()
+
+  private onDestroyCb: Function[] = []
+
+  constructor(
+    private regionalFeatureService: RegionalFeaturesService,
+    @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) private regClickIntp: ClickInterceptor,
+  ){
+
+    if (this.regClickIntp) {
+      const { deregister, register } = this.regClickIntp
+      const clickIntp = this.clickIntp.bind(this)
+      register(clickIntp)
+      this.onDestroyCb.push(() => {
+        deregister(clickIntp)
+      })
+    }
+
+  }
+
+  ngOnDestroy(){
+    while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()()
+  }
+
+  private clickIntp(ev: any, next: Function) {
+    let hoveredLandmark = null
+    this.regionalFeatureService.onHoverLandmarks$.pipe(
+      take(1)
+    ).subscribe(val => {
+      hoveredLandmark = val
+    })
+    if (hoveredLandmark) {
+      this.onClick3DLandmark.emit({
+        landmark: hoveredLandmark,
+        next
+      })
+    } else {
+      next()
+    }
+  }
+}
diff --git a/src/ui/regionalFeatures/module.ts b/src/ui/regionalFeatures/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..813cc93fc4877f1f0a55c9a7e6b93e48db5ac37b
--- /dev/null
+++ b/src/ui/regionalFeatures/module.ts
@@ -0,0 +1,44 @@
+import { CommonModule } from "@angular/common";
+import { NgModule } from "@angular/core";
+import { UtilModule } from "src/util";
+import { AngularMaterialModule } from "../sharedModules/angularMaterial.module";
+import { FeatureExplorer } from "./featureExplorer/featureExplorer.component";
+import { RegionalFeatureInteractivity } from "./interactivity.directive";
+import { FilterRegionalFeaturesByTypePipe } from "./pipes/filterRegionalFeaturesByType.pipe";
+import { FindRegionFEatureById } from "./pipes/findRegionFeatureById.pipe";
+import { RegionalFeaturesService } from "./regionalFeature.service";
+import { RegionalFeaturesCmp } from "./regionalFeaturesCmp/regionalFeaturesCmp.component";
+
+@NgModule({
+  imports: [
+    CommonModule,
+    UtilModule,
+    AngularMaterialModule,
+  ],
+  declarations: [
+    /**
+     * components
+     */
+    RegionalFeaturesCmp,
+    FeatureExplorer,
+
+    /**
+     * Directives
+     */
+    RegionalFeatureInteractivity,
+
+    /**
+     * pipes
+     */
+    FilterRegionalFeaturesByTypePipe,
+    FindRegionFEatureById,
+  ],
+  exports: [
+    RegionalFeaturesCmp,
+  ],
+  providers: [
+    RegionalFeaturesService,
+  ]
+})
+
+export class RegionalFeaturesModule{}
diff --git a/src/ui/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts b/src/ui/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f5bf231bddf4cbb5fa1ae97121c6315074b2305e
--- /dev/null
+++ b/src/ui/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { IFeature } from "../regionalFeature.service";
+
+@Pipe({
+  name: 'filterRegionalFeaturesBytype',
+  pure: true,
+})
+
+export class FilterRegionalFeaturesByTypePipe implements PipeTransform{
+  public transform(array: IFeature[], featureType: string){
+    return array.filter(f => featureType ? f.type === featureType : true )
+  }
+}
diff --git a/src/ui/regionalFeatures/pipes/findRegionFeatureById.pipe.ts b/src/ui/regionalFeatures/pipes/findRegionFeatureById.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a083a28a5a843ef36c47c29183efd72ed7e28936
--- /dev/null
+++ b/src/ui/regionalFeatures/pipes/findRegionFeatureById.pipe.ts
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { IFeature } from "../regionalFeature.service";
+
+@Pipe({
+  name: 'findRegionFeaturebyId',
+  pure: true
+})
+
+export class FindRegionFEatureById implements PipeTransform{
+  public transform(features: IFeature[], id: string){
+    return features.find(f => f['@id'] === id)
+  }
+}
diff --git a/src/ui/regionalFeatures/regionFeature.base.ts b/src/ui/regionalFeatures/regionFeature.base.ts
new file mode 100644
index 0000000000000000000000000000000000000000..de8a190b216c86b1ad5d1ab486c1e8bcfa1b5729
--- /dev/null
+++ b/src/ui/regionalFeatures/regionFeature.base.ts
@@ -0,0 +1,46 @@
+import { Input, SimpleChanges } from "@angular/core"
+import { BehaviorSubject } from "rxjs"
+import { IFeature, RegionalFeaturesService } from "./regionalFeature.service"
+
+export class RegionFeatureBase{
+
+  @Input()
+  public region: any
+
+  public features: IFeature[] = []
+
+  /**
+   * using isLoading flag for conditional rendering of root element (or display loading spinner)
+   * this is necessary, or the transcluded tab will always be the active tab,
+   * as this.features as populated via async
+   */
+  public isLoading$ = new BehaviorSubject(false)
+  private _isLoading: boolean = false
+  get isLoading(){
+    return this._isLoading
+  }
+  set isLoading(val){
+    if (val !== this._isLoading)
+      this._isLoading = val
+    this.isLoading$.next(val)
+  }
+
+  ngOnChanges(changes: SimpleChanges){
+    if (changes.region && changes.region.previousValue !== changes.region.currentValue) {
+      this.isLoading = true
+      this.features = []
+      this._regionalFeatureService.getAllFeaturesByRegion(changes.region.currentValue).pipe(
+
+      ).subscribe({
+        next: features => this.features = features,
+        complete: () => this.isLoading = false
+      })
+    }
+  }
+
+  constructor(
+    private _regionalFeatureService: RegionalFeaturesService
+  ){
+
+  }
+}
diff --git a/src/ui/regionalFeatures/regionalFeature.service.ts b/src/ui/regionalFeatures/regionalFeature.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b129bb1e64545c2481278f865bf90d75253842cc
--- /dev/null
+++ b/src/ui/regionalFeatures/regionalFeature.service.ts
@@ -0,0 +1,122 @@
+import { HttpClient } from "@angular/common/http";
+import { Injectable, OnDestroy } from "@angular/core";
+import { PureContantService } from "src/util";
+import { getIdFromFullId } from 'common/util'
+import { forkJoin, Subscription } from "rxjs";
+import { switchMap } from "rxjs/operators";
+import { IHasId } from "src/util/interfaces";
+import { select, Store } from "@ngrx/store";
+import { viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors";
+import { viewerStateAddUserLandmarks, viewreStateRemoveUserLandmarks } from "src/services/state/viewerState/actions";
+import { uiStateMouseoverUserLandmark } from "src/services/state/uiState/selectors";
+
+export interface IFeature extends IHasId{
+  type: string
+  name: string
+  data?: IHasId[]
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+
+export class RegionalFeaturesService implements OnDestroy{
+
+  private subs: Subscription[] = []
+  private templateSelected: any
+  constructor(
+    private http: HttpClient,
+    private pureConstantService: PureContantService,
+    private store$: Store<any>
+  ){
+    this.subs.push(
+      this.store$.pipe(
+        select(viewerStateSelectedTemplateSelector)
+      ).subscribe(val => this.templateSelected = val)
+    )
+  }
+
+  ngOnDestroy(){
+    while (this.subs.length > 0) this.subs.pop().unsubscribe()
+  }
+
+  public onHoverLandmarks$ = this.store$.pipe(
+    select(uiStateMouseoverUserLandmark)
+  )
+
+  public getAllFeaturesByRegion(region: {['fullId']: string}){
+    if (!region.fullId) throw new Error(`getAllFeaturesByRegion - region does not have fullId defined`)
+    const regionFullId = getIdFromFullId(region.fullId)
+
+    const hemisphereObj =  region['status'] ? { hemisphere: region['status'] } : {}
+    const refSpaceObj = this.templateSelected && this.templateSelected.fullId
+      ? { referenceSpaceId: getIdFromFullId(this.templateSelected.fullId) }
+      : {}
+
+    return this.http.get<{features: IHasId[]}>(
+      `${this.pureConstantService.backendUrl}regionalFeatures/byRegion/${encodeURIComponent( regionFullId )}`,
+      {
+        params: {
+          ...hemisphereObj,
+          ...refSpaceObj,
+          
+        },
+        responseType: 'json'
+      }
+    ).pipe(
+      switchMap(({ features }) => forkJoin(
+        features.map(({ ['@id']: featureId }) => 
+          this.http.get<IFeature>(
+            `${this.pureConstantService.backendUrl}regionalFeatures/byRegion/${encodeURIComponent( regionFullId )}/${encodeURIComponent( featureId )}`,
+            {
+              params: {
+                ...hemisphereObj,
+                ...refSpaceObj,
+              },
+              responseType: 'json'
+            }
+          )
+        )
+      )),
+    )
+  }
+
+  public getFeatureData(region: any,feature: IFeature, data: IHasId){
+    if (!feature['@id']) throw new Error(`@id attribute for feature is required`)
+    if (!data['@id']) throw new Error(`@id attribute for data is required`)
+    const refSpaceObj = this.templateSelected && this.templateSelected.fullId
+      ? { referenceSpaceId: getIdFromFullId(this.templateSelected.fullId) }
+      : {}
+    return this.http.get<IHasId>(
+      `${this.pureConstantService.backendUrl}regionalFeatures/byFeature/${encodeURIComponent(feature['@id'])}/${encodeURIComponent(data['@id'])}`,
+      {
+        params: {
+          ...refSpaceObj,
+        },
+        responseType: 'json'
+      }
+    )
+  }
+
+  public addLandmarks(lms: IHasId[]) {
+    this.store$.dispatch(
+      viewerStateAddUserLandmarks({
+        landmarks: lms.map(lm => ({
+          ...lm,
+          id: lm['@id'],
+          name: `region feature: ${lm['@id']}`
+        }))
+      })
+    )
+  }
+
+  public removeLandmarks(lms: IHasId[]) {
+    this.store$.dispatch(
+      viewreStateRemoveUserLandmarks({
+        payload: {
+          landmarkIds: lms.map(l => l['@id'])
+        }
+      })
+    )
+  }
+}
diff --git a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2cf5ec0e70d2a0407927155025525a1e1beb1463
--- /dev/null
+++ b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.component.ts
@@ -0,0 +1,28 @@
+import { Component, OnChanges, SimpleChanges } from "@angular/core";
+import { RegionalFeaturesService } from "../regionalFeature.service";
+import { RegionFeatureBase } from "../regionFeature.base";
+
+@Component({
+  selector: 'regional-features',
+  templateUrl: './regionalFeaturesCmp.template.html',
+  styleUrls: [
+    './regionalFeaturesCmp.style.css'
+  ],
+})
+
+export class RegionalFeaturesCmp extends RegionFeatureBase implements OnChanges{
+
+  ngOnChanges(changes: SimpleChanges){
+    super.ngOnChanges(changes)
+  }
+
+  constructor(
+    regionalFeatureService: RegionalFeaturesService
+  ){
+    super(regionalFeatureService)
+  }
+
+  public showingRegionFeatureId: string
+  public showingRegionFeatureIsLoading = false
+
+}
diff --git a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.style.css b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.template.html b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..0cac5e15927dfa0527277f5eed635480f65b453d
--- /dev/null
+++ b/src/ui/regionalFeatures/regionalFeaturesCmp/regionalFeaturesCmp.template.html
@@ -0,0 +1,76 @@
+<mat-tab-group *ngIf="!isLoading; else loadingTmpl">
+  <mat-tab *ngFor="let featureType of features | mapToProperty : 'type' | getUniquePipe">
+    <ng-template mat-tab-label>
+      {{ featureType }}
+    </ng-template>
+
+    <!-- lazy loading feature content -->
+    <ng-template matTabContent>
+
+      <!-- selector -->
+      <div>
+        <ng-container *ngTemplateOutlet="selectorTmpl; context: {
+          label: 'Dataset',
+          options: features | filterRegionalFeaturesBytype : featureType
+        }">
+        </ng-container>
+  
+      </div>
+
+      <!-- content -->
+      <ng-template [ngIf]="showingRegionFeatureId">
+        <ng-container *ngTemplateOutlet="featureContentTmpl; context: {
+          region: region,
+          feature: features | findRegionFeaturebyId : showingRegionFeatureId
+        }">
+        </ng-container>
+      </ng-template>
+    </ng-template>
+  </mat-tab>
+
+  <!-- transcluded content -->
+  <mat-tab>
+    <ng-template mat-tab-label>
+      Other
+    </ng-template>
+
+    <!-- lazy loading transcluded content -->
+    <ng-template matTabContent>
+      <ng-content></ng-content>
+    </ng-template>
+  </mat-tab>
+
+</mat-tab-group>
+
+<!-- feature selector -->
+<ng-template #selectorTmpl
+  let-label="label"
+  let-options="options">
+
+  <mat-form-field>
+    <mat-label>
+      {{ label }}
+    </mat-label>
+    <mat-select [(value)]="showingRegionFeatureId">
+      <mat-option *ngFor="let option of options"
+        [value]="option['@id']">
+        {{ option.name }}
+      </mat-option>
+    </mat-select>
+  </mat-form-field>
+</ng-template>
+
+<!-- feature content template -->
+<ng-template #featureContentTmpl
+  let-region="region"
+  let-feature="feature">
+  <feature-explorer
+    [region]="region"
+    [feature]="feature">
+  </feature-explorer>
+</ng-template>
+
+<!-- loading tmpl -->
+<ng-template #loadingTmpl>
+  <div class="spinnerAnimationCircle"></div>
+</ng-template>
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index 3d1faaed1492929200b9ebedac5b77a2db625889..0e09814462b7d34fe606b8872d771623af78b0cf 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -10,8 +10,6 @@ import { GetTemplateImageSrcPipe, ImgSrcSetPipe, SplashScreen } from "./nehubaCo
 import { FilterRegionDataEntries } from "src/util/pipes/filterRegionDataEntries.pipe";
 import { GroupDatasetByRegion } from "src/util/pipes/groupDataEntriesByRegion.pipe";
 
-import { GetUniquePipe } from "src/util/pipes/getUnique.pipe";
-
 import { GetLayerNameFromDatasets } from "../util/pipes/getLayerNamePipe.pipe";
 import { SafeStylePipe } from "../util/pipes/safeStyle.pipe";
 import { SortDataEntriesToRegion } from "../util/pipes/sortDataEntriesIntoRegion.pipe";
@@ -87,6 +85,7 @@ import { RegionDirective } from "./parcellationRegion/region.directive";
 import { RenderViewOriginDatasetLabelPipe } from "./parcellationRegion/region.base";
 import { RegionAccordionTooltipTextPipe } from './util'
 import { HelpOnePager } from "./helpOnePager/helpOnePager.component";
+import { RegionalFeaturesModule } from "./regionalFeatures";
 
 @NgModule({
   imports : [
@@ -105,6 +104,7 @@ import { HelpOnePager } from "./helpOnePager/helpOnePager.component";
     StateModule,
     AuthModule,
     FabSpeedDialModule,
+    RegionalFeaturesModule,
   ],
   declarations : [
     NehubaContainer,
@@ -150,7 +150,6 @@ import { HelpOnePager } from "./helpOnePager/helpOnePager.component";
     /* pipes */
     GroupDatasetByRegion,
     FilterRegionDataEntries,
-    GetUniquePipe,
     FlatmapArrayPipe,
     SafeStylePipe,
     GetLayerNameFromDatasets,
diff --git a/src/util/index.ts b/src/util/index.ts
index c2a2a8efd93d7369454b862d11509288af3c9994..25e810ea70b989a4e67ea70ae107fb598444037e 100644
--- a/src/util/index.ts
+++ b/src/util/index.ts
@@ -1,2 +1,3 @@
 export { UtilModule } from './util.module'
 export { PureContantService } from './pureConstant.service'
+export { CLICK_INTERCEPTOR_INJECTOR, ClickInterceptor } from './injectionTokens'
diff --git a/src/util/injectionTokens.ts b/src/util/injectionTokens.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c910f15350fec0dd087c674d54688dd4ccfe882f
--- /dev/null
+++ b/src/util/injectionTokens.ts
@@ -0,0 +1,8 @@
+import { InjectionToken } from "@angular/core";
+
+export const CLICK_INTERCEPTOR_INJECTOR = new InjectionToken<ClickInterceptor>('CLICK_INTERCEPTOR_INJECTOR')
+
+export interface ClickInterceptor{
+  register: (interceptorFunction: (ev: any, next: Function) => void) => void
+  deregister: (interceptorFunction: Function) => void
+}
diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d6c4147186789ad0b22d879077f2facd8c0877b5
--- /dev/null
+++ b/src/util/interfaces.ts
@@ -0,0 +1,7 @@
+/**
+ * Only the most common interfaces should reside here
+ */
+
+export interface IHasId{
+  ['@id']: string
+}
diff --git a/src/util/pipes/filterArray.pipe.ts b/src/util/pipes/filterArray.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eca8e461026de20361e62e71eeefe4d76ba96d37
--- /dev/null
+++ b/src/util/pipes/filterArray.pipe.ts
@@ -0,0 +1,11 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+@Pipe({
+  name: 'filterArray'
+})
+
+export class FilterArrayPipe implements PipeTransform{
+  public transform<T>(arr: T[], filterFn: (item: T, index: number, array: T[]) => boolean){
+    return arr.filter(filterFn)
+  }
+}
diff --git a/src/util/pipes/filterByProperty.pipe.ts b/src/util/pipes/filterByProperty.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6e7819bfe2104dcd4f7f6cc433edbc4c21311c85
--- /dev/null
+++ b/src/util/pipes/filterByProperty.pipe.ts
@@ -0,0 +1,12 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+@Pipe({
+  name: 'filterByProperty',
+  pure: true
+})
+
+export class FilterByPropertyPipe implements PipeTransform{
+  public transform<T>(input: T[], prop: string): T[]{
+    return input.filter(item => !!item[prop])
+  }
+}
diff --git a/src/util/pipes/mapToProperty.pipe.spec.ts b/src/util/pipes/getProperty.pipe.spec.ts
similarity index 100%
rename from src/util/pipes/mapToProperty.pipe.spec.ts
rename to src/util/pipes/getProperty.pipe.spec.ts
diff --git a/src/util/pipes/getProperty.pipe.ts b/src/util/pipes/getProperty.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..024135aee89a50d1554f41894ba2cac9f1bd813e
--- /dev/null
+++ b/src/util/pipes/getProperty.pipe.ts
@@ -0,0 +1,12 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+@Pipe({
+  name: 'getProperty',
+  pure: true
+})
+
+export class GetPropertyPipe implements PipeTransform{
+  public transform(input, property = '@id') {
+    return input && input[property]
+  }
+}
diff --git a/src/util/pipes/getUnique.pipe.ts b/src/util/pipes/getUnique.pipe.ts
index 892e28ef85e5c8b7003de589b579d42bc4cdd7f4..a0cdfe91857fd77afc56dfb808036636cffa9d44 100644
--- a/src/util/pipes/getUnique.pipe.ts
+++ b/src/util/pipes/getUnique.pipe.ts
@@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from "@angular/core";
 
 @Pipe({
   name: 'getUniquePipe',
+  pure: true
 })
 
 export class GetUniquePipe implements PipeTransform {
diff --git a/src/util/pipes/mapToProperty.pipe.ts b/src/util/pipes/mapToProperty.pipe.ts
index 43496f4145743984744831ea077d18b086a1c573..25d8b6267ada5bd07fa666905c9f712f44aaa53c 100644
--- a/src/util/pipes/mapToProperty.pipe.ts
+++ b/src/util/pipes/mapToProperty.pipe.ts
@@ -6,7 +6,7 @@ import { Pipe, PipeTransform } from "@angular/core";
 })
 
 export class MapToPropertyPipe implements PipeTransform{
-  public transform(input, property = '@id') {
-    return input && input[property]
+  public transform(arr: any[], prop: string){
+    return arr.map(item => prop ? item[prop] : item)
   }
 }
diff --git a/src/util/util.module.ts b/src/util/util.module.ts
index f29aa368ea5931808431ac7f5ca87f3761655c04..d03ee97f4efa39239d298b76cde710f8ba003fde 100644
--- a/src/util/util.module.ts
+++ b/src/util/util.module.ts
@@ -14,10 +14,14 @@ import { SwitchDirective } from "./directives/switch.directive";
 import { MediaQueryDirective } from './directives/mediaQuery.directive'
 import { LayoutModule } from "@angular/cdk/layout";
 import { MapToPropertyPipe } from "./pipes/mapToProperty.pipe";
-import {ClickOutsideDirective} from "src/util/directives/clickOutside.directive";
+import { ClickOutsideDirective } from "src/util/directives/clickOutside.directive";
 import { CounterDirective } from "./directives/counter.directive";
 import { GetNthElementPipe } from "./pipes/getNthElement.pipe";
 import { ParseAsNumberPipe } from "./pipes/parseAsNumber.pipe";
+import { GetUniquePipe } from "./pipes/getUnique.pipe";
+import { GetPropertyPipe } from "./pipes/getProperty.pipe";
+import { FilterArrayPipe } from "./pipes/filterArray.pipe";
+import { FilterByPropertyPipe } from "./pipes/filterByProperty.pipe";
 
 @NgModule({
   imports:[
@@ -41,6 +45,10 @@ import { ParseAsNumberPipe } from "./pipes/parseAsNumber.pipe";
     CounterDirective,
     GetNthElementPipe,
     ParseAsNumberPipe,
+    GetUniquePipe,
+    GetPropertyPipe,
+    FilterArrayPipe,
+    FilterByPropertyPipe,
   ],
   exports: [
     FilterNullPipe,
@@ -60,6 +68,10 @@ import { ParseAsNumberPipe } from "./pipes/parseAsNumber.pipe";
     CounterDirective,
     GetNthElementPipe,
     ParseAsNumberPipe,
+    GetUniquePipe,
+    GetPropertyPipe,
+    FilterArrayPipe,
+    FilterByPropertyPipe,
   ]
 })