ofs-object 0.1.3 → 0.1.4

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
  */
@@ -782,6 +795,16 @@ export declare function colorRampGradientCss(name: string, reverse?: boolean): s
782
795
  */
783
796
  export declare type ColorRampType = 'sequential' | 'diverging' | 'qualitative';
784
797
 
798
+ /**
799
+ * The colours a theme actually puts on the map, for a layer of the given geometry.
800
+ *
801
+ * A simple theme is one colour, but which one depends on the geometry: the fill of a polygon (or
802
+ * its outline when it is drawn unfilled), the stroke of a line, the marker of a point. A
803
+ * categorised or graduated theme puts every class colour it shows on the map. (New layers do not
804
+ * avoid those: see `OFSThematicEngine.defaultColorFor`.)
805
+ */
806
+ export declare function colorsOfTheme(theme: ThematicDefinition, geometry: LayerGeometryType): string[];
807
+
785
808
  export declare interface CommandArgSpec {
786
809
  /** Schema properties filled from the words before any --option, in order */
787
810
  positional?: string[];
@@ -1120,6 +1143,9 @@ export declare interface DistanceMeasure {
1120
1143
  closingMeters: number;
1121
1144
  }
1122
1145
 
1146
+ /** Below this CIEDE2000 distance two map colours are easy to confuse at a glance */
1147
+ export declare const DISTINCT_DELTA_E = 20;
1148
+
1123
1149
  /** Save an exported image in the browser */
1124
1150
  export declare function downloadMapImage(image: MapImage): void;
1125
1151
 
@@ -1998,6 +2024,21 @@ export declare interface LandAreaMeasure {
1998
2024
  raiNganWa: string;
1999
2025
  }
2000
2026
 
2027
+ /**
2028
+ * Chosen by greedy max-min selection in CIEDE2000 over the Tailwind 400-800 chromatic families,
2029
+ * starting from a blue. Every pair is at least 20 apart and every colour is at least 22 from the
2030
+ * reserved colours below.
2031
+ *
2032
+ * The blue is `#2563eb`, not the `#3b82f6` every layer used to get: that one is only 16.8 from the
2033
+ * slate grey used for "all other values", which is too close to tell apart in a legend.
2034
+ *
2035
+ * It stops at 11 because that is where the space runs out: the twelfth pick is already under 20
2036
+ * from its nearest neighbour, and every pick after it is closer still. Past this many layers the
2037
+ * picker generates colours instead (see `pickLayerColor`), and they are as far apart as the space
2038
+ * allows rather than clearly distinct.
2039
+ */
2040
+ export declare const LAYER_COLOR_PALETTE: readonly string[];
2041
+
2001
2042
  export declare type LayerGeometryType = 'point' | 'line' | 'polygon' | 'circle' | 'symbol';
2002
2043
 
2003
2044
  /** Legend group of layers; groups can be nested */
@@ -2030,6 +2071,11 @@ export declare interface LayerMetadata {
2030
2071
  sourceFile?: string;
2031
2072
  /** Attribute schema in source order (kept for export; edited through OFSAttributeTools) */
2032
2073
  fields?: FieldSchema[];
2074
+ /**
2075
+ * Colour to draw the layer with (any CSS colour). Left out, the layer gets one chosen to stand
2076
+ * apart from the layers already on the map - see `pickLayerColor`.
2077
+ */
2078
+ color?: string;
2033
2079
  [key: string]: unknown;
2034
2080
  }
2035
2081
 
@@ -2215,8 +2261,60 @@ export declare function looksGeographic(bounds: [number, number, number, number]
2215
2261
  */
2216
2262
  export declare function makeCurve(vertices: Coordinate[], bulges: number[], sagToleranceMeters?: number): OfsCurveMeta | null;
2217
2263
 
2264
+ /**
2265
+ * Largest layer given a dynamic source by default. A judgement call rather than a measurement - see
2266
+ * the class comment - and adjustable per adapter through `dynamicSourceMaxFeatures`.
2267
+ */
2268
+ export declare const MAPBOX_DYNAMIC_MAX_FEATURES = 5000;
2269
+
2270
+ /**
2271
+ * Mapbox GL adapter.
2272
+ *
2273
+ * ## Sending only what changed
2274
+ *
2275
+ * MapLibre's `updateData` takes a diff on any GeoJSON source. Mapbox's is a different thing: it
2276
+ * takes a FeatureCollection to merge in by id (a feature with a null geometry deletes), and only on a
2277
+ * source created with `dynamic: true` - on any other source it fires an error event and the update is
2278
+ * silently lost.
2279
+ *
2280
+ * And `dynamic: true` does not just switch that method on. It replaces geojson-vt, which cuts the data
2281
+ * into tiles and simplifies it for low zooms, with an index that scans every feature for every tile
2282
+ * and does not simplify at all; Mapbox's own source calls it the mode for "small data, frequently
2283
+ * updated". For a layer of tens of thousands of parcels that makes every zoomed-out tile carry every
2284
+ * parcel at full detail - far worse than resending the data.
2285
+ *
2286
+ * So a layer gets a dynamic source only while it is small (`dynamicSourceMaxFeatures`) - the drawing
2287
+ * and editing layers that change on every drag, which is what the mode is for - and every other layer
2288
+ * keeps the default source and `setData`. A dynamic layer that grows past the limit, or gains a
2289
+ * GeometryCollection (which dynamic mode cannot take), has its source rebuilt in the default mode. It
2290
+ * is not switched back if it shrinks: `setData` on a small layer is cheap anyway.
2291
+ */
2218
2292
  export declare class MapboxAdapter extends BaseGLAdapter {
2219
2293
  readonly engineName: "mapbox";
2294
+ /** Layers larger than this keep the default source (0 turns dynamic sources off) */
2295
+ dynamicSourceMaxFeatures: number;
2296
+ /** Layers whose source was created with `dynamic: true` */
2297
+ private dynamicSources;
2298
+ protected sourceOptionsFor(layer: OFSLayerData): Record<string, unknown>;
2299
+ protected supportsPartialUpdate(layer: OFSLayerData, source: any): boolean;
2300
+ /** Mapbox merges features in by id; a feature with no geometry is how it is told to delete one */
2301
+ protected sendPartialUpdate(source: any, diff: {
2302
+ add: Feature<Geometry, any>[];
2303
+ remove: (string | number)[];
2304
+ }): void;
2305
+ syncLayer(layer: OFSLayerData): void;
2306
+ protected updateSourceData(layer: OFSLayerData): void;
2307
+ removeLayer(layerId: string): void;
2308
+ private outgrewDynamicSource;
2309
+ /**
2310
+ * Replace a layer's source with one in the default mode. A source cannot change mode in place, and
2311
+ * its GL layers cannot outlive it, so all of them are removed and built again; feature state goes
2312
+ * with the old source, so the selection is put back afterwards.
2313
+ */
2314
+ private rebuildSource;
2315
+ private fitsDynamicSource;
2316
+ /** Mapbox before 3.4 has neither the option nor the method; `peerDependencies` allows >= 3.0 */
2317
+ private engineHasDynamicSources;
2220
2318
  /**
2221
2319
  * Build a map in `container`.
2222
2320
  *
@@ -2718,6 +2816,7 @@ export declare class OFSDataSource extends TypedEventEmitter<OFSDataSourceEvents
2718
2816
  * Remove a layer by ID
2719
2817
  */
2720
2818
  removeLayer(layerId: string): boolean;
2819
+ private detachLayer;
2721
2820
  /**
2722
2821
  * Get layer by ID
2723
2822
  */
@@ -2922,8 +3021,14 @@ export declare interface OFSDataSourceEvents {
2922
3021
  'layer:added': {
2923
3022
  layer: OFSLayerData;
2924
3023
  };
3024
+ /**
3025
+ * `replaced` is true when the layer went because `addLayer` was given its id again: the same layer
3026
+ * is coming straight back with new data, so whatever is kept per layer (its styling, say) should
3027
+ * stay. False when it was removed for good.
3028
+ */
2925
3029
  'layer:removed': {
2926
3030
  layerId: string;
3031
+ replaced: boolean;
2927
3032
  };
2928
3033
  'layer:updated': {
2929
3034
  layer: OFSLayerData;
@@ -3719,6 +3824,11 @@ export declare class OFSLayerData {
3719
3824
  sourceCrs?: string;
3720
3825
  sourceFile?: string;
3721
3826
  fields?: LayerMetadata['fields'];
3827
+ /**
3828
+ * The colour the layer was added with, if one was given. This is the starting point only: once
3829
+ * the layer is on the map its colour lives in its theme, and restyling it does not update this.
3830
+ */
3831
+ readonly color?: string;
3722
3832
  private featuresMap;
3723
3833
  private spatialIndex;
3724
3834
  private indexItems;
@@ -4084,7 +4194,34 @@ export declare class OFSSymbologyPanel {
4084
4194
 
4085
4195
  export declare class OFSThematicEngine extends TypedEventEmitter<ThematicEvents> {
4086
4196
  private themes;
4087
- constructor();
4197
+ private readonly random;
4198
+ private dataSource;
4199
+ private unbindDataSource;
4200
+ constructor(options?: ThematicEngineOptions);
4201
+ /**
4202
+ * Let the engine see the layers on the map.
4203
+ *
4204
+ * Without this it cannot know what colours are already in use, so every layer added without a
4205
+ * colour comes out the same default blue. With it, such a layer gets a colour that stands apart
4206
+ * from the others (see `pickLayerColor`), and a colour given in `addLayer({ color })` is applied.
4207
+ *
4208
+ * It also lets the engine forget the theme of a layer that is removed. An engine that is not bound
4209
+ * cannot see removals, and keeps every theme until `removeTheme` is called.
4210
+ *
4211
+ * OFSObject, the map adapter and the legend all call this, so it rarely needs calling by hand.
4212
+ * Binding the data source that is already bound does nothing.
4213
+ */
4214
+ bindDataSource(dataSource: OFSDataSource): void;
4215
+ /**
4216
+ * Give a new layer its theme as it is added rather than when it is first drawn. Colours are then
4217
+ * chosen in the order layers arrive - not the order some view happens to render them - and they
4218
+ * are in the theme from the start, so a project saved before the map has drawn still keeps them.
4219
+ *
4220
+ * When `addLayer` replaces a layer with the same id, the theme is still there. A colour passed to
4221
+ * `addLayer` is explicit, so it wins; without one, the layer keeps the styling it had - refreshing
4222
+ * a layer's data should not undo the way someone classified it.
4223
+ */
4224
+ private styleNewLayer;
4088
4225
  /**
4089
4226
  * Get current theme definition for a layer
4090
4227
  */
@@ -4144,6 +4281,17 @@ export declare class OFSThematicEngine extends TypedEventEmitter<ThematicEvents>
4144
4281
  private classList;
4145
4282
  private requireTheme;
4146
4283
  private defaultTheme;
4284
+ /**
4285
+ * Colour for a layer that has no theme yet: the one it was added with; otherwise, when the engine
4286
+ * can see the map, one clear of the colours the other layers show; otherwise the default blue.
4287
+ *
4288
+ * Only layers still on the map count, which is why this walks the layers rather than the themes.
4289
+ *
4290
+ * Only simple (single-colour) layers count, too. A categorised or graduated layer is read by its
4291
+ * legend - its colours stand for values, not for the layer - and a single ramp can take up most
4292
+ * of the palette, which would leave every new layer with a leftover colour.
4293
+ */
4294
+ private defaultColorFor;
4147
4295
  /**
4148
4296
  * Theme of a layer, creating the default simple theme silently (no event) when it has none
4149
4297
  */
@@ -4487,6 +4635,30 @@ export declare function parseXml(source: string): XmlElement;
4487
4635
  /** Boundary paths: polygon rings or line parts */
4488
4636
  export declare function pathsOf(geometry: Geometry): Position[][];
4489
4637
 
4638
+ /**
4639
+ * A colour for a new layer that is clearly different from the layers already on the map.
4640
+ *
4641
+ * In order, it takes a random palette colour that is distinct from:
4642
+ * 1. every layer on the map, visible or hidden;
4643
+ * 2. every visible layer (it may match a hidden one);
4644
+ * 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
4645
+ * colours. It always returns a colour: a layer that looks like another is a nuisance, a layer that
4646
+ * cannot be added is a failure.
4647
+ */
4648
+ export declare function pickLayerColor(options?: PickLayerColorOptions): string;
4649
+
4650
+ export declare interface PickLayerColorOptions {
4651
+ /** Colours of the layers currently shown. The new colour keeps clear of these first. */
4652
+ visible?: readonly string[];
4653
+ /**
4654
+ * Colours of layers that are loaded but hidden. Avoided as well while that is possible - a hidden
4655
+ * layer is one click from being shown - but given up before the visible ones are.
4656
+ */
4657
+ hidden?: readonly string[];
4658
+ /** Source of randomness in [0, 1); pass a seeded one to make the choice reproducible */
4659
+ random?: () => number;
4660
+ }
4661
+
4490
4662
  export declare interface PlanarArc {
4491
4663
  center: PlanarPoint;
4492
4664
  radius: number;
@@ -4656,6 +4828,13 @@ export declare function reportInternalError(scope: string, error: unknown): void
4656
4828
  /** The engine to build a map with, or an error saying how to supply one */
4657
4829
  export declare function requireMapEngine(global: MapEngineGlobal, options?: Record<string, any>): any;
4658
4830
 
4831
+ /**
4832
+ * Colours a layer must never be given, whatever else is on the map: the selection highlight (a
4833
+ * yellow layer hides what is selected on it), the grey used for "all other values" in a categorised
4834
+ * layer, and white and black, which vanish against light and dark basemaps.
4835
+ */
4836
+ export declare const RESERVED_LAYER_COLORS: readonly string[];
4837
+
4659
4838
  /**
4660
4839
  * Resolves all selected features across all layers in the dataset,
4661
4840
  * prioritizing active layer features first, followed by other layers,
@@ -5247,6 +5426,14 @@ export declare interface ThematicDefinition {
5247
5426
  label?: LabelSettings;
5248
5427
  }
5249
5428
 
5429
+ export declare interface ThematicEngineOptions {
5430
+ /**
5431
+ * Randomness behind the colours given to layers added without one, in [0, 1). Pass a seeded
5432
+ * function to get the same colours every run (tests, screenshots, demos).
5433
+ */
5434
+ random?: () => number;
5435
+ }
5436
+
5250
5437
  export declare interface ThematicEvents {
5251
5438
  'theme:changed': {
5252
5439
  layerId: string;