1 package net.systemeD.potlatch2.collections {
4 import flash.display.*;
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;
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.
20 public class Imagery extends EventDispatcher {
22 private static const GLOBAL_INSTANCE:Imagery = new Imagery();
23 public static function instance():Imagery { return GLOBAL_INSTANCE; }
25 private static const INDEX_URL:String="http://osmlab.github.io/editor-imagery-index/imagery.json";
27 public var collection:Array=[];
28 private var _selected:Object={};
30 /* Load catalogue file */
32 public function init():void {
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);
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;
46 // Has the user saved something? If so, create dummy object
47 var saved: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'],
54 extent: { bbox: { min_lon: -180, max_lon: 180, min_lat: -90, max_lat: 90 } }}
57 var isSet:Boolean=false;
58 var backgroundSet:Boolean = false;
59 collection.unshift({ name: "None", url: "" });
61 // Is a set already chosen? (default to Bing if not)
63 collection.forEach(function(bg:Object, index:int, array:Array):void {
64 if (saved.name && saved.name==bg.name) { _selected=bg; }
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";
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"
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"));
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));
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"));
98 private function onError(e:Event):void {
99 // placeholder error routine so exception isn't thrown
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) {
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]);
117 providers[ImageryProvider.Attribution]=areas;
119 default xml namespace = new Namespace("");
120 bg.attribution.providers=providers;
121 dispatchEvent(new Event("refreshAttribution"));
124 public function get selected():Object { return _selected; }
126 public function findBackgroundWithName(name:String):Object {
127 for each (var bg:Object in collection) {
128 if (bg.name==name) { return bg; }
133 [Bindable(event="collection_changed")]
134 public function getCollection():ArrayCollection {
135 return new ArrayCollection(collection);
138 /* --------------------
139 Imagery index parser */
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; }
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))) {
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); }
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; }
174 return new ArrayCollection(available);
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;
183 var v1:Array, v2:Array;
185 for (var i:int=0; i<n; i++) {
186 j = i+1 == n ? 0 : i + 1;
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++; }
194 return (count % 2 == 1);
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];