001package jmri.util; 002 003/* 004 * <hr> 005 * This file is part of JMRI. 006 * <p> 007 * JMRI is free software; you can redistribute it and/or modify it under 008 * the terms of version 2 of the GNU General Public License as published 009 * by the Free Software Foundation. See the "COPYING" file for a copy 010 * of this license. 011 * <p> 012 * JMRI is distributed in the hope that it will be useful, but WITHOUT 013 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 015 * for more details. 016 * 017 * @author Mark Underwood Copyright (C) 2011 018 */ 019import java.util.regex.Matcher; 020import java.util.regex.Pattern; 021import java.util.regex.PatternSyntaxException; 022import javax.vecmath.Vector3d; 023import javax.vecmath.Vector3f; 024import jmri.NamedBean; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028/** 029 * PhysicalLocation 030 * 031 * Represents a physical location on the layout in 3D space. 032 * 033 * Dimension units are not specified, but should be kept consistent in all three 034 * dimensions for a given usage. 035 * 036 * Used by VSDecoder for spatially positioning sounds on the layout. 037 * 038 * Could also be used, for example, for velocity calculations between sensors, 039 * or for keying operations locations or panel icons to a physical map view of 040 * the layout. 041 * 042 */ 043public class PhysicalLocation extends Vector3f { 044 045 private boolean _isTunnel; 046 047 // Class methods 048 /** 049 * Origin : constant representation of (0, 0, 0) 050 */ 051 public static final PhysicalLocation Origin = new PhysicalLocation(0.0f, 0.0f, 0.0f); 052 053 /** 054 * NBPropertyKey : Key name used when storing a PhysicalLocation as a 055 * NamedBean Property 056 */ 057 public static final String NBPropertyKey = "physical_location"; 058 059 /** 060 * translate() 061 * 062 * Return a PhysicalLocation that represents the position of point "loc" 063 * relative to reference point "ref". 064 * 065 * @param loc : PhysicalLocation to translate 066 * @param ref : PhysicalLocation to use as new reference point (origin) 067 * @return PhysicalLocation 068 */ 069 public static PhysicalLocation translate(PhysicalLocation loc, PhysicalLocation ref) { 070 if (loc == null || ref == null) { 071 return (loc); 072 } 073 PhysicalLocation rv = new PhysicalLocation(); 074 rv.setX(loc.getX() - ref.getX()); 075 rv.setY(loc.getY() - ref.getY()); 076 rv.setZ(loc.getZ() - ref.getZ()); 077 return (rv); 078 } 079 080 /** 081 * getBeanPhysicalLocation(NamedBean b) 082 * 083 * Extract the PhysicalLocation stored in NamedBean b, and return as a new 084 * PhysicalLocation object. 085 * 086 * If the given NamedBean does not have a PhysicalLocation property returns 087 * Origin. (should probably return null instead, but...) 088 * 089 * @param b : NamedBean 090 * @return PhysicalLocation 091 */ 092 public static PhysicalLocation getBeanPhysicalLocation(NamedBean b) { 093 String s = (String) b.getProperty(PhysicalLocation.NBPropertyKey); 094 if ((s == null) || (s.isEmpty())) { 095 return (PhysicalLocation.Origin); 096 } else { 097 return (PhysicalLocation.parse(s)); 098 } 099 } 100 101 /** 102 * setBeanPhysicalLocation(PhysicalLocation p, NamedBean b) 103 * 104 * Store PhysicalLocation p as a property in NamedBean b. 105 * 106 * @param p PhysicalLocation 107 * @param b NamedBean 108 */ 109 public static void setBeanPhysicalLocation(PhysicalLocation p, NamedBean b) { 110 b.setProperty(PhysicalLocation.NBPropertyKey, p.toString()); 111 } 112 113 /** 114 * Get a panel component that can be used to view and/or edit a location. 115 * 116 * @param title the title of the component 117 * @return a new component 118 */ 119 static public PhysicalLocationPanel getPanel(String title) { 120 return (new PhysicalLocationPanel(title)); 121 } 122 123 /** 124 * Parse a string representation (x,y,z) Returns a new PhysicalLocation 125 * object. 126 * 127 * @param pos : String "(X, Y, Z)" 128 * @return PhysicalLocation 129 */ 130 static public PhysicalLocation parse(String pos) { 131 // position is stored as a tuple string "(x,y,z)" 132 // Optional flags come immediately after the (x,y,z) in the form of "(flag)". 133 // Flags are boolean. If they are present, they are true. 134 // Regex [-+]?[0-9]*\.?[0-9]+ 135 // String syntax = "\\((\\s*[-+]?[0-9]*\\.?[0-9]+),(\\s*[-+]?[0-9]*\\.?[0-9]+),(\\s*[-+]?[0-9]*\\.?[0-9]+)\\)"; 136 // String syntax = "\\((\\s*[-+]?[0-9]*\\.?[0-9]+),(\\s*[-+]?[0-9]*\\.?[0-9]+),(\\s*[-+]?[0-9]*\\.?[0-9]+)\\)(\\([tunnel]\\))*"; 137 String syntax = "\\((\\s*[-+]?[0-9]*\\.?[0-9]+), (\\s*[-+]?[0-9]*\\.?[0-9]+), (\\s*[-+]?[0-9]*\\.?[0-9]+)\\)\\(?([tunnel]*)\\)?"; 138 try { 139 Pattern p = Pattern.compile(syntax); 140 Matcher m = p.matcher(pos); 141 if (!m.matches()) { 142 log.error("String does not match a valid position pattern. syntax= {} string = {}", syntax, pos); 143 return (null); 144 } 145 // ++debug 146 String xs = m.group(1); 147 String ys = m.group(2); 148 String zs = m.group(3); 149 log.debug("Loading position: x = {} y = {} z = {}", xs, ys, zs); 150 // --debug 151 boolean is_tunnel = false; 152 // Handle optional flags 153 for (int i = 4; i < m.groupCount() + 1; i++) { 154 if ((m.group(i) != null) && ("tunnel".equals(m.group(i)))) { 155 is_tunnel = true; 156 } 157 } 158 159 return (new PhysicalLocation(Float.parseFloat(xs), 160 Float.parseFloat(ys), 161 Float.parseFloat(zs), 162 is_tunnel)); 163 } catch (PatternSyntaxException e) { 164 log.error("Malformed listener position syntax! {}", syntax); 165 return (null); 166 } catch (IllegalStateException e) { 167 log.error("Group called before match operation executed syntax={} string= {} {}", syntax, pos, e.toString()); 168 return (null); 169 } catch (IndexOutOfBoundsException e) { 170 log.error("Index out of bounds {} string= {} {}", syntax, pos, e.toString()); 171 return (null); 172 } 173 } 174 175 /** 176 * toString() 177 * 178 * Output a string representation (x,y,z) 179 * 180 * @return String "(X, Y, Z)" 181 */ 182 @Override 183 public String toString() { 184 String s = "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")"; 185 if (_isTunnel) { 186 s += "(tunnel)"; 187 } 188 return (s); 189 } 190 191 public Vector3d toVector3d() { 192 return (new Vector3d(this)); 193 } 194 195 // Instance methods 196 /** 197 * Default constructor 198 */ 199 public PhysicalLocation() { 200 super(); 201 _isTunnel = false; 202 } 203 204 /** 205 * Constructor from Vector3f. 206 * 207 * @param v the vector 208 */ 209 public PhysicalLocation(Vector3f v) { 210 super(v); 211 _isTunnel = false; 212 } 213 214 public PhysicalLocation(Vector3d v) { 215 super(v); 216 _isTunnel = false; 217 } 218 219 /** 220 * Constructor from X, Y, Z (float) + is_tunnel (boolean) 221 * 222 * @param x location on X axis 223 * @param y location on Y axis 224 * @param z location on Z axis 225 * @param isTunnel true is location is in a tunnel 226 */ 227 public PhysicalLocation(float x, float y, float z, boolean isTunnel) { 228 super(x, y, z); 229 _isTunnel = isTunnel; 230 231 } 232 233 /** 234 * Constructor from X, Y, Z (float) 235 * 236 * @param x location on X axis 237 * @param y location on Y axis 238 * @param z location on Z axis 239 */ 240 public PhysicalLocation(float x, float y, float z) { 241 this(x, y, z, false); 242 } 243 244 /** 245 * Constructor from X, Y, Z (double) 246 * 247 * @param x location on X axis 248 * @param y location on Y axis 249 * @param z location on Z axis 250 */ 251 public PhysicalLocation(double x, double y, double z) { 252 this(x, y, z, false); 253 } 254 255 /** 256 * Constructor from X, Y, Z (double) 257 * 258 * @param x location on X axis 259 * @param y location on Y axis 260 * @param z location on Z axis 261 * @param isTunnel true if location is in a tunnel 262 */ 263 public PhysicalLocation(double x, double y, double z, boolean isTunnel) { 264 this((float) x, (float) y, (float) z, isTunnel); 265 } 266 267 /** 268 * Copy Constructor 269 * 270 * @param p the location to copy 271 */ 272 public PhysicalLocation(PhysicalLocation p) { 273 this(p.getX(), p.getY(), p.getZ(), p.isTunnel()); 274 } 275 276 public boolean isTunnel() { 277 return (_isTunnel); 278 } 279 280 public void setIsTunnel(boolean t) { 281 _isTunnel = t; 282 } 283 284 @Override 285 public boolean equals(Object o) { 286 if (o == null) { 287 return false; 288 } 289 if (this.getClass().equals(o.getClass())) { 290 if (this.hashCode() == o.hashCode()) { 291 return true; 292 } 293 } 294 return false; 295 } 296 297 @Override 298 public int hashCode() { 299 int hash = 7; 300 hash = 89 * hash + (this._isTunnel ? 1 : 0) + super.hashCode(); 301 return hash; 302 } 303 304 /** 305 * translate() 306 * 307 * Translate this PhysicalLocation's coordinates to be relative to point 308 * "ref". NOTE: This is a "permanent" internal translation, and permanently 309 * changes this PhysicalLocation's value. 310 * 311 * If you want a new PhysicalLocation that represents the relative position, 312 * call the class method translate(loc, ref) 313 * 314 * @param ref new reference (origin) point 315 */ 316 public void translate(PhysicalLocation ref) { 317 if (ref == null) { 318 return; 319 } 320 321 this.setX(this.getX() - ref.getX()); 322 this.setY(this.getY() - ref.getY()); 323 this.setZ(this.getZ() - ref.getZ()); 324 } 325 326 private static final Logger log = LoggerFactory.getLogger(PhysicalLocation.class); 327}