From 2afd56b0f2dd2586069ed18cef85a036ec706149 Mon Sep 17 00:00:00 2001 From: bchavez Date: Sat, 31 Mar 2018 21:39:16 -0700 Subject: [PATCH] * New `Address.AreaCircle(lat,lon,rad)` to generate random latitude/longitude GPS points within a specified radius. * New `Address.Geohash()` generates a random Geohash. * New `Address.Depth()` generates a random depth (down to Mariana Trench). * New `Address.Altitude()` generate a random height (up to Mount Everest). * New `Internet.Color()` format options: CSS `rgb(...)` and delimited RGB. * New `System.AndroidId()` to generate GCM registration ID. * New `System.ApplePushToken()` to generate a random Apple Push Token. * New `System.BlackBerryPin()` to generate a random Black Berry PIN. * New `Randomizer.Hash()` to generate random hashes of specified length. * New `Randomizer.String2()` to generate random strings with specified character sets. --- HISTORY.md | 12 +++ LICENSE | 6 ++ .../Bogus.Tests/DataSetTests/AddressTest.cs | 68 +++++++++++++- .../Bogus.Tests/DataSetTests/InternetTests.cs | 12 +++ Source/Bogus.Tests/DataSetTests/SystemTest.cs | 18 ++++ Source/Bogus.Tests/RandomizerTest.cs | 12 +++ Source/Bogus/Chars.cs | 29 ++++++ Source/Bogus/DataSets/Address.cs | 92 ++++++++++++++++++- Source/Bogus/DataSets/ColorFormat.cs | 21 +++++ Source/Bogus/DataSets/Internet.cs | 29 +++++- Source/Bogus/DataSets/System.cs | 24 +++++ .../Bogus/Extensions/ExtensionsForDouble.cs | 26 ++++++ Source/Bogus/Models/LatLon.cs | 17 ++++ Source/Bogus/Randomizer.cs | 13 ++- 14 files changed, 371 insertions(+), 8 deletions(-) create mode 100644 Source/Bogus/Chars.cs create mode 100644 Source/Bogus/DataSets/ColorFormat.cs create mode 100644 Source/Bogus/Extensions/ExtensionsForDouble.cs create mode 100644 Source/Bogus/Models/LatLon.cs diff --git a/HISTORY.md b/HISTORY.md index dcc27106..438a57ff 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,15 @@ +## v22.0.7 +* New `Address.AreaCircle(lat,lon,rad)` to generate random latitude/longitude GPS points within a specified radius. +* New `Address.Geohash()` generates a random Geohash. +* New `Address.Depth()` generates a random depth (down to Mariana Trench). +* New `Address.Altitude()` generate a random height (up to Mount Everest). +* New `Internet.Color()` format options: CSS `rgb(...)` and delimited RGB. +* New `System.AndroidId()` to generate GCM registration ID. +* New `System.ApplePushToken()` to generate a random Apple Push Token. +* New `System.BlackBerryPin()` to generate a random Black Berry PIN. +* New `Randomizer.Hash()` to generate random hashes of specified length. +* New `Randomizer.String2()` to generate random strings with specified character sets. + ## v22.0.6 * Added `Randomizer.String` method to generate strings. Uses `Chars()` method. * PR 136: Improve speed of `DataSet.ParseTokens()`. Thanks @danij! diff --git a/LICENSE b/LICENSE index 5b9196e6..2bdd112f 100644 --- a/LICENSE +++ b/LICENSE @@ -21,6 +21,12 @@ https://github.com/zzzprojects/Z.ExtensionMethods/ Copyright (c) 2015 kernys https://github.com/kernys/Kernys.Bson +Copyright (c) 2015 Victor Quinn +https://github.com/chancejs/chancejs + +Copyright (c) 2014 Chris Veness +https://github.com/chrisveness/geodesy/ + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/Source/Bogus.Tests/DataSetTests/AddressTest.cs b/Source/Bogus.Tests/DataSetTests/AddressTest.cs index 37eb344f..e7cc6802 100644 --- a/Source/Bogus.Tests/DataSetTests/AddressTest.cs +++ b/Source/Bogus.Tests/DataSetTests/AddressTest.cs @@ -1,13 +1,20 @@ -using Bogus.DataSets; +using Bogus.DataSets; +using Bogus.Extensions; +using Bogus.Models; using FluentAssertions; using Xunit; +using Xunit.Abstractions; +using static System.Math; namespace Bogus.Tests.DataSetTests { public class AddressTest : SeededTest { - public AddressTest() + private readonly ITestOutputHelper console; + + public AddressTest(ITestOutputHelper console) { + this.console = console; address = new Address(); } @@ -155,5 +162,62 @@ public void can_generate_an_ordnial_direction() address.OrdinalDirection().Should().Be("Southeast"); address.OrdinalDirection(true).Should().Be("NE"); } + + [Fact] + public void can_generate_a_geohash() + { + address.Geohash().Should().Be("m3un1m1"); + address.Geohash(5).Should().Be("qg9y5"); + } + + [Fact] + public void can_generate_depth() + { + address.Depth().Should().BeLessThan(0) + .And.BeGreaterOrEqualTo(-10994); + } + + [Fact] + public void can_generate_height() + { + address.Altitude().Should().BeGreaterThan(0) + .And.BeLessOrEqualTo(8848); + } + + [Fact] + public void can_get_range() + { + var center = new LatLon // somewhere in the middle of Colorado. + { + Latitude = 39, //north + Longitude = -105, //west + }; + var newPoint = address.AreaCircle(center.Latitude, center.Longitude, 1000 * 1000); //radial search around 1000 km. + + var distance = GetDistance(center, newPoint); + console.Dump(distance); + distance.Should().BeLessOrEqualTo(1000 * 1000); + } + + double GetDistance(LatLon x, LatLon y) + { + // https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js + // a = sin²(Δφ/2) + cos(φ1)⋅cos(φ2)⋅sin²(Δλ/2) + // tanδ = √(a) / √(1−a) + + const int R = 6371000; // Earth's radius in meters + var φ1 = x.Latitude.ToRadians(); var λ1 = x.Longitude.ToRadians(); + var φ2 = y.Latitude.ToRadians(); var λ2 = y.Longitude.ToRadians(); + var Δφ = φ2 - φ1; + var Δλ = λ2 - λ1; + + var a = Sin(Δφ / 2) * Sin(Δφ / 2) + + Cos(φ1) * Cos(φ2) + * Sin(Δλ / 2) * Sin(Δλ / 2); + var c = 2 * Atan2(Sqrt(a), Sqrt(1 - a)); + var d = R * c; + + return d; + } } } \ No newline at end of file diff --git a/Source/Bogus.Tests/DataSetTests/InternetTests.cs b/Source/Bogus.Tests/DataSetTests/InternetTests.cs index 0b02db82..85abc213 100644 --- a/Source/Bogus.Tests/DataSetTests/InternetTests.cs +++ b/Source/Bogus.Tests/DataSetTests/InternetTests.cs @@ -106,6 +106,18 @@ public void can_get_html_color() internet.Color().Should().Be("#4d0e68"); } + [Fact] + public void can_get_color_in_format() + { + internet.Color(format:ColorFormat.Rgb).Should().Be("rgb(77,14,104)"); + } + + [Fact] + public void can_get_color_in_grayscale() + { + internet.Color(grayscale: true).Should().Be("#4d4d4d"); + } + [Fact] public void can_get_url_with_path() { diff --git a/Source/Bogus.Tests/DataSetTests/SystemTest.cs b/Source/Bogus.Tests/DataSetTests/SystemTest.cs index c00cd054..605bbe7c 100644 --- a/Source/Bogus.Tests/DataSetTests/SystemTest.cs +++ b/Source/Bogus.Tests/DataSetTests/SystemTest.cs @@ -133,5 +133,23 @@ public void can_get_file_path_unix() { system.FilePath().Should().Be("/sys/bluetooth.js"); } + + [Fact] + public void can_get_an_android_id() + { + system.AndroidId().Should().Be("APA91D6QF2E3IvkYaKB52JW1SSkDC5IZpfBzfk6IPaXZfFrXVNTuiA3r6cj6jweAnGGuVMKTEVjTNYPcrpKQeeIRa9s20_qkYoDA-Y1830SoibG9q6IVOqm8-RjLkISEw_XqmfeunBMcolz-wjEWkwyz1vC8GjQoaeTjhhQaUeycF8MGilg13Xk"); + } + + [Fact] + public void can_get_an_apple_push_token() + { + system.ApplePushToken().Should().Be("91da090b74f2b910be0dd5991af6398351ac2ef3a6eecd74806134147385aa7e"); + } + + [Fact] + public void can_get_a_black_berry_pin() + { + system.BlackBerryPin().Should().Be("91da090b"); + } } } \ No newline at end of file diff --git a/Source/Bogus.Tests/RandomizerTest.cs b/Source/Bogus.Tests/RandomizerTest.cs index 2939109a..3081a5be 100644 --- a/Source/Bogus.Tests/RandomizerTest.cs +++ b/Source/Bogus.Tests/RandomizerTest.cs @@ -316,6 +316,18 @@ public void generate_string2_pool_min_max() x.Should().Be("xzyxyxzy"); } + [Fact] + public void generate_hash() + { + r.Hash().Should().Be("91da090b74f2b910be0dd5991af6398351ac2ef3"); + } + + [Fact] + public void generate_small_hash() + { + r.Hash(20).Should().Be("91da090b74f2b910be0d"); + } + [Fact] public void random_word_tests() { diff --git a/Source/Bogus/Chars.cs b/Source/Bogus/Chars.cs new file mode 100644 index 00000000..051dfd88 --- /dev/null +++ b/Source/Bogus/Chars.cs @@ -0,0 +1,29 @@ +namespace Bogus +{ + /// + /// Static class for holding character string constants. + /// + public static class Chars + { + /// + /// Lower case, a-z. + /// + public const string LowerCase = "abcdefghijklmnopqrstuvwxyz"; + /// + /// Upper case, A-Z. + /// + public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + /// + /// Numbers, 0-9. + /// + public const string Numbers = "0123456789"; + /// + /// Hexadecimal, 0-9 and a-z. + /// + public const string HexLowerCase = Numbers + "abcdef"; + /// + /// Hexadecimal, 0-9 and A-Z. + /// + public const string HexUpperCase = Numbers + "ABCDEF"; + } +} \ No newline at end of file diff --git a/Source/Bogus/DataSets/Address.cs b/Source/Bogus/DataSets/Address.cs index 6434adcb..7af46ba7 100644 --- a/Source/Bogus/DataSets/Address.cs +++ b/Source/Bogus/DataSets/Address.cs @@ -1,4 +1,7 @@ -using System; +using System; +using Bogus.Extensions; +using Bogus.Models; +using static System.Math; namespace Bogus.DataSets { @@ -220,6 +223,93 @@ public string OrdinalDirection(bool useAbbreviation = false) return GetRandomArrayItem("direction_abbr", min: 4, max: 8); return GetRandomArrayItem("direction", min: 4, max: 8); } + + /// + /// Generates a random Geohash. [See](https://en.wikipedia.org/wiki/Geohash). + /// + /// By default, includes 7 characters of accuracy. + public string Geohash(int length = 7) + { + return this.Random.String2(length, "0123456789bcdefghjkmnpqrstuvwxyz"); + } + + /// + /// Generate a random depth, in meters. Default max depth is -10994m (Mariana Trench). Depths are always negative. + /// + /// By default, maximum depth is -10994 meters (depth of the Mariana Trench). + public double Depth(double min = -10994) + { + if( min >= 0 ) throw new ArgumentOutOfRangeException(nameof(min), "Depths must be negative."); + return this.Random.Double(min, 0); + } + + /// + /// Generate a random altitude, in meters. Default max height is 8848m (Mount Everest). Heights are always positive. + /// + /// + public double Altitude(double max = 8848) + { + if( max <= 0 ) throw new ArgumentOutOfRangeException(nameof(max), "Heights must be positive."); + return this.Random.Double(0, max); + } + + /// + /// Get a latitude and longitude within a specific radius in meters. + /// + /// The center latitude point + /// The center longitude point + /// Radial distance from center in meters + public LatLon AreaCircle(double centerLat, double centerLon, double radiusMeters) + { + // https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js + // https://www.movable-type.co.uk/scripts/latlong.html + //Formula: φ2 = asin(sin φ1 ⋅ cos δ + cos φ1 ⋅ sin δ ⋅ cos θ) + // λ2 = λ1 + atan2(sin θ ⋅ sin δ ⋅ cos φ1, cos δ − sin φ1 ⋅ sin φ2) + // + // where φ is latitude + // where λ is longitude + // where θ is the bearing (clockwise from north) + // where δ is the angular distance d/R; + // where d being the distance traveled + // where R the earth’s radius + // + // (all angles in radians) + + const double TwoPI = 2 * PI; + + const int R = 6371000; // Earth's radius in meters + + var φ1 = centerLat.ToRadians(); + var λ1 = centerLon.ToRadians(); + + // Get a distance shorter than radiusMeters. + var d = radiusMeters * this.Random.Double(); + + // Get a random bearing between [0, 2pi] radians (0-360°) + var brng = this.Random.Double(0, TwoPI); + + var φ2 = Asin(Sin(φ1) * Cos(d / R) + + Cos(φ1) * Sin(d / R) * Cos(brng)); + var λ2 = λ1 + Atan2( + Sin(brng) * Sin(d / R) * Cos(φ1), + Cos(d / R) - Sin(φ1) * Sin(φ2) + ); + + var destLat = φ2.ToDegrees(); + var destLon = ((λ2.ToDegrees() + 540) % 360) - 180; //and normalize to −180°...+180° + + return new LatLon { Latitude = destLat, Longitude = destLon}; + } + + /// + /// Get a latitude and longitude within a specific radius in meters. + /// + /// The center of the circle + /// Distance being traveled, in meters + public LatLon AreaCircle(LatLon center, double radiusMeters) + { + return AreaCircle(center.Latitude, center.Longitude, radiusMeters); + } } /// diff --git a/Source/Bogus/DataSets/ColorFormat.cs b/Source/Bogus/DataSets/ColorFormat.cs new file mode 100644 index 00000000..ad1bc800 --- /dev/null +++ b/Source/Bogus/DataSets/ColorFormat.cs @@ -0,0 +1,21 @@ +namespace Bogus.DataSets +{ + /// + /// Type of color format + /// + public enum ColorFormat + { + /// + /// Hexadecimal format: #4d0e68 + /// + Hex = 0x1, + /// + /// CSS format: rgb(77,14,104) + /// + Rgb, + /// + /// Delimited RGB: 77,14,104 + /// + Delimited + } +} \ No newline at end of file diff --git a/Source/Bogus/DataSets/Internet.cs b/Source/Bogus/DataSets/Internet.cs index a8d20379..f4061f18 100644 --- a/Source/Bogus/DataSets/Internet.cs +++ b/Source/Bogus/DataSets/Internet.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using Bogus.Vendor; @@ -213,13 +211,36 @@ public string Password(int length = 10, bool memorable = false, string regexPatt /// Red base color /// Green base color /// Blue base color - public string Color(byte baseRed = 0, byte baseGreen = 0, byte baseBlue = 0) + /// Output a gray scale color + /// The color format + public string Color(byte baseRed = 0, byte baseGreen = 0, byte baseBlue = 0, bool grayscale = false, ColorFormat format = ColorFormat.Hex) { var red = Math.Floor((Random.Number(256) + (double)baseRed) / 2); var green = Math.Floor((Random.Number(256) + (double)baseGreen) / 2); var blue = Math.Floor((Random.Number(256) + (double)baseBlue) / 2); - return string.Format("#{0:x02}{1:x02}{2:x02}", (byte)red, (byte)green, (byte)blue); + if( grayscale ) + { + green = red; + blue = red; + } + + if( format == ColorFormat.Hex ) + { + return string.Format("#{0:x02}{1:x02}{2:x02}", (byte)red, (byte)green, (byte)blue); + } + + if( format == ColorFormat.Delimited ) + { + return DelimitedRgb(); + } + + return $"rgb({DelimitedRgb()})"; + + string DelimitedRgb() + { + return $"{(byte)red},{(byte)green},{(byte)blue}"; + } } /// diff --git a/Source/Bogus/DataSets/System.cs b/Source/Bogus/DataSets/System.cs index dab43695..7fb07d64 100644 --- a/Source/Bogus/DataSets/System.cs +++ b/Source/Bogus/DataSets/System.cs @@ -315,5 +315,29 @@ public Exception Exception() return exe; } + + /// + /// Get a random GCM registration ID. + /// + public string AndroidId() + { + return $"APA91{this.Random.String2(178, "0123456789abcefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_")}"; + } + + /// + /// Get a random Apple Push Token + /// + public string ApplePushToken() + { + return this.Random.String2(64, Chars.HexLowerCase); + } + + /// + /// Get a random BlackBerry Device PIN + /// + public string BlackBerryPin() + { + return this.Random.Hash(8); + } } } \ No newline at end of file diff --git a/Source/Bogus/Extensions/ExtensionsForDouble.cs b/Source/Bogus/Extensions/ExtensionsForDouble.cs new file mode 100644 index 00000000..6d64d6ba --- /dev/null +++ b/Source/Bogus/Extensions/ExtensionsForDouble.cs @@ -0,0 +1,26 @@ +using static System.Math; + +namespace Bogus.Extensions +{ + /// + /// Extensions for . + /// + public static class ExtensionsForDouble + { + /// + /// Convert radians to numeric (signed) degrees. + /// + public static double ToDegrees(this double radians) + { + return radians * 180 / PI; + } + + /// + /// Convert numeric degrees to radians. + /// + public static double ToRadians(this double degrees) + { + return degrees * PI / 180; + } + } +} \ No newline at end of file diff --git a/Source/Bogus/Models/LatLon.cs b/Source/Bogus/Models/LatLon.cs new file mode 100644 index 00000000..7520145f --- /dev/null +++ b/Source/Bogus/Models/LatLon.cs @@ -0,0 +1,17 @@ +namespace Bogus.Models +{ + /// + /// Creates a LatLon point on the earth's surface at the specified latitude / longitude. + /// + public struct LatLon + { + /// + /// Latitude in degrees. The geographic coordinate that specifies the north–south position of a point on the Earth's surface. + /// + public double Latitude { get; set; } + /// + /// Longitude in degrees. The geographic coordinate that specifies the east-west position of a point on the Earth's surface. + /// + public double Longitude { get; set; } + } +} \ No newline at end of file diff --git a/Source/Bogus/Randomizer.cs b/Source/Bogus/Randomizer.cs index 1fe6ee62..249147c2 100644 --- a/Source/Bogus/Randomizer.cs +++ b/Source/Bogus/Randomizer.cs @@ -338,10 +338,21 @@ public string String2(int minLength, int maxLength, string chars = "abcdefghijkl return String2(length, chars); } + /// + /// Return a random hex hash. Default 40 characters, aka SHA-1. + /// + /// The length of the hash string. Default, 40 characters, aka SHA-1. + /// Returns the hex string with uppercase characters. + public string Hash(int length = 40, bool upperCase = false) + { + if( upperCase ) + return String2(length, Bogus.Chars.HexUpperCase); + return String2(length, Bogus.Chars.HexLowerCase); + } + /// /// Get a random boolean /// - /// public bool Bool() { return Number() == 0;