[Rails] Displaying Google Maps using Google Maps API and searching routes between multiple points

Introduction

There are very few articles that can be used as a reference, and it was the most difficult part of creating a portfolio, so I wrote it because I thought it would be good to leave it as an output for my own learning and to help someone! What is the meaning of this description as a beginner? I intend to explain the part that I think. of course! Please note that there are many parts that I think.

Target

In addition to displaying Google Map with the Google Maps API, you can arbitrarily add it to the route search list from the marker balloon, and the goal is to search for routes at multiple points. ezgif com-optimize-2

Development environment

・ Ruby: 2.5.1 Rails: 5.2.1 ・ OS: macOS

Premise

・ Introduction of Slim -Enable the following APIs on the official Google Maps Platform ・ Maps JavaScript API → Display of Google Map ・ Geocoding API → Calculation of latitude and longitude from address ・ Directions API → Route search

Setting

1. Install the required gems

Gemfile


gem 'dotenv-rails' #Make API key an environment variable
gem 'gon' #Make the instance variables defined in the controller available in JavaScript.
gem 'geocoder' #Calculate latitude and longitude from the address.

Terminal


$ bundle install

2. Make the API key an environment variable

Create a ".env" file directly under the application

Terminal


$ touch .env 

Write your API key in''

.env


GOOGLE_MAP_API = 'API key that you copied'

.gitignore


/.env

3. Disable turbolinks

Gemfile


gem 'turbolinks' #Delete this line

app/assets/javascripts/application.js


//= require turbolinks //Delete this line

data-turbolinks-track': Remove'reload' attribute

ruby:app/views/layouts/application.html.slim


= stylesheet_link_tag    'application', media: 'all'
= javascript_include_tag 'application'

4. Make the Geocoding API available

Create and edit geocorder configuration file

Terminal


$ touch config/initializers/geocoder.rb

config/initializers/geocoder.rb


#Postscript
Geocoder.configure(
  lookup: :google,
  api_key: ENV['GOOGLE_MAP_API']
)

This completes the settings. From here, we will enter the implementation that displays Google Map.

Display of Google Map

1. Add a column to the model you want to add

For your app, add an address column to your Place model. The latitude and longitude columns are the longitude and latitude values calculated from the values in the address column by the Geocoding API. Since it is a decimal value, the type uses float.

Terminal


$ rails g migration AddColumnsToPlaces address:string latitude:float longitude:float

Terminal


$ rails db:migrate

2. Edit the model

models/place.rb


  #Postscript
  geocoded_by :address #Calculate latitude and longitude based on the address column.
  after_validation :geocode #Latitude and longitude are also changed when the address is changed.

3. Edit the controller

controllers/places_controller.rb


def index
  @place = Place.all
  gon.place = @place #Postscript
end

private
  def place_params
    #Added "address" to strong parameters
    params.require(:place).permit(:name, :description, :image, :address)
  end

4. Edit the view

① Edit application.html.slim Write to load gon before CSS and JavaScript.

ruby:views/layouts/application.html.slim


doctype html
html
  head
    title
      | app_name
    = csrf_meta_tags
    = csp_meta_tag
    = include_gon #Postscript
    = stylesheet_link_tag    'application', media: 'all'
    = javascript_include_tag 'application'

② Add an address input form to the new registration screen

ruby:views/places/new.html.slim


= f.label :address, 'Street address'
= f.text_field :address, class: 'form-control'

③ Describe in the file that displays Google Map

ruby:views/places/index.html.slim


div id = 'map_index' #Give id,Google Map described in js file is embedded in this part
- google_api = "https://maps.googleapis.com/maps/api/js?key=#{ ENV['GOOGLE_MAP_API'] }&callback=initMap".html_safe
script{ async src = google_api }

.map-route
  <Route search list>
  ul id = "route-list" class = "list-group" #Add callout button in js file adds that location to the li element


div id = 'directions-panel' #Distance / time is embedded
  <Distance / time between points>
  ul id = "display-list" class = "display-group"

.map-search
   = button_tag "Route search", id: "btn-search", class: "btn btn-primary", onclick:     "search()" #Search by click processing()Call a function

[About the part of google_api = ~ ~ ~ ~] → Call the initMap function when reading in the callback process. → .html_safe is escape processing → The async attribute loads JavaScript asynchronously to speed up rendering.

④ Describe the size you want to display on Google Map in scss

stylesheets/application.scss


#map_index{
  height: 400px;
  width: 400px; 
}

5. Edit the JavaScript file

This is the liver. Create a new file directly under assets / javascripts and describe it. It may be hard to see for a long time, but after defining the variables, we just define each function. the function is, ・ InitMap ・ MarkerEvent (i) ・ AddPlace (name, lat, lng, number) ・ Search () There are four in the order of. Please refer to the commented out explanations for parts and points that are difficult to understand.

assets/javascripts/googlemap.js


var map
var geocoder
var marker = [];
var infoWindow = [];
var markerData = gon.places; //Assign the instance variable defined by the controller to the variable
var place_name = [];
var place_lat = [];
var place_lng = [];

//Function to display GoogleMap(Called in callback process)
function initMap(){
    geocoder = new google.maps.Geocoder()
    //View id='map_index'Embed Google Map in the part of
    map = new google.maps.Map(document.getElementById('map_index'), {
      center: { lat: 35.6585, lng: 139.7486 }, //Centering on Tokyo Tower
      zoom: 9,
    });

    //Display multiple markers and balloons by iterative processing
    for (var i = 0; i < markerData.length; i++) {
      //Calculate the latitude and longitude of each point
      markerLatLng = new google.maps.LatLng({
        lat: markerData[i]['latitude'],
        lng: markerData[i]['longitude']
      });
      
      //Display of markers
      marker[i] = new google.maps.Marker({
        position: markerLatLng,
        map: map
      });

      //Display of balloons
      let id = markerData[i]['id']
      place_name[i]= markerData[i]['name'];
      place_lat[i]= markerData[i]['latitude'];
      place_lng[i]= markerData[i]['longitude'];
      infoWindow[i] = new google.maps.InfoWindow({
        //Contents of the balloon,Pass the array and array number of each attribute as an argument
        content: `<a href='/places/${ id }'>${ markerData[i]['name'] }</a><input type="button" value="add to" onclick="addPlace(place_name, place_lat, place_lng, ${i})">`
      });
      markerEvent(i);
    }
  }
}

//Click the marker to display a balloon
function markerEvent(i) {
  marker[i].addListener('click', function () {
    infoWindow[i].open(map, marker[i]);
  });
}

//Add to list
function addPlace(name, lat, lng, number){
  var li = $('<li>', {
    text: name[number],
    "class": "list-group-item"
  });
  li.attr("data-lat", lat[number]); // data-lat to the attribute lat[number]Put in
  li.attr("data-lng", lng[number]); // data-lng to the attribute lng[number]Put in
  $('#route-list').append(li); //id is route-Add li to the end of the list element
}

//Search for a route
function search() {
  var points = $('#route-list li');

  //When there are two or more points
  if (points.length >= 2){
      var origin; //Starting point
      var destination; //End point
      var waypoints = []; //Waypoint

      // origin, destination,Set waypoints
      for (var i = 0; i < points.length; i++) {
          points[i] = new google.maps.LatLng($(points[i]).attr("data-lat"), $(points[i]).attr("data-lng"));
          if (i == 0){
            origin = points[i];
          } else if (i == points.length-1){
            destination = points[i];
          } else {
            waypoints.push({ location: points[i], stopover: true });
          }
      }
      //Make a request
      var request = {
        origin:      origin,
        destination: destination,
        waypoints: waypoints,
        travelMode:  google.maps.TravelMode.DRIVING
      };
      //Root service request
      new google.maps.DirectionsService().route(request, function(response, status) {
        if (status == google.maps.DirectionsStatus.OK) {
          new google.maps.DirectionsRenderer({
            map: map,
            suppressMarkers : true,
            polylineOptions: { //Settings for drawn lines
              strokeColor: '#00ffdd',
              strokeOpacity: 1,
              strokeWeight: 5
            }
          }).setDirections(response);//Line drawing part
          
            //Display distance and time
            var data = response.routes[0].legs;
            for (var i = 0; i < data.length; i++) {
                //distance
                var li = $('<li>', {
                  text: data[i].distance.text,
                  "class": "display-group-item"
                });
                $('#display-list').append(li);

                //time
                var li = $('<li>', {
                  text: data[i].duration.text,
                  "class": "display-group-item"
                });
                $('#display-list').append(li);
            }
            const route = response.routes[0];
            //View id='directions-panel'Embed in the part of
            const summaryPanel = document.getElementById("directions-panel");
            summaryPanel.innerHTML = "";

            //Display the distance and time between each point
            for (let i = 0; i < route.legs.length; i++) {
              const routeSegment = i + 1;
              summaryPanel.innerHTML +=
                "<b>Route Segment: " + routeSegment + "</b><br>";
              summaryPanel.innerHTML += route.legs[i].start_address + "<br>" + " ↓ " + "<br>";
              summaryPanel.innerHTML += route.legs[i].end_address + "<br>";
              summaryPanel.innerHTML += "<" + route.legs[i].distance.text + ",";
              summaryPanel.innerHTML += route.legs[i].duration.text + ">" + "<br>";
            }
        }
      });
  }
}



__ Supplement to the content part of the content of the balloon: (because I struggled with the method of passing data) __

content: `<a href='/places/${ id }'>${ markerData[i]['name'] }</a><input type="button" value="add to" onclick="addPlace(place_name, place_lat, place_lng, ${i})">`

addPlace(place_name, place_lat, place_lng, ${i}) In the call to this function, the previous three arguments are passed as an array. The fourth argument is the expression expansion of the number (called an index) that represents what information in the array. Expression expansion in JavaScript seems to be this form. By providing such an argument, the function addPlace (name, lat, lng, number) can process information about what data it is normally.

Finally

Thank you for reading to the end. I myself am in a state where my portfolio is nearing completion and I have begun job hunting in earnest! We sincerely support those who have goals such as portfolio creation and job change activities, so let's do our best together! !!

reference

-How to display multiple markers on Google Map and display a balloon when clicked

-[Search for routes of multiple points added to the list with Google Maps API] (https://qiita.com/yoshi_01/items/d3848e4e7c854fe585bd)

Recommended Posts

[Rails] Displaying Google Maps using Google Maps API and searching routes between multiple points
Display Google Maps API with Rails and pin display
[Google Maps API] Map is not displayed and becomes blank [Rails]
[Ruby on Rails] Display and pinning of GoolgeMAP using Google API
[Rails] google maps api How to post and display including map information
[Rails] Google Maps API Description when latitude and longitude cannot be saved
[Rails 6 / Google Map API] Post an address and set multiple markers on the map
Book registration easily with Google Books API and Rails
Speech transcription procedure using Python and Google Cloud Speech API
[Ruby on Rails] Multiple pins, balloons, and links on Google map
Beginners of Google Maps API and Twitter API made "tweet map"
[Map display] Display a map from the address registered by the user using the Google Maps JavaScript API and Geocoding API!
I tried using docomo speech recognition API and Google Speech API in Java