From 7d0df31dec827eb31be74ee173fed9a91c0cd2fb Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Wed, 3 Jul 2019 11:23:27 +0200
Subject: [PATCH] chore: cleanup code improve reusability

---
 deploy/datasets/query.js | 211 +++++++++++++++++++++++----------------
 1 file changed, 125 insertions(+), 86 deletions(-)

diff --git a/deploy/datasets/query.js b/deploy/datasets/query.js
index 47a6b446d..55caaf309 100644
--- a/deploy/datasets/query.js
+++ b/deploy/datasets/query.js
@@ -32,7 +32,7 @@ const fetchDatasetFromKg = async ({ user } = {}) => {
 }
 
 
-const cacheData = ({results, ...rest}) => {
+const cacheData = ({ results, ...rest }) => {
   cachedData = results
   otherQueryResult = rest
   return cachedData
@@ -55,7 +55,7 @@ const getPublicDs = () => Promise.race([
 
 
 const getDs = ({ user }) => user
-  ? fetchDatasetFromKg({ user }).then(({results}) => results)
+  ? fetchDatasetFromKg({ user }).then(({ results }) => results)
   : getPublicDs()
 
 /**
@@ -78,7 +78,7 @@ const readConfigFile = (filename) => new Promise((resolve, reject) => {
     filepath = path.join(__dirname, '..', '..', 'src', 'res', 'ext', filename)
   }
   fs.readFile(filepath, 'utf-8', (err, data) => {
-    if(err) reject(err)
+    if (err) reject(err)
     resolve(data)
   })
 })
@@ -116,16 +116,16 @@ readConfigFile('waxholmRatV2_0.json')
  */
 const filterByPRs = (prs, atlasPr) => atlasPr
   ? prs.some(pr => {
-      const regex = new RegExp(pr.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), 'i')
-      return atlasPr.some(aPr => regex.test(aPr.name))
-    })
+    const regex = new RegExp(pr.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), 'i')
+    return atlasPr.some(aPr => regex.test(aPr.name))
+  })
   : false
 
 const manualFilter = require('./supplements/parcellation')
 
 
-const filter = (datasets = [], {templateName, parcellationName}) => datasets
-  .filter(ds => commonSenseDsFilter({ds, templateName, parcellationName }))
+const filter = (datasets = [], { templateName, parcellationName }) => datasets
+  .filter(ds => commonSenseDsFilter({ ds, templateName, parcellationName }))
   .filter(ds => {
     if (/infant/.test(ds.name))
       return false
@@ -133,20 +133,20 @@ const filter = (datasets = [], {templateName, parcellationName}) => datasets
       return ds.referenceSpaces.some(rs => rs.name === templateName)
     }
     if (parcellationName) {
-      if (parcellationName === 'Fibre Bundle Atlas - Long Bundle'){
+      if (parcellationName === 'Fibre Bundle Atlas - Long Bundle') {
         return manualFilterDWM(ds)
       }
       return ds.parcellationRegion.length > 0
         ? filterByPRs(
-            ds.parcellationRegion, 
-            parcellationName === 'JuBrain Cytoarchitectonic Atlas' && juBrain
-              ? juBrain
-              : parcellationName === 'Fibre Bundle Atlas - Short Bundle' && shortBundle
-                ? shortBundle
-                : parcellationName === 'Waxholm Space rat brain atlas v.2.0'
-                  ? waxholm
-                  : null
-          )
+          ds.parcellationRegion,
+          parcellationName === 'JuBrain Cytoarchitectonic Atlas' && juBrain
+            ? juBrain
+            : parcellationName === 'Fibre Bundle Atlas - Short Bundle' && shortBundle
+              ? shortBundle
+              : parcellationName === 'Waxholm Space rat brain atlas v.2.0'
+                ? waxholm
+                : null
+        )
         : false
     }
 
@@ -171,12 +171,12 @@ const filter = (datasets = [], {templateName, parcellationName}) => datasets
 exports.init = async () => {
   const { getPublicAccessToken: getPublic } = await kgQueryUtil()
   getPublicAccessToken = getPublic
-  const {results = []} = await fetchDatasetFromKg()
+  const { results = [] } = await fetchDatasetFromKg()
   cachedData = results
 }
 
 exports.getDatasets = ({ templateName, parcellationName, user }) => getDs({ user })
-    .then(json => filter(json, {templateName, parcellationName}))
+  .then(json => filter(json, { templateName, parcellationName }))
 
 exports.getPreview = ({ datasetName, templateSelected }) => getPreviewFile({ datasetName, templateSelected })
 
@@ -185,82 +185,121 @@ exports.getPreview = ({ datasetName, templateSelected }) => getPreviewFile({ dat
  * change to real spatial query
  */
 const cachedMap = new Map()
-const fetchSpatialDataFromKg = async ({ templateName, queryArg, user }) => {
-   try {
-    const coordsString = queryArg.split('__');
-    const boundingBoxCorners = coordsString.map(coordString => coordString.split('_'))
-
-    if (templateName === 'Waxholm Space rat brain atlas v.2.0') {
-        const boundingBoxInWaxhomV2VoxelSpace = boundingBoxCorners.map(transformWaxholmV2NmToVoxel)
-        const spatialData = await fetchSpatialData({boundingBoxInWaxhomV2VoxelSpace, user})
-        if (spatialData.length) {
-            return spatialData
-        } else {
-            return []
-        }
+
+/**
+ * TODO change to URL constructor to improve readability
+ */
+const spatialQuery = 'https://kg.humanbrainproject.eu/query/neuroglancer/seeg/coordinate/v1.0.0/spatialWithCoordinates/instances?vocab=https%3A%2F%2Fschema.hbp.eu%2FmyQuery%2F'
+
+const getXformFn = (templateSpace) => {
+  const _ = {}
+  switch(templateSpace){
+    case 'Waxholm Space rat brain atlas v.2.0': 
+      _['nmToVoxel'] = transformWaxholmV2NmToVoxel
+      _['voxelToNm'] = transformWaxholmV2VoxelToNm
+      break;
+    default: {
+      _['nmToVoxel'] = defaultXform
+      _['voxelToNm'] = defaultXform
     }
-   } catch (e) {
-    console.log('datasets#query.js#fetchSpatialDataFromKg', 'read file and parse json failed', e)
-    return []
   }
+  return _
 }
 
-exports.getSpatialDatasets = async ({ templateName, queryGeometry, queryArg, user }) => {
-  return await fetchSpatialDataFromKg({ templateName, queryArg, user })
+const getSpatialSearcParam = ({ templateName, queryArg }) => {
+  let kgSpaceName
+  const { nmToVoxel } = getXformFn(templateName)
+
+  const coordsString = queryArg.split('__');
+  const boundingBoxCorners = coordsString.map(coordString => coordString.split('_'))
+  const bbInVoxelSpace = boundingBoxCorners.map(nmToVoxel)
+
+  switch (templateName){
+    case 'Waxholm Space rat brain atlas v.2.0':
+      kgSpaceName = 'waxholmV2'
+      break;
+    default: 
+      kgSpaceName = templateName
+  }
+  return {
+    boundingBox: `${kgSpaceName}:${bbInVoxelSpace.map(v => v.join(',')).join(',')}`
+  }
 }
 
-async function fetchSpatialData({ user, boundingBoxInWaxhomV2VoxelSpace }) {
-    const { releasedOnly, option } = await getUserKGRequestInfo({ user })
-    const spatialQuery = 'https://kg.humanbrainproject.eu/query/neuroglancer/seeg/coordinate/v1.0.0/spatialWithCoordinates/instances?vocab=https%3A%2F%2Fschema.hbp.eu%2FmyQuery%2F'
-
-    return await new Promise((resolve, reject) => {
-        // ToDo need too add: "${releasedOnly ? '&databaseScope=RELEASED' : ''}"
-        request(`${spatialQuery}&boundingBox=waxholmV2:${boundingBoxInWaxhomV2VoxelSpace.map(cornerCoord => cornerCoord.join(',')).join(',')}${releasedOnly ? '&databaseScope=RELEASED' : ''}`, option, (err, resp, body) => {
-            if (err)
-                return reject(err)
-            if (resp.statusCode >= 400) {
-                return reject(resp.statusCode)
-            }
-
-            const json = JSON.parse(body).results.map(res => {
-                return {name: res.name,
-                      templateSpace: res['dataset'][0]['name'],
-                      geometry: {
-                        type: "point",
-                        space: "real",
-                        position: transformVoxelToWaxholmV2Nm ([
-                            res['coordinates'][0]['x'],
-                            res['coordinates'][0]['y'],
-                            res['coordinates'][0]['z'],
-
-                        ])}
-                      }
-            })
-            return resolve(json)
-        })
+const fetchSpatialDataFromKg = async ({ templateName, queryGeometry, queryArg, user }) => {
+
+  const { releasedOnly, option } = await getUserKGRequestInfo({ user })
+
+  const _ = getSpatialSearcParam({ templateName, queryGeometry, queryArg })
+  const url = require('url')
+  const search = new url.URLSearchParams()
+  
+  for (let key in _) {
+    search.append(key, _[key])  
+  }
+  if (releasedOnly) search.append('databaseScope', 'RELEASED')
+  
+  const _url = `${spatialQuery}&${search.toString()}`
+  return await new Promise((resolve, reject) => {
+    request(_url, option, (err, resp, body) => {
+      if (err)
+        return reject(err)
+      if (resp.statusCode >= 400) {
+        return reject(resp.statusCode)
+      }
+      const json = JSON.parse(body)
+
+      const { voxelToNm } = getXformFn(templateName)
+
+      const _ = json.results.map(({ name, coordinates}) => {
+        return {
+          name,
+          templateSpace: templateName,
+          geometry: {
+            type: 'point',
+            space: 'real',
+            position: voxelToNm([
+              coordinates[0].x,
+              coordinates[0].y,
+              coordinates[0].z
+            ])
+          }
+        }
+      })
+
+      return resolve(_)
     })
+  })
+}
+
+exports.getSpatialDatasets = async ({ templateName, queryGeometry, queryArg, user }) => {
+  /**
+   * Local data can be injected here
+   */
+  return await fetchSpatialDataFromKg({ templateName, queryGeometry, queryArg, user })
 }
 
 async function getUserKGRequestInfo({ user }) {
-    const accessToken = user && user.tokenset && user.tokenset.access_token
-    const releasedOnly = !accessToken
-    let publicAccessToken
-    if (!accessToken && getPublicAccessToken) {
-        publicAccessToken = await getPublicAccessToken()
+  const accessToken = user && user.tokenset && user.tokenset.access_token
+  const releasedOnly = !accessToken
+  let publicAccessToken
+  if (!accessToken && getPublicAccessToken) {
+    publicAccessToken = await getPublicAccessToken()
+  }
+  const option = accessToken || publicAccessToken || process.env.ACCESS_TOKEN
+    ? {
+      auth: {
+        'bearer': accessToken || publicAccessToken || process.env.ACCESS_TOKEN
+      }
     }
-    const option = accessToken || publicAccessToken || process.env.ACCESS_TOKEN
-        ? {
-            auth: {
-                'bearer': accessToken || publicAccessToken || process.env.ACCESS_TOKEN
-            }
-        }
-        : {}
+    : {}
 
-    return {option, releasedOnly}
+  return {option, releasedOnly}
 }
 
 /**
- * 
+ * perhaps export the xform fns into a different module
+ * ideally, in the future, KG can handle xform of voxel to nm
  */
 const transformWaxholmV2NmToVoxel = (coord) => {
   /**
@@ -271,19 +310,19 @@ const transformWaxholmV2NmToVoxel = (coord) => {
    * atlas viewer applies translation (below in nm) in order to center the brain
    * query already translates nm to mm, so the unit of transl should be [mm, mm, mm]
    */
-  const transl = [-9550781,-24355468,-9707031].map(v => v / 1e6)
+  const transl = [-9550781, -24355468, -9707031].map(v => v / 1e6)
 
   /**
    * mm/voxel
    */
   const voxelDim = [0.0390625, 0.0390625, 0.0390625]
-  return coord.map((v, idx) => (v - transl[idx]) / voxelDim[idx] )
+  return coord.map((v, idx) => (v - transl[idx]) / voxelDim[idx])
 }
 
-const transformVoxelToWaxholmV2Nm = (coord) => {
-  const transl = [-9550781,-24355468,-9707031].map(v => v / 1e6)
+const transformWaxholmV2VoxelToNm = (coord) => {
+  const transl = [-9550781, -24355468, -9707031].map(v => v / 1e6)
   const voxelDim = [0.0390625, 0.0390625, 0.0390625]
   return coord.map((v, idx) => (v * voxelDim[idx]) + transl[idx])
 }
 
-    
+const defaultXform = (coord) => coord
\ No newline at end of file
-- 
GitLab