diff --git a/.changeset/wise-cooks-nail.md b/.changeset/wise-cooks-nail.md new file mode 100644 index 000000000..2a3a608d1 --- /dev/null +++ b/.changeset/wise-cooks-nail.md @@ -0,0 +1,5 @@ +--- +"@koopjs/featureserver": patch +--- + +- changes to JSON specification sent to PBF encoder diff --git a/demo/provider-data/points-id-string.geojson b/demo/provider-data/points-id-string.geojson new file mode 100644 index 000000000..bbe1531ba --- /dev/null +++ b/demo/provider-data/points-id-string.geojson @@ -0,0 +1,62 @@ +{ + "type": "FeatureCollection", + "metadata": { + "idField": "id", + "fields": [ + { "name": "timestamp", "type": "Date" }, + { "name": "id", "type": "String" }, + { "name": "label", "type": "String" }, + { "name": "category", "type": "String" } + ] + }, + "features": [ + { + "type": "Feature", + "properties": { + "id": "aaa", + "timestamp": "2023-04-10T16:15:30.000Z", + "label": "White Leg", + "category": "pinto" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -80, + 25 + ] + } + }, + { + "type": "Feature", + "properties": { + "id": "bbb", + "timestamp": "2020-04-12T16:15:30.000Z", + "label": "Fireman", + "category": "pinto" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -120, + 45 + ] + } + }, + { + "type": "Feature", + "properties": { + "id": "ccc", + "timestamp": "2015-04-11T16:15:30.000Z", + "label": "Workhorse", + "category": "draft" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -100, + 40 + ] + } + } + ] +} \ No newline at end of file diff --git a/packages/featureserver/coverage-unit.svg b/packages/featureserver/coverage-unit.svg index f658d2ac8..18835f77c 100644 --- a/packages/featureserver/coverage-unit.svg +++ b/packages/featureserver/coverage-unit.svg @@ -1,20 +1,20 @@ - - coverage: 95.06% + + coverage: 95% - + - - + + \ No newline at end of file diff --git a/packages/featureserver/coverage.svg b/packages/featureserver/coverage.svg index c08c6584c..92858c5e5 100644 --- a/packages/featureserver/coverage.svg +++ b/packages/featureserver/coverage.svg @@ -1,5 +1,5 @@ - - coverage: 98.11% + + coverage: 98.16% @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/packages/featureserver/src/helpers/feature-layer-metadata.js b/packages/featureserver/src/helpers/feature-layer-metadata.js index de48a369f..2c24574b4 100644 --- a/packages/featureserver/src/helpers/feature-layer-metadata.js +++ b/packages/featureserver/src/helpers/feature-layer-metadata.js @@ -1,10 +1,6 @@ const _ = require('lodash'); const TableLayerMetadata = require('./table-layer-metadata'); -const { - PointRenderer, - LineRenderer, - PolygonRenderer -} = require('./renderers'); +const { PointRenderer, LineRenderer, PolygonRenderer } = require('./renderers'); const { calculateBounds } = require('@terraformer/spatial'); const logManager = require('../log-manager'); const getSpatialReference = require('./get-spatial-reference'); @@ -13,22 +9,20 @@ const normalizeExtent = require('./normalize-extent'); const defaults = require('../metadata-defaults'); class FeatureLayerMetadata extends TableLayerMetadata { - static create (geojson, options) { - const { - geojson: normalizedGeojson, - options: normalizedOptions - } = FeatureLayerMetadata.normalizeInput(geojson, options); + static create(geojson, options) { + const { geojson: normalizedGeojson, options: normalizedOptions } = + FeatureLayerMetadata.normalizeInput(geojson, options); const layerMetadata = new FeatureLayerMetadata(); return layerMetadata.mixinOverrides(normalizedGeojson, normalizedOptions); } - constructor () { + constructor() { super(); Object.assign(this, defaults.featureLayerDefaults()); return this; } - mixinOverrides (geojson = {}, options = {}) { + mixinOverrides(geojson = {}, options = {}) { super.mixinOverrides(geojson, options); const { renderer, extent, inputCrs, sourceSR, capabilities = {} } = options; @@ -46,14 +40,14 @@ class FeatureLayerMetadata extends TableLayerMetadata { return this; } - _setExtent (geojson, options) { + _setExtent(geojson, options) { const extent = getLayerExtent(geojson, options); if (extent) { this.extent = extent; } } - _setRenderer (renderer) { + _setRenderer(renderer) { if (renderer) { this.drawingInfo.renderer = renderer; return; @@ -68,24 +62,21 @@ class FeatureLayerMetadata extends TableLayerMetadata { } } - _setDirectOverrides (options) { + _setDirectOverrides(options) { super._setDirectOverrides(options); - const { - minScale, - maxScale - } = options; + const { minScale, maxScale } = options; _.merge(this, { minScale, - maxScale + maxScale, }); } } -function getLayerExtent (geojson, options) { +function getLayerExtent(geojson, options) { const spatialReference = getSpatialReference(geojson, options) || { wkid: 4326, - latestWkid: 4326 + latestWkid: 4326, }; const { extent } = options; @@ -97,7 +88,7 @@ function getLayerExtent (geojson, options) { return calculateExtentFromFeatures(geojson, spatialReference); } -function calculateExtentFromFeatures (geojson, spatialReference) { +function calculateExtentFromFeatures(geojson, spatialReference) { if (!geojson.features || geojson.features.length === 0) { return; } @@ -110,10 +101,12 @@ function calculateExtentFromFeatures (geojson, spatialReference) { xmax, ymin, ymax, - spatialReference + spatialReference, }; } catch (error) { - logManager.logger.debug(`Could not calculate extent from data: ${error.message}`); + logManager.logger.debug( + `Could not calculate extent from data: ${error.message}`, + ); } } diff --git a/packages/featureserver/src/helpers/feature-layer-metadata.spec.js b/packages/featureserver/src/helpers/feature-layer-metadata.spec.js index d9b4a0e3a..f1160b718 100644 --- a/packages/featureserver/src/helpers/feature-layer-metadata.spec.js +++ b/packages/featureserver/src/helpers/feature-layer-metadata.spec.js @@ -51,6 +51,7 @@ describe('FeatureLayerMetadata', () => { featureLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Feature Layer', @@ -180,6 +181,7 @@ describe('FeatureLayerMetadata', () => { featureLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Feature Layer', @@ -299,7 +301,7 @@ describe('FeatureLayerMetadata', () => { renderer: { type: 'simple', symbol: { - color: [45, 172, 128, 161], + color: [247, 150, 70, 161], outline: { color: [190, 190, 190, 105], width: 0.5, @@ -348,6 +350,7 @@ describe('FeatureLayerMetadata', () => { ); featureLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Feature Layer', @@ -467,7 +470,7 @@ describe('FeatureLayerMetadata', () => { renderer: { type: 'simple', symbol: { - color: [45, 172, 128, 161], + color: [247, 150, 70, 161], outline: { color: [190, 190, 190, 105], width: 0.5, @@ -508,6 +511,7 @@ describe('FeatureLayerMetadata', () => { featureLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Feature Layer', @@ -627,7 +631,7 @@ describe('FeatureLayerMetadata', () => { renderer: { type: 'simple', symbol: { - color: [45, 172, 128, 161], + color: [247, 150, 70, 161], outline: { color: [190, 190, 190, 105], width: 0.5, @@ -664,6 +668,7 @@ describe('FeatureLayerMetadata', () => { featureLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Feature Layer', @@ -783,7 +788,7 @@ describe('FeatureLayerMetadata', () => { renderer: { type: 'simple', symbol: { - color: [45, 172, 128, 161], + color: [247, 150, 70, 161], outline: { color: [190, 190, 190, 105], width: 0.5, @@ -826,6 +831,7 @@ describe('FeatureLayerMetadata', () => { featureLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Feature Layer', @@ -967,6 +973,7 @@ describe('FeatureLayerMetadata', () => { featureLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Feature Layer', @@ -1086,7 +1093,7 @@ describe('FeatureLayerMetadata', () => { renderer: { type: 'simple', symbol: { - color: [45, 172, 128, 161], + color: [247, 150, 70, 161], outline: { color: [190, 190, 190, 105], width: 0.5, @@ -1134,6 +1141,7 @@ describe('FeatureLayerMetadata', () => { featureLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 99, name: 'Not Set', type: 'Feature Layer', @@ -1253,7 +1261,7 @@ describe('FeatureLayerMetadata', () => { renderer: { type: 'simple', symbol: { - color: [45, 172, 128, 161], + color: [247, 150, 70, 161], outline: { color: [190, 190, 190, 105], width: 0.5, diff --git a/packages/featureserver/src/helpers/renderers.js b/packages/featureserver/src/helpers/renderers.js index e064c6d8b..ab05971ce 100644 --- a/packages/featureserver/src/helpers/renderers.js +++ b/packages/featureserver/src/helpers/renderers.js @@ -4,9 +4,9 @@ class PointRenderer { type: 'simple', symbol: { color: [ - 45, - 172, - 128, + 247, + 150, + 70, 161 ], outline: { diff --git a/packages/featureserver/src/helpers/renderers.spec.js b/packages/featureserver/src/helpers/renderers.spec.js index 6e62eaf57..6f1d9c249 100644 --- a/packages/featureserver/src/helpers/renderers.spec.js +++ b/packages/featureserver/src/helpers/renderers.spec.js @@ -14,9 +14,9 @@ describe('Renderers', () => { type: 'simple', symbol: { color: [ - 45, - 172, - 128, + 247, + 150, + 70, 161 ], outline: { diff --git a/packages/featureserver/src/helpers/table-layer-metadata.spec.js b/packages/featureserver/src/helpers/table-layer-metadata.spec.js index 5d3fddd58..7ec0e049e 100644 --- a/packages/featureserver/src/helpers/table-layer-metadata.spec.js +++ b/packages/featureserver/src/helpers/table-layer-metadata.spec.js @@ -31,6 +31,7 @@ describe('TableLayerMetadata', () => { const tableLayerMetadata = new TableLayerMetadata(); tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -151,6 +152,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -264,6 +266,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 2, name: 'Not Set', type: 'Table', @@ -375,6 +378,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 3, name: 'Not Set', type: 'Table', @@ -486,6 +490,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -597,6 +602,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -708,6 +714,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -819,6 +826,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -930,6 +938,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1041,6 +1050,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1152,6 +1162,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1266,6 +1277,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1380,6 +1392,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1491,6 +1504,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1602,6 +1616,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1713,6 +1728,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1824,6 +1840,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', @@ -1948,6 +1965,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: 90.99, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Hank Williams', type: 'Table', @@ -2201,6 +2219,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: 90.99, + supportedPbfFeatureEncodings: 'esriDefault', id: 99, name: 'Not Set', type: 'Table', @@ -2322,6 +2341,7 @@ describe('TableLayerMetadata', () => { tableLayerMetadata.should.deepEqual({ currentVersion: 11.2, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Not Set', type: 'Table', diff --git a/packages/featureserver/src/metadata-defaults.js b/packages/featureserver/src/metadata-defaults.js index bb87cefea..2611341a2 100644 --- a/packages/featureserver/src/metadata-defaults.js +++ b/packages/featureserver/src/metadata-defaults.js @@ -150,6 +150,7 @@ class MetadataDefaults { supportsQuantizationEditMode: false, supportsApplyEditsWithGlobalIds: false, supportsReturningQueryGeometry: false, + supportedPbfFeatureEncodings: 'esriDefault', advancedQueryCapabilities: { supportsPagination: true, supportsQueryAttachmentsCountOnly: false, diff --git a/packages/featureserver/src/response-handlers/helpers/send-pbf/get-geometry-transform.js b/packages/featureserver/src/response-handlers/helpers/send-pbf/get-geometry-transform.js index 4d14206d3..c0bbd9d3a 100644 --- a/packages/featureserver/src/response-handlers/helpers/send-pbf/get-geometry-transform.js +++ b/packages/featureserver/src/response-handlers/helpers/send-pbf/get-geometry-transform.js @@ -39,8 +39,9 @@ function parseQuantizationParameters(q) { tolerance = null } = q; const scale = tolerance !== null ? q.tolerance: 1; - const [ xTranslate, yTranslate ] = q.extent != null ? [q.extent.xmin, q.extent.ymax] : [0, 0]; + const [ xTranslate, yTranslate ] = q.extent != null ? [q.extent.xmin, q.extent.ymax] : [0, 0]; + return { originPosition: normalizeOriginPosition(q.originPosition), scale: { xScale: scale, yScale: scale }, diff --git a/packages/featureserver/src/response-handlers/helpers/send-pbf/index.js b/packages/featureserver/src/response-handlers/helpers/send-pbf/index.js index 9382736a7..631bb560b 100644 --- a/packages/featureserver/src/response-handlers/helpers/send-pbf/index.js +++ b/packages/featureserver/src/response-handlers/helpers/send-pbf/index.js @@ -1,11 +1,12 @@ -const { esriPBuffer: { - FeatureCollectionPBuffer: FeatureCollectionProto -}} = require('./FeatureCollection.proto.js'); +const { + esriPBuffer: { FeatureCollectionPBuffer: FeatureCollectionProto }, +} = require('./FeatureCollection.proto.js'); +const logManager = require('../../../log-manager.js'); const { transformFeaturesForPbf } = require('./transform-features-for-pbf.js'); const FILENAME = 'results.pbf'; -function sendPbf(res, payload, requestParameters) { +function sendPbf(res, jsonResponse, requestParameters) { const { returnExtentOnly } = requestParameters; if (returnExtentOnly === true) { @@ -14,8 +15,7 @@ function sendPbf(res, payload, requestParameters) { throw error; } - const pbfPayload = setPbfPayload(payload, requestParameters); - const buffer = FeatureCollectionProto.encode(pbfPayload).finish(); + const buffer = getPbfBuffer(jsonResponse, requestParameters); res.writeHead(200, [ ['content-type', 'application/x-protobuf'], @@ -26,8 +26,25 @@ function sendPbf(res, payload, requestParameters) { return res.end(buffer); } -function setPbfPayload(payload, requestParameters) { - const { returnCountOnly, returnIdsOnly, quantizationParameters } = requestParameters; +function getPbfBuffer(resultJson, requestParameters) { + const pbfJson = convertToPbfJson(resultJson, requestParameters); + const pbfMessage = FeatureCollectionProto.fromObject(pbfJson); + verifyPbfMessage(pbfMessage); + return FeatureCollectionProto.encode(pbfMessage).finish(); +} + +function verifyPbfMessage(pbfMessage) { + const messageSpecViolations = FeatureCollectionProto.verify(pbfMessage); + if (messageSpecViolations) { + logManager.logger.debug( + `FeatureCollection PBF specification violation: ${messageSpecViolations}`, + ); + } +} + +function convertToPbfJson(payload, requestParameters) { + const { returnCountOnly, returnIdsOnly, quantizationParameters } = + requestParameters; if (returnCountOnly === true) { return { diff --git a/packages/featureserver/src/response-handlers/helpers/send-pbf/index.spec.js b/packages/featureserver/src/response-handlers/helpers/send-pbf/index.spec.js index cac79b2b8..143231df4 100644 --- a/packages/featureserver/src/response-handlers/helpers/send-pbf/index.spec.js +++ b/packages/featureserver/src/response-handlers/helpers/send-pbf/index.spec.js @@ -7,6 +7,8 @@ const transformFeaturesForPbfSpy = sinon.spy(() => { }); const protoSpy = { + fromObject: (obj) => obj, + verify: () => {}, encode: sinon.spy(() => { return protoSpy; }), @@ -18,6 +20,10 @@ const protoSpy = { }), }; +const loggerSpy = { + debug: sinon.spy(), +}; + const { sendPbf } = proxyquire('./', { './transform-features-for-pbf.js': { transformFeaturesForPbf: transformFeaturesForPbfSpy, @@ -27,6 +33,7 @@ const { sendPbf } = proxyquire('./', { FeatureCollectionPBuffer: protoSpy, }, }, + '../../../log-manager.js': { logger: loggerSpy }, }); const res = { @@ -141,4 +148,43 @@ describe('sendPbf', () => { error.code.should.equal(400); } }); + + it('log spec violation', () => { + const protoSpy = { + fromObject: (obj) => obj, + verify: () => { + return 'something wrong here'; + }, + encode: sinon.spy(() => { + return protoSpy; + }), + finish: sinon.spy(() => { + return { + esri: 'pbf', + length: 99, + }; + }), + }; + + const loggerSpy = { + debug: sinon.spy(), + }; + + const { sendPbf } = proxyquire('./', { + './transform-features-for-pbf.js': { + transformFeaturesForPbf: transformFeaturesForPbfSpy, + }, + './FeatureCollection.proto.js': { + esriPBuffer: { + FeatureCollectionPBuffer: protoSpy, + }, + }, + '../../../log-manager.js': { logger: loggerSpy }, + }); + + sendPbf(res, { esri: 'json' }, {}); + loggerSpy.debug.firstCall.args.should.deepEqual([ + 'FeatureCollection PBF specification violation: something wrong here', + ]); + }); }); diff --git a/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-features-for-pbf.js b/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-features-for-pbf.js index 67ca4e006..50f756c21 100644 --- a/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-features-for-pbf.js +++ b/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-features-for-pbf.js @@ -16,11 +16,11 @@ function transformFeaturesForPbf(json, quantizationParameters) { return { objectIdFieldName, uniqueIdField, - geometryType, spatialReference, - fields, + fields: fields.map(({name, alias, type}) => ({ name, alias, fieldType: type})), features, - transform: geometryTransform + transform: geometryTransform, + geometryType: geometryType ? geometryType.replace('esriGeometry', 'esriGeometryType') : 'esriGeometryTypeNone' }; } diff --git a/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-features-for-pbf.spec.js b/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-features-for-pbf.spec.js index 38ceab81c..a5552a760 100644 --- a/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-features-for-pbf.spec.js +++ b/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-features-for-pbf.spec.js @@ -7,7 +7,7 @@ const transformedFixture = { name: 'FID', isSystemMaintained: true, }, - geometryType: 'esriGeometryPoint', + geometryType: 'esriGeometryTypePoint', spatialReference: { wkid: 102100, latestWkid: 3857, @@ -15,62 +15,38 @@ const transformedFixture = { fields: [ { name: 'CONDITIODT', - type: 'esriFieldTypeDate', + fieldType: 'esriFieldTypeDate', alias: 'CONDITIODT', - sqlType: 'sqlTypeTimestamp2', - length: 8, - domain: null, - defaultValue: null, }, { name: 'FACILITYID', - type: 'esriFieldTypeString', + fieldType: 'esriFieldTypeString', alias: 'FACILITYID', - sqlType: 'sqlTypeNVarchar', - length: 30, - domain: null, - defaultValue: null, }, { name: 'FID', - type: 'esriFieldTypeOID', + fieldType: 'esriFieldTypeOID', alias: 'FID', - sqlType: 'sqlTypeInteger', - domain: null, - defaultValue: null, }, { name: 'GlobalID_2', - type: 'esriFieldTypeGlobalID', + fieldType: 'esriFieldTypeGlobalID', alias: 'GlobalID_2', - sqlType: 'sqlTypeOther', - length: 38, - domain: null, - defaultValue: 'NEWID() WITH VALUES', }, { name: 'OBJECTID', - type: 'esriFieldTypeInteger', + fieldType: 'esriFieldTypeInteger', alias: 'OBJECTID', - sqlType: 'sqlTypeInteger', - domain: null, - defaultValue: null, }, { name: 'TBOX_L', - type: 'esriFieldTypeDouble', + fieldType: 'esriFieldTypeDouble', alias: 'TBOX_L', - sqlType: 'sqlTypeFloat', - domain: null, - defaultValue: null, }, { name: 'WARD', - type: 'esriFieldTypeSmallInteger', + fieldType: 'esriFieldTypeSmallInteger', alias: 'WARD', - sqlType: 'sqlTypeSmallInt', - domain: null, - defaultValue: null, }, ], transform: { @@ -109,18 +85,27 @@ const transformedFixture = { }, ], geometry: { - coords: ['-85716573542', '-47044113943'], + lengths: [2], + coords: [-85716573542, -47044113943], }, }, ], }; describe('transform features for PBF', () => { - it('convert Esri FeatureCollection to PBF-ready JSON', () => { + it('convert Esri Point FeatureCollection to PBF-ready JSON', () => { const fixture = createFixture(); const result = transformFeaturesForPbf(fixture); result.should.deepEqual(transformedFixture); }); + + it('convert Esri Table FeatureCollection to PBF-ready JSON', () => { + const fixture = createFixture(); + fixture.geometryType = undefined; + const result = transformFeaturesForPbf(fixture); + transformedFixture.geometryType = 'esriGeometryTypeNone'; + result.should.deepEqual(transformedFixture); + }); }); function createFixture() { diff --git a/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-to-pbf-geometry.js b/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-to-pbf-geometry.js index cdc6c8e44..40d52e3dd 100644 --- a/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-to-pbf-geometry.js +++ b/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-to-pbf-geometry.js @@ -42,23 +42,23 @@ function transformMultipoint(geometry, transform) { const points = transformPoints(geometry.points, transform); return { lengths: [points.length], - coords: points.flat().map((int) => int.toString()), + coords: points.flat(), }; } function transformPoint(geometry, transform) { const { scale: { xScale, yScale }, - translate: { xTranslate, yTranslate } + translate: { xTranslate, yTranslate }, } = transform; const x = quantizeX(geometry.x, xScale, xTranslate); const y = quantizeY(geometry.y, yScale, yTranslate); + const coords = createCoordinatesArray(x, y, geometry.z, geometry.m); return { - coords: createCoordinatesArray(x, y, geometry.z, geometry.m).map((int) => - int.toString(), - ), + lengths: [coords.length], + coords, }; } @@ -67,10 +67,7 @@ function transformPolygon(geometry, transform, hasZ, hasM) { return { lengths: rings.map((ring) => ring.length), - coords: rings - .flat() - .flat() - .map((int) => int.toString()), + coords: rings.flat().flat(), }; } @@ -79,10 +76,7 @@ function transformPolyline(geometry, transform) { return { lengths: paths.map((path) => path.length), - coords: paths - .flat() - .flat() - .map((int) => int.toString()), + coords: paths.flat().flat(), }; } @@ -110,7 +104,7 @@ function quantizeY(y, scale, translate) { function transformCoordsArray(coordsArray, transform) { const { scale: { xScale, yScale }, - translate: { xTranslate, yTranslate } + translate: { xTranslate, yTranslate }, } = transform; const result = []; let prevX; diff --git a/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-to-pbf-geometry.spec.js b/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-to-pbf-geometry.spec.js index 0651e9464..f2b4fedce 100644 --- a/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-to-pbf-geometry.spec.js +++ b/packages/featureserver/src/response-handlers/helpers/send-pbf/transform-to-pbf-geometry.spec.js @@ -41,7 +41,8 @@ describe('transformToPbfGeometry', () => { }; const result = transformToPbfGeometry(fixture, defaultTransform); result.should.deepEqual({ - coords: ['114660426458', '-349455113943'], + lengths: [2], + coords: [114660426458, -349455113943], }); }); @@ -54,7 +55,8 @@ describe('transformToPbfGeometry', () => { }; const result = transformToPbfGeometry(fixture, defaultTransform); result.should.deepEqual({ - coords: ['114660426458', '-349455113943', '100', '25.3'], + lengths: [4], + coords: [114660426458, -349455113943, 100, 25.3], }); }); @@ -76,12 +78,12 @@ describe('transformToPbfGeometry', () => { result.should.deepEqual({ lengths: [3], coords: [ - '64567221232', - '-357065421833', - '3339584724', - '0', - '2226389816', - '1534703364', + 64567221232, + -357065421833, + 3339584724, + 0, + 2226389816, + 1534703364, ], }); }); @@ -100,12 +102,12 @@ describe('transformToPbfGeometry', () => { result.should.deepEqual({ lengths: [3], coords: [ - '65737760897', - '-361640524478', - '-587036377', - '-1064003433', - '464737132', - '-183448868', + 65737760897, + -361640524478, + -587036377, + -1064003433, + 464737132, + -183448868, ], }); }); @@ -128,16 +130,16 @@ describe('transformToPbfGeometry', () => { result.should.deepEqual({ lengths: [5], coords: [ - '65680416140', - '-363479548349', - '0', - '1647807625', - '-1113194908', - '0', - '0', - '-1647807625', - '1113194908', - '0', + 65680416140, + -363479548349, + 0, + 1647807625, + -1113194908, + 0, + 0, + -1647807625, + 1113194908, + 0, ], }); }); @@ -154,7 +156,7 @@ describe('transformToPbfGeometry', () => { const result = transformToPbfGeometry(fixture, defaultTransform); result.should.deepEqual({ lengths: [2], - coords: ['66793611048', '-358626214862', '1113194908', '-1588277341'], + coords: [66793611048, -358626214862, 1113194908, -1588277341], }); }); }); @@ -178,16 +180,16 @@ describe('transformToPbfGeometry', () => { result.should.deepEqual({ lengths: [3, 2], coords: [ - '62340831416', - '-358626214862', - '4452779632', - '0', - '0', - '-3205525862', - '62340831416', - '-354020794440', - '4452779632', - '0', + 62340831416, + -358626214862, + 4452779632, + 0, + 0, + -3205525862, + 62340831416, + -354020794440, + 4452779632, + 0, ], }); }); @@ -217,26 +219,26 @@ describe('transformToPbfGeometry', () => { result.should.deepEqual({ lengths: [5, 5], coords: [ - '87944314299', - '-346802067873', - '0', - '-1403190256', - '2226389816', - '0', - '0', - '1403190256', - '-2226389816', - '0', - '92397093931', - '-344049811441', - '0', - '-1367402279', - '2226389815', - '0', - '0', - '1367402279', - '-2226389815', - '0', + 87944314299, + -346802067873, + 0, + -1403190256, + 2226389816, + 0, + 0, + 1403190256, + -2226389816, + 0, + 92397093931, + -344049811441, + 0, + -1367402279, + 2226389815, + 0, + 0, + 1367402279, + -2226389815, + 0, ], }); }); diff --git a/packages/featureserver/test/integration/layers.spec.js b/packages/featureserver/test/integration/layers.spec.js index 139f8a302..9172d09a5 100644 --- a/packages/featureserver/test/integration/layers.spec.js +++ b/packages/featureserver/test/integration/layers.spec.js @@ -15,6 +15,7 @@ describe('Layers operations', () => { layers: [ { currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'Snow', type: 'Feature Layer', @@ -231,7 +232,7 @@ describe('Layers operations', () => { renderer: { type: 'simple', symbol: { - color: [45, 172, 128, 161], + color: [247, 150, 70, 161], outline: { color: [190, 190, 190, 105], width: 0.5, diff --git a/test/geoservice-client-requests-no-geom.spec.js b/test/geoservice-client-requests-no-geom.spec.js index 9129d298a..c7d6f462d 100644 --- a/test/geoservice-client-requests-no-geom.spec.js +++ b/test/geoservice-client-requests-no-geom.spec.js @@ -114,6 +114,7 @@ describe('Typical Geoservice Client request sequence: Dataset with no geometry', { currentVersion: CURRENT_VERSION, id: 0, + supportedPbfFeatureEncodings: 'esriDefault', name: 'no-geom-w-objectid.geojson', type: 'Table', description: 'GeoJSON from no-geom-w-objectid.geojson', @@ -279,6 +280,7 @@ describe('Typical Geoservice Client request sequence: Dataset with no geometry', const info = response.body; expect(info).toEqual({ currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: 'no-geom-w-objectid.geojson', type: 'Table', diff --git a/test/helpers/client-response-fixtures.js b/test/helpers/client-response-fixtures.js index b4be4c09d..1f87184f4 100644 --- a/test/helpers/client-response-fixtures.js +++ b/test/helpers/client-response-fixtures.js @@ -71,6 +71,7 @@ function getLayersInfo(filename, idField) { layers: [ { currentVersion: CURRENT_VERSION, + supportedPbfFeatureEncodings: 'esriDefault', id: 0, name: `${filename}.geojson`, type: 'Feature Layer', @@ -193,7 +194,7 @@ function getLayersInfo(filename, idField) { renderer: { type: 'simple', symbol: { - color: [45, 172, 128, 161], + color: [247, 150, 70, 161], outline: { color: [190, 190, 190, 105], width: 0.5,