@atlas-composer/projection-loader 1.0.0 → 1.1.0-rc.3

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
@@ -2,7 +2,9 @@
2
2
 
3
3
  > Zero-dependency standalone loader for composite map projections with plugin architecture
4
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.
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.
6
+
7
+ **✨ NEW**: Now supports the updated preset format from Atlas composer 2.0+ with backward compatibility for legacy exports.
6
8
 
7
9
  ## Features
8
10
 
@@ -86,6 +88,54 @@ const projection = loadCompositeProjection(config, { width: 800, height: 600 })
86
88
 
87
89
  **Result**: ~6KB instead of ~100KB (94% reduction) 🎉
88
90
 
91
+ ## Configuration Format Support
92
+
93
+ The loader supports multiple configuration formats for maximum compatibility:
94
+
95
+ ### New Format (Atlas composer 2.0+)
96
+ ```json
97
+ {
98
+ "version": "1.0",
99
+ "metadata": { "atlasId": "france", "atlasName": "France" },
100
+ "pattern": "single-focus",
101
+ "referenceScale": 2700,
102
+ "territories": [
103
+ {
104
+ "code": "FR-MET",
105
+ "projection": {
106
+ "id": "conic-conformal",
107
+ "family": "CONIC",
108
+ "parameters": {
109
+ "rotate": [-3, -46.2, 0],
110
+ "scaleMultiplier": 1
111
+ }
112
+ },
113
+ "layout": { "translateOffset": [0, 0] }
114
+ }
115
+ ]
116
+ }
117
+ ```
118
+
119
+ ### Legacy Format (Atlas composer 1.x)
120
+ ```json
121
+ {
122
+ "version": "1.0",
123
+ "territories": [
124
+ {
125
+ "code": "FR-MET",
126
+ "projectionId": "conic-conformal",
127
+ "parameters": {
128
+ "scale": 2700,
129
+ "baseScale": 2700,
130
+ "scaleMultiplier": 1
131
+ }
132
+ }
133
+ ]
134
+ }
135
+ ```
136
+
137
+ The loader automatically detects and handles both formats seamlessly.
138
+
89
139
  ## Observable Plot Integration
90
140
 
91
141
  ```typescript
@@ -47,7 +47,7 @@ declare const naturalEarth1: ProjectionFactory;
47
47
  declare const equalEarth: ProjectionFactory;
48
48
  /**
49
49
  * Object containing all standard D3 projection factories
50
- * Keyed by the projection ID used in Atlas Composer configurations
50
+ * Keyed by the projection ID used in Atlas composer configurations
51
51
  */
52
52
  declare const d3ProjectionFactories: Record<string, ProjectionFactory>;
53
53
  /**
@@ -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":";;;;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 CHANGED
@@ -1,27 +1,3 @@
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
1
  /**
26
2
  * Generic projection-like interface that matches D3 projections
27
3
  * without requiring d3-geo as a dependency
@@ -55,11 +31,16 @@ interface ProjectionLike {
55
31
  (): [[number, number], [number, number]] | null;
56
32
  (extent: [[number, number], [number, number]] | null): ProjectionLike;
57
33
  };
34
+ clipAngle?: {
35
+ (): number;
36
+ (angle: number): ProjectionLike;
37
+ };
58
38
  stream?: (stream: StreamLike) => StreamLike;
59
39
  precision?: {
60
40
  (): number;
61
41
  (precision: number): ProjectionLike;
62
42
  };
43
+ invert?: (coordinates: [number, number]) => [number, number] | null;
63
44
  fitExtent?: (extent: [[number, number], [number, number]], object: any) => ProjectionLike;
64
45
  fitSize?: (size: [number, number], object: any) => ProjectionLike;
65
46
  fitWidth?: (width: number, object: any) => ProjectionLike;
@@ -88,32 +69,47 @@ interface ExportedConfig {
88
69
  metadata: {
89
70
  atlasId: string;
90
71
  atlasName: string;
72
+ exportDate?: string;
73
+ createdWith?: string;
74
+ notes?: string;
91
75
  };
92
76
  pattern: string;
93
- referenceScale: number;
77
+ referenceScale?: number;
78
+ canvasDimensions?: {
79
+ width: number;
80
+ height: number;
81
+ };
94
82
  territories: Territory[];
95
83
  }
96
84
  interface Territory {
97
85
  code: string;
98
86
  name: string;
99
87
  role: string;
100
- projectionId: string;
101
- projectionFamily: string;
102
- parameters: ProjectionParameters;
88
+ projectionId?: string;
89
+ projectionFamily?: string;
90
+ projection?: {
91
+ id: string;
92
+ family: string;
93
+ parameters: ProjectionParameters;
94
+ };
95
+ parameters?: ProjectionParameters;
103
96
  layout: Layout;
104
97
  bounds: [[number, number], [number, number]];
105
98
  }
106
99
  interface ProjectionParameters {
107
100
  center?: [number, number];
108
101
  rotate?: [number, number, number];
109
- scale: number;
110
- baseScale: number;
111
- scaleMultiplier: number;
102
+ scale?: number;
103
+ baseScale?: number;
104
+ scaleMultiplier?: number;
112
105
  parallels?: [number, number];
106
+ translate?: [number, number];
107
+ clipAngle?: number;
108
+ precision?: number;
113
109
  }
114
110
  interface Layout {
115
- translateOffset: [number, number];
116
- clipExtent: [[number, number], [number, number]] | null;
111
+ translateOffset?: [number, number];
112
+ clipExtent?: [[number, number], [number, number]] | null;
117
113
  }
118
114
  /**
119
115
  * Options for creating the composite projection
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  // src/standalone-projection-loader.ts
2
+ console.log("-----------------------------------------");
3
+ console.log("-----------------------------------------");
4
+ console.log("-----------------------------------------");
5
+ console.log("Standalone Projection Loader Module Loaded");
2
6
  var projectionRegistry = /* @__PURE__ */ new Map();
3
7
  function registerProjection(id, factory) {
4
8
  projectionRegistry.set(id, factory);
@@ -20,29 +24,6 @@ function getRegisteredProjections() {
20
24
  function isProjectionRegistered(id) {
21
25
  return projectionRegistry.has(id);
22
26
  }
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
27
  function loadCompositeProjection(config, options) {
47
28
  const { width, height, debug = false } = options;
48
29
  if (config.version !== "1.0") {
@@ -52,7 +33,7 @@ function loadCompositeProjection(config, options) {
52
33
  throw new Error("Configuration must contain at least one territory");
53
34
  }
54
35
  const subProjections = config.territories.map((territory) => {
55
- const proj = createSubProjection(territory, width, height);
36
+ const proj = createSubProjection(territory, width, height, config.referenceScale, debug);
56
37
  return {
57
38
  territory,
58
39
  projection: proj,
@@ -65,105 +46,194 @@ function loadCompositeProjection(config, options) {
65
46
  count: subProjections.length
66
47
  });
67
48
  }
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;
49
+ let capturedPoint = null;
50
+ const pointStream = {
51
+ point: (x, y) => {
52
+ capturedPoint = [x, y];
53
+ },
54
+ lineStart: () => {
55
+ },
56
+ lineEnd: () => {
57
+ },
58
+ polygonStart: () => {
59
+ },
60
+ polygonEnd: () => {
61
+ },
62
+ sphere: () => {
63
+ }
64
+ };
65
+ const subProjPoints = subProjections.map(({ territory, projection, bounds }) => ({
66
+ territory,
67
+ projection,
68
+ bounds,
69
+ stream: projection.stream ? projection.stream(pointStream) : null
70
+ }));
71
+ const compositeProjection = (coordinates) => {
72
+ const [lon, lat] = coordinates;
73
+ capturedPoint = null;
74
+ for (const { bounds, stream } of subProjPoints) {
75
+ if (stream && lon >= bounds[0][0] && lon <= bounds[1][0] && lat >= bounds[0][1] && lat <= bounds[1][1]) {
76
+ stream.point(lon, lat);
77
+ if (capturedPoint) {
78
+ return capturedPoint;
79
+ }
76
80
  }
77
81
  }
78
- if (!selectedProj && subProjections[0]) {
79
- selectedProj = subProjections[0].projection;
82
+ if (subProjPoints[0] && subProjPoints[0].stream) {
83
+ subProjPoints[0].stream.point(lon, lat);
84
+ if (capturedPoint) {
85
+ return capturedPoint;
86
+ }
87
+ }
88
+ return null;
89
+ };
90
+ compositeProjection.stream = (stream) => {
91
+ const streams = subProjections.map((sp) => sp.projection.stream(stream));
92
+ if (debug) {
93
+ console.log(`[Stream] Created multiplex stream with ${streams.length}/${subProjections.length} sub-streams`);
80
94
  }
81
- return selectedProj([lambda, phi]);
82
- });
83
- compositeProjection.stream = function(stream) {
84
- let activeStream = null;
85
- let bufferedPoints = [];
86
- let activeTerritoryCode = "";
87
95
  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}`);
96
+ point: (x, y) => {
97
+ try {
98
+ for (const s of streams) {
99
+ if (s && typeof s.point === "function") {
100
+ s.point(x, y);
101
+ }
95
102
  }
96
- activeStream.point(lon, lat);
103
+ } catch (error) {
104
+ if (debug)
105
+ console.error("[Stream] Error in point():", error);
97
106
  }
98
107
  },
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}`);
108
+ sphere: () => {
109
+ try {
110
+ for (const s of streams) {
111
+ if (s && typeof s.sphere === "function") {
112
+ s.sphere();
124
113
  }
125
114
  }
115
+ } catch (error) {
116
+ if (debug)
117
+ console.error("[Stream] Error in sphere():", error);
126
118
  }
127
- if (activeStream) {
128
- activeStream.lineStart();
129
- for (const [lon, lat] of bufferedPoints) {
130
- activeStream.point(lon, lat);
119
+ },
120
+ lineStart: () => {
121
+ try {
122
+ for (const s of streams) {
123
+ if (s && typeof s.lineStart === "function") {
124
+ s.lineStart();
125
+ }
131
126
  }
127
+ } catch (error) {
128
+ if (debug)
129
+ console.error("[Stream] Error in lineStart():", error);
132
130
  }
133
- bufferedPoints = [];
134
131
  },
135
- lineEnd() {
136
- if (activeStream) {
137
- activeStream.lineEnd();
132
+ lineEnd: () => {
133
+ try {
134
+ for (const s of streams) {
135
+ if (s && typeof s.lineEnd === "function") {
136
+ s.lineEnd();
137
+ }
138
+ }
139
+ } catch (error) {
140
+ if (debug)
141
+ console.error("[Stream] Error in lineEnd():", error);
138
142
  }
139
143
  },
140
- polygonStart() {
141
- bufferedPoints = [];
142
- activeStream = null;
143
- },
144
- polygonEnd() {
145
- if (activeStream) {
146
- activeStream.polygonEnd();
144
+ polygonStart: () => {
145
+ try {
146
+ for (const s of streams) {
147
+ if (s && typeof s.polygonStart === "function") {
148
+ s.polygonStart();
149
+ }
150
+ }
151
+ } catch (error) {
152
+ if (debug)
153
+ console.error("[Stream] Error in polygonStart():", error);
147
154
  }
148
- activeStream = null;
149
155
  },
150
- sphere() {
151
- if (debug) {
152
- console.warn("[Stream] sphere() called - not supported in composite projections");
156
+ polygonEnd: () => {
157
+ try {
158
+ for (const s of streams) {
159
+ if (s && typeof s.polygonEnd === "function") {
160
+ s.polygonEnd();
161
+ }
162
+ }
163
+ } catch (error) {
164
+ if (debug)
165
+ console.error("[Stream] Error in polygonEnd():", error);
153
166
  }
154
167
  }
155
168
  };
156
169
  };
157
- if (compositeProjection.scale) {
158
- compositeProjection.scale(1);
159
- }
160
- if (compositeProjection.translate) {
161
- compositeProjection.translate([width / 2, height / 2]);
162
- }
170
+ compositeProjection.invert = (coordinates) => {
171
+ if (!coordinates || !Array.isArray(coordinates) || coordinates.length < 2) {
172
+ return null;
173
+ }
174
+ const [x, y] = coordinates;
175
+ for (const { projection } of subProjections) {
176
+ if (projection.invert) {
177
+ try {
178
+ const result = projection.invert([x, y]);
179
+ if (result && Array.isArray(result) && result.length >= 2) {
180
+ return result;
181
+ }
182
+ } catch (error) {
183
+ if (debug) {
184
+ console.warn("[Invert] Error in sub-projection invert:", error);
185
+ }
186
+ }
187
+ }
188
+ }
189
+ return null;
190
+ };
191
+ compositeProjection.scale = function(_s) {
192
+ var _a, _b;
193
+ if (arguments.length === 0) {
194
+ return subProjections[0] ? ((_b = (_a = subProjections[0].projection).scale) == null ? void 0 : _b.call(_a)) || 1 : 1;
195
+ }
196
+ return compositeProjection;
197
+ };
198
+ compositeProjection.translate = function(_t) {
199
+ if (arguments.length === 0) {
200
+ return [width / 2, height / 2];
201
+ }
202
+ return compositeProjection;
203
+ };
163
204
  return compositeProjection;
164
205
  }
165
- function createSubProjection(territory, width, height) {
166
- const { projectionId, parameters, layout } = territory;
206
+ function inferProjectionIdFromFamily(family, parameters) {
207
+ switch (family.toUpperCase()) {
208
+ case "CYLINDRICAL":
209
+ return "mercator";
210
+ // Most common cylindrical projection
211
+ case "CONIC":
212
+ return parameters.parallels ? "conic-conformal" : "conic-equal-area";
213
+ case "AZIMUTHAL":
214
+ return "azimuthal-equal-area";
215
+ default:
216
+ console.warn(`Unknown projection family: ${family}, falling back to mercator`);
217
+ return "mercator";
218
+ }
219
+ }
220
+ function createSubProjection(territory, width, height, referenceScale, debug) {
221
+ var _a, _b;
222
+ let projectionId;
223
+ let parameters;
224
+ const { layout } = territory;
225
+ if (territory.projection) {
226
+ projectionId = territory.projection.id;
227
+ parameters = territory.projection.parameters;
228
+ } else if (territory.projectionId && territory.parameters) {
229
+ projectionId = territory.projectionId;
230
+ parameters = territory.parameters;
231
+ } else if (territory.projectionFamily && territory.parameters) {
232
+ projectionId = inferProjectionIdFromFamily(territory.projectionFamily, territory.parameters);
233
+ parameters = territory.parameters;
234
+ } else {
235
+ throw new Error(`Territory ${territory.code} missing projection configuration`);
236
+ }
167
237
  const factory = projectionRegistry.get(projectionId);
168
238
  if (!factory) {
169
239
  const registered = getRegisteredProjections();
@@ -177,23 +247,60 @@ function createSubProjection(territory, width, height) {
177
247
  projection.center(parameters.center);
178
248
  }
179
249
  if (parameters.rotate && projection.rotate) {
180
- projection.rotate(parameters.rotate);
250
+ const rotate = Array.isArray(parameters.rotate) ? [...parameters.rotate, 0, 0].slice(0, 3) : [0, 0, 0];
251
+ projection.rotate(rotate);
181
252
  }
182
253
  if (parameters.parallels && projection.parallels) {
183
- projection.parallels(parameters.parallels);
254
+ const parallels = Array.isArray(parameters.parallels) ? [...parameters.parallels, 0].slice(0, 2) : [0, 60];
255
+ projection.parallels(parallels);
184
256
  }
185
257
  if (projection.scale) {
186
- projection.scale(parameters.scale);
258
+ if (parameters.scale) {
259
+ projection.scale(parameters.scale);
260
+ } else if (parameters.scaleMultiplier) {
261
+ const effectiveReferenceScale = referenceScale || 2700;
262
+ const calculatedScale = effectiveReferenceScale * parameters.scaleMultiplier;
263
+ projection.scale(calculatedScale);
264
+ }
265
+ }
266
+ if (parameters.clipAngle && projection.clipAngle) {
267
+ projection.clipAngle(parameters.clipAngle);
268
+ }
269
+ if (parameters.precision && projection.precision) {
270
+ projection.precision(parameters.precision);
187
271
  }
188
272
  if (projection.translate) {
189
- const [offsetX, offsetY] = layout.translateOffset;
273
+ const [offsetX, offsetY] = layout.translateOffset || [0, 0];
190
274
  projection.translate([
191
275
  width / 2 + offsetX,
192
276
  height / 2 + offsetY
193
277
  ]);
194
278
  }
279
+ if (parameters.translate && projection.translate) {
280
+ const currentTranslate = projection.translate();
281
+ const [additionalX, additionalY] = parameters.translate;
282
+ projection.translate([
283
+ currentTranslate[0] + additionalX,
284
+ currentTranslate[1] + additionalY
285
+ ]);
286
+ }
195
287
  if (layout.clipExtent && projection.clipExtent) {
196
288
  projection.clipExtent(layout.clipExtent);
289
+ } else if (projection.clipExtent) {
290
+ const bounds = territory.bounds;
291
+ if (bounds && bounds.length === 2 && bounds[0].length === 2 && bounds[1].length === 2) {
292
+ const scale = ((_a = projection.scale) == null ? void 0 : _a.call(projection)) || 1;
293
+ const translate = ((_b = projection.translate) == null ? void 0 : _b.call(projection)) || [0, 0];
294
+ const padding = scale * 0.1;
295
+ const clipExtent = [
296
+ [translate[0] - padding, translate[1] - padding],
297
+ [translate[0] + padding, translate[1] + padding]
298
+ ];
299
+ projection.clipExtent(clipExtent);
300
+ if (debug) {
301
+ console.log(`[Clipping] Applied default clip extent for ${territory.code}:`, clipExtent);
302
+ }
303
+ }
197
304
  }
198
305
  return projection;
199
306
  }
@@ -214,11 +321,17 @@ function validateConfig(config) {
214
321
  throw new Error("Configuration must have at least one territory");
215
322
  }
216
323
  for (const territory of config.territories) {
217
- if (!territory.code || !territory.projectionId) {
218
- throw new Error(`Territory missing required fields: ${JSON.stringify(territory)}`);
324
+ if (!territory.code) {
325
+ throw new Error(`Territory missing required field 'code': ${JSON.stringify(territory)}`);
326
+ }
327
+ const hasLegacyFormat = territory.projectionId && territory.parameters;
328
+ const hasNewFormat = territory.projection && territory.projection.id && territory.projection.parameters;
329
+ const hasIncompleteFormat = territory.projectionFamily && territory.parameters;
330
+ if (!hasLegacyFormat && !hasNewFormat && !hasIncompleteFormat) {
331
+ throw new Error(`Territory ${territory.code} missing projection configuration. Available fields: ${Object.keys(territory).join(", ")}`);
219
332
  }
220
- if (!territory.parameters || !territory.bounds) {
221
- throw new Error(`Territory ${territory.code} missing parameters or bounds`);
333
+ if (!territory.bounds) {
334
+ throw new Error(`Territory ${territory.code} missing bounds`);
222
335
  }
223
336
  }
224
337
  return true;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/standalone-projection-loader.ts"],"names":[],"mappings":"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","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"]}
1
+ {"version":3,"sources":["../src/standalone-projection-loader.ts"],"names":[],"mappings":"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","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 */\nconsole.log('-----------------------------------------')\nconsole.log('-----------------------------------------')\nconsole.log('-----------------------------------------')\nconsole.log('Standalone Projection Loader Module Loaded')\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 invert?: (coordinates: [number, number]) => [number, number] | null\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 exportDate?: string\n createdWith?: string\n notes?: string\n }\n pattern: string\n referenceScale?: number\n canvasDimensions?: {\n width: number\n height: number\n }\n territories: Territory[]\n}\n\nexport interface Territory {\n code: string\n name: string\n role: string\n // Support multiple formats\n projectionId?: string // Legacy format\n projectionFamily?: string // Migration script format\n projection?: {\n id: string\n family: string\n parameters: ProjectionParameters\n } // New format\n parameters?: ProjectionParameters // Used in legacy and migration formats\n layout: Layout\n bounds: [[number, number], [number, number]]\n}\n\nexport interface ProjectionParameters {\n center?: [number, number]\n rotate?: [number, number, number]\n // Legacy format support\n scale?: number\n baseScale?: number\n scaleMultiplier?: number\n parallels?: [number, number]\n // Additional parameters from new format\n translate?: [number, number]\n clipAngle?: number\n precision?: number\n}\n\nexport interface Layout {\n translateOffset?: [number, number] // Make optional for migration script format\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// Remove unused createProjectionWrapper function\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, config.referenceScale, debug)\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 // Point capture mechanism (like Atlas Composer's CompositeProjection)\n let capturedPoint: [number, number] | null = null\n const pointStream = {\n point: (x: number, y: number) => {\n capturedPoint = [x, y]\n },\n lineStart: () => {},\n lineEnd: () => {},\n polygonStart: () => {},\n polygonEnd: () => {},\n sphere: () => {},\n }\n\n // Create point capture for each sub-projection\n const subProjPoints = subProjections.map(({ territory, projection, bounds }) => ({\n territory,\n projection,\n bounds,\n stream: projection.stream ? projection.stream(pointStream) : null,\n }))\n\n // Main projection function (like Atlas Composer's CompositeProjection)\n const compositeProjection = (coordinates: [number, number]): [number, number] | null => {\n const [lon, lat] = coordinates\n\n capturedPoint = null\n\n // Try each sub-projection's bounds\n for (const { bounds, stream } of subProjPoints) {\n if (stream\n && lon >= bounds[0][0] && lon <= bounds[1][0]\n && lat >= bounds[0][1] && lat <= bounds[1][1]) {\n // Use the stream to project the point (D3 expects degrees, not radians)\n stream.point(lon, lat)\n if (capturedPoint) {\n return capturedPoint\n }\n }\n }\n\n // Fallback: try first projection if no bounds matched\n if (subProjPoints[0] && subProjPoints[0].stream) {\n subProjPoints[0].stream.point(lon, lat)\n if (capturedPoint) {\n return capturedPoint\n }\n }\n\n // No match found\n return null\n }\n\n // Multiplex stream (like Atlas Composer's CompositeProjection)\n ;(compositeProjection as any).stream = (stream: StreamLike) => {\n const streams = subProjections.map(sp => sp.projection.stream(stream))\n\n if (debug) {\n console.log(`[Stream] Created multiplex stream with ${streams.length}/${subProjections.length} sub-streams`)\n }\n\n return {\n point: (x: number, y: number) => {\n try {\n for (const s of streams) {\n if (s && typeof s.point === 'function') {\n s.point(x, y)\n }\n }\n }\n catch (error) {\n if (debug)\n console.error('[Stream] Error in point():', error)\n }\n },\n sphere: () => {\n try {\n for (const s of streams) {\n if (s && typeof s.sphere === 'function') {\n s.sphere()\n }\n }\n }\n catch (error) {\n if (debug)\n console.error('[Stream] Error in sphere():', error)\n }\n },\n lineStart: () => {\n try {\n for (const s of streams) {\n if (s && typeof s.lineStart === 'function') {\n s.lineStart()\n }\n }\n }\n catch (error) {\n if (debug)\n console.error('[Stream] Error in lineStart():', error)\n }\n },\n lineEnd: () => {\n try {\n for (const s of streams) {\n if (s && typeof s.lineEnd === 'function') {\n s.lineEnd()\n }\n }\n }\n catch (error) {\n if (debug)\n console.error('[Stream] Error in lineEnd():', error)\n }\n },\n polygonStart: () => {\n try {\n for (const s of streams) {\n if (s && typeof s.polygonStart === 'function') {\n s.polygonStart()\n }\n }\n }\n catch (error) {\n if (debug)\n console.error('[Stream] Error in polygonStart():', error)\n }\n },\n polygonEnd: () => {\n try {\n for (const s of streams) {\n if (s && typeof s.polygonEnd === 'function') {\n s.polygonEnd()\n }\n }\n }\n catch (error) {\n if (debug)\n console.error('[Stream] Error in polygonEnd():', error)\n }\n },\n }\n }\n\n // Add invert method (like Atlas Composer's CompositeProjection)\n ;(compositeProjection as any).invert = (coordinates: [number, number]) => {\n if (!coordinates || !Array.isArray(coordinates) || coordinates.length < 2) {\n return null\n }\n\n const [x, y] = coordinates\n\n // Try each sub-projection's invert\n for (const { projection } of subProjections) {\n if (projection.invert) {\n try {\n const result = projection.invert([x, y])\n if (result && Array.isArray(result) && result.length >= 2) {\n return result as [number, number]\n }\n }\n catch (error) {\n // Continue to next projection\n if (debug) {\n console.warn('[Invert] Error in sub-projection invert:', error)\n }\n }\n }\n }\n\n return null\n }\n\n // Add scale and translate methods (like Atlas Composer's CompositeProjection)\n ;(compositeProjection as any).scale = function (_s?: number): any {\n if (arguments.length === 0) {\n // Return scale from first sub-projection as reference\n return subProjections[0] ? subProjections[0].projection.scale?.() || 1 : 1\n }\n // Setting scale - not typically used for composite projections\n // Individual territories manage their own scales\n return compositeProjection\n }\n\n ;(compositeProjection as any).translate = function (_t?: [number, number]): any {\n if (arguments.length === 0) {\n // Return center point as reference\n return [width / 2, height / 2]\n }\n // Setting translate - not typically used for composite projections\n // Individual territories manage their own translations\n return compositeProjection\n }\n\n return compositeProjection as ProjectionLike\n}\n\n/**\n * Infer projection ID from family and parameters (for migration script format)\n */\nfunction inferProjectionIdFromFamily(family: string, parameters: ProjectionParameters): string {\n // Common projection mappings based on family and parameters\n switch (family.toUpperCase()) {\n case 'CYLINDRICAL':\n return 'mercator' // Most common cylindrical projection\n case 'CONIC':\n return parameters.parallels ? 'conic-conformal' : 'conic-equal-area'\n case 'AZIMUTHAL':\n return 'azimuthal-equal-area'\n default:\n // Fallback to mercator if we can't determine\n console.warn(`Unknown projection family: ${family}, falling back to mercator`)\n return 'mercator'\n }\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 multiple formats\n let projectionId: string\n let parameters: ProjectionParameters\n const { layout } = territory\n\n if (territory.projection) {\n // New format: nested projection object\n projectionId = territory.projection.id\n parameters = territory.projection.parameters\n }\n else if (territory.projectionId && territory.parameters) {\n // Legacy format: direct properties\n projectionId = territory.projectionId\n parameters = territory.parameters\n }\n else if (territory.projectionFamily && territory.parameters) {\n // Migration script format: has projectionFamily but missing projectionId\n // Try to infer projection ID from family and parameters\n projectionId = inferProjectionIdFromFamily(territory.projectionFamily as string, territory.parameters)\n parameters = territory.parameters\n }\n else {\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 // Apply parameters\n if (parameters.center && projection.center) {\n projection.center(parameters.center)\n }\n\n if (parameters.rotate && projection.rotate) {\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 - support both legacy (scale) and new (scaleMultiplier + referenceScale) formats\n if (projection.scale) {\n if (parameters.scale) {\n // Legacy format: use direct scale value\n projection.scale(parameters.scale)\n }\n else if (parameters.scaleMultiplier) {\n // New format: calculate scale from reference scale and multiplier\n const effectiveReferenceScale = referenceScale || 2700 // Default reference scale\n const calculatedScale = effectiveReferenceScale * parameters.scaleMultiplier\n projection.scale(calculatedScale)\n }\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 parameter-level translate (additional adjustment)\n if (parameters.translate && projection.translate) {\n const currentTranslate = projection.translate()\n const [additionalX, additionalY] = parameters.translate\n projection.translate([\n currentTranslate[0] + additionalX,\n currentTranslate[1] + additionalY,\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.clipExtent && projection.clipExtent) {\n projection.clipExtent(layout.clipExtent)\n }\n else if (projection.clipExtent) {\n // If no clip extent is specified, create a default one based on territory bounds\n // This prevents the projection from processing geometry outside its area\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 // This is a simplified approach - in practice, you'd want more precise clipping\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 projection info in either format\n const hasLegacyFormat = territory.projectionId && territory.parameters\n const hasNewFormat = territory.projection && territory.projection.id && territory.projection.parameters\n const hasIncompleteFormat = territory.projectionFamily && territory.parameters // Migration script format\n\n if (!hasLegacyFormat && !hasNewFormat && !hasIncompleteFormat) {\n throw new Error(`Territory ${territory.code} missing projection configuration. Available fields: ${Object.keys(territory).join(', ')}`)\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"]}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@atlas-composer/projection-loader",
3
3
  "type": "module",
4
- "version": "1.0.0",
5
- "description": "Zero-dependency standalone loader for composite map projections with plugin architecture",
4
+ "version": "1.1.0-rc.3",
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",
8
8
  "homepage": "https://github.com/ShallowRed/atlas-composer/tree/main/packages/projection-loader#readme",
@@ -50,6 +50,8 @@
50
50
  "build": "tsup",
51
51
  "dev": "tsup --watch",
52
52
  "typecheck": "tsc --noEmit",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
53
55
  "size": "size-limit",
54
56
  "size:why": "size-limit --why"
55
57
  },
@@ -82,7 +84,10 @@
82
84
  "devDependencies": {
83
85
  "@size-limit/preset-small-lib": "^11.2.0",
84
86
  "@types/d3-geo": "^3.1.0",
87
+ "d3-geo": "^3.1.1",
88
+ "d3-geo-projection": "^4.0.0",
85
89
  "tsup": "^8.0.0",
86
- "typescript": "~5.9.3"
90
+ "typescript": "~5.9.3",
91
+ "vitest": "^3.2.4"
87
92
  }
88
93
  }