Data Provider

Here we will add a provider class that reads the /index.json file from Tileserver and produces layers from it.

A Provider is represented visually by the root nodes in the Add Data window (in its default view). Its goal is to query its server and produce leaf nodes containing Data Descriptors. These leaf nodes are represented visually by the nodes with on/off sliders in that same view, as shown below:

../../_images/serveradddata.png

When a layer is turned on, it appears as a node in the layers menu, as shown below:

../../_images/layernodes.png

While most descriptors activate layers, a custom descriptor could activate or launch anything.

First let’s just get the JSON loaded.

src/plugin/tileserver/tileserver.js
 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
53
54
55
56
57
58
59
60
61
goog.declareModuleId('plugin.tileserver.Tileserver');

import Request from 'opensphere/src/os/net/request.js';
import AbstractLoadingServer from 'opensphere/src/os/ui/server/abstractloadingserver.js';

import {ID} from './index.js';

const {default: IDataProvider} = goog.requireType('os.data.IDataProvider');


/**
 * The Tileserver provider
 * @implements {IDataProvider}
 */
export default class Tileserver extends AbstractLoadingServer {
  /**
   * Constructor.
   */
  constructor() {
    super();
    this.providerType = ID;
  }

  /**
   * @inheritDoc
   */
  load(opt_ping) {
    super.load(opt_pint);

    // load the JSON
    new Request(this.getUrl()).getPromise().
        then(this.onLoad, this.onError, this).
        thenCatch(this.onError, this);
  }

  /**
   * @param {string} response
   * @protected
   */
  onLoad(response) {
    let layers;

    try {
      layers = JSON.parse(response);
    } catch (e) {
      this.onError('Malformed JSON');
      return;
    }

    console.log('parsed json', layers);
  }

  /**
   * @param {*} e
   * @protected
   */
  onError(e) {
    const msg = Array.isArray(e) ? e.join(' ') : e.toString();
    this.setErrorMessage(msg);
  }
}

Here’s our test for it. Note that this uses the same Jasmine 1.3 that OpenSphere uses. Newer versions allow for async tests with promises. If you have an external plugin and would like to use a newer or different test library, that is up to you.

test/plugin/tileserver/tileserver.test.js
  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
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
goog.require('goog.Promise');
goog.require('os.net.Request');
goog.require('plugin.tileserver');
goog.require('plugin.tileserver.Tileserver');

describe('plugin.tileserver.Tileserver', function() {
  const Promise = goog.module.get('goog.Promise');
  const {default: Request} = goog.module.get('os.net.Request');

  const {ID} = goog.module.get('plugin.tileserver');
  const {default: Tileserver} = goog.module.get('plugin.tileserver.Tileserver');

  it('should configure properly', function() {
    var p = new Tileserver();
    var conf = {
      type: ID,
      label: 'Test Server',
      url: 'http://localhost/doesnotexist.json'
    };

    p.configure(conf);

    expect(p.getLabel()).toBe(conf.label);
    expect(p.getUrl()).toBe(conf.url);
  });

  it('should load valid JSON', function() {
    var p = new Tileserver();
    p.setUrl('/something');

    // we're going to spy on the getPromise method and return a promise resolving
    // to valid JSON
    spyOn(Request.prototype, 'getPromise').andReturn(Promise.resolve('[]'));

    spyOn(p, 'onLoad').andCallThrough();
    spyOn(p, 'onError').andCallThrough();

    runs(function() {
      p.load();
    });

    waitsFor(function() {
      return p.onLoad.calls.length;
    });

    runs(function() {
      expect(p.onLoad).toHaveBeenCalled();
      expect(p.onError).not.toHaveBeenCalled();
    });
  });

  it('should error on invalid JSON', function() {
    var p = new Tileserver();
    p.setUrl('/something');

    // we're going to spy on the getPromise method and return a promise resolving
    // to invalid JSON
    spyOn(Request.prototype, 'getPromise').andReturn(Promise.resolve('[wut'));

    spyOn(p, 'onLoad').andCallThrough();
    spyOn(p, 'onError').andCallThrough();

    runs(function() {
      p.load();
    });

    waitsFor(function() {
      return p.onLoad.calls.length;
    });

    runs(function() {
      expect(p.onLoad).toHaveBeenCalled();
      expect(p.onError).toHaveBeenCalled();
    });
  });

  it('should error on request error', function() {
    var p = new Tileserver();
    p.setUrl('/something');

    // we're going to spy on the getPromise method and return a promise rejecting
    // with errors
    spyOn(Request.prototype, 'getPromise').andReturn(
        // request rejects with arrays of all errors that occurred
        Promise.reject(['something awful happend']));

    spyOn(p, 'onLoad').andCallThrough();
    spyOn(p, 'onError').andCallThrough();

    runs(function() {
      p.load();
    });

    waitsFor(function() {
      return p.onError.calls.length;
    });

    runs(function() {
      expect(p.onLoad).not.toHaveBeenCalled();
      expect(p.onError).toHaveBeenCalled();
    });
  });
});

Run yarn test to see if it tests properly. Now we need to register our provider type in our plugin.

src/plugin/tileserer/tileserverplugin.js
 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
goog.declareModuleId('plugin.tileserver.TileserverPlugin');

import DataManager from 'opensphere/src/os/data/datamanager.js';
import ProviderEntry from 'opensphere/src/os/data/providerentry.js';
import AbstractPlugin from 'opensphere/src/os/plugin/abstractplugin.js';
import PluginManager from 'opensphere/src/os/plugin/pluginmanager.js';

import Tileserver from './tileserver.js';
import {ID} from './index.js';

/**
 * Provides Tileserver support
 */
export default class TileserverPlugin extends AbstractPlugin {
  /**
   * Constructor.
   */
  constructor() {
    super();
    this.id = ID;
    this.errorMessage = null;
  }

  /**
   * @inheritDoc
   */
  init() {
    const dm = DataManager.getInstance();
    dm.registerProviderType(new ProviderEntry(
        ID, // the type
        Tileserver, // the class
        'Tileserver', // the title
        'Tileserver layers' // the description
    ));
  }
}

// add the plugin to the application
PluginManager.getInstance().addPlugin(new TileserverPlugin());

Lastly, we need to update our config so that the application instantiates a copy of our provider.

config/settings.json
{
  "admin": {
    "providers": {
      "tileserver-example": {
        "type": "tileserver",
        "label": "Tileserver Example",
        "url": "http://localhost:8081/index.json"
      }
    }
  }
}

Run the build in OpenSphere and open the debug instance of the application. You should see a copy of the provider in both the Add Data window and in Settings > Data Servers and you can see our console.log statement in the Javascript console. However, our server is just spinning and is not processing that JSON yet. Let’s fix that next.