WebMercatorTilingScheme.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import Cartesian2 from "./Cartesian2.js";
  2. import defaultValue from "./defaultValue.js";
  3. import defined from "./defined.js";
  4. import Ellipsoid from "./Ellipsoid.js";
  5. import Rectangle from "./Rectangle.js";
  6. import WebMercatorProjection from "./WebMercatorProjection.js";
  7. /**
  8. * A tiling scheme for geometry referenced to a {@link WebMercatorProjection}, EPSG:3857. This is
  9. * the tiling scheme used by Google Maps, Microsoft Bing Maps, and most of ESRI ArcGIS Online.
  10. *
  11. * @alias WebMercatorTilingScheme
  12. * @constructor
  13. *
  14. * @param {Object} [options] Object with the following properties:
  15. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
  16. * the WGS84 ellipsoid.
  17. * @param {Number} [options.numberOfLevelZeroTilesX=1] The number of tiles in the X direction at level zero of
  18. * the tile tree.
  19. * @param {Number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
  20. * the tile tree.
  21. * @param {Cartesian2} [options.rectangleSouthwestInMeters] The southwest corner of the rectangle covered by the
  22. * tiling scheme, in meters. If this parameter or rectangleNortheastInMeters is not specified, the entire
  23. * globe is covered in the longitude direction and an equal distance is covered in the latitude
  24. * direction, resulting in a square projection.
  25. * @param {Cartesian2} [options.rectangleNortheastInMeters] The northeast corner of the rectangle covered by the
  26. * tiling scheme, in meters. If this parameter or rectangleSouthwestInMeters is not specified, the entire
  27. * globe is covered in the longitude direction and an equal distance is covered in the latitude
  28. * direction, resulting in a square projection.
  29. */
  30. function WebMercatorTilingScheme(options) {
  31. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  32. this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
  33. this._numberOfLevelZeroTilesX = defaultValue(
  34. options.numberOfLevelZeroTilesX,
  35. 1
  36. );
  37. this._numberOfLevelZeroTilesY = defaultValue(
  38. options.numberOfLevelZeroTilesY,
  39. 1
  40. );
  41. this._projection = new WebMercatorProjection(this._ellipsoid);
  42. if (
  43. defined(options.rectangleSouthwestInMeters) &&
  44. defined(options.rectangleNortheastInMeters)
  45. ) {
  46. this._rectangleSouthwestInMeters = options.rectangleSouthwestInMeters;
  47. this._rectangleNortheastInMeters = options.rectangleNortheastInMeters;
  48. } else {
  49. const semimajorAxisTimesPi = this._ellipsoid.maximumRadius * Math.PI;
  50. this._rectangleSouthwestInMeters = new Cartesian2(
  51. -semimajorAxisTimesPi,
  52. -semimajorAxisTimesPi
  53. );
  54. this._rectangleNortheastInMeters = new Cartesian2(
  55. semimajorAxisTimesPi,
  56. semimajorAxisTimesPi
  57. );
  58. }
  59. const southwest = this._projection.unproject(
  60. this._rectangleSouthwestInMeters
  61. );
  62. const northeast = this._projection.unproject(
  63. this._rectangleNortheastInMeters
  64. );
  65. this._rectangle = new Rectangle(
  66. southwest.longitude,
  67. southwest.latitude,
  68. northeast.longitude,
  69. northeast.latitude
  70. );
  71. }
  72. Object.defineProperties(WebMercatorTilingScheme.prototype, {
  73. /**
  74. * Gets the ellipsoid that is tiled by this tiling scheme.
  75. * @memberof WebMercatorTilingScheme.prototype
  76. * @type {Ellipsoid}
  77. */
  78. ellipsoid: {
  79. get: function () {
  80. return this._ellipsoid;
  81. },
  82. },
  83. /**
  84. * Gets the rectangle, in radians, covered by this tiling scheme.
  85. * @memberof WebMercatorTilingScheme.prototype
  86. * @type {Rectangle}
  87. */
  88. rectangle: {
  89. get: function () {
  90. return this._rectangle;
  91. },
  92. },
  93. /**
  94. * Gets the map projection used by this tiling scheme.
  95. * @memberof WebMercatorTilingScheme.prototype
  96. * @type {MapProjection}
  97. */
  98. projection: {
  99. get: function () {
  100. return this._projection;
  101. },
  102. },
  103. });
  104. /**
  105. * Gets the total number of tiles in the X direction at a specified level-of-detail.
  106. *
  107. * @param {Number} level The level-of-detail.
  108. * @returns {Number} The number of tiles in the X direction at the given level.
  109. */
  110. WebMercatorTilingScheme.prototype.getNumberOfXTilesAtLevel = function (level) {
  111. return this._numberOfLevelZeroTilesX << level;
  112. };
  113. /**
  114. * Gets the total number of tiles in the Y direction at a specified level-of-detail.
  115. *
  116. * @param {Number} level The level-of-detail.
  117. * @returns {Number} The number of tiles in the Y direction at the given level.
  118. */
  119. WebMercatorTilingScheme.prototype.getNumberOfYTilesAtLevel = function (level) {
  120. return this._numberOfLevelZeroTilesY << level;
  121. };
  122. /**
  123. * Transforms a rectangle specified in geodetic radians to the native coordinate system
  124. * of this tiling scheme.
  125. *
  126. * @param {Rectangle} rectangle The rectangle to transform.
  127. * @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
  128. * should be created.
  129. * @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
  130. * is undefined.
  131. */
  132. WebMercatorTilingScheme.prototype.rectangleToNativeRectangle = function (
  133. rectangle,
  134. result
  135. ) {
  136. const projection = this._projection;
  137. const southwest = projection.project(Rectangle.southwest(rectangle));
  138. const northeast = projection.project(Rectangle.northeast(rectangle));
  139. if (!defined(result)) {
  140. return new Rectangle(southwest.x, southwest.y, northeast.x, northeast.y);
  141. }
  142. result.west = southwest.x;
  143. result.south = southwest.y;
  144. result.east = northeast.x;
  145. result.north = northeast.y;
  146. return result;
  147. };
  148. /**
  149. * Converts tile x, y coordinates and level to a rectangle expressed in the native coordinates
  150. * of the tiling scheme.
  151. *
  152. * @param {Number} x The integer x coordinate of the tile.
  153. * @param {Number} y The integer y coordinate of the tile.
  154. * @param {Number} level The tile level-of-detail. Zero is the least detailed.
  155. * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
  156. * should be created.
  157. * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
  158. * if 'result' is undefined.
  159. */
  160. WebMercatorTilingScheme.prototype.tileXYToNativeRectangle = function (
  161. x,
  162. y,
  163. level,
  164. result
  165. ) {
  166. const xTiles = this.getNumberOfXTilesAtLevel(level);
  167. const yTiles = this.getNumberOfYTilesAtLevel(level);
  168. const xTileWidth =
  169. (this._rectangleNortheastInMeters.x - this._rectangleSouthwestInMeters.x) /
  170. xTiles;
  171. const west = this._rectangleSouthwestInMeters.x + x * xTileWidth;
  172. const east = this._rectangleSouthwestInMeters.x + (x + 1) * xTileWidth;
  173. const yTileHeight =
  174. (this._rectangleNortheastInMeters.y - this._rectangleSouthwestInMeters.y) /
  175. yTiles;
  176. const north = this._rectangleNortheastInMeters.y - y * yTileHeight;
  177. const south = this._rectangleNortheastInMeters.y - (y + 1) * yTileHeight;
  178. if (!defined(result)) {
  179. return new Rectangle(west, south, east, north);
  180. }
  181. result.west = west;
  182. result.south = south;
  183. result.east = east;
  184. result.north = north;
  185. return result;
  186. };
  187. /**
  188. * Converts tile x, y coordinates and level to a cartographic rectangle in radians.
  189. *
  190. * @param {Number} x The integer x coordinate of the tile.
  191. * @param {Number} y The integer y coordinate of the tile.
  192. * @param {Number} level The tile level-of-detail. Zero is the least detailed.
  193. * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
  194. * should be created.
  195. * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
  196. * if 'result' is undefined.
  197. */
  198. WebMercatorTilingScheme.prototype.tileXYToRectangle = function (
  199. x,
  200. y,
  201. level,
  202. result
  203. ) {
  204. const nativeRectangle = this.tileXYToNativeRectangle(x, y, level, result);
  205. const projection = this._projection;
  206. const southwest = projection.unproject(
  207. new Cartesian2(nativeRectangle.west, nativeRectangle.south)
  208. );
  209. const northeast = projection.unproject(
  210. new Cartesian2(nativeRectangle.east, nativeRectangle.north)
  211. );
  212. nativeRectangle.west = southwest.longitude;
  213. nativeRectangle.south = southwest.latitude;
  214. nativeRectangle.east = northeast.longitude;
  215. nativeRectangle.north = northeast.latitude;
  216. return nativeRectangle;
  217. };
  218. /**
  219. * Calculates the tile x, y coordinates of the tile containing
  220. * a given cartographic position.
  221. *
  222. * @param {Cartographic} position The position.
  223. * @param {Number} level The tile level-of-detail. Zero is the least detailed.
  224. * @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
  225. * should be created.
  226. * @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
  227. * if 'result' is undefined.
  228. */
  229. WebMercatorTilingScheme.prototype.positionToTileXY = function (
  230. position,
  231. level,
  232. result
  233. ) {
  234. const rectangle = this._rectangle;
  235. if (!Rectangle.contains(rectangle, position)) {
  236. // outside the bounds of the tiling scheme
  237. return undefined;
  238. }
  239. const xTiles = this.getNumberOfXTilesAtLevel(level);
  240. const yTiles = this.getNumberOfYTilesAtLevel(level);
  241. const overallWidth =
  242. this._rectangleNortheastInMeters.x - this._rectangleSouthwestInMeters.x;
  243. const xTileWidth = overallWidth / xTiles;
  244. const overallHeight =
  245. this._rectangleNortheastInMeters.y - this._rectangleSouthwestInMeters.y;
  246. const yTileHeight = overallHeight / yTiles;
  247. const projection = this._projection;
  248. const webMercatorPosition = projection.project(position);
  249. const distanceFromWest =
  250. webMercatorPosition.x - this._rectangleSouthwestInMeters.x;
  251. const distanceFromNorth =
  252. this._rectangleNortheastInMeters.y - webMercatorPosition.y;
  253. let xTileCoordinate = (distanceFromWest / xTileWidth) | 0;
  254. if (xTileCoordinate >= xTiles) {
  255. xTileCoordinate = xTiles - 1;
  256. }
  257. let yTileCoordinate = (distanceFromNorth / yTileHeight) | 0;
  258. if (yTileCoordinate >= yTiles) {
  259. yTileCoordinate = yTiles - 1;
  260. }
  261. if (!defined(result)) {
  262. return new Cartesian2(xTileCoordinate, yTileCoordinate);
  263. }
  264. result.x = xTileCoordinate;
  265. result.y = yTileCoordinate;
  266. return result;
  267. };
  268. export default WebMercatorTilingScheme;