├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── index.js
├── lib
└── utils.js
├── package.json
└── test
├── fixtures
├── count.json
├── countFailedPaging.json
├── countNoPaging.json
├── countPaging.json
├── countStatsFail.json
├── features-empty.json
├── features.json
├── hostedLayerInfo.json
├── ids10.0.json
├── idsStatsFail.json
├── layer.json
├── layer10.0.json
├── layerInfo.json
├── layerNoPaging.json
├── layerPaging.json
├── layerStatsFail.json
├── objectIds.json
├── page.json
├── secured.json
├── serviceInfo.json
├── stats.json
├── statsCaps.json
├── statsFail.json
├── statsFailedPaging.json
├── statsNoPaging.json
└── uncompressed.json
└── index.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['standard']
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | tags
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'stable'
4 | sudo: false
5 | cache:
6 | directories:
7 | - node_modules
8 | script:
9 | - npm test
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | ## [1.6.2] - 2019-11-05
6 | ### Changed
7 | * Throw `TypeError` when URL is not a FeatureServer or MapServer with optional layer
8 |
9 | ## [1.6.1] - 2018-11-28
10 | ### Changed
11 | * Double check pagination support with a request for the second page
12 |
13 | ## [1.6.0] - 2018-06-19
14 | ### Added
15 | * Check for an option `outSR` and if provided use it to transform data; if no outSR option default to `outSR=4326`
16 |
17 | ## [1.5.13] - 2018-01-31
18 | ### Fixed
19 | * Parse out html at the end of an otherwise valid response
20 |
21 | ## [1.5.12] - 2017-12-29
22 | ### Changed
23 | * Z value is requested when paging
24 |
25 | ## [1.5.11] - 2016-10-23
26 | ### Fixed
27 | * Use options.size to restrict max
28 |
29 | ## [1.5.10] - 2016-10-23
30 | ### Changed
31 | * Don't use forever agent on http requests
32 |
33 | ## [1.5.9] - 2016-10-20
34 | ### Changed
35 | * Use request library instead of hand-rolled http(s) requests
36 |
37 | ## [1.5.8] - 2016-10-16
38 | ### Changed
39 | * Don't set min max OIDs for a single page request
40 |
41 | ## [1.5.7] - 2016-04-07
42 | ### Changed
43 | * Remove unneeded try/catch
44 |
45 | ## [1.5.6] - 2016-04-06
46 | ### Fixed
47 | * Get correct objectID range even when field names are unexpectedly capitalized
48 |
49 | ## [1.5.5] - 2016-03-31
50 | ### Changed
51 | * Exposed `getObjectIdRange` as public function
52 |
53 | ### Fixed
54 | * Corrected bug in statistics paging strategy
55 |
56 | ## [1.5.4] - 2016-03-30
57 | ### Fixed
58 | * Don't throw exception when passed in layer is of type numeric
59 |
60 | ## [1.5.3] - 2016-03-23
61 | ### Changed
62 | * A passed-in layer will always override what's in the url
63 |
64 | ## [1.5.2] - 2016-03-03
65 | ### Fixed
66 | * Get the object id field if it's explicitly in info properties
67 | * resultOffset strategy was reversed
68 |
69 | ## [1.5.1] - 2016-03-02
70 | ### Changed
71 | * Don't use resultOffset for services not hosted on ArcGIS Online, it's not reliable
72 | * Decode streaming server response
73 |
74 | ## [1.5.0] - 2016-02-28
75 | ### Added
76 | * Service Info, Layer Info, and Metadata are memoized
77 | * Can call `info`, `layerInfo`, and `metadata` without a callback when info is memoized
78 |
79 | ### Changed
80 | * Concurrency is set during paging process
81 | * URL parsing for services is more precise
82 |
83 | ## [1.4.6] - 2016-01-20
84 | ### Fixed
85 | * Guard against missing ObjectID field when trying to page
86 | * Remove unused dep
87 |
88 | ## [1.4.5] - 2016-01-13
89 | ### Changed
90 | * Throttle concurrency down on every 2 failed requests
91 | * Throttle concurrency up on every 10 successful requests
92 |
93 | ## [1.4.4] - 2016-01-07
94 | ### Fixed
95 | * Removed bad reference to `this`
96 |
97 | ## [1.4.3] - 2016-01-04
98 | ### Changed
99 | * Concurrency is throttled when encountering errors in the paging queue
100 | ### Fixed
101 | * Urls and Layers are parsed correctly on service init
102 |
103 | ## [1.4.2] - 2015-10-13
104 | ### Changed
105 | * Removed callback from parse try statement
106 |
107 | ## [1.4.1] - 2015-10-13
108 | ### Fixed
109 | * Removed log statement that was causing the process to crash
110 |
111 | ## [1.4.0] - 2015-10-12
112 | ### Added
113 | * Backoff time for failed requests can now be set as an option
114 | * `service.info` returns information about the feature or map service
115 |
116 | ### Changed
117 | * Return the page that was successfully completed when getting features
118 | * Response parsing has been moved to its own function and the callback is no longer wrapped in a try/catch
119 |
120 | ### Fixed
121 | * No longer failing to parse multi-chunk feature service responses that are not compressed
122 |
123 | ## [1.3.1]
124 | ### Fixed
125 | * Log retry urls correclty
126 |
127 | ## [1.3.0]
128 | ### Added
129 | * Allow a logger to be passed in through options.logger when initializing a new service
130 |
131 | ### Fixed
132 | * Don't throw exception when there is no error in the json returned from server
133 | * Catch non-json responses explicitly
134 | * Don't use zlib methods unsupported in node 0.10.* in testing
135 | * Error timestamps are all written in same case
136 |
137 | ## [1.2.7] - 2015-09-03
138 | ### Fixed
139 | * Correct logic for correcting against poorly instantiated FeatureServices
140 |
141 | ## [1.2.6] - 2015-09-02
142 | ### Fixed
143 | * Don't set a layer id with a query string
144 | * Don't set a url with a query string
145 |
146 | ## [1.2.5] - 2015-09-02
147 | ### Fixed
148 | * Uncaught error when `json.error` doesn't exist in `FeatureService.layerInfo`
149 |
150 | ## [1.2.4] - 2015-09-01
151 | ### Changed
152 | * Restrict max record count to 5000 or less
153 |
154 | ### Fixed
155 | * Pages generated from statistics use the correct layer when index > 0
156 |
157 | ## [1.2.3] - 2015-08-31
158 | ### Fixed
159 | * Do not exclude object ids === 0 when building pages
160 |
161 | ## [1.2.2] - 2015-08-26
162 | ### Changed
163 | * Errors after timeouts conform to standard
164 | * Errors on metadata requests conform to standard
165 |
166 | ### Fixed
167 | * Requests are ended correctly after timeouts
168 |
169 | ## [1.2.1] - 2015-08-17
170 | ### Changed
171 | * All requests accept gzip or deflate compressed
172 | * Requests are decoded async (for compatibility with node < 0.11.12)
173 |
174 | ### Fixed
175 | * Pages are built correctly for layers with an index > 0
176 |
177 | ## [1.2.0] - 2015-08-11
178 | ### Fixed
179 | * Pages based on object ids are now formed correctly
180 |
181 | ### Changed
182 | * All errors returned to the requestor are standardized
183 |
184 | ## [1.1.1] - 2015-08-10
185 | ### Fixed
186 | * Build dist for changes
187 |
188 | ## [1.1.0] - 2015-08-10
189 | ### Added
190 | * New fixtures and integration tests for paging
191 | * Support for paging layers from server version 10.0
192 | * New fixtures and tests for decoding
193 |
194 | ### Changed
195 | * Refactored paging strategy
196 | * Moved feature request decoding into isolated function
197 |
198 | ### Fixed
199 | * Catch errors that come on 200 responses
200 | * Errors are reported correctly up the chain
201 | * Retries for all errors
202 |
203 | ## [1.0.0] - 2015-08-07
204 | ### Added
205 | * New function gets objectID from service info
206 |
207 | ### Changed
208 | * Moved to koopjs github organization
209 |
210 | ### Fixed
211 | * detach http/https modules from FeatureService instances
212 |
213 | ## [0.2.0] - 2015-08-06
214 | ### Added
215 | * Feature requests time out
216 |
217 | ### Changed
218 | * Timeout set at 90 seconds
219 |
220 | ### Fixed
221 | * Reference to non-existent functions
222 |
223 | ## [0.1.0] - 2015-08-05
224 | ### Added
225 | * Feature service requests time out after 5 minutes of inactivity by default
226 |
227 | ### Changed
228 | * TCP sockets are kept alive
229 |
230 | ## [0.0.4] - 2015-07-29
231 | ### Fixed
232 | * request method was calling the callback twice when it wrapped a callback in a try/catch
233 | * passing min and max to statsUrl for creating stats in pages method
234 |
235 | ## [0.0.3] - 2015-07-28
236 | ### Fixed
237 | * A change made in v0.0.2 broke pagination. Its now fixed and a test for pages was added.
238 |
239 | ## [0.0.2] - 2015-07-27
240 | ### Added
241 | * A method for making statistics calls to a service
242 | * Support for using the module in the browser
243 |
244 | ### Changed
245 | * http requests are all routed through the core http/https libs now
246 |
247 | ## [0.0.1] - 2015-07-22
248 | ### Added
249 | * Code for requesting data from FeatureServices
250 | * Tests on most methods
251 |
252 | [1.6.2]: https://github.com/koopjs/featureservice/compare/v1.6.1...v1.6.2
253 | [1.6.1]: https://github.com/koopjs/featureservice/compare/v1.6.0...v1.6.1
254 | [1.6.0]: https://github.com/koopjs/featureservice/compare/v1.5.13...v1.6.0
255 | [1.5.13]: https://github.com/koopjs/featureservice/compare/v1.5.12...v1.5.13
256 | [1.5.12]: https://github.com/koopjs/featureservice/compare/v1.5.11...v1.5.12
257 | [1.5.11]: https://github.com/koopjs/featureservice/compare/v1.5.10...v1.5.11
258 | [1.5.10]: https://github.com/koopjs/featureservice/compare/v1.5.9...v1.5.10
259 | [1.5.9]: https://github.com/koopjs/featureservice/compare/v1.5.8...v1.5.9
260 | [1.5.8]: https://github.com/koopjs/featureservice/compare/v1.5.7...v1.5.8
261 | [1.5.7]: https://github.com/koopjs/featureservice/compare/v1.5.6...v1.5.7
262 | [1.5.6]: https://github.com/koopjs/featureservice/compare/v1.5.5...v1.5.6
263 | [1.5.5]: https://github.com/koopjs/featureservice/compare/v1.5.4...v1.5.5
264 | [1.5.4]: https://github.com/koopjs/featureservice/compare/v1.5.3...v1.5.4
265 | [1.5.3]: https://github.com/koopjs/featureservice/compare/v1.5.2...v1.5.3
266 | [1.5.2]: https://github.com/koopjs/featureservice/compare/v1.5.1...v1.5.2
267 | [1.5.1]: https://github.com/koopjs/featureservice/compare/v1.5.0...v1.5.1
268 | [1.5.0]: https://github.com/koopjs/featureservice/compare/v1.4.6...v1.5.0
269 | [1.4.6]: https://github.com/koopjs/featureservice/compare/v1.4.5...v1.4.6
270 | [1.4.5]: https://github.com/koopjs/featureservice/compare/v1.4.4...v1.4.5
271 | [1.4.4]: https://github.com/koopjs/featureservice/compare/v1.4.3...v1.4.4
272 | [1.4.3]: https://github.com/koopjs/featureservice/compare/v1.4.2...v1.4.3
273 | [1.4.2]: https://github.com/koopjs/featureservice/compare/v1.4.1...v1.4.2
274 | [1.4.1]: https://github.com/koopjs/featureservice/compare/v1.4.0...v1.4.1
275 | [1.4.0]: https://github.com/koopjs/featureservice/compare/v1.3.1...v1.4.0
276 | [1.3.1]: https://github.com/koopjs/featureservice/compare/v1.3.0...v1.3.1
277 | [1.3.0]: https://github.com/koopjs/featureservice/compare/v1.2.7...v1.3.0
278 | [1.2.7]: https://github.com/koopjs/featureservice/compare/v1.2.6...v1.2.7
279 | [1.2.6]: https://github.com/koopjs/featureservice/compare/v1.2.5...v1.2.6
280 | [1.2.5]: https://github.com/koopjs/featureservice/compare/v1.2.4...v1.2.5
281 | [1.2.4]: https://github.com/koopjs/featureservice/compare/v1.2.3...v1.2.4
282 | [1.2.3]: https://github.com/koopjs/featureservice/compare/v1.2.2...v1.2.3
283 | [1.2.2]: https://github.com/koopjs/featureservice/compare/v1.2.1...v1.2.2
284 | [1.2.1]: https://github.com/koopjs/featureservice/compare/v1.2.0...v1.2.1
285 | [1.2.0]: https://github.com/koopjs/featureservice/compare/v1.1.1...v1.2.0
286 | [1.1.1]: https://github.com/koopjs/featureservice/compare/v1.1.0...v1.1.1
287 | [1.1.0]: https://github.com/koopjs/featureservice/compare/v1.0.0...v1.1.0
288 | [1.0.0]: https://github.com/koopjs/featureservice/compare/v0.2.0...v1.0.0
289 | [0.2.0]: https://github.com/koopjs/featureservice/compare/v0.1.0...v0.2.0
290 | [0.1.0]: https://github.com/koopjs/featureservice/compare/v0.0.4...v0.1.0
291 | [0.0.4]: https://github.com/koopjs/featureservice/compare/v0.0.3...v0.0.4
292 | [0.0.3]: https://github.com/koopjs/featureservice/compare/v0.0.2...v0.0.3
293 | [0.0.2]: https://github.com/koopjs/featureservice/compare/v0.0.1...v0.0.2
294 | [0.0.1]: https://github.com/koopjs/featureservice/releases/tag/v0.0.1
295 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 Esri
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # featureservice
2 |
3 | *Get all features from an Esri Feature Service*
4 |
5 | [![npm][npm-image]][npm-url]
6 | [![travis][travis-image]][travis-url]
7 | [![Greenkeeper badge][greenkeeper-image]][greenkeeper-url]
8 |
9 | [npm-image]: https://img.shields.io/npm/v/featureservice.svg?style=flat-square
10 | [npm-url]: https://www.npmjs.com/package/featureservice
11 | [travis-image]: https://img.shields.io/travis/koopjs/featureservice.svg?style=flat-square
12 | [travis-url]: https://travis-ci.org/koopjs/featureservice
13 | [greenkeeper-image]: https://badges.greenkeeper.io/koopjs/featureservice.svg
14 | [greenkeeper-url]: https://greenkeeper.io/
15 |
16 | A little module that extracts every feature from an Esri Feature Service. The real power in this module is that it's designed to page over a service and extract every single feature no matter what ArcGIS Server version the data is hosted on.
17 |
18 | ## Install
19 |
20 | ```
21 | npm install featureservice
22 | ```
23 |
24 | ## Usage
25 |
26 | ```javascript
27 | var FeatureService = require('featureservice')
28 |
29 | // a url to a feature service
30 | var url = 'http://....../FeatureServer/0'
31 |
32 | var service = new FeatureService(url, options)
33 | service.pages(function (err, pages) {
34 | /* will give you links to all pages of data in the service*/
35 | })
36 | ```
37 |
38 | ### Options
39 | An object passed as the second parameter when initializing a service
40 | - layer: the layer index to use
41 | - size: the maximum page size when requesting features
42 | - concurrency: the maximum concurrency for requesting features from a single server
43 | - timeOut: the amount of time to wait with no response before cancelling a request
44 | - logger: An object with a log method that takes a level and a message e.g. a Winston instance
45 |
46 | ### API
47 |
48 | #### info(callback)
49 | Get the information describing the service itself
50 |
51 | #### layerIds(callback)
52 | Get all the ids in a feature service layer
53 |
54 | #### layerInfo(callback)
55 | Get the json metadata for a service layer
56 |
57 | #### statistics(field, stats, callback)
58 | Get statistics for a field and an array of stats.
59 |
60 | ```javascript
61 | service.statistics('id', ['min', 'max'], function (err, stats) {
62 | console.log(stats.features)
63 | })
64 | ```
65 |
66 | #### pages(callback)
67 | Returns an array of page urls that would get every feature in the service
68 |
69 | ### Browser
70 |
71 | A browser ready build of this module is in `dist/featureservice.min.js`.
72 |
73 | #### Example
74 |
75 | ```html
76 |
77 |
78 |
84 |
85 | ```
86 |
87 | ## License
88 |
89 | [Apache 2.0](LICENSE)
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var queue = require('async').queue
2 | var Utils = require('./lib/utils.js')
3 |
4 | /**
5 | * Feature Service constructor. Requires a URL.
6 | * Exposes a pageQueue that can take an array of page URLs.
7 | * The pageQueue will report back each page of features as they return.
8 | *
9 | * @class
10 | * @param {string} url - address of feature service
11 | * @param {object} options - layer (default: 0)
12 | */
13 | var FeatureService = function (url, options) {
14 | // catch omission of `new` keyword
15 | if (!(this instanceof FeatureService)) {
16 | return new FeatureService(url, options)
17 | }
18 | var service = Utils.parseUrl(url)
19 | this.hosted = service.hosted
20 | this.server = service.server
21 | this.options = options || {}
22 | if (this.options.layer || this.options.layer === 0) this.options.layer = Utils.sanitizeLayer(this.options.layer)
23 | this.options.size = this.options.size || 5000
24 | this.options.backoff = this.options.backoff || 1000
25 | this.options.timeOut = this.options.timeOut || (1.5 * 60 * 1000)
26 | this.layer = this.options.layer || service.layer || 0
27 | this.outSR = this.options.outSR || 4326
28 |
29 | this.logger = this.options.logger
30 |
31 | this._request = require('xhr-request')
32 |
33 | // an async for requesting pages of data
34 | this.pageQueue = queue(this._requestFeatures.bind(this), this.options.concurrency || 4)
35 | }
36 |
37 | /**
38 | * Wraps logging functionality so a logger can be passed in
39 | *
40 | * @param {string} level - the log level to use
41 | * @param {string} message - the message to log
42 | */
43 | FeatureService.prototype.log = function (level, message) {
44 | if (this.logger && this.logger.log) return this.logger.log(level, message)
45 | this._console(level, message)
46 | }
47 |
48 | /**
49 | * Wraps console logging to make it testable
50 | *
51 | * @param {string} level - the log level to use
52 | * @param {string} message - the message to log
53 | */
54 | FeatureService.prototype._console = function (level, message) {
55 | switch (level) {
56 | case 'info':
57 | console.info(message)
58 | break
59 | case 'warn':
60 | console.warn(message)
61 | break
62 | case 'error':
63 | console.error(message)
64 | break
65 | default:
66 | console.log(message)
67 | }
68 | }
69 |
70 | /**
71 | * Wrap the request.get method for easier testing
72 | * @param {string} url
73 | * @param {function} callback
74 | */
75 | // TODO combine this with _requestFeatures
76 | FeatureService.prototype.request = function (url, callback) {
77 | var json
78 | // to ensure things are encoded just right for ArcGIS
79 | var encoded = encodeURI(decodeURI(url))
80 | var options = {
81 | timeout: this.options.timeOut,
82 | headers: {
83 | 'user-agent': 'Featureservices-Node'
84 | }
85 |
86 | }
87 | this._request(encoded, options, function (err, data, res) {
88 | if (err) {
89 | if (err.code === 'ESOCKETTIMEDOUT') err.code = 504
90 | return callback(err)
91 | }
92 | try {
93 | json = JSON.parse(data)
94 | } catch (err) {
95 | // sometimes we get html or plain strings back
96 | var pattern = new RegExp(/[^{\[]/) // eslint-disable-line
97 | if (data.slice(0, 1).match(pattern)) {
98 | return callback(new Error('Received HTML or plain text when expecting JSON'))
99 | } else if (/\n(\n|.|\s)*$/.test(data)) {
100 | try {
101 | json = JSON.parse(data.replace(/\n(\n|.|\s)*$/, ''))
102 | return callback(null, json)
103 | } catch (e) {
104 | return callback(new Error('Failed to parse server response'))
105 | }
106 | }
107 | return callback(new Error('Failed to parse server response'))
108 | }
109 | callback(null, json)
110 | })
111 | }
112 |
113 | /**
114 | * Builds a url for querying the min/max values of the object id
115 | *
116 | * @param {string} field - the name of a field to build a stat request for
117 | * @returns {string} url
118 | */
119 | FeatureService.prototype._statsUrl = function (field, stats) {
120 | field = field || this.options.objectIdField
121 | var json = []
122 |
123 | stats.forEach(function (stat) {
124 | json.push({
125 | 'statisticType': stat,
126 | 'onStatisticField': field,
127 | 'outStatisticFieldName': stat + '_' + field
128 | })
129 | })
130 |
131 | return this.server + '/' + this.layer + '/query?f=json&outFields=&outStatistics=' + JSON.stringify(json)
132 | }
133 |
134 | /**
135 | * Gets the feature service info
136 | * @param {string} field - the name of a field to build a stat request for
137 | * @param {array} stats - an array of stats to request: ['min', 'max', 'avg', 'stddev', 'count']
138 | */
139 | FeatureService.prototype.statistics = function (field, stats, callback) {
140 | var url = this._statsUrl(field, stats)
141 | this.request(url, function (err, json) {
142 | if (err || json.error) {
143 | if (!json) json = {error: {}}
144 | var error = new Error('Request for statistics failed')
145 | error.timestamp = new Date()
146 | error.code = json.error.code || 500
147 | error.body = err || json.error
148 | error.url = url
149 | return callback(error)
150 | }
151 | callback(null, json)
152 | })
153 | }
154 |
155 | /**
156 | * Gets the feature service info
157 | * @param {function} callback - called when the service info comes back
158 | */
159 | FeatureService.prototype.info = function (callback) {
160 | if (typeof callback === 'undefined') return this._info
161 | if (this._info) return callback(null, this._info)
162 | var url = this.server + '?f=json'
163 | this.request(url, function (err, json) {
164 | /**
165 | * returns error on three conditions:
166 | * 1. err is present
167 | * 2. missing response json
168 | * 3. error in response json
169 | */
170 | if (err || !json || json.error) {
171 | if (!json) json = {error: {}}
172 | var error = new Error('Request for service information failed')
173 | error.timestamp = new Date()
174 | error.url = url
175 | error.code = json.error.code || 500
176 | error.body = json.error
177 |
178 | return callback(error)
179 | }
180 | this._info = json
181 | json.url = url
182 | callback(null, json)
183 | })
184 | }
185 |
186 | /**
187 | * Gets the feature service layer info
188 | * @param {function} callback - called when the layer info comes back
189 | */
190 | FeatureService.prototype.layerInfo = function (callback) {
191 | // used saved version if available
192 | if (typeof callback === 'undefined') return this._layerInfo
193 | if (this._layerInfo) return callback(null, this._layerInfo)
194 |
195 | var url = this.server + '/' + this.layer + '?f=json'
196 |
197 | this.request(url, function (err, json) {
198 | /**
199 | * returns error on three conditions:
200 | * 1. err is present
201 | * 2. missing response json
202 | * 3. error in response json
203 | */
204 | if (err || !json || json.error) {
205 | if (!json) json = {error: {}}
206 | var error = new Error('Request for layer information failed')
207 | error.timestamp = new Date()
208 | error.url = url
209 | error.code = json.error.code || 500
210 | error.body = json.error
211 |
212 | return callback(error)
213 | }
214 |
215 | json.url = url
216 | callback(null, json)
217 | })
218 | }
219 |
220 | /**
221 | * Gets the objectID field from the service info
222 | @param {object} info the feature layer metadata
223 | @returns {string} service's object id field
224 | */
225 | FeatureService.prototype.getObjectIdField = function (info) {
226 | var oid
227 | if (!info.fields) return false
228 | if (info.objectIdField) return info.objectIdField
229 | info.fields.some(function (field) {
230 | if (field.type === 'esriFieldTypeOID') {
231 | oid = field.name
232 | return true
233 | }
234 | })
235 |
236 | return oid
237 | }
238 |
239 | /**
240 | * Gets the feature service object ids for pagination
241 | * @param {object} callback - called when the service info comes back
242 | */
243 | FeatureService.prototype.layerIds = function (callback) {
244 | var url = this.server + '/' + this.layer + '/query?where=1=1&returnIdsOnly=true&f=json'
245 | this.request(url, function (err, json) {
246 | if (err || !json.objectIds) {
247 | if (!json) json = {error: {}}
248 | var error = new Error('Request for object IDs failed')
249 | error.timestamp = new Date()
250 | error.code = json.error.code || 500
251 | error.url = url
252 | error.body = err || json.error
253 |
254 | return callback(error)
255 | }
256 | // TODO: is this really necessary
257 | json.objectIds.sort(function (a, b) { return a - b })
258 | callback(null, json.objectIds)
259 | })
260 | }
261 |
262 | /**
263 | * Count of every single feature in the service
264 | * @param {object} callback - called when the service info comes back
265 | */
266 | FeatureService.prototype.featureCount = function (callback) {
267 | var countUrl = this.server + '/' + (this.layer || 0)
268 | countUrl += '/query?where=1=1&returnCountOnly=true&f=json'
269 |
270 | this.request(countUrl, function (err, json) {
271 | if (err || json.error) {
272 | // init empty json error so we can handle building the error in one logic stream
273 | if (!json) json = {error: {}}
274 | var error = new Error('Request for feature count failed')
275 | error.timestamp = new Date()
276 | error.code = json.error.code || 500
277 | error.url = countUrl
278 | error.body = err || json.error
279 |
280 | return callback(error)
281 | }
282 |
283 | callback(null, json)
284 | })
285 | }
286 |
287 | /**
288 | * Gets and derives layer metadata from two sources
289 | * @param {function} callback - called with an error or a metadata object
290 | */
291 | FeatureService.prototype.metadata = function (callback) {
292 | if (typeof callback === 'undefined') return this._metadata
293 | if (this._metadata) return callback(null, this._metadata)
294 |
295 | this.layerInfo(function (err, layer) {
296 | if (err) {
297 | err.message = 'Unable to get layer metadata: ' + err.message
298 | return callback(err)
299 | }
300 | this._layerInfo = layer
301 | var oid = this.getObjectIdField(layer)
302 | var size = layer.maxRecordCount
303 |
304 | // TODO flatten this
305 | var metadata = {layer: layer, oid: oid, size: size}
306 |
307 | // 10.0 servers don't support count requests
308 | // they also do not show current version on the layer
309 | if (!layer.currentVersion) return callback(null, metadata)
310 |
311 | this.featureCount(function (err, json) {
312 | if (err) return callback(err)
313 | if (json.count < 1) return callback(new Error('Service returned count of 0'))
314 | metadata.count = json.count
315 | this._metadata = metadata
316 | callback(null, metadata)
317 | })
318 | }.bind(this))
319 | }
320 |
321 | /**
322 | * Build an array pages that will cover every feature in the service
323 | * @param {function} callback - called when the service info comes back
324 | */
325 | FeatureService.prototype.pages = function (callback) {
326 | this.metadata(function (err, meta) {
327 | if (err) return callback(err)
328 | if (meta.count < meta.layer.maxRecordCount && meta.count < this.options.size) return callback(null, singlePage(this.server, this.layer, this.outSR))
329 | this.concurrency = this.options.concurrency || Utils.setConcurrency(this.hosted, meta.layer.geometryType)
330 | this.maxConcurrency = this.concurrency
331 | this.pageQueue.concurrency = this.concurrency
332 | var size = Math.min(parseInt(meta.size, 10), 1000) || 1000
333 | // restrict page size to the passed in maximum
334 | if (size > 5000) size = this.options.size
335 |
336 | var layer = meta.layer
337 | var nPages = Math.ceil(meta.count / size)
338 |
339 | // if the service supports paging, we can use offset to build pages
340 | var canPage = layer.advancedQueryCapabilities && layer.advancedQueryCapabilities.supportsPagination
341 | if (canPage) {
342 | // Test pagination by requesting features from second page - supportsPagination has not been reliable
343 | var url = this.server + '/0/query?f=json&where=1=1&outFields=*&resultOffset=' + size + '&resultRecordCount=' + size
344 | this.request(url, function (err, json) {
345 | if (err) return callback(err)
346 | // If json features were returned, assume pagination works; otherwise use paging alternatives
347 | if (json && json.features && json.features.length > 0) {
348 | return callback(null, this._offsetPages(nPages, size))
349 | } else {
350 | this.pagingAlternatives(meta, size, callback)
351 | }
352 | }.bind(this))
353 | } else {
354 | this.pagingAlternatives(meta, size, callback)
355 | }
356 | }.bind(this))
357 | }
358 |
359 | /**
360 | * Build pages when pagination is not supported
361 | * @param {function} callback - called when the service info comes back
362 | */
363 | FeatureService.prototype.pagingAlternatives = function (meta, size, callback) {
364 | if (!meta.oid) return callback(new Error('ObjectID type field not found, unable to page'))
365 | this.options.objectIdField = meta.oid
366 | // if the service supports statistics, we can request the maximum and minimum id to build pages
367 | if (meta.layer.supportsStatistics) {
368 | this.getObjectIdRange(meta.oid, function (err, stats) {
369 | // if this worked then we can pagination using where clauses
370 | if (!err) return callback(null, this._rangePages(stats, size))
371 | // if it failed, try to request all the ids and split them into pages
372 | this.layerIds(function (err, ids) {
373 | // either this works or we give up
374 | if (err) return callback(err)
375 | return callback(null, this._idPages(ids, size))
376 | }.bind(this))
377 | }.bind(this))
378 | } else {
379 | // this is the last thing we can try
380 | this.layerIds(function (err, ids) {
381 | if (err) return callback(err)
382 | callback(null, this._idPages(ids, size))
383 | }.bind(this))
384 | }
385 | }
386 |
387 | /**
388 | * Handle feature server request where total number of features can be acquired in a single page
389 | * @param {*} server
390 | * @param {*} layer
391 | * @param {integer} outSR - wkid of output spatial reference
392 | */
393 | function singlePage (server, layer, outSR = 4326) {
394 | return [{req: [server, '/', layer, '/query?where=1=1&returnGeometry=true&returnZ=true&outFields=*&outSR=' + outSR + '&f=json'].join('')}]
395 | }
396 |
397 | /**
398 | * Get the max and min object id
399 | * @param {object} meta - layer metadata, holds information needed to request oid stats
400 | * @param {function} callback - returns with an error or objectID stats
401 | */
402 | FeatureService.prototype.getObjectIdRange = function (oidField, callback) {
403 | this.statistics(oidField, ['min', 'max'], function (err, statResponse) {
404 | // TODO this is handled elsewhere now so move it
405 | if (err) return callback(err)
406 | var eMsg = 'Response from statistics was invalid'
407 | var stats
408 | try {
409 | stats = findMinAndMax(statResponse)
410 | } catch (e) {
411 | return callback(new Error(eMsg))
412 | }
413 | if (!stats.min > 0 && !stats.max > 0) return callback(new Error(eMsg))
414 | callback(null, stats)
415 | })
416 | }
417 |
418 | function findMinAndMax (statResponse) {
419 | var attributes = statResponse.features[0].attributes
420 | var values = Object.keys(attributes).map(function (key) {
421 | return attributes[key]
422 | })
423 | var minMax = {}
424 | minMax.min = values[0] < values[1] ? values[0] : values[1]
425 | minMax.max = values[1] > values[0] ? values[1] : values[0]
426 | return minMax
427 | }
428 |
429 | /**
430 | * build result Offset based page requests
431 | * these pages use Server's built in paging via resultOffset and resultRecordCount
432 | * @param {integer} pages - the number of pages we'll create
433 | * @param {integer} size - the max number of features per page
434 | * @returns {object} reqs - contains all the pages for extracting features
435 | */
436 | FeatureService.prototype._offsetPages = function (pages, size) {
437 | var reqs = []
438 | var resultOffset
439 | var url = this.server
440 |
441 | for (var i = 0; i < pages; i++) {
442 | resultOffset = i * size
443 | var pageUrl = url + '/' + this.layer + '/query?outSR=' + this.outSR + '&f=json&outFields=*&where=1=1'
444 | if (pages === 1) return [{req: pageUrl + '&geometry=&returnGeometry=true&returnZ=true&geometryPrecision='}]
445 | pageUrl += '&resultOffset=' + resultOffset
446 | pageUrl += '&resultRecordCount=' + size
447 | pageUrl += '&geometry=&returnGeometry=true&returnZ=true&geometryPrecision='
448 | reqs.push({req: pageUrl})
449 | }
450 |
451 | return reqs
452 | }
453 |
454 | /**
455 | * build `id` query based page requests
456 | * these pages use object ids in URLs directly
457 | * @param {array} ids - an array of each object id in the service
458 | * @param {integer} size - the max record count for each page
459 | * @returns {object} reqs - contains all the pages for extracting features
460 | */
461 | FeatureService.prototype._idPages = function (ids, size) {
462 | var reqs = []
463 | var oidField = this.options.objectIdField || 'objectId'
464 | var pages = (ids.length / size)
465 |
466 | for (var i = 0; i < pages + 1; i++) {
467 | var pageIds = ids.splice(0, size)
468 | if (pageIds.length) {
469 | var pageMin = pageIds[0]
470 | var pageMax = pageIds.pop()
471 | var where = [oidField, ' >= ', pageMin, ' AND ', oidField, '<=', pageMax].join('')
472 | var pageUrl = this.server + '/' + (this.layer) + '/query?outSR=' + this.outSR + '&where=' + where + '&f=json&outFields=*'
473 | pageUrl += '&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=10'
474 | reqs.push({req: pageUrl})
475 | }
476 | }
477 |
478 | return reqs
479 | }
480 |
481 | /**
482 | * build object id query based page requests
483 | * these pages use object ids in where clauses via < and >
484 | * you could call this objectId queries
485 | * @param {object} stats - contains the max and min object id
486 | * @param {integer} size - the size of records to include in each page
487 | * @returns {object} reqs - contains all the pages for extracting features
488 | */
489 | FeatureService.prototype._rangePages = function (stats, size) {
490 | var reqs = []
491 | var pageUrl
492 | var pageMax
493 | var pageMin
494 | var where
495 | var objId = this.options.objectIdField
496 |
497 | var url = this.server
498 | var pages = Math.max((stats.max === size) ? stats.max : Math.ceil((stats.max - stats.min) / size), 1)
499 |
500 | for (var i = 0; i < pages; i++) {
501 | // there is a bug in server where queries fail if the max value queried is higher than the actual max
502 | // so if this is the last page, then set the max to be the maxOID
503 | if (i === pages - 1) {
504 | pageMax = stats.max
505 | } else {
506 | pageMax = stats.min + (size * (i + 1)) - 1
507 | }
508 | pageMin = stats.min + (size * i)
509 | where = [objId, '>=', pageMin, '+AND+', objId, '<=', pageMax].join('')
510 | pageUrl = url + '/' + (this.layer || 0) + '/query?outSR=' + this.outSR + '&where=' + where + '&f=json&outFields=*'
511 | pageUrl += '&geometry=&returnGeometry=true&returnZ=true&geometryPrecision='
512 | reqs.push({req: pageUrl})
513 | }
514 |
515 | return reqs
516 | }
517 |
518 | /**
519 | * Requests a page of features
520 | * @param {object} task - a task object with a "req" property
521 | * @param {function} callback
522 | */
523 | FeatureService.prototype._requestFeatures = function (task, cb) {
524 | var self = this
525 |
526 | this.request(task.req, function (err, json) {
527 | if (err) return self._catchErrors(task, err, task.req, cb)
528 | if (!json || json.error) {
529 | if (!json) json = {error: {}}
530 | var error = new Error('Request for a page of features failed')
531 | error.timestamp = new Date()
532 | error.body = json.error
533 | error.code = json.error.code || 500
534 | return self._catchErrors(task, error, task.req, cb)
535 | }
536 | self._throttleQueue()
537 | cb(null, json)
538 | })
539 | }
540 |
541 | /* Catches an errors during paging and handles retry logic
542 | * @param {object} task - the currently executing job
543 | * @param {object} e - the error in application logic or from a failed request to a server
544 | * @param {string} url - the url of the last request for pages
545 | * @param {function} cb - callback passed through to the abort paging function
546 | */
547 | FeatureService.prototype._catchErrors = function (task, error, url, cb) {
548 | this._throttleQueue(error)
549 | // be defensive in case there was no json payload
550 | error.body = error.body || {}
551 | // set the error code from the json payload if the error doesn't have one already
552 | if (!error.code) error.code = error.body.code
553 | error.url = url
554 | if (task.retry && task.retry === 3) return this._abortPaging(error, cb)
555 | // initiate the count or increment it
556 | if (!task.retry) {
557 | task.retry = 1
558 | } else {
559 | task.retry++
560 | }
561 |
562 | this.log('info', 'Re-requesting page ' + task.req + ' attempt ' + task.retry)
563 |
564 | setTimeout(function () {
565 | this._requestFeatures(task, cb)
566 | }.bind(this), task.retry * this.options.backoff)
567 | }
568 |
569 | FeatureService.prototype._throttleQueue = function (fail) {
570 | if (fail) this.concurrency -= 0.5
571 | else this.concurrency += 0.1
572 |
573 | if (this.concurrency > this.maxConcurrency) this.concurrency = this.maxConcurrency
574 |
575 | this.pageQueue.concurrency = this.concurrency >= 1 ? Math.floor(this.concurrency) : 1
576 | }
577 |
578 | /**
579 | * Aborts the request queue by emptying all queued up tasks
580 | * @param {object} error - error payload to send back to the original requestor
581 | * @param {function} callback - calls back with the error payload
582 | */
583 | FeatureService.prototype._abortPaging = function (error, callback) {
584 | this.pageQueue.kill()
585 | error.message = 'Paging aborted: ' + error.message
586 | callback(error)
587 | }
588 |
589 | module.exports = FeatureService
590 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /**
3 | * Given a service url and a geometry type, determines a default concurrency for requests
4 | *
5 | * @param {boolean} hosted - whether or not the service is hosted on ArcGIS Online
6 | * @param {string} geomType - the geometry type of the features in the service
7 | * @return {integer} the suggested concurrency
8 | */
9 | setConcurrency: function (hosted, geomType) {
10 | var naieve = hosted ? 16 : 4
11 | if (!geomType) return naieve
12 | var concurrency = geomType.match(/point/i) ? naieve : naieve / 4
13 | return Math.floor(concurrency)
14 | },
15 | /**
16 | * Parsed the layer and server from a feature service url
17 | * @param {string} url - a link to a feature service
18 | * @return {object} contains the layer, the server and whether or not the server is hosted
19 | */
20 | parseUrl: function (url) {
21 | // @todo: use the URL class once pre-node 6 has been deprecated
22 | var match = url.match(/^(.+?\/(?:feature|map)server)(?:\/(\d+))?/i)
23 |
24 | if (null === match) {
25 | throw new TypeError('unable to parse ' + url + ' as a mapserver or featureserver with optional layer')
26 | }
27 |
28 | return {
29 | layer: match[2],
30 | server: match[1],
31 | hosted: /services(\d)?(qa|dev)?.arcgis.com/.test(url)
32 | }
33 | },
34 | /**
35 | * Strip characters off a layer that don't belong
36 | * @param {string} raw - a raw layer options
37 | * @return {string} the layer index
38 | */
39 | sanitizeLayer: function (raw) {
40 | if (typeof raw === 'number') return raw
41 | else if (typeof raw !== 'string') return undefined
42 |
43 | var match = raw.match(/\/?(\d+)/)
44 | return match && match[1] ? match[1] : undefined
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "featureservice",
3 | "description": "Get all features from an Esri Feature Service",
4 | "version": "1.6.2",
5 | "author": "Chris Helm",
6 | "bugs": {
7 | "url": "https://github.com/koopjs/featureservice/issues"
8 | },
9 | "contributors": [
10 | "Daniel Fenton"
11 | ],
12 | "dependencies": {
13 | "async": "^2.1.5",
14 | "xhr-request": "^1.0.1"
15 | },
16 | "devDependencies": {
17 | "browserify": "^14.1.0",
18 | "http-browserify": "^1.7.0",
19 | "https-browserify": "1.0.0",
20 | "lodash": "^4.0.0",
21 | "minifyify": "^7.0.3",
22 | "nock": "^9.0.9",
23 | "sinon": "^3.2.1",
24 | "standard": "^10.0.0",
25 | "tap-spec": "^4.1.2",
26 | "tape": "^4.9.1",
27 | "zlib-browserify": "0.0.3"
28 | },
29 | "homepage": "https://github.com/koopjs/featureservice",
30 | "keywords": [
31 | "esri",
32 | "featureservice",
33 | "gis"
34 | ],
35 | "license": "ISC",
36 | "main": "index.js",
37 | "repository": {
38 | "type": "git",
39 | "url": "https://github.com/koopjs/featureservice.git"
40 | },
41 | "scripts": {
42 | "build": "browserify -d index.js -s FeatureService -p [minifyify --map featureservice.map.json --output dist/featureservice.map.json] > dist/featureservice.min.js",
43 | "test": "standard index.js && tape test/*.js | tap-spec"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/fixtures/count.json:
--------------------------------------------------------------------------------
1 | {"count":82}
--------------------------------------------------------------------------------
/test/fixtures/countFailedPaging.json:
--------------------------------------------------------------------------------
1 | {
2 | "count": 15592
3 | }
--------------------------------------------------------------------------------
/test/fixtures/countNoPaging.json:
--------------------------------------------------------------------------------
1 | {
2 | "count": 88
3 | }
--------------------------------------------------------------------------------
/test/fixtures/countPaging.json:
--------------------------------------------------------------------------------
1 | {"count":15592}
--------------------------------------------------------------------------------
/test/fixtures/countStatsFail.json:
--------------------------------------------------------------------------------
1 | {"count":3355}
--------------------------------------------------------------------------------
/test/fixtures/features-empty.json:
--------------------------------------------------------------------------------
1 | {"objectIdFieldName":"FID","globalIdFieldName":"","geometryType":"esriGeometryPoint","spatialReference":{"wkid":102100,"latestWkid":3857},"fields":[],"exceededTransferLimit":true,"features":[]}
--------------------------------------------------------------------------------
/test/fixtures/features.json:
--------------------------------------------------------------------------------
1 | {"objectIdFieldName":"FID","globalIdFieldName":"","geometryType":"esriGeometryPoint","spatialReference":{"wkid":102100,"latestWkid":3857},"fields":[],"exceededTransferLimit":true,"features":[{"attributes":{},"geometry":{"x":-8572256.9081608,"y":4708222.9619573336}}]}
--------------------------------------------------------------------------------
/test/fixtures/hostedLayerInfo.json:
--------------------------------------------------------------------------------
1 | {"currentVersion":10.3,"id":0,"name":"download_test","type":"Feature Layer","serviceItemId":"c6bfd157874f483ba85eacad852aea23","displayField":"","description":"","copyrightText":"","defaultVisibility":true,"editingInfo":{"lastEditDate":1447869075942},"relationships":[],"isDataVersioned":false,"supportsCalculate":true,"supportsAttachmentsByUploadId":true,"supportsRollbackOnFailureParameter":true,"supportsStatistics":true,"supportsAdvancedQueries":true,"supportsValidateSql":true,"supportsCoordinatesQuantization":true,"supportsApplyEditsWithGlobalIds":false,"advancedQueryCapabilities":{"supportsPagination":true,"supportsQueryWithDistance":true,"supportsReturningQueryExtent":true,"supportsStatistics":true,"supportsOrderBy":true,"supportsDistinct":true,"supportsQueryWithResultType":true,"supportsSqlExpression":true,"supportsReturningGeometryCentroid":false},"useStandardizedQueries":false,"geometryType":"esriGeometryPoint","minScale":9244649,"maxScale":0,"extent":{"xmin":-9796115.1898080744,"ymin":4736005.8538175914,"xmax":-9695927.6480941288,"ymax":4865942.279503176,"spatialReference":{"wkid":102100,"latestWkid":3857}},"drawingInfo":{"renderer":{"type":"simple","symbol":{"type":"esriPMS","url":"RedSphere.png","imageData":"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQBQYWludC5ORVQgdjMuNS4xTuc4+QAAB3VJREFUeF7tmPlTlEcexnve94U5mANQbgQSbgiHXHINlxpRIBpRI6wHorLERUmIisKCQWM8cqigESVQS1Kx1piNi4mW2YpbcZONrilE140RCTcy3DDAcL/zbJP8CYPDL+9Ufau7uqb7eZ7P+/a8PS8hwkcgIBAQCAgEBAICAYGAQEAgIBAQCAgEBAICAYGAQEAgIBAQCDx/AoowKXFMUhD3lQrioZaQRVRS+fxl51eBTZUTdZ41U1Rox13/0JF9csGJ05Qv4jSz/YPWohtvLmSKN5iTGGqTm1+rc6weICOBRbZs1UVnrv87T1PUeovxyNsUP9P6n5cpHtCxu24cbrmwKLdj+osWiqrVKhI0xzbmZ7m1SpJ+1pFpvE2DPvGTomOxAoNLLKGLscZYvB10cbYYjrJCb7A5mrxleOBqim+cWJRakZY0JfnD/LieI9V1MrKtwokbrAtU4Vm0A3TJnphJD4B+RxD0u0LA7w7FTE4oprOCMbklEGNrfdGf4IqnQTb4wc0MFTYibZqM7JgjO8ZdJkpMln/sKu16pHZGb7IfptIWg389DPp9kcChWODoMuDdBOhL1JgpisbUvghM7AqFbtNiaFP80RLnhbuBdqi0N+1dbUpWGde9gWpuhFi95yL7sS7BA93JAb+Fn8mh4QujgPeTgb9kAZf3Apd2A+fXQ38yHjOHozB1IAJjOSEY2RSIwVUv4dd4X9wJccGHNrJ7CYQ4GGjLeNNfM+dyvgpzQstKf3pbB2A6m97uBRE0/Ergcxr8hyqg7hrwn0vAtRIKIRX6Y2pMl0RhIj8co9nBGFrvh55l3ngU7YObng7IVnFvGS+BYUpmHziY/Ls2zgP9SX50by/G9N5w6I+ogYvpwK1SoOlHQNsGfWcd9Peqof88B/rTyzF9hAIopAByQzC0JQB9ST5oVnvhnt+LOGsprvUhxNIwa0aY7cGR6Cp7tr8+whkjawIxkRWC6YJI6N+lAKq3Qf/Tx+B77oGfaQc/8hB8w2Xwtw9Bf3kzZspXY/JIDEbfpAB2BKLvVV90Jvjgoac9vpRxE8kciTVCBMMkNirJ7k/tRHyjtxwjKV4Yp3t/6s+R4E+/DH3N6+BrS8E314Dvvg2+/Sb4hxfBf5sP/up2TF3ZhonK1zD6dhwGdwail26DzqgX8MRKiq9ZBpkSkmeYOyPM3m9Jjl+1Z9D8AgNtlAq6bZ70qsZi+q+bwV/7I/hbB8D/dAr8Axq89iz474p/G5++koHJy1sx/lkGdBc2YjA3HF0rHNHuboomuQj/5DgclIvOGCGCYRKFFuTMV7YUAD3VDQaLMfyqBcZORGPy01QKYSNm/rYV/Nd/Av9NHvgbueBrsjDzRQamKKDxT9Kgq1iLkbIUDOSHoiNcgnYHgnYZi+9ZExSbiSoMc2eE2flKcuJLa4KGRQz6/U0wlGaP0feiMH4uFpMXEjBVlYjp6lWY+SSZtim0kulYMiYuJEJXuhTDJ9UYPByOvoIwdCxfgE4bAo0Jh39xLAoVpMwIEQyTyFCQvGpLon9sJ0K3J4OBDDcMH1dj9FQsxkrjMPFRPCbOx2GyfLal9VEcxstioTulxjAFNfROJPqLl6Bnfyg6V7ugz5yBhuHwrZjBdiU5YJg7I8wOpifAKoVIW7uQ3rpOBH2b3ekVjYT2WCRG3o+mIGKgO0OrlIaebU/HYOQDNbQnojB4NJyGD0NPfjA0bwTRE6Q7hsUcWhkWN8yZqSQlWWGECAZLmJfJmbrvVSI8taK37xpbdB/wQW8xPee/8xIGjvlj8IQ/hk4G0JbWcX8MHPVDX4kveoq8ocn3xLM33NCZRcPHOGJYZIKfpQyq7JjHS6yJjcHujLHADgkpuC7h8F8zEVqXSNC2awE69lqhs8AamkO26HrbDt2H7dBVQov2NcW26CiwQtu+BWjdY4n2nZboTbfCmKcCnRyDO/YmyLPnDlHvjDH8G6zhS9/wlEnYR7X00fWrFYuWdVI0ZpuhcbcczW/R2qdAcz6t/bRov4mONeaaoYl+p22rHF0bVNAmKtBvweIXGxNcfFH8eNlC4m6wMWMusEnKpn5hyo48pj9gLe4SNG9QoGGLAk8z5XiaJUd99u8122/IpBA2K9BGg2vWWKAvRYVeLzEa7E1R422m2+MsSTem97nSYnfKyN6/mzATv7AUgqcMrUnmaFlLX3ysM0fj+t/b5lQLtK22QEfyAmiSLKFZpUJ7kBRPXKW4HqCYynWVHKSG2LkyZex1uO1mZM9lKem9Tx9jjY5iNEYo0bKMhn7ZAu0r6H5PpLXCAq0rKJClSjSGynE/QIkrQYqBPe6S2X+AJsY2Ped6iWZk6RlL0c2r5szofRsO9R5S1IfQLRCpQL1aifoYFerpsbkuTImaUJXuXIDiH6/Ys8vm3Mg8L2i20YqsO7fItKLcSXyn0kXccclVqv3MS6at9JU/Ox+ouns+SF6Z4cSupz7l8+z1ucs7LF1AQjOdxfGZzmx8Iu1TRcfnrioICAQEAgIBgYBAQCAgEBAICAQEAgIBgYBAQCAgEBAICAQEAv8H44b/6ZiGvGAAAAAASUVORK5CYII=","contentType":"image/png","width":15,"height":15}}},"allowGeometryUpdates":true,"hasAttachments":false,"htmlPopupType":"esriServerHTMLPopupTypeNone","hasM":false,"hasZ":false,"objectIdField":"FID","globalIdField":"","typeIdField":"","fields":[{"name":"FID","type":"esriFieldTypeInteger","actualType":"int","alias":"FID","sqlType":"sqlTypeInteger","nullable":false,"editable":false,"domain":null,"defaultValue":null},{"name":"rownum","type":"esriFieldTypeInteger","actualType":"int","alias":"rownum","sqlType":"sqlTypeInteger","nullable":true,"editable":true,"domain":null,"defaultValue":null},{"name":"latitude","type":"esriFieldTypeDouble","actualType":"float","alias":"latitude","sqlType":"sqlTypeFloat","nullable":true,"editable":true,"domain":null,"defaultValue":null},{"name":"longitude","type":"esriFieldTypeDouble","actualType":"float","alias":"longitude","sqlType":"sqlTypeFloat","nullable":true,"editable":true,"domain":null,"defaultValue":null},{"name":"quant_val","type":"esriFieldTypeInteger","actualType":"int","alias":"quant_val","sqlType":"sqlTypeInteger","nullable":true,"editable":true,"domain":null,"defaultValue":null},{"name":"expected_c","type":"esriFieldTypeString","actualType":"nvarchar","alias":"expected_c","sqlType":"sqlTypeNVarchar","length":80,"nullable":true,"editable":true,"domain":null,"defaultValue":null},{"name":"FID_1","type":"esriFieldTypeInteger","actualType":"int","alias":"FID_1","sqlType":"sqlTypeInteger","nullable":true,"editable":true,"domain":null,"defaultValue":null}],"indexes":[{"name":"PK__DOWNLOAD__C1BEA5A22547B608","fields":"FID","isAscending":true,"isUnique":true,"description":"clustered, unique, primary key"},{"name":"user_1374.DOWNLOAD_TEST_DOWNLOAD_TEST_Shape_sidx","fields":"Shape","isAscending":false,"isUnique":false,"description":"Shape Index"}],"types":[],"templates":[{"name":"New Feature","description":"","drawingTool":"esriFeatureEditToolPoint","prototype":{"attributes":{"rownum":null,"latitude":null,"longitude":null,"quant_val":null,"expected_c":null,"FID_1":null}}}],"supportedQueryFormats":"JSON","hasStaticData":true,"maxRecordCount":2000,"standardMaxRecordCount":32000,"tileMaxRecordCount":8000,"maxRecordCountFactor":1,"capabilities":"Query"}
--------------------------------------------------------------------------------
/test/fixtures/ids10.0.json:
--------------------------------------------------------------------------------
1 | {"objectIdFieldName":"objectid","objectIds":[1,2,3,4,5,6]}
--------------------------------------------------------------------------------
/test/fixtures/idsStatsFail.json:
--------------------------------------------------------------------------------
1 | {"objectIdFieldName":"ESRI_OID","objectIds":[43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,68,66,67,69,70,71,72,73,74,75,77,76,79,78,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1580,1579,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1596,1595,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1625,1624,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1683,1682,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1697,1696,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1756,1755,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1806,1805,1807,1808,1809,1810,1811,1812,1813,1814,1816,1815,1818,1819,1817,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1833,1832,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1862,1861,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1887,1886,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1950,1949,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2023,2024,2022,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2072,2071,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2146,2148,2147,2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,2171,2172,2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196,2197,2198,2199,2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2220,2219,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2233,2232,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243,2244,2245,2246,2247,2248,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2261,2260,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2308,2309,2310,2311,2312,2314,2313,2315,2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2336,2337,2338,2339,2340,2341,2342,2343,2344,2345,2347,2346,2348,2349,2350,2351,2352,2353,2354,2355,2356,2357,2358,2359,2360,2361,2362,2363,2364,2365,2366,2367,2368,2369,2370,2371,2372,2373,2374,2375,2376,2377,2378,2379,2380,2381,2382,2383,2384,2385,2386,2387,2388,2389,2390,2391,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2403,2402,2404,2405,2406,2408,2407,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436,2437,2439,2438,2440,2441,2442,2443,2444,2445,2446,2447,2449,2448,2450,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2471,2472,2473,2474,2475,2477,2476,2479,2478,2481,2480,2483,2482,2484,2485,2486,2488,2487,2489,2490,2491,2492,2493,2494,2495,2496,2497,2498,2499,2500,2501,2502,2503,2505,2504,2506,2507,2508,2509,2510,2511,2512,2513,2514,2515,2516,2517,2518,2519,2520,2521,2522,2523,2524,2525,2526,2527,2528,2529,2530,2531,2532,2534,2533,2535,2536,2537,2538,2539,2540,2541,2542,2543,2544,2545,2546,2547,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560,2561,2562,2563,2564,2566,2565,2567,2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2610,2611,2612,2613,2615,2614,2616,2617,2618,2619,2620,2621,2622,2623,2624,2625,2626,2627,2628,2629,2630,2631,2633,2632,2634,2635,2636,2637,2638,2639,2640,2641,2642,2643,2644,2645,2646,2647,2648,2649,2650,2651,2652,2653,2654,2655,2656,2657,2658,2659,2660,2661,2662,2663,2664,2665,2666,2667,2668,2669,2670,2671,2672,2673,2674,2675,2676,2677,2678,2679,2680,2681,2682,2683,2684,2685,2686,2687,2688,2689,2690,2691,2692,2693,2694,2695,2696,2697,2698,2699,2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732,2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751,2752,2753,2754,2755,2756,2757,2758,2759,2760,2761,2762,2763,2764,2765,2766,2767,2768,2769,2770,2771,2772,2773,2774,2775,2776,2777,2778,2779,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808,2809,2810,2811,2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823,2824,2825,2826,2827,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840,2841,2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856,2857,2858,2859,2860,2861,2862,2863,2864,2865,2866,2867,2868,2869,2870,2871,2872,2873,2874,2875,2876,2877,2878,2879,2880,2881,2882,2883,2884,2885,2886,2887,2888,2889,2890,2891,2892,2893,2894,2895,2896,2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914,2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932,2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3113,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,3135,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172,3173,3174,3175,3176,3177,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217,3218,3219,3220,3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263,3264,3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,3278,3279,3280,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293,3294,3295,3296,3297,3298,3299,3300,3301,3302,3303,3304,3305,3306,3307,3308,3309,3310,3311,3312,3313,3314,3315,3316,3317,3318,3319,3320,3321,3322,3323,3324,3325,3326,3327,3328,3329,3330,3331,3332,3333,3334,3335,3336,3337,3338,3339,3340,3341,3342,3343,3344,3345,3346,3347,3348,3349,3350,3351,3352,3353,3354,3355,3356,3357,3358,3359,3360,3361,3362,3363,3364,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3387,3388]}
--------------------------------------------------------------------------------
/test/fixtures/layer.json:
--------------------------------------------------------------------------------
1 | {"currentVersion":10.21,"id":0,"name":"Schools Map","type":"Feature Layer","displayField":"id","description":"","copyrightText":"","defaultVisibility":true,"relationships":[],"isDataVersioned":false,"supportsRollbackOnFailureParameter":true,"supportsStatistics":true,"supportsAdvancedQueries":true,"advancedQueryCapabilities":{"supportsPagination":true,"supportsQueryWithDistance":true,"supportsReturningQueryExtent":true,"supportsStatistics":true,"supportsOrderBy":true,"supportsDistinct":true},"geometryType":"esriGeometryPoint","minScale":0,"maxScale":0,"extent":{"xmin":-122.407827,"ymin":47.509701,"xmax":-122.258635,"ymax":47.725699,"spatialReference":{"wkid":4326,"latestWkid":4326}},"drawingInfo":{"renderer":{"type":"simple","symbol":{"color":[45,172,128,161],"outline":{"color":[190,190,190,105],"width":0.5,"type":"esriSLS","style":"esriSLSSolid"},"size":7.5,"type":"esriSMS","style":"esriSMSCircle"}},"labelingInfo":null},"hasM":false,"hasZ":false,"allowGeometryUpdates":true,"hasAttachments":false,"htmlPopupType":"esriServerHTMLPopupTypeNone","objectIdField":"id","globalIdField":"","typeIdField":"","fields":[{"name":"address","type":"esriFieldTypeString","alias":"address","length":128},{"name":"city_feature","type":"esriFieldTypeString","alias":"city_feature","length":128},{"name":"common_name","type":"esriFieldTypeDate","alias":"common_name"},{"name":"longitude","type":"esriFieldTypeString","alias":"longitude","length":128},{"name":"latitude","type":"esriFieldTypeString","alias":"latitude","length":128},{"name":"website_url","type":"esriFieldTypeString","alias":"website_url","length":128},{"name":"id","type":"esriFieldTypeOID","alias":"id"}],"types":[],"templates":[],"maxRecordCount":1000,"supportedQueryFormats":"JSON","hasStaticData":true,"capabilities":"Query","initialExtent":{"xmin":-122.407827,"ymin":47.509701,"xmax":-122.258635,"ymax":47.725699,"spatialReference":{"wkid":4326,"latestWkid":4326}},"fullExtent":{"xmin":-122.407827,"ymin":47.509701,"xmax":-122.258635,"ymax":47.725699,"spatialReference":{"wkid":4326,"latestWkid":4326}}}
--------------------------------------------------------------------------------
/test/fixtures/layer10.0.json:
--------------------------------------------------------------------------------
1 | {"id":2,"name":"Fire Perimeter","type":"Feature Layer","displayField":"description","description":"","copyrightText":"","relationships":[],"geometryType":"esriGeometryPolygon","minScale":0,"maxScale":0,"extent":{"xmin":-379.3359375,"ymin":-134.273368448,"xmax":6696066.20249256,"ymax":8440532.08945883,"spatialReference":{"wkid":4326}},"drawingInfo":{"renderer":{"type":"simple","symbol":{"type":"esriSFS","style":"esriSFSSolid","color":[255,167,127,255],"outline":{"type":"esriSLS","style":"esriSLSSolid","color":[230,0,0,255],"width":2}},"label":"","description":""},"transparency":0,"labelingInfo" : null},"hasAttachments":false,"htmlPopupType":"esriServerHTMLPopupTypeAsHTMLText","objectIdField":"objectid","globalIdField":"","typeIdField":"","fields":[{"name":"objectid","type":"esriFieldTypeOID","alias":"Object ID","editable":false,"domain" : null},{"name":"name","type":"esriFieldTypeString","alias":"Name","editable":true,"length":50,"domain" : null},{"name":"description","type":"esriFieldTypeString","alias":"Description","editable":true,"length":1073741822,"domain" : null}],"types":[],"templates":[{"name":"Fire Perimeter","description":"","drawingTool":"esriFeatureEditToolPolygon","prototype":{"attributes":{"description" : null,"name" : null}}}],"capabilities":"Query,Editing"}
--------------------------------------------------------------------------------
/test/fixtures/layerInfo.json:
--------------------------------------------------------------------------------
1 | {"currentVersion":10.2,"id":5,"name":"MPD - Theft from Auto - Last 30 days","type":"Feature Layer","description":"","geometryType":"esriGeometryPoint","copyrightText":"","parentLayer":null,"subLayers":[],"minScale":60000,"maxScale":0,"drawingInfo":{"renderer":{"type":"simple","symbol":{"type":"esriPMS","url":"c887b2415f3d40d2c739b6e6d8a06019","imageData":"iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAr5JREFUOI29lE1IVFEUx393fOOb9zI1LUoDw74sB4sgKKicEtr0QUoboxa2MGhhQUlh44ypCyFcFK6qhdEkBVENJGW6kTa1LZOyhJpWNRVm2Rtf6JwW4wxvpikKogMP7r3vf37n/847XI1/HNp/AVpW+3xyZhsQVQuyHigEJoERhdyZnZm5nJfXZf0R0LID9chsD8JCEOerIsAnKJ9Lyz0dmw40GZ7OW78FWnawFaEzuR8dfUf0/Td21KzISJMSQd20pgOnTE9nd1agZbcdQCQFi8eFBwMvafUPMzB4kOrqTCgK1LnYdOC106kGEIsF8kWkx6keG4vS6h+mocHLxo1ljD57hzg6oFzg9S5RgqtnaqrlfrKn2lytw0CxE9h/dwyAI0c3Y5puIpHP7Nq9JtMlICU5mtYIXEgBBVXrlMzMxAldfQTA5UuPKS0pYM/eyiywOSSqLg0IrHMK3kYmGB+3AaipWU04PMKWreV0dz9MaXLdLo4d35rcViUXSWCBE/ji+YfUOvJmgu2+VWzeVEbdvj7sRB0qvEVOYCo/CZwkMWcArPUuYufOZQwNRWj1DwPQfPI+Z/zbKSzUEw5z0ybucyZwBPAlD8vLiyktzeN2uJ6FxQbV23qxbZiX5yY/35NIzHE5eOpJGlChwoL4HAricXBrLqrWlRAK1SFAWVkBHiPhwaUcQCXhdIfyvRflDgILAKLRKUKhUU40V6PrGstXFhHw36P/XiNZ4iOzOVfSgIbRNWnZwSaEawCDg68Itm2jomIRAB5dY+nSomwwUNJkmm1fM3uIqXf0xezgShHOHjq0IS1nbeViLl7anwVGwNQ7bziP0n6VoXe0W3ZwHKEn+fmOZGd8QqkmU2+/nlnjp+vL1Dv6YrGWflTuYUFqSQxtAYnReqqQMEKv4en48rPlX1ywhtE1CZyfe/4qfgAVgOHBVsJoEQAAAABJRU5ErkJggg==","contentType":"image/png","width":15,"height":15,"angle":0,"xoffset":0,"yoffset":0},"label":"","description":""},"transparency":0,"labelingInfo":null},"defaultVisibility":false,"extent":{"xmin":390519,"ymin":128114,"xmax":407313,"ymax":146728,"spatialReference":{"wkid":26985,"latestWkid":26985}},"hasAttachments":false,"htmlPopupType":"esriServerHTMLPopupTypeAsHTMLText","displayField":"CCN","typeIdField":null,"fields":[{"name":"CCN","type":"esriFieldTypeString","alias":"CCN","length":8,"domain":null},{"name":"REPORT_DAT","type":"esriFieldTypeDate","alias":"REPORT_DAT","length":36,"domain":null},{"name":"SHIFT","type":"esriFieldTypeString","alias":"SHIFT","length":50,"domain":null},{"name":"METHOD","type":"esriFieldTypeString","alias":"METHOD","length":50,"domain":null},{"name":"OFFENSE","type":"esriFieldTypeString","alias":"OFFENSE","length":250,"domain":null},{"name":"BLOCK","type":"esriFieldTypeString","alias":"BLOCK","length":100,"domain":null},{"name":"XBLOCK","type":"esriFieldTypeDouble","alias":"XBLOCK","domain":null},{"name":"YBLOCK","type":"esriFieldTypeDouble","alias":"YBLOCK","domain":null},{"name":"WARD","type":"esriFieldTypeString","alias":"WARD","length":1,"domain":null},{"name":"ANC","type":"esriFieldTypeString","alias":"ANC","length":5,"domain":null},{"name":"DISTRICT","type":"esriFieldTypeString","alias":"DISTRICT","length":1,"domain":null},{"name":"PSA","type":"esriFieldTypeString","alias":"PSA","length":3,"domain":null},{"name":"NEIGHBORHOOD_CLUSTER","type":"esriFieldTypeString","alias":"NEIGHBORHOOD_CLUSTER","length":200,"domain":null},{"name":"BLOCK_GROUP","type":"esriFieldTypeString","alias":"BLOCK_GROUP","length":20,"domain":null},{"name":"CENSUS_TRACT","type":"esriFieldTypeString","alias":"CENSUS_TRACT","length":20,"domain":null},{"name":"VOTING_PRECINCT","type":"esriFieldTypeString","alias":"VOTING_PRECINCT","length":25,"domain":null},{"name":"LATITUDE","type":"esriFieldTypeDouble","alias":"LATITUDE","domain":null},{"name":"LONGITUDE","type":"esriFieldTypeDouble","alias":"LONGITUDE","domain":null},{"name":"BID","type":"esriFieldTypeString","alias":"BID","length":100,"domain":null},{"name":"ESRI_OID","type":"esriFieldTypeOID","alias":"ESRI_OID","domain":null},{"name":"Shape","type":"esriFieldTypeGeometry","alias":"Shape","domain":null}],"relationships":[],"canModifyLayer":false,"canScaleSymbols":false,"hasLabels":false,"capabilities":"Map,Query,Data","maxRecordCount":1000,"supportsStatistics":true,"supportsAdvancedQueries":true,"supportedQueryFormats":"JSON, AMF","ownershipBasedAccessControlForFeatures":{"allowOthersToQuery":true},"useStandardizedQueries":true}
--------------------------------------------------------------------------------
/test/fixtures/layerNoPaging.json:
--------------------------------------------------------------------------------
1 | {"currentVersion":10.2,"id":50,"name":"Metro Station Entrances","type":"Feature Layer","description":"","geometryType":"esriGeometryPoint","copyrightText":"","parentLayer":null,"subLayers":[],"minScale":135000,"maxScale":0,"drawingInfo":{"renderer":{"type":"simple","symbol":{"type":"esriPMS","url":"f83da164b54467fc9337c274affeac32","imageData":"iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAASNJREFUGJV1zr9LAnEYx/G38j1Nw+p0KmjooMITpOCgwJIgqMmGtgocQmjpH+h/aGiOs8F/4Wi4wcktGiTQwcLsh0lB2g+9uoJsKKqL8zM9w+t5Po8A2AHJShHufuDBJdYwLV3HFgDvKX9RwlbxulHw3XKTyaAIAEXbUmMziwAUC4dcneyzsH5ASI7w9mphZtdGgg1kATCmaiSXVwDoCwRpVEyWVjeQfD6s9jNm9qtBOH7rtBlVJogmNn/g3zhw/aKKMhlD1eZ46bRpNe+RwxF3XDstM67GiWsJLqsVAv2h3pebd3WeHh8YGByiVikRnZ7tjQGuz89QpzRKR3kH9njpCoB8Ll3I59LzAMfG7+Letv49+ctBw24KgF2D5P8GZ2wAPgFqslkFrCAYOAAAAABJRU5ErkJggg==","contentType":"image/png","width":8,"height":8,"angle":0,"xoffset":0,"yoffset":0},"label":"","description":""},"transparency":0,"labelingInfo":null},"defaultVisibility":false,"extent":{"xmin":-8581149.950334396,"ymin":4699438.5454374775,"xmax":-8564327.000011444,"ymax":4718217.531164601,"spatialReference":{"wkid":102100,"latestWkid":3857}},"hasAttachments":false,"htmlPopupType":"esriServerHTMLPopupTypeNone","displayField":"NAME","typeIdField":null,"fields":[{"name":"OBJECTID_1","type":"esriFieldTypeOID","alias":"OBJECTID_1","domain":null},{"name":"GIS_ID","type":"esriFieldTypeString","alias":"GIS_ID","length":16,"domain":null},{"name":"NAME","type":"esriFieldTypeString","alias":"NAME","length":48,"domain":null},{"name":"WEB_URL","type":"esriFieldTypeString","alias":"WEB_URL","length":100,"domain":null},{"name":"EXIT_TO_ST","type":"esriFieldTypeString","alias":"EXIT.TO.STreet","length":254,"domain":null},{"name":"FEATURECOD","type":"esriFieldTypeSmallInteger","alias":"FEATURECOD","domain":null},{"name":"DESCRIPTIO","type":"esriFieldTypeString","alias":"DESCRIPTIO","length":60,"domain":null},{"name":"CAPTUREYEA","type":"esriFieldTypeDate","alias":"CAPTUREYEA","length":8,"domain":null},{"name":"LINE","type":"esriFieldTypeString","alias":"LINE","length":50,"domain":null},{"name":"ADDRESS_ID","type":"esriFieldTypeDouble","alias":"ADDRESS_ID","domain":null},{"name":"SHAPE","type":"esriFieldTypeGeometry","alias":"Shape","domain":null}],"relationships":[],"canModifyLayer":false,"canScaleSymbols":false,"hasLabels":false,"capabilities":"Map,Query,Data","maxRecordCount":1000,"supportsStatistics":true,"supportsAdvancedQueries":true,"supportedQueryFormats":"JSON, AMF","ownershipBasedAccessControlForFeatures":{"allowOthersToQuery":true},"useStandardizedQueries":true}
--------------------------------------------------------------------------------
/test/fixtures/layerPaging.json:
--------------------------------------------------------------------------------
1 | {"currentVersion":10.3,"id":0,"name":"Rail Crossings (INDOT)","type":"Feature Layer","description":"Railroad Crossings (INDOT), 2006 (1:1,200) - Shows locations of rail crossings in Indiana. Attributes include the identification number ('XING_ID') for the Federal Railroad Administration (USDOT) National Highway-Rail Crossing Inventory Program. For more information, see the following URL: http://www.fra.dot.gov/us/content/801\nData were derived from a pre-existing INDOT Road basemap, the 2003 imagery of the National Agricultural Imagery Program (NAIP), and Railroad Database and Programatic Selection Processes. Provided by the Indiana Department of Transportation (INDOT), Business Information and Technology Systems, GIS Mapping. Data are current as of September 13, 2006.","geometryType":"esriGeometryPoint","copyrightText":"copyright 2011","parentLayer":null,"subLayers":[],"minScale":0,"maxScale":0,"drawingInfo":{"renderer":{"type":"simple","symbol":{"type":"esriPMS","url":"d6576a6694cd2441ba038f21d73414cb","imageData":"iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAe9JREFUKJGlkz1oU2EUhp/z5d7ENDH9SRHSwcFFRETaWBBd3FwSsYO4dbCQLCLS2KE4tHWxtERxswFdXASnkA66CSo4eOmgFMRFkNYWE1uIbdPc23sc2oT+XHDwjAce3vec9xyL/ygrqOm6bl8oFEqLSApwRMT5J7wHTQjk3NVV/EYDMQa/2URsOy8ipUBYVdPApFetZtzvi0ROxrG7FSWM+2MNv2HPqWpWRLJBypM79Xpm/f5dsJpExkcgFEaA5ucF6tcLdC8uZlQ113Jg7VPNeMvf6CrcoDY6w++Hz+gZH2Hz0xfqQ/dIlIuYSAN13TmgtF85DSBuFSuVIPlojNroLNXb0+yU35IoF4ld7kdDiruygqqmRcRpK4sI+FuAjZXsJHrzKhvDE4SGrhAdOAuA+Fug2hJzDmxbsQHY+LDAxvAE8RcP2Hz5uj0CVhjT0QHgtG3vLSCHneTPu/e7M84/JnbxPNHBc9QKs6xNPyd2K8+xgV5aubdgR1Wd8OnB9NabeRKVXRDA6u0kWRxj/ckr7FP9qGo767Ztz/OuWZa11HVniu2vH2ku/QK2QSIQPkHPzFNM/LgjIvkjsG3by6p6AWMqkTOXUhwqVS15njcVeGEt+0Cf7/tZEUnt5f9TRCrGmCP3HfgYxphKUP9w/QUXmsJhdVmCRgAAAABJRU5ErkJggg==","contentType":"image/png","width":11,"height":11,"angle":0,"xoffset":0,"yoffset":0},"label":"","description":""},"transparency":0,"labelingInfo":null},"defaultVisibility":true,"extent":{"xmin":411733.0012562731,"ymin":4189067.787922809,"xmax":688101.2267936617,"ymax":4625312.943145094,"spatialReference":{"wkid":26916,"latestWkid":26916}},"hasAttachments":false,"htmlPopupType":"esriServerHTMLPopupTypeNone","displayField":"XING_ID","typeIdField":null,"fields":[{"name":"OBJECTID","type":"esriFieldTypeOID","alias":"OBJECTID","domain":null},{"name":"XING_ID","type":"esriFieldTypeString","alias":"Federal Crossing ID (USDOT)","length":50,"domain":null},{"name":"Shape","type":"esriFieldTypeGeometry","alias":"Shape","domain":null},{"name":"GlobalID","type":"esriFieldTypeGlobalID","alias":"GlobalID","length":38,"domain":null}],"relationships":[],"canModifyLayer":false,"canScaleSymbols":false,"hasLabels":false,"capabilities":"Map,Query,Data","maxRecordCount":100,"supportsStatistics":true,"supportsAdvancedQueries":true,"supportedQueryFormats":"JSON, AMF","ownershipBasedAccessControlForFeatures":{"allowOthersToQuery":true},"useStandardizedQueries":true,"advancedQueryCapabilities":{"useStandardizedQueries":true,"supportsStatistics":true,"supportsOrderBy":true,"supportsDistinct":true,"supportsPagination":true,"supportsTrueCurve":true}}
--------------------------------------------------------------------------------
/test/fixtures/layerStatsFail.json:
--------------------------------------------------------------------------------
1 | {"currentVersion":10.2,"id":8,"name":"Crime Incidents - Last 30 Days","type":"Feature Layer","description":"","geometryType":"esriGeometryPoint","copyrightText":"","parentLayer":null,"subLayers":[],"minScale":10000,"maxScale":0,"drawingInfo":{"renderer":{"type":"simple","symbol":{"type":"esriPMS","url":"1a0fa88824a7e95b7b7d6a13eb227c8c","imageData":"iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAABOFJREFUSInNlm1sk1UUx3/3acfarWMwN3Qb2+RlA7cxcEMUggplOEGRN4UxRNQYURIEkQ+A6ZxbBBME/eB7wJdIRqPRxER8CcrGgjCCI9tgmMEQNsg27NjopO3TtX2uH9aOtisvhoT4T56kz33Ovb97zuk95+q5zdL/L4EuV3m6FJ4lAjFTQh6QCCiADUQT8KuQ2h6jseLCLQH7Qd4tEm8xCJ0cbJIKMhV4RAqx1am+USmktvl64GsCnW5LsZTeT4ChN9q1XzqQK6QQC5xuy4sx0RXWmwa61NJXpWQ7IADa7XYu2C+TPzINvaKE2DZfvEifz8uElNTAUBxS7HGppSlGQ/mOGwL7PbsKAzhts1F2opKxJzNYmTWdyWnptHTZsDb/we+X63kqeUYwEAAJ251uS3u4pyFAl6s83R9GETx+8lI7AC1qK5bGVnTHwReU0AO2Rl6WDyJEyDSQ4lOXy3IwOKf60O/erYTlTEpJja0ZgHzTOGal5pFgNODs81DbeZp93XX87e2my+EgyWQKD1icFMoW4JlBQFUty9DQioOtPT4fh8+dpdPbwevZxZjHZqIoAk3TUBQFc1YW89onUVq3m19O/cn8nAnERRvCmLLE5bIM/HMHgH6YAqB6vBxpPcdnp6po6+tgXeZCCrOyON/Tza7Gw8RHReP0ukk0xLMkN583859m7dFdWM/v57nRj2Iek8nwmJjA0joplGXAtrCQypmB1FU2HuWr8/sAiBXRFGWNB2Bnw2Hmjsrm/oxRqB4Pa/d/w2JtEnmpqTzcUsCBnjreP/M9By+OY5v5SfSKLrB4YQSgyA38KsmbTFNPO8euNDE9YRKGqKgBqx3H97Lo8lTGDE/ilbwZDDX0h3DKiNEc6KljmC6BjVPmBsMAmTMoh/SXKwAMUVFsfKCI1VUdxA8xDhisLniI6r/uorqzmY/P7iVRl8BH5udJ1OsxDenfVMXkRdw5NC4sjyRFAoZamEysy53DD+eaBsY21Fj5YNYKlk4soNP+Dy9Uf0hbdxeJsenYHA5WphWRm5wSaTktErALCDm9drWPWns9titmkkwmFAHvHfmN2RnZOPo8RCmQnpCIx+fj5/Z6suJCD3/Y2uFAeQJEyIzGrjYAvmioZf1UM9sfXo5Rr+fMJRsGvZ7PC9cwzBjDd8cbaFFb6XB3sl4zowsrfwIaI3lYDRQFXryaj5ruegB+tB1CO+Tj2bxpxMbGkOcvY72qyu5jR9nV9hMADumm3d5L2vBhIUCJrBoEVNDt0dDewn8WHe4+Vt5dGDJxdc2XpEUnMTLmDi71Oam117MibTYvjXpswMbr84aH0ydk1NeDgAZDWatTLbUCJQDxRiNLJxaEzMwZkcyaIztpcPS/r8tcyPycCeGAcFmNxtK2QUAAIfWbpPA+zjV6YG5yChvGLead5m95YsQ05t2TG8ksWL1C6jcHD4QAjcbSNqfbsgopKgnrGAHNGZ+NXXWyIGciihLRJCCJkKuMhqveDQICxERXWF1qabIktCcGpAhByb33XQ8EIAW8ZozQ9SMefKOh/F2n29KBFP/lihFQL0KuigS7JhD8nrrKD/l75EAnuY40wCqkflN4GG8KCP05BZaratlmDd8yYIa/yAfqbpe/YFQpKFaDoaz1Bpu6uXupf6G3/c8t6bbfvP8FLAvQPqFEIfAAAAAASUVORK5CYII=","contentType":"image/png","width":21,"height":21,"angle":0,"xoffset":0,"yoffset":0},"label":"","description":""},"transparency":0,"labelingInfo":null},"defaultVisibility":false,"extent":{"xmin":390519,"ymin":127432.2,"xmax":407795,"ymax":146841,"spatialReference":{"wkid":26985,"latestWkid":26985}},"hasAttachments":false,"htmlPopupType":"esriServerHTMLPopupTypeAsHTMLText","displayField":"CCN","typeIdField":null,"fields":[{"name":"CCN","type":"esriFieldTypeString","alias":"CCN","length":16,"domain":null},{"name":"REPORTDATETIME","type":"esriFieldTypeDate","alias":"REPORTDATETIME","length":36,"domain":null},{"name":"SHIFT","type":"esriFieldTypeString","alias":"SHIFT","length":50,"domain":null},{"name":"OFFENSE","type":"esriFieldTypeString","alias":"OFFENSE","length":250,"domain":null},{"name":"METHOD","type":"esriFieldTypeString","alias":"METHOD","length":50,"domain":null},{"name":"LASTMODIFIEDDATE","type":"esriFieldTypeDate","alias":"LASTMODIFIEDDATE","length":36,"domain":null},{"name":"BLOCKSITEADDRESS","type":"esriFieldTypeString","alias":"BLOCKSITEADDRESS","length":100,"domain":null},{"name":"BLOCKXCOORD","type":"esriFieldTypeDouble","alias":"BLOCKXCOORD","domain":null},{"name":"BLOCKYCOORD","type":"esriFieldTypeDouble","alias":"BLOCKYCOORD","domain":null},{"name":"WARD","type":"esriFieldTypeString","alias":"WARD","length":1,"domain":null},{"name":"ANC","type":"esriFieldTypeString","alias":"ANC","length":5,"domain":null},{"name":"DISTRICT","type":"esriFieldTypeString","alias":"DISTRICT","length":7,"domain":null},{"name":"PSA","type":"esriFieldTypeString","alias":"PSA","length":3,"domain":null},{"name":"NEIGHBORHOODCLUSTER","type":"esriFieldTypeString","alias":"NEIGHBORHOODCLUSTER","length":193,"domain":null},{"name":"BUSINESSIMPROVEMENTDISTRICT","type":"esriFieldTypeString","alias":"BUSINESSIMPROVEMENTDISTRICT","length":100,"domain":null},{"name":"BLOCK_GROUP","type":"esriFieldTypeString","alias":"BLOCK_GROUP","length":20,"domain":null},{"name":"CENSUS_TRACT","type":"esriFieldTypeString","alias":"CENSUS_TRACT","length":20,"domain":null},{"name":"VOTING_PRECINCT","type":"esriFieldTypeString","alias":"VOTING_PRECINCT","length":25,"domain":null},{"name":"START_DATE","type":"esriFieldTypeDate","alias":"START_DATE","length":36,"domain":null},{"name":"END_DATE","type":"esriFieldTypeDate","alias":"END_DATE","length":36,"domain":null},{"name":"ESRI_OID","type":"esriFieldTypeOID","alias":"ESRI_OID","domain":null},{"name":"Shape","type":"esriFieldTypeGeometry","alias":"Shape","domain":null}],"relationships":[],"canModifyLayer":false,"canScaleSymbols":false,"hasLabels":false,"capabilities":"Map,Query,Data","maxRecordCount":1000,"supportsStatistics":true,"supportsAdvancedQueries":true,"supportedQueryFormats":"JSON, AMF","ownershipBasedAccessControlForFeatures":{"allowOthersToQuery":true},"useStandardizedQueries":true}
--------------------------------------------------------------------------------
/test/fixtures/objectIds.json:
--------------------------------------------------------------------------------
1 | {"objectIdField":"id","objectIds":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82]}
--------------------------------------------------------------------------------
/test/fixtures/secured.json:
--------------------------------------------------------------------------------
1 | {"error":{"code":499,"message":"Token Required","details":[]}}
--------------------------------------------------------------------------------
/test/fixtures/serviceInfo.json:
--------------------------------------------------------------------------------
1 | {"currentVersion":10.3,"serviceDescription":"Victoria's pre-approved roads for Oversize Overmass (OSOM) Load Carrying Vehicle Scheme Permit Network.","hasVersionedData":false,"supportsDisconnectedEditing":false,"hasStaticData":false,"maxRecordCount":30000,"supportedQueryFormats":"JSON","capabilities":"Query","description":"\u003cp\u003eVictoria's gazetted roads for Over Mass Over Size. This Class 2 network shows roads mapped in three categories: Approved, Approved - Conditions Apply and Restricted\u003c/p\u003e\u003cp\u003e\u003ca href='http://data.vicroads.vic.gov.au/metadata/OSOM_SchemePermitNetwork%20-%20Open%20Data.html' target='_blank'\u003eAbout this dataset\u003c/a\u003e - \u003ca href='mailto:sissupport@roads.vic.gov.au' style='line-height: 1.38461538461538;' target='_blank'\u003eContact us\u003c/a\u003e\u003c/p\u003e","copyrightText":"VicRoads, National Heavy Vehicle Regulator","spatialReference":{"wkid":102100,"latestWkid":3857},"initialExtent":{"xmin":15456869.337833311,"ymin":-4508807.3045889772,"xmax":16758621.941338524,"ymax":-3901587.3401490455,"spatialReference":{"wkid":102100,"latestWkid":3857}},"fullExtent":{"xmin":15691939.723399997,"ymin":-4680313.4448000006,"xmax":16662035.512200002,"ymax":-4044192.2406,"spatialReference":{"wkid":102100,"latestWkid":3857}},"allowGeometryUpdates":true,"units":"esriMeters","size":8650752,"syncEnabled":false,"editorTrackingInfo":{"enableEditorTracking":false,"enableOwnershipAccessControl":false,"allowOthersToUpdate":true,"allowOthersToDelete":false},"xssPreventionInfo":{"xssPreventionEnabled":true,"xssPreventionRule":"InputOnly","xssInputRule":"rejectInvalid"},"layers":[{"id":0,"name":"HVR_OSOM_Scheme_Permit_WM","parentLayerId":-1,"defaultVisibility":true,"subLayerIds":null,"minScale":0,"maxScale":0}],"tables":[]}
--------------------------------------------------------------------------------
/test/fixtures/stats.json:
--------------------------------------------------------------------------------
1 | {"objectIdFieldName":"id","globalIdFieldName":"","hasZ":false,"hasM":false,"geometryType":"esriGeometryPoint","spatialReference":{"wkid":4326},"fields":[{"name":"min_id","type":"esriFieldTypeInteger","alias":"min_id"},{"name":"max_id","type":"esriFieldTypeInteger","alias":"max_id"}],"features":[{"attributes":{"min_id":1,"max_id":14}}]}
2 |
--------------------------------------------------------------------------------
/test/fixtures/statsCaps.json:
--------------------------------------------------------------------------------
1 | {"objectIdFieldName":"id","globalIdFieldName":"","hasZ":false,"hasM":false,"geometryType":"esriGeometryPoint","spatialReference":{"wkid":4326},"fields":[{"name":"min_id","type":"esriFieldTypeInteger","alias":"min_id"},{"name":"max_id","type":"esriFieldTypeInteger","alias":"max_id"}],"features":[{"attributes":{"MIN_ID":1,"MAX_ID":14}}]}
2 |
--------------------------------------------------------------------------------
/test/fixtures/statsFail.json:
--------------------------------------------------------------------------------
1 | {"error":{"code":400,"message":"Invalid or missing input parameters.","details":[]}}
--------------------------------------------------------------------------------
/test/fixtures/statsFailedPaging.json:
--------------------------------------------------------------------------------
1 | {"displayFieldName":"","fieldAliases":{"min_OBJECTID_1":"min_OBJECTID_1","max_OBJECTID_1":"max_OBJECTID_1"},"fields":[{"name":"min_OBJECTID_1","type":"esriFieldTypeDouble","alias":"min_OBJECTID_1"},{"name":"max_OBJECTID_1","type":"esriFieldTypeDouble","alias":"max_OBJECTID_1"}],"features":[{"attributes":{"min_OBJECTID_1":1,"max_OBJECTID_1":15592}}]}
--------------------------------------------------------------------------------
/test/fixtures/statsNoPaging.json:
--------------------------------------------------------------------------------
1 | {"displayFieldName":"","fieldAliases":{"min_OBJECTID_1":"min_OBJECTID_1","max_OBJECTID_1":"max_OBJECTID_1"},"fields":[{"name":"min_OBJECTID_1","type":"esriFieldTypeDouble","alias":"min_OBJECTID_1"},{"name":"max_OBJECTID_1","type":"esriFieldTypeDouble","alias":"max_OBJECTID_1"}],"features":[{"attributes":{"min_OBJECTID_1":1,"max_OBJECTID_1":88}}]}
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | var sinon = require('sinon')
2 | var test = require('tape')
3 | var FeatureService = require('../')
4 | var Utils = require('../lib/utils')
5 | var nock = require('nock')
6 | var fs = require('fs')
7 | var _ = require('lodash')
8 | var zlib = require('zlib')
9 |
10 | var service = new FeatureService('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1', {objectIdField: 'OBJECTID'})
11 |
12 | var serviceFixture = require('./fixtures/serviceInfo.json')
13 | var layerInfo = require('./fixtures/layerInfo.json')
14 | var hostedLayerInfo = require('./fixtures/hostedLayerInfo.json')
15 | var layerFixture = require('./fixtures/layer.json')
16 | var idFixture = require('./fixtures/objectIds.json')
17 | var countFixture = require('./fixtures/count.json')
18 | var securedFixture = require('./fixtures/secured.json')
19 | var statsFixture = require('./fixtures/stats.json')
20 | var statsFixtureCaps = require('./fixtures/statsCaps.json')
21 |
22 | test('create a service with query strings in the parameters', function (t) {
23 | var serv = new FeatureService('http://koop.whatever.com/FeatureServer/2?f=json', {layer: '2?f=json'})
24 | t.equal(serv.layer.toString(), '2')
25 | t.equal(serv.server, 'http://koop.whatever.com/FeatureServer')
26 | t.end()
27 | })
28 |
29 | test('create a service when the url has a trailing slash', function (t) {
30 | var serv = new FeatureService('http://koop.whatava.com/FeatureServer/0/', {layer: 0})
31 | t.equal(serv.layer.toString(), '0')
32 | t.equal(serv.server, 'http://koop.whatava.com/FeatureServer')
33 | t.end()
34 | })
35 |
36 | test('override layer with passed in option', function (t) {
37 | var serv = new FeatureService('http://koop.whateva.com/FeatureServer/1', {layer: '3'})
38 | t.equal(serv.layer.toString(), '3')
39 | t.end()
40 | })
41 |
42 | test('override layer with passed in option that is not a string', function (t) {
43 | var serv = new FeatureService('http://koop.whateva.com/FeatureServer/1', {layer: 3})
44 | t.equal(serv.layer.toString(), '3')
45 | t.end()
46 | })
47 |
48 | test('get the objectId', function (t) {
49 | var oid = service.getObjectIdField(layerInfo)
50 | t.equal(oid, 'ESRI_OID')
51 |
52 | t.end()
53 | })
54 |
55 | test('get the from a hosted service', function (t) {
56 | var oid = service.getObjectIdField(hostedLayerInfo)
57 | t.equal(oid, 'FID')
58 |
59 | t.end()
60 | })
61 |
62 | test('try to get the objectId when there are no fields', function (t) {
63 | var layer = _.cloneDeep(layerInfo)
64 | delete layer.fields
65 | var oid = service.getObjectIdField(layer)
66 | t.equal(oid, false)
67 |
68 | t.end()
69 | })
70 |
71 | test('build range pages', function (t) {
72 | var pages
73 | var stats = {min: 0, max: 2000}
74 | pages = service._rangePages(stats, stats.max / 2)
75 | t.equal(pages[0].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4326&where=OBJECTID>=0+AND+OBJECTID<=999&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
76 | t.equal(pages[1].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4326&where=OBJECTID>=1000+AND+OBJECTID<=2000&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
77 | t.equal(pages.length, 2)
78 | pages = service._rangePages(stats, stats.max / 4)
79 | t.equal(pages.length, 4)
80 | t.end()
81 | })
82 |
83 | test('build range pages with output spatial reference 4629', function (t) {
84 | var pages
85 | var stats = {min: 0, max: 2000}
86 | var service = new FeatureService('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1', {objectIdField: 'OBJECTID', outSR: 4629})
87 | pages = service._rangePages(stats, stats.max / 2, true)
88 | t.equal(pages[0].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4629&where=OBJECTID>=0+AND+OBJECTID<=999&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
89 | t.equal(pages[1].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4629&where=OBJECTID>=1000+AND+OBJECTID<=2000&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
90 | t.equal(pages.length, 2)
91 | t.end()
92 | })
93 |
94 | test('build id based pages', function (t) {
95 | var ids = [0, 1, 2, 3, 4, 5]
96 | var maxCount = 2
97 | var pages = service._idPages(ids, maxCount)
98 | t.equal(pages.length, 3)
99 | t.equal(pages[0].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4326&where=OBJECTID >= 0 AND OBJECTID<=1&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=10')
100 | t.equal(pages[1].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4326&where=OBJECTID >= 2 AND OBJECTID<=3&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=10')
101 | t.equal(pages[2].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4326&where=OBJECTID >= 4 AND OBJECTID<=5&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=10')
102 | t.end()
103 | })
104 |
105 | test('build id based pages with output spatial reference 4629', function (t) {
106 | var ids = [0, 1, 2, 3, 4, 5]
107 | var maxCount = 2
108 | var service = new FeatureService('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1', {objectIdField: 'OBJECTID', outSR: 4629})
109 | var pages = service._idPages(ids, maxCount, true)
110 | t.equal(pages.length, 3)
111 | t.equal(pages[0].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4629&where=OBJECTID >= 0 AND OBJECTID<=1&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=10')
112 | t.equal(pages[1].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4629&where=OBJECTID >= 2 AND OBJECTID<=3&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=10')
113 | t.equal(pages[2].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4629&where=OBJECTID >= 4 AND OBJECTID<=5&f=json&outFields=*&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=10')
114 | t.end()
115 | })
116 |
117 | test('build result offset pages', function (t) {
118 | var maxCount = 100
119 | var pages = service._offsetPages(4, maxCount)
120 | t.equal(pages[0].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=0&resultRecordCount=100&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
121 | t.equal(pages[1].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=100&resultRecordCount=100&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
122 | t.equal(pages.length, 4)
123 | t.end()
124 | })
125 |
126 | test('build result offset pages with output spatial reference 4629', function (t) {
127 | var maxCount = 100
128 | var service = new FeatureService('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1', {objectIdField: 'OBJECTID', outSR: 4629})
129 | var pages = service._offsetPages(4, maxCount, true)
130 | t.equal(pages[0].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4629&f=json&outFields=*&where=1=1&resultOffset=0&resultRecordCount=100&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
131 | t.equal(pages[1].req, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?outSR=4629&f=json&outFields=*&where=1=1&resultOffset=100&resultRecordCount=100&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
132 | t.equal(pages.length, 4)
133 | t.end()
134 | })
135 |
136 | test('creates an out statistics url', function (t) {
137 | var url = service._statsUrl('test', ['min', 'max'])
138 | t.equal(url, 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?f=json&outFields=&outStatistics=[{"statisticType":"min","onStatisticField":"test","outStatisticFieldName":"min_test"},{"statisticType":"max","onStatisticField":"test","outStatisticFieldName":"max_test"}]')
139 | t.end()
140 | })
141 |
142 | test('get the metadata for a layer on the service', function (t) {
143 | sinon.stub(service, 'request').callsFake(function (url, callback) {
144 | callback(null, layerFixture)
145 | })
146 | service.layerInfo(function (err, metadata) {
147 | t.equal(err, null)
148 | t.equal(service.request.calledWith('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1?f=json'), true)
149 | service.request.restore()
150 | t.end()
151 | })
152 | })
153 |
154 | test('get the metadata for a service', function (t) {
155 | sinon.stub(service, 'request').callsFake(function (url, callback) {
156 | callback(null, serviceFixture)
157 | })
158 | service.info(function (err, metadata) {
159 | t.equal(err, null)
160 | t.equal(service.request.calledWith('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer?f=json'), true)
161 | service.request.restore()
162 | t.end()
163 | })
164 | })
165 |
166 | test('get the info for a service once it has already been saved', function (t) {
167 | t.plan(3)
168 | var service = new FeatureService('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1', {objectIdField: 'OBJECTID'})
169 | service._info = 'foo'
170 | t.equal(service.info(), 'foo')
171 | service.info(function (err, info) {
172 | t.error(err)
173 | t.equal(info, 'foo')
174 | })
175 | })
176 |
177 | test('get the metadata for a service once it has already been saved', function (t) {
178 | t.plan(3)
179 | var service = new FeatureService('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1', {objectIdField: 'OBJECTID'})
180 | service._metadata = 'foo'
181 | t.equal(service.metadata(), 'foo')
182 | service.metadata(function (err, info) {
183 | t.error(err)
184 | t.equal(info, 'foo')
185 | })
186 | })
187 |
188 | test('get the metadata for a layer once it has already been saved', function (t) {
189 | t.plan(3)
190 | var service = new FeatureService('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1', {objectIdField: 'OBJECTID'})
191 | service._layerInfo = 'foo'
192 | t.equal(service.layerInfo(), 'foo')
193 | service.layerInfo(function (err, info) {
194 | t.error(err)
195 | t.equal(info, 'foo')
196 | })
197 | })
198 |
199 | test('get all the object ids for a layer on the service', function (t) {
200 | sinon.stub(service, 'request').callsFake(function (url, callback) {
201 | callback(null, idFixture)
202 | })
203 | service.layerIds(function (err, metadata) {
204 | t.equal(err, null)
205 | var expected = 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?where=1=1&returnIdsOnly=true&f=json'
206 | t.equal(service.request.calledWith(expected), true)
207 | service.request.restore()
208 | t.end()
209 | })
210 | })
211 |
212 | test('get the range of object ids for a service', function (t) {
213 | sinon.stub(service, 'request').callsFake(function (url, callback) {
214 | callback(null, statsFixture)
215 | })
216 | service.getObjectIdRange('id', function (err, range) {
217 | t.equal(err, null)
218 | var expected = 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?f=json&outFields=&outStatistics=[{"statisticType":"min","onStatisticField":"id","outStatisticFieldName":"min_id"},{"statisticType":"max","onStatisticField":"id","outStatisticFieldName":"max_id"}]'
219 | t.equal(service.request.calledWith(expected), true)
220 | t.equal(range.min, 1)
221 | t.equal(range.max, 14)
222 | service.request.restore()
223 | t.end()
224 | })
225 | })
226 |
227 | test('get the range of object ids for a service when the attribute names are unexpectedly capitalized', function (t) {
228 | sinon.stub(service, 'request').callsFake(function (url, callback) {
229 | callback(null, statsFixtureCaps)
230 | })
231 | service.getObjectIdRange('id', function (err, range) {
232 | t.equal(err, null)
233 | var expected = 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?f=json&outFields=&outStatistics=[{"statisticType":"min","onStatisticField":"id","outStatisticFieldName":"min_id"},{"statisticType":"max","onStatisticField":"id","outStatisticFieldName":"max_id"}]'
234 | t.equal(service.request.calledWith(expected), true)
235 | t.equal(range.min, 1)
236 | t.equal(range.max, 14)
237 | service.request.restore()
238 | t.end()
239 | })
240 | })
241 |
242 | test('get the feature count for a layer on the service', function (t) {
243 | sinon.stub(service, 'request').callsFake(function (url, callback) {
244 | callback(null, countFixture)
245 | })
246 | service.featureCount(function (err, metadata) {
247 | t.equal(err, null)
248 | var expected = 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/1/query?where=1=1&returnCountOnly=true&f=json'
249 | t.equal(service.request.calledWith(expected), true)
250 | service.request.restore()
251 | t.end()
252 | })
253 | })
254 |
255 | test('get a json error when trying to get a feature count', function (t) {
256 | sinon.stub(service, 'request').callsFake(function (url, callback) {
257 | callback(null, securedFixture)
258 | })
259 | service.featureCount(function (err, count) {
260 | t.notEqual(typeof err, 'undefined')
261 | t.equal(err.code, 499)
262 | t.equal(err.body.message, 'Token Required')
263 | service.request.restore()
264 | t.end()
265 | })
266 | })
267 |
268 | test('get an error with no response body when trying to get a feature count', function (t) {
269 | sinon.stub(service, 'request').callsFake(function (url, callback) {
270 | callback(new Error(), null)
271 | })
272 | service.featureCount(function (err, count) {
273 | t.equal(err.code, 500)
274 | service.request.restore()
275 | t.end()
276 | })
277 | })
278 |
279 | test('get a json error when trying to get layer ids', function (t) {
280 | sinon.stub(service, 'request').callsFake(function (url, callback) {
281 | callback(null, securedFixture)
282 | })
283 | service.layerIds(function (err, count) {
284 | t.notEqual(typeof err, 'undefined')
285 | t.equal(err.code, 499)
286 | t.equal(err.body.message, 'Token Required')
287 | service.request.restore()
288 | t.end()
289 | })
290 | })
291 |
292 | test('get an error with no response body when trying to get layer ids', function (t) {
293 | sinon.stub(service, 'request').callsFake(function (url, callback) {
294 | callback(new Error(), null)
295 | })
296 | service.layerIds(function (err, count) {
297 | t.equal(err.code, 500)
298 | service.request.restore()
299 | t.end()
300 | })
301 | })
302 |
303 | test('get a json error when trying to get layer info', function (t) {
304 | sinon.stub(service, 'request').callsFake(function (url, callback) {
305 | callback(null, securedFixture)
306 | })
307 | service.layerInfo(function (err, count) {
308 | t.notEqual(typeof err, 'undefined')
309 | t.equal(err.code, 499)
310 | t.equal(err.body.message, 'Token Required')
311 | service.request.restore()
312 | t.end()
313 | })
314 | })
315 |
316 | test('get an error with no response body when trying to get layer info', function (t) {
317 | sinon.stub(service, 'request').callsFake(function (url, callback) {
318 | callback(new Error(), null)
319 | })
320 | service.layerInfo(function (err, count) {
321 | t.equal(err.code, 500)
322 | service.request.restore()
323 | t.end()
324 | })
325 | })
326 |
327 | test('get a json error when trying to get statistics', function (t) {
328 | sinon.stub(service, 'request').callsFake(function (url, callback) {
329 | callback(null, securedFixture)
330 | })
331 | service.statistics('foo', ['max'], function (err, count) {
332 | t.notEqual(typeof err, 'undefined')
333 | t.equal(err.code, 499)
334 | t.equal(err.body.message, 'Token Required')
335 | service.request.restore()
336 | t.end()
337 | })
338 | })
339 |
340 | test('get an error with no response body when trying to get statistics', function (t) {
341 | sinon.stub(service, 'request').callsFake(function (url, callback) {
342 | callback(new Error(), null)
343 | })
344 | service.statistics('foo', [], function (err, count) {
345 | t.equal(err.code, 500)
346 | service.request.restore()
347 | t.end()
348 | })
349 | })
350 |
351 | test('time out when there is no response', function (t) {
352 | var error
353 | service.timeOut = 5
354 | nock('http://www.timeout.com').get('/').socketDelay(100).reply({}.toString())
355 |
356 | service.request('http://www.timeout.com', function (err, data) {
357 | error = err
358 | })
359 | setTimeout(function () {
360 | t.equal(typeof error, 'object')
361 | service.timeOut = 1000
362 | t.end()
363 | }, 25)
364 | })
365 |
366 | test('should trigger catchErrors with an error when receiving json with an error in the response', function (t) {
367 | var data = {
368 | error: {
369 | code: 400,
370 | message: 'Invalid or missing input parameters.',
371 | details: []
372 | }
373 | }
374 |
375 | var fixture = nock('http://www.error.com')
376 | fixture.get('/').reply(200, JSON.stringify(data))
377 |
378 | sinon.stub(service, '_catchErrors').callsFake(function (task, err, url, callback) {
379 | callback(err)
380 | })
381 |
382 | var task = {req: 'http://www.error.com'}
383 |
384 | service._requestFeatures(task, function (err, data) {
385 | t.notEqual(typeof err, 'undefined')
386 | t.equal(err.body.code, 400)
387 | t.equal(err.body.message, 'Invalid or missing input parameters.')
388 | service._catchErrors.restore()
389 | t.end()
390 | })
391 | })
392 |
393 | // feature request integration tests
394 | test('requesting a page of features', function (t) {
395 | var page = fs.createReadStream(__dirname + '/fixtures/page.json')
396 | var fixture = nock('http://servicesqa.arcgis.com')
397 |
398 | fixture.get('/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=1000&resultRecordCount=1000&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
399 | .reply(200, function () { return page })
400 |
401 | var service = new FeatureService('http://servicesqa.arcgis.com/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0')
402 | var task = {req: 'http://servicesqa.arcgis.com/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=1000&resultRecordCount=1000&geometry=&returnGeometry=true&returnZ=true&geometryPrecision='}
403 |
404 | service._requestFeatures(task, function (err, json) {
405 | t.error(err)
406 | t.equal(json.features.length, 1000)
407 | t.end()
408 | })
409 | })
410 |
411 | test('requesting a page of features that is gzipped', function (t) {
412 | var page = fs.createReadStream(__dirname + '/fixtures/page.json').pipe(zlib.createGzip())
413 | var fixture = nock('http://servicesqa.arcgis.com')
414 |
415 | fixture.get('/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=1000&resultRecordCount=1000&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
416 | .reply(200, function () { return page }, {'content-encoding': 'gzip'})
417 |
418 | var service = new FeatureService('http://servicesqa.arcgis.com/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0')
419 | var task = {req: 'http://servicesqa.arcgis.com/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=1000&resultRecordCount=1000&geometry=&returnGeometry=true&returnZ=true&geometryPrecision='}
420 |
421 | service._requestFeatures(task, function (err, json) {
422 | t.error(err)
423 | t.equal(json.features.length, 1000)
424 | t.end()
425 | })
426 | })
427 |
428 | test('requesting a page of features that is deflate encoded', function (t) {
429 | var page = fs.createReadStream(__dirname + '/fixtures/page.json').pipe(zlib.createDeflate())
430 | var fixture = nock('http://servicesqa.arcgis.com')
431 |
432 | fixture.get('/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=1000&resultRecordCount=1000&geometry=&returnGeometry=true&returnZ=true&geometryPrecision=')
433 | .reply(200, function () { return page }, {'content-encoding': 'deflate'})
434 |
435 | var service = new FeatureService('http://servicesqa.arcgis.com/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0')
436 | var task = {req: 'http://servicesqa.arcgis.com/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=1000&resultRecordCount=1000&geometry=&returnGeometry=true&returnZ=true&geometryPrecision='}
437 |
438 | service._requestFeatures(task, function (err, json) {
439 | t.error(err)
440 | t.equal(json.features.length, 1000)
441 | t.end()
442 | })
443 | })
444 |
445 | test('requesting a page of features and getting an html response', function (t) {
446 | var page = Buffer.from('