aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Pollack <[email protected]>2025-06-16 00:52:49 +0200
committerGitHub <[email protected]>2025-06-16 00:52:49 +0200
commitd07d96d01833085f2d3c5f9c851a572ebf8c47df (patch)
treeb72fa0a7d325744a3c616baf742c8837583b6739
parentf08b24374c81c11adff2a68015483a41ae06d9d3 (diff)
downloadfaker-d07d96d01833085f2d3c5f9c851a572ebf8c47df.tar.xz
faker-d07d96d01833085f2d3c5f9c851a572ebf8c47df.zip
feat(location): simple coordinate methods (#3528)
-rw-r--r--src/index.ts2
-rw-r--r--src/modules/location/index.ts348
-rw-r--r--src/simple-faker.ts3
-rw-r--r--test/modules/location.spec.ts106
4 files changed, 238 insertions, 221 deletions
diff --git a/src/index.ts b/src/index.ts
index a8c4ecea..c100642a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -69,7 +69,7 @@ export type { HelpersModule, SimpleHelpersModule } from './modules/helpers';
export type { ImageModule } from './modules/image';
export { IPv4Network } from './modules/internet';
export type { IPv4NetworkType, InternetModule } from './modules/internet';
-export type { LocationModule } from './modules/location';
+export type { LocationModule, SimpleLocationModule } from './modules/location';
export type { LoremModule } from './modules/lorem';
export type { MusicModule } from './modules/music';
export type { NumberModule } from './modules/number';
diff --git a/src/modules/location/index.ts b/src/modules/location/index.ts
index 29267eb1..40d065b0 100644
--- a/src/modules/location/index.ts
+++ b/src/modules/location/index.ts
@@ -1,5 +1,6 @@
+import type { Faker } from '../..';
import { FakerError } from '../../errors/faker-error';
-import { ModuleBase } from '../../internal/module-base';
+import { SimpleModuleBase } from '../../internal/module-base';
/**
* Represents a language with its full name, 2 character ISO 639-1 code, and 3 character ISO 639-2 code.
@@ -22,6 +23,178 @@ export interface Language {
}
/**
+ * Module with location functions that don't require localized data
+ */
+export class SimpleLocationModule extends SimpleModuleBase {
+ /**
+ * Generates a random latitude.
+ *
+ * @param options An options object.
+ * @param options.max The upper bound for the latitude to generate. Defaults to `90`.
+ * @param options.min The lower bound for the latitude to generate. Defaults to `-90`.
+ * @param options.precision The number of decimal points of precision for the latitude. Defaults to `4`.
+ *
+ * @example
+ * faker.location.latitude() // -30.9501
+ * faker.location.latitude({ max: 10 }) // 5.7225
+ * faker.location.latitude({ max: 10, min: -10 }) // -9.6273
+ * faker.location.latitude({ max: 10, min: -10, precision: 5 }) // 2.68452
+ *
+ * @since 8.0.0
+ */
+ latitude(
+ options: {
+ /**
+ * The upper bound for the latitude to generate.
+ *
+ * @default 90
+ */
+ max?: number;
+ /**
+ * The lower bound for the latitude to generate.
+ *
+ * @default -90
+ */
+ min?: number;
+ /**
+ * The number of decimal points of precision for the latitude.
+ *
+ * @default 4
+ */
+ precision?: number;
+ } = {}
+ ): number {
+ const { max = 90, min = -90, precision = 4 } = options;
+
+ return this.faker.number.float({ min, max, fractionDigits: precision });
+ }
+
+ /**
+ * Generates a random longitude.
+ *
+ * @param options An options object.
+ * @param options.max The upper bound for the longitude to generate. Defaults to `180`.
+ * @param options.min The lower bound for the longitude to generate. Defaults to `-180`.
+ * @param options.precision The number of decimal points of precision for the longitude. Defaults to `4`.
+ *
+ * @example
+ * faker.location.longitude() // -30.9501
+ * faker.location.longitude({ max: 10 }) // 5.7225
+ * faker.location.longitude({ max: 10, min: -10 }) // -9.6273
+ * faker.location.longitude({ max: 10, min: -10, precision: 5 }) // 2.68452
+ *
+ * @since 8.0.0
+ */
+ longitude(
+ options: {
+ /**
+ * The upper bound for the longitude to generate.
+ *
+ * @default 180
+ */
+ max?: number;
+ /**
+ * The lower bound for the longitude to generate.
+ *
+ * @default -180
+ */
+ min?: number;
+ /**
+ * The number of decimal points of precision for the longitude.
+ *
+ * @default 4
+ */
+ precision?: number;
+ } = {}
+ ): number {
+ const { max = 180, min = -180, precision = 4 } = options;
+
+ return this.faker.number.float({ max, min, fractionDigits: precision });
+ }
+
+ /**
+ * Generates a random GPS coordinate within the specified radius from the given coordinate.
+ *
+ * @param options The options for generating a GPS coordinate.
+ * @param options.origin The original coordinate to get a new coordinate close to.
+ * If no coordinate is given, a random one will be chosen.
+ * @param options.radius The maximum distance from the given coordinate to the new coordinate. Defaults to `10`.
+ * @param options.isMetric If `true` assume the radius to be in kilometers. If `false` for miles. Defaults to `false`.
+ *
+ * @example
+ * faker.location.nearbyGPSCoordinate() // [ 33.8475, -170.5953 ]
+ * faker.location.nearbyGPSCoordinate({ origin: [33, -170] }) // [ 33.0165, -170.0636 ]
+ * faker.location.nearbyGPSCoordinate({ origin: [33, -170], radius: 1000, isMetric: true }) // [ 37.9163, -179.2408 ]
+ *
+ * @since 8.0.0
+ */
+ nearbyGPSCoordinate(
+ options: {
+ /**
+ * The original coordinate to get a new coordinate close to.
+ */
+ origin?: [latitude: number, longitude: number];
+ /**
+ * The maximum distance from the given coordinate to the new coordinate.
+ *
+ * @default 10
+ */
+ radius?: number;
+ /**
+ * If `true` assume the radius to be in kilometers. If `false` for miles.
+ *
+ * @default false
+ */
+ isMetric?: boolean;
+ } = {}
+ ): [latitude: number, longitude: number] {
+ const { origin, radius = 10, isMetric = false } = options;
+
+ // If there is no origin, the best we can do is return a random GPS coordinate.
+ if (origin == null) {
+ return [this.latitude(), this.longitude()];
+ }
+
+ const angleRadians = this.faker.number.float({
+ max: 2 * Math.PI,
+ fractionDigits: 5,
+ }); // in ° radians
+
+ const radiusMetric = isMetric ? radius : radius * 1.60934; // in km
+ const errorCorrection = 0.995; // avoid float issues
+ const distanceInKm =
+ this.faker.number.float({
+ max: radiusMetric,
+ fractionDigits: 3,
+ }) * errorCorrection; // in km
+
+ /**
+ * The distance in km per degree for earth.
+ */
+ const kmPerDegree = 40_000 / 360; // in km/°
+
+ const distanceInDegree = distanceInKm / kmPerDegree; // in °
+
+ const coordinate: [latitude: number, longitude: number] = [
+ origin[0] + Math.sin(angleRadians) * distanceInDegree,
+ origin[1] + Math.cos(angleRadians) * distanceInDegree,
+ ];
+
+ // Box latitude [-90°, 90°]
+ coordinate[0] = coordinate[0] % 180;
+ if (coordinate[0] < -90 || coordinate[0] > 90) {
+ coordinate[0] = Math.sign(coordinate[0]) * 180 - coordinate[0];
+ coordinate[1] += 180;
+ }
+
+ // Box longitude [-180°, 180°]
+ coordinate[1] = (((coordinate[1] % 360) + 540) % 360) - 180;
+
+ return [coordinate[0], coordinate[1]];
+ }
+}
+
+/**
* Module to generate addresses and locations. Prior to Faker 8.0.0, this module was known as `faker.address`.
*
* ### Overview
@@ -32,7 +205,11 @@ export interface Language {
*
* For a random country, you can use [`country()`](https://fakerjs.dev/api/location.html#country) or [`countryCode()`](https://fakerjs.dev/api/location.html#countrycode).
*/
-export class LocationModule extends ModuleBase {
+export class LocationModule extends SimpleLocationModule {
+ constructor(protected readonly faker: Faker) {
+ super(faker);
+ }
+
/**
* Generates random zip code from specified format. If format is not specified,
* the locale's zip format is used.
@@ -351,92 +528,6 @@ export class LocationModule extends ModuleBase {
}
/**
- * Generates a random latitude.
- *
- * @param options An options object.
- * @param options.max The upper bound for the latitude to generate. Defaults to `90`.
- * @param options.min The lower bound for the latitude to generate. Defaults to `-90`.
- * @param options.precision The number of decimal points of precision for the latitude. Defaults to `4`.
- *
- * @example
- * faker.location.latitude() // -30.9501
- * faker.location.latitude({ max: 10 }) // 5.7225
- * faker.location.latitude({ max: 10, min: -10 }) // -9.6273
- * faker.location.latitude({ max: 10, min: -10, precision: 5 }) // 2.68452
- *
- * @since 8.0.0
- */
- latitude(
- options: {
- /**
- * The upper bound for the latitude to generate.
- *
- * @default 90
- */
- max?: number;
- /**
- * The lower bound for the latitude to generate.
- *
- * @default -90
- */
- min?: number;
- /**
- * The number of decimal points of precision for the latitude.
- *
- * @default 4
- */
- precision?: number;
- } = {}
- ): number {
- const { max = 90, min = -90, precision = 4 } = options;
-
- return this.faker.number.float({ min, max, fractionDigits: precision });
- }
-
- /**
- * Generates a random longitude.
- *
- * @param options An options object.
- * @param options.max The upper bound for the longitude to generate. Defaults to `180`.
- * @param options.min The lower bound for the longitude to generate. Defaults to `-180`.
- * @param options.precision The number of decimal points of precision for the longitude. Defaults to `4`.
- *
- * @example
- * faker.location.longitude() // -30.9501
- * faker.location.longitude({ max: 10 }) // 5.7225
- * faker.location.longitude({ max: 10, min: -10 }) // -9.6273
- * faker.location.longitude({ max: 10, min: -10, precision: 5 }) // 2.68452
- *
- * @since 8.0.0
- */
- longitude(
- options: {
- /**
- * The upper bound for the longitude to generate.
- *
- * @default 180
- */
- max?: number;
- /**
- * The lower bound for the longitude to generate.
- *
- * @default -180
- */
- min?: number;
- /**
- * The number of decimal points of precision for the longitude.
- *
- * @default 4
- */
- precision?: number;
- } = {}
- ): number {
- const { max = 180, min = -180, precision = 4 } = options;
-
- return this.faker.number.float({ max, min, fractionDigits: precision });
- }
-
- /**
* Returns a random direction (cardinal and ordinal; northwest, east, etc).
*
* @param options The options to use.
@@ -550,87 +641,6 @@ export class LocationModule extends ModuleBase {
}
/**
- * Generates a random GPS coordinate within the specified radius from the given coordinate.
- *
- * @param options The options for generating a GPS coordinate.
- * @param options.origin The original coordinate to get a new coordinate close to.
- * If no coordinate is given, a random one will be chosen.
- * @param options.radius The maximum distance from the given coordinate to the new coordinate. Defaults to `10`.
- * @param options.isMetric If `true` assume the radius to be in kilometers. If `false` for miles. Defaults to `false`.
- *
- * @example
- * faker.location.nearbyGPSCoordinate() // [ 33.8475, -170.5953 ]
- * faker.location.nearbyGPSCoordinate({ origin: [33, -170] }) // [ 33.0165, -170.0636 ]
- * faker.location.nearbyGPSCoordinate({ origin: [33, -170], radius: 1000, isMetric: true }) // [ 37.9163, -179.2408 ]
- *
- * @since 8.0.0
- */
- nearbyGPSCoordinate(
- options: {
- /**
- * The original coordinate to get a new coordinate close to.
- */
- origin?: [latitude: number, longitude: number];
- /**
- * The maximum distance from the given coordinate to the new coordinate.
- *
- * @default 10
- */
- radius?: number;
- /**
- * If `true` assume the radius to be in kilometers. If `false` for miles.
- *
- * @default false
- */
- isMetric?: boolean;
- } = {}
- ): [latitude: number, longitude: number] {
- const { origin, radius = 10, isMetric = false } = options;
-
- // If there is no origin, the best we can do is return a random GPS coordinate.
- if (origin == null) {
- return [this.latitude(), this.longitude()];
- }
-
- const angleRadians = this.faker.number.float({
- max: 2 * Math.PI,
- fractionDigits: 5,
- }); // in ° radians
-
- const radiusMetric = isMetric ? radius : radius * 1.60934; // in km
- const errorCorrection = 0.995; // avoid float issues
- const distanceInKm =
- this.faker.number.float({
- max: radiusMetric,
- fractionDigits: 3,
- }) * errorCorrection; // in km
-
- /**
- * The distance in km per degree for earth.
- */
- const kmPerDegree = 40_000 / 360; // in km/°
-
- const distanceInDegree = distanceInKm / kmPerDegree; // in °
-
- const coordinate: [latitude: number, longitude: number] = [
- origin[0] + Math.sin(angleRadians) * distanceInDegree,
- origin[1] + Math.cos(angleRadians) * distanceInDegree,
- ];
-
- // Box latitude [-90°, 90°]
- coordinate[0] = coordinate[0] % 180;
- if (coordinate[0] < -90 || coordinate[0] > 90) {
- coordinate[0] = Math.sign(coordinate[0]) * 180 - coordinate[0];
- coordinate[1] += 180;
- }
-
- // Box longitude [-180°, 180°]
- coordinate[1] = (((coordinate[1] % 360) + 540) % 360) - 180;
-
- return [coordinate[0], coordinate[1]];
- }
-
- /**
* Returns a random IANA time zone relevant to this locale.
*
* The returned time zone is tied to the current locale.
diff --git a/src/simple-faker.ts b/src/simple-faker.ts
index 35fbc866..7f86a389 100644
--- a/src/simple-faker.ts
+++ b/src/simple-faker.ts
@@ -2,6 +2,7 @@ import { randomSeed } from './internal/seed';
import { DatatypeModule } from './modules/datatype';
import { SimpleDateModule } from './modules/date';
import { SimpleHelpersModule } from './modules/helpers';
+import { SimpleLocationModule } from './modules/location';
import { NumberModule } from './modules/number';
import { StringModule } from './modules/string';
import type { Randomizer } from './randomizer';
@@ -14,6 +15,7 @@ import { generateMersenne53Randomizer } from './utils/mersenne';
* - `datatype`
* - `date` (without `month` and `weekday`)
* - `helpers` (without `fake`)
+ * - `location` (`latitude`, `longitude` and `nearbyGPSCoordinate` only)
* - `number`
* - `string`
*
@@ -85,6 +87,7 @@ export class SimpleFaker {
readonly datatype: DatatypeModule = new DatatypeModule(this);
readonly date: SimpleDateModule = new SimpleDateModule(this);
readonly helpers: SimpleHelpersModule = new SimpleHelpersModule(this);
+ readonly location: SimpleLocationModule = new SimpleLocationModule(this);
readonly number: NumberModule = new NumberModule(this);
readonly string: StringModule = new StringModule(this);
diff --git a/test/modules/location.spec.ts b/test/modules/location.spec.ts
index 45d789a4..7ca81672 100644
--- a/test/modules/location.spec.ts
+++ b/test/modules/location.spec.ts
@@ -8,6 +8,7 @@ import {
faker,
fakerEN_CA,
fakerEN_US,
+ simpleFaker,
} from '../../src';
import { seededTests } from '../support/seeded-runs';
import { times } from './../support/times';
@@ -247,22 +248,22 @@ describe('location', () => {
});
});
- describe('latitude()', () => {
+ describe.each([faker, simpleFaker])('latitude()', (fakerFn) => {
it('returns a number', () => {
- const latitude = faker.location.latitude();
+ const latitude = fakerFn.location.latitude();
expect(latitude).toBeTypeOf('number');
});
it('returns random latitude', () => {
- const latitude = faker.location.latitude();
+ const latitude = fakerFn.location.latitude();
expect(latitude).toBeGreaterThanOrEqual(-90.0);
expect(latitude).toBeLessThanOrEqual(90.0);
});
it('returns latitude with min and max and default precision', () => {
- const latitude = faker.location.latitude({ max: 5, min: -5 });
+ const latitude = fakerFn.location.latitude({ max: 5, min: -5 });
expect(
precision(latitude),
@@ -274,7 +275,7 @@ describe('location', () => {
});
it('returns random latitude with custom precision', () => {
- const latitude = faker.location.latitude({ precision: 7 });
+ const latitude = fakerFn.location.latitude({ precision: 7 });
expect(
precision(latitude),
@@ -286,22 +287,22 @@ describe('location', () => {
});
});
- describe('longitude()', () => {
+ describe.each([faker, simpleFaker])('longitude()', (fakerFn) => {
it('returns a number', () => {
- const longitude = faker.location.longitude();
+ const longitude = fakerFn.location.longitude();
expect(longitude).toBeTypeOf('number');
});
it('returns random longitude', () => {
- const longitude = faker.location.longitude();
+ const longitude = fakerFn.location.longitude();
expect(longitude).toBeGreaterThanOrEqual(-180);
expect(longitude).toBeLessThanOrEqual(180);
});
it('returns random longitude with min and max and default precision', () => {
- const longitude = faker.location.longitude({ max: 100, min: -30 });
+ const longitude = fakerFn.location.longitude({ max: 100, min: -30 });
expect(
precision(longitude),
@@ -313,7 +314,7 @@ describe('location', () => {
});
it('returns random longitude with custom precision', () => {
- const longitude = faker.location.longitude({ precision: 7 });
+ const longitude = fakerFn.location.longitude({ precision: 7 });
expect(
precision(longitude),
@@ -376,47 +377,50 @@ describe('location', () => {
});
});
- describe('nearbyGPSCoordinate()', () => {
- it.each(
- times(100).flatMap((radius) => [
- [{ isMetric: true, radius }],
- [{ isMetric: false, radius }],
- ])
- )(
- 'should return random gps coordinate within a distance of another one (%j)',
- ({ isMetric, radius }) => {
- const latitude1 = +faker.location.latitude();
- const longitude1 = +faker.location.longitude();
-
- const coordinate = faker.location.nearbyGPSCoordinate({
- origin: [latitude1, longitude1],
- radius,
- isMetric,
- });
-
- expect(coordinate).toHaveLength(2);
- expect(coordinate[0]).toBeTypeOf('number');
- expect(coordinate[1]).toBeTypeOf('number');
-
- const latitude2 = coordinate[0];
- expect(latitude2).toBeGreaterThanOrEqual(-90.0);
- expect(latitude2).toBeLessThanOrEqual(90.0);
-
- const longitude2 = coordinate[1];
- expect(longitude2).toBeGreaterThanOrEqual(-180.0);
- expect(longitude2).toBeLessThanOrEqual(180.0);
-
- const actualDistance = haversine(
- latitude1,
- longitude1,
- latitude2,
- longitude2,
- isMetric
- );
- expect(actualDistance).toBeLessThanOrEqual(radius);
- }
- );
- });
+ describe.each([faker, simpleFaker])(
+ 'nearbyGPSCoordinate()',
+ (fakerFn) => {
+ it.each(
+ times(100).flatMap((radius) => [
+ [{ isMetric: true, radius }],
+ [{ isMetric: false, radius }],
+ ])
+ )(
+ 'should return random gps coordinate within a distance of another one (%j)',
+ ({ isMetric, radius }) => {
+ const latitude1 = +fakerFn.location.latitude();
+ const longitude1 = +fakerFn.location.longitude();
+
+ const coordinate = fakerFn.location.nearbyGPSCoordinate({
+ origin: [latitude1, longitude1],
+ radius,
+ isMetric,
+ });
+
+ expect(coordinate).toHaveLength(2);
+ expect(coordinate[0]).toBeTypeOf('number');
+ expect(coordinate[1]).toBeTypeOf('number');
+
+ const latitude2 = coordinate[0];
+ expect(latitude2).toBeGreaterThanOrEqual(-90.0);
+ expect(latitude2).toBeLessThanOrEqual(90.0);
+
+ const longitude2 = coordinate[1];
+ expect(longitude2).toBeGreaterThanOrEqual(-180.0);
+ expect(longitude2).toBeLessThanOrEqual(180.0);
+
+ const actualDistance = haversine(
+ latitude1,
+ longitude1,
+ latitude2,
+ longitude2,
+ isMetric
+ );
+ expect(actualDistance).toBeLessThanOrEqual(radius);
+ }
+ );
+ }
+ );
describe('timeZone', () => {
it('should return a random timezone', () => {