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