diff --git a/dbf.js b/dbf.js
new file mode 100644
index 0000000..5e69d47
--- /dev/null
+++ b/dbf.js
@@ -0,0 +1,213 @@
+(function(window,undefined){
+
+ if(window.document && window.Worker){
+ var worker = new Worker("dbf.js")
+
+ var DBF = function(url, callback){
+ var
+ w = this._worker = worker,
+ t = this
+
+ w.onmessage = function(e){
+ t.data = e.data
+ if (callback) callback(e.data);
+ };
+
+ w.postMessage(url)
+ }
+
+ window["DBF"] = DBF
+ return
+ }
+
+ var IN_WORKER = !window.document
+ if (IN_WORKER) {
+ importScripts('stream.js')
+ onmessage = function(e){
+ new DBF(e.data);
+ };
+ }
+
+ var
+ DBASE_LEVEL = {
+ "3": "dBASE Level 5",
+ "4": "dBase Level 7"
+ },
+ DBASE_FIELD_TYPE = {
+ "N": "Number",
+ "C": "Character", // binary
+ "L": "Logical",
+ "D": "Date",
+ "M": "Memo", // binary
+ "F": "Floating point",
+ "B": "Binary",
+ "G": "General",
+ "P": "Picture",
+ "Y": "Currency",
+ "T": "DateTime",
+ "I": "Integer",
+ "V": "VariField",
+ "X": "Variant",
+ "@": "Timestamp",
+ "O": "Double",
+ "+": "Autoincrement", // (dBase Level 7)
+ "O": "Double", // (dBase Level 7)
+ "@": "Timestamp" // (dBase Level 7)
+ }
+
+ var DBF = function(url, callback){
+ var xhr = new XMLHttpRequest();
+
+ xhr.open("GET", url, false)
+ xhr.overrideMimeType("text/plain; charset=x-user-defined")
+ xhr.send()
+
+ if(200 != xhr.status)
+ throw "Unable to load " + url + " status: " + xhr.status
+
+ this.stream = new Gordon.Stream(xhr.responseText)
+ this.callback = callback
+
+ this.readFileHeader()
+ this.readFieldDescriptions()
+ this.readRecords()
+
+ this._postMessage()
+ }
+
+ DBF.prototype = {
+ constructor: DBF,
+ _postMessage: function() {
+ var data = {
+ header: this.header,
+ fields: this.fields,
+ records: this.records
+ }
+ if (IN_WORKER) postMessage(data)
+ else if (this.callback) this.callback(data)
+ },
+ readFileHeader: function(){
+ var s = this.stream,
+ header = this.header = {},
+ date = new Date;
+
+ header.version = DBASE_LEVEL[s.readSI8()]
+
+ // Date of last update; in YYMMDD format. Each byte contains the number as a binary. YY is added to a base of 1900 decimal to determine the actual year. Therefore, YY has possible values from 0x00-0xFF, which allows for a range from 1900-2155.
+ date.setUTCFullYear(1900 + s.readSI8())
+ date.setUTCMonth(s.readSI8())
+ date.setUTCDate(s.readSI8())
+
+ header.lastUpdated = date
+
+ // Number of records in file
+ header.numRecords = s.readSI32()
+
+ // Position of first data record
+ header.firstRecordPosition = s.readSI16()
+
+ // Length of one data record, including delete flag
+ header.recordLength = s.readSI16()
+
+ // Reserved; filled with zeros
+ s.offset += 16
+
+ /*
+ Table flags:
+ 0x01 file has a structural .cdx
+ 0x02 file has a Memo field
+ 0x04 file is a database (.dbc)
+ This byte can contain the sum of any of the above values. For example, the value 0x03 indicates the table has a structural .cdx and a Memo field.
+ */
+ header.flags = s.readSI8()
+
+ // Code page mark
+ header.codePageMark = s.readSI8()
+
+ // Reserved; filled with zeros.
+ s.offset += 2
+
+ },
+ readFieldDescriptions: function(){
+ var s = this.stream,
+ fields = [],
+ field
+
+ while (s.readSI8() != 0x0D) {
+ s.offset--
+ field = {}
+
+ // Field name with a maximum of 10 characters. If less than 10, it is padded with null characters (0x00).
+ field.name = s.readString(11).replace(/\u0000/g,"")
+
+ field.type = DBASE_FIELD_TYPE[s.readString(1)]
+
+ // Displacement of field in record
+ field.fieldDisplacement = s.readSI32()
+
+ // Length of field (in bytes)
+ field.fieldLength = s.readUI8()
+
+ // Number of decimal places
+ field.decimals = s.readSI8()
+
+ /*
+ Field flags:
+ 0x01 System Column (not visible to user)
+ 0x02 Column can store null values
+ 0x04 Binary column (for CHAR and MEMO only)
+ 0x06 (0x02+0x04) When a field is NULL and binary (Integer, Currency, and Character/Memo fields)
+ 0x0C Column is autoincrementing
+ */
+ field.flags = s.readSI8()
+
+ // Value of autoincrement Next value
+ field.autoincrementNextValue = s.readSI32()
+
+ // Value of autoincrement Step value
+ field.autoincrementStepValue = s.readSI8()
+
+ // Reserved
+ s.offset += 8
+
+ fields.push(field)
+ }
+
+ this.fields = fields
+
+ },
+ readRecords: function(){
+ var s = this.stream,
+ numRecords = this.header.numRecords,
+ recordsOffset = this.header.firstRecordPosition,
+ recordSize = this.header.recordLength,
+ fields = this.fields,
+ numFields = fields.length,
+ records = [],
+ field, record
+
+ for (var index = 0; index < numRecords; index++) {
+ s.offset = recordsOffset + index * recordSize
+
+ record = {}
+
+ // Data records begin with a delete flag byte. If this byte is an ASCII space (0x20), the record is not deleted. If the first byte is an asterisk (0x2A), the record is deleted
+ record._isDeleted = s.readSI8() == 42
+
+ for(var i = 0; i < numFields; i++){
+ field = fields[i]
+ record[field.name] = s.readString(field.fieldLength).trim();
+ }
+
+ records.push(record);
+ }
+
+ this.records = records
+
+ }
+ }
+
+ window["DBF"] = DBF;
+
+})(self)
+
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..94e2bc0
--- /dev/null
+++ b/index.html
@@ -0,0 +1,29 @@
+
+
+
+ Binary Test
+
+
+
+
+
+
+
+
+
+
+
diff --git a/renderer.js b/renderer.js
new file mode 100644
index 0000000..e69de29
diff --git a/shapefile.js b/shapefile.js
new file mode 100644
index 0000000..00f7c07
--- /dev/null
+++ b/shapefile.js
@@ -0,0 +1,323 @@
+(function(window,undefined){
+
+ if(window.document && window.Worker){
+ var worker = new Worker("shapefile.js")
+
+ var Shapefile = function(o, callback){
+ var
+ w = this.worker = worker,
+ t = this,
+ o = typeof o == "string" ? {shp: o} : o
+
+ w.onmessage = function(e){
+ t.data = e.date
+ if(callback) callback(e.data)
+ }
+
+ w.postMessage(["Load", o])
+
+ if(o.dbf) this.dbf = new DBF(o.dbf,function(data){
+ w.postMessage(["Add DBF Attributes", data])
+ })
+ }
+
+ window["Shapefile"] = Shapefile
+ return
+ }
+
+ var IN_WORKER = !window.document
+ if (IN_WORKER) {
+ importScripts('stream.js')
+ onmessage = function(e){
+ switch (e.data[0]) {
+ case "Load":
+ window.shapefile = new Shapefile(e.data[1])
+ break
+ case "Add DBF Attributes":
+ window.shapefile.addDBFDataToGeoJSON(e.data[1])
+ window.shapefile._postMessage()
+ break
+ default:
+ }
+ };
+ }
+
+ var SHAPE_TYPES = {
+ "0": "Null Shape",
+ "1": "Point", // standard shapes
+ "3": "PolyLine",
+ "5": "Polygon",
+ "8": "MultiPoint",
+ "11": "PointZ", // 3d shapes
+ "13": "PolyLineZ",
+ "15": "PolygonZ",
+ "18": "MultiPointZ",
+ "21": "PointM", // user-defined measurement shapes
+ "23": "PolyLineM",
+ "25": "PolygonM",
+ "28": "MultiPointM",
+ "31": "MultiPatch"
+ }
+
+ var Shapefile = function(o,callback){
+ var xhr = new XMLHttpRequest(),
+ that = this,
+ o = typeof o == "string" ? {shp: o} : o
+
+ xhr.open("GET", o.shp, false)
+ xhr.overrideMimeType("text/plain; charset=x-user-defined")
+ xhr.send()
+
+ if(200 != xhr.status)
+ throw "Unable to load " + o.shp + " status: " + xhr.status
+
+ this.url = o.shp
+ this.stream = new Gordon.Stream(xhr.responseText)
+ this.callback = callback
+
+ this.readFileHeader()
+ this.readRecords()
+ this.formatIntoGeoJson()
+
+ if(o.dbf) this.dbf = IN_WORKER ?
+ null :
+ new DBF(o.dbf,function(data){
+ that.addDBFDataToGeoJSON(data)
+ that._postMessage()
+ })
+ else this._postMessage
+ }
+
+ Shapefile.prototype = {
+ constructor: Shapefile,
+ _postMessage: function() {
+ var data = {
+ header: this.header,
+ records: this.records,
+ dbf: this.dbf,
+ geojson: this.geojson
+ }
+ if (IN_WORKER) postMessage(data)
+ else if (this.callback) this.callback(data)
+ },
+ readFileHeader: function(){
+ var s = this.stream,
+ header = this.header = {}
+
+ // The main file header is fixed at 100 bytes in length
+ if(s < 100) throw "Invalid Header Length"
+
+ // File code (always hex value 0x0000270a)
+ header.fileCode = s.readSI32(true)
+
+ if(header.fileCode != parseInt(0x0000270a))
+ throw "Invalid File Code"
+
+ // Unused; five uint32
+ s.offset += 4 * 5
+
+ // File length (in 16-bit words, including the header)
+ header.fileLength = s.readSI32(true) * 2
+
+ header.version = s.readSI32()
+
+ header.shapeType = SHAPE_TYPES[s.readSI32()]
+
+ // Minimum bounding rectangle (MBR) of all shapes contained within the shapefile; four doubles in the following order: min X, min Y, max X, max Y
+ this._readBounds(header)
+
+ // Z axis range
+ header.rangeZ = {
+ min: s.readDouble(),
+ max: s.readDouble()
+ }
+
+ // User defined measurement range
+ header.rangeM = {
+ min: s.readDouble(),
+ max: s.readDouble()
+ }
+
+ },
+ readRecords: function(){
+ var s = this.stream,
+ records = this.records = [],
+ record
+
+ do {
+ record = {}
+
+ // Record number (1-based)
+ record.id = s.readSI32(true)
+
+ if(record.id == 0) break //no more records
+
+ // Record length (in 16-bit words)
+ record.length = s.readSI32(true) * 2
+
+ record.shapeType = SHAPE_TYPES[s.readSI32()]
+
+ // Read specific shape
+ this["_read" + record.shapeType](record);
+
+ records.push(record);
+
+ } while(true);
+
+ },
+ _readBounds: function(object){
+ var s = this.stream
+
+ object.bounds = {
+ left: s.readDouble(),
+ bottom: s.readDouble(),
+ right: s.readDouble(),
+ top: s.readDouble()
+ }
+
+ return object
+ },
+ _readParts: function(record){
+ var s = this.stream,
+ nparts,
+ parts = []
+
+ nparts = record.numParts = s.readSI32()
+
+ // since number of points always proceeds number of parts, capture it now
+ record.numPoints = s.readSI32()
+
+ // parts array indicates at which index the next part starts at
+ while(nparts--) parts.push(s.readSI32())
+
+ record.parts = parts
+
+ return record
+ },
+ _readPoint: function(record){
+ var s = this.stream
+
+ record.x = s.readDouble()
+ record.y = s.readDouble()
+
+ return record
+ },
+ _readPoints: function(record){
+ var s = this.stream,
+ points = [],
+ npoints = record.numPoints || (record.numPoints = s.readSI32())
+
+ while(npoints--)
+ points.push({
+ x: s.readDouble(),
+ y: s.readDouble()
+ })
+
+ record.points = points
+
+ return record
+ },
+ _readMultiPoint: function(record){
+ var s = this.stream
+
+ this._readBounds(record)
+ this._readPoints(record)
+
+ return record
+ },
+ _readPolygon: function(record){
+ var s = this.stream
+
+ this._readBounds(record)
+ this._readParts(record)
+ this._readPoints(record)
+
+ return record
+ },
+ _readPolyLine: function(record){
+ return this._readPolygon(record);
+ },
+ formatIntoGeoJson: function(){
+ var bounds = this.header.bounds,
+ records = this.records,
+ features = [],
+ feature, geometry, points, fbounds, gcoords, parts, point,
+ geojson = {}
+
+ geojson.type = "FeatureCollection"
+ geojson.bbox = [
+ bounds.left,
+ bounds.bottom,
+ bounds.right,
+ bounds.top
+ ]
+ geojson.features = features
+
+ for (var r = 0, record; record = records[r]; r++){
+ feature = {}, fbounds = record.bounds, points = record.points, parts = record.parts
+ feature.type = "Feature"
+ feature.bbox = [
+ fbounds.left,
+ fbounds.bottom,
+ fbounds.right,
+ fbounds.top
+ ]
+ geometry = feature.geometry = {}
+
+ switch (record.shapeType) {
+ case "Point":
+ geometry.type = "Point"
+ geometry.coordinates = [
+ record.points.x,
+ record.points,y ]
+ break
+ case "MultiPoint":
+ case "PolyLine":
+ geometry.type = (record.shapeType == "PolyLine" ? "LineString" : "MultiPoint")
+ gcoords = geometry.coordinates = []
+
+ for (var p = 0; p < points.length; p++){
+ var point = points[p]
+ gcoords.push([point.x,point.y])
+ }
+ break
+ case "Polygon":
+ geometry.type = "Polygon"
+ gcoords = geometry.coordinates = []
+
+ for (var pt = 0; pt < parts.length; pt++){
+ var partIndex = parts[pt],
+ part = [],
+ point
+
+ for (var p = partIndex; p < (partIndex[pt+1] || points.length); p++){
+ point = points[p]
+ part.push([point.x,point.y])
+ }
+ gcoords.push(part)
+ }
+ break
+ default:
+ }
+ features.push(feature)
+ }
+ this.geojson = geojson
+
+ if(this._addDataAfterLoad) this.addDBFDataToGeoJSON(this._addDataAfterLoad);
+ },
+ addDBFDataToGeoJSON: function(dbfData){
+ if(!this.geojson) return (this._addDataAfterLoad = dbfData)
+
+ this.dbf = dbfData
+
+ var features = this.geojson.features,
+ len = features.length,
+ records = dbfData.records
+
+ while(len--) features[len].properties = records[len]
+ }
+ }
+
+ window["Shapefile"] = Shapefile;
+})(self)
+
diff --git a/stream.js b/stream.js
new file mode 100644
index 0000000..9666ddf
--- /dev/null
+++ b/stream.js
@@ -0,0 +1,486 @@
+/*
+ Stream Reader from Gordon.JS
+ Copyright (c) 2010 Tobias Schneider
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+var win = self,
+ doc = win.document,
+ fromCharCode = String.fromCharCode,
+ push = Array.prototype.push,
+ min = Math.min,
+ max = Math.max;
+
+(function(window,undefined){
+
+ window.Gordon = {};
+
+ var DEFLATE_CODE_LENGTH_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15],
+ DEFLATE_CODE_LENGHT_MAP = [
+ [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8], [0, 9], [0, 10], [1, 11], [1, 13], [1, 15], [1, 17],
+ [2, 19], [2, 23], [2, 27], [2, 31], [3, 35], [3, 43], [3, 51], [3, 59], [4, 67], [4, 83], [4, 99],
+ [4, 115], [5, 131], [5, 163], [5, 195], [5, 227], [0, 258]
+ ],
+ DEFLATE_DISTANCE_MAP = [
+ [0, 1], [0, 2], [0, 3], [0, 4], [1, 5], [1, 7], [2, 9], [2, 13], [3, 17], [3, 25], [4, 33], [4, 49],
+ [5, 65], [5, 97], [6, 129], [6, 193], [7, 257], [7, 385], [8, 513], [8, 769], [9, 1025], [9, 1537],
+ [10, 2049], [10, 3073], [11, 4097], [11, 6145], [12, 8193], [12, 12289], [13, 16385], [13, 24577]
+ ];
+
+ Gordon.Stream = function(data){
+ var buff = [],
+ t = this,
+ i = t.length = data.length;
+ t.offset = 0;
+ for(var i = 0; data[i]; i++){ buff.push(fromCharCode(data.charCodeAt(i) & 0xff)); }
+ t._buffer = buff.join('');
+ t._bitBuffer = null;
+ t._bitOffset = 8;
+ };
+ Gordon.Stream.prototype = {
+ readByteAt: function(pos){
+ return this._buffer.charCodeAt(pos);
+ },
+
+ readNumber: function(numBytes, bigEnd){
+ var t = this,
+ val = 0;
+ if(bigEnd){
+ while(numBytes--){ val = (val << 8) | t.readByteAt(t.offset++); }
+ }else{
+ var o = t.offset,
+ i = o + numBytes;
+ while(i > o){ val = (val << 8) | t.readByteAt(--i); }
+ t.offset += numBytes;
+ }
+ t.align();
+ return val;
+ },
+
+ readSNumber: function(numBytes, bigEnd){
+ var val = this.readNumber(numBytes, bigEnd),
+ numBits = numBytes * 8;
+ if(val >> (numBits - 1)){ val -= Math.pow(2, numBits); }
+ return val;
+ },
+
+ readSI8: function(){
+ return this.readSNumber(1);
+ },
+
+ readSI16: function(bigEnd){
+ return this.readSNumber(2, bigEnd);
+ },
+
+ readSI32: function(bigEnd){
+ return this.readSNumber(4, bigEnd);
+ },
+
+ readUI8: function(){
+ return this.readNumber(1);
+ },
+
+ readUI16: function(bigEnd){
+ return this.readNumber(2, bigEnd);
+ },
+
+ readUI24: function(bigEnd){
+ return this.readNumber(3, bigEnd);
+ },
+
+ readUI32: function(bigEnd){
+ return this.readNumber(4, bigEnd);
+ },
+
+ readFixed: function(){
+ return this._readFixedPoint(32, 16);
+ },
+
+ _readFixedPoint: function(numBits, precision){
+ return this.readSB(numBits) * Math.pow(2, -precision);
+ },
+
+ readFixed8: function(){
+ return this._readFixedPoint(16, 8);
+ },
+
+ readFloat: function(){
+ return this._readFloatingPoint(8, 23);
+ },
+
+ _readFloatingPoint: function(numEBits, numSBits){
+ var numBits = 1 + numEBits + numSBits,
+ numBytes = numBits / 8,
+ t = this,
+ val = 0.0;
+ if(numBytes > 4){
+ var i = Math.ceil(numBytes / 4);
+ while(i--){
+ var buff = [],
+ o = t.offset,
+ j = o + (numBytes >= 4 ? 4 : numBytes % 4);
+ while(j > o){
+ buff.push(t.readByteAt(--j));
+ numBytes--;
+ t.offset++;
+ }
+ }
+ var s = new Gordon.Stream(fromCharCode.apply(String, buff)),
+ sign = s.readUB(1),
+ expo = s.readUB(numEBits),
+ mantis = 0,
+ i = numSBits;
+ while(i--){
+ if(s.readBool()){ mantis += Math.pow(2, i); }
+ }
+ }else{
+ var sign = t.readUB(1),
+ expo = t.readUB(numEBits),
+ mantis = t.readUB(numSBits);
+ }
+ if(sign || expo || mantis){
+ var maxExpo = Math.pow(2, numEBits),
+ bias = ~~((maxExpo - 1) / 2),
+ scale = Math.pow(2, numSBits),
+ fract = mantis / scale;
+ if(bias){
+ if(bias < maxExpo){ val = Math.pow(2, expo - bias) * (1 + fract); }
+ else if(fract){ val = NaN; }
+ else{ val = Infinity; }
+ }else if(fract){ val = Math.pow(2, 1 - bias) * fract; }
+ if(NaN != val && sign){ val *= -1; }
+ }
+ return val;
+ },
+
+ readFloat16: function(){
+ return this._readFloatingPoint(5, 10);
+ },
+
+ readDouble: function(){
+ return this._readFloatingPoint(11, 52);
+ },
+
+ readEncodedU32: function(){
+ var val = 0;
+ for(var i = 0; i < 5; i++){
+ var num = this.readByteAt(this._offset++);
+ val = val | ((num & 0x7f) << (7 * i));
+ if(!(num & 0x80)){ break; }
+ }
+ return val;
+ },
+
+ readSB: function(numBits){
+ var val = this.readUB(numBits);
+ if(val >> (numBits - 1)){ val -= Math.pow(2, numBits); }
+ return val;
+ },
+
+ readUB: function(numBits, lsb){
+ var t = this,
+ val = 0;
+ for(var i = 0; i < numBits; i++){
+ if(8 == t._bitOffset){
+ t._bitBuffer = t.readUI8();
+ t._bitOffset = 0;
+ }
+ if(lsb){ val |= (t._bitBuffer & (0x01 << t._bitOffset++) ? 1 : 0) << i; }
+ else{ val = (val << 1) | (t._bitBuffer & (0x80 >> t._bitOffset++) ? 1 : 0); }
+ }
+ return val;
+ },
+
+ readFB: function(numBits){
+ return this._readFixedPoint(numBits, 16);
+ },
+
+ readString: function(numChars){
+ var t = this,
+ b = t._buffer;
+ if(undefined != numChars){
+ var str = b.substr(t.offset, numChars);
+ t.offset += numChars;
+ }else{
+ var chars = [],
+ i = t.length - t.offset;
+ while(i--){
+ var code = t.readByteAt(t.offset++);
+ if(code){ chars.push(fromCharCode(code)); }
+ else{ break; }
+ }
+ var str = chars.join('');
+ }
+ return str;
+ },
+
+ readBool: function(numBits){
+ return !!this.readUB(numBits || 1);
+ },
+
+ seek: function(offset, absolute){
+ var t = this;
+ t.offset = (absolute ? 0 : t.offset) + offset;
+ t.align();
+ return t;
+ },
+
+ align: function(){
+ this._bitBuffer = null;
+ this._bitOffset = 8;
+ return this;
+ },
+
+ readLanguageCode: function(){
+ return this.readUI8();
+ },
+
+ readRGB: function(){
+ return {
+ red: this.readUI8(),
+ green: this.readUI8(),
+ blue: this.readUI8()
+ }
+ },
+
+ readRGBA: function(){
+ var rgba = this.readRGB();
+ rgba.alpha = this.readUI8() / 255;
+ return rgba;
+ },
+
+ readARGB: function(){
+ var alpha = this.readUI8() / 255,
+ rgba = this.readRGB();
+ rgba.alpha = alpha;
+ return rgba;
+ },
+
+ readRect: function(){
+ var t = this;
+ numBits = t.readUB(5),
+ rect = {
+ left: t.readSB(numBits),
+ right: t.readSB(numBits),
+ top: t.readSB(numBits),
+ bottom: t.readSB(numBits)
+ };
+ t.align();
+ return rect;
+ },
+
+ readMatrix: function(){
+ var t = this,
+ hasScale = t.readBool();
+ if(hasScale){
+ var numBits = t.readUB(5),
+ scaleX = t.readFB(numBits),
+ scaleY = t.readFB(numBits);
+ }else{ var scaleX = scaleY = 1.0; }
+ var hasRotation = t.readBool();
+ if(hasRotation){
+ var numBits = t.readUB(5),
+ skewX = t.readFB(numBits),
+ skewY = t.readFB(numBits);
+ }else{ var skewX = skewY = 0.0; }
+ var numBits = t.readUB(5);
+ matrix = {
+ scaleX: scaleX, scaleY: scaleY,
+ skewX: skewX, skewY: skewY,
+ moveX: t.readSB(numBits), moveY: t.readSB(numBits)
+ };
+ t.align();
+ return matrix;
+ },
+
+ readCxform: function(){
+ return this._readCxf();
+ },
+
+ readCxformA: function(){
+ return this._readCxf(true);
+ },
+
+ _readCxf: function(withAlpha){
+ var t = this;
+ hasAddTerms = t.readBool(),
+ hasMultTerms = t.readBool(),
+ numBits = t.readUB(4);
+ if(hasMultTerms){
+ var multR = t.readSB(numBits) / 256,
+ multG = t.readSB(numBits) / 256,
+ multB = t.readSB(numBits) / 256,
+ multA = withAlpha ? t.readSB(numBits) / 256 : 1;
+ }else{ var multR = multG = multB = multA = 1; }
+ if(hasAddTerms){
+ var addR = t.readSB(numBits),
+ addG = t.readSB(numBits),
+ addB = t.readSB(numBits),
+ addA = withAlpha ? t.readSB(numBits) / 256 : 0;
+ }else{ var addR = addG = addB = addA = 0; }
+ var cxform = {
+ multR: multR, multG: multG, multB: multB, multA: multA,
+ addR: addR, addG: addG, addB: addB, addA: addA
+ }
+ t.align();
+ return cxform;
+ },
+
+ decompress: function(){
+ var t = this,
+ b = t._buffer,
+ o = t.offset,
+ data = b.substr(0, o) + t.unzip();
+ t.length = data.length;
+ t.offset = o;
+ t._buffer = data;
+ return t;
+ },
+
+ unzip: function uz(raw){
+ var t = this,
+ buff = [],
+ o = DEFLATE_CODE_LENGTH_ORDER,
+ m = DEFLATE_CODE_LENGHT_MAP,
+ d = DEFLATE_DISTANCE_MAP;
+ t.seek(2);
+ do{
+ var isFinal = t.readUB(1, true),
+ type = t.readUB(2, true);
+ if(type){
+ if(1 == type){
+ var distTable = uz.fixedDistTable,
+ litTable = uz.fixedLitTable;
+ if(!distTable){
+ var bitLengths = [];
+ for(var i = 0; i < 32; i++){ bitLengths.push(5); }
+ distTable = uz.fixedDistTable = buildHuffTable(bitLengths);
+ }
+ if(!litTable){
+ var bitLengths = [];
+ for(var i = 0; i <= 143; i++){ bitLengths.push(8); }
+ for(; i <= 255; i++){ bitLengths.push(9); }
+ for(; i <= 279; i++){ bitLengths.push(7); }
+ for(; i <= 287; i++){ bitLengths.push(8); }
+ litTable = uz.fixedLitTable = buildHuffTable(bitLengths);
+ }
+ }else{
+ var numLitLengths = t.readUB(5, true) + 257,
+ numDistLengths = t.readUB(5, true) + 1,
+ numCodeLenghts = t.readUB(4, true) + 4,
+ codeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ for(var i = 0; i < numCodeLenghts; i++){ codeLengths[o[i]] = t.readUB(3, true); }
+ var codeTable = buildHuffTable(codeLengths),
+ litLengths = [],
+ prevCodeLen = 0,
+ maxLengths = numLitLengths + numDistLengths;
+ while(litLengths.length < maxLengths){
+ var sym = decodeSymbol(t, codeTable);
+ switch(sym){
+ case 16:
+ var i = t.readUB(2, true) + 3;
+ while(i--){ litLengths.push(prevCodeLen); }
+ break;
+ case 17:
+ var i = t.readUB(3, true) + 3;
+ while(i--){ litLengths.push(0); }
+ break;
+ case 18:
+ var i = t.readUB(7, true) + 11;
+ while(i--){ litLengths.push(0); }
+ break;
+ default:
+ if(sym <= 15){
+ litLengths.push(sym);
+ prevCodeLen = sym;
+ }
+ }
+ }
+ var distTable = buildHuffTable(litLengths.splice(numLitLengths, numDistLengths)),
+ litTable = buildHuffTable(litLengths);
+ }
+ do{
+ var sym = decodeSymbol(t, litTable);
+ if(sym < 256){ buff.push(raw ? sym : fromCharCode(sym)); }
+ else if(sym > 256){
+ var lengthMap = m[sym - 257],
+ len = lengthMap[1] + t.readUB(lengthMap[0], true),
+ distMap = d[decodeSymbol(t, distTable)],
+ dist = distMap[1] + t.readUB(distMap[0], true),
+ i = buff.length - dist;
+ while(len--){ buff.push(buff[i++]); }
+ }
+ }while(256 != sym);
+ }else{
+ t.align();
+ var len = t.readUI16(),
+ nlen = t.readUI16();
+ if(raw){ while(len--){ buff.push(t.readUI8()); } }
+ else{ buff.push(t.readString(len)); }
+ }
+ }while(!isFinal);
+ t.seek(4);
+ return raw ? buff : buff.join('');
+ }
+ };
+
+ function buildHuffTable(bitLengths){
+ var numLengths = bitLengths.length,
+ blCount = [],
+ maxBits = max.apply(Math, bitLengths),
+ nextCode = [],
+ code = 0,
+ table = {},
+ i = numLengths;
+ while(i--){
+ var len = bitLengths[i];
+ blCount[len] = (blCount[len] || 0) + (len > 0);
+ }
+ for(var i = 1; i <= maxBits; i++){
+ var len = i - 1;
+ if(undefined == blCount[len]){ blCount[len] = 0; }
+ code = (code + blCount[i - 1]) << 1;
+ nextCode[i] = code;
+ }
+ for(var i = 0; i < numLengths; i++){
+ var len = bitLengths[i];
+ if(len){
+ table[nextCode[len]] = {
+ length: len,
+ symbol: i
+ };
+ nextCode[len]++;
+ }
+ }
+ return table;
+ }
+
+ function decodeSymbol(s, table) {
+ var code = 0,
+ len = 0;
+ while(true){
+ code = (code << 1) | s.readUB(1, true);
+ len++;
+ var entry = table[code];
+ if(undefined != entry && entry.length == len){ return entry.symbol }
+ }
+ }
+})(this);
+
diff --git a/testdata/110m-geography-marine-polys.dbf b/testdata/110m-geography-marine-polys.dbf
new file mode 100755
index 0000000..d2fbf27
Binary files /dev/null and b/testdata/110m-geography-marine-polys.dbf differ
diff --git a/testdata/110m-geography-marine-polys.prj b/testdata/110m-geography-marine-polys.prj
new file mode 100755
index 0000000..f45cbad
--- /dev/null
+++ b/testdata/110m-geography-marine-polys.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]
\ No newline at end of file
diff --git a/testdata/110m-geography-marine-polys.sbn b/testdata/110m-geography-marine-polys.sbn
new file mode 100755
index 0000000..2e97b3c
Binary files /dev/null and b/testdata/110m-geography-marine-polys.sbn differ
diff --git a/testdata/110m-geography-marine-polys.sbx b/testdata/110m-geography-marine-polys.sbx
new file mode 100755
index 0000000..a02c511
Binary files /dev/null and b/testdata/110m-geography-marine-polys.sbx differ
diff --git a/testdata/110m-geography-marine-polys.shp b/testdata/110m-geography-marine-polys.shp
new file mode 100755
index 0000000..5748f51
Binary files /dev/null and b/testdata/110m-geography-marine-polys.shp differ
diff --git a/testdata/110m-geography-marine-polys.shx b/testdata/110m-geography-marine-polys.shx
new file mode 100755
index 0000000..37eb2bd
Binary files /dev/null and b/testdata/110m-geography-marine-polys.shx differ
diff --git a/testdata/TM_WORLD_BORDERS_SIMPL-0.3.dbf b/testdata/TM_WORLD_BORDERS_SIMPL-0.3.dbf
new file mode 100644
index 0000000..64ea895
Binary files /dev/null and b/testdata/TM_WORLD_BORDERS_SIMPL-0.3.dbf differ
diff --git a/testdata/TM_WORLD_BORDERS_SIMPL-0.3.prj b/testdata/TM_WORLD_BORDERS_SIMPL-0.3.prj
new file mode 100644
index 0000000..f45cbad
--- /dev/null
+++ b/testdata/TM_WORLD_BORDERS_SIMPL-0.3.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]
\ No newline at end of file
diff --git a/testdata/TM_WORLD_BORDERS_SIMPL-0.3.shp b/testdata/TM_WORLD_BORDERS_SIMPL-0.3.shp
new file mode 100644
index 0000000..63e155c
Binary files /dev/null and b/testdata/TM_WORLD_BORDERS_SIMPL-0.3.shp differ
diff --git a/testdata/TM_WORLD_BORDERS_SIMPL-0.3.shx b/testdata/TM_WORLD_BORDERS_SIMPL-0.3.shx
new file mode 100644
index 0000000..18af94b
Binary files /dev/null and b/testdata/TM_WORLD_BORDERS_SIMPL-0.3.shx differ