├── .eslintrc ├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README.md ├── bin ├── mapbox-geostats └── mapbox-geostats-validate ├── index.js ├── lib ├── constants.js ├── create-layer-stats.js ├── create-stats.js ├── get-file-type.js ├── get-value-type.js ├── mapnik-analyze.js ├── register-attribute.js ├── register-attributes-map.js ├── register-feature.js ├── report-stats.js ├── tile-analyze.js ├── type-integer-to-string.js └── validate-stats.js ├── package-lock.json ├── package.json ├── schema ├── attribute.json ├── layer.json └── tilestats.json └── test ├── fixtures ├── expected │ ├── geojson-invalid-geometry-types.json │ ├── geometry-extravaganza.json │ ├── long-attribute-names.json │ ├── many-types-geojson.json │ ├── many-types-mbtiles.json │ ├── myriad-values-1-attr.json │ ├── myriad-values-5-attrs.json │ ├── myriad-values-all-attrs.json │ ├── no-features.json │ ├── populations-plus-csv.json │ ├── populations-plus-geojson.json │ ├── populations-plus-shp.json │ ├── ports-only-name.json │ ├── ports.json │ ├── prototype.json │ ├── tilestats.json │ ├── two-layers.json │ ├── two-thousand-properties-only-two.json │ ├── two-thousand-properties.json │ └── vectorgzip.json └── src │ ├── big-features-nozip.mbtiles │ ├── geometry-extravaganza.geojson │ ├── geometry-invalid-types.geojson │ ├── invalid.csv │ ├── invalid.geojson │ ├── invalid.mbtiles │ ├── invalid.shp │ ├── invalid.txt │ ├── long-attribute-names.geojson │ ├── many-types.geojson │ ├── many-types.mbtiles │ ├── myriad-values.geojson │ ├── no-features.csv │ ├── no-features.geojson │ ├── no-features.mbtiles │ ├── no-features │ ├── no-features.dbf │ ├── no-features.prj │ ├── no-features.shp │ └── no-features.shx │ ├── pngs.mbtiles │ ├── populations-plus.csv │ ├── populations-plus.geojson │ ├── populations-plus │ ├── populations-plus.dbf │ ├── populations-plus.prj │ ├── populations-plus.qpj │ ├── populations-plus.shp │ └── populations-plus.shx │ ├── ports │ ├── ports.VERSION.txt │ ├── ports.dbf │ ├── ports.prj │ ├── ports.shp │ └── ports.shx │ ├── prototype.geojson │ ├── tilestats.mbtiles │ ├── two-layers.mbtiles │ ├── two-thousand-properties.geojson │ └── vectorgzip.mbtiles ├── test.js └── utils └── sloppy-sort.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "plugins": [ 8 | "node" 9 | ], 10 | "rules": { 11 | "no-var": 2, 12 | "prefer-const": 2, 13 | "array-bracket-spacing": [2, "never"], 14 | "brace-style": [2, "1tbs"], 15 | "comma-dangle": [2, "always-multiline"], 16 | "computed-property-spacing": [2, "never"], 17 | "curly": [2, "multi-line"], 18 | "eol-last": 2, 19 | "eqeqeq": [2, "smart"], 20 | "no-extend-native": 2, 21 | "no-mixed-spaces-and-tabs": 2, 22 | "no-spaced-func": 2, 23 | "no-trailing-spaces": 2, 24 | "no-unused-vars": 2, 25 | "no-use-before-define": [2, "nofunc"], 26 | "object-curly-spacing": [2, "always"], 27 | "quotes": [2, "single", "avoid-escape"], 28 | "semi": [2, "always"], 29 | "space-before-function-paren": [2, "never"], 30 | "space-infix-ops": 2, 31 | "spaced-comment": [2, "always"], 32 | "keyword-spacing": [2, { before: true, after: true }], 33 | "strict": 2, 34 | 35 | "node/no-unsupported-features": [2, {"version": 4}], 36 | "node/no-missing-require": 2 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | tmp 4 | coverage 5 | .nyc_output 6 | *.log 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.17.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "10" 6 | 7 | addons: 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | packages: 12 | - libstdc++6 # upgrade libstdc++ on linux to support C++11 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.1.1 2 | 3 | - Republish of 1.1.0 without accidental large log file, bloating package size 4 | 5 | # 1.1.0 (deprecated) 6 | 7 | - Upgrade to mapnik 4.3.1 8 | - Test on node 8 + 10 9 | 10 | # 1.0.0 11 | 12 | - Upgrade to mapnik 3.7.0 13 | - Drops windows support 14 | 15 | # 0.5.1 16 | 17 | - Fix issue where tile-analyze Promise was not finishing execution on resolve [#34](https://github.com/mapbox/mapbox-geostats/issues/34) 18 | 19 | # 0.5.0 20 | 21 | - Adds a tilestats schema [#33](https://github.com/mapbox/mapbox-geostats/pull/33) 22 | - Adds a tilestats schema validation method [#33](https://github.com/mapbox/mapbox-geostats/pull/33) 23 | - Checks mbtiles metadata table for a pregenerated tilestats object [#33](https://github.com/mapbox/mapbox-geostats/pull/33) 24 | 25 | # 0.4.0 26 | 27 | - Upgrade mapnik@3.6.0 28 | - Update test fixtures that are now failing due to changes in mapnik core 29 | 30 | # 0.3.1 31 | 32 | - Use `mapbox-file-sniff`'s `quaff`, instead of `sniff`, to better handle large files. 33 | 34 | # 0.3.0 35 | 36 | - Upgrade mapbox-file-sniff@0.5.2 37 | - Use ~ instead of ^ for node-mbtiles and node-mapnik 38 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # global owners 2 | 3 | * @mapbox/maps-api 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Mapbox 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mapbox-geostats 2 | 3 | [![CircleCI](https://circleci.com/gh/mapbox/mapbox-geostats.svg?style=svg)](https://circleci.com/gh/mapbox/mapbox-geostats) 4 | 5 | Generate statistics about geographic data. 6 | 7 | ## Installation 8 | `npm install @mapbox/mapbox-geostats` 9 | 10 | ## About 11 | 12 | You feed the module a file, and it outputs stats about the geographic data in the file. 13 | 14 | ### Input types 15 | 16 | Supports the following file types: 17 | 18 | - GeoJSON (`.geojson`) 19 | - Shapefile (`.shp`, in a directory with its supporting files) 20 | - CSV (`.csv`) 21 | - MBTiles (`.mbtiles`) 22 | 23 | ### Limitations 24 | 25 | You'll notice the following limitations in the output: 26 | 27 | - No more than 1000 layers are counted, and no more than 100 are reported in detail. 28 | - For any given layer, no more than 1000 unique attributes are counted, and no more than 100 are reported in detail. 29 | - For any given attribute, no more than 1000 unique values are counted, and no more than 100 are reported. All values will affect the numeric stats (e.g. `min` and `max`), even if they are not reported or counted. 30 | - Attribute values that are strings longer than 256 characters are counted but not reported. 31 | - Layer names and attribute names that are strings longer than 256 characters are truncated to a length of 256. (This means that if two attribute names only vary after their 256th character, they will be considered the same. Same for layers.) 32 | 33 | #### Avoid limitations by specifying attributes 34 | 35 | Because of the necessary limitation on the number of reported attributes, you may end up with output that does not include details about a particular attribute that you wanted to learn about, because 100 attributes were already reported. 36 | 37 | If you are the victim of this misfortune, use the `attributes` option, documented below, to specify the attribute(s) whose details you'd like to inspect. 38 | 39 | When you use attributes, the limitations on attribute values change based on the following rules. 40 | 41 | - You can count a maximum of 100,000 unique values. 42 | - You can report a maximum of 10,000 unique values. 43 | 44 | So if you've specified a limited number of attributes to inspect, the number of values that will be counted and reported can be tailored to that count. If you specify one attribute, you can see up to 10,000 values reported, 100,000 values counted per attribute. If you specify 5 attributes, you can see up to 2,000 values reported, 20,000 values counted per attribute. 45 | 46 | ## CLI 47 | 48 | `mapbox-geostats` generate a tilestats JSON object 49 | ``` 50 | Generate statistics about geographic data. 51 | 52 | Usage 53 | mapbox-geostats 54 | 55 | Output is logged to the console as a JSON string. 56 | 57 | Options 58 | --attributes, -a Specify attributes to analyze. The provided value 59 | will be parsed as an array, split on commas. 60 | 61 | Example 62 | mapbox-geostats population-centers.geojson --attributes name,pop > output.json 63 | ``` 64 | 65 | `mapbox-geostats-validate`: validate a tilestats JSON object 66 | ``` 67 | Usage 68 | mapbox-geostats-validate 69 | 70 | Output is empty if valid, or a list of errors. 71 | 72 | Example 73 | mapbox-geostats-validate ./path/to/stats.json 74 | ``` 75 | 76 | ## Node 77 | 78 | ```js 79 | var geostats = require('@mapbox/mapbox-geostats'); 80 | 81 | geostats(filePath, options).then(function (stats) { 82 | // Do something with the stats 83 | }).catch(function (err) { 84 | // Do something with the error 85 | }); 86 | ``` 87 | 88 | There's just one exposed function: 89 | 90 | ### geostats(filePath[, options]) 91 | 92 | Returns a Promise that resolves with a stats object, whose structure is described below. 93 | 94 | `filepath` (*required*) is the path to the file that you'd like to analyze. 95 | 96 | `options` (*optional*) is an optional object that can have the following properties: 97 | 98 | - `attributes`: An array of strings identifying attributes that you want analyzed and reported. By default, all attributes are analyzed and reported until we reach the limitations described above. 99 | 100 | ## Output: the stats 101 | 102 | The tilestats jsonschema is specified in the /schema directory under schema/tilestats.json 103 | 104 | The stats output has this structure: 105 | 106 | ```js 107 | { 108 | // The number of layers in the source data (max. 1000) 109 | "layerCount": Number, 110 | // An array of details about the first 100 layers 111 | "layers": [ 112 | { 113 | // The name of this layer 114 | "layer": String, 115 | // The number of features in this layer 116 | "count": Number, 117 | // The dominant geometry type in this layer 118 | "geometry": String, 119 | // The number of unique attributes in this layer (max. 1000) 120 | "attributeCount": Number 121 | // An array of details about the first 100 attributes in this layer 122 | "attributes": [ 123 | { 124 | // The name of this attribute 125 | "attribute": String, 126 | // The number of unique values for this attribute (max. 1000) 127 | "count": Number, 128 | // The type of this attribute's values 129 | "type": String, // More info below ... 130 | // An array of this attribute's first 100 unique values 131 | "values": [ 132 | // ... 133 | ], 134 | // If there are *any* numbers in the values, the following 135 | // numeric stats will be reported 136 | "min": Number, 137 | "max": Number 138 | } 139 | // ... 140 | ] 141 | } 142 | // ... 143 | ] 144 | } 145 | ``` 146 | 147 | You can find more examples in the test fixtures. 148 | 149 | ### Attribute type 150 | 151 | Each attribute has one of the following types: 152 | 153 | - `'string'` if all its values are strings (or `null`). 154 | - `'number'` if all its values are numbers (or `null`). 155 | - `'boolean'` if all its values are booleans (or `null`). 156 | - `'null'` if its only value is `null`. 157 | - `'mixed'` if it has values of multiple types. 158 | 159 | Array and object values are coerced to strings. 160 | 161 | ## Known caveats 162 | 163 | - When reading MBTiles files, the feature count will be high. This is because each feature will be included in multiple tiles, so will be analyzed multiple times. 164 | - `null` sometimes appears unbidden in the attribute value lists generated from GeoJSON sources. (cf. https://github.com/mapnik/node-mapnik/issues/668) 165 | - GeoJSON without any features causes a parsing error. (cf. https://github.com/mapnik/mapnik/issues/3463) 166 | - MBTiles files whose vector data is not gzipped will not be understood. (cf. https://github.com/mapbox/tiletype/issues/4) 167 | - Because layer and attribute names are truncated at 256 characters (see above), if two attribute or layer names only vary after their 256th character, they will be considered the same --- that is, their data will be merged. 168 | - if an MBTiles file has a `tilestats` object in the `json` row of the `metadata` table that will be used instead of generating stats from the raw tiles. 169 | -------------------------------------------------------------------------------- /bin/mapbox-geostats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | /* eslint-disable no-console */ 5 | const meow = require('meow'); 6 | const geostats = require('../'); 7 | 8 | const help = ` 9 | Usage 10 | mapbox-geostats 11 | 12 | Output is logged to the console as a JSON string. 13 | 14 | Options 15 | --attributes, -a Specify attributes to analyze. The provided value 16 | will be parsed as an array, split on commas. 17 | 18 | Example 19 | mapbox-geostats cities.geojson --attributes name,pop > output.json 20 | `; 21 | 22 | const cli = meow(help, { 23 | alias: { 24 | a: 'attributes', 25 | }, 26 | }); 27 | 28 | const input = cli.input[0]; 29 | 30 | const options = {}; 31 | 32 | if (cli.flags.attributes) { 33 | options.attributes = cli.flags.attributes.split(','); 34 | } 35 | 36 | if (!input) { 37 | console.log(cli.help); 38 | } else { 39 | geostats(input, options).then((stats) => { 40 | console.log(JSON.stringify(stats)); 41 | }).catch((err) => { 42 | console.error(err.stack); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /bin/mapbox-geostats-validate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | /* eslint-disable no-console */ 5 | const meow = require('meow'); 6 | const fs = require('fs'); 7 | const validate = require('../lib/validate-stats'); 8 | 9 | const help = ` 10 | Usage 11 | mapbox-geostats-validate 12 | 13 | Output is empty if valid, or a list of errors. 14 | 15 | Example 16 | mapbox-geostats-validate ./path/to/stats.json 17 | `; 18 | 19 | const cli = meow(help); 20 | const file = cli.input[0]; 21 | 22 | if (!file) cli.showHelp(); 23 | 24 | let stats; 25 | try { 26 | const raw = fs.readFileSync(file); 27 | console.log(raw); 28 | stats = JSON.parse(raw); 29 | // stats = JSON.parse(fs.readFileSync(file)); 30 | } catch (err) { 31 | console.log('Unable to read file'); 32 | console.error(err); 33 | console.log(help); 34 | } 35 | 36 | const results = validate(stats); 37 | if (results.length) { 38 | console.log('✘ Invalid'); 39 | results.forEach(function(s) { 40 | console.log(`- ${s}`); 41 | }); 42 | } else { 43 | console.log('✔ Valid'); 44 | } 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var getFileType = require('./lib/get-file-type'); 2 | var mapnikAnalyze = require('./lib/mapnik-analyze'); 3 | var tileAnalyze = require('./lib/tile-analyze'); 4 | var reportStats = require('./lib/report-stats'); 5 | var Constants = require('./lib/constants'); 6 | 7 | function buildGeoStats(filePath, options) { 8 | options = options || {}; 9 | 10 | if (options.attributes) { 11 | if (options.attributes.length > Constants.ATTRIBUTES_MAX_REPORT) { 12 | throw new Error('Cannot report on more than ' + Constants.ATTRIBUTES_MAX_REPORT 13 | + ' attributes'); 14 | } 15 | // Conversion to a Set should make for faster lookups 16 | options.attributes = new Set(options.attributes); 17 | } 18 | 19 | // The number of unique values we'll count and record depends on 20 | // the number of attributes we might record 21 | var divisor = (options.attributes) 22 | ? Math.min(options.attributes.size, Constants.ATTRIBUTES_MAX_REPORT) 23 | : Constants.ATTRIBUTES_MAX_REPORT; 24 | options.maxValuesToCount = Math.floor(Constants.VALUES_MAX_COUNT / divisor); 25 | options.maxValuesToReport = Math.floor(Constants.VALUES_MAX_REPORT / divisor); 26 | 27 | return getFileType(filePath) 28 | .then(function (fileType) { 29 | if (fileType === Constants.FILETYPE_MBTILES) return tileAnalyze(filePath, options); 30 | return mapnikAnalyze(filePath, fileType, options); 31 | }) 32 | .then(function(stats) { 33 | return reportStats(stats, options); 34 | }); 35 | } 36 | 37 | module.exports = buildGeoStats; 38 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | LAYERS_MAX_REPORT: 100, 5 | LAYERS_MAX_COUNT: 1000, 6 | 7 | ATTRIBUTES_MAX_REPORT: 100, 8 | ATTRIBUTES_MAX_COUNT: 1000, 9 | 10 | VALUES_MAX_REPORT: 10000, 11 | VALUES_MAX_COUNT: 100000, 12 | 13 | VALUE_STRING_MAX_LENGTH: 256, 14 | NAME_TRUNCATE_LENGTH: 256, 15 | 16 | FILETYPE_GEOJSON: 'geojson', 17 | FILETYPE_SHAPEFILE: 'shp', 18 | FILETYPE_CSV: 'csv', 19 | FILETYPE_MBTILES: 'mbtiles', 20 | 21 | VALUE_TYPE_STRING: 'string', 22 | VALUE_TYPE_NUMBER: 'number', 23 | VALUE_TYPE_BOOLEAN: 'boolean', 24 | VALUE_TYPE_NULL: 'null', 25 | VALUE_TYPE_MIXED: 'mixed', 26 | }; 27 | -------------------------------------------------------------------------------- /lib/create-layer-stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns a new, empty layer stats object. 5 | * 6 | * @param {string} name 7 | * @returns {Object} The new, empty layer stats. 8 | */ 9 | module.exports = function(name) { 10 | return { 11 | name: name, 12 | attributes: Object.create(null), 13 | attributeCountSet: new Set(), 14 | featureCount: 0, 15 | attributeCount: 0, 16 | geometryCounts: Object.create(null), 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/create-stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns a new, empty stats object. 5 | * 6 | * @returns {Object} The new, empty stats. 7 | */ 8 | module.exports = function() { 9 | return { 10 | layerCountSet: new Set(), 11 | layers: [], 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/get-file-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const filesniffer = require('@mapbox/mapbox-file-sniff'); 4 | 5 | /** 6 | * Returns the file's type, as determined by filesniffer. 7 | * 8 | * @param {string} filePath 9 | * @return {Object} The file's info 10 | */ 11 | module.exports = function(filePath) { 12 | return new Promise((resolve, reject) => { 13 | if (!filePath) return reject(new Error('File path required')); 14 | 15 | filesniffer.fromFile(filePath, (err, fileinfo) => { 16 | if (err) return reject(err); 17 | resolve(fileinfo.type); 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/get-value-type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Constants = require('./constants'); 4 | 5 | /** 6 | * Returns the value type we'll report for any given value. 7 | * 8 | * @param {any} value 9 | * @return {string} The value type. 10 | */ 11 | module.exports = function(value) { 12 | if (value == null) return Constants.VALUE_TYPE_NULL; 13 | switch (typeof value) { 14 | case 'string': 15 | return Constants.VALUE_TYPE_STRING; 16 | case 'number': 17 | return Constants.VALUE_TYPE_NUMBER; 18 | case 'boolean': 19 | return Constants.VALUE_TYPE_BOOLEAN; 20 | default: 21 | throw new Error('Unknown value type for `' + value + '`'); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /lib/mapnik-analyze.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mapnik = require('mapnik'); 4 | const path = require('path'); 5 | const Constants = require('./constants'); 6 | const createStats = require('./create-stats'); 7 | const createLayerStats = require('./create-layer-stats'); 8 | const registerFeature = require('./register-feature'); 9 | const registerAttributesMap = require('./register-attributes-map'); 10 | const typeIntegerToString = require('./type-integer-to-string'); 11 | 12 | // Register datasource plugins 13 | mapnik.register_default_input_plugins(); 14 | 15 | /** 16 | * Returns stats about a file we can analyze with Mapnik. 17 | * Understand the following: 18 | * - .geojson 19 | * - .shp 20 | * - .csv 21 | * 22 | * @param {string} filePath 23 | * @param {string} fileType - One of Constants.FILETYPE_*. 24 | * @param {Object} [options] 25 | * @param {Array} [options.attributes] 26 | * @return {Object} The stats. 27 | */ 28 | module.exports = function(filePath, fileType, options) { 29 | options = options || {}; 30 | const stats = createStats(); 31 | // Derive a fake layer name from the file's name 32 | const layerName = path.basename(filePath, path.extname(filePath)); 33 | const layerStats = createLayerStats(layerName); 34 | 35 | stats.layerCountSet.add(layerName); 36 | 37 | const datasourceOptions = { file: filePath }; 38 | switch (fileType) { 39 | case Constants.FILETYPE_GEOJSON: 40 | datasourceOptions.type = 'geojson'; 41 | datasourceOptions.cache_features = false; 42 | break; 43 | case Constants.FILETYPE_SHAPEFILE: 44 | datasourceOptions.type = 'shape'; 45 | break; 46 | case Constants.FILETYPE_CSV: 47 | datasourceOptions.type = 'csv'; 48 | break; 49 | default: 50 | throw new Error('Unrecognized type for ' + filePath); 51 | } 52 | const datasource = new mapnik.Datasource(datasourceOptions); 53 | 54 | addFeatures(datasource.featureset()); 55 | 56 | stats.layers.push(layerStats); 57 | 58 | return stats; 59 | 60 | function addFeatures(features) { 61 | if (!features) return; 62 | let feature = features.next(); 63 | while (feature) { 64 | registerFeature(layerStats, { 65 | type: typeIntegerToString(feature.geometry().type()), 66 | }); 67 | 68 | registerAttributesMap(layerStats, options, feature.attributes()); 69 | 70 | feature = features.next(); 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /lib/register-attribute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Constants = require('./constants'); 4 | const getValueType = require('./get-value-type'); 5 | 6 | /** 7 | * Mutates a layer stats object to register stats 8 | * about an attribute (and returns the mutated object). 9 | * 10 | * @param {Object} layerStats 11 | * @param {Object} options 12 | * @param {number} options.maxValuesToCount - The maximum number of unique 13 | * attribute values to register. 14 | * @param {string} name - The attribute name. 15 | * @param {string|number|boolean|null} value - The attribute value. 16 | * @return {Object} The mutated layerStats. 17 | */ 18 | module.exports = function(layerStats, options, rawName, value) { 19 | const name = rawName.slice(0, Constants.NAME_TRUNCATE_LENGTH); 20 | const valueType = getValueType(value); 21 | const isValueNull = value === null; 22 | 23 | if (layerStats.attributes[name] === undefined) { 24 | const priorAttributeCount = layerStats.attributeCountSet.size; 25 | if (priorAttributeCount >= Constants.ATTRIBUTES_MAX_COUNT) return; 26 | layerStats.attributeCountSet.add(name); 27 | 28 | if (priorAttributeCount >= Constants.ATTRIBUTES_MAX_REPORT) return; 29 | layerStats.attributes[name] = { 30 | attribute: name, 31 | valueSet: new Set(), 32 | type: valueType, 33 | }; 34 | } 35 | 36 | const attribute = layerStats.attributes[name]; 37 | 38 | if (isValueNull && attribute.valueSet.has(null)) return; 39 | 40 | if (attribute.type !== Constants.VALUE_TYPE_MIXED 41 | && !isValueNull 42 | && valueType !== attribute.type) { 43 | attribute.type = Constants.VALUE_TYPE_MIXED; 44 | } 45 | 46 | if (typeof value === 'number') { 47 | if (attribute.min === undefined || value < attribute.min) attribute.min = value; 48 | if (attribute.max === undefined || value > attribute.max) attribute.max = value; 49 | } 50 | 51 | if (attribute.valueSet.size >= options.maxValuesToCount) return; 52 | 53 | attribute.valueSet.add(truncateForStorage(value)); 54 | 55 | return layerStats; 56 | }; 57 | 58 | function truncateForStorage(value) { 59 | if (typeof value !== 'string') return value; 60 | // Store overly long strings at a length one character longer than the max 61 | // so we still know later to remove them from the reported value array 62 | if (value.length <= Constants.VALUE_STRING_MAX_LENGTH) return value; 63 | return value.slice(0, Constants.VALUE_STRING_MAX_LENGTH + 1); 64 | } 65 | -------------------------------------------------------------------------------- /lib/register-attributes-map.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const registerAttribute = require('./register-attribute'); 4 | 5 | /** 6 | * Mutates a layer stats object to register stats 7 | * about an object of attributes (and returns the mutated object). 8 | * 9 | * @param {Object} layerStats 10 | * @param {Object} options 11 | * @param {Set} [options.attributes] - Specific attributes that should be registered. 12 | * @param {Object} attributes - The attributes to register, as an object 13 | * of `name: value` 14 | * @return {Object} The mutated layerStats. 15 | */ 16 | module.exports = function(layerStats, options, attributes) { 17 | const specifiedAttributes = options.attributes; 18 | 19 | Object.keys(attributes).forEach((name) => { 20 | const value = attributes[name]; 21 | if (specifiedAttributes && !specifiedAttributes.has(name)) return; 22 | registerAttribute(layerStats, options, name, value); 23 | }); 24 | 25 | return layerStats; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/register-feature.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Mutates a layer stats object to register stats 5 | * about a feature (and returns the mutated object). 6 | * 7 | * @param {Object} layerStats 8 | * @param {Object} featureData 9 | * @param {string} featureData.type - The feature's type. 10 | * @return {Object} The mutated layerStats. 11 | */ 12 | module.exports = function(layerStats, featureData) { 13 | layerStats.featureCount++; 14 | layerStats.geometryCounts[featureData.type] = 1 + 15 | (layerStats.geometryCounts[featureData.type] || 0); 16 | return layerStats; 17 | }; 18 | -------------------------------------------------------------------------------- /lib/report-stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const Constants = require('./constants'); 5 | 6 | /** 7 | * Returns an object bearing stats, formatted for output & reportage. 8 | * 9 | * @param {Object} stats - See create-layer-stats.js 10 | * @param {Object} options 11 | * @param {number} options.maxValuesToReport - The maximum number of unique 12 | * attribute values to record. 13 | * @return {Object} The report, which adheres to the relevant JSON schema. 14 | */ 15 | module.exports = function(stats, options) { 16 | 17 | // if we have a stats object already built from an mbtiles file 18 | if (!stats.layerCountSet && 19 | stats.layerCount) return stats; 20 | 21 | return { 22 | layerCount: stats.layerCountSet.size, 23 | layers: stats.layers.map(reportLayer), 24 | }; 25 | 26 | 27 | function reportLayer(layerStats) { 28 | const result = { 29 | layer: layerStats.name, 30 | count: layerStats.featureCount, 31 | attributeCount: layerStats.attributeCountSet.size, 32 | attributes: _.values(layerStats.attributes).map(reportAttribute), 33 | }; 34 | 35 | let dominantGeometry; 36 | let dominantGeometryCount = 0; 37 | Object.keys(layerStats.geometryCounts).forEach((geometry) => { 38 | const count = layerStats.geometryCounts[geometry]; 39 | if (count > dominantGeometryCount) { 40 | dominantGeometryCount = count; 41 | dominantGeometry = geometry; 42 | } 43 | }); 44 | if (dominantGeometry) result.geometry = dominantGeometry; 45 | 46 | return result; 47 | } 48 | 49 | function reportAttribute(attribute) { 50 | // Convert the Set to an array of limited size 51 | const values = []; 52 | const valueSetIterator = attribute.valueSet.values(); 53 | let item = valueSetIterator.next(); 54 | while (!item.done && values.length < options.maxValuesToReport) { 55 | if (isValueReportable(item.value)) { 56 | values.push(item.value); 57 | } 58 | item = valueSetIterator.next(); 59 | } 60 | 61 | const result = Object.assign({ 62 | values: values, 63 | count: attribute.valueSet.size, 64 | }, attribute); 65 | delete result.valueSet; 66 | 67 | return result; 68 | } 69 | }; 70 | 71 | function isValueReportable(x) { 72 | if (typeof x === 'string' 73 | && x.length > Constants.VALUE_STRING_MAX_LENGTH) return false; 74 | return true; 75 | } 76 | -------------------------------------------------------------------------------- /lib/tile-analyze.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tilelive = require('@mapbox/tilelive'); 4 | const tiletype = require('@mapbox/tiletype'); 5 | const Transform = require('stream').Transform; 6 | const util = require('util'); 7 | const MBTiles = require('@mapbox/mbtiles'); 8 | const zlib = require('zlib'); 9 | const mapboxVectorTile = require('@mapbox/vector-tile'); 10 | const Protobuf = require('pbf'); 11 | const _ = require('lodash'); 12 | const createLayerStats = require('./create-layer-stats'); 13 | const registerFeature = require('./register-feature'); 14 | const registerAttributesMap = require('./register-attributes-map'); 15 | const typeIntegerToString = require('./type-integer-to-string'); 16 | const createStats = require('./create-stats'); 17 | const Constants = require('./constants'); 18 | const validator = require('./validate-stats'); 19 | 20 | const VectorTile = mapboxVectorTile.VectorTile; 21 | 22 | function TileAnalyzeStream(processTile) { 23 | this.processTile = processTile; 24 | Transform.call(this, { objectMode: true }); 25 | } 26 | 27 | util.inherits(TileAnalyzeStream, Transform); 28 | 29 | TileAnalyzeStream.prototype._transform = function(data, enc, done) { 30 | // Duck-type the data to see if it's a tile 31 | if (data.x === undefined 32 | || data.y === undefined 33 | || data.z === undefined 34 | || data.buffer === undefined 35 | // Note that tiletype currently does not recognize non-gzipped PBFs 36 | || tiletype.type(data.buffer) !== 'pbf') { 37 | return done(); 38 | } 39 | this.processTile(data).then(() => { 40 | done(); 41 | }, done); 42 | }; 43 | 44 | /** 45 | * Returns stats about an MBTiles file. 46 | * 47 | * @param {string} filePath 48 | * @param {Object} [options] 49 | * @param {Array} [options.attributes] 50 | * @return {Object} The stats. 51 | */ 52 | module.exports = function(filePath, options) { 53 | options = options || {}; 54 | const stats = createStats(); 55 | const layerMap = {}; 56 | 57 | function getSource() { 58 | return new Promise((resolve, reject) => { 59 | new MBTiles(filePath, (err, source) => { 60 | if (err) return reject(err); 61 | resolve(source); 62 | }); 63 | }); 64 | } 65 | 66 | function analyzeSourceStream(source) { 67 | return new Promise((resolve, reject) => { 68 | 69 | // check if the tilestats object has been pre-generated in the mbtiles file 70 | // if so, return that, otherwise generate it it with analyzeSourceStream 71 | source.getInfo((err, info) => { 72 | if (err) reject(err); 73 | if (info.tilestats && validator(info.tilestats)) return resolve(info.tilestats); 74 | 75 | const zxyStream = source.createZXYStream(); 76 | const readStream = tilelive.createReadStream(source, { type: 'list' }); 77 | zxyStream.on('error', reject) 78 | .pipe(readStream) 79 | .pipe(new TileAnalyzeStream(analyzeTile)) 80 | .on('error', reject) 81 | .on('end', () => { 82 | resolve(Object.assign(stats, { 83 | layers: _.values(layerMap), 84 | })); 85 | }) 86 | .resume(); 87 | }); 88 | }); 89 | } 90 | 91 | // Unzips and parses tile data, then analyzes each layer 92 | function analyzeTile(tile) { 93 | return new Promise((resolve, reject) => { 94 | zlib.gunzip(tile.buffer, (err, inflatedBuffer) => { 95 | // We'll get this error if the data was not gzipped. 96 | // So we'll just use the original data. 97 | if (err && err.errno === zlib.Z_DATA_ERROR) { 98 | inflatedBuffer = tile.buffer; 99 | } else if (err) { 100 | return reject(err); 101 | } 102 | let vectorTile; 103 | try { 104 | vectorTile = new VectorTile(new Protobuf(inflatedBuffer)); 105 | } catch (e) { 106 | // We'll get this error if the data cannot be interpreted as a vector tile. 107 | // We skip this in order to see if we can gather data from other tiles. 108 | if (e.message.indexOf('Unimplemented type') === 0) return resolve(); 109 | return reject(e); 110 | } 111 | _.forOwn(vectorTile.layers, analyzeLayer); 112 | resolve(); 113 | }); 114 | }); 115 | } 116 | 117 | function analyzeLayer(layerData, rawLayerName) { 118 | const layerName = rawLayerName.slice(0, Constants.NAME_TRUNCATE_LENGTH); 119 | if (layerMap[layerName] === undefined) { 120 | if (stats.layerCountSet.size > Constants.LAYERS_MAX_COUNT) return; 121 | stats.layerCountSet.add(layerName); 122 | 123 | if (stats.layerCountSet.size > Constants.LAYERS_MAX_REPORT) return; 124 | layerMap[layerName] = createLayerStats(layerName); 125 | } 126 | const layerStats = layerMap[layerName]; 127 | for (let i = 0, l = layerData.length; i < l; i++) { 128 | analyzeFeature(layerStats, layerData.feature(i)); 129 | } 130 | } 131 | 132 | function analyzeFeature(layerStats, feature) { 133 | registerFeature(layerStats, { 134 | type: typeIntegerToString(feature.type), 135 | }); 136 | registerAttributesMap(layerStats, options, feature.properties); 137 | } 138 | 139 | return getSource(filePath).then(analyzeSourceStream); 140 | }; 141 | -------------------------------------------------------------------------------- /lib/type-integer-to-string.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Copied from https://github.com/mapnik/mapnik/blob/v3.0.11/include/mapnik/geometry_types.hpp 4 | 5 | module.exports = function(mapnikType) { 6 | switch (mapnikType) { 7 | case 1: 8 | return 'Point'; 9 | case 2: 10 | return 'LineString'; 11 | case 3: 12 | return 'Polygon'; 13 | case 4: 14 | return 'MultiPoint'; 15 | case 5: 16 | return 'MultiLineString'; 17 | case 6: 18 | return 'MultiPolygon'; 19 | case 7: 20 | return 'GeometryCollection'; 21 | case 0: 22 | default: 23 | return 'Unknown'; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /lib/validate-stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Validator = require('jsonschema').Validator; 4 | const schema = require('../schema/tilestats.json'); 5 | const layerSchema = require('../schema/layer.json'); 6 | const attributeSchema = require('../schema/attribute.json'); 7 | 8 | /** 9 | * Check if a stats JSON object is valid 10 | * 11 | * @param {Object} stats - a JSON tilestats object 12 | * @returns {Boolean|Array} error - returns `true` if the object is valid, or an array error strings 13 | */ 14 | module.exports = function(stats) { 15 | const v = new Validator(); 16 | v.addSchema(layerSchema, '/layer'); 17 | v.addSchema(attributeSchema, '/attribute'); 18 | 19 | const results = v.validate(stats, schema); 20 | if (!results.errors.length) return true; 21 | return results.errors.map(function(err) { 22 | return err.stack; 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/mapbox-geostats", 3 | "version": "1.1.1", 4 | "description": "Generate statistics about geographic data.", 5 | "main": "index.js", 6 | "bin": { 7 | "mapbox-geostats": "bin/mapbox-geostats", 8 | "mapbox-geostats-validate": "bin/mapbox-geostats-validate" 9 | }, 10 | "type": "commonjs", 11 | "scripts": { 12 | "lint": "eslint lib test bin/**", 13 | "lint-fix": "eslint --fix lib test bin/**", 14 | "test": "npm run lint && tap test/test.js", 15 | "coverage": "tap test/test.js --cov --coverage-report=html" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/mapbox/mapbox-geostats.git" 20 | }, 21 | "author": "Mapbox", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/mapbox/mapbox-geostats/issues" 25 | }, 26 | "homepage": "https://github.com/mapbox/mapbox-geostats#readme", 27 | "dependencies": { 28 | "@mapbox/mapbox-file-sniff": "^1.0.6", 29 | "@mapbox/mbtiles": "^0.12.1", 30 | "@mapbox/tilelive": "^6.1.0", 31 | "@mapbox/tiletype": "^0.3.0", 32 | "@mapbox/vector-tile": "^1.2.1", 33 | "d3-queue": "^3.0.7", 34 | "jsonschema": "^1.1.1", 35 | "lodash": "^4.13.1", 36 | "mapnik": "^4.5.9", 37 | "meow": "^9.0.0", 38 | "pbf": "^2.0.1" 39 | }, 40 | "devDependencies": { 41 | "eslint": "^7.32.0", 42 | "eslint-plugin-node": "^11.1.0", 43 | "tap": "^21.1.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /schema/attribute.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "/attribute", 3 | "type": "object", 4 | "properties": { 5 | "attribute": { "type": "string" }, 6 | "count": { "type": "number" }, 7 | "type": { 8 | "type": "string", 9 | "enum": ["string", "number", "boolean", "null", "mixed"] 10 | }, 11 | "values": { 12 | "anyOf": [ 13 | { "type": "array", "items": { "type": "string" } }, 14 | { "type": "array", "items": { "type": "number" } }, 15 | { "type": "array", "items": { "type": "boolean" } }, 16 | { "type": "array", "items": { "type": "null" } } 17 | ] 18 | }, 19 | "min": { "type": "number" }, 20 | "max": { "type": "number" } 21 | }, 22 | "required": [ "attribute", "count", "type", "values" ] 23 | } 24 | -------------------------------------------------------------------------------- /schema/layer.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "/layer", 3 | "type": "object", 4 | "properties": { 5 | "layer": { "type": "string" }, 6 | "count": { "type": "number" }, 7 | "geometry": { 8 | "type": "string", 9 | "enum": [ "Point", "LineString", "Polygon" ] 10 | }, 11 | "attributeCount": { "type": "number" }, 12 | "attributes": { 13 | "type": "array", 14 | "items": { "$ref": "/attribute" } 15 | } 16 | }, 17 | "required": [ "layer", "count", "geometry", "attributeCount" ] 18 | } 19 | -------------------------------------------------------------------------------- /schema/tilestats.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "/tilestats", 3 | "type": "object", 4 | "properties": { 5 | "layerCount": { "type": "number" }, 6 | "layers": { 7 | "type": "array", 8 | "items": { "$ref": "/layer" } 9 | } 10 | }, 11 | "required": [ "layerCount", "layers" ] 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/expected/geojson-invalid-geometry-types.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "geometry-invalid-types", 6 | "count": 1, 7 | "attributeCount": 1, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | "bar" 12 | ], 13 | "count": 1, 14 | "attribute": "foo", 15 | "type": "string" 16 | } 17 | ], 18 | "geometry": "Point" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /test/fixtures/expected/geometry-extravaganza.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "geometry-extravaganza", 6 | "count": 6, 7 | "attributeCount": 4, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | "bar", 12 | null 13 | ], 14 | "count": 2, 15 | "attribute": "foo", 16 | "type": "string" 17 | }, 18 | { 19 | "values": [ 20 | 1, 21 | null 22 | ], 23 | "count": 2, 24 | "attribute": "bar", 25 | "type": "number", 26 | "min": 1, 27 | "max": 1 28 | }, 29 | { 30 | "values": [ 31 | "doo", 32 | null 33 | ], 34 | "count": 2, 35 | "attribute": "doodoo", 36 | "type": "string" 37 | }, 38 | { 39 | "values": [ 40 | true 41 | ], 42 | "count": 1, 43 | "attribute": "baz", 44 | "type": "boolean" 45 | } 46 | ], 47 | "geometry": "Point" 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /test/fixtures/expected/long-attribute-names.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "long-attribute-names", 6 | "count": 2, 7 | "attributeCount": 1, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | 1, 12 | 2 13 | ], 14 | "count": 2, 15 | "attribute": "LoremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaUtenimadminimveniamquisnostrudexercitationullamcolaborisnisiutaliquipexeacommodoconsequatDuisauteiruredolorinreprehenderitinvoluptatevelitessecillumdolor", 16 | "type": "number", 17 | "min": 1, 18 | "max": 2 19 | } 20 | ], 21 | "geometry": "Point" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /test/fixtures/expected/many-types-geojson.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "many-types", 6 | "count": 6, 7 | "attributeCount": 8, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | true, 12 | null 13 | ], 14 | "count": 2, 15 | "attribute": "astonishing", 16 | "type": "boolean" 17 | }, 18 | { 19 | "values": [ 20 | 7, 21 | "7", 22 | null 23 | ], 24 | "count": 3, 25 | "attribute": "mixed", 26 | "type": "mixed", 27 | "min": 7, 28 | "max": 7 29 | }, 30 | { 31 | "values": [ 32 | "foo", 33 | "bar", 34 | "baz", 35 | "haha", 36 | "hoho", 37 | "hehe" 38 | ], 39 | "count": 6, 40 | "attribute": "name", 41 | "type": "string" 42 | }, 43 | { 44 | "values": [ 45 | "[1,2,3]", 46 | null 47 | ], 48 | "count": 2, 49 | "attribute": "numbers", 50 | "type": "string" 51 | }, 52 | { 53 | "values": [ 54 | 6, 55 | 99, 56 | null, 57 | 86 58 | ], 59 | "count": 4, 60 | "attribute": "power", 61 | "type": "number", 62 | "min": 6, 63 | "max": 99 64 | }, 65 | { 66 | "values": [ 67 | 6, 68 | "{\"foo\":\"bar\"}" 69 | ], 70 | "count": 2, 71 | "attribute": "data", 72 | "type": "mixed", 73 | "min": 6, 74 | "max": 6 75 | }, 76 | { 77 | "values": [], 78 | "count": 1, 79 | "attribute": "description", 80 | "type": "string" 81 | }, 82 | { 83 | "values": [ 84 | null 85 | ], 86 | "count": 1, 87 | "attribute": "onlyNull", 88 | "type": "null" 89 | } 90 | ], 91 | "geometry": "Point" 92 | } 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /test/fixtures/expected/many-types-mbtiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "many-types", 6 | "count": 52, 7 | "attributeCount": 7, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | true 12 | ], 13 | "count": 1, 14 | "attribute": "astonishing", 15 | "type": "boolean" 16 | }, 17 | { 18 | "values": [ 19 | 7, 20 | "7" 21 | ], 22 | "count": 2, 23 | "attribute": "mixed", 24 | "type": "mixed", 25 | "min": 7, 26 | "max": 7 27 | }, 28 | { 29 | "values": [ 30 | "foo", 31 | "bar", 32 | "baz", 33 | "haha", 34 | "hoho", 35 | "hehe" 36 | ], 37 | "count": 6, 38 | "attribute": "name", 39 | "type": "string" 40 | }, 41 | { 42 | "values": [ 43 | "[1,2,3]" 44 | ], 45 | "count": 1, 46 | "attribute": "numbers", 47 | "type": "string" 48 | }, 49 | { 50 | "values": [ 51 | 6, 52 | 99, 53 | 86 54 | ], 55 | "count": 3, 56 | "attribute": "power", 57 | "type": "number", 58 | "min": 6, 59 | "max": 99 60 | }, 61 | { 62 | "values": [ 63 | 6, 64 | "{\"foo\":\"bar\"}" 65 | ], 66 | "count": 2, 67 | "attribute": "data", 68 | "type": "mixed", 69 | "min": 6, 70 | "max": 6 71 | }, 72 | { 73 | "values": [], 74 | "count": 1, 75 | "attribute": "description", 76 | "type": "string" 77 | } 78 | ], 79 | "geometry": "Point" 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /test/fixtures/expected/myriad-values-all-attrs.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "myriad-values", 6 | "count": 10010, 7 | "attributeCount": 10, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | "0-value-0", 12 | "1-value-0", 13 | "2-value-0", 14 | "3-value-0", 15 | "4-value-0", 16 | "5-value-0", 17 | "6-value-0", 18 | "7-value-0", 19 | "8-value-0", 20 | "9-value-0", 21 | "10-value-0", 22 | "11-value-0", 23 | "12-value-0", 24 | "13-value-0", 25 | "14-value-0", 26 | "15-value-0", 27 | "16-value-0", 28 | "17-value-0", 29 | "18-value-0", 30 | "19-value-0", 31 | "20-value-0", 32 | "21-value-0", 33 | "22-value-0", 34 | "23-value-0", 35 | "24-value-0", 36 | "25-value-0", 37 | "26-value-0", 38 | "27-value-0", 39 | "28-value-0", 40 | "29-value-0", 41 | "30-value-0", 42 | "31-value-0", 43 | "32-value-0", 44 | "33-value-0", 45 | "34-value-0", 46 | "35-value-0", 47 | "36-value-0", 48 | "37-value-0", 49 | "38-value-0", 50 | "39-value-0", 51 | "40-value-0", 52 | "41-value-0", 53 | "42-value-0", 54 | "43-value-0", 55 | "44-value-0", 56 | "45-value-0", 57 | "46-value-0", 58 | "47-value-0", 59 | "48-value-0", 60 | "49-value-0", 61 | "50-value-0", 62 | "51-value-0", 63 | "52-value-0", 64 | "53-value-0", 65 | "54-value-0", 66 | "55-value-0", 67 | "56-value-0", 68 | "57-value-0", 69 | "58-value-0", 70 | "59-value-0", 71 | "60-value-0", 72 | "61-value-0", 73 | "62-value-0", 74 | "63-value-0", 75 | "64-value-0", 76 | "65-value-0", 77 | "66-value-0", 78 | "67-value-0", 79 | "68-value-0", 80 | "69-value-0", 81 | "70-value-0", 82 | "71-value-0", 83 | "72-value-0", 84 | "73-value-0", 85 | "74-value-0", 86 | "75-value-0", 87 | "76-value-0", 88 | "77-value-0", 89 | "78-value-0", 90 | "79-value-0", 91 | "80-value-0", 92 | "81-value-0", 93 | "82-value-0", 94 | "83-value-0", 95 | "84-value-0", 96 | "85-value-0", 97 | "86-value-0", 98 | "87-value-0", 99 | "88-value-0", 100 | "89-value-0", 101 | "90-value-0", 102 | "91-value-0", 103 | "92-value-0", 104 | "93-value-0", 105 | "94-value-0", 106 | "95-value-0", 107 | "96-value-0", 108 | "97-value-0", 109 | "98-value-0", 110 | "99-value-0" 111 | ], 112 | "count": 1000, 113 | "attribute": "prop-0", 114 | "type": "string" 115 | }, 116 | { 117 | "values": [ 118 | "0-value-1", 119 | "1-value-1", 120 | "2-value-1", 121 | "3-value-1", 122 | "4-value-1", 123 | "5-value-1", 124 | "6-value-1", 125 | "7-value-1", 126 | "8-value-1", 127 | "9-value-1", 128 | "10-value-1", 129 | "11-value-1", 130 | "12-value-1", 131 | "13-value-1", 132 | "14-value-1", 133 | "15-value-1", 134 | "16-value-1", 135 | "17-value-1", 136 | "18-value-1", 137 | "19-value-1", 138 | "20-value-1", 139 | "21-value-1", 140 | "22-value-1", 141 | "23-value-1", 142 | "24-value-1", 143 | "25-value-1", 144 | "26-value-1", 145 | "27-value-1", 146 | "28-value-1", 147 | "29-value-1", 148 | "30-value-1", 149 | "31-value-1", 150 | "32-value-1", 151 | "33-value-1", 152 | "34-value-1", 153 | "35-value-1", 154 | "36-value-1", 155 | "37-value-1", 156 | "38-value-1", 157 | "39-value-1", 158 | "40-value-1", 159 | "41-value-1", 160 | "42-value-1", 161 | "43-value-1", 162 | "44-value-1", 163 | "45-value-1", 164 | "46-value-1", 165 | "47-value-1", 166 | "48-value-1", 167 | "49-value-1", 168 | "50-value-1", 169 | "51-value-1", 170 | "52-value-1", 171 | "53-value-1", 172 | "54-value-1", 173 | "55-value-1", 174 | "56-value-1", 175 | "57-value-1", 176 | "58-value-1", 177 | "59-value-1", 178 | "60-value-1", 179 | "61-value-1", 180 | "62-value-1", 181 | "63-value-1", 182 | "64-value-1", 183 | "65-value-1", 184 | "66-value-1", 185 | "67-value-1", 186 | "68-value-1", 187 | "69-value-1", 188 | "70-value-1", 189 | "71-value-1", 190 | "72-value-1", 191 | "73-value-1", 192 | "74-value-1", 193 | "75-value-1", 194 | "76-value-1", 195 | "77-value-1", 196 | "78-value-1", 197 | "79-value-1", 198 | "80-value-1", 199 | "81-value-1", 200 | "82-value-1", 201 | "83-value-1", 202 | "84-value-1", 203 | "85-value-1", 204 | "86-value-1", 205 | "87-value-1", 206 | "88-value-1", 207 | "89-value-1", 208 | "90-value-1", 209 | "91-value-1", 210 | "92-value-1", 211 | "93-value-1", 212 | "94-value-1", 213 | "95-value-1", 214 | "96-value-1", 215 | "97-value-1", 216 | "98-value-1", 217 | "99-value-1" 218 | ], 219 | "count": 1000, 220 | "attribute": "prop-1", 221 | "type": "string" 222 | }, 223 | { 224 | "values": [ 225 | "0-value-2", 226 | "1-value-2", 227 | "2-value-2", 228 | "3-value-2", 229 | "4-value-2", 230 | "5-value-2", 231 | "6-value-2", 232 | "7-value-2", 233 | "8-value-2", 234 | "9-value-2", 235 | "10-value-2", 236 | "11-value-2", 237 | "12-value-2", 238 | "13-value-2", 239 | "14-value-2", 240 | "15-value-2", 241 | "16-value-2", 242 | "17-value-2", 243 | "18-value-2", 244 | "19-value-2", 245 | "20-value-2", 246 | "21-value-2", 247 | "22-value-2", 248 | "23-value-2", 249 | "24-value-2", 250 | "25-value-2", 251 | "26-value-2", 252 | "27-value-2", 253 | "28-value-2", 254 | "29-value-2", 255 | "30-value-2", 256 | "31-value-2", 257 | "32-value-2", 258 | "33-value-2", 259 | "34-value-2", 260 | "35-value-2", 261 | "36-value-2", 262 | "37-value-2", 263 | "38-value-2", 264 | "39-value-2", 265 | "40-value-2", 266 | "41-value-2", 267 | "42-value-2", 268 | "43-value-2", 269 | "44-value-2", 270 | "45-value-2", 271 | "46-value-2", 272 | "47-value-2", 273 | "48-value-2", 274 | "49-value-2", 275 | "50-value-2", 276 | "51-value-2", 277 | "52-value-2", 278 | "53-value-2", 279 | "54-value-2", 280 | "55-value-2", 281 | "56-value-2", 282 | "57-value-2", 283 | "58-value-2", 284 | "59-value-2", 285 | "60-value-2", 286 | "61-value-2", 287 | "62-value-2", 288 | "63-value-2", 289 | "64-value-2", 290 | "65-value-2", 291 | "66-value-2", 292 | "67-value-2", 293 | "68-value-2", 294 | "69-value-2", 295 | "70-value-2", 296 | "71-value-2", 297 | "72-value-2", 298 | "73-value-2", 299 | "74-value-2", 300 | "75-value-2", 301 | "76-value-2", 302 | "77-value-2", 303 | "78-value-2", 304 | "79-value-2", 305 | "80-value-2", 306 | "81-value-2", 307 | "82-value-2", 308 | "83-value-2", 309 | "84-value-2", 310 | "85-value-2", 311 | "86-value-2", 312 | "87-value-2", 313 | "88-value-2", 314 | "89-value-2", 315 | "90-value-2", 316 | "91-value-2", 317 | "92-value-2", 318 | "93-value-2", 319 | "94-value-2", 320 | "95-value-2", 321 | "96-value-2", 322 | "97-value-2", 323 | "98-value-2", 324 | "99-value-2" 325 | ], 326 | "count": 1000, 327 | "attribute": "prop-2", 328 | "type": "string" 329 | }, 330 | { 331 | "values": [ 332 | "0-value-3", 333 | "1-value-3", 334 | "2-value-3", 335 | "3-value-3", 336 | "4-value-3", 337 | "5-value-3", 338 | "6-value-3", 339 | "7-value-3", 340 | "8-value-3", 341 | "9-value-3", 342 | "10-value-3", 343 | "11-value-3", 344 | "12-value-3", 345 | "13-value-3", 346 | "14-value-3", 347 | "15-value-3", 348 | "16-value-3", 349 | "17-value-3", 350 | "18-value-3", 351 | "19-value-3", 352 | "20-value-3", 353 | "21-value-3", 354 | "22-value-3", 355 | "23-value-3", 356 | "24-value-3", 357 | "25-value-3", 358 | "26-value-3", 359 | "27-value-3", 360 | "28-value-3", 361 | "29-value-3", 362 | "30-value-3", 363 | "31-value-3", 364 | "32-value-3", 365 | "33-value-3", 366 | "34-value-3", 367 | "35-value-3", 368 | "36-value-3", 369 | "37-value-3", 370 | "38-value-3", 371 | "39-value-3", 372 | "40-value-3", 373 | "41-value-3", 374 | "42-value-3", 375 | "43-value-3", 376 | "44-value-3", 377 | "45-value-3", 378 | "46-value-3", 379 | "47-value-3", 380 | "48-value-3", 381 | "49-value-3", 382 | "50-value-3", 383 | "51-value-3", 384 | "52-value-3", 385 | "53-value-3", 386 | "54-value-3", 387 | "55-value-3", 388 | "56-value-3", 389 | "57-value-3", 390 | "58-value-3", 391 | "59-value-3", 392 | "60-value-3", 393 | "61-value-3", 394 | "62-value-3", 395 | "63-value-3", 396 | "64-value-3", 397 | "65-value-3", 398 | "66-value-3", 399 | "67-value-3", 400 | "68-value-3", 401 | "69-value-3", 402 | "70-value-3", 403 | "71-value-3", 404 | "72-value-3", 405 | "73-value-3", 406 | "74-value-3", 407 | "75-value-3", 408 | "76-value-3", 409 | "77-value-3", 410 | "78-value-3", 411 | "79-value-3", 412 | "80-value-3", 413 | "81-value-3", 414 | "82-value-3", 415 | "83-value-3", 416 | "84-value-3", 417 | "85-value-3", 418 | "86-value-3", 419 | "87-value-3", 420 | "88-value-3", 421 | "89-value-3", 422 | "90-value-3", 423 | "91-value-3", 424 | "92-value-3", 425 | "93-value-3", 426 | "94-value-3", 427 | "95-value-3", 428 | "96-value-3", 429 | "97-value-3", 430 | "98-value-3", 431 | "99-value-3" 432 | ], 433 | "count": 1000, 434 | "attribute": "prop-3", 435 | "type": "string" 436 | }, 437 | { 438 | "values": [ 439 | "0-value-4", 440 | "1-value-4", 441 | "2-value-4", 442 | "3-value-4", 443 | "4-value-4", 444 | "5-value-4", 445 | "6-value-4", 446 | "7-value-4", 447 | "8-value-4", 448 | "9-value-4", 449 | "10-value-4", 450 | "11-value-4", 451 | "12-value-4", 452 | "13-value-4", 453 | "14-value-4", 454 | "15-value-4", 455 | "16-value-4", 456 | "17-value-4", 457 | "18-value-4", 458 | "19-value-4", 459 | "20-value-4", 460 | "21-value-4", 461 | "22-value-4", 462 | "23-value-4", 463 | "24-value-4", 464 | "25-value-4", 465 | "26-value-4", 466 | "27-value-4", 467 | "28-value-4", 468 | "29-value-4", 469 | "30-value-4", 470 | "31-value-4", 471 | "32-value-4", 472 | "33-value-4", 473 | "34-value-4", 474 | "35-value-4", 475 | "36-value-4", 476 | "37-value-4", 477 | "38-value-4", 478 | "39-value-4", 479 | "40-value-4", 480 | "41-value-4", 481 | "42-value-4", 482 | "43-value-4", 483 | "44-value-4", 484 | "45-value-4", 485 | "46-value-4", 486 | "47-value-4", 487 | "48-value-4", 488 | "49-value-4", 489 | "50-value-4", 490 | "51-value-4", 491 | "52-value-4", 492 | "53-value-4", 493 | "54-value-4", 494 | "55-value-4", 495 | "56-value-4", 496 | "57-value-4", 497 | "58-value-4", 498 | "59-value-4", 499 | "60-value-4", 500 | "61-value-4", 501 | "62-value-4", 502 | "63-value-4", 503 | "64-value-4", 504 | "65-value-4", 505 | "66-value-4", 506 | "67-value-4", 507 | "68-value-4", 508 | "69-value-4", 509 | "70-value-4", 510 | "71-value-4", 511 | "72-value-4", 512 | "73-value-4", 513 | "74-value-4", 514 | "75-value-4", 515 | "76-value-4", 516 | "77-value-4", 517 | "78-value-4", 518 | "79-value-4", 519 | "80-value-4", 520 | "81-value-4", 521 | "82-value-4", 522 | "83-value-4", 523 | "84-value-4", 524 | "85-value-4", 525 | "86-value-4", 526 | "87-value-4", 527 | "88-value-4", 528 | "89-value-4", 529 | "90-value-4", 530 | "91-value-4", 531 | "92-value-4", 532 | "93-value-4", 533 | "94-value-4", 534 | "95-value-4", 535 | "96-value-4", 536 | "97-value-4", 537 | "98-value-4", 538 | "99-value-4" 539 | ], 540 | "count": 1000, 541 | "attribute": "prop-4", 542 | "type": "string" 543 | }, 544 | { 545 | "values": [ 546 | "0-value-5", 547 | "1-value-5", 548 | "2-value-5", 549 | "3-value-5", 550 | "4-value-5", 551 | "5-value-5", 552 | "6-value-5", 553 | "7-value-5", 554 | "8-value-5", 555 | "9-value-5", 556 | "10-value-5", 557 | "11-value-5", 558 | "12-value-5", 559 | "13-value-5", 560 | "14-value-5", 561 | "15-value-5", 562 | "16-value-5", 563 | "17-value-5", 564 | "18-value-5", 565 | "19-value-5", 566 | "20-value-5", 567 | "21-value-5", 568 | "22-value-5", 569 | "23-value-5", 570 | "24-value-5", 571 | "25-value-5", 572 | "26-value-5", 573 | "27-value-5", 574 | "28-value-5", 575 | "29-value-5", 576 | "30-value-5", 577 | "31-value-5", 578 | "32-value-5", 579 | "33-value-5", 580 | "34-value-5", 581 | "35-value-5", 582 | "36-value-5", 583 | "37-value-5", 584 | "38-value-5", 585 | "39-value-5", 586 | "40-value-5", 587 | "41-value-5", 588 | "42-value-5", 589 | "43-value-5", 590 | "44-value-5", 591 | "45-value-5", 592 | "46-value-5", 593 | "47-value-5", 594 | "48-value-5", 595 | "49-value-5", 596 | "50-value-5", 597 | "51-value-5", 598 | "52-value-5", 599 | "53-value-5", 600 | "54-value-5", 601 | "55-value-5", 602 | "56-value-5", 603 | "57-value-5", 604 | "58-value-5", 605 | "59-value-5", 606 | "60-value-5", 607 | "61-value-5", 608 | "62-value-5", 609 | "63-value-5", 610 | "64-value-5", 611 | "65-value-5", 612 | "66-value-5", 613 | "67-value-5", 614 | "68-value-5", 615 | "69-value-5", 616 | "70-value-5", 617 | "71-value-5", 618 | "72-value-5", 619 | "73-value-5", 620 | "74-value-5", 621 | "75-value-5", 622 | "76-value-5", 623 | "77-value-5", 624 | "78-value-5", 625 | "79-value-5", 626 | "80-value-5", 627 | "81-value-5", 628 | "82-value-5", 629 | "83-value-5", 630 | "84-value-5", 631 | "85-value-5", 632 | "86-value-5", 633 | "87-value-5", 634 | "88-value-5", 635 | "89-value-5", 636 | "90-value-5", 637 | "91-value-5", 638 | "92-value-5", 639 | "93-value-5", 640 | "94-value-5", 641 | "95-value-5", 642 | "96-value-5", 643 | "97-value-5", 644 | "98-value-5", 645 | "99-value-5" 646 | ], 647 | "count": 1000, 648 | "attribute": "prop-5", 649 | "type": "string" 650 | }, 651 | { 652 | "values": [ 653 | "0-value-6", 654 | "1-value-6", 655 | "2-value-6", 656 | "3-value-6", 657 | "4-value-6", 658 | "5-value-6", 659 | "6-value-6", 660 | "7-value-6", 661 | "8-value-6", 662 | "9-value-6", 663 | "10-value-6", 664 | "11-value-6", 665 | "12-value-6", 666 | "13-value-6", 667 | "14-value-6", 668 | "15-value-6", 669 | "16-value-6", 670 | "17-value-6", 671 | "18-value-6", 672 | "19-value-6", 673 | "20-value-6", 674 | "21-value-6", 675 | "22-value-6", 676 | "23-value-6", 677 | "24-value-6", 678 | "25-value-6", 679 | "26-value-6", 680 | "27-value-6", 681 | "28-value-6", 682 | "29-value-6", 683 | "30-value-6", 684 | "31-value-6", 685 | "32-value-6", 686 | "33-value-6", 687 | "34-value-6", 688 | "35-value-6", 689 | "36-value-6", 690 | "37-value-6", 691 | "38-value-6", 692 | "39-value-6", 693 | "40-value-6", 694 | "41-value-6", 695 | "42-value-6", 696 | "43-value-6", 697 | "44-value-6", 698 | "45-value-6", 699 | "46-value-6", 700 | "47-value-6", 701 | "48-value-6", 702 | "49-value-6", 703 | "50-value-6", 704 | "51-value-6", 705 | "52-value-6", 706 | "53-value-6", 707 | "54-value-6", 708 | "55-value-6", 709 | "56-value-6", 710 | "57-value-6", 711 | "58-value-6", 712 | "59-value-6", 713 | "60-value-6", 714 | "61-value-6", 715 | "62-value-6", 716 | "63-value-6", 717 | "64-value-6", 718 | "65-value-6", 719 | "66-value-6", 720 | "67-value-6", 721 | "68-value-6", 722 | "69-value-6", 723 | "70-value-6", 724 | "71-value-6", 725 | "72-value-6", 726 | "73-value-6", 727 | "74-value-6", 728 | "75-value-6", 729 | "76-value-6", 730 | "77-value-6", 731 | "78-value-6", 732 | "79-value-6", 733 | "80-value-6", 734 | "81-value-6", 735 | "82-value-6", 736 | "83-value-6", 737 | "84-value-6", 738 | "85-value-6", 739 | "86-value-6", 740 | "87-value-6", 741 | "88-value-6", 742 | "89-value-6", 743 | "90-value-6", 744 | "91-value-6", 745 | "92-value-6", 746 | "93-value-6", 747 | "94-value-6", 748 | "95-value-6", 749 | "96-value-6", 750 | "97-value-6", 751 | "98-value-6", 752 | "99-value-6" 753 | ], 754 | "count": 1000, 755 | "attribute": "prop-6", 756 | "type": "string" 757 | }, 758 | { 759 | "values": [ 760 | "0-value-7", 761 | "1-value-7", 762 | "2-value-7", 763 | "3-value-7", 764 | "4-value-7", 765 | "5-value-7", 766 | "6-value-7", 767 | "7-value-7", 768 | "8-value-7", 769 | "9-value-7", 770 | "10-value-7", 771 | "11-value-7", 772 | "12-value-7", 773 | "13-value-7", 774 | "14-value-7", 775 | "15-value-7", 776 | "16-value-7", 777 | "17-value-7", 778 | "18-value-7", 779 | "19-value-7", 780 | "20-value-7", 781 | "21-value-7", 782 | "22-value-7", 783 | "23-value-7", 784 | "24-value-7", 785 | "25-value-7", 786 | "26-value-7", 787 | "27-value-7", 788 | "28-value-7", 789 | "29-value-7", 790 | "30-value-7", 791 | "31-value-7", 792 | "32-value-7", 793 | "33-value-7", 794 | "34-value-7", 795 | "35-value-7", 796 | "36-value-7", 797 | "37-value-7", 798 | "38-value-7", 799 | "39-value-7", 800 | "40-value-7", 801 | "41-value-7", 802 | "42-value-7", 803 | "43-value-7", 804 | "44-value-7", 805 | "45-value-7", 806 | "46-value-7", 807 | "47-value-7", 808 | "48-value-7", 809 | "49-value-7", 810 | "50-value-7", 811 | "51-value-7", 812 | "52-value-7", 813 | "53-value-7", 814 | "54-value-7", 815 | "55-value-7", 816 | "56-value-7", 817 | "57-value-7", 818 | "58-value-7", 819 | "59-value-7", 820 | "60-value-7", 821 | "61-value-7", 822 | "62-value-7", 823 | "63-value-7", 824 | "64-value-7", 825 | "65-value-7", 826 | "66-value-7", 827 | "67-value-7", 828 | "68-value-7", 829 | "69-value-7", 830 | "70-value-7", 831 | "71-value-7", 832 | "72-value-7", 833 | "73-value-7", 834 | "74-value-7", 835 | "75-value-7", 836 | "76-value-7", 837 | "77-value-7", 838 | "78-value-7", 839 | "79-value-7", 840 | "80-value-7", 841 | "81-value-7", 842 | "82-value-7", 843 | "83-value-7", 844 | "84-value-7", 845 | "85-value-7", 846 | "86-value-7", 847 | "87-value-7", 848 | "88-value-7", 849 | "89-value-7", 850 | "90-value-7", 851 | "91-value-7", 852 | "92-value-7", 853 | "93-value-7", 854 | "94-value-7", 855 | "95-value-7", 856 | "96-value-7", 857 | "97-value-7", 858 | "98-value-7", 859 | "99-value-7" 860 | ], 861 | "count": 1000, 862 | "attribute": "prop-7", 863 | "type": "string" 864 | }, 865 | { 866 | "values": [ 867 | "0-value-8", 868 | "1-value-8", 869 | "2-value-8", 870 | "3-value-8", 871 | "4-value-8", 872 | "5-value-8", 873 | "6-value-8", 874 | "7-value-8", 875 | "8-value-8", 876 | "9-value-8", 877 | "10-value-8", 878 | "11-value-8", 879 | "12-value-8", 880 | "13-value-8", 881 | "14-value-8", 882 | "15-value-8", 883 | "16-value-8", 884 | "17-value-8", 885 | "18-value-8", 886 | "19-value-8", 887 | "20-value-8", 888 | "21-value-8", 889 | "22-value-8", 890 | "23-value-8", 891 | "24-value-8", 892 | "25-value-8", 893 | "26-value-8", 894 | "27-value-8", 895 | "28-value-8", 896 | "29-value-8", 897 | "30-value-8", 898 | "31-value-8", 899 | "32-value-8", 900 | "33-value-8", 901 | "34-value-8", 902 | "35-value-8", 903 | "36-value-8", 904 | "37-value-8", 905 | "38-value-8", 906 | "39-value-8", 907 | "40-value-8", 908 | "41-value-8", 909 | "42-value-8", 910 | "43-value-8", 911 | "44-value-8", 912 | "45-value-8", 913 | "46-value-8", 914 | "47-value-8", 915 | "48-value-8", 916 | "49-value-8", 917 | "50-value-8", 918 | "51-value-8", 919 | "52-value-8", 920 | "53-value-8", 921 | "54-value-8", 922 | "55-value-8", 923 | "56-value-8", 924 | "57-value-8", 925 | "58-value-8", 926 | "59-value-8", 927 | "60-value-8", 928 | "61-value-8", 929 | "62-value-8", 930 | "63-value-8", 931 | "64-value-8", 932 | "65-value-8", 933 | "66-value-8", 934 | "67-value-8", 935 | "68-value-8", 936 | "69-value-8", 937 | "70-value-8", 938 | "71-value-8", 939 | "72-value-8", 940 | "73-value-8", 941 | "74-value-8", 942 | "75-value-8", 943 | "76-value-8", 944 | "77-value-8", 945 | "78-value-8", 946 | "79-value-8", 947 | "80-value-8", 948 | "81-value-8", 949 | "82-value-8", 950 | "83-value-8", 951 | "84-value-8", 952 | "85-value-8", 953 | "86-value-8", 954 | "87-value-8", 955 | "88-value-8", 956 | "89-value-8", 957 | "90-value-8", 958 | "91-value-8", 959 | "92-value-8", 960 | "93-value-8", 961 | "94-value-8", 962 | "95-value-8", 963 | "96-value-8", 964 | "97-value-8", 965 | "98-value-8", 966 | "99-value-8" 967 | ], 968 | "count": 1000, 969 | "attribute": "prop-8", 970 | "type": "string" 971 | }, 972 | { 973 | "values": [ 974 | "0-value-9", 975 | "1-value-9", 976 | "2-value-9", 977 | "3-value-9", 978 | "4-value-9", 979 | "5-value-9", 980 | "6-value-9", 981 | "7-value-9", 982 | "8-value-9", 983 | "9-value-9", 984 | "10-value-9", 985 | "11-value-9", 986 | "12-value-9", 987 | "13-value-9", 988 | "14-value-9", 989 | "15-value-9", 990 | "16-value-9", 991 | "17-value-9", 992 | "18-value-9", 993 | "19-value-9", 994 | "20-value-9", 995 | "21-value-9", 996 | "22-value-9", 997 | "23-value-9", 998 | "24-value-9", 999 | "25-value-9", 1000 | "26-value-9", 1001 | "27-value-9", 1002 | "28-value-9", 1003 | "29-value-9", 1004 | "30-value-9", 1005 | "31-value-9", 1006 | "32-value-9", 1007 | "33-value-9", 1008 | "34-value-9", 1009 | "35-value-9", 1010 | "36-value-9", 1011 | "37-value-9", 1012 | "38-value-9", 1013 | "39-value-9", 1014 | "40-value-9", 1015 | "41-value-9", 1016 | "42-value-9", 1017 | "43-value-9", 1018 | "44-value-9", 1019 | "45-value-9", 1020 | "46-value-9", 1021 | "47-value-9", 1022 | "48-value-9", 1023 | "49-value-9", 1024 | "50-value-9", 1025 | "51-value-9", 1026 | "52-value-9", 1027 | "53-value-9", 1028 | "54-value-9", 1029 | "55-value-9", 1030 | "56-value-9", 1031 | "57-value-9", 1032 | "58-value-9", 1033 | "59-value-9", 1034 | "60-value-9", 1035 | "61-value-9", 1036 | "62-value-9", 1037 | "63-value-9", 1038 | "64-value-9", 1039 | "65-value-9", 1040 | "66-value-9", 1041 | "67-value-9", 1042 | "68-value-9", 1043 | "69-value-9", 1044 | "70-value-9", 1045 | "71-value-9", 1046 | "72-value-9", 1047 | "73-value-9", 1048 | "74-value-9", 1049 | "75-value-9", 1050 | "76-value-9", 1051 | "77-value-9", 1052 | "78-value-9", 1053 | "79-value-9", 1054 | "80-value-9", 1055 | "81-value-9", 1056 | "82-value-9", 1057 | "83-value-9", 1058 | "84-value-9", 1059 | "85-value-9", 1060 | "86-value-9", 1061 | "87-value-9", 1062 | "88-value-9", 1063 | "89-value-9", 1064 | "90-value-9", 1065 | "91-value-9", 1066 | "92-value-9", 1067 | "93-value-9", 1068 | "94-value-9", 1069 | "95-value-9", 1070 | "96-value-9", 1071 | "97-value-9", 1072 | "98-value-9", 1073 | "99-value-9" 1074 | ], 1075 | "count": 1000, 1076 | "attribute": "prop-9", 1077 | "type": "string" 1078 | } 1079 | ], 1080 | "geometry": "Point" 1081 | } 1082 | ] 1083 | } 1084 | -------------------------------------------------------------------------------- /test/fixtures/expected/no-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "no-features", 6 | "count": 0, 7 | "attributeCount": 0, 8 | "attributes": [] 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/expected/ports-only-name.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "ports", 6 | "count": 1081, 7 | "attributeCount": 1, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | "Sint Nicolaas", 12 | "Campana", 13 | "Zarate", 14 | "Puerto Belgrano/Bahia Blanca", 15 | "Puerto Galvan/Bahia Blanca", 16 | "Ingeniero White/Bahia Blanca", 17 | "Saint John's", 18 | "Fremantle", 19 | "Botany Bay", 20 | "Rockhampton", 21 | "Oostende (Ostend)", 22 | "Zeebrugge", 23 | "Burgas", 24 | "Mina Sulman Port", 25 | "Sitrah", 26 | "Saint George", 27 | "Mucuripe", 28 | "Niteroi", 29 | "Sao Francisco do Sul", 30 | "Chatham", 31 | "Clarenville", 32 | "Comox", 33 | "Esquimalt", 34 | "Grand Bank", 35 | "North Sydney", 36 | "Caraquet", 37 | "Chicoutimi", 38 | "Corner Brook", 39 | "Digby", 40 | "Harbour Grace", 41 | "Pictou", 42 | "Shelburne", 43 | "Sorel", 44 | "Springdale", 45 | "Saint-John's", 46 | "Coronel", 47 | "Lota", 48 | "Talcahuano", 49 | "San Antonio", 50 | "Haimen", 51 | "Qinhuangdao", 52 | "Yantian", 53 | "Yingkou", 54 | "Moin", 55 | "Banes", 56 | "Caibarien", 57 | "Isabela de Sagua", 58 | "Nuevitas", 59 | "Famagusta", 60 | "Cuxhaven", 61 | "Eckernforde", 62 | "Nordenham", 63 | "Papenburg", 64 | "Wismar", 65 | "Nyborg", 66 | "Helsingor", 67 | "Odense", 68 | "Frederikshavn", 69 | "Haderslev", 70 | "Horsens", 71 | "Kalundborg", 72 | "Kolding", 73 | "Nakskov", 74 | "Nykobing Falster", 75 | "Randers", 76 | "Rudkobing", 77 | "Svendborg", 78 | "Vejle", 79 | "Skagen", 80 | "Puerto Plata", 81 | "Rio Haina", 82 | "Mostaganem", 83 | "Damietta", 84 | "Safaga", 85 | "Algeciras", 86 | "Aviles", 87 | "Ferrol", 88 | "Huelva", 89 | "Pasajes", 90 | "Rota", 91 | "Cadiz", 92 | "Kotka", 93 | "Veitsiluoto", 94 | "Arcachon", 95 | "Deauville", 96 | "Fecamp", 97 | "La Pallice", 98 | "Saint-Malo", 99 | "Sete", 100 | "Greenock", 101 | "Poole", 102 | "Falmouth", 103 | "Leith", 104 | "Montrose", 105 | "Newport", 106 | "Sunderland", 107 | "Teesport", 108 | "Poti", 109 | "Piraeus", 110 | "Saint George's", 111 | "Cayenne", 112 | "Bakar", 113 | "Rovinj", 114 | "Sibenik", 115 | "Gonaives", 116 | "Sorong", 117 | "Alleppey", 118 | "Kakinada", 119 | "Tuticorin", 120 | "Sligo", 121 | "Licata", 122 | "Alghero", 123 | "Bagnoli", 124 | "Barletta", 125 | "Brindisi", 126 | "Crotone", 127 | "Gallipoli", 128 | "Imperia", 129 | "La Maddalena", 130 | "La Spezia", 131 | "Marsala", 132 | "Messina", 133 | "Molfetta", 134 | "Monfalcone", 135 | "Monopoli", 136 | "Pesaro", 137 | "Porto Torres", 138 | "Ravenna", 139 | "Siracusa", 140 | "Taranto", 141 | "Torre Annunziata", 142 | "Omura", 143 | "Amagasaki", 144 | "Fushiki", 145 | "Imabari", 146 | "Iwakuni", 147 | "Kainan", 148 | "Kanazawa", 149 | "Kawasaki", 150 | "Kokura", 151 | "Maizuru", 152 | "Matsusaka", 153 | "Miyazu", 154 | "Muroran", 155 | "Niigata", 156 | "Saganoseki", 157 | "Sasebo", 158 | "Shimizu", 159 | "Shimotsu", 160 | "Wakayama", 161 | "Yokosuka", 162 | "Galle", 163 | "Ventspils", 164 | "Frontera", 165 | "Minatitlan", 166 | "Pajaritos", 167 | "Santa Rosalia", 168 | "Progreso", 169 | "Tuxpan", 170 | "Kemaman", 171 | "Den Helder", 172 | "Dordrecht", 173 | "Delfzijl", 174 | "Arendal", 175 | "Drammen", 176 | "Halden", 177 | "Harstad", 178 | "Larvik", 179 | "Moss", 180 | "Sandefjord", 181 | "Tonsberg", 182 | "Lyttelton", 183 | "Qalhat", 184 | "Sohar", 185 | "Balboa", 186 | "Cristobal", 187 | "Pedregal", 188 | "Callao", 189 | "Matarani", 190 | "Swinoujscie", 191 | "Leixoes", 192 | "Setubal", 193 | "Aveiro", 194 | "Viana do Castelo", 195 | "Galati", 196 | "Nakhodka", 197 | "Kandalaksha", 198 | "Kholmsk", 199 | "Korsakov", 200 | "Kronshtadt", 201 | "Novorossiysk", 202 | "Tuapse", 203 | "Jubail", 204 | "Ras al Khafji", 205 | "Falkenberg", 206 | "Ahus", 207 | "Harnosand", 208 | "Hoganas", 209 | "Karlshamn", 210 | "Karlskrona", 211 | "Nynashamn", 212 | "Oskarshamn", 213 | "Oxelosund", 214 | "Pitea", 215 | "Sodertalje", 216 | "Trelleborg", 217 | "Uddevalla", 218 | "Ystad", 219 | "Bangkok", 220 | "Laem Chabang", 221 | "Sriracha", 222 | "Point Lisas", 223 | "Mersin", 224 | "Berdyansk", 225 | "Kerch", 226 | "Richmond", 227 | "Wilmington", 228 | "Quincy", 229 | "Nawiliwili", 230 | "Monterey", 231 | "Gloucester City", 232 | "Bayonne", 233 | "Baltimore", 234 | "Jacksonville", 235 | "Newport News", 236 | "Port Arthur", 237 | "Portsmouth", 238 | "Alexandria", 239 | "Brownsville", 240 | "Vancouver", 241 | "Guanta", 242 | "Keelung", 243 | "Arrecife", 244 | "St Thomas", 245 | "Basseterre", 246 | "New Orleans", 247 | "Freeport", 248 | "Trenton", 249 | "Goderich", 250 | "Midland", 251 | "St Joseph", 252 | "Racine", 253 | "Menominee", 254 | "Escanaba", 255 | "Sacramento", 256 | "Great Yarmouth", 257 | "Dundee", 258 | "Portmahomack Harbor", 259 | "Wick", 260 | "Oban", 261 | "Douglas", 262 | "Londonderry", 263 | "Silloth", 264 | "Ambriz", 265 | "Cabinda", 266 | "Soyo", 267 | "Durres", 268 | "Curacao", 269 | "Al Fujayrah", 270 | "Caleta Olivia", 271 | "Puerto Deseado", 272 | "Rio Gallegos", 273 | "Rio Grande", 274 | "San Julian", 275 | "Coffs Harbour", 276 | "Esperance", 277 | "Port Augusta", 278 | "Bowen", 279 | "Bundaberg", 280 | "Burketown", 281 | "Carnarvon", 282 | "Cooktown", 283 | "Derby", 284 | "Eden", 285 | "George Town", 286 | "Geraldton", 287 | "Karumba", 288 | "Kingscote", 289 | "Maryborough", 290 | "Strahan", 291 | "Mongla", 292 | "Aracaju", 293 | "Fortaleza", 294 | "Macapa", 295 | "Navegantes", 296 | "Pelotas", 297 | "Campbell River", 298 | "Goose Bay", 299 | "Masset", 300 | "Matane", 301 | "Nanisivik", 302 | "New Richmond", 303 | "Port Hardy", 304 | "Port Hawkesbury", 305 | "Squamish", 306 | "Stephenville", 307 | "Stewart", 308 | "Tahsis", 309 | "Sandspit", 310 | "Tuktoyaktuk", 311 | "Ancud", 312 | "Castro", 313 | "Chanaral", 314 | "Puerto Natales", 315 | "Taltal", 316 | "Guangzhou", 317 | "Jiangyin", 318 | "Lianyungang", 319 | "Sanya", 320 | "Zhanjiang", 321 | "Zhoushan", 322 | "San-Pedro", 323 | "Cartagena", 324 | "Tolu", 325 | "Tumaco", 326 | "Turbo", 327 | "Baracoa", 328 | "Nicaro", 329 | "Nueva Gerona", 330 | "Helgoland", 331 | "Travemunde", 332 | "Thisted", 333 | "Manta", 334 | "Esmeraldas", 335 | "Corralejo", 336 | "Ceuta", 337 | "Melilla", 338 | "San Sebastian de la Gomera", 339 | "Santa Cruz de La Palma", 340 | "Parnu", 341 | "Kuopio", 342 | "Joensuu", 343 | "Varkaus", 344 | "Savusavu", 345 | "Cherbourg", 346 | "Caen", 347 | "Calvi", 348 | "Propriano", 349 | "Quimper", 350 | "Vannes", 351 | "Yap", 352 | "Bristol", 353 | "Hull", 354 | "Newcastle upon Tyne", 355 | "Tyne", 356 | "Rochester", 357 | "Tema", 358 | "Alexandroupolis", 359 | "Kos", 360 | "Paros", 361 | "Samos", 362 | "Nanortalik", 363 | "Qaanaaq", 364 | "New Amsterdam", 365 | "La Ceiba", 366 | "Tela", 367 | "Mali Losinj", 368 | "Jacmel", 369 | "Jeremie", 370 | "Lhokseumawe", 371 | "Nunukan", 372 | "Panjang", 373 | "Tanjung Priok", 374 | "Mangalore", 375 | "Porbandar", 376 | "Ratnagiri", 377 | "Bantry", 378 | "Wexford", 379 | "Khorramshahr", 380 | "Djupivogur", 381 | "Faskrudsfjordur", 382 | "Raufarhofn", 383 | "Pantelleria", 384 | "Catania", 385 | "Fiumicino", 386 | "Lampedusa", 387 | "Manfredonia", 388 | "Ocho Rios", 389 | "Port Antonio", 390 | "Nishinoomote", 391 | "Akita", 392 | "Hachinohe", 393 | "Ishigaki", 394 | "Matsuyama", 395 | "Sakata", 396 | "Takamatsu", 397 | "Wakkanai", 398 | "Tottori", 399 | "Lamu", 400 | "Gunsan", 401 | "Pohang", 402 | "Samcheok", 403 | "Sokcho", 404 | "Ulsan", 405 | "Tripoli", 406 | "Buchanan", 407 | "Agadir", 408 | "Al Hoceima", 409 | "Dakhla", 410 | "Antalaha", 411 | "Manakara", 412 | "Mananjary", 413 | "Morondava", 414 | "Toamasina", 415 | "Loreto", 416 | "Campeche", 417 | "Cozumel", 418 | "La Paz", 419 | "Playa del Carmen", 420 | "Puerto Penasco", 421 | "Puerto Vallarta", 422 | "Coatzacoalcos", 423 | "Bassein", 424 | "Nacala", 425 | "Pemba", 426 | "Quelimane", 427 | "Port Louis", 428 | "Malacca", 429 | "Port Dickson", 430 | "Walvis Bay", 431 | "Calabar", 432 | "Groningen", 433 | "Alta", 434 | "Andenes", 435 | "Bronnoysund", 436 | "Farsund", 437 | "Floro", 438 | "Kristiansand", 439 | "Namsos", 440 | "Rorvik", 441 | "Sandnessjoen", 442 | "Stokmarknes", 443 | "Stord", 444 | "Picton", 445 | "Dunedin", 446 | "Salalah", 447 | "Gwadar", 448 | "Bocas del Toro", 449 | "La Palma", 450 | "Chimbote", 451 | "Ilo", 452 | "Mollendo", 453 | "Pisco", 454 | "Dumaguete", 455 | "General Santos", 456 | "Buka", 457 | "Daru", 458 | "Lae", 459 | "Madang", 460 | "Gdansk", 461 | "Gdynia", 462 | "Aguadilla", 463 | "Arecibo", 464 | "Mayaguez", 465 | "Faro", 466 | "Bora-Bora", 467 | "Tulcea", 468 | "Amderma", 469 | "Anadyr", 470 | "Pevek", 471 | "Kaolack", 472 | "Ziguinchor", 473 | "Longyearbyen", 474 | "Gizo", 475 | "Viru Harbour", 476 | "Kismayu", 477 | "Karlstad", 478 | "Lidkoping", 479 | "Skelleftea", 480 | "Umea", 481 | "Providenciales", 482 | "Songkhla", 483 | "Bandirma", 484 | "Samsun", 485 | "Sinop", 486 | "Trabzon", 487 | "Sevastopol", 488 | "Skagway", 489 | "Valdez", 490 | "Apalachicola", 491 | "Annapolis", 492 | "Cambridge", 493 | "Portland", 494 | "Bremerton", 495 | "Rockland", 496 | "Santa Cruz", 497 | "Chesapeake", 498 | "Cumana", 499 | "Hai Phong", 500 | "Corfu", 501 | "Tangier", 502 | "Hamilton", 503 | "Windsor", 504 | "Rouge River", 505 | "Owen Sound", 506 | "Sault Ste Marie", 507 | "Saginaw", 508 | "Cheboygan", 509 | "Muskegon", 510 | "Gary", 511 | "Waukegan", 512 | "Houghton", 513 | "Wrangell", 514 | "Petersburg", 515 | "Kemi", 516 | "Vaasa", 517 | "Turku", 518 | "Liepaja", 519 | "Europoort", 520 | "Stornoway Harbor", 521 | "Lisboa", 522 | "Bonanza", 523 | "Ibiza", 524 | "Rostov-Na-Donu", 525 | "Tel Aviv Yafo", 526 | "Tunis", 527 | "Bejaia", 528 | "Alger", 529 | "St Louis", 530 | "Tin Can Island", 531 | "Mocambique", 532 | "Assab", 533 | "Massawa", 534 | "Elat", 535 | "Al Kuwayt", 536 | "Bombay", 537 | "Jawaharlal Nehru Port", 538 | "Cochin", 539 | "Madras", 540 | "Calcutta", 541 | "Phuket", 542 | "Pulau Pinang", 543 | "Belawan", 544 | "Semarang", 545 | "Kupang", 546 | "Kuching", 547 | "Miri", 548 | "Kota Kinabalu", 549 | "Pelabuhan Sandakan", 550 | "Ujungpandang", 551 | "Davao", 552 | "Nanjing", 553 | "Hankow", 554 | "Chang Sha", 555 | "Khabarovsk", 556 | "Petropavlovsk", 557 | "Namibe", 558 | "Bahia Blanca", 559 | "La Plata", 560 | "Ushuaia", 561 | "Puerto Madryn", 562 | "Pago Pago", 563 | "Mackay", 564 | "Port Lincoln", 565 | "Port Pirie", 566 | "Albany", 567 | "Barrow Island", 568 | "Bunbury", 569 | "Burnie", 570 | "Geelong", 571 | "Gladstone", 572 | "Newcastle", 573 | "Port Kembla", 574 | "Thursday Island", 575 | "Townsville", 576 | "Weipa", 577 | "Whyalla", 578 | "Wyndham", 579 | "Brussel (Bruxelles)", 580 | "Gent (Ghent)", 581 | "Ilheus", 582 | "Maceio", 583 | "Natal", 584 | "Vitoria", 585 | "Ocean Falls", 586 | "Argentia", 587 | "Bathurst", 588 | "Gaspe", 589 | "Port Alberni", 590 | "Powell River", 591 | "Sept-Iles", 592 | "Sydney", 593 | "Trois-Rivieres (Three Rivers)", 594 | "Yarmouth", 595 | "Rimouski", 596 | "Riviere-du-Loup", 597 | "Saint-Anthony", 598 | "Summerside", 599 | "Arica", 600 | "Tocopilla", 601 | "Beihai", 602 | "Jiangmen", 603 | "Wenzhou", 604 | "Yantai", 605 | "Zhuhai", 606 | "Santa Marta", 607 | "Buenaventura", 608 | "Praia", 609 | "Golfito", 610 | "Cienfuegos", 611 | "Manzanillo", 612 | "Matanzas", 613 | "Santiago de Cuba", 614 | "Emden", 615 | "Flensburg", 616 | "Wilhelmshaven", 617 | "Esbjerg", 618 | "Sonderborg", 619 | "Ronne", 620 | "Barahona", 621 | "Cabo Rojo", 622 | "Oran", 623 | "A Coruna", 624 | "Almeria", 625 | "Gijon", 626 | "San Sebastian", 627 | "Santander", 628 | "Tarragona", 629 | "Oulu", 630 | "Boulogne-sur-Mer", 631 | "Brest", 632 | "Cannes", 633 | "Dieppe", 634 | "Granville", 635 | "La Rochelle", 636 | "Lorient", 637 | "Morlaix", 638 | "Rochefort", 639 | "Toulon", 640 | "Owendo", 641 | "Aberdeen", 642 | "Liverpool", 643 | "Plymouth", 644 | "Batumi", 645 | "Kavala", 646 | "Chios", 647 | "Kalamata", 648 | "Patras", 649 | "Volos", 650 | "Uummannaq", 651 | "Puerto Barrios", 652 | "Pula", 653 | "Rijeka", 654 | "Split", 655 | "Banjarmasin", 656 | "Tanjung Pandan", 657 | "Port Blair", 658 | "Kandla", 659 | "Pondicherry", 660 | "Cork", 661 | "Galway", 662 | "Bushehr", 663 | "Ancona", 664 | "Bari", 665 | "Cagliari", 666 | "Olbia", 667 | "Pescara", 668 | "Reggio Calabria", 669 | "Trapani", 670 | "Aomori", 671 | "Hakodate", 672 | "Kagoshima", 673 | "Kochi", 674 | "Kushiro", 675 | "Niihama", 676 | "Oita", 677 | "Shimonoseki", 678 | "Tomakomai", 679 | "Toyama", 680 | "Ube", 681 | "Malindi", 682 | "Trincomalee", 683 | "Klaipeda", 684 | "Safi", 685 | "Ensenada", 686 | "Guaymas", 687 | "Salina Cruz", 688 | "Saipan", 689 | "Nouadhibou", 690 | "Luderitz", 691 | "Bluefields", 692 | "Alesund", 693 | "Batsfjord", 694 | "Bodo", 695 | "Hammerfest", 696 | "Haugesund", 697 | "Kirkenes", 698 | "Kristiansund", 699 | "Mo i Rana", 700 | "Molde", 701 | "Mosjoen", 702 | "Narvik", 703 | "Stavanger", 704 | "Vadso", 705 | "Vardo", 706 | "New Plymouth", 707 | "Gisborne", 708 | "Greymouth", 709 | "Napier", 710 | "Tauranga", 711 | "Wanganui", 712 | "Westport", 713 | "Whangarei", 714 | "Timaru", 715 | "Talara", 716 | "Masbate", 717 | "Subic Bay", 718 | "Alotau", 719 | "Kavieng", 720 | "Kieta", 721 | "Rabaul", 722 | "Vanimo", 723 | "Ponce", 724 | "Sines", 725 | "Horta", 726 | "Arkhangelsk", 727 | "Murmansk", 728 | "Tiksi", 729 | "Jizan", 730 | "Berbera", 731 | "Nieuw Nickerie", 732 | "Malmo", 733 | "Gavle", 734 | "Halmstad", 735 | "Helsingborg", 736 | "Hudiksvall", 737 | "Kalmar", 738 | "Ornskoldsvik", 739 | "Ronneby", 740 | "Soderhamn", 741 | "Vastervik", 742 | "Visby", 743 | "Tekirdag", 744 | "Tanga", 745 | "Kherson", 746 | "Colonia", 747 | "Cordova", 748 | "Ketchikan", 749 | "Bangor", 750 | "Stockton", 751 | "Bridgeport", 752 | "New Haven", 753 | "Port Everglades", 754 | "Savannah", 755 | "Gulfport", 756 | "Newark", 757 | "Astoria", 758 | "Corpus Christi", 759 | "Pascagoula", 760 | "Tacoma", 761 | "Beaumont", 762 | "Olympia", 763 | "Port Angeles", 764 | "Port Townsend", 765 | "Puerto Cabello", 766 | "Mukalla", 767 | "East London", 768 | "Mossel Bay", 769 | "Richards Bay", 770 | "Erie", 771 | "Bay City", 772 | "Milwaukee", 773 | "Coos Bay", 774 | "Sitka", 775 | "Kodiak", 776 | "Lobito", 777 | "Abu Dhabi", 778 | "Comodoro Rivadavia", 779 | "Mar del Plata", 780 | "Cairns", 781 | "Broome", 782 | "Devonport", 783 | "Hobart", 784 | "Antwerpen", 785 | "Varna", 786 | "Belize City", 787 | "Belem", 788 | "Paranagua", 789 | "Porto Alegre", 790 | "Recife", 791 | "Salvador", 792 | "Santos", 793 | "Charlottetown", 794 | "Halifax", 795 | "Nanaimo", 796 | "Quebec", 797 | "Victoria", 798 | "Prince Rupert", 799 | "Saint-John", 800 | "Antofagasta", 801 | "Iquique", 802 | "Puerto Montt", 803 | "Haikou", 804 | "Ningbo", 805 | "Xiamen", 806 | "Abidjan", 807 | "Douala", 808 | "Pointe Noire", 809 | "Barranquilla", 810 | "Moroni", 811 | "Mariel", 812 | "Larnaca", 813 | "Limassol", 814 | "Bremen", 815 | "Hamburg", 816 | "Djibouti", 817 | "Aalborg", 818 | "Fredericia", 819 | "Alicante", 820 | "Bilbao", 821 | "Valencia", 822 | "Fox Bay", 823 | "Ajaccio", 824 | "Bastia", 825 | "Bordeaux", 826 | "Calais", 827 | "Dunkerque", 828 | "Honfleur", 829 | "Libreville", 830 | "Port Gentil", 831 | "Belfast", 832 | "Dover", 833 | "Inverness", 834 | "Takoradi", 835 | "Basse-Terre", 836 | "Pointe-a-Pitre", 837 | "Banjul", 838 | "Bata", 839 | "Iraklion", 840 | "Thessaloniki", 841 | "Kourou", 842 | "Georgetown", 843 | "Puerto Cortes", 844 | "Zadar", 845 | "Surabaya", 846 | "Manado", 847 | "Senipah", 848 | "Bhavnagar", 849 | "Visakhapatnam", 850 | "Limerick", 851 | "Bandar Abbas", 852 | "Reykjavik", 853 | "Seydisfjordur", 854 | "Trieste", 855 | "Hiroshima", 856 | "Kobe", 857 | "Nagasaki", 858 | "Otaru", 859 | "Tokyo", 860 | "Monrovia", 861 | "Colombo", 862 | "Casablanca", 863 | "Monaco", 864 | "Acapulco", 865 | "Mazatlan", 866 | "Tampico", 867 | "Beira", 868 | "Maputo", 869 | "Nouakchott", 870 | "Fort-de-France", 871 | "Noumea", 872 | "Port Harcourt", 873 | "Corinto", 874 | "Rotterdam", 875 | "Bergen", 876 | "Tromso", 877 | "Trondheim", 878 | "Nelson", 879 | "Muscat", 880 | "Cebu", 881 | "Zamboanga", 882 | "Port Moresby", 883 | "Wewak", 884 | "Porto", 885 | "Ponta Delgada", 886 | "Papeete", 887 | "Doha", 888 | "Kaliningrad", 889 | "Vladivostok", 890 | "Vyborg", 891 | "Port Sudan", 892 | "Mogadishu", 893 | "Paramaribo", 894 | "Goteborg", 895 | "Lulea", 896 | "Norrkoping", 897 | "Lome", 898 | "Izmir", 899 | "Kaohsiung", 900 | "Taichung", 901 | "Odessa", 902 | "Montevideo", 903 | "Juneau", 904 | "Nome", 905 | "Hilo", 906 | "Bellingham", 907 | "Houston", 908 | "Longview", 909 | "Kingstown", 910 | "La Guaira", 911 | "Maracaibo", 912 | "Da Nang", 913 | "Port Vila", 914 | "Apia", 915 | "Hodeidah", 916 | "Ottawa", 917 | "Buffalo", 918 | "Cleveland", 919 | "Green Bay", 920 | "Duluth", 921 | "Eureka", 922 | "Luanda", 923 | "Kralendijk, Bonaire", 924 | "Philipsburg", 925 | "Adelaide", 926 | "Brisbane", 927 | "Port Hedland", 928 | "Chittagong", 929 | "Bridgetown", 930 | "Montreal", 931 | "Punta Arenas", 932 | "Valdivia", 933 | "Dalian", 934 | "Fuzhou", 935 | "Qingdao", 936 | "Shanghai", 937 | "Shantou", 938 | "Puntarenas", 939 | "Kiel", 940 | "Roseau", 941 | "Santo Domingo", 942 | "Vigo", 943 | "Santa Cruz de Tenerife", 944 | "Tallinn", 945 | "Suva", 946 | "Saint-Tropez", 947 | "Glasgow", 948 | "Southampton", 949 | "Gibraltar", 950 | "Conakry", 951 | "Bissau", 952 | "Canea", 953 | "Katakolon", 954 | "San Jose", 955 | "Hong Kong", 956 | "Port-au-Prince", 957 | "Dublin", 958 | "Waterford", 959 | "Haifa", 960 | "Livorno", 961 | "Ischia", 962 | "Kingston", 963 | "Kitakyushu", 964 | "Mombasa", 965 | "Phnom Penh", 966 | "Beirut", 967 | "Male", 968 | "Veracruz", 969 | "Yangon", 970 | "Wellington", 971 | "Szczecin", 972 | "Jeddah", 973 | "Dakar", 974 | "Singapore", 975 | "Freetown", 976 | "Stockholm", 977 | "Canakkale", 978 | "Anchorage", 979 | "Long Beach", 980 | "Oakland", 981 | "Philadelphia", 982 | "Charleston", 983 | "Ho Chi Minh City", 984 | "Aden", 985 | "Durban", 986 | "Port Elizabeth", 987 | "Detroit", 988 | "Port Of Memphis", 989 | "Tri-City Port", 990 | "Helsinki", 991 | "Sankt-Peterburg", 992 | "Oranjestad", 993 | "Dubai", 994 | "Buenos Aires", 995 | "Darwin", 996 | "Melbourne", 997 | "Perth", 998 | "Cotonou", 999 | "Nassau", 1000 | "Rio de Janeiro", 1001 | "Valparaiso", 1002 | "Tianjin", 1003 | "Bremerhaven", 1004 | "Rostock", 1005 | "Tuborg", 1006 | "Guayaquil", 1007 | "Port Said", 1008 | "Barcelona", 1009 | "Malaga", 1010 | "Palma de Mallorca", 1011 | "Le Havre", 1012 | "Marseille", 1013 | "Nice", 1014 | "London", 1015 | "Gythion", 1016 | "Rhodes", 1017 | "Dubrovnik", 1018 | "Civitavecchia", 1019 | "Palermo", 1020 | "Savona", 1021 | "Marghera", 1022 | "Montego Bay", 1023 | "Osaka", 1024 | "Yokohama", 1025 | "Riga", 1026 | "Cabo San Lucas", 1027 | "Marsaxlokk", 1028 | "Amsterdam", 1029 | "Oslo", 1030 | "Auckland", 1031 | "Karachi", 1032 | "Colon", 1033 | "Manila", 1034 | "San Juan", 1035 | "Istanbul", 1036 | "Taipei", 1037 | "Dar es Salaam", 1038 | "Mobile", 1039 | "Los Angeles", 1040 | "San Diego", 1041 | "San Francisco", 1042 | "Miami", 1043 | "Tampa", 1044 | "Honolulu", 1045 | "Boston", 1046 | "New York", 1047 | "Galveston", 1048 | "Norfolk", 1049 | "Seattle", 1050 | "Cape Town", 1051 | "Toronto", 1052 | "Chicago" 1053 | ], 1054 | "count": 1042, 1055 | "attribute": "name", 1056 | "type": "string" 1057 | } 1058 | ], 1059 | "geometry": "Point" 1060 | } 1061 | ] 1062 | } 1063 | -------------------------------------------------------------------------------- /test/fixtures/expected/ports.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "ports", 6 | "count": 1081, 7 | "attributeCount": 5, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | "Port" 12 | ], 13 | "count": 1, 14 | "attribute": "featurecla", 15 | "type": "string" 16 | }, 17 | { 18 | "values": [ 19 | "Sint Nicolaas", 20 | "Campana", 21 | "Zarate", 22 | "Puerto Belgrano/Bahia Blanca", 23 | "Puerto Galvan/Bahia Blanca", 24 | "Ingeniero White/Bahia Blanca", 25 | "Saint John's", 26 | "Fremantle", 27 | "Botany Bay", 28 | "Rockhampton", 29 | "Oostende (Ostend)", 30 | "Zeebrugge", 31 | "Burgas", 32 | "Mina Sulman Port", 33 | "Sitrah", 34 | "Saint George", 35 | "Mucuripe", 36 | "Niteroi", 37 | "Sao Francisco do Sul", 38 | "Chatham", 39 | "Clarenville", 40 | "Comox", 41 | "Esquimalt", 42 | "Grand Bank", 43 | "North Sydney", 44 | "Caraquet", 45 | "Chicoutimi", 46 | "Corner Brook", 47 | "Digby", 48 | "Harbour Grace", 49 | "Pictou", 50 | "Shelburne", 51 | "Sorel", 52 | "Springdale", 53 | "Saint-John's", 54 | "Coronel", 55 | "Lota", 56 | "Talcahuano", 57 | "San Antonio", 58 | "Haimen", 59 | "Qinhuangdao", 60 | "Yantian", 61 | "Yingkou", 62 | "Moin", 63 | "Banes", 64 | "Caibarien", 65 | "Isabela de Sagua", 66 | "Nuevitas", 67 | "Famagusta", 68 | "Cuxhaven", 69 | "Eckernforde", 70 | "Nordenham", 71 | "Papenburg", 72 | "Wismar", 73 | "Nyborg", 74 | "Helsingor", 75 | "Odense", 76 | "Frederikshavn", 77 | "Haderslev", 78 | "Horsens", 79 | "Kalundborg", 80 | "Kolding", 81 | "Nakskov", 82 | "Nykobing Falster", 83 | "Randers", 84 | "Rudkobing", 85 | "Svendborg", 86 | "Vejle", 87 | "Skagen", 88 | "Puerto Plata", 89 | "Rio Haina", 90 | "Mostaganem", 91 | "Damietta", 92 | "Safaga", 93 | "Algeciras", 94 | "Aviles", 95 | "Ferrol", 96 | "Huelva", 97 | "Pasajes", 98 | "Rota", 99 | "Cadiz", 100 | "Kotka", 101 | "Veitsiluoto", 102 | "Arcachon", 103 | "Deauville", 104 | "Fecamp", 105 | "La Pallice", 106 | "Saint-Malo", 107 | "Sete", 108 | "Greenock", 109 | "Poole", 110 | "Falmouth", 111 | "Leith", 112 | "Montrose", 113 | "Newport", 114 | "Sunderland", 115 | "Teesport", 116 | "Poti", 117 | "Piraeus", 118 | "Saint George's" 119 | ], 120 | "count": 1000, 121 | "attribute": "name", 122 | "type": "string" 123 | }, 124 | { 125 | "values": [ 126 | 5, 127 | 10, 128 | 20, 129 | 30, 130 | 50, 131 | 75 132 | ], 133 | "count": 6, 134 | "attribute": "natlscale", 135 | "type": "number", 136 | "min": 5, 137 | "max": 75 138 | }, 139 | { 140 | "values": [ 141 | 8, 142 | 7, 143 | 6, 144 | 5, 145 | 4, 146 | 3 147 | ], 148 | "count": 6, 149 | "attribute": "scalerank", 150 | "type": "number", 151 | "min": 3, 152 | "max": 8 153 | }, 154 | { 155 | "values": [ 156 | "www.rocargo.com/SanNicolas.html", 157 | "www.consejoportuario.com.ar", 158 | "", 159 | "www.ab.gov.ag", 160 | "www.fremantleports.com.au", 161 | "www.sydneyports.com.au", 162 | "www.gpcl.com.au", 163 | "www.portofoostende.be", 164 | "www.zeebruggeport.be", 165 | "www.port-burgas.com", 166 | "www.bahrainports.gov.bh", 167 | "www.docasdoceara.com.br", 168 | "www.portosrio.gov.br", 169 | "www.apsfs.sc.gov.br", 170 | "www.cornerbrookport.com", 171 | "www.portofdigby.ns.ca", 172 | "www.pictoumarineterminals.com", 173 | "www.sjpa.com", 174 | "www.puertotalcahuano.cl", 175 | "www.sanantonioport.com", 176 | "www.yantian-port.com", 177 | "www.japdeva.go.cr", 178 | "www.cuxport.de", 179 | "www.stadtwerke-eckernfoerde.de", 180 | "www.hafen-wismar.de", 181 | "www.adp-as.com", 182 | "www.helsingoerhavn.dk", 183 | "www.nyborg.dk/Engelsk/Tourism/NyborgsHarbours", 184 | "www.frederikshavnhavn.dk", 185 | "www.horsenshavn.dk", 186 | "www.portofkalundborg.dk", 187 | "www.koldingport.dk", 188 | "www.guldborgsundhavne.dk", 189 | "www.randershavn.dk", 190 | "www.langelandkommune.dk", 191 | "www.vejleport.dk", 192 | "www.skagenhavn.dk", 193 | "www.apordom.gov.do", 194 | "www.dam-port.com", 195 | "www.mts.gov.eg", 196 | "www.apba.es", 197 | "www.avilesport.com", 198 | "www.apfsc.com", 199 | "www.puertohuelva.com", 200 | "www.puertopasajes.net", 201 | "www.puertocadiz.com", 202 | "www.portofkotka.fi", 203 | "www.arcachon.com", 204 | "www.fecamp.cci.fr", 205 | "www.larochelle.port.fr", 206 | "www.saint-malo.cci.fr", 207 | "www.sete.port.fr", 208 | "www.clydeport.co.uk", 209 | "www.phc.co.uk", 210 | "www.falmouthport.co.uk", 211 | "www.forthports.co.uk", 212 | "www.montroseport.co.uk", 213 | "www.abports.co.uk/custinfo/ports/newport.htm", 214 | "www.portofsunderland.org.uk", 215 | "www.pdports.co.uk", 216 | "www.potiseaport.com", 217 | "www.olp.gr", 218 | "www.medmarinas.com", 219 | "www.grenadacruiseport.com", 220 | "www.guyane.cci.fr", 221 | "www.portauthority-sibenik.hr", 222 | "www.apn.gouv.ht", 223 | "www.kakinadaseaports.in", 224 | "tuticorinport.gov.in", 225 | "www.porto.br.it", 226 | "www.portolaspezia.it", 227 | "www.porto.monfalcone.gorizia.it", 228 | "www.port.ravenna.it", 229 | "www.port.taranto.it", 230 | "www.city.omura.nagasaki.jp", 231 | "www.pref.ishikawa.jp", 232 | "www.city.kawasaki.jp/58/58yuuti/home/etop.html", 233 | "www.kitaqport.or.jp", 234 | "www.port.maizuru.kyoto.jp", 235 | "www.info.city.tsu.mie.jp", 236 | "www.pref.niigata.lg.jp/kowanshinko/1208883684365.html", 237 | "www.city.sasebo.nagasaki.jp", 238 | "www.portofshimizu.com", 239 | "www.city.yokosuka.kanagawa.jp/minato", 240 | "www.slpa.lk", 241 | "www.portofventspils.lv", 242 | "www.bajaport.com", 243 | "www.puertosyucatan.com", 244 | "www.puerto-de-tuxpan.com.mx", 245 | "www.portsworld.com/Ports/kemamanport.htm", 246 | "www.havendenhelder.nl", 247 | "www.groningen-seaports.com", 248 | "www.drammenhavn.no", 249 | "www.larvikhavn.vf.no", 250 | "www.moss-havn.no", 251 | "www.lpc.co.nz", 252 | "portofsohar.com", 253 | "www.ppc.com.pa", 254 | "www.cocatram.org.ni/puertosca/pto_pedregal_panama.html", 255 | "www.enapu.com.pe" 256 | ], 257 | "count": 578, 258 | "attribute": "website", 259 | "type": "string" 260 | } 261 | ], 262 | "geometry": "Point" 263 | } 264 | ] 265 | } 266 | -------------------------------------------------------------------------------- /test/fixtures/expected/prototype.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "attributeCount": 2, 6 | "attributes": [ 7 | { 8 | "attribute": "constructor", 9 | "count": 2, 10 | "max": 2, 11 | "min": 1, 12 | "type": "number", 13 | "values": [ 14 | 1, 15 | 2 16 | ] 17 | }, 18 | { 19 | "attribute": "prototype", 20 | "count": 2, 21 | "type": "string", 22 | "values": [ 23 | "foo", 24 | null 25 | ] 26 | } 27 | ], 28 | "count": 2, 29 | "geometry": "Point", 30 | "layer": "prototype" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /test/fixtures/expected/two-layers.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 2, 3 | "layers": [ 4 | { 5 | "layer": "point-two", 6 | "count": 17, 7 | "attributeCount": 0, 8 | "attributes": [], 9 | "geometry": "Point" 10 | }, 11 | { 12 | "layer": "point-one", 13 | "count": 17, 14 | "attributeCount": 0, 15 | "attributes": [], 16 | "geometry": "Point" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/expected/two-thousand-properties-only-two.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "two-thousand-properties", 6 | "count": 1, 7 | "attributeCount": 2, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | "prop-value-1031" 12 | ], 13 | "count": 1, 14 | "attribute": "prop-1031", 15 | "type": "string" 16 | }, 17 | { 18 | "values": [ 19 | "prop-value-21" 20 | ], 21 | "count": 1, 22 | "attribute": "prop-21", 23 | "type": "string" 24 | } 25 | ], 26 | "geometry": "Point" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /test/fixtures/expected/two-thousand-properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "two-thousand-properties", 6 | "count": 1, 7 | "attributeCount": 1000, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | "prop-value-0" 12 | ], 13 | "count": 1, 14 | "attribute": "prop-0", 15 | "type": "string" 16 | }, 17 | { 18 | "values": [ 19 | "prop-value-1" 20 | ], 21 | "count": 1, 22 | "attribute": "prop-1", 23 | "type": "string" 24 | }, 25 | { 26 | "values": [ 27 | "prop-value-10" 28 | ], 29 | "count": 1, 30 | "attribute": "prop-10", 31 | "type": "string" 32 | }, 33 | { 34 | "values": [ 35 | "prop-value-100" 36 | ], 37 | "count": 1, 38 | "attribute": "prop-100", 39 | "type": "string" 40 | }, 41 | { 42 | "values": [ 43 | "prop-value-1000" 44 | ], 45 | "count": 1, 46 | "attribute": "prop-1000", 47 | "type": "string" 48 | }, 49 | { 50 | "values": [ 51 | "prop-value-1001" 52 | ], 53 | "count": 1, 54 | "attribute": "prop-1001", 55 | "type": "string" 56 | }, 57 | { 58 | "values": [ 59 | "prop-value-1002" 60 | ], 61 | "count": 1, 62 | "attribute": "prop-1002", 63 | "type": "string" 64 | }, 65 | { 66 | "values": [ 67 | "prop-value-1003" 68 | ], 69 | "count": 1, 70 | "attribute": "prop-1003", 71 | "type": "string" 72 | }, 73 | { 74 | "values": [ 75 | "prop-value-1004" 76 | ], 77 | "count": 1, 78 | "attribute": "prop-1004", 79 | "type": "string" 80 | }, 81 | { 82 | "values": [ 83 | "prop-value-1005" 84 | ], 85 | "count": 1, 86 | "attribute": "prop-1005", 87 | "type": "string" 88 | }, 89 | { 90 | "values": [ 91 | "prop-value-1006" 92 | ], 93 | "count": 1, 94 | "attribute": "prop-1006", 95 | "type": "string" 96 | }, 97 | { 98 | "values": [ 99 | "prop-value-1007" 100 | ], 101 | "count": 1, 102 | "attribute": "prop-1007", 103 | "type": "string" 104 | }, 105 | { 106 | "values": [ 107 | "prop-value-1008" 108 | ], 109 | "count": 1, 110 | "attribute": "prop-1008", 111 | "type": "string" 112 | }, 113 | { 114 | "values": [ 115 | "prop-value-1009" 116 | ], 117 | "count": 1, 118 | "attribute": "prop-1009", 119 | "type": "string" 120 | }, 121 | { 122 | "values": [ 123 | "prop-value-101" 124 | ], 125 | "count": 1, 126 | "attribute": "prop-101", 127 | "type": "string" 128 | }, 129 | { 130 | "values": [ 131 | "prop-value-1010" 132 | ], 133 | "count": 1, 134 | "attribute": "prop-1010", 135 | "type": "string" 136 | }, 137 | { 138 | "values": [ 139 | "prop-value-1011" 140 | ], 141 | "count": 1, 142 | "attribute": "prop-1011", 143 | "type": "string" 144 | }, 145 | { 146 | "values": [ 147 | "prop-value-1012" 148 | ], 149 | "count": 1, 150 | "attribute": "prop-1012", 151 | "type": "string" 152 | }, 153 | { 154 | "values": [ 155 | "prop-value-1013" 156 | ], 157 | "count": 1, 158 | "attribute": "prop-1013", 159 | "type": "string" 160 | }, 161 | { 162 | "values": [ 163 | "prop-value-1014" 164 | ], 165 | "count": 1, 166 | "attribute": "prop-1014", 167 | "type": "string" 168 | }, 169 | { 170 | "values": [ 171 | "prop-value-1015" 172 | ], 173 | "count": 1, 174 | "attribute": "prop-1015", 175 | "type": "string" 176 | }, 177 | { 178 | "values": [ 179 | "prop-value-1016" 180 | ], 181 | "count": 1, 182 | "attribute": "prop-1016", 183 | "type": "string" 184 | }, 185 | { 186 | "values": [ 187 | "prop-value-1017" 188 | ], 189 | "count": 1, 190 | "attribute": "prop-1017", 191 | "type": "string" 192 | }, 193 | { 194 | "values": [ 195 | "prop-value-1018" 196 | ], 197 | "count": 1, 198 | "attribute": "prop-1018", 199 | "type": "string" 200 | }, 201 | { 202 | "values": [ 203 | "prop-value-1019" 204 | ], 205 | "count": 1, 206 | "attribute": "prop-1019", 207 | "type": "string" 208 | }, 209 | { 210 | "values": [ 211 | "prop-value-102" 212 | ], 213 | "count": 1, 214 | "attribute": "prop-102", 215 | "type": "string" 216 | }, 217 | { 218 | "values": [ 219 | "prop-value-1020" 220 | ], 221 | "count": 1, 222 | "attribute": "prop-1020", 223 | "type": "string" 224 | }, 225 | { 226 | "values": [ 227 | "prop-value-1021" 228 | ], 229 | "count": 1, 230 | "attribute": "prop-1021", 231 | "type": "string" 232 | }, 233 | { 234 | "values": [ 235 | "prop-value-1022" 236 | ], 237 | "count": 1, 238 | "attribute": "prop-1022", 239 | "type": "string" 240 | }, 241 | { 242 | "values": [ 243 | "prop-value-1023" 244 | ], 245 | "count": 1, 246 | "attribute": "prop-1023", 247 | "type": "string" 248 | }, 249 | { 250 | "values": [ 251 | "prop-value-1024" 252 | ], 253 | "count": 1, 254 | "attribute": "prop-1024", 255 | "type": "string" 256 | }, 257 | { 258 | "values": [ 259 | "prop-value-1025" 260 | ], 261 | "count": 1, 262 | "attribute": "prop-1025", 263 | "type": "string" 264 | }, 265 | { 266 | "values": [ 267 | "prop-value-1026" 268 | ], 269 | "count": 1, 270 | "attribute": "prop-1026", 271 | "type": "string" 272 | }, 273 | { 274 | "values": [ 275 | "prop-value-1027" 276 | ], 277 | "count": 1, 278 | "attribute": "prop-1027", 279 | "type": "string" 280 | }, 281 | { 282 | "values": [ 283 | "prop-value-1028" 284 | ], 285 | "count": 1, 286 | "attribute": "prop-1028", 287 | "type": "string" 288 | }, 289 | { 290 | "values": [ 291 | "prop-value-1029" 292 | ], 293 | "count": 1, 294 | "attribute": "prop-1029", 295 | "type": "string" 296 | }, 297 | { 298 | "values": [ 299 | "prop-value-103" 300 | ], 301 | "count": 1, 302 | "attribute": "prop-103", 303 | "type": "string" 304 | }, 305 | { 306 | "values": [ 307 | "prop-value-1030" 308 | ], 309 | "count": 1, 310 | "attribute": "prop-1030", 311 | "type": "string" 312 | }, 313 | { 314 | "values": [ 315 | "prop-value-1031" 316 | ], 317 | "count": 1, 318 | "attribute": "prop-1031", 319 | "type": "string" 320 | }, 321 | { 322 | "values": [ 323 | "prop-value-1032" 324 | ], 325 | "count": 1, 326 | "attribute": "prop-1032", 327 | "type": "string" 328 | }, 329 | { 330 | "values": [ 331 | "prop-value-1033" 332 | ], 333 | "count": 1, 334 | "attribute": "prop-1033", 335 | "type": "string" 336 | }, 337 | { 338 | "values": [ 339 | "prop-value-1034" 340 | ], 341 | "count": 1, 342 | "attribute": "prop-1034", 343 | "type": "string" 344 | }, 345 | { 346 | "values": [ 347 | "prop-value-1035" 348 | ], 349 | "count": 1, 350 | "attribute": "prop-1035", 351 | "type": "string" 352 | }, 353 | { 354 | "values": [ 355 | "prop-value-1036" 356 | ], 357 | "count": 1, 358 | "attribute": "prop-1036", 359 | "type": "string" 360 | }, 361 | { 362 | "values": [ 363 | "prop-value-1037" 364 | ], 365 | "count": 1, 366 | "attribute": "prop-1037", 367 | "type": "string" 368 | }, 369 | { 370 | "values": [ 371 | "prop-value-1038" 372 | ], 373 | "count": 1, 374 | "attribute": "prop-1038", 375 | "type": "string" 376 | }, 377 | { 378 | "values": [ 379 | "prop-value-1039" 380 | ], 381 | "count": 1, 382 | "attribute": "prop-1039", 383 | "type": "string" 384 | }, 385 | { 386 | "values": [ 387 | "prop-value-104" 388 | ], 389 | "count": 1, 390 | "attribute": "prop-104", 391 | "type": "string" 392 | }, 393 | { 394 | "values": [ 395 | "prop-value-1040" 396 | ], 397 | "count": 1, 398 | "attribute": "prop-1040", 399 | "type": "string" 400 | }, 401 | { 402 | "values": [ 403 | "prop-value-1041" 404 | ], 405 | "count": 1, 406 | "attribute": "prop-1041", 407 | "type": "string" 408 | }, 409 | { 410 | "values": [ 411 | "prop-value-1042" 412 | ], 413 | "count": 1, 414 | "attribute": "prop-1042", 415 | "type": "string" 416 | }, 417 | { 418 | "values": [ 419 | "prop-value-1043" 420 | ], 421 | "count": 1, 422 | "attribute": "prop-1043", 423 | "type": "string" 424 | }, 425 | { 426 | "values": [ 427 | "prop-value-1044" 428 | ], 429 | "count": 1, 430 | "attribute": "prop-1044", 431 | "type": "string" 432 | }, 433 | { 434 | "values": [ 435 | "prop-value-1045" 436 | ], 437 | "count": 1, 438 | "attribute": "prop-1045", 439 | "type": "string" 440 | }, 441 | { 442 | "values": [ 443 | "prop-value-1046" 444 | ], 445 | "count": 1, 446 | "attribute": "prop-1046", 447 | "type": "string" 448 | }, 449 | { 450 | "values": [ 451 | "prop-value-1047" 452 | ], 453 | "count": 1, 454 | "attribute": "prop-1047", 455 | "type": "string" 456 | }, 457 | { 458 | "values": [ 459 | "prop-value-1048" 460 | ], 461 | "count": 1, 462 | "attribute": "prop-1048", 463 | "type": "string" 464 | }, 465 | { 466 | "values": [ 467 | "prop-value-1049" 468 | ], 469 | "count": 1, 470 | "attribute": "prop-1049", 471 | "type": "string" 472 | }, 473 | { 474 | "values": [ 475 | "prop-value-105" 476 | ], 477 | "count": 1, 478 | "attribute": "prop-105", 479 | "type": "string" 480 | }, 481 | { 482 | "values": [ 483 | "prop-value-1050" 484 | ], 485 | "count": 1, 486 | "attribute": "prop-1050", 487 | "type": "string" 488 | }, 489 | { 490 | "values": [ 491 | "prop-value-1051" 492 | ], 493 | "count": 1, 494 | "attribute": "prop-1051", 495 | "type": "string" 496 | }, 497 | { 498 | "values": [ 499 | "prop-value-1052" 500 | ], 501 | "count": 1, 502 | "attribute": "prop-1052", 503 | "type": "string" 504 | }, 505 | { 506 | "values": [ 507 | "prop-value-1053" 508 | ], 509 | "count": 1, 510 | "attribute": "prop-1053", 511 | "type": "string" 512 | }, 513 | { 514 | "values": [ 515 | "prop-value-1054" 516 | ], 517 | "count": 1, 518 | "attribute": "prop-1054", 519 | "type": "string" 520 | }, 521 | { 522 | "values": [ 523 | "prop-value-1055" 524 | ], 525 | "count": 1, 526 | "attribute": "prop-1055", 527 | "type": "string" 528 | }, 529 | { 530 | "values": [ 531 | "prop-value-1056" 532 | ], 533 | "count": 1, 534 | "attribute": "prop-1056", 535 | "type": "string" 536 | }, 537 | { 538 | "values": [ 539 | "prop-value-1057" 540 | ], 541 | "count": 1, 542 | "attribute": "prop-1057", 543 | "type": "string" 544 | }, 545 | { 546 | "values": [ 547 | "prop-value-1058" 548 | ], 549 | "count": 1, 550 | "attribute": "prop-1058", 551 | "type": "string" 552 | }, 553 | { 554 | "values": [ 555 | "prop-value-1059" 556 | ], 557 | "count": 1, 558 | "attribute": "prop-1059", 559 | "type": "string" 560 | }, 561 | { 562 | "values": [ 563 | "prop-value-106" 564 | ], 565 | "count": 1, 566 | "attribute": "prop-106", 567 | "type": "string" 568 | }, 569 | { 570 | "values": [ 571 | "prop-value-1060" 572 | ], 573 | "count": 1, 574 | "attribute": "prop-1060", 575 | "type": "string" 576 | }, 577 | { 578 | "values": [ 579 | "prop-value-1061" 580 | ], 581 | "count": 1, 582 | "attribute": "prop-1061", 583 | "type": "string" 584 | }, 585 | { 586 | "values": [ 587 | "prop-value-1062" 588 | ], 589 | "count": 1, 590 | "attribute": "prop-1062", 591 | "type": "string" 592 | }, 593 | { 594 | "values": [ 595 | "prop-value-1063" 596 | ], 597 | "count": 1, 598 | "attribute": "prop-1063", 599 | "type": "string" 600 | }, 601 | { 602 | "values": [ 603 | "prop-value-1064" 604 | ], 605 | "count": 1, 606 | "attribute": "prop-1064", 607 | "type": "string" 608 | }, 609 | { 610 | "values": [ 611 | "prop-value-1065" 612 | ], 613 | "count": 1, 614 | "attribute": "prop-1065", 615 | "type": "string" 616 | }, 617 | { 618 | "values": [ 619 | "prop-value-1066" 620 | ], 621 | "count": 1, 622 | "attribute": "prop-1066", 623 | "type": "string" 624 | }, 625 | { 626 | "values": [ 627 | "prop-value-1067" 628 | ], 629 | "count": 1, 630 | "attribute": "prop-1067", 631 | "type": "string" 632 | }, 633 | { 634 | "values": [ 635 | "prop-value-1068" 636 | ], 637 | "count": 1, 638 | "attribute": "prop-1068", 639 | "type": "string" 640 | }, 641 | { 642 | "values": [ 643 | "prop-value-1069" 644 | ], 645 | "count": 1, 646 | "attribute": "prop-1069", 647 | "type": "string" 648 | }, 649 | { 650 | "values": [ 651 | "prop-value-107" 652 | ], 653 | "count": 1, 654 | "attribute": "prop-107", 655 | "type": "string" 656 | }, 657 | { 658 | "values": [ 659 | "prop-value-1070" 660 | ], 661 | "count": 1, 662 | "attribute": "prop-1070", 663 | "type": "string" 664 | }, 665 | { 666 | "values": [ 667 | "prop-value-1071" 668 | ], 669 | "count": 1, 670 | "attribute": "prop-1071", 671 | "type": "string" 672 | }, 673 | { 674 | "values": [ 675 | "prop-value-1072" 676 | ], 677 | "count": 1, 678 | "attribute": "prop-1072", 679 | "type": "string" 680 | }, 681 | { 682 | "values": [ 683 | "prop-value-1073" 684 | ], 685 | "count": 1, 686 | "attribute": "prop-1073", 687 | "type": "string" 688 | }, 689 | { 690 | "values": [ 691 | "prop-value-1074" 692 | ], 693 | "count": 1, 694 | "attribute": "prop-1074", 695 | "type": "string" 696 | }, 697 | { 698 | "values": [ 699 | "prop-value-1075" 700 | ], 701 | "count": 1, 702 | "attribute": "prop-1075", 703 | "type": "string" 704 | }, 705 | { 706 | "values": [ 707 | "prop-value-1076" 708 | ], 709 | "count": 1, 710 | "attribute": "prop-1076", 711 | "type": "string" 712 | }, 713 | { 714 | "values": [ 715 | "prop-value-1077" 716 | ], 717 | "count": 1, 718 | "attribute": "prop-1077", 719 | "type": "string" 720 | }, 721 | { 722 | "values": [ 723 | "prop-value-1078" 724 | ], 725 | "count": 1, 726 | "attribute": "prop-1078", 727 | "type": "string" 728 | }, 729 | { 730 | "values": [ 731 | "prop-value-1079" 732 | ], 733 | "count": 1, 734 | "attribute": "prop-1079", 735 | "type": "string" 736 | }, 737 | { 738 | "values": [ 739 | "prop-value-108" 740 | ], 741 | "count": 1, 742 | "attribute": "prop-108", 743 | "type": "string" 744 | }, 745 | { 746 | "values": [ 747 | "prop-value-1080" 748 | ], 749 | "count": 1, 750 | "attribute": "prop-1080", 751 | "type": "string" 752 | }, 753 | { 754 | "values": [ 755 | "prop-value-1081" 756 | ], 757 | "count": 1, 758 | "attribute": "prop-1081", 759 | "type": "string" 760 | }, 761 | { 762 | "values": [ 763 | "prop-value-1082" 764 | ], 765 | "count": 1, 766 | "attribute": "prop-1082", 767 | "type": "string" 768 | }, 769 | { 770 | "values": [ 771 | "prop-value-1083" 772 | ], 773 | "count": 1, 774 | "attribute": "prop-1083", 775 | "type": "string" 776 | }, 777 | { 778 | "values": [ 779 | "prop-value-1084" 780 | ], 781 | "count": 1, 782 | "attribute": "prop-1084", 783 | "type": "string" 784 | }, 785 | { 786 | "values": [ 787 | "prop-value-1085" 788 | ], 789 | "count": 1, 790 | "attribute": "prop-1085", 791 | "type": "string" 792 | }, 793 | { 794 | "values": [ 795 | "prop-value-1086" 796 | ], 797 | "count": 1, 798 | "attribute": "prop-1086", 799 | "type": "string" 800 | }, 801 | { 802 | "values": [ 803 | "prop-value-1087" 804 | ], 805 | "count": 1, 806 | "attribute": "prop-1087", 807 | "type": "string" 808 | } 809 | ], 810 | "geometry": "Point" 811 | } 812 | ] 813 | } 814 | -------------------------------------------------------------------------------- /test/fixtures/expected/vectorgzip.json: -------------------------------------------------------------------------------- 1 | { 2 | "layerCount": 1, 3 | "layers": [ 4 | { 5 | "layer": "world_merc", 6 | "count": 245, 7 | "attributeCount": 11, 8 | "attributes": [ 9 | { 10 | "values": [ 11 | 44, 12 | 238174, 13 | 8260, 14 | 2740, 15 | 2820, 16 | 124670, 17 | 20, 18 | 273669, 19 | 768230, 20 | 71, 21 | 43, 22 | 5, 23 | 1001, 24 | 13017, 25 | 2281, 26 | 5120, 27 | 108438, 28 | 65755, 29 | 11062, 30 | 2799, 31 | 845942, 32 | 11063, 33 | 527, 34 | 909351, 35 | 17652, 36 | 6463, 37 | 34150, 38 | 226705, 39 | 2568, 40 | 932743, 41 | 65209, 42 | 4700, 43 | 74880, 44 | 26, 45 | 46540, 46 | 125920, 47 | 223, 48 | 103870, 49 | 5106, 50 | 62298, 51 | 10982, 52 | 403, 53 | 24, 54 | 924, 55 | 4243, 56 | 2318, 57 | 75, 58 | 4838, 59 | 27684, 60 | 99545, 61 | 6889, 62 | 2805, 63 | 4239, 64 | 10100, 65 | 2072, 66 | 100000, 67 | 8245, 68 | 7727, 69 | 8815, 70 | 30459, 71 | 1827, 72 | 1217, 73 | 70, 74 | 366, 75 | 55010, 76 | 1000, 77 | 25767, 78 | 6949, 79 | 22754, 80 | 34, 81 | 41045, 82 | 34895, 83 | 55, 84 | 12890, 85 | 10843, 86 | 24572, 87 | 19685, 88 | 2756, 89 | 11189, 90 | 5592, 91 | 9210, 92 | 10025, 93 | 297319, 94 | 163620, 95 | 2171, 96 | 29411, 97 | 31800, 98 | 43737, 99 | 36450, 100 | 1083, 101 | 8824, 102 | 56914, 103 | 19180, 104 | 12041, 105 | 73, 106 | 9873, 107 | 1782, 108 | 269970, 109 | 23080, 110 | 1023 111 | ], 112 | "count": 204, 113 | "attribute": "AREA", 114 | "type": "number", 115 | "min": 0, 116 | "max": 1638094 117 | }, 118 | { 119 | "values": [ 120 | "AC", 121 | "AG", 122 | "AJ", 123 | "AL", 124 | "AM", 125 | "AO", 126 | "AQ", 127 | "AR", 128 | "AS", 129 | "BA", 130 | "BB", 131 | "BD", 132 | "BF", 133 | "BG", 134 | "BH", 135 | "BK", 136 | "BL", 137 | "BM", 138 | "BN", 139 | "BP", 140 | "BR", 141 | "BU", 142 | "BX", 143 | "CA", 144 | "CB", 145 | "CE", 146 | "CF", 147 | "CG", 148 | "BY", 149 | "CH", 150 | "AF", 151 | "BT", 152 | "CI", 153 | "CJ", 154 | "CM", 155 | "CD", 156 | "CN", 157 | "CO", 158 | "CS", 159 | "CT", 160 | "CU", 161 | "CV", 162 | "CW", 163 | "CY", 164 | "DA", 165 | "DJ", 166 | "DO", 167 | "DR", 168 | "EC", 169 | "EG", 170 | "EI", 171 | "EK", 172 | "EN", 173 | "ER", 174 | "ES", 175 | "ET", 176 | "AU", 177 | "EZ", 178 | "FG", 179 | "FI", 180 | "FJ", 181 | "FK", 182 | "FM", 183 | "FP", 184 | "FR", 185 | "GA", 186 | "GB", 187 | "GG", 188 | "GH", 189 | "GJ", 190 | "GL", 191 | "GM", 192 | "GQ", 193 | "GR", 194 | "GT", 195 | "GV", 196 | "GY", 197 | "HA", 198 | "HO", 199 | "HR", 200 | "HU", 201 | "IC", 202 | "IN", 203 | "IR", 204 | "IS", 205 | "IT", 206 | "IV", 207 | "IZ", 208 | "JA", 209 | "JM", 210 | "JO", 211 | "KE", 212 | "KG", 213 | "KN", 214 | "KR", 215 | "KS", 216 | "KU", 217 | "KZ", 218 | "LA", 219 | "LE" 220 | ], 221 | "count": 243, 222 | "attribute": "FIPS", 223 | "type": "string" 224 | }, 225 | { 226 | "values": [ 227 | "AG", 228 | "DZ", 229 | "AZ", 230 | "AL", 231 | "AM", 232 | "AO", 233 | "AS", 234 | "AR", 235 | "AU", 236 | "BH", 237 | "BB", 238 | "BM", 239 | "BS", 240 | "BD", 241 | "BZ", 242 | "BA", 243 | "BO", 244 | "MM", 245 | "BJ", 246 | "SB", 247 | "BR", 248 | "BG", 249 | "BN", 250 | "CA", 251 | "KH", 252 | "LK", 253 | "CG", 254 | "CD", 255 | "BI", 256 | "CN", 257 | "AF", 258 | "BT", 259 | "CL", 260 | "KY", 261 | "CM", 262 | "TD", 263 | "KM", 264 | "CO", 265 | "CR", 266 | "CF", 267 | "CU", 268 | "CV", 269 | "CK", 270 | "CY", 271 | "DK", 272 | "DJ", 273 | "DM", 274 | "DO", 275 | "EC", 276 | "EG", 277 | "IE", 278 | "GQ", 279 | "EE", 280 | "ER", 281 | "SV", 282 | "ET", 283 | "AT", 284 | "CZ", 285 | "GF", 286 | "FI", 287 | "FJ", 288 | "FK", 289 | "FM", 290 | "PF", 291 | "FR", 292 | "GM", 293 | "GA", 294 | "GE", 295 | "GH", 296 | "GD", 297 | "GL", 298 | "DE", 299 | "GU", 300 | "GR", 301 | "GT", 302 | "GN", 303 | "GY", 304 | "HT", 305 | "HN", 306 | "HR", 307 | "HU", 308 | "IS", 309 | "IN", 310 | "IR", 311 | "IL", 312 | "IT", 313 | "CI", 314 | "IQ", 315 | "JP", 316 | "JM", 317 | "JO", 318 | "KE", 319 | "KG", 320 | "KP", 321 | "KI", 322 | "KR", 323 | "KW", 324 | "KZ", 325 | "LA", 326 | "LB" 327 | ], 328 | "count": 245, 329 | "attribute": "ISO2", 330 | "type": "string" 331 | }, 332 | { 333 | "values": [ 334 | "ATG", 335 | "DZA", 336 | "AZE", 337 | "ALB", 338 | "ARM", 339 | "AGO", 340 | "ASM", 341 | "ARG", 342 | "AUS", 343 | "BHR", 344 | "BRB", 345 | "BMU", 346 | "BHS", 347 | "BGD", 348 | "BLZ", 349 | "BIH", 350 | "BOL", 351 | "MMR", 352 | "BEN", 353 | "SLB", 354 | "BRA", 355 | "BGR", 356 | "BRN", 357 | "CAN", 358 | "KHM", 359 | "LKA", 360 | "COG", 361 | "COD", 362 | "BDI", 363 | "CHN", 364 | "AFG", 365 | "BTN", 366 | "CHL", 367 | "CYM", 368 | "CMR", 369 | "TCD", 370 | "COM", 371 | "COL", 372 | "CRI", 373 | "CAF", 374 | "CUB", 375 | "CPV", 376 | "COK", 377 | "CYP", 378 | "DNK", 379 | "DJI", 380 | "DMA", 381 | "DOM", 382 | "ECU", 383 | "EGY", 384 | "IRL", 385 | "GNQ", 386 | "EST", 387 | "ERI", 388 | "SLV", 389 | "ETH", 390 | "AUT", 391 | "CZE", 392 | "GUF", 393 | "FIN", 394 | "FJI", 395 | "FLK", 396 | "FSM", 397 | "PYF", 398 | "FRA", 399 | "GMB", 400 | "GAB", 401 | "GEO", 402 | "GHA", 403 | "GRD", 404 | "GRL", 405 | "DEU", 406 | "GUM", 407 | "GRC", 408 | "GTM", 409 | "GIN", 410 | "GUY", 411 | "HTI", 412 | "HND", 413 | "HRV", 414 | "HUN", 415 | "ISL", 416 | "IND", 417 | "IRN", 418 | "ISR", 419 | "ITA", 420 | "CIV", 421 | "IRQ", 422 | "JPN", 423 | "JAM", 424 | "JOR", 425 | "KEN", 426 | "KGZ", 427 | "PRK", 428 | "KIR", 429 | "KOR", 430 | "KWT", 431 | "KAZ", 432 | "LAO", 433 | "LBN" 434 | ], 435 | "count": 245, 436 | "attribute": "ISO3", 437 | "type": "string" 438 | }, 439 | { 440 | "values": [ 441 | 17.078, 442 | 28.163, 443 | 40.43, 444 | 41.143, 445 | 40.534, 446 | -12.296, 447 | -14.318, 448 | -35.377, 449 | -24.973, 450 | 26.019, 451 | 13.153, 452 | 32.336, 453 | 24.628, 454 | 24.218, 455 | 17.219, 456 | 44.169, 457 | -16.715, 458 | 21.718, 459 | 10.541, 460 | -9.611, 461 | -10.772, 462 | 42.761, 463 | 4.468, 464 | 59.081, 465 | 12.714, 466 | 7.612, 467 | -0.055, 468 | -2.876, 469 | -3.356, 470 | 33.42, 471 | 33.677, 472 | 27.415, 473 | -23.389, 474 | 19.314, 475 | 5.133, 476 | 15.361, 477 | -11.758, 478 | 3.9, 479 | 9.971, 480 | 6.571, 481 | 21.297, 482 | 15.071, 483 | -21.219, 484 | 35.043, 485 | 56.058, 486 | 11.9, 487 | 15.475, 488 | 19.015, 489 | -1.385, 490 | 26.494, 491 | 53.177, 492 | 1.607, 493 | 58.674, 494 | 16.045, 495 | 13.736, 496 | 8.626, 497 | 47.683, 498 | 49.743, 499 | 3.924, 500 | 64.504, 501 | -17.819, 502 | -51.665, 503 | 6.883, 504 | -17.626, 505 | 46.565, 506 | 13.453, 507 | -0.591, 508 | 42.176, 509 | 7.96, 510 | 12.118, 511 | 74.719, 512 | 51.11, 513 | 13.385, 514 | 39.666, 515 | 15.256, 516 | 10.439, 517 | 4.792, 518 | 19.142, 519 | 14.819, 520 | 45.723, 521 | 47.07, 522 | 64.764, 523 | 21, 524 | 32.565, 525 | 31.026, 526 | 42.7, 527 | 7.632, 528 | 33.048, 529 | 36.491, 530 | 18.151, 531 | 30.703, 532 | 0.53, 533 | 41.465, 534 | 39.778, 535 | -1.508, 536 | 36.504, 537 | 29.476, 538 | 48.16, 539 | 19.905, 540 | 33.92 541 | ], 542 | "count": 245, 543 | "attribute": "LAT", 544 | "type": "number", 545 | "min": -54.422, 546 | "max": 78.83 547 | }, 548 | { 549 | "values": [ 550 | -61.783, 551 | 2.632, 552 | 47.395, 553 | 20.068, 554 | 44.563, 555 | 17.544, 556 | -170.73, 557 | -65.167, 558 | 136.189, 559 | 50.562, 560 | -59.559, 561 | -64.709, 562 | -78.014, 563 | 89.941, 564 | -88.602, 565 | 17.786, 566 | -64.671, 567 | 96.041, 568 | 2.469, 569 | 160.109, 570 | -53.089, 571 | 25.231, 572 | 114.591, 573 | -109.433, 574 | 104.564, 575 | 80.704, 576 | 15.986, 577 | 23.654, 578 | 29.887, 579 | 106.514, 580 | 65.216, 581 | 90.429, 582 | -69.433, 583 | -81.198, 584 | 12.277, 585 | 18.665, 586 | 43.337, 587 | -73.076, 588 | -83.946, 589 | 20.483, 590 | -77.781, 591 | -23.634, 592 | -159.782, 593 | 33.219, 594 | 9.264, 595 | 42.516, 596 | -61.356, 597 | -70.729, 598 | -78.497, 599 | 29.872, 600 | -8.152, 601 | 10.488, 602 | 25.793, 603 | 38.219, 604 | -88.866, 605 | 39.616, 606 | 14.912, 607 | 15.338, 608 | -53.241, 609 | 26.272, 610 | 177.974, 611 | -58.694, 612 | 158.235, 613 | -149.462, 614 | 2.55, 615 | -15.386, 616 | 11.797, 617 | 43.518, 618 | -1.207, 619 | -61.678, 620 | -41.391, 621 | 9.851, 622 | 144.707, 623 | 21.766, 624 | -90.398, 625 | -10.942, 626 | -58.974, 627 | -72.278, 628 | -86.863, 629 | 16.693, 630 | 19.134, 631 | -18.48, 632 | 78.5, 633 | 54.301, 634 | 34.851, 635 | 12.8, 636 | -5.556, 637 | 43.772, 638 | 139.068, 639 | -77.32, 640 | 36.319, 641 | 37.858, 642 | 74.555, 643 | 126.451, 644 | 175.036, 645 | 128.103, 646 | 47.376, 647 | 67.301, 648 | 102.471, 649 | 35.888 650 | ], 651 | "count": 244, 652 | "attribute": "LON", 653 | "type": "number", 654 | "min": -178.131, 655 | "max": 179.219 656 | }, 657 | { 658 | "values": [ 659 | "Antigua and Barbuda", 660 | "Algeria", 661 | "Azerbaijan", 662 | "Albania", 663 | "Armenia", 664 | "Angola", 665 | "American Samoa", 666 | "Argentina", 667 | "Australia", 668 | "Bahrain", 669 | "Barbados", 670 | "Bermuda", 671 | "Bahamas", 672 | "Bangladesh", 673 | "Belize", 674 | "Bosnia and Herzegovina", 675 | "Bolivia", 676 | "Burma", 677 | "Benin", 678 | "Solomon Islands", 679 | "Brazil", 680 | "Bulgaria", 681 | "Brunei Darussalam", 682 | "Canada", 683 | "Cambodia", 684 | "Sri Lanka", 685 | "Congo", 686 | "Democratic Republic of the Congo", 687 | "Burundi", 688 | "China", 689 | "Afghanistan", 690 | "Bhutan", 691 | "Chile", 692 | "Cayman Islands", 693 | "Cameroon", 694 | "Chad", 695 | "Comoros", 696 | "Colombia", 697 | "Costa Rica", 698 | "Central African Republic", 699 | "Cuba", 700 | "Cape Verde", 701 | "Cook Islands", 702 | "Cyprus", 703 | "Denmark", 704 | "Djibouti", 705 | "Dominica", 706 | "Dominican Republic", 707 | "Ecuador", 708 | "Egypt", 709 | "Ireland", 710 | "Equatorial Guinea", 711 | "Estonia", 712 | "Eritrea", 713 | "El Salvador", 714 | "Ethiopia", 715 | "Austria", 716 | "Czech Republic", 717 | "French Guiana", 718 | "Finland", 719 | "Fiji", 720 | "Falkland Islands (Malvinas)", 721 | "Micronesia, Federated States of", 722 | "French Polynesia", 723 | "France", 724 | "Gambia", 725 | "Gabon", 726 | "Georgia", 727 | "Ghana", 728 | "Grenada", 729 | "Greenland", 730 | "Germany", 731 | "Guam", 732 | "Greece", 733 | "Guatemala", 734 | "Guinea", 735 | "Guyana", 736 | "Haiti", 737 | "Honduras", 738 | "Croatia", 739 | "Hungary", 740 | "Iceland", 741 | "India", 742 | "Iran (Islamic Republic of)", 743 | "Israel", 744 | "Italy", 745 | "Cote d'Ivoire", 746 | "Iraq", 747 | "Japan", 748 | "Jamaica", 749 | "Jordan", 750 | "Kenya", 751 | "Kyrgyzstan", 752 | "Korea, Democratic People's Republic of", 753 | "Kiribati", 754 | "Korea, Republic of", 755 | "Kuwait", 756 | "Kazakhstan", 757 | "Lao People's Democratic Republic", 758 | "Lebanon" 759 | ], 760 | "count": 245, 761 | "attribute": "NAME", 762 | "type": "string" 763 | }, 764 | { 765 | "values": [ 766 | 83039, 767 | 32854159, 768 | 8352021, 769 | 3153731, 770 | 3017661, 771 | 16095214, 772 | 64051, 773 | 38747148, 774 | 20310208, 775 | 724788, 776 | 291933, 777 | 64174, 778 | 323295, 779 | 15328112, 780 | 275546, 781 | 3915238, 782 | 9182015, 783 | 47967266, 784 | 8490301, 785 | 472419, 786 | 186830759, 787 | 7744591, 788 | 373831, 789 | 32270507, 790 | 13955507, 791 | 19120763, 792 | 3609851, 793 | 58740547, 794 | 7858791, 795 | 1312978855, 796 | 25067407, 797 | 637013, 798 | 16295102, 799 | 45591, 800 | 17795149, 801 | 10145609, 802 | 797902, 803 | 4494579, 804 | 4327228, 805 | 4191429, 806 | 11259905, 807 | 506807, 808 | 13984, 809 | 836321, 810 | 5416945, 811 | 804206, 812 | 67827, 813 | 9469601, 814 | 13060993, 815 | 72849793, 816 | 4143294, 817 | 484098, 818 | 1344312, 819 | 4526722, 820 | 6668356, 821 | 78985857, 822 | 8291979, 823 | 10191762, 824 | 192099, 825 | 5246004, 826 | 828046, 827 | 2975, 828 | 110058, 829 | 255632, 830 | 60990544, 831 | 1617029, 832 | 1290693, 833 | 4473409, 834 | 2253501, 835 | 105237, 836 | 57475, 837 | 82652369, 838 | 16857, 839 | 11099737, 840 | 12709564, 841 | 9002656, 842 | 739472, 843 | 9296291, 844 | 683411, 845 | 455149, 846 | 10086387, 847 | 295732, 848 | 1134403141, 849 | 69420607, 850 | 6692037, 851 | 5864636, 852 | 18584701, 853 | 27995984, 854 | 127896740, 855 | 2682469, 856 | 5544066, 857 | 35598952, 858 | 5203547, 859 | 23615611, 860 | 92003, 861 | 47869837, 862 | 2700, 863 | 15210609, 864 | 566391, 865 | 401074 866 | ], 867 | "count": 229, 868 | "attribute": "POP2005", 869 | "type": "number", 870 | "min": 0, 871 | "max": 1312978855 872 | }, 873 | { 874 | "values": [ 875 | 19, 876 | 2, 877 | 142, 878 | 150, 879 | 9, 880 | 0 881 | ], 882 | "count": 6, 883 | "attribute": "REGION", 884 | "type": "number", 885 | "min": 0, 886 | "max": 150 887 | }, 888 | { 889 | "values": [ 890 | 29, 891 | 15, 892 | 145, 893 | 39, 894 | 17, 895 | 61, 896 | 5, 897 | 53, 898 | 21, 899 | 34, 900 | 13, 901 | 35, 902 | 11, 903 | 54, 904 | 151, 905 | 14, 906 | 30, 907 | 154, 908 | 155, 909 | 57, 910 | 143, 911 | 0, 912 | 18 913 | ], 914 | "count": 23, 915 | "attribute": "SUBREGION", 916 | "type": "number", 917 | "min": 0, 918 | "max": 155 919 | }, 920 | { 921 | "values": [ 922 | 28, 923 | 12, 924 | 31, 925 | 8, 926 | 51, 927 | 24, 928 | 16, 929 | 32, 930 | 36, 931 | 48, 932 | 52, 933 | 60, 934 | 44, 935 | 50, 936 | 84, 937 | 70, 938 | 68, 939 | 104, 940 | 204, 941 | 90, 942 | 76, 943 | 100, 944 | 96, 945 | 124, 946 | 116, 947 | 144, 948 | 178, 949 | 180, 950 | 108, 951 | 156, 952 | 4, 953 | 64, 954 | 152, 955 | 136, 956 | 120, 957 | 148, 958 | 174, 959 | 170, 960 | 188, 961 | 140, 962 | 192, 963 | 132, 964 | 184, 965 | 196, 966 | 208, 967 | 262, 968 | 212, 969 | 214, 970 | 218, 971 | 818, 972 | 372, 973 | 226, 974 | 233, 975 | 232, 976 | 222, 977 | 231, 978 | 40, 979 | 203, 980 | 254, 981 | 246, 982 | 242, 983 | 238, 984 | 583, 985 | 258, 986 | 250, 987 | 270, 988 | 266, 989 | 268, 990 | 288, 991 | 308, 992 | 304, 993 | 276, 994 | 316, 995 | 300, 996 | 320, 997 | 324, 998 | 328, 999 | 332, 1000 | 340, 1001 | 191, 1002 | 348, 1003 | 352, 1004 | 356, 1005 | 364, 1006 | 376, 1007 | 380, 1008 | 384, 1009 | 368, 1010 | 392, 1011 | 388, 1012 | 400, 1013 | 404, 1014 | 417, 1015 | 408, 1016 | 296, 1017 | 410, 1018 | 414, 1019 | 398, 1020 | 418, 1021 | 422 1022 | ], 1023 | "count": 245, 1024 | "attribute": "UN", 1025 | "type": "number", 1026 | "min": 4, 1027 | "max": 894 1028 | } 1029 | ], 1030 | "geometry": "Polygon" 1031 | } 1032 | ] 1033 | } 1034 | -------------------------------------------------------------------------------- /test/fixtures/src/big-features-nozip.mbtiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/big-features-nozip.mbtiles -------------------------------------------------------------------------------- /test/fixtures/src/geometry-extravaganza.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "foo": "bar" 8 | }, 9 | "geometry": { 10 | "type": "Point", 11 | "coordinates": [125.6, 10.1] 12 | } 13 | }, 14 | { 15 | "type": "Feature", 16 | "properties": { 17 | "foo": "bar" 18 | }, 19 | "geometry": { 20 | "type": "MultiPoint", 21 | "coordinates": [[0, 0], [1, 1]] 22 | } 23 | }, 24 | { 25 | "type": "Feature", 26 | "properties": { 27 | "foo": "bar" 28 | }, 29 | "geometry": { 30 | "type": "MultiLineString", 31 | "coordinates": [ 32 | [[0, 0], [1, 1], [2, 2]], 33 | [[10, 10], [11, 11], [12, 12]] 34 | ] 35 | } 36 | }, 37 | { 38 | "type": "Feature", 39 | "properties": { 40 | "bar": 1 41 | }, 42 | "geometry": { 43 | "type": "MultiPolygon", 44 | "coordinates": [ 45 | [ 46 | [ 47 | [101.2, 1.2], [101.8, 1.2], [101.8, 1.8], [101.2, 1.8], [101.2, 1.2] 48 | ], 49 | [ 50 | [101.2, 1.2], [101.3, 1.2], [101.3, 1.3], [101.2, 1.3], [101.2, 1.2] 51 | ], 52 | [ 53 | [101.6, 1.4], [101.7, 1.4], [101.7, 1.5], [101.6, 1.5], [101.6, 1.4] 54 | ], 55 | [ 56 | [101.5, 1.6], [101.6, 1.6], [101.6, 1.7], [101.5, 1.7], [101.5, 1.6] 57 | ] 58 | ], 59 | [ 60 | [ 61 | [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] 62 | ], 63 | [ 64 | [100.35, 0.35], [100.65, 0.35], [100.65, 0.65], [100.35, 0.65], [100.35, 0.35] 65 | ] 66 | ] 67 | ] 68 | } 69 | }, 70 | { 71 | "type": "Feature", 72 | "properties": { 73 | "doodoo": "doo" 74 | }, 75 | "geometry": { 76 | "type": "GeometryCollection", 77 | "geometries": [ 78 | { 79 | "type": "Point", 80 | "coordinates": [100.0, 0.0] 81 | }, 82 | { 83 | "type": "LineString", 84 | "coordinates": [[101.0, 0.0], [102.0, 1.0]] 85 | } 86 | ] 87 | } 88 | }, 89 | { 90 | "type": "Feature", 91 | "properties": { 92 | "baz": true 93 | }, 94 | "geometry": { 95 | "type": "Point", 96 | "coordinates": [100.6, 4.1] 97 | } 98 | } 99 | ] 100 | } 101 | -------------------------------------------------------------------------------- /test/fixtures/src/geometry-invalid-types.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "foo": "bar" 8 | }, 9 | "geometry": { 10 | "type": "doodoodoo", 11 | "coordinates": [] 12 | } 13 | }, 14 | { 15 | "type": "Feature", 16 | "properties": { 17 | "foo": "bar" 18 | }, 19 | "geometry": { 20 | "type": "Point", 21 | "coordinates": [1, 1] 22 | } 23 | }, 24 | { 25 | "type": "Feature", 26 | "properties": { 27 | "baz": true 28 | }, 29 | "geometry": { 30 | "type": "doodoodoo", 31 | "coordinates": [] 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /test/fixtures/src/invalid.csv: -------------------------------------------------------------------------------- 1 | X,foo 2 | 1,bar 3 | -------------------------------------------------------------------------------- /test/fixtures/src/invalid.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | "harumph" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/src/invalid.mbtiles: -------------------------------------------------------------------------------- 1 | doo doo doo 2 | -------------------------------------------------------------------------------- /test/fixtures/src/invalid.shp: -------------------------------------------------------------------------------- 1 | ' 2 | 2�doo doo doo 3 | -------------------------------------------------------------------------------- /test/fixtures/src/invalid.txt: -------------------------------------------------------------------------------- 1 | doo doo doo 2 | -------------------------------------------------------------------------------- /test/fixtures/src/long-attribute-names.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "LoremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaUtenimadminimveniamquisnostrudexercitationullamcolaborisnisiutaliquipexeacommodoconsequatDuisauteiruredolorinreprehenderitinvoluptatevelitessecillumdoloreeufugiatnullapariaturExcepteursintoccaecatcupidatat": 1 8 | }, 9 | "geometry": { 10 | "type": "Point", 11 | "coordinates": [1, 1] 12 | } 13 | }, 14 | { 15 | "type": "Feature", 16 | "properties": { 17 | "LoremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliquaUtenimadminimveniamquisnostrudexercitationullamcolaborisnisiutaliquipexeacommodoconsequatDuisauteiruredolorinreprehenderitinvoluptatevelitessecillumdoloreeufugiatnullapariaturExcepteursintoccaecatcupidatat": 2 18 | }, 19 | "geometry": { 20 | "type": "Point", 21 | "coordinates": [1, 1] 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /test/fixtures/src/many-types.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "name": "foo", 8 | "power": 6, 9 | "mixed": 7, 10 | "numbers": [1, 2, 3], 11 | "astonishing": true 12 | }, 13 | "geometry": { 14 | "type": "Point", 15 | "coordinates": [ 16 | 84.72656249999999, 17 | 55.7765730186677 18 | ] 19 | } 20 | }, 21 | { 22 | "type": "Feature", 23 | "properties": { 24 | "name": "bar", 25 | "mixed": "7", 26 | "power": 99 27 | }, 28 | "geometry": { 29 | "type": "LineString", 30 | "coordinates": [ 31 | [ 32 | 84.462890625, 33 | 55.32914440840507 34 | ], 35 | [ 36 | 86.0009765625, 37 | 55.751849391735284 38 | ], 39 | [ 40 | 86.044921875, 41 | 56.04749958329888 42 | ] 43 | ] 44 | } 45 | }, 46 | { 47 | "type": "Feature", 48 | "properties": { 49 | "name": "baz" 50 | }, 51 | "geometry": { 52 | "type": "Point", 53 | "coordinates": [ 54 | 83.49609375, 55 | 56.48676175249086 56 | ] 57 | } 58 | }, 59 | { 60 | "type": "Feature", 61 | "properties": { 62 | "name": "haha" 63 | }, 64 | "geometry": { 65 | "type": "Point", 66 | "coordinates": [ 67 | 85.341796875, 68 | 56.8249328650072 69 | ] 70 | } 71 | }, 72 | { 73 | "type": "Feature", 74 | "properties": { 75 | "name": "hoho", 76 | "numbers": [1, 2, 3], 77 | "data": 6 78 | }, 79 | "geometry": { 80 | "type": "LineString", 81 | "coordinates": [ 82 | [ 83 | 85.8251953125, 84 | 55.50374985927514 85 | ], 86 | [ 87 | 86.4404296875, 88 | 55.677584411089505 89 | ], 90 | [ 91 | 86.7919921875, 92 | 55.65279803318956 93 | ] 94 | ] 95 | } 96 | }, 97 | { 98 | "type": "Feature", 99 | "properties": { 100 | "name": "hehe", 101 | "power": 86, 102 | "data": { 103 | "foo": "bar" 104 | }, 105 | "onlyNull": null, 106 | "description": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 107 | }, 108 | "geometry": { 109 | "type": "Polygon", 110 | "coordinates": [ 111 | [ 112 | [ 113 | 80.947265625, 114 | 57.397624055000456 115 | ], 116 | [ 117 | 80.5078125, 118 | 56.145549500679074 119 | ], 120 | [ 121 | 81.650390625, 122 | 56.145549500679074 123 | ], 124 | [ 125 | 81.650390625, 126 | 56.559482483762245 127 | ], 128 | [ 129 | 80.947265625, 130 | 57.397624055000456 131 | ] 132 | ] 133 | ] 134 | } 135 | } 136 | ] 137 | } 138 | -------------------------------------------------------------------------------- /test/fixtures/src/many-types.mbtiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/many-types.mbtiles -------------------------------------------------------------------------------- /test/fixtures/src/no-features.csv: -------------------------------------------------------------------------------- 1 | X,Y 2 | -------------------------------------------------------------------------------- /test/fixtures/src/no-features.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [] 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/src/no-features.mbtiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/no-features.mbtiles -------------------------------------------------------------------------------- /test/fixtures/src/no-features/no-features.dbf: -------------------------------------------------------------------------------- 1 | _!W -------------------------------------------------------------------------------- /test/fixtures/src/no-features/no-features.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/fixtures/src/no-features/no-features.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/no-features/no-features.shp -------------------------------------------------------------------------------- /test/fixtures/src/no-features/no-features.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/no-features/no-features.shx -------------------------------------------------------------------------------- /test/fixtures/src/pngs.mbtiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/pngs.mbtiles -------------------------------------------------------------------------------- /test/fixtures/src/populations-plus/populations-plus.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/populations-plus/populations-plus.dbf -------------------------------------------------------------------------------- /test/fixtures/src/populations-plus/populations-plus.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/fixtures/src/populations-plus/populations-plus.qpj: -------------------------------------------------------------------------------- 1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 2 | -------------------------------------------------------------------------------- /test/fixtures/src/populations-plus/populations-plus.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/populations-plus/populations-plus.shp -------------------------------------------------------------------------------- /test/fixtures/src/populations-plus/populations-plus.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/populations-plus/populations-plus.shx -------------------------------------------------------------------------------- /test/fixtures/src/ports/ports.VERSION.txt: -------------------------------------------------------------------------------- 1 | 2.0.0 -------------------------------------------------------------------------------- /test/fixtures/src/ports/ports.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/ports/ports.dbf -------------------------------------------------------------------------------- /test/fixtures/src/ports/ports.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /test/fixtures/src/ports/ports.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/ports/ports.shp -------------------------------------------------------------------------------- /test/fixtures/src/ports/ports.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/ports/ports.shx -------------------------------------------------------------------------------- /test/fixtures/src/prototype.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "constructor": 2, 8 | "prototype": "foo" 9 | }, 10 | "geometry": { 11 | "type": "Point", 12 | "coordinates": [ 13 | 84.72656249999999, 14 | 55.7765730186677 15 | ] 16 | } 17 | }, 18 | { 19 | "type": "Feature", 20 | "properties": { 21 | "constructor": 1 22 | }, 23 | "geometry": { 24 | "type": "LineString", 25 | "coordinates": [ 26 | [ 27 | 84.462890625, 28 | 55.32914440840507 29 | ], 30 | [ 31 | 86.0009765625, 32 | 55.751849391735284 33 | ], 34 | [ 35 | 86.044921875, 36 | 56.04749958329888 37 | ] 38 | ] 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /test/fixtures/src/tilestats.mbtiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/tilestats.mbtiles -------------------------------------------------------------------------------- /test/fixtures/src/two-layers.mbtiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/two-layers.mbtiles -------------------------------------------------------------------------------- /test/fixtures/src/vectorgzip.mbtiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/mapbox-geostats/38840099078b1b4c7043c247db59b464c770919f/test/fixtures/src/vectorgzip.mbtiles -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tap').test; 4 | const _ = require('lodash'); 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | const sloppySort = require('./utils/sloppy-sort'); 8 | const geostats = require('../'); 9 | const validator = require('../lib/validate-stats'); 10 | 11 | function fixturePath(fileName) { 12 | return path.join(__dirname, 'fixtures', fileName); 13 | } 14 | 15 | function getExpected(name) { 16 | return new Promise((resolve, reject) => { 17 | fs.readFile(fixturePath(path.join('expected', name + '.json')), 'utf8', (err, data) => { 18 | if (err) return reject(err); 19 | resolve(JSON.parse(data)); 20 | }); 21 | }); 22 | } 23 | 24 | test('Errors without a file path', t => { 25 | geostats().then(() => { 26 | t.fail('should have errored'); 27 | t.end(); 28 | }).catch(err => { 29 | t.ok(err, 'errored'); 30 | t.end(); 31 | }); 32 | }); 33 | 34 | test('Errors when MBTiles file not found', t => { 35 | geostats(fixturePath('doodoodoo.mbtiles')).then(() => { 36 | t.fail('should have errored'); 37 | t.end(); 38 | }).catch(err => { 39 | t.ok(err, 'errored'); 40 | t.end(); 41 | }); 42 | }); 43 | 44 | test('Errors when Mapnik-interpreted file not found', t => { 45 | geostats(fixturePath('doodoodoo.csv')).then(() => { 46 | t.fail('should have errored'); 47 | t.end(); 48 | }).catch(err => { 49 | t.ok(err, 'errored'); 50 | t.end(); 51 | }); 52 | }); 53 | 54 | test('GeoJSON with many value types, input matching MBTiles', t => { 55 | Promise.all([ 56 | geostats(fixturePath('src/many-types.geojson')), 57 | getExpected('many-types-geojson'), 58 | ]).then((output) => { 59 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 60 | t.end(); 61 | }).catch(t.threw); 62 | }); 63 | 64 | // Key difference between the MBTiles and the GeoJSON output now 65 | // is that when Mapnik reads the GeoJSON it inserts `null` values 66 | // in weird places 67 | test('MBTiles with many value types, input matching GeoJSON', t => { 68 | Promise.all([ 69 | geostats(fixturePath('src/many-types.mbtiles')), 70 | getExpected('many-types-mbtiles'), 71 | ]).then((output) => { 72 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 73 | t.end(); 74 | }).catch(t.threw); 75 | }); 76 | 77 | test('GeoJSON with over 100 unique attributes and values, input matching Shapefile and CSV', 78 | t => { 79 | Promise.all([ 80 | geostats(fixturePath('src/populations-plus.geojson')), 81 | getExpected('populations-plus-geojson'), 82 | ]).then((output) => { 83 | t.same(output[0], output[1], 'expected output'); 84 | t.end(); 85 | }).catch(t.threw); 86 | } 87 | ); 88 | 89 | // Key difference between the Shapefile and the GeoJSON output right now 90 | // seems to be that the shapefile has converted `null` to `""` in 91 | // predominantly string-valued attributes 92 | test('Shapefile with over 100 unique attributes and values, input matching GeoJSON and CSV', 93 | t => { 94 | Promise.all([ 95 | geostats(fixturePath('src/populations-plus/populations-plus.shp')), 96 | getExpected('populations-plus-shp'), 97 | ]).then((output) => { 98 | t.same(output[0], output[1], 'expected output'); 99 | t.end(); 100 | }).catch(t.threw); 101 | } 102 | ); 103 | 104 | // Key difference between the CSV and Shapefile and GeoJSON is that it 105 | // includes X and Y attributes (it also converts `null` to `""`, like Shapefile) 106 | test('CSV with over 100 unique attributes and values, input matching GeoJSON and Shapefile', 107 | t => { 108 | Promise.all([ 109 | geostats(fixturePath('src/populations-plus.csv')), 110 | getExpected('populations-plus-csv'), 111 | ]).then((output) => { 112 | t.same(output[0], output[1], 'expected output'); 113 | t.end(); 114 | }).catch(t.threw); 115 | } 116 | ); 117 | 118 | test('Shapefile with over 1000 unique values', t => { 119 | Promise.all([ 120 | geostats(fixturePath('src/ports/ports.shp')), 121 | getExpected('ports'), 122 | ]).then((output) => { 123 | t.same(output[0], output[1], 'expected output'); 124 | t.end(); 125 | }).catch(t.threw); 126 | }); 127 | 128 | test('MBTiles with gzipped data', t => { 129 | Promise.all([ 130 | geostats(fixturePath('src/vectorgzip.mbtiles')), 131 | getExpected('vectorgzip'), 132 | ]).then((output) => { 133 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 134 | t.end(); 135 | }).catch(t.threw); 136 | }); 137 | 138 | test('MBTiles with raster data', t => { 139 | geostats(fixturePath('src/pngs.mbtiles')).then((output) => { 140 | t.same(output, { layerCount: 0, layers: [] }, 'empty output'); 141 | t.end(); 142 | }).catch(t.threw); 143 | }); 144 | 145 | test('GeoJSON with over 1000 unique attributes', t => { 146 | Promise.all([ 147 | geostats(fixturePath('src/two-thousand-properties.geojson')), 148 | getExpected('two-thousand-properties'), 149 | ]).then((output) => { 150 | const actual = sloppySort(output[0]); 151 | t.same(actual, sloppySort(output[1]), 'expected output'); 152 | t.equal(actual.layers[0].attributeCount, 1000, 'attributeCount stops at 1000'); 153 | t.equal(actual.layers[0].attributes.length, 100, 'attribute details stop at 100'); 154 | t.end(); 155 | }).catch(t.threw); 156 | }); 157 | 158 | test('GeoJSON with many geometry types', t => { 159 | Promise.all([ 160 | geostats(fixturePath('src/geometry-extravaganza.geojson')), 161 | getExpected('geometry-extravaganza'), 162 | ]).then((output) => { 163 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 164 | t.end(); 165 | }).catch(t.threw); 166 | }); 167 | 168 | test('MBTiles with no features', t => { 169 | geostats(fixturePath('src/no-features.mbtiles')).then((output) => { 170 | t.same(output, { layerCount: 0, layers: [] }, 'empty output'); 171 | t.end(); 172 | }).catch(t.threw); 173 | }); 174 | 175 | test('Shapefile with no features', t => { 176 | Promise.all([ 177 | geostats(fixturePath('src/no-features/no-features.shp')), 178 | getExpected('no-features'), 179 | ]).then((output) => { 180 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 181 | t.end(); 182 | }).catch(t.threw); 183 | }); 184 | 185 | test('CSV with no features', t => { 186 | Promise.all([ 187 | geostats(fixturePath('src/no-features.csv')), 188 | getExpected('no-features'), 189 | ]).then((output) => { 190 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 191 | t.end(); 192 | }).catch(t.threw); 193 | }); 194 | 195 | // Currently this is blocked by a bug in node-mapnik 196 | // test('GeoJSON with no features still outputs', t => { 197 | // geostats(fixturePath('src/no-features.geojson')).then((output) => { 198 | // var expected = { 199 | // layerCount: 1, 200 | // layers: [ 201 | // { 202 | // attributeCount: 0, 203 | // attributes: [], 204 | // count: 0, 205 | // layer: 'no-features' 206 | // } 207 | // ] 208 | // }; 209 | // t.same(output, expected, 'expected output'); 210 | // t.end(); 211 | // }).catch(t.threw); 212 | // }); 213 | 214 | test('invalid GeoJSON', t => { 215 | geostats(fixturePath('src/invalid.geojson')).then(() => { 216 | t.fail('should have errored'); 217 | t.end(); 218 | }).catch(err => { 219 | t.ok(err, 'errored'); 220 | t.end(); 221 | }); 222 | }); 223 | 224 | test('GeoJSON skips invalid geometry types', t => { 225 | Promise.all([ 226 | geostats(fixturePath('src/geometry-invalid-types.geojson')), 227 | getExpected('geojson-invalid-geometry-types'), 228 | ]).then((output) => { 229 | t.same(output[0], output[1], 'expected geostats'); 230 | t.end(); 231 | }).catch((err) => { 232 | t.fail(err); 233 | }); 234 | }); 235 | 236 | test('invalid Shapefile', t => { 237 | geostats(fixturePath('src/invalid.shp')).then(() => { 238 | t.fail('should have errored'); 239 | t.end(); 240 | }).catch(err => { 241 | t.ok(err, 'errored'); 242 | t.end(); 243 | }); 244 | }); 245 | 246 | test('invalid CSV', t => { 247 | geostats(fixturePath('src/invalid.csv')).then(() => { 248 | t.fail('should have errored'); 249 | t.end(); 250 | }).catch(err => { 251 | t.ok(err, 'errored'); 252 | t.end(); 253 | }); 254 | }); 255 | 256 | test('invalid MBTiles', t => { 257 | geostats(fixturePath('src/invalid.mbtiles')).then(() => { 258 | t.fail('should have errored'); 259 | t.end(); 260 | }).catch(err => { 261 | t.ok(err, 'errored'); 262 | t.end(); 263 | }); 264 | }); 265 | 266 | test('invalid file format', t => { 267 | geostats(fixturePath('src/invalid.txt')).then(() => { 268 | t.fail('should have errored'); 269 | t.end(); 270 | }).catch(err => { 271 | t.ok(err, 'errored'); 272 | t.end(); 273 | }); 274 | }); 275 | 276 | test('Shapefile with specified attribute with over 1000 values', t => { 277 | Promise.all([ 278 | geostats(fixturePath('src/ports/ports.shp'), { 279 | attributes: ['name'], 280 | }), 281 | getExpected('ports-only-name'), 282 | ]).then((output) => { 283 | const actual = sloppySort(output[0]); 284 | t.same(actual, sloppySort(output[1]), 'expected output'); 285 | const nameAttribute = _.find(actual.layers[0].attributes, attribute => { 286 | return attribute.attribute === 'name'; 287 | }); 288 | t.equal(nameAttribute.count, 1042, 'value count did not stop at 1000'); 289 | t.equal(nameAttribute.values.length, 1042, 'value details did not stop at 100'); 290 | t.end(); 291 | }).catch(t.threw); 292 | }); 293 | 294 | test('GeoJSON with specified attributes', t => { 295 | Promise.all([ 296 | geostats(fixturePath('src/two-thousand-properties.geojson'), { 297 | attributes: ['prop-21', 'prop-1031'], 298 | }), 299 | getExpected('two-thousand-properties-only-two'), 300 | ]).then((output) => { 301 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 302 | t.end(); 303 | }).catch(t.threw); 304 | }); 305 | 306 | test('GeoJSON with over 10000 unique values and no specified attributes', t => { 307 | Promise.all([ 308 | geostats(fixturePath('src/myriad-values.geojson')), 309 | getExpected('myriad-values-all-attrs'), 310 | ]).then((output) => { 311 | const actual = sloppySort(output[1]); 312 | t.same(sloppySort(output[0]), actual, 'expected output'); 313 | t.ok(actual.layers[0].attributes.every(attribute => { 314 | return attribute.count === 1000; 315 | }), 'value counts stop at 1000'); 316 | t.ok(actual.layers[0].attributes.every(attribute => { 317 | return attribute.values.length === 100; 318 | }), 'value details stop at 100'); 319 | t.end(); 320 | }).catch(t.threw); 321 | }); 322 | 323 | test('GeoJSON with over 10000 unique values and one specified attribute', t => { 324 | Promise.all([ 325 | geostats(fixturePath('src/myriad-values.geojson'), { 326 | attributes: ['prop-3'], 327 | }), 328 | getExpected('myriad-values-1-attr'), 329 | ]).then((output) => { 330 | const actual = sloppySort(output[1]); 331 | t.same(sloppySort(output[0]), actual, 'expected output'); 332 | t.ok(actual.layers[0].attributes.every(attribute => { 333 | return attribute.count === 10010; 334 | }), 'value count does not stop yet'); 335 | t.ok(actual.layers[0].attributes.every(attribute => { 336 | return attribute.values.length === 10000; 337 | }), 'value details stop at 10000'); 338 | t.end(); 339 | }).catch(t.threw); 340 | }); 341 | 342 | test('GeoJSON with over 10000 unique values and five specified attribute', t => { 343 | Promise.all([ 344 | geostats(fixturePath('src/myriad-values.geojson'), { 345 | attributes: ['prop-1', 'prop-2', 'prop-3', 'prop-4', 'prop-5'], 346 | }), 347 | getExpected('myriad-values-5-attrs'), 348 | ]).then((output) => { 349 | const actual = sloppySort(output[1]); 350 | t.same(sloppySort(output[0]), actual, 'expected output'); 351 | t.ok(actual.layers[0].attributes.every(attribute => { 352 | return attribute.count === 10010; 353 | }), 'value counts does not stop yet'); 354 | t.ok(actual.layers[0].attributes.every(attribute => { 355 | return attribute.values.length === 2000; 356 | }), 'value details stop at 2000'); 357 | t.end(); 358 | }).catch(t.threw); 359 | }); 360 | 361 | test('Trying to report on more than 100 attributes', t => { 362 | t.throws(() => { 363 | geostats(fixturePath('src/populations-plus.geojson'), { 364 | attributes: ['attr-0', 'attr-1', 'attr-2', 'attr-3', 'attr-4', 'attr-5', 365 | 'attr-6', 'attr-7', 'attr-8', 'attr-9', 'attr-10', 'attr-11', 'attr-12', 366 | 'attr-13', 'attr-14', 'attr-15', 'attr-16', 'attr-17', 'attr-18', 'attr-19', 367 | 'attr-20', 'attr-21', 'attr-22', 'attr-23', 'attr-24', 'attr-25', 'attr-26', 368 | 'attr-27', 'attr-28', 'attr-29', 'attr-30', 'attr-31', 'attr-32', 'attr-33', 369 | 'attr-34', 'attr-35', 'attr-36', 'attr-37', 'attr-38', 'attr-39', 'attr-40', 370 | 'attr-41', 'attr-42', 'attr-43', 'attr-44', 'attr-45', 'attr-46', 'attr-47', 371 | 'attr-48', 'attr-49', 'attr-50', 'attr-51', 'attr-52', 'attr-53', 'attr-54', 372 | 'attr-55', 'attr-56', 'attr-57', 'attr-58', 'attr-59', 'attr-60', 'attr-61', 373 | 'attr-62', 'attr-63', 'attr-64', 'attr-65', 'attr-66', 'attr-67', 'attr-68', 374 | 'attr-69', 'attr-70', 'attr-71', 'attr-72', 'attr-73', 'attr-74', 'attr-75', 375 | 'attr-76', 'attr-77', 'attr-78', 'attr-79', 'attr-80', 'attr-81', 'attr-82', 376 | 'attr-83', 'attr-84', 'attr-85', 'attr-86', 'attr-87', 'attr-88', 'attr-89', 377 | 'attr-90', 'attr-91', 'attr-92', 'attr-93', 'attr-94', 'attr-95', 'attr-96', 378 | 'attr-97', 'attr-98', 'attr-99', 'attr-100', 'attr-101', 'attr-102', 379 | 'attr-103', 'attr-104', 'attr-105', 'attr-106', 'attr-107', 'attr-108', 380 | 'attr-109'], 381 | }); 382 | }, 'throws'); 383 | t.end(); 384 | }); 385 | 386 | test('GeoJSON with prototype attribute', t => { 387 | Promise.all([ 388 | geostats(fixturePath('src/prototype.geojson')), 389 | getExpected('prototype'), 390 | ]).then((output) => { 391 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 392 | t.end(); 393 | }).catch(t.threw); 394 | }); 395 | 396 | test('truncate attribute names', t => { 397 | Promise.all([ 398 | geostats(fixturePath('src/long-attribute-names.geojson')), 399 | getExpected('long-attribute-names'), 400 | ]).then((output) => { 401 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 402 | t.end(); 403 | }).catch(t.threw); 404 | }); 405 | 406 | test('MBTiles with two layers', t => { 407 | Promise.all([ 408 | geostats(fixturePath('src/two-layers.mbtiles')), 409 | getExpected('two-layers'), 410 | ]).then((output) => { 411 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 412 | t.end(); 413 | }).catch(t.threw); 414 | }); 415 | 416 | test('MBTiles with tilestats metadata table returns as expected', t => { 417 | Promise.all([ 418 | geostats(fixturePath('src/tilestats.mbtiles')), 419 | getExpected('tilestats'), 420 | ]).then((output) => { 421 | t.same(sloppySort(output[0]), sloppySort(output[1]), 'expected output'); 422 | t.end(); 423 | }).catch(t.threw); 424 | }); 425 | 426 | test('[validator] valid stats object', t => { 427 | const stats = { 428 | layerCount: 1, 429 | layers: [ 430 | { 431 | layer: 'test-layer', 432 | count: 3, 433 | geometry: 'Point', 434 | attributeCount: 1, 435 | attributes: [ 436 | { 437 | attribute: 'test-attribute', 438 | count: 3, 439 | type: 'number', 440 | values: [2, 5, 19], 441 | min: 2, 442 | max: 19, 443 | }, 444 | ], 445 | }, 446 | ], 447 | }; 448 | 449 | t.ok(validator(stats)); 450 | t.end(); 451 | }); 452 | 453 | test('[validator] invalid stats object - no layers', t => { 454 | const stats = { 455 | layerCount: 1, 456 | }; 457 | 458 | const results = validator(stats); 459 | t.equal(results.length, 1, 'only one error'); 460 | t.equal(results[0], 'instance requires property "layers"', 'expected error message'); 461 | t.end(); 462 | }); 463 | 464 | test('[validator] invalid layer object - no layer name', t => { 465 | const stats = { 466 | layerCount: 1, 467 | layers: [ 468 | { 469 | count: 3, 470 | geometry: 'Point', 471 | attributeCount: 1, 472 | attributes: [ 473 | { 474 | attribute: 'test-attribute', 475 | count: 3, 476 | type: 'number', 477 | values: [2, 5, 19], 478 | min: 2, 479 | max: 19, 480 | }, 481 | ], 482 | }, 483 | ], 484 | }; 485 | 486 | const results = validator(stats); 487 | t.equal(results.length, 1, 'only one error'); 488 | t.equal(results[0], 'instance.layers[0] requires property "layer"', 'expected error message'); 489 | t.end(); 490 | }); 491 | 492 | test('[validator] invalid layer object - no layer name', t => { 493 | const stats = { 494 | layerCount: 'wrong type', 495 | layers: [ 496 | { 497 | geometry: 'Point', 498 | attributes: [ 499 | { 500 | attribute: 'test-attribute', 501 | type: 'number', 502 | min: 2, 503 | max: 19, 504 | }, 505 | ], 506 | }, 507 | ], 508 | }; 509 | 510 | const expected = sloppySort([ 511 | 'instance.layerCount is not of a type(s) number', 512 | 'instance.layers[0] requires property "count"', 513 | 'instance.layers[0].attributes[0] requires property "values"', 514 | 'instance.layers[0] requires property "layer"', 515 | 'instance.layers[0].attributes[0] requires property "count"', 516 | 'instance.layers[0] requires property "attributeCount"', 517 | ]); 518 | 519 | const results = validator(stats); 520 | t.same(sloppySort(results), expected, 'expect lots of errors'); 521 | t.end(); 522 | }); 523 | 524 | test('[validator] invalid attributes contain mulitple types', t => { 525 | const stats = { 526 | layerCount: 1, 527 | layers: [ 528 | { 529 | layer: 'test-layer', 530 | count: 3, 531 | geometry: 'Point', 532 | attributeCount: 1, 533 | attributes: [ 534 | { 535 | attribute: 'test-attribute', 536 | count: 3, 537 | type: 'number', 538 | values: [2, 'five', null], 539 | min: 2, 540 | max: 19, 541 | }, 542 | ], 543 | }, 544 | ], 545 | }; 546 | 547 | const results = validator(stats); 548 | t.equal(results.length, 1, 'only one error'); 549 | t.equal(results[0], 'instance.layers[0].attributes[0].values is not any of [subschema 0],[subschema 1],[subschema 2],[subschema 3]', 'expected error message'); 550 | t.end(); 551 | }); 552 | -------------------------------------------------------------------------------- /test/utils/sloppy-sort.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | // The purpose of the sloppy sort is only to ensure some 6 | // deterministic order in this module's JSON output. 7 | // Because of async operations in some places (esp. tile-analyze), 8 | // items in arrays (e.g. attributes, values) might not always 9 | // appear in the same the same order, but should always 10 | // have the same items. 11 | 12 | function sloppySortComparator(first, second) { 13 | const isFirstObject = _.isPlainObject(first); 14 | const isSecondObject = _.isPlainObject(second); 15 | if (!isFirstObject && isSecondObject) return -1; 16 | if (isFirstObject && !isSecondObject) return 1; 17 | if (isFirstObject && isSecondObject) { 18 | if (first.attribute < second.attribute) return -1; 19 | if (first.attribute > second.attribute) return 1; 20 | return 0; 21 | } 22 | 23 | const isFirstNumber = _.isNumber(first); 24 | const isSecondNumber = _.isNumber(second); 25 | if (!isFirstNumber && isSecondNumber) return -1; 26 | if (isFirstNumber && !isSecondNumber) return 1; 27 | if (isFirstNumber && isSecondNumber) { 28 | if (first < second) return -1; 29 | if (first > second) return 1; 30 | return 0; 31 | } 32 | 33 | const isFirstNull = _.isNull(first); 34 | const isSecondNull = _.isNull(second); 35 | if (!isFirstNull && isSecondNull) return -1; 36 | if (isFirstNull && !isSecondNull) return 1; 37 | if (isFirstNull && isSecondNull) return 0; 38 | 39 | if (String(first) < String(second)) return -1; 40 | if (String(first) > String(second)) return 1; 41 | return 0; 42 | } 43 | 44 | function sloppySortArray(arr) { 45 | return arr.map(sloppySort).sort(sloppySortComparator); 46 | } 47 | 48 | function sloppySortObject(obj) { 49 | return _.mapValues(obj, sloppySort); 50 | } 51 | 52 | function sloppySort(item) { 53 | if (_.isPlainObject(item)) return sloppySortObject(item); 54 | if (_.isArray(item)) return sloppySortArray(item); 55 | return item; 56 | } 57 | 58 | module.exports = sloppySort; 59 | --------------------------------------------------------------------------------