Search code examples
ruby-on-railsgoogle-maps-api-3open-uri

Use Maps API to calculate distance of two addresses then save each distance to database


I'm writing an app which matches children to kindergartens based on the distance.

I am trying to use the Maps API to calculate distances between multiple addresses. I want to use open-uri

https://maps.googleapis.com/maps/api/distancematrix/json?origins=[address of child]&destinations=[adress of kindergarden]&key=XYZ

that would be the request for the api.

My idea was to send the request after the creation process of the child. I would take the child address and the kindergarten address from the database and build the request.
The responded distance would be saved in a relations table for each child and kindergarten How can I access the database entries of the child and the kindergartens? And then save it to the relations table?

My current code looks as follows:

def create
    @child = Child.new child_params
    @child.user = current_user
    @child.save
    origin = @child.adress
    destination = @kiga.adress
    response = open('https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + [address of child] + '&destinations=' + [adress of kindergarden] + '&key=XYZ').read
    relations.distance = response.rows.elements[0]

    respond_to do |format|
      if @child.save
        format.html { redirect_to @child, notice: 'Child was successfully created.' }
        format.json { render :show, status: :created, location: @child }
      else
        format.html { render :new }
        format.json { render json: @child.errors, status: :unprocessable_entity }
      end
    end

Help would be appreciated?


Solution

  • The issue here is that you are trying to open the URL as a string, so it's looking for a file named that way and it does not exist.

    You have two options here:

    1. Use HTTParty gem to make HTTP requests

    This one is my favourite approach.

    Include the gem in your Gemfile:

    gem 'HTTParty'
    

    Make the request within your controller:

    url = "https://maps.googleapis.com/maps/api/distancematrix/json?origins=" \
          "#{@child.adress}&destinations=#{@kiga.adress}&key=XYZ"
    response = HTTParty.get(url)
    

    Now if you were to print out the result of response["rows"].first["elements"].first you would see an output like this:

    {"distance"=>{"text"=>"15.4 km", "value"=>15405}, "duration"=>{"text"=>"17 mins", "value"=>1001}, "status"=>"OK"}
    

    As you can see you have distance as well as duration information. I am guessing you are after the distance value in meters so:

    relations.distance = response["rows"].first["elements"].first["distance"]["value"]
    

    NOTE 1: I intentionally reproduced the typo in the word address (@child.adress, @kiga.adress) so that the code works with your setup, but give a thought to fixing it to @child.address and @kiga.address.

    NOTE 2: For simplicity I did not do any error checking on the response, that's something you should definitely take care of.

    NOTE 3: Remember to change the api key to a valid one, I hardcoded it for simplicity to XYZ like you did in your question.

    2. Use open-uri to parse your url into a valid URI

    You have to require the libraries 'open-uri' and 'JSON' in the beginning of the controller:

    require 'open-uri'
    require 'JSON'
    
        def create
        @child = Child.new child_params
        @child.user = current_user
        @child.save
        origin = @child.adress
        destination = @kiga.adress
        
        # Parse the string url into a valid URI
        url = URI.parse(
            "https://maps.googleapis.com/maps/api/distancematrix/json?origins=" \
            "#{@child.adress}&destinations=#{@kiga.adress}&key=XYZ"
        )
        # Open the URI just like you were doing
        response = open(url).read
        # Parse the string response in JSON format
        result = JSON.parse(response)
    
        # Extract the distance value in meters
        relations.distance = result["rows"].first["elements"].first["distance"]["value"]
    
        respond_to do |format|
            if @child.save
                format.html { redirect_to @child, notice: 'Child was successfully created.' }
                format.json { render :show, status: :created, location: @child }
            else
                format.html { render :new }
                format.json { render json: @child.errors, status: :unprocessable_entity }
        end
    end
    

    The contents of the variable result (after parsing it) look like the following:

    {"destination_addresses"=>["Destination address"], "origin_addresses"=>["Origin address"], "rows"=>[{"elements"=>[{"distance"=>{"text"=>"15.4 km", "value"=>15405}, "duration"=>{"text"=>"17 mins", "value"=>1001}, "status"=>"OK"}]}], "status"=>"OK"}
    

    Either if you go for the HTTParty way or the open-uri + JSON parsing way, make sure you check the response status codes. Both approaches have been tested locally on my computer with successful results.

    I hope it helps, cheers!