website/src/docs/hotchocolate/v16/integrations/spatial-data.md
Experimental: This feature is community-driven and not yet finalized. The core team has limited experience with spatial data and welcomes your feedback to guide next steps. While we try not to introduce breaking changes, we reserve the possibility to adjust the API in future releases.
Spatial data describes locations or shapes as objects. Many database providers support storing this type of data. APIs often use GeoJSON to send spatial data over the network.
The most common library for spatial data in .NET is NetTopologySuite. Entity Framework supports Spatial Data and uses NetTopologySuite as its data representation.
The HotChocolate.Spatial package integrates NetTopologySuite into Hot Chocolate. Your resolvers can return NetTopologySuite shapes, and they are transformed into GeoJSON.
Install the HotChocolate.Spatial package:
Register the spatial types on the schema builder:
builder
.AddGraphQL()
.AddSpatialTypes();
If you use data extensions to project data from a database, also install HotChocolate.Data.Spatial:
Register the data extensions:
builder
.AddGraphQL()
.AddSpatialTypes()
.AddFiltering()
.AddProjections()
.AddSpatialFiltering()
.AddSpatialProjections();
All NetTopologySuite runtime types are now bound to the corresponding GeoJSON type:
public class Pub
{
public int Id { get; set; }
public string Name { get; set; }
public Point Location { get; set; }
}
public class Query
{
public IQueryable<Pub> GetPubs(SomeDbContext someDbContext)
{
return someDbContext.Pubs;
}
}
type Pub {
id: Int!
name: String!
location: GeoJSONPointType!
}
type Query {
pubs: [Pub!]!
}
{
pubs {
id
location {
__typename
bbox
coordinates
crs
type
}
name
}
}
{
"data": {
"pubs": [
{
"id": 1,
"location": {
"__typename": "GeoJSONPointType",
"bbox": [12, 12, 12, 12],
"coordinates": [[12, 12]],
"crs": 4326,
"type": "Point"
},
"name": "The Winchester"
}
]
}
}
Hot Chocolate supports GeoJSON input and output types, along with a GeoJSON scalar for generic inputs.
| NetTopologySuite | GraphQL |
|---|---|
| Point | GeoJSONPointType |
| MultiPoint | GeoJSONMultiPointType |
| LineString | GeoJSONLineStringType |
| MultiLineString | GeoJSONMultiLineStringType |
| Polygon | GeoJSONPolygonType |
| MultiPolygon | GeoJSONMultiPolygonType |
| Geometry | GeoJSONInterface |
All GeoJSON output types implement:
interface GeoJSONInterface {
"The geometry type of the GeoJson object"
type: GeoJSONGeometryType!
"The minimum bounding box around the geometry object"
bbox: [Float]
"The coordinate reference system integer identifier"
crs: Int
}
| NetTopologySuite | GraphQL |
|---|---|
| Point | GeoJSONPointInput |
| MultiPoint | GeoJSONMultiPointInput |
| LineString | GeoJSONLineStringInput |
| MultiLineString | GeoJSONMultiLineStringInput |
| Polygon | GeoJSONPolygonInput |
| MultiPolygon | GeoJSONMultiPolygonInput |
The Geometry scalar accepts any geometry type as input. This is useful when a resolver expects any Geometry type. Use this scalar with caution, as input and output types are more expressive.
scalar Geometry
Register the spatial projection handler with .AddSpatialProjections():
builder
.AddGraphQL()
.AddProjections()
.AddSpatialTypes()
.AddSpatialProjections()
The projection middleware uses this handler to project spatial data directly to the database:
[UseProjection]
public IQueryable<Pub> GetPubs(SomeDbContext someDbContext)
{
return someDbContext.Pubs;
}
Entity Framework supports filtering on NetTopologySuite objects. HotChocolate.Spatial provides handlers for filtering spatial types on IQueryable. Register them with .AddSpatialFiltering():
builder
.AddGraphQL()
.AddFiltering()
.AddSpatialTypes()
.AddSpatialFiltering()
After registration, UseFiltering() infers the possible filter types for all Geometry-based types.
[UseFiltering]
public IQueryable<Pub> GetPubs(SomeDbContext someDbContext)
{
return someDbContext.Pubs;
}
The distance filter requires an input geometry. You can optionally buffer the geometry. All comparable filter operations are available.
{
pubs(
where: {
location: {
distance: { geometry: { type: Point, coordinates: [1, 1] }, lt: 120 }
}
}
) {
id
name
}
}
The contains filter is an implementation of Geometry.Contains. It requires an input geometry with an optional buffer.
{
counties(
where: {
area: { contains: { geometry: { type: Point, coordinates: [1, 1] } } }
}
) {
id
name
}
}
The negation is ncontains.
The touches filter is an implementation of Geometry.Touches.
{
counties(
where: {
area: {
touches: {
geometry: {
type: Polygon
coordinates: [[1, 1], ...]
}
}
}
}
) {
id
name
}
}
The negation is ntouches.
The intersects filter is an implementation of Geometry.Intersects.
{
roads(
where: {
road: {
intersects: {
geometry: {
type: LineString
coordinates: [[1, 1], ...]
}
}
}
}
) {
id
name
}
}
The negation is nintersects.
The overlaps filter is an implementation of Geometry.Overlaps. The negation is noverlaps.
The within filter is an implementation of Geometry.Within.
{
pubs(
where: {
location: {
within: { geometry: { type: Point, coordinates: [1, 1] }, buffer: 200 }
}
}
) {
id
name
}
}
The negation is nwithin.