Part 5 - Geospatial queries
Following the previous part in our Spring Data demo, where we had a look at repository methods with self written AQL, we will now take a look at Geospatial queries.
Geospatial queries are a subsection of derived queries. To use a Geospatial query on a collection, a geo index must exist on that collection. A geo index can be created on a field which is a two element array, corresponding to latitude and longitude coordinates.
As a subsection of derived queries, Geospatial queries support the same return types, and also these additional three return types: GeoPage
, GeoResult
and Georesults
. These types must be used in order to get the distance of each document as generated by the query.
Geo data modeling
To demonstrate Geospatial queries we create a new entity class Location
with a field location
of type double[]
. We also have to create a geo index on this field. We can do so by annotating the field with @GeoIndexed
. As you probably remember we have already used an index in our Character
class but we annotated the type and not the affected fields. Spring Data ArangoDB offers two ways of defining an index. With @<IndexType>Indexed
annotations indexes for single fields can be defined. If the index should include multiple fields the @<IndexType>Index
annotations can be used on the type instead. Take a look here for more informations.
Create a new class Location
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package com.arangodb.spring.demo.entity; import java.util.Arrays; import org.springframework.data.annotation.Id; import com.arangodb.springframework.annotation.Document; import com.arangodb.springframework.annotation.GeoIndexed; @Document("locations") public class Location { @Id private String id; private final String name; @GeoIndexed private final double[] location; public Location(final String name, final double[] location) { super(); this.name = name; this.location = location; } // getter & setter @Override public String toString() { return "Location [id=" + id + ", name=" + name + ", location=" + Arrays.toString(location) + "]"; } } |
and the corresponding repository LocationRepository
:
1 2 3 4 5 6 7 8 9 10 |
package com.arangodb.spring.demo.repository; import com.arangodb.spring.demo.entity.Location; import com.arangodb.springframework.repository.ArangoRepository; public interface LocationRepository extends ArangoRepository<Location> { } |
After that we create a new CommandLineRunner
, add it to our DemoApplication
and perform some insert operations with some popular locations from Game of Thrones with the coordinates of their real counterparts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.arangodb.spring.demo.runner; import java.util.Arrays; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import com.arangodb.spring.demo.entity.Location; import com.arangodb.spring.demo.repository.LocationRepository; public class GeospatialRunner implements CommandLineRunner { @Autowired private LocationRepository repository; @Override public void run(final String... args) throws Exception { System.out.println("# Geospatial"); repository.save(Arrays.asList(new Location("Dragonstone", new double[] { 55.167801, -6.815096 }), new Location("King's Landing", new double[] { 42.639752, 18.110189 }), new Location("The Red Keep", new double[] { 35.896447, 14.446442 }), new Location("Yunkai", new double[] { 31.046642, -7.129532 }), new Location("Astapor", new double[] { 31.50974, -9.774249 }), new Location("Winterfell", new double[] { 54.368321, -5.581312 }), new Location("Vaes Dothrak", new double[] { 54.16776, -6.096125 }), new Location("Beyond the wall", new double[] { 64.265473, -21.094093 }))); } } |
DemoApplication
:
1 2 3 4 |
Object[] runner = new Object[] { CrudRunner.class, ByExampleRunner.class, DerivedQueryRunner.class, RelationsRunner.class, AQLRunner.class, GeospatialRunner.class }; |
There are two kinds of Geospatial query, Near and Within.
Near
Near
sorts entities by distance from a given point. The result can be restricted with paging.
LocationRepository
:
1 |
GeoPage<Location> findByLocationNear(Point location, Pageable pageable); |
In this example we search for locations sorted by distance to a given point which match the coordinates of Winterfell. We use pagination to split the results in pages of five locations.
1 2 3 4 5 6 7 8 9 10 11 |
System.out.println("## Find the first 5 locations near 'Winterfell'"); GeoPage<Location> first5 = repository.findByLocationNear(new Point(-5.581312, 54.368321), new PageRequest(0, 5)); first5.forEach(System.out::println); System.out.println("## Find the next 5 locations near 'Winterfell' (only 3 locations left)"); GeoPage<Location> next5 = repository.findByLocationNear(new Point(-5.581312, 54.368321), new PageRequest(1, 5)); next5.forEach(System.out::println); |
Because we used the coordinates of Winterfell the distance in the output of Winterfell is 0
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Geospatial ## Find the first 5 locations near 'Winterfell' GeoResult [content: Location [id=locations/285727, name=Winterfell, location=[54.368321, -5.581312]], distance: 0.0 KILOMETERS, ] GeoResult [content: Location [id=locations/285728, name=Vaes Dothrak, location=[54.16776, -6.096125]], distance: 40.186277071066 KILOMETERS, ] GeoResult [content: Location [id=locations/285722, name=Dragonstone, location=[55.167801, -6.815096]], distance: 119.01974460184371 KILOMETERS, ] GeoResult [content: Location [id=locations/285729, name=Beyond the wall, location=[64.265473, -21.094093]], distance: 1401.4591486992315 KILOMETERS, ] GeoResult [content: Location [id=locations/285723, name=King's Landing, location=[42.639752, 18.110189]], distance: 2161.3753700194766 KILOMETERS, ] ## Find the next 5 locations near 'Winterfell' (only 3 locations left) GeoResult [content: Location [id=locations/285726, name=Astapor, location=[31.50974, -9.774249]], distance: 2563.472767382198 KILOMETERS, ] GeoResult [content: Location [id=locations/285724, name=The Red Keep, location=[35.896447, 14.446442]], distance: 2566.673647872126 KILOMETERS, ] GeoResult [content: Location [id=locations/285725, name=Yunkai, location=[31.046642, -7.129532]], distance: 2596.183041005288 KILOMETERS, ] |
Within
Within
both sorts and filters entities, returning those within the given distance, range or shape.
Lets add some methods to LocationRepository
which use different filter criteria.
1 2 3 4 5 |
GeoResults<Location> findByLocationWithin(Point location, Distance distance); Iterable<Location> findByLocationWithin(Point location, Range<Double> distanceRange); |
With these methods we can search for locations within a given distance or range to our point – Winterfell.
1 2 3 4 5 6 7 8 9 10 11 |
System.out.println("## Find all locations within 50 kilometers of 'Winterfell'"); GeoResults<Location> findWithing50kilometers = repository .findByLocationWithin(new Point(-5.581312, 54.368321), new Distance(50, Metrics.KILOMETERS)); findWithing50kilometers.forEach(System.out::println); System.out.println("## Find all locations which are 40 to 50 kilometers away from 'Winterfell'"); Iterable<Location> findByLocationWithin = repository.findByLocationWithin(new Point(-5.581312, 54.368321), new Range<>(40000., 50000.)); findByLocationWithin.forEach(System.out::println); |
As you can see in our console output, both ‘Winterfell’ and ‘Vaes Dothrak’ are located within a 50 kilometers radius around our point. But only ‘Vaes Dothrak’ is obviously more than 40 kilometers away from it.
1 2 3 4 5 6 7 |
## Find all locations within 50 kilometers of 'Winterfell' GeoResult [content: Location [id=locations/285727, name=Winterfell, location=[54.368321, -5.581312]], distance: 0.0 KILOMETERS, ] GeoResult [content: Location [id=locations/285728, name=Vaes Dothrak, location=[54.16776, -6.096125]], distance: 40.186277071066 KILOMETERS, ] ## Find all locations which are 40 to 50 kilometers away from 'Winterfell' Location [id=locations/285728, name=Vaes Dothrak, location=[54.16776, -6.096125]] |
But we can not only implement geo functions going from a single point, but it is also possible to search for locations within a polygon.
In our last example we add a method using Polygon
.
LocationRepository
:
1 |
Iterable<Location> findByLocationWithin(Polygon polygon); |
GeospatialRunner
:
1 2 3 4 |
System.out.println("## Find all locations within a given polygon"); Iterable<Location> withinPolygon = repository.findByLocationWithin( new Polygon(Arrays.asList(new Point(-25, 40), new Point(-25, 70), new Point(25, 70)))); withinPolygon.forEach(System.out::println); |
The console output should be:
1 2 3 4 5 6 7 |
## Find all locations within a given polygon Location [id=locations/285728, name=Vaes Dothrak, location=[54.16776, -6.096125]] Location [id=locations/285727, name=Winterfell, location=[54.368321, -5.581312]] Location [id=locations/285722, name=Dragonstone, location=[55.167801, -6.815096]] Location [id=locations/285729, name=Beyond the wall, location=[64.265473, -21.094093]] |
That’s it! You should now have an overview of the possibilities with Spring Data ArangoDB.