@atlas-composer/projection-loader 1.1.1 → 2.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 CHANGED
@@ -1,42 +1,42 @@
1
1
  # @atlas-composer/projection-loader
2
2
 
3
- > The runtime engine for Atlas Composer maps.
3
+ > A simple runtime engine for composite projections.
4
4
 
5
5
  This package allows you to render composite projections exported from **Atlas Composer** in your own applications. It is lightweight, and works seamlessly with D3.js.
6
6
 
7
- ## Features
7
+ ## Features
8
8
 
9
- - **Zero Runtime Dependencies** (excluding your choice of projection library).
10
- - **Plugin Architecture**: Only import the projection definitions you need.
9
+ - **Zero runtime dependencies** (excluding your choice of projection library).
10
+ - **plugin architecture**: Only import the projection definitions you need.
11
11
  - **Type-Safe**: Written in TypeScript with full type definitions.
12
12
 
13
- ## 🚀 Usage
14
-
15
- ### 1. Install
13
+ ## Installation
16
14
 
17
15
  ```bash
18
16
  npm install @atlas-composer/projection-loader d3-geo
19
17
  ```
20
18
 
21
- ### 2. Implementation
19
+ ## Usage
22
20
 
23
21
  ```typescript
24
- import { loadCompositeProjection, registerProjection } from '@atlas-composer/projection-loader'
22
+ import { ProjectionLoader } from '@atlas-composer/projection-loader'
25
23
  import * as d3 from 'd3-geo'
26
- import config from './my-exported-map.json'
24
+ import config from './my-exported-config.json'
25
+
26
+ // Create a loader instance
27
+ const loader = new ProjectionLoader()
27
28
 
28
- // 1. Register the projections required by your map
29
- registerProjection('mercator', () => d3.geoMercator())
30
- registerProjection('conic-conformal', () => d3.geoConicConformal())
29
+ // Register the projections required by your composite projection
30
+ loader.register('mercator', () => d3.geoMercator())
31
+ loader.register('conic-conformal', () => d3.geoConicConformal())
31
32
 
32
- // 2. Load the composite projection
33
- // The resulting object is a standard D3 stream-compatible projection
34
- const projection = loadCompositeProjection(config, {
33
+ // Load the composite projection
34
+ const projection = loader.load(config, {
35
35
  width: 800,
36
36
  height: 600
37
37
  })
38
38
 
39
- // 3. Render using D3
39
+ // Use with D3
40
40
  const path = d3.geoPath(projection)
41
41
 
42
42
  d3.select('svg')
@@ -46,6 +46,16 @@ d3.select('svg')
46
46
  .attr('d', path)
47
47
  ```
48
48
 
49
- ## 📖 Documentation
49
+ ## API
50
+
51
+ ### ProjectionLoader
50
52
 
51
- For more context on the ecosystem, see the [root architecture documentation](../../docs/architecture.md).
53
+ - `new ProjectionLoader()` - Create a loader instance
54
+ - `loader.register(id, factory)` - Register a projection factory
55
+ - `loader.registerAll(factories)` - Register multiple projections from an object
56
+ - `loader.load(config, options)` - Load a composite projection from configuration
57
+ - `loader.loadFromJSON(jsonString, options)` - Load from a JSON string
58
+ - `loader.isRegistered(id)` - Check if a projection is registered
59
+ - `loader.getRegistered()` - Get all registered projection IDs
60
+ - `loader.unregister(id)` - Remove a registered projection
61
+ - `loader.clear()` - Clear all registered projections
@@ -1,37 +1,6 @@
1
1
  import { ProjectionFactory } from './index.js';
2
2
  import '@atlas-composer/specification';
3
3
 
4
- /**
5
- * D3 Projection Helpers
6
- *
7
- * Optional companion file that provides ready-to-use D3 projection factory mappings.
8
- * This file has dependencies on d3-geo and d3-geo-projection, but the main loader does not.
9
- *
10
- * Users can import this to quickly register all standard D3 projections, or they can
11
- * selectively import only the projections they need for tree-shaking.
12
- *
13
- * @example
14
- * ```typescript
15
- * // Register all projections at once
16
- * import { registerProjections } from './standalone-projection-loader'
17
- * import { d3ProjectionFactories } from './d3-projection-helpers'
18
- *
19
- * registerProjections(d3ProjectionFactories)
20
- * ```
21
- *
22
- * @example
23
- * ```typescript
24
- * // Tree-shakeable: import only what you need
25
- * import { registerProjection } from './standalone-projection-loader'
26
- * import { mercator, albers } from './d3-projection-helpers'
27
- *
28
- * registerProjection('mercator', mercator)
29
- * registerProjection('albers', albers)
30
- * ```
31
- *
32
- * @packageDocumentation
33
- */
34
-
35
4
  declare const azimuthalEqualArea: ProjectionFactory;
36
5
  declare const azimuthalEquidistant: ProjectionFactory;
37
6
  declare const gnomonic: ProjectionFactory;
@@ -46,28 +15,8 @@ declare const transverseMercator: ProjectionFactory;
46
15
  declare const equirectangular: ProjectionFactory;
47
16
  declare const naturalEarth1: ProjectionFactory;
48
17
  declare const equalEarth: ProjectionFactory;
49
- /**
50
- * Object containing all standard D3 projection factories
51
- * Keyed by the projection ID used in Atlas composer configurations
52
- */
53
18
  declare const d3ProjectionFactories: Record<string, ProjectionFactory>;
54
- /**
55
- * Convenience function to register all D3 projections at once
56
- *
57
- * @example
58
- * ```typescript
59
- * import { registerProjections } from './standalone-projection-loader'
60
- * import { registerAllD3Projections } from './d3-projection-helpers'
61
- *
62
- * registerAllD3Projections(registerProjections)
63
- * ```
64
- *
65
- * @param registerFn - The registerProjections function from the loader
66
- */
67
19
  declare function registerAllD3Projections(registerFn: (factories: Record<string, ProjectionFactory>) => void): void;
68
- /**
69
- * Get list of available D3 projection IDs
70
- */
71
20
  declare function getAvailableD3Projections(): string[];
72
21
  declare const _default: {
73
22
  d3ProjectionFactories: Record<string, ProjectionFactory>;
@@ -17,23 +17,19 @@ var equirectangular = () => d3Geo.geoEquirectangular();
17
17
  var naturalEarth1 = () => d3GeoProjection.geoNaturalEarth1();
18
18
  var equalEarth = () => d3Geo.geoEqualEarth();
19
19
  var d3ProjectionFactories = {
20
- // Azimuthal
21
20
  "azimuthal-equal-area": azimuthalEqualArea,
22
21
  "azimuthal-equidistant": azimuthalEquidistant,
23
22
  "gnomonic": gnomonic,
24
23
  "orthographic": orthographic,
25
24
  "stereographic": stereographic,
26
- // Conic
27
25
  "conic-conformal": conicConformal,
28
26
  "conic-equal-area": conicEqualArea,
29
27
  "conic-equidistant": conicEquidistant,
30
28
  "albers": albers,
31
- // Cylindrical
32
29
  "mercator": mercator,
33
30
  "transverse-mercator": transverseMercator,
34
31
  "equirectangular": equirectangular,
35
32
  "natural-earth-1": naturalEarth1,
36
- // Other
37
33
  "equal-earth": equalEarth
38
34
  };
39
35
  function registerAllD3Projections(registerFn) {
@@ -46,7 +42,6 @@ var d3_projection_helpers_default = {
46
42
  d3ProjectionFactories,
47
43
  registerAllD3Projections,
48
44
  getAvailableD3Projections,
49
- // Individual projections for tree-shaking
50
45
  azimuthalEqualArea,
51
46
  azimuthalEquidistant,
52
47
  gnomonic,
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/d3-projection-helpers.ts"],"names":[],"mappings":";;;;AAIO,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;AACrD,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;AAC9C,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;AACxE,IAAM,UAAA,GAAgC,MAAY,KAAA,CAAA,aAAA;AAElD,IAAM,qBAAA,GAA2D;AAAA,EACtE,sBAAA,EAAwB,kBAAA;AAAA,EACxB,uBAAA,EAAyB,oBAAA;AAAA,EACzB,UAAA,EAAY,QAAA;AAAA,EACZ,cAAA,EAAgB,YAAA;AAAA,EAChB,eAAA,EAAiB,aAAA;AAAA,EACjB,iBAAA,EAAmB,cAAA;AAAA,EACnB,kBAAA,EAAoB,cAAA;AAAA,EACpB,mBAAA,EAAqB,gBAAA;AAAA,EACrB,QAAA,EAAU,MAAA;AAAA,EACV,UAAA,EAAY,QAAA;AAAA,EACZ,qBAAA,EAAuB,kBAAA;AAAA,EACvB,iBAAA,EAAmB,eAAA;AAAA,EACnB,iBAAA,EAAmB,aAAA;AAAA,EACnB,aAAA,EAAe;AACjB;AAEO,SAAS,yBACd,UAAA,EACM;AACN,EAAA,UAAA,CAAW,qBAAqB,CAAA;AAClC;AAEO,SAAS,yBAAA,GAAsC;AACpD,EAAA,OAAO,MAAA,CAAO,KAAK,qBAAqB,CAAA;AAC1C;AAEA,IAAO,6BAAA,GAAQ;AAAA,EACb,qBAAA;AAAA,EACA,wBAAA;AAAA,EACA,yBAAA;AAAA,EACA,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":["import type { ProjectionFactory } from './projection-loader'\nimport * as d3Geo from 'd3-geo'\nimport * as d3GeoProjection from 'd3-geo-projection'\n\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()\nexport const conicConformal: ProjectionFactory = () => d3Geo.geoConicConformal()\nexport const conicEqualArea: ProjectionFactory = () => d3Geo.geoConicEqualArea()\nexport const conicEquidistant: ProjectionFactory = () => d3Geo.geoConicEquidistant()\nexport const albers: ProjectionFactory = () => d3Geo.geoAlbers()\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()\nexport const equalEarth: ProjectionFactory = () => d3Geo.geoEqualEarth()\n\nexport const d3ProjectionFactories: Record<string, ProjectionFactory> = {\n 'azimuthal-equal-area': azimuthalEqualArea,\n 'azimuthal-equidistant': azimuthalEquidistant,\n 'gnomonic': gnomonic,\n 'orthographic': orthographic,\n 'stereographic': stereographic,\n 'conic-conformal': conicConformal,\n 'conic-equal-area': conicEqualArea,\n 'conic-equidistant': conicEquidistant,\n 'albers': albers,\n 'mercator': mercator,\n 'transverse-mercator': transverseMercator,\n 'equirectangular': equirectangular,\n 'natural-earth-1': naturalEarth1,\n 'equal-earth': equalEarth,\n}\n\nexport function registerAllD3Projections(\n registerFn: (factories: Record<string, ProjectionFactory>) => void,\n): void {\n registerFn(d3ProjectionFactories)\n}\n\nexport function getAvailableD3Projections(): string[] {\n return Object.keys(d3ProjectionFactories)\n}\n\nexport default {\n d3ProjectionFactories,\n registerAllD3Projections,\n getAvailableD3Projections,\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 CHANGED
@@ -1,38 +1,5 @@
1
- import { CompositeProjectionConfig, LayoutConfig, ProjectionParameters as ProjectionParameters$1, TerritoryConfig } from '@atlas-composer/specification';
1
+ import { CompositeProjectionConfig } from '@atlas-composer/specification';
2
2
 
3
- /**
4
- * Standalone Composite Projection Loader (Zero Dependencies)
5
- *
6
- * A pure JavaScript/TypeScript module that consumes exported composite projection
7
- * configurations and creates D3-compatible projections using a plugin architecture.
8
- *
9
- * This package uses @atlas-composer/projection-core for the shared composite
10
- * projection building logic. Users must register projection factories before
11
- * loading configurations.
12
- *
13
- * @example
14
- * ```typescript
15
- * // Register projections first
16
- * import * as d3 from 'd3-geo'
17
- * import { registerProjection, loadCompositeProjection } from './standalone-projection-loader'
18
- *
19
- * registerProjection('mercator', () => d3.geoMercator())
20
- * registerProjection('albers', () => d3.geoAlbers())
21
- *
22
- * // Then load your configuration
23
- * const projection = loadCompositeProjection(config, { width: 800, height: 600 })
24
- * ```
25
- *
26
- * @packageDocumentation
27
- */
28
-
29
- /**
30
- * Generic projection-like interface that matches D3 projections
31
- * without requiring d3-geo as a dependency
32
- *
33
- * Note: D3 projections use getter/setter pattern where calling without
34
- * arguments returns the current value, and with arguments sets and returns this.
35
- */
36
3
  interface ProjectionLike {
37
4
  (coordinates: [number, number]): [number, number] | null;
38
5
  center?: {
@@ -73,9 +40,6 @@ interface ProjectionLike {
73
40
  fitWidth?: (width: number, object: any) => ProjectionLike;
74
41
  fitHeight?: (height: number, object: any) => ProjectionLike;
75
42
  }
76
- /**
77
- * Stream protocol interface for D3 geographic transforms
78
- */
79
43
  interface StreamLike {
80
44
  point: (x: number, y: number) => void;
81
45
  lineStart: () => void;
@@ -84,166 +48,26 @@ interface StreamLike {
84
48
  polygonEnd: () => void;
85
49
  sphere?: () => void;
86
50
  }
87
- /**
88
- * Factory function that creates a projection instance
89
- */
90
51
  type ProjectionFactory = () => ProjectionLike;
91
52
 
92
- /**
93
- * Projection parameters with loader-specific extensions.
94
- * @deprecated Specification now includes all legacy parameters
95
- */
96
- type ProjectionParameters = ProjectionParameters$1;
97
- /**
98
- * Exported configuration format.
99
- * Alias for CompositeProjectionConfig for backward compatibility.
100
- * @deprecated Use CompositeProjectionConfig from @atlas-composer/specification
101
- */
102
- type ExportedConfig = CompositeProjectionConfig;
103
- /**
104
- * Territory configuration.
105
- * Alias for TerritoryConfig for backward compatibility.
106
- * @deprecated Use TerritoryConfig from @atlas-composer/specification
107
- */
108
- type Territory = TerritoryConfig;
109
- /**
110
- * Layout configuration.
111
- * Alias for LayoutConfig for backward compatibility.
112
- * @deprecated Use LayoutConfig from @atlas-composer/specification
113
- */
114
- type Layout = LayoutConfig;
115
- /**
116
- * Options for creating the composite projection
117
- */
118
53
  interface LoaderOptions {
119
- /** Canvas width in pixels */
120
54
  width: number;
121
- /** Canvas height in pixels */
122
55
  height: number;
123
- /** Whether to apply clipping to territories (default: true) */
124
56
  enableClipping?: boolean;
125
- /** Debug mode - logs territory selection (default: false) */
126
57
  debug?: boolean;
127
58
  }
128
- /**
129
- * Register a projection factory with a given ID
130
- *
131
- * @example
132
- * ```typescript
133
- * import * as d3 from 'd3-geo'
134
- * import { registerProjection } from '@atlas-composer/projection-loader'
135
- *
136
- * registerProjection('mercator', () => d3.geoMercator())
137
- * registerProjection('albers', () => d3.geoAlbers())
138
- * ```
139
- *
140
- * @param id - Projection identifier (e.g., 'mercator', 'albers')
141
- * @param factory - Function that creates a new projection instance
142
- */
143
- declare function registerProjection(id: string, factory: ProjectionFactory): void;
144
- /**
145
- * Register multiple projections at once
146
- *
147
- * @example
148
- * ```typescript
149
- * import * as d3 from 'd3-geo'
150
- * import { registerProjections } from '@atlas-composer/projection-loader'
151
- *
152
- * registerProjections({
153
- * 'mercator': () => d3.geoMercator(),
154
- * 'albers': () => d3.geoAlbers(),
155
- * 'conic-equal-area': () => d3.geoConicEqualArea()
156
- * })
157
- * ```
158
- *
159
- * @param factories - Object mapping projection IDs to factory functions
160
- */
161
- declare function registerProjections(factories: Record<string, ProjectionFactory>): void;
162
- /**
163
- * Unregister a projection
164
- *
165
- * @param id - Projection identifier to remove
166
- * @returns True if the projection was removed, false if it wasn't registered
167
- */
168
- declare function unregisterProjection(id: string): boolean;
169
- /**
170
- * Clear all registered projections
171
- */
172
- declare function clearProjections(): void;
173
- /**
174
- * Get list of currently registered projection IDs
175
- *
176
- * @returns Array of registered projection identifiers
177
- */
178
- declare function getRegisteredProjections(): string[];
179
- /**
180
- * Check if a projection is registered
181
- *
182
- * @param id - Projection identifier to check
183
- * @returns True if the projection is registered
184
- */
185
- declare function isProjectionRegistered(id: string): boolean;
186
- /**
187
- * Create a minimal projection wrapper (similar to d3.geoProjection)
188
- * This allows us to avoid the d3-geo dependency
189
- */
190
- /**
191
- * Create a D3-compatible projection from an exported composite projection configuration
192
- *
193
- * @example
194
- * ```typescript
195
- * import * as d3 from 'd3-geo'
196
- * import { registerProjection, loadCompositeProjection } from '@atlas-composer/projection-loader'
197
- *
198
- * // Register projections first
199
- * registerProjection('mercator', () => d3.geoMercator())
200
- * registerProjection('albers', () => d3.geoAlbers())
201
- *
202
- * // Load configuration
203
- * const config = JSON.parse(jsonString)
204
- *
205
- * // Create projection
206
- * const projection = loadCompositeProjection(config, {
207
- * width: 800,
208
- * height: 600
209
- * })
210
- *
211
- * // Use with D3
212
- * const path = d3.geoPath(projection)
213
- * svg.selectAll('path')
214
- * .data(countries.features)
215
- * .join('path')
216
- * .attr('d', path)
217
- * ```
218
- *
219
- * @param config - Exported composite projection configuration
220
- * @param options - Canvas dimensions and options
221
- * @returns D3-compatible projection that routes geometry to appropriate sub-projections
222
- */
223
- declare function loadCompositeProjection(config: ExportedConfig, options: LoaderOptions): ProjectionLike;
224
- /**
225
- * Validate an exported configuration
226
- *
227
- * @param config - Configuration to validate
228
- * @returns True if valid, throws error otherwise
229
- */
230
- declare function validateConfig(config: any): config is ExportedConfig;
231
- /**
232
- * Load composite projection from JSON string
233
- *
234
- * @example
235
- * ```typescript
236
- * import * as d3 from 'd3-geo'
237
- * import { registerProjection, loadFromJSON } from '@atlas-composer/projection-loader'
238
- *
239
- * // Register projections first
240
- * registerProjection('mercator', () => d3.geoMercator())
241
- *
242
- * // Load from JSON
243
- * const jsonString = fs.readFileSync('france-composite.json', 'utf-8')
244
- * const projection = loadFromJSON(jsonString, { width: 800, height: 600 })
245
- * ```
246
- */
247
- declare function loadFromJSON(jsonString: string, options: LoaderOptions): ProjectionLike;
59
+ declare class ProjectionLoader {
60
+ private factories;
61
+ register(id: string, factory: ProjectionFactory): void;
62
+ registerAll(factories: Record<string, ProjectionFactory>): void;
63
+ unregister(id: string): boolean;
64
+ clear(): void;
65
+ getRegistered(): string[];
66
+ isRegistered(id: string): boolean;
67
+ load(config: CompositeProjectionConfig, options: LoaderOptions): ProjectionLike;
68
+ loadFromJSON(jsonString: string, options: LoaderOptions): ProjectionLike;
69
+ private createSubProjection;
70
+ }
71
+ declare function validateConfig(config: unknown): asserts config is CompositeProjectionConfig;
248
72
 
249
- 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 };
73
+ export { type LoaderOptions, type ProjectionFactory, type ProjectionLike, ProjectionLoader, type StreamLike, validateConfig };
package/dist/index.js CHANGED
@@ -173,188 +173,183 @@ function buildCompositeProjection(config) {
173
173
  return composite;
174
174
  }
175
175
 
176
- // src/standalone-projection-loader.ts
177
- function resolveI18nString(value) {
178
- if (typeof value === "string") {
179
- return value;
176
+ // src/projection-loader.ts
177
+ var ProjectionLoader = class {
178
+ factories = /* @__PURE__ */ new Map();
179
+ register(id, factory) {
180
+ this.factories.set(id, factory);
180
181
  }
181
- return value.en || Object.values(value).find((v) => typeof v === "string") || "";
182
- }
183
- var projectionRegistry = /* @__PURE__ */ new Map();
184
- function registerProjection(id, factory) {
185
- projectionRegistry.set(id, factory);
186
- }
187
- function registerProjections(factories) {
188
- for (const [id, factory] of Object.entries(factories)) {
189
- registerProjection(id, factory);
190
- }
191
- }
192
- function unregisterProjection(id) {
193
- return projectionRegistry.delete(id);
194
- }
195
- function clearProjections() {
196
- projectionRegistry.clear();
197
- }
198
- function getRegisteredProjections() {
199
- return Array.from(projectionRegistry.keys());
200
- }
201
- function isProjectionRegistered(id) {
202
- return projectionRegistry.has(id);
203
- }
204
- function loadCompositeProjection(config, options) {
205
- const { width, height, debug = false } = options;
206
- if (config.version !== "1.0") {
207
- throw new Error(`Unsupported configuration version: ${config.version}`);
208
- }
209
- if (!config.territories || config.territories.length === 0) {
210
- throw new Error("Configuration must contain at least one territory");
211
- }
212
- const entries = config.territories.map((territory) => {
213
- const proj = createSubProjection(territory, width, height, config.referenceScale, debug);
214
- return {
215
- id: territory.code,
216
- name: resolveI18nString(territory.name),
217
- projection: proj,
218
- bounds: {
219
- minLon: territory.bounds[0][0],
220
- minLat: territory.bounds[0][1],
221
- maxLon: territory.bounds[1][0],
222
- maxLat: territory.bounds[1][1]
223
- }
224
- };
225
- });
226
- if (debug) {
227
- console.log("[CompositeProjection] Created sub-projections:", {
228
- territories: config.territories.map((t) => ({ code: t.code, name: resolveI18nString(t.name) })),
229
- count: entries.length
230
- });
231
- }
232
- const composite = buildCompositeProjection({ entries, debug });
233
- return composite;
234
- }
235
- function createSubProjection(territory, width, height, referenceScale, debug) {
236
- var _a, _b, _c;
237
- const { layout } = territory;
238
- const projectionId = territory.projection.id;
239
- const parameters = territory.projection.parameters;
240
- if (!projectionId || !parameters) {
241
- throw new Error(`Territory ${territory.code} missing projection configuration`);
242
- }
243
- const factory = projectionRegistry.get(projectionId);
244
- if (!factory) {
245
- const registered = getRegisteredProjections();
246
- const availableList = registered.length > 0 ? registered.join(", ") : "none";
247
- throw new Error(
248
- `Projection "${projectionId}" is not registered. Available projections: ${availableList}. Use registerProjection('${projectionId}', factory) to register it.`
249
- );
250
- }
251
- const projection = factory();
252
- const hasFocus = parameters.focusLongitude !== void 0 && parameters.focusLatitude !== void 0;
253
- const projFamily = territory.projection.family;
254
- if (hasFocus && projFamily === "CONIC" && projection.rotate) {
255
- projection.rotate([-parameters.focusLongitude, -parameters.focusLatitude, 0]);
256
- } else if (hasFocus && projection.center) {
257
- projection.center([parameters.focusLongitude, parameters.focusLatitude]);
258
- } else if (parameters.center && projection.center) {
259
- projection.center(parameters.center);
182
+ registerAll(factories) {
183
+ for (const [id, factory] of Object.entries(factories)) {
184
+ this.register(id, factory);
185
+ }
260
186
  }
261
- if (parameters.rotate && projection.rotate && !hasFocus) {
262
- const rotate = Array.isArray(parameters.rotate) ? [...parameters.rotate, 0, 0].slice(0, 3) : [0, 0, 0];
263
- projection.rotate(rotate);
187
+ unregister(id) {
188
+ return this.factories.delete(id);
264
189
  }
265
- if (parameters.parallels && projection.parallels) {
266
- const parallels = Array.isArray(parameters.parallels) ? [...parameters.parallels, 0].slice(0, 2) : [0, 60];
267
- projection.parallels(parallels);
190
+ clear() {
191
+ this.factories.clear();
268
192
  }
269
- if (projection.scale && parameters.scaleMultiplier) {
270
- const effectiveReferenceScale = referenceScale || 2700;
271
- const calculatedScale = effectiveReferenceScale * parameters.scaleMultiplier;
272
- projection.scale(calculatedScale);
193
+ getRegistered() {
194
+ return Array.from(this.factories.keys());
273
195
  }
274
- if (parameters.clipAngle && projection.clipAngle) {
275
- projection.clipAngle(parameters.clipAngle);
196
+ isRegistered(id) {
197
+ return this.factories.has(id);
276
198
  }
277
- if (parameters.precision && projection.precision) {
278
- projection.precision(parameters.precision);
199
+ load(config, options) {
200
+ const { width, height, debug = false } = options;
201
+ if (config.version !== "1.0") {
202
+ throw new Error(`Unsupported configuration version: ${config.version}`);
203
+ }
204
+ if (!config.territories || config.territories.length === 0) {
205
+ throw new Error("Configuration must contain at least one territory");
206
+ }
207
+ const entries = config.territories.map((territory) => {
208
+ const proj = this.createSubProjection(territory, width, height, config.referenceScale, debug);
209
+ return {
210
+ id: territory.code,
211
+ projection: proj,
212
+ bounds: {
213
+ minLon: territory.bounds[0][0],
214
+ minLat: territory.bounds[0][1],
215
+ maxLon: territory.bounds[1][0],
216
+ maxLat: territory.bounds[1][1]
217
+ }
218
+ };
219
+ });
220
+ if (debug) {
221
+ console.log("[CompositeProjection] Created sub-projections:", {
222
+ territories: config.territories.map((t) => t.code),
223
+ count: entries.length
224
+ });
225
+ }
226
+ return buildCompositeProjection({ entries, debug });
279
227
  }
280
- if (projection.translate) {
281
- const [offsetX, offsetY] = layout.translateOffset || [0, 0];
282
- projection.translate([
283
- width / 2 + offsetX,
284
- height / 2 + offsetY
285
- ]);
228
+ loadFromJSON(jsonString, options) {
229
+ let config;
230
+ try {
231
+ config = JSON.parse(jsonString);
232
+ } catch (error) {
233
+ throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
234
+ }
235
+ validateConfig(config);
236
+ return this.load(config, options);
286
237
  }
287
- if (layout.pixelClipExtent && projection.clipExtent) {
288
- const territoryCenter = ((_a = projection.translate) == null ? void 0 : _a.call(projection)) || [width / 2, height / 2];
289
- const clipExtent = calculateClipExtentFromPixelOffset(
290
- territoryCenter,
291
- layout.pixelClipExtent
292
- );
293
- projection.clipExtent(clipExtent);
294
- if (debug) {
295
- console.log(
296
- `[Clipping] Applied pixelClipExtent for ${territory.code}:`,
297
- `original: ${JSON.stringify(layout.pixelClipExtent)} -> transformed: ${JSON.stringify(clipExtent)}`
238
+ createSubProjection(territory, width, height, referenceScale, debug) {
239
+ var _a, _b, _c;
240
+ const { layout } = territory;
241
+ const projectionId = territory.projection.id;
242
+ const parameters = territory.projection.parameters;
243
+ if (!projectionId || !parameters) {
244
+ throw new Error(`Territory ${territory.code} missing projection configuration`);
245
+ }
246
+ const factory = this.factories.get(projectionId);
247
+ if (!factory) {
248
+ const registered = this.getRegistered();
249
+ const availableList = registered.length > 0 ? registered.join(", ") : "none";
250
+ throw new Error(
251
+ `Projection "${projectionId}" is not registered. Available projections: ${availableList}. Use loader.register('${projectionId}', factory) to register it.`
298
252
  );
299
253
  }
300
- } else if (projection.clipExtent) {
301
- const bounds = territory.bounds;
302
- if (bounds && bounds.length === 2 && bounds[0].length === 2 && bounds[1].length === 2) {
303
- const scale = ((_b = projection.scale) == null ? void 0 : _b.call(projection)) || 1;
304
- const translate = ((_c = projection.translate) == null ? void 0 : _c.call(projection)) || [0, 0];
305
- const padding = scale * 0.1;
306
- const clipExtent = [
307
- [translate[0] - padding, translate[1] - padding],
308
- [translate[0] + padding, translate[1] + padding]
309
- ];
254
+ const projection = factory();
255
+ const hasFocus = parameters.focusLongitude !== void 0 && parameters.focusLatitude !== void 0;
256
+ const projFamily = territory.projection.family;
257
+ if (hasFocus && projFamily === "CONIC" && projection.rotate) {
258
+ projection.rotate([-parameters.focusLongitude, -parameters.focusLatitude, 0]);
259
+ } else if (hasFocus && projection.center) {
260
+ projection.center([parameters.focusLongitude, parameters.focusLatitude]);
261
+ } else if (parameters.center && projection.center) {
262
+ projection.center(parameters.center);
263
+ }
264
+ if (parameters.rotate && projection.rotate && !hasFocus) {
265
+ const rotate = Array.isArray(parameters.rotate) ? [...parameters.rotate, 0, 0].slice(0, 3) : [0, 0, 0];
266
+ projection.rotate(rotate);
267
+ }
268
+ if (parameters.parallels && projection.parallels) {
269
+ const parallels = Array.isArray(parameters.parallels) ? [...parameters.parallels, 0].slice(0, 2) : [0, 60];
270
+ projection.parallels(parallels);
271
+ }
272
+ if (projection.scale && parameters.scaleMultiplier) {
273
+ const effectiveReferenceScale = referenceScale || 2700;
274
+ const calculatedScale = effectiveReferenceScale * parameters.scaleMultiplier;
275
+ projection.scale(calculatedScale);
276
+ }
277
+ if (parameters.clipAngle && projection.clipAngle) {
278
+ projection.clipAngle(parameters.clipAngle);
279
+ }
280
+ if (parameters.precision && projection.precision) {
281
+ projection.precision(parameters.precision);
282
+ }
283
+ if (projection.translate) {
284
+ const [offsetX, offsetY] = layout.translateOffset || [0, 0];
285
+ projection.translate([
286
+ width / 2 + offsetX,
287
+ height / 2 + offsetY
288
+ ]);
289
+ }
290
+ if (layout.pixelClipExtent && projection.clipExtent) {
291
+ const territoryCenter = ((_a = projection.translate) == null ? void 0 : _a.call(projection)) || [width / 2, height / 2];
292
+ const clipExtent = calculateClipExtentFromPixelOffset(
293
+ territoryCenter,
294
+ layout.pixelClipExtent
295
+ );
310
296
  projection.clipExtent(clipExtent);
311
297
  if (debug) {
312
- console.log(`[Clipping] Applied default clip extent for ${territory.code}:`, clipExtent);
298
+ console.log(
299
+ `[Clipping] Applied pixelClipExtent for ${territory.code}:`,
300
+ `original: ${JSON.stringify(layout.pixelClipExtent)} -> transformed: ${JSON.stringify(clipExtent)}`
301
+ );
302
+ }
303
+ } else if (projection.clipExtent) {
304
+ const bounds = territory.bounds;
305
+ if (bounds && bounds.length === 2 && bounds[0].length === 2 && bounds[1].length === 2) {
306
+ const scale = ((_b = projection.scale) == null ? void 0 : _b.call(projection)) || 1;
307
+ const translate = ((_c = projection.translate) == null ? void 0 : _c.call(projection)) || [0, 0];
308
+ const padding = scale * 0.1;
309
+ const clipExtent = [
310
+ [translate[0] - padding, translate[1] - padding],
311
+ [translate[0] + padding, translate[1] + padding]
312
+ ];
313
+ projection.clipExtent(clipExtent);
314
+ if (debug) {
315
+ console.log(`[Clipping] Applied default clip extent for ${territory.code}:`, clipExtent);
316
+ }
313
317
  }
314
318
  }
319
+ return projection;
315
320
  }
316
- return projection;
317
- }
321
+ };
318
322
  function validateConfig(config) {
319
323
  if (!config || typeof config !== "object") {
320
324
  throw new Error("Configuration must be an object");
321
325
  }
322
- if (!config.version) {
326
+ const cfg = config;
327
+ if (!cfg.version) {
323
328
  throw new Error("Configuration must have a version field");
324
329
  }
325
- if (!config.metadata || !config.metadata.atlasId) {
330
+ if (!cfg.metadata || typeof cfg.metadata !== "object" || !cfg.metadata.atlasId) {
326
331
  throw new Error("Configuration must have metadata with atlasId");
327
332
  }
328
- if (!config.territories || !Array.isArray(config.territories)) {
333
+ if (!cfg.territories || !Array.isArray(cfg.territories)) {
329
334
  throw new Error("Configuration must have territories array");
330
335
  }
331
- if (config.territories.length === 0) {
336
+ if (cfg.territories.length === 0) {
332
337
  throw new Error("Configuration must have at least one territory");
333
338
  }
334
- for (const territory of config.territories) {
339
+ for (const territory of cfg.territories) {
335
340
  if (!territory.code) {
336
341
  throw new Error(`Territory missing required field 'code': ${JSON.stringify(territory)}`);
337
342
  }
338
- if (!territory.projection || !territory.projection.id || !territory.projection.parameters) {
343
+ const proj = territory.projection;
344
+ if (!proj || !proj.id || !proj.parameters) {
339
345
  throw new Error(`Territory ${territory.code} missing projection configuration. Required: projection.id and projection.parameters`);
340
346
  }
341
347
  if (!territory.bounds) {
342
348
  throw new Error(`Territory ${territory.code} missing bounds`);
343
349
  }
344
350
  }
345
- return true;
346
- }
347
- function loadFromJSON(jsonString, options) {
348
- let config;
349
- try {
350
- config = JSON.parse(jsonString);
351
- } catch (error) {
352
- throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
353
- }
354
- validateConfig(config);
355
- return loadCompositeProjection(config, options);
356
351
  }
357
352
 
358
- export { clearProjections, getRegisteredProjections, isProjectionRegistered, loadCompositeProjection, loadFromJSON, registerProjection, registerProjections, unregisterProjection, validateConfig };
353
+ export { ProjectionLoader, validateConfig };
359
354
  //# sourceMappingURL=index.js.map
360
355
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../projection-core/src/bounds/checker.ts","../../projection-core/src/bounds/clip-extent.ts","../../projection-core/src/invert/validator.ts","../../projection-core/src/stream/multiplexer.ts","../../projection-core/src/stream/point-capture.ts","../../projection-core/src/composite/builder.ts","../src/standalone-projection-loader.ts"],"names":["normalizeBounds"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Bounds Checker\n *\n * Pure functions for checking if geographic points fall within bounds.\n * Used by composite projections to route points to the correct sub-projection.\n */\n\nimport type { GeoBounds, GeoBoundsArray } from '../types'\n\n/**\n * Check if a geographic point is within the given bounds\n *\n * @param lon - Longitude of the point\n * @param lat - Latitude of the point\n * @param bounds - Geographic bounds to check against\n * @param tolerance - Optional tolerance for edge cases (default: 0)\n * @returns True if the point is within bounds\n *\n * @example\n * ```typescript\n * const bounds = { minLon: -10, minLat: 35, maxLon: 5, maxLat: 45 }\n *\n * isPointInBounds(-5, 40, bounds) // true (inside Portugal/Spain)\n * isPointInBounds(10, 50, bounds) // false (outside bounds)\n * isPointInBounds(-10, 35, bounds, 0.01) // true (on edge, with tolerance)\n * ```\n */\nexport function isPointInBounds(\n lon: number,\n lat: number,\n bounds: GeoBounds,\n tolerance = 0,\n): boolean {\n return (\n lon >= bounds.minLon - tolerance\n && lon <= bounds.maxLon + tolerance\n && lat >= bounds.minLat - tolerance\n && lat <= bounds.maxLat + tolerance\n )\n}\n\n/**\n * Convert a bounds array to a GeoBounds object\n *\n * @param bounds - Bounds array [[minLon, minLat], [maxLon, maxLat]]\n * @returns GeoBounds object\n *\n * @example\n * ```typescript\n * const arrayBounds: GeoBoundsArray = [[-10, 35], [5, 45]]\n * const objectBounds = boundsFromArray(arrayBounds)\n * // { minLon: -10, minLat: 35, maxLon: 5, maxLat: 45 }\n * ```\n */\nexport function boundsFromArray(bounds: GeoBoundsArray): GeoBounds {\n return {\n minLon: bounds[0][0],\n minLat: bounds[0][1],\n maxLon: bounds[1][0],\n maxLat: bounds[1][1],\n }\n}\n\n/**\n * Convert a GeoBounds object to a bounds array\n *\n * @param bounds - GeoBounds object\n * @returns Bounds array [[minLon, minLat], [maxLon, maxLat]]\n *\n * @example\n * ```typescript\n * const objectBounds = { minLon: -10, minLat: 35, maxLon: 5, maxLat: 45 }\n * const arrayBounds = boundsToArray(objectBounds)\n * // [[-10, 35], [5, 45]]\n * ```\n */\nexport function boundsToArray(bounds: GeoBounds): GeoBoundsArray {\n return [\n [bounds.minLon, bounds.minLat],\n [bounds.maxLon, bounds.maxLat],\n ]\n}\n","/**\n * Clip Extent Calculator\n *\n * Pure functions for calculating clip extents from geographic bounds\n * or pixel offsets. Used by composite projections to properly clip\n * each territory's rendering area.\n */\n\nimport type { ClipExtentOptions, GeoBounds, ProjectionLike } from '../types'\n\n/**\n * Calculate clip extent from geographic bounds\n *\n * Projects the corner points of geographic bounds to get the corresponding\n * screen coordinates for clipping. This is critical for composite projections\n * to ensure each territory only renders within its designated area.\n *\n * @param projection - The projection to use for transforming bounds\n * @param bounds - Geographic bounds { minLon, minLat, maxLon, maxLat }\n * @param options - Optional configuration (epsilon for padding)\n * @returns Clip extent [[x0, y0], [x1, y1]] or null if projection fails\n *\n * @example\n * ```typescript\n * const bounds = { minLon: -10, minLat: 35, maxLon: 5, maxLat: 45 }\n * const clipExtent = calculateClipExtentFromBounds(projection, bounds)\n *\n * if (clipExtent) {\n * projection.clipExtent(clipExtent)\n * }\n * ```\n */\nexport function calculateClipExtentFromBounds(\n projection: ProjectionLike,\n bounds: GeoBounds,\n options: ClipExtentOptions = {},\n): [[number, number], [number, number]] | null {\n const { epsilon = 1e-6 } = options\n\n // Project the corner points\n // Note: Geographic coordinates are [lon, lat] where:\n // - minLon is west, maxLon is east\n // - minLat is south, maxLat is north\n // In screen coordinates:\n // - topLeft corresponds to [minLon, maxLat] (northwest corner)\n // - bottomRight corresponds to [maxLon, minLat] (southeast corner)\n const topLeft = projection([\n bounds.minLon + epsilon,\n bounds.maxLat - epsilon,\n ])\n const bottomRight = projection([\n bounds.maxLon - epsilon,\n bounds.minLat + epsilon,\n ])\n\n if (!topLeft || !bottomRight) {\n return null\n }\n\n return [\n [topLeft[0], topLeft[1]],\n [bottomRight[0], bottomRight[1]],\n ]\n}\n\n/**\n * Calculate clip extent from pixel offsets\n *\n * Converts a pixel-based clip extent (relative to territory center)\n * to absolute screen coordinates. Used when clip extent is stored\n * as offsets in exported configurations.\n *\n * @param center - The territory center in screen coordinates [x, y]\n * @param pixelClipExtent - Pixel offsets [x1, y1, x2, y2] relative to center\n * @param epsilon - Small value for padding (default: 1e-6)\n * @returns Clip extent [[x0, y0], [x1, y1]] in absolute screen coordinates\n *\n * @example\n * ```typescript\n * const center = [400, 300] // Territory center in pixels\n * const offsets = [-100, -80, 100, 80] // Relative clip bounds\n *\n * const clipExtent = calculateClipExtentFromPixelOffset(center, offsets)\n * // [[300, 220], [500, 380]]\n *\n * projection.clipExtent(clipExtent)\n * ```\n */\nexport function calculateClipExtentFromPixelOffset(\n center: [number, number],\n pixelClipExtent: [number, number, number, number],\n epsilon = 1e-6,\n): [[number, number], [number, number]] {\n const [x1, y1, x2, y2] = pixelClipExtent\n\n return [\n [center[0] + x1 + epsilon, center[1] + y1 + epsilon],\n [center[0] + x2 - epsilon, center[1] + y2 - epsilon],\n ]\n}\n","/**\n * Invert Validator\n *\n * Validates inverted coordinates against territory bounds to ensure\n * correct territory matching. This is critical for composite projections\n * where multiple territories may overlap in screen space.\n */\n\nimport type { GeoBounds, GeoBoundsArray, InvertOptions, InvertResult, SubProjectionEntry } from '../types'\n\nimport { boundsFromArray, isPointInBounds } from '../bounds/checker'\n\n/**\n * Normalize bounds to GeoBounds format\n */\nfunction normalizeBounds(bounds: GeoBounds | GeoBoundsArray): GeoBounds {\n if ('minLon' in bounds) {\n return bounds\n }\n return boundsFromArray(bounds)\n}\n\n/**\n * Invert screen coordinates with bounds validation\n *\n * Tries each sub-projection's invert method and validates the result\n * against the territory's geographic bounds. This ensures that when\n * multiple territories overlap in screen space (common in composite\n * projections), the correct territory is identified.\n *\n * @param screenCoords - Screen coordinates [x, y] to invert\n * @param entries - Array of sub-projection entries with bounds\n * @param options - Optional configuration (tolerance, debug)\n * @returns InvertResult with coordinates and territoryId, or null if no match\n *\n * @example\n * ```typescript\n * const entries = [\n * { id: 'usa', projection: usaProj, bounds: usaBounds },\n * { id: 'alaska', projection: alaskaProj, bounds: alaskaBounds },\n * ]\n *\n * const result = invertWithBoundsValidation([400, 300], entries)\n *\n * if (result) {\n * console.log(`Clicked on ${result.territoryId} at [${result.coordinates}]`)\n * }\n * ```\n */\nexport function invertWithBoundsValidation(\n screenCoords: [number, number],\n entries: SubProjectionEntry[],\n options: InvertOptions = {},\n): InvertResult | null {\n const { tolerance = 0.01, debug = false } = options\n const [x, y] = screenCoords\n\n for (const entry of entries) {\n const { projection, bounds, id } = entry\n\n if (!projection.invert) {\n continue\n }\n\n try {\n const result = projection.invert([x, y])\n\n if (!result || !Array.isArray(result) || result.length < 2) {\n continue\n }\n\n const [lon, lat] = result\n\n if (bounds) {\n const geoBounds = normalizeBounds(bounds)\n\n if (isPointInBounds(lon, lat, geoBounds, tolerance)) {\n if (debug) {\n console.log(`[Invert] Matched ${id}: [${x}, ${y}] -> [${lon}, ${lat}]`)\n }\n return { coordinates: result as [number, number], territoryId: id }\n }\n else if (debug) {\n console.log(`[Invert] Rejected ${id}: [${lon}, ${lat}] outside bounds`)\n }\n }\n else {\n // No bounds available, accept first successful invert (legacy behavior)\n if (debug) {\n console.log(`[Invert] No bounds for ${id}, accepting [${lon}, ${lat}]`)\n }\n return { coordinates: result as [number, number], territoryId: id }\n }\n }\n catch (error) {\n if (debug) {\n console.warn(`[Invert] Error in ${id}:`, error)\n }\n }\n }\n\n if (debug) {\n console.log(`[Invert] Failed to invert [${x}, ${y}]`)\n }\n\n return null\n}\n","/**\n * Stream Multiplexer\n *\n * Creates a stream that fans out geometry to multiple projections.\n * Used by composite projections to render all territories simultaneously.\n *\n * When d3.geoPath renders geometry through a composite projection,\n * the multiplexed stream ensures each sub-projection receives the\n * full geometry and applies its own clipping/transform.\n */\n\nimport type { ProjectionLike, StreamLike } from '../types'\n\n/**\n * Create a stream multiplexer for multiple projections\n *\n * @param projections - Array of projections to multiplex to\n * @returns Function that creates a multiplexed stream\n *\n * @example\n * ```typescript\n * const multiplex = createStreamMultiplexer([usaProj, alaskaProj, hawaiiProj])\n *\n * // Use as projection.stream method\n * compositeProjection.stream = multiplex\n *\n * // When d3.geoPath renders, all projections receive the geometry\n * const path = d3.geoPath(compositeProjection)\n * svg.selectAll('path')\n * .data(countries.features)\n * .join('path')\n * .attr('d', path)\n * ```\n */\nexport function createStreamMultiplexer(\n projections: ProjectionLike[],\n): (stream: StreamLike) => StreamLike {\n return (stream: StreamLike): StreamLike => {\n const streams = projections.map(p => p.stream(stream))\n\n return {\n point: (x: number, y: number) => {\n for (const s of streams) s.point(x, y)\n },\n lineStart: () => {\n for (const s of streams) s.lineStart()\n },\n lineEnd: () => {\n for (const s of streams) s.lineEnd()\n },\n polygonStart: () => {\n for (const s of streams) s.polygonStart()\n },\n polygonEnd: () => {\n for (const s of streams) s.polygonEnd()\n },\n sphere: () => {\n for (const s of streams) {\n if (s.sphere)\n s.sphere()\n }\n },\n }\n }\n}\n","/**\n * Point Capture Stream\n *\n * Creates a stream that captures projected point coordinates.\n * Used by composite projections to determine which sub-projection\n * successfully projects a geographic point.\n *\n * The pattern follows d3-composite-projections (albersUsa) approach:\n * 1. Create a point stream that stores the projected coordinates\n * 2. Pipe geographic coordinates through each sub-projection's stream\n * 3. Check if the point was captured (projection succeeded)\n */\n\nimport type { PointCaptureResult, StreamLike } from '../types'\n\n/**\n * Create a point capture stream for projection routing\n *\n * @returns Object with pointStream, getCapturedPoint, and resetCapture\n *\n * @example\n * ```typescript\n * const { pointStream, getCapturedPoint, resetCapture } = createPointCaptureStream()\n *\n * // Create capture stream for a projection\n * const captureStream = projection.stream(pointStream)\n *\n * // Project a point\n * resetCapture()\n * captureStream.point(longitude, latitude)\n *\n * // Check result\n * const projected = getCapturedPoint()\n * if (projected) {\n * console.log(`Projected to [${projected[0]}, ${projected[1]}]`)\n * }\n * ```\n */\nexport function createPointCaptureStream(): PointCaptureResult {\n let capturedPoint: [number, number] | null = null\n\n const pointStream: StreamLike = {\n point: (x: number, y: number) => {\n capturedPoint = [x, y]\n },\n lineStart: () => {},\n lineEnd: () => {},\n polygonStart: () => {},\n polygonEnd: () => {},\n sphere: () => {},\n }\n\n return {\n pointStream,\n getCapturedPoint: () => capturedPoint,\n resetCapture: () => {\n capturedPoint = null\n },\n }\n}\n","/**\n * Composite Projection Builder\n *\n * Creates a D3-compatible composite projection from multiple sub-projections.\n * This follows the same pattern as d3-composite-projections (albersUsa).\n *\n * The composite projection:\n * 1. Routes projection calls to the correct sub-projection based on bounds\n * 2. Multiplexes geometry streaming to all sub-projections for rendering\n * 3. Validates invert operations against territory bounds\n */\n\nimport type {\n CompositeProjectionConfig,\n GeoBounds,\n GeoBoundsArray,\n ProjectionLike,\n} from '../types'\n\nimport { boundsFromArray, isPointInBounds } from '../bounds/checker'\nimport { invertWithBoundsValidation } from '../invert/validator'\nimport { createStreamMultiplexer } from '../stream/multiplexer'\nimport { createPointCaptureStream } from '../stream/point-capture'\n\n/**\n * Normalize bounds to GeoBounds format\n */\nfunction normalizeBounds(bounds: GeoBounds | GeoBoundsArray): GeoBounds {\n if ('minLon' in bounds) {\n return bounds\n }\n return boundsFromArray(bounds)\n}\n\n/**\n * Build a composite projection from sub-projection entries\n *\n * Creates a D3-compatible projection that routes geographic points to the\n * appropriate sub-projection based on territory bounds. When rendering,\n * geometry is multiplexed to all sub-projections so each territory renders\n * its portion of the world.\n *\n * @param config - Configuration with entries and optional debug flag\n * @returns D3-compatible composite projection\n *\n * @example\n * ```typescript\n * // Create sub-projections (e.g., using d3-geo)\n * const usaProj = d3.geoAlbers()\n * .center([0, 38])\n * .rotate([96, 0])\n * .scale(1000)\n *\n * const alaskaProj = d3.geoConicEqualArea()\n * .center([0, 64])\n * .rotate([154, 0])\n * .scale(400)\n *\n * // Build composite\n * const composite = buildCompositeProjection({\n * entries: [\n * {\n * id: 'usa',\n * name: 'Continental USA',\n * projection: usaProj,\n * bounds: { minLon: -125, minLat: 24, maxLon: -66, maxLat: 50 }\n * },\n * {\n * id: 'alaska',\n * name: 'Alaska',\n * projection: alaskaProj,\n * bounds: { minLon: -180, minLat: 51, maxLon: -129, maxLat: 72 }\n * },\n * ],\n * })\n *\n * // Use with D3\n * const path = d3.geoPath(composite)\n * svg.selectAll('path')\n * .data(countries.features)\n * .join('path')\n * .attr('d', path)\n * ```\n */\nexport function buildCompositeProjection(\n config: CompositeProjectionConfig,\n): ProjectionLike {\n const { entries, debug = false } = config\n\n if (entries.length === 0) {\n throw new Error('Cannot build composite projection with no entries')\n }\n\n // Create point capture mechanism\n const { pointStream, getCapturedPoint, resetCapture } = createPointCaptureStream()\n\n // Create point capture streams for each entry\n const entryStreams = entries.map(entry => ({\n entry,\n stream: entry.projection.stream(pointStream),\n }))\n\n // Main projection function - routes to correct sub-projection based on bounds\n const project = (coordinates: [number, number]): [number, number] | null => {\n const [lon, lat] = coordinates\n resetCapture()\n\n for (const { entry, stream } of entryStreams) {\n if (entry.bounds) {\n const bounds = normalizeBounds(entry.bounds)\n\n if (isPointInBounds(lon, lat, bounds)) {\n stream.point(lon, lat)\n const captured = getCapturedPoint()\n\n if (captured) {\n return captured\n }\n }\n }\n }\n\n return null\n }\n\n // Cast to ProjectionLike and add required methods\n const composite = project as ProjectionLike\n\n // Stream method - multiplex to all sub-projections\n composite.stream = createStreamMultiplexer(entries.map(e => e.projection))\n\n // Invert method - with bounds validation\n composite.invert = (coords: [number, number]) => {\n const result = invertWithBoundsValidation(coords, entries, { debug })\n return result?.coordinates ?? null\n }\n\n // Scale getter/setter - returns reference from first sub-projection\n composite.scale = function (_s?: number): any {\n if (arguments.length === 0) {\n return entries[0]?.projection.scale() ?? 1\n }\n // Setting scale on composite is a no-op (individual territories manage their scales)\n return composite\n } as ProjectionLike['scale']\n\n // Translate getter/setter - returns reference translate\n composite.translate = function (_t?: [number, number]): any {\n if (arguments.length === 0) {\n return entries[0]?.projection.translate() ?? [0, 0]\n }\n // Setting translate on composite is a no-op (individual territories manage their translates)\n return composite\n } as ProjectionLike['translate']\n\n return composite\n}\n","/**\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 uses @atlas-composer/projection-core for the shared composite\n * projection building logic. Users must register projection factories before\n * 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\nimport type { ProjectionLike as CoreProjectionLike, SubProjectionEntry } from '@atlas-composer/projection-core'\nimport type {\n CompositeProjectionConfig,\n GeoBounds,\n I18nString,\n LayoutConfig,\n ProjectionParameters as SpecProjectionParameters,\n TerritoryConfig,\n} from '@atlas-composer/specification'\n\nimport {\n buildCompositeProjection,\n calculateClipExtentFromPixelOffset,\n} from '@atlas-composer/projection-core'\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 clipAngle?: {\n (): number\n (angle: number): 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// Re-export specification types for convenience\nexport type { CompositeProjectionConfig, GeoBounds, I18nString, LayoutConfig, TerritoryConfig }\n\n/**\n * Resolve an I18nString to a plain string.\n * For i18n objects, returns the English value or the first available translation.\n */\nfunction resolveI18nString(value: I18nString): string {\n if (typeof value === 'string') {\n return value\n }\n // Return English first, or the first available language\n return value.en || Object.values(value).find(v => typeof v === 'string') || ''\n}\n\n/**\n * Projection parameters with loader-specific extensions.\n * @deprecated Specification now includes all legacy parameters\n */\nexport type ProjectionParameters = SpecProjectionParameters\n\n/**\n * Exported configuration format.\n * Alias for CompositeProjectionConfig for backward compatibility.\n * @deprecated Use CompositeProjectionConfig from @atlas-composer/specification\n */\nexport type ExportedConfig = CompositeProjectionConfig\n\n/**\n * Territory configuration.\n * Alias for TerritoryConfig for backward compatibility.\n * @deprecated Use TerritoryConfig from @atlas-composer/specification\n */\nexport type Territory = TerritoryConfig\n\n/**\n * Layout configuration.\n * Alias for LayoutConfig for backward compatibility.\n * @deprecated Use LayoutConfig from @atlas-composer/specification\n */\nexport type Layout = LayoutConfig\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 */\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 and convert to SubProjectionEntry format\n const entries: SubProjectionEntry[] = config.territories.map((territory) => {\n const proj = createSubProjection(territory, width, height, config.referenceScale, debug)\n\n return {\n id: territory.code,\n name: resolveI18nString(territory.name),\n projection: proj as CoreProjectionLike,\n bounds: {\n minLon: territory.bounds[0][0],\n minLat: territory.bounds[0][1],\n maxLon: territory.bounds[1][0],\n maxLat: territory.bounds[1][1],\n },\n }\n })\n\n if (debug) {\n console.log('[CompositeProjection] Created sub-projections:', {\n territories: config.territories.map(t => ({ code: t.code, name: resolveI18nString(t.name) })),\n count: entries.length,\n })\n }\n\n // Use projection-core to build the composite projection\n const composite = buildCompositeProjection({ entries, debug })\n\n return composite as ProjectionLike\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 referenceScale?: number,\n debug?: boolean,\n): ProjectionLike {\n // Extract projection info and parameters from nested projection object\n const { layout } = territory\n const projectionId = territory.projection.id\n const parameters = territory.projection.parameters\n\n if (!projectionId || !parameters) {\n throw new Error(`Territory ${territory.code} missing projection configuration`)\n }\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 // Convert legacy focusLongitude/focusLatitude to center/rotate\n const hasFocus = parameters.focusLongitude !== undefined && parameters.focusLatitude !== undefined\n const projFamily = territory.projection.family\n\n // Apply parameters based on projection family\n if (hasFocus && projFamily === 'CONIC' && projection.rotate) {\n // Conic projections use rotate: [-focusLon, -focusLat, 0]\n projection.rotate([-parameters.focusLongitude!, -parameters.focusLatitude!, 0])\n }\n else if (hasFocus && projection.center) {\n // Other projections use center: [focusLon, focusLat]\n projection.center([parameters.focusLongitude!, parameters.focusLatitude!])\n }\n else if (parameters.center && projection.center) {\n // Standard center format\n projection.center(parameters.center)\n }\n\n if (parameters.rotate && projection.rotate && !hasFocus) {\n // Only apply rotate if not already set by focusLongitude/focusLatitude\n // Ensure rotate has exactly 3 elements\n const rotate = Array.isArray(parameters.rotate)\n ? [...parameters.rotate, 0, 0].slice(0, 3) as [number, number, number]\n : [0, 0, 0] as [number, number, number]\n projection.rotate(rotate)\n }\n\n if (parameters.parallels && projection.parallels) {\n // Ensure parallels has exactly 2 elements\n const parallels = Array.isArray(parameters.parallels)\n ? [...parameters.parallels, 0].slice(0, 2) as [number, number]\n : [0, 60] as [number, number]\n projection.parallels(parallels)\n }\n\n // Handle scale using scaleMultiplier and referenceScale\n if (projection.scale && parameters.scaleMultiplier) {\n const effectiveReferenceScale = referenceScale || 2700 // Default reference scale\n const calculatedScale = effectiveReferenceScale * parameters.scaleMultiplier\n projection.scale(calculatedScale)\n }\n\n // Apply additional parameters\n if (parameters.clipAngle && projection.clipAngle) {\n projection.clipAngle(parameters.clipAngle)\n }\n\n if (parameters.precision && projection.precision) {\n projection.precision(parameters.precision)\n }\n\n // Apply layout translate\n if (projection.translate) {\n const [offsetX, offsetY] = layout.translateOffset || [0, 0] // Default to center if missing\n projection.translate([\n width / 2 + offsetX,\n height / 2 + offsetY,\n ])\n }\n\n // Apply clipping - this is CRITICAL for composite projections\n // Each sub-projection MUST have clipping to avoid geometry processing conflicts\n if (layout.pixelClipExtent && projection.clipExtent) {\n // Get territory center from translate\n const territoryCenter = projection.translate?.() || [width / 2, height / 2]\n\n // Use core utility for clip extent calculation\n const clipExtent = calculateClipExtentFromPixelOffset(\n territoryCenter,\n layout.pixelClipExtent,\n )\n\n projection.clipExtent(clipExtent)\n if (debug) {\n console.log(\n `[Clipping] Applied pixelClipExtent for ${territory.code}:`,\n `original: ${JSON.stringify(layout.pixelClipExtent)} -> transformed: ${JSON.stringify(clipExtent)}`,\n )\n }\n }\n else if (projection.clipExtent) {\n // If no clip extent is specified, create a default one based on territory bounds\n const bounds = territory.bounds\n if (bounds && bounds.length === 2 && bounds[0].length === 2 && bounds[1].length === 2) {\n // Convert geographic bounds to pixel bounds (approximate)\n const scale = projection.scale?.() || 1\n const translate = projection.translate?.() || [0, 0]\n\n // Create a reasonable clip extent based on the geographic bounds\n const padding = scale * 0.1 // 10% padding\n const clipExtent: [[number, number], [number, number]] = [\n [translate[0] - padding, translate[1] - padding],\n [translate[0] + padding, translate[1] + padding],\n ]\n\n projection.clipExtent(clipExtent)\n\n if (debug) {\n console.log(`[Clipping] Applied default clip extent for ${territory.code}:`, clipExtent)\n }\n }\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) {\n throw new Error(`Territory missing required field 'code': ${JSON.stringify(territory)}`)\n }\n\n // Check for required nested projection format\n if (!territory.projection || !territory.projection.id || !territory.projection.parameters) {\n throw new Error(`Territory ${territory.code} missing projection configuration. Required: projection.id and projection.parameters`)\n }\n\n if (!territory.bounds) {\n throw new Error(`Territory ${territory.code} missing 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"]}
1
+ {"version":3,"sources":["../../projection-core/src/bounds/checker.ts","../../projection-core/src/bounds/clip-extent.ts","../../projection-core/src/invert/validator.ts","../../projection-core/src/stream/multiplexer.ts","../../projection-core/src/stream/point-capture.ts","../../projection-core/src/composite/builder.ts","../src/projection-loader.ts"],"names":["normalizeBounds"],"mappings":"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","file":"index.js","sourcesContent":["import type { GeoBounds, GeoBoundsArray } from '../types'\n\nexport function isPointInBounds(\n lon: number,\n lat: number,\n bounds: GeoBounds,\n tolerance = 0,\n): boolean {\n return (\n lon >= bounds.minLon - tolerance\n && lon <= bounds.maxLon + tolerance\n && lat >= bounds.minLat - tolerance\n && lat <= bounds.maxLat + tolerance\n )\n}\n\nexport function boundsFromArray(bounds: GeoBoundsArray): GeoBounds {\n return {\n minLon: bounds[0][0],\n minLat: bounds[0][1],\n maxLon: bounds[1][0],\n maxLat: bounds[1][1],\n }\n}\n\nexport function boundsToArray(bounds: GeoBounds): GeoBoundsArray {\n return [\n [bounds.minLon, bounds.minLat],\n [bounds.maxLon, bounds.maxLat],\n ]\n}\n","import type { ClipExtentOptions, GeoBounds, ProjectionLike } from '../types'\n\nexport function calculateClipExtentFromBounds(\n projection: ProjectionLike,\n bounds: GeoBounds,\n options: ClipExtentOptions = {},\n): [[number, number], [number, number]] | null {\n const { epsilon = 1e-6 } = options\n\n const topLeft = projection([\n bounds.minLon + epsilon,\n bounds.maxLat - epsilon,\n ])\n const bottomRight = projection([\n bounds.maxLon - epsilon,\n bounds.minLat + epsilon,\n ])\n\n if (!topLeft || !bottomRight) {\n return null\n }\n\n return [\n [topLeft[0], topLeft[1]],\n [bottomRight[0], bottomRight[1]],\n ]\n}\n\nexport function calculateClipExtentFromPixelOffset(\n center: [number, number],\n pixelClipExtent: [number, number, number, number],\n epsilon = 1e-6,\n): [[number, number], [number, number]] {\n const [x1, y1, x2, y2] = pixelClipExtent\n\n return [\n [center[0] + x1 + epsilon, center[1] + y1 + epsilon],\n [center[0] + x2 - epsilon, center[1] + y2 - epsilon],\n ]\n}\n","import type { GeoBounds, GeoBoundsArray, InvertOptions, InvertResult, SubProjectionEntry } from '../types'\n\nimport { boundsFromArray, isPointInBounds } from '../bounds/checker'\n\nfunction normalizeBounds(bounds: GeoBounds | GeoBoundsArray): GeoBounds {\n if ('minLon' in bounds) {\n return bounds\n }\n return boundsFromArray(bounds)\n}\n\nexport function invertWithBoundsValidation(\n screenCoords: [number, number],\n entries: SubProjectionEntry[],\n options: InvertOptions = {},\n): InvertResult | null {\n const { tolerance = 0.01, debug = false } = options\n const [x, y] = screenCoords\n\n for (const entry of entries) {\n const { projection, bounds, id } = entry\n\n if (!projection.invert) {\n continue\n }\n\n try {\n const result = projection.invert([x, y])\n\n if (!result || !Array.isArray(result) || result.length < 2) {\n continue\n }\n\n const [lon, lat] = result\n\n if (bounds) {\n const geoBounds = normalizeBounds(bounds)\n\n if (isPointInBounds(lon, lat, geoBounds, tolerance)) {\n if (debug) {\n console.log(`[Invert] Matched ${id}: [${x}, ${y}] -> [${lon}, ${lat}]`)\n }\n return { coordinates: result as [number, number], territoryId: id }\n }\n else if (debug) {\n console.log(`[Invert] Rejected ${id}: [${lon}, ${lat}] outside bounds`)\n }\n }\n else {\n if (debug) {\n console.log(`[Invert] No bounds for ${id}, accepting [${lon}, ${lat}]`)\n }\n return { coordinates: result as [number, number], territoryId: id }\n }\n }\n catch (error) {\n if (debug) {\n console.warn(`[Invert] Error in ${id}:`, error)\n }\n }\n }\n\n if (debug) {\n console.log(`[Invert] Failed to invert [${x}, ${y}]`)\n }\n\n return null\n}\n","import type { ProjectionLike, StreamLike } from '../types'\n\nexport function createStreamMultiplexer(\n projections: ProjectionLike[],\n): (stream: StreamLike) => StreamLike {\n return (stream: StreamLike): StreamLike => {\n const streams = projections.map(p => p.stream(stream))\n\n return {\n point: (x: number, y: number) => {\n for (const s of streams) s.point(x, y)\n },\n lineStart: () => {\n for (const s of streams) s.lineStart()\n },\n lineEnd: () => {\n for (const s of streams) s.lineEnd()\n },\n polygonStart: () => {\n for (const s of streams) s.polygonStart()\n },\n polygonEnd: () => {\n for (const s of streams) s.polygonEnd()\n },\n sphere: () => {\n for (const s of streams) {\n if (s.sphere)\n s.sphere()\n }\n },\n }\n }\n}\n","import type { PointCaptureResult, StreamLike } from '../types'\n\nexport function createPointCaptureStream(): PointCaptureResult {\n let capturedPoint: [number, number] | null = null\n\n const pointStream: StreamLike = {\n point: (x: number, y: number) => {\n capturedPoint = [x, y]\n },\n lineStart: () => {},\n lineEnd: () => {},\n polygonStart: () => {},\n polygonEnd: () => {},\n sphere: () => {},\n }\n\n return {\n pointStream,\n getCapturedPoint: () => capturedPoint,\n resetCapture: () => {\n capturedPoint = null\n },\n }\n}\n","import type {\n CompositeProjectionConfig,\n GeoBounds,\n GeoBoundsArray,\n ProjectionLike,\n} from '../types'\n\nimport { boundsFromArray, isPointInBounds } from '../bounds/checker'\nimport { invertWithBoundsValidation } from '../invert/validator'\nimport { createStreamMultiplexer } from '../stream/multiplexer'\nimport { createPointCaptureStream } from '../stream/point-capture'\n\nfunction normalizeBounds(bounds: GeoBounds | GeoBoundsArray): GeoBounds {\n if ('minLon' in bounds) {\n return bounds\n }\n return boundsFromArray(bounds)\n}\n\nexport function buildCompositeProjection(\n config: CompositeProjectionConfig,\n): ProjectionLike {\n const { entries, debug = false } = config\n\n if (entries.length === 0) {\n throw new Error('Cannot build composite projection with no entries')\n }\n\n const { pointStream, getCapturedPoint, resetCapture } = createPointCaptureStream()\n\n const entryStreams = entries.map(entry => ({\n entry,\n stream: entry.projection.stream(pointStream),\n }))\n\n const project = (coordinates: [number, number]): [number, number] | null => {\n const [lon, lat] = coordinates\n resetCapture()\n\n for (const { entry, stream } of entryStreams) {\n if (entry.bounds) {\n const bounds = normalizeBounds(entry.bounds)\n\n if (isPointInBounds(lon, lat, bounds)) {\n stream.point(lon, lat)\n const captured = getCapturedPoint()\n\n if (captured) {\n return captured\n }\n }\n }\n }\n\n return null\n }\n\n const composite = project as ProjectionLike\n\n composite.stream = createStreamMultiplexer(entries.map(e => e.projection))\n\n composite.invert = (coords: [number, number]) => {\n const result = invertWithBoundsValidation(coords, entries, { debug })\n return result?.coordinates ?? null\n }\n\n composite.scale = function (_s?: number): any {\n if (arguments.length === 0) {\n return entries[0]?.projection.scale() ?? 1\n }\n return composite\n } as ProjectionLike['scale']\n\n composite.translate = function (_t?: [number, number]): any {\n if (arguments.length === 0) {\n return entries[0]?.projection.translate() ?? [0, 0]\n }\n return composite\n } as ProjectionLike['translate']\n\n return composite\n}\n","import type { ProjectionLike as CoreProjectionLike, SubProjectionEntry } from '@atlas-composer/projection-core'\nimport type {\n CompositeProjectionConfig,\n GeoBounds,\n LayoutConfig,\n TerritoryConfig,\n} from '@atlas-composer/specification'\n\nimport {\n buildCompositeProjection,\n calculateClipExtentFromPixelOffset,\n} from '@atlas-composer/projection-core'\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 clipAngle?: {\n (): number\n (angle: number): 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\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\nexport type ProjectionFactory = () => ProjectionLike\n\nexport type { CompositeProjectionConfig, GeoBounds, LayoutConfig, TerritoryConfig }\n\nexport interface LoaderOptions {\n width: number\n height: number\n enableClipping?: boolean\n debug?: boolean\n}\n\nexport class ProjectionLoader {\n private factories = new Map<string, ProjectionFactory>()\n\n register(id: string, factory: ProjectionFactory): void {\n this.factories.set(id, factory)\n }\n\n registerAll(factories: Record<string, ProjectionFactory>): void {\n for (const [id, factory] of Object.entries(factories)) {\n this.register(id, factory)\n }\n }\n\n unregister(id: string): boolean {\n return this.factories.delete(id)\n }\n\n clear(): void {\n this.factories.clear()\n }\n\n getRegistered(): string[] {\n return Array.from(this.factories.keys())\n }\n\n isRegistered(id: string): boolean {\n return this.factories.has(id)\n }\n\n load(config: CompositeProjectionConfig, options: LoaderOptions): ProjectionLike {\n const { width, height, debug = false } = options\n\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 const entries: SubProjectionEntry[] = config.territories.map((territory) => {\n const proj = this.createSubProjection(territory, width, height, config.referenceScale, debug)\n\n return {\n id: territory.code,\n projection: proj as CoreProjectionLike,\n bounds: {\n minLon: territory.bounds[0][0],\n minLat: territory.bounds[0][1],\n maxLon: territory.bounds[1][0],\n maxLat: territory.bounds[1][1],\n },\n }\n })\n\n if (debug) {\n console.log('[CompositeProjection] Created sub-projections:', {\n territories: config.territories.map(t => t.code),\n count: entries.length,\n })\n }\n\n return buildCompositeProjection({ entries, debug }) as ProjectionLike\n }\n\n loadFromJSON(jsonString: string, options: LoaderOptions): ProjectionLike {\n let config: unknown\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 this.load(config, options)\n }\n\n private createSubProjection(\n territory: TerritoryConfig,\n width: number,\n height: number,\n referenceScale?: number,\n debug?: boolean,\n ): ProjectionLike {\n const { layout } = territory\n const projectionId = territory.projection.id\n const parameters = territory.projection.parameters\n\n if (!projectionId || !parameters) {\n throw new Error(`Territory ${territory.code} missing projection configuration`)\n }\n\n const factory = this.factories.get(projectionId)\n if (!factory) {\n const registered = this.getRegistered()\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 loader.register('${projectionId}', factory) to register it.`,\n )\n }\n\n const projection = factory()\n\n const hasFocus = parameters.focusLongitude !== undefined && parameters.focusLatitude !== undefined\n const projFamily = territory.projection.family\n\n if (hasFocus && projFamily === 'CONIC' && projection.rotate) {\n projection.rotate([-parameters.focusLongitude!, -parameters.focusLatitude!, 0])\n }\n else if (hasFocus && projection.center) {\n projection.center([parameters.focusLongitude!, parameters.focusLatitude!])\n }\n else if (parameters.center && projection.center) {\n projection.center(parameters.center)\n }\n\n if (parameters.rotate && projection.rotate && !hasFocus) {\n const rotate = Array.isArray(parameters.rotate)\n ? [...parameters.rotate, 0, 0].slice(0, 3) as [number, number, number]\n : [0, 0, 0] as [number, number, number]\n projection.rotate(rotate)\n }\n\n if (parameters.parallels && projection.parallels) {\n const parallels = Array.isArray(parameters.parallels)\n ? [...parameters.parallels, 0].slice(0, 2) as [number, number]\n : [0, 60] as [number, number]\n projection.parallels(parallels)\n }\n\n if (projection.scale && parameters.scaleMultiplier) {\n const effectiveReferenceScale = referenceScale || 2700\n const calculatedScale = effectiveReferenceScale * parameters.scaleMultiplier\n projection.scale(calculatedScale)\n }\n\n if (parameters.clipAngle && projection.clipAngle) {\n projection.clipAngle(parameters.clipAngle)\n }\n\n if (parameters.precision && projection.precision) {\n projection.precision(parameters.precision)\n }\n\n if (projection.translate) {\n const [offsetX, offsetY] = layout.translateOffset || [0, 0]\n projection.translate([\n width / 2 + offsetX,\n height / 2 + offsetY,\n ])\n }\n\n if (layout.pixelClipExtent && projection.clipExtent) {\n const territoryCenter = projection.translate?.() || [width / 2, height / 2]\n\n const clipExtent = calculateClipExtentFromPixelOffset(\n territoryCenter,\n layout.pixelClipExtent,\n )\n\n projection.clipExtent(clipExtent)\n if (debug) {\n console.log(\n `[Clipping] Applied pixelClipExtent for ${territory.code}:`,\n `original: ${JSON.stringify(layout.pixelClipExtent)} -> transformed: ${JSON.stringify(clipExtent)}`,\n )\n }\n }\n else if (projection.clipExtent) {\n const bounds = territory.bounds\n if (bounds && bounds.length === 2 && bounds[0].length === 2 && bounds[1].length === 2) {\n const scale = projection.scale?.() || 1\n const translate = projection.translate?.() || [0, 0]\n\n const padding = scale * 0.1\n const clipExtent: [[number, number], [number, number]] = [\n [translate[0] - padding, translate[1] - padding],\n [translate[0] + padding, translate[1] + padding],\n ]\n\n projection.clipExtent(clipExtent)\n\n if (debug) {\n console.log(`[Clipping] Applied default clip extent for ${territory.code}:`, clipExtent)\n }\n }\n }\n\n return projection\n }\n}\n\nexport function validateConfig(config: unknown): asserts config is CompositeProjectionConfig {\n if (!config || typeof config !== 'object') {\n throw new Error('Configuration must be an object')\n }\n\n const cfg = config as Record<string, unknown>\n\n if (!cfg.version) {\n throw new Error('Configuration must have a version field')\n }\n\n if (!cfg.metadata || typeof cfg.metadata !== 'object' || !(cfg.metadata as Record<string, unknown>).atlasId) {\n throw new Error('Configuration must have metadata with atlasId')\n }\n\n if (!cfg.territories || !Array.isArray(cfg.territories)) {\n throw new Error('Configuration must have territories array')\n }\n\n if (cfg.territories.length === 0) {\n throw new Error('Configuration must have at least one territory')\n }\n\n for (const territory of cfg.territories as Record<string, unknown>[]) {\n if (!territory.code) {\n throw new Error(`Territory missing required field 'code': ${JSON.stringify(territory)}`)\n }\n\n const proj = territory.projection as Record<string, unknown> | undefined\n if (!proj || !proj.id || !proj.parameters) {\n throw new Error(`Territory ${territory.code} missing projection configuration. Required: projection.id and projection.parameters`)\n }\n\n if (!territory.bounds) {\n throw new Error(`Territory ${territory.code} missing bounds`)\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@atlas-composer/projection-loader",
3
3
  "type": "module",
4
- "version": "1.1.1",
4
+ "version": "2.0.0",
5
5
  "description": "Zero-dependency standalone loader for composite map projections with plugin architecture. Supports Atlas composer 2.0+ presets with backward compatibility.",
6
6
  "author": "Lucas Poulain (ShallowRed)",
7
7
  "license": "MIT",