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

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

more changes required by Groovy 2.5.0

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