Allow more than one imagery source to have a logo
[potlatch2.git] / net / systemeD / potlatch2 / collections / Imagery.as
1 package net.systemeD.potlatch2.collections {
2
3         import flash.events.*;
4         import flash.display.*;
5         import flash.net.*;
6         import flash.text.TextField;
7         import net.systemeD.halcyon.DebugURLRequest;
8         import net.systemeD.halcyon.Map;
9         import net.systemeD.halcyon.MapEvent;
10         import net.systemeD.potlatch2.FunctionKeyManager;
11         import net.systemeD.potlatch2.Yahoo;
12         import mx.collections.ArrayCollection;
13
14         /*
15                 There's lots of further tidying we can do:
16                 - remove all the horrid Yahoo stuff
17                 - remove the backreferences to _map and send events instead
18                 but this will do for now and help remove the clutter from potlatch2.mxml.
19         */
20
21         public class Imagery extends EventDispatcher {
22
23         private static const GLOBAL_INSTANCE:Imagery = new Imagery();
24         public static function instance():Imagery { return GLOBAL_INSTANCE; }
25
26                 public var collection:Array=[];
27                 private var _selected:Object={};
28
29                 private var _map:Map;
30                 private var _overlay:Sprite;
31                 private var _yahoo:Yahoo;
32
33                 /* Load catalogue file */
34
35                 public function init(map:Map, overlay:Sprite, yahoo:Yahoo):void {
36                         _map = map;
37                         _overlay = overlay;
38                         _yahoo = yahoo;
39
40                         // load imagery file
41                 var request:DebugURLRequest = new DebugURLRequest("imagery.xml");
42                 var loader:URLLoader = new URLLoader();
43                 loader.addEventListener(Event.COMPLETE, onImageryLoad);
44                 loader.load(request.request);
45
46                         // create map listeners
47                         map.addEventListener(MapEvent.MOVE, moveHandler);
48                         map.addEventListener(MapEvent.RESIZE, resizeHandler);
49                 }
50
51         private function onImageryLoad(event:Event):void {
52                         var xml:XML = new XML(URLLoader(event.target).data);
53                         var saved:Object = {};
54                         var bg:Object;
55                         if (SharedObject.getLocal("user_state").data['background_url']!=undefined) {
56                                 saved={ name: SharedObject.getLocal("user_state").data['background_name'],
57                                                 url:  SharedObject.getLocal("user_state").data['background_url' ] };
58                         }
59
60                         var isSet:Boolean=false;
61             var backgroundSet:Boolean = false;
62
63                         // Read all values from XML file
64                         collection=new Array({ name: "None", url: "" });
65                         for each(var set:XML in xml.set) {
66                                 var obj:Object={};
67                                 var a:XML;
68                                 for each (a in set.@*) { obj[a.name().localName]=a.toString(); }
69                                 for each (a in set.* ) { obj[a.name()          ]=a.toString(); }
70                 collection.push(obj);
71                                 if ((saved.url  && obj.url ==saved.url) ||
72                                     (saved.name && obj.name==saved.name && obj.name!='Custom')) { isSet=true; }
73                         }
74
75                         // Add user's previous preference (from SharedObject) if we didn't find it in the XML file
76             if (!isSet && saved.name && saved.url && saved.url!='') {
77                 collection.push(saved);
78                 isSet=true;
79             }
80
81                         // Automatically select the user's previous preference
82                         var defaultBackground:Object=null;
83                         for each (bg in collection) {
84                                 if (bg.name==saved.name || bg.url==saved.url) {
85                                         setBackground(bg);
86                     backgroundSet = true;
87                                 } else if (bg.default) {
88                                         defaultBackground=bg;
89                                 }
90                         }
91
92             // Otherwise, set whatever's specified as default
93             if (!backgroundSet && defaultBackground) {
94                 setBackground(defaultBackground);
95             }
96
97                         // Get any attribution and logo details
98                         collection.forEach(function(bg:Object, index:int, array:Array):void {
99                                 if (bg.logo) {
100                                         // load the logo
101                                         var loader:Loader = new Loader();
102                                         loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void { onLogoLoad(e,bg); });
103                                         loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
104                                         loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
105                                         loader.load(new URLRequest(bg.logo));
106                                 }
107                                 if (bg.attribution_url) {
108                                         // load the attribution
109                                 var urlloader:URLLoader = new URLLoader();
110                                         urlloader.addEventListener(Event.COMPLETE, function(e:Event):void { onAttributionLoad(e,bg); });
111                                         urlloader.addEventListener(IOErrorEvent.IO_ERROR, onError);
112                                         urlloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
113                                 urlloader.load(new URLRequest(bg.attribution_url));
114                                 }
115                         });
116
117                         // Tell the function key manager that we'd like to receive function key calls
118                         FunctionKeyManager.instance().registerListener('Background imagery',
119                                 function(o:String):void { setBackground(findBackgroundWithName(o)); });
120                         dispatchEvent(new Event("collection_changed"));
121                 }
122                 
123                 private function onError(e:Event):void {
124                         // placeholder error routine so exception isn't thrown
125                 }
126                 
127                 public function onLogoLoad(e:Event, bg:Object):void {
128                         bg.logoData  = Bitmap(LoaderInfo(e.target).content).bitmapData;
129                         bg.logoWidth = e.target.loader.width;
130                         bg.logoHeight= e.target.loader.height;
131                         setLogo();
132                 }
133                 
134                 public function onAttributionLoad(e:Event,bg: Object):void {
135                         // if we ever need to cope with non-Microsoft attribution, then this should look at bg.scheme
136             default xml namespace = Namespace("http://schemas.microsoft.com/search/local/ws/rest/v1");
137             var xml:XML = new XML(e.target.data);
138                         var attribution:Object = {};
139             for each (var ImageryProvider:XML in xml..ImageryProvider) {
140                 var areas:Array=[];
141                 for each (var CoverageArea:XML in ImageryProvider.CoverageArea) {
142                     areas.push([CoverageArea.ZoomMin,
143                                 CoverageArea.ZoomMax,
144                                 CoverageArea.BoundingBox.SouthLatitude,
145                                 CoverageArea.BoundingBox.WestLongitude,
146                                 CoverageArea.BoundingBox.NorthLatitude,
147                                 CoverageArea.BoundingBox.EastLongitude]);
148                 }
149                 attribution[ImageryProvider.Attribution]=areas;
150             }
151                         default xml namespace = new Namespace("");
152                         bg.attribution=attribution;
153                         setAttribution();
154                 }
155
156                 public function setBackground(bg:Object):void {
157                         // set background
158                         _selected=bg;
159                         if (bg.url=='yahoo') { dispatchEvent(new CollectionEvent(CollectionEvent.SELECT, {url:''})); _yahoo.show(); }
160                                         else { dispatchEvent(new CollectionEvent(CollectionEvent.SELECT, bg      )); _yahoo.hide(); }
161                         // update attribution and logo
162                         _overlay.visible=bg.attribution || bg.logo || bg.terms_url;
163                         setLogo(); setAttribution(); setTerms();
164                         // save as SharedObject for next time
165                         var obj:SharedObject = SharedObject.getLocal("user_state");
166                         obj.setProperty('background_url' ,String(bg.url));
167                         obj.setProperty('background_name',String(bg.name));
168                         obj.flush();
169                 }
170                 
171                 public function get selected():Object { return _selected; }
172                 
173                 private function findBackgroundWithName(name:String):Object {
174                         for each (var bg:Object in collection) {
175                                 if (bg.name==name) { return bg; }
176                         }
177                         return { url:'' };
178                 }
179
180                 private function moveHandler(event:MapEvent):void {
181                         setAttribution();
182                         dispatchEvent(new Event("collection_changed"));
183                 }
184                 private function setAttribution():void {
185                         var tf:TextField=TextField(_overlay.getChildAt(0));
186                         tf.text='';
187                         if (!_selected.attribution) return;
188                         var attr:Array=[];
189                         for (var provider:String in _selected.attribution) {
190                                 for each (var bounds:Array in _selected.attribution[provider]) {
191                                         if (_map.scale>=bounds[0] && _map.scale<=bounds[1] &&
192                                           ((_map.edge_l>bounds[3] && _map.edge_l<bounds[5]) ||
193                                            (_map.edge_r>bounds[3] && _map.edge_r<bounds[5]) ||
194                                    (_map.edge_l<bounds[3] && _map.edge_r>bounds[5])) &&
195                                           ((_map.edge_b>bounds[2] && _map.edge_b<bounds[4]) ||
196                                            (_map.edge_t>bounds[2] && _map.edge_t<bounds[4]) ||
197                                            (_map.edge_b<bounds[2] && _map.edge_t>bounds[4]))) {
198                                                 attr.push(provider);
199                                         }
200                                 }
201                         }
202                         if (attr.length==0) return;
203                         tf.text="Background "+attr.join(", ");
204                         positionAttribution();
205                         dispatchEvent(new MapEvent(MapEvent.BUMP, { y: tf.textHeight }));       // don't let the toolbox obscure it
206                 }
207                 private function positionAttribution():void {
208                         var tf:TextField=TextField(_overlay.getChildAt(0));
209                         tf.x=_map.mapwidth  - 5 - tf.textWidth;
210                         tf.y=_map.mapheight - 5 - tf.textHeight;
211                 }
212
213                 private function setLogo():void {
214                         while (_overlay.numChildren>2) { _overlay.removeChildAt(2); }
215                         if (!_selected.logoData) return;
216                         var logo:Sprite=new Sprite();
217                         logo.addChild(new Bitmap(_selected.logoData));
218                         if (_selected.logo_url) { logo.buttonMode=true; logo.addEventListener(MouseEvent.CLICK, launchLogoLink, false, 0, true); }
219                         _overlay.addChild(logo);
220                         positionLogo();
221                 }
222                 private function positionLogo():void {
223                         _overlay.getChildAt(2).x=5;
224                         _overlay.getChildAt(2).y=_map.mapheight - 5 - _selected.logoHeight - (_selected.terms_url ? 10 : 0);
225                 }
226                 private function launchLogoLink(e:Event):void {
227                         if (!_selected.logo_url) return;
228                         navigateToURL(new URLRequest(_selected.logo_url), '_blank');
229                 }
230                 private function setTerms():void {
231                         var terms:TextField=TextField(_overlay.getChildAt(1));
232                         if (!_selected.terms_url) { terms.text=''; return; }
233                         terms.text="Background terms of use";
234                         positionTerms();
235                         terms.addEventListener(MouseEvent.CLICK, launchTermsLink, false, 0, true);
236                 }
237                 private function positionTerms():void {
238                         _overlay.getChildAt(1).x=5;
239                         _overlay.getChildAt(1).y=_map.mapheight - 15;
240                 }
241                 private function launchTermsLink(e:Event):void {
242                         if (!_selected.terms_url) return;
243                         navigateToURL(new URLRequest(_selected.terms_url), '_blank');
244                 }
245
246                 private function resizeHandler(event:MapEvent):void {
247                         if (_selected.logoData) positionLogo();
248                         if (_selected.terms_url) positionTerms();
249                         if (_selected.attribution) positionAttribution();
250                 }
251
252                 [Bindable(event="collection_changed")]
253                 public function getAvailableImagery():ArrayCollection {
254                         var available:Array=[];
255                         for each (var bg:Object in collection) {
256                                 if (bg.minlon) {
257                                         // if there's a bbox, check the current viewport intersects it
258                                         if (((_map.edge_l>bg.minlon && _map.edge_l<bg.maxlon) ||
259                                              (_map.edge_r>bg.minlon && _map.edge_r<bg.maxlon) ||
260                                              (_map.edge_l<bg.minlon && _map.edge_r>bg.maxlon)) &&
261                                             ((_map.edge_b>bg.minlat && _map.edge_b<bg.maxlat) ||
262                                              (_map.edge_t>bg.minlat && _map.edge_t<bg.maxlat) ||
263                                              (_map.edge_b<bg.minlat && _map.edge_t>bg.maxlat))) {
264                                                 available.push(bg);
265                                         }
266                                 } else {
267                                         // if there's no bbox (i.e. global set), include it anyway
268                                         available.push(bg);
269                                 }
270                         }
271                         return new ArrayCollection(available);
272                 }
273
274         }
275         
276 }