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

Last change on this file since 18704 was 18686, checked in by richard, 10 years ago

move shift-click>command-click

File size: 54.3 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,8/Math.pow(2,Math.min(_root.scale,17)-13));
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 enlarge=1;
811                var anchorsize=120/Math.pow(2,_root.scale-13);
812                if (preferences.data.thinlines) { enlarge=0.5; }
813                if (_root.scale>15) {
814                        switch (_root.scale) {
815                                case 15: anchorsize+=10*enlarge; break;
816                                case 16: anchorsize+=7 *enlarge; break;
817                                case 17: anchorsize+=5 *enlarge; break;
818                                case 18: anchorsize+=6 *enlarge; break;
819                                case 19: anchorsize+=6 *enlarge; break;
820                        }
821                }
822                var group=atype+"s";
823                _root.map.createEmptyMovieClip(group,d);
824                for (var i=0; i<this.path.length; i+=1) {
825                        var asprite=atype; if (this.path[i].numberOfWays()>1) { asprite+="_junction"; }
826                        _root.map[group].attachMovie(asprite,i,i);
827                        _root.map[group][i]._x=this.path[i].x;
828                        _root.map[group][i]._y=this.path[i].y;
829                        _root.map[group][i]._xscale=anchorsize;
830                        _root.map[group][i]._yscale=anchorsize;
831                        _root.map[group][i].node=this.path[i].id;
832                        _root.map[group][i].way=this;
833                        if (this.path[i].tagged) {
834                                // anchor point should be black if it has tags
835                                _root.map[group][i].blacken=new Color(_root.map[group][i]);
836                                _root.map[group][i].blacken.setTransform(to_black);
837                        }
838                }
839        };
840
841        // ---- Split, merge, reverse
842
843        OSMWay.prototype.splitWay=function(point,newattr) {
844                var i,z;
845                if (point>0 && point<(this.path.length-1) && !this.historic) {
846                        _root.newwayid--;                                                                                       // create new way
847                        _root.map.ways.attachMovie("way",newwayid,++waydepth);          //  |
848                        _root.map.ways[newwayid].path=shallowCopy(this.path);           // deep copy path array
849                        this.removeNodeIndex();
850
851                        if (newattr) { _root.map.ways[newwayid].attr=newattr; }
852                                        else { _root.map.ways[newwayid].attr=deepCopy(this.attr); }
853
854                        z=_root.wayrels[this._name];                                                            // copy relations
855                        for (i in z) {                                                                                          //  |
856                                _root.map.relations[i].setWayRole(newwayid,z[i]);               //  |
857                        }                                                                                                                       //  |
858 
859                        this.path.splice(Math.floor(point)+1);                                          // current way
860                        this.redraw();                                                                                          //  |
861                        this.createNodeIndex();
862
863                        _root.map.ways[newwayid].path.splice(0,point);                          // new way
864                        _root.map.ways[newwayid].locked=this.locked;                            //  |
865                        _root.map.ways[newwayid].redraw();                                                      //  |
866                        _root.map.ways[newwayid].clean=false;                                           //  | - in case it doesn't upload
867                        _root.map.ways[newwayid].upload();                                                      //  |
868                        _root.map.ways[newwayid].createNodeIndex();                                     //  |
869
870                        this.clean=false;                                                                                       // upload current way
871                        this.upload();                                                                                          //  |
872                        this.select();                                                                                          //  |
873                        uploadDirtyRelations();
874                        _root.undo.append(UndoStack.prototype.undo_splitway,
875                                                          new Array(this,_root.map.ways[newwayid]),
876                                                          iText('action_splitway'));
877                };
878        };
879
880        //              Merge (start/end of this way,other way object,start/end of other way)
881
882        OSMWay.prototype.mergeWay=function(topos,otherway,frompos) {
883                var i,z;
884                var conflict=false;
885                if (this.historic || otherway.historic) { return; }
886
887                var mergepoint=this.path.length;
888                if (topos==0) {
889                        _root.undo.append(UndoStack.prototype.undo_mergeways,
890                                                          new Array(this,deepCopy(otherway.attr),deepCopy(this.attr),frompos),
891                                                          iText('action_mergeways'));
892                } else {
893                        _root.undo.append(UndoStack.prototype.undo_mergeways,
894                                                          new Array(this,deepCopy(this.attr),deepCopy(otherway.attr),topos),
895                                                          iText('action_mergeways'));
896                }
897                if (frompos==0) { for (i=0; i<otherway.path.length;    i+=1) { this.addPointFrom(topos,otherway,i); } }
898                                   else { for (i=otherway.path.length-1; i>=0; i-=1) { this.addPointFrom(topos,otherway,i); } }
899
900                // Merge attributes
901                z=otherway.attr;
902                for (i in z) {
903                        if (otherway.attr[i].substr(0,6)=='(type ') { otherway.attr[i]=null; }
904                        if (this.attr[i].substr(0,6)=='(type ') { this.attr[i]=null; }
905                        if (this.attr[i]) {
906                                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; }
907                        } else {
908                                this.attr[i]=otherway.attr[i];
909                        }
910                        if (!this.attr[i]) { delete this.attr[i]; }
911                }
912
913                // Merge relations
914                z=_root.wayrels[otherway._name];                                                // copy relations
915                for (i in z) {                                                                                  //  |
916                        if (!_root.wayrels[this._name][i]) {                            //  |
917                                _root.map.relations[i].setWayRole(this._name,z[i]);     
918                        }                                                                                                       //  |
919                }                                                                                                               //  |
920                memberDeleted('Way', otherway._name);                                   // then remove old way from them
921                z=otherway.deletednodes;                                                                //  | and its deletednodes
922                for (i in z) { memberDeleted('Node',z[i]); }                    //  |
923
924                // Add to list of merged ways (so they can be deleted on next putway)
925        if (_root.sandbox) {
926            otherway.remove();
927        } else {
928                this.mergedways.push(new Array(otherway._name,otherway.version,otherway.deletednodes));
929                this.mergedways.concat(otherway.mergedways);
930        }
931                this.clean=false;
932                markClean(false);
933                if (otherway.locked) { this.locked=true; }
934                otherway.removeNodeIndex();
935                removeMovieClip(_root.map.areas[otherway._name]);
936                removeMovieClip(otherway);
937                if (this._name==_root.wayselected) { 
938                        _root.panel.properties.reinit();
939                }
940                if (conflict) { setAdvice(false,iText('advice_tagconflict')); }
941        };
942
943        OSMWay.prototype.addPointFrom=function(topos,otherway,srcpt) {
944                if (topos==0) { if (this.path[0                                 ]==otherway.path[srcpt]) { return; } }  // don't add duplicate points
945                                 else { if (this.path[this.path.length-1]==otherway.path[srcpt]) { return; } }  //  |
946                if (topos==0) { this.path.unshift(otherway.path[srcpt]); }
947                             else { this.path.push(otherway.path[srcpt]); }
948                otherway.path[srcpt].addWay(this._name);
949        };
950
951        OSMWay.prototype.mergeAtCommonPoint=function(sel) {
952                var selstart =sel.path[0];
953                var sellen   =sel.path.length-1;
954                var selend   =sel.path[sellen];
955                var thisstart=this.path[0];
956                var thislen  =this.path.length-1;
957                var thisend  =this.path[thislen];
958                if      (selstart==thisstart) { sel.mergeWay(0,this,0);                    return true; }
959                else if (selstart==thisend  ) { sel.mergeWay(0,this,thislen);      return true; }
960                else if (selend  ==thisstart) { sel.mergeWay(sellen,this,0);       return true; }
961                else if (selend  ==thisend  ) { sel.mergeWay(sellen,this,thislen); return true; }
962                else                                              { setAdvice(true,iText('advice_nocommonpoint')); return false; }
963        };
964
965        // ---- Reverse order
966       
967        OSMWay.prototype.reverseWay=function() {
968                if (this.path.length<2) { return; }
969                if (_root.drawpoint>-1) { _root.drawpoint=(this.path.length-1)-_root.drawpoint; }
970                this.path.reverse();
971                this.redraw();
972                this.direction();
973                this.select();
974                this.clean=false;
975                markClean(false);
976                _root.undo.append(UndoStack.prototype.undo_reverse,new Array(this),iText('action_reverseway'));
977        };
978
979        // ---- Move all nodes within a way
980       
981        OSMWay.prototype.moveNodes=function(xdiff,ydiff) {
982                var i,n;
983                var movedalready=new Array();
984                this.clean=false;
985                markClean(false);
986                for (i=0; i<this.path.length; i+=1) {
987                        n=this.path[i].id;
988                        if (movedalready[n]) {
989                        } else {
990                                this.path[i].moveTo(this.path[i].x+xdiff,
991                                                                        this.path[i].y+ydiff,
992                                                                        this._name);
993                                movedalready[n]=true;
994                        }
995                }
996        };
997
998        // ---- Add node to deleted list
999        //              (should have been removed from way first)
1000       
1001        OSMWay.prototype.markAsDeleted=function(rnode,check2) {
1002                var d=true;
1003                // If we're just removing one point, check it's not used elsewhere in the way before removing from .ways
1004                if (check2) {
1005                        var z=this.path; 
1006                        for (var i in z) { if (this.path[i].id==rnode.id) { d=false; } }
1007                }
1008                if (d) { rnode.removeWay(this._name); }
1009                if (rnode.numberOfWays()==0 && rnode.id>0) { this.deletednodes[rnode.id]=rnode.version; }
1010        };
1011
1012
1013        // ---- Check for duplicates (e.g. when C is removed from ABCB)
1014       
1015        OSMWay.prototype.removeDuplicates=function() {
1016                var z=this.path; var ch=false;
1017                for (var i in z) {
1018                        if (i>0) {
1019                                if (this.path[i]==this.path[i-1]) { this.path.splice(i,1); ch=true; }
1020                        }
1021                }
1022                return ch;
1023        };
1024
1025        // ---- Add point into way with SHIFT-clicking
1026        //              cf http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/source.vba
1027        //              for algorithm to find nearest point on a line
1028       
1029        OSMWay.prototype.insertAnchorPoint=function(nodeobj) {
1030                var nx,ny,u,closest,closei,i,a,b,direct,via,newpoint;
1031                nx=_root.map._xmouse;   // where we're inserting it
1032                ny=_root.map._ymouse;   //      |
1033                closest=0.1; closei=0;
1034                for (i=0; i<(this.path.length)-1; i+=1) {
1035                        a=this.path[i  ];
1036                        b=this.path[i+1];
1037                        direct=Math.sqrt((b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y));
1038                        via   =Math.sqrt((nx -a.x)*(nx -a.x)+(ny -a.y)*(ny -a.y));
1039                        via  +=Math.sqrt((nx -b.x)*(nx -b.x)+(ny -b.y)*(ny -b.y));
1040                        if (Math.abs(via/direct-1)<closest) {
1041                                closei=i+1;
1042                                closest=Math.abs(via/direct-1);
1043                                u=((nx-a.x)*(b.x-a.x)+
1044                                   (ny-a.y)*(b.y-a.y))/
1045                                   (Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2));
1046                                nodeobj.x=a.x+u*(b.x-a.x);
1047                                nodeobj.y=a.y+u*(b.y-a.y);
1048                        }
1049                }
1050                // Insert
1051                nodeobj.addWay(this._name);
1052                this.path.splice(closei,0,nodeobj);
1053                this.clean=false;
1054                this.redraw();
1055                markClean(false);
1056//              _root.adjustedxmouse=tx;        // in case we're adding extra points...
1057//              _root.adjustedymouse=ty;        // should probably return an array or an object **
1058                return closei;
1059        };
1060
1061        //              Wrapper around the above to:
1062        //              - insert at mouse position
1063        //              - add to undo stack
1064        //              - add intersection at any crossing ways
1065
1066        OSMWay.prototype.insertAnchorPointAtMouse=function() {
1067                _root.newnodeid--;
1068                _root.nodes[newnodeid]=new Node(newnodeid,0,0,new Object(),0);
1069                _root.pointselected=this.insertAnchorPoint(_root.nodes[newnodeid]);
1070                var waylist=new Array(); waylist.push(this);
1071                var poslist=new Array(); poslist.push(_root.pointselected);
1072                for (qway in _root.map.ways) {
1073                        if (_root.map.ways[qway].hitTest(_root._xmouse,_root._ymouse,true) && qway!=this._name) {
1074                                poslist.push(_root.map.ways[qway].insertAnchorPoint(_root.nodes[newnodeid]));
1075                                waylist.push(_root.map.ways[qway]);
1076                        }
1077                }
1078                _root.undo.append(UndoStack.prototype.undo_addpoint,
1079                                                  new Array(waylist,poslist), iText('action_insertnode'));
1080                _root.ws.highlightPoints(5000,"anchor");
1081                _root.map.anchors[pointselected].beginDrag();
1082                updateInspector();
1083        };
1084
1085        // ---- Remove point from this way (only)
1086       
1087        OSMWay.prototype.removeAnchorPoint=function(point) {
1088                // ** if length<2, then mark as way removal
1089                _root.undo.append(UndoStack.prototype.undo_deletepoint,
1090                                                  new Array(deepCopy(this.path[point]),
1091                                                                        new Array(this._name),
1092                                                                        new Array(point)),
1093                                                  iText('action_deletepoint'));
1094                var rnode=this.path[point];
1095                this.path.splice(point,1);
1096                this.removeDuplicates();
1097                this.markAsDeleted(rnode,true);
1098                if (rnode.numberOfWays()==0) { memberDeleted('Node', rnode.id); }
1099                if (this.path.length<2) { this.remove(); }
1100                                                   else { this.redraw(); this.clean=false; }
1101        };
1102
1103        // ---- Bounding box utility functions
1104
1105        OSMWay.prototype.resetBBox=function() {
1106                this.xmin=this.ymin= 999;
1107                this.xmax=this.ymax=-999;
1108        };
1109       
1110        OSMWay.prototype.updateBBox=function(long,lat) {
1111                this.xmin=Math.min(long,this.xmin);
1112                this.xmax=Math.max(long,this.xmax);
1113                this.ymin=Math.min(lat ,this.ymin);
1114                this.ymax=Math.max(lat ,this.ymax);
1115        };
1116
1117        // ---- Reset 'uploading' flag
1118
1119        OSMWay.prototype.notUploading=function() {
1120                this.uploading=false;
1121                var z=this.path; for (i in z) { this.path[i].uploading=false; }
1122                updateInspector();
1123        };
1124
1125        // ---- Node->way associations
1126       
1127        OSMWay.prototype.createNodeIndex  =function() { var z=this.path; for (var i in z) { this.path[i].addWay(this._name);    } };
1128        OSMWay.prototype.removeNodeIndex  =function() { var z=this.path; for (var i in z) { this.path[i].removeWay(this._name); } };
1129        OSMWay.prototype.renumberNodeIndex=function(n) {
1130                var z=this.path; for (i in z) { 
1131                        this.path[i].removeWay(this._name);
1132                        this.path[i].addWay(n);
1133                }
1134        };
1135        OSMWay.prototype.hasDependentNodes=function() {
1136        if (_root.sandbox && _root.uploading) { return false; }    // not an issue in consecutive uploads
1137                var d=false;
1138                var z=this.path; for (var i in z) {
1139                        if (this.path[i].id<0) {
1140                                var ways=this.path[i].ways; for (var w in ways) {
1141                                        if (_root.map.ways[w].uploading) { d=true; }
1142                                }
1143                        }
1144                }
1145                return d;
1146        };
1147
1148        // =====================================================================================
1149        // Offset path
1150        // ** much of the dialogue box could be refactored to share with relations dialogue
1151
1152        function askOffset() {
1153                if (!_root.wayselected) { return; }
1154                _root.windows.attachMovie("modal","offset",++windowdepth);
1155                _root.windows.offset.init(300, 170, [iText('cancel'), iText('ok')], completeOffset);
1156                var z = 5;
1157                var box = _root.windows.offset.box;
1158               
1159                box.createTextField("title",z++,7,7,300-14,20);
1160                box.title.text = iText('prompt_createparallel');
1161                with (box.title) {
1162                        wordWrap=true;
1163                        setTextFormat(boldText);
1164                        selectable=false; type='dynamic';
1165                }
1166                adjustTextField(box.title);
1167               
1168                box.createTextField("instr",z++,7,30,300-14,40);
1169
1170                // Create radio buttons and menu
1171
1172                box.attachMovie("radio","offsetoption",z++);
1173                box.offsetoption.addButton(10,35 ,iText('offset_dual'));
1174                box.offsetoption.addButton(10,55 ,iText('offset_motorway'));
1175                box.offsetoption.addButton(10,75 ,iText('offset_narrowcanal'));
1176                box.offsetoption.addButton(10,95 ,iText('offset_broadcanal'));
1177                box.offsetoption.addButton(10,115,iText('offset_choose'));
1178                box.offsetoption.select(1);
1179
1180                var w=box.offsetoption[5].prompt._width+25;
1181                box.createTextField("useroffset",z++,w,110,290-w,17);
1182                box.useroffset.setNewTextFormat(plainSmall);
1183                box.useroffset.type='input';
1184                box.useroffset.backgroundColor=0xDDDDDD;
1185                box.useroffset.background=true;
1186                box.useroffset.border=true;
1187                box.useroffset.borderColor=0xFFFFFF;
1188                box.useroffset.onSetFocus=function() { this._parent.offsetoption.select(5); };
1189        }
1190
1191        // typical widths:
1192        // central reservation:
1193        //       4.5m on a rural motorway/dual carriageway
1194        //       3.5m on an urban motorway
1195        //       1.8m on an urban dual carriageway
1196        // lane widths are typically always 3.65m
1197        // hard shoulders are typically 3.30m
1198        // hard strips are typically 1m
1199
1200        function completeOffset(button) {
1201                if (button!=iText('ok')) { return false; }
1202                var radio=_root.windows.offset.box.offsetoption.selected;
1203                var m;
1204                if (radio==5) {
1205                        m=_root.windows.offset.box.useroffset.text;
1206                        if (!button) { return false; }
1207                } else {
1208                        m=new Array(0,4.5+3.65*2,4.5+3.65*3,5.5,11);
1209                        m=m[radio];
1210                }
1211                var thislat=coord2lat(_root.map._y);                    // near as dammit
1212                var latfactor=Math.cos(thislat/(180/Math.PI));  // 111200m in a degree at the equator
1213                m=masterscale/(111200*latfactor)*m;                             // 111200m*cos(lat in radians) elsewhere
1214                _root.ws.offset( m);
1215                _root.ws.offset(-m);
1216                _root.undo.append(UndoStack.prototype.undo_makeways,
1217                                                  new Array(_root.newwayid,_root.newwayid+1),
1218                                                  iText('action_createparallel'));
1219        }
1220
1221        // Create (locked) offset way
1222        // offset is + or - depending on which side
1223
1224        OSMWay.prototype.offset=function(tpoffset) {
1225                var a,b,o,df,x,y;
1226                var offsetx=new Array();
1227                var offsety=new Array();
1228                var wm=1;       // was 10 before
1229
1230                _root.newwayid--;                                                                                       // create new way
1231                _root.map.ways.attachMovie("way",newwayid,++waydepth);          //  |
1232                var nw=_root.map.ways[newwayid];
1233                nw.locked=true;
1234                nw.clean=false;
1235
1236                // Normalise, and calculate offset vectors
1237
1238                for (var i=0; i<this.path.length; i++) {
1239                        a=this.path[i  ].y - this.path[i+1].y;
1240                        b=this.path[i+1].x - this.path[i  ].x;
1241                        h=Math.sqrt(a*a+b*b);
1242                        if (h!=0) { a=a/h; b=b/h; }
1243                                 else { a=0; b=0; }
1244                        offsetx[i]=wm*a;
1245                        offsety[i]=wm*b;
1246                }
1247
1248                _root.newnodeid--;
1249                _root.nodes[newnodeid]=new Node(newnodeid,this.path[0].x+tpoffset*offsetx[0],
1250                                                                                                  this.path[0].y+tpoffset*offsety[0],
1251                                                                                                  new Object(),0);
1252                _root.nodes[newnodeid].addWay(newwayid);
1253                nw.path.push(_root.nodes[newnodeid]);
1254               
1255                for (i=1; i<(this.path.length-1); i++) {
1256       
1257                        a=det(offsetx[i]-offsetx[i-1],
1258                                  offsety[i]-offsety[i-1],
1259                                  this.path[i+1].x - this.path[i  ].x,
1260                                  this.path[i+1].y - this.path[i  ].y);
1261                        b=det(this.path[i  ].x - this.path[i-1].x,
1262                                  this.path[i  ].y - this.path[i-1].y,
1263                                  this.path[i+1].x - this.path[i  ].x,
1264                                  this.path[i+1].y - this.path[i  ].y);
1265                        if (b!=0) { df=a/b; } else { df=0; }
1266                       
1267                        x=this.path[i].x + tpoffset*(offsetx[i-1]+df*(this.path[i].x - this.path[i-1].x));
1268                        y=this.path[i].y + tpoffset*(offsety[i-1]+df*(this.path[i].y - this.path[i-1].y));
1269       
1270                        _root.newnodeid--;
1271                        _root.nodes[newnodeid]=new Node(newnodeid,x,y,new Object(),0);
1272                        _root.nodes[newnodeid].addWay(newwayid);
1273                        nw.path.push(_root.nodes[newnodeid]);
1274                }
1275       
1276                _root.newnodeid--;
1277                _root.nodes[newnodeid]=new Node(newnodeid,this.path[i].x+tpoffset*offsetx[i-1],
1278                                                                                                  this.path[i].y+tpoffset*offsety[i-1],new Object(),0);
1279                _root.nodes[newnodeid].addWay(newwayid);
1280                nw.path.push(_root.nodes[newnodeid]);
1281                nw.redraw();
1282        };
1283
1284    function det(a,b,c,d) { return a*d-b*c; }
1285
1286        // =====================================================================================
1287        // Inspector
1288       
1289        OSMWay.prototype.inspect=function() {
1290                var str='';
1291
1292                // Status
1293                if (this.locked) { str+=iText('inspector_way_locked') + "\n"; }
1294                if (!this.clean) { str+=iText('inspector_unsaved'); }
1295                if (this.uploading) { str+=' ' + iText('inspector_uploading'); }
1296                if (!this.clean || this.uploading) { str+="\n"; }
1297
1298                // Number of nodes
1299                if (this.path[this.path.length-1]==this.path[0]) {
1300            str += iText('inspector_way_nodes_closed', this.path.length);
1301        } else {
1302            str += iText('inspector_way_nodes', this.path.length);
1303        }
1304                str+="\n";
1305
1306                // Connections to other ways of same type
1307                var principal='';
1308                if      (this.attr['highway' ]) { principal='highway';  }
1309                else if (this.attr['waterway']) { principal='waterway'; }
1310                else if (this.attr['railway' ]) { principal='railway';  }
1311                var same=0; var different=0;
1312                var z=this.path; for (i in z) {
1313                        var w=z[i].ways; for (id in w) {
1314                                if (id!=this._name) {
1315                                        if (_root.map.ways[id].attr[principal]==this.attr[principal]) { same++; }
1316                                        else if (_root.map.ways[id].attr[principal]) { different++; }
1317                                }
1318                        }
1319                }
1320
1321                if (this.attr[principal]==undefined) { 
1322            str += iText('inspector_way_connects_to', same);
1323                } else {
1324            str += iText('inspector_way_connects_to_principal', same, this.attr[principal], different, principal);
1325                }
1326
1327                return "<p>"+str+"</p>";
1328        };
1329
1330
1331
1332        Object.registerClass("way",OSMWay);
1333
1334
1335        // =====================================================================================
1336        // Drawing support functions
1337
1338        // removeNodeFromWays - now see Node.removeFromAllWays
1339
1340        // startNewWay            - create new way from first node
1341
1342        function startNewWay(node) {
1343                uploadSelected();
1344                _root.newwayid--;
1345                selectWay(newwayid);
1346                _root.poiselected=0;
1347                _root.map.ways.attachMovie("way",newwayid,++waydepth);
1348                _root.map.ways[newwayid].path[0]=_root.nodes[node];
1349                _root.map.ways[newwayid].redraw();
1350                _root.map.ways[newwayid].select();
1351                _root.map.ways[newwayid].clean=false;
1352                _root.nodes[node].addWay(newwayid);
1353                _root.map.anchors[0].startElastic();
1354                _root.drawpoint=0;
1355                markClean(false);
1356                setTooltip(iText('hint_drawmode'),0);
1357        }
1358
1359        // addEndPoint(node object) - add point to start/end of line
1360
1361        function addEndPoint(node) {
1362                var drawnode=_root.ws.path[_root.drawpoint];
1363                if (_root.drawpoint==_root.ws.path.length-1) {
1364                        _root.ws.path.push(node);
1365                        _root.drawpoint=_root.ws.path.length-1;
1366                } else {
1367                        _root.ws.path.unshift(node);    // drawpoint=0, add to start
1368                }
1369                node.addWay(_root.wayselected);
1370       
1371                // Redraw line (if possible, just extend it to save time)
1372                if (_root.ws.getFill()>-1 || 
1373                        _root.ws.path.length<3 ||
1374                        _root.pointselected>-2 ||
1375                        _root.ws.attr['oneway']) {
1376                        _root.ws.redraw();
1377                        _root.ws.select();
1378                } else {
1379                        _root.ws.line.moveTo(drawnode.x,drawnode.y);
1380                        _root.ws.line.lineTo(node.x,node.y);
1381                        if (casing[_root.ws.attr['highway']]) {
1382                                _root.map.areas[wayselected].moveTo(drawnode.x,drawnode.y);
1383                                _root.map.areas[wayselected].lineTo(node.x,node.y);
1384                        }
1385                        _root.map.highlight.moveTo(drawnode.x,drawnode.y);
1386                        _root.map.highlight.lineTo(node.x,node.y);
1387                        _root.ws.direction();
1388                        _root.ws.highlightPoints(5000,"anchor");
1389                        removeMovieClip(_root.map.anchorhints);
1390                        var z=_root.wayrels[_root.wayselected];
1391                        for (var rel in z) { _root.map.relations[rel].redraw(); }
1392                }
1393                // Mark as unclean
1394                _root.ws.clean=false;
1395                markClean(false);
1396                _root.map.elastic.clear();
1397                var poslist=new Array(); poslist.push(_root.drawpoint);
1398                _root.undo.append(UndoStack.prototype.undo_addpoint,
1399                                                  new Array(new Array(_root.ws),poslist),
1400                                                  iText('action_addpoint'));
1401                updateInspector();
1402        }
1403
1404        function stopDrawing() {
1405                _root.map.anchors[_root.drawpoint].endElastic();
1406                _root.drawpoint=-1;
1407                if (_root.ws.path.length<=1) { 
1408                        // way not long enough, so abort
1409                        _root.ws.removeNodeIndex();
1410                        removeMovieClip(_root.map.areas[wayselected]);
1411                        removeMovieClip(_root.ws);
1412                        removeMovieClip(_root.map.anchors);
1413                        updateInspector();
1414                }
1415                _root.map.elastic.clear();
1416                clearTooltip();
1417        };
1418
1419        // cycleStacked - cycle through ways sharing same point
1420
1421        function cycleStacked() {
1422                if (_root.pointselected>-2) {
1423                        stopDrawing();
1424                        var id=_root.ws.path[_root.pointselected].id;
1425                        var firstfound=0; var nextfound=0;
1426                        for (qway in _root.map.ways) {
1427                                if (qway!=_root.wayselected) {
1428                                        for (qs=0; qs<_root.map.ways[qway].path.length; qs+=1) {
1429                                                if (_root.map.ways[qway].path[qs].id==id) {
1430                                                        var qw=Math.floor(qway);
1431                                                        if (firstfound==0 || qw<firstfound) { firstfound=qw; }
1432                                                        if ((nextfound==0 || qw<nextfound) && qw>wayselected) { nextfound=qw; }
1433                                                }
1434                                        }
1435                                }
1436                        }
1437                        if (firstfound) {
1438                                if (nextfound==0) { var nextfound=firstfound; }
1439                                _root.map.ways[nextfound].swapDepths(_root.ws);
1440                                _root.map.ways[nextfound].select();
1441                        }
1442                }
1443        };
1444
1445        // ================================================================
1446        // Way communication
1447       
1448        // whichWays    - get list of ways from remoting server
1449
1450        function whichWays(force) {
1451                _root.lastwhichways=new Date();
1452                if (_root.waycount>500) { purgeWays(); }
1453                if (_root.poicount>500) { purgePOIs(); }
1454                if (_root.edge_l>=_root.bigedge_l &&
1455                        _root.edge_r<=_root.bigedge_r &&
1456                        _root.edge_b>=_root.bigedge_b &&
1457                        _root.edge_t<=_root.bigedge_t && (!force)) {
1458                        // we have already loaded this area, so ignore
1459                } else {
1460                        whichresponder=function() {};
1461                        whichresponder.onResult=function(result) {
1462                                _root.whichreceived+=1;
1463                                var code=result.shift(); var msg=result.shift(); if (code) { handleError(code,msg,result); return; }
1464                                var waylist  =result[0];
1465                                var pointlist=result[1];
1466                                var relationlist=result[2];
1467                                var s;
1468
1469                                for (i in waylist) {                                                                            // ways
1470                                        way=waylist[i][0];                                                                              //  |
1471                                        if ((!_root.map.ways[way] || _root.map.ways[way].version!=waylist[i][1]) && !_root.waystodelete[way]) {
1472                                                _root.map.ways.attachMovie("way",way,++waydepth);       //  |
1473                                                _root.map.ways[way].load();                                                     //  |
1474                                                _root.waycount+=1;                                                                      //  |
1475                                                _root.waysrequested+=1;                                                         //  |
1476                                        }
1477                                }
1478                               
1479                                for (i in pointlist) {                                                                          // POIs
1480                                        point=pointlist[i][0];                                                                  //  |
1481                                        if ((!_root.map.pois[point] || _root.map.pois[point].version!=pointlist[i][4]) && !_root.poistodelete[point]) {
1482                                                var a=getPOIIcon(pointlist[i][3]);                                      //  |
1483                                                if (a=='poi') { s=poiscale; } else { s=iconscale; }     //  |
1484                                                _root.map.pois.attachMovie(a,point,++poidepth); //  |
1485                                                _root.map.pois[point]._x=long2coord(pointlist[i][1]);// |
1486                                                _root.map.pois[point]._y=lat2coord (pointlist[i][2]);// |
1487                                                _root.map.pois[point]._xscale=_root.map.pois[point]._yscale=s;
1488                                                _root.map.pois[point].version=pointlist[i][4];          //  |
1489                                                _root.map.pois[point].attr=pointlist[i][3];                     //  |
1490                                                _root.map.pois[point].icon=a;                                           //  |
1491                                                _root.poicount+=1;                                                                      //  |
1492                                                if (point==prenode) { deselectAll(); prenode=undefined;
1493                                                                                          _root.map.pois[point].select(); }
1494                                        }
1495                                }
1496
1497                                for (i in relationlist) {
1498                                        rel = relationlist[i][0];
1499                    if (!_root.map.relations[rel] || _root.map.relations[rel].version!=relationlist[i][1]) {
1500                                                _root.map.relations.attachMovie("relation",rel,++reldepth);
1501                                                _root.map.relations[rel].load();
1502                                                _root.relcount+=1;
1503                                                _root.relsrequested+=1;
1504                                        }
1505                }
1506                        };
1507                        remote_read.call('whichways',whichresponder,_root.edge_l,_root.edge_b,_root.edge_r,_root.edge_t);
1508                        _root.bigedge_l=_root.edge_l; _root.bigedge_r=_root.edge_r;
1509                        _root.bigedge_b=_root.edge_b; _root.bigedge_t=_root.edge_t;
1510                        _root.whichrequested+=1;
1511                }
1512        }
1513
1514        // purgeWays - remove any clean ways outside current view
1515       
1516        function purgeWays() {
1517                for (qway in _root.map.ways) {
1518                        if (qway==_root.wayselected) {
1519                        } else if (!_root.map.ways[qway].clean) {
1520                                _root.map.ways[qway].upload();
1521                                uploadDirtyRelations();
1522                        } else if (((_root.map.ways[qway].xmin<edge_l && _root.map.ways[qway].xmax<edge_l) ||
1523                                                (_root.map.ways[qway].xmin>edge_r && _root.map.ways[qway].xmax>edge_r) ||
1524                                            (_root.map.ways[qway].ymin<edge_b && _root.map.ways[qway].ymax<edge_b) ||
1525                                                (_root.map.ways[qway].ymin>edge_t && _root.map.ways[qway].ymax>edge_t))) {
1526                                removeMovieClip(_root.map.ways[qway]);
1527                                removeMovieClip(_root.map.areas[qway]);
1528                                _root.waycount-=1;
1529                        }
1530                }
1531                _root.bigedge_l=_root.edge_l; _root.bigedge_r=_root.edge_r;
1532                _root.bigedge_b=_root.edge_b; _root.bigedge_t=_root.edge_t;
1533        }
1534
1535        function selectWay(id) {
1536                _root.lastwayselected=_root.wayselected;
1537                _root.wayselected=Math.floor(id);
1538                _root.ws=_root.map.ways[id];
1539               
1540                if (id==0) {
1541                        _root.panel.advanced.disableOption(0);
1542                        _root.panel.advanced.disableOption(1);
1543                } else {
1544                        _root.panel.advanced.enableOption(0);
1545                        _root.panel.advanced.enableOption(1);
1546                }
1547                updateInspector();
1548        }
1549       
1550        function renumberWay(ow,nw) {
1551                _root.map.ways[ow].renumberNodeIndex(nw);
1552                wayrels[nw]=wayrels[ow]; delete wayrels[ow];
1553                _root.map.ways[ow]._name=nw;
1554                renumberMemberOfRelation('Way', ow, nw);
1555                if (_root.map.areas[ow]) { _root.map.areas[ow]._name=nw; }
1556                if (_root.panel.t_details.text==ow) { _root.panel.t_details.text=nw; _root.panel.t_details.setTextFormat(plainText); }
1557                if (wayselected==ow) { selectWay(nw); }
1558        }
1559       
1560        function mergeWayKeepingID(w1,w2) {
1561                var t=(w1==_root.ws || w2==_root.ws);
1562                var w,s;
1563                if (Number(w1._name)<0 && Number(w2._name)>0) {
1564                        s=w1.mergeAtCommonPoint(w2); w=w2;
1565                } else {
1566                        s=w2.mergeAtCommonPoint(w1); w=w1;
1567                }
1568                if (s) { w.redraw(); if (t) { w.select(); } }
1569        }
1570
1571        function uploadDirtyWays(allow_ws) {
1572                if (_root.sandbox) { return; }
1573                var z=_root.map.ways;
1574                for (i in z) {
1575                        if (!_root.map.ways[i].clean && (i!=wayselected || allow_ws) && !_root.map.ways[i].hasDependentNodes()) { 
1576                                _root.map.ways[i].upload();
1577                        }
1578                }
1579        };
1580
Note: See TracBrowser for help on using the repository browser.