diff --git a/packages/maptalks/src/core/util/path.ts b/packages/maptalks/src/core/util/path.ts index a3ed2f4596..20eba7ae3c 100644 --- a/packages/maptalks/src/core/util/path.ts +++ b/packages/maptalks/src/core/util/path.ts @@ -301,12 +301,77 @@ export function getMinMaxAltitude(altitude: number | number[] | number[][]): [nu return [min, max]; } +export function pointsToCoordinates(map, points: Point[], glRes: number, altitude: number): Coordinate[] { + const ring = []; + for (let i = 0, len = points.length; i < len; i++) { + const pt = points[i]; + const c = map.pointAtResToCoordinate(pt, glRes); + c.z = altitude; + ring[i] = c; + } + // ring.push(ring[0].copy()); + return ring; +} + +const WORLD_CENTER = new Coordinate(0, 0); + +export function getEllipseGLSize(center: Coordinate, measurer, map, halfWidth: number, halfHeight: number) { + const glRes = map.getGLRes(); + const c1 = measurer.locate(WORLD_CENTER, 1, 0); + // const c2 = measurer.locate(CENTER, 0, halfHeight); + const glCenter = map.coordToPointAtRes(center, glRes); + const p0 = map.coordToPointAtRes(WORLD_CENTER, glRes); + const p1 = map.coordToPointAtRes(c1, glRes); + // const p2 = map.coordToPointAtRes(c2, glRes); + const glWidth = p0.distanceTo(p1) * halfWidth; + const glHeight = glWidth * halfHeight / halfWidth; + + return { + glWidth, + glHeight, + glCenter + } +} + +export function getIgnoreProjectionGeometryCenter(geo) { + const ignoreProjection = geo.options.ignoreProjection; + if (ignoreProjection) { + const ring = geo.getShell ? geo.getShell() : null; + if (ring && ring.length) { + const map = geo.getMap(); + if (!map) { + return; + } + const glRes = map.getGLRes(); + const points = ring.map(c => { + return map.coordToPointAtRes(c, glRes); + }) + + let sumx = 0, + sumy = 0, + counter = 0; + const size = points.length; + for (let i = 0; i < size; i++) { + if (points[i]) { + if (isNumber(points[i].x) && isNumber(points[i].y)) { + sumx += points[i].x; + sumy += points[i].y; + counter++; + } + } + } + const p = new Point(sumx / counter, sumy / counter); + return map.pointAtResToCoordinate(p, glRes); + } + } +} + /** * point left segment - * @param p - * @param p1 - * @param p2 - * @returns + * @param p + * @param p1 + * @param p2 + * @returns */ export function pointLeftSegment(p: Point, p1: Point, p2: Point) { const x1 = p1.x, y1 = p1.y; @@ -320,7 +385,7 @@ function pointRightSegment(p: Point, p1: Point, p2: Point) { } /** - * + * * LT--------------------RT * \ / * \ / @@ -329,12 +394,12 @@ function pointRightSegment(p: Point, p1: Point, p2: Point) { * camera behind * * Points within a convex quadrilateral - * @param point - * @param p1 - * @param p2 - * @param p3 - * @param p4 - * @returns + * @param point + * @param p1 + * @param p2 + * @param p3 + * @param p4 + * @returns */ export function pointInQuadrilateral(p: Point, LT: Point, RT: Point, RB: Point, LB: Point) { //LT-RT @@ -352,11 +417,11 @@ export function pointInQuadrilateral(p: Point, LT: Point, RT: Point, RB: Point, /** * 直线和直线的交点 * Intersection of two line - * @param p1 - * @param p2 - * @param p3 - * @param p4 - * @returns + * @param p1 + * @param p2 + * @param p3 + * @param p4 + * @returns */ export function lineIntersection(p1: Point, p2: Point, p3: Point, p4: Point): Point | null { const dx1 = p2.x - p1.x, dy1 = p2.y - p1.y; @@ -404,8 +469,8 @@ export function lineIntersection(p1: Point, p2: Point, p3: Point, p4: Point): Po /** * Does it contain altitude values - * @param altitudes - * @returns + * @param altitudes + * @returns */ export function altitudesHasData(altitudes: number | Array) { if (isNumber(altitudes)) { diff --git a/packages/maptalks/src/geometry/CenterMixin.ts b/packages/maptalks/src/geometry/CenterMixin.ts index 54060a9ac8..0ed354930f 100644 --- a/packages/maptalks/src/geometry/CenterMixin.ts +++ b/packages/maptalks/src/geometry/CenterMixin.ts @@ -12,112 +12,113 @@ import type { Map } from '../map'; * @mixin CenterMixin */ export default function (Base: T) { - return class extends Base { - //@internal - _coordinates: Coordinate - //@internal - _pcenter: Coordinate - //@internal - _dirtyCoords: boolean - getMap?(): Map - //@internal - _getProjection?(): CommonProjectionType - onPositionChanged?(): void - //@internal - _verifyProjection?(): void - //@internal - _clearCache?(): void - //@internal - _translateRotatePivot?(coordinate: Coordinate): this; - /** - * 获取几何图形的中心点 - * @english - * Get geometry's center - * @return {Coordinate} - center of the geometry - * @function CenterMixin.getCoordinates - */ - getCoordinates(): Coordinate { - return this._coordinates; - } + return class extends Base { + //@internal + _coordinates: Coordinate + //@internal + _pcenter: Coordinate + //@internal + _dirtyCoords: boolean + getMap?(): Map + //@internal + _getProjection?(): CommonProjectionType + onPositionChanged?(): void + //@internal + _verifyProjection?(): void + //@internal + _clearCache?(): void + //@internal + _translateRotatePivot?(coordinate: Coordinate): this; + getShell?(); + /** + * 获取几何图形的中心点 + * @english + * Get geometry's center + * @return {Coordinate} - center of the geometry + * @function CenterMixin.getCoordinates + */ + getCoordinates(): Coordinate { + return this._coordinates; + } - /** - * 设置几何图形的中心点 - * @english - * Set a new center to the geometry - * @param {Coordinate|Number[]} coordinates - new center - * @return {Geometry} this - * @fires Geometry#positionchange - * @function CenterMixin.setCoordinates - */ - setCoordinates(coordinates: Coordinate | Array) { - const center = (coordinates instanceof Coordinate) ? coordinates : new Coordinate(coordinates as [number, number, number]); - this._translateRotatePivot(center); - this._coordinates = center; - if (!this.getMap()) { - //When not on a layer or when creating a new one, temporarily save the coordinates, - this._dirtyCoords = true; - this.onPositionChanged(); - return this; - } - const projection = this._getProjection(); - this._setPrjCoordinates(projection.project(this._coordinates)); - return this; - } + /** + * 设置几何图形的中心点 + * @english + * Set a new center to the geometry + * @param {Coordinate|Number[]} coordinates - new center + * @return {Geometry} this + * @fires Geometry#positionchange + * @function CenterMixin.setCoordinates + */ + setCoordinates(coordinates: Coordinate | Array) { + const center = (coordinates instanceof Coordinate) ? coordinates : new Coordinate(coordinates as [number, number, number]); + this._translateRotatePivot(center); + this._coordinates = center; + if (!this.getMap()) { + //When not on a layer or when creating a new one, temporarily save the coordinates, + this._dirtyCoords = true; + this.onPositionChanged(); + return this; + } + const projection = this._getProjection(); + this._setPrjCoordinates(projection.project(this._coordinates)); + return this; + } - //Gets view point of the geometry's center - //@internal - _getCenter2DPoint(res?: number): Point { - const map = this.getMap(); - if (!map) { - return null; - } - const pcenter = this._getPrjCoordinates(); - if (!pcenter) { return null; } - if (!res) { - res = map._getResolution(); - } - return map._prjToPointAtRes(pcenter, res); - } + //Gets view point of the geometry's center + //@internal + _getCenter2DPoint(res?: number): Point { + const map = this.getMap(); + if (!map) { + return null; + } + const pcenter = this._getPrjCoordinates(); + if (!pcenter) { return null; } + if (!res) { + res = map._getResolution(); + } + return map._prjToPointAtRes(pcenter, res); + } - //@internal - _getPrjCoordinates(): Coordinate { - const projection = this._getProjection(); - this._verifyProjection(); - if (!this._pcenter && projection) { - if (this._coordinates) { - this._pcenter = projection.project(this._coordinates); - } - } - return this._pcenter; + //@internal + _getPrjCoordinates(): Coordinate { + const projection = this._getProjection(); + this._verifyProjection(); + if (!this._pcenter && projection) { + if (this._coordinates) { + this._pcenter = projection.project(this._coordinates); } + } + return this._pcenter; + } - //Set center by projected coordinates - //@internal - _setPrjCoordinates(pcenter: Coordinate): void { - this._pcenter = pcenter; - this.onPositionChanged(); - } + //Set center by projected coordinates + //@internal + _setPrjCoordinates(pcenter: Coordinate): void { + this._pcenter = pcenter; + this.onPositionChanged(); + } - //update cached const iables if geometry is updated. - //@internal - _updateCache(): void { - this._clearCache(); - const projection = this._getProjection(); - if (this._pcenter && projection) { - this._coordinates = projection.unproject(this._pcenter); - } - } + //update cached const iables if geometry is updated. + //@internal + _updateCache(): void { + this._clearCache(); + const projection = this._getProjection(); + if (this._pcenter && projection) { + this._coordinates = projection.unproject(this._pcenter); + } + } - //@internal - _clearProjection(): void { - this._pcenter = null; - // @ts-expect-error todo - super._clearProjection(); - } + //@internal + _clearProjection(): void { + this._pcenter = null; + // @ts-expect-error todo + super._clearProjection(); + } - //@internal - _computeCenter(): Coordinate | null { - return this._coordinates ? this._coordinates.copy() : null; - } - }; + //@internal + _computeCenter(): Coordinate | null { + return this._coordinates ? this._coordinates.copy() : null; + } + }; } diff --git a/packages/maptalks/src/geometry/Circle.ts b/packages/maptalks/src/geometry/Circle.ts index 1dc46dceb2..c135749fae 100644 --- a/packages/maptalks/src/geometry/Circle.ts +++ b/packages/maptalks/src/geometry/Circle.ts @@ -1,5 +1,5 @@ import { extend, isNil } from '../core/util'; -import { withInEllipse } from '../core/util/path'; +import { getEllipseGLSize, pointsToCoordinates, withInEllipse } from '../core/util/path'; import Coordinate from '../geo/Coordinate'; import Extent from '../geo/Extent'; import Point from '../geo/Point'; @@ -14,7 +14,8 @@ import Polygon, { PolygonOptionsType, RingCoordinates, RingsCoordinates } from ' * @instance */ const options: CircleOptionsType = { - 'numberOfShellPoints': 60 + 'numberOfShellPoints': 60, + 'ignoreProjection': false }; /** @@ -93,6 +94,26 @@ export class Circle extends CenterMixin(Polygon) { radius = this.getRadius(); const shell = []; let rad, dx, dy; + const options = this.options as CircleOptionsType; + const ignoreProjection = options.ignoreProjection; + const map = this.getMap(); + + if (ignoreProjection && map) { + const glRes = map.getGLRes(); + const { glWidth, glHeight, glCenter } = getEllipseGLSize(center, measurer, map, radius, radius); + const R = Math.max(glWidth, glHeight); + const pts: Point[] = []; + for (let i = 0, len = numberOfPoints - 1; i < len; i++) { + rad = (360 * i / len) * Math.PI / 180; + const x = Math.cos(rad) * R + glCenter.x; + const y = Math.sin(rad) * R + glCenter.y; + const p = new Point(x, y); + pts[i] = p; + } + const ring = pointsToCoordinates(map, pts, glRes, center.z); + ring.push(ring[0].copy()); + return ring; + } for (let i = 0, len = numberOfPoints - 1; i < len; i++) { rad = (360 * i / len) * Math.PI / 180; dx = radius * Math.cos(rad); @@ -101,6 +122,7 @@ export class Circle extends CenterMixin(Polygon) { vertex.z = center.z; shell.push(vertex); } + shell.push(shell[0]); return shell; } @@ -221,4 +243,8 @@ export default Circle; export type CircleOptionsType = PolygonOptionsType & { numberOfShellPoints?: number; +} & SpecialGeometryOptionsType; + +export type SpecialGeometryOptionsType = { + ignoreProjection?: boolean; } diff --git a/packages/maptalks/src/geometry/Ellipse.ts b/packages/maptalks/src/geometry/Ellipse.ts index 9274ddca7e..7d61ed73de 100644 --- a/packages/maptalks/src/geometry/Ellipse.ts +++ b/packages/maptalks/src/geometry/Ellipse.ts @@ -1,9 +1,9 @@ import { extend, isNil, pushIn } from '../core/util'; -import { withInEllipse } from '../core/util/path'; +import { getEllipseGLSize, pointsToCoordinates, withInEllipse } from '../core/util/path'; import Coordinate from '../geo/Coordinate'; import CenterMixin from './CenterMixin'; import Polygon, { PolygonOptionsType, RingCoordinates, RingsCoordinates } from './Polygon'; -import Circle from './Circle'; +import Circle, { SpecialGeometryOptionsType } from './Circle'; import Point from '../geo/Point'; import Extent from '../geo/Extent'; @@ -44,7 +44,8 @@ function angleT(numberOfShellPoints: number) { * @instance */ const options: EllipseOptionsType = { - 'numberOfShellPoints': 81 + 'numberOfShellPoints': 81, + 'ignoreProjection': false }; /** @@ -182,6 +183,42 @@ export class Ellipse extends CenterMixin(Polygon) { } let deg, rad, dx, dy; + const options = this.options as EllipseOptionsType; + const ignoreProjection = options.ignoreProjection; + const map = this.getMap(); + if (ignoreProjection && map) { + const glRes = map.getGLRes(); + const { glWidth, glHeight, glCenter } = getEllipseGLSize(center, measurer, map, width / 2, height / 2); + //gl width + const w = glWidth * 2; + //gl height + const h = glHeight * 2; + + const s = Math.pow(w / 2, 2) * Math.pow(h / 2, 2), + sx = Math.pow(w / 2, 2), + sy = Math.pow(h / 2, 2); + const pts: Point[] = []; + for (let i = 0; i < angles.length; i++) { + deg = angles[i]; + rad = deg * Math.PI / 180; + dx = Math.sqrt(s / (sx * Math.pow(Math.tan(rad), 2) + sy)); + dy = Math.sqrt(s / (sy * Math.pow(1 / Math.tan(rad), 2) + sx)); + if (deg > 90 && deg < 270) { + dx *= -1; + } + if (deg > 180 && deg < 360) { + dy *= -1; + } + const p = glCenter.copy(); + p.x += dx; + p.y += dy; + pts[i] = p; + } + const ring = pointsToCoordinates(map, pts, glRes, center.z); + ring.push(ring[0].copy()); + return ring; + } + for (let i = 0; i < angles.length; i++) { deg = angles[i]; rad = deg * Math.PI / 180; @@ -319,4 +356,4 @@ export default Ellipse; export type EllipseOptionsType = PolygonOptionsType & { numberOfShellPoints?: number; debug?: boolean; -} +} & SpecialGeometryOptionsType; diff --git a/packages/maptalks/src/geometry/Rectangle.ts b/packages/maptalks/src/geometry/Rectangle.ts index 7d8ac23c7f..40c1aa6577 100644 --- a/packages/maptalks/src/geometry/Rectangle.ts +++ b/packages/maptalks/src/geometry/Rectangle.ts @@ -1,8 +1,10 @@ import { extend, isNil } from '../core/util'; +import { getEllipseGLSize, getIgnoreProjectionGeometryCenter, pointsToCoordinates } 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 { SpecialGeometryOptionsType } from './Circle'; import Polygon, { PolygonOptionsType, RingCoordinates, RingsCoordinates } from './Polygon'; /** @@ -144,6 +146,20 @@ export class Rectangle extends Polygon { sy = 1; } } + const options = this.options as RectangleOptionsType; + const ignoreProjection = options.ignoreProjection; + if (map && ignoreProjection) { + const center = nw, width = this._width, height = this._height; + const { glWidth, glHeight, glCenter } = getEllipseGLSize(center, measurer, map, width, height); + const p1 = glCenter.add(glWidth * sx, 0); + const p2 = glCenter.add(glWidth * sx, glHeight * sy); + const p3 = glCenter.add(0, glHeight * sy); + const glRes = map.getGLRes(); + + const coordinates = pointsToCoordinates(map, [glCenter, p1, p2, p3] as Point[], glRes, center.z); + return coordinates; + } + const points = []; points.push(nw); const p0 = measurer.locate(nw, sx * this._width, 0); @@ -236,6 +252,10 @@ export class Rectangle extends Polygon { //@internal _computeCenter(measurer?: any): Coordinate { + const center = getIgnoreProjectionGeometryCenter(this); + if (center) { + return center; + } return measurer.locate(this._coordinates, this._width / 2, -this._height / 2); } @@ -348,4 +368,4 @@ Rectangle.registerJSONType('Rectangle'); export default Rectangle; -export type RectangleOptionsType = PolygonOptionsType; +export type RectangleOptionsType = PolygonOptionsType & SpecialGeometryOptionsType; diff --git a/packages/maptalks/src/geometry/Sector.ts b/packages/maptalks/src/geometry/Sector.ts index 025686a6ee..0d11b9e82c 100644 --- a/packages/maptalks/src/geometry/Sector.ts +++ b/packages/maptalks/src/geometry/Sector.ts @@ -1,5 +1,6 @@ import { Geometry } from './Geometry'; import { extend, isNil } from '../core/util'; +import { getEllipseGLSize, pointsToCoordinates } from '../core/util/path'; import Coordinate from '../geo/Coordinate'; import Extent from '../geo/Extent'; import Point from '../geo/Point'; @@ -136,6 +137,27 @@ export class Sector extends Circle { // startAngle = this.getStartAngle(), angle = endAngle - startAngle; let rad, dx, dy; + const options = this.options as SectorOptionsType; + const ignoreProjection = options.ignoreProjection; + const map = this.getMap(); + if (ignoreProjection && map) { + const glRes = map.getGLRes(); + const { glWidth, glHeight, glCenter } = getEllipseGLSize(center, measurer, map, radius, radius); + const r = Math.max(glWidth, glHeight); + const pts: Point[] = []; + for (let i = 0; i < numberOfPoints; i++) { + rad = (angle * i / (numberOfPoints - 1) + startAngle) * Math.PI / 180; + dx = radius * Math.cos(rad); + dy = radius * Math.sin(rad); + const x = Math.cos(rad) * r + glCenter.x; + const y = Math.sin(rad) * r + glCenter.y; + const p = new Point(x, y); + pts[i] = p; + } + const ring = pointsToCoordinates(map, pts, glRes, center.z); + ring.push(center.copy()); + return ring; + } for (let i = 0; i < numberOfPoints; i++) { rad = (angle * i / (numberOfPoints - 1) + startAngle) * Math.PI / 180; dx = radius * Math.cos(rad); @@ -245,6 +267,4 @@ Sector.registerJSONType('Sector'); export default Sector; -export type SectorOptionsType = CircleOptionsType & { - numberOfShellPoints?: number; -} +export type SectorOptionsType = CircleOptionsType; diff --git a/packages/maptalks/src/renderer/geometry/VectorRenderer.ts b/packages/maptalks/src/renderer/geometry/VectorRenderer.ts index f5e88b626f..7733b03d16 100644 --- a/packages/maptalks/src/renderer/geometry/VectorRenderer.ts +++ b/packages/maptalks/src/renderer/geometry/VectorRenderer.ts @@ -110,6 +110,9 @@ const el = { } const map = this.getMap(); const altitude = this._getAltitude(); + if ((this instanceof Ellipse || this instanceof Circle || this instanceof Rectangle) && this.options.ignoreProjection) { + return true; + } // when map is tilting, draw the circle/ellipse as a polygon by vertexes. return altitude > 0 || map.getPitch() || ((this instanceof Ellipse) && map.getBearing()); },