Geo-referenced IF With TADS3

Hello All,

I am working on a geo-referenced tour project that lets travelers see the various facets (wildlife, ecology, etc.) of a location as they walk a trail.

The overall goal is for the interpreter to automatically update the user’s session when their position (rounded to two decimal places) matches the coordinates listed as TADS3 rooms (probably defined as a ‘case statement’).

Here’s the pseudo code:

switch position
  case(lat = -122.65 && lon = 45.63)
    me.scriptedTravelTo(room_one);

  case(lat = -122.78 && lon = 45.72)
    me.scriptedTravelTo(room_two);
...

So far the project is going well but I’m having trouble passing regular GPS position updates to TADS3 for processing.

At this point I am using Ben Cressey’s excellent webscrptres extension [url]https://intfiction.org/t/invoking-js-directly-from-webui/4955/1] to run the JavaScript from a source file I created called ‘gps_daemon.t’.

Here’s what the GPS daemon file looks like (I’m using the DefineAction simply for testing, this will, of course, be a daemon in the production version):

#charset "utf-8"
#include <adv3.h>
#include <en_us.h>

DefineIAction(GPS);

VerbRule(GPS)
        'gps'
        :  GPSAction
        verbPhrase = 'script/scripting'
;

#define GPS(STR, ARGS...) JavaScript.eval( ## #@STR ##, ##ARGS ## )

modify GPSAction
  execAction() {
    GPS({
var options = {
  enableHighAccuracy: true,
  timeout: 5000,
  maximumAge: 0
};

function success(pos) {
  var crd = pos.coords;

  console.log('Your current position is:');
  console.log('Latitude : ' + crd.latitude);
  console.log('Longitude: ' + crd.longitude);
  console.log('More or less ' + crd.accuracy + ' meters.');
};
function error(err) {
  console.warn('ERROR(' + err.code + '): ' + err.message);
};

navigator.geolocation.watchPosition(success, error, options);
    });
  }
;

Sure enough, as soon as I type ‘GPS’ into the interpreter, the JavaScript launches with position updates to the JavaScript console every 5,000 milliseconds:

Your current position is:
Latitude : 44.53651190000001
Longitude: -122.90703389999999
More or less 1449513 meters.

(The ‘1449513 meters’ accuracy is as a result of the position getting taken by my IP address rather than an actual GPS fix)

There are, however, a couple of problems with this approach.

First, there seems to be no way that I can see to pass the position to TADS3 by any means as a) the ‘crd.latitude’ and ‘crd.longitude’ variables are local, not global for use in other functions.

Second, the webscrptres extension permits acting on returned variables (and at that I’m not sure if the extension permits returning an object, i.e. both lat an lon variables) only after the JavaScript portion of the source file completes. In practical terms, even if I could pass an object to a TADS3 function after this code the browser would repeatedly ask the client for permission to share their location.

Another alternative I’ve thought of, of course, is simply including the GPS JavaScript code as stand-alone include file (via main.htm) and processing it that way. Looking at util.js and friends did not immediately reveal how I might do this.

Overall, either using webscrptres or not (though I confess that I like the elegance of using this method), how can I have TADS3 monitor geolocation objects and act to move the interactor to the appropriate location accordingly?

I don’t know anything about how the TADS/JS integration works, but generally you’d make the variable global by defining it in the global scope:

var crd = {};

function success(pos) {
   crd = pos.coords;   // <-- no var keyword here
  // ...

If TADS specifically can’t or won’t access the global scope then that won’t help, but as said I don’t know how it works.

Are you sure? Browsers usually ask for permission only once per domain and remember the choice automatically. I don’t think it’s even technically possible to trigger a re-ask.

This is alright as far as it goes. Oddly, I found that I could not access the globally defined variable as I did just this. I really should avoid globals anyway.

Unfortunately, yes. Permissions are asked each function call, technically, not per domain. It appears to be per domain as most instances run once from their index.html’s ‘include script’ statement.

Here is a concept diagram of the system’s logic. This model runs completely in TADS within the webscriptres framework:

Here is my code to date. No matter what I do I cannot seem to get the contents of the ‘crd’ object into the callback function.

#charse t "utf-8"
#include <adv3.h>
#include <en_us.h>

DefineIAction(GPS);

VerbRule(GPS)
        'gps'
        :  GPSAction
        verbPhrase = 'script/scripting'
;

#define GPS(STR, ARGS...) JavaScript.eval( ## #@STR ##, ##ARGS ## )

modify GPSAction
  execAction() {
    GPS({
var options = {
  enableHighAccuracy: true,
  timeout: 5000,
  maximumAge: 0
};

function success(pos) {
  var crd = pos.coords;

  console.log('Your current position is:');
  console.log('Latitude : ' + crd.latitude);
  console.log('Longitude: ' + crd.longitude);
  console.log('More or less ' + crd.accuracy + ' meters.');
  report_position(crd);
};


function error(err) {
  console.warn('ERROR(' + err.code + '): ' + err.message);
};

report_position = function report_position(crd) {
  alert('Here is crd.coords: ' + crd);
  alert('Latitude : ' + crd.latitude);
  return crd.latitude;
};

navigator.geolocation.watchPosition(success, error, options);
    }, process_position);
  }
;

process_position(report_position) {

"Lat: <<report_position>>. ";
}

Do you want to send GPS coordinates repeatedly say every few seconds to the TADS interpreter in the background while it is waiting for user input, or you want to pass coordinates with every command user types, but not in the mean time?

The former. This way I can issue a scriptedMoveTo() when the GPS background daemon indicates that they are at one of a list of pre-defined locations.

Thanks for your reply!

Ok, I will look into it, but it will definitely take some time…

You will make a lot of people happy (desktop browser, full screen):

http://portfolio.cooper.stevenson.name/friends_wildlife_presentation/main.html

I’ll happily put your name in the credits if you like.

In your example above you have TADS function “process_position” and you try to call it from JavaScript. Given how similar the syntax of TADS and JavaScript is and how “magically” the JS code is written in the middle of TADS source using macros, it is so easy to loose orientation and to mix them together, but they are two different worlds, TADS runs on the web server and Javascript on the client browser. You need to explicitly transfer data from client to server over the network. Try the following:

[code]DefineIAction(GPS);

VerbRule(GPS)
‘gps’
: GPSAction
verbPhrase = ‘script/scripting’
;

#define JS(STR, ARGS…) JavaScript.eval( ## #@STR ##, ##ARGS ## )

modify GPSAction
execAction()
{
JS({

var options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};

function success(pos) {
serverRequest("/webui/gpsEvent?lat=" + pos.coords.latitude
+ ‘&lon=’ + pos.coords.longitude + ‘&acc=’ + pos.coords.accuracy);
};

function error(err) {
console.warn(‘ERROR(’ + err.code + '): ’ + err.message);
};

navigator.geolocation.watchPosition(success, error, options);

    });
}

;

gpsEvent: WebResource
vpath = ‘/webui/gpsEvent’

processRequest(req, query)
{
    /* Lets simulate interesting position change with random, act only in about 1/5 cases. */
    if(rand(5) == 0)
    {
        /* Do something... */
        "latitude = <<query['lat']>>, longitude = <<query['lon']>>, accuracy = <<query['acc']>> ";

        /* send a new prompt */
        commandWin.write('

> ');
commandWin.flushWin();

        /* set the UI state */
        commandWin.mode = 'inputLine';
        commandWin.isInputOpen = true;

        /* send the inputLine event to the client */
        commandWin.sendWinEvent('<inputLine/>');
    }

    sendAck(req);
}

;[/code]
While webscript extension is able to feed back results from javascript into TADS, it assumes that the return value is available once the js code finishes, so when the watchPosition callback fires it is too late. You can overcome this by sending the result from the callback, you just need to generate correct id. But then there is second problem, webscript deletes the callback once it is run, because it assumes it will be used only once and you need periodic updates. So in the end it is easier to create your own handling for the GPS events as is shown above.

I don’t have any problems regarding repetitive asking permission for coordinates by the browser. Do you?

Tomas, this worked, thank you!

I am taking today and the weekend to study this, but please take my appreciation. I hope, too, that this helps others.