001package com.astrolabsoftware.FinkBrowser.HBaser;
002
003import com.Lomikel.HBaser.HBaseClient;
004import com.Lomikel.HBaser.HBaseSQLClient;
005import com.Lomikel.Utils.DateTimeManagement;
006import com.Lomikel.Utils.Pair;
007import com.Lomikel.Utils.LomikelException;
008
009// HealPix
010import cds.healpix.Healpix;
011import cds.healpix.HealpixNested;
012import cds.healpix.HealpixNestedFixedRadiusConeComputer;
013import cds.healpix.HealpixNestedBMOC;
014import cds.healpix.FlatHashIterator;
015import static cds.healpix.VerticesAndPathComputer.LON_INDEX;
016import static cds.healpix.VerticesAndPathComputer.LAT_INDEX;
017
018// HBase
019import org.apache.hadoop.hbase.TableExistsException;
020
021// Java
022import java.lang.Math;
023import java.util.Map;
024import java.util.TreeMap;
025import java.util.Set;
026import java.util.TreeSet;
027import java.util.stream.Collectors;
028import java.io.IOException;
029
030// Log4J
031import org.apache.logging.log4j.Logger;
032import org.apache.logging.log4j.LogManager;
033
034/** <code>FinkHBaseClient</code> handles connectionto HBase table
035  * with specific Fink functionality. 
036  * It expects the main table with schema and two schemaless aux tables:
037  * <ul>
038  * <li><b>*.jd</b> table with <code>key = jd.alert</code> and one
039  * column <code>i:objectId</code>.</li>
040  * <li><b>*.pixel</b> table with <code>key = pixel_jd</code> and
041  * columns <code>i:objectId,i:dec,i:ra</code><li>
042  * </ul>
043  * @opt attributes
044  * @opt operations
045  * @opt types
046  * @opt visibility
047  * @author <a href="mailto:Julius.Hrivnac@cern.ch">J.Hrivnac</a> */
048public class FinkHBaseClient extends HBaseSQLClient {
049   
050  /** Create.
051    * @param zookeepers The comma-separated list of zookeper ids.
052    * @param clientPort The client port. 
053    * @throws LomikelException If anything goes wrong. */
054  public FinkHBaseClient(String zookeepers,
055                         String clientPort) throws LomikelException {
056    super(zookeepers, clientPort);
057    //setFinkEvaluatorFunctions();
058    }
059       
060  /** Create.
061    * @param zookeepers The comma-separated list of zookeper ids.
062    * @param clientPort The client port. 
063    * @throws LomikelException If anything goes wrong. */
064  public FinkHBaseClient(String zookeepers,
065                         int    clientPort) throws LomikelException {
066    super(zookeepers, clientPort);
067    //setFinkEvaluatorFunctions();
068    }
069   
070  /** Create.
071    * @param url The HBase url.
072    * @throws LomikelException If anything goes wrong. */
073  public FinkHBaseClient(String url) throws LomikelException {
074    super(url);
075    //setFinkEvaluatorFunctions();
076    }
077   
078  /** Create on <em>localhost</em>.
079    * @throws LomikelException If anything goes wrong. */
080  // TBD: is it needed, does it work ok ?
081  public FinkHBaseClient() throws LomikelException {
082    super(null, null);
083    setFinkEvaluatorFunctions();
084    }
085    
086  /** Setup the default sets of evaluation functions. */
087  private void setFinkEvaluatorFunctions() {
088    try {
089      evaluator().setEvaluatorFunctions("com.astrolabsoftware.FinkBrowser.HBaser.FinkEvaluatorFunctions", "com/astrolabsoftware/FinkBrowser/HBaser/FinkEvaluatorFunctions.groovy");
090      evaluator().setEvaluatorFunctions(null, "com/astrolabsoftware/FinkBrowser/WebService/FinkHBaseColumnsProcessor.groovy");
091      }
092    catch (LomikelException e) {
093      log.error("Cannot set EvaluatorFunctions", e);
094      }
095    }
096   
097  /** Get alerts between two Julian dates (inclusive).
098    * @param jdStart   The starting Julian date (including day franction).
099    * @param jdStop    The stopping Julian date (including day franction).
100    * @param reversed  Wheter results should be reversly ordered.
101    *                  <tt>true</tt> implies that results limits will be counted backwards.
102    * @param filter    The names of required values as <tt>family:column,...</tt>.
103    *                  It can be <tt>null</tt>.
104    * @param ifkey     Whether give also entries keys.
105    * @param iftime    Whether give also entries timestamps.
106    * @return          The {@link Map} of {@link Map}s of results as <tt>key-&t;{family:column-&gt;value}</tt>. */
107  public Map<String, Map<String, String>> search(String  jdStart,
108                                                 String  jdStop,
109                                                 boolean reversed,
110                                                 String  filter,
111                                                 boolean ifkey,
112                                                 boolean iftime)  {
113    log.debug("Searching for alerts in jd interval: " + jdStart + " - " + jdStop);
114    Map<String, String> searchMap = jd2keys(jdStart, jdStop, reversed);
115    if (searchMap.isEmpty()) {
116      return new TreeMap<String, Map<String, String>>();
117      }
118   // searching each entry separately to profit from HBase start/stop row optimisation
119    Map<String, Map<String, String>> allResults = new TreeMap<>();
120    Map<String, Map<String, String>> aResult;
121    Map<String, String> sMap;
122    for (String key : searchMap.get("key:key:exact").split(",")) {
123      aResult = scan(null,
124                "key:key:" + key + ":exact",
125                filter,
126                0,
127                0,
128                ifkey,
129                iftime);
130      allResults.putAll(aResult);
131      }
132    return allResults;
133    }
134    
135   /** Get alerts within a spacial cone (inclusive).
136    * @param ra     The central value of ra (in deg).
137    * @param dec    The central value of dec (in deg).
138    * @param delta  The maximal angular distance from the central direction (in deg).
139    * @param filter The names of required values as <tt>family:column,...</tt>.
140    *               It can be <tt>null</tt>.
141    * @param ifkey  Whether give also entries keys.
142    * @param iftime Whether give also entries timestamps.
143    * @return       The {@link Map} of {@link Map}s of results as <tt>key-&t;{family:column-&gt;value}</tt>. */
144  public Map<String, Map<String, String>> search(double  ra,
145                                                 double  dec,
146                                                 double  delta,
147                                                 String  filter,
148                                                 boolean ifkey,
149                                                 boolean iftime)  {
150    log.debug("Searching for alerts within " + delta + " deg of (ra, dec) = (" + ra + ", " + dec + ")");
151    Map<String, String> searchMap = radec2keys(ra, dec, delta);
152    if (searchMap.isEmpty()) {
153      return new TreeMap<String, Map<String, String>>();
154      }
155    // searching each entry separately to profit from HBase start/stop row optimisation
156    Map<String, Map<String, String>> allResults = new TreeMap<>();
157    Map<String, Map<String, String>> aResult;
158    Map<String, String> sMap;
159    for (String key : searchMap.get("key:key:exact").split(",")) {
160      aResult = scan(null,
161                "key:key:" + key + ":exact",
162                filter,
163                0,
164                0,
165                ifkey,
166                iftime);
167      allResults.putAll(aResult);
168      }
169    return allResults;
170    }
171  
172  /** Give all objectIds corresponding to specified Julian Date.
173    * It uses *.jd table.
174    * @param jd        The Julian Data (with day fraction).
175    * @param reversed  Wheter results should be reversly ordered.
176    *                  <tt>true</tt> implies that results limits will be counted backwards.
177    * @return          The {@link Map} of corresponding keys of the main table,
178    *                  in the format expected for the scan methods. */
179  public Map<String, String> jd2keys(String jd,
180                                     boolean reversed) {
181    Map<String, String> searchMap = new TreeMap<>();
182    try {
183      HBaseClient client = new HBaseClient(zookeepers(), clientPort());
184      client.connect(tableName() + ".jd");
185      client.setReversed(reversed);
186      client.setLimit(limit());
187      client.setSearchLimit(searchLimit());
188      Map<String, Map<String, String>> results = client.scan(null,
189                                                             "key:key:" + jd,
190                                                             null,
191                                                             0,
192                                                             0,
193                                                             false,
194                                                             false);
195      String keys = results.keySet().stream().map(m -> {String[] key = m.split("_"); return key[1] + "_" + key[0];}).collect(Collectors.joining(","));
196      if (keys != null && !keys.trim().equals("")) { 
197        searchMap.put("key:key:exact", keys);
198        }
199      client.close();
200      }
201    catch (LomikelException e) {
202      log.error("Cannot search", e);
203      }
204    return searchMap;
205    }
206   
207  /** Give all objectIds between two specified Julian Dates (inclusive).
208    * It uses *.jd table.
209    * @param jdStart   The start Julian Data (with day fraction), evaluated as literal prefix scan.
210    * @param jdStart   The stop Julian Data (with day fraction), evaluated as literal prefix scan.
211    * @param reversed  Wheter results should be reversly ordered.
212    *                  <tt>true</tt> implies that results limits will be counted backwards.
213    * @return          The {@link Map} of corresponding keys of the main table,
214    *                  in the format expected for the scan methods. */
215  public Map<String, String> jd2keys(String jdStart,
216                                     String jdStop,
217                                     boolean reversed)  {
218    Map<String, String> searchMap = new TreeMap<>();
219    try {
220      HBaseClient client = new HBaseClient(zookeepers(), clientPort());
221      client.connect(tableName() + ".jd");
222      client.setRangeScan(true);
223      client.setReversed(reversed);
224      client.setLimit(limit());
225      client.setSearchLimit(searchLimit());
226      Map<String, Map<String, String>> results = client.scan(null,
227                                                             "key:key:" + jdStart + ":prefix," + "key:key:" + jdStop + ":prefix",
228                                                             null,
229                                                             0,
230                                                             0,
231                                                             false,
232                                                             false);
233      String keys = results.keySet().stream().map(m -> {String[] key = m.split("_"); return key[1] + "_" + key[0];}).collect(Collectors.joining(","));
234      if (keys != null && !keys.trim().equals("")) { 
235        searchMap.put("key:key:exact", keys);
236        }
237      client.close();
238      }
239    catch (LomikelException e) {
240      log.error("Cannot search", e);
241      }
242    return searchMap;
243    }
244    
245  /** Give all objectIds within a spacial cone.
246    * It uses *.pixel table.
247    * @param ra    The central value of ra/lon  (in deg).
248    * @param dec   The central value of dec/lat (in deg).
249    * @param delta The maximal angular distance from the central direction (in deg).
250    * @return      The {@link Map} of corresponding keys of the main table,
251    *              in the format expected for the scan methods. */
252  public Map<String, String> radec2keys(double ra,
253                                        double dec,
254                                        double delta)  {
255    double coneCenterLon = Math.toRadians(ra);
256    double coneCenterLat = Math.toRadians(dec);
257    double coneRadiusDel = Math.toRadians(delta);
258    //HealpixNestedFixedRadiusConeComputer cc = _hn.newConeComputer(coneRadiusDel);     // beta code!!
259    HealpixNestedFixedRadiusConeComputer cc = _hn.newConeComputerApprox(coneRadiusDel); // robust code
260    HealpixNestedBMOC bmoc = cc.overlappingCenters(coneCenterLon, coneCenterLat);
261    String pixs = "" + _hn.toRing(_hn.hash(coneCenterLon, coneCenterLat));
262    log.debug("Central pixel: " + pixs);
263    int n = 0;
264    FlatHashIterator hIt = bmoc.flatHashIterator();
265    //while (hIt.hasNext()) {
266    //  pixs +=  _hn.toRing(hIt.next()) + ",";
267    //  n++;
268    //  }
269    for (HealpixNestedBMOC.CurrentValueAccessor cell : bmoc) {
270      // cell.getDepth(), cell.isFull(), cell.getRawValue()
271      pixs += "," + _hn.toRing(cell.getHash());
272      n++;
273      } 
274    log.debug("" + n + " cells found (using nside = " + _NSIDE + ", depth = " + Healpix.depth(_NSIDE) + ")");
275    Map<String, String> pixMap = new TreeMap<>();
276    pixMap.put("key:key:prefix", pixs);
277    Map<String, String> searchMap = new TreeMap<>();
278    try {
279      HBaseClient client = new HBaseClient(zookeepers(), clientPort());
280      client.connect(tableName() + ".pixel", null);
281      client.setLimit(limit());
282      client.setSearchLimit(searchLimit());
283      Map<String, Map<String, String>> results = client.scan(null,
284                                                             pixMap,
285                                                             "i:objectId",
286                                                             0,
287                                                             0,
288                                                             false,
289                                                             false);
290      //log.info(results);
291      String keys = results.values().stream().map(m -> m.get("i:objectId")).collect(Collectors.joining(","));
292      if (keys != null && !keys.trim().equals("")) { 
293        searchMap.put("key:key:prefix", keys);
294        }
295      client.close();
296      }
297    catch (LomikelException e) {
298      log.error("Cannot search", e);
299      }
300    return searchMap;
301    }
302    
303  /** Give the timeline for the column. It makes use of the Julian Date alert time
304    * instead of HBase timestamp. 
305    * @param columnName The name of the column.
306    * @param search     The search terms as <tt>family:column:value,...</tt>.
307    *                   Key can be searched with <tt>family:column = key:key<tt> "pseudo-name".
308    *                   {@link Comparator} can be chosen as <tt>family:column:value:comparator</tt>
309    *                   among <tt>exact,prefix,substring,regex</tt>.
310    *                   The default for key is <tt>prefix</tt>,
311    *                   the default for columns is <tt>substring</tt>.
312    *                   It can be <tt>null</tt>.
313    *                   All searches are executed as prefix searches.    
314    * @return         The {@link Set} of {@link Pair}s of JulianDate-value. */
315  @Override
316  public Set<Pair<String, String>> timeline(String columnName,
317                                            String search) {
318    log.debug("Getting alerts timeline of " + columnName + " with " + search);
319    Set<Pair<String, String>> tl = new TreeSet<>();
320    Map<String, Map<String, String>> results = scan(null, search, columnName + ",i:jd", 0, false, false);
321    Pair<String, String> p;
322    for (Map.Entry<String, Map<String, String>> entry : results.entrySet()) {
323      if (!entry.getKey().startsWith("schema")) {
324        p = Pair.of(entry.getValue().get("i:jd"    ),
325                    entry.getValue().get(columnName));
326        tl.add(p);
327        }
328      }
329    return tl;
330    }
331    
332  /** Give all recent values of the column. It makes use of the Julian Date alert time
333    * instead of HBase timestamp. 
334    * Results are ordered by the Julian Date alert time, so evetual limits on results
335    * number will be apllied backwards in Julian date time.
336    * @param columnName     The name of the column.
337    * @param prefixValue    The column value prefix to search for.
338    * @param minutes        How far into the past it should search. 
339    * @param getValues      Whether to get column values or row keys.
340    * @return               The {@link Set} of different values of that column. */
341  @Override
342  public Set<String> latests(String  columnName,
343                             String  prefixValue,
344                             long    minutes,
345                             boolean getValues) {
346    log.debug("Getting " + columnName + " of alerts prefixed by " + prefixValue + " from last " + minutes + " minutes");
347    Set<String> l = new TreeSet<>();
348    double nowJD = DateTimeManagement.julianDate();
349    double minJD = nowJD - minutes / 60.0 / 24.0;
350    Map<String, Map<String, String>> results = search(String.valueOf(minJD),
351                                                      String.valueOf(nowJD),
352                                                      true,
353                                                      columnName,
354                                                      false,
355                                                      false);
356    for (Map.Entry<String, Map<String, String>> entry : results.entrySet()) {
357      l.add(getValues ? entry.getValue().get(columnName) : entry.getKey());
358      }
359    return l;
360    }
361    
362  /** Give all recent values of the column.
363    * The original implementation from {@link HBaseClient}.
364    * Results are ordered by the row key, so evetual limits on results
365    * number will be apllied to them and not to the time.
366    * @param columnName     The name of the column.
367    * @param substringValue The column value substring to search for.
368    * @param minutes        How far into the past it should search (in minutes). 
369    * @param getValues      Whether to get column values or row keys.
370    * @return               The {@link Set} of different values of that column. */
371  public Set<String> latestsT(String  columnName,
372                              String  prefixValue,
373                              long    minutes,
374                              boolean getValues) {
375    return super.latests(columnName, prefixValue, minutes, getValues);
376    }
377  
378  /** Create aux pixel map hash table.
379    * @param keyPrefixSearch The prefix search of row key.
380    * @throws LomikelException If anything goes wrong.
381    * @throws LomikelException If anything goes wrong. */
382  // BUG: should write numberts with schema
383  public void createPixelTable(String keyPrefixSearch) throws LomikelException, IOException {
384    String pixelTableName = tableName() + ".pixel";
385    try {
386      create(pixelTableName, new String[]{"i", "b", "d", "a"});
387      }
388    catch (TableExistsException e) {
389      log.warn("Table " + pixelTableName + " already exists, will be reused");
390      }
391    HBaseClient pixelClient  = new HBaseClient(zookeepers(), clientPort());
392    pixelClient.connect(pixelTableName,  null);
393    Map<String, Map<String, String>> results = scan(null, "key:key:" + keyPrefixSearch + ":prefix", "i:objectId,i:ra,i:dec", 0, false, false);    
394    String objectId;
395    String ra;
396    String dec;
397    String key;
398    log.debug("Writing " + pixelTableName + "...");
399    int n = 0;
400    for (Map.Entry<String, Map<String, String>> entry : results.entrySet()) {
401      objectId = entry.getValue().get("i:objectId");
402      ra       = entry.getValue().get("i:ra");
403      dec      = entry.getValue().get("i:dec");
404      pixelClient.put(Long.toString(_hn.hash(Math.toRadians(Double.valueOf(ra)),
405                                             Math.toRadians(Double.valueOf(dec)))) + "_" + objectId,
406                      new String[]{"i:ra:"       + ra,
407                                   "i:dec:"      + dec,
408                                   "i:objectId:" + objectId});
409      System.out.print(".");
410      if (n++ % 100 == 0) {
411        System.out.print(n-1);
412        }
413      }
414    System.out.println();
415    log.debug("" + n + " rows written");
416    pixelClient.close();
417    }
418  
419  /** Create aux jd map hash table.
420    * @param keyPrefixSearch The prefix search of row key.
421    * @throws IOException      If anything goes wrong.
422    * @throws LomikelException If anything goes wrong. */
423  // BUG: should write numbers with schema
424  public void createJDTable(String keyPrefixSearch) throws LomikelException, IOException {
425    String jdTableName = tableName() + ".jd";
426    try {
427      create(jdTableName, new String[]{"i", "b", "d", "a"});
428      }
429    catch (TableExistsException e) {
430      log.warn("Table " + jdTableName + " already exists, will be reused");
431      }
432    HBaseClient jdClient  = new HBaseClient(zookeepers(), clientPort());
433    jdClient.connect(jdTableName,  null);
434    Map<String, Map<String, String>> results = scan(null, "key:key:" + keyPrefixSearch + ":prefix", "i:objectId,i:jd", 0, false, false);    
435    String objectId;
436    String jd;
437    String key;
438    log.debug("Writing " + jdTableName + "...");
439    int n = 0;
440    for (Map.Entry<String, Map<String, String>> entry : results.entrySet()) {
441      objectId = entry.getValue().get("i:objectId");
442      jd       = entry.getValue().get("i:jd");
443      jdClient.put(jd + "_" + objectId,
444                      new String[]{"i:jd:"       + jd,
445                                   "i:objectId:" + objectId});
446      System.out.print(".");
447      if (n++ % 100 == 0) {
448        System.out.print(n-1);
449        }
450      }
451    System.out.println();
452    log.debug("" + n + " rows written");
453    jdClient.close();
454    }    
455  /** Assemble curves of variable columns from another table
456    * as multi-versioned columns of the current table.
457    * All previous lightcurves for selected <em>objectId</em>s are deleted.
458    * @param sourceClient The {@link HBaseClient} of the source table.
459    *                     It should be already opened and connected with appropriate schema.
460    * @param objectIds    The comma-separated list of <em>objectIds</em> to extract.
461    * @param columns      The comma-separated list of columns (incl. families) to extract.
462    * @param schemaName   The name of the schema to be created in the new table.
463    *                     The columns in the new table will belong to the <em>c</em> family
464    *                     and will have the type of <em>double</em>. */
465  public void assembleCurves(HBaseClient sourceClient,
466                             String      objectIds,
467                             String      columns,
468                             String      schemaName) {
469    String[] schema = columns.split(",");
470    for (int i = 0; i < schema.length; i++) {
471      schema[i] = "c:" + schema[i].split(":")[1] + ":double";
472      }
473    try {
474      put(schemaName, schema);
475      }
476    catch (IOException e) {
477      log.error("Cannot create schema " + schemaName + " = " + schema, e);
478      }
479    try {
480      connect(tableName(), schemaName);
481      }
482    catch (LomikelException e) {
483      log.error("Cannot reconnect to " + tableName() + " with new schema", e);
484      }
485    Map<String, Map<String, String>> results;
486    Set<String> curves = new TreeSet<>();
487    String value;
488    for (String objectId : objectIds.split(",")) {
489      delete(objectId);
490      results = sourceClient.scan(null, "key:key:" + objectId + ":prefix", columns, 0, false, false);
491      log.debug("Adding " + objectId + "[" + results.size() + "]");
492      for (Map.Entry<String, Map<String, String>> row : results.entrySet()) {
493        curves.clear();
494        for (Map.Entry<String, String> e : row.getValue().entrySet()) {
495          value = e.getValue();
496          if (!value.trim().equals("NaN") && !value.trim().equals("null")) {
497            curves.add("c:" + e.getKey().split(":")[1] + ":" + value.trim());
498            }
499          }
500        try {
501          if (!curves.isEmpty()) {
502            put(objectId, curves.toArray(new String[0]));
503            }
504          }
505        catch (IOException e) {
506          log.error("Cannot insert " + objectId + " = " + curves, e);
507         }
508        }
509      }
510    }
511    
512  /** Assemble lightcurves from another table
513    * as multi-versioned columns of the current table.
514    * All previous lightcurves for selected <em>objectId</em>s are deleted.
515    * The colums schema is embedded in this class sourcecode.
516    * @param sourceClient The {@link HBaseClient} of the source table.
517    *                     It should be already opened and connected with appropriate schema.
518    * @param objectIds    The comma-separated list of <em>objectId</em>s to extract. */
519   public void assembleLightCurves(HBaseClient sourceClient,
520                                   String      objectIds) {
521    String columns = "i:jd,d:lc_features_g,d:lc_features_r";
522    String schemaName = "schema_lc_0_0_0";
523    int slength = LIGHTCURVE_SCHEMA.length;
524    String[] schema     = new String[2 * slength];
525    String[] subcolumns = new String[2 * slength];
526    for (int i = 0; i < slength; i++) {
527      schema[    i          ] = "c:lc_g_" + LIGHTCURVE_SCHEMA[i] + ":double";
528      schema[    i + slength] = "c:lc_r_" + LIGHTCURVE_SCHEMA[i] + ":double";
529      subcolumns[i          ] = "c:lc_g_" + LIGHTCURVE_SCHEMA[i];
530      subcolumns[i + slength] = "c:lc_r_" + LIGHTCURVE_SCHEMA[i];
531      }
532    try {
533      put(schemaName, schema);
534      }
535    catch (IOException e) {
536      log.error("Cannot create schema " + schemaName + " = " + schema, e);
537      }
538    try {
539      connect(tableName(), schemaName);
540      }
541    catch (LomikelException e) {
542      log.error("Cannot reconnect to " + tableName() + " with new schema", e);
543      }
544    Map<String, Map<String, String>> results;
545    Set<String> curves = new TreeSet<>();
546    int i;
547    for (String objectId : objectIds.split(",")) {
548      delete(objectId);
549      results = sourceClient.scan(null, "key:key:" + objectId + ":prefix", columns, 0, false, false);
550      log.debug("Adding " + objectId + "[" + results.size() + "]");
551      for (Map.Entry<String, Map<String, String>> row : results.entrySet()) {
552        curves.clear();
553        i = 0;
554        for (Map.Entry<String, String> e : row.getValue().entrySet()) {
555          if (e.getValue().contains("]")) {            
556            for (String value : e.getValue().replaceAll("\\[", "").replaceAll("]", "").split(",")) {
557              if (!value.trim().equals("NaN") && !value.trim().equals("null")) {
558                curves.add(subcolumns[i] + ":" + value.trim());
559                }
560              i++;
561              }
562            }
563          else {
564            curves.add("c:jd:" + e.getValue());
565            }
566          }
567        try {
568          if (!curves.isEmpty()) {
569            put(objectId, curves.toArray(new String[0]));
570            }
571          }
572        catch (IOException e) {
573          log.error("Cannot insert " + objectId + " = " + curves, e);
574          }
575        }
576      }
577    }
578    
579  private static String[] LIGHTCURVE_SCHEMA = new String[]{"lc00",
580                                                           "lc01",
581                                                           "lc02",
582                                                           "lc03",
583                                                           "lc04",
584                                                           "lc05",
585                                                           "lc06",
586                                                           "lc07",
587                                                           "lc08",
588                                                           "lc09",
589                                                           "lc10",
590                                                           "lc11",
591                                                           "lc12",
592                                                           "lc13",
593                                                           "lc14",
594                                                           "lc15",
595                                                           "lc16",
596                                                           "lc17",
597                                                           "lc18",
598                                                           "lc19",
599                                                           "lc20",
600                                                           "lc21",
601                                                           "lc22",
602                                                           "lc23",
603                                                           "lc24",
604                                                           "lc25",
605                                                           "lc26"};
606    
607  private static int _NSIDE = 131072; // BUG: magic number 
608    
609  private static HealpixNested _hn = Healpix.getNested(Healpix.depth(_NSIDE));  
610    
611  /** Logging . */
612  private static Logger log = LogManager.getLogger(FinkHBaseClient.class);
613
614  }