UNPKG

s2maps-gpu

Version:

S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.

291 lines (290 loc) 9.98 kB
/** * Source data may only tilelize data to a certain max zoom, but we may request a tile at a higher zoom * Therefore we scale, shift, and clip the geometry as needed for that specific higher zoomed tile. * * 1) scale up by distance between tiles (if parent is 2 zooms above, you double size twice) * 2) shift x and y by position of current tile * 3) clip the geometry by 0->extent (include buffer if not points) * @param extent - the extent of the current tile * @param tile - the tile request * @returns the scale, shift, and clip parameters */ export function scaleShiftParams(extent, tile) { const { parent } = tile; if (parent === undefined) return; const parentZoom = parent.zoom; let { i, j, zoom } = tile; // get the scale const scale = 1 << (zoom - parentZoom); // get x and y shift let xShift = 0; let yShift = 0; while (zoom > parentZoom) { const div = 1 << (zoom - parentZoom); if (i % 2 !== 0) xShift += extent / div; if (j % 2 !== 0) yShift += extent / div; // decrement i = i >> 1; j = j >> 1; zoom--; } return { scale, xShift, yShift }; } /** * Scale, shift, and clip polygons according to the tile request and extent * @param geometry - input multi polygon * @param extent - extent is the tile "pixel" size * @param tile - the tile request * @returns the scaled, shifted, and clipped polygons */ export function scaleShiftClipPoints(geometry, extent, tile) { const scaleShift = scaleShiftParams(extent, tile); if (scaleShift === undefined) return geometry; const { scale, xShift, yShift } = scaleShift; return _scaleShiftClipPoints(geometry, extent, xShift, yShift, scale); } /** * Scale, shift, and clip polygons according to the tile request and extent * @param geometry - input multi polygon * @param extent - extent is the tile "pixel" size * @param tile - the tile request * @returns the scaled, shifted, and clipped polygons */ export function scaleShiftClipLines(geometry, extent, tile) { const scaleShift = scaleShiftParams(extent, tile); if (scaleShift === undefined) return geometry; const { scale, xShift, yShift } = scaleShift; return _scaleShiftClipLines(geometry, extent, xShift, yShift, scale); } /** * Scale, shift, and clip polygons according to the tile request and extent * @param geometry - input multi polygon * @param extent - extent is the tile "pixel" size * @param tile - the tile request * @returns the scaled, shifted, and clipped polygons */ export function scaleShiftClipPolys(geometry, extent, tile) { const scaleShift = scaleShiftParams(extent, tile); if (scaleShift === undefined) return geometry; const { scale, xShift, yShift } = scaleShift; return _scaleShiftClipPolys(geometry, extent, xShift, yShift, scale); } /** * scale, shift, and clip lines from linestrings or polygons * @param geometry - input vector geometry * @param extent - extent is the tile "pixel" size * @param xShift - x-coordinate shift * @param yShift - y-coordinate shift * @param scale - scale factor * @returns resultant vector geometry post scale, shift, and clip */ function _scaleShiftClipPolys(geometry, extent, xShift, yShift, scale) { // shift & scale for (const poly of geometry) { for (const line of poly) shiftScale(line, xShift, yShift, scale); } // clip let newGeometry = []; const newGeo = []; for (const poly of geometry) { const newPoly = []; for (const line of poly) newPoly.push(...clipLine(line, extent, true)); if (newPoly.length > 0) newGeo.push(newPoly); } newGeometry = newGeo; if (newGeometry.length > 0) return newGeometry; else return []; } /** * scale, shift, and clip lines from linestrings or polygons * @param geometry - input vector geometry * @param extent - extent is the tile "pixel" size * @param xShift - x-coordinate shift * @param yShift - y-coordinate shift * @param scale - scale factor * @returns resultant vector geometry post scale, shift, and clip */ function _scaleShiftClipLines(geometry, extent, xShift, yShift, scale) { // shift & scale for (const line of geometry) shiftScale(line, xShift, yShift, scale); // clip const newGeo = []; for (const line of geometry) { newGeo.push(...clipLine(line, extent, false)); } if (newGeo.length > 0) return newGeo; else return []; } /** * scale, shift, and clip points * @param geometry - input points * @param extent - extent is the tile "pixel" size * @param xShift - x-coordinate shift * @param yShift - y-coordinate shift * @param scale - scale factor * @returns resultant vector points post scale, shift, and clip */ function _scaleShiftClipPoints(geometry, extent, xShift, yShift, scale) { // shift & scale shiftScale(geometry, xShift, yShift, scale); // clip for (let i = 0; i < geometry.length; i++) { const point = geometry[i]; if (point.x < 0 || point.x > extent || point.y < 0 || point.y > extent) { geometry.splice(i, 1); i--; } } return geometry; } /** * Shift and scale adjustment to a collection of points * @param points - collection of input points * @param xShift - x-coordinate shift * @param yShift - y-coordinate shift * @param scale - scale factor */ function shiftScale(points, xShift, yShift, scale) { for (const point of points) { point.x = (point.x - xShift) * scale; point.y = (point.y - yShift) * scale; } } /** * Clip a collection of lines * @param lines - collection of input lines * @param extent - extent is the tile "pixel" size * @param isPolygon - true if the geometry is a polygon * @param buffer - buffer size * @returns collection of clipped lines */ export function clipLines(lines, extent, isPolygon, buffer = 80) { const res = []; for (const line of lines) res.push(...clipLine(line, extent, isPolygon, buffer)); return res; } /** * uses a buffer of 80 as default * @param line - input line * @param extent - extent is the tile "pixel" size * @param isPolygon - true if the geometry is a polygon * @param buffer - buffer size (default of 80) * @returns collection of clipped lines */ function clipLine(line, extent, isPolygon, buffer = 80) { const res = []; const vertical = []; // slice vertically _clipLine(line, vertical, -buffer, extent + buffer, 1, isPolygon); // slice horizontally for (const vertLine of vertical) _clipLine(vertLine, res, -buffer, extent + buffer, 0, isPolygon); return res; } /** * Clip a line * @param line - input line * @param newGeom - collection of clipped lines to store the result to * @param k1 - lower bound * @param k2 - upper bound * @param axis - axis (0 for x or 1 for y) * @param isPolygon - true if the geometry is a polygon otherwise it's a linestring */ function _clipLine(line, newGeom, k1, k2, axis, isPolygon) { let slice = []; const intersect = axis === 0 ? intersectX : intersectY; const len = line.length - 1; for (let i = 0; i < len; i++) { const ax = line[i].x; const ay = line[i].y; const bx = line[i + 1].x; const by = line[i + 1].y; const a = axis === 0 ? ax : ay; const b = axis === 0 ? bx : by; let exited = false; if (a < k1) { // ---|--> | (line enters the clip region from the left) if (b > k1) intersect(slice, ax, ay, bx, by, k1); } else if (a > k2) { // | <--|--- (line enters the clip region from the right) if (b < k2) intersect(slice, ax, ay, bx, by, k2); } else { slice.push({ x: ax, y: ay }); } if (b < k1 && a >= k1) { // <--|--- | or <--|-----|--- (line exits the clip region on the left) intersect(slice, ax, ay, bx, by, k1); exited = true; } if (b > k2 && a <= k2) { // | ---|--> or ---|-----|--> (line exits the clip region on the right) intersect(slice, ax, ay, bx, by, k2); exited = true; } if (!isPolygon && exited) { newGeom.push(slice); slice = []; } } // add the last point const ax = line[len].x; const ay = line[len].y; const a = axis === 0 ? ax : ay; if (a >= k1 && a <= k2) slice.push({ x: ax, y: ay }); // close the polygon if its endpoints are not the same after clipping if (isPolygon && slice.length < 3) return; const last = slice.length - 1; if (isPolygon && (slice[last].x !== slice[0].x || slice[last].y !== slice[0].y)) { slice.push({ x: slice[0].x, y: slice[0].y }); } // add the final slice if (slice.length > 0) newGeom.push(slice); } /** * Get the X-intersection point * @param out - collection of intersection points to store the result to * @param ax - input point A's x coordinate * @param ay - input point A's y coordinate * @param bx - input point B's x coordinate * @param by - input point B's y coordinate * @param x - intersection point's x coordinate */ function intersectX(out, ax, ay, bx, by, x) { const t = (x - ax) / (bx - ax); out.push({ x, y: ay + (by - ay) * t }); } /** * Get the Y-intersection point * @param out - collection of intersection points to store the result to * @param ax - input point A's x coordinate * @param ay - input point A's y coordinate * @param bx - input point B's x coordinate * @param by - input point B's y coordinate * @param y - intersection point's y coordinate */ function intersectY(out, ax, ay, bx, by, y) { const t = (y - ay) / (by - ay); out.push({ x: ax + (bx - ax) * t, y }); }