mirror of
https://github.com/wavded/js-shapefile-to-geojson
synced 2024-11-23 14:34:54 +00:00
8da264ae24
Shapefile and DBF constructors now support referencing files via both url and a HTML 5 File API handle. This can be used to load and map content added to file input(s) and processed on the client browser. FileReader and FileReaderSync are both used depending upon availability to make this functionality work in both Gecko and Webkit inside Web Workers.
374 lines
11 KiB
JavaScript
374 lines
11 KiB
JavaScript
(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)
|
|
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"
|
|
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
|
|
|
|
// 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)
|
|
|