Skip to content

[Spec] Spatial Filters #1747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
steveoh opened this issue Apr 22, 2020 · 13 comments
Closed

[Spec] Spatial Filters #1747

steveoh opened this issue Apr 22, 2020 · 13 comments
Assignees
Milestone

Comments

@steveoh
Copy link
Contributor

steveoh commented Apr 22, 2020

Spatial Filters

Introduction

Create a set of spatial filters based on a subset of OGC spatial functions.

Proposal

Example Objects

All examples will take place in the following domain:

public class Foo {
     public MultiPolygon shape { get; set; }
     public string bas { get; set; }
}

Proposal

Words

Suggested words in the following examples like shape_contains, shape_crosses, shape_equals, shape_intersects, shape_overlaps, shape_touches, shape_within will be a part of the NamingConvention and can be customized by the user.

For line and polygon geometries, shape_length and shape_area will generate the same filters as the scalar int type.

Spatial filters in HotChocolate will look like the following example:

query foos($point: geometry) {
  foos(where: {shape_contains: $point }}) {
    bas
  }
}

Since points and lines have a hard time intersecting with other points, a method to buffer them into polygons would be very useful for filtering points or lines that are near other points. e.g. an address point in relation to covid testing centers (points) or address points along a road (line).

query foos($point: geometry) {
  foos(where: {shape_contains: $point, buffer: 10, unit: "meters" }}) {
    bas
  }
}

The input geometry should allow for the GeoJSON spec and optionally allow for other geometry types like the esri spec to be extended into the system. With custom middleware users could use an address or a route and mile post or a place name to resolve a geometry from an external system. These results will need to be cached or persisted since they can be used multiple times in a single query.

Prior GeoJSON art

Data is stored in different projections to have the least amount of distortion when making a round world flat. If the input geometry coordinates are stored in a different projection than the underlying data, a hook to reproject the data will be required. This could be resolved via a web service, code, or via a database function if supported.

How others did it

hasura

query geom_table($polygon: geometry){
  geom_table(
    where: {geom_col: {_st_within: $polygon}}
  ){
    id
    geom_col
  }
}
query {
  search_landmarks_near_user(
    args: {userid: 3, distance_kms: 20}
  ){
    user_id
    location
    nearby_landmarks
  }
}

sample output.

The output location serialized as geojson
The landmark location appears to be well known binary.

{
  "data": {
    "search_landmarks_near_user": [
      {
        "user_id": 3,
        "location": {
          "type": "Point",
          "crs": {
            "type": "name",
            "properties": {
              "name": "urn:ogc:def:crs:EPSG::4326"
            }
          },
          "coordinates": [
            12.9406589,
            77.6185572
          ]
        },
        "nearby_landmarks": [
          {
            "id": 3,
            "name": "blue tokai",
            "type": "coffee shop",
            "location": "0101000020E61000004E74A785DCF22940BE44060399665340"
          }
        ]
      }
    ]
  }
}

graphqljs neo4j

query {
  Business(location: { latitude: 46.870035, longitude: -113.990976 }) {
    name
    location {
      latitude
      longitude
    }
  }
}

with a buffer

{
  Business(
    filter: {
      location_distance_lt: {
        point: { latitude: 46.859924, longitude: -113.985402 }
        distance: 1500
      }
    }
  ) {
    name
    location {
      latitude
      longitude
    }
  }
}

Displaying Spatial Data

Introduction

Create a way to display a geometry when requested as a scalar type.

Proposal

Create a a scalar type that can represent the geometry as a javascript serialized object e.g. GeoJSON, string, or binary.

Words

Suggested words in the following examples like area, length, centroid, envelope, wkt, wkb, and all will be a part of the NamingConvention and can be customized by the user.

A centroid will return the center of the geometry. The envelope will return the minimum bounding box around the geometry. wkt will return the well known text representation, wkb will return the well known binary as a byte[], and all will return the full shape as geojson. Area returns the ST_Area. Length returns the ST_Length. Every word used here will return the representation as geojson unless otherwise specified or where not applicable e.g. wkt, wkb.

The output serialization can be modified by using arguments.

{
  foos() {
    shape(format: GEOJSON, ESRIJSON, CUSTOM)
    {
       centroid,
       area,
       length
    }
  }
}

The output coordinate system can also be supplied as an argument using the well known id.

{
  foos() {
    shape(wkid: 3857, 4326)
    {
       envelope
    }
  }
}
@michaelstaib
Copy link
Member

#1650

@PascalSenn
Copy link
Member

PascalSenn commented Apr 22, 2020

From Slack Channel:
This could lead to issues. GEOJSOn is structurally different from esrijson or "custom"

{
  foos() {
    shape(format: GEOJSON, ESRIJSON, CUSTOM)
    {
       centroid,
       area,
       length
    }
  }
}

This would be possible with interfaces implements interfaces

{
  foos {
    shape
    {
       ... on GeoJSON {
         centroid
         area
         length
       }
       ... on Point {
         lat
         long
       }
    }
  }
}

@PascalSenn
Copy link
Member

I also think this would limit us:

query foos($point: geometry) {
  foos(where: {shape_contains: $point, buffer: 10, unit: "meters" }}) {
    bas
  }
}

basically the input type must be smth like a input union
I am not sure what we want to provide as an input

Also, what is implied by this:
{shape_contains: $point, buffer: 10, unit: "meters" }
Hasura did this quite nicely i think

query geom_table($point: geometry){
  geom_table(
    where: {geom_col: {_st_d_within: {distance: 3, from: $point}}}
  ){
    id
    geom_col
  }
}

@PascalSenn
Copy link
Member

@steveoh @John0x Can you guys type out a few example SQL queries?
From easy to complex
i.E.

  • A is at least 3 meters away from B
  • The distance between A and B is lower than 7 miles
  • A is withing B
    etc.

I think it would make sense to then translate them into the graphql and see what we want to support.
This would also enable use to compare it with other implementations

@steveoh
Copy link
Contributor Author

steveoh commented Apr 22, 2020

This could lead to issues. GEOJSOn is structurally different from esrijson or "custom"

{
  foos() {
    shape(format: GEOJSON, ESRIJSON, CUSTOM)
    {
       centroid,
       area,
       length
    }
  }
}

It should be pointed out that format would be a singular choice and not an array.

This would be similar to arguments from https://graphql.org/learn/queries/#arguments

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}

or from a hasura schema,

type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}

or from this dgraph discussion

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

@steveoh
Copy link
Contributor Author

steveoh commented Apr 22, 2020

Also, what is implied by this:
{shape_contains: $point, buffer: 10, unit: "meters" }

This implies that given a $point

  • create a circle from the input point with a 10 meter radius as a new polygon
  • select all the bas that the new polygon contains from the foos shape field

It is the same as

location_distance_lt: {
        point: { latitude: 46.859924, longitude: -113.985402 }
        distance: 1500

but the unit is implied and documented as meters and buffer is a more technical term than distance.

@steveoh
Copy link
Contributor Author

steveoh commented Apr 22, 2020

  • A is at least 3 meters away from B

SQL

select name from society.cemeteries c 
where ST_Distance(c.shape, ST_SetSRID(ST_Point(430178,4506818), 26912)) >= 3

Possible Implementation 1

{
   cemeteries (where: { distance_gte: { location: {
        "type": "Point",
        "coordinates": [430178, 4506818],
        "crs": {
        "type": "name",
        "properties": {
          "name": "urn:ogc:def:crs:EPSG::26912"
          }
        }
      }, distance: 3 }) {
     name
   }
   cemeteries (where: { distance_gt: { location: { this could be anything that can resolve to a geometry; an address, milepost, place name, etc }, distance: 3}) {
     name
   }
}

This would necessitate creating all of the number type filters.

  • The distance between A and B is lower than 7 miles

SQL

caveat: need hook to convert input between units. 1 mile = 1609.344 meters

select name from society.cemeteries c 
where ST_Distance(c.shape, ST_SetSRID(ST_Point(430178,4506818), 26912)) < (7 * 1609.344)

Possible Implementation 1

{
  cemeteries(where: { distance_lt: { location: {}, distance: 7, unit: MILES }}) {
	name
  }
}
  • A is within B

SQL

select count(*) from location.address_points ap 
where ST_Intersects(ap.shape, ST_Buffer(ST_SetSRID(ST_Point(430178,4506818), 26912), 3))

select count(*) from location.address_points ap 
where ST_Within(ap.shape, ST_Buffer(ST_SetSRID(ST_Point(430178,4506818), 26912), 3))

select count(*) from location.address_points ap 
where ST_Contains(ap.shape, ST_Buffer(ST_SetSRID(ST_Point(430178,4506818), 26912), 3))

Possible Implementation 1

{
  address_points(where: { intersects: { location: {}, distance: 3, unit: METERS }}) {
	name
  }
  address_points(where: { within: { location: {} }) {
    name
  }
  address_points(where: { contains: { location: {} }) {
    name
  }
}

etc.

point in polygon query: What city am I in

SQL

select name from boundaries.municipal_boundaries city 
where ST_Intersects(city.shape, ST_SetSRID(ST_Point(430178,4506818), 26912))

Possible Implementation 1

{
  cities(where: { intersects: { location: {} }) {
    name
  }
}

area query: find all cities less than 1 sq mile in size

SQL

select name from boundaries.municipal_boundaries city 
where ST_Area(city.shape) < 2589988

Possible Implementation 1

{
  cities(where: { area_gt: { value: 1, unit: MILE) {
    name
  }
}

length query: what earthquake faults are longer than a mile

SQL

select * from geoscience.quaternary_faults faults
where ST_Length(faults.shape) > 1610

Possible Implementation 1

{
  faults(where: { length_gt: { value: 1, unit: MILE ) {
    name
  }
}

closest to me: fire stations

SQL

select * from society.fire_stations fs2 
order by ST_Distance(shape, ST_SetSRID(ST_Point(430178,4506818), 26912))
limit 1

Possible Implementation 1

{
  fire_stations(first: 1, order_by: resulting distance, this seems to currently only allow field names?, where: { distance: { location: {} ) {
    nodes {
	  name
    }
  }
}

I think it would make sense to then translate them into the graphql and see what we want to support.

This would also enable use to compare it with other implementations

@michaelstaib
Copy link
Member

It starts to look good @steveoh.

I will go through all of this on the weekend. Are at the moment busy with the ndc conference.

@steveoh
Copy link
Contributor Author

steveoh commented Apr 27, 2020

I look forward to your comments @michaelstaib

@PascalSenn
Copy link
Member

PascalSenn commented Apr 29, 2020

so here are my 5 cents.

I played around a little. I think the way spatial filters work can be divided into four subcategories.

  1. Array, Scalar, Object Filters
  2. Filters that need GeoJsonInput
  3. Complex Filters with Custom Types
  4. (Out of scoped)

1. Array, Scalar, Object Filters

These filters are pretty straight forward. Many of them should already work today. They might need a method call, this would not be possible today. It is not complicated to build the expression for it. Array filters and some string filters already do method calls. Not a big deal.

Example

// Scalar
public virtual double Area { get; }
// Object
public abstract Geometry Boundary { get; }
// Array
public abstract Coordinate[] Coordinates { get; }
// Method call
public Geometry Normalized()

2. Filters that need GeoJsonInput

These filters require GeoJsonInput. They are slightly more complex. Where it is probably not gonna be too much of a challenge to find a good way to return these values, it is hard to use them as input types.
Without the input union spec, we will be very limited in input types of GeoJsonData.
We will have to look into how we handle this. We may not want to use the GeoJsonSpec: RFC7947

We will also add the buffer on the GeoJsonInput input type. This way every input that accepts a geometry can be buffered.
A buffer might as well not be just a distance, but a few properties more

public Geometry Buffer(double distance, int quadrantSegments, EndCapStyle endCapStyle)

Example

public bool Disjoint(Geometry g);
public bool Covers(Geometry g);
public bool CoveredBy(Geometry g);

3. Complex Filters with Custom Types

These filter require dedicated input types. They usually need a GeoJsonInput and return a Geometry. This way they can be chained further. As GraphQL does not support "paramters" in input objects.

Example

public Geometry Difference(Geometry other);
public Geometry Intersection(Geometry other);
public Geometry SymmetricDifference(Geometry other);

4. (Out of scoped)

There are a few methods that return types that are just to complex to implement for now. The scope of the "basic" features is already massive.


Database Support Overview

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Area
Geometry.AsBinary()
Geometry.AsText()
Geometry.Boundary
Geometry.Buffer(double)
Geometry.Buffer(double, int)
Geometry.Centroid
Geometry.Contains(Geometry)
Geometry.ConvexHull()
Geometry.CoveredBy(Geometry)
Geometry.Covers(Geometry)
Geometry.Crosses(Geometry)
Geometry.Difference(Geometry)
Geometry.Dimension
Geometry.Disjoint(Geometry)
Geometry.Distance(Geometry)
Geometry.Envelope
Geometry.EqualsExact(Geometry)
Geometry.EqualsTopologically(Geometry)
Geometry.GeometryType
Geometry.GetGeometryN(int)
Geometry.InteriorPoint
Geometry.Intersection(Geometry)
Geometry.Intersects(Geometry)
Geometry.IsEmpty
Geometry.IsSimple
Geometry.IsValid
Geometry.IsWithinDistance(Geometry, double)
Geometry.Length
Geometry.NumGeometries
Geometry.NumPoints
Geometry.OgcGeometryType
Geometry.Overlaps(Geometry)
Geometry.PointOnSurface
Geometry.Relate(Geometry, string)
Geometry.Reverse()
Geometry.SRID
Geometry.SymmetricDifference(Geometry)
Geometry.ToBinary()
Geometry.ToText()
Geometry.Touches(Geometry)
Geometry.Union()
Geometry.Union(Geometry)
Geometry.Within(Geometry)
GeometryCollection.Count
GeometryCollection[int]
LineString.Count
LineString.EndPoint
LineString.GetPointN(int)
LineString.IsClosed
LineString.IsRing
LineString.StartPoint
MultiLineString.IsClosed
Point.M
Point.X
Point.Y
Point.Z
Polygon.ExteriorRing
Polygon.GetInteriorRingN(int)
Polygon.NumInteriorRings
? ? ? ? ?

Filter Types

Geometry

The following definitions are the definition of Geometry. All operations defined by Geometry are also defined on Point, LineString, Polygon, GeometryCollection or one of its child!

1. Array, Scalar, Object Filters


Area

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Area
Returns the area of this Geometry. Areal Geometries have a non-zero area. They override this function to compute the area. Others return 0.0

Declaration

public virtual double Area { get; }

Property Value

Type Description
Double The area of the Geometry.

Proposed SDL

extend input GeometryFilter {
  area: Float
  area_gt: Float
  area_gte: Float
  area_lt: Float
  area_lte: Float
  area_in: [Float]
  area_not: Float
  area_not_gt: Float
  area_not_gte: Float
  area_not_lt: Float
  area_not_lte: Float
  area_not_in: [Float]
}

Example

{
  cities(where: { shape: { area_gt: 5 } }) {
    name
  }
}

Boundary

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Boundary
Returns the boundary, or an empty geometry of appropriate dimension if this Geometry is empty. For a discussion of this function, see the OpenGIS Simple Features Specification. As stated in SFS Section 2.1.13.1, "the boundary of a Geometry is a set of Geometries of the next lower dimension."

Declaration

public abstract Geometry Boundary { get; }

Property Value

Type Description
Geometry The closure of the combinatorial boundary of this Geometry.

Proposed SDL

extend input GeometryFilter {
  boundary: GeometryFilter
}

Example

{
  cities(
    where: {
        shape: {
            boundary: {
                contains {
                    type: "Point",
                    coordinates: [430178, 4506818] }
        }}}) {
    name
  }
}

BoundaryDimension

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
Returns the dimension of this Geometries inherent boundary.

Declaration

public abstract Dimension BoundaryDimension { get; }

Property Value

Type Description
Dimension The dimension of the boundary of the class implementing this interface, whether or not this object is the empty point. Returns Dimension.False if the boundary is the empty point.

Proposed SDL

extend input GeometryFilter {
  boundaryDimension: Dimension
  boundaryDimension_gt: Dimension
  boundaryDimension_gte: Dimension
  boundaryDimension_lt: Dimension
  boundaryDimension_lte: Dimension
  boundaryDimension_in: [Dimension]
  boundaryDimension_not: Dimension
  boundaryDimension_not_gt: Dimension
  boundaryDimension_not_gte: Dimension
  boundaryDimension_not_lt: Dimension
  boundaryDimension_not_lte: Dimension
  boundaryDimension_not_in: [Dimension]
}

Example

{
  cities(where: { shape: { boundaryDimension_in: [CURVE, POINT] } }) {
    name
  }
}

Centroid

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Centroid
Computes the centroid of this Geometry. The centroid is equal to the centroid of the set of component Geometries of highest dimension (since the lower-dimension geometries contribute zero "weight" to the centroid).

The centroid of an empty geometry is POINT EMPTY.

Declaration

public virtual Point Centroid { get; }

Property Value

Type Description
Point A Point which is the centroid of this Geometry.

Proposed SDL

extend input GeometryFilter {
  centroid: PointFilter
}

Example

{
  cities(where: { shape: { centroid: { x: 430178, y: 4506818 } } }) {
    name
  }
}

Coordinate

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
Returns a vertex of this Geometry (usually, but not necessarily, the first one).

Declaration

public abstract Coordinate Coordinate { get; }

Property Value

Type Description
Coordinate a Coordinate which is a vertex of this Geometry.

Proposed SDL

extend input GeometryFilter {
  coordinate: CoordinateFilter
}

Example

{
  cities(where: { shape: { coordinate: { x: 430178, y: 4506818, z: 0 } } }) {
    name
  }
}

Coordinates

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
Returns an array containing the values of all the vertices for this geometry.

Declaration

public abstract Coordinate[] Coordinates { get; }

Property Value

Type Description
Coordinate[] The vertices of this Geometry.

Proposed SDL

extend input GeometryFilter {
  coordinates_any: Boolean
  coordinates_some: CoordinateFilter
  coordinates_all: CoordinateFilter
  coordinates_none: CoordinateFilter
}

Example

{
  cities(
    where: { shape: { coordinates_some: { x: 430178, y: 4506818, z: 0 } } }
  ) {
    name
  }
}

Dimension

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Dimension
Returns the dimension of this geometry.

Declaration

public abstract Dimension Dimension { get; }

Property Value

Type Description
Dimension The topological dimensions of this geometry

Proposed SDL

extend input GeometryFilter {
  dimension: Dimension
  dimension_gt: Dimension
  dimension_gte: Dimension
  dimension_lt: Dimension
  dimension_lte: Dimension
  dimension_in: [Dimension!]
  dimension_not: Dimension
  dimension_not_gt: Dimension
  dimension_not_gte: Dimension
  dimension_not_lt: Dimension
  dimension_not_lte: Dimension
  dimension_not_in: [Dimension!]
}

Proposed SDL

{
  cities(where: { shape: { dimension_in: [CURVE, POINT] } }) {
    name
  }
}

Envelope

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Envelope
Gets a geometry representing the envelope (bounding box) of this Geometry.

Declaration

public Geometry Envelope { get; }

Property Value

Type Description
Geometry A Geometry representing the envelope of this Geometry

Proposed SDL

extend input GeometryFilter {
  envelope: GeometryFilter
}

Example

{
  cities(
    where: {
        shape: {
            envelope: {
                contains {
                    type: "Point",
                    coordinates: [430178, 4506818] }
        }}}) {
    name
  }
}

GeometryType

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.GeometryType
Returns the name of this Geometry's actual class.

Declaration

public abstract string GeometryType { get; }

Property Value

Type Description
String The name of this Geometrys actual class.

Proposed SDL

extend input GeometryFilter {
  geometryType: String
  geometryType_startWith: String
  geometryType_endsWith: String
  geometryType_contains: String
  geometryType_in: [String!]
  geometryType_not: String
  geometryType_not_startWith: String
  geometryType_not_endsWith: String
  geometryType_not_contains: String
  geometryType_not_in: [String!]
}

InteriorPoint

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.InteriorPoint
Computes an interior point of this Geometry.

Declaration

public virtual Point InteriorPoint { get; }

Property Value

Type Description
Point A Point which is in the interior of this Geometry.

Proposed SDL

extend input GeometryFilter {
  interiorPoint: PointFilter
}

Example

{
  cities(where: { shape: { interiorPoint: { x: 430178, y: 4506818 } } }) {
    name
  }
}

IsEmpty

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.IsEmpty
ests whether the set of points covered in this Geometry is empty.

Declaration

public abstract bool IsEmpty { get; }

Property Value

Type Description
Boolean true if this Geometry does not cover any points.

Proposed SDL

extend input GeometryFilter {
  isEmpty: bool
  isEmpty_not: bool
}

Example

{
  cities(where: { shape: { isEmpty: true } }) {
    name
  }
}

IsGeometryCollection

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
Gets a value indicating if this geometry is a geometry collection

Declaration

protected bool IsGeometryCollection { get; }

Property Value

Type Description
Boolean

Proposed SDL

extend input GeometryFilter {
  isGeometryCollection: bool
  isGeometryCollection_not: bool
}

Example

{
  cities(where: { shape: { isGeometryCollection: true } }) {
    name
  }
}

IsRectangle

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?

Declaration

public virtual bool IsRectangle { get; }

Property Value

Type Description
Boolean

Proposed SDL

extend input GeometryFilter {
  isRectangle: bool
  isRectangle_not: bool
}

Example

{
  cities(where: { shape: { isRectangle: true } }) {
    name
  }
}

IsSimple

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.IsSimple
Tests whether this Geometry is simple.

The SFS definition of simplicity follows the general rule that a Geometry is simple if it has no points of self-tangency, self-intersection or other anomalous points.

Simplicity is defined for each Geometry subclass as follows:

Declaration

public virtual bool IsSimple { get; }

Proposed SDL

extend input GeometryFilter {
  isSimple: bool
  isSimple_not: bool
}

Example

{
  cities(where: { shape: { isSimple: true } }) {
    name
  }
}

IsValid

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.IsValid
IsValid
Tests whether this Geometry is topologically valid, according to the OGC SFS specification.

For validity rules see the documentation for the specific geometry subclass.

Declaration

public virtual bool IsValid { get; }

Proposed SDL

extend input GeometryFilter {
  isValid: bool
  isValid_not: bool
}

Example

{
  cities(where: { shape: { isValid: true } }) {
    name
  }
}

Length

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Length
Returns the length of this Geometry. Linear geometries return their length. Areal geometries return their perimeter. They override this function to compute the length. Others return 0.0

Declaration

public virtual double Length { get; }

Property Value

Type Description
Double The length of the Geometry.

Proposed SDL

extend input GeometryFilter {
  length: Float
  length_gt: Float
  length_gte: Float
  length_lt: Float
  length_lte: Float
  length_in: [Float!]
  length_not: Float
  length_not_gt: Float
  length_not_gte: Float
  length_not_lt: Float
  length_not_lte: Float
  length_not_in: [Float!]
}

Example

{
  cities(where: { shape: { length: 2 } }) {
    name
  }
}

NumGeometries

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.NumGeometries
Returns the number of Geometryes in a GeometryCollection, or 1, if the geometry is not a collection.

Declaration

public virtual int NumGeometries { get; }

Property Value

Type Description
Int32

Proposed SDL

extend input GeometryFilter {
  numGeometries: Int
  numGeometries_gt: Int
  numGeometries_gte: Int
  numGeometries_lt: Int
  numGeometries_lte: Int
  numGeometries_in: [Int!]
  numGeometries_not: Int
  numGeometries_not_gt: Int
  numGeometries_not_gte: Int
  numGeometries_not_lt: Int
  numGeometries_not_lte: Int
  numGeometries_not_in: [Int!]
}

Example

{
  cities(where: { shape: { numGeometries: 2 } }) {
    name
  }
}

NumPoints

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.NumPoints
Returns the count of this Geometrys vertices. The Geometry s contained by composite Geometrys must be Geometry's; that is, they must implement NumPoints.

Declaration

public abstract int NumPoints { get; }

Property Value

Type Description
Int32 The number of vertices in this Geometry.

Proposed SDL

extend input GeometryFilter {
  numPoints: Int
  numPoints_gt: Int
  numPoints_gte: Int
  numPoints_lt: Int
  numPoints_lte: Int
  numPoints_in: [Int!]
  numPoints_not: Int
  numPoints_not_gt: Int
  numPoints_not_gte: Int
  numPoints_not_lt: Int
  numPoints_not_lte: Int
  numPoints_not_in: [Int!]
}

Example

{
  cities(where: { shape: { numPoints: 2 } }) {
    name
  }
}

SRID

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.SRID
RSets the ID of the Spatial Reference System used by the Geometry.

Declaration

public int SRID { get; set; }

Property Value

Type Description
Int32

Proposed SDL

extend input GeometryFilter {
  srid: Int
  srid_gt: Int
  srid_gte: Int
  srid_lt: Int
  srid_lte: Int
  srid_in: [Int!]
  srid_not: Int
  srid_not_gt: Int
  srid_not_gte: Int
  srid_not_lt: Int
  srid_not_lte: Int
  srid_not_in: [Int!]
}

Example

{
  cities(where: { shape: { srid: 2 } }) {
    name
  }
}

ConvexHull

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.ConvexHull()
Returns the smallest convex Polygon that contains all the points in the Geometry. This obviously applies only to Geometry s which contain 3 or more points.

Declaration

public virtual Geometry ConvexHull()

Returns

Type Description
Geometry the -area convex polygon containing this Geometry's points.
databaseEntry.ConvexHull()

Proposed SDL

extend input GeometryFilter {
  convexHull: GeometryFilter
}

Example

query Example($other: GeometryFilter) {
  cities(where: { shape: { convexHull: $other } }) {
    name
  }
}

Normalized

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
Creates a new Geometry which is a normalized copy of this Geometry.

Declaration

public Geometry Normalized()

Returns

Type Description
Geometry A normalized copy of this geometry.
databaseEntry.Normalized() // Geometry

Proposed SDL

extend input GeometryFilter {
  normalized: GeometryFilter
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { normalized: { shape: { area: 5 } } }) {
    name
  }
}

Reverse

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Reverse()
Computes a new geometry which has all component coordinate sequences in reverse order (opposite orientation) to this one.

Declaration

public abstract Geometry Reverse()

Returns

Type Description
Geometry A reversed geometry
databaseEntry.Reverse() // Geometry

Proposed SDL

extend input GeometryFilter {
  reverse: GeometryFilter
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { reverse: { shape: { area: 5 } } }) {
    name
  }
}

2. Filters that need GeoJsonInput


Contains

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Contains(Geometry)
Contains(Geometry)
Tests whether this geometry contains the argument geometry.

Declaration

public virtual bool Contains(Geometry g)

Parameters

Type Name Description
Geometry g the Geometry with which to compare this Geometry

Returns

Type Description
Boolean true if this Geometry contains g
databaseEntry.Contains(inputType)
!databaseEntry.Contains(inputType)

Proposed SDL

extend input GeometryFilter {
  contains: GeoJsonFitlerInput
  contains_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { contains: $other } }) {
    name
  }
}

ContainedIn / ContainedBy? Whithin

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Within(Geometry)
```csharp
databaseEntry.Within(inputType)
!databaseEntry.Within(inputType)

Proposed SDL

extend input GeometryFilter {
  within: GeoJsonFitlerInput
  within_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { within: $other } }) {
    name
  }
}

Covers

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Covers(Geometry)
Tests whether this geometry covers the argument geometry

Declaration

public virtual bool Covers(Geometry g)

Parameters

Type Name Description
Geometry g The Geometry with which to compare this Geometry

Returns

Type Description
Boolean true if this Geometry covers g
databaseEntry.Covers(inputType)
!databaseEntry.Covers(inputType)

Proposed SDL

extend input GeometryFilter {
  covers: GeoJsonFitlerInput
  covers_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { covers: $other } }) {
    name
  }
}

CoveredBy

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.CoveredBy(Geometry)
Tests whether this geometry is covered by the specified geometry.

Declaration

public bool CoveredBy(Geometry g)

Parameters

Type Name Description
Geometry g the Geometry with which to compare this Geometry

Returns

Type Description
Boolean true if this Geometry is covered by g
databaseEntry.CoveredBy(inputType)
!databaseEntry.CoveredBy(inputType)

Proposed SDL

extend input GeometryFilter {
  coveredBy: GeoJsonFitlerInput
  coveredBy_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { coveredBy: $other } }) {
    name
  }
}

Crosses

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Crosses(Geometry)
Tests whether this geometry crosses the specified geometry.

Declaration

public virtual bool Crosses(Geometry g)

Parameters

Type Name Description
Geometry g The Geometry with which to compare this Geometry

Returns

Type Description
Boolean true if the two Geometrys cross.
databaseEntry.Crosses(inputType)
!databaseEntry.Crosses(inputType)

Proposed SDL

extend input GeometryFilter {
  crosses: GeoJsonFitlerInput
  crosses_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { crosses: $other } }) {
    name
  }
}

Disjoint

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Disjoint(Geometry)
Tests whether this geometry is disjoint from the argument geometry.

Declaration

public bool Disjoint(Geometry g)

Parameters

Type Name Description
Geometry g The Geometry with which to compare this Geometry.

Returns

Type Description
Boolean true if the two Geometrys are disjoint.
databaseEntry.Disjoint(inputType)

Proposed SDL

extend input GeometryFilter {
  disjoint: GeoJsonFitlerInput
  disjoint_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { disjoint: $other } }) {
    name
  }
}

Equals

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
> Probleme here is, we never really had `equals` like this on an object type

Tests whether this geometry is structurally and numerically equal to a given Object.

Declaration

public override bool Equals(object o)

Parameters

Type Name Description
Object o The object to compare

Returns

Type Description
Boolean true if this geometry is exactly equal to the argument
databaseEntry.Equals(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  equals: GeoJsonFitlerInput
  equals_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { equals: $other } }) {
    name
  }
}

EqualsExact

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.EqualsExact(Geometry)
Returns true if the two Geometrys are exactly equal. Two Geometries are exactly equal if:

This provides a stricter test of equality than EqualsTopologically(Geometry), which is more useful in certain situations (such as using geometries as keys in collections).

This method does not test the values of the GeometryFactory, the SRID, or the UserData fields.

To properly test equality between different geometries, it is usually necessary to Normalize() them first.

Declaration

public bool EqualsExact(Geometry other)

Parameters

Type Name Description
Geometry other The Geometry with which to compare this Geometry.
databaseEntry.Equals(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  equalsExact: GeoJsonFitlerInput
  equalsExact_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { equalsExact: $other } }) {
    name
  }
}

EqualsTopologically

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.EqualsTopologically(Geometry)
Tests whether this geometry is topologically equal to the argument geometry as defined by the SFS Equals predicate.

Declaration

public virtual bool EqualsTopologically(Geometry g)

Parameters

Type Name Description
Geometry g the Geometry with which to compare this Geometry

Returns

Type Description
Boolean true if the two

Geometry
s are topologically equal

databaseEntry.Equals(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  equalsTopologically: GeoJsonFitlerInput
  equalsTopologically_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { equalsTopologically: $other } }) {
    name
  }
}

Distance

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Distance(Geometry)
Returns the minimum distance between this Geometry and another Geometry g.

Declaration

public virtual double Distance(Geometry g)

Parameters

Type Name Description
Geometry g The Geometry from which to compute the distance.

Returns

Type Description
Double The distance between the geometries
databaseEntry.Distance(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  distance: DistanceInput
}

input DistanceInput {
  from: GeoJsonFitlerInput!
  is: Float
  is_gt: Float
  is_gte: Float
  is_lt: Float
  is_lte: Float
  is_in: Float
  is_not: Float
  is_not_gt: Float
  is_not_gte: Float
  is_not_lt: Float
  is_not_lte: Float
  is_not_in: Float
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { distance: { of: $other, is: 5 } } }) {
    name
  }
}

Intersection

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Intersection(Geometry)
Computes a Geometry representing the point-set which is common to both this Geometry and the other Geometry.

The intersection of two geometries of different dimension produces a result geometry of dimension less than or equal to the minimum dimension of the input geometries. The result geometry may be a heterogeneous GeometryCollection. If the result is empty, it is an atomic geometry with the dimension of the lowest input dimension.

Intersection of GeometryCollections is supported only for homogeneous collection types.

Non-empty heterogeneous GeometryCollection arguments are not supported.

Declaration

public Geometry Intersection(Geometry other)

Parameters

Type Name Description
Geometry other The Geometry with which to compute the intersection.

Returns

Type Description
Geometry A geometry representing the point-set common to the two Geometrys.
databaseEntry.Intersection(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  intersection: IntersectionInput
}

input IntersectionInput {
  of: GeoJsonFitlerInput
  is: GeometryFilter
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(
    where: { shape: { intersection: { of: $other, is: { area_gt: 5 } } } }
  ) {
    name
  }
}

Intersects

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Intersects(Geometry)
Tests whether this geometry intersects the argument geometry.

Declaration

public virtual bool Intersects(Geometry g)

Parameters

Type Name Description
Geometry g The Geometry with which to compare this Geometry.

Returns

Type Description
Boolean true if the two Geometrys intersect.
databaseEntry.Intersects(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  intersects: GeoJsonFitlerInput
  intersects_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { intersects: $other } }) {
    name
  }
}

Overlaps

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Overlaps(Geometry)
Tests whether this geometry overlaps the specified geometry.

Declaration

public virtual bool Overlaps(Geometry g)

Parameters

Type Name Description
Geometry g The Geometry with which to compare this Geometry.

Returns

Type Description
Boolean true if the two Geometrys overlap. For this function to return true, the Geometry s must be two points, two curves or two surfaces.
databaseEntry.Overlaps(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  overlaps: GeoJsonFitlerInput
  overlaps_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { overlaps: $other } }) {
    name
  }
}

Touches

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Touches(Geometry)
Tests whether this geometry touches the argument geometry

Declaration

public virtual bool Touches(Geometry g)

Parameters

Type Name Description
Geometry g The Geometry with which to compare this Geometry.

Returns

Type Description
Boolean true if the two Geometrys touch; Returns false if both Geometrys are points.
databaseEntry.Touches(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  touches: GeoJsonFitlerInput
  touches_not: GeoJsonFitlerInput
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { touches: $other } }) {
    name
  }
}

Complex Filters with Custom Types


Union

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Union(Geometry)
> Collision.. this method exists with and without parameter.

Computes a Geometry representing the point-set which is contained in both this Geometry and the other Geometry.

Declaration

public Geometry Union(Geometry other)

Parameters

Type Name Description
Geometry other the Geometry with which to compute the union

Returns

Type Description
Geometry A point-set combining the points of this Geometry and the points of other
databaseEntry.Union(inputType) // Geometry

Proposed SDL

extend input GeometryFilter {
  intersection: UnionInput
}

input UnionInput {
  of: GeoJsonFitlerInput
  is: GeometryFilter
}

Example

query Example($other: GeoJsonFitlerInput, $other2: GeoJsonFitlerInput) {
  cities(
    where: {
      shape: {
        union: {
          of: $other
          is: { union: { of: $other2, is: { area_gt: 5 } } }
        }
      }
    }
  ) {
    name
  }
}

Difference

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Difference(Geometry)
Computes a Geometry representing the closure of the point-set of the points contained in this Geometry that are not contained in the other Geometry.

If the result is empty, it is an atomic geometry with the dimension of the left-hand input.

Non-empty GeometryCollection arguments are not supported.

Declaration

public Geometry Difference(Geometry other)

Parameters

Type Name Description
Geometry other The Geometry with which to compute the difference.

Returns

Type Description
Geometry A Geometry representing the point-set difference of this Geometry with other.
databaseEntry.Difference(inputType) // Geometry

Proposed SDL

# We would need smht like
extend input GeometryFilter {
  difference: DifferenceInput
}

input DifferenceInput {
  of: GeoJsonFitlerInput
  is: GeometryFilter
}

Example

query Example($other: GeoJsonFitlerInput) {
  cities(where: { shape: { difference: { of: $other, is: { area_gt: 5 } } } }) {
    name
  }
}

Out of scoped


EnvelopeInternal

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
Gets an Envelope containing the minimum and maximum x and y values in this Geometry. If the geometry is empty, an empty Envelope is returned.

Declaration

public Envelope EnvelopeInternal { get; }

Property Value

Type Description
Envelope the envelope of this Geometry.

OgcGeometryType

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.OgcGeometryType
Gets the OGC geometry type

Declaration

public abstract OgcGeometryType OgcGeometryType { get; }

PointOnSurface

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.PointOnSurface
> out of scope bc equal to InteriorPoint

InteriorPoint

Declaration

public Point PointOnSurface { get; }

Property Value

Type Description
Point

PrecisionModel

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
Returns the PrecisionModel used by the Geometry.

Declaration

public PrecisionModel PrecisionModel { get; }

Property Value

Type Description
PrecisionModel the specification of the grid of allowable points, for this Geometry and all other Geometrys.

SortIndex

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
? ? ? ? ?
Gets a value to sort the geometry

Declaration

protected abstract Geometry.SortIndexValue SortIndex { get; }

IsWithinDistance

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.IsWithinDistance(Geometry, double)
> I think is already covered by `distance_lt`?

Tests whether the distance from this Geometry to another is less than or equal to a specified value.

Declaration

public virtual bool IsWithinDistance(Geometry geom, double distance)

Parameters

Type Name Description
Geometry geom the Geometry to check the distance to.
Double distance the distance value to compare.

Returns

Type Description
Boolean true if the geometries are less than distance apart.

Relate

NetTopologySuite SQL Server (geometry) SQL Server (geography) SQLite Npgsql
Geometry.Relate(Geometry, string)
Returns the DE-9IM intersection matrix for the two Geometrys.

Declaration

public virtual IntersectionMatrix Relate(Geometry g)

Parameters

Type Name Description
Geometry g The Geometry with which to compare this Geometry

Returns

Type Description
IntersectionMatrix A matrix describing the intersections of the interiors, boundaries and exteriors of the two Geometrys.

@John0x
Copy link
Contributor

John0x commented Apr 29, 2020

Nice work @PascalSenn :)

The biggest problem, that I can see right now, is to get input and output of spatial data to be consistent. I tried using the GeoJSON format with a custom Scalar (see repo). Taking a structured object without typings as the input and returning a stringified json object as the output. It works using the online playground but causes a lot of weird problems with the apollo react client (for input).

http://docs.opengeospatial.org/per/18-021.pdf
Might be interesting. Haven't read through yet, but GraphQL starts on page 61

@steveoh
Copy link
Contributor Author

steveoh commented May 5, 2020

Slack cliff notes

  1. The API will be fully typed to avoid problems with performance, tooling and validation
  2. Input & Output types can already be extended with fields. this allows adding fields for fromJson, toJson by the user
  3. Deserialization overriding will be integrated in the new execution method (making fromJson actually do something)
  4. Fitler types will only allow one input type at the time (distance:{from:$point}), no unions

SDL shape and support

Create types for

  1. point
  2. line string
  3. polygon
  4. multi point
  5. multi line string
  6. multi polygon

e.g.

point: { "x": 1, "y": 1, "srid": 123123 }

srid will be optional with available server side default so clients to not need to specify on every request.

e.g.

.AddEntityFrameworkSpatial(opt => opt.DefaultSRID = 26912)

Filtering support

  1. intersect
    where: { 
      location:{ 
       intersects: { 
        this: {"x": 1,"y": 2}
     }}} 
  2. closest
    where: { 
      location:{ 
       closest: { 
        to: {"x": 1,"y": 2}
     }}} 
  3. distance
 where: { 
  location:{ 
   distance: { 
    from: {"x": 1,"y": 2} ,
    is_lt:5, 
    unit: MILES
 }}} 

@steveoh steveoh mentioned this issue May 5, 2020
@John0x John0x changed the title Filters: Spatial Filters; Scalars: Spatial Scalars [Spec] Spatial Filters May 6, 2020
@michaelstaib michaelstaib modified the milestones: HC-11.x.x, HC-11.0.0 Nov 1, 2020
@michaelstaib
Copy link
Member

This one is now merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants