@atlas-composer/projection-loader 1.1.0-rc.5 → 1.1.0-rc.9
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/LICENSE +21 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +39 -36
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Lucas Poulain
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
CHANGED
|
@@ -64,7 +64,6 @@ interface ProjectionLike {
|
|
|
64
64
|
(): number;
|
|
65
65
|
(precision: number): ProjectionLike;
|
|
66
66
|
};
|
|
67
|
-
invert?: (coordinates: [number, number]) => [number, number] | null;
|
|
68
67
|
fitExtent?: (extent: [[number, number], [number, number]], object: any) => ProjectionLike;
|
|
69
68
|
fitSize?: (size: [number, number], object: any) => ProjectionLike;
|
|
70
69
|
fitWidth?: (width: number, object: any) => ProjectionLike;
|
|
@@ -206,6 +205,10 @@ declare function getRegisteredProjections(): string[];
|
|
|
206
205
|
* @returns True if the projection is registered
|
|
207
206
|
*/
|
|
208
207
|
declare function isProjectionRegistered(id: string): boolean;
|
|
208
|
+
/**
|
|
209
|
+
* Create a minimal projection wrapper (similar to d3.geoProjection)
|
|
210
|
+
* This allows us to avoid the d3-geo dependency
|
|
211
|
+
*/
|
|
209
212
|
/**
|
|
210
213
|
* Create a D3-compatible projection from an exported composite projection configuration
|
|
211
214
|
*
|
package/dist/index.js
CHANGED
|
@@ -58,66 +58,54 @@ function loadCompositeProjection(config, options) {
|
|
|
58
58
|
sphere: () => {
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
|
-
const subProjPoints = subProjections.map((
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
const subProjPoints = subProjections.map((subProj) => ({
|
|
62
|
+
subProj: {
|
|
63
|
+
territoryCode: subProj.territory.code,
|
|
64
|
+
territoryName: subProj.territory.name,
|
|
65
|
+
bounds: subProj.bounds,
|
|
66
|
+
projection: subProj.projection
|
|
67
|
+
},
|
|
68
|
+
stream: subProj.projection.stream(pointStream)
|
|
66
69
|
}));
|
|
67
70
|
const compositeProjection = (coordinates) => {
|
|
68
71
|
const [lon, lat] = coordinates;
|
|
69
72
|
capturedPoint = null;
|
|
70
|
-
for (const {
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
73
|
+
for (const { subProj, stream } of subProjPoints) {
|
|
74
|
+
if (subProj.bounds) {
|
|
75
|
+
const [[minLon, minLat], [maxLon, maxLat]] = subProj.bounds;
|
|
76
|
+
if (lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat) {
|
|
77
|
+
stream.point(lon, lat);
|
|
78
|
+
if (capturedPoint) {
|
|
79
|
+
return capturedPoint;
|
|
80
|
+
}
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
|
-
if (subProjPoints[0] && subProjPoints[0].stream) {
|
|
79
|
-
subProjPoints[0].stream.point(lon, lat);
|
|
80
|
-
if (capturedPoint) {
|
|
81
|
-
return capturedPoint;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
84
|
return null;
|
|
85
85
|
};
|
|
86
86
|
compositeProjection.stream = (stream) => {
|
|
87
87
|
const streams = subProjections.map((sp) => sp.projection.stream(stream));
|
|
88
|
-
if (debug) {
|
|
89
|
-
console.log(`[Stream] Created multiplex stream with ${streams.length}/${subProjections.length} sub-streams`);
|
|
90
|
-
}
|
|
91
88
|
return {
|
|
92
89
|
point: (x, y) => {
|
|
93
|
-
for (const s of streams)
|
|
94
|
-
if (s) s.point(x, y);
|
|
95
|
-
}
|
|
90
|
+
for (const s of streams) s.point(x, y);
|
|
96
91
|
},
|
|
97
92
|
sphere: () => {
|
|
98
93
|
for (const s of streams) {
|
|
99
|
-
if (s
|
|
94
|
+
if (s.sphere)
|
|
95
|
+
s.sphere();
|
|
100
96
|
}
|
|
101
97
|
},
|
|
102
98
|
lineStart: () => {
|
|
103
|
-
for (const s of streams)
|
|
104
|
-
if (s) s.lineStart();
|
|
105
|
-
}
|
|
99
|
+
for (const s of streams) s.lineStart();
|
|
106
100
|
},
|
|
107
101
|
lineEnd: () => {
|
|
108
|
-
for (const s of streams)
|
|
109
|
-
if (s) s.lineEnd();
|
|
110
|
-
}
|
|
102
|
+
for (const s of streams) s.lineEnd();
|
|
111
103
|
},
|
|
112
104
|
polygonStart: () => {
|
|
113
|
-
for (const s of streams)
|
|
114
|
-
if (s) s.polygonStart();
|
|
115
|
-
}
|
|
105
|
+
for (const s of streams) s.polygonStart();
|
|
116
106
|
},
|
|
117
107
|
polygonEnd: () => {
|
|
118
|
-
for (const s of streams)
|
|
119
|
-
if (s) s.polygonEnd();
|
|
120
|
-
}
|
|
108
|
+
for (const s of streams) s.polygonEnd();
|
|
121
109
|
}
|
|
122
110
|
};
|
|
123
111
|
};
|
|
@@ -239,7 +227,22 @@ function createSubProjection(territory, width, height, referenceScale, debug) {
|
|
|
239
227
|
]);
|
|
240
228
|
}
|
|
241
229
|
if (layout.clipExtent && projection.clipExtent) {
|
|
242
|
-
|
|
230
|
+
const effectiveReferenceScale = referenceScale || 2700;
|
|
231
|
+
const centerX = width / 2;
|
|
232
|
+
const centerY = height / 2;
|
|
233
|
+
const [[x1, y1], [x2, y2]] = layout.clipExtent;
|
|
234
|
+
const epsilon = 1e-6;
|
|
235
|
+
const transformedClipExtent = [
|
|
236
|
+
[centerX + x1 * effectiveReferenceScale + epsilon, centerY + y1 * effectiveReferenceScale + epsilon],
|
|
237
|
+
[centerX + x2 * effectiveReferenceScale - epsilon, centerY + y2 * effectiveReferenceScale - epsilon]
|
|
238
|
+
];
|
|
239
|
+
projection.clipExtent(transformedClipExtent);
|
|
240
|
+
if (debug) {
|
|
241
|
+
console.log(
|
|
242
|
+
`[Clipping] Applied Atlas Composer-style clip extent for ${territory.code}:`,
|
|
243
|
+
`original: ${JSON.stringify(layout.clipExtent)} -> transformed: ${JSON.stringify(transformedClipExtent)}`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
243
246
|
} else if (projection.clipExtent) {
|
|
244
247
|
const bounds = territory.bounds;
|
|
245
248
|
if (bounds && bounds.length === 2 && bounds[0].length === 2 && bounds[1].length === 2) {
|
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 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 for (const s of streams) {\n if (s) s.point(x, y)\n }\n },\n sphere: () => {\n for (const s of streams) {\n if (s && s.sphere) s.sphere()\n }\n },\n lineStart: () => {\n for (const s of streams) {\n if (s) s.lineStart()\n }\n },\n lineEnd: () => {\n for (const s of streams) {\n if (s) s.lineEnd()\n }\n },\n polygonStart: () => {\n for (const s of streams) {\n if (s) s.polygonStart()\n }\n },\n polygonEnd: () => {\n for (const s of streams) {\n if (s) s.polygonEnd()\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"]}
|
|
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 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/**\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/**\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\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(subProj => ({\n subProj: {\n territoryCode: subProj.territory.code,\n territoryName: subProj.territory.name,\n bounds: subProj.bounds,\n projection: subProj.projection,\n },\n stream: subProj.projection.stream(pointStream),\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 { subProj, stream } of subProjPoints) {\n if (subProj.bounds) {\n const [[minLon, minLat], [maxLon, maxLat]] = subProj.bounds\n\n if (lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat) {\n // Project through stream (offset already applied in projection.translate)\n stream.point(lon, lat)\n\n if (capturedPoint) {\n return capturedPoint\n }\n }\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: any) => {\n const streams = subProjections.map(sp => sp.projection.stream(stream))\n return {\n point: (x: number, y: number) => {\n for (const s of streams) s.point(x, y)\n },\n sphere: () => {\n for (const s of streams) {\n if (s.sphere)\n s.sphere()\n }\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 }\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 as any).invert) {\n try {\n const result = (projection as any).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 // Like Atlas Composer: clipExtent is relative to the main projection coordinate system\n // Get the reference scale (use same as Atlas Composer does)\n const effectiveReferenceScale = referenceScale || 2700\n const centerX = width / 2\n const centerY = height / 2\n \n // Apply Atlas Composer's approach: clipExtent values are multiplied by reference scale\n // and positioned relative to map center, with small epsilon for padding\n const [[x1, y1], [x2, y2]] = layout.clipExtent\n const epsilon = 1e-6 // Small value for padding, matching d3-composite-projections\n \n const transformedClipExtent: [[number, number], [number, number]] = [\n [centerX + x1 * effectiveReferenceScale + epsilon, centerY + y1 * effectiveReferenceScale + epsilon],\n [centerX + x2 * effectiveReferenceScale - epsilon, centerY + y2 * effectiveReferenceScale - epsilon],\n ]\n \n projection.clipExtent(transformedClipExtent)\n if (debug) {\n console.log(`[Clipping] Applied Atlas Composer-style clip extent for ${territory.code}:`, \n `original: ${JSON.stringify(layout.clipExtent)} -> transformed: ${JSON.stringify(transformedClipExtent)}`)\n }\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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlas-composer/projection-loader",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.1.0-rc.
|
|
4
|
+
"version": "1.1.0-rc.9",
|
|
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",
|
|
@@ -46,15 +46,6 @@
|
|
|
46
46
|
"README.md",
|
|
47
47
|
"dist"
|
|
48
48
|
],
|
|
49
|
-
"scripts": {
|
|
50
|
-
"build": "tsup",
|
|
51
|
-
"dev": "tsup --watch",
|
|
52
|
-
"typecheck": "tsc --noEmit",
|
|
53
|
-
"test": "vitest run",
|
|
54
|
-
"test:watch": "vitest",
|
|
55
|
-
"size": "size-limit",
|
|
56
|
-
"size:why": "size-limit --why"
|
|
57
|
-
},
|
|
58
49
|
"size-limit": [
|
|
59
50
|
{
|
|
60
51
|
"name": "Main bundle (ESM)",
|
|
@@ -89,5 +80,14 @@
|
|
|
89
80
|
"tsup": "^8.0.0",
|
|
90
81
|
"typescript": "~5.9.3",
|
|
91
82
|
"vitest": "^3.2.4"
|
|
83
|
+
},
|
|
84
|
+
"scripts": {
|
|
85
|
+
"build": "tsup",
|
|
86
|
+
"dev": "tsup --watch",
|
|
87
|
+
"typecheck": "tsc --noEmit",
|
|
88
|
+
"test": "vitest run",
|
|
89
|
+
"test:watch": "vitest",
|
|
90
|
+
"size": "size-limit",
|
|
91
|
+
"size:why": "size-limit --why"
|
|
92
92
|
}
|
|
93
|
-
}
|
|
93
|
+
}
|