diff --git a/index.js b/index.js index c4be8d6..0d4fbf6 100644 --- a/index.js +++ b/index.js @@ -43,7 +43,8 @@ function getFeatures(url, callback) { geometry: {}, properties: {} }; -// console.log(record); + // TODO: Crashes here when a Shape file has been appended to and number of + // records do not match in .shp and .dbf. feature.properties = attrs.values; switch(record.shapeType) { @@ -60,18 +61,28 @@ function getFeatures(url, callback) { } break; case Shp.ShpType.SHAPE_POLYGON: + var coordinates = [], + j, len, + ring, featureRing; feature.geometry.type = Shp.GeoJsonType[record.shapeType]; feature.geometry.coordinates = []; - var ringsLen = record.shape.rings.length; - for (var j = 0; j < ringsLen; j++) { - var ring = record.shape.rings[j]; + for (j = 0, len = record.shape.rings.length; j < len; j++) { + ring = record.shape.rings[j]; if (ring.length < 1) continue; - var featureRing = feature.geometry.coordinates[j] = []; - var ringLen = ring.length; - for (var k = 0; k < ringLen; k++) { - featureRing.push([ring[k].x, ring[k].y]); + featureRing = ring.map(function(point) { + return [point.x, point.y]; + }); + if (coordinates.length > 0 && ring.isClockwise) { + feature.geometry.type = 'MultiPolygon'; + feature.geometry.coordinates.push(coordinates); + coordinates = []; } + coordinates.push(featureRing); } + if (feature.geometry.coordinates.length === 0) + feature.geometry.coordinates = coordinates; + else + feature.geometry.coordinates.push(coordinates); break; default: throw(new Error("Error converting SHP to geojson: Unsupported feature type")) diff --git a/lib/shp.js b/lib/shp.js index ef04025..ee0a4ff 100644 --- a/lib/shp.js +++ b/lib/shp.js @@ -4,6 +4,13 @@ var fs = require('fs'); var BinaryReader = require('./BinaryReader'); +function isClockwise(points) { + return points.reduce(function (sum, c, i, points) { + var n = points[(i+1)%points.length]; + return sum + (n.x-c.x)*(n.y+c.y); + }, 0) >= 0; +} + var ShpFile = function(path) { this.header = {}; this._reader = new BinaryReader(path+".shp"); @@ -266,7 +273,7 @@ function ShpPolyline(reader, size) { reader.bigEndian = false; - this.box = { + this.box = { x: reader.readDouble(), y: reader.readDouble(), width: reader.readDouble(), @@ -289,14 +296,18 @@ function ShpPolyline(reader, size) { // convert points, and ringOffsets arrays to an array of rings: var removed = 0; - var split; + var split, ring; ringOffsets.shift(); while(ringOffsets.length) { split = ringOffsets.shift(); - this.rings.push(points.splice(0,split-removed)); + ring = points.splice(0,split-removed); + ring.isClockwise = isClockwise(ring); + this.rings.push(ring); removed = split; } - this.rings.push(points); + ring = points; + ring.isClockwise = isClockwise(ring); + this.rings.push(ring); } } diff --git a/test/shp.spec.js b/test/shp.spec.js index 50f753a..759a1e6 100644 --- a/test/shp.spec.js +++ b/test/shp.spec.js @@ -8,10 +8,11 @@ describe('Shapefile Reader', function() { it('Can correctly convert multiPolygon shapefile to GeoJSON', function(done){ var good_json = JSON.parse(fs.readFileSync(__dirname + '/data/multipolygon.json', "utf8")); shpFile.readFile(__dirname + '/data/multipolygon', function(error, data){ - expect(data.features[0].geometry.coordinates[0][0][0]).to.be.closeTo(good_json.features[0].geometry.coordinates[0][0][0], 0.00001); - expect(data.features[0].geometry.coordinates[0][0][1]).to.be.closeTo(good_json.features[0].geometry.coordinates[0][0][1], 0.00001); - expect(data.features[0].geometry.coordinates[0][1][0]).to.be.closeTo(good_json.features[0].geometry.coordinates[0][1][0], 0.00001); - expect(data.features[0].geometry.coordinates[0][1][1]).to.be.closeTo(good_json.features[0].geometry.coordinates[0][1][1], 0.00001); + expect(data.features[0].geometry.type).to.equal('MultiPolygon'); + expect(data.features[0].geometry.coordinates[0][0][0][0]).to.be.closeTo(good_json.features[0].geometry.coordinates[0][0][0][0], 0.00001); + expect(data.features[0].geometry.coordinates[0][0][0][1]).to.be.closeTo(good_json.features[0].geometry.coordinates[0][0][0][1], 0.00001); + expect(data.features[0].geometry.coordinates[0][0][1][0]).to.be.closeTo(good_json.features[0].geometry.coordinates[0][0][1][0], 0.00001); + expect(data.features[0].geometry.coordinates[0][0][1][1]).to.be.closeTo(good_json.features[0].geometry.coordinates[0][0][1][1], 0.00001); }); done(); });