Motivation
Supporting Geolocation features natively in GraphQL so that users of the GraphQL API can benefit from them.
User Impact
Users can directly use these features without having to define their own custom types and using custom queries.
Implementation
https://dgraph.io/docs/query-language/#geolocation shows that Dgraph supports storing Point, Polygon, MultiPolygon geo types. We need to be able to support near
, within
, contains
and intersects
query filter. We can have custom scalars for these within our predefined GraphQL schema like below.
Custom scalars
Inside GraphQL±, a predicate which has geo
type can store data for a Point
, Polygon
or MultiPolygon
but since GraphQL is strongly typed, a field inside a type will be able to only store one type of data.
type Point {
Latitude: Float!
Longitude: Float!
}
type PointList {
Points: [Point!]!
}
type Polygon {
Coordinates: [PointList!]!
}
type MultiPolygon {
Coordinates: [Polygon!]!
}
An example of using custom scalars
Say, we have a hotel type with has a location and area, something like below.
type Hotel {
id: ID!
name: String!
location: Point
// TODO - See if MultiPolygon has different behaviour from Polygon.
area: Polygon
}
In the Dgraph schema that is generated, we would automatically add an index to these fields like below.
Hotel.location: geo @index(geo) .
Hotel.area: geo @index(geo) .
Mutations
For this AddHotelInput
and HotelPatch
would contain location
and area
.
input AddHotelInput {
name: String!
location: Point
area: Polygon
}
input AuthorPatch {
name: String!
location: Point
area: Polygon
}
Queries
We would generate the Geo queries as part of the Hotel filter so that it can be combined with other filters.
input NearFilter {
Distance: Float!
Coordinate: Point!
}
input WithinFilter {
# TODO - Verify if we also allow searching with a MultiPolygon, the docs are not very clear about this.
polygon: Polygon!
}
input ContainsFilter {
# The user should be giving one of these.
# TODO - Verify if this can also accept MultiPolygon as an input.
point: Point
polygon: Polygon
}
input IntersectsFilter {
# The user should be giving one of these.
polygon: Polygon
mulitPolygon: MultiPolygon
}
input PointGeoFilter {
near: NearFilter
within: WithinFilter
}
input PolygonGeoFilter {
near: NearFilter
within: WithinFilter
contains: ContainsFilter
intersects: IntersectsFilter
}
input HotelFilter {
location: PointGeoFilter
area: PolygonGeoFilter
and: HotelFilter
or: HotelFilter
not: HotelFilter
}
near
GraphQL± Definition - Matches all entities where the location given by predicate
is within distance
meters of geojson coordinate [long, lat]
.
graphql+-
{
tourist(func: near(loc, [-122.469829, 37.771935], 1000) ) {
name
}
}
graphql
queryHotel(filter: {
location: {
near: {
coordinate: {
latitute: 37.771935,
longitude: -122.469829
},
distance: 1000
}
}
}) {
name
}
Note, the near query can be used for returning Point as well as Polygon, MultiPolygon that are near a point. So if the user used area
instead of location
, it should return Polygon’s near the given point.
within
GraphQL± Definition - Matches all entities where the location given by predicate
lies within the polygon specified by the geojson coordinate array.
graphql+-
{
tourist(func: within(loc, [[[....]]] )) {
name
}
}
graphql
queryHotel(filter: {
location: {
within: {
polygon: {
coordinates: [[[....]]],
}
}
}
}) {
name
}
The within query would allow searching for all geo entities (point, polygon) within a polygon. So they would be generated for all types.
// TODO(pawan) - Verify if we support within queries for searching in a multipolygon.
contains
GraphQL± Definition - Matches all entities where the polygon describing the location given by predicate
contains geojson coordinate [long, lat]
or given geojson polygon.
ContainsFilter
would only be generated for Polygon/MultiPolygon.
graphql+-
{
tourist(func: contains(loc, [ -122.50326097011566, 37.73353615592843 ] )) {
name
}
}
graphql
queryHotel(filter: {
area: {
contains: {
point: {
coordinates: [],
}
}
}
}) {
name
}
intersects
GraphQL± Definition - Matches all entities where the polygon describing the location given by predicate
intersects the given geojson polygon.
IntersectsFilter
would only be generated for Polygon/MultiPolygon.
graphql+-
{
tourist(func: intersects(loc, [[[...]]] )) {
name
}
}
graphql
queryHotel(filter: {
area: {
intersects: {
polygon: {
coordinates: [[[...]]],
}
}
}
}) {
name
}
Needs further research
- Understanding of which functions are supported for MultiPolygon type. The docs don’t shed much light on this and we would have to dig deeper into the code to find this out.
References
- To understand what Point, Polygon and MultiPolygon mean. GeoJSON draft version 6 - GeoJSON
- https://dgraph.io/docs/query-language/#geolocation