Index: /applications/editors/josm/haiti_earthquake/radiotv/Kml2OsmConverter.groovy
===================================================================
--- /applications/editors/josm/haiti_earthquake/radiotv/Kml2OsmConverter.groovy	(revision 19939)
+++ /applications/editors/josm/haiti_earthquake/radiotv/Kml2OsmConverter.groovy	(revision 19939)
@@ -0,0 +1,325 @@
+package ch.guggis.haiti.radiotv;
+
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.io.BufferedReader;
+import javax.xml.xpath.*
+import groovy.util.IndentPrinter;
+import groovy.util.XmlParser
+import groovy.xml.MarkupBuilder;
+import groovy.xml.DOMBuilder;
+import javax.xml.parsers.DocumentBuilderFactory
+import org.xml.sax.InputSource
+import groovy.xml.StreamingMarkupBuilder
+import org.w3c.dom.Node 
+import java.text.SimpleDateFormat
+import groovy.util.CliBuilder
+
+
+/**
+ * A radio or tv studio in the IMS list 
+ */
+class Studio {
+	private static def xpath = XPathFactory.newInstance().newXPath()
+
+    def id
+	def lat
+	def lon
+	def String name = null
+	def String type = null
+	def String address = null
+	def String contact = null
+	def String email = null
+	def String phone = null
+	def String frequency = null
+	def String notes = null
+	
+	/**
+	 * Create a studio from a Placemark in the KML file 
+	 */
+	public Studio(Node placemark) {
+		initId(placemark)
+		initName(placemark)
+		initType(placemark)
+		initAddress(placemark)
+		initContact(placemark)
+		initPhone(placemark)
+		initFrequency(placemark)
+		initNotes(placemark)
+		initEmail(placemark)
+		initLatLon(placemark)
+	}
+
+	private def normalize(String s) {
+		if (s == null) return s
+		s = s.trim()
+		s = s.replaceAll("\n", " ")
+		s = s.substring(0,Math.min(s.length(), 255))
+		return s
+	}
+
+	/**
+	 * init the the from the Placemark node 
+	 */
+	static private def exprId = xpath.compile("./@id")
+	private def initId(placemark) {
+		id = exprId.evaluate(placemark, XPathConstants.STRING)
+	}
+	
+	/**
+	 * init the lat/lon-coordinates from the Placemark node 
+	 */
+	static private def exprLatLon = xpath.compile("./Point/coordinates/text()")
+	private def initLatLon(placemark) {
+		def latlon = exprLatLon.evaluate(placemark, XPathConstants.STRING)
+		lat = null
+		lon = null
+		if (latlon == null) {
+			return
+		}
+		def latlonarr = latlon.split(",")
+		if (latlonarr == null || latlonarr.length != 2) {
+			println "Error: illegal format of latlon '${latlon}' for studio '${id}'"
+			return
+		}
+		lat = latlonarr[1].trim()
+		lon = latlonarr[0].trim()
+	}
+	
+	/**
+	 * init the name from the Placemark 
+	 */
+	static private def exprName = xpath.compile("./name/text()")
+	def initName(placemark) {
+		name = exprName.evaluate(placemark, XPathConstants.STRING)
+		if (name != null) name = name.trim()
+	}
+	
+	/**
+	 * init the media type from the Placemark 
+	 */
+	static private def exprType = xpath.compile("./ExtendedData/Data[@name = 'Type']/value/text()")
+	def initType(placemark) {
+		type = exprType.evaluate(placemark, XPathConstants.STRING)		
+		if (type != null) type = type.trim()
+	}
+	
+	/**
+	 * init the address from the Placemark 
+	 */
+	static private def exprAddress = xpath.compile("./ExtendedData/Data[@name = 'Address']/value/text()")
+	def initAddress(placemark) {
+		address = normalize(exprAddress.evaluate(placemark, XPathConstants.STRING))		
+	}
+	
+	/**
+	 * init the contact information from the Placemark 
+	 */
+	static private def exprContact = xpath.compile("./ExtendedData/Data[@name = 'Contact']/value/text()")
+	def initContact(placemark) {
+		contact = normalize(exprContact.evaluate(placemark, XPathConstants.STRING))		
+	}
+	
+	/**
+	 * init the email address from the Placemark 
+	 */
+	static private def exprEmail = xpath.compile("./ExtendedData/Data[@name = 'Email']/value/text()")
+	def initEmail(placemark) {
+		email = normalize(exprEmail.evaluate(placemark, XPathConstants.STRING))		
+	}
+	
+	/**
+	 * init the frequency from the Placemark
+	 */
+	static private def exprFrequency = xpath.compile("./ExtendedData/Data[@name = 'Frequency']/value/text()")
+	def initFrequency(placemark) {
+		frequency = normalize(exprFrequency.evaluate(placemark, XPathConstants.STRING))		
+	}
+
+	/**
+	 * init the notes from the Placemark
+	 */
+	static private def exprNotes = xpath.compile("./ExtendedData/Data[@name = 'Notes']/value/text()")
+	def initNotes(placemark) {
+		notes = normalize(exprNotes.evaluate(placemark, XPathConstants.STRING))		
+	}
+
+	/**
+	 * init the phone from the Placemark 
+	 */
+	static private def exprPhone = xpath.compile("./ExtendedData/Data[@name = 'Subtitle']/value/text()")
+	def initPhone(placemark) {
+		phone = normalize(exprPhone.evaluate(placemark, XPathConstants.STRING))		
+		if (phone != null) {
+			def matcher = phone =~ /^\s*Tel\s*:\s*(.*)/
+			if (matcher.matches()) {
+				phone = matcher[0][1]
+			}			
+		}
+	}
+
+	/**
+	 * Replies true if this studio has a valid position
+	 */
+	def boolean isValidPosition() {
+		return lat != null && lon != null
+	}
+	
+	def boolean isTower() {
+		return notes != null && notes.startsWith("This location was found using GPS at the site")
+	}
+	
+	def boolean isTvStation() {
+		return type == "TV Stations"
+	}
+}
+
+
+/**
+ * The converter
+ */
+class Kml2OsmConverter {
+	static final public String RADIO_TV_FILE = "C:/data/projekte/haiti/radiotv/RadioStationsHaitiJan2010.xml"
+	private def xpath = XPathFactory.newInstance().newXPath()
+
+    def reader
+	def writer
+
+	def process() {
+		def builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+		def doc  = builder.parse(new InputSource(reader)).documentElement
+		def markup = new MarkupBuilder(writer)	
+		def nodeId = 0
+		markup.getMkp().pi(xml: [version: "1.0", encoding:"UTF-8"])
+		markup.getMkp().comment("""
+Automatically generated from this list of radio and tv studios in haiti:
+http://spreadsheets.google.com/pub?key=tZP0wXS4HMLAWEhDlzdW36w&output=txt&output=txt&gid=0&range=kml_output
+
+Generated on: ${new SimpleDateFormat().format(new Date())}					
+""")					
+		markup.osm(version: "0.6", generator: "Ism2Osm") {
+			xpath.evaluate("//Placemark", doc, XPathConstants.NODESET).each {
+				Node placemark ->
+				def studio = new Studio(placemark)
+				println "Processing studio '${studio.id}' with name '${studio.name}'"
+				if (!studio.isValidPosition()) {
+					println "Error: studio '${studio.id}' doesn't have a valid position. Skipping."
+					return
+				}
+				if (!studio.id) {
+					println "Error: studio '${studio.id}' doesn't have a valid IMS id. Skipping."
+					return
+				}
+				nodeId--
+				node(id:nodeId, version:1, lat: studio.lat, lon: studio.lon) {
+					if (studio.isTower()) {
+						tag(k:"man_made", v:"tower");
+						tag(k:"tower:type", v:"communication")
+					} else {
+						tag(k:"amenity", v:"studio")
+						if (studio.isTvStation()) {
+							tag(k:"type", v:"video")
+						}
+					}
+					if (studio.name) {
+						tag(k:"name", v:studio.name)
+					}
+					if (studio.type) {
+						tag(k:"ims:media_type", v:studio.type)
+					}	
+					tag(k:"ims:id", v:studio.id)
+					if (studio.address) {
+						tag(k:"addr", v:studio.address)
+					}
+					if (studio.contact) {
+						tag(k:"contact", v:studio.contact)
+					}
+					if (studio.email) {
+						tag(k: "contact:email", v:studio.email)
+					}
+					if (studio.phone) {
+						tag(k: "phone", v:studio.phone)
+					}
+					if (studio.frequency) {
+						tag(k: "ims:frequency", v:studio.frequency)
+					}
+					if (studio.notes) {
+						tag(k: "note", v:studio.notes)
+					}
+					tag(k:"source_ref", v: "http://spreadsheets.google.com/pub?key=tZP0wXS4HMLAWEhDlzdW36w&output=txt&output=txt&gid=0&range=kml_output")
+					tag(k:"source", "CartONG - http://www.cartong.org")
+				}
+			}			
+		}
+			
+	}
+
+	def usage() {
+		println """
+groovy ch.guggis.haiti.radiotv.Kml2OsmConverter [options]
+Options:
+		-h, --help				show help information
+		-i, --input-file 		the input file. Reads from stdin if missing
+		-o, --output-file		the output file. Writes to stdout if missing. 
+"""
+	}
+	
+	def fail(msg) {
+		println msg
+		usage()
+		System.exit(1)
+	}
+
+    def processCommandLineOptions(argArray) {
+		def inputFile
+		def outputFile
+		def args = Arrays.asList(argArray)
+		args = args.reverse()
+		def arg = args.pop()
+		while(arg != null) {
+			switch(arg) {
+				case "-i":
+				case "--input-file":
+					inputFile = args.pop()
+					if (inputFile == null) {
+						fail "Error: missing input file"
+					}
+					break
+				case "-o":
+				case "--output-file":
+					outputFile = args.pop()
+					if (outputFile == null) {
+						fail "Error: missing output file"
+					}
+					break
+					
+				case "-h":
+				case "--help":
+					usage()
+					System.exit(0)
+					break		
+				
+				default:
+					fail "Illegal argument ${arg}"
+			}
+			arg = args.empty ? null : args.pop()
+		}
+
+		if (inputFile) {
+			reader = new File(inputFile).newReader("UTF-8")
+		} else {
+			reader = new BufferedReader(new InputStreamReader(System.in, "UTF-8"))
+		}
+		if (outputFile) {
+			writer = new File(outputFile).newWriter("UTF-8")
+		} else {
+			writer = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"))
+		}	
+	}
+		
+	static public void main(args) {
+		def task = new Kml2OsmConverter()
+		task.processCommandLineOptions(args)
+		task.process()
+	}
+}
Index: /applications/editors/josm/haiti_earthquake/radiotv/README.txt
===================================================================
--- /applications/editors/josm/haiti_earthquake/radiotv/README.txt	(revision 19939)
+++ /applications/editors/josm/haiti_earthquake/radiotv/README.txt	(revision 19939)
@@ -0,0 +1,5 @@
+Scripts for importing an updating a set of radio and tv studios in Haiti.
+
+See http://wiki.openstreetmap.org/wiki/WikiProject_Haiti/Tasks_and_Ideas/RadioTvStations
+
+NOTE: this is probably not the right place in the OSM SVN for these files. Will later be moved.
Index: /applications/editors/josm/haiti_earthquake/radiotv/UpdateFileBuilder.groovy
===================================================================
--- /applications/editors/josm/haiti_earthquake/radiotv/UpdateFileBuilder.groovy	(revision 19939)
+++ /applications/editors/josm/haiti_earthquake/radiotv/UpdateFileBuilder.groovy	(revision 19939)
@@ -0,0 +1,195 @@
+package ch.guggis.haiti.radiotv;
+
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.io.BufferedReader;
+import javax.xml.xpath.*
+import groovy.util.IndentPrinter;
+import groovy.util.XmlParser
+import groovy.xml.MarkupBuilder;
+import groovy.xml.DOMBuilder;
+import javax.xml.parsers.DocumentBuilderFactory
+import org.xml.sax.InputSource
+import groovy.xml.StreamingMarkupBuilder
+import org.w3c.dom.Node 
+import java.text.SimpleDateFormat
+import groovy.util.CliBuilder
+import groovy.xml.XmlUtil
+
+/**
+ * Takes a file with the newest data for studios from IMS and creates an OSM file
+ * with property ids, version info and action="modify" attributes, in order to open
+ * it in JOSM and upload it to OSM. 
+ * 
+ */
+class UpdateFileBuilder {
+	private static def xpath = XPathFactory.newInstance().newXPath()
+	def lastChangesetId
+	def reader
+	def writer
+	
+	def queryApi(url) {
+		def builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+		def doc  = builder.parse(
+			new InputSource(
+				new InputStreamReader(
+					new URL(url).openStream(),
+					"UTF-8"
+				)
+			)
+		).documentElement
+		return doc	
+	}
+	/**
+	 * Retrieves the nodes which have been updated and create in the last update of
+	 * the studio dataset 
+	 * 
+	 */
+	def fetchChangeset() {
+		return queryApi("http://api.openstreetmap.org/api/0.6/changeset/${lastChangesetId}/download")
+	}
+	
+	
+	/**
+	 * Fetch the current version for all nodes representing IMS studios 
+	 * 
+	 */
+	def currentIMSStudioObjectsFromOsm(changeset) {
+		def ids = []
+		xpath.evaluate("//node", changeset, XPathConstants.NODESET).each { node ->
+			ids << xpath.evaluate("./@id", node, XPathConstants.STRING)
+		}
+		return queryApi("http://api.openstreetmap.org/api/0.6/nodes?nodes=" + ids.join(","))
+	}
+	
+	/** 
+	 * Load the new data from the OSM file
+	 * 
+	 */
+	def loadNewStudios() {
+		def builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+		def doc  = builder.parse(
+				new InputSource(
+						reader
+				)
+		).documentElement
+		return doc	
+	}
+	
+	/**
+	 * Update the id and version in the studio objects. 
+	 */
+	def updateIdsAndVersions(newStudios, oldStudios) {
+		xpath.evaluate("//node", newStudios, XPathConstants.NODESET).each { newStudio ->
+			def imsid = xpath.evaluate("./tag[@k = 'ims:id']/@v", newStudio, XPathConstants.STRING)
+			println "Processing studio '${imsid}' ..."			
+			def osmid = xpath.evaluate("//tag[@k = 'ims:id'][@v = '${imsid}']/../@id", oldStudios, XPathConstants.STRING)
+			def osmversion = xpath.evaluate("//tag[@k = 'pid'][@v = '${imsid}']/../@version", oldStudios, XPathConstants.STRING)
+			if (!osmid) {
+				println "Warning: didn't find an existing node for studio '${imsid}'. Adding the studio instead of updating it."				
+				return
+			} 			
+			newStudio.setAttribute("id", osmid)
+			newStudio.setAttribute("version", osmversion)
+			newStudio.setAttribute("action", "modify")
+		}
+		return newStudios
+	}
+	
+	
+	def process() {
+		println "Fetching changeset '${lastChangesetId}' from OSM API ..."
+		def changeset = fetchChangeset()
+		println "Fetching the current versions of the nodes from the OSM API ..."
+		def oldStudios = currentIMSStudioObjectsFromOsm(changeset)
+		println "Loading the new nodes for IMS studios ..."
+		def newStudios = loadNewStudios()
+		println "Updating the ids and versions of the new studios ..."
+		updateIdsAndVersions(newStudios, oldStudios)
+		println "Writing the update changeset file ..."
+		writer.println newStudios		
+		writer.flush()		
+	}
+	
+	def usage() {
+		println """
+groovy ch.guggis.haiti.radiotv.UpdateFileBuilder [options]
+Options:
+  -cs, --last-changeset   the changeset id used in the last upload. Mandatory.
+  -h, --help              show help information
+  -i, --input-file        the input file. Reads from stdin if missing
+  -o, --output-file       the output file. Writes to stdout if missing. 
+"""
+	}
+	
+	def fail(msg) {
+		println msg
+		usage()
+		System.exit(1)
+	}
+	
+	def processCommandLineOptions(argArray) {
+		def inputFile
+		def outputFile
+		def args = Arrays.asList(argArray)
+		args = args.reverse()
+		def arg = args.pop()
+		while(arg != null) {
+			switch(arg) {
+				case "-i":
+				case "--input-file":
+					inputFile = args.pop()
+					if (inputFile == null) {
+						fail "Error: missing input file"
+					}
+					break
+				case "-o":
+				case "--output-file":
+					outputFile = args.pop()
+					if (outputFile == null) {
+						fail "Error: missing output file"
+					}
+					break
+				case "-cs":
+				case "--last-changeset":
+					lastChangesetId = args.pop()
+					if (lastChangesetId == null) {
+						fail "Error: missing changeset id"
+					}
+					break
+				
+				case "-h":
+				case "--help":
+					usage()
+					System.exit(0)
+					break		
+				
+				default:
+					fail "Illegal argument ${arg}"
+			}
+			arg = args.empty ? null : args.pop()
+		}
+		
+		if (!lastChangesetId) {
+			fail("Mandatory command line option '-cs' missing.")
+			System.exit(1)
+		}
+		
+		if (inputFile) {
+			reader = new File(inputFile).newReader("UTF-8")
+		} else {
+			reader = new BufferedReader(new InputStreamReader(System.in, "UTF-8"))
+		}
+		if (outputFile) {
+			writer = new File(outputFile).newWriter("UTF-8")
+		} else {
+			writer = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"))
+		}	
+	}	
+	
+	static public void main(args) {
+		def task = new UpdateFileBuilder()
+		task.processCommandLineOptions(args)
+		task.process()
+	}
+}
