Server Import UI

Server type detection works exactly the same as file type detection. Let’s add a detection function for a Tileserver /index.json response and register it.

src/plugin/tileserver/mime.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
goog.declareModuleId('plugin.tileserver.mime');

import {register} from 'opensphere/src/os/file/mime.js';
import {TYPE} from 'opensphere/src/os/file/mime/json.js';
import {ID} from './index.js';

const Promise = goog.require('goog.Promise');
const {default: OSFile} = goog.requireType('os.file.File');


/**
 * @param {ArrayBuffer} buffer
 * @param {OSFile} file
 * @param {*=} opt_context
 * @return {!Promise<*|undefined>}
 */
export const detect = function(buffer, file, opt_context) {
  var retVal;

  // the parent type (JSON) gives the context as the parsed JSON
  // (so far)

  if (opt_context && Array.isArray(opt_context) && opt_context.length && 'tilejson' in opt_context[0]) {
    retVal = opt_context;
  }

  return Promise.resolve(retVal);
};

register(
    // for providers, this must be the same as the ProviderEntry ID
    ID,
    detect,
    // the priority; lower numbers run earlier
    0,
    // the parent type
    TYPE);

Let’s test that.

test/plugin/tileserver/mime.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
goog.require('os.file.File');
goog.require('os.file.mime');
goog.require('os.file.mime.mock');
goog.require('plugin.tileserver.mime');

describe('plugin.tileserver.mime', function() {
  const {default: OSFile} = goog.module.get('os.file.File');
  const {getTypeChain} = goog.module.get('os.file.mime');
  const {testNo, testYes} = goog.module.get('os.file.mime.mock');
  const {ID} = goog.module.get('plugin.tileserver');

  it('should detect tileserver responses', function() {
    var json = '  [{"format":"png","tilejson":"2.0.0"}] ';
    var buffer = new TextEncoder().encode(json);

    var file = new OSFile();
    file.setUrl('http://something.com/index.json');

    var testFunc = testYes(ID);
    testFunc(buffer, file);
  });

  it('should not detect other JSON', function() {
    var json = '  {type: "FeatureCollection"} ';
    var buffer = new TextEncoder().encode(json);

    var file = new OSFile();
    file.setUrl('http://something.com/other.json');

    var testFunc = testNo(ID);
    testFunc(buffer, file);
  });

  it('should register itself with mime detection', function() {
    var chain = getTypeChain(ID).join(', ');
    expect(chain).toBe('application/octet-stream, text/plain, text/xml, ' + ID);
  });
});

Now have the plugin import it.

src/plugin/tileserver/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
40
41
42
43
44
goog.declareModuleId('plugin.tileserver.TileserverPlugin');

import './mime.js';

import ConfigDescriptor from 'opensphere/src/os/data/configdescriptor.js';
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
    ));

    dm.registerDescriptorType(ID, ConfigDescriptor);
  }
}

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

Now we need to make an Angular directive so the user has a form to give the server a title and potentially modify the URL.

src/plugin/tileserver/tileserverimport.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
goog.declareModuleId('plugin.tileserver.TileserverImportUI');

import {ROOT} from 'opensphere/src/os/os.js';
import Module from 'opensphere/src/os/ui/module.js';
import SingleUrlProviderImportCtrl from 'opensphere/src/os/ui/singleurlproviderimport.js';

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


/**
 * The Tileserver import directive
 * @return {angular.Directive}
 */
export const directive = () => ({
  restrict: 'E',
  replace: true,
  templateUrl: ROOT + 'views/forms/singleurl.html',
  controller: Controller,
  controllerAs: 'ctrl'
});

/**
 * The element tag for the directive.
 * @type {string}
 */
export const directiveTag = 'tileserver';

/**
 * Add the directive to the module
 */
Module.directive(directiveTag, [directive]);

/**
 * Controller for the Tileserver server import dialog
 * @unrestricted
 */
export class Controller extends SingleUrlProviderImportCtrl {
  /**
   * Constructor.
   * @param {!angular.Scope} $scope
   * @param {!angular.JQLite} $element
   * @ngInject
   */
  constructor($scope, $element) {
    super($scope, $element);

    const file = /** @type {os.file.File} */ ($scope['config']['file']);
    $scope['config']['url'] = file ? file.getUrl() : this.getUrl();
    $scope['urlExample'] = 'https://www.example.com/somepath/index.json';
    $scope['config']['type'] = ID;
    $scope['config']['label'] = this.getLabel() || 'Tileserver';
  }

  /**
   * @inheritDoc
   */
  getDataProvider() {
    const dp = new Tileserver();
    dp.configure(this.scope['config']);
    return dp;
  }

  /**
   * @inheritDoc
   */
  getUrl() {
    if (this.dp) {
      const url = /** @type {Tileserver} */ (this.dp).getUrl();
      return url || '';
    }

    return '';
  }

  /**
   * @return {string}
   */
  getLabel() {
    if (this.dp) {
      const label = /** @type {Tileserver} */ (this.dp).getLabel();
      return label || '';
    }

    return '';
  }
}

Note that we do not have our own UI template. We are reusing one from OpenSphere and just overriding a couple of functions in the controller.

Now let’s hook that up in our plugin.

src/plugin/tileserver/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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
goog.declareModuleId('plugin.tileserver.TileserverPlugin');

import './mime.js';

import ConfigDescriptor from 'opensphere/src/os/data/configdescriptor.js';
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 ImportManager from 'opensphere/src/os/ui/im/importmanager.js';
import ProviderImportUI from 'opensphere/src/os/ui/providerimportui.js';

import Tileserver from './tileserver.js';
import {directiveTag as importUi} from './tileserverimport.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
    ));

    dm.registerDescriptorType(ID, ConfigDescriptor);

    const im = ImportManager.getInstance();
    // The argument to ProviderImportUI is the directive. A simple one like this will
    // be expanded under the hood to '<tileserver></tileserver>'. However, you can
    // define your own full markup and pass it in if you like.
    im.registerImportUI(ID, new ProviderImportUI(importUi));
  }
}

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

Save and run the build. Open the debug instance and go to Settings > Data Servers. You can now hit the little view icon on the Tileserver provider in that list. In addition, you can go to Add Data > Import File/URL and add the URL to the index.json. It will pop up a UI for you to edit the title and the URL, as shown below:

../../_images/addtileserver.png

Saving this should save a new persistent copy of the provider in Data Servers. Edit and Delete should also work as expected. Note that the user cannot edit or delete providers that are configured in settings.

The final step is getting the provider type to appear in the Data Servers > Add Server UI. This UI enumerates supported server types to allow importing a specific type.

First we’ll create a directive to display the import form without the additional window content.

src/plugin/tileserver/tileserverimport.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
104
105
106
107
108
goog.declareModuleId('plugin.tileserver.TileserverImportUI');

import {ROOT} from 'opensphere/src/os/os.js';
import Module from 'opensphere/src/os/ui/module.js';
import SingleUrlProviderImportCtrl from 'opensphere/src/os/ui/singleurlproviderimport.js';

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


/**
 * The Tileserver import directive
 * @return {angular.Directive}
 */
export const directive = () => ({
  restrict: 'E',
  replace: true,
  templateUrl: ROOT + 'views/forms/singleurl.html',
  controller: Controller,
  controllerAs: 'ctrl'
});

/**
 * The element tag for the directive.
 * @type {string}
 */
export const directiveTag = 'tileserver';

/**
 * Add the directive to the module
 */
Module.directive(directiveTag, [directive]);

/**
 * The Tileserver import directive
 * @return {angular.Directive}
 */
export const formDirective = () => {
  const directive = directive();
  directive.templateUrl = ROOT + 'views/forms/singleurlform.html';
  return directive;
};

/**
 * The element tag for the directive.
 * @type {string}
 */
export const formDirectiveTag = 'tileserverform';

/**
 * Add the directive to the module
 */
Module.directive(formDirectiveTag, [formDirective]);

/**
 * Controller for the Tileserver server import dialog
 * @unrestricted
 */
export class Controller extends SingleUrlProviderImportCtrl {
  /**
   * Constructor.
   * @param {!angular.Scope} $scope
   * @param {!angular.JQLite} $element
   * @ngInject
   */
  constructor($scope, $element) {
    super($scope, $element);

    const file = /** @type {os.file.File} */ ($scope['config']['file']);
    $scope['config']['url'] = file ? file.getUrl() : this.getUrl();
    $scope['urlExample'] = 'https://www.example.com/somepath/index.json';
    $scope['config']['type'] = ID;
    $scope['config']['label'] = this.getLabel() || 'Tileserver';
  }

  /**
   * @inheritDoc
   */
  getDataProvider() {
    const dp = new Tileserver();
    dp.configure(this.scope['config']);
    return dp;
  }

  /**
   * @inheritDoc
   */
  getUrl() {
    if (this.dp) {
      const url = /** @type {Tileserver} */ (this.dp).getUrl();
      return url || '';
    }

    return '';
  }

  /**
   * @return {string}
   */
  getLabel() {
    if (this.dp) {
      const label = /** @type {Tileserver} */ (this.dp).getLabel();
      return label || '';
    }

    return '';
  }
}

Then we’ll register the server type with the form UI.

src/plugin/tileserver/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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
goog.declareModuleId('plugin.tileserver.TileserverPlugin');

import './mime.js';

import ConfigDescriptor from 'opensphere/src/os/data/configdescriptor.js';
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 ImportManager from 'opensphere/src/os/ui/im/importmanager.js';
import ProviderImportUI from 'opensphere/src/os/ui/providerimportui.js';

import Tileserver from './tileserver.js';
import {directiveTag as importUi, formDirectiveTag as formUi} from './tileserverimport.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
    ));

    dm.registerDescriptorType(ID, ConfigDescriptor);

    const im = ImportManager.getInstance();
    // The argument to ProviderImportUI is the directive. A simple one like this will
    // be expanded under the hood to '<tileserver></tileserver>'. However, you can
    // define your own full markup and pass it in if you like.
    im.registerImportUI(ID, new ProviderImportUI(importUi));

    // Register the server type with the import manager so it will appear in the Add Server UI.
    im.registerServerType(ID, {
      type: ID,
      formUi,
      label: 'Tileserver'
    });
  }
}

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

Reload the debug application, then go to Settings > Data Servers > Add Server. You should now see a Tileserver option in the dropdown that will display the import UI.

../../_images/addserverui.png

That’s pretty much it for providers. If you want to connect to a provider using formats not already supported by OpenSphere, then follow the Parser and Layer Config sections of the File Type Plugin Guide.