Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Location inside specific boundaries #337

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<!-- J -->
<!-- K -->
<!-- L -->
- [Faker Location](lib/fake/location.ex)
- [Faker.Lorem](lib/faker/lorem.ex)
- [Faker.Lorem.Shakespeare](lib/faker/lorem/shakespeare.ex)
- [Faker.Lorem.Shakespeare.En](lib/faker/lorem/shakespeare/en.ex)
Expand Down
58 changes: 58 additions & 0 deletions lib/faker/location.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule Faker.Location do
@moduledoc """
Generating random locations in given area
"""
@type location :: {float(), float()}

@earth_radius 6_378_137
@base_location {51.4, 0.0}

@doc """
Returns a random location inside a square of 2x distance of base location
in distance from base location using the haversine formula
https://en.wikipedia.org/wiki/Haversine_formula/

## Examples
iex(1)> Faker.Location.location(111000)
{50.710821532265896, -1.0547258975429654}
iex(2)> Faker.Location.location(111000)
{51.570031504217205, -1.2954042535826502}
iex(3)> Faker.Location.location(111000)
{51.237239966568545, 0.6234308093351572}
iex(4)> Faker.Location.location(111000)
{50.932080421149315, -1.5390276090887278}
"""
@spec location(integer(), location()) :: location()
def location(distance, base_location \\ @base_location) do
offset_in_metres(base_location, distance)
end

defp offset_in_metres({lat, lon}, distance) do
rand_lat =
Faker.random_uniform()
|> shift_distance(distance)
|> offset_in_rad_lat()
|> rad_to_deg()
|> Kernel.+(lat)

rand_lon =
Faker.random_uniform()
|> shift_distance(distance)
|> offset_in_rad_lon(lat)
|> rad_to_deg()
|> Kernel.+(lon)

{rand_lat, rand_lon}
end

defp shift_distance(val, distance), do: distance * (val - 0.5) * 2

defp offset_in_rad_lat(meters), do: meters / @earth_radius

defp offset_in_rad_lon(meters, lat), do: meters / earth_slice_radius_on_lng(lat)

defp earth_slice_radius_on_lng(lat),
do: @earth_radius * :math.cos(:math.pi() * (lat / 180))

defp rad_to_deg(rad), do: rad * (180 / :math.pi())
end
15 changes: 15 additions & 0 deletions test/faker/location_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Faker.LocationTest do
use ExUnit.Case, async: true

doctest Faker.Location

test "location/2" do
{longitude, latitude} = Faker.Location.location(110_000, {0, 0})
assert is_float(longitude)
assert is_float(latitude)
assert latitude < 1
assert latitude > -1
assert longitude < 1
assert longitude > -1
end
end