{"id":343,"date":"2013-04-16T23:26:51","date_gmt":"2013-04-17T05:26:51","guid":{"rendered":"http:\/\/www.crccheck.com\/blog\/?p=343"},"modified":"2015-10-11T22:23:20","modified_gmt":"2015-10-12T04:23:20","slug":"we-dont-need-no-stinkin-geo","status":"publish","type":"post","link":"https:\/\/www.crccheck.com\/blog\/we-dont-need-no-stinkin-geo\/","title":{"rendered":"Dissecting Elevators Part 6: GeoDjango? We don&#8217;t need no stinkin&#8217; GeoDjango"},"content":{"rendered":"<p>Being able to locate the nearest elevator was a <em>must have<\/em> feature for me. Being able to deploy using the free tier on Heroku was a <em>nice to have<\/em> feature. Luckily, I managed to find a way to do both: do the calculations client side in JavaScript.<\/p>\n<p>Lucky for me, I found a post, <a href=\"http:\/\/www.movable-type.co.uk\/scripts\/latlong.html\">Calculate distance, bearing and more between Latitude\/Longitude points<\/a>, with some JavaScript algorithms on how to get distances between two lat\/long points. There&#8217;s no such thing as perfect distance algorithm, but if you start making assumptions about the shape of the Earth, you can get a pretty good guess. I implemented four different ways to calculating distance; each a tradeoff between accuracy and speed. The four ways being: the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Haversine_formula\">Haversine formula<\/a>, the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Spherical_law_of_cosines\">spherical law of cosines<\/a>, the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Equirectangular_projection\">equirectangular projection (Pythagorean)<\/a>, and <a href=\"http:\/\/en.wikipedia.org\/wiki\/Taxicab_geometry\">rectilinear (Taxicab) distance<\/a>. For my own reference, I&#8217;m putting a copy of the <a href=\"https:\/\/github.com\/texastribune\/tx_elevators\/blob\/2013-april-fools\/tx_elevators\/static\/tx_elevators\/js\/tx_elevators.js#L195-L237\">JavaScript algorithms<\/a> here:<\/p>\n<pre><code>\/\/ distance approximators\r\n\/** Converts numeric degrees to radians *\/\r\nif (!Number.prototype.toRad){\r\n  Number.prototype.toRad = function(){\r\n    return this * Math.PI \/ 180;\r\n  };\r\n}\r\n\r\nvar distance = {\r\n  R: 6371,  \/\/ kilometers\r\n  haversine: function(lat1, lng1, lat2, lng2){\r\n    var dLat = (lat2-lat1).toRad();\r\n    var dLon = (lng2-lng1).toRad();\r\n    lat1 = lat1.toRad();\r\n    lat2 = lat2.toRad();\r\n\r\n    var a = Math.sin(dLat\/2) * Math.sin(dLat\/2) +\r\n      Math.sin(dLon\/2) * Math.sin(dLon\/2) * Math.cos(lat1) * Math.cos(lat2);\r\n    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\r\n    return distance.R * c;\r\n  },\r\n  spherical: function(lat1, lng1, lat2, lng2){\r\n    lat1 = lat1.toRad();\r\n    lat2 = lat2.toRad();\r\n    lng1 = lng1.toRad();\r\n    lng2 = lng2.toRad();\r\n    return Math.acos(Math.sin(lat1)*Math.sin(lat2) +\r\n      Math.cos(lat1) * Math.cos(lat2) *\r\n      Math.cos(lng2 - lng1)) * distance.R;\r\n  },\r\n  pythagorean: function(lat1, lng1, lat2, lng2){\r\n    lat1 = lat1.toRad();\r\n    lng1 = lng1.toRad();\r\n    lat2 = lat2.toRad();\r\n    lng2 = lng2.toRad();\r\n    var x = (lng2 - lng1) * Math.cos((lat1 + lat2) \/ 2),\r\n        y = lat2 - lat1;\r\n    return Math.sqrt(x * x + y * y) * distance.R;\r\n  },\r\n  taxicab: function(lat1, lng1, lat2, lng2){\r\n    lat1 = lat1.toRad();\r\n    lng1 = lng1.toRad();\r\n    lat2 = lat2.toRad();\r\n    lng2 = lng2.toRad();\r\n    return (Math.abs(lat1 - lat2) + Math.abs(lng1 - lng2)) * distance.R;\r\n  }\r\n};\r\n<\/code><\/pre>\n<p>For more information about the first three distance metrics, you should check out the <a href=\"http:\/\/www.movable-type.co.uk\/scripts\/latlong.html\">original post<\/a>. You can see that the computational complexity for each algorithm decreases dramatically, from using trigonometry and a square root, to pure trig, to a simple trig and a square root, to basic arithmetic. <a href=\"https:\/\/github.com\/texastribune\/tx_elevators\/blob\/2013-april-fools\/tx_elevators\/static\/tx_elevators\/js\/tx_elevators.js#L240-L252\">The code<\/a> for getting the ten closest buildings turned out pretty simple. Here, <code>_data<\/code> is a global array of all the buildings:<\/p>\n<pre><code>\/\/ Get the closest `Building`s to `lat` and `lng`.\r\n\/\/\r\n\/\/ Modifies the global `_data` by storing the distance and also sorts it.\r\nvar closestBuildings = function(lat, lng){\r\n  var metric = distance.spherical, x;\r\n  for (var i = 0; i &lt; _data.length; i++){\r\n    x = _data[i];\r\n    x.distance = metric(lat, lng, x.latitude, x.longitude);\r\n  }\r\n  \/\/ go ahead and sort in place.\r\n  _data.sort(function(a, b){ return a.distance - b.distance; });\r\n  return _data.slice(0, 10);\r\n};\r\n<\/code><\/pre>\n<h2>Evaluating the different distance metrics<\/h2>\n<p>Just to see what the difference was between the four different distance metrics, I compared the result of the same search. I picked an arbitrary point where there weren&#8217;t too many elevators so it would have to go out a long ways before getting 10 elevators. I like BBQ, so I picked <a href=\"http:\/\/elevators.texastribune.org\/building\/36049-best-western-lockhart-hotel-suites\/\" target=\"_blank\">a place<\/a> in Lockhart, TX. To my surprise, not only did the Haversine, spherical law of cosines, and equirectangular projection give the same results in the same order, but they also gave the same distances to 4 significant digits (your results on the <a href=\"http:\/\/elevators.texastribune.org\/building\/36049-best-western-lockhart-hotel-suites\/\" target=\"_blank\">live site<\/a> may look different from mine because of differences between the data on my local machine and the live site):<\/p>\n<figure id=\"attachment_353\" aria-describedby=\"caption-attachment-353\" style=\"width: 300px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.crccheck.com\/blog\/we-dont-need-no-stinkin-geo\/closest-elevator-trig\/\" rel=\"attachment wp-att-353\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-353\" alt=\"Closest Elevators to Lockhart, TX\" src=\"http:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator-trig-300x140.png\" width=\"300\" height=\"140\" srcset=\"https:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator-trig-300x140.png 300w, https:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator-trig.png 724w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-353\" class=\"wp-caption-text\">Closest elevators to Lockhart<\/figcaption><\/figure>\n<p>The results of the search using the taxicab metric were very not bad either. You can see that the taxicab metric punishes going diagonally:<\/p>\n<figure id=\"attachment_354\" aria-describedby=\"caption-attachment-354\" style=\"width: 300px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.crccheck.com\/blog\/we-dont-need-no-stinkin-geo\/closest-elevator-taxicab\/\" rel=\"attachment wp-att-354\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-354\" alt=\"Closest elevators to Lockhart using the taxicab metric\" src=\"http:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator-Taxicab-300x140.png\" width=\"300\" height=\"140\" srcset=\"https:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator-Taxicab-300x140.png 300w, https:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator-Taxicab.png 724w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-354\" class=\"wp-caption-text\">Closest elevators to Lockhart using the taxicab metric<\/figcaption><\/figure>\n<p>They were even better (obviously) over shorter distances, as you can see in this comparison for downtown Austin:<\/p>\n<figure id=\"attachment_359\" aria-describedby=\"caption-attachment-359\" style=\"width: 300px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.crccheck.com\/blog\/we-dont-need-no-stinkin-geo\/closest-elevator2-trig\/\" rel=\"attachment wp-att-359\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-359\" alt=\"Closest elevators, downtown Austin\" src=\"http:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator2-trig-300x91.png\" width=\"300\" height=\"91\" srcset=\"https:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator2-trig-300x91.png 300w, https:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator2-trig.png 724w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-359\" class=\"wp-caption-text\">Closest elevators, downtown Austin<\/figcaption><\/figure>\n<figure id=\"attachment_357\" aria-describedby=\"caption-attachment-357\" style=\"width: 300px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.crccheck.com\/blog\/we-dont-need-no-stinkin-geo\/closest-elevator2-taxicab\/\" rel=\"attachment wp-att-357\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-357\" alt=\"Closest elevators, downtown Austin, using the taxicab metric\" src=\"http:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator2-Taxicab-300x91.png\" width=\"300\" height=\"91\" srcset=\"https:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator2-Taxicab-300x91.png 300w, https:\/\/www.crccheck.com\/blog\/wp-content\/uploads\/2013\/04\/Closest-Elevator2-Taxicab.png 724w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-357\" class=\"wp-caption-text\">Closest elevators, downtown Austin, using the taxicab metric<\/figcaption><\/figure>\n<p>You can see I got the same results in a slightly different order.<\/p>\n<h2>Trickery!<\/h2>\n<p>If you&#8217;re wondering how I got the map pins to go from A to J and match the labels on the legend, wonder no more. The answer is: <strong>I cheated<\/strong>. I created the map pins using the <a href=\"https:\/\/developers.google.com\/chart\/infographics\/docs\/dynamic_icons#plain_pin\" target=\"_blank\">old Google charts API<\/a> in <a href=\"https:\/\/github.com\/texastribune\/tx_elevators\/blob\/2013-april-fools\/tx_elevators\/static\/tx_elevators\/js\/tx_elevators.js#L141-L143\" target=\"_blank\">my mapping code<\/a>:<\/p>\n<pre><code>\"http:\/\/chart.apis.google.com\/chart?chst=d_map_pin_letter&amp;chld=\" +\r\n        String.fromCharCode(65 + i) + \"|\" + pinColor,\r\n<\/code><\/pre>\n<p>If you vary <code>i<\/code> from 0 to 9, you get the letters A to J. The legend is an ordered list with the <a href=\"https:\/\/github.com\/texastribune\/tx_elevators\/blob\/2013-april-fools\/tx_elevators\/static\/tx_elevators\/css\/tx_elevators.css#L16\">css<\/a>: <code>list-style: upper-alpha<\/code>, which handles going from A to J on the map legend. Since both are built from the same source in the same order, they match up.<\/p>\n<h2>Conclusion<\/h2>\n<p>And there you have it. Basic closest point searches done client side. The nearly fully geocoded dataset with 23,205 buildings takes a megabyte to transfer, but it could be optimized. At launch, I only had a fraction of the buildings geocoded, so it was less than 150 kilobytes then. I may change the default distance metric from spherical to Pythagorean to get some more speed. It would be an interesting exercise to convert to GeoDjango and compare the distance results again.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Being able to locate the nearest elevator was a must have feature for me. Being able to deploy using the free tier on Heroku was a nice to have feature. Luckily, I managed to find a way to do both: do the calculations client side in JavaScript. Lucky for me, I found a post, Calculate&hellip;<\/p>\n <a href=\"https:\/\/www.crccheck.com\/blog\/we-dont-need-no-stinkin-geo\/\" title=\"Dissecting Elevators Part 6: GeoDjango? We don&#8217;t need no stinkin&#8217; GeoDjango\" class=\"entry-more-link\"><span>Read More<\/span> <span class=\"screen-reader-text\">Dissecting Elevators Part 6: GeoDjango? We don&#8217;t need no stinkin&#8217; GeoDjango<\/span><\/a>","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"Layout":"","footnotes":""},"categories":[21,4],"tags":[12,57],"class_list":["entry","author-showmewhatyougot","post-343","post","type-post","status-publish","format-standard","category-case-study","category-technical","tag-js","tag-maps"],"_links":{"self":[{"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/posts\/343","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/comments?post=343"}],"version-history":[{"count":19,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/posts\/343\/revisions"}],"predecessor-version":[{"id":746,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/posts\/343\/revisions\/746"}],"wp:attachment":[{"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/media?parent=343"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/categories?post=343"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/tags?post=343"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}