## geoid – Efficient storage and queriying of geographic data

(require geoid) | package: geoid |

The geoid library allows representing latidude/longitude coordinates using 64-bit integers. The integer values are ordered to preseve locality, which means that these IDs can be stored in databases and used to query and efficiently retrieve locations which are in a geographic region.

This library is inspired by the S2 Geometry library, in the sense that the integer IDs are determined in the same way. Until this libary has its own introduction section, you can read S2 Cell Hierarchy to understand how geoids work. What this library calls geoids, are called cells by the S2 libary.

Also, while inspired by the S2 library, this Racket module is an independent implementation that does not generate compatible IDs and does not aim to have the same API and functionality as that library.

A geoid is a 64 bit integer between first-valid-geoid and last-valid-geoid. In particular, 0 is not a valid geoid (they actualy start at 1)

Geoids which are close together represent geographic locations which are close together, but the reverse is not true: there are geographic locations which are close together, but their geoids are very different

The projection method, while not uniform accross the globe, it only has a small distortion and there are no singularities anywhere, including at the poles

Geoids split the earth surface at different levels: the highest level is level 30, where the earch surface is split into 6 faces. At each lower level, the geoids are split into four, for example at level 29, each of the 6 faces are split into four, producing 24 total geois. The subdivision goes on until level 0 is reached. At level 0, each geoid represents a patch of earth of approximately 0.7 cm2.

### 1` `Storing geoids in a SQLite database

Geoids use all 64 bits, and more than half of them have the highest bit set. SQLite will store numbers as signed 64 bits and convert unsigned 64 bit numbers to 64 bit floating points.

To store geoids in a SQLLite database, you need to subtract 263 from it, to convert it to a signed number. This will preserve the ordering properties of the geoid. Just remember to add back 263 back to it before using a geoid retrived from the database this way.

The geoid->sqlite-integer and sqlite-integer->geoid functions can be used to convert to and from SQLite representation.

### 2` `API Documentation

procedure

(last-valid-geoid) → exact-integer?

See geoid-stride to determine the amount to add to a geoid to obtain the next geoid at the same level.

procedure

This can be used to create half-open geoid ranges, for example by leaf-span.

procedure

(valid-geoid? geoid) → boolean?

geoid : exact-integer?

procedure

(lat-lng->geoid lat lng) → exact-integer?

lat : real? lng : real?

(geoid->lat-lng geoid) →

real? real? geoid : exact-integer?

The conversion from latitude/longitude to geoid and back has a small amount of error, emprirical testing showed this to be less than 7 millimeters, which is sufficient for the type of applications intended for this libary. Note that this error is not constant accross the globe, and 7 millimeters is the maximum error seen.

procedure

(geoid-level geoid) → (integer-in 0 30)

geoid : exact-integer?

Geoids produced by lat-lng->geoid are at level 0 and you can use enclosing-geoid to obtain a geoid at a higher level.

procedure

(geoid-stride geoid) → exact-integer?

geoid : exact-integer?

procedure

(enclosing-geoid geoid level) → exact-integer?

geoid : exact-integer? level : (integer-in 0 30)

procedure

(split-geoid geoid)

→ (list/c exact-integer? exact-integer? exact-integer? exact-integer?) geoid : exact-integer?

The returned geoids are not in any particular order.

procedure

(leaf-geoid? geoid) → boolean?

geoid : exact-integer?

procedure

(leaf-span geoid) →

exact-integer? exact-integer? geoid : exact-integer?

All geoids which are inside this geoid, regardless of level, are contained in the returned number range, so this range can be used to check if any geoid is inside this one.

The leaf span returned by this function can be used to search for geoids in an SQL query, however, if you do that, make sure you adjust them, as noted in the SQLite section above.

procedure

(contains-geoid? this-geoid other-geoid) → boolean?

this-geoid : exact-integer? other-geoid : exact-integer?

This a convenient function, but if you need to check lots of geoids, this will be slower than obtainging the leaf-span of this-geoid and checking of other geoids are inside the returned range.

procedure

(leaf-corners geoid)

→ (list/c exact-integer? exact-integer? exact-integer? exact-integer?) geoid : exact-integer?

procedure

(leaf-outline geoid [ #:steps steps #:closed? closed?]) → (listof exact-integer?) geoid : exact-integer? steps : (and/c integer? positive?) = 10 closed? : boolean? = #t

steps specifies the number of geoids ot put on each side of the rectangle, while closed? specifies if the first geoid should be duplicated as the last element in the list, to close the loop.

As with leaf-corners, geoids are placed in counter-clockwise order, but there is no guarantee about the start corner of the loop.

procedure

(lat-lng-rect geoid) →

real? real? real? real? geoid : exact-integer?

The bounding box encloses the geoid minimally, but geoids and bounding boxes don’t overlap exactly, so the bounding box will always be bigger than then geoid and there will be other geoids which are inside the bounding box, but not in the geoid.

procedure

(random-geoid level #:parent parent) → exact-integer?

level : exact-integer? parent : (or/c exact-integer #f)

This function is intended to generate geoids for unit tests.

procedure

(geoid->sqlite-integer geoid) → exact-integer?

geoid : exact-integer? (sqlite-integer->geoid i) → exact-integer? i : exact-integer?

Geoids are unsigned 64 bit values, and SQLite will only store signed values. Unsigned 64 bit values which are greater than the maximum signed 64 bit value will be converted to floating point numbers, loosing precision. This means that geoids cannot be stored directly into a SQLite database.

These pair of functions subtract 263 from the geoid (or add that value back) to make sure the value is stored correctly. The ordering of the geoids is preserved, so they can still be used to index geograpic information.