Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send WebAssembly binary over Jupyter WebSocket #461

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion lonboard/_map.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import sys
from functools import lru_cache
from pathlib import Path
from typing import IO, TYPE_CHECKING, Optional, Sequence, TextIO, Union

Expand Down Expand Up @@ -46,6 +47,13 @@
"""


@lru_cache
def _load_parquet_wasm_binary() -> bytes:
"""Load the gzipped parquet-wasm binary blob"""
with open(bundler_output_dir / "arrow2_bg.wasm.gz", "rb") as f:
return f.read()


class Map(BaseAnyWidget):
"""
The top-level class used to display a map in a Jupyter Widget.
Expand Down Expand Up @@ -91,10 +99,14 @@ def __init__(
if isinstance(layers, BaseLayer):
layers = [layers]

super().__init__(layers=layers, **kwargs)
_parquet_wasm_content = _load_parquet_wasm_binary()
super().__init__(
layers=layers, _parquet_wasm_content=_parquet_wasm_content, **kwargs
)

_esm = bundler_output_dir / "index.js"
_css = bundler_output_dir / "index.css"
_parquet_wasm_content = traitlets.Bytes(allow_none=False).tag(sync=True)

view_state = ViewStateTrait()
"""
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"vitest": "^1.4.0"
},
"scripts": {
"build": "node ./build.mjs",
"build": "node ./build.mjs && gzip -c node_modules/parquet-wasm/esm/arrow2_bg.wasm > lonboard/static/arrow2_bg.wasm.gz",
"build:watch": "nodemon --watch src/ --exec \"npm run build\" --ext js,json,ts,tsx,css",
"fmt:check": "prettier './src/**/*.{ts,tsx,css}' --check",
"fmt": "prettier './src/**/*.{ts,tsx,css}' --write",
Expand Down
8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ authors = ["Kyle Barron <[email protected]>"]
license = "MIT"
readme = "README.md"
packages = [{ include = "lonboard" }]
include = ["lonboard/static/*.js", "lonboard/static/*.css", "MANIFEST.in"]
include = [
"lonboard/static/*.js",
"lonboard/static/*.css",
"lonboard/static/*.wasm",
"lonboard/static/*.wasm.gz",
"MANIFEST.in",
]

[tool.poetry.dependencies]
python = "^3.8"
Expand Down
12 changes: 8 additions & 4 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import * as React from "react";
import { useState, useEffect } from "react";
import { createRender, useModelState, useModel } from "@anywidget/react";
import type { Initialize, Render } from "@anywidget/types";
import type { Initialize, InitializeProps, Render } from "@anywidget/types";
import Map from "react-map-gl/maplibre";
import DeckGL from "@deck.gl/react/typed";
import { MapViewState, type Layer } from "@deck.gl/core/typed";
import { BaseLayerModel, initializeLayer } from "./model/index.js";
import type { WidgetModel } from "@jupyter-widgets/base";
import { initParquetWasm } from "./parquet.js";
import { initParquetWasmFromBinary } from "./parquet.js";
import { getTooltip } from "./tooltip/index.js";
import { isDefined, loadChildModels } from "./util.js";
import { v4 as uuidv4 } from "uuid";
import { Message } from "./types.js";
import { flyTo } from "./actions/fly-to.js";
import { useViewStateDebounced } from "./state";

await initParquetWasm();

const DEFAULT_INITIAL_VIEW_STATE = {
latitude: 10,
longitude: 0,
Expand Down Expand Up @@ -190,7 +188,13 @@ function App() {
);
}

async function initialize({ model }: InitializeProps): Promise<void> {
const parquetWasmBinary: DataView = model.get("_parquet_wasm_content");
await initParquetWasmFromBinary(parquetWasmBinary);
}

const module: { render: Render; initialize?: Initialize } = {
initialize,
render: createRender(App),
};

Expand Down
28 changes: 20 additions & 8 deletions src/parquet.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { useEffect, useState } from "react";
import _initParquetWasm, { readParquet } from "parquet-wasm/esm/arrow2";
import { initSync, readParquet } from "parquet-wasm/esm/arrow2";
import * as arrow from "apache-arrow";

// NOTE: this version must be synced exactly with the parquet-wasm version in
// use.
const PARQUET_WASM_VERSION = "0.5.0";
const PARQUET_WASM_CDN_URL = `https://cdn.jsdelivr.net/npm/parquet-wasm@${PARQUET_WASM_VERSION}/esm/arrow2_bg.wasm`;
let WASM_READY: boolean = false;

export async function initParquetWasm() {
// https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API
async function decompressBlob(blob: Blob) {
const ds = new DecompressionStream("gzip");
const decompressedStream = blob.stream().pipeThrough(ds);
return await new Response(decompressedStream).blob();
}

/**
* Initialize parquet-wasm from an existing WASM binary blob.
* It is expected that this WASM has been gzipped
*
* @return Whether initialization succeeded
*/
export async function initParquetWasmFromBinary(view: DataView): Promise<void> {
if (WASM_READY) {
return;
}

await _initParquetWasm(PARQUET_WASM_CDN_URL);
let blob = new Blob([view]);
const decompressedBlob = await decompressBlob(blob);
const decompressedBuffer = await decompressedBlob.arrayBuffer();

initSync(decompressedBuffer);
WASM_READY = true;
}

Expand Down
Loading