adapt for larger fonts on Linux; fix indexing with renumbered nodes
[rails.git] / app / views / browse / start.rjs
1 page.replace_html :sidebar_title, 'Data'
2 page.replace_html :sidebar_content, :partial => 'start'
3 page << <<EOJ
4   var browseBoxControl;
5   var browseActive;
6   var browseMode = "auto";
7   var browseBounds;
8   var browseFeatureList;
9   var browseActiveFeature;
10   var browseDataLayer;
11   var browseSelectControl;
12   var browseObjectList;
13
14   OpenLayers.Feature.Vector.style['default'].strokeWidth = 3;
15   OpenLayers.Feature.Vector.style['default'].cursor = "pointer";
16     
17   function startBrowse() {
18     browseActive = true;
19
20     openSidebar({ onclose: stopBrowse });
21
22     var vectors = new OpenLayers.Layer.Vector();
23     
24     browseBoxControl = new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.RegularPolygon, { 
25       handlerOptions: {
26         sides: 4,
27         snapAngle: 90,
28         irregular: true,
29         persist: true,
30         callbacks: { done: endDrag }
31       }
32     });
33     map.addControl(browseBoxControl);
34
35     map.events.register("moveend", map, showData);
36     map.events.triggerEvent("moveend");
37   }
38
39   function showData() {
40     if (browseMode == "auto") {
41       if (map.getZoom() >= 15) {
42           useMap();
43       } else {
44           setStatus("Zoom in or select an area of the map to view");
45       }    
46     }
47   }
48
49   function stopBrowse() {
50     if (browseActive) {
51       browseActive = false;
52
53       if (browseSelectControl) {   
54         browseSelectControl.destroy();  
55         browseSelectControl = null;
56       } 
57
58       if (browseBoxControl) {
59         browseBoxControl.destroy();
60         browseBoxControl = null;
61       }         
62
63       if (browseActiveFeature) {
64         browseActiveFeature.destroy(); 
65         browseActiveFeature = null; 
66       }
67
68       if (browseDataLayer) {
69         browseDataLayer.destroy();
70         browseDataLayer = null; 
71       } 
72
73       map.dataLayer.setVisibility(false);
74       map.events.unregister("moveend", map, showData);
75     }    
76   }
77
78   function startDrag() {
79     $("browse_select_box").innerHTML='Drag a box on the map to select an area';
80
81     browseBoxControl.activate();
82
83     return false;
84   };
85
86   $("browse_select_box").onclick = startDrag;
87
88   function useMap() {
89     var bounds = map.getExtent();
90     var projected = bounds.clone().transform(map.getProjectionObject(), epsg4326);
91
92     if (!browseBounds || !browseBounds.containsBounds(projected)) {
93       var center = bounds.getCenterLonLat();
94       var tileWidth = bounds.getWidth() * 1.2;
95       var tileHeight = bounds.getHeight() * 1.2;
96       var tileBounds = new OpenLayers.Bounds(center.lon - (tileWidth / 2),
97                                              center.lat - (tileHeight / 2),
98                                              center.lon + (tileWidth / 2),
99                                              center.lat + (tileHeight / 2));
100
101       browseBounds = tileBounds;
102       getData(tileBounds);
103
104       browseMode = "auto";
105
106       $("browse_select_view").style.display = "none";
107     }
108
109     return false;
110   }
111
112   $("browse_select_view").onclick = useMap;
113
114   function endDrag(bbox) {
115     var bounds = bbox.getBounds();
116     var projected = bounds.clone().transform(map.getProjectionObject(), epsg4326);
117
118     browseBoxControl.deactivate();
119     browseBounds = projected;
120     getData(bounds);
121
122     browseMode = "manual";  
123
124     $("browse_select_box").innerHTML = "Manually select a different area";
125     $("browse_select_view").style.display = "inline";
126   }
127
128   function displayFeatureWarning() {
129     clearStatus();
130
131     var div = document.createElement("div");
132
133     var p = document.createElement("p");
134     p.appendChild(document.createTextNode("You have loaded an area which contains " + browseFeatureList.length + " features. In general, some browsers may not cope well with displaying this quantity of data. Generally, browsers work best at displaying less than 100 features at a time: doing anything else may make your browser slow/unresponsive. If you are sure you want to display this data, you may do so by clicking the button below."));
135     div.appendChild(p);
136
137     var input = document.createElement("input");
138     input.type = "submit";
139     input.value = "Load Data";
140     input.onclick = loadFeatureList;
141     div.appendChild(input); 
142
143     $("browse_content").innerHTML = "";
144     $("browse_content").appendChild(div);
145   }
146
147   function loadFeatureList() {
148     browseDataLayer.addFeatures(browseFeatureList);
149     browseDataLayer.events.triggerEvent("loadend");
150
151     browseFeatureList = []; 
152
153     return false;
154   }    
155
156   function customDataLoader(request) {
157     if (browseActive) {
158       var doc = request.responseXML;
159
160       if (!doc || !doc.documentElement) {
161         doc = request.responseText;
162       }
163
164       var options = {};
165
166       OpenLayers.Util.extend(options, this.formatOptions);
167
168       if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
169         options.externalProjection = this.projection;
170         options.internalProjection = this.map.getProjectionObject();
171       }    
172
173       var gml = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
174
175       browseFeatureList = gml.read(doc);
176
177       if (!this.maxFeatures || browseFeatureList.length <= this.maxFeatures) {
178         loadFeatureList();
179       } else {
180         displayFeatureWarning();
181       }
182     }
183   }
184
185   function getData(bounds) {
186     var projected = bounds.clone().transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
187     var size = projected.getWidth() * projected.getHeight();
188
189     if (size > 0.25) {
190       setStatus("Unable to load: Bounding box size of " + size + " is too large (must be smaller than 0.25)");
191     } else {
192       loadGML("/api/#{API_VERSION}/map?bbox=" + projected.toBBOX());
193     }
194   }
195
196   function loadGML(url) {
197     setStatus("Loading...");
198     $("browse_content").innerHTML = "";
199
200     if (!browseDataLayer) {
201       var style = new OpenLayers.Style();
202
203       style.addRules([new OpenLayers.Rule({
204         symbolizer: {
205           Polygon: { fillColor: '#ff0000', strokeColor: '#ff0000' },
206           Line: { fillColor: '#ffff00', strokeColor: '#000000', strokeOpacity: '0.4' },
207           Point: { fillColor: '#00ff00', strokeColor: '#00ff00' }
208         }
209       })]);
210
211       browseDataLayer = new OpenLayers.Layer.GML("Data", url, {
212         format: OpenLayers.Format.OSM,
213         formatOptions: {
214           checkTags: true, 
215           interestingTagsExclude: ['source','source_ref','source:ref','history','attribution','created_by','tiger:county','tiger:tlid','tiger:upload_uuid']
216         },
217         maxFeatures: 100,
218         requestSuccess: customDataLoader,
219         displayInLayerSwitcher: false,
220         styleMap: new OpenLayers.StyleMap({
221           'default': style,
222           'select': { strokeColor: '#0000ff', strokeWidth: 8 }
223         })
224       });
225       browseDataLayer.events.register("loadend", browseDataLayer, dataLoaded );
226       map.addLayer(browseDataLayer);
227             
228       browseSelectControl = new OpenLayers.Control.SelectFeature(browseDataLayer, { onSelect: onFeatureSelect });
229       browseSelectControl.handlers.feature.stopDown = false;
230       browseSelectControl.handlers.feature.stopUp = false;
231       map.addControl(browseSelectControl);
232       browseSelectControl.activate();
233     } else {
234       browseDataLayer.setUrl(url);
235     }
236
237     browseActiveFeature = null;
238   }
239
240   function dataLoaded() {
241     if (browseActive) {
242       clearStatus();
243         
244       browseObjectList = document.createElement("div")
245
246       var heading = document.createElement("p");
247       heading.className = "browse_heading";
248       heading.appendChild(document.createTextNode("Object list"));
249       browseObjectList.appendChild(heading);
250
251       var list = document.createElement("ul");
252
253       for (var i = 0; i < this.features.length; i++) {
254         var feature = this.features[i]; 
255             
256         // Type, for linking
257         var type = featureType(feature);
258         var typeName = ucFirst(type);
259         var li = document.createElement("li");
260         li.appendChild(document.createTextNode(typeName + " "));
261             
262         // Link, for viewing in the tab
263         var link = document.createElement("a");
264         link.href =  "/browse/" + type + "/" + feature.osm_id; 
265         var name = feature.attributes.name || feature.osm_id;
266         link.appendChild(document.createTextNode(name));
267         link.feature = feature;
268         link.onclick = OpenLayers.Function.bind(viewFeatureLink, link);   
269         li.appendChild(link);
270
271         list.appendChild(li);
272       }
273
274       browseObjectList.appendChild(list)
275
276       var link = document.createElement("a");
277       link.href = this.url;
278       link.appendChild(document.createTextNode("API"));
279       browseObjectList.appendChild(link);
280
281       $("browse_content").innerHTML = "";
282       $("browse_content").appendChild(browseObjectList); 
283     }
284   }
285     
286   function viewFeatureLink() {
287     var layer = this.feature.layer;
288
289     for (var i = 0; i < layer.selectedFeatures.length; i++) {
290       var f = layer.selectedFeatures[i]; 
291       layer.drawFeature(f, layer.styleMap.createSymbolizer(f, "default"));
292     }
293
294     onFeatureSelect(this.feature);
295
296     if (browseMode != "auto") {
297       map.setCenter(this.feature.geometry.getBounds().getCenterLonLat()); 
298     }
299
300     return false;
301   }
302     
303   function loadObjectList() {
304     $("browse_content").innerHTML="";
305     $("browse_content").appendChild(browseObjectList);
306
307     return false;
308   }
309       
310   function onFeatureSelect(feature) {
311     // Unselect previously selected feature
312     if (browseActiveFeature) {
313       browseActiveFeature.layer.drawFeature(
314         browseActiveFeature, 
315         browseActiveFeature.layer.styleMap.createSymbolizer(browseActiveFeature, "default")
316       );
317     }
318
319     // Redraw in selected style
320     feature.layer.drawFeature(
321       feature, feature.layer.styleMap.createSymbolizer(feature, "select")
322     );
323
324     // If the current object is the list, don't innerHTML="", since that could clear it.
325     if ($("browse_content").firstChild == browseObjectList) { 
326       $("browse_content").removeChild(browseObjectList);
327     } else { 
328       $("browse_content").innerHTML = "";
329     }   
330         
331     // Create a link back to the object list
332     var div = document.createElement("div");
333     div.style.textAlign = "center";
334     div.style.marginBottom = "20px";
335     $("browse_content").appendChild(div);
336     var link = document.createElement("a");
337     link.href = "#";
338     link.onclick = loadObjectList;
339     link.appendChild(document.createTextNode("Display object list"));
340     div.appendChild(link);
341
342     var table = document.createElement("table");
343     table.width = "100%";
344     table.className = "browse_heading";
345     $("browse_content").appendChild(table);
346
347     var tr = document.createElement("tr");
348     table.appendChild(tr);
349
350     var heading = document.createElement("td");
351     heading.appendChild(document.createTextNode(featureName(feature)));
352     tr.appendChild(heading);
353
354     var td = document.createElement("td");
355     td.align = "right";
356     tr.appendChild(td);
357
358     var type = featureType(feature);
359     var link = document.createElement("a");   
360     link.href = "/browse/" + type + "/" + feature.osm_id;
361     link.appendChild(document.createTextNode("Details"));
362     td.appendChild(link);
363
364     var div = document.createElement("div");
365     div.className = "browse_details";
366
367     $("browse_content").appendChild(div);
368
369     // Now the list of attributes
370     var ul = document.createElement("ul");
371     for (var key in feature.attributes) {
372       var li = document.createElement("li");
373       var b = document.createElement("b");
374       b.appendChild(document.createTextNode(key));
375       li.appendChild(b);
376       li.appendChild(document.createTextNode(": " + feature.attributes[key]));
377       ul.appendChild(li);
378     }
379         
380     div.appendChild(ul);
381         
382     var link = document.createElement("a");   
383     link.href =  "/browse/" + type + "/" + feature.osm_id + "/history";
384     link.appendChild(document.createTextNode("Show history"));
385     link.onclick = OpenLayers.Function.bind(loadHistory, {
386       type: type, feature: feature, link: link
387     });
388         
389     div.appendChild(link);
390
391     // Stash the currently drawn feature
392     browseActiveFeature = feature; 
393   }   
394
395   function loadHistory() {
396     this.link.href = "";
397     this.link.innerHTML = "Wait...";
398
399     new Ajax.Request("/api/#{API_VERSION}/" + this.type + "/" + this.feature.osm_id + "/history", {
400       onComplete: OpenLayers.Function.bind(displayHistory, this)
401     });
402
403     return false;
404   }
405
406   function displayHistory(request) {
407     if (browseActiveFeature.osm_id != this.feature.osm_id || $("browse_content").firstChild == browseObjectList)  { 
408         return false;
409     } 
410
411     this.link.parentNode.removeChild(this.link);
412
413     var doc = request.responseXML;
414
415     var table = document.createElement("table");
416     table.width = "100%";
417     table.className = "browse_heading";
418     $("browse_content").appendChild(table);
419
420     var tr = document.createElement("tr");
421     table.appendChild(tr);
422
423     var heading = document.createElement("td");
424     heading.appendChild(document.createTextNode("History for " + featureName(this.feature)));
425     tr.appendChild(heading);
426
427     var td = document.createElement("td");
428     td.align = "right";
429     tr.appendChild(td);
430
431     var link = document.createElement("a");   
432     link.href = "/browse/" + this.type + "/" + this.feature.osm_id + "/history";
433     link.appendChild(document.createTextNode("Details"));
434     td.appendChild(link);
435
436     var div = document.createElement("div");
437     div.className = "browse_details";
438
439     var nodes = doc.getElementsByTagName(this.type);
440     var history = document.createElement("ul");  
441     for (var i = nodes.length - 1; i >= 0; i--) {
442       var user = nodes[i].getAttribute("user") || "private user";
443       var timestamp = nodes[i].getAttribute("timestamp");
444       var item = document.createElement("li");
445       item.appendChild(document.createTextNode("Edited by " + user + " at " + timestamp));
446       history.appendChild(item);
447     }
448     div.appendChild(history);
449
450     $("browse_content").appendChild(div); 
451   }
452
453   function featureType(feature) {
454     if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
455       return "node";
456     } else {
457       return "way";
458     }
459   }
460   
461   function featureName(feature) {
462     if (feature.attributes.name) {
463       return feature.attributes.name;
464     } else {
465       return ucFirst(featureType(feature)) + " " + feature.osm_id;
466     }
467   }
468
469   function setStatus(status) {
470     $("browse_status").innerHTML = status;
471     $("browse_status").style.display = "block";
472   }
473   
474   function clearStatus() {
475     $("browse_status").innerHTML = "";
476     $("browse_status").style.display = "none";
477   }
478
479   function ucFirst(str) {
480     return str.substr(0,1).toUpperCase() + str.substr(1,str.length);
481   }
482
483   startBrowse();
484 EOJ