text: /**
   * Represents a way. A way is automatically added to the geohash index when
   * it is instantiated.
   * 
   * @augments Feature
   */
  var Way = Class.create(Feature, 
  /**
   * @lends Way#
   */
  {
  	__type__: 'Way',
  	/**
  	 * Sets up this way's properties and adds it to the geohash index
  	 * @param {Object} data         A set of properties that will be copied to this Way.
  	 * @constructs
  	 */
      initialize: function($super, data) {
  		$super()
  		geohash = geohash || true
  		/**
  		 * Number of frames this Way has existed for
  		 * @type Number
  		 */
  		this.age = 0
  		/**
  		 * Timestamp of way creation
  		 * @type Date
  		 */
  		this.birthdate = new Date
  		/**
  		 * If true, this way will have a red border
  		 * @type Boolean
  		 */
  		this.highlight = false
  		/**
  		 * Nodes that make up this Way
  		 * @type Node[]
  		 */
  		this.nodes = []
  		/**
  		 * If true, this way will be treated a a polygon and filled when drawn
  		 * @type Boolean
  		 */
  		this.closed_poly = false
  		
  		this.is_hovered = false
  		
  		Object.extend(this, data)
  		
  		if (this.nodes.length > 1 && this.nodes.first().x == this.nodes.last().x && 
  			this.nodes.first().y == this.nodes.last().y) 
  				this.closed_poly = true
  				
  		if (this.tags.get('natural') == "coastline") this.closed_poly = true
  		
  		if (this.closed_poly) {
  			var centroid = Geometry.poly_centroid(this.nodes)
  			this.x = centroid[0]*2
  			this.y = centroid[1]*2
  		} else {
  			// attempt to make letters follow line segments:
  			this.x = (this.middle_segment()[0].x+this.middle_segment()[1].x)/2
  			this.y = (this.middle_segment()[0].y+this.middle_segment()[1].y)/2
  		}
  		
  		this.area = Geometry.poly_area(this.nodes)
  		this.bbox = Geometry.calculate_bounding_box(this.nodes)
  		
  		// calculate longest dimension to file in a correct geohash:
  		// Can we do this in lon/lat only, i.e. save some calculation?
  		this.width = Math.abs(Projection.x_to_lon(this.bbox[1])-Projection.x_to_lon(this.bbox[3]))
  		this.height = Math.abs(Projection.y_to_lat(this.bbox[0])-Projection.y_to_lat(this.bbox[2]))
  		
  		Feature.ways.set(this.id,this)
  		if (this.coastline) {
  			Coastline.coastlines.push(this)
  		} else {
  			Style.parse_styles(this,Style.styles.way)
  			Geohash.put_object(this)
  		}
      },
  	/**
  	 * for coastlines, the [prev,next] way in the series
  	 */
  	neighbors: [false,false],
  	/**
  	 * Adds a reference to itself into the 'chain' array and calls coastline_chain on the next or prev member
  	 * @param {Array}    chain  The array representing the chain of connected Ways
  	 * @param {Boolean}  prev   If the call is going to the prev member
  	 * @param {Boolean}  next   If the call is going to the next member
  	 */	
  	chain: function(chain,prev,next) {
  		// check if this way has appeared in the chain already:
  		var uniq = true
  		chain.each(function(way) {
  			if (way.id == this.id) uniq = false
  		},this)
  		if (uniq) {
  			if (prev) chain.push(this)
  			else chain.unshift(this)
  			$l(chain.length + ","+prev+next)
  			if (prev && this.neighbors[0]) { // this is the initial call
  				this.neighbors[0].chain(chain,true,false)
  			}
  			if (next && this.neighbors[1]) {
  				this.neighbors[1].chain(chain,false,true)
  			}
  		}
  		return chain
  	},
  	/** 
  	 * Finds the middle-most line segment
  	 * @return a tuple of nodes
  	 * @type Node[]
  	 */	
  	 middle_segment: function() {
          if (this.nodes.length == 1) {
              return [this.nodes[0], this.nodes[0]]
          }
          else if (this.nodes.length == 2) {
              return [this.nodes[0], this.nodes[1]]
          }
          else {
              return [this.nodes[Math.floor(this.nodes.length/2)],
  			        this.nodes[Math.floor(this.nodes.length/2)+1]]
          }
  	},
  	/**
  	 * Finds the angle of the middle-most line segment
  	 * @return The angle, in radians
  	 * @type Number
  	 */
  	middle_segment_angle: function() {
          var segment = this.middle_segment()
          if (segment[1]) {
              var _x = segment[0].x-segment[1].x
              var _y = segment[0].y-segment[1].y
              return (Math.tan(_y/_x))
          } else return 0
  	},
  	/**
  	 * Draws this way on the canvas
  	 */
  	draw: function($super) {
  		$super()
  		this.age += 1;
  	},
  	/**
  	 * Applies hover and mouseDown styles
  	 */
  	style: function() {
  		if (this.hover || this.menu) {
  			this.is_hovered = this.is_inside(Map.pointer_x(), Map.pointer_y())
  		}
  		// hover
  		if (this.hover && this.is_hovered) {
  				if (!this.hover_styles_applied) {
  					Mouse.hovered_features.push(this)
  					this.apply_hover_styles()
  					this.hover_styles_applied = true
  				}
  				if (!Object.isUndefined(this.hover.action)) this.hover.action.bind(this)()
  		}
  		else if (this.hover_styles_applied) {
  			Mouse.hovered_features = Mouse.hovered_features.without(this)
  			this.remove_hover_styles()
  			this.hover_styles_applied = false
  		}
  
  		// mouseDown
  		if (this.mouseDown && Mouse.down == true && this.is_hovered) {
  			if (!this.click_styles_applied) {
  				this.apply_click_styles()
  				this.click_styles_applied = true
  			}
  			if (!Object.isUndefined(this.mouseDown.action)) this.mouseDown.action.bind(this)()
  		}
  		else if (this.click_styles_applied) {
  			this.remove_click_styles()
  			this.click_styles_applied = false
  		}
  		
  		if (this.menu) {
  			if (this.is_hovered) {
  				this.menu.each(function(id) {
  					ContextMenu.cond_items[id].avail = true
  					ContextMenu.cond_items[id].context = this
  				}, this)
  			}
  			else {
  				this.menu.each(function(id) {
  					if (ContextMenu.cond_items[id].context == this) {
  						ContextMenu.cond_items[id].avail = false
  						ContextMenu.cond_items[id].context = window
  					}
  				}, this)
  			}
  		}
  	},
  	/**
  	 * Draws on the canvas to display this way
  	 */
  	shape: function() {
  		$C.opacity(1)
  		// fade in after load:
  		if (Object.isUndefined(this.opacity)) this.opacity = 1
  		if ((Glop.date - this.birthdate) < 4000) {
  			$C.opacity(Math.max(0,0.1+this.opacity*((Glop.date - this.birthdate)/4000)))
  		} else {
  			$C.opacity(this.opacity)
  		}
  
  		$C.begin_path()
  		if (Config.distort) $C.move_to(this.nodes[0].x,this.nodes[0].y+Math.max(0,75-Geometry.distance(this.nodes[0].x,this.nodes[0].y,Map.pointer_x(),Map.pointer_y())/4))
  		else $C.move_to(this.nodes[0].x,this.nodes[0].y)
  
  		if (Map.resolution == 0) Map.resolution = 1
  		this.nodes.each(function(node,index){
  			if ((index % Map.resolution == 0) || index == this.nodes.length-1 || this.nodes.length <= 30) {
  				// eye candy demo:
  				if (Config.distort) $C.line_to(node.x,node.y+Math.max(0,75-Geometry.distance(node.x,node.y,Map.pointer_x(),Map.pointer_y())/4))
  				else $C.line_to(node.x,node.y)
  			}
  		},this)
  
  		// fill the polygon if the beginning and end nodes are the same.
  		// we'll have to change this for open polys, like coastlines
  		if (this.outlineColor && this.outlineWidth) $C.outline(this.outlineColor,this.outlineWidth)
  		else $C.stroke()
  		if (this.closed_poly) $C.fill()
  
  		if (this.image) {
  			if (!this.image.src) {
  				var src = this.image
  				this.image = new Image()
  				this.image.src = src
  			} else if (this.image.width > 0) {
  				$C.draw_image(this.image, this.x-this.image.width/2, this.y-this.image.height/2)	
  			}
  		}
  		// show bboxes for ways:
  		// $C.line_width(1)
  		// $C.stroke_style('red')
  		// $C.stroke_rect(this.bbox[1],this.bbox[0],this.bbox[3]-this.bbox[1],this.bbox[2]-this.bbox[0])
  	},
  	apply_default_styles: function($super) {
  		$super()
  		this.outline_color = null
  		this.outline_width = 0
  	},
  	refresh_styles: function() {
  		this.apply_default_styles()
  		Style.parse_styles(this, Style.styles.way)
  	},
  	is_inside: function(x, y) {
  		if (this.closed_poly) {
  			return Geometry.is_point_in_poly(this.nodes, x, y)
  		}
  		else {
  			width = this.lineWidth + this.outline_width
  			
  			return Geometry.point_line_distance(x, y, this.nodes) < width
  		}
  	}
  })
  