diff --git a/fixtures.js b/fixtures.js index 45e4372..a14d4f5 100644 --- a/fixtures.js +++ b/fixtures.js @@ -2,7 +2,7 @@ var mapnik = require('mapnik'); var path = require('path'); var fs = require('fs'); -mapnik.register_datasource(path.join(mapnik.settings.paths.input_plugins,'geojson.input')); +mapnik.register_datasource(path.join(mapnik.settings.paths.input_plugins, 'geojson.input')); var fixtures = { "zero-point": { @@ -31,6 +31,19 @@ var fixtures = { } ] }, + "zero-polygon": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [] + }, + "properties": {} + } + ] + }, "singleton-multi-point": { "type": "FeatureCollection", "features": [ @@ -57,6 +70,19 @@ var fixtures = { } ] }, + "singleton-multi-polygon": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[0, 0], [1, 0], [1, 1], [0, 0]]]] + }, + "properties": {} + } + ] + }, "multi-point": { "type": "FeatureCollection", "features": [ @@ -82,11 +108,50 @@ var fixtures = { "properties": {} } ] + }, + "multi-polygon": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[0, 0], [1, 0], [1, 1], [0, 0]]], [[[0, 0], [-1, 0], [-1, -1], [0, 0]]]] + }, + "properties": {} + } + ] + }, + "polygon-with-inner": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [[[-2, 2], [2, 2], [2, -2], [-2, -2], [-2, 2]], [[-1, 1], [1, 1], [1, -1], [-1, -1], [-1, 1]]] + }, + "properties": {} + } + ] + }, + "stacked-multipolygon": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[-2, 2], [2, 2], [2, -2], [-2, -2], [-2, 2]]], [[[-1, 1], [1, 1], [1, -1], [-1, -1], [-1, 1]]]] + }, + "properties": {} + } + ] } } for (var fixture in fixtures) { - var vtile = new mapnik.VectorTile(0,0,0); + var vtile = new mapnik.VectorTile(0, 0, 0); vtile.addGeoJSON(JSON.stringify(fixtures[fixture]), "geojson"); fs.writeFileSync('./test/fixtures/' + fixture + '.pbf', vtile.getData()); } diff --git a/lib/vectortilefeature.js b/lib/vectortilefeature.js index 0bef037..916053a 100644 --- a/lib/vectortilefeature.js +++ b/lib/vectortilefeature.js @@ -131,10 +131,10 @@ VectorTileFeature.prototype.toGeoJSON = function(x, y, z) { x0 = this.extent * x, y0 = this.extent * y, coords = this.loadGeometry(), - type = VectorTileFeature.types[this.type]; + type = VectorTileFeature.types[this.type], + i, j; - for (var i = 0; i < coords.length; i++) { - var line = coords[i]; + function project(line) { for (var j = 0; j < line.length; j++) { var p = line[j], y2 = 180 - (p.y + y0) * 360 / size; line[j] = [ @@ -144,15 +144,36 @@ VectorTileFeature.prototype.toGeoJSON = function(x, y, z) { } } - if (type === 'Point' && coords.length === 1) { - coords = coords[0][0]; - } else if (type === 'Point') { - coords = coords.map(function (c) { return c[0]; }); - type = 'MultiPoint'; - } else if (type === 'LineString' && coords.length === 1) { + switch (this.type) { + case 1: + var points = []; + for (i = 0; i < coords.length; i++) { + points[i] = coords[i][0]; + } + coords = points; + project(coords); + break; + + case 2: + for (i = 0; i < coords.length; i++) { + project(coords[i]); + } + break; + + case 3: + coords = classifyRings(coords); + for (i = 0; i < coords.length; i++) { + for (j = 0; j < coords[i].length; j++) { + project(coords[i][j]); + } + } + break; + } + + if (coords.length === 1) { coords = coords[0]; - } else if (type === 'LineString') { - type = 'MultiLineString'; + } else { + type = 'Multi' + type; } var result = { @@ -170,3 +191,43 @@ VectorTileFeature.prototype.toGeoJSON = function(x, y, z) { return result; }; + +// classifies an array of rings into polygons with outer rings and holes + +function classifyRings(rings) { + var len = rings.length; + + if (len <= 1) return [rings]; + + var polygons = [], + polygon, + ccw; + + for (var i = 0; i < len; i++) { + var area = signedArea(rings[i]); + if (area === 0) continue; + + if (ccw === undefined) ccw = area < 0; + + if (ccw === area < 0) { + if (polygon) polygons.push(polygon); + polygon = [rings[i]]; + + } else { + polygon.push(rings[i]); + } + } + if (polygon) polygons.push(polygon); + + return polygons; +} + +function signedArea(ring) { + var sum = 0; + for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) { + p1 = ring[i]; + p2 = ring[j]; + sum += (p2.x - p1.x) * (p1.y + p2.y); + } + return sum; +} diff --git a/package.json b/package.json index e9b2235..b53df6d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "benchmark": "^1.0.0", "coveralls": "~2.11.2", "istanbul": "~0.3.6", - "mapnik": "^3.1.6", + "mapnik": "^3.5.13", "jshint": "^2.6.3", "pbf": "^1.3.2", "tape": "~3.5.0", diff --git a/test/fixtures/multi-line.pbf b/test/fixtures/multi-line.pbf index 31f215b..a000816 100644 --- a/test/fixtures/multi-line.pbf +++ b/test/fixtures/multi-line.pbf @@ -1,4 +1,4 @@ -$ -geojson" – Ò +$x +geojson(€ " – Ò .- .+ -.-(€ x \ No newline at end of file +.- \ No newline at end of file diff --git a/test/fixtures/multi-point.pbf b/test/fixtures/multi-point.pbf index f2c7030..f49d513 100644 --- a/test/fixtures/multi-point.pbf +++ b/test/fixtures/multi-point.pbf @@ -1,2 +1,2 @@ - -geojson" – Ò .-(€ x \ No newline at end of file +x +geojson(€  "– Ò.- \ No newline at end of file diff --git a/test/fixtures/multi-polygon.pbf b/test/fixtures/multi-polygon.pbf new file mode 100644 index 0000000..0d81bd6 Binary files /dev/null and b/test/fixtures/multi-polygon.pbf differ diff --git a/test/fixtures/multipolygon.pbf b/test/fixtures/multipolygon.pbf new file mode 100644 index 0000000..171c073 Binary files /dev/null and b/test/fixtures/multipolygon.pbf differ diff --git a/test/fixtures/polygon-with-inner.pbf b/test/fixtures/polygon-with-inner.pbf new file mode 100644 index 0000000..5550fd8 Binary files /dev/null and b/test/fixtures/polygon-with-inner.pbf differ diff --git a/test/fixtures/singleton-multi-line.pbf b/test/fixtures/singleton-multi-line.pbf index 0615bae..8623527 100644 --- a/test/fixtures/singleton-multi-line.pbf +++ b/test/fixtures/singleton-multi-line.pbf @@ -1,3 +1,3 @@ - -geojson" – Ò -.-(€ x \ No newline at end of file +x +geojson(€ " – Ò +.- \ No newline at end of file diff --git a/test/fixtures/singleton-multi-point.pbf b/test/fixtures/singleton-multi-point.pbf index f2f2eed..1e08de9 100644 --- a/test/fixtures/singleton-multi-point.pbf +++ b/test/fixtures/singleton-multi-point.pbf @@ -1,2 +1,2 @@ - -geojson " – Ò(€ x \ No newline at end of file +x +geojson(€  " – Ò \ No newline at end of file diff --git a/test/fixtures/singleton-multi-polygon.pbf b/test/fixtures/singleton-multi-polygon.pbf new file mode 100644 index 0000000..a48ff0c Binary files /dev/null and b/test/fixtures/singleton-multi-polygon.pbf differ diff --git a/test/fixtures/stacked-multipolygon.pbf b/test/fixtures/stacked-multipolygon.pbf new file mode 100644 index 0000000..e039c4c Binary files /dev/null and b/test/fixtures/stacked-multipolygon.pbf differ diff --git a/test/fixtures/zero-polygon.pbf b/test/fixtures/zero-polygon.pbf new file mode 100644 index 0000000..e69de29 diff --git a/test/parse.test.js b/test/parse.test.js index 768f80f..6ecba23 100644 --- a/test/parse.test.js +++ b/test/parse.test.js @@ -150,6 +150,10 @@ test('parsing vector tiles', function(t) { type: 'LineString', coordinates: [[1, 2], [3, 4]] }, 1e-1)); + t.ok(approximateDeepEqual(geoJSONFromFixture("singleton-multi-polygon").geometry, { + type: 'Polygon', + coordinates: [[[1, 0], [0, 0], [1, 1], [1, 0]]] + }, 1e-1)); t.ok(approximateDeepEqual(geoJSONFromFixture("multi-point").geometry, { type: 'MultiPoint', @@ -159,6 +163,20 @@ test('parsing vector tiles', function(t) { type: 'MultiLineString', coordinates: [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] }, 1e-1)); + t.ok(approximateDeepEqual(geoJSONFromFixture("multi-polygon").geometry, { + type: 'MultiPolygon', + coordinates: [[[[1, 0], [0, 0], [1, 1], [1, 0]]], [[[-1, -1], [-1, 0], [0, 0], [-1, -1]]]] + }, 1e-1)); + + // https://github.com/mapbox/vector-tile-js/issues/32 + t.ok(approximateDeepEqual(geoJSONFromFixture("polygon-with-inner").geometry, { + type: 'Polygon', + coordinates: [[[2, -2], [-2, -2], [-2, 2], [2, 2], [2, -2]], [[-1, 1], [-1, -1], [1, -1], [1, 1], [-1, 1]]] + }, 1e-1)); + t.ok(approximateDeepEqual(geoJSONFromFixture("stacked-multipolygon").geometry, { + type: 'MultiPolygon', + coordinates: [[[[2, -2], [-2, -2], [-2, 2], [2, 2], [2, -2]]], [[[1, -1], [-1, -1], [-1, 1], [1, 1], [1, -1]]]] + }, 1e-1)); t.end(); })