{"id":19795619,"url":"https://github.com/dkogan/inaccessibility","last_synced_at":"2025-05-01T03:30:30.236Z","repository":{"id":31623776,"uuid":"35188854","full_name":"dkogan/inaccessibility","owner":"dkogan","description":null,"archived":false,"fork":false,"pushed_at":"2017-09-24T03:09:12.000Z","size":209,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-06T08:08:03.677Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Perl","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/dkogan.png","metadata":{"files":{"readme":"README.org","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}},"created_at":"2015-05-06T23:50:50.000Z","updated_at":"2021-02-03T09:21:44.000Z","dependencies_parsed_at":"2022-09-09T23:21:21.143Z","dependency_job_id":null,"html_url":"https://github.com/dkogan/inaccessibility","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Finaccessibility","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Finaccessibility/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Finaccessibility/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Finaccessibility/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dkogan","download_url":"https://codeload.github.com/dkogan/inaccessibility/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251817809,"owners_count":21648811,"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":[],"created_at":"2024-11-12T07:16:52.850Z","updated_at":"2025-05-01T03:30:29.964Z","avatar_url":"https://github.com/dkogan.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"* Overview\n\nSo I was out hiking with a friend, and a question came up about where the least\naccessible point of the San Gabriel Mountains was, with \"accessible\" defined as\nhaving a road or trail nearby. This repository answers that question.\n\nThis is called the [[http://en.wikipedia.org/wiki/Pole_of_inaccessibility][Pole of Inaccessibility]]: a point that is as far away as\npossible from a given set of objects. Locations of such poles are known for the\nmost landlocked spot on earth or most far away from land. Here we limit\nourselves to the San Gabriel Mountains, and try to stay away from roads and\ntrails.\n\n* Approach\n\n** Input data processing\n\n[[http://www.openstreetmap.org][OpenStreetMap]] has open data I can use to map out all the roads and trails. This\nis the input dataset.\n\nFor 2D geometry, the best approach to compute the Pole of Inaccessibility\nappears to be to construct a [[http://en.wikipedia.org/wiki/Voronoi_diagram][Voronoi diagram]] of the geometry we're trying to\nstay away from, and to find the Voronoi vertex corresponding to the\nfurthest-away point.\n\nOur world is not 2D. Instead, it has varying elevation sitting on top of an\nellipsoid. The grand purpose here is to compute a location that hardy people can\nvisit and to tell everybody they did it, so extreme accuracy is not required.\nThus I claim that assuming the world is locally-flat and using the\nVoronoi-diagram-based method is sufficient. So I construct a plane that best\ndescribes my query area and project all my input points to this plane. I use a\nplane that is tangent to the Earth's surface at the center of the query area.\nThis clearly wouldn't work if trying to find the pole of inaccessibility of\nsomething as large as an ocean, for instance, but it works here.\n\nTo compute the tangent plane, I assume the Earth is spherical. As I move along\nthe tangent plane away from the point of tangency, the elevation error grows:\n\n E = sqrt(R_earth * R_earth + d * d) - R_earth\n\nThe San Gabriels are about 80km across, and the tangent plane sits in the\nmiddle, so at worst d = 40km and the error is about 125m. That's plenty good\nenough.\n\nI ignore the ellipsoid shape of the Earth outright. I ignore the topography as\nwell, since including it in my distance metrics would require a fancier\nalgorithm than making a Voronoi diagram, and it would make the notion of\n\"inaccessibility\" more ambiguous.\n\n** Pole of Inaccessibility computation\n\nI want to use the most basic Voronoi algorithm, so I represent my input as a set\nof points only; no line segments. To get reasonable accuracy, I make sure to\nsample each road at least every 100m.\n\nNow that I have my set of dense-enough points in 2D, I construct the Voronoi\ndiagram. Without constraints the furthest-away point would be infinitely far off\nto one side, so generally people constrain the solution to lie within the convex\nhull of the input points. This means that the Pole of Inaccessibility lies\neither on a Voronoi vertex or at an intersection of a Voronoi edge and the\nconvex hull of the input. In my case there are generally more roads at the edges\nof my query area that in the interior (less stuff in the mountains than in the\nflats), so I simply assume that the Pole of Inaccessibility is not on the convex\nhull. This simplifies my implementation since I simply ignore all the Voronoi\nvertices that are outside of the query region.\n\nSo I need to look at every Voronoi vertex, check the distance between it and an\nadjacent input point, and return the vertex with the largest such distance.\n\n* Implementation\n\nEach step in the process lives in its own program. This simplifies\nimplementation and makes it easy to work on each piece separately.\n\n** Data import\n\nFirst we query OSM. This is done with the =query.sh= script. It takes in corners\nof the query area, constructs the query, sends it off to the server, and stores\nthe result. =query.sh= takes 4 arguments; lat0, lon0, lat1, lon1, and stores its\noutput in a file called =query_$lat0_$lon0_$lat1_$lon1.json=. The query uses the\n[[http://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL][OSM Overpass query language]]. By default I simply look at all the roads, trails\n(everything with a =highway= tag):\n\n#+BEGIN_EXAMPLE\n[out:json];\n\nway [\"highway\"] ($lat0,$lon0,$lat1,$lon1);\n\n(._;\u003e;);\n\nout;\n#+END_EXAMPLE\n\nIf I want to only consider roads in my computation (allow trails), then I can\nexclude trails from the query:\n\n#+BEGIN_EXAMPLE\n[out:json];\n\nway [\"highway\"] [\"highway\" != \"footway\" ] [\"highway\" != \"path\" ] ($lat0,$lon0,$lat1,$lon1);\n\n(._;\u003e;);\n\nout;\n#+END_EXAMPLE\n\nSample invocation:\n\n#+BEGIN_EXAMPLE\n$ ./query.sh 34.1390884 -118.4944153 34.5020298 -117.5852966\n\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100 28.3M    0 28.3M    0   138  72803      0 --:--:--  0:06:48 --:--:--  138k\n\n\n$ ls -lh query*\n\n-rw-r--r-- 1 dkogan dkogan 29M May  6 04:12 query_34.1390884_-118.4944153_34.5020298_-117.5852966.json\n#+END_EXAMPLE\n\n** Data massaging\n\nNext, I take the lat/lon pairs, map them to the tangent plane and make sure the\ndata is sufficiently dense. This is done by the =massage_input.pl= script. It\ntakes in the =query_....json= file we just obtained, and generates a\n=points_$lat0_$lon0_$lat1_$lon1.dat= file that is a list of (x,y) tuples in my\nplane. There's a small header of 4 values, representing the bounds of my data so\nthat I can reject outlying vertices, as described earlier.\n\nSample invocation:\n\n#+BEGIN_EXAMPLE\n$ ./massage_input.pl query_34.1390884_-118.4944153_34.5020298_-117.5852966.json\n\n\n$ ls -lh points*\n\n-rw-r--r-- 1 dkogan dkogan 4.3M May  6 04:20 points_34.1390884_-118.4944153_34.5020298_-117.5852966.dat\n#+END_EXAMPLE\n\n** Pole of Inaccessibility computation\n\nNow we can compute the Voronoi diagram. I use [[http://www.boost.org/doc/libs/1_57_0/libs/polygon/doc/index.htm][boost::polygon]] to do this. I had\nconcerns that this step would be prohibitively slow, but the algorithm and this\nimplementation are quick-enough such that this \"just works\".\n\nThe =points_....dat= file is inputs on standard input. Note that this is\ndifferent from the other tools that read a file on the commandline instead.\n\nFor each Voronoi vertex I get an arbitrary neighboring edge, and an arbitrary\nneighboring cell. The distance between the vertex and the cell center is\nidentical for any such edge, cell by definition of a Voronoi vertex. I keep\ntrack of the cell with the largest distance between the vertex and the cell\ncenter, and I report the vertex with the largest such distance as my Pole of\nInaccessibility.\n\nSample invocation:\n\n#+BEGIN_EXAMPLE\n$ ./voronoi \u003c points_34.1390884_-118.4944153_34.5020298_-117.5852966.dat \n\nfurthest point center, surrounding points:\n25541 -78\n25308 4259\n21223 -543\n26931 -4192\ndistance: 4342.873206\n#+END_EXAMPLE\n\nBam! So the Pole of Inaccessibility is about 4.3 km from the nearest trail/road.\nThe coordinates here are in my 2D tangent plane, which isn't super useful. Now I\nconvert them to lat/lon and I'm done.\n\n** Unmapping the planar coordinates\n\nI do this with the massaging script as before simply by passing the coords in on\nthe commandline:\n\n#+BEGIN_EXAMPLE\n$ ./massage_input.pl query_34.1390884_-118.4944153_34.5020298_-117.5852966.json 25541 -78 25308 4259 21223 -543 26931 -4192\n\n34.3206972918426,-117.761740671673\n34.3597096140465,-117.764149633831\n34.3165155855012,-117.808770212857\n34.2837076589351,-117.746734473897\n#+END_EXAMPLE\n\nOK. Done.\n\n* Results\n\nI did this twice: once avoiding all roads, trails and again avoiding roads only.\nBoth Poles of Inaccessibility are above the East Fork of the San Gabriel River,\nby Ross Mountain:\n\n| pole         | lat,lon               | distance to nearest (m) |\n|--------------+-----------------------+-------------------------|\n| roads,trails | 34.3206973,-117.76174 |                    4343 |\n| roads only   | 34.3107784,-117.74736 |                    5677 |\n\nThis is shown nicely on the map:\n\nhttp://caltopo.com/m/4N7A\n\n[[file:poles.png]]\n\nLooks like the bounding spots for the roads,trails point are the road up to\nSouth Mt Hawkins, the PCT on top of Mt. Baden-Powell and the trail at the Bridge\nto Nowhere.\n\nThe bounding spots for the roads-only point is the same road up to South Mt\nHawkins, the Cabin Flat Campground and Shoemaker Canyon Road.\n* License\n\nAll code Copyright 2015 Dima Kogan, released under the terms of the Lesser GNU\nPublic License (any version)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdkogan%2Finaccessibility","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdkogan%2Finaccessibility","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdkogan%2Finaccessibility/lists"}