@cityjson/flatcitybuf
    Preparing search index...

    Class FcbReader

    The reader facade: open an .fcb resource once, then answer queries against it.

    Construct one with the static factory that matches the source -- FcbReader.fromUrl (HTTP range requests), FcbReader.fromBlob (a browser Blob/File), FcbReader.fromBytes (an in-memory buffer), FcbReader.fromReader (any RangeReader) -- or fromFile(path) from the package's separate "./node" subpath, which is where the node:fs source lives so the package root never imports node:*. All of them are async because all of them read and validate the header up front.

    Opening reads the header and nothing else -- fromUrl costs exactly one HTTP request. FcbReader.select then reads the index it needs and only the features that matched, so a bbox query over a remote file fetches a few index pages plus those features, never the whole file.

    const reader = await FcbReader.fromUrl('https://example.com/city.fcb')
    const hits = await reader.select({
    spatial: { kind: 'bbox', value: [minX, minY, maxX, maxY] },
    where: [{ field: 'b3_h_dak_50p', operator: 'Gt', value: 20 }],
    })
    console.log(hits.featuresCount) // total matches, not the page size
    for await (const feature of hits) {
    console.log(toCityJSONFeature(feature, reader.header))
    }

    Call FcbReader.close (or use await using) when the source holds an OS resource -- fromFile does; the in-memory, Blob and fetch sources have nothing to release. Either way the reader is spent afterwards: a further select or selectAll throws.

    Index
    • get header(): HeaderView

      The parsed file header, read once when the reader was constructed: info (owned metadata -- version, transform, geographical extent, the attribute columns and which of them are indexed), layout (byte offsets of the R-tree, the attribute indices and the feature section) and raw (the FlatBuffers table, for the few fields info does not surface). Pass it to toCityJSONMetadata for the CityJSON metadata object.

      Returns HeaderView

    • Lets callers write await using r = await fromFile(path).

      Returns Promise<void>

    • The whole file as a CityJSONSeq stream: the metadata line first, then one line per feature, in stored order.

      An async generator rather than an array, for the same reason selectAll is a cursor: a city model does not have to fit in memory, and a caller writing .jsonl wants to emit each line as it arrives. Callers who do want it all can Array.fromAsync.

      Parameters

      Returns AsyncGenerator<CityJSON | CityJSONFeature, void, undefined>

    • Releases the underlying reader if it holds an OS resource -- fromFile opens a node:fs handle that has to stay open for later queries and so cannot be closed inside fromFile. Idempotent, and a no-op that resolves immediately for in-memory sources.

      Returns Promise<void>

    • The general query entry point: a spatial filter, paging, or both.

      Order of operations, and it matters:

      1. Validate every argument -- limit, offset, and the query geometry -- BEFORE touching the reader, so a caller mistake never costs a request.
      2. Run the search. A spatial query descends the packed R-tree with the header's OWN index_node_size; a hardcoded 16 mis-traverses any file written with another node size. An attribute query descends one static B+tree per condition. For a String column the B+tree answers with CANDIDATES, because its keys are truncated to 50 bytes.
      3. POST-FILTER those candidates against each feature's decoded, untruncated attributes (postFilterCandidates). This is what turns candidates into answers, and its position in this list is the whole point: it runs after the intersection and BEFORE step 4.
      4. Count, then page the sorted result list. featuresCount reports the total MATCH count -- post-filtered, not the candidate count -- and is unaffected by limit/offset.

      A where and a spatial given together are AND-intersected on feature offset: both index searches return their hits sorted ascending and de-duplicated, so the intersection is a sorted merge. nearest with where is refused outright (UnsupportedQueryCombination).

      The signal is threaded into the actual reads on BOTH paths, not merely held here: into the R-tree traversal and each hit's feature read when spatial is given, and into scan's per-feature reads (re-checked between features) when it is not. A signal that only lived on this facade would cancel nothing -- the reads are where the in-flight work is.

      Parameters

      Returns Promise<FeatureCursor>

      FcbError with code UnsupportedQueryCombination when spatial.kind is 'nearest' and a non-empty where is also given; NoIndex when a spatial query is run against a file that carries no R-tree; AttributeIndexNotFound when a where names a column that does not exist or that the writer did not index; UnsupportedColumnType for a where on a Json or Binary column; InvalidArgument for a negative or non-integral limit/offset, a malformed query geometry, or a value whose type does not match its column.

    • Every feature in the file, in stored order. Async because later selection modes (spatial, attribute) must read an index before they can produce their first feature; selectAll has nothing to read, so it resolves immediately, but the signature is shared.

      Takes ReadOpts directly (rather than a SelectOptions-shaped object) because it has nothing else to validate -- no spatial query, no paging -- so the only thing worth threading is the signal, straight into scan's reads.

      Parameters

      Returns Promise<FeatureCursor>

    • Reads an .fcb file from a Blob -- or a File, which extends it, so this is the entry point for an <input type="file"> or a drag-and-drop upload. Backed by Blob.slice(), so only the ranges a query actually needs are ever materialised. Nothing to close.

      Parameters

      • blob: Blob

      Returns Promise<FcbReader>

    • Reads an .fcb file already in memory. The bytes are COPIED (see BytesRangeReader), so the caller may afterwards mutate the array or transfer its ArrayBuffer to a worker without corrupting an open reader. Nothing to close.

      Parameters

      • bytes: Uint8Array

      Returns Promise<FcbReader>

    • The primitive every other constructor and every later feature builds on (Task 11's fromUrl, Task 12's select, the request-log tests).

      The reader is used EXACTLY as given -- no buffering decorator is inserted here. Caching is a property of the source, not of the facade: wrapping unconditionally would hide how many requests a scan really makes, which is precisely what the HTTP work needs to be able to see and tune. A caller over a chatty transport composes new BufferedRangeReader(source) itself.

      A sequential scan through the resulting cursor issues TWO read calls per feature, not one: readFeature (src/feature/index.ts) reads the 4-byte size prefix, then re-reads those same 4 bytes as part of a second, 4 + len-byte read for the body. See readFeature's docstring for why. This reader does nothing to hide that -- a request-count assertion against reader.reads.length should expect 2 * featuresCount (plus the header's own reads), not featuresCount. fromFile inherits this as-is: FileRangeReader has no internal buffering, so a sequential scan costs two pread syscalls per feature. Callers for whom request count matters -- HTTP chief among them -- should wrap their source in new BufferedRangeReader(source) before calling fromReader, exactly as the paragraph above already says for caching in general.

      Parameters

      Returns Promise<FcbReader>

    • Opens a remote .fcb file over fetch, with strict validation of the server's Range support (see io/fetch.ts).

      Unlike fromReader, this DOES wrap the source in a BufferedRangeReader -- fromReader's docstring explains why it itself does not (matching the C++ reference, and leaving request counting visible to callers who need it), and says a chatty transport should compose one. HTTP is exactly that transport: without buffering, the two read() calls readFeature makes per feature (a 4-byte size prefix, then a 4 + len body read that re-reads those same 4 bytes) would each become a separate HTTP request.

      The buffer starts at OPEN_PREFETCH_SIZE so its first miss -- forced by readHeader's very first read() call -- asks the underlying FetchRangeReader for EXACTLY the window that reader already cached during open() (io/fetch.ts's prefetch), which is why opening costs one physical request rather than two. Once the header is parsed, the window is widened to fetchSize (DEFAULT_FETCH_SIZE, 1 MB, unless overridden) for the feature scan that follows -- mirrors http_reader/mod.rs's own reset of min_req_size after _open.

      Parameters

      Returns Promise<FcbReader>