Geospatial traversals
Geospatial queries are used to discover geospatial information.
All geospatial points can be searched for specified values using inside()
with a circle radius value of zero.
All points or linestrings can be searched within a circle or polygon using inside()
and a specified radius.
All points must be specified in |
Distance calculations are crucial to proper results. DSE Search indexes can be created for geospatial data in DataStax Graph (DSG), 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.
Schema and data
The examples here use the following schema:
schema.vertexLabel('location').
partitionBy('name', Text).
property('geo_point', Point).
create()
schema.vertexLabel('lineLocation').
partitionBy('name', Text).
property('geo_line', LineString).
create()
schema.vertexLabel('polyLocation').
partitionBy('name', Text).
property('geo_polygon', Polygon).
create()
schema.vertexLabel('lineLocation').
searchIndex().
ifNotExists().
by('name').asString().
by('geo_line').
create()
schema.vertexLabel('location').
searchIndex().
ifNotExists().
by('name').asString().
by('geo_point').
create()
schema.vertexLabel('polyLocation').
searchIndex().
ifNotExists().
by('name').asString().
by('geo_polygon').
create()
The example use the following data:
g.addV('location').
property('name', 'Paris').
property('geo_point','POINT (2.352222 48.856614)' as Point)
g.addV('location').
property('name', 'London').
property('geo_point','POINT (-0.127758 51.507351)' as Point)
g.addV('location').
property('name', 'Dublin').
property('geo_point','POINT (-6.26031 53.349805)' as Point)
g.addV('location').
property('name', 'Aachen').
property('geo_point','POINT (6.083887 50.775346)' as Point)
g.addV('location').
property('name', 'Tokyo').
property('geo_point','POINT (139.691706 35.689487)' as Point)
g.addV('lineLocation').
property('name', 'ParisLondon').
property('geo_line', 'LINESTRING(2.352222 48.856614, -0.127758 51.507351)' as LineString)
g.addV('lineLocation').
property('name', 'LondonDublin').
property('geo_line', 'LINESTRING(-0.127758 51.507351, -6.26031 53.349805)' as LineString)
g.addV('lineLocation').
property('name', 'ParisAachen').
property('geo_line', 'LINESTRING(2.352222 48.856614, 6.083887 50.775346)' as LineString)
g.addV('lineLocation').
property('name', 'AachenTokyo').
property('geo_line', 'LINESTRING(6.083887 50.775346, 139.691706 35.689487)' as LineString)
g.addV('polyLocation').
property('name', 'ParisLondonDublin').
property('geo_polygon', 'POLYGON ((2.352222 48.856614, -0.127758 51.507351, -6.26031 53.349805))' as Polygon)
g.addV('polyLocation').
property('name', 'LondonDublinAachen').
property('geo_polygon', 'POLYGON ((-0.127758 51.507351, -6.26031 53.349805, 6.083887 50.775346))' as Polygon)
g.addV('polyLocation').
property('name', 'DublinAachenTokyo').
property('geo_polygon', 'POLYGON ((-6.26031 53.349805, 6.083887 50.775346, 139.691706 35.689487))' as Polygon)
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 points that match specified information
All geospatial queries must use the inside()
step.
To find the stored data that matches a point mapped to the specified (longitude, latitude), set the radius value to zero:
g.V().hasLabel('location').
has('geo_point',Geo.inside(Geo.point(2.352222, 48.856614), 0, Geo.Unit.MILES)).
elementMap()
results in:
g.V().hasLabel('location').
has('geo_point',Geo.inside(Geo.point(2.352222, 48.856614), 0, Geo.Unit.MILES)).
elementMap()
Find stored geospatial points, linestrings, or polygons 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().hasLabel('location').
has('geo_point',Geo.inside(Geo.point(2.352222, 48.856614), 0, Geo.Unit.MILES)).
elementMap()
lists:
==>Paris
==>London
==>Aachen
Centering the query on Paris and searching within 4.2 degree radius 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', 'geo_line', Geo.inside(Geo.point(2.352222, 48.856614), 9713, Geo.Unit.KILOMETERS)).
values('name')
lists:
==>ParisLondon
==>ParisAachen
==>LondonDublin
==>AachenTokyo
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.
Find all the polygons within a radius from a particular location (centerpoint):
g.V().
has('polyLocation', 'geo_polygon', Geo.inside(Geo.point(2.352222, 48.856614), 138, Geo.Unit.DEGREES)).
values('name')
lists:
==>ParisLondonDublin
==>DublinAachenTokyo
==>LondonDublinAachen
Centering the query on Paris and searching within a 138 degree radius returns four stored polygons: Paris-London-Dublin, Dublin-Aachen-Tokyo, and London-Dublin-Aachen.
Find people in cities
One additional schema is required for finding where people live, an edge between person
and location
, as well as an inverse()
index:
schema.edgeLabel('lives_in').
ifNotExists().
from('person').to('location').
create()
schema.edgeLabel('lives_in').
from('person').
to('location').
materializedView('person__lives_in__location_by_location_name').
ifNotExists().
inverse().
create()
Some data for the edges must also be added:
// tag::AllPersonLivesInLocation[]
// person to location edges
g.V('dseg:/person/01e22ca6-da10-4cf7-8903-9b7e30c25805').as('a').
V('dseg:/location/London').as('b').
addE('lives_in').from('a').to('b')
g.V('dseg:/person/adb8744c-d015-4d78-918a-d7f062c59e8f').as('a').
V('dseg:/location/Paris').as('b').
addE('lives_in').from('a').to('b')
g.V('dseg:/person/f092107c-0c5c-47e7-917c-82c7fc2a2493').as('a').
V('dseg:/location/Paris').as('b').
addE('lives_in').from('a').to('b')
g.V('dseg:/person/4ce9caf1-25b8-468e-a983-69bad20c017a').as('a').
V('dseg:/location/Dublin').as('b').
addE('lives_in').from('a').to('b')
g.V('dseg:/person/888ad970-0efc-4e2c-b234-b6a71c30efb5').as('a').
V('dseg:/location/Aachen').as('b').
addE('lives_in').from('a').to('b')
Of course, this data can be loaded using the DataStax Bulk Loader as well, from CSV or other formatted files.
First list the place names for all cities within the given radius (50 kilometers) from Paris:
g.V().has('location', 'geo_point',Geo.inside(Geo.point(2.352222,48.856614),50,Geo.Unit.KILOMETERS))
results in:
==>Paris
Let’s find all the authors within the given radius (50 kilometers) from Paris, ordered by the authors name:
g.V().has('location', 'geo_point', Geo.inside(Geo.point(2.352222,48.856614),50,Geo.Unit.KILOMETERS)).
order().
by('name').as('Location').
in('lives_in').as('Author').
select('Location','Author').
by('name').
by('name')
results in:
==>{Location=Paris, Author=Louisette BERTHOLIE}
==>{Location=Paris, Author=Simone BECK}
This query uses some additional methods such as order()
and select()
that are explained in Simple Traversals.
If we expand the radius to 400 kilometers from Paris, we can find authors in other cities:
g.V().has('location', 'geo_point', Geo.inside(Geo.point(2.352222,48.856614),400,Geo.Unit.KILOMETERS)).
order().
by('name').as('Location').
in('lives_in').as('Author').
select('Location','Author').
by('name').
by('name')
results in:
==>{Location=Paris, Author=Louisette BERTHOLIE}
==>{Location=Aachen, Author=Fritz STREIFF}
==>{Location=Paris, Author=Simone BECK}
==>{Location=London, Author=Kelsie KERR}
Find celery at the store
You are a mathematics teacher writing a simple geospatial problem for your students. They are great fans of ants on a log, a snack made with celery, cream cheese, and raisins. So, you decide to help them find the nearest stores to their house which have celery in stock.
Mary is the student whose home we’ll use as the starting point. Just like finding people in cities, we can use Mary’s house as the center of the circle we’ll explore to find stores with celery within 5 miles of her house:
g.V().hasLabel('store').
as('Store').
out('is_located_at').
has('geo_point', Geo.inside(Geo.point(37.8, -122.25), 5, Geo.Unit.MILES)).
in().out().
has('name','celery').
as('Ingred').
select('Store', 'Ingred').
by('name').
by('name')
results in:
==>{Store=Zippy Mart, Ingred=celery}
==>{Store=Mamma's Grocery, Ingred=celery}
Mary has a choice of two markets to visit!
You could expand this query to find the stores that have either beef or celery, if Mary is planning to make Beef Bourguignon:
g.V().hasLabel('store').
as('Store').
out('is_located_at').
has('geo_point', Geo.inside(Geo.point(37.8, -122.25), 5, Geo.Unit.MILES)).
in().out().
has('name',within('celery','beef')).
as('Ingred').
select('Store', 'Ingred').
by('name').
by('name')
results in:
g.V().hasLabel('store').
as('Store').
out('is_located_at').
has('geo_point', Geo.inside(Geo.point(37.8, -122.25), 5, Geo.Unit.MILES)).
in().out().
has('name',within('celery','beef')).
as('Ingred').
select('Store', 'Ingred').
by('name').
by('name')
Looks like Mary is headed to Zippy Mart where she can buy both of her ingredients.