Ticket #17170: 17170.patch
| File 17170.patch, 45.1 KB (added by , 7 years ago) |
|---|
-
deleted file scripts/TagInfoExtract.groovy
commit c5e399a1b2f55ef2cd43ce5e04d8cc90d3971b02 Author: Simon Legner <Simon.Legner@gmail.com> Date: Fri Jan 4 00:20:34 2019 +0100 fix #17170 - Migrate TagInfoExtract.groovy to Java diff --git a/scripts/TagInfoExtract.groovy b/scripts/TagInfoExtract.groovy deleted file mode 100644 index 4f3f565cb..000000000+ - 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 mappaint8 * groovy -cp dist/josm-custom.jar scripts/taginfoextract.groovy -t presets9 * groovy -cp dist/josm-custom.jar scripts/taginfoextract.groovy -t external_presets10 */11 import java.awt.image.BufferedImage12 import java.nio.file.FileSystems13 import java.nio.file.Files14 import java.nio.file.Path15 import java.time.Instant16 import java.time.ZoneId17 import java.time.format.DateTimeFormatter18 19 import javax.imageio.ImageIO20 import javax.json.Json21 import javax.json.stream.JsonGenerator22 23 import org.openstreetmap.josm.actions.DeleteAction24 import org.openstreetmap.josm.command.DeleteCommand25 import org.openstreetmap.josm.data.Preferences26 import org.openstreetmap.josm.data.Version27 import org.openstreetmap.josm.data.coor.LatLon28 import org.openstreetmap.josm.data.osm.Node29 import org.openstreetmap.josm.data.osm.OsmPrimitive30 import org.openstreetmap.josm.data.osm.Way31 import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings32 import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer33 import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;34 import org.openstreetmap.josm.data.preferences.JosmUrls35 import org.openstreetmap.josm.data.projection.ProjectionRegistry36 import org.openstreetmap.josm.data.projection.Projections37 import org.openstreetmap.josm.gui.NavigatableComponent38 import org.openstreetmap.josm.gui.mappaint.Environment39 import org.openstreetmap.josm.gui.mappaint.MultiCascade40 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference41 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource42 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.SimpleKeyValueCondition43 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector44 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser45 import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement46 import org.openstreetmap.josm.gui.mappaint.styleelement.LineElement47 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement48 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference49 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset50 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader51 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType52 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem53 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem.MatchType54 import org.openstreetmap.josm.io.CachedFile55 import org.openstreetmap.josm.spi.preferences.Config56 import org.openstreetmap.josm.tools.Logging57 import org.openstreetmap.josm.tools.RightAndLefthandTraffic58 import org.openstreetmap.josm.tools.Territories59 import org.openstreetmap.josm.tools.Utils60 61 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings62 63 class TagInfoExtract {64 65 static def options66 static String image_dir67 int josm_svn_revision68 String input_file69 MapCSSStyleSource style_source70 FileWriter output_file71 String base_dir = "."72 Set tags = []73 74 private def cached_svnrev75 76 /**77 * Check if a certain tag is supported by the style as node / way / area.78 */79 abstract class Checker {80 81 def tag82 OsmPrimitive osm83 84 Checker(tag) {85 this.tag = tag86 }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 range97 if (env.layer == null) {98 env.layer = "default"99 }100 r.execute(env)101 }102 }103 env.layer = "default"104 return env105 }106 107 /**108 * Create image file from StyleElement.109 * @return the URL110 */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_dir119 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 false128 */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 @Override139 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 @Override159 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 true170 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 @Override182 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 true198 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.t217 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) > 0255 }256 image_dir = 'taginfo-img'257 if (options.imgdir) {258 image_dir = options.imgdir259 }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 values278 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 += tag296 }297 }298 }299 return tags300 }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 presets310 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 += t318 } 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 = it333 def types = []334 def final_url = null335 336 def node_url = new NodeChecker(tag).find_url(true)337 if (node_url) {338 types += 'node'339 final_url = node_url340 }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_url346 }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_url353 }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 obj360 }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 == null368 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_VERSION430 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 = null440 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_svnrev476 if (options.svnrev) {477 cached_svnrev = Integer.parseInt(options.svnrev)478 return cached_svnrev479 }480 def xml481 if (options.svnweb) {482 xml = "svn info --xml https://svn.openstreetmap.org/applications/share/map-icons/classic.small".execute().text483 } else {484 xml = "svn info --xml ${base_dir}/images/styles/standard/".execute().text485 }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 > 0491 return cached_svnrev492 }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.selector512 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 } -
src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java
diff --git a/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java b/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java index 85164bf34..8f015eefb 100644
a b public void addGui(PreferenceTabbedPane gui) { 181 181 gui.addValidationListener(validationListener); 182 182 } 183 183 184 static class TaggingPresetSourceEditor extends SourceEditor {184 public static class TaggingPresetSourceEditor extends SourceEditor { 185 185 186 186 private static final String ICONPREF = "taggingpreset.icon.sources"; 187 187 188 TaggingPresetSourceEditor() {188 public TaggingPresetSourceEditor() { 189 189 super(SourceType.TAGGING_PRESET, Config.getUrls().getJOSMWebsite()+"/presets", presetSourceProviders, true); 190 190 } 191 191 -
src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java
diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java index 698d56412..d40ce08ed 100644
a b protected static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, Strin 164 164 return returnValue; 165 165 } 166 166 167 /** 168 * Determines whether key or key+value are required. 169 * @return whether key or key+value are required 170 */ 171 public boolean isKeyRequired() { 172 final MatchType type = MatchType.ofString(match); 173 return MatchType.KEY_REQUIRED.equals(type) || MatchType.KEY_VALUE_REQUIRED.equals(type); 174 } 175 167 176 /** 168 177 * Returns the default match. 169 178 * @return the default match -
new file src/org/openstreetmap/josm/tools/TagInfoExtract.java
diff --git a/src/org/openstreetmap/josm/tools/TagInfoExtract.java b/src/org/openstreetmap/josm/tools/TagInfoExtract.java new file mode 100644 index 000000000..5f57a532b
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.tools; 3 4 import java.awt.Graphics2D; 5 import java.awt.image.BufferedImage; 6 import java.io.BufferedReader; 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.io.OutputStream; 10 import java.io.StringWriter; 11 import java.io.UncheckedIOException; 12 import java.io.Writer; 13 import java.nio.file.Files; 14 import java.nio.file.Path; 15 import java.nio.file.Paths; 16 import java.time.Instant; 17 import java.time.ZoneId; 18 import java.time.format.DateTimeFormatter; 19 import java.util.ArrayList; 20 import java.util.Arrays; 21 import java.util.Collection; 22 import java.util.Collections; 23 import java.util.EnumSet; 24 import java.util.List; 25 import java.util.Locale; 26 import java.util.Optional; 27 import java.util.Set; 28 import java.util.stream.Collectors; 29 30 import javax.imageio.ImageIO; 31 import javax.json.Json; 32 import javax.json.JsonArrayBuilder; 33 import javax.json.JsonObjectBuilder; 34 import javax.json.JsonWriter; 35 import javax.json.stream.JsonGenerator; 36 37 import org.openstreetmap.josm.actions.DeleteAction; 38 import org.openstreetmap.josm.command.DeleteCommand; 39 import org.openstreetmap.josm.data.Preferences; 40 import org.openstreetmap.josm.data.Version; 41 import org.openstreetmap.josm.data.coor.LatLon; 42 import org.openstreetmap.josm.data.osm.Node; 43 import org.openstreetmap.josm.data.osm.OsmPrimitive; 44 import org.openstreetmap.josm.data.osm.Tag; 45 import org.openstreetmap.josm.data.osm.Way; 46 import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings; 47 import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer; 48 import org.openstreetmap.josm.data.preferences.JosmBaseDirectories; 49 import org.openstreetmap.josm.data.preferences.JosmUrls; 50 import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry; 51 import org.openstreetmap.josm.data.preferences.sources.SourceEntry; 52 import org.openstreetmap.josm.data.projection.ProjectionRegistry; 53 import org.openstreetmap.josm.data.projection.Projections; 54 import org.openstreetmap.josm.gui.NavigatableComponent; 55 import org.openstreetmap.josm.gui.mappaint.Cascade; 56 import org.openstreetmap.josm.gui.mappaint.Environment; 57 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 58 import org.openstreetmap.josm.gui.mappaint.MultiCascade; 59 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory; 60 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule; 61 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 62 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector; 63 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser; 64 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException; 65 import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement; 66 import org.openstreetmap.josm.gui.mappaint.styleelement.LineElement; 67 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement; 68 import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference; 69 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 70 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader; 71 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 72 import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; 73 import org.openstreetmap.josm.io.CachedFile; 74 import org.openstreetmap.josm.io.OsmTransferException; 75 import org.openstreetmap.josm.spi.preferences.Config; 76 import org.xml.sax.SAXException; 77 78 class TagInfoExtract { 79 80 /** 81 * Main method. 82 */ 83 public static void main(String[] args) throws Exception { 84 TagInfoExtract script = new TagInfoExtract(); 85 script.parseCommandLineArguments(args); 86 switch (script.options.mode) { 87 case MAPPAINT: 88 script.runStyleSheet(); 89 break; 90 case PRESETS: 91 script.runPresets(); 92 break; 93 case EXTERNAL_PRESETS: 94 script.runExternalPresets(); 95 break; 96 default: 97 throw new IllegalStateException("Invalid type " + script.options.mode); 98 } 99 if (!script.options.noexit) { 100 System.exit(0); 101 } 102 } 103 104 enum Mode { 105 MAPPAINT, PRESETS, EXTERNAL_PRESETS 106 } 107 108 private final Options options = new Options(); 109 private MapCSSStyleSource styleSource; 110 111 /** 112 * Parse command line arguments. 113 */ 114 private void parseCommandLineArguments(String[] args) { 115 if (args.length == 1 && "--help".equals(args[0])) { 116 this.usage(); 117 } 118 final OptionParser parser = new OptionParser(getClass().getName()); 119 parser.addArgumentParameter("type", OptionParser.OptionCount.REQUIRED, options::setMode); 120 parser.addArgumentParameter("input", OptionParser.OptionCount.OPTIONAL, options::setInputFile); 121 parser.addArgumentParameter("output", OptionParser.OptionCount.OPTIONAL, options::setOutputFile); 122 parser.addArgumentParameter("imgdir", OptionParser.OptionCount.OPTIONAL, options::setImageDir); 123 parser.addArgumentParameter("imgurlprefix", OptionParser.OptionCount.OPTIONAL, options::setImageUrlPrefix); 124 parser.addFlagParameter("noexit", options::setNoExit); 125 parser.addFlagParameter("help", this::usage); 126 parser.parseOptionsOrExit(Arrays.asList(args)); 127 } 128 129 private void usage() { 130 System.out.println("java " + getClass().getName()); 131 System.out.println(" --type TYPE\tthe project type to be generated: " + Arrays.toString(Mode.values())); 132 System.out.println(" --input FILE\tthe input file to use (overrides defaults for types mappaint, presets)"); 133 System.out.println(" --output FILE\tthe output file to use (defaults to STDOUT)"); 134 System.out.println(" --imgdir DIRECTORY\tthe directory to put the generated images in (default: " + options.imageDir + ")"); 135 System.out.println(" --imgurlprefix STRING\timage URLs prefix for generated image files (public path on webserver)"); 136 System.out.println(" --noexit\tdo not call System.exit(), for use from Ant script"); 137 System.out.println(" --help\tshow this help"); 138 System.exit(0); 139 } 140 141 private static class Options { 142 Mode mode; 143 int josmSvnRevision = Version.getInstance().getVersion(); 144 Path baseDir = Paths.get(""); 145 Path imageDir = Paths.get("taginfo-img"); 146 String imageUrlPrefix; 147 CachedFile inputFile; 148 Path outputFile; 149 boolean noexit; 150 151 void setMode(String value) { 152 mode = Mode.valueOf(value.toUpperCase(Locale.ENGLISH)); 153 switch (mode) { 154 case MAPPAINT: 155 inputFile = new CachedFile("resource://styles/standard/elemstyles.mapcss"); 156 break; 157 case PRESETS: 158 inputFile = new CachedFile("resource://data/defaultpresets.xml"); 159 break; 160 default: 161 inputFile = null; 162 } 163 } 164 165 void setInputFile(String value) { 166 inputFile = new CachedFile(value); 167 } 168 169 void setOutputFile(String value) { 170 outputFile = Paths.get(value); 171 } 172 173 void setImageDir(String value) { 174 imageDir = Paths.get(value); 175 try { 176 Files.createDirectories(imageDir); 177 } catch (IOException e) { 178 throw new UncheckedIOException(e); 179 } 180 } 181 182 void setImageUrlPrefix(String value) { 183 imageUrlPrefix = value; 184 } 185 186 void setNoExit() { 187 noexit = true; 188 } 189 } 190 private void runPresets() throws IOException, SAXException { 191 init(); 192 try (BufferedReader reader = options.inputFile.getContentReader()) { 193 Collection<TaggingPreset> presets = TaggingPresetReader.readAll(reader, true); 194 List<TagInfoTag> tags = convertPresets(presets, "", true); 195 writeJson("JOSM main presets", "Tags supported by the default presets in the OSM editor JOSM", tags); 196 } 197 } 198 199 private List<TagInfoTag> convertPresets(Iterable<TaggingPreset> presets, String descriptionPrefix, boolean addImages) { 200 final List<TagInfoTag> tags = new ArrayList<>(); 201 for (TaggingPreset preset : presets) { 202 for (KeyedItem item : Utils.filteredCollection(preset.data, KeyedItem.class)) { 203 final Iterable<String> values = item.isKeyRequired() 204 ? item.getValues() 205 : Collections.emptyList(); 206 for (String value : values) { 207 final Set<TagInfoTag.Type> types = preset.types.stream() 208 .map(it -> it.equals(TaggingPresetType.CLOSEDWAY) ? TagInfoTag.Type.AREA : it.equals(TaggingPresetType.MULTIPOLYGON) ? TagInfoTag.Type.RELATION : TagInfoTag.Type.valueOf(it.toString())) 209 .collect(Collectors.toCollection(() -> EnumSet.noneOf(TagInfoTag.Type.class))); 210 tags.add(new TagInfoTag(descriptionPrefix + preset.getName(), item.key, value, types, 211 addImages && preset.iconName != null ? findImageUrl(preset.iconName) : null)); 212 } 213 } 214 } 215 216 return tags; 217 } 218 219 private void runExternalPresets() throws IOException, OsmTransferException, SAXException { 220 init(); 221 TaggingPresetReader.setLoadIcons(false); 222 final Collection<ExtendedSourceEntry> sources = new TaggingPresetPreference.TaggingPresetSourceEditor().loadAndGetAvailableSources(); 223 final List<TagInfoTag> tags = new ArrayList<>(); 224 for (SourceEntry source : sources) { 225 if (source.url.startsWith("resource")) { 226 // default presets 227 continue; 228 } 229 try { 230 System.out.println("Loading " + source.url); 231 Collection<TaggingPreset> presets = TaggingPresetReader.readAll(source.url, false); 232 final List<TagInfoTag> t = convertPresets(presets, source.title + " ", false); 233 System.out.println("Converting " + t.size() + " presets of " + source.title); 234 tags.addAll(t); 235 } catch (Exception ex) { 236 System.err.println("Skipping " + source.url + " due to error"); 237 ex.printStackTrace(); 238 } 239 240 } 241 242 writeJson("JOSM user presets", "Tags supported by the user contributed presets in the OSM editor JOSM", tags); 243 } 244 245 private void runStyleSheet() throws IOException, ParseException { 246 init(); 247 parseStyleSheet(); 248 final List<TagInfoTag> tags = convertStyleSheet(); 249 writeJson("JOSM main mappaint style", "Tags supported by the main mappaint style in the OSM editor JOSM", tags); 250 } 251 252 private static class TagInfoTag { 253 final String description; 254 final String key; 255 final String value; 256 final Set<Type> objectTypes; 257 final String iconURL; 258 259 TagInfoTag(String description, String key, String value, Set<Type> objectTypes, String iconURL) { 260 this.description = description; 261 this.key = key; 262 this.value = value; 263 this.objectTypes = objectTypes; 264 this.iconURL = iconURL; 265 } 266 267 JsonObjectBuilder toJson() { 268 final JsonObjectBuilder object = Json.createObjectBuilder(); 269 if (description != null) { 270 object.add("description", description); 271 } 272 object.add("key", key); 273 object.add("value", value); 274 if ((!objectTypes.isEmpty())) { 275 final JsonArrayBuilder types = Json.createArrayBuilder(); 276 objectTypes.stream().map(Enum::name).map(String::toLowerCase).forEach(types::add); 277 object.add("object_types", types); 278 } 279 if (iconURL != null ) { 280 object.add("icon_url", iconURL); 281 } 282 return object; 283 } 284 285 enum Type { 286 NODE, WAY, AREA, RELATION 287 } 288 } 289 290 private void writeJson(String name, String description, Iterable<TagInfoTag> tags) throws IOException { 291 try (Writer writer = options.outputFile != null ? Files.newBufferedWriter(options.outputFile) : new StringWriter(); 292 JsonWriter json = Json 293 .createWriterFactory(Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true)) 294 .createWriter(writer)) { 295 JsonObjectBuilder project = Json.createObjectBuilder() 296 .add("name", name) 297 .add("description", description) 298 .add("project_url", "https://josm.openstreetmap.de/") 299 .add("icon_url", "https://josm.openstreetmap.de/export/7770/josm/trunk/images/logo_16x16x8.png") 300 .add("contact_name", "JOSM developer team") 301 .add("contact_email", "josm-dev@openstreetmap.org"); 302 final JsonArrayBuilder jsonTags = Json.createArrayBuilder(); 303 for (TagInfoTag t : tags) { 304 jsonTags.add(t.toJson()); 305 } 306 json.writeObject(Json.createObjectBuilder() 307 .add("data_format", 1) 308 .add("data_updated", DateTimeFormatter.ofPattern("yyyyMMdd'T'hhmmss'Z'").withZone(ZoneId.of("Z")).format(Instant.now())) 309 .add("project", project) 310 .add("tags", jsonTags) 311 .build()); 312 if (options.outputFile == null) { 313 System.out.println(writer.toString()); 314 } 315 } 316 } 317 318 /** 319 * Initialize the script. 320 */ 321 private void init() throws IOException { 322 Logging.setLogLevel(Logging.LEVEL_INFO); 323 Preferences.main().enableSaveOnPut(false); 324 Config.setPreferencesInstance(Preferences.main()); 325 Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance()); 326 Config.setUrlsProvider(JosmUrls.getInstance()); 327 ProjectionRegistry.setProjection(Projections.getProjectionByCode("EPSG:3857")); 328 Path tmpdir = Files.createTempDirectory(options.baseDir, "pref"); 329 tmpdir.toFile().deleteOnExit(); 330 System.setProperty("josm.home", tmpdir.toString()); 331 DeleteCommand.setDeletionCallback(DeleteAction.defaultDeletionCallback); 332 Territories.initialize(); 333 RightAndLefthandTraffic.initialize(); 334 } 335 336 /** 337 * Determine full image url (can refer to JOSM or OSM repository). 338 */ 339 private String findImageUrl(String path) { 340 final Path f = options.baseDir.resolve("images").resolve(path); 341 if (Files.exists(f)) { 342 return "https://josm.openstreetmap.de/export/" + options.josmSvnRevision + "/josm/trunk/images/" + path; 343 } 344 throw new IllegalStateException("Cannot find image url for " + path); 345 } 346 347 /** 348 * Read the style sheet file and parse the MapCSS code. 349 */ 350 private void parseStyleSheet() throws IOException, ParseException { 351 try (InputStream stream = options.inputFile.getInputStream()) { 352 MapCSSParser parser = new MapCSSParser(stream, "UTF-8", MapCSSParser.LexicalState.DEFAULT); 353 styleSource = new MapCSSStyleSource(""); 354 styleSource.url = ""; 355 parser.sheet(styleSource); 356 } 357 } 358 359 /** 360 * Collect all the tag from the style sheet. 361 */ 362 private List<TagInfoTag> convertStyleSheet() { 363 return styleSource.rules.stream() 364 .map(rule -> rule.selector) 365 .filter(Selector.GeneralSelector.class::isInstance) 366 .map(Selector.GeneralSelector.class::cast) 367 .map(Selector.AbstractSelector::getConditions) 368 .flatMap(Collection::stream) 369 .filter(ConditionFactory.SimpleKeyValueCondition.class::isInstance) 370 .map(ConditionFactory.SimpleKeyValueCondition.class::cast) 371 .map(condition -> condition.asTag(null)) 372 .distinct() 373 .map(tag -> { 374 String iconUrl = null; 375 final EnumSet<TagInfoTag.Type> types = EnumSet.noneOf(TagInfoTag.Type.class); 376 Optional<String> nodeUrl = new NodeChecker(tag).findUrl(true); 377 if (nodeUrl.isPresent()) { 378 iconUrl = nodeUrl.get(); 379 types.add(TagInfoTag.Type.NODE); 380 } 381 Optional<String> wayUrl = new WayChecker(tag).findUrl(iconUrl == null); 382 if (wayUrl.isPresent()) { 383 if (iconUrl == null) { 384 iconUrl = wayUrl.get(); 385 } 386 types.add(TagInfoTag.Type.WAY); 387 } 388 Optional<String> areaUrl = new AreaChecker(tag).findUrl(iconUrl == null); 389 if (areaUrl.isPresent()) { 390 if (iconUrl == null) { 391 iconUrl = areaUrl.get(); 392 } 393 types.add(TagInfoTag.Type.AREA); 394 } 395 return new TagInfoTag(null, tag.getKey(), tag.getValue(), types, iconUrl); 396 }) 397 .collect(Collectors.toList()); 398 } 399 400 /** 401 * Check if a certain tag is supported by the style as node / way / area. 402 */ 403 private abstract class Checker { 404 Checker(Tag tag) { 405 this.tag = tag; 406 } 407 408 Environment applyStylesheet(OsmPrimitive osm) { 409 osm.put(tag); 410 MultiCascade mc = new MultiCascade(); 411 412 Environment env = new Environment(osm, mc, null, styleSource); 413 for (MapCSSRule r : styleSource.rules) { 414 env.clearSelectorMatchingInformation(); 415 if (r.selector.matches(env)) { 416 // ignore selector range 417 if (env.layer == null) { 418 env.layer = "default"; 419 } 420 r.execute(env); 421 } 422 } 423 env.layer = "default"; 424 return env; 425 } 426 427 /** 428 * Create image file from StyleElement. 429 * 430 * @return the URL 431 */ 432 String createImage(StyleElement elem_style, final String type, NavigatableComponent nc) { 433 BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); 434 Graphics2D g = img.createGraphics(); 435 g.setClip(0, 0, 16, 16); 436 StyledMapRenderer renderer = new StyledMapRenderer(g, nc, false); 437 renderer.getSettings(false); 438 elem_style.paintPrimitive(osm, MapPaintSettings.INSTANCE, renderer, false, false, false); 439 final String imageName = type + "_" + tag + ".png"; 440 try (OutputStream out = Files.newOutputStream(options.imageDir.resolve(imageName))) { 441 ImageIO.write(img, "png", out); 442 } catch (IOException e) { 443 throw new UncheckedIOException(e); 444 } 445 final String baseUrl = options.imageUrlPrefix != null ? options.imageUrlPrefix : options.imageDir.toString(); 446 return baseUrl + "/" + imageName; 447 } 448 449 /** 450 * Checks, if tag is supported and find URL for image icon in this case. 451 * 452 * @param generateImage if true, create or find a suitable image icon and return URL, 453 * if false, just check if tag is supported and return true or false 454 */ 455 abstract Optional<String> findUrl(boolean generateImage); 456 457 protected Tag tag; 458 protected OsmPrimitive osm; 459 } 460 461 private class NodeChecker extends Checker { 462 NodeChecker(Tag tag) { 463 super(tag); 464 } 465 466 @Override 467 Optional<String> findUrl(boolean generateImage) { 468 this.osm = new Node(LatLon.ZERO); 469 Environment env = applyStylesheet(osm); 470 Cascade c = env.mc.getCascade("default"); 471 Object image = c.get("icon-image"); 472 if (image instanceof MapPaintStyles.IconReference && !((MapPaintStyles.IconReference) image).isDeprecatedIcon()) { 473 return Optional.of(findImageUrl(((MapPaintStyles.IconReference) image).iconName)); 474 } 475 return Optional.empty(); 476 } 477 478 } 479 480 private class WayChecker extends Checker { 481 WayChecker(Tag tag) { 482 super(tag); 483 } 484 485 @Override 486 Optional<String> findUrl(boolean generateImage) { 487 this.osm = new Way(); 488 NavigatableComponent nc = new NavigatableComponent(); 489 Node n1 = new Node(nc.getLatLon(2, 8)); 490 Node n2 = new Node(nc.getLatLon(14, 8)); 491 ((Way) osm).addNode(n1); 492 ((Way) osm).addNode(n2); 493 Environment env = applyStylesheet(osm); 494 LineElement les = LineElement.createLine(env); 495 if (les != null) { 496 if (!generateImage) return Optional.of(""); 497 return Optional.of(createImage(les, "way", nc)); 498 } 499 return Optional.empty(); 500 } 501 502 } 503 504 private class AreaChecker extends Checker { 505 AreaChecker(Tag tag) { 506 super(tag); 507 } 508 509 @Override 510 Optional<String> findUrl(boolean generateImage) { 511 this.osm = new Way(); 512 NavigatableComponent nc = new NavigatableComponent(); 513 Node n1 = new Node(nc.getLatLon(2, 2)); 514 Node n2 = new Node(nc.getLatLon(14, 2)); 515 Node n3 = new Node(nc.getLatLon(14, 14)); 516 Node n4 = new Node(nc.getLatLon(2, 14)); 517 ((Way) osm).addNode(n1); 518 ((Way) osm).addNode(n2); 519 ((Way) osm).addNode(n3); 520 ((Way) osm).addNode(n4); 521 ((Way) osm).addNode(n1); 522 Environment env = applyStylesheet(osm); 523 AreaElement aes = AreaElement.create(env); 524 if (aes != null) { 525 if (!generateImage) return Optional.of(""); 526 return Optional.of(createImage(aes, "area", nc)); 527 } 528 return Optional.empty(); 529 } 530 531 } 532 }
