智慧水务管理系统 - 精河县供水工程综合管理平台

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. import {
  2. addAllToArray,
  3. binarySearch,
  4. ClockRange,
  5. ClockStep,
  6. defined,
  7. DeveloperError,
  8. JulianDate,
  9. } from "@cesium/engine";
  10. import knockout from "../ThirdParty/knockout.js";
  11. import createCommand from "../createCommand.js";
  12. import ToggleButtonViewModel from "../ToggleButtonViewModel.js";
  13. const monthNames = [
  14. "Jan",
  15. "Feb",
  16. "Mar",
  17. "Apr",
  18. "May",
  19. "Jun",
  20. "Jul",
  21. "Aug",
  22. "Sep",
  23. "Oct",
  24. "Nov",
  25. "Dec",
  26. ];
  27. const realtimeShuttleRingAngle = 15;
  28. const maxShuttleRingAngle = 105;
  29. function numberComparator(left, right) {
  30. return left - right;
  31. }
  32. function getTypicalMultiplierIndex(multiplier, shuttleRingTicks) {
  33. const index = binarySearch(shuttleRingTicks, multiplier, numberComparator);
  34. return index < 0 ? ~index : index;
  35. }
  36. function angleToMultiplier(angle, shuttleRingTicks) {
  37. //Use a linear scale for -1 to 1 between -15 < angle < 15 degrees
  38. if (Math.abs(angle) <= realtimeShuttleRingAngle) {
  39. return angle / realtimeShuttleRingAngle;
  40. }
  41. const minp = realtimeShuttleRingAngle;
  42. const maxp = maxShuttleRingAngle;
  43. let maxv;
  44. const minv = 0;
  45. let scale;
  46. if (angle > 0) {
  47. maxv = Math.log(shuttleRingTicks[shuttleRingTicks.length - 1]);
  48. scale = (maxv - minv) / (maxp - minp);
  49. return Math.exp(minv + scale * (angle - minp));
  50. }
  51. maxv = Math.log(-shuttleRingTicks[0]);
  52. scale = (maxv - minv) / (maxp - minp);
  53. return -Math.exp(minv + scale * (Math.abs(angle) - minp));
  54. }
  55. function multiplierToAngle(multiplier, shuttleRingTicks, clockViewModel) {
  56. if (clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK) {
  57. return realtimeShuttleRingAngle;
  58. }
  59. if (Math.abs(multiplier) <= 1) {
  60. return multiplier * realtimeShuttleRingAngle;
  61. }
  62. const fastedMultipler = shuttleRingTicks[shuttleRingTicks.length - 1];
  63. if (multiplier > fastedMultipler) {
  64. multiplier = fastedMultipler;
  65. } else if (multiplier < -fastedMultipler) {
  66. multiplier = -fastedMultipler;
  67. }
  68. const minp = realtimeShuttleRingAngle;
  69. const maxp = maxShuttleRingAngle;
  70. let maxv;
  71. const minv = 0;
  72. let scale;
  73. if (multiplier > 0) {
  74. maxv = Math.log(fastedMultipler);
  75. scale = (maxv - minv) / (maxp - minp);
  76. return (Math.log(multiplier) - minv) / scale + minp;
  77. }
  78. maxv = Math.log(-shuttleRingTicks[0]);
  79. scale = (maxv - minv) / (maxp - minp);
  80. return -((Math.log(Math.abs(multiplier)) - minv) / scale + minp);
  81. }
  82. /**
  83. * The view model for the {@link Animation} widget.
  84. * @alias AnimationViewModel
  85. * @constructor
  86. *
  87. * @param {ClockViewModel} clockViewModel The ClockViewModel instance to use.
  88. *
  89. * @see Animation
  90. */
  91. function AnimationViewModel(clockViewModel) {
  92. //>>includeStart('debug', pragmas.debug);
  93. if (!defined(clockViewModel)) {
  94. throw new DeveloperError("clockViewModel is required.");
  95. }
  96. //>>includeEnd('debug');
  97. const that = this;
  98. this._clockViewModel = clockViewModel;
  99. this._allShuttleRingTicks = [];
  100. this._dateFormatter = AnimationViewModel.defaultDateFormatter;
  101. this._timeFormatter = AnimationViewModel.defaultTimeFormatter;
  102. /**
  103. * Gets or sets whether the shuttle ring is currently being dragged. This property is observable.
  104. * @type {boolean}
  105. * @default false
  106. */
  107. this.shuttleRingDragging = false;
  108. /**
  109. * Gets or sets whether dragging the shuttle ring should cause the multiplier
  110. * to snap to the defined tick values rather than interpolating between them.
  111. * This property is observable.
  112. * @type {boolean}
  113. * @default false
  114. */
  115. this.snapToTicks = false;
  116. knockout.track(this, [
  117. "_allShuttleRingTicks",
  118. "_dateFormatter",
  119. "_timeFormatter",
  120. "shuttleRingDragging",
  121. "snapToTicks",
  122. ]);
  123. this._sortedFilteredPositiveTicks = [];
  124. this.setShuttleRingTicks(AnimationViewModel.defaultTicks);
  125. /**
  126. * Gets the string representation of the current time. This property is observable.
  127. * @type {string}
  128. */
  129. this.timeLabel = undefined;
  130. knockout.defineProperty(this, "timeLabel", function () {
  131. return that._timeFormatter(that._clockViewModel.currentTime, that);
  132. });
  133. /**
  134. * Gets the string representation of the current date. This property is observable.
  135. * @type {string}
  136. */
  137. this.dateLabel = undefined;
  138. knockout.defineProperty(this, "dateLabel", function () {
  139. return that._dateFormatter(that._clockViewModel.currentTime, that);
  140. });
  141. /**
  142. * Gets the string representation of the current multiplier. This property is observable.
  143. * @type {string}
  144. */
  145. this.multiplierLabel = undefined;
  146. knockout.defineProperty(this, "multiplierLabel", function () {
  147. const clockViewModel = that._clockViewModel;
  148. if (clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK) {
  149. return "Today";
  150. }
  151. const multiplier = clockViewModel.multiplier;
  152. //If it's a whole number, just return it.
  153. if (multiplier % 1 === 0) {
  154. return `${multiplier.toFixed(0)}x`;
  155. }
  156. //Convert to decimal string and remove any trailing zeroes
  157. return `${multiplier.toFixed(3).replace(/0{0,3}$/, "")}x`;
  158. });
  159. /**
  160. * Gets or sets the current shuttle ring angle. This property is observable.
  161. * @type {number}
  162. */
  163. this.shuttleRingAngle = undefined;
  164. knockout.defineProperty(this, "shuttleRingAngle", {
  165. get: function () {
  166. return multiplierToAngle(
  167. clockViewModel.multiplier,
  168. that._allShuttleRingTicks,
  169. clockViewModel,
  170. );
  171. },
  172. set: function (angle) {
  173. angle = Math.max(
  174. Math.min(angle, maxShuttleRingAngle),
  175. -maxShuttleRingAngle,
  176. );
  177. const ticks = that._allShuttleRingTicks;
  178. const clockViewModel = that._clockViewModel;
  179. clockViewModel.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
  180. //If we are at the max angle, simply return the max value in either direction.
  181. if (Math.abs(angle) === maxShuttleRingAngle) {
  182. clockViewModel.multiplier =
  183. angle > 0 ? ticks[ticks.length - 1] : ticks[0];
  184. return;
  185. }
  186. let multiplier = angleToMultiplier(angle, ticks);
  187. if (that.snapToTicks) {
  188. multiplier = ticks[getTypicalMultiplierIndex(multiplier, ticks)];
  189. } else if (multiplier !== 0) {
  190. const positiveMultiplier = Math.abs(multiplier);
  191. if (positiveMultiplier > 100) {
  192. const numDigits = positiveMultiplier.toFixed(0).length - 2;
  193. const divisor = Math.pow(10, numDigits);
  194. multiplier = (Math.round(multiplier / divisor) * divisor) | 0;
  195. } else if (positiveMultiplier > realtimeShuttleRingAngle) {
  196. multiplier = Math.round(multiplier);
  197. } else if (positiveMultiplier > 1) {
  198. multiplier = +multiplier.toFixed(1);
  199. } else if (positiveMultiplier > 0) {
  200. multiplier = +multiplier.toFixed(2);
  201. }
  202. }
  203. clockViewModel.multiplier = multiplier;
  204. },
  205. });
  206. this._canAnimate = undefined;
  207. knockout.defineProperty(this, "_canAnimate", function () {
  208. const clockViewModel = that._clockViewModel;
  209. const clockRange = clockViewModel.clockRange;
  210. if (that.shuttleRingDragging || clockRange === ClockRange.UNBOUNDED) {
  211. return true;
  212. }
  213. const multiplier = clockViewModel.multiplier;
  214. const currentTime = clockViewModel.currentTime;
  215. const startTime = clockViewModel.startTime;
  216. let result = false;
  217. if (clockRange === ClockRange.LOOP_STOP) {
  218. result =
  219. JulianDate.greaterThan(currentTime, startTime) ||
  220. (currentTime.equals(startTime) && multiplier > 0);
  221. } else {
  222. const stopTime = clockViewModel.stopTime;
  223. result =
  224. (JulianDate.greaterThan(currentTime, startTime) &&
  225. JulianDate.lessThan(currentTime, stopTime)) || //
  226. (currentTime.equals(startTime) && multiplier > 0) || //
  227. (currentTime.equals(stopTime) && multiplier < 0);
  228. }
  229. if (!result) {
  230. clockViewModel.shouldAnimate = false;
  231. }
  232. return result;
  233. });
  234. this._isSystemTimeAvailable = undefined;
  235. knockout.defineProperty(this, "_isSystemTimeAvailable", function () {
  236. const clockViewModel = that._clockViewModel;
  237. const clockRange = clockViewModel.clockRange;
  238. if (clockRange === ClockRange.UNBOUNDED) {
  239. return true;
  240. }
  241. const systemTime = clockViewModel.systemTime;
  242. return (
  243. JulianDate.greaterThanOrEquals(systemTime, clockViewModel.startTime) &&
  244. JulianDate.lessThanOrEquals(systemTime, clockViewModel.stopTime)
  245. );
  246. });
  247. this._isAnimating = undefined;
  248. knockout.defineProperty(this, "_isAnimating", function () {
  249. return (
  250. that._clockViewModel.shouldAnimate &&
  251. (that._canAnimate || that.shuttleRingDragging)
  252. );
  253. });
  254. const pauseCommand = createCommand(function () {
  255. const clockViewModel = that._clockViewModel;
  256. if (clockViewModel.shouldAnimate) {
  257. clockViewModel.shouldAnimate = false;
  258. } else if (that._canAnimate) {
  259. clockViewModel.shouldAnimate = true;
  260. }
  261. });
  262. this._pauseViewModel = new ToggleButtonViewModel(pauseCommand, {
  263. toggled: knockout.computed(function () {
  264. return !that._isAnimating;
  265. }),
  266. tooltip: "Pause",
  267. });
  268. const playReverseCommand = createCommand(function () {
  269. const clockViewModel = that._clockViewModel;
  270. const multiplier = clockViewModel.multiplier;
  271. if (multiplier > 0) {
  272. clockViewModel.multiplier = -multiplier;
  273. }
  274. clockViewModel.shouldAnimate = true;
  275. });
  276. this._playReverseViewModel = new ToggleButtonViewModel(playReverseCommand, {
  277. toggled: knockout.computed(function () {
  278. return that._isAnimating && clockViewModel.multiplier < 0;
  279. }),
  280. tooltip: "Play Reverse",
  281. });
  282. const playForwardCommand = createCommand(function () {
  283. const clockViewModel = that._clockViewModel;
  284. const multiplier = clockViewModel.multiplier;
  285. if (multiplier < 0) {
  286. clockViewModel.multiplier = -multiplier;
  287. }
  288. clockViewModel.shouldAnimate = true;
  289. });
  290. this._playForwardViewModel = new ToggleButtonViewModel(playForwardCommand, {
  291. toggled: knockout.computed(function () {
  292. return (
  293. that._isAnimating &&
  294. clockViewModel.multiplier > 0 &&
  295. clockViewModel.clockStep !== ClockStep.SYSTEM_CLOCK
  296. );
  297. }),
  298. tooltip: "Play Forward",
  299. });
  300. const playRealtimeCommand = createCommand(
  301. function () {
  302. that._clockViewModel.clockStep = ClockStep.SYSTEM_CLOCK;
  303. },
  304. knockout.getObservable(this, "_isSystemTimeAvailable"),
  305. );
  306. this._playRealtimeViewModel = new ToggleButtonViewModel(playRealtimeCommand, {
  307. toggled: knockout.computed(function () {
  308. return clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK;
  309. }),
  310. tooltip: knockout.computed(function () {
  311. return that._isSystemTimeAvailable
  312. ? "Today (real-time)"
  313. : "Current time not in range";
  314. }),
  315. });
  316. this._slower = createCommand(function () {
  317. const clockViewModel = that._clockViewModel;
  318. const shuttleRingTicks = that._allShuttleRingTicks;
  319. const multiplier = clockViewModel.multiplier;
  320. const index = getTypicalMultiplierIndex(multiplier, shuttleRingTicks) - 1;
  321. if (index >= 0) {
  322. clockViewModel.multiplier = shuttleRingTicks[index];
  323. }
  324. });
  325. this._faster = createCommand(function () {
  326. const clockViewModel = that._clockViewModel;
  327. const shuttleRingTicks = that._allShuttleRingTicks;
  328. const multiplier = clockViewModel.multiplier;
  329. const index = getTypicalMultiplierIndex(multiplier, shuttleRingTicks) + 1;
  330. if (index < shuttleRingTicks.length) {
  331. clockViewModel.multiplier = shuttleRingTicks[index];
  332. }
  333. });
  334. }
  335. /**
  336. * Gets or sets the default date formatter used by new instances.
  337. *
  338. * @member
  339. * @type {AnimationViewModel.DateFormatter}
  340. */
  341. AnimationViewModel.defaultDateFormatter = function (date, viewModel) {
  342. const gregorianDate = JulianDate.toGregorianDate(date);
  343. return `${monthNames[gregorianDate.month - 1]} ${gregorianDate.day} ${
  344. gregorianDate.year
  345. }`;
  346. };
  347. /**
  348. * Gets or sets the default array of known clock multipliers associated with new instances of the shuttle ring.
  349. * @type {number[]}
  350. */
  351. AnimationViewModel.defaultTicks = [
  352. //
  353. 0.001,
  354. 0.002,
  355. 0.005,
  356. 0.01,
  357. 0.02,
  358. 0.05,
  359. 0.1,
  360. 0.25,
  361. 0.5,
  362. 1.0,
  363. 2.0,
  364. 5.0,
  365. 10.0, //
  366. 15.0,
  367. 30.0,
  368. 60.0,
  369. 120.0,
  370. 300.0,
  371. 600.0,
  372. 900.0,
  373. 1800.0,
  374. 3600.0,
  375. 7200.0,
  376. 14400.0, //
  377. 21600.0,
  378. 43200.0,
  379. 86400.0,
  380. 172800.0,
  381. 345600.0,
  382. 604800.0,
  383. ];
  384. /**
  385. * Gets or sets the default time formatter used by new instances.
  386. *
  387. * @member
  388. * @type {AnimationViewModel.TimeFormatter}
  389. */
  390. AnimationViewModel.defaultTimeFormatter = function (date, viewModel) {
  391. const gregorianDate = JulianDate.toGregorianDate(date);
  392. const millisecond = Math.round(gregorianDate.millisecond);
  393. if (Math.abs(viewModel._clockViewModel.multiplier) < 1) {
  394. return `${gregorianDate.hour
  395. .toString()
  396. .padStart(2, "0")}:${gregorianDate.minute
  397. .toString()
  398. .padStart(2, "0")}:${gregorianDate.second
  399. .toString()
  400. .padStart(2, "0")}.${millisecond.toString().padStart(3, "0")}`;
  401. }
  402. return `${gregorianDate.hour
  403. .toString()
  404. .padStart(2, "0")}:${gregorianDate.minute
  405. .toString()
  406. .padStart(2, "0")}:${gregorianDate.second.toString().padStart(2, "0")} UTC`;
  407. };
  408. /**
  409. * Gets a copy of the array of positive known clock multipliers to associate with the shuttle ring.
  410. *
  411. * @returns {number[]} The array of known clock multipliers associated with the shuttle ring.
  412. */
  413. AnimationViewModel.prototype.getShuttleRingTicks = function () {
  414. return this._sortedFilteredPositiveTicks.slice(0);
  415. };
  416. /**
  417. * Sets the array of positive known clock multipliers to associate with the shuttle ring.
  418. * These values will have negative equivalents created for them and sets both the minimum
  419. * and maximum range of values for the shuttle ring as well as the values that are snapped
  420. * to when a single click is made. The values need not be in order, as they will be sorted
  421. * automatically, and duplicate values will be removed.
  422. *
  423. * @param {number[]} positiveTicks The list of known positive clock multipliers to associate with the shuttle ring.
  424. */
  425. AnimationViewModel.prototype.setShuttleRingTicks = function (positiveTicks) {
  426. //>>includeStart('debug', pragmas.debug);
  427. if (!defined(positiveTicks)) {
  428. throw new DeveloperError("positiveTicks is required.");
  429. }
  430. //>>includeEnd('debug');
  431. let i;
  432. let len;
  433. let tick;
  434. const hash = {};
  435. const sortedFilteredPositiveTicks = this._sortedFilteredPositiveTicks;
  436. sortedFilteredPositiveTicks.length = 0;
  437. for (i = 0, len = positiveTicks.length; i < len; ++i) {
  438. tick = positiveTicks[i];
  439. //filter duplicates
  440. if (!hash.hasOwnProperty(tick)) {
  441. hash[tick] = true;
  442. sortedFilteredPositiveTicks.push(tick);
  443. }
  444. }
  445. sortedFilteredPositiveTicks.sort(numberComparator);
  446. const allTicks = [];
  447. for (len = sortedFilteredPositiveTicks.length, i = len - 1; i >= 0; --i) {
  448. tick = sortedFilteredPositiveTicks[i];
  449. if (tick !== 0) {
  450. allTicks.push(-tick);
  451. }
  452. }
  453. addAllToArray(allTicks, sortedFilteredPositiveTicks);
  454. this._allShuttleRingTicks = allTicks;
  455. };
  456. Object.defineProperties(AnimationViewModel.prototype, {
  457. /**
  458. * Gets a command that decreases the speed of animation.
  459. * @memberof AnimationViewModel.prototype
  460. * @type {Command}
  461. */
  462. slower: {
  463. get: function () {
  464. return this._slower;
  465. },
  466. },
  467. /**
  468. * Gets a command that increases the speed of animation.
  469. * @memberof AnimationViewModel.prototype
  470. * @type {Command}
  471. */
  472. faster: {
  473. get: function () {
  474. return this._faster;
  475. },
  476. },
  477. /**
  478. * Gets the clock view model.
  479. * @memberof AnimationViewModel.prototype
  480. *
  481. * @type {ClockViewModel}
  482. */
  483. clockViewModel: {
  484. get: function () {
  485. return this._clockViewModel;
  486. },
  487. },
  488. /**
  489. * Gets the pause toggle button view model.
  490. * @memberof AnimationViewModel.prototype
  491. *
  492. * @type {ToggleButtonViewModel}
  493. */
  494. pauseViewModel: {
  495. get: function () {
  496. return this._pauseViewModel;
  497. },
  498. },
  499. /**
  500. * Gets the reverse toggle button view model.
  501. * @memberof AnimationViewModel.prototype
  502. *
  503. * @type {ToggleButtonViewModel}
  504. */
  505. playReverseViewModel: {
  506. get: function () {
  507. return this._playReverseViewModel;
  508. },
  509. },
  510. /**
  511. * Gets the play toggle button view model.
  512. * @memberof AnimationViewModel.prototype
  513. *
  514. * @type {ToggleButtonViewModel}
  515. */
  516. playForwardViewModel: {
  517. get: function () {
  518. return this._playForwardViewModel;
  519. },
  520. },
  521. /**
  522. * Gets the realtime toggle button view model.
  523. * @memberof AnimationViewModel.prototype
  524. *
  525. * @type {ToggleButtonViewModel}
  526. */
  527. playRealtimeViewModel: {
  528. get: function () {
  529. return this._playRealtimeViewModel;
  530. },
  531. },
  532. /**
  533. * Gets or sets the function which formats a date for display.
  534. * @memberof AnimationViewModel.prototype
  535. *
  536. * @type {AnimationViewModel.DateFormatter}
  537. * @default AnimationViewModel.defaultDateFormatter
  538. */
  539. dateFormatter: {
  540. //TODO:@exception {DeveloperError} dateFormatter must be a function.
  541. get: function () {
  542. return this._dateFormatter;
  543. },
  544. set: function (dateFormatter) {
  545. //>>includeStart('debug', pragmas.debug);
  546. if (typeof dateFormatter !== "function") {
  547. throw new DeveloperError("dateFormatter must be a function");
  548. }
  549. //>>includeEnd('debug');
  550. this._dateFormatter = dateFormatter;
  551. },
  552. },
  553. /**
  554. * Gets or sets the function which formats a time for display.
  555. * @memberof AnimationViewModel.prototype
  556. *
  557. * @type {AnimationViewModel.TimeFormatter}
  558. * @default AnimationViewModel.defaultTimeFormatter
  559. */
  560. timeFormatter: {
  561. //TODO:@exception {DeveloperError} timeFormatter must be a function.
  562. get: function () {
  563. return this._timeFormatter;
  564. },
  565. set: function (timeFormatter) {
  566. //>>includeStart('debug', pragmas.debug);
  567. if (typeof timeFormatter !== "function") {
  568. throw new DeveloperError("timeFormatter must be a function");
  569. }
  570. //>>includeEnd('debug');
  571. this._timeFormatter = timeFormatter;
  572. },
  573. },
  574. });
  575. //Currently exposed for tests.
  576. AnimationViewModel._maxShuttleRingAngle = maxShuttleRingAngle;
  577. AnimationViewModel._realtimeShuttleRingAngle = realtimeShuttleRingAngle;
  578. /**
  579. * A function that formats a date for display.
  580. * @callback AnimationViewModel.DateFormatter
  581. *
  582. * @param {JulianDate} date The date to be formatted
  583. * @param {AnimationViewModel} viewModel The AnimationViewModel instance requesting formatting.
  584. * @returns {string} The string representation of the calendar date portion of the provided date.
  585. */
  586. /**
  587. * A function that formats a time for display.
  588. * @callback AnimationViewModel.TimeFormatter
  589. *
  590. * @param {JulianDate} date The date to be formatted
  591. * @param {AnimationViewModel} viewModel The AnimationViewModel instance requesting formatting.
  592. * @returns {string} The string representation of the time portion of the provided date.
  593. */
  594. export default AnimationViewModel;