Openlayers實現(xiàn)角度測量的方法
概述
在前面介紹了如何在 Openlayers 中進行長度和面積的測量,可以參考:《Openlayers 實現(xiàn)長度測量》,《openlayers 實現(xiàn)面積測量》。
那么如何在 Openlayers 中進行角度的測量呢?很遺憾ol/sphere
模塊中沒有提供對應(yīng)角度測量的 API 或方法,但是我們可以自己實現(xiàn)。
實踐
效果展示
角度量測繪制
結(jié)果
實現(xiàn)思路
實現(xiàn)思路主要有兩點:一是計算夾角的度數(shù);二則是夾角的圓弧表示,上圖中表示角度的圓弧,可以是 0 - 180° 中的任意一個角度,且它總是包裹在夾角內(nèi),連接夾角的兩邊。
夾角的度數(shù)計算
- 確定夾角
數(shù)學(xué)常識可知,夾角度數(shù)的區(qū)間必定是[0°,180°],而且,夾角是由三個坐標(biāo)點的位置確定大小的,如上圖中的頂點A、B、C。因此,我們可以內(nèi)定頂點 ∠ABC是我們需要測量的夾角。
- 確定頂點坐標(biāo)位置
可以在地圖上隨機取三個點作為頂點,但是這樣并不科學(xué),這樣無法滿足繪制特定角度的需求。選點還是通過ol/interaction
模塊的Draw
API 進行拾取。
但geom.getCoordinates()
返回的坐標(biāo)個數(shù)達到四個時,就調(diào)用this.draw.finishDrawing()
方法結(jié)束繪制。因為它的返回值倒數(shù)的兩個坐標(biāo)都是一樣的,因此即使我們只需要三個點的坐標(biāo),也需要等到它的返回值長度是4。
實現(xiàn)代碼如下:
this.draw.on( "drawstart", (evt: { feature: Feature<Geometry>, coordinate: Coordinate }) => { const { feature, coordinate } = evt; this.listenGeometryChange = feature.getGeometry().on("change", (evt) => { const geom = evt.target; let startPoint = geom.getFirstCoordinate(); this.addMarker({ coordinate: startPoint, symbolId: "A", anchor: [0, 0] }); const coordinates = geom.getCoordinates().slice(0, -1); if (coordinates.length > 1) { this.addMarker({ coordinate: coordinates[1], symbolId: "B", anchor: [1, 1], }); } const pointscount = geom.getCoordinates(); if (pointscount.length >= 4) { this.addMarker({ coordinate: coordinates[2], symbolId: "C", anchor: [0, 0], }); this.addAngleMark({ coordinate: coordinates[1], Angles: calculateAngle(coordinates), }); this.draw.finishDrawing(); } }); } );
- 如何計算夾角
兩點的坐標(biāo)位置決定了兩點之間的距離,即當(dāng)我們知道A、B和C的坐標(biāo)后,就可以知道線段AB和BC的長度了,然后通過數(shù)據(jù)計算就可以知道 ∠ABC的大小了。前面提到夾角的區(qū)間范圍,因為在電腦中,夾角也可以是負(fù)值,這個取決于它對應(yīng)的方向是順時針還是逆時針方向,因此要保證夾角的范圍在區(qū)間[0°,180°]內(nèi)。
封裝的計算夾角的方法calculateAngle
如下:
const calculateAngle = (points: Coordinate[]) => { // 提取坐標(biāo)點 A, B, C const [A, B, C] = points; // 計算向量 AB 和 BC const AB = { x: B[0] - A[0], y: B[1] - A[1] }; const BC = { x: C[0] - B[0], y: C[1] - B[1] }; // 計算點積 const dotProduct = AB.x * BC.x + AB.y * BC.y; // 計算向量的模 const magnitudeAB = Math.sqrt(AB.x ** 2 + AB.y ** 2); const magnitudeBC = Math.sqrt(BC.x ** 2 + BC.y ** 2); // 計算余弦值 const cosTheta = dotProduct / (magnitudeAB * magnitudeBC); // 確保 cosTheta 在 -1 和 1 之間 const clippedCosTheta = Math.max(-1, Math.min(1, cosTheta)); // 計算夾角(弧度轉(zhuǎn)換為度) const angleInRadians = Math.acos(clippedCosTheta); const angleInDegrees = angleInRadians * (180 / Math.PI); // 計算方向(使用叉積) const crossProduct = AB.x * BC.y - AB.y * BC.x; // 如果叉積為負(fù) const angle = crossProduct < 0 ? angleInDegrees - 180 : 180 - angleInDegrees; return angle; };
- 夾角的圓弧
通過上面可以計算得到 ∠ABC夾角的度數(shù)了,因為是自由繪制選點,因此無法保證BA或者BC是否與水平方向平行一致。小學(xué)時代,用量角器測量時,第一步就是需要保證量角器的底邊與夾角的一邊對齊,這樣的測量結(jié)果才準(zhǔn)確。但是在 Openlayers 中,我們就是需要去實現(xiàn)這樣一個量角器,無論在地圖上選擇哪三個頂點,圓弧都能準(zhǔn)確表示角度。并且這個圓弧的長度不固定。
這個需求可以使用canvas或者svg實現(xiàn),例子中是使用svg實現(xiàn)的。
- svg 畫一個圓弧
效果如下:
拖動滑塊,可以動態(tài)繪制任意一段圓弧,其代碼如下:
<svg width="200" height="200"> <path id="arc" fill="none" stroke="blue" stroke-width="2" /> <line id="radial1" x1="100" y1="100" stroke="red" stroke-width="2" stroke-dasharray="5,5" /> <line id="radial2" x1="100" y1="100" stroke="red" stroke-width="2" stroke-dasharray="5,5" /> <text fill="black" font-size="16" text-anchor="middle"> <textPath href="#arc" rel="external nofollow" startOffset="50%" side="left"> <tspan dy="-5" id="text"></tspan> </textPath> </text> </svg>
function drawArc(adjustedAngle) { const endY = centerY + radius * Math.cos((adjustedAngle * Math.PI) / 180); const endX = centerX - radius * Math.sin((adjustedAngle * Math.PI) / 180); const startY = centerX; const startX = centerY - radius; const largeArcFlag = adjustedAngle > 180 ? 0 : 1; const d_1 = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY}`; //表示非直角 const d_2 = `M ${endX} ${endY} L ${startX} ${endY} L${startX} ${startY}`; //表示直角 const angle = Math.abs(adjustedAngle); let lastD = d_1; if (angle == 540) { lastD = d_2; } arcPath.setAttribute("d", lastD); radial1.setAttribute("x2", startX); radial1.setAttribute("y2", startY); radial2.setAttribute("x2", endX); radial2.setAttribute("y2", endY); }
- 圓弧貼圖
現(xiàn)在需要將圓弧以O(shè)verlay的方式添加到 ∠ABC處就完事了,結(jié)果如下:
上面的情形就很詭異,因為圓弧始終是在水平位置,而我們選點是隨意的,因此圓弧需要旋轉(zhuǎn)一定的角度,使得圓弧的兩邊與夾角的兩邊對齊。但是具體旋轉(zhuǎn)多少角度,逆時針旋轉(zhuǎn)還是順時針旋轉(zhuǎn)?這個就取決于線段BA與BC的傾斜角度,準(zhǔn)確來說是以頂點B為原點建立坐標(biāo)系,水平方向為X軸且向右為X軸正半軸,垂直方向為Y軸且向上為Y軸正半軸。那么圓弧的旋轉(zhuǎn)角度和方向就取決于BA和BC與X 軸負(fù)半軸的夾角。
和 CSS 中transform的rotate規(guī)則報紙一致,規(guī)定圓弧逆時針旋轉(zhuǎn)為負(fù),順時針旋轉(zhuǎn)為正。因此,我們可以規(guī)定如果BA(或者BC)在第三象限或者是第四象限,則它與X軸負(fù)半軸的夾角為負(fù)值;反之,如果在第一象限則為正數(shù)鈍角,第二象限則為正數(shù)銳角
計算 BA 或者 BC 與 X 軸負(fù)半軸夾角
主要還是用到數(shù)學(xué)知識,代碼如下:
function calculateAnglePoint(points) { const [A, B, C] = points; const [Ax, Ay] = A; const [Bx, By] = B; const [Cx, Cy] = C; // 計算向量 BA 和 BC const BA = { x: Ax - Bx, y: Ay - By }; // BA 向量(從 B 到 A) const BC = { x: Cx - Bx, y: Cy - By }; // BC 向量(從 B 到 C) // 計算 BA 和 BC 向量與 X 軸的夾角(單位:度) let angleBA = Math.atan2(BA.y, BA.x) * (180 / Math.PI); // [-180, 180] 范圍 let angleBC = Math.atan2(BC.y, BC.x) * (180 / Math.PI); // [-180, 180] 范圍 // 計算 BA 向量與 X 軸負(fù)半軸的夾角 if (angleBA >= 0 && angleBA < 90) { // 第一象限,夾角為正鈍角 angleBA = 180 - angleBA; } else if (angleBA >= 90 && angleBA <= 180) { // 第二象限,夾角為正銳角 angleBA = 180 - angleBA; } else if (angleBA < 0 && angleBA >= -90) { // 第四象限,夾角為負(fù)鈍角 angleBA = Math.abs(angleBA) - 180; } else { // 第三象限,夾角為負(fù)銳角 angleBA = Math.abs(angleBA) - 180; } // 計算 BC 向量與 X 軸負(fù)半軸的夾角 if (angleBC >= 0 && angleBC < 90) { // 第一象限,夾角為正鈍角 angleBC = 180 - angleBC; } else if (angleBC >= 90 && angleBC <= 180) { // 第二象限,夾角為正銳角 angleBC = 180 - angleBC; } else if (angleBC < 0 && angleBC >= -90) { // 第四象限,夾角為負(fù)鈍角 angleBC = Math.abs(angleBC) - 180; } else { // 第三象限,夾角為負(fù)銳角 angleBC = Math.abs(angleBC) - 180; } return { angleBA: angleBA, // BA 向量與 X 軸負(fù)半軸的夾角 angleBC: angleBC, // BC 向量與 X 軸負(fù)半軸的夾角 }; }
- 確定旋轉(zhuǎn)角度
由上得到了angleBA和angleBC,毫無疑問,如果BA和BC在第一二象限,則圓弧需要順時針旋轉(zhuǎn),且旋轉(zhuǎn)角度為Math.min(angleBA,angleBC),反之都在第三四象限,則旋轉(zhuǎn)角度為- Math.max(Math.abs(angleBa),Math.abs(angleBC)),如果BA和BC在不同象限,那就要分情況討論了,具體判斷規(guī)則如下:
let rotate = 0; if (angleBA < 0 && angleBC < 0) { rotate = -Math.max(Math.abs(angleBA), Math.abs(angleBC)); } if (angleBA > 0 && angleBC > 0) { rotate = Math.min(Math.abs(angleBA), Math.abs(angleBC)); } //第一二三象限 不同象限 if (angleBA >= 135 && angleBA <= 180 && angleBC <= -45 && angleBC >= -90) { rotate = angleBA; } if (angleBC >= 135 && angleBC <= 180 && angleBA <= -45 && angleBA >= -90) { rotate = angleBC; } if (angleBA >= 135 && angleBA <= 180 && angleBC <= 0 && angleBC >= -45) { rotate = angleBC; } if (angleBC >= 135 && angleBC <= 180 && angleBA <= 0 && angleBA >= -45) { rotate = angleBA; } if (angleBA >= 90 && angleBA <= 135 && angleBC <= 0 && angleBC >= -45) { rotate = angleBC; } if (angleBC >= 90 && angleBC <= 135 && angleBA <= 0 && angleBA >= -45) { rotate = angleBA; } if (angleBA >= 90 && angleBA <= 135 && angleBC >= -90 && angleBC <= -45) { rotate = angleBA; } if (angleBC >= 90 && angleBC <= 135 && angleBA >= -90 && angleBA <= -45) { rotate = angleBC; } if (angleBA >= 0 && angleBA <= 90 && angleBC <= 0 && angleBC >= -90) { rotate = angleBC; } if (angleBC >= 0 && angleBC <= 90 && angleBA <= 0 && angleBA >= -90) { rotate = angleBA; } //第一二四象限不同象限 if (angleBC >= -180 && angleBC <= -90 && angleBA <= 180 && angleBA >= 90) { rotate = angleBA; } if (angleBA >= -180 && angleBA <= -90 && angleBC <= 180 && angleBC >= 90) { rotate = angleBC; } if (angleBC >= -135 && angleBC <= -90 && angleBA >= 0 && angleBA <= 45) { rotate = angleBC; } if (angleBC >= -135 && angleBC <= -90 && angleBA >= 45 && angleBA <= 90) { rotate = angleBA; } if (angleBA >= -135 && angleBA <= -90 && angleBC >= 0 && angleBC <= 45) { rotate = angleBA; } if (angleBA >= -135 && angleBA <= -90 && angleBC >= 45 && angleBC <= 90) { rotate = angleBC; } if (angleBC >= -180 && angleBC <= -135 && angleBA >= 0 && angleBA <= 45) { rotate = angleBC; } if (angleBC >= -180 && angleBC <= -135 && angleBA >= 45 && angleBA <= 90) { rotate = angleBA; } if (angleBA >= -180 && angleBA <= -135 && angleBC >= 0 && angleBC <= 45) { rotate = angleBA; } if (angleBA >= -180 && angleBA <= -135 && angleBC >= 45 && angleBC <= 90) { rotate = angleBC; }
總結(jié)
在 Openlayers 中測量角度主要還是要用到一些數(shù)據(jù)的基礎(chǔ)知識,重難點就是圓弧的旋轉(zhuǎn)表示,理解基本原理后發(fā)現(xiàn)也就那么一回事。
到此這篇關(guān)于Openlayers實現(xiàn)角度測量的方法的文章就介紹到這了,更多相關(guān)Openlayers角度測量內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
理解JAVASCRIPT中hasOwnProperty()的作用
JavaScript中hasOwnProperty函數(shù)方法是返回一個布爾值,指出一個對象是否具有指定名稱的屬性2013-06-06layui表單驗證select下拉框?qū)崿F(xiàn)驗證的方法
今天小編就為大家分享一篇layui表單驗證select下拉框?qū)崿F(xiàn)驗證的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09iframe如何動態(tài)創(chuàng)建及釋放其所占內(nèi)存
一個項目后期測試發(fā)現(xiàn)瀏覽器內(nèi)存一直居高不下,而且打開iframe頁面越多內(nèi)存占用越大,在IE系列瀏覽器中尤其明顯,下面與大家分享下iframe動態(tài)創(chuàng)建及釋放內(nèi)存2014-09-09微信小程序?qū)崿F(xiàn)省市區(qū)三級地址選擇
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)省市區(qū)三級地址選擇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-11-11