-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest-node.mjs
More file actions
2375 lines (1981 loc) · 114 KB
/
test-node.mjs
File metadata and controls
2375 lines (1981 loc) · 114 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env node
/**
* WasmTS Node.js Test Suite
* Tests all functionality including 2D, 3D, and 4D geometry support
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log('=== WasmTS Node.js Test Suite ===\n');
// Load WASM binary directly using fs since Node.js fetch doesn't support file:// well
const wasmJsFile = join(__dirname, '../dist/wasmts.js');
const wasmPath = join(__dirname, '../dist/wasmts.js.wasm');
const wasmBinary = readFileSync(wasmPath);
// Set __filename globally so wasmts.js can find the .wasm file
// This needs to be set BEFORE importing wasmts.js
globalThis.__filename = wasmJsFile;
// Patch fetch to handle .wasm files from our cache
const originalFetch = globalThis.fetch;
globalThis.fetch = function(url, ...args) {
if (url && (url.endsWith('.wasm') || url.includes('wasmts.js.wasm'))) {
return Promise.resolve({
ok: true,
arrayBuffer: () => Promise.resolve(wasmBinary.buffer.slice(wasmBinary.byteOffset, wasmBinary.byteOffset + wasmBinary.byteLength))
});
}
return originalFetch(url, ...args);
};
import('../dist/wasmts.js').then(() => {
console.log('PASS: WASM module loaded\n');
// Wait for initialization
setTimeout(() => {
try {
runAllTests();
} catch (err) {
console.error('Test execution failed:', err);
process.exit(1);
}
}, 2000);
}).catch(err => {
console.error('Failed to load WASM module:', err);
process.exit(1);
});
function runAllTests() {
console.log('--- API Namespace Tests ---\n');
testNamespaces();
console.log('\n--- 2D Geometry Tests ---\n');
test2DGeometry();
console.log('\n--- Buffer Operations ---\n');
testBufferOperations();
console.log('\n--- Intersecting Circles ---\n');
testIntersectingCircles();
console.log('\n--- WKT I/O Tests ---\n');
testWKTIO();
console.log('\n--- WKB I/O Tests ---\n');
testWKBIO();
console.log('\n--- STRtree Spatial Index ---\n');
testSTRtree();
console.log('\n--- 3D Geometry Tests (XYZ) ---\n');
test3DGeometry();
console.log('\n--- 4D Geometry Tests (XYZM) ---\n');
test4DGeometry();
console.log('\n--- PreparedGeometry Tests ---\n');
testPreparedGeometry();
console.log('\n--- Geometry Analysis Algorithms ---\n');
testRectangleAlgorithms();
console.log('\n--- Advanced Buffering ---\n');
testAdvancedBuffering();
console.log('\n--- Offset Curves ---\n');
testOffsetCurves();
console.log('\n--- LineMerger ---\n');
testLineMerger();
console.log('\n--- CascadedPolygonUnion ---\n');
testCascadedPolygonUnion();
console.log('\n--- New Geometry Methods ---\n');
testNewGeometryMethods();
console.log('\n--- GeoJSON I/O Tests ---\n');
testGeoJSONIO();
console.log('\n--- Polygon Accessors ---\n');
testPolygonAccessors();
console.log('\n--- Distance Operations ---\n');
testDistanceOperations();
console.log('\n--- Geometry Factory Methods ---\n');
testGeometryFactory();
console.log('\n--- Coordinate Sequence ---\n');
testCoordinateSequence();
console.log('\n--- Coordinate Sequence Filter ---\n');
testCoordinateSequenceFilter();
console.log('\n--- Geometry Base Class ---\n');
testGeometryBaseClass();
console.log('\n--- IntersectionMatrix ---\n');
testIntersectionMatrix();
testDimension();
console.log('\n--- Point (getX, getY) ---\n');
testPointMethods();
console.log('\n--- LineString/LinearRing ---\n');
testLineStringMethods();
console.log('\n--- Envelope Methods ---\n');
testEnvelopeMethods();
console.log('\n--- Geometry Additional Methods ---\n');
testGeometryAdditionalMethods();
console.log('\n--- Densifier ---\n');
testDensifier();
console.log('\n--- GeometryFixer ---\n');
testGeometryFixer();
console.log('\n--- CoverageUnion ---\n');
testCoverageUnion();
console.log('\n--- PrecisionModel ---\n');
testPrecisionModel();
console.log('\n--- GeometryPrecisionReducer ---\n');
testGeometryPrecisionReducer();
console.log('\n=== All Tests Passed PASS: ===\n');
}
function testNamespaces() {
assert(typeof wasmts !== 'undefined', 'wasmts namespace exists');
assert(typeof wasmts.geom !== 'undefined', 'wasmts.geom namespace exists');
assert(typeof wasmts.io !== 'undefined', 'wasmts.io namespace exists');
assert(typeof wasmts.index !== 'undefined', 'wasmts.index namespace exists');
assert(typeof wasmts.index.strtree !== 'undefined', 'wasmts.index.strtree namespace exists');
console.log('PASS: All namespaces found');
}
function test2DGeometry() {
// Create Point
const point = wasmts.geom.createPoint(5, 10);
assert(point.type === 'Point', 'Point created');
const coords = point.getCoordinates();
assert(coords.length === 1, 'Point has 1 coordinate');
assert(coords[0].x === 5 && coords[0].y === 10, 'Point coordinates correct');
console.log('PASS: Point creation:', coords[0]);
// Create LineString via WKT (Node.js doesn't support array parameters with GraalVM webimage)
const line = wasmts.io.WKTReader.read('LINESTRING (0 0, 10 10, 20 0)');
assert(line.type === 'LineString', 'LineString created');
const lineCoords = line.getCoordinates();
assert(lineCoords.length === 3, 'LineString has 3 coordinates');
console.log('PASS: LineString creation: 3 points');
// Create Polygon via WKT
const polygon = wasmts.io.WKTReader.read('POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0))');
assert(polygon.type === 'Polygon', 'Polygon created');
const area = polygon.getArea();
assert(area === 10000, 'Polygon area is 10000');
console.log('PASS: Polygon creation: area =', area);
// Create MultiPoint via WKT
const multiPoint = wasmts.io.WKTReader.read('MULTIPOINT ((0 0), (10 10))');
assert(multiPoint.type === 'MultiPoint', 'MultiPoint created');
console.log('PASS: MultiPoint creation');
}
function testBufferOperations() {
// Create a point and buffer it
const point = wasmts.geom.createPoint(0, 0);
const buffered = point.buffer(10);
// Get area (should be π * r² ≈ 314.16)
const area = buffered.getArea();
const expectedArea = Math.PI * 100;
const diff = Math.abs(area - expectedArea);
console.log(' Buffer area:', area.toFixed(2), 'Expected:', expectedArea.toFixed(2), 'Diff:', diff.toFixed(2));
assert(Math.abs(area - expectedArea) < 3, 'Buffer area approximately correct (within 3 units)');
console.log('PASS: Buffer area:', area.toFixed(2));
// Convert to WKT
const wkt = buffered.toString();
assert(wkt.startsWith('POLYGON'), 'Buffered geometry is POLYGON');
console.log('PASS: WKT output:', wkt.substring(0, 50) + '...');
// Try union with another buffer
const point2 = wasmts.geom.createPoint(15, 0);
const buffered2 = point2.buffer(10);
const union = buffered.union(buffered2);
const unionArea = union.getArea();
assert(unionArea > area, 'Union area is larger than single buffer');
console.log('PASS: Union area:', unionArea.toFixed(2));
}
function testIntersectingCircles() {
// Create two overlapping circles
const circle1 = wasmts.geom.createPoint(0, 0).buffer(10);
const circle2 = wasmts.geom.createPoint(15, 0).buffer(10);
// Check if they intersect
const intersects = circle1.intersects(circle2);
assert(intersects === true, 'Circles intersect');
console.log('PASS: Circles intersect:', intersects);
// Get the intersection geometry
const intersection = circle1.intersection(circle2);
const intersectionArea = intersection.getArea();
assert(intersectionArea > 0, 'Intersection has area');
console.log('PASS: Intersection area:', intersectionArea.toFixed(2));
// Get the union
const union = circle1.union(circle2);
const unionArea = union.getArea();
console.log('PASS: Union area:', unionArea.toFixed(2));
// Calculate overlap percentage
const overlapPercent = (intersectionArea / unionArea * 100).toFixed(1);
console.log('PASS: Overlap:', overlapPercent + '%');
// Output as WKT and WKB
const wkt = wasmts.io.WKTWriter.write(intersection);
assert(wkt.startsWith('POLYGON'), 'Intersection is POLYGON');
const wkb = wasmts.io.WKBWriter.write(intersection);
assert(wkb instanceof Uint8Array, 'WKB is Uint8Array');
console.log('PASS: WKB length:', wkb.length, 'bytes');
}
function testWKTIO() {
// Read WKT
const wkt = 'POINT (5 10)';
const geom = wasmts.io.WKTReader.read(wkt);
assert(geom.type === 'Point', 'WKT parsed as Point');
console.log('PASS: readWKT:', wkt);
// Write WKT
const output = wasmts.io.WKTWriter.write(geom);
assert(output.includes('5') && output.includes('10'), 'WKT contains coordinates');
console.log('PASS: writeWKT:', output);
// Test LineString WKT
const lineWKT = 'LINESTRING (0 0, 10 10, 20 0)';
const line = wasmts.io.WKTReader.read(lineWKT);
assert(line.type === 'LineString', 'LineString parsed');
const lineOut = wasmts.io.WKTWriter.write(line);
console.log('PASS: LineString WKT:', lineOut);
// Test Polygon WKT
const polyWKT = 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))';
const poly = wasmts.io.WKTReader.read(polyWKT);
assert(poly.type === 'Polygon', 'Polygon parsed');
const polyArea = poly.getArea();
assert(polyArea === 100, 'Polygon area is 100');
console.log('PASS: Polygon WKT area:', polyArea);
}
function testWKBIO() {
// Create geometry
const point = wasmts.geom.createPoint(5, 10);
// Write to WKB
const wkb = wasmts.io.WKBWriter.write(point);
assert(wkb instanceof Uint8Array, 'writeWKB returns Uint8Array');
assert(wkb.length > 0, 'WKB has data');
console.log('PASS: writeWKB: length =', wkb.length, 'bytes');
console.log(' Hex:', Array.from(wkb.slice(0, 16)).map(b => b.toString(16).padStart(2, '0')).join(' '));
// Read from WKB
const parsed = wasmts.io.WKBReader.read(wkb);
assert(parsed.type === 'Point', 'readWKB parsed as Point');
const coords = parsed.getCoordinates();
assert(coords[0].x === 5 && coords[0].y === 10, 'WKB round-trip preserves coordinates');
console.log('PASS: readWKB: coordinates =', coords[0]);
// Test with polygon
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))');
const polyWkb = wasmts.io.WKBWriter.write(poly);
const polyParsed = wasmts.io.WKBReader.read(polyWkb);
assert(polyParsed.getArea() === 100, 'Polygon WKB round-trip preserves area');
console.log('PASS: Polygon WKB round-trip: area =', polyParsed.getArea());
}
function testSTRtree() {
// Create STRtree
const index = wasmts.index.strtree.STRtree.create();
console.log('PASS: STRtree created');
// Create geometries and insert into STRtree
for (let i = 0; i < 10; i++) {
const point = wasmts.geom.createPoint(i * 10, i * 10);
const buffered = point.buffer(5);
const envelope = buffered.getEnvelopeInternal();
wasmts.index.strtree.STRtree.insert(index, envelope, { id: i, name: `Geometry ${i}` });
}
console.log('PASS: Inserted 10 geometries');
// Query with search envelope
const searchEnv = wasmts.geom.createEnvelope(20, 30, 20, 30);
const results = wasmts.index.strtree.STRtree.query(index, searchEnv);
assert(results.length > 0, 'Query returned results');
console.log('PASS: Query found', results.length, 'results');
// Check result structure
const first = results[0];
assert(first !== undefined, 'Result exists');
console.log('PASS: First result:', first);
}
function test3DGeometry() {
// Test 3D point (XYZ)
const point3d = wasmts.geom.createPoint(5, 10, 15);
assert(point3d.type === 'Point', '3D Point created');
console.log('PASS: 3D Point created');
const coords3d = point3d.getCoordinates();
assert(coords3d.length === 1, '3D Point has 1 coordinate');
assert(coords3d[0].x === 5, 'X coordinate correct');
assert(coords3d[0].y === 10, 'Y coordinate correct');
assert(coords3d[0].z === 15, 'Z coordinate correct');
console.log('PASS: 3D coordinates:', { x: coords3d[0].x, y: coords3d[0].y, z: coords3d[0].z });
// Test 3D LineString via WKT
const line3d = wasmts.io.WKTReader.read('LINESTRING Z (0 0 0, 1 1 10, 2 0 20)');
assert(line3d.type === 'LineString', '3D LineString created');
const lineCoords3d = line3d.getCoordinates();
assert(lineCoords3d.length === 3, '3D LineString has 3 coordinates');
assert(lineCoords3d[1].z === 10, 'Middle point Z coordinate correct');
console.log('PASS: 3D LineString: 3 points with Z values');
// Test WKT output for 3D
const wkt3d = wasmts.io.WKTWriter.write(point3d);
console.log('PASS: 3D WKT:', wkt3d);
// Test WKT round-trip
const parsed3d = wasmts.io.WKTReader.read(wkt3d);
assert(parsed3d.type === 'Point', '3D WKT parsed');
console.log('PASS: 3D WKT round-trip successful');
// Test WKB for 3D
const wkb3d = wasmts.io.WKBWriter.write(point3d);
assert(wkb3d instanceof Uint8Array, '3D WKB is Uint8Array');
console.log('PASS: 3D WKB length:', wkb3d.length, 'bytes');
const parsedWkb3d = wasmts.io.WKBReader.read(wkb3d);
assert(parsedWkb3d.type === 'Point', '3D WKB parsed');
const wkbCoords = parsedWkb3d.getCoordinates();
assert(wkbCoords[0].z === 15, '3D WKB Z coordinate preserved');
console.log('PASS: 3D WKB round-trip: Z =', wkbCoords[0].z);
// Test distance calculation (2D projection)
const point3d_a = wasmts.geom.createPoint(0, 0, 0);
const point3d_b = wasmts.geom.createPoint(3, 4, 0);
const distance = point3d_a.distance(point3d_b);
assert(distance === 5, '3D point distance (2D projection) is 5');
console.log('PASS: 3D distance (2D projection):', distance);
}
function test4DGeometry() {
// Test 4D point (XYZM)
const point4d = wasmts.geom.createPoint(5, 10, 15, 20);
assert(point4d.type === 'Point', '4D Point created');
console.log('PASS: 4D Point created');
const coords4d = point4d.getCoordinates();
assert(coords4d.length === 1, '4D Point has 1 coordinate');
assert(coords4d[0].x === 5, 'X coordinate correct');
assert(coords4d[0].y === 10, 'Y coordinate correct');
assert(coords4d[0].z === 15, 'Z coordinate correct');
assert(coords4d[0].m === 20, 'M coordinate correct');
console.log('PASS: 4D coordinates:', { x: coords4d[0].x, y: coords4d[0].y, z: coords4d[0].z, m: coords4d[0].m });
// Test 4D LineString via WKT
const line4d = wasmts.io.WKTReader.read('LINESTRING ZM (0 0 0 100, 1 1 10 200, 2 0 20 300)');
assert(line4d.type === 'LineString', '4D LineString created');
const lineCoords4d = line4d.getCoordinates();
assert(lineCoords4d.length === 3, '4D LineString has 3 coordinates');
assert(lineCoords4d[1].z === 10, 'Middle point Z coordinate correct');
assert(lineCoords4d[1].m === 200, 'Middle point M coordinate correct');
console.log('PASS: 4D LineString: 3 points with Z and M values');
// Test WKT output for 4D
const wkt4d = wasmts.io.WKTWriter.write(point4d);
console.log('PASS: 4D WKT:', wkt4d);
// Test WKB for 4D
const wkb4d = wasmts.io.WKBWriter.write(point4d);
assert(wkb4d instanceof Uint8Array, '4D WKB is Uint8Array');
console.log('PASS: 4D WKB length:', wkb4d.length, 'bytes');
const parsedWkb4d = wasmts.io.WKBReader.read(wkb4d);
assert(parsedWkb4d.type === 'Point', '4D WKB parsed');
const wkbCoords = parsedWkb4d.getCoordinates();
assert(wkbCoords[0].z === 15, '4D WKB Z coordinate preserved');
assert(wkbCoords[0].m === 20, '4D WKB M coordinate preserved');
console.log('PASS: 4D WKB round-trip: Z =', wkbCoords[0].z, ', M =', wkbCoords[0].m);
}
function testPreparedGeometry() {
// Create a large polygon for containment tests
const container = wasmts.io.WKTReader.read('POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0))');
// Prepare the geometry for optimized predicates
assert(typeof wasmts.geom.prep !== 'undefined', 'wasmts.geom.prep namespace exists');
assert(typeof wasmts.geom.prep.PreparedGeometryFactory !== 'undefined', 'PreparedGeometryFactory class exists');
const prepared = wasmts.geom.prep.PreparedGeometryFactory.prepare(container);
assert(prepared !== null && prepared !== undefined, 'PreparedGeometry created');
assert(prepared._jtsPreparedGeometry !== undefined, 'PreparedGeometry has internal reference');
console.log('PASS: PreparedGeometry created');
// Test containsProperly - point clearly inside
const insidePoint = wasmts.geom.createPoint(50, 50);
const containsResult = wasmts.geom.prep.PreparedGeometry.containsProperly(prepared, insidePoint);
assert(containsResult === true, 'containsProperly returns true for inside point');
console.log('PASS: containsProperly(inside point) = true');
// Test containsProperly - point on boundary (should be false for containsProperly)
const boundaryPoint = wasmts.geom.createPoint(0, 0);
const boundaryResult = wasmts.geom.prep.PreparedGeometry.containsProperly(prepared, boundaryPoint);
assert(boundaryResult === false, 'containsProperly returns false for boundary point');
console.log('PASS: containsProperly(boundary point) = false');
// Test containsProperly - point outside
const outsidePoint = wasmts.geom.createPoint(150, 150);
const outsideResult = wasmts.geom.prep.PreparedGeometry.containsProperly(prepared, outsidePoint);
assert(outsideResult === false, 'containsProperly returns false for outside point');
console.log('PASS: containsProperly(outside point) = false');
// Test coveredBy - create a polygon that is covered by the container
const innerPoly = wasmts.io.WKTReader.read('POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))');
const coveredResult = wasmts.geom.prep.PreparedGeometry.coveredBy(prepared, innerPoly);
assert(coveredResult === false, 'coveredBy returns false (container not covered by inner)');
console.log('PASS: coveredBy test completed');
// Test getGeometry - extract underlying geometry
const extracted = wasmts.geom.prep.PreparedGeometry.getGeometry(prepared);
assert(extracted !== null && extracted !== undefined, 'getGeometry returns geometry');
assert(extracted.type === 'Polygon', 'extracted geometry is Polygon');
assert(extracted.getArea() === 10000, 'extracted geometry has correct area');
console.log('PASS: getGeometry returned original Polygon with area =', extracted.getArea());
// Test contains - PreparedGeometry.contains(prepared, geom)
const containsInside = wasmts.geom.prep.PreparedGeometry.contains(prepared, insidePoint);
assert(containsInside === true, 'contains returns true for inside point');
const containsOutside = wasmts.geom.prep.PreparedGeometry.contains(prepared, outsidePoint);
assert(containsOutside === false, 'contains returns false for outside point');
console.log('PASS: PreparedGeometry.contains works');
// Test covers - PreparedGeometry.covers(prepared, geom)
const coversInner = wasmts.geom.prep.PreparedGeometry.covers(prepared, innerPoly);
assert(coversInner === true, 'covers returns true for inner polygon');
const coversOutside = wasmts.geom.prep.PreparedGeometry.covers(prepared, outsidePoint);
assert(coversOutside === false, 'covers returns false for outside point');
console.log('PASS: PreparedGeometry.covers works');
// Test intersects - PreparedGeometry.intersects(prepared, geom)
const overlappingPoly = wasmts.io.WKTReader.read('POLYGON ((50 50, 150 50, 150 150, 50 150, 50 50))');
const intersectsOverlap = wasmts.geom.prep.PreparedGeometry.intersects(prepared, overlappingPoly);
assert(intersectsOverlap === true, 'intersects returns true for overlapping polygon');
const disjointPoly = wasmts.io.WKTReader.read('POLYGON ((200 200, 300 200, 300 300, 200 300, 200 200))');
const intersectsDisjoint = wasmts.geom.prep.PreparedGeometry.intersects(prepared, disjointPoly);
assert(intersectsDisjoint === false, 'intersects returns false for disjoint polygon');
console.log('PASS: PreparedGeometry.intersects works');
// Test disjoint - PreparedGeometry.disjoint(prepared, geom)
const disjointResult = wasmts.geom.prep.PreparedGeometry.disjoint(prepared, disjointPoly);
assert(disjointResult === true, 'disjoint returns true for disjoint polygon');
const notDisjoint = wasmts.geom.prep.PreparedGeometry.disjoint(prepared, overlappingPoly);
assert(notDisjoint === false, 'disjoint returns false for overlapping polygon');
console.log('PASS: PreparedGeometry.disjoint works');
// Test within - PreparedGeometry.within(prepared, geom)
const largerPoly = wasmts.io.WKTReader.read('POLYGON ((-10 -10, 110 -10, 110 110, -10 110, -10 -10))');
const withinLarger = wasmts.geom.prep.PreparedGeometry.within(prepared, largerPoly);
assert(withinLarger === true, 'within returns true when prepared is inside larger');
const withinSmaller = wasmts.geom.prep.PreparedGeometry.within(prepared, innerPoly);
assert(withinSmaller === false, 'within returns false when prepared is not inside smaller');
console.log('PASS: PreparedGeometry.within works');
// Test overlaps - PreparedGeometry.overlaps(prepared, geom)
const overlapsResult = wasmts.geom.prep.PreparedGeometry.overlaps(prepared, overlappingPoly);
assert(overlapsResult === true, 'overlaps returns true for overlapping polygon');
const overlapsContained = wasmts.geom.prep.PreparedGeometry.overlaps(prepared, innerPoly);
assert(overlapsContained === false, 'overlaps returns false for contained polygon');
console.log('PASS: PreparedGeometry.overlaps works');
// Test touches - PreparedGeometry.touches(prepared, geom)
const touchingPoly = wasmts.io.WKTReader.read('POLYGON ((100 0, 200 0, 200 100, 100 100, 100 0))');
const touchesResult = wasmts.geom.prep.PreparedGeometry.touches(prepared, touchingPoly);
assert(touchesResult === true, 'touches returns true for adjacent polygon');
const notTouches = wasmts.geom.prep.PreparedGeometry.touches(prepared, disjointPoly);
assert(notTouches === false, 'touches returns false for disjoint polygon');
console.log('PASS: PreparedGeometry.touches works');
// Test crosses - PreparedGeometry.crosses(prepared, geom)
const crossingLine = wasmts.io.WKTReader.read('LINESTRING (-10 50, 110 50)');
const crossesResult = wasmts.geom.prep.PreparedGeometry.crosses(prepared, crossingLine);
assert(crossesResult === true, 'crosses returns true for line crossing polygon');
const nonCrossingLine = wasmts.io.WKTReader.read('LINESTRING (10 10, 90 90)');
const notCrosses = wasmts.geom.prep.PreparedGeometry.crosses(prepared, nonCrossingLine);
assert(notCrosses === false, 'crosses returns false for line inside polygon');
console.log('PASS: PreparedGeometry.crosses works');
// Performance comparison - run containsProperly multiple times
const testPoints = [];
for (let i = 0; i < 10; i++) {
testPoints.push(wasmts.geom.createPoint(Math.random() * 120 - 10, Math.random() * 120 - 10));
}
for (const pt of testPoints) {
wasmts.geom.prep.PreparedGeometry.containsProperly(prepared, pt);
}
console.log('PASS: Ran containsProperly on 10 test points');
console.log('PASS: All PreparedGeometry predicate tests completed');
}
function testRectangleAlgorithms() {
// Create an irregular polygon
const polygon = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 2, 12 10, 2 12, 0 0))');
console.log('Created irregular polygon, area:', polygon.getArea().toFixed(2));
// Test namespace exists
assert(typeof wasmts.algorithm !== 'undefined', 'wasmts.algorithm namespace exists');
assert(typeof wasmts.algorithm.MinimumDiameter !== 'undefined', 'MinimumDiameter class exists');
assert(typeof wasmts.algorithm.MinimumBoundingCircle !== 'undefined', 'MinimumBoundingCircle class exists');
// Test MinimumDiameter - finds minimum-width bounding rectangle
const minDiamRect = wasmts.algorithm.MinimumDiameter.getMinimumRectangle(polygon);
assert(minDiamRect !== null && minDiamRect !== undefined, 'minimumDiameter returned geometry');
assert(minDiamRect.type === 'LineString' || minDiamRect.type === 'Polygon', 'minimumDiameter returns LineString or Polygon');
console.log('PASS: MinimumDiameter rectangle:', minDiamRect.type);
if (minDiamRect.type === 'Polygon') {
const rectArea = minDiamRect.getArea();
console.log(' Minimum-width rectangle area:', rectArea.toFixed(2));
assert(rectArea > 0, 'Rectangle has positive area');
}
// Test MinimumAreaRectangle - finds smallest bounding rectangle
const minAreaRect = wasmts.algorithm.MinimumAreaRectangle.getMinimumRectangle(polygon);
assert(minAreaRect !== null && minAreaRect !== undefined, 'minimumAreaRectangle returned geometry');
assert(minAreaRect.type === 'Polygon' || minAreaRect.type === 'LineString' || minAreaRect.type === 'Point', 'minimumAreaRectangle returns geometry');
console.log('PASS: MinimumAreaRectangle:', minAreaRect.type);
if (minAreaRect.type === 'Polygon') {
const rectArea = minAreaRect.getArea();
console.log(' Minimum-area rectangle area:', rectArea.toFixed(2));
assert(rectArea > 0, 'Rectangle has positive area');
// Minimum area rectangle should contain the original polygon
assert(minAreaRect.contains(polygon), 'Minimum area rectangle contains original polygon');
console.log('PASS: Minimum area rectangle contains original polygon');
}
// Test MinimumBoundingCircle
const circle = wasmts.algorithm.MinimumBoundingCircle.getCircle(polygon);
assert(circle !== null && circle !== undefined, 'MinimumBoundingCircle.getCircle returned geometry');
assert(circle.type === 'Polygon', 'Bounding circle is a Polygon');
console.log('PASS: MinimumBoundingCircle.getCircle:', circle.type, 'area:', circle.getArea().toFixed(2));
const centre = wasmts.algorithm.MinimumBoundingCircle.getCentre(polygon);
assert(typeof centre.x === 'number' && typeof centre.y === 'number', 'getCentre returns coordinate');
console.log('PASS: MinimumBoundingCircle.getCentre:', centre);
const radius = wasmts.algorithm.MinimumBoundingCircle.getRadius(polygon);
assert(typeof radius === 'number' && radius > 0, 'getRadius returns positive number');
console.log('PASS: MinimumBoundingCircle.getRadius:', radius.toFixed(2));
// Test with a simple triangle
const triangle = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 5 10, 0 0))');
const triMinRect = wasmts.algorithm.MinimumAreaRectangle.getMinimumRectangle(triangle);
console.log('PASS: Tested algorithms on triangle, result type:', triMinRect.type);
}
function testAdvancedBuffering() {
// BufferParameters constants (from JTS)
const CAP_ROUND = 1, CAP_FLAT = 2, CAP_SQUARE = 3;
const JOIN_ROUND = 1, JOIN_MITRE = 2, JOIN_BEVEL = 3;
// Create a simple line
const line = wasmts.io.WKTReader.read('LINESTRING (0 0, 10 0)');
console.log('Created test line');
// Test 1: Round caps (default-like)
const roundBuffer = line.buffer(2, CAP_ROUND, JOIN_ROUND, 5.0);
assert(roundBuffer !== null && roundBuffer !== undefined, 'Round cap buffer created');
assert(roundBuffer.type === 'Polygon', 'Round cap buffer is Polygon');
const roundArea = roundBuffer.getArea();
console.log('PASS: Round cap buffer area:', roundArea.toFixed(2));
// Test 2: Flat caps (single-sided effect on lines)
const flatBuffer = line.buffer(2, CAP_FLAT, JOIN_ROUND, 5.0);
assert(flatBuffer !== null && flatBuffer !== undefined, 'Flat cap buffer created');
const flatArea = flatBuffer.getArea();
console.log('PASS: Flat cap buffer area:', flatArea.toFixed(2));
assert(flatArea < roundArea, 'Flat cap buffer has smaller area than round');
// Test 3: Square caps
const squareBuffer = line.buffer(2, CAP_SQUARE, JOIN_ROUND, 5.0);
assert(squareBuffer !== null && squareBuffer !== undefined, 'Square cap buffer created');
const squareArea = squareBuffer.getArea();
console.log('PASS: Square cap buffer area:', squareArea.toFixed(2));
// Test 4: Mitre join on polygon with sharp corners
const sharpPoly = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 1, 5 1, 5 10, 0 10, 0 0))');
const mitreBuffer = sharpPoly.buffer(0.5, CAP_ROUND, JOIN_MITRE, 10.0);
assert(mitreBuffer !== null && mitreBuffer !== undefined, 'Mitre join buffer created');
console.log('PASS: Mitre join buffer created for sharp polygon');
// Test 5: Bevel join
const bevelBuffer = sharpPoly.buffer(0.5, CAP_ROUND, JOIN_BEVEL, 5.0);
assert(bevelBuffer !== null && bevelBuffer !== undefined, 'Bevel join buffer created');
console.log('PASS: Bevel join buffer created');
// Test 6: Negative buffer (erosion/deflate)
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 20 0, 20 20, 0 20, 0 0))');
const eroded = poly.buffer(-2, CAP_ROUND, JOIN_ROUND, 5.0);
assert(eroded !== null && eroded !== undefined, 'Negative buffer (erosion) created');
if (eroded.type !== 'GeometryCollection' && !eroded.isEmpty()) {
const erodedArea = eroded.getArea();
const originalArea = poly.getArea();
assert(erodedArea < originalArea, 'Eroded polygon has smaller area');
console.log('PASS: Erosion reduced area from', originalArea, 'to', erodedArea.toFixed(2));
}
console.log('PASS: All advanced buffering tests completed');
}
function testOffsetCurves() {
// Buffer parameter constants
const CAP_ROUND = 1, CAP_FLAT = 2, CAP_SQUARE = 3;
const JOIN_ROUND = 1, JOIN_MITRE = 2, JOIN_BEVEL = 3;
// Test 1: Simple line offset
const line = wasmts.io.WKTReader.read('LINESTRING (0 0, 10 0, 10 10)');
console.log('Created test line');
// Standard offset
const offset1 = wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(line, 2);
assert(offset1 !== null && offset1 !== undefined, 'Offset curve created');
assert(offset1.type === 'LineString', 'Offset curve is LineString');
const coords1 = offset1.getCoordinates();
console.log('PASS: Standard offset curve created with', coords1.length, 'points');
// Negative offset (other side)
const offset2 = wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(line, -2);
assert(offset2 !== null && offset2 !== undefined, 'Negative offset curve created');
const coords2 = offset2.getCoordinates();
console.log('PASS: Negative offset curve created with', coords2.length, 'points');
// Test 2: Offset with custom parameters
const offset3 = wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(line, 2, CAP_FLAT, JOIN_MITRE, 10.0);
assert(offset3 !== null && offset3 !== undefined, 'Offset with custom params created');
console.log('PASS: Offset curve with custom parameters created');
// Test 3: Polygon exterior ring offset
const poly = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))');
const polyOffset = wasmts.operation.buffer.OffsetCurveBuilder.getOffsetCurve(poly, 2);
assert(polyOffset !== null && polyOffset !== undefined, 'Polygon offset curve created');
console.log('PASS: Polygon offset curve created');
console.log('PASS: All offset curve tests completed');
}
function testLineMerger() {
// Test line merging - combines connected linestrings
// Create separate line segments that connect
const line1 = wasmts.io.WKTReader.read('LINESTRING (0 0, 5 0)');
const line2 = wasmts.io.WKTReader.read('LINESTRING (5 0, 10 0)');
const line3 = wasmts.io.WKTReader.read('LINESTRING (10 0, 10 5)');
// Separate disconnected line
const line4 = wasmts.io.WKTReader.read('LINESTRING (20 20, 25 25)');
console.log('Created test lines');
// Create LineMerger
const merger = wasmts.operation.linemerge.LineMerger.create();
assert(merger !== null && merger !== undefined, 'LineMerger created');
console.log('PASS: LineMerger created');
// Add lines to merger
wasmts.operation.linemerge.LineMerger.add(merger, line1);
wasmts.operation.linemerge.LineMerger.add(merger, line2);
wasmts.operation.linemerge.LineMerger.add(merger, line3);
wasmts.operation.linemerge.LineMerger.add(merger, line4);
console.log('PASS: Added 4 lines to merger');
// Get merged result
const merged = wasmts.operation.linemerge.LineMerger.getMergedLineStrings(merger);
assert(merged !== null && merged !== undefined, 'Got merged result');
assert(Array.isArray(merged), 'Result is array');
console.log('PASS: Got merged linestrings, count:', merged.length);
// Should have 2 merged lines: one from line1+line2+line3, one from line4
assert(merged.length === 2, 'Expected 2 merged lines');
console.log('PASS: Correct number of merged lines (2)');
// Check first merged line (should connect 3 segments)
const firstMerged = merged[0];
assert(firstMerged.type === 'LineString', 'First result is LineString');
const coords1 = firstMerged.getCoordinates();
console.log('PASS: First merged line has', coords1.length, 'points');
// Check second merged line (disconnected, unchanged)
const secondMerged = merged[1];
assert(secondMerged.type === 'LineString', 'Second result is LineString');
const coords2 = secondMerged.getCoordinates();
console.log('PASS: Second merged line has', coords2.length, 'points');
console.log('PASS: All LineMerger tests completed');
}
function testCascadedPolygonUnion() {
// Test efficient union of many polygons
// Create multiple overlapping polygons
const poly1 = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))');
const poly2 = wasmts.io.WKTReader.read('POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))');
const poly3 = wasmts.io.WKTReader.read('POLYGON ((10 10, 20 10, 20 20, 10 20, 10 10))');
const poly4 = wasmts.io.WKTReader.read('POLYGON ((8 0, 18 0, 18 8, 8 8, 8 0))');
console.log('Created 4 overlapping polygons');
// Create array of polygons
const polygons = [poly1, poly2, poly3, poly4];
// Perform cascaded union
const union = wasmts.operation.union.CascadedPolygonUnion.union(polygons);
assert(union !== null && union !== undefined, 'Cascaded union result created');
assert(union.type === 'Polygon', 'Result is Polygon');
console.log('PASS: Cascaded union created');
// Union should have larger area than any individual polygon
const unionArea = union.getArea();
const poly1Area = poly1.getArea();
assert(unionArea > poly1Area, 'Union area larger than individual polygon');
console.log('PASS: Union area:', unionArea.toFixed(2), '(individual polygon: 100)');
// Union should be smaller than sum of all areas (due to overlaps)
const totalArea = poly1.getArea() + poly2.getArea() + poly3.getArea() + poly4.getArea();
assert(unionArea < totalArea, 'Union area smaller than sum (overlaps removed)');
console.log('PASS: Union removes overlaps (total: ' + totalArea + ', union: ' + unionArea.toFixed(2) + ')');
console.log('PASS: All CascadedPolygonUnion tests completed');
}
function testNewGeometryMethods() {
// Test data
const poly1 = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))');
const poly3 = wasmts.io.WKTReader.read('POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))'); // overlapping
const line = wasmts.io.WKTReader.read('LINESTRING (0 0, 5 5, 10 0)');
const complexLine = wasmts.io.WKTReader.read('LINESTRING (0 0, 1 1, 1 0, 0 1, 2 2)'); // self-intersecting
// Test copy first (needed for equalsTopo test)
const poly1Copy = poly1.copy();
assert(poly1Copy !== null, 'copy returns geometry');
assert(poly1Copy.type === 'Polygon', 'Copy is same type');
assert(poly1Copy.getArea() === poly1.getArea(), 'Copy has same area');
console.log('PASS: copy test (early check)');
// Test equalsTopo
assert(poly1Copy.equalsTopo(poly1) === true, 'equalsTopo returns true for copied geometry');
assert(poly1.equalsTopo(poly3) === false, 'equalsTopo returns false for different geometries');
console.log('PASS: equalsTopo tests');
// Test covers and coveredBy
const container = wasmts.io.WKTReader.read('POLYGON ((0 0, 20 0, 20 20, 0 20, 0 0))');
const contained = wasmts.io.WKTReader.read('POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))');
assert(container.covers(contained) === true, 'covers returns true when geometry covers another');
assert(contained.coveredBy(container) === true, 'coveredBy returns true when geometry is covered');
assert(contained.covers(container) === false, 'covers returns false when geometry does not cover');
console.log('PASS: covers/coveredBy tests');
// Test getEnvelope
const envelope = poly1.getEnvelope();
assert(envelope !== null, 'getEnvelope returns geometry');
assert(envelope.type === 'Polygon', 'Envelope is a Polygon');
const envArea = envelope.getArea();
assert(envArea === 100, 'Envelope area matches bounding box');
console.log('PASS: getEnvelope test, area:', envArea);
// Test getInteriorPoint
const interiorPt = poly1.getInteriorPoint();
assert(interiorPt !== null, 'getInteriorPoint returns point');
assert(interiorPt.type === 'Point', 'Interior point is a Point');
assert(poly1.contains(interiorPt) === true, 'Interior point is inside geometry');
console.log('PASS: getInteriorPoint test');
// Test copy
const polyCopy = poly1.copy();
assert(polyCopy !== null, 'copy returns geometry');
assert(polyCopy.equalsTopo(poly1) === true, 'Copy is topologically equal to original');
assert(polyCopy.getArea() === poly1.getArea(), 'Copy has same area');
console.log('PASS: copy test');
// Test reverse
const reversed = line.reverse();
assert(reversed !== null, 'reverse returns geometry');
const origCoords = line.getCoordinates();
const revCoords = reversed.getCoordinates();
assert(revCoords.length === origCoords.length, 'Reversed has same number of coordinates');
assert(revCoords[0].x === origCoords[origCoords.length - 1].x, 'Reversed coordinates are in opposite order');
console.log('PASS: reverse test');
// Test normalize
const normalized = poly1.normalize();
assert(normalized !== null, 'normalize returns geometry');
assert(normalized.type === 'Polygon', 'Normalized geometry is same type');
assert(normalized.getArea() === poly1.getArea(), 'Normalized has same area');
console.log('PASS: normalize test');
// Test isSimple
assert(line.isSimple() === true, 'isSimple returns true for simple line');
assert(complexLine.isSimple() === false, 'isSimple returns false for self-intersecting line');
console.log('PASS: isSimple test');
// Test isRectangle
assert(poly1.isRectangle() === true, 'isRectangle returns true for rectangle');
const triangle = wasmts.io.WKTReader.read('POLYGON ((0 0, 10 0, 5 10, 0 0))');
assert(triangle.isRectangle() === false, 'isRectangle returns false for triangle');
console.log('PASS: isRectangle test');
// Test getUserData / setUserData
const testData = { id: 123, name: 'test polygon' };
poly1.setUserData(testData);
const retrievedData = poly1.getUserData();
assert(retrievedData !== null, 'getUserData returns data');
assert(retrievedData.id === 123, 'User data preserved correctly');
assert(retrievedData.name === 'test polygon', 'User data object properties preserved');
console.log('PASS: getUserData/setUserData test');
// Test getGeometryType
const testPoint = wasmts.geom.createPoint(1, 2);
const pointType = wasmts.geom.getGeometryType(testPoint);
assert(pointType === 'Point', 'Point type should be "Point"');
console.log('PASS: getGeometryType for Point:', pointType);
const lineWkt = 'LINESTRING(0 0, 1 1, 2 0)';
const testLine = wasmts.io.WKTReader.read(lineWkt);
const lineType = wasmts.geom.getGeometryType(testLine);
assert(lineType === 'LineString', 'LineString type should be "LineString"');
console.log('PASS: getGeometryType for LineString:', lineType);
const polyWkt = 'POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))';
const testPoly = wasmts.io.WKTReader.read(polyWkt);
const polyType = wasmts.geom.getGeometryType(testPoly);
assert(polyType === 'Polygon', 'Polygon type should be "Polygon"');
console.log('PASS: getGeometryType for Polygon:', polyType);
console.log('PASS: All new geometry methods tested');
}
function testGeoJSONIO() {
// Test GeoJSON namespace exists
assert(typeof wasmts.io.GeoJSONReader !== 'undefined', 'GeoJSONReader exists');
assert(typeof wasmts.io.GeoJSONWriter !== 'undefined', 'GeoJSONWriter exists');
console.log('PASS: GeoJSON namespaces exist');
// Test Point
const pointGeoJSON = '{"type":"Point","coordinates":[5,10]}';
const point = wasmts.io.GeoJSONReader.read(pointGeoJSON);
assert(point !== null && point !== undefined, 'GeoJSON Point parsed');
assert(point.type === 'Point', 'GeoJSON Point has correct type');
const pointCoords = point.getCoordinates();
assert(pointCoords[0].x === 5, 'Point X coordinate correct');
assert(pointCoords[0].y === 10, 'Point Y coordinate correct');
console.log('PASS: GeoJSON Point read:', pointCoords[0]);
// Test Point write
const pointOut = wasmts.io.GeoJSONWriter.write(point);
assert(typeof pointOut === 'string', 'GeoJSONWriter returns string');
const pointParsed = JSON.parse(pointOut);
assert(pointParsed.type === 'Point', 'Written GeoJSON has correct type');
assert(pointParsed.coordinates[0] === 5, 'Written coordinates correct');
console.log('PASS: GeoJSON Point write:', pointOut);
// Test LineString
const lineGeoJSON = '{"type":"LineString","coordinates":[[0,0],[10,10],[20,0]]}';
const line = wasmts.io.GeoJSONReader.read(lineGeoJSON);
assert(line.type === 'LineString', 'GeoJSON LineString parsed');
const lineCoords = line.getCoordinates();
assert(lineCoords.length === 3, 'LineString has 3 coordinates');
console.log('PASS: GeoJSON LineString read');
// Test LineString round-trip
const lineOut = wasmts.io.GeoJSONWriter.write(line);
const lineParsed = JSON.parse(lineOut);
assert(lineParsed.type === 'LineString', 'LineString round-trip type');
assert(lineParsed.coordinates.length === 3, 'LineString round-trip coords');
console.log('PASS: GeoJSON LineString round-trip');
// Test Polygon
const polyGeoJSON = '{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}';
const poly = wasmts.io.GeoJSONReader.read(polyGeoJSON);
assert(poly.type === 'Polygon', 'GeoJSON Polygon parsed');
const polyArea = poly.getArea();
assert(polyArea === 100, 'Polygon area is 100');
console.log('PASS: GeoJSON Polygon read, area:', polyArea);
// Test Polygon round-trip
const polyOut = wasmts.io.GeoJSONWriter.write(poly);
const polyParsed = JSON.parse(polyOut);
assert(polyParsed.type === 'Polygon', 'Polygon round-trip type');
assert(polyParsed.coordinates[0].length === 5, 'Polygon ring has 5 coords');
console.log('PASS: GeoJSON Polygon round-trip');
// Test Polygon with hole
const polyWithHoleGeoJSON = '{"type":"Polygon","coordinates":[[[0,0],[20,0],[20,20],[0,20],[0,0]],[[5,5],[15,5],[15,15],[5,15],[5,5]]]}';
const polyWithHole = wasmts.io.GeoJSONReader.read(polyWithHoleGeoJSON);
assert(polyWithHole.type === 'Polygon', 'Polygon with hole parsed');
const holeArea = polyWithHole.getArea();
assert(holeArea === 300, 'Polygon with hole area is 300 (400 - 100)');
console.log('PASS: GeoJSON Polygon with hole, area:', holeArea);
// Test MultiPoint
const multiPointGeoJSON = '{"type":"MultiPoint","coordinates":[[0,0],[10,10],[20,20]]}';
const multiPoint = wasmts.io.GeoJSONReader.read(multiPointGeoJSON);
assert(multiPoint.type === 'MultiPoint', 'GeoJSON MultiPoint parsed');
console.log('PASS: GeoJSON MultiPoint read');
// Test MultiLineString
const multiLineGeoJSON = '{"type":"MultiLineString","coordinates":[[[0,0],[10,10]],[[20,20],[30,30]]]}';
const multiLine = wasmts.io.GeoJSONReader.read(multiLineGeoJSON);
assert(multiLine.type === 'MultiLineString', 'GeoJSON MultiLineString parsed');
console.log('PASS: GeoJSON MultiLineString read');
// Test MultiPolygon
const multiPolyGeoJSON = '{"type":"MultiPolygon","coordinates":[[[[0,0],[10,0],[10,10],[0,10],[0,0]]],[[[20,20],[30,20],[30,30],[20,30],[20,20]]]]}';
const multiPoly = wasmts.io.GeoJSONReader.read(multiPolyGeoJSON);
assert(multiPoly.type === 'MultiPolygon', 'GeoJSON MultiPolygon parsed');
const multiPolyArea = multiPoly.getArea();
assert(multiPolyArea === 200, 'MultiPolygon area is 200');
console.log('PASS: GeoJSON MultiPolygon read, area:', multiPolyArea);
// Test GeometryCollection
const gcGeoJSON = '{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[0,0]},{"type":"LineString","coordinates":[[0,0],[10,10]]}]}';
const gc = wasmts.io.GeoJSONReader.read(gcGeoJSON);
assert(gc.type === 'GeometryCollection', 'GeoJSON GeometryCollection parsed');
console.log('PASS: GeoJSON GeometryCollection read');
// Test 3D coordinates
const point3dGeoJSON = '{"type":"Point","coordinates":[5,10,15]}';
const point3d = wasmts.io.GeoJSONReader.read(point3dGeoJSON);
const coords3d = point3d.getCoordinates();
assert(coords3d[0].z === 15, '3D Point Z coordinate preserved');
console.log('PASS: GeoJSON 3D Point read, z:', coords3d[0].z);
// Test 3D round-trip
const point3dOut = wasmts.io.GeoJSONWriter.write(point3d);
const point3dParsed = JSON.parse(point3dOut);
assert(point3dParsed.coordinates.length === 3, '3D Point has 3 coordinates');
assert(point3dParsed.coordinates[2] === 15, '3D Z coordinate in output');
console.log('PASS: GeoJSON 3D round-trip');
// Test buffer result to GeoJSON
const buffered = wasmts.geom.createPoint(0, 0).buffer(10);
const bufferedGeoJSON = wasmts.io.GeoJSONWriter.write(buffered);
const bufferedParsed = JSON.parse(bufferedGeoJSON);
assert(bufferedParsed.type === 'Polygon', 'Buffered point is Polygon in GeoJSON');
assert(bufferedParsed.coordinates[0].length > 10, 'Buffer has many vertices');
console.log('PASS: Buffer to GeoJSON, vertices:', bufferedParsed.coordinates[0].length);
// Test GeoJSONWriter instance API (1:1 coverage)
console.log('\n--- GeoJSON Instance API Tests ---\n');
// Test GeoJSONWriter.create()
const writer = wasmts.io.GeoJSONWriter.create();
assert(writer !== null && writer !== undefined, 'GeoJSONWriter.create() works');