UNPKG

open-vector-tile

Version:

This library reads/writes Open Vector Tiles

373 lines 13 kB
/** * Create shapes * * Used by Layer's and Feature's M-Values * Must be an object of key values * all keys will be the same, values will be different * A layer's Shape defines what the properties look like for every Feature in that layer * so we only have to store the properties and M-Value shape **once** per layer. * @param cache - the cache where all data is stored in a column format * @param shape - the shape object to encode * @returns - The index of where the shape was stored in the cache */ export function encodeShape(cache, shape) { // this will store a "shape" of numbers on how to rebuild the object const shapeStore = []; // encode the shape data _encodeShape(shape, shapeStore, cache); // return the index of the shape index return cache.addColumnData(9 /* OColumnName.shapes */, shapeStore); } /** * Encodes a shape object into a format suitable for storing in a cache. * @param shape - the shape object to encode. Recursively encodes nested objects. * @param shapeStore - list of key (string) indices. Also includes if the value is an object or not. * @param cache - the cache where all data is stored in a column format. */ function _encodeShape(shape, shapeStore, cache) { if (Array.isArray(shape)) { shapeStore.push(0); _encodeShape(shape[0], shapeStore, cache); } else if (typeof shape === 'object') { const entries = Object.entries(shape); shapeStore.push(encodeAttribute(1, entries.length)); for (const [key, value] of entries) { // store key shapeStore.push(cache.addColumnData(1 /* OColumnName.string */, key)); _encodeShape(value, shapeStore, cache); } } else { shapeStore.push(encodeAttribute(2, shapePrimitiveToColumnName(shape))); } } /** * @param shapeIndex - the index to the key indices and whether the value is an object or not * @param cache - the cache where all data is stored in a column format * @returns - The shape object */ export function decodeShape(shapeIndex, cache) { const shapeStore = cache.getColumn(9 /* OColumnName.shapes */, shapeIndex); // duplicate the array to avoid modifying the original return _decodeShape(cache, [...shapeStore]); } /** * @param cache - the cache where all data is stored in a column format * @param shapeStore - the shape data encoded as an array * @returns - The shape object */ function _decodeShape(cache, shapeStore) { const attribute = decodeAttribute(shapeStore.shift() ?? 0); if (attribute.type === 0) { // Array return [_decodeShape(cache, shapeStore)]; } else if (attribute.type === 1) { // Object const length = attribute.countOrCol; const obj = {}; for (let i = 0; i < length; i++) { const keyIndex = shapeStore.shift() ?? 0; const key = cache.getColumn(1 /* OColumnName.string */, keyIndex); obj[key] = _decodeShape(cache, shapeStore); } return obj; } else { // Primitive value return columnNameToShapePrimitive(attribute.countOrCol); } } /** * @param value - the value to encode * @param shape - the shape of the value * @param cache - the cache where all data is stored in a column format * @returns - The index of where the value was stored in the cache */ export function encodeValue(value, shape, cache) { const valueStore = []; _encodeValue(value, shape, valueStore, cache); return cache.addColumnData(9 /* OColumnName.shapes */, valueStore); } /** * @param value - the value to encode * @param shape - the shape of the value * @param valueStore - list of key (string) indices. Also includes if the value is an object or not * @param cache - the cache where all data is stored in a column format */ function _encodeValue(value, shape, valueStore, cache) { // we follow the rules of the shape if (Array.isArray(shape)) { value = value; valueStore.push(value.length); for (const v of value) { _encodeValue(v, shape[0], valueStore, cache); } } else if (typeof shape === 'object') { const keys = Object.keys(shape); value = value; for (const key of keys) { // key stored already by shape _encodeValue(value?.[key], shape[key], valueStore, cache); } } else { if (shape === 'string') { valueStore.push(cache.addColumnData(1 /* OColumnName.string */, value ?? '')); } else if (shape === 'u64') { valueStore.push(cache.addNumber(value ?? 0, 2 /* OColumnName.unsigned */)); } else if (shape === 'i64') { valueStore.push(cache.addNumber(value ?? 0, 3 /* OColumnName.signed */)); } else if (shape === 'f32') { valueStore.push(cache.addNumber(value ?? 0, 4 /* OColumnName.float */)); } else if (shape === 'f64') { valueStore.push(cache.addNumber(value ?? 0, 5 /* OColumnName.double */)); } else if (shape === 'bool') { valueStore.push(cache.addNumber(value ? 1 : 0, 2 /* OColumnName.unsigned */)); } } } /** * @param valueIndex - the index of the encoded value in the cache * @param shape - the shape of the value to decode * @param cache - the cache where all data is stored in a column format * @returns The decoded value */ export function decodeValue(valueIndex, shape, cache) { const valueStore = cache.getColumn(9 /* OColumnName.shapes */, valueIndex); // duplicate the array to avoid modifying the original return _decodeValue([...valueStore], shape, cache); } /** * @param valueStore - the encoded value data as an array * @param shape - the shape of the value to decode * @param cache - the cache where all data is stored in a column format * @returns The decoded value */ function _decodeValue(valueStore, shape, cache) { if (Array.isArray(shape)) { const length = valueStore.shift() ?? 0; const arr = []; for (let i = 0; i < length; i++) { arr.push(_decodeValue(valueStore, shape[0], cache)); } return arr; } else if (typeof shape === 'object') { const obj = {}; for (const key in shape) { obj[key] = _decodeValue(valueStore, shape[key], cache); } return obj; } else { if (shape === 'null') return null; const columnValue = valueStore.shift() ?? 0; if (shape === 'string') { return cache.getColumn(1 /* OColumnName.string */, columnValue); } else if (shape === 'bool') { return cache.getColumn(2 /* OColumnName.unsigned */, columnValue) !== 0; } else if (shape === 'u64') { return cache.getColumn(2 /* OColumnName.unsigned */, columnValue); } else if (shape === 'i64') { return cache.getColumn(3 /* OColumnName.signed */, columnValue); } else if (shape === 'f32') { return cache.getColumn(4 /* OColumnName.float */, columnValue); } else { // f64 return cache.getColumn(5 /* OColumnName.double */, columnValue); } } } /** * @param type - 0 is array, 1 is object, 2 is value * @param countOrColname - the length of the object or array; if value, its the column name * - can match columns for [0-4] (string, u64, i64, f32, f64), * - or matches a type: 5 represents bool and null, 6 represents array, 7 represents object * @returns - the encoded message */ function encodeAttribute(type, countOrColname) { return (countOrColname << 2) + type; } /** * @param num - the column and index encoded together * @returns - the decoded message */ function decodeAttribute(num) { return { type: (num & 0b11), countOrCol: num >> 2 }; } /** * @param type - the primitive shape to convert * @returns - the column name corresponding to the shape primitive */ function shapePrimitiveToColumnName(type) { if (type === 'string') return 1 /* OColumnName.string */; else if (type === 'u64') return 2 /* OColumnName.unsigned */; else if (type === 'i64') return 3 /* OColumnName.signed */; else if (type === 'f32') return 4 /* OColumnName.float */; else if (type === 'f64') return 5 /* OColumnName.double */; else if (type === 'bool') return 6; else return 7; } /** * @param columnName - the column name to convert * @returns - the primitive shape corresponding to the column name */ function columnNameToShapePrimitive(columnName) { if (columnName === 1 /* OColumnName.string */) return 'string'; else if (columnName === 2 /* OColumnName.unsigned */) return 'u64'; else if (columnName === 3 /* OColumnName.signed */) return 'i64'; else if (columnName === 4 /* OColumnName.float */) return 'f32'; else if (columnName === 5 /* OColumnName.double */) return 'f64'; else if (columnName === 6) return 'bool'; else return 'null'; } //? The Following are utility functions when the user doesn't pre-define the Properties/M-Value //? Shapes to store: /** * @param data - the data to create the shape from * @returns - the shape type we want to create based upon the data */ export function createShapeFromData(data) { const shape = {}; for (const d of data) { for (const k in d) shape[k] = getShapesValueType(d[k]); } return shape; } /** * Update/Mutate the shape from the data provided * @param shape - the shape * @param data - the data to update the shape */ export function updateShapeFromData(shape, data) { for (const k in data) shape[k] = getShapesValueType(data[k]); } /** * @param value - conform to the rules of OValue * @returns - the shape type */ function getShapesValueType(value) { if (Array.isArray(value)) { // If it's a number type and the first number is a u64 but the second is an i64 or f64? // otherwise just return the first case in the array const types = value.map(getShapesValueType); return [validateTypes(types)]; } else if (typeof value === 'object' && value !== null) { return createShapeFromData([value]); } else { return getPrimitiveType(value); } } /** * @param value - the primtive value to get the type from * @returns - the primitive type from the value */ function getPrimitiveType(value) { const type = typeof value; if (type === 'string') { return 'string'; } else if (type === 'number') { if (Number.isInteger(value)) { return value < 0 ? 'i64' : 'u64'; } else { return 'f64'; } } else if (type === 'boolean') { return 'bool'; } else { return 'null'; } } /** * This is primarily to check if the type is a primitive. * If the primitive is a number, find the "depth", the most complex is f64, then i64, then u64. * Otherwise, if the primitives don't match, throw an error. * If the type is NOT a primitive, ensure that all types in the array match * @param types - either a primitive type, array, or object * @returns - a single type from the list to validate the correct type to be parsed from values later */ export function validateTypes(types) { if (typeof types[0] === 'string') { // first ensure all primitive types are the same (excluding numbers) let baseType = types[0]; const basePrim = isNumber(baseType); for (const type of types) { if (type !== baseType) { if (isNumber(type) && basePrim) { baseType = getHighestOrderNumber(baseType, type); } else { throw new Error('All types must be the same'); } } } return baseType; } else { const typeCheck = types.every((t) => typeof t === typeof types[0] && Array.isArray(t) === Array.isArray(types[0])); if (!typeCheck) throw new Error('All types must be the same'); return types[0]; } } /** * given a primitive type, check if one of them is a "number" type * @param type - any primitive type * @returns - true if the type is a number */ function isNumber(type) { return type === 'i64' || type === 'u64' || type === 'f32' || type === 'f64'; } /** * @param typeA - either i64, u64, or f64 * @param typeB - either i64, u64, or f64 * @returns - the "highest order number" e.g. f64 > i64 > u64 */ function getHighestOrderNumber(typeA, typeB) { if (typeA === 'f64' || typeB === 'f64') { return 'f64'; } else if (typeA === 'i64' || typeB === 'i64') { return 'i64'; } else { return 'u64'; } } //# sourceMappingURL=shape.js.map