Gravity Forms, Open Street Map API, and Leaflet JS

I’m sketching this one out because it’s a fun mix of things. I’m going to use Gravity Forms to accept submissions, the Open Street Map API will convert the address into latitude/longitude, and then we’ll display the data on a Leaflet JS map. I could have done it all in Google but I was irritated by their credit card requirement.

Gravity Forms

I built a pretty typical form in Gravity Forms to collect some information. I used the standard address field and removed the more detailed street address pieces. That preserved privacy and made the searches more likely to work.
Gravity Forms address field with three of the address elements turned off and indicated by arrows.

Getting the Lat/Long from Open Street Map API

What I needed to do then was pass the address variables to the Open Street Map API to get lat/long coordinates. Since I was creating a post via Gravity Forms, I could use the gform_after_submission hook to use the form information to add info to the post we just made.

The function below is a bit long but it’s not fancy. It is called right after the form gets submitted.

Section 1 (see code comments) gets any/all of the address information from the form and formats it in the way that the API wants. I push it into an array because using PHP’s implode function is easier than checking for the field, appending it, and adding & for me. I can just let it happen automatically this way.

Section 2 gets the data from the API with the parameters we’re passing from the form. The URL ends up looking something like https://nominatim.openstreetmap.org/search?&city=Richmond&state=Virginia&country=united%20states%20of%20america&limit=1&format=json We’re limiting our responses to one and we’re asking for the JSON format.

You’ll also note that if the country is “United States” I replace it with “United States of America.” I don’t know why that gets better search responses but it does. It took me a while to figure that out. It was also easier to do this than figure out how to change it in the Gravity Forms list.

Section 3 takes the API data and writes it to the custom fields. You’ll notice I do that for the latitude, longitude, and the featured image URL.

function dlinq_mapit_get_latlng($entry, $form){
   $post_id = $entry['post_id'];//get the id of the post that was just created
   $args = array();
//SECTION 1: get the form address values if they exist and put them in an array
   if(rgar($entry, '2.3')){
      $city = 'city=' . rgar($entry, '2.3');  //these line up to the field ID and subfield    
      array_push($args, $city);
   }
   if(rgar($entry, '2.4')){
      $state = 'state=' . rgar($entry, '2.4');      
      array_push($args, $state);
   }
   if(rgar($entry, '2.5')){
      $postalcode = 'postalcode=' . rgar($entry, '2.5');      
      array_push($args, $postalcode);
   }
   
   if(rgar($entry, '2.6')){
      if (rgar($entry, '2.6') == "United States"){
         $clean_country = 'United States of America';
      } else {
         $clean_country = rgar($entry, '2.6');
      }
      $country = 'country=' . $clean_country;      
      array_push($args, $country);
   }   
   $addy = implode('&', $args);
   $clean = str_replace(' ', '%20', $addy);//replace spaces with %20 for URL kindness urlencode was too much 

   //SECTION 2: Get content from API   
   $resp = dlinq_mapit_fetch($clean);
   $json = json_decode($resp);

   $lat = $json[0]->lat;
   $lng = $json[0]->lon;

   //SECTION 3: Update the post custom fields
   update_post_meta( $post_id, 'lat', $lat, '' );
   update_post_meta( $post_id, 'lng', $lng, '' );
   $img_url = get_the_post_thumbnail_url( $post_id, 'medium' );
   update_post_meta( $post_id, 'f_img', $img_url, '');
}


//get our API data
function dlinq_mapit_fetch($clean){
   $url = "https://nominatim.openstreetmap.org/search?{$clean}&limit=1&format=json";
   $curl = curl_init($url);
   curl_setopt($curl, CURLOPT_URL, $url);
   curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

   $headers = array(
      "Accept: application/json",
   );
   curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

   curl_setopt($curl,CURLOPT_USERAGENT,'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13');//need a user agent for the API

   $resp = curl_exec($curl);
   curl_close($curl);
   return $resp;
}

Adding Custom Fields to the API

Since I have the title and content of the post in the API already, it seemed easiest just to make the custom fields visible as well. I have to write the code below for each custom field I want to expose in the rest API.

function lat_get_post_meta_cb($object, $field_name, $request){
        return get_post_meta($object['id'], $field_name, true); 
}
function lat_update_post_meta_cb($value, $object, $field_name){
  return update_post_meta($object['id'], $field_name, $value); 
}
add_action('rest_api_init', function(){
  register_rest_field('post', 'lat', 
    array(
    'get_callback' => 'lat_get_post_meta_cb', 
    'update_callback' => 'lat_update_post_meta_cb', 
    'schema' => null
    )
  ); 
});

Making the Map and Markers

We’ll make a template for the map.

<?php
/**
 * Template Name: Map Display
 *
 * Template for displaying a map.
 *
 * @package understrap
 */

get_header();

while ( have_posts() ) : the_post();
    echo "<div id='map'></div>";    
    
endwhile;

get_footer();?>

Now since this is a plugin, adding page templates is kind of a hassle. I found this solution after trying a few others that didn’t work. So now we have a page.


function wpse255804_add_page_template ($templates) {
    $templates['map-display.php'] = 'Map Display';
    return $templates;
    }
add_filter ('theme_page_templates', 'wpse255804_add_page_template');


function wpse255804_redirect_page_template ($template) {
    $post = get_post();
    $page_template = get_post_meta( $post->ID, '_wp_page_template', true );
    if ('map-display.php' == basename ($page_template))
        $template = WP_PLUGIN_DIR . '/dlinq_map_it/inc/map-display.php';
    return $template;
    }
add_filter ('page_template', 'wpse255804_redirect_page_template');

Now we move from PHP to javascript. This code gets the WordPress JSON and loops through it to write the markers to the map.


const wpJson = document.querySelector('link[rel="https://api.w.org/"]').href + 'wp/v2/posts?per_page=99';

fetch(wpJson).then(response => response.json()).then(data => markerMaker(data));

function markerMaker(data){
    data.forEach((item, index) => {
      const title = item.title.rendered;      
      const lat = item.lat;
      const long = item.lng;
      const marker = L.marker([lat, long]).addTo(map);
      let imgHtml = '';
      let bioHtml = '';
      if(item.f_img){
        const imgUrl = item.f_img;
        imgHtml = `<img src="${imgUrl}" alt="Picture of the home town for ${title}" width="150px" height="auto">`;
      }
      if(item.content.rendered){
        const content = item.content.rendered;
        bioHtml = `<div class="bio">${content}</div>`;
      }
      if (lat != '' && long != ''){
        marker.bindPopup(`<h2 class="popup-name">${title}</h2> ${imgHtml}${bioHtml}`);
      }
  });
}

We can enqueue the script for just the page template using the code below. I had to bump leaflet up to 1.8 due to an issue with clicking on markers in Safari.

add_action('wp_enqueue_scripts', 'dlinq_mapit_load_scripts');

function dlinq_mapit_load_scripts() {                           
    $deps = array('jquery');
    $version= '1.0'; 
    $in_footer = true; 
      if ( is_page( 'map-display' ) ) {
         wp_enqueue_script('leaflet', 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.8.0-beta.1/leaflet.js', '', '1', $in_footer);
          wp_enqueue_script('dlinq-map-js', plugin_dir_url( __FILE__) . 'js/dlinq-map.js', 'leaflet', $version, $in_footer);           
       }
    wp_enqueue_style( 'dlinq-map-css', plugin_dir_url( __FILE__) . 'css/dlinq-map.css');
    wp_enqueue_style('leaflet', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.css');
}

Now you end up with something like this.

See the Pen
BL embed example
by Tom (@twwoodward)
on CodePen.

Comments on this post

  1. Al said on June 22, 2022 at 4:02 am

    This is awesome work, Tom. Thanks for sharing.
    Any chance you could chuck the files you’ve used in a git, or package it up if it’s a plugin? Keen to have a fiddle with this, but uncertain where each bit of code has been put (functions.php / plugin folder / new files / somewhere else?) so can’t easily replicate it.

    Cheers!

    • Tom Woodward said on June 22, 2022 at 8:18 am

      For sure. I have it up as a plugin. The form fields are hard coded so I put an export of the form I used in the form directory for import. You’ll need to change the access token to work with your site.

      • Al said on June 22, 2022 at 5:46 pm

        Thanks so much, Tom. Really appreciate it. 🙂

Leave a Reply

Trackbacks and Pingbacks on this post

No trackbacks.

TrackBack URL