/* Copyright (c) 2007 Ben Howell * This software is licensed under the MIT License * * 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 to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ using System; using System.Text.RegularExpressions; namespace GoArrow { public struct Coordinates { public static readonly Coordinates NO_COORDINATES = new Coordinates(double.NaN, double.NaN); public const string NO_COORDINATES_STRING = ""; public const string UNKNOWN_COORDINATES_STRING = ""; // This is the only data stored by each instance of the struct public double NS, EW; public Coordinates(double NS, double EW) { this.NS = NS; this.EW = EW; } public Coordinates(int landcell, double yOffset, double xOffset) { this.NS = LandblockToNS(landcell, yOffset); this.EW = LandblockToEW(landcell, xOffset); } public Coordinates(int landcell, double yOffset, double xOffset, int precision) { this.NS = Math.Round(LandblockToNS(landcell, yOffset), precision); this.EW = Math.Round(LandblockToEW(landcell, xOffset), precision); } public Coordinates(Decal.Adapter.Wrappers.CoordsObject obj) { this.NS = obj.NorthSouth; this.EW = obj.EastWest; } public Coordinates(Decal.Adapter.Wrappers.CoordsObject obj, int precision) { this.NS = Math.Round(obj.NorthSouth, precision); this.EW = Math.Round(obj.EastWest, precision); } public static explicit operator Decal.Adapter.Wrappers.CoordsObject(Coordinates coords) { return new Decal.Adapter.Wrappers.CoordsObject(coords.NS, coords.EW); } public double AngleTo(Coordinates dest) { return Math.Atan2(dest.EW - EW, dest.NS - NS); } public double DistanceTo(Coordinates dest) { double x = dest.EW - EW; double y = dest.NS - NS; if (double.IsNaN(x + y)) return double.PositiveInfinity; return Math.Sqrt(x * x + y * y); } public Coordinates RelativeTo(Coordinates dest) { return new Coordinates(dest.NS - NS, dest.EW - EW); } public static Coordinates Round(Coordinates coords, int precision) { return new Coordinates(Math.Round(coords.NS, precision), Math.Round(coords.EW, precision)); } // Latitude public static double LandblockToNS(int landcell, double yOffset) { uint l = (uint)((landcell & 0x00FF0000) / 0x2000); return (l + yOffset / 24.0 - 1019.5) / 10.0; } // Longitude public static double LandblockToEW(int landcell, double xOffset) { uint l = (uint)((landcell & 0xFF000000) / 0x200000); return (l + xOffset / 24.0 - 1019.5) / 10.0; } #region String Parsing public static bool TryParse(string parseString, out Coordinates coords) { if (parseString == null) { coords = Coordinates.NO_COORDINATES; return false; } return FromRegexMatch(FindCoords(parseString), out coords); } public static bool TryParse(string parseString, bool allowNoCoords, out Coordinates coords) { if (allowNoCoords && parseString == "" || parseString == NO_COORDINATES_STRING || parseString == UNKNOWN_COORDINATES_STRING) { coords = NO_COORDINATES; return true; } return FromRegexMatch(FindCoords(parseString), out coords); } public static bool FromRegexMatch(Match m, out Coordinates coords) { coords = NO_COORDINATES; try { if (!m.Success) return false; coords.NS = double.Parse(m.Groups["NSval"].Value, System.Globalization.CultureInfo.InvariantCulture.NumberFormat); if (m.Groups["NSchr"].Value.ToLower() == "s") { coords.NS = -coords.NS; } coords.EW = double.Parse(m.Groups["EWval"].Value); if (m.Groups["EWchr"].Value.ToLower() == "w") { coords.EW = -coords.EW; } } catch (Exception ex) { Util.HandleException(ex); return false; } return true; } // regExDouble matches a 0-3 digit decimal number with // optionally 1-4 digits after the decimal place (ex: "139", "32.0132", ".16") private const string regExDouble = @"(\d{1,3}(\.\d{1,4})?)|(\.\d{1,4})"; // Create a regular expression that's compiled at startup, // rather than each time it's needed private static Regex CoordSearchRegex = new Regex( @"(?" + regExDouble + @")\s*(?[ns])" + @"[;/,\s]{0,4}\s*" + @"(?" + regExDouble + @")\s*(?[ew])", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// /// Searches through the string for coordinates. /// /// The string to search through /// A regular expression MatchCollection containing 0 or more matches. /// Each match will have four named groups: /// "NSval" = The double value of the N/S part of the coordinate. /// "NSchr" = Either "n" or "s" (may be upper case). /// "EWval" = The double value of the E/W part of the coordinate. /// "EWchr" = Either "e" or "w" (may be upper case). public static MatchCollection FindAllCoords(string parseString) { return CoordSearchRegex.Matches(parseString); } public static Match FindCoords(string parseString) { return CoordSearchRegex.Match(parseString); } #if false internal static WorldObject player = null; public Coordinates(string parseString) { try { Match m = FindCoords(parseString); if (m.Success) { NS = double.Parse(m.Groups["NSval"].Value); if (m.Groups["NSchr"].Value.ToLower() == "s") { NS = -NS; } EW = double.Parse(m.Groups["EWval"].Value); if (m.Groups["EWchr"].Value.ToLower() == "w") { EW = -EW; } } else { /* * No regex matches. Parse using old, less-reliable parsing * code that doesn't require N/S, E/W specified. */ // Get the player's current coordniates double playerNS = 0, playerEW = 0; if (player != null) player.Coordinates(ref playerNS, ref playerEW); int i = 0; parseString = parseString.ToLower(); NS = OldParseHelper(ref parseString, ref i, 'n', 's', playerNS < 0); EW = OldParseHelper(ref parseString, ref i, 'e', 'w', playerEW < 0); } } catch (Exception ex) { NS = EW = 0; Util.HandleException(ex); } } public Coordinates(Match coordsSearchMatch) { if (coordsSearchMatch.Success) { NS = double.Parse(coordsSearchMatch.Groups["NSval"].Value); if (coordsSearchMatch.Groups["NSchr"].Value.ToLower() == "s") { NS = -NS; } EW = double.Parse(coordsSearchMatch.Groups["EWval"].Value); if (coordsSearchMatch.Groups["EWchr"].Value.ToLower() == "w") { EW = -EW; } } else { NS = 0; EW = 0; } } private static double OldParseHelper(ref string s, ref int i, char chNE, char chSW, bool playerIsSW) { double retVal = 0; for (; i < s.Length; i++) { // Check if the first char of the string is a number // (or second if first is a decimal point) int idx = (s[i] == '.' && s.Length > 1) ? i + 1 : i; if (s[idx] >= '0' && s[idx] <= '9') { // Find the length of the current number int len = s.Length - i; bool decimalPointFound = false; for (int j = i; j < s.Length; j++) { if (s[j] == '.' && !decimalPointFound) decimalPointFound = true; else if (!(s[j] >= '0' && s[j] <= '9')) { len = j - i; break; } } // Parse the number part retVal = double.Parse(s.Substring(i, len)); // Advance i past the number i = i + len; // Flip sign if south or west if (i >= s.Length || (s[i] != chNE && s[i] != chSW)) { if (playerIsSW) retVal = -retVal; } else if (s[i] == chSW) retVal = -retVal; break; } } return retVal; } #endif #endregion public override string ToString() { return ToString(false); } public string ToString(bool useUnknownString) { if (double.IsNaN(NS) || double.IsNaN(EW)) return useUnknownString ? UNKNOWN_COORDINATES_STRING : NO_COORDINATES_STRING; // If one of the numbers has more than 1 decimal place of precision, // write both with 2 decimal places. Otherwise, just use 1. double ns10 = NS * 10, ew10 = EW * 10; if (Math.Floor(ns10) != ns10 || Math.Floor(ew10) != ew10) return ToString("0.00", useUnknownString); else return ToString("0.0", useUnknownString); } public string ToString(string numberFormat) { return ToString(numberFormat, false); } public string ToString(string numberFormat, bool useUnknownString) { if (double.IsNaN(NS) || double.IsNaN(EW)) return useUnknownString ? UNKNOWN_COORDINATES_STRING : NO_COORDINATES_STRING; return Math.Abs(NS).ToString(numberFormat) + (NS >= 0 ? "N" : "S") + ", " + Math.Abs(EW).ToString(numberFormat) + (EW >= 0 ? "E" : "W"); } public static bool operator ==(Coordinates a, Coordinates b) { return (a.NS == b.NS && a.EW == b.EW) || (double.IsNaN(a.NS) && double.IsNaN(b.NS)); } public static bool operator !=(Coordinates a, Coordinates b) { return !(a == b); } public override bool Equals(object obj) { if (obj is Coordinates) { Coordinates c = (Coordinates)obj; return (this == c); } return false; } public override int GetHashCode() { if (double.IsNaN(NS) || double.IsNaN(EW)) return int.MaxValue; return (int)(NS * 1000000) ^ (int)(EW * 10000); } } }