Sample Sensors App: Windows 8* Compass and Inclinometer driving Google Street View* API using WinJS

Page created by Anna Crawford
 
CONTINUE READING
Sample Sensors App: Windows 8* Compass and Inclinometer driving Google Street View* API using WinJS
Sample Sensors App: Windows 8*
Compass and Inclinometer driving Google
Street View* API using WinJS
Introduction
MapPanningJS is an example Windows Store app written in WinJS using two sensors on Ultrabook™
devices to control Google's popular Street View API. The Compass class is used to control horizontal
panning (heading), and the Inclinometer class is used to control vertical tilt (pitch). The user can see
different views by pointing in different directions or looking up and down simply by physically
manipulating the Ultrabook.

Technical Overview
The Compass and Inclinometer classes are part of the Windows.Devices.Sensors namespace available in
Windows 8 via the WinRT API used by Windows Store apps. This namespace is available to all WinRT
language projections including C#, C/C++, and JavaScript*. This sample is written in the Windows Library
for JavaScript commonly referred to as WinJS. The sensors in our example send data to a full-screen
viewport rendering of street level pictures using Google Maps Street View API. The Street View API is a
web service freely provided by Google. If you are going to use Google Street View API in your business
applications, you will need an API key. Use the link below to find out more.

https://developers.google.com/maps/documentation/streetview/

Technical Details
The following pseudo-code describes building the sample app at a high level. See the supplied sample
code for concrete implementation details.

    1.   Initialize the Compass and Inclinometer
    2.   Initialize the Street View Google API viewport
    3.   Process ReadingChanged events for each sensor
    4.   Create a low-pass filter
    5.   Start a timer and communicate with the viewport

Initialize the Compass and Inclinometer
Both the Compass and Inclinometer are members of the Windows.Devices.Sensors namespace. Simply
assign a variable to the getDefault() for each sensor. The sensor will not generate readings until a
reportInterval is set. Luckily the sensor can provide the minimum reporting interval so we don't need to
guess.
default.js
 var compass = Windows.Devices.Sensors.Compass.getDefault();
 var inclinometer = Windows.Devices.Sensors.Inclinometer.getDefault();
 compass.reportInterval = compass.minimumReportInterval;
 inclinometer.reportInterval = inclinometer.minimumReportInterval;

Initialize the Street View Google API viewport
Google Street View Image API is a service that renders a viewport based on a number of parameters.
Using external APIs with WinJS can be tricky. Google Street View does not play nicely with WinJS out of
the box, but there is a simple way for them to co-exist and that is by using an iFrame to host the
viewport. To ensure that the Google API works properly it needs to run under the web context (as
opposed to local context) so our iFrame src attribute must be prefixed with the ms-appx-web schema.
The loading of Google API will happen in the iFrame while the sensor processing will happen in the host
app. We'll communicate between our app JS and the iFrame JS using HTML5 Web Messaging.
default.html
 
map.html
 
map.js
 var loc = new google.maps.LatLng(40.758692, -73.985341, true); //Times Square
 var pov = { heading: 0, pitch: 0, zoom: 0 };
 var streetViewOptions = {
         position: loc,
         pov: pov,
         linksControl: false,
         panControl: false,
         zoomControl: false,
         disableDefaultUI: true,
         clickToGo: false,
         addressControl: false,
         scrollwheel: false
     };
 var streetView = new google.maps.StreetViewPanorama(document.getElementById("map"),
 streetViewOptions);

Once initialized, the full screen viewport will render a panorama of Times Square, New York.
Process ReadingChanged events for each sensor
We have two options for getting readings from the sensors: build a "game loop" and periodically pole
the sensors by calling getCurrentReading() or respond to the sensors readingchanged event. We'll do the
latter and collect readings in arrays for later processing. When the readingchanged event fires it sends a
specific class as the argument depending on the sensor type. For the Compass it's the
CompassReadingChangedEventArgs, and for the Inclinometer we'll see
InclinometerReadingChangedEventArgs. Both of these classes contain a reading property of type
CompassReading or InclinometerReading, respectively. For the Compass we are interested in the
readings headingMagneticNorth property and for the Inclinometer we are interested in pitchDegrees.
We also want to record the timestamp in both cases.

default.js
 var headings = new Array();
 var pitches = new Array();

 // listen to sensor readingchanged events
 // record the reading and the timestamp
 compass.addEventListener("readingchanged", function (args) {
        headings[headings.length] = {
               reading: args.reading.headingMagneticNorth,
               timeStamp: args.reading.timestamp
        };
 });

 inclinometer.addEventListener("readingchanged", function (args) {
        pitches[pitches.length] = {
               reading: args.reading.pitchDegrees,
               timeStamp: args.reading.timestamp
        };
 });

Create a low-pass filter
Sensor data can be chatty and noisy. Physically moving the Ultrabook can fire sensors every dozen
milliseconds, and the magnitude of those readings can be surprisingly varied. To get a good reading that
reflects the intention of the user, we'll pass this array of readings through a low-pass filter. Filtering the
readings will reduce the noise and result in a less shaky experience with our viewport. There are many
implementations of low-pass filtering, and we'll use one of the simplest for this example. Our low-pass
filter takes two arguments: the array of reading/timestamp pairs and a smoothing factor. We arbitrarily
use a smoothing factor of 2 that can be modified to increase/decrease the smoothing. I'm not going to
explain the algorithm in detail, but I'll sum it up by saying it looks at the array and splits the difference
between readings accounting for time between samples thus removing exaggerations and outliers.
default.js
 function lowPassfilter(readings, smoothing) {
        var value = readings[0].reading;
        var lastUpdate = readings[0].timeStamp;

          for (var i  = 1; i < readings.length; ++i) {
                 var  now = readings[i].timeStamp;
                 var  currentValue = readings[i].reading;
                 var  elapsedTime = (now - lastUpdate) / 1000;
                 var  filteredValue = value + (currentValue - value) /
                                       (smoothing / elapsedTime);
                  readings[i].reading = filteredValue;
                  lastUpdate = now;
                  value = filteredValue;
             }
 }

Create a timer
Timers in JavaScript are easy to create using setInterval(). We’ll use a timer to send messages to the
viewport hosted in the iFrame. Once every second feels like a proper frame rate for a full-screen Google
Street View app. Sending orientation data to the viewport at a faster rate than it can generate the
images results in a loss of fidelity. You may see partially rendered views and/or image panels that don't
line up properly. Play around with frame rates until you find one that best suits your needs. During every
iteration of the timer, both reading arrays are processed through our filter, and the final result is sent on
to the iFrame using HTML Web Messaging. Inside the iFrame our message is picked up, and the new
orientation data is sent on to the Google API to render a new viewport.
default.js
 setInterval(function () {
             if (pitches.length > 0) {
                 lowPassfilter(pitches, 2);
                 var pitch = pitches[pitches.length - 1].reading;
                 if (settings.walking) {
                     if (pitch > 10) {
                         iframe.postMessage("walk", "*");
                     }
                 } else {
                     iframe.postMessage("pitch:" + pitch, "*");
                 }
             }
             if (headings.length > 0) {
                 lowPassfilter(headings, 2);
                 iframe.postMessage("heading:" + headings[headings.length -
 1].reading, "*");
             }
             pitches = new Array();
             headings = new Array();
         }, 1000);

map.js
 function receiveMessage(sender) {
        var opts = sender.data.split(":");
        switch (opts[0]) {
        case "heading":
               pov.heading = parseFloat(opts[1]);
               streetView.setPov(pov);
               break;
         case "pitch":
               pov.pitch = parseFloat(opts[1]);
               streetView.setPov(pov);
               break;
         }
 }

Performance / Results
On reference Ultrabook hardware, the sensor readingchanged events fire at 16-ms intervals. This rate is
much faster than the refresh rate of the viewport that is streaming image panels over the Internet from
Google’s API. Observations indicate the readingchanged events are also queued. Attempting to perform
viewport updates with each reading change has two undesirable effects: partial updates to the viewport
resulting in image misalignment and continued viewport updates after physical orientation changes
have stopped. Introducing a reading collection mechanism and passing readings through a low-pass
filter combined with matching the viewport update interval to the observed frame rate mitigated these
challenges.

Conclusion
Driving publically available APIs from Ultrabook sensors is straightforward in Windows 8. The Windows
Runtime (WinRT) provides easy access to the sensors via the Windows.Devices.Sensors namespace.
Notices
INFORMATION IN THIS DOCUMENT IS PROVIDED IN CONNECTION WITH INTEL PRODUCTS. NO LICENSE, EXPRESS
OR IMPLIED, BY ESTOPPEL OR OTHERWISE, TO ANY INTELLECTUAL PROPERTY RIGHTS IS GRANTED BY THIS
DOCUMENT. EXCEPT AS PROVIDED IN INTEL'S TERMS AND CONDITIONS OF SALE FOR SUCH PRODUCTS, INTEL
ASSUMES NO LIABILITY WHATSOEVER AND INTEL DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY, RELATING TO
SALE AND/OR USE OF INTEL PRODUCTS INCLUDING LIABILITY OR WARRANTIES RELATING TO FITNESS FOR A
PARTICULAR PURPOSE, MERCHANTABILITY, OR INFRINGEMENT OF ANY PATENT, COPYRIGHT OR OTHER
INTELLECTUAL PROPERTY RIGHT.

UNLESS OTHERWISE AGREED IN WRITING BY INTEL, THE INTEL PRODUCTS ARE NOT DESIGNED NOR INTENDED FOR
ANY APPLICATION IN WHICH THE FAILURE OF THE INTEL PRODUCT COULD CREATE A SITUATION WHERE PERSONAL
INJURY OR DEATH MAY OCCUR.

Intel may make changes to specifications and product descriptions at any time, without notice. Designers must not
rely on the absence or characteristics of any features or instructions marked "reserved" or "undefined." Intel
reserves these for future definition and shall have no responsibility whatsoever for conflicts or incompatibilities
arising from future changes to them. The information here is subject to change without notice. Do not finalize a
design with this information.

The products described in this document may contain design defects or errors known as errata which may cause
the product to deviate from published specifications. Current characterized errata are available on request.

Contact your local Intel sales office or your distributor to obtain the latest specifications and before placing your
product order.

Copies of documents which have an order number and are referenced in this document, or other Intel literature,
may be obtained by calling 1-800-548-4725, or go to: http://www.intel.com/design/literature.htm

Software and workloads used in performance tests may have been optimized for performance only on Intel
microprocessors. Performance tests, such as SYSmark* and MobileMark*, are measured using specific computer
systems, components, software, operations, and functions. Any change to any of those factors may cause the
results to vary. You should consult other information and performance tests to assist you in fully evaluating your
contemplated purchases, including the performance of that product when combined with other products.

Any software source code reprinted in this document is furnished under a software license and may only be used
or copied in accordance with the terms of that license.

Intel, the Intel logo, and Ultrabook are trademarks of Intel Corporation in the US and/or other countries.

Copyright © 2012 Intel Corporation. All rights reserved.

*Other names and brands may be claimed as the property of others.
You can also read