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;