Pin Address Locations on Embedded Maps

Contents

Overview

This article uses Leaflet (External link) to convert street addresses into geographical locations. The locations are pinned onto maps that are embedded into records of Kintone Apps.

This example illustrates how the Leaflet library can be used to make a Lunch Map App on Kintone. The App keeps track of various restaurant locations around the office. Each record contains information regarding a restaurant. A map displaying the geographical location of the restaurant shows up when viewing the Record Details page. There is an option to add a reference point. In this example, the Kintone San Francisco office is the reference point.

Library Introductions

This example uses the following libraries

  • Leaflet (External link): an open-source JavaScript library for interactive maps.
  • LocationIQ (External link): a Maps & Geocoding platform provider.
  • OpenStreetMap (External link): a collaborative project to create a free editable map of the world.

Sample Image

The customization displays a map on the header of the Record Details page. The location placed in the Address field is pinned on the map.

A screenshot of the Record Details page displaying a map, with pins for a Korean diner.

The reference point is also pinned, and can be viewed if the address is close, or the map is zoomed out.

A screenshot of the Record Details page displaying a map, with pins for a Korean diner and Kintone USA.

Preparation

Create the Form

Create an App (External link) that includes the following fields.

Field Type Field Name Field Code
Text Place place
Text Address address

Set the Libraries

CSS

Set the following URL into the App’s JavaScript and CSS Customization settings (External link), under the Upload CSS File for PC option.

  • https://unpkg.com/leaflet@1.5.1/dist/leaflet.css
JavaScript

Set the following URLs into the App’s JavaScript and CSS Customization settings (External link), under the Upload JavaScript for PC option.

  • https://js.kintone.com/jquery/3.4.1/jquery.min.js
  • https://unpkg.com/leaflet@1.5.1/dist/leaflet.js

Generate a LocationIQ API Token

LocationIQ’s Forward Geocoding API (External link) is used to convert street addresses to geographical coordinates.

  1. Create an account on the LocationIQ Registration Page (External link)
  2. Navigate to LocationIQ Access Token Create page (External link) and create an Access Token. Follow the instructions on Location IQ’s docs (External link) for more details.

Sample Code

Type the following code into a text editor and save it as a JavaScript file. Upload it to the App’s JavaScript and CSS Customization settings (External link). Replace Insert_Access_Token_Here with LocationIQ’s access token.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
(function() {
  'use strict';

  function forwardGeocode(name, address, map) {
    // Insert LocationIQ Access Token Below
    var locationIQToken = 'Insert_Access_Token_Here'; // include 'pk.' heading as well
    var encodedAddress = encodeURI(address);
    var URL = 'https://us1.locationiq.com/v1/search.php?key=' + locationIQToken + '&q=' + encodedAddress + '&format=json';
    var method = 'GET';
    kintone.proxy(URL, method, {}, {}, function(response) {
      var response_array = JSON.parse(response);
      // restaurant_GPS contains the GPS coordinates of the restaurant in [Latitude, Longitude] format
      var restaurant_GPS = [parseFloat(response_array[0].lat), parseFloat(response_array[0].lon)];
      var restaurant = L.marker(restaurant_GPS).addTo(map);
      restaurant.bindPopup(name, {autoClose: false}).openPopup();
    });
  }

  kintone.events.on('app.record.detail.show', function(event) {
    // Kintone API
    var record = event.record; // Accesses the Record in the Kintone App
    var address = record.address.value; // Gets data from "address" text field from the Record
    var name = record.place.value; // Gets data from "place" text field from the Record
    var headerSpace = kintone.app.record.getHeaderMenuSpaceElement(); // Fetches the header space
    var mapSpace = document.createElement('div');
    mapSpace.style.height = '300px';
    mapSpace.setAttribute('id', 'kintone');
    headerSpace.appendChild(mapSpace);

    // Leaflet Library
    var referencePointGPS = [37.789808, -122.401767]; // [Latitude, Longitude] GPS coordinates for Kintone Office
    var kintoneMap = L.map('kintone').setView(referencePointGPS, 16);
    // Map Layer with Geographical Features
    var geo_layer = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png', {
      maxZoom: 18
    });
    // Map Layer with Labels
    var label_layer = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager_only_labels/{z}/{x}/{y}.png', {
      maxZoom: 18,
      zIndex: 10
    });
    geo_layer.addTo(kintoneMap);
    label_layer.addTo(kintoneMap);

    // Reference Point
    // Kintone San Francisco Office is pinned as a reference point
    var reference = L.marker(referencePointGPS).addTo(kintoneMap);
    reference.bindPopup('Kintone USA', {autoClose: false}).openPopup();

    forwardGeocode(name, address, kintoneMap);
  });
})();
caution
Attention

Caution: The order in which JavaScript and CSS are uploaded to an app matters. In this example, ensure that the jQuery and leaflet.js libraries are uploaded before the custom JavaScript file. You can change the order of uploads by clicking and dragging on the arrows for each item on the Upload JavaScript / CSS page.

The JavaScript and CSS Customization settings page should look like the following:

Customization Setting Page Screenshot

After saving the settings and clicking on Update App, navigate to the Record Details page. A map should be displayed in the header of the Record list.

A screenshot of the Record Details page displaying a map, with pins for a Boba shop and Kintone USA.

Code Explanation

kintone.events.on Event

1
kintone.events.on('app.record.detail.show', function(event) {...});

The event is triggered when the Record Details view is displayed. The event object is passed as an argument so that details of the record, such as the Address and Place values, can be accessed. For more information, refer to the Kintone Event Handling API article.

Header Space

1
var headerSpace = kintone.app.record.getHeaderMenuSpaceElement();

The getHeaderMenuSpaceElement method fetches the header space in the Record Details view as an element object.

1
2
3
4
var mapSpace = document.createElement('div');
mapSpace.style.height = '300px';
mapSpace.setAttribute('id', 'kintone');
headerSpace.appendChild(mapSpace);

A div element is created to contain the map, and is appended to the Header Menu Space Element. The style.height attribute is set to 300px, a length suitable for displaying the map.

Creating the Map View

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var kintoneMap = L.map('kintone').setView(referencePointGPS, 16);
// Map Layer with Geographical Features
var geo_layer = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png', {
  maxZoom: 18
});
// Map Layer with Labels
var label_layer = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager_only_labels/{z}/{x}/{y}.png', {
  maxZoom: 18,
  zIndex: 10
});
geo_layer.addTo(kintoneMap);
label_layer.addTo(kintoneMap);

The first line in this snippet instantiates a map and attaches it to the header space div by referencing its id ‘kintone’. setView(center, zoom, options) is a method that centers the map view around center, a set of geographical coordinates. A zoom level is specified in the second parameter. tileLayer(urlTemplate, options) adds raster tiles to the map, i.e. the actual geographical details of the place being mapped. This example uses basemaps (External link) from Carto.

The first layer adds generic geographical features such as rivers and roads, while the second adds labels. The maxZoom attribute restricts the maximum zoom-in level for the map view. The zIndex attribute controls the vertical stacking order of the two overlaying map layers. This ensures that labels will be superimposed above the generic geographical features.

Reference Point

1
2
3
4
var referencePointGPS = [37.789808, -122.401767]; // [Latitude, Longitude] GPS coordinates for Kintone Office
// ...
var reference = L.marker(referencePointGPS).addTo(kintoneMap);
reference.bindPopup('Kintone USA', {autoClose: false}).openPopup();

L.marker(LatLng) is used to place a pin at the location specified in the LatLng variable. In this example, the referencePointGPS variable is placed in the L.marker() while holding the geographic coordinates of Kintone USA.

bindPopup() specifies the label of the popup. The autoClose options ensure that multiple popups can appear simultaneously. openPopup() automatically displays the markers’ label by default.

To add a reference point, another variable is created with the value L.marker(geographic_coordinates_variable).addTo(kintoneMap). The geographic_coordinates_variable parameter is set as the GPS coordinates. Write the coordinates in the format of: [Latitude, Longitude] (e.g. [37.789808, -122.401767])

forwardGeocode(name, address, map) function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function forwardGeocode(name, address, map) {
  // Insert LocationIQ Access Token Below
  var locationIQToken = 'Insert_Access_Token_Here'; // include 'pk.' heading as well
  var encodedAddress = encodeURI(address);
  var URL = 'https://us1.locationiq.com/v1/search.php?key=' + locationIQToken + '&q=' + encodedAddress + '&format=json';
  var method = 'GET';
  kintone.proxy(URL, method, {}, {}, function(response) {
    var response_array = JSON.parse(response);
    // restaurant_GPS contains the GPS coordinates of the restaurant in [Latitude, Longitude] format
    var restaurant_GPS = [parseFloat(response_array[0].lat), parseFloat(response_array[0].lon)];
    var restaurant = L.marker(restaurant_GPS).addTo(map);
    restaurant.bindPopup(name, {autoClose: false}).openPopup();
  });
}

Leaflet requires addresses to be geocoded (converted to geographical coordinates) for processing. The LocationIQ API is required to accomplish this conversion. After UTF-8 encoding the address, kintone.proxy() is used to make a GET API call with the address included in the URL.

The kintone.proxy() response is returned as a string and JSON.parse() is used to convert it into an array of objects. LocationIQ returns the coordinates as strings and parseFloat() is used to convert them to floating-point numbers.

References

Licenses

Service & License Page License Type
LeafletJS (External link) 2-Clause BSD License (External link)
CARTO (formerly CartoDB) (External link) Apache License 2.0 (External link)
LocationIQ (External link) Creative Commons Attribution-ShareAlike 2.0 Generic (CC BY-SA 2.0) License (External link)
jQuery (External link) MIT License (External link)
OpenStreetMap (External link) Open Data Commons Open Database License (ODbL) (External link)