move attribution to bottom right, and support a 'Terms of use' URL (add <terms_url...
[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                 public var selected:Object={};
28
29                 private var _yahooDefault:Boolean=false;
30                 private var _map:Map;
31                 private var _overlay:Sprite;
32                 private var _yahoo:Yahoo;
33
34                 /* Load catalogue file */
35
36                 public function init(map:Map, overlay:Sprite, yahoo:Yahoo, yahooDefault:Boolean):void {
37                         _map = map;
38                         _overlay = overlay;
39                         _yahoo = yahoo;
40                         _yahooDefault = yahooDefault;
41
42                         // load imagery file
43                 var request:DebugURLRequest = new DebugURLRequest("imagery.xml");
44                 var loader:URLLoader = new URLLoader();
45                 loader.addEventListener(Event.COMPLETE, onImageryLoad);
46                 loader.load(request.request);
47
48                         // create map listeners
49                         map.addEventListener(MapEvent.MOVE, moveHandler);
50                         map.addEventListener(MapEvent.RESIZE, resizeHandler);
51                 }
52
53         private function onImageryLoad(event:Event):void {
54                         var xml:XML = new XML(URLLoader(event.target).data);
55                         var saved:Object;
56                         var bg:Object;
57                         if (SharedObject.getLocal("user_state").data['background_url']) {
58                                 saved={ name: SharedObject.getLocal("user_state").data['background_name'],
59                                                 url:  SharedObject.getLocal("user_state").data['background_url' ] };
60                         } else {
61                                 saved={ url: ''};
62                         }
63
64                         var isSet:Boolean=false;
65             var backgroundSet:Boolean = false;
66
67                         // Read all values from XML file
68             collection=new Array(
69                                 { name: "None", url: "" },
70                                 { name: "Yahoo", url: "yahoo", sourcetag: "Yahoo" } );
71                         for each(var set:XML in xml.set) {
72                                 var obj:Object={};
73                                 var a:XML;
74                                 for each (a in set.@*) { obj[a.name().localName]=a.toString(); }
75                                 for each (a in set.* ) { obj[a.name()          ]=a.toString(); }
76                 collection.push(obj);
77                                 if ((obj.url ==saved.url) ||
78                                     (obj.name==saved.name && obj.name!='Custom')) { isSet=true; }
79                         }
80
81                         // Add user's previous preference (from SharedObject) if we didn't find it in the XML file
82             if (!isSet && saved.name && saved.url && saved.url!='' && saved.url!='yahoo') {
83                 collection.push(saved);
84                 isSet=true;
85             }
86
87                         // Automatically select the user's previous preference
88                         for each (bg in collection) {
89                                 if (bg.name==saved.name || bg.url==saved.url) {
90                                         setBackground(bg);
91                     backgroundSet = true;
92                                 }
93                         }
94
95             // Otherwise, set Yahoo as default (if this was passed in as an option from the embedding page)
96             if (!backgroundSet && _yahooDefault) {
97                 setBackground(collection[1]);
98             }
99
100                         // Get any attribution and logo details
101                         for each (bg in collection) {
102                                 if (bg.logo) {
103                                         // load the logo
104                                         var loader:Loader = new Loader();
105                                         var thisbg1:Object = bg;                        // scope it for the closure
106                                         loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void { onLogoLoad(e,thisbg1); });
107                                         loader.load(new URLRequest(bg.logo));
108                                 }
109                                 if (bg.attribution_url) {
110                                         // load the attribution
111                                         trace("requesting "+bg.attribution_url);
112                                 var urlloader:URLLoader = new URLLoader();
113                                         var thisbg2:Object = bg;                        // scope it for the closure
114                                         urlloader.addEventListener(Event.COMPLETE, function(e:Event):void { onAttributionLoad(e,thisbg2); });
115                                 urlloader.load(new URLRequest(bg.attribution_url));
116                                 }
117                         }
118
119                         // Tell the function key manager that we'd like to receive function key calls
120                         FunctionKeyManager.instance().registerListener('Background imagery',
121                                 function(o:String):void { setBackground(findBackgroundWithName(o)); });
122                         dispatchEvent(new Event("collection_changed"));
123                 }
124                 
125                 public function onLogoLoad(e:Event, bg:Object):void {
126                         bg.logoData  = Bitmap(LoaderInfo(e.target).content).bitmapData;
127                         bg.logoWidth = e.target.loader.width;
128                         bg.logoHeight= e.target.loader.height;
129                         setLogo();
130                 }
131                 
132                 public function onAttributionLoad(e:Event,bg: Object):void {
133                         trace ("onAttributionLoad");
134                         // if we ever need to cope with non-Microsoft attribution, then this should look at bg.scheme
135                         // someone who actually likes XML can replace the following with the 'proper' way of doing it
136                         var xmlnsPattern:RegExp = new RegExp("xmlns[^\"]*\"[^\"]*\"", "gi");
137                         var xsiPattern:RegExp = new RegExp("xsi[^\"]*\"[^\"]*\"", "gi");
138                         var s:String=e.target.data;
139                         var xml:XML = new XML(s.replace(xmlnsPattern, "").replace(xsiPattern, ""));
140                         var attribution:Object = {};
141                         for each (var ResourceSets:XML in xml.child("ResourceSets")) {
142                                 for each (var ResourceSet:XML in ResourceSets.child("ResourceSet")) {
143                                         for each (var Resources:XML in ResourceSet.child("Resources")) {
144                                                 for each (var ImageryMetadata:XML in Resources.child("ImageryMetadata")) {
145                                                         for each (var ImageryProvider:XML in ImageryMetadata.child("ImageryProvider")) {
146                                                                 var areas:Array=[];
147                                                                 for each (var CoverageArea:XML in ImageryProvider.child("CoverageArea")) {
148                                                                         areas.push([CoverageArea.ZoomMin,
149                                                                                     CoverageArea.ZoomMax,
150                                                                                     CoverageArea.BoundingBox.SouthLatitude,
151                                                                                     CoverageArea.BoundingBox.WestLongitude,
152                                                                                     CoverageArea.BoundingBox.NorthLatitude,
153                                                                                     CoverageArea.BoundingBox.EastLongitude]);
154                                                                 }
155                                                                 attribution[ImageryProvider.Attribution]=areas;
156                                                         }
157                                                 }
158                                         }
159                                 }
160                         }
161                         bg.attribution=attribution;
162                         setAttribution();
163                 }
164
165                 public function setBackground(bg:Object):void {
166                         // set background
167                         selected=bg;
168                         if (bg.url=='yahoo') { _map.setBackground({url:''}); _yahoo.show(); }
169                                         else { _map.setBackground(bg      ); _yahoo.hide(); }
170                         // update attribution and logo
171                         _overlay.visible=bg.attribution || bg.logo || bg.terms_url;
172                         setLogo(); setAttribution(); setTerms();
173                         // save as SharedObject for next time
174                         var obj:SharedObject = SharedObject.getLocal("user_state");
175                         obj.setProperty('background_url' ,String(bg.url));
176                         obj.setProperty('background_name',String(bg.name));
177                         obj.flush();
178                 }
179                 
180                 private function findBackgroundWithName(name:String):Object {
181                         for each (var bg:Object in collection) {
182                                 if (bg.name==name) { return bg; }
183                         }
184                         return { url:'' };
185                 }
186
187                 private function moveHandler(event:MapEvent):void {
188                         setAttribution();
189                 }
190                 private function setAttribution():void {
191                         var tf:TextField=TextField(_overlay.getChildAt(0));
192                         tf.text='';
193                         if (!selected.attribution) return;
194                         var attr:Array=[];
195                         for (var provider:String in selected.attribution) {
196                                 for each (var bounds:Array in selected.attribution[provider]) {
197                                         if (_map.scale>=bounds[0] && _map.scale<=bounds[1] &&
198                                           ((_map.edge_l>bounds[3] && _map.edge_l<bounds[5]) ||
199                                            (_map.edge_r>bounds[3] && _map.edge_r<bounds[5]) ||
200                                    (_map.edge_l<bounds[3] && _map.edge_r>bounds[5])) &&
201                                           ((_map.edge_b>bounds[2] && _map.edge_b<bounds[4]) ||
202                                            (_map.edge_t>bounds[2] && _map.edge_t<bounds[4]) ||
203                                            (_map.edge_b<bounds[2] && _map.edge_t>bounds[4]))) {
204                                                 attr.push(provider);
205                                         }
206                                 }
207                         }
208                         if (attr.length==0) return;
209                         tf.text="Background "+attr.join(", ");
210                         tf.x=_map.mapwidth  - 5 - tf.textWidth;
211                         tf.y=_map.mapheight - 5 - tf.textHeight;
212                         dispatchEvent(new MapEvent(MapEvent.BUMP, { y: tf.textHeight }));       // don't let the toolbox obscure it
213                 }
214                 private function resizeHandler(event:MapEvent):void {
215                         if (!selected.logoData) return;
216                         _overlay.getChildAt(2).y=event.params.height - 5 - selected.logoHeight - (selected.terms_url ? 10 : 0);
217                 }
218                 private function setLogo():void {
219                         while (_overlay.numChildren>2) { _overlay.removeChildAt(2); }
220                         if (!selected.logoData) return;
221                         var logo:Sprite=new Sprite();
222                         logo.addChild(new Bitmap(selected.logoData));
223                         logo.x=5; logo.y=_map.mapheight - 5 - selected.logoHeight - (selected.terms_url ? 10 : 0);
224                         if (selected.logo_url) { logo.buttonMode=true; logo.addEventListener(MouseEvent.CLICK, launchLogoLink, false, 0, true); }
225                         _overlay.addChild(logo);
226                 }
227                 private function launchLogoLink(e:Event):void {
228                         if (!selected.logo_url) return;
229                         navigateToURL(new URLRequest(selected.logo_url), '_blank');
230                 }
231                 private function setTerms():void {
232                         var terms:TextField=TextField(_overlay.getChildAt(1));
233                         if (!selected.terms_url) { terms.text=''; return; }
234                         terms.text="Background terms of use";
235                         terms.x=5; terms.y=_map.mapheight - 15;
236                         terms.addEventListener(MouseEvent.CLICK, launchTermsLink, false, 0, true);
237                 }
238                 private function launchTermsLink(e:Event):void {
239                         if (!selected.terms_url) return;
240                         navigateToURL(new URLRequest(selected.terms_url), '_blank');
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 }