Geospatial traversals

Creating geospatial traversal queries.

Geospatial queries are used to discover geospatial information. All geospatial data types (points, linestrings, and polygons) can be searched for specified values with simple queries. More interesting traversal queries discover points or linestrings within a radius from a specified point or within a specified geospatial polygon.
Important: All points must be specified in (longitude, latitude) following WKT format.
Distance calculations are crucial to proper results. DSE Search indexes can be created for geospatial data in DSE Graph, and DSE Search uses the Haversine formula to determine the great-circle distance between two points. DSE Search indexes cannot be created for polygons, but are essential to making geospatial point and linestring queries performant.
Note: A point of confusion can occur if the same geospatial query is run with or without a DSE Search index. Without a search index, geospatial queries always return exact results. DSE Search indexes, however, trade off write performance and index size for query accuracy with two tunable parameters, maxDistErr (default: 0.000009) and distErrPct (default: 0.025). Inconsistent results in these two cases are due to the distance calculation algorithm variation of the default values of these parameters. DSE Graph can pass values for these two parameters when creating the search index. Change maxDistErr in withError(maxDistErr, distErrPct) to 0.0 to force both index-backed and non-index-backed queries to yield the same value:
schema.vertexLabel('location').index('search').search().by('point').withError(0.000009,0.0).add()

Schema and data

The examples here use the following schema:
// SCHEMA
// POINT
schema.propertyKey('name').Text().create()
schema.propertyKey('point').Point().withGeoBounds().create()
schema.vertexLabel('location').properties('name','point').create()
// LINESTRING
schema.propertyKey('line').Linestring().withGeoBounds().create()
schema.vertexLabel('lineLocation').properties('name','line').create()
// POLYGON
schema.propertyKey('polygon').Polygon().withGeoBounds().create()
schema.vertexLabel('polyLocation').properties('name','polygon').create()
// MATERIALIZED VIEW INDEXES
schema.vertexLabel('location').index('byname').materialized().by('name').add()
schema.vertexLabel('lineLocation').index('byname').materialized().by('name').add()
schema.vertexLabel('polyLocation').index('byname').materialized().by('name').add()
//SEARCH INDEX - ONLY WORKS FOR POINT AND LINESTRING
schema.vertexLabel('location').index('search').search().by('point').add()
schema.vertexLabel('lineLocation').index('search').search().by('line').add()
The example use the following data:
// Create a point
graph.addVertex(label,'location','name','Paris','point',Geo.point(2.352222, 48.856614))
graph.addVertex(label,'location','name','London','point',Geo.point(-0.127758,51.507351))
graph.addVertex(label,'location','name','Dublin','point',Geo.point(-6.26031, 53.349805))
graph.addVertex(label,'location','name','Aachen','point',Geo.point(6.083887, 50.775346))
graph.addVertex(label,'location','name','Tokyo','point',Geo.point(139.691706, 35.689487))

// Create a linestring
graph.addVertex(label, 'lineLocation', 'name', 'ParisLondon', 'line', "LINESTRING(2.352222 48.856614, -0.127758 51.507351)")
graph.addVertex(label, 'lineLocation', 'name', 'LondonDublin', 'line', "LINESTRING(-0.127758 51.507351, -6.26031 53.349805)")
graph.addVertex(label, 'lineLocation', 'name', 'ParisAachen', 'line', "LINESTRING(2.352222 48.856614, 6.083887 50.775346)")
graph.addVertex(label, 'lineLocation', 'name', 'AachenTokyo', 'line', "LINESTRING(6.083887 50.775346, 139.691706 35.689487)")

// Create a polygon
graph.addVertex(label, 'polyLocation','name', 'ParisLondonDublin', 'polygon',Geo.polygon(2.352222, 48.856614, -0.127758, 51.507351, -6.26031, 53.349805))
graph.addVertex(label, 'polyLocation','name', 'LondonDublinAachen', 'polygon',Geo.polygon(-0.127758, 51.507351, -6.26031, 53.349805, 6.083887, 50.775346))
graph.addVertex(label, 'polyLocation','name', 'DublinAachenTokyo', 'polygon',Geo.polygon(-6.26031, 53.349805, 6.083887, 50.775346, 139.691706, 35.689487))
The example data has the following approximate distances:
// PARIS TO LONDON: 3.08 DEGREES; 344 KM; 214 MI; 344,000 M
// PARIS TO AACHEN: 3.07 DEGREES; 343 KM; 213 MI; 343,000 M
// PARIS TO DUBLIN: 7.02 DEGREES; 781 KM; 485 MI; 781,000 M
// PARIS TO TOYKO: 86.3 DEGREES; 9713 KM; 6035 MI; 9,713,000 M

Find stored geospatial data that matches specified information

Find the stored data that matches a point mapped to the specified (longitude, latitude):
g.V().
has('location','point', Geo.point(2.352222, 48.856614)).
valueMap()
results in:
{name=[Paris], point=[POINT (2.352222 48.856614)]}
Find the stored data that matches a line mapped to the specified points:
 g.V().
has('lineLocation','line',Geo.lineString(2.352222, 48.856614, -0.127758, 51.507351)).
valueMap()
results in:
{line=[LINESTRING (2.352222 48.856614, -0.127758 51.507351)], name=[ParisLondon]}
Find the stored data that matches a polygon mapped to the specified points:
 g.V().
has('polyLocation', 'polygon',Geo.polygon(2.352222, 48.856614, -0.127758, 51.507351, -6.26031, 53.349805)).
valueMap()
results in:
{polygon=[POLYGON ((2.352222 48.856614, -0.127758 51.507351, -6.26031 53.349805, 2.352222 48.856614))], name=[ParisLondonDublin]}

Find stored geospatial points or linestrings within a specified radius from a specified point

These queries, as well as the queries that use a specified geospatial polygon use a method Geo.inside() that specifies a point, a radius, and the units to be used.

Several units are available with use of the Geo.inside() method:
DEGREES
Degrees of distance. One degree of latitude is approximately 111.2 kilometers, whereas one degree of longitude depends on the distance from the equator. At the equator, one degree of longitude equals 111.2 kilometers, but at 45 degrees of latitude, one degree of longitude is 78.6 kilometers. While the physical distance over a single degree of longitude changes with latitude, we calculate only great-circle distances in degrees.
KILOMETERS
Kilometers of distance.
MILES
Miles of distance.
METERS
Meters of distance.
Find all the cities (points) within a radius from a particular location (centerpoint):
g.V().
has('location', 'point', Geo.inside(Geo.point(2.352222, 48.856614), 4.2, Geo.Unit.DEGREES)).
values('name')
lists:
==>Paris
==>London
==>Aachen
Centering the query on Paris and searching within 4.2 degrees returns three cities: Paris, London, and Aachen from the dataset.
Find all the linestrings within a radius from a particular location (centerpoint):
g.V().
has('lineLocation', 'line', Geo.inside(Geo.point(2.352222, 48.856614), 9713, Geo.Unit.KILOMETERS)).
values('name')
lists:
==>ParisLondon
==>LondonDublin
==>AachenTokyo
==>ParisAachen
Centering the query on Paris and searching within 9713 kilometers returns four stored linestrings: Paris to London, London to Dublin, Aachen to Tokyo, and Paris to Aachen. Note that London to Dublin was not a stored linestring.

Find stored geospatial points or linestrings within a specified geospatial polygon

Polygons may be used in these queries with a search index on point.
Note: If a query is not backed by a search index, the results are consistent with geospatial coordinates, automatically defined using withGeoBounds(). This means that queries will return accurate results if the polygon crosses the international dateline. A query not backed by a search index will only use Cartesian coordinates if the underlying graph property keys are declared using withBounds().
Find all cities (points) within a specified geospatial polygon:
g.V().
has('location', 'point', Geo.inside(Geo.polygon(-6.26031, 53.349805, 6.083887, 50.775346, 139.691706, 35.689487))).
values('name')
lists:
==>Dublin
==>Aachen
==>Tokyo
This results is not surprising, since the three points used to create the polygon represent the three cities discovered.
Find all cities (points) within a specified geospatial polygon generated with a WKT tool:
g.V().
has('location', 'point', Geo.inside(Geo.polygon(-7.9541015625, 55.148273231753834,-9.6240234375, 51.47539580264131,1.0986328125, 50.86924482345238,0.5712890625, 53.29887631763788,-7.9541015625, 55.148273231753834))).
values('name')
lists:
==>London
==>Dublin
The polygon used encompasses most of the Republic of Ireland as well as the southern half of the United Kingdom, and finds London and Dublin within the polygon.
find linestrings within a polygon
g.V().
has('lineLocation', 'line', Geo.inside(Geo.polygon(-6.26031, 53.349805, 6.083887, 50.775346, 139.691706, 35.689487))).
values('name')
lists:
==>AachenTokyo
Since two of the points in the specified polygon represent Aachen and Tokyo, it is reassuring that the linestring of Aachen to Tokyo is found.

Schema and data

The examples here use the following schema:
//SCHEMA
// PROPERTY KEYS
// Check for previous creation of property key with ifNotExists()
schema.propertyKey('name').Text().ifNotExists().create()
schema.propertyKey('gender').Text().ifNotExists().create()
schema.propertyKey('location').Point().withGeoBounds().ifNotExists().create()
// VERTEX LABELS
schema.vertexLabel('author').properties('name','gender').ifNotExists().create()
schema.vertexLabel('place').properties('name','location').create()
// EDGE LABELS
schema.edgeLabel('livesIn').connection('author','place').ifNotExists().create()
// VERTEX INDEXES
// Secondary
schema.vertexLabel('author').index('byName').secondary().by('name').add()
// Search
schema.vertexLabel('author').index('search').search().by('name').asString().ifNotExists().add()
schema.vertexLabel('place').index('search').search().by('location')ifNotExists().add();
The examples use the following data:
//VERTICES
// AUTHOR VERTICES
juliaChild = graph.addVertex(label, 'author', 'name','Julia Child', 'gender', 'F')
simoneBeck = graph.addVertex(label, 'author', 'name', 'Simone Beck', 'gender', 'F')
louisetteBertholie = graph.addVertex(label, 'author', 'name', 'Louisette Bertholie', 'gender', 'F')
patriciaSimon = graph.addVertex(label, 'author', 'name', 'Patricia Simon', 'gender', 'F')
aliceWaters = graph.addVertex(label, 'author', 'name', 'Alice Waters', 'gender', 'F')
patriciaCurtan = graph.addVertex(label, 'author', 'name', 'Patricia Curtan', 'gender', 'F')
kelsieKerr = graph.addVertex(label, 'author', 'name', 'Kelsie Kerr', 'gender', 'F')
fritzStreiff = graph.addVertex(label, 'author', 'name', 'Fritz Streiff', 'gender', 'M')
emerilLagasse = graph.addVertex(label, 'author', 'name', 'Emeril Lagasse', 'gender', 'M')
jamesBeard = graph.addVertex(label, 'author', 'name', 'James Beard', 'gender', 'M')

// PLACE VERTICES
newYork = graph.addVertex(label, 'place', 'name', 'New York', 'location', Geo.point(74.0059,40.7128));
paris = graph.addVertex(label, 'place', 'name', 'Paris', 'location', Geo.point(2.3522, 48.8566));
newOrleans = graph.addVertex(label, 'place', 'name', 'New Orleans', 'location', Geo.point(90.0715, 29.9511));
losAngeles = graph.addVertex(label, 'place', 'name', 'Los Angeles', 'location', Geo.point(118.2437, 34.0522));
london = graph.addVertex(label, 'place', 'name', 'London', 'location', Geo.point(-0.1278, 51.5074));
chicago = graph.addVertex(label, 'place', 'name', 'Chicago', 'location', Geo.point(-87.6298, 41.8781136));
tokyo = graph.addVertex(label, 'place', 'name', 'Tokyo', 'location', Geo.point(139.6917, 35.6895));

// EDGES
juliaChild.addEdge('livesIn', newYork);
simoneBeck.addEdge('livesIn', paris);
louisetteBertholie.addEdge('livesIn', london);
patriciaSimon.addEdge('livesIn', newYork);
aliceWaters.addEdge('livesIn', losAngeles);
patriciaCurtan.addEdge('livesIn', chicago);
kelsieKerr.addEdge('livesIn', tokyo);
fritzStreiff.addEdge('livesIn', tokyo);
emerilLagasse.addEdge('livesIn', newOrleans);
jamesBeard.addEdge('livesIn', london);
Of course, this data can be loaded using the DSE Graph Loader as well, from CSV or other formatted files.

Find authors who live within a certain distance from a specified city in sorted order

First list the place names for all cities within the given radius (50 degrees) from New York (the approximate centerpoint listed:
g.V().
has('place', 'location', Geo.inside(Geo.point(74.0,40.5),50,Geo.Unit.DEGREES)).
values('name')
results in:
==>New York
==>Paris
==>New Orleans
==>Los Angeles
Now list the place names and authors who live in those cities for all cities within the given radius (50 degrees) from New York (the approximate centerpoint), sorted in alphabetical order:
// Order by name, not by distance from location point given
g.V().has('place', 'location', Geo.inside(Geo.point(74.0,40.5),50,Geo.Unit.DEGREES)).
order().by('name').
as('Location').
in().as('Author').
select('Location','Author').
by('name').
by('name')
finds:
==>{Location=Los Angeles, Author=Alice Waters}
==>{Location=New Orleans, Author=Emeril Lagasse}
==>{Location=New York, Author=Patricia Simon}
==>{Location=New York, Author=Julia Child}
==>{Location=Paris, Author=Simone Beck}
This query uses some additional methods such as order() and select() that are explained in Simple Traversals.
Now list the place names and authors who live in those cities for all cities within the given radius (50 degrees) from New York (the approximate centerpoint) , sorted by the distance from the centerpoint:
// Order by distance from NYC
g.V().has('place', 'location', Geo.inside(Geo.point(74.0,40.5),50,Geo.Unit.DEGREES)).
order().by{it.value('location').getOgcGeometry().distance(Geo.point(74.0059,40.7128).getOgcGeometry())}.
as('Location').
in().as('Author').
select('Location', 'Author').
by('name').
by('name')
==>{Location=New York, Author=Patricia Simon}
==>{Location=New York, Author=Julia Child}
==>{Location=New Orleans, Author=Emeril Lagasse}
==>{Location=Los Angeles, Author=Alice Waters}
==>{Location=Paris, Author=Simone Beck}
This query introduces some additional methods that must be imported in order for the query to succeed: getOgcGeometry() and distance(). Importing the library is accomplished in the original script using:
import com.esri.core.geometry.ogc.OGCGeometry;