modernize (ESM, modern deps, GitHub Actions)

This commit is contained in:
Vladimir Agafonkin 2024-07-10 22:58:17 +03:00
parent 77851380b6
commit 4b81485642
15 changed files with 1782 additions and 711 deletions

View File

@ -1,3 +0,0 @@
{
"extends": "eslint-config-unstyled"
}

19
.github/workflows/node.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Node
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test

View File

@ -1,7 +0,0 @@
language: node_js
sudo: false
node_js:
- 4
script:
- npm test
- npm run cov

View File

@ -1,5 +1,4 @@
Copyright (c) 2014, Mapbox
Copyright (c) 2024, Mapbox
All rights reserved.

36
bench.js Normal file
View File

@ -0,0 +1,36 @@
import Pbf from 'pbf';
import {VectorTile} from './index.js';
import Benchmark from 'benchmark';
import fs from 'fs';
const suite = new Benchmark.Suite();
const data = fs.readFileSync(new URL('test/fixtures/14-8801-5371.vector.pbf', import.meta.url));
readTile(); // output any errors before running the suite
readTile(true);
suite
.add('read tile with geometries', () => {
readTile(true);
})
.add('read tile without geometries', () => {
readTile();
})
.on('cycle', (event) => {
console.log(String(event.target));
})
.run();
function readTile(loadGeom) {
const buf = new Pbf(data),
vt = new VectorTile(buf);
for (const id in vt.layers) {
const layer = vt.layers[id];
for (let i = 0; i < layer.length; i++) {
const feature = layer.feature(i);
if (loadGeom) feature.loadGeometry();
}
}
}

10
eslint.config.js Normal file
View File

@ -0,0 +1,10 @@
import config from 'eslint-config-mourner';
export default [
...config,
{
rules: {
'no-case-declarations': 0
}
}
];

View File

@ -1,157 +0,0 @@
var mapnik = require('mapnik');
var path = require('path');
var fs = require('fs');
mapnik.register_datasource(path.join(mapnik.settings.paths.input_plugins, 'geojson.input'));
var fixtures = {
"zero-point": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiPoint",
"coordinates": []
},
"properties": {}
}
]
},
"zero-line": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiLineString",
"coordinates": []
},
"properties": {}
}
]
},
"zero-polygon": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": []
},
"properties": {}
}
]
},
"singleton-multi-point": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiPoint",
"coordinates": [[1, 2]]
},
"properties": {}
}
]
},
"singleton-multi-line": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiLineString",
"coordinates": [[[1, 2], [3, 4]]]
},
"properties": {}
}
]
},
"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": [
{
"type": "Feature",
"geometry": {
"type": "MultiPoint",
"coordinates": [[1, 2], [3, 4]]
},
"properties": {}
}
]
},
"multi-line": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiLineString",
"coordinates": [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
},
"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);
vtile.addGeoJSON(JSON.stringify(fixtures[fixture]), "geojson");
fs.writeFileSync('./test/fixtures/' + fixture + '.pbf', vtile.getData());
}

303
index.js
View File

@ -1,3 +1,300 @@
module.exports.VectorTile = require('./lib/vectortile.js');
module.exports.VectorTileFeature = require('./lib/vectortilefeature.js');
module.exports.VectorTileLayer = require('./lib/vectortilelayer.js');
import Point from '@mapbox/point-geometry';
export class VectorTileFeature {
constructor(pbf, end, extent, keys, values) {
// Public
this.properties = {};
this.extent = extent;
this.type = 0;
// Private
this._pbf = pbf;
this._geometry = -1;
this._keys = keys;
this._values = values;
pbf.readFields(readFeature, this, end);
}
loadGeometry() {
const pbf = this._pbf;
pbf.pos = this._geometry;
const end = pbf.readVarint() + pbf.pos;
const lines = [];
let cmd = 1,
length = 0,
x = 0,
y = 0,
line;
while (pbf.pos < end) {
if (length <= 0) {
const cmdLen = pbf.readVarint();
cmd = cmdLen & 0x7;
length = cmdLen >> 3;
}
length--;
if (cmd === 1 || cmd === 2) {
x += pbf.readSVarint();
y += pbf.readSVarint();
if (cmd === 1) { // moveTo
if (line) lines.push(line);
line = [];
}
line.push(new Point(x, y));
} else if (cmd === 7) {
// Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90
if (line) {
line.push(line[0].clone()); // closePolygon
}
} else {
throw new Error(`unknown command ${ cmd}`);
}
}
if (line) lines.push(line);
return lines;
}
bbox() {
const pbf = this._pbf;
pbf.pos = this._geometry;
const end = pbf.readVarint() + pbf.pos;
let cmd = 1,
length = 0,
x = 0,
y = 0,
x1 = Infinity,
x2 = -Infinity,
y1 = Infinity,
y2 = -Infinity;
while (pbf.pos < end) {
if (length <= 0) {
const cmdLen = pbf.readVarint();
cmd = cmdLen & 0x7;
length = cmdLen >> 3;
}
length--;
if (cmd === 1 || cmd === 2) {
x += pbf.readSVarint();
y += pbf.readSVarint();
if (x < x1) x1 = x;
if (x > x2) x2 = x;
if (y < y1) y1 = y;
if (y > y2) y2 = y;
} else if (cmd !== 7) {
throw new Error(`unknown command ${ cmd}`);
}
}
return [x1, y1, x2, y2];
}
toGeoJSON(x, y, z) {
const size = this.extent * Math.pow(2, z),
x0 = this.extent * x,
y0 = this.extent * y;
let type = VectorTileFeature.types[this.type],
coords = this.loadGeometry();
function project(line) {
for (let j = 0; j < line.length; j++) {
const p = line[j], y2 = 180 - (p.y + y0) * 360 / size;
line[j] = [
(p.x + x0) * 360 / size - 180,
360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90
];
}
}
switch (this.type) {
case 1:
const points = [];
for (let i = 0; i < coords.length; i++) {
points[i] = coords[i][0];
}
coords = points;
project(coords);
break;
case 2:
for (let i = 0; i < coords.length; i++) {
project(coords[i]);
}
break;
case 3:
coords = classifyRings(coords);
for (let i = 0; i < coords.length; i++) {
for (let j = 0; j < coords[i].length; j++) {
project(coords[i][j]);
}
}
break;
}
if (coords.length === 1) {
coords = coords[0];
} else {
type = `Multi${type}`;
}
const result = {
type: 'Feature',
geometry: {
type,
coordinates: coords
},
properties: this.properties
};
if ('id' in this) {
result.id = this.id;
}
return result;
}
}
VectorTileFeature.types = ['Unknown', 'Point', 'LineString', 'Polygon'];
function readFeature(tag, feature, pbf) {
if (tag === 1) feature.id = pbf.readVarint();
else if (tag === 2) readTag(pbf, feature);
else if (tag === 3) feature.type = pbf.readVarint();
else if (tag === 4) feature._geometry = pbf.pos;
}
function readTag(pbf, feature) {
const end = pbf.readVarint() + pbf.pos;
while (pbf.pos < end) {
const key = feature._keys[pbf.readVarint()],
value = feature._values[pbf.readVarint()];
feature.properties[key] = value;
}
}
// classifies an array of rings into polygons with outer rings and holes
export function classifyRings(rings) {
const len = rings.length;
if (len <= 1) return [rings];
const polygons = [];
let polygon, ccw;
for (let i = 0; i < len; i++) {
const 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) {
let sum = 0;
for (let 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;
}
export class VectorTileLayer {
constructor(pbf, end) {
// Public
this.version = 1;
this.name = null;
this.extent = 4096;
this.length = 0;
// Private
this._pbf = pbf;
this._keys = [];
this._values = [];
this._features = [];
pbf.readFields(readLayer, this, end);
this.length = this._features.length;
}
// return feature `i` from this layer as a `VectorTileFeature`
feature(i) {
if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds');
this._pbf.pos = this._features[i];
const end = this._pbf.readVarint() + this._pbf.pos;
return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values);
}
}
function readLayer(tag, layer, pbf) {
if (tag === 15) layer.version = pbf.readVarint();
else if (tag === 1) layer.name = pbf.readString();
else if (tag === 5) layer.extent = pbf.readVarint();
else if (tag === 2) layer._features.push(pbf.pos);
else if (tag === 3) layer._keys.push(pbf.readString());
else if (tag === 4) layer._values.push(readValueMessage(pbf));
}
function readValueMessage(pbf) {
let value = null;
const end = pbf.readVarint() + pbf.pos;
while (pbf.pos < end) {
const tag = pbf.readVarint() >> 3;
value = tag === 1 ? pbf.readString() :
tag === 2 ? pbf.readFloat() :
tag === 3 ? pbf.readDouble() :
tag === 4 ? pbf.readVarint64() :
tag === 5 ? pbf.readVarint() :
tag === 6 ? pbf.readSVarint() :
tag === 7 ? pbf.readBoolean() : null;
}
return value;
}
export class VectorTile {
constructor(pbf, end) {
this.layers = pbf.readFields(readTile, {}, end);
}
}
function readTile(tag, layers, pbf) {
if (tag === 3) {
const layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos);
if (layer.length) layers[layer.name] = layer;
}
}

View File

@ -1,17 +0,0 @@
'use strict';
var VectorTileLayer = require('./vectortilelayer');
module.exports = VectorTile;
function VectorTile(pbf, end) {
this.layers = pbf.readFields(readTile, {}, end);
}
function readTile(tag, layers, pbf) {
if (tag === 3) {
var layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos);
if (layer.length) layers[layer.name] = layer;
}
}

View File

@ -1,233 +0,0 @@
'use strict';
var Point = require('@mapbox/point-geometry');
module.exports = VectorTileFeature;
function VectorTileFeature(pbf, end, extent, keys, values) {
// Public
this.properties = {};
this.extent = extent;
this.type = 0;
// Private
this._pbf = pbf;
this._geometry = -1;
this._keys = keys;
this._values = values;
pbf.readFields(readFeature, this, end);
}
function readFeature(tag, feature, pbf) {
if (tag == 1) feature.id = pbf.readVarint();
else if (tag == 2) readTag(pbf, feature);
else if (tag == 3) feature.type = pbf.readVarint();
else if (tag == 4) feature._geometry = pbf.pos;
}
function readTag(pbf, feature) {
var end = pbf.readVarint() + pbf.pos;
while (pbf.pos < end) {
var key = feature._keys[pbf.readVarint()],
value = feature._values[pbf.readVarint()];
feature.properties[key] = value;
}
}
VectorTileFeature.types = ['Unknown', 'Point', 'LineString', 'Polygon'];
VectorTileFeature.prototype.loadGeometry = function() {
var pbf = this._pbf;
pbf.pos = this._geometry;
var end = pbf.readVarint() + pbf.pos,
cmd = 1,
length = 0,
x = 0,
y = 0,
lines = [],
line;
while (pbf.pos < end) {
if (length <= 0) {
var cmdLen = pbf.readVarint();
cmd = cmdLen & 0x7;
length = cmdLen >> 3;
}
length--;
if (cmd === 1 || cmd === 2) {
x += pbf.readSVarint();
y += pbf.readSVarint();
if (cmd === 1) { // moveTo
if (line) lines.push(line);
line = [];
}
line.push(new Point(x, y));
} else if (cmd === 7) {
// Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90
if (line) {
line.push(line[0].clone()); // closePolygon
}
} else {
throw new Error('unknown command ' + cmd);
}
}
if (line) lines.push(line);
return lines;
};
VectorTileFeature.prototype.bbox = function() {
var pbf = this._pbf;
pbf.pos = this._geometry;
var end = pbf.readVarint() + pbf.pos,
cmd = 1,
length = 0,
x = 0,
y = 0,
x1 = Infinity,
x2 = -Infinity,
y1 = Infinity,
y2 = -Infinity;
while (pbf.pos < end) {
if (length <= 0) {
var cmdLen = pbf.readVarint();
cmd = cmdLen & 0x7;
length = cmdLen >> 3;
}
length--;
if (cmd === 1 || cmd === 2) {
x += pbf.readSVarint();
y += pbf.readSVarint();
if (x < x1) x1 = x;
if (x > x2) x2 = x;
if (y < y1) y1 = y;
if (y > y2) y2 = y;
} else if (cmd !== 7) {
throw new Error('unknown command ' + cmd);
}
}
return [x1, y1, x2, y2];
};
VectorTileFeature.prototype.toGeoJSON = function(x, y, z) {
var size = this.extent * Math.pow(2, z),
x0 = this.extent * x,
y0 = this.extent * y,
coords = this.loadGeometry(),
type = VectorTileFeature.types[this.type],
i, j;
function project(line) {
for (var j = 0; j < line.length; j++) {
var p = line[j], y2 = 180 - (p.y + y0) * 360 / size;
line[j] = [
(p.x + x0) * 360 / size - 180,
360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90
];
}
}
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 {
type = 'Multi' + type;
}
var result = {
type: "Feature",
geometry: {
type: type,
coordinates: coords
},
properties: this.properties
};
if ('id' in this) {
result.id = this.id;
}
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;
}

View File

@ -1,61 +0,0 @@
'use strict';
var VectorTileFeature = require('./vectortilefeature.js');
module.exports = VectorTileLayer;
function VectorTileLayer(pbf, end) {
// Public
this.version = 1;
this.name = null;
this.extent = 4096;
this.length = 0;
// Private
this._pbf = pbf;
this._keys = [];
this._values = [];
this._features = [];
pbf.readFields(readLayer, this, end);
this.length = this._features.length;
}
function readLayer(tag, layer, pbf) {
if (tag === 15) layer.version = pbf.readVarint();
else if (tag === 1) layer.name = pbf.readString();
else if (tag === 5) layer.extent = pbf.readVarint();
else if (tag === 2) layer._features.push(pbf.pos);
else if (tag === 3) layer._keys.push(pbf.readString());
else if (tag === 4) layer._values.push(readValueMessage(pbf));
}
function readValueMessage(pbf) {
var value = null,
end = pbf.readVarint() + pbf.pos;
while (pbf.pos < end) {
var tag = pbf.readVarint() >> 3;
value = tag === 1 ? pbf.readString() :
tag === 2 ? pbf.readFloat() :
tag === 3 ? pbf.readDouble() :
tag === 4 ? pbf.readVarint64() :
tag === 5 ? pbf.readVarint() :
tag === 6 ? pbf.readSVarint() :
tag === 7 ? pbf.readBoolean() : null;
}
return value;
}
// return feature `i` from this layer as a `VectorTileFeature`
VectorTileLayer.prototype.feature = function(i) {
if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds');
this._pbf.pos = this._features[i];
var end = this._pbf.readVarint() + this._pbf.pos;
return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values);
};

1249
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,31 +3,24 @@
"description": "Parses vector tiles",
"repository": "https://github.com/mapbox/vector-tile-js.git",
"version": "1.3.1",
"type": "module",
"exports": "./index.js",
"license": "BSD-3-Clause",
"main": "index.js",
"dependencies": {
"@mapbox/point-geometry": "~0.1.0"
},
"devDependencies": {
"benchmark": "^1.0.0",
"coveralls": "~2.11.2",
"istanbul": "~0.3.6",
"mapnik": "~3.6.0",
"jshint": "^2.6.3",
"pbf": "^1.3.2",
"tape": "~3.5.0",
"eslint": "~1.00.0",
"eslint-config-unstyled": "^1.1.0"
},
"jshintConfig": {
"trailing": true,
"undef": true,
"unused": true,
"indent": 4,
"node": true
"benchmark": "^2.1.4",
"eslint": "^9.6.0",
"eslint-config-mourner": "^4.0.1",
"pbf": "^4.0.1"
},
"scripts": {
"test": "eslint lib index.js && jshint lib && tape test/parse.test.js",
"cov": "istanbul cover ./node_modules/.bin/tape test/parse.test.js && coveralls < ./coverage/lcov.info"
}
"lint": "eslint index.js test/*.js",
"pretest": "npm run lint",
"test": "node --test",
"cov": "node --test --experimental-test-coverage"
},
"files": []
}

View File

@ -1,36 +0,0 @@
var Pbf = require('pbf'),
VectorTile = require('..').VectorTile,
Benchmark = require('benchmark'),
fs = require('fs');
var suite = new Benchmark.Suite(),
data = fs.readFileSync(__dirname + '/fixtures/14-8801-5371.vector.Pbf');
readTile(); // output any errors before running the suite
readTile(true);
suite
.add('read tile with geometries', function() {
readTile(true);
})
.add('read tile without geometries', function() {
readTile();
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.run();
function readTile(loadGeom, loadPacked) {
var buf = new Pbf(data),
vt = new VectorTile(buf);
for (var id in vt.layers) {
var layer = vt.layers[id];
for (var i = 0; i < layer.length; i++) {
var feature = layer.feature(i);
if (loadGeom) feature.loadGeometry();
}
}
}

View File

@ -1,9 +1,16 @@
var test = require('tape'),
fs = require('fs'),
Protobuf = require('pbf'),
VectorTile = require('..').VectorTile,
VectorTileLayer = require('..').VectorTileLayer,
VectorTileFeature = require('..').VectorTileFeature;
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'fs';
import Protobuf from 'pbf';
import {VectorTile, VectorTileLayer, VectorTileFeature} from '../index.js';
import Point from '@mapbox/point-geometry';
function getFixtureTile(name) {
const data = fs.readFileSync(new URL(`fixtures/${name}.pbf`, import.meta.url));
return new VectorTile(new Protobuf(data));
}
const tile = getFixtureTile('14-8801-5371.vector');
function approximateDeepEqual(a, b, epsilon) {
epsilon = epsilon || 1e-6;
@ -15,211 +22,186 @@ function approximateDeepEqual(a, b, epsilon) {
if (a === null || typeof a !== 'object')
return a === b;
var ka = Object.keys(a);
var kb = Object.keys(b);
const ka = Object.keys(a);
const kb = Object.keys(b);
if (ka.length != kb.length)
if (ka.length !== kb.length)
return false;
ka.sort();
kb.sort();
for (var i = 0; i < ka.length; i++)
if (ka[i] != kb[i] || !approximateDeepEqual(a[ka[i]], b[ka[i]], epsilon))
for (let i = 0; i < ka.length; i++)
if (ka[i] !== kb[i] || !approximateDeepEqual(a[ka[i]], b[ka[i]], epsilon))
return false;
return true;
}
test('parsing vector tiles', function(t) {
var data = fs.readFileSync(__dirname + '/fixtures/14-8801-5371.vector.pbf');
test('should have all layers', () => {
assert.deepEqual(Object.keys(tile.layers), [
'landuse', 'waterway', 'water', 'barrier_line', 'building',
'landuse_overlay', 'tunnel', 'road', 'bridge', 'place_label',
'water_label', 'poi_label', 'road_label', 'waterway_label']);
});
t.test('should have all layers', function(t) {
var tile = new VectorTile(new Protobuf(data));
test('should extract the tags of a feature', () => {
assert.equal(tile.layers.poi_label.length, 558);
t.deepEqual(Object.keys(tile.layers), [
'landuse', 'waterway', 'water', 'barrier_line', 'building',
'landuse_overlay', 'tunnel', 'road', 'bridge', 'place_label',
'water_label', 'poi_label', 'road_label', 'waterway_label' ]);
const park = tile.layers.poi_label.feature(11);
t.end();
});
assert.deepEqual(park.bbox(), [3898, 1731, 3898, 1731]);
t.test('should extract the tags of a feature', function(t) {
var tile = new VectorTile(new Protobuf(data));
assert.throws(() => {
tile.layers.poi_label.feature(1e9);
}, 'throws on reading a feature out of bounds');
t.equal(tile.layers.poi_label.length, 558);
assert.equal(park.id, 3000003150561);
var park = tile.layers.poi_label.feature(11);
assert.equal(park.properties.name, 'Mauerpark');
assert.equal(park.properties.type, 'Park');
t.deepEqual(park.bbox(), [ 3898, 1731, 3898, 1731 ]);
// Check point geometry
assert.deepEqual(park.loadGeometry(), [[new Point(3898, 1731)]]);
t.throws(function() {
var park = tile.layers.poi_label.feature(1e9);
}, 'throws on reading a feature out of bounds');
// Check line geometry
assert.deepEqual(tile.layers.road.feature(656).loadGeometry(), [[new Point(1988, 306), new Point(1808, 321), new Point(1506, 347)]]);
});
t.equal(park.id, 3000003150561);
test('changing first point of a polygon should not change last point', () => {
const building = tile.layers.building.feature(0).loadGeometry();
assert.deepEqual(building, [[new Point(2039, -32), new Point(2035, -31), new Point(2032, -31), new Point(2032, -32), new Point(2039, -32)]]);
building[0][0].x = 1;
building[0][0].y = 2;
building[0][1].x = 3;
building[0][1].y = 4;
assert.deepEqual(building, [[new Point(1, 2), new Point(3, 4), new Point(2032, -31), new Point(2032, -32), new Point(2039, -32)]]);
});
t.equal(park.properties.name, 'Mauerpark');
t.equal(park.properties.type, 'Park');
// Check point geometry
t.deepEqual(park.loadGeometry(), [ [ { x: 3898, y: 1731 } ] ]);
// Check line geometry
t.deepEqual(tile.layers.road.feature(656).loadGeometry(), [ [ { x: 1988, y: 306 }, { x: 1808, y: 321 }, { x: 1506, y: 347 } ] ]);
t.end();
});
t.test('changing first point of a polygon should not change last point', function(t) {
var tile = new VectorTile(new Protobuf(data));
var building = tile.layers.building.feature(0).loadGeometry();
t.deepEqual(building, [ [ { x: 2039, y: -32 }, { x: 2035, y: -31 }, { x: 2032, y: -31 }, { x: 2032, y: -32 }, { x: 2039, y: -32 } ] ]);
building[0][0].x = 1;
building[0][0].y = 2;
building[0][1].x = 3;
building[0][1].y = 4;
t.deepEqual(building, [ [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 2032, y: -31 }, { x: 2032, y: -32 }, { x: 2039, y: -32 } ] ]);
t.end();
});
t.test('toGeoJSON', function(t) {
var tile = new VectorTile(new Protobuf(data));
t.ok(approximateDeepEqual(tile.layers.poi_label.feature(11).toGeoJSON(8801, 5371, 14), {
type: 'Feature',
id: 3000003150561,
properties: {
localrank: 1,
maki: 'park',
name: 'Mauerpark',
name_de: 'Mauerpark',
name_en: 'Mauerpark',
name_es: 'Mauerpark',
name_fr: 'Mauerpark',
osm_id: 3000003150561,
ref: '',
scalerank: 2,
type: 'Park'
},
geometry: {
type: 'Point',
coordinates: [13.402258157730103, 52.54398925380624]
}
}));
t.ok(approximateDeepEqual(tile.layers.bridge.feature(0).toGeoJSON(8801, 5371, 14), {
type: 'Feature',
id: 238162948,
properties: {
class: 'service',
oneway: 0,
osm_id: 238162948,
type: 'service'
},
geometry: {
type: 'LineString',
coordinates: [[13.399457931518555, 52.546334844036416], [13.399441838264465, 52.546504478525016]]
}
}));
t.ok(approximateDeepEqual(tile.layers.building.feature(0).toGeoJSON(8801, 5371, 14), {
type: 'Feature',
id: 1000267229912,
properties: {
osm_id: 1000267229912
},
geometry: {
type: 'Polygon',
coordinates: [[[13.392285704612732, 52.54974045706258], [13.392264246940613, 52.549737195107554],
[13.392248153686523, 52.549737195107554], [13.392248153686523, 52.54974045706258],
[13.392285704612732, 52.54974045706258]]]
}
}));
function geoJSONFromFixture(name) {
var tile = new VectorTile(new Protobuf(fs.readFileSync(__dirname + '/fixtures/' + name + '.pbf')));
return tile.layers.geojson.feature(0).toGeoJSON(0, 0, 0);
}
// https://github.com/mapbox/vector-tile-spec/issues/30
t.ok(approximateDeepEqual(geoJSONFromFixture("singleton-multi-point").geometry, {
test('toGeoJSON', () => {
assert.ok(approximateDeepEqual(tile.layers.poi_label.feature(11).toGeoJSON(8801, 5371, 14), {
type: 'Feature',
id: 3000003150561,
properties: {
localrank: 1,
maki: 'park',
name: 'Mauerpark',
'name_de': 'Mauerpark',
'name_en': 'Mauerpark',
'name_es': 'Mauerpark',
'name_fr': 'Mauerpark',
'osm_id': 3000003150561,
ref: '',
scalerank: 2,
type: 'Park'
},
geometry: {
type: 'Point',
coordinates: [1, 2]
}, 1e-1));
t.ok(approximateDeepEqual(geoJSONFromFixture("singleton-multi-line").geometry, {
coordinates: [13.402258157730103, 52.54398925380624]
}
}));
assert.ok(approximateDeepEqual(tile.layers.bridge.feature(0).toGeoJSON(8801, 5371, 14), {
type: 'Feature',
id: 238162948,
properties: {
class: 'service',
oneway: 0,
'osm_id': 238162948,
type: 'service'
},
geometry: {
type: 'LineString',
coordinates: [[1, 2], [3, 4]]
}, 1e-1));
t.ok(approximateDeepEqual(geoJSONFromFixture("singleton-multi-polygon").geometry, {
coordinates: [[13.399457931518555, 52.546334844036416], [13.399441838264465, 52.546504478525016]]
}
}));
assert.ok(approximateDeepEqual(tile.layers.building.feature(0).toGeoJSON(8801, 5371, 14), {
type: 'Feature',
id: 1000267229912,
properties: {
'osm_id': 1000267229912
},
geometry: {
type: 'Polygon',
coordinates: [[[1, 0], [0, 0], [1, 1], [1, 0]]]
}, 1e-1));
coordinates: [[[13.392285704612732, 52.54974045706258], [13.392264246940613, 52.549737195107554],
[13.392248153686523, 52.549737195107554], [13.392248153686523, 52.54974045706258],
[13.392285704612732, 52.54974045706258]]]
}
}));
t.ok(approximateDeepEqual(geoJSONFromFixture("multi-point").geometry, {
type: 'MultiPoint',
coordinates: [[1, 2], [3, 4]]
}, 1e-1));
t.ok(approximateDeepEqual(geoJSONFromFixture("multi-line").geometry, {
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));
function geoJSONFromFixture(name) {
const tile = getFixtureTile(name);
return tile.layers.geojson.feature(0).toGeoJSON(0, 0, 0);
}
// 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));
// https://github.com/mapbox/vector-tile-spec/issues/30
assert.ok(approximateDeepEqual(geoJSONFromFixture('singleton-multi-point').geometry, {
type: 'Point',
coordinates: [1, 2]
}, 1e-1));
assert.ok(approximateDeepEqual(geoJSONFromFixture('singleton-multi-line').geometry, {
type: 'LineString',
coordinates: [[1, 2], [3, 4]]
}, 1e-1));
assert.ok(approximateDeepEqual(geoJSONFromFixture('singleton-multi-polygon').geometry, {
type: 'Polygon',
coordinates: [[[1, 0], [0, 0], [1, 1], [1, 0]]]
}, 1e-1));
assert.ok(approximateDeepEqual(geoJSONFromFixture('multi-point').geometry, {
type: 'MultiPoint',
coordinates: [[1, 2], [3, 4]]
}, 1e-1));
assert.ok(approximateDeepEqual(geoJSONFromFixture('multi-line').geometry, {
type: 'MultiLineString',
coordinates: [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
}, 1e-1));
assert.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
assert.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));
assert.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();
})
});
test('VectorTileLayer', function(t) {
var emptyLayer = new VectorTileLayer(new Protobuf(new Buffer([])));
t.ok(emptyLayer, 'can be created with no values');
t.end();
test('VectorTileLayer', () => {
const emptyLayer = new VectorTileLayer(new Protobuf(Buffer.alloc(0)));
assert.ok(emptyLayer, 'can be created with no values');
});
test('VectorTileFeature', function(t) {
var emptyFeature = new VectorTileFeature(new Protobuf(new Buffer([])));
t.ok(emptyFeature, 'can be created with no values');
t.ok(Array.isArray(VectorTileFeature.types));
t.deepEqual(VectorTileFeature.types, ['Unknown', 'Point', 'LineString', 'Polygon']);
t.end();
test('VectorTileFeature', () => {
const emptyFeature = new VectorTileFeature(new Protobuf(Buffer.alloc(0)));
assert.ok(emptyFeature, 'can be created with no values');
assert.ok(Array.isArray(VectorTileFeature.types));
assert.deepEqual(VectorTileFeature.types, ['Unknown', 'Point', 'LineString', 'Polygon']);
});
test('https://github.com/mapbox/vector-tile-js/issues/15', function(t) {
var data = fs.readFileSync(__dirname + '/fixtures/lots-of-tags.vector.pbf');
var tile = new VectorTile(new Protobuf(data));
t.ok(tile.layers["stuttgart-rails"].feature(0));
t.end();
test('https://github.com/mapbox/vector-tile-js/issues/15', () => {
const tile = getFixtureTile('lots-of-tags.vector');
assert.ok(tile.layers['stuttgart-rails'].feature(0));
});
test('https://github.com/mapbox/mapbox-gl-js/issues/1019', function(t) {
var data = fs.readFileSync(__dirname + '/fixtures/12-1143-1497.vector.pbf');
var tile = new VectorTile(new Protobuf(data));
t.ok(tile.layers["water"].feature(1).loadGeometry());
t.end();
test('https://github.com/mapbox/mapbox-gl-js/issues/1019', () => {
const tile = getFixtureTile('12-1143-1497.vector');
assert.ok(tile.layers.water.feature(1).loadGeometry());
});
test('https://github.com/mapbox/vector-tile-js/issues/60', function(t) {
var data = fs.readFileSync(__dirname + '/fixtures/multipolygon-with-closepath.pbf');
var tile = new VectorTile(new Protobuf(data));
for (var id in tile.layers) {
var layer = tile.layers[id];
for (var i = 0; i < layer.length; i++) {
test('https://github.com/mapbox/vector-tile-js/issues/60', () => {
const tile = getFixtureTile('multipolygon-with-closepath');
for (const id in tile.layers) {
const layer = tile.layers[id];
for (let i = 0; i < layer.length; i++) {
layer.feature(i).loadGeometry();
}
}
t.end();
});