OpenLayers 6 代碼繪制/draw交互組件繪制兩種方式繪制橢圓過(guò)程詳解
引言
OpenLayers可以通過(guò)代碼繪制多種幾何形狀,也可以通過(guò)draw類型的交互組件繪制幾何形狀,官方實(shí)例提供了類如圓、折線、矩形、星形等方法。除此之外,橢圓這種圖形其實(shí)也是非常常見(jiàn)的幾何圖形,但是官方?jīng)]有提供現(xiàn)成的API,本文從使用代碼繪制和交互繪制兩種途徑詳細(xì)講解一下橢圓的繪制。
一點(diǎn)理論基礎(chǔ)
眾所周知,OGC提供的標(biāo)準(zhǔn)geometry類型只有點(diǎn)、線、面以及它們的組合,并沒(méi)有圓和橢圓,OpenLayers繪制圓的時(shí)候,采用的是正多邊形逼近法擬合的“圓形”。雖然渲染到canvas上的實(shí)際圖形是個(gè)多邊形,但是在數(shù)據(jù)結(jié)構(gòu)上,它仍然是個(gè)圓。圓形的公式可以寫為:
\frac{X{2}}{a{2}}+\frac{Y{2}}{a{2}}=1
相對(duì)的,橢圓的公式可以寫為:
\frac{X{2}}{a{2}}+\frac{Y{2}}{b{2}}=1
橢圓公式中的Y可以看做圓公式中的Y變換了\frac{a}倍之后得來(lái)。這里的公式中的X和Y對(duì)應(yīng)的就是橫縱坐標(biāo)值。
OpenLayers的SimpleGeometry類及其子類提供了具有這種功能的函數(shù)scale,可以對(duì)橫縱坐標(biāo)進(jìn)行按比例的拉伸變換??梢岳眠@個(gè)函數(shù)實(shí)現(xiàn)橢圓的繪制。
核心代碼
下面是實(shí)現(xiàn)繪制橢圓的核心代碼,無(wú)論是用代碼繪制還是用draw交互組件繪制都需要用到它:
function genEllipseGeom(radiusMajor, radiusMinor, center, rotation) {
var circle = new Circle(center, radiusMinor);
var polygon = fromCircle(circle, 64);
polygon.scale(radiusMajor / radiusMinor, 1);
polygon.rotate(rotation, center);
return polygon;
}
參數(shù)radiusMajor, radiusMinor, center, rotation分別對(duì)應(yīng)橢圓的長(zhǎng)半軸、短半軸、重心和旋轉(zhuǎn)角度。
算法的主要思想是:首先以短半軸為半徑生成了一個(gè)Circle類型的幾何,然后通過(guò)這個(gè)理想圓生成了一個(gè)正64邊形多邊形,擬合這個(gè)圓。再將這個(gè)多邊形進(jìn)行按比例變換,變換之后的結(jié)果再進(jìn)行旋轉(zhuǎn),最后得到的就是使用多邊形擬合的橢圓。
代碼控制繪制橢圓的完整代碼
import { Map, View } from 'ol';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import Circle from 'ol/geom/Circle';
import { fromCircle } from 'ol/geom/Polygon'
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import Feature from 'ol/Feature';
let tileLayer = new TileLayer({
source: new OSM()
})
let map = new Map({
target: 'map',
layers: [
tileLayer
],
view: new View({
center: [11936406.337013, 3786384.633134],
zoom: 5,
constrainResolution: true
})
});
var vSource = new VectorSource()
var vLayer = new VectorLayer(
{
source: vSource,
}
)
function genEllipseGeom(radiusMajor, radiusMinor, center, rotation) {
var circle = new Circle(center, radiusMinor);
var polygon = fromCircle(circle, 64);
polygon.scale(radiusMajor / radiusMinor, 1);
polygon.rotate(rotation, center);
return polygon;
}
var elGeom = genEllipseGeom(600000, 400000, [11936406.337013, 3786384.633134], Math.PI / 4);
var ef = new Feature(elGeom);
vSource.addFeature(ef);
map.addLayer(vLayer)
draw交互組件繪制橢圓
使用draw交互組件繪制橢圓與繪制圓形是類似的,可以有兩種思路:
兩點(diǎn)法:第一個(gè)點(diǎn)確定重心,第二個(gè)點(diǎn)與重心的橫坐標(biāo)之差確定長(zhǎng)半軸,縱坐標(biāo)值差確定短半軸。這個(gè)方案的缺點(diǎn)是無(wú)法通過(guò)繪制來(lái)自由定義橢圓的旋轉(zhuǎn)角度。
三點(diǎn)法:同樣第一個(gè)點(diǎn)確定重心,第二個(gè)點(diǎn)與重心的連線確定長(zhǎng)半軸,連線與橫坐標(biāo)軸的夾角確定旋轉(zhuǎn)角度;第三個(gè)點(diǎn)到長(zhǎng)半軸的距離確定短半軸。
本文使用三點(diǎn)法來(lái)實(shí)現(xiàn)橢圓的交互繪制。主要思路是通過(guò)自定義draw組件的geometryFunction來(lái)實(shí)現(xiàn)三點(diǎn)法繪制的邏輯。
draw交互組件繪制橢圓的完整代碼
import { Map, View } from 'ol';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import Circle from 'ol/geom/Circle';
import Polygon from 'ol/geom/Polygon';
import { fromCircle } from 'ol/geom/Polygon'
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import Feature from 'ol/Feature';
import Draw from 'ol/interaction/Draw';
import Style from 'ol/style/Style';
import Fill from 'ol/style/Fill';
import Stroke from 'ol/style/Stroke';
import CircleStyle from 'ol/style/Circle';
import GeometryType from 'ol/geom/GeometryType';
function createEditingStyle(feature) {
const styles = {};
const white = [255, 255, 255, 1];
const blue = [0, 0, 255, 1];
const width = 3;
styles[GeometryType.POLYGON] = [
new Style({
fill: new Fill({
color: [255, 255, 255, 0.3]
}),
stroke: new Stroke({
color: "#00FF00",
})
})
];
styles[GeometryType.MULTI_POLYGON] =
styles[GeometryType.POLYGON];
styles[GeometryType.LINE_STRING] = [
new Style({
stroke: new Stroke({
color: [0, 255, 0, 0.3],
width: width
})
})
];
styles[GeometryType.POINT] = [
new Style({
image: new CircleStyle({
radius: width * 2,
fill: new Fill({
color: blue
}),
stroke: new Stroke({
color: white,
width: width / 2
})
}),
zIndex: Infinity
})
];
return styles[feature.getGeometry().getType()]
}
let tileLayer = new TileLayer({
source: new OSM()
})
let map = new Map({
target: 'map',
layers: [
tileLayer
],
view: new View({
center: [11936406.337013, 3786384.633134],
zoom: 5,
constrainResolution: true
})
});
var vSource = new VectorSource()
var vLayer = new VectorLayer(
{
source: vSource,
}
)
function genEllipseGeom(radiusMajor, radiusMinor, center, rotation) {
var circle = new Circle(center, radiusMinor);
var polygon = fromCircle(circle, 64);
polygon.scale(radiusMajor / radiusMinor, 1);
polygon.rotate(rotation, center);
return polygon;
}
var elGeom = genEllipseGeom(600000, 400000, [11936406.337013, 3786384.633134], 0);
var ef = new Feature(elGeom);
vSource.addFeature(ef);
map.addLayer(vLayer)
var value = 'Polygon';
var geometryFunction;
var maxPoints = 3;
function geometryFunction(coordinates, geometry) {
let cArray = coordinates[0]
let center = cArray[0];
let startPoint = cArray[1];
let endPoint = cArray[2];
if (!geometry) {
geometry = new Polygon([]);
}
if (cArray.length == 3) {
let coordinatesRing = cArray.slice()
coordinatesRing.push(center)
let plg = new Polygon([coordinatesRing])
let plygArea = plg.getArea()
let radiusMajor = Math.sqrt(
Math.pow(center[0] - startPoint[0], 2) +
Math.pow(center[1] - startPoint[1], 2)
)
let radiusMinor = (plygArea * 2) / radiusMajor;
let dx = startPoint[0] - center[0];
let dy = startPoint[1] - center[1];
let rotation = Math.atan(dx / dy);
rotation = dy > 0 ? -rotation - Math.PI * 0.5 : -(Math.PI * 0.5 + rotation);
let f = genEllipseGeom(radiusMajor, radiusMinor, center, rotation)
geometry.setCoordinates(
f.getCoordinates()
)
}
return geometry
}
var draw = new Draw({
source: vSource,
type: value,
geometryFunction: geometryFunction,
maxPoints: maxPoints,
style: createEditingStyle
});
map.addInteraction(draw);
我在企鵝家的課堂和CSDN學(xué)院都開(kāi)通了《OpenLayers實(shí)例詳解》課程,歡迎報(bào)名學(xué)習(xí)。