-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathindex.js
200 lines (174 loc) · 6.69 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
var zlib = require('zlib');
var isgeocsv = require('@mapbox/detect-geocsv');
var invalid = require('./lib/invalid');
var fs = require('fs');
var bf = require('buffer');
module.exports.fromBuffer = fromBuffer;
module.exports.fromFile = fromFile;
/**
* Main sniffer.
*/
function fromBuffer(buffer, callback) {
if (!callback || typeof callback !== 'function') throw new Error('Invalid callback. Must be a function.');
if (!Buffer.isBuffer(buffer)) return callback(invalid('Input is not a valid buffer.'));
detect(buffer, function(err, type) {
if (err) return callback(err);
var protocol = getProtocol(type);
return callback(null, {type: type, protocol: protocol});
});
};
function fromFile(file, callback) {
if (!callback || typeof callback !== 'function') throw new Error('Invalid callback. Must be a function.');
fs.open(file, 'r', function(err, fd) {
if (err) return callback(err);
fs.fstat(fd, function(err, stats) {
if (err) return callback(err);
if (stats.size === 0) return callback(invalid('File is zero bytes.'));
var size = stats.size < 512 ? stats.size : 512;
fs.read(fd, new Buffer.alloc(size), 0, size, 0, function(err, bytes, buffer) {
if (bytes <= 2)
err = err || invalid('File too small');
fs.close(fd, function(closeErr) {
if (err || closeErr) return callback(err || closeErr);
detect(buffer, function(err, type) {
if (err) return callback(err);
var protocol = getProtocol(type);
return callback(null, {type: type, protocol: protocol});
});
});
});
});
});
}
/**
* promisify zlib.gunzip.
*
* @param {buffer} buffer -
* @returns {promise}
*/
function gunzipPromise (buffer) {
return new Promise((resolve, reject) => {
zlib.gunzip(buffer, {finishFlush: zlib.Z_SYNC_FLUSH }, (err, data) => {
if (err) return reject(err);
return resolve(data);
});
});
};
/**
* Check if a buffer is compressed.
* From: http://www.zlib.org/rfc-gzip.html
* The three first value of the buffer should be:
* - ID1 = 31 (0x1f)
* - ID2 = 139 (0x8b)
* - CM = 8 (0x08)
*
* @param {buffer} buffer -
* @returns {bool}
*/
function isGzip (buffer) {
return buffer[0] === 0x1F
&& buffer[1] === 0x8B;
}
function detect(buffer, callback) {
if (isGzip(buffer)) {
gunzipPromise(buffer)
.then(data => {
//check for tm2z
if (data.toString('ascii', 257, 262) === 'ustar') return callback(null, 'tm2z');
//check for serial tiles
const head = data.slice(0, 50);
if (head.toString().indexOf('JSONBREAKFASTTIME') === 0) return callback(null, 'serialtiles');
//check for tif+gz
if (
(data.slice(0, 2).toString() === 'II' || data.slice(0, 2).toString() === 'MM')
&& ((data[2] === 42) || data[3] === 42 || data[2] === 43)) {
return callback(null, 'tif+gz');
}
return callback(invalid('Unknown filetype'));
})
.catch(err => {
return callback(invalid('Unknown filetype'));
})
} else {
var header = buffer.toString().substring(0, 400);
// check for topojson/geojson
if (header.trim().indexOf('{') == 0) {
// Remove spaces
var str = JSON.stringify(header);
var nospaces = str.replace(/\s/g, '');
header = JSON.parse(nospaces);
if (header.indexOf('\"tilejson\":') !== -1) return callback(null, 'tilejson');
if ((header.indexOf('\"arcs\":') !== -1) || (header.indexOf('\"objects\":') !== -1)) return callback(null, 'topojson');
if ((header.indexOf('\"features\":') !== -1) || (header.indexOf('\"geometries\":') !== -1) || (header.indexOf('\"coordinates\":') !== -1)) return callback(null, 'geojson');
if (header.indexOf('\"type\":') !== -1) {
var m = /"type":\s?"(.+?)"/.exec(header);
if (!m) {
return callback(invalid('Unknown filetype'));
}
if (m[1] === 'Topology') return callback(null, 'topojson');
if (m[1] === 'Feature' ||
m[1] === 'FeatureCollection' ||
m[1] === 'Point' ||
m[1] === 'MultiPoint' ||
m[1] === 'LineString' ||
m[1] === 'MultiLineString' ||
m[1] === 'Polygon' ||
m[1] === 'MultiPolygon' ||
m[1] === 'GeometryCollection') return callback(null, 'geojson');
}
return callback(invalid('Unknown filetype'));
}
var head = header.substring(0, 100);
if (head.indexOf('SQLite format 3') === 0) {
return callback(null, 'mbtiles');
}
if ((head[0] + head[1]) === 'PK') {
return callback(null, 'zip');
}
// check if geotiff/bigtiff
// matches gdal validation logic: https://github.com/OSGeo/gdal/blob/trunk/gdal/frmts/gtiff/geotiff.cpp#L6892-L6893
if ((head.slice(0, 2).toString() === 'II' || head.slice(0, 2).toString() === 'MM') && ((buffer[2] === 42) || buffer[3] === 42 || buffer[2] === 43)) {
return callback(null, 'tif');
}
// take into account BOM char at index 0
if (((head.indexOf('<?xml') === 1) || (head.indexOf('<?xml') === 0)) && (head.indexOf('<kml') !== -1)) {
return callback(null, 'kml');
}
// take into account BOM char at index 0
if (((head.indexOf('<?xml') === 1) || (head.indexOf('<?xml') === 0)) && (head.indexOf('<gpx') !== -1)) {
return callback(null, 'gpx');
}
if (head.indexOf('<VRTDataset') !== -1) {
return callback(null, 'vrt');
}
// check for unzipped .shp
if (buffer.length > 32 && buffer.readUInt32BE(0) === 9994) {
return callback(null, 'shp');
}
// Check for geocsv
if (isgeocsv(buffer)) {
return callback(null, 'csv');
}
// If not one of the test is valid then return "Unknown"
return callback(invalid('Unknown filetype'));
}
}
function getProtocol(type) {
var mapping = {
csv: 'omnivore:',
mbtiles: 'mbtiles:',
shp: 'omnivore:',
zip: 'omnivore:',
tif: 'omnivore:',
'tif+gz': 'omnivore:',
vrt: 'omnivore:',
geojson: 'omnivore:',
topojson: 'omnivore:',
kml: 'omnivore:',
gpx: 'omnivore:',
tilejson: 'tilejson:',
tm2z: 'tm2z:',
serialtiles: 'serialtiles:'
};
return mapping[type];
}