# Geospatial

Geospatial provides abstractions for dealing with geographical locations efficiently. It is not a generic point/line/polygon handling library like RGeo, but a specially crafted library to deal with querying for points on a map efficiently.

## Motivation

We had a need to query a database of places efficiently using SQLite. We did some investigation and found that SQLite (at least at the time) couldn't use composite indexes efficiently. Our testing revealed that MySQL also didn't really do well with large amounts of data. We had a table with 5Gb of data, and 15Gb of indexes. Crazy.

After researching geospatial hashing algorithms, I found this blog post and decided to implement a geospatial hash using the Hilbert curve. This library exposes a fast indexing and querying mechanism based on Hilbert curves, for points on a map, which can be integrated into a database or other systems as required.

The design of this library is inspired by Space-Filling Curves in Scala, which exposes curves as composable mathematical hash functions.

For another solution to this problem, Google uses S2 Geometry which is a more specific implementation designed for geospatial indexes only.

## Installation

gem 'geospatial'

And then execute:

\$ bundle

Or install it yourself as:

\$ gem install geospatial

## Usage

The simplest way to use this library is to use the built in Map:

map = Geospatial::Map.new
map << Geospatial::Location.new(170.53, -43.89) # Lake Tekapo, New Zealand.
map << Geospatial::Location.new(170.45, -43.94) # Lake Alex, New Zealand.
map << Geospatial::Location.new(151.21, -33.85) # Sydney, Australia.

map.sort! # or assume an ordered database index.

new_zealand = Geospatial::Box.from_bounds(Vector[166.0, -48.0], Vector[180.0, -34.0])

points = subject.query(new_zealand)
expect(points).to include(lake_tekapo, lake_alex)
expect(points).to_not include(sydney)

At a lower level you can use the method in the Geospatial::Hilbert module to map, unmap and traverse the Hilbert mapping.

### Computing a Hilbert Index

#!/usr/bin/env ruby
require 'geospatial/map'
require 'geospatial/dimensions'
require 'geospatial/hilbert/curve'

map = Geospatial::Map.for_earth(30)
coordinate = ARGV.collect(&:to_f)
index = map.hash_for_coordinates(coordinate)
puts index

### Geotemporal Indexes

The Hilbert curve is multi-dimensional and therefore can represent multi-dimensional data, e.g. latitude, longitude and time, in a single index. The curve expands uniformly in all dimensions, so you can't control the precision of the dimensions independently.

Mathematically speaking, it's possible to compose curves together to form curves of different precision/properties. However, how these fit together generally is a bit more complex, especially in terms of exploring the curve via traversal.

### Finding the distance between two locations

lake_tekapo = Geospatial::Location.new(170.53, -43.89) # Lake Tekapo, New Zealand.
lake_alex = Geospatial::Location.new(170.45, -43.94) # Lake Alex, New Zealand.
distance = lake_alex.distance_from(lake_tekapo)
puts distance # 8479.056408162045 meters

## Contributing

1. Fork it
2. Create your feature branch (git checkout -b my-new-feature)
4. Push to the branch (git push origin my-new-feature)
5. Create new Pull Request