Skip to content

Commit 4c73ff6

Browse files
committed
fix: make it uniformly distributed; fix tests; add to README
1 parent 63d0464 commit 4c73ff6

File tree

3 files changed

+67
-21
lines changed

3 files changed

+67
-21
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ Where:
7474
- **`radius`** *required* The distance (meters) from `centerPoint`.
7575
- **`randomFn`** *optional* A random function. Output is >=0 and <=1. Allows usage of seeded random number generators (see [`seedrandom`](https://www.npmjs.com/package/seedrandom)) - more predictability when testing.
7676

77+
### `randomLocation.randomAnnulusPoint(...)`
78+
79+
Outputs a Point ( `{ latitude: ..., longitude: ... }`) of random coordinates in a region bounded by two concentric circles (annulus).
80+
81+
Function definition:
82+
83+
```js
84+
const randomCircumferencePoint= (centerPoint, radius, randomFn = Math.random) => { ... }
85+
```
86+
87+
Where:
88+
89+
- **`centerPoint`** *required* An object with a `latitude` and `longitude` fields.
90+
- **`innerRadius`** *required* The readius of the smaller circle.
91+
- **`outerRadius`** *required* The readius of the larger circle.
92+
- **`randomFn`** *optional* A random function. Output is >=0 and <=1. Allows usage of seeded random number generators (see [`seedrandom`](https://www.npmjs.com/package/seedrandom)) - more predictability when testing.
93+
94+
7795

7896
## Usage
7997

src/index.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,33 @@ const randomCirclePoint = (centerPoint, radius, randomFn = Math.random) => {
7070
};
7171

7272
/**
73+
* Returns a random point in a region bounded by two concentric circles (annulus).
74+
*
75+
* centerPoint - the center point of both circles.
76+
* innerRadius - the radius of the smaller circle.
77+
* outerRadius - the radius of the larger circle
78+
* randomFn - *optional* A random function. Output is >=0 and <=1. Allows
79+
* usage of seeded random number generators - i.e. allows us to predict
80+
* generated random coordiantes. default is Math.random()
7381
*
74-
* Given a center point C and a smaller radius and bigger radius, return a point that is
75-
* between the two circles
7682
*/
7783
const randomAnnulusPoint = (
7884
centerPoint,
7985
innerRadius,
8086
outerRadius,
8187
randomFn = Math.random
8288
) => {
83-
if (innerRadius > outerRadius) {
84-
throw new Error('innerRadus should be smaller');
89+
if (innerRadius >= outerRadius) {
90+
throw new Error(
91+
`innerRadius (${innerRadius}) should be smaller than outerRadius (${outerRadius})`
92+
);
8593
}
8694

95+
const deltaRadius = outerRadius - innerRadius;
96+
8797
return randomCircumferencePoint(
8898
centerPoint,
89-
Math.floor(Math.random() * (outerRadius - innerRadius + 1) + innerRadius),
99+
innerRadius + Math.sqrt(randomFn()) * deltaRadius,
90100
randomFn
91101
);
92102
};

src/index.test.js

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -109,45 +109,63 @@ describe('random-location', () => {
109109
});
110110
});
111111

112-
describe('random generation of points between circles', () => {
113-
test('between circles given center point and inner and outer radius', () => {
112+
describe('random generation of random annulus points', () => {
113+
test('innerRadius is larger than outerRadius', () => {
114114
// Eiffel Tower
115115
const P1 = {
116116
latitude: 48.8583736,
117117
longitude: 2.2922926,
118118
};
119-
const R = 1000; // meters
120-
const R2 = 2000;
119+
const innerRadius = 1000; // meters
120+
const outerRadius = 123;
121+
expect(() =>
122+
randomLocation.randomAnnulusPoint(P1, innerRadius, outerRadius)
123+
).toThrow(
124+
`innerRadius (${innerRadius}) should be smaller than outerRadius (${outerRadius})`
125+
);
126+
});
127+
128+
test('on an annulus of two concentric circles', () => {
129+
// Eiffel Tower
130+
const P1 = {
131+
latitude: 48.8583736,
132+
longitude: 2.2922926,
133+
};
134+
const innerRadius = 1000; // meters
135+
const outerRadius = 2000;
121136

122137
for (let i = 0; i < 100; i++) {
123-
const randomPoint = randomLocation.randomAnnulusPoint(P1, R, R2);
138+
const randomPoint = randomLocation.randomAnnulusPoint(
139+
P1,
140+
innerRadius,
141+
outerRadius
142+
);
124143
const distance = Math.floor(randomLocation.distance(P1, randomPoint));
125-
expect(distance).toBeLessThanOrEqual(R2 + 1);
126-
expect(distance).toBeGreaterThanOrEqual(R);
144+
expect(distance).toBeLessThanOrEqual(outerRadius);
145+
expect(distance).toBeGreaterThanOrEqual(innerRadius);
127146
}
128147
});
129-
130-
test('between inner and outer circle given seed random', () => {
148+
test('on an annulus of two concentric circles; using seedrandom', () => {
131149
// Eiffel Tower
132150
const P1 = {
133151
latitude: 48.8583736,
134152
longitude: 2.2922926,
135153
};
136-
const R = 1234; // meters
137-
const R2 = 2300;
154+
const innerRadius = 1000; // meters
155+
const outerRadius = 2000;
138156
const seed = 'this is another seed';
157+
const randomFn = seedrandom(seed);
139158

140159
for (let i = 0; i < 100; i++) {
141-
const randomFn = seedrandom(seed);
142160
const randomPoint = randomLocation.randomAnnulusPoint(
143161
P1,
144-
R,
145-
R2,
162+
innerRadius,
163+
outerRadius,
146164
randomFn
147165
);
148166
const distance = Math.floor(randomLocation.distance(P1, randomPoint));
149-
expect(distance).toBeLessThanOrEqual(R2 + 1);
150-
expect(distance).toBeGreaterThanOrEqual(R);
167+
expect(distance).toBeLessThanOrEqual(outerRadius);
168+
expect(distance).toBeGreaterThanOrEqual(innerRadius);
151169
}
152170
});
153171
});

0 commit comments

Comments
 (0)