source: josm/trunk/src/org/openstreetmap/josm/tools/GeoPropertyIndex.java@ 17379

Last change on this file since 17379 was 14484, checked in by Don-vip, 5 years ago

fix #17058 - Cannot add assertMatch/assertNoMatch declarations with inside() selector

  • Property svn:eol-style set to native
File size: 6.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import org.openstreetmap.josm.data.coor.LatLon;
5import org.openstreetmap.josm.data.osm.BBox;
6
7/**
8 * Fast index to look up properties of the earth surface.
9 *
10 * It is expected that there is a relatively slow method to look up the property
11 * for a certain coordinate and that there are larger areas with a uniform
12 * property.
13 *
14 * This index tries to find rectangles with uniform property and caches them.
15 * Rectangles are subdivided, if there are different properties within.
16 * (Up to a maximum level, when the slow method is used again.)
17 *
18 * @param <T> the property (like land/water or nation)
19 */
20public class GeoPropertyIndex<T> {
21
22 private final int maxLevel;
23 private final GeoProperty<T> geoProp;
24 private final GPLevel<T> root;
25 private GPLevel<T> lastLevelUsed;
26
27 private static final boolean DEBUG = false;
28
29 /**
30 * Create new GeoPropertyIndex.
31 * @param geoProp the input property that should be made faster by this index
32 * @param maxLevel max level
33 */
34 public GeoPropertyIndex(GeoProperty<T> geoProp, int maxLevel) {
35 this.geoProp = geoProp;
36 this.maxLevel = maxLevel;
37 this.root = new GPLevel<>(0, new BBox(-180, -90, 180, 90), null, this);
38 this.lastLevelUsed = root;
39 }
40
41 /**
42 * Look up the property for a certain point.
43 * This gives the same result as {@link GeoProperty#get(LatLon)}, but
44 * should be faster.
45 * @param ll the point coordinates
46 * @return property value at that point
47 */
48 public T get(LatLon ll) {
49 return lastLevelUsed.get(ll);
50 }
51
52 /**
53 * Returns the geo property.
54 * @return the geo property
55 * @since 14484
56 */
57 public final GeoProperty<T> getGeoProperty() {
58 return geoProp;
59 }
60
61 /**
62 * Gets the index of the given coordinate. Only used internally
63 * @param ll The lat/lon coordinate
64 * @param level The scale level
65 * @return The index for that position
66 */
67 public static int index(LatLon ll, int level) {
68 long noParts = 1L << level;
69 long x = ((long) ((ll.lon() + 180.0) * noParts / 360.0)) & 1;
70 long y = ((long) ((ll.lat() + 90.0) * noParts / 180.0)) & 1;
71 return (int) (2 * x + y);
72 }
73
74 protected static class GPLevel<T> {
75 private final T val;
76 private final int level;
77 private final BBox bbox;
78 private final GPLevel<T> parent;
79 private final GeoPropertyIndex<T> owner;
80
81 // child order by index is sw, nw, se, ne
82 private GPLevel<T>[] children;
83
84 public GPLevel(int level, BBox bbox, GPLevel<T> parent, GeoPropertyIndex<T> owner) {
85 this.level = level;
86 this.bbox = bbox;
87 this.parent = parent;
88 this.owner = owner;
89 this.val = owner.geoProp.get(bbox);
90 }
91
92 public T get(LatLon ll) {
93 if (isInside(ll))
94 return getBounded(ll);
95 if (DEBUG) System.err.print("up["+level+"]");
96 return parent != null ? parent.get(ll) : null;
97 }
98
99 private T getBounded(LatLon ll) {
100 if (DEBUG) System.err.print("GPLevel["+level+"]"+bbox+" ");
101 if (!isInside(ll)) {
102 throw new AssertionError("Point "+ll+" should be inside "+bbox);
103 }
104 if (val != null) {
105 if (DEBUG) System.err.println(" hit! "+val);
106 owner.lastLevelUsed = this;
107 return val;
108 }
109 if (level >= owner.maxLevel) {
110 if (DEBUG) System.err.println(" max level reached !");
111 return owner.geoProp.get(ll);
112 }
113
114 if (children == null) {
115 @SuppressWarnings("unchecked")
116 GPLevel<T>[] tmp = new GPLevel[4];
117 this.children = tmp;
118 }
119
120 int idx = index(ll, level+1);
121 if (children[idx] == null) {
122 double lon1, lat1;
123 switch (idx) {
124 case 0:
125 lon1 = bbox.getTopLeftLon();
126 lat1 = bbox.getBottomRightLat();
127 break;
128 case 1:
129 lon1 = bbox.getTopLeftLon();
130 lat1 = bbox.getTopLeftLat();
131 break;
132 case 2:
133 lon1 = bbox.getBottomRightLon();
134 lat1 = bbox.getBottomRightLat();
135 break;
136 case 3:
137 lon1 = bbox.getBottomRightLon();
138 lat1 = bbox.getTopLeftLat();
139 break;
140 default:
141 throw new AssertionError();
142 }
143 if (DEBUG) System.err.println(" - new with idx "+idx);
144 LatLon center = bbox.getCenter();
145 BBox b = new BBox(lon1, lat1, center.lon(), center.lat());
146 children[idx] = new GPLevel<>(level + 1, b, this, owner);
147 }
148 return children[idx].getBounded(ll);
149 }
150
151 /**
152 * Checks, if a point is inside this tile.
153 * Makes sure, that neighboring tiles do not overlap, i.e. a point exactly
154 * on the border of two tiles must be inside exactly one of the tiles.
155 * @param ll the coordinates of the point
156 * @return true, if it is inside of the box
157 */
158 boolean isInside(LatLon ll) {
159 return bbox.getTopLeftLon() <= ll.lon() &&
160 (ll.lon() < bbox.getBottomRightLon() || (ll.lon() == 180.0 && bbox.getBottomRightLon() == 180.0)) &&
161 bbox.getBottomRightLat() <= ll.lat() &&
162 (ll.lat() < bbox.getTopLeftLat() || (ll.lat() == 90.0 && bbox.getTopLeftLat() == 90.0));
163 }
164
165 @Override
166 public String toString() {
167 return "GPLevel [val=" + val + ", level=" + level + ", bbox=" + bbox + ']';
168 }
169 }
170
171 @Override
172 public String toString() {
173 return "GeoPropertyIndex [maxLevel=" + maxLevel + ", geoProp=" + geoProp + ", root=" + root + ", lastLevelUsed="
174 + lastLevelUsed + ']';
175 }
176}
Note: See TracBrowser for help on using the repository browser.