You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
375 lines
12 KiB
375 lines
12 KiB
(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 || "") + "tools/shapefileToGeoJson/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) |
|
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); |
|
|
|
|