Current File : /home/jvzmxxx/wiki1/extensions/Kartographer/lib/wikimedia-mapdata.js
'use strict';

/* globals module */
/**
 * Data loader.
 *
 * @class Kartographer.Data.DataLoader
 */
var DataLoader = function ( createPromise, createResolvedPromise, mwApi, clientStore, title, debounce, bind ) {

  var DataLoader = function () {
    /**
     * @type {Object} Hash of group ids and associated promises.
     * @private
     */
    this.promiseByGroup = {};
    /**
     * @type {string[]} List of group ids to fetch next time
     *   {@link #fetch} is called.
     *
     * @private
     */
    this.nextFetch = [];

    if ( debounce && bind ) {
      this.fetch = debounce( 100, bind( this.fetch, this ) );
    }
  };

  clientStore = clientStore || {};

  /**
   * @param {string} groupId
   * @return {Promise}
   */
  DataLoader.prototype.fetchGroup = function ( groupId ) {
    var promise = this.promiseByGroup[ groupId ],
        resolveFunc, rejectFunc;
    if ( !promise ) {
      if ( clientStore[ groupId ] ) {
        promise = createResolvedPromise( clientStore[ groupId ] );
      } else {
        // FIXME: this is a horrible hack
        // The resolve and reject functions are attached to the promise object's instance
        // so that they can be called from the fetch function later
        this.nextFetch.push( groupId );
        promise = createPromise( function ( resolve, reject ) {
          resolveFunc = resolve;
          rejectFunc = reject;
        } );
        promise.mwResolve = resolveFunc;
        promise.mwReject = rejectFunc;
      }

      this.promiseByGroup[ groupId ] = promise;
    }
    return promise;
  };

  /**
   * @return {Promise}
   */
  DataLoader.prototype.fetch = function () {
    var loader = this,
        groupsToLoad = loader.nextFetch;

    if ( !groupsToLoad.length ) {
      return createResolvedPromise();
    }

    loader.nextFetch = [];

    // FIXME: we need to fix this horrid hack
    // http://stackoverflow.com/questions/39970101/combine-multiple-debounce-promises-in-js
    function setPromises( groupsToLoad, values, err ) {
      var i, promise;

      for ( i = 0; i < groupsToLoad.length; i++ ) {
        promise = loader.promiseByGroup[ groupsToLoad[ i ] ];
        if ( promise.mwResolve ) {
          if ( err ) {
            promise.mwReject( err );
          } else {
            promise.mwResolve( values[ groupsToLoad[ i ] ] || {} );
          }
          delete promise.mwResolve;
          delete promise.mwReject;
        }
      }
    }

    return mwApi( {
      action: 'query',
      formatversion: '2',
      titles: title,
      prop: 'mapdata',
      mpdlimit: 'max',
      mpdgroups: groupsToLoad.join( '|' )
    } ).then( function ( data ) {
      var rawMapData = data.query.pages[ 0 ].mapdata;
      setPromises( groupsToLoad, rawMapData && JSON.parse( rawMapData ) || {} );
    }, function ( err ) {
      setPromises( groupsToLoad, undefined, err );
    } );
  };

  return new DataLoader();
};

/* globals module */
/**
 * Group parent class.
 *
 * @class Kartographer.Data.Group
 * @extends L.Class
 * @abstract
 */

/**
 * @param {string} groupId
 * @param {Object} [geoJSON]
 * @param {Object} [options]
 * @constructor
 */
var Group$1 = function ( ) {
  // call the constructor
  this.initialize.apply( this, arguments );
};

Group$1.prototype.initialize = function ( groupId, geoJSON, options ) {
  this.id = groupId;
  this.geoJSON = geoJSON || null;
  this.options = options || {};
};

/**
 * @return {Object} Group GeoJSON
 */
Group$1.prototype.getGeoJSON = function () {
  return this.geoJSON;
};

/**
 * @return {string} Group annotation
 */
Group$1.prototype.getAttribution = function () {
  return this.options.attribution;
};

var Group_1 = Group$1;

/* globals module */
/**
 * External Data Group.
 *
 * @class Kartographer.Data.Group.External
 * @extends Kartographer.Data.Group
 */
var Group_External = function ( extend, isEmptyObject, isArray, getJSON, mwMsg, mwUri, Group ) {

  var ExternalGroup = function () {
    // call the constructor
    this.initialize.apply( this, arguments );
    this.isExternal = true;
  };

  extend( ExternalGroup.prototype, Group.prototype );

  ExternalGroup.prototype.initialize = function ( groupId, geoJSON, options ) {
    options = options || {};

    Group.prototype.initialize.call( this, groupId, geoJSON, options );
  };

  /**
   * @return {Promise}
   */
  ExternalGroup.prototype.fetch = function () {
    var group = this,
        data = group.geoJSON;

    if ( group.promise ) {
      return group.promise;
    }

    if ( !data.url ) {
      throw new Error( 'ExternalData has no url' );
    }

    group.promise = getJSON( data.url ).then( function ( geodata ) {
      var baseProps = data.properties,
          geometry,
          coordinates,
          i, j;

      switch ( data.service ) {

        case 'page':
          if ( geodata.jsondata && geodata.jsondata.data ) {
            extend( data, geodata.jsondata.data );
          }
          // FIXME: error reporting, at least to console.log
          break;

        case 'geomask':
          // Mask-out the entire world 10 times east and west,
          // and add each result geometry as a hole
          coordinates = [ [ [ 3600, -180 ], [ 3600, 180 ], [ -3600, 180 ], [ -3600, -180 ], [ 3600, -180 ] ] ];
          for ( i = 0; i < geodata.features.length; i++ ) {
            geometry = geodata.features[ i ].geometry;
            if ( !geometry ) {
              continue;
            }
            // Only add the very first (outer) polygon
            switch ( geometry.type ) {
              case 'Polygon':
                coordinates.push( geometry.coordinates[ 0 ] );
                break;
              case 'MultiPolygon':
                for ( j = 0; j < geometry.coordinates.length; j++ ) {
                  coordinates.push( geometry.coordinates[ j ][ 0 ] );
                }
                break;
            }
          }
          data.type = 'Feature';
          data.geometry = {
            type: 'Polygon',
            coordinates: coordinates
          };
          break;

        case 'geoshape':
        case 'geoline':

          // HACK: workaround for T144777 - we should be using topojson instead
          extend( data, geodata );

          // data.type = 'FeatureCollection';
          // data.features = [];
          // $.each( geodata.objects, function ( key ) {
          // 	data.features.push( topojson.feature( geodata, geodata.objects[ key ] ) );
          // } );

          // Each feature returned from geoshape service may contain "properties"
          // If externalData element has properties, merge it with properties in the feature
          if ( baseProps ) {
            for ( i = 0; i < data.features.length; i++ ) {
              if ( isEmptyObject( data.features[ i ].properties ) ) {
                data.features[ i ].properties = baseProps;
              } else {
                data.features[ i ].properties = extend( {}, baseProps, data.features[ i ].properties );
              }
            }
          }
          break;

        default:
          throw new Error( 'Unknown externalData service ' + data.service );
      }

      if ( mwMsg ) {
        group.parseAttribution();
      }
    }, function () {
      group.failed = true;
    } );

    return group.promise;
  };

  ExternalGroup.prototype.parseAttribution = function () {
    var i,
        group = this,
        ids = [],
        links = [],
        uri = mwUri( group.geoJSON.url );

    switch ( group.geoJSON.service ) {
      case 'page':
        // FIXME: add link to commons page
        break;

      case 'geoshape':
      case 'geoline':
        if ( uri.query.query ) {
          links.push( '<a target="_blank" href="//query.wikidata.org/#' +
              encodeURI( uri.query.query ) +
              '">' +
              mwMsg( 'kartographer-attribution-externaldata-query' ) +
              '</a>' );
        }

        if ( uri.query.ids ) {
          ids = uri.query.ids.split( ',' );

          for ( i = 0; i < ids.length; i++ ) {
            links.push( '<a target="_blank" href="//www.wikidata.org/wiki/' +
                encodeURI( ids[ i ] ) +
                '">' +
                encodeURI( ids[ i ] ) +
                '</a>' );
          }
        }
        group.attribution = mwMsg(
            'kartographer-attribution-externaldata',
            mwMsg( 'project-localized-name-wikidatawiki' ),
            links
        );
        break;
    }
  };

  return ExternalGroup;
};

/* globals module */
/**
 * Data store.
 *
 * @class Kartographer.Data.DataStore
 */
var DataStore = function () {

  var DataStore = function () {
    this.groups = {};
  };

  /**
   * @param {Kartographer.Data.Group} group
   * @return {Kartographer.Data.Group}
   */
  DataStore.prototype.add = function ( group ) {
    this.groups[ group.id ] = group;
    return group;
  };

  /**
   * @return {Kartographer.Data.Group}
   */
  DataStore.prototype.get = function ( groupId ) {
    return this.groups[ groupId ];
  };

  /**
   * @return {boolean}
   */
  DataStore.prototype.has = function ( groupId ) {
    return ( groupId in this.groups );
  };

  return new DataStore();
};

/* globals module */
/**
 * A hybrid group is a group that is not considered as a {@link Kartographer.Data.Group.HybridGroup}
 * because it does not implement a `fetch` method.
 *
 * This abstraction is useful for the Developer API: the data is passed directly but still needs to
 * be parsed to extract the external sub-groups.
 *
 * @class Kartographer.Data.Group.HybridGroup
 * @extends Kartographer.Data.Group
 */
var Group_Hybrid = function ( extend, createResolvedPromise, isPlainObject, isArray, whenAllPromises, Group, ExternalGroup, DataLoader, DataStore ) {

  var HybridGroup = function () {
    // call the constructor
    this.initialize.apply( this, arguments );
  };

  function isExternalDataGroup( data ) {
    return isPlainObject( data ) && data.type && data.type === 'ExternalData';
  }

  extend( HybridGroup.prototype, Group.prototype );

  HybridGroup.prototype.initialize = function ( groupId, geoJSON, options ) {
    options = options || {};

    Group.prototype.initialize.call( this, groupId, geoJSON, options );
    this.externals = [];
    this.isExternal = false;
  };

  /**
   * @return {Promise}
   */
  HybridGroup.prototype.load = function () {
    var group = this;

    return group.parse( group.getGeoJSON() ).then( function ( group ) {
      return group.fetchExternalGroups();
    } );
  };

  /**
   * @return {Promise}
   */
  HybridGroup.prototype.fetchExternalGroups = function () {
    var promises = [],
        group = this,
        key,
        externals = group.externals;

    for ( key in externals ) {
      promises.push( externals[ key ].fetch() );
    }

    return whenAllPromises( promises ).then( function () {
      return group;
    } );
  };

  /**
   * @return {Promise}
   */
  HybridGroup.prototype.parse = function ( apiGeoJSON ) {
    var group = this,
        geoJSON,
        externalKey,
        i;

    group.apiGeoJSON = apiGeoJSON;
    apiGeoJSON = JSON.parse( JSON.stringify( apiGeoJSON ) );
    if ( isArray( apiGeoJSON ) ) {
      geoJSON = [];
      for ( i = 0; i < apiGeoJSON.length; i++ ) {
        if ( isExternalDataGroup( apiGeoJSON[ i ] ) ) {
          externalKey = JSON.stringify( apiGeoJSON[ i ] );
          group.externals.push(
            DataStore.get( externalKey ) ||
            DataStore.add( new ExternalGroup( externalKey, apiGeoJSON[ i ] ) )
          );
        } else {
          geoJSON.push( apiGeoJSON[ i ] );
        }
      }
    } else if ( isExternalDataGroup( geoJSON ) ) {
      externalKey = JSON.stringify( geoJSON );
      group.externals.push(
        DataStore.get( externalKey ) ||
        DataStore.add( new ExternalGroup( externalKey, geoJSON ) )
      );
      geoJSON = {};
    }

    group.geoJSON = geoJSON;

    return createResolvedPromise( group );
  };

  return HybridGroup;
};

/* globals module */
/**
 * Internal Data Group.
 *
 * @class Kartographer.Data.Group.Internal
 * @extends Kartographer.Data.Group.HybridGroup
 */
var Group_Internal = function ( extend, HybridGroup, ExternalGroup, DataLoader ) {

  var InternalGroup = function () {
    // call the constructor
    this.initialize.apply( this, arguments );
  };

  extend( InternalGroup.prototype, HybridGroup.prototype );

  /**
   * @return {Promise}
   */
  InternalGroup.prototype.fetch = function () {
    var group = this;

    if ( group.promise ) {
      return group.promise;
    }

    group.promise = DataLoader.fetchGroup( group.id ).then( function ( apiGeoJSON ) {
      return group.parse( apiGeoJSON ).then( function ( group ) {
        return group.fetchExternalGroups();
      } );
    }, function () {
      group.failed = true;
    } );
    return group.promise;
  };
  return InternalGroup;
};

/* globals module */
/**
 * Data Manager.
 *
 * @class Kartographer.Data.DataManager
 */

var dataLoaderLib = DataLoader;
var Group = Group_1;
var externalGroupLib = Group_External;
var dataStoreLib = DataStore;
var hybridGroupLib = Group_Hybrid;
var internalGroupLib = Group_Internal;

var DataManager = function ( wrappers ) {

  var
    createResolvedPromise = function ( value ) {
      return wrappers.createPromise( function ( resolve ) {
        resolve( value );
      } );
    },
    DataLoader$$1 = dataLoaderLib(
      wrappers.createPromise,
      createResolvedPromise,
      wrappers.mwApi,
      wrappers.clientStore,
      wrappers.title,
      wrappers.debounce,
      wrappers.bind
    ),
    ExternalGroup = externalGroupLib(
      wrappers.extend,
      wrappers.isEmptyObject,
      wrappers.isArray,
      wrappers.getJSON,
      wrappers.mwMsg,
      wrappers.mwUri,
      Group
    ),
    DataStore$$1 = dataStoreLib(),
    HybridGroup = hybridGroupLib(
      wrappers.extend,
      createResolvedPromise,
      wrappers.isPlainObject,
      wrappers.isArray,
      wrappers.whenAllPromises,
      Group,
      ExternalGroup,
      DataLoader$$1,
      DataStore$$1
    ),
    InternalGroup = internalGroupLib(
      wrappers.extend,
      HybridGroup,
      ExternalGroup,
      DataLoader$$1
    ),
    DataManager = function () {};

  /**
   * @param {string[]} groupIds List of group ids to load.
   * @return {Promise}
   */
  DataManager.prototype.loadGroups = function ( groupIds ) {
    var promises = [],
        group,
        i;

    if ( !wrappers.isArray( groupIds ) ) {
      groupIds = [ groupIds ];
    }
    for ( i = 0; i < groupIds.length; i++ ) {
      group = DataStore$$1.get( groupIds[ i ] ) || DataStore$$1.add( new InternalGroup( groupIds[ i ] ) );
      /* jshint loopfunc:true */
      promises.push( wrappers.createPromise( function ( resolve ) {
        group.fetch().then( resolve, resolve );
      } ) );
      /* jshint loopfunc:false */
    }

    DataLoader$$1.fetch();

    return wrappers.whenAllPromises( promises ).then( function () {
      var groupList = [],
          group,
          i;

      for ( i = 0; i < groupIds.length; i++ ) {

        group = DataStore$$1.get( groupIds[ i ] );
        if ( group.failed || !wrappers.isEmptyObject( group.getGeoJSON() ) ) {
          groupList = groupList.concat( group );
        }
        groupList = groupList.concat( group.externals );
      }

      return groupList;
    } );
  };

  /**
   * @param {Object} geoJSON
   * @return {Promise}
   */
  DataManager.prototype.load = function ( geoJSON ) {
    var group = new HybridGroup( null, geoJSON );

    return group.load().then( function () {
      var groupList = [];

      if ( !wrappers.isEmptyObject( group.getGeoJSON() ) ) {
        groupList = groupList.concat( group );
      }

      return groupList.concat( group.externals );
    } );
  };

  return new DataManager();
};

var index = DataManager;

module.exports = index;