refactor the potlatch 2 code to look more MVC wrt the API parts
[potlatch2.git] / net / systemeD / halcyon / WayUI.as
1 package net.systemeD.halcyon {
2
3         import flash.display.*;
4         import flash.geom.Matrix;
5         import flash.geom.Point;
6         import flash.geom.Rectangle;
7         import flash.text.AntiAliasType;
8         import flash.text.GridFitType;
9         import flash.text.TextField;
10         import flash.text.TextFormat;
11         import net.systemeD.halcyon.styleparser.*;
12     import net.systemeD.halcyon.connection.*;
13
14         public class WayUI {
15         private var way:Way;
16
17                 public var pathlength:Number;                           // length of path
18
19                 public var layer:int=0;                                         // map layer
20                 public var map:Map;                                                     // reference to parent map
21                 public var stroke:Sprite;                                       // instance in display list
22                 public var fill:Sprite;                                         //  |
23                 public var roadname:Sprite;                                     //  |
24
25                 public static const DEFAULT_TEXTFIELD_PARAMS:Object = {
26                         embedFonts: true,
27                         antiAliasType: AntiAliasType.ADVANCED,
28                         gridFitType: GridFitType.NONE
29                 };
30                 [Embed(source="fonts/DejaVuSans.ttf", fontFamily="DejaVu", fontWeight="normal", mimeType="application/x-font-truetype")]
31                 public static var DejaVu:Class;
32                 public var nameformat:TextFormat;
33
34
35                 public function WayUI(way:Way, map:Map) {
36                         this.way = way;
37                         this.map = map;
38             init();
39                 }
40                 
41                 private function init():void {
42                         var lx:Number, ly:Number;
43                         pathlength=0;
44                         
45                         for ( var i:uint = 0; i < way.length; i++ ) {
46                 var node:Node = way.getNode(i);
47                 var lat:Number = node.lat;
48                 var lon:Number = node.lon;
49 //                              updateBbox(lon, lat);
50                                 if ( !isNaN(lx) ) {
51                     pathlength += Math.sqrt( Math.pow(lon-lx,2)+Math.pow(lat-ly,2) );
52                 }
53                                 lx=lon; ly=lat;
54                         }
55
56                         pathlength*=map.scalefactor;
57                         redraw();
58                         // ** various other stuff
59                 }
60
61                 // ------------------------------------------------------------------------------------------
62                 // Redraw
63
64                 public function redraw():void {
65             var tags:Object = way.getTagsCopy();
66
67                         // ** remove previous version from any layer
68                         layer=5;
69                         if ( tags['layer'] )
70                 layer=Math.min(Math.max(tags['layer']+5,-5),5)+5;
71
72                         // find/create sprites
73                         if (stroke) {
74                                 fill.graphics.clear(); 
75                                 stroke.graphics.clear(); 
76                                 roadname.graphics.clear();
77                                 while (roadname.numChildren) { roadname.removeChildAt(0); }
78                         } else {
79                                 fill=new Sprite(); addToLayer(fill,0);
80                                 stroke=new Sprite(); addToLayer(stroke,1); 
81                                 roadname=new Sprite(); addToLayer(roadname,2); 
82                         }
83                         var g:Graphics=stroke.graphics;
84                         var f:Graphics=fill.graphics;
85
86                         // set style
87                         var styles:Array=map.ruleset.getStyle(false, tags, map.scale);
88
89                         // ShapeStyle
90                         // ** do line-caps/joints
91                         var doStroke:Boolean=false, doDashed:Boolean=false;
92                         var doFill:Boolean=false, fill_colour:uint, fill_opacity:Number;
93                         var doCasing:Boolean=false, doDashedCasing:Boolean=false;
94                         if (styles[0]) {
95                                 var ss:ShapeStyle=styles[0];
96                                 if (ss.isStroked) {     doStroke=true;
97                                                                         doDashed=(ss.stroke_dashArray.length>0);
98                                                                         g.lineStyle(ss.stroke_width, ss.stroke_colour, ss.stroke_opacity/100,
99                                                                                                 false,"normal", ss.stroke_linecap,ss.stroke_linejoin); }
100                                 if (ss.isCased)   { doCasing=true;
101                                                                         doDashedCasing=(ss.casing_dashArray.length>0);
102                                                                         f.lineStyle(ss.casing_width, ss.casing_colour, ss.casing_opacity/100,
103                                                                                                 false,"normal", ss.stroke_linecap, ss.stroke_linejoin); }
104                                 if (ss.isFilled)  { doFill=true;
105                                                                         fill_colour = ss.fill_colour;
106                                                                         fill_opacity= ss.fill_opacity/100; }
107                         }
108
109                         // draw line
110                         if (doFill)
111                 f.beginFill(fill_colour,fill_opacity);
112                         if (doStroke)
113                 g.moveTo(map.lon2coord(way.getNode(0).lon), map.latp2coord(way.getNode(0).latp));
114                         if (doFill || doCasing)
115                 f.moveTo(map.lon2coord(way.getNode(0).lon), map.latp2coord(way.getNode(0).latp));
116
117                         if (doDashed)
118                 dashedLine(g,ss.stroke_dashArray);
119                         else if (doStroke)
120                 solidLine(g);
121                         
122                         if (doDashedCasing) {
123                 dashedLine(f,ss.casing_dashArray);
124                 f.lineStyle();
125             }
126                         if (doFill) {
127                                 f.beginFill(fill_colour,fill_opacity); 
128                                 solidLine(f);
129                                 f.endFill(); 
130                         } else if (doCasing && !doDashedCasing) {
131                 solidLine(f);
132             }
133
134                         // TextStyle
135                         // ** do pull-out
136                         if (styles[2] && styles[2].tag && tags[styles[2].tag]) {
137                                 var ts:TextStyle=styles[2];
138                                 nameformat = new TextFormat(ts.font_name   ? ts.font_name : "DejaVu",
139                                                                                         ts.text_size   ? ts.text_size : 8,
140                                                                                         ts.text_colour ? ts.text_colour: 0,
141                                                                                         ts.font_bold   ? ts.font_bold : false,
142                                                                                         ts.font_italic ? ts.font_italic: false);
143                                 var a:String=tags[ts.tag]; if (ts.font_caps) { a=a.toUpperCase(); }
144                                 writeName(roadname,a,ts.text_offset ? ts.text_offset : 0);
145                         }
146                         // ShieldStyle - 3
147                         // ** to do
148                 }
149                 
150                 // ------------------------------------------------------------------------------------------
151                 // Drawing support functions
152
153                 // Draw solid polyline
154                 
155                 private function solidLine(g:Graphics):void {
156             var node:Node = way.getNode(0);
157                         g.moveTo(map.lon2coord(node.lon), map.latp2coord(node.latp));
158                         for (var i:uint = 1; i < way.length; i++) {
159                 node = way.getNode(i);
160                                 g.lineTo(map.lon2coord(node.lon), map.latp2coord(node.latp));
161                         }
162                 }
163
164                 // Draw dashed polyline
165                 
166                 private function dashedLine(g:Graphics,dashes:Array):void {
167                         var draw:Boolean=false, dashleft:Number=0, dc:Array=new Array();
168                         var a:Number, xc:Number, yc:Number;
169                         var curx:Number, cury:Number;
170                         var dx:Number, dy:Number, segleft:Number=0;
171                         var i:int=0;
172
173             var node:Node = way.getNode(0);
174             var nextNode:Node = way.getNode(0);
175                         g.moveTo(map.lon2coord(node.lon), map.latp2coord(node.latp));
176                         while (i < way.length-1 || segleft>0) {
177                                 if (dashleft<=0) {      // should be ==0
178                                         if (dc.length==0) { dc=dashes.slice(0); }
179                                         dashleft=dc.shift();
180                                         draw=!draw;
181                                 }
182                                 if (segleft<=0) {       // should be ==0
183                     node = way.getNode(i);
184                     nextNode = way.getNode(i+1);
185                                         curx=map.lon2coord(node.lon);
186                     dx=map.lon2coord(nextNode.lon)-curx;
187                                         cury=map.latp2coord(node.latp);
188                     dy=map.latp2coord(nextNode.latp)-cury;
189                                         a=Math.atan2(dy,dx); xc=Math.cos(a); yc=Math.sin(a);
190                                         segleft=Math.sqrt(dx*dx+dy*dy);
191                                         i++;
192                                 }
193
194                                 if (segleft<=dashleft) {
195                                         // the path segment is shorter than the dash
196                                         curx+=dx; cury+=dy;
197                                         moveLine(g,curx,cury,draw);
198                                         dashleft-=segleft; segleft=0;
199                                 } else {
200                                         // the path segment is longer than the dash
201                                         curx+=dashleft*xc; dx-=dashleft*xc;
202                                         cury+=dashleft*yc; dy-=dashleft*yc;
203                                         moveLine(g,curx,cury,draw);
204                                         segleft-=dashleft; dashleft=0;
205                                 }
206                         }
207                 }
208
209                 private function moveLine(g:Graphics,x:Number,y:Number,draw:Boolean):void {
210                         if (draw) { g.lineTo(x,y); }
211                                  else { g.moveTo(x,y); }
212                 }
213
214                 
215                 // Find point partway (0-1) along a path
216                 // returns (x,y,angle)
217                 // inspired by senocular's Path.as
218                 
219                 private function pointAt(t:Number):Array {
220                         var totallen:Number = t*pathlength;
221                         var curlen:Number = 0;
222                         var dx:Number, dy:Number, seglen:Number;
223                         for (var i:int = 1; i < way.length; i++){
224                                 dx=map.lon2coord(way.getNode(i).lon)-map.lon2coord(way.getNode(i-1).lon);
225                                 dy=map.latp2coord(way.getNode(i).latp)-map.latp2coord(way.getNode(i-1).latp);
226                                 seglen=Math.sqrt(dx*dx+dy*dy);
227                                 if (totallen > curlen+seglen) { curlen+=seglen; continue; }
228                                 return new Array(map.lon2coord(way.getNode(i-1).lon)+(totallen-curlen)/seglen*dx,
229                                                                  map.latp2coord(way.getNode(i-1).latp)+(totallen-curlen)/seglen*dy,
230                                                                  Math.atan2(dy,dx));
231                         }
232                         return new Array(0, 0, 0);
233                 }
234
235                 // Draw name along path
236                 // based on code by Tom Carden
237                 // ** needs styling
238                 
239                 private function writeName(s:Sprite,a:String,textOffset:Number=0):void {
240
241                         // make a dummy textfield so we can measure its width
242                         var tf:TextField = new TextField();
243                         tf.defaultTextFormat = nameformat;
244                         tf.text = a;
245                         tf.width = tf.textWidth+4;
246                         tf.height = tf.textHeight+4;
247                         if (pathlength<tf.width) { return; }    // no room for text?
248
249                         var t1:Number = (pathlength/2 - tf.width/2) / pathlength; var p1:Array=pointAt(t1);
250                         var t2:Number = (pathlength/2 + tf.width/2) / pathlength; var p2:Array=pointAt(t2);
251
252                         var angleOffset:Number; // so we can do a 180ยบ if we're running backwards
253                         var offsetSign:Number;  // -1 if we're starting at t2
254                         var tStart:Number;      // t1 or t2
255
256                         // make sure text doesn't run right->left or upside down
257                         if (p1[0] < p2[0] && 
258                                 p1[2] < Math.PI/2 &&
259                                 p1[2] > -Math.PI/2) {
260                                 angleOffset = 0; offsetSign = 1; tStart = t1;
261                         } else {
262                                 angleOffset = Math.PI; offsetSign = -1; tStart = t2;
263                         } 
264
265                         // make a textfield for each char, centered on the line,
266                         // using getCharBoundaries to rotate it around its center point
267                         var chars:Array = a.split('');
268                         for (var i:int = 0; i < chars.length; i++) {
269                                 var rect:Rectangle = tf.getCharBoundaries(i);
270                                 if (rect) {
271                                         s.addChild(rotatedLetter(chars[i],
272                                                                                          tStart + offsetSign*(rect.left+rect.width/2)/pathlength,
273                                                                                          rect.width, tf.height, angleOffset, textOffset));
274                                 }
275                         }
276                 }
277
278                 private function rotatedLetter(char:String, t:Number, w:Number, h:Number, a:Number, o:Number):TextField {
279                         var tf:TextField = new TextField();
280                         tf.embedFonts = true;
281                         tf.defaultTextFormat = nameformat;
282                         tf.text = char;
283                         tf.width = tf.textWidth+4;
284                         tf.height = tf.textHeight+4;
285
286                         var p:Array=pointAt(t);
287                         var matrix:Matrix = new Matrix();
288                         matrix.translate(-w/2, -h/2-o);
289                         // ** add (say) -4 to the height to move it up by 4px
290                         matrix.rotate(p[2]+a);
291                         matrix.translate(p[0], p[1]);
292                         tf.transform.matrix = matrix;
293                         return tf;
294                 }
295                 
296                 // Add object (stroke/fill/roadname) to layer sprite
297                 
298                 private function addToLayer(s:Sprite,sublayer:uint):void {
299                         var l:DisplayObject=Map(map).getChildAt(layer);
300                         var o:DisplayObject=Sprite(l).getChildAt(sublayer);
301                         Sprite(o).addChild(s);
302                 }
303         }
304 }