source: josm/trunk/src/org/openstreetmap/josm/gui/util/Tag2Link.java@ 15679

Last change on this file since 15679 was 15679, checked in by simon04, 4 years ago

see #13901 see #18542 - Obtain tag2link rules from OSM Sophox

File size: 6.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.util;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.io.BufferedReader;
8import java.io.IOException;
9import java.net.MalformedURLException;
10import java.net.URL;
11import java.util.regex.Matcher;
12import java.util.regex.Pattern;
13import java.util.stream.Stream;
14
15import javax.json.Json;
16import javax.json.JsonArray;
17import javax.json.JsonValue;
18
19import com.drew.lang.Charsets;
20import org.openstreetmap.josm.data.osm.OsmUtils;
21import org.openstreetmap.josm.io.CachedFile;
22import org.openstreetmap.josm.tools.Logging;
23import org.openstreetmap.josm.tools.MultiMap;
24import org.openstreetmap.josm.tools.Utils;
25
26/**
27 * Extracts web links from OSM tags.
28 * <p></p>
29 * The following rules are used:
30 * <ul>
31 * <li>internal rules for basic tags</li>
32 * <li>rules from Wikidata based on OSM tag or key (P1282); formatter URL (P1630); third-party formatter URL (P3303)</li>
33 * <li>rules from OSM Sophox based on permanent key ID (P16); formatter URL (P8)</li>
34 * </ul>
35 *
36 * @since 15673
37 */
38public final class Tag2Link {
39
40 // Related implementations:
41 // - https://github.com/openstreetmap/openstreetmap-website/blob/master/app/helpers/browse_tags_helper.rb
42
43 /**
44 * Maps OSM keys to formatter URLs from Wikidata and OSM Sophox where {@code "$1"} has to be replaced by a value.
45 */
46 protected static MultiMap<String, String> wikidataRules = new MultiMap<>();
47
48 private Tag2Link() {
49 // private constructor for utility class
50 }
51
52 @FunctionalInterface
53 interface LinkConsumer {
54 void acceptLink(String name, String url);
55 }
56
57 /**
58 * Initializes the tag2link rules
59 */
60 public static void initialize() {
61 try {
62 wikidataRules.clear();
63 fetchRulesViaSPARQL("resource://data/tag2link.wikidata.sparql", "https://query.wikidata.org/sparql");
64 fetchRulesViaSPARQL("resource://data/tag2link.sophox.sparql", "https://sophox.org/sparql");
65 } catch (Exception e) {
66 Logging.error("Failed to initialize tag2link rules");
67 Logging.error(e);
68 }
69 }
70
71 /**
72 * Fetches rules from Wikidata using a SPARQL query.
73 *
74 * @param query the SPARQL query
75 * @param server the query server
76 * @throws IOException in case of I/O error
77 */
78 private static void fetchRulesViaSPARQL(final String query, final String server) throws IOException {
79 final int initialSize = wikidataRules.size();
80 final String sparql = new String(new CachedFile(query).getByteContent(), Charsets.UTF_8);
81 final CachedFile sparqlFile = new CachedFile(server + "?query=" + Utils.encodeUrl(sparql))
82 .setHttpAccept("application/json");
83
84 final JsonArray rules;
85 try (BufferedReader reader = sparqlFile.getContentReader()) {
86 rules = Json.createReader(reader).read().asJsonObject().getJsonObject("results").getJsonArray("bindings");
87 }
88
89 for (JsonValue rule : rules) {
90 final String key = rule.asJsonObject().getJsonObject("OSM_key").getString("value");
91 final String url = rule.asJsonObject().getJsonObject("formatter_URL").getString("value");
92 if (key.startsWith("Key:")) {
93 wikidataRules.put(key.substring("Key:".length()), url);
94 }
95 }
96 // We handle those keys ourselves
97 Stream.of("image", "url", "website", "wikidata", "wikimedia_commons")
98 .forEach(wikidataRules::remove);
99
100 final int size = wikidataRules.size() - initialSize;
101 Logging.info(trn(
102 "Obtained {0} Tag2Link rule from {1}",
103 "Obtained {0} Tag2Link rules from {1}",
104 size, size, server));
105 }
106
107 static void getLinksForTag(String key, String value, LinkConsumer linkConsumer) {
108 Matcher keyMatcher;
109 Matcher valueMatcher;
110
111 // Search
112 if (key.matches("^(.+[:_])?name([:_].+)?$")) {
113 linkConsumer.acceptLink(tr("Search on DuckDuckGo"), "https://duckduckgo.com/?q=" + value);
114 }
115
116 // Common
117 final boolean valueIsURL = value.matches("^(http:|https:|www\\.).*");
118 if (key.matches("^(.+[:_])?website([:_].+)?$") && valueIsURL) {
119 linkConsumer.acceptLink(getLinkName(value, key), value);
120 }
121 if (key.matches("^(.+[:_])?source([:_].+)?$") && valueIsURL) {
122 linkConsumer.acceptLink(getLinkName(value, key), value);
123 }
124 if (key.matches("^(.+[:_])?url([:_].+)?$") && valueIsURL) {
125 linkConsumer.acceptLink(getLinkName(value, key), value);
126 }
127 if (key.matches("image") && valueIsURL) {
128 linkConsumer.acceptLink(tr("View image"), value);
129 }
130
131 // Wikimedia
132 if ((keyMatcher = Pattern.compile("wikipedia(:(?<lang>\\p{Lower}{2,}))?").matcher(key)).matches()
133 && (valueMatcher = Pattern.compile("((?<lang>\\p{Lower}{2,}):)?(?<article>.*)").matcher(value)).matches()) {
134 final String lang = Utils.firstNotEmptyString("en", keyMatcher.group("lang"), valueMatcher.group("lang"));
135 linkConsumer.acceptLink(tr("View Wikipedia article"), "https://" + lang + ".wikipedia.org/wiki/" + valueMatcher.group("article"));
136 }
137 if (key.matches("(.*:)?wikidata")) {
138 OsmUtils.splitMultipleValues(value)
139 .forEach(q -> linkConsumer.acceptLink(tr("View Wikidata item {0}", q), "https://www.wikidata.org/wiki/" + q));
140 }
141 if (key.matches("(.*:)?species")) {
142 final String url = "https://species.wikimedia.org/wiki/" + value;
143 linkConsumer.acceptLink(getLinkName(url, key), url);
144 }
145 if (key.matches("wikimedia_commons|image") && value.matches("(?i:File):.*")) {
146 linkConsumer.acceptLink(tr("View image on Wikimedia Commons"), "https://commons.wikimedia.org/wiki/" + value);
147 }
148 if (key.matches("wikimedia_commons|image") && value.matches("(?i:Category):.*")) {
149 linkConsumer.acceptLink(tr("View category on Wikimedia Commons"), "https://commons.wikimedia.org/wiki/" + value);
150 }
151
152 wikidataRules.getValues(key).forEach(urlFormatter -> {
153 final String url = urlFormatter.replace("$1", value);
154 linkConsumer.acceptLink(getLinkName(url, key), url);
155 });
156 }
157
158 private static String getLinkName(String url, String fallback) {
159 try {
160 return tr("Open {0}", new URL(url).getHost());
161 } catch (MalformedURLException e) {
162 return tr("Open {0}", fallback);
163 }
164 }
165
166}
Note: See TracBrowser for help on using the repository browser.