版本 12.0.0 中的新增内容。
地理空间数据或“地理数据”指定地球表面上的点和几何对象。 使用地理数据类型,您可以创建查询来检查给定点是否包含在形状中。 例如,您可以查找距离指定点15公里范围内的所有咖啡店。
在版本 12.3.0 中进行了更改: Atlas Device Sync 支持的地理空间数据
Realm Node.js SDK v12.3.0 及更高版本增加了对 Atlas Device Sync 中地理空间数据的支持。 这允许您订阅同步 Realm 中的地理空间查询。 如果您尝试使用旧版本的 SDK 订阅地理空间查询,您将收到带有补偿写入的服务器错误。 有关托管同步订阅的更多信息,请参阅托管同步订阅 - Node.js SDK。
有关使用Device Sync查询地理空间数据的更多信息,请参阅App Services文档中的地理空间数据。
地理空间数据类型
Node.js SDK 支持使用以下数据类型进行地理空间查询:
GeoPoint
GeoCircle
GeoBox
GeoPolygon
SDK 提供这些地理空间数据类型是为了简化地理空间数据的查询。 您无法直接持久化这些数据类型。
有关如何保存地理空间数据的信息,请参阅本页的保存地理空间数据部分。
GeoPoint
GeoPoint定义了地球表面的特定位置。 所有地理空间数据类型都使用 GeoPoints来定义其位置。
GeoPoint 可以是以下三种类型之一:
一个对象:
latitude:一个数值longitude:一个数值altitude:可选数值
CanonicalGeoPoint:满足点的 GeoJSON 规范的接口GeoPosition:具有经度、纬度和可选海拔高度的数组
GeoPoint 仅用作其他形状的构建基块:GeoCircle、GeoBox 和 GeoPolygon。 这些形状和 GeoPoint 类型用于查询,而不是用于持久性。
要将地理空间数据保存到 Realm,请参阅持久地理空间数据。
GeoCircle
GeoCircle定义了地球表面上的一个圆。 您可以通过提供以下内容来定义GeoCircle :
代表圆心的
GeoPoint表示圆的距离(半径)的数字
半径距离使用弧度作为测量单位。 SDK 提供了方法kmToRadians和miToRadians将公里或英里转换为弧度。
以下代码显示了创建圆形的两个示例:
const smallCircle = { center: [-121.9, 47.3], // The GeoCircle radius is measured in radians. // This radian distance corresponds with 0.25 degrees. distance: 0.004363323, }; const largeCircleCenter = { longitude: -122.6, latitude: 47.8, }; // Realm provides `kmToRadians` and `miToRadians` // to convert these measurements. Import the relevant // convenience method for your app's needs. const radiusFromKm = kmToRadians(44.4); const largeCircle = { center: largeCircleCenter, distance: radiusFromKm, };
const smallCircle: GeoCircle = { center: [-121.9, 47.3], // The GeoCircle radius is measured in radians. // This radian distance corresponds with 0.25 degrees. distance: 0.004363323, }; const largeCircleCenter: GeoPoint = { longitude: -122.6, latitude: 47.8, }; // Realm provides `kmToRadians` and `miToRadians` // to convert these measurements. Import the relevant // convenience method for your app's needs. const radiusFromKm = kmToRadians(44.4); const largeCircle: GeoCircle = { center: largeCircleCenter, distance: radiusFromKm, };

GeoBox
GeoBox在地球表面定义了一个矩形。 您可以通过指定左下(西南)角和右上角(东北)角来定义矩形。 GeoBox 的行为与相应的GeoPolygon 相同。
以下示例将创建2框:
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, };

GeoPolygon
GeoPolygon定义了地球表面上的多边形。 由于多边形是闭合形状,因此必须提供至少4个点: 3个点用于定义多边形的形状,以及第四个点用于闭合形状。
重要
多边形中的第四个点必须与第一个点相同。
您还可以通过定义一个或多个“孔”来排除多边形内的区域。 孔洞是指其边界完全位于外部多边形内的另一个多边形。 以下示例创建了 3 个多边形:第一个是具有 5 个点的基本多边形,第一个是具有单孔的相同多边形,第三个是具有两个孔的相同多边形:
// Create a basic polygon 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], ], }; // Create a polygon with one hole 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], }; // Add a second hole to the polygon 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], };
// Create a basic polygon 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], ], }; // Create a polygon with one hole 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], }; // Add a second hole to the polygon 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], };

保留地理空间数据
重要
无法保留地理空间数据类型
目前,您只能保留地理空间数据。 地理空间数据类型无法直接持久化。 例如,您不能声明类型为GeoBox的属性。
这些类型只能用作地理空间查询的参数。
如果要持久保存地理空间数据,则必须符合GeoJSON规范 。
创建与 GeoJSON 兼容的类
要创建符合 GeoJSON 规范的类,您需要:
创建一个嵌入式 Realm 对象。有关嵌入式对象的更多信息,请参阅嵌入式对象 - Node.js SDK。
至少添加 GeoJSON 规范要求的两个字段:
类型为
double[]的字段,映射到 Realm 模式中的“坐标”(区分大小写)属性。类型为
string的字段,映射到“类型”属性。 此字段的值必须是“Point”。
为了简化地理数据持久性,您可以定义一个实现CanonicalGeoPoint的模型,该模型已经具有正确的形状。 以下示例显示了一个名为MyGeoPoint的嵌入式类,用于保存地理空间数据:
class MyGeoPoint { type = "Point"; constructor(long, lat) { this.coordinates = [long, lat]; } static schema = { name: "MyGeoPoint", embedded: true, properties: { type: "string", coordinates: "double[]", }, }; }
// Implement `CanonicalGeoPoint` // for convenience when persisting geodata. 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[]", }, }; }
使用嵌入式类
然后,您可以在域模型中使用自定义MyGeoPoint类,如以下示例所示:
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", }; }
您可以将类的实例添加到 Realm,就像添加任何其他 Realm 模型一样。但是,在此示例中,由于MyGeoPoint类未扩展Realm.Object ,因此在打开域时必须指定MyGeoPoint.schema :
const realm = await Realm.open({ // `MyGeoPoint` does not extend `Realm.Object`, so you pass // only the `.schema` when opening the realm. schema: [Company, MyGeoPoint.schema], }); // Add geospatial object to realm. 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({ // `MyGeoPoint` does not extend `Realm.Object`, so you pass // only the `.schema` when opening the realm. schema: [Company, MyGeoPoint.schema], }); // Add geospatial object to realm. 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), }); });
下图显示了创建这两个公司对象的结果。

查询地理空间数据
要查询地理空间数据,可以将geoWithin操作符与RQL结合使用。 geoWithin操作符获取嵌入式对象的“坐标”属性(定义我们正在查询的点)和地理空间形状之一,以检查该点是否包含在该形状中。
注意
无论地理数据区域的形状如何,查询地理空间数据的格式都是相同的。
以下示例展示了如何查询各种形状以返回形状内的公司列表:
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}` );
