rectangle-zoom.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <template>
  2. <div class="rectangle-zoom-container" ref="containerRef"></div>
  3. </template>
  4. <script>
  5. import { ref, onMounted, onUnmounted, watch } from 'vue';
  6. export default {
  7. name: 'RectangleZoom',
  8. props: {
  9. viewer: {
  10. type: Object,
  11. required: true
  12. },
  13. enabled: {
  14. type: Boolean,
  15. default: true
  16. },
  17. lineColor: {
  18. type: String,
  19. default: '#00aaff'
  20. },
  21. fillColor: {
  22. type: String,
  23. default: 'rgba(0, 170, 255, 0.2)'
  24. },
  25. lineWidth: {
  26. type: Number,
  27. default: 2
  28. },
  29. minSize: {
  30. type: Number,
  31. default: 10
  32. }
  33. },
  34. setup(props) {
  35. const containerRef = ref(null);
  36. let canvas = null;
  37. let ctx = null;
  38. let isDrawing = false;
  39. let isShiftDown = false;
  40. let startX = 0;
  41. let startY = 0;
  42. let currentX = 0;
  43. let currentY = 0;
  44. const initCanvas = () => {
  45. if (!props.viewer || !props.viewer.container) return;
  46. const container = props.viewer.container;
  47. const existingCanvas = document.getElementById('rectangle-zoom-canvas');
  48. if (existingCanvas) {
  49. existingCanvas.parentNode.removeChild(existingCanvas);
  50. }
  51. canvas = document.createElement('canvas');
  52. canvas.id = 'rectangle-zoom-canvas';
  53. canvas.style.position = 'absolute';
  54. canvas.style.top = '0';
  55. canvas.style.left = '0';
  56. canvas.style.width = '100%';
  57. canvas.style.height = '100%';
  58. canvas.style.pointerEvents = 'none';
  59. canvas.style.zIndex = '1000';
  60. container.appendChild(canvas);
  61. ctx = canvas.getContext('2d');
  62. resizeCanvas();
  63. };
  64. const resizeCanvas = () => {
  65. if (!canvas || !props.viewer || !props.viewer.container) return;
  66. const rect = props.viewer.container.getBoundingClientRect();
  67. canvas.width = rect.width;
  68. canvas.height = rect.height;
  69. };
  70. const drawRectangle = () => {
  71. if (!ctx) return;
  72. ctx.clearRect(0, 0, canvas.width, canvas.height);
  73. if (!isDrawing) return;
  74. const x = Math.min(startX, currentX);
  75. const y = Math.min(startY, currentY);
  76. const width = Math.abs(currentX - startX);
  77. const height = Math.abs(currentY - startY);
  78. ctx.beginPath();
  79. ctx.strokeStyle = props.lineColor;
  80. ctx.lineWidth = props.lineWidth;
  81. ctx.setLineDash([8, 4]);
  82. ctx.strokeRect(x, y, width, height);
  83. ctx.fillStyle = props.fillColor;
  84. ctx.fillRect(x, y, width, height);
  85. ctx.setLineDash([]);
  86. };
  87. const getRectangleExtent = () => {
  88. if (!props.viewer || !props.viewer.scene || !props.viewer.scene.globe) return null;
  89. const x1 = Math.min(startX, currentX);
  90. const y1 = Math.min(startY, currentY);
  91. const x2 = Math.max(startX, currentX);
  92. const y2 = Math.max(startY, currentY);
  93. const scene = props.viewer.scene;
  94. const camera = props.viewer.camera;
  95. const globe = scene.globe;
  96. const corners = [
  97. [x1, y1], [x2, y1], [x2, y2], [x1, y2]
  98. ];
  99. let lons = [];
  100. let lats = [];
  101. for (const [cx, cy] of corners) {
  102. const ray = camera.getPickRay(new Cesium.Cartesian2(cx, cy));
  103. if (!ray) continue;
  104. let cartesian;
  105. if (globe) {
  106. cartesian = globe.pick(ray, scene);
  107. }
  108. if (!cartesian) {
  109. cartesian = camera.pickEllipsoid(new Cesium.Cartesian2(cx, cy), Cesium.Ellipsoid.WGS84);
  110. }
  111. if (!cartesian) continue;
  112. const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
  113. lons.push(cartographic.longitude);
  114. lats.push(cartographic.latitude);
  115. }
  116. if (lons.length === 0) return null;
  117. return {
  118. west: Math.min(...lons),
  119. east: Math.max(...lons),
  120. south: Math.min(...lats),
  121. north: Math.max(...lats)
  122. };
  123. };
  124. const flyToExtent = (extent) => {
  125. if (!extent || !props.viewer) return;
  126. const rectangle = Cesium.Rectangle.fromRadians(
  127. extent.west, extent.south, extent.east, extent.north
  128. );
  129. props.viewer.camera.flyTo({
  130. destination: rectangle,
  131. duration: 1.0,
  132. easingFunction: Cesium.EasingFunction.QUADRATIC_IN_OUT
  133. });
  134. };
  135. const setCanvasCapture = (capture) => {
  136. if (!canvas) return;
  137. canvas.style.pointerEvents = capture ? 'auto' : 'none';
  138. };
  139. const setCursor = (cursor) => {
  140. if (canvas) {
  141. canvas.style.cursor = cursor;
  142. }
  143. if (!props.viewer) return;
  144. if (props.viewer.container) {
  145. props.viewer.container.style.cursor = cursor;
  146. }
  147. if (props.viewer.scene && props.viewer.scene.canvas) {
  148. props.viewer.scene.canvas.style.cursor = cursor;
  149. }
  150. };
  151. const clearCursorOverride = () => {
  152. if (canvas) {
  153. canvas.style.cursor = '';
  154. }
  155. if (!props.viewer) return;
  156. if (props.viewer.container) {
  157. props.viewer.container.style.cursor = '';
  158. }
  159. if (props.viewer.scene && props.viewer.scene.canvas) {
  160. props.viewer.scene.canvas.style.cursor = '';
  161. }
  162. };
  163. const getCanvasPos = (clientX, clientY) => {
  164. if (!canvas) return null;
  165. const rect = canvas.getBoundingClientRect();
  166. return new Cesium.Cartesian2(clientX - rect.left, clientY - rect.top);
  167. };
  168. const handleMouseDown = (event) => {
  169. if (!props.enabled) return;
  170. if (event.button !== 0) return;
  171. if (!isShiftDown) return;
  172. const pos = getCanvasPos(event.clientX, event.clientY);
  173. if (!pos) return;
  174. startX = pos.x;
  175. startY = pos.y;
  176. currentX = pos.x;
  177. currentY = pos.y;
  178. isDrawing = true;
  179. };
  180. const handleMouseMove = (event) => {
  181. if (!isDrawing) return;
  182. const pos = getCanvasPos(event.clientX, event.clientY);
  183. if (!pos) return;
  184. currentX = pos.x;
  185. currentY = pos.y;
  186. drawRectangle();
  187. };
  188. const endDrawing = () => {
  189. if (!isDrawing) return;
  190. if (!ctx) return;
  191. const width = Math.abs(currentX - startX);
  192. const height = Math.abs(currentY - startY);
  193. if (width >= props.minSize && height >= props.minSize) {
  194. const extent = getRectangleExtent();
  195. if (extent) {
  196. flyToExtent(extent);
  197. }
  198. }
  199. ctx.clearRect(0, 0, canvas.width, canvas.height);
  200. isDrawing = false;
  201. };
  202. const handleMouseUp = (event) => {
  203. if (!isDrawing) return;
  204. if (event.button !== 0) return;
  205. const pos = getCanvasPos(event.clientX, event.clientY);
  206. if (pos) {
  207. currentX = pos.x;
  208. currentY = pos.y;
  209. }
  210. endDrawing();
  211. };
  212. const handleKeyDown = (event) => {
  213. if (event.key === 'Shift' || event.key === 'ShiftLeft' || event.key === 'ShiftRight') {
  214. isShiftDown = true;
  215. setCanvasCapture(true);
  216. setCursor('crosshair');
  217. }
  218. };
  219. const handleKeyUp = (event) => {
  220. if (event.key === 'Shift' || event.key === 'ShiftLeft' || event.key === 'ShiftRight') {
  221. isShiftDown = false;
  222. endDrawing();
  223. setCanvasCapture(false);
  224. clearCursorOverride();
  225. }
  226. };
  227. const addEventListeners = () => {
  228. if (!canvas) return;
  229. canvas.addEventListener('mousedown', handleMouseDown);
  230. canvas.addEventListener('mousemove', handleMouseMove);
  231. canvas.addEventListener('mouseup', handleMouseUp);
  232. canvas.addEventListener('mouseleave', endDrawing);
  233. window.addEventListener('mouseup', handleMouseUp);
  234. window.addEventListener('keydown', handleKeyDown);
  235. window.addEventListener('keyup', handleKeyUp);
  236. window.addEventListener('resize', resizeCanvas);
  237. };
  238. const removeEventListeners = () => {
  239. if (canvas) {
  240. canvas.removeEventListener('mousedown', handleMouseDown);
  241. canvas.removeEventListener('mousemove', handleMouseMove);
  242. canvas.removeEventListener('mouseup', handleMouseUp);
  243. canvas.removeEventListener('mouseleave', endDrawing);
  244. }
  245. window.removeEventListener('mouseup', handleMouseUp);
  246. window.removeEventListener('keydown', handleKeyDown);
  247. window.removeEventListener('keyup', handleKeyUp);
  248. window.removeEventListener('resize', resizeCanvas);
  249. };
  250. const destroyCanvas = () => {
  251. if (canvas && canvas.parentNode) {
  252. canvas.parentNode.removeChild(canvas);
  253. canvas = null;
  254. ctx = null;
  255. }
  256. };
  257. onMounted(() => {
  258. if (props.enabled) {
  259. initCanvas();
  260. addEventListeners();
  261. }
  262. });
  263. onUnmounted(() => {
  264. removeEventListeners();
  265. destroyCanvas();
  266. });
  267. watch(() => props.enabled, (newVal) => {
  268. if (newVal) {
  269. initCanvas();
  270. addEventListeners();
  271. } else {
  272. removeEventListeners();
  273. destroyCanvas();
  274. }
  275. });
  276. return {
  277. containerRef
  278. };
  279. }
  280. };
  281. </script>
  282. <style scoped>
  283. .rectangle-zoom-container {
  284. display: none;
  285. }
  286. </style>