mapbox-gl
Version:
A WebGL interactive maps library
91 lines (77 loc) • 3.44 kB
JavaScript
// @flow
import {warnOnce, clamp} from '../util/util.js';
import EXTENT from './extent.js';
import {lngFromMercatorX, latFromMercatorY} from '../geo/mercator_coordinate.js';
import resample from '../geo/projection/resample.js';
import Point from '@mapbox/point-geometry';
import type {CanonicalTileID} from '../source/tile_id.js';
import type {TileTransform} from '../geo/projection/tile_transform.js';
// These bounds define the minimum and maximum supported coordinate values.
// While visible coordinates are within [0, EXTENT], tiles may theoretically
// contain coordinates within [-Infinity, Infinity]. Our range is limited by the
// number of bits used to represent the coordinate.
const BITS = 15;
const MAX = Math.pow(2, BITS - 1) - 1;
const MIN = -MAX - 1;
function preparePoint(point: Point, scale: number) {
const x = Math.round(point.x * scale);
const y = Math.round(point.y * scale);
point.x = clamp(x, MIN, MAX);
point.y = clamp(y, MIN, MAX);
if (x < point.x || x > point.x + 1 || y < point.y || y > point.y + 1) {
// warn when exceeding allowed extent except for the 1-px-off case
// https://github.com/mapbox/mapbox-gl-js/issues/8992
warnOnce('Geometry exceeds allowed extent, reduce your vector tile buffer size');
}
return point;
}
// a subset of VectorTileGeometry
interface FeatureWithGeometry {
extent: number;
type: 1 | 2 | 3;
loadGeometry(): Array<Array<Point>>;
}
/**
* Loads a geometry from a VectorTileFeature and scales it to the common extent
* used internally.
* @param {VectorTileFeature} feature
* @private
*/
export default function loadGeometry(feature: FeatureWithGeometry, canonical?: CanonicalTileID, tileTransform?: TileTransform): Array<Array<Point>> {
const geometry = feature.loadGeometry();
const extent = feature.extent;
const extentScale = EXTENT / extent;
if (canonical && tileTransform && tileTransform.projection.isReprojectedInTileSpace) {
const z2 = 1 << canonical.z;
const {scale, x, y, projection} = tileTransform;
const reproject = (p: Point) => {
const lng = lngFromMercatorX((canonical.x + p.x / extent) / z2);
const lat = latFromMercatorY((canonical.y + p.y / extent) / z2);
const p2 = projection.project(lng, lat);
p.x = (p2.x * scale - x) * extent;
p.y = (p2.y * scale - y) * extent;
};
for (let i = 0; i < geometry.length; i++) {
if (feature.type !== 1) {
geometry[i] = resample(geometry[i], reproject, 1); // resample lines and polygons
} else { // points
const line = [];
for (const p of geometry[i]) {
// filter out point features outside tile boundaries now; it'd be harder to do later
// when the coords are reprojected and no longer axis-aligned; ideally this would happen
// or not depending on how the geometry is used, but we forego the complexity for now
if (p.x < 0 || p.x >= extent || p.y < 0 || p.y >= extent) continue;
reproject(p);
line.push(p);
}
geometry[i] = line;
}
}
}
for (const line of geometry) {
for (const p of line) {
preparePoint(p, extentScale);
}
}
return geometry;
}