From 4f9e8c13fcb5b71650d9bad9e67ae1ed15883ee3 Mon Sep 17 00:00:00 2001 From: hu de yi Date: Mon, 14 Jul 2025 15:11:10 +0800 Subject: [PATCH 1/2] Circle/Ellipse/Sector support ignoreProjection --- packages/maptalks/src/core/util/path.ts | 12 +++++ packages/maptalks/src/geometry/Circle.ts | 30 ++++++++++++- packages/maptalks/src/geometry/Ellipse.ts | 44 ++++++++++++++++++- packages/maptalks/src/geometry/Sector.ts | 24 ++++++++++ .../src/renderer/geometry/VectorRenderer.ts | 3 ++ 5 files changed, 109 insertions(+), 4 deletions(-) diff --git a/packages/maptalks/src/core/util/path.ts b/packages/maptalks/src/core/util/path.ts index d90ca1b857..f551c438bd 100644 --- a/packages/maptalks/src/core/util/path.ts +++ b/packages/maptalks/src/core/util/path.ts @@ -301,3 +301,15 @@ 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; +} + diff --git a/packages/maptalks/src/geometry/Circle.ts b/packages/maptalks/src/geometry/Circle.ts index 1dc46dceb2..ca70cfc51d 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 { 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,10 @@ 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(); + for (let i = 0, len = numberOfPoints - 1; i < len; i++) { rad = (360 * i / len) * Math.PI / 180; dx = radius * Math.cos(rad); @@ -100,6 +105,26 @@ export class Circle extends CenterMixin(Polygon) { const vertex = measurer.locate(center, dx, dy); vertex.z = center.z; shell.push(vertex); + if (ignoreProjection) { + break; + } + } + if (ignoreProjection && map) { + const glRes = map.getGLRes(); + const pt = map.coordToPointAtRes(center, glRes); + const p1 = map.coordToPointAtRes(shell[0], glRes); + const r = pt.distanceTo(p1); + 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 + pt.x; + const y = Math.sin(rad) * r + pt.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; } shell.push(shell[0]); return shell; @@ -221,4 +246,5 @@ export default Circle; export type CircleOptionsType = PolygonOptionsType & { numberOfShellPoints?: number; + ignoreProjection?: boolean; } diff --git a/packages/maptalks/src/geometry/Ellipse.ts b/packages/maptalks/src/geometry/Ellipse.ts index 9274ddca7e..f09cb938b1 100644 --- a/packages/maptalks/src/geometry/Ellipse.ts +++ b/packages/maptalks/src/geometry/Ellipse.ts @@ -1,5 +1,5 @@ import { extend, isNil, pushIn } from '../core/util'; -import { withInEllipse } from '../core/util/path'; +import { pointsToCoordinates, withInEllipse } from '../core/util/path'; import Coordinate from '../geo/Coordinate'; import CenterMixin from './CenterMixin'; import Polygon, { PolygonOptionsType, RingCoordinates, RingsCoordinates } from './Polygon'; @@ -44,7 +44,8 @@ function angleT(numberOfShellPoints: number) { * @instance */ const options: EllipseOptionsType = { - 'numberOfShellPoints': 81 + 'numberOfShellPoints': 81, + 'ignoreProjection': false }; /** @@ -182,6 +183,44 @@ 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 pt = map.coordToPointAtRes(center, glRes); + const c1 = measurer.locate(center, width / 2, 0); + + const p1 = map.coordToPointAtRes(c1, glRes); + //gl width + const w = p1.distanceTo(pt) * 2; + //gl width 高度计算直接利用和宽度的比即可,不用去measurer计算,因为相同的长度,由于投影的影响横轴和纵轴计算结果是不同的 + const h = w * height / width; + 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 = pt.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 +358,5 @@ export default Ellipse; export type EllipseOptionsType = PolygonOptionsType & { numberOfShellPoints?: number; debug?: boolean; + ignoreProjection?: boolean; } diff --git a/packages/maptalks/src/geometry/Sector.ts b/packages/maptalks/src/geometry/Sector.ts index 16a7f754c1..6aade2d827 100644 --- a/packages/maptalks/src/geometry/Sector.ts +++ b/packages/maptalks/src/geometry/Sector.ts @@ -1,4 +1,5 @@ import { extend, isNil } from '../core/util'; +import { pointsToCoordinates } from '../core/util/path'; import Coordinate from '../geo/Coordinate'; import Extent from '../geo/Extent'; import Point from '../geo/Point'; @@ -135,6 +136,29 @@ 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 pt = map.coordToPointAtRes(center, glRes); + const c1 = measurer.locate(center, radius, 0); + const p1 = map.coordToPointAtRes(c1, glRes); + const r = pt.distanceTo(p1); + 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 + pt.x; + const y = Math.sin(rad) * r + pt.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); diff --git a/packages/maptalks/src/renderer/geometry/VectorRenderer.ts b/packages/maptalks/src/renderer/geometry/VectorRenderer.ts index f5e88b626f..0c97dd7be1 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) { + 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()); }, From 10207597110d2e429f9ee3f372032e7e7b088fb0 Mon Sep 17 00:00:00 2001 From: hu de yi Date: Tue, 15 Jul 2025 09:56:42 +0800 Subject: [PATCH 2/2] tweaks --- packages/maptalks/src/core/util/path.ts | 14 ++++++++++ packages/maptalks/src/geometry/Circle.ts | 31 ++++++++++------------- packages/maptalks/src/geometry/Ellipse.ts | 15 +++++------ packages/maptalks/src/geometry/Sector.ts | 12 ++++----- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/packages/maptalks/src/core/util/path.ts b/packages/maptalks/src/core/util/path.ts index f551c438bd..4a78bc0cd1 100644 --- a/packages/maptalks/src/core/util/path.ts +++ b/packages/maptalks/src/core/util/path.ts @@ -313,3 +313,17 @@ export function pointsToCoordinates(map, points: Point[], glRes: number, altitud return ring; } +export function getEllipseGLSize(center: Coordinate, measurer, map, halfWidth: number, halfHeight: number) { + const glRes = map.getGLRes(); + const c1 = measurer.locate(center, halfWidth, 0); + const c2 = measurer.locate(center, 0, halfHeight); + const pt = map.coordToPointAtRes(center, glRes); + const p1 = map.coordToPointAtRes(c1, glRes); + const p2 = map.coordToPointAtRes(c2, glRes); + return { + glWidth: pt.distanceTo(p1), + glHeight: pt.distanceTo(p2), + glCenter: pt + } +} + diff --git a/packages/maptalks/src/geometry/Circle.ts b/packages/maptalks/src/geometry/Circle.ts index ca70cfc51d..418fcacc00 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 { pointsToCoordinates, 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'; @@ -98,27 +98,15 @@ export class Circle extends CenterMixin(Polygon) { const ignoreProjection = options.ignoreProjection; const map = this.getMap(); - 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); - if (ignoreProjection) { - break; - } - } if (ignoreProjection && map) { const glRes = map.getGLRes(); - const pt = map.coordToPointAtRes(center, glRes); - const p1 = map.coordToPointAtRes(shell[0], glRes); - const r = pt.distanceTo(p1); + 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 + pt.x; - const y = Math.sin(rad) * r + pt.y; + 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; } @@ -126,6 +114,15 @@ export class Circle extends CenterMixin(Polygon) { 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); + 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; } diff --git a/packages/maptalks/src/geometry/Ellipse.ts b/packages/maptalks/src/geometry/Ellipse.ts index f09cb938b1..47c1f90cf7 100644 --- a/packages/maptalks/src/geometry/Ellipse.ts +++ b/packages/maptalks/src/geometry/Ellipse.ts @@ -1,5 +1,5 @@ import { extend, isNil, pushIn } from '../core/util'; -import { pointsToCoordinates, 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'; @@ -188,14 +188,11 @@ export class Ellipse extends CenterMixin(Polygon) { const map = this.getMap(); if (ignoreProjection && map) { const glRes = map.getGLRes(); - const pt = map.coordToPointAtRes(center, glRes); - const c1 = measurer.locate(center, width / 2, 0); - - const p1 = map.coordToPointAtRes(c1, glRes); + const { glWidth, glHeight, glCenter } = getEllipseGLSize(center, measurer, map, width / 2, height / 2); //gl width - const w = p1.distanceTo(pt) * 2; - //gl width 高度计算直接利用和宽度的比即可,不用去measurer计算,因为相同的长度,由于投影的影响横轴和纵轴计算结果是不同的 - const h = w * height / width; + const w = glWidth * 2; + //gl width + 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); @@ -211,7 +208,7 @@ export class Ellipse extends CenterMixin(Polygon) { if (deg > 180 && deg < 360) { dy *= -1; } - const p = pt.copy(); + const p = glCenter.copy(); p.x += dx; p.y += dy; pts[i] = p; diff --git a/packages/maptalks/src/geometry/Sector.ts b/packages/maptalks/src/geometry/Sector.ts index 6aade2d827..231b479d58 100644 --- a/packages/maptalks/src/geometry/Sector.ts +++ b/packages/maptalks/src/geometry/Sector.ts @@ -1,5 +1,5 @@ import { extend, isNil } from '../core/util'; -import { pointsToCoordinates } from '../core/util/path'; +import { getEllipseGLSize, pointsToCoordinates } from '../core/util/path'; import Coordinate from '../geo/Coordinate'; import Extent from '../geo/Extent'; import Point from '../geo/Point'; @@ -141,17 +141,15 @@ export class Sector extends Circle { const map = this.getMap(); if (ignoreProjection && map) { const glRes = map.getGLRes(); - const pt = map.coordToPointAtRes(center, glRes); - const c1 = measurer.locate(center, radius, 0); - const p1 = map.coordToPointAtRes(c1, glRes); - const r = pt.distanceTo(p1); + 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 + pt.x; - const y = Math.sin(rad) * r + pt.y; + 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; }