Skip to main content
typo and review
Source Link
Peter Krauss
  • 2.4k
  • 24
  • 47

Use the S2.latLngToKey(lat,lon,level) to obtain YourCoolValuekey=YourCoolValue of your point

The keykey is like a base4 Geohash or a tile-quadkey. It is a string with the face of the S2 Cube and face_pos, the hierarchical position (base4) of the cell in the hierarchical (Hilbert) grid. The result of this example is

g   Sample          Cell_id_base16idHex              Face/KeyFace_pos 

--- Level  13
A1  1 Washington M: 89b7b7a400000000    4/1031233123310
A2  2 Josephine Sh: 89c259ac00000000    4/1032010230311
A3  3 Delacorte Th: 89c2589400000000    4/1032010230102
A3  4 Central Park: 89c2589c00000000    4/1032010230103
A3  5 Alexander Ha: 89c2589400000000    4/1032010230102
B   6 Merlion:      31da190c00000000    1/2032310030201
B   7 Merlion Park: 31da190c00000000    1/2032310030201
--- Level  20
A1  1 Washington M: 89b7b7a1bdf00000    4/10312331233100313233
A2  2 Josephine Sh: 89c259aa96100000    4/10320102303111102300
A3  3 Delacorte Th: 89c25891ac900000    4/10320102301020311210
A3  4 Central Park: 89c2589a08900000    4/10320102301031001010
A3  5 Alexander Ha: 89c2589747d00000    4/10320102301023220332
B   6 Merlion:      31da19085c300000    1/20323100302010023201
B   7 Merlion Park: 31da190860700000    1/20323100302010030003

The column Cell_id_base16idHex is the standard hexadecimal (base16) representation of the S2 Geometry Cell identifier (cell ID). The column Face/Key is an alternative representation of the same cell ID.

Each cell of the S2 Geometry Cell IDgrid have an unique number (64 bits unsigned integer) used to identify ifself, the internallythat is a 64structure of bits unsigned integerexpressing the position in the Hilbert curve (Face_pos) of a cube face.

You can use as geocode (said "MyCoolValue" in the question) any one, the Cell_id_base16idHex or the   key=Face/KeyFace_pos.

Cell IDCell ID and cell KeyCell Key have the same information, but ID mix the face informationand face_pos, and Keykey not. Face is explicit and each digit of face_pos is a level in the pure cell positiongrid structure.

There are some (minor) advantages of Hilbert curve (S2) over Z-order curve (Geohash), but no one is perfect... S2 Geometry indexation system is not oriented to human-readable codes, if the prefix of to cells are not the same In both, it is not guaranteenpossible two neighbors in the grid that have identifiers (!keys). There are a chance that the points are neighbors and its keys very different.

About convert "Face/Key"Face_pos" to hexadecimal, is possible for example convert the base4 10002200 to hexa 40a0, but base4 with more one digit will result in entirely different (or invalid) hexadecimal; for example 100022003 results in 102801... To avoid this problem, an extra digit must be added by a extend base16 algorithm The "more one digit" ocurrs when you compare for example level 18 keys with level 19 keys.

To avoid this problem, an extra digit must be added by a extend base16 algorithm.

function base4_to_base16(str) {
  const tr = {"0":"00","1":"01","2":"10","3":"11"}
  let strBin=''
  for(let i of str.split('')) strBin += tr[i]
  return BigInt('0b'+strBin).toString(16)
} 

function base4_to_base16h(str) {
  const tr = {"0":"J","1":"K","2":"L","3":"M"}
  let len = str.length
  if (len % 2 == 0)
    return base4_to_base16(str);
  let h = base4_to_base16( str.slice(0,-1) )  // cut last
  return h+tr[ str.slice(-1) ]
} 

var key = S2.latLngToKey(i.lat, i.lon, level);
[face,face_pos] = key.split('/');
keyHex = face+base4_to_base16hface + base4_to_base16h(face_pos)
g   Sample           Face/Key      Face_pos       = keyHex        bugKeyidHex

--- Level  18
A1  1 Washington M: 4/103123312331003132 = 44dbdbd0de   4dbdbd0de89b7b7a1bd000000
A2  2 Josephine Sh: 4/103201023031111023 = 44e12cd54b   4e12cd54b89c259aa97000000
A3  3 Delacorte Th: 4/103201023010203112 = 44e12c48d6   4e12c48d689c25891ad000000
A3  4 Central Park: 4/103201023010310010 = 44e12c4d04   4e12c4d0489c2589a09000000
A3  5 Alexander Ha: 4/103201023010232203 = 44e12c4ba3   4e12c4ba389c2589747000000
B   6 Merlion:   :   1/203231003020100232 = 18ed0c842e   8ed0c842e31da19085d000000
B   7 Merlion Park: 1/203231003020100300 = 18ed0c8430   8ed0c843031da190861000000
--- Level  19 (compare bugKey here!)
A1  1 Washington M: 4/1031233123310031323 = 44dbdbd0deM 136f6f437b89b7b7a1bdc00000
A2  2 Josephine Sh: 4/1032010230311110230 = 44e12cd54bJ 1384b3552c89c259aa96400000
A3  3 Delacorte Th: 4/1032010230102031121 = 44e12c48d6K 1384b1235989c25891acc00000
A3  4 Central Park: 4/1032010230103100101 = 44e12c4d04K 1384b1341189c2589a08c00000
A3  5 Alexander Ha: 4/1032010230102322033 = 44e12c4ba3M 1384b12e8f89c2589747c00000
B   6 Merlion:   :   1/2032310030201002320 = 18ed0c842eJ 23b43210b831da19085c400000
B   7 Merlion Park: 1/2032310030201003000 = 18ed0c8430J 23b43210c031da190860400000
--- Level  22
A1  1 Washington M: 4/1031233123310031323331 = 44dbdbd0defd 4dbdbd0defd89b7b7a1bdfb0000
A2  2 Josephine Sh: 4/1032010230311110230000 = 44e12cd54b00 4e12cd54b0089c259aa96010000
A3  3 Delacorte Th: 4/1032010230102031121011 = 44e12c48d645 4e12c48d64589c25891ac8b0000
A3  4 Central Park: 4/1032010230103100101000 = 44e12c4d0440 4e12c4d044089c2589a08810000
A3  5 Alexander Ha: 4/1032010230102322033211 = 44e12c4ba3e5 4e12c4ba3e589c2589747cb0000
B   6 Merlion:   :   1/2032310030201002320132 = 18ed0c842e1e 8ed0c842e1e31da19085c3d0000
B   7 Merlion Park: 1/2032310030201003000332 = 18ed0c84303e 8ed0c84303e31da1908607d0000

The idHex can be reduced with the trailing 0's removed (e.g. 89b7b7a1bd000000 reduced to 89b7b7a1bd). The information into keyHex and idHex are same, differing only in one bit.

The keyHex is better: represents the same cell with the same number of digits, but with a evident relationship with "Face/Face_pos" and always preserving prefix, with no distortions. The hexadecimal (base16) representation is aligned with the base4.

Notice the folowing idHex in the table, to see the distortions:

  • sample A1 (no distorcion). Level 18 is 89b7b7a1bd and level 19 is 89b7b7a1bdc, so concatenating one digit c.
  • sample A2 (distorcion). Level 18 is 89c259aa97 and level 19 is 89c259aa964, changed final 7 to 6 and concatenate 4.

Use the S2.latLngToKey(lat,lon,level) to obtain YourCoolValue of your point

The key like a base4 Geohash or a tile-quadkey. It is a string with the face of the S2 Cube and face_pos, the hierarchical position (base4) of the cell in the hierarchical (Hilbert) grid. The result of this example is

g   Sample          Cell_id_base16      Face/Key 

--- Level  13
A1  1 Washington M: 89b7b7a400000000    4/1031233123310
A2  2 Josephine Sh: 89c259ac00000000    4/1032010230311
A3  3 Delacorte Th: 89c2589400000000    4/1032010230102
A3  4 Central Park: 89c2589c00000000    4/1032010230103
A3  5 Alexander Ha: 89c2589400000000    4/1032010230102
B   6 Merlion:      31da190c00000000    1/2032310030201
B   7 Merlion Park: 31da190c00000000    1/2032310030201
--- Level  20
A1  1 Washington M: 89b7b7a1bdf00000    4/10312331233100313233
A2  2 Josephine Sh: 89c259aa96100000    4/10320102303111102300
A3  3 Delacorte Th: 89c25891ac900000    4/10320102301020311210
A3  4 Central Park: 89c2589a08900000    4/10320102301031001010
A3  5 Alexander Ha: 89c2589747d00000    4/10320102301023220332
B   6 Merlion:      31da19085c300000    1/20323100302010023201
B   7 Merlion Park: 31da190860700000    1/20323100302010030003

The column Cell_id_base16 is the standard representation of the S2 Geometry Cell ID, the internally is a 64 bits unsigned integer.

You can use as geocode (said "MyCoolValue" in the question) any one, the Cell_id_base16 or the  Face/Key.

Cell ID and cell Key have the same information, but ID mix the face information, and Key is the pure cell position.

There are some (minor) advantages of Hilbert curve (S2) over Z-order curve (Geohash), but no one is perfect... S2 Geometry indexation system is not oriented to human-readable codes, if the prefix of to cells are not the same, it is not guaranteen (!). There are a chance that the points are neighbors and its keys very different.

About convert "Face/Key" to hexadecimal, is possible for example convert the base4 10002200 to hexa 40a0, but base4 with more one digit will result in entirely different (or invalid) hexadecimal; for example 100022003 results in 102801... To avoid this problem, an extra digit must be added by a extend base16 algorithm.

function base4_to_base16(str) {
  const tr = {"0":"00","1":"01","2":"10","3":"11"}
  let strBin=''
  for(let i of str.split('')) strBin += tr[i]
  return BigInt('0b'+strBin).toString(16)
} 

function base4_to_base16h(str) {
  const tr = {"0":"J","1":"K","2":"L","3":"M"}
  let len = str.length
  if (len % 2 == 0)
    return base4_to_base16(str);
  let h = base4_to_base16( str.slice(0,-1) )  // cut last
  return h+tr[ str.slice(-1) ]
} 

var key = S2.latLngToKey(i.lat, i.lon, level);
[face,face_pos] = key.split('/');
keyHex = face+base4_to_base16h(face_pos)
g   Sample          Face/Key             = keyHex        bugKey

--- Level  18
A1  1 Washington M: 4/103123312331003132 = 44dbdbd0de   4dbdbd0de
A2  2 Josephine Sh: 4/103201023031111023 = 44e12cd54b   4e12cd54b
A3  3 Delacorte Th: 4/103201023010203112 = 44e12c48d6   4e12c48d6
A3  4 Central Park: 4/103201023010310010 = 44e12c4d04   4e12c4d04
A3  5 Alexander Ha: 4/103201023010232203 = 44e12c4ba3   4e12c4ba3
B   6 Merlion:      1/203231003020100232 = 18ed0c842e   8ed0c842e
B   7 Merlion Park: 1/203231003020100300 = 18ed0c8430   8ed0c8430
--- Level  19 (compare bugKey here!)
A1  1 Washington M: 4/1031233123310031323 = 44dbdbd0deM 136f6f437b
A2  2 Josephine Sh: 4/1032010230311110230 = 44e12cd54bJ 1384b3552c
A3  3 Delacorte Th: 4/1032010230102031121 = 44e12c48d6K 1384b12359
A3  4 Central Park: 4/1032010230103100101 = 44e12c4d04K 1384b13411
A3  5 Alexander Ha: 4/1032010230102322033 = 44e12c4ba3M 1384b12e8f
B   6 Merlion:      1/2032310030201002320 = 18ed0c842eJ 23b43210b8
B   7 Merlion Park: 1/2032310030201003000 = 18ed0c8430J 23b43210c0
--- Level  22
A1  1 Washington M: 4/1031233123310031323331 = 44dbdbd0defd 4dbdbd0defd
A2  2 Josephine Sh: 4/1032010230311110230000 = 44e12cd54b00 4e12cd54b00
A3  3 Delacorte Th: 4/1032010230102031121011 = 44e12c48d645 4e12c48d645
A3  4 Central Park: 4/1032010230103100101000 = 44e12c4d0440 4e12c4d0440
A3  5 Alexander Ha: 4/1032010230102322033211 = 44e12c4ba3e5 4e12c4ba3e5
B   6 Merlion:      1/2032310030201002320132 = 18ed0c842e1e 8ed0c842e1e
B   7 Merlion Park: 1/2032310030201003000332 = 18ed0c84303e 8ed0c84303e

Use the S2.latLngToKey(lat,lon,level) to obtain key=YourCoolValue of your point

The key is like a base4 Geohash or a tile-quadkey. It is a string with the face of the S2 Cube and face_pos, the hierarchical position (base4) of the cell in the hierarchical (Hilbert) grid. The result of this example is

g   Sample          idHex              Face/Face_pos 

--- Level  13
A1  1 Washington M: 89b7b7a400000000    4/1031233123310
A2  2 Josephine Sh: 89c259ac00000000    4/1032010230311
A3  3 Delacorte Th: 89c2589400000000    4/1032010230102
A3  4 Central Park: 89c2589c00000000    4/1032010230103
A3  5 Alexander Ha: 89c2589400000000    4/1032010230102
B   6 Merlion:      31da190c00000000    1/2032310030201
B   7 Merlion Park: 31da190c00000000    1/2032310030201
--- Level  20
A1  1 Washington M: 89b7b7a1bdf00000    4/10312331233100313233
A2  2 Josephine Sh: 89c259aa96100000    4/10320102303111102300
A3  3 Delacorte Th: 89c25891ac900000    4/10320102301020311210
A3  4 Central Park: 89c2589a08900000    4/10320102301031001010
A3  5 Alexander Ha: 89c2589747d00000    4/10320102301023220332
B   6 Merlion:      31da19085c300000    1/20323100302010023201
B   7 Merlion Park: 31da190860700000    1/20323100302010030003

The column idHex is the standard hexadecimal (base16) representation of the S2 Geometry Cell identifier (cell ID). The column Face/Key is an alternative representation of the same cell ID.

Each cell of the S2 Geometry grid have an unique number (64 bits unsigned integer) used to identify ifself, that is a structure of bits expressing the position in the Hilbert curve (Face_pos) of a cube face.

You can use as geocode (said "MyCoolValue" in the question) any one, the idHex or the key=Face/Face_pos.

Cell ID and Cell Key have the same information, but ID mix the face and face_pos, and key not. Face is explicit and each digit of face_pos is a level in the grid structure.

There are some (minor) advantages of Hilbert curve (S2) over Z-order curve (Geohash), but no one is perfect... In both, is possible two neighbors in the grid that have identifiers (keys) very different.

About convert "Face/Face_pos" to hexadecimal, is possible for example convert the base4 10002200 to hexa 40a0, but base4 with more one digit will result in entirely different (or invalid) hexadecimal; for example 100022003 results in 102801... The "more one digit" ocurrs when you compare for example level 18 keys with level 19 keys.

To avoid this problem, an extra digit must be added by a extend base16 algorithm.

function base4_to_base16(str) {
  const tr = {"0":"00","1":"01","2":"10","3":"11"}
  let strBin=''
  for(let i of str.split('')) strBin += tr[i]
  return BigInt('0b'+strBin).toString(16)
} 

function base4_to_base16h(str) {
  const tr = {"0":"J","1":"K","2":"L","3":"M"}
  let len = str.length
  if (len % 2 == 0)
    return base4_to_base16(str);
  let h = base4_to_base16( str.slice(0,-1) )  // cut last
  return h+tr[ str.slice(-1) ]
} 

var key = S2.latLngToKey(i.lat, i.lon, level);
[face,face_pos] = key.split('/');
keyHex = face + base4_to_base16h(face_pos)
g   Sample           Face/Face_pos       = keyHex       idHex

--- Level  18
A1  1 Washington M: 4/103123312331003132 = 44dbdbd0de   89b7b7a1bd000000
A2  2 Josephine Sh: 4/103201023031111023 = 44e12cd54b   89c259aa97000000
A3  3 Delacorte Th: 4/103201023010203112 = 44e12c48d6   89c25891ad000000
A3  4 Central Park: 4/103201023010310010 = 44e12c4d04   89c2589a09000000
A3  5 Alexander Ha: 4/103201023010232203 = 44e12c4ba3   89c2589747000000
B   6 Merlion   :   1/203231003020100232 = 18ed0c842e   31da19085d000000
B   7 Merlion Park: 1/203231003020100300 = 18ed0c8430   31da190861000000
--- Level  19
A1  1 Washington M: 4/1031233123310031323 = 44dbdbd0deM 89b7b7a1bdc00000
A2  2 Josephine Sh: 4/1032010230311110230 = 44e12cd54bJ 89c259aa96400000
A3  3 Delacorte Th: 4/1032010230102031121 = 44e12c48d6K 89c25891acc00000
A3  4 Central Park: 4/1032010230103100101 = 44e12c4d04K 89c2589a08c00000
A3  5 Alexander Ha: 4/1032010230102322033 = 44e12c4ba3M 89c2589747c00000
B   6 Merlion   :   1/2032310030201002320 = 18ed0c842eJ 31da19085c400000
B   7 Merlion Park: 1/2032310030201003000 = 18ed0c8430J 31da190860400000
--- Level  22
A1  1 Washington M: 4/1031233123310031323331 = 44dbdbd0defd 89b7b7a1bdfb0000
A2  2 Josephine Sh: 4/1032010230311110230000 = 44e12cd54b00 89c259aa96010000
A3  3 Delacorte Th: 4/1032010230102031121011 = 44e12c48d645 89c25891ac8b0000
A3  4 Central Park: 4/1032010230103100101000 = 44e12c4d0440 89c2589a08810000
A3  5 Alexander Ha: 4/1032010230102322033211 = 44e12c4ba3e5 89c2589747cb0000
B   6 Merlion   :   1/2032310030201002320132 = 18ed0c842e1e 31da19085c3d0000
B   7 Merlion Park: 1/2032310030201003000332 = 18ed0c84303e 31da1908607d0000

The idHex can be reduced with the trailing 0's removed (e.g. 89b7b7a1bd000000 reduced to 89b7b7a1bd). The information into keyHex and idHex are same, differing only in one bit.

The keyHex is better: represents the same cell with the same number of digits, but with a evident relationship with "Face/Face_pos" and always preserving prefix, with no distortions. The hexadecimal (base16) representation is aligned with the base4.

Notice the folowing idHex in the table, to see the distortions:

  • sample A1 (no distorcion). Level 18 is 89b7b7a1bd and level 19 is 89b7b7a1bdc, so concatenating one digit c.
  • sample A2 (distorcion). Level 18 is 89c259aa97 and level 19 is 89c259aa964, changed final 7 to 6 and concatenate 4.
general review, now a good solution
Source Link
Peter Krauss
  • 2.4k
  • 24
  • 47

The S2 Geometry library is complex, but for your needs you can use a partial implementation, as some Javascript port.

You can use NodeJS installing the s2-geometry package (e. g. by npm install s2-geometry), or this CDN link for HTML pages.

Short answer

Use the S2.latLngToKey(lat,lon,level) to obtain YourCoolValue of your point

    var vals = [
      S2.latLngToKey(48.669, -4.329, 20), // reference
      S2.latLngToKey(48.668, -4.330, 20), // near
      S2.latLngToKey(49, -4.3, 20)        // far
    ]
      

The key like a base4 Geohash or a tile-quadkey. It is a string with the face of the S2 Cube and face_pos, the hierarchical position (base4) of the cell in the hierarchical (Hilbert) grid. The result of this example is

[ '2/10002200003102120322',
  '2/10002200003131222211',
  '2/10002130111010302012' ]

Where you can see same big prefix for two near points, 2/100022000031 and only little prefix when the points are not near, 2/10002.

About the distance that the commom prefix represents, check the S2 cell Statistics: level 13 seems adequate for check 1km, that is the 13-digits

[ '2/1000220000310',
  '2/1000220000313',
  '2/1000213011101' ]

Tests

I done my tests using the Javascript S2 libraryS2 geometry library, and starting with a sample of well-known places, obtained by this Wikidata-query:

[
Group QID       Name {"g":"A1","qid":178114,        "lat":            latitude  longitude

A1    Q178114   Washington Monument      38.889475,88948 "lon": -77.035244}
     A2   ,{"g":"A2","qid":17300119, Q17300119 Josephine Shaw Fountain  "lat":40.754,    "lon":-73.9841}
A3    Q1060845  Delacorte Theater ,{"g":"A3","qid":1060845,       "lat":40.7801,   "lon":-73.968767}
A3    Q160409   Central ,{"g":"A3","qid":160409,Park        "lat":     40.7825,   "lon":-73.966111}
 A3    Q19473784 Alexander Hamilton ,{"g":"A3","qid":19473784,      "lat":40.781028, "lon":-73.964556}
B     Q208760   ,{"g":"B","qid":208760,Merlion         "lat":         1.287022,2870222 "lon": 103.854689}
B     Q6819812  Merlion ,{"g":"B","qid":6819812,Park        "lat":     1.28683,  "lon":  103.855}
]

Save it as samples.json.

It is a list ofNote: to add other objects where lat and lon are usual latitude and longitude ISO values, g stands "group of samples" and qid "Wikidata Q-id"or check more details use QID. For instance the first sample have qid=178114QID=178114 so you can check details with http://wikidata.org/entity/Q URL: http://wikidata.org/entity/Q178114

With Javascript you need to use NodeJS, andComplete install the s2-geometry packageJavascript code, e. g. by npm install s2-geometry. Now you can runwith sample data and illustrating options for encoding:

'use strict';
var S2 = require('s2-geometry').S2; // this line for NodeJS

var geosamples= [
  {"g":"A1","item":"Q178114",  "iso3166":"US","lat":"38.889475","lon":"-77.035244444","name":"Washington Monument"},
  {"g":"A2","item":"Q17300119","iso3166":"US","lat":"40.754","lon":"-73.9841","name":"Josephine Shaw Lowell Memorial Fountain"},
  {"g":"A3","item":"Q1060845", "iso3166":"US","lat":"40.7801","lon":"-73.968766666","name":"Delacorte Theater"},
  {"g":"A3","item":"Q160409",  "iso3166":"US","lat":"40.7825","lon":"-73.966111111","name":"Central Park"},
  {"g":"A3","item":"Q19473784","iso3166":"US","lat":"40.781027777","lon":"-73.964555555","name":"Alexander Hamilton"},
  {"g":"B", "item":"Q208760",  "iso3166":"SG","lat":"1.287022222","lon":"103.854688888","name":"Merlion"},
  {"g":"B", "item":"Q6819812", "iso3166":"SG","lat":"1.28683","lon":"103.855","name":"Merlion Park"}
];

function show(level=13) {
  console.log("--- Level ",level)
  let face,face_pos;
  let j=1
  for (const i of geosamples) {
        var key = S2.latLngToKey(i.lat, i.lon, level); // base4 hiearchy
        let id = BigInt( S2.keyToId(key) ); // Cell ID is a 64 bits integer
        let idHex = id.toString(16)  // compact human-readable complete code
        console.log(i.g+"\t"+j,i.name.slice(0,12)+":\t" + idHex+"\t" key);
        j++
  }
}
show();
show(20);

Results:

'useg strict';  Sample  
const fs = require('fs');
var S2 = require('s2-geometry').S2;  Cell_id_base16      Face/Key 

let--- rawdataLevel = fs.readFileSync('samples.json');13
letA1 geosamples =1 JSON.parse(rawdata);

varWashington levelM: =89b7b7a400000000 20; /  4/1031233123310
A2 20 for2 theJosephine parks,Sh: 2589c259ac00000000 for other samples 4/1032010230311
forA3 (const i3 ofDelacorte geosamples)Th: {89c2589400000000    4/1032010230102
A3  4 Central Park: 89c2589c00000000   var key4/1032010230103
A3 = S2.latLngToKey(i.lat,5 i.lon,Alexander level);Ha: 89c2589400000000    4/1032010230102
B   6 Merlion:    var id =31da190c00000000 S2.keyToId(key);
   1/2032310030201
B   7 Merlion console.log(i.g+"\tQ"+i.qid+"Park:\t"+id);
} 31da190c00000000    1/2032310030201

Result

---- LEVELLevel 25 ----20
A1  Q1781141 Washington M: 89b7b7a1bdf00000   9923602209239030784
 4/10312331233100313233
A2  Q17300119:2 Josephine 9926595117873935360

A3Sh: 89c259aa96100000 Q1060845:   99265939113663068164/10320102303111102300
A3  Q1604093 Delacorte Th: 89c25891ac900000   - 4/10320102301020311210
A3  Q194737844 Central Park: 89c2589a08900000 9926593935445775360
   4/10320102301031001010
BA3  5 Q208760Alexander Ha: 89c2589747d00000   3592211176479349760 4/10320102301023220332
B   Q68198126 Merlion:   -

level 20, the parks:

A3  Q1060845: 31da19085c300000  9926593911366615040  1/20323100302010023201
B   Q68198127 Merlion Park: 31da190860700000  3592211176549777408  1/20323100302010030003

As you see, A1The column Cell_id_base16 is the standard representation of the S2 Geometry Cell ID, A2 and A3 are at same countrythe internally is a 64 bits unsigned integer.

You can use as geocode (USAsaid "MyCoolValue" in the question) any one, so all have the commom prefix 992Cell_id_base16 at country levelor the Face/Key. A2

Cell ID and A3 are incell Key have the same cityinformation, but ID mix the face information, and shows same 992659 prefix. All A3 samples are atKey is the Central Parkpure cell position.


Complete answer

The main advantage of S2 Geometry over Geohash is uniformity, the (NYnear) region, showing bigger prefixconstant shape and area of the S2 Geometry cells. A grid of equal-area is very important in statistics and another applications, 99265939see this Open Geospatial Consortium standard about the theme. The fact that level 25 cells

There are into level cellsome (the parkminor) advantages of Hilbert curve (S2) over Z-order curve (Geohash), but no one is perfect... S2 Geometry indexation system is not aparent inoriented to human-readable codes, if the numbersprefix of to cells are not the same, thereit is not guaranteen (!). There are special calculationsa chance that the points are neighbors and its keys very different.

For application where you need 100% reliable result, asuse also the functions like containsGetEdgeNeighbors() of the s2-geometry package.

Suggestion for base16 encoding

About convert "Face/Key" to hexadecimal,  is possible for example convert the base4 exactArea()10002200 to hexa 40a0, etcbut base4 with more one digit will result in entirely different (or invalid) hexadecimal; for example 100022003 results in 102801... To avoid this problem, an extra digit must be added by a extend base16 algorithm.


 
function base4_to_base16(str) {
  const tr = {"0":"00","1":"01","2":"10","3":"11"}
  let strBin=''
  for(let i of str.split('')) strBin += tr[i]
  return BigInt('0b'+strBin).toString(16)
} 

function base4_to_base16h(str) {
  const tr = {"0":"J","1":"K","2":"L","3":"M"}
  let len = str.length
  if (len % 2 == 0)
    return base4_to_base16(str);
  let h = base4_to_base16( str.slice(0,-1) )  // cut last
  return h+tr[ str.slice(-1) ]
} 

var key = S2.latLngToKey(i.lat, i.lon, level);
[face,face_pos] = key.split('/');
keyHex = face+base4_to_base16h(face_pos)

Sample detailsComparative results

wikidata_idg scale  Sample name
Q178114     cm²    Face/Key Washington Monument (DC)
Q17300119   ~70m²   Josephine Shaw Lowell Fountain (NY)= keyHex        bugKey
Q1060845
--- Level  18
A1  ~1km²1 Washington M: Delacorte4/103123312331003132 Theater= 44dbdbd0de   4dbdbd0de
Q160409A2  2 Josephine Sh: 4/103201023031111023 = 44e12cd54b   4e12cd54b
A3  3. Delacorte Th: 4/103201023010203112 km²= 44e12c48d6   4e12c48d6
A3  4 Central Park: 4/103201023010310010 = 44e12c4d04   4e12c4d04
A3  5 Alexander Ha: 4/103201023010232203 = 44e12c4ba3   4e12c4ba3
B   6 Merlion:      1/203231003020100232 = 18ed0c842e   8ed0c842e
B   7 Merlion Park: 1/203231003020100300 = 18ed0c8430   8ed0c8430
--- Level  19 (NYcompare bugKey here!)
Q19473784A1  1 ~1m²Washington M: 4/1031233123310031323 = 44dbdbd0deM 136f6f437b
A2  2 Josephine Sh: 4/1032010230311110230 = 44e12cd54bJ 1384b3552c
A3  3 Delacorte Th: 4/1032010230102031121 = 44e12c48d6K 1384b12359
A3  4 Central Park: 4/1032010230103100101 = 44e12c4d04K 1384b13411
A3  5 Alexander Hamilton'sHa: statue4/1032010230102322033 = 44e12c4ba3M 1384b12e8f
Q208760B   6 Merlion: ~30m²     1/2032310030201002320 = 18ed0c842eJ 23b43210b8
B   7 Merlion fountainePark: 1/2032310030201003000 = 18ed0c8430J 23b43210c0
Q6819812--- Level  22
A1  1 Washington M: 4/1031233123310031323331 = 44dbdbd0defd 4dbdbd0defd
A2  2. Josephine Sh: 4/1032010230311110230000 = 44e12cd54b00 4e12cd54b00
A3  3 Delacorte Th: 4/1032010230102031121011 = 44e12c48d645 4e12c48d645
A3  4 Central Park: 4/1032010230103100101000 = 44e12c4d0440 4e12c4d0440
A3  5 km²Alexander Ha: 4/1032010230102322033211 = 44e12c4ba3e5 4e12c4ba3e5
B   6 Merlion:      1/2032310030201002320132 = 18ed0c842e1e 8ed0c842e1e
B   7 Merlion Park: (Singapore)1/2032310030201003000332 = 18ed0c84303e 8ed0c84303e

I done my tests using the Javascript S2 library, and starting with a sample of well-known places,

[
         {"g":"A1","qid":178114,        "lat":38.889475, "lon":-77.035244}
        ,{"g":"A2","qid":17300119,      "lat":40.754,    "lon":-73.9841}
        ,{"g":"A3","qid":1060845,       "lat":40.7801,   "lon":-73.968767}
        ,{"g":"A3","qid":160409,        "lat":40.7825,   "lon":-73.966111}
        ,{"g":"A3","qid":19473784,      "lat":40.781028, "lon":-73.964556}
        ,{"g":"B","qid":208760,         "lat":1.287022, "lon":103.854689}
        ,{"g":"B","qid":6819812,        "lat":1.28683,  "lon":103.855}
]

Save it as samples.json.

It is a list of objects where lat and lon are usual latitude and longitude ISO values, g stands "group of samples" and qid "Wikidata Q-id". For instance the first sample have qid=178114 so you can check details with http://wikidata.org/entity/Q URL: http://wikidata.org/entity/Q178114

With Javascript you need to use NodeJS, and install the s2-geometry package, e. g. by npm install s2-geometry. Now you can run

'use strict';    
const fs = require('fs');
var S2 = require('s2-geometry').S2;

let rawdata = fs.readFileSync('samples.json');
let geosamples = JSON.parse(rawdata);

var level = 20; // 20 for the parks, 25 for other samples
for (const i of geosamples) {
        var key = S2.latLngToKey(i.lat, i.lon, level);
        var id = S2.keyToId(key);
        console.log(i.g+"\tQ"+i.qid+":\t"+id);
}

Result

---- LEVEL 25 ----
A1  Q178114:    9923602209239030784

A2  Q17300119:  9926595117873935360

A3  Q1060845:   9926593911366306816
A3  Q160409:    -
A3  Q19473784:  9926593935445775360

B   Q208760:    3592211176479349760
B   Q6819812:   -

level 20, the parks:

A3  Q1060845:   9926593911366615040
B   Q6819812:   3592211176549777408

As you see, A1, A2 and A3 are at same country (USA), so all have the commom prefix 992 at country level. A2 and A3 are in the same city, and shows same 992659 prefix. All A3 samples are at the Central Park (NY) region, showing bigger prefix, 99265939. The fact that level 25 cells are into level cell (the park) is not aparent in the numbers, there are special calculations, as contains(),  exactArea(), etc.


 

Sample details

wikidata_id scale   name
Q178114     cm²     Washington Monument (DC)
Q17300119   ~70m²   Josephine Shaw Lowell Fountain (NY)
Q1060845    ~1km²   Delacorte Theater
Q160409     3.4 km² Central Park (NY)
Q19473784   ~1m²    Alexander Hamilton's statue
Q208760     ~30m²   Merlion fountaine
Q6819812    2.5 km² Merlion Park (Singapore)

The S2 Geometry library is complex, but for your needs you can use a partial implementation, as some Javascript port.

You can use NodeJS installing the s2-geometry package (e. g. by npm install s2-geometry), or this CDN link for HTML pages.

Short answer

Use the S2.latLngToKey(lat,lon,level) to obtain YourCoolValue of your point

    var vals = [
      S2.latLngToKey(48.669, -4.329, 20), // reference
      S2.latLngToKey(48.668, -4.330, 20), // near
      S2.latLngToKey(49, -4.3, 20)        // far
    ]
      

The key like a base4 Geohash or a tile-quadkey. It is a string with the face of the S2 Cube and face_pos, the hierarchical position (base4) of the cell in the hierarchical (Hilbert) grid. The result of this example is

[ '2/10002200003102120322',
  '2/10002200003131222211',
  '2/10002130111010302012' ]

Where you can see same big prefix for two near points, 2/100022000031 and only little prefix when the points are not near, 2/10002.

About the distance that the commom prefix represents, check the S2 cell Statistics: level 13 seems adequate for check 1km, that is the 13-digits

[ '2/1000220000310',
  '2/1000220000313',
  '2/1000213011101' ]

Tests

I done my tests using the Javascript S2 geometry library, and starting with a sample of well-known places, obtained by this Wikidata-query:

Group QID       Name                     latitude  longitude

A1    Q178114   Washington Monument      38.88948  -77.035244
A2    Q17300119 Josephine Shaw Fountain  40.754    -73.9841
A3    Q1060845  Delacorte Theater        40.7801   -73.968767
A3    Q160409   Central Park             40.7825   -73.966111
A3    Q19473784 Alexander Hamilton       40.781028 -73.964556
B     Q208760   Merlion                  1.2870222  103.854689
B     Q6819812  Merlion Park             1.28683    103.855

Note: to add other objects or check more details use QID. For instance the first sample have QID=178114 so you can check details with http://wikidata.org/entity/ URL: http://wikidata.org/entity/Q178114

Complete Javascript code, with sample data and illustrating options for encoding:

'use strict';
var S2 = require('s2-geometry').S2; // this line for NodeJS

var geosamples= [
  {"g":"A1","item":"Q178114",  "iso3166":"US","lat":"38.889475","lon":"-77.035244444","name":"Washington Monument"},
  {"g":"A2","item":"Q17300119","iso3166":"US","lat":"40.754","lon":"-73.9841","name":"Josephine Shaw Lowell Memorial Fountain"},
  {"g":"A3","item":"Q1060845", "iso3166":"US","lat":"40.7801","lon":"-73.968766666","name":"Delacorte Theater"},
  {"g":"A3","item":"Q160409",  "iso3166":"US","lat":"40.7825","lon":"-73.966111111","name":"Central Park"},
  {"g":"A3","item":"Q19473784","iso3166":"US","lat":"40.781027777","lon":"-73.964555555","name":"Alexander Hamilton"},
  {"g":"B", "item":"Q208760",  "iso3166":"SG","lat":"1.287022222","lon":"103.854688888","name":"Merlion"},
  {"g":"B", "item":"Q6819812", "iso3166":"SG","lat":"1.28683","lon":"103.855","name":"Merlion Park"}
];

function show(level=13) {
  console.log("--- Level ",level)
  let face,face_pos;
  let j=1
  for (const i of geosamples) {
        var key = S2.latLngToKey(i.lat, i.lon, level); // base4 hiearchy
        let id = BigInt( S2.keyToId(key) ); // Cell ID is a 64 bits integer
        let idHex = id.toString(16)  // compact human-readable complete code
        console.log(i.g+"\t"+j,i.name.slice(0,12)+":\t" + idHex+"\t" key);
        j++
  }
}
show();
show(20);

Results:

g   Sample          Cell_id_base16      Face/Key 

--- Level  13
A1  1 Washington M: 89b7b7a400000000    4/1031233123310
A2  2 Josephine Sh: 89c259ac00000000    4/1032010230311
A3  3 Delacorte Th: 89c2589400000000    4/1032010230102
A3  4 Central Park: 89c2589c00000000    4/1032010230103
A3  5 Alexander Ha: 89c2589400000000    4/1032010230102
B   6 Merlion:      31da190c00000000    1/2032310030201
B   7 Merlion Park: 31da190c00000000    1/2032310030201
--- Level  20
A1  1 Washington M: 89b7b7a1bdf00000    4/10312331233100313233
A2  2 Josephine Sh: 89c259aa96100000    4/10320102303111102300
A3  3 Delacorte Th: 89c25891ac900000    4/10320102301020311210
A3  4 Central Park: 89c2589a08900000    4/10320102301031001010
A3  5 Alexander Ha: 89c2589747d00000    4/10320102301023220332
B   6 Merlion:      31da19085c300000    1/20323100302010023201
B   7 Merlion Park: 31da190860700000    1/20323100302010030003

The column Cell_id_base16 is the standard representation of the S2 Geometry Cell ID, the internally is a 64 bits unsigned integer.

You can use as geocode (said "MyCoolValue" in the question) any one, the Cell_id_base16 or the Face/Key.

Cell ID and cell Key have the same information, but ID mix the face information, and Key is the pure cell position.


Complete answer

The main advantage of S2 Geometry over Geohash is uniformity, the (near) constant shape and area of the S2 Geometry cells. A grid of equal-area is very important in statistics and another applications, see this Open Geospatial Consortium standard about the theme.

There are some (minor) advantages of Hilbert curve (S2) over Z-order curve (Geohash), but no one is perfect... S2 Geometry indexation system is not oriented to human-readable codes, if the prefix of to cells are not the same, it is not guaranteen (!). There are a chance that the points are neighbors and its keys very different.

For application where you need 100% reliable result, use also the functions like GetEdgeNeighbors() of the s2-geometry package.

Suggestion for base16 encoding

About convert "Face/Key" to hexadecimal, is possible for example convert the base4 10002200 to hexa 40a0, but base4 with more one digit will result in entirely different (or invalid) hexadecimal; for example 100022003 results in 102801... To avoid this problem, an extra digit must be added by a extend base16 algorithm.

function base4_to_base16(str) {
  const tr = {"0":"00","1":"01","2":"10","3":"11"}
  let strBin=''
  for(let i of str.split('')) strBin += tr[i]
  return BigInt('0b'+strBin).toString(16)
} 

function base4_to_base16h(str) {
  const tr = {"0":"J","1":"K","2":"L","3":"M"}
  let len = str.length
  if (len % 2 == 0)
    return base4_to_base16(str);
  let h = base4_to_base16( str.slice(0,-1) )  // cut last
  return h+tr[ str.slice(-1) ]
} 

var key = S2.latLngToKey(i.lat, i.lon, level);
[face,face_pos] = key.split('/');
keyHex = face+base4_to_base16h(face_pos)

Comparative results

g   Sample          Face/Key             = keyHex        bugKey

--- Level  18
A1  1 Washington M: 4/103123312331003132 = 44dbdbd0de   4dbdbd0de
A2  2 Josephine Sh: 4/103201023031111023 = 44e12cd54b   4e12cd54b
A3  3 Delacorte Th: 4/103201023010203112 = 44e12c48d6   4e12c48d6
A3  4 Central Park: 4/103201023010310010 = 44e12c4d04   4e12c4d04
A3  5 Alexander Ha: 4/103201023010232203 = 44e12c4ba3   4e12c4ba3
B   6 Merlion:      1/203231003020100232 = 18ed0c842e   8ed0c842e
B   7 Merlion Park: 1/203231003020100300 = 18ed0c8430   8ed0c8430
--- Level  19 (compare bugKey here!)
A1  1 Washington M: 4/1031233123310031323 = 44dbdbd0deM 136f6f437b
A2  2 Josephine Sh: 4/1032010230311110230 = 44e12cd54bJ 1384b3552c
A3  3 Delacorte Th: 4/1032010230102031121 = 44e12c48d6K 1384b12359
A3  4 Central Park: 4/1032010230103100101 = 44e12c4d04K 1384b13411
A3  5 Alexander Ha: 4/1032010230102322033 = 44e12c4ba3M 1384b12e8f
B   6 Merlion:      1/2032310030201002320 = 18ed0c842eJ 23b43210b8
B   7 Merlion Park: 1/2032310030201003000 = 18ed0c8430J 23b43210c0
--- Level  22
A1  1 Washington M: 4/1031233123310031323331 = 44dbdbd0defd 4dbdbd0defd
A2  2 Josephine Sh: 4/1032010230311110230000 = 44e12cd54b00 4e12cd54b00
A3  3 Delacorte Th: 4/1032010230102031121011 = 44e12c48d645 4e12c48d645
A3  4 Central Park: 4/1032010230103100101000 = 44e12c4d0440 4e12c4d0440
A3  5 Alexander Ha: 4/1032010230102322033211 = 44e12c4ba3e5 4e12c4ba3e5
B   6 Merlion:      1/2032310030201002320132 = 18ed0c842e1e 8ed0c842e1e
B   7 Merlion Park: 1/2032310030201003000332 = 18ed0c84303e 8ed0c84303e
Source Link
Peter Krauss
  • 2.4k
  • 24
  • 47

I done my tests using the Javascript S2 library, and starting with a sample of well-known places,

[
         {"g":"A1","qid":178114,        "lat":38.889475, "lon":-77.035244}
        ,{"g":"A2","qid":17300119,      "lat":40.754,    "lon":-73.9841}
        ,{"g":"A3","qid":1060845,       "lat":40.7801,   "lon":-73.968767}
        ,{"g":"A3","qid":160409,        "lat":40.7825,   "lon":-73.966111}
        ,{"g":"A3","qid":19473784,      "lat":40.781028, "lon":-73.964556}
        ,{"g":"B","qid":208760,         "lat":1.287022, "lon":103.854689}
        ,{"g":"B","qid":6819812,        "lat":1.28683,  "lon":103.855}
]

Save it as samples.json.

It is a list of objects where lat and lon are usual latitude and longitude ISO values, g stands "group of samples" and qid "Wikidata Q-id". For instance the first sample have qid=178114 so you can check details with http://wikidata.org/entity/Q URL: http://wikidata.org/entity/Q178114

With Javascript you need to use NodeJS, and install the s2-geometry package, e. g. by npm install s2-geometry. Now you can run

'use strict';    
const fs = require('fs');
var S2 = require('s2-geometry').S2;

let rawdata = fs.readFileSync('samples.json');
let geosamples = JSON.parse(rawdata);

var level = 20; // 20 for the parks, 25 for other samples
for (const i of geosamples) {
        var key = S2.latLngToKey(i.lat, i.lon, level);
        var id = S2.keyToId(key);
        console.log(i.g+"\tQ"+i.qid+":\t"+id);
}

Result

---- LEVEL 25 ----
A1  Q178114:    9923602209239030784

A2  Q17300119:  9926595117873935360

A3  Q1060845:   9926593911366306816
A3  Q160409:    -
A3  Q19473784:  9926593935445775360

B   Q208760:    3592211176479349760
B   Q6819812:   -

level 20, the parks:

A3  Q1060845:   9926593911366615040
B   Q6819812:   3592211176549777408

As you see, A1, A2 and A3 are at same country (USA), so all have the commom prefix 992 at country level. A2 and A3 are in the same city, and shows same 992659 prefix. All A3 samples are at the Central Park (NY) region, showing bigger prefix, 99265939. The fact that level 25 cells are into level cell (the park) is not aparent in the numbers, there are special calculations, as contains(), exactArea(), etc.


Sample details

wikidata_id scale   name
Q178114     cm²     Washington Monument (DC)
Q17300119   ~70m²   Josephine Shaw Lowell Fountain (NY)
Q1060845    ~1km²   Delacorte Theater
Q160409     3.4 km² Central Park (NY)
Q19473784   ~1m²    Alexander Hamilton's statue
Q208760     ~30m²   Merlion fountaine
Q6819812    2.5 km² Merlion Park (Singapore)