25 Feb 2017, 09:57

From OSX to Linux

I’ve been a long time UNIX user, ditched Microsoft back in the 90s for FreeBSD, Solaris and Linux on the desktop, but when Apple released MacOS X, I’ve used it as a workstation.

For the last years I’ve used Linux desktops but not on my main computer, today here I am switching back to Linux.

This post is not about the reasons I’m switching, they are simple.
My typical work day is mostly about parsing giant files, running VM and Dockers, coding in Go and not about developing for iOS anymore.

I’m using Arch Linux and KDE/Plasma but many items from this list apply to any Linux distributions.

Put your user in the following groups uucp audio input lp.

Bonjour, mDNS and .local

I was used to query the .local domain to ssh my laptop back.

  • Install nss-mdns, add mdns_minimal [NOTFOUND=return] before resolve in /etc/nsswith.conf
  • Install avahi and start avahi-daemon.service.
  • To make your ssh server visible to others, cp /usr/share/doc/avahi/ssh.service /etc/avahi/services/

Try pinging a Mac host on your LAN with ping hostname.local

Google drive

Install kio-gdrive then start Dolphin and go to Network then Google drive and set up your account or by running the shell command: kioclient5 exec gdrive:/.

Emojis in color 💻

Install noto-fonts-emoji and edit .config/fontconfig/fonts.conf as follow

<?xml version='1.0'?>
<!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>

This setup is working in most applications but can sometimes display weird results in terminals.

2 Factor USB key U2F

I have a cheap Fido U2F key but Chrome was unable to see it. Edit /etc/udev/rules.d/50-fido-u2f.rules

# this udev file should be used with udev 188 and newer
ACTION!="add|change", GOTO="u2f_end"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0850|0880", TAG+="uaccess"

Win key aka ⌘ cmd key

On a PC keyboard the left alt and the windows key are inverted in opposite to a Mac where alt is left of ⌘.
To avoid being lost when I switch back to my Macbook I’ve physically inverted the keys and change the behavior in Plasma in “Hardware | Input Devices”.


Compose key

To type special or accentuated characters you’ll use the “Compose” key.
You can set the compose key in Plasma in “Hardware | Input Devices”.


I’m using the right alt, which is the right ⌘ cmd on a Mac (and also a compose key).

For a complete list of compose shortcuts see the bottom of this page


The tool/window equivalent to running Spotlight is called “Plasma Search”, you can configure what it can search for.
The mapping for this key in “System Settings | Global Shortcuts | Run Command”.

System Settings | Global Shortcuts | Run Command

It’s also capable of indexing files content, in KDE/Plasma this service is provided by baloo, ignored directories can be set by calling “Configure File Search”.

You can empty your baloo index by stopping by killing all your baloo processes and rm -r .local/share/baloo then restart for indexation balooctl start

Exposé and active corners

It’s called “Screen Edges” and it’s under “Windows behavior”.


Alternative for Dash

Dash was part of my workflow to get documentation, an alternative solution is to install zeal it’s using the exact same docsets as Dash.
Also see DevDocs

Samba share

Install samba, tweak /etc/samba/smb.conf and enable nmbd.service.
I personally prefer a different password than my shell account: smpasswd -a yourusername.

Taking Screenshot

Install spectacle and use the Ptr Sc key (this shortcut can be setup in Plasma).

Webcam, Hangout and Skype

I have a Logitech C920, it worked without any configuration, inside Chrome so Hangout and even with Skype for Linux.

Mounting a macOS disk

That one is weird …
mount -o ro,sizelimit=498876809216 /dev/sda2 /mnt/OSX

If your existing partition was big, you need to find this magic number by following this guide


Steam needs x86 32 bits libraries, on Arch you have to enable multilib by editing /etc/pacman.conf

Include = /etc/pacman.d/mirrorlist

Then install the steam package.


I prefer QEMU/KVM & libvirt-manager, when available, over Virtualbox, it’s more integrated into the system, and it’s capable of emulating other cpu architectures like aarch64…

One more huge advantage for QEMU it’s also capable of booting a virtual macOS X


Install cups and print-manager then enable org.cups.cupsd.service, note that you need the .local resolution above for network printer resolution.
Also install hplib for HP printers.

Share your session aka remote desktop

Install x11vnc and run x11vnc -usepw -once -noxdamage -ncache 10 from your X session.

Note that this vnc server is not compatible with macOS X embedded vnc viewer (vnc://hostname), here is one for Chrome RealVNC

Remember vnc protocol is not secure and you must use an SSH tunnel over it.

Magic Trackpad

I’m using a magic trackpad 2, after enabling bluetooth and pairing the trackpad, one, two & three fingers touches, vertical & horizontal scroll worked via hid_magicmouse module, still looking how to enable more gestures.

The bad

  • Not as nice, not as well integrated, for example supporting HiDPI with only one retina screen is weird with Xorg.
  • Key shorcuts are a giant mess under Linux, every applications have their own and it can’t be configured centrally.
  • I’m still missing some applications like Sketch, but most of all Tower when dealing with a git merge issue.

The good

After 8 years of absence on Linux as main desktop, things have changed, it’s not free from bugs but way more simpler to use now than before, and way more configurable than macOS X.

It won’t work for everybody but I’m really happy with this setup, the gain compared to a Mac is big, first the machine itself a 4Ghz i7 with 64G of ram does not even exist at Apple (Hackintosh is not a good solution), ZFS, native Docker over ZFS, better OpenGL (faster fps in games), well maintained packages over Brew/Macports, well maintained drivers, my work is easier …


20 Oct 2016, 08:58

Telegraf & Prometheus Swiss Army Knife for Metrics

There are a lot of different solutions when it comes to collecting metrics, I found myself happy with this hybrid solution.

Telegraf is an agent written in Go for collecting metrics from the system it’s running on.
It’s developed by Influxdata the people behind InfluxDB, but Telegraf has a lot of outputs plugins and can be used without InfluxDB.
Many different platform (FreeBSD, Linux, x86, Arm …) are offered and only one single static binary (Thanks to Golang) is needed to deploy an agent.

Prometheus is a time series database for your metrics, with an efficient storage.
It’s easy to deploy, no external dependencies, it’s gaining traction in the community because it’s a complete solution, for example capable of discovering your targets inside a Kubernete cluster.

Here is a simple configuration to discover both products.

You can deploy the agent on every hosts you want to monitor but need only one Prometheus running.

Install Prometheus

Download Prometheus for your platform and edit a config file named prometheus.yml.

  - job_name: 'telegraf'
    scrape_interval: 10s
      - targets: ['mynode:9126']

Prometheus is a special beast in the monitoring world, the agents are not connecting to the server, it’s the opposite the server is scrapping the agents.
In this config we are creating a job called telegraf to be scrapped every 10s connecting to mynode host on port 9126.

That’s all you need to run a Prometheus server, start it by specifying a path to store the metrics and the path of the config file:

prometheus -storage.local.path /opt/local/var/prometheus -config.file prometheus.yml

The server will listen on port 9090 for the HTTP console.

Install Telegraf

Download Telegraf agent for your platform and edit telegraf.conf.

    listen = ""

# Read metrics about cpu usage
  ## Whether to report per-cpu stats or not
  percpu = true
  ## Whether to report total system cpu stats or not
  totalcpu = true
  ## If true, collect raw CPU time metrics.
  collect_cpu_time = false

# Read metrics about memory usage

# Read metrics about network interface usage
  ## By default, telegraf gathers stats from any up interface (excluding loopback)
  ## Setting interfaces will tell it to gather these explicit interfaces,
  ## regardless of status.
  interfaces = ["en2"]

Remember this is the node agent “client” but since Prometheus server will connect it, you are providing a listening endpoint.
Starts the agent with telegraf -config telegraf.conf

There are many more inputs plugins for telegraf for example you can monitor all your Docker instance.

# Read metrics about docker containers
  endpoint = "unix:///var/run/docker.sock"
  ## Only collect metrics for these containers, collect all if empty
  container_names = []
  ## Timeout for docker list, info, and stats commands
  timeout = "5s"

  ## Whether to report for each container per-device blkio (8:0, 8:1...) and
  ## network (eth0, eth1, ...) stats or not
  perdevice = false
  ## Whether to report for each container total blkio and network stats or not
  total = false

It’s also capable of monitoring third parties product like MySQL, Cassandra

Read Metrics

Prometheus is provided with a visual HTTP console & query tool available on port 9090.

The query language is described here.

The console can’t be really used as a dashboard, you can use Grafana which can speak directly to prometheus.

Install or run Grafana with docker

docker run -i -p 3000:3000 -e "GF_SECURITY_ADMIN_PASSWORD=mypassword"  grafana/grafana

Point your browser to the port 3000.
Add a Prometheus data source and point the host to your Prometheus server port 9090.

Then create your dashboard, here are some queries to display the telegraf agents:

  • CPU
    cpu_usage_idle{host="myhost", cpu="cpu-total"}
    cpu_usage_user{host="myhost", cpu="cpu-total"}
    cpu_usage_system{host="myhost", cpu="cpu-total"}

  • Memory

  • Docker Memory
    Legend format {{container_name}}

  • Docker CPU
    Legend format {{container_name}}

Graph prometheus


You can easily instrument your own development using the client libraries.
There is also a Prometheus gateway for the short lived jobs, so you can batch to the gateway between the scrap period.

It’s a simple setup but capable of handling a lot of data in different contexts, system monitoring & instrumentation.

28 Sep 2016, 13:15

gRPC wrong types context and Go 1.7

If you are following Go development you probably know that:
Go 1.7 moves the golang.org/x/net/context package into the standard library as context, Yeah !
Unfortunately it won’t work for everything, I’ve spent some time understanding this one.

For example if you are using gRPC you can hit this problem, here is an interface generated by gRPC:

type APRSServer interface {
    GetPastMessages(context.Context, *Point) (*ARPSMessages, error)

But when compiling:

/main.go:153: cannot use &s (type *Server) as type protorpc.APRSServer in argument to protorpc.RegisterAPRSServer:                                                                           
        *Server does not implement protorpc.APRSServer (wrong type for GetPastMessages method)                                                                                                
                have GetPastMessages("context".Context, *protorpc.Point) (*protorpc.ARPSMessages, error)                                                                                      
                want GetPastMessages("golang.org/x/net/context".Context, *protorpc.Point) (*protorpc.ARPSMessages, error)   

The compiler is complaining about wrong types for the context argument.
Problem is gRPC generated code is importing context as golang.org/x/net/context, that’s the only way it remains compatible between Go 1.6 & Go 1.7.

So a quick solution is to import context in your own code to use the old path:

// we have to use the old context path here for gRPC compat see https://github.com/grpc/grpc-go/issues/711
context "golang.org/x/net/context"

Also note that go tool fix is now capable of fixing the import path with -force context.

22 Sep 2016, 15:39

Using Go mobile on iOS for real

Go Mobile can generate native framework for iOS and Android using Go code, I was curious what could be achieved with it.
Most tutorials are Hello world and I wanted to test it with real code.
You can use it to generate a full app only using Go code, but I’m only interested by the bindings part (SDK applications), using a native ObjC/Swift app calling Go code.

I’m using some existing Go code regionagogo, (a geofence database), moderately complex since it uses BoltDB and Google S2 library.

Go Mobile is limited to a subset of types you can use, main reason is to be correctly transcribed to Java and ObjC.

So the first thing to do is to write some Go wrapper like you would do in ObjC to call some C++ code but first:

Functions must return either no results, one result, or two results where the type of the second is the built-in error type.
So far no major complication, but also note that you can’t return slices, that could be a major pain, there are some workaround solutions like go-mobile-collection that can generate an API to operate on slice.
You also have to respect some naming for your constructor like New...().

At the end your wrapper won’t look very Goish but it’s what it takes for it to be translated.

Knowing those constraints you can write a simple wrapper like this:

package mobile

type GeoDB struct {
	db regionagogo.GeoFenceDB

func NewGeoDB() *GeoDB {
	g := &GeoDB{}
	return g

func (g *GeoDB) OpenDB(path string) error {
	db, err := rbolt.NewGeoFenceBoltDB(path)
	if err != nil {
		return err
	g.db = db
	return nil


Then use the gomobile command:

gomobile bind -target=ios github.com/akhenakh/regionagogo/mobile

It generates a native iOS Mobile.Framework, drop it into your XCode project and start using it.

  • GeoDB translates to GoMobileNewGeoDB() and returns a GoMobileGeoDB* in ObjC domain.
  • OpenDB(path string) error to - (BOOL)openDB:(NSString*)path error:(NSError**)error.

A simple example would be:

GoMobileGeoDB *db = GoMobileNewGeoDB();
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"region" ofType:@"db"];
NSError *error;
[db openDB:resourcePath error:&error];
if (error != nil) {
    NSLog(@"error opening db %@", [error localizedDescription]);

It’s performing very well, on my iPhone 6, around 4,000 queries per second to test a position in a small fence, to 60,000 queries in a hit miss (calling Go overhead is not that big), while using a ridiculously small amount of memory.

I’ve made a demo iOS app where you can hit the map and it tells you in which fence you are.

Remember this is running locally on your phone without any network access (but the map), the geo computation and the Polygons are returned by Go code.

Until now I had to maintain libraries in both languages, Go mobile is a nice alternative!

Sources for the wrapper are available in regionagogo gomobile branch, and here is the iOS demo app.

20 Sep 2016, 17:40

gRPC Envoy Nghttp2 and Load Balancing

I’ve been using gRPC at work and in several personal projects for months and happy with it, but when it comes to load balancing gRPC does not come with batteries included.

For a long time the only document was the Load Balancing draft in the gRPC repo, the clients should implement a Picker interface to know about the servers, so the pooling and controling the load were handled by the clients.
HTTP/2 was new and most of the reverse proxies implementations were not capable of load balancing gRPC HTTP2 frames, the only solution was to use a TCP load balancer, generating errors, improper and weird behaviours for the clients.

At least two projects are now supporting gRPC load balancing easily.

  • The recently announced Envoy from Lyft
  • And nghttpx from nghttp2

Here are some notes to simply load balance two gRPC Helloworld server! running on ports 50050 & 50051.

  • For nghttp2, a simple configuration file will do

  • For envoy, here is the cluster part

    "clusters": [
        "name": "local_service",
        "connect_timeout_ms": 250,
        "type": "static",
        "lb_type": "least_request",
        "features": "http2",
        "hosts": [
            "url": "tcp://"
            "url": "tcp://"

You can then tweak the greeter_client to loop for requests, so you can simulate a client doing multiple requests while killing/restarting your servers.

for {  
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name}) 
    if err != nil { 
        log.Printf("could not greet: %v", err)
    log.Printf("Greeting: %s", r.Message)

And modify the greeter_server to show on which port/server you get your response:

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name + port}, nil 

Those tests aren’t enabling any TLS so use grpc.WithInsecure().

Note that Envoy is also capable of bridging your HTTP/1.1 queries to gRPC, which is a killer feature (I haven’t tested it yet) , you would normally do it by code with gRPC-gateway.

Envoy is really new and I’m still digging into but already proves itself to be a complete load balancing proxy solution with or without gRPC in your stack.

22 Jul 2016, 19:11

Streaming using a Raspberry Pi Camera to Twitch in Full HD while injecting audio from rtl sdr

I have found the right setup to stream in 1080p from a Raspberry Pi using the camera to TwitchTV while injecting audio on the fly!

Create an account on Twitch and grab you stream key in the Dashboard.

This stream.sh script will create a FIFO start rtl_fm at freq 162.550M to listen to Canada weather bulletin (use your local NOAA channel) and inject and encode the audio to the existing h264 stream from the camera then stream it to twitch using rtmp.

#! /bin/bash
KEY="live_XXXXX_XXXXXXXXXXXXXX" # put your key here

mkfifo /tmp/streamaudio.wav
rtl_fm -f 162.550M -s 22050 -g 30 | sox -r 22050 -t raw -e signed -b 16 -c 1 -V1 - -r 22050 -t wavpcm  -  > /tmp/streamaudio.wav &

/opt/vc/bin/raspivid -n -vf -t 0 -w 1920 -h 1080 -fps 25 -o - | ffmpeg -re -i - -i /tmp/streamaudio.wav -codec copy -strict experimental -acodec libmp3lame -ar 22050 -threads 8 -f flv "$STREAM_URL/$KEY"

You can of course send other audio sources or no audio at all.

This setup is taking around 10% cpu on a Raspberry Pi 2.

If you are lucky enough my channel will be up and running.

What is RTL SDR ?

Using a 20$ USB dongle you have a software radio scranner, capable of listening to radio amateurs, NOAA weather bulletin even satellites.

Note that you can also stream from your laptop using the great and free ObsProject.

Happy streaming.

03 Jul 2016, 08:15

Enabling Gometalinter with Jetbrains Editors

I’ve been using Jetbrains editor (the free Idea community edition) or Pycharm with the Go plugins and very happy with this setup, the editor is providing some realtime linting but I was missing gometalinter.

First install gometalinter

go get -u github.com/alecthomas/gometalinter
gometalinter --install --update

To add support inside Jetbrains editors use the External tools feature.
In Preferences > Tools > External Tools, add a configuration.

gometalinter setup for jetbrains

Set the Program path to your $GOPATH/bin/gometalinter
Parameters to --deadline=15s --vendor $FileDir$
Working directory to $ProjectFileDir$

For the linters messages to be clickable and jump to code add an Output filter with Regular expression to match output $FILE_PATH$:$LINE$

gometalinter setup filter for jetbrains

08 Mar 2016, 11:23

A geo database for polygons, optimization

If you read this blog, you know I’ve recently released a project called regionagogo, a geo shape lookup database, described in this blogpost.

It uses the current Go S2 implementation, which is not yet as complete as the C++ implementation, for example the region coverer of a shape does not really compute cell around the shape but around the bounding box instead.

Using the shape of the polygon makes the covered cells more precise and smaller, resulting at the end to less PIP tests which are costly.

S2 over a shape
The same coverage with Go S2 would have returned 8 big cells of the same size, covering over regions.

I’ve created regionagogogen a quick and dirty command line program for OSX that takes a GeoJSON file containing your regions and then compute the database using the S2 C++ implementation, it’s for OSX only cause I’m using ObjC as a bridge to C++ which I don’t know enough.

Also note that regionagogogen includes an S2 port that works on iOS/OSX and some ObjC geo helper classes.

The Docker image fore regionagogo is also using the optimized database.

18 Feb 2016, 17:16

A geo database for polygons, foundations

On a previous post, I’ve described how to use the S2 geo library to create a fast geo database, but it was to store locations (points) and only to perform range queries, a complete geo database would have regions/polygons queries.

Looking for a solution

I had this need: querying for the countries or subregions of hundreds of coordinates per second, without relying on an external service.

One solution, using my previous technique, could have been to store every cities in the world and then perform a proximity query around my point to get the closest cities, but it works only in populated area and it’s only an approximation.

I looked into others solutions, there is some smart ideas using UTF-grid, but it’s a client side solution and also an approximation tied to the resolution of the computed grid.

S2 to the rescue

S2 cells have some nice properties, they are segments on the Hilbert curve, expressed as range of uint64, so I had the intuition the problem to perform fast region lookup could be simplified as find all mathematical segments containing my location expressed as an uint64.

S2 over a country

Using a Segment Tree datastructure, I first tried an in memory engine, using Natural Earth Data, loading the whole world countries shapes into S2 loops (a Loop represents a simple spherical polygon), transforming then into cells using the region coverer, it returns cells of different levels, add them to the segment tree.

Segment Tree

To query, simply tranform the location into an S2 Cell (level 30) and perform a stubbing query that intersects the segments, every segments crossed are cells that covered a part of a Loop.
It will reduce the problem to test a few Loop vs thousands of them, finally perform ContainsPoint against the found loops cause the point could be inside the Cell but not inside the Loop itself.

Et voilà! It works!

The segment tree structure itself is very low on memory, the loops/polygons data could be stored on disk and loaded on requests, I’ve tested a second implementation using LevelDB using this technique.

If you have a very large tree (for example cities limits for the whole world), you can even put the segment tree on a KV storage, using this paper Interval Indexing and Querying on Key-Value Cloud Stores.

Region a gogo

As a demonstration here is a working microservice called regionagogo, simply returning the country & state for a given location.
It loads geo data for the whole world and answers to HTTP queries using small amout of memory.

GET /country?lat=19.542915&lng=-155.665857

    "code": "US",
    "name": "Hawaii"

Here is a Docker image so you can deploy it on your stack.

Note that it performs really well but can be improved a lot, for example the actual Go S2 implementation is still using Rect boxing around loops, that’s why regionagogo is using a data file so it can be generated from the C++ version.


This technique seems to work well for stubbing queries, region queries, geofencing …
It can be a solid foundation to create a flexible and simple geo database.

26 Jan 2016, 14:01

A fast geo database with Google S2 take #2

Six months ago, I wrote on this blog about Geohashes and LevelDB with Go, to create a fast geo database.
This post is very similar as it works the same way but replacing GeoHashes with Google S2 library for better performances.

There is an S2 Golang implementation maintened by Google not as complete as the C++ one but close.

For the storage this post will stay agnostic to avoid any troll, but it applies to any Key Value storages: LevelDB/RocksDB, LMDB, Redis…
I personnaly use BoltDB and gtreap for my experimentations.

This post will focus on Go usage but can be applied to any languages.

Or skip to the images below for visual explanations.

Why not Geohash?

Geohash is a great solution to perform geo coordinates queries but the way it works can sometimes be an issue with your data.

  • Remember geohashes are cells of 12 different widths from 5000km to 3.7cm, when you perform a lookup around a position, if your position is close to a cell’s edge you could miss some points from the adjacent cell, that’s why you have to query for the 8 neightbour cells, it means 9 range queries into your DB to find all the closest points from your location.

  • If your lookup does not fit in level 4 39km by 19.5km, the next level is 156km by 156km!

  • The query is not performed around your coordinates, you search for the cell you are in then you query for the adjacent cells at the same level/radius, based on your needs, it means it works very approximately and you can only perform ‘circles’ lookup around the cell you are in.

  • The most precise geohash needs 12 bytes storage.

  • -90 +90 and +180 -180, -0 +0 are not sides by sides prefixes.

Why S2?

S2 cells have a level ranging from 30 ~0.7cm² to 0 ~85,000,000km².
S2 cells are encoded on an uint64, easy to store.

The main advantage is the region coverer algorithm, give it a region and the maximum number of cells you want, S2 will return some cells at different levels that cover the region you asked for, remember one cell corresponds to a range lookup you’ll have to perform in your database.

The coverage is more accurate it means less read from the DB, less objects unmarshalling…

Real world study

We want to query for objects inside Paris city limits using a rectangle:

Using level 5 we can’t fit the left part of the city.
We could add 3 cells (12 total DB queries ) on the left but most algorithms will zoom out to level 4.

But now we are querying for the whole region.

Using s2 asking for 9 cells using a rectangle around the city limits.

s2 vs h4
The zones queried by Geohash in pink and S2 in green.

Example S2 storage

Let’s say we want to store every cities in the world and perform a lookup to find the closest cities around, first we need to compute the CellId for each cities.

// Compute the CellID for lat, lng
c := s2.CellIDFromLatLng(s2.LatLngFromDegrees(lat, lng))

// store the uint64 value of c to its bigendian binary form
key := make([]byte, 8)
binary.BigEndian.PutUint64(key, uint64(c))

Big endian is needed to order bytes lexicographically, so we can seek later from one cell to the next closest cell on the Hilbert curve.

c is a CellID to the level 30.

Now we can store key as the key and a value (a string or msgpack/protobuf) for our city, in the database.

Example S2 lookup

For the lookup we use the opposite procedure, first looking for one CellID.

// citiesInCellID looks for cities inside c
func citiesInCellID(c s2.CellID) {
  // compute min & max limits for c
  bmin := make([]byte, 8)
  bmax := make([]byte, 8)
  binary.BigEndian.PutUint64(bmin, uint64(c.RangeMin()))
  binary.BigEndian.PutUint64(bmax, uint64(c.RangeMax()))

  // perform a range lookup in the DB from bmin key to bmax key, cur is our DB cursor
  var cell s2.CellID
  for k, v := cur.Seek(bmin); k != nil && bytes.Compare(k, bmax) <= 0; k, v = cur.Next() {
    buf := bytes.NewReader(k)
    binary.Read(buf, binary.BigEndian, &cell)

    // Read back a city
    ll := cell.LatLng()
    lat := float64(ll.Lat.Degrees())
    lng := float64(ll.Lng.Degrees())
    name = string(v)
    fmt.Println(lat, lng, name)

Then compute the CellIDs for the region we want to cover.

rect := s2.RectFromLatLng(s2.LatLngFromDegrees(48.99, 1.852))
rect = rect.AddPoint(s2.LatLngFromDegrees(48.68, 2.75))

rc := &s2.RegionCoverer{MaxLevel: 20, MaxCells: 8}
r := s2.Region(rect.CapBound())
covering := rc.Covering(r)

for _, c := range covering {

RegionCoverer will return at most 8 cells (in this case 7 cells: 4 8, 1 7, 1 9, 1 10) that is guaranteed to cover the given region, it means we may have to exclude cities that were NOT in our rect, with func (Rect) ContainsLatLng(LatLng).

Congrats we have a working geo db.

S2 can do more with complex shapes like Polygons and includes a lot of tools to compute distances, areas, intersections between shapes…

Here is a Github repo for the data & scripts generated for the images