diff --git a/.gitignore b/.gitignore index 8abcf6a..53b42f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ *.sublime-workspace .DS_Store -dist/ node_modules npm-debug.log diff --git a/dbf/read.js b/dbf/read.js index 9671dd5..b68b101 100644 --- a/dbf/read.js +++ b/dbf/read.js @@ -16,9 +16,14 @@ var types = { export default 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) { + if(!value || (value && value[0] === 0x1a)) { + return {done: true, value: undefined}; + } + var values = 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}; + }, {}); + values.markedAsDeleted = value[0] === 0x2A; + return {done: false, value: values}; }); } diff --git a/dist/shapefile.node.js b/dist/shapefile.node.js new file mode 100644 index 0000000..d163dab --- /dev/null +++ b/dist/shapefile.node.js @@ -0,0 +1,376 @@ +'use strict'; + +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) { + if(!value || (value && value[0] === 0x1a)) { + return {done: true, value: undefined}; + } + var values = that._fields.reduce(function(p, f) { + p[f.name] = types[f.type](that._decode(value.subarray(i, i += f.length))); + return p; + }, {}); + values.markedAsDeleted = value[0] === 0x2A; + return {done: false, value: values}; + }); +}; + +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;