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->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->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 }