Skip to main content

Getting Started with Optic

Building Queries with Geospatial Constraints

Geospatial search is a powerful feature of MarkLogic query capabilities that you will want to explore. See Geospatial Search Applications in the Search Developer's Guide for more detailed information than we provide here.

We want to find all our employees within a 25-mile radius of a customer located in a tri-state area. Using that customer’s specific latitude and longitude and a geospatial query will yield better results than using either the employees’ state or ZIP Code to find nearby employees.

So, when we built our employee profile view, we set the scalarType of the Point column to point. Then we set Point's value to a single latitude, longitude coordinate point using cts:point to pull them from the individual lat and long properties of the document:

// Snippet from TDE Employee:
{
  "name": "Point",                           // Column 18: Point
  "scalarType": "point",                     // Data Type: point
  "val": "cts:point(point/lat, point/long)", // Values from point property array
  "coordinateSystem": "wgs84",               // Coordinate System: WGS84
  "nullable": true,
  "invalidValues": "ignore"
}
// Snippet from employee document:

"point" {                                    // point property array
 "lat": 37.443029,
 "long": -121.720815
}

When we created our view column with the scalarType point, we also followed best practice of specifying its coordinateSystem even though wgs84 is the default.

Now, we can use the Point column in any function requiring latitude and longitude as an x,y coordinate point.

An Optic query like this one retrieves a row sequence containing specified columns and ordered from employee closest to farthest away from a specified geolocation:

const location = cts.point(39.7176,-75.9349) // In NE MD near PA & DE borders

op.fromView('Employee', 'Profile')
  .where(
    op.geo.within(
      op.col('Point'),
      geo.circlePolygon(cts.circle(25, location),0.5)
    )
  )
  .bind(op.as('Distance', op.geo.distance(op.col('Point'), location)))
  .select(['GUID', 'Surname', 'State', 'Point', 'Distance'])
  .orderBy(op.col('Distance'))
  .offsetLimit(0, 100)
  .result();

We used this query to retrieve a row sequence for each employee within a 25-mile radius of our customer’s location, ordered from closest to farthest away. Each row contains the GUID, Surname, State, and Point columns as well as Distance, a column calculated within the query to contain how far each employee is from our customer:

Here is the 4-row x 5-column result:

{
  "Employee.Profile.GUID": "b7d04aa0-8348-4794-b9a6-a2f2723f3ffe", 
  "Employee.Profile.Surname": "English", 
  "Employee.Profile.State": "DE", 
  "Employee.Profile.Point": "39.761456,-75.803268", 
  "Distance": 7.63593344994741},
}
{
  "Employee.Profile.GUID": "8de256d2-b6ba-4b3f-9b2e-2e0f65fa9025", 
  "Employee.Profile.Surname": "Desantis", 
  "Employee.Profile.State": "PA", 
  "Employee.Profile.Point": "39.68557,-75.559578", 
  "Distance": 20.1233662009832},
}
{
  "Employee.Profile.GUID": "1c208e16-5350-4e4e-83eb-24ccc72bdabd", 
  "Employee.Profile.Surname": "McCray", 
  "Employee.Profile.State": "MD", 
  "Employee.Profile.Point": "39.622498,-76.300697", 
  "Distance": 20.5768543237538},
}
{
  "Employee.Profile.GUID": "97f4c500-b158-4e92-9cdb-f69e24bc2c57", 
  "Employee.Profile.Surname": "Barlow", 
  "Employee.Profile.State": "PA", 
  "Employee.Profile.Point": "39.738686,-76.396965", 
  "Distance": 24.6574870655178}
}
  • The columns are the ones we specified in select().

  • The columns are presented in the order they appear in select().

  • Employees from 3 different states are among the results.

  • We could pass this result to a client application that plots points onto a map.