Skip to content

Language Primitives

/

Data types

Geometries

A geometry is a type based on the GeoJSON spec that is optimised for working with data pertaining to locations on Earth.

SurrealDB supports GeoJSON, including Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and Collection values. SurrealQL converts a GeoJSON object written in a query into a geometry, and from SurrealDB 4.0 it also converts a GeoJSON object built at run time wherever only a geometry fits. See Objects built at run time.

Type

Description

Point

A geolocation point with longitude and latitude

LineString

A GeoJSON LineString value for storing a geometric path

Polygon

A GeoJSON Polygon value for storing a geometric area

MultiPoint

A value which contains multiple geometry points

MultiLineString

A value which contains multiple geometry lines

MultiPolygon

A value which contains multiple geometry polygons

Collection

A value which contains multiple different geometry types

There are two main points to keep in mind when creating a geometry type in SurrealDB. They are:

  • Points are defined according to the GeoJSON spec, which specifies longitude before latitude. Many sites - including Google Maps - provide location data in the opposite order, so be sure to confirm that any data being used to create a Point is in the order (longitude, latitude), and not the other way around.

  • A geometry created from an object must contain a type field and a coordinates field, no more and no less.

This can be shown by calling the type::is_geometry() function on some sample objects.

-- Has both a `type` and a `coordinates` field, each with valid data
{ type: "Point", coordinates: [-0.118092, 51.509865] }.is_geometry();
//- true

-- Lacks a `type` field
{ coordinates: [-0.118092, 51.509865] }.is_geometry();
//- false

-- Carries a field beyond `type` and `coordinates`
{ type: "Point", coordinates: [-0.118092, 51.509865], unnecessary: "data" }.is_geometry();
//- false

An object that is built at run time stays an object, such as one built from parameters, decoded from JSON with encoding::json::decode() or http::get(), or read from a field. From SurrealDB 4.0, such an object is converted to a geometry wherever only a geometry fits: a field of type geometry, an argument of a geo:: function, a <geometry> cast, and an operand of the geometry operators INSIDE, OUTSIDE, INTERSECTS, CONTAINS, CONTAINSALL and CONTAINSANY, such as $point INSIDE $area for two objects decoded from JSON. On an object that is not a GeoJSON geometry, these operators keep their usual meaning, so $area CONTAINS 'coordinates' still checks for the key. The object must still be exactly a GeoJSON geometry, so an object with extra fields such as bbox, or a geometry of the wrong kind for a type such as geometry<point>, is refused.

LET $coords = [-0.118092, 51.509865];
LET $point = { type: "Point", coordinates: $coords };
[
	$point.is_geometry(),
	(<geometry> $point).is_geometry(),
	geo::distance($point, (0, 51.5))
];
Output
[false, true, 8246.78908673967f]

A type that can also hold the object as it is, such as geometry | object, keeps it as an object. Records that were stored before the field's type changed are not converted. A MIGRATE EXISTING ROWS clause converts them:

ALTER FIELD location ON venue TYPE option<geometry>
    MIGRATE EXISTING ROWS AS location_to_geometry
    USING <geometry> $value FALLBACK NONE;

The simplest form of GeoJSON that SurrealDB supports is a geolocation point. These can be written using two different formats. The first format is that of an object that matches the GeoJSON spec.

CREATE city:london SET centre = {
    type: "Point",
    coordinates: [-0.118092, 51.509865],
};

The other format is a simple 2-element tuple containing the longitude and the latitude of a location. This output for this format is no different from the above, and is simply a convenience due to the frequency of use of the Point type.

CREATE city:london SET centre = (-0.118092, 51.509865);


A GeoJSON LineString value for storing a geometric path.

CREATE city:london SET distance = {
    type: "LineString",
    coordinates: [[-0.118092, 51.509865],[0.1785278, 51.37692386]],
};


A GeoJSON Polygon value for storing a geometric area.

CREATE city:london SET boundary = {
	type: "Polygon",
	coordinates: [[
		[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
		[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
		[-0.38314819, 51.37692386]
	]]
};


MultiPoints can be used to store multiple geometry points in a single value.

CREATE person:tobie SET locations = {
	type: "MultiPoint",
	coordinates: [
		[10.0, 11.2],
		[10.5, 11.9]
	],
};


A MultiLineString can be used to store multiple geometry lines in a single value.

CREATE travel:yellowstone SET routes = {
	type: "MultiLineString",
	coordinates: [
		[ [10.0, 11.2], [10.5, 11.9] ],
		[ [11.0, 12.2], [11.5, 12.9], [12.0, 13.0] ]
	]
}


MultiPolygons can be used to store multiple geometry polygons in a single value.

CREATE university:oxford SET locations = {
	type: "MultiPolygon",
	coordinates: [
		[
			[ [10.0, 11.2], [10.5, 11.9], [10.8, 12.0], [10.0, 11.2] ]
		],
		[
			[ [9.0, 11.2], [10.5, 11.9], [10.3, 13.0], [9.0, 11.2] ]
		]
	]
};


Collections can be used to store multiple different geometry types in a single value.

CREATE university:oxford SET buildings = {
	type: "GeometryCollection",
	geometries: [
		{
			type: "MultiPoint",
			coordinates: [
				[10.0, 11.2],
				[10.5, 11.9]
			],
		},
		{
			type: "Polygon",
			coordinates: [[
				[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
				[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
				[-0.38314819, 51.37692386]
			]]
		},
		{
			type: "MultiPolygon",
			coordinates: [
				[
					[ [10.0, 11.2], [10.5, 11.9], [10.8, 12.0], [10.0, 11.2] ]
				],
				[
					[ [9.0, 11.2], [10.5, 11.9], [10.3, 13.0], [9.0, 11.2] ]
				]
			]
		}
	]
};


The following example includes five records from an open database with cities worldwide that have a population of at least 1000. The queries below create a city record from each entry that includes their name, location, and name. Next, it uses the geo::distance function to find their two closest neighbours, relating them via the close_to relation table. The final query can be viewed in traditional form to see each city's neighbours, or on SurrealDB Studio's graph view to see a visual representation of the network of closely linked cities.

DEFINE TABLE city SCHEMAFULL;
DEFINE FIELD name ON city TYPE string;
DEFINE FIELD location ON city TYPE point;

FOR $city IN [{"geoname_id": "5881639", "name": "100 Mile House", "ascii_name": "100 Mile House", "feature_class": "P", "feature_code": "PPL", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "02", "admin2_code": "5941", "admin3_code": "5941005", "admin4_code": null, "population": 1980, "elevation": null, "dem": 928, "timezone": "America/Vancouver", "modification_date": "2019-11-26", "label_en": "Canada", "coordinates": {"lon": -121.28594, "lat": 51.64982}},{"geoname_id": "5896969", "name": "Beaverlodge", "ascii_name": "Beaverlodge", "feature_class": "P", "feature_code": "PPL", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "01", "admin2_code": "4819009", "admin3_code": null, "admin4_code": null, "population": 2219, "elevation": null, "dem": 723, "timezone": "America/Edmonton", "modification_date": "2024-02-28", "label_en": "Canada", "coordinates": {"lon": -119.43605, "lat": 55.21664}},{"geoname_id": "5911606", "name": "Burnaby", "ascii_name": "Burnaby", "feature_class": "P", "feature_code": "PPLA3", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "02", "admin2_code": "5915", "admin3_code": "5915025", "admin4_code": null, "population": 202799, "elevation": null, "dem": 87, "timezone": "America/Vancouver", "modification_date": "2019-02-26", "label_en": "Canada", "coordinates": {"lon": -122.95263, "lat": 49.26636}},{"geoname_id": "5920996", "name": "Chertsey", "ascii_name": "Chertsey", "feature_class": "P", "feature_code": "PPL", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "10", "admin2_code": "14", "admin3_code": "62047", "admin4_code": null, "population": 4836, "elevation": null, "dem": 251, "timezone": "America/Toronto", "modification_date": "2016-06-22", "label_en": "Canada", "coordinates": {"lon": -73.89095, "lat": 46.07109}},{"geoname_id": "5941905", "name": "Dorset Park", "ascii_name": "Dorset Park", "alternate_names": null, "feature_class": "P", "feature_code": "PPLX", "country_code": "CA", "cou_name_en": "Canada", "country_code_2": null, "admin1_code": "08", "admin2_code": "3520", "admin3_code": null, "admin4_code": null, "population": 25003, "elevation": null, "dem": 164, "timezone": "America/Toronto", "modification_date": "2020-05-02", "label_en": "Canada", "coordinates": {"lon": -79.28215, "lat": 43.75386}}]

{
    CREATE type::record("city", <int>$city.geoname_id) SET
		location = <point>[$city.coordinates.lon, $city.coordinates.lat],
		name = $city.name;        
};

FOR $city IN SELECT * FROM city {
    LET $this_location = $city.location;
    LET $closest = 
		(SELECT id, location, geo::distance($this_location, location) AS distance
		  FROM city
	ORDER BY distance ASC
	LIMIT 3
		).filter(|$c| $c.distance != 0);
    FOR $closest IN $closest {
      RELATE $city->close_to->$closest SET
	  	distance = geo::distance($city.location, $closest.location);
    };
};

SELECT name, id, ->close_to->city AS neighbours FROM city;

Available since: v4.0.0

A query that filters on a geometry field, such as WHERE location INSIDE $area or WHERE geo::distance(location, $here) < 10000, reads every record of the table unless the field has an index. A SPATIAL index answers these conditions from the index instead:

DEFINE INDEX city_location ON city FIELDS location SPATIAL;

The example above orders every city by distance to find its nearest neighbours. An ordering by distance does not use the index, so on a large table, add a radius to the WHERE clause to limit the records that are sorted.

Was this page helpful?