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:
The Data Accessor Function
fromView()
pulls data indexed for the viewProfile
associated with the schemaEmployee
into a row sequence of this view’s columns.The Expression Function
op.geo.within()
is one of many geospatial expression functions. It returnsTRUE
if the region specified in the first parameter is within the region specified by the second parameter:Its first parameter provides the first region with
col(Point)
:Point
’s coordinates.Its second parameter provides the second region: a polygon approximating a geospatial circle that
geo.circlePolygon()
determines.So,
op.geo.within()
returnsTRUE
for everyPoint
within a 25-mile radius of our customer’slocation
.
The Operator Function
bind()
usesas()
to define a new column and sets its value for each row in turn.The Expression Function
op.geo.distance()
calculates the radial distance between two points.The Operator Function
select()
constrains the query to only the specified columns.The Operator Function
orderBy()
sorts its input row sequence into the order specified.The Auxiliary Function
asc()
explicitly sorts rows in ascending order by the specified column's value. The default order is ascending. The Auxiliary Functiondesc()
sorts in descending order.The Operator Function
offsetLimit()
restricts results returned. The first parameter specifies the number of results to skip; the second, the number of results to return. So, (0, 100) returns the first 100 results.The Executor Function
result()
executes the query and returns the results as a row sequence.
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.