@math.gl/crs 4.2.0-alpha.5

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/dist/index.cjs ADDED
@@ -0,0 +1,804 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // dist/index.js
20
+ var index_exports = {};
21
+ __export(index_exports, {
22
+ PROJJSON_SCHEMA_URL: () => PROJJSON_SCHEMA_URL,
23
+ PROJJSON_SCHEMA_VERSION: () => PROJJSON_SCHEMA_VERSION,
24
+ PROJStringSyntaxError: () => PROJStringSyntaxError,
25
+ WKTCRSSyntaxError: () => WKTCRSSyntaxError,
26
+ WKTCRSValidationError: () => WKTCRSValidationError,
27
+ encodePROJString: () => encodePROJString,
28
+ encodeWKTCRS: () => encodeWKTCRS,
29
+ parsePROJString: () => parsePROJString,
30
+ parseWKTCRS: () => parseWKTCRS,
31
+ validateWKTCRS: () => validateWKTCRS
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // dist/projjson-schema-constants.js
36
+ var PROJJSON_SCHEMA_VERSION = "0.7";
37
+ var PROJJSON_SCHEMA_URL = "https://proj.org/schemas/v0.7/projjson.schema.json";
38
+
39
+ // dist/wkt-crs.js
40
+ var WKTCRSSyntaxError = class extends SyntaxError {
41
+ /** Create a WKT syntax error at a source location. */
42
+ constructor(message, source, offset) {
43
+ const location = getSourceLocation(source, offset);
44
+ super(`${message} at ${location.line}:${location.column}`);
45
+ this.name = "WKTCRSSyntaxError";
46
+ this.offset = offset;
47
+ this.line = location.line;
48
+ this.column = location.column;
49
+ }
50
+ };
51
+ var WKTCRSValidationError = class extends Error {
52
+ /** Create an error from one or more validation issues. */
53
+ constructor(issues) {
54
+ var _a;
55
+ super(((_a = issues[0]) == null ? void 0 : _a.message) || "WKT validation failed");
56
+ this.name = "WKTCRSValidationError";
57
+ this.issues = issues;
58
+ }
59
+ };
60
+ var NUMBER_PATTERN = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[Ee][+-]?\d+)?$/;
61
+ var KEYWORD_PATTERN = /^[A-Za-z][A-Za-z0-9_]*/;
62
+ var WKT1_ROOT_KEYWORDS = /* @__PURE__ */ new Set([
63
+ "COMPD_CS",
64
+ "CONCAT_MT",
65
+ "FITTED_CS",
66
+ "GEOCCS",
67
+ "GEOGCS",
68
+ "INVERSE_MT",
69
+ "LOCAL_CS",
70
+ "PARAM_MT",
71
+ "PASSTHROUGH_MT",
72
+ "PROJCS",
73
+ "VERT_CS"
74
+ ]);
75
+ var WKT1_KEYWORDS = /* @__PURE__ */ new Set([
76
+ ...WKT1_ROOT_KEYWORDS,
77
+ "AUTHORITY",
78
+ "AXIS",
79
+ "CONCAT_MT",
80
+ "DATUM",
81
+ "INVERSE_MT",
82
+ "LOCAL_DATUM",
83
+ "PARAMETER",
84
+ "PARAM_MT",
85
+ "PASSTHROUGH_MT",
86
+ "PRIMEM",
87
+ "PROJECTION",
88
+ "SPHEROID",
89
+ "TOWGS84",
90
+ "UNIT",
91
+ "VERT_DATUM"
92
+ ]);
93
+ var GDAL_WKT1_ROOT_KEYWORDS = new Set(WKT1_ROOT_KEYWORDS);
94
+ var GDAL_WKT1_KEYWORDS = /* @__PURE__ */ new Set([...WKT1_KEYWORDS, "EXTENSION"]);
95
+ var ESRI_WKT1_ROOT_KEYWORDS = /* @__PURE__ */ new Set([...WKT1_ROOT_KEYWORDS, "COMPDCS", "LOCALCS", "VERTCS"]);
96
+ var ESRI_WKT1_KEYWORDS = /* @__PURE__ */ new Set([...WKT1_KEYWORDS, "COMPDCS", "LOCALCS", "VDATUM", "VERTCS"]);
97
+ var WKT2_2015_ROOT_KEYWORDS = /* @__PURE__ */ new Set([
98
+ "BOUNDCRS",
99
+ "COMPOUNDCRS",
100
+ "COORDINATEOPERATION",
101
+ "ENGCRS",
102
+ "ENGINEERINGCRS",
103
+ "GEODCRS",
104
+ "GEODETICCRS",
105
+ "PARAMETRICCRS",
106
+ "PROJCRS",
107
+ "PROJECTEDCRS",
108
+ "TIMECRS",
109
+ "VERTCRS",
110
+ "VERTICALCRS"
111
+ ]);
112
+ var WKT2_2019_ROOT_KEYWORDS = /* @__PURE__ */ new Set([
113
+ ...WKT2_2015_ROOT_KEYWORDS,
114
+ "CONCATENATEDOPERATION",
115
+ "COORDINATEMETADATA",
116
+ "DERIVEDPROJCRS",
117
+ "GEOGCRS",
118
+ "GEOGRAPHICCRS",
119
+ "POINTMOTIONOPERATION"
120
+ ]);
121
+ var WKT2_KEYWORDS = /* @__PURE__ */ new Set([
122
+ ...WKT2_2019_ROOT_KEYWORDS,
123
+ "ABRIDGEDTRANSFORMATION",
124
+ "ANCHOR",
125
+ "ANGLEUNIT",
126
+ "AREA",
127
+ "AXIS",
128
+ "BASEENGCRS",
129
+ "BASEGEODCRS",
130
+ "BASEGEOGCRS",
131
+ "BASEPARAMCRS",
132
+ "BASEPROJCRS",
133
+ "BASETIMECRS",
134
+ "BASEVERTCRS",
135
+ "BBOX",
136
+ "BEARING",
137
+ "CALENDAR",
138
+ "CITATION",
139
+ "CONVERSION",
140
+ "COORDEPOCH",
141
+ "CS",
142
+ "DATUM",
143
+ "DERIVINGCONVERSION",
144
+ "DYNAMIC",
145
+ "EDATUM",
146
+ "ELLIPSOID",
147
+ "ENGINEERINGDATUM",
148
+ "ENSEMBLE",
149
+ "ENSEMBLEACCURACY",
150
+ "EPOCH",
151
+ "FRAMEEPOCH",
152
+ "GEODETICDATUM",
153
+ "GEOIDMODEL",
154
+ "ID",
155
+ "INTERPOLATIONCRS",
156
+ "LENGTHUNIT",
157
+ "MEMBER",
158
+ "MERIDIAN",
159
+ "METHOD",
160
+ "MODEL",
161
+ "OPERATIONACCURACY",
162
+ "ORDER",
163
+ "PARAMETER",
164
+ "PARAMETERFILE",
165
+ "PARAMETRICDATUM",
166
+ "PARAMETRICUNIT",
167
+ "PDATUM",
168
+ "PRIMEM",
169
+ "PRIMEMERIDIAN",
170
+ "REMARK",
171
+ "SCALEUNIT",
172
+ "SCOPE",
173
+ "SOURCECRS",
174
+ "STEP",
175
+ "TARGETCRS",
176
+ "TDATUM",
177
+ "TEMPORALQUANTITY",
178
+ "TIMEEXTENT",
179
+ "TIMEORIGIN",
180
+ "TIMEUNIT",
181
+ "TIMEDATUM",
182
+ "TRIAXIAL",
183
+ "TRF",
184
+ "URI",
185
+ "USAGE",
186
+ "VDATUM",
187
+ "VERSION",
188
+ "VERTICALDATUM",
189
+ "VERTICALEXTENT",
190
+ "VELOCITYGRID",
191
+ "VRF"
192
+ ]);
193
+ var WKT2_2019_ADDITION_KEYWORDS = /* @__PURE__ */ new Set([
194
+ "BASEGEOGCRS",
195
+ "CALENDAR",
196
+ "CONCATENATEDOPERATION",
197
+ "COORDEPOCH",
198
+ "COORDINATEMETADATA",
199
+ "DERIVEDPROJCRS",
200
+ "DYNAMIC",
201
+ "ENSEMBLE",
202
+ "ENSEMBLEACCURACY",
203
+ "EPOCH",
204
+ "FRAMEEPOCH",
205
+ "GEOGCRS",
206
+ "GEOGRAPHICCRS",
207
+ "GEOIDMODEL",
208
+ "MEMBER",
209
+ "MODEL",
210
+ "POINTMOTIONOPERATION",
211
+ "STEP",
212
+ "TEMPORALQUANTITY",
213
+ "TRF",
214
+ "TRIAXIAL",
215
+ "VELOCITYGRID",
216
+ "VRF"
217
+ ]);
218
+ var WKT2_2015_KEYWORDS = new Set([...WKT2_KEYWORDS].filter((keyword) => !WKT2_2019_ADDITION_KEYWORDS.has(keyword)));
219
+ var PROFILE_ROOT_KEYWORDS = {
220
+ wkt1: WKT1_ROOT_KEYWORDS,
221
+ gdal: GDAL_WKT1_ROOT_KEYWORDS,
222
+ esri: ESRI_WKT1_ROOT_KEYWORDS,
223
+ "wkt2:2015": WKT2_2015_ROOT_KEYWORDS,
224
+ "wkt2:2019": WKT2_2019_ROOT_KEYWORDS
225
+ };
226
+ var PROFILE_KEYWORDS = {
227
+ wkt1: WKT1_KEYWORDS,
228
+ gdal: GDAL_WKT1_KEYWORDS,
229
+ esri: ESRI_WKT1_KEYWORDS,
230
+ "wkt2:2015": WKT2_2015_KEYWORDS,
231
+ "wkt2:2019": WKT2_KEYWORDS
232
+ };
233
+ function parseWKTCRS(text, options) {
234
+ const parser = new WKTParser(text);
235
+ const ast = { type: "wkt-crs", root: parser.parse() };
236
+ if (options == null ? void 0 : options.strict) {
237
+ const issues = validateWKTCRS(ast, {
238
+ profile: options.profile,
239
+ allowExtensions: false
240
+ });
241
+ if (issues.length > 0) {
242
+ throw new WKTCRSValidationError(issues);
243
+ }
244
+ }
245
+ return ast;
246
+ }
247
+ function encodeWKTCRS(ast, options) {
248
+ var _a;
249
+ if (!ast || ast.type !== "wkt-crs" || ((_a = ast.root) == null ? void 0 : _a.type) !== "node") {
250
+ throw new TypeError("encodeWKTCRS expects a WKTCRSAst");
251
+ }
252
+ const format = (options == null ? void 0 : options.format) || "compact";
253
+ const indent = (options == null ? void 0 : options.indent) ?? 2;
254
+ if (!Number.isInteger(indent) || indent < 0) {
255
+ throw new RangeError("WKT indentation must be a non-negative integer");
256
+ }
257
+ validateEncodableNode(ast.root);
258
+ return encodeNode(ast.root, format, indent, 0);
259
+ }
260
+ function validateWKTCRS(ast, options) {
261
+ var _a;
262
+ if (!ast || ast.type !== "wkt-crs" || ((_a = ast.root) == null ? void 0 : _a.type) !== "node") {
263
+ throw new TypeError("validateWKTCRS expects a WKTCRSAst");
264
+ }
265
+ const profile = resolveProfile(ast.root.keyword, (options == null ? void 0 : options.profile) || "auto");
266
+ const rootKeywords = PROFILE_ROOT_KEYWORDS[profile];
267
+ const knownKeywords = PROFILE_KEYWORDS[profile];
268
+ const issues = [];
269
+ const rootKeyword = ast.root.keyword.toUpperCase();
270
+ if (!rootKeywords.has(rootKeyword)) {
271
+ issues.push({
272
+ code: "invalid-root",
273
+ message: `${ast.root.keyword} is not a ${profile} root keyword`,
274
+ path: [],
275
+ keyword: ast.root.keyword
276
+ });
277
+ }
278
+ visitNode(ast.root, [], (node) => {
279
+ const keyword = node.node.keyword.toUpperCase();
280
+ if (!(options == null ? void 0 : options.allowExtensions) && !knownKeywords.has(keyword)) {
281
+ issues.push({
282
+ code: "unknown-keyword",
283
+ message: `${node.node.keyword} is not defined by the ${profile} profile`,
284
+ path: node.path,
285
+ keyword: node.node.keyword
286
+ });
287
+ }
288
+ if (node.node.values.length === 0) {
289
+ issues.push({
290
+ code: "empty-node",
291
+ message: `${node.node.keyword} must contain at least one value`,
292
+ path: node.path,
293
+ keyword: node.node.keyword
294
+ });
295
+ }
296
+ const grammarMessage = validateNodeValues(node.node);
297
+ if (grammarMessage) {
298
+ issues.push({
299
+ code: "invalid-value",
300
+ message: grammarMessage,
301
+ path: node.path,
302
+ keyword: node.node.keyword
303
+ });
304
+ }
305
+ });
306
+ return issues;
307
+ }
308
+ var WKTParser = class {
309
+ constructor(source) {
310
+ this.offset = 0;
311
+ this.source = source;
312
+ }
313
+ parse() {
314
+ this.skipWhitespace();
315
+ const root = this.parseNode();
316
+ this.skipWhitespace();
317
+ if (this.offset !== this.source.length) {
318
+ this.fail("Unexpected content after WKT root");
319
+ }
320
+ return root;
321
+ }
322
+ parseNode() {
323
+ const keyword = this.parseKeyword();
324
+ return this.parseNodeAfterKeyword(keyword);
325
+ }
326
+ parseNodeAfterKeyword(keyword) {
327
+ this.skipWhitespace();
328
+ const opening = this.source[this.offset];
329
+ if (opening !== "[" && opening !== "(") {
330
+ this.fail(`Expected '[' or '(' after ${keyword}`);
331
+ }
332
+ this.offset++;
333
+ const delimiter = opening === "[" ? "bracket" : "parenthesis";
334
+ const closing = opening === "[" ? "]" : ")";
335
+ const values = [];
336
+ this.skipWhitespace();
337
+ if (this.source[this.offset] === closing) {
338
+ this.offset++;
339
+ return { type: "node", keyword, delimiter, values };
340
+ }
341
+ while (this.offset < this.source.length) {
342
+ values.push(this.parseValue());
343
+ this.skipWhitespace();
344
+ const character = this.source[this.offset];
345
+ if (character === ",") {
346
+ this.offset++;
347
+ this.skipWhitespace();
348
+ if (this.source[this.offset] === closing) {
349
+ this.fail("Expected a WKT value after comma");
350
+ }
351
+ continue;
352
+ }
353
+ if (character === closing) {
354
+ this.offset++;
355
+ return { type: "node", keyword, delimiter, values };
356
+ }
357
+ if (character === "]" || character === ")") {
358
+ this.fail(`Expected '${closing}' to close ${keyword}`);
359
+ }
360
+ this.fail(`Expected ',' or '${closing}' in ${keyword}`);
361
+ }
362
+ this.fail(`Unterminated ${keyword} node`);
363
+ }
364
+ parseValue() {
365
+ this.skipWhitespace();
366
+ const character = this.source[this.offset];
367
+ if (character === '"') {
368
+ return this.parseString();
369
+ }
370
+ const start = this.offset;
371
+ while (this.offset < this.source.length && !/[\s,\[\]()]/.test(this.source[this.offset])) {
372
+ this.offset++;
373
+ }
374
+ if (start === this.offset) {
375
+ this.fail("Expected a WKT value");
376
+ }
377
+ const value = this.source.slice(start, this.offset);
378
+ this.skipWhitespace();
379
+ if (this.source[this.offset] === "[" || this.source[this.offset] === "(") {
380
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(value)) {
381
+ this.fail(`Invalid WKT keyword '${value}'`);
382
+ }
383
+ return this.parseNodeAfterKeyword(value);
384
+ }
385
+ if (NUMBER_PATTERN.test(value)) {
386
+ return { type: "number", value: Number(value), raw: value };
387
+ }
388
+ return { type: "enumeration", value };
389
+ }
390
+ parseKeyword() {
391
+ const match = this.source.slice(this.offset).match(KEYWORD_PATTERN);
392
+ if (!match) {
393
+ this.fail("Expected a WKT keyword");
394
+ }
395
+ this.offset += match[0].length;
396
+ return match[0];
397
+ }
398
+ parseString() {
399
+ this.offset++;
400
+ let value = "";
401
+ while (this.offset < this.source.length) {
402
+ const character = this.source[this.offset++];
403
+ if (character === '"') {
404
+ if (this.source[this.offset] === '"') {
405
+ value += '"';
406
+ this.offset++;
407
+ continue;
408
+ }
409
+ return { type: "string", value };
410
+ }
411
+ value += character;
412
+ }
413
+ this.fail("Unterminated WKT string");
414
+ }
415
+ skipWhitespace() {
416
+ while (this.offset < this.source.length && /\s/.test(this.source[this.offset])) {
417
+ this.offset++;
418
+ }
419
+ }
420
+ fail(message) {
421
+ throw new WKTCRSSyntaxError(message, this.source, this.offset);
422
+ }
423
+ };
424
+ function encodeNode(node, format, indent, depth) {
425
+ const opening = node.delimiter === "bracket" ? "[" : "(";
426
+ const closing = node.delimiter === "bracket" ? "]" : ")";
427
+ if (node.values.length === 0) {
428
+ return `${node.keyword}${opening}${closing}`;
429
+ }
430
+ if (format === "compact") {
431
+ return `${node.keyword}${opening}${node.values.map((value) => encodeValue(value, format, indent, depth + 1)).join(",")}${closing}`;
432
+ }
433
+ const indentation = " ".repeat(indent * (depth + 1));
434
+ const closingIndentation = " ".repeat(indent * depth);
435
+ const values = node.values.map((value) => `${indentation}${encodeValue(value, format, indent, depth + 1)}`).join(",\n");
436
+ return `${node.keyword}${opening}
437
+ ${values}
438
+ ${closingIndentation}${closing}`;
439
+ }
440
+ function encodeValue(value, format, indent, depth) {
441
+ switch (value.type) {
442
+ case "node":
443
+ return encodeNode(value, format, indent, depth);
444
+ case "string":
445
+ return `"${value.value.replace(/"/g, '""')}"`;
446
+ case "number":
447
+ return value.raw;
448
+ case "enumeration":
449
+ return value.value;
450
+ default:
451
+ throw new TypeError("Invalid WKT value type");
452
+ }
453
+ }
454
+ function validateEncodableNode(node) {
455
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(node.keyword)) {
456
+ throw new TypeError(`Invalid WKT keyword: ${node.keyword}`);
457
+ }
458
+ if (node.delimiter !== "bracket" && node.delimiter !== "parenthesis") {
459
+ throw new TypeError(`Invalid WKT delimiter: ${node.delimiter}`);
460
+ }
461
+ if (!Array.isArray(node.values)) {
462
+ throw new TypeError(`Invalid WKT values for ${node.keyword}`);
463
+ }
464
+ for (const value of node.values) {
465
+ if (!value || typeof value !== "object") {
466
+ throw new TypeError(`Invalid WKT value in ${node.keyword}`);
467
+ }
468
+ switch (value.type) {
469
+ case "node":
470
+ validateEncodableNode(value);
471
+ break;
472
+ case "string":
473
+ if (typeof value.value !== "string") {
474
+ throw new TypeError(`Invalid WKT string in ${node.keyword}`);
475
+ }
476
+ break;
477
+ case "number":
478
+ if (typeof value.raw !== "string" || !NUMBER_PATTERN.test(value.raw)) {
479
+ throw new TypeError(`Invalid WKT number lexeme: ${value.raw}`);
480
+ }
481
+ break;
482
+ case "enumeration":
483
+ if (typeof value.value !== "string" || !value.value || /[\s,\[\]()"]/.test(value.value)) {
484
+ throw new TypeError(`Invalid WKT enumeration: ${value.value}`);
485
+ }
486
+ break;
487
+ default:
488
+ throw new TypeError(`Invalid WKT value in ${node.keyword}`);
489
+ }
490
+ }
491
+ }
492
+ function resolveProfile(rootKeyword, profile) {
493
+ if (profile !== "auto") {
494
+ return profile;
495
+ }
496
+ return WKT1_ROOT_KEYWORDS.has(rootKeyword.toUpperCase()) ? "wkt1" : "wkt2:2019";
497
+ }
498
+ function validateNodeValues(node) {
499
+ const keyword = node.keyword.toUpperCase();
500
+ const values = node.values;
501
+ const typeAt = (index, ...types) => Boolean(values[index] && types.includes(values[index].type));
502
+ const exact = (length, ...types) => values.length === length && types.every((type, index) => typeAt(index, type));
503
+ if ([
504
+ "ANGLEUNIT",
505
+ "LENGTHUNIT",
506
+ "PARAMETRICUNIT",
507
+ "SCALEUNIT",
508
+ "TIMEUNIT",
509
+ "TEMPORALQUANTITY",
510
+ "UNIT"
511
+ ].includes(keyword) && !(values.length >= 2 && typeAt(0, "string") && typeAt(1, "number"))) {
512
+ return `${node.keyword} must start with a quoted name and numeric conversion factor`;
513
+ }
514
+ if (keyword === "CS" && !exact(2, "enumeration", "number")) {
515
+ return `${node.keyword} must contain a coordinate-system type and dimension`;
516
+ }
517
+ if (keyword === "BBOX" && !exact(4, "number", "number", "number", "number")) {
518
+ return `${node.keyword} must contain four numeric bounds`;
519
+ }
520
+ if ([
521
+ "ENSEMBLEACCURACY",
522
+ "EPOCH",
523
+ "COORDEPOCH",
524
+ "FRAMEEPOCH",
525
+ "OPERATIONACCURACY",
526
+ "ORDER"
527
+ ].includes(keyword) && !exact(1, "number")) {
528
+ return `${node.keyword} must contain one number`;
529
+ }
530
+ if (["ANCHOR", "AREA", "CALENDAR", "CITATION", "REMARK", "SCOPE", "URI", "VERSION"].includes(keyword) && !exact(1, "string")) {
531
+ return `${node.keyword} must contain one quoted string`;
532
+ }
533
+ if (["ELLIPSOID", "SPHEROID", "TRIAXIAL"].includes(keyword) && !(values.length >= 3 && typeAt(0, "string") && typeAt(1, "number") && typeAt(2, "number"))) {
534
+ return `${node.keyword} must start with a quoted name and numeric ellipsoid axes`;
535
+ }
536
+ if (["PARAMETER"].includes(keyword) && !(values.length >= 2 && typeAt(0, "string") && typeAt(1, "number"))) {
537
+ return `${node.keyword} must start with a quoted name and numeric value`;
538
+ }
539
+ if (keyword === "PARAMETERFILE" && !exact(2, "string", "string")) {
540
+ return `${node.keyword} must contain a quoted name and file name`;
541
+ }
542
+ if (["AUTHORITY", "ID"].includes(keyword) && !(values.length >= 2 && typeAt(0, "string") && typeAt(1, "string", "number"))) {
543
+ return `${node.keyword} must start with a quoted authority and identifier`;
544
+ }
545
+ if (["AXIS"].includes(keyword) && !(values.length >= 2 && typeAt(0, "string") && typeAt(1, "enumeration"))) {
546
+ return `${node.keyword} must start with a quoted name and axis direction`;
547
+ }
548
+ if (["METHOD", "PROJECTION"].includes(keyword) && !(values.length >= 1 && typeAt(0, "string"))) {
549
+ return `${node.keyword} must start with a quoted name`;
550
+ }
551
+ if (keyword === "TIMEEXTENT") {
552
+ const isTemporalValue = (index) => typeAt(index, "enumeration", "string");
553
+ if (values.length !== 2 || !isTemporalValue(0) || !isTemporalValue(1)) {
554
+ return `${node.keyword} must contain two date-time or quoted values`;
555
+ }
556
+ }
557
+ if (keyword === "VERTICALEXTENT") {
558
+ if (values.length < 2 || values.length > 3 || !typeAt(0, "number") || !typeAt(1, "number") || values.length === 3 && !typeAt(2, "node")) {
559
+ return `${node.keyword} must contain two numeric bounds and an optional unit`;
560
+ }
561
+ }
562
+ if (keyword === "COORDINATEMETADATA") {
563
+ if (values.length < 1 || values.length > 2 || !typeAt(0, "node") || values.length === 2 && (!typeAt(1, "node") || !["EPOCH", "COORDEPOCH"].includes(values[1].keyword.toUpperCase()))) {
564
+ return `${node.keyword} must contain a CRS and an optional coordinate epoch`;
565
+ }
566
+ }
567
+ if (keyword === "BOUNDCRS") {
568
+ const childKeywords = values.slice(0, 3).map((value) => value.type === "node" ? value.keyword.toUpperCase() : "");
569
+ if (childKeywords[0] !== "SOURCECRS" || childKeywords[1] !== "TARGETCRS" || childKeywords[2] !== "ABRIDGEDTRANSFORMATION") {
570
+ return `${node.keyword} must start with SOURCECRS, TARGETCRS, and ABRIDGEDTRANSFORMATION`;
571
+ }
572
+ }
573
+ if ([
574
+ "COMPOUNDCRS",
575
+ "CONCATENATEDOPERATION",
576
+ "COORDINATEOPERATION",
577
+ "DERIVEDPROJCRS",
578
+ "ENGCRS",
579
+ "ENGINEERINGCRS",
580
+ "GEODCRS",
581
+ "GEODETICCRS",
582
+ "GEOGCRS",
583
+ "GEOGRAPHICCRS",
584
+ "PARAMETRICCRS",
585
+ "POINTMOTIONOPERATION",
586
+ "PROJCRS",
587
+ "PROJECTEDCRS",
588
+ "TIMECRS",
589
+ "VERTCRS",
590
+ "VERTICALCRS"
591
+ ].includes(keyword) && !typeAt(0, "string")) {
592
+ return `${node.keyword} must start with a quoted name`;
593
+ }
594
+ return null;
595
+ }
596
+ function visitNode(node, path, visitor) {
597
+ visitor({ node, path });
598
+ for (let index = 0; index < node.values.length; index++) {
599
+ const value = node.values[index];
600
+ if (value.type === "node") {
601
+ visitNode(value, [...path, index], visitor);
602
+ }
603
+ }
604
+ }
605
+ function getSourceLocation(source, offset) {
606
+ let line = 1;
607
+ let column = 1;
608
+ for (let index = 0; index < offset && index < source.length; index++) {
609
+ if (source[index] === "\n") {
610
+ line++;
611
+ column = 1;
612
+ } else {
613
+ column++;
614
+ }
615
+ }
616
+ return { line, column };
617
+ }
618
+
619
+ // dist/proj-string.js
620
+ var PROJStringSyntaxError = class extends SyntaxError {
621
+ /** Create a PROJ string syntax error at a source location. */
622
+ constructor(message, source, offset) {
623
+ const location = getSourceLocation2(source, offset);
624
+ super(`${message} at ${location.line}:${location.column}`);
625
+ this.name = "PROJStringSyntaxError";
626
+ this.offset = offset;
627
+ this.line = location.line;
628
+ this.column = location.column;
629
+ }
630
+ };
631
+ function parsePROJString(text) {
632
+ const tokens = tokenizePROJString(text);
633
+ if (tokens.length === 0) {
634
+ throw new PROJStringSyntaxError("PROJ string is empty", text, 0);
635
+ }
636
+ const parameters = tokens.map((token) => parseParameter(token.raw, token.offset, text));
637
+ return { type: "proj-string", parameters };
638
+ }
639
+ function encodePROJString(ast, options) {
640
+ if (!ast || ast.type !== "proj-string" || !Array.isArray(ast.parameters)) {
641
+ throw new TypeError("encodePROJString expects a PROJStringAst");
642
+ }
643
+ if (ast.parameters.length === 0) {
644
+ throw new TypeError("encodePROJString expects at least one parameter");
645
+ }
646
+ const separator = (options == null ? void 0 : options.format) === "multiline" ? "\n" : " ";
647
+ return ast.parameters.map((parameter) => {
648
+ if (!parameter.name || /[\s+=]/.test(parameter.name)) {
649
+ throw new TypeError(`Invalid PROJ parameter name: ${parameter.name}`);
650
+ }
651
+ if (parameter.rawValue !== void 0) {
652
+ validateRawParameterValue(parameter.rawValue);
653
+ return `+${parameter.name}=${parameter.rawValue}`;
654
+ }
655
+ if (parameter.value !== void 0) {
656
+ return `+${parameter.name}=${encodeParameterValue(parameter.value)}`;
657
+ }
658
+ return `+${parameter.name}`;
659
+ }).join(separator);
660
+ }
661
+ function validateRawParameterValue(rawValue) {
662
+ if (typeof rawValue !== "string") {
663
+ throw new TypeError("Invalid PROJ raw parameter value");
664
+ }
665
+ if (!rawValue) {
666
+ return;
667
+ }
668
+ const first = rawValue[0];
669
+ if (first !== '"' && first !== "'") {
670
+ if (/\s|["']/.test(rawValue)) {
671
+ throw new TypeError(`Invalid PROJ raw parameter value: ${rawValue}`);
672
+ }
673
+ return;
674
+ }
675
+ let escaped = false;
676
+ for (let index = 1; index < rawValue.length; index++) {
677
+ const character = rawValue[index];
678
+ if (escaped) {
679
+ escaped = false;
680
+ continue;
681
+ }
682
+ if (character === "\\") {
683
+ escaped = true;
684
+ continue;
685
+ }
686
+ if (character === first && index !== rawValue.length - 1) {
687
+ throw new TypeError(`Invalid PROJ raw parameter value: ${rawValue}`);
688
+ }
689
+ }
690
+ if (escaped || rawValue[rawValue.length - 1] !== first) {
691
+ throw new TypeError(`Invalid PROJ raw parameter value: ${rawValue}`);
692
+ }
693
+ }
694
+ function parseParameter(rawToken, offset, source) {
695
+ const token = rawToken.startsWith("+") ? rawToken.slice(1) : rawToken;
696
+ if (!token) {
697
+ throw new PROJStringSyntaxError("Expected a parameter after '+'", source, offset);
698
+ }
699
+ const equalsIndex = token.indexOf("=");
700
+ const name = equalsIndex < 0 ? token : token.slice(0, equalsIndex);
701
+ if (!name || /[\s+=]/.test(name)) {
702
+ throw new PROJStringSyntaxError(`Invalid PROJ parameter name '${name}'`, source, offset);
703
+ }
704
+ if (equalsIndex < 0) {
705
+ return { type: "parameter", name };
706
+ }
707
+ const rawValue = token.slice(equalsIndex + 1);
708
+ const value = decodeParameterValue(rawValue, source, offset + equalsIndex + 1);
709
+ return { type: "parameter", name, value, rawValue };
710
+ }
711
+ function tokenizePROJString(source) {
712
+ const tokens = [];
713
+ let offset = 0;
714
+ while (offset < source.length) {
715
+ while (offset < source.length && /\s/.test(source[offset])) {
716
+ offset++;
717
+ }
718
+ if (offset >= source.length) {
719
+ break;
720
+ }
721
+ const start = offset;
722
+ let quote = null;
723
+ let escaped = false;
724
+ while (offset < source.length) {
725
+ const character = source[offset];
726
+ if (escaped) {
727
+ escaped = false;
728
+ offset++;
729
+ continue;
730
+ }
731
+ if (character === "\\" && quote) {
732
+ escaped = true;
733
+ offset++;
734
+ continue;
735
+ }
736
+ if (quote) {
737
+ if (character === quote) {
738
+ quote = null;
739
+ }
740
+ offset++;
741
+ continue;
742
+ }
743
+ if (character === '"' || character === "'") {
744
+ quote = character;
745
+ offset++;
746
+ continue;
747
+ }
748
+ if (/\s/.test(character)) {
749
+ break;
750
+ }
751
+ offset++;
752
+ }
753
+ if (quote) {
754
+ throw new PROJStringSyntaxError("Unterminated quoted PROJ value", source, start);
755
+ }
756
+ tokens.push({ raw: source.slice(start, offset), offset: start });
757
+ }
758
+ return tokens;
759
+ }
760
+ function decodeParameterValue(rawValue, source, offset) {
761
+ if (!rawValue) {
762
+ return "";
763
+ }
764
+ const first = rawValue[0];
765
+ if (first !== '"' && first !== "'") {
766
+ if (rawValue.includes('"') || rawValue.includes("'")) {
767
+ throw new PROJStringSyntaxError("Quote must begin a PROJ parameter value", source, offset);
768
+ }
769
+ return rawValue;
770
+ }
771
+ if (rawValue[rawValue.length - 1] !== first) {
772
+ throw new PROJStringSyntaxError("Unterminated quoted PROJ value", source, offset);
773
+ }
774
+ let value = "";
775
+ for (let index = 1; index < rawValue.length - 1; index++) {
776
+ const character = rawValue[index];
777
+ if (character === "\\" && index + 1 < rawValue.length - 1) {
778
+ value += rawValue[++index];
779
+ } else {
780
+ value += character;
781
+ }
782
+ }
783
+ return value;
784
+ }
785
+ function encodeParameterValue(value) {
786
+ if (value && !/\s/.test(value) && !/["']/.test(value)) {
787
+ return value;
788
+ }
789
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
790
+ }
791
+ function getSourceLocation2(source, offset) {
792
+ let line = 1;
793
+ let column = 1;
794
+ for (let index = 0; index < offset && index < source.length; index++) {
795
+ if (source[index] === "\n") {
796
+ line++;
797
+ column = 1;
798
+ } else {
799
+ column++;
800
+ }
801
+ }
802
+ return { line, column };
803
+ }
804
+ //# sourceMappingURL=index.cjs.map