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

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,14 @@ export declare const DEFAULT_MAX_NETWORK_POINTS = 5000;
897
1071
 
898
1072
  export declare const DEFAULT_POLAR_TRACKING: PolarTrackingSettings;
899
1073
 
1074
+ /**
1075
+ * How far the stored polyline may sag away from the true arc, in meters. Five centimeters is below
1076
+ * the positional accuracy of the survey data these plans are drawn over, and is deliberately a
1077
+ * distance on the ground rather than a step count: a fixed number of steps makes a 2 m arc far more
1078
+ * detailed than it needs to be and a 2 km arc visibly faceted.
1079
+ */
1080
+ export declare const DEFAULT_SAG_TOLERANCE_METERS = 0.05;
1081
+
900
1082
  export declare const DEFAULT_SYMBOL_COLOR = "#3b82f6";
901
1083
 
902
1084
  export declare const DEFAULT_TOPOLOGY_OPTIONS: ResolvedTopologyOptions;
@@ -980,6 +1162,14 @@ export declare interface DrawEvents {
980
1162
  mode: DrawMode;
981
1163
  coordinates: Coordinate[];
982
1164
  };
1165
+ 'draw:arc-segment': {
1166
+ /** Whether the next segment will be drawn as an arc */
1167
+ enabled: boolean;
1168
+ /** Fixed turn in degrees, or null when the arc follows on smoothly from the last segment */
1169
+ sweepDegrees: number | null;
1170
+ /** False when an arc was asked for but there is nothing yet for it to continue from */
1171
+ ready: boolean;
1172
+ };
983
1173
  'draw:tracking-points': {
984
1174
  points: Coordinate[];
985
1175
  };
@@ -1417,7 +1607,11 @@ export declare class GridTooLargeError extends OFSError {
1417
1607
  export declare interface GripHandle {
1418
1608
  id: string;
1419
1609
  coordinate: Coordinate;
1420
- type: 'vertex' | 'midpoint' | 'center';
1610
+ /**
1611
+ * `curve` is the bend handle of an arc segment: dragging it reshapes the arc rather than moving a
1612
+ * point, which is why it is not a `midpoint` - dragging one of those inserts a vertex.
1613
+ */
1614
+ type: 'vertex' | 'midpoint' | 'center' | 'curve';
1421
1615
  index: number;
1422
1616
  ringIndex: number;
1423
1617
  partIndex?: number;
@@ -1442,6 +1636,9 @@ export declare interface GripsEditorEvents {
1442
1636
  'grip:clear': void;
1443
1637
  }
1444
1638
 
1639
+ /** A curve with no bend worth drawing is just a polyline and needs no metadata */
1640
+ export declare function hasCurvedSegments(meta: OfsCurveMeta, projection?: PlanarProjection): boolean;
1641
+
1445
1642
  /** Stable short hash for image ids */
1446
1643
  export declare function hashString(text: string): string;
1447
1644
 
@@ -1681,6 +1878,20 @@ export declare function isInlineSvg(source: string): boolean;
1681
1878
 
1682
1879
  export declare function isInternalProperty(name: string): boolean;
1683
1880
 
1881
+ /**
1882
+ * Whether one segment is bent enough to be worth treating as an arc.
1883
+ *
1884
+ * The test is the sag the bend actually produces on the ground, not the size of the bulge number: a
1885
+ * bulge is a shape, so the same value is a sharp bend across a 5 m segment and an invisible one
1886
+ * across 5 km. Judging it by the same tolerance the geometry is sampled at means a segment counts as
1887
+ * curved exactly when drawing it as an arc would put the line somewhere a straight one would not.
1888
+ *
1889
+ * Two points at the same latitude are the case that makes this matter: a parallel is not a straight
1890
+ * line on a Transverse Mercator plane, so dragging a grip back onto the apparent line leaves a
1891
+ * millimetre of real bend behind. Without this the segment would never straighten again.
1892
+ */
1893
+ export declare function isSegmentCurved(meta: OfsCurveMeta, index: number, projection?: PlanarProjection): boolean;
1894
+
1684
1895
  export declare function isTruthy(value: ExpressionValue): boolean;
1685
1896
 
1686
1897
  /**
@@ -1935,6 +2146,12 @@ export declare type LocationPredicate = 'intersects' | 'within' | 'contains' | '
1935
2146
  /** True when every coordinate is a plausible lng/lat */
1936
2147
  export declare function looksGeographic(bounds: [number, number, number, number]): boolean;
1937
2148
 
2149
+ /**
2150
+ * Build a curve definition from the vertices a user placed and the bend of each segment.
2151
+ * Returns null when the run is straight throughout, so a plain polyline carries no extra properties.
2152
+ */
2153
+ export declare function makeCurve(vertices: Coordinate[], bulges: number[], sagToleranceMeters?: number): OfsCurveMeta | null;
2154
+
1938
2155
  export declare class MapboxAdapter extends BaseGLAdapter {
1939
2156
  readonly engineName: "mapbox";
1940
2157
  init(container: HTMLElement | string, options?: Record<string, any>): Promise<void>;
@@ -2046,6 +2263,9 @@ export declare interface MarkerSymbol {
2046
2263
  allowOverlap?: boolean;
2047
2264
  }
2048
2265
 
2266
+ /** Never emit more than this many points for one arc, whatever the tolerance asks for */
2267
+ export declare const MAX_ARC_POINTS = 2048;
2268
+
2049
2269
  /** Distance (m) and user angle from `from` to `to` */
2050
2270
  export declare function measure(from: Coordinate, to: Coordinate, frame?: InputFrame): {
2051
2271
  distance: number;
@@ -2352,6 +2572,23 @@ export declare class OFSCommandRegistry {
2352
2572
  suggest(prefix: string): string[];
2353
2573
  }
2354
2574
 
2575
+ export declare interface OfsCurveMeta {
2576
+ version: 1;
2577
+ /** The points the user actually placed, in order. The geometry samples the curve between them. */
2578
+ vertices: Coordinate[];
2579
+ /**
2580
+ * One entry per segment, so always one shorter than `vertices`. Zero is a straight segment;
2581
+ * otherwise the tangent of a quarter of the arc's sweep, positive counter-clockwise.
2582
+ */
2583
+ bulges: number[];
2584
+ /**
2585
+ * How far the sampled geometry may stand away from the true curve, in meters on the ground. Kept
2586
+ * with the feature so that re-sampling after an edit reproduces the fidelity it was drawn at,
2587
+ * rather than silently changing it.
2588
+ */
2589
+ sagToleranceMeters: number;
2590
+ }
2591
+
2355
2592
  export declare class OFSDataSource extends TypedEventEmitter<OFSDataSourceEvents> {
2356
2593
  private layers;
2357
2594
  private groups;
@@ -2631,6 +2868,17 @@ export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
2631
2868
  private pointPick;
2632
2869
  private currentMode;
2633
2870
  private activeCoordinates;
2871
+ /**
2872
+ * Bend of each segment already placed, one shorter than `activeCoordinates`. Zero is straight.
2873
+ * See `curves/curve-feature.ts` for why a curve is one number per segment rather than a centre.
2874
+ */
2875
+ private activeBulges;
2876
+ /** Whether the segment about to be drawn is an arc (AutoCAD PLINE's Arc option) */
2877
+ private arcSegment;
2878
+ /** Fixed turn for arc segments, in radians; null means each arc leaves the last one smoothly */
2879
+ private arcSweepRadians;
2880
+ /** How far stored geometry may sag from the curves it samples */
2881
+ private sagToleranceMeters;
2634
2882
  private targetLayerId;
2635
2883
  private tempSourceId;
2636
2884
  private unsubClick;
@@ -2662,6 +2910,47 @@ export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
2662
2910
  * Choose how typed angles are interpreted: 'bearing' (0° = North, clockwise) or 'cad' (0° = East, counter-clockwise)
2663
2911
  */
2664
2912
  setAngleConvention(convention: AngleConvention): void;
2913
+ /**
2914
+ * Draw the next segment as an arc instead of a straight line, the way AutoCAD's PLINE takes its
2915
+ * Arc and Line options. Only `line` and `polygon` carry curved segments.
2916
+ *
2917
+ * With no `sweepDegrees` the arc leaves the previous segment along the direction it arrived in, so
2918
+ * the join comes out smooth and no further input is needed - one click still places one vertex.
2919
+ * That needs something to continue from, so an arc asked for as the very first segment reports
2920
+ * `ready: false` and is drawn straight until there is a segment behind it. Passing `sweepDegrees`
2921
+ * fixes the turn instead, which works from the first segment and is what a command or an agent
2922
+ * uses when the bend is a specification rather than something drawn by eye.
2923
+ */
2924
+ setArcSegment(enabled: boolean, options?: {
2925
+ sweepDegrees?: number | null;
2926
+ }): void;
2927
+ /** Swap between straight and curved segments, returning the state that is now in force */
2928
+ toggleArcSegment(): boolean;
2929
+ /** Whether the segment about to be drawn is an arc */
2930
+ isArcSegment(): boolean;
2931
+ /** Bend of each segment placed so far, one shorter than the vertex list */
2932
+ getActiveBulges(): number[];
2933
+ /**
2934
+ * How far stored geometry may stand away from the curve it samples, in meters on the ground.
2935
+ * It is recorded on each feature as it is drawn, so tightening it later does not silently change
2936
+ * the fidelity of anything already on the map.
2937
+ */
2938
+ setSagTolerance(meters: number): void;
2939
+ getSagTolerance(): number;
2940
+ /**
2941
+ * Which way the drawing is heading as it leaves the last vertex placed: along the last straight
2942
+ * segment, or along the tangent at the end of the last arc. Null before there is a segment at all.
2943
+ */
2944
+ private headingAtLastVertex;
2945
+ /** Bend of a segment running from the last vertex to `end`, under the arc settings in force */
2946
+ private bulgeForSegmentTo;
2947
+ /**
2948
+ * Length of a drawn line, measuring each arc along the arc rather than along the polyline that
2949
+ * samples it. The sampled version is always a little short, by design - it is a chord chain.
2950
+ */
2951
+ private curveLengthMeters;
2952
+ /** The line as it is being drawn, sampled through any arcs, with the live segment on the end */
2953
+ private previewCoordinates;
2665
2954
  /** Map adapter used by these tools */
2666
2955
  getMapAdapter(): IMapAdapter;
2667
2956
  /** Last known cursor position after snapping / tracking */
@@ -2812,6 +3101,20 @@ export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
2812
3101
  scaleFeature(layerId: string, featureId: string | number, factor: number, origin?: Coordinate): Feature<Geometry> | null;
2813
3102
  explodeFeature(layerId: string, featureId: string | number): Feature<LineString>[] | null;
2814
3103
  filletFeature(layerId: string, featureId: string | number, vertexIndex: number, radiusMeters?: number): Feature<Polygon | LineString> | null;
3104
+ /**
3105
+ * Bend one segment of an existing line or polygon into an arc turning through `sweepDegrees`, or
3106
+ * straighten it again with a sweep of zero. The segment is numbered from the vertices the user
3107
+ * placed, which for a feature that already holds a curve are not the same as the coordinates in
3108
+ * its geometry - see `getFeatureCurveSegments` to list them.
3109
+ */
3110
+ curveFeatureSegment(layerId: string, featureId: string | number, segmentIndex: number, sweepDegrees: number): Feature<Geometry, any> | null;
3111
+ /** Segments of a feature that can be bent, with where each one's handle sits and how deep it runs */
3112
+ getFeatureCurveSegments(layerId: string, featureId: string | number): {
3113
+ index: number;
3114
+ curved: boolean;
3115
+ coordinate: Coordinate;
3116
+ sagMeters: number;
3117
+ }[];
2815
3118
  /**
2816
3119
  * Start interactive Grips editing on a feature
2817
3120
  */
@@ -3146,6 +3449,28 @@ export declare class OFSGripsEditor extends TypedEventEmitter<GripsEditorEvents>
3146
3449
  /**
3147
3450
  * Generate grip handles for the active feature
3148
3451
  */
3452
+ /**
3453
+ * Grips come from the curve's own vertices, never from the geometry. A curved feature's geometry
3454
+ * is a sampled polyline whose points are an artefact of the drawing tolerance, so a handle on each
3455
+ * of those would bury the feature and let a drag pull one sample point off the arc it belongs to.
3456
+ */
3457
+ private curveStateOf;
3458
+ /** Write vertices and bends back to the active feature */
3459
+ private writeCurveState;
3460
+ /** Every segment of the feature being edited, for a UI offering to bend or straighten one */
3461
+ getCurveSegments(): CurveSegmentInfo[];
3462
+ /**
3463
+ * Bend one segment of the feature being edited into an arc - the action behind a "convert to arc"
3464
+ * menu entry. `sweepDegrees` says how far the arc turns; it is required rather than guessed,
3465
+ * because there is no one bend that is obviously the right one for a straight line.
3466
+ */
3467
+ convertSegmentToArc(segmentIndex: number, sweepDegrees: number): boolean;
3468
+ /** Set one segment's bend directly, as a DXF bulge factor */
3469
+ setSegmentBulge(segmentIndex: number, bulge: number): boolean;
3470
+ /** Take the bend out of one segment, leaving a straight line between its two vertices */
3471
+ straightenSegment(segmentIndex: number): boolean;
3472
+ /** Handles for a feature whose vertices live in its properties rather than in its geometry */
3473
+ private curveHandles;
3149
3474
  getHandles(): GripHandle[];
3150
3475
  private setupLayers;
3151
3476
  private renderGrips;
@@ -3158,7 +3483,14 @@ export declare class OFSGripsEditor extends TypedEventEmitter<GripsEditorEvents>
3158
3483
  private handleContextMenu;
3159
3484
  private handleMove;
3160
3485
  private applyCoordinateChange;
3486
+ /**
3487
+ * Drag handling for a feature that carries a curve. A bend handle reshapes its arc so the curve
3488
+ * follows the pointer; a vertex handle moves the point itself and the arcs on either side bend to
3489
+ * keep up, because a bulge is defined against its own two ends rather than against a fixed centre.
3490
+ */
3491
+ private applyCurveChange;
3161
3492
  private insertVertexAtMidpoint;
3493
+ private insertVertexIntoGeometry;
3162
3494
  /**
3163
3495
  * Delete selected vertex
3164
3496
  */
@@ -3876,6 +4208,15 @@ export declare function parseXml(source: string): XmlElement;
3876
4208
  /** Boundary paths: polygon rings or line parts */
3877
4209
  export declare function pathsOf(geometry: Geometry): Position[][];
3878
4210
 
4211
+ export declare interface PlanarArc {
4212
+ center: PlanarPoint;
4213
+ radius: number;
4214
+ /** Direction of the start point from the centre, in radians (atan2 convention) */
4215
+ startAngle: number;
4216
+ /** Signed turn from start to end, in radians. Positive turns counter-clockwise; never zero, never beyond a full turn. */
4217
+ sweep: number;
4218
+ }
4219
+
3879
4220
  export declare type PlanarPoint = [number, number];
3880
4221
 
3881
4222
  export declare class PlanarProjection {
@@ -3936,6 +4277,9 @@ export declare function printImportResult(consoleService: IOFSConsole, result: I
3936
4277
  /** Print an import / export report: summary lines, then issues grouped by code */
3937
4278
  export declare function printIssues(consoleService: IOFSConsole, issues: ImportIssue[], limit?: number): void;
3938
4279
 
4280
+ /** Plane to do the arc maths on, centred on the curve so distances come out in ground meters */
4281
+ export declare function projectionForVertices(vertices: Coordinate[]): PlanarProjection;
4282
+
3939
4283
  export declare class PromptBuilder {
3940
4284
  /**
3941
4285
  * Summarize current map state for LLM prompt context.
@@ -3969,6 +4313,9 @@ export declare function rasterizeMarker(request: Extract<SymbolImageRequest, {
3969
4313
  */
3970
4314
  export declare function readCoordinate(value: unknown): Coordinate;
3971
4315
 
4316
+ /** Read and validate a curve definition off a feature. Returns null when there is none, or it is malformed. */
4317
+ export declare function readCurve(feature: Feature<Geometry, any> | null | undefined): OfsCurveMeta | null;
4318
+
3972
4319
  export declare function readDbf(buffer: Uint8Array, options?: {
3973
4320
  encoding?: string;
3974
4321
  cpg?: string;
@@ -4106,6 +4453,9 @@ export declare function restoreCollinearVertices<G extends Polygon | MultiPolygo
4106
4453
  restored: number;
4107
4454
  };
4108
4455
 
4456
+ /** The same arc travelled the other way: ends swap and the sweep changes sign */
4457
+ export declare function reverseArc(arc: PlanarArc): PlanarArc;
4458
+
4109
4459
  /** Planar area of one ring in m² (absolute) */
4110
4460
  export declare function ringArea(ring: Position[], projection: PlanarProjection): number;
4111
4461
 
@@ -4150,6 +4500,12 @@ declare type Schema = NonNullable<OFSCommandProcess['schema']>;
4150
4500
  /** Property names of a schema, including none when the command has no schema */
4151
4501
  export declare function schemaProperties(schema: Schema | undefined): string[];
4152
4502
 
4503
+ /**
4504
+ * The arc of one segment, or null when that segment is straight.
4505
+ * `index` counts segments, so segment 0 runs from vertex 0 to vertex 1.
4506
+ */
4507
+ export declare function segmentArc(meta: OfsCurveMeta, index: number, projection?: PlanarProjection): PlanarArc | null;
4508
+
4153
4509
  export declare interface SegmentHit {
4154
4510
  distance: number;
4155
4511
  /** Parameter along the segment, 0..1 */
@@ -4166,6 +4522,12 @@ export declare interface SegmentMeasure {
4166
4522
  angleDeg: number;
4167
4523
  }
4168
4524
 
4525
+ /**
4526
+ * How far a bulged segment stands off its own chord at the deepest point, in meters. Exactly half
4527
+ * the chord times the bulge, which falls out of the definitions of the two.
4528
+ */
4529
+ export declare function segmentSagitta(chordLengthMeters: number, bulge: number): number;
4530
+
4169
4531
  export declare interface SelectedFeatureRef {
4170
4532
  layerId: string;
4171
4533
  feature: Feature<Geometry>;
@@ -4238,11 +4600,30 @@ export declare class SnappingEngine {
4238
4600
  * @param additionalCoords Optional extra coordinates to snap to (e.g. start vertex of in-progress polygon)
4239
4601
  * @param options.excludeFeatureId Feature to ignore (e.g. the feature being edited)
4240
4602
  * @param options.fromPoint Previous point, enables perpendicular snapping
4603
+ * @param options.connectAdditional Treat `additionalCoords` as a connected run, so the segments
4604
+ * between them can be snapped to as well. True while drawing, where those segments are really
4605
+ * there. False when the points are just a list, such as the vertices of a curve, where the
4606
+ * straight lines between them are not part of any shape on the map.
4241
4607
  */
4242
4608
  getSnap(cursorCoord: Coordinate, additionalCoords?: Coordinate[], options?: {
4243
4609
  excludeFeatureId?: string | number | null;
4244
4610
  fromPoint?: Coordinate | null;
4611
+ connectAdditional?: boolean;
4245
4612
  }): SnapResult | null;
4613
+ /**
4614
+ * Snap targets of a feature that carries a curve.
4615
+ *
4616
+ * Its geometry is a polyline sampled off the curve, so every point in it except the placed
4617
+ * vertices is an artefact of the drawing tolerance. Snapping to one of those drops a point where
4618
+ * the user never drew anything, and it is worse than useless while dragging an arc's own bend
4619
+ * handle: the handle lands on a sample point of the arc it is supposed to be shaping, and the
4620
+ * curve collapses onto its chord.
4621
+ *
4622
+ * So the arcs are snapped to as arcs. A vertex is one the user placed; the nearest point on an
4623
+ * arc is found on the arc itself; and an arc offers its centre, which is the one point a circular
4624
+ * feature is most often measured from.
4625
+ */
4626
+ private considerCurve;
4246
4627
  /**
4247
4628
  * Proper crossing point of two segments (shared endpoints are vertices, not intersections)
4248
4629
  */
@@ -4504,6 +4885,21 @@ export declare interface TableResult {
4504
4885
  filtered: number;
4505
4886
  }
4506
4887
 
4888
+ /**
4889
+ * Sample an arc into the polyline that actually gets stored, including both end points.
4890
+ *
4891
+ * The step count comes from the sag tolerance: for a chord spanning angle `d` on radius `r` the
4892
+ * middle of the arc stands `r * (1 - cos(d / 2))` away from it, so the angle that keeps that under
4893
+ * the tolerance is `2 * acos(1 - tolerance / r)`.
4894
+ */
4895
+ export declare function tessellateArc(arc: PlanarArc, sagToleranceMeters?: number, maxPoints?: number): PlanarPoint[];
4896
+
4897
+ /**
4898
+ * Sample a curve into the coordinate list that gets stored in the geometry. Straight segments
4899
+ * contribute their end vertex alone; curved ones contribute the points of the arc between.
4900
+ */
4901
+ export declare function tessellateCurve(meta: OfsCurveMeta, projection?: PlanarProjection): Coordinate[];
4902
+
4507
4903
  /** Text content including descendants */
4508
4904
  export declare function textContent(el: XmlElement | undefined): string;
4509
4905