Building A Location Finder App Powered by the Google Maps JavaScript API
A Case Study in Structured Problem Solving
As a web developer working at a marketing agency, I’m often called upon to create small front-end applications for clients who want to offer a special tool or experience to visitors of their website. This gives me the opportunity to apply the PEDAC problem solving process I learned at Launch School to “real life” development situations.
A recent client project provided a good case study how well the PEDAC approach works when applied to slightly more complex problems, so I decided to write out a case study showing how I used a structured approach to break the problem down into smaller pieces, solve the pieces, put them back together, and then ultimately arrive at code that actually worked.
You can read about the specifics of PEDAC at the link above, but in this article I’ll take you through my approach to the “problem”, which in this case was in the form of a feature request from a client.
My goal with this is to try to demonstrate how taking the time to really understand the problem well and break complexity into simpler pieces can lead to a much easier time of creating an algorithm that solves the problem, and thus a more straightforward task in translating that algorithm into code (and of course, debugging that code).
Note: just because it’s my favorite fast food restaurant, and they have a ton of locations, I decided to use Chipotle locations to populate my example data for this article and demo. They had nothing to do with this :-).
A Description of the Problem
A client’s business has multiple locations in a number of states across the US. They already had a Google map on their website that included markers representing each location, and they requested that we add some functionality that would allow the user to “Find Locations Near Me”.
Specifically, the user should be able to input their street address and a radius in miles, and then view a list of locations within the specified radius, as well as an updated version of the Google map showing only markers for those locations that fall within the specified radius.
Understanding the Problem
The first step was to attempt to decompose the problem description into smaller problems, and start to think about what requirements in each were implicit or explicit.
To start with, the most obvious task was simply generating a custom Google map with a set of markers on it, that is constrained or “zoomed” in properly to show just the general area bounded by those markers farthest from one another.
The Google Maps JavaScript API provides a way to achieve this in a straightforward manner by allowing a callback function to be passed along as a parameter appended to an API request. I had already created a simple custom map function for the client with markers, info windows associated with each marker, and bounds, so this part of the problem was already solved (note: you can read a great tutorial about the API here, reference Google’s excellent and extensive API documentation here, and see a short tutorial about adding markers to custom Google maps here).
Here’s the basic example Google provides for embedding a custom map in an HTML document via an API request:
Markers
Markers are an important part of the problem description that requires implicit knowledge — what are they, exactly?
Markers in a Google map are objects of type Marker that include a “position” property whose value should be another object with Latitude and Longitude properties — either a literal object with these properties or a “latLng object”. This latLng object allows the marker to be precisely rendered relative to the map itself. The map on which the marker should be placed is then provided as the other argument when the marker is created.
Here’s a simple example of a function that could be appended to a Google Maps API request to generate a map with one marker — you can see the marker being created on line 9 (code example provided by Google):
I knew at this point that a broad description of the approach was this: I was going to be creating a JavaScript function that would pass along some kind of marker data to the Google Maps API, which would then return a rendered Google Map to the specified HTML element (#map). The hard part would be ensuring the proper location data was generated and filtered correctly given the user input.
Input, Output & Data Types
Having accounted for the map and marker generation process, I next focused on what the input and output of my callback function should be in order to fulfill the requirements, along with an idea of what data type I’d most likely be dealing with for each.
Inputs:
- The user’s location: a string, like “1350 West Street, Los Angeles, CA”, or even just “Los Angeles”
- The user’s desired radius in miles: a number
- The list of all locations: an Array of objects, which each contain location data for a business, like “Address”, “Phone Number”, as well as “Latitude” and “Longitude” (you can see the locations data I used for this demo here)
Output:
- A filtered list of locations: an array of objects. This will be used to 1) output a list of locations as text, and 2) build an API call to Google to re-render the map with the specified markers.
With the input and output more clearly defined, and some of the implicit requirements translated into the language of the problem domain and API being consumed, it felt like my mental model of the problem was taking shape.
Digging Deeper Into the Problem
The next step was to make the requirements themselves a lot more specific — for example, what does it mean for a location to be with X miles of another location? Well, if my latLng value is {latY, lngY} and the business’s latLng is {latZ, lngZ}, it should just mean that the distance from {latY, lngY} to {latZ, lngZ} is less than or equal to X.
Therefore, if I could find a way to reliably compute the distance between two latLng objects and check if the result was less than or equal to the radius input by the user, the rest of the problem would consist of simply filtering the list of locations to include only those that fit that criteria (a hint that I’d probably be using Array abstractions).
But how to actually find the distance between any two locations on the Earth’s surface, even if I had the latitude and longitudes?
Some quick research revealed that if both locations are in the form of latitude/longitude coordinates, they can be treated as points on a sphere, and then the distance between them can be calculated via something called the Haversine formula.
That looked complicated…maybe a little too complicated for the amount of time I could reasonably use to solve this problem before the client got antsy.
Luckily, some more research into the documentation at Google quickly revealed there is indeed an easier way available — the Spherical Geometry library that’s part of the Google Maps API includes a function that computes the distance between two latitude/longitude points, and returns the distance in meters.
Since I already had the latitude and longitude data available for each location (I found it manually at the beginning of this process), all I would need was a way to convert the user’s address into latitude/longitude, and then a Google latLng object, as well.
Once again, lazy developers everywhere will rejoice with me in learning that Google provided a way to do this easily via another free tool— the Geocoding API.
Sending a request to the Geocoding API with an address string parameter returns a JSON object that includes location latitude and longitude data — here’s an example using Oriole Park at Camden Yards, which is located at 333 W Camden Street, Baltimore MD:
Request: https://maps.googleapis.com/maps/api/geocode/json?address=333+W+Camden+St,+Baltimore,+MD+21201&key=MY_API_KEY
Response:
That’s a lot of useful data, but most importantly, it includes the “lat” and “lng” as properties that can be accessed under “geometry: location”.
Test Cases & Examples
I now had a good idea of the helper functions that would be involved in implementing my callback function — together they’d need to take the user’s input, make the API requests as needed, parse the response(s), and output the updated map.
Each of the helper functions represented a small problem unto itself, which required its own set of test cases to ensure that its behavior was what I expected.
Rather than exhaustively list the actual code for these, I’ll just exhaustively explain how I mentally modeled what was needed for each sub-problem:
- A “create searchable map” function should generate a google map, rendering the correct markers (and their associated infoWindows). The markers should be passed in as an argument
- The user’s address information (a string) should be combined with an API key and partial URL to return a correctly formatted Geocoding API request (which is essentially just an HTTP GET request with the address data added as a parameter)
- The Geocoding API request should return a response in the form of a JSON object that includes the correct latitude and longitude for the address it was asked about, or a status of “ZERO_RESULTS” if an address doesn’t exist/is poorly formatted
- The “distance between” function from the spherical geometry library should return the correct distance in meters between two latLng objects passed in as arguments
- A “convert meters to miles” helper function should return the correct number of miles given a number of meters as an argument
- An “is within radius” helper function should return true given two latLng objects and a radius number as arguments, if the distance between the two latLng objects is less than or equal to the radius
- A “filter locations” helper function should return a subset of a “locations” array including only elements for which the “is within radius” function returned true
- A second call to the “create map” function, when passed the filtered locations array, should render markers correctly for only those locations
Once I knew each piece was working individually, all that remained was putting it all together, and testing a variety of inputs for which I already knew the correct return values.
Once that was working, I could safely move on to higher level concerns like enforcing/validating user input, creating a user interface with HTML and CSS, and then making sure the interactions between my JavaScript and the DOM were all working as intended.
Edge Cases
To deal with edge cases in the user input, I decided to rely on simply sanitizing the user input strings since this would be a client-side app, and because the Google APIs are pretty forgiving of bad input. As far as I could determine, if something really strange or empty did get placed into the API request, the response would simply be a “ZERO_RESULTS” message or an error, both of which I could account for with a guard clause.
Algorithm
Because of the time I spent breaking the problem into smaller pieces and testing each piece, by this point the algorithm for the problem as a whole now felt more straightforward, and implicit requirements had been defined and abstracted out as needed.
This was my algorithm for implementing the solution as a client-side application:
- Take a radius (number) and address (string) from the user via an HTML form
- If both inputs are valid, then convert the address into a set of latitude/longitude coordinates
- Filter the array of locations, returning a new array including only those locations where the distance between the user’s coordinates and the location’s coordinates is ≤ the user’s radius
- Output a new version of the Google map, passing in the filtered locations list and using it to create markers
- Output the list of filtered locations as HTML elements below the map
Code
If you made it this far, or if you TLDR;’d down here after skimming the intro, you’re in luck! It’s finally time for the solution.
Here’s the approach I came up with to implement my “Create Searchable Map” algorithm, using plain JavaScript with a bit of jQuery: https://github.com/Dchyk/locations-near-me/blob/master/createSearchableMap.js.
Note: You can play around with a live demo of this project over on my website, and view the full project repo over at my Github.
The demo data of Chipotle locations is here and takes form of an array of objects.
Example:
Thanks for Reading
I hope you enjoyed this attempt to illustrate my approach to problem solving, and I hope you got a sense of how well a process like PEDAC can serve as a framework for dealing with not just coding interview challenge questions, but actual software feature requests.
Thank you for reading, and I welcome any feedback or comments you might have about any of this.