6791876ba30394c3aad18702708feb4bfb6ec761
braney
  Thu Jul 19 17:04:44 2018 -0700
add bigBed numeric and filterBy filters

diff --git src/hg/hgTracks/bigBedTrack.c src/hg/hgTracks/bigBedTrack.c
index dfc933c..e2e7c54 100644
--- src/hg/hgTracks/bigBedTrack.c
+++ src/hg/hgTracks/bigBedTrack.c
@@ -12,30 +12,184 @@
 #include "hdb.h"
 #include "bedCart.h"
 #include "hgTracks.h"
 #include "hmmstats.h"
 #include "localmem.h"
 #include "wigCommon.h"
 #include "bbiFile.h"
 #include "obscure.h"
 #include "bigWig.h"
 #include "bigBed.h"
 #include "bigWarn.h"
 #include "errCatch.h"
 #include "trackHub.h"
 #include "net.h"
 #include "bigPsl.h"
+#include "bigBedFilter.h"
+
+struct bigBedFilter *bigBedMakeNumberFilter(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb, char *filter, char *defaultLimits,  char *field)
+/* Make a filter on this column if the trackDb or cart wants us to. */
+{
+struct bigBedFilter *ret = NULL;
+char *setting = trackDbSettingClosestToHome(tdb, filter);
+int fieldNum =  bbExtraFieldIndex(bbi, field) + 3;
+if (setting)
+    {
+    boolean invalid = FALSE;
+    double minValueTdb = 0,maxValueTdb = NO_VALUE;
+    colonPairToDoubles(setting,&minValueTdb,&maxValueTdb);
+    double minLimit=NO_VALUE,maxLimit=NO_VALUE,min=minValueTdb,max=maxValueTdb;
+    colonPairToDoubles(defaultLimits,&minLimit,&maxLimit);
+    getScoreFloatRangeFromCart(cart,tdb,FALSE,filter,&minLimit,&maxLimit,&min,&max);
+    if ((int)minLimit != NO_VALUE || (int)maxLimit != NO_VALUE)
+        {
+        // assume tdb default values within range!
+        // (don't give user errors that have no consequence)
+        if ((min != minValueTdb && (((int)minLimit != NO_VALUE && min < minLimit)
+                                || ((int)maxLimit != NO_VALUE && min > maxLimit)))
+        ||  (max != maxValueTdb && (((int)minLimit != NO_VALUE && max < minLimit)
+                                || ((int)maxLimit != NO_VALUE && max > maxLimit))))
+            {
+            invalid = TRUE;
+            char value[64];
+            if ((int)max == NO_VALUE) // min only is allowed, but max only is not
+                safef(value, sizeof(value), "entered minimum (%g)", min);
+            else
+                safef(value, sizeof(value), "entered range (min:%g and max:%g)", min, max);
+            char limits[64];
+            if ((int)minLimit != NO_VALUE && (int)maxLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates limits (%g to %g)", minLimit, maxLimit);
+            else if ((int)minLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates lower limit (%g)", minLimit);
+            else //if ((int)maxLimit != NO_VALUE)
+                safef(limits, sizeof(limits), "violates uppper limit (%g)", maxLimit);
+            warn("invalid filter by %s: %s %s for track %s", field, value, limits, tdb->track);
+            }
+        }
+    if (invalid)
+        {
+        char filterLimitName[64];
+        safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MIN);
+        cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
+        safef(filterLimitName, sizeof(filterLimitName), "%s%s", filter, _MAX);
+        cartRemoveVariableClosestToHome(cart,tdb,FALSE,filterLimitName);
+        }
+    else if (((int)min != NO_VALUE && ((int)minLimit == NO_VALUE || minLimit != min))
+         ||  ((int)max != NO_VALUE && ((int)maxLimit == NO_VALUE || maxLimit != max)))
+         // Assumes min==NO_VALUE or min==minLimit is no filter
+         // Assumes max==NO_VALUE or max==maxLimit is no filter!
+        {
+        AllocVar(ret);
+        ret->fieldNum = fieldNum;
+        if ((int)max == NO_VALUE || ((int)maxLimit != NO_VALUE && maxLimit == max))
+            {
+            ret->comparisonType = COMPARE_MORE;
+            ret->value1 = min;
+            }
+        else if ((int)min == NO_VALUE || ((int)minLimit != NO_VALUE && minLimit == min))
+            {
+            ret->comparisonType = COMPARE_LESS;
+            ret->value1 = max;
+            }
+        else
+            {
+            ret->comparisonType = COMPARE_BETWEEN;
+            ret->value1 = min;
+            ret->value2 = max;
+            }
+        }
+    }
+return ret;
+}
+
+
+struct bigBedFilter *bigBedMakeFilterBy(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb, char *field, struct slName *choices)
+/* Add a bigBed filter using a trackDb filterBy statement. */
+{
+struct bigBedFilter *filter;
+
+AllocVar(filter);
+filter->fieldNum =  bbExtraFieldIndex(bbi, field) + 3;
+filter->comparisonType = COMPARE_HASH;
+filter->valueHash = newHash(5);
+
+for(; choices; choices = choices->next)
+    hashStore(filter->valueHash, choices->name);
+
+return filter;
+}
+
+struct bigBedFilter *bigBedBuildFilters(struct cart *cart, struct bbiFile *bbi, struct trackDb *tdb)
+/* Build all the numeric and filterBy filters for a bigBed */
+{
+struct bigBedFilter *filters = NULL, *filter;
+struct slName *filterSettings = trackDbSettingsWildMatch(tdb, "*Filter");
+
+for(; filterSettings; filterSettings = filterSettings->next)
+    {
+    char *fieldName = cloneString(filterSettings->name);
+    fieldName[strlen(fieldName) - sizeof "Filter" + 1] = 0;
+    if ((filter = bigBedMakeNumberFilter(cart, bbi, tdb, filterSettings->name, NULL, fieldName)) != NULL)
+        slAddHead(&filters, filter);
+    }
+
+filterBy_t *filterBySet = filterBySetGet(tdb, cart,NULL);
+filterBy_t *filterBy = filterBySet;
+for (;filterBy != NULL; filterBy = filterBy->next)
+    {
+    if (filterBy->slChoices && differentString(filterBy->slChoices->name, "All")) 
+        {
+        if ((filter = bigBedMakeFilterBy(cart, bbi, tdb, filterBy->column, filterBy->slChoices)) != NULL)
+            slAddHead(&filters, filter);
+        }
+    }
+
+return filters;
+}
+
+
+boolean bigBedFilterInterval(char **bedRow, struct bigBedFilter *filters)
+/* Go through a row and filter based on filters.  Return TRUE if all filters are passed. */
+{
+struct bigBedFilter *filter;
+for(filter = filters; filter; filter = filter->next)
+    {
+    double val = atof(bedRow[filter->fieldNum]);
+
+    switch(filter->comparisonType)
+        {
+        case COMPARE_HASH:
+            if (!hashLookup(filter->valueHash, bedRow[filter->fieldNum]))
+                return FALSE;
+            break;
+        case COMPARE_LESS:
+            if (!(val <= filter->value1))
+                return FALSE;
+            break;
+        case COMPARE_MORE:
+            if (!(val >= filter->value1))
+                return FALSE;
+            break;
+        case COMPARE_BETWEEN:
+            if (!((val >= filter->value1) && (val <= filter->value2)))
+                return FALSE;
+            break;
+        }
+    }
+return TRUE;
+}
+
 
 struct bbiFile *fetchBbiForTrack(struct track *track)
 /* Fetch bbiFile from track, opening it if it is not already open. */
 {
 struct bbiFile *bbi = track->bbiFile;
 if (bbi == NULL)
     {
     char *fileName = NULL;
     if (track->parallelLoading) // do not use mysql during parallel fetch
 	{
 	fileName = cloneString(trackDbSetting(track->tdb, "bigDataUrl"));
         if (fileName == NULL)
             fileName = cloneString(trackDbSetting(track->tdb, "bigGeneDataUrl"));
 	}
     else 
@@ -172,78 +326,85 @@
 char *scoreFilter = cartOrTdbString(cart, track->tdb, "scoreFilter", NULL);
 char *mouseOverField = cartOrTdbString(cart, track->tdb, "mouseOverField", NULL);
 int minScore = 0;
 if (scoreFilter)
     minScore = atoi(scoreFilter);
 
 struct bbiFile *bbi = fetchBbiForTrack(track);
 int seqTypeField =  0;
 if (sameString(track->tdb->type, "bigPsl"))
     {
     seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
     }
 
 int mouseOverIdx = bbExtraFieldIndex(bbi, mouseOverField);
 
-bbiFileClose(&bbi);
 track->bbiFile = NULL;
 
+struct bigBedFilter *filters = bigBedBuildFilters(cart, bbi, track->tdb) ;
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
-    struct linkedFeatures *lf;
+    struct linkedFeatures *lf = NULL;
     if (sameString(track->tdb->type, "bigPsl"))
 	{
 	char *seq, *cds;
 	struct psl *psl = pslFromBigPsl(chromName, bb, seqTypeField,  &seq, &cds); 
 	int sizeMul =  pslIsProtein(psl) ? 3 : 1;
 	boolean isXeno = 0;  // just affects grayIx
 	boolean nameGetsPos = FALSE; // we want the name to stay the name
 
 	lf = lfFromPslx(psl, sizeMul, isXeno, nameGetsPos, track);
 	lf->original = psl;
 	if ((seq != NULL) && (lf->orientation == -1))
 	    reverseComplement(seq, strlen(seq));
 	lf->extra = seq;
 	lf->cds = cds;
 	}
     else
 	{
         char startBuf[16], endBuf[16];
         char *bedRow[32];
 
         bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow));
+        if (bigBedFilterInterval(bedRow, filters))
+            {
             struct bed *bed = bedLoadN(bedRow, fieldCount);
             lf = bedMungToLinkedFeatures(&bed, tdb, fieldCount,
                 scoreMin, scoreMax, useItemRgb);
             }
+	}
+
+    if (lf == NULL)
+        continue;
 
     lf->label = makeLabel(track,  bb);
     if (sameString(track->tdb->type, "bigGenePred") || startsWith("genePred", track->tdb->type))
         {
         lf->original = genePredFromBigGenePred(chromName, bb); 
         }
 
     char* mouseOver = restField(bb, mouseOverIdx);
     lf->mouseOver   = mouseOver; // leaks some memory, cloneString handles NULL ifself 
 
     if (scoreFilter == NULL || lf->score >= minScore)
 	slAddHead(pLfList, lf);
     }
 lmCleanup(&lm);
 
 if (!trackDbSettingClosestToHomeOn(track->tdb, "linkIdInName"))
     track->itemName = bigLfItemName;
+bbiFileClose(&bbi);
 }
 
 
 boolean canDrawBigBedDense(struct track *tg)
 /* Return TRUE if conditions are such that can do the fast bigBed dense data fetch and
  * draw. */
 {
 return tg->isBigBed;
 }
 
 
 void bigBedDrawDense(struct track *tg, int seqStart, int seqEnd,
         struct hvGfx *hvg, int xOff, int yOff, int width,
         MgFont *font, Color color)
 /* Use big-bed summary data to quickly draw bigBed. */