import { GEOMETRY_COLLECTION_TYPES, NUMERICAL_PROPERTIES } from '../core/Constants';
import Class from '../core/Class';
import Eventable, { BaseEventParamsType, HandlerFnResultType } from '../core/Eventable';
import JSONAble from '../core/JSONAble';
import Handlerable from '../handler/Handlerable';
import {
extend,
isNil,
isString,
isNumber,
isObject,
forEachCoord,
flash
} from '../core/util';
import { extendSymbol, getSymbolHash } from '../core/util/style';
import { loadGeoSymbol } from '../core/mapbox';
import { convertResourceUrl, getExternalResources } from '../core/util/resource';
import { replaceVariable, describeText } from '../core/util/strings';
import { isTextSymbol } from '../core/util/marker';
import Coordinate from '../geo/Coordinate';
import Point from '../geo/Point';
import Extent from '../geo/Extent';
import PointExtent from '../geo/PointExtent';
import Painter from '../renderer/geometry/Painter';
import CollectionPainter from '../renderer/geometry/CollectionPainter';
import SpatialReference from '../map/spatial-reference/SpatialReference';
import { isFunctionDefinition } from '../core/mapbox';
import { getDefaultBBOX, pointsBBOX } from '../core/util/bbox';
import { SizeLike } from '../geo/Size';
import type { ProjectionType } from '../geo/projection';
import OverlayLayer, { addGeometryFitViewOptions } from '../layer/OverlayLayer'
import GeometryCollection from './GeometryCollection'
import type { Map } from '../map';
import { WithNull } from '../types/typings';
import { InfoWindowOptionsType } from '../ui/InfoWindow';
import { getMinMaxAltitude } from '../core/util/path';
import { MapStateCache } from '../map/MapStateCache';
const TEMP_POINT0 = new Point(0, 0);
const TEMP_EXTENT = new PointExtent();
const TEMP_PROPERTIES = {};
function validateExtent(extent: Extent): boolean {
if (!extent) {
return false;
}
const { xmin, ymin, xmax, ymax } = extent;
return (xmax - xmin > 0 && ymax - ymin > 0);
}
/**
* @property {Object} options - geometry options
* @property {Boolean} [options.id=null] - id of the geometry
* @property {Boolean} [options.visible=true] - whether the geometry is visible.
* @property {Boolean} [options.editable=true] - whether the geometry can be edited.
* @property {Boolean} [options.interactive=true] - whether the geometry can be interactived.
* @property {String} [options.cursor=null] - cursor style when mouseover the geometry, same as the definition in CSS.
* @property {String} [options.measure=EPSG:4326] - the measure code for the geometry, defines {@tutorial measureGeometry how it can be measured}.
* @property {Boolean} [options.draggable=false] - whether the geometry can be dragged.
* @property {Boolean} [options.dragShadow=true] - if true, during geometry dragging, a shadow will be dragged before geometry was moved.
* @property {Boolean} [options.dragOnAxis=null] - if set, geometry can only be dragged along the specified axis, possible values: x, y
* @property {Number} [options.zIndex=undefined] - geometry's initial zIndex
* @property {Boolean} [options.antiMeridian=false] - geometry's antiMeridian
* @memberOf Geometry
* @instance
*/
const options: GeometryOptionsType = {
'id': null,
'visible': true,
'interactive': true,
'editable': true,
'cursor': null,
'antiMeridian': false,
'defaultProjection': 'EPSG:4326' // BAIDU, IDENTITY
};
/**
* 所有几何图形的基类。
* 它定义了所有几何图形类共享的通用方法。
* 它是抽象的,不打算被实例化而是被扩展。
* @english
* Base class for all the geometries.
* It defines common methods that all the geometry classes share.
* It is abstract and not intended to be instantiated but extended.
*
* @category geometry
* @abstract
* @extends Class
* @mixes Eventable
* @mixes Handlerable
* @mixes JSONAble
* @mixes ui.Menuable
*/
export class Geometry extends JSONAble(Eventable(Handlerable(Class))) {
options: GeometryOptionsType;
type: string;
//@internal
_layer: OverlayLayer;
//@internal
_angle: number
//@internal
_pivot: Coordinate
//@internal
_id: string
properties: Record;
//@internal
_symbol: any
//@internal
_symbolUpdated: any
//@internal
_compiledSymbol: any
//@internal
_symbolHash: any
//@internal
_textDesc: any
//@internal
_eventSymbolProperties: any
//@internal
_sizeSymbol: any
//@internal
_internalId: number
//@internal
_extent: Extent
//@internal
_fixedExtent: PointExtent
//@internal
_extent2d: PointExtent
//@internal
_externSymbol: any
//@internal
_parent: Geometry | GeometryCollection
//@internal
_silence: boolean
//@internal
_projCode: string
//@internal
_painter: Painter
//@internal
_maskPainter: CollectionPainter | Painter
//@internal
_dirtyCoords: boolean;
//@internal
_pcenter: Coordinate
//@internal
_coordinates: any;
//@internal
_infoWinOptions: InfoWindowOptionsType;
//@internal
_minAlt: number
//@internal
_maxAlt: number;
// 在 VectorLayerCanvasRenderer 附加的信息
//@internal
_isCheck?: boolean;
//@internal
_cPoint?: any;
//@internal
_inCurrentView?: boolean;
// 在 Marker 中附加的信息,Marker 和其子类都具有此属性
isPoint?: boolean;
//@internal
_savedVisible?: boolean;
//
//@internal
_paintAsPath?: () => any;
//@internal
_getPaintParams?: (disableSimplify?: boolean) => any[];
//@internal
_simplified?: boolean;
//@internal
_dirtyRotate?: boolean;
// 本身应该存于 Path 类,但是由于渲染层需要大量的特殊熟悉判断,定义在这里回减少很多麻烦
getHoles?(): Array>;
//@internal
__connectors: Array;
getShell?(): Array;
getGeometries?(): Geometry[];
getCoordinates?(): Coordinate | Array | Array> | Array>>
setCoordinates?(coordinate: any): this;
//@internal
_computeCenter?(T: any): Coordinate;
//@internal
_computeExtent?(T: any): Extent;
onRemove?(): void;
//@internal
_computeGeodesicLength?(T: any): number;
//@internal
_computeGeodesicArea?(T: any): number;
getRotateOffsetAngle?(): number;
//@internal
_computePrjExtent?(T: null | ProjectionType): Extent;
//@internal
_updateCache?(): void;
onAdd?(): void;
propertiesDirty: boolean;
constructor(options: GeometryOptionsType) {
const opts = extend({}, options);
const symbol = opts['symbol'];
const properties = opts['properties'];
const id = opts['id'];
delete opts['symbol'];
delete opts['id'];
delete opts['properties'];
super(opts);
this.propertiesDirty = false;
if (symbol) {
this.setSymbol(symbol);
} else {
this._genSizeSymbol();
}
if (properties) {
this.setProperties(properties);
}
if (!isNil(id)) {
this.setId(id);
}
//record rotate
if (options && isNumber(options.rotateAngle)) {
this._dirtyRotate = true;
}
}
static fromJSON(json: { [key: string]: any } | Array<{ [key: string]: any }>): Geometry | Array {
return json as Geometry;
}
/**
* 获取几何图形第一个坐标点
* @english
* Returns the first coordinate of the geometry.
*
* @return {Coordinate} First Coordinate
*/
getFirstCoordinate(): Coordinate {
if (this.type === 'GeometryCollection') {
const geometries = this.getGeometries();
if (!geometries.length) {
return null;
}
return geometries[0].getFirstCoordinate();
}
let coordinates: any = this.getCoordinates();
if (!Array.isArray(coordinates)) {
return coordinates;
}
do {
coordinates = coordinates[0];
} while (Array.isArray(coordinates) && coordinates.length > 0);
return coordinates;
}
/**
* 获取几何图形最后一个坐标点
* @english
* Returns the last coordinate of the geometry.
*
* @return {Coordinate} Last Coordinate
*/
getLastCoordinate(): Coordinate {
if (this.type === 'GeometryCollection') {
const geometries = this.getGeometries();
if (!geometries.length) {
return null;
}
return geometries[geometries.length - 1].getLastCoordinate();
}
let coordinates: any = this.getCoordinates();
if (!Array.isArray(coordinates)) {
return coordinates;
}
do {
coordinates = coordinates[coordinates.length - 1];
} while (Array.isArray(coordinates) && coordinates.length > 0);
return coordinates;
}
/**
* 将几何图形添加到指定图层上
* @english
* Adds the geometry to a layer
* @param {Layer} layer - layer add to
* @param {Boolean} [fitview=false] - automatically set the map to a fit center and zoom for the geometry
* @return {Geometry} this
* @fires Geometry#add
*/
addTo(layer: OverlayLayer, fitview?: boolean | addGeometryFitViewOptions): this {
layer.addGeometry(this, fitview);
return this;
}
/**
* 获取几何图形所在的图层
* @english
* Get the layer which this geometry added to.
* @returns {Layer} - layer added to
*/
getLayer(): OverlayLayer {
if (!this._layer) {
return null;
}
return this._layer;
}
/**
* 获取几何图形所在的地图对象
* @english
* Get the map which this geometry added to
* @returns {Map} - map added to
*/
getMap(): Map | null {
if (!this._layer) {
return null;
}
return this._layer.getMap();
}
/**
* 获取几何图形的id
* @english
* Gets geometry's id. Id is set by setId or constructor options.
* @returns {String|Number} geometry的id
*/
getId(): string {
return this._id;
}
/**
* 给几何图形设置id
* @english
* Set geometry's id.
* @param {String} id - new id
* @returns {Geometry} this
* @fires Geometry#idchange
*/
setId(id: string): this {
const oldId = this.getId();
this._id = id;
/**
* idchange event.
*
* @event Geometry#idchange
* @type {Object}
* @property {String} type - idchange
* @property {Geometry} target - the geometry fires the event
* @property {String|Number} old - value of the old id
* @property {String|Number} new - value of the new id
*/
this._fireEvent('idchange', {
'old': oldId,
'new': id
});
return this;
}
/**
* 获取几何图形的属性
* @english
* Get geometry's properties. Defined by GeoJSON as [feature's properties]{@link http://geojson.org/geojson-spec.html#feature-objects}.
*
* @returns {Object} properties
*/
getProperties(): { [key: string]: any } | null {
if (!this.properties) {
if (this._getParent()) {
return this._getParent().getProperties();
}
return null;
}
return this.properties;
}
/**
* 给几何图形设置新的属性
* Set a new properties to geometry.
* @param {Object} properties - new properties
* @returns {Geometry} this
* @fires Geometry#propertieschange
*/
setProperties(properties: { [key: string]: any }): this {
this.propertiesDirty = true;
const old = this.properties;
this.properties = isObject(properties) ? extend({}, properties) : properties;
const children = this.getGeometries ? this.getGeometries() : [];
children.forEach(child => {
const subPro = child.getProperties ? child.getProperties() || {} : {};
const mergePro = extend(subPro, properties || {});
if (child.setProperties) {
child.setProperties(mergePro);
}
});
//such as altitude update
this._clearAltitudeCache();
this._repaint();
/**
* propertieschange event, thrown when geometry's properties is changed.
*
* @event Geometry#propertieschange
* @type {Object}
* @property {String} type - propertieschange
* @property {Geometry} target - the geometry fires the event
* @property {String|Number} old - value of the old properties
* @property {String|Number} new - value of the new properties
*/
this._fireEvent('propertieschange', {
'old': old,
'new': properties
});
return this;
}
/**
* 获取几何图形的类型,例如“点”,"线"
* @english
* Get type of the geometry, e.g. "Point", "LineString"
* @returns {String} type of the geometry
*/
getType(): string {
return this.type;
}
/**
* 获取几何图形的样式
* @english
* Get symbol of the geometry
* @returns {Object} geometry's symbol
*/
getSymbol(): any {
const s = this._symbol;
if (s) {
if (!Array.isArray(s)) {
return extend({}, s);
} else {
return extendSymbol(s);
}
}
return null;
}
/**
* 给几何图形设置样式
* @english
* Set a new symbol to style the geometry.
* @param {Object} symbol - new symbol
* @see {@tutorial symbol Style a geometry with symbols}
* @return {Geometry} this
* @fires Geometry#symbolchange
*/
setSymbol(symbol: any): this {
this._symbolUpdated = symbol;
this._symbol = this._prepareSymbol(symbol);
this.onSymbolChanged();
delete this._compiledSymbol;
delete this._symbolHash;
return this;
}
/**
* 获取样式的哈希值
* @english
* Get symbol's hash code
* @return {String}
*/
getSymbolHash(): string {
if (!this._symbolHash) {
this._symbolHash = getSymbolHash(this._symbolUpdated);
}
return this._symbolHash;
}
/**
* 更新几何图形当前的样式
* @english
* Update geometry's current symbol.
*
* @param {Object | Array} props - symbol properties to update
* @return {Geometry} this
* @fires Geometry#symbolchange
* @example
* var marker = new Marker([0, 0], {
* // if has markerFile , the priority of the picture is greater than the vector and the path of svg
* // svg image type:'path';vector type:'cross','x','diamond','bar','square','rectangle','triangle','ellipse','pin','pie'
* symbol : {
* markerType : 'ellipse',
* markerWidth : 20,
* markerHeight : 30
* }
* });
* // update symbol's markerWidth to 40
* marker.updateSymbol({
* markerWidth : 40
* });
*/
updateSymbol(props: any): this {
if (!props) {
return this;
}
let s = this._getSymbol();
const propsIsArray = Array.isArray(props);
if (!s) {
//generate defalut empty symbol
s = this._getInternalSymbol() || {};
// if (propsIsArray) {
// s = props.map(() => { return {} });
// }
}
if (!Array.isArray(s) && propsIsArray) {
//only read first element of props
props = props[0] || {};
}
if (Array.isArray(s)) {
if (!propsIsArray) {
//auto generate array symbol
props = [props];
while (props.length < s.length) {
props.push({});
}
// throw new Error('Parameter of updateSymbol is not an array.');
}
for (let i = 0; i < props.length; i++) {
if (isTextSymbol(props[i])) {
delete this._textDesc;
}
if (s[i] && props[i]) {
s[i] = extendSymbol(s[i], props[i]);
}
}
} else if (Array.isArray(props)) {
// throw new Error('Geometry\'s symbol is object but the update symbol is array.');
console.error('Geometry\'s symbol is object but the update symbol is array.');
console.error('Geometry\'s symbol and update symbol:', s, props);
return this;
} else {
if (isTextSymbol(s)) {
delete this._textDesc;
}
if (s) {
s = extendSymbol(s, props);
} else {
s = extendSymbol(this._getInternalSymbol(), props);
}
}
this._eventSymbolProperties = props;
delete this._compiledSymbol;
return this.setSymbol(s);
}
/**
* 如果几何图形有文本内容,就获取它
* @english
* Get geometry's text content if it has
* @returns {String}
*/
getTextContent(): any {
const symbol = this._getInternalSymbol();
if (Array.isArray(symbol)) {
const contents = [];
let has = false;
for (let i = 0; i < symbol.length; i++) {
contents[i] = replaceVariable(symbol[i] && symbol[i]['textName'], this.getProperties());
if (!isNil(contents[i])) {
has = true;
}
}
return has ? contents : null;
}
return replaceVariable(symbol && symbol['textName'], this.getProperties());
}
getTextDesc(): any {
if (!this._textDesc) {
const textContent = this.getTextContent();
// if textName='',this is error
// if (!textContent) {
// return null;
// }
const symbol = this._sizeSymbol;
const isArray = Array.isArray(textContent);
if (Array.isArray(symbol)) {
this._textDesc = symbol.map((s, i) => {
return describeText(isArray ? textContent[i] : '', s);
});
} else {
this._textDesc = describeText(textContent, symbol);
}
}
return this._textDesc;
}
/**
* 获取几何图形中心点
* @english
* Get the geographical center of the geometry.
*
* @returns {Coordinate}
*/
getCenter(): Coordinate {
return this._computeCenter(this._getMeasurer());
}
/**
* 获取几何图形的包围盒范围
* @english
* Get the geometry's geographical extent
*
* @returns {Extent} geometry's extent
*/
getExtent(): Extent {
const prjExt = this._getPrjExtent();
const projection = this._getProjection();
let extent: Extent;
if (prjExt && projection) {
const min = projection.unproject(new Coordinate(prjExt['xmin'], prjExt['ymin'])),
max = projection.unproject(new Coordinate(prjExt['xmax'], prjExt['ymax']));
extent = new Extent(min, max, projection);
} else {
extent = this._computeExtent(this._getMeasurer());
}
if (extent) {
const altitudes = this._getAltitude();
const [minAlt, maxAlt] = getMinMaxAltitude(altitudes);
extent.zmin = minAlt;
extent.zmax = maxAlt;
}
return extent;
}
/**
* 获取几何图形的屏幕像素范围
* @english
* Get geometry's screen extent in pixel
*
* @returns {PointExtent}
*/
getContainerExtent(out?: PointExtent): PointExtent {
const extent2d = this.get2DExtent();
if (!extent2d || !extent2d.isValid()) {
return null;
}
const map = this.getMap();
// const center = this.getCenter();
const glRes = map.getGLRes();
const minAltitude = this.getMinAltitude();
const extent = extent2d.convertTo(c => map._pointAtResToContainerPoint(c, glRes, minAltitude, TEMP_POINT0), out);
const maxAltitude = this.getMaxAltitude();
if (maxAltitude !== minAltitude) {
const extent2 = extent2d.convertTo(c => map._pointAtResToContainerPoint(c, glRes, maxAltitude, TEMP_POINT0), TEMP_EXTENT);
extent._combine(extent2);
}
const layer = this.getLayer();
if (layer && this.type === 'LineString' && maxAltitude && layer.options['drawAltitude']) {
const groundExtent = extent2d.convertTo(c => map._pointAtResToContainerPoint(c, glRes, 0, TEMP_POINT0), TEMP_EXTENT);
extent._combine(groundExtent);
}
if (extent) {
const fixedExtent = this._getFixedExtent();
if (validateExtent(fixedExtent)) {
extent._add(fixedExtent);
}
}
const smoothness = this.options['smoothness'];
if (smoothness) {
extent._expand(extent.getWidth() * 0.15);
}
return extent;
}
//@internal
_getFixedExtent(): PointExtent {
// only for LineString and Polygon, Marker's will be overrided
if (!this._fixedExtent) {
this._fixedExtent = new PointExtent();
}
const symbol = this._sizeSymbol;
let t = (symbol && symbol['lineWidth'] || 1) / 2;
const border = (symbol && symbol['lineStrokeWidth'] || 0);
t += border;
this._fixedExtent.set(-t, -t, t, t);
const dx = (symbol && symbol['lineDx']) || 0;
this._fixedExtent._add([dx, 0]);
const dy = (symbol && symbol['lineDy']) || 0;
this._fixedExtent._add([0, dy]);
return this._fixedExtent;
}
get2DExtent(): PointExtent {
const map = this.getMap();
if (!map) {
return null;
}
if (this._extent2d) {
return this._extent2d;
}
const extent = this._getPrjExtent();
if (!extent || !extent.isValid()) {
return null;
}
const min = extent.getMin();
const max = extent.getMax();
const glRes = map.getGLRes();
map._prjToPointAtRes(min, glRes, min);
map._prjToPointAtRes(max, glRes, max);
this._extent2d = new PointExtent(min, max);
const cache = MapStateCache[map.id];
const zoom = cache ? cache.zoom : map.getZoom();
(this._extent2d as any).z = zoom;
return this._extent2d;
}
/**
* 获取几何体的像素大小,不同缩放级别的像素大小可能会有所不同。
* @english
* Get pixel size of the geometry, which may vary in different zoom levels.
*
* @returns {Size}
*/
getSize(): SizeLike {
const extent = this.getContainerExtent();
return extent ? extent.getSize() : null;
}
/**
* 几何体是否包含输入容器点
* @english
* Whehter the geometry contains the input container point.
*
* @param {Point|Coordinate} point - input container point or coordinate
* @param {Number} [t=undefined] - tolerance in pixel
* @return {Boolean}
* @example
* var circle = new Circle([0, 0], 1000)
* .addTo(layer);
* var contains = circle.containsPoint(new maptalks.Point(400, 300));
*/
containsPoint(containerPoint: Point, t?: number): boolean {
if (!this.getMap()) {
throw new Error('The geometry is required to be added on a map to perform "containsPoint".');
}
if (containerPoint instanceof Coordinate) {
containerPoint = this.getMap().coordToContainerPoint(containerPoint);
}
return this._containsPoint(containerPoint, t);
// return this._containsPoint(this.getMap()._containerPointToPoint(new Point(containerPoint)), t);
}
//@internal
_containsPoint(containerPoint: Point, t?: number): boolean {
const painter = this._getPainter();
if (!painter) {
return false;
}
t = t || 0;
if (this._hitTestTolerance) {
t += this._hitTestTolerance();
}
return painter.hitTest(containerPoint, t);
}
/**
* 显示几何图形
* @english
* Show the geometry.
*
* @return {Geometry} this
* @fires Geometry#show
*/
show(): this {
this.options['visible'] = true;
if (this.getMap()) {
const painter = this._getPainter();
if (painter) {
painter.show();
}
/**
* show event
*
* @event Geometry#show
* @type {Object}
* @property {String} type - show
* @property {Geometry} target - the geometry fires the event
*/
this._fireEvent('show');
}
return this;
}
/**
* 隐藏几何图形
* @english
* Hide the geometry
*
* @return {Geometry} this
* @fires Geometry#hide
*/
hide(): this {
this.options['visible'] = false;
if (this.getMap()) {
this.onHide();
const painter = this._getPainter();
if (painter) {
painter.hide();
}
/**
* hide event
*
* @event Geometry#hide
* @type {Object}
* @property {String} type - hide
* @property {Geometry} target - the geometry fires the event
*/
this._fireEvent('hide');
}
return this;
}
/**
* 几何图形是否可见
* @english
* Whether the geometry is visible
*
* @returns {Boolean}
*/
isVisible(): boolean {
if (!this.options['visible']) {
return false;
}
const symbol = this._getInternalSymbol();
if (!symbol) {
return true;
}
if (!this.symbolIsVisible()) {
return false;
}
if (Array.isArray(symbol)) {
if (!symbol.length) {
return true;
}
for (let i = 0, l = symbol.length; i < l; i++) {
if (isNil(symbol[i]['opacity']) || symbol[i]['opacity'] > 0) {
return true;
}
}
return false;
} else {
return (isNil(symbol['opacity']) || isObject(symbol['opacity']) || (isNumber(symbol['opacity']) && symbol['opacity'] > 0));
}
}
/**
* symbol是否可见
* @english
* Whether the geometry symbol is visible
*
* @returns {Boolean}
*/
symbolIsVisible(): boolean {
//function-type
let symbols = this._getCompiledSymbol();
if (!symbols) {
return true;
}
if (!Array.isArray(symbols)) {
symbols = [symbols];
}
for (let i = 0, len = symbols.length; i < len; i++) {
const symbol = symbols[i];
if (!symbol) {
continue;
}
const isVisible = symbol.visible;
if (isVisible !== false && isVisible !== 0) {
return true;
}
}
return false;
}
/**
* 获取几何图形所在层级,默认是0
* @english
* Get zIndex of the geometry, default is 0
* @return {Number} zIndex
*/
getZIndex(): number {
return this.options['zIndex'] || 0;
}
/**
* 给几何图形设置新的层级并触发zindexchange事件(将导致层对几何体进行排序并进行渲染)
* @english
* Set a new zIndex to Geometry and fire zindexchange event (will cause layer to sort geometries and render)
* @param {Number} zIndex - new zIndex
* @return {Geometry} this
* @fires Geometry#zindexchange
*/
setZIndex(zIndex: number): this {
const old = this.options['zIndex'];
this.options['zIndex'] = zIndex;
/**
* 层级改变事件,当几何图形层级发生改变将会触发
* @english
* zindexchange event, fired when geometry's zIndex is changed.
*
* @event Geometry#zindexchange
* @type {Object}
* @property {String} type - zindexchange
* @property {Geometry} target - the geometry fires the event
* @property {Number} old - old zIndex
* @property {Number} new - new zIndex
*/
this._fireEvent('zindexchange', {
'old': old,
'new': zIndex
});
return this;
}
/**
* 仅将新的zIndex设置为Geometry,而不触发zindexchange事件
* 当需要更新许多几何图形的zIndex时,可以用来提高性能
* 当更新了N个几何体时,可以将setZIndexSilently与(N-1)个几何体一起使用,并将setZIendex与要排序和渲染的层的最后一个几何体一同使用。
* @english
* Only set a new zIndex to Geometry without firing zindexchange event.
* Can be useful to improve perf when a lot of geometries' zIndex need to be updated.
* When updated N geometries, You can use setZIndexSilently with (N-1) geometries and use setZIndex with the last geometry for layer to sort and render.
* @param {Number} zIndex - new zIndex
* @return {Geometry} this
*/
setZIndexSilently(zIndex: number): this {
this.options['zIndex'] = zIndex;
return this;
}
/**
* 将几何图形至于顶层
* @english
* Bring the geometry on the top
* @return {Geometry} this
* @fires Geometry#zindexchange
*/
bringToFront(): this {
const layer = this.getLayer();
if (!layer || !layer.getGeoMaxZIndex) {
return this;
}
const topZ = layer.getGeoMaxZIndex();
this.setZIndex(topZ + 1);
return this;
}
/**
* 将几何图形置于底层
* @english
* Bring the geometry to the back
* @return {Geometry} this
* @fires Geometry#zindexchange
*/
bringToBack(): this {
const layer = this.getLayer();
if (!layer || !layer.getGeoMinZIndex) {
return this;
}
const bottomZ = layer.getGeoMinZIndex();
this.setZIndex(bottomZ - 1);
return this;
}
/**
* 按给定偏移平移或移动几何体
* @english
* Translate or move the geometry by the given offset.
*
* @param {Coordinate} offset - translate offset
* @return {Geometry} this
* @fires Geometry#positionchange
* @fires Geometry#shapechange
*/
/**
* Translate or move the geometry by the given offset.
*
* @param {Number} x - x offset
* @param {Number} y - y offset
* @param {Number} z - z offset
* @return {Geometry} this
* @fires Geometry#positionchange
* @fires Geometry#shapechange
*/
translate(x: number | Coordinate, y?: number, z?: number): this {
if (isNil(x)) {
return this;
}
const offset = new Coordinate(x as number, y, z);
if (offset.x === 0 && offset.y === 0 && (isNil(offset.z) || offset.z === 0)) {
return this;
}
const coordinates: any = this.getCoordinates();
this._silence = true;
if (coordinates) {
if (Array.isArray(coordinates)) {
const translated = forEachCoord(coordinates, function (coord) {
return coord.add(offset);
});
this.setCoordinates(translated);
} else {
this.setCoordinates(coordinates.add(offset));
}
}
this._silence = false;
this._fireEvent('positionchange');
return this;
}
//translate rotate Pivot when coordinates change
//@interlal
_translateRotatePivot(newCoordinate: Coordinate) {
if (!this._pivot || !newCoordinate) {
return this;
}
if (this.options.rotatePivot) {
const oldCoordinate = this.getCoordinates();
if (!oldCoordinate) {
return this;
}
if (!(newCoordinate instanceof Coordinate)) {
newCoordinate = new Coordinate(newCoordinate);
}
const offset = newCoordinate.sub(oldCoordinate as Coordinate);
if (offset.x === 0 && offset.y === 0) {
return this;
}
this._pivot._add(offset);
this.options.rotatePivot = this._pivot.toArray();
}
return this;
}
/**
* 闪烁几何图形,按一定的内部显示和隐藏计数次数。
* @english
* Flash the geometry, show and hide by certain internal for times of count.
*
* @param {Number} [interval=100] - interval of flash, in millisecond (ms)
* @param {Number} [count=4] - flash times
* @param {Function} [cb=null] - callback function when flash ended
* @param {*} [context=null] - callback context
* @return {Geometry} this
*/
flash(interval: number, count: number, cb: () => void, context: any): this {
return flash.call(this, interval, count, cb, context);
}
/**
* 返回不包含事件侦听器的几何体的副本。
* @english
* Returns a copy of the geometry without the event listeners.
* @returns {Geometry} copy
*/
copy(): Geometry {
const json = this.toJSON();
const ret = Geometry.fromJSON(json) as Geometry;
//restore visibility
ret.options['visible'] = true;
return ret;
}
/**
* 将其自身从图层中移除(如果有的话)。
* @english
* remove itself from the layer if any.
* @returns {Geometry} this
* @fires Geometry#removestart
* @fires Geometry#remove
*/
remove() {
const layer = this.getLayer();
if (!layer) {
return this;
}
/**
* removestart event.
*
* @event Geometry#removestart
* @type {Object}
* @property {String} type - removestart
* @property {Geometry} target - the geometry fires the event
*/
this._fireEvent('removestart');
this._unbind();
/**
* removeend event.
*
* @event Geometry#removeend
* @type {Object}
* @property {String} type - removeend
* @property {Geometry} target - the geometry fires the event
*/
this._fireEvent('removeend');
/**
* remove event.
*
* @event Geometry#remove
* @type {Object}
* @property {String} type - remove
* @property {Geometry} target - the geometry fires the event
*/
this._fireEvent('remove');
return this;
}
/**
* 将几何对象导出成geojson对象
* @english
* Exports [geometry]{@link http://geojson.org/geojson-spec.html#feature-objects} out of a GeoJSON feature.
* @return {Object} GeoJSON Geometry
*/
toGeoJSONGeometry(): { [key: string]: any } {
const gJson = this._exportGeoJSONGeometry();
return gJson;
}
/**
* 导出geojson对象中的一个feature
* @english
* Exports a GeoJSON feature.
* @param {Object} [opts=null] - export options
* @param {Boolean} [opts.geometry=true] - whether export geometry
* @param {Boolean} [opts.properties=true] - whether export properties
* @returns {Object} GeoJSON Feature
*/
toGeoJSON(opts?: { [key: string]: any }): { [key: string]: any } {
if (!opts) {
opts = {};
}
const feature = {
'type': 'Feature',
'geometry': null
};
if (isNil(opts['geometry']) || opts['geometry']) {
const geoJSON = this._exportGeoJSONGeometry();
feature['geometry'] = geoJSON;
}
const id = this.getId();
if (!isNil(id)) {
feature['id'] = id;
}
let properties;
if (isNil(opts['properties']) || opts['properties']) {
properties = this._exportProperties();
}
feature['properties'] = properties;
return feature;
}
/**
* 从几何体中导出一个配置文件json。
* 除了导出特性对象,概要文件json还包含符号、构造选项和信息窗口信息。
* 配置文件json可以存储在其他地方,稍后用于重现几何图形
* 由于函数的序列化问题,概要文件json中不包括事件侦听器和上下文菜单
* @english
* Export a profile json out of the geometry.
* Besides exporting the feature object, a profile json also contains symbol, construct options and infowindow info.
* The profile json can be stored somewhere else and be used to reproduce the geometry later.
* Due to the problem of serialization for functions, event listeners and contextmenu are not included in profile json.
* @example
* // an example of a profile json.
* var profile = {
"feature": {
"type": "Feature",
"id" : "point1",
"geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
"properties": {"prop0": "value0"}
},
//construct options.
"options":{
"draggable" : true
},
//symbol
"symbol":{
"markerFile" : "http://foo.com/icon.png",
"markerWidth" : 20,
"markerHeight": 20
},
//infowindow info
"infowindow" : {
"options" : {
"style" : "black"
},
"title" : "this is a infowindow title",
"content" : "this is a infowindow content"
}
};
* @param {Object} [options=null] - export options
* @param {Boolean} [opts.geometry=true] - whether export feature's geometry
* @param {Boolean} [opts.properties=true] - whether export feature's properties
* @param {Boolean} [opts.options=true] - whether export construct options
* @param {Boolean} [opts.symbol=true] - whether export symbol
* @param {Boolean} [opts.infoWindow=true] - whether export infowindow
* @return {Object} profile json object
*/
toJSON(options?: { [key: string]: any }): { [key: string]: any } {
//一个Graphic的profile
/*
//因为响应函数无法被序列化, 所以menu, 事件listener等无法被包含在graphic中
}*/
if (!options) {
options = {};
}
const json = this._toJSON(options);
const other = this._exportGraphicOptions(options);
extend(json, other);
return json;
}
/**
* 获取几何图形的地理长度
* @english
* Get the geographic length of the geometry.
* @returns {Number} geographic length, unit is meter
*/
getLength(): number {
return this._computeGeodesicLength(this._getMeasurer());
}
/**
* 获取几何图形的面积
* @english
* Get the geographic area of the geometry.
* @returns {Number} geographic area, unit is sq.meter
*/
getArea(): number {
return this._computeGeodesicArea(this._getMeasurer());
}
/**
* 按给定角度围绕轴心点旋转几何体
* @english
* Rotate the geometry of given angle around a pivot point
* @param {Number} angle - angle to rotate in degree
* @param {Coordinate} [pivot=null] - optional, will be the geometry's center by default
* @returns {Geometry} this
*/
rotate(angle: number, pivot?: Coordinate): this {
if (!isNumber(angle)) {
console.error(`angle:${angle} is not number`);
return this;
}
if (!this._painter) {
this._dirtyRotate = true;
}
if (this.type === 'GeometryCollection') {
const geometries = this.getGeometries();
geometries.forEach(g => g.rotate(angle, pivot));
return this;
}
if (!pivot) {
pivot = this.getCenter();
} else {
pivot = new Coordinate(pivot);
}
this._angle = angle;
this._pivot = pivot;
const measurer = this._getMeasurer();
const coordinates: any = this.getCoordinates();
if (!Array.isArray(coordinates)) {
//exclude Rectangle ,Ellipse,Sector by shell judge
if ((pivot.x !== coordinates.x || pivot.y !== coordinates.y) && !this.getShell) {
const c = measurer._rotate(coordinates, pivot, angle);
this.setCoordinates(c);
} else {
//only redraw ,not to change coordinate
this.onPositionChanged();
}
if (this.getShell) {
this.options.rotateAngle = angle;
this.options.rotatePivot = pivot.toArray();
}
return this;
}
forEachCoord(coordinates, c => {
return measurer._rotate(c, pivot, angle);
});
this.setCoordinates(coordinates);
return this;
}
//@internal
_rotatePrjCoordinates(coordinates: Coordinate | Array): Coordinate | Coordinate[] {
if (!coordinates || this._angle === 0 || !this._pivot) {
return coordinates;
}
const projection = this._getProjection();
if (!projection) {
return coordinates;
}
let offsetAngle = 0;
const isArray = Array.isArray(coordinates);
const coord = isArray ? coordinates : [coordinates];
const rotatePrjCoordinates: Coordinate[] = [];
let cx, cy;
//sector is special
if (this.getRotateOffsetAngle) {
offsetAngle = this.getRotateOffsetAngle();
const center = coord[coord.length - 1];
cx = center.x;
cy = center.y;
} else {
const bbox = getDefaultBBOX();
//cal all points center
pointsBBOX(coord, bbox);
const [minx, miny, maxx, maxy] = bbox;
cx = (minx + maxx) / 2;
cy = (miny + maxy) / 2;
}
//图形按照自身的几何中心旋转
for (let i = 0, len = coord.length; i < len; i++) {
const c = coord[i];
const { x, y } = c;
const dx = x - cx, dy = y - cy;
const r = Math.sqrt(dx * dx + dy * dy);
const sAngle = getSegmentAngle(cx, cy, x, y);
const rad = (sAngle - this._angle + offsetAngle) / 180 * Math.PI;
const rx = Math.cos(rad) * r, ry = Math.sin(rad) * r;
const rc = new Coordinate(cx + rx, cy + ry);
rotatePrjCoordinates.push(rc);
}
const prjCenter = projection.project(this._pivot);
const rx = prjCenter.x, ry = prjCenter.y;
//translate rotate center
const translateX = cx - rx, translateY = cy - ry;
//平移到指定的选中中心点
for (let i = 0, len = rotatePrjCoordinates.length; i < len; i++) {
const c = rotatePrjCoordinates[i];
c.x -= translateX;
c.y -= translateY;
}
if (isArray) {
return rotatePrjCoordinates;
}
return rotatePrjCoordinates[0];
}
isRotated(): boolean {
return !!(isNumber(this._angle) && this._pivot);
}
/**
* 获取连线的连接点
* @english
* Get the connect points for [ConnectorLine]{@link ConnectorLine}
* @return {Coordinate[]} connect points
* @private
*/
//@internal
_getConnectPoints(): Coordinate[] {
return [this.getCenter()];
}
//options initializing
//@internal
_initOptions(options: GeometryOptionsType): void {
const opts = extend({}, options);
const symbol = opts['symbol'];
const properties = opts['properties'];
const id = opts['id'];
delete opts['symbol'];
delete opts['id'];
delete opts['properties'];
this._setOptions(opts);
if (symbol) {
this.setSymbol(symbol);
}
if (properties) {
this.setProperties(properties);
}
if (!isNil(id)) {
this.setId(id);
}
}
//bind the geometry to a layer
//@internal
_bindLayer(layer: OverlayLayer): void {
if (layer === this.getLayer()) {
return;
}
//check dupliaction
if (this.getLayer()) {
throw new Error('Geometry cannot be added to two or more layers at the same time.');
}
this._layer = layer;
this._clearCache();
this._bindInfoWindow();
this._bindMenu();
// this._clearProjection();
// this.callInitHooks();
}
//@internal
_prepareSymbol(symbol: any): any {
if (Array.isArray(symbol)) {
const cookedSymbols = [];
for (let i = 0; i < symbol.length; i++) {
cookedSymbols.push(convertResourceUrl(this._checkAndCopySymbol(symbol[i])));
}
return cookedSymbols;
} else if (symbol) {
symbol = this._checkAndCopySymbol(symbol);
return convertResourceUrl(symbol);
}
return null;
}
//@internal
_checkAndCopySymbol(symbol: any): any {
const s = {};
for (const i in symbol) {
if (NUMERICAL_PROPERTIES[i] && isString(symbol[i])) {
s[i] = +symbol[i];
} else {
s[i] = symbol[i];
}
}
return s;
}
//@internal
_getSymbol(): any {
return this._symbol;
}
/**
* 将外部符号设置为几何体,例如VectorLayer的setStyle中的样式
* @english
* Sets a external symbol to the geometry, e.g. style from VectorLayer's setStyle
* @private
* @param {Object} symbol - external symbol
*/
//@internal
_setExternSymbol(symbol: any): this {
this._eventSymbolProperties = symbol;
if (!this._symbol) {
delete this._textDesc;
}
this._externSymbol = this._prepareSymbol(symbol);
this.onSymbolChanged();
return this;
}
//@internal
_getInternalSymbol(): any {
if (this._symbol) {
return this._symbol;
} else if (this._externSymbol) {
return this._externSymbol;
} else if (this.options['symbol']) {
return this.options['symbol'];
}
return null;
}
//@internal
_getPrjExtent(): Extent {
const p = this._getProjection();
this._verifyProjection();
if (!this._extent && p) {
this._extent = this._computePrjExtent(p);
}
return this._extent;
}
//@internal
_unbind(): void {
const layer = this.getLayer();
if (!layer) {
return;
}
if (this._animPlayer) {
this._animPlayer.finish();
}
// this._clearHandlers();
//contextmenu
this._unbindMenu();
//infowindow
this._unbindInfoWindow();
if (this.isEditing()) {
this.endEdit();
}
this._removePainter();
if (this.onRemove) {
this.onRemove();
}
if (layer.onRemoveGeometry) {
layer.onRemoveGeometry(this);
}
delete this._layer;
delete this._internalId;
delete this._extent;
}
//@internal
_getInternalId(): number {
return this._internalId;
}
//只能被图层调用
//@internal
_setInternalId(id: number): void {
this._internalId = id;
}
//@internal
_getMeasurer(): any {
if (this._getProjection()) {
return this._getProjection();
}
return SpatialReference.getProjectionInstance(this.options['defaultProjection']);
}
//@internal
_getProjection(): WithNull {
const map = this.getMap();
if (map) {
return map.getProjection();
}
return null;
}
//@internal
_verifyProjection(): void {
const projection = this._getProjection();
if (this._projCode && projection && this._projCode !== projection.code) {
this._clearProjection();
}
this._projCode = projection ? projection.code : this._projCode;
}
//获取geometry样式中依赖的外部图片资源
//@internal
_getExternalResources(): string[] {
const symbol = this._getInternalSymbol();
return getExternalResources(symbol);
}
//@internal
_getPainter(): any {
//for performance
if (this._painter) {
return this._painter;
}
const layer = this.getLayer();
if (layer) {
if (GEOMETRY_COLLECTION_TYPES.indexOf(this.type) !== -1) {
//@ts-expect-error todo 待vectorlayer ts完善
if (layer.constructor.getCollectionPainterClass) {
//@ts-expect-error todo 待vectorlayer ts完善
const clazz = layer.constructor.getCollectionPainterClass();
if (clazz) {
this._painter = new clazz(this);
}
}
//@ts-expect-error todo 待vectorlayer ts完善
} else if (layer.constructor.getPainterClass) {
//@ts-expect-error todo 待vectorlayer ts完善
const clazz = layer.constructor.getPainterClass();
if (clazz) {
this._painter = new clazz(this);
}
}
}
return this._painter;
}
//@internal
_getMaskPainter(): CollectionPainter | Painter {
if (this._maskPainter) {
return this._maskPainter;
}
this._maskPainter = this.getGeometries && this.getGeometries() ? new CollectionPainter(this, true) : new Painter(this);
return this._maskPainter;
}
//@internal
_removePainter(): void {
if (this._painter) {
this._painter.remove();
}
delete this._painter;
}
//@internal
_paint(extent?: Extent): void {
if (!this.symbolIsVisible()) {
return;
}
if (this._painter) {
if (this._dirtyCoords) {
delete this._dirtyCoords;
const projection = this._getProjection();
if (projection) {
this._pcenter = projection.project(this._coordinates);
this._clearCache();
}
}
if (this._dirtyRotate && isNumber(this.options.rotateAngle)) {
this.rotate(this.options.rotateAngle, this.options.rotatePivot as unknown as Coordinate);
}
this._dirtyRotate = false;
this._painter.paint(extent);
}
}
//@internal
_clearCache(): void {
delete this._extent;
delete this._extent2d;
this._clearAltitudeCache();
//MultiGeometry
if (this._parent) {
delete this._parent._extent;
delete this._parent._extent2d;
this._parent._clearAltitudeCache();
}
}
//@internal
_clearProjection(): void {
delete this._extent;
delete this._extent2d;
//MultiGeometry
if (this._parent) {
delete this._parent._extent;
delete this._parent._extent2d;
}
}
//@internal
_repaint(): void {
if (this._painter) {
this._painter.repaint();
}
}
onHide(): void {
this.closeMenu();
this.closeInfoWindow();
}
onShapeChanged(): void {
this._clearCache();
this._repaint();
/**
* shapechange event.
*
* @event Geometry#shapechange
* @type {Object}
* @property {String} type - shapechange
* @property {Geometry} target - the geometry fires the event
*/
this._fireEvent('shapechange');
}
onPositionChanged(): void {
this._clearCache();
this._repaint();
/**
* positionchange event.
*
* @event Geometry#positionchange
* @type {Object}
* @property {String} type - positionchange
* @property {Geometry} target - the geometry fires the event
*/
this._fireEvent('positionchange');
}
onSymbolChanged(): void {
if (this._painter) {
this._painter.refreshSymbol();
}
const e: any = {};
if (this._eventSymbolProperties) {
e.properties = JSON.parse(JSON.stringify(this._eventSymbolProperties));
delete this._eventSymbolProperties;
} else {
delete this._textDesc;
}
this._genSizeSymbol();
/**
* symbolchange event.
*
* @event Geometry#symbolchange
* @type {Object}
* @property {String} type - symbolchange
* @property {Geometry} target - the geometry fires the event
* @property {Object} properties - symbol properties to update if has
*/
this._fireEvent('symbolchange', e);
}
//@internal
_genSizeSymbol(): void {
const symbol = this._getInternalSymbol();
if (!symbol) {
delete this._sizeSymbol;
return;
}
if (Array.isArray(symbol)) {
this._sizeSymbol = [];
let dynamicSize = false;
for (let i = 0; i < symbol.length; i++) {
const s = this._sizeSymbol[i] = this._getSizeSymbol(symbol[i]);
if (!dynamicSize && s && s._dynamic) {
dynamicSize = true;
}
}
this._sizeSymbol._dynamic = dynamicSize;
} else {
this._sizeSymbol = this._getSizeSymbol(symbol);
}
}
//@internal
_getSizeSymbol(symbol: any): any {
const symbolSize = loadGeoSymbol({
lineWidth: symbol['lineWidth'],
lineDx: symbol['lineDx'],
lineDy: symbol['lineDy']
}, this);
if (isFunctionDefinition(symbol['lineWidth']) || isFunctionDefinition(symbol['lineDx']) || isFunctionDefinition(symbol['lineDy'])) {
symbolSize._dynamic = true;
}
return symbolSize;
}
//@internal
_getCompiledSymbol(): any {
if (this._compiledSymbol) {
return this._compiledSymbol;
}
this._compiledSymbol = loadGeoSymbol(this._getInternalSymbol(), this);
return this._compiledSymbol;
}
onConfig(conf: any): void {
let properties;
if (conf['properties']) {
properties = conf['properties'];
delete conf['properties'];
}
let needRepaint = false;
for (const p in conf) {
if (conf.hasOwnProperty(p)) {
const prefix = p.slice(0, 5);
if (prefix === 'arrow' || prefix === 'smoot') {
needRepaint = true;
break;
}
}
}
if (properties) {
this.setProperties(properties);
this._repaint();
} else if (needRepaint) {
this._repaint();
}
}
/**
* 将父对象设置为几何体,通常是“多重多边形”、“几何集合”等
* @english
* Set a parent to the geometry, which is usually a MultiPolygon, GeometryCollection, etc
* @param {GeometryCollection} geometry - parent geometry
* @private
*/
//@internal
_setParent(geometry?: Geometry | GeometryCollection): void {
if (geometry) {
this._parent = geometry;
}
}
//@internal
_getParent(): any {
return this._parent;
}
//@internal
_fireEvent(eventName: string, param?: BaseEventParamsType) {
if (this._silence) {
return;
}
if (this.getLayer() && this.getLayer()._onGeometryEvent) {
if (!param) {
param = {};
}
param['type'] = eventName;
param['target'] = this;
this.getLayer()._onGeometryEvent(param as HandlerFnResultType);
}
this.fire(eventName, param);
}
//@internal
_toJSON(options?: any): any {
return {
'feature': this.toGeoJSON(options)
};
}
//@internal
_recordVisible() {
let visible = this.options.visible;
if (isNil(visible)) {
visible = true;
}
this._savedVisible = visible;
}
//@internal
_recoveryVisible() {
delete this._savedVisible;
}
//@internal
_exportGraphicOptions(options: any): any {
const json = {};
if (isNil(options['options']) || options['options']) {
json['options'] = this.config();
}
if (json['options'] && this.isEditing && this.isEditing()) {
json['options'].visible = this._savedVisible;
}
if (isNil(options['symbol']) || options['symbol']) {
json['symbol'] = this.getSymbol();
}
if (isNil(options['infoWindow']) || options['infoWindow']) {
if (this._infoWinOptions) {
json['infoWindow'] = this._infoWinOptions;
}
}
return json;
}
//@internal
_exportGeoJSONGeometry(): any {
const points: any = this.getCoordinates();
const coordinates = Coordinate.toNumberArrays(points);
return {
'type': this.getType(),
'coordinates': coordinates
};
}
//@internal
_exportProperties(): any {
let properties = null;
const geoProperties = this.getProperties();
if (!isNil(geoProperties)) {
if (isObject(geoProperties)) {
properties = extend({}, geoProperties);
} else {
properties = geoProperties;
}
}
return properties;
}
//@internal
_hitTestTolerance(): number {
return 0;
}
//------------- altitude + layer.altitude -------------
//this is for vectorlayer
//内部方法 for render,返回的值受layer和layer.options.enableAltitude,layer.options.altitude影响
//@internal
_getAltitude(): number | number[] | number[][] {
const layer = this.getLayer();
if (!layer) {
return 0;
}
const layerOpts = layer.options;
const layerAltitude = layer.getAltitude ? layer.getAltitude() : 0;
const enableAltitude = layerOpts['enableAltitude'];
if (!enableAltitude && (layer as any).isVectorLayer) {
return layerAltitude;
}
const altitudeProperty = getAltitudeProperty(layer);
const properties = this.properties || TEMP_PROPERTIES;
const altitude = properties[altitudeProperty];
//if properties.altitude is null
//for new Geometry([x,y,z])
if (isNil(altitude)) {
const alts = getGeometryCoordinatesAlts(this, layerAltitude, enableAltitude);
if (!isNil(alts)) {
return alts;
}
return layerAltitude;
}
//old,the altitude is bind properties
if (Array.isArray(altitude)) {
return altitude.map(alt => {
return alt + layerAltitude;
});
}
return altitude + layerAltitude;
}
//this for user
getAltitude(): number | number[] | number[][] {
const layer = this.getLayer();
const altitudeProperty = getAltitudeProperty(layer);
const properties = this.properties || TEMP_PROPERTIES;
const altitude = properties[altitudeProperty];
if (!isNil(altitude)) {
return altitude;
}
const alts = getGeometryCoordinatesAlts(this, 0, false);
if (!isNil(alts)) {
return alts;
}
return 0;
}
hasAltitude(): boolean {
this._genMinMaxAlt();
return !!this._minAlt || !!this._maxAlt;
}
setAltitude(alt: number): this {
if (!isNumber(alt)) {
return this;
}
const layer = this.getLayer();
const altitudeProperty = getAltitudeProperty(layer);
const properties = this.properties || TEMP_PROPERTIES;
const altitude = properties[altitudeProperty];
//update properties altitude
if (!isNil(altitude)) {
if (Array.isArray(altitude)) {
for (let i = 0, len = altitude.length; i < len; i++) {
altitude[i] = alt;
}
} else {
properties[altitudeProperty] = alt;
}
}
const coordinates: any = this.getCoordinates ? this.getCoordinates() : null;
if (!coordinates) {
return this;
}
//update coordinates.z
setCoordinatesAlt(coordinates, alt);
if (layer) {
const renderer = layer.getRenderer();
//for webgllayer,pointlayer/linestringlayer/polygonlayer
if (renderer && (renderer.gl || (renderer as any).device)) {
this.setCoordinates(coordinates);
} else if (renderer) {
this._repaint();
}
}
this._clearAltitudeCache();
this.onPositionChanged();
return this;
}
//@internal
_genMinMaxAlt(): void {
if (this._minAlt === undefined || this._maxAlt === undefined) {
const altitude = this._getAltitude();
const [min, max] = getMinMaxAltitude(altitude);
this._minAlt = min;
this._maxAlt = max;
}
}
getMinAltitude(): number {
this._genMinMaxAlt();
return this._minAlt;
}
getMaxAltitude(): number {
this._genMinMaxAlt();
return this._maxAlt;
}
//clear alt cache
//@internal
_clearAltitudeCache(): Geometry {
this._minAlt = undefined;
this._maxAlt = undefined;
return this;
}
//@internal
_getEditCenter() {
const center = this.getCenter().copy();
center.z = 0;
const altitude = this.getAltitude();
if (isNumber(altitude)) {
center.z = altitude;
return center;
}
let total = 0, count = 0;
//计算图形海拔的平均值,确定图形中心点的海拔坐标
const checkArray = (altitudeArray) => {
for (let i = 0, len = altitudeArray.length; i < len; i++) {
if (Array.isArray(altitudeArray[i])) {
checkArray(altitudeArray[i]);
} else {
count++;
total += altitudeArray[i];
}
}
}
if (Array.isArray(altitude)) {
checkArray(altitude);
center.z = total / count;
}
return center;
}
}
Geometry.mergeOptions(options);
export type GeometryOptionsType = {
id?: string;
visible?: boolean;
interactive?: boolean;
editable?: boolean;
cursor?: string;
antiMeridian?: boolean;
defaultProjection?: string;
measure?: string;
draggable?: boolean;
dragShadow?: boolean;
dragOnAxis?: string;
dragOnScreenAxis?: boolean;
zIndex?: number;
symbol?: any;
properties?: { [key: string]: any };
rotateAngle?: number;
rotatePivot?: Array;
}
function getAltitudeProperty(layer: OverlayLayer): string {
let altitudeProperty = 'altitude';
if (layer) {
//only VectorLayer support properties.altitude
if (!(layer as any).isVectorLayer) {
return null;
}
const layerOpts = layer.options;
altitudeProperty = layerOpts['altitudeProperty'];
}
return altitudeProperty;
}
function getGeometryCoordinatesAlts(geometry: Geometry, layerAlt: number, enableAltitude: boolean): number | number[] | number[][] {
const coordinates: any = geometry.getCoordinates ? geometry.getCoordinates() : null;
if (coordinates) {
const tempAlts = [];
coordinatesHasAlt(coordinates, tempAlts);
if (tempAlts.length) {
const alts = getCoordinatesAlts(coordinates, layerAlt, enableAltitude);
// if (geometry.getShell && Array.isArray(alts[0])) {
// return alts;
// }
return alts;
}
}
return null;
}
function setCoordinatesAlt(coordinates: Coordinate, alt: number): void {
if (Array.isArray(coordinates)) {
for (let i = 0, len = coordinates.length; i < len; i++) {
setCoordinatesAlt(coordinates[i], alt);
}
} else {
coordinates.z = alt;
}
}
function coordinatesHasAlt(coordinates: Coordinate, tempAlts: number[]) {
if (tempAlts.length) {
return;
}
if (Array.isArray(coordinates)) {
for (let i = 0, len = coordinates.length; i < len; i++) {
coordinatesHasAlt(coordinates[i], tempAlts);
}
} else if (isNumber(coordinates.z)) {
tempAlts.push(coordinates.z);
}
}
function getCoordinatesAlts(coordinates: Coordinate, layerAlt: number, enableAltitude: boolean): number | number[] {
if (Array.isArray(coordinates)) {
const alts = [];
for (let i = 0, len = coordinates.length; i < len; i++) {
alts.push(getCoordinatesAlts(coordinates[i], layerAlt, enableAltitude));
}
return alts;
}
if (isNumber(coordinates.z)) {
return enableAltitude ? layerAlt + coordinates.z : coordinates.z;
} else if (enableAltitude) {
return layerAlt;
} else {
return 0;
}
}
function getSegmentAngle(cx: number, cy: number, x: number, y: number): number {
if (cx === x) {
if (y > cy) {
return -90;
}
return 90;
}
x -= cx;
y -= cy;
//经纬坐标系和屏幕坐标正好相反,经纬度向上递增,而屏幕坐标递减
y = -y;
const rad = Math.atan2(y, x);
return rad / Math.PI * 180;
}
export default Geometry;