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