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