OpenLayers 6 代码绘制/draw交互组件绘制两种方式绘制椭圆过程详解

引言

OpenLayers可以通过代码绘制多种几何形状,也可以通过draw类型的交互组件绘制几何形状,官方实例提供了类如圆、折线、矩形、星形等方法。除此之外,椭圆这种图形其实也是非常常见的几何图形,但是官方没有提供现成的API,本文从使用代码绘制和交互绘制两种途径详细讲解一下椭圆的绘制。
在这里插入图片描述

一点理论基础

众所周知,OGC提供的标准geometry类型只有点、线、面以及它们的组合,并没有圆和椭圆,OpenLayers绘制圆的时候,采用的是正多边形逼近法拟合的“圆形”。虽然渲染到canvas上的实际图形是个多边形,但是在数据结构上,它仍然是个圆。圆形的公式可以写为:

\frac{X{2}}{a{2}}+\frac{Y{2}}{a{2}}=1

相对的,椭圆的公式可以写为:

\frac{X{2}}{a{2}}+\frac{Y{2}}{b{2}}=1

椭圆公式中的Y可以看做圆公式中的Y变换了\frac{a}{b}倍之后得来。这里的公式中的X和Y对应的就是横纵坐标值。

OpenLayers的SimpleGeometry类及其子类提供了具有这种功能的函数scale,可以对横纵坐标进行按比例的拉伸变换。可以利用这个函数实现椭圆的绘制。
核心代码

下面是实现绘制椭圆的核心代码,无论是用代码绘制还是用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;
}

参数radiusMajor, radiusMinor, center, rotation分别对应椭圆的长半轴、短半轴、重心和旋转角度。

算法的主要思想是:首先以短半轴为半径生成了一个Circle类型的几何,然后通过这个理想圆生成了一个正64边形多边形,拟合这个圆。再将这个多边形进行按比例变换,变换之后的结果再进行旋转,最后得到的就是使用多边形拟合的椭圆。
代码控制绘制椭圆的完整代码

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交互组件绘制椭圆与绘制圆形是类似的,可以有两种思路:

两点法:第一个点确定重心,第二个点与重心的横坐标之差确定长半轴,纵坐标值差确定短半轴。这个方案的缺点是无法通过绘制来自由定义椭圆的旋转角度。
三点法:同样第一个点确定重心,第二个点与重心的连线确定长半轴,连线与横坐标轴的夹角确定旋转角度;第三个点到长半轴的距离确定短半轴。

本文使用三点法来实现椭圆的交互绘制。主要思路是通过自定义draw组件的geometryFunction来实现三点法绘制的逻辑。
在这里插入图片描述

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学院都开通了《OpenLayers实例详解》课程,欢迎报名学习。