#!/usr/bin/ruby
require 'rubygems'
require 'xml/libxml'
require 'geo_ruby'
require 'cgi'


#Bearing from point A to point B, in degrees.
def bearing(pointA, pointB)
	return 0 if pointA == pointB
	a = pointB.x - pointA.x
	b = pointB.y - pointA.y

	res =  Math.acos(b / Math.sqrt(a*a+b*b)) / Math::PI * 180;
	a < 0 ? 360 - res : res
end

def fileNameEncode(strData)	
	strData=strData.gsub(" ", "_")
	strData=strData.gsub(";", "sc")
	strData=strData.gsub(":", "cln")
	strData=strData.gsub(",", "c")
	strData=strData.gsub("!", "exc")
	strData=CGI::escape(strData)
	strData=strData.gsub("%", "char")
	return strData
end


def webMapsLiteHTML(jsArrayLines, minLat,maxLat,minLon,maxLon) 
	html = ""		
	html += "<script type='text/javascript' src='http://tile.cloudmade.com/wml/latest/web-maps-lite.js'></script>\n"
	html += "<script type='text/javascript' src='./chain-disp.js'></script>\n"
	html += "<script type='text/javascript' src='./edit-link.js'></script>\n"

	html += "<script type='text/javascript'>\n"
	html += "	var chains = [\n"
	html += "	" + jsArrayLines.join(",\n\t") 
	html += "	];\n"
	html += "	var tiles = new CM.Tiles.CloudMade.Web({key: 'ed24c9065bb55c1090d3d76e7ab8577e', styleId:998});\n"
	html += "	var map = new CM.Map('mapdiv', tiles);\n"
	html += "	bounds = new CM.LatLngBounds([\n"
	html += "	   new CM.LatLng( " + minLat.to_s + ", " + minLon.to_s + "),\n"
	html += "	   new CM.LatLng( " + maxLat.to_s + ", " + maxLon.to_s + ")\n"
	html += "	]);\n"		
	html += "	map.zoomToBounds(bounds);\n"
	html += "	processArray();\n"
	html += "</script>\n"
	return html
end



filename = ARGV[0]
if filename==nil :
	puts "No arguments. Please specify input OSM file"
	exit
end

puts "XML parsing to Document object file " + filename
doc = XML::Document.file(filename)



puts "doc.find //node"
nodes = doc.find('//node')

puts "Building nodes hash from "+ nodes.length.to_s + " <node> tags in the XML"
nodesHash = {}
for index in (0...nodes.length)
	nodeElement = nodes[index]
	id = nodeElement.attributes['id']
	nodesHash[id] = nodeElement
end
puts nodesHash.length.to_s + " nodes hashed by id"

NONELABLE = "<i>[none]</i>"

puts "doc.find //way"
ways = doc.find('//way')

puts "Building waysByRef hash"
waysByRef = {}
waysByRef[NONELABLE] = [ ]

for index in (0...ways.length)
	wayElement = ways[index]
	
	wayData = {}
	wayData[ :id ] = wayElement.attributes['id'] 
	wayData[ :timestamp ] = wayElement.attributes['timestamp'] 
	wayData[ :user ] = wayElement.attributes['user'] 
	
	#find start and end node ids for this way plus tags for name/ref and oneway
	name = NONELABLE
	reftag = NONELABLE
	intreftag = NONELABLE
	oneway = ""
	bridge = ""
	layer = ""
	startNodeId = ""
	startLat =""
	startLon =""
	endNodeId = ""
	endLat =""
	endLon =""
	
	wayElement.children.each do |wayChildElement|

		if wayChildElement.name=="nd" :
			if startNodeId=="" :
				startNodeId = wayChildElement.attributes['ref']
			else
				endNodeId = wayChildElement.attributes['ref']
			end
			
		elsif wayChildElement.name=="tag" :

			if wayChildElement.attributes['k']=='name' :
				name = wayChildElement.attributes['v']
			elsif wayChildElement.attributes['k']=='ref' :
				reftag = wayChildElement.attributes['v']
			elsif wayChildElement.attributes['k']=='int_ref' :
				intreftag = wayChildElement.attributes['v']
			elsif wayChildElement.attributes['k']=='oneway' :
				oneway = wayChildElement.attributes['v']
			elsif wayChildElement.attributes['k']=='bridge' :
				bridge = wayChildElement.attributes['v']
			elsif wayChildElement.attributes['k']=='layer' :
				layer = wayChildElement.attributes['v']
			end
		end
	end
	
	reftag=intreftag if intreftag!=NONELABLE
	
	#reftag=reftag.gsub('-', ' ')
	
	reftag="<i>[space]</i>" if reftag==" "
	
	
	wayData[:name] = name
	wayData[:reftag] = reftag
	wayData[:oneway] = oneway
	wayData[:bridge] = bridge
	wayData[:layer] = layer
	
	if startNodeId!="" :
		#startNode = doc.find("//node[@id=\"" + startNodeId + "\"]").first
		startNode = nodesHash[startNodeId]
		
		if startNode.nil? :
			startLat = "\"NOT FOUND\""
			startLon = "\"NOT FOUND\""
		else 
			startLat = startNode.attributes['lat']
			startLon = startNode.attributes['lon']
		end
	end

	wayData[:startNodeId] = startNodeId 
	wayData[:startLat] = startLat 
	wayData[:startLon] = startLon 
	
	if endNodeId!="" :
		#endNode = doc.find("//node[@id=\"" + endNodeId + "\"]").first
		endNode = nodesHash[endNodeId]
		
		if endNode.nil? :
			endLat = "\"NOT FOUND\""
			endLon = "\"NOT FOUND\""
		else 
			endLat = endNode.attributes['lat']
			endLon = endNode.attributes['lon']
		end
	end
	
	wayData[:endNodeId] = endNodeId 
	wayData[:endLat] = endLat
	wayData[:endLon] = endLon
	
	
	
	waysByRef[reftag] = [ ] if waysByRef[reftag].nil?
	waysByRef[reftag] << wayData
	
	puts index.to_s+"/"+ways.length.to_s+" waysByRef:"+waysByRef.length.to_s  if index % 100 == 0
	
end



puts "Building chain data"

system("rm htmlreport/index.html")
system("rm htmlreport/chains-*.html")

tsvOutputFile = File.new("waychainsummary.tsv", "w")

indexHTMLfile = File.new("htmlreport/index.html", "w")

indexHTMLfile.puts "<html>"
indexHTMLfile.puts "<head><title>refs for U.S. interstates:</title>"
indexHTMLfile.puts "<link rel=\"stylesheet\" type=text/css href=\"waychains.css\">"
indexHTMLfile.puts "</head>"
indexHTMLfile.puts "<h1>refs for U.S. interstates:</h1>"
indexHTMLfile.puts "<ul>"


allChainHTMLfile = File.new("htmlreport/chains-all.html", "w")

allChainHTMLfile.puts "<html>"
allChainHTMLfile.puts "<head><title>Chains of ways for all refs</title>"
allChainHTMLfile.puts "<link rel=\"stylesheet\" type=text/css href=\"waychains.css\">"
allChainHTMLfile.puts "</head>"
allChainHTMLfile.puts "<div id='mapdiv'></div>"
allChainHTMLfile.puts "<h1>Chains of ways for all refs</h1>"
allChainHTMLfile.puts "<table border=1>"
allJSArray = []

allPairsHTMLfile = File.new("htmlreport/chains-paired.html", "w")

allPairsHTMLfile.puts "<html>"
allPairsHTMLfile.puts "<head><title>Waychain pairs</title>"
allPairsHTMLfile.puts "<link rel=\"stylesheet\" type=text/css href=\"waychains.css\">"
allPairsHTMLfile.puts "</head>"
allPairsHTMLfile.puts "<div id='mapdiv'></div>"
allPairsHTMLfile.puts "<h1>All paired waychains</h1>"
allPairsHTMLfile.puts "<table border=1>"
pairsJSArray = []


columnHeadingsHTML  = "<tr>\n"
columnHeadingsHTML += "  <th>ref</th>\n"
columnHeadingsHTML += "  <th># of ways</th>\n"
columnHeadingsHTML += "  <th>dupe<br>nodes</th>\n"
columnHeadingsHTML += "  <th>start</th>\n"
columnHeadingsHTML += "  <th>end</th>\n"
columnHeadingsHTML += "  <th>length</th>\n"
columnHeadingsHTML += "  <th>bearing</th>\n"
columnHeadingsHTML += "</tr>"

allChainHTMLfile.puts columnHeadingsHTML
allPairsHTMLfile.puts columnHeadingsHTML

gennote = "<p class=\"gennote\">Generated " + DateTime.now.to_s + " from file " + filename + "</p>"

refCount = 0
pairedRefCount = 0
totalChainCount = 0

waysByRef.keys.sort.each do |reftag|
	
	if reftag!=NONELABLE :
	
		waysForThisRef = waysByRef[reftag]
		
		puts "Chaining ways with ref " + reftag 


		thisRefHTMLfileName = "chains-" + fileNameEncode(reftag) + ".html"
		
		thisRefHTMLfile = File.new("htmlreport/" + thisRefHTMLfileName , "w")
		
		thisRefHTMLfile.puts "<html width='100%' height='100%' >"
		thisRefHTMLfile.puts "<head><title>" + reftag + "</title>"
		thisRefHTMLfile.puts "<link rel=\"stylesheet\" type=text/css href=\"waychains.css\">"
		thisRefHTMLfile.puts "</head>"
		thisRefHTMLfile.puts "<body width=100% height=100%>"

		thisRefHTMLfile.puts "<div id='mapdiv'></div>"
		
		thisRefHTMLfile.puts "<h1>Chains of ways with ref='" + reftag + "'</h1>"
		thisRefHTMLfile.puts "<table border=1 class=\"chaintable\">"
		thisRefHTMLfile.puts columnHeadingsHTML
		thisRefJSArray = [ ]
		
		chainsForThisRef = []
		thisRefMaxLat = -999
		thisRefMaxLon = -999
		thisRefMinLat = 999
		thisRefMinLon = 999
		
		remainingWays = waysForThisRef

		while not remainingWays.empty? :
		

			chainSoFar = [] #array of wayDatas
			chainData = {} #summary data for the chain
			
			#grab any way to start with
			anyWay = remainingWays.pop
			chainSoFar << anyWay
	
			chainData[:reftag] = reftag
			chainData[:count] = 1
			chainData[:duplicateNodes] = 0
			chainData[:startNodeId] = anyWay[:startNodeId]
			chainData[:startLat]    = anyWay[:startLat]
			chainData[:startLon]    = anyWay[:startLon]
			chainData[:endNodeId]   = anyWay[:endNodeId]
			chainData[:endLat]      = anyWay[:endLat]
			chainData[:endLon]      = anyWay[:endLon]

			#loop through remaining ways looking for one to join on either end of the chain
			chainExpanded = true;
			while chainExpanded :
								
				chainExpanded = false;
				for index in (0...remainingWays.length)
					onWay = remainingWays[index]
					
					
					if (onWay[:startLat]==chainData[:endLat] and onWay[:startLon]==chainData[:endLon] and onWay[:startLon]!="NOT FOUND") or onWay[:startNodeId]==chainData[:endNodeId] :
						#Found one to to add to the end of the chain
						chainData[:duplicateNodes]+=1 if onWay[:startNodeId]!=chainData[:endNodeId]
						
						chainSoFar << onWay
						chainData[:endNodeId] = onWay[:endNodeId]
						chainData[:endLat]    = onWay[:endLat]
						chainData[:endLon]    = onWay[:endLon]
						chainExpanded = true
						
					elsif (onWay[:endLat]==chainData[:startLat] and onWay[:endLon]==chainData[:startLon] and onWay[:endLon]!="NOT FOUND") or onWay[:endNodeId]==chainData[:startNodeId] :
						#Found one to to add to the start of the chain
						chainData[:duplicateNodes]+=1 if onWay[:endNodeId]!=chainData[:startNodeId]
						
						chainSoFar.unshift(onWay)
						chainData[:startNodeId] = onWay[:startNodeId]
						chainData[:startLat]    = onWay[:startLat]
						chainData[:startLon]    = onWay[:startLon]
						chainExpanded = true
					end
					
					if chainExpanded :
						chainData[:count] +=1
						remainingWays.delete_at(index)
						break
					end
					
				end
			end
			
			totalChainCount +=1
			
			startPoint = GeoRuby::SimpleFeatures::Point.from_lon_lat(chainData[:startLon].to_f, chainData[:startLat].to_f)
			endPoint   = GeoRuby::SimpleFeatures::Point.from_lon_lat(chainData[:endLon].to_f, chainData[:endLat].to_f )
			
			chainData[:length] = startPoint.ellipsoidal_distance(endPoint).round
			chainData[:bearing] = bearing(startPoint,endPoint).round
				
			chainsForThisRef << chainData
			
			#output chain details
			tsvLine  = totalChainCount.to_s + "\t"
			tsvLine += chainData[:reftag] + "\t"
			tsvLine += chainData[:count].to_s + "\t"
			tsvLine += chainData[:duplicateNodes].to_s + "\t"
			tsvLine += chainData[:startNodeId] + "\t"
			tsvLine += chainData[:startLat] + "\t"
			tsvLine += chainData[:startLon] + "\t"
			tsvLine += chainData[:endNodeId] + "\t"
			tsvLine += chainData[:endLat] + "\t"
			tsvLine += chainData[:endLon] + "\t"
			tsvLine += chainData[:length].to_s  + "\t"
			tsvLine += chainData[:bearing].to_s + "\t"
			tsvOutputFile.puts tsvLine
			

			
			
			if chainData[:count]==1
				wayCount = "<a href=\"http://www.openstreetmap.org/browse/way/" + chainSoFar[0][ :id ] +"\">1</a>"			
			else 
				wayCount = chainData[:count].to_s
			end
			
			htmlLine  = "<tr>\n"
			htmlLine += "  <td><a href=\"./" + thisRefHTMLfileName + "\">"+ chainData[:reftag] + "</a></td>\n"
			htmlLine += "  <td>" + wayCount + "</td>\n"
			htmlLine += "  <td>" + chainData[:duplicateNodes].to_s + "</td>\n"
			htmlLine += "  <td><a href=\"http://www.openstreetmap.org/browse/node/" + chainData[:startNodeId] + "\">start</a></td>\n"
			htmlLine += "  <td><a href=\"http://www.openstreetmap.org/browse/node/" + chainData[:endNodeId] + "\">end</a></td>\n"
			htmlLine += "  <td>" + chainData[:length].to_s  + "</td>\n"
			htmlLine += "  <td>" + chainData[:bearing].to_s + "</td>\n"
			htmlLine += "</tr>\n"
			
			allChainHTMLfile.puts htmlLine
			thisRefHTMLfile.puts htmlLine
			
			
			jsArrayLine  = "["
			jsArrayLine += "\"" + chainData[:reftag] +  "\","
			jsArrayLine += chainData[:count].to_s + ","
			jsArrayLine += chainData[:duplicateNodes].to_s + ","
			jsArrayLine += chainData[:startLat] + ","
			jsArrayLine += chainData[:startLon] + ","
			jsArrayLine += chainData[:endLat] + ","
			jsArrayLine += chainData[:endLon]
			jsArrayLine += "]"
			
			thisRefJSArray << jsArrayLine
			allJSArray << jsArrayLine
			
			
			thisRefMaxLat = chainData[:startLat].to_f if chainData[:startLat].to_f>thisRefMaxLat
			thisRefMaxLat = chainData[:endLat].to_f   if chainData[:endLat].to_f>thisRefMaxLat
			thisRefMinLat = chainData[:startLat].to_f if chainData[:startLat].to_f<thisRefMinLat
			thisRefMinLat = chainData[:endLat].to_f   if chainData[:endLat].to_f<thisRefMinLat
			thisRefMaxLon = chainData[:startLon].to_f if chainData[:startLon].to_f>thisRefMaxLon
			thisRefMaxLon = chainData[:endLon].to_f   if chainData[:endLon].to_f>thisRefMaxLon
			thisRefMinLon = chainData[:startLon].to_f if chainData[:startLon].to_f<thisRefMinLon
			thisRefMinLon = chainData[:endLon].to_f	  if chainData[:endLon].to_f<thisRefMinLon
			
		end


		thisRefHTMLfile.puts "</table>"
		
		
		chainInfo = ""
		if chainsForThisRef.length==2 :
			bearingDiff = chainsForThisRef[0][:bearing] - chainsForThisRef[1][:bearing]
			bearingDiff =-bearingDiff if bearingDiff<0
			
			if bearingDiff>175 and bearingDiff<185 :
				chainInfo = "2 chains. <span class=\"goodchaincount\">Paired</span> <!-- bearingDiff=" + bearingDiff.to_s + "-->"
				pairedRefCount +=1


				allPairsHTMLfile.puts htmlLine				
				pairsJSArray << jsArrayLine
			
			else 
				chainInfo = "2 chains. bearing &Delta; <span class=\"badchaincount\"> "+ bearingDiff.to_s+"</span>"
			end
			
		else
			chainInfo = "<span class=\"badchaincount\">" + chainsForThisRef.length.to_s + "</span> chains"
		end
		
		thisRefHTMLfile.puts chainInfo
		thisRefHTMLfile.puts "<br><br>"
		thisRefHTMLfile.puts "<a href=\"index.html\"><< Index</a>"
			
		thisRefHTMLfile.puts webMapsLiteHTML(thisRefJSArray, thisRefMinLat, thisRefMaxLat, thisRefMinLon, thisRefMaxLon)
		
		thisRefHTMLfile.puts gennote
		thisRefHTMLfile.puts "</html>"
		thisRefHTMLfile.close
		
		
		indexLine =  "<li>"
		indexLine += "<a href=\"./" + thisRefHTMLfileName + "\">" + reftag + "</a> - "
		indexLine += chainInfo
		indexLine += "</li>"
		indexHTMLfile.puts indexLine

	end
end

allChainHTMLfile.puts "</table>"
allChainHTMLfile.puts totalChainCount.to_s + " chains<br><br>"
allChainHTMLfile.puts "<a href=\"index.html\"><< Index</a>"
allChainHTMLfile.puts webMapsLiteHTML(allJSArray, 24.4, 49.2, -128.5, -66.3)
allChainHTMLfile.puts gennote
allChainHTMLfile.puts "</html>"
allChainHTMLfile.close


allPairsHTMLfile.puts "</table>"
allPairsHTMLfile.puts pairsJSArray.length.to_s + " pairs</br></br>"
allPairsHTMLfile.puts "<a href=\"index.html\"><< Index</a>"
allPairsHTMLfile.puts webMapsLiteHTML(pairsJSArray, 24.4, 49.2, -128.5, -66.3)
allPairsHTMLfile.puts gennote
allPairsHTMLfile.puts "</html>"
allPairsHTMLfile.close

refCount = waysByRef.length
refCount -=1 if not waysByRef[NONELABLE].nil?

noRefWaysCount=0
noRefWaysCount=waysByRef[NONELABLE].length if not waysByRef[NONELABLE].nil?

percentPaired= pairedRefCount*100/refCount

indexHTMLfile.puts "</ul>"
indexHTMLfile.puts "<p><a href=\"chains-paired.html\" name=\"totals\">All paired refs map</a></p>"
indexHTMLfile.puts "<p><a href=\"chains-all.html\" name=\"totals\">All refs</a></p>"

indexHTMLfile.puts "<h3>Totals:</h3>"
indexHTMLfile.puts "<p>"+ pairedRefCount.to_s + " paired out of " + refCount.to_s + " different ref values (" + percentPaired.to_s + "%)</p>"
indexHTMLfile.puts "<p>"+ noRefWaysCount.to_s + " ways with no ref</p>"
indexHTMLfile.puts gennote
indexHTMLfile.puts "</html>"

indexHTMLfile.close


#Log what happens

planetsize=0
planetsize=File.size("planet-new.osm.gz") if File.exists?("planet-new.osm.gz")


wayChainSummaryLogfile = File.open("waychainsummary.log.tsv", "a")
wayChainSummaryLogfile.puts DateTime.now.to_s + "\t" + pairedRefCount.to_s + "\t" + refCount.to_s + "\t" + noRefWaysCount.to_s + "\t" + percentPaired.to_s + "\t" + planetsize.to_s
wayChainSummaryLogfile.close

puts "DONE. "
