using CommandLine; using StackExchange.Redis; using System; using System.Collections.Generic; using System.Linq; using RedisManager.Utils; namespace RedisManager.Commands { /// /// Contains command line options and implementations for Redis geospatial operations. /// Provides functionality for GEOADD, GEORADIUS, GEODIST, GEOHASH, and GEOPOS commands. /// [Verb("geoadd", HelpText = "Add one or more geospatial items to a key.")] public class GeoAddOptions { [Option('i', "instance", Required = true, HelpText = "Instance name.")] public string Instance { get; set; } [Value(0, MetaName = "key", Required = true, HelpText = "Key name.")] public string Key { get; set; } [Value(1, MetaName = "members", Min = 3, Required = true, HelpText = "Members with longitude,latitude,name format.")] public IEnumerable Members { get; set; } } [Verb("georadius", HelpText = "Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point.")] public class GeoRadiusOptions { [Option('i', "instance", Required = true, HelpText = "Instance name.")] public string Instance { get; set; } [Value(0, MetaName = "key", Required = true, HelpText = "Key name.")] public string Key { get; set; } [Value(1, MetaName = "longitude", Required = true, HelpText = "Longitude.")] public double Longitude { get; set; } [Value(2, MetaName = "latitude", Required = true, HelpText = "Latitude.")] public double Latitude { get; set; } [Value(3, MetaName = "radius", Required = true, HelpText = "Radius.")] public double Radius { get; set; } [Option("unit", Default = "m", HelpText = "Unit (m, km, mi, ft).")] public string Unit { get; set; } [Option("withcoord", HelpText = "Include coordinates in output.")] public bool WithCoord { get; set; } [Option("withdist", HelpText = "Include distance in output.")] public bool WithDist { get; set; } [Option("withhash", HelpText = "Include hash in output.")] public bool WithHash { get; set; } [Option("count", HelpText = "Limit results count.")] public int Count { get; set; } [Option("asc", HelpText = "Sort ascending by distance.")] public bool Asc { get; set; } [Option("desc", HelpText = "Sort descending by distance.")] public bool Desc { get; set; } } [Verb("geodist", HelpText = "Return the distance between two members of a geospatial index.")] public class GeoDistOptions { [Option('i', "instance", Required = true, HelpText = "Instance name.")] public string Instance { get; set; } [Value(0, MetaName = "key", Required = true, HelpText = "Key name.")] public string Key { get; set; } [Value(1, MetaName = "member1", Required = true, HelpText = "First member.")] public string Member1 { get; set; } [Value(2, MetaName = "member2", Required = true, HelpText = "Second member.")] public string Member2 { get; set; } [Option("unit", Default = "m", HelpText = "Unit (m, km, mi, ft).")] public string Unit { get; set; } } [Verb("geohash", HelpText = "Returns members of a geospatial index as standard geohash strings.")] public class GeoHashOptions { [Option('i', "instance", Required = true, HelpText = "Instance name.")] public string Instance { get; set; } [Value(0, MetaName = "key", Required = true, HelpText = "Key name.")] public string Key { get; set; } [Value(1, MetaName = "members", Min = 1, Required = true, HelpText = "Members.")] public IEnumerable Members { get; set; } } [Verb("geopos", HelpText = "Returns longitude and latitude of members of a geospatial index.")] public class GeoPosOptions { [Option('i', "instance", Required = true, HelpText = "Instance name.")] public string Instance { get; set; } [Value(0, MetaName = "key", Required = true, HelpText = "Key name.")] public string Key { get; set; } [Value(1, MetaName = "members", Min = 1, Required = true, HelpText = "Members.")] public IEnumerable Members { get; set; } } public static class GeoCommands { /// /// Executes the GEOADD command to add one or more geospatial items to a key. /// /// The GeoAddOptions containing instance, key, and members. /// The RedisManager configuration. /// Exit code (0 for success, non-zero for failure). public static int RunGeoAdd(GeoAddOptions opts, Config config) { var instance = RedisUtils.GetInstance(config, opts.Instance); using var redis = RedisUtils.ConnectRedis(instance); var db = redis.GetDatabase(); var args = new List { opts.Key }; var membersList = opts.Members.ToList(); for (int i = 0; i + 2 < membersList.Count; i += 3) { args.Add(membersList[i]); // longitude args.Add(membersList[i + 1]); // latitude args.Add(membersList[i + 2]); // name } var result = db.Execute("GEOADD", args.ToArray()); Console.WriteLine(Output.Green($"Added: {result}")); return 0; } /// /// Executes the GEORADIUS command to query a geospatial index for members within a radius. /// /// The GeoRadiusOptions containing instance, key, longitude, latitude, radius, and options. /// The RedisManager configuration. /// Exit code (0 for success, non-zero for failure). public static int RunGeoRadius(GeoRadiusOptions opts, Config config) { var instance = RedisUtils.GetInstance(config, opts.Instance); using var redis = RedisUtils.ConnectRedis(instance); var db = redis.GetDatabase(); var args = new List { opts.Key, opts.Longitude, opts.Latitude, opts.Radius, opts.Unit }; if (opts.WithCoord) args.Add("WITHCOORD"); if (opts.WithDist) args.Add("WITHDIST"); if (opts.WithHash) args.Add("WITHHASH"); if (opts.Count > 0) { args.Add("COUNT"); args.Add(opts.Count); } if (opts.Asc) args.Add("ASC"); if (opts.Desc) args.Add("DESC"); var result = db.Execute("GEORADIUS", args.ToArray()); Console.WriteLine(Output.Green($"Raw result: {result}")); return 0; } /// /// Executes the GEODIST command to return the distance between two members of a geospatial index. /// /// The GeoDistOptions containing instance, key, member1, member2, and unit. /// The RedisManager configuration. /// Exit code (0 for success, non-zero for failure). public static int RunGeoDist(GeoDistOptions opts, Config config) { var instance = RedisUtils.GetInstance(config, opts.Instance); using var redis = RedisUtils.ConnectRedis(instance); var db = redis.GetDatabase(); var result = db.Execute("GEODIST", new object[] { opts.Key, opts.Member1, opts.Member2, opts.Unit }); Console.WriteLine(Output.Green($"Distance: {result} {opts.Unit}")); return 0; } /// /// Executes the GEOHASH command to return geohash strings for members of a geospatial index. /// /// The GeoHashOptions containing instance, key, and members. /// The RedisManager configuration. /// Exit code (0 for success, non-zero for failure). public static int RunGeoHash(GeoHashOptions opts, Config config) { var instance = RedisUtils.GetInstance(config, opts.Instance); using var redis = RedisUtils.ConnectRedis(instance); var db = redis.GetDatabase(); var args = new List { opts.Key }; args.AddRange(opts.Members); var result = db.Execute("GEOHASH", args.ToArray()); RedisResult[] hashes; if (result is Array) hashes = (RedisResult[])result; else hashes = new RedisResult[] { result }; var membersList = opts.Members.ToList(); for (int i = 0; i < membersList.Count; i++) { var hash = hashes != null && i < hashes.Length ? hashes[i]?.ToString() : null; if (!string.IsNullOrEmpty(hash)) { Console.WriteLine(Output.Green($"{membersList[i]}: {hash}")); } else { Console.WriteLine(Output.Red($"{membersList[i]}: not found")); } } return 0; } /// /// Executes the GEOPOS command to return longitude and latitude of members of a geospatial index. /// /// The GeoPosOptions containing instance, key, and members. /// The RedisManager configuration. /// Exit code (0 for success, non-zero for failure). public static int RunGeoPos(GeoPosOptions opts, Config config) { var instance = RedisUtils.GetInstance(config, opts.Instance); using var redis = RedisUtils.ConnectRedis(instance); var db = redis.GetDatabase(); var args = new List { opts.Key }; args.AddRange(opts.Members); var result = db.Execute("GEOPOS", args.ToArray()); RedisResult[] positions; if (result is Array) positions = (RedisResult[])result; else positions = new RedisResult[] { result }; var membersList = opts.Members.ToList(); for (int i = 0; i < membersList.Count; i++) { var pos = positions != null && i < positions.Length ? positions[i]?.ToString() : null; if (!string.IsNullOrEmpty(pos)) { Console.WriteLine(Output.Green($"{membersList[i]}: {pos}")); } else { Console.WriteLine(Output.Red($"{membersList[i]}: not found")); } } return 0; } private static GeoUnit GetGeoUnit(string unit) { return unit.ToLower() switch { "km" => GeoUnit.Kilometers, "mi" => GeoUnit.Miles, "ft" => GeoUnit.Feet, _ => GeoUnit.Meters }; } } }