import { extend, isNil } from '../core/util';
import { withInEllipse } from '../core/util/path';
import Coordinate from '../geo/Coordinate';
import Extent from '../geo/Extent';
import Point from '../geo/Point';
import { CommonProjectionType } from '../geo/projection';
import { MapStateCache } from '../map/MapStateCache';
import CenterMixin from './CenterMixin';
import Polygon, { PolygonOptionsType, RingCoordinates, RingsCoordinates } from './Polygon';
/**
* @property {Object} options
* @property {Number} [options.numberOfShellPoints=60] - number of shell points when converting the circle to a polygon.
* @memberOf Circle
* @instance
*/
const options: CircleOptionsType = {
'numberOfShellPoints': 60
};
/**
* @classdesc
* Represents a Circle Geometry.
* @category geometry
* @extends Polygon
* @mixes Geometry.Center
* @example
* var circle = new Circle([100, 0], 1000, {
* id : 'circle0',
* properties : {
* foo : 'bar'
* }
* });
* @mixes CenterMixin
*/
export class Circle extends CenterMixin(Polygon) {
//@internal
_radius: number
static fromJSON(json: Record): Circle {
const feature = json['feature'];
const circle = new Circle(json['coordinates'], json['radius'], json['options']);
circle.setProperties(feature['properties']);
return circle;
}
/**
* @param {Coordinate} center - center of the circle
* @param {Number} radius - radius of the circle, in meter
* @param {Object} [options=null] - construct options defined in [Circle]{@link Circle#options}
*/
constructor(coordinates: Coordinate | Array, radius: number, options?: CircleOptionsType) {
super(null, options);
if (coordinates) {
this.setCoordinates(coordinates);
}
this._radius = radius;
}
/**
* 获取圆形的半径
* @english
* Get radius of the circle
* @return {Number}
*/
getRadius(): number {
return this._radius;
}
/**
* 给圆形设置新的半径
* @english
* Set a new radius to the circle
* @param {Number} radius - new radius
* @return {Circle} this
* @fires Circle#shapechange
*/
setRadius(radius: number) {
this._radius = radius;
this.onShapeChanged();
return this;
}
/**
* 获取作为多边形的圆的外壳,外壳点数由[options.numberOfShellPoints决定
* @english
* Gets the shell of the circle as a polygon, number of the shell points is decided by [options.numberOfShellPoints]{@link Circle#options}
* @return {Coordinate[]} - shell coordinates
*/
getShell(): RingCoordinates {
const measurer = this._getMeasurer(),
center = this.getCoordinates(),
numberOfPoints = this.options['numberOfShellPoints'],
radius = this.getRadius();
const shell = [];
let rad, dx, dy;
for (let i = 0, len = numberOfPoints - 1; i < len; i++) {
rad = (360 * i / len) * Math.PI / 180;
dx = radius * Math.cos(rad);
dy = radius * Math.sin(rad);
const vertex = measurer.locate(center, dx, dy);
vertex.z = center.z;
shell.push(vertex);
}
shell.push(shell[0]);
return shell;
}
/**
* 圆没有任何孔,总是返回null
* @english
* Circle won't have any holes, always returns null
* @return {Object[]} an empty array
*/
getHoles(): RingsCoordinates {
return [];
}
animateShow(): any {
return this.show();
}
//@internal
_containsPoint(point: Point, tolerance?: number): boolean {
const map = this.getMap();
const cache = MapStateCache[map.id];
const pitch = cache ? cache.pitch : map.getPitch();
if (pitch) {
return super._containsPoint(point, tolerance);
}
const center = map._pointToContainerPoint(this._getCenter2DPoint()),
size = this.getSize(),
t = this._hitTestTolerance() + (tolerance || 0),
se = center.add(size.width / 2, size.height / 2);
return withInEllipse(point, center, se, t);
}
//@internal
_computePrjExtent(projection: CommonProjectionType): Extent {
const minmax = this._getMinMax(projection);
if (!minmax) {
return null;
}
const pcenter = this._getPrjCoordinates();
const pminmax = minmax.map(c => projection.project(c));
const leftx = pminmax[0].x - pcenter.x;
const rightx = pminmax[1].x - pcenter.x;
const topy = pminmax[2].y - pcenter.y;
const bottomy = pminmax[3].y - pcenter.y;
return new Extent(pcenter.add(leftx, topy), pcenter.add(rightx, bottomy));
}
//@internal
_computeExtent(measurer: any): Extent {
const minmax = this._getMinMax(measurer);
if (!minmax) {
return null;
}
return new Extent(minmax[0].x, minmax[2].y, minmax[1].x, minmax[3].y, this._getProjection());
}
//@internal
_getMinMax(measurer: any): [Coordinate, Coordinate, Coordinate, Coordinate] {
if (!measurer || !this._coordinates || isNil(this._radius)) {
return null;
}
const radius = this._radius;
const p1 = measurer.locate(this._coordinates, -radius, 0),
p2 = measurer.locate(this._coordinates, radius, 0),
p3 = measurer.locate(this._coordinates, 0, radius),
p4 = measurer.locate(this._coordinates, 0, -radius);
return [p1, p2, p3, p4];
}
//@internal
_computeGeodesicLength(): number {
if (isNil(this._radius)) {
return 0;
}
return Math.PI * 2 * this._radius;
}
//@internal
_computeGeodesicArea(): number {
if (isNil(this._radius)) {
return 0;
}
return Math.PI * Math.pow(this._radius, 2);
}
//@internal
_exportGeoJSONGeometry() {
const coordinates = Coordinate.toNumberArrays([this.getShell()]);
return {
'type': 'Polygon',
'coordinates': coordinates
};
}
//@internal
_toJSON(options: any) {
const center = this.getCenter();
const opts = extend({}, options);
opts.geometry = false;
const feature = this.toGeoJSON(opts);
feature['geometry'] = {
'type': 'Polygon'
};
return {
'feature': feature,
'subType': 'Circle',
'coordinates': center.toArray(),
'radius': this.getRadius()
};
}
}
Circle.mergeOptions(options);
Circle.registerJSONType('Circle');
export default Circle;
export type CircleOptionsType = PolygonOptionsType & {
numberOfShellPoints?: number;
}