{"id":20453843,"url":"https://github.com/anyways-open/geodesic-triangles","last_synced_at":"2025-06-28T01:38:20.557Z","repository":{"id":116245522,"uuid":"255929095","full_name":"anyways-open/geodesic-triangles","owner":"anyways-open","description":"A library that divides the earth into triangular cells of roughly the same sizes and converts WGS84 to and from these cells","archived":false,"fork":false,"pushed_at":"2020-04-30T13:35:52.000Z","size":14774,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-13T08:05:37.958Z","etag":null,"topics":["datascience","geo","geodesic","triangles"],"latest_commit_sha":null,"homepage":null,"language":"C#","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/anyways-open.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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-15T13:42:52.000Z","updated_at":"2020-04-30T13:35:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"e5d367e4-9430-485d-9c8d-eac8df565169","html_url":"https://github.com/anyways-open/geodesic-triangles","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/anyways-open/geodesic-triangles","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anyways-open%2Fgeodesic-triangles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anyways-open%2Fgeodesic-triangles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anyways-open%2Fgeodesic-triangles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anyways-open%2Fgeodesic-triangles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anyways-open","download_url":"https://codeload.github.com/anyways-open/geodesic-triangles/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anyways-open%2Fgeodesic-triangles/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262361665,"owners_count":23299085,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["datascience","geo","geodesic","triangles"],"created_at":"2024-11-15T11:14:06.423Z","updated_at":"2025-06-28T01:38:20.527Z","avatar_url":"https://github.com/anyways-open.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# geodesic-triangles\n\n[![Build status](https://build.anyways.eu/app/rest/builds/buildType:(id:anyways_Libraries_GeodesicTriangles)/statusIcon.svg)](https://build.anyways.eu/viewType.html?buildTypeId=anyways_Libraries_GeodesicTriangles\u0026guest=1)  \n\n\n![NuGet status](https://buildstats.info/nuget/Geodesic.Triangles?includePreReleases=true)\n\nGeodesic-Triangles is a small library, which uses fancy math to split the surface of the earth into triangles, where the triangles are roughly the same size.\n\nEvery triangle has an identifier, and for every point the identifier of the containing triangle can be quickly calculated.\nFor every identifier, the centerpoint or outline of the corresponding triangle can be quickly calculated too.\n\nThe input coordinates are WGS84 (which is the common format for web applications such as OpenStreetMap and many others).\n(Note: WGS84 only supports latitudes between -85° and 85°, this library supports up to -90° and 90°).\n\n## Using the library\n\nUsing the library is pretty simple:\n\n```\nimport Anyways.GeodesicTriangles.GeodesicTriangles\n\n....\n\n\n    // Get the identifier of the triangle which contains the given point\n    ulong id = (longitude, latitude).TriangleId();\n    \n    // Convert back to a coordinate by using the center point of the triangle\n    var (longitude0, latitude0) = id.TriangleCenterPoint();\n\n    // Get the outline of the triangle which is represented by this identifier\n    var poly = id.PolygonAround()\n    \n    // Build a histogram in order to know which cells have more coordinates\n    List\u003c(double lon, double lat)\u003e coordinates = ...\n    var histogram = new Dictionary\u003culong, uint\u003e();\n    foreach(var c in coordinates){\n        var id = c.TriangleId(15); // A precision of 15 wil generate triangles which are sized like a moderate village\n        if(!histogram.ContainsKey(id)) histogram[id] = 0;\n        histogram[id]++;\n    }\n\n```\n\n# Example\n\nAll the triangles of all precisions to encode a location in Belgium, Europe:\n\n\n![Brugge encoded](Examples/ExampleTriangles_Brugge.png)\n\n\n## Underlying mathematics\n\n\n_(Note: all coordinates are written as `(lon, lat)`)_\n\nIn orer to determine the ID of the triangle containing the given point, we progressively build an array of numbers, indicating the which part of the world we are working with.\n\n### Octant\n\nTo get started, the surface of the world is split in eight parts, each called an **octant**.\n\nOctant *1* lies between `(0,0)`, `(90,0)` and the north pole `(*,90)`; octant *2* lies between `(90,0)`, `(180,0)` and the north pole, octant *3* is between `(180,0)`,`(270,0)` and the north pole, and the *4*th octant (you guessed it) is between `(270,0)`, `(360,0)` and the north pole.\n\nThe southern hemisphere follows the same pattern, with octant *5* between `(0,0)`, `(90,0)` and the _south pole_, octant *6* between `(90,0)`, `(180,0)` and the south pole, ...\n\nThe octant number (between 1-8 inclusive; not zero-indexed) is the first number we keep track of. The octant coordinates is the first and biggest _triangle_ that the point falls in. Note that this triangle contains a pole as angle, which makes it _look_ like a rectangle on a Mercator projected map:\n\n\n![BruggeOctant](Examples/ExampleTriangles_Brugge0.png)\n\n\n### Quandrants\n\nWhen the octant is determined, this corresponding triangle is cut into four parts: a triangle at the top, a triangle on the left, one on the right and one in the middle. This can be done easily, as the coordinates describing these triangle are either the average of the points of the bigger triangle, or points from this bigger triangle.\n\nEvery triangle is assigned a number. We determine in which triangle the coordinate we want to encode falls; the corresponding number is appended to the array with the ID.\nIf this triangle is still to big for the intended use, it can be split into four parts again, repeating the process just described until the result is satisfactory.\n\n![First quadrant](Examples/Brugge1.png)\n![Second quadrant](Examples/Brugge2.png)\n![Third quadrant](Examples/Brugge3.png)\n\n### Encoding the ID\n\nIn the end, we end up with a list of numbers describing triangles, each one a quarter of the size of the previous, bigger triangle. This list of triangles will have the format of:\n\n`[ number between 1 - 8, number between 0 - 3, number between 0 - 3, number between 0 - 3, ...]`\n\nThis can efficiently be encoded into a ulong or uint. The interested reader can refer directly to the source code to see the details of this encoding.\n\n### Quadrants revisited\n\nEven though the triangles containing the point can be easily determined, it is quite hard to determine if a point lies within the triange, as the lines of a triangle over the world is not straight but a curve.\n\nIn order to circumvent this (and to speed up the calculation), the points are converted into ZOT-space. This is a polar projection, but instead of having a unit circle representing the equator, a unit square is used (with the corners aligning with `0°`,`90°`,`180` and `270°` longitude). Calculating this for a point in the first octant is done as following (although heavily simplified):\n\n1) Convert from WGS84 to polar, where the polar coordinate is `(longitude, distance from the pole)`, or simply `(longitude, 90° - latitude)`\n1) Calculate the distance `d` between point on the equator with the same longitudefrom the pole in ZOT-space. In polar coordinates, a point on the equator is always 90° away. In ZOT-space, these points fall on the line between `(0,-90°)` and `(90°,0). \n2) Rescale the polar coordinate to `(longitude, (90° - latitude) * (d / 90°))`\n\nAt this point, calculating the quadrant from a triangle can be done with Manhattan distance. For more details, see the included papers.\n\n\n\n## Approximate sizes per precision level\n\nThis table gives a rough indication of how big a triangle is on each precision level.\nThe numbers are rounded, but can differ slightly between the triangles.\n\n\nDigits of precision | Size of triangle (km²) \t| Size estimate\n------------------- | -------------------------\t| ------\n1                   | 64 000 000 \t\t\t\t| 1/8 of the earth\n2                   | 22 500 000 \t\t\t\t| a big continent\n3                   | 4 250 000 \t\t\t\t| a small continent\n4                   | 1 400 000 \t\t\t\t| a few big countries\n5                   | 310 000 \t\t\t\t\t| a big country\n6                   | 82 500 \t\t\t\t\t| a small country\n7                   | 20 000\t\t\t\t\t| \n8                   | 5 000 \t\t\t\t\t| a region containing multiple cities or a metropole as Paris or NY\n9                   | 1 250 \t\t\t\t\t| a big city such as brussles\n10                  | \t320 \t\t\t\t\t| a smaller city\n11                  | 80 \t\t\t\t\t\t| a village or multiple city districts\n12                  | 20 \t\t\t\t\t\t| a city district\n13                  | 5 \t\t\t\t\t\t| a city quarter\n14                  | 1.25 \t\t\t\t\t\t| quite few residential blocks\n15                  | 0.30 \t\t\t\t\t\t| a pair of residential blocks\n16                  | 0.08 \t\t\t\t\t\t| half a residential block\n17                  | 20 000m² \t\t\t\t\t| around 10 packed houses\n18                  | 5 000m² \t\t\t\t\t| a few houses\n19                  | 1 200m² \t\t\t\t\t| \n20                  | 300m² \t\t\t\t\t| one house\n21                  | 75m² \t\t\t\t\t\t| a room\n22                  | 20m² \t\t\t\t\t\t| a very very tiny house\n23                  | 5m² \t\t\t\t\t\t| \n24                  | 1.2m² \t\t\t\t\t| A garden table\n25                  | 0.25m² \t\t\t\t\t| \n\n\n# Top level triangles\n\nAn example of the first triangle splits:\n\n![Triangles](Examples/Triangles.png)\n\n## Attribution\n\nImages in this readme are encoded with Geojson.io, the background map is by [OpenStreetMap.org], styled by [Mapbox.com]\nLogo 'Sphere' by Lluisa Iborra from the Noun Project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanyways-open%2Fgeodesic-triangles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanyways-open%2Fgeodesic-triangles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanyways-open%2Fgeodesic-triangles/lists"}