source: josm/trunk/src/org/openstreetmap/josm/gui/DefaultNameFormatter.java@ 4339

Last change on this file since 4339 was 4324, checked in by xeen, 13 years ago

(probably) fix #3122

  • Property svn:eol-style set to native
File size: 22.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trc;
6import static org.openstreetmap.josm.tools.I18n.trc_lazy;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.util.ArrayList;
10import java.util.Arrays;
11import java.util.Collections;
12import java.util.Comparator;
13import java.util.HashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Set;
17
18import org.openstreetmap.josm.Main;
19import org.openstreetmap.josm.data.coor.CoordinateFormat;
20import org.openstreetmap.josm.data.osm.Changeset;
21import org.openstreetmap.josm.data.osm.INode;
22import org.openstreetmap.josm.data.osm.IPrimitive;
23import org.openstreetmap.josm.data.osm.IRelation;
24import org.openstreetmap.josm.data.osm.IWay;
25import org.openstreetmap.josm.data.osm.NameFormatter;
26import org.openstreetmap.josm.data.osm.Node;
27import org.openstreetmap.josm.data.osm.OsmPrimitive;
28import org.openstreetmap.josm.data.osm.OsmUtils;
29import org.openstreetmap.josm.data.osm.Relation;
30import org.openstreetmap.josm.data.osm.RelationMember;
31import org.openstreetmap.josm.data.osm.Way;
32import org.openstreetmap.josm.data.osm.history.HistoryNameFormatter;
33import org.openstreetmap.josm.data.osm.history.HistoryNode;
34import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
35import org.openstreetmap.josm.data.osm.history.HistoryRelation;
36import org.openstreetmap.josm.data.osm.history.HistoryWay;
37
38/**
39 * This is the default implementation of a {@see NameFormatter} for names of {@see OsmPrimitive}s.
40 *
41 */
42public class DefaultNameFormatter implements NameFormatter, HistoryNameFormatter {
43
44 static private DefaultNameFormatter instance;
45
46 private static final LinkedList<NameFormatterHook> formatHooks = new LinkedList<NameFormatterHook>();
47
48 /**
49 * Replies the unique instance of this formatter
50 *
51 * @return the unique instance of this formatter
52 */
53 static public DefaultNameFormatter getInstance() {
54 if (instance == null) {
55 instance = new DefaultNameFormatter();
56 }
57 return instance;
58 }
59
60 /**
61 * Registers a format hook. Adds the hook at the first position of the format hooks.
62 * (for plugins)
63 *
64 * @param hook the format hook. Ignored if null.
65 */
66 public static void registerFormatHook(NameFormatterHook hook) {
67 if (hook == null) return;
68 if (!formatHooks.contains(hook)) {
69 formatHooks.add(0,hook);
70 }
71 }
72
73 /**
74 * Unregisters a format hook. Removes the hook from the list of format hooks.
75 *
76 * @param hook the format hook. Ignored if null.
77 */
78 public static void unregisterFormatHook(NameFormatterHook hook) {
79 if (hook == null) return;
80 if (formatHooks.contains(hook)) {
81 formatHooks.remove(hook);
82 }
83 }
84
85 /** the default list of tags which are used as naming tags in relations */
86 static public final String[] DEFAULT_NAMING_TAGS_FOR_RELATIONS = {"name", "ref", "restriction", "landuse", "natural",
87 "public_transport", ":LocationCode", "note"};
88
89 /** the current list of tags used as naming tags in relations */
90 static private List<String> namingTagsForRelations = null;
91
92 /**
93 * Replies the list of naming tags used in relations. The list is given (in this order) by:
94 * <ul>
95 * <li>by the tag names in the preference <tt>relation.nameOrder</tt></li>
96 * <li>by the default tags in {@see #DEFAULT_NAMING_TAGS_FOR_RELATIONS}
97 * </ul>
98 *
99 * @return the list of naming tags used in relations
100 */
101 static public List<String> getNamingtagsForRelations() {
102 if (namingTagsForRelations == null) {
103 namingTagsForRelations = new ArrayList<String>(
104 Main.pref.getCollection("relation.nameOrder", Arrays.asList(DEFAULT_NAMING_TAGS_FOR_RELATIONS))
105 );
106 }
107 return namingTagsForRelations;
108 }
109
110 /**
111 * Decorates the name of primitive with its id, if the preference
112 * <tt>osm-primitives.showid</tt> is set. Shows unique id if osm-primitives.showid.new-primitives is set
113 *
114 * @param name the name without the id
115 * @param primitive the primitive
116 * @return the decorated name
117 */
118 protected String decorateNameWithId(String name, IPrimitive primitive) {
119 if (Main.pref.getBoolean("osm-primitives.showid"))
120 if (Main.pref.getBoolean("osm-primitives.showid.new-primitives"))
121 return name + tr(" [id: {0}]", primitive.getUniqueId());
122 else
123 return name + tr(" [id: {0}]", primitive.getId());
124 else
125 return name;
126 }
127
128 /**
129 * Formats a name for a node
130 *
131 * @param node the node
132 * @return the name
133 */
134 public String format(INode node) {
135 String name = "";
136 if (node.isIncomplete()) {
137 name = tr("incomplete");
138 } else {
139 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
140 name = node.getLocalName();
141 } else {
142 name = node.getName();
143 }
144 if(name == null)
145 {
146 String s;
147 if((s = node.get("addr:housename")) != null) {
148 /* I18n: name of house as parameter */
149 name = tr("House {0}", s);
150 }
151 if(name == null && (s = node.get("addr:housenumber")) != null) {
152 String t = node.get("addr:street");
153 if(t != null) {
154 /* I18n: house number, street as parameter, number should remain
155 before street for better visibility */
156 name = tr("House number {0} at {1}", s, t);
157 }
158 else {
159 /* I18n: house number as parameter */
160 name = tr("House number {0}", s);
161 }
162 }
163 }
164
165 if (name == null) {
166 name = node.isNew() ? tr("node") : ""+ node.getId();
167 }
168 name += " \u200E(" + node.getCoor().latToString(CoordinateFormat.getDefaultFormat()) + ", " + node.getCoor().lonToString(CoordinateFormat.getDefaultFormat()) + ")";
169 }
170 name = decorateNameWithId(name, node);
171
172 for (NameFormatterHook hook: formatHooks) {
173 String hookResult = hook.checkFormat(node, name);
174 if (hookResult != null) {
175 return hookResult;
176 }
177 }
178
179 return name;
180 }
181
182 private final Comparator<Node> nodeComparator = new Comparator<Node>() {
183 @Override
184 public int compare(Node n1, Node n2) {
185 return format(n1).compareTo(format(n2));
186 }
187 };
188
189 public Comparator<Node> getNodeComparator() {
190 return nodeComparator;
191 }
192
193
194 /**
195 * Formats a name for a way
196 *
197 * @param way the way
198 * @return the name
199 */
200 public String format(IWay way) {
201 String name = "";
202 if (way.isIncomplete()) {
203 name = tr("incomplete");
204 } else {
205 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
206 name = way.getLocalName();
207 } else {
208 name = way.getName();
209 }
210 if (name == null) {
211 name = way.get("ref");
212 }
213 if (name == null) {
214 name =
215 (way.get("highway") != null) ? tr("highway") :
216 (way.get("railway") != null) ? tr("railway") :
217 (way.get("waterway") != null) ? tr("waterway") :
218 (way.get("landuse") != null) ? tr("landuse") : null;
219 }
220 if(name == null)
221 {
222 String s;
223 if((s = way.get("addr:housename")) != null) {
224 /* I18n: name of house as parameter */
225 name = tr("House {0}", s);
226 }
227 if(name == null && (s = way.get("addr:housenumber")) != null) {
228 String t = way.get("addr:street");
229 if(t != null) {
230 /* I18n: house number, street as parameter, number should remain
231 before street for better visibility */
232 name = tr("House number {0} at {1}", s, t);
233 }
234 else {
235 /* I18n: house number as parameter */
236 name = tr("House number {0}", s);
237 }
238 }
239 }
240
241 int nodesNo = way.getNodesCount();
242 if (nodesNo > 1 && way.isClosed()) {
243 nodesNo--;
244 }
245 if(name == null || name.length() == 0) {
246 name = String.valueOf(way.getId());
247 }
248 /* note: length == 0 should no longer happen, but leave the bracket code
249 nevertheless, who knows what future brings */
250 /* I18n: count of nodes as parameter */
251 String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
252 name += (name.length() > 0) ? " ("+nodes+")" : nodes;
253 }
254 name = decorateNameWithId(name, way);
255
256 for (NameFormatterHook hook: formatHooks) {
257 String hookResult = hook.checkFormat(way, name);
258 if (hookResult != null) {
259 return hookResult;
260 }
261 }
262
263 return name;
264 }
265
266 private final Comparator<Way> wayComparator = new Comparator<Way>() {
267 @Override
268 public int compare(Way w1, Way w2) {
269 return format(w1).compareTo(format(w2));
270 }
271 };
272
273 public Comparator<Way> getWayComparator() {
274 return wayComparator;
275 }
276
277
278 /**
279 * Formats a name for a relation
280 *
281 * @param relation the relation
282 * @return the name
283 */
284 public String format(IRelation relation) {
285 String name;
286 if (relation.isIncomplete()) {
287 name = tr("incomplete");
288 } else {
289 name = getRelationTypeName(relation);
290 String relationName = getRelationName(relation);
291 if (relationName == null) {
292 relationName = Long.toString(relation.getId());
293 } else {
294 relationName = "\"" + relationName + "\"";
295 }
296 name += " (" + relationName + ", ";
297
298 int mbno = relation.getMembersCount();
299 name += trn("{0} member", "{0} members", mbno, mbno);
300
301 if (relation instanceof Relation) {
302 if (((Relation) relation).hasIncompleteMembers()) {
303 name += ", "+tr("incomplete");
304 }
305 }
306
307 name += ")";
308 }
309 name = decorateNameWithId(name, relation);
310
311 for (NameFormatterHook hook: formatHooks) {
312 String hookResult = hook.checkFormat(relation, name);
313 if (hookResult != null) {
314 return hookResult;
315 }
316 }
317
318 return name;
319 }
320
321 private final Comparator<Relation> relationComparator = new Comparator<Relation>() {
322 @Override
323 public int compare(Relation r1, Relation r2) {
324 String type1 = getRelationTypeName(r1);
325 String type2 = getRelationTypeName(r2);
326
327 int comp = type1.compareTo(type2);
328 if (comp != 0)
329 return comp;
330
331 String name1 = getRelationName(r1);
332 String name2 = getRelationName(r2);
333
334 if (name1 == null && name2 == null)
335 return (r1.getUniqueId() > r2.getUniqueId())?1:-1;
336 else if (name1 == null)
337 return -1;
338 else if (name2 == null)
339 return 1;
340 else if (!name1.isEmpty() && !name2.isEmpty() && Character.isDigit(name1.charAt(0)) && Character.isDigit(name2.charAt(0))) {
341 //Compare numerically
342 String ln1 = getLeadingNumber(name1);
343 String ln2 = getLeadingNumber(name2);
344
345 comp = Long.valueOf(ln1).compareTo(Long.valueOf(ln2));
346 if (comp != 0)
347 return comp;
348
349 // put 1 before 0001
350 comp = ln1.compareTo(ln2);
351 if (comp != 0)
352 return comp;
353
354 comp = name1.substring(ln1.length()).compareTo(name2.substring(ln2.length()));
355 if (comp != 0)
356 return comp;
357 } else {
358 comp = name1.compareToIgnoreCase(name2);
359 if (comp != 0)
360 return comp;
361 }
362
363 if (r1.getMembersCount() != r2.getMembersCount())
364 return (r1.getMembersCount() > r2.getMembersCount())?1:-1;
365
366 comp = Boolean.valueOf(r1.hasIncompleteMembers()).compareTo(Boolean.valueOf(r2.hasIncompleteMembers()));
367 if (comp != 0)
368 return comp;
369
370 return r1.getUniqueId() > r2.getUniqueId()?1:-1;
371 }
372 };
373
374 public Comparator<Relation> getRelationComparator() {
375 return relationComparator;
376 }
377
378 private String getLeadingNumber(String s) {
379 int i = 0;
380 while (i < s.length() && Character.isDigit(s.charAt(i))) {
381 i++;
382 }
383 return s.substring(0, i);
384 }
385
386 private String getRelationTypeName(IRelation relation) {
387 String name = trc("Relation type", relation.get("type"));
388 if (name == null) {
389 name = (relation.get("public_transport") != null) ? tr("public transport") : null;
390 }
391 if (name == null) {
392 String building = relation.get("building");
393 if(OsmUtils.isTrue(building)) {
394 name = tr("building");
395 } else if(building != null)
396 {
397 name = tr(building); // translate tag!
398 }
399 }
400 if (name == null) {
401 name = trc("Place type", relation.get("place"));
402 }
403 if (name == null) {
404 name = tr("relation");
405 }
406 String admin_level = relation.get("admin_level");
407 if (admin_level != null) {
408 name += "["+admin_level+"]";
409 }
410
411 for (NameFormatterHook hook: formatHooks) {
412 String hookResult = hook.checkRelationTypeName(relation, name);
413 if (hookResult != null) {
414 return hookResult;
415 }
416 }
417
418 return name;
419 }
420
421 private String getNameTagValue(IRelation relation, String nameTag) {
422 if (nameTag.equals("name")) {
423 if (Main.pref.getBoolean("osm-primitives.localize-name", true))
424 return relation.getLocalName();
425 else
426 return relation.getName();
427 } else if (nameTag.equals(":LocationCode")) {
428 for (String m : relation.keySet()) {
429 if (m.endsWith(nameTag))
430 return relation.get(m);
431 }
432 return null;
433 } else
434 return trc_lazy(nameTag, relation.get(nameTag));
435 }
436
437 private String getRelationName(IRelation relation) {
438 String nameTag = null;
439 for (String n : getNamingtagsForRelations()) {
440 nameTag = getNameTagValue(relation, n);
441 if (nameTag != null)
442 return nameTag;
443 }
444 return null;
445 }
446
447 /**
448 * Formats a name for a changeset
449 *
450 * @param changeset the changeset
451 * @return the name
452 */
453 public String format(Changeset changeset) {
454 return tr("Changeset {0}",changeset.getId());
455 }
456
457 /**
458 * Builds a default tooltip text for the primitive <code>primitive</code>.
459 *
460 * @param primitive the primitmive
461 * @return the tooltip text
462 */
463 public String buildDefaultToolTip(IPrimitive primitive) {
464 StringBuilder sb = new StringBuilder();
465 sb.append("<html>");
466 sb.append("<strong>id</strong>=")
467 .append(primitive.getId())
468 .append("<br>");
469 ArrayList<String> keyList = new ArrayList<String>(primitive.keySet());
470 Collections.sort(keyList);
471 for (int i = 0; i < keyList.size(); i++) {
472 if (i > 0) {
473 sb.append("<br>");
474 }
475 String key = keyList.get(i);
476 sb.append("<strong>")
477 .append(key)
478 .append("</strong>")
479 .append("=");
480 String value = primitive.get(key);
481 while(value.length() != 0) {
482 sb.append(value.substring(0,Math.min(50, value.length())));
483 if (value.length() > 50) {
484 sb.append("<br>");
485 value = value.substring(50);
486 } else {
487 value = "";
488 }
489 }
490 }
491 sb.append("</html>");
492 return sb.toString();
493 }
494
495 /**
496 * Decorates the name of primitive with its id, if the preference
497 * <tt>osm-primitives.showid</tt> is set.
498 *
499 * The id is append to the {@see StringBuilder} passed in in <code>name</code>.
500 *
501 * @param name the name without the id
502 * @param primitive the primitive
503 */
504 protected void decorateNameWithId(StringBuilder name, HistoryOsmPrimitive primitive) {
505 if (Main.pref.getBoolean("osm-primitives.showid")) {
506 name.append(tr(" [id: {0}]", primitive.getId()));
507 }
508 }
509
510 /**
511 * Formats a name for a history node
512 *
513 * @param node the node
514 * @return the name
515 */
516 public String format(HistoryNode node) {
517 StringBuilder sb = new StringBuilder();
518 String name;
519 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
520 name = node.getLocalName();
521 } else {
522 name = node.getName();
523 }
524 if (name == null) {
525 sb.append(node.getId());
526 } else {
527 sb.append(name);
528 }
529 sb.append(" (")
530 .append(node.getCoords().latToString(CoordinateFormat.getDefaultFormat()))
531 .append(", ")
532 .append(node.getCoords().lonToString(CoordinateFormat.getDefaultFormat()))
533 .append(")");
534 decorateNameWithId(sb, node);
535 return sb.toString();
536 }
537
538 /**
539 * Formats a name for a way
540 *
541 * @param way the way
542 * @return the name
543 */
544 public String format(HistoryWay way) {
545 StringBuilder sb = new StringBuilder();
546 String name;
547 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
548 name = way.getLocalName();
549 } else {
550 name = way.getName();
551 }
552 if (name != null) {
553 sb.append(name);
554 }
555 if (sb.length() == 0 && way.get("ref") != null) {
556 sb.append(way.get("ref"));
557 }
558 if (sb.length() == 0) {
559 sb.append(
560 (way.get("highway") != null) ? tr("highway") :
561 (way.get("railway") != null) ? tr("railway") :
562 (way.get("waterway") != null) ? tr("waterway") :
563 (way.get("landuse") != null) ? tr("landuse") : ""
564 );
565 }
566
567 int nodesNo = way.isClosed() ? way.getNumNodes() -1 : way.getNumNodes();
568 String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
569 if(sb.length() == 0 ) {
570 sb.append(way.getId());
571 }
572 /* note: length == 0 should no longer happen, but leave the bracket code
573 nevertheless, who knows what future brings */
574 sb.append((sb.length() > 0) ? " ("+nodes+")" : nodes);
575 decorateNameWithId(sb, way);
576 return sb.toString();
577 }
578
579 /**
580 * Formats a name for a {@see HistoryRelation})
581 *
582 * @param relation the relation
583 * @return the name
584 */
585 public String format(HistoryRelation relation) {
586 StringBuilder sb = new StringBuilder();
587 if (relation.get("type") != null) {
588 sb.append(relation.get("type"));
589 } else {
590 sb.append(tr("relation"));
591 }
592 sb.append(" (");
593 String nameTag = null;
594 Set<String> namingTags = new HashSet<String>(getNamingtagsForRelations());
595 for (String n : relation.getTags().keySet()) {
596 // #3328: "note " and " note" are name tags too
597 if (namingTags.contains(n.trim())) {
598 if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
599 nameTag = relation.getLocalName();
600 } else {
601 nameTag = relation.getName();
602 }
603 if (nameTag == null) {
604 nameTag = relation.get(n);
605 }
606 }
607 if (nameTag != null) {
608 break;
609 }
610 }
611 if (nameTag == null) {
612 sb.append(Long.toString(relation.getId())).append(", ");
613 } else {
614 sb.append("\"").append(nameTag).append("\", ");
615 }
616
617 int mbno = relation.getNumMembers();
618 sb.append(trn("{0} member", "{0} members", mbno, mbno)).append(")");
619
620 decorateNameWithId(sb, relation);
621 return sb.toString();
622 }
623
624 /**
625 * Builds a default tooltip text for an HistoryOsmPrimitive <code>primitive</code>.
626 *
627 * @param primitive the primitmive
628 * @return the tooltip text
629 */
630 public String buildDefaultToolTip(HistoryOsmPrimitive primitive) {
631 StringBuilder sb = new StringBuilder();
632 sb.append("<html>");
633 sb.append("<strong>id</strong>=")
634 .append(primitive.getId())
635 .append("<br>");
636 ArrayList<String> keyList = new ArrayList<String>(primitive.getTags().keySet());
637 Collections.sort(keyList);
638 for (int i = 0; i < keyList.size(); i++) {
639 if (i > 0) {
640 sb.append("<br>");
641 }
642 String key = keyList.get(i);
643 sb.append("<strong>")
644 .append(key)
645 .append("</strong>")
646 .append("=");
647 String value = primitive.get(key);
648 while(value.length() != 0) {
649 sb.append(value.substring(0,Math.min(50, value.length())));
650 if (value.length() > 50) {
651 sb.append("<br>");
652 value = value.substring(50);
653 } else {
654 value = "";
655 }
656 }
657 }
658 sb.append("</html>");
659 return sb.toString();
660 }
661}
Note: See TracBrowser for help on using the repository browser.