Models/GeoRssCatalogItem.js

"use strict";

/*global require*/
var clone = require("terriajs-cesium/Source/Core/clone").default;
var defined = require("terriajs-cesium/Source/Core/defined").default;
var RuntimeError = require("terriajs-cesium/Source/Core/RuntimeError").default;
var when = require("terriajs-cesium/Source/ThirdParty/when").default;
var GeoJsonCatalogItem = require("./GeoJsonCatalogItem");
var CatalogItem = require("./CatalogItem");
var inherit = require("../Core/inherit");
const loadXML = require("../Core/loadXML");
var Metadata = require("./Metadata");
var proxyCatalogItemUrl = require("./proxyCatalogItemUrl");
var geoRssConvertor = require("../Map/geoRssConvertor");
var geoRss2Convertor = geoRssConvertor.geoRss2ToGeoJson;
var geoRssAtomConvertor = geoRssConvertor.geoRssAtomToGeoJson;

/**
 * A {@link CatalogItem} representing GeoRSS data.
 *
 * @alias GeoRssCatalogItem
 * @constructor
 * @extends CatalogItem
 *
 * @param {Terria} terria The Terria instance.
 * @param {String} [url] The URL from which to retrieve the CZML data.
 */
function GeoRssCatalogItem(terria) {
  CatalogItem.call(this, terria);

  this._geoJsonItem = undefined;
}

inherit(CatalogItem, GeoRssCatalogItem);

Object.defineProperties(GeoRssCatalogItem.prototype, {
  /**
   * Returns true if we can zoom to this item. Depends on observable properties, and so updates once loaded.
   * @memberOf GeoRssCatalogItem.prototype
   * @type {Boolean}
   */
  canZoomTo: {
    get: function() {
      return true;
    }
  },

  /**
   * Gets the type of data member represented by this instance.
   * @memberOf GeoRssCatalogItem.prototype
   * @type {String}
   */
  type: {
    get: function() {
      return "georss";
    }
  },

  /**
   * Gets a human-readable name for this type of data source, 'GeoRSS'.
   * @memberOf GeoRssCatalogItem.prototype
   * @type {String}
   */
  typeName: {
    get: function() {
      return "GeoRSS";
    }
  },

  /**
   * Gets the metadata associated with this data source and the server that provided it, if applicable.
   * @memberOf GeoRssCatalogItem.prototype
   * @type {Metadata}
   */
  metadata: {
    get: function() {
      var result = new Metadata();
      result.isLoading = false;
      result.dataSourceErrorMessage =
        "This data source does not have any details available.";
      result.serviceErrorMessage =
        "This service does not have any details available.";
      return result;
    }
  },

  /**
   * Gets the set of functions used to update individual properties in {@link CatalogMember#updateFromJson}.
   * When a property name in the returned object literal matches the name of a property on this instance, the value
   * will be called as a function and passed a reference to this instance, a reference to the source JSON object
   * literal, and the name of the property.
   * @memberOf GeoRssCatalogItem.prototype
   * @type {Object}
   */
  updaters: {
    get: function() {
      return GeoRssCatalogItem.defaultUpdaters;
    }
  },

  /**
   * Gets the set of functions used to serialize individual properties in {@link CatalogMember#serializeToJson}.
   * When a property name on the model matches the name of a property in the serializers object literal,
   * the value will be called as a function and passed a reference to the model, a reference to the destination
   * JSON object literal, and the name of the property.
   * @memberOf GeoRssCatalogItem.prototype
   * @type {Object}
   */
  serializers: {
    get: function() {
      return GeoRssCatalogItem.defaultSerializers;
    }
  },

  /**
   * Gets the data source associated with this catalog item.
   * @memberOf GeoRssCatalogItem.prototype
   * @type {DataSource}
   */
  dataSource: {
    get: function() {
      return defined(this._geoJsonItem)
        ? this._geoJsonItem.dataSource
        : undefined;
    }
  }
});

GeoRssCatalogItem.defaultUpdaters = clone(CatalogItem.defaultUpdaters);
Object.freeze(GeoRssCatalogItem.defaultUpdaters);

GeoRssCatalogItem.defaultSerializers = clone(CatalogItem.defaultSerializers);

GeoRssCatalogItem.prototype._getValuesThatInfluenceLoad = function() {
  return [this.url];
};

GeoRssCatalogItem.prototype._load = function() {
  this._geoJsonItem = new GeoJsonCatalogItem(this.terria);
  this._geoJsonItem.data = loadGeoRss(this);
  this._geoJsonItem.name = this.name;
  var that = this;

  return this._geoJsonItem.load().then(function() {
    if (!defined(that.rectangle)) {
      that.rectangle = that._geoJsonItem.rectangle;
    }
  });
};

GeoRssCatalogItem.prototype._enable = function() {
  if (defined(this._geoJsonItem)) {
    this._geoJsonItem._enable();
  }
};

GeoRssCatalogItem.prototype._disable = function() {
  if (defined(this._geoJsonItem)) {
    this._geoJsonItem._disable();
  }
};

GeoRssCatalogItem.prototype._show = function() {
  if (defined(this._geoJsonItem)) {
    this._geoJsonItem._show();
  }
};

GeoRssCatalogItem.prototype._hide = function() {
  if (defined(this._geoJsonItem)) {
    this._geoJsonItem._hide();
  }
};

GeoRssCatalogItem.prototype.showOnSeparateMap = function(globeOrMap) {
  if (defined(this._geoJsonItem)) {
    return this._geoJsonItem.showOnSeparateMap(globeOrMap);
  }
};
GeoRssCatalogItem.prototype.zoomTo = function() {
  var that = this;
  return when(this.load(), function() {
    if (defined(that.nowViewingCatalogItem)) {
      return that.nowViewingCatalogItem.zoomTo();
    }

    if (defined(that.rectangle)) {
      return CatalogItem.prototype.zoomTo.call(that);
    }

    if (!defined(that.dataSource)) {
      return;
    }

    return that.terria.currentViewer.zoomTo(that.dataSource);
  });
};

function loadGeoRss(geoRssItem) {
  return loadXML(proxyCatalogItemUrl(geoRssItem, geoRssItem.url)).then(function(
    xml
  ) {
    if (typeof xml === "string") {
      const parser = new DOMParser();
      xml = parser.parseFromString(xml, "text/xml");
    }
    var documentElement = xml.documentElement;
    var promise;
    if (documentElement.localName === "rss") {
      promise = geoRss2Convertor(xml);
      resolveCatalogMetaData(
        documentElement.getElementsByTagName("channel")[0],
        geoRssItem
      );
    } else if (documentElement.localName === "feed") {
      promise = geoRssAtomConvertor(xml);
      resolveCatalogMetaData(documentElement, geoRssItem);
    } else {
      throw new RuntimeError("document is not valid");
    }
    return promise;
  });
}

function resolveCatalogMetaData(documentElement, geoRssItem) {
  var childNodes = documentElement.childNodes;

  for (
    var childNodeIndex = 0;
    childNodeIndex < childNodes.length;
    ++childNodeIndex
  ) {
    var child = childNodes[childNodeIndex];
    if (
      child.nodeType !== 1 ||
      child.localName === "item" ||
      child.localName === "entry"
    ) {
      continue;
    }
    if (child.localName === "title") {
      geoRssItem.name = child.textContent;
    } else if (child.localName === "description") {
      geoRssItem.description = child.textContent;
    } else if (child.localName === "copyright") {
      geoRssItem.copyrightText = child.copyright;
    } else if (child.localName === "author") {
      var authorNode = child.childNodes;
      var name;
      var email;
      for (
        var authorIndex = 0;
        authorIndex < authorNode.length;
        ++authorIndex
      ) {
        var authorChild = authorNode[authorIndex];
        if (authorChild.nodeType === 1) {
          if (authorChild.localName === "name") {
            name = authorChild.textContent;
          } else if (authorChild.localName === "email") {
            email = authorChild.textContent;
          }
        }
      }
      //TODO: check if this can be dataCustodian or author of the item
      if (name === undefined && email === undefined) {
        geoRssItem.dataCustodian = child.textContent;
      } else {
        geoRssItem.dataCustodian = name + ", " + email;
      }
    }
  }
  if (
    !defined(geoRssItem.name) ||
    geoRssItem.name === geoRssItem.url ||
    geoRssItem.name === "Unnamed Item"
  ) {
    geoRssItem.name = cleanUrl(geoRssItem.url);
  }
}

function cleanUrl(url) {
  return url.substring(url.lastIndexOf("/") + 1);
}

module.exports = GeoRssCatalogItem;