1 | package ch.guggis.haiti.radiotv;
|
---|
2 |
|
---|
3 | import java.io.OutputStreamWriter;
|
---|
4 | import java.io.StringReader;
|
---|
5 | import java.io.BufferedReader;
|
---|
6 | import javax.xml.xpath.*
|
---|
7 | import groovy.util.IndentPrinter;
|
---|
8 | import groovy.util.XmlParser
|
---|
9 | import groovy.xml.MarkupBuilder;
|
---|
10 | import groovy.xml.DOMBuilder;
|
---|
11 | import javax.xml.parsers.DocumentBuilderFactory
|
---|
12 | import org.xml.sax.InputSource
|
---|
13 | import groovy.xml.StreamingMarkupBuilder
|
---|
14 | import org.w3c.dom.Node
|
---|
15 | import java.text.SimpleDateFormat
|
---|
16 | import groovy.util.CliBuilder
|
---|
17 |
|
---|
18 |
|
---|
19 | /**
|
---|
20 | * A radio or tv studio in the IMS list
|
---|
21 | */
|
---|
22 | class Studio {
|
---|
23 | private static def xpath = XPathFactory.newInstance().newXPath()
|
---|
24 |
|
---|
25 | def id
|
---|
26 | def lat
|
---|
27 | def lon
|
---|
28 | def String name = null
|
---|
29 | def String type = null
|
---|
30 | def String address = null
|
---|
31 | def String contact = null
|
---|
32 | def String email = null
|
---|
33 | def String phone = null
|
---|
34 | def String frequency = null
|
---|
35 | def String notes = null
|
---|
36 |
|
---|
37 | /**
|
---|
38 | * Create a studio from a Placemark in the KML file
|
---|
39 | */
|
---|
40 | public Studio(Node placemark) {
|
---|
41 | initId(placemark)
|
---|
42 | initName(placemark)
|
---|
43 | initType(placemark)
|
---|
44 | initAddress(placemark)
|
---|
45 | initContact(placemark)
|
---|
46 | initPhone(placemark)
|
---|
47 | initFrequency(placemark)
|
---|
48 | initNotes(placemark)
|
---|
49 | initEmail(placemark)
|
---|
50 | initLatLon(placemark)
|
---|
51 | }
|
---|
52 |
|
---|
53 | private def normalize(String s) {
|
---|
54 | if (s == null) return s
|
---|
55 | s = s.trim()
|
---|
56 | s = s.replaceAll("\n", " ")
|
---|
57 | s = s.substring(0,Math.min(s.length(), 255))
|
---|
58 | return s
|
---|
59 | }
|
---|
60 |
|
---|
61 | /**
|
---|
62 | * init the the from the Placemark node
|
---|
63 | */
|
---|
64 | static private def exprId = xpath.compile("./@id")
|
---|
65 | private def initId(placemark) {
|
---|
66 | id = exprId.evaluate(placemark, XPathConstants.STRING)
|
---|
67 | }
|
---|
68 |
|
---|
69 | /**
|
---|
70 | * init the lat/lon-coordinates from the Placemark node
|
---|
71 | */
|
---|
72 | static private def exprLatLon = xpath.compile("./Point/coordinates/text()")
|
---|
73 | private def initLatLon(placemark) {
|
---|
74 | def latlon = exprLatLon.evaluate(placemark, XPathConstants.STRING)
|
---|
75 | lat = null
|
---|
76 | lon = null
|
---|
77 | if (latlon == null) {
|
---|
78 | return
|
---|
79 | }
|
---|
80 | def latlonarr = latlon.split(",")
|
---|
81 | if (latlonarr == null || latlonarr.length != 2) {
|
---|
82 | println "Error: illegal format of latlon '${latlon}' for studio '${id}'"
|
---|
83 | return
|
---|
84 | }
|
---|
85 | lat = latlonarr[1].trim()
|
---|
86 | lon = latlonarr[0].trim()
|
---|
87 | }
|
---|
88 |
|
---|
89 | /**
|
---|
90 | * init the name from the Placemark
|
---|
91 | */
|
---|
92 | static private def exprName = xpath.compile("./name/text()")
|
---|
93 | def initName(placemark) {
|
---|
94 | name = exprName.evaluate(placemark, XPathConstants.STRING)
|
---|
95 | if (name != null) name = name.trim()
|
---|
96 | }
|
---|
97 |
|
---|
98 | /**
|
---|
99 | * init the media type from the Placemark
|
---|
100 | */
|
---|
101 | static private def exprType = xpath.compile("./ExtendedData/Data[@name = 'Type']/value/text()")
|
---|
102 | def initType(placemark) {
|
---|
103 | type = exprType.evaluate(placemark, XPathConstants.STRING)
|
---|
104 | if (type != null) type = type.trim()
|
---|
105 | }
|
---|
106 |
|
---|
107 | /**
|
---|
108 | * init the address from the Placemark
|
---|
109 | */
|
---|
110 | static private def exprAddress = xpath.compile("./ExtendedData/Data[@name = 'Address']/value/text()")
|
---|
111 | def initAddress(placemark) {
|
---|
112 | address = normalize(exprAddress.evaluate(placemark, XPathConstants.STRING))
|
---|
113 | }
|
---|
114 |
|
---|
115 | /**
|
---|
116 | * init the contact information from the Placemark
|
---|
117 | */
|
---|
118 | static private def exprContact = xpath.compile("./ExtendedData/Data[@name = 'Contact']/value/text()")
|
---|
119 | def initContact(placemark) {
|
---|
120 | contact = normalize(exprContact.evaluate(placemark, XPathConstants.STRING))
|
---|
121 | }
|
---|
122 |
|
---|
123 | /**
|
---|
124 | * init the email address from the Placemark
|
---|
125 | */
|
---|
126 | static private def exprEmail = xpath.compile("./ExtendedData/Data[@name = 'Email']/value/text()")
|
---|
127 | def initEmail(placemark) {
|
---|
128 | email = normalize(exprEmail.evaluate(placemark, XPathConstants.STRING))
|
---|
129 | }
|
---|
130 |
|
---|
131 | /**
|
---|
132 | * init the frequency from the Placemark
|
---|
133 | */
|
---|
134 | static private def exprFrequency = xpath.compile("./ExtendedData/Data[@name = 'Frequency']/value/text()")
|
---|
135 | def initFrequency(placemark) {
|
---|
136 | frequency = normalize(exprFrequency.evaluate(placemark, XPathConstants.STRING))
|
---|
137 | }
|
---|
138 |
|
---|
139 | /**
|
---|
140 | * init the notes from the Placemark
|
---|
141 | */
|
---|
142 | static private def exprNotes = xpath.compile("./ExtendedData/Data[@name = 'Notes']/value/text()")
|
---|
143 | def initNotes(placemark) {
|
---|
144 | notes = normalize(exprNotes.evaluate(placemark, XPathConstants.STRING))
|
---|
145 | }
|
---|
146 |
|
---|
147 | /**
|
---|
148 | * init the phone from the Placemark
|
---|
149 | */
|
---|
150 | static private def exprPhone = xpath.compile("./ExtendedData/Data[@name = 'Subtitle']/value/text()")
|
---|
151 | def initPhone(placemark) {
|
---|
152 | phone = normalize(exprPhone.evaluate(placemark, XPathConstants.STRING))
|
---|
153 | if (phone != null) {
|
---|
154 | def matcher = phone =~ /^\s*Tel\s*:\s*(.*)/
|
---|
155 | if (matcher.matches()) {
|
---|
156 | phone = matcher[0][1]
|
---|
157 | }
|
---|
158 | }
|
---|
159 | }
|
---|
160 |
|
---|
161 | /**
|
---|
162 | * Replies true if this studio has a valid position
|
---|
163 | */
|
---|
164 | def boolean isValidPosition() {
|
---|
165 | return lat != null && lon != null
|
---|
166 | }
|
---|
167 |
|
---|
168 | def boolean isTower() {
|
---|
169 | return notes != null && notes.startsWith("This location was found using GPS at the site")
|
---|
170 | }
|
---|
171 |
|
---|
172 | def boolean isTvStation() {
|
---|
173 | return type == "TV Stations"
|
---|
174 | }
|
---|
175 | }
|
---|
176 |
|
---|
177 |
|
---|
178 | /**
|
---|
179 | * The converter
|
---|
180 | */
|
---|
181 | class Kml2OsmConverter {
|
---|
182 | static final public String RADIO_TV_FILE = "C:/data/projekte/haiti/radiotv/RadioStationsHaitiJan2010.xml"
|
---|
183 | private def xpath = XPathFactory.newInstance().newXPath()
|
---|
184 |
|
---|
185 | def reader
|
---|
186 | def writer
|
---|
187 |
|
---|
188 | def process() {
|
---|
189 | def builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
---|
190 | def doc = builder.parse(new InputSource(reader)).documentElement
|
---|
191 | def markup = new MarkupBuilder(writer)
|
---|
192 | def nodeId = 0
|
---|
193 | markup.getMkp().pi(xml: [version: "1.0", encoding:"UTF-8"])
|
---|
194 | markup.getMkp().comment("""
|
---|
195 | Automatically generated from this list of radio and tv studios in haiti:
|
---|
196 | http://spreadsheets.google.com/pub?key=tZP0wXS4HMLAWEhDlzdW36w&output=txt&output=txt&gid=0&range=kml_output
|
---|
197 |
|
---|
198 | Generated on: ${new SimpleDateFormat().format(new Date())}
|
---|
199 | """)
|
---|
200 | markup.osm(version: "0.6", generator: "Ism2Osm") {
|
---|
201 | xpath.evaluate("//Placemark", doc, XPathConstants.NODESET).each {
|
---|
202 | Node placemark ->
|
---|
203 | def studio = new Studio(placemark)
|
---|
204 | println "Processing studio '${studio.id}' with name '${studio.name}'"
|
---|
205 | if (!studio.isValidPosition()) {
|
---|
206 | println "Error: studio '${studio.id}' doesn't have a valid position. Skipping."
|
---|
207 | return
|
---|
208 | }
|
---|
209 | if (!studio.id) {
|
---|
210 | println "Error: studio '${studio.id}' doesn't have a valid IMS id. Skipping."
|
---|
211 | return
|
---|
212 | }
|
---|
213 | nodeId--
|
---|
214 | node(id:nodeId, version:1, lat: studio.lat, lon: studio.lon) {
|
---|
215 | if (studio.isTower()) {
|
---|
216 | tag(k:"man_made", v:"tower");
|
---|
217 | tag(k:"tower:type", v:"communication")
|
---|
218 | } else {
|
---|
219 | tag(k:"amenity", v:"studio")
|
---|
220 | if (studio.isTvStation()) {
|
---|
221 | tag(k:"type", v:"video")
|
---|
222 | }
|
---|
223 | }
|
---|
224 | if (studio.name) {
|
---|
225 | tag(k:"name", v:studio.name)
|
---|
226 | }
|
---|
227 | if (studio.type) {
|
---|
228 | tag(k:"ims:media_type", v:studio.type)
|
---|
229 | }
|
---|
230 | tag(k:"ims:id", v:studio.id)
|
---|
231 | if (studio.address) {
|
---|
232 | tag(k:"addr", v:studio.address)
|
---|
233 | }
|
---|
234 | if (studio.contact) {
|
---|
235 | tag(k:"contact", v:studio.contact)
|
---|
236 | }
|
---|
237 | if (studio.email) {
|
---|
238 | tag(k: "contact:email", v:studio.email)
|
---|
239 | }
|
---|
240 | if (studio.phone) {
|
---|
241 | tag(k: "phone", v:studio.phone)
|
---|
242 | }
|
---|
243 | if (studio.frequency) {
|
---|
244 | tag(k: "ims:frequency", v:studio.frequency)
|
---|
245 | }
|
---|
246 | if (studio.notes) {
|
---|
247 | tag(k: "note", v:studio.notes)
|
---|
248 | }
|
---|
249 | tag(k:"source_ref", v: "http://spreadsheets.google.com/pub?key=tZP0wXS4HMLAWEhDlzdW36w&output=txt&output=txt&gid=0&range=kml_output")
|
---|
250 | tag(k:"source", "CartONG - http://www.cartong.org")
|
---|
251 | }
|
---|
252 | }
|
---|
253 | }
|
---|
254 |
|
---|
255 | }
|
---|
256 |
|
---|
257 | def usage() {
|
---|
258 | println """
|
---|
259 | groovy ch.guggis.haiti.radiotv.Kml2OsmConverter [options]
|
---|
260 | Options:
|
---|
261 | -h, --help show help information
|
---|
262 | -i, --input-file the input file. Reads from stdin if missing
|
---|
263 | -o, --output-file the output file. Writes to stdout if missing.
|
---|
264 | """
|
---|
265 | }
|
---|
266 |
|
---|
267 | def fail(msg) {
|
---|
268 | println msg
|
---|
269 | usage()
|
---|
270 | System.exit(1)
|
---|
271 | }
|
---|
272 |
|
---|
273 | def processCommandLineOptions(argArray) {
|
---|
274 | def inputFile
|
---|
275 | def outputFile
|
---|
276 | def args = Arrays.asList(argArray)
|
---|
277 | args = args.reverse()
|
---|
278 | def arg = args.pop()
|
---|
279 | while(arg != null) {
|
---|
280 | switch(arg) {
|
---|
281 | case "-i":
|
---|
282 | case "--input-file":
|
---|
283 | inputFile = args.pop()
|
---|
284 | if (inputFile == null) {
|
---|
285 | fail "Error: missing input file"
|
---|
286 | }
|
---|
287 | break
|
---|
288 | case "-o":
|
---|
289 | case "--output-file":
|
---|
290 | outputFile = args.pop()
|
---|
291 | if (outputFile == null) {
|
---|
292 | fail "Error: missing output file"
|
---|
293 | }
|
---|
294 | break
|
---|
295 |
|
---|
296 | case "-h":
|
---|
297 | case "--help":
|
---|
298 | usage()
|
---|
299 | System.exit(0)
|
---|
300 | break
|
---|
301 |
|
---|
302 | default:
|
---|
303 | fail "Illegal argument ${arg}"
|
---|
304 | }
|
---|
305 | arg = args.empty ? null : args.pop()
|
---|
306 | }
|
---|
307 |
|
---|
308 | if (inputFile) {
|
---|
309 | reader = new File(inputFile).newReader("UTF-8")
|
---|
310 | } else {
|
---|
311 | reader = new BufferedReader(new InputStreamReader(System.in, "UTF-8"))
|
---|
312 | }
|
---|
313 | if (outputFile) {
|
---|
314 | writer = new File(outputFile).newWriter("UTF-8")
|
---|
315 | } else {
|
---|
316 | writer = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"))
|
---|
317 | }
|
---|
318 | }
|
---|
319 |
|
---|
320 | static public void main(args) {
|
---|
321 | def task = new Kml2OsmConverter()
|
---|
322 | task.processCommandLineOptions(args)
|
---|
323 | task.process()
|
---|
324 | }
|
---|
325 | }
|
---|