Skip to content

Commit

Permalink
feat: add DmUnsignedPrefixedHemisphereFormat for parsing DM coordinat…
Browse files Browse the repository at this point in the history
…es and enhance tests
  • Loading branch information
reskume committed Dec 4, 2024
1 parent a54b511 commit 89badb1
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 73 deletions.
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,29 +84,29 @@ const parser = new Parser({ formatParsers: [customFormatParser, decimalParser] }

### Supported Formats

Currently the out-of-the-box format parsers support the following formats and to a degree their respective variants with or without whitespaces:
Currently the out-of-the-box format parsers supports various formats and handles their various variations ,e.g. with or without whitespaces, gracefully:

- `10, 12`
- `1.234, 5.678`
- `1.234,5.678`
- `1.234 5.678`
- `12N,56E`
- `12.234 N 56.678 E`
- `12.234 N, 56.678 E`
- `12.234N,56.678E`
- `12.234N56.678E`
- `1° 5°`
- `1.234° 5.678°`
- `1.234°, 5.678°`
- `1.234°,5.678°`
- `N 12° E 5°`
- `N 1.234° E 5.678°`
- `N 1.234°, E5.678°`
- `1° N 5° E`
- `1.234° N 5.678° E`
- `1.234° N, 5.678° E`
- `1.234°N,5.678°E`
- `1.234°N5.678°E`
- `4007N 7407W`
- `4007.38N7407.38W`
- `10, 12`
- `1.234, 5.678`
- `N 12, E 56`
- `N 12.234 E 56.678`
- `N 12.234, E 56.678`
- `12 N, 56 E`
- `12.234 N 56.678 E`
- `12.234 N, 56.678 E`
- `N 4007 W 7407`
- `N 4007.38 W 7407.38`
- `4007 N 7407 W`
- `4007.38 N 7407.38 W`
- `4007.38N 7407.38W`
- ``
- ``
- ``
- ``
Expand Down
77 changes: 77 additions & 0 deletions src/formats/dm-unsigned-prefixed-hemisphere-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { z } from 'zod';
import type { Coordinate, DmsCoordinate } from '../types.js';
import { validateSchema } from '../validate-schema.js';
import { BaseFormat } from './base-format.js';

const REGEX = /^([NS]\s*\d{3,4}(\.\d+)?\s*)\s*([EW]\s*\d{3,5}(\.\d+)?\s*)$/;

/**
* Parses coordinates strings in DM format with decimal minutes.
*
* Supported formats:
*
* N4007 W7407
* N4007.38W7407.38
* N 4007.38 W 7407.38
* N4007.38 W7407.38
*/
export class DmUnsignedPrefixedHemisphereFormat extends BaseFormat {
parse(coordinateString: string): Coordinate {
validateSchema(coordinateString, z.string(), { assert: true, name: 'coordinateString' });

if (DmUnsignedPrefixedHemisphereFormat.canParse(coordinateString) === false) {
throw new Error('Invalid coordinate string');
}
this.enforceNoHyphen(coordinateString);
// use the regex to parse the latitude and longitude
const match = coordinateString.match(REGEX);
if (match == null) {
throw new Error('Invalid coordinate string');
}
const latitude = match[1];
const longitude = match[3];
// to DMS
const dmsLat = this.toDms(latitude.replace(/\s/g, ''));
const dmsLon = this.toDms(longitude.replace(/\s/g, ''));
// DMS to decimal
const decimalLat = this.dmsToDecimal(dmsLat);
const decimalLon = this.dmsToDecimal(dmsLon);
this.enforceValidLatitude(decimalLat);
this.enforceValidLongitude(decimalLon);
const lat = decimalLat.toFixed(this.precision);
const lon = decimalLon.toFixed(this.precision);

return {
latitude: parseFloat(lat),
longitude: parseFloat(lon),
};
}

/**
* Converts a DMS notation coordinate like "4007.38N" to DMS parts.
*
* @param {string} value - The parsed DMS value, e.g. "4007.38N" or "74007.38W"
* @return {import('../types').openaip.CoordinateParser.DmsCoordinate}
*/
toDms(value: string): DmsCoordinate {
validateSchema(value, z.string(), { assert: true, name: 'value' });

const match = value.match(/^([NSWE])(\d{2,3})(\d{2})(\.\d+)?$/);
if (match) {
return {
degrees: parseInt(match[2], 10),
minutes: parseInt(match[3], 10),
seconds: Math.round(60 * parseFloat(`0${match[4]}`)),
direction: match[1],
};
} else {
throw new Error('Invalid coordinate string');
}
}

static canParse(coordinateString: string): boolean {
validateSchema(coordinateString, z.string(), { assert: true, name: 'coordinateString' });

return REGEX.test(coordinateString);
}
}
2 changes: 2 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DecimalSignedSuffixedHemisphereFormat } from './formats/decimal-signed-
import { DecimalUnsignedFormat } from './formats/decimal-unsigned-format.js';
import { DecimalUnsignedPrefixedHemisphereFormat } from './formats/decimal-unsigned-prefixed-hemisphere-format.js';
import { DecimalUnsignedSuffixedHemisphereFormat } from './formats/decimal-unsigned-suffixed-hemisphere-format.js';
import { DmUnsignedPrefixedHemisphereFormat } from './formats/dm-unsigned-prefixed-hemisphere-format.js';
import { DmUnsignedSuffixedHemisphereFormat } from './formats/dm-unsigned-suffixed-hemisphere-format.js';
import type { Coordinate } from './types.js';
import { validateSchema } from './validate-schema.js';
Expand Down Expand Up @@ -49,6 +50,7 @@ export class Parser {
new DecimalSignedFormat({ precision: precision }),
new DecimalUnsignedPrefixedHemisphereFormat({ precision: precision }),
new DecimalSignedSuffixedHemisphereFormat({ precision: precision }),
new DmUnsignedPrefixedHemisphereFormat({ precision: precision }),
new DmUnsignedSuffixedHemisphereFormat({ precision: precision }),
];
let formatParsers = options?.formatParsers || defaultParsers;
Expand Down
21 changes: 0 additions & 21 deletions tests/decimal-unsigned-format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,4 @@ describe('parse', () => {
expect(result.latitude).toBe(1.234);
expect(result.longitude).toBe(5.678);
});

it("returns the correct latitude and longitude for '1.23412312 5.6782356' with precision 4", () => {
const formatParser = new DecimalUnsignedFormat({ precision: 4 });
const result = formatParser.parse('1.23412312 5.6782356');
expect(result.latitude).toBe(1.2341);
expect(result.longitude).toBe(5.6782);
});

it("returns the correct latitude and longitude for '-1.23412312 -5.6782356' with precision 4", () => {
const formatParser = new DecimalUnsignedFormat({ precision: 4 });
const result = formatParser.parse('-1.23412312 -5.6782356');
expect(result.latitude).toBe(-1.2341);
expect(result.longitude).toBe(-5.6782);
});

it("returns the correct latitude and longitude for '1.23412312 -5.6782356' with precision 4", () => {
const formatParser = new DecimalUnsignedFormat({ precision: 4 });
const result = formatParser.parse('1.23412312 -5.6782356');
expect(result.latitude).toBe(1.2341);
expect(result.longitude).toBe(-5.6782);
});
});
46 changes: 46 additions & 0 deletions tests/dm-unsigned-prefixed-hemisphere-format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest';
import { DmUnsignedPrefixedHemisphereFormat } from '../src/formats/dm-unsigned-prefixed-hemisphere-format.js';

describe('canParse', () => {
it('returns true for known formats', () => {
expect(DmUnsignedPrefixedHemisphereFormat.canParse('N4007 W7407')).toBe(true);
expect(DmUnsignedPrefixedHemisphereFormat.canParse('N4007.38W7407.38')).toBe(true);
expect(DmUnsignedPrefixedHemisphereFormat.canParse('N 4007.38 W 7407.38')).toBe(true);
expect(DmUnsignedPrefixedHemisphereFormat.canParse('N4007.38 W7407.38')).toBe(true);
});

it('returns false for unknown formats', () => {
expect(DmUnsignedPrefixedHemisphereFormat.canParse('4007.38N740738W')).toBe(false);
expect(DmUnsignedPrefixedHemisphereFormat.canParse('4007.38N7407.38P')).toBe(false);
expect(DmUnsignedPrefixedHemisphereFormat.canParse('4007.38N7407.38 ')).toBe(false);
});
});
describe('parse', () => {
it("returns the correct latitude and longitude for 'N4007 W7407'", () => {
const formatParser = new DmUnsignedPrefixedHemisphereFormat();
const result = formatParser.parse('N4007 W7407');
expect(result.latitude).toBe(40.117);
expect(result.longitude).toBe(-74.117);
});

it("returns the correct latitude and longitude for 'N4007.38W7407.38'", () => {
const formatParser = new DmUnsignedPrefixedHemisphereFormat();
const result = formatParser.parse('N4007.38W7407.38');
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it("returns the correct latitude and longitude for 'N 4007.38 W 7407.38'", () => {
const formatParser = new DmUnsignedPrefixedHemisphereFormat();
const result = formatParser.parse('N 4007.38 W 7407.38');
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it("returns the correct latitude and longitude for 'N4007.38 W7407.38'", () => {
const formatParser = new DmUnsignedPrefixedHemisphereFormat();
const result = formatParser.parse('N4007.38 W7407.38');
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});
});
9 changes: 2 additions & 7 deletions tests/dm-unsigned-suffixed-hemisphere-format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { DmUnsignedSuffixedHemisphereFormat } from '../src/formats/dm-unsigned-s

describe('canParse', () => {
it('returns true for known formats', () => {
expect(DmUnsignedSuffixedHemisphereFormat.canParse('4007N 7407W')).toBe(true);
expect(DmUnsignedSuffixedHemisphereFormat.canParse('4007.38N7407.38W')).toBe(true);
expect(DmUnsignedSuffixedHemisphereFormat.canParse('4007.38 N 7407.38 W')).toBe(true);
expect(DmUnsignedSuffixedHemisphereFormat.canParse('4007.38N 7407.38W')).toBe(true);
});

Expand Down Expand Up @@ -41,11 +43,4 @@ describe('parse', () => {
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it("returns the correct latitude and longitude for '4007.3812342N 7407.38123W' with precision 4", () => {
const formatParser = new DmUnsignedSuffixedHemisphereFormat({ precision: 4 });
const result = formatParser.parse('4007.3812342N 7407.38123W');
expect(result.latitude).toBe(40.1231);
expect(result.longitude).toBe(-74.1231);
});
});
57 changes: 29 additions & 28 deletions tests/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,27 +152,6 @@ describe('Test that all configured format parsers do not interfere', () => {
expect(result.latitude).toBe(1.234);
expect(result.longitude).toBe(5.678);
});

it("returns the correct latitude and longitude for '1.23412312 5.6782356' with precision 4", () => {
const parser = new Parser();
const result = parser.parse('1.23412312 5.6782356');
expect(result.latitude).toBe(1.234);
expect(result.longitude).toBe(5.678);
});

it("returns the correct latitude and longitude for '-1.23412312 -5.6782356' with precision 4", () => {
const parser = new Parser();
const result = parser.parse('-1.23412312 -5.6782356');
expect(result.latitude).toBe(-1.234);
expect(result.longitude).toBe(-5.678);
});

it("returns the correct latitude and longitude for '1.23412312 -5.6782356' with precision 4", () => {
const parser = new Parser();
const result = parser.parse('1.23412312 -5.6782356');
expect(result.latitude).toBe(1.234);
expect(result.longitude).toBe(-5.678);
});
});
describe('test decimal unsigned prefixed hemisphere format', () => {
it("returns the correct latitude and longitude for 'N12 E56'", () => {
Expand Down Expand Up @@ -253,6 +232,35 @@ describe('Test that all configured format parsers do not interfere', () => {
expect(result.longitude).toBe(5.678);
});
});
describe('test dm unsigned prefixed hemisphere format', () => {
it("returns the correct latitude and longitude for 'N4007 W7407'", () => {
const parser = new Parser();
const result = parser.parse('N4007 W7407');
expect(result.latitude).toBe(40.117);
expect(result.longitude).toBe(-74.117);
});

it("returns the correct latitude and longitude for 'N4007.38W7407.38'", () => {
const parser = new Parser();
const result = parser.parse('N4007.38W7407.38');
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it("returns the correct latitude and longitude for 'N 4007.38 W 7407.38'", () => {
const parser = new Parser();
const result = parser.parse('N 4007.38 W 7407.38');
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it("returns the correct latitude and longitude for 'N4007.38 W7407.38'", () => {
const parser = new Parser();
const result = parser.parse('N4007.38 W7407.38');
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});
});
describe('test dm unsigned suffixed hemisphere format', () => {
it("returns the correct latitude and longitude for '4007N 7407W'", () => {
const parser = new Parser();
Expand Down Expand Up @@ -281,12 +289,5 @@ describe('Test that all configured format parsers do not interfere', () => {
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it("returns the correct latitude and longitude for '4007.3812342N 7407.38123W' with precision 4", () => {
const parser = new Parser();
const result = parser.parse('4007.3812342N 7407.38123W');
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});
});
});

0 comments on commit 89badb1

Please sign in to comment.