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

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

see #15229 - deprecate Main.pref

  • Property svn:eol-style set to native
File size: 20.0 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.actions.DeleteAction
24import org.openstreetmap.josm.command.DeleteCommand
25import org.openstreetmap.josm.data.Preferences
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.ProjectionRegistry
36import org.openstreetmap.josm.data.projection.Projections
37import org.openstreetmap.josm.gui.NavigatableComponent
38import org.openstreetmap.josm.gui.mappaint.Environment
39import org.openstreetmap.josm.gui.mappaint.MultiCascade
40import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference
41import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource
42import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition
43import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector
44import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser
45import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement
46import org.openstreetmap.josm.gui.mappaint.styleelement.LineElement
47import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement
48import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference
49import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset
50import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader
51import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType
52import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem
53import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem.MatchType
54import org.openstreetmap.josm.io.CachedFile
55import org.openstreetmap.josm.spi.preferences.Config
56import org.openstreetmap.josm.tools.Logging
57import org.openstreetmap.josm.tools.RightAndLefthandTraffic
58import org.openstreetmap.josm.tools.Territories
59import org.openstreetmap.josm.tools.Utils
60
61import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
62
63class TagInfoExtract {
64
65 static def options
66 static String image_dir
67 int josm_svn_revision
68 String input_file
69 MapCSSStyleSource style_source
70 FileWriter output_file
71 String base_dir = "."
72 Set tags = []
73
74 private def cached_svnrev
75
76 /**
77 * Check if a certain tag is supported by the style as node / way / area.
78 */
79 abstract class Checker {
80
81 def tag
82 OsmPrimitive osm
83
84 Checker(tag) {
85 this.tag = tag
86 }
87
88 Environment apply_stylesheet(OsmPrimitive osm) {
89 osm.put(tag[0], tag[1])
90 MultiCascade mc = new MultiCascade()
91
92 Environment env = new Environment(osm, mc, null, style_source)
93 for (def r in style_source.rules) {
94 env.clearSelectorMatchingInformation()
95 if (r.selector.matches(env)) {
96 // ignore selector range
97 if (env.layer == null) {
98 env.layer = "default"
99 }
100 r.execute(env)
101 }
102 }
103 env.layer = "default"
104 return env
105 }
106
107 /**
108 * Create image file from StyleElement.
109 * @return the URL
110 */
111 def create_image(StyleElement elem_style, type, nc) {
112 def img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB)
113 def g = img.createGraphics()
114 g.setClip(0, 0, 16, 16)
115 def renderer = new StyledMapRenderer(g, nc, false)
116 renderer.getSettings(false)
117 elem_style.paintPrimitive(osm, MapPaintSettings.INSTANCE, renderer, false, false, false)
118 def base_url = options.imgurlprefix ? options.imgurlprefix : image_dir
119 def image_name = "${type}_${tag[0]}=${tag[1]}.png"
120 ImageIO.write(img, "png", new File("${image_dir}/${image_name}"))
121 return "${base_url}/${image_name}"
122 }
123
124 /**
125 * Checks, if tag is supported and find URL for image icon in this case.
126 * @param generate_image if true, create or find a suitable image icon and return URL,
127 * if false, just check if tag is supported and return true or false
128 */
129 abstract def find_url(boolean generate_image)
130 }
131
132 @SuppressFBWarnings(value = "MF_CLASS_MASKS_FIELD")
133 class NodeChecker extends Checker {
134 NodeChecker(tag) {
135 super(tag)
136 }
137
138 @Override
139 def find_url(boolean generate_image) {
140 osm = new Node(LatLon.ZERO)
141 def env = apply_stylesheet(osm)
142 def c = env.mc.getCascade("default")
143 def image = c.get("icon-image")
144 if (image) {
145 if (image instanceof IconReference && !image.isDeprecatedIcon()) {
146 return find_image_url(image.iconName)
147 }
148 }
149 }
150 }
151
152 @SuppressFBWarnings(value = "MF_CLASS_MASKS_FIELD")
153 class WayChecker extends Checker {
154 WayChecker(tag) {
155 super(tag)
156 }
157
158 @Override
159 def find_url(boolean generate_image) {
160 osm = new Way()
161 def nc = new NavigatableComponent()
162 def n1 = new Node(nc.getLatLon(2,8))
163 def n2 = new Node(nc.getLatLon(14,8))
164 ((Way)osm).addNode(n1)
165 ((Way)osm).addNode(n2)
166 def env = apply_stylesheet(osm)
167 def les = LineElement.createLine(env)
168 if (les != null) {
169 if (!generate_image) return true
170 return create_image(les, 'way', nc)
171 }
172 }
173 }
174
175 @SuppressFBWarnings(value = "MF_CLASS_MASKS_FIELD")
176 class AreaChecker extends Checker {
177 AreaChecker(tag) {
178 super(tag)
179 }
180
181 @Override
182 def find_url(boolean generate_image) {
183 osm = new Way()
184 def nc = new NavigatableComponent()
185 def n1 = new Node(nc.getLatLon(2,2))
186 def n2 = new Node(nc.getLatLon(14,2))
187 def n3 = new Node(nc.getLatLon(14,14))
188 def n4 = new Node(nc.getLatLon(2,14))
189 ((Way)osm).addNode(n1)
190 ((Way)osm).addNode(n2)
191 ((Way)osm).addNode(n3)
192 ((Way)osm).addNode(n4)
193 ((Way)osm).addNode(n1)
194 def env = apply_stylesheet(osm)
195 def aes = AreaElement.create(env)
196 if (aes != null) {
197 if (!generate_image) return true
198 return create_image(aes, 'area', nc)
199 }
200 }
201 }
202
203 /**
204 * Main method.
205 */
206 static main(def args) {
207 parse_command_line_arguments(args)
208 def script = new TagInfoExtract()
209 if (!options.t || options.t == 'mappaint') {
210 script.run()
211 } else if (options.t == 'presets') {
212 script.run_presets()
213 } else if (options.t == 'external_presets') {
214 script.run_external_presets()
215 } else {
216 System.err.println 'Invalid type ' + options.t
217 if (!options.noexit) {
218 System.exit(1)
219 }
220 }
221
222 if (!options.noexit) {
223 System.exit(0)
224 }
225 }
226
227 /**
228 * Parse command line arguments.
229 */
230 static void parse_command_line_arguments(args) {
231 def cli = new CliBuilder(usage:'taginfoextract.groovy [options] [inputfile]',
232 header:"Options:",
233 footer:"[inputfile] the file to process (optional, default is 'resource://styles/standard/elemstyles.mapcss')")
234 cli.o(args:1, argName: "file", "output file (json), - prints to stdout (default: -)")
235 cli.t(args:1, argName: "type", "the project type to be generated")
236 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)")
237 cli._(longOpt:'imgdir', args:1, argName:"directory", "directory to put the generated images in (default: ./taginfo-img)")
238 cli._(longOpt:'noexit', "don't call System.exit(), for use from Ant script")
239 cli._(longOpt:'svnweb', 'fetch revision of the repository https://svn.openstreetmap.org/ from web and not from the local repository')
240 cli._(longOpt:'imgurlprefix', args:1, argName:'prefix', 'image URLs prefix for generated image files')
241 cli.h(longOpt:'help', "show this help")
242 options = cli.parse(args)
243
244 if (options.h) {
245 cli.usage()
246 System.exit(0)
247 }
248 if (options.arguments().size() > 1) {
249 System.err.println "Error: More than one input file given!"
250 cli.usage()
251 System.exit(-1)
252 }
253 if (options.svnrev) {
254 assert Integer.parseInt(options.svnrev) > 0
255 }
256 image_dir = 'taginfo-img'
257 if (options.imgdir) {
258 image_dir = options.imgdir
259 }
260 def image_dir_file = new File(image_dir)
261 if (!image_dir_file.exists()) {
262 image_dir_file.mkdirs()
263 }
264 }
265
266 void run_presets() {
267 init()
268 def presets = TaggingPresetReader.readAll(input_file, true)
269 def tags = convert_presets(presets, "", true)
270 write_json("JOSM main presets", "Tags supported by the default presets in the OSM editor JOSM", tags)
271 }
272
273 def convert_presets(Iterable<TaggingPreset> presets, String descriptionPrefix, boolean addImages) {
274 def tags = []
275 for (TaggingPreset preset : presets) {
276 for (KeyedItem item : Utils.filteredCollection(preset.data, KeyedItem.class)) {
277 def values
278 switch (MatchType.ofString(item.match)) {
279 case MatchType.KEY_REQUIRED: values = item.getValues(); break;
280 case MatchType.KEY_VALUE_REQUIRED: values = item.getValues(); break;
281 default: values = [];
282 }
283 for (String value : values) {
284 def tag = [
285 description: descriptionPrefix + preset.name,
286 key: item.key,
287 value: value,
288 ]
289 def otypes = preset.types.collect {
290 it == TaggingPresetType.CLOSEDWAY ? "area" :
291 (it == TaggingPresetType.MULTIPOLYGON ? "relation" : it.toString().toLowerCase(Locale.ENGLISH))
292 }
293 if (!otypes.isEmpty()) tag += [object_types: otypes]
294 if (addImages && preset.iconName) tag += [icon_url: find_image_url(preset.iconName)]
295 tags += tag
296 }
297 }
298 }
299 return tags
300 }
301
302 void run_external_presets() {
303 init()
304 TaggingPresetReader.setLoadIcons(false)
305 def sources = new TaggingPresetPreference.TaggingPresetSourceEditor().loadAndGetAvailableSources()
306 def tags = []
307 for (def source : sources) {
308 if (source.url.startsWith("resource")) {
309 // default presets
310 continue;
311 }
312 try {
313 println "Loading ${source.url}"
314 def presets = TaggingPresetReader.readAll(source.url, false)
315 def t = convert_presets(presets, source.title + " ", false)
316 println "Converting ${t.size()} presets of ${source.title}"
317 tags += t
318 } catch (Exception ex) {
319 System.err.println("Skipping ${source.url} due to error")
320 ex.printStackTrace()
321 }
322 }
323 write_json("JOSM user presets", "Tags supported by the user contributed presets in the OSM editor JOSM", tags)
324 }
325
326 void run() {
327 init()
328 parse_style_sheet()
329 collect_tags()
330
331 def tags = tags.collect {
332 def tag = it
333 def types = []
334 def final_url = null
335
336 def node_url = new NodeChecker(tag).find_url(true)
337 if (node_url) {
338 types += 'node'
339 final_url = node_url
340 }
341 def way_url = new WayChecker(tag).find_url(final_url == null)
342 if (way_url) {
343 types += 'way'
344 if (!final_url) {
345 final_url = way_url
346 }
347 }
348 def area_url = new AreaChecker(tag).find_url(final_url == null)
349 if (area_url) {
350 types += 'area'
351 if (!final_url) {
352 final_url = area_url
353 }
354 }
355
356 def obj = [key: tag[0], value: tag[1]]
357 if (types) obj += [object_types: types]
358 if (final_url) obj += [icon_url: final_url]
359 obj
360 }
361
362 write_json("JOSM main mappaint style", "Tags supported by the main mappaint style in the OSM editor JOSM", tags)
363 }
364
365 void write_json(String name, String description, List<Map<String, ?>> tags) {
366 def config = [:]
367 config[JsonGenerator.PRETTY_PRINTING] = output_file == null
368 def writer = output_file != null ? output_file : new StringWriter()
369 def json = Json.createWriterFactory(config).createWriter(writer)
370 try {
371 def project = Json.createObjectBuilder()
372 .add("name", name)
373 .add("description", description)
374 .add("project_url", "https://josm.openstreetmap.de/")
375 .add("icon_url", "https://josm.openstreetmap.de/export/7770/josm/trunk/images/logo_16x16x8.png")
376 .add("contact_name", "JOSM developer team")
377 .add("contact_email", "josm-dev@openstreetmap.org")
378 def jsonTags = Json.createArrayBuilder()
379 for (def t : tags) {
380 def o = Json.createObjectBuilder()
381 for (def e : t.entrySet()) {
382 def val = e.getValue()
383 if (e.getValue() instanceof List) {
384 def arr = Json.createArrayBuilder()
385 for (def v : e.getValue()) {
386 arr.add(v)
387 }
388 val = arr.build()
389 }
390 o.add(e.getKey(), val)
391 }
392 jsonTags.add(o.build())
393 }
394 json.writeObject(Json.createObjectBuilder()
395 .add("data_format", 1)
396 .add("data_updated", DateTimeFormatter.ofPattern("yyyyMMdd'T'hhmmss'Z'").withZone(ZoneId.of("Z")).format(Instant.now()))
397 .add("project", project.build())
398 .add("tags", jsonTags.build())
399 .build())
400 } finally {
401 json.close()
402 }
403
404 if (output_file != null) {
405 output_file.close()
406 } else {
407 print writer.toString()
408 }
409 }
410
411 /**
412 * Initialize the script.
413 */
414 def init() {
415 Logging.setLogLevel(Logging.LEVEL_INFO)
416 Preferences.main().enableSaveOnPut(false)
417 Config.setPreferencesInstance(Preferences.main())
418 Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance())
419 Config.setUrlsProvider(JosmUrls.getInstance())
420 ProjectionRegistry.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.