{"id":41845050,"url":"https://github.com/to-masz/s2examples","last_synced_at":"2026-01-25T09:44:35.062Z","repository":{"id":72476514,"uuid":"102379433","full_name":"to-masz/s2examples","owner":"to-masz","description":null,"archived":false,"fork":false,"pushed_at":"2017-09-04T20:39:30.000Z","size":177,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"master","last_synced_at":"2024-01-30T09:16:23.213Z","etag":null,"topics":["elasticsearch","geohashing","geolocation","geometry","geospatial","google-maps","php","s2"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/to-masz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-09-04T16:08:23.000Z","updated_at":"2023-02-23T21:20:17.000Z","dependencies_parsed_at":"2023-04-21T18:01:54.590Z","dependency_job_id":null,"html_url":"https://github.com/to-masz/s2examples","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"a22f01f2fbba9a65d89534f6b8dedea214f1bce4"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/to-masz/s2examples","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/to-masz%2Fs2examples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/to-masz%2Fs2examples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/to-masz%2Fs2examples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/to-masz%2Fs2examples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/to-masz","download_url":"https://codeload.github.com/to-masz/s2examples/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/to-masz%2Fs2examples/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28750900,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T09:00:19.176Z","status":"ssl_error","status_checked_at":"2026-01-25T09:00:04.131Z","response_time":113,"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":["elasticsearch","geohashing","geolocation","geometry","geospatial","google-maps","php","s2"],"created_at":"2026-01-25T09:44:34.450Z","updated_at":"2026-01-25T09:44:35.040Z","avatar_url":"https://github.com/to-masz.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# S2 exmples\n\nThis repository contains the complete example on how to use Google's S2 library in PHP. Please follow this README to understand the contents. \n\nIt is a live example from my presentation **Having fun with geospatial data in your software. An introduction to Google's S2 geometry library.**\n\n## Preliminary notes\n\nS2 library is written originally in C++. It was ported to several languages, unfortunately there is no really good port in PHP. \n**It gives you a great opportunity to contribute to PHP world and rewrite the library as other people did for Java or Python**.\n\nI recommend to use `NicklasWallgren/s2-geometry-library-php` fork, that is a fork of another fork of another fork of ... Simply everyone in that chain have added or fixed quite important part of the library. Nicklas seems to be one, that has time to review and merge all pull requests.\n \n```bash\ncomposer require NicklasWallgren/s2-geometry-library-php\n```\n\nFor now there are few changes pending, so this repo uses my fork.\n\n\n## Simple examples\n\n### S2 cell identifiers\n\nCheck `utils/examples.php` for simple examples of using `S2CellId` class.\n\n```php\n$lat = 52.4049292;\n$lng = 16.9096754;\n\n$s2CellId = S2CellId::fromLatLng(S2LatLng::fromDegrees($lat, $lng));\n\necho $s2CellId-\u003eid() . PHP_EOL;\necho decbin($s2CellId-\u003eid()) . PHP_EOL;\necho $s2CellId-\u003etoToken() . PHP_EOL;\n```\n\nTo create S2 cell identifier you can simply use pair of latitude and longitude coordinates. \nThen you can get its representation as 64-bit integer, binary string or string token. The above code output following results:\n\n```\n5117315353002051839\n100011100000100010110110011001101101011010101110000100011111111\n47045b336b5708ff\n```\n\nPlease note the meaning of that binary representation is\n\nbits |  | meaning \n--- | --- | ---\n0-2 | 100 | The face of the cube the location belongs to (from 0 to 5). The example location belongs to the 4th face.\n3-63 | 01110000010001011011001100110110101101010111000010001111111 | Each 2 bits define the given node at each level in quadtree.\n64 | 1 | Whether the location is precise (1) or approximated (0).\n\n\nAs the hierarchy of the S2 cells is 30 levels quadtreee, you often want to get a less precise identifier.\n\n```php\n$s2CellIdLvl10 = $s2CellId-\u003eparent(10);\n\necho $s2CellIdLvl10-\u003eid() . PHP_EOL;\necho decbin($s2CellIdLvl10-\u003eid()) . PHP_EOL;\necho $s2CellIdLvl10-\u003etoToken() . PHP_EOL;\n```\n\nThe above example gets the 10th level of the cell id. Please note the level is the level of the quadtree. It means the 1st level is the less precise and 30th is the exact location.\nThe result of the code is (please note the difference with previous output):\n\n```\n5117315132157853696\n100011100000100010110110000000000000000000000000000000000000000\n47045b\n```\n\n### S2 cell neighbours\n\nAs the cell identifier belongs to quadtree, it has only 3 neighbours in the tree (other nodes from the same parent node).\nBut each cell has 8 neighbours, fortunately it is very easy to get them all.\n\n```php\n$neighbors = [];\n$s2CellId-\u003egetAllNeighbors($s2CellId-\u003elevel(), $neighbors);\n\nforeach ($neighbors as $neighbor) {\n    echo $neighbor-\u003eid() . PHP_EOL;\n    echo decbin($neighbor-\u003eid()) . PHP_EOL;\n    echo $neighbor-\u003etoToken() . PHP_EOL;\n    echo PHP_EOL;\n}\n```\n\nPlease note 4th, 5th and 7th neighbour are from the same parent node and are very close on the Hilbert curve. \n\n```\n5117315353002051671\n100011100000100010110110011001101101011010101110000100001010111\n47045b336b570857\n\n5117315353002052011\n100011100000100010110110011001101101011010101110000100110101011\n47045b336b5709ab\n\n5117315353002051669\n100011100000100010110110011001101101011010101110000100001010101\n47045b336b570855\n\n5117315353002051837\n100011100000100010110110011001101101011010101110000100011111101\n47045b336b5708fd\n\n5117315353002051833\n100011100000100010110110011001101101011010101110000100011111001\n47045b336b5708f9\n\n5117315353002051841\n100011100000100010110110011001101101011010101110000100100000001\n47045b336b570901\n\n5117315353002051835\n100011100000100010110110011001101101011010101110000100011111011\n47045b336b5708fb\n\n5117315353002051843\n100011100000100010110110011001101101011010101110000100100000011\n47045b336b570903\n```\n\n## Indexing S2 cells\n\nHere I use the Elasticsearch as a storage for S2 cells. I recommend official docker image and PHP client `elasticsearch/elasticsearch`.\n\n```bin\ndocker run --name elasticsearch-test -p 9200:9200 -e \"http.host=0.0.0.0\" -e \"transport.host=127.0.0.1\" docker.elastic.co/elasticsearch/elasticsearch:5.5.2\n```\n\nThe code you can find in `utils/indexer.php`.\n\nOur goal is to make the cell ID findable by any ID of its parents in the cell hierarchy (in quadtree).\n\nThen we can use inverted index to store all IDs as a way for very effective querying it later.\n\nYou can define your index mapping for s2 cells field like following.\n\n```\n's2cells' =\u003e [\n    'type' =\u003e 'text',\n    'analyzer' =\u003e 'whitespace'\n]\n```\n\nAnd we want to index all the cell IDs from the hierarchy. The method `isFace()` returns true for the root node of the quadtree.\n\n```php\n$s2 = S2CellId::fromLatLng(S2LatLng::fromDegrees($data[1], $data[2]));\n$s2cell = $s2-\u003etoToken();\n$s2cells = [$s2cell];\nwhile (!$s2-\u003eisFace()) {\n    $s2 = $s2-\u003eparent();\n    $s2cells[] = $s2-\u003etoToken();\n}\n```\n\nCheck the indexer code for more details.\n\n\n## Running web example\n\nAfter indexing a data run PHP webserver and check [localhost:8080](http://localhost:8080).\nYou should also provide your Key for accessing Google Maps Javascript API in `web/index.html`. \n\n```bash\nphp -S localhost:8080 -t web/\n```\n\n### Region covering\n\nOf the main goal of S2 library is to provide efficient way to approximate even very complicated polygons. \n\nCurrently this project only supports covering the rectangle area. It is because there is a need for some fixes in library to fully support polygon covering.\n\nCovering a region is as simple as using `S2RegionCoverer` class. It accepts any implementation of `S2Region` interface. \n\n```php\n$region = new S2LatLngRect(S2LatLng::fromDegrees($ne[0], $sw[1]), S2LatLng::fromDegrees($sw[0], $ne[1]));\n```\n```php\n$covering = [];\n$regionCoverer = new S2RegionCoverer();\n$regionCoverer-\u003esetMaxCells($requestBody['maxCells']);\n$regionCoverer-\u003esetMinLevel($requestBody['minLevel']);\n$regionCoverer-\u003esetMaxLevel($requestBody['maxLevel']);\n$regionCoverer-\u003egetCovering($region, $covering);\n```\n\nAs a result we have a list of `S2CellId` objects in `$covering`. They respect the parameters provided to region coverer: min cell level, max cell level, max number of cells.\n\nSee `src/RegoinCovererAction.php` for more details.\n\n### Querying \n\nAnd at the end one of the coolest search technology combinations out there - the ability to combine geo and search. \n\nQuerying the Elasticsearch in our configuration for this combination is very easy.\n\n```php\n$must = [];\nif (!empty($title)) {\n    $must[] = [ 'match' =\u003e [ 'title' =\u003e $title ] ];\n}\nif (!empty($s2cells)) {\n    $must[] = [ 'match' =\u003e [ 's2cells' =\u003e ['query' =\u003e $s2cells, 'operator' =\u003e 'OR'] ] ];\n}\n        \n$params = [\n    'index' =\u003e 'example',\n    'type' =\u003e 'my_type',\n    'body' =\u003e [\n        'size' =\u003e 1000,\n        'query' =\u003e [\n            'bool' =\u003e [\n                'must' =\u003e $must\n            ]\n        ]\n    ]\n];\n```\n\nSee `src/SearchAction.php` for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fto-masz%2Fs2examples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fto-masz%2Fs2examples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fto-masz%2Fs2examples/lists"}