Skip to content

Commit

Permalink
feat(mvt): TableTileSource refactor, improved typing (#2990)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed May 17, 2024
1 parent 2705434 commit acd10a2
Show file tree
Hide file tree
Showing 32 changed files with 941 additions and 267 deletions.
19 changes: 13 additions & 6 deletions modules/mvt/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

export type {MVTLoaderOptions} from './mvt-loader';
export {MVTLoader, MVTWorkerLoader} from './mvt-loader';
// TileJSONLoader

export type {TileJSON} from './lib/parse-tilejson';
export type {TileJSONLoaderOptions} from './tilejson-loader';
export {TileJSONLoader} from './tilejson-loader';
export type {TileJSONLoaderOptions} from './tilejson-loader';
export type {TileJSON} from './lib/parse-tilejson';

// MVTLoader

export {MVTLoader, MVTWorkerLoader} from './mvt-loader';
export type {MVTLoaderOptions} from './mvt-loader';

// MVTSource

export {MVTSource} from './mvt-source';
export type {MVTTileSource, MVTTileSourceProps} from './mvt-source';

// TableTileSource
// TableTileSource (dynamically tiles a table)

export type {VectorTilerSource, VectorTilerSourceProps} from './table-tile-source';
export {TableTileSource} from './table-tile-source';
export type {DynamicVectorTileSource, DynamicVectorTileSourceProps} from './table-tile-source';
2 changes: 1 addition & 1 deletion modules/mvt/src/lib/parse-tilejson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ function attributeTypeToFieldType(aType: string): {type: string} {
const type = aType.toLowerCase();
if (!type || !attrTypeMap[type]) {
// console.warn(
// `cannot convert attribute type ${type} to loaders.gl data type, use string by default`
// `cannot convert feature type ${type} to loaders.gl data type, use string by default`
// );
}
return attrTypeMap[type] || {type: 'string'};
Expand Down
2 changes: 1 addition & 1 deletion modules/mvt/src/lib/utils/geometry-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function convertToLocalCoordinates(
* @param feature
*/
export function convertToLocalCoordinatesFlat(data: number[], extent: number): void {
for (let i = 0, il = data.length; i < il; ++i) {
for (let i = 0; i < data.length; ++i) {
data[i] /= extent;
}
}
Expand Down
6 changes: 3 additions & 3 deletions modules/mvt/src/lib/vector-tiler/features/clip-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// Forked from https://github.com/mapbox/geojson-vt under compatible ISC license

import type {ProtoFeature} from './proto-feature';
import {createFeature} from './proto-feature';
import {createProtoFeature} from './proto-feature';

/* eslint-disable no-continue */

Expand Down Expand Up @@ -82,7 +82,7 @@ export function clipFeatures(
if (newGeometry.length) {
if (options.lineMetrics && type === 'LineString') {
for (const line of newGeometry) {
clipped.push(createFeature(feature.id, type, line, feature.tags));
clipped.push(createProtoFeature(feature.id, type, line, feature.tags));
}
continue;
}
Expand All @@ -100,7 +100,7 @@ export function clipFeatures(
type = newGeometry.length === 3 ? 'Point' : 'MultiPoint';
}

clipped.push(createFeature(feature.id, type, newGeometry, feature.tags));
clipped.push(createProtoFeature(feature.id, type, newGeometry, feature.tags));
}
}

Expand Down
159 changes: 90 additions & 69 deletions modules/mvt/src/lib/vector-tiler/features/convert-feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,8 @@
import type {Feature, FeatureCollection} from '@loaders.gl/schema';
import type {ProtoFeature} from './proto-feature';

import {simplify} from './simplify-path';
import {createFeature} from './proto-feature';

/**
* converts a GeoJSON feature into an intermediate projected JSON vector format
* with simplification data
*/
export function convertFeatures(data: Feature | FeatureCollection, options): ProtoFeature[] {
const features = [];
if (data.type === 'FeatureCollection') {
for (let i = 0; i < data.features.length; i++) {
convertFeature(features, data.features[i], options, i);
}
} else if (data.type === 'Feature') {
convertFeature(features, data, options);
} else {
// single geometry or a geometry collection
convertFeature(features, {geometry: data}, options);
}

return features;
}
import {createProtoFeature} from './proto-feature';
import {simplifyPath} from './simplify-path';

export type ConvertFeatureOptions = {
/** max zoom to preserve detail on */
Expand All @@ -43,16 +23,43 @@ export type ConvertFeatureOptions = {
lineMetrics?: boolean;
};

/**
* converts a GeoJSON feature into an intermediate projected JSON vector format
* with simplification data
*/
export function convertFeaturesToProtoFeature(
data: Feature | FeatureCollection,
options: ConvertFeatureOptions
): ProtoFeature[] {
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(
features: ProtoFeature[],
geojson: Feature,
options: ConvertFeatureOptions,
index: number
): void {
): ProtoFeature {
// GeoJSON geometries can be null, but no vector tile will include them.
if (!geojson.geometry) {
return;
}
Expand All @@ -67,53 +74,67 @@ function convertFeature(
} else if (options.generateId) {
id = index || 0;
}
if (type === 'Point') {
convertPoint(coords, geometry);
} else if (type === 'MultiPoint') {
for (const p of coords) {
convertPoint(p, geometry);
}
} else if (type === 'LineString') {
convertLine(coords, geometry, tolerance, false);
} else if (type === '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(createFeature(id, 'LineString', geometry, geojson.properties));

switch (type) {
case 'Point':
convertPoint(coords, geometry);
break;

case 'MultiPoint':
for (const p of coords) {
convertPoint(p, geometry);
}
return;
} else {
convertLines(coords, geometry, tolerance, false);
}
} else if (type === 'Polygon') {
convertLines(coords, geometry, tolerance, true);
} else if (type === 'MultiPolygon') {
for (const polygon of coords) {
const newPolygon = [];
convertLines(polygon, newPolygon, tolerance, true);
geometry.push(newPolygon);
}
} else if (type === 'GeometryCollection') {
for (const singleGeometry of geojson.geometry.geometries) {
convertFeature(
features,
{
id,
geometry: singleGeometry,
properties: geojson.properties
},
options,
index
);
}
return;
} else {
throw new Error('Input data is not a valid GeoJSON object.');
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.');
}

features.push(createFeature(id, type, geometry, geojson.properties));
return createProtoFeature(id, type, geometry, geojson.properties);
}

function convertPoint(coords, out): void {
Expand Down Expand Up @@ -143,7 +164,7 @@ function convertLine(ring: number[], out, tolerance: number, isPolygon: boolean)

const last = out.length - 3;
out[2] = 1;
simplify(out, 0, last, tolerance);
simplifyPath(out, 0, last, tolerance);
out[last + 2] = 1;

out.size = Math.abs(size);
Expand Down
94 changes: 69 additions & 25 deletions modules/mvt/src/lib/vector-tiler/features/proto-feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,101 @@
// Forked from https://github.com/mapbox/geojson-vt under compatible ISC license

export type ProtoFeature = {
type: any;
geometry: any;
type: 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | 'Polygon' | 'MultiPolygon';
simplifiedType: 1 | 2 | 3;
geometry: any[];

// book keeping
id?: string;
tags?: string[];
tags?: Record<string, unknown>;

// spatial extents
/** spatial extents */
minX: number;
/** spatial extents */
maxX: number;
/** spatial extents */
minY: number;
/** spatial extents */
maxY: number;
};

export function createFeature(id, type, geom, tags): ProtoFeature {
export type GeoJSONTileGeometry =
| GeoJSONTilePointGeometry
| GeoJSONTileLineGeometry
| GeoJSONTilePolygonGeometry;

export type GeoJSONTilePointGeometry = {
simplifiedType: 1;
geometry: number[];
};

export type GeoJSONTileLineGeometry = {
simplifiedType: 1;
geometry: number[][];
};

export type GeoJSONTilePolygonGeometry = {
simplifiedType: 1;
geometry: number[][][];
};

export function createProtoFeature(
id: any,
type: 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | 'Polygon' | 'MultiPolygon',
geometry: any[],
tags
): ProtoFeature {
const feature: ProtoFeature = {
// eslint-disable-next-line
id: id == null ? null : id,
type,
geometry: geom,
simplifiedType: undefined!, // TODO
geometry,
tags,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};

if (type === 'Point' || type === 'MultiPoint' || type === 'LineString') {
calcLineBBox(feature, geom);
} else if (type === 'Polygon') {
// the outer ring (ie [0]) contains all inner rings
calcLineBBox(feature, geom[0]);
} else if (type === 'MultiLineString') {
for (const line of geom) {
calcLineBBox(feature, line);
}
} else if (type === 'MultiPolygon') {
for (const polygon of geom) {
// TODO break out into separate function
switch (type) {
case 'Point':
case 'MultiPoint':
case 'LineString':
calcLineBBox(feature, geometry);
break;

case 'MultiLineString':
for (const line of geometry) {
calcLineBBox(feature, line);
}
break;

case 'Polygon':
// the outer ring (ie [0]) contains all inner rings
calcLineBBox(feature, polygon[0]);
}
calcLineBBox(feature, geometry[0]);
break;

case 'MultiPolygon':
for (const polygon of geometry) {
// the outer ring (ie [0]) contains all inner rings
calcLineBBox(feature, polygon[0]);
}
break;

default:
throw new Error(String(type));
}

return feature;
}

function calcLineBBox(feature, geom) {
for (let i = 0; i < geom.length; i += 3) {
feature.minX = Math.min(feature.minX, geom[i]);
feature.minY = Math.min(feature.minY, geom[i + 1]);
feature.maxX = Math.max(feature.maxX, geom[i]);
feature.maxY = Math.max(feature.maxY, geom[i + 1]);
function calcLineBBox(feature, geometry) {
for (let i = 0; i < geometry.length; i += 3) {
feature.minX = Math.min(feature.minX, geometry[i]);
feature.minY = Math.min(feature.minY, geometry[i + 1]);
feature.maxX = Math.max(feature.maxX, geometry[i]);
feature.maxY = Math.max(feature.maxY, geometry[i + 1]);
}
}

0 comments on commit acd10a2

Please sign in to comment.