{"id":1875,"date":"2020-03-16T15:09:59","date_gmt":"2020-03-16T06:09:59","guid":{"rendered":"http:\/\/bebensiganteng.com\/?p=1875"},"modified":"2022-06-06T22:12:26","modified_gmt":"2022-06-06T13:12:26","slug":"hourtrip-the-technical-report","status":"publish","type":"post","link":"https:\/\/rahmat-hidayat.com\/?p=1875","title":{"rendered":"HourTrip &#8211; The Technical report"},"content":{"rendered":"\n<p><strong>Update 2020\/06\/03<\/strong> Well, we&#8217;ve finally ended it, 1.5 years of collecting data, taking the photo of every single location, and making the MVP went down the drain.<\/p>\n\n\n\n<p>But worry not, although I&#8217;m getting older, this will not be my last venture, as long I can work 16-18 hours a day someday I will have a business.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Hourtrip is a travel website that I did with a few of my friends although we are still struggling on the business side, I&#8217;m quite proud of the algorithm.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Have you gone on a trip and thought of \u201cI only have 6 hours to spend where should I go?\u201d well with Hourtrip you can!&nbsp;<\/p><\/blockquote>\n\n\n\n<p>The concept is pretty simple, have you gone on a trip and thought of  &#8220;I only have 6 hours to spend where should I go?&#8221; well with Hourtrip you can! and it&#8217;s smart enough to know whether it&#8217;s time for lunch or dinner, which places you should go to, determine what time you should go back, etc.<\/p>\n\n\n\n<p>As an overview, the algorithm is pretty simple. Extract the user&#8217;s preference, use that data to filter the database, and then show the recommended locations.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/rahmat-hidayat.com\/wp-content\/uploads\/2020\/03\/graph.001.jpg\"><img decoding=\"async\" loading=\"lazy\" width=\"1079\" height=\"669\" src=\"https:\/\/rahmat-hidayat.com\/wp-content\/uploads\/2020\/03\/graph.001.jpg\" alt=\"\" class=\"wp-image-1879\"\/><\/a><\/figure>\n\n\n\n<p>To show the recommended locations,  it needs to iteratively calculate the closest location from the original location, the graph below explains roughly how the iteration works.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/rahmat-hidayat.com\/wp-content\/uploads\/2020\/03\/algorithm.jpg\"><img decoding=\"async\" loading=\"lazy\" width=\"1497\" height=\"921\" src=\"https:\/\/rahmat-hidayat.com\/wp-content\/uploads\/2020\/03\/algorithm.jpg\" alt=\"\" class=\"wp-image-1906\"\/><\/a><\/figure>\n\n\n\n<p><strong>Check the weather<\/strong><br>I use <a href=\"https:\/\/openweathermap.org\/\">OpenWeatherMap<\/a> API to get the info, at this point, I only check whether it&#8217;s bad or good, in the future, I&#8217;m planning to check whether the weather is good enough for travelling. <\/p>\n\n\n\n<p>For example, a drizzle might not stop you from going to that restaurant you like.<\/p>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nURL = &quot;http:\/\/api.openweathermap.org\/data\/2.5\/forecast\/?units=metric&amp;amp;APPID=0cdde5a888c7dc5664225b6112dcd07d&quot;\npath = URL + &quot;&amp;amp;lat=&quot; + kwargs['origin']['lat'] + &quot;&amp;amp;lon=&quot; + kwargs['origin']['lng']\nresult = json.loads(urlopen(path).read().decode('utf-8'))\n<\/pre>\n\n\n<p><strong>Check the preferences<\/strong><br>Checking the preferences is simply a hash lookup.<\/p>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nif self.match_tags(self._types, v['tag']):\n  di = DestinationItem(data=v)\n  self._dest_list.append(di)\n<\/pre>\n\n\n<p><strong>Check the walking duration<\/strong> <strong>and Location&#8217;s tiers<\/strong><br>There are two components when checking the walking duration, the time to get from the current location to the next location, and the current location to the original point.<\/p>\n\n\n\n<p>Determining the walking speed is quite tricky and I have to admit is a mixture of real data and hunch, according to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Preferred_walking_speed\">Wikipedia<\/a>, the average walking speed is 1.4 m\/s but for tourists, they might walk longer than that, due to the traffic, pausing for the sights, having unruly kids, etc. so I add another 1 second to anticipate that.<\/p>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nWALKING_SPEED   = 2.4  #  +1 to anticipate sight seeing, traffic, etc.\n\ndef get_distance(self, node_origin, node_dest):\n\n    origin  = self.get_latlng(node_origin)\n    dest    = self.get_latlng(node_dest)\n    dist    = round(geodesic(origin, dest).kilometers * 1000)\n\n    return {\n        'dist_from_source': dist, # meters\n        'walking_time': round(dist\/WALKING_SPEED)\n    }\n<\/pre>\n\n\n<p>In the beginning, to calculate the nearest distance I use Google Map API, but boy was that expensive! Nearly bankrupt all of us so I use <a href=\"https:\/\/geopy.readthedocs.io\/en\/stable\/\">GeoPy library<\/a> instead.<\/p>\n\n\n\n<p>Once the walking duration is understood it&#8217;s easy to get to the nearest location, now this has to be tied with the location&#8217;s tier (score value). <\/p>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ndef get_shortest_dest(self, node_origin, tier, p_dist=None):\n\n  dist    = None\n  index   = -1\n  arrival = None\n\n  for i, v in enumerate(self._dest_list):\n\n    if self.match_tags([v.tier], tier):\n\n      d = self.get_distance(node_origin,v)\n      # estimated arrival times\n      # TODO: there's a variable for arrival_time, change tothat\n      arrival = self._timestamp + datetime.timedelta(seconds=d['walking_time'])\n\n      if v.is_open(arrival):\n\n        if dist is None:\n          dist = d['dist_from_source']\n\n        if p_dist is None:\n\n          if d['dist_from_source'] &lt;= dist:\n            dist    = d['dist_from_source']\n            index   = i\n        else:\n          # if between dest_a and dest_b subtier exist then calculate\n          if d['dist_from_source'] &lt;= dist and d['dist_from_source'] &lt;= p_dist:\n            dist    = d['dist_from_source']\n            index   = i\n\n  return { 'index': index, 'dist': dist, 'arrival': arrival}\n\ndef get_next_dest(self, node_origin):\n\n    result  = self.get_shortest_dest(node_origin, ['1','2'])\n    i       = result['index']\n    ii      = -1\n\n    # 2. if tier [1,2] undefined, search [3,4]\n    if i &lt; 0:\n        result  = self.get_shortest_dest(node_origin, ['3','4'])\n        ii      = result['index']\n\n    else:\n        # if tier [1,2] exist, search for the closest [3,4] tier\n        # since the last tier has already been removed, then automatically the next tier will be refered\n        result  = self.get_shortest_dest(node_origin, ['3','4'], result['dist'])\n        ii      = result['index']\n\n    # return tier 3\/4\n    if ii &gt;= 0 and len(self._result_dest) &gt; 0:\n\n        tmp_tier = self._dest_list[ii].tier\n        tmp_all_tier = self._main_tmp.get_all_tier()\n\n        # if tmp_tier not in tmp_all_tier:\n        #     # ensure that each leg has only 1 3\/4 tier\n        #     self._main_tmp.add_tier(tmp_tier, ii)\n        #     return self._dest_list.pop(ii)\n\n        # TODO: this repetition, refactor\n        if self._dest_list[ii].is_gluttony_time(result['arrival']):\n\n            if tmp_tier not in tmp_all_tier:\n                self._main_tmp.add_tier(tmp_tier, ii)\n                return self._dest_list.pop(ii)\n\n        else:\n\n            if tmp_tier not in tmp_all_tier:\n                self._main_tmp.add_tier(tmp_tier, ii)\n                return self._dest_list.pop(ii)\n\n\n\n    if i &gt;= 0:\n        # 1. save it to the main_temp for refference\n        self._main_tmp = self._dest_list[i]\n        return self._dest_list.pop(i)\n\n    return None\n<\/pre>\n\n\n<p>Each trip will be distributed between 1 premium locations&#8217; tier and 3 ordinary location&#8217;s tier so if the user still has time to visit other places then the same pattern will be repeated.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/rahmat-hidayat.com\/wp-content\/uploads\/2020\/03\/pattern.jpg\"><img decoding=\"async\" loading=\"lazy\" width=\"682\" height=\"1024\" src=\"https:\/\/rahmat-hidayat.com\/wp-content\/uploads\/2020\/03\/pattern.jpg\" alt=\"\" class=\"wp-image-1916\"\/><\/a><\/figure>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ Calculating the time budget\n\nif(walking time + spending time + going home time &lt; time budget) {\n   repeat;\n} else {\n   return result;\n}\n<\/pre>\n\n\n<p>And of course, the pattern may vary depending on distances and schedules.<\/p>\n\n\n\n<p><strong>Check schedules<\/strong><br>There are 3 schedules I need to check, locations&#8217; schedule, public transport schedule, and whether it&#8217;s time for lunch\/dinner.<\/p>\n\n\n\n<p>Checking it is a simple lookup table comparison.<\/p>\n\n\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ndef is_gluttony_time(self, time):\n\n    # Restaurant 1100-1400, 1700-2100\n    # Cafe 0700-1100, 1300-1700\n    # Liquor 1700-2600\n    time_int = int(time.strftime('%H%M'))\n    time_slot = [\n        {\n            'open': [1100, 1700],\n            'close': [1400, 2100],\n            'tag': ['restaurant']\n        },\n        {\n            'open': [700, 1300],\n            'close': [1100, 1700],\n            'tag': ['restaurant','cafe']\n        },\n        {\n            'open': [1700],\n            'close': [2600],\n            'tag': ['liquor']\n        }\n    ]\n\n    for i, data in enumerate(time_slot):\n\n        a_open   = data['open']\n        a_close  = data['close']\n\n        for j, v in enumerate(a_open):\n            if time_int &gt; v and time_int &lt; a_close[j]:\n\n                # TODO: change self._types to tag\n                check = self.match_tags(time_slot[j]['tag'], self._types)\n\n                if check:\n                    self._raw['is_gluttony'] = True\n\n                return check\n\n    return False\n<\/pre>\n\n\n<p>So that&#8217;s the gist of it, I still have the plan to expand it, find some investors and do what all startup founders do drown in money and be famous or have a mental breakdown, hahaha.<\/p>\n\n\n\n<p>Icons made by <a href=\"https:\/\/www.flaticon.com\/authors\/ddara\">dDara<\/a> from <a href=\"https:\/\/www.flaticon.com\/\">www.flaticon.com<\/a><br>Icons made by <a href=\"https:\/\/www.flaticon.com\/authors\/catkuro\">catkuro<\/a> from <a href=\"https:\/\/www.flaticon.com\/\">www.flaticon.com<\/a><br>Icons made by <a href=\"https:\/\/www.flaticon.com\/authors\/smashicons\">Smashicons<\/a> from <a href=\"https:\/\/www.flaticon.com\/\">www.flaticon.com<\/a><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Update 2020\/06\/03 Well, we&#8217;ve finally ended it, 1.5 years of collecting data, taking the photo of every single location, and making the MVP went down the drain. But worry not, although I&#8217;m getting older, this will not be my last venture, as long I can work 16-18 hours a day someday I will have a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[7,23,29,31,35,36,203,52],"tags":[],"_links":{"self":[{"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=\/wp\/v2\/posts\/1875"}],"collection":[{"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1875"}],"version-history":[{"count":40,"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=\/wp\/v2\/posts\/1875\/revisions"}],"predecessor-version":[{"id":2019,"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=\/wp\/v2\/posts\/1875\/revisions\/2019"}],"wp:attachment":[{"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1875"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1875"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rahmat-hidayat.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1875"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}