<commit_before>/***
|''Name''|TiddlySpaceConfig|
|''Version''|0.6.3|
|''Description''|TiddlySpace configuration|
|''Status''|stable|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceConfig.js|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlyWebConfig ServerSideSavingPlugin TiddlyFileImporter|
!Code
***/
//{{{
(function($) {

var tweb = config.extensions.tiddlyweb;

var recipe = config.defaultCustomFields["server.workspace"].split("recipes/")[1];

// hijack search macro to add custom attributes for mobile devices
var _search = config.macros.search.handler;
config.macros.search.handler = function(place, macroName, params) {
	_search.apply(this, arguments);
	$(".searchField:input", place).
		attr({ autocapitalize: "off", autocorrect: "off" });
};

// arg is either a container name or a tiddler object
// if fuzzy is truthy, space may be inferred from workspace (for new tiddlers)
// returns space object or false
var determineSpace = function(arg, fuzzy) {
	if(typeof arg == "string") { // container name
		var space = split(arg, "_", "r");
		return ["public", "private"].contains(space.type) ? space : false;
	} else if(arg) { // tiddler
		var container = determineContainer(arg, fuzzy);
		return container ? determineSpace(container.name, fuzzy) : false;
	} else {
		return false;
	}
};

// if fuzzy is truthy, container may be inferred from workspace for new tiddlers
// returns container object or false
var determineContainer = function(tiddler, fuzzy) { // TODO: expose?
	var bag = tiddler.fields["server.bag"];
	var recipe = tiddler.fields["server.recipe"]; // XXX: unused/irrelevant/redundant!?
	if(bag) {
		return { type: "bag", name: bag };
	} else if(recipe) {
		return { type: "recipe", name: recipe };
	} else if(fuzzy) { // new tiddler
		var workspace = tiddler.fields["server.workspace"];
		if(workspace) {
			var container = split(workspace, "/", "l");
			return ["bags", "recipes"].contains(container.type) ? container : false;
		} else {
			return false;
		}
	} else {
		return false;
	}
};

// hijack removeTiddlerCallback to restore tiddler from recipe cascade -- TODO: move into TiddlyWebWiki?
var sssp = config.extensions.ServerSideSavingPlugin;
var _removeTiddlerCallback = sssp.removeTiddlerCallback;
sssp.removeTiddlerCallback = function(context, userParams) {
	var title = context.tiddler.title;
	var recipe = context.tiddler.fields["server.recipe"];
	_removeTiddlerCallback.apply(this, arguments);
	if(recipe) {
		context.workspace = "recipes/" + recipe;
		var callback = function(context, userParams) {
			if(context.status) {
				var dirty = store.isDirty();
				store.saveTiddler(context.tiddler);
				store.setDirty(dirty);
			} else {
				store.notify(title, true);
			}
		};
		context.adaptor.getTiddler(title, context, null, callback);
	}
};

// splits a string once using delimiter
// mode "l" splits at the first, "r" at the last occurrence
// returns an object with members type and name
var split = function(str, sep, mode) {
	mode = mode == "r" ? "pop" : "shift"; // TODO: use +/-1 instead of "l"/"r"?
	var arr = str.split(sep);
	var type = arr.length > 1 ? arr[mode]() : null;
	return { type: type, name: arr.join(sep) };
};

// hijack saveTiddler to accept Tiddler instance
var _saveTiddler = TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier,
		modified, tags, fields, clearChangeCount, created, creator) {
	if(title instanceof Tiddler) { // overloading first argument
		var t = $.extend(new Tiddler(title.title), title);
		t = _saveTiddler.apply(this, [t.title, t.title, t.text, t.modifier,
			t.modified, t.tags, t.fields, false, t.created, t.creator]);
		return t;
	} else {
		return _saveTiddler.apply(this, arguments);
	}
};

var coreBags = ["system", "tiddlyspace"];
var systemSpaces = ["plugins", "info", "images", "theme"];
systemSpaces = $.map(systemSpaces, function(item, i) {
	return "system-%0_public".format(item);
});
var disabledTabs = [];

var plugin = config.extensions.tiddlyspace = {
	currentSpace: determineSpace(recipe),
	coreBags: coreBags.concat(systemSpaces),

	determineSpace: determineSpace,
	isValidSpaceName: function(name) {
		return name.match(/^[a-z][0-9a-z\-]*[0-9a-z]$/) ? true : false;
	},
	// returns the URL for a space's avatar (SiteIcon) based on a server_host
	// object and an optional space name
	// optional nocors argument prevents cross-domain URLs from being generated
	getAvatar: function(host, space, nocors) {
		if(space && typeof space != "string") { // backwards compatibility -- XXX: deprecated
			space = space.name;
		}
		var subdomain = nocors ? plugin.currentSpace.name : space;
		host = host ? this.getHost(host, subdomain) : "";
		var bag = space ? "%0_public".format([space]) : "tiddlyspace";
		return "%0/bags/%1/tiddlers/SiteIcon".format([host, bag]);
	},
	// returns the URL based on a server_host object (scheme, host, port) and an
	// optional subdomain
	getHost: function(host, subdomain) {
		subdomain = subdomain ? subdomain + "." : "";
		var url = "%0://%1%2".format([host.scheme, subdomain, host.host]);
		var port = host.port;
		if(port && !["80", "443"].contains(port)) {
			url += ":" + port;
		}
		return url;
	},
	disableTab: function(tabTiddler) {
		if(typeof(tabTiddler) == "string") {
			disabledTabs.push(tabTiddler);
		} else {
			for(var i = 0; i < tabTiddler.length; i++) {
				plugin.disableTab(tabTiddler[i]);
			}
		}
	},
	isDisabledTab: function(tabTitle) {
		var match = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])", "mg").exec(tabTitle);
		var tabIdentifier = match ? match[1] : tabTitle;
		return disabledTabs.contains(tabIdentifier);
	}
};

tweb.serverPrefix = tweb.host.split("/")[3] || ""; // XXX: assumes root handler
tweb.getStatus(function(status) {
	var url = plugin.getHost(status.server_host);
	tweb.status.server_host.url = url;
	config.messages.tsVersion = status.version;
});

if(window.location.protocol != "file:") {
	// set global read-only mode based on membership heuristics
	var indicator = store.getTiddler("SiteTitle") || tiddler;
	readOnly = !(recipe.split("_").pop() == "private" ||
		tweb.hasPermission("write", indicator));
	// replace TiddlyWiki's ImportTiddlers due to cross-domain restrictions
	if(config.macros.fileImport) {
		$.extend(config.macros.importTiddlers, config.macros.fileImport);
	}
}

// ensure backstage is always initialized
// required to circumvent TiddlyWiki's read-only based handling
config.macros.backstageInit = {
	init: function() {
		showBackstage = true;
	}
};

// disable evaluated macro parameters for security reasons
config.evaluateMacroParameters = "none";
var _parseParams = String.prototype.parseParams;
String.prototype.parseParams = function(defaultName, defaultValue, allowEval,
		noNames, cascadeDefaults) {
	if(config.evaluateMacroParameters == "none") {
		arguments[2] = false;
	}
	return _parseParams.apply(this, arguments);
};

var _tabsMacro = config.macros.tabs.handler;
config.macros.tabs.handler = function(place, macroName, params) {
	var newParams = [params[0]]; // keep cookie name
	for(var i = 1; i < params.length; i += 3) {
		var tabTitle = params[i + 2];
		if(!plugin.isDisabledTab(tabTitle)){
			newParams = newParams.concat(params[i], params[i + 1], tabTitle);
		}
	}
	_tabsMacro.apply(this, [place, macroName, newParams]);
};

// disable ControlView for XHRs by default
$.ajaxSetup({
	beforeSend: function(xhr) {
		xhr.setRequestHeader("X-ControlView", "false");
	}
});
// TiddlyWeb adaptor currently still uses httpReq, which needs extra magic -- XXX: obsolete this!
var _httpReq = httpReq;
httpReq = function(type, url, callback, params, headers, data, contentType,
		username, password, allowCache) {
	headers = headers || {};
	headers["X-ControlView"] = "false";
	_httpReq.apply(this, arguments);
};

// register style sheet for backstage separately (important)
store.addNotification("StyleSheetBackstage", refreshStyles);

})(jQuery);
//}}}
<commit_msg>fixed changecount after restoring tiddler from recipe cascade<commit_after>/***
|''Name''|TiddlySpaceConfig|
|''Version''|0.6.4|
|''Description''|TiddlySpace configuration|
|''Status''|stable|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceConfig.js|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlyWebConfig ServerSideSavingPlugin TiddlyFileImporter|
!Code
***/
//{{{
(function($) {

var tweb = config.extensions.tiddlyweb;

var recipe = config.defaultCustomFields["server.workspace"].split("recipes/")[1];

// hijack search macro to add custom attributes for mobile devices
var _search = config.macros.search.handler;
config.macros.search.handler = function(place, macroName, params) {
	_search.apply(this, arguments);
	$(".searchField:input", place).
		attr({ autocapitalize: "off", autocorrect: "off" });
};

// arg is either a container name or a tiddler object
// if fuzzy is truthy, space may be inferred from workspace (for new tiddlers)
// returns space object or false
var determineSpace = function(arg, fuzzy) {
	if(typeof arg == "string") { // container name
		var space = split(arg, "_", "r");
		return ["public", "private"].contains(space.type) ? space : false;
	} else if(arg) { // tiddler
		var container = determineContainer(arg, fuzzy);
		return container ? determineSpace(container.name, fuzzy) : false;
	} else {
		return false;
	}
};

// if fuzzy is truthy, container may be inferred from workspace for new tiddlers
// returns container object or false
var determineContainer = function(tiddler, fuzzy) { // TODO: expose?
	var bag = tiddler.fields["server.bag"];
	var recipe = tiddler.fields["server.recipe"]; // XXX: unused/irrelevant/redundant!?
	if(bag) {
		return { type: "bag", name: bag };
	} else if(recipe) {
		return { type: "recipe", name: recipe };
	} else if(fuzzy) { // new tiddler
		var workspace = tiddler.fields["server.workspace"];
		if(workspace) {
			var container = split(workspace, "/", "l");
			return ["bags", "recipes"].contains(container.type) ? container : false;
		} else {
			return false;
		}
	} else {
		return false;
	}
};

// hijack removeTiddlerCallback to restore tiddler from recipe cascade -- TODO: move into TiddlyWebWiki?
var sssp = config.extensions.ServerSideSavingPlugin;
var _removeTiddlerCallback = sssp.removeTiddlerCallback;
sssp.removeTiddlerCallback = function(context, userParams) {
	var title = context.tiddler.title;
	var recipe = context.tiddler.fields["server.recipe"];
	_removeTiddlerCallback.apply(this, arguments);
	if(recipe) {
		context.workspace = "recipes/" + recipe;
		var callback = function(context, userParams) {
			if(context.status) {
				var dirty = store.isDirty();
				store.saveTiddler(context.tiddler).clearChangeCount();
				store.setDirty(dirty);
			} else {
				store.notify(title, true);
			}
		};
		context.adaptor.getTiddler(title, context, null, callback);
	}
};

// splits a string once using delimiter
// mode "l" splits at the first, "r" at the last occurrence
// returns an object with members type and name
var split = function(str, sep, mode) {
	mode = mode == "r" ? "pop" : "shift"; // TODO: use +/-1 instead of "l"/"r"?
	var arr = str.split(sep);
	var type = arr.length > 1 ? arr[mode]() : null;
	return { type: type, name: arr.join(sep) };
};

// hijack saveTiddler to accept Tiddler instance
var _saveTiddler = TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier,
		modified, tags, fields, clearChangeCount, created, creator) {
	if(title instanceof Tiddler) { // overloading first argument
		var t = $.extend(new Tiddler(title.title), title);
		t = _saveTiddler.apply(this, [t.title, t.title, t.text, t.modifier,
			t.modified, t.tags, t.fields, false, t.created, t.creator]);
		return t;
	} else {
		return _saveTiddler.apply(this, arguments);
	}
};

var coreBags = ["system", "tiddlyspace"];
var systemSpaces = ["plugins", "info", "images", "theme"];
systemSpaces = $.map(systemSpaces, function(item, i) {
	return "system-%0_public".format(item);
});
var disabledTabs = [];

var plugin = config.extensions.tiddlyspace = {
	currentSpace: determineSpace(recipe),
	coreBags: coreBags.concat(systemSpaces),

	determineSpace: determineSpace,
	isValidSpaceName: function(name) {
		return name.match(/^[a-z][0-9a-z\-]*[0-9a-z]$/) ? true : false;
	},
	// returns the URL for a space's avatar (SiteIcon) based on a server_host
	// object and an optional space name
	// optional nocors argument prevents cross-domain URLs from being generated
	getAvatar: function(host, space, nocors) {
		if(space && typeof space != "string") { // backwards compatibility -- XXX: deprecated
			space = space.name;
		}
		var subdomain = nocors ? plugin.currentSpace.name : space;
		host = host ? this.getHost(host, subdomain) : "";
		var bag = space ? "%0_public".format([space]) : "tiddlyspace";
		return "%0/bags/%1/tiddlers/SiteIcon".format([host, bag]);
	},
	// returns the URL based on a server_host object (scheme, host, port) and an
	// optional subdomain
	getHost: function(host, subdomain) {
		subdomain = subdomain ? subdomain + "." : "";
		var url = "%0://%1%2".format([host.scheme, subdomain, host.host]);
		var port = host.port;
		if(port && !["80", "443"].contains(port)) {
			url += ":" + port;
		}
		return url;
	},
	disableTab: function(tabTiddler) {
		if(typeof(tabTiddler) == "string") {
			disabledTabs.push(tabTiddler);
		} else {
			for(var i = 0; i < tabTiddler.length; i++) {
				plugin.disableTab(tabTiddler[i]);
			}
		}
	},
	isDisabledTab: function(tabTitle) {
		var match = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])", "mg").exec(tabTitle);
		var tabIdentifier = match ? match[1] : tabTitle;
		return disabledTabs.contains(tabIdentifier);
	}
};

tweb.serverPrefix = tweb.host.split("/")[3] || ""; // XXX: assumes root handler
tweb.getStatus(function(status) {
	var url = plugin.getHost(status.server_host);
	tweb.status.server_host.url = url;
	config.messages.tsVersion = status.version;
});

if(window.location.protocol != "file:") {
	// set global read-only mode based on membership heuristics
	var indicator = store.getTiddler("SiteTitle") || tiddler;
	readOnly = !(recipe.split("_").pop() == "private" ||
		tweb.hasPermission("write", indicator));
	// replace TiddlyWiki's ImportTiddlers due to cross-domain restrictions
	if(config.macros.fileImport) {
		$.extend(config.macros.importTiddlers, config.macros.fileImport);
	}
}

// ensure backstage is always initialized
// required to circumvent TiddlyWiki's read-only based handling
config.macros.backstageInit = {
	init: function() {
		showBackstage = true;
	}
};

// disable evaluated macro parameters for security reasons
config.evaluateMacroParameters = "none";
var _parseParams = String.prototype.parseParams;
String.prototype.parseParams = function(defaultName, defaultValue, allowEval,
		noNames, cascadeDefaults) {
	if(config.evaluateMacroParameters == "none") {
		arguments[2] = false;
	}
	return _parseParams.apply(this, arguments);
};

var _tabsMacro = config.macros.tabs.handler;
config.macros.tabs.handler = function(place, macroName, params) {
	var newParams = [params[0]]; // keep cookie name
	for(var i = 1; i < params.length; i += 3) {
		var tabTitle = params[i + 2];
		if(!plugin.isDisabledTab(tabTitle)){
			newParams = newParams.concat(params[i], params[i + 1], tabTitle);
		}
	}
	_tabsMacro.apply(this, [place, macroName, newParams]);
};

// disable ControlView for XHRs by default
$.ajaxSetup({
	beforeSend: function(xhr) {
		xhr.setRequestHeader("X-ControlView", "false");
	}
});
// TiddlyWeb adaptor currently still uses httpReq, which needs extra magic -- XXX: obsolete this!
var _httpReq = httpReq;
httpReq = function(type, url, callback, params, headers, data, contentType,
		username, password, allowCache) {
	headers = headers || {};
	headers["X-ControlView"] = "false";
	_httpReq.apply(this, arguments);
};

// register style sheet for backstage separately (important)
store.addNotification("StyleSheetBackstage", refreshStyles);

})(jQuery);
//}}}
<|endoftext|>


<commit_before>/**
 * @module ol/source/VectorTile
 */

import TileState from '../TileState.js';
import VectorRenderTile from '../VectorRenderTile.js';
import Tile from '../VectorTile.js';
import {toSize} from '../size.js';
import UrlTile from './UrlTile.js';
import {getKeyZXY, getKey} from '../tilecoord.js';
import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid.js';
import {buffer as bufferExtent, getIntersection} from '../extent.js';
import {listen, unlistenByKey} from '../events.js';
import EventType from '../events/EventType.js';
import {loadFeaturesXhr} from '../featureloader.js';
import {isEmpty} from '../obj.js';
import {equals} from '../array.js';

/**
 * @typedef {Object} Options
 * @property {import("./Source.js").AttributionLike} [attributions] Attributions.
 * @property {number} [cacheSize=128] Cache size.
 * @property {import("../extent.js").Extent} [extent]
 * @property {import("../format/Feature.js").default} [format] Feature format for tiles. Used and required by the default.
 * @property {boolean} [overlaps=true] This source may have overlapping geometries. Setting this
 * to `false` (e.g. for sources with polygons that represent administrative
 * boundaries or TopoJSON sources) allows the renderer to optimise fill and
 * stroke operations.
 * @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Projection of the tile grid.
 * @property {import("./State.js").default} [state] Source state.
 * @property {typeof import("../VectorTile.js").default} [tileClass] Class used to instantiate image tiles.
 * Default is {@link module:ol/VectorTile}.
 * @property {number} [maxZoom=22] Optional max zoom level.
 * @property {number} [minZoom] Optional min zoom level.
 * @property {number|import("../size.js").Size} [tileSize=512] Optional tile size.
 * @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
 * @property {import("../Tile.js").LoadFunction} [tileLoadFunction]
 * Optional function to load a tile given a URL. Could look like this for pbf tiles:
 * ```js
 * function(tile, url) {
 *   tile.setLoader(function(extent, resolution, projection) {
 *     fetch(url).then(function(response) {
 *       response.arrayBuffer().then(function(data) {
 *         const format = tile.getFormat() // ol/format/MVT configured as source format
 *         const features = format.readFeatures(data, {
 *           extent: extent,
 *           featureProjection: projection
 *         });
 *         tile.setFeatures(features);
 *       });
 *     });
 *   });
 * }
 * ```
 * @property {import("../Tile.js").UrlFunction} [tileUrlFunction] Optional function to get tile URL given a tile coordinate and the projection.
 * @property {string} [url] URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders.
 * A `{?-?}` template pattern, for example `subdomain{a-f}.domain.com`, may be
 * used instead of defining each one separately in the `urls` option.
 * @property {number} [transition] A duration for tile opacity
 * transitions in milliseconds. A duration of 0 disables the opacity transition.
 * @property {Array<string>} [urls] An array of URL templates.
 * @property {boolean} [wrapX=true] Whether to wrap the world horizontally.
 * When set to `false`, only one world
 * will be rendered. When set to `true`, tiles will be wrapped horizontally to
 * render multiple worlds.
 */

/**
 * @classdesc
 * Class for layer sources providing vector data divided into a tile grid, to be
 * used with {@link module:ol/layer/VectorTile~VectorTile}. Although this source receives tiles
 * with vector features from the server, it is not meant for feature editing.
 * Features are optimized for rendering, their geometries are clipped at or near
 * tile boundaries and simplified for a view resolution. See
 * {@link module:ol/source/Vector} for vector sources that are suitable for feature
 * editing.
 *
 * @fires import("./Tile.js").TileSourceEvent
 * @api
 */
class VectorTile extends UrlTile {
  /**
   * @param {!Options} options Vector tile options.
   */
  constructor(options) {
    const projection = options.projection || 'EPSG:3857';

    const extent = options.extent || extentFromProjection(projection);

    const tileGrid = options.tileGrid || createXYZ({
      extent: extent,
      maxZoom: options.maxZoom || 22,
      minZoom: options.minZoom,
      tileSize: options.tileSize || 512
    });

    super({
      attributions: options.attributions,
      cacheSize: options.cacheSize,
      opaque: false,
      projection: projection,
      state: options.state,
      tileGrid: tileGrid,
      tileLoadFunction: options.tileLoadFunction ? options.tileLoadFunction : defaultLoadFunction,
      tileUrlFunction: options.tileUrlFunction,
      url: options.url,
      urls: options.urls,
      wrapX: options.wrapX === undefined ? true : options.wrapX,
      transition: options.transition
    });

    /**
     * @private
     * @type {import("../format/Feature.js").default}
     */
    this.format_ = options.format ? options.format : null;

    /**
     * @type {Object<string, import("./VectorTile").default>}
     */
    this.loadingTiles_ = {};

    /**
     * @private
     * @type {Object<string, import("../VectorTile.js").default>}
     */
    this.sourceTiles_ = {};

    /**
     * @private
     * @type {Object<string, Array<import("../VectorTile.js").default>>}
     */
    this.sourceTilesByTileKey_ = {};

    /**
     * @private
     * @type {boolean}
     */
    this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;

    /**
     * @protected
     * @type {typeof import("../VectorTile.js").default}
     */
    this.tileClass = options.tileClass ? options.tileClass : Tile;

    /**
     * @private
     * @type {Object<string, import("../tilegrid/TileGrid.js").default>}
     */
    this.tileGrids_ = {};

  }

  /**
   * @return {boolean} The source can have overlapping geometries.
   */
  getOverlaps() {
    return this.overlaps_;
  }

  /**
   * clear {@link module:ol/TileCache~TileCache} and delete all source tiles
   * @api
   */
  clear() {
    this.tileCache.clear();
    this.sourceTiles_ = {};
    this.sourceTilesByTileKey_ = {};
  }

  /**
   * @param {number} pixelRatio Pixel ratio.
   * @param {import("../proj/Projection").default} projection Projection.
   * @param {VectorRenderTile} tile Vector image tile.
   * @return {Array<import("../VectorTile").default>} Tile keys.
   */
  getSourceTiles(pixelRatio, projection, tile) {
    const sourceTiles = [];
    const urlTileCoord = tile.wrappedTileCoord;
    if (urlTileCoord) {
      const tileGrid = this.getTileGridForProjection(projection);
      const extent = tileGrid.getTileCoordExtent(urlTileCoord);
      const z = urlTileCoord[0];
      const resolution = tileGrid.getResolution(z);
      // make extent 1 pixel smaller so we don't load tiles for < 0.5 pixel render space
      bufferExtent(extent, -1 / resolution, extent);
      const sourceTileGrid = this.tileGrid;
      const sourceExtent = sourceTileGrid.getExtent();
      if (sourceExtent) {
        getIntersection(extent, sourceExtent, extent);
      }
      const sourceZ = sourceTileGrid.getZForResolution(resolution, 1);
      const minZoom = sourceTileGrid.getMinZoom();

      let loadedZ = sourceZ + 1;
      let covered, empty;
      do {
        --loadedZ;
        covered = true;
        empty = true;
        sourceTileGrid.forEachTileCoord(extent, loadedZ, function(sourceTileCoord) {
          const tileKey = getKey(sourceTileCoord);
          let sourceTile;
          if (tileKey in this.sourceTiles_) {
            sourceTile = this.sourceTiles_[tileKey];
            const state = sourceTile.getState();
            if (state === TileState.LOADED || state === TileState.ERROR || state === TileState.EMPTY) {
              empty = empty && state === TileState.EMPTY;
              sourceTiles.push(sourceTile);
              return;
            }
          } else if (loadedZ === sourceZ) {
            const tileUrl = this.tileUrlFunction(sourceTileCoord, pixelRatio, projection);
            sourceTile = new this.tileClass(sourceTileCoord,
              tileUrl == undefined ? TileState.EMPTY : TileState.IDLE,
              tileUrl == undefined ? '' : tileUrl,
              this.format_, this.tileLoadFunction);
            sourceTile.extent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
            sourceTile.projection = projection;
            sourceTile.resolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
            this.sourceTiles_[tileKey] = sourceTile;
            empty = empty && sourceTile.getState() === TileState.EMPTY;
            listen(sourceTile, EventType.CHANGE, this.handleTileChange, this);
            sourceTile.load();
          } else {
            empty = false;
          }
          covered = false;
          if (!sourceTile) {
            return;
          }
          if (sourceTile.getState() !== TileState.EMPTY && tile.getState() === TileState.IDLE) {
            tile.loadingSourceTiles++;
            const key = listen(sourceTile, EventType.CHANGE, function() {
              const state = sourceTile.getState();
              const sourceTileKey = getKey(sourceTile.tileCoord);
              if (state === TileState.LOADED || state === TileState.ERROR) {
                if (state === TileState.LOADED) {
                  unlistenByKey(key);
                  tile.loadingSourceTiles--;
                  delete tile.errorSourceTileKeys[sourceTileKey];
                } else if (state === TileState.ERROR) {
                  tile.errorSourceTileKeys[sourceTileKey] = true;
                }
                if (tile.loadingSourceTiles - Object.keys(tile.errorSourceTileKeys).length === 0) {
                  tile.hifi = true;
                  tile.sourceZ = sourceZ;
                  tile.setState(isEmpty(tile.errorSourceTileKeys) ? TileState.LOADED : TileState.ERROR);
                }
              }
            });
          }
        }.bind(this));
        if (!covered) {
          sourceTiles.length = 0;
        }
      } while (!covered && loadedZ > minZoom);
      if (!empty && tile.getState() === TileState.IDLE) {
        tile.setState(TileState.LOADING);
      }
      if (covered || empty) {
        tile.hifi = sourceZ === loadedZ;
        tile.sourceZ = loadedZ;
        const previousSourceTiles = this.sourceTilesByTileKey_[getKey(tile.tileCoord)];
        if (tile.getState() < TileState.LOADED) {
          tile.setState(empty ? TileState.EMPTY : TileState.LOADED);
        } else if (!previousSourceTiles || !equals(sourceTiles, previousSourceTiles)) {
          this.removeSourceTiles(tile);
          this.addSourceTiles(tile, sourceTiles);
        }
      }
    }
    return sourceTiles;
  }

  /**
   * @param {VectorRenderTile} tile Tile.
   * @param {Array<import("../VectorTile").default>} sourceTiles Source tiles.
   */
  addSourceTiles(tile, sourceTiles) {
    this.sourceTilesByTileKey_[getKey(tile.tileCoord)] = sourceTiles;
    for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
      sourceTiles[i].consumers++;
    }
  }

  /**
   * @param {VectorRenderTile} tile Tile.
   */
  removeSourceTiles(tile) {
    const tileKey = getKey(tile.tileCoord);
    if (tileKey in this.sourceTilesByTileKey_) {
      const sourceTiles = this.sourceTilesByTileKey_[tileKey];
      for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
        const sourceTile = sourceTiles[i];
        sourceTile.consumers--;
        if (sourceTile.consumers === 0) {
          sourceTile.dispose();
          delete this.sourceTiles_[getKey(sourceTile.tileCoord)];
        }
      }
    }
    delete this.sourceTilesByTileKey_[tileKey];
  }

  /**
   * @inheritDoc
   */
  getTile(z, x, y, pixelRatio, projection) {
    const tileCoordKey = getKeyZXY(z, x, y);
    if (this.tileCache.containsKey(tileCoordKey)) {
      return (
        /** @type {!import("../Tile.js").default} */ (this.tileCache.get(tileCoordKey))
      );
    } else {
      const tileCoord = [z, x, y];
      const urlTileCoord = this.getTileCoordForTileUrlFunction(
        tileCoord, projection);
      const tile = new VectorRenderTile(
        tileCoord,
        urlTileCoord !== null ? TileState.IDLE : TileState.EMPTY,
        urlTileCoord,
        this.tileGrid,
        this.getSourceTiles.bind(this, pixelRatio, projection),
        this.removeSourceTiles.bind(this));

      tile.key = this.getRevision().toString();
      this.tileCache.set(tileCoordKey, tile);
      return tile;
    }
  }

  /**
   * @inheritDoc
   */
  getTileGridForProjection(projection) {
    const code = projection.getCode();
    let tileGrid = this.tileGrids_[code];
    if (!tileGrid) {
      // A tile grid that matches the tile size of the source tile grid is more
      // likely to have 1:1 relationships between source tiles and rendered tiles.
      const sourceTileGrid = this.tileGrid;
      tileGrid = this.tileGrids_[code] = createForProjection(projection, undefined,
        sourceTileGrid ? sourceTileGrid.getTileSize(sourceTileGrid.getMinZoom()) : undefined);
    }
    return tileGrid;
  }

  /**
   * @inheritDoc
   */
  getTilePixelRatio(pixelRatio) {
    return pixelRatio;
  }

  /**
   * @inheritDoc
   */
  getTilePixelSize(z, pixelRatio, projection) {
    const tileGrid = this.getTileGridForProjection(projection);
    const tileSize = toSize(tileGrid.getTileSize(z), this.tmpSize);
    return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)];
  }
}

export default VectorTile;

/**
 * Sets the loader for a tile.
 * @param {import("../VectorTile.js").default} tile Vector tile.
 * @param {string} url URL.
 */
export function defaultLoadFunction(tile, url) {
  const loader = loadFeaturesXhr(url, tile.getFormat(), tile.onLoad.bind(tile), tile.onError.bind(tile));
  tile.setLoader(loader);
}
<commit_msg>Add attributionsCollapsible option to source/VectorTile<commit_after>/**
 * @module ol/source/VectorTile
 */

import TileState from '../TileState.js';
import VectorRenderTile from '../VectorRenderTile.js';
import Tile from '../VectorTile.js';
import {toSize} from '../size.js';
import UrlTile from './UrlTile.js';
import {getKeyZXY, getKey} from '../tilecoord.js';
import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid.js';
import {buffer as bufferExtent, getIntersection} from '../extent.js';
import {listen, unlistenByKey} from '../events.js';
import EventType from '../events/EventType.js';
import {loadFeaturesXhr} from '../featureloader.js';
import {isEmpty} from '../obj.js';
import {equals} from '../array.js';

/**
 * @typedef {Object} Options
 * @property {import("./Source.js").AttributionLike} [attributions] Attributions.
 * @property {boolean} [attributionsCollapsible=true] Attributions are collapsible.
 * @property {number} [cacheSize=128] Cache size.
 * @property {import("../extent.js").Extent} [extent]
 * @property {import("../format/Feature.js").default} [format] Feature format for tiles. Used and required by the default.
 * @property {boolean} [overlaps=true] This source may have overlapping geometries. Setting this
 * to `false` (e.g. for sources with polygons that represent administrative
 * boundaries or TopoJSON sources) allows the renderer to optimise fill and
 * stroke operations.
 * @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Projection of the tile grid.
 * @property {import("./State.js").default} [state] Source state.
 * @property {typeof import("../VectorTile.js").default} [tileClass] Class used to instantiate image tiles.
 * Default is {@link module:ol/VectorTile}.
 * @property {number} [maxZoom=22] Optional max zoom level.
 * @property {number} [minZoom] Optional min zoom level.
 * @property {number|import("../size.js").Size} [tileSize=512] Optional tile size.
 * @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
 * @property {import("../Tile.js").LoadFunction} [tileLoadFunction]
 * Optional function to load a tile given a URL. Could look like this for pbf tiles:
 * ```js
 * function(tile, url) {
 *   tile.setLoader(function(extent, resolution, projection) {
 *     fetch(url).then(function(response) {
 *       response.arrayBuffer().then(function(data) {
 *         const format = tile.getFormat() // ol/format/MVT configured as source format
 *         const features = format.readFeatures(data, {
 *           extent: extent,
 *           featureProjection: projection
 *         });
 *         tile.setFeatures(features);
 *       });
 *     });
 *   });
 * }
 * ```
 * @property {import("../Tile.js").UrlFunction} [tileUrlFunction] Optional function to get tile URL given a tile coordinate and the projection.
 * @property {string} [url] URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders.
 * A `{?-?}` template pattern, for example `subdomain{a-f}.domain.com`, may be
 * used instead of defining each one separately in the `urls` option.
 * @property {number} [transition] A duration for tile opacity
 * transitions in milliseconds. A duration of 0 disables the opacity transition.
 * @property {Array<string>} [urls] An array of URL templates.
 * @property {boolean} [wrapX=true] Whether to wrap the world horizontally.
 * When set to `false`, only one world
 * will be rendered. When set to `true`, tiles will be wrapped horizontally to
 * render multiple worlds.
 */

/**
 * @classdesc
 * Class for layer sources providing vector data divided into a tile grid, to be
 * used with {@link module:ol/layer/VectorTile~VectorTile}. Although this source receives tiles
 * with vector features from the server, it is not meant for feature editing.
 * Features are optimized for rendering, their geometries are clipped at or near
 * tile boundaries and simplified for a view resolution. See
 * {@link module:ol/source/Vector} for vector sources that are suitable for feature
 * editing.
 *
 * @fires import("./Tile.js").TileSourceEvent
 * @api
 */
class VectorTile extends UrlTile {
  /**
   * @param {!Options} options Vector tile options.
   */
  constructor(options) {
    const projection = options.projection || 'EPSG:3857';

    const extent = options.extent || extentFromProjection(projection);

    const tileGrid = options.tileGrid || createXYZ({
      extent: extent,
      maxZoom: options.maxZoom || 22,
      minZoom: options.minZoom,
      tileSize: options.tileSize || 512
    });

    super({
      attributions: options.attributions,
      attributionsCollapsible: options.attributionsCollapsible,
      cacheSize: options.cacheSize,
      opaque: false,
      projection: projection,
      state: options.state,
      tileGrid: tileGrid,
      tileLoadFunction: options.tileLoadFunction ? options.tileLoadFunction : defaultLoadFunction,
      tileUrlFunction: options.tileUrlFunction,
      url: options.url,
      urls: options.urls,
      wrapX: options.wrapX === undefined ? true : options.wrapX,
      transition: options.transition
    });

    /**
     * @private
     * @type {import("../format/Feature.js").default}
     */
    this.format_ = options.format ? options.format : null;

    /**
     * @type {Object<string, import("./VectorTile").default>}
     */
    this.loadingTiles_ = {};

    /**
     * @private
     * @type {Object<string, import("../VectorTile.js").default>}
     */
    this.sourceTiles_ = {};

    /**
     * @private
     * @type {Object<string, Array<import("../VectorTile.js").default>>}
     */
    this.sourceTilesByTileKey_ = {};

    /**
     * @private
     * @type {boolean}
     */
    this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;

    /**
     * @protected
     * @type {typeof import("../VectorTile.js").default}
     */
    this.tileClass = options.tileClass ? options.tileClass : Tile;

    /**
     * @private
     * @type {Object<string, import("../tilegrid/TileGrid.js").default>}
     */
    this.tileGrids_ = {};

  }

  /**
   * @return {boolean} The source can have overlapping geometries.
   */
  getOverlaps() {
    return this.overlaps_;
  }

  /**
   * clear {@link module:ol/TileCache~TileCache} and delete all source tiles
   * @api
   */
  clear() {
    this.tileCache.clear();
    this.sourceTiles_ = {};
    this.sourceTilesByTileKey_ = {};
  }

  /**
   * @param {number} pixelRatio Pixel ratio.
   * @param {import("../proj/Projection").default} projection Projection.
   * @param {VectorRenderTile} tile Vector image tile.
   * @return {Array<import("../VectorTile").default>} Tile keys.
   */
  getSourceTiles(pixelRatio, projection, tile) {
    const sourceTiles = [];
    const urlTileCoord = tile.wrappedTileCoord;
    if (urlTileCoord) {
      const tileGrid = this.getTileGridForProjection(projection);
      const extent = tileGrid.getTileCoordExtent(urlTileCoord);
      const z = urlTileCoord[0];
      const resolution = tileGrid.getResolution(z);
      // make extent 1 pixel smaller so we don't load tiles for < 0.5 pixel render space
      bufferExtent(extent, -1 / resolution, extent);
      const sourceTileGrid = this.tileGrid;
      const sourceExtent = sourceTileGrid.getExtent();
      if (sourceExtent) {
        getIntersection(extent, sourceExtent, extent);
      }
      const sourceZ = sourceTileGrid.getZForResolution(resolution, 1);
      const minZoom = sourceTileGrid.getMinZoom();

      let loadedZ = sourceZ + 1;
      let covered, empty;
      do {
        --loadedZ;
        covered = true;
        empty = true;
        sourceTileGrid.forEachTileCoord(extent, loadedZ, function(sourceTileCoord) {
          const tileKey = getKey(sourceTileCoord);
          let sourceTile;
          if (tileKey in this.sourceTiles_) {
            sourceTile = this.sourceTiles_[tileKey];
            const state = sourceTile.getState();
            if (state === TileState.LOADED || state === TileState.ERROR || state === TileState.EMPTY) {
              empty = empty && state === TileState.EMPTY;
              sourceTiles.push(sourceTile);
              return;
            }
          } else if (loadedZ === sourceZ) {
            const tileUrl = this.tileUrlFunction(sourceTileCoord, pixelRatio, projection);
            sourceTile = new this.tileClass(sourceTileCoord,
              tileUrl == undefined ? TileState.EMPTY : TileState.IDLE,
              tileUrl == undefined ? '' : tileUrl,
              this.format_, this.tileLoadFunction);
            sourceTile.extent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
            sourceTile.projection = projection;
            sourceTile.resolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
            this.sourceTiles_[tileKey] = sourceTile;
            empty = empty && sourceTile.getState() === TileState.EMPTY;
            listen(sourceTile, EventType.CHANGE, this.handleTileChange, this);
            sourceTile.load();
          } else {
            empty = false;
          }
          covered = false;
          if (!sourceTile) {
            return;
          }
          if (sourceTile.getState() !== TileState.EMPTY && tile.getState() === TileState.IDLE) {
            tile.loadingSourceTiles++;
            const key = listen(sourceTile, EventType.CHANGE, function() {
              const state = sourceTile.getState();
              const sourceTileKey = getKey(sourceTile.tileCoord);
              if (state === TileState.LOADED || state === TileState.ERROR) {
                if (state === TileState.LOADED) {
                  unlistenByKey(key);
                  tile.loadingSourceTiles--;
                  delete tile.errorSourceTileKeys[sourceTileKey];
                } else if (state === TileState.ERROR) {
                  tile.errorSourceTileKeys[sourceTileKey] = true;
                }
                if (tile.loadingSourceTiles - Object.keys(tile.errorSourceTileKeys).length === 0) {
                  tile.hifi = true;
                  tile.sourceZ = sourceZ;
                  tile.setState(isEmpty(tile.errorSourceTileKeys) ? TileState.LOADED : TileState.ERROR);
                }
              }
            });
          }
        }.bind(this));
        if (!covered) {
          sourceTiles.length = 0;
        }
      } while (!covered && loadedZ > minZoom);
      if (!empty && tile.getState() === TileState.IDLE) {
        tile.setState(TileState.LOADING);
      }
      if (covered || empty) {
        tile.hifi = sourceZ === loadedZ;
        tile.sourceZ = loadedZ;
        const previousSourceTiles = this.sourceTilesByTileKey_[getKey(tile.tileCoord)];
        if (tile.getState() < TileState.LOADED) {
          tile.setState(empty ? TileState.EMPTY : TileState.LOADED);
        } else if (!previousSourceTiles || !equals(sourceTiles, previousSourceTiles)) {
          this.removeSourceTiles(tile);
          this.addSourceTiles(tile, sourceTiles);
        }
      }
    }
    return sourceTiles;
  }

  /**
   * @param {VectorRenderTile} tile Tile.
   * @param {Array<import("../VectorTile").default>} sourceTiles Source tiles.
   */
  addSourceTiles(tile, sourceTiles) {
    this.sourceTilesByTileKey_[getKey(tile.tileCoord)] = sourceTiles;
    for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
      sourceTiles[i].consumers++;
    }
  }

  /**
   * @param {VectorRenderTile} tile Tile.
   */
  removeSourceTiles(tile) {
    const tileKey = getKey(tile.tileCoord);
    if (tileKey in this.sourceTilesByTileKey_) {
      const sourceTiles = this.sourceTilesByTileKey_[tileKey];
      for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
        const sourceTile = sourceTiles[i];
        sourceTile.consumers--;
        if (sourceTile.consumers === 0) {
          sourceTile.dispose();
          delete this.sourceTiles_[getKey(sourceTile.tileCoord)];
        }
      }
    }
    delete this.sourceTilesByTileKey_[tileKey];
  }

  /**
   * @inheritDoc
   */
  getTile(z, x, y, pixelRatio, projection) {
    const tileCoordKey = getKeyZXY(z, x, y);
    if (this.tileCache.containsKey(tileCoordKey)) {
      return (
        /** @type {!import("../Tile.js").default} */ (this.tileCache.get(tileCoordKey))
      );
    } else {
      const tileCoord = [z, x, y];
      const urlTileCoord = this.getTileCoordForTileUrlFunction(
        tileCoord, projection);
      const tile = new VectorRenderTile(
        tileCoord,
        urlTileCoord !== null ? TileState.IDLE : TileState.EMPTY,
        urlTileCoord,
        this.tileGrid,
        this.getSourceTiles.bind(this, pixelRatio, projection),
        this.removeSourceTiles.bind(this));

      tile.key = this.getRevision().toString();
      this.tileCache.set(tileCoordKey, tile);
      return tile;
    }
  }

  /**
   * @inheritDoc
   */
  getTileGridForProjection(projection) {
    const code = projection.getCode();
    let tileGrid = this.tileGrids_[code];
    if (!tileGrid) {
      // A tile grid that matches the tile size of the source tile grid is more
      // likely to have 1:1 relationships between source tiles and rendered tiles.
      const sourceTileGrid = this.tileGrid;
      tileGrid = this.tileGrids_[code] = createForProjection(projection, undefined,
        sourceTileGrid ? sourceTileGrid.getTileSize(sourceTileGrid.getMinZoom()) : undefined);
    }
    return tileGrid;
  }

  /**
   * @inheritDoc
   */
  getTilePixelRatio(pixelRatio) {
    return pixelRatio;
  }

  /**
   * @inheritDoc
   */
  getTilePixelSize(z, pixelRatio, projection) {
    const tileGrid = this.getTileGridForProjection(projection);
    const tileSize = toSize(tileGrid.getTileSize(z), this.tmpSize);
    return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)];
  }
}

export default VectorTile;

/**
 * Sets the loader for a tile.
 * @param {import("../VectorTile.js").default} tile Vector tile.
 * @param {string} url URL.
 */
export function defaultLoadFunction(tile, url) {
  const loader = loadFeaturesXhr(url, tile.getFormat(), tile.onLoad.bind(tile), tile.onError.bind(tile));
  tile.setLoader(loader);
}
<|endoftext|>


<commit_before>goog.provide('ol.tilegrid.TileGrid');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('ol.Coordinate');
goog.require('ol.Projection');
goog.require('ol.ProjectionUnits');
goog.require('ol.Size');
goog.require('ol.TileCoord');
goog.require('ol.TileRange');
goog.require('ol.array');
goog.require('ol.extent');

/**
 * @define {number} Default tile size.
 */
ol.DEFAULT_TILE_SIZE = 256;

/**
 * @define {number} Default maximum zoom for default tile grids.
 */
ol.DEFAULT_MAX_ZOOM = 42;

/**
 * @constructor
 * @param {ol.tilegrid.TileGridOptions} options Tile grid options.
 */
ol.tilegrid.TileGrid = function(options) {

  /**
   * @protected
   * @type {number}
   */
  this.minZoom = goog.isDef(options.minZoom) ? options.minZoom : 0;

  /**
   * @private
   * @type {!Array.<number>}
   */
  this.resolutions_ = options.resolutions;
  goog.asserts.assert(goog.array.isSorted(this.resolutions_, function(a, b) {
    return b - a;
  }, true));

  /**
   * @protected
   * @type {number}
   */
  this.maxZoom = this.resolutions_.length - 1;

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.origin_ = goog.isDef(options.origin) ? options.origin : null;

  /**
   * @private
   * @type {Array.<ol.Coordinate>}
   */
  this.origins_ = null;
  if (goog.isDef(options.origins)) {
    this.origins_ = options.origins;
    goog.asserts.assert(this.origins_.length == this.maxZoom + 1);
  }
  goog.asserts.assert(
      (goog.isNull(this.origin_) && !goog.isNull(this.origins_)) ||
      (!goog.isNull(this.origin_) && goog.isNull(this.origins_)));

  /**
   * @private
   * @type {Array.<ol.Size>}
   */
  this.tileSizes_ = null;
  if (goog.isDef(options.tileSizes)) {
    this.tileSizes_ = options.tileSizes;
    goog.asserts.assert(this.tileSizes_.length == this.maxZoom + 1);
  }

  /**
   * @private
   * @type {ol.Size}
   */
  this.tileSize_ = goog.isDef(options.tileSize) ?
      options.tileSize :
      goog.isNull(this.tileSizes_) ?
          [ol.DEFAULT_TILE_SIZE, ol.DEFAULT_TILE_SIZE] : null;
  goog.asserts.assert(
      (goog.isNull(this.tileSize_) && !goog.isNull(this.tileSizes_)) ||
      (!goog.isNull(this.tileSize_) && goog.isNull(this.tileSizes_)));

};

/**
 * @private
 * @type {ol.TileCoord}
 */
ol.tilegrid.TileGrid.tmpTileCoord_ = new ol.TileCoord(0, 0, 0);

/**
 * @param {{extent: (ol.Extent|undefined),
 *          wrapX: (boolean|undefined)}=} opt_options Options.
 * @return {function(ol.TileCoord, ol.Projection, ol.TileCoord=): ol.TileCoord}
 *     Tile coordinate transform.
 */
ol.tilegrid.TileGrid.prototype.createTileCoordTransform = goog.abstractMethod;

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {function(this: T, number, ol.TileRange): boolean} callback Callback.
 * @param {T=} opt_obj Object.
 * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {boolean} Callback succeeded.
 * @template T
 */
ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange =
    function(tileCoord, callback, opt_obj, opt_tileRange, opt_extent) {
  var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
  var z = tileCoord.z - 1;
  while (z >= this.minZoom) {
    if (callback.call(opt_obj, z,
        this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) {
      return true;
    }
    --z;
  }
  return false;
};

/**
 * @return {number} Max zoom.
 */
ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
  return this.maxZoom;
};

/**
 * @return {number} Min zoom.
 */
ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
  return this.minZoom;
};

/**
 * @param {number} z Z.
 * @return {ol.Coordinate} Origin.
 */
ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
  if (!goog.isNull(this.origin_)) {
    return this.origin_;
  } else {
    goog.asserts.assert(!goog.isNull(this.origins_));
    goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
    return this.origins_[z];
  }
};

/**
 * @param {number} z Z.
 * @return {number} Resolution.
 */
ol.tilegrid.TileGrid.prototype.getResolution = function(z) {
  goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
  return this.resolutions_[z];
};

/**
 * @return {Array.<number>} Resolutions.
 */
ol.tilegrid.TileGrid.prototype.getResolutions = function() {
  return this.resolutions_;
};

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange =
    function(tileCoord, opt_tileRange, opt_extent) {
  if (tileCoord.z < this.resolutions_.length) {
    var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
    return this.getTileRangeForExtentAndZ(
        tileCoordExtent, tileCoord.z + 1, opt_tileRange);
  } else {
    return null;
  }
};

/**
 * @param {number} z Z.
 * @param {ol.TileRange} tileRange Tile range.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {ol.Extent} Extent.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeExtent =
    function(z, tileRange, opt_extent) {
  var origin = this.getOrigin(z);
  var resolution = this.getResolution(z);
  var tileSize = this.getTileSize(z);
  var minX = origin[0] + tileRange.minX * tileSize[0] * resolution;
  var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution;
  var minY = origin[1] + tileRange.minY * tileSize[1] * resolution;
  var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution;
  return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent);
};

/**
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution =
    function(extent, resolution, opt_tileRange) {
  var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_;
  this.getTileCoordForXYAndResolution_(
      extent[0], extent[2], resolution, false, tileCoord);
  var minX = tileCoord.x;
  var minY = tileCoord.y;
  this.getTileCoordForXYAndResolution_(
      extent[1], extent[3], resolution, true, tileCoord);
  return ol.TileRange.createOrUpdate(
      minX, tileCoord.x, minY, tileCoord.y, opt_tileRange);
};

/**
 * @param {ol.Extent} extent Extent.
 * @param {number} z Z.
 * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ =
    function(extent, z, opt_tileRange) {
  var resolution = this.getResolution(z);
  return this.getTileRangeForExtentAndResolution(
      extent, resolution, opt_tileRange);
};

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @return {ol.Coordinate} Tile center.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) {
  var origin = this.getOrigin(tileCoord.z);
  var resolution = this.getResolution(tileCoord.z);
  var tileSize = this.getTileSize(tileCoord.z);
  return [
    origin[0] + (tileCoord.x + 0.5) * tileSize[0] * resolution,
    origin[1] + (tileCoord.y + 0.5) * tileSize[1] * resolution
  ];
};

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Extent=} opt_extent Temporary extent object.
 * @return {ol.Extent} Extent.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordExtent =
    function(tileCoord, opt_extent) {
  var origin = this.getOrigin(tileCoord.z);
  var resolution = this.getResolution(tileCoord.z);
  var tileSize = this.getTileSize(tileCoord.z);
  var minX = origin[0] + tileCoord.x * tileSize[0] * resolution;
  var maxX = minX + tileSize[0] * resolution;
  var minY = origin[1] + tileCoord.y * tileSize[1] * resolution;
  var maxY = minY + tileSize[1] * resolution;
  return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent);
};

/**
 * Get the tile coordinate for the given map coordinate and resolution.  This
 * method considers that coordinates that intersect tile boundaries should be
 * assigned the higher tile coordinate.
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} resolution Resolution.
 * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(
    coordinate, resolution, opt_tileCoord) {
  return this.getTileCoordForXYAndResolution_(
      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
};

/**
 * @param {number} x X.
 * @param {number} y Y.
 * @param {number} resolution Resolution.
 * @param {boolean} reverseIntersectionPolicy Instead of letting edge
 *     intersections go to the higher tile coordinate, let edge intersections
 *     go to the lower tile coordinate.
 * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 * @private
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function(
    x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) {
  var z = this.getZForResolution(resolution);
  var scale = resolution / this.getResolution(z);
  var origin = this.getOrigin(z);
  var tileSize = this.getTileSize(z);

  var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize[0]);
  var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize[1]);

  if (reverseIntersectionPolicy) {
    tileCoordX = Math.ceil(tileCoordX) - 1;
    tileCoordY = Math.ceil(tileCoordY) - 1;
  } else {
    tileCoordX = Math.floor(tileCoordX);
    tileCoordY = Math.floor(tileCoordY);
  }

  return ol.TileCoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord);
};

/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} z Z.
 * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ =
    function(coordinate, z, opt_tileCoord) {
  var resolution = this.getResolution(z);
  return this.getTileCoordForXYAndResolution_(
      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
};

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @return {number} Tile resolution.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) {
  goog.asserts.assert(
      this.minZoom <= tileCoord.z && tileCoord.z <= this.maxZoom);
  return this.resolutions_[tileCoord.z];
};

/**
 * @param {number} z Z.
 * @return {ol.Size} Tile size.
 */
ol.tilegrid.TileGrid.prototype.getTileSize = function(z) {
  if (!goog.isNull(this.tileSize_)) {
    return this.tileSize_;
  } else {
    goog.asserts.assert(!goog.isNull(this.tileSizes_));
    goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
    return this.tileSizes_[z];
  }
};

/**
 * @param {number} resolution Resolution.
 * @return {number} Z.
 */
ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) {
  return ol.array.linearFindNearest(this.resolutions_, resolution, 0);
};

/**
 * @param {ol.Projection} projection Projection.
 * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection.
 */
ol.tilegrid.getForProjection = function(projection) {
  var tileGrid = projection.getDefaultTileGrid();
  if (goog.isNull(tileGrid)) {
    tileGrid = ol.tilegrid.createForProjection(projection);
    projection.setDefaultTileGrid(tileGrid);
  }
  return tileGrid;
};

/**
 * @param {ol.Projection} projection Projection.
 * @param {number=} opt_maxZoom Maximum zoom level.
 * @param {ol.Size=} opt_tileSize Tile size.
 * @return {ol.tilegrid.TileGrid} TileGrid instance.
 */
ol.tilegrid.createForProjection =
    function(projection, opt_maxZoom, opt_tileSize) {
  var projectionExtent = projection.getExtent();
  var size = goog.isNull(projectionExtent) ?
      360 * ol.METERS_PER_UNIT[ol.ProjectionUnits.DEGREES] /
          projection.getMetersPerUnit() :
      Math.max(projectionExtent[1] - projectionExtent[0],
          projectionExtent[3] - projectionExtent[2]);
  var maxZoom = goog.isDef(opt_maxZoom) ?
      opt_maxZoom : ol.DEFAULT_MAX_ZOOM;
  var tileSize = goog.isDef(opt_tileSize) ?
      opt_tileSize : [ol.DEFAULT_TILE_SIZE, ol.DEFAULT_TILE_SIZE];
  var resolutions = new Array(maxZoom + 1);
  var maxResolution = size / Math.max(tileSize[0], tileSize[1]);
  for (var z = 0, zz = resolutions.length; z < zz; ++z) {
    resolutions[z] = maxResolution / Math.pow(2, z);
  }
  return new ol.tilegrid.TileGrid({
    origin: goog.isNull(projectionExtent) ? [0, 0] :
        ol.extent.getBottomLeft(projectionExtent),
    resolutions: resolutions,
    tileSize: tileSize
  });
};
<commit_msg>Tile has a child if z < maxZoom<commit_after>goog.provide('ol.tilegrid.TileGrid');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('ol.Coordinate');
goog.require('ol.Projection');
goog.require('ol.ProjectionUnits');
goog.require('ol.Size');
goog.require('ol.TileCoord');
goog.require('ol.TileRange');
goog.require('ol.array');
goog.require('ol.extent');

/**
 * @define {number} Default tile size.
 */
ol.DEFAULT_TILE_SIZE = 256;

/**
 * @define {number} Default maximum zoom for default tile grids.
 */
ol.DEFAULT_MAX_ZOOM = 42;

/**
 * @constructor
 * @param {ol.tilegrid.TileGridOptions} options Tile grid options.
 */
ol.tilegrid.TileGrid = function(options) {

  /**
   * @protected
   * @type {number}
   */
  this.minZoom = goog.isDef(options.minZoom) ? options.minZoom : 0;

  /**
   * @private
   * @type {!Array.<number>}
   */
  this.resolutions_ = options.resolutions;
  goog.asserts.assert(goog.array.isSorted(this.resolutions_, function(a, b) {
    return b - a;
  }, true));

  /**
   * @protected
   * @type {number}
   */
  this.maxZoom = this.resolutions_.length - 1;

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.origin_ = goog.isDef(options.origin) ? options.origin : null;

  /**
   * @private
   * @type {Array.<ol.Coordinate>}
   */
  this.origins_ = null;
  if (goog.isDef(options.origins)) {
    this.origins_ = options.origins;
    goog.asserts.assert(this.origins_.length == this.maxZoom + 1);
  }
  goog.asserts.assert(
      (goog.isNull(this.origin_) && !goog.isNull(this.origins_)) ||
      (!goog.isNull(this.origin_) && goog.isNull(this.origins_)));

  /**
   * @private
   * @type {Array.<ol.Size>}
   */
  this.tileSizes_ = null;
  if (goog.isDef(options.tileSizes)) {
    this.tileSizes_ = options.tileSizes;
    goog.asserts.assert(this.tileSizes_.length == this.maxZoom + 1);
  }

  /**
   * @private
   * @type {ol.Size}
   */
  this.tileSize_ = goog.isDef(options.tileSize) ?
      options.tileSize :
      goog.isNull(this.tileSizes_) ?
          [ol.DEFAULT_TILE_SIZE, ol.DEFAULT_TILE_SIZE] : null;
  goog.asserts.assert(
      (goog.isNull(this.tileSize_) && !goog.isNull(this.tileSizes_)) ||
      (!goog.isNull(this.tileSize_) && goog.isNull(this.tileSizes_)));

};

/**
 * @private
 * @type {ol.TileCoord}
 */
ol.tilegrid.TileGrid.tmpTileCoord_ = new ol.TileCoord(0, 0, 0);

/**
 * @param {{extent: (ol.Extent|undefined),
 *          wrapX: (boolean|undefined)}=} opt_options Options.
 * @return {function(ol.TileCoord, ol.Projection, ol.TileCoord=): ol.TileCoord}
 *     Tile coordinate transform.
 */
ol.tilegrid.TileGrid.prototype.createTileCoordTransform = goog.abstractMethod;

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {function(this: T, number, ol.TileRange): boolean} callback Callback.
 * @param {T=} opt_obj Object.
 * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {boolean} Callback succeeded.
 * @template T
 */
ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange =
    function(tileCoord, callback, opt_obj, opt_tileRange, opt_extent) {
  var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
  var z = tileCoord.z - 1;
  while (z >= this.minZoom) {
    if (callback.call(opt_obj, z,
        this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) {
      return true;
    }
    --z;
  }
  return false;
};

/**
 * @return {number} Max zoom.
 */
ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
  return this.maxZoom;
};

/**
 * @return {number} Min zoom.
 */
ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
  return this.minZoom;
};

/**
 * @param {number} z Z.
 * @return {ol.Coordinate} Origin.
 */
ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
  if (!goog.isNull(this.origin_)) {
    return this.origin_;
  } else {
    goog.asserts.assert(!goog.isNull(this.origins_));
    goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
    return this.origins_[z];
  }
};

/**
 * @param {number} z Z.
 * @return {number} Resolution.
 */
ol.tilegrid.TileGrid.prototype.getResolution = function(z) {
  goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
  return this.resolutions_[z];
};

/**
 * @return {Array.<number>} Resolutions.
 */
ol.tilegrid.TileGrid.prototype.getResolutions = function() {
  return this.resolutions_;
};

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange =
    function(tileCoord, opt_tileRange, opt_extent) {
  if (tileCoord.z < this.maxZoom) {
    var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
    return this.getTileRangeForExtentAndZ(
        tileCoordExtent, tileCoord.z + 1, opt_tileRange);
  } else {
    return null;
  }
};

/**
 * @param {number} z Z.
 * @param {ol.TileRange} tileRange Tile range.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {ol.Extent} Extent.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeExtent =
    function(z, tileRange, opt_extent) {
  var origin = this.getOrigin(z);
  var resolution = this.getResolution(z);
  var tileSize = this.getTileSize(z);
  var minX = origin[0] + tileRange.minX * tileSize[0] * resolution;
  var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution;
  var minY = origin[1] + tileRange.minY * tileSize[1] * resolution;
  var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution;
  return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent);
};

/**
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution =
    function(extent, resolution, opt_tileRange) {
  var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_;
  this.getTileCoordForXYAndResolution_(
      extent[0], extent[2], resolution, false, tileCoord);
  var minX = tileCoord.x;
  var minY = tileCoord.y;
  this.getTileCoordForXYAndResolution_(
      extent[1], extent[3], resolution, true, tileCoord);
  return ol.TileRange.createOrUpdate(
      minX, tileCoord.x, minY, tileCoord.y, opt_tileRange);
};

/**
 * @param {ol.Extent} extent Extent.
 * @param {number} z Z.
 * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ =
    function(extent, z, opt_tileRange) {
  var resolution = this.getResolution(z);
  return this.getTileRangeForExtentAndResolution(
      extent, resolution, opt_tileRange);
};

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @return {ol.Coordinate} Tile center.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) {
  var origin = this.getOrigin(tileCoord.z);
  var resolution = this.getResolution(tileCoord.z);
  var tileSize = this.getTileSize(tileCoord.z);
  return [
    origin[0] + (tileCoord.x + 0.5) * tileSize[0] * resolution,
    origin[1] + (tileCoord.y + 0.5) * tileSize[1] * resolution
  ];
};

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Extent=} opt_extent Temporary extent object.
 * @return {ol.Extent} Extent.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordExtent =
    function(tileCoord, opt_extent) {
  var origin = this.getOrigin(tileCoord.z);
  var resolution = this.getResolution(tileCoord.z);
  var tileSize = this.getTileSize(tileCoord.z);
  var minX = origin[0] + tileCoord.x * tileSize[0] * resolution;
  var maxX = minX + tileSize[0] * resolution;
  var minY = origin[1] + tileCoord.y * tileSize[1] * resolution;
  var maxY = minY + tileSize[1] * resolution;
  return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent);
};

/**
 * Get the tile coordinate for the given map coordinate and resolution.  This
 * method considers that coordinates that intersect tile boundaries should be
 * assigned the higher tile coordinate.
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} resolution Resolution.
 * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(
    coordinate, resolution, opt_tileCoord) {
  return this.getTileCoordForXYAndResolution_(
      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
};

/**
 * @param {number} x X.
 * @param {number} y Y.
 * @param {number} resolution Resolution.
 * @param {boolean} reverseIntersectionPolicy Instead of letting edge
 *     intersections go to the higher tile coordinate, let edge intersections
 *     go to the lower tile coordinate.
 * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 * @private
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function(
    x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) {
  var z = this.getZForResolution(resolution);
  var scale = resolution / this.getResolution(z);
  var origin = this.getOrigin(z);
  var tileSize = this.getTileSize(z);

  var tileCoordX = scale * (x - origin[0]) / (resolution * tileSize[0]);
  var tileCoordY = scale * (y - origin[1]) / (resolution * tileSize[1]);

  if (reverseIntersectionPolicy) {
    tileCoordX = Math.ceil(tileCoordX) - 1;
    tileCoordY = Math.ceil(tileCoordY) - 1;
  } else {
    tileCoordX = Math.floor(tileCoordX);
    tileCoordY = Math.floor(tileCoordY);
  }

  return ol.TileCoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord);
};

/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} z Z.
 * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ =
    function(coordinate, z, opt_tileCoord) {
  var resolution = this.getResolution(z);
  return this.getTileCoordForXYAndResolution_(
      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
};

/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @return {number} Tile resolution.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) {
  goog.asserts.assert(
      this.minZoom <= tileCoord.z && tileCoord.z <= this.maxZoom);
  return this.resolutions_[tileCoord.z];
};

/**
 * @param {number} z Z.
 * @return {ol.Size} Tile size.
 */
ol.tilegrid.TileGrid.prototype.getTileSize = function(z) {
  if (!goog.isNull(this.tileSize_)) {
    return this.tileSize_;
  } else {
    goog.asserts.assert(!goog.isNull(this.tileSizes_));
    goog.asserts.assert(this.minZoom <= z && z <= this.maxZoom);
    return this.tileSizes_[z];
  }
};

/**
 * @param {number} resolution Resolution.
 * @return {number} Z.
 */
ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) {
  return ol.array.linearFindNearest(this.resolutions_, resolution, 0);
};

/**
 * @param {ol.Projection} projection Projection.
 * @return {ol.tilegrid.TileGrid} Default tile grid for the passed projection.
 */
ol.tilegrid.getForProjection = function(projection) {
  var tileGrid = projection.getDefaultTileGrid();
  if (goog.isNull(tileGrid)) {
    tileGrid = ol.tilegrid.createForProjection(projection);
    projection.setDefaultTileGrid(tileGrid);
  }
  return tileGrid;
};

/**
 * @param {ol.Projection} projection Projection.
 * @param {number=} opt_maxZoom Maximum zoom level.
 * @param {ol.Size=} opt_tileSize Tile size.
 * @return {ol.tilegrid.TileGrid} TileGrid instance.
 */
ol.tilegrid.createForProjection =
    function(projection, opt_maxZoom, opt_tileSize) {
  var projectionExtent = projection.getExtent();
  var size = goog.isNull(projectionExtent) ?
      360 * ol.METERS_PER_UNIT[ol.ProjectionUnits.DEGREES] /
          projection.getMetersPerUnit() :
      Math.max(projectionExtent[1] - projectionExtent[0],
          projectionExtent[3] - projectionExtent[2]);
  var maxZoom = goog.isDef(opt_maxZoom) ?
      opt_maxZoom : ol.DEFAULT_MAX_ZOOM;
  var tileSize = goog.isDef(opt_tileSize) ?
      opt_tileSize : [ol.DEFAULT_TILE_SIZE, ol.DEFAULT_TILE_SIZE];
  var resolutions = new Array(maxZoom + 1);
  var maxResolution = size / Math.max(tileSize[0], tileSize[1]);
  for (var z = 0, zz = resolutions.length; z < zz; ++z) {
    resolutions[z] = maxResolution / Math.pow(2, z);
  }
  return new ol.tilegrid.TileGrid({
    origin: goog.isNull(projectionExtent) ? [0, 0] :
        ol.extent.getBottomLeft(projectionExtent),
    resolutions: resolutions,
    tileSize: tileSize
  });
};
<|endoftext|>


<commit_before>/* @flow */

import bitcoin from 'bitcoinjs-lib'
import { decodeB40, hash160, hash128, DUST_MINIMUM } from './utils'
import { config } from '../config'
import bigi from 'bigi'

// todo : add name length / character verification

export class BlockstackNamespace {
  namespaceID: string
  version: number
  lifetime: number
  coeff: number
  base: number
  buckets: Array<number>
  nonalphaDiscount: number
  noVowelDiscount: number

  constructor(namespaceID: string) {
    if (namespaceID.length > 19) {
      throw new Error("Namespace ID too long (19 chars max)")
    }
    if (!namespaceID.match('[0123456789abcdefghijklmnopqrstuvwxyz_-]+')) {
      throw new Error("Namespace ID can only use characters 0123456789abcdefghijklmnopqrstuvwxyz-_")
    }

    this.namespaceID = namespaceID
    this.version = -1
    this.lifetime = -1
    this.coeff = -1
    this.base = -1
    this.buckets = [-1]
    this.nonalphaDiscount = -1
    this.noVowelDiscount = -1
  }

  check() {
    try {
      this.setVersion(this.version)
      this.setLifetime(this.lifetime)
      this.setCoeff(this.coeff)
      this.setBase(this.base)
      this.setBuckets(this.buckets)
      this.setNonalphaDiscount(this.nonalphaDiscount)
      this.setNoVowelDiscount(this.noVowelDiscount)
      return true
    }
    catch (e) {
      return false
    }
  }

  setVersion(version: number) {
    if (version < 0 || version > 2**16 - 1) {
      throw new Error("Invalid version: must be a 16-bit number")
    }
    this.version = version
  }

  setLifetime(lifetime: number) {
    if (lifetime < 0 || lifetime > 2**32 - 1) {
      throw new Error("Invalid lifetime: must be a 32-bit number")
    }
    this.lifetime = lifetime
  }

  setCoeff(coeff: number) {
    if (coeff < 0 || coeff > 255) {
      throw new Error("Invalid coeff: must be an 8-bit number")
    }
    this.coeff = coeff
  }

  setBase(base: number) {
    if (base < 0 || base > 255) {
      throw new Error("Invalid base: must be an 8-bit number")
    }
    this.base = base
  }

  setBuckets(buckets: Array<number>) {
    if (buckets.length != 16) {
      throw new Error("Invalid buckets: must have 16 entries")
    }

    for (let i = 0; i < buckets.length; i++) {
      if (buckets[i] < 0 || buckets[i] > 15) {
        throw new Error("Invalid buckets: must be 4-bit numbers")
      }
    }

    this.buckets = buckets.slice(0)
  }

  setNonalphaDiscount(nonalphaDiscount: number) {
    if (nonalphaDiscount < 0 || nonalphaDiscount > 15) {
      throw new Error("Invalid nonalphaDiscount: must be a 4-bit number")
    }
    this.nonalphaDiscount = nonalphaDiscount
  }

  setNoVowelDiscount(noVowelDiscount: number) {
    if (noVowelDiscount < 0 || noVowelDiscount > 15) {
      throw new Error("Invalid noVowelDiscount: must be a 4-bit number")
    }
    this.noVowelDiscount = noVowelDiscount
  }

  toHexPayload() {
    const lifeHex = ('00000000' + this.lifetime.toString(16)).slice(-8)
    const coeffHex = ('00' + this.coeff.toString(16)).slice(-2)
    const baseHex = ('00' + this.base.toString(16)).slice(-2)
    const bucketHex = this.buckets.map((b) => {return b.toString(16);}).reduce((b1, b2) => {return b1 + b2}, '')
    const discountHex = this.nonalphaDiscount.toString(16) + this.noVowelDiscount.toString(16)
    const versionHex = ('0000' + this.version.toString(16)).slice(-4)
    const namespaceIDHex = new Buffer(this.namespaceID).toString('hex')

    return lifeHex + coeffHex + baseHex + bucketHex + discountHex + versionHex + namespaceIDHex
  }
}

export function makePreorderSkeleton(
  fullyQualifiedName: string, consensusHash : string, preorderAddress: string,
  burnAddress : string, burnAmount: {units: string, amount: Object},
  registerAddress: ?string = null) {
  // Returns a preorder tx skeleton.
  //   with 3 outputs : 1. the Blockstack Preorder OP_RETURN data
  //                    2. the Preorder's change address (5500 satoshi minimum)
  //                    3. the BURN
  //
  //    0     2  3                                              23             39          47
  //    |-----|--|----------------------------------------------|--------------|-----------|
  //    magic op  hash160(name.ns_id,script_pubkey,register_addr) consensus hash  STACKS
  //                                                                             (optional)

  // Returns an unsigned serialized transaction.
  const network = config.network
  const nameBuff = Buffer.from(decodeB40(fullyQualifiedName), 'hex') // base40
  const scriptPublicKey = bitcoin.address.toOutputScript(preorderAddress, network.layer1)

  const dataBuffers = [nameBuff, scriptPublicKey]

  if (registerAddress) {
    const registerBuff = Buffer.from(registerAddress, 'ascii')
    dataBuffers.push(registerBuff)
  }

  const dataBuff = Buffer.concat(dataBuffers)

  const hashed = hash160(dataBuff)

  const opReturnBufferLen = burnAmount.units == 'BTC' ? 40 : 48
  const opReturnBuffer = Buffer.alloc(opReturnBufferLen)
  opReturnBuffer.write('id?', 0, 3, 'ascii')
  hashed.copy(opReturnBuffer, 3)
  opReturnBuffer.write(consensusHash, 23, 16, 'hex')

  if (burnAmount.units == 'STACKS') {
    const burnHex = burnAmount.amount.toHex()
    const paddedBurnHex = ('0000000000000000' + burnHex).slice(-16)
    opReturnBuffer.write(paddedBurnHex, 40, 8, 'hex')
  }

  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)
  tx.addOutput(preorderAddress, DUST_MINIMUM)

  if (burnAmount.units == 'BTC') {
    const btcBurnAmount = parseInt(burnAmount.amount.toHex(), 16)
    tx.addOutput(burnAddress, btcBurnAmount)
  }
  else {
    tx.addOutput(burnAddress, DUST_MINIMUM)
  }

  return tx.buildIncomplete()
}

export function makeRegisterSkeleton(
  fullyQualifiedName: string, ownerAddress: string,
  valueHash: ?string = null, burnTokenAmount: ?Object = null) {
  // Returns a register tx skeleton.
  //   with 2 outputs : 1. The register OP_RETURN
  //                    2. The owner address (can be different from REGISTER address on renewals)

  // You MUST make the first input a UTXO from the current OWNER *or* the
  //   funder of the PREORDER

  // in the case of a renewal, this would need to be modified to include a change address
  //  as output (3) before the burn output (4)

  let payload
  const network = config.network

  if (valueHash) {
    if (valueHash.length !== 40) {
      throw new Error('Value hash length incorrect. Expecting 20-bytes, hex-encoded')
    }
    const payloadLen = burnTokenAmount !== null ? 65 : 57
    payload = Buffer.alloc(payloadLen, 0)
    payload.write(fullyQualifiedName, 0, 37, 'ascii')
    payload.write(valueHash, 37, 20, 'hex')
    if (burnTokenAmount !== null) {
      const burnHex = burnTokenAmount.toHex()
      const paddedBurnHex = ('0000000000000000' + burnHex).slice(-16)
      payload.write(paddedBurnHex, 57, 16, 'hex')
    }
  } else {
    payload = Buffer.from(fullyQualifiedName, 'ascii')
  }

  const opReturnBuffer = Buffer.concat([Buffer.from('id:', 'ascii'), payload])
  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)
  tx.addOutput(ownerAddress, DUST_MINIMUM)

  return tx.buildIncomplete()
}

export function makeRenewalSkeleton(
  fullyQualifiedName: string, nextOwnerAddress: string, lastOwnerAddress: string,
  burnAddress: string, burnAmount: {units: string, amount: Object}, valueHash: ?string = null) {
  const network = config.network
  const burnTokenAmount = burnAmount.units === 'BTC' ? null : burnAmount.amount
  const burnBTCAmount = burnAmount.units == 'BTC' ? parseInt(burnAmount.amount.toHex(), 16) : DUST_MINIMUM
  const registerTX = makeRegisterSkeleton(
    fullyQualifiedName, nextOwnerAddress, valueHash, burnTokenAmount)
  const txB = bitcoin.TransactionBuilder.fromTransaction(
    registerTX, network.layer1)
  txB.addOutput(lastOwnerAddress, DUST_MINIMUM)
  txB.addOutput(burnAddress, burnBTCAmount)
  return txB.buildIncomplete()
}

export function makeTransferSkeleton(
  fullyQualifiedName: string, consensusHash: string, newOwner: string,
  keepZonefile: boolean = false) {
  // Returns a transfer tx skeleton.
  //   with 2 outputs : 1. the Blockstack Transfer OP_RETURN data
  //                    2. the new owner with a DUST_MINIMUM value (5500 satoshi)
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction.
  const network = config.network
  const opRet = Buffer.alloc(36)
  let keepChar = '~'
  if (keepZonefile) {
    keepChar = '>'
  }

  opRet.write('id>', 0, 3, 'ascii')
  opRet.write(keepChar, 3, 1, 'ascii')

  const hashed = hash128(Buffer.from(fullyQualifiedName, 'ascii'))
  hashed.copy(opRet, 4)
  opRet.write(consensusHash, 20, 16, 'hex')

  const opRetPayload = bitcoin.script.nullDataOutput(opRet)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(opRetPayload, 0)
  tx.addOutput(newOwner, DUST_MINIMUM)

  return tx.buildIncomplete()
}

export function makeUpdateSkeleton(
  fullyQualifiedName: string, consensusHash: string, valueHash: string) {
  // Returns an update tx skeleton.
  //   with 1 output : 1. the Blockstack update OP_RETURN
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction.
  const network = config.network
  const opRet = Buffer.alloc(39)

  const nameBuff = Buffer.from(fullyQualifiedName, 'ascii')
  const consensusBuff = Buffer.from(consensusHash, 'ascii')

  const hashedName = hash128(Buffer.concat(
    [nameBuff, consensusBuff]))

  opRet.write('id+', 0, 3, 'ascii')
  hashedName.copy(opRet, 3)
  opRet.write(valueHash, 19, 20, 'hex')

  const opRetPayload = bitcoin.script.nullDataOutput(opRet)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(opRetPayload, 0)

  return tx.buildIncomplete()
}

export function makeRevokeSkeleton(fullyQualifiedName: string) {
  // Returns a revoke tx skeleton
  //    with 1 output: 1. the Blockstack revoke OP_RETURN
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction
  const network = config.network
  const opRet = Buffer.alloc(3)

  const nameBuff = Buffer.from(fullyQualifiedName, 'ascii')

  opRet.write('id~', 0, 3, 'ascii')

  const opReturnBuffer = Buffer.concat([opRet, nameBuff])
  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)

  return tx.buildIncomplete()
}

export function makeNamespacePreorderSkeleton(
  namespaceID: string, consensusHash : string, preorderAddress: string,
  registerAddress: string, burnAmount: {units: string, amount: Object}) {
  // Returns a namespace preorder tx skeleton.
  // Returns an unsigned serialized transaction.
  if (burnAmount.Units !== 'BTC' && burnAmount.units !== 'STACKS') {
    throw new Error(`Invalid burnUnits ${burnUnits}`)
  }

  const network = config.network
  const burnAddress = network.coerceAddress('1111111111111111111114oLvT2')
  const namespaceIDBuff = Buffer.from(decodeB40(namespaceID), 'hex') // base40
  const scriptPublicKey = bitcoin.address.toOutputScript(preorderAddress, network.layer1)
  const registerBuff = Buffer.from(registerAddress, 'ascii')

  const dataBuffers = [namespaceIDBuff, scriptPublicKey, registerBuff]
  const dataBuff = Buffer.concat(dataBuffers)

  const hashed = hash160(dataBuff)

  let btcBurnAmount = DUST_MINIMUM
  let opReturnBufferLen = 40
  if (burnAmount.units === 'STACKS') {
    opReturnBufferLen = 48
  }
  else {
    btcBurnAmount = parseInt(burnAmount.amount.toHex(), 16)
  }

  const opReturnBuffer = Buffer.alloc(opReturnBufferLen)
  opReturnBuffer.write('id*', 0, 3, 'ascii')
  hashed.copy(opReturnBuffer, 3)
  opReturnBuffer.write(consensusHash, 23, 16, 'hex')

  if (burnAmount.units == 'STACKS') {
    const burnHex = burnAmount.amount.toHex()
    const paddedBurnHex = ('0000000000000000' + burnHex).slice(-16)
    opReturnBuffer.write(paddedBurnHex, 40, 16, 'hex')
  }

  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)
  tx.addOutput(preorderAddress, DUST_MINIMUM)
  tx.addOutput(burnAddress, btcBurnAmount)

  return tx.buildIncomplete()
}

export function makeNamespaceRevealSkeleton(
  namespace: BlockstackNamespace, revealAddress: string) {

  const network = config.network
  const hexPayload = namespace.toHexPayload()

  const opReturnBuffer = Buffer.alloc(3 + hexPayload.length/2)
  opReturnBuffer.write('id&', 0, 3, 'ascii')
  opReturnBuffer.write(hexPayload, 3, hexPayload.length/2, 'hex')

  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)
  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)
  tx.addOutput(revealAddress, DUST_MINIMUM)

  return tx.buildIncomplete()
}

export function makeNamespaceReadySkeleton(
  namespaceID: string) {

  const network = config.network
  const opReturnBuffer = Buffer.alloc(3 + namespaceID.length)
  opReturnBuffer.write('id!', 0, 3, 'ascii')
  opReturnBuffer.write(namespaceID, 3, namespaceID.length, 'ascii')

  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)
  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)

  return tx.buildIncomplete()
}
<commit_msg>de-lint<commit_after>/* @flow */

import bitcoin from 'bitcoinjs-lib'
import { decodeB40, hash160, hash128, DUST_MINIMUM } from './utils'
import { config } from '../config'

// todo : add name length / character verification

export class BlockstackNamespace {
  namespaceID: string
  version: number
  lifetime: number
  coeff: number
  base: number
  buckets: Array<number>
  nonalphaDiscount: number
  noVowelDiscount: number

  constructor(namespaceID: string) {
    if (namespaceID.length > 19) {
      throw new Error('Namespace ID too long (19 chars max)')
    }
    if (!namespaceID.match('[0123456789abcdefghijklmnopqrstuvwxyz_-]+')) {
      throw new Error('Namespace ID can only use characters 0123456789abcdefghijklmnopqrstuvwxyz-_')
    }

    this.namespaceID = namespaceID
    this.version = -1
    this.lifetime = -1
    this.coeff = -1
    this.base = -1
    this.buckets = [-1]
    this.nonalphaDiscount = -1
    this.noVowelDiscount = -1
  }

  check() {
    try {
      this.setVersion(this.version)
      this.setLifetime(this.lifetime)
      this.setCoeff(this.coeff)
      this.setBase(this.base)
      this.setBuckets(this.buckets)
      this.setNonalphaDiscount(this.nonalphaDiscount)
      this.setNoVowelDiscount(this.noVowelDiscount)
      return true
    } catch (e) {
      return false
    }
  }

  setVersion(version: number) {
    if (version < 0 || version > 2 ** 16 - 1) {
      throw new Error('Invalid version: must be a 16-bit number')
    }
    this.version = version
  }

  setLifetime(lifetime: number) {
    if (lifetime < 0 || lifetime > 2 ** 32 - 1) {
      throw new Error('Invalid lifetime: must be a 32-bit number')
    }
    this.lifetime = lifetime
  }

  setCoeff(coeff: number) {
    if (coeff < 0 || coeff > 255) {
      throw new Error('Invalid coeff: must be an 8-bit number')
    }
    this.coeff = coeff
  }

  setBase(base: number) {
    if (base < 0 || base > 255) {
      throw new Error('Invalid base: must be an 8-bit number')
    }
    this.base = base
  }

  setBuckets(buckets: Array<number>) {
    if (buckets.length !== 16) {
      throw new Error('Invalid buckets: must have 16 entries')
    }

    for (let i = 0; i < buckets.length; i++) {
      if (buckets[i] < 0 || buckets[i] > 15) {
        throw new Error('Invalid buckets: must be 4-bit numbers')
      }
    }

    this.buckets = buckets.slice(0)
  }

  setNonalphaDiscount(nonalphaDiscount: number) {
    if (nonalphaDiscount < 0 || nonalphaDiscount > 15) {
      throw new Error('Invalid nonalphaDiscount: must be a 4-bit number')
    }
    this.nonalphaDiscount = nonalphaDiscount
  }

  setNoVowelDiscount(noVowelDiscount: number) {
    if (noVowelDiscount < 0 || noVowelDiscount > 15) {
      throw new Error('Invalid noVowelDiscount: must be a 4-bit number')
    }
    this.noVowelDiscount = noVowelDiscount
  }

  toHexPayload() {
    const lifeHex = `00000000${this.lifetime.toString(16)}`.slice(-8)
    const coeffHex = `00${this.coeff.toString(16)}`.slice(-2)
    const baseHex = `00${this.base.toString(16)}`.slice(-2)
    const bucketHex = this.buckets.map((b) => b.toString(16)).reduce((b1, b2) => b1 + b2, '')
    const discountHex = this.nonalphaDiscount.toString(16) + this.noVowelDiscount.toString(16)
    const versionHex = `0000${this.version.toString(16)}`.slice(-4)
    const namespaceIDHex = new Buffer(this.namespaceID).toString('hex')

    return lifeHex + coeffHex + baseHex + bucketHex + discountHex + versionHex + namespaceIDHex
  }
}

export function makePreorderSkeleton(
  fullyQualifiedName: string, consensusHash : string, preorderAddress: string,
  burnAddress : string, burnAmount: {units: string, amount: Object},
  registerAddress: ?string = null) {
  // Returns a preorder tx skeleton.
  //   with 3 outputs : 1. the Blockstack Preorder OP_RETURN data
  //                    2. the Preorder's change address (5500 satoshi minimum)
  //                    3. the BURN
  //
  // 0     2  3                                     23             39          47            66
  // |-----|--|--------------------------------------|--------------|-----------|-------------|
  // magic op  hash160(fqn,scriptPubkey,registerAddr) consensus hash token burn  token type
  //                                                                 (optional)   (optional)

  // Returns an unsigned serialized transaction.
  const network = config.network
  const nameBuff = Buffer.from(decodeB40(fullyQualifiedName), 'hex') // base40
  const scriptPublicKey = bitcoin.address.toOutputScript(preorderAddress, network.layer1)

  const dataBuffers = [nameBuff, scriptPublicKey]

  if (registerAddress) {
    const registerBuff = Buffer.from(registerAddress, 'ascii')
    dataBuffers.push(registerBuff)
  }

  const dataBuff = Buffer.concat(dataBuffers)

  const hashed = hash160(dataBuff)

  const opReturnBufferLen = burnAmount.units === 'BTC' ? 40 : 48
  const opReturnBuffer = Buffer.alloc(opReturnBufferLen)
  opReturnBuffer.write('id?', 0, 3, 'ascii')
  hashed.copy(opReturnBuffer, 3)
  opReturnBuffer.write(consensusHash, 23, 16, 'hex')

  if (burnAmount.units !== 'BTC') {
    const burnHex = burnAmount.amount.toHex()
    const paddedBurnHex = `0000000000000000${burnHex}`.slice(-16)

    opReturnBuffer.write(paddedBurnHex, 40, 8, 'hex')
    opReturnBuffer.write(burnAmount.units, 48, burnAmount.units.length, 'ascii')
  }

  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)
  tx.addOutput(preorderAddress, DUST_MINIMUM)

  if (burnAmount.units === 'BTC') {
    const btcBurnAmount = parseInt(burnAmount.amount.toHex(), 16)
    tx.addOutput(burnAddress, btcBurnAmount)
  } else {
    tx.addOutput(burnAddress, DUST_MINIMUM)
  }

  return tx.buildIncomplete()
}

export function makeRegisterSkeleton(
  fullyQualifiedName: string, ownerAddress: string,
  valueHash: ?string = null, burnTokenAmount: ?Object = null) {
  // Returns a register tx skeleton.
  //   with 2 outputs : 1. The register OP_RETURN
  //                    2. The owner address (can be different from REGISTER address on renewals)

  // You MUST make the first input a UTXO from the current OWNER *or* the
  //   funder of the PREORDER

  // in the case of a renewal, this would need to be modified to include a change address
  //  as output (3) before the burn output (4)

  let payload
  const network = config.network

  if (valueHash) {
    if (valueHash.length !== 40) {
      throw new Error('Value hash length incorrect. Expecting 20-bytes, hex-encoded')
    }
    const payloadLen = burnTokenAmount !== null ? 65 : 57
    payload = Buffer.alloc(payloadLen, 0)
    payload.write(fullyQualifiedName, 0, 37, 'ascii')
    payload.write(valueHash, 37, 20, 'hex')
    if (burnTokenAmount !== null) {
      const burnHex = burnTokenAmount.toHex()
      const paddedBurnHex = `0000000000000000${burnHex}`.slice(-16)
      payload.write(paddedBurnHex, 57, 16, 'hex')
    }
  } else {
    payload = Buffer.from(fullyQualifiedName, 'ascii')
  }

  const opReturnBuffer = Buffer.concat([Buffer.from('id:', 'ascii'), payload])
  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)
  tx.addOutput(ownerAddress, DUST_MINIMUM)

  return tx.buildIncomplete()
}

export function makeRenewalSkeleton(
  fullyQualifiedName: string, nextOwnerAddress: string, lastOwnerAddress: string,
  burnAddress: string, burnAmount: {units: string, amount: Object}, valueHash: ?string = null) {
  const network = config.network
  const burnTokenAmount = burnAmount.units === 'BTC' ? null : burnAmount.amount
  const burnBTCAmount = burnAmount.units === 'BTC' ?
    parseInt(burnAmount.amount.toHex(), 16) : DUST_MINIMUM
  const registerTX = makeRegisterSkeleton(
    fullyQualifiedName, nextOwnerAddress, valueHash, burnTokenAmount)
  const txB = bitcoin.TransactionBuilder.fromTransaction(
    registerTX, network.layer1)
  txB.addOutput(lastOwnerAddress, DUST_MINIMUM)
  txB.addOutput(burnAddress, burnBTCAmount)
  return txB.buildIncomplete()
}

export function makeTransferSkeleton(
  fullyQualifiedName: string, consensusHash: string, newOwner: string,
  keepZonefile: boolean = false) {
  // Returns a transfer tx skeleton.
  //   with 2 outputs : 1. the Blockstack Transfer OP_RETURN data
  //                    2. the new owner with a DUST_MINIMUM value (5500 satoshi)
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction.
  const network = config.network
  const opRet = Buffer.alloc(36)
  let keepChar = '~'
  if (keepZonefile) {
    keepChar = '>'
  }

  opRet.write('id>', 0, 3, 'ascii')
  opRet.write(keepChar, 3, 1, 'ascii')

  const hashed = hash128(Buffer.from(fullyQualifiedName, 'ascii'))
  hashed.copy(opRet, 4)
  opRet.write(consensusHash, 20, 16, 'hex')

  const opRetPayload = bitcoin.script.nullDataOutput(opRet)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(opRetPayload, 0)
  tx.addOutput(newOwner, DUST_MINIMUM)

  return tx.buildIncomplete()
}

export function makeUpdateSkeleton(
  fullyQualifiedName: string, consensusHash: string, valueHash: string) {
  // Returns an update tx skeleton.
  //   with 1 output : 1. the Blockstack update OP_RETURN
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction.
  const network = config.network
  const opRet = Buffer.alloc(39)

  const nameBuff = Buffer.from(fullyQualifiedName, 'ascii')
  const consensusBuff = Buffer.from(consensusHash, 'ascii')

  const hashedName = hash128(Buffer.concat(
    [nameBuff, consensusBuff]))

  opRet.write('id+', 0, 3, 'ascii')
  hashedName.copy(opRet, 3)
  opRet.write(valueHash, 19, 20, 'hex')

  const opRetPayload = bitcoin.script.nullDataOutput(opRet)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(opRetPayload, 0)

  return tx.buildIncomplete()
}

export function makeRevokeSkeleton(fullyQualifiedName: string) {
  // Returns a revoke tx skeleton
  //    with 1 output: 1. the Blockstack revoke OP_RETURN
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction
  const network = config.network
  const opRet = Buffer.alloc(3)

  const nameBuff = Buffer.from(fullyQualifiedName, 'ascii')

  opRet.write('id~', 0, 3, 'ascii')

  const opReturnBuffer = Buffer.concat([opRet, nameBuff])
  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)

  return tx.buildIncomplete()
}

export function makeNamespacePreorderSkeleton(
  namespaceID: string, consensusHash : string, preorderAddress: string,
  registerAddress: string, burnAmount: {units: string, amount: Object}) {
  // Returns a namespace preorder tx skeleton.
  // Returns an unsigned serialized transaction.
  if (burnAmount.units !== 'BTC' && burnAmount.units !== 'STACKS') {
    throw new Error(`Invalid burnUnits ${burnAmount.units}`)
  }

  const network = config.network
  const burnAddress = network.coerceAddress('1111111111111111111114oLvT2')
  const namespaceIDBuff = Buffer.from(decodeB40(namespaceID), 'hex') // base40
  const scriptPublicKey = bitcoin.address.toOutputScript(preorderAddress, network.layer1)
  const registerBuff = Buffer.from(registerAddress, 'ascii')

  const dataBuffers = [namespaceIDBuff, scriptPublicKey, registerBuff]
  const dataBuff = Buffer.concat(dataBuffers)

  const hashed = hash160(dataBuff)

  let btcBurnAmount = DUST_MINIMUM
  let opReturnBufferLen = 40
  if (burnAmount.units === 'STACKS') {
    opReturnBufferLen = 48
  } else {
    btcBurnAmount = parseInt(burnAmount.amount.toHex(), 16)
  }

  const opReturnBuffer = Buffer.alloc(opReturnBufferLen)
  opReturnBuffer.write('id*', 0, 3, 'ascii')
  hashed.copy(opReturnBuffer, 3)
  opReturnBuffer.write(consensusHash, 23, 16, 'hex')

  if (burnAmount.units === 'STACKS') {
    const burnHex = burnAmount.amount.toHex()
    const paddedBurnHex = `0000000000000000${burnHex}`.slice(-16)
    opReturnBuffer.write(paddedBurnHex, 40, 16, 'hex')
  }

  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)

  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)
  tx.addOutput(preorderAddress, DUST_MINIMUM)
  tx.addOutput(burnAddress, btcBurnAmount)

  return tx.buildIncomplete()
}

export function makeNamespaceRevealSkeleton(
  namespace: BlockstackNamespace, revealAddress: string) {
  const network = config.network
  const hexPayload = namespace.toHexPayload()

  const opReturnBuffer = Buffer.alloc(3 + hexPayload.length / 2)
  opReturnBuffer.write('id&', 0, 3, 'ascii')
  opReturnBuffer.write(hexPayload, 3, hexPayload.length / 2, 'hex')

  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)
  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)
  tx.addOutput(revealAddress, DUST_MINIMUM)

  return tx.buildIncomplete()
}

export function makeNamespaceReadySkeleton(
  namespaceID: string) {
  const network = config.network
  const opReturnBuffer = Buffer.alloc(3 + namespaceID.length)
  opReturnBuffer.write('id!', 0, 3, 'ascii')
  opReturnBuffer.write(namespaceID, 3, namespaceID.length, 'ascii')

  const nullOutput = bitcoin.script.nullDataOutput(opReturnBuffer)
  const tx = new bitcoin.TransactionBuilder(network.layer1)

  tx.addOutput(nullOutput, 0)

  return tx.buildIncomplete()
}
<|endoftext|>


<commit_before>module.exports = [
  'container',
  'cover',
  'stretch',
];
<commit_msg>Fixes #31. Proper propTypes for image resizeMode<commit_after>module.exports = [
  'contain',
  'cover',
  'stretch',
];
<|endoftext|>