ofs-object 0.1.0-beta.1 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -80,6 +80,97 @@ declare type AnyFeature = Feature<Geometry, any>;
80
80
  /** Apply a class color / size / override on top of the base symbol */
81
81
  export declare function applyClassSymbol(base: SymbolStyle, classColor: string | undefined, classSize: number | undefined, override?: ClassSymbolOverride): SymbolStyle;
82
82
 
83
+ /**
84
+ * Rebuild a feature from vertices and bends: the curve goes into the properties and the polyline
85
+ * that samples it into the geometry. A run with no bend left drops the property, so straightening
86
+ * the last arc leaves an ordinary feature behind.
87
+ */
88
+ export declare function applyCurveState(feature: Feature<Geometry, any>, state: CurveState): Feature<Geometry, any> | null;
89
+
90
+ /** True when the point lies to the left of the chord, the side the arc bulges towards */
91
+ export declare function arcBulgesLeft(arc: PlanarArc): boolean;
92
+
93
+ /** Straight-line distance between the two ends, in meters */
94
+ export declare function arcChordLength(arc: PlanarArc): number;
95
+
96
+ /** The point an arc ends at */
97
+ export declare function arcEndPoint(arc: PlanarArc): PlanarPoint;
98
+
99
+ /** Rebuild an arc from two ends and a DXF bulge factor. Returns null for a bulge of zero (a straight segment). */
100
+ export declare function arcFromBulge(start: PlanarPoint, end: PlanarPoint, bulge: number): PlanarArc | null;
101
+
102
+ /**
103
+ * AutoCAD's Start, Center, Length - the length of the *chord*, not of the arc. Returns null when the
104
+ * chord is longer than the diameter, which no arc on this circle can span.
105
+ */
106
+ export declare function arcFromStartCenterChord(start: PlanarPoint, center: PlanarPoint, chordLength: number, ccw?: boolean): PlanarArc | null;
107
+
108
+ /**
109
+ * AutoCAD's Start, Center, End. The radius comes from the start point; the end point only supplies
110
+ * a direction, so an end that is not exactly on the circle is pulled onto it rather than refused.
111
+ */
112
+ export declare function arcFromStartCenterEnd(start: PlanarPoint, center: PlanarPoint, end: PlanarPoint, ccw?: boolean): PlanarArc | null;
113
+
114
+ /** AutoCAD's Start, Center, Angle. A positive sweep turns counter-clockwise. */
115
+ export declare function arcFromStartCenterSweep(start: PlanarPoint, center: PlanarPoint, sweep: number): PlanarArc | null;
116
+
117
+ /**
118
+ * AutoCAD's Start, End, Direction: the arc leaves `start` heading along `direction` and still reaches
119
+ * `end`. This is the one a polyline uses when a straight run turns into a curve, because passing the
120
+ * previous segment's direction makes the arc tangent to it and the join comes out smooth.
121
+ *
122
+ * Returns null when `direction` points straight at (or straight away from) `end`: the answer there is
123
+ * a line, not an arc of infinite radius.
124
+ */
125
+ export declare function arcFromStartEndDirection(start: PlanarPoint, end: PlanarPoint, direction: PlanarPoint): PlanarArc | null;
126
+
127
+ /**
128
+ * AutoCAD's Start, End, Radius. Two ends and a radius describe four different arcs, so the caller
129
+ * says which: `ccw` picks the direction of travel and `major` picks the long way round.
130
+ * Returns null when the radius is too small to reach both ends.
131
+ */
132
+ export declare function arcFromStartEndRadius(start: PlanarPoint, end: PlanarPoint, radius: number, options?: {
133
+ ccw?: boolean;
134
+ major?: boolean;
135
+ }): PlanarArc | null;
136
+
137
+ /**
138
+ * AutoCAD's Start, End, Angle. The sweep and the two ends fix the radius, so this is the form to use
139
+ * when the turn matters more than the size - a pipe bend specified as "45 degrees", say.
140
+ */
141
+ export declare function arcFromStartEndSweep(start: PlanarPoint, end: PlanarPoint, sweep: number): PlanarArc | null;
142
+
143
+ /** Length along the arc, in meters */
144
+ export declare function arcLength(arc: PlanarArc): number;
145
+
146
+ /** The point halfway along the arc - the grip a user drags to change the bulge */
147
+ export declare function arcMidPoint(arc: PlanarArc): PlanarPoint;
148
+
149
+ /** The point at parameter t along the arc, t = 0 at the start and t = 1 at the end */
150
+ export declare function arcPointAt(arc: PlanarArc, t: number): PlanarPoint;
151
+
152
+ /** The point an arc starts at */
153
+ export declare function arcStartPoint(arc: PlanarArc): PlanarPoint;
154
+
155
+ /** Unit direction of travel at parameter t, pointing the way the arc is drawn */
156
+ export declare function arcTangentAt(arc: PlanarArc, t: number): PlanarPoint;
157
+
158
+ /**
159
+ * The arc through three points, in the order given. Returns null when the points are collinear or
160
+ * two of them coincide - there is no circle through them, and the caller should draw a line.
161
+ *
162
+ * This is AutoCAD's 3-Point arc, and also what a "drag the midpoint" grip produces: the two ends
163
+ * stay put and the dragged point rides the arc.
164
+ */
165
+ export declare function arcThroughPoints(start: PlanarPoint, through: PlanarPoint, end: PlanarPoint): PlanarArc | null;
166
+
167
+ /**
168
+ * DXF bulge factor: the tangent of a quarter of the sweep. DXF and several CAD formats store an arc
169
+ * inside a polyline this way, so keeping the conversion here means the export path needs no arc
170
+ * maths of its own.
171
+ */
172
+ export declare function arcToBulge(arc: PlanarArc): number;
173
+
83
174
  /** 1 rai = 4 ngan = 400 square wa = 1600 m² */
84
175
  export declare const AREA_UNITS: Record<string, number>;
85
176
 
@@ -114,6 +205,9 @@ export declare function askRequiredPoint(consoleService: IOFSConsole, prompt: st
114
205
 
115
206
  export declare function assertValidPolygon(geometry: Polygon | MultiPolygon, what: string): void;
116
207
 
208
+ /** Write a curve definition onto a properties object, or clear it when there is no curve left */
209
+ export declare function attachCurve(properties: Record<string, any>, meta: OfsCurveMeta | null): Record<string, any>;
210
+
117
211
  export declare interface AttributeRow {
118
212
  featureId: string | number;
119
213
  selected: boolean;
@@ -258,6 +352,13 @@ export declare abstract class BaseGLAdapter implements IMapAdapter {
258
352
  destroy(): void;
259
353
  }
260
354
 
355
+ /**
356
+ * Bend one segment of a feature into an arc turning through `sweepDegrees`, or straighten it when
357
+ * the sweep is zero. The turn is asked for rather than guessed: there is no one bend that is
358
+ * obviously right for a straight line. Returns null when the segment or the feature cannot take one.
359
+ */
360
+ export declare function bendSegment(feature: Feature<Geometry, any>, segmentIndex: number, sweepDegrees: number): Feature<Geometry, any> | null;
361
+
261
362
  export declare type BoundingBox = [number, number, number, number];
262
363
 
263
364
  /** Build contiguous breaks from ascending upper bounds, with colors, labels and counts */
@@ -275,6 +376,13 @@ export declare const BUILTIN_MARKER_SHAPES: MarkerShape[];
275
376
  /** Points along a bulge segment (excluding the start point, including the end point) */
276
377
  export declare function bulgePoints(p1: Position, p2: Position, bulge: number, maxStepDeg: number): Position[];
277
378
 
379
+ /**
380
+ * The bulge that makes the segment from `start` to `end` pass through `through`. This is what a
381
+ * midpoint grip reports while it is being dragged: the two ends stay put and the arc follows the
382
+ * pointer. Returns 0 when the three points line up, which straightens the segment again.
383
+ */
384
+ export declare function bulgeThroughPoint(start: Coordinate, through: Coordinate, end: Coordinate, projection?: PlanarProjection): number;
385
+
278
386
  export declare class CADOperations {
279
387
  /**
280
388
  * Split a Polygon using a LineString.
@@ -601,6 +709,28 @@ export declare interface ClassSymbolOverride {
601
709
  rotationDeg?: number;
602
710
  }
603
711
 
712
+ /**
713
+ * Closest point on the arc to an arbitrary point, and how far away it is. Used for snapping onto a
714
+ * curve: the stored polyline's vertices are sampling artefacts, so snapping to them would put a
715
+ * point where the user never drew one.
716
+ */
717
+ export declare function closestPointOnArc(arc: PlanarArc, point: PlanarPoint): {
718
+ point: PlanarPoint;
719
+ t: number;
720
+ distance: number;
721
+ };
722
+
723
+ /**
724
+ * Closest point on the curve to a coordinate, measured against the real arcs rather than the sampled
725
+ * polyline. Snapping has to ask this: the sampled vertices are an artefact of the tolerance, so
726
+ * snapping to one of them would drop a point where the user never drew anything.
727
+ */
728
+ export declare function closestPointOnCurve(meta: OfsCurveMeta, coordinate: Coordinate, projection?: PlanarProjection): {
729
+ coordinate: Coordinate;
730
+ segmentIndex: number;
731
+ distanceMeters: number;
732
+ } | null;
733
+
604
734
  /**
605
735
  * Check and convert one value for a field. Text is converted to the field type only when the meaning is
606
736
  * unambiguous ("12" for a number field, "true"/"1" for a boolean); anything else is refused.
@@ -861,6 +991,50 @@ export declare function crsToOgcWkt(crs: ResolvedCrs): string;
861
991
  /** WKT for .prj export (ESRI flavour for common CRSs) */
862
992
  export declare function crsToPrj(crs: ResolvedCrs): string;
863
993
 
994
+ /** Property name the curve definition lives under */
995
+ export declare const CURVE_PROPERTY = "ofsCurve";
996
+
997
+ export declare interface CurveGripPoint {
998
+ /** Index of the segment this grip bends */
999
+ segmentIndex: number;
1000
+ coordinate: Coordinate;
1001
+ /** True once the segment is already curved; a straight segment's grip is its plain midpoint */
1002
+ curved: boolean;
1003
+ }
1004
+
1005
+ /**
1006
+ * One grip per segment, sitting at the middle of the arc, or at the middle of the straight line for
1007
+ * a segment that has not been bent yet. Dragging one is how a drawn feature gains a curve.
1008
+ */
1009
+ export declare function curveGripPoints(vertices: Coordinate[], bulges: number[], sagToleranceMeters?: number, projection?: PlanarProjection): CurveGripPoint[];
1010
+
1011
+ /** One segment of a feature that can be straight or bent, as a UI would list it for a menu */
1012
+ export declare interface CurveSegmentInfo {
1013
+ index: number;
1014
+ curved: boolean;
1015
+ /** Where the bend handle sits: the middle of the arc, or the middle of the straight segment */
1016
+ coordinate: Coordinate;
1017
+ /** How far the arc stands off its chord, in meters; zero while the segment is straight */
1018
+ sagMeters: number;
1019
+ }
1020
+
1021
+ export declare interface CurveState {
1022
+ vertices: Coordinate[];
1023
+ bulges: number[];
1024
+ sagToleranceMeters: number;
1025
+ }
1026
+
1027
+ /**
1028
+ * The vertices and bends behind a feature, whether or not it has been curved yet.
1029
+ *
1030
+ * A feature that carries a curve keeps its real vertices in the properties, because its geometry is
1031
+ * a sampled polyline whose points are an artefact of the drawing tolerance. A feature with no curve
1032
+ * has no such distinction: its coordinates are its vertices and every segment is straight.
1033
+ *
1034
+ * Only single-ring lines and polygons can hold a curve; anything else returns null.
1035
+ */
1036
+ export declare function curveStateOf(feature: Feature<Geometry, any> | null | undefined): CurveState | null;
1037
+
864
1038
  export declare interface DbfTable {
865
1039
  fields: (FieldDefinition & {
866
1040
  dbfType: string;
@@ -897,6 +1071,16 @@ export declare const DEFAULT_MAX_NETWORK_POINTS = 5000;
897
1071
 
898
1072
  export declare const DEFAULT_POLAR_TRACKING: PolarTrackingSettings;
899
1073
 
1074
+ export declare const DEFAULT_PREVIEW_STYLE: DrawPreviewStyle;
1075
+
1076
+ /**
1077
+ * How far the stored polyline may sag away from the true arc, in meters. Five centimeters is below
1078
+ * the positional accuracy of the survey data these plans are drawn over, and is deliberately a
1079
+ * distance on the ground rather than a step count: a fixed number of steps makes a 2 m arc far more
1080
+ * detailed than it needs to be and a 2 km arc visibly faceted.
1081
+ */
1082
+ export declare const DEFAULT_SAG_TOLERANCE_METERS = 0.05;
1083
+
900
1084
  export declare const DEFAULT_SYMBOL_COLOR = "#3b82f6";
901
1085
 
902
1086
  export declare const DEFAULT_TOPOLOGY_OPTIONS: ResolvedTopologyOptions;
@@ -980,6 +1164,14 @@ export declare interface DrawEvents {
980
1164
  mode: DrawMode;
981
1165
  coordinates: Coordinate[];
982
1166
  };
1167
+ 'draw:arc-segment': {
1168
+ /** Whether the next segment will be drawn as an arc */
1169
+ enabled: boolean;
1170
+ /** Fixed turn in degrees, or null when the arc follows on smoothly from the last segment */
1171
+ sweepDegrees: number | null;
1172
+ /** False when an arc was asked for but there is nothing yet for it to continue from */
1173
+ ready: boolean;
1174
+ };
983
1175
  'draw:tracking-points': {
984
1176
  points: Coordinate[];
985
1177
  };
@@ -1063,6 +1255,21 @@ export declare type DrawMode = 'point' | 'line' | 'polygon' | 'circle' | 'rectan
1063
1255
  */
1064
1256
  export declare function drawNorthArrow(ctx: DrawingContext, x: number, y: number, size?: number, bearingDeg?: number): void;
1065
1257
 
1258
+ export declare interface DrawPreviewStyle {
1259
+ pointColor: string;
1260
+ pointRadius: number;
1261
+ pointStrokeColor: string;
1262
+ pointStrokeWidth: number;
1263
+ lineColor: string;
1264
+ lineWidth: number;
1265
+ /** Dash pattern in multiples of the line width; an empty list draws a solid line */
1266
+ lineDash: number[];
1267
+ fillColor: string;
1268
+ fillOpacity: number;
1269
+ /** Applied to points and lines together, so the whole preview can be dimmed at once */
1270
+ opacity: number;
1271
+ }
1272
+
1066
1273
  /** Draw a scale bar with its left end at (x, y) (y is the bottom of the bar) */
1067
1274
  export declare function drawScaleBar(ctx: DrawingContext, plan: ScaleBarPlan, x: number, y: number, style?: ScaleBarStyle): void;
1068
1275
 
@@ -1417,7 +1624,11 @@ export declare class GridTooLargeError extends OFSError {
1417
1624
  export declare interface GripHandle {
1418
1625
  id: string;
1419
1626
  coordinate: Coordinate;
1420
- type: 'vertex' | 'midpoint' | 'center';
1627
+ /**
1628
+ * `curve` is the bend handle of an arc segment: dragging it reshapes the arc rather than moving a
1629
+ * point, which is why it is not a `midpoint` - dragging one of those inserts a vertex.
1630
+ */
1631
+ type: 'vertex' | 'midpoint' | 'center' | 'curve';
1421
1632
  index: number;
1422
1633
  ringIndex: number;
1423
1634
  partIndex?: number;
@@ -1442,6 +1653,9 @@ export declare interface GripsEditorEvents {
1442
1653
  'grip:clear': void;
1443
1654
  }
1444
1655
 
1656
+ /** A curve with no bend worth drawing is just a polyline and needs no metadata */
1657
+ export declare function hasCurvedSegments(meta: OfsCurveMeta, projection?: PlanarProjection): boolean;
1658
+
1445
1659
  /** Stable short hash for image ids */
1446
1660
  export declare function hashString(text: string): string;
1447
1661
 
@@ -1681,6 +1895,20 @@ export declare function isInlineSvg(source: string): boolean;
1681
1895
 
1682
1896
  export declare function isInternalProperty(name: string): boolean;
1683
1897
 
1898
+ /**
1899
+ * Whether one segment is bent enough to be worth treating as an arc.
1900
+ *
1901
+ * The test is the sag the bend actually produces on the ground, not the size of the bulge number: a
1902
+ * bulge is a shape, so the same value is a sharp bend across a 5 m segment and an invisible one
1903
+ * across 5 km. Judging it by the same tolerance the geometry is sampled at means a segment counts as
1904
+ * curved exactly when drawing it as an arc would put the line somewhere a straight one would not.
1905
+ *
1906
+ * Two points at the same latitude are the case that makes this matter: a parallel is not a straight
1907
+ * line on a Transverse Mercator plane, so dragging a grip back onto the apparent line leaves a
1908
+ * millimetre of real bend behind. Without this the segment would never straighten again.
1909
+ */
1910
+ export declare function isSegmentCurved(meta: OfsCurveMeta, index: number, projection?: PlanarProjection): boolean;
1911
+
1684
1912
  export declare function isTruthy(value: ExpressionValue): boolean;
1685
1913
 
1686
1914
  /**
@@ -1935,6 +2163,12 @@ export declare type LocationPredicate = 'intersects' | 'within' | 'contains' | '
1935
2163
  /** True when every coordinate is a plausible lng/lat */
1936
2164
  export declare function looksGeographic(bounds: [number, number, number, number]): boolean;
1937
2165
 
2166
+ /**
2167
+ * Build a curve definition from the vertices a user placed and the bend of each segment.
2168
+ * Returns null when the run is straight throughout, so a plain polyline carries no extra properties.
2169
+ */
2170
+ export declare function makeCurve(vertices: Coordinate[], bulges: number[], sagToleranceMeters?: number): OfsCurveMeta | null;
2171
+
1938
2172
  export declare class MapboxAdapter extends BaseGLAdapter {
1939
2173
  readonly engineName: "mapbox";
1940
2174
  init(container: HTMLElement | string, options?: Record<string, any>): Promise<void>;
@@ -2046,6 +2280,9 @@ export declare interface MarkerSymbol {
2046
2280
  allowOverlap?: boolean;
2047
2281
  }
2048
2282
 
2283
+ /** Never emit more than this many points for one arc, whatever the tolerance asks for */
2284
+ export declare const MAX_ARC_POINTS = 2048;
2285
+
2049
2286
  /** Distance (m) and user angle from `from` to `to` */
2050
2287
  export declare function measure(from: Coordinate, to: Coordinate, frame?: InputFrame): {
2051
2288
  distance: number;
@@ -2352,6 +2589,23 @@ export declare class OFSCommandRegistry {
2352
2589
  suggest(prefix: string): string[];
2353
2590
  }
2354
2591
 
2592
+ export declare interface OfsCurveMeta {
2593
+ version: 1;
2594
+ /** The points the user actually placed, in order. The geometry samples the curve between them. */
2595
+ vertices: Coordinate[];
2596
+ /**
2597
+ * One entry per segment, so always one shorter than `vertices`. Zero is a straight segment;
2598
+ * otherwise the tangent of a quarter of the arc's sweep, positive counter-clockwise.
2599
+ */
2600
+ bulges: number[];
2601
+ /**
2602
+ * How far the sampled geometry may stand away from the true curve, in meters on the ground. Kept
2603
+ * with the feature so that re-sampling after an edit reproduces the fidelity it was drawn at,
2604
+ * rather than silently changing it.
2605
+ */
2606
+ sagToleranceMeters: number;
2607
+ }
2608
+
2355
2609
  export declare class OFSDataSource extends TypedEventEmitter<OFSDataSourceEvents> {
2356
2610
  private layers;
2357
2611
  private groups;
@@ -2610,12 +2864,64 @@ export declare interface OFSDataSourceEvents {
2610
2864
  */
2611
2865
  export declare type OFSDiagnosticHandler = (scope: string, error: unknown) => void;
2612
2866
 
2867
+ export declare class OFSDrawPreview {
2868
+ private mapAdapter;
2869
+ private sourceId;
2870
+ private style;
2871
+ private current;
2872
+ /** Layer ids in draw order, bottom first */
2873
+ get layerIds(): string[];
2874
+ constructor(mapAdapter: IMapAdapter);
2875
+ private raw;
2876
+ /**
2877
+ * Create the source and layers. Called for you the first time anything is drawn, so a caller that
2878
+ * only ever uses `set` never has to think about it.
2879
+ */
2880
+ setupLayers(): void;
2881
+ /**
2882
+ * Replace what the preview shows. Safe to call on every mouse move: it writes one GeoJSON payload
2883
+ * and touches nothing else.
2884
+ *
2885
+ * Points, lines and polygons can be mixed freely. A feature may carry `color`, `opacity`, `width`,
2886
+ * `radius`, `fillOpacity`, `strokeColor` or `strokeWidth` in its properties to depart from the
2887
+ * style for itself.
2888
+ */
2889
+ set(features: Feature<Geometry, any>[] | FeatureCollection | null): void;
2890
+ /**
2891
+ * Take the preview off the map.
2892
+ *
2893
+ * This is never called for you when a drawing finishes or is cancelled. The preview belongs to the
2894
+ * caller and only the caller knows whether it has gone stale, so scaffolding tied to a drawing
2895
+ * session is cleared from a `draw:complete` and `draw:cancel` handler.
2896
+ */
2897
+ clear(): void;
2898
+ /** What the preview is currently showing */
2899
+ getFeatures(): Feature<Geometry, any>[];
2900
+ isEmpty(): boolean;
2901
+ /** Change the look of the preview. Anything left out keeps its current value. */
2902
+ setStyle(style: Partial<DrawPreviewStyle>): void;
2903
+ getStyle(): DrawPreviewStyle;
2904
+ /**
2905
+ * Lift the preview above the data layers. The drawing guides are raised after this, so the rubber
2906
+ * band and the measurements stay readable over whatever the preview is showing.
2907
+ */
2908
+ bringToFront(): void;
2909
+ /** Remove the layers and the source, for when the draw tools are torn down */
2910
+ destroy(): void;
2911
+ }
2912
+
2613
2913
  export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
2614
2914
  private dataSource;
2615
2915
  private mapAdapter;
2616
2916
  readonly snapping: SnappingEngine;
2617
2917
  readonly grips: OFSGripsEditor;
2618
2918
  readonly selectionOverlay: OFSSelectionOverlay;
2919
+ /**
2920
+ * A scratch layer for the host application to draw on while a tool is in use - a planting grid
2921
+ * following the line being drawn, the parcels a cutting line is about to divide. Nothing put here
2922
+ * reaches the data source, the undo history or an export. See `preview/draw-preview.ts`.
2923
+ */
2924
+ readonly preview: OFSDrawPreview;
2619
2925
  orthoEnabled: boolean;
2620
2926
  polarTracking: PolarTrackingSettings;
2621
2927
  /** Angle convention and UCS used for typed angles, relative coordinates, ortho and polar tracking */
@@ -2631,6 +2937,17 @@ export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
2631
2937
  private pointPick;
2632
2938
  private currentMode;
2633
2939
  private activeCoordinates;
2940
+ /**
2941
+ * Bend of each segment already placed, one shorter than `activeCoordinates`. Zero is straight.
2942
+ * See `curves/curve-feature.ts` for why a curve is one number per segment rather than a centre.
2943
+ */
2944
+ private activeBulges;
2945
+ /** Whether the segment about to be drawn is an arc (AutoCAD PLINE's Arc option) */
2946
+ private arcSegment;
2947
+ /** Fixed turn for arc segments, in radians; null means each arc leaves the last one smoothly */
2948
+ private arcSweepRadians;
2949
+ /** How far stored geometry may sag from the curves it samples */
2950
+ private sagToleranceMeters;
2634
2951
  private targetLayerId;
2635
2952
  private tempSourceId;
2636
2953
  private unsubClick;
@@ -2662,6 +2979,47 @@ export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
2662
2979
  * Choose how typed angles are interpreted: 'bearing' (0° = North, clockwise) or 'cad' (0° = East, counter-clockwise)
2663
2980
  */
2664
2981
  setAngleConvention(convention: AngleConvention): void;
2982
+ /**
2983
+ * Draw the next segment as an arc instead of a straight line, the way AutoCAD's PLINE takes its
2984
+ * Arc and Line options. Only `line` and `polygon` carry curved segments.
2985
+ *
2986
+ * With no `sweepDegrees` the arc leaves the previous segment along the direction it arrived in, so
2987
+ * the join comes out smooth and no further input is needed - one click still places one vertex.
2988
+ * That needs something to continue from, so an arc asked for as the very first segment reports
2989
+ * `ready: false` and is drawn straight until there is a segment behind it. Passing `sweepDegrees`
2990
+ * fixes the turn instead, which works from the first segment and is what a command or an agent
2991
+ * uses when the bend is a specification rather than something drawn by eye.
2992
+ */
2993
+ setArcSegment(enabled: boolean, options?: {
2994
+ sweepDegrees?: number | null;
2995
+ }): void;
2996
+ /** Swap between straight and curved segments, returning the state that is now in force */
2997
+ toggleArcSegment(): boolean;
2998
+ /** Whether the segment about to be drawn is an arc */
2999
+ isArcSegment(): boolean;
3000
+ /** Bend of each segment placed so far, one shorter than the vertex list */
3001
+ getActiveBulges(): number[];
3002
+ /**
3003
+ * How far stored geometry may stand away from the curve it samples, in meters on the ground.
3004
+ * It is recorded on each feature as it is drawn, so tightening it later does not silently change
3005
+ * the fidelity of anything already on the map.
3006
+ */
3007
+ setSagTolerance(meters: number): void;
3008
+ getSagTolerance(): number;
3009
+ /**
3010
+ * Which way the drawing is heading as it leaves the last vertex placed: along the last straight
3011
+ * segment, or along the tangent at the end of the last arc. Null before there is a segment at all.
3012
+ */
3013
+ private headingAtLastVertex;
3014
+ /** Bend of a segment running from the last vertex to `end`, under the arc settings in force */
3015
+ private bulgeForSegmentTo;
3016
+ /**
3017
+ * Length of a drawn line, measuring each arc along the arc rather than along the polyline that
3018
+ * samples it. The sampled version is always a little short, by design - it is a chord chain.
3019
+ */
3020
+ private curveLengthMeters;
3021
+ /** The line as it is being drawn, sampled through any arcs, with the live segment on the end */
3022
+ private previewCoordinates;
2665
3023
  /** Map adapter used by these tools */
2666
3024
  getMapAdapter(): IMapAdapter;
2667
3025
  /** Last known cursor position after snapping / tracking */
@@ -2812,6 +3170,20 @@ export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
2812
3170
  scaleFeature(layerId: string, featureId: string | number, factor: number, origin?: Coordinate): Feature<Geometry> | null;
2813
3171
  explodeFeature(layerId: string, featureId: string | number): Feature<LineString>[] | null;
2814
3172
  filletFeature(layerId: string, featureId: string | number, vertexIndex: number, radiusMeters?: number): Feature<Polygon | LineString> | null;
3173
+ /**
3174
+ * Bend one segment of an existing line or polygon into an arc turning through `sweepDegrees`, or
3175
+ * straighten it again with a sweep of zero. The segment is numbered from the vertices the user
3176
+ * placed, which for a feature that already holds a curve are not the same as the coordinates in
3177
+ * its geometry - see `getFeatureCurveSegments` to list them.
3178
+ */
3179
+ curveFeatureSegment(layerId: string, featureId: string | number, segmentIndex: number, sweepDegrees: number): Feature<Geometry, any> | null;
3180
+ /** Segments of a feature that can be bent, with where each one's handle sits and how deep it runs */
3181
+ getFeatureCurveSegments(layerId: string, featureId: string | number): {
3182
+ index: number;
3183
+ curved: boolean;
3184
+ coordinate: Coordinate;
3185
+ sagMeters: number;
3186
+ }[];
2815
3187
  /**
2816
3188
  * Start interactive Grips editing on a feature
2817
3189
  */
@@ -3146,6 +3518,28 @@ export declare class OFSGripsEditor extends TypedEventEmitter<GripsEditorEvents>
3146
3518
  /**
3147
3519
  * Generate grip handles for the active feature
3148
3520
  */
3521
+ /**
3522
+ * Grips come from the curve's own vertices, never from the geometry. A curved feature's geometry
3523
+ * is a sampled polyline whose points are an artefact of the drawing tolerance, so a handle on each
3524
+ * of those would bury the feature and let a drag pull one sample point off the arc it belongs to.
3525
+ */
3526
+ private curveStateOf;
3527
+ /** Write vertices and bends back to the active feature */
3528
+ private writeCurveState;
3529
+ /** Every segment of the feature being edited, for a UI offering to bend or straighten one */
3530
+ getCurveSegments(): CurveSegmentInfo[];
3531
+ /**
3532
+ * Bend one segment of the feature being edited into an arc - the action behind a "convert to arc"
3533
+ * menu entry. `sweepDegrees` says how far the arc turns; it is required rather than guessed,
3534
+ * because there is no one bend that is obviously the right one for a straight line.
3535
+ */
3536
+ convertSegmentToArc(segmentIndex: number, sweepDegrees: number): boolean;
3537
+ /** Set one segment's bend directly, as a DXF bulge factor */
3538
+ setSegmentBulge(segmentIndex: number, bulge: number): boolean;
3539
+ /** Take the bend out of one segment, leaving a straight line between its two vertices */
3540
+ straightenSegment(segmentIndex: number): boolean;
3541
+ /** Handles for a feature whose vertices live in its properties rather than in its geometry */
3542
+ private curveHandles;
3149
3543
  getHandles(): GripHandle[];
3150
3544
  private setupLayers;
3151
3545
  private renderGrips;
@@ -3158,7 +3552,14 @@ export declare class OFSGripsEditor extends TypedEventEmitter<GripsEditorEvents>
3158
3552
  private handleContextMenu;
3159
3553
  private handleMove;
3160
3554
  private applyCoordinateChange;
3555
+ /**
3556
+ * Drag handling for a feature that carries a curve. A bend handle reshapes its arc so the curve
3557
+ * follows the pointer; a vertex handle moves the point itself and the arcs on either side bend to
3558
+ * keep up, because a bulge is defined against its own two ends rather than against a fixed centre.
3559
+ */
3560
+ private applyCurveChange;
3161
3561
  private insertVertexAtMidpoint;
3562
+ private insertVertexIntoGeometry;
3162
3563
  /**
3163
3564
  * Delete selected vertex
3164
3565
  */
@@ -3876,6 +4277,15 @@ export declare function parseXml(source: string): XmlElement;
3876
4277
  /** Boundary paths: polygon rings or line parts */
3877
4278
  export declare function pathsOf(geometry: Geometry): Position[][];
3878
4279
 
4280
+ export declare interface PlanarArc {
4281
+ center: PlanarPoint;
4282
+ radius: number;
4283
+ /** Direction of the start point from the centre, in radians (atan2 convention) */
4284
+ startAngle: number;
4285
+ /** Signed turn from start to end, in radians. Positive turns counter-clockwise; never zero, never beyond a full turn. */
4286
+ sweep: number;
4287
+ }
4288
+
3879
4289
  export declare type PlanarPoint = [number, number];
3880
4290
 
3881
4291
  export declare class PlanarProjection {
@@ -3936,6 +4346,9 @@ export declare function printImportResult(consoleService: IOFSConsole, result: I
3936
4346
  /** Print an import / export report: summary lines, then issues grouped by code */
3937
4347
  export declare function printIssues(consoleService: IOFSConsole, issues: ImportIssue[], limit?: number): void;
3938
4348
 
4349
+ /** Plane to do the arc maths on, centred on the curve so distances come out in ground meters */
4350
+ export declare function projectionForVertices(vertices: Coordinate[]): PlanarProjection;
4351
+
3939
4352
  export declare class PromptBuilder {
3940
4353
  /**
3941
4354
  * Summarize current map state for LLM prompt context.
@@ -3969,6 +4382,9 @@ export declare function rasterizeMarker(request: Extract<SymbolImageRequest, {
3969
4382
  */
3970
4383
  export declare function readCoordinate(value: unknown): Coordinate;
3971
4384
 
4385
+ /** Read and validate a curve definition off a feature. Returns null when there is none, or it is malformed. */
4386
+ export declare function readCurve(feature: Feature<Geometry, any> | null | undefined): OfsCurveMeta | null;
4387
+
3972
4388
  export declare function readDbf(buffer: Uint8Array, options?: {
3973
4389
  encoding?: string;
3974
4390
  cpg?: string;
@@ -4106,6 +4522,9 @@ export declare function restoreCollinearVertices<G extends Polygon | MultiPolygo
4106
4522
  restored: number;
4107
4523
  };
4108
4524
 
4525
+ /** The same arc travelled the other way: ends swap and the sweep changes sign */
4526
+ export declare function reverseArc(arc: PlanarArc): PlanarArc;
4527
+
4109
4528
  /** Planar area of one ring in m² (absolute) */
4110
4529
  export declare function ringArea(ring: Position[], projection: PlanarProjection): number;
4111
4530
 
@@ -4150,6 +4569,12 @@ declare type Schema = NonNullable<OFSCommandProcess['schema']>;
4150
4569
  /** Property names of a schema, including none when the command has no schema */
4151
4570
  export declare function schemaProperties(schema: Schema | undefined): string[];
4152
4571
 
4572
+ /**
4573
+ * The arc of one segment, or null when that segment is straight.
4574
+ * `index` counts segments, so segment 0 runs from vertex 0 to vertex 1.
4575
+ */
4576
+ export declare function segmentArc(meta: OfsCurveMeta, index: number, projection?: PlanarProjection): PlanarArc | null;
4577
+
4153
4578
  export declare interface SegmentHit {
4154
4579
  distance: number;
4155
4580
  /** Parameter along the segment, 0..1 */
@@ -4166,6 +4591,12 @@ export declare interface SegmentMeasure {
4166
4591
  angleDeg: number;
4167
4592
  }
4168
4593
 
4594
+ /**
4595
+ * How far a bulged segment stands off its own chord at the deepest point, in meters. Exactly half
4596
+ * the chord times the bulge, which falls out of the definitions of the two.
4597
+ */
4598
+ export declare function segmentSagitta(chordLengthMeters: number, bulge: number): number;
4599
+
4169
4600
  export declare interface SelectedFeatureRef {
4170
4601
  layerId: string;
4171
4602
  feature: Feature<Geometry>;
@@ -4238,11 +4669,30 @@ export declare class SnappingEngine {
4238
4669
  * @param additionalCoords Optional extra coordinates to snap to (e.g. start vertex of in-progress polygon)
4239
4670
  * @param options.excludeFeatureId Feature to ignore (e.g. the feature being edited)
4240
4671
  * @param options.fromPoint Previous point, enables perpendicular snapping
4672
+ * @param options.connectAdditional Treat `additionalCoords` as a connected run, so the segments
4673
+ * between them can be snapped to as well. True while drawing, where those segments are really
4674
+ * there. False when the points are just a list, such as the vertices of a curve, where the
4675
+ * straight lines between them are not part of any shape on the map.
4241
4676
  */
4242
4677
  getSnap(cursorCoord: Coordinate, additionalCoords?: Coordinate[], options?: {
4243
4678
  excludeFeatureId?: string | number | null;
4244
4679
  fromPoint?: Coordinate | null;
4680
+ connectAdditional?: boolean;
4245
4681
  }): SnapResult | null;
4682
+ /**
4683
+ * Snap targets of a feature that carries a curve.
4684
+ *
4685
+ * Its geometry is a polyline sampled off the curve, so every point in it except the placed
4686
+ * vertices is an artefact of the drawing tolerance. Snapping to one of those drops a point where
4687
+ * the user never drew anything, and it is worse than useless while dragging an arc's own bend
4688
+ * handle: the handle lands on a sample point of the arc it is supposed to be shaping, and the
4689
+ * curve collapses onto its chord.
4690
+ *
4691
+ * So the arcs are snapped to as arcs. A vertex is one the user placed; the nearest point on an
4692
+ * arc is found on the arc itself; and an arc offers its centre, which is the one point a circular
4693
+ * feature is most often measured from.
4694
+ */
4695
+ private considerCurve;
4246
4696
  /**
4247
4697
  * Proper crossing point of two segments (shared endpoints are vertices, not intersections)
4248
4698
  */
@@ -4504,6 +4954,21 @@ export declare interface TableResult {
4504
4954
  filtered: number;
4505
4955
  }
4506
4956
 
4957
+ /**
4958
+ * Sample an arc into the polyline that actually gets stored, including both end points.
4959
+ *
4960
+ * The step count comes from the sag tolerance: for a chord spanning angle `d` on radius `r` the
4961
+ * middle of the arc stands `r * (1 - cos(d / 2))` away from it, so the angle that keeps that under
4962
+ * the tolerance is `2 * acos(1 - tolerance / r)`.
4963
+ */
4964
+ export declare function tessellateArc(arc: PlanarArc, sagToleranceMeters?: number, maxPoints?: number): PlanarPoint[];
4965
+
4966
+ /**
4967
+ * Sample a curve into the coordinate list that gets stored in the geometry. Straight segments
4968
+ * contribute their end vertex alone; curved ones contribute the points of the arc between.
4969
+ */
4970
+ export declare function tessellateCurve(meta: OfsCurveMeta, projection?: PlanarProjection): Coordinate[];
4971
+
4507
4972
  /** Text content including descendants */
4508
4973
  export declare function textContent(el: XmlElement | undefined): string;
4509
4974