From 8e4fc98d2eb4f1f8d282866c616bcbb4de1c0ccb Mon Sep 17 00:00:00 2001 From: Remenby31 Date: Wed, 25 Mar 2026 11:35:48 +0100 Subject: [PATCH] fix(select): support circle resize on globe and web-mercator projections Circle features were distorted into ovals when resized because the select mode used generic bbox-based coordinate scaling. On globe projection, resizing threw an error entirely. This fix detects circle features (via the `radiusKilometers` property) and regenerates the circle from its center + new radius instead of scaling individual coordinates. The center is captured once at drag start to prevent centroid drift during progressive updates. Works for both `globe` (geodesic circle) and `web-mercator` projections. Closes #436 --- .../src/modes/select/select.mode.ts | 102 +++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/packages/terra-draw/src/modes/select/select.mode.ts b/packages/terra-draw/src/modes/select/select.mode.ts index 505c3d5a..7b0bbe35 100644 --- a/packages/terra-draw/src/modes/select/select.mode.ts +++ b/packages/terra-draw/src/modes/select/select.mode.ts @@ -15,7 +15,7 @@ import { MARKER_URL_DEFAULT, FinishActions, } from "../../common"; -import { LineString, Point, Polygon, Position } from "geojson"; +import { Feature, LineString, Point, Polygon, Position } from "geojson"; import { BaseModeOptions, CustomStyling, @@ -37,6 +37,9 @@ import { RotateFeatureBehavior } from "./behaviors/rotate-feature.behavior"; import { ScaleFeatureBehavior } from "./behaviors/scale-feature.behavior"; import { FeatureId, GeoJSONStoreFeatures } from "../../store/store"; import { getDefaultStyling } from "../../util/styling"; +import { circle, circleWebMercator } from "../../geometry/shape/create-circle"; +import { centroid } from "../../geometry/centroid"; +import { haversineDistanceKilometers } from "../../geometry/measure/haversine-distance"; import { DragCoordinateResizeBehavior, ResizeOptions, @@ -173,6 +176,10 @@ export class TerraDrawSelectMode extends TerraDrawBaseSelectMode(selectedId); + const feature = { + type: "Feature" as const, + geometry, + properties: {}, + }; + this.circleResizeCenter = centroid(feature as Feature); + } + setMapDraggability(false); return; } @@ -966,6 +989,19 @@ export class TerraDrawSelectMode extends TerraDrawBaseSelectMode(featureId); + + // Use the center captured at drag start to avoid drift + const center = this.circleResizeCenter; + if (!center) { + return; + } + + const newRadius = haversineDistanceKilometers(center, [ + event.lng, + event.lat, + ]); + + if (newRadius <= 0) { + return; + } + + const steps = geometry.coordinates[0].length - 1; // -1 for closing coord + + const updatedCircle = + this.projection === "globe" + ? circle({ + center, + radiusKilometers: newRadius, + coordinatePrecision: this.coordinatePrecision, + steps, + }) + : circleWebMercator({ + center, + radiusKilometers: newRadius, + coordinatePrecision: this.coordinatePrecision, + steps, + }); + + const updated = this.mutateFeature.updatePolygon({ + featureId, + coordinateMutations: { + type: Mutations.Replace, + coordinates: updatedCircle.geometry.coordinates, + }, + propertyMutations: { + radiusKilometers: newRadius, + }, + context: { + updateType: UpdateTypes.Provisional, + }, + }); + + if (!updated) { + return; + } + + const featureCoordinates = updated.geometry.coordinates; + this.midPoints.updateAllInPlace({ featureCoordinates }); + this.selectionPoints.updateAllInPlace({ featureCoordinates }); + } + afterFeatureUpdated(feature: GeoJSONStoreFeatures) { // If we have a selected feature and it has been updated // we need to update the selection points and midpoints