import { describe, expect, it } from "vitest"; import { analyseCollection, haversine, simplifyLine, toDms, } from "../../src/geo/analysis"; import { parseCoordinateCsv, parseGeoJson, parseGpx, serializeGeo, } from "../../src/geo/formats"; describe("geospatial formats", () => { it("parses GeoJSON and preserves safe scalar properties", () => { const result = parseGeoJson( '{"type":"Feature","properties":{"name":"A","nested":{"x":1}},"geometry":{"type":"Point","coordinates":[13.4,52.5]}}', ); expect(result.collection.features[0]?.properties).toEqual({ name: "A" }); }); it("rejects XML document types", () => expect(() => parseGpx("")).toThrow(/DOCTYPE/u)); it("rejects excessive XML nesting before DOM construction", () => expect(() => parseGpx(`${"".repeat(257)}${"".repeat(257)}`)).toThrow( /256-level/u, )); it("round-trips coordinate CSV into valid GeoJSON", () => { const result = parseCoordinateCsv( "name,lat,lon,elevation\nBerlin,52.5,13.4,42", ); expect( parseGeoJson(serializeGeo(result.collection, "geojson").text).collection, ).toEqual(result.collection); }); }); describe("geospatial analysis", () => { it("uses great-circle distance", () => expect(haversine([0, 0], [1, 0])).toBeCloseTo(111_195, -1)); it("simplifies a line while preserving endpoints", () => expect( simplifyLine( [ [0, 0], [0.001, 0.00001], [0.002, 0], ], 10, ), ).toEqual([ [0, 0], [0.002, 0], ])); it("analyses distance and elevation", () => expect( analyseCollection({ type: "FeatureCollection", features: [ { type: "Feature", properties: {}, geometry: { type: "LineString", coordinates: [ [0, 0, 10], [0.01, 0, 25], ], }, }, ], }), ).toMatchObject({ points: 2, features: 1, ascent: 15, descent: 0 })); it("formats directional DMS", () => expect(toDms(-13.5, "longitude")).toBe("13° 30′ 0.000″ W")); });