diff --git a/src/types.ts b/src/types.ts index 559eb72..4161942 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,6 +10,13 @@ import { Timestamp, } from "../deps.ts"; import { WriteConcern } from "./types/read_write_concern.ts"; +import { + $geoAny, + $geoMultiPolygon, + $geoPolygon, + CenterSpecifier, + ShapeOperator, +} from "./types/geospatial.ts"; export interface Server { host: string; @@ -645,10 +652,12 @@ interface FilterOperators extends Document { $jsonSchema?: Document; $mod?: TValue extends number ? [number, number] : never; $regex?: string | RegExp | BSONRegExp; - $geoIntersects?: { $geometry: Document }; - $geoWithin?: Document; - $near?: Document; - $nearSphere?: TValue; + $geoIntersects?: $geoAny; + $geoWithin?: $geoPolygon | $geoMultiPolygon | ShapeOperator; + $near?: CenterSpecifier; + $nearSphere?: CenterSpecifier; + $minDistance?: number; + $maxDistance?: number; // deno-lint-ignore no-explicit-any $all?: Array; // deno-lint-ignore no-explicit-any diff --git a/src/types/geojson.ts b/src/types/geojson.ts new file mode 100644 index 0000000..2e96f5a --- /dev/null +++ b/src/types/geojson.ts @@ -0,0 +1,203 @@ +// Note: +// Copied from the link below +// - https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/master/types/geojson/index.d.ts +// +// See also +// - https://www.npmjs.com/package/@types/geojson + +// Type definitions for non-npm package geojson 7946.0 +// Project: https://geojson.org/ +// Definitions by: Jacob Bruun +// Arne Schubert +// Jeff Jacobson +// Ilia Choly +// Dan Vanderkam +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.3 + +// Note: as of the RFC 7946 version of GeoJSON, Coordinate Reference Systems +// are no longer supported. (See https://tools.ietf.org/html/rfc7946#appendix-B)} + +// export as namespace GeoJSON; + +/** + * The valid values for the "type" property of GeoJSON geometry objects. + * https://tools.ietf.org/html/rfc7946#section-1.4 + */ +export type GeoJsonGeometryTypes = Geometry["type"]; + +/** + * The value values for the "type" property of GeoJSON Objects. + * https://tools.ietf.org/html/rfc7946#section-1.4 + */ +export type GeoJsonTypes = GeoJSON["type"]; + +/** + * Bounding box + * https://tools.ietf.org/html/rfc7946#section-5 + */ +export type BBox = [number, number, number, number] | [ + number, + number, + number, + number, + number, + number, +]; + +/** + * A Position is an array of coordinates. + * https://tools.ietf.org/html/rfc7946#section-3.1.1 + * Array should contain between two and three elements. + * The previous GeoJSON specification allowed more elements (e.g., which could be used to represent M values), + * but the current specification only allows X, Y, and (optionally) Z to be defined. + */ +export type Position = number[]; // [number, number] | [number, number, number]; + +/** + * The base GeoJSON object. + * https://tools.ietf.org/html/rfc7946#section-3 + * The GeoJSON specification also allows foreign members + * (https://tools.ietf.org/html/rfc7946#section-6.1) + * Developers should use "&" type in TypeScript or extend the interface + * to add these foreign members. + */ +export interface GeoJsonObject { + // Don't include foreign members directly into this type def. + // in order to preserve type safety. + // [key: string]: any; + /** + * Specifies the type of GeoJSON object. + */ + type: GeoJsonTypes; + /** + * Bounding box of the coordinate range of the object's Geometries, Features, or Feature Collections. + * The value of the bbox member is an array of length 2*n where n is the number of dimensions + * represented in the contained geometries, with all axes of the most southwesterly point + * followed by all axes of the more northeasterly point. + * The axes order of a bbox follows the axes order of geometries. + * https://tools.ietf.org/html/rfc7946#section-5 + */ + bbox?: BBox | undefined; +} + +/** + * Union of GeoJSON objects. + */ +export type GeoJSON = Geometry | Feature | FeatureCollection; + +/** + * Geometry object. + * https://tools.ietf.org/html/rfc7946#section-3 + */ +export type Geometry = + | Point + | MultiPoint + | LineString + | MultiLineString + | Polygon + | MultiPolygon + | GeometryCollection; +export type GeometryObject = Geometry; + +/** + * Point geometry object. + * https://tools.ietf.org/html/rfc7946#section-3.1.2 + */ +export interface Point extends GeoJsonObject { + type: "Point"; + coordinates: Position; +} + +/** + * MultiPoint geometry object. + * https://tools.ietf.org/html/rfc7946#section-3.1.3 + */ +export interface MultiPoint extends GeoJsonObject { + type: "MultiPoint"; + coordinates: Position[]; +} + +/** + * LineString geometry object. + * https://tools.ietf.org/html/rfc7946#section-3.1.4 + */ +export interface LineString extends GeoJsonObject { + type: "LineString"; + coordinates: Position[]; +} + +/** + * MultiLineString geometry object. + * https://tools.ietf.org/html/rfc7946#section-3.1.5 + */ +export interface MultiLineString extends GeoJsonObject { + type: "MultiLineString"; + coordinates: Position[][]; +} + +/** + * Polygon geometry object. + * https://tools.ietf.org/html/rfc7946#section-3.1.6 + */ +export interface Polygon extends GeoJsonObject { + type: "Polygon"; + coordinates: Position[][]; +} + +/** + * MultiPolygon geometry object. + * https://tools.ietf.org/html/rfc7946#section-3.1.7 + */ +export interface MultiPolygon extends GeoJsonObject { + type: "MultiPolygon"; + coordinates: Position[][][]; +} + +/** + * Geometry Collection + * https://tools.ietf.org/html/rfc7946#section-3.1.8 + */ +export interface GeometryCollection extends GeoJsonObject { + type: "GeometryCollection"; + geometries: Geometry[]; +} + +// deno-lint-ignore no-explicit-any +export type GeoJsonProperties = { [name: string]: any } | null; + +/** + * A feature object which contains a geometry and associated properties. + * https://tools.ietf.org/html/rfc7946#section-3.2 + */ +export interface Feature< + G extends Geometry | null = Geometry, + P = GeoJsonProperties, +> extends GeoJsonObject { + type: "Feature"; + /** + * The feature's geometry + */ + geometry: G; + /** + * A value that uniquely identifies this feature in a + * https://tools.ietf.org/html/rfc7946#section-3.2. + */ + id?: string | number | undefined; + /** + * Properties associated with this feature. + */ + properties: P; +} + +/** + * A collection of feature objects. + * https://tools.ietf.org/html/rfc7946#section-3.3 + */ +export interface FeatureCollection< + G extends Geometry | null = Geometry, + P = GeoJsonProperties, +> extends GeoJsonObject { + type: "FeatureCollection"; + features: Array>; +} diff --git a/src/types/geospatial.ts b/src/types/geospatial.ts new file mode 100644 index 0000000..90bc516 --- /dev/null +++ b/src/types/geospatial.ts @@ -0,0 +1,245 @@ +import { Document } from "../../deps.ts"; +import { + GeoJsonObject, + GeometryCollection, + GeometryObject, + LineString, + MultiLineString, + MultiPoint, + MultiPolygon, + Point, + Polygon, + Position, +} from "./geojson.ts"; + +/** + * https://www.mongodb.com/docs/manual/reference/operator/query/geometry/#mongodb-query-op.-geometry + */ +interface GeoJsonOperators { + $geometry: G & CoordinateReferenceSystem; +} + +/** + * https://datatracker.ietf.org/doc/html/rfc7946#section-4 + */ +interface CoordinateReferenceSystem { + crs?: { + type: string; + properties: { name: string }; + }; +} + +/** + * https://www.mongodb.com/docs/manual/reference/operator/query/minDistance/ + * https://www.mongodb.com/docs/manual/reference/operator/query/maxDistance/ + */ +export interface DistanceConstraint { + $minDistance?: number; + $maxDistance?: number; +} + +export type LegacyPoint = Position; + +/** + * Example: + * + * ```ts + * { + * $geometry: GeometryObject, // any GeoJSON object + * } + * ``` + */ +export type $geoAny = GeoJsonOperators; + +/** + * Example: + * + * ```ts + * { + * $geometry: { type: "Point", coordinates: [ 40, 5 ] }, + * } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/geojson/#point + */ +export type $geoPoint = GeoJsonOperators; + +/** + * Example: + * + * ```ts + * { + * $geometry: { type: "LineString", coordinates: [ [ 40, 5 ], [ 41, 6 ] ] } + * } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/geojson/#linestring + */ +export type $geoLineString = GeoJsonOperators; + +/** + * Example: + * + * ```ts + * { + * $geometry: { + * type: "Polygon", + * coordinates: [ [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ] ] + * }, + * } + * + * ``` + * https://www.mongodb.com/docs/manual/reference/geojson/#polygon + */ +export type $geoPolygon = GeoJsonOperators; + +/** + * Example: + * + * ```ts + * { + * $geometry: { + * type: "MultiPoint", + * coordinates: [ + * [ -73.9580, 40.8003 ], + * [ -73.9498, 40.7968 ], + * [ -73.9737, 40.7648 ], + * [ -73.9814, 40.7681 ] + * ] + * }, + * } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/geojson/#multipoint + */ +export type $geoMultiPoint = GeoJsonOperators; + +/** + * Example: + * + * ```ts + * { + * $geometry: { + * type: "MultiLineString", + * coordinates: [ + * [ [ -73.96943, 40.78519 ], [ -73.96082, 40.78095 ] ], + * [ [ -73.96415, 40.79229 ], [ -73.95544, 40.78854 ] ], + * [ [ -73.97162, 40.78205 ], [ -73.96374, 40.77715 ] ], + * [ [ -73.97880, 40.77247 ], [ -73.97036, 40.76811 ] ] + * ] + * } + * } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/geojson/#multilinestring + */ +export type $geoMultiLineString = GeoJsonOperators; + +/** + * Example: + * + * ```ts + * { + * $geometry: { + * type: "MultiPolygon", + * coordinates: [ + * [ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.9814, 40.7681 ], [ -73.958, 40.8003 ] ] ], + * [ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.958, 40.8003 ] ] ] + * ] + * }, + * } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/geojson/#multipolygon + */ +export type $geoMultiPolygon = GeoJsonOperators; + +/** + * Example: + * + * ```ts + * { + * $geometry: { + * type: "GeometryCollection", + * geometries: [ + * { + * type: "MultiPoint", + * coordinates: [ + * [ -73.9580, 40.8003 ], + * [ -73.9498, 40.7968 ], + * [ -73.9737, 40.7648 ], + * [ -73.9814, 40.7681 ] + * ] + * }, + * { + * type: "MultiLineString", + * coordinates: [ + * [ [ -73.96943, 40.78519 ], [ -73.96082, 40.78095 ] ], + * [ [ -73.96415, 40.79229 ], [ -73.95544, 40.78854 ] ], + * [ [ -73.97162, 40.78205 ], [ -73.96374, 40.77715 ] ], + * [ [ -73.97880, 40.77247 ], [ -73.97036, 40.76811 ] ] + * ] + * } + * ] + * } + * } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/geojson/#geometrycollection + */ +export type $geoCollection = GeoJsonOperators; + +/** + * Example: + * + * ```ts + * { $box: [ [ 0, 0 ], [ 100, 100 ] ] } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/operator/query/box/#-box + */ +export type $box = { $box: [LegacyPoint, LegacyPoint] }; + +/** + * Example: + * + * ```ts + * { $polygon: [ [ 0 , 0 ], [ 3 , 6 ], [ 6 , 0 ] ] } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/operator/query/polygon/#-polygon + */ +export type $polygon = { $polygon: LegacyPoint[] }; + +/** + * Example: + * + * ```ts + * { $center: [ [-74, 40.74], 10 ] } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/operator/query/center/#definition + */ +export type $center = { $center: [LegacyPoint, number] }; + +/** + * Example: + * + * ```ts + * { $centerSphere: [ [ -88, 30 ], 10/3963.2 ] } + * ``` + * + * https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/#-centersphere + */ +export type $centerSphere = { $centerSphere: [LegacyPoint, number] }; + +export type ShapeOperator = + | $box + | $polygon + | $center + | $centerSphere; + +export type CenterSpecifier = + | ($geoPoint & DistanceConstraint) + | LegacyPoint + | Document; diff --git a/tests/cases/09_geospatial_types.ts b/tests/cases/09_geospatial_types.ts new file mode 100644 index 0000000..0bfc919 --- /dev/null +++ b/tests/cases/09_geospatial_types.ts @@ -0,0 +1,881 @@ +import { Database } from "../../mod.ts"; +import { Collection } from "../../src/collection/collection.ts"; +import { + $box, + $center, + $centerSphere, + $geoCollection, + $geoLineString, + $geoMultiLineString, + $geoMultiPoint, + $geoMultiPolygon, + $geoPoint, + $geoPolygon, + $polygon, + CenterSpecifier, + DistanceConstraint, + LegacyPoint, + ShapeOperator, +} from "../../src/types/geospatial.ts"; +import { Geometry, GeometryObject, Point } from "../../src/types/geojson.ts"; +import { testWithClient } from "../common.ts"; +import { assert, assertEquals } from "../test.deps.ts"; + +interface IPlace { + _id: string; + name: string; + location: Point; + legacyLocation: [number, number]; // a testing field for legacy operators + legacyLocationDocument: { + lon: number; + lat: number; + }; +} + +interface INeighborhoods { + _id: string; + name: string; + geometry: GeometryObject; +} + +interface IPosition { + _id: string; + pos: [number, number]; +} + +// Test utility types +type LegacyNearQuery = { + $near: LegacyPoint; +} & DistanceConstraint; + +type LegacyNearSphereQuery = { + $nearSphere: LegacyPoint; +} & DistanceConstraint; + +type LegacyNearDocumentQuery = { + $near: { lon: number; lat: number }; +} & DistanceConstraint; + +type LegacyNearSphereDocumentQuery = { + $nearSphere: { lon: number; lat: number }; +} & DistanceConstraint; + +const placeDataString = await Deno.readTextFile( + "tests/testdata/sample_places.json", +); + +// deno-lint-ignore no-explicit-any +const placeData: IPlace[] = (JSON.parse(placeDataString) as any[]) + .map((el) => ({ + _id: el._id.$oid, + name: el.name, + location: el.location as Point, + legacyLocation: el.location.coordinates as [number, number], + legacyLocationDocument: { + lon: el.location.coordinates[0], + lat: el.location.coordinates[1], + }, + })); + +const neighborhoodsDataString = await Deno.readTextFile( + "tests/testdata/sample_neighborhoods.json", +); + +const neighborhoodsData: INeighborhoods[] = + // deno-lint-ignore no-explicit-any + (JSON.parse(neighborhoodsDataString) as any[]).map((item) => ({ + _id: item._id.$oid, + name: item.name, + geometry: item.geometry as Geometry, + })); + +Deno.test({ + name: "Geospatial: sanity tests for types", + fn: () => { + const geoPoint: $geoPoint = { + $geometry: { + type: "Point", + coordinates: [40, 5], + }, + }; + + const _geoLineString: $geoLineString = { + $geometry: { + type: "LineString", + coordinates: [[40, 5], [41, 6]], + }, + }; + + const _geoPolygon: $geoPolygon = { + $geometry: { + type: "Polygon", + coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]], + }, + }; + + const _geoMultiPoint: $geoMultiPoint = { + $geometry: { + type: "MultiPoint", + coordinates: [ + [-73.9580, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.9814, 40.7681], + ], + }, + }; + + const _geoMultiLineString: $geoMultiLineString = { + $geometry: { + type: "MultiLineString", + coordinates: [ + [[-73.96943, 40.78519], [-73.96082, 40.78095]], + [[-73.96415, 40.79229], [-73.95544, 40.78854]], + [[-73.97162, 40.78205], [-73.96374, 40.77715]], + [[-73.97880, 40.77247], [-73.97036, 40.76811]], + ], + }, + }; + + const _geoMultiPolygon: $geoMultiPolygon = { + $geometry: { + type: "MultiPolygon", + coordinates: [ + [ + [ + [-73.958, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.9814, 40.7681], + [-73.958, 40.8003], + ], + ], + [ + [ + [-73.958, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.958, 40.8003], + ], + ], + ], + }, + }; + + const _geoCollection: $geoCollection = { + $geometry: { + type: "GeometryCollection", + geometries: [ + { + type: "MultiPoint", + coordinates: [ + [-73.9580, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.9814, 40.7681], + ], + }, + { + type: "MultiLineString", + coordinates: [ + [[-73.96943, 40.78519], [-73.96082, 40.78095]], + [[-73.96415, 40.79229], [-73.95544, 40.78854]], + [[-73.97162, 40.78205], [-73.96374, 40.77715]], + [[-73.97880, 40.77247], [-73.97036, 40.76811]], + ], + }, + ], + }, + }; + + const box: $box = { $box: [[0, 0], [100, 100]] }; + const polygon: $polygon = { $polygon: [[0, 0], [3, 6], [6, 0]] }; + const center: $center = { $center: [[-74, 40.74], 10] }; + const centerSphere: $centerSphere = { + $centerSphere: [[-88, 30], 10 / 3963.2], + }; + + // union type tests + const _shapeOperator1: ShapeOperator = box; + const _shapeOperator2: ShapeOperator = polygon; + const _shapeOperator3: ShapeOperator = center; + const _shapeOperator4: ShapeOperator = centerSphere; + + const _centerSpecifier1: CenterSpecifier = geoPoint; + const _centerSpecifier2: CenterSpecifier = { ...geoPoint, $minDistance: 1 }; + const _centerSpecifier3: CenterSpecifier = { ...geoPoint, $maxDistance: 1 }; + const _centerSpecifier4: CenterSpecifier = { + ...geoPoint, + $minDistance: 1, + $maxDistance: 1, + }; + const _legacyPoint: CenterSpecifier = [0, 1]; + const _documentStylePoint: CenterSpecifier = { lon: 0, lat: 1 }; + }, +}); + +/** + * Sanity tests for geospatial queries. + * + * Tests are based on the summary table of geospatial query operators from the link below. + * https://www.mongodb.com/docs/manual/geospatial-queries/#geospatial-models + * + * Test data picked from below links + * https://www.mongodb.com/docs/manual/tutorial/geospatial-tutorial/#searching-for-restaurants + * + * Places + * https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/restaurants.json + * + * Neighborhoods + * https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/neighborhoods.json + */ +testWithClient( + "Geospatial: sanity tests for types by actual querying", + async (client) => { + const db = client.database("test"); + await test_$near_and_$nearSphere_queries(db); + await test_$geoWithin_queries(db); + await test_$geoIntersects(db); + await db.collection("mongo_test_places").drop().catch(console.error); + await db.collection("mongo_test_positions").drop().catch(console.error); + await db.collection("mongo_test_neighborhoods").drop().catch(console.error); + }, +); + +async function test_$near_and_$nearSphere_queries(db: Database) { + const placeCollection = db.collection("mongo_test_places"); + + await placeCollection.createIndexes({ + indexes: [ + // An 2dsphere index for `location` + { + name: "location_2dsphere", + key: { location: "2dsphere" }, + "2dsphereIndexVersion": 3, + }, + // An 2d index for `legacyLocation` + { + name: "legacyLocation_2d", + key: { legacyLocation: "2d" }, + }, + { + name: "legacyLocationDocument_2d", + key: { legacyLocationDocument: "2d" }, + }, + ], + }); + + await placeCollection.insertMany(placeData); + + const queries = [ + { + coordinates: [-73.856077, 40.848447], + }, + { + // with a $maxDistance contraint + coordinates: [-73.856077, 40.848447], + $maxDistance: 100, + }, + { + // with a $minDistance contraint + coordinates: [-73.856077, 40.848447], + $minDistance: 100, + }, + { + // GeoJSON with a $min/$max distance contraint + coordinates: [-73.856077, 40.848447], + $maxDistance: 100, + $minDistance: 10, + }, + ]; + + await testGeoJsonQueries(placeCollection, queries); + await testLegacyQueries(placeCollection, queries); +} + +async function testGeoJsonQueries( + placeCollection: Collection, + queries: ({ coordinates: number[] } & DistanceConstraint)[], +) { + const geoJsonQueries: ($geoPoint & DistanceConstraint)[] = queries.map( + (data) => { + const { coordinates, $maxDistance, $minDistance } = data; + const geoJsonQueryItem: $geoPoint & DistanceConstraint = { + $geometry: { + type: "Point", + coordinates, + }, + $minDistance, + $maxDistance, + }; + + return removeUndefinedDistanceConstraint(geoJsonQueryItem); + }, + ); + + for await (const geoQuery of geoJsonQueries) { + // with $near + await placeCollection.find({ + location: { + $near: geoQuery, + }, + }).toArray(); + + // with $nearSphere + await placeCollection.find({ + location: { + $nearSphere: geoQuery, + }, + }).toArray(); + } +} + +async function testLegacyQueries( + placeCollection: Collection, + queries: ({ coordinates: number[] } & DistanceConstraint)[], +) { + const legacyQueries: + ({ $near: LegacyPoint; $nearSphere: LegacyPoint } & DistanceConstraint)[] = + queries.map( + (data) => { + const { coordinates, $maxDistance, $minDistance } = data; + + const queryItem = { + $near: coordinates, + $nearSphere: coordinates, + $minDistance, + $maxDistance, + }; + + if ($maxDistance === undefined) { + delete queryItem["$maxDistance"]; + } + if ($minDistance === undefined) { + delete queryItem["$minDistance"]; + } + + return queryItem; + }, + ); + + for await (const query of legacyQueries) { + // with $near + await placeCollection.find({ + legacyLocation: query as LegacyNearQuery, + }).toArray(); + + // with $nearSphere + await placeCollection.find({ + legacyLocation: query as LegacyNearSphereQuery, + }).toArray(); + + const [lon, lat] = query.$near!; + const { $minDistance, $maxDistance } = query; + + const documentStyleQuery = removeUndefinedDistanceConstraint({ + $near: { lon, lat }, + $nearSphere: { lon, lat }, + $minDistance, + $maxDistance, + }); + + // with $near + await placeCollection.find({ + legacyLocationDocument: documentStyleQuery as LegacyNearDocumentQuery, + }).toArray(); + + // with $nearSphere + await placeCollection.find({ + legacyLocationDocument: + documentStyleQuery as LegacyNearSphereDocumentQuery, + }).toArray(); + } +} + +function removeUndefinedDistanceConstraint( + obj: T & DistanceConstraint, +): T & DistanceConstraint { + const result = { ...obj }; + const { $minDistance, $maxDistance } = obj; + + if ($minDistance === undefined) { + delete result["$minDistance"]; + } + + if ($maxDistance === undefined) { + delete result["$maxDistance"]; + } + + return result; +} + +async function test_$geoWithin_queries(db: Database) { + await test_$geoWithin_by_GeoJson_queries(db); + await test_$geoWithin_by_ShapeOperators(db); +} + +async function test_$geoWithin_by_GeoJson_queries(db: Database) { + const places = db.collection("mongo_test_places"); + + const foundPlacesByPolygon = await places.find({ + location: { + $geoWithin: { + $geometry: { + type: "Polygon", + coordinates: [ + [ + [-73.857, 40.848], + [-73.857, 40.849], + [-73.856, 40.849], + [-73.856, 40.848], + [-73.857, 40.848], + ], + ], + }, + }, + }, + }).toArray(); + + assert(foundPlacesByPolygon); + + // Manipulated the query so that there should be only one place, which is "Morris Park Bake Shop" + assertEquals(foundPlacesByPolygon.length, 1); + assertEquals(foundPlacesByPolygon[0].name, "Morris Park Bake Shop"); + + const foundPlacesByMultiPolygon = await places.find({ + location: { + $geoWithin: { + $geometry: { + type: "MultiPolygon", + coordinates: [ + [ + [ + [-73.958, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.9814, 40.7681], + [-73.958, 40.8003], + ], + ], + [ + [ + [-73.958, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.958, 40.8003], + ], + ], + ], + }, + }, + }, + }).toArray(); + + assert(foundPlacesByMultiPolygon); + + // Manipulated the places data so that there should be only one place, which is "Cafe1 & Cafe 4 (American Museum Of Natural History)" + assertEquals(foundPlacesByMultiPolygon.length, 1); + assertEquals( + foundPlacesByMultiPolygon[0].name, + "Cafe1 & Cafe 4 (American Museum Of Natural History)", + ); +} + +async function test_$geoWithin_by_ShapeOperators(db: Database) { + const positions = db.collection("mongo_test_positions"); + + await positions.createIndexes({ + indexes: [ + // An 2d index for `pos` + { + name: "pos_2d", + key: { pos: "2d" }, + }, + ], + }); + + const dataToInsert: Omit[] = []; + const xs = [-1, 0, 1]; + const ys = [-1, 0, 1]; + + for (const x of xs) { + for (const y of ys) { + dataToInsert.push({ pos: [x, y] }); + } + } + + await positions.insertMany(dataToInsert); + + await test_$geoWithin_by_$box(positions); + await test_$geoWithin_by_$polygon(positions); + await test_$geoWithin_by_$center(positions); + await test_$geoWithin_by_$centerSphere(positions); +} + +async function test_$geoWithin_by_$box(positions: Collection) { + const foundPositions = await positions.find({ + pos: { + $geoWithin: { + $box: [ + [-1, -1], // bottom left + [1, 1], // upper right + ], + }, + }, + }).toArray(); + + assert(foundPositions); + assertEquals(foundPositions.length, 9); +} + +async function test_$geoWithin_by_$polygon(positions: Collection) { + const foundPositions = await positions.find({ + pos: { + $geoWithin: { + $polygon: [[-1, 0], [0, 1], [1, 0], [0, -1]], // a diamond shaped polygon + }, + }, + }).toArray(); + + assert(foundPositions); + assertEquals(foundPositions.length, 5); +} + +async function test_$geoWithin_by_$center(positions: Collection) { + const foundPositions = await positions.find({ + pos: { + $geoWithin: { + $center: [[0, 0], 1], // a circle with radius 1 + }, + }, + }).toArray(); + + assert(foundPositions); + assertEquals(foundPositions.length, 5); +} + +async function test_$geoWithin_by_$centerSphere( + positions: Collection, +) { + const foundPositions = await positions.find({ + pos: { + $geoWithin: { + $centerSphere: [[0, 0], 0.0174535], // a sphere with 0.0174535 radian + }, + }, + }).toArray(); + + assert(foundPositions); + // 0.0174535 radian is a bit greater than 1.0, so it covers 5 points in the coordinates + assertEquals(foundPositions.length, 5); +} + +async function test_$geoIntersects(db: Database) { + const neighborhoods = db.collection( + "mongo_test_neighborhoods", + ); + + await neighborhoods.createIndexes({ + indexes: [ + // An 2dsphere index for `geometry` + { + name: "geometry_2dsphere", + key: { geometry: "2dsphere" }, + "2dsphereIndexVersion": 3, + }, + ], + }); + + await neighborhoods.insertMany(neighborhoodsData); + + const intersectionByPoint = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + "type": "Point", + "coordinates": [-73.95095412329623, 40.77543392621753], + }, + }, + }, + }).toArray(); + + assert(intersectionByPoint); + assertEquals(intersectionByPoint.length, 1); + assertEquals(intersectionByPoint[0].name, "Yorkville"); + + const intersectionByLineString = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "LineString", + coordinates: [ + [-73.95852104926365, 40.77889702821282], + [-73.95095412329623, 40.77543392621753], + ], + }, + }, + }, + }).toArray(); + + assert(intersectionByLineString); + assertEquals(intersectionByLineString.length, 1); + assertEquals(intersectionByLineString[0].name, "Yorkville"); + + const intersectionByPolygon = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "Polygon", + coordinates: [ + [ + [ + -73.95852104926365, + 40.77889702821282, + ], + [ + -73.95095412329623, + 40.77543392621753, + ], + [ + -73.95296019452276, + 40.779724262361626, + ], + [ + -73.95605545882601, + 40.77954043344108, + ], + [ + -73.95852104926365, + 40.77889702821282, + ], + ], + ], + }, + }, + }, + }).toArray(); + + assert(intersectionByPolygon); + assertEquals(intersectionByPolygon.length, 1); + assertEquals(intersectionByPolygon[0].name, "Yorkville"); + + const intersectionByMultiPoint = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "MultiPoint", + coordinates: [ + [ + -73.95852104926365, + 40.77889702821282, + ], + [ + -73.95095412329623, + 40.77543392621753, + ], + [ + -73.95296019452276, + 40.779724262361626, + ], + [ + -73.95605545882601, + 40.77954043344108, + ], + [ + -73.95852104926365, + 40.77889702821282, + ], + ], + }, + }, + }, + }).toArray(); + + assert(intersectionByMultiPoint); + assertEquals(intersectionByMultiPoint.length, 1); + assertEquals(intersectionByMultiPoint[0].name, "Yorkville"); + + const intersectionByMultiLineString = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "MultiLineString", + coordinates: [ + [ + [ + -73.95852104926365, + 40.77889702821282, + ], + [ + -73.95095412329623, + 40.77543392621753, + ], + ], + [ + [ + -73.95605545882601, + 40.77954043344108, + ], + [ + -73.95296019452276, + 40.779724262361626, + ], + ], + ], + }, + }, + }, + }).toArray(); + + assert(intersectionByMultiLineString); + assertEquals(intersectionByMultiLineString.length, 1); + assertEquals(intersectionByMultiLineString[0].name, "Yorkville"); + + const intersectionByMultiPolygon = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "MultiPolygon", + coordinates: [ + [ + [ + [ + -73.958, + 40.8003, + ], + [ + -73.9737, + 40.7648, + ], + [ + -73.9498, + 40.7968, + ], + [ + -73.958, + 40.8003, + ], + ], + ], + [ + [ + [ + -73.95852104926365, + 40.77889702821282, + ], + [ + -73.95095412329623, + 40.77543392621753, + ], + [ + -73.95296019452276, + 40.779724262361626, + ], + [ + -73.95605545882601, + 40.77954043344108, + ], + [ + -73.95852104926365, + 40.77889702821282, + ], + ], + ], + ], + }, + }, + }, + }).toArray(); + + assert(intersectionByMultiPolygon); + assertEquals(intersectionByMultiPolygon.length, 1); + assertEquals(intersectionByMultiPolygon[0].name, "Yorkville"); + + const intersectionByCollection = await neighborhoods.find( + { + geometry: { + $geoIntersects: { + $geometry: { + type: "GeometryCollection", + geometries: [ + { + type: "Point", + coordinates: [-73.95095412329623, 40.77543392621753], + }, + { + type: "MultiPoint", + coordinates: [ + [ + -73.958, + 40.8003, + ], + [ + -73.9498, + 40.7968, + ], + [ + -73.9737, + 40.7648, + ], + [ + -73.9814, + 40.7681, + ], + ], + }, + { + type: "MultiLineString", + coordinates: [ + [ + [ + -73.96943, + 40.78519, + ], + [ + -73.96082, + 40.78095, + ], + ], + [ + [ + -73.96415, + 40.79229, + ], + [ + -73.95544, + 40.78854, + ], + ], + [ + [ + -73.97162, + 40.78205, + ], + [ + -73.96374, + 40.77715, + ], + ], + [ + [ + -73.9788, + 40.77247, + ], + [ + -73.97036, + 40.76811, + ], + ], + ], + }, + ], + }, + }, + }, + }, + ).toArray(); + + assert(intersectionByCollection); + assertEquals(intersectionByCollection.length, 1); + assertEquals(intersectionByCollection[0].name, "Yorkville"); +} diff --git a/tests/cases/99_cleanup.ts b/tests/cases/99_cleanup.ts index 0212f26..6d339e1 100644 --- a/tests/cases/99_cleanup.ts +++ b/tests/cases/99_cleanup.ts @@ -5,6 +5,9 @@ testWithClient("cleanup", async (client) => { const db = client.database("test"); try { await db.collection("mongo_test_users").drop().catch((e) => e); + await db.collection("mongo_test_places").drop().catch((e) => e); + await db.collection("mongo_test_positions").drop().catch((e) => e); + await db.collection("mongo_test_neighborhoods").drop().catch((e) => e); await new GridFSBucket(db, { bucketName: "deno_logo" }) .drop().catch((e) => e); await new GridFSBucket(db, { bucketName: "echo" }) diff --git a/tests/test.ts b/tests/test.ts index cd67983..bad3542 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -7,4 +7,5 @@ import "./cases/05_srv.ts"; import "./cases/06_gridfs.ts"; import "./cases/07_worker.ts"; import "./cases/08_find_cursor.ts"; +import "./cases/09_geospatial_types.ts"; import "./cases/99_cleanup.ts"; diff --git a/tests/testdata/sample_neighborhoods.json b/tests/testdata/sample_neighborhoods.json new file mode 100644 index 0000000..d7b7079 --- /dev/null +++ b/tests/testdata/sample_neighborhoods.json @@ -0,0 +1,958 @@ +[ + { + "_id": { + "$oid": "55cb9c666c522cafdb053a1a" + }, + "name": "Bedford", + "geometry": { + "coordinates": [ + [ + [ + -73.94193078816193, + 40.70072523469547 + ], + [ + -73.94220058705264, + 40.700890667467746 + ], + [ + -73.94306406845838, + 40.7014244350918 + ], + [ + -73.94322686012315, + 40.701520709145726 + ], + [ + -73.9438247374114, + 40.701862007878276 + ], + [ + -73.94400504048726, + 40.70196179219718 + ], + [ + -73.94431460096804, + 40.70213334535181 + ], + [ + -73.94463910154856, + 40.70231369467456 + ], + [ + -73.94544988192177, + 40.702760635974364 + ], + [ + -73.9458549904679, + 40.70298720677488 + ], + [ + -73.94625107780892, + 40.70320874745355 + ], + [ + -73.94705205297524, + 40.70366394934019 + ], + [ + -73.94753858146478, + 40.703350650664795 + ], + [ + -73.9493787354337, + 40.70215888982628 + ], + [ + -73.95027424109588, + 40.70157924195056 + ], + [ + -73.95128819434734, + 40.70092236548591 + ], + [ + -73.951920189279, + 40.70148754916077 + ], + [ + -73.95255052777945, + 40.7020516665144 + ], + [ + -73.95318085172319, + 40.70261690547745 + ], + [ + -73.9538119690652, + 40.70318097979544 + ], + [ + -73.95572361014881, + 40.70194576955721 + ], + [ + -73.95745736372834, + 40.70082260318457 + ], + [ + -73.95722517405626, + 40.69999935002626 + ], + [ + -73.957167301054, + 40.69970786791901 + ], + [ + -73.95701993123406, + 40.698973914349565 + ], + [ + -73.95795938220984, + 40.69882000321581 + ], + [ + -73.95885874627406, + 40.6986773264162 + ], + [ + -73.96019688857467, + 40.698462727438596 + ], + [ + -73.96105100700007, + 40.698326078819065 + ], + [ + -73.96092543804184, + 40.69773650701631 + ], + [ + -73.96062056531659, + 40.6963201401926 + ], + [ + -73.96015854658333, + 40.69411730915604 + ], + [ + -73.95931047927598, + 40.69421508783189 + ], + [ + -73.95799732979468, + 40.694365838684114 + ], + [ + -73.9570870194244, + 40.694470440162995 + ], + [ + -73.95614239268207, + 40.69457901857237 + ], + [ + -73.95582662769675, + 40.69299238288233 + ], + [ + -73.95541057949602, + 40.690908291885876 + ], + [ + -73.95635602958276, + 40.69079978191732 + ], + [ + -73.95727249112613, + 40.69069347835403 + ], + [ + -73.95808563435828, + 40.69060146372528 + ], + [ + -73.9581804874994, + 40.69059073040573 + ], + [ + -73.95857844775936, + 40.690545694857576 + ], + [ + -73.95886713625437, + 40.69051224801476 + ], + [ + -73.95899330491858, + 40.69049762936284 + ], + [ + -73.95928133730644, + 40.69046425696955 + ], + [ + -73.95934862389824, + 40.6904564609108 + ], + [ + -73.95942791320554, + 40.69044727471387 + ], + [ + -73.95957591847137, + 40.69042998753855 + ], + [ + -73.96008336800442, + 40.6903707157072 + ], + [ + -73.96013760800457, + 40.69036438035883 + ], + [ + -73.96029281668112, + 40.690346249915414 + ], + [ + -73.96023740336433, + 40.69006222280781 + ], + [ + -73.96022304539724, + 40.689988627383755 + ], + [ + -73.96018691858275, + 40.689803455988546 + ], + [ + -73.96017256138677, + 40.68972986156118 + ], + [ + -73.96012172912181, + 40.68946930706387 + ], + [ + -73.96009714565346, + 40.689345210097464 + ], + [ + -73.96000519802635, + 40.688881033718204 + ], + [ + -73.95985939425704, + 40.688147451217226 + ], + [ + -73.95971374756459, + 40.6874156340909 + ], + [ + -73.95956770121337, + 40.68668255592727 + ], + [ + -73.95684165193596, + 40.68699607883792 + ], + [ + -73.95468418850508, + 40.68724485443714 + ], + [ + -73.95453798607406, + 40.68651117540455 + ], + [ + -73.95439296867414, + 40.685779720013606 + ], + [ + -73.95424647696164, + 40.68504624826183 + ], + [ + -73.95410042574005, + 40.684313107633436 + ], + [ + -73.95395453033524, + 40.68358077882069 + ], + [ + -73.95380893530668, + 40.68284800827331 + ], + [ + -73.95366256227194, + 40.68211490361348 + ], + [ + -73.95351616791015, + 40.68138260047889 + ], + [ + -73.95337017508861, + 40.68064050844431 + ], + [ + -73.95155682676496, + 40.680498847575564 + ], + [ + -73.95115828512961, + 40.68047861480679 + ], + [ + -73.9495568871113, + 40.68039040292329 + ], + [ + -73.9477302355664, + 40.680291846282316 + ], + [ + -73.94674915979888, + 40.680239661363046 + ], + [ + -73.94627470970092, + 40.68021332692951 + ], + [ + -73.94397347805013, + 40.680088128426995 + ], + [ + -73.94326176928655, + 40.68005060712657 + ], + [ + -73.94120864299748, + 40.67993835375214 + ], + [ + -73.94032793962053, + 40.67988997506463 + ], + [ + -73.94047635005941, + 40.680635695422964 + ], + [ + -73.94062005382186, + 40.68137013010259 + ], + [ + -73.94076893329961, + 40.68210083903887 + ], + [ + -73.9409133427312, + 40.682833617234294 + ], + [ + -73.94105783787931, + 40.68356687284592 + ], + [ + -73.94120399291621, + 40.68429983923358 + ], + [ + -73.94134827184915, + 40.685031202512505 + ], + [ + -73.94149491757346, + 40.68576452882908 + ], + [ + -73.94163933150469, + 40.68649727009258 + ], + [ + -73.94178527584324, + 40.687228372121126 + ], + [ + -73.9419324508184, + 40.687962958755094 + ], + [ + -73.94207684924385, + 40.68869720298344 + ], + [ + -73.94222203471806, + 40.68942797886745 + ], + [ + -73.94236932748694, + 40.690159944665304 + ], + [ + -73.94251587928605, + 40.69089200097073 + ], + [ + -73.94266181801652, + 40.69162434435983 + ], + [ + -73.94280765181726, + 40.692357794128945 + ], + [ + -73.94295136131598, + 40.69309078423585 + ], + [ + -73.94310040895432, + 40.69382302905847 + ], + [ + -73.94311427813774, + 40.693894720557466 + ], + [ + -73.94312826743185, + 40.693967038330925 + ], + [ + -73.943242490861, + 40.694557485733355 + ], + [ + -73.94338802084431, + 40.69528899051899 + ], + [ + -73.94352527471477, + 40.69603085523812 + ], + [ + -73.94354024149403, + 40.6961081421151 + ], + [ + -73.9435563415296, + 40.69619128295102 + ], + [ + -73.94362121369004, + 40.696526279661654 + ], + [ + -73.94363806934868, + 40.69661331854307 + ], + [ + -73.9436842703752, + 40.69685189440415 + ], + [ + -73.94368427322361, + 40.696851909818065 + ], + [ + -73.9437245356891, + 40.697059812179496 + ], + [ + -73.94374306706803, + 40.69715549995503 + ], + [ + -73.94378455587042, + 40.6973697290538 + ], + [ + -73.94380383211836, + 40.697469265449826 + ], + [ + -73.94391750192877, + 40.69805620211356 + ], + [ + -73.94394947271304, + 40.69822127983908 + ], + [ + -73.94409591260093, + 40.69897295461309 + ], + [ + -73.94424286147482, + 40.69969927964773 + ], + [ + -73.9443878859649, + 40.70042452378256 + ], + [ + -73.94193078816193, + 40.70072523469547 + ] + ] + ], + "type": "Polygon" + } + }, + { + "_id": { + "$oid": "55cb9c666c522cafdb053a32" + }, + "name": "Yorkville", + "geometry": { + "coordinates": [ + [ + [ + [ + -73.93804640603437, + 40.78082954427551 + ], + [ + -73.93806723089932, + 40.78094470269807 + ], + [ + -73.93804948921758, + 40.78105580604244 + ], + [ + -73.93796757721647, + 40.78128933406025 + ], + [ + -73.93808213268761, + 40.781418437274596 + ], + [ + -73.93830097951646, + 40.78136208436499 + ], + [ + -73.93874423089275, + 40.78104387604222 + ], + [ + -73.93882863410155, + 40.7807634507276 + ], + [ + -73.93883951845037, + 40.78059924979984 + ], + [ + -73.93872802926595, + 40.78051074370458 + ], + [ + -73.9392178962528, + 40.780013520221424 + ], + [ + -73.93930528496779, + 40.77995122455209 + ], + [ + -73.93937515379675, + 40.7799787718492 + ], + [ + -73.93949329752056, + 40.77987369131541 + ], + [ + -73.93943238100773, + 40.77981220027457 + ], + [ + -73.93958378972476, + 40.77957647400713 + ], + [ + -73.93953015793254, + 40.77952762026046 + ], + [ + -73.939318219876, + 40.77971800182686 + ], + [ + -73.9392374617828, + 40.77973339355579 + ], + [ + -73.93861200821945, + 40.780338955868935 + ], + [ + -73.93839622591737, + 40.78026243339117 + ], + [ + -73.938237735094, + 40.780259079465324 + ], + [ + -73.93779269036344, + 40.78035790421231 + ], + [ + -73.93759894622622, + 40.78046784086144 + ], + [ + -73.9376536596381, + 40.78079000755278 + ], + [ + -73.93764631821483, + 40.78100121589283 + ], + [ + -73.93779944179562, + 40.781031339796634 + ], + [ + -73.93779448726416, + 40.780781258755425 + ], + [ + -73.93790133623939, + 40.78074999617253 + ], + [ + -73.93804640603437, + 40.78082954427551 + ] + ] + ], + [ + [ + [ + -73.94383256676022, + 40.782859089475245 + ], + [ + -73.9438932453981, + 40.78288772733219 + ], + [ + -73.943954601631, + 40.78291552015379 + ], + [ + -73.94405439446334, + 40.78296435804619 + ], + [ + -73.94464665991993, + 40.78321787330016 + ], + [ + -73.94472264526928, + 40.78324757264143 + ], + [ + -73.94706938226709, + 40.78423622711466 + ], + [ + -73.94933170560954, + 40.78519312655759 + ], + [ + -73.94982390409011, + 40.78451557104566 + ], + [ + -73.95028254920818, + 40.78389046999264 + ], + [ + -73.95073913807909, + 40.78326171027431 + ], + [ + -73.95119927339022, + 40.78263339296121 + ], + [ + -73.95165920749085, + 40.78200767416535 + ], + [ + -73.95212203271687, + 40.781386710220794 + ], + [ + -73.95257421938265, + 40.780753034022965 + ], + [ + -73.95303367951709, + 40.78012545503866 + ], + [ + -73.95349212383219, + 40.779496622676206 + ], + [ + -73.9539837022136, + 40.77882211062083 + ], + [ + -73.95448224114962, + 40.778138665021665 + ], + [ + -73.95494520074166, + 40.77750394666421 + ], + [ + -73.95540729761609, + 40.77686847989678 + ], + [ + -73.9558693368217, + 40.77623622584272 + ], + [ + -73.9563349232549, + 40.77560078338051 + ], + [ + -73.95679749082419, + 40.77496631066816 + ], + [ + -73.9572941004381, + 40.77428355507528 + ], + [ + -73.95505481622959, + 40.773336851176026 + ], + [ + -73.95268752458824, + 40.77234302098273 + ], + [ + -73.95043175557528, + 40.77138973153775 + ], + [ + -73.94807387944711, + 40.77039439466542 + ], + [ + -73.94779385491792, + 40.77024915206164 + ], + [ + -73.94771625089346, + 40.77021606808562 + ], + [ + -73.94766538436596, + 40.77019325312954 + ], + [ + -73.94761452955883, + 40.770170429444896 + ], + [ + -73.94752257888095, + 40.77012969528945 + ], + [ + -73.94748975456329, + 40.7701151545216 + ], + [ + -73.94690764133767, + 40.77073391172354 + ], + [ + -73.94640418919069, + 40.77126904561922 + ], + [ + -73.94561514012553, + 40.771943026085076 + ], + [ + -73.94487889596748, + 40.77256956150469 + ], + [ + -73.9444243359585, + 40.77304532251584 + ], + [ + -73.94396084370877, + 40.773530420091916 + ], + [ + -73.94366431814802, + 40.773840764176796 + ], + [ + -73.9433389475049, + 40.77421119259063 + ], + [ + -73.94293043781397, + 40.774676268035805 + ], + [ + -73.9425876964068, + 40.77521895652805 + ], + [ + -73.94240100949048, + 40.775726927526115 + ], + [ + -73.94208160457376, + 40.77595701631703 + ], + [ + -73.94200266722274, + 40.77618531738269 + ], + [ + -73.94207418803877, + 40.77691784697707 + ], + [ + -73.94209312757741, + 40.77696471695027 + ], + [ + -73.94212060092353, + 40.77700907806879 + ], + [ + -73.94215605522935, + 40.77705003762475 + ], + [ + -73.94219877703716, + 40.777086771360594 + ], + [ + -73.94224790663611, + 40.77711854005664 + ], + [ + -73.9423024553623, + 40.777144704407284 + ], + [ + -73.94236132549445, + 40.77716473788681 + ], + [ + -73.94232881653734, + 40.777242655641246 + ], + [ + -73.94243848174503, + 40.777315235765954 + ], + [ + -73.94245740990844, + 40.77740747334762 + ], + [ + -73.94265530676857, + 40.77812496557382 + ], + [ + -73.94289340818793, + 40.7786140932461 + ], + [ + -73.94253556320852, + 40.77909095606242 + ], + [ + -73.94271237476218, + 40.7792148569398 + ], + [ + -73.94262799584376, + 40.77932311896862 + ], + [ + -73.94271600545089, + 40.77954416947608 + ], + [ + -73.94278066641971, + 40.77950615406814 + ], + [ + -73.94300423950467, + 40.77963949547416 + ], + [ + -73.94321386265224, + 40.77931758865997 + ], + [ + -73.94360047475412, + 40.78015909946616 + ], + [ + -73.94371468850564, + 40.78062981552884 + ], + [ + -73.94387075988706, + 40.78127302657157 + ], + [ + -73.94388068102455, + 40.78153204336433 + ], + [ + -73.94364823539011, + 40.78265616133325 + ], + [ + -73.9435442071252, + 40.78288052382262 + ], + [ + -73.9435697971985, + 40.782923078573404 + ], + [ + -73.94363201727829, + 40.78296232368537 + ], + [ + -73.9437600527451, + 40.78282893171711 + ], + [ + -73.94383256676022, + 40.782859089475245 + ] + ] + ] + ], + "type": "MultiPolygon" + } + } +] diff --git a/tests/testdata/sample_places.json b/tests/testdata/sample_places.json new file mode 100644 index 0000000..1fe87b2 --- /dev/null +++ b/tests/testdata/sample_places.json @@ -0,0 +1,158 @@ +[ + { + "_id": { + "$oid": "55cba2476c522cafdb053add" + }, + "location": { + "coordinates": [ + -73.856077, + 40.848447 + ], + "type": "Point" + }, + "name": "Morris Park Bake Shop" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ade" + }, + "location": { + "coordinates": [ + -73.961704, + 40.662942 + ], + "type": "Point" + }, + "name": "Wendy'S" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053adf" + }, + "location": { + "coordinates": [ + -73.98241999999999, + 40.579505 + ], + "type": "Point" + }, + "name": "Riviera Caterer" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ae0" + }, + "location": { + "coordinates": [ + -73.8601152, + 40.7311739 + ], + "type": "Point" + }, + "name": "Tov Kosher Kitchen" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ae1" + }, + "location": { + "coordinates": [ + -73.8803827, + 40.7643124 + ], + "type": "Point" + }, + "name": "Brunos On The Boulevard" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ae2" + }, + "location": { + "coordinates": [ + -73.98513559999999, + 40.7676919 + ], + "type": "Point" + }, + "name": "Dj Reynolds Pub And Restaurant" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ae3" + }, + "location": { + "coordinates": [ + -73.9068506, + 40.6199034 + ], + "type": "Point" + }, + "name": "Wilken'S Fine Food" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ae4" + }, + "location": { + "coordinates": [ + -74.00528899999999, + 40.628886 + ], + "type": "Point" + }, + "name": "Regina Caterers" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ae5" + }, + "location": { + "coordinates": [ + -73.9482609, + 40.6408271 + ], + "type": "Point" + }, + "name": "Taste The Tropics Ice Cream" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ae6" + }, + "location": { + "coordinates": [ + -74.1377286, + 40.6119572 + ], + "type": "Point" + }, + "name": "Kosher Island" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb053ae7" + }, + "location": { + "coordinates": [ + -73.8786113, + 40.8502883 + ], + "type": "Point" + }, + "name": "Wild Asia" + }, + { + "_id": { + "$oid": "55cba2476c522cafdb058a57" + }, + "location": { + "coordinates": [ + -73.9653551, + 40.7828647 + ], + "type": "Point" + }, + "name": "Cafe1 & Cafe 4 (American Museum Of Natural History)" + } +]