81d00f3eec6ea6978c9a71ed6a48c84a0bd0c987
braney
  Wed Apr 22 14:51:08 2026 -0700
hgFind: remap bigBed search hits from source to destination coords when the track is quickLifted. Previously a search for e.g. "BRCA2" on a quickLifted hub (hg38 tracks displayed on HG02257.pat) returned hits at hg38 chr13 coordinates; clicking the result errored with "Sorry, couldn't locate chr13:... in <dest>". Adds quickLiftLiftPos() in hg/lib/quickLift.c, which reads the source->dest liftOverChainFile and calls liftOverRemapRange. Called from bigBedIntervalListToHgPositions in hg/lib/bigBedFind.c whenever tdb has quickLiftUrl/quickLiftDb; hits that don't map through the chain are dropped. refs #36340

diff --git src/hg/lib/bigBedFind.c src/hg/lib/bigBedFind.c
index 24b85d71d62..70f19589be0 100644
--- src/hg/lib/bigBedFind.c
+++ src/hg/lib/bigBedFind.c
@@ -1,40 +1,47 @@
 #include "common.h"
 #include "bPlusTree.h"
 #include "bbiFile.h"
 #include "bigBed.h"
 #include "hgFind.h"
 #include "trix.h"
 #include "trackHub.h"
 #include "hubConnect.h"
 #include "hdb.h"
 #include "errCatch.h"
 #include "bigBedLabel.h"
 #include "bigBedFind.h"
 #include "genbank.h"
+#include "quickLift.h"
 
 static struct hgPos *bigBedIntervalListToHgPositions(struct cart *cart, struct trackDb *tdb,
                         struct bbiFile *bbi, char *term, struct bigBedInterval *intervalList,
                         char *description, struct hgFindSpec *hfs, char *db)
 /* Given an open bigBed file, and an interval list, return a pointer to a list of hgPos structures. */
 {
 struct hgPos *posList = NULL;
 char chromName[bbi->chromBpt->keySize+1];
 int lastChromId = -1;
 struct bigBedInterval *interval;
 struct slInt *labelColumns = NULL;
 
+// If this track is being quickLifted, the bigBed returns hits in the source
+// assembly's coordinates; we need to remap them to the destination assembly.
+char *quickLiftDb = trackDbSetting(tdb, "quickLiftDb");
+boolean quickLifted = (trackDbSetting(tdb, "quickLiftUrl") != NULL) && isNotEmpty(quickLiftDb);
+char *destDb = trackHubSkipHubName(db);
+
 // Generic searchItemLabel support: allows ${fieldName} patterns in hgFindSpec
 char *searchItemLabel = NULL;
 char **fieldNames = NULL;
 if (hfs)
     searchItemLabel = hgFindSpecSetting(hfs, "searchItemLabel");
 if (searchItemLabel)
     {
     AllocArray(fieldNames, bbi->fieldCount);
     struct slName *field = NULL, *fields = bbFieldNames(bbi);
     int i = 0;
     for (field = fields; field != NULL && i < bbi->fieldCount; field = field->next)
         fieldNames[i++] = field->name;
     }
 
 // MANE/HGNC description support
@@ -45,40 +52,51 @@
     {
     as = bigBedAsOrDefault(bbi);
     if (sameString(hfs->searchName, "mane"))
         {
         conn = hAllocConn(db);
         ncbiIdIx = asColumnFindIx(as->columnList, "ncbiId");
         }
     else if (sameString(hfs->searchName, "hgnc"))
         geneNameIx = asColumnFindIx(as->columnList, "geneName");
     }
 
 bigBedLabelCalculateFields(cart, tdb, bbi,  &labelColumns );
 char startBuf[256], endBuf[256], *row[bbi->fieldCount];
 for (interval = intervalList; interval != NULL; interval = interval->next)
     {
+    bbiCachedChromLookup(bbi, interval->chromId, lastChromId, chromName, sizeof(chromName));
+    lastChromId = interval->chromId;
+
+    char *posChrom = chromName;
+    int posStart = interval->start;
+    int posEnd = interval->end;
+    if (quickLifted)
+        {
+        // Remap source-assembly coords to destination-assembly coords; skip if unmappable.
+        if (!quickLiftLiftPos(quickLiftDb, destDb, chromName, interval->start, interval->end,
+                              &posChrom, &posStart, &posEnd))
+            continue;
+        }
+
     struct hgPos *hgPos;
     AllocVar(hgPos);
     slAddHead(&posList, hgPos);
 
-    bbiCachedChromLookup(bbi, interval->chromId, lastChromId, chromName, sizeof(chromName));
-    lastChromId = interval->chromId;
-
-    hgPos->chrom = cloneString(chromName);
-    hgPos->chromStart = interval->start;
-    hgPos->chromEnd = interval->end;
+    hgPos->chrom = cloneString(posChrom);
+    hgPos->chromStart = posStart;
+    hgPos->chromEnd = posEnd;
     hgPos->description = cloneString(description);
 
     int rowFieldCount = bigBedIntervalToRow(interval, chromName, startBuf, endBuf, row, bbi->fieldCount);
     if (searchItemLabel)
         {
         hgPos->name = replaceFieldInPattern(searchItemLabel, bbi->fieldCount, fieldNames, row);
         }
     else
         {
         hgPos->name = bigBedMakeLabel(tdb, labelColumns, interval, chromName);
         }
     // browserName needs to correspond to tg->mapItemName()
     if (rowFieldCount > 3)
         hgPos->browserName = cloneString(row[3]);
     else