Skip to content

Instantly share code, notes, and snippets.

@2803media
Created September 19, 2018 13:14
Show Gist options
  • Select an option

  • Save 2803media/2c2e370fb317311969bd9efcea09e521 to your computer and use it in GitHub Desktop.

Select an option

Save 2803media/2c2e370fb317311969bd9efcea09e521 to your computer and use it in GitHub Desktop.
WebWorker.js
'use strict';
var simplify_1 = simplify$1;
// calculate simplification data using optimized Douglas-Peucker algorithm
function simplify$1(points, tolerance) {
var sqTolerance = tolerance * tolerance,
len = points.length,
first = 0,
last = len - 1,
stack = [],
i, maxSqDist, sqDist, index;
// always retain the endpoints (1 is the max value)
points[first][2] = 1;
points[last][2] = 1;
// avoid recursion by using a stack
while (last) {
maxSqDist = 0;
for (i = first + 1; i < last; i++) {
sqDist = getSqSegDist(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
points[index][2] = maxSqDist; // save the point importance in squared pixels as a z coordinate
stack.push(first);
stack.push(index);
first = index;
} else {
last = stack.pop();
first = stack.pop();
}
}
}
// square distance from a point to a segment
function getSqSegDist(p, a, b) {
var x = a[0], y = a[1],
bx = b[0], by = b[1],
px = p[0], py = p[1],
dx = bx - x,
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;
}
var convert_1 = convert$1;
var simplify = simplify_1;
// converts GeoJSON feature into an intermediate projected JSON vector format with simplification data
function convert$1(data, tolerance) {
var features = [];
if (data.type === 'FeatureCollection') {
for (var i = 0; i < data.features.length; i++) {
convertFeature(features, data.features[i], tolerance);
}
} else if (data.type === 'Feature') {
convertFeature(features, data, tolerance);
} else {
// single geometry or a geometry collection
convertFeature(features, {geometry: data}, tolerance);
}
return features;
}
function convertFeature(features, feature, tolerance) {
if (feature.geometry === null) {
// ignore features with null geometry
return;
}
var geom = feature.geometry,
type = geom.type,
coords = geom.coordinates,
tags = feature.properties,
i, j, rings, projectedRing;
if (type === 'Point') {
features.push(create(tags, 1, [projectPoint(coords)]));
} else if (type === 'MultiPoint') {
features.push(create(tags, 1, project(coords)));
} else if (type === 'LineString') {
features.push(create(tags, 2, [project(coords, tolerance)]));
} else if (type === 'MultiLineString' || type === 'Polygon') {
rings = [];
for (i = 0; i < coords.length; i++) {
projectedRing = project(coords[i], tolerance);
if (type === 'Polygon') { projectedRing.outer = (i === 0); }
rings.push(projectedRing);
}
features.push(create(tags, type === 'Polygon' ? 3 : 2, rings));
} else if (type === 'MultiPolygon') {
rings = [];
for (i = 0; i < coords.length; i++) {
for (j = 0; j < coords[i].length; j++) {
projectedRing = project(coords[i][j], tolerance);
projectedRing.outer = (j === 0);
rings.push(projectedRing);
}
}
features.push(create(tags, 3, rings));
} else if (type === 'GeometryCollection') {
for (i = 0; i < geom.geometries.length; i++) {
convertFeature(features, {
geometry: geom.geometries[i],
properties: tags
}, tolerance);
}
} else {
throw new Error('Input data is not a valid GeoJSON object.');
}
}
function create(tags, type, geometry) {
var feature = {
geometry: geometry,
type: type,
tags: tags || null,
min: [2, 1], // initial bbox values;
max: [-1, 0] // note that coords are usually in [0..1] range
};
calcBBox(feature);
return feature;
}
function project(lonlats, tolerance) {
var projected = [];
for (var i = 0; i < lonlats.length; i++) {
projected.push(projectPoint(lonlats[i]));
}
if (tolerance) {
simplify(projected, tolerance);
calcSize(projected);
}
return projected;
}
function projectPoint(p) {
var sin = Math.sin(p[1] * Math.PI / 180),
x = (p[0] / 360 + 0.5),
y = (0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI);
y = y < 0 ? 0 :
y > 1 ? 1 : y;
return [x, y, 0];
}
// calculate area and length of the poly
function calcSize(points) {
var area = 0,
dist = 0;
for (var i = 0, a, b; i < points.length - 1; i++) {
a = b || points[i];
b = points[i + 1];
area += a[0] * b[1] - b[0] * a[1];
// use Manhattan distance instead of Euclidian one to avoid expensive square root computation
dist += Math.abs(b[0] - a[0]) + Math.abs(b[1] - a[1]);
}
points.area = Math.abs(area / 2);
points.dist = dist;
}
// calculate the feature bounding box for faster clipping later
function calcBBox(feature) {
var geometry = feature.geometry,
min = feature.min,
max = feature.max;
if (feature.type === 1) { calcRingBBox(min, max, geometry); }
else { for (var i = 0; i < geometry.length; i++) { calcRingBBox(min, max, geometry[i]); } }
return feature;
}
function calcRingBBox(min, max, points) {
for (var i = 0, p; i < points.length; i++) {
p = points[i];
min[0] = Math.min(p[0], min[0]);
max[0] = Math.max(p[0], max[0]);
min[1] = Math.min(p[1], min[1]);
max[1] = Math.max(p[1], max[1]);
}
}
var tile = transformTile;
var point = transformPoint;
// 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 = tile.z2,
tx = tile.x,
ty = tile.y,
i, j, k;
for (i = 0; i < tile.features.length; i++) {
var feature = tile.features[i],
geom = feature.geometry,
type = feature.type;
if (type === 1) {
for (j = 0; j < geom.length; j++) { geom[j] = transformPoint(geom[j], extent, z2, tx, ty); }
} else {
for (j = 0; j < geom.length; j++) {
var ring = geom[j];
for (k = 0; k < ring.length; k++) { ring[k] = transformPoint(ring[k], extent, z2, tx, ty); }
}
}
}
tile.transformed = true;
return tile;
}
function transformPoint(p, extent, z2, tx, ty) {
var x = Math.round(extent * (p[0] * z2 - tx)),
y = Math.round(extent * (p[1] * z2 - ty));
return [x, y];
}
var transform$1 = {
tile: tile,
point: point
};
var clip_1 = clip$1;
/* clip features between two axis-parallel lines:
* | |
* ___|___ | /
* / | \____|____/
* | |
*/
function clip$1(features, scale, k1, k2, axis, intersect, minAll, maxAll) {
k1 /= scale;
k2 /= scale;
if (minAll >= k1 && maxAll <= k2) { return features; } // trivial accept
else if (minAll > k2 || maxAll < k1) { return null; } // trivial reject
var clipped = [];
for (var i = 0; i < features.length; i++) {
var feature = features[i],
geometry = feature.geometry,
type = feature.type,
min, max;
min = feature.min[axis];
max = feature.max[axis];
if (min >= k1 && max <= k2) { // trivial accept
clipped.push(feature);
continue;
} else if (min > k2 || max < k1) { continue; } // trivial reject
var slices = type === 1 ?
clipPoints(geometry, k1, k2, axis) :
clipGeometry(geometry, k1, k2, axis, intersect, type === 3);
if (slices.length) {
// if a feature got clipped, it will likely get clipped on the next zoom level as well,
// so there's no need to recalculate bboxes
clipped.push({
geometry: slices,
type: type,
tags: features[i].tags || null,
min: feature.min,
max: feature.max
});
}
}
return clipped.length ? clipped : null;
}
function clipPoints(geometry, k1, k2, axis) {
var slice = [];
for (var i = 0; i < geometry.length; i++) {
var a = geometry[i],
ak = a[axis];
if (ak >= k1 && ak <= k2) { slice.push(a); }
}
return slice;
}
function clipGeometry(geometry, k1, k2, axis, intersect, closed) {
var slices = [];
for (var i = 0; i < geometry.length; i++) {
var ak = 0,
bk = 0,
b = null,
points = geometry[i],
area = points.area,
dist = points.dist,
outer = points.outer,
len = points.length,
a, j, last;
var slice = [];
for (j = 0; j < len - 1; j++) {
a = b || points[j];
b = points[j + 1];
ak = bk || a[axis];
bk = b[axis];
if (ak < k1) {
if ((bk > k2)) { // ---|-----|-->
slice.push(intersect(a, b, k1), intersect(a, b, k2));
if (!closed) { slice = newSlice(slices, slice, area, dist, outer); }
} else if (bk >= k1) { slice.push(intersect(a, b, k1)); } // ---|--> |
} else if (ak > k2) {
if ((bk < k1)) { // <--|-----|---
slice.push(intersect(a, b, k2), intersect(a, b, k1));
if (!closed) { slice = newSlice(slices, slice, area, dist, outer); }
} else if (bk <= k2) { slice.push(intersect(a, b, k2)); } // | <--|---
} else {
slice.push(a);
if (bk < k1) { // <--|--- |
slice.push(intersect(a, b, k1));
if (!closed) { slice = newSlice(slices, slice, area, dist, outer); }
} else if (bk > k2) { // | ---|-->
slice.push(intersect(a, b, k2));
if (!closed) { slice = newSlice(slices, slice, area, dist, outer); }
}
// | --> |
}
}
// add the last point
a = points[len - 1];
ak = a[axis];
if (ak >= k1 && ak <= k2) { slice.push(a); }
// close the polygon if its endpoints are not the same after clipping
last = slice[slice.length - 1];
if (closed && last && (slice[0][0] !== last[0] || slice[0][1] !== last[1])) { slice.push(slice[0]); }
// add the final slice
newSlice(slices, slice, area, dist, outer);
}
return slices;
}
function newSlice(slices, slice, area, dist, outer) {
if (slice.length) {
// we don't recalculate the area/length of the unclipped geometry because the case where it goes
// below the visibility threshold as a result of clipping is rare, so we avoid doing unnecessary work
slice.area = area;
slice.dist = dist;
if (outer !== undefined) { slice.outer = outer; }
slices.push(slice);
}
return [];
}
var clip$2 = clip_1;
var wrap_1 = wrap$1;
function wrap$1(features, buffer, intersectX) {
var merged = features,
left = clip$2(features, 1, -1 - buffer, buffer, 0, intersectX, -1, 2), // left world copy
right = clip$2(features, 1, 1 - buffer, 2 + buffer, 0, intersectX, -1, 2); // right world copy
if (left || right) {
merged = clip$2(features, 1, -buffer, 1 + buffer, 0, intersectX, -1, 2); // center world copy
if (left) { merged = shiftFeatureCoords(left, 1).concat(merged); } // merge left into center
if (right) { merged = merged.concat(shiftFeatureCoords(right, -1)); } // merge right into center
}
return merged;
}
function shiftFeatureCoords(features, offset) {
var newFeatures = [];
for (var i = 0; i < features.length; i++) {
var feature = features[i],
type = feature.type;
var newGeometry;
if (type === 1) {
newGeometry = shiftCoords(feature.geometry, offset);
} else {
newGeometry = [];
for (var j = 0; j < feature.geometry.length; j++) {
newGeometry.push(shiftCoords(feature.geometry[j], offset));
}
}
newFeatures.push({
geometry: newGeometry,
type: type,
tags: feature.tags,
min: [feature.min[0] + offset, feature.min[1]],
max: [feature.max[0] + offset, feature.max[1]]
});
}
return newFeatures;
}
function shiftCoords(points, offset) {
var newPoints = [];
newPoints.area = points.area;
newPoints.dist = points.dist;
for (var i = 0; i < points.length; i++) {
newPoints.push([points[i][0] + offset, points[i][1], points[i][2]]);
}
return newPoints;
}
var tile$1 = createTile$1;
function createTile$1(features, z2, tx, ty, tolerance, noSimplify) {
var tile = {
features: [],
numPoints: 0,
numSimplified: 0,
numFeatures: 0,
source: null,
x: tx,
y: ty,
z2: z2,
transformed: false,
min: [2, 1],
max: [-1, 0]
};
for (var i = 0; i < features.length; i++) {
tile.numFeatures++;
addFeature(tile, features[i], tolerance, noSimplify);
var min = features[i].min,
max = features[i].max;
if (min[0] < tile.min[0]) { tile.min[0] = min[0]; }
if (min[1] < tile.min[1]) { tile.min[1] = min[1]; }
if (max[0] > tile.max[0]) { tile.max[0] = max[0]; }
if (max[1] > tile.max[1]) { tile.max[1] = max[1]; }
}
return tile;
}
function addFeature(tile, feature, tolerance, noSimplify) {
var geom = feature.geometry,
type = feature.type,
simplified = [],
sqTolerance = tolerance * tolerance,
i, j, ring, p;
if (type === 1) {
for (i = 0; i < geom.length; i++) {
simplified.push(geom[i]);
tile.numPoints++;
tile.numSimplified++;
}
} else {
// simplify and transform projected coordinates for tile geometry
for (i = 0; i < geom.length; i++) {
ring = geom[i];
// filter out tiny polylines & polygons
if (!noSimplify && ((type === 2 && ring.dist < tolerance) ||
(type === 3 && ring.area < sqTolerance))) {
tile.numPoints += ring.length;
continue;
}
var simplifiedRing = [];
for (j = 0; j < ring.length; j++) {
p = ring[j];
// keep points with importance > tolerance
if (noSimplify || p[2] > sqTolerance) {
simplifiedRing.push(p);
tile.numSimplified++;
}
tile.numPoints++;
}
if (type === 3) { rewind(simplifiedRing, ring.outer); }
simplified.push(simplifiedRing);
}
}
if (simplified.length) {
tile.features.push({
geometry: simplified,
type: type,
tags: feature.tags || null
});
}
}
function rewind(ring, clockwise) {
var area = signedArea(ring);
if (area < 0 === clockwise) { ring.reverse(); }
}
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[0] - p1[0]) * (p1[1] + p2[1]);
}
return sum;
}
var index = geojsonvt;
var convert = convert_1;
var transform = transform$1;
var clip = clip_1;
var wrap = wrap_1;
var createTile = tile$1; // final simplified tile generation
function geojsonvt(data, options) {
return new GeoJSONVT(data, options);
}
function GeoJSONVT(data, options) {
options = this.options = extend(Object.create(this.options), options);
var debug = options.debug;
if (debug) { console.time('preprocess data'); }
var z2 = 1 << options.maxZoom, // 2^z
features = convert(data, options.tolerance / (z2 * options.extent));
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;
}
features = wrap(features, options.buffer / options.extent, intersectX);
// 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));
}
}
GeoJSONVT.prototype.options = {
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
solidChildren: false, // whether to tile solid square tiles further
tolerance: 3, // simplification tolerance (higher means simpler)
extent: 4096, // tile extent
buffer: 64, // tile buffer on each side
debug: 0 // logging level (0, 1 or 2)
};
GeoJSONVT.prototype.splitTile = function (features, z, x, y, cz, cx, cy) {
var this$1 = this;
var stack = [features, z, x, y],
options = this.options,
debug = options.debug,
solid = null;
// 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,
id = toID(z, x, y),
tile = this$1.tiles[id],
tileTolerance = z === options.maxZoom ? 0 : options.tolerance / (z2 * options.extent);
if (!tile) {
if (debug > 1) { console.time('creation'); }
tile = this$1.tiles[id] = createTile(features, z2, x, y, tileTolerance, z === options.maxZoom);
this$1.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$1.stats[key] = (this$1.stats[key] || 0) + 1;
this$1.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) {
// 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 {
// stop tiling if we reached base zoom or our target tile zoom
if (z === options.maxZoom || z === cz) { continue; }
// stop tiling if it's not an ancestor of the target tile
var m = 1 << (cz - z);
if (x !== Math.floor(cx / m) || y !== Math.floor(cy / m)) { continue; }
}
// stop tiling if the tile is solid clipped square
if (!options.solidChildren && isClippedSquare(tile, options.extent, options.buffer)) {
if (cz) { solid = z; } // and remember the zoom if we're drilling down
continue;
}
// if we slice further down, no need to keep source geometry
tile.source = null;
if (debug > 1) { console.time('clipping'); }
// values we'll use for clipping
var k1 = 0.5 * options.buffer / options.extent,
k2 = 0.5 - k1,
k3 = 0.5 + k1,
k4 = 1 + k1,
tl, bl, tr, br, left, right;
tl = bl = tr = br = null;
left = clip(features, z2, x - k1, x + k3, 0, intersectX, tile.min[0], tile.max[0]);
right = clip(features, z2, x + k2, x + k4, 0, intersectX, tile.min[0], tile.max[0]);
if (left) {
tl = clip(left, z2, y - k1, y + k3, 1, intersectY, tile.min[1], tile.max[1]);
bl = clip(left, z2, y + k2, y + k4, 1, intersectY, tile.min[1], tile.max[1]);
}
if (right) {
tr = clip(right, z2, y - k1, y + k3, 1, intersectY, tile.min[1], tile.max[1]);
br = clip(right, z2, y + k2, y + k4, 1, intersectY, tile.min[1], tile.max[1]);
}
if (debug > 1) { console.timeEnd('clipping'); }
if (tl) { stack.push(tl, z + 1, x * 2, y * 2); }
if (bl) { stack.push(bl, z + 1, x * 2, y * 2 + 1); }
if (tr) { stack.push(tr, z + 1, x * 2 + 1, y * 2); }
if (br) { stack.push(br, z + 1, x * 2 + 1, y * 2 + 1); }
}
return solid;
};
GeoJSONVT.prototype.getTile = function (z, x, y) {
var this$1 = this;
var options = this.options,
extent = options.extent,
debug = options.debug;
var z2 = 1 << z;
x = ((x % z2) + z2) % z2; // wrap tile x coordinate
var id = toID(z, x, y);
if (this.tiles[id]) { return transform.tile(this.tiles[id], extent); }
if (debug > 1) { console.log('drilling down to z%d-%d-%d', z, x, y); }
var z0 = z,
x0 = x,
y0 = y,
parent;
while (!parent && z0 > 0) {
z0--;
x0 = Math.floor(x0 / 2);
y0 = Math.floor(y0 / 2);
parent = this$1.tiles[toID(z0, x0, y0)];
}
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); }
// it parent tile is a solid clipped square, return it instead since it's identical
if (isClippedSquare(parent, extent, options.buffer)) { return transform.tile(parent, extent); }
if (debug > 1) { console.time('drilling down'); }
var solid = this.splitTile(parent.source, z0, x0, y0, z, x, y);
if (debug > 1) { console.timeEnd('drilling down'); }
// one of the parent tiles was a solid clipped square
if (solid !== null) {
var m = 1 << (z - solid);
id = toID(solid, Math.floor(x / m), Math.floor(y / m));
}
return this.tiles[id] ? transform.tile(this.tiles[id], extent) : null;
};
function toID(z, x, y) {
return (((1 << z) * y + x) * 32) + z;
}
function intersectX(a, b, x) {
return [x, (x - a[0]) * (b[1] - a[1]) / (b[0] - a[0]) + a[1], 1];
}
function intersectY(a, b, y) {
return [(y - a[1]) * (b[0] - a[0]) / (b[1] - a[1]) + a[0], y, 1];
}
function extend(dest, src) {
for (var i in src) { dest[i] = src[i]; }
return dest;
}
// checks whether a tile is a whole-area fill after clipping; if it is, there's no sense slicing it further
function isClippedSquare(tile, extent, buffer) {
var features = tile.source;
if (features.length !== 1) { return false; }
var feature = features[0];
if (feature.type !== 3 || feature.geometry.length > 1) { return false; }
var len = feature.geometry[0].length;
if (len !== 5) { return false; }
for (var i = 0; i < len; i++) {
var p = transform.point(feature.geometry[0][i], extent, tile.z2, tile.x, tile.y);
if ((p[0] !== -buffer && p[0] !== extent + buffer) ||
(p[1] !== -buffer && p[1] !== extent + buffer)) { return false; }
}
return true;
}
var identity = function(x) {
return x;
};
var transform$3 = function(topology) {
if ((transform = topology.transform) == null) { return identity; }
var transform,
x0,
y0,
kx = transform.scale[0],
ky = transform.scale[1],
dx = transform.translate[0],
dy = transform.translate[1];
return function(point, i) {
if (!i) { x0 = y0 = 0; }
point[0] = (x0 += point[0]) * kx + dx;
point[1] = (y0 += point[1]) * ky + dy;
return point;
};
};
var bbox = function(topology) {
var bbox = topology.bbox;
function bboxPoint(p0) {
p1[0] = p0[0], p1[1] = p0[1], t(p1);
if (p1[0] < x0) { x0 = p1[0]; }
if (p1[0] > x1) { x1 = p1[0]; }
if (p1[1] < y0) { y0 = p1[1]; }
if (p1[1] > y1) { y1 = p1[1]; }
}
function bboxGeometry(o) {
switch (o.type) {
case "GeometryCollection": o.geometries.forEach(bboxGeometry); break;
case "Point": bboxPoint(o.coordinates); break;
case "MultiPoint": o.coordinates.forEach(bboxPoint); break;
}
}
if (!bbox) {
var t = transform$3(topology), p0, p1 = new Array(2), name,
x0 = Infinity, y0 = x0, x1 = -x0, y1 = -x0;
topology.arcs.forEach(function(arc) {
var i = -1, n = arc.length;
while (++i < n) {
p0 = arc[i], p1[0] = p0[0], p1[1] = p0[1], t(p1, i);
if (p1[0] < x0) { x0 = p1[0]; }
if (p1[0] > x1) { x1 = p1[0]; }
if (p1[1] < y0) { y0 = p1[1]; }
if (p1[1] > y1) { y1 = p1[1]; }
}
});
for (name in topology.objects) {
bboxGeometry(topology.objects[name]);
}
bbox = topology.bbox = [x0, y0, x1, y1];
}
return bbox;
};
var reverse = function(array, n) {
var t, j = array.length, i = j - n;
while (i < --j) { t = array[i], array[i++] = array[j], array[j] = t; }
};
var feature = function(topology, o) {
return o.type === "GeometryCollection"
? {type: "FeatureCollection", features: o.geometries.map(function(o) { return feature$1(topology, o); })}
: feature$1(topology, o);
};
function feature$1(topology, o) {
var id = o.id,
bbox = o.bbox,
properties = o.properties == null ? {} : o.properties,
geometry = object(topology, o);
return id == null && bbox == null ? {type: "Feature", properties: properties, geometry: geometry}
: bbox == null ? {type: "Feature", id: id, properties: properties, geometry: geometry}
: {type: "Feature", id: id, bbox: bbox, properties: properties, geometry: geometry};
}
function object(topology, o) {
var transformPoint = transform$3(topology),
arcs = topology.arcs;
function arc(i, points) {
if (points.length) { points.pop(); }
for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length; k < n; ++k) {
points.push(transformPoint(a[k].slice(), k));
}
if (i < 0) { reverse(points, n); }
}
function point(p) {
return transformPoint(p.slice());
}
function line(arcs) {
var points = [];
for (var i = 0, n = arcs.length; i < n; ++i) { arc(arcs[i], points); }
if (points.length < 2) { points.push(points[0].slice()); }
return points;
}
function ring(arcs) {
var points = line(arcs);
while (points.length < 4) { points.push(points[0].slice()); }
return points;
}
function polygon(arcs) {
return arcs.map(ring);
}
function geometry(o) {
var type = o.type, coordinates;
switch (type) {
case "GeometryCollection": return {type: type, geometries: o.geometries.map(geometry)};
case "Point": coordinates = point(o.coordinates); break;
case "MultiPoint": coordinates = o.coordinates.map(point); break;
case "LineString": coordinates = line(o.arcs); break;
case "MultiLineString": coordinates = o.arcs.map(line); break;
case "Polygon": coordinates = polygon(o.arcs); break;
case "MultiPolygon": coordinates = o.arcs.map(polygon); break;
default: return null;
}
return {type: type, coordinates: coordinates};
}
return geometry(o);
}
var stitch = function(topology, arcs) {
var stitchedArcs = {},
fragmentByStart = {},
fragmentByEnd = {},
fragments = [],
emptyIndex = -1;
// Stitch empty arcs first, since they may be subsumed by other arcs.
arcs.forEach(function(i, j) {
var arc = topology.arcs[i < 0 ? ~i : i], t;
if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {
t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
}
});
arcs.forEach(function(i) {
var e = ends(i),
start = e[0],
end = e[1],
f, g;
if (f = fragmentByEnd[start]) {
delete fragmentByEnd[f.end];
f.push(i);
f.end = end;
if (g = fragmentByStart[end]) {
delete fragmentByStart[g.start];
var fg = g === f ? f : f.concat(g);
fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else if (f = fragmentByStart[end]) {
delete fragmentByStart[f.start];
f.unshift(i);
f.start = start;
if (g = fragmentByEnd[start]) {
delete fragmentByEnd[g.end];
var gf = g === f ? f : g.concat(f);
fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
} else {
fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
}
} else {
f = [i];
fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
}
});
function ends(i) {
var arc = topology.arcs[i < 0 ? ~i : i], p0 = arc[0], p1;
if (topology.transform) { p1 = [0, 0], arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; }); }
else { p1 = arc[arc.length - 1]; }
return i < 0 ? [p1, p0] : [p0, p1];
}
function flush(fragmentByEnd, fragmentByStart) {
for (var k in fragmentByEnd) {
var f = fragmentByEnd[k];
delete fragmentByStart[f.start];
delete f.start;
delete f.end;
f.forEach(function(i) { stitchedArcs[i < 0 ? ~i : i] = 1; });
fragments.push(f);
}
}
flush(fragmentByEnd, fragmentByStart);
flush(fragmentByStart, fragmentByEnd);
arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) { fragments.push([i]); } });
return fragments;
};
function extractArcs(topology, object$$1, filter) {
var arcs = [],
geomsByArc = [],
geom;
function extract0(i) {
var j = i < 0 ? ~i : i;
(geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom});
}
function extract1(arcs) {
arcs.forEach(extract0);
}
function extract2(arcs) {
arcs.forEach(extract1);
}
function extract3(arcs) {
arcs.forEach(extract2);
}
function geometry(o) {
switch (geom = o, o.type) {
case "GeometryCollection": o.geometries.forEach(geometry); break;
case "LineString": extract1(o.arcs); break;
case "MultiLineString": case "Polygon": extract2(o.arcs); break;
case "MultiPolygon": extract3(o.arcs); break;
}
}
geometry(object$$1);
geomsByArc.forEach(filter == null
? function(geoms) { arcs.push(geoms[0].i); }
: function(geoms) { if (filter(geoms[0].g, geoms[geoms.length - 1].g)) { arcs.push(geoms[0].i); } });
return arcs;
}
function planarRingArea(ring) {
var i = -1, n = ring.length, a, b = ring[n - 1], area = 0;
while (++i < n) { a = b, b = ring[i], area += a[0] * b[1] - a[1] * b[0]; }
return Math.abs(area); // Note: doubled area!
}
var bisect = function(a, x) {
var lo = 0, hi = a.length;
while (lo < hi) {
var mid = lo + hi >>> 1;
if (a[mid] < x) { lo = mid + 1; }
else { hi = mid; }
}
return lo;
};
var slicers = {};
var options;
onmessage = function (e) {
if (e.data[0] === 'slice') {
// Given a blob of GeoJSON and some topojson/geojson-vt options, do the slicing.
var geojson = e.data[1];
options = e.data[2];
if (geojson.type && geojson.type === 'Topology') {
for (var layerName in geojson.objects) {
slicers[layerName] = index(
feature(geojson, geojson.objects[layerName])
, options);
}
} else {
slicers[options.vectorTileLayerName] = index(geojson, options);
}
} else if (e.data[0] === 'get') {
// Gets the vector tile for the given coordinates, sends it back as a message
var coords = e.data[1];
var tileLayers = {};
for (var layerName in slicers) {
var slicedTileLayer = slicers[layerName].getTile(coords.z, coords.x, coords.y);
if (slicedTileLayer) {
var vectorTileLayer = {
features: [],
extent: options.extent,
name: options.vectorTileLayerName,
length: slicedTileLayer.features.length
};
for (var i in slicedTileLayer.features) {
var feat = {
geometry: slicedTileLayer.features[i].geometry,
properties: slicedTileLayer.features[i].tags,
type: slicedTileLayer.features[i].type // 1 = point, 2 = line, 3 = polygon
};
vectorTileLayer.features.push(feat);
}
tileLayers[layerName] = vectorTileLayer;
}
}
postMessage({ layers: tileLayers, coords: coords });
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment