@atlas-composer/projection-loader 1.0.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 +409 -0
- package/dist/d3-projection-helpers.d.ts +91 -0
- package/dist/d3-projection-helpers.js +68 -0
- package/dist/d3-projection-helpers.js.map +1 -0
- package/dist/index.d.ts +248 -0
- package/dist/index.js +239 -0
- package/dist/index.js.map +1 -0
- package/package.json +88 -0
package/README.md
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# @atlas-composer/projection-loader
|
|
2
|
+
|
|
3
|
+
> Zero-dependency standalone loader for composite map projections with plugin architecture
|
|
4
|
+
|
|
5
|
+
A lightweight, framework-agnostic library for loading composite map projections exported from [Atlas composer](https://github.com/ShallowRed/atlas-composer). Features a plugin architecture that lets you register only the projections you need, achieving **94% smaller bundle sizes** compared to including all D3 projections.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🎯 **Zero Dependencies** - Bring your own projections (D3, Proj4, or custom)
|
|
10
|
+
- 📦 **Tree-Shakeable** - Only bundle what you use (~6KB vs 100KB)
|
|
11
|
+
- 🔌 **Plugin Architecture** - Register projections on-demand
|
|
12
|
+
- 🌐 **Framework Agnostic** - Works with D3, Observable Plot, React, Vue, Svelte
|
|
13
|
+
- 📘 **Full TypeScript Support** - Complete type definitions included
|
|
14
|
+
- ⚡ **Fast** - Optimized stream multiplexing for efficient rendering
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @atlas-composer/projection-loader d3-geo d3-geo-projection
|
|
20
|
+
# or
|
|
21
|
+
pnpm add @atlas-composer/projection-loader d3-geo d3-geo-projection
|
|
22
|
+
# or
|
|
23
|
+
yarn add @atlas-composer/projection-loader d3-geo d3-geo-projection
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { loadCompositeProjection, registerProjection } from '@atlas-composer/projection-loader'
|
|
30
|
+
import * as d3 from 'd3-geo'
|
|
31
|
+
|
|
32
|
+
// 2. Load your exported configuration
|
|
33
|
+
import config from './france-composite.json'
|
|
34
|
+
|
|
35
|
+
// 1. Register projections (only what you need!)
|
|
36
|
+
registerProjection('mercator', () => d3.geoMercator())
|
|
37
|
+
registerProjection('conic-conformal', () => d3.geoConicConformal())
|
|
38
|
+
|
|
39
|
+
const projection = loadCompositeProjection(config, {
|
|
40
|
+
width: 800,
|
|
41
|
+
height: 600
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// 3. Use with D3
|
|
45
|
+
const path = d3.geoPath(projection)
|
|
46
|
+
|
|
47
|
+
svg.selectAll('path')
|
|
48
|
+
.data(features)
|
|
49
|
+
.join('path')
|
|
50
|
+
.attr('d', path)
|
|
51
|
+
.attr('fill', 'lightgray')
|
|
52
|
+
.attr('stroke', 'white')
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage with Helpers (Convenience)
|
|
56
|
+
|
|
57
|
+
For quick prototyping, use the optional helpers module:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { registerProjections } from '@atlas-composer/projection-loader'
|
|
61
|
+
import { d3ProjectionFactories } from '@atlas-composer/projection-loader/helpers'
|
|
62
|
+
|
|
63
|
+
// Register all standard D3 projections at once
|
|
64
|
+
registerProjections(d3ProjectionFactories)
|
|
65
|
+
|
|
66
|
+
// Now load your configuration
|
|
67
|
+
const projection = loadCompositeProjection(config, { width: 800, height: 600 })
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Tree-Shaking (Production)
|
|
71
|
+
|
|
72
|
+
For optimal bundle sizes, import only what you need:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { loadCompositeProjection, registerProjections } from '@atlas-composer/projection-loader'
|
|
76
|
+
import { geoConicConformal, geoMercator } from 'd3-geo'
|
|
77
|
+
|
|
78
|
+
// Only these two projections will be in your bundle
|
|
79
|
+
registerProjections({
|
|
80
|
+
'mercator': () => geoMercator(),
|
|
81
|
+
'conic-conformal': () => geoConicConformal()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const projection = loadCompositeProjection(config, { width: 800, height: 600 })
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Result**: ~6KB instead of ~100KB (94% reduction) 🎉
|
|
88
|
+
|
|
89
|
+
## Observable Plot Integration
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { loadCompositeProjection, registerProjection } from '@atlas-composer/projection-loader'
|
|
93
|
+
import * as Plot from '@observablehq/plot'
|
|
94
|
+
import * as d3 from 'd3-geo'
|
|
95
|
+
|
|
96
|
+
// Register projections
|
|
97
|
+
registerProjection('mercator', () => d3.geoMercator())
|
|
98
|
+
registerProjection('conic-conformal', () => d3.geoConicConformal())
|
|
99
|
+
|
|
100
|
+
// Create projection factory for Plot
|
|
101
|
+
function createProjection({ width, height }) {
|
|
102
|
+
return loadCompositeProjection(config, { width, height })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Use with Plot
|
|
106
|
+
Plot.plot({
|
|
107
|
+
width: 975,
|
|
108
|
+
height: 610,
|
|
109
|
+
projection: createProjection,
|
|
110
|
+
marks: [
|
|
111
|
+
Plot.geo(countries, { fill: 'lightgray', stroke: 'white' })
|
|
112
|
+
]
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Framework Examples
|
|
117
|
+
|
|
118
|
+
### React
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { loadCompositeProjection, registerProjections } from '@atlas-composer/projection-loader'
|
|
122
|
+
import * as d3 from 'd3-geo'
|
|
123
|
+
import { useEffect, useRef } from 'react'
|
|
124
|
+
|
|
125
|
+
function MapComponent({ config }) {
|
|
126
|
+
const svgRef = useRef<SVGSVGElement>(null)
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
// Register projections once
|
|
130
|
+
registerProjections({
|
|
131
|
+
'mercator': () => d3.geoMercator(),
|
|
132
|
+
'conic-conformal': () => d3.geoConicConformal()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Create projection
|
|
136
|
+
const projection = loadCompositeProjection(config, {
|
|
137
|
+
width: 800,
|
|
138
|
+
height: 600
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Render map
|
|
142
|
+
const path = d3.geoPath(projection)
|
|
143
|
+
const svg = d3.select(svgRef.current)
|
|
144
|
+
|
|
145
|
+
svg.selectAll('path')
|
|
146
|
+
.data(features)
|
|
147
|
+
.join('path')
|
|
148
|
+
.attr('d', path)
|
|
149
|
+
.attr('fill', 'lightgray')
|
|
150
|
+
}, [config])
|
|
151
|
+
|
|
152
|
+
return <svg ref={svgRef} width={800} height={600} />
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Vue 3
|
|
157
|
+
|
|
158
|
+
```vue
|
|
159
|
+
<script setup lang="ts">
|
|
160
|
+
import { loadCompositeProjection, registerProjections } from '@atlas-composer/projection-loader'
|
|
161
|
+
import * as d3 from 'd3-geo'
|
|
162
|
+
import { onMounted, ref } from 'vue'
|
|
163
|
+
|
|
164
|
+
const props = defineProps<{ config: any }>()
|
|
165
|
+
const svgRef = ref<SVGSVGElement>()
|
|
166
|
+
|
|
167
|
+
onMounted(() => {
|
|
168
|
+
registerProjections({
|
|
169
|
+
'mercator': () => d3.geoMercator(),
|
|
170
|
+
'conic-conformal': () => d3.geoConicConformal()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const projection = loadCompositeProjection(props.config, {
|
|
174
|
+
width: 800,
|
|
175
|
+
height: 600
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const path = d3.geoPath(projection)
|
|
179
|
+
const svg = d3.select(svgRef.value)
|
|
180
|
+
|
|
181
|
+
svg.selectAll('path')
|
|
182
|
+
.data(features)
|
|
183
|
+
.join('path')
|
|
184
|
+
.attr('d', path)
|
|
185
|
+
.attr('fill', 'lightgray')
|
|
186
|
+
})
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<template>
|
|
190
|
+
<svg
|
|
191
|
+
ref="svgRef"
|
|
192
|
+
width="800"
|
|
193
|
+
height="600"
|
|
194
|
+
/>
|
|
195
|
+
</template>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Svelte
|
|
199
|
+
|
|
200
|
+
```svelte
|
|
201
|
+
<script lang="ts">
|
|
202
|
+
import * as d3 from 'd3-geo'
|
|
203
|
+
import { onMount } from 'svelte'
|
|
204
|
+
import { loadCompositeProjection, registerProjections } from '@atlas-composer/projection-loader'
|
|
205
|
+
|
|
206
|
+
export let config: any
|
|
207
|
+
|
|
208
|
+
let svgElement: SVGSVGElement
|
|
209
|
+
|
|
210
|
+
onMount(() => {
|
|
211
|
+
registerProjections({
|
|
212
|
+
'mercator': () => d3.geoMercator(),
|
|
213
|
+
'conic-conformal': () => d3.geoConicConformal()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const projection = loadCompositeProjection(config, {
|
|
217
|
+
width: 800,
|
|
218
|
+
height: 600
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const path = d3.geoPath(projection)
|
|
222
|
+
const svg = d3.select(svgElement)
|
|
223
|
+
|
|
224
|
+
svg.selectAll('path')
|
|
225
|
+
.data(features)
|
|
226
|
+
.join('path')
|
|
227
|
+
.attr('d', path)
|
|
228
|
+
.attr('fill', 'lightgray')
|
|
229
|
+
})
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<svg bind:this={svgElement} width="800" height="600" />
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Custom Projections
|
|
236
|
+
|
|
237
|
+
You can register any projection factory, including custom implementations:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { registerProjection } from '@atlas-composer/projection-loader'
|
|
241
|
+
|
|
242
|
+
// Custom projection
|
|
243
|
+
registerProjection('my-custom', () => {
|
|
244
|
+
// Return a D3-compatible projection
|
|
245
|
+
return {
|
|
246
|
+
// Implement projection interface
|
|
247
|
+
(coordinates) => [x, y],
|
|
248
|
+
scale: (s?) => s ? (scale = s, this) : scale,
|
|
249
|
+
translate: (t?) => t ? (translate = t, this) : translate,
|
|
250
|
+
// ... other D3 projection methods
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// Proj4 wrapper
|
|
255
|
+
import proj4 from 'proj4'
|
|
256
|
+
|
|
257
|
+
registerProjection('lambert93', () => {
|
|
258
|
+
const projection = proj4('EPSG:2154')
|
|
259
|
+
return {
|
|
260
|
+
(coords) => projection.forward(coords),
|
|
261
|
+
scale: () => 1,
|
|
262
|
+
translate: () => [0, 0]
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## API Reference
|
|
268
|
+
|
|
269
|
+
### Core Functions
|
|
270
|
+
|
|
271
|
+
#### `registerProjection(id: string, factory: ProjectionFactory): void`
|
|
272
|
+
|
|
273
|
+
Register a projection factory with a given ID.
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
registerProjection('mercator', () => d3.geoMercator())
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### `registerProjections(factories: Record<string, ProjectionFactory>): void`
|
|
280
|
+
|
|
281
|
+
Register multiple projections at once.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
registerProjections({
|
|
285
|
+
mercator: () => d3.geoMercator(),
|
|
286
|
+
albers: () => d3.geoAlbers()
|
|
287
|
+
})
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### `loadCompositeProjection(config: ExportedConfig, options: LoaderOptions): ProjectionLike`
|
|
291
|
+
|
|
292
|
+
Load a composite projection from an exported configuration.
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
const projection = loadCompositeProjection(config, {
|
|
296
|
+
width: 800,
|
|
297
|
+
height: 600,
|
|
298
|
+
enableClipping: true, // optional, default: true
|
|
299
|
+
debug: false // optional, default: false
|
|
300
|
+
})
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### `loadFromJSON(jsonString: string, options: LoaderOptions): ProjectionLike`
|
|
304
|
+
|
|
305
|
+
Load a composite projection from a JSON string.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
const jsonString = fs.readFileSync('config.json', 'utf-8')
|
|
309
|
+
const projection = loadFromJSON(jsonString, { width: 800, height: 600 })
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Utility Functions
|
|
313
|
+
|
|
314
|
+
#### `getRegisteredProjections(): string[]`
|
|
315
|
+
|
|
316
|
+
Get list of registered projection IDs.
|
|
317
|
+
|
|
318
|
+
#### `isProjectionRegistered(id: string): boolean`
|
|
319
|
+
|
|
320
|
+
Check if a projection is registered.
|
|
321
|
+
|
|
322
|
+
#### `unregisterProjection(id: string): boolean`
|
|
323
|
+
|
|
324
|
+
Remove a projection from the registry.
|
|
325
|
+
|
|
326
|
+
#### `clearProjections(): void`
|
|
327
|
+
|
|
328
|
+
Clear all registered projections.
|
|
329
|
+
|
|
330
|
+
#### `validateConfig(config: any): boolean`
|
|
331
|
+
|
|
332
|
+
Validate a configuration object. Throws descriptive errors if invalid.
|
|
333
|
+
|
|
334
|
+
## Configuration Format
|
|
335
|
+
|
|
336
|
+
Exported configurations follow this structure:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
interface ExportedConfig {
|
|
340
|
+
version: '1.0'
|
|
341
|
+
metadata: {
|
|
342
|
+
atlasId: string
|
|
343
|
+
atlasName: string
|
|
344
|
+
}
|
|
345
|
+
pattern: 'single-focus' | 'equal-members'
|
|
346
|
+
referenceScale: number
|
|
347
|
+
territories: Territory[]
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
interface Territory {
|
|
351
|
+
code: string
|
|
352
|
+
name: string
|
|
353
|
+
role: 'primary' | 'secondary' | 'member'
|
|
354
|
+
projectionId: string
|
|
355
|
+
projectionFamily: string
|
|
356
|
+
parameters: {
|
|
357
|
+
center?: [number, number]
|
|
358
|
+
rotate?: [number, number, number]
|
|
359
|
+
parallels?: [number, number]
|
|
360
|
+
scale: number
|
|
361
|
+
baseScale: number
|
|
362
|
+
scaleMultiplier: number
|
|
363
|
+
}
|
|
364
|
+
layout: {
|
|
365
|
+
translateOffset: [number, number]
|
|
366
|
+
clipExtent: [[number, number], [number, number]] | null
|
|
367
|
+
}
|
|
368
|
+
bounds: [[number, number], [number, number]]
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Bundle Size
|
|
373
|
+
|
|
374
|
+
| Approach | Bundle Size | Savings |
|
|
375
|
+
|----------|-------------|---------|
|
|
376
|
+
| All D3 projections | ~100KB | - |
|
|
377
|
+
| With projection-loader | ~6KB | **94%** 🎉 |
|
|
378
|
+
|
|
379
|
+
## TypeScript Support
|
|
380
|
+
|
|
381
|
+
Full TypeScript definitions are included. No need for `@types/*` packages.
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import type {
|
|
385
|
+
ExportedConfig,
|
|
386
|
+
LoaderOptions,
|
|
387
|
+
ProjectionFactory,
|
|
388
|
+
ProjectionLike,
|
|
389
|
+
Territory
|
|
390
|
+
} from '@atlas-composer/projection-loader'
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Browser Support
|
|
394
|
+
|
|
395
|
+
Works in all modern browsers that support ES2020+. For older browsers, transpile with your build tool.
|
|
396
|
+
|
|
397
|
+
## Contributing
|
|
398
|
+
|
|
399
|
+
Contributions are welcome! This package is part of the [Atlas composer](https://github.com/ShallowRed/atlas-composer) monorepo.
|
|
400
|
+
|
|
401
|
+
## License
|
|
402
|
+
|
|
403
|
+
MIT © 2025 Lucas Poulain
|
|
404
|
+
|
|
405
|
+
## Related
|
|
406
|
+
|
|
407
|
+
- [Atlas composer](https://github.com/ShallowRed/atlas-composer) - Create custom composite projections
|
|
408
|
+
- [D3.js](https://d3js.org/) - Data visualization library
|
|
409
|
+
- [Observable Plot](https://observablehq.com/plot/) - High-level plotting library
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { ProjectionFactory } from './index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* D3 Projection Helpers
|
|
5
|
+
*
|
|
6
|
+
* Optional companion file that provides ready-to-use D3 projection factory mappings.
|
|
7
|
+
* This file has dependencies on d3-geo and d3-geo-projection, but the main loader does not.
|
|
8
|
+
*
|
|
9
|
+
* Users can import this to quickly register all standard D3 projections, or they can
|
|
10
|
+
* selectively import only the projections they need for tree-shaking.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Register all projections at once
|
|
15
|
+
* import { registerProjections } from './standalone-projection-loader'
|
|
16
|
+
* import { d3ProjectionFactories } from './d3-projection-helpers'
|
|
17
|
+
*
|
|
18
|
+
* registerProjections(d3ProjectionFactories)
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Tree-shakeable: import only what you need
|
|
24
|
+
* import { registerProjection } from './standalone-projection-loader'
|
|
25
|
+
* import { mercator, albers } from './d3-projection-helpers'
|
|
26
|
+
*
|
|
27
|
+
* registerProjection('mercator', mercator)
|
|
28
|
+
* registerProjection('albers', albers)
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
declare const azimuthalEqualArea: ProjectionFactory;
|
|
35
|
+
declare const azimuthalEquidistant: ProjectionFactory;
|
|
36
|
+
declare const gnomonic: ProjectionFactory;
|
|
37
|
+
declare const orthographic: ProjectionFactory;
|
|
38
|
+
declare const stereographic: ProjectionFactory;
|
|
39
|
+
declare const conicConformal: ProjectionFactory;
|
|
40
|
+
declare const conicEqualArea: ProjectionFactory;
|
|
41
|
+
declare const conicEquidistant: ProjectionFactory;
|
|
42
|
+
declare const albers: ProjectionFactory;
|
|
43
|
+
declare const mercator: ProjectionFactory;
|
|
44
|
+
declare const transverseMercator: ProjectionFactory;
|
|
45
|
+
declare const equirectangular: ProjectionFactory;
|
|
46
|
+
declare const naturalEarth1: ProjectionFactory;
|
|
47
|
+
declare const equalEarth: ProjectionFactory;
|
|
48
|
+
/**
|
|
49
|
+
* Object containing all standard D3 projection factories
|
|
50
|
+
* Keyed by the projection ID used in Atlas Composer configurations
|
|
51
|
+
*/
|
|
52
|
+
declare const d3ProjectionFactories: Record<string, ProjectionFactory>;
|
|
53
|
+
/**
|
|
54
|
+
* Convenience function to register all D3 projections at once
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import { registerProjections } from './standalone-projection-loader'
|
|
59
|
+
* import { registerAllD3Projections } from './d3-projection-helpers'
|
|
60
|
+
*
|
|
61
|
+
* registerAllD3Projections(registerProjections)
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @param registerFn - The registerProjections function from the loader
|
|
65
|
+
*/
|
|
66
|
+
declare function registerAllD3Projections(registerFn: (factories: Record<string, ProjectionFactory>) => void): void;
|
|
67
|
+
/**
|
|
68
|
+
* Get list of available D3 projection IDs
|
|
69
|
+
*/
|
|
70
|
+
declare function getAvailableD3Projections(): string[];
|
|
71
|
+
declare const _default: {
|
|
72
|
+
d3ProjectionFactories: Record<string, ProjectionFactory>;
|
|
73
|
+
registerAllD3Projections: typeof registerAllD3Projections;
|
|
74
|
+
getAvailableD3Projections: typeof getAvailableD3Projections;
|
|
75
|
+
azimuthalEqualArea: ProjectionFactory;
|
|
76
|
+
azimuthalEquidistant: ProjectionFactory;
|
|
77
|
+
gnomonic: ProjectionFactory;
|
|
78
|
+
orthographic: ProjectionFactory;
|
|
79
|
+
stereographic: ProjectionFactory;
|
|
80
|
+
conicConformal: ProjectionFactory;
|
|
81
|
+
conicEqualArea: ProjectionFactory;
|
|
82
|
+
conicEquidistant: ProjectionFactory;
|
|
83
|
+
albers: ProjectionFactory;
|
|
84
|
+
mercator: ProjectionFactory;
|
|
85
|
+
transverseMercator: ProjectionFactory;
|
|
86
|
+
equirectangular: ProjectionFactory;
|
|
87
|
+
naturalEarth1: ProjectionFactory;
|
|
88
|
+
equalEarth: ProjectionFactory;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export { albers, azimuthalEqualArea, azimuthalEquidistant, conicConformal, conicEqualArea, conicEquidistant, d3ProjectionFactories, _default as default, equalEarth, equirectangular, getAvailableD3Projections, gnomonic, mercator, naturalEarth1, orthographic, registerAllD3Projections, stereographic, transverseMercator };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as d3Geo from 'd3-geo';
|
|
2
|
+
import * as d3GeoProjection from 'd3-geo-projection';
|
|
3
|
+
|
|
4
|
+
// src/d3-projection-helpers.ts
|
|
5
|
+
var azimuthalEqualArea = () => d3Geo.geoAzimuthalEqualArea();
|
|
6
|
+
var azimuthalEquidistant = () => d3Geo.geoAzimuthalEquidistant();
|
|
7
|
+
var gnomonic = () => d3Geo.geoGnomonic();
|
|
8
|
+
var orthographic = () => d3Geo.geoOrthographic();
|
|
9
|
+
var stereographic = () => d3Geo.geoStereographic();
|
|
10
|
+
var conicConformal = () => d3Geo.geoConicConformal();
|
|
11
|
+
var conicEqualArea = () => d3Geo.geoConicEqualArea();
|
|
12
|
+
var conicEquidistant = () => d3Geo.geoConicEquidistant();
|
|
13
|
+
var albers = () => d3Geo.geoAlbers();
|
|
14
|
+
var mercator = () => d3Geo.geoMercator();
|
|
15
|
+
var transverseMercator = () => d3Geo.geoTransverseMercator();
|
|
16
|
+
var equirectangular = () => d3Geo.geoEquirectangular();
|
|
17
|
+
var naturalEarth1 = () => d3GeoProjection.geoNaturalEarth1();
|
|
18
|
+
var equalEarth = () => d3Geo.geoEqualEarth();
|
|
19
|
+
var d3ProjectionFactories = {
|
|
20
|
+
// Azimuthal
|
|
21
|
+
"azimuthal-equal-area": azimuthalEqualArea,
|
|
22
|
+
"azimuthal-equidistant": azimuthalEquidistant,
|
|
23
|
+
"gnomonic": gnomonic,
|
|
24
|
+
"orthographic": orthographic,
|
|
25
|
+
"stereographic": stereographic,
|
|
26
|
+
// Conic
|
|
27
|
+
"conic-conformal": conicConformal,
|
|
28
|
+
"conic-equal-area": conicEqualArea,
|
|
29
|
+
"conic-equidistant": conicEquidistant,
|
|
30
|
+
"albers": albers,
|
|
31
|
+
// Cylindrical
|
|
32
|
+
"mercator": mercator,
|
|
33
|
+
"transverse-mercator": transverseMercator,
|
|
34
|
+
"equirectangular": equirectangular,
|
|
35
|
+
"natural-earth-1": naturalEarth1,
|
|
36
|
+
// Other
|
|
37
|
+
"equal-earth": equalEarth
|
|
38
|
+
};
|
|
39
|
+
function registerAllD3Projections(registerFn) {
|
|
40
|
+
registerFn(d3ProjectionFactories);
|
|
41
|
+
}
|
|
42
|
+
function getAvailableD3Projections() {
|
|
43
|
+
return Object.keys(d3ProjectionFactories);
|
|
44
|
+
}
|
|
45
|
+
var d3_projection_helpers_default = {
|
|
46
|
+
d3ProjectionFactories,
|
|
47
|
+
registerAllD3Projections,
|
|
48
|
+
getAvailableD3Projections,
|
|
49
|
+
// Individual projections for tree-shaking
|
|
50
|
+
azimuthalEqualArea,
|
|
51
|
+
azimuthalEquidistant,
|
|
52
|
+
gnomonic,
|
|
53
|
+
orthographic,
|
|
54
|
+
stereographic,
|
|
55
|
+
conicConformal,
|
|
56
|
+
conicEqualArea,
|
|
57
|
+
conicEquidistant,
|
|
58
|
+
albers,
|
|
59
|
+
mercator,
|
|
60
|
+
transverseMercator,
|
|
61
|
+
equirectangular,
|
|
62
|
+
naturalEarth1,
|
|
63
|
+
equalEarth
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { albers, azimuthalEqualArea, azimuthalEquidistant, conicConformal, conicEqualArea, conicEquidistant, d3ProjectionFactories, d3_projection_helpers_default as default, equalEarth, equirectangular, getAvailableD3Projections, gnomonic, mercator, naturalEarth1, orthographic, registerAllD3Projections, stereographic, transverseMercator };
|
|
67
|
+
//# sourceMappingURL=d3-projection-helpers.js.map
|
|
68
|
+
//# sourceMappingURL=d3-projection-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/d3-projection-helpers.ts"],"names":[],"mappings":";;;;AAoCO,IAAM,kBAAA,GAAwC,MAAY,KAAA,CAAA,qBAAA;AAC1D,IAAM,oBAAA,GAA0C,MAAY,KAAA,CAAA,uBAAA;AAC5D,IAAM,QAAA,GAA8B,MAAY,KAAA,CAAA,WAAA;AAChD,IAAM,YAAA,GAAkC,MAAY,KAAA,CAAA,eAAA;AACpD,IAAM,aAAA,GAAmC,MAAY,KAAA,CAAA,gBAAA;AAGrD,IAAM,cAAA,GAAoC,MAAY,KAAA,CAAA,iBAAA;AACtD,IAAM,cAAA,GAAoC,MAAY,KAAA,CAAA,iBAAA;AACtD,IAAM,gBAAA,GAAsC,MAAY,KAAA,CAAA,mBAAA;AACxD,IAAM,MAAA,GAA4B,MAAY,KAAA,CAAA,SAAA;AAG9C,IAAM,QAAA,GAA8B,MAAY,KAAA,CAAA,WAAA;AAChD,IAAM,kBAAA,GAAwC,MAAY,KAAA,CAAA,qBAAA;AAC1D,IAAM,eAAA,GAAqC,MAAY,KAAA,CAAA,kBAAA;AACvD,IAAM,aAAA,GAAmC,MAA+B,eAAA,CAAA,gBAAA;AAGxE,IAAM,UAAA,GAAgC,MAAY,KAAA,CAAA,aAAA;AAMlD,IAAM,qBAAA,GAA2D;AAAA;AAAA,EAEtE,sBAAA,EAAwB,kBAAA;AAAA,EACxB,uBAAA,EAAyB,oBAAA;AAAA,EACzB,UAAA,EAAY,QAAA;AAAA,EACZ,cAAA,EAAgB,YAAA;AAAA,EAChB,eAAA,EAAiB,aAAA;AAAA;AAAA,EAGjB,iBAAA,EAAmB,cAAA;AAAA,EACnB,kBAAA,EAAoB,cAAA;AAAA,EACpB,mBAAA,EAAqB,gBAAA;AAAA,EACrB,QAAA,EAAU,MAAA;AAAA;AAAA,EAGV,UAAA,EAAY,QAAA;AAAA,EACZ,qBAAA,EAAuB,kBAAA;AAAA,EACvB,iBAAA,EAAmB,eAAA;AAAA,EACnB,iBAAA,EAAmB,aAAA;AAAA;AAAA,EAGnB,aAAA,EAAe;AACjB;AAeO,SAAS,yBACd,UAAA,EACM;AACN,EAAA,UAAA,CAAW,qBAAqB,CAAA;AAClC;AAKO,SAAS,yBAAA,GAAsC;AACpD,EAAA,OAAO,MAAA,CAAO,KAAK,qBAAqB,CAAA;AAC1C;AAGA,IAAO,6BAAA,GAAQ;AAAA,EACb,qBAAA;AAAA,EACA,wBAAA;AAAA,EACA,yBAAA;AAAA;AAAA,EAGA,kBAAA;AAAA,EACA,oBAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF","file":"d3-projection-helpers.js","sourcesContent":["/**\n * D3 Projection Helpers\n *\n * Optional companion file that provides ready-to-use D3 projection factory mappings.\n * This file has dependencies on d3-geo and d3-geo-projection, but the main loader does not.\n *\n * Users can import this to quickly register all standard D3 projections, or they can\n * selectively import only the projections they need for tree-shaking.\n *\n * @example\n * ```typescript\n * // Register all projections at once\n * import { registerProjections } from './standalone-projection-loader'\n * import { d3ProjectionFactories } from './d3-projection-helpers'\n *\n * registerProjections(d3ProjectionFactories)\n * ```\n *\n * @example\n * ```typescript\n * // Tree-shakeable: import only what you need\n * import { registerProjection } from './standalone-projection-loader'\n * import { mercator, albers } from './d3-projection-helpers'\n *\n * registerProjection('mercator', mercator)\n * registerProjection('albers', albers)\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { ProjectionFactory } from './standalone-projection-loader'\nimport * as d3Geo from 'd3-geo'\nimport * as d3GeoProjection from 'd3-geo-projection'\n\n// Azimuthal projections\nexport const azimuthalEqualArea: ProjectionFactory = () => d3Geo.geoAzimuthalEqualArea()\nexport const azimuthalEquidistant: ProjectionFactory = () => d3Geo.geoAzimuthalEquidistant()\nexport const gnomonic: ProjectionFactory = () => d3Geo.geoGnomonic()\nexport const orthographic: ProjectionFactory = () => d3Geo.geoOrthographic()\nexport const stereographic: ProjectionFactory = () => d3Geo.geoStereographic()\n\n// Conic projections\nexport const conicConformal: ProjectionFactory = () => d3Geo.geoConicConformal()\nexport const conicEqualArea: ProjectionFactory = () => d3Geo.geoConicEqualArea()\nexport const conicEquidistant: ProjectionFactory = () => d3Geo.geoConicEquidistant()\nexport const albers: ProjectionFactory = () => d3Geo.geoAlbers()\n\n// Cylindrical projections\nexport const mercator: ProjectionFactory = () => d3Geo.geoMercator()\nexport const transverseMercator: ProjectionFactory = () => d3Geo.geoTransverseMercator()\nexport const equirectangular: ProjectionFactory = () => d3Geo.geoEquirectangular()\nexport const naturalEarth1: ProjectionFactory = () => (d3GeoProjection as any).geoNaturalEarth1()\n\n// Other projections\nexport const equalEarth: ProjectionFactory = () => d3Geo.geoEqualEarth()\n\n/**\n * Object containing all standard D3 projection factories\n * Keyed by the projection ID used in Atlas Composer configurations\n */\nexport const d3ProjectionFactories: Record<string, ProjectionFactory> = {\n // Azimuthal\n 'azimuthal-equal-area': azimuthalEqualArea,\n 'azimuthal-equidistant': azimuthalEquidistant,\n 'gnomonic': gnomonic,\n 'orthographic': orthographic,\n 'stereographic': stereographic,\n\n // Conic\n 'conic-conformal': conicConformal,\n 'conic-equal-area': conicEqualArea,\n 'conic-equidistant': conicEquidistant,\n 'albers': albers,\n\n // Cylindrical\n 'mercator': mercator,\n 'transverse-mercator': transverseMercator,\n 'equirectangular': equirectangular,\n 'natural-earth-1': naturalEarth1,\n\n // Other\n 'equal-earth': equalEarth,\n}\n\n/**\n * Convenience function to register all D3 projections at once\n *\n * @example\n * ```typescript\n * import { registerProjections } from './standalone-projection-loader'\n * import { registerAllD3Projections } from './d3-projection-helpers'\n *\n * registerAllD3Projections(registerProjections)\n * ```\n *\n * @param registerFn - The registerProjections function from the loader\n */\nexport function registerAllD3Projections(\n registerFn: (factories: Record<string, ProjectionFactory>) => void,\n): void {\n registerFn(d3ProjectionFactories)\n}\n\n/**\n * Get list of available D3 projection IDs\n */\nexport function getAvailableD3Projections(): string[] {\n return Object.keys(d3ProjectionFactories)\n}\n\n// Default export\nexport default {\n d3ProjectionFactories,\n registerAllD3Projections,\n getAvailableD3Projections,\n\n // Individual projections for tree-shaking\n azimuthalEqualArea,\n azimuthalEquidistant,\n gnomonic,\n orthographic,\n stereographic,\n conicConformal,\n conicEqualArea,\n conicEquidistant,\n albers,\n mercator,\n transverseMercator,\n equirectangular,\n naturalEarth1,\n equalEarth,\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone Composite Projection Loader (Zero Dependencies)
|
|
3
|
+
*
|
|
4
|
+
* A pure JavaScript/TypeScript module that consumes exported composite projection
|
|
5
|
+
* configurations and creates D3-compatible projections using a plugin architecture.
|
|
6
|
+
*
|
|
7
|
+
* This package has ZERO dependencies. Users must register projection factories
|
|
8
|
+
* before loading configurations.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // Register projections first
|
|
13
|
+
* import * as d3 from 'd3-geo'
|
|
14
|
+
* import { registerProjection, loadCompositeProjection } from './standalone-projection-loader'
|
|
15
|
+
*
|
|
16
|
+
* registerProjection('mercator', () => d3.geoMercator())
|
|
17
|
+
* registerProjection('albers', () => d3.geoAlbers())
|
|
18
|
+
*
|
|
19
|
+
* // Then load your configuration
|
|
20
|
+
* const projection = loadCompositeProjection(config, { width: 800, height: 600 })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @packageDocumentation
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Generic projection-like interface that matches D3 projections
|
|
27
|
+
* without requiring d3-geo as a dependency
|
|
28
|
+
*
|
|
29
|
+
* Note: D3 projections use getter/setter pattern where calling without
|
|
30
|
+
* arguments returns the current value, and with arguments sets and returns this.
|
|
31
|
+
*/
|
|
32
|
+
interface ProjectionLike {
|
|
33
|
+
(coordinates: [number, number]): [number, number] | null;
|
|
34
|
+
center?: {
|
|
35
|
+
(): [number, number];
|
|
36
|
+
(center: [number, number]): ProjectionLike;
|
|
37
|
+
};
|
|
38
|
+
rotate?: {
|
|
39
|
+
(): [number, number, number];
|
|
40
|
+
(angles: [number, number, number]): ProjectionLike;
|
|
41
|
+
};
|
|
42
|
+
parallels?: {
|
|
43
|
+
(): [number, number];
|
|
44
|
+
(parallels: [number, number]): ProjectionLike;
|
|
45
|
+
};
|
|
46
|
+
scale?: {
|
|
47
|
+
(): number;
|
|
48
|
+
(scale: number): ProjectionLike;
|
|
49
|
+
};
|
|
50
|
+
translate?: {
|
|
51
|
+
(): [number, number];
|
|
52
|
+
(translate: [number, number]): ProjectionLike;
|
|
53
|
+
};
|
|
54
|
+
clipExtent?: {
|
|
55
|
+
(): [[number, number], [number, number]] | null;
|
|
56
|
+
(extent: [[number, number], [number, number]] | null): ProjectionLike;
|
|
57
|
+
};
|
|
58
|
+
stream?: (stream: StreamLike) => StreamLike;
|
|
59
|
+
precision?: {
|
|
60
|
+
(): number;
|
|
61
|
+
(precision: number): ProjectionLike;
|
|
62
|
+
};
|
|
63
|
+
fitExtent?: (extent: [[number, number], [number, number]], object: any) => ProjectionLike;
|
|
64
|
+
fitSize?: (size: [number, number], object: any) => ProjectionLike;
|
|
65
|
+
fitWidth?: (width: number, object: any) => ProjectionLike;
|
|
66
|
+
fitHeight?: (height: number, object: any) => ProjectionLike;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Stream protocol interface for D3 geographic transforms
|
|
70
|
+
*/
|
|
71
|
+
interface StreamLike {
|
|
72
|
+
point: (x: number, y: number) => void;
|
|
73
|
+
lineStart: () => void;
|
|
74
|
+
lineEnd: () => void;
|
|
75
|
+
polygonStart: () => void;
|
|
76
|
+
polygonEnd: () => void;
|
|
77
|
+
sphere?: () => void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Factory function that creates a projection instance
|
|
81
|
+
*/
|
|
82
|
+
type ProjectionFactory = () => ProjectionLike;
|
|
83
|
+
/**
|
|
84
|
+
* Exported configuration format (subset needed for loading)
|
|
85
|
+
*/
|
|
86
|
+
interface ExportedConfig {
|
|
87
|
+
version: string;
|
|
88
|
+
metadata: {
|
|
89
|
+
atlasId: string;
|
|
90
|
+
atlasName: string;
|
|
91
|
+
};
|
|
92
|
+
pattern: string;
|
|
93
|
+
referenceScale: number;
|
|
94
|
+
territories: Territory[];
|
|
95
|
+
}
|
|
96
|
+
interface Territory {
|
|
97
|
+
code: string;
|
|
98
|
+
name: string;
|
|
99
|
+
role: string;
|
|
100
|
+
projectionId: string;
|
|
101
|
+
projectionFamily: string;
|
|
102
|
+
parameters: ProjectionParameters;
|
|
103
|
+
layout: Layout;
|
|
104
|
+
bounds: [[number, number], [number, number]];
|
|
105
|
+
}
|
|
106
|
+
interface ProjectionParameters {
|
|
107
|
+
center?: [number, number];
|
|
108
|
+
rotate?: [number, number, number];
|
|
109
|
+
scale: number;
|
|
110
|
+
baseScale: number;
|
|
111
|
+
scaleMultiplier: number;
|
|
112
|
+
parallels?: [number, number];
|
|
113
|
+
}
|
|
114
|
+
interface Layout {
|
|
115
|
+
translateOffset: [number, number];
|
|
116
|
+
clipExtent: [[number, number], [number, number]] | null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Options for creating the composite projection
|
|
120
|
+
*/
|
|
121
|
+
interface LoaderOptions {
|
|
122
|
+
/** Canvas width in pixels */
|
|
123
|
+
width: number;
|
|
124
|
+
/** Canvas height in pixels */
|
|
125
|
+
height: number;
|
|
126
|
+
/** Whether to apply clipping to territories (default: true) */
|
|
127
|
+
enableClipping?: boolean;
|
|
128
|
+
/** Debug mode - logs territory selection (default: false) */
|
|
129
|
+
debug?: boolean;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Register a projection factory with a given ID
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* import * as d3 from 'd3-geo'
|
|
137
|
+
* import { registerProjection } from '@atlas-composer/projection-loader'
|
|
138
|
+
*
|
|
139
|
+
* registerProjection('mercator', () => d3.geoMercator())
|
|
140
|
+
* registerProjection('albers', () => d3.geoAlbers())
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @param id - Projection identifier (e.g., 'mercator', 'albers')
|
|
144
|
+
* @param factory - Function that creates a new projection instance
|
|
145
|
+
*/
|
|
146
|
+
declare function registerProjection(id: string, factory: ProjectionFactory): void;
|
|
147
|
+
/**
|
|
148
|
+
* Register multiple projections at once
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* import * as d3 from 'd3-geo'
|
|
153
|
+
* import { registerProjections } from '@atlas-composer/projection-loader'
|
|
154
|
+
*
|
|
155
|
+
* registerProjections({
|
|
156
|
+
* 'mercator': () => d3.geoMercator(),
|
|
157
|
+
* 'albers': () => d3.geoAlbers(),
|
|
158
|
+
* 'conic-equal-area': () => d3.geoConicEqualArea()
|
|
159
|
+
* })
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* @param factories - Object mapping projection IDs to factory functions
|
|
163
|
+
*/
|
|
164
|
+
declare function registerProjections(factories: Record<string, ProjectionFactory>): void;
|
|
165
|
+
/**
|
|
166
|
+
* Unregister a projection
|
|
167
|
+
*
|
|
168
|
+
* @param id - Projection identifier to remove
|
|
169
|
+
* @returns True if the projection was removed, false if it wasn't registered
|
|
170
|
+
*/
|
|
171
|
+
declare function unregisterProjection(id: string): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Clear all registered projections
|
|
174
|
+
*/
|
|
175
|
+
declare function clearProjections(): void;
|
|
176
|
+
/**
|
|
177
|
+
* Get list of currently registered projection IDs
|
|
178
|
+
*
|
|
179
|
+
* @returns Array of registered projection identifiers
|
|
180
|
+
*/
|
|
181
|
+
declare function getRegisteredProjections(): string[];
|
|
182
|
+
/**
|
|
183
|
+
* Check if a projection is registered
|
|
184
|
+
*
|
|
185
|
+
* @param id - Projection identifier to check
|
|
186
|
+
* @returns True if the projection is registered
|
|
187
|
+
*/
|
|
188
|
+
declare function isProjectionRegistered(id: string): boolean;
|
|
189
|
+
/**
|
|
190
|
+
* Create a D3-compatible projection from an exported composite projection configuration
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* import * as d3 from 'd3-geo'
|
|
195
|
+
* import { registerProjection, loadCompositeProjection } from '@atlas-composer/projection-loader'
|
|
196
|
+
*
|
|
197
|
+
* // Register projections first
|
|
198
|
+
* registerProjection('mercator', () => d3.geoMercator())
|
|
199
|
+
* registerProjection('albers', () => d3.geoAlbers())
|
|
200
|
+
*
|
|
201
|
+
* // Load configuration
|
|
202
|
+
* const config = JSON.parse(jsonString)
|
|
203
|
+
*
|
|
204
|
+
* // Create projection
|
|
205
|
+
* const projection = loadCompositeProjection(config, {
|
|
206
|
+
* width: 800,
|
|
207
|
+
* height: 600
|
|
208
|
+
* })
|
|
209
|
+
*
|
|
210
|
+
* // Use with D3
|
|
211
|
+
* const path = d3.geoPath(projection)
|
|
212
|
+
* svg.selectAll('path')
|
|
213
|
+
* .data(countries.features)
|
|
214
|
+
* .join('path')
|
|
215
|
+
* .attr('d', path)
|
|
216
|
+
* ```
|
|
217
|
+
*
|
|
218
|
+
* @param config - Exported composite projection configuration
|
|
219
|
+
* @param options - Canvas dimensions and options
|
|
220
|
+
* @returns D3-compatible projection that routes geometry to appropriate sub-projections
|
|
221
|
+
*/
|
|
222
|
+
declare function loadCompositeProjection(config: ExportedConfig, options: LoaderOptions): ProjectionLike;
|
|
223
|
+
/**
|
|
224
|
+
* Validate an exported configuration
|
|
225
|
+
*
|
|
226
|
+
* @param config - Configuration to validate
|
|
227
|
+
* @returns True if valid, throws error otherwise
|
|
228
|
+
*/
|
|
229
|
+
declare function validateConfig(config: any): config is ExportedConfig;
|
|
230
|
+
/**
|
|
231
|
+
* Load composite projection from JSON string
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* import * as d3 from 'd3-geo'
|
|
236
|
+
* import { registerProjection, loadFromJSON } from '@atlas-composer/projection-loader'
|
|
237
|
+
*
|
|
238
|
+
* // Register projections first
|
|
239
|
+
* registerProjection('mercator', () => d3.geoMercator())
|
|
240
|
+
*
|
|
241
|
+
* // Load from JSON
|
|
242
|
+
* const jsonString = fs.readFileSync('france-composite.json', 'utf-8')
|
|
243
|
+
* const projection = loadFromJSON(jsonString, { width: 800, height: 600 })
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
declare function loadFromJSON(jsonString: string, options: LoaderOptions): ProjectionLike;
|
|
247
|
+
|
|
248
|
+
export { type ExportedConfig, type Layout, type LoaderOptions, type ProjectionFactory, type ProjectionLike, type ProjectionParameters, type StreamLike, type Territory, clearProjections, getRegisteredProjections, isProjectionRegistered, loadCompositeProjection, loadFromJSON, registerProjection, registerProjections, unregisterProjection, validateConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// src/standalone-projection-loader.ts
|
|
2
|
+
var projectionRegistry = /* @__PURE__ */ new Map();
|
|
3
|
+
function registerProjection(id, factory) {
|
|
4
|
+
projectionRegistry.set(id, factory);
|
|
5
|
+
}
|
|
6
|
+
function registerProjections(factories) {
|
|
7
|
+
for (const [id, factory] of Object.entries(factories)) {
|
|
8
|
+
registerProjection(id, factory);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function unregisterProjection(id) {
|
|
12
|
+
return projectionRegistry.delete(id);
|
|
13
|
+
}
|
|
14
|
+
function clearProjections() {
|
|
15
|
+
projectionRegistry.clear();
|
|
16
|
+
}
|
|
17
|
+
function getRegisteredProjections() {
|
|
18
|
+
return Array.from(projectionRegistry.keys());
|
|
19
|
+
}
|
|
20
|
+
function isProjectionRegistered(id) {
|
|
21
|
+
return projectionRegistry.has(id);
|
|
22
|
+
}
|
|
23
|
+
function createProjectionWrapper(project) {
|
|
24
|
+
let _scale = 150;
|
|
25
|
+
let _translate = [480, 250];
|
|
26
|
+
const projection = function(coordinates) {
|
|
27
|
+
const point = project(coordinates[0] * Math.PI / 180, coordinates[1] * Math.PI / 180);
|
|
28
|
+
if (!point)
|
|
29
|
+
return null;
|
|
30
|
+
return [point[0] * _scale + _translate[0], point[1] * _scale + _translate[1]];
|
|
31
|
+
};
|
|
32
|
+
projection.scale = ((s) => {
|
|
33
|
+
if (arguments.length === 0)
|
|
34
|
+
return _scale;
|
|
35
|
+
_scale = s;
|
|
36
|
+
return projection;
|
|
37
|
+
});
|
|
38
|
+
projection.translate = ((t) => {
|
|
39
|
+
if (arguments.length === 0)
|
|
40
|
+
return _translate;
|
|
41
|
+
_translate = t;
|
|
42
|
+
return projection;
|
|
43
|
+
});
|
|
44
|
+
return projection;
|
|
45
|
+
}
|
|
46
|
+
function loadCompositeProjection(config, options) {
|
|
47
|
+
const { width, height, debug = false } = options;
|
|
48
|
+
if (config.version !== "1.0") {
|
|
49
|
+
throw new Error(`Unsupported configuration version: ${config.version}`);
|
|
50
|
+
}
|
|
51
|
+
if (!config.territories || config.territories.length === 0) {
|
|
52
|
+
throw new Error("Configuration must contain at least one territory");
|
|
53
|
+
}
|
|
54
|
+
const subProjections = config.territories.map((territory) => {
|
|
55
|
+
const proj = createSubProjection(territory, width, height);
|
|
56
|
+
return {
|
|
57
|
+
territory,
|
|
58
|
+
projection: proj,
|
|
59
|
+
bounds: territory.bounds
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
if (debug) {
|
|
63
|
+
console.log("[CompositeProjection] Created sub-projections:", {
|
|
64
|
+
territories: config.territories.map((t) => ({ code: t.code, name: t.name })),
|
|
65
|
+
count: subProjections.length
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const compositeProjection = createProjectionWrapper((lambda, phi) => {
|
|
69
|
+
const lon = lambda * 180 / Math.PI;
|
|
70
|
+
const lat = phi * 180 / Math.PI;
|
|
71
|
+
let selectedProj = null;
|
|
72
|
+
for (const { projection, bounds } of subProjections) {
|
|
73
|
+
if (lon >= bounds[0][0] && lon <= bounds[1][0] && lat >= bounds[0][1] && lat <= bounds[1][1]) {
|
|
74
|
+
selectedProj = projection;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!selectedProj && subProjections[0]) {
|
|
79
|
+
selectedProj = subProjections[0].projection;
|
|
80
|
+
}
|
|
81
|
+
return selectedProj([lambda, phi]);
|
|
82
|
+
});
|
|
83
|
+
compositeProjection.stream = function(stream) {
|
|
84
|
+
let activeStream = null;
|
|
85
|
+
let bufferedPoints = [];
|
|
86
|
+
let activeTerritoryCode = "";
|
|
87
|
+
return {
|
|
88
|
+
point(lon, lat) {
|
|
89
|
+
bufferedPoints.push([lon, lat]);
|
|
90
|
+
if (activeStream) {
|
|
91
|
+
const lonDeg = lon * 180 / Math.PI;
|
|
92
|
+
const latDeg = lat * 180 / Math.PI;
|
|
93
|
+
if (debug) {
|
|
94
|
+
console.log(`[Stream] Point: [${lonDeg.toFixed(2)}, ${latDeg.toFixed(2)}] \u2192 ${activeTerritoryCode}`);
|
|
95
|
+
}
|
|
96
|
+
activeStream.point(lon, lat);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
lineStart() {
|
|
100
|
+
if (bufferedPoints.length > 0 && bufferedPoints[0]) {
|
|
101
|
+
const [lon, lat] = bufferedPoints[0];
|
|
102
|
+
const lonDeg = lon * 180 / Math.PI;
|
|
103
|
+
const latDeg = lat * 180 / Math.PI;
|
|
104
|
+
for (const { territory, projection, bounds } of subProjections) {
|
|
105
|
+
if (lonDeg >= bounds[0][0] && lonDeg <= bounds[1][0] && latDeg >= bounds[0][1] && latDeg <= bounds[1][1]) {
|
|
106
|
+
if (projection.stream) {
|
|
107
|
+
activeStream = projection.stream(stream);
|
|
108
|
+
activeTerritoryCode = territory.code;
|
|
109
|
+
}
|
|
110
|
+
if (debug) {
|
|
111
|
+
console.log(`[Stream] Line started in territory: ${territory.code}`);
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!activeStream && subProjections[0]) {
|
|
117
|
+
const firstProj = subProjections[0].projection;
|
|
118
|
+
if (firstProj.stream) {
|
|
119
|
+
activeStream = firstProj.stream(stream);
|
|
120
|
+
activeTerritoryCode = subProjections[0].territory.code;
|
|
121
|
+
}
|
|
122
|
+
if (debug) {
|
|
123
|
+
console.log(`[Stream] Line started (fallback): ${activeTerritoryCode}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (activeStream) {
|
|
128
|
+
activeStream.lineStart();
|
|
129
|
+
for (const [lon, lat] of bufferedPoints) {
|
|
130
|
+
activeStream.point(lon, lat);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
bufferedPoints = [];
|
|
134
|
+
},
|
|
135
|
+
lineEnd() {
|
|
136
|
+
if (activeStream) {
|
|
137
|
+
activeStream.lineEnd();
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
polygonStart() {
|
|
141
|
+
bufferedPoints = [];
|
|
142
|
+
activeStream = null;
|
|
143
|
+
},
|
|
144
|
+
polygonEnd() {
|
|
145
|
+
if (activeStream) {
|
|
146
|
+
activeStream.polygonEnd();
|
|
147
|
+
}
|
|
148
|
+
activeStream = null;
|
|
149
|
+
},
|
|
150
|
+
sphere() {
|
|
151
|
+
if (debug) {
|
|
152
|
+
console.warn("[Stream] sphere() called - not supported in composite projections");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
if (compositeProjection.scale) {
|
|
158
|
+
compositeProjection.scale(1);
|
|
159
|
+
}
|
|
160
|
+
if (compositeProjection.translate) {
|
|
161
|
+
compositeProjection.translate([width / 2, height / 2]);
|
|
162
|
+
}
|
|
163
|
+
return compositeProjection;
|
|
164
|
+
}
|
|
165
|
+
function createSubProjection(territory, width, height) {
|
|
166
|
+
const { projectionId, parameters, layout } = territory;
|
|
167
|
+
const factory = projectionRegistry.get(projectionId);
|
|
168
|
+
if (!factory) {
|
|
169
|
+
const registered = getRegisteredProjections();
|
|
170
|
+
const availableList = registered.length > 0 ? registered.join(", ") : "none";
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Projection "${projectionId}" is not registered. Available projections: ${availableList}. Use registerProjection('${projectionId}', factory) to register it.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const projection = factory();
|
|
176
|
+
if (parameters.center && projection.center) {
|
|
177
|
+
projection.center(parameters.center);
|
|
178
|
+
}
|
|
179
|
+
if (parameters.rotate && projection.rotate) {
|
|
180
|
+
projection.rotate(parameters.rotate);
|
|
181
|
+
}
|
|
182
|
+
if (parameters.parallels && projection.parallels) {
|
|
183
|
+
projection.parallels(parameters.parallels);
|
|
184
|
+
}
|
|
185
|
+
if (projection.scale) {
|
|
186
|
+
projection.scale(parameters.scale);
|
|
187
|
+
}
|
|
188
|
+
if (projection.translate) {
|
|
189
|
+
const [offsetX, offsetY] = layout.translateOffset;
|
|
190
|
+
projection.translate([
|
|
191
|
+
width / 2 + offsetX,
|
|
192
|
+
height / 2 + offsetY
|
|
193
|
+
]);
|
|
194
|
+
}
|
|
195
|
+
if (layout.clipExtent && projection.clipExtent) {
|
|
196
|
+
projection.clipExtent(layout.clipExtent);
|
|
197
|
+
}
|
|
198
|
+
return projection;
|
|
199
|
+
}
|
|
200
|
+
function validateConfig(config) {
|
|
201
|
+
if (!config || typeof config !== "object") {
|
|
202
|
+
throw new Error("Configuration must be an object");
|
|
203
|
+
}
|
|
204
|
+
if (!config.version) {
|
|
205
|
+
throw new Error("Configuration must have a version field");
|
|
206
|
+
}
|
|
207
|
+
if (!config.metadata || !config.metadata.atlasId) {
|
|
208
|
+
throw new Error("Configuration must have metadata with atlasId");
|
|
209
|
+
}
|
|
210
|
+
if (!config.territories || !Array.isArray(config.territories)) {
|
|
211
|
+
throw new Error("Configuration must have territories array");
|
|
212
|
+
}
|
|
213
|
+
if (config.territories.length === 0) {
|
|
214
|
+
throw new Error("Configuration must have at least one territory");
|
|
215
|
+
}
|
|
216
|
+
for (const territory of config.territories) {
|
|
217
|
+
if (!territory.code || !territory.projectionId) {
|
|
218
|
+
throw new Error(`Territory missing required fields: ${JSON.stringify(territory)}`);
|
|
219
|
+
}
|
|
220
|
+
if (!territory.parameters || !territory.bounds) {
|
|
221
|
+
throw new Error(`Territory ${territory.code} missing parameters or bounds`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
function loadFromJSON(jsonString, options) {
|
|
227
|
+
let config;
|
|
228
|
+
try {
|
|
229
|
+
config = JSON.parse(jsonString);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
232
|
+
}
|
|
233
|
+
validateConfig(config);
|
|
234
|
+
return loadCompositeProjection(config, options);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export { clearProjections, getRegisteredProjections, isProjectionRegistered, loadCompositeProjection, loadFromJSON, registerProjection, registerProjections, unregisterProjection, validateConfig };
|
|
238
|
+
//# sourceMappingURL=index.js.map
|
|
239
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/standalone-projection-loader.ts"],"names":[],"mappings":";AA+IA,IAAM,kBAAA,uBAAyB,GAAA,EAA+B;AAiBvD,SAAS,kBAAA,CAAmB,IAAY,OAAA,EAAkC;AAC/E,EAAA,kBAAA,CAAmB,GAAA,CAAI,IAAI,OAAO,CAAA;AACpC;AAmBO,SAAS,oBAAoB,SAAA,EAAoD;AACtF,EAAA,KAAA,MAAW,CAAC,EAAA,EAAI,OAAO,KAAK,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAA,EAAG;AACrD,IAAA,kBAAA,CAAmB,IAAI,OAAO,CAAA;AAAA,EAChC;AACF;AAQO,SAAS,qBAAqB,EAAA,EAAqB;AACxD,EAAA,OAAO,kBAAA,CAAmB,OAAO,EAAE,CAAA;AACrC;AAKO,SAAS,gBAAA,GAAyB;AACvC,EAAA,kBAAA,CAAmB,KAAA,EAAM;AAC3B;AAOO,SAAS,wBAAA,GAAqC;AACnD,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,kBAAA,CAAmB,IAAA,EAAM,CAAA;AAC7C;AAQO,SAAS,uBAAuB,EAAA,EAAqB;AAC1D,EAAA,OAAO,kBAAA,CAAmB,IAAI,EAAE,CAAA;AAClC;AAMA,SAAS,wBACP,OAAA,EACgB;AAChB,EAAA,IAAI,MAAA,GAAS,GAAA;AACb,EAAA,IAAI,UAAA,GAA+B,CAAC,GAAA,EAAK,GAAG,CAAA;AAE5C,EAAA,MAAM,UAAA,GAAa,SAAU,WAAA,EAAwD;AACnF,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,WAAA,CAAY,CAAC,CAAA,GAAI,IAAA,CAAK,EAAA,GAAK,GAAA,EAAK,WAAA,CAAY,CAAC,CAAA,GAAI,IAAA,CAAK,KAAK,GAAG,CAAA;AACpF,IAAA,IAAI,CAAC,KAAA;AACH,MAAA,OAAO,IAAA;AACT,IAAA,OAAO,CAAC,KAAA,CAAM,CAAC,CAAA,GAAI,SAAS,UAAA,CAAW,CAAC,CAAA,EAAG,KAAA,CAAM,CAAC,CAAA,GAAI,MAAA,GAAS,UAAA,CAAW,CAAC,CAAC,CAAA;AAAA,EAC9E,CAAA;AAGA,EAAA,UAAA,CAAW,KAAA,IAAS,CAAC,CAAA,KAAoB;AACvC,IAAA,IAAI,UAAU,MAAA,KAAW,CAAA;AACvB,MAAA,OAAO,MAAA;AACT,IAAA,MAAA,GAAS,CAAA;AACT,IAAA,OAAO,UAAA;AAAA,EACT,CAAA,CAAA;AAGA,EAAA,UAAA,CAAW,SAAA,IAAa,CAAC,CAAA,KAA8B;AACrD,IAAA,IAAI,UAAU,MAAA,KAAW,CAAA;AACvB,MAAA,OAAO,UAAA;AACT,IAAA,UAAA,GAAa,CAAA;AACb,IAAA,OAAO,UAAA;AAAA,EACT,CAAA,CAAA;AAEA,EAAA,OAAO,UAAA;AACT;AAmCO,SAAS,uBAAA,CACd,QACA,OAAA,EACgB;AAChB,EAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,GAAQ,OAAM,GAAI,OAAA;AAGzC,EAAA,IAAI,MAAA,CAAO,YAAY,KAAA,EAAO;AAC5B,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mCAAA,EAAsC,MAAA,CAAO,OAAO,CAAA,CAAE,CAAA;AAAA,EACxE;AAEA,EAAA,IAAI,CAAC,MAAA,CAAO,WAAA,IAAe,MAAA,CAAO,WAAA,CAAY,WAAW,CAAA,EAAG;AAC1D,IAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,EACrE;AAGA,EAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,WAAA,CAAY,GAAA,CAAI,CAAC,SAAA,KAAc;AAC3D,IAAA,MAAM,IAAA,GAAO,mBAAA,CAAoB,SAAA,EAAW,KAAA,EAAO,MAAM,CAAA;AAEzD,IAAA,OAAO;AAAA,MACL,SAAA;AAAA,MACA,UAAA,EAAY,IAAA;AAAA,MACZ,QAAQ,SAAA,CAAU;AAAA,KACpB;AAAA,EACF,CAAC,CAAA;AAED,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,OAAA,CAAQ,IAAI,gDAAA,EAAkD;AAAA,MAC5D,WAAA,EAAa,MAAA,CAAO,WAAA,CAAY,GAAA,CAAI,CAAA,CAAA,MAAM,EAAE,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,IAAA,EAAM,CAAA,CAAE,IAAA,EAAK,CAAE,CAAA;AAAA,MACzE,OAAO,cAAA,CAAe;AAAA,KACvB,CAAA;AAAA,EACH;AAGA,EAAA,MAAM,mBAAA,GAAsB,uBAAA,CAAwB,CAAC,MAAA,EAAgB,GAAA,KAAgB;AAEnF,IAAA,MAAM,GAAA,GAAO,MAAA,GAAS,GAAA,GAAO,IAAA,CAAK,EAAA;AAClC,IAAA,MAAM,GAAA,GAAO,GAAA,GAAM,GAAA,GAAO,IAAA,CAAK,EAAA;AAG/B,IAAA,IAAI,YAAA,GAAe,IAAA;AACnB,IAAA,KAAA,MAAW,EAAE,UAAA,EAAY,MAAA,EAAO,IAAK,cAAA,EAAgB;AACnD,MAAA,IACE,GAAA,IAAO,OAAO,CAAC,CAAA,CAAE,CAAC,CAAA,IAAK,GAAA,IAAO,MAAA,CAAO,CAAC,CAAA,CAAE,CAAC,KACtC,GAAA,IAAO,MAAA,CAAO,CAAC,CAAA,CAAE,CAAC,CAAA,IAAK,OAAO,MAAA,CAAO,CAAC,CAAA,CAAE,CAAC,CAAA,EAC5C;AACA,QAAA,YAAA,GAAe,UAAA;AACf,QAAA;AAAA,MACF;AAAA,IACF;AAGA,IAAA,IAAI,CAAC,YAAA,IAAgB,cAAA,CAAe,CAAC,CAAA,EAAG;AACtC,MAAA,YAAA,GAAe,cAAA,CAAe,CAAC,CAAA,CAAE,UAAA;AAAA,IACnC;AAGA,IAAA,OAAO,YAAA,CAAc,CAAC,MAAA,EAAQ,GAAG,CAAC,CAAA;AAAA,EACpC,CAAC,CAAA;AAGD,EAAA,mBAAA,CAAoB,MAAA,GAAS,SAAU,MAAA,EAAgC;AACrE,IAAA,IAAI,YAAA,GAAkC,IAAA;AACtC,IAAA,IAAI,iBAA0C,EAAC;AAC/C,IAAA,IAAI,mBAAA,GAAsB,EAAA;AAE1B,IAAA,OAAO;AAAA,MACL,KAAA,CAAM,KAAa,GAAA,EAAa;AAE9B,QAAA,cAAA,CAAe,IAAA,CAAK,CAAC,GAAA,EAAK,GAAG,CAAC,CAAA;AAG9B,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,MAAM,MAAA,GAAU,GAAA,GAAM,GAAA,GAAO,IAAA,CAAK,EAAA;AAClC,UAAA,MAAM,MAAA,GAAU,GAAA,GAAM,GAAA,GAAO,IAAA,CAAK,EAAA;AAElC,UAAA,IAAI,KAAA,EAAO;AACT,YAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,iBAAA,EAAoB,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAA,EAAK,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAC,CAAA,SAAA,EAAO,mBAAmB,CAAA,CAAE,CAAA;AAAA,UACrG;AAEA,UAAA,YAAA,CAAa,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA,QAC7B;AAAA,MACF,CAAA;AAAA,MAEA,SAAA,GAAY;AAEV,QAAA,IAAI,cAAA,CAAe,MAAA,GAAS,CAAA,IAAK,cAAA,CAAe,CAAC,CAAA,EAAG;AAClD,UAAA,MAAM,CAAC,GAAA,EAAK,GAAG,CAAA,GAAI,eAAe,CAAC,CAAA;AACnC,UAAA,MAAM,MAAA,GAAU,GAAA,GAAM,GAAA,GAAO,IAAA,CAAK,EAAA;AAClC,UAAA,MAAM,MAAA,GAAU,GAAA,GAAM,GAAA,GAAO,IAAA,CAAK,EAAA;AAGlC,UAAA,KAAA,MAAW,EAAE,SAAA,EAAW,UAAA,EAAY,MAAA,MAAY,cAAA,EAAgB;AAC9D,YAAA,IACE,MAAA,IAAU,OAAO,CAAC,CAAA,CAAE,CAAC,CAAA,IAAK,MAAA,IAAU,MAAA,CAAO,CAAC,CAAA,CAAE,CAAC,KAC5C,MAAA,IAAU,MAAA,CAAO,CAAC,CAAA,CAAE,CAAC,CAAA,IAAK,UAAU,MAAA,CAAO,CAAC,CAAA,CAAE,CAAC,CAAA,EAClD;AAEA,cAAA,IAAI,WAAW,MAAA,EAAQ;AACrB,gBAAA,YAAA,GAAe,UAAA,CAAW,OAAO,MAAM,CAAA;AACvC,gBAAA,mBAAA,GAAsB,SAAA,CAAU,IAAA;AAAA,cAClC;AAEA,cAAA,IAAI,KAAA,EAAO;AACT,gBAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,oCAAA,EAAuC,SAAA,CAAU,IAAI,CAAA,CAAE,CAAA;AAAA,cACrE;AACA,cAAA;AAAA,YACF;AAAA,UACF;AAGA,UAAA,IAAI,CAAC,YAAA,IAAgB,cAAA,CAAe,CAAC,CAAA,EAAG;AACtC,YAAA,MAAM,SAAA,GAAY,cAAA,CAAe,CAAC,CAAA,CAAE,UAAA;AACpC,YAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,cAAA,YAAA,GAAe,SAAA,CAAU,OAAO,MAAM,CAAA;AACtC,cAAA,mBAAA,GAAsB,cAAA,CAAe,CAAC,CAAA,CAAE,SAAA,CAAU,IAAA;AAAA,YACpD;AAEA,YAAA,IAAI,KAAA,EAAO;AACT,cAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,kCAAA,EAAqC,mBAAmB,CAAA,CAAE,CAAA;AAAA,YACxE;AAAA,UACF;AAAA,QACF;AAEA,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,YAAA,CAAa,SAAA,EAAU;AAGvB,UAAA,KAAA,MAAW,CAAC,GAAA,EAAK,GAAG,CAAA,IAAK,cAAA,EAAgB;AACvC,YAAA,YAAA,CAAa,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA,UAC7B;AAAA,QACF;AAEA,QAAA,cAAA,GAAiB,EAAC;AAAA,MACpB,CAAA;AAAA,MAEA,OAAA,GAAU;AACR,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,YAAA,CAAa,OAAA,EAAQ;AAAA,QACvB;AAAA,MACF,CAAA;AAAA,MAEA,YAAA,GAAe;AACb,QAAA,cAAA,GAAiB,EAAC;AAClB,QAAA,YAAA,GAAe,IAAA;AAAA,MACjB,CAAA;AAAA,MAEA,UAAA,GAAa;AACX,QAAA,IAAI,YAAA,EAAc;AAChB,UAAA,YAAA,CAAa,UAAA,EAAW;AAAA,QAC1B;AACA,QAAA,YAAA,GAAe,IAAA;AAAA,MACjB,CAAA;AAAA,MAEA,MAAA,GAAS;AAEP,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,KAAK,mEAAmE,CAAA;AAAA,QAClF;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAIA,EAAA,IAAI,oBAAoB,KAAA,EAAO;AAC7B,IAAA,mBAAA,CAAoB,MAAM,CAAC,CAAA;AAAA,EAC7B;AACA,EAAA,IAAI,oBAAoB,SAAA,EAAW;AACjC,IAAA,mBAAA,CAAoB,UAAU,CAAC,KAAA,GAAQ,CAAA,EAAG,MAAA,GAAS,CAAC,CAAC,CAAA;AAAA,EACvD;AAEA,EAAA,OAAO,mBAAA;AACT;AAKA,SAAS,mBAAA,CACP,SAAA,EACA,KAAA,EACA,MAAA,EACgB;AAChB,EAAA,MAAM,EAAE,YAAA,EAAc,UAAA,EAAY,MAAA,EAAO,GAAI,SAAA;AAG7C,EAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,GAAA,CAAI,YAAY,CAAA;AACnD,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,aAAa,wBAAA,EAAyB;AAC5C,IAAA,MAAM,gBAAgB,UAAA,CAAW,MAAA,GAAS,IAAI,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA;AACtE,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,YAAA,EAAe,YAAY,CAAA,4CAAA,EACC,aAAa,6BACZ,YAAY,CAAA,2BAAA;AAAA,KAC3C;AAAA,EACF;AAGA,EAAA,MAAM,aAAa,OAAA,EAAQ;AAG3B,EAAA,IAAI,UAAA,CAAW,MAAA,IAAU,UAAA,CAAW,MAAA,EAAQ;AAC1C,IAAA,UAAA,CAAW,MAAA,CAAO,WAAW,MAAM,CAAA;AAAA,EACrC;AAEA,EAAA,IAAI,UAAA,CAAW,MAAA,IAAU,UAAA,CAAW,MAAA,EAAQ;AAC1C,IAAA,UAAA,CAAW,MAAA,CAAO,WAAW,MAAM,CAAA;AAAA,EACrC;AAEA,EAAA,IAAI,UAAA,CAAW,SAAA,IAAa,UAAA,CAAW,SAAA,EAAW;AAChD,IAAA,UAAA,CAAW,SAAA,CAAU,WAAW,SAAS,CAAA;AAAA,EAC3C;AAEA,EAAA,IAAI,WAAW,KAAA,EAAO;AACpB,IAAA,UAAA,CAAW,KAAA,CAAM,WAAW,KAAK,CAAA;AAAA,EACnC;AAGA,EAAA,IAAI,WAAW,SAAA,EAAW;AACxB,IAAA,MAAM,CAAC,OAAA,EAAS,OAAO,CAAA,GAAI,MAAA,CAAO,eAAA;AAClC,IAAA,UAAA,CAAW,SAAA,CAAU;AAAA,MACnB,QAAQ,CAAA,GAAI,OAAA;AAAA,MACZ,SAAS,CAAA,GAAI;AAAA,KACd,CAAA;AAAA,EACH;AAGA,EAAA,IAAI,MAAA,CAAO,UAAA,IAAc,UAAA,CAAW,UAAA,EAAY;AAC9C,IAAA,UAAA,CAAW,UAAA,CAAW,OAAO,UAAU,CAAA;AAAA,EACzC;AAEA,EAAA,OAAO,UAAA;AACT;AAQO,SAAS,eAAe,MAAA,EAAuC;AACpE,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,EAAU;AACzC,IAAA,MAAM,IAAI,MAAM,iCAAiC,CAAA;AAAA,EACnD;AAEA,EAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,IAAA,MAAM,IAAI,MAAM,yCAAyC,CAAA;AAAA,EAC3D;AAEA,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,IAAY,CAAC,MAAA,CAAO,SAAS,OAAA,EAAS;AAChD,IAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,EACjE;AAEA,EAAA,IAAI,CAAC,OAAO,WAAA,IAAe,CAAC,MAAM,OAAA,CAAQ,MAAA,CAAO,WAAW,CAAA,EAAG;AAC7D,IAAA,MAAM,IAAI,MAAM,2CAA2C,CAAA;AAAA,EAC7D;AAEA,EAAA,IAAI,MAAA,CAAO,WAAA,CAAY,MAAA,KAAW,CAAA,EAAG;AACnC,IAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,EAClE;AAGA,EAAA,KAAA,MAAW,SAAA,IAAa,OAAO,WAAA,EAAa;AAC1C,IAAA,IAAI,CAAC,SAAA,CAAU,IAAA,IAAQ,CAAC,UAAU,YAAA,EAAc;AAC9C,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mCAAA,EAAsC,KAAK,SAAA,CAAU,SAAS,CAAC,CAAA,CAAE,CAAA;AAAA,IACnF;AAEA,IAAA,IAAI,CAAC,SAAA,CAAU,UAAA,IAAc,CAAC,UAAU,MAAA,EAAQ;AAC9C,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,UAAA,EAAa,SAAA,CAAU,IAAI,CAAA,6BAAA,CAA+B,CAAA;AAAA,IAC5E;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAkBO,SAAS,YAAA,CACd,YACA,OAAA,EACgB;AAChB,EAAA,IAAI,MAAA;AAEJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,UAAU,CAAA;AAAA,EAChC,SACO,KAAA,EAAO;AACZ,IAAA,MAAM,IAAI,MAAM,CAAA,cAAA,EAAiB,KAAA,YAAiB,QAAQ,KAAA,CAAM,OAAA,GAAU,eAAe,CAAA,CAAE,CAAA;AAAA,EAC7F;AAEA,EAAA,cAAA,CAAe,MAAM,CAAA;AACrB,EAAA,OAAO,uBAAA,CAAwB,QAAQ,OAAO,CAAA;AAChD","file":"index.js","sourcesContent":["/**\n * Standalone Composite Projection Loader (Zero Dependencies)\n *\n * A pure JavaScript/TypeScript module that consumes exported composite projection\n * configurations and creates D3-compatible projections using a plugin architecture.\n *\n * This package has ZERO dependencies. Users must register projection factories\n * before loading configurations.\n *\n * @example\n * ```typescript\n * // Register projections first\n * import * as d3 from 'd3-geo'\n * import { registerProjection, loadCompositeProjection } from './standalone-projection-loader'\n *\n * registerProjection('mercator', () => d3.geoMercator())\n * registerProjection('albers', () => d3.geoAlbers())\n *\n * // Then load your configuration\n * const projection = loadCompositeProjection(config, { width: 800, height: 600 })\n * ```\n *\n * @packageDocumentation\n */\n\n/**\n * Generic projection-like interface that matches D3 projections\n * without requiring d3-geo as a dependency\n *\n * Note: D3 projections use getter/setter pattern where calling without\n * arguments returns the current value, and with arguments sets and returns this.\n */\nexport interface ProjectionLike {\n (coordinates: [number, number]): [number, number] | null\n center?: {\n (): [number, number]\n (center: [number, number]): ProjectionLike\n }\n rotate?: {\n (): [number, number, number]\n (angles: [number, number, number]): ProjectionLike\n }\n parallels?: {\n (): [number, number]\n (parallels: [number, number]): ProjectionLike\n }\n scale?: {\n (): number\n (scale: number): ProjectionLike\n }\n translate?: {\n (): [number, number]\n (translate: [number, number]): ProjectionLike\n }\n clipExtent?: {\n (): [[number, number], [number, number]] | null\n (extent: [[number, number], [number, number]] | null): ProjectionLike\n }\n stream?: (stream: StreamLike) => StreamLike\n precision?: {\n (): number\n (precision: number): ProjectionLike\n }\n fitExtent?: (extent: [[number, number], [number, number]], object: any) => ProjectionLike\n fitSize?: (size: [number, number], object: any) => ProjectionLike\n fitWidth?: (width: number, object: any) => ProjectionLike\n fitHeight?: (height: number, object: any) => ProjectionLike\n}\n\n/**\n * Stream protocol interface for D3 geographic transforms\n */\nexport interface StreamLike {\n point: (x: number, y: number) => void\n lineStart: () => void\n lineEnd: () => void\n polygonStart: () => void\n polygonEnd: () => void\n sphere?: () => void\n}\n\n/**\n * Factory function that creates a projection instance\n */\nexport type ProjectionFactory = () => ProjectionLike\n\n/**\n * Exported configuration format (subset needed for loading)\n */\nexport interface ExportedConfig {\n version: string\n metadata: {\n atlasId: string\n atlasName: string\n }\n pattern: string\n referenceScale: number\n territories: Territory[]\n}\n\nexport interface Territory {\n code: string\n name: string\n role: string\n projectionId: string\n projectionFamily: string\n parameters: ProjectionParameters\n layout: Layout\n bounds: [[number, number], [number, number]]\n}\n\nexport interface ProjectionParameters {\n center?: [number, number]\n rotate?: [number, number, number]\n scale: number\n baseScale: number\n scaleMultiplier: number\n parallels?: [number, number]\n}\n\nexport interface Layout {\n translateOffset: [number, number]\n clipExtent: [[number, number], [number, number]] | null\n}\n\n/**\n * Options for creating the composite projection\n */\nexport interface LoaderOptions {\n /** Canvas width in pixels */\n width: number\n /** Canvas height in pixels */\n height: number\n /** Whether to apply clipping to territories (default: true) */\n enableClipping?: boolean\n /** Debug mode - logs territory selection (default: false) */\n debug?: boolean\n}\n\n/**\n * Runtime registry for projection factories\n * Users must register projections before loading configurations\n */\nconst projectionRegistry = new Map<string, ProjectionFactory>()\n\n/**\n * Register a projection factory with a given ID\n *\n * @example\n * ```typescript\n * import * as d3 from 'd3-geo'\n * import { registerProjection } from '@atlas-composer/projection-loader'\n *\n * registerProjection('mercator', () => d3.geoMercator())\n * registerProjection('albers', () => d3.geoAlbers())\n * ```\n *\n * @param id - Projection identifier (e.g., 'mercator', 'albers')\n * @param factory - Function that creates a new projection instance\n */\nexport function registerProjection(id: string, factory: ProjectionFactory): void {\n projectionRegistry.set(id, factory)\n}\n\n/**\n * Register multiple projections at once\n *\n * @example\n * ```typescript\n * import * as d3 from 'd3-geo'\n * import { registerProjections } from '@atlas-composer/projection-loader'\n *\n * registerProjections({\n * 'mercator': () => d3.geoMercator(),\n * 'albers': () => d3.geoAlbers(),\n * 'conic-equal-area': () => d3.geoConicEqualArea()\n * })\n * ```\n *\n * @param factories - Object mapping projection IDs to factory functions\n */\nexport function registerProjections(factories: Record<string, ProjectionFactory>): void {\n for (const [id, factory] of Object.entries(factories)) {\n registerProjection(id, factory)\n }\n}\n\n/**\n * Unregister a projection\n *\n * @param id - Projection identifier to remove\n * @returns True if the projection was removed, false if it wasn't registered\n */\nexport function unregisterProjection(id: string): boolean {\n return projectionRegistry.delete(id)\n}\n\n/**\n * Clear all registered projections\n */\nexport function clearProjections(): void {\n projectionRegistry.clear()\n}\n\n/**\n * Get list of currently registered projection IDs\n *\n * @returns Array of registered projection identifiers\n */\nexport function getRegisteredProjections(): string[] {\n return Array.from(projectionRegistry.keys())\n}\n\n/**\n * Check if a projection is registered\n *\n * @param id - Projection identifier to check\n * @returns True if the projection is registered\n */\nexport function isProjectionRegistered(id: string): boolean {\n return projectionRegistry.has(id)\n}\n\n/**\n * Create a minimal projection wrapper (similar to d3.geoProjection)\n * This allows us to avoid the d3-geo dependency\n */\nfunction createProjectionWrapper(\n project: (lambda: number, phi: number) => [number, number] | null,\n): ProjectionLike {\n let _scale = 150\n let _translate: [number, number] = [480, 250]\n\n const projection = function (coordinates: [number, number]): [number, number] | null {\n const point = project(coordinates[0] * Math.PI / 180, coordinates[1] * Math.PI / 180)\n if (!point)\n return null\n return [point[0] * _scale + _translate[0], point[1] * _scale + _translate[1]]\n } as ProjectionLike\n\n // D3-style getter/setter for scale\n projection.scale = ((s?: number): any => {\n if (arguments.length === 0)\n return _scale\n _scale = s!\n return projection\n }) as any\n\n // D3-style getter/setter for translate\n projection.translate = ((t?: [number, number]): any => {\n if (arguments.length === 0)\n return _translate\n _translate = t!\n return projection\n }) as any\n\n return projection\n}\n\n/**\n * Create a D3-compatible projection from an exported composite projection configuration\n *\n * @example\n * ```typescript\n * import * as d3 from 'd3-geo'\n * import { registerProjection, loadCompositeProjection } from '@atlas-composer/projection-loader'\n *\n * // Register projections first\n * registerProjection('mercator', () => d3.geoMercator())\n * registerProjection('albers', () => d3.geoAlbers())\n *\n * // Load configuration\n * const config = JSON.parse(jsonString)\n *\n * // Create projection\n * const projection = loadCompositeProjection(config, {\n * width: 800,\n * height: 600\n * })\n *\n * // Use with D3\n * const path = d3.geoPath(projection)\n * svg.selectAll('path')\n * .data(countries.features)\n * .join('path')\n * .attr('d', path)\n * ```\n *\n * @param config - Exported composite projection configuration\n * @param options - Canvas dimensions and options\n * @returns D3-compatible projection that routes geometry to appropriate sub-projections\n */\nexport function loadCompositeProjection(\n config: ExportedConfig,\n options: LoaderOptions,\n): ProjectionLike {\n const { width, height, debug = false } = options\n\n // Validate configuration version\n if (config.version !== '1.0') {\n throw new Error(`Unsupported configuration version: ${config.version}`)\n }\n\n if (!config.territories || config.territories.length === 0) {\n throw new Error('Configuration must contain at least one territory')\n }\n\n // Create sub-projections for each territory\n const subProjections = config.territories.map((territory) => {\n const proj = createSubProjection(territory, width, height)\n\n return {\n territory,\n projection: proj,\n bounds: territory.bounds,\n }\n })\n\n if (debug) {\n console.log('[CompositeProjection] Created sub-projections:', {\n territories: config.territories.map(t => ({ code: t.code, name: t.name })),\n count: subProjections.length,\n })\n }\n\n // Create composite projection using custom stream multiplexing\n const compositeProjection = createProjectionWrapper((lambda: number, phi: number) => {\n // Convert radians to degrees for bounds checking\n const lon = (lambda * 180) / Math.PI\n const lat = (phi * 180) / Math.PI\n\n // Find which territory this point belongs to\n let selectedProj = null\n for (const { projection, bounds } of subProjections) {\n if (\n lon >= bounds[0][0] && lon <= bounds[1][0]\n && lat >= bounds[0][1] && lat <= bounds[1][1]\n ) {\n selectedProj = projection\n break\n }\n }\n\n // If no territory matched, use first projection (fallback)\n if (!selectedProj && subProjections[0]) {\n selectedProj = subProjections[0].projection\n }\n\n // Project the point (should always have a projection by this point)\n return selectedProj!([lambda, phi])\n })\n\n // Implement stream multiplexing for proper geometry routing\n compositeProjection.stream = function (stream: StreamLike): StreamLike {\n let activeStream: StreamLike | null = null\n let bufferedPoints: Array<[number, number]> = []\n let activeTerritoryCode = ''\n\n return {\n point(lon: number, lat: number) {\n // Buffer points until we can determine which territory they belong to\n bufferedPoints.push([lon, lat])\n\n // If we have an active stream, forward the point\n if (activeStream) {\n const lonDeg = (lon * 180) / Math.PI\n const latDeg = (lat * 180) / Math.PI\n\n if (debug) {\n console.log(`[Stream] Point: [${lonDeg.toFixed(2)}, ${latDeg.toFixed(2)}] → ${activeTerritoryCode}`)\n }\n\n activeStream.point(lon, lat)\n }\n },\n\n lineStart() {\n // Determine which territory this line belongs to\n if (bufferedPoints.length > 0 && bufferedPoints[0]) {\n const [lon, lat] = bufferedPoints[0]\n const lonDeg = (lon * 180) / Math.PI\n const latDeg = (lat * 180) / Math.PI\n\n // Find matching territory\n for (const { territory, projection, bounds } of subProjections) {\n if (\n lonDeg >= bounds[0][0] && lonDeg <= bounds[1][0]\n && latDeg >= bounds[0][1] && latDeg <= bounds[1][1]\n ) {\n // Use the projection's stream if available\n if (projection.stream) {\n activeStream = projection.stream(stream)\n activeTerritoryCode = territory.code\n }\n\n if (debug) {\n console.log(`[Stream] Line started in territory: ${territory.code}`)\n }\n break\n }\n }\n\n // Fallback to first projection\n if (!activeStream && subProjections[0]) {\n const firstProj = subProjections[0].projection\n if (firstProj.stream) {\n activeStream = firstProj.stream(stream)\n activeTerritoryCode = subProjections[0].territory.code\n }\n\n if (debug) {\n console.log(`[Stream] Line started (fallback): ${activeTerritoryCode}`)\n }\n }\n }\n\n if (activeStream) {\n activeStream.lineStart()\n\n // Replay buffered points\n for (const [lon, lat] of bufferedPoints) {\n activeStream.point(lon, lat)\n }\n }\n\n bufferedPoints = []\n },\n\n lineEnd() {\n if (activeStream) {\n activeStream.lineEnd()\n }\n },\n\n polygonStart() {\n bufferedPoints = []\n activeStream = null\n },\n\n polygonEnd() {\n if (activeStream) {\n activeStream.polygonEnd()\n }\n activeStream = null\n },\n\n sphere() {\n // Not supported in composite projections\n if (debug) {\n console.warn('[Stream] sphere() called - not supported in composite projections')\n }\n },\n }\n }\n\n // Set reasonable defaults for the composite projection\n // Note: Individual territories handle their own scale/translate\n if (compositeProjection.scale) {\n compositeProjection.scale(1)\n }\n if (compositeProjection.translate) {\n compositeProjection.translate([width / 2, height / 2])\n }\n\n return compositeProjection\n}\n\n/**\n * Create a sub-projection for a single territory\n */\nfunction createSubProjection(\n territory: Territory,\n width: number,\n height: number,\n): ProjectionLike {\n const { projectionId, parameters, layout } = territory\n\n // Get projection factory from registry\n const factory = projectionRegistry.get(projectionId)\n if (!factory) {\n const registered = getRegisteredProjections()\n const availableList = registered.length > 0 ? registered.join(', ') : 'none'\n throw new Error(\n `Projection \"${projectionId}\" is not registered. `\n + `Available projections: ${availableList}. `\n + `Use registerProjection('${projectionId}', factory) to register it.`,\n )\n }\n\n // Create projection instance\n const projection = factory()\n\n // Apply parameters\n if (parameters.center && projection.center) {\n projection.center(parameters.center)\n }\n\n if (parameters.rotate && projection.rotate) {\n projection.rotate(parameters.rotate)\n }\n\n if (parameters.parallels && projection.parallels) {\n projection.parallels(parameters.parallels)\n }\n\n if (projection.scale) {\n projection.scale(parameters.scale)\n }\n\n // Apply layout translate\n if (projection.translate) {\n const [offsetX, offsetY] = layout.translateOffset\n projection.translate([\n width / 2 + offsetX,\n height / 2 + offsetY,\n ])\n }\n\n // Apply clipping if specified\n if (layout.clipExtent && projection.clipExtent) {\n projection.clipExtent(layout.clipExtent)\n }\n\n return projection\n}\n\n/**\n * Validate an exported configuration\n *\n * @param config - Configuration to validate\n * @returns True if valid, throws error otherwise\n */\nexport function validateConfig(config: any): config is ExportedConfig {\n if (!config || typeof config !== 'object') {\n throw new Error('Configuration must be an object')\n }\n\n if (!config.version) {\n throw new Error('Configuration must have a version field')\n }\n\n if (!config.metadata || !config.metadata.atlasId) {\n throw new Error('Configuration must have metadata with atlasId')\n }\n\n if (!config.territories || !Array.isArray(config.territories)) {\n throw new Error('Configuration must have territories array')\n }\n\n if (config.territories.length === 0) {\n throw new Error('Configuration must have at least one territory')\n }\n\n // Validate each territory\n for (const territory of config.territories) {\n if (!territory.code || !territory.projectionId) {\n throw new Error(`Territory missing required fields: ${JSON.stringify(territory)}`)\n }\n\n if (!territory.parameters || !territory.bounds) {\n throw new Error(`Territory ${territory.code} missing parameters or bounds`)\n }\n }\n\n return true\n}\n\n/**\n * Load composite projection from JSON string\n *\n * @example\n * ```typescript\n * import * as d3 from 'd3-geo'\n * import { registerProjection, loadFromJSON } from '@atlas-composer/projection-loader'\n *\n * // Register projections first\n * registerProjection('mercator', () => d3.geoMercator())\n *\n * // Load from JSON\n * const jsonString = fs.readFileSync('france-composite.json', 'utf-8')\n * const projection = loadFromJSON(jsonString, { width: 800, height: 600 })\n * ```\n */\nexport function loadFromJSON(\n jsonString: string,\n options: LoaderOptions,\n): ProjectionLike {\n let config: any\n\n try {\n config = JSON.parse(jsonString)\n }\n catch (error) {\n throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : 'Unknown error'}`)\n }\n\n validateConfig(config)\n return loadCompositeProjection(config, options)\n}\n\n// Default export\nexport default {\n // Core loading functions\n loadCompositeProjection,\n loadFromJSON,\n validateConfig,\n\n // Registry management\n registerProjection,\n registerProjections,\n unregisterProjection,\n clearProjections,\n getRegisteredProjections,\n isProjectionRegistered,\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atlas-composer/projection-loader",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Zero-dependency standalone loader for composite map projections with plugin architecture",
|
|
6
|
+
"author": "Lucas Poulain (ShallowRed)",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/ShallowRed/atlas-composer/tree/main/packages/projection-loader#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ShallowRed/atlas-composer.git",
|
|
12
|
+
"directory": "packages/projection-loader"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/ShallowRed/atlas-composer/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"cartography",
|
|
19
|
+
"composite",
|
|
20
|
+
"d3",
|
|
21
|
+
"d3-geo",
|
|
22
|
+
"geography",
|
|
23
|
+
"gis",
|
|
24
|
+
"map",
|
|
25
|
+
"observable-plot",
|
|
26
|
+
"projection",
|
|
27
|
+
"tree-shakeable",
|
|
28
|
+
"zero-dependencies"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./helpers": {
|
|
37
|
+
"types": "./dist/d3-projection-helpers.d.ts",
|
|
38
|
+
"import": "./dist/d3-projection-helpers.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"main": "./dist/index.js",
|
|
42
|
+
"module": "./dist/index.js",
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"files": [
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"README.md",
|
|
47
|
+
"dist"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"dev": "tsup --watch",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"size": "size-limit",
|
|
54
|
+
"size:why": "size-limit --why"
|
|
55
|
+
},
|
|
56
|
+
"size-limit": [
|
|
57
|
+
{
|
|
58
|
+
"name": "Main bundle (ESM)",
|
|
59
|
+
"path": "dist/index.js",
|
|
60
|
+
"limit": "10 KB",
|
|
61
|
+
"gzip": true
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "D3 helpers (optional)",
|
|
65
|
+
"path": "dist/d3-projection-helpers.js",
|
|
66
|
+
"limit": "3 KB",
|
|
67
|
+
"gzip": true
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"d3-geo": ">=3.0.0",
|
|
72
|
+
"d3-geo-projection": ">=4.0.0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"d3-geo": {
|
|
76
|
+
"optional": false
|
|
77
|
+
},
|
|
78
|
+
"d3-geo-projection": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@size-limit/preset-small-lib": "^11.2.0",
|
|
84
|
+
"@types/d3-geo": "^3.1.0",
|
|
85
|
+
"tsup": "^8.0.0",
|
|
86
|
+
"typescript": "~5.9.3"
|
|
87
|
+
}
|
|
88
|
+
}
|