Created
September 19, 2018 13:14
-
-
Save 2803media/2c2e370fb317311969bd9efcea09e521 to your computer and use it in GitHub Desktop.
WebWorker.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| '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