import { PhysicalStreamType } from "../metadata/tile/physicalStreamType"; import { DictionaryType } from "../metadata/tile/dictionaryType"; import { LengthType } from "../metadata/tile/lengthType"; import { OffsetType } from "../metadata/tile/offsetType"; import { PhysicalLevelTechnique } from "../metadata/tile/physicalLevelTechnique"; import { LogicalLevelTechnique } from "../metadata/tile/logicalLevelTechnique"; import IntWrapper from "../decoding/intWrapper"; import { encodeBooleanRle, encodeStrings, createStringLengths, concatenateBuffers } from "./encodingUtils"; import { encodeVarintInt32Value, encodeVarintInt32 } from "./integerEncodingUtils"; /** * Encodes plain strings into a complete stream with PRESENT (if needed), LENGTH, and DATA streams. * @param strings - Array of strings (can include null values) * @returns Encoded Uint8Array that can be passed to decodeString */ export function encodePlainStrings(strings) { const hasNull = strings.some((s) => s === null); const nonNullStrings = strings.filter((s) => s !== null); const stringBytes = encodeStrings(nonNullStrings); const streams = []; // Add PRESENT stream if nulls exist if (hasNull) { const nullabilityValues = strings.map((s) => s !== null); streams.push(createStream(PhysicalStreamType.PRESENT, encodeBooleanRle(nullabilityValues), { technique: PhysicalLevelTechnique.VARINT, count: nullabilityValues.length, })); } // Add LENGTH stream const lengths = createStringLengths(nonNullStrings); streams.push(createStream(PhysicalStreamType.LENGTH, encodeVarintInt32(lengths), { logical: { lengthType: LengthType.VAR_BINARY }, technique: PhysicalLevelTechnique.VARINT, count: lengths.length, })); // Add DATA stream streams.push(createStream(PhysicalStreamType.DATA, stringBytes, { logical: { dictionaryType: DictionaryType.NONE }, })); return concatenateBuffers(...streams); } /** * Encodes dictionary-compressed strings into a complete stream. * @param strings - Array of strings (can include null values) * @returns Encoded Uint8Array that can be passed to decodeString */ export function encodeDictionaryStrings(strings) { const hasNull = strings.some((s) => s === null); const nonNullStrings = strings.filter((s) => s !== null); // Create dictionary of unique strings const uniqueStrings = Array.from(new Set(nonNullStrings)); const stringMap = new Map(uniqueStrings.map((s, i) => [s, i])); const offsets = nonNullStrings.map((s) => { const offset = stringMap.get(s); if (offset === undefined) { throw new Error(`String not found in dictionary: ${s}`); } return offset; }); const stringBytes = encodeStrings(uniqueStrings); const lengths = createStringLengths(uniqueStrings); const streams = []; // Add PRESENT stream if nulls exist if (hasNull) { const nullabilityValues = strings.map((s) => s !== null); streams.push(createStream(PhysicalStreamType.PRESENT, encodeBooleanRle(nullabilityValues), { technique: PhysicalLevelTechnique.VARINT, count: nullabilityValues.length, })); } // Add OFFSET stream streams.push(createStream(PhysicalStreamType.OFFSET, encodeVarintInt32(new Uint32Array(offsets)), { logical: { offsetType: OffsetType.STRING }, technique: PhysicalLevelTechnique.VARINT, count: offsets.length, })); // Add LENGTH stream (for dictionary) streams.push(createStream(PhysicalStreamType.LENGTH, encodeVarintInt32(lengths), { logical: { lengthType: LengthType.DICTIONARY }, technique: PhysicalLevelTechnique.VARINT, count: lengths.length, })); // Add DATA stream streams.push(createStream(PhysicalStreamType.DATA, stringBytes, { logical: { dictionaryType: DictionaryType.SINGLE }, })); return concatenateBuffers(...streams); } function createStream(physicalType, data, options = {}) { const count = options.count ?? 0; return buildEncodedStream({ physicalStreamType: physicalType, logicalStreamType: options.logical ?? {}, logicalLevelTechnique1: LogicalLevelTechnique.NONE, logicalLevelTechnique2: LogicalLevelTechnique.NONE, physicalLevelTechnique: options.technique ?? PhysicalLevelTechnique.NONE, numValues: count, byteLength: data.length, decompressedCount: count, }, data); } function buildEncodedStream(streamMetadata, encodedData) { const updatedMetadata = { ...streamMetadata, byteLength: encodedData.length, }; const metadataBuffer = encodeStreamMetadata(updatedMetadata); const result = new Uint8Array(metadataBuffer.length + encodedData.length); result.set(metadataBuffer, 0); result.set(encodedData, metadataBuffer.length); return result; } function encodeStreamMetadata(metadata) { const buffer = new Uint8Array(100); let writeOffset = 0; // Byte 1: Stream type const physicalTypeIndex = Object.values(PhysicalStreamType).indexOf(metadata.physicalStreamType); const lowerNibble = getLogicalSubtypeValue(metadata); buffer[writeOffset++] = (physicalTypeIndex << 4) | lowerNibble; // Byte 2: Encoding techniques const llt1Index = Object.values(LogicalLevelTechnique).indexOf(metadata.logicalLevelTechnique1); const llt2Index = Object.values(LogicalLevelTechnique).indexOf(metadata.logicalLevelTechnique2); const pltIndex = Object.values(PhysicalLevelTechnique).indexOf(metadata.physicalLevelTechnique); buffer[writeOffset++] = (llt1Index << 5) | (llt2Index << 2) | pltIndex; // Variable-length fields const offset = new IntWrapper(writeOffset); encodeVarintInt32Value(metadata.numValues, buffer, offset); encodeVarintInt32Value(metadata.byteLength, buffer, offset); return buffer.slice(0, offset.get()); } function getLogicalSubtypeValue(metadata) { const { physicalStreamType, logicalStreamType } = metadata; switch (physicalStreamType) { case PhysicalStreamType.DATA: return logicalStreamType.dictionaryType !== undefined ? Object.values(DictionaryType).indexOf(logicalStreamType.dictionaryType) : 0; case PhysicalStreamType.OFFSET: return logicalStreamType.offsetType !== undefined ? Object.values(OffsetType).indexOf(logicalStreamType.offsetType) : 0; case PhysicalStreamType.LENGTH: return logicalStreamType.lengthType !== undefined ? Object.values(LengthType).indexOf(logicalStreamType.lengthType) : 0; default: return 0; } } //# sourceMappingURL=stringEncoder.js.map