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.

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.
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!
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.
Thanks so much, Tom. Really appreciate it. 🙂