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>