docs: point field and near query

This commit is contained in:
Dan Ribbens
2021-08-25 16:55:47 -04:00
parent c187da00b1
commit 34630757b9
9 changed files with 82 additions and 7 deletions

View File

@@ -1,8 +1,8 @@
/* eslint-disable no-param-reassign */
import { CollectionConfig } from '../../src/collections/config/types';
const validateFieldTransformAction = (hook: string, value = null) => {
if (value !== null && !Array.isArray(value)) {
const validateFieldTransformAction = (hook: string, value) => {
if (value !== undefined && value !== null && !Array.isArray(value)) {
console.error(hook, value);
throw new Error('Field transformAction should convert value to array [x, y] and not { coordinates: [x, y] }');
}
@@ -68,6 +68,12 @@ const Geolocation: CollectionConfig = {
type: 'point',
label: 'Localized Point',
localized: true,
hooks: {
beforeValidate: [({ value }) => validateFieldTransformAction('beforeValidate', value)],
beforeChange: [({ value }) => validateFieldTransformAction('beforeChange', value)],
afterChange: [({ value }) => validateFieldTransformAction('afterChange', value)],
afterRead: [({ value }) => validateFieldTransformAction('afterRead', value)],
},
},
],
};

View File

@@ -5,7 +5,7 @@ import { Block } from '../../src/fields/config/types';
const validateLocalizationTransform = (hook: string, value, req: PayloadRequest) => {
if (req.locale !== 'all' && value !== undefined && typeof value !== 'string') {
console.error(hook, value);
throw new Error('Field text transformation in hook is wonky');
throw new Error('Locale transformation should happen before hook is called');
}
return value;
};

View File

@@ -41,6 +41,7 @@ const Pages = {
- [Email](/docs/fields/email) - validates the entry is a properly formatted email
- [Group](/docs/fields/group) - nest fields within an object
- [Number](/docs/fields/number) - field that enforces that its value be a number
- [Point](/docs/fields/point) - geometric coordinates for location data
- [Radio](/docs/fields/radio) - radio button group, allowing only one value to be selected
- [Relationship](/docs/fields/relationship) - assign relationships to other collections
- [Rich Text](/docs/fields/rich-text) - fully extensible Rich Text editor

54
docs/fields/point.mdx Normal file
View File

@@ -0,0 +1,54 @@
---
title: Point Field
label: Point
order: 95
desc: The Point field type stores coordinates in the database. Learn how to use Point field for geolocation and geometry.
keywords: point, geolocation, geospatial, geojson, 2dsphere, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
---
<Banner >
The Point field type saves a pair of coordinates in the database and assigns an index for location related queries.
</Banner>
The data structure in the database matches the GeoJSON structure to represent point. The Payload APIs simplifies the object data to only the [x, y] location.
### Config
| Option | Description |
| ---------------- | ----------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. |
| **`label`** | Used as a field label in the Admin panel and to name the generated GraphQL type. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build a [MongoDB index](https://docs.mongodb.com/manual/indexes/) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
*\* An asterisk denotes that a property is required.*
### Example
`collections/ExampleCollection.js`
```js
{
slug: 'example-collection',
fields: [
{
name: 'location',
type: 'point',
label: 'Location',
},
]
}
```
### Querying
In order to do query based on the distance to another point, you can use the `near` operator. When querying using the near operator, the returned documents will be sorted by nearest first.

View File

@@ -63,6 +63,7 @@ The above example demonstrates a simple query but you can get much more complex.
| `in` | The value must be found within the provided comma-delimited list of values. |
| `not_in` | The value must NOT be within the provided comma-delimited list of values. |
| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). |
| `near` | For distance related to a [point field]('/docs/fields/point') comma separated as `<longitude>, <latitude>, <maxDistance in meters (nullable)>, <minDistance in meters (nullable)>`. |
<Banner type="success">
<strong>Tip</strong>:<br/>

View File

@@ -46,8 +46,11 @@ const numeric = [
];
const geo = [
...base,
...boolean,
{
label: 'exists',
value: 'exists',
},
{
label: 'near',
value: 'near',

View File

@@ -188,6 +188,7 @@ export const checkbox = baseField.keys({
export const point = baseField.keys({
type: joi.string().valid('point').required(),
name: joi.string().required(),
defaultValue: joi.array().items(joi.number()).max(2).min(2),
});
export const relationship = baseField.keys({

View File

@@ -218,14 +218,18 @@ class ParamParser {
break;
case 'near':
// eslint-disable-next-line no-case-declarations
const [longitude, latitude, maxDistance, minDistance] = convertArrayFromCommaDelineated(formattedValue);
const [x, y, maxDistance, minDistance] = convertArrayFromCommaDelineated(formattedValue);
if (!x || !y || (!maxDistance && !minDistance)) {
formattedValue = undefined;
break;
}
formattedValue = {
$near: {
$geometry: { type: 'Point', coordinates: [parseFloat(longitude), parseFloat(latitude)] },
$geometry: { type: 'Point', coordinates: [parseFloat(x), parseFloat(y)] },
},
};
if (maxDistance) formattedValue.$near.$maxDistance = parseFloat(maxDistance);
if (minDistance) formattedValue.$near.$maxDistance = parseFloat(maxDistance);
if (minDistance) formattedValue.$near.$minDistance = parseFloat(minDistance);
break;
default:
break;

View File

@@ -1,3 +1,5 @@
const path = require('path');
const fs = require('fs');
require('isomorphic-fetch');
require('../../demo/server');
@@ -6,7 +8,10 @@ const { email, password } = require('../../src/mongoose/testCredentials');
const { serverURL } = loadConfig();
const mediaDir = path.join(__dirname, '../../demo', 'media');
const globalSetup = async () => {
fs.rmdirSync(mediaDir, { recursive: true });
const response = await fetch(`${serverURL}/api/admins/first-register`, {
body: JSON.stringify({
email,