Skip to content

Commit

Permalink
feat: add DmsUnsignedDelimitedFormat for parsing DMS unsigned delimit…
Browse files Browse the repository at this point in the history
…ed coordinates and include related tests
  • Loading branch information
reskume committed Dec 4, 2024
1 parent 4826486 commit d1c192c
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 44 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,13 @@ Currently the out-of-the-box format parsers supports various formats and handles
- `N40°7'23", W74°7'23"`
- `N40°7'23"W74°7'23"`
- `N 40°7'23.123" W 74°7'23.123"`
- `40:7:23 -74:7:23`
- `40:7:23, -74:7:23`
- `40:7:23.123, -74:7:23.123`
- ``
- ``
- ``
- ``
- ``
- ``
- ``
78 changes: 78 additions & 0 deletions src/formats/dms-unsigned-delimited-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { z } from 'zod';
import type { Coordinate } from '../types.js';
import { validateSchema } from '../validate-schema.js';
import { BaseFormat } from './base-format.js';

const REGEX = /^(-?\d+):(\d+):(\d+(?:\.\d+)?)\s*\,?\s*(-?\d+):(\d+):(\d+(?:\.\d+)?)$/;

/**
* Parses coordinates strings in DMS unsigned delimited format. Coordinate ordering is
* always latitude, longitude.
*
* Supported formats:
*
* 40:7:23 -74:7:23
* 40:7:23, -74:7:23
* 40:7:23.123, -74:7:23.123
*/
export class DmsUnsignedDelimitedFormat extends BaseFormat {
parse(coordinateString: string): Coordinate {
validateSchema(coordinateString, z.string(), { assert: true, name: 'coordinateString' });

if (DmsUnsignedDelimitedFormat.canParse(coordinateString) === false) {
throw new Error('Invalid coordinate string');
}
// use the regex to parse the latitude and longitude
const match = coordinateString.match(REGEX);
if (match == null) {
throw new Error('Invalid coordinate string');
}
const matchLatDegree = match[1];
const matchLatMinutes = match[2];
const matchLatSeconds = match[3];
const matchLonDegree = match[4];
const matchLonMinutes = match[5];
const matchLonSeconds = match[6];

this.enforceValidLatitudeDegrees(matchLatDegree);
this.enforceValidMinutes(matchLatMinutes);
this.enforceValidSeconds(matchLatSeconds);
this.enforceValidLongitudeDegrees(matchLonDegree);
this.enforceValidMinutes(matchLonMinutes);
this.enforceValidSeconds(matchLonSeconds);

const latDegree = Number.parseInt(matchLatDegree);
const latMinutes = Number.parseInt(matchLatMinutes);
const latSeconds = Number.parseFloat(matchLatSeconds);
const lonDegree = Number.parseInt(matchLonDegree);
const lonMinutes = Number.parseInt(matchLonMinutes);
const lonSeconds = Number.parseFloat(matchLonSeconds);

const decimalLat = this.dmsToDecimal({
degrees: Math.abs(latDegree),
minutes: latMinutes,
seconds: latSeconds,
direction: latDegree < 0 ? 'S' : 'N',
});
const decimalLon = this.dmsToDecimal({
degrees: Math.abs(lonDegree),
minutes: lonMinutes,
seconds: lonSeconds,
direction: lonDegree < 0 ? 'W' : 'E',
});

const lat = decimalLat.toFixed(this.precision);
const lon = decimalLon.toFixed(this.precision);

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

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 @@ -11,6 +11,7 @@ import { DmUnsignedSuffixedHemisphereFormat } from './formats/dm-unsigned-suffix
import { DmsSignedFormat } from './formats/dms-signed-format.js';
import { DmsSignedPrefixedHemisphereFormat } from './formats/dms-signed-prefixed-hemisphere-format.js';
import { DmsSignedSuffixedHemisphereFormat } from './formats/dms-signed-suffixed-hemisphere-format.js';
import { DmsUnsignedDelimitedFormat } from './formats/dms-unsigned-delimited-format.js';
import { DmsUnsignedFormat } from './formats/dms-unsigned-format.js';
import type { Coordinate } from './types.js';
import { validateSchema } from './validate-schema.js';
Expand Down Expand Up @@ -59,6 +60,7 @@ export class Parser {
new DmsSignedFormat({ precision: precision }),
new DmsSignedPrefixedHemisphereFormat({ precision: precision }),
new DmsSignedSuffixedHemisphereFormat({ precision: precision }),
new DmsUnsignedDelimitedFormat({ precision: precision }),
new DmsUnsignedFormat({ precision: precision }),
];
let formatParsers = options?.formatParsers || defaultParsers;
Expand Down
41 changes: 41 additions & 0 deletions tests/dms-unsigned-delimited-format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest';
import { DmsUnsignedDelimitedFormat } from '../src/formats/dms-unsigned-delimited-format.js';

describe('canParse', () => {
it('returns true for known formats', () => {
expect(DmsUnsignedDelimitedFormat.canParse(`40:7:23 -74:7:23`)).toBe(true);
expect(DmsUnsignedDelimitedFormat.canParse(`40:7:23, -74:7:23`)).toBe(true);
expect(DmsUnsignedDelimitedFormat.canParse(`40:7:23,-74:7:23`)).toBe(true);
expect(DmsUnsignedDelimitedFormat.canParse(`40:7:23.9999, -74:7:23.9999`)).toBe(true);
});
});
describe('parse', () => {
it(`returns the correct latitude and longitude for 40:7:23 -74:7:23`, () => {
const formatParser = new DmsUnsignedDelimitedFormat();
const result = formatParser.parse(`40:7:23 -74:7:23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40:7:23, -74:7:23`, () => {
const formatParser = new DmsUnsignedDelimitedFormat();
const result = formatParser.parse(`40:7:23, -74:7:23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40:7:23,-74:7:23`, () => {
const formatParser = new DmsUnsignedDelimitedFormat();
const result = formatParser.parse(`40:7:23,-74:7:23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40:7:23.9999", -74:7:23.9999`, () => {
const formatParser = new DmsUnsignedDelimitedFormat({ precision: 5 });
const result = formatParser.parse(`40:7:23.9999, -74:7:23.9999`);

expect(result.latitude).toBe(40.12333);
expect(result.longitude).toBe(-74.12333);
});
});
92 changes: 48 additions & 44 deletions tests/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,50 +334,6 @@ describe('Test that all configured format parsers do not interfere', () => {
expect(result.longitude).toBe(-74.12333);
});
});
describe('test dsm unsigned format', () => {
it(`returns the correct latitude and longitude for 40 7 23 -74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23 -74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23, -74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23 , -74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23,-74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23,-74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23 -74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23 -74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23, -74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23, -74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23.9999, -74 7 23.9999`, () => {
const parser = new Parser({ precision: 5 });
const result = parser.parse(`40 7 23.9999, -74 7 23.9999`);

expect(result.latitude).toBe(40.12333);
expect(result.longitude).toBe(-74.12333);
});
});
describe('test dms signed prefixed hemisphere format', () => {
it(`returns the correct latitude and longitude for N40°7'23" W74°7'23"`, () => {
const parser = new Parser();
Expand Down Expand Up @@ -479,5 +435,53 @@ describe('Test that all configured format parsers do not interfere', () => {
expect(result.longitude).toBe(-74.12333);
});
});
describe('test dms unsigned delimited format', () => {

});
describe('test dms unsigned format', () => {
it(`returns the correct latitude and longitude for 40 7 23 -74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23 -74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23, -74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23 , -74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23,-74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23,-74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23 -74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23 -74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23, -74 7 23`, () => {
const parser = new Parser();
const result = parser.parse(`40 7 23, -74 7 23`);
expect(result.latitude).toBe(40.123);
expect(result.longitude).toBe(-74.123);
});

it(`returns the correct latitude and longitude for 40 7 23.9999, -74 7 23.9999`, () => {
const parser = new Parser({ precision: 5 });
const result = parser.parse(`40 7 23.9999, -74 7 23.9999`);

expect(result.latitude).toBe(40.12333);
expect(result.longitude).toBe(-74.12333);
});
});
// describe('test', () => {});
// describe('test', () => {});
});

0 comments on commit d1c192c

Please sign in to comment.