bajo-spatial 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/BajoSpatial.html +1 -1
- package/docs/data/search.json +1 -1
- package/docs/global.html +1 -1
- package/docs/index.js.html +57 -17
- package/docs/scripts/core.js +477 -476
- package/docs/scripts/resize.js +36 -36
- package/docs/scripts/search.js +105 -105
- package/docs/scripts/third-party/fuse.js +1 -1
- package/docs/scripts/third-party/hljs-line-num-original.js +285 -282
- package/docs/scripts/third-party/hljs-line-num.js +1 -1
- package/docs/scripts/third-party/hljs-original.js +1202 -1195
- package/docs/scripts/third-party/hljs.js +1 -1
- package/docs/scripts/third-party/popper.js +1 -1
- package/docs/scripts/third-party/tippy.js +1 -1
- package/docs/scripts/third-party/tocbot.js +509 -508
- package/index.js +44 -4
- package/package.json +4 -2
- package/test/bajo-spatial.test.js +181 -0
- package/wiki/CHANGES.md +5 -0
package/index.js
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plugin factory
|
|
2
|
+
* Plugin factory.
|
|
3
|
+
*
|
|
4
|
+
* **Never** call this function directly!!! It's only-meant to be called by the {@link https://ardhi.github.io/bajo|Bajo framework} during plugin initialization.
|
|
3
5
|
*
|
|
4
6
|
* @param {string} pkgName - NPM package name
|
|
5
|
-
* @returns {class}
|
|
7
|
+
* @returns {class} BajoSpatial
|
|
6
8
|
*/
|
|
7
9
|
async function factory (pkgName) {
|
|
8
10
|
const me = this
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
|
-
* BajoSpatial class
|
|
13
|
+
* BajoSpatial class definition
|
|
12
14
|
*
|
|
13
15
|
* @class
|
|
14
16
|
*/
|
|
15
17
|
class BajoSpatial extends this.app.baseClass.Base {
|
|
18
|
+
/**
|
|
19
|
+
* Creates an instance of BajoSpatial.
|
|
20
|
+
*/
|
|
16
21
|
constructor () {
|
|
17
22
|
super(pkgName, me.app)
|
|
23
|
+
/**
|
|
24
|
+
* @property {object} config - Configuration object
|
|
25
|
+
*/
|
|
18
26
|
this.config = {}
|
|
19
27
|
}
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Builds a bounding box query for a given model.
|
|
31
|
+
*
|
|
32
|
+
* @async
|
|
33
|
+
* @method
|
|
34
|
+
* @param {object} params - Parameters for building the query.
|
|
35
|
+
* @param {string} params.bbox - Bounding box coordinates.
|
|
36
|
+
* @param {object} [params.query={}] - Existing query object to merge with.
|
|
37
|
+
* @param {object} params.model - {@link https://ardhi.github.io/dobo|Dobo model} to apply the query to.
|
|
38
|
+
* @param {object} [params.options={}] - Additional options for query building.
|
|
39
|
+
* @param {string} [params.options.bboxLatField='lat'] - Latitude field name in the model.
|
|
40
|
+
* @param {string} [params.options.bboxLngField='lng'] - Longitude field name in the model.
|
|
41
|
+
* @returns {object} - The modified query object with bounding box conditions.
|
|
42
|
+
*/
|
|
43
|
+
buildBboxQuery = async (params = {}) => {
|
|
44
|
+
let { bbox, query = {}, model, options = {} } = params
|
|
22
45
|
const { merge, isEmpty } = this.app.lib._
|
|
23
46
|
const props = model.properties.map(item => item.name)
|
|
24
47
|
const { bboxLatField = 'lat', bboxLngField = 'lng' } = options
|
|
@@ -42,6 +65,15 @@ async function factory (pkgName) {
|
|
|
42
65
|
return query
|
|
43
66
|
}
|
|
44
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Gets the bounding box for a given country ID.
|
|
70
|
+
* Requires the `bajoCommonDb` and `dobo` plugins to be available. Otherwise, it will return undefined.
|
|
71
|
+
*
|
|
72
|
+
* @async
|
|
73
|
+
* @method
|
|
74
|
+
* @param {string} item - Country ID.
|
|
75
|
+
* @returns {Promise<string|undefined>} - The bounding box string or undefined if not found.
|
|
76
|
+
*/
|
|
45
77
|
getCountryBbox = async (item) => {
|
|
46
78
|
item = item + ''
|
|
47
79
|
if (item.includes(',')) return
|
|
@@ -52,6 +84,14 @@ async function factory (pkgName) {
|
|
|
52
84
|
throw this.error('Invalid country bbox \'%s\'', item, { statusCode: 400 })
|
|
53
85
|
}
|
|
54
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Parses a bounding box input.
|
|
89
|
+
*
|
|
90
|
+
* @async
|
|
91
|
+
* @method
|
|
92
|
+
* @param {string} input - The bounding box input, either a country ID or coordinates.
|
|
93
|
+
* @returns {Promise<Array<number>|undefined>} - The parsed bounding box coordinates or undefined if not found.
|
|
94
|
+
*/
|
|
55
95
|
parseBbox = async (input) => {
|
|
56
96
|
const { isSet } = this.app.lib.aneka
|
|
57
97
|
if (input.length === 2 && !input.includes(',')) return await this.getCountryBbox(input)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bajo-spatial",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Spatial functions for Bajo Framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"geolib": "^3.3.4"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
+
"chai": "^6.2.2",
|
|
37
38
|
"clean-jsdoc-theme": "^4.3.0",
|
|
38
|
-
"jsdoc-plugin-intersection": "^1.0.4"
|
|
39
|
+
"jsdoc-plugin-intersection": "^1.0.4",
|
|
40
|
+
"mocha": "^11.7.6"
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/* global describe, it, beforeEach */
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai'
|
|
4
|
+
|
|
5
|
+
import factory from '../index.js'
|
|
6
|
+
|
|
7
|
+
describe('BajoSpatial', () => {
|
|
8
|
+
let app
|
|
9
|
+
let BajoSpatial
|
|
10
|
+
let spatial
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
app = {
|
|
14
|
+
lib: {
|
|
15
|
+
_: {
|
|
16
|
+
merge: (target, source) => Object.assign(target, source),
|
|
17
|
+
isEmpty: (value) => {
|
|
18
|
+
if (value == null) return true
|
|
19
|
+
if (Array.isArray(value) || typeof value === 'string') return value.length === 0
|
|
20
|
+
if (typeof value === 'object') return Object.keys(value).length === 0
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
aneka: {
|
|
25
|
+
isSet: (value) => value !== null && value !== undefined
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
baseClass: {
|
|
29
|
+
Base: class Base {
|
|
30
|
+
constructor (pkgName, appRef) {
|
|
31
|
+
this.pkgName = pkgName
|
|
32
|
+
this.app = appRef
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
error (msg, item) {
|
|
36
|
+
const text = msg.replace('%s', item)
|
|
37
|
+
return new Error(text)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
BajoSpatial = await factory.call({ app }, 'bajo-spatial')
|
|
44
|
+
spatial = new BajoSpatial()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('initializes with empty config object', () => {
|
|
48
|
+
expect(spatial.config).to.deep.equal({})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('parseBbox parses valid coordinates', async () => {
|
|
52
|
+
const result = await spatial.parseBbox('100,-10,120,10')
|
|
53
|
+
|
|
54
|
+
expect(result).to.deep.equal([100, -10, 120, 10])
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('parseBbox throws for invalid coordinates', async () => {
|
|
58
|
+
try {
|
|
59
|
+
await spatial.parseBbox('100,-10,200,10')
|
|
60
|
+
expect.fail('Expected parseBbox to throw')
|
|
61
|
+
} catch (err) {
|
|
62
|
+
expect(err.message).to.equal("Invalid bbox '100,-10,200,10'")
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('parseBbox resolves country code through getCountryBbox', async () => {
|
|
67
|
+
spatial.getCountryBbox = async (code) => {
|
|
68
|
+
expect(code).to.equal('ID')
|
|
69
|
+
return [95, -11, 141, 6]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await spatial.parseBbox('ID')
|
|
73
|
+
|
|
74
|
+
expect(result).to.deep.equal([95, -11, 141, 6])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('buildBboxQuery returns merged query when fields exist', async () => {
|
|
78
|
+
const model = { properties: [{ name: 'lat' }, { name: 'lng' }] }
|
|
79
|
+
|
|
80
|
+
const result = await spatial.buildBboxQuery({
|
|
81
|
+
bbox: '100,-10,120,10',
|
|
82
|
+
query: { status: 'active' },
|
|
83
|
+
model
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
expect(result).to.deep.equal({
|
|
87
|
+
status: 'active',
|
|
88
|
+
lng: { $gte: 100, $lte: 120 },
|
|
89
|
+
lat: { $gte: -10, $lte: 10 }
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('buildBboxQuery appends bbox query to $and', async () => {
|
|
94
|
+
const model = { properties: [{ name: 'lat' }, { name: 'lng' }] }
|
|
95
|
+
const query = { $and: [{ status: 'active' }] }
|
|
96
|
+
|
|
97
|
+
const result = await spatial.buildBboxQuery({
|
|
98
|
+
bbox: '100,-10,120,10',
|
|
99
|
+
query,
|
|
100
|
+
model
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
expect(result.$and).to.have.lengthOf(2)
|
|
104
|
+
expect(result.$and[1]).to.deep.equal({
|
|
105
|
+
lng: { $gte: 100, $lte: 120 },
|
|
106
|
+
lat: { $gte: -10, $lte: 10 }
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('buildBboxQuery converts $or query into $and with bbox query', async () => {
|
|
111
|
+
const model = { properties: [{ name: 'lat' }, { name: 'lng' }] }
|
|
112
|
+
const query = { $or: [{ status: 'active' }, { status: 'pending' }] }
|
|
113
|
+
|
|
114
|
+
const result = await spatial.buildBboxQuery({
|
|
115
|
+
bbox: '100,-10,120,10',
|
|
116
|
+
query,
|
|
117
|
+
model
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
expect(result).to.deep.equal({
|
|
121
|
+
$and: [
|
|
122
|
+
[{ status: 'active' }, { status: 'pending' }],
|
|
123
|
+
{
|
|
124
|
+
lng: { $gte: 100, $lte: 120 },
|
|
125
|
+
lat: { $gte: -10, $lte: 10 }
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('buildBboxQuery returns original query when model fields are missing', async () => {
|
|
132
|
+
const model = { properties: [{ name: 'latitude' }, { name: 'longitude' }] }
|
|
133
|
+
const query = { status: 'active' }
|
|
134
|
+
|
|
135
|
+
const result = await spatial.buildBboxQuery({
|
|
136
|
+
bbox: '100,-10,120,10',
|
|
137
|
+
query,
|
|
138
|
+
model
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
expect(result).to.deep.equal({ status: 'active' })
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('getCountryBbox returns undefined for direct bbox string or missing dependencies', async () => {
|
|
145
|
+
expect(await spatial.getCountryBbox('1,2,3,4')).to.equal(undefined)
|
|
146
|
+
expect(await spatial.getCountryBbox('ID')).to.equal(undefined)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('getCountryBbox returns bbox from CdbCountry model', async () => {
|
|
150
|
+
app.bajoCommonDb = {}
|
|
151
|
+
app.dobo = {
|
|
152
|
+
getModel: () => ({
|
|
153
|
+
getRecord: async (id, opts) => {
|
|
154
|
+
expect(id).to.equal('ID')
|
|
155
|
+
expect(opts).to.deep.equal({ throwNotFound: false })
|
|
156
|
+
return { bbox: '95,-11,141,6' }
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const result = await spatial.getCountryBbox('ID')
|
|
162
|
+
|
|
163
|
+
expect(result).to.equal('95,-11,141,6')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('getCountryBbox throws when country id is unknown', async () => {
|
|
167
|
+
app.bajoCommonDb = {}
|
|
168
|
+
app.dobo = {
|
|
169
|
+
getModel: () => ({
|
|
170
|
+
getRecord: async () => null
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
await spatial.getCountryBbox('ID')
|
|
176
|
+
expect.fail('Expected getCountryBbox to throw')
|
|
177
|
+
} catch (err) {
|
|
178
|
+
expect(err.message).to.equal("Invalid country bbox 'ID'")
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
})
|