ofs-object 0.1.3 → 0.1.5

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
@@ -317,9 +317,22 @@ export declare abstract class BaseGLAdapter implements IMapAdapter {
317
317
  * whole source has to be resent (every feature's label text changes at once).
318
318
  */
319
319
  protected syncLabelSource(layer: OFSLayerData): boolean;
320
+ /** Extra options for a layer's new GeoJSON source; an engine that needs some overrides this */
321
+ protected sourceOptionsFor(_layer: OFSLayerData): Record<string, unknown>;
322
+ /**
323
+ * Whether changes to this layer's source can be sent as a diff instead of the whole collection.
324
+ * The default is MapLibre's, whose `updateData` takes a diff on any GeoJSON source. Mapbox's
325
+ * takes something else and only on some sources, so MapboxAdapter overrides this and the next.
326
+ */
327
+ protected supportsPartialUpdate(_layer: OFSLayerData, source: any): boolean;
328
+ /** Send a diff worked out by `updateSourceData`, in the form the engine takes it */
329
+ protected sendPartialUpdate(source: any, diff: {
330
+ add: Feature<Geometry, any>[];
331
+ remove: (string | number)[];
332
+ }): void;
320
333
  /**
321
- * Send changes of a layer to its map source. Uses GeoJSONSource.updateData (MapLibre) with only
322
- * the added / changed / removed features when possible, otherwise falls back to setData.
334
+ * Send changes of a layer to its map source: only the added / changed / removed features when the
335
+ * engine can take a diff (see `supportsPartialUpdate`), otherwise the whole collection via setData.
323
336
  * Changed features are detected by object identity: OFSLayerData replaces the stored object on
324
337
  * every add or update.
325
338
  */
@@ -351,6 +364,9 @@ export declare abstract class BaseGLAdapter implements IMapAdapter {
351
364
  */
352
365
  protected listen(event: string, handler: (e: any) => void): void;
353
366
  protected setupMapEventListeners(): void;
367
+ /** undefined = not worked out yet for the current style */
368
+ private styleFontCache;
369
+ getStyleFont(): string[] | null;
354
370
  getRawMap<T = any>(): T;
355
371
  /**
356
372
  * Take every source and layer this adapter added off a map it does not own, so the host is
@@ -393,27 +409,18 @@ export declare function bulgePoints(p1: Position, p2: Position, bulge: number, m
393
409
  export declare function bulgeThroughPoint(start: Coordinate, through: Coordinate, end: Coordinate, projection?: PlanarProjection): number;
394
410
 
395
411
  export declare class CADOperations {
396
- /**
397
- * Split a Polygon using a LineString.
398
- * Creates a hairline buffer "blade" to perform difference, then flattens into pieces.
399
- */
400
- /**
401
- * Extend a ray from startPt through endPt until safely outside the polygon's bounding box.
402
- * Uses Cartesian unit vector in coordinate space to avoid spherical bearing drift.
403
- */
404
- private static extendRayOutside;
405
- /**
406
- * Prepare cutting line: ensures head and tail pierce outside the polygon if they stop inside.
407
- * The cutting line is ALWAYS kept as a single continuous LineString (never decomposed into separate rays).
408
- */
409
- private static prepareCuttingLine;
410
412
  /**
411
413
  * Split a single Polygon by a single LineString using planar graph noded polygonization.
412
414
  */
413
415
  private static splitSinglePolygonByLine;
414
416
  /**
415
- * Split a Polygon using a LineString.
416
- * Ensures cutting lines pierce completely through boundaries without leaving unsevered edges or dead-end slits.
417
+ * Split a Polygon using a LineString. Returns null when the line does not cut it.
418
+ *
419
+ * The line has to cross the polygon from one side to the other, as in QGIS. An end that stops
420
+ * inside used to be carried on in a straight line - 1.5 times the polygon's diagonal, worked out in
421
+ * degrees - so a line that only poked in still cut, along a path nobody drew. Now it does not cut.
422
+ * An end on the boundary is fine, snapped or computed onto an edge: that is a crossing, and the
423
+ * noding below finds it without any extension.
417
424
  */
418
425
  static splitPolygonByLine(polygon: Feature<Polygon | MultiPolygon>, cuttingLine: Feature<LineString>): Feature<Polygon>[] | null;
419
426
  /**
@@ -782,6 +789,16 @@ export declare function colorRampGradientCss(name: string, reverse?: boolean): s
782
789
  */
783
790
  export declare type ColorRampType = 'sequential' | 'diverging' | 'qualitative';
784
791
 
792
+ /**
793
+ * The colours a theme actually puts on the map, for a layer of the given geometry.
794
+ *
795
+ * A simple theme is one colour, but which one depends on the geometry: the fill of a polygon (or
796
+ * its outline when it is drawn unfilled), the stroke of a line, the marker of a point. A
797
+ * categorised or graduated theme puts every class colour it shows on the map. (New layers do not
798
+ * avoid those: see `OFSThematicEngine.defaultColorFor`.)
799
+ */
800
+ export declare function colorsOfTheme(theme: ThematicDefinition, geometry: LayerGeometryType): string[];
801
+
785
802
  export declare interface CommandArgSpec {
786
803
  /** Schema properties filled from the words before any --option, in order */
787
804
  positional?: string[];
@@ -1120,6 +1137,9 @@ export declare interface DistanceMeasure {
1120
1137
  closingMeters: number;
1121
1138
  }
1122
1139
 
1140
+ /** Below this CIEDE2000 distance two map colours are easy to confuse at a glance */
1141
+ export declare const DISTINCT_DELTA_E = 20;
1142
+
1123
1143
  /** Save an exported image in the browser */
1124
1144
  export declare function downloadMapImage(image: MapImage): void;
1125
1145
 
@@ -1222,8 +1242,11 @@ export declare interface DrawGuideSettings {
1222
1242
  angles: boolean;
1223
1243
  /** How far off the segment the dimension line sits, in screen pixels */
1224
1244
  dimensionOffsetPixels: number;
1225
- /** Glyph names for the labels; the map style must provide them */
1226
- font: string[];
1245
+ /**
1246
+ * Glyph font stack for the labels; the map style's glyph server must have it. null (the default)
1247
+ * uses the font the basemap's own labels use - see `IMapAdapter.getStyleFont`.
1248
+ */
1249
+ font: string[] | null;
1227
1250
  }
1228
1251
 
1229
1252
  /** Minimal 2D context surface used by the drawing helpers (so they can be tested without a DOM) */
@@ -1754,6 +1777,12 @@ export declare interface IMapAdapter {
1754
1777
  setDoubleClickZoom(enabled: boolean): void;
1755
1778
  /** Raw map engine access */
1756
1779
  getRawMap<T = any>(): T;
1780
+ /**
1781
+ * The glyph font stack the basemap's own labels use, so text added on top is drawn in a font its
1782
+ * glyph server has. Null when the style names none. Optional: an adapter without it gets the
1783
+ * engine default font.
1784
+ */
1785
+ getStyleFont?(): string[] | null;
1757
1786
  /** Cleanup */
1758
1787
  destroy(): void;
1759
1788
  }
@@ -1998,6 +2027,21 @@ export declare interface LandAreaMeasure {
1998
2027
  raiNganWa: string;
1999
2028
  }
2000
2029
 
2030
+ /**
2031
+ * Chosen by greedy max-min selection in CIEDE2000 over the Tailwind 400-800 chromatic families,
2032
+ * starting from a blue. Every pair is at least 20 apart and every colour is at least 22 from the
2033
+ * reserved colours below.
2034
+ *
2035
+ * The blue is `#2563eb`, not the `#3b82f6` every layer used to get: that one is only 16.8 from the
2036
+ * slate grey used for "all other values", which is too close to tell apart in a legend.
2037
+ *
2038
+ * It stops at 11 because that is where the space runs out: the twelfth pick is already under 20
2039
+ * from its nearest neighbour, and every pick after it is closer still. Past this many layers the
2040
+ * picker generates colours instead (see `pickLayerColor`), and they are as far apart as the space
2041
+ * allows rather than clearly distinct.
2042
+ */
2043
+ export declare const LAYER_COLOR_PALETTE: readonly string[];
2044
+
2001
2045
  export declare type LayerGeometryType = 'point' | 'line' | 'polygon' | 'circle' | 'symbol';
2002
2046
 
2003
2047
  /** Legend group of layers; groups can be nested */
@@ -2030,6 +2074,11 @@ export declare interface LayerMetadata {
2030
2074
  sourceFile?: string;
2031
2075
  /** Attribute schema in source order (kept for export; edited through OFSAttributeTools) */
2032
2076
  fields?: FieldSchema[];
2077
+ /**
2078
+ * Colour to draw the layer with (any CSS colour). Left out, the layer gets one chosen to stand
2079
+ * apart from the layers already on the map - see `pickLayerColor`.
2080
+ */
2081
+ color?: string;
2033
2082
  [key: string]: unknown;
2034
2083
  }
2035
2084
 
@@ -2215,8 +2264,60 @@ export declare function looksGeographic(bounds: [number, number, number, number]
2215
2264
  */
2216
2265
  export declare function makeCurve(vertices: Coordinate[], bulges: number[], sagToleranceMeters?: number): OfsCurveMeta | null;
2217
2266
 
2267
+ /**
2268
+ * Largest layer given a dynamic source by default. A judgement call rather than a measurement - see
2269
+ * the class comment - and adjustable per adapter through `dynamicSourceMaxFeatures`.
2270
+ */
2271
+ export declare const MAPBOX_DYNAMIC_MAX_FEATURES = 5000;
2272
+
2273
+ /**
2274
+ * Mapbox GL adapter.
2275
+ *
2276
+ * ## Sending only what changed
2277
+ *
2278
+ * MapLibre's `updateData` takes a diff on any GeoJSON source. Mapbox's is a different thing: it
2279
+ * takes a FeatureCollection to merge in by id (a feature with a null geometry deletes), and only on a
2280
+ * source created with `dynamic: true` - on any other source it fires an error event and the update is
2281
+ * silently lost.
2282
+ *
2283
+ * And `dynamic: true` does not just switch that method on. It replaces geojson-vt, which cuts the data
2284
+ * into tiles and simplifies it for low zooms, with an index that scans every feature for every tile
2285
+ * and does not simplify at all; Mapbox's own source calls it the mode for "small data, frequently
2286
+ * updated". For a layer of tens of thousands of parcels that makes every zoomed-out tile carry every
2287
+ * parcel at full detail - far worse than resending the data.
2288
+ *
2289
+ * So a layer gets a dynamic source only while it is small (`dynamicSourceMaxFeatures`) - the drawing
2290
+ * and editing layers that change on every drag, which is what the mode is for - and every other layer
2291
+ * keeps the default source and `setData`. A dynamic layer that grows past the limit, or gains a
2292
+ * GeometryCollection (which dynamic mode cannot take), has its source rebuilt in the default mode. It
2293
+ * is not switched back if it shrinks: `setData` on a small layer is cheap anyway.
2294
+ */
2218
2295
  export declare class MapboxAdapter extends BaseGLAdapter {
2219
2296
  readonly engineName: "mapbox";
2297
+ /** Layers larger than this keep the default source (0 turns dynamic sources off) */
2298
+ dynamicSourceMaxFeatures: number;
2299
+ /** Layers whose source was created with `dynamic: true` */
2300
+ private dynamicSources;
2301
+ protected sourceOptionsFor(layer: OFSLayerData): Record<string, unknown>;
2302
+ protected supportsPartialUpdate(layer: OFSLayerData, source: any): boolean;
2303
+ /** Mapbox merges features in by id; a feature with no geometry is how it is told to delete one */
2304
+ protected sendPartialUpdate(source: any, diff: {
2305
+ add: Feature<Geometry, any>[];
2306
+ remove: (string | number)[];
2307
+ }): void;
2308
+ syncLayer(layer: OFSLayerData): void;
2309
+ protected updateSourceData(layer: OFSLayerData): void;
2310
+ removeLayer(layerId: string): void;
2311
+ private outgrewDynamicSource;
2312
+ /**
2313
+ * Replace a layer's source with one in the default mode. A source cannot change mode in place, and
2314
+ * its GL layers cannot outlive it, so all of them are removed and built again; feature state goes
2315
+ * with the old source, so the selection is put back afterwards.
2316
+ */
2317
+ private rebuildSource;
2318
+ private fitsDynamicSource;
2319
+ /** Mapbox before 3.4 has neither the option nor the method; `peerDependencies` allows >= 3.0 */
2320
+ private engineHasDynamicSources;
2220
2321
  /**
2221
2322
  * Build a map in `container`.
2222
2323
  *
@@ -2718,6 +2819,7 @@ export declare class OFSDataSource extends TypedEventEmitter<OFSDataSourceEvents
2718
2819
  * Remove a layer by ID
2719
2820
  */
2720
2821
  removeLayer(layerId: string): boolean;
2822
+ private detachLayer;
2721
2823
  /**
2722
2824
  * Get layer by ID
2723
2825
  */
@@ -2876,6 +2978,8 @@ export declare class OFSDataSource extends TypedEventEmitter<OFSDataSourceEvents
2876
2978
  * Create buffer around feature and add as new feature or replace
2877
2979
  */
2878
2980
  buffer(layerId: string, featureId: string | number, distance: number, units?: turf.Units, targetLayerId?: string): Feature<Polygon | MultiPolygon>;
2981
+ /** The polygon layer that buffers of a line or point layer go into, created on first use */
2982
+ private bufferLayerFor;
2879
2983
  /**
2880
2984
  * Calculate polygon area in square meters or hectares
2881
2985
  */
@@ -2922,8 +3026,14 @@ export declare interface OFSDataSourceEvents {
2922
3026
  'layer:added': {
2923
3027
  layer: OFSLayerData;
2924
3028
  };
3029
+ /**
3030
+ * `replaced` is true when the layer went because `addLayer` was given its id again: the same layer
3031
+ * is coming straight back with new data, so whatever is kept per layer (its styling, say) should
3032
+ * stay. False when it was removed for good.
3033
+ */
2925
3034
  'layer:removed': {
2926
3035
  layerId: string;
3036
+ replaced: boolean;
2927
3037
  };
2928
3038
  'layer:updated': {
2929
3039
  layer: OFSLayerData;
@@ -3403,7 +3513,6 @@ export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
3403
3513
  result: Feature<Geometry>;
3404
3514
  };
3405
3515
  private replaceLineWithPieces;
3406
- private safeRawMap;
3407
3516
  /** Segments of all visible features except `exclude` (used as cutting edges / boundaries) */
3408
3517
  private boundarySegments;
3409
3518
  /**
@@ -3485,6 +3594,8 @@ export declare class OFSDrawTools extends TypedEventEmitter<DrawEvents> {
3485
3594
  * in any mode), then the active drawing mode, and nothing at all when no tool is running.
3486
3595
  */
3487
3596
  private applyCursor;
3597
+ /** The font the guide labels are drawn in: the one set, else the basemap's, else the engine default */
3598
+ private guideFont;
3488
3599
  /** Current guide settings (a copy: change them through `setGuides`) */
3489
3600
  getGuides(): DrawGuideSettings;
3490
3601
  /**
@@ -3719,6 +3830,11 @@ export declare class OFSLayerData {
3719
3830
  sourceCrs?: string;
3720
3831
  sourceFile?: string;
3721
3832
  fields?: LayerMetadata['fields'];
3833
+ /**
3834
+ * The colour the layer was added with, if one was given. This is the starting point only: once
3835
+ * the layer is on the map its colour lives in its theme, and restyling it does not update this.
3836
+ */
3837
+ readonly color?: string;
3722
3838
  private featuresMap;
3723
3839
  private spatialIndex;
3724
3840
  private indexItems;
@@ -4084,7 +4200,34 @@ export declare class OFSSymbologyPanel {
4084
4200
 
4085
4201
  export declare class OFSThematicEngine extends TypedEventEmitter<ThematicEvents> {
4086
4202
  private themes;
4087
- constructor();
4203
+ private readonly random;
4204
+ private dataSource;
4205
+ private unbindDataSource;
4206
+ constructor(options?: ThematicEngineOptions);
4207
+ /**
4208
+ * Let the engine see the layers on the map.
4209
+ *
4210
+ * Without this it cannot know what colours are already in use, so every layer added without a
4211
+ * colour comes out the same default blue. With it, such a layer gets a colour that stands apart
4212
+ * from the others (see `pickLayerColor`), and a colour given in `addLayer({ color })` is applied.
4213
+ *
4214
+ * It also lets the engine forget the theme of a layer that is removed. An engine that is not bound
4215
+ * cannot see removals, and keeps every theme until `removeTheme` is called.
4216
+ *
4217
+ * OFSObject, the map adapter and the legend all call this, so it rarely needs calling by hand.
4218
+ * Binding the data source that is already bound does nothing.
4219
+ */
4220
+ bindDataSource(dataSource: OFSDataSource): void;
4221
+ /**
4222
+ * Give a new layer its theme as it is added rather than when it is first drawn. Colours are then
4223
+ * chosen in the order layers arrive - not the order some view happens to render them - and they
4224
+ * are in the theme from the start, so a project saved before the map has drawn still keeps them.
4225
+ *
4226
+ * When `addLayer` replaces a layer with the same id, the theme is still there. A colour passed to
4227
+ * `addLayer` is explicit, so it wins; without one, the layer keeps the styling it had - refreshing
4228
+ * a layer's data should not undo the way someone classified it.
4229
+ */
4230
+ private styleNewLayer;
4088
4231
  /**
4089
4232
  * Get current theme definition for a layer
4090
4233
  */
@@ -4098,6 +4241,13 @@ export declare class OFSThematicEngine extends TypedEventEmitter<ThematicEvents>
4098
4241
  exportThemes(): ThematicDefinition[];
4099
4242
  /** Restore themes saved with exportThemes */
4100
4243
  importThemes(themes: ThematicDefinition[]): void;
4244
+ /**
4245
+ * Settings that belong to the layer rather than to the way it is classified, carried over when the
4246
+ * renderer changes. In QGIS labels are a tab of their own and switching Single / Categorized /
4247
+ * Graduated never touches them; here switching used to drop them, along with the selection
4248
+ * highlight colour.
4249
+ */
4250
+ private carriedLayerSettings;
4101
4251
  private carriedSymbol;
4102
4252
  /**
4103
4253
  * Create a simple uniform color theme
@@ -4120,6 +4270,11 @@ export declare class OFSThematicEngine extends TypedEventEmitter<ThematicEvents>
4120
4270
  * symbol overrides are kept where the same category value / class position still exists.
4121
4271
  */
4122
4272
  reclassify(layer: OFSLayerData): ThematicDefinition | undefined;
4273
+ /**
4274
+ * The first colour of `palette` that is clearly different from every colour in `inUse`; when the
4275
+ * palette has none left, the colour furthest from them all.
4276
+ */
4277
+ private unusedClassColor;
4123
4278
  private createCategoricalThemeData;
4124
4279
  /**
4125
4280
  * Merge changes into the base symbol (creates a simple theme when the layer has none)
@@ -4144,6 +4299,17 @@ export declare class OFSThematicEngine extends TypedEventEmitter<ThematicEvents>
4144
4299
  private classList;
4145
4300
  private requireTheme;
4146
4301
  private defaultTheme;
4302
+ /**
4303
+ * Colour for a layer that has no theme yet: the one it was added with; otherwise, when the engine
4304
+ * can see the map, one clear of the colours the other layers show; otherwise the default blue.
4305
+ *
4306
+ * Only layers still on the map count, which is why this walks the layers rather than the themes.
4307
+ *
4308
+ * Only simple (single-colour) layers count, too. A categorised or graduated layer is read by its
4309
+ * legend - its colours stand for values, not for the layer - and a single ramp can take up most
4310
+ * of the palette, which would leave every new layer with a leftover colour.
4311
+ */
4312
+ private defaultColorFor;
4147
4313
  /**
4148
4314
  * Theme of a layer, creating the default simple theme silently (no event) when it has none
4149
4315
  */
@@ -4487,6 +4653,30 @@ export declare function parseXml(source: string): XmlElement;
4487
4653
  /** Boundary paths: polygon rings or line parts */
4488
4654
  export declare function pathsOf(geometry: Geometry): Position[][];
4489
4655
 
4656
+ /**
4657
+ * A colour for a new layer that is clearly different from the layers already on the map.
4658
+ *
4659
+ * In order, it takes a random palette colour that is distinct from:
4660
+ * 1. every layer on the map, visible or hidden;
4661
+ * 2. every visible layer (it may match a hidden one);
4662
+ * and when neither exists - more than about eleven layers - the colour furthest from the visible layers, out of the palette and a batch of random
4663
+ * colours. It always returns a colour: a layer that looks like another is a nuisance, a layer that
4664
+ * cannot be added is a failure.
4665
+ */
4666
+ export declare function pickLayerColor(options?: PickLayerColorOptions): string;
4667
+
4668
+ export declare interface PickLayerColorOptions {
4669
+ /** Colours of the layers currently shown. The new colour keeps clear of these first. */
4670
+ visible?: readonly string[];
4671
+ /**
4672
+ * Colours of layers that are loaded but hidden. Avoided as well while that is possible - a hidden
4673
+ * layer is one click from being shown - but given up before the visible ones are.
4674
+ */
4675
+ hidden?: readonly string[];
4676
+ /** Source of randomness in [0, 1); pass a seeded one to make the choice reproducible */
4677
+ random?: () => number;
4678
+ }
4679
+
4490
4680
  export declare interface PlanarArc {
4491
4681
  center: PlanarPoint;
4492
4682
  radius: number;
@@ -4656,6 +4846,13 @@ export declare function reportInternalError(scope: string, error: unknown): void
4656
4846
  /** The engine to build a map with, or an error saying how to supply one */
4657
4847
  export declare function requireMapEngine(global: MapEngineGlobal, options?: Record<string, any>): any;
4658
4848
 
4849
+ /**
4850
+ * Colours a layer must never be given, whatever else is on the map: the selection highlight (a
4851
+ * yellow layer hides what is selected on it), the grey used for "all other values" in a categorised
4852
+ * layer, and white and black, which vanish against light and dark basemaps.
4853
+ */
4854
+ export declare const RESERVED_LAYER_COLORS: readonly string[];
4855
+
4659
4856
  /**
4660
4857
  * Resolves all selected features across all layers in the dataset,
4661
4858
  * prioritizing active layer features first, followed by other layers,
@@ -5247,6 +5444,14 @@ export declare interface ThematicDefinition {
5247
5444
  label?: LabelSettings;
5248
5445
  }
5249
5446
 
5447
+ export declare interface ThematicEngineOptions {
5448
+ /**
5449
+ * Randomness behind the colours given to layers added without one, in [0, 1). Pass a seeded
5450
+ * function to get the same colours every run (tests, screenshots, demos).
5451
+ */
5452
+ random?: () => number;
5453
+ }
5454
+
5250
5455
  export declare interface ThematicEvents {
5251
5456
  'theme:changed': {
5252
5457
  layerId: string;