Performing geolocation searches and filtering in Oracle Commerce Cloud Search

Gregory Eschbacher
Oracle Developers
Published in
6 min readJun 9, 2019

Many retailers offer a “Find a nearest store” feature on their site. With features added as part of the 19A release of Oracle Commerce Cloud, Search can provide this functionality via the use of APIs.

This article will walk through the necessary steps of preparing data, updating configuration, indexing and querying.

I have also created a Postman workspace containing the REST queries used for this tutorial, and I highly recommend you use Postman with that workspace to follow along this tutorial.

A lot of this material was covered in a recent webinar that I helped run about indexing additional content. That webinar can be viewed on Oracle Partner Network . The workflow for geolocation is nearly the same as the buyer’s guides we indexed in that webinar, so please watch that if you have detailed questions.

Using the Postman workspace

I plan on sharing Postman workspaces for my articles in the future as it allows you to see exactly what I’m doing. In order to use this workspace, you need to set up a few environment variables:

  • ADMIN_URL: Complete URL to your admin server. This should include https:// etc.
  • SF_URL: Same as above, but to your storefront server
  • app_key: In OCC Admin, you must create a REST API application key

Once you set up those environment variables, you should be able to perform the “Login to Admin” request. When you do that, another environment variable is magically created called “token”. This allows all subsequent queries in the workspace to be authenticated.

Reminder: The login timer only lasts a little while, so you might have to perform “Login to Admin” a few times during the process.

To create the “app_key” in OCC, do the following:

  1. Log into OCC Admin
  2. Go to Settings -> Web APIs
  3. Click on Registered Applications
  4. +Registered Application
  5. Give it a name (I called mine “postman”)
  6. Click on your application name, and “Click to reveal” the application key
  7. Copy that key in its entirety (it’s quite long). For reference, my application key was 222 characters long. I believe they will always end in a =

Summary of Steps to Create and Search Geolocation Data

The overall steps will be:

  • Prepare your data as JSON documents in the format described below
  • POST store location data to /gsdata/v1/cloud/data/stores
  • Configure custom attributes at /gsadmin/v1/cloud/attributes/stores
  • Create a custom search service to handle queries against this data at /gsadmin/v1/cloud/pages/Default/storeSearch
  • Initiate indexing
  • Query the data

Preparing your data

Here is an example record containing store information.

{
"items": [
{
"content.type": "store",
"record.id" : "store-1",
"store.name": "ACME NJ",
"store.city": "Newark",
"store.zip": "07076",
"store.address": "123 Main St",
"store.state": "New Jersey",
"store.phone": "908-123-4567",
"store.geocode" : "40.735657,-74.172363"

},
....
}

The key information to point out is the geocode format. It is latitude comma longitude. Don’t add extra spaces or anything.

We will only use the “store.geocode” field in the queries below. The other values represent example data that you might wish to render on the screen.

Uploading your data

Regarding the JSON above, you would have one item in the array of items per store.

When your data is ready, you would POST it to /gsdata/v1/cloud/data/stores . Note that the “stores” in that URL is of your own choosing, much like how “buyingGuides” was created in the OPN webinar.

You can also GET that URL to view the uploaded documents. For more information on that, consult the webinar and other OPN content (such as https://community.oracle.com/groups/oracle-commerce-cloud-group/blog/2019/03/11/enhancing-your-search-with-the-type-ahead-framework )

Defining the attributes

In the JSON above, we created attributes such as store.geocode, store.phone, etc. Those are attribute names of your own creation, and that means we have to define them before they will show up in the index.

The Postman workspace I shared has the full list of attributes and their schema, so I won’t paste that here. Instead, here is the attribute definition for “store.geocode”.

"store.geocode": {
"ecr:lastModifiedBy": "admin",
"propertyDataType": "GEOCODE",
"ecr:type": "property"

},

Note that it is of ecr:type = property, and that the propertyDataType=GEOCODE.

POST these custom attributes to /gsadmin/v1/cloud/attributes/stores . Like the store records above, the “stores” in that URL is of your own choosing. As mentioned in some of my other articles, I no longer recommend adding new custom attributes to /attributes/system. Instead, create your own folder based on the type of record. The word “stores” doesn’t matter as all attributes get merged together.

If you need to update attributes, then GET /gsadmin/v1/cloud/attributes/stores , modify the JSON, and then PUT it back. PUT replaces, POST creates.

Creating a Store search service

At this point, we have uploaded data and created attribute configurations. Now we need to prepare a search service for querying that data.

Here is a basic store search service. POST this to /gsadmin/v1/cloud/pages/Default/storeSearch

{
"ecr:lastModifiedBy": "admin",
"contentType": "Page",
"ecr:type": "page",
"contentItem": {
"@name": "Store Search Service",
"@type": "TypeaheadService",
"@appFilterState": {
"@type": "FilterState",
"recordFilters": [
"content.type:store"
]
},

"resultsList": {
"@type": "ResultsList",
"relRankStrategy": "maxfield,glom,nterms,static(guides.description)",
"recordsPerPage": "20",
"fieldNames": [
"store.name", "store.address", "store.zip", "store.state", "store.phone", "store.city"
]
}
}
}

The important bits to point out:

  • We have a recordFilter on “content.type:store”. This limits the service to only search our new store location records (who have values “content.type” : “store” in my sample data)
  • We configure “fieldNames” to list the new attributes we created to be returned. In my previous article, I described how we can use “fieldNames” and “subRecordFieldNames” to limit the data returned.

When it comes to geolocation data, we’re not really searching it with keyword searches. So the relRankStrategy doesn’t matter.

Instead, we perform sorts and filters, as we’ll see below.

Initiate Indexing

At this point, we’re ready to start indexing. To do so, POST /ccadmin/v1/search/index?op=baseline

That will take the data, attributes and search service and update the index with that information. Note that we used “op=baseline”, as a baseline is required when schema has changed (specifically, when we added to /attributes).

(For more background information on indexing, see my previous article “Understanding indexing for Search in Oracle Commerce Cloud” )

Querying geolocation data

There are two kinds of queries that are typically performed with geolocation data. Those would be:

  • “Show me the list of stores closest to me in order of distance”
  • “Show me the list of stores within X kilometers of this location”

Both are possible with Search.

In my sample data, I have created 4 stores: Newark NJ, New York City, Denver Colorado, and Seattle Washington.

First, here is an example query that simply sorts the stores based on the distance from Trenton NJ.

/ccstoreui/v1/assembler/pages/Default/storeSearch?N=0&Ns=store.geocode(40.217052,-74.742935)

So let’s dive into this query:

  • /assembler/pages/Default/storeSearch points to the custom search service we created
  • store.geocode is the field name we created in the data and defined under /attributes/stores
  • Ns= means to sort.
  • Ns=store.geocode(40.217052,-74.742935) basically instructs the index to sort based on this location (which is the location of Trenton NJ, which is closer to Newark NJ with NYC close behind)

In our next query, we will filter based on distance:

/ccstoreui/v1/assembler/pages/Default/storeSearch?N=0&Ns=store.geocode(40.217052,-74.742935)&Nfg=store.geocode|40.217052|-74.742935|150

Okay, so this is mostly the same query as above but now we have the addition of the &Nfg= parameter. Think of this as “navigation filter geocode”.

For this string, the format is:

fieldName|latitude|longitude|radius(kilometers)

So in our example, we are filtering for records that are within 150km of Trenton, NJ. Then, for that set of results, we are sorting based on distance from that location. (You could also filter without the sort. They are not tied together)

With those 2 types of queries, you are able to implement a typical store location search.

Note that the pipe character needs to be URL encoded in your web applications. It’s okay in Postman, but you will need to encode it in your front-end code.

A note on geolocation distance measurement

A quick side note on how the index calculates distances.

  1. The distance formula is based on detecting distances on a sphere
  2. The distance formula uses “as the crow flies” for values. Meaning, it doesn’t take into account streets, highways, etc. Google Maps or some other API would have to provide that level of detail

Closing thoughts….

This is most useful in the context of “show me stores near the location as reported by the browser”. Javascript has an api where you can access the location (detailed a bit here).

However, there are other ways to implement this. If you ask the shopper for a zip code, for instance, that might require something like:

  • Ask for zip code
  • Use a web service to translate zip code into long/lat
  • Perform the queries above

Hopefully you found this useful. Remember, you must be on 19A or higher before you can access the /gsdata endpoint to load data.

If you have any suggestions on topics for future articles, please let me know!

--

--

Gregory Eschbacher
Oracle Developers

technologist, Oracle Cloud architect, commerce specialist