27 Aug 2018, 00:19

Wasm with Go to build an S2 cover map viewer

Share

I needed a reason to use the new Go 1.11 Wasm port for “real”.

To make it short, it compiles Go code to Wasm binary format for a virtual machine running in web browsers.

I’ve always needed a debug tool to display S2 Cells on a map for different shapes, some online tools already exist:

I’ve planned for a Qt Go app or a QGIS plugin with C++ bindings to Python but to ship those modules would be a nightmare.

I needed another solution, a simple web app would do it, not very complicated but since I hate HTML/CSS/js, I’ve never bothered to start one…
The s2map solution was great but having to start a backend and pay for it, was a no go for the long run, plus since I work with Go I needed something that was relying on the S2 Go port for matching results.

So here I am doing some web dev…

Wasm & Go

First Wasm is not the best solution (GopherJS maybe is) to my problem but hey it’s working.

The main() is a bit weird but close enough to a normal Go program:

func registerCallbacks() {
	js.Global().Set("geocell", js.NewCallback(geoJSONToCells))
}

func main() {
	c := make(chan struct{}, 0)
	println("Wasm ready")
	registerCallbacks()
	<-c
}

Since our function geocell() will be called by js, we wait for a channel that will never be triggered so the main loop won’t return.

NewCallback() wants a fn func(event Value) it means you can’t return directly from Go to js

func geoJSONToCells(i []js.Value)  

Just a slice of untyped values thank you js.

All other functions (not exposed to js) can be normal Go functions, packages …

Interaction with the DOM is very limited via the package syscall/js
So far, to update back the UI (from Go to js), I pass the result of the computation via a set and call the js method, very hackish …

func updateUIWithData(data string) {
	js.Global().Set("data", data)
	js.Global().Call("updateui")
}

The updateui() is a regular js function that processes data and updates the DOM.

App

The app is calling a lot of Go code and libraries that were not written for the web but are now executed from a webpage without any backends:

  • the web interface creates a shape on a js Leaflet layer
  • exports the shape in GeoJSON
  • serializes it to JSON
  • calls the geoJSONToCells() func passing the JSON as string argument
  • computes the S2 cells in the Go world
  • sets the result back via a js var containing GeoJSON as string
  • reads back this GeoJSON and displays it as a Leaflet layer

The first loading and running of the Wasm is very slow on Chrome but not on Firefox, the execution itself is really fast.

ws2

Code is on Github and Demo is hosted on Github Pages at https://s2.inair.space, sorry folks Wasm works on phones but the demo won’t be very useful on mobile.

Size

You can argue 11M (1.5M gzipped) is too big for a webapp providing only one functionality, and you are probably right (then look at a modern webpage), but how big would be a Python app shipped with the C++ library or a full Qt app …

Also the size could be a non-issue, in a near future, a solution like jsgo.io could provide package level CDN caching.

Conclusion

Again you should have really good reasons to use Wasm, the back and forth between js & Wasm is nonsense, but the tooling will improve and I’m sure we will see plenty of solutions around it (one is gRPC in the browser).

EDIT: Call() can call a js method no need for eval !