Geo-query format specification in GraphQL

Consider for example there are airports, restaurants, person, city.

The location predicates are as follows
(airport → a.location[point])
(restaurant → r.location[point])
(person → home.location[point])
(person → office.location[point])
(city → area[polygon])

Say we want to find all the restaurants within 50 km from person Xs home:

query restaurantsNearby {
 me(_uid_:x) {
  restaurants @filter(near("home.location", "r.location", "50km")) {
   name, rating, type
  }
 }
}

If we want to find restaurants within a city Y:

query restaurantsInY {
 city(_uid_:y) {
  restaurant @filter(within("area", "r.location")) {
   name, rating, type
  }
 }
}

So to generalise the formats:

  • near :
  1. Using parents location predicate:
    @filter( near( < parent-location-predicate>, < child-location-predicate>, radius)
  2. Using a geoJson location:
    @filter( near(< geo-json point>, < child-location-predicate>, radius)
  • within:
  1. Using parents area:
    @filter( within(< parent-polygon-predicate>, < child-location-predicate>)
  2. Using a geoJson location:
    @filter( within(< geo-json polygon>, < child-location-predicate>)

Also if we want to start off with a location we could do something like this:

# query to get all airports within 100km of some point.
{
 me(_loc_) @filter( nearStart(< geo-json point>, "100km") && allof("type", "airport") {
  name
 }
}

- In case the root node has the type _loc_ we first refer to the geo index
and gather all the UIDs that are referred to by those index keys. Then we 
could apply other filters based on the nodes that we want.
[ note that nearStart and withinStart would be special filters that would get
all the points that would fall in a given geographical area]. Also in this case, 
the root node would no longer be a single UID.

I hope this gives a general idea of what I’m trying to convey. Suggestions and thoughts are welcome @minions.

2 Likes

Overall looks good. Will give a detailed reply tomorrow.

Couldn’t we start a query like so:

{
  near("co-ordinates", "r.location", "50km") {
    name, rating, type
  }
}

Similarly,

{
  near("lat,lng", "r.location", "50km") @filter(allOf("type", "airport")) {
    name, rating, type
  }

This also seems like something we should be able to do. Esssentially, near and within are both functions which just produce a list of uids, which can then be used in any way possible. Either for filtering, or to start a query. That sort of design would also keep things generic, and avoid having to build nearStart etc.

Is there anything in particular I should be looking at here?

When I first thought about this, I thought it would be good to make it look like a normal filter. This means you might have to first do a query to grab the location or area that you want. Then do a query like

query restaurantsNearby {
 me(_uid_:x) {
  restaurants @filter(near("r.location", "some point location...", "50km")) {
   name, rating, type
  }
 }
}

Like other filters, the first argument is the predicate being filtered. It is clearly not as powerful as what Manish has in mind. Just throwing it out there. Advantage is that the geo filter code will look very similar in structure to the current filter code.

I think the idea that we must start from a single entity, that’s not really necessary. I wrote it that way, because at the moment that’s the best we could do. But, near, within, and your string matching things like, allOf etc. are a lot stronger functions, that we can use to directly start off a query. They’d be run, then produce the first set of uids, which are then treated like the usual way.

So, what we get is these beautiful functions which can produce uids, which can be used as either filters or queries; and what changes is the currently hardcoded assertion that root must have just one uid. Root would now be able to have multiple uids.

I see. This is a more powerful kind of filter which links source to dest UIDs.

1 Like

Note that I’m thinking of them as functions which produce UIDs. And then they get used for either filtering or for querying or whatever. And as we progress, we can keep on adding more such functions, like say,

between("date.of.birth", 1960, 1962)
between("film.year_of_release", 2009,2010)

etc.

Sure. We can do away with the _loc_ thing and just use functoins which could populate UIDs from index in place of the _uid_ that we do currently, we just need a wraper name so that we could use it in the result. eg restaurant

{
  restaurant(near("co-ordinates", "r.location", "50km")) {
    name, rating, type
  }
}

Also, note that r.location doesn’t have a context here for the filter itself, we could say that we only want to retain the UIDs which have an r.location predicate and which are within 50km from the given coordinate [or alternately we could fetch all the UIDs which fall in the given buckets without the predicates (like r.location) and apply filtering on them].

For a start, I’d stick to the current method where we operate on destUIDs and filter them for different query types (near, within, etc.). We can move on to more powerful filters (say generators that populate UIDs) later.

Why is that? Is it harder to do, design-wise? If you do this filter approach now, and leave the generator approach for later, are you sure that you wouldn’t have to rewrite the filter approach code.

Think in terms of design – what design would give you the generators. And then backtrack from it, to figure out what you need to implement for filters.

I think design wise, the functions (say near, within etc. or even anyOf and allOf) would just return a list of UIDs. Currently we have a @filter directive and if the function goes inside the @filter directive it’d act as a filter (on destUIDs and trim the list by intersecting). In future, we could have @generate which would append new UIDs generated from the index to the destUIDs.

Doest this sound right? @jchiu

One possibility might be this:

near(r.location, somePointLocation, "50km") {
  ...
}

which would essentially go to the index for “r.location”, and return the appropriate UIDs. The syntax could also be @generate(near(...)) instead of just near(...). This would be the root of the query.

I think the @generate syntax seems like a workaround some implementation issue. We should be able to directly call near() at the root of the query, just like we say, debug, or me.

In fact, I’d say, let’s reverse the order of implementation. What if we implement the ability to have these as root first, and then worry about using them in filtering? That way, we can be sure that the general design of these functions work, and we’re not doing special casing for filters.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.