Blog

How to Search for Partial Match Geopoints

by on September 1, 2019

Backendless supports multiple ways to perform a geopoint search. You can search in radius, a rectangular area, or with an SQL query. Additionally, there is a way to search based on a partial metadata match. This type of search can be combined with all other aforementioned search types.

A partial match search will return geopoints whose metadata matches the values specified in the query with the specified (or greater) percentage match. Consider the sample geodata from the article on how to import geodata from a CSV file. The imported data is rendered as shown below in Backendless Console:

Suppose each geopoint represents a profile of a person with interests and food preferences. Each geopoint contains the following metadata properties: burgers, hiking, chinese, gender, and city, with the first three containing the values of yes or no. Suppose a search needs to find all the geopoints that have a 30% match for the following key/value pairs: burgers=yes, hiking=yes and/or chinese=yes. In other words, it does not matter which properties match as long as one-third of them have the specified values (one third is roughly 30%). Consider the following code:

    BackendlessGeoQuery query = new BackendlessGeoQuery();
    query.addCategory("personals");
    query.setIncludeMeta(true);
    
    query.putRelativeFindMetadata("burgers", "true");
    query.putRelativeFindMetadata("hiking", "true");
    query.putRelativeFindMetadata("chinese", "true");
    query.setRelativeFindPercentThreshold(30.0);
    
    AsyncCallback<List<SearchMatchesResult>> relativeFindCallback = new AsyncCallback<List<SearchMatchesResult>>() {
       @Override
       public void handleResponse(List<SearchMatchesResult> searchMatchesCollection) {
           for (SearchMatchesResult result : searchMatchesCollection) {
               Log.i(TAG, "MATCH: %" + result.getMatches());
               Log.i(TAG, result.getGeoPoint().toString());
               Log.i(TAG, "================================");
           }
       }
    
       @Override
       public void handleFault(BackendlessFault fault) {
           Log.e(TAG, fault.getMessage());
       }
    };
    
    Backendless.Geo.relativeFind(query, relativeFindCallback);

    val query = BackendlessGeoQuery()
    query.addCategory("personals")
    query.isIncludeMeta = true
    
    query.putRelativeFindMetadata("burgers", "true")
    query.putRelativeFindMetadata("hiking", "true")
    query.putRelativeFindMetadata("chinese", "true")
    query.relativeFindPercentThreshold = 30.0
    
    val relativeFindCallback = object : AsyncCallback<List<SearchMatchesResult>> {
       override fun handleResponse(searchMatchesCollection: List<SearchMatchesResult>) {
           for (result in searchMatchesCollection) {
               Log.i(TAG, "MATCH: %${result.matches}")
               Log.i(TAG, result.geoPoint.toString())
               Log.i(TAG, "================================")
           }
       }
    
       override fun handleFault(fault: BackendlessFault) {
           Log.e(TAG, fault.message)
       }
    }
    
    Backendless.Geo.relativeFind(query, relativeFindCallback)

    BackendlessGeoQuery *geoQuery = [BackendlessGeoQuery new];
    geoQuery.categories = @[@"personals"];
    geoQuery.includemetadata = YES;
    geoQuery.relativeFindMetadata = @{@"burgers": @"true", @"hiking": @"true", @"chinese": @"true"};
    geoQuery.relativeFindPercentThreshold = 30.0;
        
    [Backendless.shared.geo relativeFindWithGeoQuery:geoQuery responseHandler:^(NSArray *searchMatchResults) {
        for (SearchMatchesResult *result in searchMatchResults) {
            NSLog(@"MATCH: %f%%", result.matches);
            NSLog(@"GeoPoint - %@", [result.geoPoint pointDescription]);
            NSLog(@"================================");
        }
    } errorHandler:^(Fault *fault) {
        NSLog(@"Error: %@", fault.message);
    }];

    let geoQuery = BackendlessGeoQuery()
    geoQuery.categories = ["personals"]
    geoQuery.includemetadata = true
    geoQuery.relativeFindMetadata = ["burgers": "true", "hiking": "true", "chinese": "true"]
    geoQuery.relativeFindPercentThreshold = 30.0
            
    Backendless.shared.geo.relativeFind(geoQuery: geoQuery, responseHandler: { searchMatchResults in
        for result in searchMatchResults {
            print("MATCH: \(result.matches)%")
            print("GeoPoint - \(result.geoPoint?.pointDescription() ?? "")")
            print("================================")
        }
    }, errorHandler: { fault in
        print("Error: \(fault.message ?? "")")
    })

    async function findRelativeGeoPoints() {
      const query = new Backendless.Geo.Query({
        categories: ['personals'],
    
        relativeFindMetadata: {
          burgers: true,
          hiking : true,
          chinese: true,
        },
    
        relativeFindPercentThreshold: 30,
      })
    
      try {
        const items = await Backendless.Geo.relativeFind(query)
    
        items.forEach(item => {
          console.log(`MATCH: ${item.matches}%`)
          console.log(item.geoPoint)
          console.log('================================')
        })
    
      } catch (error) {
        console.error(`Server reported an error: [${error.code}] - ${error.message}`)
      }
    }
    
    findRelativeGeoPoints()
    

    BackendlessGeoQuery geoQuery = BackendlessGeoQuery()
     ..categories = ["personals"]
     ..includeMeta = true
     ..relativeFindPercentThreshold = 30.0
     ..relativeFindMetadata = {
       "burgers": "true",
       "hiking": "true",
       "chinese": "true"
     };
    
    Backendless.Geo.relativeFind(geoQuery).then((searchMatchesCollection) {
     searchMatchesCollection.forEach((result) {
       print("MATCH: %${result.matches}");
       print(result.geoPoint);
       print("================================");
     });
    });


    The program output is:

    MATCH: 66.6667%
    GeoPoint{objectId='86EB3C30-A4C0-A46E-FF4B-BE9678647000', latitude=29.76328, longitude=-95.36327, categories=[personals], metadata={burgers=true, gender=male, hiking=false, chinese=true, city=Houston}, distance=null}
    ================================
    MATCH: 66.6667%
    GeoPoint{objectId='A1A35837-E30B-419A-FF5E-1C9C30E0C100', latitude=32.78306, longitude=-96.80667, categories=[personals], metadata={gender=female, burgers=true, hiking=true, chinese=false, city=Dallas}, distance=null}
    ================================
    MATCH: 33.3333%
    GeoPoint{objectId='4745949A-8941-9DCC-FFAB-9D068CFF0100', latitude=33.15067, longitude=-96.82361, categories=[personals], metadata={gender=female, burgers=false, chinese=true, hiking=false, city=Frisco}, distance=null}
    ================================
    MATCH: 33.3333%
    GeoPoint{objectId='CC5ECD9F-7A8F-7A9B-FF44-6CC19569CC00', latitude=33.01984, longitude=-96.69889, categories=[personals], metadata={gender=male, burgers=false, chinese=false, hiking=true, city=Plano}, distance=null}
    ================================

    As you can see, the search accomplished exactly what we wanted – a partial match at or greater than the percentage threshold. Notice that the search results are returned through a collection of SearchMatchesResult objects. Each object contains a geopoint and the percentage match for the geopoint’s metadata against the requested key/value properties.

    Leave a Reply