projection.main.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /**
  2. * DevExtreme (viz/vector_map/projection.main.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var extend = require("../../core/utils/extend").extend;
  11. var eventEmitterModule = require("./event_emitter");
  12. var _Number = Number;
  13. var _min = Math.min;
  14. var _max = Math.max;
  15. var _abs = Math.abs;
  16. var _round = Math.round;
  17. var _ln = Math.log;
  18. var _pow = Math.pow;
  19. var TWO_TO_LN2 = 2 / Math.LN2;
  20. var MIN_BOUNDS_RANGE = 1 / 3600 / 180 / 10;
  21. var DEFAULT_MIN_ZOOM = 1;
  22. var DEFAULT_MAX_ZOOM = 256;
  23. var DEFAULT_CENTER = [NaN, NaN];
  24. var DEFAULT_ENGINE_NAME = "mercator";
  25. function floatsEqual(f1, f2) {
  26. return _abs(f1 - f2) < 1e-8
  27. }
  28. function arraysEqual(a1, a2) {
  29. return floatsEqual(a1[0], a2[0]) && floatsEqual(a1[1], a2[1])
  30. }
  31. function parseAndClamp(value, minValue, maxValue, defaultValue) {
  32. var val = _Number(value);
  33. return isFinite(val) ? _min(_max(val, minValue), maxValue) : defaultValue
  34. }
  35. function parseAndClampArray(value, minValue, maxValue, defaultValue) {
  36. return [parseAndClamp(value[0], minValue[0], maxValue[0], defaultValue[0]), parseAndClamp(value[1], minValue[1], maxValue[1], defaultValue[1])]
  37. }
  38. function getEngine(engine) {
  39. return engine instanceof Engine && engine || projection.get(engine) || projection.get(DEFAULT_ENGINE_NAME)
  40. }
  41. function Projection(parameters) {
  42. var that = this;
  43. that._initEvents();
  44. that._params = parameters;
  45. that._engine = getEngine();
  46. that._center = that._engine.center();
  47. that._adjustCenter()
  48. }
  49. Projection.prototype = {
  50. constructor: Projection,
  51. _minZoom: DEFAULT_MIN_ZOOM,
  52. _maxZoom: DEFAULT_MAX_ZOOM,
  53. _zoom: DEFAULT_MIN_ZOOM,
  54. _center: DEFAULT_CENTER,
  55. _canvas: {},
  56. _scale: [],
  57. dispose: function() {
  58. this._disposeEvents()
  59. },
  60. setEngine: function(value) {
  61. var that = this;
  62. var engine = getEngine(value);
  63. if (that._engine !== engine) {
  64. that._engine = engine;
  65. that._fire("engine");
  66. if (that._changeCenter(engine.center())) {
  67. that._triggerCenterChanged()
  68. }
  69. if (that._changeZoom(that._minZoom)) {
  70. that._triggerZoomChanged()
  71. }
  72. that._adjustCenter();
  73. that._setupScreen()
  74. }
  75. },
  76. setBounds: function(bounds) {
  77. if (void 0 !== bounds) {
  78. this.setEngine(this._engine.original().bounds(bounds))
  79. }
  80. },
  81. _setupScreen: function() {
  82. var that = this;
  83. var canvas = that._canvas;
  84. var width = canvas.width;
  85. var height = canvas.height;
  86. var aspectRatio = that._engine.ar();
  87. that._x0 = canvas.left + width / 2;
  88. that._y0 = canvas.top + height / 2;
  89. if (width / height <= aspectRatio) {
  90. that._xRadius = width / 2;
  91. that._yRadius = width / 2 / aspectRatio
  92. } else {
  93. that._xRadius = height / 2 * aspectRatio;
  94. that._yRadius = height / 2
  95. }
  96. that._fire("screen")
  97. },
  98. setSize: function(canvas) {
  99. var that = this;
  100. that._canvas = canvas;
  101. that._setupScreen()
  102. },
  103. _toScreen: function(coordinates) {
  104. return [this._x0 + this._xRadius * coordinates[0], this._y0 + this._yRadius * coordinates[1]]
  105. },
  106. _fromScreen: function(coordinates) {
  107. return [(coordinates[0] - this._x0) / this._xRadius, (coordinates[1] - this._y0) / this._yRadius]
  108. },
  109. _toTransformed: function(coordinates) {
  110. return [coordinates[0] * this._zoom + this._xCenter, coordinates[1] * this._zoom + this._yCenter]
  111. },
  112. _toTransformedFast: function(coordinates) {
  113. return [coordinates[0] * this._zoom, coordinates[1] * this._zoom]
  114. },
  115. _fromTransformed: function(coordinates) {
  116. return [(coordinates[0] - this._xCenter) / this._zoom, (coordinates[1] - this._yCenter) / this._zoom]
  117. },
  118. _adjustCenter: function() {
  119. var that = this;
  120. var center = that._engine.project(that._center);
  121. that._xCenter = -center[0] * that._zoom || 0;
  122. that._yCenter = -center[1] * that._zoom || 0
  123. },
  124. project: function(coordinates) {
  125. return this._engine.project(coordinates)
  126. },
  127. transform: function(coordinates) {
  128. return this._toScreen(this._toTransformedFast(coordinates))
  129. },
  130. isInvertible: function() {
  131. return this._engine.isInvertible()
  132. },
  133. getSquareSize: function(size) {
  134. return [size[0] * this._zoom * this._xRadius, size[1] * this._zoom * this._yRadius]
  135. },
  136. getZoom: function() {
  137. return this._zoom
  138. },
  139. _changeZoom: function(value) {
  140. var that = this;
  141. var oldZoom = that._zoom;
  142. var newZoom = that._zoom = parseAndClamp(value, that._minZoom, that._maxZoom, that._minZoom);
  143. var isChanged = !floatsEqual(oldZoom, newZoom);
  144. if (isChanged) {
  145. that._adjustCenter();
  146. that._fire("zoom")
  147. }
  148. return isChanged
  149. },
  150. setZoom: function(value) {
  151. if (this._engine.isInvertible() && this._changeZoom(value)) {
  152. this._triggerZoomChanged()
  153. }
  154. },
  155. getScaledZoom: function() {
  156. return _round((this._scale.length - 1) * _ln(this._zoom) / _ln(this._maxZoom))
  157. },
  158. setScaledZoom: function(scaledZoom) {
  159. this.setZoom(this._scale[_round(scaledZoom)])
  160. },
  161. changeScaledZoom: function(deltaZoom) {
  162. this.setZoom(this._scale[_max(_min(_round(this.getScaledZoom() + deltaZoom), this._scale.length - 1), 0)])
  163. },
  164. getZoomScalePartition: function() {
  165. return this._scale.length - 1
  166. },
  167. _setupScaling: function() {
  168. var that = this;
  169. var k = _round(TWO_TO_LN2 * _ln(that._maxZoom));
  170. var i = 1;
  171. k = k > 4 ? k : 4;
  172. var step = _pow(that._maxZoom, 1 / k);
  173. var zoom = that._minZoom;
  174. that._scale = [zoom];
  175. for (; i <= k; ++i) {
  176. that._scale.push(zoom *= step)
  177. }
  178. },
  179. setMaxZoom: function(maxZoom) {
  180. var that = this;
  181. that._minZoom = DEFAULT_MIN_ZOOM;
  182. that._maxZoom = parseAndClamp(maxZoom, that._minZoom, _Number.MAX_VALUE, DEFAULT_MAX_ZOOM);
  183. that._setupScaling();
  184. if (that._zoom > that._maxZoom) {
  185. that.setZoom(that._maxZoom)
  186. }
  187. that._fire("max-zoom")
  188. },
  189. getCenter: function() {
  190. return this._center.slice()
  191. },
  192. setCenter: function(value) {
  193. if (this._engine.isInvertible() && this._changeCenter(value || [])) {
  194. this._triggerCenterChanged()
  195. }
  196. },
  197. _changeCenter: function(value) {
  198. var that = this;
  199. var engine = that._engine;
  200. var oldCenter = that._center;
  201. var newCenter = that._center = parseAndClampArray(value, engine.min(), engine.max(), engine.center());
  202. var isChanged = !arraysEqual(oldCenter, newCenter);
  203. if (isChanged) {
  204. that._adjustCenter();
  205. that._fire("center")
  206. }
  207. return isChanged
  208. },
  209. _triggerCenterChanged: function() {
  210. this._params.centerChanged(this.getCenter())
  211. },
  212. _triggerZoomChanged: function() {
  213. this._params.zoomChanged(this.getZoom())
  214. },
  215. setCenterByPoint: function(coordinates, screenPosition) {
  216. var that = this;
  217. var p = that._engine.project(coordinates);
  218. var q = that._fromScreen(screenPosition);
  219. that.setCenter(that._engine.unproject([-q[0] / that._zoom + p[0], -q[1] / that._zoom + p[1]]))
  220. },
  221. beginMoveCenter: function() {
  222. if (this._engine.isInvertible()) {
  223. this._moveCenter = this._center
  224. }
  225. },
  226. endMoveCenter: function() {
  227. var that = this;
  228. if (that._moveCenter) {
  229. if (!arraysEqual(that._moveCenter, that._center)) {
  230. that._triggerCenterChanged()
  231. }
  232. that._moveCenter = null
  233. }
  234. },
  235. moveCenter: function(shift) {
  236. var that = this;
  237. if (that._moveCenter) {
  238. var current = that._toScreen(that._toTransformed(that._engine.project(that._center)));
  239. var center = that._engine.unproject(that._fromTransformed(that._fromScreen([current[0] + shift[0], current[1] + shift[1]])));
  240. that._changeCenter(center)
  241. }
  242. },
  243. getViewport: function() {
  244. var that = this;
  245. var unproject = that._engine.unproject;
  246. var lt = unproject(that._fromTransformed([-1, -1]));
  247. var lb = unproject(that._fromTransformed([-1, 1]));
  248. var rt = unproject(that._fromTransformed([1, -1]));
  249. var rb = unproject(that._fromTransformed([1, 1]));
  250. var minMax = findMinMax([selectFarthestPoint(lt[0], lb[0], rt[0], rb[0]), selectFarthestPoint(lt[1], rt[1], lb[1], rb[1])], [selectFarthestPoint(rt[0], rb[0], lt[0], lb[0]), selectFarthestPoint(lb[1], rb[1], lt[1], rt[1])]);
  251. return [].concat(minMax.min, minMax.max)
  252. },
  253. setViewport: function(viewport) {
  254. var engine = this._engine;
  255. var data = viewport ? getZoomAndCenterFromViewport(engine.project, engine.unproject, viewport) : [this._minZoom, engine.center()];
  256. this.setZoom(data[0]);
  257. this.setCenter(data[1])
  258. },
  259. getTransform: function() {
  260. return {
  261. translateX: this._xCenter * this._xRadius,
  262. translateY: this._yCenter * this._yRadius
  263. }
  264. },
  265. fromScreenPoint: function(coordinates) {
  266. return this._engine.unproject(this._fromTransformed(this._fromScreen(coordinates)))
  267. },
  268. _eventNames: ["engine", "screen", "center", "zoom", "max-zoom"]
  269. };
  270. eventEmitterModule.makeEventEmitter(Projection);
  271. function selectFarthestPoint(point1, point2, basePoint1, basePoint2) {
  272. var basePoint = (basePoint1 + basePoint2) / 2;
  273. return _abs(point1 - basePoint) > _abs(point2 - basePoint) ? point1 : point2
  274. }
  275. function selectClosestPoint(point1, point2, basePoint1, basePoint2) {
  276. var basePoint = (basePoint1 + basePoint2) / 2;
  277. return _abs(point1 - basePoint) < _abs(point2 - basePoint) ? point1 : point2
  278. }
  279. function getZoomAndCenterFromViewport(project, unproject, viewport) {
  280. var lt = project([viewport[0], viewport[3]]);
  281. var lb = project([viewport[0], viewport[1]]);
  282. var rt = project([viewport[2], viewport[3]]);
  283. var rb = project([viewport[2], viewport[1]]);
  284. var l = selectClosestPoint(lt[0], lb[0], rt[0], rb[0]);
  285. var r = selectClosestPoint(rt[0], rb[0], lt[0], lb[0]);
  286. var t = selectClosestPoint(lt[1], rt[1], lb[1], rb[1]);
  287. var b = selectClosestPoint(lb[1], rb[1], lt[1], rt[1]);
  288. return [2 / _max(_abs(l - r), _abs(t - b)), unproject([(l + r) / 2, (t + b) / 2])]
  289. }
  290. function setMinMax(engine, p1, p2) {
  291. var minMax = findMinMax(p1, p2);
  292. engine.min = returnArray(minMax.min);
  293. engine.max = returnArray(minMax.max)
  294. }
  295. function Engine(parameters) {
  296. var that = this;
  297. var project = createProjectMethod(parameters.to);
  298. var unproject = parameters.from ? createUnprojectMethod(parameters.from) : returnValue(DEFAULT_CENTER);
  299. that.project = project;
  300. that.unproject = unproject;
  301. that.original = returnValue(that);
  302. that.source = function() {
  303. return extend({}, parameters)
  304. };
  305. that.isInvertible = returnValue(!!parameters.from);
  306. that.ar = returnValue(parameters.aspectRatio > 0 ? _Number(parameters.aspectRatio) : 1);
  307. that.center = returnArray(unproject([0, 0]));
  308. setMinMax(that, [unproject([-1, 0])[0], unproject([0, 1])[1]], [unproject([1, 0])[0], unproject([0, -1])[1]])
  309. }
  310. Engine.prototype.aspectRatio = function(aspectRatio) {
  311. var engine = new Engine(extend(this.source(), {
  312. aspectRatio: aspectRatio
  313. }));
  314. engine.original = this.original;
  315. engine.min = this.min;
  316. engine.max = this.max;
  317. return engine
  318. };
  319. Engine.prototype.bounds = function(bounds) {
  320. bounds = bounds || [];
  321. var parameters = this.source();
  322. var min = this.min();
  323. var max = this.max();
  324. var b1 = parseAndClampArray([bounds[0], bounds[1]], min, max, min);
  325. var b2 = parseAndClampArray([bounds[2], bounds[3]], min, max, max);
  326. var p1 = parameters.to(b1);
  327. var p2 = parameters.to(b2);
  328. var delta = _min(_abs(p2[0] - p1[0]) > MIN_BOUNDS_RANGE ? _abs(p2[0] - p1[0]) : 2, _abs(p2[1] - p1[1]) > MIN_BOUNDS_RANGE ? _abs(p2[1] - p1[1]) : 2);
  329. if (delta < 2) {
  330. extend(parameters, createProjectUnprojectMethods(parameters.to, parameters.from, p1, p2, delta))
  331. }
  332. var engine = new Engine(parameters);
  333. engine.original = this.original;
  334. setMinMax(engine, b1, b2);
  335. return engine
  336. };
  337. function isEngine(engine) {
  338. return engine instanceof Engine
  339. }
  340. function invertVerticalAxis(pair) {
  341. return [pair[0], -pair[1]]
  342. }
  343. function createProjectMethod(method) {
  344. return function(arg) {
  345. return invertVerticalAxis(method(arg))
  346. }
  347. }
  348. function createUnprojectMethod(method) {
  349. return function(arg) {
  350. return method(invertVerticalAxis(arg))
  351. }
  352. }
  353. function returnValue(value) {
  354. return function() {
  355. return value
  356. }
  357. }
  358. function returnArray(value) {
  359. return function() {
  360. return value.slice()
  361. }
  362. }
  363. function projection(parameters) {
  364. return parameters && parameters.to ? new Engine(parameters) : null
  365. }
  366. function findMinMax(p1, p2) {
  367. return {
  368. min: [_min(p1[0], p2[0]), _min(p1[1], p2[1])],
  369. max: [_max(p1[0], p2[0]), _max(p1[1], p2[1])]
  370. }
  371. }
  372. var projectionsCache = {};
  373. projection.get = function(name) {
  374. return projectionsCache[name] || null
  375. };
  376. projection.add = function(name, engine) {
  377. if (!projectionsCache[name] && isEngine(engine)) {
  378. projectionsCache[name] = engine
  379. }
  380. return projection
  381. };
  382. function createProjectUnprojectMethods(project, unproject, p1, p2, delta) {
  383. var x0 = (p1[0] + p2[0]) / 2 - delta / 2;
  384. var y0 = (p1[1] + p2[1]) / 2 - delta / 2;
  385. var k = 2 / delta;
  386. return {
  387. to: function(coordinates) {
  388. var p = project(coordinates);
  389. return [-1 + (p[0] - x0) * k, -1 + (p[1] - y0) * k]
  390. },
  391. from: function(coordinates) {
  392. var p = [x0 + (coordinates[0] + 1) / k, y0 + (coordinates[1] + 1) / k];
  393. return unproject(p)
  394. }
  395. }
  396. }
  397. exports.Projection = Projection;
  398. exports.projection = projection;