Add floating imagery window
[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 net.systemeD.halcyon.FileBank;
7         import net.systemeD.halcyon.Map;
8         import net.systemeD.halcyon.MapEvent;
9         import net.systemeD.potlatch2.FunctionKeyManager;
10         import mx.collections.ArrayCollection;
11     import com.adobe.serialization.json.JSON;
12         import mx.core.FlexGlobals;
13
14         /*
15                 There's lots of further tidying we can do:
16                 - remove the backreferences to _map and send events instead
17                 but this will do for now and help remove the clutter from potlatch2.mxml.
18         */
19
20         public class Imagery extends EventDispatcher {
21
22         private static const GLOBAL_INSTANCE:Imagery = new Imagery();
23         public static function instance():Imagery { return GLOBAL_INSTANCE; }
24
25                 private static const INDEX_URL:String="http://osmlab.github.io/editor-imagery-index/imagery.json";
26
27                 public var collection:Array=[];
28                 private var _selected:Object={};
29
30                 /* Load catalogue file */
31
32                 public function init():void {
33                         // load imagery file
34                         var request:URLRequest = new URLRequest(INDEX_URL);
35                         var loader:URLLoader = new URLLoader();
36                         loader.addEventListener(Event.COMPLETE, onImageryIndexLoad);
37                         loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
38                         loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
39                         loader.load(request);
40                 }
41
42                 private function onImageryIndexLoad(event:Event):void {
43                         var result:String = String(event.target.data);
44                         collection = com.adobe.serialization.json.JSON.decode(result) as Array;
45
46                         // Has the user saved something? If so, create dummy object
47                         var saved:Object = {};
48                         var bg:Object;
49                         if (SharedObject.getLocal("user_state","/").data['background_url']!=undefined) {
50                                 saved={ url:  SharedObject.getLocal("user_state","/").data['background_url' ],
51                                                 name: SharedObject.getLocal("user_state","/").data['background_name'],
52                                                 userDefined: true,
53                                                 type: "tms",
54                                                 extent: { bbox: { min_lon: -180, max_lon: 180, min_lat: -90, max_lat: 90 } }}
55                         }
56
57                         var isSet:Boolean=false;
58             var backgroundSet:Boolean = false;
59                         collection.unshift({ name: "None", url: "" });
60
61                         // Is a set already chosen? (default to Bing if not)
62                         _selected=null;
63                         collection.forEach(function(bg:Object, index:int, array:Array):void {
64                                 if (saved.name && saved.name==bg.name) { _selected=bg; }
65                                 if (bg.id=='Bing') {
66                                         bg.url="http://ecn.t{switch:0,1,2,3}.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=587&mkt=en-gb&n=z";
67                                         bg.attribution={
68                                                 data_url: "http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/0,0?zl=1&mapVersion=v1&key=Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU&include=ImageryProviders&output=xml",
69                                                 logo: "bing_maps.png",
70                                                 url: "http://opengeodata.org/microsoft-imagery-details"
71                                         }
72                                 }
73                                 if (bg.id=='Bing' && !_selected) { _selected=bg; }
74                                 if (bg.attribution && bg.attribution.logo) {
75                                         // load the logo (pretty much Bing-only)
76                                         FileBank.getInstance().addFromFile(bg.attribution.logo, function (fb:FileBank, name:String):void {
77                                                 bg.logoData   = fb.getAsBitmapData(name);
78                                                 bg.logoWidth  = fb.getWidth(name);
79                                                 bg.logoHeight = fb.getHeight(name);
80                                                 dispatchEvent(new Event("refreshAttribution"));
81                                                 });
82                                 }
83                                 if (bg.attribution && bg.attribution.data_url) {
84                                         // load the attribution (pretty much Bing-only)
85                                 var urlloader:URLLoader = new URLLoader();
86                                         urlloader.addEventListener(Event.COMPLETE, function(e:Event):void { onAttributionLoad(e,bg); });
87                                         urlloader.addEventListener(IOErrorEvent.IO_ERROR, onError);
88                                         urlloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
89                                 urlloader.load(new URLRequest(bg.attribution.data_url));
90                                 }
91                         });
92                         if (saved.name && !_selected) { collection.push(saved); _selected=saved; }
93                         if (_selected) dispatchEvent(new CollectionEvent(CollectionEvent.SELECT, _selected));
94                         dispatchEvent(new Event("imageryLoaded"));
95                         dispatchEvent(new Event("collection_changed"));
96                 }
97                 
98                 private function onError(e:Event):void {
99                         // placeholder error routine so exception isn't thrown
100                 }
101                 
102                 public function onAttributionLoad(e:Event,bg: Object):void {
103                         // if we ever need to cope with non-Microsoft attribution, then this should look at bg.scheme
104             default xml namespace = Namespace("http://schemas.microsoft.com/search/local/ws/rest/v1");
105             var xml:XML = new XML(e.target.data);
106                         var providers:Object = {};
107             for each (var ImageryProvider:XML in xml..ImageryProvider) {
108                 var areas:Array=[];
109                 for each (var CoverageArea:XML in ImageryProvider.CoverageArea) {
110                     areas.push([CoverageArea.ZoomMin,
111                                 CoverageArea.ZoomMax,
112                                 CoverageArea.BoundingBox.SouthLatitude,
113                                 CoverageArea.BoundingBox.WestLongitude,
114                                 CoverageArea.BoundingBox.NorthLatitude,
115                                 CoverageArea.BoundingBox.EastLongitude]);
116                 }
117                 providers[ImageryProvider.Attribution]=areas;
118             }
119                         default xml namespace = new Namespace("");
120                         bg.attribution.providers=providers;
121                         dispatchEvent(new Event("refreshAttribution"));
122                 }
123
124                 public function get selected():Object { return _selected; }
125                 
126                 public function findBackgroundWithName(name:String):Object {
127                         for each (var bg:Object in collection) {
128                                 if (bg.name==name) { return bg; }
129                         }
130                         return { url:'' };
131                 }
132
133         [Bindable(event="collection_changed")]
134         public function getCollection():ArrayCollection {
135             return new ArrayCollection(collection);
136         }
137
138                 /* --------------------
139                    Imagery index parser */
140
141                 [Bindable(event="collection_changed")]
142                 public function getAvailableImagery(map:Map):ArrayCollection {
143                         var available:Array=[];
144                         for each (var bg:Object in collection) {
145                                 if (bg.extent && bg.extent.polygon) {
146                                         // check if in boundary polygon
147                                         var included:Boolean=false;
148                                         for each (var poly:Array in bg.extent.polygon) {
149                                                 if (pointInPolygon(map.centre_lon, map.centre_lat, poly)) { included=true; }
150                                         }
151                                         if (included) { available.push(bg); }
152                                 } else if (bg.extent && bg.extent.bbox && bg.extent.bbox.min_lon) {
153                                         // if there's a bbox, check the current viewport intersects it
154                                         if (((map.edge_l>bg.extent.bbox.min_lon && map.edge_l<bg.extent.bbox.max_lon) ||
155                                              (map.edge_r>bg.extent.bbox.min_lon && map.edge_r<bg.extent.bbox.max_lon) ||
156                                              (map.edge_l<bg.extent.bbox.min_lon && map.edge_r>bg.extent.bbox.max_lon)) &&
157                                             ((map.edge_b>bg.extent.bbox.min_lat && map.edge_b<bg.extent.bbox.max_lat) ||
158                                              (map.edge_t>bg.extent.bbox.min_lat && map.edge_t<bg.extent.bbox.max_lat) ||
159                                              (map.edge_b<bg.extent.bbox.min_lat && map.edge_t>bg.extent.bbox.max_lat))) {
160                                                 available.push(bg);
161                                         }
162                                 } else if (!bg.type || bg.type!='wms') {
163                                         // if there's no bbox (i.e. global set) and default is set, include it
164                                         if (bg.name=='None' || bg.default || bg.userDefined) { available.push(bg); }
165                                 }
166                         }
167                         available.sort(function(a:Object,b:Object):int {
168                                 if (a.name=='None') { return -1; }
169                                 else if (b.name=='None') { return 1; }
170                                 else if (a.name<b.name) { return -1; }
171                                 else if (a.name>b.name) { return 1; }
172                                 return 0;
173                         });
174                         return new ArrayCollection(available);
175                 }
176
177                 public function pointInPolygon(x:Number,y:Number,vertices:Array):Boolean {
178                         // http://muongames.com/2013/07/point-in-a-polygon-in-as3-theory-and-code/
179                         // Loop through vertices, check if point is left of each line.
180                         // If it is, check if it line intersects with horizontal ray from point p
181                         var n:int = vertices.length;
182                         var j:int;
183                         var v1:Array, v2:Array;
184                         var count:int;
185                         for (var i:int=0; i<n; i++) {
186                                 j = i+1 == n ? 0 : i + 1;
187                                 v1 = vertices[i];
188                                 v2 = vertices[j];
189                                 // does point lie to the left of the line?
190                                 if (isLeft(x,y,v1,v2)) {
191                                         if ((y > v1[1] && y <= v2[1]) || (y > v2[1] && y <= v1[1])) { count++; }
192                                 }
193                         }
194                         return (count % 2 == 1);
195                 }
196
197                 public function isLeft(x:Number, y:Number, v1:Array, v2:Array):Boolean {
198                         if (v1[0] == v2[0]) { return (x <= v1[0]); }
199                         var m:Number = (v2[1] - v1[1]) / (v2[0] - v1[0]);
200                         var x2:Number = (y - v1[1]) / m + v1[0];
201                         return (x <= x2);
202                 }
203
204
205         }
206         
207 }