001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trn; 006 007import java.util.Arrays; 008import java.util.List; 009import java.util.stream.Collectors; 010import java.util.stream.Stream; 011 012import org.openstreetmap.josm.data.conflict.Conflict; 013import org.openstreetmap.josm.data.coor.ILatLon; 014import org.openstreetmap.josm.data.coor.LatLon; 015import org.openstreetmap.josm.data.coor.conversion.AbstractCoordinateFormat; 016import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat; 017import org.openstreetmap.josm.data.coor.conversion.ProjectedCoordinateFormat; 018import org.openstreetmap.josm.data.osm.BBox; 019import org.openstreetmap.josm.data.osm.DataSet; 020import org.openstreetmap.josm.data.osm.INode; 021import org.openstreetmap.josm.data.osm.IPrimitive; 022import org.openstreetmap.josm.data.osm.IRelation; 023import org.openstreetmap.josm.data.osm.IRelationMember; 024import org.openstreetmap.josm.data.osm.IWay; 025import org.openstreetmap.josm.data.osm.OsmData; 026import org.openstreetmap.josm.data.osm.OsmPrimitive; 027import org.openstreetmap.josm.data.projection.ProjectionRegistry; 028import org.openstreetmap.josm.data.projection.proj.TransverseMercator; 029import org.openstreetmap.josm.data.projection.proj.TransverseMercator.Hemisphere; 030import org.openstreetmap.josm.tools.Geometry; 031import org.openstreetmap.josm.tools.Pair; 032import org.openstreetmap.josm.tools.date.DateUtils; 033 034/** 035 * Textual representation of primitive contents, used in {@code InspectPrimitiveDialog}. 036 * @since 10198 037 */ 038public class InspectPrimitiveDataText { 039 private static final String INDENT = " "; 040 private static final char NL = '\n'; 041 042 private final StringBuilder s = new StringBuilder(); 043 private final OsmData<?, ?, ?, ?> ds; 044 045 InspectPrimitiveDataText(OsmData<?, ?, ?, ?> ds) { 046 this.ds = ds; 047 } 048 049 private InspectPrimitiveDataText add(String title, String... values) { 050 s.append(INDENT).append(title); 051 for (String v : values) { 052 s.append(v); 053 } 054 s.append(NL); 055 return this; 056 } 057 058 private static String getNameAndId(String name, long id) { 059 if (name != null) { 060 return name + tr(" ({0})", /* sic to avoid thousand separators */ Long.toString(id)); 061 } else { 062 return Long.toString(id); 063 } 064 } 065 066 /** 067 * Adds a new OSM primitive. 068 * @param o primitive to add 069 */ 070 public void addPrimitive(IPrimitive o) { 071 072 addHeadline(o); 073 074 if (!(o.getDataSet() != null && o.getDataSet().getPrimitiveById(o) != null)) { 075 s.append(NL).append(INDENT).append(tr("not in data set")).append(NL); 076 return; 077 } 078 if (o.isIncomplete()) { 079 s.append(NL).append(INDENT).append(tr("incomplete")).append(NL); 080 return; 081 } 082 s.append(NL); 083 084 addState(o); 085 addCommon(o); 086 addAttributes(o); 087 addSpecial(o); 088 addReferrers(s, o); 089 if (o instanceof OsmPrimitive) { 090 addConflicts((OsmPrimitive) o); 091 } 092 s.append(NL); 093 } 094 095 void addHeadline(IPrimitive o) { 096 addType(o); 097 addNameAndId(o); 098 } 099 100 void addType(IPrimitive o) { 101 if (o instanceof INode) { 102 s.append(tr("Node: ")); 103 } else if (o instanceof IWay) { 104 s.append(tr("Way: ")); 105 } else if (o instanceof IRelation) { 106 s.append(tr("Relation: ")); 107 } 108 } 109 110 void addNameAndId(IPrimitive o) { 111 String name = o.get("name"); 112 if (name == null) { 113 s.append(o.getUniqueId()); 114 } else { 115 s.append(getNameAndId(name, o.getUniqueId())); 116 } 117 } 118 119 void addState(IPrimitive o) { 120 StringBuilder sb = new StringBuilder(INDENT); 121 /* selected state is left out: not interesting as it is always selected */ 122 if (o.isDeleted()) { 123 sb.append(tr("deleted")).append(INDENT); 124 } 125 if (!o.isVisible()) { 126 sb.append(tr("deleted-on-server")).append(INDENT); 127 } 128 if (o.isModified()) { 129 sb.append(tr("modified")).append(INDENT); 130 } 131 if (o.isDisabledAndHidden()) { 132 sb.append(tr("filtered/hidden")).append(INDENT); 133 } 134 if (o.isDisabled()) { 135 sb.append(tr("filtered/disabled")).append(INDENT); 136 } 137 if (o.hasDirectionKeys()) { 138 if (o.reversedDirection()) { 139 sb.append(tr("has direction keys (reversed)")).append(INDENT); 140 } else { 141 sb.append(tr("has direction keys")).append(INDENT); 142 } 143 } 144 String state = sb.toString().trim(); 145 if (!state.isEmpty()) { 146 add(tr("State: "), sb.toString().trim()); 147 } 148 } 149 150 void addCommon(IPrimitive o) { 151 add(tr("Data Set: "), Integer.toHexString(o.getDataSet().hashCode())); 152 add(tr("Edited at: "), o.isTimestampEmpty() ? tr("<new object>") 153 : DateUtils.fromTimestamp(o.getRawTimestamp())); 154 add(tr("Edited by: "), o.getUser() == null ? tr("<new object>") 155 : getNameAndId(o.getUser().getName(), o.getUser().getId())); 156 add(tr("Version:"), " ", Integer.toString(o.getVersion())); 157 add(tr("In changeset: "), Integer.toString(o.getChangesetId())); 158 } 159 160 void addAttributes(IPrimitive o) { 161 if (o.hasKeys()) { 162 add(tr("Tags: ")); 163 for (String key : o.keySet()) { 164 s.append(INDENT).append(INDENT); 165 s.append(String.format("\"%s\"=\"%s\"%n", key, o.get(key))); 166 } 167 } 168 } 169 170 void addSpecial(IPrimitive o) { 171 if (o instanceof INode) { 172 addCoordinates((INode) o); 173 } else if (o instanceof IWay) { 174 addBbox(o); 175 add(tr("Centroid: "), toStringCSV(false, 176 ProjectionRegistry.getProjection().eastNorth2latlon(Geometry.getCentroid(((IWay<?>) o).getNodes())))); 177 addWayNodes((IWay<?>) o); 178 } else if (o instanceof IRelation) { 179 addBbox(o); 180 addRelationMembers((IRelation<?>) o); 181 } 182 } 183 184 void addRelationMembers(IRelation<?> r) { 185 add(trn("{0} Member: ", "{0} Members: ", r.getMembersCount(), r.getMembersCount())); 186 for (IRelationMember<?> m : r.getMembers()) { 187 s.append(INDENT).append(INDENT); 188 addHeadline(m.getMember()); 189 s.append(tr(" as \"{0}\"", m.getRole())); 190 s.append(NL); 191 } 192 } 193 194 void addWayNodes(IWay<?> w) { 195 add(tr("{0} Nodes: ", w.getNodesCount())); 196 for (INode n : w.getNodes()) { 197 s.append(INDENT).append(INDENT); 198 addNameAndId(n); 199 s.append(NL); 200 } 201 } 202 203 void addBbox(IPrimitive o) { 204 BBox bbox = o.getBBox(); 205 if (bbox != null) { 206 final LatLon bottomRight = bbox.getBottomRight(); 207 final LatLon topLeft = bbox.getTopLeft(); 208 add(tr("Bounding box: "), toStringCSV(false, bottomRight, topLeft)); 209 add(tr("Bounding box (projected): "), toStringCSV(true, bottomRight, topLeft)); 210 add(tr("Center of bounding box: "), toStringCSV(false, bbox.getCenter())); 211 } 212 } 213 214 void addCoordinates(INode n) { 215 if (n.isLatLonKnown()) { 216 add(tr("Coordinates:"), " ", toStringCSV(false, n)); 217 add(tr("Coordinates (projected): "), toStringCSV(true, n)); 218 Pair<Integer, Hemisphere> utmZone = TransverseMercator.locateUtmZone(n.getCoor()); 219 String utmLabel = tr("UTM Zone"); 220 add(utmLabel, utmLabel.endsWith(":") ? " " : ": ", Integer.toString(utmZone.a), utmZone.b.name().substring(0, 1)); 221 } 222 } 223 224 void addReferrers(StringBuilder s, IPrimitive o) { 225 List<? extends IPrimitive> refs = o.getReferrers(); 226 if (!refs.isEmpty()) { 227 add(tr("Part of: ")); 228 for (IPrimitive p : refs) { 229 s.append(INDENT).append(INDENT); 230 addHeadline(p); 231 s.append(NL); 232 } 233 } 234 } 235 236 void addConflicts(OsmPrimitive o) { 237 Conflict<?> c = ((DataSet) ds).getConflicts().getConflictForMy(o); 238 if (c != null) { 239 add(tr("In conflict with: ")); 240 addNameAndId(c.getTheir()); 241 } 242 } 243 244 /** 245 * Returns the coordinates in human-readable format. 246 * @param projected whether to use projected coordinates 247 * @param coordinates the coordinates to format 248 * @return String in the format {@code "1.23456, 2.34567"} 249 */ 250 private static String toStringCSV(boolean projected, ILatLon... coordinates) { 251 final AbstractCoordinateFormat format = projected 252 ? ProjectedCoordinateFormat.INSTANCE 253 : DecimalDegreesCoordinateFormat.INSTANCE; 254 return Arrays.stream(coordinates) 255 .flatMap(ll -> Stream.of(format.latToString(ll), format.lonToString(ll))) 256 .collect(Collectors.joining(", ")); 257 } 258 259 @Override 260 public String toString() { 261 return s.toString(); 262 } 263}