# DynamoDB_Geo ## DynamoDB This is an attempt at storing and querying geohash data in DynamoDB. We store objects in DynamoDB, when we query, we look for a customizable number of stored items by zooming out exactly one level. Returned in an attempt at the closest items first. Items are searched as the following. Given the lat, long we calculate a hash 9x0qz. We go up one level to 9x0q and calculate all neighbouring hashes. 9x0p 9x0r 9x0x 9x0n 9x0q 9x0w => ["9x0q", "9x0r", "9x0x", "9x0w", "9x0t", "9x0m", "9x0j", "9x0n", "9x0p"] 9x0j 9x0m 9x0t From this, we ask DynamoDB starting from our own cell, going north, and wrapping clockwise for all objects in these cells up to a configurable number of objects. We then calculate the neighbours of our more local hash 9x0qz, and sort the results of the larger hashes using similar rules. 9x0rn 9x0rp 9x0x0 9x0qy 9x0qz 9x0wb => ["9x0qz", "9x0rp", "9x0x0", "9x0wb", "9x0w8", "9x0qx", "9x0qw", "9x0qy", "9x0rn"] 9x0qw 9x0qx 9x0w8 Using the same pattern as before (Middle -> North -> Clockwise), we sort the returned stores giving priority to the localization. ### Real talk I know this isn't the most wonderful way to do this, I am still trying to think of something better. Currently it uses up to 8 queries to DynamoDB, I'd like to cut that down to a single query. There is a similar library written in Java and JS, but it uses Google S2 for the Geohashing, which has properties that allow them to do the zoom-out technique with a single query, however Google S2 does not exist as a C library (or Ruby library for that matter). The other alternative is Uber H3, however it has the same issues of not being C, or Ruby. If we were able to use a unique but deterministic way to calcuate the range key based off of the hash key that would allow us to query every single larger cell in a single batch query. I am currently thinking of more clever ways to do this - obviously I am open to suggestions. ## Objects ### DynamodbManager **Attributes** table_name: Sets the DynamoDB Table Name hash_key: Sets the name of the primary hash attribute range_key: Sets the name of the sort/range attribute geohash_key: Sets the name of the localized hash attribute geojson_key: Sets the name of the metadata attribute hash_key_length: Sets size of the outer hash length local_area_size: Sets the size of the inner hash length max_item_return: Sets the max number of items to return **Methods** Initialization Description: Creates a new DynamodbManager object. Inputs are variables to your AWS account. If access_key_id and secret_access_key are provided they are used. If not provided, it falls back to ENV variables, then secret credential storage (profile name). All arguments are keyword arguments Input: region => String table_name => String access_key_id => String secret_access_key => String profile_name => 'default' Output: Aws::DynamoDB::Client #new => Object Building and describing a DynamoDB Table Description: Shows the current configured table, or creates a table to configured as requested Input: Output: Aws::DynamoDB::Types::DescribeTableOutput #table => Object Creating a new item Description: Inserts a new item Input: Store Output: Aws::DynamoDB::Types::PutItemOutput #put_store => Object Querying stores Description: Look for stores dependent on the input Lat, Long (as described above) Input: Store Output: Array[Store] #get_stores => Array[Store] ### Store **Methods** Initialization Description: Initialization Input: Hash => { latitude # Required longitude # Required address city state zip area_code phone name geohash # Calculated based on lat,long if not provided } Output: Store #new => Store ### DynamodbGeo This only exists as a quick and easy way to create a DynamodbManager. The only method is `.new` and it passes all arguments along to DynamodbManager and returns an instance of DynamodbManager ## Geohashing Includes a Geohash implimentation written in C ### Methods Encoding a Geohash Description: Takes in a latitude, longitude, and precision, and returns a geohash as a string Input: latitude => Number longitude => Number precision => Number Output: Geohash => String Geohash.encode(lat, long, precision) => String Decoding a Geohash Description: Takes in a hash, and returns a Geocoord Input: Geohash => String Output: Geocoord => Object Geohash.decode(hash) => Geocoord Viewing all my neighbours Description: Takes in a Geohash and shows the surrounding 8 neighbours Physical representation: NW N NE 7 0 1 W X E => 6 X 2 SW S SE 5 4 3 Flat representation: N NE E SE S SW W NW 0 1 2 3 4 5 6 7 Input: Geohash => String Output: Array[Geocoord] Geohash.neighbours(hash) => Array[Geohash] Viewing one of my neighbours Description: Takes in a Geohash and shows the neighbour in the cardinal direction NORTH = 0 EAST = 1 SOUTH = 2 WEST = 3 Input: Geohash => String Direction => Integer Output: Geohash Geohash.neighbour(hash) => Geohash Get width and height about a specific precision Description: Takes in a precision value, returns the height and width of the box Input: precision => Number Output: Dimension => Object Geohash.dimensions(precision) => Dimension ### Objects #### Geocoord(Object) => attr_accessor: :latitude, :longitude, :north, :south, :east, :west, :dimension #### Dimension(Object) => attr_accessor: :length, :width