New in version 12.0.0.
Geospatial data, or "geodata", specifies points and geometric objects on the Earth's surface. With the geodata types, you can create queries that check whether a given point is contained within a shape. For example, you can find all coffee shops within 15Â km of a specified point.
Changed in version 12.3.0: Geospatial data supported in Atlas Device Sync
Realm Node.js SDK v12.3.0 and later adds support for geospatial data in Atlas Device Sync. This allows you to subscribe to geospatial queries in a synced realm. If you try to subscribe to a geospatial query with an older version of the SDK, you will receive a server error with a compensating write. For more information about managing your Sync subscriptions, refer to Manage Sync Subscriptions - Node.js SDK.
For more information on querying geospatial data with Device Sync, refer to Geospatial Data in the App Services documentation.
The Node.js SDK supports geospatial queries using the following data types:
GeoPoint
GeoCircle
GeoBox
GeoPolygon
The SDK provides these geospatial data types to simplify querying geospatial data. You cannot persist these data types directly.
For information on how to persist geospatial data, refer to the Persist Geospatial Data section on this page.
A GeoPoint defines a specific location on the Earth's surface. All of the geospatial data types use GeoPoints
to define their location.
GeoPoint
can be one of three types:
An object with:
latitude
: a number value
longitude
: a number value
altitude
: an optional number value
CanonicalGeoPoint
: an interface that satisfies the GeoJSON specifications for a point
GeoPosition
: an array with longitude, latitude, and an optional altitude
A GeoPoint is used only as a building block of the other shapes: GeoCircle, GeoBox, and GeoPolygon. These shapes, and the GeoPoint type, are used in queries, not for persistance.
To save geospatial data to realm, refer to Persist Geospatial Data.
A GeoCircle defines a circle on the Earth's surface. You define a GeoCircle
by providing:
A GeoPoint
for the center of the circle
A number for the distance (radius) of the circle
The radius distance uses radians as the unit of measure. The SDK provides the methods kmToRadians
and miToRadians
to convert kilometers or miles to radians.
The following code shows two examples of creating a circle:
const smallCircle = { center: [-121.9, 47.3], distance: 0.004363323,};const largeCircleCenter = { longitude: -122.6, latitude: 47.8,};const radiusFromKm = kmToRadians(44.4);const largeCircle = { center: largeCircleCenter, distance: radiusFromKm,};
const smallCircle: GeoCircle = { center: [-121.9, 47.3], distance: 0.004363323,};const largeCircleCenter: GeoPoint = { longitude: -122.6, latitude: 47.8,};const radiusFromKm = kmToRadians(44.4);const largeCircle: GeoCircle = { center: largeCircleCenter, distance: radiusFromKm,};
A GeoBox defines a rectangle on the Earth's surface. You define the rectangle by specifying the bottom left (southwest) corner and the top right (northeast) corner. A GeoBox behaves in the same way as the corresponding GeoPolygon.
The following example creates 2 boxes:
const largeBox = { bottomLeft: [-122.7, 47.3], topRight: [-122.1, 48.1],};const smallBoxBottomLeft = { longitude: -122.4, latitude: 47.5,};const smallBoxTopRight = { longitude: -121.8, latitude: 47.9,};const smallBox = { bottomLeft: smallBoxBottomLeft, topRight: smallBoxTopRight,};
const largeBox: GeoBox = { bottomLeft: [-122.7, 47.3], topRight: [-122.1, 48.1],};const smallBoxBottomLeft: GeoPoint = { longitude: -122.4, latitude: 47.5,};const smallBoxTopRight: GeoPoint = { longitude: -121.8, latitude: 47.9,};const smallBox: GeoBox = { bottomLeft: smallBoxBottomLeft, topRight: smallBoxTopRight,};
A GeoPolygon defines a polygon on the Earth's surface. Because a polygon is a closed shape, you must provide a minimum of 4 points: 3 points to define the polygon's shape, and a fourth to close the shape.
ImportantThe fourth point in a polygon must be the same as the first point.
You can also exclude areas within a polygon by defining one or more "holes". A hole is another polygon whose bounds fit completely within the outer polygon. The following example creates 3 polygons: one is a basic polygon with 5 points, one is the same polygon with a single hole, and the third is the same polygon with two holes:
const basicPolygon = { outerRing: [ [-122.8, 48.0], [-121.8, 48.2], [-121.6, 47.6], [-122.0, 47.0], [-122.6, 47.2], [-122.8, 48.0], ],};const outerRing = [ [-122.8, 48.0], [-121.8, 48.2], [-121.6, 47.6], [-122.0, 47.0], [-122.6, 47.2], [-122.8, 48.0],];const hole = [ [-122.6, 47.8], [-122.2, 47.7], [-122.6, 47.4], [-122.5, 47.6], [-122.6, 47.8],];const polygonWithOneHole = { outerRing: outerRing, holes: [hole],};const hole2 = [ { longitude: -122.05, latitude: 47.55, }, { longitude: -121.9, latitude: 47.55, }, { longitude: -122.1, latitude: 47.3, }, { longitude: -122.05, latitude: 47.55, },];const polygonWithTwoHoles = { outerRing: outerRing, holes: [hole, hole2],};
const basicPolygon: GeoPolygon = { outerRing: [ [-122.8, 48.0], [-121.8, 48.2], [-121.6, 47.6], [-122.0, 47.0], [-122.6, 47.2], [-122.8, 48.0], ],};const outerRing: GeoPoint[] = [ [-122.8, 48.0], [-121.8, 48.2], [-121.6, 47.6], [-122.0, 47.0], [-122.6, 47.2], [-122.8, 48.0],];const hole: GeoPoint[] = [ [-122.6, 47.8], [-122.2, 47.7], [-122.6, 47.4], [-122.5, 47.6], [-122.6, 47.8],];const polygonWithOneHole: GeoPolygon = { outerRing: outerRing, holes: [hole],};const hole2: GeoPoint[] = [ { longitude: -122.05, latitude: 47.55, }, { longitude: -121.9, latitude: 47.55, }, { longitude: -122.1, latitude: 47.3, }, { longitude: -122.05, latitude: 47.55, },];const polygonWithTwoHoles: GeoPolygon = { outerRing: outerRing, holes: [hole, hole2],};
Important Cannot Persist Geospatial Data Types
Currently, you can only persist geospatial data. Geospatial data types cannot be persisted directly. For example, you can't declare a property that is of type GeoBox
.
These types can only be used as arguments for geospatial queries.
If you want to persist geospatial data, it must conform to the GeoJSON spec .
To create a class that conforms to the GeoJSON spec, you:
Create an embedded realm object. For more information about embedded objects, refer to Embedded Objects - Node.js SDK.
At a minimum, add the two fields required by the GeoJSON spec:
A field of type double[]
that maps to a "coordinates" (case sensitive) property in the realm schema.
A field of type string
that maps to a "type" property. The value of this field must be "Point".
To simplify geodata persistance, you can define a model that implements CanonicalGeoPoint
, which already has the correct shape. The following example shows an embedded class named MyGeoPoint
that is used to persist geospatial data:
class MyGeoPoint { type = "Point"; constructor(long, lat) { this.coordinates = [long, lat]; } static schema = { name: "MyGeoPoint", embedded: true, properties: { type: "string", coordinates: "double[]", }, };}
class MyGeoPoint implements CanonicalGeoPoint { coordinates!: GeoPosition; type = "Point" as const; constructor(long: number, lat: number) { this.coordinates = [long, lat]; } static schema: ObjectSchema = { name: "MyGeoPoint", embedded: true, properties: { type: "string", coordinates: "double[]", }, };}
You then use the custom MyGeoPoint
class in your realm model, as shown in the following example:
class Company extends Realm.Object { static schema = { name: "Company", properties: { _id: "int", location: "MyGeoPoint", }, primaryKey: "_id", };}
class Company extends Realm.Object<Company> { _id!: number; location!: MyGeoPoint; static schema: ObjectSchema = { name: "Company", properties: { _id: "int", location: "MyGeoPoint", }, primaryKey: "_id", };}
You add instances of your class to the realm just like any other Realm model. However, in this example, because the MyGeoPoint
class does not extend Realm.Object
, we must specify MyGeoPoint.schema
when opening the realm:
const realm = await Realm.open({ schema: [Company, MyGeoPoint.schema],});realm.write(() => { realm.create(Company, { _id: 6, location: new MyGeoPoint(-122.35, 47.68), }); realm.create(Company, { _id: 9, location: new MyGeoPoint(-121.85, 47.9), });});
const realm = await Realm.open({ schema: [Company, MyGeoPoint.schema],});realm.write(() => { realm.create(Company, { _id: 6, location: new MyGeoPoint(-122.35, 47.68), }); realm.create(Company, { _id: 9, location: new MyGeoPoint(-121.85, 47.9), });});
The following image shows the results of creating these two company objects.
To query against geospatial data, you can use the geoWithin
operator with RQL. The geoWithin
operator takes the "coordinates" property of an embedded object that defines the point we're querying, and one of the geospatial shapes to check if that point is contained within the shape.
The format for querying geospatial data is the same, regardless of the shape of the geodata region.
The following examples show querying against various shapes to return a list of companies within the shape:
GeoCircle
const companiesInSmallCircle = realm .objects(Company) .filtered("location geoWithin $0", smallCircle);console.debug(`Companies in smallCircle: ${companiesInSmallCircle.length}`);const companiesInLargeCircle = realm .objects(Company) .filtered("location geoWithin $0", largeCircle);console.debug(`Companies in largeCircle: ${companiesInLargeCircle.length}`);
const companiesInSmallCircle = realm .objects(Company) .filtered("location geoWithin $0", smallCircle);console.debug(`Companies in smallCircle: ${companiesInSmallCircle.length}`);const companiesInLargeCircle = realm .objects(Company) .filtered("location geoWithin $0", largeCircle);console.debug(`Companies in largeCircle: ${companiesInLargeCircle.length}`);
GeoBox
const companiesInLargeBox = realm .objects(Company) .filtered("location geoWithin $0", largeBox);console.debug(`Companies in large box: ${companiesInLargeBox.length}`);const companiesInSmallBox = realm .objects(Company) .filtered("location geoWithin $0", smallBox);console.debug(`Companies in small box: ${companiesInSmallBox.length}`);
const companiesInLargeBox = realm .objects(Company) .filtered("location geoWithin $0", largeBox);console.debug(`Companies in large box: ${companiesInLargeBox.length}`);const companiesInSmallBox = realm .objects(Company) .filtered("location geoWithin $0", smallBox);console.debug(`Companies in small box: ${companiesInSmallBox.length}`);
GeoPolygon
const companiesInBasicPolygon = realm .objects(Company) .filtered("location geoWithin $0", basicPolygon);console.debug( `Companies in basic polygon: ${companiesInBasicPolygon.length}`);const companiesInPolygonWithTwoHoles = realm .objects(Company) .filtered("location geoWithin $0", polygonWithTwoHoles);console.debug( `Companies in polygon with two holes: ${companiesInPolygonWithTwoHoles.length}`);
const companiesInBasicPolygon = realm .objects(Company) .filtered("location geoWithin $0", basicPolygon);console.debug( `Companies in basic polygon: ${companiesInBasicPolygon.length}`);const companiesInPolygonWithTwoHoles = realm .objects(Company) .filtered("location geoWithin $0", polygonWithTwoHoles);console.debug( `Companies in polygon with two holes: ${companiesInPolygonWithTwoHoles.length}`);
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4