itowns
Version:
A JS/WebGL framework for 3D geospatial data visualization
277 lines (232 loc) • 9.42 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _three = require("three");
var _pbf = _interopRequireDefault(require("pbf"));
var _vectorTile = require("@mapbox/vector-tile");
var _Extent = require("../Core/Geographic/Extent");
var _Feature = require("../Core/Feature");
var _mapboxGlStyleSpec = require("@mapbox/mapbox-gl-style-spec");
var _Style = _interopRequireDefault(require("../Core/Style"));
var worldDimension3857 = _Extent.globalExtentTMS.get('EPSG:3857').dimensions();
var globalExtent = new _three.Vector3(worldDimension3857.x, worldDimension3857.y, 1);
var lastPoint = new _three.Vector2();
var firstPoint = new _three.Vector2();
var styleCache = new Map(); // Classify option, it allows to classify a full polygon and its holes.
// Each polygon with its holes are in one FeatureGeometry.
// A polygon is determined by its clockwise direction and the holes are in the opposite direction.
// Clockwise direction is determined by Shoelace formula https://en.wikipedia.org/wiki/Shoelace_formula
// Draw polygon with canvas doesn't need to classify however it is necessary for meshs.
function vtFeatureToFeatureGeometry(vtFeature, feature) {
var classify = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var geometry = feature.bindNewGeometry();
classify = classify && feature.type === _Feature.FEATURE_TYPES.POLYGON;
geometry.properties = vtFeature.properties;
var pbf = vtFeature._pbf;
pbf.pos = vtFeature._geometry;
var end = pbf.readVarint() + pbf.pos;
var cmd = 1;
var length = 0;
var x = 0;
var y = 0;
var count = 0;
var sum = 0;
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) {
if (count) {
if (classify && sum > 0 && geometry.indices.length > 0) {
feature.updateExtent(geometry);
geometry = feature.bindNewGeometry();
geometry.properties = vtFeature.properties;
}
geometry.closeSubGeometry(count);
geometry.getLastSubGeometry().ccw = sum < 0;
}
count = 0;
sum = 0;
}
count++;
geometry.pushCoordinatesValues(x, y);
if (count == 1) {
firstPoint.set(x, y);
lastPoint.set(x, y);
} else if (classify && count > 1) {
sum += (lastPoint.x - x) * (lastPoint.y + y);
lastPoint.set(x, y);
}
} else if (cmd === 7) {
if (count) {
count++;
geometry.pushCoordinatesValues(firstPoint.x, firstPoint.y);
if (classify) {
sum += (lastPoint.x - firstPoint.x) * (lastPoint.y + firstPoint.y);
}
}
} else {
throw new Error("unknown command ".concat(cmd));
}
}
if (count) {
if (classify && sum > 0 && geometry.indices.length > 0) {
feature.updateExtent(geometry);
geometry = feature.bindNewGeometry();
geometry.properties = vtFeature.properties;
}
geometry.closeSubGeometry(count);
geometry.getLastSubGeometry().ccw = sum < 0;
}
feature.updateExtent(geometry);
}
var defaultFilter = function () {
return true;
};
function readPBF(file, options) {
var vectorTile = new _vectorTile.VectorTile(new _pbf["default"](file));
var extentSource = options.extentSource || file.extent;
var sourceLayers = Object.keys(vectorTile.layers);
if (sourceLayers.length < 1) {
return;
} // x,y,z tile coordinates
var x = extentSource.col;
var z = extentSource.zoom; // We need to move from TMS to Google/Bing/OSM coordinates
// https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/
// Only if the layer.origin is top
var y = options.isInverted ? extentSource.row : (1 << z) - extentSource.row - 1;
options.buildExtent = true;
options.mergeFeatures = true;
options.withAltitude = false;
options.withNormal = false;
var features = new _Feature.FeatureCollection('EPSG:3857', options); // TODO remove defaultFilter;
features.filter = options.filter || defaultFilter;
var vFeature = vectorTile.layers[sourceLayers[0]]; // TODO: verify if size is correct because is computed with only one feature (vFeature).
var size = vFeature.extent * Math.pow(2, z);
var center = -0.5 * size;
features.scale.set(size, -size, 1).divide(globalExtent);
features.translation.set(-(vFeature.extent * x + center), -(vFeature.extent * y + center), 0).divide(features.scale);
var allLayers = features.filter;
if (!features.filter.loaded) {
allLayers.forEach(function (l) {
l.filterExpression = (0, _mapboxGlStyleSpec.featureFilter)(l.filter);
});
features.filter.loaded = true;
}
sourceLayers.forEach(function (layer_id) {
var sourceLayer = vectorTile.layers[layer_id];
var layersSource = allLayers.filter(function (l) {
return sourceLayer.name == l['source-layer'];
});
var _loop = function (i) {
var vtFeature = sourceLayer.feature(i);
var layers = layersSource.filter(function (l) {
return l.filterExpression({
zoom: z
}, vtFeature) && (!l.minzoom || l.minzoom <= z) && (!l.maxzoom || l.maxzoom >= z);
});
var feature = void 0;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
var _loop2 = function () {
var layer = _step.value;
var tag = "".concat(layer.id, "_zoom_").concat(z); // Fix doens't pass instance of properties
var style = styleCache.get(tag);
if (!style) {
style = new _Style["default"]();
style.setFromVectorTileLayer(layer, extentSource.zoom, options.sprites);
styleCache.set(tag, style);
}
var order = allLayers.findIndex(function (l) {
return l.id == layer.id;
});
if (!feature) {
feature = features.requestFeatureById(layer.id, vtFeature.type - 1);
feature.id = layer.id;
feature.order = order;
feature.style = style;
vtFeatureToFeatureGeometry(vtFeature, feature);
} else if (!features.features.find(function (f) {
return f.id === layer.id;
})) {
feature = features.newFeatureByReference(feature);
feature.id = layer.id;
feature.order = order;
feature.style = style;
}
};
for (var _iterator = layers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
_loop2();
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
_iterator["return"]();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
};
for (var i = sourceLayer.length - 1; i >= 0; i--) {
_loop(i);
}
});
features.removeEmptyFeature(); // TODO verify if is needed to updateExtent for previous features.
features.updateExtent();
features.features.sort(function (a, b) {
return a.order - b.order;
});
features.extent = extentSource;
return Promise.resolve(features);
}
/**
* @module VectorTileParser
*/
var _default = {
/**
* Parse a vector tile file and return a [Feature]{@link module:GeoJsonParser.Feature}
* or an array of Features. While multiple formats of vector tile are
* available, the only one supported for the moment is the
* [Mapbox Vector Tile]{@link https://www.mapbox.com/vector-tiles/specification/}.
*
* @param {ArrayBuffer} file - The vector tile file to parse.
* @param {Object} options - Options controlling the parsing.
* @param {Extent} options.extent - The Extent to convert the input coordinates to.
* @param {Extent=} options.filteringExtent - Optional filter to reject features
* outside of this extent.
* @param {boolean} [options.mergeFeatures=true] - If true all geometries are merged by type and multi-type
* @param {boolean} [options.withNormal=true] - If true each coordinate normal is computed
* @param {boolean} [options.withAltitude=true] - If true each coordinate altitude is kept
* @param {function=} options.filter - Filter function to remove features.
* @param {string=} options.isInverted - This option is to be set to the
* correct value, true or false (default being false), if the computation of
* the coordinates needs to be inverted to same scheme as OSM, Google Maps
* or other system. See [this link]{@link
* https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates}
* for more informations.
*
* @return {Promise} A Promise resolving with a Feature or an array a
* Features.
*/
parse: function parse(file, options) {
return Promise.resolve(readPBF(file, options));
}
};
exports["default"] = _default;
;