Coming soon…

Something else that we’ve been working on…

Stay tuned!

Hack hard, hack fast.

We have a lot of place data. A lot. We use this data in nearly everything we do, but we wanted to investigate different ways to use it and expose it. So we spent a day exploring what those different ways might be. Below are four proofs of concept we built in that day. All need just a bit more polish before they’re production-ready.

Day Trip Planner

Inspired by The New York Times’ “36 Hours” series of travel articles—examples include Cincinnati and Chicago—we wanted to put together a proof-of-concept day trip generator.

Our initial implementation starts with a list of templates that represent things you might do on a day trip, like “eat breakfast at 8:30am” or “visit a local point of interest at 10am.” Each template defines the search criteria that we use to find matching places to suggest in a specific city. For example, the “breakfast” template might search for places that are open from at least 8am until 10am and are categorized as “diners” or “cafes.”

Given a city, we can then find the “best” places in or around that city which satisfy the search criteria for each activity. We inject a little bit of randomness into that definition of “best”, so we can generate more than one interesting day plan per city. After generating a day plan, it's also possible to replace individual places in the itinerary with new suggestions. Once the user is satisfied with their day plan, they can save it as a trip.

Random Trip Generator

In late 2014, Daniele Quercia gave a TED talk about “happy maps”. Using existing data we have in our database, we took a swing at creating “happy trips” via an algorithm that generates a random trip based on how many waypoints a user wants to visit and how far out of the way a user is willing to travel—think “I’m Feeling Lucky” for roadtrips.

Once origin and destination points have been entered, the trip re-routes to a random collection of points that are within the specified distance. These points are entered in an optimal order based on location, and the set of places are selected by the highest user engagement; we also determine the optimal times to stop for food and gas and select the best place to book a hotel based on the length of the trip.

Discover Routes Containing a Place

We keep tallies of the number of times places are added to trips or saved to users’ saved places and collections. Until now, we’ve kept these numbers for internal use, primarily to gauge popularity. Displaying these numbers on the place pages is a simple feature, but could be very meaningful to users. It adds another metric for users to determine the quality of a place. Including the drop-down of collections and trips in which a place belongs provides social validation and could provide an inlet into discovery we have yet to explore.

Personalization on iOS

Our mobile team wanted to focus on making a feature both personal and unique to a mobile device. Since we know which places you have saved, and your location when you open the map, we were able to create a proof of concept that focuses on giving recommendations tailored exactly to your taste!

As you see in the accompanying video, we added a button to the map that will open a personal recommendations view. When you tap the button, the app will check to see the types of places you save most often and suggest places in the area that seem most up your alley. Or, if you’re planning a trip in and you don’t just want to see the most popular places in your destination, you could move the map to your destination and tap the button to see the best places for you in that area.

Server-generated HTML In a Single-page Web Application

Meridian is the client framework we have built around Roadtrippers. One unique feature of Meridian is how and where we generate our HTML. Rather than rely on JavaScript to generate DOM elements for Roadtrippers, Meridian relies heavily on server-generated HTML.

The Server, You Say?

Roadtrippers is backed by a Ruby on Rails application. One thing Rails excels at is spitting out HTML. HTML generated on the server can also be cached for reuse, for example when another user visits the same blog post or place page. So while Rails can generate HTML very quickly, it can pull it from cache even faster.

That is not to say using server-generated HTML is easy to roll out. If it were easy, everyone would be doing it already. With Meridian we have built a solution that allows us to harness the speed of server-generated HTML with the responsiveness of a single-page application.

One Hundred Million Entry Points

For the purposes of this paragraph, an “entry point” will be any way a user can enter a web application. Typically, single page web apps have a single entry point. That is not the case with Roadtrippers. Roadtrippers has as many entry points as we have places in Atlas, plus however many blog posts we have, plus however many users are registered, plus..., etc. Every valid URL within Roadtrippers serves as an entry point.

We are particularly interested in those entry points that we seed into search engines. When someone searches for “POPS Soda Ranch” we want our place page to appear in the results. When the user clicks on the link, we want them to land immediately on the page for POPS Soda Ranch and have the full Roadtrippers experience.

Web Applications for Robots

Gone are the days when search engines are painstakingly curated by humans. Robots have taken those jobs, so those robots need to find our pages in order to properly index and rank them. But until recently, those robots did not care one whit about JavaScript.

Early on, before Google announced that their crawler would execute JavaScript, we needed Roadtrippers pages to appear in search results. At that time the robots relied entirely on the HTML that came during the page load.

Given the dual advantages of speed and indexing, our goal was clear: we need to generate our pages’ HTML on the server. But how do we do that and start our single-page app?

Respect the URL

Meridian uses the same route to access an object whether landing on the page as an entry point or navigating within the Roadtrippers app. The Rails server generates the HTML for both cases, based on whether the request comes from an AJAX request.

Meridian also has established conventions about where in the DOM tree views are added. These conventions make for a smooth transition between the server HTML and the client JavaScript.

Digging the Hooks In

Now that Meridian can fetch full and partial pages from the Rails app, it needs to plug that HTML into the JavaScript client. This presents a problem.

Server-rendering has become a hot topic lately among JavaScript frameworks. With React, react-router supports server generated markup, provided the server is Node.js. Ember can take advantage of server-rendered HTML using FastBoot, again, if your server is Node.js. Meridian, however, needs to work with a Rails server.

Enter Backbone

Backbone is not a sexy framework these days. It is widely regarded as the tool with which an application’s framework is built, and that has certainly been the case with Meridian. Ember and React bring their own standards of how things are to be done, but they don’t fit with how Roadtrippers functions. Backbone’s permissiveness allows Meridian to plug arbitrary HTML into a Backbone.View.

Each Backbone.View has a member called $el that wraps up the DOM generated by the render function. However, Backbone.View allows us to give it some DOM to tuck inside of $el.

With a little bit of jQuery we can give Backbone.View a DOM element:

new ShowPlaceView
  el: $('show-place-view')

When we create the Backbone.View it will take the DOM element we give it and work all of its Backbone magic, just without ever using a call to render. The events and handlers defined for the Backbone.View will get bound up just like they would have when using a render function.

Meridian utilizes the HTML generated by the Rails server according to the established convention just like this. When it comes time to open a new view, we need to get more HTML from the Rails server:

$.get '/url/route/to/html', (html) ->
  new ShowPlaceView
    el: $(html)

Meridian uses the same URL to fetch the partial HTML and give it to the Backbone.View. From there on, Meridian’s views operates like normal Backbone.Views.

No Silver Bullet

The approach Meridian uses to server generated HTML does not work in all cases. Within Meridian, there are still some components that use client-side templates to generate HTML in JavaScript. We have found this is beneficial for highly interactive components for which the server round-trip takes too long to provide an excellent experience. Fortunately this technique is not all-or-nothing; it can be applied judiciously to optimize for user experience.

How Do I Convince my Boss/Team/Spouse/Benefactor/... ?

Generating HTML on your application’s server yields faster initial page loads, results in a smaller over-all payload (because you no longer have client-side templates), and a better over-all user experience. It can be rolled out piecemeal, so that the results can be measured and the hypothesis of better user experience tested. There is no need to generate all of the HTML on the server, but for those components where it has great impact it can and should be utilized.

Introducing Imagineer

On 3/13/2015, we set Imagineer loose as one of our newest and most cutting edge open source projects. I'll explain why we built it, how it helped us, and where it's headed.

The problem

Images are important. At Roadtrippers, we use images to show our users that we know where we're taking them. We provide a lot of guides and blog posts. We also drive and decorate other content types with images.

Designs change, and new image sizes are required often as we shuffle our layout and inch toward the perfect UX. As our internal image library grew, the time it took to create a new version grew. Generating resized images was no small task for a few million place records with any number of images!

A little about us

Our back end is built with Ruby on Rails, and we use the rightfully popular CarrierWave gem to manage images for our models. Prior to Imagineer, we used two different integrations with CW:

These have their own merits. Fog is useful, but new sizes require loading each record, generating, and resaving. Cloudinary takes much of that pain away, but we ended up up paying for features we didn't need and feeling limited by their API.

Enter Imagineer

We set out to create a solution for ourselves. We had a list of goals:

  • Simple code to create new images, no extra tasks need to be ran
  • Keep image processing off our app servers
  • Able to handle a flood of identical requests, typical for new image versions

With that in mind, we created Imagineer. Imagineer produces images based on the request URL. If you request /-resize_50x50/filename.png, you recieve a copy of /filename.png that is 50px wide and 50px tall.

Imagineer is a simple Node app and Lambda function. The Node app handles the image requests, calls the Lambda function, and redirects the connection to the image after creation. The Lambda function downloads the image from S3, creates the new version, and finally stores the new version in S3.

What this means for us

When someone requests an image that doesn't exist, that request is forwarded to Imagineer. The application doesn't have to worry about anything other than knowing how to encode the request. We extended CarrierWave in our app to translate processes to Imagineer paths.

Processesing of images is handled entirely in Lambda. One of our goals here was just to get familiar with Lambda and see how we could make it work for us. By doing so, we were able to remove image processing from our app while making it flood resistant. We pushed the optimization even further by placing a CDN—for us, that's MaxCDN—in front of the whole thing to add an extra caching layer and all the benefits of geographically distributed assets. The app server and the Imagineer server can safely defer to Lambda, S3, and the CDN.

Using Node and Lambda, we were able to accomplish everything we set out to do in about 215 lines of code! We began using some version of this in production months ago, and haven't looked back.

Challenges and caveats

Configuration is open ended, and therefore potentially not straightforward. You will need to host the Node app somewhere. For that, we chose Elasticbeanstalk.

You will need to forward image requests to Imagineer. For that, we use a redirect rule on the static web hosting option that S3 provides. Our configuration is posted in the Imagineer README.

Resources for Lambda functions are limited. We use the maximum of 1024mb memory for each process, but things like large image files and changing interlacing can cause the process to overload and die.

The future

Some key features that we'd like to work on are expanding the supported image manipulations, cleaning up the path encoding, and adding more automated configuration.

Our hope is that other folks might benefit from using Imagineer, and lend their feedback or development skills to continue improving the project. It's a tool that we rely on, but also a gift to the open source community that has given us the tools to build Roadtrippers.

Holler if you've got any questions about Imagineer or anything else that we do here at Roadtrippers. If you're interested in more about our experience with Lambda, check out Lambda: Bees With Frickin' Laser Beams. Thank you for reading!

Introducing_imagineer

2015-05-13-introducing_imagineer

layout: post
title: “Introducing Imagineer”
author: Justin Hall


On 3/13/2015, we set Imagineer loose as one of our newest and most cutting edge open source projects. I’ll explain why we built it, how it helped us, and where it’s headed.

The problem

Images are important. At Roadtrippers, we use images to show our users that we know where we’re taking them. We provide a lot of guides and blog posts. We also drive and decorate other content types with images.

Designs change, and new image sizes are required often as we shuffle our layout and inch toward the perfect UX. As our internal image library grew, the time it took to create a new version grew. Generating resized images was no small task for a few million place records with any number of images!

A little about us

Our back end is built with Ruby on Rails, and we use the rightfully popular CarrierWave gem to manage images for our models. Prior to Imagineer, we used two different integrations with CW:

These have their own merits. Fog is useful, but new sizes require loading each record, generating, and resaving. Cloudinary takes much of that pain away, but we ended up up paying for features we didn’t need and feeling limited by their API.

Enter Imagineer

We set out to create a solution for ourselves. We had a list of goals:

  • Simple code to create new images, no extra tasks need to be ran
  • Keep image processing off our app servers
  • Able to handle a flood of identical requests, typical for new image versions

With that in mind, we created Imagineer. Imagineer produces images based on the request URL. If you request /-resize_50x50/filename.png, you recieve a copy of /filename.png that is 50px wide and 50px tall.

Imagineer is a simple Node app and Lambda function. The Node app handles the image requests, calls the Lambda function, and redirects the connection to the image after creation. The Lambda function downloads the image from S3, creates the new version, and finally stores the new version in S3.

What this means for us

When someone requests an image that doesn’t exist, that request is forwarded to Imagineer. The application doesn’t have to worry about anything other than knowing how to encode the request. We extended CarrierWave in our app to translate processes to Imagineer paths.

Processesing of images is handled entirely in Lambda. One of our goals here was just to get familiar with Lambda and see how we could make it work for us. By doing so, we were able to remove image processing from our app while making it flood resistant. We pushed the optimization even further by placing a CDN, and for us that’s MaxCDN, in front of the whole thing. The app server and the Imagineer server can safely defer to Lambda, S3, and the CDN to do all the heavy lifting.

Using Node and Lambda, we were able to accomplish everything we set out to do in about 215 lines of code! We began using some version of this in production months ago, and haven’t looked back.

Challenges and caveats

Configuration is open ended, and therefore potentially not straightforward. You will need to host the Node app somewhere. For that, we chose Elasticbeanstalk.

You will need to forward image requests to Imagineer. For that, we use a redirect rule on the static web hosting option that S3 provides. Our configuration is posted in the Imagineer README.

Resources for Lambda functions are limited. We use the maximum of 1024mb memory for each process, but things like large image files and changing interlacing can cause the process to overload and die.

The future

Some key features that we’d like to work on are expanding the supported image manipulations, cleaning up the path encoding, and adding more automated configuration.

Our hope is that other folks might benefit from using Imagineer, and lend their feedback or development skills to continue improving the project. It’s a tool that we rely on, but also a gift to the open source community that has given us the tools to build Roadtrippers.

Holler if you’ve got any questions about Imagineer or anything else that we do here at Roadtrippers. If you’re interested in more about our experience with Lambda, check out Lambda: Bees With Frickin’ Laser Beams. Thank you for reading!