Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

geolocation as trigger?

Hi everyone,

I've hunted around, but I don't think anyone's asked this before, so...

I'm imagining a story that'd be played on a mobile device. Some decisions, branches would go as per normal; others would require the player to be in a physical location to reveal new options (or indeed, new passages). For this to work, I'm imagining a javascript chunk in a passage that would call html5 geolocation api?

Would that work? Could that work?

Comments

  • I have not personally tried this but after briefly reading this documentation on MDN I don't see why it would not work.

    The code would go in your story's Story Javascript area (not in a passage) and would need to do at least the following:

    1. Check if geolocation exists and is turned on.
    Remember not everyone reading your story will be on a mobile device and/or have a mobile device that supports geolocation.

    2. Determine if their geolocation is one of the ones you are looking for and then update a Twine variable based on the answer, you can then use this variable to control the availability of selected passages / features.

    note: The Harlowe story format does not currently support a simple method for updating Twine variables using javascript code therefor I would suggest using either SugarCube or Snowman 2 instead.
  • Thanks! This was the kind of conceptual framework I was wrestling with. I will give this a try.
  • fantastic concept, @shawn ! I hope you get it working and people have lots of fun running around the place trying to discover new info.
  • Thanks @feliwebwork! The thought occurs that if I could get this to work with relative positioning, then the game could be played anywhere. Passages might trigger when the player had moved say 100 ft from the start, and the game could prompt 'what do you see' or 'do you see x, y, z' and go from there..

    One could build that kind of serendipity into the narrative (somehow), where the game played from (say) the local school as a starting point would be a completely different narrative experience than if the player started at the local park.

    Just a thought. Absolute positioning, and comparing passages against a list is probably more within my capabilities (potentially; I need to learn about variables etc!)
  • shawn wrote: »
    The thought occurs that if I could get this to work with relative positioning, then the game could be played anywhere. Passages might trigger when the player had moved say 100 ft from the start, and the game could prompt 'what do you see' or 'do you see x, y, z' and go from there..
    I can see the news headlines now. "Sweet, innocent young child walks off cliff playing evil Twine game!"

    And that's it for my social commentary.

    ;-)
  • edited May 2015
    ok, so, using twine 2.0, sugarcube format, in the 'edit story javascript', this snippet is supposed to watch the user's geolocation, and pass the coordinates to a variable called Location - erm, at least, I think that's what it ought to do. It does not. Anyway -
    prerender.findLocation = 
      function getLocation() 
    	{
        var watchID = navigator.geolocation.watchPosition(function(position) 
    		{
      	state.active.variables["Location"]=position.coords.latitude, position.coords.longitude;
    		}
    	}
    
    

    then, I'd have one super passage, where I'd have an if statement, comparing $Location against my list of points of interest (ok, so an array?). If there's a match, the correct passage would display...?
  • note: there is a syntax error in your code, it is missing a close bracket and a couple of semi-colons
    prerender.findLocation = function getLocation() {
    	var watchID = navigator.geolocation.watchPosition(function(position) {
    		state.active.variables["Location"]=position.coords.latitude, position.coords.longitude;
    	});
    };
    

    I can't test your code because I don't use a smartphone but I noticed a couple of things.

    1. Registering a watchPosition callback:

    I believe SugarCube's prerender event happens every time a Reader changes from one passage to another, so therefor your code is registering a new watchPosition callback over and over again.

    Something like the following should work but you really need to add extra code checking for things like if geolocation exists and any other possible thing that could go wrong:
    var watchID = navigator.geolocation.watchPosition(function(position) {
    	state.active.variables["Location"]=position.coords.latitude, position.coords.longitude;
    });
    

    2. Unless you are planing on using a background thread/worker then even though the $Location variable will be updated in semi-real time the Reader is not going to see the result of their location change until they move from one passage to another.

    If this is the case then I suggest not using a watchPosition and using getCurrentPosition instead, either at the time when a decision needs to be made of showing a geolocation passage or not, or by giving the Reader a button to press when the they have changed locations.
  • edited May 2015
    greyelf wrote: »
    I believe SugarCube's prerender event happens every time a Reader changes from one passage to another, so therefor your code is registering a new watchPosition callback over and over again.
    Correct. All of the task object callbacks are called (at various points) when passage navigation happens and, yes, that means that the code is registering a new watchPosition() success callback each time passage navigation occurs. Worse, it overwrites the ID variable each time, meaning that none of the watchPosition() success callbacks, save the very last, can be cancelled.

    Beyond that, the code is probably still incorrect as it assigns only the latitude to $Location. I assume that you, @shawn, were trying to assign both the latitude and longitude.

    I'll also echo @greyelf and suggest that using watchPosition() is likely overkill for what you seem to want to do. So, my suggestion would be something like the following (goes in the Story JavaScript):
    (function () {
    	if ("geolocation" in navigator && typeof navigator.geolocation.getCurrentPosition === "function") {
    		// setup the success and error callbacks as well as the options object
    		var	positionSuccess = function (position) {
    				// you could simply assign the `coords` object to `$Location`,
    				// however, this assigns only the latitude and longitude since
    				// that seems to have been what you were attempting to do before
    				state.active.variables["Location"] = {
    					latitude  : position.coords.latitude,
    					longitude : position.coords.longitude
    				};
    				// access would be like: $Location.latitude and $Location.longitude
    			},
    			positionError   = function (error) {
    				/* currently a no-op; code that handles errors */
    			},
    			positionOptions = {
    				maximumAge : 60000 // (in ms) cached results may not be older than 1 minute
    				                   // this can probably be tweaked upwards a bit
    			};
    
    		// since the API is asynchronous, we give `$Location` an initial value, so
    		// trying to access it immediately causes no issues if the first callback
    		// takes a while
    		state.active.variables["Location"] = { latitude : 0, longitude : 0 };
    
    		// make an initial call for a position while the system is still starting
    		// up, so we can get real data ASAP (probably not strictly necessary as the
    		// first call via the `predisplay` task [below] should happen soon enough)
    		navigator.geolocation.getCurrentPosition(
    			positionSuccess,
    			positionError,
    			positionOptions
    		);
    
    		// register a `predisplay` task which attempts to update the `$Location`
    		// variable whenever passage navigation occurs
    		predisplay["geoGetCurrentPosition"] = function () {
    			navigator.geolocation.getCurrentPosition(
    				positionSuccess,
    				positionError,
    				positionOptions
    			);
    		};
    	} else {
    		/* currently a no-op; code that handles a missing/disabled geolocation API */
    	}
    }());
    
  • edited May 2015
    Thank you @greyelf and @TheMadExile! This is brilliant - I'm learning a lot.

    UGuPH9B.png


    So now I just need to check the location, and depending on results, update the passage accordingly. I can do that!
  • edited May 2015
    Hot damn.

    So, I put this in a passage:
    <<if $Location.latitude eq 43.653226>><<display "Downtown Toronto">>
    <</if>>
    

    and create a passage called "Downtown Toronto". I play through, and I get the kind of response I'm looking for. Of course, I'm only looking for the latitude here. Guess I'd need some sort of AND statement to get both. Anyway-

    FYI, this is my storyboard:

    bzNgmUg.png

    So now, I need to build in a bit of tolerance on the location trigger (we don't need to get right down to the metre) and perhaps, rather than absolute positioning, it'd make sense to go for relative positioning. Although absolute vs relative would depend on what one actually wants the geotrigger for.

    And of course, more locations. It might be ugly, but a series of if-then statements will do the trick. I expect there are more elegant ways, some sort of of look-up table.
  • edited May 2015
    Damn. Loaded the html onto my device to test, and it's not grabbing the geolocation. Chrome browser, Android 4.2.2, Galaxy tab 2.

    But getting closer, closer...

    anyway, here's the html, which those interested can open in the twine 2 import

    https://dl.dropboxusercontent.com/u/37716296/geolocate5.html
  • Thanks so much for sharing, @Shawn. Looking forward to testing / playing with it.
  • I don't suppose anyone knows if something along the lines of pattern matching can be used?

    ie, if regex were possible, something like <<if $Location.latitude eq 43.6[0-9]\w>>

    so that the user doesn't have to be pin-point on the spot in order to hit the trigger?

    Perhaps there is a kludge.
  • Ok, I can hard-code a buffer for a single point like this:
    <<set $buffer = 0.5>>
    <<set $Torontolat = 43.653226>>
    <<set $Torontolong = -79.3831843>>
    <<if $Location.latitude lte ($Torontolat + $buffer) and $Location.latitude gte ($Torontolat - $buffer) and $Location.longitude lte ($Torontolong + $buffer) and $Location.longitude gte ($Torontolong - $buffer)>><<display "Downtown Toronto">>
    <<else>><<display "I don't know anything about where you are">>
    <</if>>
    

    Ugly, but does the trick.
  • edited May 2015
    Something like the following should work (goes in Story JavaScript):
    (function () {
    	window.approxEqual = function (a, b, allowedDiff) { // allowedDiff must always be > 0
    		if (a === b) { // handles various "exact" edge cases
    			return true;
    		}
    		allowedDiff = allowedDiff || 0.5;
    		return Math.abs(a - b) < allowedDiff;
    	};
    }());
    
    The default allowed difference is 0.5 (as used in your previous example). You may specify a different one by passing it in as the third parameter.

    Usage with default allowed difference (0.5):
    <<if approxEqual($Location.latitude, $Torontolat) and approxEqual($Location.longitude, $Torontolong)>><<display "Downtown Toronto">>
    <<else>><<display "I don't know anything about where you are">>
    <</if>>
    
    Usage with specified allowed difference:
    <<if approxEqual($Location.latitude, $Torontolat, 0.05) and approxEqual($Location.longitude, $Torontolong, 0.05)>><<display "Downtown Toronto">>
    <<else>><<display "I don't know anything about where you are">>
    <</if>>
    
  • edited May 2015
    OH NO

    I'm sorry - folks had great answers to my note above, and I don't know what I did, but I've deleted their answer.

    I'm sorry - I'm such a dufus. Would you mind reposting?


    I remain a dufus.
  • edited May 2015
    Hi folks,

    I wrote up this process on my blog at http://electricarchaeology.ca/2015/05/20/low-friction-augmented-reality/ and the link to my story file is at the end. You can download 'The Ottawa Anomaly' there, open it in the Twine 2 editor to see how I put this together.

    It's not elegant - no doubt there's better ways of adding & managing points of interest - and the narrative leaves a bit to be desired, but ideally one of you will take a crack at making this all work better!

    I'd like to think that geotriggers opens a new vista for twineworks.

    Thanks again for all your help,
    Shawn
  • Brilliant!
  • Geolocation tends to be based on areas not single points. Squares and rectangles are pretty simple:
    <<if (($lat gt low) and ($lat lt high)) and (($long gt low) and ($long lt high))>>
    

    Circles aren't much harder:
    <<set $dlat to center.lat - $lat>>
    <<set $dlong to center.long - $long>>
    <<set $dist2 to $dlat * $dlat + $dlong * $dlong>>
    <<if $dist2 lt 100>>
    

    The above does a 10 meter circle ($dist2 is the square of your distance from the center of it).

    Values without $ need replacing with constants.
  • So I've been trying to work with this code, but I get constant errors about objects not existing such as state and predisplay. Is this due to the code being outdated? I'm new to Twine, and thus have no concept of what has changed, and to what. Could anyone help with this?
  • Sheepyhead wrote: »
    So I've been trying to work with this code, but I get constant errors about objects not existing such as state and predisplay. Is this due to the code being outdated? I'm new to Twine, and thus have no concept of what has changed, and to what. Could anyone help with this?
    The Twine family (Twine 1/2, Twee, TweeGo, Twee2, etc.) are compilers—in some cases IDEs. The engines that authors' projects are built into are the story formats and they are what matters in regards to what features are available to authors. Assuming you are using Twine 2, the built-in story formats are: Harlowe, Snowman, and SugarCube (v1).

    You are likely using the default story format for Twine 2, which is Harlowe. The code given above will only function as-is in SugarCube (v1 or v2). Adapting it to Harlowe probably wouldn't be too difficult, but it would have to be adapted. And, for the sake of completeness, adapting it to Snowman should be fairly trivial.
  • Sheepyhead wrote: »
    So I've been trying to work with this code, but I get constant errors about objects not existing such as state and predisplay. Is this due to the code being outdated? I'm new to Twine, and thus have no concept of what has changed, and to what. Could anyone help with this?

    I've created a game using this code (http://blog.patientrock.com/guardian-blue-light-twine/) in Sugarcube 2 and it took a bit of tweaking to get it running. You'll have to spend some time learning what his code does and see where the issue is. It's been a couple months for me so I don't remember exactly-- I think my issue was that it wasn't refreshing the player's coordinates fast enough. Dig around his various projects and take a look at the code he provides: https://electricarchaeology.ca/2015/05/20/low-friction-augmented-reality/

Sign In or Register to comment.