import { Evented, type EventHandler } from "../events.js"; import type { ReadonlyFeatureSource } from "../source-types.js"; import { type MarkerIcon } from "../layers/icon.js"; import { Marker, type MarkerOptions } from "../layers/marker.js"; import type { GeoJSONFeature } from "../geojson-types.js"; import { LatLng, Point, type LatLngBoundsLike, type LatLngLike, type PointLike } from "../geo.js"; import { type OverlayContent, type OverlayContentContext, type OverlayRenderable, type PopupOptions } from "../overlays/div-overlay.js"; import { SpatialGridIndex } from "./spatial-grid-index.js"; import type { QueryHit, ResolvedQueryOptions } from "../layer.js"; import { type ManagedGeometry } from "./object-geometry.js"; import type { ObjectVisualizationByZoom, ObjectVisualizationMode } from "./object-scene.js"; import type { HeatBackend, HeatMode, HeatEvaluation } from "./heat.js"; import type { ManagedIconOptions, ManagedIconSource } from "./object-icon-atlas.js"; import type { ObjectSearchOptions, ObjectSearchResult } from "./object-search-index.js"; import type { ClusterPropertiesConfig } from "./object-cluster-aggregates.js"; import { type AsyncBatchOptions } from "./async-batch.js"; import { type ObjectId, type ObjectState, type ObjectStyle, type ObjectStyleContext, type ObjectStyleResolver } from "./object-types.js"; export type { ObjectId } from "./object-types.js"; export type { ManagedGeometry, ManagedLineStringGeometry, ManagedPointGeometry, ManagedPolygonGeometry } from "./object-geometry.js"; export type { ObjectCollisionMode, ObjectGradientStop, ObjectLabelStyle, ObjectLineStyle, ObjectPolygonStyle, ObjectState, ObjectStateValue, ObjectStyle, ObjectStyleContext, ObjectStyleResolver, ObjectTrailStyle } from "./object-types.js"; export type { ObjectSearchOptions, ObjectSearchResult } from "./object-search-index.js"; export type { ClusterPropertiesConfig, ClusterPropertyDefinition } from "./object-cluster-aggregates.js"; export type { ObjectVisualizationByZoom, ObjectVisualizationMode } from "./object-scene.js"; export type { ManagedIconOptions, ManagedIconSource } from "./object-icon-atlas.js"; export interface ManagedObject { id?: ObjectId; /** Named point position. GeoJSON tuples belong in `geometry.coordinates`. */ coordinates?: LatLngLike; geometry?: ManagedGeometry | { coordinates?: number[]; type?: string; }; properties?: { title?: string; [key: string]: unknown; }; [key: string]: unknown; } export interface ObjectManagerAsyncOptions extends AsyncBatchOptions { /** Schedule one render/layout invalidation after the import. Default true. */ render?: boolean; } export type ClusterRenderer = "dom" | "webgl" | "auto"; /** Fixed category / interaction palette for WebGL singles (RGBA 0..1). */ export declare const OBJECT_MANAGER_PALETTE: { alpha: readonly [number, number, number, 0.88]; beta: readonly [number, number, number, 0.88]; gamma: readonly [number, number, number, 0.88]; alert: readonly [number, number, number, 0.92]; selected: readonly [number, number, number, 0.95]; hover: readonly [number, number, number, 0.95]; }; export interface ObjectManagerOptions { /** Optional reactive GeoJSON source shared with GeoJSON and text layers. */ source?: ReadonlyFeatureSource | null; minZoom?: number; marker?: MarkerOptions; clusterize?: boolean; /** When true (default), WebGL singles use category/alert/selected/hover palette colors. */ styleByCategory?: boolean; /** * Data-driven style resolver. Point styles use fill/fillOpacity/size; color/opacity * remain compatibility aliases. Unspecified properties fall back to legacy/defaults. * Priority: base defaults → legacy category/alert/selected/hover → custom `style` → normalize. */ style?: ObjectStyleResolver | null; /** * Cluster radius in CSS/world pixels at the clustered zoom (Leaflet-style). * Default 50. Clamped to ≥ 20. */ clusterRadiusPixels?: number; clusterMinPoints?: number; clusterMaxZoom?: number; clusterZoomOnClick?: boolean; spiderfyOnMaxZoom?: boolean; spiderfyDistanceMultiplier?: number; zoomToBoundsOnClick?: boolean; indexCellSize?: number; /** * Custom cluster badge. Default: count label with size/color tiers * (`sm` < 10, `md` < 100, `lg` ≥ 100) — similar to Leaflet.markercluster. */ clusterIcon?: ClusterIconFactory | null; clusterClassName?: string; clusterTitle?: (count: number, ids: ObjectId[]) => string; clusterAriaLabel?: (count: number, ids: ObjectId[]) => string; /** * Cluster / mass-point drawing backend. * - `dom` — Marker/DivIcon * - `webgl` — GPU points for unclustered objects; canvas cluster badges when clustering * - `auto` — webgl when indexed objects ≥ `webglThreshold` (with or without clustering) */ clusterRenderer?: ClusterRenderer; /** Object count at which `auto` switches to WebGL. Default 2000. */ webglThreshold?: number; /** * Offload first/zoom cluster layout to a Worker. * - `true` / `false` — force * - `auto` — worker when indexed objects ≥ `layoutWorkerThreshold` */ layoutWorker?: boolean | "auto"; /** Default 5000. */ layoutWorkerThreshold?: number; /** * Largest collection that gets a full all-zoom cluster hierarchy. Above this * limit ObjectManager keeps compact, worker-built layouts only for zooms that * are actually visited. Default 250000. `0` = unlimited hierarchy. */ clusterHierarchyMaxObjects?: number; /** Stop `add()` once this many objects are stored. `0` / unset = unlimited. */ maxObjects?: number; /** Enable label/icon declutter in the viewport. */ declutter?: boolean; /** Active visualization strategy. Default "objects". */ visualization?: ObjectVisualizationMode; visualizationByZoom?: ObjectVisualizationByZoom; /** Local search index fields (e.g. `properties.name`). */ search?: { fields: string[]; normalize?: boolean; } | null; /** Temporal filter extractors (Unix ms). */ time?: { value?: (object: ManagedObject) => number | null; from?: (object: ManagedObject) => number | null; to?: (object: ManagedObject) => number | null; } | null; /** Cluster aggregate reducers. */ clusterProperties?: ClusterPropertiesConfig; /** Optional heatmap value/weight for visualization:"heatmap"|"auto". */ heatmapWeight?: ((object: ManagedObject, id?: ObjectId) => number) | null; /** * Field value that maps to the top of the gradient. Without it the scale is relative to * whatever the current peak happens to be, so a field of uniformly low weights still paints * its warmest corner red. Set it when `heatmapWeight` returns an absolute scale. */ heatmapReferenceMax?: number | null; /** Heat visualization from one scalar field: colors, contours, or both. Default "heatmap". */ heatmapDisplay?: HeatMode; /** Draw one caption per visible contour level. Default true. */ heatmapIsolineLabels?: boolean; /** Scalar-field compute backend. `auto` avoids GPU readback for contour modes. */ heatmapBackend?: HeatBackend; /** Full immutable dataset field, or per-zoom refinement. Default `"static"`. */ heatmapEvaluation?: HeatEvaluation; /** Absolute isoline value interval, or automatic engineering step. */ heatmapIsolineStep?: "auto" | number; /** Cluster badge styling using aggregate properties. */ clusterStyle?: ((cluster: { id: string; count: number; properties: Record; containsSelected: boolean; }, context: Readonly) => ObjectStyle | null | undefined) | null; /** * Icon/label/trail/path/polygon scene layers + per-object scene geometries. * Set `false` for mass WebGL points (100k–1M) — skips the O(n) scene sync that * otherwise runs on every render after Phase-2 scene work. * Default `true`. */ sceneFeatures?: boolean; /** * Cap LineString/Polygon vertex count on ingest. Default 65536. `0` = unlimited. */ maxVerticesPerGeometry?: number; } export type ClusterIconFactory = (count: number, ids: ObjectId[]) => MarkerIcon; export type ObjectFilter = (object: ManagedObject, id: ObjectId) => boolean; export interface ObjectPopupContext extends OverlayContentContext { manager: ObjectManager; object: ManagedObject; objectId: ObjectId; } export interface ClusterPopupContext extends OverlayContentContext { manager: ObjectManager; clusterId: string; objectIds: ObjectId[]; objects: ManagedObject[]; } export type ObjectPopupContent = OverlayContent | ((object: ManagedObject, id: ObjectId, context: ObjectPopupContext) => OverlayRenderable | Promise); export type ClusterPopupContent = OverlayContent | ((objects: ManagedObject[], ids: ObjectId[], context: ClusterPopupContext) => OverlayRenderable | Promise); export interface ObjectManagerStats { objects: number; indexedObjects: number; indexCells: number; visibleObjects: number; objectMarkers: number; clusters: number; renderedMarkers: number; renderer: "dom" | "webgl"; layoutZoom: number | null; clusterStrategy: "none" | "greedy" | "hierarchy"; } interface ObjectManagerMap extends Evented { zoom: number; size?: { width: number; height: number; }; getBounds(): LatLngBoundsLike; getPane?(name: string): HTMLElement | null | undefined; latLngToLayerPoint(value: LatLngLike): Point; latLngToContainerPoint?(value: LatLngLike): Point; containerPointToLatLng(value: PointLike): LatLng; setView(center: LatLngLike, zoom: number): unknown; fitBounds(bounds: LatLngBoundsLike, options?: { padding?: number; }): unknown; /** Present on a real `Orihon`; optional so tests can pass a minimal map stub. */ flyTo?(center: LatLngLike, zoom?: number, options?: { durationMs?: number; }): unknown; crs?: { code: "EPSG:3857" | "Simple"; }; /** Terminal-lifecycle flag from `Orihon`; optional so tests can pass a minimal map stub. */ isDestroyed?: boolean; } type ResolvedObjectManagerOptions = Required; export interface ObjectManagerEventMap { objectstatechange: { id: ObjectId; state: Readonly; changedKeys: string[]; }; stylechange: { style: ObjectStyleResolver | null; }; iconregister: { name: string; }; iconremove: { name: string; }; timerangechange: { from: number | null; to: number | null; }; visualizationchange: { visualization: ObjectVisualizationMode; }; spiderfy: { clusterId: string; objectIds: ObjectId[]; }; unspiderfy: { clusterId: string | null; }; render: { stats: ObjectManagerStats; }; error: { error: unknown; phase: "layout"; }; click: { objectId: ObjectId; object: ManagedObject | undefined; layer?: Marker; latlng?: LatLngLike; originalEvent?: MouseEvent | PointerEvent; }; hover: { objectId: ObjectId | null; object: ManagedObject | null | undefined; latlng?: LatLngLike | null; originalEvent?: MouseEvent | PointerEvent; }; clusterclick: { clusterId: string; objectIds: ObjectId[]; count: number; latlng: LatLngLike; originalEvent?: MouseEvent | PointerEvent; }; } export declare class ObjectManager extends Evented { #private; get isDestroyed(): boolean; protected assertAlive(): void; readonly options: ResolvedObjectManagerOptions; readonly items: Map; readonly markers: Map; readonly clusters: Map; readonly clusterMembers: Map; readonly index: SpatialGridIndex; map: ObjectManagerMap | null; filter: ObjectFilter | null; readonly _render: EventHandler; readonly _scheduleRender: () => void; readonly _scheduleLabelRedraw: () => void; private _visibleObjects; private _popupBinding; private _clusterPopupBinding; private _activePopup; private _layout; private _clusterIndex; private _layoutDirty; private _webglLayer; private _webglMeta; /** GPU slot index for each object id currently drawn in `_webglMeta`. */ private _webglIdToIndex; /** Full (unfiltered) GPU pack — filter/live compact without re-encoding mercator. */ private _webglPack; /** GPU draw list is a sparse subset of `_webglPack` (alarms-only etc). */ private _gpuSubset; private _heatWeightBuf; /** Reused source-slot scratch for compacting filtered WebGL packs. */ private _webglFilterIndexScratch; /** Reused packed slot mask for indexed temporal filtering. */ private _webglSystemMask; /** Last full WebGL rebuild timings; intentionally internal but readable from JS benchmarks. */ private _webglSyncProfile; /** Tiny bounded cache for repeated CSS colors returned by mass-point style resolvers. */ private readonly _webglColorCache; /** Reused buffers for batched WebGL style patches. */ private _webglStylePatchIndices; private _webglStylePatchColors; private _webglStylePatchSizes; private _heatRefreshTimer; private _heatRefreshPending; private _webglSyncedZoom; private _webglDataEpoch; private _webglSyncedEpoch; /** Time-range + filter fingerprint so pan does not rebuild a compacted GPU view. */ private _webglViewKey; private _selectedId; private _hoveredId; private readonly objectStates; private _styleResolver; private _styleZoom; /** Nested beginBulk()/endBulk() depth — suppress per-chunk invalidate+render. */ private _bulkDepth; private readonly scene; private _clusterCanvas; private _canvasClusterCount; private _activeRenderer; private _workerPool; private _layoutGeneration; private _layoutPromise; private _clusterSyncZoom; private _clusterSyncGeneration; private _clusterPool; private readonly _clusterMarkerKey; /** Keep all badges mounted when total clusters ≤ this (avoids pan add/remove churn). */ private _clusterDomBudget; /** Cached single-zoom greedy results while the hierarchy index is still building. */ private _greedyCache; private _pendingGreedyZoom; private _greedyRaf; private _greedyPromise; /** Above this size, never run sync greedy on every zoomend (stale+coalesce instead). */ private _greedyZoomInlineLimit; private _layoutIds; private _layoutCoords; private _layoutPacked; private _layoutPackDirty; /** Monotonic version of the packed coordinate dataset installed in GeometryWorkerPool. */ private _layoutDatasetVersion; private _leafMask; private _leafMaskFilter; private _leafMaskIndex; private _spiderMarkers; private _spiderLegs; private _spiderClusterId; private _sourceUnsubscribe; private readonly _unspiderfyOnMapClick; constructor(options?: ObjectManagerOptions); addTo(map: ObjectManagerMap): this; /** Detach rendering/listeners while retaining data and allowing later addTo(). */ detach(): this; destroy(): this; add(features: ManagedObject | ManagedObject[]): this; /** * Cooperatively ingest a large iterable without one long main-thread task. * Layout invalidation/render remains suspended until the import finishes. * Cancellation keeps the already accepted prefix and flushes one final invalidate. */ addAsync(features: Iterable | AsyncIterable, options?: ObjectManagerAsyncOptions): Promise; /** * Suspend layout invalidation + scheduled renders across many `add`/`update` calls. * Pair with `endBulk()` — required for chunked 100k–1M ingest while the manager is on the map. */ beginBulk(): this; /** Flush one layout invalidate (+ optional render) after `beginBulk()`. */ endBulk(options?: { render?: boolean; }): this; /** * Toggle icon/label/trail/path/polygon scene work. `false` is the mass-point fast path. * Re-enabling rebuilds scene geometries from stored objects. */ setSceneFeatures(enabled: boolean): this; /** * In-place update of existing objects (coordinates / properties / geometry). * On the WebGL non-cluster path this patches GPU buffers instead of a full layout rebuild — * critical for realtime stress at 100k–1M. */ update(features: ManagedObject | ManagedObject[], options?: { animate?: boolean; durationMs?: number; }): this; updateObjects(features: Iterable, options?: { animate?: boolean; durationMs?: number; }): this; moveObject(id: ObjectId, coordinates: LatLngLike, options?: { animate?: boolean; durationMs?: number; }): this; removeObjects(ids: ObjectId | ObjectId[]): this; getObject(id: ObjectId): ManagedObject | undefined; getObjects(): ManagedObject[]; setFilter(filter: ObjectFilter | null): this; /** * Restrict the WebGL draw list to these ids (O(k) from the packed buffer). * Pass `null` to restore every packed point. No layout rebuild. */ setVisibleIds(ids: Iterable | null): this; getSelectedId(): ObjectId | null; setSelected(id: ObjectId | null): this; getHoveredId(): ObjectId | null; setHovered(id: ObjectId | null): this; getObjectState(id: ObjectId): Readonly; setObjectState(id: ObjectId, state: Partial): this; setObjectStates(updates: Iterable<{ id: ObjectId; state: Partial; }>): this; removeObjectState(id: ObjectId, keys?: keyof ObjectState | Array): this; clearObjectStates(): this; setStyle(style: ObjectStyleResolver | null): this; registerIcon(name: string, source: ManagedIconSource, options?: ManagedIconOptions): this; removeIcon(name: string): this; hasIcon(name: string): boolean; clearIcons(): this; search(query: string, options?: ObjectSearchOptions): ObjectSearchResult[]; setTime(timestamp: number | null): this; setTimeRange(from: number | null, to: number | null): this; setVisualization(mode: ObjectVisualizationMode): this; /** * Centre the attached map on an object and select it. `animation: "fly"` runs the map's * `flyTo` curve; the default jumps. The previous `animate` flag was accepted and never read, * so it is rejected rather than quietly kept working with a new meaning. */ focusObject(id: ObjectId, options?: { zoom?: number; animation?: "none" | "fly"; durationMs?: number; }): this; bindPopup(content: ObjectPopupContent, options?: PopupOptions): this; unbindPopup(): this; bindClusterPopup(content: ClusterPopupContent, options?: PopupOptions): this; unbindClusterPopup(): this; openPopup(id: ObjectId): this; closePopup(): this; hasOpenPopup(): boolean; setClusterize(enabled: boolean): this; setClusterRadiusPixels(radiusPixels: number): this; setClusterRenderer(renderer: ClusterRenderer): this; clear(): this; getStats(): ObjectManagerStats; queryHit(point: Point, options: ResolvedQueryOptions): QueryHit | QueryHit[] | null; spiderfyCluster(clusterId: string): this; unspiderfy(): this; render(): void; /** * Paint clusters for the current (or given) zoom quickly, then finish the zoom hierarchy * before resolving (worker when enabled). First paint still happens before the await. */ prepareLayout(zoom?: number): Promise; } export declare function objectManager(options?: ObjectManagerOptions): ObjectManager; //# sourceMappingURL=object-manager.d.ts.map