{"id":30453970,"url":"https://github.com/codecrafters-io/redis-geocoding-algorithm","last_synced_at":"2026-04-15T20:02:42.515Z","repository":{"id":309649585,"uuid":"1037028709","full_name":"codecrafters-io/redis-geocoding-algorithm","owner":"codecrafters-io","description":"An explainer on how Redis's geocoding algorithm works","archived":false,"fork":false,"pushed_at":"2025-08-27T09:02:40.000Z","size":553,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-29T15:44:48.446Z","etag":null,"topics":["build-your-own-x","redis"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codecrafters-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-13T00:42:08.000Z","updated_at":"2025-08-29T10:56:27.000Z","dependencies_parsed_at":"2025-08-13T04:18:02.125Z","dependency_job_id":"f580df6c-03f9-44ee-a86a-a85122771d28","html_url":"https://github.com/codecrafters-io/redis-geocoding-algorithm","commit_stats":null,"previous_names":["codecrafters-io/redis-geohash-algorithm"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/codecrafters-io/redis-geocoding-algorithm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecrafters-io%2Fredis-geocoding-algorithm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecrafters-io%2Fredis-geocoding-algorithm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecrafters-io%2Fredis-geocoding-algorithm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecrafters-io%2Fredis-geocoding-algorithm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codecrafters-io","download_url":"https://codeload.github.com/codecrafters-io/redis-geocoding-algorithm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecrafters-io%2Fredis-geocoding-algorithm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31857625,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"ssl_error","status_checked_at":"2026-04-15T15:24:39.138Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["build-your-own-x","redis"],"created_at":"2025-08-23T16:01:29.800Z","updated_at":"2026-04-15T20:02:42.510Z","avatar_url":"https://github.com/codecrafters-io.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"Redis converts Geospatial data (latitude and longitude) into a single \"score\" value so that it can be stored in a [Sorted Set](https://redis.io/docs/latest/develop/data-types/sorted-sets/).\n\nThis repository explains the algorithm used for this conversion.\n\n# Encoding\n\nEncoding is a 3-step process.\n\n1. Latitude and longitude are normalized to the [0, 2^26) range\n2. The normalized values are truncated to integers (i.e. the decimal part is dropped)\n3. The bits of the normalized latitude and longitude values are interleaved to get a 52-bit integer value\n\n## Step 1: Normalization\n\nThe values of latitude and longitude are normalized to values in the [0, 2^26) range.\n\nThe official Redis source code implements this in [geohash.c](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L141-L148).\n\nHere's some pseudocode illustrating how this is done:\n\n```python\nMIN_LATITUDE = -85.05112878\nMAX_LATITUDE = 85.05112878\nMIN_LONGITUDE = -180\nMAX_LONGITUDE = 180\n\nLATITUDE_RANGE = MAX_LATITUDE - MIN_LATITUDE\nLONGITUDE_RANGE = MAX_LONGITUDE - MIN_LONGITUDE\n\nnormalized_latitude = 2^26 * (latitude - MIN_LATITUDE) / LATITUDE_RANGE\nnormalized_longitude = 2^26 * (longitude - MIN_LONGITUDE) / LONGITUDE_RANGE\n```\n\n\u003e [!NOTE]\n\u003e The latitude range Redis accepts is +/-85.05° and not +/-90°. This is because of the [Web Mercator projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) used to project the Earth onto a 2D plane.\n\nThese normalized values are combined in the next steps to calculate a \"score\".\n\n## Step 2: Truncation\n\nThe normalized values (floats) are truncated to integers. This is not rounding, the decimal part is dropped entirely.\n\nIn the Redis source code, this conversion happens implicitly when the `double` values are casted to `uint32_t` [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L149).\n\nIn Python, this conversion can be done explicitly using the `int()` function:\n\n```python\nnormalized_latitude = int(normalized_latitude)\nnormalized_longitude = int(normalized_longitude)\n```\n\n## Step 3: Interleaving\n\nThe bits of the normalized latitude and longitude values are then interleaved to get a 64-bit integer value.\n\nIn the Redis source code, this is done in the [`interleave64` function](https://github.com/redis/redis/blob/eac48279ad21b8612038953fefa0dcf926773efc/src/geohash.c#L52-L77).\n\nHere's some pseudocode illustrating how this is done:\n\n```python\ndef interleave(x: int, y: int) -\u003e int:\n    # First, the values are spread from 32-bit to 64-bit integers.\n    # This is done by inserting 32 zero bits in-between.\n    #\n    # Before spread: x1  x2  ...  x31  x32\n    # After spread:  0   x1  ...   0   x16  ... 0  x31  0  x32\n    x = spread_int32_to_int64(x)\n    y = spread_int32_to_int64(y)\n\n    # The y value is then shifted 1 bit to the left.\n    # Before shift: 0   y1   0   y2 ... 0   y31   0   y32\n    # After shift:  y1   0   y2 ... 0   y31   0   y32   0\n    y_shifted = y \u003c\u003c 1\n\n    # Next, x and y_shifted are combined using a bitwise OR.\n    #\n    # Before bitwise OR (x): 0   x1   0   x2   ...  0   x31    0   x32\n    # Before bitwise OR (y): y1  0    y2  0    ...  y31  0    y32   0\n    # After bitwise OR     : y1  x2   y2  x2   ...  y31  x31  y32  x32\n    return x | y_shifted\n\n# Spreads a 32-bit integer to a 64-bit integer by inserting\n# 32 zero bits in-between.\n#\n# Before spread: x1  x2  ...  x31  x32\n# After spread:  0   x1  ...   0   x16  ... 0  x31  0  x32\ndef spread_int32_to_int64(v: int) -\u003e int:\n    # Ensure only lower 32 bits are non-zero.\n    v = v \u0026 0xFFFFFFFF\n\n    # Bitwise operations to spread 32 bits into 64 bits with zeros in-between\n    v = (v | (v \u003c\u003c 16)) \u0026 0x0000FFFF0000FFFF\n    v = (v | (v \u003c\u003c 8))  \u0026 0x00FF00FF00FF00FF\n    v = (v | (v \u003c\u003c 4))  \u0026 0x0F0F0F0F0F0F0F0F\n    v = (v | (v \u003c\u003c 2))  \u0026 0x3333333333333333\n    v = (v | (v \u003c\u003c 1))  \u0026 0x5555555555555555\n\n    return v\n\nscore = interleave(normalized_latitude, normalized_longitude)\n```\n\n# Decoding\n\nDecoding a score converts it back to the original latitude and longitude values. This is essentially the reverse of the encoding process.\n\n## Step 1: Separating the Interleaved Bits\n\nFirst, we need to separate the interleaved latitude and longitude bits from the score. Since longitude bits were shifted left by 1 during encoding, we need to shift them back.\n\n```python\n# Extract longitude bits (they were shifted left by 1 during encoding)\ny = geo_code \u003e\u003e 1\n\n# Extract latitude bits (they were in the original positions)\nx = geo_code\n```\n\n## Step 2: Compacting 64-bit integer to 32-bit integers\n\nThe bits were spread from 32-bit to 64-bit integers during encoding. Now we need to compact them back to 32-bit integers.\n\n```python\n# Compact both latitude and longitude back to 32-bit integers\ngrid_latitude_number = compact_int64_to_int32(x)\ngrid_longitude_number = compact_int64_to_int32(y)\n```\n\nHere's a pseudocode illustrating how int64 is compacted to int32.\n```python\ndef compact_int64_to_int32(v: int) -\u003e int:\n    # Keep only the bits in even positions\n    v = v \u0026 0x5555555555555555\n\n    # Before masking: w1   v1  ...   w2   v16  ... w31  v31  w32  v32\n    # After masking: 0   v1  ...   0   v16  ... 0  v31  0  v32\n\n    # Where w1, w2,..w31 are the digits from longitude if we're compacting latitude, or digits from latitude if we're compacting longitude\n    # So, we mask them out and only keep the relevant bits that we wish to compact\n\n    # ------\n    # Reverse the spreading process by shifting and masking\n    v = (v | (v \u003e\u003e 1)) \u0026 0x3333333333333333\n    v = (v | (v \u003e\u003e 2)) \u0026 0x0F0F0F0F0F0F0F0F\n    v = (v | (v \u003e\u003e 4)) \u0026 0x00FF00FF00FF00FF\n    v = (v | (v \u003e\u003e 8)) \u0026 0x0000FFFF0000FFFF\n    v = (v | (v \u003e\u003e 16)) \u0026 0x00000000FFFFFFFF\n\n    # Before compacting: 0   v1  ...   0   v16  ... 0  v31  0  v32\n    # After compacting: v1  v2  ...  v31  v32\n    # -----\n    \n    return v\n```\n\n## Step 3: Converting Back to Geographic Coordinates\n\nThe decoded 32-bit integers represent grid cell numbers. We convert them back to geographic coordinates by reversing the normalization process. Here's a pseudocode illustrating this process.\n\n```python\ndef convert_grid_numbers_to_coordinates(grid_latitude_number, grid_longitude_number) -\u003e (float, float):\n    # Calculate the grid boundaries\n    grid_latitude_min = MIN_LATITUDE + LATITUDE_RANGE * (grid_latitude_number / (2**26))\n    grid_latitude_max = MIN_LATITUDE + LATITUDE_RANGE * ((grid_latitude_number + 1) / (2**26))\n    grid_longitude_min = MIN_LONGITUDE + LONGITUDE_RANGE * (grid_longitude_number / (2**26))\n    grid_longitude_max = MIN_LONGITUDE + LONGITUDE_RANGE * ((grid_longitude_number + 1) / (2**26))\n    \n    # Calculate the center point of the grid cell\n    latitude = (grid_latitude_min + grid_latitude_max) / 2\n    longitude = (grid_longitude_min + grid_longitude_max) / 2\n    return (latitude, longitude)\n```\n\n\u003e [!NOTE]\n\u003e The decoded coordinates represent the center of a grid cell, not the exact original coordinates. This is because the encoding process truncates coordinates to grid cells. The precision depends on the grid resolution (which is determined by the 26-bit normalization).\n\n# FAQ\n\n### Why is the latitude range from -85.05° to 85.05° and not -90° to 90°?\n\nThis is because of the [Web Mercator projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) used to project the Earth onto a 2D plane.\n\n### Why are the latitude and longitude normalized to 26-bit integers instead of 32-bit integers?\n\nIn [sorted sets](https://redis.io/docs/latest/data-types/sorted-sets/), scores are stored as [float64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) numbers. Float64 values have limits on integer precision. They [can represent integers upto 2^53 accurately](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Precision_limitations_on_integer_values) but start to lose precision after that. Using 26-bit integers ensures that the final score is under 2^52 and thus within the integer precision limits of float64.\n\n### How do I test my implementation?\n\nHere are some example values to test your implementation against:\n\n| Place      | Latitude | Longitude | Score              |\n| ---------- | -------- | --------- | -------------------|\n| Bangkok    | 13.7220  | 100.5252  | 3962257306574459.0 |\n| Beijing    | 39.9075  | 116.3972  | 4069885364908765.0 |\n| Berlin     | 52.5244  | 13.4105   | 3673983964876493.0 |\n| Copenhagen | 55.6759  | 12.5655   | 3685973395504349.0 |\n| New Delhi  | 28.6667  | 77.2167   | 3631527070936756.0 |\n| Kathmandu  | 27.7017  | 85.3206   | 3639507404773204.0 |\n| London     | 51.5074  | -0.1278   | 2163557714755072.0 |\n| New York   | 40.7128  | -74.0060  | 1791873974549446.0 |\n| Paris      | 48.8534  | 2.3488    | 3663832752681684.0 |\n| Sydney     | -33.8688 | 151.2093  | 3252046221964352.0 |\n| Tokyo      | 35.6895  | 139.6917  | 4171231230197045.0 |\n| Vienna     | 48.2064  | 16.3707   | 3673109836391743.0 |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodecrafters-io%2Fredis-geocoding-algorithm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodecrafters-io%2Fredis-geocoding-algorithm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodecrafters-io%2Fredis-geocoding-algorithm/lists"}