@object-ui/plugin-map 17.5.0 → 17.6.0

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/README.md CHANGED
@@ -1,15 +1,19 @@
1
1
  # @object-ui/plugin-map
2
2
 
3
- Map visualization plugin for Object UI - Display geographic data with interactive maps.
3
+ Map view plugin for Object UI.
4
4
 
5
- ## Features
5
+ Renders the records of an **ObjectQL query** as markers on a MapLibre map: every
6
+ marker comes from a record's own coordinate fields, and the first paint frames the
7
+ records that were fetched. It is a *view over data* — there is no authored marker
8
+ list, and no pin you place by hand.
6
9
 
7
- - **Interactive Maps** - Zoomable, pannable map visualization
8
- - **Markers** - Add markers with custom icons and popups
9
- - **Layers** - Multiple map layers support
10
- - **Geolocation** - User location detection
11
- - **Customizable** - Tailwind CSS styling support
12
- - **Responsive** - Mobile-friendly map controls
10
+ Importing the package registers two component types on the `ComponentRegistry`,
11
+ both resolving to the same renderer:
12
+
13
+ - `object-map` — the object-bound renderer
14
+ - `map` — the bare spec view-type name (`ViewTypeSchema`'s `'map'`), for a node
15
+ authored with it directly. Inside an `ObjectView`, a `map` view is compiled to
16
+ an `object-map` node, so both spellings end at the same component.
13
17
 
14
18
  ## Installation
15
19
 
@@ -17,263 +21,171 @@ Map visualization plugin for Object UI - Display geographic data with interactiv
17
21
  pnpm add @object-ui/plugin-map
18
22
  ```
19
23
 
20
- ## Usage
21
-
22
- ### Automatic Registration (Side-Effect Import)
23
-
24
- ```typescript
25
- // In your app entry point (e.g., App.tsx or main.tsx)
26
- import '@object-ui/plugin-map';
27
-
28
- // Now you can use map types in your schemas
29
- const schema = {
30
- type: 'map',
31
- center: { lat: 37.7749, lng: -122.4194 },
32
- zoom: 12,
33
- markers: [
34
- { lat: 37.7749, lng: -122.4194, label: 'San Francisco' }
35
- ]
36
- };
37
- ```
38
-
39
- ### Manual Registration
24
+ ## Requires a bundler — plain Node cannot import this package
40
25
 
41
- ```typescript
42
- import { mapComponents } from '@object-ui/plugin-map';
43
- import { ComponentRegistry } from '@object-ui/core';
26
+ `ObjectMap` imports MapLibre's stylesheet at module scope
27
+ (`import 'maplibre-gl/dist/maplibre-gl.css'`), and Node has no loader for `.css` at all.
28
+ Importing the published entry from plain Node ESM — no bundler, no loader hooks — therefore
29
+ resolves and then fails during evaluation:
44
30
 
45
- // Register map components
46
- Object.entries(mapComponents).forEach(([type, component]) => {
47
- ComponentRegistry.register(type, component);
48
- });
31
+ ```text
32
+ TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".css"
33
+ for .../maplibre-gl/dist/maplibre-gl.css
49
34
  ```
50
35
 
51
- ## Schema API
52
-
53
- ### Map
36
+ **This is a supported-configuration statement, not a bug to report.** Unbundled Node
37
+ consumption is not supported for style-carrying plugin packages. It was ruled that way on
38
+ [objectui#5384](https://github.com/objectstack-ai/objectui/issues/5384) — deliberately, over
39
+ the alternative of moving the stylesheet out of module scope — because a MapLibre canvas
40
+ without its stylesheet is not a map, and no unbundled-Node consumer exists to serve.
54
41
 
55
- Display an interactive map:
42
+ Consume it through a host that handles CSS imports, which every supported host does: Vite,
43
+ webpack, or Next with the package listed in `transpilePackages`. If you have a real need to
44
+ import it under plain Node — SSR with no bundler, a Node-side script — please open an issue.
45
+ That reopens the question as a design decision rather than a defect, and the shape of your
46
+ consumer is the missing input.
56
47
 
57
- ```typescript
58
- {
59
- type: 'map',
60
- center: { lat: number, lng: number },
61
- zoom?: number, // Default: 10
62
- markers?: MapMarker[],
63
- layers?: MapLayer[],
64
- height?: number | string,
65
- onMarkerClick?: (marker) => void,
66
- className?: string
67
- }
68
- ```
69
-
70
- ### Map Marker
71
-
72
- ```typescript
73
- interface MapMarker {
74
- lat: number;
75
- lng: number;
76
- label?: string;
77
- icon?: string; // Icon URL or name
78
- popup?: string | ReactNode;
79
- color?: string; // Marker color
80
- }
81
- ```
82
-
83
- ## Examples
84
-
85
- ### Basic Map
86
-
87
- ```typescript
88
- const schema = {
89
- type: 'map',
90
- center: { lat: 40.7128, lng: -74.0060 },
91
- zoom: 13,
92
- height: 500,
93
- markers: [
94
- {
95
- lat: 40.7128,
96
- lng: -74.0060,
97
- label: 'New York City',
98
- popup: 'The Big Apple'
99
- }
100
- ]
101
- };
102
- ```
103
-
104
- ### Multiple Markers
48
+ ## Usage
105
49
 
106
- ```typescript
107
- const schema = {
108
- type: 'map',
109
- center: { lat: 37.7749, lng: -122.4194 },
110
- zoom: 10,
111
- markers: [
112
- {
113
- lat: 37.7749,
114
- lng: -122.4194,
115
- label: 'San Francisco',
116
- color: 'red',
117
- popup: 'Golden Gate Bridge'
118
- },
119
- {
120
- lat: 37.8044,
121
- lng: -122.2712,
122
- label: 'Oakland',
123
- color: 'blue',
124
- popup: 'Port of Oakland'
125
- },
126
- {
127
- lat: 37.3382,
128
- lng: -121.8863,
129
- label: 'San Jose',
130
- color: 'green',
131
- popup: 'Silicon Valley'
132
- }
133
- ]
134
- };
135
- ```
50
+ Registration is a side effect of the import. There is no manual-registration
51
+ export to iterate over — the import *is* the registration.
136
52
 
137
- ### Interactive Map
53
+ ```ts
54
+ import '@object-ui/plugin-map';
138
55
 
139
- ```typescript
56
+ // Object-bound: the markers are the records the query returns.
140
57
  const schema = {
141
- type: 'map',
142
- center: { lat: 51.5074, lng: -0.1278 },
143
- zoom: 12,
144
- markers: [/* markers */],
145
- onMarkerClick: (marker) => {
146
- console.log('Marker clicked:', marker);
147
- // Show marker details
58
+ type: 'object-map',
59
+ objectName: 'stores',
60
+ map: {
61
+ latitudeField: 'lat',
62
+ longitudeField: 'lng',
63
+ titleField: 'name',
64
+ descriptionField: 'address',
148
65
  },
149
- onMapClick: (coordinates) => {
150
- console.log('Map clicked at:', coordinates);
151
- // Add new marker
152
- }
153
66
  };
154
67
  ```
155
68
 
156
- ### Map with Data Binding
157
-
158
- ```typescript
159
- const schema = {
160
- type: 'map',
161
- center: { lat: 37.7749, lng: -122.4194 },
162
- zoom: 10,
163
- markers: '${data.locations.map(loc => ({ lat: loc.latitude, lng: loc.longitude, label: loc.name }))}',
164
- onMarkerClick: (marker) => {
165
- // Handle marker click
166
- }
167
- };
168
- ```
169
-
170
- ## Integration with ObjectQL
171
-
172
- Connect map to ObjectStack data sources:
173
-
174
- ```typescript
175
- import { createObjectStackAdapter } from '@object-ui/data-objectstack';
176
-
177
- const dataSource = createObjectStackAdapter({
178
- baseUrl: 'https://api.example.com',
179
- token: 'your-auth-token'
180
- });
69
+ A literal record array instead of a query, with the same `map` block:
181
70
 
71
+ ```ts
182
72
  const schema = {
183
73
  type: 'object-map',
184
- dataSource,
185
- object: 'locations',
186
- latField: 'latitude',
187
- lngField: 'longitude',
188
- labelField: 'name',
189
- popupField: 'description',
190
- center: { lat: 37.7749, lng: -122.4194 },
191
- zoom: 10
74
+ staticData: [
75
+ { id: 1, name: 'San Francisco HQ', lat: 37.7749, lng: -122.4194 },
76
+ { id: 2, name: 'Oakland Office', lat: 37.8044, lng: -122.2711 },
77
+ ],
78
+ map: { latitudeField: 'lat', longitudeField: 'lng', titleField: 'name' },
192
79
  };
193
80
  ```
194
81
 
195
- ## Map Features
196
-
197
- ### Custom Markers
198
-
199
- ```typescript
200
- const schema = {
201
- type: 'map',
202
- markers: [
203
- {
204
- lat: 40.7128,
205
- lng: -74.0060,
206
- icon: '/custom-marker.png',
207
- popup: {
208
- type: 'card',
209
- title: 'Location Details',
210
- body: 'Custom popup content'
211
- }
212
- }
213
- ]
214
- };
82
+ `filter` and `sort` are the **query's** filter and order — they reach the data
83
+ source as `$filter` / `$orderby`, and the spec's per-element `dataSource` binding
84
+ is honoured as well. The map issues no row cap of its own.
85
+
86
+ ## The `map` block
87
+
88
+ The declared configuration input. Every key is optional:
89
+
90
+ | Key | Description |
91
+ | --- | --- |
92
+ | `latitudeField` | Record field holding the latitude. Needs `longitudeField` alongside it; both values must be numbers. |
93
+ | `longitudeField` | Record field holding the longitude. |
94
+ | `locationField` | Single field holding both coordinates — see the formats below. Used when the lat/lng pair yields nothing. |
95
+ | `titleField` | Field shown as the marker title. Omitted, markers are titled `Marker`. |
96
+ | `descriptionField` | Field shown under the title in the marker popup. |
97
+ | `zoom` | Zoom level. Declaring it opts this view out of the auto-fit (see below). |
98
+ | `center` | `[latitude, longitude]` — a two-number **tuple**, latitude first. Declaring it opts this view out of the auto-fit. |
99
+ | `style` | MapLibre style URL/spec, replacing the default public demo style. |
100
+
101
+ **The block replaces the field-name defaults, it is not merged with them.** With
102
+ no map configuration at all the component falls back to the field names
103
+ `latitude` / `longitude` / `location` / `name` / `description`; the moment a `map`
104
+ block is present, only what it declares is read. So `map: { titleField: 'name' }`
105
+ on its own names no coordinate field, places nothing, and renders an empty map
106
+ under the excluded-records notice — the defaults do not fill the gap.
107
+
108
+ ## Initial camera
109
+
110
+ There is no default zoom and no default centre. With records to show and no
111
+ camera declared, the map **fits the records**: their bounding box, measured along
112
+ the shortest arc that contains them (so a set straddling the antimeridian is
113
+ framed across the line, not around the far side of the planet), with 48px of
114
+ padding and a city-scale zoom ceiling of 12 — a single record does not become a
115
+ rooftop view.
116
+
117
+ Two cases sit outside the fit:
118
+
119
+ - **Nothing placeable** (empty result, or no record yielding coordinates): the
120
+ whole world, centred on `0, 0`.
121
+ - **A declared camera**: `zoom` or `center` in the `map` block wins and the fit is
122
+ skipped. Declaring one half keeps the other derived — `zoom` alone is applied at
123
+ the centre of the records, `center` alone at a continental zoom.
124
+
125
+ A `center` that is not a two-number tuple (the `{ lat, lng }` object form, say) is
126
+ rejected by the config schema, warned about in the console, and **not** adapted —
127
+ and it does not cost the view its fit.
128
+
129
+ ## Coordinate formats
130
+
131
+ `locationField` reads any of:
132
+
133
+ ```ts
134
+ { location: { lat: 37.7749, lng: -122.4194 } } // also latitude/longitude, lon
135
+ { location: '37.7749,-122.4194' } // "lat,lng"
136
+ { location: [37.7749, -122.4194] } // [lat, lng]
215
137
  ```
216
138
 
217
- ### Geolocation
218
-
219
- ```typescript
220
- const schema = {
221
- type: 'map',
222
- useGeolocation: true, // Center map on user's location
223
- zoom: 15,
224
- markers: []
225
- };
226
- ```
139
+ A record whose coordinates are missing, unparseable, or out of range (latitude
140
+ beyond ±90, longitude beyond ±180) is left off the map and counted in a notice
141
+ above it, rather than being silently dropped or rescued.
227
142
 
228
- ### Map Layers
143
+ ## What this component does not read
229
144
 
230
- ```typescript
231
- const schema = {
232
- type: 'map',
233
- center: { lat: 37.7749, lng: -122.4194 },
234
- layers: [
235
- { type: 'heatmap', data: [/* heatmap data */] },
236
- { type: 'polygon', coordinates: [/* polygon coordinates */], color: 'rgba(255, 0, 0, 0.3)' }
237
- ]
238
- };
239
- ```
145
+ Keys that look plausible on a map schema but have no read site here: `markers`
146
+ (markers are records), `layers`, `height` (the container is a fixed responsive
147
+ height, 300px through 600px), `useGeolocation` (the map carries a
148
+ user-initiated "show my location" button instead), and per-marker `icon` /
149
+ `color` / `popup` styling. A `map` configuration stashed under `filter.map` — a
150
+ shape predating the `map` input — is no longer read either, and says so in the
151
+ console.
240
152
 
241
- ## TypeScript Support
153
+ ## Using `ObjectMap` directly
242
154
 
243
- ```typescript
244
- import type { MapSchema, MapMarker } from '@object-ui/plugin-map';
155
+ `ObjectMap` (the component), `ObjectMapRenderer` (the registered wrapper, for a
156
+ host that registers types itself) and the `ObjectMapProps` type are the package's
157
+ exports:
245
158
 
246
- const marker: MapMarker = {
247
- lat: 37.7749,
248
- lng: -122.4194,
249
- label: 'San Francisco',
250
- color: 'red'
251
- };
159
+ ```tsx
160
+ import { ObjectMap } from '@object-ui/plugin-map';
252
161
 
253
- const map: MapSchema = {
254
- type: 'map',
255
- center: { lat: 37.7749, lng: -122.4194 },
256
- zoom: 12,
257
- markers: [marker]
258
- };
162
+ <ObjectMap
163
+ schema={{ type: 'object-map', objectName: 'stores', map: { latitudeField: 'lat', longitudeField: 'lng' } }}
164
+ dataSource={dataSource}
165
+ onMarkerClick={(record) => console.log(record)}
166
+ />;
259
167
  ```
260
168
 
261
- ## Customization
262
-
263
- Style the map with Tailwind classes:
264
-
265
- ```typescript
266
- const schema = {
267
- type: 'map',
268
- className: 'rounded-lg shadow-xl border-2 border-gray-200',
269
- height: '600px',
270
- center: { lat: 37.7749, lng: -122.4194 }
271
- };
272
- ```
169
+ | Prop | Description |
170
+ | --- | --- |
171
+ | `schema` | The map schema — the keys above. |
172
+ | `dataSource` | Resolves the `object` provider. Not needed for `staticData` or an inline `data` array. |
173
+ | `className` | Classes for the wrapper around the map. |
174
+ | `data` | Records to render directly, bypassing the component's own fetch — the shape `ListView` passes when it already holds the rows. Tracked live: passing a new array after mount (e.g. once a host's own in-flight query resolves) updates the map. |
175
+ | `onMarkerClick` | Called with the clicked record. |
176
+ | `onRowClick` | Record click handler; takes priority over the `navigation` overlay. |
177
+ | `onEdit` / `onDelete` | Passing either adds that button to the marker popup (and to the mobile record sheet). |
178
+ | `enableClustering` | Forces clustering on; without it, clustering starts above 100 visible markers. |
179
+ | `clusterRadius` | Clustering granularity (default `50`): the grid cell is `clusterRadius / 2 ** zoom`, so a larger value groups more aggressively. |
180
+
181
+ In a schema-driven page these handlers may equally be authored on the node
182
+ itself: `SchemaRenderer` spreads a node's non-metadata properties onto the
183
+ component.
273
184
 
274
185
  ## Links
275
186
 
276
- - 📚 [Documentation](https://www.objectui.org/docs/plugins/plugin-map)
187
+ - 📚 [Documentation](https://www.objectui.org/docs/plugins/plugin-map) — the full
188
+ authoring reference for the schema and the `map` block
277
189
  - 📦 [npm package](https://www.npmjs.com/package/@object-ui/plugin-map)
278
190
  - 📝 [Changelog](./CHANGELOG.md)
279
191
  - 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
@@ -1,16 +1,31 @@
1
1
  import { default as React } from 'react';
2
- import { ObjectGridSchema, DataSource } from '@object-ui/types';
2
+ import { ObjectMapSchema, DataSource } from '@object-ui/types';
3
3
  export interface ObjectMapProps {
4
- schema: ObjectGridSchema;
4
+ schema: ObjectMapSchema;
5
5
  dataSource?: DataSource;
6
6
  className?: string;
7
+ /**
8
+ * Records to render directly, bypassing this component's own fetch — the
9
+ * shape `ListView` passes when it already holds the rows. Declared as its
10
+ * own prop (not read off the `rest` spread) so the fetch effect can depend
11
+ * on this one value: naming the whole `rest` object in that effect's deps
12
+ * instead would refetch on every render, since `rest` is a fresh object
13
+ * each render (objectui#5003).
14
+ */
15
+ data?: any[];
7
16
  onMarkerClick?: (record: any) => void;
8
17
  onRowClick?: (record: any) => void;
9
18
  onEdit?: (record: any) => void;
10
19
  onDelete?: (record: any) => void;
11
20
  /** Enable marker clustering for dense data */
12
21
  enableClustering?: boolean;
13
- /** Cluster radius in pixels (default: 50) */
22
+ /**
23
+ * Clustering grid granularity (default: 50). NOT screen pixels — the grid
24
+ * cell edge is `clusterRadius / 2 ** zoom`, in coordinate DEGREES (the
25
+ * marker's `[lng, lat]`), so a larger value groups more aggressively and
26
+ * the effective granularity shrinks exponentially as zoom increases. See
27
+ * `clusterMarkers` below.
28
+ */
14
29
  clusterRadius?: number;
15
30
  }
16
31
  export declare const ObjectMap: React.FC<ObjectMapProps>;
@@ -1 +1 @@
1
- {"version":3,"file":"ObjectMap.d.ts","sourceRoot":"","sources":["../src/ObjectMap.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAY,MAAM,kBAAkB,CAAC;AAO/E,OAAO,kCAAkC,CAAC;AAqB1C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACjC,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6CAA6C;IAC7C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA+SD,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA4d9C,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"ObjectMap.d.ts","sourceRoot":"","sources":["../src/ObjectMap.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAmB,UAAU,EAAY,MAAM,kBAAkB,CAAC;AAY/F,OAAO,kCAAkC,CAAC;AAkB1C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACb,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACjC,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkcD,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA2jB9C,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * Initial-camera derivation for ObjectMap (objectui#4941).
10
+ *
11
+ * A map view whose query returned records must never first-paint an empty
12
+ * viewport. The camera is therefore derived from the records themselves: the
13
+ * marker set's bounding box, handed to MapLibre so it fits the box with the
14
+ * real container size (`initialViewState.bounds` -> the `bounds` /
15
+ * `fitBoundsOptions` pair on the MapLibre `Map` constructor, which overrides
16
+ * `center` / `zoom`).
17
+ *
18
+ * Two things this module deliberately does NOT do:
19
+ *
20
+ * 1. It does not rescue out-of-range record coordinates. The platform's
21
+ * `location` value is `{ lat, lng }` with longitude bounded to [-180, 180]
22
+ * by `@objectstack/spec` (`data/field-value.zod.ts`), so a longitude outside
23
+ * that range is a producer-side defect, not a dialect this renderer should
24
+ * learn to read. `ObjectMap` keeps rejecting such records and counting them
25
+ * in its "invalid coordinates" notice. The normalization here is projection
26
+ * arithmetic over already-valid values, applied to the CAMERA only.
27
+ * 2. It does not introduce any authoring surface. The only camera declaration
28
+ * read anywhere is the documented `zoom` / `center` pair of the map config
29
+ * block (`content/docs/plugins/plugin-map.mdx`), and an author who declares
30
+ * it keeps winning over everything computed here.
31
+ */
32
+ /**
33
+ * `[[west, south], [east, north]]` — the two-corner form MapLibre accepts as
34
+ * `LngLatBoundsLike`. `east` may exceed 180: that is how MapLibre expects a box
35
+ * crossing the antimeridian to be spelled (its own `adjustAntiMeridian` produces
36
+ * exactly this shape), and it is what keeps the fitted camera on the same world
37
+ * copy as the markers.
38
+ */
39
+ export type MarkerBounds = [[number, number], [number, number]];
40
+ /** Breathing room around the fitted box, in pixels. */
41
+ export declare const FIT_PADDING_PX = 48;
42
+ /**
43
+ * Ceiling for the fitted zoom. A single record — or several records at one
44
+ * address — fits a zero-width box, and an unbounded fit answers that with the
45
+ * map's maximum zoom (22): a building-level view of a style whose tiles usually
46
+ * stop far short of it. City scale is the useful answer instead.
47
+ */
48
+ export declare const FIT_MAX_ZOOM = 12;
49
+ /** Zoom used when there is nothing to fit: the whole world, not a random sea. */
50
+ export declare const EMPTY_VIEW_ZOOM = 2;
51
+ /**
52
+ * Zoom used when a partially declared camera pins the position but not the
53
+ * scale, so the box cannot drive the fit. Same value the component used before
54
+ * the fit existed.
55
+ */
56
+ export declare const UNFITTED_CENTER_ZOOM = 3;
57
+ /**
58
+ * Latitude ceiling for the fitted box. Web Mercator is unbounded at the poles,
59
+ * so a record sitting exactly at +/-90 projects to infinity and takes the whole
60
+ * camera with it. Markers themselves stay at their real latitude.
61
+ */
62
+ export declare const MAX_FIT_LATITUDE = 85;
63
+ /**
64
+ * Fold any longitude onto the [-180, 180) circle.
65
+ *
66
+ * In-range values return unchanged rather than through the modulo, which would
67
+ * round-trip them into a neighbouring float (-74.006 came back as
68
+ * -74.00599999999997) and put that drift into every fitted box.
69
+ */
70
+ export declare function normalizeLongitude(lng: number): number;
71
+ /**
72
+ * Bounding box of a marker set, along the SHORTEST arc that contains every
73
+ * marker.
74
+ *
75
+ * Why the shortest arc and not `[min(lng), max(lng)]`: the naive extremes read
76
+ * the circle as a line, so a set straddling the antimeridian (say 179 and -179,
77
+ * two degrees apart) yields a 358-degree-wide box whose midpoint is 0 — the
78
+ * antipode of the data, half a planet away. MapLibre then places the markers in
79
+ * whichever world copy is nearest their previous screen position (`smartWrap`),
80
+ * which is how a fitted-looking camera ends up showing an empty ocean with the
81
+ * records sitting on a neighbouring copy of the world.
82
+ *
83
+ * The widest EMPTY gap between adjacent longitudes is the seam to cut; what
84
+ * remains is the shortest containing arc. `west` is the first marker east of
85
+ * that seam, and the span is measured eastward from it, so the box may extend
86
+ * past 180.
87
+ *
88
+ * @param coordinates marker coordinates in MapLibre order, `[lng, lat]`
89
+ * @returns the box, or `null` when there is nothing to fit
90
+ */
91
+ export declare function computeMarkerBounds(coordinates: ReadonlyArray<readonly [number, number]>): MarkerBounds | null;
92
+ /**
93
+ * Centre of a box from {@link computeMarkerBounds}, for the paths that cannot
94
+ * hand the box to a fit (a declared zoom with no declared centre). The
95
+ * longitude is folded back onto the circle so a box crossing the antimeridian
96
+ * still yields a real centre rather than a value past 180.
97
+ */
98
+ export declare function boundsCenter(bounds: MarkerBounds): {
99
+ longitude: number;
100
+ latitude: number;
101
+ };
102
+ //# sourceMappingURL=camera.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhE,uDAAuD;AACvD,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;GAKG;AACH,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B,iFAAiF;AACjF,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGtD;AAKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACpD,YAAY,GAAG,IAAI,CA8BrB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAM1F"}