Skip to content

Commit cb44931

Browse files
Merge pull request #152 from sat-utils/develop
publish 0.2.3
2 parents 72ac9f7 + 1e3f7c9 commit cb44931

File tree

14 files changed

+169
-113
lines changed

14 files changed

+169
-113
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88

9+
## [v0.2.3] - 2019-01-29
10+
11+
### Fixed
12+
- Proper handling of bounding box passed as string
13+
14+
### Changed
15+
- De-normalize Item properties to include all properties from collection
16+
- Flattened elastic search to simplify query logic
17+
- Items returned will now include all 'Common' properties that are in the Items Collection
18+
919
## [v0.2.2] - 2019-01-21
1020

1121
### Fixed
@@ -74,7 +84,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
7484
- Refactor and improve splitting
7585

7686
[Unreleased]: https://github.com/sat-utils/sat-api/compare/master...develop
77-
[v0.2.2]: https://github.com/sat-utils/sat-api/compare/v0.2.0...v0.2.2
87+
[v0.2.3]: https://github.com/sat-utils/sat-api/compare/v0.2.2...v0.2.3
88+
[v0.2.2]: https://github.com/sat-utils/sat-api/compare/v0.2.1...v0.2.2
7889
[v0.2.1]: https://github.com/sat-utils/sat-api/compare/v0.2.0...v0.2.1
7990
[v0.2.0]: https://github.com/sat-utils/sat-api/compare/v0.1.0...v0.2.0
8091
[v0.1.0]: https://github.com/sat-utils/sat-api/compare/v0.0.2...v0.1.0

lerna.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"lerna": "2.11.0",
3-
"version": "0.2.2",
3+
"version": "0.2.3",
44
"npmClient": "yarn",
55
"packages": [
66
"packages/*"

packages/api-lib/libs/api.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ const extractBbox = function (params) {
3838
let intersectsGeometry
3939
const { bbox } = params
4040
if (bbox) {
41-
const boundingBox = extent(bbox)
41+
let bboxArray
42+
if (typeof bbox === 'string') {
43+
bboxArray = JSON.parse(bbox)
44+
} else {
45+
bboxArray = bbox
46+
}
47+
const boundingBox = extent(bboxArray)
4248
const geojson = feature(boundingBox.polygon())
4349
intersectsGeometry = geojson
4450
}
@@ -236,15 +242,8 @@ const buildPageLinks = function (meta, parameters, endpoint) {
236242
}
237243

238244
const searchItems = async function (parameters, page, limit, backend, endpoint) {
239-
const arbitraryLimit = 5000
240-
const { results: collectionResults } =
241-
await backend.search(parameters, 'collections', 1, arbitraryLimit)
242-
const collectionList = collectionResults.map((result) => result.id)
243-
const collectionsQuery = Object.assign(
244-
{}, parameters, { parentCollections: collectionList }
245-
)
246245
const { results: itemsResults, meta: itemsMeta } =
247-
await backend.search(collectionsQuery, 'items', page, limit)
246+
await backend.search(parameters, 'items', page, limit)
248247
const pageLinks = buildPageLinks(itemsMeta, parameters, endpoint)
249248
const items = addItemLinks(itemsResults, endpoint)
250249
const response = wrapResponseInFeatureCollection(itemsMeta, items, pageLinks)

packages/api-lib/libs/es.js

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const ElasticsearchWritableStream = require('./ElasticSearchWriteableStream')
88
const logger = require('./logger')
99

1010
let _esClient
11-
1211
/*
1312
This module is used for connecting to an Elasticsearch instance, writing records,
1413
searching records, and managing the indexes. It looks for the ES_HOST environment
@@ -77,7 +76,6 @@ async function esClient() {
7776
// Create STAC mappings
7877
async function prepare(index) {
7978
// TODO - different mappings for collection and item
80-
let ready
8179
const props = {
8280
'type': 'object',
8381
properties: {
@@ -106,7 +104,6 @@ async function prepare(index) {
106104
}]
107105
const client = await esClient()
108106
const indexExists = await client.indices.exists({ index })
109-
110107
if (!indexExists) {
111108
const payload = {
112109
index,
@@ -133,49 +130,71 @@ async function prepare(index) {
133130
try {
134131
await client.indices.create(payload)
135132
logger.info(`Created index: ${JSON.stringify(payload)}`)
136-
ready = 0
137133
} catch (error) {
138134
const debugMessage = `Error creating index, already created: ${error}`
139135
logger.debug(debugMessage)
140136
}
141137
}
142-
return ready
143138
}
144139

145140
// Given an input stream and a transform, write records to an elasticsearch instance
146141
async function _stream() {
147-
const toEs = through2.obj({ objectMode: true }, (data, encoding, next) => {
148-
let index = ''
149-
if (data && data.hasOwnProperty('extent')) {
150-
index = 'collections'
151-
} else if (data && data.hasOwnProperty('geometry')) {
152-
index = 'items'
153-
} else {
154-
next()
155-
return
156-
}
157-
// remove any hierarchy links in a non-mutating way
158-
const hlinks = ['self', 'root', 'parent', 'child', 'collection', 'item']
159-
const links = data.links.filter((link) => hlinks.includes(link))
160-
const dataNoLinks = Object.assign({}, data, { links })
161-
162-
// create ES record
163-
const record = {
164-
index,
165-
type: 'doc',
166-
id: dataNoLinks.id,
167-
action: 'update',
168-
_retry_on_conflict: 3,
169-
body: {
170-
doc: dataNoLinks,
171-
doc_as_upsert: true
172-
}
173-
}
174-
next(null, record)
175-
})
176142
let esStreams
177143
try {
144+
let collections = []
178145
const client = await esClient()
146+
const indexExists = await client.indices.exists({ index: 'collections' })
147+
if (indexExists) {
148+
const body = { query: { match_all: {} } }
149+
const searchParams = {
150+
index: 'collections',
151+
body
152+
}
153+
const resultBody = await client.search(searchParams)
154+
collections = resultBody.hits.hits.map((r) => (r._source))
155+
}
156+
157+
const toEs = through2.obj({ objectMode: true }, (data, encoding, next) => {
158+
let index = ''
159+
if (data && data.hasOwnProperty('extent')) {
160+
index = 'collections'
161+
} else if (data && data.hasOwnProperty('geometry')) {
162+
index = 'items'
163+
} else {
164+
next()
165+
return
166+
}
167+
// remove any hierarchy links in a non-mutating way
168+
const hlinks = ['self', 'root', 'parent', 'child', 'collection', 'item']
169+
const links = data.links.filter((link) => hlinks.includes(link))
170+
let esDataObject = Object.assign({}, data, { links })
171+
if (index === 'items') {
172+
const collectionId = data.properties.collection
173+
const itemCollection =
174+
collections.find((collection) => (collectionId === collection.id))
175+
if (itemCollection) {
176+
const flatProperties =
177+
Object.assign({}, itemCollection.properties, data.properties)
178+
esDataObject = Object.assign({}, esDataObject, { properties: flatProperties })
179+
} else {
180+
logger.error(`${data.id} has no collection`)
181+
}
182+
}
183+
184+
// create ES record
185+
const record = {
186+
index,
187+
type: 'doc',
188+
id: esDataObject.id,
189+
action: 'update',
190+
_retry_on_conflict: 3,
191+
body: {
192+
doc: esDataObject,
193+
doc_as_upsert: true
194+
}
195+
}
196+
next(null, record)
197+
})
179198
const esStream = new ElasticsearchWritableStream({ client: client }, {
180199
objectMode: true,
181200
highWaterMark: process.env.ES_BATCH_SIZE || 500
@@ -243,9 +262,11 @@ function buildDatetimeQuery(parameters) {
243262

244263
function buildQuery(parameters) {
245264
const eq = 'eq'
246-
const { query, parentCollections, intersects } = parameters
265+
const { query, intersects } = parameters
247266
let must = []
248267
if (query) {
268+
// Using reduce rather than map as we don't currently support all
269+
// stac query operators.
249270
must = Object.keys(query).reduce((accumulator, property) => {
250271
const operatorsObject = query[property]
251272
const operators = Object.keys(operatorsObject)
@@ -265,6 +286,7 @@ function buildQuery(parameters) {
265286
return accumulator
266287
}, must)
267288
}
289+
268290
if (intersects) {
269291
const { geometry } = intersects
270292
must.push({
@@ -279,19 +301,7 @@ function buildQuery(parameters) {
279301
must.push(datetimeQuery)
280302
}
281303

282-
let filter
283-
if (parentCollections && parentCollections.length !== 0) {
284-
filter = {
285-
bool: {
286-
should: [
287-
{ terms: { 'properties.collection': parentCollections } },
288-
{ bool: { must } }
289-
]
290-
}
291-
}
292-
} else {
293-
filter = { bool: { must } }
294-
}
304+
const filter = { bool: { must } }
295305
const queryBody = {
296306
constant_score: { filter }
297307
}

packages/api-lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sat-utils/api-lib",
3-
"version": "0.2.2",
3+
"version": "0.2.3",
44
"description": "A library for creating a search API of public Satellites metadata using Elasticsearch",
55
"main": "index.js",
66
"scripts": {

packages/api-lib/tests/fixtures/stac/collection2.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220
"eo:gsd": 15,
221221
"eo:instrument": "OLI_TIRS",
222222
"eo:off_nadir": 0,
223-
"eo:platform": "landsat-8"
223+
"eo:platform": "platform2"
224224
},
225225
"providers": [
226226
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
process.env.ES_HOST = `http://${process.env.DOCKER_NAME}:4571`
2+
const ingest = require('../../libs/ingest').ingest
3+
const backend = require('../../libs/es')
4+
5+
async function doIngest() {
6+
await ingest('../fixtures/stac/catalog.json', backend, true, true)
7+
console.log('Collections done')
8+
}
9+
doIngest()

packages/api-lib/tests/integration/ingestData.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ process.env.ES_HOST = `http://${process.env.DOCKER_NAME}:4571`
22
const ingest = require('../../libs/ingest').ingest
33
const backend = require('../../libs/es')
44

5-
//ingest('../fixtures/stac/catalog.json', backend)
6-
ingest('https://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json', backend)
7-
5+
async function doIngest() {
6+
await ingest('../fixtures/stac/catalog.json', backend)
7+
console.log('Items done')
8+
}
9+
//ingest('https://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json', backend)
10+
doIngest()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/bash
22
docker-compose up & while ! nc -z $DOCKER_NAME 4571; do sleep 1; done;
33
sleep 20;
4-
node ./ingestData.js && yarn ava ./tests/integration/test_api.js
4+
node ./ingestCollections.js && node ./ingestData.js && yarn ava ./tests/integration/test_api.js

packages/api-lib/tests/integration/test_api.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,27 @@ test('stac/search sort', async (t) => {
151151
}, backend, endpoint)
152152
t.is(response.features[0].id, 'LC80100102015082LGN00')
153153
})
154+
155+
test('stac/search flattened collection properties', async (t) => {
156+
let response = await search('/stac/search', {
157+
query: {
158+
'eo:platform': {
159+
eq: 'platform2'
160+
}
161+
}
162+
}, backend, endpoint)
163+
t.is(response.features[0].id, 'collection2_item')
164+
165+
response = await search('/stac/search', {
166+
query: {
167+
'eo:platform': {
168+
eq: 'landsat-8'
169+
}
170+
}
171+
}, backend, endpoint)
172+
const havePlatform =
173+
response.features.filter(
174+
(item) => (item.properties['eo:platform'] === 'landsat-8')
175+
)
176+
t.is(havePlatform.length, response.features.length)
177+
})

0 commit comments

Comments
 (0)