geo
Geospatial Nearest Neighbour
Which store is actually nearest?
The same 'what is near me' question as the geohash, answered by a vector index.
A vector index is a nearest-neighbour structure over any space you can put a point in, and a map is one. Project latitude and longitude onto the unit sphere and a place becomes three numbers; the straight-line gap between two of them rises and falls exactly with the distance you would walk. Same seven places as the Store Locator, same question, and the answer comes back ranked and with no cell boundary in it.
The model
Place
A store, carried twice: as a geohash for the cell query, and as a point on the unit sphere.
- pk
- PLACE#<placeId>
Attributes: name (S), lat (N), lon (N), geohash (S), openUntil (S), GSI1PK (S), GSI1SK (S), position (L)
Access patterns
- SearchVectors · by-positionThe five nearest, ranked
The five closest places to a point, in order, with the distance as the score.
- Query · GSI1Everything in the geohash cell
The same question answered the usual way, by reading one geohash cell. Run it after the search and compare the two lists.
Design notes
Two dimensions do not fit in one sort key. Three fit in a vector.why
The Store Locator interleaves latitude and longitude into one string because a sort key is one dimension and a position is two. A vector index has no such limit: it takes as many dimensions as you declare. So the position does not have to be flattened at all, and once it is not flattened there is no cell, no prefix, and no boundary.
Why latitude and longitude are not the vectorwhy
Two naive attempts both fail. Cosine on a raw lat/long pair measures the angle that pair makes at the origin of a degree grid, a point off the west coast of Africa that means nothing, so two places on opposite sides of the world can score as identical. Euclidean on raw lat/long is closer to sane but distorts with latitude: a degree of longitude is 111 km at the equator and 66 km in Leeds, so a circle in degree space is an ellipse on the ground, and it tears completely at the antimeridian, where 179.9 east and 179.9 west are 22 km apart on the equator and 360 units away from each other in the grid. Neither is fixable by choosing a different distance function, because the problem is the space, not the metric.
Project to the unit sphere, and Euclidean is forcedmodelling
x = cos(lat)cos(lon), y = cos(lat)sin(lon), z = sin(lat), with both angles in radians. Every place is now a point on a sphere of radius one, and the straight-line gap between two of them, which is what EUCLIDEAN reports, is 2*sin(theta/2) for a central angle theta. That climbs without ever turning back as theta goes from zero to pi, so the order a Euclidean search returns is exactly the order of true great-circle distance. Nothing here is approximated. The score converts back: kilometres = 2 * 6371 * asin(score / 2), which on this seed agrees with haversine to within half a metre.
The cell returns the further place and misses the nearer onetrade-off
Run both patterns and read the two lists side by side. The search returns Briggate at 0.45 km, The Headrow at 0.48, Kirkgate Market at 0.53, Holbeck at 0.92 and Hyde Park at 2.24, which is the true order. The cell query returns four places and Holbeck is not among them, because Holbeck sits in gcwc while the phone sits in gcwf. It is the fourth-nearest and the cell cannot see it, while Hyde Park, two and a half times further away, comes back because it happens to share a prefix. That is the geohash's known edge case, and it is not an edge: at a 23 km cell, every query near a boundary has one.
There is no radius, and there is no page twotrade-off
A search takes a TopK and nothing else. You cannot ask for everything within five kilometres, and if the five nearest branches are all in another county you will get them anyway with no hint that they are far. Filtering by score afterwards is your application's job, and it spends from a budget of at most a hundred results with no LastEvaluatedKey to continue from. The cell query has the opposite shape: it is bounded by geometry rather than by count, so a dense city centre returns everything and the moors return nothing. Neither is the general answer, which is why both are on this table.
The index stores 32-bit copies, so the floor is about a metreoperations
A unit-sphere component sits between minus one and one, and a 32-bit float resolves a value that size to about one part in seventeen million, which is roughly 40 cm on the Earth's surface. Two doorways a few centimetres apart are the same point as far as this index is concerned. That is fine for finding a branch and wrong for anything surveying-grade, and it is a property of the vector index rather than of the projection: the base table still holds whatever precision you wrote.
Nothing here is a model, and the write path is arithmeticwhy
Three trigonometric calls turn a coordinate into this vector. There is no inference, no weights to ship, no cost per write and nothing to re-embed when a library version moves. That is the difference worth noticing: a vector index is a nearest-neighbour structure over a space, and whether that space came out of a neural network or out of cos and sin is not something the index knows or cares about.
Read it against
- Store Locator
The same seven places and the same question, answered twice: a geohash prefix on a sort key, and a nearest-neighbour search over the projected coordinates.
Taught in the course
- A different key for a different question - A secondary index re-keys your items so you can Query them on a new axis.
- A vector is just an attribute - A vector is a list of numbers on an item. An index is what makes it searchable.
- Three ways to be near - The metric is fixed at index creation, and the space your numbers came from has usually already chosen it.
The five closest places to a point, in order, with the distance as the score.
Choose an access pattern above, or build your own request. See what comes back and what it costs.
Write transactions, streams, tags and TTL are among the operations this browser build leaves out. dynoxide's native build has them.