viewerDragDropMixin.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import {
  2. CzmlDataSource,
  3. defaultValue,
  4. defined,
  5. DeveloperError,
  6. Event,
  7. GeoJsonDataSource,
  8. getElement,
  9. GpxDataSource,
  10. KmlDataSource,
  11. wrapFunction,
  12. } from "@cesium/engine";
  13. /**
  14. * A mixin which adds default drag and drop support for CZML files to the Viewer widget.
  15. * Rather than being called directly, this function is normally passed as
  16. * a parameter to {@link Viewer#extend}, as shown in the example below.
  17. * @function viewerDragDropMixin
  18. * @param {Viewer} viewer The viewer instance.
  19. * @param {object} [options] Object with the following properties:
  20. * @param {Element|string} [options.dropTarget=viewer.container] The DOM element which will serve as the drop target.
  21. * @param {boolean} [options.clearOnDrop=true] When true, dropping files will clear all existing data sources first, when false, new data sources will be loaded after the existing ones.
  22. * @param {boolean} [options.flyToOnDrop=true] When true, dropping files will fly to the data source once it is loaded.
  23. * @param {boolean} [options.clampToGround=true] When true, datasources are clamped to the ground.
  24. * @param {Proxy} [options.proxy] The proxy to be used for KML network links.
  25. *
  26. * @exception {DeveloperError} Element with id <options.dropTarget> does not exist in the document.
  27. * @exception {DeveloperError} dropTarget is already defined by another mixin.
  28. * @exception {DeveloperError} dropEnabled is already defined by another mixin.
  29. * @exception {DeveloperError} dropError is already defined by another mixin.
  30. * @exception {DeveloperError} clearOnDrop is already defined by another mixin.
  31. *
  32. * @example
  33. * // Add basic drag and drop support and pop up an alert window on error.
  34. * const viewer = new Cesium.Viewer('cesiumContainer');
  35. * viewer.extend(Cesium.viewerDragDropMixin);
  36. * viewer.dropError.addEventListener(function(viewerArg, source, error) {
  37. * window.alert('Error processing ' + source + ':' + error);
  38. * });
  39. */
  40. function viewerDragDropMixin(viewer, options) {
  41. //>>includeStart('debug', pragmas.debug);
  42. if (!defined(viewer)) {
  43. throw new DeveloperError("viewer is required.");
  44. }
  45. if (viewer.hasOwnProperty("dropTarget")) {
  46. throw new DeveloperError("dropTarget is already defined by another mixin.");
  47. }
  48. if (viewer.hasOwnProperty("dropEnabled")) {
  49. throw new DeveloperError(
  50. "dropEnabled is already defined by another mixin."
  51. );
  52. }
  53. if (viewer.hasOwnProperty("dropError")) {
  54. throw new DeveloperError("dropError is already defined by another mixin.");
  55. }
  56. if (viewer.hasOwnProperty("clearOnDrop")) {
  57. throw new DeveloperError(
  58. "clearOnDrop is already defined by another mixin."
  59. );
  60. }
  61. if (viewer.hasOwnProperty("flyToOnDrop")) {
  62. throw new DeveloperError(
  63. "flyToOnDrop is already defined by another mixin."
  64. );
  65. }
  66. //>>includeEnd('debug');
  67. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  68. //Local variables to be closed over by defineProperties.
  69. let dropEnabled = true;
  70. let flyToOnDrop = defaultValue(options.flyToOnDrop, true);
  71. const dropError = new Event();
  72. let clearOnDrop = defaultValue(options.clearOnDrop, true);
  73. let dropTarget = defaultValue(options.dropTarget, viewer.container);
  74. let clampToGround = defaultValue(options.clampToGround, true);
  75. let proxy = options.proxy;
  76. dropTarget = getElement(dropTarget);
  77. Object.defineProperties(viewer, {
  78. /**
  79. * Gets or sets the element to serve as the drop target.
  80. * @memberof viewerDragDropMixin.prototype
  81. * @type {Element}
  82. */
  83. dropTarget: {
  84. //TODO See https://github.com/CesiumGS/cesium/issues/832
  85. get: function () {
  86. return dropTarget;
  87. },
  88. set: function (value) {
  89. //>>includeStart('debug', pragmas.debug);
  90. if (!defined(value)) {
  91. throw new DeveloperError("value is required.");
  92. }
  93. //>>includeEnd('debug');
  94. unsubscribe(dropTarget, handleDrop);
  95. dropTarget = value;
  96. subscribe(dropTarget, handleDrop);
  97. },
  98. },
  99. /**
  100. * Gets or sets a value indicating if drag and drop support is enabled.
  101. * @memberof viewerDragDropMixin.prototype
  102. * @type {Element}
  103. */
  104. dropEnabled: {
  105. get: function () {
  106. return dropEnabled;
  107. },
  108. set: function (value) {
  109. if (value !== dropEnabled) {
  110. if (value) {
  111. subscribe(dropTarget, handleDrop);
  112. } else {
  113. unsubscribe(dropTarget, handleDrop);
  114. }
  115. dropEnabled = value;
  116. }
  117. },
  118. },
  119. /**
  120. * Gets the event that will be raised when an error is encountered during drop processing.
  121. * @memberof viewerDragDropMixin.prototype
  122. * @type {Event}
  123. */
  124. dropError: {
  125. get: function () {
  126. return dropError;
  127. },
  128. },
  129. /**
  130. * Gets or sets a value indicating if existing data sources should be cleared before adding the newly dropped sources.
  131. * @memberof viewerDragDropMixin.prototype
  132. * @type {boolean}
  133. */
  134. clearOnDrop: {
  135. get: function () {
  136. return clearOnDrop;
  137. },
  138. set: function (value) {
  139. clearOnDrop = value;
  140. },
  141. },
  142. /**
  143. * Gets or sets a value indicating if the camera should fly to the data source after it is loaded.
  144. * @memberof viewerDragDropMixin.prototype
  145. * @type {boolean}
  146. */
  147. flyToOnDrop: {
  148. get: function () {
  149. return flyToOnDrop;
  150. },
  151. set: function (value) {
  152. flyToOnDrop = value;
  153. },
  154. },
  155. /**
  156. * Gets or sets the proxy to be used for KML.
  157. * @memberof viewerDragDropMixin.prototype
  158. * @type {Proxy}
  159. */
  160. proxy: {
  161. get: function () {
  162. return proxy;
  163. },
  164. set: function (value) {
  165. proxy = value;
  166. },
  167. },
  168. /**
  169. * Gets or sets a value indicating if the datasources should be clamped to the ground
  170. * @memberof viewerDragDropMixin.prototype
  171. * @type {boolean}
  172. */
  173. clampToGround: {
  174. get: function () {
  175. return clampToGround;
  176. },
  177. set: function (value) {
  178. clampToGround = value;
  179. },
  180. },
  181. });
  182. function handleDrop(event) {
  183. stop(event);
  184. if (clearOnDrop) {
  185. viewer.entities.removeAll();
  186. viewer.dataSources.removeAll();
  187. }
  188. const files = event.dataTransfer.files;
  189. const length = files.length;
  190. for (let i = 0; i < length; i++) {
  191. const file = files[i];
  192. const reader = new FileReader();
  193. reader.onload = createOnLoadCallback(viewer, file, proxy, clampToGround);
  194. reader.onerror = createDropErrorCallback(viewer, file);
  195. reader.readAsText(file);
  196. }
  197. }
  198. //Enable drop by default;
  199. subscribe(dropTarget, handleDrop);
  200. //Wrap the destroy function to make sure all events are unsubscribed from
  201. viewer.destroy = wrapFunction(viewer, viewer.destroy, function () {
  202. viewer.dropEnabled = false;
  203. });
  204. //Specs need access to handleDrop
  205. viewer._handleDrop = handleDrop;
  206. }
  207. function stop(event) {
  208. event.stopPropagation();
  209. event.preventDefault();
  210. }
  211. function unsubscribe(dropTarget, handleDrop) {
  212. const currentTarget = dropTarget;
  213. if (defined(currentTarget)) {
  214. currentTarget.removeEventListener("drop", handleDrop, false);
  215. currentTarget.removeEventListener("dragenter", stop, false);
  216. currentTarget.removeEventListener("dragover", stop, false);
  217. currentTarget.removeEventListener("dragexit", stop, false);
  218. }
  219. }
  220. function subscribe(dropTarget, handleDrop) {
  221. dropTarget.addEventListener("drop", handleDrop, false);
  222. dropTarget.addEventListener("dragenter", stop, false);
  223. dropTarget.addEventListener("dragover", stop, false);
  224. dropTarget.addEventListener("dragexit", stop, false);
  225. }
  226. function createOnLoadCallback(viewer, file, proxy, clampToGround) {
  227. const scene = viewer.scene;
  228. return function (evt) {
  229. const fileName = file.name;
  230. try {
  231. let loadPromise;
  232. if (/\.czml$/i.test(fileName)) {
  233. loadPromise = CzmlDataSource.load(JSON.parse(evt.target.result), {
  234. sourceUri: fileName,
  235. });
  236. } else if (
  237. /\.geojson$/i.test(fileName) ||
  238. /\.json$/i.test(fileName) ||
  239. /\.topojson$/i.test(fileName)
  240. ) {
  241. loadPromise = GeoJsonDataSource.load(JSON.parse(evt.target.result), {
  242. sourceUri: fileName,
  243. clampToGround: clampToGround,
  244. });
  245. } else if (/\.(kml|kmz)$/i.test(fileName)) {
  246. loadPromise = KmlDataSource.load(file, {
  247. sourceUri: fileName,
  248. proxy: proxy,
  249. camera: scene.camera,
  250. canvas: scene.canvas,
  251. clampToGround: clampToGround,
  252. screenOverlayContainer: viewer.container,
  253. });
  254. } else if (/\.gpx$/i.test(fileName)) {
  255. loadPromise = GpxDataSource.load(file, {
  256. sourceUri: fileName,
  257. proxy: proxy,
  258. });
  259. } else {
  260. viewer.dropError.raiseEvent(
  261. viewer,
  262. fileName,
  263. `Unrecognized file: ${fileName}`
  264. );
  265. return;
  266. }
  267. if (defined(loadPromise)) {
  268. viewer.dataSources
  269. .add(loadPromise)
  270. .then(function (dataSource) {
  271. if (viewer.flyToOnDrop) {
  272. viewer.flyTo(dataSource);
  273. }
  274. })
  275. .catch(function (error) {
  276. viewer.dropError.raiseEvent(viewer, fileName, error);
  277. });
  278. }
  279. } catch (error) {
  280. viewer.dropError.raiseEvent(viewer, fileName, error);
  281. }
  282. };
  283. }
  284. function createDropErrorCallback(viewer, file) {
  285. return function (evt) {
  286. viewer.dropError.raiseEvent(viewer, file.name, evt.target.error);
  287. };
  288. }
  289. export default viewerDragDropMixin;