Libosmium WASM
Written: 1/7/2024
This is a WASM example on libosmium using their CPP library. You shall find sample OSM protobuf extracts from OSM france mirror.
Upload *.osm.pbf file:
[JS] Loading WASM...

Build script

em++ -O3 -Wno-register -Wno-deprecated -pthread \
    -I./include -I./external/protozero/include -I./external/zlib \
    -sPROXY_TO_PTHREAD -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency \
    -sINITIAL_MEMORY=128MB -sWASM_MEM_MAX=256MB -sALLOW_MEMORY_GROWTH \
    -sEXPORTED_RUNTIME_METHODS=FS,ccall,cwrap -sEXPORTED_FUNCTIONS=_main \
    -sMODULARIZE -sEXPORT_NAME="createLibosmium" \
    -sASSERTIONS -sNO_DISABLE_EXCEPTION_CATCHING \
    ./main.cpp \
    ./external/zlib/adler32.c \
    ./external/zlib/crc32.c \
    ./external/zlib/deflate.c \
    ./external/zlib/infback.c \
    ./external/zlib/inffast.c \
    ./external/zlib/inflate.c \
    ./external/zlib/inftrees.c \
    ./external/zlib/trees.c \
    ./external/zlib/zutil.c \
    ./external/zlib/uncompr.c \
    -o ./build/libosmium.js

libosmium.cpp

#include <emscripten.h>

#include <iostream>

#include <osmium/io/pbf_input.hpp>
#include <osmium/handler.hpp>
#include <osmium/util/memory.hpp>
#include <osmium/visitor.hpp>
#include <osmium/osm/types.hpp>
#include <osmium/index/map/sparse_mem_array.hpp>
#include <osmium/handler/node_locations_for_ways.hpp>
#include <osmium/index/map/flex_mem.hpp>
#include <osmium/handler/node_locations_for_ways.hpp>

using index_type = osmium::index::map::FlexMem<osmium::unsigned_object_id_type, osmium::Location>;
using location_handler_type = osmium::handler::NodeLocationsForWays<index_type>;

typedef struct {
    int64_t id;
    double lat;
    double lon;
} Node;
typedef struct {
    std::vector<Node> nodes;
    bool one_way;
} Way;

std::map<int64_t, Node> nodes;
std::vector<Way> ways;


struct OSMFileHandler : public osmium::handler::Handler {

    void way(const osmium::Way& w) {
        bool is_within = true;
        Way my_way;
        my_way.one_way = false;
        if (w.tags().has_key("oneway") && std::strcmp("yes", w.tags().get_value_by_key("oneway")) == 0) {
                my_way.one_way = true;
        }

        if (w.tags().has_key("junction") && std::strcmp("roundabout", w.tags().get_value_by_key("junction")) == 0) {
            my_way.one_way = true;
        }

        for (auto n : w.nodes()) {
            Node my_node = {
                .id = (int64_t) n.ref(),
                .lat = n.location().lat(),
                .lon = n.location().lon()
            };

            nodes[my_node.id] = my_node;

            my_way.nodes.push_back(my_node);
        }

        ways.push_back(my_way);
    }
};

int main(int argc, char* argv[]) {
    std::string file_path = "input.pbf";

    try {
        std::cout << "Opening file" << std::endl;
        const osmium::io::File input_file{file_path};
        std::cout << "Reading file" << std::endl;
        osmium::io::Reader reader{input_file};

        index_type index;
        OSMFileHandler handler;
        location_handler_type location_handler{index};
        osmium::apply(reader, location_handler, handler);
        reader.close();

        std::cout << "Nodes: "     << nodes.size() << "\n";
        std::cout << "Ways: "      << ways.size() << "\n";

        std::cout << "Closing file" << std::endl;
        reader.close();
    } catch (const std::exception& e) {
        std::cerr << "Failed to load file" << std::endl;
        std::cerr << e.what() << '\n';
        return 1;
    }
}

main-worker.ts

// @ts-ignore
const __filename = "libosmium.js"; // what a hack 🤣
importScripts("/libosmium-wasm/libosmium.js");

interface LibosmiumModule extends EmscriptenModule {
	ccall: typeof ccall;
	FS: typeof FS;
}

let libosmiumModule: LibosmiumModule;

declare var createLibosmium: EmscriptenModuleFactory<LibosmiumModule>;

createLibosmium({
	noInitialRun: true,
	locateFile: (path: string) => {
		return "/libosmium-wasm/" + path;
	},
	print: (str: string) => {
		self.postMessage({
			type: "stdout",
			payload: `[WASM] ${str}\n`,
		});
	},
})
	.then((lib) => {
		self.postMessage({
			type: "stdout",
			payload: "\n[JS] Loaded\n",
		});
		self.postMessage({
			type: "loaded",
			payload: true,
		});
		libosmiumModule = lib;
	})
	.catch(console.log);

self.onmessage = (msg) => {
	if (msg.data.type === "main") {
		const file = new Uint8Array(msg.data.payload.data);
		if (!libosmiumModule.FS.analyzePath("/input.pbf", false).exists) {
			const stream = libosmiumModule.FS.open("/input.pbf", "w");
			libosmiumModule.FS.write(stream, file, 0, file.length);
			libosmiumModule.FS.close(stream);
		}
		self.postMessage({
			type: "stdout",
			payload: "[JS] Done. Starting main..\n",
		});
		try {
			libosmiumModule.ccall("main", null, [], []);
		} catch (e) {
			self.postMessage({
				type: "stdout",
				payload: "[JS] An error occured\n",
			});
		}
	}
};

Svelte Source (This page)

<script lang="ts">
	import PageContainer from "$lib/components/PageContainer.svelte";
	import "highlight.js/styles/github-dark.css";
	import { onMount } from "svelte";
	import hljs from "highlight.js";

	export let data;

	let loaded = false;
	let worker: Worker;
	let stdout: string = "[JS] Loading WASM...";

	onMount(() => {
		hljs.highlightAll();
		worker = new Worker("./libosmium-wasm/main-worker.js", {
			name: "libosmium.js",
		});

		worker.addEventListener("message", (msg) => {
			if (msg.data.type === "stdout") {
				stdout += msg.data.payload;
			}
			if (msg.data.type === "loaded") {
				loaded = msg.data.payload;
			}
		});
	});

	function readFile(file: File): Promise<ArrayBuffer> {
		return new Promise((resolve, reject) => {
			const fr = new FileReader();
			fr.onload = () => {
				if (fr.result instanceof ArrayBuffer) {
					resolve(fr.result);
				} else {
					reject("Invalid file reader instance");
				}
			};
			fr.onerror = reject;
			fr.readAsArrayBuffer(file);
		});
	}

	async function process(e: Event) {
		e.preventDefault();
		const fileDom = document.getElementById(
			"inputFile",
		) as HTMLInputElement;
		if (!fileDom.files) return;
		stdout += "[JS] Sending file from JS to WASM\n";
		const data = await readFile(fileDom.files[0]);
		worker.postMessage(
			{
				type: "main",
				payload: {
					data,
				},
			},
			[data],
		);
	}
</script>

<svelte:head>
	<style>
		#stdout {
			background-color: black;
			color: green;
			min-height: 200px;
			padding: 10px;
			font-weight: bold;
			font-family: monospace;
			white-space: pre-wrap;
		}
		#file-form {
			display: flex;
			flex-shrink: 1;
			flex-direction: column;
			gap: 10px;
		}
	</style>
</svelte:head>

<PageContainer>
	<div>
		<div class="text-2xl font-bold text-secondary">
			{data.title}
		</div>
		<div class="text-sm font-bold text-primary">
			Written: {data.written}
		</div>

		<div>
			This is a WASM example on libosmium using their CPP library.
			You shall find sample OSM protobuf extracts <a target="_blank" class="link link-secondary" href="https://download.openstreetmap.fr/extracts/">from OSM france mirror.</a>
		</div>

		<form id="file-form" on:submit={process}>
			<h6 class="text-xl">Upload *.osm.pbf file:</h6>
			
			<input id="inputFile" type="file" />
			<button disabled={!loaded} class="btn btn-primary" type="submit"
				>Process</button
			>
			<div id="stdout">{stdout}</div>
		</form>

		<div class="flex flex-col gap-10 mt-10">
			<div>
				<h2 class="text-2xl text-secondary">Build script</h2>
				<pre><code class="hljs language-cpp">{data["buildSource"]}</code></pre>
			</div>
			<div>
				<h2 class="text-2xl text-secondary">libosmium.cpp</h2>
				<pre><code class="hljs language-cpp">{data["libosmium.cpp"]}</code></pre>
			</div>
			<div>
				<h2 class="text-2xl text-secondary">main-worker.ts</h2>
				<pre><code class="hljs language-ts">{data["workerSource"]}</code></pre>
			</div>
			<div>
				<h2 class="text-2xl text-secondary">Svelte Source (This page)</h2>
				<pre><code class="hljs language-html">{data["svelteSource"]}</code></pre>
			</div>
		</div>
	</div>
</PageContainer>
$ git rev-parse --short HEAD
1597778