source: josm/trunk/src/org/openstreetmap/josm/data/osm/Node.java@ 4153

Last change on this file since 4153 was 4126, checked in by bastiK, 13 years ago

memory optimizations for Node & WayPoint (Patch by Gubaer, modified)

The field 'proj' in CachedLatLon is a waste of memory. For the 2 classes where this has the greatest impact, the cache for the projected coordinates is replaced by 2 simple double fields (east & north). On projection change, they have to be invalidated explicitly. This is handled by the DataSet & the GpxLayer.

  • Property svn:eol-style set to native
File size: 9.3 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm;
3
4import org.openstreetmap.josm.Main;
5import org.openstreetmap.josm.data.coor.EastNorth;
6import org.openstreetmap.josm.data.coor.LatLon;
7import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
8import org.openstreetmap.josm.data.osm.visitor.Visitor;
9import org.openstreetmap.josm.data.projection.Projections;
10
11/**
12 * One node data, consisting of one world coordinate waypoint.
13 *
14 * @author imi
15 */
16public final class Node extends OsmPrimitive implements INode {
17
18 /*
19 * We "inline" lat/lon rather than using a LatLon-object => reduces memory footprint
20 */
21 static private final double COORDINATE_NOT_DEFINED = Double.NaN;
22 private double lat = COORDINATE_NOT_DEFINED;
23 private double lon = COORDINATE_NOT_DEFINED;
24
25 /*
26 * the cached projected coordinates
27 */
28 private double east = Double.NaN;
29 private double north = Double.NaN;
30
31 private boolean isLatLonKnown() {
32 return lat != COORDINATE_NOT_DEFINED && lon != COORDINATE_NOT_DEFINED;
33 }
34
35 @Override
36 public final void setCoor(LatLon coor) {
37 if(coor != null){
38 updateCoor(coor, null);
39 }
40 }
41
42 @Override
43 public final void setEastNorth(EastNorth eastNorth) {
44 if(eastNorth != null) {
45 updateCoor(null, eastNorth);
46 }
47 }
48
49 private void updateCoor(LatLon coor, EastNorth eastNorth) {
50 if (getDataSet() != null) {
51 boolean locked = writeLock();
52 try {
53 getDataSet().fireNodeMoved(this, coor, eastNorth);
54 } finally {
55 writeUnlock(locked);
56 }
57 } else {
58 setCoorInternal(coor, eastNorth);
59 }
60 }
61
62 @Override
63 public final LatLon getCoor() {
64 if (!isLatLonKnown()) return null;
65 return new LatLon(lat,lon);
66 }
67
68 /**
69 * <p>Replies the projected east/north coordinates.</p>
70 *
71 * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates.
72 * Internally caches the projected coordinates.</p>
73 *
74 * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must
75 * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p>
76 *
77 * <p>Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node.
78 *
79 * @return the east north coordinates or {@code null}
80 * @see #invalidateEastNorthCache()
81 *
82 */
83 @Override
84 public final EastNorth getEastNorth() {
85 if (!isLatLonKnown()) return null;
86
87 if (getDataSet() == null)
88 // there is no dataset that listens for projection changes
89 // and invalidates the cache, so we don't use the cache at all
90 return Projections.project(new LatLon(lat, lon));
91
92 if (Double.isNaN(east) || Double.isNaN(north)) {
93 // projected coordinates haven't been calculated yet,
94 // so fill the cache of the projected node coordinates
95 EastNorth en = Projections.project(new LatLon(lat, lon));
96 this.east = en.east();
97 this.north = en.north();
98 }
99 return new EastNorth(east, north);
100 }
101
102 /**
103 * To be used only by Dataset.reindexNode
104 */
105 protected void setCoorInternal(LatLon coor, EastNorth eastNorth) {
106 if (coor != null) {
107 this.lat = coor.lat();
108 this.lon = coor.lon();
109 invalidateEastNorthCache();
110 } else if (eastNorth != null) {
111 LatLon ll = Projections.inverseProject(eastNorth);
112 this.lat = ll.lat();
113 this.lon = ll.lon();
114 this.east = eastNorth.east();
115 this.north = eastNorth.north();
116 } else
117 throw new IllegalArgumentException();
118 }
119
120 protected Node(long id, boolean allowNegative) {
121 super(id, allowNegative);
122 }
123
124 /**
125 * Create a new local node.
126 *
127 */
128 public Node() {
129 this(0, false);
130 }
131
132 /**
133 * Create an incomplete Node object
134 */
135 public Node(long id) {
136 super(id, false);
137 }
138
139 /**
140 * Create new node
141 * @param id
142 * @param version
143 */
144 public Node(long id, int version) {
145 super(id, version, false);
146 }
147
148 /**
149 *
150 * @param clone
151 * @param clearId If true, set version to 0 and id to new unique value
152 */
153 public Node(Node clone, boolean clearId) {
154 super(clone.getUniqueId(), true /* allow negative IDs */);
155 cloneFrom(clone);
156 if (clearId) {
157 clearOsmId();
158 }
159 }
160
161 /**
162 * Create an identical clone of the argument (including the id)
163 */
164 public Node(Node clone) {
165 this(clone, false);
166 }
167
168 public Node(LatLon latlon) {
169 super(0, false);
170 setCoor(latlon);
171 }
172
173 public Node(EastNorth eastNorth) {
174 super(0, false);
175 setEastNorth(eastNorth);
176 }
177
178 @Override
179 void setDataset(DataSet dataSet) {
180 super.setDataset(dataSet);
181 if (!isIncomplete() && (getCoor() == null || getEastNorth() == null))
182 throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString() + get3892DebugInfo());
183 }
184
185 @Override public void visit(Visitor visitor) {
186 visitor.visit(this);
187 }
188
189 @Override public void visit(PrimitiveVisitor visitor) {
190 visitor.visit(this);
191 }
192
193 @Override public void cloneFrom(OsmPrimitive osm) {
194 boolean locked = writeLock();
195 try {
196 super.cloneFrom(osm);
197 setCoor(((Node)osm).getCoor());
198 } finally {
199 writeUnlock(locked);
200 }
201 }
202
203 /**
204 * Merges the technical and semantical attributes from <code>other</code> onto this.
205 *
206 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
207 * have an assigend OSM id, the IDs have to be the same.
208 *
209 * @param other the other primitive. Must not be null.
210 * @throws IllegalArgumentException thrown if other is null.
211 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
212 * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId()
213 */
214 @Override
215 public void mergeFrom(OsmPrimitive other) {
216 boolean locked = writeLock();
217 try {
218 super.mergeFrom(other);
219 if (!other.isIncomplete()) {
220 setCoor(((Node)other).getCoor());
221 }
222 } finally {
223 writeUnlock(locked);
224 }
225 }
226
227 @Override public void load(PrimitiveData data) {
228 boolean locked = writeLock();
229 try {
230 super.load(data);
231 setCoor(((NodeData)data).getCoor());
232 } finally {
233 writeUnlock(locked);
234 }
235 }
236
237 @Override public NodeData save() {
238 NodeData data = new NodeData();
239 saveCommonAttributes(data);
240 if (!isIncomplete()) {
241 data.setCoor(getCoor());
242 }
243 return data;
244 }
245
246 @Override public String toString() {
247 String coorDesc = isLatLonKnown() ? "lat="+lat+",lon="+lon : "";
248 return "{Node id=" + getUniqueId() + " version=" + getVersion() + " " + getFlagsAsString() + " " + coorDesc+"}";
249 }
250
251 @Override
252 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
253 if (other == null || ! (other instanceof Node) )
254 return false;
255 if (! super.hasEqualSemanticAttributes(other))
256 return false;
257 Node n = (Node)other;
258 LatLon coor = getCoor();
259 LatLon otherCoor = n.getCoor();
260 if (coor == null && otherCoor == null)
261 return true;
262 else if (coor != null && otherCoor != null)
263 return coor.equalsEpsilon(otherCoor);
264 else
265 return false;
266 }
267
268 @Override
269 public int compareTo(OsmPrimitive o) {
270 return o instanceof Node ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : 1;
271 }
272
273 @Override
274 public String getDisplayName(NameFormatter formatter) {
275 return formatter.format(this);
276 }
277
278 @Override
279 public OsmPrimitiveType getType() {
280 return OsmPrimitiveType.NODE;
281 }
282
283 @Override
284 public BBox getBBox() {
285 return new BBox(this);
286 }
287
288 @Override
289 public void updatePosition() {
290 }
291
292 public boolean isConnectionNode() {
293 return isReferredByWays(2);
294 }
295
296 public String get3892DebugInfo() {
297 StringBuilder builder = new StringBuilder();
298 builder.append("Unexpected error. Please report it to http://josm.openstreetmap.de/ticket/3892\n");
299 builder.append(toString());
300 builder.append("\n");
301 if (isLatLonKnown()) {
302 builder.append("Coor is null\n");
303 } else {
304 builder.append(String.format("EastNorth: %s\n", getEastNorth()));
305 builder.append(Main.getProjection());
306 builder.append("\n");
307 }
308
309 return builder.toString();
310 }
311
312 /**
313 * Invoke to invalidate the internal cache of projected east/north coordinates.
314 * Coordinates are reprojected on demand when the {@link #getEastNorth()} is invoked
315 * next time.
316 */
317 public void invalidateEastNorthCache() {
318 this.east = Double.NaN;
319 this.north = Double.NaN;
320 }
321}
Note: See TracBrowser for help on using the repository browser.