docs: point field and near query
This commit is contained in:
@@ -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)],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
54
docs/fields/point.mdx
Normal 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.
|
||||
@@ -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/>
|
||||
|
||||
@@ -46,8 +46,11 @@ const numeric = [
|
||||
];
|
||||
|
||||
const geo = [
|
||||
...base,
|
||||
...boolean,
|
||||
{
|
||||
label: 'exists',
|
||||
value: 'exists',
|
||||
},
|
||||
{
|
||||
label: 'near',
|
||||
value: 'near',
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user