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

visgl / loaders.gl / 25603943277

09 May 2026 02:49PM UTC coverage: 60.005%. Remained the same
25603943277

push

github

web-flow
chore: Add Node 26 to CI (#3420)

12967 of 23938 branches covered (54.17%)

Branch coverage included in aggregate %.

26843 of 42406 relevant lines covered (63.3%)

14662.34 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 || {};
474✔
17

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

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

34
  end(): void {
35
    this.arrayBuffers = [];
444✔
36
    this.ended = true;
444✔
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;
2,146✔
47
    for (const arrayBuffer of this.arrayBuffers) {
2,146✔
48
      bytesAvailable += arrayBuffer.byteLength;
1,934✔
49
      if (bytesAvailable >= bytes) {
1,934✔
50
        return true;
1,640✔
51
      }
52
    }
53
    return false;
506✔
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,272✔
64
    const selectedBuffers: any = [];
3,272✔
65

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

69
      // Current buffer isn't long enough to reach global offset
70
      if (offset + buf.byteLength <= 0) {
3,285✔
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,271✔
80
      let end: number;
81

82
      // Length of requested bytes is contained in current buffer
83
      if (start + bytes <= buf.byteLength) {
3,285✔
84
        end = start + bytes;
3,242✔
85
        selectedBuffers.push([i, [start, end]]);
3,242✔
86
        return selectedBuffers;
3,242✔
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
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,256✔
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,256✔
113
      throw new Error('binary data exhausted');
15✔
114
    }
115

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

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

126
      this.offset += bytes;
3,220✔
127
      this.disposeBuffers();
3,220✔
128
      return view;
3,220✔
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,226✔
143
      this.arrayBuffers.length > 0 &&
6,466✔
144
      this.offset - this.maxRewindBytes >= this.arrayBuffers[0].byteLength
145
    ) {
146
      this.offset -= this.arrayBuffers[0].byteLength;
225✔
147
      this.arrayBuffers.shift();
225✔
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,142✔
186
  }
187
  /**
188
   * @param bytes
189
   */
190
  rewind(bytes: number): void {
191
    // TODO - only works if offset is already set
192
    this.offset -= bytes;
812✔
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