/shapefile.js
JavaScript | 375 lines | 292 code | 70 blank | 13 comment | 42 complexity | a8310a4c18ce2a1313facc1575e591cb MD5 | raw file
- (function(window,undefined){
- if(window.document && window.Worker){
- var worker = null;
- var Shapefile = function(o, callback){
- var
- t = this,
- o = typeof o == "string" ? {shp: o} : o
- if (!worker) {
- var path = (o.jsRoot || "") + "shapefile.js"
- var w = worker = this.worker = new Worker(path)
- } else {
- var w = worker
- }
- 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 o = typeof o == "string" ? {shp: o} : o
- this.callback = callback
- if (!!(o.shp.lastModifiedDate || o.shp.lastModified))
- this.handleFile(o);
- else
- this.handleUri(o);
- }
- Shapefile.prototype = {
- constructor: Shapefile,
- handleUri: function(o) {
- var xhr = new XMLHttpRequest(),
- that = this
-
- 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.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()
- },
- handleFile: function(o) {
- this.options = o
- if (!!window.FileReader) {
- var reader = new FileReader();
- } else {
- var reader = new FileReaderSync();
- }
- reader.onload = (function(that){
- return function(e){
- that.onFileLoad(e.target.result)
- }
- })(this);
- if (!!window.FileReader) {
- reader.readAsBinaryString(o.shp);
- } else {
- this.onFileLoad(reader.readAsBinaryString(o.shp));
- }
- },
- onFileLoad: function(data) {
- this.stream = new Gordon.Stream(data)
- this.readFileHeader()
- this.readRecords()
- this.formatIntoGeoJson()
- if(this.options.dbf) this.dbf = IN_WORKER ?
- null :
- new DBF(this.options.dbf,function(data){
- that.addDBFDataToGeoJSON(data)
- that._postMessage()
- })
- else this._postMessage()
- },
- _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"
- if (record.shapeType !== 'Point') {
- feature.bbox = [
- fbounds.left,
- fbounds.bottom,
- fbounds.right,
- fbounds.top
- ]
- }
- geometry = feature.geometry = {}
- switch (record.shapeType) {
- case "Point":
- geometry.type = "Point"
- geometry.coordinates = [
- record.x,
- record.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
- // partIndex 0 == main poly, partIndex > 0 == holes in poly
- for (var p = partIndex; p < (parts[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);