7

In google maps, the search input box auto completes addresses as a user types. Is there a way to do this in R Shiny with access to the autocomplete value in order to use with a mapping package?

enter image description here

There is a javascript method here. I've tried to use this method in R Shiny in the code below. SymbolixAU pointed out using google_map( search_box = TRUE ) which is a simple solution. Unfortunately it doesn't work in my code and also because I would like the search box to be separate from the map.

The attempt below has a text input my_address, text output copy_of_address and a googleway map my_map in this order on the page.

The intended behaviour is for a user to enter text into text input my_address, have it autocomplete with an address (this works), the address will be copied into text output copy_of_address (this only shows what was typed, not the autocompleted version) and finally the map is to be centred on this address.

The shiny app

See that the input box has the autocomplete address, however the copy of the address and map is using only the user input text.

In the code below, replace MyKey with your google api key (sometimes an empty string works).

library(shiny)
library(googleway)

key <- "MyKey"
set_key(key = key)
google_keys()

ui <- shiny::basicPage(

  div(
    textInput(inputId = "my_address", label = "")    
    ,textOutput(outputId = "copy_of_address")
    ,HTML(paste0("
          <script>
            function initAutocomplete() {
            new google.maps.places.Autocomplete(
            (document.getElementById('my_address')),
            {types: ['geocode']}
            );
            }
            </script>
            <script src='https://maps.googleapis.com/maps/api/js?key=", key,"&libraries=places&callback=initAutocomplete'
            async defer></script>
    "))
    ,google_mapOutput(outputId = "my_map")
  )

)

server <- function(input, output) {

  my_address <- reactive({
    input$my_address
  })

  output$copy_of_address <- renderText({
    my_address()
  })

  output$my_map <- renderGoogle_map({
    my_address <- my_address()
    validate(
      need(my_address, "Address not available")
    )

    df <- google_geocode(address = my_address)
    my_coords <- geocode_coordinates(df)
    my_coords <- c(my_coords$lat[1], my_coords$lng[1])

    google_map(
      location = my_coords,
      zoom = 12,
      map_type_control = FALSE,
      zoom_control = FALSE,
      street_view_control = FALSE
    )
  })

}

shinyApp(ui, server)
Vlad
  • 3,058
  • 4
  • 25
  • 53
  • Are you using this on a map, or independent of a map (i.e, a separate widget)? – SymbolixAU Nov 17 '18 at 04:30
  • At the moment I have a map below the text box being used for the address - so it is not independent of a map. The map will centre on the address after it is entered. – Vlad Nov 17 '18 at 04:41
  • 1
    If you use `google_map( search_box = TRUE )` it will place a search box on the map and autocomplete – SymbolixAU Nov 17 '18 at 04:47
  • 1
    [here's an example of a shiny](https://github.com/SymbolixAU/googleway/blob/master/tests/manual_tests.R#L418), using the search box, and observing the results of what the user searches for. – SymbolixAU Nov 17 '18 at 04:53
  • Is there a way to have the search box outside the map but still work together? The use case is entering the address first and then showing the map. – Vlad Nov 17 '18 at 04:56
  • There's the `google_place_autocomplete` function, but, I don't think I ever got it to work. Might be worth having a look at. Happy to discuss its implementation over on the github issues page if you don't find an alternative solution. – SymbolixAU Nov 17 '18 at 05:06
  • @SymbolixAU before you pointed out `search_box` I tried the javascript solution and had the autocomplete working with a text input with `input_id = my_id`. However when reading the textbox input `input$my_id` the value was only what the user entered, not the autocomplete version. – Vlad Nov 17 '18 at 05:11
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/183808/discussion-between-vlad-and-symbolixau). – Vlad Nov 17 '18 at 05:12

1 Answers1

11

This wasn't easy for me to figure out, but I think I have a reasonable solution. It ultimately came down to copying and pasting the example from Google very carefully, reading about sending messages from Javascript to Shiny, putting the ideas together, and getting lucky on the 200th attempt...

To Give Credit Where It's Due:

Google's link: https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete

RStudio's Link: https://shiny.rstudio.com/articles/js-send-message.html

My potential solution:

library(shiny)
library(googleway)

key <- "MyKey"
set_key(key = key)
#google_keys()

ui <- shiny::basicPage(

  div(
    textInput(inputId = "my_address", label = "Type An Address")    
    ,textOutput(outputId = "full_address")
    ,HTML(paste0(" <script> 
                function initAutocomplete() {

                 var autocomplete =   new google.maps.places.Autocomplete(document.getElementById('my_address'),{types: ['geocode']});
                 autocomplete.setFields(['address_components', 'formatted_address',  'geometry', 'icon', 'name']);
                 autocomplete.addListener('place_changed', function() {
                 var place = autocomplete.getPlace();
                 if (!place.geometry) {
                 return;
                 }

                 var addressPretty = place.formatted_address;
                 var address = '';
                 if (place.address_components) {
                 address = [
                 (place.address_components[0] && place.address_components[0].short_name || ''),
                 (place.address_components[1] && place.address_components[1].short_name || ''),
                 (place.address_components[2] && place.address_components[2].short_name || ''),
                 (place.address_components[3] && place.address_components[3].short_name || ''),
                 (place.address_components[4] && place.address_components[4].short_name || ''),
                 (place.address_components[5] && place.address_components[5].short_name || ''),
                 (place.address_components[6] && place.address_components[6].short_name || ''),
                 (place.address_components[7] && place.address_components[7].short_name || '')
                 ].join(' ');
                 }
                 var address_number =''
                 address_number = [(place.address_components[0] && place.address_components[0].short_name || '')]
                 var coords = place.geometry.location;
                 //console.log(address);
                 Shiny.onInputChange('jsValue', address);
                 Shiny.onInputChange('jsValueAddressNumber', address_number);
                 Shiny.onInputChange('jsValuePretty', addressPretty);
                 Shiny.onInputChange('jsValueCoords', coords);});}
                 </script> 
                 <script src='https://maps.googleapis.com/maps/api/js?key=", key,"&libraries=places&callback=initAutocomplete' async defer></script>"))
    ,google_mapOutput(outputId = "my_map")
    )

    )

server <- function(input, output) {

  my_address <- reactive({
    if(!is.null(input$jsValueAddressNumber)){
      if(length(grep(pattern = input$jsValueAddressNumber, x = input$jsValuePretty ))==0){
        final_address<- c(input$jsValueAddressNumber, input$jsValuePretty)
      } else{
        final_address<- input$jsValuePretty
      }
      final_address
    }
  })


  output$full_address <- renderText({
    if(!is.null(my_address())){
      my_address()
    }
  })

  output$my_map <- renderGoogle_map({
    my_address <- my_address()
    shiny::validate(
      need(my_address, "Address not available")
    )

    not_a_df <- google_geocode(address = my_address)
    my_coords <- geocode_coordinates(not_a_df)
    my_coords <- c(my_coords$lat[1], my_coords$lng[1])

    google_map(
      location = my_coords,
      zoom = 12,
      map_type_control = FALSE,
      zoom_control = FALSE,
      street_view_control = FALSE
    )
  })

}

shinyApp(ui, server)

I hope this helps. Cheers! --nate

nate
  • 1,172
  • 1
  • 11
  • 26
  • Does this also answer your [other question](https://stackoverflow.com/questions/54848254/shinydashboard-google-places-autocomplete-invalidvalueerror-not-an-instance-o) ? – SymbolixAU Feb 25 '19 at 21:22
  • No. This was my answer to @Vlad. I thought it was a great idea and wanted to put it into a `shinydashboard` I built. However, I encountered the `InvalidValueError` problem when I switched from a `shiny` app to a `shinydashboard`. – nate Feb 25 '19 at 21:27
  • oh I see. yeah it's strange you get the error in the dashboard then. – SymbolixAU Feb 25 '19 at 21:28
  • I have a partial solution. I'll add it to my question: https://stackoverflow.com/questions/54848254/shinydashboard-google-places-autocomplete-invalidvalueerror-not-an-instance-o – nate Feb 25 '19 at 21:40
  • Hi! Maybe someone still read this: Does this solution still work today? I tried to mimic it and my input window always gets grayed out after a few seconds and get the display "Opps! Something went wrong." in the textbox. I'm grateful for any hints! @nate – Alex_ Aug 28 '21 at 15:10