• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / loaders.gl / 25070233425

28 Apr 2026 06:20PM UTC coverage: 59.434% (+0.01%) from 59.423%
25070233425

push

github

web-flow
website: Add tabs for navigating between format docs (#3407)

11310 of 20887 branches covered (54.15%)

Branch coverage included in aggregate %.

89 of 136 new or added lines in 13 files covered. (65.44%)

1742 existing lines in 132 files now uncovered.

23500 of 37682 relevant lines covered (62.36%)

16296.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/modules/shapefile/src/lib/streaming/binary-chunk-reader.ts
1
// loaders.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
export type BinaryChunkReaderOptions = {
6
  maxRewindBytes: number;
7
};
8

9
export class BinaryChunkReader {
10
  offset: number;
11
  arrayBuffers: ArrayBuffer[];
12
  ended: boolean;
13
  maxRewindBytes: number;
14

15
  constructor(options?: BinaryChunkReaderOptions) {
16
    const {maxRewindBytes = 0} = options || {};
477✔
17

18
    /** current global offset into current array buffer*/
19
    this.offset = 0;
477✔
20
    /** current buffer from iterator */
21
    this.arrayBuffers = [];
477✔
22
    this.ended = false;
477✔
23

24
    /** bytes behind offset to hold on to */
25
    this.maxRewindBytes = maxRewindBytes;
477✔
26
  }
27
  /**
28
   * @param arrayBuffer
29
   */
30
  write(arrayBuffer: ArrayBuffer): void {
31
    this.arrayBuffers.push(arrayBuffer);
501✔
32
  }
33

34
  end(): void {
35
    this.arrayBuffers = [];
433✔
36
    this.ended = true;
433✔
37
  }
38

39
  /**
40
   * Has enough bytes available in array buffers
41
   *
42
   * @param bytes Number of bytes
43
   * @return boolean
44
   */
45
  hasAvailableBytes(bytes: number): boolean {
46
    let bytesAvailable = -this.offset;
1,900✔
47
    for (const arrayBuffer of this.arrayBuffers) {
1,900✔
48
      bytesAvailable += arrayBuffer.byteLength;
1,695✔
49
      if (bytesAvailable >= bytes) {
1,695✔
50
        return true;
1,478✔
51
      }
52
    }
53
    return false;
422✔
54
  }
55

56
  /**
57
   * Find offsets of byte ranges within this.arrayBuffers
58
   *
59
   * @param  bytes Byte length to read
60
   * @return Arrays with byte ranges pointing to this.arrayBuffers, Output type is nested array, e.g. [ [0, [1, 2]], ...]
61
   */
62
  findBufferOffsets(bytes: number): any[] | null {
63
    let offset = -this.offset;
3,105✔
64
    const selectedBuffers: any = [];
3,105✔
65

66
    for (let i = 0; i < this.arrayBuffers.length; i++) {
3,105✔
67
      const buf = this.arrayBuffers[i];
3,118✔
68

69
      // Current buffer isn't long enough to reach global offset
70
      if (offset + buf.byteLength <= 0) {
3,118✔
71
        offset += buf.byteLength;
14✔
72
        // eslint-disable-next-line no-continue
73
        continue;
14✔
74
      }
75

76
      // Find start/end offsets for this buffer
77
      // When offset < 0, need to skip over Math.abs(offset) bytes
78
      // When offset > 0, implies bytes in previous buffer, start at 0
79
      const start = offset <= 0 ? Math.abs(offset) : 0;
3,104✔
80
      let end: number;
81

82
      // Length of requested bytes is contained in current buffer
83
      if (start + bytes <= buf.byteLength) {
3,118✔
84
        end = start + bytes;
3,075✔
85
        selectedBuffers.push([i, [start, end]]);
3,075✔
86
        return selectedBuffers;
3,075✔
87
      }
88

89
      // Will need to look into next buffer
90
      end = buf.byteLength;
29✔
91
      selectedBuffers.push([i, [start, end]]);
29✔
92

93
      // Need to read fewer bytes in next iter
94
      bytes -= buf.byteLength - start;
29✔
95
      offset += buf.byteLength;
29✔
96
    }
97

98
    // Should only finish loop if exhausted all arrays
UNCOV
99
    return null;
30✔
100
  }
101

102
  /**
103
   * Get the required number of bytes from the iterator
104
   *
105
   * @param bytes Number of bytes
106
   * @return DataView with data
107
   */
108
  getDataView(bytes: number): DataView | null {
109
    const bufferOffsets = this.findBufferOffsets(bytes);
3,089✔
110
    // return `null` if not enough data, except if end() already called, in
111
    // which case throw an error.
112
    if (!bufferOffsets && this.ended) {
3,089✔
UNCOV
113
      throw new Error('binary data exhausted');
15✔
114
    }
115

116
    if (!bufferOffsets) {
3,074✔
UNCOV
117
      return null;
15✔
118
    }
119

120
    // If only one arrayBuffer needed, return DataView directly
121
    if (bufferOffsets.length === 1) {
3,059✔
122
      const [bufferIndex, [start, end]] = bufferOffsets[0];
3,053✔
123
      const arrayBuffer = this.arrayBuffers[bufferIndex];
3,053✔
124
      const view = new DataView(arrayBuffer, start, end - start);
3,053✔
125

126
      this.offset += bytes;
3,053✔
127
      this.disposeBuffers();
3,053✔
128
      return view;
3,053✔
129
    }
130

131
    // Concatenate portions of multiple ArrayBuffers
132
    const view = new DataView(this._combineArrayBuffers(bufferOffsets));
6✔
133
    this.offset += bytes;
6✔
134
    this.disposeBuffers();
6✔
135
    return view;
6✔
136
  }
137

138
  /**
139
   * Dispose of old array buffers
140
   */
141
  disposeBuffers(): void {
142
    while (
3,059✔
143
      this.arrayBuffers.length > 0 &&
6,132✔
144
      this.offset - this.maxRewindBytes >= this.arrayBuffers[0].byteLength
145
    ) {
146
      this.offset -= this.arrayBuffers[0].byteLength;
233✔
147
      this.arrayBuffers.shift();
233✔
148
    }
149
  }
150

151
  /**
152
   * Copy multiple ArrayBuffers into one contiguous ArrayBuffer
153
   *
154
   * In contrast to concatenateArrayBuffers, this only copies the necessary
155
   * portions of the source arrays, rather than first copying the entire arrays
156
   * then taking a part of them.
157
   *
158
   * @param bufferOffsets List of internal array offsets
159
   * @return New contiguous ArrayBuffer
160
   */
161
  _combineArrayBuffers(bufferOffsets: any[]): ArrayBufferLike {
162
    let byteLength: number = 0;
6✔
163
    for (const bufferOffset of bufferOffsets) {
6✔
164
      const [start, end] = bufferOffset[1];
12✔
165
      byteLength += end - start;
12✔
166
    }
167

168
    const result = new Uint8Array(byteLength);
6✔
169

170
    // Copy the subarrays
171
    let resultOffset: number = 0;
6✔
172
    for (const bufferOffset of bufferOffsets) {
6✔
173
      const [bufferIndex, [start, end]] = bufferOffset;
12✔
174
      const sourceArray = new Uint8Array(this.arrayBuffers[bufferIndex]);
12✔
175
      result.set(sourceArray.subarray(start, end), resultOffset);
12✔
176
      resultOffset += end - start;
12✔
177
    }
178

179
    return result.buffer;
6✔
180
  }
181
  /**
182
   * @param bytes
183
   */
184
  skip(bytes: number): void {
185
    this.offset += bytes;
1,150✔
186
  }
187
  /**
188
   * @param bytes
189
   */
190
  rewind(bytes: number): void {
191
    // TODO - only works if offset is already set
192
    this.offset -= bytes;
743✔
193
  }
194
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc