shapefile
Version:
An implementation of the shapefile (.shp) spatial data format.
372 lines (323 loc) • 11.6 kB
JavaScript
var TextDecoder = require("text-encoding").TextDecoder;
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var path = _interopDefault(require('path-source'));
var array = _interopDefault(require('array-source'));
var stream = _interopDefault(require('stream-source'));
var slice = _interopDefault(require('slice-source'));
var dbf_cancel = function() {
return this._source.cancel();
};
var readBoolean = function(value) {
return /^[nf]$/i.test(value) ? false
: /^[yt]$/i.test(value) ? true
: null;
};
var readDate = function(value) {
return new Date(+value.substring(0, 4), value.substring(4, 6) - 1, +value.substring(6, 8));
};
var readNumber = function(value) {
return !(value = value.trim()) || isNaN(value = +value) ? null : value;
};
var readString = function(value) {
return value.trim() || null;
};
var types = {
B: readNumber,
C: readString,
D: readDate,
F: readNumber,
L: readBoolean,
M: readNumber,
N: readNumber
};
var dbf_read = function() {
var that = this, i = 1;
return that._source.slice(that._recordLength).then(function(value) {
return value && (value[0] !== 0x1a) ? {done: false, value: that._fields.reduce(function(p, f) {
p[f.name] = types[f.type](that._decode(value.subarray(i, i += f.length)));
return p;
}, {})} : {done: true, value: undefined};
});
};
var view = function(array$$1) {
return new DataView(array$$1.buffer, array$$1.byteOffset, array$$1.byteLength);
};
var dbf = function(source, decoder) {
source = slice(source);
return source.slice(32).then(function(array$$1) {
var head = view(array$$1);
return source.slice(head.getUint16(8, true) - 32).then(function(array$$1) {
return new Dbf(source, decoder, head, view(array$$1));
});
});
};
function Dbf(source, decoder, head, body) {
this._source = source;
this._decode = decoder.decode.bind(decoder);
this._recordLength = head.getUint16(10, true);
this._fields = [];
for (var n = 0; body.getUint8(n) !== 0x0d; n += 32) {
for (var j = 0; j < 11; ++j) if (body.getUint8(n + j) === 0) break;
this._fields.push({
name: this._decode(new Uint8Array(body.buffer, body.byteOffset + n, j)),
type: String.fromCharCode(body.getUint8(n + 11)),
length: body.getUint8(n + 16)
});
}
}
var prototype = Dbf.prototype;
prototype.read = dbf_read;
prototype.cancel = dbf_cancel;
function cancel() {
return this._source.cancel();
}
var parseMultiPoint = function(record) {
var i = 40, j, n = record.getInt32(36, true), coordinates = new Array(n);
for (j = 0; j < n; ++j, i += 16) coordinates[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
return {type: "MultiPoint", coordinates: coordinates};
};
var parseNull = function() {
return null;
};
var parsePoint = function(record) {
return {type: "Point", coordinates: [record.getFloat64(4, true), record.getFloat64(12, true)]};
};
var parsePolygon = function(record) {
var i = 44, j, n = record.getInt32(36, true), m = record.getInt32(40, true), parts = new Array(n), points = new Array(m), polygons = [], holes = [];
for (j = 0; j < n; ++j, i += 4) parts[j] = record.getInt32(i, true);
for (j = 0; j < m; ++j, i += 16) points[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
parts.forEach(function(i, j) {
var ring = points.slice(i, parts[j + 1]);
if (ringClockwise(ring)) polygons.push([ring]);
else holes.push(ring);
});
holes.forEach(function(hole) {
polygons.some(function(polygon) {
if (ringContainsSome(polygon[0], hole)) {
polygon.push(hole);
return true;
}
}) || polygons.push([hole]);
});
return polygons.length === 1
? {type: "Polygon", coordinates: polygons[0]}
: {type: "MultiPolygon", coordinates: polygons};
};
function ringClockwise(ring) {
if ((n = ring.length) < 4) return false;
var i = 0, n, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];
while (++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];
return area >= 0;
}
function ringContainsSome(ring, hole) {
var i = -1, n = hole.length, c;
while (++i < n) {
if (c = ringContains(ring, hole[i])) {
return c > 0;
}
}
return false;
}
function ringContains(ring, point) {
var x = point[0], y = point[1], contains = -1;
for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {
var pi = ring[i], xi = pi[0], yi = pi[1],
pj = ring[j], xj = pj[0], yj = pj[1];
if (segmentContains(pi, pj, point)) {
return 0;
}
if (((yi > y) !== (yj > y)) && ((x < (xj - xi) * (y - yi) / (yj - yi) + xi))) {
contains = -contains;
}
}
return contains;
}
function segmentContains(p0, p1, p2) {
var x20 = p2[0] - p0[0], y20 = p2[1] - p0[1];
if (x20 === 0 && y20 === 0) return true;
var x10 = p1[0] - p0[0], y10 = p1[1] - p0[1];
if (x10 === 0 && y10 === 0) return false;
var t = (x20 * x10 + y20 * y10) / (x10 * x10 + y10 * y10);
return t < 0 || t > 1 ? false : t === 0 || t === 1 ? true : t * x10 === x20 && t * y10 === y20;
}
var parsePolyLine = function(record) {
var i = 44, j, n = record.getInt32(36, true), m = record.getInt32(40, true), parts = new Array(n), points = new Array(m);
for (j = 0; j < n; ++j, i += 4) parts[j] = record.getInt32(i, true);
for (j = 0; j < m; ++j, i += 16) points[j] = [record.getFloat64(i, true), record.getFloat64(i + 8, true)];
return n === 1
? {type: "LineString", coordinates: points}
: {type: "MultiLineString", coordinates: parts.map(function(i, j) { return points.slice(i, parts[j + 1]); })};
};
var concat = function(a, b) {
var ab = new Uint8Array(a.length + b.length);
ab.set(a, 0);
ab.set(b, a.length);
return ab;
};
var shp_read = function() {
var that = this;
++that._index;
return that._source.slice(12).then(function(array$$1) {
if (array$$1 == null) return {done: true, value: undefined};
var header = view(array$$1);
// If the record starts with an invalid shape type (see #36), scan ahead in
// four-byte increments to find the next valid record, identified by the
// expected index, a non-empty content length and a valid shape type.
function skip() {
return that._source.slice(4).then(function(chunk) {
if (chunk == null) return {done: true, value: undefined};
header = view(array$$1 = concat(array$$1.slice(4), chunk));
return header.getInt32(0, false) !== that._index ? skip() : read();
});
}
// All records should have at least four bytes (for the record shape type),
// so an invalid content length indicates corruption.
function read() {
var length = header.getInt32(4, false) * 2 - 4, type = header.getInt32(8, true);
return length < 0 || (type && type !== that._type) ? skip() : that._source.slice(length).then(function(chunk) {
return {done: false, value: type ? that._parse(view(concat(array$$1.slice(8), chunk))) : null};
});
}
return read();
});
};
var parsers = {
0: parseNull,
1: parsePoint,
3: parsePolyLine,
5: parsePolygon,
8: parseMultiPoint,
11: parsePoint, // PointZ
13: parsePolyLine, // PolyLineZ
15: parsePolygon, // PolygonZ
18: parseMultiPoint, // MultiPointZ
21: parsePoint, // PointM
23: parsePolyLine, // PolyLineM
25: parsePolygon, // PolygonM
28: parseMultiPoint // MultiPointM
};
var shp = function(source) {
source = slice(source);
return source.slice(100).then(function(array$$1) {
return new Shp(source, view(array$$1));
});
};
function Shp(source, header) {
var type = header.getInt32(32, true);
if (!(type in parsers)) throw new Error("unsupported shape type: " + type);
this._source = source;
this._type = type;
this._index = 0;
this._parse = parsers[type];
this.bbox = [header.getFloat64(36, true), header.getFloat64(44, true), header.getFloat64(52, true), header.getFloat64(60, true)];
}
var prototype$2 = Shp.prototype;
prototype$2.read = shp_read;
prototype$2.cancel = cancel;
function noop() {}
var shapefile_cancel = function() {
return Promise.all([
this._dbf && this._dbf.cancel(),
this._shp.cancel()
]).then(noop);
};
var shapefile_read = function() {
var that = this;
return Promise.all([
that._dbf ? that._dbf.read() : {value: {}},
that._shp.read()
]).then(function(results) {
var dbf = results[0], shp = results[1];
return shp.done ? shp : {
done: false,
value: {
type: "Feature",
properties: dbf.value,
geometry: shp.value
}
};
});
};
var shapefile = function(shpSource, dbfSource, decoder) {
return Promise.all([
shp(shpSource),
dbfSource && dbf(dbfSource, decoder)
]).then(function(sources) {
return new Shapefile(sources[0], sources[1]);
});
};
function Shapefile(shp$$1, dbf$$1) {
this._shp = shp$$1;
this._dbf = dbf$$1;
this.bbox = shp$$1.bbox;
}
var prototype$1 = Shapefile.prototype;
prototype$1.read = shapefile_read;
prototype$1.cancel = shapefile_cancel;
function open(shp$$1, dbf$$1, options) {
if (typeof dbf$$1 === "string") {
if (!/\.dbf$/.test(dbf$$1)) dbf$$1 += ".dbf";
dbf$$1 = path(dbf$$1, options);
} else if (dbf$$1 instanceof ArrayBuffer || dbf$$1 instanceof Uint8Array) {
dbf$$1 = array(dbf$$1);
} else if (dbf$$1 != null) {
dbf$$1 = stream(dbf$$1);
}
if (typeof shp$$1 === "string") {
if (!/\.shp$/.test(shp$$1)) shp$$1 += ".shp";
if (dbf$$1 === undefined) dbf$$1 = path(shp$$1.substring(0, shp$$1.length - 4) + ".dbf", options).catch(function() {});
shp$$1 = path(shp$$1, options);
} else if (shp$$1 instanceof ArrayBuffer || shp$$1 instanceof Uint8Array) {
shp$$1 = array(shp$$1);
} else {
shp$$1 = stream(shp$$1);
}
return Promise.all([shp$$1, dbf$$1]).then(function(sources) {
var shp$$1 = sources[0], dbf$$1 = sources[1], encoding = "windows-1252";
if (options && options.encoding != null) encoding = options.encoding;
return shapefile(shp$$1, dbf$$1, dbf$$1 && new TextDecoder(encoding));
});
}
function openShp(source, options) {
if (typeof source === "string") {
if (!/\.shp$/.test(source)) source += ".shp";
source = path(source, options);
} else if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
source = array(source);
} else {
source = stream(source);
}
return Promise.resolve(source).then(shp);
}
function openDbf(source, options) {
var encoding = "windows-1252";
if (options && options.encoding != null) encoding = options.encoding;
encoding = new TextDecoder(encoding);
if (typeof source === "string") {
if (!/\.dbf$/.test(source)) source += ".dbf";
source = path(source, options);
} else if (source instanceof ArrayBuffer || source instanceof Uint8Array) {
source = array(source);
} else {
source = stream(source);
}
return Promise.resolve(source).then(function(source) {
return dbf(source, encoding);
});
}
function read(shp$$1, dbf$$1, options) {
return open(shp$$1, dbf$$1, options).then(function(source) {
var features = [], collection = {type: "FeatureCollection", features: features, bbox: source.bbox};
return source.read().then(function read(result) {
if (result.done) return collection;
features.push(result.value);
return source.read().then(read);
});
});
}
exports.open = open;
exports.openShp = openShp;
exports.openDbf = openDbf;
exports.read = read;
;