@mapgis/geojson-vt
Version:
Slice GeoJSON data into vector tiles efficiently
1,064 lines (896 loc) • 34.4 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('proj4')) :
typeof define === 'function' && define.amd ? define(['proj4'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.geojsonvt = factory(global.proj4));
})(this, (function (proj4) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var proj4__default = /*#__PURE__*/_interopDefaultLegacy(proj4);
/**
* @description: 投影坐标系对象
*/
var Projection = function Projection(code, def, bounds, width, height) {
this.bounds = bounds;
this.width = width;
this.height = height;
if (!def) { throw new Error('自定义参考系必须指定参数系信息def'); }
// 验证proj4库是否存在
if (!proj4__default["default"]) { throw new Error('proj4对象不存在,自定义投影必须引入proj4对象'); }
var isP4 = this._isProj4Obj(code);
this._proj = isP4 ? code : this._projFromCodeDef(code, def);
};
/**
* @description: 投影方法
* @param {Array|Object} lnglat
* @return {*}
*/
Projection.prototype.project = function project (lnglat) {
var lng, lat;
if (Array.isArray(lnglat)) {
lng = lnglat[0];
lat = lnglat[1];
} else {
lng = lnglat.lng;
lat = lnglat.lat;
}
var coords = this._proj.forward([lng, lat]);
var bounds = this.bounds;
var x = (coords[0] - bounds[0]) / this.width;
var y = (bounds[3] - coords[1]) / this.height;
return [x, y];
};
/**
* @description: 反投影方法
* @param {Array|Object} point
* @return {*}
*/
Projection.prototype.unproject = function unproject (point) {
var x, y;
if (Array.isArray(point)) {
x = point[0];
y = point[1];
} else {
x = point.x;
y = point.y;
}
return this._proj.inverse([x, y]);
};
/**
* @description: 定义proj投影
* @param {*} code
* @param {*} def
* @return {*}
*/
Projection.prototype._projFromCodeDef = function _projFromCodeDef (code, def) {
if (def) {
proj4__default["default"].defs(code, def);
} else if (proj4__default["default"].defs[code] === undefined) {
var urn = code.split(':');
if (urn.length > 3) {
code = (urn[urn.length - 3]) + ":" + (urn[urn.length - 1]);
}
if (proj4__default["default"].defs[code] === undefined) {
throw new Error(("No projection definition for code " + code));
}
}
return proj4__default["default"](code);
};
/**
* @description: 判断是否是proj4
* @param {*} a
* @return {*}
*/
Projection.prototype._isProj4Obj = function _isProj4Obj (a) {
return (typeof a.inverse !== 'undefined' &&
typeof a.forward !== 'undefined');
};
// calculate simplification data using optimized Douglas-Peucker algorithm
function simplify(coords, first, last, sqTolerance) {
var maxSqDist = sqTolerance;
var mid = (last - first) >> 1;
var minPosToMid = last - first;
var index;
var ax = coords[first];
var ay = coords[first + 1];
var bx = coords[last];
var by = coords[last + 1];
for (var i = first + 3; i < last; i += 3) {
var d = getSqSegDist(coords[i], coords[i + 1], ax, ay, bx, by);
if (d > maxSqDist) {
index = i;
maxSqDist = d;
} else if (d === maxSqDist) {
// a workaround to ensure we choose a pivot close to the middle of the list,
// reducing recursion depth, for certain degenerate inputs
// https://github.com/mapbox/geojson-vt/issues/104
var posToMid = Math.abs(i - mid);
if (posToMid < minPosToMid) {
index = i;
minPosToMid = posToMid;
}
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 3) { simplify(coords, first, index, sqTolerance); }
coords[index + 2] = maxSqDist;
if (last - index > 3) { simplify(coords, index, last, sqTolerance); }
}
}
// square distance from a point to a segment
function getSqSegDist(px, py, x, y, bx, by) {
var dx = bx - x;
var dy = by - y;
if (dx !== 0 || dy !== 0) {
var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = bx;
y = by;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = px - x;
dy = py - y;
return dx * dx + dy * dy;
}
function createFeature(id, type, geom, tags) {
var feature = {
id: id == null ? null : id,
type: type,
geometry: geom,
tags: tags,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};
if (type === 'Point' || type === 'MultiPoint' || type === 'LineString') {
calcLineBBox(feature, geom);
} else if (type === 'Polygon') {
// the outer ring (ie [0]) contains all inner rings
calcLineBBox(feature, geom[0]);
} else if (type === 'MultiLineString') {
for (var i = 0, list = geom; i < list.length; i += 1) {
var line = list[i];
calcLineBBox(feature, line);
}
} else if (type === 'MultiPolygon') {
for (var i$1 = 0, list$1 = geom; i$1 < list$1.length; i$1 += 1) {
// the outer ring (ie [0]) contains all inner rings
var polygon = list$1[i$1];
calcLineBBox(feature, polygon[0]);
}
}
return feature;
}
function calcLineBBox(feature, geom) {
for (var i = 0; i < geom.length; i += 3) {
feature.minX = Math.min(feature.minX, geom[i]);
feature.minY = Math.min(feature.minY, geom[i + 1]);
feature.maxX = Math.max(feature.maxX, geom[i]);
feature.maxY = Math.max(feature.maxY, geom[i + 1]);
}
}
// converts GeoJSON feature into an intermediate projected JSON vector format with simplification data
function convert(data, options) {
var features = [];
if (data.type === 'FeatureCollection') {
for (var i = 0; i < data.features.length; i++) {
convertFeature(features, data.features[i], options, i);
}
} else if (data.type === 'Feature') {
convertFeature(features, data, options);
} else {
// single geometry or a geometry collection
convertFeature(features, {geometry: data}, options);
}
return features;
}
/**
* 转换要素,主要是坐标投影变换
* @param {*} features 传回外部的数据
* @param {*} geojson 要处理的geojson数据
* @param {*} options 参数
* @param {*} options.crs 投影参数, 要区分4326以及3857
* @param {*} options.projectionParams 详细投影参数
* @param {*} index 处理的geojson数据的序号
*/
function convertFeature(features, geojson, options, index) {
if (!geojson.geometry) { return; }
var crs = options.crs;
var projectionParams = options.projectionParams;
var projection;
if (projectionParams && projectionParams.def) {
// projectionParams ={
// "code": "EPSG:4547",
// "def": "+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs +type=crs",
// "originX": 401635,
// "originY": 3542709,
// "width": 216746.66666666663,
// "height": 216746.6666666665,
// "bounds": [
// 401635,
// 3325962.3333333335,
// 618381.6666666666,
// 3542709
// ]
// }
var code = projectionParams.code;
var def = projectionParams.def;
var bounds = projectionParams.bounds;
var width = projectionParams.width;
var height = projectionParams.height;
projection = new Projection(code, def, bounds, width, height);
}
var coords = geojson.geometry.coordinates;
var type = geojson.geometry.type;
var tolerance = Math.pow(
options.tolerance / ((1 << options.maxZoom) * options.extent),
2
);
var geometry = [];
var id = geojson.id;
if (options.promoteId) {
id = geojson.properties[options.promoteId];
} else if (options.generateId) {
id = index || 0;
}
if (type === 'Point') {
convertPoint(coords, geometry, crs, projection);
} else if (type === 'MultiPoint') {
for (var i = 0, list = coords; i < list.length; i += 1) {
var p = list[i];
convertPoint(p, geometry, crs, projection);
}
} else if (type === 'LineString') {
convertLine(coords, geometry, tolerance, false, crs, projection);
} else if (type === 'MultiLineString') {
if (options.lineMetrics) {
// explode into linestrings to be able to track metrics
for (var i$1 = 0, list$1 = coords; i$1 < list$1.length; i$1 += 1) {
var line = list$1[i$1];
geometry = [];
convertLine(line, geometry, tolerance, false, crs, projection);
features.push(
createFeature(id, 'LineString', geometry, geojson.properties)
);
}
return;
} else {
convertLines(coords, geometry, tolerance, false, crs, projection);
}
} else if (type === 'Polygon') {
convertLines(coords, geometry, tolerance, true, crs, projection);
} else if (type === 'MultiPolygon') {
for (var i$2 = 0, list$2 = coords; i$2 < list$2.length; i$2 += 1) {
var polygon = list$2[i$2];
var newPolygon = [];
convertLines(polygon, newPolygon, tolerance, true, crs, projection);
geometry.push(newPolygon);
}
} else if (type === 'GeometryCollection') {
for (var i$3 = 0, list$3 = geojson.geometry.geometries; i$3 < list$3.length; i$3 += 1) {
var singleGeometry = list$3[i$3];
convertFeature(
features,
{
id: id,
geometry: singleGeometry,
properties: geojson.properties,
},
options,
index
);
}
return;
} else {
throw new Error('Input data is not a valid GeoJSON object.');
}
features.push(createFeature(id, type, geometry, geojson.properties));
}
function convertPoint(coords, out, crs, projection) {
var coord = project(coords, crs, projection);
out.push(
coord[0],
coord[1],
0
);
}
function convertLine(ring, out, tolerance, isPolygon, crs, projection) {
var x0, y0;
var size = 0;
for (var j = 0; j < ring.length; j++) {
var ref = project(ring[j], crs, projection);
var x = ref[0];
var y = ref[1];
out.push(x, y, 0);
if (j > 0) {
if (isPolygon) {
size += (x0 * y - x * y0) / 2; // area
} else {
size += Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)); // length
}
}
x0 = x;
y0 = y;
}
var last = out.length - 3;
out[2] = 1;
simplify(out, 0, last, tolerance);
out[last + 2] = 1;
out.size = Math.abs(size);
out.start = 0;
out.end = out.size;
}
function convertLines(rings, out, tolerance, isPolygon, crs, projection) {
for (var i = 0; i < rings.length; i++) {
var geom = [];
convertLine(rings[i], geom, tolerance, isPolygon, crs, projection);
out.push(geom);
}
}
/**
* 投影转换,将经纬度转为矩阵范围
* @param {Array} lnglatArr
* @param {*} crs
* @param {*} projection
* @returns 返回投影后的坐标,注意这里的坐标不是平面坐标值,而是矩阵值,即 [0 ~ 1]之间
*/
function project(lnglatArr, crs, projection) {
var lng = lnglatArr[0];
var lat = lnglatArr[1];
var coords = [];
if (projection) {
coords = projection.project([lng, lat]);
} else if (crs === 'EPSG:3857') {
var x = lnglatArr[0];
var y = lnglatArr[1];
var transformX = x / 360 + 0.5;
var sin = Math.sin((y * Math.PI) / 180);
var transformY = 0.5 - (0.25 * Math.log((1 + sin) / (1 - sin))) / Math.PI;
coords = [transformX, transformY];
} else if (crs === 'EPSG:4326') {
var x$1 = lnglatArr[0];
var y$1 = lnglatArr[1];
var transformX$1 = x$1 / 360 + 0.5;
var transformY$1 = (90 - y$1) / 360;
coords = [transformX$1, transformY$1];
} else {
throw new Error('未预定义投影转换方法');
}
return coords
}
/* clip features between two vertical or horizontal axis-parallel lines:
* | |
* ___|___ | /
* / | \____|____/
* | |
*
* k1 and k2 are the line coordinates
* axis: 0 for x, 1 for y
* minAll and maxAll: minimum and maximum coordinate value for all features
*/
function clip(features, scale, k1, k2, axis, minAll, maxAll, options) {
k1 /= scale;
k2 /= scale;
if (minAll >= k1 && maxAll < k2) { return features; } // trivial accept
else if (maxAll < k1 || minAll >= k2) { return null; } // trivial reject
var clipped = [];
for (var i$2 = 0, list$2 = features; i$2 < list$2.length; i$2 += 1) {
var feature = list$2[i$2];
var geometry = feature.geometry;
var type = feature.type;
var min = axis === 0 ? feature.minX : feature.minY;
var max = axis === 0 ? feature.maxX : feature.maxY;
if (min >= k1 && max < k2) { // trivial accept
clipped.push(feature);
continue;
} else if (max < k1 || min >= k2) { // trivial reject
continue;
}
var newGeometry = [];
if (type === 'Point' || type === 'MultiPoint') {
clipPoints(geometry, newGeometry, k1, k2, axis);
} else if (type === 'LineString') {
clipLine(geometry, newGeometry, k1, k2, axis, false, options.lineMetrics);
} else if (type === 'MultiLineString') {
clipLines(geometry, newGeometry, k1, k2, axis, false);
} else if (type === 'Polygon') {
clipLines(geometry, newGeometry, k1, k2, axis, true);
} else if (type === 'MultiPolygon') {
for (var i = 0, list = geometry; i < list.length; i += 1) {
var polygon = list[i];
var newPolygon = [];
clipLines(polygon, newPolygon, k1, k2, axis, true);
if (newPolygon.length) {
newGeometry.push(newPolygon);
}
}
}
if (newGeometry.length) {
if (options.lineMetrics && type === 'LineString') {
for (var i$1 = 0, list$1 = newGeometry; i$1 < list$1.length; i$1 += 1) {
var line = list$1[i$1];
clipped.push(createFeature(feature.id, type, line, feature.tags));
}
continue;
}
if (type === 'LineString' || type === 'MultiLineString') {
if (newGeometry.length === 1) {
type = 'LineString';
newGeometry = newGeometry[0];
} else {
type = 'MultiLineString';
}
}
if (type === 'Point' || type === 'MultiPoint') {
type = newGeometry.length === 3 ? 'Point' : 'MultiPoint';
}
clipped.push(createFeature(feature.id, type, newGeometry, feature.tags));
}
}
return clipped.length ? clipped : null;
}
function clipPoints(geom, newGeom, k1, k2, axis) {
for (var i = 0; i < geom.length; i += 3) {
var a = geom[i + axis];
if (a >= k1 && a <= k2) {
addPoint(newGeom, geom[i], geom[i + 1], geom[i + 2]);
}
}
}
function clipLine(geom, newGeom, k1, k2, axis, isPolygon, trackMetrics) {
var slice = newSlice(geom);
var intersect = axis === 0 ? intersectX : intersectY;
var len = geom.start;
var segLen, t;
for (var i = 0; i < geom.length - 3; i += 3) {
var ax$1 = geom[i];
var ay$1 = geom[i + 1];
var az$1 = geom[i + 2];
var bx = geom[i + 3];
var by = geom[i + 4];
var a$1 = axis === 0 ? ax$1 : ay$1;
var b = axis === 0 ? bx : by;
var exited = false;
if (trackMetrics) { segLen = Math.sqrt(Math.pow(ax$1 - bx, 2) + Math.pow(ay$1 - by, 2)); }
if (a$1 < k1) {
// ---|--> | (line enters the clip region from the left)
if (b > k1) {
t = intersect(slice, ax$1, ay$1, bx, by, k1);
if (trackMetrics) { slice.start = len + segLen * t; }
}
} else if (a$1 > k2) {
// | <--|--- (line enters the clip region from the right)
if (b < k2) {
t = intersect(slice, ax$1, ay$1, bx, by, k2);
if (trackMetrics) { slice.start = len + segLen * t; }
}
} else {
addPoint(slice, ax$1, ay$1, az$1);
}
if (b < k1 && a$1 >= k1) {
// <--|--- | or <--|-----|--- (line exits the clip region on the left)
t = intersect(slice, ax$1, ay$1, bx, by, k1);
exited = true;
}
if (b > k2 && a$1 <= k2) {
// | ---|--> or ---|-----|--> (line exits the clip region on the right)
t = intersect(slice, ax$1, ay$1, bx, by, k2);
exited = true;
}
if (!isPolygon && exited) {
if (trackMetrics) { slice.end = len + segLen * t; }
newGeom.push(slice);
slice = newSlice(geom);
}
if (trackMetrics) { len += segLen; }
}
// add the last point
var last = geom.length - 3;
var ax = geom[last];
var ay = geom[last + 1];
var az = geom[last + 2];
var a = axis === 0 ? ax : ay;
if (a >= k1 && a <= k2) { addPoint(slice, ax, ay, az); }
// close the polygon if its endpoints are not the same after clipping
last = slice.length - 3;
if (isPolygon && last >= 3 && (slice[last] !== slice[0] || slice[last + 1] !== slice[1])) {
addPoint(slice, slice[0], slice[1], slice[2]);
}
// add the final slice
if (slice.length) {
newGeom.push(slice);
}
}
function newSlice(line) {
var slice = [];
slice.size = line.size;
slice.start = line.start;
slice.end = line.end;
return slice;
}
function clipLines(geom, newGeom, k1, k2, axis, isPolygon) {
for (var i = 0, list = geom; i < list.length; i += 1) {
var line = list[i];
clipLine(line, newGeom, k1, k2, axis, isPolygon, false);
}
}
function addPoint(out, x, y, z) {
out.push(x, y, z);
}
function intersectX(out, ax, ay, bx, by, x) {
var t = (x - ax) / (bx - ax);
addPoint(out, x, ay + (by - ay) * t, 1);
return t;
}
function intersectY(out, ax, ay, bx, by, y) {
var t = (y - ay) / (by - ay);
addPoint(out, ax + (bx - ax) * t, y, 1);
return t;
}
// Transforms the coordinates of each feature in the given tile from
// mercator-projected space into (extent x extent) tile space.
function transformTile(tile, extent) {
if (tile.transformed) { return tile; }
var z2 = 1 << tile.z;
var tx = tile.x;
var ty = tile.y;
for (var i = 0, list = tile.features; i < list.length; i += 1) {
var feature = list[i];
var geom = feature.geometry;
var type = feature.type;
feature.geometry = [];
if (type === 1) {
for (var j = 0; j < geom.length; j += 2) {
feature.geometry.push(transformPoint(geom[j], geom[j + 1], extent, z2, tx, ty));
}
} else {
for (var j$1 = 0; j$1 < geom.length; j$1++) {
var ring = [];
for (var k = 0; k < geom[j$1].length; k += 2) {
ring.push(transformPoint(geom[j$1][k], geom[j$1][k + 1], extent, z2, tx, ty));
}
feature.geometry.push(ring);
}
}
}
tile.transformed = true;
return tile;
}
function transformPoint(x, y, extent, z2, tx, ty) {
return [
Math.round(extent * (x * z2 - tx)),
Math.round(extent * (y * z2 - ty))];
}
function createTile(features, z, tx, ty, options) {
var tolerance = z === options.maxZoom ? 0 : options.tolerance / ((1 << z) * options.extent);
var tile = {
features: [],
numPoints: 0,
numSimplified: 0,
numFeatures: features.length,
source: null,
x: tx,
y: ty,
z: z,
transformed: false,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};
for (var i = 0, list = features; i < list.length; i += 1) {
var feature = list[i];
addFeature(tile, feature, tolerance, options);
}
return tile;
}
function addFeature(tile, feature, tolerance, options) {
var geom = feature.geometry;
var type = feature.type;
var simplified = [];
tile.minX = Math.min(tile.minX, feature.minX);
tile.minY = Math.min(tile.minY, feature.minY);
tile.maxX = Math.max(tile.maxX, feature.maxX);
tile.maxY = Math.max(tile.maxY, feature.maxY);
if (type === 'Point' || type === 'MultiPoint') {
for (var i = 0; i < geom.length; i += 3) {
simplified.push(geom[i], geom[i + 1]);
tile.numPoints++;
tile.numSimplified++;
}
} else if (type === 'LineString') {
addLine(simplified, geom, tile, tolerance, false, false);
} else if (type === 'MultiLineString' || type === 'Polygon') {
for (var i$1 = 0; i$1 < geom.length; i$1++) {
addLine(simplified, geom[i$1], tile, tolerance, type === 'Polygon', i$1 === 0);
}
} else if (type === 'MultiPolygon') {
for (var k = 0; k < geom.length; k++) {
var polygon = geom[k];
for (var i$2 = 0; i$2 < polygon.length; i$2++) {
addLine(simplified, polygon[i$2], tile, tolerance, true, i$2 === 0);
}
}
}
if (simplified.length) {
var tags = feature.tags || null;
if (type === 'LineString' && options.lineMetrics) {
tags = {};
for (var key in feature.tags) { tags[key] = feature.tags[key]; }
tags['mapbox_clip_start'] = geom.start / geom.size;
tags['mapbox_clip_end'] = geom.end / geom.size;
}
var tileFeature = {
geometry: simplified,
type: type === 'Polygon' || type === 'MultiPolygon' ? 3 :
(type === 'LineString' || type === 'MultiLineString' ? 2 : 1),
tags: tags
};
if (feature.id !== null) {
tileFeature.id = feature.id;
}
tile.features.push(tileFeature);
}
}
function addLine(result, geom, tile, tolerance, isPolygon, isOuter) {
var sqTolerance = tolerance * tolerance;
if (tolerance > 0 && (geom.size < (isPolygon ? sqTolerance : tolerance))) {
tile.numPoints += geom.length / 3;
return;
}
var ring = [];
for (var i = 0; i < geom.length; i += 3) {
if (tolerance === 0 || geom[i + 2] > sqTolerance) {
tile.numSimplified++;
ring.push(geom[i], geom[i + 1]);
}
tile.numPoints++;
}
if (isPolygon) { rewind(ring, isOuter); }
result.push(ring);
}
function rewind(ring, clockwise) {
var area = 0;
for (var i = 0, len = ring.length, j = len - 2; i < len; j = i, i += 2) {
area += (ring[i] - ring[j]) * (ring[i + 1] + ring[j + 1]);
}
if (area > 0 === clockwise) {
for (var i$1 = 0, len$1 = ring.length; i$1 < len$1 / 2; i$1 += 2) {
var x = ring[i$1];
var y = ring[i$1 + 1];
ring[i$1] = ring[len$1 - 2 - i$1];
ring[i$1 + 1] = ring[len$1 - 1 - i$1];
ring[len$1 - 2 - i$1] = x;
ring[len$1 - 1 - i$1] = y;
}
}
}
var defaultOptions = {
crs: 'EPSG:3857', // 默认的投影坐标系 3857 4326走经纬度裁图
maxZoom: 14, // max zoom to preserve detail on
indexMaxZoom: 5, // max zoom in the tile index
indexMaxPoints: 100000, // max number of points per tile in the tile index
tolerance: 3, // simplification tolerance (higher means simpler)
extent: 4096, // tile extent
buffer: 64, // tile buffer on each side
lineMetrics: false, // whether to calculate line metrics
promoteId: null, // name of a feature property to be promoted to feature.id
generateId: false, // whether to generate feature ids. Cannot be used with promoteId
debug: 0, // logging level (0, 1 or 2)
isWrapData: false // 表示是否对geojson数据进行分段处理。如果为true,则会对超出范围的数据重新计算,保证其在世界范围内显示。如果为false,则不处理。
};
var GeoJSONVT = function GeoJSONVT(data, options) {
options = this.options = extend(Object.create(defaultOptions), options);
var debug = options.debug;
if (debug) { console.time('preprocess data'); }
if (options.maxZoom < 0 || options.maxZoom > 24) { throw new Error('maxZoom should be in the 0-24 range'); }
if (options.promoteId && options.generateId) { throw new Error('promoteId and generateId cannot be used together.'); }
// projects and adds simplification info
var features = convert(data, options);
this.features = features;
// tiles and tileCoords are part of the public API
this.tiles = {};
this.tileCoords = [];
if (debug) {
console.timeEnd('preprocess data');
console.log('index: maxZoom: %d, maxPoints: %d', options.indexMaxZoom, options.indexMaxPoints);
console.time('generate tiles');
this.stats = {};
this.total = 0;
}
// wraps features (ie extreme west and extreme east)
// 移除worldCopies操作,目前按屏幕范围取瓦片范围
// if (options.isWrapData) {
// features = wrap(features, options);
// }
// start slicing from the top tile down
// if (features.length) this.splitTile(features, 0, 0, 0);
// if (debug) {
// if (features.length) console.log('features: %d, points: %d', this.tiles[0].numFeatures, this.tiles[0].numPoints);
// console.timeEnd('generate tiles');
// console.log('tiles generated:', this.total, JSON.stringify(this.stats));
// }
};
// splits features from a parent tile to sub-tiles.
// z, x, and y are the coordinates of the parent tile
// cz, cx, and cy are the coordinates of the target tile
//
// If no target tile is specified, splitting stops when we reach the maximum
// zoom or the number of points is low as specified in the options.
GeoJSONVT.prototype.splitTile = function splitTile (features, z, x, y, cz, cx, cy) {
var stack = [features, z, x, y];
var options = this.options;
var debug = options.debug;
// avoid recursion by using a processing queue
while (stack.length) {
y = stack.pop();
x = stack.pop();
z = stack.pop();
features = stack.pop();
var z2 = 1 << z;
var id = toID(z, x, y);
var tile = this.tiles[id];
if (!tile) {
if (debug > 1) { console.time('creation'); }
tile = this.tiles[id] = createTile(features, z, x, y, options);
this.tileCoords.push({z: z, x: x, y: y});
if (debug) {
if (debug > 1) {
console.log('tile z%d-%d-%d (features: %d, points: %d, simplified: %d)',
z, x, y, tile.numFeatures, tile.numPoints, tile.numSimplified);
console.timeEnd('creation');
}
var key = "z" + z;
this.stats[key] = (this.stats[key] || 0) + 1;
this.total++;
}
}
// save reference to original geometry in tile so that we can drill down later if we stop now
tile.source = features;
// if it's the first-pass tiling
if (cz == null) {
// stop tiling if we reached max zoom, or if the tile is too simple
if (z === options.indexMaxZoom || tile.numPoints <= options.indexMaxPoints) { continue; }
// if a drilldown to a specific tile
} else if (z === options.maxZoom || z === cz) {
// stop tiling if we reached base zoom or our target tile zoom
continue;
} else if (cz != null) {
// stop tiling if it's not an ancestor of the target tile
var zoomSteps = cz - z;
if (x !== cx >> zoomSteps || y !== cy >> zoomSteps) { continue; }
}
// if we slice further down, no need to keep source geometry
tile.source = null;
if (features.length === 0) { continue; }
if (debug > 1) { console.time('clipping'); }
// values we'll use for clipping
var k1 = 0.5 * options.buffer / options.extent;
var k2 = 0.5 - k1;
var k3 = 0.5 + k1;
var k4 = 1 + k1;
var tl = null;
var bl = null;
var tr = null;
var br = null;
var left = clip(features, z2, x - k1, x + k3, 0, tile.minX, tile.maxX, options);
var right = clip(features, z2, x + k2, x + k4, 0, tile.minX, tile.maxX, options);
features = null;
if (left) {
tl = clip(left, z2, y - k1, y + k3, 1, tile.minY, tile.maxY, options);
bl = clip(left, z2, y + k2, y + k4, 1, tile.minY, tile.maxY, options);
left = null;
}
if (right) {
tr = clip(right, z2, y - k1, y + k3, 1, tile.minY, tile.maxY, options);
br = clip(right, z2, y + k2, y + k4, 1, tile.minY, tile.maxY, options);
right = null;
}
if (debug > 1) { console.timeEnd('clipping'); }
stack.push(tl || [], z + 1, x * 2, y * 2);
stack.push(bl || [], z + 1, x * 2, y * 2 + 1);
stack.push(tr || [], z + 1, x * 2 + 1, y * 2);
stack.push(br || [], z + 1, x * 2 + 1, y * 2 + 1);
}
};
/**
* @description: 创建0级瓦片
* @param {*} x0
* @param {*} y0
* @param {*} options
* @return {*}
*/
GeoJSONVT.prototype._createZeroTile = function _createZeroTile (x0, y0, options) {
var z0 = 0;
var k1 = 0.5 * options.buffer / options.extent;
var scale = 1 >> z0;
var idZero = toID(z0, x0, y0);
var xMin = x0 * scale;
var xMax = (x0 + 1) * scale;
var yMin = y0 * scale;
var yMax = (y0 + 1) * scale;
var tile;
// 横向裁剪
var clipWidth = clip(this.features, scale, xMin - k1, xMax + k1, 0, -Infinity, Infinity, options);
if (clipWidth) {
var clipFeatures = clip(
clipWidth,
scale,
yMin - k1,
yMax + k1,
1,
-Infinity,
Infinity,
options
);
if (clipFeatures) {
this.tiles[idZero] = transformTile(createTile(clipFeatures, z0, x0, y0, options), options.extent);
this.tiles[idZero].source = clipFeatures;
tile = this.tiles[idZero];
}
}
return tile;
};
GeoJSONVT.prototype.getTile = function getTile (z, x, y) {
z = +z;
x = +x;
y = +y;
var options = this.options;
var extent = options.extent;
var debug = options.debug;
if (z < 0 || z > 24) { return null; }
// 去除warp操作,目前坐标系是无限延伸的,不能warp范围
// const z2 = 1 << z;
// x = (x + z2) & (z2 - 1); // wrap tile x coordinate
var id = toID(z, x, y);
if (this.tiles[id]) {
return transformTile(this.tiles[id], extent);
} else if (z === 0) {
return this._createZeroTile(x, y, options);
}
if (debug > 1) { console.log('drilling down to z%d-%d-%d', z, x, y); }
var z0 = z;
var x0 = x;
var y0 = y;
var parent;
while (!parent && z0 > 0) {
z0--;
x0 = x0 >> 1;
y0 = y0 >> 1;
var idZero = toID(z0, x0, y0);
parent = this.tiles[idZero];
if (z0 === 0 && !parent) {
parent = this._createZeroTile(x0, y0, options);
}
}
if (!parent || !parent.source) { return null; }
// if we found a parent tile containing the original geometry, we can drill down from it
if (debug > 1) {
console.log('found parent tile z%d-%d-%d', z0, x0, y0);
console.time('drilling down');
}
this.splitTile(parent.source, z0, x0, y0, z, x, y);
if (debug > 1) { console.timeEnd('drilling down'); }
return this.tiles[id] ? transformTile(this.tiles[id], extent) : null;
};
/**
* @description: 瓦片编码方式(此插件默认的编码方式在0级多张瓦片时会导致瓦片重复)
* @param {*} z
* @param {*} x
* @param {*} y
* @return {*}
*/
function toID(z, x, y) {
return x.toString(36) + y.toString(36) + z.toString(36);
}
function extend(dest, src) {
for (var i in src) { dest[i] = src[i]; }
return dest;
}
function geojsonvt(data, options) {
return new GeoJSONVT(data, options);
}
return geojsonvt;
}));