I used the new technique of REST interfaces to provide a new geocoding add-in. At its core is the call to the respective REST interface of MapQuest, a company providing lots of routing services and applications.
One of MapQuest’s services is routing. For every pair of addresses provided, MapQuest sends back the latitude and longitude of each location, as well as administrative information such as country and state. It also supplies the locations of all waypoints between the two points and where to change direction to travel from the first address to the second. Summary information such as total distance and estimated traveling time is also provided. If the function is called with just two addresses, it returns the route and/or distance between them. If you provide a list of addresses, then routes will be calculated for each pair of consecutive list elements. Connections will then be given between element 1 and element 2, between element 2 and element 3 and so on. A list of n locations will produce n-1 routes.
Since data tables are the usual way to collect information in JMP, analysis starts with the user assigning roles to the columns of the data table via a user dialog. The Geocoding 2020 add-in works the same way. After the user has completed the dialog, the program collects the relevant data from the data table, translates it into the structure needed by the REST API and then sends it to MapQuest. So, the rough structure of the program is: 1) complete the user dialog, 2) collect data, 3) prepare data for interface, 4) send interface request, 5) interpret results, 6) write JMP data table(s). The third step, data preparation, is defined as a function and is available as MapQuest_route.jsl in the JSL Cookbook. You can embed it in your own geo application or use it to get ideas of how to structure data for REST calls.
Baseline functionality
You, or any user of your program needs a vlid API-key from MapQuest.
JMP data comes in form of data table columns, while REST interfaces most likely use JSON structures. As a result, there needs to be functionality that translates between both formats. The JSL also has structures for data collection, either lists or associative arrays. The purpose of this function is to encapsulate all non-native JSL commands. The necessary data is provided with the typical JSL variables in character, numeric or list formats. The data that is returned from the call comes as JSL lists as well. So, embedding this function in a JSL script doesn’t require any additional knowledge beyond JSL standards.
The interface
JSL functions are called with a statement like:
result = function();
If the function needs or accepts parameters, they are listed in the brackets. This function, GC_route, can be called with empty brackets or with a full list of parameters. Calling it with empty brackets invokes a parameter definition window with an outline for input and output, as shown in the following sections:
![bernd_heinen_0-1596909166655.png bernd_heinen_0-1596909166655.png](https://community.jmp.com/t5/image/serverpage/image-id/26113i224B05915CE37C88/image-dimensions/835x600?v=v2)
Parameters in a function call are always positional parameters. When the function is called with parameters, ALL mandatory parameters need to be supplied, but they may be empty.
In my blog post about my geocoding add-in, I used a few of the local tourist sites around my hometown as examples of how to use the add-in. Let’s take two of those to see how this interface works:
|
Alte Freiheit 24a, 42103 Wuppertal, Deutschland
|
Wuppertaler Schwebebahn, a 13-km- long suspension railway, the oldest electric elevated railway with hanging cars in the world, inaugurated 1901.
|
|
Schloßplatz 2, 42659 Solingen, Deutschland
|
Schloss Burg, a reconstructed castle, originating from the 12th century.
|
First, you need an access key from MapQuest, let’s assume it is 1234ABCD. Also, assume that:
- Every address is just one string of text.
- You have provided a pair of address strings; no data table is involved.
- You want to travel by car.
- You’re only interested in distances and travel times.
- You want the distances in miles.
One way of using this interface is to take the address strings as they are. In this case, the format indicator (Position 2) needs to be 1 or larger. Then, the function call looks like this:
routes = GC_route ("1234ABCD", "", 1, "fastest", "m", {"Alte Freiheit 24a, 42103 Wuppertal, Deutschland",
"Schloßplatz 2, 42659 Solingen, Deutschland"}, {}, {}, {}, {}, {}, {});
If the addresses are not in one string, but in separate elements, the format indicator needs to be 0 (zero), meaning your call would look like:
routes = GC_route ("1234ABCD", "", 0, "fastest", "m", {}, {"Deutschland", "Deutschland"}, {},
{"Wuppertal", "Solingen"}, {42103, 42659}, {"Alte Freiheit 24a", "Schloßplatz 2"}, {})
Of course, you don’t need to put the values in as parameters; you can also supply a variable, e.g., a list variable with the list of country names.
![bernd_heinen_3-1596909166707.png bernd_heinen_3-1596909166707.png](https://community.jmp.com/t5/image/serverpage/image-id/26114i76233D7340EB14D5/image-dimensions/835x711?v=v2)
In any case, the result is the same list of data:
{200, "HTTP/1.1 200 OK", "", {}, {""}, {51.256414, 51.137675}, {7.148503, 7.152488}, {"close", "exact"},{"DE", "DE"},
{"North Rhine-Westphalia", "North Rhine-Westphalia"}, {"Wuppertal", "Solingen"}, {"42103", "42659"},
{"Elberfeld", "Unterburg"}, {"Alte Freiheit", "Schloßplatz 2"}, {., 16.002}, {., 1600}, {}, {}, {0, 1}, [=>], }
Both locations are found, and the distance and traveling time are calculated. Therefore, parameter five is a list with an empty string. At the end, there is the symbol for an empty associative array ([=>]). It is empty because parameter 13, the detail flag, was set to zero. Otherwise, the routing information could be found in this array.
A minor change in the parameter list of the call causes a significant change in the output. That is the change of parameter 13, the “detail” flag. If it is set to 1, the output is extended by an array that holds all the routing information:
{200, "HTTP/1.1 200 OK", "", {}, {""}, {51.20971, 51.21327}, {7.07255, 7.08376},{"exact", "exact"}, {"DE", "DE"},
{"Nordrhein-Westfalen", "Nordrhein-Westfalen"}, {"", ""}, {"42653", "42653"}, {"Solingen", "Solingen"},
{"Klosterhof 4", "Lützowstraße 347"}, {., 1.0767}, {., 965}, {}, {}, {0, 1},
["gc#city" => {"Solingen", ., ., ., ., ., ., ., ., "Solingen"},
"gc#coordtype" => {"Point of Interest", "Waypoint", "Waypoint", "Waypoint", "Waypoint", "Waypoint", "Waypoint", "Waypoint", "Waypoint", "lPoint of Interest"},
"gc#ctrycd" => {"DE", ., ., ., ., ., ., ., ., "DE"},
"gc#dist" => {., 0.0064, 0.0499, 0.1481, 0.0853, 0.2366, 0.2993, 0.2478, 0.0032, 1.0767},
"gc#district" => {"", ., ., ., ., ., ., ., ., ""},
"gc#lat" => {51.20971, 51.2099, 51.209949, 51.210171, 51.209187, 51.209667, 51.210789, 51.212135,51.213226, 51.21327},
"gc#leg" => {0, 1, 1, 1, 1, 1, 1, 1, 1},
"gc#long" => {7.07255, 7.072152, 7.072176, 7.072798, 7.074032, 7.074655, 7.077532, 7.081177, 7.084009, 7.08376},
"gc#msg" => {"", ., ., ., ., ., ., ., .},
"gc#postalc" => {"42653", ., ., ., ., ., ., ., ., "42653"},
"gc#ql" => {"exact", ., ., ., ., ., ., ., ., "exact"},
"gc#resp" => {0, ., ., ., ., ., ., ., .},
"gc#state" => {"Nordrhein-Westfalen", ., ., ., ., ., ., ., ., "Nordrhein-Westfalen"},
"gc#street" => {"Klosterhof 4", "Klosterhof", ., "Gerberstraße", ., "Gräfrather Heide", .,
"Gräfrather Heide", "Lützowstraße", "Lützowstraße 347"},
"gc#time" => {., "00:00:05", "00:00:45", "00:02:13", "00:01:17", "00:03:32", "00:04:28", "00:03:42", "00:00:03", "00:16:05"},
"gc#wpidx" => {0, 1, 2, 3, 4, 5, 6, 7, 8, 0}], }
For simplicity’s sake, this example uses a route between two places that are close to one other. The route is composed of eight waypoints between the start and end point. They are enumerated in the last list of the associative array with the key “gc#wpidx”. Some information is only available for the start and end positions, which is why some lists have so many missing values. Every list has the same number of elements so that the whole array can easily be turned into a data table.
Technical details
The function takes a pair of addresses or a list of addresses as input. MapQuest sends back location and geoinformation about the endpoints of each journey, each location’s coordinates, the directions for each point between them, and where a change in direction must take place. For this simple task, MapQuest offers different input formats. The address information can be supplied as one text string with comma separated parts, like “Deutschland, Schloßplatz 2, 42659 Solingen”, which is the address of a famous castle near my hometown. Alternatively, the address can be given as single elements: country = “Deutschland”, Street = “Schloßplatz 2”, city = “Solingen”, postal code = “42659”.
I assume that, in most cases, more than one address will be looked up. So, the function expects to get address information as lists, either a list of strings with complete addresses or several lists with address elements. Each address, regardless of its format, needs to provide its country. And there is no check if the required minimum information is given. All other elements are optional.
If detailed routing is requested, an array with lots of lists is returned. The first element of each list belongs to the first location, subsequent elements to the route, then again one element for the second location and so on. In order to distinguish between the locations that have been input to the request and the routing points that have come back, the gc#coordtype names the original locations “Point of Interest” and the points in between “Waypoints.” The gc#leg list glues together all points that lead to a destination. The first location does not belong to a leg (my definition). For the route to and including the first destination, it is one; for the route to the second destination, it is two, and so on.
If a JSL variable with a reference to a data table is supplied for parameter 2 and detailed routing is requested, then it is assumed that the list of locations is identical to the list of locations in the data table. All content of the referenced data table will be copied and inserted into the array with the detailed routing. This information can only be linked to the points of interest, since all lists have missing values for the waypoints.
If a route can’t be found, an error message is given at the position that corresponds to the point of origin for that leg. Subsequent routes will be calculated, if possible.
If you provide a link to a data table and that data table has excluded rows, then you must supply a list of excluded rows (
excludedrows = As List( dt << Get Excluded Rows )
); as parameter 12. Otherwise, lists will get out of sync. In the message list, these observations are marked as “excluded”. If there are more “excluded” observations than originally in the data table, these observations failed to provide a minimum of location information.
I hope this piece of code can help you with your project or just provide some insight into the application of JSL. I’m open to any questions or suggestions, but since it is part of the Geocoding 2020 add-in, I will only make changes that are consistent with its intended use in that add-in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.