Changeset 26811 in osm for applications
- Timestamp:
- 2011-10-09T09:51:22+02:00 (13 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/reltoolbox/src/relcontext/actions/CreateMultipolygonAction.java
r26290 r26811 11 11 import org.openstreetmap.josm.Main; 12 12 import org.openstreetmap.josm.actions.JosmAction; 13 import org.openstreetmap.josm.actions.SplitWayAction; 13 14 import org.openstreetmap.josm.command.*; 15 import org.openstreetmap.josm.data.coor.EastNorth; 14 16 import org.openstreetmap.josm.data.osm.*; 17 import org.openstreetmap.josm.data.osm.MultipolygonCreate.JoinedPolygon; 18 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 19 import org.openstreetmap.josm.gui.DefaultNameFormatter; 15 20 import org.openstreetmap.josm.tools.GBC; 21 import org.openstreetmap.josm.tools.Geometry; 22 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection; 16 23 import org.openstreetmap.josm.tools.Shortcut; 17 24 import relcontext.ChosenRelation; … … 29 36 30 37 public CreateMultipolygonAction( ChosenRelation chRel ) { 31 32 33 34 35 38 super("Multi", "data/multipolygon", tr("Create a multipolygon from selected objects"), 39 Shortcut.registerShortcut("reltoolbox:multipolygon", tr("Relation Toolbox: {0}", tr("Create multipolygon")), 40 KeyEvent.VK_B, Shortcut.GROUP_HOTKEY), true); 41 this.chRel = chRel; 42 updateEnabledState(); 36 43 } 37 44 38 45 public CreateMultipolygonAction() { 39 40 } 41 46 this(null); 47 } 48 42 49 public static boolean getDefaultPropertyValue( String property ) { 43 if( property.equals("boundary") ) return false; 44 else if( property.equals("boundaryways") ) return true; 45 else if( property.equals("tags") ) return true; 46 else if( property.equals("alltags") ) return false; 47 else if( property.equals("single") ) return true; 48 throw new IllegalArgumentException(property); 50 if( property.equals("boundary") ) 51 return false; 52 else if( property.equals("boundaryways") ) 53 return true; 54 else if( property.equals("tags") ) 55 return true; 56 else if( property.equals("alltags") ) 57 return false; 58 else if( property.equals("single") ) 59 return true; 60 throw new IllegalArgumentException(property); 49 61 } 50 62 51 63 private boolean getPref( String property ) { 52 64 return Main.pref.getBoolean(PREF_MULTIPOLY + property, getDefaultPropertyValue(property)); 53 65 } 54 66 55 67 public void actionPerformed( ActionEvent e ) { 56 // for now, just copying standard action 57 MultipolygonCreate mpc = new MultipolygonCreate(); 58 String error = mpc.makeFromWays(getCurrentDataSet().getSelectedWays()); 59 if( error != null ) { 60 JOptionPane.showMessageDialog(Main.parent, error); 61 return; 62 } 63 Relation rel = new Relation(); 64 boolean isBoundary = getPref("boundary"); 65 if( isBoundary ) { 66 rel.put("type", "boundary"); 67 rel.put("boundary", "administrative"); 68 } else 69 rel.put("type", "multipolygon"); 70 for( MultipolygonCreate.JoinedPolygon poly : mpc.outerWays ) 71 for( Way w : poly.ways ) 72 rel.addMember(new RelationMember("outer", w)); 73 for( MultipolygonCreate.JoinedPolygon poly : mpc.innerWays ) 74 for( Way w : poly.ways ) 75 rel.addMember(new RelationMember("inner", w)); 76 List<Command> list = removeTagsFromInnerWays(rel); 77 if( !list.isEmpty() && isBoundary ) { 78 Main.main.undoRedo.add(new SequenceCommand(tr("Move tags from ways to relation"), list)); 79 list = new ArrayList<Command>(); 80 } 81 if( isBoundary ) { 82 if( !askForAdminLevelAndName(rel) ) 83 return; 84 addBoundaryMembers(rel); 85 if( getPref("boundaryways") ) 86 list.addAll(fixWayTagsForBoundary(rel)); 87 } 88 list.add(new AddCommand(rel)); 89 Main.main.undoRedo.add(new SequenceCommand(tr("Create multipolygon"), list)); 90 91 if( chRel != null ) 92 chRel.set(rel); 93 94 getCurrentDataSet().setSelected(rel); 68 boolean isBoundary = getPref("boundary"); 69 Collection<Way> selectedWays = getCurrentDataSet().getSelectedWays(); 70 if( !isBoundary && getPref("tags") ) { 71 if( selectedWays.size() == 1 && !selectedWays.iterator().next().isClosed() ) { 72 Relation newRelation = tryToCloseOneWay(selectedWays.iterator().next()); 73 if( newRelation != null ) { 74 if( chRel != null ) 75 chRel.set(newRelation); 76 return; 77 } 78 } 79 if( areAllOfThoseRings(getCurrentDataSet().getSelectedWays()) ) { 80 List<Relation> rels = makeManySimpleMultipolygons(getCurrentDataSet().getSelectedWays()); 81 if( chRel != null ) 82 chRel.set(rels.size() == 1 ? rels.get(0) : null); 83 return; 84 } 85 } 86 87 // for now, just copying standard action 88 MultipolygonCreate mpc = new MultipolygonCreate(); 89 String error = mpc.makeFromWays(getCurrentDataSet().getSelectedWays()); 90 if( error != null ) { 91 JOptionPane.showMessageDialog(Main.parent, error); 92 return; 93 } 94 Relation rel = new Relation(); 95 if( isBoundary ) { 96 rel.put("type", "boundary"); 97 rel.put("boundary", "administrative"); 98 } else 99 rel.put("type", "multipolygon"); 100 for( MultipolygonCreate.JoinedPolygon poly : mpc.outerWays ) 101 for( Way w : poly.ways ) 102 rel.addMember(new RelationMember("outer", w)); 103 for( MultipolygonCreate.JoinedPolygon poly : mpc.innerWays ) 104 for( Way w : poly.ways ) 105 rel.addMember(new RelationMember("inner", w)); 106 List<Command> list = removeTagsFromInnerWays(rel); 107 if( !list.isEmpty() && isBoundary ) { 108 Main.main.undoRedo.add(new SequenceCommand(tr("Move tags from ways to relation"), list)); 109 list = new ArrayList<Command>(); 110 } 111 if( isBoundary ) { 112 if( !askForAdminLevelAndName(rel) ) 113 return; 114 addBoundaryMembers(rel); 115 if( getPref("boundaryways") ) 116 list.addAll(fixWayTagsForBoundary(rel)); 117 } 118 list.add(new AddCommand(rel)); 119 Main.main.undoRedo.add(new SequenceCommand(tr("Create multipolygon"), list)); 120 121 if( chRel != null ) 122 chRel.set(rel); 123 124 getCurrentDataSet().setSelected(rel); 95 125 } 96 126 97 127 @Override 98 128 protected void updateEnabledState() { 99 100 101 102 103 129 if( getCurrentDataSet() == null ) { 130 setEnabled(false); 131 } else { 132 updateEnabledState(getCurrentDataSet().getSelected()); 133 } 104 134 } 105 135 106 136 @Override 107 137 protected void updateEnabledState( Collection<? extends OsmPrimitive> selection ) { 108 boolean enabled = true;109 110 enabled = false;111 112 113 114 115 enabled = false;116 117 118 119 120 121 setEnabled(enabled);138 boolean isEnabled = true; 139 if( selection == null || selection.isEmpty() ) 140 isEnabled = false; 141 else { 142 if( !getPref("boundary") ) { 143 for( OsmPrimitive p : selection ) { 144 if( !(p instanceof Way) ) { 145 isEnabled = false; 146 break; 147 } 148 } 149 } 150 } 151 setEnabled(isEnabled); 122 152 } 123 153 … … 126 156 */ 127 157 private void addBoundaryMembers( Relation rel ) { 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 158 for( OsmPrimitive p : getCurrentDataSet().getSelected() ) { 159 String role = null; 160 if( p.getType().equals(OsmPrimitiveType.RELATION) ) { 161 role = "subarea"; 162 } else if( p.getType().equals(OsmPrimitiveType.NODE) ) { 163 Node n = (Node)p; 164 if( !n.isIncomplete() ) { 165 if( n.hasKey("place") ) 166 role = "admin_centre"; 167 else 168 role = "label"; 169 } 170 } 171 if( role != null ) 172 rel.addMember(new RelationMember(role, p)); 173 } 144 174 } 145 175 … … 148 178 */ 149 179 private List<Command> fixWayTagsForBoundary( Relation rel ) { 150 List<Command> commands = new ArrayList<Command>(); 151 if( !rel.hasKey("boundary") || !rel.hasKey("admin_level") ) 152 return commands; 153 String adminLevelStr = rel.get("admin_level"); 154 int adminLevel = 0; 155 try { 156 adminLevel = Integer.parseInt(adminLevelStr); 157 } catch( NumberFormatException e ) { 158 return commands; 159 } 160 Set<OsmPrimitive> waysBoundary = new HashSet<OsmPrimitive>(); 161 Set<OsmPrimitive> waysAdminLevel = new HashSet<OsmPrimitive>(); 162 for( OsmPrimitive p : rel.getMemberPrimitives() ) { 163 if( p instanceof Way ) { 164 int count = 0; 165 if( p.hasKey("boundary") && p.get("boundary").equals("administrative") ) 166 count++; 167 if( p.hasKey("admin_level") ) 168 count++; 169 if( p.keySet().size() - count == 0 ) { 170 if( !p.hasKey("boundary") ) 171 waysBoundary.add(p); 172 if( !p.hasKey("admin_level") ) { 173 waysAdminLevel.add(p); 174 } else { 175 try { 176 int oldAdminLevel = Integer.parseInt(p.get("admin_level")); 177 if( oldAdminLevel > adminLevel ) 178 waysAdminLevel.add(p); 179 } catch( NumberFormatException e ) { 180 waysAdminLevel.add(p); // some garbage, replace it 181 } 182 } 183 } 184 } 185 } 186 if( !waysBoundary.isEmpty() ) 187 commands.add(new ChangePropertyCommand(waysBoundary, "boundary", "administrative")); 188 if( !waysAdminLevel.isEmpty() ) 189 commands.add(new ChangePropertyCommand(waysAdminLevel, "admin_level", adminLevelStr)); 190 return commands; 191 } 192 193 static public final String[] DEFAULT_LINEAR_TAGS = {"barrier", "source"}; 194 180 List<Command> commands = new ArrayList<Command>(); 181 if( !rel.hasKey("boundary") || !rel.hasKey("admin_level") ) 182 return commands; 183 String adminLevelStr = rel.get("admin_level"); 184 int adminLevel = 0; 185 try { 186 adminLevel = Integer.parseInt(adminLevelStr); 187 } catch( NumberFormatException e ) { 188 return commands; 189 } 190 Set<OsmPrimitive> waysBoundary = new HashSet<OsmPrimitive>(); 191 Set<OsmPrimitive> waysAdminLevel = new HashSet<OsmPrimitive>(); 192 for( OsmPrimitive p : rel.getMemberPrimitives() ) { 193 if( p instanceof Way ) { 194 int count = 0; 195 if( p.hasKey("boundary") && p.get("boundary").equals("administrative") ) 196 count++; 197 if( p.hasKey("admin_level") ) 198 count++; 199 if( p.keySet().size() - count == 0 ) { 200 if( !p.hasKey("boundary") ) 201 waysBoundary.add(p); 202 if( !p.hasKey("admin_level") ) { 203 waysAdminLevel.add(p); 204 } else { 205 try { 206 int oldAdminLevel = Integer.parseInt(p.get("admin_level")); 207 if( oldAdminLevel > adminLevel ) 208 waysAdminLevel.add(p); 209 } catch( NumberFormatException e ) { 210 waysAdminLevel.add(p); // some garbage, replace it 211 } 212 } 213 } 214 } 215 } 216 if( !waysBoundary.isEmpty() ) 217 commands.add(new ChangePropertyCommand(waysBoundary, "boundary", "administrative")); 218 if( !waysAdminLevel.isEmpty() ) 219 commands.add(new ChangePropertyCommand(waysAdminLevel, "admin_level", adminLevelStr)); 220 return commands; 221 } 222 static public final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList(new String[] {"barrier", "source"}); 195 223 private static final Set<String> REMOVE_FROM_BOUNDARY_TAGS = new TreeSet<String>(Arrays.asList(new String[] { 196 197 }));224 "boundary", "boundary_type", "type", "admin_level" 225 })); 198 226 199 227 /** … … 202 230 * Todo: rewrite it. 203 231 */ 204 private List<Command> removeTagsFromInnerWays( Relation relation) {205 206 207 if (relation.hasKeys()){208 for(String key: relation.keySet()) {209 210 211 212 213 214 215 216 217 218 for (RelationMember m: relation.getMembers()) {219 220 if (m.hasRole() && m.getRole() == "inner" && m.isWay() && m.getWay().hasKeys()) {221 222 223 224 if (m.hasRole() && m.getRole() == "outer" && m.isWay() && m.getWay().hasKeys()) {225 226 227 for (String key: way.keySet()) {228 if (!values.containsKey(key)) { //relation values take precedence229 230 231 232 233 234 235 236 237 238 239 240 for( RelationMember m: relation.getMembers() )241 242 243 244 245 246 247 248 249 for( String linearTag : Main.pref.getCollection(PREF_MULTIPOLY + "lineartags", Arrays.asList(DEFAULT_LINEAR_TAGS)))250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 for(String key: values.keySet()) {269 270 271 272 for (Way way: innerWays) {273 274 275 276 277 278 279 280 for (Way way: outerWays) {281 if (way.hasKey(key)) {282 283 284 285 286 287 if (affectedWays.size() > 0) {288 289 290 291 292 293 294 295 296 297 298 299 300 && (!isBoundary || key.equals("admin_level") || key.equals("name"))) {301 302 303 304 305 306 307 308 309 310 311 312 232 private List<Command> removeTagsFromInnerWays( Relation relation ) { 233 Map<String, String> values = new HashMap<String, String>(); 234 235 if( relation.hasKeys() ) { 236 for( String key : relation.keySet() ) { 237 values.put(key, relation.get(key)); 238 } 239 } 240 241 List<Way> innerWays = new ArrayList<Way>(); 242 List<Way> outerWays = new ArrayList<Way>(); 243 244 Set<String> conflictingKeys = new TreeSet<String>(); 245 246 for( RelationMember m : relation.getMembers() ) { 247 248 if( m.hasRole() && "inner".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys() ) { 249 innerWays.add(m.getWay()); 250 } 251 252 if( m.hasRole() && "outer".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys() ) { 253 Way way = m.getWay(); 254 outerWays.add(way); 255 for( String key : way.keySet() ) { 256 if( !values.containsKey(key) ) { //relation values take precedence 257 values.put(key, way.get(key)); 258 } else if( !relation.hasKey(key) && !values.get(key).equals(way.get(key)) ) { 259 conflictingKeys.add(key); 260 } 261 } 262 } 263 } 264 265 // filter out empty key conflicts - we need second iteration 266 boolean isBoundary = getPref("boundary"); 267 if( isBoundary || !getPref("alltags") ) 268 for( RelationMember m : relation.getMembers() ) 269 if( m.hasRole() && m.getRole().equals("outer") && m.isWay() ) 270 for( String key : values.keySet() ) 271 if( !m.getWay().hasKey(key) && !relation.hasKey(key) ) 272 conflictingKeys.add(key); 273 274 for( String key : conflictingKeys ) 275 values.remove(key); 276 277 for( String linearTag : Main.pref.getCollection(PREF_MULTIPOLY + "lineartags", DEFAULT_LINEAR_TAGS) ) 278 values.remove(linearTag); 279 280 if( values.containsKey("natural") && values.get("natural").equals("coastline") ) 281 values.remove("natural"); 282 283 String name = values.get("name"); 284 if( isBoundary ) { 285 Set<String> keySet = new TreeSet<String>(values.keySet()); 286 for( String key : keySet ) 287 if( !REMOVE_FROM_BOUNDARY_TAGS.contains(key) ) 288 values.remove(key); 289 } 290 291 values.put("area", "yes"); 292 293 List<Command> commands = new ArrayList<Command>(); 294 boolean moveTags = getPref("tags"); 295 296 for( String key : values.keySet() ) { 297 List<OsmPrimitive> affectedWays = new ArrayList<OsmPrimitive>(); 298 String value = values.get(key); 299 300 for( Way way : innerWays ) { 301 if( way.hasKey(key) && (isBoundary || value.equals(way.get(key))) ) { 302 affectedWays.add(way); 303 } 304 } 305 306 if( moveTags ) { 307 // remove duplicated tags from outer ways 308 for( Way way : outerWays ) { 309 if( way.hasKey(key) ) { 310 affectedWays.add(way); 311 } 312 } 313 } 314 315 if( affectedWays.size() > 0 ) { 316 commands.add(new ChangePropertyCommand(affectedWays, key, null)); 317 } 318 } 319 320 if( moveTags ) { 321 // add those tag values to the relation 322 if( isBoundary ) 323 values.put("name", name); 324 boolean fixed = false; 325 Relation r2 = new Relation(relation); 326 for( String key : values.keySet() ) { 327 if( !r2.hasKey(key) && !key.equals("area") 328 && (!isBoundary || key.equals("admin_level") || key.equals("name")) ) { 329 if( relation.isNew() ) 330 relation.put(key, values.get(key)); 331 else 332 r2.put(key, values.get(key)); 333 fixed = true; 334 } 335 } 336 if( fixed && !relation.isNew() ) 337 commands.add(new ChangeCommand(relation, r2)); 338 } 339 340 return commands; 313 341 } 314 342 … … 319 347 */ 320 348 private boolean askForAdminLevelAndName( Relation rel ) { 321 String relAL = rel.get("admin_level"); 322 String relName = rel.get("name"); 323 if( relAL != null && relName != null ) 324 return true; 325 326 JPanel panel = new JPanel(new GridBagLayout()); 327 panel.add(new JLabel(tr("Enter admin level and name for the border relation:")), GBC.eol().insets(0, 0, 0, 5)); 328 329 final JTextField admin = new JTextField(); 330 admin.setText(relAL != null ? relAL : Main.pref.get(PREF_MULTIPOLY + "lastadmin", "")); 331 panel.add(new JLabel(tr("Admin level")), GBC.std()); 332 panel.add(Box.createHorizontalStrut(10), GBC.std()); 333 panel.add(admin, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 5)); 334 335 final JTextField name = new JTextField(); 336 if( relName != null ) 337 name.setText(relName); 338 panel.add(new JLabel(tr("Name")), GBC.std()); 339 panel.add(Box.createHorizontalStrut(10), GBC.std()); 340 panel.add(name, GBC.eol().fill(GBC.HORIZONTAL)); 341 342 final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION) { 343 @Override 344 public void selectInitialValue() { 345 admin.requestFocusInWindow(); 346 admin.selectAll(); 347 } 348 }; 349 final JDialog dlg = optionPane.createDialog(Main.parent, tr("Create a new relation")); 350 dlg.setModalityType(ModalityType.DOCUMENT_MODAL); 351 352 name.addActionListener(new ActionListener() { 353 public void actionPerformed( ActionEvent e ) { 354 dlg.setVisible(false); 355 optionPane.setValue(JOptionPane.OK_OPTION); 356 } 357 }); 358 359 dlg.setVisible(true); 360 361 Object answer = optionPane.getValue(); 362 if( answer == null || answer == JOptionPane.UNINITIALIZED_VALUE 363 || (answer instanceof Integer && (Integer)answer != JOptionPane.OK_OPTION) ) { 364 return false; 365 } 366 367 String admin_level = admin.getText().trim(); 368 String new_name = name.getText().trim(); 369 if( admin_level.equals("10") || (admin_level.length() == 1 && Character.isDigit(admin_level.charAt(0))) ) { 370 rel.put("admin_level", admin_level); 371 Main.pref.put(PREF_MULTIPOLY + "lastadmin", admin_level); 372 } 373 if( new_name.length() > 0 ) 374 rel.put("name", new_name); 375 return true; 349 String relAL = rel.get("admin_level"); 350 String relName = rel.get("name"); 351 if( relAL != null && relName != null ) 352 return true; 353 354 JPanel panel = new JPanel(new GridBagLayout()); 355 panel.add(new JLabel(tr("Enter admin level and name for the border relation:")), GBC.eol().insets(0, 0, 0, 5)); 356 357 final JTextField admin = new JTextField(); 358 admin.setText(relAL != null ? relAL : Main.pref.get(PREF_MULTIPOLY + "lastadmin", "")); 359 panel.add(new JLabel(tr("Admin level")), GBC.std()); 360 panel.add(Box.createHorizontalStrut(10), GBC.std()); 361 panel.add(admin, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 5)); 362 363 final JTextField name = new JTextField(); 364 if( relName != null ) 365 name.setText(relName); 366 panel.add(new JLabel(tr("Name")), GBC.std()); 367 panel.add(Box.createHorizontalStrut(10), GBC.std()); 368 panel.add(name, GBC.eol().fill(GBC.HORIZONTAL)); 369 370 final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION) { 371 @Override 372 public void selectInitialValue() { 373 admin.requestFocusInWindow(); 374 admin.selectAll(); 375 } 376 }; 377 final JDialog dlg = optionPane.createDialog(Main.parent, tr("Create a new relation")); 378 dlg.setModalityType(ModalityType.DOCUMENT_MODAL); 379 380 name.addActionListener(new ActionListener() { 381 public void actionPerformed( ActionEvent e ) { 382 dlg.setVisible(false); 383 optionPane.setValue(JOptionPane.OK_OPTION); 384 } 385 }); 386 387 dlg.setVisible(true); 388 389 Object answer = optionPane.getValue(); 390 if( answer == null || answer == JOptionPane.UNINITIALIZED_VALUE 391 || (answer instanceof Integer && (Integer)answer != JOptionPane.OK_OPTION) ) { 392 return false; 393 } 394 395 String admin_level = admin.getText().trim(); 396 String new_name = name.getText().trim(); 397 if( admin_level.equals("10") || (admin_level.length() == 1 && Character.isDigit(admin_level.charAt(0))) ) { 398 rel.put("admin_level", admin_level); 399 Main.pref.put(PREF_MULTIPOLY + "lastadmin", admin_level); 400 } 401 if( new_name.length() > 0 ) 402 rel.put("name", new_name); 403 return true; 404 } 405 406 private boolean areAllOfThoseRings( Collection<Way> ways ) { 407 List<Way> rings = new ArrayList<Way>(); 408 List<Way> otherWays = new ArrayList<Way>(); 409 for( Way way : ways ) { 410 if( way.isClosed() ) 411 rings.add(way); 412 else 413 otherWays.add(way); 414 } 415 if( rings.isEmpty() || ways.size() == 1 ) 416 return false; // todo: for one ring, attach it to neares multipolygons 417 418 // check that every segment touches just one ring 419 for( Way segment : otherWays ) { 420 boolean found = false; 421 for( Way ring : rings ) { 422 System.out.println("segment " + segment.getId() + ", ring " + ring.getId()); 423 System.out.println("ring.containsNode(segment.firstNode()) = " + ring.containsNode(segment.firstNode())); 424 System.out.println("ring.containsNode(segment.lastNode() = " + ring.containsNode(segment.lastNode())); 425 System.out.println("segmentInsidePolygon(segment.getNode(0), segment.getNode(1), ring.getNodes()) = " + segmentInsidePolygon(segment.getNode(0), segment.getNode(1), ring.getNodes())); 426 if( ring.containsNode(segment.firstNode()) && ring.containsNode(segment.lastNode()) 427 && !segmentInsidePolygon(segment.getNode(0), segment.getNode(1), ring.getNodes()) ) 428 found = true; 429 } 430 if( !found ) 431 return false; 432 } 433 434 // check for non-containment of rings 435 for( int i = 0; i < rings.size() - 1; i++ ) { 436 for( int j = i + 1; j < rings.size(); j++ ) { 437 PolygonIntersection intersection = Geometry.polygonIntersection(rings.get(i).getNodes(), rings.get(j).getNodes()); 438 if( intersection == PolygonIntersection.FIRST_INSIDE_SECOND || intersection == PolygonIntersection.SECOND_INSIDE_FIRST ) 439 return false; 440 } 441 } 442 443 return true; 444 } 445 446 /** 447 * Creates ALOT of Multipolygons and pets him gently. 448 * @return list of new relations. 449 */ 450 private List<Relation> makeManySimpleMultipolygons( Collection<Way> selection ) { 451 List<Command> commands = new ArrayList<Command>(); 452 List<Way> ways = new ArrayList<Way>(selection.size()); 453 Map<Way, Way> wayDiff = new HashMap<Way, Way>(selection.size()); 454 List<Relation> relations = new ArrayList<Relation>(ways.size()); 455 Collection<String> linearTags = Main.pref.getCollection(PREF_MULTIPOLY + "lineartags", DEFAULT_LINEAR_TAGS); 456 for( Way w : selection ) { 457 Way newWay = new Way(w); 458 wayDiff.put(w, newWay); 459 commands.add(new ChangeCommand(w, newWay)); 460 ways.add(newWay); 461 Relation r = new Relation(); 462 r.put("type", "multipolygon"); 463 r.addMember(new RelationMember("outer", w)); 464 // move tags to relations 465 for( String key : newWay.keySet() ) { 466 if( !linearTags.contains(key) ) { 467 r.put(key, newWay.get(key)); 468 newWay.remove(key); 469 } 470 } 471 if( !w.isClosed() ) { 472 Way ring = null; 473 for( Way tring : selection ) 474 if( tring.containsNode(newWay.firstNode()) && tring.containsNode(newWay.lastNode()) 475 && !segmentInsidePolygon(newWay.getNode(0), newWay.getNode(1), tring.getNodes()) ) 476 ring = tring; 477 Way intersection = makeIntersectionLine(newWay, ring); 478 commands.add(new AddCommand(intersection)); 479 r.addMember(new RelationMember("outer", intersection)); 480 } 481 relations.add(r); 482 } 483 484 for( int i = 0; i < relations.size() - 1; i++ ) 485 for( int j = i + 1; j < relations.size(); j++ ) 486 collideMultipolygons(relations.get(i), relations.get(j), commands, wayDiff); 487 488 for( Relation r : relations ) 489 commands.add(new AddCommand(r)); 490 Main.main.undoRedo.add(new SequenceCommand(tr("Create multipolygons from rings"), commands)); 491 return relations; 492 } 493 494 /** 495 * Copies segment from {@code ring} to close a multipolygon containing {@code segment}. 496 * @param segment Unclosed segment. 497 * @param ring Closed ring. 498 * @return Missing way. 499 */ 500 private Way makeIntersectionLine( Way segment, Way ring ) { 501 List<Node> nodes = new ArrayList<Node>(ring.getNodes()); 502 nodes.remove(nodes.size() - 1); 503 int index1 = nodes.indexOf(segment.firstNode()); 504 int index2 = nodes.indexOf(segment.lastNode()); 505 if( index1 == index2 || index1 < 0 || index2 < 0 ) 506 return null; 507 508 // split ring 509 List<List<Node>> chunks = new ArrayList<List<Node>>(2); 510 chunks.add(new ArrayList<Node>()); 511 chunks.add(new ArrayList<Node>()); 512 int chunk = 0, i = index1; 513 boolean madeCircle = false; 514 while( i != index1 || !madeCircle ) { 515 chunks.get(chunk).add(nodes.get(i)); 516 if( i == index2 ) { 517 chunk = 1 - chunk; 518 chunks.get(chunk).add(nodes.get(i)); 519 } 520 if( ++i >= nodes.size() ) 521 i = 0; 522 madeCircle = true; 523 } 524 chunks.get(chunk).add(nodes.get(i)); 525 526 // check which segment to add 527 List<Node> testRing = new ArrayList<Node>(segment.getNodes()); 528 closePolygon(testRing, chunks.get(0)); 529 chunk = segmentInsidePolygon(chunks.get(1).get(0), chunks.get(1).get(1), testRing) ? 1 : 0; 530 531 // create way 532 Way w = new Way(); 533 w.setKeys(segment.getKeys()); 534 w.setNodes(chunks.get(chunk)); 535 return w; 536 } 537 538 /** 539 * Appends "append" to "base" so the closed polygon forms. 540 */ 541 private void closePolygon( List<Node> base, List<Node> append ) { 542 if( append.get(0).equals(base.get(0)) && append.get(append.size() - 1).equals(base.get(base.size() - 1)) ) { 543 List<Node> ap2 = new ArrayList<Node>(append); 544 Collections.reverse(ap2); 545 append = ap2; 546 } 547 base.remove(base.size() - 1); 548 base.addAll(append); 549 } 550 551 /** 552 * Checks if a middle point between two nodes is inside a polygon. Useful to check if the way is inside. 553 */ 554 private boolean segmentInsidePolygon( Node n1, Node n2, List<Node> polygon ) { 555 EastNorth en1 = n1.getEastNorth(); 556 EastNorth en2 = n2.getEastNorth(); 557 Node testNode = new Node(new EastNorth((en1.east() + en2.east()) / 2.0, (en1.north() + en2.north()) / 2.0)); 558 return Geometry.nodeInsidePolygon(testNode, polygon); 559 } 560 561 /** 562 * Removes any intersections between multipolygons. 563 * @param r1 First multipolygon. 564 * @param r2 Second multipolygon. 565 * @param commands List of commands. Only add way commands go there, also see wayDiff. 566 * @param wayDiff The mapping old way to new Way: if there is no entry in this map, it is created, and 567 * a ChangeCommand is issued. 568 */ 569 private static void collideMultipolygons( Relation r1, Relation r2, List<Command> commands, Map<Way, Way> wayDiff ) { 570 } 571 572 /** 573 * Splits a way with regard to containing relations. This modifies the way and the relation, be prepared. 574 * @param w The way. 575 * @param n The node to split at. 576 * @param commands List of commands to add way/relation changing to. If null, never mind. 577 * @return Newly created ways. <b>Warning:</b> if commands is no not, newWays contains {@code w}. 578 */ 579 public static List<Way> splitWay( Way w, Node n1, Node n2, List<Command> commands ) { 580 List<Node> nodes = new ArrayList<Node>(w.getNodes()); 581 if( w.isClosed()) 582 nodes.remove(nodes.size()-1); 583 int index1 = nodes.indexOf(n1); 584 int index2 = n2 == null ? -1 : nodes.indexOf(n2); 585 if( index1 > index2 ) { 586 int tmp = index1; 587 index1 = index2; 588 index2 = tmp; 589 } 590 // right now index2 >= index1 591 if( index2 < 1 || index1 >= w.getNodesCount() - 1 || index2 >= w.getNodesCount() ) 592 return Collections.emptyList(); 593 if( w.isClosed() && (index1 < 0 || index1 == index2 || index1 + w.getNodesCount() == index2) ) 594 return Collections.emptyList(); 595 596 // make a list of segments 597 List<List<Node>> chunks = new ArrayList<List<Node>>(2); 598 List<Node> chunk = new ArrayList<Node>(); 599 for( int i = 0; i < nodes.size(); i++ ) { 600 chunk.add(nodes.get(i)); 601 if( (w.isClosed() || chunk.size() > 1) && (i == index1 || i == index2) ) { 602 chunks.add(chunk); 603 chunk = new ArrayList<Node>(); 604 chunk.add(nodes.get(i)); 605 } 606 } 607 chunks.add(chunk); 608 609 // for closed way ignore the way boundary 610 if( w.isClosed() ) { 611 chunks.get(chunks.size() - 1).addAll(chunks.get(0)); 612 chunks.remove(0); 613 } else if( chunks.get(chunks.size()-1).size() < 2 ) 614 chunks.remove(chunks.size()-1); 615 616 // todo remove debug: show chunks array contents 617 /*for( List<Node> c1 : chunks ) { 618 for( Node cn1 : c1 ) 619 System.out.print(cn1.getId() + ","); 620 System.out.println(); 621 }*/ 622 623 // build a map of referencing relations 624 Map<Relation, Integer> references = new HashMap<Relation, Integer>(); 625 List<Command> relationCommands = new ArrayList<Command>(); 626 for( OsmPrimitive p : w.getReferrers() ) { 627 if( p instanceof Relation ) { 628 Relation rel = commands == null ? (Relation)p : new Relation((Relation)p); 629 if( commands != null ) 630 relationCommands.add(new ChangeCommand((Relation)p, rel)); 631 for( int i = 0; i < rel.getMembersCount(); i++ ) 632 if( rel.getMember(i).getMember().equals(w) ) 633 references.put(rel, Integer.valueOf(i)); 634 } 635 } 636 637 // build ways 638 List<Way> result = new ArrayList<Way>(); 639 Way updatedWay = commands == null ? w : new Way(w); 640 updatedWay.setNodes(chunks.get(0)); 641 if( commands != null ) { 642 commands.add(new ChangeCommand(w, updatedWay)); 643 result.add(updatedWay); 644 } 645 646 for( int i = 1; i < chunks.size(); i++ ) { 647 List<Node> achunk = chunks.get(i); 648 Way newWay = new Way(); 649 newWay.setKeys(w.getKeys()); 650 result.add(newWay); 651 for( Relation rel : references.keySet() ) { 652 int relIndex = references.get(rel); 653 rel.addMember(relIndex + 1, new RelationMember(rel.getMember(relIndex).getRole(), newWay)); 654 } 655 newWay.setNodes(achunk); 656 if( commands != null ) 657 commands.add(new AddCommand(newWay)); 658 } 659 if( commands != null ) 660 commands.addAll(relationCommands); 661 return result; 662 } 663 664 public static List<Way> splitWay( Way w, Node n1, Node n2 ) { 665 return splitWay(w, n1, n2, null); 666 } 667 668 /** 669 * Find a way the tips of a segment, ensure it's in a multipolygon and try to close the relation. 670 */ 671 private Relation tryToCloseOneWay( Way segment ) { 672 if( segment.isClosed() || segment.isIncomplete() ) 673 return null; 674 675 List<Way> ways = intersection( 676 OsmPrimitive.getFilteredList(segment.firstNode().getReferrers(), Way.class), 677 OsmPrimitive.getFilteredList(segment.lastNode().getReferrers(), Way.class)); 678 ways.remove(segment); 679 for( Iterator<Way> iter = ways.iterator(); iter.hasNext(); ) { 680 boolean save = false; 681 for( OsmPrimitive ref : iter.next().getReferrers() ) 682 if( ref instanceof Relation && ((Relation)ref).isMultipolygon() && !ref.isDeleted() ) 683 save = true; 684 if( !save ) 685 iter.remove(); 686 } 687 if( ways.isEmpty() ) 688 return null; // well... 689 Way target = ways.get(0); 690 691 // time to create a new multipolygon relation and a command stack 692 List<Command> commands = new ArrayList<Command>(); 693 Relation newRelation = new Relation(); 694 newRelation.put("type", "multipolygon"); 695 newRelation.addMember(new RelationMember("outer", segment)); 696 Collection<String> linearTags = Main.pref.getCollection(PREF_MULTIPOLY + "lineartags", DEFAULT_LINEAR_TAGS); 697 Way segmentCopy = new Way(segment); 698 boolean changed = false; 699 for( String key : segmentCopy.keySet() ) { 700 if( !linearTags.contains(key) ) { 701 newRelation.put(key, segmentCopy.get(key)); 702 segmentCopy.remove(key); 703 changed = true; 704 } 705 } 706 if( changed ) 707 commands.add(new ChangeCommand(segment, segmentCopy)); 708 709 // now split the way, at last 710 List<Way> newWays = new ArrayList<Way>(splitWay(target, segment.firstNode(), segment.lastNode(), commands)); 711 712 Way addingWay = null; 713 if( target.isClosed() ) { 714 Way utarget = newWays.get(1); 715 Way alternate = newWays.get(0); 716 List<Node> testRing = new ArrayList<Node>(segment.getNodes()); 717 closePolygon(testRing, utarget.getNodes()); 718 addingWay = segmentInsidePolygon(alternate.getNode(0), alternate.getNode(1), testRing) ? alternate : utarget; 719 } else { 720 for( Way w : newWays ) { 721 if( (w.firstNode().equals(segment.firstNode()) && w.lastNode().equals(segment.lastNode())) 722 || (w.firstNode().equals(segment.lastNode()) && w.lastNode().equals(segment.firstNode())) ) { 723 addingWay = w; 724 break; 725 } 726 } 727 } 728 newRelation.addMember(new RelationMember("outer", addingWay.getUniqueId() == target.getUniqueId() ? target : addingWay)); 729 commands.add(new AddCommand(newRelation)); 730 Main.main.undoRedo.add(new SequenceCommand(tr("Complete multipolygon for way {0}", 731 DefaultNameFormatter.getInstance().format(segment)), commands)); 732 return newRelation; 733 } 734 735 /** 736 * Find a multipolygon at the tips of a segment, try to close the way. 737 * 738 * Note: this method is abandoned because of it's skyrocketing complexity. The only thing is 739 * left to write is splitting and ordering ways (see below). But I doubt there is a point to it. 740 */ 741 private Relation tryToCloseOneWayOld( Way segment ) { 742 if( segment.isClosed() || segment.isIncomplete() ) 743 return null; 744 745 // find relations that have ways from both arrays 746 Set<Relation> relations1 = new HashSet<Relation>(); 747 for( Way way : OsmPrimitive.getFilteredList(segment.firstNode().getReferrers(), Way.class) ) 748 relations1.addAll(OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)); 749 Set<Relation> relations2 = new HashSet<Relation>(); 750 for( Way way : OsmPrimitive.getFilteredList(segment.lastNode().getReferrers(), Way.class) ) 751 relations2.addAll(OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)); 752 List<Relation> relations = intersection(relations1, relations2); 753 for( Iterator<Relation> iter = relations.iterator(); iter.hasNext(); ) { 754 Relation candidate = iter.next(); 755 if( !candidate.isMultipolygon() || candidate.isDeleted() || candidate.isIncomplete() ) 756 iter.remove(); 757 } 758 if( relations.isEmpty() ) 759 return null; 760 int i = 0; 761 String error = ""; 762 MultipolygonCreate mpc = new MultipolygonCreate(); 763 JoinedPolygon poly = null; 764 while( error != null && i < relations.size() ) { 765 error = mpc.makeFromWays(OsmPrimitive.getFilteredSet(relations.get(i).getMemberPrimitives(), Way.class)); 766 if( error != null ) { 767 for( JoinedPolygon p : mpc.outerWays ) { 768 if( p.nodes.contains(segment.firstNode()) && p.nodes.contains(segment.lastNode()) ) { 769 poly = p; 770 break; 771 } 772 } 773 } 774 i++; 775 } 776 if( poly == null ) 777 return null; // no correct multipolygons with outer contour that segment touches 778 Relation multipolygon = relations.get(i - 1); 779 780 // time to create a new multipolygon relation and a command stack 781 List<Command> commands = new ArrayList<Command>(); 782 Relation newRelation = new Relation(); 783 newRelation.put("type", "multipolygon"); 784 newRelation.addMember(new RelationMember("outer", segment)); 785 Collection<String> linearTags = Main.pref.getCollection(PREF_MULTIPOLY + "lineartags", DEFAULT_LINEAR_TAGS); 786 Way segmentCopy = new Way(segment); 787 boolean changed = false; 788 for( String key : segmentCopy.keySet() ) { 789 if( !linearTags.contains(key) ) { 790 newRelation.put(key, segmentCopy.get(key)); 791 segmentCopy.remove(key); 792 changed = true; 793 } 794 } 795 if( changed ) 796 commands.add(new ChangeCommand(segment, segmentCopy)); 797 798 // find a path from one point to another via found outer contour 799 // but first, determine in which order to traverse nodes 800 int index1 = poly.nodes.indexOf(segment.firstNode()); 801 int index2 = poly.nodes.indexOf(segment.lastNode()); 802 803 List<List<Node>> chunks = new ArrayList<List<Node>>(2); 804 chunks.add(new ArrayList<Node>()); 805 chunks.add(new ArrayList<Node>()); 806 int chunk = 0; 807 i = index1; 808 boolean madeCircle = false; 809 while( i != index1 || !madeCircle ) { 810 chunks.get(chunk).add(poly.nodes.get(i)); 811 if( i == index2 ) { 812 chunk = 1 - chunk; 813 chunks.get(chunk).add(poly.nodes.get(i)); 814 } 815 if( ++i >= poly.nodes.size() ) 816 i = 0; 817 madeCircle = true; 818 } 819 chunks.get(chunk).add(poly.nodes.get(i)); 820 821 // check which segment to add 822 List<Node> testRing = new ArrayList<Node>(segment.getNodes()); 823 closePolygon(testRing, chunks.get(0)); 824 // Node startNode = segmentInsidePolygon(chunks.get(1).get(0), chunks.get(1).get(1), testRing) ? segment.lastNode() : segment.firstNode(); 825 // Node endNode = startNode.equals(segment.firstNode()) ? segment.lastNode() : segment.firstNode(); 826 int startIndex = segmentInsidePolygon(chunks.get(1).get(0), chunks.get(1).get(1), testRing) ? index2 : index1; 827 int endIndex = startIndex == index2 ? index1 : index2; 828 Node startNode = poly.nodes.get(startIndex); 829 Node endNode = poly.nodes.get(endIndex); 830 831 // add ways containing nodes from startNode to endNode 832 // note: they are in order! 833 i = 0; 834 while( i < poly.ways.size() && !poly.ways.get(i).containsNode(startNode) ) 835 i++; 836 int startNodeIndex = poly.ways.get(i).getNodes().indexOf(startNode); 837 if( startNodeIndex == 0 || startNodeIndex == poly.ways.get(i).getNodesCount() - 1 ) 838 i++; // if it's the last node, take next way 839 840 if( poly.ways.get(i).containsNode(endNode) ) { 841 // ok, both nodes are in the same way 842 // split it, return the new part 843 List<Way> newWays = splitWay(poly.ways.get(i), startNode, endNode, commands); 844 // find which of the parts we need (in case of closed way) 845 Node testNode = poly.nodes.get((index1 + 1) % poly.nodes.size()); 846 } else { 847 // so, let's take ways one by one 848 // todo: split way 1 and add relevant part 849 List<Way> newWays = splitWay(poly.ways.get(i), startNode, endNode, commands); 850 i++; 851 while( !poly.ways.get(i).containsNode(endNode) ) { 852 newRelation.addMember(new RelationMember("outer", poly.ways.get(i))); 853 i++; 854 } 855 // todo: split way 2 and add relevant part 856 newWays = splitWay(poly.ways.get(i), startNode, endNode, commands); 857 } 858 859 commands.add(new AddCommand(newRelation)); 860 Main.main.undoRedo.add(new SequenceCommand(tr("Complete multipolygon for way {0}", 861 DefaultNameFormatter.getInstance().format(segment)), commands)); 862 return newRelation; 863 } 864 865 /** 866 * Returns all elements from {@code list1} that are in {@code list2}. 867 */ 868 private static <T> List<T> intersection( Collection<T> list1, Collection<T> list2 ) { 869 List<T> result = new ArrayList<T>(); 870 for( T item : list1 ) 871 if( list2.contains(item) ) 872 result.add(item); 873 return result; 376 874 } 377 875 }
Note:
See TracChangeset
for help on using the changeset viewer.