@loaders.gl/mvt
Version:
Loader for Mapbox Vector Tiles
141 lines (140 loc) • 4.73 kB
JavaScript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
// Forked from https://github.com/mapbox/geojson-vt under compatible ISC license
import { createProtoFeature } from "./proto-feature.js";
import { simplifyPath } from "./simplify-path.js";
/**
* converts a GeoJSON feature into an intermediate projected JSON vector format
* with simplification data
*/
export function convertFeaturesToProtoFeature(data, options) {
const protoFeatures = [];
switch (data.type) {
case 'FeatureCollection':
let i = 0;
for (const feature of data.features) {
protoFeatures.push(convertFeature(feature, options, i++));
}
break;
case 'Feature':
protoFeatures.push(convertFeature(data, options));
break;
default:
// single geometry or a geometry collection
protoFeatures.push(convertFeature({ geometry: data }, options));
}
return protoFeatures;
}
/**
* converts a GeoJSON feature into an intermediate projected JSON vector format
* with simplification data
*/
function convertFeature(geojson, options, index) {
// GeoJSON geometries can be null, but no vector tile will include them.
if (!geojson.geometry) {
return;
}
const coords = geojson.geometry.coordinates;
const type = geojson.geometry.type;
const tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2);
let geometry = [];
let id = geojson.id;
if (options.promoteId) {
id = geojson.properties[options.promoteId];
}
else if (options.generateId) {
id = index || 0;
}
switch (type) {
case 'Point':
convertPoint(coords, geometry);
break;
case 'MultiPoint':
for (const p of coords) {
convertPoint(p, geometry);
}
break;
case 'LineString':
convertLine(coords, geometry, tolerance, false);
break;
case 'MultiLineString':
if (options.lineMetrics) {
// explode into linestrings to be able to track metrics
for (const line of coords) {
geometry = [];
convertLine(line, geometry, tolerance, false);
features.push(createProtoFeature(id, 'LineString', geometry, geojson.properties));
}
return;
convertLines(coords, geometry, tolerance, false);
}
break;
case 'Polygon':
convertLines(coords, geometry, tolerance, true);
break;
case 'MultiPolygon':
for (const polygon of coords) {
const newPolygon = [];
convertLines(polygon, newPolygon, tolerance, true);
geometry.push(newPolygon);
}
break;
case 'GeometryCollection':
for (const singleGeometry of geojson.geometry.geometries) {
convertFeature(features, {
id,
geometry: singleGeometry,
properties: geojson.properties
}, options, index);
}
break;
default:
throw new Error('Input data is not a valid GeoJSON object.');
}
return createProtoFeature(id, type, geometry, geojson.properties);
}
function convertPoint(coords, out) {
out.push(projectX(coords[0]), projectY(coords[1]), 0);
}
function convertLine(ring, out, tolerance, isPolygon) {
let x0, y0;
let size = 0;
for (let j = 0; j < ring.length; j++) {
const x = projectX(ring[j][0]);
const y = projectY(ring[j][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;
}
const last = out.length - 3;
out[2] = 1;
simplifyPath(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) {
for (let i = 0; i < rings.length; i++) {
const geom = [];
convertLine(rings[i], geom, tolerance, isPolygon);
out.push(geom);
}
}
function projectX(x) {
return x / 360 + 0.5;
}
function projectY(y) {
const sin = Math.sin((y * Math.PI) / 180);
const y2 = 0.5 - (0.25 * Math.log((1 + sin) / (1 - sin))) / Math.PI;
return y2 < 0 ? 0 : y2 > 1 ? 1 : y2;
}