Ticket #1652: JoinAreasFunction.patch

File JoinAreasFunction.patch, 57.8 KB (added by xeen, 17 years ago)

Includes TestFile.osm, JoinAreas.java and changes to UtilsPlugin.java

  • data/Join

     
     1<?xml version='1.0' encoding='UTF-8'?>
     2<osm version='0.5' generator='JOSM'>
     3  <bounds minlat='49.6428455937926' minlon='8.97891998291015' maxlat='49.6659612524238' maxlon='9.01926040649414' origin='JOSM' />
     4  <node id='-1' lat='49.665557350679464' lon='8.980018030697323'>
     5    <tag k='name' v='Multipolygon' />
     6  </node>
     7  <node id='-2' lat='49.66293444373006' lon='8.979904268383734' />
     8  <node id='-3' lat='49.66309431357888' lon='8.98771163350794' />
     9  <node id='-4' lat='49.66550322539429' lon='8.987481709734286' />
     10  <node id='-5' lat='49.661140812390876' lon='8.980754551545285' />
     11  <node id='-6' lat='49.66414787714609' lon='8.98167662069808' />
     12  <node id='-7' lat='49.66193599760082' lon='8.983668051096597' />
     13  <node id='-8' lat='49.66423062929877' lon='8.985896954118042' />
     14  <node id='-9' lat='49.66122094281029' lon='8.987593944961972' />
     15  <node id='-10' lat='49.665246272951265' lon='8.996673744036638'>
     16    <tag k='name' v='No Additional Inner Nodes' />
     17  </node>
     18  <node id='-11' lat='49.66260897027721' lon='8.996995205661822' />
     19  <node id='-12' lat='49.66247822670222' lon='9.006405902938653' />
     20  <node id='-13' lat='49.66509579743186' lon='9.006492938770437' />
     21  <node id='-14' lat='49.663673852556485' lon='9.001872836660935' />
     22  <node id='-15' lat='49.66356462957665' lon='9.003786018319271' />
     23  <node id='-16' lat='49.659381328509475' lon='9.002998050852923' />
     24  <node id='-17' lat='49.66102960777179' lon='9.004102187558955' />
     25  <node id='-18' lat='49.658143795735675' lon='9.001321365825214' />
     26  <node id='-19' lat='49.65470614469758' lon='9.000967096568774' />
     27  <node id='-20' lat='49.65966007592634' lon='8.995756927212474'>
     28    <tag k='name' v='With Inner Nodes, resolve tag conflicts beforehand' />
     29  </node>
     30  <node id='-21' lat='49.65482452933729' lon='8.99800181533644' />
     31  <node id='-22' lat='49.658215448844345' lon='8.998101909487305' />
     32  <node id='-23' lat='49.656334599426415' lon='8.99596685442522' />
     33  <node id='-24' lat='49.65622420968986' lon='8.999630518959972' />
     34  <node id='-25' lat='49.65595437351232' lon='9.002670090930014' />
     35  <node id='-26' lat='49.664767306664714' lon='9.00358600531296' />
     36  <node id='-27' lat='49.652897713964435' lon='8.995398194580462'>
     37    <tag k='name' v='Overlapping Itself' />
     38  </node>
     39  <node id='-28' lat='49.66298227683501' lon='9.007574342263663' />
     40  <node id='-29' lat='49.657294551336726' lon='9.005523880219158' />
     41  <node id='-30' lat='49.64759565941927' lon='8.995601060044594' />
     42  <node id='-31' lat='49.659323888301465' lon='9.00052935158495' />
     43  <node id='-32' lat='49.65131105391522' lon='9.00298658642922' />
     44  <node id='-33' lat='49.64965980248303' lon='9.003039719712563' />
     45  <node id='-34' lat='49.647526853143034' lon='8.99921412331189' />
     46  <node id='-35' lat='49.65151745640557' lon='8.997992057795011' />
     47  <node id='-36' lat='49.648042897844014' lon='9.003942985529385' />
     48  <node id='-37' lat='49.6534782364111' lon='9.003730452396017' />
     49  <node id='-38' lat='49.64965980248303' lon='8.997301325111557' />
     50  <node id='-39' lat='49.64862774189198' lon='8.99756699152827' />
     51  <node id='-40' lat='49.650123242376026' lon='8.984223634986959' />
     52  <node id='-41' lat='49.64837612802103' lon='8.982669694368326' />
     53  <node id='-42' lat='49.65267519581664' lon='8.982531354736969'>
     54    <tag k='name' v='Joining an two areas with self-overlap' />
     55  </node>
     56  <node id='-43' lat='49.648702988205066' lon='8.99108732452821' />
     57  <node id='-44' lat='49.6498411096278' lon='8.989584718400737' />
     58  <node id='-45' lat='49.65180728142213' lon='8.98635078159476' />
     59  <node id='-46' lat='49.65154365880048' lon='8.989894759403137' />
     60  <node id='-47' lat='49.64821655131596' lon='8.986618619738293' />
     61  <node id='-48' lat='49.65263419499493' lon='8.992015874465995' />
     62  <node id='-49' lat='49.64910487989931' lon='8.98434746261959' />
     63  <node id='-50' lat='49.661575265881794' lon='9.002417415068946' />
     64  <node id='-51' lat='49.66111564855195' lon='8.994837077867599' />
     65  <node id='-52' lat='49.664258008517386' lon='8.994834196254615' />
     66  <node id='-53' lat='49.66327697326473' lon='8.996037451903568' />
     67  <node id='-54' lat='49.66163205844883' lon='8.996182583343169' />
     68  <node id='-55' lat='49.66336086872286' lon='9.000172520579218' />
     69  <node id='-56' lat='49.66439890567706' lon='9.000253362850133' />
     70  <node id='-57' lat='49.653656418352305' lon='8.980925219001294' />
     71  <node id='-58' lat='49.65083240952062' lon='8.981009751040055' />
     72  <node id='-59' lat='49.65093842995153' lon='8.98362536207706' />
     73  <node id='-60' lat='49.65366266054767' lon='8.983765181752341' />
     74  <node id='-61' lat='49.65918199608578' lon='8.984744990288629' />
     75  <node id='-62' lat='49.65684188230803' lon='8.984902160770126' />
     76  <node id='-63' lat='49.65687579780412' lon='8.979558364399258' />
     77  <node id='-64' lat='49.65918199608578' lon='8.97945358407826'>
     78    <tag k='name' v='Intersection already have nodes added' />
     79  </node>
     80  <node id='-65' lat='49.65545132661812' lon='8.981025288893223' />
     81  <node id='-66' lat='49.65558501959522' lon='8.988437026602341' />
     82  <node id='-67' lat='49.65931567881406' lon='8.986865321787379'>
     83    <tag k='name' v='SOME Intersection already have nodes added' />
     84  </node>
     85  <node id='-68' lat='49.65700948686801' lon='8.986970102108378' />
     86  <node id='-69' lat='49.65687579780412' lon='8.980815728251226' />
     87  <node id='-70' lat='49.65809673992071' lon='8.980815728251226' />
     88  <node id='-71' lat='49.65792716645855' lon='8.983382846115664' />
     89  <node id='-72' lat='49.65701145955204' lon='8.98306850515267' />
     90  <node id='-73' lat='49.65700948686801' lon='8.988227465960346' />
     91  <node id='-74' lat='49.660221488976575' lon='8.977137289660517' />
     92  <node id='-75' lat='49.66019710027813' lon='9.021519330497165' />
     93  <node id='-76' lat='49.66824309940781' lon='8.99379608098765' />
     94  <node id='-77' lat='49.65823042563044' lon='8.988227465960346' />
     95  <node id='-78' lat='49.65931567881406' lon='8.992156727997747' />
     96  <node id='-79' lat='49.65697557146509' lon='8.992313898479246' />
     97  <node id='-80' lat='49.65806085263415' lon='8.990794583824783' />
     98  <node id='-81' lat='49.6423143331186' lon='8.993645046699799' />
     99  <node id='-82' lat='49.66770174614116' lon='9.008788568211484' />
     100  <node id='-83' lat='49.64184704638359' lon='9.008847909558769' />
     101  <node id='-84' lat='49.654142227092336' lon='8.97793106762297' />
     102  <node id='-85' lat='49.65418064466472' lon='9.02166564057246' />
     103  <node id='-86' lat='49.665325869211465' lon='9.010629016616129'>
     104    <tag k='name' v='Member of some (i.e. not multigon) relation' />
     105  </node>
     106  <node id='-87' lat='49.66089950426598' lon='9.010651964271892' />
     107  <node id='-88' lat='49.66084008723247' lon='9.01744447037755' />
     108  <node id='-89' lat='49.66210268358402' lon='9.017513313344837' />
     109  <node id='-90' lat='49.662236368338334' lon='9.011707556436962' />
     110  <node id='-91' lat='49.663989090028004' lon='9.01179934706001' />
     111  <node id='-92' lat='49.663929676767346' lon='9.01471369934183' />
     112  <node id='-93' lat='49.66156794089354' lon='9.014644856374543' />
     113  <node id='-94' lat='49.66149367061061' lon='9.016824883671967' />
     114  <node id='-95' lat='49.665266457583265' lon='9.016733093048918' />
     115  <node id='-96' lat='49.6657566013461' lon='9.017559208656362' />
     116  <node id='-97' lat='49.66572689580407' lon='9.018683643788718' />
     117  <node id='-98' lat='49.66467233731082' lon='9.018729539100242' />
     118  <node id='-99' lat='49.65954150350348' lon='9.010225255249217'>
     119    <tag k='name' v='One Area already part of multipolygon (as outer)' />
     120  </node>
     121  <node id='-100' lat='49.65948208481168' lon='9.016329331682007' />
     122  <node id='-101' lat='49.65507724377221' lon='9.016274166792183' />
     123  <node id='-102' lat='49.65831483593041' lon='9.011804408209608' />
     124  <node id='-103' lat='49.656561909834345' lon='9.011712617586559' />
     125  <node id='-104' lat='49.65825541574074' lon='9.014718760491427' />
     126  <node id='-105' lat='49.655114612313845' lon='9.01024820290498' />
     127  <node id='-106' lat='49.65649630973553' lon='9.01456121513066' />
     128  <node id='-107' lat='49.659983770585875' lon='9.015660786026782' />
     129  <node id='-108' lat='49.65777838791238' lon='9.0156778202983' />
     130  <node id='-109' lat='49.6577783879124' lon='9.01859068072843' />
     131  <node id='-110' lat='49.659950690584154' lon='9.018522543642344' />
     132  <node id='-111' lat='49.650467504431084' lon='9.01556653387284' />
     133  <node id='-112' lat='49.650467504431084' lon='9.012653673442712' />
     134  <node id='-113' lat='49.649036528490285' lon='9.01626713010843' />
     135  <node id='-114' lat='49.651484566652904' lon='9.01400037262895' />
     136  <node id='-115' lat='49.653441916259965' lon='9.016322294998254' />
     137  <node id='-116' lat='49.65267321841768' lon='9.012636639171191' />
     138  <node id='-117' lat='49.652640133446276' lon='9.015498396786755' />
     139  <node id='-118' lat='49.64972521590133' lon='9.013842827268181' />
     140  <node id='-119' lat='49.64907390166997' lon='9.010241166221228' />
     141  <node id='-120' lat='49.649790825127035' lon='9.010994229724082' />
     142  <node id='-121' lat='49.65350134232707' lon='9.010218218565463'>
     143    <tag k='name' v='One Area already part of multipolygon (as inner)' />
     144  </node>
     145  <node id='-122' lat='49.651543995109876' lon='9.011086020347133' />
     146  <node id='-123' lat='49.64702795382207' lon='9.021373906176958' />
     147  <node id='-124' lat='49.64698953060391' lon='8.97763933322747' />
     148  <node id='-125' lat='49.6440995060445' lon='9.015089670434026' />
     149  <node id='-126' lat='49.64633804682556' lon='9.016044421132413' />
     150  <node id='-127' lat='49.64329477394559' lon='9.00982115694612' />
     151  <node id='-128' lat='49.64474487637503' lon='9.013313148784425' />
     152  <node id='-129' lat='49.646452701397266' lon='9.00974135512665'>
     153    <tag k='name' v='Both part of other relations' />
     154  </node>
     155  <node id='-130' lat='49.645694403782294' lon='9.012131348650524' />
     156  <node id='-131' lat='49.64477854294109' lon='9.010865000644996' />
     157  <node id='-132' lat='49.64566131406722' lon='9.01503858959705' />
     158  <node id='-133' lat='49.64333102750582' lon='9.015932402078883' />
     159  <node id='-134' lat='49.6436516438452' lon='9.013337536747516' />
     160  <node id='-135' lat='49.6440995060445' lon='9.012176810003897' />
     161  <node id='-136' lat='49.64364363066422' lon='9.010830064185653' />
     162  <node id='-137' lat='49.64634787643868' lon='9.01859251804108' />
     163  <node id='-138' lat='49.64442618533402' lon='9.018507236795525' />
     164  <node id='-139' lat='49.64446300005248' lon='9.017591884759854' />
     165  <node id='-140' lat='49.646377327053195' lon='9.017591884759854' />
     166  <node id='-141' action='modify' lat='49.645108516691536' lon='9.00501200093294' />
     167  <node id='-142' action='modify' lat='49.644279396738646' lon='8.99701844322583' />
     168  <node id='-143' action='modify' lat='49.643570600890634' lon='9.005424239693767' />
     169  <node id='-144' action='modify' lat='49.64579713321828' lon='9.001725329495672' />
     170  <node id='-145' action='modify' lat='49.645870282835396' lon='9.000573288940334' />
     171  <node id='-146' action='modify' lat='49.644533441390884' lon='9.001738472218983' />
     172  <node id='-147' action='modify' lat='49.64620102922634' lon='8.995285052653605' />
     173  <node id='-148' action='modify' lat='49.645549394972946' lon='8.997512960693886' />
     174  <node id='-149' action='modify' lat='49.64390917555046' lon='9.00236900726505' />
     175  <node id='-150' action='modify' lat='49.643583272941804' lon='8.996426078862356' />
     176  <node id='-151' action='modify' lat='49.645064815871' lon='8.999528632035787' />
     177  <node id='-152' action='modify' lat='49.64660348405886' lon='9.007804150387257' />
     178  <node id='-153' action='modify' lat='49.644215448513066' lon='8.987601210318644' />
     179  <node id='-154' action='modify' lat='49.64509437228782' lon='8.985632145182455' />
     180  <node id='-155' action='modify' lat='49.64512070290696' lon='8.990191229749867' />
     181  <node id='-156' action='modify' lat='49.64400653203681' lon='8.985527741087623' />
     182  <node id='-157' action='modify' lat='49.64362715512701' lon='8.983795590458412' />
     183  <node id='-158' action='modify' lat='49.6438359138228' lon='8.988873075900425' />
     184  <node id='-159' action='modify' lat='49.64624752120218' lon='8.990209078516749' />
     185  <node id='-160' action='modify' lat='49.64561767902291' lon='8.987111582364214' />
     186  <node id='-161' lat='49.664846643627826' lon='9.02334771024883' />
     187  <node id='-162' lat='49.66903704959538' lon='9.022880487521558' />
     188  <node id='-163' lat='49.671931242953896' lon='9.026017554404675' />
     189  <node id='-164' lat='49.66722269132057' lon='9.026351284924155' />
     190  <node id='-165' lat='49.6687778596993' lon='9.028553906352727' />
     191  <node id='-166' lat='49.66476023970688' lon='9.029688590118962' />
     192  <node id='-167' action='modify' lat='49.64560080131112' lon='8.988407502515859' />
     193  <node id='-168' action='modify' lat='49.64619224514668' lon='8.984061673983168' />
     194  <node id='-169' action='modify' lat='49.64654691824636' lon='8.982418392157395' />
     195  <node id='-170' action='modify' lat='49.64294039575888' lon='8.98264180269116' />
     196  <node id='-171' lat='49.66726589111355' lon='9.018808975183893' />
     197  <node id='-172' lat='49.67180165588925' lon='9.019276197911166'>
     198    <tag k='name' v='out of bounds' />
     199  </node>
     200  <node id='-173' action='modify' lat='49.64305580574678' lon='8.992869020494213' />
     201  <node id='-174' action='modify' lat='49.6465677851024' lon='8.992596999330347' />
     202  <way id='-175'>
     203    <nd ref='-145' />
     204    <nd ref='-146' />
     205    <nd ref='-142' />
     206    <nd ref='-148' />
     207    <nd ref='-145' />
     208  </way>
     209  <way id='-176'>
     210    <nd ref='-66' />
     211    <nd ref='-73' />
     212    <nd ref='-77' />
     213    <nd ref='-80' />
     214    <nd ref='-66' />
     215    <tag k='parking' v='surface' />
     216    <tag k='amenity' v='parking' />
     217  </way>
     218  <way id='-177'>
     219    <nd ref='-168' />
     220    <nd ref='-159' />
     221    <nd ref='-155' />
     222    <nd ref='-154' />
     223    <nd ref='-156' />
     224    <nd ref='-153' />
     225    <nd ref='-160' />
     226    <nd ref='-167' />
     227    <nd ref='-158' />
     228    <nd ref='-157' />
     229    <nd ref='-168' />
     230    <tag k='name' v='Thing in the Middle' />
     231  </way>
     232  <way id='-178'>
     233    <nd ref='-67' />
     234    <nd ref='-68' />
     235    <nd ref='-73' />
     236    <nd ref='-79' />
     237    <nd ref='-78' />
     238    <nd ref='-67' />
     239    <tag k='parking' v='surface' />
     240    <tag k='amenity' v='parking' />
     241  </way>
     242  <way id='-179'>
     243    <nd ref='-116' />
     244    <nd ref='-112' />
     245    <nd ref='-111' />
     246    <nd ref='-117' />
     247    <nd ref='-116' />
     248  </way>
     249  <way id='-180'>
     250    <nd ref='-26' />
     251    <nd ref='-15' />
     252    <nd ref='-28' />
     253    <tag k='highway' v='footway' />
     254  </way>
     255  <way id='-181'>
     256    <nd ref='-76' />
     257    <nd ref='-81' />
     258    <tag k='highway' v='motorway' />
     259    <tag k='lanes' v='5' />
     260  </way>
     261  <way id='-182'>
     262    <nd ref='-113' />
     263    <nd ref='-115' />
     264    <nd ref='-121' />
     265    <nd ref='-119' />
     266    <nd ref='-113' />
     267    <tag k='parking' v='surface' />
     268    <tag k='amenity' v='parking' />
     269  </way>
     270  <way id='-183'>
     271    <nd ref='-137' />
     272    <nd ref='-138' />
     273    <nd ref='-139' />
     274    <nd ref='-140' />
     275    <nd ref='-137' />
     276  </way>
     277  <way id='-184'>
     278    <nd ref='-96' />
     279    <nd ref='-97' />
     280    <nd ref='-98' />
     281  </way>
     282  <way id='-185'>
     283    <nd ref='-134' />
     284    <nd ref='-136' />
     285    <nd ref='-131' />
     286    <nd ref='-128' />
     287    <nd ref='-134' />
     288  </way>
     289  <way id='-186'>
     290    <nd ref='-101' />
     291    <nd ref='-100' />
     292    <nd ref='-99' />
     293    <nd ref='-105' />
     294    <nd ref='-101' />
     295    <tag k='parking' v='surface' />
     296    <tag k='amenity' v='parking' />
     297  </way>
     298  <way id='-187'>
     299    <nd ref='-124' />
     300    <nd ref='-123' />
     301    <tag k='highway' v='motorway' />
     302    <tag k='lanes' v='5' />
     303  </way>
     304  <way id='-188'>
     305    <nd ref='-25' />
     306    <nd ref='-16' />
     307    <nd ref='-20' />
     308    <nd ref='-23' />
     309    <nd ref='-24' />
     310    <nd ref='-25' />
     311    <tag k='landuse' v='retail' />
     312  </way>
     313  <way id='-189'>
     314    <nd ref='-130' />
     315    <nd ref='-135' />
     316    <nd ref='-125' />
     317    <nd ref='-132' />
     318    <nd ref='-130' />
     319  </way>
     320  <way id='-190'>
     321    <nd ref='-65' />
     322    <nd ref='-69' />
     323    <nd ref='-70' />
     324    <nd ref='-71' />
     325    <nd ref='-72' />
     326    <nd ref='-65' />
     327    <tag k='parking' v='surface' />
     328    <tag k='amenity' v='parking' />
     329  </way>
     330  <way id='-191'>
     331    <nd ref='-133' />
     332    <nd ref='-126' />
     333    <nd ref='-129' />
     334    <nd ref='-127' />
     335    <nd ref='-133' />
     336    <tag k='parking' v='surface' />
     337    <tag k='amenity' v='parking' />
     338  </way>
     339  <way id='-192'>
     340    <nd ref='-74' />
     341    <nd ref='-75' />
     342    <tag k='highway' v='motorway' />
     343    <tag k='lanes' v='5' />
     344  </way>
     345  <way id='-193'>
     346    <nd ref='-31' />
     347    <nd ref='-18' />
     348    <nd ref='-29' />
     349    <tag k='highway' v='footway' />
     350  </way>
     351  <way id='-194'>
     352    <nd ref='-172' />
     353    <nd ref='-171' />
     354    <nd ref='-164' />
     355    <nd ref='-163' />
     356    <nd ref='-172' />
     357    <tag k='parking' v='surface' />
     358    <tag k='amenity' v='parking' />
     359  </way>
     360  <way id='-195'>
     361    <nd ref='-17' />
     362    <nd ref='-51' />
     363    <nd ref='-52' />
     364    <nd ref='-56' />
     365    <nd ref='-55' />
     366    <nd ref='-53' />
     367    <nd ref='-54' />
     368    <nd ref='-50' />
     369    <nd ref='-14' />
     370    <nd ref='-15' />
     371    <nd ref='-17' />
     372    <tag k='parking' v='surface' />
     373    <tag k='amenity' v='parking' />
     374  </way>
     375  <way id='-196'>
     376    <nd ref='-107' />
     377    <nd ref='-108' />
     378    <nd ref='-109' />
     379    <nd ref='-110' />
     380    <nd ref='-107' />
     381  </way>
     382  <way id='-197'>
     383    <nd ref='-150' />
     384    <nd ref='-147' />
     385    <nd ref='-152' />
     386    <nd ref='-143' />
     387    <nd ref='-150' />
     388    <tag k='parking' v='surface' />
     389    <tag k='amenity' v='parking' />
     390  </way>
     391  <way id='-198'>
     392    <nd ref='-86' />
     393    <nd ref='-87' />
     394    <nd ref='-88' />
     395    <nd ref='-89' />
     396    <nd ref='-90' />
     397    <nd ref='-91' />
     398    <nd ref='-92' />
     399    <nd ref='-93' />
     400    <nd ref='-94' />
     401    <nd ref='-95' />
     402    <nd ref='-86' />
     403    <tag k='parking' v='surface' />
     404    <tag k='amenity' v='parking' />
     405  </way>
     406  <way id='-199'>
     407    <nd ref='-10' />
     408    <nd ref='-11' />
     409    <nd ref='-12' />
     410    <nd ref='-13' />
     411    <nd ref='-10' />
     412    <tag k='parking' v='surface' />
     413    <tag k='amenity' v='parking' />
     414  </way>
     415  <way id='-200'>
     416    <nd ref='-151' />
     417    <nd ref='-144' />
     418    <nd ref='-141' />
     419    <nd ref='-149' />
     420    <nd ref='-151' />
     421  </way>
     422  <way id='-201'>
     423    <nd ref='-82' />
     424    <nd ref='-83' />
     425    <tag k='highway' v='motorway' />
     426    <tag k='lanes' v='5' />
     427  </way>
     428  <way id='-202'>
     429    <nd ref='-1' />
     430    <nd ref='-2' />
     431    <nd ref='-3' />
     432    <nd ref='-4' />
     433    <nd ref='-1' />
     434    <tag k='parking' v='surface' />
     435    <tag k='amenity' v='parking' />
     436  </way>
     437  <way id='-203'>
     438    <nd ref='-84' />
     439    <nd ref='-85' />
     440    <tag k='highway' v='motorway' />
     441    <tag k='lanes' v='5' />
     442  </way>
     443  <way id='-204'>
     444    <nd ref='-64' />
     445    <nd ref='-63' />
     446    <nd ref='-69' />
     447    <nd ref='-72' />
     448    <nd ref='-62' />
     449    <nd ref='-61' />
     450    <nd ref='-64' />
     451    <tag k='parking' v='surface' />
     452    <tag k='amenity' v='parking' />
     453  </way>
     454  <way id='-205'>
     455    <nd ref='-42' />
     456    <nd ref='-48' />
     457    <nd ref='-43' />
     458    <nd ref='-49' />
     459    <nd ref='-40' />
     460    <nd ref='-44' />
     461    <nd ref='-46' />
     462    <nd ref='-45' />
     463    <nd ref='-47' />
     464    <nd ref='-41' />
     465    <nd ref='-42' />
     466    <tag k='parking' v='surface' />
     467    <tag k='amenity' v='parking' />
     468  </way>
     469  <way id='-206'>
     470    <nd ref='-118' />
     471    <nd ref='-120' />
     472    <nd ref='-122' />
     473    <nd ref='-114' />
     474    <nd ref='-118' />
     475  </way>
     476  <way id='-207'>
     477    <nd ref='-5' />
     478    <nd ref='-6' />
     479    <nd ref='-7' />
     480    <nd ref='-8' />
     481    <nd ref='-9' />
     482    <nd ref='-5' />
     483    <tag k='parking' v='surface' />
     484    <tag k='amenity' v='parking' />
     485  </way>
     486  <way id='-208'>
     487    <nd ref='-169' />
     488    <nd ref='-170' />
     489    <nd ref='-173' />
     490    <nd ref='-174' />
     491    <nd ref='-169' />
     492    <tag k='parking' v='surface' />
     493    <tag k='name' v='Outer blob' />
     494    <tag k='amenity' v='parking' />
     495  </way>
     496  <way id='-209'>
     497    <nd ref='-21' />
     498    <nd ref='-22' />
     499    <nd ref='-18' />
     500    <nd ref='-19' />
     501    <nd ref='-21' />
     502    <tag k='landuse' v='basin' />
     503  </way>
     504  <way id='-210'>
     505    <nd ref='-106' />
     506    <nd ref='-103' />
     507    <nd ref='-102' />
     508    <nd ref='-104' />
     509    <nd ref='-106' />
     510  </way>
     511  <way id='-211'>
     512    <nd ref='-57' />
     513    <nd ref='-58' />
     514    <nd ref='-59' />
     515    <nd ref='-60' />
     516    <nd ref='-57' />
     517    <tag k='parking' v='surface' />
     518    <tag k='amenity' v='parking' />
     519  </way>
     520  <way id='-212'>
     521    <nd ref='-162' />
     522    <nd ref='-161' />
     523    <nd ref='-166' />
     524    <nd ref='-165' />
     525    <nd ref='-162' />
     526    <tag k='parking' v='surface' />
     527    <tag k='amenity' v='parking' />
     528  </way>
     529  <way id='-213'>
     530    <nd ref='-27' />
     531    <nd ref='-37' />
     532    <nd ref='-36' />
     533    <nd ref='-39' />
     534    <nd ref='-38' />
     535    <nd ref='-33' />
     536    <nd ref='-32' />
     537    <nd ref='-35' />
     538    <nd ref='-34' />
     539    <nd ref='-30' />
     540    <nd ref='-27' />
     541    <tag k='parking' v='surface' />
     542    <tag k='amenity' v='parking' />
     543  </way>
     544  <relation id='-214'>
     545    <member type='way' ref='-206' role='inner' />
     546    <member type='way' ref='-182' role='outer' />
     547    <tag k='name' v='3.' />
     548    <tag k='type' v='multipolygon' />
     549  </relation>
     550  <relation id='-215'>
     551  </relation>
     552  <relation id='-216'>
     553    <member type='way' ref='-175' role='inner' />
     554    <member type='way' ref='-200' role='inner' />
     555    <member type='way' ref='-197' role='outer' />
     556    <tag k='name' v='5.' />
     557    <tag k='type' v='multipolygon' />
     558  </relation>
     559  <relation id='-217'>
     560    <member type='way' ref='-177' role='inner' />
     561    <member type='way' ref='-208' role='outer' />
     562    <tag k='name' v='6.' />
     563    <tag k='type' v='multipolygon' />
     564  </relation>
     565  <relation id='-218'>
     566    <member type='way' ref='-183' role='' />
     567    <member type='way' ref='-189' role='' />
     568    <tag k='name ' v='4. b' />
     569  </relation>
     570  <relation id='-219'>
     571    <member type='way' ref='-198' role='area' />
     572    <member type='node' ref='-90' role='node in area' />
     573    <member type='node' ref='-92' role='node in area' />
     574    <member type='way' ref='-184' role='some other way' />
     575    <member type='node' ref='-93' role='to be deleted node in area' />
     576    <tag k='name' v='1.' />
     577  </relation>
     578  <relation id='-220'>
     579    <member type='way' ref='-185' role='inner' />
     580    <member type='way' ref='-191' role='outer' />
     581    <tag k='name' v='4. a' />
     582    <tag k='type' v='multipolygon' />
     583  </relation>
     584  <relation id='-221'>
     585    <member type='way' ref='-210' role='inner' />
     586    <member type='way' ref='-186' role='outer' />
     587    <tag k='name' v='2.' />
     588    <tag k='type' v='multipolygon' />
     589  </relation>
     590</osm>
  • src/UtilsPlugin/JoinAreasAction.java

    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
    
    Property changes on: images\joinareas.png
    ___________________________________________________________________
    Name: svn:mime-type
       + application/octet-stream
    
     
     1package UtilsPlugin;
     2
     3import static org.openstreetmap.josm.tools.I18n.tr;
     4import static org.openstreetmap.josm.tools.I18n.trn;
     5
     6import java.awt.event.ActionEvent;
     7import java.awt.event.KeyEvent;
     8import java.awt.geom.Line2D;
     9import java.awt.geom.Point2D;
     10import java.awt.GridBagLayout;
     11import java.awt.Point;
     12import java.awt.Polygon;
     13
     14import java.util.ArrayList;
     15import java.util.Arrays;
     16import java.util.Collection;
     17import java.util.Collections;
     18import java.util.Comparator;
     19import java.util.HashMap;
     20import java.util.HashSet;
     21import java.util.LinkedList;
     22import java.util.List;
     23import java.util.Map;
     24import java.util.Map.Entry;
     25import java.util.Set;
     26import java.util.TreeMap;
     27import java.util.TreeSet;
     28
     29import javax.swing.Box;
     30import javax.swing.JComboBox;
     31import javax.swing.JLabel;
     32import javax.swing.JOptionPane;
     33import javax.swing.JPanel;
     34
     35import org.openstreetmap.josm.actions.CombineWayAction;
     36import org.openstreetmap.josm.actions.JosmAction;
     37import org.openstreetmap.josm.actions.ReverseWayAction;
     38import org.openstreetmap.josm.actions.SplitWayAction;
     39
     40import org.openstreetmap.josm.command.AddCommand;
     41import org.openstreetmap.josm.command.ChangeCommand;
     42import org.openstreetmap.josm.command.Command;
     43import org.openstreetmap.josm.command.DeleteCommand;
     44import org.openstreetmap.josm.command.SequenceCommand;
     45
     46import org.openstreetmap.josm.data.Bounds;
     47import org.openstreetmap.josm.data.coor.EastNorth;
     48import org.openstreetmap.josm.data.coor.LatLon;
     49import org.openstreetmap.josm.data.osm.DataSet;
     50import org.openstreetmap.josm.data.osm.DataSource;
     51import org.openstreetmap.josm.data.osm.Node;
     52import org.openstreetmap.josm.data.osm.OsmPrimitive;
     53import org.openstreetmap.josm.data.osm.Relation;
     54import org.openstreetmap.josm.data.osm.RelationMember;
     55import org.openstreetmap.josm.data.osm.TigerUtils;
     56import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
     57import org.openstreetmap.josm.data.osm.Way;
     58import org.openstreetmap.josm.data.osm.WaySegment;
     59import org.openstreetmap.josm.data.projection.Epsg4326;
     60import org.openstreetmap.josm.data.projection.Lambert;
     61import org.openstreetmap.josm.data.projection.Mercator;
     62import org.openstreetmap.josm.data.UndoRedoHandler;
     63
     64import org.openstreetmap.josm.gui.ExtendedDialog;
     65import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     66import org.openstreetmap.josm.Main;
     67import org.openstreetmap.josm.tools.GBC;
     68import org.openstreetmap.josm.tools.Pair;
     69import org.openstreetmap.josm.tools.Shortcut;
     70
     71public class JoinAreasAction extends JosmAction {
     72    // This will be used to commit commands and unite them into one large command sequence at the end
     73    private LinkedList<Command> cmds = new LinkedList<Command>();
     74    private int cmdsCount = 0;
     75   
     76    // HelperClass
     77    // Saves a node and two positions where to insert the node into the ways
     78    private class NodeToSegs implements Comparable<NodeToSegs> {
     79        public int pos;
     80        public Node n;
     81        public double dis;
     82        public NodeToSegs(int pos, Node n, LatLon dis) {
     83            this.pos = pos;
     84            this.n = n;
     85            this.dis = n.coor.greatCircleDistance(dis);
     86        }
     87       
     88        public int compareTo(NodeToSegs o) {
     89            if(this.pos == o.pos)
     90                return (this.dis - o.dis) > 0 ? 1 : -1;
     91            return this.pos - o.pos;
     92        }
     93    };
     94   
     95    // HelperClass
     96    // Saves a relation and a role an OsmPrimitve was part of until it was stripped from all relations
     97    private class RelationRole {
     98        public Relation rel;
     99        public String role;
     100        public RelationRole(Relation rel, String role) {
     101            this.rel = rel;
     102            this.role = role;
     103        }
     104       
     105        public boolean equals(Object other) {
     106            if (!(other instanceof RelationRole)) return false;
     107            RelationRole otherMember = (RelationRole) other;
     108            return otherMember.role.equals(role) && otherMember.rel.equals(rel);
     109        }
     110    }
     111
     112    // Adds the menu entry, Shortcuts, etc.
     113    public JoinAreasAction() {
     114        super(tr("Join overlapping Areas"), "joinareas", tr("Joins areas that overlap each other"), Shortcut.registerShortcut("tools:joinareas", tr("Tool: {0}", tr("Join overlapping Areas")),
     115        KeyEvent.VK_J, Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true);
     116    }
     117   
     118    /**
     119     * Gets called whenever the shortcut is pressed or the menu entry is selected
     120     * Checks whether the selected objects are suitable to join and joins them if so
     121     */
     122    public void actionPerformed(ActionEvent e) {
     123        int result = new ExtendedDialog(Main.parent,
     124            tr("Enter values for all conflicts."),
     125            new JLabel(tr("THIS IS EXPERIMENTAL. Save your work and verify before uploading.")),
     126            new String[] {tr("Continue anyway"), tr("Cancel")},
     127            new String[] {"joinareas.png", "cancel.png"}).getValue();
     128        if(result != 1) return;
     129       
     130        Collection<OsmPrimitive> selection = Main.ds.getSelectedWays();
     131
     132        int ways = 0;   
     133        Way[] selWays = new Way[2];
     134       
     135        LinkedList<Bounds> bounds = new LinkedList<Bounds>();
     136        OsmDataLayer dataLayer = Main.main.editLayer();
     137        for (DataSource ds : dataLayer.data.dataSources) {
     138            if (ds.bounds != null)
     139                bounds.add(ds.bounds);
     140        }
     141       
     142        boolean askedAlready = false;
     143        for (OsmPrimitive prim : selection) {
     144            Way way = (Way) prim;       
     145           
     146            // Too many ways
     147            if(ways == 2) {
     148                JOptionPane.showMessageDialog(Main.parent, tr("Only up to two areas can be joined at the moment."));
     149                return;
     150            }
     151           
     152            if(!way.isClosed()) {
     153                JOptionPane.showMessageDialog(Main.parent, tr("\"{0}\" is not closed and therefore can't be joined.", way.getName()));
     154                return;
     155            }
     156           
     157            // This is copied from SimplifyAction and should be probably ported to tools
     158            for (Node node : way.nodes) {
     159                if(askedAlready) break;
     160                boolean isInsideOneBoundingBox = false;
     161                for (Bounds b : bounds) {
     162                    if (b.contains(node.coor)) {
     163                        isInsideOneBoundingBox = true;
     164                        break;
     165                    }
     166                }
     167               
     168                if (!isInsideOneBoundingBox) {
     169                    int option = JOptionPane.showConfirmDialog(Main.parent,
     170                            tr("The selected way(s) have nodes outside of the downloaded data region.\n"
     171                                    + "This can lead to nodes being deleted accidentally.\n"
     172                                    + "Are you really sure to continue?"),
     173                            tr("Please abort if you are not sure"), JOptionPane.YES_NO_OPTION,
     174                            JOptionPane.WARNING_MESSAGE);
     175
     176                    if (option != JOptionPane.YES_OPTION) return;
     177                    askedAlready = true;
     178                    break;
     179                }
     180            }
     181             
     182            selWays[ways] = way;
     183            ways++;
     184        }
     185
     186        if (ways < 1) {
     187            JOptionPane.showMessageDialog(Main.parent, tr("Please select at least one closed way the should be joined."));
     188            return;
     189        }
     190               
     191        if(joinAreas(selWays[0], selWays[ways == 2 ? 1 : 0])) {
     192            Main.map.mapView.repaint();
     193            DataSet.fireSelectionChanged(Main.ds.getSelected());
     194        } else
     195            JOptionPane.showMessageDialog(Main.parent, tr("No intersections found. Nothing was changed."));
     196    }
     197   
     198
     199    /**
     200     * Will join two overlapping areas
     201     * @param Way First way/area
     202     * @param Way Second way/area
     203     * @return boolean Whether to display the "no operation" message
     204     */
     205    private boolean joinAreas(Way a, Way b) {
     206        // Fix self-overlapping first or other errors
     207        boolean same = a.equals(b);
     208        boolean hadChanges = false;
     209        if(!same) {
     210            if(checkForTagConflicts(a, b)) return true; // User aborted, so don't warn again
     211            hadChanges = joinAreas(a, a);
     212            hadChanges = joinAreas(b, b) || hadChanges;
     213        }
     214       
     215        ArrayList<OsmPrimitive> nodes = addIntersections(a, b);
     216        if(nodes.size() == 0) return hadChanges;
     217        commitCommands("Added node on all intersections"); 
     218       
     219        // Remove ways from all relations so ways can be combined/split quietly
     220        ArrayList<RelationRole> relations = removeFromRelations(a);
     221        if(!same) relations.addAll(removeFromRelations(b));
     222        if(relations.size() > 0)
     223           JOptionPane.showMessageDialog(Main.parent, tr("Some of the ways were part of relations that have been modified. Please verify no errors have been introduced."));
     224         
     225        Collection<Way> allWays = splitWaysOnNodes(a, b, nodes);
     226
     227        // Find all nodes and inner ways save them to a list
     228        Collection<Node> allNodes = getNodesFromWays(allWays);       
     229        Collection<Way> innerWays = findInnerWays(allWays, allNodes);
     230       
     231        // Join outer ways
     232        Way outerWay = joinOuterWays(allWays, innerWays);
     233       
     234        // Fix Multipolygons if there are any
     235        Collection<Way> newInnerWays = fixMultigons(innerWays, outerWay);
     236       
     237        // Delete the remaining inner ways
     238        if(innerWays.size() > 0)
     239            cmds.add(DeleteCommand.delete(innerWays, true));
     240        commitCommands("Restore inner ways that belong to multigons and delete remaining ways");
     241       
     242       
     243        // We can attach our new multipolygon relation and pretend it has always been there
     244        addOwnMultigonRelation(newInnerWays, outerWay, relations);
     245        fixRelations(relations, outerWay);
     246        commitCommands("Fix relations");
     247       
     248        stripTags(newInnerWays);
     249        makeCommitsOneAction(
     250            a.equals(b)
     251                ? "Joined self-overlapping area " + a.getName()
     252                : "Joined overlapping areas " + a.getName() + " and " + b.getName()
     253        );
     254
     255        return true;
     256    }
     257   
     258    /**
     259     * Checks if tags of two given ways differ, and presents the user a dialog to solve conflicts
     260     * @param Way First way to check
     261     * @param Way Second Way to check
     262     * @return boolean True if not all conflicts could be resolved, False if everything's fine
     263     */
     264    private boolean checkForTagConflicts(Way a, Way b) {
     265        ArrayList<Way> ways = new ArrayList<Way>();
     266        ways.add(a);
     267        ways.add(b);
     268   
     269        // This is mostly copied and pasted from CombineWayAction.java and one day should be moved into tools
     270        Map<String, Set<String>> props = new TreeMap<String, Set<String>>();
     271        for (Way w : ways) {
     272            for (Entry<String,String> e : w.entrySet()) {
     273                if (!props.containsKey(e.getKey()))
     274                    props.put(e.getKey(), new TreeSet<String>());
     275                props.get(e.getKey()).add(e.getValue());
     276            }
     277        }
     278       
     279        Way ax = new Way(a);
     280        Way bx = new Way(b);
     281       
     282        Map<String, JComboBox> components = new HashMap<String, JComboBox>();
     283        JPanel p = new JPanel(new GridBagLayout());
     284        for (Entry<String, Set<String>> e : props.entrySet()) {
     285            if (TigerUtils.isTigerTag(e.getKey())) {
     286                String combined = TigerUtils.combineTags(e.getKey(), e.getValue());
     287                ax.put(e.getKey(), combined);
     288                bx.put(e.getKey(), combined);
     289            } else if (e.getValue().size() > 1) {
     290                if("created_by".equals(e.getKey()))
     291                {
     292                    ax.put("created_by", "JOSM");
     293                    bx.put("created_by", "JOSM");
     294                } else {
     295                    JComboBox c = new JComboBox(e.getValue().toArray());
     296                    c.setEditable(true);
     297                    p.add(new JLabel(e.getKey()), GBC.std());
     298                    p.add(Box.createHorizontalStrut(10), GBC.std());
     299                    p.add(c, GBC.eol());
     300                    components.put(e.getKey(), c);
     301                }
     302            } else {
     303                String val = e.getValue().iterator().next();
     304                ax.put(e.getKey(), val);
     305                bx.put(e.getKey(), val);
     306            }
     307        }
     308       
     309        if (components.isEmpty())
     310            return false; // No conflicts found
     311       
     312        int result = new ExtendedDialog(Main.parent,
     313            tr("Enter values for all conflicts."),
     314            p,
     315            new String[] {tr("Solve Conflicts"), tr("Cancel")},
     316            new String[] {"dialogs/conflict.png", "cancel.png"}).getValue();
     317           
     318        if (result != 1) return true; // user cancel, unresolvable conflicts
     319       
     320        for (Entry<String, JComboBox> e : components.entrySet()) {
     321            String val = e.getValue().getEditor().getItem().toString();
     322            ax.put(e.getKey(), val);
     323            bx.put(e.getKey(), val);
     324        }
     325   
     326        cmds.add(new ChangeCommand(a, ax));
     327        cmds.add(new ChangeCommand(b, bx));
     328        commitCommands("Fix tag conflicts");
     329        return false;
     330    }
     331   
     332    /**
     333     * Will find all intersection and add nodes there for two given ways
     334     * @param Way First way
     335     * @param Way Second way
     336     * @return ArrayList<OsmPrimitive> List of new nodes
     337     */
     338    private ArrayList<OsmPrimitive> addIntersections(Way a, Way b) {
     339        boolean same = a.equals(b);
     340        int nodesSizeA = a.nodes.size();
     341        int nodesSizeB = b.nodes.size();
     342
     343        // We use OsmPrimitive here instead of Node because we later need to split a way at these nodes.
     344        // With OsmPrimitve we can simply add the way and don't have to loop over the nodes
     345        ArrayList<OsmPrimitive> nodes = new ArrayList<OsmPrimitive>();
     346        ArrayList<NodeToSegs> nodesA = new ArrayList<NodeToSegs>();
     347        ArrayList<NodeToSegs> nodesB = new ArrayList<NodeToSegs>();
     348
     349        for (int i = (same ? 1 : 0); i < nodesSizeA - 1; i++) {
     350            for (int j = (same ? i + 2 : 0); j < nodesSizeB - 1; j++) {
     351                // Avoid re-adding nodes that already exist on (some) intersections
     352                if(a.nodes.get(i).equals(b.nodes.get(j)) || a.nodes.get(i+1).equals(b.nodes.get(j)))   {
     353                    nodes.add(b.nodes.get(j));
     354                    continue;
     355                } else
     356                if(a.nodes.get(i).equals(b.nodes.get(j+1)) || a.nodes.get(i+1).equals(b.nodes.get(j+1))) {
     357                    nodes.add(b.nodes.get(j+1));
     358                    continue;
     359                }
     360                LatLon intersection = getLineLineIntersection(
     361                        a.nodes.get(i)  .eastNorth.east(), a.nodes.get(i)  .eastNorth.north(),
     362                        a.nodes.get(i+1).eastNorth.east(), a.nodes.get(i+1).eastNorth.north(),
     363                        b.nodes.get(j)  .eastNorth.east(), b.nodes.get(j)  .eastNorth.north(),
     364                        b.nodes.get(j+1).eastNorth.east(), b.nodes.get(j+1).eastNorth.north());
     365                if(intersection == null) continue;
     366               
     367                // Create the node. Adding them to the ways must be delayed because we still loop over them
     368                Node n = new Node(intersection);
     369                cmds.add(new AddCommand(n));
     370                nodes.add(n);
     371                // The distance is needed to sort and add the nodes in direction of the way
     372                nodesA.add(new NodeToSegs(i,  n, a.nodes.get(i).coor));
     373                if(same)
     374                    nodesA.add(new NodeToSegs(j,  n, a.nodes.get(j).coor));
     375                else   
     376                    nodesB.add(new NodeToSegs(j,  n, b.nodes.get(j).coor));
     377            }
     378        }
     379       
     380        addNodesToWay(a, nodesA);
     381        if(!same) addNodesToWay(b, nodesB);
     382       
     383        return nodes;
     384    }
     385   
     386    /**
     387     * Finds the intersection of two lines
     388     * @return LatLon null if no intersection was found, the LatLon coordinates of the intersection otherwise
     389     */
     390    static private LatLon getLineLineIntersection(
     391                double x1, double y1, double x2, double y2,
     392                double x3, double y3, double x4, double y4) {
     393               
     394        if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return null;
     395
     396        // Convert line from (point, point) form to ax+by=c
     397        double a1 = y2 - y1;
     398        double b1 = x1 - x2;
     399        double c1 = x2*y1 - x1*y2;
     400
     401        double a2 = y4 - y3;
     402        double b2 = x3 - x4;
     403        double c2 = x4*y3 - x3*y4;
     404
     405        // Solve the equations
     406        double det = a1*b2 - a2*b1;       
     407        if(det == 0) return null; // Lines are parallel
     408       
     409        return Main.proj.eastNorth2latlon(new EastNorth(
     410            (b1*c2 - b2*c1)/det,
     411            (a2*c1 -a1*c2)/det
     412        ));
     413    }
     414
     415    /**
     416     * Inserts given nodes with positions into the given ways
     417     * @param Way The way to insert the nodes into
     418     * @param Collection<NodeToSegs> The list of nodes with positions to insert
     419     */   
     420    private void addNodesToWay(Way a, ArrayList<NodeToSegs> nodes) {
     421        Way ax=new Way(a);
     422        List<NodeToSegs> newnodes = new ArrayList<NodeToSegs>();       
     423        Collections.sort(nodes);
     424       
     425        int numOfAdds = 1;
     426        for(NodeToSegs n : nodes) {
     427            ax.addNode(n.pos + numOfAdds, n.n);
     428            numOfAdds++;
     429        }
     430       
     431        cmds.add(new ChangeCommand(a, ax));
     432    }
     433   
     434    /**
     435     * Commits the command list with a description
     436     * @param String The description of what the commands do
     437     */
     438    private void commitCommands(String description) {
     439        switch(cmds.size()) {
     440            case 0:
     441                return;
     442            case 1:
     443                Main.main.undoRedo.add(cmds.getFirst());
     444                break;
     445            default:
     446                Command c = new SequenceCommand(tr(description), cmds);
     447                Main.main.undoRedo.add(c);
     448                break;
     449        }
     450       
     451        cmds.clear();
     452        cmdsCount++;
     453    }
     454   
     455    /**
     456     * Removes a given OsmPrimitive from all relations
     457     * @param OsmPrimitive Element to remove from all relations
     458     * @return ArrayList<RelationRole> List of relations with roles the primitives was part of
     459     */
     460    private ArrayList<RelationRole> removeFromRelations(OsmPrimitive osm) {
     461        ArrayList<RelationRole> result = new ArrayList<RelationRole>();
     462        for (Relation r : Main.ds.relations) {
     463            if (r.deleted || r.incomplete) continue;
     464            for (RelationMember rm : r.members) {
     465                if (rm.member != osm) continue;
     466               
     467                Relation newRel = new Relation(r);
     468                newRel.members.remove(rm);
     469               
     470                cmds.add(new ChangeCommand(r, newRel));
     471                RelationRole saverel =  new RelationRole(r, rm.role);
     472                if(!result.contains(saverel)) result.add(saverel);
     473                break;
     474            }
     475        }
     476       
     477        commitCommands("Removed Element from Relations");
     478        return result;
     479    }
     480
     481    /**
     482     * This is a hacky implementation to make use of the splitWayAction code and
     483     * should be improved. SplitWayAction needs to expose its splitWay function though.
     484     */
     485    private Collection<Way> splitWaysOnNodes(Way a, Way b, Collection<OsmPrimitive> nodes) {
     486        ArrayList<Way> ways = new ArrayList<Way>();
     487        ways.add(a);
     488        if(!a.equals(b)) ways.add(b);
     489       
     490        List<OsmPrimitive> affected = new ArrayList<OsmPrimitive>();
     491        for (Way way : ways) {
     492            nodes.add(way);
     493            Main.ds.setSelected(nodes);
     494            nodes.remove(way);
     495            new SplitWayAction().actionPerformed(null);
     496            cmdsCount++;
     497            affected.addAll(Main.ds.getSelectedWays());
     498        }
     499        return osmprim2way(affected);
     500    }
     501     
     502    /**
     503     * Converts a list of OsmPrimitives to a list of Ways
     504     * @param Collection<OsmPrimitive> The OsmPrimitives list that's needed as a list of Ways
     505     * @return Collection<Way> The list as list of Ways
     506     */
     507    static private Collection<Way> osmprim2way(Collection<OsmPrimitive> ways) {
     508        Collection<Way> result = new ArrayList<Way>();
     509        for(OsmPrimitive w: ways) {
     510            if(w instanceof Way) result.add((Way) w);
     511        }
     512        return result;
     513    }
     514   
     515    /**
     516     * Returns all nodes for given ways
     517     * @param Collection<Way> The list of ways which nodes are to be returned
     518     * @return Collection<Node> The list of nodes the ways contain
     519     */
     520    private Collection<Node> getNodesFromWays(Collection<Way> ways) {
     521        Collection<Node> allNodes = new ArrayList<Node>();
     522        for(Way w: ways) allNodes.addAll(w.nodes);
     523        return allNodes;
     524    }
     525   
     526    /**
     527     * Finds all inner ways for a given list of Ways and Nodes from a multigon by constructing a polygon
     528     * for each way, looking for inner nodes that are not part of this way. If a node is found, all ways
     529     * containing this node are added to the list
     530     * @param Collection<Way> A list of (splitted) ways that form a multigon
     531     * @param Collection<Node> A list of nodes that belong to the multigon
     532     * @return Collection<Way> A list of ways that are positioned inside the outer borders of the multigon
     533     */
     534    private Collection<Way> findInnerWays(Collection<Way> multigonWays, Collection<Node> multigonNodes) {
     535        Collection<Way> innerWays = new ArrayList<Way>();
     536        for(Way w: multigonWays) {           
     537            Polygon poly = new Polygon();
     538            for(Node n: ((Way)w).nodes) poly.addPoint(latlonToXY(n.coor.lat()), latlonToXY(n.coor.lon()));
     539           
     540            for(Node n: multigonNodes) {
     541                if(!((Way)w).nodes.contains(n) && poly.contains(latlonToXY(n.coor.lat()), latlonToXY(n.coor.lon()))) {
     542                    innerWays.addAll(getWaysByNode(multigonWays, n));
     543                }
     544            }
     545        }
     546       
     547        return innerWays;
     548    }
     549   
     550    // Polygon only supports int coordinates, so convert them
     551    private int latlonToXY(double val) {
     552        return (int)Math.round(val*1000000);
     553    }
     554   
     555    /**
     556     * Finds all ways that contain the given node.
     557     * @param Collection<OsmPrimitive> A collection of OsmPrimitives, but only ways will be honored
     558     * @param Node The node the ways should be checked against
     559     * @return Collection<Way> A list of ways that contain the given node
     560     */
     561    private Collection<Way> getWaysByNode(Collection<Way> w, Node n) {
     562        Collection<Way> deletedWays = new ArrayList<Way>();
     563        for(Way way : w) {
     564            if(!((Way)way).nodes.contains(n)) continue;
     565            if(!deletedWays.contains(way)) deletedWays.add(way); // Will need this later for multigons
     566        }
     567        return deletedWays;
     568    }
     569   
     570    /**
     571     * Joins the two outer ways and deletes all short ways that can't be part of a multipolygon anyway
     572     * @param Collection<OsmPrimitive> The list of all ways that belong to that multigon
     573     * @param Collection<Way> The list of inner ways that belong to that multigon
     574     * @return Way The newly created outer way
     575     */
     576    private Way joinOuterWays(Collection<Way> multigonWays, Collection<Way> innerWays) {
     577        ArrayList<Way> join = new ArrayList<Way>();
     578        for(Way w: multigonWays) {
     579            // Skip inner ways
     580            if(innerWays.contains(w)) continue;
     581           
     582            if(w.nodes.size() <= 2)
     583                cmds.add(new DeleteCommand(w));
     584            else
     585                join.add(w);
     586        }
     587
     588        commitCommands("Join Areas: Remove Short Ways");       
     589        return joinWays(join);
     590    }
     591   
     592    /**
     593     * Joins a list of ways (using CombineWayAction and ReverseWayAction if necessary to quiet the former)
     594     * @param ArrayList<Way> The list of ways to join
     595     * @return Way The newly created way
     596     */
     597    private Way joinWays(ArrayList<Way> ways) {
     598        if(ways.size() < 2) return ways.get(0);
     599        //Main.ds.setSelected(ways);       
     600       
     601        // This will turn ways so all of them point in the same direction and CombineAction won't bug
     602        // the user about this.
     603        Way a = null;
     604        for(Way b : ways) {
     605            if(a == null) {
     606                a = b;
     607                continue;
     608            }
     609            if(a.nodes.get(0).equals(b.nodes.get(0)) ||
     610               a.nodes.get(a.nodes.size()-1).equals(b.nodes.get(b.nodes.size()-1))) {
     611                Main.ds.setSelected(b);
     612                new ReverseWayAction().actionPerformed(null);
     613                cmdsCount++;
     614            }         
     615            a = b;
     616        }
     617        Main.ds.setSelected(ways);
     618        new CombineWayAction().actionPerformed(null);
     619        cmdsCount++;
     620        return (Way)(Main.ds.getSelectedWays().toArray())[0];
     621    }
     622   
     623    /**
     624     * Finds all ways that may be part of a multipolygon relation and removes them from the given list.
     625     * It will automatically combine "good" ways
     626     * @param Collection<Way> The list of inner ways to check
     627     * @param Way The newly created outer way
     628     * @return ArrayList<Way> The List of newly created inner ways
     629     */
     630    private ArrayList<Way> fixMultigons(Collection<Way> uninterestingWays, Way outerWay) {
     631        Collection<Node> innerNodes = getNodesFromWays(uninterestingWays);
     632        Collection<Node> outerNodes = outerWay.nodes;
     633       
     634        // The newly created inner ways. uninterestingWays is passed by reference and therefore modified in-place
     635        ArrayList<Way> newInnerWays = new ArrayList<Way>();
     636       
     637        // Now we need to find all inner ways that contain a remaining node, but no outer nodes
     638        // Remaining nodes are those that contain to more than one way. All nodes that belong to an
     639        // inner multigon part will have at least two ways, so we can use this to find which ways do
     640        // belong to the multigon.
     641        Collection<Way> possibleWays = new ArrayList<Way>();
     642        wayIterator: for(Way w : uninterestingWays) {
     643            boolean hasInnerNodes = false;
     644            for(Node n : w.nodes) {
     645                if(outerNodes.contains(n)) continue wayIterator;
     646                if(!hasInnerNodes && innerNodes.contains(n)) hasInnerNodes = true;
     647            }
     648            if(!hasInnerNodes && w.nodes.size() >= 2) continue;
     649            possibleWays.add(w);
     650        }
     651       
     652        // Join all ways that have one start/ending node in common
     653        Way joined = null;
     654        outerIterator: do {
     655            joined = null;
     656            for(Way w1 : possibleWays) {
     657                if(w1.isClosed()) {
     658                    // The way is already closed
     659                    uninterestingWays.remove(w1);
     660                    if(!newInnerWays.contains(w1))
     661                        newInnerWays.add(w1);                   
     662                    continue;
     663                }
     664                for(Way w2 : possibleWays) {
     665                    if(w2.isClosed() || !checkIfWaysCanBeCombined(w1, w2)) continue;
     666                   
     667                    ArrayList<Way> joinThem = new ArrayList<Way>();
     668                    joinThem.add(w1);
     669                    joinThem.add(w2);
     670                    uninterestingWays.removeAll(joinThem);
     671                    possibleWays.removeAll(joinThem);
     672                   
     673                    newInnerWays.add(joinWays(joinThem));
     674                    continue outerIterator;
     675                }
     676            }
     677        } while(joined != null);
     678
     679        return newInnerWays;
     680    }   
     681   
     682    /**
     683     * Checks if two ways share one starting/ending node
     684     * @param Way first way
     685     * @param Way second way
     686     * @return boolean Wheter the ways share a starting/ending node or not
     687     */
     688    private boolean checkIfWaysCanBeCombined(Way w1, Way w2) {
     689        if(w1.equals(w2)) return false;
     690       
     691        if(w1.nodes.get(0).equals(w2.nodes.get(0))) return true;
     692        if(w1.nodes.get(0).equals(w2.nodes.get(w2.nodes.size()-1))) return true;
     693       
     694        if(w1.nodes.get(w1.nodes.size()-1).equals(w2.nodes.get(0))) return true;
     695        if(w1.nodes.get(w1.nodes.size()-1).equals(w2.nodes.get(w2.nodes.size()-1))) return true;
     696       
     697        return false;
     698    }
     699   
     700    /**
     701     * Will add own multipolygon relation to the "previously existing" relations. Fixup is done by fixRelations
     702     * @param Collection<Way> List of already closed inner ways
     703     * @param Way The outer way
     704     * @param ArrayList<RelationRole> The list of relation with roles to add own relation to
     705     */
     706    private void addOwnMultigonRelation(Collection<Way> inner, Way outer, ArrayList<RelationRole> rels) {
     707        if(inner.size() == 0) return;
     708        // Create new multipolygon relation and add all inner ways to it
     709        Relation newRel = new Relation();
     710        newRel.put("type", "multipolygon");
     711        for(Way w : inner)
     712            newRel.members.add(new RelationMember("inner", w));
     713        cmds.add(new AddCommand(newRel));   
     714       
     715        // We don't add outer to the relation because it will be handed to fixRelations()
     716        // which will then do the remaining work. Collections are passed by reference, so no
     717        // need to return it
     718        rels.add(new RelationRole(newRel, "outer"));
     719        //return rels;
     720    }
     721   
     722    /**
     723     * Adds the previously removed relations again to the outer way. If there are multiple multipolygon
     724     * relations where the joined areas were in "outer" role a new relation is created instead with all
     725     * members of both. This function depends on multigon relations to be valid already, it won't fix them.
     726     * @param ArrayList<RelationRole> List of relations with roles the (original) ways were part of
     727     * @param Way The newly created outer area/way
     728     */
     729    private void fixRelations(ArrayList<RelationRole> rels, Way outer) {
     730        ArrayList<RelationRole> multiouters = new ArrayList<RelationRole>();
     731        for(RelationRole r : rels) {
     732            if( r.rel.get("type") != null &&
     733                r.rel.get("type").equalsIgnoreCase("multipolygon") &&
     734                r.role.equalsIgnoreCase("outer")
     735              ) {
     736                multiouters.add(r);
     737                continue;
     738            }
     739            // Add it back!
     740            Relation newRel = new Relation(r.rel);
     741            newRel.members.add(new RelationMember(r.role, outer));               
     742            cmds.add(new ChangeCommand(r.rel, newRel));
     743        }
     744       
     745        Relation newRel = null;
     746        switch(multiouters.size()) {
     747            case 0:
     748                return;
     749            case 1:
     750                // Found only one to be part of a multipolygon relation, so just add it back as well
     751                newRel = new Relation(multiouters.get(0).rel);
     752                newRel.members.add(new RelationMember(multiouters.get(0).role, outer));               
     753                cmds.add(new ChangeCommand(multiouters.get(0).rel, newRel));
     754                return;
     755            default:
     756                // Create a new relation with all previous members and (Way)outer as outer.
     757                newRel = new Relation();
     758                for(RelationRole r : multiouters) {
     759                    // Add members
     760                    for(RelationMember rm : r.rel.members)
     761                        if(!newRel.members.contains(rm)) newRel.members.add(rm);
     762                    // Add tags
     763                    for (String key : r.rel.keys.keySet()) {
     764                        newRel.put(key, r.rel.keys.get(key));
     765                    }
     766                    // Delete old relation
     767                    cmds.add(new DeleteCommand(r.rel));
     768                }
     769                newRel.members.add(new RelationMember("outer", outer));
     770                cmds.add(new AddCommand(newRel));
     771        }
     772    }
     773   
     774    /**
     775     * @param Collection<Way> The List of Ways to remove all tags from
     776     */
     777    private void stripTags(Collection<Way> ways) {
     778        for(Way w: ways) stripTags(w);
     779        commitCommands("Remove tags from inner ways");
     780    }
     781   
     782    /**
     783     * @param Way The Way to remove all tags from
     784     */
     785    private void stripTags(Way x) {
     786        if(x.keys == null) return;
     787        Way y = new Way(x);
     788        for (String key : x.keys.keySet())
     789            y.remove(key);
     790        cmds.add(new ChangeCommand(x, y));
     791    }
     792   
     793    /**
     794     * Takes the last cmdsCount actions back and combines them into a single action
     795     * (for when the user wants to undo the join action)
     796     * @param String The commit message to display
     797     */
     798    private void makeCommitsOneAction(String message) {
     799        UndoRedoHandler ur = Main.main.undoRedo;
     800        cmds.clear();
     801        for(int i = ur.commands.size() - cmdsCount; i < ur.commands.size(); i++) 
     802            cmds.add(ur.commands.get(i));
     803       
     804        for(int i = 0; i < cmdsCount; i++)
     805            ur.undo();
     806           
     807        commitCommands(message == null ? "Join Areas Function" : message);
     808        cmdsCount = 0;
     809    }
     810}
  • src/UtilsPlugin/UtilsPlugin.java

     
    2222
    2323public class UtilsPlugin extends Plugin {
    2424    JMenuItem SimplifyWay;
     25    JMenuItem JoinAreas;
    2526    JumpToAction JumpToAct = new JumpToAction();
    2627
    2728    public UtilsPlugin() {
    2829        SimplifyWay = MainMenu.add(Main.main.menu.toolsMenu, new SimplifyWayAction());
     30        JoinAreas = MainMenu.add(Main.main.menu.toolsMenu, new JoinAreasAction());
    2931        SimplifyWay.setEnabled(false);
    3032    }
    3133
     
    3335    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
    3436        if (oldFrame == null && newFrame != null) {
    3537            SimplifyWay.setEnabled(true);
     38            JoinAreas.setEnabled(true);
    3639            newFrame.statusLine.addMouseListener(JumpToAct);
    3740        }
    3841    }