source: subversion/applications/editors/potlatch/way.as @ 18822

Last change on this file since 18822 was 18749, checked in by richard, 10 years ago

Potlatch 1.3

File size: 53.9 KB
Line 
1
2        // =====================================================================================
3        // OOP classes - OSMWay
4
5        // ---- Initialise
6       
7        function OSMWay() {
8                this.resetBBox();
9                this.path=new Array();                  // list of nodes
10                this.attr=new Array();                  // hash of tags
11                this.mergedways=new Array();    // list of ways merged into this
12                this.deletednodes=new Object(); // hash of nodes deleted from this
13        };
14
15        OSMWay.prototype=new MovieClip();
16        OSMWay.prototype.clean=true;                            // altered since last upload?
17        OSMWay.prototype.uploading=false;                       // currently uploading?
18        OSMWay.prototype.locked=false;                          // locked against upload?
19        OSMWay.prototype.version=0;                                     // version number?
20        OSMWay.prototype.uid=0;                                         // user ID (used for TIGER detection)
21        OSMWay.prototype.historic=false;                        // is this an undeleted, not-uploaded way?
22        OSMWay.prototype.checkconnections=false;        // check shared nodes on reload
23
24        // ---- Load from remote server
25
26        OSMWay.prototype.load=function() {
27                responder = function() { };
28                responder.onResult = function(result) {
29                        _root.waysreceived+=1;
30                        var code=result.shift(); var msg=result.shift(); if (code) { handleError(code,msg,result); return; }
31                        var w=result[0];
32                        if (length(result[1])==0) { removeMovieClip(_root.map.ways[w]); 
33                                                                                removeMovieClip(_root.map.areas[w]); return; }
34                        var i,id,x,y,prepoint,redrawws;
35                        _root.map.ways[w].clean=true;
36                        _root.map.ways[w].locked=false;
37                        _root.map.ways[w].historic=false;
38                        _root.map.ways[w].version=result[3];
39                        _root.map.ways[w].uid=result[4];
40                        _root.map.ways[w].removeNodeIndex();
41                        _root.map.ways[w].path=[];
42                        _root.map.ways[w].resetBBox();
43                        for (i=0; i<result[1].length; i++) {
44                                x =result[1][i][0];
45                                y =result[1][i][1];
46                                id=result[1][i][2];
47                                _root.map.ways[w].updateBBox(x,y);
48                                x=long2coord(x); y=lat2coord(y);
49                                if (nodes[id]) {
50                                        // already exists: move node in other ways if required
51                                        // ** maybe we should take out 'w'? not sure
52                                        if (_root.map.ways[w].checkconnections) { nodes[id].moveTo(x,y,w); }
53                                } else {
54                                        // doesn't exist, so create new node
55                                        _root.nodes[id]=new Node(id,x,y,result[1][i][3],result[1][i][4]);
56                                        _root.nodes[id].clean=true;
57                                        if (id==prenode) { prepoint=i; }
58                                }
59                                _root.map.ways[w].path.push(_root.nodes[id]);
60                                _root.nodes[id].addWay(w);
61                                if (_root.nodes[id].ways[_root.wayselected] && w!=_root.wayselected) { redrawws=true; }
62                        }
63                        _root.map.ways[w].attr=result[2];
64                        _root.map.ways[w].redraw();
65                        if (redrawws)  { _root.ws.highlightPoints(5000,"anchor"); }     // need to redraw [_]s if new connections loaded
66                        if (w==preway) { _root.map.ways[w].select(); preway=undefined; }
67                        if (prepoint)  { _root.map.ways[w].select(); 
68                                                         _root.map.anchors[prepoint].select();
69                                                         prenode=prepoint=undefined; }
70                        _root.map.ways[w].clearPOIs();
71                        _root.map.ways[w].checkconnections=false;
72                };
73                remote_read.call('getway',responder,Math.floor(this._name));
74        };
75
76        OSMWay.prototype.loadFromDeleted=function(timestamp) {
77                delresponder=function() { };
78                delresponder.onResult=function(result) {
79                        var code=result.shift(); var msg=result.shift(); if (code) { handleError(code,msg,result); return; }
80                        var i,id,n;
81                        var w=_root.map.ways[result[0]];
82                        var oldpath=new Object();               // make sure to delete any nodes not in reverted way
83                        for (i=0; i<w.path.length; i++) { oldpath[w.path[i].id]=w.path[i].version; }
84                        if (result[4]) { w.historic=false; w.clean=true;  }
85                                  else { w.historic=true;  w.clean=false; }
86                        w.version=result[3];
87                        w.removeNodeIndex();
88                        w.path=[];
89                        w.resetBBox();
90                        for (i=0; i<result[1].length; i++) {
91                                n=result[1][i];
92                                x=n[0]; y=n[1]; id=n[2];                                        // 3:current version, 4:tags, 5:reuse?
93                                if (id<0) { id=--_root.newnodeid; }                     // assign negative IDs to anything moved
94                                delete oldpath[id];
95                                w.updateBBox(x,y);
96                                x=long2coord(x); y=lat2coord(y);
97                                if (nodes[id]) {
98                                        if (x!=nodes[id].x || y!=nodes[id].y) {
99                                                // ** also needs to check that tags haven't changed
100                                                nodes[id].moveTo(x,y,w);
101                                                nodes[id].attr=n[4];
102                                                nodes[id].version=n[3];
103                                                w.clean=false;
104                                        }
105                                        if (n[5]) { nodes[id].clean=true; }             // is visible and current version
106                                } else {
107                                        _root.nodes[id]=new Node(id,x,y,n[4],n[3]);
108                                }
109                                w.path.push(_root.nodes[id]);
110                                _root.nodes[id].addWay(result[0]);
111                        }
112                        for (i in oldpath) { if (i>0) { w.deletednodes[i]=oldpath[i]; } }
113                        w.attr=result[2];
114                        if (w==ws) { w.select(); }
115                                  else { w.locked=true; }
116                        w.redraw();
117                        w.clearPOIs();
118                };
119                remote_read.call('getway_old',delresponder,Number(this._name),timestamp);
120        };
121
122        OSMWay.prototype.clearPOIs=function() {
123                // check if any way nodes are POIs, delete the POIs if so
124                var i,z;
125                z=this.path;
126                for (i in z) {
127                        if (_root.map.pois[this.path[i].id]) { removeMovieClip(_root.map.pois[this.path[i].id]); }
128                }
129        };
130
131        // ---- Draw line
132
133        OSMWay.prototype.redraw=function(skip,skiponeway) {
134                this.createEmptyMovieClip("taggednodes",3);
135
136                if (skip && !this.attr["oneway"]) {
137                        // We're at the same scale as previously, so don't redraw
138                        // ** will refactor this when we do proper POI icons
139                        for (var i=0; i<this.path.length; i+=1) {
140                                if (this.path[i].tagged) {
141                                        this.taggednodes.attachMovie("poiinway",i,i);
142                                        this.taggednodes[i]._x=this.path[i].x;
143                                        this.taggednodes[i]._y=this.path[i].y;
144                                        this.taggednodes[i]._xscale=this.taggednodes[i]._yscale=taggedscale;
145                                }
146                        }
147                } else {
148
149                        // Either the line has changed, or we've changed scale
150                        this.createEmptyMovieClip("line",1);                                    // clear line
151                        this.createEmptyMovieClip("arrows",2);                                  //  |
152                        var linealpha=100; // -50*(this.locked==true);
153                        var casingx=(scale<17 || preferences.data.thinlines) ? 1.5 : 1.3; var casingcol=0x222222;
154       
155                        // Set stroke
156       
157                        var f=this.getFill();
158                        if              (this.locked)                                    { this.line.lineStyle(linewidth,0xFF0000,linealpha,false,"none"); }
159                        else if (colours[this.attr["highway"]])  { this.line.lineStyle(linewidth,colours[this.attr["highway" ]],linealpha,false,"none"); }
160                        else if (colours[this.attr["waterway"]]) { this.line.lineStyle(linewidth,colours[this.attr["waterway"]],linealpha,false,"none"); }
161                        else if (colours[this.attr["railway"]])  { this.line.lineStyle(linewidth,colours[this.attr["railway" ]],linealpha,false,"none"); }
162                        else {
163                                var c=0xAAAAAA; var z=this.attr;
164                                for (var i in z) { if (i!='created_by' && this.attr[i]!='' && this.attr[i].substr(0,6)!='(type ') { c=0x707070; } }
165                                this.line.lineStyle((f>-1 || this.attr['boundary']) ? areawidth : linewidth,c,linealpha,false,"none");
166                        }
167                       
168                        // Draw fill/casing
169       
170                        var br=false;
171                        if (preferences.data.tiger && this.uid==7168 && this.version==1 && this.clean && this.attr["tiger:tlid"]) {
172                casingx=2; casingcol=0xFF00FF;
173                        } else if (preferences.data.noname && this.attr["highway"] && (!this.attr["name"] || this.attr["name"].substr(0,6)=='(type ')) {
174                casingx=2; casingcol=0xFF0000;
175                        } else if (this.attr["bridge"] && this.attr["bridge"]!="no") {
176                                casingx=(scale<17) ? 2 : 1.8; br=true;
177                        }
178
179                        if ((f>-1 || br || casing[this.attr['highway']]) && !this.locked) {
180                                if (!_root.map.areas[this._name]) { _root.map.areas.createEmptyMovieClip(this._name,++areadepth); }
181                                with (_root.map.areas[this._name]) {
182                                        clear();
183                                        enabled=false;
184                                        moveTo(this.path[0].x,this.path[0].y); 
185                                        if (f>-1) { beginFill(f,20); }
186                                                 else { lineStyle(linewidth*casingx,casingcol,100,false,"none"); }
187                                        for (var i=1; i<this.path.length; i+=1) {
188                                                lineTo(this.path[i].x,this.path[i].y);
189                                        }
190                                        if (f>-1) { endFill(); }
191                                };
192                        } else if (_root.map.areas[this._name]) {
193                                removeMovieClip(_root.map.areas[this._name]);
194                        }
195       
196                        // Draw line and tagged nodes
197       
198                        this.line.moveTo(this.path[0].x,this.path[0].y);
199                        for (var i=0; i<this.path.length; i+=1) {
200                                if (i>0) { this.line.lineTo(this.path[i].x,this.path[i].y); }
201                                if (this.path[i].tagged) {
202                                        // **** attach correct icon:
203                                        // if (this.path[i].attr['frog']) {
204                                        // this.taggednodes.attachMovie("poi_22",i,i);
205                                        // ...probably don't have to do _root.map.pois[point].__proto__=undefined;
206                                        //    because clicks are handled by the way movieclip
207                                        this.taggednodes.attachMovie("poiinway",i,i);
208                                        this.taggednodes[i]._x=this.path[i].x;
209                                        this.taggednodes[i]._y=this.path[i].y;
210                                        this.taggednodes[i]._xscale=this.taggednodes[i]._yscale=taggedscale;
211                                }
212                        }
213
214                        // Draw relations
215
216                        var z=_root.wayrels[this._name];
217                        for (var rel in z) { _root.map.relations[rel].redraw(); }
218                       
219                        // Draw direction arrows
220                       
221                        if (this.attr["oneway"] && !skiponeway) {
222                                this.drawArrows(this.attr["oneway"]);
223                        }
224                }
225        };
226
227        OSMWay.prototype.getFill=function() {
228                var f=-1; 
229                if (this.path[this.path.length-1]==this.path[0] && this.path.length>2) {
230                        if (this.attr['area']) { f='0x777777'; }
231                        var z=this.attr;
232                        for (var i in z) { if (areas[i] && this.attr[i]!='' && this.attr[i]!='coastline') { f=areas[i]; } }
233                }
234                return f;
235        };
236
237        OSMWay.prototype.drawArrows=function(dir) {
238                if (dir=='no' || dir=='false') { return; }
239                var dashes=[4,1,1,1,1,10];
240                var widths=[2,7,5,3,1,0];
241                if (dir=="-1") { dashes.reverse(); widths.reverse(); }
242                var arrowcol=0x6C70D5; if (this.attr['highway']=='primary' || this.attr['highway']=='primary_link' || 
243                                                                   this.attr['highway']=='trunk' || this.attr['highway']=='trunk_link' || 
244                                                                   this.attr['highway']=='motorway' || this.attr['highway']=='motorway_link') { arrowcol=0; }
245               
246                var draw=false;
247                var dashleft=0;
248                var segleft=0;
249                var dc=[]; var wc=[];
250                var a,xc,yc,curx,cury,dx,dy;
251                var i=0;
252
253                var g=this.arrows;
254                g.moveTo(this.path[0].x,this.path[0].y);
255                while (i<this.path.length-1 || segleft>0) {
256                        if (dashleft<=0) {
257                                if (dc.length==0) { dc=dashes.slice(0); wc=widths.slice(0); }
258                                dashleft=dc.shift()/bscale*2;
259                                dashwidth=wc.shift();
260                        }
261                        if (segleft<=0) {
262                                curx=this.path[i].x; dx=this.path[i+1].x-curx;
263                                cury=this.path[i].y; dy=this.path[i+1].y-cury;
264                                a=Math.atan2(dy,dx); xc=Math.cos(a); yc=Math.sin(a);
265                                segleft=Math.sqrt(dx*dx+dy*dy);
266                                i++;
267                        }
268
269                        if (segleft<=dashleft) {
270                                // the path segment is shorter than the dash
271                                curx+=dx; cury+=dy;
272                                moveLine(g,curx,cury,dashwidth,arrowcol);
273                                dashleft-=segleft; segleft=0;
274                        } else {
275                                // the path segment is longer than the dash
276                                curx+=dashleft*xc; dx-=dashleft*xc;
277                                cury+=dashleft*yc; dy-=dashleft*yc;
278                                moveLine(g,curx,cury,dashwidth,arrowcol);
279                                segleft-=dashleft; dashleft=0;
280                        }
281                }
282        };
283
284        function moveLine(g,x,y,dashwidth,colour) {
285                if (dashwidth==0) {
286                        g.moveTo(x,y);
287                } else {
288                        g.lineStyle(dashwidth,colour,100,false,"none","none");
289                        g.lineTo(x,y);
290                }
291        };
292
293        // ---- Tidy in line/circle
294       
295        OSMWay.prototype.tidy=function() {
296                var a=this.path[0]; var b=this.path[this.path.length-1];
297                var w=new Object(); w[this._name]=true;
298                if (a.id==b.id) {
299                        // Tidy in circle
300                        if (this.path.length<4) { return; }
301                        this.saveChangeUndo();
302                       
303                        // Find centre-point
304                        var patharea=0;
305                        var cx=0; var lx=b.x; //coord2long(b.x);
306                        var cy=0; var ly=b.y; //coord2y(b.y);
307                        for (var i=0; i<this.path.length; i++) {
308                var latp=this.path[i].y; //coord2y(this.path[i].y);
309                var lon =this.path[i].x; // coord2long(this.path[i].x);
310                                var sc = (lx*latp-lon*ly); //*masterscale;
311                                cx += (lx+lon)*sc;
312                                cy += (ly+latp)*sc;
313                                patharea += sc;
314                                lx=lon; ly=latp;
315                        }
316                        patharea/=2;
317                        cx=cx/patharea/6; //long2coord(cx/patharea/6);
318                        cy=cy/patharea/6; //y2coord(cy/patharea/6);
319
320                        // Average distance to centre
321                        var d=0; var angles=[];
322                        for (var i=0; i<this.path.length; i++) {
323                                d+=Math.sqrt(Math.pow(this.path[i].x-cx,2)+Math.pow(this.path[i].y-cy,2));
324                        }
325                        d=d/this.path.length;
326                       
327                        // Move each node
328                        for (var i=0; i<this.path.length-1; i++) {
329                                var c=Math.sqrt(Math.pow(this.path[i].x-cx,2)+Math.pow(this.path[i].y-cy,2));
330                                this.path[i].x=cx+(this.path[i].x-cx)/c*d;
331                                this.path[i].y=cy+(this.path[i].y-cy)/c*d;
332                                this.path[i].clean=false;
333                                var l=this.path[i].ways; for (var o in l) { w[o]=true; }
334                        }
335
336                        // Insert extra nodes to make circle
337                        // clockwise: angles decrease, wrapping round from -170 to 170
338                        var newpath=[]; var diff,b;
339                        var clockwise=_root.panel.i_clockwise._visible;
340                        for (var i=0; i<this.path.length-1; i++) {
341                                var j=(i+1) % this.path.length;
342                                newpath.push(this.path[i]);
343                                a1=Math.atan2(this.path[i].x-cx,this.path[i].y-cy)*(180/Math.PI);
344                                a2=Math.atan2(this.path[j].x-cx,this.path[j].y-cy)*(180/Math.PI);
345
346                                if (clockwise) {
347                                        if (a2>a1) { a2=a2-360; }
348                                        diff=a1-a2;
349                                        if (diff>20) {
350                                                for (var ang=a1-20; ang>a2+10; ang-=20) {
351                                                        _root.newnodeid--;
352                                                        _root.nodes[newnodeid]=new Node(newnodeid,cx+Math.sin(ang*Math.PI/180)*d,cy+Math.cos(ang*Math.PI/180)*d,new Object(),0);
353                                                        _root.nodes[newnodeid].addWay(this._name);
354                                                        newpath.push(_root.nodes[newnodeid]);
355                                                }
356                                        }
357                                } else {
358                                        if (a1>a2) { a1=a1-360; }
359                                        diff=a2-a1;
360                                        if (diff>20) {
361                                                for (var ang=a1+20; ang<a2-10; ang+=20) {
362                                                        _root.newnodeid--;
363                                                        _root.nodes[newnodeid]=new Node(newnodeid,cx+Math.sin(ang*Math.PI/180)*d,cy+Math.cos(ang*Math.PI/180)*d,new Object(),0);
364                                                        _root.nodes[newnodeid].addWay(this._name);
365                                                        newpath.push(_root.nodes[newnodeid]);
366                                                }
367                                        }
368                                }
369                        }
370                        newpath.push(this.path[this.path.length-1]);
371                        this.path=newpath;
372
373                } else {
374                        // Tidy in line
375                        if (this.path.length<3) { return; }
376
377                        // First, calculate divergence to make sure we're not straightening a really bendy way
378                        var d=0; var t,x1,y1;
379                        var thislat=coord2lat(_root.map._y); var latfactor=Math.cos(thislat/(180/Math.PI));
380                        for (var i=1; i<this.path.length-1; i++) {
381                                u=((this.path[i].x-a.x)*(b.x-a.x)+
382                                   (this.path[i].y-a.y)*(b.y-a.y))/
383                                   (Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2));
384                                x1=a.x+u*(b.x-a.x);
385                                y1=a.y+u*(b.y-a.y);
386                                t=Math.sqrt(Math.pow(x1-this.path[i].x,2)+Math.pow(y1-this.path[i].y,2));
387                                t=Math.floor((111200*latfactor)*t/masterscale);
388                                if (t>d) { d=t; }
389                        }
390                        if (d>50 && !Key.isDown(Key.SHIFT)) { setAdvice(false,iText('advice_bendy')); return; }
391                        this.saveChangeUndo();
392
393                        var el=long2coord(_root.bigedge_l);             // We don't want to delete any off-screen nodes
394                        var er=long2coord(_root.bigedge_r);             // (because they might be used in other ways)
395                        var et= lat2coord(_root.bigedge_t);
396                        var eb= lat2coord(_root.bigedge_b);
397                        var retain=new Array(a);
398                        for (var i=1; i<this.path.length-1; i++) {
399                                if (this.path[i].numberOfWays()>1 || hasTags(this.path[i].attr) ||
400                                        this.path[i].x<el || this.path[i].x>er || 
401                                        this.path[i].y<et || this.path[i].y>eb) {
402                                        u=((this.path[i].x-a.x)*(b.x-a.x)+
403                                           (this.path[i].y-a.y)*(b.y-a.y))/
404                                           (Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2));
405                                        this.path[i].x=a.x+u*(b.x-a.x);
406                                        this.path[i].y=a.y+u*(b.y-a.y);
407                                        this.path[i].clean=false;
408                                        var l=this.path[i].ways; for (var o in l) { w[o]=true; }
409                                        retain.push(this.path[i]);
410                                } else {
411                                        this.markAsDeleted(this.path[i],false);
412                                        memberDeleted('Node', this.path[i].id);
413                                }
414                        }
415                        retain.push(b);
416                        this.path=retain;
417                        this.removeDuplicates();
418                }
419                this.clean=false; markClean(false);
420                for (var i in w) { _root.map.ways[i].redraw(); }
421                this.select();
422        };
423
424
425
426        // ---- Show direction
427
428        OSMWay.prototype.direction=function() {
429                if (this.path.length<2) {
430                        _root.panel.i_clockwise._visible=false;
431                        _root.panel.i_anticlockwise._visible=false;
432                        _root.panel.i_direction._visible=true;
433                        _root.panel.i_direction._alpha=50;
434                } else {
435                        var dx=this.path[this.path.length-1].x-this.path[0].x;
436                        var dy=this.path[this.path.length-1].y-this.path[0].y;
437                        if (dx!=0 || dy!=0) {
438                                // Non-circular
439                                _root.panel.i_direction._rotation=180-Math.atan2(dx,dy)*(180/Math.PI)-45;
440                                _root.panel.i_direction._alpha=100;
441                                _root.panel.i_direction._visible=true;
442                                _root.panel.i_clockwise._visible=false;
443                                _root.panel.i_anticlockwise._visible=false;
444                        } else {
445                                // Circular
446                                _root.panel.i_direction._visible=false;
447                                // Find lowest rightmost point
448                                // cf http://geometryalgorithms.com/Archive/algorithm_0101/
449                                var lowest=0;
450                                var xmax=-999999; var ymin=-999999;
451                                for (var i=0; i<this.path.length; i++) {
452                                        if      (this.path[i].y> ymin) { lowest=i; xmin=this.path[i].x; ymin=this.path[i].y; }
453                                        else if (this.path[i].y==ymin
454                                                  && this.path[i].x> xmax) { lowest=i; xmin=this.path[i].x; ymin=this.path[i].y; }
455                                }
456                                var clockwise=(this.onLeft(lowest)>0);
457                                _root.panel.i_clockwise._visible=clockwise;
458                                _root.panel.i_anticlockwise._visible=!clockwise;
459                        }
460                }
461        };
462
463        OSMWay.prototype.onLeft=function(j) {
464                var left=0;
465                if (this.path.length>=3) {
466                        var i=j-1; if (i==-1) { i=this.path.length-2; }
467                        var k=j+1; if (k==this.path.length) { k=1; }
468                        left=((this.path[j].x-this.path[i].x) * (this.path[k].y-this.path[i].y) -
469                                  (this.path[k].x-this.path[i].x) * (this.path[j].y-this.path[i].y));
470                }
471                return left;
472        };
473       
474
475
476        // ---- Remove from server
477       
478        OSMWay.prototype.remove=function() {
479                clearFloater();
480                memberDeleted('Way', this._name);
481                var z=this.path; for (var i in z) {
482                        if (z[i].numberOfWays()==1) { memberDeleted('Node', z[i].id); }
483                }
484                uploadDirtyRelations();
485
486                this.deleteMergedWays();
487                this.removeNodeIndex();
488
489                if (!this.historic && !this.locked) {
490                        var z=shallowCopy(this.path); this.path=new Array();
491                        for (var i in z) { 
492                                this.markAsDeleted(z[i],false); }
493                        if (_root.sandbox) {
494                                if (this._name>=0) { 
495                                        _root.waystodelete[this._name]=[this.version,deepCopy(this.deletednodes)];
496                                } else {
497                                        var z=this.deletednodes; for (var j in z) { deleteNodeAsPOI(j); }
498                                        var z=this.path;         for (var j in z) { deleteNodeAsPOI(j.id); }
499                                        markClean(false);
500                                }
501                        } else if (this._name>=0) {
502                                if (renewChangeset()) { return; }
503                                deleteresponder = function() { };
504                                deleteresponder.onResult = function(result) { deletewayRespond(result); };
505                                _root.writesrequested++;
506                                remote_write.call('deleteway',deleteresponder,_root.usertoken,_root.changeset,Number(this._name),Number(this.version),this.deletednodes);
507                        }
508                }
509
510                if (this._name==wayselected) { stopDrawing(); deselectAll(); }
511                removeMovieClip(_root.map.areas[this._name]);
512                removeMovieClip(this);
513        };
514
515        function deletewayRespond(result) {
516                _root.writesrequested--;
517                var code=result.shift(); var msg=result.shift(); if (code) { handleError(code,msg,result); return; }
518                var z=result[2]; for (var i in z) { delete _root.nodes[i]; }
519                operationDone(result[0]);
520                freshenChangeset();
521        };
522
523        function deleteNodeAsPOI(nid) {
524                if (nid<0) { return; }
525                if (_root.nodes[nid].numberOfWays()>0) { return; }
526                // if we're not going to send a deleteway for this (because it's negative - probably due to a split),
527                // then delete it as a POI instead
528                var n=_root.nodes[nid];
529                _root.poistodelete[nid]=[n.version,coord2long(n.x),coord2lat(n.y),deepCopy(n.attr)];
530        }
531
532        // ---- Variant with confirmation if any nodes have tags
533       
534        OSMWay.prototype.removeWithConfirm=function() {
535                var c=true;
536                var z=this.path;
537                for (var i in z) {
538                        if (this.path[i].tagged && hashLength(this.path[i].ways)==1) { c=false; }
539                }
540                if (c) {
541                        _root.ws.saveDeleteUndo(iText('deleting'));
542                        this.remove();
543                        markClean(true);
544                } else {
545                        _root.windows.attachMovie("modal","confirm",++windowdepth);
546                        _root.windows.confirm.init(275,80,new Array(iText('cancel'),iText('delete')),
547                                function(choice) {
548                                        if (choice==iText('delete')) { _root.ws.saveDeleteUndo(iText('deleting')); _root.ws.remove(); markClean(true); }
549                                });
550                        _root.windows.confirm.box.createTextField("prompt",2,7,9,250,100);
551                        writeText(_root.windows.confirm.box.prompt,iText('prompt_taggedpoints'));
552                }
553        };
554
555        // ---- Upload to server
556       
557        OSMWay.prototype.upload=function() {
558                putresponder=function() { };
559                putresponder.onResult=function(result) {
560                        _root.writesrequested--;
561                        var code=result.shift(); var msg=result.shift(); if (code) { _root.map.ways[result[0]].notUploading(); handleError(code,msg,result); return; }
562
563                        var i,r,z,nw,ow;
564                        ow=result[0];                   // old way ID
565                        nw=result[1];                   // new way ID
566                        if (ow!=nw) { renumberWay(ow,nw); }
567                        _root.map.ways[nw].clean=true;
568                        _root.map.ways[nw].uploading=false;
569                        _root.map.ways[nw].historic=false;
570                        _root.map.ways[nw].version=result[3];
571                       
572                        // renumber nodes (and any relations/ways they're in)
573                        z=result[2];
574                        for (var oid in z) {
575                                var nid = result[2][oid];
576                                nodes[oid].renumberTo(nid);
577                                nodes[nid].addWay(nw);
578                                renumberMemberOfRelation('Node', oid, nid);
579                        }
580                        for (var oid in z) { delete _root.nodes[oid]; } // delete -ve nodes
581                       
582                        // set versions, clean, not uploading
583                        z=result[4]; for (var nid in z) { nodes[nid].version=result[4][nid]; nodes[nid].uploading=false; nodes[nid].clean=true; }
584
585                        // remove deleted nodes
586                        z=result[5]; for (var nid in z) { c=_root.map.ways[nw]; delete c.deletednodes[nid]; delete _root.nodes[nid]; }
587                       
588                        _root.map.ways[nw].clearPOIs();
589                        uploadDirtyRelations();
590                        _root.map.ways[nw].deleteMergedWays();
591                        uploadDirtyWays();                      // make sure dependencies are uploaded
592                        operationDone(ow);
593                        freshenChangeset();
594                        updateInspector();
595                };
596
597                if (!this.uploading && !this.hasDependentNodes() && !this.locked && (!_root.sandbox || _root.uploading) && this.path.length>1) {
598                        this.deleteMergedWays();
599
600                        // Assemble list of changed nodes, and send
601                        if (renewChangeset()) { return; }
602                        this.uploading=true;
603                        var sendpath =new Array();
604                        var sendnodes=new Array();
605                        for (i=0; i<this.path.length; i++) {
606                                sendpath.push(this.path[i].id);
607                                if (!this.path[i].clean && !this.path[i].uploading) {
608                                        sendnodes.push(new Array(coord2long(this.path[i].x),
609                                                                                         coord2lat (this.path[i].y),
610                                                                                         this.path[i].id,this.path[i].version,
611                                                                                         deepCopy  (this.path[i].attr)));
612                                        this.path[i].uploading=true;
613                                }
614                        }
615                        delete this.attr['created_by'];
616                        _root.writesrequested++;
617                        remote_write.call('putway',putresponder,_root.usertoken,_root.changeset,this.version,Number(this._name),sendpath,this.attr,sendnodes,this.deletednodes);
618                        updateInspector();
619                } else { 
620                        operationDone(this._name);      // next please!
621                }
622        };
623
624        // ---- Delete any ways merged into this one
625
626        OSMWay.prototype.deleteMergedWays=function() {
627                while (this.mergedways.length>0) {
628                        var i=this.mergedways.shift();
629                        _root.map.ways.attachMovie("way",i[0],++waydepth);      // can't remove unless the movieclip exists!
630                        _root.map.ways[i[0]].version=i[1];                                      //  |
631                        _root.map.ways[i[0]].deletednodes=i[2];                         //  |
632                        _root.map.ways[i[0]].remove();
633                }
634        };
635
636        // ---- Revert to copy in database
637       
638        OSMWay.prototype.reload=function() {
639                _root.waysrequested+=1;
640                while (this.mergedways.length>0) {
641                        var i=this.mergedways.shift();
642                        _root.waysrequested+=1;
643                        _root.map.ways.attachMovie("way",i[0],++waydepth);
644                        _root.map.ways[i[0]].load();
645                }
646                this.checkconnections=true;
647                this.load();
648        };
649       
650        // ---- Save for undo
651
652        OSMWay.prototype.saveDeleteUndo=function(str) {
653                _root.undo.append(UndoStack.prototype.undo_deleteway,
654                                                  new Array(this._name,this._x,this._y,
655                                                                        deepCopy(this.attr),deepCopy(this.path),this.version),
656                                                                        iText('a_way',str));
657        };
658       
659        OSMWay.prototype.saveChangeUndo=function(str) {
660                _root.undo.append(UndoStack.prototype.undo_changeway,
661                                                  new Array(this._name,deepCopy(this.path),deepCopy(this.deletednodes),deepCopy(this.attr)),
662                                                  iText('action_changeway'));
663        };
664
665
666        // ---- Click handling 
667
668        OSMWay.prototype.onRollOver=function() {
669                if (this._name!=_root.wayselected && _root.drawpoint>-1 && !_root.map.anchorhints) {
670                        this.highlightPoints(5001,"anchorhint");
671                        setPointer('penplus');
672                } else if (_root.drawpoint>-1) { setPointer('penplus'); }
673                                                                  else { setPointer(''); }
674                if (this._name!=_root.wayselected) { var a=getName(this.attr,waynames); if (a) { setFloater(a); } }
675        };
676       
677        OSMWay.prototype.onRollOut=function() {
678                if (this.hitTest(_root._xmouse,_root._ymouse,true)) { return; } // false rollout
679                if (_root.wayselected) { setPointer(''   ); }
680                                                  else { setPointer('pen'); }
681                _root.map.anchorhints.removeMovieClip();
682                clearFloater();
683        };
684       
685        OSMWay.prototype.onPress=function() {
686                removeWelcome(true);
687                if (Key.isDown(Key.SHIFT) && this._name==_root.wayselected && _root.drawpoint==-1) {
688                        // shift-click current way: insert point
689                        this.insertAnchorPointAtMouse();
690                } else if (Key.isDown(Key.SHIFT) && _root.wayselected && this.name!=_root.wayselected && _root.drawpoint==-1 && _root.ws.hitTest(_root._xmouse,_root._ymouse,true)) {
691                        // shift-click other way (at intersection with current): make intersection
692                        this.insertAnchorPointAtMouse();
693                } else if (Key.isDown(Key.CONTROL) && _root.wayselected && this.name!=_root.wayselected && _root.drawpoint==-1) {
694                        // control/command-click other way: merge two ways
695                        mergeWayKeepingID(this,_root.ws);
696                } else if (_root.drawpoint>-1) {
697                        // click other way while drawing: insert point as junction
698                        if (!this.historic) {
699                                if (this._name==_root.wayselected && _root.drawpoint>0) {
700                                        _root.drawpoint+=1;     // inserting node earlier into the way currently being drawn
701                                }
702                                _root.newnodeid--;
703                                _root.nodes[newnodeid]=new Node(newnodeid,0,0,new Object(),0);
704                                this.insertAnchorPoint(_root.nodes[newnodeid]);
705                                this.highlightPoints(5001,"anchorhint");
706                                addEndPoint(_root.nodes[newnodeid]);
707                        }
708                        _root.junction=true;
709                        restartElastic();
710                } else {
711                        // click way: select
712                        _root.panel.properties.saveAttributes();
713                        this.select();
714                        clearTooltip();
715                        _root.clicktime=new Date();
716                        // was the click on a tagged node? if so, select directly
717                        var n;
718                        for (var i=0; i<this.path.length; i+=1) {
719                                if (this.taggednodes[i].hitTest(_root._xmouse,_root._ymouse,true)) { n=i; }
720                        }
721                        if (n) { _root.map.anchors[n].beginDrag();
722                                         _root.map.anchors[n].select(); }
723                          else { this.beginDrag(); }
724                }
725        };
726
727        OSMWay.prototype.beginDrag=function() {
728                this.onMouseMove=function() { this.trackDrag(); };
729                this.onMouseUp  =function() { this.endDrag();   };
730                this.dragged=false;
731                this.held=true;
732                _root.firstxmouse=_root.map._xmouse;
733                _root.firstymouse=_root.map._ymouse;
734        };
735
736        OSMWay.prototype.trackDrag=function() {
737                var t=new Date();
738                var longclick=(t.getTime()-_root.clicktime)>1000;
739                var xdist=Math.abs(_root.map._xmouse-_root.firstxmouse);
740                var ydist=Math.abs(_root.map._ymouse-_root.firstymouse);
741                // Don't enable drag unless way held for a while after click
742                if ((xdist>=tolerance   || ydist>=tolerance  ) &&
743                        (t.getTime()-_root.clicktime)<300 &&
744                        lastwayselected!=wayselected) { this.held=false; }
745                // Move way if dragged a long way, or dragged a short way after a while
746                if ((xdist>=tolerance*4 || ydist>=tolerance*4) ||
747                   ((xdist>=tolerance/4 || ydist>=tolerance/4) && longclick) &&
748                   this.held) {
749                        this.dragged=true;
750                }
751                if (this.dragged) {
752                        _root.map.anchors._x=_root.map.areas[this._name]._x=_root.map.highlight._x=this._x=_root.map._xmouse-_root.firstxmouse;
753                        _root.map.anchors._y=_root.map.areas[this._name]._y=_root.map.highlight._y=this._y=_root.map._ymouse-_root.firstymouse;
754                }
755        };
756       
757        OSMWay.prototype.endDrag=function() {
758                delete this.onMouseMove;
759                delete this.onMouseUp;
760                _root.map.anchors._x=_root.map.areas[this._name]._x=_root.map.highlight._x=this._x=0;
761                _root.map.anchors._y=_root.map.areas[this._name]._y=_root.map.highlight._y=this._y=0;
762                if (this.dragged) {
763                        this.moveNodes(_root.map._xmouse-_root.firstxmouse,_root.map._ymouse-_root.firstymouse);
764                        setAdvice(false,iText('advice_waydragged'));
765                        this.redraw();
766                        this.select();
767                        _root.undo.append(UndoStack.prototype.undo_movenodes,
768                                                          new Array(this,_root.map._xmouse-_root.firstxmouse,
769                                                                                         _root.map._ymouse-_root.firstymouse),
770                                                          iText('action_moveway'));
771                }
772        };
773       
774        // ---- Select/highlight
775       
776        OSMWay.prototype.select=function() {
777                _root.panel.properties.tidy();
778                if (_root.wayselected!=this._name || _root.poiselected!=0) { uploadSelected(); }
779//              _root.panel.properties.saveAttributes();
780                _root.pointselected=-2;
781                selectWay(this._name);
782                _root.poiselected=0;
783                this.highlightPoints(5000,"anchor");
784                removeMovieClip(_root.map.anchorhints);
785                this.highlight();
786                setTypeText(iText('way'),this._name);
787                removeIconPanel();
788                _root.panel.properties.init('way',getPanelColumns(),4);
789                _root.panel.presets.init(_root.panel.properties);
790                updateButtons();
791                updateScissors(false);
792        };
793       
794        OSMWay.prototype.highlight=function() {
795                _root.map.createEmptyMovieClip("highlight",5);
796                if (_root.pointselected>-2) {
797                        highlightSquare(_root.map.anchors[pointselected]._x,_root.map.anchors[pointselected]._y, _root.anchorsize*0.05);
798                } else {
799                        var linecolour=0xFFFF00; if (this.locked) { var linecolour=0x00FFFF; }
800                        _root.map.highlight.lineStyle(linewidth*1.5+8,linecolour,80,false,"none");
801                        _root.map.highlight.moveTo(this.path[0].x,this.path[0].y);
802                        for (var i=1; i<this.path.length; i+=1) {
803                                _root.map.highlight.lineTo(this.path[i].x,this.path[i].y);
804                        }
805                }
806                this.direction();
807        };
808
809        OSMWay.prototype.highlightPoints=function(d,atype) {
810                var group=atype+"s";
811                _root.map.createEmptyMovieClip(group,d);
812                for (var i=0; i<this.path.length; i+=1) {
813                        var asprite=atype; if (this.path[i].numberOfWays()>1) { asprite+="_junction"; }
814                        _root.map[group].attachMovie(asprite,i,i);
815                        _root.map[group][i]._x=this.path[i].x;
816                        _root.map[group][i]._y=this.path[i].y;
817                        _root.map[group][i]._xscale=_root.anchorsize;
818                        _root.map[group][i]._yscale=_root.anchorsize;
819                        _root.map[group][i].node=this.path[i].id;
820                        _root.map[group][i].way=this;
821                        if (this.path[i].tagged) {
822                                // anchor point should be black if it has tags
823                                _root.map[group][i].blacken=new Color(_root.map[group][i]);
824                                _root.map[group][i].blacken.setTransform(to_black);
825                        }
826                }
827        };
828
829        // ---- Split, merge, reverse
830
831        OSMWay.prototype.splitWay=function(point,newattr) {
832                var i,z;
833                if (point>0 && point<(this.path.length-1) && !this.historic) {
834                        _root.newwayid--;                                                                                       // create new way
835                        _root.map.ways.attachMovie("way",newwayid,++waydepth);          //  |
836                        _root.map.ways[newwayid].path=shallowCopy(this.path);           // deep copy path array
837                        this.removeNodeIndex();
838
839                        if (newattr) { _root.map.ways[newwayid].attr=newattr; }
840                                        else { _root.map.ways[newwayid].attr=deepCopy(this.attr); }
841
842                        z=_root.wayrels[this._name];                                                            // copy relations
843                        for (i in z) {                                                                                          //  |
844                                _root.map.relations[i].setWayRole(newwayid,z[i]);               //  |
845                        }                                                                                                                       //  |
846 
847                        this.path.splice(Math.floor(point)+1);                                          // current way
848                        this.redraw();                                                                                          //  |
849                        this.createNodeIndex();
850
851                        _root.map.ways[newwayid].path.splice(0,point);                          // new way
852                        _root.map.ways[newwayid].locked=this.locked;                            //  |
853                        _root.map.ways[newwayid].redraw();                                                      //  |
854                        _root.map.ways[newwayid].clean=false;                                           //  | - in case it doesn't upload
855                        _root.map.ways[newwayid].upload();                                                      //  |
856                        _root.map.ways[newwayid].createNodeIndex();                                     //  |
857
858                        this.clean=false;                                                                                       // upload current way
859                        this.upload();                                                                                          //  |
860                        this.select();                                                                                          //  |
861                        uploadDirtyRelations();
862                        _root.undo.append(UndoStack.prototype.undo_splitway,
863                                                          new Array(this,_root.map.ways[newwayid]),
864                                                          iText('action_splitway'));
865                };
866        };
867
868        //              Merge (start/end of this way,other way object,start/end of other way)
869
870        OSMWay.prototype.mergeWay=function(topos,otherway,frompos) {
871                var i,z;
872                var conflict=false;
873                if (this.historic || otherway.historic) { return; }
874
875                var mergepoint=this.path.length;
876                if (topos==0) {
877                        _root.undo.append(UndoStack.prototype.undo_mergeways,
878                                                          new Array(this,deepCopy(otherway.attr),deepCopy(this.attr),frompos),
879                                                          iText('action_mergeways'));
880                } else {
881                        _root.undo.append(UndoStack.prototype.undo_mergeways,
882                                                          new Array(this,deepCopy(this.attr),deepCopy(otherway.attr),topos),
883                                                          iText('action_mergeways'));
884                }
885                if (frompos==0) { for (i=0; i<otherway.path.length;    i+=1) { this.addPointFrom(topos,otherway,i); } }
886                                   else { for (i=otherway.path.length-1; i>=0; i-=1) { this.addPointFrom(topos,otherway,i); } }
887
888                // Merge attributes
889                z=otherway.attr;
890                for (i in z) {
891                        if (otherway.attr[i].substr(0,6)=='(type ') { otherway.attr[i]=null; }
892                        if (this.attr[i].substr(0,6)=='(type ') { this.attr[i]=null; }
893                        if (this.attr[i]) {
894                                if (this.attr[i]!=otherway.attr[i] && otherway.attr[i]) { var s=this.attr[i]+'; '+otherway.attr[i]; this.attr[i]=s.substr(0,255); conflict=true; }
895                        } else {
896                                this.attr[i]=otherway.attr[i];
897                        }
898                        if (!this.attr[i]) { delete this.attr[i]; }
899                }
900
901                // Merge relations
902                z=_root.wayrels[otherway._name];                                                // copy relations
903                for (i in z) {                                                                                  //  |
904                        if (!_root.wayrels[this._name][i]) {                            //  |
905                                _root.map.relations[i].setWayRole(this._name,z[i]);     
906                        }                                                                                                       //  |
907                }                                                                                                               //  |
908                memberDeleted('Way', otherway._name);                                   // then remove old way from them
909                z=otherway.deletednodes;                                                                //  | and its deletednodes
910                for (i in z) { memberDeleted('Node',z[i]); }                    //  |
911
912                // Add to list of merged ways (so they can be deleted on next putway)
913        if (_root.sandbox) {
914            otherway.remove();
915        } else {
916                this.mergedways.push(new Array(otherway._name,otherway.version,otherway.deletednodes));
917                this.mergedways.concat(otherway.mergedways);
918        }
919                this.clean=false;
920                markClean(false);
921                if (otherway.locked) { this.locked=true; }
922                otherway.removeNodeIndex();
923                removeMovieClip(_root.map.areas[otherway._name]);
924                removeMovieClip(otherway);
925                if (this._name==_root.wayselected) { 
926                        _root.panel.properties.reinit();
927                }
928                if (conflict) { setAdvice(false,iText('advice_tagconflict')); }
929        };
930
931        OSMWay.prototype.addPointFrom=function(topos,otherway,srcpt) {
932                if (topos==0) { if (this.path[0                                 ]==otherway.path[srcpt]) { return; } }  // don't add duplicate points
933                                 else { if (this.path[this.path.length-1]==otherway.path[srcpt]) { return; } }  //  |
934                if (topos==0) { this.path.unshift(otherway.path[srcpt]); }
935                             else { this.path.push(otherway.path[srcpt]); }
936                otherway.path[srcpt].addWay(this._name);
937        };
938
939        OSMWay.prototype.mergeAtCommonPoint=function(sel) {
940                var selstart =sel.path[0];
941                var sellen   =sel.path.length-1;
942                var selend   =sel.path[sellen];
943                var thisstart=this.path[0];
944                var thislen  =this.path.length-1;
945                var thisend  =this.path[thislen];
946                if      (selstart==thisstart) { sel.mergeWay(0,this,0);                    return true; }
947                else if (selstart==thisend  ) { sel.mergeWay(0,this,thislen);      return true; }
948                else if (selend  ==thisstart) { sel.mergeWay(sellen,this,0);       return true; }
949                else if (selend  ==thisend  ) { sel.mergeWay(sellen,this,thislen); return true; }
950                else                                              { setAdvice(true,iText('advice_nocommonpoint')); return false; }
951        };
952
953        // ---- Reverse order
954       
955        OSMWay.prototype.reverseWay=function() {
956                if (this.path.length<2) { return; }
957                if (_root.drawpoint>-1) { _root.drawpoint=(this.path.length-1)-_root.drawpoint; }
958                this.path.reverse();
959                this.redraw();
960                this.direction();
961                this.select();
962                this.clean=false;
963                markClean(false);
964                _root.undo.append(UndoStack.prototype.undo_reverse,new Array(this),iText('action_reverseway'));
965        };
966
967        // ---- Move all nodes within a way
968       
969        OSMWay.prototype.moveNodes=function(xdiff,ydiff) {
970                var i,n;
971                var movedalready=new Array();
972                this.clean=false;
973                markClean(false);
974                for (i=0; i<this.path.length; i+=1) {
975                        n=this.path[i].id;
976                        if (movedalready[n]) {
977                        } else {
978                                this.path[i].moveTo(this.path[i].x+xdiff,
979                                                                        this.path[i].y+ydiff,
980                                                                        this._name);
981                                movedalready[n]=true;
982                        }
983                }
984        };
985
986        // ---- Add node to deleted list
987        //              (should have been removed from way first)
988       
989        OSMWay.prototype.markAsDeleted=function(rnode,check2) {
990                var d=true;
991                // If we're just removing one point, check it's not used elsewhere in the way before removing from .ways
992                if (check2) {
993                        var z=this.path; 
994                        for (var i in z) { if (this.path[i].id==rnode.id) { d=false; } }
995                }
996                if (d) { rnode.removeWay(this._name); }
997                if (rnode.numberOfWays()==0 && rnode.id>0) { this.deletednodes[rnode.id]=rnode.version; }
998        };
999
1000
1001        // ---- Check for duplicates (e.g. when C is removed from ABCB)
1002       
1003        OSMWay.prototype.removeDuplicates=function() {
1004                var z=this.path; var ch=false;
1005                for (var i in z) {
1006                        if (i>0) {
1007                                if (this.path[i]==this.path[i-1]) { this.path.splice(i,1); ch=true; }
1008                        }
1009                }
1010                return ch;
1011        };
1012
1013        // ---- Add point into way with SHIFT-clicking
1014        //              cf http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/source.vba
1015        //              for algorithm to find nearest point on a line
1016       
1017        OSMWay.prototype.insertAnchorPoint=function(nodeobj) {
1018                var nx,ny,u,closest,closei,i,a,b,direct,via,newpoint;
1019                nx=_root.map._xmouse;   // where we're inserting it
1020                ny=_root.map._ymouse;   //      |
1021                closest=0.1; closei=0;
1022                for (i=0; i<(this.path.length)-1; i+=1) {
1023                        a=this.path[i  ];
1024                        b=this.path[i+1];
1025                        direct=Math.sqrt((b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y));
1026                        via   =Math.sqrt((nx -a.x)*(nx -a.x)+(ny -a.y)*(ny -a.y));
1027                        via  +=Math.sqrt((nx -b.x)*(nx -b.x)+(ny -b.y)*(ny -b.y));
1028                        if (Math.abs(via/direct-1)<closest) {
1029                                closei=i+1;
1030                                closest=Math.abs(via/direct-1);
1031                                u=((nx-a.x)*(b.x-a.x)+
1032                                   (ny-a.y)*(b.y-a.y))/
1033                                   (Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2));
1034                                nodeobj.x=a.x+u*(b.x-a.x);
1035                                nodeobj.y=a.y+u*(b.y-a.y);
1036                        }
1037                }
1038                // Insert
1039                nodeobj.addWay(this._name);
1040                this.path.splice(closei,0,nodeobj);
1041                this.clean=false;
1042                this.redraw();
1043                markClean(false);
1044//              _root.adjustedxmouse=tx;        // in case we're adding extra points...
1045//              _root.adjustedymouse=ty;        // should probably return an array or an object **
1046                return closei;
1047        };
1048
1049        //              Wrapper around the above to:
1050        //              - insert at mouse position
1051        //              - add to undo stack
1052        //              - add intersection at any crossing ways
1053
1054        OSMWay.prototype.insertAnchorPointAtMouse=function() {
1055                _root.newnodeid--;
1056                _root.nodes[newnodeid]=new Node(newnodeid,0,0,new Object(),0);
1057                _root.pointselected=this.insertAnchorPoint(_root.nodes[newnodeid]);
1058                var waylist=new Array(); waylist.push(this);
1059                var poslist=new Array(); poslist.push(_root.pointselected);
1060                for (qway in _root.map.ways) {
1061                        if (_root.map.ways[qway].hitTest(_root._xmouse,_root._ymouse,true) && qway!=this._name) {
1062                                poslist.push(_root.map.ways[qway].insertAnchorPoint(_root.nodes[newnodeid]));
1063                                waylist.push(_root.map.ways[qway]);
1064                        }
1065                }
1066                _root.undo.append(UndoStack.prototype.undo_addpoint,
1067                                                  new Array(waylist,poslist), iText('action_insertnode'));
1068                _root.ws.highlightPoints(5000,"anchor");
1069                _root.map.anchors[pointselected].beginDrag();
1070                updateInspector();
1071        };
1072
1073        // ---- Remove point from this way (only)
1074       
1075        OSMWay.prototype.removeAnchorPoint=function(point) {
1076                // ** if length<2, then mark as way removal
1077                _root.undo.append(UndoStack.prototype.undo_deletepoint,
1078                                                  new Array(deepCopy(this.path[point]),
1079                                                                        new Array(this._name),
1080                                                                        new Array(point)),
1081                                                  iText('action_deletepoint'));
1082                var rnode=this.path[point];
1083                this.path.splice(point,1);
1084                this.removeDuplicates();
1085                this.markAsDeleted(rnode,true);
1086                if (rnode.numberOfWays()==0) { memberDeleted('Node', rnode.id); }
1087                if (this.path.length<2) { this.remove(); }
1088                                                   else { this.redraw(); this.clean=false; }
1089        };
1090
1091        // ---- Bounding box utility functions
1092
1093        OSMWay.prototype.resetBBox=function() {
1094                this.xmin=this.ymin= 999;
1095                this.xmax=this.ymax=-999;
1096        };
1097       
1098        OSMWay.prototype.updateBBox=function(long,lat) {
1099                this.xmin=Math.min(long,this.xmin);
1100                this.xmax=Math.max(long,this.xmax);
1101                this.ymin=Math.min(lat ,this.ymin);
1102                this.ymax=Math.max(lat ,this.ymax);
1103        };
1104
1105        // ---- Reset 'uploading' flag
1106
1107        OSMWay.prototype.notUploading=function() {
1108                this.uploading=false;
1109                var z=this.path; for (i in z) { this.path[i].uploading=false; }
1110                updateInspector();
1111        };
1112
1113        // ---- Node->way associations
1114       
1115        OSMWay.prototype.createNodeIndex  =function() { var z=this.path; for (var i in z) { this.path[i].addWay(this._name);    } };
1116        OSMWay.prototype.removeNodeIndex  =function() { var z=this.path; for (var i in z) { this.path[i].removeWay(this._name); } };
1117        OSMWay.prototype.renumberNodeIndex=function(n) {
1118                var z=this.path; for (i in z) { 
1119                        this.path[i].removeWay(this._name);
1120                        this.path[i].addWay(n);
1121                }
1122        };
1123        OSMWay.prototype.hasDependentNodes=function() {
1124        if (_root.sandbox && _root.uploading) { return false; }    // not an issue in consecutive uploads
1125                var d=false;
1126                var z=this.path; for (var i in z) {
1127                        if (this.path[i].id<0) {
1128                                var ways=this.path[i].ways; for (var w in ways) {
1129                                        if (_root.map.ways[w].uploading) { d=true; }
1130                                }
1131                        }
1132                }
1133                return d;
1134        };
1135
1136        // =====================================================================================
1137        // Offset path
1138        // ** much of the dialogue box could be refactored to share with relations dialogue
1139
1140        function askOffset() {
1141                if (!_root.wayselected) { return; }
1142                _root.windows.attachMovie("modal","offset",++windowdepth);
1143                _root.windows.offset.init(300, 170, [iText('cancel'), iText('ok')], completeOffset);
1144                var z = 5;
1145                var box = _root.windows.offset.box;
1146               
1147                box.createTextField("title",z++,7,7,300-14,20);
1148                box.title.text = iText('prompt_createparallel');
1149                with (box.title) {
1150                        wordWrap=true;
1151                        setTextFormat(boldText);
1152                        selectable=false; type='dynamic';
1153                }
1154                adjustTextField(box.title);
1155               
1156                box.createTextField("instr",z++,7,30,300-14,40);
1157
1158                // Create radio buttons and menu
1159
1160                box.attachMovie("radio","offsetoption",z++);
1161                box.offsetoption.addButton(10,35 ,iText('offset_dual'));
1162                box.offsetoption.addButton(10,55 ,iText('offset_motorway'));
1163                box.offsetoption.addButton(10,75 ,iText('offset_narrowcanal'));
1164                box.offsetoption.addButton(10,95 ,iText('offset_broadcanal'));
1165                box.offsetoption.addButton(10,115,iText('offset_choose'));
1166                box.offsetoption.select(1);
1167
1168                var w=box.offsetoption[5].prompt._width+25;
1169                box.createTextField("useroffset",z++,w,110,290-w,17);
1170                box.useroffset.setNewTextFormat(plainSmall);
1171                box.useroffset.type='input';
1172                box.useroffset.backgroundColor=0xDDDDDD;
1173                box.useroffset.background=true;
1174                box.useroffset.border=true;
1175                box.useroffset.borderColor=0xFFFFFF;
1176                box.useroffset.onSetFocus=function() { this._parent.offsetoption.select(5); };
1177        }
1178
1179        // typical widths:
1180        // central reservation:
1181        //       4.5m on a rural motorway/dual carriageway
1182        //       3.5m on an urban motorway
1183        //       1.8m on an urban dual carriageway
1184        // lane widths are typically always 3.65m
1185        // hard shoulders are typically 3.30m
1186        // hard strips are typically 1m
1187
1188        function completeOffset(button) {
1189                if (button!=iText('ok')) { return false; }
1190                var radio=_root.windows.offset.box.offsetoption.selected;
1191                var m;
1192                if (radio==5) {
1193                        m=_root.windows.offset.box.useroffset.text;
1194                        if (!button) { return false; }
1195                } else {
1196                        m=new Array(0,4.5+3.65*2,4.5+3.65*3,5.5,11);
1197                        m=m[radio];
1198                }
1199                var thislat=coord2lat(_root.map._y);                    // near as dammit
1200                var latfactor=Math.cos(thislat/(180/Math.PI));  // 111200m in a degree at the equator
1201                m=masterscale/(111200*latfactor)*m;                             // 111200m*cos(lat in radians) elsewhere
1202                _root.ws.offset( m);
1203                _root.ws.offset(-m);
1204                _root.undo.append(UndoStack.prototype.undo_makeways,
1205                                                  new Array(_root.newwayid,_root.newwayid+1),
1206                                                  iText('action_createparallel'));
1207        }
1208
1209        // Create (locked) offset way
1210        // offset is + or - depending on which side
1211
1212        OSMWay.prototype.offset=function(tpoffset) {
1213                var a,b,o,df,x,y;
1214                var offsetx=new Array();
1215                var offsety=new Array();
1216                var wm=1;       // was 10 before
1217
1218                _root.newwayid--;                                                                                       // create new way
1219                _root.map.ways.attachMovie("way",newwayid,++waydepth);          //  |
1220                var nw=_root.map.ways[newwayid];
1221                nw.locked=true;
1222                nw.clean=false;
1223
1224                // Normalise, and calculate offset vectors
1225
1226                for (var i=0; i<this.path.length; i++) {
1227                        a=this.path[i  ].y - this.path[i+1].y;
1228                        b=this.path[i+1].x - this.path[i  ].x;
1229                        h=Math.sqrt(a*a+b*b);
1230                        if (h!=0) { a=a/h; b=b/h; }
1231                                 else { a=0; b=0; }
1232                        offsetx[i]=wm*a;
1233                        offsety[i]=wm*b;
1234                }
1235
1236                _root.newnodeid--;
1237                _root.nodes[newnodeid]=new Node(newnodeid,this.path[0].x+tpoffset*offsetx[0],
1238                                                                                                  this.path[0].y+tpoffset*offsety[0],
1239                                                                                                  new Object(),0);
1240                _root.nodes[newnodeid].addWay(newwayid);
1241                nw.path.push(_root.nodes[newnodeid]);
1242               
1243                for (i=1; i<(this.path.length-1); i++) {
1244       
1245                        a=det(offsetx[i]-offsetx[i-1],
1246                                  offsety[i]-offsety[i-1],
1247                                  this.path[i+1].x - this.path[i  ].x,
1248                                  this.path[i+1].y - this.path[i  ].y);
1249                        b=det(this.path[i  ].x - this.path[i-1].x,
1250                                  this.path[i  ].y - this.path[i-1].y,
1251                                  this.path[i+1].x - this.path[i  ].x,
1252                                  this.path[i+1].y - this.path[i  ].y);
1253                        if (b!=0) { df=a/b; } else { df=0; }
1254                       
1255                        x=this.path[i].x + tpoffset*(offsetx[i-1]+df*(this.path[i].x - this.path[i-1].x));
1256                        y=this.path[i].y + tpoffset*(offsety[i-1]+df*(this.path[i].y - this.path[i-1].y));
1257       
1258                        _root.newnodeid--;
1259                        _root.nodes[newnodeid]=new Node(newnodeid,x,y,new Object(),0);
1260                        _root.nodes[newnodeid].addWay(newwayid);
1261                        nw.path.push(_root.nodes[newnodeid]);
1262                }
1263       
1264                _root.newnodeid--;
1265                _root.nodes[newnodeid]=new Node(newnodeid,this.path[i].x+tpoffset*offsetx[i-1],
1266                                                                                                  this.path[i].y+tpoffset*offsety[i-1],new Object(),0);
1267                _root.nodes[newnodeid].addWay(newwayid);
1268                nw.path.push(_root.nodes[newnodeid]);
1269                nw.redraw();
1270        };
1271
1272    function det(a,b,c,d) { return a*d-b*c; }
1273
1274        // =====================================================================================
1275        // Inspector
1276       
1277        OSMWay.prototype.inspect=function() {
1278                var str='';
1279
1280                // Status
1281                if (this.locked) { str+=iText('inspector_way_locked') + "\n"; }
1282                if (!this.clean) { str+=iText('inspector_unsaved'); }
1283                if (this.uploading) { str+=' ' + iText('inspector_uploading'); }
1284                if (!this.clean || this.uploading) { str+="\n"; }
1285
1286                // Number of nodes
1287                if (this.path[this.path.length-1]==this.path[0]) {
1288            str += iText('inspector_way_nodes_closed', this.path.length);
1289        } else {
1290            str += iText('inspector_way_nodes', this.path.length);
1291        }
1292                str+="\n";
1293
1294                // Connections to other ways of same type
1295                var principal='';
1296                if      (this.attr['highway' ]) { principal='highway';  }
1297                else if (this.attr['waterway']) { principal='waterway'; }
1298                else if (this.attr['railway' ]) { principal='railway';  }
1299                var same=0; var different=0;
1300                var z=this.path; for (i in z) {
1301                        var w=z[i].ways; for (id in w) {
1302                                if (id!=this._name) {
1303                                        if (_root.map.ways[id].attr[principal]==this.attr[principal]) { same++; }
1304                                        else if (_root.map.ways[id].attr[principal]) { different++; }
1305                                }
1306                        }
1307                }
1308
1309                if (this.attr[principal]==undefined) { 
1310            str += iText('inspector_way_connects_to', same);
1311                } else {
1312            str += iText('inspector_way_connects_to_principal', same, this.attr[principal], different, principal);
1313                }
1314
1315                return "<p>"+str+"</p>";
1316        };
1317
1318
1319
1320        Object.registerClass("way",OSMWay);
1321
1322
1323        // =====================================================================================
1324        // Drawing support functions
1325
1326        // removeNodeFromWays - now see Node.removeFromAllWays
1327
1328        // startNewWay            - create new way from first node
1329
1330        function startNewWay(node) {
1331                uploadSelected();
1332                _root.newwayid--;
1333                selectWay(newwayid);
1334                _root.poiselected=0;
1335                _root.map.ways.attachMovie("way",newwayid,++waydepth);
1336                _root.map.ways[newwayid].path[0]=_root.nodes[node];
1337                _root.map.ways[newwayid].redraw();
1338                _root.map.ways[newwayid].select();
1339                _root.map.ways[newwayid].clean=false;
1340                _root.nodes[node].addWay(newwayid);
1341                _root.map.anchors[0].startElastic();
1342                _root.drawpoint=0;
1343                markClean(false);
1344                setTooltip(iText('hint_drawmode'),0);
1345        }
1346
1347        // addEndPoint(node object) - add point to start/end of line
1348
1349        function addEndPoint(node) {
1350                var drawnode=_root.ws.path[_root.drawpoint];
1351                if (_root.drawpoint==_root.ws.path.length-1) {
1352                        _root.ws.path.push(node);
1353                        _root.drawpoint=_root.ws.path.length-1;
1354                } else {
1355                        _root.ws.path.unshift(node);    // drawpoint=0, add to start
1356                }
1357                node.addWay(_root.wayselected);
1358       
1359                // Redraw line (if possible, just extend it to save time)
1360                if (_root.ws.getFill()>-1 || 
1361                        _root.ws.path.length<3 ||
1362                        _root.pointselected>-2 ||
1363                        _root.ws.attr['oneway']) {
1364                        _root.ws.redraw();
1365                        _root.ws.select();
1366                } else {
1367                        _root.ws.line.moveTo(drawnode.x,drawnode.y);
1368                        _root.ws.line.lineTo(node.x,node.y);
1369                        if (casing[_root.ws.attr['highway']]) {
1370                                _root.map.areas[wayselected].moveTo(drawnode.x,drawnode.y);
1371                                _root.map.areas[wayselected].lineTo(node.x,node.y);
1372                        }
1373                        _root.map.highlight.moveTo(drawnode.x,drawnode.y);
1374                        _root.map.highlight.lineTo(node.x,node.y);
1375                        _root.ws.direction();
1376                        _root.ws.highlightPoints(5000,"anchor");
1377                        removeMovieClip(_root.map.anchorhints);
1378                        var z=_root.wayrels[_root.wayselected];
1379                        for (var rel in z) { _root.map.relations[rel].redraw(); }
1380                }
1381                // Mark as unclean
1382                _root.ws.clean=false;
1383                markClean(false);
1384                _root.map.elastic.clear();
1385                var poslist=new Array(); poslist.push(_root.drawpoint);
1386                _root.undo.append(UndoStack.prototype.undo_addpoint,
1387                                                  new Array(new Array(_root.ws),poslist),
1388                                                  iText('action_addpoint'));
1389                updateInspector();
1390        }
1391
1392        function stopDrawing() {
1393                _root.map.anchors[_root.drawpoint].endElastic();
1394                _root.drawpoint=-1;
1395                if (_root.ws.path.length<=1) { 
1396                        // way not long enough, so abort
1397                        _root.ws.removeNodeIndex();
1398                        removeMovieClip(_root.map.areas[wayselected]);
1399                        removeMovieClip(_root.ws);
1400                        removeMovieClip(_root.map.anchors);
1401                        updateInspector();
1402                }
1403                _root.map.elastic.clear();
1404                clearTooltip();
1405        };
1406
1407        // cycleStacked - cycle through ways sharing same point
1408
1409        function cycleStacked() {
1410                if (_root.pointselected>-2) {
1411                        stopDrawing();
1412                        var id=_root.ws.path[_root.pointselected].id;
1413                        var firstfound=0; var nextfound=0;
1414                        for (qway in _root.map.ways) {
1415                                if (qway!=_root.wayselected) {
1416                                        for (qs=0; qs<_root.map.ways[qway].path.length; qs+=1) {
1417                                                if (_root.map.ways[qway].path[qs].id==id) {
1418                                                        var qw=Math.floor(qway);
1419                                                        if (firstfound==0 || qw<firstfound) { firstfound=qw; }
1420                                                        if ((nextfound==0 || qw<nextfound) && qw>wayselected) { nextfound=qw; }
1421                                                }
1422                                        }
1423                                }
1424                        }
1425                        if (firstfound) {
1426                                if (nextfound==0) { var nextfound=firstfound; }
1427                                _root.map.ways[nextfound].swapDepths(_root.ws);
1428                                _root.map.ways[nextfound].select();
1429                        }
1430                }
1431        };
1432
1433        // ================================================================
1434        // Way communication
1435       
1436        // whichWays    - get list of ways from remoting server
1437
1438        function whichWays(force) {
1439                _root.lastwhichways=new Date();
1440                if (_root.waycount>500) { purgeWays(); }
1441                if (_root.poicount>500) { purgePOIs(); }
1442                if (_root.edge_l>=_root.bigedge_l &&
1443                        _root.edge_r<=_root.bigedge_r &&
1444                        _root.edge_b>=_root.bigedge_b &&
1445                        _root.edge_t<=_root.bigedge_t && (!force)) {
1446                        // we have already loaded this area, so ignore
1447                } else {
1448                        whichresponder=function() {};
1449                        whichresponder.onResult=function(result) {
1450                                _root.whichreceived+=1;
1451                                var code=result.shift(); var msg=result.shift(); if (code) { handleError(code,msg,result); return; }
1452                                var waylist  =result[0];
1453                                var pointlist=result[1];
1454                                var relationlist=result[2];
1455                                var s;
1456
1457                                for (i in waylist) {                                                                            // ways
1458                                        way=waylist[i][0];                                                                              //  |
1459                                        if ((!_root.map.ways[way] || _root.map.ways[way].version!=waylist[i][1]) && !_root.waystodelete[way]) {
1460                                                _root.map.ways.attachMovie("way",way,++waydepth);       //  |
1461                                                _root.map.ways[way].load();                                                     //  |
1462                                                _root.waycount+=1;                                                                      //  |
1463                                                _root.waysrequested+=1;                                                         //  |
1464                                        }
1465                                }
1466                               
1467                                for (i in pointlist) {                                                                          // POIs
1468                                        point=pointlist[i][0];                                                                  //  |
1469                                        if ((!_root.map.pois[point] || _root.map.pois[point].version!=pointlist[i][4]) && !_root.poistodelete[point]) {
1470                                                var a=getPOIIcon(pointlist[i][3]);                                      //  |
1471                                                if (a=='poi') { s=poiscale; } else { s=iconscale; }     //  |
1472                                                _root.map.pois.attachMovie(a,point,++poidepth); //  |
1473                                                _root.map.pois[point]._x=long2coord(pointlist[i][1]);// |
1474                                                _root.map.pois[point]._y=lat2coord (pointlist[i][2]);// |
1475                                                _root.map.pois[point]._xscale=_root.map.pois[point]._yscale=s;
1476                                                _root.map.pois[point].version=pointlist[i][4];          //  |
1477                                                _root.map.pois[point].attr=pointlist[i][3];                     //  |
1478                                                _root.map.pois[point].icon=a;                                           //  |
1479                                                _root.poicount+=1;                                                                      //  |
1480                                                if (point==prenode) { deselectAll(); prenode=undefined;
1481                                                                                          _root.map.pois[point].select(); }
1482                                        }
1483                                }
1484
1485                                for (i in relationlist) {
1486                                        rel = relationlist[i][0];
1487                    if (!_root.map.relations[rel] || _root.map.relations[rel].version!=relationlist[i][1]) {
1488                                                _root.map.relations.attachMovie("relation",rel,++reldepth);
1489                                                _root.map.relations[rel].load();
1490                                                _root.relcount+=1;
1491                                                _root.relsrequested+=1;
1492                                        }
1493                }
1494                        };
1495                        remote_read.call('whichways',whichresponder,_root.edge_l,_root.edge_b,_root.edge_r,_root.edge_t);
1496                        _root.bigedge_l=_root.edge_l; _root.bigedge_r=_root.edge_r;
1497                        _root.bigedge_b=_root.edge_b; _root.bigedge_t=_root.edge_t;
1498                        _root.whichrequested+=1;
1499                }
1500        }
1501
1502        // purgeWays - remove any clean ways outside current view
1503       
1504        function purgeWays() {
1505                for (qway in _root.map.ways) {
1506                        if (qway==_root.wayselected) {
1507                        } else if (!_root.map.ways[qway].clean) {
1508                                _root.map.ways[qway].upload();
1509                                uploadDirtyRelations();
1510                        } else if (((_root.map.ways[qway].xmin<edge_l && _root.map.ways[qway].xmax<edge_l) ||
1511                                                (_root.map.ways[qway].xmin>edge_r && _root.map.ways[qway].xmax>edge_r) ||
1512                                            (_root.map.ways[qway].ymin<edge_b && _root.map.ways[qway].ymax<edge_b) ||
1513                                                (_root.map.ways[qway].ymin>edge_t && _root.map.ways[qway].ymax>edge_t))) {
1514                                removeMovieClip(_root.map.ways[qway]);
1515                                removeMovieClip(_root.map.areas[qway]);
1516                                _root.waycount-=1;
1517                        }
1518                }
1519                _root.bigedge_l=_root.edge_l; _root.bigedge_r=_root.edge_r;
1520                _root.bigedge_b=_root.edge_b; _root.bigedge_t=_root.edge_t;
1521        }
1522
1523        function selectWay(id) {
1524                _root.lastwayselected=_root.wayselected;
1525                _root.wayselected=Math.floor(id);
1526                _root.ws=_root.map.ways[id];
1527               
1528                if (id==0) {
1529                        _root.panel.advanced.disableOption(0);
1530                        _root.panel.advanced.disableOption(1);
1531                } else {
1532                        _root.panel.advanced.enableOption(0);
1533                        _root.panel.advanced.enableOption(1);
1534                }
1535                updateInspector();
1536        }
1537       
1538        function renumberWay(ow,nw) {
1539                _root.map.ways[ow].renumberNodeIndex(nw);
1540                wayrels[nw]=wayrels[ow]; delete wayrels[ow];
1541                _root.map.ways[ow]._name=nw;
1542                renumberMemberOfRelation('Way', ow, nw);
1543                if (_root.map.areas[ow]) { _root.map.areas[ow]._name=nw; }
1544                if (_root.panel.t_details.text==ow) { _root.panel.t_details.text=nw; _root.panel.t_details.setTextFormat(plainText); }
1545                if (wayselected==ow) { selectWay(nw); }
1546        }
1547       
1548        function mergeWayKeepingID(w1,w2) {
1549                var t=(w1==_root.ws || w2==_root.ws);
1550                var w,s;
1551                if (Number(w1._name)<0 && Number(w2._name)>0) {
1552                        s=w1.mergeAtCommonPoint(w2); w=w2;
1553                } else {
1554                        s=w2.mergeAtCommonPoint(w1); w=w1;
1555                }
1556                if (s) { w.redraw(); if (t) { w.select(); } }
1557        }
1558
1559        function uploadDirtyWays(allow_ws) {
1560                if (_root.sandbox) { return; }
1561                var z=_root.map.ways;
1562                for (i in z) {
1563                        if (!_root.map.ways[i].clean && (i!=wayselected || allow_ws) && !_root.map.ways[i].hasDependentNodes()) { 
1564                                _root.map.ways[i].upload();
1565                        }
1566                }
1567        };
1568
Note: See TracBrowser for help on using the repository browser.