source: josm/trunk/scripts/TagInfoExtract.groovy@ 14119

Last change on this file since 14119 was 14119, checked in by Don-vip, 6 years ago

see #15229 - deprecate all Main methods returning an URL

  • Property svn:eol-style set to native
File size: 19.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2/**
3 * Extracts tag information for the taginfo project.
4 *
5 * Run from the base directory of a JOSM checkout:
6 *
7 * groovy -cp dist/josm-custom.jar scripts/taginfoextract.groovy -t mappaint
8 * groovy -cp dist/josm-custom.jar scripts/taginfoextract.groovy -t presets
9 * groovy -cp dist/josm-custom.jar scripts/taginfoextract.groovy -t external_presets
10 */
11import java.awt.image.BufferedImage
12import java.nio.file.FileSystems
13import java.nio.file.Files
14import java.nio.file.Path
15import java.time.Instant
16import java.time.ZoneId
17import java.time.format.DateTimeFormatter
18
19import javax.imageio.ImageIO
20import javax.json.Json
21import javax.json.stream.JsonGenerator
22
23import org.openstreetmap.josm.Main
24import org.openstreetmap.josm.actions.DeleteAction
25import org.openstreetmap.josm.command.DeleteCommand
26import org.openstreetmap.josm.data.Version
27import org.openstreetmap.josm.data.coor.LatLon
28import org.openstreetmap.josm.data.osm.Node
29import org.openstreetmap.josm.data.osm.OsmPrimitive
30import org.openstreetmap.josm.data.osm.Way
31import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings
32import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer
33import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
34import org.openstreetmap.josm.data.preferences.JosmUrls
35import org.openstreetmap.josm.data.projection.Projections
36import org.openstreetmap.josm.gui.NavigatableComponent
37import org.openstreetmap.josm.gui.mappaint.Environment
38import org.openstreetmap.josm.gui.mappaint.MultiCascade
39import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference
40import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource
41import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition
42import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector
43import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser
44import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement
45import org.openstreetmap.josm.gui.mappaint.styleelement.LineElement
46import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement
47import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference
48import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset
49import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader
50import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType
51import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem
52import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem.MatchType
53import org.openstreetmap.josm.io.CachedFile
54import org.openstreetmap.josm.spi.preferences.Config
55import org.openstreetmap.josm.tools.Logging
56import org.openstreetmap.josm.tools.RightAndLefthandTraffic
57import org.openstreetmap.josm.tools.Territories
58import org.openstreetmap.josm.tools.Utils
59
60import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
61
62class TagInfoExtract {
63
64 static def options
65 static String image_dir
66 int josm_svn_revision
67 String input_file
68 MapCSSStyleSource style_source
69 FileWriter output_file
70 String base_dir = "."
71 Set tags = []
72
73 private def cached_svnrev
74
75 /**
76 * Check if a certain tag is supported by the style as node / way / area.
77 */
78 abstract class Checker {
79
80 def tag
81 OsmPrimitive osm
82
83 Checker(tag) {
84 this.tag = tag
85 }
86
87 Environment apply_stylesheet(OsmPrimitive osm) {
88 osm.put(tag[0], tag[1])
89 MultiCascade mc = new MultiCascade()
90
91 Environment env = new Environment(osm, mc, null, style_source)
92 for (def r in style_source.rules) {
93 env.clearSelectorMatchingInformation()
94 if (r.selector.matches(env)) {
95 // ignore selector range
96 if (env.layer == null) {
97 env.layer = "default"
98 }
99 r.execute(env)
100 }
101 }
102 env.layer = "default"
103 return env
104 }
105
106 /**
107 * Create image file from StyleElement.
108 * @return the URL
109 */
110 def create_image(StyleElement elem_style, type, nc) {
111 def img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB)
112 def g = img.createGraphics()
113 g.setClip(0, 0, 16, 16)
114 def renderer = new StyledMapRenderer(g, nc, false)
115 renderer.getSettings(false)
116 elem_style.paintPrimitive(osm, MapPaintSettings.INSTANCE, renderer, false, false, false)
117 def base_url = options.imgurlprefix ? options.imgurlprefix : image_dir
118 def image_name = "${type}_${tag[0]}=${tag[1]}.png"
119 ImageIO.write(img, "png", new File("${image_dir}/${image_name}"))
120 return "${base_url}/${image_name}"
121 }
122
123 /**
124 * Checks, if tag is supported and find URL for image icon in this case.
125 * @param generate_image if true, create or find a suitable image icon and return URL,
126 * if false, just check if tag is supported and return true or false
127 */
128 abstract def find_url(boolean generate_image)
129 }
130
131 @SuppressFBWarnings(value = "MF_CLASS_MASKS_FIELD")
132 class NodeChecker extends Checker {
133 NodeChecker(tag) {
134 super(tag)
135 }
136
137 @Override
138 def find_url(boolean generate_image) {
139 osm = new Node(LatLon.ZERO)
140 def env = apply_stylesheet(osm)
141 def c = env.mc.getCascade("default")
142 def image = c.get("icon-image")
143 if (image) {
144 if (image instanceof IconReference && !image.isDeprecatedIcon()) {
145 return find_image_url(image.iconName)
146 }
147 }
148 }
149 }
150
151 @SuppressFBWarnings(value = "MF_CLASS_MASKS_FIELD")
152 class WayChecker extends Checker {
153 WayChecker(tag) {
154 super(tag)
155 }
156
157 @Override
158 def find_url(boolean generate_image) {
159 osm = new Way()
160 def nc = new NavigatableComponent()
161 def n1 = new Node(nc.getLatLon(2,8))
162 def n2 = new Node(nc.getLatLon(14,8))
163 ((Way)osm).addNode(n1)
164 ((Way)osm).addNode(n2)
165 def env = apply_stylesheet(osm)
166 def les = LineElement.createLine(env)
167 if (les != null) {
168 if (!generate_image) return true
169 return create_image(les, 'way', nc)
170 }
171 }
172 }
173
174 @SuppressFBWarnings(value = "MF_CLASS_MASKS_FIELD")
175 class AreaChecker extends Checker {
176 AreaChecker(tag) {
177 super(tag)
178 }
179
180 @Override
181 def find_url(boolean generate_image) {
182 osm = new Way()
183 def nc = new NavigatableComponent()
184 def n1 = new Node(nc.getLatLon(2,2))
185 def n2 = new Node(nc.getLatLon(14,2))
186 def n3 = new Node(nc.getLatLon(14,14))
187 def n4 = new Node(nc.getLatLon(2,14))
188 ((Way)osm).addNode(n1)
189 ((Way)osm).addNode(n2)
190 ((Way)osm).addNode(n3)
191 ((Way)osm).addNode(n4)
192 ((Way)osm).addNode(n1)
193 def env = apply_stylesheet(osm)
194 def aes = AreaElement.create(env)
195 if (aes != null) {
196 if (!generate_image) return true
197 return create_image(aes, 'area', nc)
198 }
199 }
200 }
201
202 /**
203 * Main method.
204 */
205 static main(def args) {
206 parse_command_line_arguments(args)
207 def script = new TagInfoExtract()
208 if (!options.t || options.t == 'mappaint') {
209 script.run()
210 } else if (options.t == 'presets') {
211 script.run_presets()
212 } else if (options.t == 'external_presets') {
213 script.run_external_presets()
214 } else {
215 System.err.println 'Invalid type ' + options.t
216 if (!options.noexit) {
217 System.exit(1)
218 }
219 }
220
221 if (!options.noexit) {
222 System.exit(0)
223 }
224 }
225
226 /**
227 * Parse command line arguments.
228 */
229 static void parse_command_line_arguments(args) {
230 def cli = new CliBuilder(usage:'taginfoextract.groovy [options] [inputfile]',
231 header:"Options:",
232 footer:"[inputfile] the file to process (optional, default is 'resource://styles/standard/elemstyles.mapcss')")
233 cli.o(args:1, argName: "file", "output file (json), - prints to stdout (default: -)")
234 cli.t(args:1, argName: "type", "the project type to be generated")
235 cli._(longOpt:'svnrev', args:1, argName:"revision", "corresponding revision of the repository https://svn.openstreetmap.org/ (optional, current revision is read from the local checkout or from the web if not given, see --svnweb)")
236 cli._(longOpt:'imgdir', args:1, argName:"directory", "directory to put the generated images in (default: ./taginfo-img)")
237 cli._(longOpt:'noexit', "don't call System.exit(), for use from Ant script")
238 cli._(longOpt:'svnweb', 'fetch revision of the repository https://svn.openstreetmap.org/ from web and not from the local repository')
239 cli._(longOpt:'imgurlprefix', args:1, argName:'prefix', 'image URLs prefix for generated image files')
240 cli.h(longOpt:'help', "show this help")
241 options = cli.parse(args)
242
243 if (options.h) {
244 cli.usage()
245 System.exit(0)
246 }
247 if (options.arguments().size() > 1) {
248 System.err.println "Error: More than one input file given!"
249 cli.usage()
250 System.exit(-1)
251 }
252 if (options.svnrev) {
253 assert Integer.parseInt(options.svnrev) > 0
254 }
255 image_dir = 'taginfo-img'
256 if (options.imgdir) {
257 image_dir = options.imgdir
258 }
259 def image_dir_file = new File(image_dir)
260 if (!image_dir_file.exists()) {
261 image_dir_file.mkdirs()
262 }
263 }
264
265 void run_presets() {
266 init()
267 def presets = TaggingPresetReader.readAll(input_file, true)
268 def tags = convert_presets(presets, "", true)
269 write_json("JOSM main presets", "Tags supported by the default presets in the OSM editor JOSM", tags)
270 }
271
272 def convert_presets(Iterable<TaggingPreset> presets, String descriptionPrefix, boolean addImages) {
273 def tags = []
274 for (TaggingPreset preset : presets) {
275 for (KeyedItem item : Utils.filteredCollection(preset.data, KeyedItem.class)) {
276 def values
277 switch (MatchType.ofString(item.match)) {
278 case MatchType.KEY_REQUIRED: values = item.getValues(); break;
279 case MatchType.KEY_VALUE_REQUIRED: values = item.getValues(); break;
280 default: values = [];
281 }
282 for (String value : values) {
283 def tag = [
284 description: descriptionPrefix + preset.name,
285 key: item.key,
286 value: value,
287 ]
288 def otypes = preset.types.collect {
289 it == TaggingPresetType.CLOSEDWAY ? "area" :
290 (it == TaggingPresetType.MULTIPOLYGON ? "relation" : it.toString().toLowerCase(Locale.ENGLISH))
291 }
292 if (!otypes.isEmpty()) tag += [object_types: otypes]
293 if (addImages && preset.iconName) tag += [icon_url: find_image_url(preset.iconName)]
294 tags += tag
295 }
296 }
297 }
298 return tags
299 }
300
301 void run_external_presets() {
302 init()
303 TaggingPresetReader.setLoadIcons(false)
304 def sources = new TaggingPresetPreference.TaggingPresetSourceEditor().loadAndGetAvailableSources()
305 def tags = []
306 for (def source : sources) {
307 if (source.url.startsWith("resource")) {
308 // default presets
309 continue;
310 }
311 try {
312 println "Loading ${source.url}"
313 def presets = TaggingPresetReader.readAll(source.url, false)
314 def t = convert_presets(presets, source.title + " ", false)
315 println "Converting ${t.size()} presets of ${source.title}"
316 tags += t
317 } catch (Exception ex) {
318 System.err.println("Skipping ${source.url} due to error")
319 ex.printStackTrace()
320 }
321 }
322 write_json("JOSM user presets", "Tags supported by the user contributed presets in the OSM editor JOSM", tags)
323 }
324
325 void run() {
326 init()
327 parse_style_sheet()
328 collect_tags()
329
330 def tags = tags.collect {
331 def tag = it
332 def types = []
333 def final_url = null
334
335 def node_url = new NodeChecker(tag).find_url(true)
336 if (node_url) {
337 types += 'node'
338 final_url = node_url
339 }
340 def way_url = new WayChecker(tag).find_url(final_url == null)
341 if (way_url) {
342 types += 'way'
343 if (!final_url) {
344 final_url = way_url
345 }
346 }
347 def area_url = new AreaChecker(tag).find_url(final_url == null)
348 if (area_url) {
349 types += 'area'
350 if (!final_url) {
351 final_url = area_url
352 }
353 }
354
355 def obj = [key: tag[0], value: tag[1]]
356 if (types) obj += [object_types: types]
357 if (final_url) obj += [icon_url: final_url]
358 obj
359 }
360
361 write_json("JOSM main mappaint style", "Tags supported by the main mappaint style in the OSM editor JOSM", tags)
362 }
363
364 void write_json(String name, String description, List<Map<String, ?>> tags) {
365 def config = [:]
366 config[JsonGenerator.PRETTY_PRINTING] = output_file == null
367 def writer = output_file != null ? output_file : new StringWriter()
368 def json = Json.createWriterFactory(config).createWriter(writer)
369 try {
370 def project = Json.createObjectBuilder()
371 .add("name", name)
372 .add("description", description)
373 .add("project_url", "https://josm.openstreetmap.de/")
374 .add("icon_url", "https://josm.openstreetmap.de/export/7770/josm/trunk/images/logo_16x16x8.png")
375 .add("contact_name", "JOSM developer team")
376 .add("contact_email", "josm-dev@openstreetmap.org")
377 def jsonTags = Json.createArrayBuilder()
378 for (def t : tags) {
379 def o = Json.createObjectBuilder()
380 for (def e : t.entrySet()) {
381 def val = e.getValue()
382 if (e.getValue() instanceof List) {
383 def arr = Json.createArrayBuilder()
384 for (def v : e.getValue()) {
385 arr.add(v)
386 }
387 val = arr.build()
388 }
389 o.add(e.getKey(), val)
390 }
391 jsonTags.add(o.build())
392 }
393 json.writeObject(Json.createObjectBuilder()
394 .add("data_format", 1)
395 .add("data_updated", DateTimeFormatter.ofPattern("yyyyMMdd'T'hhmmss'Z'").withZone(ZoneId.of("Z")).format(Instant.now()))
396 .add("project", project.build())
397 .add("tags", jsonTags.build())
398 .build())
399 } finally {
400 json.close()
401 }
402
403 if (output_file != null) {
404 output_file.close()
405 } else {
406 print writer.toString()
407 }
408 }
409
410 /**
411 * Initialize the script.
412 */
413 def init() {
414 Main.determinePlatformHook()
415 Logging.setLogLevel(Logging.LEVEL_INFO)
416 Main.pref.enableSaveOnPut(false)
417 Config.setPreferencesInstance(Main.pref)
418 Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance())
419 Config.setUrlsProvider(JosmUrls.getInstance())
420 Main.setProjection(Projections.getProjectionByCode("EPSG:3857"))
421 Path tmpdir = Files.createTempDirectory(FileSystems.getDefault().getPath(base_dir), "pref")
422 tmpdir.toFile().deleteOnExit()
423 System.setProperty("josm.home", tmpdir.toString())
424 DeleteCommand.setDeletionCallback(DeleteAction.defaultDeletionCallback)
425 Territories.initialize()
426 RightAndLefthandTraffic.initialize()
427
428 josm_svn_revision = Version.getInstance().getVersion()
429 assert josm_svn_revision != Version.JOSM_UNKNOWN_VERSION
430
431 if (options.arguments().size() == 0 && (!options.t || options.t == 'mappaint')) {
432 input_file = "resource://styles/standard/elemstyles.mapcss"
433 } else if (options.arguments().size() == 0 && options.t == 'presets') {
434 input_file = "resource://data/defaultpresets.xml"
435 } else {
436 input_file = options.arguments()[0]
437 }
438
439 output_file = null
440 if (options.o && options.o != "-") {
441 output_file = new FileWriter(options.o)
442 }
443 }
444
445 /**
446 * Determine full image url (can refer to JOSM or OSM repository).
447 */
448 def find_image_url(String path) {
449 def f = new File("${base_dir}/images/styles/standard/${path}")
450 if (f.exists()) {
451 def rev = osm_svn_revision()
452 return "https://trac.openstreetmap.org/export/${rev}/subversion/applications/share/map-icons/classic.small/${path}"
453 }
454 f = new File("${base_dir}/images/${path}")
455 if (f.exists()) {
456 if (path.startsWith("images/styles/standard/")) {
457 path = path.substring("images/styles/standard/".length())
458 def rev = osm_svn_revision()
459 return "https://trac.openstreetmap.org/export/${rev}/subversion/applications/share/map-icons/classic.small/${path}"
460 } else if (path.startsWith("styles/standard/")) {
461 path = path.substring("styles/standard/".length())
462 def rev = osm_svn_revision()
463 return "https://trac.openstreetmap.org/export/${rev}/subversion/applications/share/map-icons/classic.small/${path}"
464 } else {
465 return "https://josm.openstreetmap.de/export/${josm_svn_revision}/josm/trunk/images/${path}"
466 }
467 }
468 assert false, "Cannot find image url for ${path}"
469 }
470
471 /**
472 * Get revision for the repository https://svn.openstreetmap.org.
473 */
474 def osm_svn_revision() {
475 if (cached_svnrev != null) return cached_svnrev
476 if (options.svnrev) {
477 cached_svnrev = Integer.parseInt(options.svnrev)
478 return cached_svnrev
479 }
480 def xml
481 if (options.svnweb) {
482 xml = "svn info --xml https://svn.openstreetmap.org/applications/share/map-icons/classic.small".execute().text
483 } else {
484 xml = "svn info --xml ${base_dir}/images/styles/standard/".execute().text
485 }
486
487 def svninfo = new XmlParser().parseText(xml)
488 def rev = svninfo.entry.'@revision'[0]
489 cached_svnrev = Integer.parseInt(rev)
490 assert cached_svnrev > 0
491 return cached_svnrev
492 }
493
494 /**
495 * Read the style sheet file and parse the MapCSS code.
496 */
497 def parse_style_sheet() {
498 def file = new CachedFile(input_file)
499 def stream = file.getInputStream()
500 def parser = new MapCSSParser(stream, "UTF-8", MapCSSParser.LexicalState.DEFAULT)
501 style_source = new MapCSSStyleSource("")
502 style_source.url = ""
503 parser.sheet(style_source)
504 }
505
506 /**
507 * Collect all the tag from the style sheet.
508 */
509 def collect_tags() {
510 for (rule in style_source.rules) {
511 def selector = rule.selector
512 if (selector instanceof GeneralSelector) {
513 def conditions = selector.getConditions()
514 for (cond in conditions) {
515 if (cond instanceof SimpleKeyValueCondition) {
516 tags.add([cond.k, cond.v])
517 }
518 }
519 }
520 }
521 }
522}
Note: See TracBrowser for help on using the repository browser.