{"id":19795621,"url":"https://github.com/dkogan/culdesacs","last_synced_at":"2026-06-13T15:32:49.863Z","repository":{"id":36332184,"uuid":"40636885","full_name":"dkogan/culdesacs","owner":"dkogan","description":null,"archived":false,"fork":false,"pushed_at":"2015-08-16T08:10:45.000Z","size":140,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-02-11T02:25:37.892Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/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-08-13T03:25:19.000Z","updated_at":"2015-08-13T03:25:30.000Z","dependencies_parsed_at":"2022-09-09T23:40:22.606Z","dependency_job_id":null,"html_url":"https://github.com/dkogan/culdesacs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dkogan/culdesacs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Fculdesacs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Fculdesacs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Fculdesacs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Fculdesacs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dkogan","download_url":"https://codeload.github.com/dkogan/culdesacs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkogan%2Fculdesacs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34290345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2024-11-12T07:16:53.178Z","updated_at":"2026-06-13T15:32:49.846Z","avatar_url":"https://github.com/dkogan.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+OPTIONS: tex:dvipng\n\n* Reference\n\nThis is all described better (with pictures!) in a blog post:\n\nhttp://notes.secretsauce.net/notes/2015/08/16_least-convenient-location-in-los-angeles-from-koreatown.html\n\n* Overview\n\nTalking to a friend, a question came up about finding the point in LA's road\nnetwork that's most inconvenient to get to, with /inconvenient/ being a vague\nnotion describing a closed residential neighborhood full of dead ends; the\nfurthest of these dead ends would be most inconvenient indeed. This repository\nattempts to answer that question.\n\nI want /inconvenient/ to mean\n\n#+BEGIN_QUOTE\nFurthest to reach via the road network, but nearest as-the-crow-flies.\n#+END_QUOTE\n\nNote that this type of metric is not a universal one, but is relative to a\nparticular starting point. This makes sense, however: a location that's\ninconvenient from one location could be very convenient from another.\n\nThis metric could be expressed in many ways. I keep it simple, and compute a\nrelative inefficiency coefficient:\n\n=(d_road - d_direct) / d_direct=\n\nThus the goal is to find a location within a given radius of the starting point\nthat maximizes this relative inefficiency.\n\n* Approach\n\nI use [[http://www.openstreetmap.org][OpenStreetMap]] for the road data. This is all aimed at bicycling, so I'm\nlooking at all roads except freeways and ones marked private. I /am/ looking at\nfootpaths, trails, etc.\n\nOnce I have the road network, I run [[https://en.wikipedia.org/wiki/Dijkstra's_algorithm][Dijkstra's Algorithm]] to compute the shortest\npath from my starting point to every other point on the map. Then I can easily\ncompute the inefficiency for each such point, and pick the point with the\nhighest inefficiency. I use OSM nodes as the \"points\". It is possible that the\nlocation I'm looking for is inbetween a pair of nodes, but the nodes will really\nbe close enough. Also, the \"distance\" between adjacent nodes can take into\naccount terrain type, elevation, road type and so on. I ignore all that, and\nsimply look at the 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 I query OSM. This is done with the =query.pl= script. It takes in the\ncenter point and the query radius. The query uses the [[http://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL][OSM Overpass query\nlanguage]]. I use this simple query, filling in the center point and radius:\n\n#+BEGIN_EXAMPLE\n[out:json];\n\nway\n [\"highway\"]\n [\"highway\" !~ \"motorway|motorway_link\" ]\n [\"access\" !~ \"private\" ]\n [\"access\" !~ \"no\" ]\n (around:$rad,$lat,$lon);\n\n(._;\u003e;);\n\nout;\n#+END_EXAMPLE\n\nSample invocation:\n\n#+BEGIN_EXAMPLE\n$ ./query.pl --center 34.0690448,-118.292924 --rad 20miles\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100  .....\n\n$ ls -lhrt *(om[1])\n-rw-r--r-- 1 dima dima 81M Aug 14 00:44 query_34.0690448_-118.292924_20miles.json\n\n#+END_EXAMPLE\n\n** Data massaging\n\nNow I need to take the OSM query results, and manipulate them into a form\nreadable by the Dijkstra's algorithm solver. This is done by the\n=massage_input.pl= script. This script does nothing interesting, but it doesn it\ninefficiently, so it's CPU and RAM-hungry and takes a few minutes. Sample\ninvocation:\n\n#+BEGIN_EXAMPLE\n$ ./massage_input.pl query_34.0690448_-118.292924_20miles.json \u003e query_34.0690448_-118.292924_20miles.net\n#+END_EXAMPLE\n\n*** Neighbor list representation\n\nAn implementation choice here was how to represent the neighbor list for a node.\nI want the main computation (next section) to be able to query this very\nquickly, and I don't want the list to take much space, and I don't want to\nfragment my memory with many small allocations. Thus I have a single contiguous\narray of integers =neighbor_pool=. Each node has a single integer index into\nthis pool. At this index the =neighbor_pool= contains a list of node indices\nthat are neighbors of the node in question. A special node index of -1 signifies\nthe end of the neighbor list for that node.\n\n** Inefficiency coefficient computation\n\nI now feed the massaged data to Dijkstra's algorithm implemented in =compute.c=.\nI need a priority queue where elements can be inserted, removed and updated.\nApparently most heap implementations don't have an 'update' mechanism, so it\ntook a little while to find a working one. I ended up using [[https://en.wikipedia.org/wiki/B-heap][phk's b-heap]]\nimplementation from the [[https://www.varnish-cache.org/trac/browser/lib/libvarnish/binary_heap.c][varnish source tree]]. It stores arbitrary pointers\n(64-bit on my box); 32-bit indices into a pool would be more efficient, but this\nis fast enough.\n\nSample invocation:\n\n#+BEGIN_EXAMPLE\n$ ./compute \u003c query_34.0690448_-118.292924_20miles.net \u003e query_34.0690448_-118.292924_20miles.out\n\n$ head -n 2 query_34.0690448_-118.292924_20miles.out\n34.069046 -118.292923 0.000000 0.000000\n34.070034 -118.292931 109.863564 109.863564\n#+END_EXAMPLE\n\nThe output is all nodes, sorted by the road distance to the node. The columns\nare lat,lon,d_road,d_direct.\n\n*** Distance from latitude/longitude pairs\n\nOne implementation note here is how to compute the distance between two\nlatitude/longitude pairs. The most direct way is to convert each\nlatitude/longitude pair into a unit vector, compute the dot product, take the\narccos and multiply by the radius of the Earth. This requires 9 trigonometric\noperations and relies on the arccos of a number close to 1, which is inaccurate.\nOne could instead compute the arcsin of the magnitude of the cross-product, but\nthis requires even more computation. I want something simpler:\n\n#+BEGIN_EXAMPLE\ndist = Rearth * angle\n\ncos(angle) = dot(v0,v1) = dot( (cos(lon0)*cos(lat0), sin(lon0)*cos(lat0), sin(lat0)),\n                               (cos(lon1)*cos(lat1), sin(lon1)*cos(lat1), sin(lat1)) ) =\n\n           = cos(lat0)*cos(lat1) * ( cos(lon0)*cos(lon1) + sin(lon0)*sin(lon1) ) +\n             sin(lat0)*sin(lat1) =\n\n           = cos(lat0)*cos(lat1) * cos(diff_lon) + sin(lat0)*sin(lat1)\n\ncos(diff_lon) ~ 1 - diff_lon^2/2 so\n\ncos(angle) = cos(lat0)*cos(lat1) + sin(lat0)*sin(lat1) - diff_lon^2/2*cos(lat0)*cos(lat1) =\n           = cos(diff_lat) - cos(lat0)*cos(lat1)*diff_lon^2/2 ~\n           ~ 1 - diff_lat^2/2 - diff_lon^2/2*cos(lat0)*cos(lat1)\n\ncos(angle) ~ 1 - angle^2/2, so\n\nangle^2 ~ diff_lat^2 + diff_lon^2*cos(lat0)*cos(lat1)\n\nangle ~ sqrt(diff_lat^2 + diff_lon^2 * cos(lat0)*cos(lat1))\n\n#+END_EXAMPLE\n\nThis is nice and simple. Is it sufficiently accurate? This python script tests\nit:\n\n#+BEGIN_SRC python\nimport numpy as np\nlat0,lon0 = 34.0690448,-118.292924  # 3rd/New Hampshire\nlat1,lon1 = 33.93,-118.4314         # LAX\n\nlat0,lon0,lat1,lon1 = [x * np.pi/180.0 for x in lat0,lon0,lat1,lon1]\n\nRearth = 6371000\n\nv0 = np.array((np.cos(lat0)*np.cos(lon0), np.cos(lat0)*np.sin(lon0),np.sin(lat0)))\nv1 = np.array((np.cos(lat1)*np.cos(lon1), np.cos(lat1)*np.sin(lon1),np.sin(lat1)))\n\ndist_accurate = np.sqrt( (lat0-lat1)**2 + (lon0-lon1)**2 * np.cos(lat0)*np.cos(lat1) ) * Rearth\ndist_approx   = np.arccos(np.inner(v0,v1)) * Rearth\n\nprint dist_accurate\nprint dist_approx\nprint dist_accurate - dist_approx\n#+END_SRC\n\nBetween Koreatown and LAX there's quite a bit of difference in both latitude and\nlongitude. Both methods say the distance is about 20km, with a disagreement of\n3mm. This is plenty good enough.\n\n* Results\n\nI want to find the least convenient location from the intersection of New\nHampshire and 3rd street in Los Angeles within 20 miles or so.\n\nThe output of =compute= is sorted by road distance from the start. I prepend the\ncoefficient of inconvenience, re-sort the list and take 50 most inconvenient\nlocations by invoking\n\n#+BEGIN_EXAMPLE\n\u003cquery_34.0690448_-118.292924_20miles.out\n   awk '$4 {printf \"%f %f %f %f %f\\n\",($3-$4)/$4,$1,$2,$3,$4}' |\n   sort -n -k1 -r | head -n 50\n#+END_EXAMPLE\n\nThe output is this:\n\n| Inconvenience |  Latitude |   Longitude | Road distance (m) | Direct distance (m) |\n|---------------+-----------+-------------+-------------------+---------------------|\n|      1.142052 | 34.068104 | -118.290382 |        549.216980 |          256.397583 |\n|      1.139839 | 34.071629 | -118.288956 |        994.499390 |          464.754242 |\n|      1.139147 | 34.068066 | -118.290436 |        542.721497 |          253.709305 |\n|      1.136799 | 34.068130 | -118.290329 |        554.962891 |          259.716919 |\n|      1.127631 | 34.068031 | -118.290466 |        537.980652 |          252.854279 |\n|      1.120537 | 34.068153 | -118.290253 |        562.437012 |          265.233337 |\n|      1.106771 | 34.067982 | -118.290504 |        531.442017 |          252.254257 |\n|      1.103518 | 34.068169 | -118.290184 |        568.985352 |          270.492218 |\n|      1.083344 | 34.067940 | -118.290527 |        526.321899 |          252.633179 |\n|      1.079027 | 34.068176 | -118.290100 |        576.762024 |          277.419189 |\n|      1.041816 | 34.067883 | -118.290543 |        519.805908 |          254.580200 |\n|      1.034252 | 34.070259 | -118.291237 |        418.454498 |          205.704315 |\n|      1.019096 | 34.071594 | -118.287888 |       1097.392212 |          543.506653 |\n|      0.974731 | 34.068214 | -118.289680 |        617.407532 |          312.654022 |\n|      0.970095 | 34.068176 | -118.289719 |        611.899475 |          310.593842 |\n|      0.917598 | 34.068111 | -118.289383 |        656.267517 |          342.234131 |\n|      0.910048 | 34.068165 | -118.289383 |        650.329041 |          340.477783 |\n|      0.902491 | 34.068214 | -118.289383 |        644.814758 |          338.931915 |\n|      0.770809 | 34.067570 | -118.290543 |        485.023560 |          273.899414 |\n|      0.760711 | 34.068214 | -118.288643 |        712.981384 |          404.939484 |\n|      0.753344 | 34.068214 | -118.288597 |        717.197876 |          409.045654 |\n|      0.750541 | 34.033188 | -118.279716 |       7297.569824 |         4168.751465 |\n|      0.747349 | 34.031826 | -118.279968 |       7526.415039 |         4307.333008 |\n|      0.743357 | 34.067772 | -118.289474 |        606.347107 |          347.804382 |\n|      0.741902 | 34.067787 | -118.289436 |        610.249084 |          350.334900 |\n|      0.740024 | 34.067749 | -118.289505 |        602.555115 |          346.291290 |\n|      0.739944 | 34.031769 | -118.279823 |       7511.619141 |         4317.161621 |\n|      0.738388 | 34.031582 | -118.280746 |       7499.802734 |         4314.228516 |\n|      0.737889 | 34.067795 | -118.289398 |        613.863831 |          353.223755 |\n|      0.737716 | 34.031742 | -118.279800 |       7507.977051 |         4320.601562 |\n|      0.736297 | 34.031372 | -118.280258 |       7550.486816 |         4348.613770 |\n|      0.735083 | 34.068108 | -118.288734 |        693.459473 |          399.669403 |\n|      0.734607 | 34.067730 | -118.289520 |        600.010803 |          345.905945 |\n|      0.732851 | 34.031685 | -118.279747 |       7499.933105 |         4328.088379 |\n|      0.730817 | 34.067795 | -118.289352 |        618.080322 |          357.103241 |\n|      0.730543 | 34.031654 | -118.279732 |       7496.259766 |         4331.739746 |\n|      0.728622 | 34.031628 | -118.279724 |       7493.208496 |         4334.787109 |\n|      0.727123 | 34.067707 | -118.289536 |        597.103455 |          345.721344 |\n|      0.726802 | 34.031601 | -118.279724 |       7490.239258 |         4337.637207 |\n|      0.724309 | 34.031563 | -118.279739 |       7485.770508 |         4341.315430 |\n|      0.723138 | 34.067791 | -118.289307 |        622.318115 |          361.153992 |\n|      0.722826 | 34.031540 | -118.279755 |       7482.862793 |         4343.366211 |\n|      0.722384 | 34.094849 | -118.236145 |      10273.032227 |         5964.425293 |\n|      0.721979 | 34.094719 | -118.235779 |      10309.708008 |         5987.128906 |\n|      0.721011 | 34.094639 | -118.235474 |      10339.187500 |         6007.625977 |\n|      0.720812 | 34.094620 | -118.235405 |      10345.856445 |         6012.193359 |\n|      0.720105 | 34.031498 | -118.279778 |       7477.742188 |         4347.258789 |\n|      0.720078 | 34.094543 | -118.235138 |      10371.867188 |         6029.880859 |\n|      0.719789 | 34.031509 | -118.279755 |       7475.278809 |         4346.624512 |\n|      0.719616 | 34.095020 | -118.236320 |      10248.023438 |         5959.484863 |\n\nThere are 3 clusters of data. All the stuff \u003c 500m away from the start is\nmostly degenerate and uninteresting. Most of the points are in walkways in Shatto\nRecreation Center. They're all so close to the start that any inefficiency is\nexaggerated by the small =d_direct=. I make the rules, so I claim these aren't\nthe least convenient point.\n\nNext we have the points about 4.2km away as the crow flies. These all appear in\nan improperly-mapped group of sidewalks around Saint James park:\nhttp://www.openstreetmap.org/#map=18/34.03173/-118.27892.\n\nHere the sidewalks appear as separate ways that don't connect with the roads\nthey abut. So according to the data, connecting to the network of sidewalks can\nonly happen in one location, making these appear less convenient than they\nactually are. (I think these should be removed entirely, but it looks like the\nOSM committee people think both ways are fine. OK; it'll be fixed eventually in\nsome way).\n\nThe next cluster of data is about 6km away as the crow flies. These are all at\nthe road connecting to the Metrolink maintenance facility at Taylor Yard:\nhttp://www.openstreetmap.org/#map=17/34.09371/-118.23463. This makes sense! This\nlocation is on the other side of the LA river from Koreatown, so getting here\nrequires a lengthy detour to the nearest bikeable bridge. The nearest one\n(Riverside Drive) is 2.5km by road away, but this is in the opposite direction\nfrom Koreatown. The nearest one in the other direction is Fletcher Drive, 3.8km\nby road.\n\nSo the least convenient point from New Hampshire / 3rd is at lat/lon\n34.094849,-118.236145. This location is 10.3km away by road, but only 6.0km as\nthe crow flies, for an inconvenience coefficient of 0.72.\n\n* License\n\nAll code I wrote is Copyright 2015 Dima Kogan, released under the terms of the\nLesser GNU Public License (any version). This includes everything except the\nfiles from Varnish, whose copyright and licensing appear in the specific files.\nThese are binary_heap.c, binary_heap.h, miniobj.h\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdkogan%2Fculdesacs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdkogan%2Fculdesacs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdkogan%2Fculdesacs/lists"}