source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java@ 4191

Last change on this file since 4191 was 4191, checked in by stoecker, 13 years ago

remove old debug stuff

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/plain
File size: 17.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm.visitor.paint.relations;
3
4import java.awt.Point;
5import java.awt.Polygon;
6import java.awt.Rectangle;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.List;
10
11import org.openstreetmap.josm.Main;
12import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
13import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
14import org.openstreetmap.josm.data.osm.Node;
15import org.openstreetmap.josm.data.osm.Relation;
16import org.openstreetmap.josm.data.osm.RelationMember;
17import org.openstreetmap.josm.data.osm.Way;
18import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
19import org.openstreetmap.josm.gui.NavigatableComponent;
20
21public class Multipolygon {
22 /** preference key for a collection of roles which indicate that the respective member belongs to an
23 * <em>outer</em> polygon. Default is <tt>outer</tt>.
24 */
25 static public final String PREF_KEY_OUTER_ROLES = "mappaint.multipolygon.outer.roles";
26 /** preference key for collection of role prefixes which indicate that the respective
27 * member belongs to an <em>outer</em> polygon. Default is empty.
28 */
29 static public final String PREF_KEY_OUTER_ROLE_PREFIXES = "mappaint.multipolygon.outer.role-prefixes";
30 /** preference key for a collection of roles which indicate that the respective member belongs to an
31 * <em>inner</em> polygon. Default is <tt>inner</tt>.
32 */
33 static public final String PREF_KEY_INNER_ROLES = "mappaint.multipolygon.inner.roles";
34 /** preference key for collection of role prefixes which indicate that the respective
35 * member belongs to an <em>inner</em> polygon. Default is empty.
36 */
37 static public final String PREF_KEY_INNER_ROLE_PREFIXES = "mappaint.multipolygon.inner.role-prefixes";
38
39 /**
40 * <p>Kind of strategy object which is responsible for deciding whether a given
41 * member role indicates that the member belongs to an <em>outer</em> or an
42 * <em>inner</em> polygon.</p>
43 *
44 * <p>The decision is taken based on preference settings, see the four preference keys
45 * above.</p>
46 *
47 */
48 private static class MultipolygonRoleMatcher implements PreferenceChangedListener{
49 private final List<String> outerExactRoles = new ArrayList<String>();
50 private final List<String> outerRolePrefixes = new ArrayList<String>();
51 private final List<String> innerExactRoles = new ArrayList<String>();
52 private final List<String> innerRolePrefixes = new ArrayList<String>();
53
54 private void initDefaults() {
55 outerExactRoles.clear();
56 outerRolePrefixes.clear();
57 innerExactRoles.clear();
58 innerRolePrefixes.clear();
59 outerExactRoles.add("outer");
60 innerExactRoles.add("inner");
61 }
62
63 private void setNormalized(Collection<String> literals, List<String> target){
64 target.clear();
65 for(String l: literals) {
66 if (l == null) {
67 continue;
68 }
69 l = l.trim();
70 if (!target.contains(l)) {
71 target.add(l);
72 }
73 }
74 }
75
76 private void initFromPreferences() {
77 initDefaults();
78 if (Main.pref == null) return;
79 Collection<String> literals;
80 literals = Main.pref.getCollection(PREF_KEY_OUTER_ROLES);
81 if (literals != null && !literals.isEmpty()){
82 setNormalized(literals, outerExactRoles);
83 }
84 literals = Main.pref.getCollection(PREF_KEY_OUTER_ROLE_PREFIXES);
85 if (literals != null && !literals.isEmpty()){
86 setNormalized(literals, outerRolePrefixes);
87 }
88 literals = Main.pref.getCollection(PREF_KEY_INNER_ROLES);
89 if (literals != null && !literals.isEmpty()){
90 setNormalized(literals, innerExactRoles);
91 }
92 literals = Main.pref.getCollection(PREF_KEY_INNER_ROLE_PREFIXES);
93 if (literals != null && !literals.isEmpty()){
94 setNormalized(literals, innerRolePrefixes);
95 }
96 }
97
98 @Override
99 public void preferenceChanged(PreferenceChangeEvent evt) {
100 if (PREF_KEY_INNER_ROLE_PREFIXES.equals(evt.getKey()) ||
101 PREF_KEY_INNER_ROLES.equals(evt.getKey()) ||
102 PREF_KEY_OUTER_ROLE_PREFIXES.equals(evt.getKey()) ||
103 PREF_KEY_OUTER_ROLES.equals(evt.getKey())){
104 initFromPreferences();
105 }
106 }
107
108 public boolean isOuterRole(String role){
109 if (role == null) return false;
110 for (String candidate: outerExactRoles) {
111 if (role.equals(candidate)) return true;
112 }
113 for (String candidate: outerRolePrefixes) {
114 if (role.startsWith(candidate)) return true;
115 }
116 return false;
117 }
118
119 public boolean isInnerRole(String role){
120 if (role == null) return false;
121 for (String candidate: innerExactRoles) {
122 if (role.equals(candidate)) return true;
123 }
124 for (String candidate: innerRolePrefixes) {
125 if (role.startsWith(candidate)) return true;
126 }
127 return false;
128 }
129 }
130
131 /*
132 * Init a private global matcher object which will listen to preference
133 * changes.
134 */
135 private static MultipolygonRoleMatcher roleMatcher;
136 private static MultipolygonRoleMatcher getMultipoloygonRoleMatcher() {
137 if (roleMatcher == null) {
138 roleMatcher = new MultipolygonRoleMatcher();
139 if (Main.pref != null){
140 roleMatcher.initFromPreferences();
141 Main.pref.addPreferenceChangeListener(roleMatcher);
142 }
143 }
144 return roleMatcher;
145 }
146
147 public static class JoinedWay {
148 private final List<Node> nodes;
149 private final boolean selected;
150
151 public JoinedWay(List<Node> nodes, boolean selected) {
152 this.nodes = nodes;
153 this.selected = selected;
154 }
155
156 public List<Node> getNodes() {
157 return nodes;
158 }
159
160 public boolean isSelected() {
161 return selected;
162 }
163
164 public boolean isClosed() {
165 return nodes.isEmpty() || nodes.get(nodes.size() - 1).equals(nodes.get(0));
166 }
167 }
168
169 public static class PolyData {
170 public enum Intersection {INSIDE, OUTSIDE, CROSSING}
171
172 public Polygon poly = new Polygon();
173 public final boolean selected;
174 private Point lastP;
175 private Rectangle bounds;
176
177 public PolyData(NavigatableComponent nc, JoinedWay joinedWay) {
178 this(nc, joinedWay.getNodes(), joinedWay.isSelected());
179 }
180
181 public PolyData(NavigatableComponent nc, List<Node> nodes, boolean selected) {
182 this.selected = selected;
183 Point p = null;
184 for (Node n : nodes)
185 {
186 p = nc.getPoint(n);
187 poly.addPoint(p.x,p.y);
188 }
189 if (!nodes.get(0).equals(nodes.get(nodes.size() - 1))) {
190 p = nc.getPoint(nodes.get(0));
191 poly.addPoint(p.x, p.y);
192 }
193 lastP = p;
194 }
195
196 public PolyData(PolyData copy) {
197 poly = new Polygon(copy.poly.xpoints, copy.poly.ypoints, copy.poly.npoints);
198 this.selected = copy.selected;
199 lastP = copy.lastP;
200 }
201
202 public Intersection contains(Polygon p) {
203 int contains = p.npoints;
204 for(int i = 0; i < p.npoints; ++i)
205 {
206 if(poly.contains(p.xpoints[i],p.ypoints[i])) {
207 --contains;
208 }
209 }
210 if(contains == 0) return Intersection.INSIDE;
211 if(contains == p.npoints) return Intersection.OUTSIDE;
212 return Intersection.CROSSING;
213 }
214
215 public void addInner(Polygon p) {
216 for(int i = 0; i < p.npoints; ++i) {
217 poly.addPoint(p.xpoints[i],p.ypoints[i]);
218 }
219 poly.addPoint(lastP.x, lastP.y);
220 }
221
222 public Polygon get() {
223 return poly;
224 }
225
226 public Rectangle getBounds() {
227 if (bounds == null) {
228 bounds = poly.getBounds();
229 }
230 return bounds;
231 }
232
233 @Override
234 public String toString() {
235 return "Points: " + poly.npoints + " Selected: " + selected;
236 }
237 }
238
239 private final NavigatableComponent nc;
240
241 private final List<Way> innerWays = new ArrayList<Way>();
242 private final List<Way> outerWays = new ArrayList<Way>();
243 private final List<PolyData> innerPolygons = new ArrayList<PolyData>();
244 private final List<PolyData> outerPolygons = new ArrayList<PolyData>();
245 private final List<PolyData> combinedPolygons = new ArrayList<PolyData>();
246
247 public Multipolygon(NavigatableComponent nc) {
248 this.nc = nc;
249 }
250
251 public void load(Relation r) {
252 MultipolygonRoleMatcher matcher = getMultipoloygonRoleMatcher();
253
254 // Fill inner and outer list with valid ways
255 for (RelationMember m : r.getMembers()) {
256 if (m.getMember().isDrawable()) {
257 if(m.isWay()) {
258 Way w = m.getWay();
259
260 if(w.getNodesCount() < 2) {
261 continue;
262 }
263
264 if(matcher.isInnerRole(m.getRole())) {
265 innerWays.add(w);
266 } else if(matcher.isOuterRole(m.getRole())) {
267 outerWays.add(w);
268 } else if (!m.hasRole()) {
269 outerWays.add(w);
270 } // Remaining roles ignored
271 } // Non ways ignored
272 }
273 }
274
275 createPolygons(innerWays, innerPolygons);
276 createPolygons(outerWays, outerPolygons);
277 if (!outerPolygons.isEmpty()) {
278 addInnerToOuters();
279 }
280 }
281
282 private void createPolygons(List<Way> ways, List<PolyData> result) {
283 List<Way> waysToJoin = new ArrayList<Way>();
284 for (Way way: ways) {
285 if (way.isClosed()) {
286 result.add(new PolyData(nc, way.getNodes(), way.isSelected()));
287 } else {
288 waysToJoin.add(way);
289 }
290 }
291
292 for (JoinedWay jw: joinWays(waysToJoin)) {
293 result.add(new PolyData(nc, jw));
294 }
295 }
296
297 public static Collection<JoinedWay> joinWays(Collection<Way> join)
298 {
299 Collection<JoinedWay> res = new ArrayList<JoinedWay>();
300 Way[] joinArray = join.toArray(new Way[join.size()]);
301 int left = join.size();
302 while(left != 0)
303 {
304 Way w = null;
305 boolean selected = false;
306 List<Node> n = null;
307 boolean joined = true;
308 while(joined && left != 0)
309 {
310 joined = false;
311 for(int i = 0; i < joinArray.length && left != 0; ++i)
312 {
313 if(joinArray[i] != null)
314 {
315 Way c = joinArray[i];
316 if(w == null)
317 { w = c; selected = w.isSelected(); joinArray[i] = null; --left; }
318 else
319 {
320 int mode = 0;
321 int cl = c.getNodesCount()-1;
322 int nl;
323 if(n == null)
324 {
325 nl = w.getNodesCount()-1;
326 if(w.getNode(nl) == c.getNode(0)) {
327 mode = 21;
328 } else if(w.getNode(nl) == c.getNode(cl)) {
329 mode = 22;
330 } else if(w.getNode(0) == c.getNode(0)) {
331 mode = 11;
332 } else if(w.getNode(0) == c.getNode(cl)) {
333 mode = 12;
334 }
335 }
336 else
337 {
338 nl = n.size()-1;
339 if(n.get(nl) == c.getNode(0)) {
340 mode = 21;
341 } else if(n.get(0) == c.getNode(cl)) {
342 mode = 12;
343 } else if(n.get(0) == c.getNode(0)) {
344 mode = 11;
345 } else if(n.get(nl) == c.getNode(cl)) {
346 mode = 22;
347 }
348 }
349 if(mode != 0)
350 {
351 joinArray[i] = null;
352 joined = true;
353 if(c.isSelected()) {
354 selected = true;
355 }
356 --left;
357 if(n == null) {
358 n = w.getNodes();
359 }
360 n.remove((mode == 21 || mode == 22) ? nl : 0);
361 if(mode == 21) {
362 n.addAll(c.getNodes());
363 } else if(mode == 12) {
364 n.addAll(0, c.getNodes());
365 } else if(mode == 22)
366 {
367 for(Node node : c.getNodes()) {
368 n.add(nl, node);
369 }
370 }
371 else /* mode == 11 */
372 {
373 for(Node node : c.getNodes()) {
374 n.add(0, node);
375 }
376 }
377 }
378 }
379 }
380 } /* for(i = ... */
381 } /* while(joined) */
382
383 if (n == null) {
384 n = w.getNodes();
385 }
386
387 res.add(new JoinedWay(n, selected));
388 } /* while(left != 0) */
389
390 return res;
391 }
392
393 public PolyData findOuterPolygon(PolyData inner, List<PolyData> outerPolygons) {
394 PolyData result = null;
395
396 {// First try to test only bbox, use precise testing only if we don't get unique result
397 Rectangle innerBox = inner.getBounds();
398 PolyData insidePolygon = null;
399 PolyData intersectingPolygon = null;
400 int insideCount = 0;
401 int intersectingCount = 0;
402
403 for (PolyData outer: outerPolygons) {
404 if (outer.getBounds().contains(innerBox)) {
405 insidePolygon = outer;
406 insideCount++;
407 } else if (outer.getBounds().intersects(innerBox)) {
408 intersectingPolygon = outer;
409 intersectingCount++;
410 }
411 }
412
413 if (insideCount == 1)
414 return insidePolygon;
415 else if (intersectingCount == 1)
416 return intersectingPolygon;
417 }
418
419 for (PolyData combined : outerPolygons) {
420 Intersection c = combined.contains(inner.poly);
421 if(c != Intersection.OUTSIDE)
422 {
423 if(result == null || result.contains(combined.poly) != Intersection.INSIDE) {
424 result = combined;
425 }
426 }
427 }
428 return result;
429 }
430
431 private void addInnerToOuters() {
432
433 if (innerPolygons.isEmpty()) {
434 combinedPolygons.addAll(outerPolygons);
435 } else if (outerPolygons.size() == 1) {
436 PolyData combinedOuter = new PolyData(outerPolygons.get(0));
437 for (PolyData inner: innerPolygons) {
438 combinedOuter.addInner(inner.poly);
439 }
440 combinedPolygons.add(combinedOuter);
441 } else {
442 for (PolyData outer: outerPolygons) {
443 combinedPolygons.add(new PolyData(outer));
444 }
445
446 for (PolyData pdInner: innerPolygons) {
447 PolyData o = findOuterPolygon(pdInner, combinedPolygons);
448 if(o == null) {
449 o = outerPolygons.get(0);
450 }
451 o.addInner(pdInner.poly);
452 }
453 }
454 }
455
456 public List<Way> getOuterWays() {
457 return outerWays;
458 }
459
460 public List<Way> getInnerWays() {
461 return innerWays;
462 }
463
464 public List<PolyData> getInnerPolygons() {
465 return innerPolygons;
466 }
467
468 public List<PolyData> getOuterPolygons() {
469 return outerPolygons;
470 }
471
472 public List<PolyData> getCombinedPolygons() {
473 return combinedPolygons;
474 }
475
476}
Note: See TracBrowser for help on using the repository browser.